@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.
- package/README.md +4 -5
- package/bin/doflow.js +1 -1
- package/core/.claude-plugin/plugin.json +1 -1
- package/core/.codex-plugin/plugin.json +1 -1
- package/core/.plugin/plugin.json +1 -1
- package/core/harnesses/claude/hooks/mcp-tool-guard.sh +4 -1
- package/core/harnesses/claude/hooks/pre-implement-gate.sh +4 -1
- package/core/harnesses/claude/settings/settings.json +8 -9
- package/core/harnesses/codex/hooks/hooks.json +0 -10
- package/core/harnesses/codex/hooks/mcp-tool-guard.sh +4 -1
- package/core/harnesses/codex/hooks/pre-implement-gate.sh +4 -1
- package/core/harnesses/copilot/hooks/doflow-lib.sh +4 -3
- package/core/harnesses/copilot/hooks/doflow.json +0 -6
- package/core/harnesses/kiro/agents/doflow.json +0 -10
- package/core/harnesses/kiro/hooks/doflow.json +0 -10
- package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +7 -3
- package/core/harnesses/kiro/hooks/pre-implement-gate.sh +7 -3
- package/core/harnesses/pi/extensions/doflow-guard.js +3 -2
- package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +1 -1
- package/core/harnesses/shared/hooks/adapters/antigravity.js +1 -1
- package/core/harnesses/shared/hooks/bridge/bridge.sh +35 -25
- package/core/harnesses/shared/hooks/bridge/envelope.jq +1 -1
- package/core/harnesses/shared/hooks/bridge/respond.sh +6 -0
- package/core/harnesses/shared/hooks/policies/capture-failure.sh +1 -1
- package/core/harnesses/shared/hooks/policies/deny-json.sh +23 -1
- package/core/harnesses/shared/hooks/policies/mcp-policy.conf +1 -1
- package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +4 -6
- package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +20 -21
- package/core/harnesses/shared/hooks/policies/stop-check.sh +7 -6
- package/core/harnesses/shared/hooks/policies/subagent-audit.sh +1 -1
- package/core/harnesses/shared/hooks/stream-hook-runner.js +11 -12
- package/core/registry/contracts.json +1 -1
- package/core/registry/contribution-points.json +5 -2
- package/core/registry/gates.json +5 -5
- package/core/registry/harnesses.json +9 -9
- package/core/registry/intake.json +53 -0
- package/core/registry/lifecycle.json +133 -711
- package/core/registry/package-templates/intake-category/files/category.json +8 -0
- package/core/registry/package-templates/intake-category/template.json +4 -0
- package/core/registry/packages/doflow/doflow-package.json +2 -2
- package/core/registry/rules.json +8 -8
- package/core/registry/settings.json +0 -4
- package/core/registry/storage.json +8 -0
- package/core/registry/telemetry.json +1 -1
- package/core/registry/verification.json +0 -5
- package/core/shared/guidance/references/DOFLOW_CHAIN.md +4 -4
- package/core/shared/guidance/references/ORCHESTRATION.md +2 -0
- package/core/shared/guidance/references/WORKFLOW_HANDOFF.md +19 -1
- package/core/shared/guidance/rules/RULES.md +2 -2
- package/core/shared/scripts/doflow/bash/validate-artifacts.sh +1 -1
- package/core/shared/scripts/doflow/bin/doflow-run +5 -2
- package/core/shared/skills/do/SKILL.md +3 -3
- package/core/shared/skills/do-brainstorm/SKILL.md +26 -24
- package/core/shared/skills/do-brainstorm/references/handoff_branches.md +2 -1
- package/core/shared/skills/do-code-review/SKILL.md +7 -4
- package/core/shared/skills/do-design/SKILL.md +5 -3
- package/core/shared/skills/do-diagnose/SKILL.md +4 -2
- package/core/shared/skills/do-document/SKILL.md +1 -0
- package/core/shared/skills/do-document/references/embedded-research.md +1 -1
- package/core/shared/skills/do-execute-plan/SKILL.md +4 -2
- package/core/shared/skills/do-execute-plan/references/run_position.md +3 -1
- package/core/shared/skills/do-execute-plan/references/scaffold.md +1 -1
- package/core/shared/skills/do-flow/SKILL.md +4 -4
- package/core/shared/skills/do-flow/references/task_classes.md +5 -5
- package/core/shared/skills/do-implement/SKILL.md +9 -7
- package/core/shared/skills/do-plan/SKILL.md +9 -6
- package/core/shared/workflows/do-flow-chain.md +2 -0
- package/package.json +1 -1
- package/src/adapters/claude/index.js +23 -2
- package/src/adapters/codex/hooks.js +39 -3
- package/src/adapters/codex/index.js +13 -0
- package/src/adapters/copy-tree.js +1 -2
- package/src/cli/commands/status.js +1 -1
- package/src/cli/index.js +20 -8
- package/src/cli/runtime-commands.js +12 -4
- package/src/lifecycle/coverage.js +1 -30
- package/src/lifecycle/hook-tier.js +2 -2
- package/src/release/docs-blocks.js +32 -52
- package/src/runtime/context/pointer.js +67 -12
- package/src/runtime/context-pack.js +4 -1
- package/src/runtime/evidence-ledger.js +18 -0
- package/src/runtime/health.js +59 -1
- package/src/runtime/implementation-gate.js +4 -4
- package/src/runtime/intake/engine.js +677 -0
- package/src/runtime/intake/index.js +136 -0
- package/src/runtime/intake/manifest.js +140 -0
- package/src/runtime/intake/taxonomy.js +206 -0
- package/src/runtime/lifecycle/cli.js +73 -5
- package/src/runtime/lifecycle/followup.js +32 -6
- package/src/runtime/lifecycle/overview.js +2 -1
- package/src/runtime/packages/points.js +10 -0
- package/src/runtime/roadmap/attendance.js +137 -0
- package/src/runtime/roadmap/gates.js +6 -3
- package/src/runtime/roadmap/loop.js +113 -30
- package/src/runtime/roadmap/merge.js +17 -8
- package/src/runtime/roadmap/rows.js +13 -4
- package/src/runtime/roadmap/seed.js +153 -41
- package/src/runtime/roadmap/worktree-cleanup.js +147 -0
- package/src/runtime/settings/registry.js +1 -1
- package/src/runtime/slots.js +1 -1
- package/src/runtime/task-classifier.js +39 -4
- package/src/runtime/task-state.js +2 -3
- package/src/runtime/verb-view.js +267 -0
- package/src/runtime/verification/engine.js +0 -45
- package/src/runtime/worker/launch.js +5 -37
- package/src/runtime/worker/readiness.js +2 -2
- package/src/runtime/workflow-orchestrator.js +33 -5
- package/core/harnesses/claude/hooks/pre-bash-guard.sh +0 -10
- package/core/harnesses/codex/hooks/pre-bash-guard.sh +0 -10
- package/core/harnesses/copilot/hooks/doflow-pre-bash-guard.sh +0 -9
- package/core/harnesses/kiro/hooks/pre-bash-guard.sh +0 -25
- package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +0 -105
- 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
|
|
128
|
+
## The advisory hook does not know about classes
|
|
129
129
|
|
|
130
|
-
`pre-implement-gate.sh` keys on branch and artifact state (what it
|
|
131
|
-
current branch already carries a feature directory the gate
|
|
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
|
-
|
|
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
|
|
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
|
|
122
|
-
|
|
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
|
|
150
|
-
|
|
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
|
|
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
|
|
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),
|
|
63
|
-
hook
|
|
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
|
|
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
|
@@ -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:
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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
|
-
|
|
581
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
}
|