@khoavu882/doflow 2.0.0 → 2.0.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.
Files changed (113) hide show
  1. package/README.md +4 -5
  2. package/bin/doflow.js +1 -1
  3. package/core/.claude-plugin/plugin.json +1 -1
  4. package/core/.codex-plugin/plugin.json +1 -1
  5. package/core/.plugin/plugin.json +1 -1
  6. package/core/harnesses/claude/hooks/mcp-tool-guard.sh +4 -1
  7. package/core/harnesses/claude/hooks/pre-implement-gate.sh +4 -1
  8. package/core/harnesses/claude/settings/settings.json +8 -9
  9. package/core/harnesses/codex/hooks/hooks.json +0 -10
  10. package/core/harnesses/codex/hooks/mcp-tool-guard.sh +4 -1
  11. package/core/harnesses/codex/hooks/pre-implement-gate.sh +4 -1
  12. package/core/harnesses/copilot/hooks/doflow-lib.sh +4 -3
  13. package/core/harnesses/copilot/hooks/doflow.json +0 -6
  14. package/core/harnesses/kiro/agents/doflow.json +0 -10
  15. package/core/harnesses/kiro/hooks/doflow.json +0 -10
  16. package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +7 -3
  17. package/core/harnesses/kiro/hooks/pre-implement-gate.sh +7 -3
  18. package/core/harnesses/pi/extensions/doflow-guard.js +3 -2
  19. package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +1 -1
  20. package/core/harnesses/shared/hooks/adapters/antigravity.js +1 -1
  21. package/core/harnesses/shared/hooks/bridge/bridge.sh +35 -25
  22. package/core/harnesses/shared/hooks/bridge/envelope.jq +1 -1
  23. package/core/harnesses/shared/hooks/bridge/respond.sh +6 -0
  24. package/core/harnesses/shared/hooks/policies/capture-failure.sh +1 -1
  25. package/core/harnesses/shared/hooks/policies/deny-json.sh +23 -1
  26. package/core/harnesses/shared/hooks/policies/mcp-policy.conf +1 -1
  27. package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +4 -6
  28. package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +20 -21
  29. package/core/harnesses/shared/hooks/policies/stop-check.sh +7 -6
  30. package/core/harnesses/shared/hooks/policies/subagent-audit.sh +1 -1
  31. package/core/harnesses/shared/hooks/stream-hook-runner.js +11 -12
  32. package/core/registry/contracts.json +1 -1
  33. package/core/registry/contribution-points.json +5 -2
  34. package/core/registry/gates.json +5 -5
  35. package/core/registry/harnesses.json +9 -9
  36. package/core/registry/intake.json +53 -0
  37. package/core/registry/lifecycle.json +133 -711
  38. package/core/registry/package-templates/intake-category/files/category.json +8 -0
  39. package/core/registry/package-templates/intake-category/template.json +4 -0
  40. package/core/registry/packages/doflow/doflow-package.json +2 -2
  41. package/core/registry/rules.json +8 -8
  42. package/core/registry/settings.json +0 -4
  43. package/core/registry/storage.json +8 -0
  44. package/core/registry/telemetry.json +1 -1
  45. package/core/registry/verification.json +0 -5
  46. package/core/shared/guidance/references/DOFLOW_CHAIN.md +4 -4
  47. package/core/shared/guidance/references/ORCHESTRATION.md +2 -0
  48. package/core/shared/guidance/references/WORKFLOW_HANDOFF.md +19 -1
  49. package/core/shared/guidance/rules/RULES.md +2 -2
  50. package/core/shared/scripts/doflow/bash/validate-artifacts.sh +1 -1
  51. package/core/shared/scripts/doflow/bin/doflow-run +5 -2
  52. package/core/shared/skills/do/SKILL.md +3 -3
  53. package/core/shared/skills/do-brainstorm/SKILL.md +26 -24
  54. package/core/shared/skills/do-brainstorm/references/handoff_branches.md +2 -1
  55. package/core/shared/skills/do-code-review/SKILL.md +7 -4
  56. package/core/shared/skills/do-design/SKILL.md +5 -3
  57. package/core/shared/skills/do-diagnose/SKILL.md +4 -2
  58. package/core/shared/skills/do-document/SKILL.md +1 -0
  59. package/core/shared/skills/do-document/references/embedded-research.md +1 -1
  60. package/core/shared/skills/do-execute-plan/SKILL.md +4 -2
  61. package/core/shared/skills/do-execute-plan/references/run_position.md +3 -1
  62. package/core/shared/skills/do-execute-plan/references/scaffold.md +1 -1
  63. package/core/shared/skills/do-flow/SKILL.md +4 -4
  64. package/core/shared/skills/do-flow/references/task_classes.md +5 -5
  65. package/core/shared/skills/do-implement/SKILL.md +9 -7
  66. package/core/shared/skills/do-plan/SKILL.md +9 -6
  67. package/core/shared/workflows/do-flow-chain.md +2 -0
  68. package/package.json +1 -1
  69. package/src/adapters/claude/index.js +23 -2
  70. package/src/adapters/codex/hooks.js +39 -3
  71. package/src/adapters/codex/index.js +13 -0
  72. package/src/adapters/copy-tree.js +1 -2
  73. package/src/cli/commands/status.js +1 -1
  74. package/src/cli/index.js +20 -8
  75. package/src/cli/runtime-commands.js +12 -4
  76. package/src/lifecycle/coverage.js +1 -30
  77. package/src/lifecycle/hook-tier.js +2 -2
  78. package/src/release/docs-blocks.js +32 -52
  79. package/src/runtime/context/pointer.js +67 -12
  80. package/src/runtime/context-pack.js +4 -1
  81. package/src/runtime/evidence-ledger.js +18 -0
  82. package/src/runtime/health.js +59 -1
  83. package/src/runtime/implementation-gate.js +4 -4
  84. package/src/runtime/intake/engine.js +677 -0
  85. package/src/runtime/intake/index.js +136 -0
  86. package/src/runtime/intake/manifest.js +140 -0
  87. package/src/runtime/intake/taxonomy.js +206 -0
  88. package/src/runtime/lifecycle/cli.js +73 -5
  89. package/src/runtime/lifecycle/followup.js +32 -6
  90. package/src/runtime/lifecycle/overview.js +2 -1
  91. package/src/runtime/packages/points.js +10 -0
  92. package/src/runtime/roadmap/attendance.js +137 -0
  93. package/src/runtime/roadmap/gates.js +6 -3
  94. package/src/runtime/roadmap/loop.js +113 -30
  95. package/src/runtime/roadmap/merge.js +17 -8
  96. package/src/runtime/roadmap/rows.js +13 -4
  97. package/src/runtime/roadmap/seed.js +153 -41
  98. package/src/runtime/roadmap/worktree-cleanup.js +147 -0
  99. package/src/runtime/settings/registry.js +1 -1
  100. package/src/runtime/slots.js +1 -1
  101. package/src/runtime/task-classifier.js +39 -4
  102. package/src/runtime/task-state.js +2 -3
  103. package/src/runtime/verb-view.js +267 -0
  104. package/src/runtime/verification/engine.js +0 -45
  105. package/src/runtime/worker/launch.js +5 -37
  106. package/src/runtime/worker/readiness.js +2 -2
  107. package/src/runtime/workflow-orchestrator.js +33 -5
  108. package/core/harnesses/claude/hooks/pre-bash-guard.sh +0 -10
  109. package/core/harnesses/codex/hooks/pre-bash-guard.sh +0 -10
  110. package/core/harnesses/copilot/hooks/doflow-pre-bash-guard.sh +0 -9
  111. package/core/harnesses/kiro/hooks/pre-bash-guard.sh +0 -25
  112. package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +0 -105
  113. package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +0 -579
@@ -125,10 +125,10 @@ A `caller-not-a-stage` rejection is about **you**, not about the class. Answer i
125
125
  stage — propose one of those, or hand the work to the skill this class names for the stage you
126
126
  meant. Re-proposing the same class will be rejected the same way.
127
127
 
128
- ## The hard hook does not know about classes
128
+ ## The advisory hook does not know about classes
129
129
 
130
- `pre-implement-gate.sh` keys on branch and artifact state (what it denies: SKILL.md step 7): if the
131
- current branch already carries a feature directory the gate rejects, it denies source edits
130
+ `pre-implement-gate.sh` keys on branch and artifact state (what it advises on: SKILL.md step 7): if the
131
+ current branch already carries a feature directory the gate flags, it advises on source edits
132
132
  regardless of the class in play — so a `bug` or `trivial-edit` workflow started on that branch will
133
- be blocked at its implementation stage. Surface the hook's message as written. Do not route around
134
- it, and do not create placeholder artifacts to satisfy it.
133
+ get that advisory at its implementation stage. Surface the hook's message as written. Do not route
134
+ around it, and do not create placeholder artifacts to satisfy it.
@@ -53,6 +53,8 @@ Run every command below from the project root — the walk-up starts at `$PWD`.
53
53
 
54
54
  **Stop when** every work-list finding the contract names has an answer or a stated gap, **and** the last round produced no new work-list finding. A round that only restates what you already have is the last round. Report the remaining gaps rather than continuing.
55
55
 
56
+ Once per stage, after `feature_slug` resolves, run `"$DOFLOW" attendance --task-id "<feature_slug>"`; `unattended` → `references/WORKFLOW_HANDOFF.md` § Unattended stage.
57
+
56
58
  2. **Resolve the task id, then evaluate readiness — skip only when standalone**:
57
59
  - `<task id>` is the same unit every recording stage keys on: the plan task id when one exists,
58
60
  otherwise the feature slug that step 1's `"$DOFLOW" paths --json` resolved, or the issue id you
@@ -73,9 +75,9 @@ Run every command below from the project root — the walk-up starts at `$PWD`.
73
75
  of `bug`, `refactor`, `dependency-change` or `trivial-edit` the recorded work already fits.
74
76
  `do-implement` never serves `feature`; that class's implementation stage is `/do-execute-plan`.
75
77
  ```bash
76
- "$DOFLOW" classify --task-class "<proposed>" --calling-skill do-implement --json
78
+ "$DOFLOW" classify --task-class "<proposed>" --calling-skill do-implement --view brief --json
77
79
  ```
78
- Branch on the returned `outcome`, not the exit code. `ACCEPTED` — the returned `workflow` names
80
+ Branch on `outcome`, not the exit code. `ACCEPTED` — the returned `workflow` names
79
81
  the validated class to evaluate readiness against. `REJECTED` — stop, print `message` verbatim
80
82
  (it names `validClasses`), ask the user to choose from that set, then re-validate; never
81
83
  substitute `feature` for the class that was rejected.
@@ -118,8 +120,8 @@ Run every command below from the project root — the walk-up starts at `$PWD`.
118
120
  established.
119
121
  - The repository's `pre-implement-gate` hook checks file existence (`requirement.md` /
120
122
  `design.md` / `plan.md`) on every `Edit`/`Write` call regardless of which skill is editing. On
121
- a harness with an edit hook it also denies the edit of a task held to a readiness record that
122
- is not `READY`; this step writes that record, and is not a way around the hook.
123
+ a harness with an edit hook it also advises when a held task's readiness record is not
124
+ `READY`; the hook never denies. This step writes that record, and is not a way around it.
123
125
 
124
126
  3. **Understand before writing**:
125
127
  - Read the files the change touches and their immediate neighbors — naming conventions, error
@@ -146,8 +148,8 @@ Run every command below from the project root — the walk-up starts at `$PWD`.
146
148
  unimplemented mock functions, or `throw new Error('Not implemented')` placeholders in the
147
149
  produced change. Every changed component must be fully wired and functional.
148
150
  - Before the first source edit, a run for this task needs step 2's recorded readiness to be
149
- `READY`: the hook denies the edit otherwise where there is one, and elsewhere step 7's handoff
150
- is refused after the edits are made. A standalone `NEEDS_EVIDENCE` does not pass it.
151
+ `READY`: the edit hook only advises, and the runtime refuses step 7's handoff after the edits are
152
+ made. A standalone `NEEDS_EVIDENCE` does not pass it.
151
153
  - Keep it scoped to what was asked — no bonus refactors, no speculative abstractions (the same
152
154
  scope-discipline rule the chain's own artifacts would otherwise remind you of; here, hold
153
155
  yourself to it directly).
@@ -215,6 +217,6 @@ advance past the mutating stage only this skill may complete.
215
217
  multi-task checklist through specialist subagents — that is `/do-execute-plan`'s job once a real
216
218
  plan exists; silently override the repository's own `pre-implement-gate` hook. That hook triggers
217
219
  on any `Edit`/`Write` call, not on which skill made it — a branch mid-chain with a started feature
218
- but no `design.md` still gets blocked here exactly as it would anywhere else, and that is by
220
+ but no `design.md` still gets the same advisory here as it would anywhere else, and that is by
219
221
  design, not a gap this skill works around. Resolve a workflow gate, or start a workflow run for a
220
222
  standalone implementation.
@@ -37,13 +37,15 @@ Run every command below from the project root — the walk-up starts at `$PWD`.
37
37
 
38
38
  `feature_slug` `null` with a non-empty `candidate_slugs` is an unresolved choice, not "no active feature": ask one option per entry, re-resolve with `"$DOFLOW" paths --json --slug="<chosen>"`, and use that slug for the rest of this flow. `/do-flow` passing `--slug` already resolves it — no prompt then.
39
39
 
40
+ Once per stage, after `feature_slug` resolves, run `"$DOFLOW" attendance --task-id "<feature_slug>"`; `unattended` → `references/WORKFLOW_HANDOFF.md` § Unattended stage.
41
+
40
42
  2. **Propose one task class; the runtime validates it** — name exactly one class id for the work
41
43
  being planned. `/do-flow` passes one when it invoked this skill; a user who named one settles it;
42
44
  otherwise derive it from `requirement.md` and `design.md`, not from their mere existence.
43
45
  ```bash
44
- "$DOFLOW" classify --task-class "<proposed>" --calling-skill do-plan --json
46
+ "$DOFLOW" classify --task-class "<proposed>" --calling-skill do-plan --view brief --json
45
47
  ```
46
- Branch on the returned `outcome` field, not the exit code.
48
+ Branch on `outcome`, not the exit code.
47
49
  - **`ACCEPTED`** — the returned `workflow` is this run's plan of record; read `stages`, `gates` and `handoff` off it rather than from memory.
48
50
  - **`REJECTED`** — **stop.** Print `message` verbatim (it already names `validClasses` and any `suggestions`), ask the user to choose from `validClasses`, then re-validate. Never substitute `feature`.
49
51
  A rejection may be about **you** rather than the class (`reason: caller-not-a-stage`). Then the fix is to propose one of the classes in `fit.hostingClasses`, or to hand the work to the skill this class names for the stage you meant — not to re-propose the same class.
@@ -59,8 +61,8 @@ stages to implementation, and writing them a `plan.md` adds a gate their workflo
59
61
  not have.
60
62
 
61
63
  3. **Precondition (advisory)** — if `has_requirement` or `has_design` is false, warn and offer to
62
- run `/do-brainstorm` / `/do-design` first. This gate is **advisory** (skippable), not the hard
63
- hook gate.
64
+ run `/do-brainstorm` / `/do-design` first. This gate is **advisory** (skippable), like the edit
65
+ hook.
64
66
  4. **Read inputs** — compile the recorded prior context first, using the same task id rule step 10
65
67
  uses below (the plan task id for anything already scoped to a single `- [ ]` task, otherwise the
66
68
  feature slug):
@@ -124,7 +126,7 @@ Filling §8's task dependency graph, read the guidance tree's `references/DIAGRA
124
126
  SHOULD`) and PASS/FAIL; a base MUST that tier-2 makes SHOULD names that overlay version in its
125
127
  row. On a violation, revise the approach first. A MUST marked FAIL or left
126
128
  without a row refuses step 11's handoff; a SHOULD FAIL is a note. That refusal is the runtime's,
127
- beside the hard hook that covers artifact existence (`references/DOFLOW_CHAIN.md` scopes both).
129
+ beside the advisory hook on artifact existence (`references/DOFLOW_CHAIN.md` scopes both).
128
130
  There is no waiver: revise the plan, or a person amends the constitution with `/do-constitution`.
129
131
  7. **Decompose into Tasks (section 8)** — dependency-ordered, `[US#]`-traced to the requirement's
130
132
  user stories, owner+files named per task, with checkpoints and completion criteria.
@@ -197,7 +199,8 @@ Item schema, provenance rules, and the refused-field list: the guidance tree's `
197
199
  ```bash
198
200
  "$DOFLOW" render-audit --slug="<task id>" --json
199
201
  ```
200
- Leave any returned approval gate for `/do-flow` or `/do-execute-plan` to present.
202
+ Leave any returned approval gate for `/do-flow` or `/do-execute-plan` to present (unattended: the loop
203
+ decides it by policy).
201
204
 
202
205
  12. **Stop** — report the plan path, Constitution Check result, the task count (`[P]`/sequential),
203
206
  and the derived branch name/repo count when the Repo Branch Plan is populated.
@@ -13,5 +13,7 @@ delivery sequence, pausing where human judgment matters:
13
13
  5. Execute `/do-execute-plan --scope next` repeatedly until the plan's checklist is complete.
14
14
  6. Run `/do-test`, then `/do-code-review`, and report both results before proposing any commit.
15
15
 
16
+ Unattended (no person watching), stop at each pause above and report; silence is never approval.
17
+
16
18
  If the user comments directly inside any specification file, re-read it, apply the requested
17
19
  changes, and ask for approval again before moving on.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khoavu882/doflow",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "DoFlow config installer — npx CLI for the registry-driven multi-harness install/update/remove lifecycle",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -29,6 +29,12 @@ const SETTINGS_RENDERER = 'claude-settings';
29
29
  * its install destination is declared (paths.statuslineScript).
30
30
  */
31
31
  const SETTINGS_FILES = ['settings.json', 'keybindings.json'];
32
+ /** Hook entries an earlier DoFlow wrote into settings.json and no longer ships. The update prunes their
33
+ * scripts, so a merge strips these entries from the live file (and a remove does too) instead of leaving a
34
+ * hook that points at a missing script. Written in the global form; `retiredHooks` rewrites the prefix for project scope. */
35
+ const RETIRED_SETTINGS_HOOKS = {
36
+ PreToolUse: [{ matcher: 'Bash', hooks: [{ type: 'command', command: `${GLOBAL_HOOK_PREFIX}pre-bash-guard.sh` }] }],
37
+ };
32
38
 
33
39
  function sha256(value ) {
34
40
  return crypto.createHash('sha256').update(value).digest('hex');
@@ -194,6 +200,18 @@ function createClaudeAdapter({ declaredPaths = declaredHarnessPaths().claude } =
194
200
  const { hooks, ...rest } = parsed;
195
201
  return { raw: serializeSettings(rest), droppedHooks: hooks };
196
202
  }
203
+ function retiredHooks(scope ) {
204
+ const raw = JSON.stringify(RETIRED_SETTINGS_HOOKS);
205
+ return JSON.parse(scope === 'global' ? raw : raw.split(GLOBAL_HOOK_PREFIX).join(PROJECT_HOOK_PREFIX));
206
+ }
207
+ /** The managed settings text with the retired hook entries added, so a remove strips them as well. */
208
+ function withRetiredHooks(raw , scope ) {
209
+ let parsed;
210
+ try { parsed = JSON.parse(raw); } catch { return raw; }
211
+ const hooks = { ...(parsed.hooks || {}) };
212
+ for (const [event, entries] of Object.entries (retiredHooks(scope))) hooks[event] = [...(hooks[event] || []), ...entries];
213
+ return JSON.stringify({ ...parsed, hooks });
214
+ }
197
215
  /** The live settings without DoFlow's own entries of `hooks`; every other key is left alone. */
198
216
  function withoutManagedHooks(current , hooks ) {
199
217
  if (!hooks || !current || typeof current !== 'object' || current.hooks === undefined) return current;
@@ -246,7 +264,9 @@ function createClaudeAdapter({ declaredPaths = declaredHarnessPaths().claude } =
246
264
  // entries, so a drifted file is safe to process rather than a reason to refuse.
247
265
  changes.push({ assetId: asset.id, target, operation: 'remove', ownershipIdentity: `doflow:claude:settings:${fileName}`,
248
266
  ...settingsChangeExtras(fileName), fingerprint: prev.fingerprint,
249
- managed: settingsContent(fileName, fs.readFileSync(sourceAbs, 'utf8'), scope),
267
+ managed: fileName === 'settings.json'
268
+ ? withRetiredHooks(settingsContent(fileName, fs.readFileSync(sourceAbs, 'utf8'), scope), scope)
269
+ : settingsContent(fileName, fs.readFileSync(sourceAbs, 'utf8'), scope),
250
270
  projection: { renderer: SETTINGS_RENDERER } });
251
271
  }
252
272
  if (previous.get(statuslineScriptName)) {
@@ -286,7 +306,8 @@ function createClaudeAdapter({ declaredPaths = declaredHarnessPaths().claude } =
286
306
  const currentRaw = fs.readFileSync(target, 'utf8');
287
307
  let merged;
288
308
  try {
289
- merged = mergeSettings(withoutManagedHooks(JSON.parse(currentRaw), tiered.droppedHooks), JSON.parse(managedRaw));
309
+ const live = fileName === 'settings.json' ? withoutManagedHooks(JSON.parse(currentRaw), retiredHooks(scope)) : JSON.parse(currentRaw);
310
+ merged = mergeSettings(withoutManagedHooks(live, tiered.droppedHooks), JSON.parse(managedRaw));
290
311
  } catch {
291
312
  conflicts.push(`${fileName} is not valid JSON and cannot be merged`);
292
313
  continue;
@@ -17,6 +17,41 @@ const SUPPORTED_EVENTS = new Set([
17
17
  'UserPromptSubmit', 'SubagentStop', 'Stop', 'SessionStart', 'SubagentStart', 'SessionEnd',
18
18
  ]);
19
19
 
20
+ // An earlier DoFlow wired a pre-bash-guard front door into hooks.json and deployed its script. Both
21
+ // are gone from the shipped source; a merge drops the entry from the live file so an updated install
22
+ // does not keep a hook that runs a script nothing refreshes, and the script goes with it. Only the
23
+ // exact command DoFlow shipped is dropped: a user's own hook that happens to name a script of the same
24
+ // file name stays.
25
+ const RETIRED_HOOK_SCRIPT = 'pre-bash-guard.sh';
26
+ const RETIRED_HOOK_COMMAND = 'bash "$(root=$(git rev-parse --show-toplevel 2>/dev/null) && [ -f "$root/.codex/hooks/pre-bash-guard.sh" ] && printf \'%s/.codex/hooks/pre-bash-guard.sh\' "$root" || printf \'%s/hooks/pre-bash-guard.sh\' "${CODEX_HOME:-$HOME/.codex}")"';
27
+
28
+ function withoutRetiredHooks(hooks ) {
29
+ const result = {};
30
+ for (const [event, groups] of Object.entries (hooks || {})) {
31
+ if (!Array.isArray(groups)) { result[event] = groups; continue; }
32
+ const kept = groups.map((group ) => (Array.isArray(group?.hooks)
33
+ ? { ...group, hooks: group.hooks.filter((handler ) => handler?.command !== RETIRED_HOOK_COMMAND) }
34
+ : group)).filter((group ) => !Array.isArray(group?.hooks) || group.hooks.length > 0);
35
+ if (kept.length) result[event] = kept;
36
+ }
37
+ return result;
38
+ }
39
+
40
+ /** Whether the retired front door is still on disk as DoFlow's own (it names the bridge call it was written with). */
41
+ function ownsRetiredHookScript(destinationHooksDir , fsImpl = fs) {
42
+ const target = path.join(destinationHooksDir, RETIRED_HOOK_SCRIPT);
43
+ try {
44
+ return fsImpl.lstatSync(target).isFile() && fsImpl.readFileSync(target, 'utf8').includes('doflow_bridge codex PreToolUse pre-bash-guard');
45
+ } catch { return false; }
46
+ }
47
+
48
+ /** Delete the retired front door when it is still DoFlow's own. */
49
+ function removeRetiredHookScript(destinationHooksDir , fsImpl = fs, dryRun = false) {
50
+ if (!ownsRetiredHookScript(destinationHooksDir, fsImpl)) return 0;
51
+ if (!dryRun) { try { fsImpl.unlinkSync(path.join(destinationHooksDir, RETIRED_HOOK_SCRIPT)); } catch { return 0; } }
52
+ return 1;
53
+ }
54
+
20
55
  function validationError(errors , message ) { errors.push(message); }
21
56
 
22
57
  function validateHooksConfig(config ) {
@@ -148,7 +183,7 @@ function planCodexHooks({ config, sourceFile, sourceHooksDir, destinationContext
148
183
  try {
149
184
  const existing = JSON.parse(original);
150
185
  if (existing?.hooks && desired?.hooks) {
151
- mergedDesired = { ...existing, ...desired, hooks: mergeHooks(existing.hooks, desired.hooks) };
186
+ mergedDesired = { ...existing, ...desired, hooks: mergeHooks(withoutRetiredHooks(existing.hooks), desired.hooks) };
152
187
  }
153
188
  } catch {
154
189
  // malformed existing will be flagged or left for schema validation
@@ -167,7 +202,7 @@ function planCodexHooks({ config, sourceFile, sourceHooksDir, destinationContext
167
202
  return { ok: false, status: 'conflict', destination, original, changes: [], commands,
168
203
  errors: scripts.edited.map((name ) => `Codex hook script '${name}' was modified outside DoFlow`) };
169
204
  }
170
- const scriptsOnly = !configChanged && scripts.stale.length > 0;
205
+ const scriptsOnly = !configChanged && (scripts.stale.length > 0 || ownsRetiredHookScript(destinationHooksDir, fsImpl));
171
206
  const changed = configChanged || scriptsOnly;
172
207
  // The scripts deployCodexHooks copies beside hooks.json, so a backup of this change holds them too.
173
208
  const companionTargets = [...new Set([
@@ -204,6 +239,7 @@ function deployCodexHooks(plan , { dryRun = false, fsImpl = fs }
204
239
  fsImpl.copyFileSync(source, target);
205
240
  fsImpl.chmodSync(target, fsImpl.statSync(source).mode & 0o777);
206
241
  }
242
+ removeRetiredHookScript(plan.destinationHooksDir, fsImpl);
207
243
  return { ...plan, applied: true };
208
244
  }
209
245
 
@@ -220,7 +256,7 @@ function shippedHookScripts(scriptsDir , fsImpl = fs) {
220
256
  * user edited or added stays. The directory goes only once it is empty. */
221
257
  function removeCodexHookScripts({ scriptsDir, destinationHooksDir } = {}, { dryRun = false, fsImpl = fs } = {}) {
222
258
  if (!destinationHooksDir || !fsImpl.existsSync(destinationHooksDir)) return { removed: 0 };
223
- let removed = 0;
259
+ let removed = removeRetiredHookScript(destinationHooksDir, fsImpl, dryRun);
224
260
  for (const name of shippedHookScripts(scriptsDir, fsImpl)) {
225
261
  const target = path.join(destinationHooksDir, name);
226
262
  // lstat, not stat: a symlink (even to a file with the shipped bytes) is not DoFlow's copy.
@@ -503,9 +503,22 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
503
503
  hooks = { ...hooks, ok: false, status: 'conflict', changes: [], errors: ['Codex hooks.json was modified outside DoFlow'] };
504
504
  }
505
505
  }
506
+ if (hooks.ok) hooks = withStaleScriptReleases(hooks, scriptRows(neutralResources, context));
506
507
  return hooks;
507
508
  }
508
509
 
510
+ /** A script row for a name the source no longer ships (a retired front door) is released with the
511
+ * plan, so the ledger does not keep a row for a file the update deletes. The script itself goes by
512
+ * its bytes (deployCodexHooks), so this only drops the row. */
513
+ function withStaleScriptReleases(hooks , rows ) {
514
+ const shipped = new Set(hooks.scriptNames);
515
+ const releases = rows.filter((row ) => !shipped.has(row.identity)).map((row ) => ({ type: 'remove', assetId: row.assetId, identity: row.identity,
516
+ target: row.target, ownershipIdentity: row.ownershipIdentity, kind: row.kind, release: true }));
517
+ if (!releases.length) return hooks;
518
+ return { ...hooks, changes: [...hooks.changes, ...releases],
519
+ ...(hooks.status === 'unchanged' ? { status: 'change', scriptsOnly: true } : {}) };
520
+ }
521
+
509
522
  /** Agents/hooks share one removing-vs-planning fork: removal always owns both via the neutral
510
523
  * ledger's own records, regardless of what native input the caller provided this run. */
511
524
  /** A removal drops the rows of the hook scripts too. The scripts themselves go by their bytes
@@ -371,14 +371,13 @@ function kiroHook({ name, trigger, matcher, script, timeout }
371
371
  };
372
372
  }
373
373
 
374
- /** The three preToolUse hooks every DoFlow-rendered Kiro agent carries. The MCP guard uses the
374
+ /** The two preToolUse hooks every DoFlow-rendered Kiro agent carries. The MCP guard uses the
375
375
  * "*" matcher: Kiro documents "@<server>" (every tool of one server) and "*" but no all-MCP
376
376
  * matcher, and mcp-tool-guard.sh exits 0 immediately for any non-MCP tool name, so "*" costs one
377
377
  * fast no-op for built-in tools and still covers every MCP server. */
378
378
  function kiroPreToolUseHooks() {
379
379
  return [
380
380
  kiroHook({ name: 'doflow-pre-implementation-gate', trigger: 'preToolUse', matcher: 'write', script: 'pre-implement-gate.sh', timeout: 10 }),
381
- kiroHook({ name: 'doflow-pre-bash-guard', trigger: 'preToolUse', matcher: 'shell', script: 'pre-bash-guard.sh', timeout: 10 }),
382
381
  kiroHook({ name: 'doflow-mcp-tool-guard', trigger: 'preToolUse', matcher: '*', script: 'mcp-tool-guard.sh', timeout: 10 }),
383
382
  ];
384
383
  }
@@ -136,7 +136,7 @@ function cmdStatus(o ) {
136
136
  if (line) console.log(line);
137
137
  if (status.errors.length) console.log(` ${`${label} issues:`.padEnd(24)}${status.errors.join('; ')}`);
138
138
  if (harness === 'codex') {
139
- console.log(' Codex capability gaps: no Claude-only event emulation is installed; review docs/capability-map.md#codex-capability-detail');
139
+ console.log(' Codex capability gaps: no Claude-only event emulation is installed');
140
140
  }
141
141
  }
142
142
  if (ctx.registry) {
package/src/cli/index.js CHANGED
@@ -150,7 +150,7 @@ function parseArgs(argv ) {
150
150
  case '--action': {
151
151
  const val = argv[i + 1];
152
152
  // `review`, `protocol`, `config`, `primary` and `package` answer a missing value as usage, exit 2; other verbs keep exit 1.
153
- if (val === undefined || val.startsWith('-')) { console.error(`doflow: ${a} requires a value`); process.exit(o.cmd === 'review' || o.cmd === 'protocol' || o.cmd === 'config' || o.cmd === 'primary' || o.cmd === 'fleet' || o.cmd === 'transcript' || o.cmd === 'package' ? 2 : 1); }
153
+ if (val === undefined || val.startsWith('-')) { console.error(`doflow: ${a} requires a value`); process.exit(o.cmd === 'review' || o.cmd === 'protocol' || o.cmd === 'config' || o.cmd === 'primary' || o.cmd === 'fleet' || o.cmd === 'transcript' || o.cmd === 'package' || o.cmd === 'worktree' ? 2 : 1); }
154
154
  o.action = val; o.actionGiven = true; i++; break;
155
155
  }
156
156
  case '--task-class': {
@@ -248,7 +248,7 @@ function parseArgs(argv ) {
248
248
 
249
249
  /** Verbs that answer an unrecognised flag as a usage error (exit 2); every other verb keeps exit 1. */
250
250
  function unknownFlagExit(cmd ) {
251
- return cmd === 'protocol' || cmd === 'config' || cmd === 'rules' || cmd === 'review' ? 2 : 1;
251
+ return cmd === 'protocol' || cmd === 'config' || cmd === 'rules' || cmd === 'review' || cmd === 'worktree' || cmd === 'attendance' ? 2 : 1;
252
252
  }
253
253
 
254
254
  /** Longest part of an unrecognised argument echoed back; the rest is cut and marked. */
@@ -277,6 +277,7 @@ const RUNTIME_STRING_FLAGS = new Map([
277
277
  ['--request', 'request'], // classify: request text for an advisory second opinion
278
278
  ['--request-file', 'requestFile'], // classify: request text read from a file
279
279
  // decision --action add. `--statement`, `--stage`, `--slug` and `--batch` above are shared.
280
+ ['--run-id', 'runId'], // intake --action undo: the sort run to undo (default: the latest)
280
281
  ['--topic', 'topic'], // the subject key a decision is live on
281
282
  ['--channel', 'channel'], // question | gate | prompt | default | resolution
282
283
  ['--supersedes', 'supersedes'], // comma-separated DEC-### ids this decision replaces
@@ -434,8 +435,9 @@ const RUNTIME_STRING_FLAGS = new Map([
434
435
  ['--dir', 'dir'], // package --action new: authored package target directory
435
436
  ['--name', 'name'], // slot: the slot's name, 1 to 64 characters of a-z, 0-9 and - (115 IC-008)
436
437
  ['--label', 'label'], // slot --action run: the holder's label, at most 80 characters (115 IC-008)
437
- ['--cursor', 'cursor'], // transcript --action read: the cursor an earlier read returned (093 IC-022)
438
- ['--limit', 'limit'], // transcript --action read: at most this many events, validated in the verb (093 IC-022)
438
+ ['--cursor', 'cursor'], // transcript --action read: the cursor an earlier read returned (093 IC-022) · followup --action list: the nextCursor of an earlier page (137 IC-004)
439
+ ['--limit', 'limit'], // transcript --action read: at most this many events, validated in the verb (093 IC-022) · followup --action list: the page size (137 IC-004)
440
+ ['--view', 'view'], // followup --action list, lifecycle --action overview, classify: brief or full, validated in the verb (137 IC-001)
439
441
  ['--window', 'window'], // transcript --action stats: minutes, 1 to 60, validated in the verb (093 IC-023)
440
442
  ['--port', 'port'], // ui: the Console's port, 0 to 65535, checked in commands/ui.ts (092 IC-008)
441
443
  ]);
@@ -546,7 +548,7 @@ Commands:
546
548
  context-grant Grant one compacted summary to a launched run and lineage (--summary, --to-run, --to-lineage)
547
549
  retrieval-plan Declare a stage's information needs, attempt retrieval, then report every declared item
548
550
  outcome Record a task's terminal state with its basis, or show it (--task-id)
549
- classify Validate a proposed task class and return its workflow (--task-class)
551
+ classify Validate a proposed task class and return its workflow (--task-class [--view brief|full])
550
552
  workflow Resolve a task class to its stages, gates and readiness templates
551
553
  orchestrate Start or resume a run: complete stages, decide gates (--action)
552
554
  research-request Open, list or resolve a feature-stage research question (--task-id)
@@ -577,15 +579,16 @@ Commands:
577
579
  config unset <key> --layer user|project|local [--json] remove the key from that layer's file
578
580
  scaffold Emit the reviewable code scaffold the active feature's artifacts imply
579
581
  decision Register, list or compact the active feature's decisions (--action init|add|list|compact)
580
- followup Record, list, take, settle, promote or report what a feature left unfinished (--action add|list|take|settle|promote|report)
581
- lifecycle Open follow-ups, feature tracking, releases and status for the project (--action overview|init|release|status|merged)
582
+ intake Sort, preview, undo, list or file prompt text for the active feature's additional-context folder (--action sort|preview|undo|list|file-prompt)
583
+ followup Record, list, take, settle, promote or report what a feature left unfinished (--action add|list|take|settle|promote|report; list: [--view brief|full] [--cursor <c>] [--limit <n>] [--ids <FU-id,...>])
584
+ lifecycle Open follow-ups, feature tracking, releases and status for the project (--action overview|init|release|status|merged; overview: [--view brief|full])
582
585
  goal Add, check, link, list or close a goal and its checklist (--action add|item|check|link|list|done)
583
586
  failure List, settle or switch off the failures DoFlow captured on this machine (--action list|settle|capture)
584
587
  (a value beginning with '-' must be written --flag=value)
585
588
  roadmap Session-driven autopilot runner (--action render|status|next|audit|init-policy [--check] [--row <id>] [--run <id>]).
586
589
  roadmap --action init-policy [--commit] writes the gate policy for review, or with --commit commits it onto local develop when develop is not checked out in a worktree (otherwise it prints the commands to run there; never pushes);
587
590
  roadmap seed --action check|apply|amend --draft <path> [--json] validates and writes new rows;
588
- rows written while autopilot is on land proposed; roadmap promote <id...> [--json] (person, autopilot off) makes them ready
591
+ rows written while autopilot is on land proposed; roadmap promote <id...> [--json] (person: asks y/N on the terminal) makes them ready
589
592
  plan-check Check a plan's task lines the way the task-attempt ledger reads them: plan-check <plan path> [--json]
590
593
  review Validate a feature's review/findings.json; print errors, advisories and counts (exit 0 valid, 1 invalid, 2 usage).
591
594
  review --action check [--slug <name> | --path <findings.json>] [--json]
@@ -604,6 +607,10 @@ Commands:
604
607
  transcript Read an agent's recent events, or the project's traffic (read-only, run in a project).
605
608
  transcript --action read --agent <key> [--cursor <c>] [--limit <n>] [--json] masked events and DoFlow's record events (exit 0, 1 agent-not-in-project or session-not-in-project, 2 usage)
606
609
  transcript --action stats [--window <minutes 1..60>] [--json] events per minute, tool calls, tokens, who works with whom (exit 0, 2 usage)
610
+ worktree Remove a merged feature's worktree and branch, never by force (--action cleanup).
611
+ worktree --action cleanup --slug <row slug> [--json] copies agent-docs/doflow/<slug> back to the main checkout first (exit 0 cleaned, 1 kept or lease-held with the reason, 2 usage)
612
+ attendance Say whether a person is watching a stage (read-only).
613
+ attendance --task-id <row slug> [--run <rl-id>] [--json] prints "unattended <source>" or "attended <reason>" (exit 0, 2 usage)
607
614
  package List the contribution points a package may fill, check a package directory, show the packages, add and update a local package, or turn it on and off.
608
615
  package --action points [--json]
609
616
  package --action check --path <dir> [--json] (read-only; exit 0 clean, 1 findings, 2 usage)
@@ -673,6 +680,7 @@ Runtime verb arguments (accept --flag value or --flag=value):
673
680
  failure: list|settle|capture
674
681
  roadmap: render|status|next|audit|init-policy (seed: check|apply|amend; promote <id...>)
675
682
  slot: run|list|peek|release
683
+ worktree: cleanup
676
684
  models: sync|list|describe
677
685
  review: check|start
678
686
  protocol: list|resolve
@@ -700,6 +708,8 @@ Runtime verb arguments (accept --flag value or --flag=value):
700
708
  disposition in the JSON; 2 usage or an unreadable store or plan)
701
709
  --rationale, --proposed-by, --calling-skill classify
702
710
  --request <text>, --request-file <file> classify (mutually exclusive; optional second opinion)
711
+ --view brief|full followup --action list (default brief), lifecycle --action overview, classify (default full); brief drops detail, full is the whole answer
712
+ --cursor <c>, --limit <n> followup --action list (the cursor is opaque: pass back a page's nextCursor); also transcript --action read
703
713
  --intent, --query, --check route
704
714
  --statement, --claim-id,
705
715
  --evidence-id, --relation claim
@@ -772,6 +782,8 @@ Runtime verb arguments (accept --flag value or --flag=value):
772
782
  --tag, --confirm, --feature,
773
783
  --exclude (repeatable), --reason lifecycle (release, merged)
774
784
  --fp, --as, --reason, --set, --all failure (list, settle, capture)
785
+ --run-id <id> intake --action undo (default: the latest sort run that has a move to undo)
786
+ --stdin | --file <path>, --stage intake --action file-prompt (the text, unchanged, from exactly one of --stdin or --file; --stage names the filing stage)
775
787
  --harness, --need (repeatable),
776
788
  --model-check, --budget-tokens,
777
789
  --task, --token, --prompt-file,
@@ -54,6 +54,7 @@ const { handleLeakScanCommand } = require('../runtime/leak-scan.js');
54
54
  const { handleRecoverCommand } = require('../runtime/recovery.js');
55
55
  const { handleScaffoldCommand } = require('../runtime/scaffold/generate.js');
56
56
  const { handleDecisionCommand, isReservedChannel, CHANNEL_RESERVED } = require('../runtime/decision-register.js');
57
+ const { handleIntakeCommand } = require('../runtime/intake/index.js');
57
58
  const { handleFollowupCommand, handleLifecycleCommand, handleGoalCommand } = require('../runtime/lifecycle/cli.js');
58
59
  const { projectRoot: lifecycleProjectRoot } = require('../runtime/lifecycle/root.js');
59
60
  const { upkeepAfterVerb } = require('../runtime/storage/upkeep.js');
@@ -66,6 +67,8 @@ const { handleProtocolCommand } = require('../runtime/review/protocol-command.js
66
67
  const { handleRulesCommand } = require('../runtime/rules-report.js');
67
68
  const { handlePackageCommand } = require('../runtime/packages/command.js');
68
69
  const { handleModelsCommand } = require('../runtime/models/command.js');
70
+ const { handleWorktreeCommand } = require('../runtime/roadmap/worktree-cleanup.js');
71
+ const { handleAttendanceCommand } = require('../runtime/roadmap/attendance.js');
69
72
  // `plan-check` is read-only (115 IC-001): no upkeep, which would write `.gitignore` and `upkeep-due`.
70
73
  // `review` writes only its one telemetry line (095 IC-003).
71
74
  // `protocol` only prints the registry and the resolved protocol (119 IC-004), so it has nothing to keep up.
@@ -73,9 +76,10 @@ const { handleModelsCommand } = require('../runtime/models/command.js');
73
76
  // `primary` only prints the resolved main session (120 IC-008) and writes nothing.
74
77
  // `fleet` only lists the projects this machine knows from two read-only stores (093 IC-019) and writes nothing.
75
78
  // `transcript` only reads agents' transcripts and DoFlow's own records and prints them (093 IC-022, IC-023); it writes nothing.
79
+ // `attendance` is read-only; `worktree --action cleanup` removes a merged feature's worktree under the roadmap lease and writes no DoFlow state, so neither runs upkeep.
76
80
  // `package` stays here (122 IC-012): `new` writes only the target folder; `add --plan`, `update --plan` and `remove` write the store and lock and run upkeep (see `runsUpkeep`).
77
81
  // `slot` is not here: `run`, `list` and `release` write the slot files and run upkeep; only `slot --action peek` is read-only and skips it (093 IC-018 b, see `runsUpkeep`).
78
- const NO_UPKEEP = new Set(['storage', 'config', 'plan-check', 'review', 'protocol', 'rules', 'primary', 'package', 'models', 'fleet', 'transcript']);
82
+ const NO_UPKEEP = new Set(['storage', 'config', 'plan-check', 'review', 'protocol', 'rules', 'primary', 'package', 'models', 'fleet', 'transcript', 'worktree', 'attendance']);
79
83
  const runsUpkeep = (o ) => (!NO_UPKEEP.has(o.cmd) && !(o.cmd === 'slot' && o.actionGiven && o.action === 'peek'))
80
84
  || (o.cmd === 'package' && ((typeof o.planId === 'string' && (o.action === 'add' || o.action === 'update')) || o.action === 'remove'));
81
85
  // Required on first use, so a CLI whose failure modules cannot be loaded still runs every other verb.
@@ -378,10 +382,12 @@ function dispatchRuntimeCommandInner(o , moment ) {
378
382
  // The feature is resolved from the working directory (or --slug) by the same resolver
379
383
  // `scaffold` uses; the flags are the caller's own statements, validated by the register module.
380
384
  case 'decision': return handleDecisionCommand({ action: o.action, projectRoot: evidenceRoot(o), slug: o.slug, json: o.json, flags: { topic: o.topic, statement: o.statement, channel: o.channel, stage: o.stage, rationale: o.rationale, supersedes: o.supersedes, refs: o.refs, source: o.source, batch: o.batchPath, all: o.all } });
385
+ // 136: the feature is resolved the way `decision` resolves it; the engine moves files only inside its additional-context folder.
386
+ case 'intake': return handleIntakeCommand({ action: o.action, projectRoot: evidenceRoot(o), slug: o.slug, json: o.json, flags: { runId: o.runId, stdin: o.stdin, file: o.file, stage: o.stage } });
381
387
  // Feature 046: the project's follow-up store lives at the repository root (never `-g`, which
382
388
  // the handlers refuse), so the working directory is the starting point, not a scope switch.
383
- case 'followup': return handleFollowupCommand({ action: o.actionGiven ? o.action : undefined, cwd: evidenceRoot(o), global: o.global, slug: o.slug, json: o.json, flags: { statement: o.statement, stage: o.stage, source: o.source, taskClass: o.taskClass, taskId: o.taskId, release: o.release, batch: o.batchPath, channel: o.channel, state: o.state, ids: o.ids, as: o.as, reason: o.reason, evidence: o.evidence, title: o.title, file: o.file, stdin: o.stdin, text: o.text, feature: o.feature, tag: o.tag, confirm: o.confirm, exclude: o.exclude } });
384
- case 'lifecycle': return handleLifecycleCommand({ action: o.actionGiven ? o.action : undefined, cwd: evidenceRoot(o), global: o.global, slug: o.slug, json: o.json, flags: { take: o.take, intent: o.intent, goal: o.goal, maintain: o.maintain, since: o.since, tag: o.tag, confirm: o.confirm, feature: o.feature, exclude: o.exclude, reason: o.reason, channel: o.channel } });
389
+ case 'followup': return handleFollowupCommand({ action: o.actionGiven ? o.action : undefined, cwd: evidenceRoot(o), global: o.global, slug: o.slug, json: o.json, flags: { statement: o.statement, stage: o.stage, source: o.source, taskClass: o.taskClass, taskId: o.taskId, release: o.release, batch: o.batchPath, channel: o.channel, state: o.state, ids: o.ids, view: o.view, cursor: o.cursor, limit: o.limit, as: o.as, reason: o.reason, evidence: o.evidence, title: o.title, file: o.file, stdin: o.stdin, text: o.text, feature: o.feature, tag: o.tag, confirm: o.confirm, exclude: o.exclude } });
390
+ case 'lifecycle': return handleLifecycleCommand({ action: o.actionGiven ? o.action : undefined, cwd: evidenceRoot(o), global: o.global, slug: o.slug, json: o.json, flags: { view: o.view, take: o.take, intent: o.intent, goal: o.goal, maintain: o.maintain, since: o.since, tag: o.tag, confirm: o.confirm, feature: o.feature, exclude: o.exclude, reason: o.reason, channel: o.channel } });
385
391
  case 'goal': return handleGoalCommand({ action: o.actionGiven ? o.action : undefined, cwd: evidenceRoot(o), global: o.global, slug: o.slug, json: o.json, flags: { goal: o.goal, statement: o.statement, item: o.item, text: o.text, evidence: o.evidence, unmet: o.unmet, replace: o.replace, reason: o.reason, channel: o.channel } });
386
392
  // The failure store is per machine, so `-g` changes nothing and the working directory only matters
387
393
  // to `settle --as imported`, which looks for the DoFlow repository from there.
@@ -389,7 +395,7 @@ function dispatchRuntimeCommandInner(o , moment ) {
389
395
  // The rest of design §4.2's Node arm. REPO_ROOT locates the registries that ship with the
390
396
  // package (workflows, capabilities, verification); evidenceRoot(o) locates the caller's own
391
397
  // state and source tree, following the same scope rules as every other command.
392
- case 'classify': return handleClassifyCommand({ taskClass: o.taskClass, rationale: o.rationale, proposedBy: o.proposedBy, callingSkill: o.callingSkill, request: o.request, requestFile: o.requestFile, taskId: o.taskId, projectRoot: evidenceRoot(o), json: o.json });
398
+ case 'classify': return handleClassifyCommand({ taskClass: o.taskClass, rationale: o.rationale, proposedBy: o.proposedBy, callingSkill: o.callingSkill, view: o.view, request: o.request, requestFile: o.requestFile, taskId: o.taskId, projectRoot: evidenceRoot(o), json: o.json });
393
399
  case 'workflow': return handleWorkflowCommand({ taskClass: o.taskClass, json: o.json });
394
400
  case 'orchestrate': return handleOrchestrateCommand({ action: o.action, taskId: o.taskId, taskClass: o.taskClass, stage: o.stage, gate: o.gate, node: o.node, decision: o.decision, policy: o.policy, note: o.note, reason: o.reason, forced: o.forced, verificationPlan: o.verificationPlan, scope: o.scope, invariants: o.invariants, result: o.result, mcpOutcome: o.mcpOutcome, callingSkill: o.callingSkill, slug: o.slug, recordSlug: storeSlug, dryRun: o.dryRun, json: o.json, repoRoot: REPO_ROOT, stateRoot: storeRoot, sourceRoot: evidenceRoot(o), buildAdapters: buildAdapterRegistry });
395
401
  case 'research-request': return handleResearchRequestCommand({ slug: storeSlug, action: o.action === 'status' ? 'list' : o.action, taskId: requireTaskId(o), stageId: o.stageId, question: o.question, reason: o.reason, blocking: o.blocking === undefined ? undefined : o.blocking === 'true' ? true : o.blocking === 'false' ? false : o.blocking, requestId: o.requestId, outcome: o.researchOutcome, claimId: o.claimId, evidenceIds: o.evidenceIds, gap: o.gap, json: o.json, projectRoot: storeRoot, sourceRoot: evidenceRoot(o) });
@@ -432,6 +438,8 @@ function dispatchRuntimeCommandInner(o , moment ) {
432
438
  case 'fleet': return handleFleetCommand({ action: o.actionGiven ? o.action : undefined, json: o.json, flags: o.flagsGiven, env: process.env, cwd: process.cwd() });
433
439
  case 'transcript': return handleTranscriptCommand({ action: o.actionGiven ? o.action : undefined, agent: o.agent, cursor: o.cursor, limit: o.limit, window: o.window, json: o.json, flags: o.flagsGiven, env: process.env, cwd: process.cwd() });
434
440
  case 'models': return handleModelsCommand({ action: o.actionGiven ? o.action : (o.positional?.[0] || 'list'), positional: o.positional, harness: o.harness, model: o.model, json: o.json, projectRoot: evidenceRoot(o) });
441
+ case 'worktree': return handleWorktreeCommand({ action: o.actionGiven ? o.action : undefined, slug: o.slug, json: o.json, flags: o.flagsGiven, env: process.env, cwd: process.cwd() });
442
+ case 'attendance': return handleAttendanceCommand({ taskId: o.taskId, run: o.run, json: o.json, flags: o.flagsGiven, env: process.env, cwd: process.cwd() });
435
443
  default: console.error(`doflow: unknown command '${o.cmd}'`); process.exit(1);
436
444
  }
437
445
  }
@@ -2,7 +2,6 @@
2
2
 
3
3
  const crypto = require('node:crypto');
4
4
  const CLASS_ORDER = Object.freeze(['unknown', 'unsupported', 'observation', 'advisory', 'blocking']);
5
- const POLICY_ORDER = Object.freeze(['pre-implementation-gate', 'pre-bash-guard', 'session-context', 'mcp-tool-guard', 'stop-check']);
6
5
  const HARNESS_ORDER = Object.freeze(['claude', 'codex', 'opencode', 'pi', 'copilot', 'kiro', 'antigravity']);
7
6
  const BEHAVIOUR_ORDER = Object.freeze(['context-authority', 'default-silence', 'summary-scope', 'summary-grant', 'identity', 'check-freshness']);
8
7
  const NEVER_BLOCKING_EVENTS = new Set([
@@ -43,32 +42,4 @@ function enforcementFor(coverage , query
43
42
  reason: weakest ? `${weakest.class} at (${weakest.policy}, ${weakest.harness}, ${weakest.event}, ${weakest.profile})` : `unknown at (${query.policy}, ${query.harness}, *, ${query.profile ?? '*'})`,
44
43
  };
45
44
  }
46
- function renderEnforcementMatrix(coverage ) {
47
- const order = (items , value ) => {
48
- const index = items.indexOf(value);
49
- return index < 0 ? items.length : index;
50
- };
51
- const rows = [...coverage.rows].sort((a , b ) => order(POLICY_ORDER, a.policy) - order(POLICY_ORDER, b.policy)
52
- || order(HARNESS_ORDER, a.harness) - order(HARNESS_ORDER, b.harness)
53
- || a.event.localeCompare(b.event) || a.profile.localeCompare(b.profile));
54
- const lines = [`Schema ${coverage.schemaVersion}; revision ${coverage.revision}; ${coverage.rowsDigest}`, '| Policy | Harness | Native event | Profile | Class | Verification | Prerequisites | Does not cover |', '|---|---|---|---|---|---|---|---|'];
55
- for (const row of rows) lines.push(`| ${row.policy} | ${row.harness} | ${row.event} | ${row.profile} | ${row.class} | ${row.verification} | ${(row.prerequisites || []).join(', ')} | ${(row.doesNotCover || []).join(', ')} |`);
56
- return lines.join('\n');
57
- }
58
- // IC-019: one row per behaviour x harness, ordered by IC-018's behaviour list, then HARNESS_ORDER.
59
- function renderBehaviourMatrix(coverage ) {
60
- const order = (items , value ) => {
61
- const index = items.indexOf(value);
62
- return index < 0 ? items.length : index;
63
- };
64
- const entries = [...(coverage.behaviours || [])].sort((a , b ) => order(BEHAVIOUR_ORDER, a.behaviour) - order(BEHAVIOUR_ORDER, b.behaviour)
65
- || order(HARNESS_ORDER, a.harness) - order(HARNESS_ORDER, b.harness)
66
- || a.behaviour.localeCompare(b.behaviour) || a.harness.localeCompare(b.harness));
67
- const lines = [`Schema ${coverage.schemaVersion}; revision ${coverage.revision}`, '| Behaviour | Harness | Status | Limitation or tests |', '|---|---|---|---|'];
68
- for (const entry of entries) {
69
- const detail = entry.status === 'enforced' ? (entry.tests || []).join(', ') : (entry.limitation || '');
70
- lines.push(`| ${entry.behaviour} | ${entry.harness} | ${entry.status} | ${detail} |`);
71
- }
72
- return lines.join('\n');
73
- }
74
- module.exports = { enforcementFor, renderEnforcementMatrix, renderBehaviourMatrix, BEHAVIOUR_ORDER, HARNESS_ORDER, rowsDigest, UnknownEnforcementTarget, NEVER_BLOCKING_EVENTS };
45
+ module.exports = { enforcementFor, BEHAVIOUR_ORDER, HARNESS_ORDER, rowsDigest, UnknownEnforcementTarget, NEVER_BLOCKING_EVENTS };
@@ -39,13 +39,13 @@ function resolveHookTier({ env = process.env, loadSettings }
39
39
  }
40
40
 
41
41
  /** One WARN line per planned harness that gets no hooks although it is the primary session (claude)
42
- * or a worker the person configured: there it runs without DoFlow's destructive-command guard. */
42
+ * or a worker the person configured: there it runs without DoFlow's gates. */
43
43
  function hookTierWarnings(registry , targets , tier ) {
44
44
  const warnings = [];
45
45
  for (const harness of registry.harnesses) {
46
46
  if (!targets.includes(harness.id) || tier.harnesses.includes(harness.id) || !getsHooks(registry, harness)) continue;
47
47
  const role = harness.id === 'claude' ? 'the primary session' : tier.workers?.includes(harness.id) ? `a worker in ${WORKERS_KEY}` : null;
48
- if (role) warnings.push(`${harness.id} is ${role} but not in ${HOOKS_KEY}: it runs without DoFlow's hooks, gates or destructive-command guard`);
48
+ if (role) warnings.push(`${harness.id} is ${role} but not in ${HOOKS_KEY}: it runs without DoFlow's hooks or gates`);
49
49
  }
50
50
  return warnings;
51
51
  }