Skip to content

Feature/751#757

Merged
gltanaka merged 5 commits intopromptdriven:mainfrom
vishalramvelu:feature/751
Apr 8, 2026
Merged

Feature/751#757
gltanaka merged 5 commits intopromptdriven:mainfrom
vishalramvelu:feature/751

Conversation

@vishalramvelu
Copy link
Copy Markdown
Contributor

Description

Adds a duplicate-run guard for expensive CLI entry points: sync, generate, and fix.
After a guarded command finishes through the normal CLI summary path, we persist a small record at .pdd/last_run.json (normalized argv tail after the program name, cwd, git rev-parse HEAD, subcommand, timestamp). Before the next run of the same subcommand, if argv, cwd, HEAD, and subcommand match the last record and the timestamp is inside a configurable window (default 15 minutes via PDD_DUPLICATE_WINDOW_MIN, or PDD_DUPLICATE_WINDOW_SEC for overrides), we treat it as a likely duplicate LLM run:
CI=1: print a warning to stderr and continue (no scripted CI break by default).
Interactive TTY (stdin and stdout TTY, not --quiet): warning plus Continue anyway? [y/N]; declining aborts the run.
Otherwise: non-interactive failure with click.UsageError unless bypassed.
Bypass: global --force or PDD_ALLOW_DUPLICATE_RUN=1. Disable entirely: PDD_DISABLE_DUPLICATE_GUARD=1.
Under pytest, the guard is off unless PDD_TEST_DUPLICATE_GUARD=1, so the existing suite is unaffected.
Wiring: check_duplicate_before_subcommand runs from the root CLI callback (with stream restore on block/abort); record_after_guarded_command runs from process_commands after a successful guarded flow.

Testing

uv run pytest tests/test_duplicate_cli_guard.py -v — covers guard on/off under pytest, CI warn-only path, non-interactive block, TTY prompt (yes/no), --force / PDD_ALLOW_DUPLICATE_RUN, different git HEAD, time window expiry, and post-run recording behavior.

Run two same commands back to back (manual)

Checklist

  • CI / local pytest green for the suites above
  • Relevant Prompts changed and submitted PR to PDD_CAP

Fixes #751

Copy link
Copy Markdown
Contributor

@gltanaka gltanaka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change Request

Blocking Issues

1. CI gate (validate-arch-includes) is high-risk with no proof it passes today

The PR adds pdd validate-arch-includes to .github/workflows/unit-tests.yml before unit tests. The root architecture.json is 276KB with dozens of entries. If any entry's dependencies disagree with its prompt's <include> tags — which is likely given normal development drift — CI breaks for everyone on merge. Please either:

  • Demonstrate this command passes on main today, or
  • Ship it as a warning-only step (non-blocking) initially, or
  • Remove it from CI and add it in a follow-up once validated

2. Duplicate guard checks git HEAD instead of file content — blocks legitimate re-runs

The most common reason to re-run pdd sync is after editing a prompt file. But editing a prompt doesn't change git HEAD until you commit. So the guard blocks the typical workflow:

pdd sync foo
vim prompts/foo.prompt   # edit
pdd sync foo             # BLOCKED — same argv, same HEAD

Users will learn to always pass --force, making the guard dead weight. Consider fingerprinting the mtimes or content hashes of relevant prompt files instead of (or in addition to) git HEAD.

3. _last_run_path uses cwd instead of project root

def _last_run_path(cwd: str) -> Path:
    return Path(cwd) / ".pdd" / _LAST_RUN_FILENAME

If a user runs from a subdirectory, the record is written there. The next run from project root won't find it. This should use find_project_root() (which this PR already moves to architecture_registry.py) to ensure the guard always reads/writes the same file.

4. Broad except Exception in auto-deps integration hides bugs

In auto_deps_main.py:

except Exception as arch_exc:
    rprint(f"[yellow]Warning: Could not update architecture.json...")

Every possible bug (KeyError, TypeError, AttributeError) in the new 177-line auto_deps_architecture.py becomes a silently ignored yellow warning. Please catch specific expected exceptions (OSError, json.JSONDecodeError) and let real bugs propagate.

Non-blocking Issues (address or explain)

5. Architecture.json reformatting riskmerge_auto_deps_includes_into_architecture re-serializes the entire file with json.dumps(arch_data, indent=2). On the 276KB root architecture.json, this can produce a massive diff if the existing formatting differs. Consider writing back only the modified entry or preserving the original formatting.

6. Cross-module import of private functionauto_deps_architecture.py imports _resolve_architecture_prompt_path from architecture_include_validation.py. The underscore prefix means private. Either rename it to make it public or extract it to a shared location.

7. No way to suppress validation warnings without --quiet — The new architecture/include warnings fire on every sync. During normal development where drift is expected, this is noisy. Consider a dedicated flag (e.g., --no-arch-warnings) or only emitting these in --verbose mode.

8. Unrelated prompt change — The BUG_STEP_TIMEOUTS addition to agentic_bug_orchestrator_python.prompt is unrelated to any feature in this PR. Consider splitting it into its own commit at minimum.

@gltanaka
Copy link
Copy Markdown
Contributor

gltanaka commented Apr 6, 2026

target 4/10

@vishalramvelu
Copy link
Copy Markdown
Contributor Author

Change Request

Blocking Issues

1. CI gate (validate-arch-includes) is high-risk with no proof it passes today

The PR adds pdd validate-arch-includes to .github/workflows/unit-tests.yml before unit tests. The root architecture.json is 276KB with dozens of entries. If any entry's dependencies disagree with its prompt's <include> tags — which is likely given normal development drift — CI breaks for everyone on merge. Please either:

  • Demonstrate this command passes on main today, or
  • Ship it as a warning-only step (non-blocking) initially, or
  • Remove it from CI and add it in a follow-up once validated

2. Duplicate guard checks git HEAD instead of file content — blocks legitimate re-runs

The most common reason to re-run pdd sync is after editing a prompt file. But editing a prompt doesn't change git HEAD until you commit. So the guard blocks the typical workflow:

pdd sync foo
vim prompts/foo.prompt   # edit
pdd sync foo             # BLOCKED — same argv, same HEAD

Users will learn to always pass --force, making the guard dead weight. Consider fingerprinting the mtimes or content hashes of relevant prompt files instead of (or in addition to) git HEAD.

3. _last_run_path uses cwd instead of project root

def _last_run_path(cwd: str) -> Path:
    return Path(cwd) / ".pdd" / _LAST_RUN_FILENAME

If a user runs from a subdirectory, the record is written there. The next run from project root won't find it. This should use find_project_root() (which this PR already moves to architecture_registry.py) to ensure the guard always reads/writes the same file.

4. Broad except Exception in auto-deps integration hides bugs

In auto_deps_main.py:

except Exception as arch_exc:
    rprint(f"[yellow]Warning: Could not update architecture.json...")

Every possible bug (KeyError, TypeError, AttributeError) in the new 177-line auto_deps_architecture.py becomes a silently ignored yellow warning. Please catch specific expected exceptions (OSError, json.JSONDecodeError) and let real bugs propagate.

Non-blocking Issues (address or explain)

5. Architecture.json reformatting riskmerge_auto_deps_includes_into_architecture re-serializes the entire file with json.dumps(arch_data, indent=2). On the 276KB root architecture.json, this can produce a massive diff if the existing formatting differs. Consider writing back only the modified entry or preserving the original formatting.

6. Cross-module import of private functionauto_deps_architecture.py imports _resolve_architecture_prompt_path from architecture_include_validation.py. The underscore prefix means private. Either rename it to make it public or extract it to a shared location.

7. No way to suppress validation warnings without --quiet — The new architecture/include warnings fire on every sync. During normal development where drift is expected, this is noisy. Consider a dedicated flag (e.g., --no-arch-warnings) or only emitting these in --verbose mode.

8. Unrelated prompt change — The BUG_STEP_TIMEOUTS addition to agentic_bug_orchestrator_python.prompt is unrelated to any feature in this PR. Consider splitting it into its own commit at minimum.

Ok thanks for the review. I have made these changes.

  • CI validate-arch-includes (blocking)
    Shipped as warning-only, non-blocking. The workflow now runs pdd validate-arch-includes and, on non-zero exit, emits a GitHub warning where the step always exits 0 so the job does not fail.

  • Duplicate guard vs HEAD / prompt edits (blocking)
    The guard no longer treats HEAD alone as the fingerprint. It uses SHA256(HEAD + git status --porcelain) so uncommitted prompt edits change the fingerprint and a second pdd sync with the same argv is not blocked. Legacy last_run.json records without fingerprint still fall back to HEAD-only for compatibility.

  • _last_run_path / cwd (blocking)
    State is stored under find_project_root() (same discovery as .pddrc / .git), with project_root in the record and comparison. Not raw cwd from a subdirectory.

  • Broad except Exception in auto-deps (blocking)
    The architecture merge path now catches only OSError, json.JSONDecodeError, and ValueError while other exceptions propagate.

  • architecture.json reformatting (non-blocking)
    Updates are specific. Only the dependencies field for the affected entry is patched in the file text, with minimal append when auto-deps only adds new dependency strings. There is no full-file rewrite on success. If a safe in-place patch or write fails, we skip the write (no last-resort json.dumps of the whole file) and return updated: False with a short message.

  • Private import (non-blocking)
    Exposed resolve_architecture_prompt_path as the public API in architecture_include_validation.py. auto_deps_architecture.py imports that instead of _resolve_architecture_prompt_path.

  • Validation noise (non-blocking)
    Architecture / include warnings are shown only with --verbose (and still suppressed with --quiet). No separate --no-arch-warnings flag; default sync stays quiet unless the user opts into verbose.

  • Bug_step_timeout (non-blocking)
    I kept this because it was needed for passing a CI test but I can remove it and split into another PR if needed.

@gltanaka
Copy link
Copy Markdown
Contributor

gltanaka commented Apr 8, 2026

E2E Validation Results

Cherry-picked all 5 commits onto main and ran manual end-to-end testing beyond the unit test suite. All review feedback items verified as addressed.

Unit Tests

  • 45/45 pass (test_duplicate_cli_guard, test_architecture_include_validation, test_auto_deps_architecture, test_sync_graph_order_consistency)

Manual E2E Tests (all pass)

# Test Result
1 validate-arch-includes CLI on real repo Runs, detects 153 pre-existing mismatches, exits 1
2 Duplicate guard blocks identical re-run Blocked with clear error message
3 --force bypasses guard Proceeds normally
4 PDD_ALLOW_DUPLICATE_RUN=1 bypasses guard Proceeds normally
5 PDD_DISABLE_DUPLICATE_GUARD=1 disables guard entirely No warning, no block
6 Editing a prompt file then re-running (fingerprint change) Not blocked — fingerprint correctly changes via git status --porcelain
7 CI mode (CI=1) Warns to stderr, continues execution (non-blocking)
8 Interactive TTY: user answers "n" Aborts, exit 1
9 Interactive TTY: user answers "y" Proceeds to sync, exit 0
10 Auto-deps architecture.json surgical update (real files) Correctly patches only dependencies field, rest of JSON byte-stable
11 Auto-deps idempotency (run merge twice) Second run is noop
12 Subdirectory find_project_root (run from pdd/core/) Correctly resolves project root, finds same last_run.json
13 Cross-subcommand isolation (generate record doesn't block sync) Different subcommand/argv does not match
14 validate-arch-includes on clean test project "No mismatches found", exit 0
15 validate-arch-includes detects introduced mismatch Correct warning, exit 1
16 Time window expiry (backdated 20 min > 15 min default) Guard allows the run
17 --verbose shows arch warnings during sync, default mode stays quiet Correct

Gaps Filed as Issues

All three are follow-up items, not merge blockers.

Copy link
Copy Markdown
Contributor

@gltanaka gltanaka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All blocking review items addressed and verified with 17 manual E2E tests. Follow-up gaps filed as #767, #768, #769.

@gltanaka gltanaka merged commit b4e259c into promptdriven:main Apr 8, 2026
1 check passed
Serhan-Asad pushed a commit that referenced this pull request Apr 15, 2026
…ommands

Closes #768 — these LLM-heavy subcommands were not covered by the
duplicate-run guard added in PR #757, risking wasted cost on accidental
re-runs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Duplicate-run guard: warn (or confirm) when re-running the same expensive PDD command with unchanged inputs

2 participants