@sabaiway/agent-workflow-kit 3.3.0 → 3.4.0

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/CHANGELOG.md CHANGED
@@ -4,6 +4,20 @@ Semantically versioned ([semver](https://semver.org)), newest first. The `versio
4
4
  is the current release. `upgrade` mode reads a project's `docs/ai/.workflow-version` and applies
5
5
  every `migrations/<version>-<slug>.md` newer than it, in semver order.
6
6
 
7
+ ## 3.4.0 — review-state names a latent arm on a clean-tree PASS (AD-064)
8
+
9
+ `review-state --check` under a configured `reviewed` or `council` recipe on a clean tree no longer
10
+ returns a bare `PASS — nothing to review`. It now NAMES every plan still in flight and states the
11
+ forward consequence — `this gate arms as soon as the
12
+ tree is dirty` — so a spent boot prompt left under a bare name is discoverable BEFORE it blocks,
13
+ instead of surfacing at the worst moment: a pending commit, or the landing of a feature worktree when
14
+ main first turns dirty. The quiet case (configured `solo`, no plan in flight, a non-git cwd) still
15
+ passes silently through the earlier-returning arms; dirty-tree behaviour is unchanged. A `doc-parity`
16
+ binding pins the
17
+ live notice to `references/modes/review-state.md`, so a reworded doc that drops it fails the gate — a
18
+ prose-only bar becomes a mechanism. This is the safe, converged slice of the deferred parallel-track
19
+ work (AD-063), shipped on its own with no worktrees-ownership coupling.
20
+
7
21
  ## 3.3.0 — version-pin honesty: the profile stops claiming a limit it never observed (AD-062)
8
22
 
9
23
  `velocity --autonomy` told every user their credentials could not be protected by the sandbox —
package/SKILL.md CHANGED
@@ -3,7 +3,7 @@ name: agent-workflow-kit
3
3
  description: Deploy or upgrade a portable AI-agent memory-and-workflow system in any project. Use when the user wants to bootstrap `docs/ai/` + an entry-point `AGENTS.md` (+ `CLAUDE.md` alias) + cap/archive/index enforcement in a new or existing repo, set up the Memory Map and session protocols, install the docs-rotation pre-commit hook, or run `/agent-workflow-kit` / `/agent-workflow-kit upgrade`. Triggers on phrases like "set up the memory system", "deploy the AI workflow here", "bootstrap docs/ai", "upgrade the workflow".
4
4
  disable-model-invocation: true
5
5
  metadata:
6
- version: '3.3.0'
6
+ version: '3.4.0'
7
7
  ---
8
8
 
9
9
  # agent-workflow-kit
package/capability.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "schema": 1,
4
4
  "name": "agent-workflow-kit",
5
5
  "kind": "composition-root",
6
- "version": "3.3.0",
6
+ "version": "3.4.0",
7
7
  "provides": [],
8
8
  "roles": {},
9
9
  "detect": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sabaiway/agent-workflow-kit",
3
- "version": "3.3.0",
3
+ "version": "3.4.0",
4
4
  "description": "Portable, cross-agent memory & workflow for AI coding agents — Claude Code, Codex, Cursor, Devin Desktop. One command deploys an AGENTS.md entry point + docs/ai context with cap/archive/index enforcement into any repo.",
5
5
  "keywords": [
6
6
  "ai-agents",
@@ -5,7 +5,7 @@ Read-only **review-receipt checker** (AD-038) — it makes *"reviewed ≠ shippe
5
5
  Run `node ${CLAUDE_SKILL_DIR}/tools/review-state.mjs [--check] [--json]`:
6
6
 
7
7
  1. Plain run → the human report: resolved recipe + source, plan-in-flight, tree fingerprint, per-backend receipt state (current / stale / ungrounded / probe / rejected / missing) with verdict + grounding + timestamp.
8
- 2. **`--check`** → the gate exit code. The **normative exit contract lives in the tool header** (the single home — do not re-enumerate it elsewhere): exit 0 for a CONFIGURED solo recipe (a computed readiness-degrade NEVER silently becomes solo — it needs the explicit degrade record below), no plan in flight (the `docs/plans` naming convention: `queue.md` and `EXECUTE-`/`FEEDBACK-`-prefixed or `PROMPT`/`prompt`/`handoff`-carrying names are scratch), a clean tree, a non-git cwd, or every recipe-named backend SATISFIED for the current tree. **Satisfaction is ship-class-only on the LATEST NORMAL receipt (D3(b)):** per backend, the latest probe-free current-fingerprint receipt is selected FIRST and THEN verdict-checked — only the recognized ship-class vocabulary (`ship` / `ship with nits`) satisfies; a recognized NEGATIVE (`revise` / `rethink` / `rework`) is an authoritative VETO (an earlier ship never survives a later revise); an UNRECOGNIZED verdict (e.g. `unknown` from a dead run) fails CLOSED unconditionally — a later `unknown` never lets an earlier SHIP stand, and a fresh normal re-run supersedes it. **The ONLY escape is an explicit degrade RECORD** (`core-evidence.mjs degrade --backend <name> --reason "…"`, fingerprint-bound to the current tree) — and never all backends: ≥1 non-degraded ship-class receipt is required whenever ≥1 backend is configured; a malformed evidence store denies the escape fail-closed but never fails an independently-satisfied tree. Exit 1 otherwise — missing, **stale** (ANY edit after a review moves the fingerprint), ungrounded, vetoed, or unrecognized. Plan/diff receipts and continuations (`agy-review --continue`) are **informational-only**: after a fold, only a **fresh grounded re-run** (`codex-review code`; `agy-review code --facts @f`) restores green. **Probe receipts never attest either:** a `CODEX_PROBE=1` / `AGY_PROBE=1` review runs with the frontier-model/max-effort guard OFF, so the wrapper stamps `probe:true` and this checker excludes it — per receipt, so a real review at the same fingerprint still satisfies. **Silence is not a declaration:** a malformed *or* absent probe marker is rejected fail-closed and stated in the check line. Honest bound: receipts are **not authenticated** (a forger could write `probe:false` as easily as any other field) — like the rest of the receipt this is a self-discipline mechanism, not a security boundary.
8
+ 2. **`--check`** → the gate exit code. The **normative exit contract lives in the tool header** (the single home — do not re-enumerate it elsewhere): exit 0 for a CONFIGURED solo recipe (a computed readiness-degrade NEVER silently becomes solo — it needs the explicit degrade record below), no plan in flight (the `docs/plans` naming convention: `queue.md` and `EXECUTE-`/`FEEDBACK-`-prefixed or `PROMPT`/`prompt`/`handoff`-carrying names are scratch), a clean tree, a non-git cwd, or every recipe-named backend SATISFIED for the current tree. **A clean-tree PASS under a NON-SOLO review obligation is never silent about a latent arm:** when a `reviewed` or `council` recipe is configured, it NAMES every plan in flight and states that this gate arms as soon as the tree is dirty — the condition is discoverable BEFORE it blocks, instead of surfacing at the worst moment (a pending commit, or the landing of a feature worktree when main first turns dirty). Nothing is announced under a configured `solo` recipe or a non-git cwd, where the gate can never arm. **Satisfaction is ship-class-only on the LATEST NORMAL receipt (D3(b)):** per backend, the latest probe-free current-fingerprint receipt is selected FIRST and THEN verdict-checked — only the recognized ship-class vocabulary (`ship` / `ship with nits`) satisfies; a recognized NEGATIVE (`revise` / `rethink` / `rework`) is an authoritative VETO (an earlier ship never survives a later revise); an UNRECOGNIZED verdict (e.g. `unknown` from a dead run) fails CLOSED unconditionally — a later `unknown` never lets an earlier SHIP stand, and a fresh normal re-run supersedes it. **The ONLY escape is an explicit degrade RECORD** (`core-evidence.mjs degrade --backend <name> --reason "…"`, fingerprint-bound to the current tree) — and never all backends: ≥1 non-degraded ship-class receipt is required whenever ≥1 backend is configured; a malformed evidence store denies the escape fail-closed but never fails an independently-satisfied tree. Exit 1 otherwise — missing, **stale** (ANY edit after a review moves the fingerprint), ungrounded, vetoed, or unrecognized. Plan/diff receipts and continuations (`agy-review --continue`) are **informational-only**: after a fold, only a **fresh grounded re-run** (`codex-review code`; `agy-review code --facts @f`) restores green. **Probe receipts never attest either:** a `CODEX_PROBE=1` / `AGY_PROBE=1` review runs with the frontier-model/max-effort guard OFF, so the wrapper stamps `probe:true` and this checker excludes it — per receipt, so a real review at the same fingerprint still satisfies. **Silence is not a declaration:** a malformed *or* absent probe marker is rejected fail-closed and stated in the check line. Honest bound: receipts are **not authenticated** (a forger could write `probe:false` as easily as any other field) — like the rest of the receipt this is a self-discipline mechanism, not a security boundary.
9
9
  3. **Wire it as a gate by hand OR via the explicit-consent init preview — never without consent (AD-021/D9).** The candidate line for your own `docs/ai/gates.json`: `{ "id": "review-state", "title": "Review receipts current for the uncommitted tree", "cmd": "node <path-to-this-skill>/tools/review-state.mjs --check" }` — with the path your project actually reaches the kit by, QUOTED so a path with spaces survives, executable from the project root. The consent-gated gates-init preview (`${CLAUDE_SKILL_DIR}/references/modes/gates.md`, consent-fill section) offers exactly this entry — path resolved and quoted — ONLY when your `docs/ai/orchestration.json` declares `reviewed`/`council` on `plan-execution.review` (the slot this checker enforces); it writes nothing without your explicit yes. Once declared, the opt-in `${CLAUDE_SKILL_DIR}/references/modes/hook.md` auto-approves it like any other declared gate — and `run-gates --final` requires this check among its canonical core gates.
10
10
  4. **`--await [--timeout <s>]`** (BUGFREE-3 / AD-049) → BLOCK until every recipe-named backend is SATISFIED for the current tree (i.e. until `--check` would PASS), or the bounded timeout elapses (a loud exit 1; default 900s). Run it after dispatching the review bridges to WAIT for their receipts to land instead of hand-polling a pid: the durable completion signal is the **receipt**, never a process event (a harness "completed" notification fires early; a bridge's output late-flushes). It inherits the `--check` decision whole — a backend with a current-tree degrade RECORD stops being waited on (the shared decideCheck), so you never hand-`--await` around a known degrade. Still read-only (it re-reads the receipts + the evidence store); solo / no-plan / clean-tree resolve instantly.
11
11
 
@@ -33,6 +33,7 @@ import {
33
33
  ACKS_FILE,
34
34
  } from './recommendations.mjs';
35
35
  import { SKIPPED_READONLY } from './setup-backends.mjs';
36
+ import { LATENT_ARM_NOTICE } from './review-state.mjs';
36
37
 
37
38
  const KIT_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
38
39
 
@@ -41,6 +42,7 @@ const RECOMMENDATIONS_DOC = 'references/modes/recommendations.md';
41
42
  const UPGRADE_DOC = 'references/modes/upgrade.md';
42
43
  const VELOCITY_DOC = 'references/modes/velocity.md';
43
44
  const SETUP_DOC = 'references/modes/setup.md';
45
+ const REVIEW_STATE_DOC = 'references/modes/review-state.md';
44
46
 
45
47
  // A typed usage failure (exit 2) for the CLI parser — the codebase's typed-error idiom (no classes).
46
48
  const usageFail = (message) => Object.assign(new Error(message), { exitCode: 2 });
@@ -90,6 +92,10 @@ export const BINDINGS = Object.freeze([
90
92
  // (setup.md owns --refresh-placed; upgrade.md pastes its lines) — a reworded doc dropping the
91
93
  // outcome fails this pin plus the gate. The token tracks the exported SETUP constant.
92
94
  valueBinding('refresh-skipped-readonly', SKIPPED_READONLY, SKIPPED_READONLY, [SETUP_DOC, UPGRADE_DOC]),
95
+ // The "the tool knows and does not say" contract: a clean-tree PASS must still name a latent arm.
96
+ // It was a prose-only bar a doc could silently drop, so it is pinned to the live string the tool
97
+ // actually emits — a reworded doc dropping the notice fails this pin plus the gate.
98
+ valueBinding('latent-arm-notice', LATENT_ARM_NOTICE, LATENT_ARM_NOTICE, [REVIEW_STATE_DOC]),
93
99
  ].map((b) => Object.freeze(b)));
94
100
 
95
101
  // ── the pure checker (readText is injectable for hermetic tests) ────────────────────────
@@ -135,8 +141,9 @@ Usage:
135
141
 
136
142
  A CLOSED, exported registry binds each live code constant — the autonomy-doctor contract (the EXIT
137
143
  table, the status tokens, the trusted-dir allowlist), the recommendations/upgrade presentation
138
- contract (section header, empty line, verdict templates), the acks-store path, and the setup
139
- refresh degrade token — to the exact token its references/modes/*.md contract must carry, and
144
+ contract (section header, empty line, verdict templates), the acks-store path, the setup refresh
145
+ degrade token, and the review-state clean-tree latent-arm notice — to the exact token its
146
+ references/modes/*.md contract must carry, and
140
147
  asserts the CURRENT value renders into every bound file. A drifted doc, an unreadable bound file,
141
148
  or an absent token FAILS CLOSED.
142
149
 
@@ -13,7 +13,11 @@
13
13
  // absent config with no reviewer backend ready); when no plan is in flight (docs/plans/
14
14
  // holds no top-level .md that is not queue.md and not scratch by the naming convention:
15
15
  // prefixes EXECUTE- / FEEDBACK-, or a name containing PROMPT / prompt / handoff); when
16
- // the tree is clean (nothing to review); when the cwd is not a git work tree; and when
16
+ // the tree is clean (nothing to review under a non-solo review obligation the PASS
17
+ // still NAMES every plan in flight and states that the gate arms as soon as the tree
18
+ // turns dirty, so a latent arm is discoverable before it blocks a pending commit); when
19
+ // the cwd is not a git work tree
20
+ // (the gate can never arm there, so nothing is announced); and when
17
21
  // the obligations are SATISFIED: under `reviewed`, >=1 backend's latest normal receipt
18
22
  // attests SHIP-CLASS (ship / ship with nits) for the current tree; under `council`,
19
23
  // EVERY review-capable backend attests ship-class OR carries an explicit current-tree
@@ -172,6 +176,25 @@ const maskAdvisoryLine = (state) =>
172
176
 
173
177
  // ── plan-in-flight detector (the AD-038 naming convention; documented in queue.md) ─────
174
178
 
179
+ // The clean-tree PASS wording, split so the doc-parity registry can bind the forward-looking half
180
+ // to the mode doc — the notice is the contract, not decoration.
181
+ export const CLEAN_TREE_PASS = 'the working tree is clean — nothing to review';
182
+ export const LATENT_ARM_NOTICE = 'this gate arms as soon as the tree is dirty';
183
+
184
+ const UNSAFE_INLINE_SCALARS = /[\u007f-\u009f\u2028\u2029]/gu;
185
+ // A plan FILENAME reaches a one-line gate report. JSON quoting handles the C0 control bytes; the C1
186
+ // range (DEL + U+0080..U+009F, including the U+0085 line break) and the two Unicode line separators
187
+ // survive JSON.stringify and can still break a line, so they are escaped explicitly — every escape a
188
+ // valid JSON \uXXXX, so the token round-trips through JSON.parse. The plan set stays STRING-typed for
189
+ // its consumers (procedures / worktrees / the JSON surface); a name carrying INVALID UTF-8 bytes is a
190
+ // stated residual (queue: PARALLEL-TRACK-REDESIGN — two such names could collapse in the DISPLAY only,
191
+ // never in the gate decision, which keys on the receipt fingerprint, not the name).
192
+ export const quoteReportName = (name) =>
193
+ JSON.stringify(name).replace(
194
+ UNSAFE_INLINE_SCALARS,
195
+ (value) => `\\u${value.codePointAt(0).toString(16).padStart(4, '0')}`,
196
+ );
197
+
175
198
  // Scratch by the naming convention: EXECUTE-/FEEDBACK- prefixes, or a name carrying PROMPT/prompt/
176
199
  // handoff. queue.md is the series index, never a plan.
177
200
  export const isScratchPlanName = (name) =>
@@ -183,7 +206,7 @@ export const isScratchPlanName = (name) =>
183
206
  name.includes('handoff');
184
207
 
185
208
  // The in-flight plan files: top-level docs/plans/*.md minus queue.md minus scratch. [] when the
186
- // directory is absent (no plans → nothing in flight).
209
+ // directory is absent (no plans → nothing in flight). STRING-typed for every consumer.
187
210
  export const plansInFlight = (cwd, readdir = readdirSync) => {
188
211
  let entries;
189
212
  try {
@@ -392,7 +415,14 @@ export const decideCheck = (state) => {
392
415
  }
393
416
  if (state.plans.length === 0) return { code: 0, reason: `no plan in flight (docs/plans/ holds no active plan) — no receipt required${earlyNotes}` };
394
417
  if (state.fingerprint == null) return { code: 0, reason: `not a git work tree — nothing to fingerprint${earlyNotes}` };
395
- if (state.clean === true) return { code: 0, reason: `the working tree is clean nothing to review${earlyNotes}` };
418
+ // A clean tree PASSES, but never silently: the no-plan arm above already returned, so reaching
419
+ // here means >=1 plan IS in flight and this gate arms the moment the tree turns dirty. Naming
420
+ // each file forward-looking is what makes the arm discoverable BEFORE it blocks a pending commit.
421
+ if (state.clean === true) {
422
+ // Each name is byte-preserving and quoted because this reason is one gate-report line.
423
+ const named = state.plans.map((p) => quoteReportName(p)).join(', ');
424
+ return { code: 0, reason: `${CLEAN_TREE_PASS} — ${state.plans.length} plan(s) in flight: ${named} — ${LATENT_ARM_NOTICE}${earlyNotes}` };
425
+ }
396
426
  const exempt = new Set(state.degradedExempt);
397
427
  const satisfied = state.backends.filter((b) => b.state === 'current' && b.shipClass);
398
428
  const vetoed = state.backends.filter((b) => b.state === 'current' && !b.shipClass);
@@ -463,7 +493,7 @@ const formatHuman = (state, check) => {
463
493
  `review-state — ${ACTIVITY}.${SLOT} = ${state.obligations.recipe ?? '(unknowable)'} (${src})${state.requiredBackends.length ? ` → ${state.requiredBackends.join(' + ')}${state.obligations.perBackend ? '' : ' (any one, ship-class)'}` : ''}`,
464
494
  ];
465
495
  if (state.detectionWarning) lines.push(` ⚠ ${state.detectionWarning}`);
466
- lines.push(` plan in flight: ${state.plans.length ? state.plans.join(', ') : '(none)'}`);
496
+ lines.push(` plan in flight: ${state.plans.length ? state.plans.map((name) => quoteReportName(name)).join(', ') : '(none)'}`);
467
497
  if (state.fingerprint == null) lines.push(' tree: not a git work tree');
468
498
  else if (state.clean === true) lines.push(' tree: clean (nothing to review)');
469
499
  else lines.push(` tree fingerprint: ${state.fingerprint}`);
@@ -517,7 +547,8 @@ The ONLY escape for an unavailable backend under council is an explicit current-
517
547
  record (node core-evidence.mjs degrade) — and never all backends.
518
548
 
519
549
  --check exits 0/1 per the normative contract in the tool header: 0 for solo / no plan in flight /
520
- a clean tree / not-a-git-tree / obligations satisfied (reviewed: >=1 ship-class attestation;
550
+ a clean tree (under a non-solo review obligation the PASS names every plan in flight and its arm) /
551
+ not-a-git-tree / obligations satisfied (reviewed: >=1 ship-class attestation;
521
552
  council: every backend ship-class or degrade-recorded, >=1 real ship); 1 on a veto, an
522
553
  unrecognized verdict, a missing/stale/ungrounded/probe-only backend without a degrade record,
523
554
  an all-degraded tree, or a down detector with no configured recipe.