@gtrabanco/pi-agentic-workflow 0.18.5 → 0.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -246,9 +246,13 @@ dialog.
246
246
 
247
247
  ## Notes
248
248
 
249
- - Verified against Pi 0.99.1 (2026-09-29) (`pi install`, package skills, friendly
249
+ - Verified against the peer floor Pi 0.99.1 (2026-09-29) (`pi install`, package skills, friendly
250
250
  command registration, routed set/clear, settings console round-trip, `sendUserMessage`
251
- with prompt template expansion).
251
+ with prompt template expansion). Also verified against Pi 1.2.0 (2026-10-14) via a local
252
+ two-leg `tsc --noEmit` run (legs `0.99.1` + `latest`, the `latest` leg resolving to pi
253
+ 1.2.0; COMPAT-OK); continuously against the
254
+ `latest` dist-tag by the `pi-compat-guard` PR gate (the leg is a moving tripwire,
255
+ not a pinned version — a breaking release shows red here first).
252
256
  - The package declares Pi as a peer dependency; it bundles no copy of Pi.
253
257
  - Skills can instruct the model to run commands. Review them as you would any
254
258
  third-party package.
@@ -284,12 +284,16 @@ export default function extension(pi) {
284
284
  });
285
285
  return decision.block ? { block: true, reason: decision.reason } : undefined;
286
286
  });
287
- pi.on("agent_settled", (_event, ctx) => {
288
- void router.settle(toInvocationContext(ctx));
287
+ pi.on("agent_settled", (event, ctx) => {
288
+ // pi 1.1.0+: event may carry { aborted: true }
289
+ const aborted = event?.aborted === true;
290
+ void router.settle(toInvocationContext(ctx), aborted);
289
291
  // Terminal hygiene is said out loud once the turn is over; a clean tree
290
292
  // stays silent. The probe is time-bounded (2000 ms) and swallows timeout,
291
293
  // spawn error and non-zero status, so an unresponsive git can never park
292
294
  // the settled turn — best-effort by construction (issue #182 F4).
295
+ // The dirty-worktree warning still fires on abort (an aborted run is exactly
296
+ // when half-applied edits can be left behind).
293
297
  const warning = guards.dirtyWorktreeWarning(readGitStatusBounded(ctx.cwd));
294
298
  if (warning)
295
299
  ctx.ui.notify(warning, "warning");
@@ -29,7 +29,7 @@ export interface Router<M extends ModelRef = ModelRef> {
29
29
  /** Pi `thinking_level_select` — same distinction for the thinking level. */
30
30
  noteThinkingLevelSelect(level: ThinkingLevel): void;
31
31
  /** Pi `agent_settled` — restores whatever this turn still owns. */
32
- settle(ctx: InvocationContext<M>): Promise<void>;
32
+ settle(ctx: InvocationContext<M>, aborted?: boolean): Promise<void>;
33
33
  /** True while a routed turn owns the session. The console shows and clears it. */
34
34
  inFlight(): boolean;
35
35
  /**
@@ -79,13 +79,20 @@ export function createRouter({ surface, loadConfig, hint, settingsCommand, known
79
79
  await restore(turn, surface(ctx), ctx, `undo: nothing was dispatched by /${turn.command}`);
80
80
  return true;
81
81
  },
82
- async settle(ctx) {
82
+ async settle(ctx, aborted) {
83
83
  const turn = pending;
84
84
  if (!turn)
85
85
  return;
86
86
  // Cleared first: the restore fires its own select events, and a turn that is
87
87
  // already over must not read them as operator changes.
88
88
  pending = undefined;
89
+ // `aborted`: pi 1.1.0 added `agent_settled.aborted` to distinguish a
90
+ // cancelled run from a finished one. An aborted turn is cleared without
91
+ // restoring the snapshot — an interrupted run must not silently swap the
92
+ // session model underneath the operator, matching the backing-off stance
93
+ // for an operator mid-turn change (AC7, pi 1.1.0 scope extension).
94
+ if (aborted)
95
+ return;
89
96
  // `keep`: leave the routed model and thinking level in the open chat window,
90
97
  // so a follow-up edit or question keeps running on the model that planned it.
91
98
  // The operator's own mid-turn choice is still untouched (nothing is restored,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gtrabanco/pi-agentic-workflow",
3
- "version": "0.18.5",
3
+ "version": "0.19.1",
4
4
  "description": "Pi package: canonical agentic-workflow skills, friendly slash commands, and per-command model routing.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -57,7 +57,7 @@
57
57
  "@earendil-works/pi-tui": "*"
58
58
  },
59
59
  "dependencies": {
60
- "@gtrabanco/agentic-workflow": "0.1.1",
60
+ "@gtrabanco/agentic-workflow": "0.2.0",
61
61
  "@gtrabanco/agentic-workflow-schema": "4.5.0"
62
62
  },
63
63
  "devDependencies": {
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: audit-pr
3
3
  user-invocable: true
4
- version: 5.4.0
4
+ version: 5.6.0
5
5
  argument-hint: <pr-number> (optional — defaults to the current branch's PR)
6
6
  author: "Gabriel Trabanco <1969593+gtrabanco@users.noreply.github.com>"
7
7
  license: MIT
@@ -120,9 +120,20 @@ gh pr view <N> --json headRefOid,comments
120
120
  review evidence, then evaluate the delivery gates below.
121
121
  - **absent** — no matching marker on the PR → **BLOCKER**: no review evidence at
122
122
  the head; route to `/review-change`.
123
- - **stale** — a marker exists but its `sha` does not equal `headRefOid`. Any SHA
124
- mismatch voids the receipt → **BLOCKER**: route to `/review-change` for a
125
- re-review. Do not use a local `git diff` to override the PR-head comparison.
123
+ - **stale** — a marker exists but its `sha` does not equal `headRefOid`. Before
124
+ voiding, apply the scope-aware judge (fix/286, #182 AC 3/9): a receipt that
125
+ recorded a scope manifest (`scope=<64-hex>` in the marker) stays **current**
126
+ when the head delta (receipt sha → head, `git diff --name-only`) touches only
127
+ non-affecting paths — the closed classes (session log `docs/LOGS.md`, agent
128
+ toolstate/memory `.engram/`, `.pi/`, `.serena/`) are owned by
129
+ `scripts/scope-manifest.mjs`; any path matching no named class is affecting.
130
+ Run `bun scripts/review-receipt.mjs verify --pr <N>` — it computes the delta
131
+ locally and answers `current` (exit 0) or `stale` (exit 4, naming the
132
+ affecting paths). A legacy receipt without a scope attribute, an affecting
133
+ delta, or an unresolvable delta voids the receipt → **BLOCKER**: route to
134
+ `/review-change` for a re-review. Do not use a local `git diff` to override
135
+ the PR-head comparison — the delta check only ever *keeps* a scoped receipt
136
+ current; it never manufactures currency for a scope-less one.
126
137
 
127
138
  Never compose, reconstruct, or "spot-check" the review from the diff to clear a
128
139
  missing/stale receipt — that is `review-change`'s turn, and re-litigating axes
@@ -88,16 +88,57 @@ This gate sits downstream of both pre-execution reviews, so it verifies their
88
88
  authority **survived the build** — it never re-reviews a plan and never re-judges a
89
89
  verdict:
90
90
 
91
- 1. **Upstream lineage is current.** The unit's `progress.md` carries
92
- `## Pre-execution review receipt v1 — plan` whose digest re-derives identically
93
- (`scripts/pre-execution-snapshot.mjs verify --stage plan --parent <the receipt's
94
- Product digest>`; a fix unit binds no parent —
95
- `structural.reasonCode`/`changedPaths` name the drifted dimension), and — for a
96
- feature unit — its named `— spec` parent re-derives the same way. Bound artifacts
97
- are frozen: new implementation-phase files are allowed, edits to a bound artifact
98
- are not. Stale, missing, wrong-stage **or impossible-timeline** lineage → **BLOCKED**,
99
- `→ Next: /unit-lane <unit>` (the lane's `review` step re-derives the artifact). A `SPEC-REVIEW-PASS` never satisfies the plan hop, and vice versa.
100
- 2. **Obligations are closed.** Every row of the unit's obligation ledger is
91
+ 1. **Upstream lineage is current** — run the machine check and read its fixed
92
+ verdict; the gate never re-implements the check by hand:
93
+ `bun scripts/unit-lineage.mjs --unit <unit>` prints exactly one of
94
+ `LINEAGE OK — legacy …` / `LINEAGE OK — lane-era …` (pass) or
95
+ `LINEAGE BLOCKED — <reason>` followed by a `→ Next:` route (**BLOCKED** —
96
+ that route). The check keys the lane-era/legacy split on evidence, not on an
97
+ author-controlled artifact (fix/285; the lane-era pipeline structurally never
98
+ writes `progress.md` or `ACCEPTANCE.md`, so demanding them made the gate
99
+ unsatisfiable for every lane-era unit — issue #285):
100
+ - **Legacy path** — opened only when the unit's `progress.md` carries a
101
+ `## Pre-execution review receipt v1 — plan` whose digest **re-derives**
102
+ (`scripts/pre-execution-snapshot.mjs verify --stage plan` exits 0 over the
103
+ bound bytes): the unit's
104
+ `progress.md` carries
105
+ `## Pre-execution review receipt v1 — plan` whose digest re-derives
106
+ identically (`scripts/pre-execution-snapshot.mjs verify --stage plan
107
+ --parent <the receipt's Product digest>`; a fix unit binds no parent —
108
+ `structural.reasonCode`/`changedPaths` name the drifted dimension), and —
109
+ for a feature unit — its named `— spec` parent re-derives the same way.
110
+ The acceptance manifest binds when present (fix/285): a unit that carries
111
+ `ACCEPTANCE.md` still binds it in the snapshot. Bound artifacts
112
+ are frozen: new implementation-phase files are allowed, edits to a bound
113
+ artifact are not. A receipt that does not re-derive — stale, missing,
114
+ wrong-stage **or impossible-timeline** — is never a pass: the check falls
115
+ through to the lane-era path, so a planted or stale `progress.md` cannot
116
+ buy a weaker gate (and a unit doc with no lane-era surface left is then
117
+ **BLOCKED**, `→ Next: /unit-lane <unit>`).
118
+ - **Lane-era path** (feature 61's single-doc contract) — the unit doc's
119
+ Evidence section carries the `## Triaged steps` block whose `Steps:` /
120
+ `Skipped:` / `Budget:` lines re-derive identically from the doc's own
121
+ facts (`bun scripts/unit-route.mjs --triage <unit>`), and the obligation
122
+ ledger carries ≥1 row with every row `verified` or an explicit
123
+ `n/a: <reason>` (see item 2 for the ledger shapes). This comparison binds
124
+ the unit doc's **bytes**: the triage output is a pure function of the
125
+ doc's content, so any tampering — with the pasted block *or* with the
126
+ facts (Tasks, scope, tests) the block is derived from — changes the
127
+ re-derived lines and the check answers **BLOCKED**,
128
+ `→ Next: /unit-lane <unit> --retriage`. No separate digest surface is
129
+ needed (the earlier "frozen digest anchor" wording claimed
130
+ `unit-route.mjs --triage` emits a digest of the pasted block; it never
131
+ did, and the paragraph is retracted — review finding F9). An absent or
132
+ empty obligation ledger is a producer defect, not an audit pass: the
133
+ lane's plan step writes the ledgers (see item 2), and a vacuous ledger
134
+ cannot demonstrate closure — **BLOCKED**, `→ Next: /unit-lane <unit>`; if
135
+ there are truly no obligations, the ledger carries a single
136
+ `n/a: <reason>` row.
137
+ A `SPEC-REVIEW-PASS` never satisfies the plan hop, and vice versa.
138
+ 2. **Obligations are closed.** Every row of the unit's obligation ledger —
139
+ `### Obligations` inside the unit doc (embedded shape) or
140
+ `planning-obligations.md` (separate-file shape, per the sizing rule in
141
+ `pre-execution-review`'s `LEDGERS.md`; never both) — is
101
142
  `verified` — with the validator that ran on this candidate — or an explicit
102
143
  `n/a: <reason>`. Any `planned`, `in-progress`, blank, or `deferred` row is
103
144
  **BLOCKED**, naming the ids. `deferred` is legal only when the user amended the
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: init-workspace
3
3
  user-invocable: true
4
- version: 3.2.0
4
+ version: 3.3.0
5
5
  argument-hint: <target-dir>
6
6
  author: "Gabriel Trabanco <1969593+gtrabanco@users.noreply.github.com>"
7
7
  license: MIT
@@ -109,6 +109,16 @@ loads exactly upgrade and skips bootstrap plus portability.
109
109
  is not listed (markdown/yaml/json are never auto-detected), and a listed
110
110
  server that fails to start must be removed (one failure disables every
111
111
  server in the project). Document verification is a script's job.
112
+ - **Retrieval index scaffold and git hooks are consent-gated (AC22).** The
113
+ index surface — the committed `.agentic-workflow/index.json` config plus the
114
+ three freshness hooks (`post-merge`, `post-checkout`, `post-rewrite`, all
115
+ `index-sync.sh` installed into `.git/hooks/`) — is installed **only after an
116
+ explicit yes** to the question "install the doc retrieval index scaffold and
117
+ its git hooks?". Without consent: install nothing, record it as a residual
118
+ (the entry point still runs and builds its index on first query — the hooks
119
+ are only a freshness fast path), and never write `.git/hooks/` or the config
120
+ on a later visit without a fresh yes. The hooks are best-effort by contract
121
+ (`doc --sync --quiet`, always exit 0 — a fast path never breaks git).
112
122
  - **Never redefine the urgency label vocabulary here.** `skills/triage-issue
113
123
  /SKILL.md` is the sole owner of the `urgent`/`fix-next` names, colors, and
114
124
  apply rules — this skill only seeds those two labels into the repo; it never
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: pre-execution-review
3
3
  user-invocable: false
4
- version: 2.6.0
4
+ version: 2.7.0
5
5
  author: "Gabriel Trabanco <1969593+gtrabanco@users.noreply.github.com>"
6
6
  license: MIT
7
7
  description: >
@@ -28,9 +28,15 @@ digest at all; the fixed form that stands in the digest's place is each stage's
28
28
 
29
29
  Rules the builder enforces and no caller may improvise around:
30
30
 
31
- - **`SPEC.md` and `ACCEPTANCE.md` are required** for their stage: a missing one is
31
+ - **`SPEC.md` is required** for its stage: a missing one is
32
32
  refused, not silently dropped, because dropping it would bind a smaller set than
33
33
  the contract reviewed.
34
+ - **`ACCEPTANCE.md` binds when present** (fix/285): the acceptance manifest is a
35
+ pre-lane artifact. A unit that carries one (pre-lane and adoption units) binds
36
+ it as the `acceptance` row; a lane-era unit whose acceptance criteria live in
37
+ the unit doc builds without that row. Requiring the manifest made every
38
+ lane-era plan snapshot unsatisfiable — the enforcer demanded a file the
39
+ current pipeline structurally never writes (issue #285).
34
40
  - **A ledger that lives inside the SPEC has no row** (D20): an XS/S or fix unit
35
41
  embeds `### Planning evidence` / `### Obligations`, so those rows are absent and
36
42
  their bytes are already bound by the `spec` row. Never point a row at a file that
@@ -2,7 +2,7 @@
2
2
  name: product-audit
3
3
  user-invocable: true
4
4
  disable-model-invocation: true
5
- version: 3.3.0
5
+ version: 3.4.0
6
6
  metadata:
7
7
  opencode/autoinvoke: false
8
8
  argument-hint: <path-or-area> (optional — defaults to the whole product)
@@ -10,9 +10,12 @@ author: "Gabriel Trabanco <1969593+gtrabanco@users.noreply.github.com>"
10
10
  license: MIT
11
11
  description: >
12
12
  Audit the whole product across code, quality, process, docs, roadmap, and
13
- tooling. Persist one severity-ranked, F-numbered report with proposals; never
14
- fix or file work. Triggers: "product-audit", "audit the product", "full health
15
- check", "are we product-ready", "CTO review".
13
+ tooling; it owns the repo-wide bug hunt, security research, and broken-version
14
+ investigation (scoped and budgeted per dimension, as distinct from
15
+ `review-change`'s change-scoped review). Persist one severity-ranked,
16
+ F-numbered report with proposals; never fix or file work. Triggers:
17
+ "product-audit", "audit the product", "full health check", "are we product-ready",
18
+ "CTO review".
16
19
  ---
17
20
 
18
21
  # Product Audit
@@ -38,6 +41,11 @@ first on purpose).
38
41
  - Periodically (every few features) or at a product-ready milestone.
39
42
  - When you want the broad, honest picture — quality, security, debt, docs, and
40
43
  roadmap — not the review of a single change (`review-change`) or PR (`audit-pr`).
44
+ - **It owns the repo-wide sweep.** Open-ended, repository-wide bug hunting,
45
+ security research and broken-version investigation land **here** — scoped,
46
+ sampled and budgeted per dimension (state what you sampled). The same hunt
47
+ against one change is `review-change`'s job: neither skill absorbs the other's
48
+ scope.
41
49
 
42
50
  Unlike diff, PR, or docs-only reviews, this skill covers the whole product.
43
51
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: review-change
3
3
  user-invocable: true
4
- version: 3.8.0
4
+ version: 3.11.0
5
5
  argument-hint: <path-or-glob> [--adversarial N] [--synthesize]
6
6
  author: "Gabriel Trabanco <1969593+gtrabanco@users.noreply.github.com>"
7
7
  license: MIT
@@ -31,6 +31,13 @@ For a final PR review, the turn is incomplete until this additional box passes:
31
31
  `review-change:pass` marker is confirmed, so its exit code IS this box
32
32
  ```
33
33
 
34
+ When the receipt emitter runs, it also records the affecting-path scope
35
+ manifest (fix/286) so a foreign-only commit after the review (a session-log or
36
+ toolstate commit on a shared checkout) does not void the receipt at the merge
37
+ gate — pass `--scope-base main` (the unit's base ref) and the runtime derives
38
+ the manifest itself; never hand-assemble a digest. A receipt without a scope
39
+ manifest stays head-bound (legacy semantics).
40
+
34
41
  The receipt closeout is a precondition of the report, not a follow-up: do not
35
42
  print the fixed report block on a non-zero exit. A clean report without a
36
43
  current receipt must not recommend `/audit-pr`.
@@ -68,6 +75,32 @@ mapping candidate evidence.
68
75
  Default target is the **current change** (branch diff vs the default branch);
69
76
  accept a path/glob to widen or narrow. State the scope at the top of the report.
70
77
 
78
+ ## Review charter — dismantle the claim
79
+
80
+ The workflow's job is to demonstrate that what works, works — **and works
81
+ well**: programmatically, engineering-wise, decision-wise, and against user
82
+ expectation. This review exists to **try to dismantle that claim**; if it
83
+ survives the attempt, that survival *is* the review outcome. So:
84
+
85
+ - **In-scope defects are this review's job — never “out of scope”.** A bug, a
86
+ security issue, or a guard skip inside the current feature's scope is a
87
+ `review-change` finding: a feature is not complete while it has one. The
88
+ reward is not delivery, it is meeting the acceptance criteria, adequate
89
+ engineering without over-engineering, meeting **other features' requirements**
90
+ (a page that needs auth verifies the session *and* the permission; a change to
91
+ another table that a requirement depends on stays in scope even if it “looks”
92
+ out of scope), and above all the user's expectation.
93
+ - **Ask, don't infer — the whole expectation surface.** Where the plan, the diff
94
+ or the requirement leaves the user's expectation unclear, surface it as a
95
+ question in non-technical terms (a technical user asks for an app; a
96
+ non-technical user asks for “an app to measure my heart rate”) — never as an
97
+ assumed intent. This extends ask-don't-infer from acceptance criteria to every
98
+ expectation the change is judged against.
99
+ - **Repo-wide hunting is not this review.** Open-ended bug hunting, security
100
+ research and broken-version investigation *across the repository* belong to
101
+ `product-audit`, where the sweep is scoped and budgeted; this review stays on
102
+ the change at hand.
103
+
71
104
  ## Step 0 — Discover the project & the change (always first)
72
105
 
73
106
  Per Workflow conventions + documentation map, decide axes from:
@@ -76,6 +109,12 @@ Per Workflow conventions + documentation map, decide axes from:
76
109
  and optional recorded platform skills (extras only).
77
110
  2. **Footprint:** what the diff touches (UI, API, infra, domain). An axis applies
78
111
  only when both project and footprint support it.
112
+ 3. **Prior findings (optional retrieval aid, never authoritative):** when the
113
+ index entry point exists, `agentic-workflow doc --query "<change topic>"
114
+ --json-only` may surface earlier findings, decisions and session-log entries
115
+ on the same surface before the passes run. A missing index, an absent entry
116
+ point, or an empty answer changes nothing — grep stays the exhaustive
117
+ fallback, and the index is never a decision authority.
79
118
 
80
119
  ## Applicability matrix (default; the project's docs refine it)
81
120
 
@@ -60,9 +60,16 @@ exact command tokens.
60
60
  bun scripts/review-receipt.mjs emit --pr <N> \
61
61
  --head "$(git rev-parse HEAD)" --scope "<reviewed surface>" \
62
62
  --axes "<axes run and skipped>" --coverage "<criterion-to-evidence>" \
63
- --invariants pass|n/a --proposals <n> --manual "<items or none>"
63
+ --invariants pass|n/a --proposals <n> --manual "<items or none>" \
64
+ --scope-base main
64
65
  ```
65
66
 
67
+ `--scope-base <ref>` (fix/286) records the affecting-path scope manifest
68
+ beside the reviewed head: the emitter derives it from the branch delta vs
69
+ that ref, so a later foreign-only commit (session log, agent toolstate) no
70
+ longer voids the receipt at the merge gate — any affecting-path delta still
71
+ voids it. Never hand-assemble the manifest or its digest.
72
+
66
73
  The script is the contract: it refuses a PR whose `headRefOid` differs from
67
74
  the reviewed head, posts the one idempotent SHA-bound comment (skipping an
68
75
  identical one), re-reads the comments, and **exits non-zero unless the newest
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: triage-issue
3
3
  user-invocable: true
4
- version: 2.9.0
4
+ version: 2.10.0
5
5
  argument-hint: <issue-number> [more issue numbers…] | <audit-id> F<k> [F<j>…] | --prioritize-now <unit> F<k> [F<j>…]
6
6
  author: "Gabriel Trabanco <1969593+gtrabanco@users.noreply.github.com>"
7
7
  license: MIT
@@ -57,7 +57,13 @@ first on purpose).
57
57
 
58
58
  Per the agent guide's **Workflow conventions** + **documentation map**, then read
59
59
  what THIS skill needs: the fix index (e.g. `docs/fix/README.md`) and fix SPEC
60
- template, and the roadmap. In review-finding mode, read the target unit's SPEC,
60
+ template, and the roadmap. **Retrieval aid (optional, never authoritative):**
61
+ when the index entry point exists, run it once before the forge read —
62
+ `agentic-workflow doc --query "<topic terms>" --json-only` — to surface prior
63
+ findings, decisions and session-log entries on this topic. It is a fast path
64
+ over the same corpus grep covers: a missing index, an absent entry point, or an
65
+ empty answer changes nothing (grep stays the exhaustive fallback — AC21), and
66
+ the index is never a decision authority. In review-finding mode, read the target unit's SPEC,
61
67
  acceptance, `review-findings.md`, current diff, and PR instead of looking for an
62
68
  issue comment. Otherwise read the issue in full, including comments and labels
63
69
  (forge CLI per the project's Workflow conventions — examples use `gh`):
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: unit-lane
3
3
  user-invocable: true
4
- version: 1.1.1
4
+ version: 1.4.0
5
5
  argument-hint: <NN-slug | "<idea>"> [--retriage] | --fix <issue-number> | --from-issue <issue-number>
6
6
  description: >
7
7
  One-shot lane conductor for a delivery unit: triage, implement, evidence, and
@@ -48,7 +48,9 @@ Both `--fix <n>` and `--from-issue <n>` are the exact forms
48
48
 
49
49
  Per the agent guide's **Workflow conventions** + **documentation map**, then read
50
50
  `docs/features/_TEMPLATE/SPEC.md` (the unit doc template). If the unit folder
51
- exists, read its current `SPEC.md` to avoid overwriting.
51
+ exists, read its current `SPEC.md` to avoid overwriting. If this work returns
52
+ to an area other unit docs already touch, scan their `## Future` sections and
53
+ surface each open row as plan-now / keep-deferred / discard before continuing.
52
54
 
53
55
  ## Process
54
56
 
@@ -67,7 +69,11 @@ For `--fix`/`--from-issue`, read the tracked issue in the forge first and keep
67
69
  its number; the folder name carries it (`fix/<issue-number>-<topic>`). Fill
68
70
  objective, why, user outcome, and non-goals from the invocation (or the issue).
69
71
  Acceptance criteria are induced from user scenarios — **ask-don't-infer**; if the
70
- request is too vague, STOP and ask the user with concrete options. See
72
+ request is too vague, STOP and ask the user with concrete options. Open
73
+ decisions that are not acceptance criteria run through the design step's
74
+ **gap interview** — one batched form-turn with recommended options + why,
75
+ `Decide for me` (recorded in `decisions.md`), and cheap defaults recorded as
76
+ assumptions. See
71
77
  [the unit doc template](references/STEPS.md) for section order.
72
78
 
73
79
  ### 2. Triage — read the catalog, never re-derive
@@ -113,13 +119,16 @@ Before every commit:
113
119
  ### 6. Close the unit
114
120
 
115
121
  When all triaged steps are done, run the review pack axis summary, update the
116
- unit doc's status, and print the closing block.
122
+ unit doc's status, resolve every `## Future` row with the user (planned now —
123
+ registered on explicit request only —, kept deferred, or discarded, dated),
124
+ and print the closing block.
117
125
 
118
126
  ## Guardrails
119
127
 
120
128
  **Allowed:**
121
129
  - Only the triaged steps listed by the catalog
122
130
  - Changes scoped to the acceptance criteria
131
+ - Deferred user ideas recorded in the unit doc's `## Future`, never implemented
123
132
  - Documentation updates in docs/ and template/
124
133
  - Conventional commit messages per the project's Workflow conventions
125
134
 
@@ -2,14 +2,58 @@
2
2
 
3
3
  ## Purpose
4
4
 
5
- Induce acceptance criteria from user scenarios and close entity, role, and
6
- expectation boundaries — distilled from the full design skill's closure.
5
+ Run the gap interview, induce acceptance criteria from user scenarios, and
6
+ close entity, role, and expectation boundaries — distilled from the full design
7
+ skill's closure.
7
8
 
8
9
  ## Inputs
9
10
 
10
11
  - The unit's objective, why, and user outcome from the SPEC
12
+ - The user's original request, the repository's current state, and the
13
+ installed tooling — whatever they already answer is never asked again
11
14
  - Research findings from the research step
12
15
 
16
+ ## The gap interview (runs before acceptance criteria lock)
17
+
18
+ Collect every open decision the unit depends on and filter each one:
19
+
20
+ | The gap… | The lane does |
21
+ |---|---|
22
+ | The request, the repo, or the installed tooling already answers it | Never asks |
23
+ | Blocks now and is expensive to undo once code sits on top | Asks |
24
+ | Blocks a later task that is already foreseeable | Asks now as a possibility; re-asks only if conditions change |
25
+ | Cheap to change later | Takes a reasonable default and records it as an assumption — never silent |
26
+
27
+ Expensive to undo = the choices that cost a lot to change once code exists on
28
+ top: **scope** (personal / team / global), **stack**, **architecture pattern**,
29
+ **data model**, **deployment target**, and **security configuration**.
30
+
31
+ Mechanics — one batched form-turn, never one-question-per-turn:
32
+
33
+ - Group all independent questions into a single turn; ≤6 slots, one-word
34
+ defaults accepted, ≤2 ambiguity follow-ups per slot.
35
+ - Ask a single question first only when its answer changes the others. When
36
+ more than five gaps remain, the root decision (objective or scope) goes
37
+ first — its answer usually removes most of the rest.
38
+ - Every slot carries: the **recommended option with its why**, one
39
+ alternative, **"Decide for me"**, and **"Explain the options"** — the user
40
+ may always answer freely.
41
+ - **"Decide for me"**: the lane chooses, then records the choice and its
42
+ reason as a dated row in the unit folder's `decisions.md` — explicit, never
43
+ guessed silently.
44
+ - **"Explain the options"**: explain each alternative didactically, then
45
+ re-ask only that slot.
46
+ - A tool the project lacks may be offered as one more option (with its
47
+ recommendation and why); installing it always needs the user's explicit
48
+ consent.
49
+ - **Ready when:** no expensive-to-undo blocking gap remains unanswered — not
50
+ when no gaps remain. An answer the user already gave is never re-asked.
51
+
52
+ Deferred ideas ("down the road I'd like…") are never implemented, never turned
53
+ into spec or tasks by this unit: they go verbatim to the unit doc's
54
+ `## Future` section, dated, so the architecture may accommodate them without
55
+ building them.
56
+
13
57
  ## Fixed output contract
14
58
 
15
59
  Fill the unit doc's **Acceptance criteria** section. Each AC must be:
@@ -19,6 +63,12 @@ Fill the unit doc's **Acceptance criteria** section. Each AC must be:
19
63
 
20
64
  ## Checklist (pass only if)
21
65
 
66
+ - [ ] The gap interview ran before the ACs locked: one batched form-turn,
67
+ each gap filtered through the table above
68
+ - [ ] No expensive-to-undo blocking gap remains unanswered
69
+ - [ ] Every "Decide for me" choice is recorded with its reason in `decisions.md`
70
+ - [ ] Cheap defaults are recorded as assumptions, correctable by the user
71
+ - [ ] Deferred user ideas landed in `## Future`, not in scope
22
72
  - [ ] Every AC is induced from a user scenario, not inferred from vague intent
23
73
  - [ ] Acceptance criteria are command-verified where possible
24
74
  - [ ] Entity closure: every entity mentioned has its role in the unit stated
@@ -28,8 +78,12 @@ Fill the unit doc's **Acceptance criteria** section. Each AC must be:
28
78
 
29
79
  ## Forbidden
30
80
 
81
+ - Do not ask what the request, the repo, or the installed tooling already answers
82
+ - Do not ask one question per turn when slots are independent
83
+ - Do not implement, spec, or task anything from `## Future`
84
+ - Do not install a missing tool without explicit user consent
31
85
  - Do not build a separate DESIGN.md — one SPEC, all in the unit doc
32
86
  - Do not invent product scope the user did not confirm
33
87
  - Do not pre-fill engineering content (phases, tests, architecture)
34
88
  - Do not stamp "designed" with an incomplete closure row
35
- - An incomplete row silently un-does the skill's purpose
89
+ - An incomplete row silently un-does the skill's purpose
@@ -31,6 +31,62 @@ backticked command **and** an expected outcome, e.g. `exits 0`), and the nested
31
31
  set, written from the planner's own discovery, and a file that is missing from
32
32
  it is never out of scope for the executor.
33
33
 
34
+ ## Prior-decisions contradiction sweep
35
+
36
+ Before the plan is accepted, sweep its claims against what the repository has
37
+ **already decided in writing** — a plan must not contradict a prior decision in
38
+ architecture, engineering, product, or any other domain.
39
+
40
+ **Candidate sources** — deterministic keyword/FTS matching, no new index and no
41
+ new tooling: `docs/features/<NN>-<slug>/decisions.md`,
42
+ `docs/fix/<issue>-<topic>/decisions.md`, the project's architectural invariants,
43
+ the Normalized Repository State (`discover-repository-state` /
44
+ `resolve-repository-state`), `AGENTS.md`, and a unit SPEC's `## Design status`
45
+ (legacy marker, units ≤ 60).
46
+
47
+ **Fixed evidence shape** — one row per candidate hit, the decision quoted from
48
+ its source:
49
+
50
+ | claim | prior decision | source path | relation |
51
+ |---|---|---|---|
52
+ | <the plan claim> | <the recorded decision> | <path:line> | `contradicts` \| `compatible` |
53
+
54
+ The cell value is exactly one of two values: `contradicts` or `compatible` —
55
+ a row relation, not a stage verdict.
56
+
57
+ **Advisory only — no new authority.** `contradicts` routes to
58
+ `resolve-repository-state` only when the hit is against a frozen NRS fact
59
+ (its only candidate source that carries a `<contradiction-id>` row after a
60
+ `discover-repository-state` step); hits against
61
+ `decisions.md` / `AGENTS.md` / architectural invariants / `## Design status`
62
+ surface a `NEEDS-DECISION` for the user, per the existing rules. A plan with **no
63
+ candidate match is clean and proceeds** — a miss never blocks it, a missed
64
+ candidate can never void a plan, and the sweep issues no stage verdict and grants no
65
+ gate of its own.
66
+
67
+ ## Planning ledgers (same step)
68
+
69
+ Cut both planning ledgers in the same pass — the plan snapshot binds them, and
70
+ the audit's lineage gate closes obligations from them (fix/285). The closed row
71
+ shapes, column orders, and vocabularies are owned by
72
+ `skills/pre-execution-review/references/LEDGERS.md`; this step only decides the
73
+ home and fills the rows:
74
+
75
+ - **Planning evidence** — one row per Engineering claim the SPEC's Engineering
76
+ half (or a fix SPEC's Objective/Why) makes. Ids `PE-001`, `PE-002`, …; an
77
+ unsampled assumption is `unknown` with an owner (`ASSUMPTION-UNVERIFIED`),
78
+ never a silent citation.
79
+ - **Obligations** — one row per normative behaviour, applicable compatibility
80
+ invariant, affected use case, and required failure state. Ids `O1`, `O2`, …;
81
+ exactly one phase and one task each; every row `planned` at cut time,
82
+ `verified` (or evidenced `n/a`) before the unit ships.
83
+
84
+ **Home (sizing rule, never both):** XS/S embeds both tables under the unit
85
+ doc's `### Planning evidence` / `### Obligations` headings — the plan snapshot
86
+ binds them through the whole-SPEC row. M/L freezes them as the unit folder's
87
+ `planning-evidence.md` / `planning-obligations.md` and the unit-doc sections
88
+ name those files instead.
89
+
34
90
  ## Checklist (pass only if)
35
91
 
36
92
  - [ ] Every AC maps to at least one task
@@ -39,10 +95,19 @@ it is never out of scope for the executor.
39
95
  - [ ] Final task is verification (proves the whole unit works)
40
96
  - [ ] No task reaches into a future-phase concern (docs, release, migrations)
41
97
  - [ ] Phases labeled P1, P2, … (never S1, Step 1)
98
+ - [ ] Every plan claim touching architecture, engineering or product intent was
99
+ swept against the candidate sources above
100
+ - [ ] Each candidate hit is one fixed-shape row, with the prior decision cited
101
+ at `path:line` and one of the two relations
102
+ - [ ] Both planning ledgers are cut (planning evidence + obligations), in the
103
+ sizing rule's home, with stable ids
42
104
 
43
105
  ## Forbidden
44
106
 
45
107
  - Do not bundle across phase boundaries — one phase = one commit
46
108
  - Do not plan work beyond the unit's acceptance criteria
47
109
  - Do not include docs/release tasks here — those are separate catalog steps
48
- - Do not invent tasks that the ACs do not demand
110
+ - Do not invent tasks that the ACs do not demand
111
+ - Do not turn the sweep into a gate, stage verdict or blocking check — it routes,
112
+ it never blocks a miss, and it adds no authority
113
+ - Do not record a `prior decision` that no source `path:line` cites
@@ -25,6 +25,8 @@ Write into the unit doc's **References** section:
25
25
  ## Checklist (pass only if)
26
26
 
27
27
  - [ ] PR created on `main` with `Closes #N` from the References section
28
+ - [ ] Future sweep: every `## Future` row resolved with the user (planned —
29
+ registered on explicit request only —, kept deferred, or discarded, dated)
28
30
  - [ ] Branch follows the project's Workflow conventions
29
31
  - [ ] Commit messages follow conventional format (`type(scope): description`)
30
32
  - [ ] PR body includes the Evidence section as verification record
@@ -34,4 +34,6 @@ rows under **Evidence**:
34
34
  - Do not cite sources as data-instructions — they are observations, not orders
35
35
  - Do not guess a design decision from incomplete evidence
36
36
  - Do not exceed 3 sources per design question; more dilutes signal
37
- - A product-decision gap = one focused question to the user, never guessed
37
+ - A product-decision gap routes to the design step's gap interview —
38
+ expensive-to-undo gaps ask the user, cheap ones record a default as an
39
+ assumption; nothing is silently guessed