Skip to content

feat: Cursor harness support (2.5.63) - #661

Merged
apackeer merged 6 commits into
v2from
feature/cursor-harness
Aug 11, 2026
Merged

feat: Cursor harness support (2.5.63)#661
apackeer merged 6 commits into
v2from
feature/cursor-harness

Conversation

@apackeer

Copy link
Copy Markdown
Contributor

Summary

Adds the sixth harness: Cursor (dist/cursor/), serving the Cursor IDE and the Cursor CLI (agent) from one tree. Ships as 2.5.14 with the CHANGELOG entry, new guide chapter (docs/guide/harnesses/cursor.md), and full roster/docs sweep.

Every behavioral claim was live-verified against cursor-agent 2026.07.23 on Linux (spike corpus + probes; fixture payloads captured field-verbatim into tests/fixtures/cursor-hook-payloads/).

Design

Cursor is the most "native" port so far: no emit.ts - the standard core projection is consumed directly.

  • .cursor/ carries the engine plus every native surface: the orchestrator + generated stage runners in skills/ (invoked as /aidlc, args forwarded inline), the 14 personas in agents/ as live native subagents (the task tool targets them by name - no emitted twins), the method rule in rules/aidlc.mdc, hooks.json, and cli.json (pre-approves Shell(bun) only).
  • Tier column ships all-null (cursor: { model: null } for every tier): Cursor model availability is plan-dependent (Free accounts reject every named model with rc 0), so a pinned id would hard-fail lower-plan installs. Agents inherit the session model.
  • Method include: Cursor rules do not expand @-imports (live-verified), so rules/aidlc.mdc (alwaysApply) carries a read instruction naming the method files, and the sessionStart hook injects live workflow context. /aidlc space <name> re-points the rule in place (new .cursor arm in aidlc-includes.ts).
  • Hook adapter (aidlc-cursor-adapter.ts, the one authored code file) normalizes Cursor's camelCase events into the 13 byte-shared core hooks:
    • PreToolUse guard blocks convert from core exit-2 + stderr to Cursor's {"permission":"deny","agent_message"} channel (live-verified to block and relay).
    • Stop cannot block on Cursor: a core decision: block becomes an advisory followup_message nudge (the opencode posture).
    • Shell maps to Bash; Cursor's first-class Delete tool (unique to this harness) is presented to the reviewer-scope guard as a path-shaped write so a unit-scoped reviewer cannot delete a sibling unit's artifacts - the audit path keeps the real name so a deletion is never logged as a write.
    • Subagent identity is reconstructed via a Task-spawn ledger (Cursor emits no per-subagent identity; subagentStart/Stop never fire on the CLI). Nested Task delegation from a delegated conversation is denied without clobbering the parent's ledger entry.
    • Resumed conversations get the P8 intent-rebind offer through beforeSubmitPrompt's blocking user_message (Cursor's sessionStart carries no resume discriminator); the core session-start hook gains a rebind_check probe mode that emits no session events. Interactive-only: agent -p never fires beforeSubmitPrompt.
  • Plugins: kind: "cursor" projects Cursor's flat camelCase hooks.sessionStart[].command schema with the required version field - Cursor silently delivers zero hook events for a hooks.json without it (probe-verified) - and compose.ts falls back to its own parent directory for the plugin root (Cursor sets no env var).
  • Release binaries: cursor joins RUNTIME_DISTRIBUTIONS; aidlc adapter cursor <target> routes through the packaged adapter; build gates cover both.

Documented constraints

  • Headless approval gates refuse by design: the human-presence mint rides beforeSubmitPrompt, which only fires interactively, so agent -p records no HUMAN_TURN and a gated stage refuses - the presence guard working as intended, documented in the guide + shipped AGENTS.md rather than worked around. Headless suits the read-only utilities and autonomous Construction.
  • Scripting trap: the Cursor CLI exits 0 on every outcome including auth/plan errors; the e2e driver and docs assert on output text, never rc.
  • Bedrock BYOK on Cursor is IDE-only (static keys on Pro, IAM role on Teams) and is documented doc-verified, not live-verified.

Tests

  • t250-cursor-packaging (9): dist drift guard, core byte-parity, .mdc-only rules dir, camelCase hooks wiring with adapter-arm cross-check, no-model-pin agent frontmatter, cli.json shape, foreign-engine-dir sweep, doctor recognition, install-idiom docs pin.
  • t251-cursor-adapter (15): live-captured payload corpus through the adapter - context re-key, guard deny conversion, identity-ledger spawn/attribute/clear, nested-delegation deny, rebind offer + consumption + quiet path, Delete reviewer-scoping (sibling denied, own unit allowed) with the state-transition guard keeping the real tool name, audit/mint/session-end rows, advisory stop, malformed-stdin fail-open.
  • t-run-cursor-status.serial e2e (gate AIDLC_CURSOR_RUN_LIVE=1): /aidlc --status on the shipped tree via agent -p - ran live green on Linux.
  • Extended: harness-matrix (+cursor-rule/cursor-hooks capability arms), t220 tier pin, t221 registration case, t157 include branch, t239 roster, t-active-space-includes repoint cases, t188 plugin schema + real compose run, t228/t230/t238 adapter export/dispatcher route/binary gates, coverage registry.

Gate evidence

  • smoke+unit: 179 files, 0 failures (final re-gate stamp 2026-07-26T21-54-57Z).
  • integration: full tier run had 5 reds, all attributed and none in this branch's logic: t89 (13a) + t66 (2a) reproduce on clean v2; t72/t176 live-SDK latency flakes and t163 parallel-contention flake, all green solo; t145's one red was a stray /tmp/package.json module-resolution trap, green after clearing.
  • bun scripts/package.ts --check clean on all six trees + plugin projections; bun run typecheck clean.
  • Live probes on the shipped dist: --status (e2e), --version, --doctor 39/39 pass.

Notes for reviewers

  • Version slot: 2.5.14 assumes the open 2.5.12/2.5.13 claims land first; will re-bump on rebase if merge order differs.
  • This branch textually overlaps the in-flight copilot-harness branch on the shared registration files (aidlc-tiers.ts, manifest-types.ts, harness-matrix.ts, t239 roster); second-to-merge rebases.

@leandrodamascena leandrodamascena left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The Cursor harness is well structured and covers the nominal path, but several reproducible issues could break real workflows or overwrite existing project configuration.

1. High: The global subagent ledger conflates concurrent conversations

Location: harness/cursor/hooks/aidlc-cursor-adapter.ts:135-175,287-300

The adapter stores only one {agent, parent} entry per project. While conversation A has an active subagent, calls from conversation B have a different conversation_id and are incorrectly attributed to the subagent from A.

I reproduced this by starting a reviewer in A and invoking Task from B. B was denied as nested delegation even though it was an independent parent conversation. Reads and writes from B may also receive the reviewer restrictions from A. The ledger should track entries by parent conversation or Task ID rather than assuming every conversation different from the recorded parent is its subagent.

2. High: Multi-root workspaces can run hooks against the wrong repository

Location: harness/cursor/hooks/aidlc-cursor-adapter.ts:73-82

The adapter selects workspace_roots[0] before process.cwd(). Cursor explicitly supports multiple workspace roots, and the first root may not own the .cursor/hooks.json that launched the hook.

I reproduced this with [repoA, repoB] while running the adapter from repoB, where the active workflow existed. No session context was produced when repoB was second; reversing the array made it work. This also redirects audit, sensors, reviewer-scope enforcement, and runtime compilation. The adapter should prefer the project-hook cwd or resolve the root that owns the executing adapter.

3. High: Installation overwrites existing Cursor configuration

Locations: README.md:247-251, docs/guide/harnesses/cursor.md:50-54

The documented commands overwrite existing .cursor/hooks.json, .cursor/cli.json, and AGENTS.md. I reproduced this with existing content in all three files; none was preserved after applying the documented copy operations. Overwriting hooks.json may silently remove existing security or auditing hooks. The installation flow needs explicit structural merging or an installer that refuses unresolved collisions.

4. Medium: Failed Tasks leave stale subagent attribution

Locations: harness/cursor/hooks.json:13-19, harness/cursor/hooks/aidlc-cursor-adapter.ts:330-337

The ledger is created during preToolUse Task but cleared only by successful postToolUse Task. Cursor provides postToolUseFailure for failed, denied, timed-out, and interrupted calls, but it is not registered. A failed Task therefore leaves attribution active for up to 30 minutes, causing unrelated conversations to be treated as that subagent. Register postToolUseFailure and clear only the corresponding Task entry.

5. Medium: Default-space initialization modifies 11 committed agent files

Locations: core/tools/aidlc-includes.ts:122-128,181-215, core/hooks/aidlc-session-start.ts:52-60

The distributed agents contain <active-space> paths. On the first sessionStart, the rewriter replaces these with default, dirtying 11 files even though the active space has never changed. This contradicts the documented zero-churn guarantee. The active-space test compares restored bytes for the rule file but not the agent file, so it misses the regression.

6. Medium: Intent rebind depends on an optional identifier

Location: harness/cursor/hooks/aidlc-cursor-adapter.ts:213-217,250-256

Cursor documents conversation_id as the stable conversation identifier, but the rebind implementation only works when session_id exists. I reproduced this by retaining conversation_id while removing session_id: switching from intent A to B produced no rebind warning. The adapter should fall back to conversation_id.

7. Medium: Generated Cursor plugin hooks require a Unix shell

Location: scripts/package.ts:906-917

The generated hook invokes sh -c. Native Windows Cursor installations do not necessarily provide sh, so plugin composition fails before Bun runs. The Cursor hook should invoke a portable Bun script directly and perform executable discovery in TypeScript.

Verification

  • Cursor packaging and adapter tests: 24 passed.
  • Active-space tests: 18 passed.
  • Cursor package drift check: passed.
  • Plugin integration test was blocked locally by missing smol-toml.
  • Worktree remained clean.

@apackeer

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review - all reproducible, all real. Everything is addressed in cb45ad4.

1. Concurrent-conversation conflation (High) - You were right that the single-entry ledger conflated independent conversations, and your suggested direction (track by parent conversation / Task ID) surfaced a deeper problem when I went to implement it: Cursor's payloads carry no parent lineage at all. transcript_path is null at preToolUse time, and when it appears on postToolUse it names the subagent's own conversation (agent-transcripts/<own-id>/<own-id>.jsonl, flat), never the parent. What live probing did confirm both ways: sessionStart and beforeSubmitPrompt fire only for top-level conversations, never for a Task subagent's. The adapter now keeps a registry of known top-level conversations (registered at sessionStart/beforeSubmitPrompt/Task spawn) plus one spawn record per Task, and attributes a call to the subagent only when live spawn records name a single agent and the calling conversation is not a registered main. Your repro is covered - conversation B always fires sessionStart before it can act, so it can never be mistaken for A's subagent. Concurrent spawns of different agents make identity ambiguous and fail open rather than guessing (the 12a contract dispatches one reviewer at a time, so the enforced case stays unambiguous). t251 test 5 pins your exact repro; test 10 covers concurrent parents, isolated records, and the mixed-agent fail-open.

2. Multi-root workspaces (High) - Fixed. The adapter no longer consults workspace_roots at all: explicit env override first, then the hook's own cwd (Cursor runs project hooks from the root that owns the .cursor/hooks.json, which is also what the relative bun .cursor/hooks/... command already depends on). t251 test 12 pins cwd winning over a wrong first root.

3. Install overwrites existing configuration (High) - Fixed with a merge-aware installer, bun dist/cursor/install.ts <project>. It structurally merges hooks.json hook arrays and cli.json permission arrays (refusing allow/deny contradictions), appends marked AI-DLC sections to existing AGENTS.md and .gitignore instead of replacing them, preserves .cursor/.gitignore, and refuses malformed JSON or byte-differing collisions before writing anything - no partial installs. README and the Cursor guide now document the installer instead of the cp -R idiom. t250 tests 9-12 cover merge, both refusal paths, idempotent rerun, and the docs.

4. Failed Tasks leave stale attribution (Medium) - postToolUseFailure is now registered and clears the failed Task's record. One finding from live-probing this on cursor-agent 2026.07.23: postToolUse never delivers for the Task tool at all (it fires for Read/Write/Shell), so on today's CLI neither post-hook clear actually runs. sessionEnd - which does fire reliably - now also clears the conversation's records, verified live (ledger empty after a real Task run). The post-hook clears stay wired for versions that deliver them, and the TTL remains the backstop.

5. Default-space init dirties 11 committed files (Medium) - Fixed at the packager: shipped Cursor persona bodies now carry the concrete default-space path, so the first sessionStart repoint is a byte-identical no-op (verified: 0 writes at default, 12 on a space switch, byte-restoration on switching back). t250 pins the token's absence in dist. Worth noting the same churn pre-exists in the opencode distribution on the base branch - same rewriter, 22 files - so I've kept that out of this PR's scope; happy to file it as a follow-up.

6. Rebind depends on optional session_id (Medium) - Fixed, session_id ?? conversation_id feeds session-start, session-end, and the rebind probe. t251 test 8 now runs with session_id stripped.

7. Plugin hooks require a Unix shell (Medium) - Fixed for Cursor: the emitted hook command is now bun ./hooks/aidlc-plugin-compose.ts .cursor, a launcher that probes aidlc via Bun.which and falls back to the sibling compose.ts via process.execPath - no sh, no POSIX expansion. The other harness projections keep their existing shell probe (their hosts guarantee a POSIX shell); unifying them on the Bun launcher would be a reasonable follow-up.

Two adjacent hardenings that fell out of the same adapter work: a background agent's prompts no longer mint human presence (is_background_agent gate on the mint target), and attributed calls refresh ledger freshness so a legitimately long review can't silently outlive the 30-minute window mid-task.

Verification: full smoke+unit tier green (179 files), packaging drift check green across all six trees, and a live end-to-end run on cursor-agent 2026.07.23 - a scoped reviewer subagent denied on a sibling-unit read and on a nested Task, allowed on its own unit, an independent conversation never conflated, ledger empty after session end.

@leandrodamascena leandrodamascena left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hey Arden, thanks for round 1. The attribution-registry rework and installer are meaningful progress. I ran the updated implementation through an adversarial review and executable probes. Four confirmed defects still block merge.

Blockers

[P1] Stale Task records disable reviewer-scope enforcement

harness/cursor/hooks/aidlc-cursor-adapter.ts:284-307

Cursor CLI does not deliver postToolUse for Task, so an earlier lead-agent spawn remains in the ledger when the reviewer is dispatched. With records for different agents, activeSubagent() returns an empty identity. The reviewer-scope hook then treats the reviewer as an ordinary conversation and permits sibling-unit reads and deletes.

I reproduced this against the shipped Cursor distribution: a single reviewer record correctly denies the sibling read, while sequential developer and reviewer records allow it. The synthetic Task completion used by t251 does not occur under the verified CLI behavior.

This also prevents SUBAGENT_COMPLETED from being recorded. Please clear or conclusively attribute completed Task records before reviewer dispatch, or deny reviewer-scoped operations when attribution is ambiguous.

[P1] The installer cannot perform its documented upgrade

harness/cursor/install.ts:167-177

Every existing framework file whose bytes differ from the refreshed distribution is classified as an unresolved collision. Normal use already changes managed files such as aidlc/active-space, .cursor/rules/aidlc.mdc, and persona memory paths. Any genuine framework upgrade also changes shipped files.

I reproduced a fresh install, space repoint, and reinstall; it fails on:

.cursor/rules/aidlc.mdc
aidlc/active-space

The current test covers only a byte-identical reinstall. The installer needs to distinguish managed files, runtime-mutated files, and genuinely project-owned collisions.

[P1] Cursor plugin composition silently becomes a no-op when aidlc is installed

scripts/plugin-hooks-template/aidlc-plugin-compose.ts:11-20

The preferred branch runs aidlc plugin sync without setting AIDLC_PLUGIN_ROOT, PLUGIN_ROOT, or CLAUDE_PLUGIN_ROOT. plugin sync discovers plugins exclusively through those variables, so it prints "no installed plugins; nothing to sync" and exits successfully. The launcher then exits before executing sibling compose.ts.

This is confirmed through the actual control path. The integration test misses it by clearing PATH, which forces the fallback branch. Please pass the launcher's resolved plugin root into plugin sync, or fall back when sync composes zero plugins.

[P1] Security-critical guards remain fail-open on failure

harness/cursor/hooks.json:13-15
harness/cursor/hooks/aidlc-cursor-adapter.ts:469-477

The preToolUse hook omits Cursor's supported failClosed: true, and the adapter denies only when a child guard exits exactly 2. A missing, crashing, or invalid guard returns another status that is ignored, after which the adapter exits successfully and Cursor permits the operation.

I reproduced this by removing the reviewer-scope hook: an otherwise protected operation exits 0 with no denial. Please configure failClosed: true and convert unexpected child failures into a denial inside the adapter.

Additional Finding

[P2] Generated "opt-in" skills remain automatically model-invocable

harness/cursor/manifest.ts:85-86

Cursor automatically applies relevant skills unless they declare:

disable-model-invocation: true

The generated runners use user-invocable: true, which Cursor does not interpret as explicit-only. Consequently, an ordinary coding request can activate a state-mutating stage runner without the user invoking /aidlc-*.

Please emit Cursor-specific explicit-only frontmatter for these runner skills.

Housekeeping

PR #613 also currently claims version 2.5.14. Whichever PR merges second must rebump and rename its changelog heading.

The IDE payload contract remains a testing gap: the fixture corpus contains CLI captures only. I would treat that as residual risk rather than a blocker until real IDE payloads demonstrate a behavioral difference.

Validation

  • 112 tests passed with 602 assertions.
  • bun scripts/package.ts cursor --check passed.
  • git diff --check origin/v2...HEAD passed.
  • Reviewed remote head: cb45ad4b640066c6e7245ec836f31c5868b317ed.

Recommendation: Request Changes.

@apackeer

Copy link
Copy Markdown
Contributor Author

Review round 2 is addressed in 49d21dd.

  • Stale same-parent Task records are retired and logged before the next synchronous Task dispatch; mixed-agent ambiguity now preserves reviewer-scope enforcement instead of failing open.
  • Cursor preToolUse now declares failClosed: true; malformed adapter input and missing, crashing, or otherwise nonzero child guards deny the operation.
  • The Cursor installer now records framework ownership in .cursor/aidlc-install.json, upgrades managed files and hook metadata, recognizes pre-receipt installs, and preserves active space plus project-owned method memory.
  • The Cursor plugin launcher exports all supported plugin-root variables before invoking aidlc plugin sync, covering the installed-CLI path rather than only the fallback path.
  • Generated Cursor stage and scope runners, including plugin-composed runners, now carry disable-model-invocation: true.
  • The release metadata was rebumped from 2.5.14 to 2.5.15.

Verification:

  • Full smoke + unit suite: 179 files, 4,748 assertions, 0 failures.
  • Cursor adapter, packaging/installer, plugin composition, runner, and version focused tests pass.
  • bun run check passes: all six harness package-parity checks, TypeScript projects, and Biome over 550 files.
  • git diff --check passes.

@apackeer
apackeer force-pushed the feature/cursor-harness branch from 49d21dd to f4db441 Compare July 30, 2026 06:56
@apackeer apackeer changed the title feat: Cursor harness support (2.5.14) feat: Cursor harness support (2.5.29) Jul 30, 2026
@apackeer

Copy link
Copy Markdown
Contributor Author

Rebased onto the current v2 branch and force-pushed with lease. The release metadata is now consistently 2.5.29 across the changelog, README badge, authored version source, generated distributions, and PR title (2.5.27 is reserved by #646 and 2.5.28 by #664). The Cursor distribution was regenerated against the rebased core.

Validation completed locally:

  • bash tests/run-tests.sh --smoke --unit: 182 files, 4,810 assertions, 0 failures
  • bun run check: package parity for all six harnesses, all TypeScript configs, and Biome passed
  • git diff --check passed

@leandrodamascena, rereview requested.

@apackeer

Copy link
Copy Markdown
Contributor Author

Pushed 8654977, a rebase plus one new commit on top of the previously reviewed head (f4db441, history rewritten by the rebase).

Rebase + version. Rebased onto v2 at d0cd10a (2.5.30). The unreleased Cursor CHANGELOG entry moved from 2.5.29 to 2.5.31 (amended in place, dated 2026-07-31); trio and README badge updated, t68 green.

New commit: native workflow shortcuts, integrating ideas from #685 (credit to @rauldiaz for the design direction):

  • The method include is now split. .cursor/rules/aidlc.mdc stays alwaysApply but carries only the standing org/team/project layers; four new .cursor/rules/aidlc-phase-*.mdc rules are agent-decided (alwaysApply: false + description) and point at the matching phase file, so phase guidance loads only when relevant instead of on every turn. /aidlc space <name> re-points all five rules in place (the includes re-pointer now walks the whole top-level rules dir), and the installer's managed-content rewrite covers every rule.
  • Three Cursor-native shortcut skills: /aidlc-status, /aidlc-jump --stage <slug|#> (or --phase <name|#>), and /aidlc-scope <name>. They package the matching /aidlc forms through the same engine and forwarding loop (aliases, not alternate state paths), carry disable-model-invocation: true, and validate their arguments before calling the engine so a missing target never degrades into a bare next. No legacy .cursor/commands/ surface ships.
  • Doctor gains per-file checks for the four phase rules.

Live verification. #685's agent-decided rule type was doc-verified only, so I probed it on cursor-agent 2026.07.23 headless before adopting it: planted distinct codewords in two phase memory files, then ran a construction-phase question (surfaced only the construction codeword), an ideation-phase question (only the ideation codeword), and an off-topic control (neither). Per-phase selectivity works as documented; the manifest and harness guide now carry the live-verified note.

Gates on 8654977: smoke 1330/1330, unit 3512/3512 (includes the t174 docs-gate pin for the reworded feature-matrix row and a coverage-registry refresh), package.ts --check clean across all six trees.

Note for other open PRs: 2.5.31 is also claimed elsewhere; whichever merges second re-bumps per the changelog policy.

iuryeng added a commit to iuryeng/aidlc-workflows that referenced this pull request Jul 31, 2026
v2 shipped 2.5.30 while this branch still declared 2.5.27. Scanned the
open PRs by their actual AIDLC_VERSION diff rather than their titles:
2.5.31 (awslabs#535, awslabs#661, awslabs#686), 2.5.32 (awslabs#660, awslabs#687) and 2.5.33 (awslabs#689) are
claimed, so this takes 2.5.34.

The CHANGELOG entry was rebuilt from v2's file with this branch's block
reinserted, so no upstream heading is lost. Its sensor-cache bullet now
describes the engine-path match rather than the leaf-name one, and a new
bullet covers the clean-filter binding. Coverage registry regenerated
with the tool, not hand-edited.
iuryeng added a commit to iuryeng/aidlc-workflows that referenced this pull request Aug 3, 2026
v2 shipped 2.5.30 while this branch still declared 2.5.27. Scanned the
open PRs by their actual AIDLC_VERSION diff rather than their titles:
2.5.31 (awslabs#535, awslabs#661, awslabs#686), 2.5.32 (awslabs#660, awslabs#687) and 2.5.33 (awslabs#689) are
claimed, so this takes 2.5.34.

The CHANGELOG entry was rebuilt from v2's file with this branch's block
reinserted, so no upstream heading is lost. Its sensor-cache bullet now
describes the engine-path match rather than the leaf-name one, and a new
bullet covers the clean-filter binding. Coverage registry regenerated
with the tool, not hand-edited.
@apackeer

apackeer commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

@leandrodamascena, could you please take another review pass on the current head?

@apackeer apackeer mentioned this pull request Aug 5, 2026
4 tasks
@apackeer
apackeer force-pushed the feature/cursor-harness branch from 8654977 to df3a104 Compare August 6, 2026 10:56
@apackeer apackeer changed the title feat: Cursor harness support (2.5.29) feat: Cursor harness support (2.5.47) Aug 6, 2026
@apackeer
apackeer force-pushed the feature/cursor-harness branch 2 times, most recently from 77ba660 to b7e279b Compare August 7, 2026 23:19
@apackeer apackeer changed the title feat: Cursor harness support (2.5.47) feat: Cursor harness support (2.5.58) Aug 7, 2026
@apackeer

apackeer commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto the current v2 tip (3cb39c64, PR #707) and force-pushed b7e279bf. The release metadata and PR title are now consistently 2.5.58.

I verified #707 rather than relying on ancestry alone: its merge commit is the direct parent of this branch, the overlapping behavioral hunks remain intact, and Cursor now carries the project-facing voice/narration contract, intent-create, scope-aware phase directories, and the renamed hook bodies. All seven retired hook files are absent from the tracked source and generated distributions; the .aidlc-stop-hook/ counter directory intentionally retains its compatibility name.

The rebase also forward-ports the current safety seams into Cursor: plan approval runs before Task attribution is recorded, and review-freeze joins the fail-closed PreToolUse guard chain.

Verification on b7e279bf:

  • smoke + unit: 206 files, 5,381 assertions, 0 failures
  • focused integration for renamed hooks, intent-create, plugin composition, and stop-loop behavior: 6 files, 191 assertions, 0 failures
  • bun run check: package parity for all six harnesses, all TypeScript projects, and Biome passed
  • coverage registry freshness/ratchet and git diff --check: passed
  • GitHub CI: build, changelog, contract checks, and smoke + unit all passed

@leandrodamascena, rereview requested.

@apackeer
apackeer force-pushed the feature/cursor-harness branch from b7e279b to 8b7133b Compare August 8, 2026 11:41

@leandrodamascena leandrodamascena left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The current head is structurally strong and all existing checks pass, but adversarial testing found five blocking correctness/security issues and two additional reliability gaps.

1. [P1] Shell working directory bypasses reviewer scope and review freeze

harness/cursor/hooks/aidlc-cursor-adapter.ts:376-383

Cursor supplies the effective shell directory through tool_input.cwd in the captured fixture and documents tool_input.working_directory in the current hook contract. The adapter forwards those fields only inside tool_input; the core reviewer-scope and review-freeze hooks resolve relative shell paths from top-level cwd, which is empty in the captured payload and therefore falls back to the project root.

I reproduced a reviewer scoped to unit-a successfully passing the guards for:

{
  "tool_name": "Shell",
  "cwd": "",
  "tool_input": {
    "command": "cat design.md",
    "cwd": "<record>/construction/unit-b"
  }
}

Cursor executes the command in unit-b, while the guards evaluate design.md relative to the project root and allow it. Relative writes can bypass review freeze through the same mismatch. Normalize tool_input.cwd / working_directory into the top-level cwd expected by the shared guards and add sibling-read plus protected-write regressions.

2. [P1] A reviewer can delete its own identity ledger and escape enforcement

harness/cursor/hooks/aidlc-cursor-adapter.ts:171-215,285-348

Subagent attribution lives in predictable, writable /tmp/aidlc-cursor-subagent-*.json files. A reviewer can run rm against its ledger because reviewer scope permits paths outside construction/. Once the file is gone, activeSubagent() returns an empty identity and the next sibling-unit operation is treated as an ordinary conversation.

I reproduced the full sequence through the shipped adapter: reviewer spawn, permitted removal of the ledger file, then a permitted Read of unit-b despite a dispatch record binding the reviewer to unit-a. Ledger write/read failures also deliberately degrade to the same fail-open state.

The attribution store needs protection from delegated tools, and failures to establish or recover identity must not silently disable reviewer enforcement while a reviewer dispatch is active.

3. [P1] Receipt-backed reinstall removes the selected-plugin configuration

harness/cursor/install.ts:254-264

The installer stores hashes in .cursor/aidlc-install.json but never compares the current file with the previous receipt hash. The mere presence of a path in priorReceipt.managedFiles grants unconditional overwrite permission.

I reproduced a normal install, added "plugins": ["aidlc"] to .cursor/tools/data/harness.json, and reran the installer. The reinstall succeeded and removed the selection. Since an absent selection means all installed plugins are enabled, an upgrade can silently change an explicit opt-out into broad enablement. The same overwrite path temporarily strips composed graph/stage state.

Preserve runtime-owned fields/state during upgrades or distinguish unchanged prior framework bytes from files modified after installation. Add a receipt-backed reinstall test after select-plugins.

4. [P1] The emitted Cursor plugin hook can succeed without composing the project

scripts/plugin-hooks-template/aidlc-plugin-compose.ts:11-34
scripts/plugin-hooks-template/compose.ts:41-48,354-357

The launcher never reads the SessionStart payload and exports no project root. When it executes from the plugin directory, both aidlc plugin sync and the fallback composer resolve the project from the plugin cwd/PWD. The composer finds no .cursor/tools/aidlc-graph.ts there and returns success as a deliberate non-AIDLC no-op.

I reproduced the emitted launcher with a real SessionStart payload whose workspace_roots named an installed Cursor project. It exited 0 while the target project received no plugin stage. The integration test masks this by setting AIDLC_PROJECT_DIR explicitly and running with the project as cwd, neither of which is established by the emitted hook.

Parse the hook payload and export an unambiguous project root, including an explicit multi-root policy. Test the emitted launcher from the plugin directory without injecting AIDLC_PROJECT_DIR.

5. [P1] Cursor discovers duplicate plugin agents with conflicting projections

scripts/package.ts:902-918,995-1017
scripts/plugin-hooks-template/compose.ts:1311-1322

Cursor officially auto-discovers agents/ when .cursor-plugin/plugin.json does not override that component. The emitted plugin therefore exposes agents/test-pro-metrics-agent.md directly, while compose copies the same native identity into project .cursor/agents/.

The plugin copy retains unresolved {{HARNESS_DIR}} text and model: sonnet; the project copy competes under the same name. Resolution is host-dependent, and the named model also contradicts this PR’s documented lower-plan portability rule for Cursor agents.

Emit one authoritative native agent surface, apply the Cursor token/model projection to it, and add plugin-agent assertions covering duplicate discovery, token substitution, and absence of named model pins.

6. [P2] Stop forwarding expires after Cursor’s default five continuations

harness/cursor/hooks.json:29-30

Cursor documents a default loop_limit of five for stop hooks. The current registration omits it, so a workflow needing more than five forwarding nudges can terminate while the core still reports pending work. Existing coverage verifies only one follow-up. Set and test an intentional limit suitable for the AIDLC forwarding contract.

7. [P2] Restoring a missing native surface points it at the wrong active space

harness/cursor/install.ts:255-256

Existing differing rules/agents pass through managedContent(), but a missing managed file uses raw copy. I reproduced an install on active space team-b, deleted a phase rule, and reinstalled. The restored rule pointed to aidlc/spaces/default/memory/ while aidlc/active-space remained team-b. Apply active-space rewriting to both copy and update paths.

Verification

  • Official focused unit slice: 88 tests passed.
  • Plugin composition integration: 77 tests passed.
  • bun scripts/package.ts --check: all six harnesses passed.
  • TypeScript and Biome checks passed.
  • GitHub CI is green.
  • Six custom adversarial probes confirmed the reported behaviors.
  • Branch is current with v2; git diff --check and worktree status are clean.

The green suite covers nominal packaging and adapter paths but does not exercise the effective shell cwd, hostile ledger mutation, receipt-backed runtime state, real plugin-hook cwd, duplicate Cursor plugin discovery, or stop-loop exhaustion. Recommendation: keep CHANGES_REQUESTED until the five P1 paths are fixed and regression-tested.

@apackeer

apackeer commented Aug 9, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the independent follow-up review and pushed the rewritten head at ef53481f (b386e74f + ef53481f).

Follow-up fixes

  • Closed the remaining reviewer-state escape: the Cursor adapter now combines the shared concrete shell write-target parser with glob-prefix checks. It denies exact paths, ancestor deletes, unquoted wildcards, character classes, record-parent wildcards, ledger-prefix wildcards, and broad .aidlc-* patterns before they can remove the attribution ledger or reviewer dispatch record.
  • Narrowed Cursor reinstall preservation to stages actually modified by plugins: a stage is preserved only when named by a plugin-contrib-*.json sidecar or carrying a <!-- plugin: seam sentinel. Pure-core stage edits now use ordinary receipt-hash collision handling, and every managed runtime file intentionally preserved is printed to stdout.
  • The fresh-install {kind:"write"} mode observation was informational; no change was needed because the current Cursor distribution has no executable or binary assets.

The previously verified fixes remain intact: shell cwd normalization, fail-closed reviewer attribution, plugin selection/composed-state preservation, SessionStart project-root discovery, one authoritative Cursor plugin-agent surface, loop_limit: 10, and active-space-aware restoration.

Validation

  • tests/logs/2026-08-09T20-42-08ZResult: PASS (t250, t251, t188; 119 assertions, 0 failed)
  • tests/logs/2026-08-09T20-43-44ZResult: PASS (t68, t174; 10 assertions, 0 failed)
  • bun scripts/package.ts --check — all six harness trees in sync
  • All TypeScript projects passed
  • Biome and git diff --check passed
  • Version source, README badge, and changelog remain 2.5.61 with heading date 2026-08-09

@leandrodamascena leandrodamascena left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Re-review of ef53481f: the earlier fixes address several findings, but three blockers remain.

  1. P1 - harmless delegated Shell calls are denied from the project root (harness/cursor/hooks/aidlc-cursor-adapter.ts:538). Object.values(toolInput) treats working_directory as a path being accessed. Because overlapsProtectedPath() also matches ancestors, the project root overlaps the attribution ledger beneath it. A reviewer Shell call such as echo ok with working_directory set to the project root is therefore denied as an attempt to access reviewer attribution state. Cursor commonly supplies this field, so normal reviewer execution can be blocked. Restrict this check to actual path operands/targets rather than cwd metadata.

  2. P1 - delegated code can still erase attribution state and bypass reviewer scope (harness/cursor/hooks/aidlc-cursor-adapter.ts:503). The protection is lexical: a Bun/Node command whose Base64 payload removes .aidlc-cursor-subagents and .aidlc-reviewer-dispatch.json passes the guard because those paths are not visible in the command text. After executing it, the adapter loses reviewer attribution and allows a read from a sibling unit. If this state is a security boundary, arbitrary interpreter execution needs to be denied for delegated reviewers or isolated with a real sandbox; token/path matching cannot enforce the stated invariant.

  3. P1 - selected-plugin reinstall freezes stale compiled routing data across upgrades (harness/cursor/install.ts:204). Whenever harness.json contains an explicit plugins selection, managedContent() returns the existing stage-graph.json and scope-grid.json. Reinstalling a newer release can therefore update stage source files while retaining the previous compiled graph used for routing. The current test at tests/unit/t250-cursor-packaging.test.ts:492 codifies byte-for-byte graph preservation but does not model a release upgrade. Recompose the selected plugin set against the newly installed core data instead of preserving old compiled outputs.

The branch is also currently two commits behind v2, and GitHub reports it as conflicting (mergeable_state: dirty), so it must be rebased and revalidated before approval.

Verification completed on this head: t250, t251, t188, package parity, typecheck, lint, git diff --check, and focused adversarial reproductions.

@apackeer
apackeer force-pushed the feature/cursor-harness branch from ef53481 to 1cfab2d Compare August 10, 2026 18:44
@apackeer

Copy link
Copy Markdown
Contributor Author

Addressed the August 10 re-review and the follow-up rebase reconciliation at 1cfab2d1.

  • Restricted reviewer-state path checks to actual path operands, so cwd / working_directory metadata no longer blocks harmless project-root Shell calls.
  • Denied general-purpose interpreter and dynamic-evaluation Shell calls from review delegates, covering encoded Bun/Node attempts to erase attribution state.
  • Changed Cursor reinstall upgrades to regenerate selected-plugin routing against the upgraded core instead of retaining stale compiled graph/grid data.
  • Rebased onto current v2 and reconciled the parallel Copilot/Cursor prose into one seven-harness form, including the shared workspace scanner, repository maps, guide/reference docs, plugin projections, and model-tier table.
  • Added tailored Cursor fresh-session runner wording.
  • Kept release metadata consistently at 2.5.63 dated 2026-08-10.

Validation:

  • Focused smoke + unit + integration slice: tests/logs/2026-08-10T12-20-33ZResult: PASS, 8 files, 2,375 assertions, 0 failures.
  • bun scripts/package.ts --check: all seven harness trees in sync.
  • TypeScript, Biome, coverage/docs/version guards, Cursor packaging/adapter tests, and plugin composition passed.

@leandrodamascena leandrodamascena left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for the latest updates. I performed another end-to-end review of the current head, including the installer, adapter enforcement, plugin upgrade behavior, generated distribution, documentation parity, and focused tests.

Several earlier issues were addressed, particularly the working_directory false positive and parts of plugin routing regeneration. However, the PR is still not ready for approval. The remaining issues include two deterministic CI failures and multiple enforcement or upgrade paths not covered by the current tests.

1. Fresh-session behavior is still missing

Location: harness/cursor/skills/aidlc/SKILL.md:79,144

The Cursor orchestrator still uses run-then-continue and instructs the current conversation to execute the next intent after completing the current one.

That conflicts with the current AI-DLC fresh-session contract: when an intent completes and another queued intent exists, the current session must stop and instruct the user to start a fresh session. Continuing in the existing conversation carries stale context, assumptions, tool history, and potentially the previous intent's role state into the next intent.

This is also a deterministic CI failure in t181-conductor-skill-parity.test.ts.

Expected: Cursor should use the same stop-and-handoff semantics as the other harnesses.

Requested change: Update both Cursor orchestration paths to stop after completion and present the fresh-session continuation instruction. Add Cursor to all corresponding parity assertions.

2. Reviewer protection can still be bypassed through shell expansion

Location: harness/cursor/hooks/aidlc-cursor-adapter.ts:528-582

The new shell guard blocks literal references to protected reviewer-attribution files and blocks direct interpreter use. That closes some paths, but it does not resolve shell expansion before execution.

For example, a command can construct the protected paths dynamically:

ledger="aidlc/.aidlc-cursor-subagents"
dispatch="aidlc/spaces/default/intents/<intent>/.aidlc-reviewer-dispatch.json"
rm -rf "$ledger" "$dispatch"

The adapter evaluates the literal command tokens before the shell expands $ledger and $dispatch. The protected paths therefore do not appear in the parsed operands, but the shell still deletes them.

If the attribution ledger and reviewer dispatch marker are removed, subsequent calls can lose reviewer attribution and avoid reviewer-only restrictions. Blocking python, node, perl, and similar interpreters does not close this path because expansion is performed by the shell itself.

Expected: Delegated agents must not be able to mutate reviewer attribution state, regardless of whether a protected path is written literally or assembled dynamically.

Requested change: Use an enforcement boundary that does not depend only on lexical command inspection. At minimum, account for shell assignments and expansions and add regression tests using dynamically constructed paths.

3. Partial ledger loss does not fail closed

Location: harness/cursor/hooks/aidlc-cursor-adapter.ts:339-378

The current fail-closed fallback only consults the active reviewer dispatch when no active ledger records remain.

A problematic case remains:

  1. A reviewer record and an unrelated developer record are active.
  2. The reviewer record becomes missing, malformed, unreadable, or otherwise excluded by readRecord().
  3. The developer record remains readable.
  4. The reviewer conversation produces another protected tool call.
  5. The adapter sees one remaining agent and attributes the call to the developer.
  6. The active reviewer dispatch fallback is not consulted because the active-record array is non-empty.

This means partial ledger corruption can weaken enforcement more than total ledger loss. The existing test around t251-cursor-adapter.test.ts:998-1015 covers the zero-record case but not this partial-loss case.

Expected: While a reviewer dispatch is active, uncertainty involving an unknown conversation must never resolve to a non-reviewer identity merely because an unrelated record remains.

Requested change: Incorporate the active reviewer dispatch into ambiguous attribution even when non-reviewer records exist. Add a regression test with one missing reviewer record and one surviving developer record.

4. Plugin-modified stages can remain permanently pinned to stale core content

Location: harness/cursor/install.ts:171-177,419,528-530

The installer preserves an existing stage whenever its contents differ from the framework source. That preservation also applies to stages generated by plugin composition.

The subsequent plugin-routing refresh recompiles the selected graph, but it uses the already-preserved stage as its input. It does not reconstruct the stage from the new core version and reapply the plugin contribution.

The resulting upgrade sequence is:

  1. Version A installs a core stage.
  2. A plugin contribution modifies the installed stage.
  3. Version B ships an important update to the same core stage.
  4. The installer sees the composed stage differs from the Version B source and preserves it.
  5. The receipt is updated.
  6. Plugin routing is regenerated from the preserved Version A-derived stage.
  7. The Version B core fix never reaches that stage.

t188-plugin-compose.test.ts:327-350 currently expects the old composed content to remain, so the test codifies this stale-stage behavior rather than detecting it.

Expected: Framework-owned plugin composition should be reproducible from the new core source plus the installed plugin contribution. Only genuine user edits should be preserved.

Requested change: Track enough provenance to distinguish generated plugin composition from user modification, then reconstruct composed stages during upgrades. Update the test to verify that new core content and plugin contributions are both present after upgrade.

5. Pre-receipt migration can overwrite user-modified files

Location: harness/cursor/install.ts:71-77,395,446-451

For an older installation without an ownership receipt, the installer treats the presence of three sentinel files as sufficient evidence that framework files are safe to overwrite.

That establishes that AI-DLC was previously installed, but it does not establish that every framework-shaped file still matches the original distribution. A user may have customized rules, agents, stages, hooks, or tools before receipts existed.

Under the current migration path, those files can be considered framework-owned and overwritten without a hash comparison or backup.

Expected: Lack of historical ownership metadata should not be interpreted as proof that every existing file is unmodified.

Requested change: Use a conservative migration strategy, such as comparing against known prior distribution hashes, preserving unverified files, creating backups, or requiring explicit confirmation before overwriting uncertain content. Add a migration test with a modified pre-receipt framework file.

6. Cursor runtime state is not fully excluded from Git

Location: harness/cursor/dot-gitignore:48-49

Cursor's generated .gitignore is missing:

aidlc/spaces/*/intents/.aidlc-*

This allows hidden intent-level runtime state to appear as untracked or staged content. Besides repository noise, some of these files can contain transient framework state that should remain local.

This omission causes the deterministic failure in t157-workspace-shell-seed.test.ts.

Expected: Cursor should emit the same runtime ignore coverage as the other harnesses.

Requested change: Add the missing pattern to the authored Cursor template, regenerate dist/, and keep t157 green.

7. Real Task completion is not reliably audited

Location: harness/cursor/hooks/aidlc-cursor-adapter.ts:630-641,778-785

The implementation notes that Cursor CLI does not emit a Task postToolUse event. The adapter compensates by retiring an earlier record when the same parent starts another Task, but that does not cover all completed tasks.

If a parent launches one Task and never launches another, sessionEnd removes the remaining records without emitting the same completion audit event. The explicit SUBAGENT_COMPLETED path appears to depend on a synthetic event rather than the normal Cursor lifecycle.

This can leave the audit trail with a dispatch/start record but no corresponding completion record.

Expected: Every delegated Task should eventually have a deterministic terminal audit event, including the final Task in a session.

Requested change: Emit an appropriately qualified completion or inferred-completion event when retiring records at session end, or document and test another reliable lifecycle signal.

8. Question-rendering parity is incomplete

Location: harness/cursor/skills/aidlc/question-rendering.md:110-132

Cursor's question-rendering guidance does not include the current fresh-numbering and visible-key mapping requirements.

The relevant parity assertions in t181-conductor-skill-parity.test.ts:396-411 also omit Cursor, so this divergence is not detected by that portion of the test suite.

Without these rules, rendered option numbers can drift after filtering, and the visible option number may not map deterministically to the stored answer key.

Expected: Cursor should follow the same deterministic numbering and answer-key contract as the other harnesses.

Requested change: Port the current shared guidance to Cursor and include Cursor in the parity assertions.

9. PR title and shipped version do not match

The PR title still refers to version 2.5.58, while the implementation, changelog, and generated outputs are now at 2.5.63.

This is not a functional blocker, but it makes review history and release scope harder to understand.

Requested change: Update the PR title to reflect the actual release version or remove the version from the title.

Validation results

I reproduced the current test state locally:

FAIL tests/unit/t157-workspace-shell-seed.test.ts
FAIL tests/unit/t181-conductor-skill-parity.test.ts

The following focused validation passed:

t250-cursor-packaging.test.ts
t251-cursor-adapter.test.ts
t188-plugin-compose.test.ts
bun scripts/package.ts --check

Those passing tests confirm that the generated package is internally consistent with the authored source. They do not resolve the behavioral gaps above; in particular, the plugin test currently expects the stale-stage outcome, and the adapter tests do not cover dynamic shell expansion or partial reviewer-ledger loss.

Review conclusion

The working_directory false positive appears resolved. The interpreter restrictions and plugin-routing recompilation are useful improvements, but they only partially address the original reviewer-enforcement and upgrade concerns.

I recommend keeping this review at Request changes until:

  1. t157 and t181 pass.
  2. Reviewer attribution fails closed under dynamic path construction and partial ledger loss.
  3. Plugin-composed stages receive new core updates without discarding plugin contributions.
  4. Pre-receipt migration handles uncertain ownership conservatively.
  5. The missing parity and lifecycle cases have regression coverage.

@apackeer apackeer changed the title feat: Cursor harness support (2.5.58) feat: Cursor harness support (2.5.63) Aug 10, 2026
@apackeer
apackeer force-pushed the feature/cursor-harness branch from 1cfab2d to 85c64bd Compare August 10, 2026 20:05
@apackeer

Copy link
Copy Markdown
Contributor Author

Addressed the August 10 review at 85c64bd2 and rebased the branch onto the current v2 tip.

Fixes

  • Cursor now follows the shared run-then-stop contract after creating a second intent: it stops, tells the user to start a fresh Cursor chat, and does not continue the new intent in the prior conversation.
  • Cursor question rendering now starts each question at 1, maps visible numbers deterministically back to source option keys, and keeps file-backed A-E/X labels as storage-only values.
  • Added the missing aidlc/spaces/*/intents/.aidlc-* runtime ignore.
  • Reviewer attribution now fails closed under shell parameter expansion/dynamic evaluation and under partial ledger loss; a surviving non-reviewer record can no longer mask a missing reviewer record.
  • sessionEnd emits an explicitly inferred canonical SUBAGENT_COMPLETED event for Cursor's final live Task before retiring its ledger record.
  • Cursor upgrades now reconstruct plugin-composed stages from receipt-verified prior core bytes plus recorded structural/prose contributions. New core content and plugin contributions both survive; unexplained user edits are refused.
  • Pre-receipt installs no longer treat framework-shaped files as proven ownership. Any differing unverified managed file blocks the install before writes.
  • The PR title now matches the shipped 2.5.63 version.

Verification

  • Focused unit slice (t68, t157, t181, t250, t251): 77 tests, 0 failures.
  • Plugin composition integration (t188): 79 tests, 0 failures.
  • t251 green-alone rerun: 25 tests, 0 failures.
  • bun run check, bun scripts/package.ts --check, and git diff --check pass.
  • GitHub contract, changelog, and build checks are green; smoke + unit is currently running.

@leandrodamascena leandrodamascena left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Requesting changes. The latest revision resolves the earlier fresh-session, gitignore, question-rendering, partial-ledger, and inferred Task-completion findings. The following issues remain.

1. [P1] Reject symlinked installer targets

harness/cursor/install.ts:919-958

The installer reads and writes project paths without checking whether the target or one of its parent directories is a symlink. This allows installation into a project containing a symlinked AGENTS.md, .gitignore, or .cursor directory to modify files outside the requested project root.

I reproduced this with AGENTS.md symlinked to an external file. The installer exited successfully and appended the AI-DLC section to the external target.

This matters when installing into a cloned or otherwise untrusted repository: a repository-controlled symlink can redirect writes to any location writable by the user.

Please reject symlinked targets and symlinked parent components, or resolve each destination and verify that its real path remains under the intended project root before writing.

2. [P1] Shell wrappers can remove reviewer attribution state

harness/cursor/hooks/aidlc-cursor-adapter.ts:591-624

The new attribution-state protection can still be bypassed by combining a shell wrapper with quote concatenation:

command rm -f /project/.../.aidlc-reviewer-dispatch.jso''n

The shell resolves jso''n to json, but the adapter does not recognize the operation:

  • The serialized-input substring check does not contain the complete protected filename.
  • shellWriteTargets() treats command as the executable and does not inspect the nested rm.
  • shellWords() reconstructs the path, but the follow-up check only evaluates glob prefixes.
  • The dynamic-evaluation guard does not classify command as an interpreter or expansion.

After this command executes, the reviewer ledger still identifies the delegated reviewer, but aidlc-reviewer-scope.ts intentionally fails open when the dispatch record is missing. The reviewer can therefore access sibling-unit construction artifacts.

Please unwrap recognized shell command prefixes such as command before classifying the underlying operation, and check every reconstructed concrete shell word against the protected paths rather than checking only wildcard prefixes.

3. [P2] Reconcile plugin ownership when core adopts a contributed value

harness/cursor/install.ts:546-615

The installer reconstructs a plugin-composed stage by removing sidecar-owned values from the installed stage and applying them to the new core source. However, it does not update the corresponding plugin-contrib-*.json ownership record.

A problematic upgrade sequence is:

  1. Core v1 does not declare artifact-x.
  2. Plugin P contributes artifact-x and records it in its contribution sidecar.
  3. Core v2 adds artifact-x natively.
  4. The installer upgrades the stage and retains artifact-x, but the sidecar still identifies it as plugin-owned.
  5. The user disables plugin P.
  6. stripDisabledPluginContributions() removes artifact-x, even though it is now required by core v2.

The installed stage then diverges from the upgraded core definition.

Please remove values already present in the new core source from plugin ownership records, or regenerate the contribution sidecars so they describe only contributions that remain additive after the upgrade.

4. [P2] Remove obsolete receipt-owned files during upgrades

harness/cursor/install.ts:845-907

managedFiles begins with every entry from the previous receipt, but installation only iterates files present in the new distribution. There is no deletion action for files that were previously managed but have since been removed upstream.

I reproduced this by adding a receipt-owned .cursor/rules/obsolete.mdc and reinstalling. The installer exited successfully, left the obsolete rule active, and retained its receipt entry.

This can preserve removed rules, hooks, agents, or tools indefinitely. In the case of hooks and rules, obsolete behavior remains active even though the installed release no longer ships it.

Please compare the previous receipt with the new managed-file set. A removed path can be deleted safely when its current hash still matches the previous receipt. If it was modified locally, preserve it and report the ownership conflict instead of silently retaining it as framework-managed.

5. [P2] Shell wrappers bypass the review-freeze hook

harness/cursor/hooks/aidlc-cursor-adapter.ts:814-829
core/hooks/aidlc-review-freeze.ts:334-448

The review-freeze parser recognizes direct mutation commands:

truncate -s 0 requirements.md

It does not recognize the same command through a standard shell wrapper:

command truncate -s 0 requirements.md

For the wrapped form, shellWriteTargets() sees command as the executable and returns no mutation targets. The adapter consequently permits modification of an artifact covered by a fresh READY receipt.

The artifact fingerprint still prevents final acceptance of the modified bytes, so this is not a bypass of the final receipt-validation floor. It does bypass the deterministic freeze, invalidates the terminal receipt, and can return the workflow to the review/rewrite loop that the freeze was introduced to prevent.

Please unwrap supported shell prefixes before mutation classification and add wrapper cases for each protected mutator.

6. [P2] Interpreter detection rejects ordinary command arguments

harness/cursor/hooks/aidlc-cursor-adapter.ts:580-588

shellInvokesDynamicEvaluation() applies the interpreter regular expression to every parsed shell word rather than only executable positions. This rejects ordinary search and output commands when their data happens to name an interpreter.

Examples:

rg node README.md
printf '%s\n' python
rg 'source' README.md

The first two are rejected because node or python appears anywhere in the argument list. The final command is rejected by the quote-unaware source expression.

This creates avoidable false positives for delegated reviewers and can block legitimate read-only investigation.

Please identify executable positions per command segment, including supported wrapper handling, and apply eval/source detection only where those tokens have shell semantics rather than where they appear as quoted data or arguments.

7. [P2] Expired main markers can misattribute top-level conversations

harness/cursor/hooks/aidlc-cursor-adapter.ts:244-255
harness/cursor/hooks/aidlc-cursor-adapter.ts:339-386

isKnownMain() checks the marker TTL before refreshing it. A legitimate top-level conversation that has been idle for more than 30 minutes is therefore no longer recognized as main on its next event.

If another conversation currently has a live reviewer task, the resumed top-level conversation can be attributed as that reviewer. Its normal operations may then be denied by reviewer-scope, dynamic-evaluation, or nested-delegation controls.

The existing refresh prevents expiry during continuous activity, but not after an ordinary idle period.

Please avoid using inactivity alone to revoke a known top-level identity, or introduce a separate lifecycle signal that distinguishes a genuinely stale conversation from a valid conversation resuming after inactivity.

Validation

The following local validation passed:

  • 241 focused unit and integration tests
  • Cursor adapter and installer suites
  • Reviewer-scope and review-freeze suites under the repository test-runner guard environment
  • Version/changelog synchronization
  • Workspace-shell seed tests
  • bun scripts/package.ts --check for every harness

All current CI checks are green. Findings 1 and 2 remain merge-blocking because they permit writes outside the installation root and deterministic removal of reviewer-scope enforcement, respectively.

@apackeer
apackeer merged commit d7807fe into v2 Aug 11, 2026
5 checks passed
@apackeer
apackeer deleted the feature/cursor-harness branch August 11, 2026 08:14
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.

2 participants