@tacuchi/agent-workflow-cli 22.0.0 → 22.1.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 (49) hide show
  1. package/dist/adapters/git-cli.js +37 -12
  2. package/dist/adapters/git-cli.js.map +1 -1
  3. package/dist/adapters/node-process.js +25 -5
  4. package/dist/adapters/node-process.js.map +1 -1
  5. package/dist/application/decision-note-service.js +154 -0
  6. package/dist/application/decision-note-service.js.map +1 -0
  7. package/dist/application/decision-registration-service.js +124 -0
  8. package/dist/application/decision-registration-service.js.map +1 -0
  9. package/dist/application/docs-canon-service.js +13 -2
  10. package/dist/application/docs-canon-service.js.map +1 -1
  11. package/dist/application/flow/submit.js +83 -10
  12. package/dist/application/flow/submit.js.map +1 -1
  13. package/dist/application/mcp-test-connection-service.js +5 -3
  14. package/dist/application/mcp-test-connection-service.js.map +1 -1
  15. package/dist/application/parsers/spec-relation.js +50 -0
  16. package/dist/application/parsers/spec-relation.js.map +1 -1
  17. package/dist/application/resume-service.js +52 -0
  18. package/dist/application/resume-service.js.map +1 -1
  19. package/dist/application/self/install-plugin-skills-git.js +8 -4
  20. package/dist/application/self/install-plugin-skills-git.js.map +1 -1
  21. package/dist/application/source-boundary-policy.js +23 -2
  22. package/dist/application/source-boundary-policy.js.map +1 -1
  23. package/dist/application/workline-index-service.js +154 -4
  24. package/dist/application/workline-index-service.js.map +1 -1
  25. package/dist/domain/decision-choice.js +71 -0
  26. package/dist/domain/decision-choice.js.map +1 -0
  27. package/dist/domain/decision-note.js +235 -0
  28. package/dist/domain/decision-note.js.map +1 -0
  29. package/dist/domain/decision-preview.js +159 -0
  30. package/dist/domain/decision-preview.js.map +1 -0
  31. package/dist/domain/design/governance.js +2 -4
  32. package/dist/domain/design/governance.js.map +1 -1
  33. package/dist/domain/effective-contract.js +138 -0
  34. package/dist/domain/effective-contract.js.map +1 -0
  35. package/dist/domain/flow/authority.js +113 -7
  36. package/dist/domain/flow/authority.js.map +1 -1
  37. package/dist/domain/flow/run-state.js +66 -15
  38. package/dist/domain/flow/run-state.js.map +1 -1
  39. package/dist/domain/lineage.js +190 -0
  40. package/dist/domain/lineage.js.map +1 -0
  41. package/dist/domain/proposal.js +18 -0
  42. package/dist/domain/proposal.js.map +1 -1
  43. package/dist/domain/reconciliation.js +50 -0
  44. package/dist/domain/reconciliation.js.map +1 -0
  45. package/dist/domain/sealed-record.js +19 -0
  46. package/dist/domain/sealed-record.js.map +1 -0
  47. package/package.json +1 -1
  48. package/skills/w/context/MANIFEST.json +10 -0
  49. package/skills/w/loops/plan-exec-loop/LOOP.md +37 -12
@@ -0,0 +1,190 @@
1
+ import { CORRELATIVE_SOURCE } from "./correlative.js";
2
+ import { CRITERION_GLOBAL, isDigest } from "./design/identity.js";
3
+ import { baseDigest } from "./proposal.js";
4
+ const BASELINE_LABEL = /^\s*>\s*Baseline:\s*(.+?)\s*$/i;
5
+ /** `<spec path>@sha256:<64 hex>` — the path is the hint, the digest is the seal. */
6
+ const BASELINE_VALUE = /^(\S+?)@(sha256:[0-9a-fA-F]+)$/;
7
+ /**
8
+ * The digest of a spec's bytes, in the ONE form this system writes.
9
+ *
10
+ * `sha256:`-prefixed because that is what `isDigest` accepts and what every
11
+ * design baseline already publishes. A bare-hex second spelling would be a
12
+ * comparison that fails for a reason nobody can see — the exact failure
13
+ * `baseDigest` exists to prevent, one level up.
14
+ */
15
+ export function specBaselineDigest(specText) {
16
+ return `sha256:${baseDigest(specText)}`;
17
+ }
18
+ /** The line a publication writes into the plan's header blockquote. */
19
+ export function formatSpecBaseline(baseline) {
20
+ return `> Baseline: ${baseline.path}@${baseline.digest}`;
21
+ }
22
+ /**
23
+ * Read the seal from a plan's header blockquote.
24
+ *
25
+ * Bounded to the block between the title and the first `##` section, exactly
26
+ * like `Derived from`: a plan that quotes the marker inside its own prose is
27
+ * describing the contract, not declaring its own lineage.
28
+ */
29
+ export function parsePlanBaseline(lines, fenced, end, specDir) {
30
+ for (let i = 0; i < end; i += 1) {
31
+ const line = lines[i];
32
+ if (line === undefined || fenced[i] === true)
33
+ continue;
34
+ const labelled = BASELINE_LABEL.exec(line);
35
+ if (labelled === null)
36
+ continue;
37
+ return readBaselineValue(labelled[1] ?? "", specDir);
38
+ }
39
+ return { status: "absent" };
40
+ }
41
+ function readBaselineValue(raw, specDir) {
42
+ const value = raw.replace(/^`|`$/g, "").trim();
43
+ const parts = BASELINE_VALUE.exec(value);
44
+ if (parts === null) {
45
+ return {
46
+ status: "malformed",
47
+ raw: value,
48
+ why: "no tiene la forma '<ruta de la spec>@sha256:<64 hex>'",
49
+ action: `escribí '> Baseline: ${specDir}/NNN-spec-<slug>.md@sha256:<64 hex>'`,
50
+ };
51
+ }
52
+ const path = parts[1];
53
+ const digest = parts[2].toLowerCase();
54
+ if (!isDigest(digest)) {
55
+ return {
56
+ status: "malformed",
57
+ raw: value,
58
+ why: "el digest no es 'sha256:' seguido de 64 hex",
59
+ action: "volvé a sellar el plan desde la spec que consumió",
60
+ };
61
+ }
62
+ const number = correlativeOfSpecPath(path, specDir);
63
+ if (number === null) {
64
+ return {
65
+ status: "malformed",
66
+ raw: value,
67
+ why: `la ruta no es una spec bajo '${specDir}/'`,
68
+ action: `apuntá el baseline a un documento de '${specDir}/'`,
69
+ };
70
+ }
71
+ return { status: "sealed", baseline: { path, number, digest } };
72
+ }
73
+ function correlativeOfSpecPath(path, specDir) {
74
+ const escaped = specDir.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
75
+ const re = new RegExp(`^${escaped}/(${CORRELATIVE_SOURCE})-spec[^\\s]*\\.md$`);
76
+ return re.exec(path)?.[1] ?? null;
77
+ }
78
+ /**
79
+ * The plan text with its baseline sealed — inserted when absent, corrected when
80
+ * it says anything else.
81
+ *
82
+ * The seal is written by PUBLICATION, not by whoever authored the document: a
83
+ * digest is not something an author can be asked to compute by hand, and a
84
+ * baseline that is optional in practice is a baseline that drifts. Returning the
85
+ * text (rather than writing it) is what keeps it inside the SAME proposal that
86
+ * publishes the plan — the stamped bytes are the ones previewed, approved and
87
+ * written, so no reader ever sees a published plan whose seal landed separately.
88
+ *
89
+ * The line goes into the header blockquote, right after `Derived from`: the two
90
+ * belong together — one names the spec, the other pins its version.
91
+ */
92
+ export function withSpecBaseline(planText, baseline) {
93
+ const line = formatSpecBaseline(baseline);
94
+ const lines = planText.split("\n");
95
+ const end = headerBlockEnd(lines);
96
+ let lastQuote = -1;
97
+ let derivedAt = -1;
98
+ for (let i = 0; i < end; i += 1) {
99
+ const raw = lines[i];
100
+ if (raw === undefined)
101
+ continue;
102
+ if (!/^\s*>/.test(raw))
103
+ continue;
104
+ lastQuote = i;
105
+ if (BASELINE_LABEL.test(raw)) {
106
+ if (raw === line)
107
+ return planText;
108
+ lines[i] = line;
109
+ return lines.join("\n");
110
+ }
111
+ if (/derived from/i.test(raw))
112
+ derivedAt = i;
113
+ }
114
+ const at = derivedAt >= 0 ? derivedAt + 1 : lastQuote + 1;
115
+ // No blockquote at all: the document does not have the header this seal lives
116
+ // in, and inventing one would restructure somebody's plan to fit a field.
117
+ if (lastQuote < 0)
118
+ return planText;
119
+ lines.splice(at, 0, line);
120
+ return lines.join("\n");
121
+ }
122
+ /** Index of the first `##` section, or the end — the header block's bound. */
123
+ function headerBlockEnd(lines) {
124
+ let fenced = false;
125
+ for (const [index, raw] of lines.entries()) {
126
+ if (raw === undefined)
127
+ continue;
128
+ if (/^\s*```/.test(raw)) {
129
+ fenced = !fenced;
130
+ continue;
131
+ }
132
+ if (!fenced && /^##\s/.test(raw))
133
+ return index;
134
+ }
135
+ return lines.length;
136
+ }
137
+ /**
138
+ * The seal against the spec as it reads NOW.
139
+ *
140
+ * `specText` is `null` when the workspace holds no such spec: that is
141
+ * `unresolved`, not `divergent` — a document that is not there did not change,
142
+ * and saying it did would send whoever reads it to diff against nothing.
143
+ */
144
+ export function alignSpecBaseline(seal, specText) {
145
+ if (seal.status === "absent")
146
+ return { status: "unsealed" };
147
+ if (seal.status === "malformed") {
148
+ return { status: "malformed", why: seal.why, action: seal.action };
149
+ }
150
+ if (specText === null) {
151
+ return { status: "unresolved", reason: "spec-not-found", path: seal.baseline.path };
152
+ }
153
+ const current = specBaselineDigest(specText);
154
+ if (current === seal.baseline.digest)
155
+ return { status: "aligned", digest: current };
156
+ return {
157
+ status: "divergent",
158
+ sealed_digest: seal.baseline.digest,
159
+ current_digest: current,
160
+ };
161
+ }
162
+ /**
163
+ * The acceptance criteria a spec states, in document order and without
164
+ * duplicates.
165
+ *
166
+ * The grammar is NOT a second one: it is `CRITERION_GLOBAL`, the same closed
167
+ * form the design subsystem already harvests (`S033/AC-01`, `S013/AC-SEM-11`).
168
+ * A note that has to say WHICH assertion it replaces addresses it with this and
169
+ * nothing else — inventing a parallel syntax is how two ids for one criterion
170
+ * start disagreeing.
171
+ */
172
+ export function specCriteria(lines, fenced) {
173
+ const seen = new Set();
174
+ const out = [];
175
+ for (const [index, line] of lines.entries()) {
176
+ if (line === undefined || fenced[index] === true)
177
+ continue;
178
+ // Fresh instance per line: the shared literal carries /g, whose lastIndex
179
+ // would otherwise leak between lines.
180
+ for (const match of line.matchAll(new RegExp(CRITERION_GLOBAL.source, CRITERION_GLOBAL.flags))) {
181
+ const id = match[0];
182
+ if (seen.has(id))
183
+ continue;
184
+ seen.add(id);
185
+ out.push(id);
186
+ }
187
+ }
188
+ return out;
189
+ }
190
+ //# sourceMappingURL=lineage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lineage.js","sourceRoot":"","sources":["../../src/domain/lineage.ts"],"names":[],"mappings":"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"}
@@ -88,4 +88,22 @@ export function proposalDigest(body) {
88
88
  export function destinationsOf(proposal) {
89
89
  return [...new Set(proposal.artifacts.map((a) => a.path))].sort();
90
90
  }
91
+ /**
92
+ * What publishing these artifacts really exercises — observed, never declared.
93
+ *
94
+ * One rule, one place. Whoever seals a proposal has to answer "does this
95
+ * overwrite anything?", and two callers answering it separately is how a preview
96
+ * comes to say "creates" about a write the authorization then classifies as a
97
+ * replacement. Callers that know a destination is theirs already (a slot the run
98
+ * reserved to hold a number, and never published into) pass `overwrite: false`
99
+ * for it: nobody's content is lost, which is the only thing the classes measure.
100
+ */
101
+ export function observedEffects(artifacts) {
102
+ const effects = [];
103
+ if (artifacts.some((a) => !a.overwrite))
104
+ effects.push("local_additive");
105
+ if (artifacts.some((a) => a.overwrite))
106
+ effects.push("mutate_overwrite");
107
+ return effects;
108
+ }
91
109
  //# sourceMappingURL=proposal.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"proposal.js","sourceRoot":"","sources":["../../src/domain/proposal.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"proposal.js","sourceRoot":"","sources":["../../src/domain/proposal.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Project what the composed contract still owes.
3
+ *
4
+ * Reads the contract's obligations rather than the chain's, because the contract
5
+ * is the one place that already decided which notes are in force. Walking the
6
+ * chain again here would be a second answer to that question.
7
+ */
8
+ export function reconciliationOf(contract, chain) {
9
+ const resumeOf = new Map(chain.map((note) => [note.id, note.resume_point]));
10
+ const pending = [];
11
+ for (const obligation of contract.obligations) {
12
+ const resume = resumeOf.get(obligation.by);
13
+ // An obligation whose note is not in the chain handed over cannot say where
14
+ // to resume, and inventing a point would send the run somewhere nobody
15
+ // decided. It stays pending — which is the safe half — and names its note.
16
+ pending.push({
17
+ text: obligation.text,
18
+ by: obligation.by,
19
+ resume_point: resume ?? `${obligation.by} (nota ausente de la cadena)`,
20
+ });
21
+ }
22
+ return {
23
+ pending,
24
+ resume_point: pending[0]?.resume_point ?? null,
25
+ closable: pending.length === 0,
26
+ };
27
+ }
28
+ /**
29
+ * Whether this plan may be declared closed, and what to do when it may not.
30
+ *
31
+ * The refusal is the point of the whole phase: a validated result stops counting
32
+ * as sufficient proof of a contract it no longer satisfies. Letting the plan
33
+ * close anyway would file the compensatory work as finished by the very run that
34
+ * created it.
35
+ */
36
+ export function checkClosable(reconciliation) {
37
+ if (reconciliation.closable)
38
+ return [];
39
+ const owed = reconciliation.pending
40
+ .map((obligation) => `${obligation.by}: ${obligation.text}`)
41
+ .join("; ");
42
+ return [
43
+ {
44
+ code: "RECONCILIATION_PENDING",
45
+ message: `el plan tiene compensación abierta y no se puede declarar cerrado — ${owed}`,
46
+ action: `resolvé el trabajo compensatorio desde ${reconciliation.resume_point} y publicá una nota que sustituya a la que lo creó sin volver a arrastrarlo: así se salda una obligación en una cadena append-only`,
47
+ },
48
+ ];
49
+ }
50
+ //# sourceMappingURL=reconciliation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reconciliation.js","sourceRoot":"","sources":["../../src/domain/reconciliation.ts"],"names":[],"mappings":"AAgDA;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAC9B,QAA2B,EAC3B,KAA8B;IAE9B,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAC5E,MAAM,OAAO,GAAwB,EAAE,CAAC;IACxC,KAAK,MAAM,UAAU,IAAI,QAAQ,CAAC,WAAW,EAAE,CAAC;QAC9C,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QAC3C,4EAA4E;QAC5E,uEAAuE;QACvE,2EAA2E;QAC3E,OAAO,CAAC,IAAI,CAAC;YACX,IAAI,EAAE,UAAU,CAAC,IAAI;YACrB,EAAE,EAAE,UAAU,CAAC,EAAE;YACjB,YAAY,EAAE,MAAM,IAAI,GAAG,UAAU,CAAC,EAAE,8BAA8B;SACvE,CAAC,CAAC;IACL,CAAC;IACD,OAAO;QACL,OAAO;QACP,YAAY,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,YAAY,IAAI,IAAI;QAC9C,QAAQ,EAAE,OAAO,CAAC,MAAM,KAAK,CAAC;KAC/B,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAAC,cAAkC;IAC9D,IAAI,cAAc,CAAC,QAAQ;QAAE,OAAO,EAAE,CAAC;IACvC,MAAM,IAAI,GAAG,cAAc,CAAC,OAAO;SAChC,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,GAAG,UAAU,CAAC,EAAE,KAAK,UAAU,CAAC,IAAI,EAAE,CAAC;SAC3D,IAAI,CAAC,IAAI,CAAC,CAAC;IACd,OAAO;QACL;YACE,IAAI,EAAE,wBAAwB;YAC9B,OAAO,EAAE,uEAAuE,IAAI,EAAE;YACtF,MAAM,EAAE,0CAA0C,cAAc,CAAC,YAAY,oIAAoI;SAClN;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,19 @@
1
+ import { createHash } from "node:crypto";
2
+ import { canonicalJson } from "../application/semantic-operation/protocol.js";
3
+ /**
4
+ * A sealed record's own digest, over its canonical JSON WITHOUT `digest`.
5
+ *
6
+ * Extracted so the two kinds of record that need it — a design governance
7
+ * record and a decision note — seal by the SAME code and not merely by the same
8
+ * described rule. Two implementations of "hash this record" drift the day one of
9
+ * them changes, and the failure is silent: both sides keep producing a digest
10
+ * and only their comparison stops matching.
11
+ *
12
+ * Dropping `digest` is not a detail: a value cannot contain its own hash, so a
13
+ * record that included it would be unverifiable by construction.
14
+ */
15
+ export function sealedRecordDigest(record) {
16
+ const { digest: _drop, ...rest } = record;
17
+ return `sha256:${createHash("sha256").update(canonicalJson(rest), "utf8").digest("hex")}`;
18
+ }
19
+ //# sourceMappingURL=sealed-record.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sealed-record.js","sourceRoot":"","sources":["../../src/domain/sealed-record.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,+CAA+C,CAAC;AAE9E;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAyC;IAC1E,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI,EAAE,GAAG,MAAM,CAAC;IAC1C,OAAO,UAAU,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;AAC5F,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tacuchi/agent-workflow-cli",
3
- "version": "22.0.0",
3
+ "version": "22.1.0",
4
4
  "description": "Runtime CLI for Workline — the stages + loops + artifacts system for agent work. Bundles the universal `w` skill set under `skills/w/` (slash commands `/w:*`: spec-new/spec-refine, plan-new/plan-exec, quick, persist, workspace-init, export-*); `self install --target <host>` copies SKILL + commands + hooks into the host. Pluggable capability skills resolve through `.workflow/skills.toml`. Namespace auto-detected from any `.<ns>/sessions/` dir in CWD; default `workflow`.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -32,6 +32,8 @@
32
32
  "quick.deliverable-is-analysis": "what the task produces is an analysis or a design, so its criterion is a rubric a person ratifies",
33
33
  "spec.functional-ambiguity": "a functional ambiguity with two readings that would change what gets built is blocking this spec",
34
34
  "spec.solution-space-unexplored": "a trigger of the ideation gate fires: the spec settled on its first idea and the alternatives would change the outcome",
35
+ "spec.escalation-adopted": "this refine consumed the escalation package an execution handed over, instead of re-deriving its diagnosis",
36
+ "spec.escalation-absent": "no escalation package reached this refine, so it starts from the spec alone",
35
37
  "spec.independent-outcomes": "the prompt holds outcomes each worth delivering on its own",
36
38
  "spec.enumerated-features": "the prompt enumerates distinct features explicitly",
37
39
  "spec.distinct-moments": "the parts are requested for different moments or in a stated order",
@@ -50,6 +52,14 @@
50
52
  "plan.entry-gap-structural": "phases, contracts or journey are missing, or temporary behavior leaves its boundary undeclared",
51
53
  "plan.deviation-structural": "the change that appeared while implementing touches a contract, the participating components, the phase order or the simulation boundary",
52
54
  "plan.deviation-functional": "the change that appeared while implementing touches the expected result, the scope, a business rule or an acceptance criterion",
55
+ "plan.deviation-composable": "the change keeps the functional lineage and can be reconciled forward as a durable decision note instead of returning to a refine",
56
+ "plan.deviation-escalation": "what was found does not compose against this lineage and belongs to a spec of its own",
57
+ "plan.closure-lineage": "the divergence stays inside the same functional lineage: it amends how the promise is met, not who promised it",
58
+ "plan.closure-intent": "the intent behind the divergence is settled, so nothing further has to be elicited before deciding",
59
+ "plan.closure-impact": "the transitive impact can be enumerated: every consumer the decision reaches is nameable",
60
+ "plan.closure-recoverable": "the result can be sealed and recovered: an identical retry gives the same outcome and a failure leaves nothing half-written",
61
+ "plan.escalation-adopted": "this refine consumed the escalation package an execution handed over, instead of re-deriving its diagnosis",
62
+ "plan.escalation-absent": "no escalation package reached this refine, so it starts from the document alone",
53
63
  "chassis.context-pressure": "a fresh reader would need the CHECKPOINT to continue this run",
54
64
  "plan.tasks-to-mark": "at least one task of the batch finished its local work and its checkbox is still open",
55
65
  "plan.plan-closable": "every phase of the plan will be validated once this batch closes, so the plan can be sealed done",
@@ -139,22 +139,47 @@ reconciliation** in `CHECKPOINT`, never reported as published.
139
139
 
140
140
  ## Deviation gate
141
141
 
142
- Execution resolves **detail**, never **redesign**. This gate lives **only** in this loop the chassis does not carry it.
142
+ Execution resolves **detail**, never **redesign** and between those two there is a third thing: a
143
+ divergence that keeps the functional lineage and can be reconciled **forward**.
144
+ This gate lives **only** in this loop — the chassis does not carry it. It has **four** exits, and the
145
+ run stops at it: declaring a deviation no longer carries on to the commit by itself.
143
146
 
144
- - **Local decision — `plan-exec` continues.** A class or method name; a local helper; internal code layout; imports; a choice between equivalent APIs already allowed; a fix needed to compile; a minor refactor that does not move the journey; one extra focused test for a risk found while implementing. Recorded in `DECISION` only when it is not obvious.
147
+ - **Local decision — `plan-exec` continues.** A class or method name; a local helper; internal code layout; imports; a choice between equivalent APIs already allowed; a fix needed to compile; a minor refactor that does not move the journey; one extra focused test for a risk found while implementing. Declares **no signal** — the gate is not raised — and is recorded in `DECISION` only when it is not obvious.
148
+ - **Composable decision — a note is registered and execution continues.** The change amends how the promise is met without changing who promised it. It is the only exit that writes a **decision note**, and it is available **only** when all four closures hold (below). The spec and the plan stay byte-exact.
145
149
  - **Structural deviation — stop and return to `plan-refine`.** Stop when the change touches: an input or output; an observable state; a relevant endpoint or public contract; the set of participating components or repositories; the phase order; the simulation boundary; the integration strategy; a material dependency; the main persistence mechanism; a phase already `validada`; the evidence needed to demonstrate the result. A decision that substantially expands or shrinks the work counts too.
146
150
  - **Functional change — return to `spec-refine`.** Stop when the change touches: the expected result; the functional scope; a business rule; an acceptance criterion; the actor or consumer; or a product decision.
147
151
 
148
- On either return path: `CHECKPOINT` records the state reached and the trigger, the phase keeps the state it really has (`en ejecución` or `bloqueada`, never `validada`), the working tree is left committed or acknowledged (Delta 2), and the human is told through a structured-choice which command to run. Resuming later over the corrected plan is a normal `create_or_resume`.
149
-
150
- | Finding | `plan-exec` | `plan-refine` | `spec-refine` |
151
- |---|---|---|---|
152
- | Rename a helper · internal implementation · test for a local risk | continues | — | — |
153
- | Change the public DTO | stops | yes | yes, if behavior changes |
154
- | Add a participating repository | stops | yes | — |
155
- | Move the simulation to another boundary | stops | yes | — |
156
- | Change the phase order | stops | yes | |
157
- | Add a functional rule · change an acceptance criterion | stops | — | yes |
152
+ **Eligibility is closure, never size.** The composable exit opens only when the four close: the
153
+ divergence stays in the **same functional lineage**; its **intent is settled**, so nothing further has
154
+ to be elicited; its **transitive impact can be enumerated**, every consumer nameable; and its
155
+ **result can be sealed and recovered**, so an identical retry gives the same outcome and a failure
156
+ leaves nothing half-written. Counts of criteria, phases or repositories are reported and decide
157
+ nothing: a divergence touching one criterion that closes three of the four is not composable, and one
158
+ touching nine that closes all four is.
159
+
160
+ > **Which exit the run takes is the person's, and which are offered is not this document's call:** the deterministic steps below are decided by the CLI (`aw flow advance`), not by this document. Recognizing the divergence and closing its eligibility are judgment; counting the closures and raising the choice are a rule.
161
+
162
+ On any exit that leaves the phase: `CHECKPOINT` records the state reached and the trigger, the phase
163
+ keeps the state it really has (`en ejecución` or `bloqueada`, never `validada`), and the working tree
164
+ is left committed or acknowledged (Delta 2). On the two return paths and on an escalation, the
165
+ **escalation package** travels with it — diagnosis, decisions already taken, evidence gathered and the
166
+ return point — and the destination **declares that it consumed it**, so returning never costs redoing
167
+ the analysis execution had already done. Resuming later over the corrected plan is a normal
168
+ `create_or_resume`.
169
+
170
+ **More than one divergence may appear in a run, and every one is kept.** A later recognition never
171
+ erases an earlier one.
172
+
173
+ | Finding | `plan-exec` | note | `plan-refine` | `spec-refine` |
174
+ |---|---|---|---|---|
175
+ | Rename a helper · internal implementation · test for a local risk | continues | — | — | — |
176
+ | Same promise, different route, four closures held | continues | yes | — | — |
177
+ | Change the public DTO | stops | — | yes | yes, if behavior changes |
178
+ | Add a participating repository | stops | — | yes | — |
179
+ | Move the simulation to another boundary | stops | — | yes | — |
180
+ | Change the phase order | stops | — | yes | — |
181
+ | Add a functional rule · change an acceptance criterion | stops | — | — | yes |
182
+ | Does not compose against this lineage at all | stops | — | — | a spec of its own |
158
183
 
159
184
  ## Delta 2 — Git policy: **safe branch + proposed commits**
160
185