@jaimevalasek/aioson 1.30.2 → 1.36.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 (158) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/README.md +19 -6
  3. package/docs/en/1-understand/ecosystem-map.md +45 -29
  4. package/docs/en/1-understand/glossary.md +5 -5
  5. package/docs/en/1-understand/what-is-aioson.md +5 -5
  6. package/docs/en/2-start/existing-project.md +7 -7
  7. package/docs/en/2-start/first-project.md +32 -38
  8. package/docs/en/2-start/initial-decisions.md +18 -17
  9. package/docs/en/3-recipes/README.md +2 -2
  10. package/docs/en/3-recipes/continuity-between-sessions.md +2 -2
  11. package/docs/en/3-recipes/from-idea-to-prd-via-briefing.md +5 -2
  12. package/docs/en/3-recipes/full-feature-with-sheldon.md +327 -338
  13. package/docs/en/4-agents/README.md +28 -14
  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/cli-reference.md +51 -48
  17. package/docs/en/5-reference/executable-verification.md +10 -7
  18. package/docs/en/5-reference/parallel.md +2 -0
  19. package/docs/en/5-reference/qa-browser.md +2 -2
  20. package/docs/en/README.md +3 -3
  21. package/docs/pt/1-entender/glossario.md +4 -4
  22. package/docs/pt/1-entender/mapa-do-ecossistema.md +32 -21
  23. package/docs/pt/2-comecar/decisoes-iniciais.md +16 -16
  24. package/docs/pt/2-comecar/primeiro-projeto.md +1 -1
  25. package/docs/pt/3-receitas/README.md +1 -1
  26. package/docs/pt/3-receitas/app-saas-do-zero.md +35 -122
  27. package/docs/pt/3-receitas/feature-completa-com-sheldon.md +289 -338
  28. package/docs/pt/4-agentes/analyst.md +9 -5
  29. package/docs/pt/4-agentes/architect.md +9 -5
  30. package/docs/pt/4-agentes/dev.md +19 -7
  31. package/docs/pt/4-agentes/discovery-design-doc.md +25 -23
  32. package/docs/pt/4-agentes/orchestrator.md +164 -118
  33. package/docs/pt/4-agentes/pentester.md +5 -5
  34. package/docs/pt/4-agentes/pm.md +15 -7
  35. package/docs/pt/4-agentes/sheldon.md +166 -99
  36. package/docs/pt/4-agentes/ux-ui.md +7 -4
  37. package/docs/pt/5-referencia/agent-chain-continuity.md +14 -5
  38. package/docs/pt/5-referencia/comandos-cli.md +1 -1
  39. package/docs/pt/5-referencia/harness-retro.md +2 -1
  40. package/docs/pt/5-referencia/sdd-automation-scripts.md +6 -6
  41. package/docs/pt/5-referencia/sdd-framework.md +53 -16
  42. package/docs/pt/5-referencia/sdd-planos-e-estrutura.md +27 -15
  43. package/docs/pt/README.md +4 -4
  44. package/docs/pt/agentes.md +48 -50
  45. package/package.json +2 -2
  46. package/src/artifact-kinds.js +110 -0
  47. package/src/cli.js +82 -42
  48. package/src/commands/agent-epilogue.js +251 -186
  49. package/src/commands/agents.js +104 -48
  50. package/src/commands/audit-code.js +344 -0
  51. package/src/commands/classify.js +75 -13
  52. package/src/commands/feature-close.js +43 -18
  53. package/src/commands/harness-check.js +259 -175
  54. package/src/commands/harness-retro-promote.js +387 -0
  55. package/src/commands/live.js +24 -0
  56. package/src/commands/prototype-check.js +163 -0
  57. package/src/commands/review-feature.js +189 -0
  58. package/src/commands/runtime.js +81 -66
  59. package/src/commands/sync-agents-copy.js +115 -0
  60. package/src/commands/verification-plan.js +116 -0
  61. package/src/commands/verify-artifact.js +530 -0
  62. package/src/commands/verify-implementation.js +428 -0
  63. package/src/commands/workflow-execute.js +309 -309
  64. package/src/commands/workflow-next.js +361 -19
  65. package/src/commands/workflow-plan.js +5 -5
  66. package/src/constants.js +4 -0
  67. package/src/gateway-pointer-merge.js +7 -1
  68. package/src/handoff-contract.js +267 -172
  69. package/src/harness/contract-integrity-gate.js +172 -0
  70. package/src/harness/contract-integrity.js +111 -0
  71. package/src/harness/contract-schema.js +377 -332
  72. package/src/harness/detect-runtime-feature.js +90 -0
  73. package/src/harness/static-criteria.js +192 -0
  74. package/src/i18n/messages/en.js +8 -4
  75. package/src/i18n/messages/es.js +8 -4
  76. package/src/i18n/messages/fr.js +8 -4
  77. package/src/i18n/messages/pt-BR.js +8 -4
  78. package/src/install-wizard.js +8 -8
  79. package/src/installer.js +13 -6
  80. package/src/lib/retro/retro-render.js +10 -1
  81. package/src/lib/retro/retro-sources.js +45 -27
  82. package/src/lib/retro/verification-reports.js +230 -0
  83. package/src/parser.js +6 -0
  84. package/src/preflight-engine.js +12 -12
  85. package/src/runtime-store.js +13 -9
  86. package/src/verification/evidence-bundle.js +251 -0
  87. package/src/verification/ledger-store.js +221 -0
  88. package/src/verification/path-policy.js +74 -0
  89. package/src/verification/policy-engine.js +95 -0
  90. package/src/verification/prompt-package.js +314 -0
  91. package/src/verification/redaction.js +77 -0
  92. package/src/verification/report-parser.js +132 -0
  93. package/src/verification/report-store.js +97 -0
  94. package/src/verification/result.js +16 -0
  95. package/src/verification/runners/index.js +319 -0
  96. package/src/verification/runtime-telemetry.js +144 -0
  97. package/src/verification/schema.js +276 -0
  98. package/src/verification/source-discovery.js +153 -0
  99. package/src/verification-policy.js +398 -0
  100. package/src/version.js +52 -1
  101. package/template/.aioson/agents/analyst.md +9 -6
  102. package/template/.aioson/agents/architect.md +34 -5
  103. package/template/.aioson/agents/briefing-refiner.md +25 -0
  104. package/template/.aioson/agents/briefing.md +69 -12
  105. package/template/.aioson/agents/committer.md +2 -1
  106. package/template/.aioson/agents/copywriter.md +30 -21
  107. package/template/.aioson/agents/design-hybrid-forge.md +28 -15
  108. package/template/.aioson/agents/dev.md +28 -10
  109. package/template/.aioson/agents/deyvin.md +3 -2
  110. package/template/.aioson/agents/discover.md +12 -3
  111. package/template/.aioson/agents/discovery-design-doc.md +7 -2
  112. package/template/.aioson/agents/genome.md +19 -10
  113. package/template/.aioson/agents/neo.md +30 -30
  114. package/template/.aioson/agents/orache.md +20 -11
  115. package/template/.aioson/agents/orchestrator.md +84 -7
  116. package/template/.aioson/agents/pm.md +8 -8
  117. package/template/.aioson/agents/product.md +15 -11
  118. package/template/.aioson/agents/profiler-enricher.md +20 -11
  119. package/template/.aioson/agents/profiler-forge.md +20 -11
  120. package/template/.aioson/agents/profiler-researcher.md +20 -11
  121. package/template/.aioson/agents/qa.md +87 -17
  122. package/template/.aioson/agents/scope-check.md +19 -5
  123. package/template/.aioson/agents/setup.md +12 -1
  124. package/template/.aioson/agents/sheldon.md +92 -9
  125. package/template/.aioson/agents/site-forge.md +11 -2
  126. package/template/.aioson/agents/squad.md +20 -5
  127. package/template/.aioson/agents/ux-ui.md +4 -2
  128. package/template/.aioson/agents/validator.md +33 -1
  129. package/template/.aioson/config/verification.json +61 -0
  130. package/template/.aioson/config.md +13 -9
  131. package/template/.aioson/docs/LAYERS.md +2 -2
  132. package/template/.aioson/docs/autopilot-handoff.md +10 -10
  133. package/template/.aioson/docs/dev/execution-discipline.md +41 -0
  134. package/template/.aioson/docs/dev/phase-loop.md +47 -0
  135. package/template/.aioson/docs/feature-expansion-taxonomy.md +31 -3
  136. package/template/.aioson/docs/presets/workflow.config.full-merged.json +15 -0
  137. package/template/.aioson/docs/presets/workflow.config.lean.json +15 -0
  138. package/template/.aioson/docs/product/prd-contract.md +12 -12
  139. package/template/.aioson/docs/prototype-contract.md +98 -0
  140. package/template/.aioson/docs/reference-identity.md +94 -0
  141. package/template/.aioson/docs/sheldon/harness-contract.md +155 -28
  142. package/template/.aioson/docs/verification-config.md +82 -0
  143. package/template/.aioson/docs/verify-artifact-gates.md +91 -0
  144. package/template/.aioson/docs/workflow-lean-lane.md +129 -0
  145. package/template/.aioson/skills/design/interface-design/references/intent-and-domain.md +2 -0
  146. package/template/.aioson/skills/process/aioson-spec-driven/SKILL.md +16 -14
  147. package/template/.aioson/skills/process/aioson-spec-driven/references/approval-gates.md +2 -2
  148. package/template/.aioson/skills/process/aioson-spec-driven/references/classification-map.md +4 -4
  149. package/template/.aioson/skills/process/aioson-spec-driven/references/dev.md +15 -15
  150. package/template/.aioson/skills/process/briefing-expansion-scout/SKILL.md +25 -3
  151. package/template/.aioson/skills/process/design-hybrid-forge/SKILL.md +5 -3
  152. package/template/.aioson/skills/process/design-hybrid-forge/references/external-source-ingestion.md +89 -0
  153. package/template/.aioson/skills/process/product-scope-expansion/SKILL.md +29 -2
  154. package/template/.aioson/skills/process/prototype-forge/SKILL.md +98 -0
  155. package/template/.aioson/skills/process/reference-identity-extract/SKILL.md +164 -0
  156. package/template/.aioson/skills/process/sheldon-expansion-audit/SKILL.md +23 -2
  157. package/template/.aioson/skills/static/multi-agent-patterns.md +5 -5
  158. package/template/CLAUDE.md +15 -11
@@ -37,17 +37,17 @@ const {
37
37
  extractStatusWritePathItems
38
38
  } = require('../parallel-workspace');
39
39
 
40
- const BAR = '━'.repeat(45);
41
- const EXECUTION_STATE_RELATIVE_PATH = '.aioson/context/workflow-execute.json';
42
- const DEFAULT_AGENTIC_MAX_CYCLES = 3;
40
+ const BAR = '━'.repeat(45);
41
+ const EXECUTION_STATE_RELATIVE_PATH = '.aioson/context/workflow-execute.json';
42
+ const DEFAULT_AGENTIC_MAX_CYCLES = 3;
43
43
 
44
44
  const STEP_META = {
45
45
  setup: { description: 'Initialize project context', gate_before: null, gate_after: null },
46
46
  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 },
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 },
51
51
  pm: { description: 'Backlog + PM plan', gate_before: 'B', gate_after: 'C' },
52
52
  orchestrator: { description: 'Coordinate execution lanes', gate_before: 'C', gate_after: null },
53
53
  dev: { description: 'Implementation', gate_before: null, gate_after: 'C' },
@@ -89,105 +89,105 @@ function normalizeClassification(value, fallback = 'SMALL') {
89
89
  return ['MICRO', 'SMALL', 'MEDIUM'].includes(safe) ? safe : fallback;
90
90
  }
91
91
 
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 = []) {
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 = []) {
191
191
  const done = new Set([...(completed || []), ...(skipped || [])].map(normalizeAgentName));
192
192
  return sequence.find((stage) => !done.has(normalizeAgentName(stage))) || null;
193
193
  }
@@ -244,9 +244,9 @@ function inferCompletedStagesFromArtifacts(sequence, artifacts, gates) {
244
244
  return completed;
245
245
  }
246
246
 
247
- async function seedFeatureWorkflowState(targetDir, slug, classification, startFrom) {
248
- const statePath = path.join(targetDir, STATE_RELATIVE_PATH);
249
- const existing = await readJsonIfExists(statePath);
247
+ async function seedFeatureWorkflowState(targetDir, slug, classification, startFrom) {
248
+ const statePath = path.join(targetDir, STATE_RELATIVE_PATH);
249
+ const existing = await readJsonIfExists(statePath);
250
250
  const focusStage = getFocusStage(existing);
251
251
 
252
252
  if (
@@ -301,141 +301,141 @@ async function seedFeatureWorkflowState(targetDir, slug, classification, startFr
301
301
  detour: null,
302
302
  updatedAt: new Date().toISOString()
303
303
  };
304
-
305
- await writeJson(statePath, state);
306
- return {
307
- ok: true,
304
+
305
+ await writeJson(statePath, state);
306
+ return {
307
+ ok: true,
308
308
  resumed: false,
309
309
  state,
310
310
  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) {
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) {
439
439
  const sequence = workflowState && Array.isArray(workflowState.sequence) && workflowState.sequence.length > 0
440
440
  ? [...workflowState.sequence]
441
441
  : await resolveFeatureSequence(targetDir, classification);
@@ -472,11 +472,11 @@ async function buildExecutionPlan(targetDir, slug, classification, workflowState
472
472
  else if (focusStage === agent) status = 'active';
473
473
 
474
474
  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 — ` +
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 — ` +
480
480
  `responsible: ${responsible} — ` +
481
481
  `approve with: aioson gate:approve .${featureArg} --gate=${meta.gate_before}`
482
482
  );
@@ -565,10 +565,10 @@ async function writeExecutionCheckpoint(targetDir, payload) {
565
565
  checkpoint: payload.checkpoint || null,
566
566
  status_snapshot: payload.statusSnapshot || null,
567
567
  suggestion: payload.suggestion || null,
568
- resume_command: payload.resumeCommand || null,
569
- agentic_policy: payload.agenticPolicy || null,
570
- history
571
- };
568
+ resume_command: payload.resumeCommand || null,
569
+ agentic_policy: payload.agenticPolicy || null,
570
+ history
571
+ };
572
572
  await writeJson(execPath, nextPayload);
573
573
  return nextPayload;
574
574
  }
@@ -718,11 +718,11 @@ async function runWorkflowExecute({ args, options = {}, logger }) {
718
718
  return { ok: false };
719
719
  }
720
720
 
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);
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);
726
726
 
727
727
  const autonomyProtocol = await readAutonomyProtocol(targetDir);
728
728
  const toolPolicy = getToolPolicy(autonomyProtocol, tool);
@@ -756,9 +756,9 @@ async function runWorkflowExecute({ args, options = {}, logger }) {
756
756
  }
757
757
  }
758
758
 
759
- const seeded = dryRun
760
- ? await previewFeatureWorkflowState(targetDir, slug, classification, startFrom)
761
- : await seedFeatureWorkflowState(targetDir, slug, classification, startFrom);
759
+ const seeded = dryRun
760
+ ? await previewFeatureWorkflowState(targetDir, slug, classification, startFrom)
761
+ : await seedFeatureWorkflowState(targetDir, slug, classification, startFrom);
762
762
  if (!seeded.ok) {
763
763
  if (options.json) return seeded;
764
764
  logger.error(
@@ -781,28 +781,28 @@ async function runWorkflowExecute({ args, options = {}, logger }) {
781
781
 
782
782
  const activePlan = planData.steps.filter((step) => step.status !== 'completed' && !(skipOptional && step.optional));
783
783
  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(' ');
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(' ');
806
806
 
807
807
  if (dryRun) {
808
808
  const result = {
@@ -821,11 +821,11 @@ async function runWorkflowExecute({ args, options = {}, logger }) {
821
821
  blocked_steps: blockedSteps.length,
822
822
  gates: planData.gates,
823
823
  status_snapshot: statusSnapshot,
824
- suggestion: statusSnapshot && statusSnapshot.suggestion ? statusSnapshot.suggestion : null,
825
- resume_command: resumeCommand,
826
- agentic_policy: agenticPolicy,
827
- parallel_guard: parallelGuard
828
- };
824
+ suggestion: statusSnapshot && statusSnapshot.suggestion ? statusSnapshot.suggestion : null,
825
+ resume_command: resumeCommand,
826
+ agentic_policy: agenticPolicy,
827
+ parallel_guard: parallelGuard
828
+ };
829
829
 
830
830
  if (options.json) return result;
831
831
 
@@ -844,14 +844,14 @@ async function runWorkflowExecute({ args, options = {}, logger }) {
844
844
  logger.log('');
845
845
  logger.log(`Blocked steps: ${blockedSteps.length} | Remaining: ${activePlan.length}`);
846
846
  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;
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;
855
855
  }
856
856
 
857
857
  const executionTransitions = [];
@@ -931,11 +931,11 @@ async function runWorkflowExecute({ args, options = {}, logger }) {
931
931
  resumed: seeded.resumed,
932
932
  status: activation && activation.agent ? 'active' : 'completed',
933
933
  checkpoint: buildCheckpointPayload(activation, handoff, handoffProtocol),
934
- statusSnapshot: refreshedStatus,
935
- suggestion: refreshedStatus && refreshedStatus.suggestion ? refreshedStatus.suggestion : null,
936
- resumeCommand,
937
- agenticPolicy
938
- });
934
+ statusSnapshot: refreshedStatus,
935
+ suggestion: refreshedStatus && refreshedStatus.suggestion ? refreshedStatus.suggestion : null,
936
+ resumeCommand,
937
+ agenticPolicy
938
+ });
939
939
 
940
940
  const result = {
941
941
  ok: true,
@@ -950,10 +950,10 @@ async function runWorkflowExecute({ args, options = {}, logger }) {
950
950
  checkpoint: executionState.checkpoint,
951
951
  execution_state: executionState,
952
952
  status_snapshot: refreshedStatus,
953
- suggestion: refreshedStatus && refreshedStatus.suggestion ? refreshedStatus.suggestion : null,
954
- resume_command: resumeCommand,
955
- agentic_policy: agenticPolicy,
956
- transitions: executionTransitions,
953
+ suggestion: refreshedStatus && refreshedStatus.suggestion ? refreshedStatus.suggestion : null,
954
+ resume_command: resumeCommand,
955
+ agentic_policy: agenticPolicy,
956
+ transitions: executionTransitions,
957
957
  active_stage: activation && activation.agent ? activation.agent : null,
958
958
  next_stage: activation && activation.next ? activation.next : null,
959
959
  handoff,
@@ -971,10 +971,10 @@ async function runWorkflowExecute({ args, options = {}, logger }) {
971
971
  return result;
972
972
  }
973
973
 
974
- module.exports = {
975
- EXECUTION_STATE_RELATIVE_PATH,
976
- buildAgenticPolicy,
977
- buildExecutionPlan,
978
- seedFeatureWorkflowState,
979
- runWorkflowExecute
980
- };
974
+ module.exports = {
975
+ EXECUTION_STATE_RELATIVE_PATH,
976
+ buildAgenticPolicy,
977
+ buildExecutionPlan,
978
+ seedFeatureWorkflowState,
979
+ runWorkflowExecute
980
+ };