@tacuchi/agent-workflow-cli 21.0.0 → 21.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/README.md +3 -2
  2. package/dist/adapters/git-cli.js +12 -7
  3. package/dist/adapters/git-cli.js.map +1 -1
  4. package/dist/application/capability/compose.js +161 -0
  5. package/dist/application/capability/compose.js.map +1 -0
  6. package/dist/application/capability/descriptor-loader.js +109 -0
  7. package/dist/application/capability/descriptor-loader.js.map +1 -0
  8. package/dist/application/capability/design-handler.js +299 -0
  9. package/dist/application/capability/design-handler.js.map +1 -0
  10. package/dist/application/capability/dispatcher.js +312 -0
  11. package/dist/application/capability/dispatcher.js.map +1 -0
  12. package/dist/application/capability/durable-effect.js +220 -0
  13. package/dist/application/capability/durable-effect.js.map +1 -0
  14. package/dist/application/capability/installed-inventory.js +168 -0
  15. package/dist/application/capability/installed-inventory.js.map +1 -0
  16. package/dist/application/capability/readiness.js +204 -0
  17. package/dist/application/capability/readiness.js.map +1 -0
  18. package/dist/application/capability/resolution.js +264 -0
  19. package/dist/application/capability/resolution.js.map +1 -0
  20. package/dist/application/capability/wrapper.js +195 -0
  21. package/dist/application/capability/wrapper.js.map +1 -0
  22. package/dist/application/context/manifest.js +11 -5
  23. package/dist/application/context/manifest.js.map +1 -1
  24. package/dist/application/context/measure.js +5 -2
  25. package/dist/application/context/measure.js.map +1 -1
  26. package/dist/application/context/plan-service.js +6 -6
  27. package/dist/application/context/plan-service.js.map +1 -1
  28. package/dist/application/design/design-bundle-service.js +148 -0
  29. package/dist/application/design/design-bundle-service.js.map +1 -0
  30. package/dist/application/design/design-proposal-service.js +69 -0
  31. package/dist/application/design/design-proposal-service.js.map +1 -0
  32. package/dist/application/design/design-publish-service.js +275 -7
  33. package/dist/application/design/design-publish-service.js.map +1 -1
  34. package/dist/application/design/design-record-service.js +100 -0
  35. package/dist/application/design/design-record-service.js.map +1 -0
  36. package/dist/application/flow/advance.js +533 -0
  37. package/dist/application/flow/advance.js.map +1 -0
  38. package/dist/application/flow/flow-service.js +65 -0
  39. package/dist/application/flow/flow-service.js.map +1 -0
  40. package/dist/application/flow/run-projection.js +80 -0
  41. package/dist/application/flow/run-projection.js.map +1 -0
  42. package/dist/application/flow/run-state-service.js +107 -0
  43. package/dist/application/flow/run-state-service.js.map +1 -0
  44. package/dist/application/flow/submit.js +423 -0
  45. package/dist/application/flow/submit.js.map +1 -0
  46. package/dist/application/markdown.js +16 -2
  47. package/dist/application/markdown.js.map +1 -1
  48. package/dist/application/resume-service.js +23 -10
  49. package/dist/application/resume-service.js.map +1 -1
  50. package/dist/application/self/hooks-toml.js +137 -17
  51. package/dist/application/self/hooks-toml.js.map +1 -1
  52. package/dist/application/self/host-states.js +87 -1
  53. package/dist/application/self/host-states.js.map +1 -1
  54. package/dist/application/self/install-hooks.js +62 -6
  55. package/dist/application/self/install-hooks.js.map +1 -1
  56. package/dist/application/self/install-skill.js +37 -6
  57. package/dist/application/self/install-skill.js.map +1 -1
  58. package/dist/application/self/uninstall.js +21 -0
  59. package/dist/application/self/uninstall.js.map +1 -1
  60. package/dist/application/skills-resolver-service.js +8 -2
  61. package/dist/application/skills-resolver-service.js.map +1 -1
  62. package/dist/application/status-service.js +4 -1
  63. package/dist/application/status-service.js.map +1 -1
  64. package/dist/application/workline-index-service.js +15 -2
  65. package/dist/application/workline-index-service.js.map +1 -1
  66. package/dist/application/workspace-init-service.js +13 -1
  67. package/dist/application/workspace-init-service.js.map +1 -1
  68. package/dist/cli/commands/capability.js +182 -0
  69. package/dist/cli/commands/capability.js.map +1 -0
  70. package/dist/cli/commands/flow.js +71 -0
  71. package/dist/cli/commands/flow.js.map +1 -0
  72. package/dist/cli/commands/index.js +4 -0
  73. package/dist/cli/commands/index.js.map +1 -1
  74. package/dist/cli/commands/skills.js +55 -2
  75. package/dist/cli/commands/skills.js.map +1 -1
  76. package/dist/cli/commands/status.js +5 -0
  77. package/dist/cli/commands/status.js.map +1 -1
  78. package/dist/cli/help-groups.js +6 -0
  79. package/dist/cli/help-groups.js.map +1 -1
  80. package/dist/cli/parser.js +6 -0
  81. package/dist/cli/parser.js.map +1 -1
  82. package/dist/cli/tui/components/host-admin-section.js +14 -2
  83. package/dist/cli/tui/components/host-admin-section.js.map +1 -1
  84. package/dist/cli/tui/data/workflow-content.js +13 -1
  85. package/dist/cli/tui/data/workflow-content.js.map +1 -1
  86. package/dist/cli/tui/tabs/workflow-tab.js +8 -1
  87. package/dist/cli/tui/tabs/workflow-tab.js.map +1 -1
  88. package/dist/domain/capability/descriptor.js +553 -0
  89. package/dist/domain/capability/descriptor.js.map +1 -0
  90. package/dist/domain/capability/effects.js +90 -0
  91. package/dist/domain/capability/effects.js.map +1 -0
  92. package/dist/domain/capability/protocol.js +494 -0
  93. package/dist/domain/capability/protocol.js.map +1 -0
  94. package/dist/domain/contract-reader.js +84 -0
  95. package/dist/domain/contract-reader.js.map +1 -0
  96. package/dist/domain/design/adapter.js +161 -0
  97. package/dist/domain/design/adapter.js.map +1 -0
  98. package/dist/domain/design/artifact.js +148 -4
  99. package/dist/domain/design/artifact.js.map +1 -1
  100. package/dist/domain/design/capability.js +263 -4
  101. package/dist/domain/design/capability.js.map +1 -1
  102. package/dist/domain/design/direct.js +108 -0
  103. package/dist/domain/design/direct.js.map +1 -0
  104. package/dist/domain/design/external-send.js +65 -0
  105. package/dist/domain/design/external-send.js.map +1 -0
  106. package/dist/domain/design/maturity.js +71 -0
  107. package/dist/domain/design/maturity.js.map +1 -1
  108. package/dist/domain/design/offline.js +92 -0
  109. package/dist/domain/design/offline.js.map +1 -0
  110. package/dist/domain/design/profiles.js +212 -0
  111. package/dist/domain/design/profiles.js.map +1 -0
  112. package/dist/domain/design/proposal.js +234 -0
  113. package/dist/domain/design/proposal.js.map +1 -0
  114. package/dist/domain/design/render-bundle.js +0 -0
  115. package/dist/domain/design/render-bundle.js.map +1 -0
  116. package/dist/domain/design/rendition.js +472 -0
  117. package/dist/domain/design/rendition.js.map +1 -0
  118. package/dist/domain/design/sources.js +180 -0
  119. package/dist/domain/design/sources.js.map +1 -0
  120. package/dist/domain/design/validation.js +12 -68
  121. package/dist/domain/design/validation.js.map +1 -1
  122. package/dist/domain/design/visual-evidence.js +155 -0
  123. package/dist/domain/design/visual-evidence.js.map +1 -0
  124. package/dist/domain/flow/answer.js +459 -0
  125. package/dist/domain/flow/answer.js.map +1 -0
  126. package/dist/domain/flow/authority.js +2338 -0
  127. package/dist/domain/flow/authority.js.map +1 -0
  128. package/dist/domain/flow/authorization.js +62 -0
  129. package/dist/domain/flow/authorization.js.map +1 -0
  130. package/dist/domain/flow/directive.js +421 -0
  131. package/dist/domain/flow/directive.js.map +1 -0
  132. package/dist/domain/flow/rules.js +140 -0
  133. package/dist/domain/flow/rules.js.map +1 -0
  134. package/dist/domain/flow/run-state.js +317 -0
  135. package/dist/domain/flow/run-state.js.map +1 -0
  136. package/dist/domain/harnesses.js +124 -4
  137. package/dist/domain/harnesses.js.map +1 -1
  138. package/dist/domain/host-verification.js +8 -8
  139. package/dist/domain/skills.js +13 -14
  140. package/dist/domain/skills.js.map +1 -1
  141. package/dist/domain/structured-choice-stamp.js +106 -0
  142. package/dist/domain/structured-choice-stamp.js.map +1 -0
  143. package/package.json +1 -1
  144. package/skills/w/SKILL.md +5 -5
  145. package/skills/w/context/MANIFEST.json +42 -1
  146. package/skills/w/harness/HARNESS.md +30 -4
  147. package/skills/w/loops/CHASSIS.md +11 -20
  148. package/skills/w/loops/CODE-POLICIES.md +9 -15
  149. package/skills/w/loops/plan-exec-loop/LOOP.md +11 -13
  150. package/skills/w/loops/plan-new-loop/LOOP.md +6 -4
  151. package/skills/w/loops/plan-refine-loop/LOOP.md +5 -3
  152. package/skills/w/loops/quick-loop/LOOP.md +18 -26
  153. package/skills/w/loops/spec-refine-loop/LOOP.md +14 -18
  154. package/skills/w/modules/COMPACTION.md +2 -6
  155. package/skills/w/modules/DB-SCRIPTS-ONLY.md +2 -0
  156. package/skills/w/modules/DESIGN-REFERENCES.md +15 -11
  157. package/skills/w/modules/IDEATION-GATE.md +3 -3
  158. package/skills/w/modules/PLAN-EXECUTION-BATCHES.md +17 -36
  159. package/skills/w/modules/PLAN-INPUT.md +5 -3
  160. package/skills/w/modules/PLAN-REFINE-SPLIT.md +3 -1
  161. package/skills/w/modules/PLAN-SPLIT-GATE.md +7 -5
  162. package/skills/w/modules/PROMPT-CONTINUITY.md +3 -1
  163. package/skills/w/modules/SPEC-CHANGE-SHAPE.md +4 -4
  164. package/skills/w/roles/README.md +2 -0
  165. package/skills/w/roles/design/CONTRACT.md +80 -0
  166. package/skills/w/roles/design/ROLE.md +7 -0
  167. package/skills/w/schemas/capability-descriptor.schema.json +260 -0
  168. package/skills/w/schemas/design/design-render-bundle.v1.schema.json +191 -0
  169. package/skills/w/schemas/design/design-rendition.v1.schema.json +232 -0
  170. package/skills/w/schemas/design/ui-screen.v1.schema.json +29 -1
@@ -0,0 +1,2338 @@
1
+ /**
2
+ * Who decides what, for every public journey — as data.
3
+ *
4
+ * The registry below is the answer to one question asked of every decision and
5
+ * transition Workline makes: is the correct result derivable from validated
6
+ * inputs, persisted state and explicit rules (`cli`), does it need
7
+ * interpretation, research, synthesis or authorship (`agent`), or is it a
8
+ * preference or an authorization nobody may infer (`human`)?
9
+ *
10
+ * It is a TABLE, never a branch — the same shape `COMPOSED_OPERATIONS` uses. A
11
+ * new transition is a new row; the engine, the ownership projection and the
12
+ * doctrine guards all read this one source. Forcing determinism with a heuristic
13
+ * is explicitly NOT the goal: a decision that is really interpretation is
14
+ * classified `agent` and stays there.
15
+ *
16
+ * `ownership` is the migration axis, and it is deliberately separate from
17
+ * authority: `cli-owned` means this CLI decides WHEN the step is asked and the
18
+ * doctrine may no longer re-state it as a rule, while `authority` still answers
19
+ * who produces the answer. The axis started with a second value — `legacy`, "the
20
+ * doctrine Markdown still decides it" — and every row began there so the
21
+ * migration would be observable rather than asserted. It is now CLOSED: the
22
+ * vocabulary has one member, so a row that fails to declare ownership is a
23
+ * compile error instead of a silent return to a document.
24
+ */
25
+ import { WORKLINE_FLOWS } from "../../application/capability/compose.js";
26
+ export const FLOW_AUTHORITIES = ["cli", "agent", "human"];
27
+ export const TRANSITION_OWNERSHIPS = ["cli-owned"];
28
+ /** Transversal rules of the chassis that no single flow owns. */
29
+ export const CHASSIS_SCOPE = "chassis";
30
+ /** Prefix of a scope that belongs to a transversal command instead of a flow. */
31
+ export const COMMAND_SCOPE_PREFIX = "cmd:";
32
+ /** The four migration tranches the plan cuts over one at a time. */
33
+ export const FLOW_TRANCHES = ["quick", "spec", "plan", "chassis"];
34
+ /** A decision computes a verdict; writing is the exception that declares itself. */
35
+ export const DEFAULT_TRANSITION_EFFECTS = ["read_only"];
36
+ /** What applying this transition does. */
37
+ export function effectsOf(decision) {
38
+ return decision.effects ?? DEFAULT_TRANSITION_EFFECTS;
39
+ }
40
+ /**
41
+ * The run coordinates an invocation may reference, and nothing else.
42
+ *
43
+ * Closed on purpose: an invocation that could interpolate arbitrary state would
44
+ * be a template language, and a registry row would stop being readable as the
45
+ * exact thing that runs. These two are what a session-scoped command needs — the
46
+ * folder and its correlative — and both are facts the engine owns.
47
+ */
48
+ export const RUN_PLACEHOLDERS = ["{session}", "{code}"];
49
+ /** The delegated action of a transition, or `null` when the engine applies it itself. */
50
+ export function actionOf(decision) {
51
+ return decision.action ?? null;
52
+ }
53
+ /** What makes this transition happen at all, or `null` when it always does. */
54
+ export function conditionOf(decision) {
55
+ return decision.condition ?? null;
56
+ }
57
+ /** The alternatives this row declares as its own, or `null` for the generic pair. */
58
+ export function alternativesOf(decision) {
59
+ return decision.alternatives ?? null;
60
+ }
61
+ /**
62
+ * Where a transversal row sits inside a flow's journey.
63
+ *
64
+ * The chassis is not a `WorklineFlow`, so nothing walks `chassis` on its own: a
65
+ * run walks ONE flow, and a transversal step is only crossed if it is composed
66
+ * into that flow's journey at a declared position. `prefix` runs before the
67
+ * flow's own first step and `suffix` after its last — which is exactly the two
68
+ * positions the engine's transversal steps occupy, one establishing a constraint
69
+ * the whole run is held to, the other closing it.
70
+ *
71
+ * Declared as data next to the row for the same reason the journey itself is a
72
+ * table: a position computed in code would be a second place to look when asking
73
+ * "when does this happen?".
74
+ */
75
+ export const RUN_PLACEMENTS = ["prefix", "suffix"];
76
+ /** How this row is observable when no run walks it, or `null` when one does. */
77
+ export function realizationOf(decision) {
78
+ return decision.realized_by ?? null;
79
+ }
80
+ /** Where this row sits in a flow's journey, or `null` when it is the flow's own. */
81
+ export function placementOf(decision) {
82
+ return decision.placement ?? null;
83
+ }
84
+ const CHASSIS = CHASSIS_SCOPE;
85
+ const cmd = (name) => `${COMMAND_SCOPE_PREFIX}${name}`;
86
+ /**
87
+ * What `loops/quick-loop/LOOP.md` says about who decides its deterministic steps.
88
+ *
89
+ * ONE marker for the whole tranche, not one per row: the guard demands the text
90
+ * be present in the document, and twelve variants of the same sentence would cost
91
+ * context budget to say the same thing twelve times. The document keeps the
92
+ * EXPLANATION of each rule and hands over the rule itself.
93
+ */
94
+ const QUICK_ATTRIBUTION = "the deterministic steps below are decided by the CLI (`aw flow advance`), not by this document";
95
+ /**
96
+ * What the engine's own document says about who decides its transversal steps.
97
+ *
98
+ * Deliberately the SAME sentence the tranches use. The chassis is the last
99
+ * document to hand its rules over, and reading the identical marker there is the
100
+ * point: a rule the CLI decides says so the same way wherever it used to live.
101
+ */
102
+ const CHASSIS_ATTRIBUTION = QUICK_ATTRIBUTION;
103
+ /**
104
+ * What `modules/COMPACTION.md` says about who decides compaction.
105
+ *
106
+ * Its own marker rather than the shared one: compaction is not a step of any
107
+ * journey — it fires at whatever boundary the run happens to be standing on —
108
+ * so the sentence names the command that executes it instead of the walk.
109
+ */
110
+ const COMPACTION_ATTRIBUTION = "`aw checkpoint-write --can-pause`";
111
+ /**
112
+ * What the continuity documents say about who resolves which line a prompt joins.
113
+ *
114
+ * Also its own: these rules fire BEFORE a run exists, so no directive can carry
115
+ * them. What decides is the session resolution of the commands named here.
116
+ */
117
+ const CONTINUITY_ATTRIBUTION = "`aw resume`";
118
+ /**
119
+ * The entry gate's rule: two of the five declared signals.
120
+ *
121
+ * Shared by the row that APPLIES it and by the row that only happens when it
122
+ * fired, so the number lives once. Changing it here changes both, which is the
123
+ * whole reason it is a constant and not two literals.
124
+ */
125
+ const ENTRY_SIZE_THRESHOLD = { observed: "quick.entry-gate-signal", min: 2 };
126
+ /**
127
+ * The one thing a session-scoped action needs: this run's session.
128
+ *
129
+ * `aw` runs from the workspace root, so the target of a session-scoped
130
+ * invocation is the session it acts ON, and the correlative travels in the
131
+ * arguments. Both are bound from the run state before the boundary is emitted.
132
+ */
133
+ const SESSION_TARGET = "{session}";
134
+ const CHASSIS_MD = "loops/CHASSIS.md";
135
+ const CODE_POLICIES_MD = "loops/CODE-POLICIES.md";
136
+ const SKILL_MD = "SKILL.md";
137
+ const QUICK_LOOP = "loops/quick-loop/LOOP.md";
138
+ const SPEC_LOOP = "loops/spec-refine-loop/LOOP.md";
139
+ const IDEATION_GATE = "modules/IDEATION-GATE.md";
140
+ const CHANGE_SHAPE = "modules/SPEC-CHANGE-SHAPE.md";
141
+ /**
142
+ * What SPEC's documents say about who decides their deterministic steps.
143
+ *
144
+ * One marker per document, because the guard reads each row's own document: the
145
+ * loop states it once for its nine rows, and each migrated module states it for
146
+ * its own. Three documents used to be absent from this list because a tranche that
147
+ * had not been cut over still read them; the closing tranche resolved each on its
148
+ * own terms. `SPEC-CHANGE-SHAPE.md` gained the split branch's steps, which were
149
+ * carrying `spec-new`'s document by mistake. `DESIGN-REFERENCES.md` attributes its
150
+ * one remaining row to `aw designs` rather than to this marker, because the CLI
151
+ * puts the inventory in front of the judgment instead of deciding the step.
152
+ * `SPLIT-GATE.md` keeps its rule and gets no marker at all: it belongs to
153
+ * `/w:spec-new`, which starts no loop, and the registry declares that exclusion.
154
+ */
155
+ const SPEC_ATTRIBUTION = "the deterministic steps below are decided by the CLI (`aw flow advance`), not by this document";
156
+ const PLAN_NEW_LOOP = "loops/plan-new-loop/LOOP.md";
157
+ const PLAN_REFINE_LOOP = "loops/plan-refine-loop/LOOP.md";
158
+ const PLAN_EXEC_LOOP = "loops/plan-exec-loop/LOOP.md";
159
+ const BATCHES_MD = "modules/PLAN-EXECUTION-BATCHES.md";
160
+ const PLAN_SPLIT_GATE = "modules/PLAN-SPLIT-GATE.md";
161
+ const PLAN_REFINE_SPLIT = "modules/PLAN-REFINE-SPLIT.md";
162
+ const PLAN_INPUT = "modules/PLAN-INPUT.md";
163
+ const DB_SCRIPTS_ONLY = "modules/DB-SCRIPTS-ONLY.md";
164
+ /**
165
+ * What PLAN's documents say about who decides their deterministic steps.
166
+ *
167
+ * Nine documents, one marker each — the three loops, the four PLAN modules, and
168
+ * the two the code-editing loops share. `CODE-POLICIES.md` and
169
+ * `DB-SCRIPTS-ONLY.md` are here for a reason worth stating: their only readers
170
+ * are `quick` and `plan-exec`, and with PLAN cut over neither is doctrine's
171
+ * anymore. Until that tranche they had to stay, because retiring a rule from a
172
+ * document an undirected journey reads would leave that journey without it.
173
+ */
174
+ const PLAN_ATTRIBUTION = "the deterministic steps below are decided by the CLI (`aw flow advance`), not by this document";
175
+ /**
176
+ * The multi-plan gate's rule: two of the five declared signals.
177
+ *
178
+ * A factory rather than a constant because the SAME rule is applied by two
179
+ * journeys over their own observation — `plan-new` cuts a plan being generated,
180
+ * `plan-refine` cuts one that already exists — and a threshold may only count
181
+ * signals declared inside the journey it belongs to. What must not be duplicated
182
+ * is the number, and this keeps it in one place.
183
+ */
184
+ const splitThreshold = (observed) => ({ observed, min: 2 });
185
+ /**
186
+ * The batching rule, stated the way the module states it.
187
+ *
188
+ * "A range is eligible only when ALL of these are true… anything else is
189
+ * `isolated`." So the threshold counts the facts that BREAK eligibility, and one
190
+ * is enough: `min: 1` is not a weak bar here, it is the whole rule. The three
191
+ * journeys that infer a partition each observe their own row, for the same reason
192
+ * the split gate does.
193
+ */
194
+ const ineligibleRange = (observed) => ({ observed, min: 1 });
195
+ /** The five observable facts that make a consecutive range ineligible for `continuous`. */
196
+ const BATCH_ELIGIBILITY_SIGNALS = [
197
+ "plan.dependency-outside-range",
198
+ "plan.result-shapes-later",
199
+ "plan.blocker-between-phases",
200
+ "plan.recovery-boundary",
201
+ "plan.not-one-reviewable-unit",
202
+ ];
203
+ /** The five signals of the multi-plan split gate, shared by both plan loops. */
204
+ const PLAN_SPLIT_SIGNALS = [
205
+ "plan.independent-tranches",
206
+ "plan.no-shared-deps",
207
+ "plan.distinct-priorities",
208
+ "plan.far-beyond-s",
209
+ "plan.staging-requested",
210
+ ];
211
+ /**
212
+ * Every decision and transition of every public journey, in journey order
213
+ * within each scope. The engine walks a scope's rows in this order.
214
+ */
215
+ export const FLOW_DECISIONS = [
216
+ // ── Transversal chassis: the engine every loop runs underneath its deltas ──
217
+ //
218
+ // Three forms of observable ownership live in this block, and which one a row
219
+ // takes is a fact about the RULE, not a preference. A row with a `placement` is
220
+ // composed into every flow's journey and a real run crosses it. A row with a
221
+ // `realized_by` is held by something that already exists — a symbol of this
222
+ // engine, or the flow rows that instance it. Everything else that used to be
223
+ // here moved to the scope of the command that executes it, because a rule that
224
+ // fires between prompts or at an arbitrary boundary is not a step of a journey.
225
+ {
226
+ id: "chassis.docs-boundary",
227
+ scope: CHASSIS,
228
+ title: "resolver en qué carpeta de docs puede escribir el loop",
229
+ authority: "cli",
230
+ ownership: "cli-owned",
231
+ document: CHASSIS_MD,
232
+ attribution: CHASSIS_ATTRIBUTION,
233
+ // First of the whole journey, and deliberately so: it fixes the only folders
234
+ // this run may write BEFORE any step that writes is even emitted. Resolved
235
+ // afterwards it would be a rule checked against writes that already happened.
236
+ placement: "prefix",
237
+ },
238
+ {
239
+ id: "chassis.research-exhaustion",
240
+ scope: CHASSIS,
241
+ title: "marcar un gap agotado tras el tope de intentos y degradarlo",
242
+ authority: "cli",
243
+ ownership: "cli-owned",
244
+ document: CHASSIS_MD,
245
+ attribution: CHASSIS_ATTRIBUTION,
246
+ // Same shape as the boundary above: the step fixes the cap the whole run is
247
+ // held to, and the engine applies it at every boundary. What it prevents is
248
+ // the loop the doctrine names — asking the same thing until something gives —
249
+ // by turning the attempt after the cap into a degradation with a destination.
250
+ placement: "prefix",
251
+ },
252
+ {
253
+ id: "chassis.session-create-or-resume",
254
+ scope: CHASSIS,
255
+ title: "abrir la sesión de la corrida o reanudar la existente",
256
+ authority: "cli",
257
+ ownership: "cli-owned",
258
+ document: CHASSIS_MD,
259
+ attribution: CHASSIS_ATTRIBUTION,
260
+ effects: ["local_additive"],
261
+ // Not a step of the composed journey, and the reason is precise: every flow
262
+ // ALREADY opens with its own session row, so composing a sixth one would ask
263
+ // the same question twice per run. The rule is transversal; its occurrences
264
+ // are these five, and they are what make it true.
265
+ realized_by: {
266
+ kind: "transitions",
267
+ ids: [
268
+ "quick.session-create",
269
+ "spec-refine.session",
270
+ "plan-new.session",
271
+ "plan-refine.session",
272
+ "plan-exec.session",
273
+ ],
274
+ },
275
+ },
276
+ {
277
+ id: "chassis.session-numbering",
278
+ scope: CHASSIS,
279
+ title: "asignar el NNN global y secuencial de la sesión",
280
+ authority: "cli",
281
+ ownership: "cli-owned",
282
+ document: "modules/SESSION-NUMBERING.md",
283
+ attribution: "The CLI owns the number (hard rule)",
284
+ realized_by: { kind: "transitions", ids: ["session-create.numbering"] },
285
+ },
286
+ {
287
+ id: "chassis.success-criteria-seed",
288
+ scope: CHASSIS,
289
+ title: "sembrar los criterios de éxito antes de ejecutar (verification-first)",
290
+ authority: "cli",
291
+ ownership: "cli-owned",
292
+ document: CHASSIS_MD,
293
+ attribution: CHASSIS_ATTRIBUTION,
294
+ // QUICK's two, and only QUICK's: it is the one flow whose deliverable has no
295
+ // document of its own, so its criteria have to be authored and ratified as a
296
+ // step. The other four take theirs from the spec or plan they already read —
297
+ // "referenced, not duplicated" — so there is nothing for them to seed, and
298
+ // inventing a row for each would be four steps that ask nothing.
299
+ realized_by: {
300
+ kind: "transitions",
301
+ ids: ["quick.success-criteria-authoring", "quick.success-criteria-ratification"],
302
+ },
303
+ },
304
+ {
305
+ id: "chassis.gap-detection",
306
+ scope: CHASSIS,
307
+ title: "detectar los gaps materiales del trabajo",
308
+ authority: "agent",
309
+ ownership: "cli-owned",
310
+ document: CHASSIS_MD,
311
+ attribution: CHASSIS_ATTRIBUTION,
312
+ realized_by: {
313
+ kind: "transitions",
314
+ ids: ["spec-refine.gap-recognition", "plan-exec.entry-gap-recognition"],
315
+ },
316
+ },
317
+ {
318
+ id: "chassis.gap-batching",
319
+ scope: CHASSIS,
320
+ title: "tomar un lote de a lo sumo 3 gaps por vuelta",
321
+ authority: "cli",
322
+ ownership: "cli-owned",
323
+ document: CHASSIS_MD,
324
+ attribution: CHASSIS_ATTRIBUTION,
325
+ // The engine holds the stricter form of the same rule: it stops at the FIRST
326
+ // step it cannot apply, so a run never carries more than one open boundary.
327
+ // "At most three" is the ceiling of a loop that batches; one is what a
328
+ // directed journey emits, and it satisfies the ceiling by construction.
329
+ realized_by: {
330
+ kind: "engine",
331
+ module: "src/application/flow/advance.ts",
332
+ symbol: "resolveBoundary",
333
+ },
334
+ },
335
+ {
336
+ id: "chassis.resolver-selection",
337
+ scope: CHASSIS,
338
+ title: "elegir el resolvedor de un gap con la regla adoptar/investigar/probar/preguntar",
339
+ authority: "cli",
340
+ ownership: "cli-owned",
341
+ document: CHASSIS_MD,
342
+ attribution: CHASSIS_ATTRIBUTION,
343
+ // The ask-vs-research discriminator IS the boundary taxonomy, already: a
344
+ // `semantic` boundary is the judgment the agent produces, a `human` one the
345
+ // preference only a person holds, an `execution` one the thing that has to be
346
+ // run. Choosing the resolver and classifying the boundary are one act.
347
+ realized_by: {
348
+ kind: "engine",
349
+ module: "src/application/flow/advance.ts",
350
+ symbol: "boundaryKind",
351
+ },
352
+ },
353
+ {
354
+ id: "chassis.minimality-lens",
355
+ scope: CHASSIS,
356
+ title: "juzgar si el entregable pesa más de lo que sus criterios exigen",
357
+ authority: "agent",
358
+ ownership: "cli-owned",
359
+ document: CHASSIS_MD,
360
+ attribution: CHASSIS_ATTRIBUTION,
361
+ realized_by: {
362
+ kind: "transitions",
363
+ ids: ["quick.review-findings", "plan-exec.review-findings"],
364
+ },
365
+ },
366
+ {
367
+ id: "chassis.convergence-gate",
368
+ scope: CHASSIS,
369
+ title: "evaluar el gate de convergencia sobre los criterios de éxito",
370
+ authority: "cli",
371
+ ownership: "cli-owned",
372
+ document: CHASSIS_MD,
373
+ attribution: CHASSIS_ATTRIBUTION,
374
+ // One per flow, which is what "each heir names its own instance of this gate"
375
+ // means once the heirs stopped naming it in prose.
376
+ realized_by: {
377
+ kind: "transitions",
378
+ ids: [
379
+ "quick.convergence-gate",
380
+ "spec-refine.ready-gate",
381
+ "plan-new.coherence-gate",
382
+ "plan-refine.executability-gate",
383
+ "plan-exec.final-validation",
384
+ ],
385
+ },
386
+ },
387
+ {
388
+ id: "chassis.criteria-flip",
389
+ scope: CHASSIS,
390
+ title: "marcar en verde los criterios que el gate aprobó",
391
+ authority: "cli",
392
+ ownership: "cli-owned",
393
+ document: CHASSIS_MD,
394
+ attribution: CHASSIS_ATTRIBUTION,
395
+ effects: ["mutate_overwrite"],
396
+ // The two rows that WRITE the approved state into the document the gate
397
+ // judged. `quick` has none because it has no document to write it into, and
398
+ // the two plan-authoring flows converge on a document they hand to the next
399
+ // flow rather than on one they mark green.
400
+ realized_by: {
401
+ kind: "transitions",
402
+ ids: ["spec-refine.status-promotion", "plan-exec.plan-done"],
403
+ },
404
+ },
405
+ {
406
+ id: "chassis.structured-choice-shape",
407
+ scope: CHASSIS,
408
+ title: "armar la pregunta: hasta 3 de contenido más el control de flujo, recomendación primero",
409
+ authority: "cli",
410
+ ownership: "cli-owned",
411
+ document: CHASSIS_MD,
412
+ attribution: CHASSIS_ATTRIBUTION,
413
+ // Enforced at construction, not advised: a directive whose choices lack a
414
+ // consequence, or carry zero or two recommendations, is refused before it
415
+ // reaches anybody. That is stronger than the doctrine it replaces, which
416
+ // could only state the shape and hope.
417
+ realized_by: {
418
+ kind: "engine",
419
+ module: "src/domain/flow/directive.ts",
420
+ symbol: "checkChoices",
421
+ },
422
+ },
423
+ {
424
+ id: "chassis.flow-control",
425
+ scope: CHASSIS,
426
+ title: "decidir Compactar o Cerrar en cualquier momento",
427
+ authority: "human",
428
+ ownership: "cli-owned",
429
+ document: CHASSIS_MD,
430
+ attribution: CHASSIS_ATTRIBUTION,
431
+ // The control is appended by the engine to every boundary that offers
432
+ // alternatives — never by the row, which is what keeps a tranche from writing
433
+ // a question nobody can walk away from or pause.
434
+ realized_by: {
435
+ kind: "engine",
436
+ module: "src/application/flow/advance.ts",
437
+ symbol: "flowControlChoices",
438
+ },
439
+ },
440
+ {
441
+ id: "chassis.finalize",
442
+ scope: CHASSIS,
443
+ title: "persistir CHECKPOINT, escribir BACKLOG solo si algo quedó diferido y cerrar la sesión",
444
+ authority: "cli",
445
+ ownership: "cli-owned",
446
+ document: CHASSIS_MD,
447
+ attribution: CHASSIS_ATTRIBUTION,
448
+ effects: ["mutate_overwrite"],
449
+ // The only suffix: every flow ends the same way, and no flow row says so —
450
+ // each tranche stopped at its own last decision and the close was doctrine's.
451
+ placement: "suffix",
452
+ // Delegated for the reason every write is: closing the session upserts its
453
+ // HISTORY row, and a run that recorded "finalized" without that having
454
+ // happened would leave the durable register disagreeing with the session.
455
+ action: {
456
+ invocation: {
457
+ program: "aw",
458
+ args: ["session-close", "--code", "{code}"],
459
+ target: ".",
460
+ input: null,
461
+ },
462
+ evidence: ["chassis.sesion-cerrada"],
463
+ idempotent: true,
464
+ recovery: "una sesión que no cerró deja la corrida abierta: no la marques finalizada — reparás la fila del registro con 'aw history-update' y volvés a cerrar",
465
+ },
466
+ },
467
+ // ── QUICK — the pilot tranche, and the first one this CLI decides ──────────
468
+ //
469
+ // Twelve rows migrated with the pilot: every decision whose rule lived in the
470
+ // loop's own document. The five in CODE-POLICIES/DB-SCRIPTS-ONLY waited for the
471
+ // PLAN tranche and travelled with it — those two documents are read by `quick`
472
+ // and `plan-exec` and by nobody else, so only once execution was cut over could
473
+ // their rules be retired without leaving a journey without them.
474
+ {
475
+ id: "quick.entry-gate-signal",
476
+ scope: "quick",
477
+ title: "reconocer cada señal de tamaño en el objetivo recibido",
478
+ authority: "agent",
479
+ ownership: "cli-owned",
480
+ document: QUICK_LOOP,
481
+ attribution: QUICK_ATTRIBUTION,
482
+ signals: [
483
+ "quick.needs-architecture",
484
+ "quick.two-or-more-sources",
485
+ "quick.multiple-deliverables",
486
+ "quick.large-feature-or-refactor",
487
+ "quick.ambiguous-requirements",
488
+ ],
489
+ },
490
+ {
491
+ id: "quick.entry-size-gate",
492
+ scope: "quick",
493
+ title: "aplicar el umbral de dos señales que dispara el gate de entrada",
494
+ authority: "cli",
495
+ ownership: "cli-owned",
496
+ document: QUICK_LOOP,
497
+ attribution: QUICK_ATTRIBUTION,
498
+ // The row is the MOMENT the verdict is computed; the rule itself is
499
+ // `ENTRY_SIZE_THRESHOLD`, read by every step conditional on it. Declaring the
500
+ // threshold here too would be a second copy that nothing reads.
501
+ },
502
+ {
503
+ id: "quick.anti-duplicate",
504
+ scope: "quick",
505
+ title: "recomendar reanudar la spec o sesión que ya cubre este objetivo",
506
+ authority: "cli",
507
+ ownership: "cli-owned",
508
+ document: QUICK_LOOP,
509
+ attribution: QUICK_ATTRIBUTION,
510
+ // Conditional for the same reason the choice is: the search exists to change
511
+ // what the gate recommends, so on a task that never triggers the gate it is a
512
+ // read nobody asked for. The doctrine had it INSIDE the gate branch.
513
+ condition: {
514
+ threshold: ENTRY_SIZE_THRESHOLD,
515
+ otherwise: "el umbral no disparó: no hay gate que recomiende reanudar nada",
516
+ },
517
+ // The search is the board this CLI already projects — specs, plans and
518
+ // sessions in one read — so what the run credits is a real listing and not
519
+ // "I looked". Read-only, so it never stops to be authorized.
520
+ action: {
521
+ invocation: { program: "aw", args: ["status", "--json"], target: ".", input: null },
522
+ evidence: ["quick.board-listed"],
523
+ idempotent: true,
524
+ recovery: "volvé a correr 'aw status --json' y devolvé su salida real; si el tablero no se puede leer, resolvé eso antes de seguir",
525
+ },
526
+ },
527
+ {
528
+ id: "quick.gate-choice",
529
+ scope: "quick",
530
+ title: "elegir entre cambiar a SPEC, seguir en quick o recortar el alcance",
531
+ authority: "human",
532
+ ownership: "cli-owned",
533
+ document: QUICK_LOOP,
534
+ attribution: QUICK_ATTRIBUTION,
535
+ // The gate only exists when it fired. Asking always would make the directed
536
+ // journey ask what the doctrine it replaces does not ask — and "borderline
537
+ // continues in quick without asking" is the doctrine's own words.
538
+ condition: {
539
+ threshold: ENTRY_SIZE_THRESHOLD,
540
+ otherwise: "el umbral de dos señales no disparó: la tarea sigue en quick sin preguntar nada",
541
+ },
542
+ alternatives: [
543
+ {
544
+ label: "Cambiar a SPEC",
545
+ consequence: "no se crea sesión quick: la línea de trabajo pasa al flow SPEC con el objetivo original",
546
+ recommended: true,
547
+ },
548
+ {
549
+ label: "Seguir en quick",
550
+ consequence: "el recorrido continúa como quick con el objetivo tal cual llegó",
551
+ recommended: false,
552
+ },
553
+ {
554
+ label: "Recortar alcance",
555
+ consequence: "el objetivo pasa a ser la sub-tarea que sí entra en un quick y el resto queda en BACKLOG",
556
+ recommended: false,
557
+ },
558
+ ],
559
+ },
560
+ {
561
+ id: "quick.session-create",
562
+ scope: "quick",
563
+ title: "crear la sesión liviana de la tarea",
564
+ authority: "cli",
565
+ ownership: "cli-owned",
566
+ document: QUICK_LOOP,
567
+ attribution: QUICK_ATTRIBUTION,
568
+ effects: ["local_additive"],
569
+ // The engine cannot author the descriptor or the objective — those are the
570
+ // caller's — so what it names is the read that proves the session it is
571
+ // running inside really exists as an artifact, with its SESSION.md.
572
+ action: {
573
+ invocation: {
574
+ program: "aw",
575
+ args: ["session-artifacts", "--code", "{code}"],
576
+ target: SESSION_TARGET,
577
+ input: null,
578
+ },
579
+ evidence: ["quick.session-present"],
580
+ idempotent: true,
581
+ recovery: "creá la sesión con 'aw session-create --type quick --name <slug>-quick --objetivo \"<objetivo>\"' y volvé a devolver la lectura",
582
+ },
583
+ },
584
+ {
585
+ id: "quick.success-criteria-authoring",
586
+ scope: "quick",
587
+ title: "redactar la prueba o la rúbrica proporcional del entregable",
588
+ authority: "agent",
589
+ ownership: "cli-owned",
590
+ document: QUICK_LOOP,
591
+ attribution: QUICK_ATTRIBUTION,
592
+ // One signal, and it is the one the next row's condition reads: whether what
593
+ // is being verified is a rubric a person has to ratify, or a check that runs.
594
+ signals: ["quick.deliverable-is-analysis"],
595
+ },
596
+ {
597
+ id: "quick.success-criteria-ratification",
598
+ scope: "quick",
599
+ title: "ratificar la rúbrica cuando el entregable es análisis o diseño",
600
+ authority: "human",
601
+ ownership: "cli-owned",
602
+ document: QUICK_LOOP,
603
+ attribution: QUICK_ATTRIBUTION,
604
+ condition: {
605
+ threshold: { observed: "quick.success-criteria-authoring", min: 1 },
606
+ otherwise: "el entregable no es análisis ni diseño: su criterio es una prueba que corre, y no se ratifica",
607
+ },
608
+ },
609
+ {
610
+ id: "quick.artifact-seed-order",
611
+ scope: "quick",
612
+ title: "sembrar objetivo, criterios y CHECKPOINT antes de trabajar",
613
+ authority: "cli",
614
+ ownership: "cli-owned",
615
+ document: QUICK_LOOP,
616
+ attribution: QUICK_ATTRIBUTION,
617
+ effects: ["local_additive"],
618
+ // Verification-first is only real if the seed is checkable: the three pieces
619
+ // come back as the artifacts' actual content, not as a claim that they were
620
+ // written.
621
+ action: {
622
+ invocation: {
623
+ program: "aw",
624
+ args: ["session-artifacts", "--code", "{code}", "--dump", "objetivo,checkpoint"],
625
+ target: SESSION_TARGET,
626
+ input: null,
627
+ },
628
+ evidence: [
629
+ "quick.objetivo-sembrado",
630
+ "quick.criterios-sembrados",
631
+ "quick.checkpoint-sembrado",
632
+ ],
633
+ idempotent: true,
634
+ recovery: "sembrá lo que falte (objetivo, criterios de éxito y CHECKPOINT.Pending) y volvé a devolver el dump: sembrar de nuevo lo ya escrito no rompe nada",
635
+ },
636
+ },
637
+ {
638
+ id: "quick.branch-precondition",
639
+ scope: "quick",
640
+ title: "verificar la rama esperada de cada fuente antes de editar",
641
+ authority: "cli",
642
+ ownership: "cli-owned",
643
+ document: CODE_POLICIES_MD,
644
+ attribution: PLAN_ATTRIBUTION,
645
+ // Same read as PLAN's, and for the same reason: `aw check-branch` with no
646
+ // --source resolves no target and passes unconditionally.
647
+ action: {
648
+ invocation: { program: "aw", args: ["sources", "--verbose"], target: ".", input: null },
649
+ evidence: ["quick.rama-verificada"],
650
+ idempotent: true,
651
+ recovery: "resolvé la rama de la fuente que no coincide y volvé a leer las fuentes: nunca limpies ni cambies de rama sin confirmación",
652
+ },
653
+ },
654
+ {
655
+ id: "quick.deliverable-authoring",
656
+ scope: "quick",
657
+ title: "producir el cambio mínimo o el análisis que la tarea pide",
658
+ authority: "agent",
659
+ ownership: "cli-owned",
660
+ document: QUICK_LOOP,
661
+ attribution: QUICK_ATTRIBUTION,
662
+ },
663
+ {
664
+ id: "quick.db-touched",
665
+ scope: "quick",
666
+ title: "reconocer si la tarea llegó a tocar una base de datos",
667
+ authority: "agent",
668
+ ownership: "cli-owned",
669
+ document: DB_SCRIPTS_ONLY,
670
+ attribution: PLAN_ATTRIBUTION,
671
+ // A quick that never went near a database has no statement to derive and no
672
+ // `SCRIPTS.sql` to hand back, so the row below it used to demand an artifact
673
+ // that should not exist. Declaring the signal is what lets the rule apply
674
+ // exactly where it has something to govern.
675
+ signals: ["quick.db-touched"],
676
+ },
677
+ {
678
+ id: "quick.db-scripts-only",
679
+ scope: "quick",
680
+ title: "derivar todo DDL o DML al script de la sesión sin ejecutarlo",
681
+ authority: "cli",
682
+ ownership: "cli-owned",
683
+ document: DB_SCRIPTS_ONLY,
684
+ attribution: PLAN_ATTRIBUTION,
685
+ effects: ["local_additive"],
686
+ condition: {
687
+ threshold: { observed: "quick.db-touched", of: ["quick.db-touched"], min: 1 },
688
+ otherwise: "la tarea no tocó ninguna base de datos: no hay sentencia que derivar",
689
+ },
690
+ // Migrated with the PLAN tranche and not with QUICK, for the same reason
691
+ // CODE-POLICIES was: `plan-exec` reads this module too, and retiring its rule
692
+ // while execution still decided from it would have left that journey without
693
+ // it. What comes back is the script itself — the whole point of the rule is
694
+ // that the statement was WRITTEN and not run.
695
+ action: {
696
+ invocation: {
697
+ program: "aw",
698
+ args: ["session-artifacts", "--code", "{code}", "--dump", "scripts"],
699
+ target: SESSION_TARGET,
700
+ input: null,
701
+ },
702
+ evidence: ["quick.scripts-derivados"],
703
+ idempotent: true,
704
+ recovery: "escribí el DDL o DML en el SCRIPTS.sql de la sesión y volvé a devolver el dump; ejecutarlo no es una alternativa que este contrato admita",
705
+ },
706
+ },
707
+ {
708
+ id: "quick.growth-escalation",
709
+ scope: "quick",
710
+ title: "aplicar el mismo umbral de señales cuando la tarea crece a mitad del loop",
711
+ authority: "cli",
712
+ ownership: "cli-owned",
713
+ document: QUICK_LOOP,
714
+ attribution: QUICK_ATTRIBUTION,
715
+ // "The same threshold" as the entry gate, and it is the same one: the mid-loop
716
+ // escalation re-applies `ENTRY_SIZE_THRESHOLD` over the signals declared then.
717
+ },
718
+ {
719
+ id: "quick.escalation-destination",
720
+ scope: "quick",
721
+ title: "resolver SPEC en vivo y PLAN diferido como destinos de la escalación",
722
+ authority: "cli",
723
+ ownership: "cli-owned",
724
+ document: QUICK_LOOP,
725
+ attribution: QUICK_ATTRIBUTION,
726
+ },
727
+ {
728
+ id: "quick.convergence-gate",
729
+ scope: "quick",
730
+ title: "evaluar los criterios proporcionales de la tarea",
731
+ authority: "cli",
732
+ ownership: "cli-owned",
733
+ document: QUICK_LOOP,
734
+ attribution: QUICK_ATTRIBUTION,
735
+ effects: ["execute"],
736
+ // The criteria are authored per task, so no fixed runner can be named without
737
+ // inventing a rule this CLI does not have. What it CAN name is the artifact
738
+ // that holds them — and it demands back the real output of running them.
739
+ // `execute` is not self-authorizable, so the run stops to be authorized
740
+ // BEFORE this invocation is ever emitted.
741
+ action: {
742
+ invocation: {
743
+ program: "aw",
744
+ args: ["session-artifacts", "--code", "{code}", "--dump", "objetivo"],
745
+ target: SESSION_TARGET,
746
+ input: null,
747
+ },
748
+ evidence: ["quick.criterios-verdes"],
749
+ idempotent: true,
750
+ recovery: "arreglá lo que el criterio reprobó y volvé a correr sus validaciones: la transición sigue pendiente hasta que su salida real vuelva en verde",
751
+ },
752
+ },
753
+ {
754
+ id: "quick.review-precedence",
755
+ scope: "quick",
756
+ title: "exigir el gate de revisión antes de proponer el commit",
757
+ authority: "cli",
758
+ ownership: "cli-owned",
759
+ document: CODE_POLICIES_MD,
760
+ attribution: PLAN_ATTRIBUTION,
761
+ },
762
+ {
763
+ id: "quick.review-findings",
764
+ scope: "quick",
765
+ title: "releer el diff y juzgar sus hallazgos con las convenciones instaladas",
766
+ authority: "agent",
767
+ ownership: "cli-owned",
768
+ document: CODE_POLICIES_MD,
769
+ attribution: PLAN_ATTRIBUTION,
770
+ },
771
+ {
772
+ id: "quick.commit-authorization",
773
+ scope: "quick",
774
+ title: "aprobar el commit propuesto de la tarea",
775
+ authority: "human",
776
+ ownership: "cli-owned",
777
+ document: CODE_POLICIES_MD,
778
+ attribution: PLAN_ATTRIBUTION,
779
+ alternatives: [
780
+ {
781
+ label: "Aprobar el commit",
782
+ consequence: "se crea un solo commit al cierre de la tarea; sin push, sin --amend y sin --no-verify",
783
+ recommended: true,
784
+ },
785
+ {
786
+ label: "Dejar la tarea sin commitear",
787
+ consequence: "los cambios quedan en el árbol de trabajo y la tarea se registra en BACKLOG",
788
+ recommended: false,
789
+ },
790
+ ],
791
+ },
792
+ // ── SPEC ──────────────────────────────────────────────────────────────────
793
+ {
794
+ id: "spec-refine.session",
795
+ scope: "spec-refine",
796
+ title: "abrir o reanudar la sesión de refinamiento de la spec",
797
+ authority: "cli",
798
+ ownership: "cli-owned",
799
+ document: SPEC_LOOP,
800
+ attribution: SPEC_ATTRIBUTION,
801
+ effects: ["local_additive"],
802
+ // Same shape as QUICK's: the engine cannot author the descriptor, so what it
803
+ // names is the read that proves the session it runs inside exists as an
804
+ // artifact — with its objective and its seeded criteria.
805
+ action: {
806
+ invocation: {
807
+ program: "aw",
808
+ args: ["session-artifacts", "--code", "{code}"],
809
+ target: SESSION_TARGET,
810
+ input: null,
811
+ },
812
+ evidence: ["spec.session-present"],
813
+ idempotent: true,
814
+ recovery: "creá la sesión con 'aw session-create --type refine --name <slug>-spec-refine --objetivo \"<objetivo>\"' y volvé a devolver la lectura",
815
+ },
816
+ },
817
+ {
818
+ id: "spec-refine.baseline-scope",
819
+ scope: "spec-refine",
820
+ title: "decidir cuánto comportamiento actual hace falta establecer",
821
+ authority: "agent",
822
+ ownership: "cli-owned",
823
+ document: SPEC_LOOP,
824
+ attribution: SPEC_ATTRIBUTION,
825
+ },
826
+ {
827
+ id: "spec-refine.change-shape-gate",
828
+ scope: "spec-refine",
829
+ title: "resolver la forma del cambio: una sola spec, dividir o reemplazar",
830
+ authority: "cli",
831
+ ownership: "cli-owned",
832
+ document: CHANGE_SHAPE,
833
+ attribution: SPEC_ATTRIBUTION,
834
+ },
835
+ // The split branch of the shape gate, and its document is the shape module, not
836
+ // `SPLIT-GATE.md`. That was the mis-scoping this tranche had to resolve before
837
+ // it could migrate anything: the three rows carried `spec-new`'s document —
838
+ // which `spec-refine` never loads — so a real run stopped here handing the step
839
+ // back to a file its own read-set had not given it. The steps belong to
840
+ // `SPEC-CHANGE-SHAPE.md`, which already states this branch; the CRITERION stays
841
+ // stated once in `SPLIT-GATE.md`, and the shape module points at it.
842
+ {
843
+ id: "spec-refine.split-signal",
844
+ scope: "spec-refine",
845
+ title: "reconocer cada señal de división en la spec investigada",
846
+ authority: "agent",
847
+ ownership: "cli-owned",
848
+ document: CHANGE_SHAPE,
849
+ attribution: SPEC_ATTRIBUTION,
850
+ signals: [
851
+ "spec.independent-outcomes",
852
+ "spec.enumerated-features",
853
+ "spec.distinct-moments",
854
+ "spec.independent-value",
855
+ ],
856
+ },
857
+ {
858
+ id: "spec-refine.split-gate",
859
+ scope: "spec-refine",
860
+ title: "aplicar el umbral de dos señales que dispara la rama de división",
861
+ authority: "cli",
862
+ ownership: "cli-owned",
863
+ document: CHANGE_SHAPE,
864
+ attribution: SPEC_ATTRIBUTION,
865
+ },
866
+ {
867
+ id: "spec-refine.split-choice",
868
+ scope: "spec-refine",
869
+ title: "elegir entre dividir en varias specs o conservar una sola",
870
+ authority: "human",
871
+ ownership: "cli-owned",
872
+ document: CHANGE_SHAPE,
873
+ attribution: SPEC_ATTRIBUTION,
874
+ // "Borderline, or evidence too thin to tell → one spec, NO QUESTION." Not the
875
+ // PLAN factory even though the number matches: that one counts PLAN's five
876
+ // signals over PLAN's document, and collapsing them would tie two gates that
877
+ // are free to move apart to a single literal.
878
+ condition: {
879
+ threshold: { observed: "spec-refine.split-signal", min: 2 },
880
+ otherwise: "el umbral de dos señales no disparó: la spec conserva su forma y no se pregunta nada",
881
+ },
882
+ alternatives: [
883
+ {
884
+ label: "Dividir en varias specs",
885
+ consequence: "el original queda reducido a su resultado restante y cada resultado extraído nace como spec hermana",
886
+ recommended: true,
887
+ },
888
+ {
889
+ label: "Una sola spec",
890
+ consequence: "el gap queda agotado para esta corrida y el refinamiento sigue sobre esta spec",
891
+ recommended: false,
892
+ },
893
+ ],
894
+ },
895
+ {
896
+ id: "spec-refine.gap-recognition",
897
+ scope: "spec-refine",
898
+ title: "reconocer qué clase de gap tiene la spec delante",
899
+ authority: "agent",
900
+ ownership: "cli-owned",
901
+ document: SPEC_LOOP,
902
+ attribution: SPEC_ATTRIBUTION,
903
+ // Two of the taxonomy's kinds decide whether a later step happens at all, so
904
+ // they travel as declared signals instead of as prose the next reader has to
905
+ // re-derive. Recognizing them is judgment — the taxonomy that explains HOW to
906
+ // recognize them stays in the document.
907
+ signals: ["spec.functional-ambiguity", "spec.solution-space-unexplored"],
908
+ },
909
+ {
910
+ id: "spec-refine.gap-destination",
911
+ scope: "spec-refine",
912
+ title: "clasificar el gap por destino: bloquea SPEC, es de PLAN o se difiere",
913
+ authority: "cli",
914
+ ownership: "cli-owned",
915
+ document: SPEC_LOOP,
916
+ attribution: SPEC_ATTRIBUTION,
917
+ },
918
+ {
919
+ id: "spec-refine.ideation-trigger",
920
+ scope: "spec-refine",
921
+ title: "aplicar el disparador condicional del gate de ideación",
922
+ authority: "cli",
923
+ ownership: "cli-owned",
924
+ document: IDEATION_GATE,
925
+ attribution: SPEC_ATTRIBUTION,
926
+ },
927
+ {
928
+ id: "spec-refine.ideation-consent",
929
+ scope: "spec-refine",
930
+ title: "consentir la ronda de ideación o seguir sin ella",
931
+ authority: "human",
932
+ ownership: "cli-owned",
933
+ document: IDEATION_GATE,
934
+ attribution: SPEC_ATTRIBUTION,
935
+ // "Unexplored solution space is not a universal gap": the gate stays shut
936
+ // unless the signal is declared. Offering it always would burn context on a
937
+ // spec whose direction nobody doubts — the document's own words.
938
+ condition: {
939
+ threshold: {
940
+ observed: "spec-refine.gap-recognition",
941
+ of: ["spec.solution-space-unexplored"],
942
+ min: 1,
943
+ },
944
+ otherwise: "ningún disparador de ideación fue declarado: la spec no abre la ronda divergente",
945
+ },
946
+ alternatives: [
947
+ {
948
+ label: "Explorar ideas",
949
+ consequence: "se corre una ronda de ideación y sus veredictos vuelven como un lote propio de la conversación",
950
+ recommended: true,
951
+ },
952
+ {
953
+ label: "Seguir sin ideación",
954
+ consequence: "el gap queda agotado para esta corrida y el refinamiento sigue sin la ronda",
955
+ recommended: false,
956
+ },
957
+ ],
958
+ },
959
+ {
960
+ id: "spec-refine.content-authoring",
961
+ scope: "spec-refine",
962
+ title: "redactar requisito, contexto, criterios y escenarios de la spec",
963
+ authority: "agent",
964
+ ownership: "cli-owned",
965
+ document: SPEC_LOOP,
966
+ attribution: SPEC_ATTRIBUTION,
967
+ },
968
+ {
969
+ id: "spec-refine.functional-ambiguity",
970
+ scope: "spec-refine",
971
+ title: "cerrar una ambigüedad funcional que puede cambiar lo que se construye",
972
+ authority: "human",
973
+ ownership: "cli-owned",
974
+ document: SPEC_LOOP,
975
+ attribution: SPEC_ATTRIBUTION,
976
+ // Only when one was declared. A spec with no ambiguity that stopped to ask
977
+ // about one would train the reader to answer questions with no subject.
978
+ condition: {
979
+ threshold: {
980
+ observed: "spec-refine.gap-recognition",
981
+ of: ["spec.functional-ambiguity"],
982
+ min: 1,
983
+ },
984
+ otherwise: "no se declaró ninguna ambigüedad funcional bloqueante: no hay nada que la persona tenga que cerrar",
985
+ },
986
+ },
987
+ {
988
+ id: "spec-refine.design-reuse",
989
+ scope: "spec-refine",
990
+ title: "juzgar si un baseline de diseño compatible sirve o hace falta una revisión nueva",
991
+ authority: "agent",
992
+ ownership: "cli-owned",
993
+ document: "modules/DESIGN-REFERENCES.md",
994
+ // The judgment is the agent's and the criterion stays in the module. What the
995
+ // CLI owns is the step: `aw designs` is what puts the existing baselines in
996
+ // front of whoever judges, so the question is asked over a real inventory
997
+ // instead of a recollection.
998
+ attribution: "`aw designs` lists what the workspace already has",
999
+ },
1000
+ {
1001
+ id: "spec-refine.design-publication",
1002
+ scope: "spec-refine",
1003
+ title: "validar y publicar la revisión del package de diseño",
1004
+ authority: "cli",
1005
+ ownership: "cli-owned",
1006
+ document: "modules/DESIGN-REFERENCES.md",
1007
+ attribution: "capability over the **UI Design Package v1**",
1008
+ effects: ["local_additive"],
1009
+ },
1010
+ {
1011
+ id: "spec-refine.ready-gate",
1012
+ scope: "spec-refine",
1013
+ title: "evaluar el gate ready-for-plan sobre los criterios declarados",
1014
+ authority: "cli",
1015
+ ownership: "cli-owned",
1016
+ document: SPEC_LOOP,
1017
+ attribution: SPEC_ATTRIBUTION,
1018
+ // The checklist is the run's own `Success criteria`, seeded before refining.
1019
+ // The engine names the read that holds them and demands the real state of
1020
+ // each one back: "the gate passed" is not a result.
1021
+ action: {
1022
+ invocation: {
1023
+ program: "aw",
1024
+ args: ["session-artifacts", "--code", "{code}", "--dump", "objetivo"],
1025
+ target: SESSION_TARGET,
1026
+ input: null,
1027
+ },
1028
+ evidence: ["spec.ready-for-plan-checklist"],
1029
+ idempotent: true,
1030
+ recovery: "lo que el checklist reprobó vuelve al loop como gap: resolvelo y volvé a evaluar el gate con su estado real",
1031
+ },
1032
+ },
1033
+ {
1034
+ id: "spec-refine.save-confirmation",
1035
+ scope: "spec-refine",
1036
+ title: "confirmar la sobreescritura de la spec",
1037
+ authority: "human",
1038
+ ownership: "cli-owned",
1039
+ document: SPEC_LOOP,
1040
+ attribution: SPEC_ATTRIBUTION,
1041
+ // BEFORE the stamp, and the real walk is what proved it: the migrated journey
1042
+ // was promoting the status and asking for the overwrite afterwards, so the
1043
+ // person would have been confirming a write that already happened. The
1044
+ // doctrine's own line is `edit_in_place_with_confirm(spec) + stamp`.
1045
+ alternatives: [
1046
+ {
1047
+ label: "Guardar especificación refinada",
1048
+ consequence: "la spec se sobrescribe en su lugar y queda sellada como ready-for-plan",
1049
+ recommended: true,
1050
+ },
1051
+ {
1052
+ label: "Preguntar algo más",
1053
+ consequence: "el refinamiento sigue abierto y la spec queda como está",
1054
+ recommended: false,
1055
+ },
1056
+ ],
1057
+ },
1058
+ {
1059
+ id: "spec-refine.status-promotion",
1060
+ scope: "spec-refine",
1061
+ title: "promover el status de la spec a ready-for-plan",
1062
+ authority: "cli",
1063
+ ownership: "cli-owned",
1064
+ document: SPEC_LOOP,
1065
+ attribution: SPEC_ATTRIBUTION,
1066
+ effects: ["mutate_overwrite"],
1067
+ // The stamp is a write on a document the engine does not edit, so what it
1068
+ // demands back is the board's own reading of that document's status. The
1069
+ // effect is not self-authorizable: the run stops to be authorized before this
1070
+ // invocation is ever named.
1071
+ action: {
1072
+ invocation: { program: "aw", args: ["status", "--json"], target: ".", input: null },
1073
+ evidence: ["spec.status-ready-for-plan"],
1074
+ idempotent: true,
1075
+ recovery: "sellá 'status: ready-for-plan' en la spec y volvé a devolver la lectura del tablero; si el sello no está, la transición sigue pendiente",
1076
+ },
1077
+ },
1078
+ // ── PLAN — new ────────────────────────────────────────────────────────────
1079
+ //
1080
+ // The third tranche, and the one with the most effect surface: it writes plan
1081
+ // documents, runs checks and reaches Git. Row ORDER is left exactly as the
1082
+ // previous tranches found it wherever the doctrine does not force a change —
1083
+ // reordering without evidence would be a claim about the journey nobody made.
1084
+ {
1085
+ id: "plan-new.spec-readiness",
1086
+ scope: "plan-new",
1087
+ title: "leer el status de la spec y sugerir refinar sin bloquear",
1088
+ authority: "cli",
1089
+ ownership: "cli-owned",
1090
+ document: PLAN_NEW_LOOP,
1091
+ attribution: PLAN_ATTRIBUTION,
1092
+ // "Read from the spec's frontmatter `status`, never from the filename" — so
1093
+ // what the run credits is the board's reading of that document, not a claim
1094
+ // that somebody looked. Suggesting is the outcome; it never blocks.
1095
+ action: {
1096
+ invocation: { program: "aw", args: ["status", "--json"], target: ".", input: null },
1097
+ evidence: ["plan.spec-status-leido"],
1098
+ idempotent: true,
1099
+ recovery: "volvé a correr 'aw status --json' y devolvé su salida real; sin el status de la spec no hay nada que sugerir ni que dar por listo",
1100
+ },
1101
+ },
1102
+ {
1103
+ id: "plan-new.session",
1104
+ scope: "plan-new",
1105
+ title: "abrir o reanudar la sesión de generación del plan",
1106
+ authority: "cli",
1107
+ ownership: "cli-owned",
1108
+ document: PLAN_NEW_LOOP,
1109
+ attribution: PLAN_ATTRIBUTION,
1110
+ effects: ["local_additive"],
1111
+ action: {
1112
+ invocation: {
1113
+ program: "aw",
1114
+ args: ["session-artifacts", "--code", "{code}"],
1115
+ target: SESSION_TARGET,
1116
+ input: null,
1117
+ },
1118
+ evidence: ["plan.session-present"],
1119
+ idempotent: true,
1120
+ recovery: "creá la sesión con 'aw session-create --type refine --name <slug>-plan-new --objetivo \"<objetivo>\"' y volvé a devolver la lectura",
1121
+ },
1122
+ },
1123
+ {
1124
+ id: "plan-new.slug-derivation",
1125
+ scope: "plan-new",
1126
+ title: "derivar el slug del plan desde el requisito de la spec",
1127
+ authority: "agent",
1128
+ ownership: "cli-owned",
1129
+ document: PLAN_NEW_LOOP,
1130
+ attribution: PLAN_ATTRIBUTION,
1131
+ },
1132
+ {
1133
+ id: "plan-new.numbering",
1134
+ scope: "plan-new",
1135
+ title: "asignar el correlativo del documento del plan",
1136
+ authority: "cli",
1137
+ ownership: "cli-owned",
1138
+ document: PLAN_NEW_LOOP,
1139
+ attribution: "`aw next-number docs/plans`",
1140
+ },
1141
+ {
1142
+ id: "plan-new.phase-shaping",
1143
+ scope: "plan-new",
1144
+ title: "agrupar el trabajo en estados verificables del sistema",
1145
+ authority: "agent",
1146
+ ownership: "cli-owned",
1147
+ document: PLAN_NEW_LOOP,
1148
+ attribution: PLAN_ATTRIBUTION,
1149
+ },
1150
+ {
1151
+ id: "plan-new.batch-eligibility-signal",
1152
+ scope: "plan-new",
1153
+ title: "reconocer qué hecho observable rompe la elegibilidad de un rango continuo",
1154
+ authority: "agent",
1155
+ ownership: "cli-owned",
1156
+ document: BATCHES_MD,
1157
+ attribution: PLAN_ATTRIBUTION,
1158
+ // The module's five eligibility conditions, stated as what BREAKS them. The
1159
+ // document phrased them as "the AI infers from observable facts, not a
1160
+ // preference question" — and that is exactly the frontier: seeing the fact is
1161
+ // judgment, turning it into `isolated` is the rule below.
1162
+ signals: [...BATCH_ELIGIBILITY_SIGNALS],
1163
+ },
1164
+ {
1165
+ id: "plan-new.batch-inference",
1166
+ scope: "plan-new",
1167
+ title: "inferir la partición máxima de execution batches",
1168
+ authority: "cli",
1169
+ ownership: "cli-owned",
1170
+ document: BATCHES_MD,
1171
+ attribution: PLAN_ATTRIBUTION,
1172
+ },
1173
+ {
1174
+ id: "plan-new.batch-isolation",
1175
+ scope: "plan-new",
1176
+ title: "aislar el rango cuyo hecho observable rompe su elegibilidad",
1177
+ authority: "cli",
1178
+ ownership: "cli-owned",
1179
+ document: BATCHES_MD,
1180
+ attribution: PLAN_ATTRIBUTION,
1181
+ condition: {
1182
+ threshold: ineligibleRange("plan-new.batch-eligibility-signal"),
1183
+ otherwise: "ningún hecho observable rompe la elegibilidad: el rango máximo entra entero como un batch continuo",
1184
+ },
1185
+ },
1186
+ {
1187
+ id: "plan-new.split-signal",
1188
+ scope: "plan-new",
1189
+ title: "reconocer cada señal de división en tramos del plan",
1190
+ authority: "agent",
1191
+ ownership: "cli-owned",
1192
+ document: PLAN_SPLIT_GATE,
1193
+ attribution: PLAN_ATTRIBUTION,
1194
+ signals: [...PLAN_SPLIT_SIGNALS],
1195
+ },
1196
+ {
1197
+ id: "plan-new.split-gate",
1198
+ scope: "plan-new",
1199
+ title: "aplicar el umbral de dos señales del gate multi-plan",
1200
+ authority: "cli",
1201
+ ownership: "cli-owned",
1202
+ document: PLAN_SPLIT_GATE,
1203
+ attribution: PLAN_ATTRIBUTION,
1204
+ },
1205
+ {
1206
+ id: "plan-new.split-choice",
1207
+ scope: "plan-new",
1208
+ title: "elegir entre dividir en varios planes o conservar uno solo",
1209
+ authority: "human",
1210
+ ownership: "cli-owned",
1211
+ document: PLAN_SPLIT_GATE,
1212
+ attribution: PLAN_ATTRIBUTION,
1213
+ // "It fires ONLY on clear signals… borderline → one plan, no question." A
1214
+ // directed journey that asked anyway would ask what the doctrine it replaces
1215
+ // explicitly refuses to ask.
1216
+ condition: {
1217
+ threshold: splitThreshold("plan-new.split-signal"),
1218
+ otherwise: "el umbral de dos señales no disparó: el trabajo queda en un solo plan y no se pregunta nada",
1219
+ },
1220
+ alternatives: [
1221
+ {
1222
+ label: "Dividir en varios planes",
1223
+ consequence: "cada tramo se elabora completo como plan hermano, con su origen y el orden entre ellos",
1224
+ recommended: true,
1225
+ },
1226
+ {
1227
+ label: "Un solo plan",
1228
+ consequence: "el gap queda agotado para esta corrida y el trabajo sigue como un plan único",
1229
+ recommended: false,
1230
+ },
1231
+ ],
1232
+ },
1233
+ {
1234
+ id: "plan-new.coherence-gate",
1235
+ scope: "plan-new",
1236
+ title: "evaluar el gate de coherencia del plan generado",
1237
+ authority: "cli",
1238
+ ownership: "cli-owned",
1239
+ document: PLAN_NEW_LOOP,
1240
+ attribution: PLAN_ATTRIBUTION,
1241
+ // Same shape as SPEC's ready gate: the checklist is the run's own `Success
1242
+ // criteria`, seeded before planning, and what comes back is the real state of
1243
+ // each line. "The gate passed" is not a result.
1244
+ action: {
1245
+ invocation: {
1246
+ program: "aw",
1247
+ args: ["session-artifacts", "--code", "{code}", "--dump", "objetivo"],
1248
+ target: SESSION_TARGET,
1249
+ input: null,
1250
+ },
1251
+ evidence: ["plan.coherence-checklist"],
1252
+ idempotent: true,
1253
+ recovery: "lo que el checklist reprobó vuelve al loop como gap: resolvelo y volvé a evaluar el gate con su estado real",
1254
+ },
1255
+ },
1256
+ {
1257
+ id: "plan-new.save-confirmation",
1258
+ scope: "plan-new",
1259
+ title: "confirmar la escritura del plan o de sus hermanos",
1260
+ authority: "human",
1261
+ ownership: "cli-owned",
1262
+ document: PLAN_NEW_LOOP,
1263
+ attribution: PLAN_ATTRIBUTION,
1264
+ alternatives: [
1265
+ {
1266
+ label: "Guardar plan",
1267
+ consequence: "el plan se escribe en docs/plans, y si el split fue aceptado se escriben también sus hermanos",
1268
+ recommended: true,
1269
+ },
1270
+ {
1271
+ label: "Preguntar algo más",
1272
+ consequence: "la generación sigue abierta y no se escribe ningún documento",
1273
+ recommended: false,
1274
+ },
1275
+ ],
1276
+ },
1277
+ {
1278
+ id: "plan-new.adoption",
1279
+ scope: "plan-new",
1280
+ title: "adoptar en una sola pasada un plan construido fuera del loop",
1281
+ authority: "cli",
1282
+ ownership: "cli-owned",
1283
+ document: PLAN_INPUT,
1284
+ attribution: PLAN_ATTRIBUTION,
1285
+ },
1286
+ // ── PLAN — refine ─────────────────────────────────────────────────────────
1287
+ {
1288
+ id: "plan-refine.session",
1289
+ scope: "plan-refine",
1290
+ title: "abrir, reanudar o reabrir la sesión de refinamiento del plan",
1291
+ authority: "cli",
1292
+ ownership: "cli-owned",
1293
+ document: PLAN_REFINE_LOOP,
1294
+ attribution: PLAN_ATTRIBUTION,
1295
+ effects: ["local_additive"],
1296
+ action: {
1297
+ invocation: {
1298
+ program: "aw",
1299
+ args: ["session-artifacts", "--code", "{code}"],
1300
+ target: SESSION_TARGET,
1301
+ input: null,
1302
+ },
1303
+ evidence: ["plan.session-present"],
1304
+ idempotent: true,
1305
+ recovery: "creá o reabrí la sesión con 'aw session-create' o 'aw session-resume --code <NNN> --reopen' y volvé a devolver la lectura",
1306
+ },
1307
+ },
1308
+ {
1309
+ id: "plan-refine.journey-map",
1310
+ scope: "plan-refine",
1311
+ title: "mapear contrato observable, recorrido técnico, estrategia incremental y evidencia",
1312
+ authority: "agent",
1313
+ ownership: "cli-owned",
1314
+ document: PLAN_REFINE_LOOP,
1315
+ attribution: PLAN_ATTRIBUTION,
1316
+ },
1317
+ {
1318
+ id: "plan-refine.preserve-validated",
1319
+ scope: "plan-refine",
1320
+ title: "conservar las fases validadas y rediseñar solo el trabajo pendiente",
1321
+ authority: "cli",
1322
+ ownership: "cli-owned",
1323
+ document: PLAN_REFINE_LOOP,
1324
+ attribution: PLAN_ATTRIBUTION,
1325
+ },
1326
+ {
1327
+ id: "plan-refine.batch-eligibility-signal",
1328
+ scope: "plan-refine",
1329
+ title: "reconocer qué hecho observable rompe la elegibilidad de un rango continuo",
1330
+ authority: "agent",
1331
+ ownership: "cli-owned",
1332
+ document: BATCHES_MD,
1333
+ attribution: PLAN_ATTRIBUTION,
1334
+ signals: [...BATCH_ELIGIBILITY_SIGNALS],
1335
+ },
1336
+ {
1337
+ id: "plan-refine.batch-reinference",
1338
+ scope: "plan-refine",
1339
+ title: "re-inferir y escribir la partición completa de batches",
1340
+ authority: "cli",
1341
+ ownership: "cli-owned",
1342
+ document: BATCHES_MD,
1343
+ attribution: PLAN_ATTRIBUTION,
1344
+ },
1345
+ {
1346
+ id: "plan-refine.batch-isolation",
1347
+ scope: "plan-refine",
1348
+ title: "aislar el rango cuyo hecho observable rompe su elegibilidad",
1349
+ authority: "cli",
1350
+ ownership: "cli-owned",
1351
+ document: BATCHES_MD,
1352
+ attribution: PLAN_ATTRIBUTION,
1353
+ condition: {
1354
+ threshold: ineligibleRange("plan-refine.batch-eligibility-signal"),
1355
+ otherwise: "ningún hecho observable rompe la elegibilidad: el rango máximo entra entero como un batch continuo",
1356
+ },
1357
+ },
1358
+ {
1359
+ id: "plan-refine.split-signal",
1360
+ scope: "plan-refine",
1361
+ title: "reconocer cada señal de división sobre el plan que ya existe",
1362
+ authority: "agent",
1363
+ ownership: "cli-owned",
1364
+ document: PLAN_SPLIT_GATE,
1365
+ attribution: PLAN_ATTRIBUTION,
1366
+ // The gate is defined ONCE in that module and this loop only adds the
1367
+ // in-place semantics — so the signals are read from the same vocabulary. The
1368
+ // row exists per journey because a threshold may only count signals declared
1369
+ // inside the journey it belongs to.
1370
+ signals: [...PLAN_SPLIT_SIGNALS],
1371
+ },
1372
+ {
1373
+ id: "plan-refine.executability-gate",
1374
+ scope: "plan-refine",
1375
+ title: "evaluar el gate de ejecutabilidad del plan",
1376
+ authority: "cli",
1377
+ ownership: "cli-owned",
1378
+ document: PLAN_REFINE_LOOP,
1379
+ attribution: PLAN_ATTRIBUTION,
1380
+ action: {
1381
+ invocation: {
1382
+ program: "aw",
1383
+ args: ["session-artifacts", "--code", "{code}", "--dump", "objetivo"],
1384
+ target: SESSION_TARGET,
1385
+ input: null,
1386
+ },
1387
+ evidence: ["plan.executability-checklist"],
1388
+ idempotent: true,
1389
+ recovery: "lo que el checklist reprobó vuelve al loop como gap: resolvelo y volvé a evaluar el gate con su estado real",
1390
+ },
1391
+ },
1392
+ {
1393
+ id: "plan-refine.save-confirmation",
1394
+ scope: "plan-refine",
1395
+ title: "confirmar la sobreescritura del plan refinado",
1396
+ authority: "human",
1397
+ ownership: "cli-owned",
1398
+ document: PLAN_REFINE_LOOP,
1399
+ attribution: PLAN_ATTRIBUTION,
1400
+ alternatives: [
1401
+ {
1402
+ label: "Guardar plan refinado",
1403
+ consequence: "el plan se edita en su lugar con su traza de refinamiento, y si el split fue aceptado se escriben los hermanos extraídos",
1404
+ recommended: true,
1405
+ },
1406
+ {
1407
+ label: "Preguntar algo más",
1408
+ consequence: "el refinamiento sigue abierto y el plan queda como está",
1409
+ recommended: false,
1410
+ },
1411
+ ],
1412
+ },
1413
+ {
1414
+ id: "plan-refine.split-in-place",
1415
+ scope: "plan-refine",
1416
+ title: "reducir el plan original y extraer los hermanos sin mover trabajo completado",
1417
+ authority: "cli",
1418
+ ownership: "cli-owned",
1419
+ document: PLAN_REFINE_SPLIT,
1420
+ attribution: PLAN_ATTRIBUTION,
1421
+ effects: ["local_additive", "mutate_overwrite"],
1422
+ // AFTER the confirmation, and the doctrine's own sequence is why: the save
1423
+ // branch reads `Guardar planes → edit original reduced (confirmation) + write
1424
+ // extracted siblings`. The registry had both writes ahead of the gate and of
1425
+ // the confirmation — the same defect the SPEC tranche found in its stamp, so
1426
+ // the order moved to where the document puts it.
1427
+ action: {
1428
+ invocation: { program: "aw", args: ["status", "--json"], target: ".", input: null },
1429
+ evidence: ["plan.hermanos-extraidos"],
1430
+ idempotent: true,
1431
+ recovery: "reducí el original en su lugar, escribí los hermanos extraídos y volvé a devolver la lectura; una tarea ya marcada nunca se muda a un hermano",
1432
+ },
1433
+ condition: {
1434
+ threshold: splitThreshold("plan-refine.split-signal"),
1435
+ otherwise: "el umbral de dos señales no disparó: el plan conserva su número y su alcance, y no se extrae ningún hermano",
1436
+ },
1437
+ },
1438
+ {
1439
+ id: "plan-refine.normalize-on-write",
1440
+ scope: "plan-refine",
1441
+ title: "normalizar la forma sin escribir bloques condicionales vacíos ni tocar estados",
1442
+ authority: "cli",
1443
+ ownership: "cli-owned",
1444
+ document: PLAN_REFINE_LOOP,
1445
+ attribution: PLAN_ATTRIBUTION,
1446
+ effects: ["mutate_overwrite"],
1447
+ // A write on a document the engine does not edit, so it is delegated like
1448
+ // every other one. It had neither action nor evidence, which meant the run
1449
+ // recorded "normalized" for something nothing performed — the phantom
1450
+ // confirmation this whole contract exists to refuse.
1451
+ action: {
1452
+ invocation: { program: "aw", args: ["status", "--json"], target: ".", input: null },
1453
+ evidence: ["plan.forma-normalizada"],
1454
+ idempotent: true,
1455
+ recovery: "normalizá la forma sin tocar estados ni casillas y volvé a devolver la lectura; normalizar de nuevo lo ya normalizado no rompe nada",
1456
+ },
1457
+ },
1458
+ // ── PLAN — exec ───────────────────────────────────────────────────────────
1459
+ {
1460
+ id: "plan-exec.session",
1461
+ scope: "plan-exec",
1462
+ title: "abrir o reanudar la sesión única de la corrida de ejecución",
1463
+ authority: "cli",
1464
+ ownership: "cli-owned",
1465
+ document: PLAN_EXEC_LOOP,
1466
+ attribution: PLAN_ATTRIBUTION,
1467
+ effects: ["local_additive"],
1468
+ action: {
1469
+ invocation: {
1470
+ program: "aw",
1471
+ args: ["session-artifacts", "--code", "{code}"],
1472
+ target: SESSION_TARGET,
1473
+ input: null,
1474
+ },
1475
+ evidence: ["plan.session-present"],
1476
+ idempotent: true,
1477
+ recovery: "creá la sesión con 'aw session-create --type exec --name <slug>-plan-exec --objetivo \"<objetivo>\"' y volvé a devolver la lectura",
1478
+ },
1479
+ },
1480
+ {
1481
+ id: "plan-exec.entry-gate",
1482
+ scope: "plan-exec",
1483
+ title: "verificar en la entrada que el plan tiene forma ejecutable",
1484
+ authority: "cli",
1485
+ ownership: "cli-owned",
1486
+ document: PLAN_EXEC_LOOP,
1487
+ attribution: PLAN_ATTRIBUTION,
1488
+ // The board already parses what the gate checks — the phase blocks, their
1489
+ // state lines and the plan's own status — so what comes back is that reading
1490
+ // and not "I read the plan". A plan whose shape the board cannot resolve is
1491
+ // the gate's finding, not a detail to wave through.
1492
+ action: {
1493
+ invocation: { program: "aw", args: ["status", "--json"], target: ".", input: null },
1494
+ evidence: ["plan.forma-ejecutable"],
1495
+ idempotent: true,
1496
+ recovery: "volvé a correr 'aw status --json' y devolvé su salida real; si el plan no se puede leer, eso ES el hallazgo del gate",
1497
+ },
1498
+ },
1499
+ {
1500
+ id: "plan-exec.entry-gap-recognition",
1501
+ scope: "plan-exec",
1502
+ title: "reconocer qué clase de hueco dejó el gate de entrada",
1503
+ authority: "agent",
1504
+ ownership: "cli-owned",
1505
+ document: PLAN_EXEC_LOOP,
1506
+ attribution: PLAN_ATTRIBUTION,
1507
+ // Two signals for two different consequences, and neither is "no gap": a plan
1508
+ // that passes the gate declares nothing here, which is what makes both rules
1509
+ // below skip. Recognizing the class is judgment; what each class costs is the
1510
+ // rule the CLI applies.
1511
+ signals: ["plan.entry-gap-minor", "plan.entry-gap-structural"],
1512
+ },
1513
+ {
1514
+ id: "plan-exec.entry-gap-severity",
1515
+ scope: "plan-exec",
1516
+ title: "distinguir un hueco menor de uno estructural en el plan",
1517
+ authority: "cli",
1518
+ ownership: "cli-owned",
1519
+ document: PLAN_EXEC_LOOP,
1520
+ attribution: PLAN_ATTRIBUTION,
1521
+ condition: {
1522
+ threshold: { observed: "plan-exec.entry-gap-recognition", min: 1 },
1523
+ otherwise: "el gate de entrada no encontró ningún hueco: no hay severidad que clasificar",
1524
+ },
1525
+ },
1526
+ {
1527
+ id: "plan-exec.normalization-consent",
1528
+ scope: "plan-exec",
1529
+ title: "consentir la normalización del plan o derivar a plan-refine",
1530
+ authority: "human",
1531
+ ownership: "cli-owned",
1532
+ document: PLAN_EXEC_LOOP,
1533
+ attribution: PLAN_ATTRIBUTION,
1534
+ // ONLY the minor gap is offered. A structural one "does not improvise": it
1535
+ // leaves this loop, and putting `Normalizar y ejecutar` in front of somebody
1536
+ // holding a structural gap is how a plan gets patched instead of refined.
1537
+ condition: {
1538
+ threshold: {
1539
+ observed: "plan-exec.entry-gap-recognition",
1540
+ of: ["plan.entry-gap-minor"],
1541
+ min: 1,
1542
+ },
1543
+ otherwise: "no se declaró ningún hueco menor: no hay normalización que consentir",
1544
+ },
1545
+ alternatives: [
1546
+ {
1547
+ label: "Normalizar y ejecutar",
1548
+ consequence: "los bloques de fase se editan en su lugar sin agregar alcance ni mover ninguna frontera, y la ejecución sigue",
1549
+ recommended: true,
1550
+ },
1551
+ {
1552
+ label: "Ir a plan-refine",
1553
+ consequence: "la ejecución no arranca: el hallazgo queda en CHECKPOINT y el trabajo sigue en /w:plan-refine",
1554
+ recommended: false,
1555
+ },
1556
+ ],
1557
+ },
1558
+ {
1559
+ id: "plan-exec.batch-eligibility-signal",
1560
+ scope: "plan-exec",
1561
+ title: "reconocer qué hecho del checkout vivo rompe la elegibilidad de un rango",
1562
+ authority: "agent",
1563
+ ownership: "cli-owned",
1564
+ document: BATCHES_MD,
1565
+ attribution: PLAN_ATTRIBUTION,
1566
+ // Execution observes the same five facts as planning, but over live state —
1567
+ // dependencies, branches, working trees, blockers and risks — which is why it
1568
+ // may merge or split what the plan declared without asking.
1569
+ signals: [...BATCH_ELIGIBILITY_SIGNALS],
1570
+ },
1571
+ {
1572
+ id: "plan-exec.batch-inference",
1573
+ scope: "plan-exec",
1574
+ title: "re-inferir los batches efectivos sobre el estado vivo",
1575
+ authority: "cli",
1576
+ ownership: "cli-owned",
1577
+ document: BATCHES_MD,
1578
+ attribution: PLAN_ATTRIBUTION,
1579
+ },
1580
+ {
1581
+ id: "plan-exec.batch-isolation",
1582
+ scope: "plan-exec",
1583
+ title: "aislar el rango cuyo hecho del checkout vivo rompe su elegibilidad",
1584
+ authority: "cli",
1585
+ ownership: "cli-owned",
1586
+ document: BATCHES_MD,
1587
+ attribution: PLAN_ATTRIBUTION,
1588
+ condition: {
1589
+ threshold: ineligibleRange("plan-exec.batch-eligibility-signal"),
1590
+ otherwise: "ningún hecho del checkout vivo rompe la elegibilidad: el rango máximo entra entero como un batch continuo",
1591
+ },
1592
+ },
1593
+ {
1594
+ id: "plan-exec.design-precondition",
1595
+ scope: "plan-exec",
1596
+ title: "resolver el veredicto de diseño de una tarea que pinea una referencia",
1597
+ authority: "cli",
1598
+ ownership: "cli-owned",
1599
+ document: "modules/DESIGN-REFERENCES.md",
1600
+ attribution: "`aw designs --plan`",
1601
+ },
1602
+ {
1603
+ id: "plan-exec.branch-precondition",
1604
+ scope: "plan-exec",
1605
+ title: "verificar la rama de cada fuente afectada antes del batch",
1606
+ authority: "cli",
1607
+ ownership: "cli-owned",
1608
+ document: CODE_POLICIES_MD,
1609
+ attribution: PLAN_ATTRIBUTION,
1610
+ // "Before editing… verify EVERY current branch" — every, so the read has to
1611
+ // cover every declared source. `aw check-branch` cannot: without --source it
1612
+ // resolves no target and answers `match: true` unconditionally, which would
1613
+ // credit "branch verified" against a command that checked nothing. The real
1614
+ // walk is what surfaced that. `aw sources` enriches each declared source with
1615
+ // its current branch, the expected one and whether they match.
1616
+ action: {
1617
+ invocation: { program: "aw", args: ["sources", "--verbose"], target: ".", input: null },
1618
+ evidence: ["plan.rama-verificada"],
1619
+ idempotent: true,
1620
+ recovery: "resolvé la rama de la fuente que no coincide y volvé a leer las fuentes: nunca limpies ni cambies de rama sin confirmación",
1621
+ },
1622
+ },
1623
+ {
1624
+ id: "plan-exec.implementation",
1625
+ scope: "plan-exec",
1626
+ title: "implementar el trabajo mínimo de cada tarea de la fase",
1627
+ authority: "agent",
1628
+ ownership: "cli-owned",
1629
+ document: PLAN_EXEC_LOOP,
1630
+ attribution: PLAN_ATTRIBUTION,
1631
+ },
1632
+ {
1633
+ id: "plan-exec.deviation-recognition",
1634
+ scope: "plan-exec",
1635
+ title: "reconocer qué toca el cambio que apareció al implementar",
1636
+ authority: "agent",
1637
+ ownership: "cli-owned",
1638
+ document: PLAN_EXEC_LOOP,
1639
+ attribution: PLAN_ATTRIBUTION,
1640
+ // Only the two that LEAVE the loop are signals. A local decision declares
1641
+ // nothing, which is the doctrine's own default — "plan-exec continues" — and
1642
+ // it is what keeps the gate below from stopping a run that has nothing to
1643
+ // classify.
1644
+ signals: ["plan.deviation-structural", "plan.deviation-functional"],
1645
+ },
1646
+ {
1647
+ id: "plan-exec.deviation-gate",
1648
+ scope: "plan-exec",
1649
+ title: "clasificar la desviación en local, estructural o funcional y derivar",
1650
+ authority: "cli",
1651
+ ownership: "cli-owned",
1652
+ document: PLAN_EXEC_LOOP,
1653
+ attribution: PLAN_ATTRIBUTION,
1654
+ condition: {
1655
+ threshold: { observed: "plan-exec.deviation-recognition", min: 1 },
1656
+ otherwise: "no se declaró ninguna desviación estructural ni funcional: lo local se resuelve en la fase y la ejecución sigue",
1657
+ },
1658
+ },
1659
+ {
1660
+ id: "plan-exec.pending-effects",
1661
+ scope: "plan-exec",
1662
+ title: "reconocer qué queda por hacer al cerrar el batch",
1663
+ authority: "agent",
1664
+ ownership: "cli-owned",
1665
+ document: PLAN_EXEC_LOOP,
1666
+ attribution: PLAN_ATTRIBUTION,
1667
+ // The three rows below it write, run or commit — and in a legitimate batch any
1668
+ // of the three may have nothing to do: boxes already ticked by an earlier run,
1669
+ // phases still pending, a batch with no code. Without this row each of them
1670
+ // demanded its effect anyway, so the only truthful answer was to refuse, and a
1671
+ // refused boundary that keeps being re-emitted exhausts and stops the run.
1672
+ //
1673
+ // The signals are POSITIVE — "there IS something to do" — because the engine's
1674
+ // threshold is positive: a row applies when its signal was observed and is
1675
+ // passed over when it was not. The inverse cannot be expressed, and inverting
1676
+ // one by mistake would skip a step that DID apply, crediting work nobody did.
1677
+ signals: ["plan.tasks-to-mark", "plan.plan-closable", "plan.commit-pending"],
1678
+ },
1679
+ {
1680
+ id: "plan-exec.task-marking",
1681
+ scope: "plan-exec",
1682
+ title: "marcar la tarea cuando su trabajo local termina",
1683
+ authority: "cli",
1684
+ ownership: "cli-owned",
1685
+ document: PLAN_EXEC_LOOP,
1686
+ attribution: PLAN_ATTRIBUTION,
1687
+ effects: ["mutate_overwrite"],
1688
+ condition: {
1689
+ threshold: {
1690
+ observed: "plan-exec.pending-effects",
1691
+ of: ["plan.tasks-to-mark"],
1692
+ min: 1,
1693
+ },
1694
+ otherwise: "ninguna casilla terminó su trabajo local en este batch: no hay nada que marcar en el plan",
1695
+ },
1696
+ // The plan-doc is the per-task source of truth, and the engine does not edit
1697
+ // it — so the write is delegated and what comes back is the board's count of
1698
+ // ticked boxes. "I marked it" is the one thing this contract will not take.
1699
+ action: {
1700
+ invocation: { program: "aw", args: ["status", "--json"], target: ".", input: null },
1701
+ evidence: ["plan.casillas-marcadas"],
1702
+ idempotent: true,
1703
+ recovery: "marcá la casilla de la tarea cuyo trabajo local terminó y volvé a devolver la lectura del tablero; marcar de nuevo lo ya marcado no rompe nada",
1704
+ },
1705
+ },
1706
+ {
1707
+ id: "plan-exec.phase-state-transition",
1708
+ scope: "plan-exec",
1709
+ title: "aplicar la transición de estado de fase con sus precondiciones",
1710
+ authority: "cli",
1711
+ ownership: "cli-owned",
1712
+ document: PLAN_EXEC_LOOP,
1713
+ attribution: PLAN_ATTRIBUTION,
1714
+ effects: ["mutate_overwrite"],
1715
+ // The precondition is what this row exists for: `validada` requires the proof
1716
+ // to have RUN and passed, never the checkboxes. The state line is a write on a
1717
+ // document the engine does not own, so the board's reading of that line is the
1718
+ // evidence — and the board is the same thing that calls a plan `inconsistent`
1719
+ // when a state and its boxes disagree.
1720
+ action: {
1721
+ invocation: { program: "aw", args: ["status", "--json"], target: ".", input: null },
1722
+ evidence: ["plan.estado-de-fase-aplicado"],
1723
+ idempotent: true,
1724
+ recovery: "escribí el '> Estado:' que la fase realmente tiene —con su '> Bloqueo:' si quedó bloqueada— y volvé a devolver la lectura; una fase sin su prueba corrida no pasa a validada",
1725
+ },
1726
+ },
1727
+ {
1728
+ id: "plan-exec.validation-execution",
1729
+ scope: "plan-exec",
1730
+ title: "correr las pruebas de fase y las validaciones aplicables al cierre del batch",
1731
+ authority: "cli",
1732
+ ownership: "cli-owned",
1733
+ document: PLAN_EXEC_LOOP,
1734
+ attribution: PLAN_ATTRIBUTION,
1735
+ effects: ["execute"],
1736
+ // The proofs are authored per phase, so no fixed runner can be named without
1737
+ // inventing a rule this CLI does not have. What it CAN name is the artifact
1738
+ // holding the run's criteria, and it demands the real output of having run
1739
+ // them. This is the row `plan-exec.commit-enablement` stands on: reaching the
1740
+ // commit means having come THROUGH here with a result.
1741
+ action: {
1742
+ invocation: {
1743
+ program: "aw",
1744
+ args: ["session-artifacts", "--code", "{code}", "--dump", "objetivo"],
1745
+ target: SESSION_TARGET,
1746
+ input: null,
1747
+ },
1748
+ evidence: ["plan.validaciones-de-fase-verdes"],
1749
+ idempotent: true,
1750
+ recovery: "arreglá lo que la validación reprobó y volvé a correr las pruebas afectadas: la transición sigue pendiente hasta que su salida real vuelva en verde",
1751
+ },
1752
+ },
1753
+ {
1754
+ id: "plan-exec.deferred-check",
1755
+ scope: "plan-exec",
1756
+ title: "dejar bloqueada la fase cuyo chequeo operativo no puede correrse",
1757
+ authority: "cli",
1758
+ ownership: "cli-owned",
1759
+ document: PLAN_EXEC_LOOP,
1760
+ attribution: PLAN_ATTRIBUTION,
1761
+ },
1762
+ {
1763
+ id: "plan-exec.review-findings",
1764
+ scope: "plan-exec",
1765
+ title: "releer el diff del batch y juzgar sus hallazgos",
1766
+ authority: "agent",
1767
+ ownership: "cli-owned",
1768
+ document: CODE_POLICIES_MD,
1769
+ attribution: PLAN_ATTRIBUTION,
1770
+ },
1771
+ {
1772
+ id: "plan-exec.final-validation",
1773
+ scope: "plan-exec",
1774
+ title: "evaluar la validación final que habilita cerrar el plan",
1775
+ authority: "cli",
1776
+ ownership: "cli-owned",
1777
+ document: PLAN_EXEC_LOOP,
1778
+ attribution: PLAN_ATTRIBUTION,
1779
+ // BEFORE Git, and the real walk is what proved it: the registry had inherited
1780
+ // an order where the plan was committed and only then validated and stamped.
1781
+ // Both documents say the opposite — "last Batch also runs final validation
1782
+ // before Git", and "mark plan done, then commit once per affected source", so
1783
+ // that the status write rides in the same commit instead of orphaning it.
1784
+ // The convergence gate of PLAN-exec. The board is what distinguishes the three
1785
+ // states this rule turns on — every phase green with no closure reads
1786
+ // `final_validation_pending`, and a deferred check keeps its phase blocked —
1787
+ // so it is read, not asserted.
1788
+ action: {
1789
+ invocation: { program: "aw", args: ["status", "--json"], target: ".", input: null },
1790
+ evidence: ["plan.validacion-final-verde"],
1791
+ idempotent: true,
1792
+ recovery: "un chequeo diferido nunca cuenta como aprobado: deja su fase bloqueada y el plan abierto, así que corré lo que falte y volvé a leer el tablero",
1793
+ },
1794
+ },
1795
+ {
1796
+ id: "plan-exec.commit-enablement",
1797
+ scope: "plan-exec",
1798
+ title: "habilitar un commit por fuente solo tras un batch realmente verde",
1799
+ authority: "cli",
1800
+ ownership: "cli-owned",
1801
+ document: CODE_POLICIES_MD,
1802
+ attribution: PLAN_ATTRIBUTION,
1803
+ // "A failed or UNRUN check never authorizes a commit." The rule is enforced by
1804
+ // position, not by asking: this row sits behind the delegated validation and
1805
+ // behind the review, and neither can be passed with a narration. There is no
1806
+ // field a caller could set to arrive here without them.
1807
+ },
1808
+ {
1809
+ id: "plan-exec.commit-authorization",
1810
+ scope: "plan-exec",
1811
+ title: "aprobar los commits del batch o preautorizarlos condicionalmente",
1812
+ authority: "human",
1813
+ ownership: "cli-owned",
1814
+ document: CODE_POLICIES_MD,
1815
+ attribution: PLAN_ATTRIBUTION,
1816
+ alternatives: [
1817
+ {
1818
+ label: "Aprobar los commits del batch",
1819
+ consequence: "se crea exactamente un commit por fuente afectada; sin push, sin --amend y sin --no-verify",
1820
+ recommended: true,
1821
+ },
1822
+ {
1823
+ label: "Dejar el batch sin commitear",
1824
+ consequence: "los cambios quedan en el árbol de trabajo y el batch se registra sin commitear en CHECKPOINT y BACKLOG",
1825
+ recommended: false,
1826
+ },
1827
+ ],
1828
+ },
1829
+ {
1830
+ id: "plan-exec.plan-done",
1831
+ scope: "plan-exec",
1832
+ title: "escribir el estado done del plan con su línea de cierre",
1833
+ authority: "cli",
1834
+ ownership: "cli-owned",
1835
+ document: PLAN_EXEC_LOOP,
1836
+ attribution: PLAN_ATTRIBUTION,
1837
+ effects: ["mutate_overwrite"],
1838
+ condition: {
1839
+ threshold: {
1840
+ observed: "plan-exec.pending-effects",
1841
+ of: ["plan.plan-closable"],
1842
+ min: 1,
1843
+ },
1844
+ otherwise: "al plan le quedan fases sin validar: sellarlo acá sería marcarlo done desde los contadores",
1845
+ },
1846
+ action: {
1847
+ invocation: { program: "aw", args: ["status", "--json"], target: ".", input: null },
1848
+ evidence: ["plan.estado-done-sellado"],
1849
+ idempotent: true,
1850
+ recovery: "escribí '> Estado: done' y su '> Cierre:' en la línea de abajo y volvé a devolver la lectura; si el tablero no lo lee cerrado, la transición sigue pendiente",
1851
+ },
1852
+ },
1853
+ {
1854
+ id: "plan-exec.commit-execution",
1855
+ scope: "plan-exec",
1856
+ title: "crear un commit por fuente afectada y dejar cada árbol limpio o reconocido",
1857
+ authority: "cli",
1858
+ ownership: "cli-owned",
1859
+ document: CODE_POLICIES_MD,
1860
+ attribution: PLAN_ATTRIBUTION,
1861
+ effects: ["execute", "local_additive"],
1862
+ condition: {
1863
+ threshold: {
1864
+ observed: "plan-exec.pending-effects",
1865
+ of: ["plan.commit-pending"],
1866
+ min: 1,
1867
+ },
1868
+ otherwise: "ninguna fuente afectada quedó con cambios sin commitear: no hay commit que crear",
1869
+ },
1870
+ // Approving is not committing. The authorization above is a preference; this
1871
+ // is the effect, and it comes back as the sources' real git state — which is
1872
+ // also the between-unit precondition the policy demands ("each working tree
1873
+ // clean or explicitly acknowledged").
1874
+ action: {
1875
+ invocation: { program: "aw", args: ["sources", "--verbose"], target: ".", input: null },
1876
+ evidence: ["plan.commits-por-fuente"],
1877
+ idempotent: false,
1878
+ recovery: "una fuente que quedó con cambios sin commitear deja el batch SIN commitear: registralo así en CHECKPOINT y BACKLOG en vez de commitear a medias",
1879
+ },
1880
+ },
1881
+ // ── Transversal commands (universe = the command registry) ────────────────
1882
+ {
1883
+ id: "status.board-projection",
1884
+ scope: cmd("status"),
1885
+ title: "proyectar el tablero del workspace desde el índice documental",
1886
+ authority: "cli",
1887
+ ownership: "cli-owned",
1888
+ document: "commands/status.md",
1889
+ attribution: "`aw status`",
1890
+ },
1891
+ {
1892
+ id: "resume.priority-derivation",
1893
+ scope: cmd("resume"),
1894
+ title: "derivar qué continuar y con qué comando exacto",
1895
+ authority: "cli",
1896
+ ownership: "cli-owned",
1897
+ document: "commands/resume.md",
1898
+ attribution: "`aw resume`",
1899
+ },
1900
+ {
1901
+ id: "resume.route-choice",
1902
+ scope: cmd("resume"),
1903
+ title: "elegir cuál de las continuaciones propuestas se ejecuta",
1904
+ authority: "human",
1905
+ ownership: "cli-owned",
1906
+ document: "commands/resume.md",
1907
+ // The person picks; what the CLI owns is the ballot. Which candidates appear,
1908
+ // in what order and with what next command all come from `aw resume`, and the
1909
+ // document says the offer is made over those and no others.
1910
+ attribution: "only for CLI candidates",
1911
+ },
1912
+ // Continuity across prompts, contracted where it is executed. These rules were
1913
+ // the chassis' and could not become steps of a journey: they decide WHICH run a
1914
+ // prompt belongs to, which is answered before any run state exists. What
1915
+ // decides is `resolveSessionTarget` behind the three commands below.
1916
+ {
1917
+ id: "resume.bare-prompt-continues",
1918
+ scope: cmd("resume"),
1919
+ title: "continuar la sesión más reciente ante un prompt sin comando",
1920
+ authority: "cli",
1921
+ ownership: "cli-owned",
1922
+ document: "modules/PROMPT-CONTINUITY.md",
1923
+ attribution: CONTINUITY_ATTRIBUTION,
1924
+ },
1925
+ {
1926
+ id: "resume.prompt-relatedness",
1927
+ scope: cmd("resume"),
1928
+ title: "juzgar si el prompt nuevo pertenece a la línea de trabajo abierta",
1929
+ authority: "agent",
1930
+ ownership: "cli-owned",
1931
+ document: "modules/PROMPT-CONTINUITY.md",
1932
+ attribution: CONTINUITY_ATTRIBUTION,
1933
+ },
1934
+ {
1935
+ id: "resume.escalation-consent",
1936
+ scope: cmd("resume"),
1937
+ title: "consentir una escalación que abre línea nueva sin comando",
1938
+ authority: "human",
1939
+ ownership: "cli-owned",
1940
+ document: SKILL_MD,
1941
+ attribution: CONTINUITY_ATTRIBUTION,
1942
+ },
1943
+ {
1944
+ id: "persist.shape-classification",
1945
+ scope: cmd("persist"),
1946
+ title: "clasificar la forma del trabajo ya hecho en la conversación",
1947
+ authority: "agent",
1948
+ ownership: "cli-owned",
1949
+ document: "commands/persist.md",
1950
+ // Classifying is judgment and the routing table is the module's. What the CLI
1951
+ // owns is that the classification is answered against the inventory `prepare`
1952
+ // returned and admitted only through the digest `validate` hands back — a
1953
+ // shape nobody may assert on their own word. That is the line this sentence
1954
+ // draws, and it names who holds the other side of it.
1955
+ attribution: "belong to `aw persist`",
1956
+ },
1957
+ {
1958
+ id: "persist.routing",
1959
+ scope: cmd("persist"),
1960
+ title: "resolver destino, numeración y escritura del trabajo persistido",
1961
+ authority: "cli",
1962
+ ownership: "cli-owned",
1963
+ document: "modules/PERSIST-ROUTING.md",
1964
+ attribution: "(owned by this command)",
1965
+ effects: ["local_additive"],
1966
+ },
1967
+ {
1968
+ id: "context-plan.signal-declaration",
1969
+ scope: cmd("context-plan"),
1970
+ title: "declarar qué señales observa la corrida",
1971
+ authority: "agent",
1972
+ ownership: "cli-owned",
1973
+ document: "commands/plan-exec.md",
1974
+ // Recognizing that a case carries a signal is judgment; which signals exist
1975
+ // and what each one loads is not. The invocation is the vocabulary: a signal
1976
+ // outside it returns nothing, so the declaration is answered against the
1977
+ // catalog instead of against whatever the reader remembers.
1978
+ attribution: "aw context-plan --command plan-exec --signal",
1979
+ },
1980
+ {
1981
+ id: "context-plan.read-set",
1982
+ scope: cmd("context-plan"),
1983
+ title: "resolver el read-set exacto que la invocación debe cargar",
1984
+ authority: "cli",
1985
+ ownership: "cli-owned",
1986
+ document: "commands/plan-exec.md",
1987
+ attribution: "aw context-plan --command plan-exec",
1988
+ },
1989
+ {
1990
+ id: "context-budget.verdict",
1991
+ scope: cmd("context-budget"),
1992
+ title: "medir el costo de carga de un comando contra su techo",
1993
+ authority: "cli",
1994
+ ownership: "cli-owned",
1995
+ // The manifest, not a command doc: the budget policy the verdict reads lives
1996
+ // there and nothing else in the bundle states this decision. The attribution
1997
+ // guard is what surfaced it — the previous pointer (`commands/plan-exec.md`)
1998
+ // names `aw context-plan` and never mentions the measurement at all.
1999
+ document: "context/MANIFEST.json",
2000
+ attribution: "(aw context-budget)",
2001
+ },
2002
+ {
2003
+ id: "session-create.numbering",
2004
+ scope: cmd("session-create"),
2005
+ title: "asignar el NNN global al crear la sesión",
2006
+ authority: "cli",
2007
+ ownership: "cli-owned",
2008
+ document: "modules/SESSION-NUMBERING.md",
2009
+ attribution: "`aw session-create`",
2010
+ effects: ["local_additive"],
2011
+ },
2012
+ {
2013
+ id: "session-create.new-work-line",
2014
+ scope: cmd("session-create"),
2015
+ title: "tratar un comando de flow como línea de trabajo nueva",
2016
+ authority: "cli",
2017
+ ownership: "cli-owned",
2018
+ document: "modules/PROMPT-CONTINUITY.md",
2019
+ attribution: "`aw session-create`",
2020
+ effects: ["local_additive"],
2021
+ },
2022
+ {
2023
+ id: "session-close.closure",
2024
+ scope: cmd("session-close"),
2025
+ title: "cerrar la sesión y actualizar su fila del registro durable",
2026
+ authority: "cli",
2027
+ ownership: "cli-owned",
2028
+ document: "modules/SESSION-NUMBERING.md",
2029
+ attribution: "`aw session-close`",
2030
+ effects: ["mutate_overwrite"],
2031
+ },
2032
+ {
2033
+ id: "session-resume.reopen",
2034
+ scope: cmd("session-resume"),
2035
+ title: "resolver y reabrir la sesión que continúa el trabajo",
2036
+ authority: "cli",
2037
+ ownership: "cli-owned",
2038
+ document: "modules/SESSION-NUMBERING.md",
2039
+ attribution: "`aw session-resume --code <NNN> --reopen`",
2040
+ effects: ["mutate_overwrite"],
2041
+ },
2042
+ {
2043
+ id: "session-resume.locate",
2044
+ scope: cmd("session-resume"),
2045
+ title: "localizar una sesión existente por descriptor y origen",
2046
+ authority: "cli",
2047
+ ownership: "cli-owned",
2048
+ document: "modules/SESSION-NUMBERING.md",
2049
+ attribution: "`aw session-resume --code <NNN> --reopen`",
2050
+ },
2051
+ {
2052
+ id: "session-resume.rerun-is-create-or-resume",
2053
+ scope: cmd("session-resume"),
2054
+ title: "re-ejecutar el mismo comando sobre la misma entrada como crear-o-reanudar",
2055
+ authority: "cli",
2056
+ ownership: "cli-owned",
2057
+ document: SKILL_MD,
2058
+ attribution: "`aw session-resume --code <NNN> --reopen`",
2059
+ effects: ["mutate_overwrite"],
2060
+ },
2061
+ // Compaction, contracted in the command the host already wires as its
2062
+ // PreCompact hook. Not a step either, and for a sharper reason than continuity:
2063
+ // it fires at whatever boundary the run is standing on, so no position in a
2064
+ // journey could be its own.
2065
+ {
2066
+ id: "checkpoint-write.context-pressure-signal",
2067
+ scope: cmd("checkpoint-write"),
2068
+ title: "reconocer que la corrida está bajo presión de contexto",
2069
+ authority: "agent",
2070
+ ownership: "cli-owned",
2071
+ document: "modules/COMPACTION.md",
2072
+ attribution: COMPACTION_ATTRIBUTION,
2073
+ signals: ["chassis.context-pressure"],
2074
+ },
2075
+ {
2076
+ id: "checkpoint-write.compaction-mode",
2077
+ scope: cmd("checkpoint-write"),
2078
+ title: "elegir el modo de compactación confirm o auto desde la configuración",
2079
+ authority: "cli",
2080
+ ownership: "cli-owned",
2081
+ document: "modules/COMPACTION.md",
2082
+ attribution: COMPACTION_ATTRIBUTION,
2083
+ },
2084
+ {
2085
+ id: "checkpoint-write.compaction-degradation",
2086
+ scope: cmd("checkpoint-write"),
2087
+ title: "degradar auto a confirm cuando el host no tiene mecanismo no interactivo",
2088
+ authority: "cli",
2089
+ ownership: "cli-owned",
2090
+ document: "modules/COMPACTION.md",
2091
+ attribution: COMPACTION_ATTRIBUTION,
2092
+ },
2093
+ {
2094
+ id: "checkpoint-write.before-compacting",
2095
+ scope: cmd("checkpoint-write"),
2096
+ title: "exigir el CHECKPOINT escrito antes de que dispare cualquier compactación",
2097
+ authority: "cli",
2098
+ ownership: "cli-owned",
2099
+ document: "modules/COMPACTION.md",
2100
+ attribution: COMPACTION_ATTRIBUTION,
2101
+ effects: ["mutate_overwrite"],
2102
+ },
2103
+ {
2104
+ id: "check-branch.verdict",
2105
+ scope: cmd("check-branch"),
2106
+ title: "verificar si la fuente está en la rama que el trabajo espera",
2107
+ authority: "cli",
2108
+ ownership: "cli-owned",
2109
+ document: CODE_POLICIES_MD,
2110
+ attribution: "aw check-branch",
2111
+ },
2112
+ {
2113
+ id: "next-number.correlative",
2114
+ scope: cmd("next-number"),
2115
+ title: "entregar el correlativo siguiente de una carpeta documental",
2116
+ authority: "cli",
2117
+ ownership: "cli-owned",
2118
+ document: "modules/SESSION-NUMBERING.md",
2119
+ attribution: "The CLI owns the number (hard rule)",
2120
+ },
2121
+ {
2122
+ id: "capability.routing",
2123
+ scope: cmd("capability"),
2124
+ title: "resolver ruta, autorización de efectos y receipt de un intento",
2125
+ authority: "cli",
2126
+ ownership: "cli-owned",
2127
+ document: "roles/design/CONTRACT.md",
2128
+ attribution: "aw capability prepare",
2129
+ },
2130
+ {
2131
+ id: "designs.reference-verdict",
2132
+ scope: cmd("designs"),
2133
+ title: "decidir si una referencia de diseño resuelve, cambió, fue revocada o no cierra",
2134
+ authority: "cli",
2135
+ ownership: "cli-owned",
2136
+ document: "modules/DESIGN-REFERENCES.md",
2137
+ attribution: "`aw designs`",
2138
+ },
2139
+ {
2140
+ id: "workspace-init.scaffold",
2141
+ scope: cmd("workspace-init"),
2142
+ title: "sembrar el andamiaje mínimo del workspace",
2143
+ authority: "cli",
2144
+ ownership: "cli-owned",
2145
+ document: "modules/WORKSPACE-SCAFFOLD.md",
2146
+ attribution: "CLI-owned `.gitignore`",
2147
+ effects: ["local_additive"],
2148
+ },
2149
+ {
2150
+ id: "generate-launch.detection",
2151
+ scope: cmd("generate-launch"),
2152
+ title: "detectar el stack de cada fuente y generar sus artefactos de arranque",
2153
+ authority: "cli",
2154
+ ownership: "cli-owned",
2155
+ document: "modules/LAUNCH-DETECTION.md",
2156
+ attribution: "Loaded when the CLI's detection is wrong",
2157
+ effects: ["local_additive"],
2158
+ },
2159
+ {
2160
+ id: "fix-git.intent",
2161
+ scope: cmd("fix-git"),
2162
+ title: "interpretar la intención de cada conflicto de la fusión en curso",
2163
+ authority: "agent",
2164
+ ownership: "cli-owned",
2165
+ document: "commands/fix-git.md",
2166
+ // Reading base/ours/theirs for intent is judgment. What is not: which files
2167
+ // may be answered for, that the blob hashes still hold, and that a leftover
2168
+ // marker is a rejection. The interpretation is supplied; nothing lands on it
2169
+ // alone.
2170
+ attribution: "the CLI owns the effects",
2171
+ },
2172
+ {
2173
+ id: "fix-git.resolution-write",
2174
+ scope: cmd("fix-git"),
2175
+ title: "escribir la resolución validada en los archivos en conflicto",
2176
+ authority: "cli",
2177
+ ownership: "cli-owned",
2178
+ document: "commands/fix-git.md",
2179
+ attribution: "`aw fix-git`",
2180
+ effects: ["mutate_overwrite"],
2181
+ },
2182
+ {
2183
+ id: "export.selection",
2184
+ scope: cmd("export-reports"),
2185
+ title: "seleccionar y sintetizar el material de las sesiones que se promueve",
2186
+ authority: "agent",
2187
+ ownership: "cli-owned",
2188
+ document: "commands/export-reports.md",
2189
+ // Synthesizing is authorship. What it may synthesize FROM is not: the corpus
2190
+ // comes from `prepare`, so the promotion is over the sessions the CLI listed
2191
+ // and not over whatever else the conversation remembers.
2192
+ attribution: "`aw export-reports` owns the corpus, the numbering and the write",
2193
+ },
2194
+ {
2195
+ id: "export.numbering-and-write",
2196
+ scope: cmd("export-reports"),
2197
+ title: "resolver corpus, numeración y escritura del dossier promovido",
2198
+ authority: "cli",
2199
+ ownership: "cli-owned",
2200
+ document: "commands/export-reports.md",
2201
+ attribution: "`aw export-reports`",
2202
+ effects: ["local_additive"],
2203
+ },
2204
+ ];
2205
+ /**
2206
+ * Public commands with no journey decision of their own.
2207
+ *
2208
+ * Read together with {@link FLOW_DECISIONS} this is the exhaustiveness claim:
2209
+ * every registered command is either classified or excluded on the record.
2210
+ *
2211
+ * The universe is TWO surfaces, not one. Most entries name an `aw` command, but a
2212
+ * `/w:` command that starts no loop is a public journey just the same, and the
2213
+ * only one of those the engine cannot direct earns its exclusion here rather than
2214
+ * a second list: one field, one reason, one guard.
2215
+ */
2216
+ export const COMMAND_EXCLUSIONS = [
2217
+ {
2218
+ command: "spec-new",
2219
+ reason: "comando `/w:` de una sola pasada que no abre loop: sin corrida que dirigir, su gate de división lo aplica el propio comando con la regla de modules/SPLIT-GATE.md, que por eso conserva su enunciado",
2220
+ },
2221
+ { command: "sessions", reason: "listado read-only del inventario de sesiones" },
2222
+ { command: "session-artifacts", reason: "inspección read-only de lo que guarda una sesión" },
2223
+ { command: "checkpoint-read", reason: "lectura del CHECKPOINT sin decidir continuación" },
2224
+ { command: "auto-compact-on-close", reason: "gatillo de cierre del host, sin regla propia" },
2225
+ { command: "resume-summary", reason: "resumen post-compactación sin decisión de recorrido" },
2226
+ { command: "stack", reason: "detección de stack informativa" },
2227
+ { command: "sources", reason: "inventario de fuentes del workspace" },
2228
+ { command: "set-working-branch", reason: "configuración declarativa de rama" },
2229
+ { command: "set-qa-branch", reason: "configuración declarativa de rama" },
2230
+ { command: "remove-source", reason: "operación de configuración del workspace" },
2231
+ { command: "git-flow", reason: "utilidad de ramas sin recorrido de flow" },
2232
+ { command: "merge-state", reason: "lectura del estado de una fusión" },
2233
+ { command: "attach-multiroot", reason: "configuración de multiroot" },
2234
+ { command: "detach-multiroot", reason: "configuración de multiroot" },
2235
+ { command: "visibility", reason: "configuración de visibilidad de fuentes" },
2236
+ { command: "skills", reason: "inventario de capacidades instaladas y su readiness" },
2237
+ { command: "skill-index", reason: "índice de bindings de capacidades" },
2238
+ {
2239
+ command: "export-diagrams",
2240
+ reason: "comparte la clasificación de export-reports: selección semántica del agente más corpus, numeración y escritura del CLI, con su propio destino documental",
2241
+ },
2242
+ {
2243
+ command: "export-manuals",
2244
+ reason: "comparte la clasificación de export-reports: selección semántica del agente más corpus, numeración y escritura del CLI, con su propio destino documental",
2245
+ },
2246
+ {
2247
+ command: "export-scripts",
2248
+ reason: "comparte la clasificación de export-reports: selección semántica del agente más corpus, numeración y escritura del CLI, con su propio destino documental",
2249
+ },
2250
+ { command: "history-update", reason: "reparación del registro durable" },
2251
+ { command: "project-md-upsert", reason: "escritura del bloque de proyecto en el host" },
2252
+ { command: "code-scan", reason: "barrido read-only del código" },
2253
+ { command: "plugin-doctor", reason: "diagnóstico de instalación" },
2254
+ { command: "plugin-cache", reason: "mantenimiento de caché" },
2255
+ { command: "host-doctor", reason: "diagnóstico de hosts" },
2256
+ { command: "release-data", reason: "datos de release del paquete" },
2257
+ { command: "bootstrap-dsn", reason: "configuración de credenciales de desarrollo" },
2258
+ { command: "hook", reason: "punto de entrada de los hooks del host" },
2259
+ { command: "mcp", reason: "configuración de servidores MCP" },
2260
+ { command: "self", reason: "instalación y mantenimiento del propio CLI" },
2261
+ { command: "harness", reason: "dev-only: inspección del harness" },
2262
+ { command: "profiles", reason: "dev-only: perfiles de ejecución" },
2263
+ { command: "logs", reason: "dev-only: lectura de logs" },
2264
+ { command: "flow", reason: "el motor mismo: aplica el registro, no declara una fila propia" },
2265
+ ];
2266
+ /** The scope's decisions, in the journey order the table declares. */
2267
+ export function decisionsOfScope(scope) {
2268
+ return FLOW_DECISIONS.filter((decision) => decision.scope === scope);
2269
+ }
2270
+ /**
2271
+ * The journey a run of this flow actually walks: its own steps, with the
2272
+ * transversal ones composed in at their declared positions.
2273
+ *
2274
+ * This function is the answer to why the chassis could not be migrated the way a
2275
+ * flow tranche was. A run's cursor is an index into ONE list, and until now that
2276
+ * list was `decisionsOfScope(flow)` — so every `chassis` row was unreachable by
2277
+ * construction, and flipping one to `cli-owned` would have declared ownership of
2278
+ * a step no run can cross. Composing is what turns the declaration into something
2279
+ * a real run demonstrates.
2280
+ *
2281
+ * Every caller that walks, resolves or projects a run reads the journey from
2282
+ * HERE. Two of them composing independently would put the same step at two
2283
+ * positions, and a cursor is only meaningful against one list.
2284
+ */
2285
+ export function journeyOfFlow(flow) {
2286
+ const transversal = decisionsOfScope(CHASSIS_SCOPE);
2287
+ const at = (placement) => transversal.filter((decision) => decision.placement === placement);
2288
+ return [...at("prefix"), ...decisionsOfScope(flow), ...at("suffix")];
2289
+ }
2290
+ /**
2291
+ * The `docs/` folders a flow may write, as the chassis' boundary states them.
2292
+ *
2293
+ * A table rather than a rule with exceptions: the boundary is "its own flow's
2294
+ * doc, plus the category of a capability it composes", and both halves are facts
2295
+ * about the flow. `quick` writes none at all — it has no document — and that
2296
+ * empty list is a real answer, not a missing entry, which is why the map is
2297
+ * exhaustive over the five flows instead of falling back to a default.
2298
+ *
2299
+ * The `design` category is here rather than derived from the composition because
2300
+ * whether a flow MAY publish a package revision is a property of the flow, not of
2301
+ * whichever run happens to compose the capability.
2302
+ */
2303
+ export const DOCS_BOUNDARY = {
2304
+ quick: [],
2305
+ "spec-refine": ["docs/specs", "docs/designs"],
2306
+ "plan-new": ["docs/plans"],
2307
+ "plan-refine": ["docs/plans"],
2308
+ "plan-exec": ["docs/plans", "docs/designs"],
2309
+ };
2310
+ /** The flow a scope names, or null for the chassis and for command scopes. */
2311
+ export function flowOfScope(scope) {
2312
+ return WORKLINE_FLOWS.includes(scope) ? scope : null;
2313
+ }
2314
+ /** The command a scope names, or null when the scope is a flow or the chassis. */
2315
+ export function commandOfScope(scope) {
2316
+ return scope.startsWith(COMMAND_SCOPE_PREFIX) ? scope.slice(COMMAND_SCOPE_PREFIX.length) : null;
2317
+ }
2318
+ /**
2319
+ * The migration tranche a flow belongs to — DERIVED, never stored next to the
2320
+ * flow.
2321
+ *
2322
+ * Persisting both would create two sources for one fact, and the day they
2323
+ * disagree the state is unusable: the whole initiative exists to remove exactly
2324
+ * that kind of double authority.
2325
+ */
2326
+ export function trancheOfFlow(flow) {
2327
+ if (flow === "quick")
2328
+ return "quick";
2329
+ return flow === "spec-refine" ? "spec" : "plan";
2330
+ }
2331
+ // `hasLegacyOwnership(scope)` lived here until the migration closed. It answered
2332
+ // "does anything in this scope still decide from the doctrine", and the honest
2333
+ // end of it is deletion, not an inverted twin: the answer is now `false` for
2334
+ // every scope by construction, and an exported predicate nothing in production
2335
+ // asks would be exactly the defect this initiative spent seventeen phases
2336
+ // removing — a declared surface with no consumer. The guards assert the closing
2337
+ // state over `FLOW_DECISIONS` directly, which is where it is true.
2338
+ //# sourceMappingURL=authority.js.map