@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.
Files changed (176) hide show
  1. package/README.md +4 -5
  2. package/bin/doflow.js +1 -1
  3. package/core/.claude-plugin/plugin.json +1 -1
  4. package/core/.codex-plugin/plugin.json +1 -1
  5. package/core/.plugin/plugin.json +1 -1
  6. package/core/harnesses/claude/hooks/mcp-tool-guard.sh +4 -1
  7. package/core/harnesses/claude/hooks/pre-implement-gate.sh +4 -1
  8. package/core/harnesses/claude/hooks/session-end.sh +21 -1
  9. package/core/harnesses/claude/settings/settings.json +8 -9
  10. package/core/harnesses/codex/hooks/hooks.json +0 -10
  11. package/core/harnesses/codex/hooks/mcp-tool-guard.sh +4 -1
  12. package/core/harnesses/codex/hooks/pre-implement-gate.sh +4 -1
  13. package/core/harnesses/codex/hooks/session-end.impl.sh +21 -1
  14. package/core/harnesses/copilot/hooks/doflow-lib.sh +4 -3
  15. package/core/harnesses/copilot/hooks/doflow.json +0 -6
  16. package/core/harnesses/kiro/agents/doflow.json +0 -10
  17. package/core/harnesses/kiro/hooks/doflow.json +0 -10
  18. package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +7 -3
  19. package/core/harnesses/kiro/hooks/pre-implement-gate.sh +7 -3
  20. package/core/harnesses/pi/extensions/doflow-guard.js +3 -2
  21. package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +1 -1
  22. package/core/harnesses/shared/hooks/adapters/antigravity.js +1 -1
  23. package/core/harnesses/shared/hooks/bridge/bridge.sh +35 -25
  24. package/core/harnesses/shared/hooks/bridge/envelope.jq +1 -1
  25. package/core/harnesses/shared/hooks/bridge/respond.sh +6 -0
  26. package/core/harnesses/shared/hooks/policies/capture-failure.sh +1 -1
  27. package/core/harnesses/shared/hooks/policies/compact-text.sh +8 -0
  28. package/core/harnesses/shared/hooks/policies/deny-json.sh +23 -1
  29. package/core/harnesses/shared/hooks/policies/mcp-policy.conf +1 -1
  30. package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +4 -6
  31. package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +20 -21
  32. package/core/harnesses/shared/hooks/policies/stop-check.sh +7 -6
  33. package/core/harnesses/shared/hooks/policies/subagent-audit.sh +26 -2
  34. package/core/harnesses/shared/hooks/stream-hook-runner.js +11 -12
  35. package/core/registry/contracts.json +1 -1
  36. package/core/registry/contribution-points.json +5 -2
  37. package/core/registry/gates.json +5 -5
  38. package/core/registry/harnesses.json +9 -9
  39. package/core/registry/intake.json +53 -0
  40. package/core/registry/lifecycle.json +176 -503
  41. package/core/registry/package-templates/intake-category/files/category.json +8 -0
  42. package/core/registry/package-templates/intake-category/template.json +4 -0
  43. package/core/registry/packages/doflow/doflow-package.json +2 -2
  44. package/core/registry/rules.json +8 -8
  45. package/core/registry/settings.json +12 -16
  46. package/core/registry/storage.json +9 -2
  47. package/core/registry/telemetry.json +1 -1
  48. package/core/registry/verification.json +0 -5
  49. package/core/shared/guidance/references/ARTIFACT_FORMAT.md +7 -0
  50. package/core/shared/guidance/references/DOFLOW_CHAIN.md +4 -4
  51. package/core/shared/guidance/references/ORCHESTRATION.md +2 -0
  52. package/core/shared/guidance/references/WORKFLOW_HANDOFF.md +19 -1
  53. package/core/shared/guidance/rules/RULES.md +2 -2
  54. package/core/shared/scripts/doflow/bash/do-git-state.sh +12 -4
  55. package/core/shared/scripts/doflow/bash/do-parallel-check.sh +86 -33
  56. package/core/shared/scripts/doflow/bash/do-review-package.sh +36 -2
  57. package/core/shared/scripts/doflow/bash/validate-artifacts.sh +1 -1
  58. package/core/shared/scripts/doflow/bin/doflow-run +23 -2
  59. package/core/shared/skills/do/SKILL.md +3 -3
  60. package/core/shared/skills/do-brainstorm/SKILL.md +26 -24
  61. package/core/shared/skills/do-brainstorm/references/handoff_branches.md +2 -1
  62. package/core/shared/skills/do-code-review/SKILL.md +7 -4
  63. package/core/shared/skills/do-design/SKILL.md +11 -5
  64. package/core/shared/skills/do-design/references/data_model_example.md +167 -0
  65. package/core/shared/skills/do-diagnose/SKILL.md +4 -2
  66. package/core/shared/skills/do-document/SKILL.md +1 -0
  67. package/core/shared/skills/do-document/references/embedded-research.md +1 -1
  68. package/core/shared/skills/do-execute-plan/SKILL.md +4 -2
  69. package/core/shared/skills/do-execute-plan/references/run_position.md +3 -1
  70. package/core/shared/skills/do-execute-plan/references/scaffold.md +1 -1
  71. package/core/shared/skills/do-flow/SKILL.md +4 -4
  72. package/core/shared/skills/do-flow/references/task_classes.md +5 -5
  73. package/core/shared/skills/do-implement/SKILL.md +9 -7
  74. package/core/shared/skills/do-plan/SKILL.md +9 -6
  75. package/core/shared/templates/doflow/data-model-template.md +136 -102
  76. package/core/shared/workflows/do-flow-chain.md +2 -0
  77. package/package.json +1 -1
  78. package/src/adapters/antigravity/index.js +54 -12
  79. package/src/adapters/claude/index.js +23 -2
  80. package/src/adapters/codex/agents.js +15 -1
  81. package/src/adapters/codex/config.js +5 -2
  82. package/src/adapters/codex/hooks.js +39 -3
  83. package/src/adapters/codex/index.js +13 -0
  84. package/src/adapters/copilot/index.js +11 -4
  85. package/src/adapters/copy-tree.js +8 -2
  86. package/src/adapters/created-file.js +41 -0
  87. package/src/adapters/mcp-entries.js +11 -1
  88. package/src/adapters/opencode/index.js +32 -5
  89. package/src/adapters/pi/index.js +15 -4
  90. package/src/adapters/pi/mcp.js +44 -18
  91. package/src/cli/commands/remove.js +3 -1
  92. package/src/cli/commands/status.js +1 -1
  93. package/src/cli/commands/update.js +7 -3
  94. package/src/cli/index.js +43 -19
  95. package/src/cli/runtime-commands.js +13 -5
  96. package/src/cli/shared.js +5 -3
  97. package/src/helper/marker-merge.js +11 -7
  98. package/src/install/backup-set.js +7 -3
  99. package/src/install/backup.js +13 -7
  100. package/src/install/context.js +2 -1
  101. package/src/install/manifest.js +35 -11
  102. package/src/install/paths.js +38 -0
  103. package/src/install/targets.js +11 -2
  104. package/src/lifecycle/coverage.js +1 -30
  105. package/src/lifecycle/hook-tier.js +2 -2
  106. package/src/lifecycle/index.js +80 -3
  107. package/src/lifecycle/retired-harnesses.js +16 -1
  108. package/src/lifecycle/view.js +3 -2
  109. package/src/registry/index.js +21 -4
  110. package/src/release/docs-blocks.js +37 -53
  111. package/src/runtime/advice/doctor.js +2 -2
  112. package/src/runtime/brief-tools.js +2 -1
  113. package/src/runtime/capability-router.js +26 -3
  114. package/src/runtime/context/pointer.js +84 -17
  115. package/src/runtime/context-pack.js +52 -9
  116. package/src/runtime/dispatch/integrate.js +27 -8
  117. package/src/runtime/dispatch/loop.js +7 -0
  118. package/src/runtime/dispatch/verify-run.js +9 -3
  119. package/src/runtime/evidence-ledger.js +18 -0
  120. package/src/runtime/feature-pointer.js +10 -1
  121. package/src/runtime/health.js +68 -5
  122. package/src/runtime/implementation-gate.js +14 -6
  123. package/src/runtime/intake/engine.js +679 -0
  124. package/src/runtime/intake/index.js +136 -0
  125. package/src/runtime/intake/manifest.js +140 -0
  126. package/src/runtime/intake/taxonomy.js +206 -0
  127. package/src/runtime/knowledge/code-retrieval.js +11 -5
  128. package/src/runtime/knowledge/semble-provider.js +12 -2
  129. package/src/runtime/lifecycle/cli.js +76 -6
  130. package/src/runtime/lifecycle/followup.js +32 -6
  131. package/src/runtime/lifecycle/overview.js +34 -3
  132. package/src/runtime/lifecycle/release.js +16 -3
  133. package/src/runtime/lifecycle/retention.js +12 -15
  134. package/src/runtime/lifecycle/store-upkeep.js +13 -3
  135. package/src/runtime/mask.js +3 -3
  136. package/src/runtime/mcp-consultations.js +11 -5
  137. package/src/runtime/packages/points.js +10 -0
  138. package/src/runtime/research-request.js +3 -1
  139. package/src/runtime/review/command.js +3 -7
  140. package/src/runtime/review/start.js +24 -10
  141. package/src/runtime/roadmap/attendance.js +137 -0
  142. package/src/runtime/roadmap/gates.js +6 -3
  143. package/src/runtime/roadmap/loop.js +127 -33
  144. package/src/runtime/roadmap/merge.js +17 -8
  145. package/src/runtime/roadmap/rows.js +15 -5
  146. package/src/runtime/roadmap/seed.js +153 -41
  147. package/src/runtime/roadmap/worktree-cleanup.js +147 -0
  148. package/src/runtime/settings/files.js +3 -1
  149. package/src/runtime/settings/prepared-run.js +12 -2
  150. package/src/runtime/settings/registry.js +1 -1
  151. package/src/runtime/settings/resolve.js +13 -1
  152. package/src/runtime/settings/write.js +73 -2
  153. package/src/runtime/slots.js +1 -1
  154. package/src/runtime/storage/registry.js +2 -2
  155. package/src/runtime/storage/roots.js +4 -18
  156. package/src/runtime/storage/rules.js +22 -46
  157. package/src/runtime/storage/work-status.js +14 -4
  158. package/src/runtime/task-classifier.js +39 -4
  159. package/src/runtime/task-scope.js +17 -2
  160. package/src/runtime/task-state.js +13 -7
  161. package/src/runtime/trace/analyze.js +4 -2
  162. package/src/runtime/trace/indicators.js +25 -20
  163. package/src/runtime/trace/spans.js +4 -5
  164. package/src/runtime/verb-view.js +268 -0
  165. package/src/runtime/verification/engine.js +0 -45
  166. package/src/runtime/worker/command.js +4 -2
  167. package/src/runtime/worker/launch.js +5 -37
  168. package/src/runtime/worker/readiness.js +10 -6
  169. package/src/runtime/worker/supervisor.js +18 -6
  170. package/src/runtime/workflow-orchestrator.js +39 -7
  171. package/core/harnesses/claude/hooks/pre-bash-guard.sh +0 -10
  172. package/core/harnesses/codex/hooks/pre-bash-guard.sh +0 -10
  173. package/core/harnesses/copilot/hooks/doflow-pre-bash-guard.sh +0 -9
  174. package/core/harnesses/kiro/hooks/pre-bash-guard.sh +0 -25
  175. package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +0 -105
  176. 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 the returned `outcome`, not the exit code. `ACCEPTED` — the returned `workflow` names
80
+ Branch on `outcome`, not the exit code. `ACCEPTED` — the returned `workflow` names
79
81
  the validated class to evaluate readiness against. `REJECTED` — stop, print `message` verbatim
80
82
  (it names `validClasses`), ask the user to choose from that set, then re-validate; never
81
83
  substitute `feature` for the class that was rejected.
@@ -118,8 +120,8 @@ Run every command below from the project root — the walk-up starts at `$PWD`.
118
120
  established.
119
121
  - The repository's `pre-implement-gate` hook checks file existence (`requirement.md` /
120
122
  `design.md` / `plan.md`) on every `Edit`/`Write` call regardless of which skill is editing. On
121
- a harness with an edit hook it also denies the edit of a task held to a readiness record that
122
- is not `READY`; this step writes that record, and is not a way around the hook.
123
+ a harness with an edit hook it also advises when a held task's readiness record is not
124
+ `READY`; the hook never denies. This step writes that record, and is not a way around it.
123
125
 
124
126
  3. **Understand before writing**:
125
127
  - Read the files the change touches and their immediate neighbors — naming conventions, error
@@ -146,8 +148,8 @@ Run every command below from the project root — the walk-up starts at `$PWD`.
146
148
  unimplemented mock functions, or `throw new Error('Not implemented')` placeholders in the
147
149
  produced change. Every changed component must be fully wired and functional.
148
150
  - Before the first source edit, a run for this task needs step 2's recorded readiness to be
149
- `READY`: the hook denies the edit otherwise where there is one, and elsewhere step 7's handoff
150
- is refused after the edits are made. A standalone `NEEDS_EVIDENCE` does not pass it.
151
+ `READY`: the edit hook only advises, and the runtime refuses step 7's handoff after the edits are
152
+ made. A standalone `NEEDS_EVIDENCE` does not pass it.
151
153
  - Keep it scoped to what was asked — no bonus refactors, no speculative abstractions (the same
152
154
  scope-discipline rule the chain's own artifacts would otherwise remind you of; here, hold
153
155
  yourself to it directly).
@@ -215,6 +217,6 @@ advance past the mutating stage only this skill may complete.
215
217
  multi-task checklist through specialist subagents — that is `/do-execute-plan`'s job once a real
216
218
  plan exists; silently override the repository's own `pre-implement-gate` hook. That hook triggers
217
219
  on any `Edit`/`Write` call, not on which skill made it — a branch mid-chain with a started feature
218
- but no `design.md` still gets blocked here exactly as it would anywhere else, and that is by
220
+ but no `design.md` still gets the same advisory here as it would anywhere else, and that is by
219
221
  design, not a gap this skill works around. Resolve a workflow gate, or start a workflow run for a
220
222
  standalone implementation.
@@ -37,13 +37,15 @@ Run every command below from the project root — the walk-up starts at `$PWD`.
37
37
 
38
38
  `feature_slug` `null` with a non-empty `candidate_slugs` is an unresolved choice, not "no active feature": ask one option per entry, re-resolve with `"$DOFLOW" paths --json --slug="<chosen>"`, and use that slug for the rest of this flow. `/do-flow` passing `--slug` already resolves it — no prompt then.
39
39
 
40
+ Once per stage, after `feature_slug` resolves, run `"$DOFLOW" attendance --task-id "<feature_slug>"`; `unattended` → `references/WORKFLOW_HANDOFF.md` § Unattended stage.
41
+
40
42
  2. **Propose one task class; the runtime validates it** — name exactly one class id for the work
41
43
  being planned. `/do-flow` passes one when it invoked this skill; a user who named one settles it;
42
44
  otherwise derive it from `requirement.md` and `design.md`, not from their mere existence.
43
45
  ```bash
44
- "$DOFLOW" classify --task-class "<proposed>" --calling-skill do-plan --json
46
+ "$DOFLOW" classify --task-class "<proposed>" --calling-skill do-plan --view brief --json
45
47
  ```
46
- Branch on the returned `outcome` field, not the exit code.
48
+ Branch on `outcome`, not the exit code.
47
49
  - **`ACCEPTED`** — the returned `workflow` is this run's plan of record; read `stages`, `gates` and `handoff` off it rather than from memory.
48
50
  - **`REJECTED`** — **stop.** Print `message` verbatim (it already names `validClasses` and any `suggestions`), ask the user to choose from `validClasses`, then re-validate. Never substitute `feature`.
49
51
  A rejection may be about **you** rather than the class (`reason: caller-not-a-stage`). Then the fix is to propose one of the classes in `fit.hostingClasses`, or to hand the work to the skill this class names for the stage you meant — not to re-propose the same class.
@@ -59,8 +61,8 @@ stages to implementation, and writing them a `plan.md` adds a gate their workflo
59
61
  not have.
60
62
 
61
63
  3. **Precondition (advisory)** — if `has_requirement` or `has_design` is false, warn and offer to
62
- run `/do-brainstorm` / `/do-design` first. This gate is **advisory** (skippable), not the hard
63
- hook gate.
64
+ run `/do-brainstorm` / `/do-design` first. This gate is **advisory** (skippable), like the edit
65
+ hook.
64
66
  4. **Read inputs** — compile the recorded prior context first, using the same task id rule step 10
65
67
  uses below (the plan task id for anything already scoped to a single `- [ ]` task, otherwise the
66
68
  feature slug):
@@ -124,7 +126,7 @@ Filling §8's task dependency graph, read the guidance tree's `references/DIAGRA
124
126
  SHOULD`) and PASS/FAIL; a base MUST that tier-2 makes SHOULD names that overlay version in its
125
127
  row. On a violation, revise the approach first. A MUST marked FAIL or left
126
128
  without a row refuses step 11's handoff; a SHOULD FAIL is a note. That refusal is the runtime's,
127
- beside the hard hook that covers artifact existence (`references/DOFLOW_CHAIN.md` scopes both).
129
+ beside the advisory hook on artifact existence (`references/DOFLOW_CHAIN.md` scopes both).
128
130
  There is no waiver: revise the plan, or a person amends the constitution with `/do-constitution`.
129
131
  7. **Decompose into Tasks (section 8)** — dependency-ordered, `[US#]`-traced to the requirement's
130
132
  user stories, owner+files named per task, with checkpoints and completion criteria.
@@ -197,7 +199,8 @@ Item schema, provenance rules, and the refused-field list: the guidance tree's `
197
199
  ```bash
198
200
  "$DOFLOW" render-audit --slug="<task id>" --json
199
201
  ```
200
- Leave any returned approval gate for `/do-flow` or `/do-execute-plan` to present.
202
+ Leave any returned approval gate for `/do-flow` or `/do-execute-plan` to present (unattended: the loop
203
+ decides it by policy).
201
204
 
202
205
  12. **Stop** — report the plan path, Constitution Check result, the task count (`[P]`/sequential),
203
206
  and the derived branch name/repo count when the Repo Branch Plan is populated.
@@ -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 narrative
6
- > (Data Model and Sequence / Data Flow) and specs.md's interface-contract detail (§1). Reads ./design.md; distinct from ./specs.md
7
- > the way a persisted shape and the flows that move through it are distinct from the contracts that
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`. Neither §1 nor §2 below carries an `ID`-bearing index — a diagram is not
12
- > a normative, citable statement — so both stay free-form; §3 History is index-then-detail like
13
- > every other chain artifact's final section, and its item-level `Status` is only `Live` or
14
- > `Superseded → <ref>`.
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, NOT part of the indexed convention above — diagrams and schema tables are not
19
- ID-bearing normative statements, so this section stays exactly as specs-template.md's former
20
- §2 already was.
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
- The model is presented in two tiers. The conceptual domain map comes first and carries no
23
- attributes, so the shape of the domain is legible before any field name. One ER view per
24
- bounded subdomain follows, each carrying the attributes, keys and cardinalities of that
25
- subdomain alone. Splitting one diagram into views must not drop an entity, a key or a
26
- constraint the single diagram carried. -->
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
- <!-- The entities and the relationships between them, with no attributes. Skip with "N/A: [why]" if
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
- USER["User"]
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 carrying the attributes,
46
- keys and cardinalities of that subdomain alone. Skip with "N/A: [why]" if the feature persists
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
- USER ||--o{ POST : "creates"
52
- USER {
53
- uuid id PK
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
- COMMENT {
67
- uuid id PK
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 table responsibility] | `id` (UUID) | `[user_id]` → `users(id)` | `idx_[table]_[col]` | `src/db/schema/[table].ts` |
77
+ | `[table_name]` | [one-line responsibility] | [key] | [fk → table(key)] | [index] | [path] |
82
78
 
83
79
  ### UX / UI Specifications
84
- - **Design Tokens & Cues:** [color tokens (e.g. Indigo for Work, Emerald for Personal), badge variants, typography]
85
- - **Component States:** [loading, empty, populated, error states]
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, NOT part of the indexed convention — a diagram is not an ID-bearing normative
90
- statement, so this section stays free-form exactly as §1 does.
91
-
92
- One `sequenceDiagram` per non-trivial interaction the feature introduces. Scaffolding matches
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
- Skip with "N/A: [why]" if the feature introduces no non-trivial interaction. -->
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 API as [entry point]
121
- participant SVC as [service]
97
+ participant SVC as [component]
122
98
  participant DB as [data store]
123
99
 
124
- User->>API: [request]
125
- API->>SVC: [call]
126
-
100
+ User->>SVC: [request]
127
101
  alt [failure condition]
128
- SVC-->>API: [error shape]
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-->>API: [payload]
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
- Note over SVC,DB: [invariant that must hold across this step]
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
- [If the failure branch has consequences the diagram cannot carry — a retry policy, a compensating
141
- action, an idempotency requirement — state them in a sentence beneath the diagram rather than
142
- crowding the fence.]
142
+ **Transitions**
143
143
 
144
- ### Flow: [name]
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
- <!-- One "#### Flow: <name>" heading per end-to-end flow, each embedding its committed render from
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
- This slot carries NO inline text diagram, and that asymmetry is forced by grammar rather than
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
- `references/DIAGRAM_GUIDE.md` §6 governs the lane conventions. Skip with "N/A: [why]" when the
156
- feature's flow involves a single actor. -->
152
+ **Terminal states:** [state, state]
157
153
 
158
- ![End-to-end flow: [name]](design/c4/flow-[name].png)
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
- [One sentence: what triggers this flow and what it ends with. Any consequence the lanes cannot
161
- carry — a retry policy, a compensating action, an idempotency requirement — belongs here rather
162
- than crowded into the diagram.]
194
+ | Entity | Retention | PII class | Migration | Source |
195
+ |---|---|---|---|---|
196
+ | [entity] | [period or rule] | [none, direct, indirect] | [step or none] | [Source] |
163
197
 
164
- ## 3. History
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khoavu882/doflow",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "DoFlow config installer — npx CLI for the registry-driven multi-harness install/update/remove lifecycle",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -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
- const separator = existing === '' || existing.endsWith('\n') ? '\n' : '\n\n';
233
- const content = `${existing}${separator}${rendered}`;
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
- return `${existing.slice(0, start)}${existing.slice(after)}`;
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
- const declaredBy = (harness ) => {
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 a row recorded as kept. A handover
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?.retainedEdit === true };
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: settingsContent(fileName, fs.readFileSync(sourceAbs, 'utf8'), scope),
267
+ managed: fileName === 'settings.json'
268
+ ? withRetiredHooks(settingsContent(fileName, fs.readFileSync(sourceAbs, 'utf8'), scope), scope)
269
+ : settingsContent(fileName, fs.readFileSync(sourceAbs, 'utf8'), scope),
250
270
  projection: { renderer: SETTINGS_RENDERER } });
251
271
  }
252
272
  if (previous.get(statuslineScriptName)) {
@@ -286,7 +306,8 @@ function createClaudeAdapter({ declaredPaths = declaredHarnessPaths().claude } =
286
306
  const currentRaw = fs.readFileSync(target, 'utf8');
287
307
  let merged;
288
308
  try {
289
- merged = mergeSettings(withoutManagedHooks(JSON.parse(currentRaw), tiered.droppedHooks), JSON.parse(managedRaw));
309
+ const live = fileName === 'settings.json' ? withoutManagedHooks(JSON.parse(currentRaw), retiredHooks(scope)) : JSON.parse(currentRaw);
310
+ merged = mergeSettings(withoutManagedHooks(live, tiered.droppedHooks), JSON.parse(managedRaw));
290
311
  } catch {
291
312
  conflicts.push(`${fileName} is not valid JSON and cannot be merged`);
292
313
  continue;
@@ -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) atomicWrite(change.file, change.content, fsImpl);
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