@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.
- package/CHANGELOG.md +75 -0
- package/README.md +19 -6
- package/docs/en/1-understand/ecosystem-map.md +45 -29
- package/docs/en/1-understand/glossary.md +5 -5
- package/docs/en/1-understand/what-is-aioson.md +5 -5
- package/docs/en/2-start/existing-project.md +7 -7
- package/docs/en/2-start/first-project.md +32 -38
- package/docs/en/2-start/initial-decisions.md +18 -17
- package/docs/en/3-recipes/README.md +2 -2
- package/docs/en/3-recipes/continuity-between-sessions.md +2 -2
- package/docs/en/3-recipes/from-idea-to-prd-via-briefing.md +5 -2
- package/docs/en/3-recipes/full-feature-with-sheldon.md +327 -338
- package/docs/en/4-agents/README.md +28 -14
- package/docs/en/4-agents/discovery-design-doc.md +20 -8
- package/docs/en/4-agents/forge-run.md +3 -2
- package/docs/en/5-reference/cli-reference.md +51 -48
- package/docs/en/5-reference/executable-verification.md +10 -7
- package/docs/en/5-reference/parallel.md +2 -0
- package/docs/en/5-reference/qa-browser.md +2 -2
- package/docs/en/README.md +3 -3
- package/docs/pt/1-entender/glossario.md +4 -4
- package/docs/pt/1-entender/mapa-do-ecossistema.md +32 -21
- package/docs/pt/2-comecar/decisoes-iniciais.md +16 -16
- package/docs/pt/2-comecar/primeiro-projeto.md +1 -1
- package/docs/pt/3-receitas/README.md +1 -1
- package/docs/pt/3-receitas/app-saas-do-zero.md +35 -122
- package/docs/pt/3-receitas/feature-completa-com-sheldon.md +289 -338
- package/docs/pt/4-agentes/analyst.md +9 -5
- package/docs/pt/4-agentes/architect.md +9 -5
- package/docs/pt/4-agentes/dev.md +19 -7
- package/docs/pt/4-agentes/discovery-design-doc.md +25 -23
- package/docs/pt/4-agentes/orchestrator.md +164 -118
- package/docs/pt/4-agentes/pentester.md +5 -5
- package/docs/pt/4-agentes/pm.md +15 -7
- package/docs/pt/4-agentes/sheldon.md +166 -99
- package/docs/pt/4-agentes/ux-ui.md +7 -4
- package/docs/pt/5-referencia/agent-chain-continuity.md +14 -5
- package/docs/pt/5-referencia/comandos-cli.md +1 -1
- package/docs/pt/5-referencia/harness-retro.md +2 -1
- package/docs/pt/5-referencia/sdd-automation-scripts.md +6 -6
- package/docs/pt/5-referencia/sdd-framework.md +53 -16
- package/docs/pt/5-referencia/sdd-planos-e-estrutura.md +27 -15
- package/docs/pt/README.md +4 -4
- package/docs/pt/agentes.md +48 -50
- package/package.json +2 -2
- package/src/artifact-kinds.js +110 -0
- package/src/cli.js +82 -42
- package/src/commands/agent-epilogue.js +251 -186
- package/src/commands/agents.js +104 -48
- package/src/commands/audit-code.js +344 -0
- package/src/commands/classify.js +75 -13
- package/src/commands/feature-close.js +43 -18
- package/src/commands/harness-check.js +259 -175
- package/src/commands/harness-retro-promote.js +387 -0
- package/src/commands/live.js +24 -0
- package/src/commands/prototype-check.js +163 -0
- package/src/commands/review-feature.js +189 -0
- package/src/commands/runtime.js +81 -66
- package/src/commands/sync-agents-copy.js +115 -0
- package/src/commands/verification-plan.js +116 -0
- package/src/commands/verify-artifact.js +530 -0
- package/src/commands/verify-implementation.js +428 -0
- package/src/commands/workflow-execute.js +309 -309
- package/src/commands/workflow-next.js +361 -19
- package/src/commands/workflow-plan.js +5 -5
- package/src/constants.js +4 -0
- package/src/gateway-pointer-merge.js +7 -1
- package/src/handoff-contract.js +267 -172
- package/src/harness/contract-integrity-gate.js +172 -0
- package/src/harness/contract-integrity.js +111 -0
- package/src/harness/contract-schema.js +377 -332
- package/src/harness/detect-runtime-feature.js +90 -0
- package/src/harness/static-criteria.js +192 -0
- package/src/i18n/messages/en.js +8 -4
- package/src/i18n/messages/es.js +8 -4
- package/src/i18n/messages/fr.js +8 -4
- package/src/i18n/messages/pt-BR.js +8 -4
- package/src/install-wizard.js +8 -8
- package/src/installer.js +13 -6
- package/src/lib/retro/retro-render.js +10 -1
- package/src/lib/retro/retro-sources.js +45 -27
- package/src/lib/retro/verification-reports.js +230 -0
- package/src/parser.js +6 -0
- package/src/preflight-engine.js +12 -12
- package/src/runtime-store.js +13 -9
- package/src/verification/evidence-bundle.js +251 -0
- package/src/verification/ledger-store.js +221 -0
- package/src/verification/path-policy.js +74 -0
- package/src/verification/policy-engine.js +95 -0
- package/src/verification/prompt-package.js +314 -0
- package/src/verification/redaction.js +77 -0
- package/src/verification/report-parser.js +132 -0
- package/src/verification/report-store.js +97 -0
- package/src/verification/result.js +16 -0
- package/src/verification/runners/index.js +319 -0
- package/src/verification/runtime-telemetry.js +144 -0
- package/src/verification/schema.js +276 -0
- package/src/verification/source-discovery.js +153 -0
- package/src/verification-policy.js +398 -0
- package/src/version.js +52 -1
- package/template/.aioson/agents/analyst.md +9 -6
- package/template/.aioson/agents/architect.md +34 -5
- package/template/.aioson/agents/briefing-refiner.md +25 -0
- package/template/.aioson/agents/briefing.md +69 -12
- package/template/.aioson/agents/committer.md +2 -1
- package/template/.aioson/agents/copywriter.md +30 -21
- package/template/.aioson/agents/design-hybrid-forge.md +28 -15
- package/template/.aioson/agents/dev.md +28 -10
- package/template/.aioson/agents/deyvin.md +3 -2
- package/template/.aioson/agents/discover.md +12 -3
- package/template/.aioson/agents/discovery-design-doc.md +7 -2
- package/template/.aioson/agents/genome.md +19 -10
- package/template/.aioson/agents/neo.md +30 -30
- package/template/.aioson/agents/orache.md +20 -11
- package/template/.aioson/agents/orchestrator.md +84 -7
- package/template/.aioson/agents/pm.md +8 -8
- package/template/.aioson/agents/product.md +15 -11
- package/template/.aioson/agents/profiler-enricher.md +20 -11
- package/template/.aioson/agents/profiler-forge.md +20 -11
- package/template/.aioson/agents/profiler-researcher.md +20 -11
- package/template/.aioson/agents/qa.md +87 -17
- package/template/.aioson/agents/scope-check.md +19 -5
- package/template/.aioson/agents/setup.md +12 -1
- package/template/.aioson/agents/sheldon.md +92 -9
- package/template/.aioson/agents/site-forge.md +11 -2
- package/template/.aioson/agents/squad.md +20 -5
- package/template/.aioson/agents/ux-ui.md +4 -2
- package/template/.aioson/agents/validator.md +33 -1
- package/template/.aioson/config/verification.json +61 -0
- package/template/.aioson/config.md +13 -9
- package/template/.aioson/docs/LAYERS.md +2 -2
- package/template/.aioson/docs/autopilot-handoff.md +10 -10
- package/template/.aioson/docs/dev/execution-discipline.md +41 -0
- package/template/.aioson/docs/dev/phase-loop.md +47 -0
- package/template/.aioson/docs/feature-expansion-taxonomy.md +31 -3
- package/template/.aioson/docs/presets/workflow.config.full-merged.json +15 -0
- package/template/.aioson/docs/presets/workflow.config.lean.json +15 -0
- package/template/.aioson/docs/product/prd-contract.md +12 -12
- package/template/.aioson/docs/prototype-contract.md +98 -0
- package/template/.aioson/docs/reference-identity.md +94 -0
- package/template/.aioson/docs/sheldon/harness-contract.md +155 -28
- package/template/.aioson/docs/verification-config.md +82 -0
- package/template/.aioson/docs/verify-artifact-gates.md +91 -0
- package/template/.aioson/docs/workflow-lean-lane.md +129 -0
- package/template/.aioson/skills/design/interface-design/references/intent-and-domain.md +2 -0
- package/template/.aioson/skills/process/aioson-spec-driven/SKILL.md +16 -14
- package/template/.aioson/skills/process/aioson-spec-driven/references/approval-gates.md +2 -2
- package/template/.aioson/skills/process/aioson-spec-driven/references/classification-map.md +4 -4
- package/template/.aioson/skills/process/aioson-spec-driven/references/dev.md +15 -15
- package/template/.aioson/skills/process/briefing-expansion-scout/SKILL.md +25 -3
- package/template/.aioson/skills/process/design-hybrid-forge/SKILL.md +5 -3
- package/template/.aioson/skills/process/design-hybrid-forge/references/external-source-ingestion.md +89 -0
- package/template/.aioson/skills/process/product-scope-expansion/SKILL.md +29 -2
- package/template/.aioson/skills/process/prototype-forge/SKILL.md +98 -0
- package/template/.aioson/skills/process/reference-identity-extract/SKILL.md +164 -0
- package/template/.aioson/skills/process/sheldon-expansion-audit/SKILL.md +23 -2
- package/template/.aioson/skills/static/multi-agent-patterns.md +5 -5
- package/template/CLAUDE.md +15 -11
package/src/handoff-contract.js
CHANGED
|
@@ -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
|
-
|
|
33
|
-
artifacts: (targetDir, state) => {
|
|
34
|
-
if (state.mode
|
|
35
|
-
return [
|
|
36
|
-
|
|
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:
|
|
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
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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,
|