@jaimevalasek/aioson 1.33.1 → 1.37.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 (162) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/docs/en/1-understand/ecosystem-map.md +45 -29
  3. package/docs/en/1-understand/glossary.md +6 -6
  4. package/docs/en/1-understand/what-is-aioson.md +5 -5
  5. package/docs/en/2-start/existing-project.md +7 -7
  6. package/docs/en/2-start/first-project.md +33 -39
  7. package/docs/en/2-start/initial-decisions.md +22 -19
  8. package/docs/en/3-recipes/README.md +2 -2
  9. package/docs/en/3-recipes/continuity-between-sessions.md +2 -2
  10. package/docs/en/3-recipes/from-idea-to-prd-via-briefing.md +13 -3
  11. package/docs/en/3-recipes/full-feature-with-sheldon.md +329 -338
  12. package/docs/en/4-agents/README.md +33 -16
  13. package/docs/en/4-agents/briefing-refiner.md +146 -0
  14. package/docs/en/4-agents/discovery-design-doc.md +20 -8
  15. package/docs/en/4-agents/forge-run.md +3 -2
  16. package/docs/en/5-reference/README.md +1 -0
  17. package/docs/en/5-reference/autopilot-handoff.md +286 -0
  18. package/docs/en/5-reference/cli-reference.md +57 -48
  19. package/docs/en/5-reference/executable-verification.md +10 -7
  20. package/docs/en/5-reference/parallel.md +2 -0
  21. package/docs/en/5-reference/qa-browser.md +2 -2
  22. package/docs/en/README.md +3 -3
  23. package/docs/pt/1-entender/glossario.md +5 -5
  24. package/docs/pt/1-entender/mapa-do-ecossistema.md +33 -22
  25. package/docs/pt/2-comecar/decisoes-iniciais.md +20 -18
  26. package/docs/pt/2-comecar/primeiro-projeto.md +2 -2
  27. package/docs/pt/3-receitas/README.md +1 -1
  28. package/docs/pt/3-receitas/app-saas-do-zero.md +35 -122
  29. package/docs/pt/3-receitas/da-ideia-ao-prd-via-briefing.md +8 -1
  30. package/docs/pt/3-receitas/feature-completa-com-sheldon.md +291 -338
  31. package/docs/pt/4-agentes/README.md +13 -11
  32. package/docs/pt/4-agentes/analyst.md +9 -5
  33. package/docs/pt/4-agentes/architect.md +9 -5
  34. package/docs/pt/4-agentes/briefing-refiner.md +64 -40
  35. package/docs/pt/4-agentes/briefing.md +6 -1
  36. package/docs/pt/4-agentes/dev.md +38 -8
  37. package/docs/pt/4-agentes/deyvin.md +4 -0
  38. package/docs/pt/4-agentes/discover.md +4 -0
  39. package/docs/pt/4-agentes/discovery-design-doc.md +25 -23
  40. package/docs/pt/4-agentes/neo.md +4 -0
  41. package/docs/pt/4-agentes/orache.md +6 -0
  42. package/docs/pt/4-agentes/orchestrator.md +176 -118
  43. package/docs/pt/4-agentes/pentester.md +11 -5
  44. package/docs/pt/4-agentes/pm.md +15 -7
  45. package/docs/pt/4-agentes/product.md +19 -1
  46. package/docs/pt/4-agentes/qa.md +10 -2
  47. package/docs/pt/4-agentes/setup.md +3 -1
  48. package/docs/pt/4-agentes/sheldon.md +178 -99
  49. package/docs/pt/4-agentes/tester.md +6 -0
  50. package/docs/pt/4-agentes/ux-ui.md +9 -5
  51. package/docs/pt/5-referencia/README.md +1 -1
  52. package/docs/pt/5-referencia/agent-chain-continuity.md +14 -5
  53. package/docs/pt/5-referencia/autopilot-handoff.md +191 -74
  54. package/docs/pt/5-referencia/comandos-cli.md +16 -7
  55. package/docs/pt/5-referencia/sdd-automation-scripts.md +6 -6
  56. package/docs/pt/5-referencia/sdd-framework.md +53 -16
  57. package/docs/pt/5-referencia/sdd-planos-e-estrutura.md +27 -15
  58. package/docs/pt/5-referencia/skills.md +2 -0
  59. package/docs/pt/README.md +4 -4
  60. package/docs/pt/agentes.md +50 -50
  61. package/package.json +2 -2
  62. package/src/agents.js +1 -1
  63. package/src/artifact-kinds.js +111 -0
  64. package/src/autopilot-signal.js +71 -0
  65. package/src/cli.js +112 -81
  66. package/src/commands/agent-epilogue.js +251 -186
  67. package/src/commands/agents.js +122 -50
  68. package/src/commands/audit-code.js +344 -0
  69. package/src/commands/briefing.js +337 -1
  70. package/src/commands/classify.js +389 -389
  71. package/src/commands/feature-close.js +122 -4
  72. package/src/commands/harness-check.js +259 -175
  73. package/src/commands/live.js +71 -11
  74. package/src/commands/review-feature.js +189 -0
  75. package/src/commands/runtime.js +81 -66
  76. package/src/commands/sync-agents-copy.js +115 -0
  77. package/src/commands/update.js +5 -1
  78. package/src/commands/verification-plan.js +143 -0
  79. package/src/commands/verify-artifact.js +593 -0
  80. package/src/commands/workflow-execute.js +434 -316
  81. package/src/commands/workflow-next.js +193 -20
  82. package/src/commands/workflow-plan.js +5 -5
  83. package/src/constants.js +2 -0
  84. package/src/doctor.js +4 -2
  85. package/src/gateway-pointer-merge.js +7 -1
  86. package/src/handoff-contract.js +267 -172
  87. package/src/harness/contract-integrity-gate.js +172 -0
  88. package/src/harness/contract-integrity.js +111 -0
  89. package/src/harness/contract-schema.js +377 -332
  90. package/src/harness/detect-runtime-feature.js +90 -0
  91. package/src/harness/static-criteria.js +192 -0
  92. package/src/i18n/messages/en.js +10 -5
  93. package/src/i18n/messages/es.js +10 -5
  94. package/src/i18n/messages/fr.js +10 -5
  95. package/src/i18n/messages/pt-BR.js +10 -5
  96. package/src/install-wizard.js +8 -8
  97. package/src/installer.js +13 -6
  98. package/src/lib/briefing-refiner/apply-feedback.js +18 -4
  99. package/src/lib/briefing-refiner/feedback-schema.js +73 -4
  100. package/src/lib/briefing-refiner/refinement-report.js +11 -0
  101. package/src/lib/briefing-refiner/review-html.js +388 -68
  102. package/src/parser.js +12 -0
  103. package/src/preflight-engine.js +12 -12
  104. package/src/verification/policy-engine.js +95 -95
  105. package/src/verification-policy.js +398 -0
  106. package/src/version.js +52 -1
  107. package/template/.aioson/agents/architect.md +34 -5
  108. package/template/.aioson/agents/briefing-refiner.md +91 -48
  109. package/template/.aioson/agents/briefing.md +4 -0
  110. package/template/.aioson/agents/committer.md +2 -1
  111. package/template/.aioson/agents/copywriter.md +30 -21
  112. package/template/.aioson/agents/design-hybrid-forge.md +10 -1
  113. package/template/.aioson/agents/dev.md +35 -23
  114. package/template/.aioson/agents/deyvin.md +4 -0
  115. package/template/.aioson/agents/discover.md +16 -3
  116. package/template/.aioson/agents/discovery-design-doc.md +7 -2
  117. package/template/.aioson/agents/genome.md +19 -10
  118. package/template/.aioson/agents/neo.md +34 -30
  119. package/template/.aioson/agents/orache.md +24 -11
  120. package/template/.aioson/agents/orchestrator.md +100 -7
  121. package/template/.aioson/agents/pentester.md +4 -0
  122. package/template/.aioson/agents/pm.md +8 -8
  123. package/template/.aioson/agents/product.md +25 -1
  124. package/template/.aioson/agents/profiler-enricher.md +20 -11
  125. package/template/.aioson/agents/profiler-forge.md +20 -11
  126. package/template/.aioson/agents/profiler-researcher.md +20 -11
  127. package/template/.aioson/agents/qa.md +96 -31
  128. package/template/.aioson/agents/setup.md +12 -1
  129. package/template/.aioson/agents/sheldon.md +103 -13
  130. package/template/.aioson/agents/site-forge.md +11 -2
  131. package/template/.aioson/agents/squad.md +20 -5
  132. package/template/.aioson/agents/tester.md +4 -0
  133. package/template/.aioson/agents/ux-ui.md +2 -1
  134. package/template/.aioson/agents/validator.md +33 -1
  135. package/template/.aioson/config/verification.json +61 -0
  136. package/template/.aioson/config.md +13 -9
  137. package/template/.aioson/docs/LAYERS.md +2 -2
  138. package/template/.aioson/docs/agent-help.md +126 -0
  139. package/template/.aioson/docs/autopilot-handoff.md +30 -20
  140. package/template/.aioson/docs/dev/execution-discipline.md +41 -0
  141. package/template/.aioson/docs/dev/phase-loop.md +50 -0
  142. package/template/.aioson/docs/play/llm-data-and-bindings.md +70 -7
  143. package/template/.aioson/docs/presets/workflow.config.full-merged.json +15 -0
  144. package/template/.aioson/docs/presets/workflow.config.lean.json +15 -0
  145. package/template/.aioson/docs/product/prd-contract.md +12 -12
  146. package/template/.aioson/docs/prototype-contract.md +21 -4
  147. package/template/.aioson/docs/reference-identity.md +94 -0
  148. package/template/.aioson/docs/sheldon/harness-contract.md +155 -28
  149. package/template/.aioson/docs/verification-config.md +82 -0
  150. package/template/.aioson/docs/verify-artifact-gates.md +91 -0
  151. package/template/.aioson/docs/workflow-lean-lane.md +129 -0
  152. package/template/.aioson/skills/design/interface-design/SKILL.md +17 -0
  153. package/template/.aioson/skills/design/interface-design/references/intent-and-domain.md +2 -0
  154. package/template/.aioson/skills/process/aioson-spec-driven/SKILL.md +16 -14
  155. package/template/.aioson/skills/process/aioson-spec-driven/references/approval-gates.md +2 -2
  156. package/template/.aioson/skills/process/aioson-spec-driven/references/classification-map.md +4 -4
  157. package/template/.aioson/skills/process/aioson-spec-driven/references/dev.md +15 -15
  158. package/template/.aioson/skills/process/prototype-forge/SKILL.md +6 -0
  159. package/template/.aioson/skills/process/reference-identity-extract/SKILL.md +164 -0
  160. package/template/.aioson/skills/static/multi-agent-patterns.md +5 -5
  161. package/template/AGENTS.md +36 -36
  162. package/template/CLAUDE.md +15 -11
@@ -27,6 +27,7 @@ const { readHandoff, readHandoffProtocol } = require('../session-handoff');
27
27
  const {
28
28
  STATE_RELATIVE_PATH,
29
29
  buildDefaultWorkflowConfig,
30
+ detectWorkflowMode,
30
31
  readWorkflowConfig,
31
32
  runWorkflowNext
32
33
  } = require('./workflow-next');
@@ -37,17 +38,17 @@ const {
37
38
  extractStatusWritePathItems
38
39
  } = require('../parallel-workspace');
39
40
 
40
- const BAR = '━'.repeat(45);
41
- const EXECUTION_STATE_RELATIVE_PATH = '.aioson/context/workflow-execute.json';
42
- const DEFAULT_AGENTIC_MAX_CYCLES = 3;
41
+ const BAR = '━'.repeat(45);
42
+ const EXECUTION_STATE_RELATIVE_PATH = '.aioson/context/workflow-execute.json';
43
+ const DEFAULT_AGENTIC_MAX_CYCLES = 3;
43
44
 
44
45
  const STEP_META = {
45
46
  setup: { description: 'Initialize project context', gate_before: null, gate_after: null },
46
47
  product: { description: 'Generate PRD', gate_before: null, gate_after: null },
47
- analyst: { description: 'Map requirements + spec', gate_before: null, gate_after: 'A' },
48
- architect: { description: 'Architecture design', gate_before: 'A', gate_after: 'B' },
49
- 'discovery-design-doc': { description: 'Prepare design-doc and readiness contract', gate_before: 'B', gate_after: null },
50
- 'ux-ui': { description: 'UI/UX design', gate_before: 'A', gate_after: 'B', optional: true },
48
+ analyst: { description: 'Map requirements + spec', gate_before: null, gate_after: 'A' },
49
+ architect: { description: 'Architecture design', gate_before: 'A', gate_after: 'B' },
50
+ 'discovery-design-doc': { description: 'Prepare design-doc and readiness contract', gate_before: 'B', gate_after: null },
51
+ 'ux-ui': { description: 'UI/UX design', gate_before: 'A', gate_after: 'B', optional: true },
51
52
  pm: { description: 'Backlog + PM plan', gate_before: 'B', gate_after: 'C' },
52
53
  orchestrator: { description: 'Coordinate execution lanes', gate_before: 'C', gate_after: null },
53
54
  dev: { description: 'Implementation', gate_before: null, gate_after: 'C' },
@@ -89,105 +90,120 @@ function normalizeClassification(value, fallback = 'SMALL') {
89
90
  return ['MICRO', 'SMALL', 'MEDIUM'].includes(safe) ? safe : fallback;
90
91
  }
91
92
 
92
- function normalizeAgentName(value) {
93
- return String(value || '').trim().toLowerCase().replace(/^@/, '');
94
- }
95
-
96
- function quoteCliArg(value) {
97
- return `'${String(value || '').replace(/'/g, "'\\''")}'`;
98
- }
99
-
100
- function parsePositiveIntegerOption(value, fallback, min = 1, max = 10) {
101
- const parsed = Number.parseInt(String(value || ''), 10);
102
- if (!Number.isInteger(parsed)) return fallback;
103
- if (parsed < min) return min;
104
- if (parsed > max) return max;
105
- return parsed;
106
- }
107
-
108
- function isAgenticRequested(options = {}) {
109
- return Boolean(
110
- options.agentic ||
111
- options['agentic-run'] ||
112
- options.autopilot === 'agentic' ||
113
- options.autopilot === 'runtime'
114
- );
115
- }
116
-
117
- function buildAgenticPolicy(options = {}, classification = 'SMALL') {
118
- const enabled = isAgenticRequested(options);
119
- if (!enabled) return null;
120
-
121
- const maxDevQaCycles = parsePositiveIntegerOption(
122
- options['max-dev-qa-cycles'] || options.maxDevQaCycles || options['max-cycles'],
123
- DEFAULT_AGENTIC_MAX_CYCLES
124
- );
125
- const maxTesterCycles = parsePositiveIntegerOption(
126
- options['max-tester-cycles'] || options.maxTesterCycles || options['max-specialist-cycles'],
127
- DEFAULT_AGENTIC_MAX_CYCLES
128
- );
129
- const maxPentesterCycles = parsePositiveIntegerOption(
130
- options['max-pentester-cycles'] || options.maxPentesterCycles || options['max-specialist-cycles'],
131
- DEFAULT_AGENTIC_MAX_CYCLES
132
- );
133
-
134
- return {
135
- enabled: true,
136
- mode: 'runtime_policy',
137
- source: 'workflow:execute',
138
- stop_conditions: [
139
- 'feature_status_done',
140
- 'human_decision_required',
141
- 'gate_blocked',
142
- 'context_budget_exceeded',
143
- 'cycle_limit_reached',
144
- 'critical_security_human_gate',
145
- 'feature_close_human_gate'
146
- ],
147
- review_cycle: {
148
- hub: 'qa',
149
- max_dev_qa_cycles: maxDevQaCycles,
150
- max_tester_correction_cycles: maxTesterCycles,
151
- max_pentester_correction_cycles: maxPentesterCycles,
152
- qa_fail_route: 'dev',
153
- tester_route: 'tester after qa coverage trigger',
154
- pentester_route: 'pentester after sensitive-surface trigger or MEDIUM sequence stage',
155
- validator_route: 'validator when harness contract is present',
156
- feature_close: 'human_gate'
157
- },
158
- lanes: {
159
- enabled: classification === 'MEDIUM',
160
- strategy: 'parallelize_only_independent_write_scopes',
161
- guard_command: 'aioson parallel:guard . --lane=<n>',
162
- conflict_action: 'block_lane'
163
- },
164
- sidecars: {
165
- scouts: {
166
- enabled: true,
167
- read_only: true,
168
- max_per_session: 3,
169
- max_files_in_scope: 20,
170
- allowed_parent_agents: ['deyvin', 'dev', 'product', 'briefing', 'orache']
171
- },
172
- research: {
173
- enabled: true,
174
- cache_dir: 'researchs/',
175
- cache_ttl_days: 7
176
- }
177
- }
178
- };
179
- }
180
-
181
- function formatAgenticPolicyLines(policy) {
182
- if (!policy || !policy.enabled) return [];
183
- return [
184
- `Agentic policy: enabled (dev<->qa max ${policy.review_cycle.max_dev_qa_cycles} cycles)`,
185
- `Review loop: qa fail -> dev; tester max ${policy.review_cycle.max_tester_correction_cycles}; pentester max ${policy.review_cycle.max_pentester_correction_cycles}; close=${policy.review_cycle.feature_close}`,
186
- `Parallel lanes: ${policy.lanes.enabled ? 'enabled for independent write scopes' : 'disabled for this classification'}`
187
- ];
188
- }
189
-
190
- function findNextFromSequence(sequence, completed, skipped = []) {
93
+ function normalizeAgentName(value) {
94
+ return String(value || '').trim().toLowerCase().replace(/^@/, '');
95
+ }
96
+
97
+ function quoteCliArg(value) {
98
+ return `'${String(value || '').replace(/'/g, "'\\''")}'`;
99
+ }
100
+
101
+ function parsePositiveIntegerOption(value, fallback, min = 1, max = 10) {
102
+ const parsed = Number.parseInt(String(value || ''), 10);
103
+ if (!Number.isInteger(parsed)) return fallback;
104
+ if (parsed < min) return min;
105
+ if (parsed > max) return max;
106
+ return parsed;
107
+ }
108
+
109
+ function isAgenticRequested(options = {}) {
110
+ return Boolean(
111
+ options.agentic ||
112
+ options['agentic-run'] ||
113
+ options.autopilot === 'agentic' ||
114
+ options.autopilot === 'runtime' ||
115
+ // Seeding the scheme IS turning on autopilot — the whole point of --seed is to
116
+ // persist an enabled agentic_policy the interactive agents then follow.
117
+ options.seed ||
118
+ options['seed-only']
119
+ );
120
+ }
121
+
122
+ function buildAgenticPolicy(options = {}, classification = 'SMALL') {
123
+ // --seed --step: the per-feature "step by step" choice. Writes an explicitly
124
+ // DISARMED scheme — resolveAutopilotSignal and the agent triggers treat it as
125
+ // the feature-level override that wins over a project-wide auto_handoff: true.
126
+ if (options.step) {
127
+ return {
128
+ enabled: false,
129
+ mode: 'step_by_step',
130
+ source: 'workflow:execute --step'
131
+ };
132
+ }
133
+
134
+ const enabled = isAgenticRequested(options);
135
+ if (!enabled) return null;
136
+
137
+ const maxDevQaCycles = parsePositiveIntegerOption(
138
+ options['max-dev-qa-cycles'] || options.maxDevQaCycles || options['max-cycles'],
139
+ DEFAULT_AGENTIC_MAX_CYCLES
140
+ );
141
+ const maxTesterCycles = parsePositiveIntegerOption(
142
+ options['max-tester-cycles'] || options.maxTesterCycles || options['max-specialist-cycles'],
143
+ DEFAULT_AGENTIC_MAX_CYCLES
144
+ );
145
+ const maxPentesterCycles = parsePositiveIntegerOption(
146
+ options['max-pentester-cycles'] || options.maxPentesterCycles || options['max-specialist-cycles'],
147
+ DEFAULT_AGENTIC_MAX_CYCLES
148
+ );
149
+
150
+ return {
151
+ enabled: true,
152
+ mode: 'runtime_policy',
153
+ source: 'workflow:execute',
154
+ stop_conditions: [
155
+ 'feature_status_done',
156
+ 'human_decision_required',
157
+ 'gate_blocked',
158
+ 'context_budget_exceeded',
159
+ 'cycle_limit_reached',
160
+ 'critical_security_human_gate',
161
+ 'feature_close_human_gate'
162
+ ],
163
+ review_cycle: {
164
+ hub: 'qa',
165
+ max_dev_qa_cycles: maxDevQaCycles,
166
+ max_tester_correction_cycles: maxTesterCycles,
167
+ max_pentester_correction_cycles: maxPentesterCycles,
168
+ qa_fail_route: 'dev',
169
+ tester_route: 'tester after qa coverage trigger',
170
+ pentester_route: 'pentester after sensitive-surface trigger or MEDIUM sequence stage',
171
+ validator_route: 'validator when harness contract is present',
172
+ feature_close: 'human_gate'
173
+ },
174
+ lanes: {
175
+ enabled: classification === 'MEDIUM',
176
+ strategy: 'parallelize_only_independent_write_scopes',
177
+ guard_command: 'aioson parallel:guard . --lane=<n>',
178
+ conflict_action: 'block_lane'
179
+ },
180
+ sidecars: {
181
+ scouts: {
182
+ enabled: true,
183
+ read_only: true,
184
+ max_per_session: 3,
185
+ max_files_in_scope: 20,
186
+ allowed_parent_agents: ['deyvin', 'dev', 'product', 'briefing', 'orache']
187
+ },
188
+ research: {
189
+ enabled: true,
190
+ cache_dir: 'researchs/',
191
+ cache_ttl_days: 7
192
+ }
193
+ }
194
+ };
195
+ }
196
+
197
+ function formatAgenticPolicyLines(policy) {
198
+ if (!policy || !policy.enabled) return [];
199
+ return [
200
+ `Agentic policy: enabled (dev<->qa max ${policy.review_cycle.max_dev_qa_cycles} cycles)`,
201
+ `Review loop: qa fail -> dev; tester max ${policy.review_cycle.max_tester_correction_cycles}; pentester max ${policy.review_cycle.max_pentester_correction_cycles}; close=${policy.review_cycle.feature_close}`,
202
+ `Parallel lanes: ${policy.lanes.enabled ? 'enabled for independent write scopes' : 'disabled for this classification'}`
203
+ ];
204
+ }
205
+
206
+ function findNextFromSequence(sequence, completed, skipped = []) {
191
207
  const done = new Set([...(completed || []), ...(skipped || [])].map(normalizeAgentName));
192
208
  return sequence.find((stage) => !done.has(normalizeAgentName(stage))) || null;
193
209
  }
@@ -229,6 +245,18 @@ function inferCompletedStagesFromArtifacts(sequence, artifacts, gates) {
229
245
  inferred = Boolean(artifacts.requirements && artifacts.requirements.exists && gates.requirements === 'approved');
230
246
  } else if (normalized === 'architect') {
231
247
  inferred = Boolean(artifacts.architecture && artifacts.architecture.exists && gates.design === 'approved');
248
+ } else if (normalized === 'sheldon' || normalized === 'orchestrator') {
249
+ // Single spec authorities (lean @sheldon / maestro @orchestrator) collapse
250
+ // Gates A/B/C into one hop — done once the spec package exists with the
251
+ // collapsed gates approved. Without this, a seed run AFTER the spec stage
252
+ // finished reported `next: sheldon|orchestrator` and pointed the whole
253
+ // agentic scheme backwards.
254
+ inferred = Boolean(
255
+ artifacts.spec && artifacts.spec.exists &&
256
+ gates.requirements === 'approved' &&
257
+ gates.design === 'approved' &&
258
+ gates.plan === 'approved'
259
+ );
232
260
  } else if (normalized === 'pm') {
233
261
  inferred = Boolean(artifacts.implementation_plan && artifacts.implementation_plan.exists && gates.plan === 'approved');
234
262
  } else if (normalized === 'qa') {
@@ -244,9 +272,17 @@ function inferCompletedStagesFromArtifacts(sequence, artifacts, gates) {
244
272
  return completed;
245
273
  }
246
274
 
247
- async function seedFeatureWorkflowState(targetDir, slug, classification, startFrom) {
248
- const statePath = path.join(targetDir, STATE_RELATIVE_PATH);
249
- const existing = await readJsonIfExists(statePath);
275
+ /**
276
+ * Reads the persisted workflow state for a seed/preview of `slug`, applying the
277
+ * SAME staleness guard as loadOrCreateState: a state whose feature is no longer
278
+ * the features.md-active feature is stale and gets discarded (the loader would
279
+ * throw it away one command later anyway — hard-failing the seed on it just
280
+ * silently disarmed autopilot). The refusal survives only for a genuinely
281
+ * active different feature.
282
+ */
283
+ async function resolveExistingFeatureState(targetDir, slug) {
284
+ const statePath = path.join(targetDir, STATE_RELATIVE_PATH);
285
+ let existing = await readJsonIfExists(statePath);
250
286
  const focusStage = getFocusStage(existing);
251
287
 
252
288
  if (
@@ -256,14 +292,41 @@ async function seedFeatureWorkflowState(targetDir, slug, classification, startFr
256
292
  existing.featureSlug !== slug &&
257
293
  focusStage
258
294
  ) {
259
- return {
260
- ok: false,
261
- reason: 'different_active_feature',
262
- active_feature: existing.featureSlug,
263
- active_stage: focusStage
264
- };
295
+ let modeInfo = null;
296
+ try {
297
+ modeInfo = await detectWorkflowMode(targetDir);
298
+ } catch {
299
+ modeInfo = null;
300
+ }
301
+ const existingIsActive = Boolean(
302
+ modeInfo && modeInfo.mode === 'feature' && modeInfo.featureSlug === existing.featureSlug
303
+ );
304
+ const requestedIsActive = Boolean(
305
+ modeInfo && modeInfo.mode === 'feature' && modeInfo.featureSlug === slug
306
+ );
307
+ if (!existingIsActive || requestedIsActive) {
308
+ existing = null; // stale pointer — reseed for the requested feature
309
+ } else {
310
+ return {
311
+ existing: null,
312
+ refusal: {
313
+ ok: false,
314
+ reason: 'different_active_feature',
315
+ active_feature: existing.featureSlug,
316
+ active_stage: focusStage
317
+ }
318
+ };
319
+ }
265
320
  }
266
321
 
322
+ return { existing, refusal: null };
323
+ }
324
+
325
+ async function seedFeatureWorkflowState(targetDir, slug, classification, startFrom) {
326
+ const statePath = path.join(targetDir, STATE_RELATIVE_PATH);
327
+ const { existing, refusal } = await resolveExistingFeatureState(targetDir, slug);
328
+ if (refusal) return refusal;
329
+
267
330
  if (existing && existing.mode === 'feature' && existing.featureSlug === slug) {
268
331
  return {
269
332
  ok: true,
@@ -301,141 +364,126 @@ async function seedFeatureWorkflowState(targetDir, slug, classification, startFr
301
364
  detour: null,
302
365
  updatedAt: new Date().toISOString()
303
366
  };
304
-
305
- await writeJson(statePath, state);
306
- return {
307
- ok: true,
367
+
368
+ await writeJson(statePath, state);
369
+ return {
370
+ ok: true,
308
371
  resumed: false,
309
372
  state,
310
373
  statePath: STATE_RELATIVE_PATH
311
- };
312
- }
313
-
314
- async function previewFeatureWorkflowState(targetDir, slug, classification, startFrom) {
315
- const existing = await readJsonIfExists(path.join(targetDir, STATE_RELATIVE_PATH));
316
- const focusStage = getFocusStage(existing);
317
-
318
- if (
319
- existing &&
320
- existing.mode === 'feature' &&
321
- existing.featureSlug &&
322
- existing.featureSlug !== slug &&
323
- focusStage
324
- ) {
325
- return {
326
- ok: false,
327
- reason: 'different_active_feature',
328
- active_feature: existing.featureSlug,
329
- active_stage: focusStage
330
- };
331
- }
332
-
333
- if (existing && existing.mode === 'feature' && existing.featureSlug === slug) {
334
- return {
335
- ok: true,
336
- resumed: true,
337
- state: existing,
338
- statePath: STATE_RELATIVE_PATH
339
- };
340
- }
341
-
342
- const artifacts = await scanArtifacts(targetDir, slug);
343
- const gates = await readPhaseGates(targetDir, slug);
344
- const sequence = await resolveFeatureSequence(targetDir, classification);
345
- const completed = inferCompletedStagesFromArtifacts(sequence, artifacts, gates);
346
- const normalizedStartFrom = startFrom ? normalizeAgentName(startFrom) : null;
347
-
348
- let skipped = [];
349
- let next = findNextFromSequence(sequence, completed, skipped);
350
-
351
- if (normalizedStartFrom && sequence.includes(normalizedStartFrom)) {
352
- const targetIndex = sequence.indexOf(normalizedStartFrom);
353
- skipped = sequence.slice(0, targetIndex).filter((stage) => !completed.includes(stage));
354
- next = normalizedStartFrom;
355
- }
356
-
357
- return {
358
- ok: true,
359
- resumed: false,
360
- state: {
361
- version: 1,
362
- mode: 'feature',
363
- classification,
364
- sequence,
365
- current: null,
366
- next,
367
- completed,
368
- skipped,
369
- featureSlug: slug,
370
- detour: null,
371
- updatedAt: new Date().toISOString()
372
- },
373
- statePath: STATE_RELATIVE_PATH
374
- };
375
- }
376
-
377
- function buildDryRunSuggestion(planData) {
378
- const active = planData.steps.find((step) => step.status === 'active');
379
- if (active) {
380
- return {
381
- action: active.predicted_blockers.length > 0 ? 'complete_stage' : 'continue_stage',
382
- agent: active.agent,
383
- command: `aioson workflow:next . --complete=${active.agent}`,
384
- reason: `Preview stage @${active.agent}.`
385
- };
386
- }
387
-
388
- const next = planData.steps.find((step) => step.status !== 'completed' && step.status !== 'skipped');
389
- if (!next) {
390
- return {
391
- action: 'workflow_complete',
392
- agent: null,
393
- command: null,
394
- reason: 'Preview workflow has no pending stage.'
395
- };
396
- }
397
-
398
- return {
399
- action: next.status === 'blocked' ? 'blocked' : 'activate_stage',
400
- agent: next.agent,
401
- command: `aioson workflow:next . --agent=${next.agent}`,
402
- reason: next.status === 'blocked'
403
- ? `Preview stage @${next.agent} is blocked.`
404
- : `Preview next official stage is @${next.agent}.`
405
- };
406
- }
407
-
408
- function buildDryRunArtifactsSummary(artifacts) {
409
- return Object.entries(artifacts || {}).map(([id, artifact]) => ({
410
- id,
411
- label: artifact && artifact.path ? path.basename(artifact.path) : id,
412
- file: artifact && artifact.path ? artifact.path : null,
413
- exists: Boolean(artifact && artifact.exists)
414
- }));
415
- }
416
-
417
- function buildDryRunStatusSnapshot({ targetDir, slug, classification, workflowState, planData, tool }) {
418
- const suggestion = buildDryRunSuggestion(planData);
419
- return {
420
- ok: true,
421
- projectName: path.basename(targetDir),
422
- classification,
423
- mode: 'feature',
424
- featureSlug: slug,
425
- tool,
426
- state: workflowState,
427
- stateCreated: false,
428
- dryRun: true,
429
- activeStage: workflowState.current || workflowState.next || null,
430
- queuedNextStage: workflowState.next || null,
431
- pendingGates: [],
432
- contractCheck: null,
433
- artifacts: buildDryRunArtifactsSummary(planData.artifacts),
434
- suggestion
435
- };
436
- }
437
-
438
- async function buildExecutionPlan(targetDir, slug, classification, workflowState = null, startFrom = null) {
374
+ };
375
+ }
376
+
377
+ async function previewFeatureWorkflowState(targetDir, slug, classification, startFrom) {
378
+ const { existing, refusal } = await resolveExistingFeatureState(targetDir, slug);
379
+ if (refusal) return refusal;
380
+
381
+ if (existing && existing.mode === 'feature' && existing.featureSlug === slug) {
382
+ return {
383
+ ok: true,
384
+ resumed: true,
385
+ state: existing,
386
+ statePath: STATE_RELATIVE_PATH
387
+ };
388
+ }
389
+
390
+ const artifacts = await scanArtifacts(targetDir, slug);
391
+ const gates = await readPhaseGates(targetDir, slug);
392
+ const sequence = await resolveFeatureSequence(targetDir, classification);
393
+ const completed = inferCompletedStagesFromArtifacts(sequence, artifacts, gates);
394
+ const normalizedStartFrom = startFrom ? normalizeAgentName(startFrom) : null;
395
+
396
+ let skipped = [];
397
+ let next = findNextFromSequence(sequence, completed, skipped);
398
+
399
+ if (normalizedStartFrom && sequence.includes(normalizedStartFrom)) {
400
+ const targetIndex = sequence.indexOf(normalizedStartFrom);
401
+ skipped = sequence.slice(0, targetIndex).filter((stage) => !completed.includes(stage));
402
+ next = normalizedStartFrom;
403
+ }
404
+
405
+ return {
406
+ ok: true,
407
+ resumed: false,
408
+ state: {
409
+ version: 1,
410
+ mode: 'feature',
411
+ classification,
412
+ sequence,
413
+ current: null,
414
+ next,
415
+ completed,
416
+ skipped,
417
+ featureSlug: slug,
418
+ detour: null,
419
+ updatedAt: new Date().toISOString()
420
+ },
421
+ statePath: STATE_RELATIVE_PATH
422
+ };
423
+ }
424
+
425
+ function buildDryRunSuggestion(planData) {
426
+ const active = planData.steps.find((step) => step.status === 'active');
427
+ if (active) {
428
+ return {
429
+ action: active.predicted_blockers.length > 0 ? 'complete_stage' : 'continue_stage',
430
+ agent: active.agent,
431
+ command: `aioson workflow:next . --complete=${active.agent}`,
432
+ reason: `Preview stage @${active.agent}.`
433
+ };
434
+ }
435
+
436
+ const next = planData.steps.find((step) => step.status !== 'completed' && step.status !== 'skipped');
437
+ if (!next) {
438
+ return {
439
+ action: 'workflow_complete',
440
+ agent: null,
441
+ command: null,
442
+ reason: 'Preview workflow has no pending stage.'
443
+ };
444
+ }
445
+
446
+ return {
447
+ action: next.status === 'blocked' ? 'blocked' : 'activate_stage',
448
+ agent: next.agent,
449
+ command: `aioson workflow:next . --agent=${next.agent}`,
450
+ reason: next.status === 'blocked'
451
+ ? `Preview stage @${next.agent} is blocked.`
452
+ : `Preview next official stage is @${next.agent}.`
453
+ };
454
+ }
455
+
456
+ function buildDryRunArtifactsSummary(artifacts) {
457
+ return Object.entries(artifacts || {}).map(([id, artifact]) => ({
458
+ id,
459
+ label: artifact && artifact.path ? path.basename(artifact.path) : id,
460
+ file: artifact && artifact.path ? artifact.path : null,
461
+ exists: Boolean(artifact && artifact.exists)
462
+ }));
463
+ }
464
+
465
+ function buildDryRunStatusSnapshot({ targetDir, slug, classification, workflowState, planData, tool }) {
466
+ const suggestion = buildDryRunSuggestion(planData);
467
+ return {
468
+ ok: true,
469
+ projectName: path.basename(targetDir),
470
+ classification,
471
+ mode: 'feature',
472
+ featureSlug: slug,
473
+ tool,
474
+ state: workflowState,
475
+ stateCreated: false,
476
+ dryRun: true,
477
+ activeStage: workflowState.current || workflowState.next || null,
478
+ queuedNextStage: workflowState.next || null,
479
+ pendingGates: [],
480
+ contractCheck: null,
481
+ artifacts: buildDryRunArtifactsSummary(planData.artifacts),
482
+ suggestion
483
+ };
484
+ }
485
+
486
+ async function buildExecutionPlan(targetDir, slug, classification, workflowState = null, startFrom = null) {
439
487
  const sequence = workflowState && Array.isArray(workflowState.sequence) && workflowState.sequence.length > 0
440
488
  ? [...workflowState.sequence]
441
489
  : await resolveFeatureSequence(targetDir, classification);
@@ -472,11 +520,11 @@ async function buildExecutionPlan(targetDir, slug, classification, workflowState
472
520
  else if (focusStage === agent) status = 'active';
473
521
 
474
522
  const predictedBlockers = [];
475
- if (status !== 'completed' && status !== 'skipped' && meta.gate_before && !isGateApproved(gates, meta.gate_before)) {
476
- const responsible = GATE_RESPONSIBLE_AGENT[meta.gate_before] || 'previous agent';
477
- const featureArg = slug ? ` --feature=${quoteCliArg(slug)}` : '';
478
- predictedBlockers.push(
479
- `Gate ${meta.gate_before} (${GATE_NAMES[meta.gate_before] || meta.gate_before}) not approved — ` +
523
+ if (status !== 'completed' && status !== 'skipped' && meta.gate_before && !isGateApproved(gates, meta.gate_before)) {
524
+ const responsible = GATE_RESPONSIBLE_AGENT[meta.gate_before] || 'previous agent';
525
+ const featureArg = slug ? ` --feature=${quoteCliArg(slug)}` : '';
526
+ predictedBlockers.push(
527
+ `Gate ${meta.gate_before} (${GATE_NAMES[meta.gate_before] || meta.gate_before}) not approved — ` +
480
528
  `responsible: ${responsible} — ` +
481
529
  `approve with: aioson gate:approve .${featureArg} --gate=${meta.gate_before}`
482
530
  );
@@ -539,7 +587,11 @@ function buildCheckpointPayload(activation, handoff, handoffProtocol) {
539
587
  async function writeExecutionCheckpoint(targetDir, payload) {
540
588
  const execPath = path.join(targetDir, EXECUTION_STATE_RELATIVE_PATH);
541
589
  const existing = await readJsonIfExists(execPath);
542
- const history = Array.isArray(existing && existing.history) ? [...existing.history] : [];
590
+ // History is per-feature: never carry a previous feature's checkpoints into
591
+ // a new feature's scheme.
592
+ const history = existing && existing.feature === payload.feature && Array.isArray(existing.history)
593
+ ? [...existing.history]
594
+ : [];
543
595
  if (payload.checkpoint) {
544
596
  history.push({
545
597
  at: new Date().toISOString(),
@@ -565,10 +617,10 @@ async function writeExecutionCheckpoint(targetDir, payload) {
565
617
  checkpoint: payload.checkpoint || null,
566
618
  status_snapshot: payload.statusSnapshot || null,
567
619
  suggestion: payload.suggestion || null,
568
- resume_command: payload.resumeCommand || null,
569
- agentic_policy: payload.agenticPolicy || null,
570
- history
571
- };
620
+ resume_command: payload.resumeCommand || null,
621
+ agentic_policy: payload.agenticPolicy || null,
622
+ history
623
+ };
572
624
  await writeJson(execPath, nextPayload);
573
625
  return nextPayload;
574
626
  }
@@ -702,6 +754,9 @@ async function runWorkflowExecute({ args, options = {}, logger }) {
702
754
  const tool = options.tool ? String(options.tool).trim() : 'claude';
703
755
  const requestedMode = options.mode ? String(options.mode).trim() : null;
704
756
  const dryRun = Boolean(options['dry-run'] || options.dry);
757
+ // --step (disarm) is by definition record-only: it writes the disarmed scheme
758
+ // and never drives stage transitions, with or without an explicit --seed.
759
+ const seedOnly = Boolean(options.seed || options['seed-only'] || options.step);
705
760
  const startFrom = options['start-from'] ? String(options['start-from']).trim() : null;
706
761
  const skipOptional = Boolean(options['skip-optional']);
707
762
  const parsedMaxCheckpoints = Number.parseInt(String(options['max-checkpoints'] || '1'), 10);
@@ -718,11 +773,11 @@ async function runWorkflowExecute({ args, options = {}, logger }) {
718
773
  return { ok: false };
719
774
  }
720
775
 
721
- let classification = options.classification ? String(options.classification).toUpperCase() : null;
722
- if (!classification) classification = await detectClassification(targetDir, slug);
723
- if (!classification) classification = 'SMALL';
724
- classification = normalizeClassification(classification, 'SMALL');
725
- const agenticPolicy = buildAgenticPolicy(options, classification);
776
+ let classification = options.classification ? String(options.classification).toUpperCase() : null;
777
+ if (!classification) classification = await detectClassification(targetDir, slug);
778
+ if (!classification) classification = 'SMALL';
779
+ classification = normalizeClassification(classification, 'SMALL');
780
+ const agenticPolicy = buildAgenticPolicy(options, classification);
726
781
 
727
782
  const autonomyProtocol = await readAutonomyProtocol(targetDir);
728
783
  const toolPolicy = getToolPolicy(autonomyProtocol, tool);
@@ -756,9 +811,9 @@ async function runWorkflowExecute({ args, options = {}, logger }) {
756
811
  }
757
812
  }
758
813
 
759
- const seeded = dryRun
760
- ? await previewFeatureWorkflowState(targetDir, slug, classification, startFrom)
761
- : await seedFeatureWorkflowState(targetDir, slug, classification, startFrom);
814
+ const seeded = dryRun
815
+ ? await previewFeatureWorkflowState(targetDir, slug, classification, startFrom)
816
+ : await seedFeatureWorkflowState(targetDir, slug, classification, startFrom);
762
817
  if (!seeded.ok) {
763
818
  if (options.json) return seeded;
764
819
  logger.error(
@@ -781,28 +836,32 @@ async function runWorkflowExecute({ args, options = {}, logger }) {
781
836
 
782
837
  const activePlan = planData.steps.filter((step) => step.status !== 'completed' && !(skipOptional && step.optional));
783
838
  const blockedSteps = planData.steps.filter((step) => step.predicted_blockers.length > 0);
784
- const statusSnapshot = dryRun
785
- ? buildDryRunStatusSnapshot({ targetDir, slug, classification, workflowState: seeded.state, planData, tool })
786
- : await readStatusSnapshot(targetDir, tool, t);
787
- const resumeCommand = [
788
- 'aioson',
789
- 'workflow:execute',
790
- quoteCliArg(targetDir),
791
- `--feature=${quoteCliArg(slug)}`,
792
- `--tool=${quoteCliArg(tool)}`,
793
- ...(requestedMode ? [`--mode=${quoteCliArg(requestedMode)}`] : []),
794
- ...(maxCheckpoints !== 1 ? [`--max-checkpoints=${quoteCliArg(maxCheckpoints)}`] : []),
795
- ...(agenticPolicy ? ['--agentic'] : []),
796
- ...(agenticPolicy && agenticPolicy.review_cycle.max_dev_qa_cycles !== DEFAULT_AGENTIC_MAX_CYCLES
797
- ? [`--max-dev-qa-cycles=${quoteCliArg(agenticPolicy.review_cycle.max_dev_qa_cycles)}`]
798
- : []),
799
- ...(agenticPolicy && agenticPolicy.review_cycle.max_tester_correction_cycles !== DEFAULT_AGENTIC_MAX_CYCLES
800
- ? [`--max-tester-cycles=${quoteCliArg(agenticPolicy.review_cycle.max_tester_correction_cycles)}`]
801
- : []),
802
- ...(agenticPolicy && agenticPolicy.review_cycle.max_pentester_correction_cycles !== DEFAULT_AGENTIC_MAX_CYCLES
803
- ? [`--max-pentester-cycles=${quoteCliArg(agenticPolicy.review_cycle.max_pentester_correction_cycles)}`]
804
- : [])
805
- ].join(' ');
839
+ const statusSnapshot = dryRun
840
+ ? buildDryRunStatusSnapshot({ targetDir, slug, classification, workflowState: seeded.state, planData, tool })
841
+ : await readStatusSnapshot(targetDir, tool, t);
842
+ const resumeCommand = [
843
+ 'aioson',
844
+ 'workflow:execute',
845
+ quoteCliArg(targetDir),
846
+ `--feature=${quoteCliArg(slug)}`,
847
+ `--tool=${quoteCliArg(tool)}`,
848
+ ...(requestedMode ? [`--mode=${quoteCliArg(requestedMode)}`] : []),
849
+ ...(maxCheckpoints !== 1 ? [`--max-checkpoints=${quoteCliArg(maxCheckpoints)}`] : []),
850
+ // A seed run must resume as a seed run — `--agentic` (the CLI-advancing
851
+ // runner) is the opposite of the seed-only contract. A disarm run keeps
852
+ // its --step so replaying it never re-arms the scheme.
853
+ ...(seedOnly ? ['--seed'] : agenticPolicy && agenticPolicy.enabled ? ['--agentic'] : []),
854
+ ...(options.step ? ['--step'] : []),
855
+ ...(agenticPolicy && agenticPolicy.review_cycle && agenticPolicy.review_cycle.max_dev_qa_cycles !== DEFAULT_AGENTIC_MAX_CYCLES
856
+ ? [`--max-dev-qa-cycles=${quoteCliArg(agenticPolicy.review_cycle.max_dev_qa_cycles)}`]
857
+ : []),
858
+ ...(agenticPolicy && agenticPolicy.review_cycle && agenticPolicy.review_cycle.max_tester_correction_cycles !== DEFAULT_AGENTIC_MAX_CYCLES
859
+ ? [`--max-tester-cycles=${quoteCliArg(agenticPolicy.review_cycle.max_tester_correction_cycles)}`]
860
+ : []),
861
+ ...(agenticPolicy && agenticPolicy.review_cycle && agenticPolicy.review_cycle.max_pentester_correction_cycles !== DEFAULT_AGENTIC_MAX_CYCLES
862
+ ? [`--max-pentester-cycles=${quoteCliArg(agenticPolicy.review_cycle.max_pentester_correction_cycles)}`]
863
+ : [])
864
+ ].join(' ');
806
865
 
807
866
  if (dryRun) {
808
867
  const result = {
@@ -821,11 +880,11 @@ async function runWorkflowExecute({ args, options = {}, logger }) {
821
880
  blocked_steps: blockedSteps.length,
822
881
  gates: planData.gates,
823
882
  status_snapshot: statusSnapshot,
824
- suggestion: statusSnapshot && statusSnapshot.suggestion ? statusSnapshot.suggestion : null,
825
- resume_command: resumeCommand,
826
- agentic_policy: agenticPolicy,
827
- parallel_guard: parallelGuard
828
- };
883
+ suggestion: statusSnapshot && statusSnapshot.suggestion ? statusSnapshot.suggestion : null,
884
+ resume_command: resumeCommand,
885
+ agentic_policy: agenticPolicy,
886
+ parallel_guard: parallelGuard
887
+ };
829
888
 
830
889
  if (options.json) return result;
831
890
 
@@ -844,14 +903,73 @@ async function runWorkflowExecute({ args, options = {}, logger }) {
844
903
  logger.log('');
845
904
  logger.log(`Blocked steps: ${blockedSteps.length} | Remaining: ${activePlan.length}`);
846
905
  logger.log(`Resume state: ${seeded.resumed ? 'existing workflow state reused' : 'new workflow state seeded'}`);
847
- if (statusSnapshot && statusSnapshot.suggestion && statusSnapshot.suggestion.command) {
848
- logger.log(`Suggested command: ${statusSnapshot.suggestion.command}`);
849
- }
850
- for (const line of formatAgenticPolicyLines(agenticPolicy)) {
851
- logger.log(line);
852
- }
853
- logger.log('');
854
- return result;
906
+ if (statusSnapshot && statusSnapshot.suggestion && statusSnapshot.suggestion.command) {
907
+ logger.log(`Suggested command: ${statusSnapshot.suggestion.command}`);
908
+ }
909
+ for (const line of formatAgenticPolicyLines(agenticPolicy)) {
910
+ logger.log(line);
911
+ }
912
+ logger.log('');
913
+ return result;
914
+ }
915
+
916
+ // --seed: persist the workflow.state.json + workflow-execute.json (with an
917
+ // enabled agentic_policy) and STOP. Unlike a full run, it does not drive stage
918
+ // transitions from the CLI — in an interactive Claude Code session the agents
919
+ // themselves follow the scheme. This is what the spec->dev handoff calls so a
920
+ // feature built the normal way carries the autopilot contract without the user
921
+ // running anything. Idempotent: re-seeding the same slug reuses existing state.
922
+ if (seedOnly) {
923
+ const handoff = await readHandoff(targetDir);
924
+ const handoffProtocol = await readHandoffProtocol(targetDir);
925
+ const nextStage = seeded.state ? (seeded.state.current || seeded.state.next || null) : null;
926
+ const executionState = await writeExecutionCheckpoint(targetDir, {
927
+ feature: slug,
928
+ classification,
929
+ tool,
930
+ requestedMode,
931
+ resumed: seeded.resumed,
932
+ status: nextStage ? 'active' : 'completed',
933
+ checkpoint: buildCheckpointPayload(null, handoff, handoffProtocol),
934
+ statusSnapshot,
935
+ suggestion: statusSnapshot && statusSnapshot.suggestion ? statusSnapshot.suggestion : null,
936
+ resumeCommand,
937
+ agenticPolicy
938
+ });
939
+
940
+ const result = {
941
+ ok: true,
942
+ feature: slug,
943
+ classification,
944
+ tool,
945
+ requested_mode: requestedMode,
946
+ seeded: true,
947
+ resumed: seeded.resumed,
948
+ state_path: seeded.statePath,
949
+ execution_state_path: EXECUTION_STATE_RELATIVE_PATH,
950
+ next_stage: nextStage,
951
+ checkpoint: executionState.checkpoint,
952
+ execution_state: executionState,
953
+ status_snapshot: statusSnapshot,
954
+ suggestion: statusSnapshot && statusSnapshot.suggestion ? statusSnapshot.suggestion : null,
955
+ resume_command: resumeCommand,
956
+ agentic_policy: agenticPolicy,
957
+ parallel_guard: parallelGuard
958
+ };
959
+
960
+ if (options.json) return result;
961
+
962
+ logger.log('');
963
+ logger.log(`Agentic workflow scheme seeded → ${EXECUTION_STATE_RELATIVE_PATH}`);
964
+ logger.log(`Feature: ${slug} (${classification}) Next stage: @${nextStage || 'none'}`);
965
+ logger.log(
966
+ `Autopilot: ${agenticPolicy && agenticPolicy.enabled
967
+ ? 'enabled — interactive agents run the chain to feature:close (human gate)'
968
+ : 'disabled'}`
969
+ );
970
+ for (const line of formatAgenticPolicyLines(agenticPolicy)) logger.log(line);
971
+ logger.log('');
972
+ return result;
855
973
  }
856
974
 
857
975
  const executionTransitions = [];
@@ -931,11 +1049,11 @@ async function runWorkflowExecute({ args, options = {}, logger }) {
931
1049
  resumed: seeded.resumed,
932
1050
  status: activation && activation.agent ? 'active' : 'completed',
933
1051
  checkpoint: buildCheckpointPayload(activation, handoff, handoffProtocol),
934
- statusSnapshot: refreshedStatus,
935
- suggestion: refreshedStatus && refreshedStatus.suggestion ? refreshedStatus.suggestion : null,
936
- resumeCommand,
937
- agenticPolicy
938
- });
1052
+ statusSnapshot: refreshedStatus,
1053
+ suggestion: refreshedStatus && refreshedStatus.suggestion ? refreshedStatus.suggestion : null,
1054
+ resumeCommand,
1055
+ agenticPolicy
1056
+ });
939
1057
 
940
1058
  const result = {
941
1059
  ok: true,
@@ -950,10 +1068,10 @@ async function runWorkflowExecute({ args, options = {}, logger }) {
950
1068
  checkpoint: executionState.checkpoint,
951
1069
  execution_state: executionState,
952
1070
  status_snapshot: refreshedStatus,
953
- suggestion: refreshedStatus && refreshedStatus.suggestion ? refreshedStatus.suggestion : null,
954
- resume_command: resumeCommand,
955
- agentic_policy: agenticPolicy,
956
- transitions: executionTransitions,
1071
+ suggestion: refreshedStatus && refreshedStatus.suggestion ? refreshedStatus.suggestion : null,
1072
+ resume_command: resumeCommand,
1073
+ agentic_policy: agenticPolicy,
1074
+ transitions: executionTransitions,
957
1075
  active_stage: activation && activation.agent ? activation.agent : null,
958
1076
  next_stage: activation && activation.next ? activation.next : null,
959
1077
  handoff,
@@ -971,10 +1089,10 @@ async function runWorkflowExecute({ args, options = {}, logger }) {
971
1089
  return result;
972
1090
  }
973
1091
 
974
- module.exports = {
975
- EXECUTION_STATE_RELATIVE_PATH,
976
- buildAgenticPolicy,
977
- buildExecutionPlan,
978
- seedFeatureWorkflowState,
979
- runWorkflowExecute
980
- };
1092
+ module.exports = {
1093
+ EXECUTION_STATE_RELATIVE_PATH,
1094
+ buildAgenticPolicy,
1095
+ buildExecutionPlan,
1096
+ seedFeatureWorkflowState,
1097
+ runWorkflowExecute
1098
+ };