@khoavu882/doflow 2.0.0 → 2.0.2
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/hooks/session-end.sh +21 -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/codex/hooks/session-end.impl.sh +21 -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/compact-text.sh +8 -0
- 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 +26 -2
- 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 +176 -503
- 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 +12 -16
- package/core/registry/storage.json +9 -2
- package/core/registry/telemetry.json +1 -1
- package/core/registry/verification.json +0 -5
- package/core/shared/guidance/references/ARTIFACT_FORMAT.md +7 -0
- 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/do-git-state.sh +12 -4
- package/core/shared/scripts/doflow/bash/do-parallel-check.sh +86 -33
- package/core/shared/scripts/doflow/bash/do-review-package.sh +36 -2
- package/core/shared/scripts/doflow/bash/validate-artifacts.sh +1 -1
- package/core/shared/scripts/doflow/bin/doflow-run +23 -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 +11 -5
- package/core/shared/skills/do-design/references/data_model_example.md +167 -0
- 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/templates/doflow/data-model-template.md +136 -102
- package/core/shared/workflows/do-flow-chain.md +2 -0
- package/package.json +1 -1
- package/src/adapters/antigravity/index.js +54 -12
- package/src/adapters/claude/index.js +23 -2
- package/src/adapters/codex/agents.js +15 -1
- package/src/adapters/codex/config.js +5 -2
- package/src/adapters/codex/hooks.js +39 -3
- package/src/adapters/codex/index.js +13 -0
- package/src/adapters/copilot/index.js +11 -4
- package/src/adapters/copy-tree.js +8 -2
- package/src/adapters/created-file.js +41 -0
- package/src/adapters/mcp-entries.js +11 -1
- package/src/adapters/opencode/index.js +32 -5
- package/src/adapters/pi/index.js +15 -4
- package/src/adapters/pi/mcp.js +44 -18
- package/src/cli/commands/remove.js +3 -1
- package/src/cli/commands/status.js +1 -1
- package/src/cli/commands/update.js +7 -3
- package/src/cli/index.js +43 -19
- package/src/cli/runtime-commands.js +13 -5
- package/src/cli/shared.js +5 -3
- package/src/helper/marker-merge.js +11 -7
- package/src/install/backup-set.js +7 -3
- package/src/install/backup.js +13 -7
- package/src/install/context.js +2 -1
- package/src/install/manifest.js +35 -11
- package/src/install/paths.js +38 -0
- package/src/install/targets.js +11 -2
- package/src/lifecycle/coverage.js +1 -30
- package/src/lifecycle/hook-tier.js +2 -2
- package/src/lifecycle/index.js +80 -3
- package/src/lifecycle/retired-harnesses.js +16 -1
- package/src/lifecycle/view.js +3 -2
- package/src/registry/index.js +21 -4
- package/src/release/docs-blocks.js +37 -53
- package/src/runtime/advice/doctor.js +2 -2
- package/src/runtime/brief-tools.js +2 -1
- package/src/runtime/capability-router.js +26 -3
- package/src/runtime/context/pointer.js +84 -17
- package/src/runtime/context-pack.js +52 -9
- package/src/runtime/dispatch/integrate.js +27 -8
- package/src/runtime/dispatch/loop.js +7 -0
- package/src/runtime/dispatch/verify-run.js +9 -3
- package/src/runtime/evidence-ledger.js +18 -0
- package/src/runtime/feature-pointer.js +10 -1
- package/src/runtime/health.js +68 -5
- package/src/runtime/implementation-gate.js +14 -6
- package/src/runtime/intake/engine.js +679 -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/knowledge/code-retrieval.js +11 -5
- package/src/runtime/knowledge/semble-provider.js +12 -2
- package/src/runtime/lifecycle/cli.js +76 -6
- package/src/runtime/lifecycle/followup.js +32 -6
- package/src/runtime/lifecycle/overview.js +34 -3
- package/src/runtime/lifecycle/release.js +16 -3
- package/src/runtime/lifecycle/retention.js +12 -15
- package/src/runtime/lifecycle/store-upkeep.js +13 -3
- package/src/runtime/mask.js +3 -3
- package/src/runtime/mcp-consultations.js +11 -5
- package/src/runtime/packages/points.js +10 -0
- package/src/runtime/research-request.js +3 -1
- package/src/runtime/review/command.js +3 -7
- package/src/runtime/review/start.js +24 -10
- package/src/runtime/roadmap/attendance.js +137 -0
- package/src/runtime/roadmap/gates.js +6 -3
- package/src/runtime/roadmap/loop.js +127 -33
- package/src/runtime/roadmap/merge.js +17 -8
- package/src/runtime/roadmap/rows.js +15 -5
- package/src/runtime/roadmap/seed.js +153 -41
- package/src/runtime/roadmap/worktree-cleanup.js +147 -0
- package/src/runtime/settings/files.js +3 -1
- package/src/runtime/settings/prepared-run.js +12 -2
- package/src/runtime/settings/registry.js +1 -1
- package/src/runtime/settings/resolve.js +13 -1
- package/src/runtime/settings/write.js +73 -2
- package/src/runtime/slots.js +1 -1
- package/src/runtime/storage/registry.js +2 -2
- package/src/runtime/storage/roots.js +4 -18
- package/src/runtime/storage/rules.js +22 -46
- package/src/runtime/storage/work-status.js +14 -4
- package/src/runtime/task-classifier.js +39 -4
- package/src/runtime/task-scope.js +17 -2
- package/src/runtime/task-state.js +13 -7
- package/src/runtime/trace/analyze.js +4 -2
- package/src/runtime/trace/indicators.js +25 -20
- package/src/runtime/trace/spans.js +4 -5
- package/src/runtime/verb-view.js +268 -0
- package/src/runtime/verification/engine.js +0 -45
- package/src/runtime/worker/command.js +4 -2
- package/src/runtime/worker/launch.js +5 -37
- package/src/runtime/worker/readiness.js +10 -6
- package/src/runtime/worker/supervisor.js +18 -6
- package/src/runtime/workflow-orchestrator.js +39 -7
- 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
|
@@ -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.
|
|
@@ -2,166 +2,200 @@
|
|
|
2
2
|
|
|
3
3
|
**Feature:** [NNN-slug] · **Design:** ./design.md · **Maturity:** Draft · **Created:** [DATE]
|
|
4
4
|
|
|
5
|
-
> Entity/data model and data-flow diagrams a design decided, split out of design.md's own
|
|
6
|
-
> (Data Model and Sequence / Data Flow) and specs.md's interface-contract detail (§1). Reads
|
|
7
|
-
> the way a persisted shape and the flows that move through it
|
|
8
|
-
> expose them.
|
|
5
|
+
> Entity/data model, lifecycles and data-flow diagrams a design decided, split out of design.md's own
|
|
6
|
+
> narrative (Data Model and Sequence / Data Flow) and specs.md's interface-contract detail (§1). Reads
|
|
7
|
+
> ./design.md; distinct from ./specs.md the way a persisted shape and the flows that move through it
|
|
8
|
+
> are distinct from the contracts that expose them.
|
|
9
9
|
>
|
|
10
10
|
> Structure follows `references/ARTIFACT_FORMAT.md`: the document-level `Maturity` is `Draft`,
|
|
11
|
-
> `In review` or `Approved`.
|
|
12
|
-
>
|
|
13
|
-
>
|
|
14
|
-
|
|
11
|
+
> `In review` or `Approved`. The file has nine sections. Only §9 History carries an `ID`-bearing
|
|
12
|
+
> index (index-then-detail, item-level `Status` is `Live` or `Superseded → <ref>`); §1 to §8 are
|
|
13
|
+
> free-form, because a diagram or a table row is not a normative, citable statement.
|
|
14
|
+
|
|
15
|
+
<!-- Rules for every section below.
|
|
16
|
+
Source: each row of the §1 dictionary and of every table in §3 to §8 ends with a Source cell
|
|
17
|
+
holding exactly one of: FR-###, NFR-###, IC-###, code:<path>:<line>, proposed, UNKNOWN -> Open Point.
|
|
18
|
+
A `proposed` or `UNKNOWN -> Open Point` row is copied to design.md Open Points for Review.
|
|
19
|
+
Placeholders are bracketed and bare; fill them, never leave a row of placeholders in place.
|
|
20
|
+
A conditional section accepts "N/A: [why]" in place of its content.
|
|
21
|
+
Worked example, read only while filling this file: do-design/references/data_model_example.md -->
|
|
15
22
|
|
|
16
23
|
## 1. Data Model
|
|
17
24
|
|
|
18
|
-
<!-- Free-form
|
|
19
|
-
|
|
20
|
-
|
|
25
|
+
<!-- Free-form. Order: domain inventory, domain map, ER views, data dictionary, schemas, UX.
|
|
26
|
+
The domain map carries no attributes, so the shape of the domain is legible before any field
|
|
27
|
+
name. One ER view per bounded subdomain carries that subdomain's attributes, keys and
|
|
28
|
+
cardinalities alone; splitting must not drop an entity, a key or a constraint. -->
|
|
21
29
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
30
|
+
### Domain inventory
|
|
31
|
+
|
|
32
|
+
| Domain | Entities | Stateful | Owner | Source |
|
|
33
|
+
|---|---|---|---|---|
|
|
34
|
+
| [domain] | [entity, entity] | [yes or no] | [owning component] | [Source] |
|
|
27
35
|
|
|
28
36
|
### Conceptual domain map
|
|
29
37
|
|
|
30
|
-
<!--
|
|
31
|
-
the feature persists no entity. -->
|
|
38
|
+
<!-- A flowchart of entities and relationships, no attributes. "N/A: [why]" if the feature stores no entity (persisted captures count as entities). -->
|
|
32
39
|
|
|
33
40
|
```mermaid
|
|
34
41
|
flowchart LR
|
|
35
|
-
|
|
36
|
-
POST["Post"]
|
|
37
|
-
COMMENT["Comment"]
|
|
38
|
-
|
|
39
|
-
USER -->|"creates"| POST
|
|
40
|
-
POST -->|"contains"| COMMENT
|
|
42
|
+
A["[Entity]"] -->|"[relationship]"| B["[Entity]"]
|
|
41
43
|
```
|
|
42
44
|
|
|
43
45
|
#### ER view: [subdomain]
|
|
44
46
|
|
|
45
|
-
<!-- One heading of this form per bounded subdomain, each an erDiagram
|
|
46
|
-
|
|
47
|
-
no entity. -->
|
|
47
|
+
<!-- One heading of this form per bounded subdomain, each an erDiagram. Every entity named here
|
|
48
|
+
needs a dictionary block below and a lifecycle block in §3. -->
|
|
48
49
|
|
|
49
50
|
```mermaid
|
|
50
51
|
erDiagram
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
string email UK
|
|
55
|
-
string name
|
|
56
|
-
timestamp created_at
|
|
57
|
-
}
|
|
58
|
-
POST ||--o{ COMMENT : "contains"
|
|
59
|
-
POST {
|
|
60
|
-
uuid id PK
|
|
61
|
-
uuid user_id FK
|
|
62
|
-
string title
|
|
63
|
-
text content
|
|
64
|
-
timestamp created_at
|
|
52
|
+
[ENTITY_A] ||--o{ [ENTITY_B] : "[relationship]"
|
|
53
|
+
[ENTITY_A] {
|
|
54
|
+
[type] [field] PK
|
|
65
55
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
uuid post_id FK
|
|
69
|
-
uuid author_id FK
|
|
70
|
-
text body
|
|
71
|
-
timestamp created_at
|
|
56
|
+
[ENTITY_B] {
|
|
57
|
+
[type] [field] FK
|
|
72
58
|
}
|
|
73
59
|
```
|
|
74
60
|
|
|
61
|
+
### Data dictionary
|
|
62
|
+
|
|
63
|
+
#### Dictionary: [entity]
|
|
64
|
+
|
|
65
|
+
<!-- One block per ER entity, one row per field. -->
|
|
66
|
+
|
|
67
|
+
| Field | Type | Null | Rule | PII class | Source |
|
|
68
|
+
|---|---|---|---|---|---|
|
|
69
|
+
| [field] | [type] | [yes or no] | [constraint or format] | [none, direct, indirect] | [Source] |
|
|
70
|
+
|
|
75
71
|
### Database Schemas (ORM / DDL)
|
|
76
72
|
|
|
77
73
|
<!-- The catalog of every table the views above draw, with its keys, indexes and ORM file. -->
|
|
78
74
|
|
|
79
75
|
| Table | Purpose | Primary Key | Foreign Keys & Relations | Key Indexes | Schema / ORM File |
|
|
80
76
|
|---|---|---|---|---|---|
|
|
81
|
-
| `[table_name]` | [one-line
|
|
77
|
+
| `[table_name]` | [one-line responsibility] | [key] | [fk → table(key)] | [index] | [path] |
|
|
82
78
|
|
|
83
79
|
### UX / UI Specifications
|
|
84
|
-
- **Design Tokens & Cues:** [
|
|
85
|
-
- **Component States:** [loading, empty, populated, error
|
|
80
|
+
- **Design Tokens & Cues:** [tokens, badge variants, typography]
|
|
81
|
+
- **Component States:** [loading, empty, populated, error]
|
|
86
82
|
|
|
87
83
|
## 2. Sequence / Data Flow
|
|
88
84
|
|
|
89
|
-
<!-- Free-form
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
§1's depth deliberately: this is the chain's most-used diagram type, not an afterthought.
|
|
94
|
-
|
|
95
|
-
`references/DIAGRAM_GUIDE.md` governs the lifeline ceiling. There is no message ceiling,
|
|
96
|
-
because a sequence diagram lays out columnwise and cannot tangle however many messages it
|
|
97
|
-
carries — so show the whole interaction rather than trimming it to look tidy.
|
|
98
|
-
|
|
99
|
-
Conventions:
|
|
100
|
-
- Declare every participant up front, in the order they first act, so the columns read
|
|
101
|
-
left-to-right in causal order.
|
|
102
|
-
- Use `actor` for a human or an external caller and `participant` for a component.
|
|
103
|
-
- `->>` is a call, `-->>` is a return. Keep returns only where the returned value matters.
|
|
104
|
-
- Put the failure path in an `alt` block. An interaction drawn only on its happy path hides
|
|
105
|
-
the branch a reader most needs.
|
|
106
|
-
- `Note over X,Y:` carries a constraint or an invariant, never narration of the line above.
|
|
107
|
-
- Prefer `rect` over `par` to mark a region: `par` claims the steps happen simultaneously,
|
|
108
|
-
which most groupings cannot support. Reserve `par` for genuine parallelism, `alt` for a
|
|
109
|
-
branch.
|
|
85
|
+
<!-- Free-form. `references/DIAGRAM_GUIDE.md` governs lifeline ceilings; there is no message ceiling.
|
|
86
|
+
Declare participants in the order they first act. `->>` is a call, `-->>` a return. Put the
|
|
87
|
+
failure path in an `alt` block. Prefer `rect` over `par` unless steps are truly parallel.
|
|
88
|
+
"N/A: [why]" if the feature has no non-trivial interaction. -->
|
|
110
89
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
### [Interaction name]
|
|
90
|
+
### Interaction: [name]
|
|
114
91
|
|
|
115
92
|
[One sentence: what triggers this flow and what it ends with.]
|
|
116
93
|
|
|
117
94
|
```mermaid
|
|
118
95
|
sequenceDiagram
|
|
119
96
|
actor User
|
|
120
|
-
participant
|
|
121
|
-
participant SVC as [service]
|
|
97
|
+
participant SVC as [component]
|
|
122
98
|
participant DB as [data store]
|
|
123
99
|
|
|
124
|
-
User->>
|
|
125
|
-
API->>SVC: [call]
|
|
126
|
-
|
|
100
|
+
User->>SVC: [request]
|
|
127
101
|
alt [failure condition]
|
|
128
|
-
SVC-->>
|
|
129
|
-
API-->>User: [status code and message]
|
|
102
|
+
SVC-->>User: [error]
|
|
130
103
|
else [success]
|
|
131
104
|
SVC->>DB: [read or write]
|
|
132
105
|
DB-->>SVC: [result]
|
|
133
|
-
SVC-->>
|
|
134
|
-
API-->>User: [status code]
|
|
106
|
+
SVC-->>User: [response]
|
|
135
107
|
end
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Swimlane flows
|
|
111
|
+
|
|
112
|
+
<!-- One "#### Flow: <name>" per end-to-end flow, each embedding its committed render from
|
|
113
|
+
design/c4/flow-<name>.png. The name matches the source file's stem; nothing keeps a manifest.
|
|
114
|
+
No inline text diagram here: Mermaid has no lane primitive. `references/DIAGRAM_GUIDE.md` §6
|
|
115
|
+
governs lanes. "N/A: [why]" when the flow has a single actor. -->
|
|
116
|
+
|
|
117
|
+
#### Flow: [name]
|
|
118
|
+
|
|
119
|
+
![End-to-end flow: [name]](design/c4/flow-[name].png)
|
|
120
|
+
|
|
121
|
+
[One sentence: what triggers this flow and what it ends with.]
|
|
136
122
|
|
|
137
|
-
|
|
123
|
+
## 3. Lifecycles
|
|
124
|
+
|
|
125
|
+
> **Authoritative:** this section governs [the state, rule or outcome].
|
|
126
|
+
|
|
127
|
+
<!-- Every entity state lives here and nowhere else. Other sections link to the entity by name and
|
|
128
|
+
restate no state (ARTIFACT_FORMAT.md §11). One block per ER entity.
|
|
129
|
+
Stateful entity: a stateDiagram-v2 of at most 10 states, a Transitions table, Illegal
|
|
130
|
+
transitions and terminal states. Every diagram state (except [*]) appears in the From or To
|
|
131
|
+
column. Above 10 states, split the entity or group states into a composite state.
|
|
132
|
+
Stateless entity: the single line "N/A: stateless, [why]". -->
|
|
133
|
+
|
|
134
|
+
#### Lifecycle: [entity]
|
|
135
|
+
|
|
136
|
+
```mermaid
|
|
137
|
+
stateDiagram-v2
|
|
138
|
+
[*] --> [State]
|
|
139
|
+
[State] --> [State]: [event]
|
|
138
140
|
```
|
|
139
141
|
|
|
140
|
-
|
|
141
|
-
action, an idempotency requirement — state them in a sentence beneath the diagram rather than
|
|
142
|
-
crowding the fence.]
|
|
142
|
+
**Transitions**
|
|
143
143
|
|
|
144
|
-
|
|
144
|
+
| From | Event | Guard | Actor | Side effect | To | Emitted event | Source |
|
|
145
|
+
|---|---|---|---|---|---|---|---|
|
|
146
|
+
| [state] | [event] | [condition] | [who triggers] | [effect] | [state] | [event or none] | [Source] |
|
|
145
147
|
|
|
146
|
-
|
|
147
|
-
design/c4/flow-<name>.png. The heading name matches its source file's stem, which is the only
|
|
148
|
-
index -- nothing maintains a manifest, because a manifest could disagree with the directory.
|
|
148
|
+
**Illegal transitions**
|
|
149
149
|
|
|
150
|
-
|
|
151
|
-
chosen: Mermaid has no lane primitive, and approximating lanes with subgraph grouping would
|
|
152
|
-
carry no lane semantics and so would misrepresent the diagram it stood in for. The authored
|
|
153
|
-
.puml remains readable text under review, so the flow's logic is still diffable.
|
|
150
|
+
- [From] → [To]: [why it is refused]
|
|
154
151
|
|
|
155
|
-
|
|
156
|
-
feature's flow involves a single actor. -->
|
|
152
|
+
**Terminal states:** [state, state]
|
|
157
153
|
|
|
158
|
-
|
|
154
|
+
#### Lifecycle: [entity]
|
|
155
|
+
|
|
156
|
+
N/A: stateless, [why]
|
|
157
|
+
|
|
158
|
+
## 4. Cross-entity coupling
|
|
159
|
+
|
|
160
|
+
<!-- Conditional: two or more actors; otherwise "N/A: [why]". Link to the §2 flow by name; restate
|
|
161
|
+
no picture and no state. -->
|
|
162
|
+
|
|
163
|
+
| Trigger entity and transition | Effect on entity | Via event | Source |
|
|
164
|
+
|---|---|---|---|
|
|
165
|
+
| [entity: From → To] | [entity: effect] | [event] | [Source] |
|
|
166
|
+
|
|
167
|
+
## 5. Invariants
|
|
168
|
+
|
|
169
|
+
<!-- Mandatory. A statement that must hold at all times, and where it is enforced. -->
|
|
170
|
+
|
|
171
|
+
| Invariant | Holds over | Enforced by | Source |
|
|
172
|
+
|---|---|---|---|
|
|
173
|
+
| [statement] | [entities or fields] | [constraint, code path, or check] | [Source] |
|
|
174
|
+
|
|
175
|
+
## 6. Ownership / CRUD matrix
|
|
176
|
+
|
|
177
|
+
| Entity | Owner | Create | Read | Update | Delete | Source |
|
|
178
|
+
|---|---|---|---|---|---|---|
|
|
179
|
+
| [entity] | [component] | [who] | [who] | [who] | [who] | [Source] |
|
|
180
|
+
|
|
181
|
+
## 7. Event catalogue
|
|
182
|
+
|
|
183
|
+
<!-- Domain events this feature emits; external stream events it only consumes are not listed here.
|
|
184
|
+
"N/A: [why]" when no domain event is emitted. -->
|
|
185
|
+
|
|
186
|
+
| Event | Emitted by | Payload | Consumers | Source |
|
|
187
|
+
|---|---|---|---|---|
|
|
188
|
+
| [event] | [component] | [fields] | [components] | [Source] |
|
|
189
|
+
|
|
190
|
+
## 8. Retention, PII and migration
|
|
191
|
+
|
|
192
|
+
<!-- Conditional: "N/A: [why]" when nothing is retained, personal or migrated. -->
|
|
159
193
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
194
|
+
| Entity | Retention | PII class | Migration | Source |
|
|
195
|
+
|---|---|---|---|---|
|
|
196
|
+
| [entity] | [period or rule] | [none, direct, indirect] | [step or none] | [Source] |
|
|
163
197
|
|
|
164
|
-
##
|
|
198
|
+
## 9. History
|
|
165
199
|
|
|
166
200
|
<!-- Superseded data-model entries move here; the index row above stays as a tombstone with
|
|
167
201
|
`Superseded → <ref>`. -->
|
|
@@ -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
|
@@ -26,6 +26,7 @@ const { MARKER_START, MARKER_END, removeMarkedSection } = require('../../helper/
|
|
|
26
26
|
const { planTree, applyTree, removeTree, verifyTree, resolveTransform, copyTreeAssets, ledgerFileResources, ledgerSiblingFingerprints, siblingReplacedNotices, fingerprint, readJson, sourceDirFor } = require('../copy-tree.js');
|
|
27
27
|
const { declaredHarnessPaths, resolveHarnessPaths } = require('../../helper/harness-paths.js');
|
|
28
28
|
const { renderGuidancePointer } = require('../guidance-pointer.js');
|
|
29
|
+
const { replacementManagesFile } = require('../../lifecycle/retired-harnesses.js');
|
|
29
30
|
const { readMcpFiles, planMcpEntries, verifyMcpEntries, ownedMcpIds, writeMcpEntries } = require('../mcp-entries.js');
|
|
30
31
|
|
|
31
32
|
const HARNESS = 'antigravity';
|
|
@@ -229,8 +230,9 @@ function managedInstruction(existing , rendered , { name = 'AGENTS.md',
|
|
|
229
230
|
if (start === -1 && predecessor) start = existing.indexOf(PREDECESSOR_MARKER_START);
|
|
230
231
|
const end = existing.indexOf(MARKER_END);
|
|
231
232
|
if (start === -1 && end === -1) {
|
|
232
|
-
|
|
233
|
-
|
|
233
|
+
// Always exactly one '\n' before the span, so strippedInstruction can take back exactly that one
|
|
234
|
+
// byte whatever the file ended with (a file with no final newline just gets the span on the next line).
|
|
235
|
+
const content = `${existing}\n${rendered}`;
|
|
234
236
|
return { ok: true, operation: 'merge', content };
|
|
235
237
|
}
|
|
236
238
|
if (start === -1 || end === -1 || end < start) return { ok: false, conflict: `${name} has malformed DoFlow managed-section markers` };
|
|
@@ -245,7 +247,11 @@ function strippedInstruction(existing ) {
|
|
|
245
247
|
if (start === -1 || end === -1 || end < start) return null;
|
|
246
248
|
let after = end + MARKER_END.length;
|
|
247
249
|
if (existing[after] === '\n') after += 1;
|
|
248
|
-
|
|
250
|
+
const before = existing.slice(0, start);
|
|
251
|
+
const rest = existing.slice(after);
|
|
252
|
+
// Exact inverse of managedInstruction's append: a span at the end of the file goes with the one '\n'
|
|
253
|
+
// put before it. A span with the person's text after it was placed, not appended; it keeps its before.
|
|
254
|
+
return `${rest === '' && before.endsWith('\n') ? before.slice(0, -1) : before}${rest}`;
|
|
249
255
|
}
|
|
250
256
|
|
|
251
257
|
// ---- copy-tree components (skills, agents, locator) ----
|
|
@@ -318,6 +324,45 @@ function planTrees({ assets, paths, scope, neutralResources, removing, repoRoot,
|
|
|
318
324
|
return { changes, conflicts, targets, notices: [...siblingReplacedNotices(treeResults), ...notices] };
|
|
319
325
|
}
|
|
320
326
|
|
|
327
|
+
// ---- legacy Gemini shim ----
|
|
328
|
+
|
|
329
|
+
/** sha256 of every `doflow-run` locator the retired Gemini adapter wrote to ~/.gemini/bin, which
|
|
330
|
+
* moved to ~/.gemini/config/bin in f7928c48. Each is a blob of core/harnesses/shared/locator/doflow-run
|
|
331
|
+
* that was current while the adapter still projected to ~/.gemini/bin (blob: commit that introduced
|
|
332
|
+
* it): 22340592: e575b259, 34bb6c6c: 1fbab132, 344ebd52: a3c15cdb. Hashes only; the bodies stay in git. */
|
|
333
|
+
const LEGACY_GEMINI_SHIM_SHA256 = Object.freeze([
|
|
334
|
+
'bee62b0a97b9365fba95541a2d2979b1dd57f63a13e0d7bcc6077147664008a4',
|
|
335
|
+
'66530de094c6ec22bda9219b19f9c02e6a076068aa671b5eb6dff463d06f966a',
|
|
336
|
+
'9b5cae2a84d7486deff8ecabe477c06af5802d77af6efa72476897f08b8864d6',
|
|
337
|
+
]);
|
|
338
|
+
|
|
339
|
+
/** The one change that retires a stale ~/.gemini/bin/doflow-run: global scope only, and only a
|
|
340
|
+
* regular file (never a link) whose bytes equal a shipped legacy shim. Anything else at that path
|
|
341
|
+
* is the person's and is left alone. No ledger row owns the file, so the change carries
|
|
342
|
+
* `legacyRetirement` and the lifecycle journals it as a swept tombstone (src/lifecycle/index.js). */
|
|
343
|
+
function planLegacyGeminiShim({ assets, paths, scope, fsImpl = fs } ) {
|
|
344
|
+
const none = { changes: [], notices: [] };
|
|
345
|
+
const locator = scope === 'global' ? (assets ?? []).find((asset ) => asset.id === 'locator.doflow') : null;
|
|
346
|
+
const currentDir = locator && treeDestFor(locator, paths, scope);
|
|
347
|
+
if (!currentDir) return none;
|
|
348
|
+
const target = path.join(paths.root, '.gemini', 'bin', 'doflow-run');
|
|
349
|
+
let digest;
|
|
350
|
+
try {
|
|
351
|
+
if (!fsImpl.lstatSync(target).isFile()) return none;
|
|
352
|
+
digest = sha256File(fsImpl, target);
|
|
353
|
+
} catch { return none; }
|
|
354
|
+
if (!LEGACY_GEMINI_SHIM_SHA256.includes(digest)) return none;
|
|
355
|
+
return {
|
|
356
|
+
changes: [{
|
|
357
|
+
assetId: locator.id, target, operation: 'remove', kind: 'copy-tree-file', identity: 'bin/doflow-run',
|
|
358
|
+
ownershipIdentity: `doflow:${HARNESS}:legacy:locator.doflow:bin/doflow-run`,
|
|
359
|
+
fingerprint: digest, projection: { renderer: 'copy-tree' },
|
|
360
|
+
legacyRetirement: { toTarget: path.join(currentDir, 'doflow-run') },
|
|
361
|
+
}],
|
|
362
|
+
notices: ['removed ~/.gemini/bin/doflow-run: an unmodified older DoFlow shim, replaced by ~/.gemini/config/bin/doflow-run'],
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
321
366
|
function runTreeChanges(changes , mode ) {
|
|
322
367
|
// Route by what each change IS, not by which verb invoked us: remove() delegates here with a
|
|
323
368
|
// plan full of operation:'remove' changes, and applyTree deliberately skips those — so routing
|
|
@@ -367,17 +412,13 @@ function instructionTarget({ paths, assets, registry } ) {
|
|
|
367
412
|
if (!paths.instruction) return null;
|
|
368
413
|
if (scopeOf(paths) === 'project') return paths.instruction;
|
|
369
414
|
if (!(assets ?? []).some((asset ) => asset.id === POINTER_ASSET_ID)) return null;
|
|
370
|
-
|
|
371
|
-
if (harness?.id === HARNESS || !harness?.paths?.instruction) return false;
|
|
372
|
-
try { return resolveHarnessPaths({ instruction: harness.paths.instruction }, { scope: 'global', scopeRoot: paths.root, homeDir: paths.root }).instruction === paths.instruction; } catch { return false; }
|
|
373
|
-
};
|
|
374
|
-
return (registry?.harnesses ?? []).some(declaredBy) ? null : paths.instruction;
|
|
415
|
+
return replacementManagesFile({ registry, harnessId: HARNESS, surface: 'instruction', target: paths.instruction, scope: 'global', scopeRoot: paths.root }) ? paths.instruction : null;
|
|
375
416
|
}
|
|
376
417
|
|
|
377
418
|
/** How this run stands to a section it took over (IC-007): the lifecycle's handover verdict for the
|
|
378
419
|
* file, this harness's own row on it, and whether a person-edited file stays as it is (`retained`:
|
|
379
420
|
* edited before the handover, or kept since, and no --force). `predecessor` lets the retired
|
|
380
|
-
* harness's span count as the section to replace; `keptRow` is
|
|
421
|
+
* harness's span count as the section to replace; `keptRow` is any own row on the file. A handover
|
|
381
422
|
* file whose DoFlow span already is this harness's render was written by DoFlow itself (a run that
|
|
382
423
|
* threw after that write and before its ledger write), so it does not count as edited. */
|
|
383
424
|
function instructionState({ target, context, resources, paths, fsImpl = fs } ) {
|
|
@@ -391,7 +432,7 @@ function instructionState({ target, context, resources, paths, fsImpl = fs }
|
|
|
391
432
|
};
|
|
392
433
|
const edited = (handover?.edited === true && !ownWrite()) || ownRow?.retainedEdit === true;
|
|
393
434
|
return { predecessor: Boolean(handover) || ownRow?.retainedEdit === true, retained: edited && context.force !== true,
|
|
394
|
-
keptRow: ownRow
|
|
435
|
+
keptRow: ownRow !== null };
|
|
395
436
|
}
|
|
396
437
|
|
|
397
438
|
function instructionsChange(target , fields ) {
|
|
@@ -492,6 +533,7 @@ function plan(options = {}, impl = {}) {
|
|
|
492
533
|
const instructions = planInstructions({ paths, target: instructionFile, removing, fsImpl,
|
|
493
534
|
state: instructionState({ target: instructionFile, context, resources: neutralResources, paths, fsImpl }) });
|
|
494
535
|
const trees = planTrees({ assets: options.assets, paths, scope, neutralResources, removing, repoRoot: context.repoRoot, force: context.force === true, fsImpl });
|
|
536
|
+
const legacyShim = removing ? { changes: [], notices: [] } : planLegacyGeminiShim({ assets: options.assets, paths, scope, fsImpl });
|
|
495
537
|
const mcp = planMcpEntries({
|
|
496
538
|
...mcpEntriesInput({ paths, mcp: selectedServers, mcpAdoptable: options.mcpAdoptable, resources: neutralResources }),
|
|
497
539
|
files: discovery.mcpSnapshot, removing,
|
|
@@ -501,14 +543,14 @@ function plan(options = {}, impl = {}) {
|
|
|
501
543
|
const hooksPlan = context.hooksOff === true && !removing ? { changes: [], conflicts: [] }
|
|
502
544
|
: planHooks({ paths, scope, neutralResources, removing, repoRoot: context.repoRoot, fsImpl });
|
|
503
545
|
|
|
504
|
-
const changes = [...instructions.changes, ...trees.changes, ...mcp.changes, ...hooksPlan.changes];
|
|
546
|
+
const changes = [...instructions.changes, ...trees.changes, ...legacyShim.changes, ...mcp.changes, ...hooksPlan.changes];
|
|
505
547
|
const conflicts = [...instructions.conflicts, ...trees.conflicts, ...mcp.conflicts, ...hooksPlan.conflicts];
|
|
506
548
|
return {
|
|
507
549
|
changes,
|
|
508
550
|
conflicts,
|
|
509
551
|
prerequisites: [],
|
|
510
552
|
requiredNativeResources: changes,
|
|
511
|
-
notices: [...instructions.notices, ...trees.notices, ...mcp.notices],
|
|
553
|
+
notices: [...instructions.notices, ...trees.notices, ...legacyShim.notices, ...mcp.notices],
|
|
512
554
|
};
|
|
513
555
|
}
|
|
514
556
|
|
|
@@ -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;
|
|
@@ -194,6 +194,17 @@ function planCodexAgents({ scope, codexDir, projectRoot, sourceDir, sourceVersio
|
|
|
194
194
|
else if (current === null || current !== agent.source) changes.push({ type: current === null ? 'create' : 'update', identity, file, content: agent.source });
|
|
195
195
|
nextManagedResources.push(resourceFor(agent, scope, sourceVersion));
|
|
196
196
|
}
|
|
197
|
+
// An owned agent the source no longer ships (removed, or downgraded out of the selection): the ledger
|
|
198
|
+
// row is dropped above, so the file has to be dealt with here or it stays in ~/.codex/agents for good.
|
|
199
|
+
// An unmodified file is removed; one the user changed is released (ownership dropped, file kept).
|
|
200
|
+
const shipped = new Set(agents.map((agent ) => `agent:${agent.name}`));
|
|
201
|
+
for (const [identity, record] of owned) {
|
|
202
|
+
if (shipped.has(identity) || !/^agent:[a-z][a-z0-9-]*$/.test(identity)) continue;
|
|
203
|
+
const file = destinationFor(directory, identity.slice('agent:'.length));
|
|
204
|
+
if (!fsImpl.existsSync(file)) continue;
|
|
205
|
+
if (fingerprint(fsImpl.readFileSync(file, 'utf8')) === record.fingerprint) changes.push({ type: 'remove', identity, file });
|
|
206
|
+
else changes.push({ type: 'remove', identity, file, release: true });
|
|
207
|
+
}
|
|
197
208
|
if (conflicts.length) return { ok: false, status: 'conflict', directory, agents, changes: [], conflicts, managedResources };
|
|
198
209
|
return {
|
|
199
210
|
ok: true, status: changes.length ? 'change' : 'unchanged', directory, agents, changes, conflicts: [],
|
|
@@ -203,7 +214,10 @@ function planCodexAgents({ scope, codexDir, projectRoot, sourceDir, sourceVersio
|
|
|
203
214
|
|
|
204
215
|
function applyCodexAgents(plan , { dryRun = false, fsImpl = fs } = {}) {
|
|
205
216
|
if (!plan.ok || dryRun || plan.status === 'unchanged') return { ...plan, applied: false };
|
|
206
|
-
for (const change of plan.changes)
|
|
217
|
+
for (const change of plan.changes) {
|
|
218
|
+
if (change.type !== 'remove') atomicWrite(change.file, change.content, fsImpl);
|
|
219
|
+
else if (!change.release) fsImpl.rmSync(change.file, { force: true });
|
|
220
|
+
}
|
|
207
221
|
return { ...plan, applied: true };
|
|
208
222
|
}
|
|
209
223
|
|