@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
@@ -8,9 +8,10 @@
8
8
  * sessions early instead of discovering missing artifacts later.
9
9
  */
10
10
 
11
- const path = require('node:path');
12
- const { readFileSafe, fileExists } = require('./preflight-engine');
13
- const { auditAcceptanceCriteriaTests } = require('./lib/ac-test-audit');
11
+ const path = require('node:path');
12
+ const { readFileSafe, fileExists } = require('./preflight-engine');
13
+ const { auditAcceptanceCriteriaTests } = require('./lib/ac-test-audit');
14
+ const { evaluateContractIntegrityGate } = require('./harness/contract-integrity-gate');
14
15
 
15
16
  // Contract definitions per agent stage
16
17
  const CONTRACTS = {
@@ -29,48 +30,68 @@ const CONTRACTS = {
29
30
  gates: [],
30
31
  contextUpdates: ['.aioson/context/project-pulse.md']
31
32
  },
32
- analyst: {
33
- artifacts: (targetDir, state) => {
34
- if (state.mode === 'feature' && state.featureSlug) {
35
- return [
36
- `.aioson/context/requirements-${state.featureSlug}.md`,
33
+ sheldon: {
34
+ artifacts: (targetDir, state) => {
35
+ if (state.mode !== 'feature' || !state.featureSlug) {
36
+ return ['.aioson/context/sheldon-enrichment.md'];
37
+ }
38
+ const slug = state.featureSlug;
39
+ const base = [`.aioson/context/sheldon-enrichment-${slug}.md`];
40
+ if (!isLeanSheldonState(state)) return base;
41
+ return [
42
+ ...base,
43
+ `.aioson/context/requirements-${slug}.md`,
44
+ `.aioson/context/spec-${slug}.md`,
45
+ `.aioson/context/design-doc-${slug}.md`,
46
+ `.aioson/context/readiness-${slug}.md`,
47
+ `.aioson/context/implementation-plan-${slug}.md`
48
+ ];
49
+ },
50
+ gates: [],
51
+ contextUpdates: []
52
+ },
53
+ analyst: {
54
+ artifacts: (targetDir, state) => {
55
+ if (state.mode === 'feature' && state.featureSlug) {
56
+ return [
57
+ `.aioson/context/requirements-${state.featureSlug}.md`,
37
58
  `.aioson/context/spec-${state.featureSlug}.md`
38
59
  ];
39
60
  }
40
61
  return ['.aioson/context/discovery.md'];
41
62
  },
42
- gates: ['A'], // Gate A must be approved
43
- contextUpdates: ['.aioson/context/project-pulse.md']
44
- },
45
- 'scope-check': {
46
- artifacts: (targetDir, state) => {
47
- if (state.mode === 'feature' && state.featureSlug) {
48
- return [`.aioson/context/scope-check-${state.featureSlug}.md`];
49
- }
50
- return ['.aioson/context/scope-check.md'];
51
- },
52
- gates: [],
53
- contextUpdates: ['.aioson/context/project-pulse.md']
54
- },
55
- architect: {
56
- artifacts: ['.aioson/context/architecture.md'],
57
- gates: ['B'],
58
- contextUpdates: ['.aioson/context/project-pulse.md']
59
- },
60
- 'discovery-design-doc': {
61
- artifacts: (targetDir, state) => {
62
- if (state.mode === 'feature' && state.featureSlug) {
63
- return [
64
- `.aioson/context/design-doc-${state.featureSlug}.md`,
65
- `.aioson/context/readiness-${state.featureSlug}.md`
66
- ];
67
- }
68
- return ['.aioson/context/design-doc.md', '.aioson/context/readiness.md'];
69
- },
70
- gates: [],
71
- contextUpdates: ['.aioson/context/project-pulse.md']
72
- },
73
- 'ux-ui': {
63
+ gates: ['A'], // Gate A must be approved
64
+ contextUpdates: ['.aioson/context/project-pulse.md']
65
+ },
66
+ 'scope-check': {
67
+ artifacts: (targetDir, state) => {
68
+ if (state.mode === 'feature' && state.featureSlug) {
69
+ return [`.aioson/context/scope-check-${state.featureSlug}.md`];
70
+ }
71
+ return ['.aioson/context/scope-check.md'];
72
+ },
73
+ gates: [],
74
+ contextUpdates: ['.aioson/context/project-pulse.md']
75
+ },
76
+ architect: {
77
+ artifacts: ['.aioson/context/architecture.md'],
78
+ gates: ['B'],
79
+ contextUpdates: ['.aioson/context/project-pulse.md']
80
+ },
81
+ 'discovery-design-doc': {
82
+ artifacts: (targetDir, state) => {
83
+ if (state.mode === 'feature' && state.featureSlug) {
84
+ return [
85
+ `.aioson/context/design-doc-${state.featureSlug}.md`,
86
+ `.aioson/context/readiness-${state.featureSlug}.md`
87
+ ];
88
+ }
89
+ return ['.aioson/context/design-doc.md', '.aioson/context/readiness.md'];
90
+ },
91
+ gates: [],
92
+ contextUpdates: ['.aioson/context/project-pulse.md']
93
+ },
94
+ 'ux-ui': {
74
95
  artifacts: ['.aioson/context/ui-spec.md'],
75
96
  gates: ['B'],
76
97
  contextUpdates: ['.aioson/context/project-pulse.md']
@@ -87,7 +108,22 @@ const CONTRACTS = {
87
108
  contextUpdates: ['.aioson/context/project-pulse.md']
88
109
  },
89
110
  orchestrator: {
90
- artifacts: ['.aioson/context/parallel'],
111
+ artifacts: (targetDir, state) => {
112
+ // Maestro lane (MEDIUM, orchestrator → dev): the orchestrator produces the
113
+ // gated spec package via fan-out, so expect those artifacts. Otherwise it is
114
+ // the parallel-implementation coordinator and owns the lane workspace.
115
+ if (state && state.mode === 'feature' && state.featureSlug && isMaestroOrchestratorState(state)) {
116
+ const slug = state.featureSlug;
117
+ return [
118
+ `.aioson/context/requirements-${slug}.md`,
119
+ `.aioson/context/spec-${slug}.md`,
120
+ `.aioson/context/design-doc-${slug}.md`,
121
+ `.aioson/context/readiness-${slug}.md`,
122
+ `.aioson/context/implementation-plan-${slug}.md`
123
+ ];
124
+ }
125
+ return ['.aioson/context/parallel'];
126
+ },
91
127
  gates: [],
92
128
  contextUpdates: ['.aioson/context/project-pulse.md']
93
129
  },
@@ -123,6 +159,27 @@ const CONTRACTS = {
123
159
  }
124
160
  };
125
161
 
162
+ function normalizeAgentName(input) {
163
+ return String(input || '')
164
+ .trim()
165
+ .toLowerCase()
166
+ .replace(/^@/, '');
167
+ }
168
+
169
+ function isLeanSheldonState(state) {
170
+ const sequence = Array.isArray(state?.sequence) ? state.sequence.map(normalizeAgentName) : [];
171
+ const sheldonIndex = sequence.indexOf('sheldon');
172
+ return sheldonIndex !== -1 && sequence[sheldonIndex + 1] === 'dev';
173
+ }
174
+
175
+ // MEDIUM maestro lane: @orchestrator routes straight to @dev (it is the single
176
+ // spec authority that fans out to analyst/architect/pm sub-agents and consolidates).
177
+ function isMaestroOrchestratorState(state) {
178
+ const sequence = Array.isArray(state?.sequence) ? state.sequence.map(normalizeAgentName) : [];
179
+ const idx = sequence.indexOf('orchestrator');
180
+ return idx !== -1 && sequence[idx + 1] === 'dev';
181
+ }
182
+
126
183
  async function readSecurityFindings(findingsPath) {
127
184
  try {
128
185
  const content = await readFileSafe(findingsPath);
@@ -173,48 +230,48 @@ function getFindingIdentifier(finding) {
173
230
  return 'unknown-finding';
174
231
  }
175
232
 
176
- async function resolveArtifacts(contract, targetDir, state) {
177
- const raw = typeof contract.artifacts === 'function'
178
- ? contract.artifacts(targetDir, state)
179
- : contract.artifacts;
180
- return raw.map((p) => path.join(targetDir, p));
181
- }
182
-
183
- async function validateDiscoveryDesignDocArtifacts(targetDir, state) {
184
- const slug = state?.mode === 'feature' && state?.featureSlug ? state.featureSlug : null;
185
- const pairs = slug
186
- ? [
187
- [
188
- `.aioson/context/design-doc-${slug}.md`,
189
- `.aioson/context/readiness-${slug}.md`
190
- ],
191
- [
192
- '.aioson/context/design-doc.md',
193
- '.aioson/context/readiness.md'
194
- ]
195
- ]
196
- : [
197
- [
198
- '.aioson/context/design-doc.md',
199
- '.aioson/context/readiness.md'
200
- ]
201
- ];
202
-
203
- for (const pair of pairs) {
204
- const [designDoc, readiness] = pair;
205
- if (
206
- await fileExists(path.join(targetDir, designDoc)) &&
207
- await fileExists(path.join(targetDir, readiness))
208
- ) {
209
- return [];
210
- }
211
- }
212
-
213
- const expected = slug
214
- ? `.aioson/context/design-doc-${slug}.md + .aioson/context/readiness-${slug}.md`
215
- : '.aioson/context/design-doc.md + .aioson/context/readiness.md';
216
- return [`missing artifact: ${expected}`];
217
- }
233
+ async function resolveArtifacts(contract, targetDir, state) {
234
+ const raw = typeof contract.artifacts === 'function'
235
+ ? contract.artifacts(targetDir, state)
236
+ : contract.artifacts;
237
+ return raw.map((p) => path.join(targetDir, p));
238
+ }
239
+
240
+ async function validateDiscoveryDesignDocArtifacts(targetDir, state) {
241
+ const slug = state?.mode === 'feature' && state?.featureSlug ? state.featureSlug : null;
242
+ const pairs = slug
243
+ ? [
244
+ [
245
+ `.aioson/context/design-doc-${slug}.md`,
246
+ `.aioson/context/readiness-${slug}.md`
247
+ ],
248
+ [
249
+ '.aioson/context/design-doc.md',
250
+ '.aioson/context/readiness.md'
251
+ ]
252
+ ]
253
+ : [
254
+ [
255
+ '.aioson/context/design-doc.md',
256
+ '.aioson/context/readiness.md'
257
+ ]
258
+ ];
259
+
260
+ for (const pair of pairs) {
261
+ const [designDoc, readiness] = pair;
262
+ if (
263
+ await fileExists(path.join(targetDir, designDoc)) &&
264
+ await fileExists(path.join(targetDir, readiness))
265
+ ) {
266
+ return [];
267
+ }
268
+ }
269
+
270
+ const expected = slug
271
+ ? `.aioson/context/design-doc-${slug}.md + .aioson/context/readiness-${slug}.md`
272
+ : '.aioson/context/design-doc.md + .aioson/context/readiness.md';
273
+ return [`missing artifact: ${expected}`];
274
+ }
218
275
 
219
276
  function parseFrontmatterValue(markdown, key) {
220
277
  const fmMatch = String(markdown || '').match(/^---\r?\n([\s\S]*?)\r?\n---/);
@@ -288,49 +345,49 @@ async function checkGateApproval(targetDir, gateLetter, slug, classification, pr
288
345
  return { ok: false, reason: 'spec_missing' };
289
346
  }
290
347
 
291
- const gateNames = { A: 'requirements', B: 'design', C: 'plan', D: 'execution' };
292
- const gateName = gateNames[gateLetter];
293
-
294
- // Parse frontmatter when present. Explicit gate fields take precedence over
295
- // free-text gate lines so `gate_requirements: pending` cannot be overridden
296
- // accidentally by stale prose elsewhere in the spec.
297
- const fmMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
298
- if (fmMatch) {
299
- const fm = {};
300
- for (const line of fmMatch[1].split(/\r?\n/)) {
301
- const idx = line.indexOf(':');
302
- if (idx === -1) continue;
303
- const key = line.slice(0, idx).trim();
304
- const val = line.slice(idx + 1).trim().replace(/^["']|["']$/g, '');
305
- if (key) fm[key] = val;
306
- }
307
-
308
- // Check explicit gate field
309
- const gateVal = fm[`gate_${gateName}`] || fm[`gate${gateLetter}`] || fm[`gate_${gateLetter}`];
310
- if (gateVal) {
311
- return gateVal.toLowerCase() === 'approved'
312
- ? { ok: true }
313
- : { ok: false, reason: `gate_${gateName}_not_approved` };
314
- }
315
-
316
- // Check phase_gates JSON
317
- if (fm.phase_gates) {
318
- try {
319
- const parsed = JSON.parse(fm.phase_gates.replace(/'/g, '"'));
320
- if (parsed[gateName]) {
321
- return parsed[gateName] === 'approved'
322
- ? { ok: true }
323
- : { ok: false, reason: `gate_${gateName}_not_approved` };
324
- }
325
- } catch {
326
- // ignore
327
- }
328
- }
329
- }
330
-
331
- // Check content for gate approval lines
332
- const gateLineRe = new RegExp(`gate\\s+${gateLetter}[^:]*:\\s*approved`, 'i');
333
- if (gateLineRe.test(content)) {
348
+ const gateNames = { A: 'requirements', B: 'design', C: 'plan', D: 'execution' };
349
+ const gateName = gateNames[gateLetter];
350
+
351
+ // Parse frontmatter when present. Explicit gate fields take precedence over
352
+ // free-text gate lines so `gate_requirements: pending` cannot be overridden
353
+ // accidentally by stale prose elsewhere in the spec.
354
+ const fmMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
355
+ if (fmMatch) {
356
+ const fm = {};
357
+ for (const line of fmMatch[1].split(/\r?\n/)) {
358
+ const idx = line.indexOf(':');
359
+ if (idx === -1) continue;
360
+ const key = line.slice(0, idx).trim();
361
+ const val = line.slice(idx + 1).trim().replace(/^["']|["']$/g, '');
362
+ if (key) fm[key] = val;
363
+ }
364
+
365
+ // Check explicit gate field
366
+ const gateVal = fm[`gate_${gateName}`] || fm[`gate${gateLetter}`] || fm[`gate_${gateLetter}`];
367
+ if (gateVal) {
368
+ return gateVal.toLowerCase() === 'approved'
369
+ ? { ok: true }
370
+ : { ok: false, reason: `gate_${gateName}_not_approved` };
371
+ }
372
+
373
+ // Check phase_gates JSON
374
+ if (fm.phase_gates) {
375
+ try {
376
+ const parsed = JSON.parse(fm.phase_gates.replace(/'/g, '"'));
377
+ if (parsed[gateName]) {
378
+ return parsed[gateName] === 'approved'
379
+ ? { ok: true }
380
+ : { ok: false, reason: `gate_${gateName}_not_approved` };
381
+ }
382
+ } catch {
383
+ // ignore
384
+ }
385
+ }
386
+ }
387
+
388
+ // Check content for gate approval lines
389
+ const gateLineRe = new RegExp(`gate\\s+${gateLetter}[^:]*:\\s*approved`, 'i');
390
+ if (gateLineRe.test(content)) {
334
391
  return { ok: true };
335
392
  }
336
393
 
@@ -340,12 +397,12 @@ async function checkGateApproval(targetDir, gateLetter, slug, classification, pr
340
397
  const passMatch = content.match(/\*\*Verdict:\*\*\s*(PASS)/i);
341
398
  if (passMatch) {
342
399
  return { ok: true };
343
- }
344
- }
345
- }
346
-
347
- return { ok: false, reason: fmMatch ? `gate_${gateName}_not_approved` : 'no_frontmatter' };
348
- }
400
+ }
401
+ }
402
+ }
403
+
404
+ return { ok: false, reason: fmMatch ? `gate_${gateName}_not_approved` : 'no_frontmatter' };
405
+ }
349
406
 
350
407
  async function validateHandoffContract(targetDir, state, stageName) {
351
408
  const contract = CONTRACTS[stageName];
@@ -362,19 +419,19 @@ async function validateHandoffContract(targetDir, state, stageName) {
362
419
  const projectClassification = await readProjectClassification(targetDir);
363
420
 
364
421
  // 1. Artifacts
365
- if (stageName === 'discovery-design-doc') {
366
- missing.push(...await validateDiscoveryDesignDocArtifacts(targetDir, state));
367
- } else {
368
- const artifactPaths = await resolveArtifacts(contract, targetDir, state);
369
- for (const p of artifactPaths) {
370
- if (!(await fileExists(p))) {
371
- missing.push(`missing artifact: ${path.relative(targetDir, p)}`);
372
- }
373
- }
374
- }
422
+ if (stageName === 'discovery-design-doc') {
423
+ missing.push(...await validateDiscoveryDesignDocArtifacts(targetDir, state));
424
+ } else {
425
+ const artifactPaths = await resolveArtifacts(contract, targetDir, state);
426
+ for (const p of artifactPaths) {
427
+ if (!(await fileExists(p))) {
428
+ missing.push(`missing artifact: ${path.relative(targetDir, p)}`);
429
+ }
430
+ }
431
+ }
375
432
 
376
433
  // 2. Gates
377
- for (const gateLetter of contract.gates) {
434
+ for (const gateLetter of contract.gates) {
378
435
  const gateCheck = await checkGateApproval(
379
436
  targetDir,
380
437
  gateLetter,
@@ -385,16 +442,54 @@ async function validateHandoffContract(targetDir, state, stageName) {
385
442
  if (!gateCheck.ok) {
386
443
  missing.push(`gate ${gateLetter} not approved (${gateCheck.reason})`);
387
444
  }
388
- }
389
-
390
- if (stageName === 'qa' && state.featureSlug) {
391
- const acAudit = await auditAcceptanceCriteriaTests(targetDir, state.featureSlug);
392
- if (!acAudit.ok) {
393
- missing.push(`AC test audit failed: missing tests for ${acAudit.missing.join(', ')}`);
394
- }
395
- }
396
-
397
- // 3. Context updates (soft check — just warn if completely missing)
445
+ }
446
+
447
+ // Single-spec-authority lanes that route straight to @dev — lean @sheldon (SMALL)
448
+ // and maestro @orchestrator (MEDIUM) — collapse Gates A/B/C + the plan into one
449
+ // hop, so re-check them here instead of at the (absent) per-hop stages.
450
+ const isSingleSpecAuthorityToDev = Boolean(state.featureSlug) && (
451
+ (stageName === 'sheldon' && isLeanSheldonState(state)) ||
452
+ (stageName === 'orchestrator' && isMaestroOrchestratorState(state))
453
+ );
454
+ if (isSingleSpecAuthorityToDev) {
455
+ for (const gateLetter of ['A', 'B', 'C']) {
456
+ const gateCheck = await checkGateApproval(
457
+ targetDir,
458
+ gateLetter,
459
+ state.featureSlug,
460
+ classification,
461
+ projectClassification
462
+ );
463
+ if (!gateCheck.ok) {
464
+ missing.push(`gate ${gateLetter} not approved (${gateCheck.reason})`);
465
+ }
466
+ }
467
+
468
+ const planPath = path.join(targetDir, '.aioson', 'context', `implementation-plan-${state.featureSlug}.md`);
469
+ const planContent = await readFileSafe(planPath);
470
+ const planStatus = planContent ? parseFrontmatterValue(planContent, 'status') : null;
471
+ if (String(planStatus || '').toLowerCase() !== 'approved') {
472
+ missing.push(`implementation-plan-${state.featureSlug}.md status is ${planStatus || 'missing'} — @${stageName} must approve the collapsed Gate C plan`);
473
+ }
474
+
475
+ const integrityGate = await evaluateContractIntegrityGate(targetDir, state.featureSlug, {
476
+ runChecks: false
477
+ });
478
+ if (!integrityGate.ok) {
479
+ missing.push(...integrityGate.errors.map((err) =>
480
+ `harness contract integrity failed (${err.code}): ${err.message}`
481
+ ));
482
+ }
483
+ }
484
+
485
+ if (stageName === 'qa' && state.featureSlug) {
486
+ const acAudit = await auditAcceptanceCriteriaTests(targetDir, state.featureSlug);
487
+ if (!acAudit.ok) {
488
+ missing.push(`AC test audit failed: missing tests for ${acAudit.missing.join(', ')}`);
489
+ }
490
+ }
491
+
492
+ // 3. Context updates (soft check — just warn if completely missing)
398
493
  for (const p of contract.contextUpdates) {
399
494
  const abs = path.join(targetDir, p);
400
495
  if (!(await fileExists(abs))) {
@@ -488,10 +583,10 @@ async function getBlockingRevisions(targetDir, featureSlug) {
488
583
  * getCanonicalArtifactsForAgent
489
584
  *
490
585
  * Public lookup helper used by `runAgentDone` (F2 — workflow-handoff-integrity v1.9.5)
491
- * to determine which artifact paths an agent is expected to produce. Returns the
492
- * paths declared by the agent's contract in CONTRACTS, plus accepted fallback
493
- * candidates for contracts that support legacy artifact names, fully resolved
494
- * against the workflow state.
586
+ * to determine which artifact paths an agent is expected to produce. Returns the
587
+ * paths declared by the agent's contract in CONTRACTS, plus accepted fallback
588
+ * candidates for contracts that support legacy artifact names, fully resolved
589
+ * against the workflow state.
495
590
  *
496
591
  * @param {string} agent Agent name (with or without leading `@`).
497
592
  * @param {string} targetDir Project root path (absolute).
@@ -501,21 +596,21 @@ async function getBlockingRevisions(targetDir, featureSlug) {
501
596
  * means the agent produces no canonical artifact (e.g.
502
597
  * `@committer`, `@dev`) — auto-emit should be skipped.
503
598
  */
504
- async function getCanonicalArtifactsForAgent(agent, targetDir, state) {
505
- const normalizedAgent = String(agent || '').replace(/^@/, '').toLowerCase();
506
- if (!normalizedAgent) return null;
507
- const contract = CONTRACTS[normalizedAgent];
508
- if (!contract) return null;
509
- if (normalizedAgent === 'discovery-design-doc' && state?.mode === 'feature' && state?.featureSlug) {
510
- return [
511
- `.aioson/context/design-doc-${state.featureSlug}.md`,
512
- `.aioson/context/readiness-${state.featureSlug}.md`,
513
- '.aioson/context/design-doc.md',
514
- '.aioson/context/readiness.md'
515
- ].map((p) => path.join(targetDir, p));
516
- }
517
- return await resolveArtifacts(contract, targetDir, state || {});
518
- }
599
+ async function getCanonicalArtifactsForAgent(agent, targetDir, state) {
600
+ const normalizedAgent = String(agent || '').replace(/^@/, '').toLowerCase();
601
+ if (!normalizedAgent) return null;
602
+ const contract = CONTRACTS[normalizedAgent];
603
+ if (!contract) return null;
604
+ if (normalizedAgent === 'discovery-design-doc' && state?.mode === 'feature' && state?.featureSlug) {
605
+ return [
606
+ `.aioson/context/design-doc-${state.featureSlug}.md`,
607
+ `.aioson/context/readiness-${state.featureSlug}.md`,
608
+ '.aioson/context/design-doc.md',
609
+ '.aioson/context/readiness.md'
610
+ ].map((p) => path.join(targetDir, p));
611
+ }
612
+ return await resolveArtifacts(contract, targetDir, state || {});
613
+ }
519
614
 
520
615
  module.exports = {
521
616
  parseFrontmatterValue,