@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
|
@@ -23,6 +23,20 @@ const { runSecurityAudit } = require('./security-audit');
|
|
|
23
23
|
const dossierBootstrap = require('../dossier/dossier-bootstrap');
|
|
24
24
|
const dossierStore = require('../dossier/store');
|
|
25
25
|
const { emitDossierEvent } = require('../lib/dossier-telemetry');
|
|
26
|
+
const { parseVerificationReport } = require('../verification/report-parser');
|
|
27
|
+
const { applyPolicy } = require('../verification/policy-engine');
|
|
28
|
+
const { normalizePolicy } = require('../verification/result');
|
|
29
|
+
const {
|
|
30
|
+
evaluateContractIntegrityGate,
|
|
31
|
+
formatContractIntegrityGateError
|
|
32
|
+
} = require('../harness/contract-integrity-gate');
|
|
33
|
+
const { runSpecAnalyze } = require('./spec-analyze');
|
|
34
|
+
const {
|
|
35
|
+
validateFeatureSlug,
|
|
36
|
+
featureContextDir,
|
|
37
|
+
verificationRunsDir,
|
|
38
|
+
relativeFromRoot
|
|
39
|
+
} = require('../verification/path-policy');
|
|
26
40
|
|
|
27
41
|
const STATE_RELATIVE_PATH = '.aioson/context/workflow.state.json';
|
|
28
42
|
const CONFIG_RELATIVE_PATH = '.aioson/context/workflow.config.json';
|
|
@@ -31,21 +45,34 @@ const SCOPE_CHECK_MODES = new Set(['pre-dev', 'post-dev', 'post-fix', 'final']);
|
|
|
31
45
|
|
|
32
46
|
const DEFAULT_FEATURE_WORKFLOW_BY_CLASSIFICATION = {
|
|
33
47
|
MICRO: ['product', 'dev', 'qa'],
|
|
34
|
-
SMALL:
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
MEDIUM
|
|
48
|
+
// SMALL defaults to the lean lane: @sheldon is the single spec authority
|
|
49
|
+
// (requirements + spec + design-doc + readiness + plan + harness-contract in
|
|
50
|
+
// one pass), replacing analyst/scope-check/architect/discovery-design-doc.
|
|
51
|
+
// Those agents remain available as opt-in detours (allowDetours: true).
|
|
52
|
+
SMALL: ['product', 'sheldon', 'dev', 'qa'],
|
|
53
|
+
// MEDIUM collapses the spec phase into @orchestrator — the maestro that fans out
|
|
54
|
+
// to @analyst + @architect + @pm (+ @ux-ui when UI-heavy) as sub-agents, then
|
|
55
|
+
// consolidates/verifies/redoes their output into the gated spec package
|
|
56
|
+
// (requirements + spec[A/B/C approved] + design-doc + readiness + implementation-
|
|
57
|
+
// plan + harness-contract) and hands to @dev. SMALL = @sheldon solo (vertical),
|
|
58
|
+
// MEDIUM = @orchestrator fan-out (horizontal). The scope drift check is enforced at
|
|
59
|
+
// the dev/qa done gate (finalizeCurrentStage). @pentester stays as the post-dev
|
|
60
|
+
// security stage. analyst/architect/pm/ddd/scope-check/ux-ui remain opt-in detours.
|
|
61
|
+
MEDIUM: ['product', 'orchestrator', 'dev', 'pentester', 'qa']
|
|
40
62
|
};
|
|
41
63
|
|
|
42
64
|
// Stages eligible for autopilot handoff (auto_handoff: true in project.context.md).
|
|
43
65
|
// Two segments — see .aioson/docs/autopilot-handoff.md:
|
|
44
|
-
// 1. analyst -> dev: deterministic pre-dev chain. Prompt-only clients stop
|
|
45
|
-
// before the first @dev entry; workflow:execute --agentic may resume it
|
|
46
|
-
// through a fresh checkpointed activation.
|
|
66
|
+
// 1. analyst -> dev: deterministic pre-dev chain. Prompt-only clients stop
|
|
67
|
+
// before the first @dev entry; workflow:execute --agentic may resume it
|
|
68
|
+
// through a fresh checkpointed activation.
|
|
47
69
|
// 2. post-dev review cycle: @dev → @qa → @tester/@pentester (when their @qa triggers
|
|
48
70
|
// fire) → @validator → STOPS before feature:close (human approves the close).
|
|
71
|
+
// @product and @sheldon are intentionally absent: per config.md, upstream agents
|
|
72
|
+
// (@briefing/@product/@sheldon) always hand off MANUALLY. In the lean lane
|
|
73
|
+
// (product → sheldon → dev → qa) this means auto_handoff is a deliberate no-op
|
|
74
|
+
// pre-dev — @sheldon is the only pre-dev agent and hands off by hand — and is
|
|
75
|
+
// active only on the post-dev cycle (dev → qa). This is by design, not an omission.
|
|
49
76
|
const AUTOPILOT_HANDOFF_STAGES = new Set([
|
|
50
77
|
'analyst', 'scope-check', 'architect', 'discovery-design-doc', 'pm',
|
|
51
78
|
'dev', 'qa', 'tester', 'pentester', 'validator'
|
|
@@ -64,13 +91,21 @@ function normalizeClassification(value, fallback = 'MICRO') {
|
|
|
64
91
|
return fallback;
|
|
65
92
|
}
|
|
66
93
|
|
|
94
|
+
// MEDIUM maestro lane: @orchestrator routes straight to @dev (it is the single
|
|
95
|
+
// spec authority that fans out to analyst/architect/pm sub-agents and consolidates).
|
|
96
|
+
function isMaestroOrchestratorState(state) {
|
|
97
|
+
const sequence = Array.isArray(state && state.sequence) ? state.sequence.map(normalizeAgentName) : [];
|
|
98
|
+
const idx = sequence.indexOf('orchestrator');
|
|
99
|
+
return idx !== -1 && sequence[idx + 1] === 'dev';
|
|
100
|
+
}
|
|
101
|
+
|
|
67
102
|
function buildDefaultWorkflowConfig() {
|
|
68
103
|
return {
|
|
69
104
|
version: 1,
|
|
70
105
|
project: {
|
|
71
106
|
MICRO: ['setup', 'dev'],
|
|
72
|
-
SMALL: ['setup', 'product', '
|
|
73
|
-
MEDIUM: ['setup', 'product', '
|
|
107
|
+
SMALL: ['setup', 'product', 'sheldon', 'dev', 'qa'],
|
|
108
|
+
MEDIUM: ['setup', 'product', 'orchestrator', 'dev', 'qa']
|
|
74
109
|
},
|
|
75
110
|
feature: DEFAULT_FEATURE_WORKFLOW_BY_CLASSIFICATION,
|
|
76
111
|
rules: {
|
|
@@ -114,6 +149,18 @@ function getScopeCheckModeOption(options = {}) {
|
|
|
114
149
|
);
|
|
115
150
|
}
|
|
116
151
|
|
|
152
|
+
function resolveVerificationPolicy(options = {}, state = {}) {
|
|
153
|
+
const explicit = options.verificationPolicy ||
|
|
154
|
+
options['verification-policy'] ||
|
|
155
|
+
options.verification_policy ||
|
|
156
|
+
options.policy;
|
|
157
|
+
if (explicit) return normalizePolicy(explicit) || 'standard';
|
|
158
|
+
// No explicit policy: MEDIUM features default to strict so the auto-injected
|
|
159
|
+
// scope-check verification briefing matches the strict `--check-report` the
|
|
160
|
+
// @dev / @scope-check prompts run. Smaller tiers stay advisory (standard).
|
|
161
|
+
return String(state.classification || '').toUpperCase() === 'MEDIUM' ? 'strict' : 'standard';
|
|
162
|
+
}
|
|
163
|
+
|
|
117
164
|
function chooseActiveFeature(features, preferredSlug = null) {
|
|
118
165
|
const activeFeatures = (features || []).filter((feature) => feature.status === 'in_progress');
|
|
119
166
|
if (preferredSlug) {
|
|
@@ -330,6 +377,19 @@ async function validateStageArtifacts(targetDir, state, stage) {
|
|
|
330
377
|
}
|
|
331
378
|
|
|
332
379
|
if (stage === 'orchestrator') {
|
|
380
|
+
// Maestro lane (MEDIUM, orchestrator → dev): the orchestrator is the single
|
|
381
|
+
// spec authority — "done" once the gated spec package exists (mirrors how the
|
|
382
|
+
// per-hop stages were inferred). Otherwise it is the parallel-impl coordinator
|
|
383
|
+
// and owns the lane workspace.
|
|
384
|
+
if (state.mode === 'feature' && slug && isMaestroOrchestratorState(state)) {
|
|
385
|
+
const designDoc = [path.join(base, `design-doc-${slug}.md`), path.join(base, 'design-doc.md')];
|
|
386
|
+
const readiness = [path.join(base, `readiness-${slug}.md`), path.join(base, 'readiness.md')];
|
|
387
|
+
return (await exists(path.join(base, `requirements-${slug}.md`)))
|
|
388
|
+
&& (await exists(path.join(base, `spec-${slug}.md`)))
|
|
389
|
+
&& (await exists(path.join(base, `implementation-plan-${slug}.md`)))
|
|
390
|
+
&& (await anyExists(designDoc))
|
|
391
|
+
&& (await anyExists(readiness));
|
|
392
|
+
}
|
|
333
393
|
return await exists(path.join(base, 'parallel'));
|
|
334
394
|
}
|
|
335
395
|
|
|
@@ -746,9 +806,94 @@ async function finalizeCurrentStage(targetDir, config, state, stageName) {
|
|
|
746
806
|
if (!normalizedStage) {
|
|
747
807
|
throw new Error('No stage is active to complete.');
|
|
748
808
|
}
|
|
809
|
+
let auditCodeSummary = null;
|
|
749
810
|
|
|
750
811
|
// ── Harness Done Gate ───────────────────────────────────────────────────
|
|
751
812
|
if (state.mode === 'feature' && state.featureSlug) {
|
|
813
|
+
if (normalizedStage === 'dev' || normalizedStage === 'qa') {
|
|
814
|
+
const integrityGate = await evaluateContractIntegrityGate(targetDir, state.featureSlug, {
|
|
815
|
+
runChecks: true
|
|
816
|
+
});
|
|
817
|
+
if (!integrityGate.ok) {
|
|
818
|
+
const errMsg = formatContractIntegrityGateError(integrityGate, normalizedStage);
|
|
819
|
+
await logError(targetDir, normalizedStage, errMsg, 'harness-contract');
|
|
820
|
+
throw new Error(errMsg);
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// ── Scope drift gate (absorbs @scope-check's deterministic spec:analyze) ──
|
|
824
|
+
// scope-check is no longer a default stage; preserve its drift check so a
|
|
825
|
+
// dev/qa completion still blocks on real drift — a design-doc/readiness that
|
|
826
|
+
// declares `readiness: blocked`, or a malformed/invalid harness-contract.json.
|
|
827
|
+
// Non-strict spec:analyze only raises error-severity findings for those
|
|
828
|
+
// genuine problems (never for merely-absent artifacts), so this never
|
|
829
|
+
// false-blocks an artifact-light feature. Defensive: never crashes the run.
|
|
830
|
+
try {
|
|
831
|
+
const drift = await runSpecAnalyze({
|
|
832
|
+
args: [targetDir],
|
|
833
|
+
options: { feature: state.featureSlug },
|
|
834
|
+
logger: { log() {}, error() {} }
|
|
835
|
+
});
|
|
836
|
+
if (drift && Array.isArray(drift.findings) && drift.summary && drift.summary.errors > 0) {
|
|
837
|
+
const errs = drift.findings
|
|
838
|
+
.filter((f) => f.severity === 'error')
|
|
839
|
+
.map((f) => ` - ${f.check}: ${f.message}`)
|
|
840
|
+
.join('\n');
|
|
841
|
+
const driftMsg = `[Scope Drift Gate] @${normalizedStage} blocked — spec:analyze found ${drift.summary.errors} drift error(s):\n${errs}\nResolve the drift (or run @scope-check) before completing this stage.`;
|
|
842
|
+
await logError(targetDir, normalizedStage, driftMsg, 'scope-drift');
|
|
843
|
+
throw new Error(driftMsg);
|
|
844
|
+
}
|
|
845
|
+
} catch (driftErr) {
|
|
846
|
+
if (driftErr && driftErr.message && driftErr.message.includes('[Scope Drift Gate]')) throw driftErr;
|
|
847
|
+
// spec:analyze unavailable or non-analyzable — non-blocking
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// ── Code-quality gate (deterministic audit:code) ───────────────────────
|
|
851
|
+
// Build-free scan of the changed files for the non-security categories
|
|
852
|
+
// (anti-patterns / TODOs / dead code / duplication). Unlike the integrity
|
|
853
|
+
// and drift gates above — which enforce the feature's DECLARED contract /
|
|
854
|
+
// spec — audit:code is a heuristic opinion, so it defaults to ADVISORY: the
|
|
855
|
+
// report is persisted (.aioson/context/audit-code.json), a guard event is
|
|
856
|
+
// emitted, and a summary rides the finalize result, but the stage is NOT
|
|
857
|
+
// blocked. Set verification.json `audit_code.tracked_gate: "block"` to make a
|
|
858
|
+
// HIGH finding in scope a hard gate, or "off" to skip. Defensive: only a
|
|
859
|
+
// deliberate block-mode throw escapes; any other error is swallowed.
|
|
860
|
+
try {
|
|
861
|
+
const { readVerificationConfig, getAuditCodePolicy } = require('../verification-policy');
|
|
862
|
+
const auditPolicy = getAuditCodePolicy(await readVerificationConfig(targetDir));
|
|
863
|
+
if (auditPolicy.tracked_gate !== 'off') {
|
|
864
|
+
const { runAuditCode } = require('./audit-code');
|
|
865
|
+
const codeAudit = await runAuditCode({
|
|
866
|
+
args: [targetDir],
|
|
867
|
+
options: { changed: auditPolicy.scope !== 'full', json: true, suppressExitCode: true },
|
|
868
|
+
logger: { log() {}, error() {} }
|
|
869
|
+
});
|
|
870
|
+
const high = codeAudit && codeAudit.by_severity ? (codeAudit.by_severity.HIGH || 0) : 0;
|
|
871
|
+
const med = codeAudit && codeAudit.by_severity ? (codeAudit.by_severity.MED || 0) : 0;
|
|
872
|
+
const categories = Object.keys((codeAudit && codeAudit.by_category) || {});
|
|
873
|
+
auditCodeSummary = { gate: auditPolicy.tracked_gate, scope: auditPolicy.scope, high, med, categories };
|
|
874
|
+
if (high > 0 && auditPolicy.tracked_gate === 'block') {
|
|
875
|
+
const msg = `[Code-Quality Gate] @${normalizedStage} blocked — audit:code found ${high} HIGH finding(s) in the ${auditPolicy.scope} files (${categories.join(', ')}). Fix them, or relax verification.json audit_code.tracked_gate to "advisory"/"off". See .aioson/context/audit-code.json`;
|
|
876
|
+
await logError(targetDir, normalizedStage, msg, 'audit-code');
|
|
877
|
+
throw new Error(msg);
|
|
878
|
+
}
|
|
879
|
+
if (high > 0) {
|
|
880
|
+
try {
|
|
881
|
+
const { emitGuardEvent } = require('../harness/guard-events');
|
|
882
|
+
await emitGuardEvent(targetDir, {
|
|
883
|
+
eventType: 'audit_code_findings',
|
|
884
|
+
agent: normalizedStage,
|
|
885
|
+
message: `${high} HIGH / ${med} MED in ${auditPolicy.scope} files (${categories.join(', ')}) — advisory`,
|
|
886
|
+
payload: { slug: state.featureSlug, high, med, scope: auditPolicy.scope }
|
|
887
|
+
});
|
|
888
|
+
} catch { /* telemetry best-effort */ }
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
} catch (auditErr) {
|
|
892
|
+
if (auditErr && auditErr.message && auditErr.message.includes('[Code-Quality Gate]')) throw auditErr;
|
|
893
|
+
// audit:code unavailable / non-git — non-blocking
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
|
|
752
897
|
const contractPath = path.join(targetDir, '.aioson', 'plans', state.featureSlug, 'harness-contract.json');
|
|
753
898
|
const progressPath = path.join(targetDir, '.aioson', 'plans', state.featureSlug, 'progress.json');
|
|
754
899
|
|
|
@@ -848,7 +993,11 @@ async function finalizeCurrentStage(targetDir, config, state, stageName) {
|
|
|
848
993
|
detour: null
|
|
849
994
|
});
|
|
850
995
|
|
|
851
|
-
return {
|
|
996
|
+
return {
|
|
997
|
+
state: nextState,
|
|
998
|
+
completedStage: normalizedStage,
|
|
999
|
+
...(auditCodeSummary ? { auditCode: auditCodeSummary } : {})
|
|
1000
|
+
};
|
|
852
1001
|
}
|
|
853
1002
|
|
|
854
1003
|
/**
|
|
@@ -1032,6 +1181,8 @@ async function resolveStageDependencies(targetDir, state, stageName, agent) {
|
|
|
1032
1181
|
'readiness.md',
|
|
1033
1182
|
'ui-spec.md',
|
|
1034
1183
|
slug ? `implementation-plan-${slug}.md` : 'implementation-plan.md',
|
|
1184
|
+
slug ? `features/${slug}/implementation-ledger.md` : null,
|
|
1185
|
+
slug ? `features/${slug}/verification-report.md` : null,
|
|
1035
1186
|
'dev-state.md',
|
|
1036
1187
|
'last-handoff.json',
|
|
1037
1188
|
'project-pulse.md'
|
|
@@ -1139,6 +1290,146 @@ function buildScopeCheckActivationContext(state, mode) {
|
|
|
1139
1290
|
return lines.join('\n');
|
|
1140
1291
|
}
|
|
1141
1292
|
|
|
1293
|
+
function routeLabel(route) {
|
|
1294
|
+
return route ? `@${normalizeAgentName(route)}` : '@qa';
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
function workflowGuidanceForVerification(verdict, route, normalReturnTo) {
|
|
1298
|
+
if (verdict === 'PASS') {
|
|
1299
|
+
return `PASS: keep normal workflow ownership (${routeLabel(normalReturnTo || route)}), then continue diff/scope review.`;
|
|
1300
|
+
}
|
|
1301
|
+
if (verdict === 'NEEDS_DEV_FIX') {
|
|
1302
|
+
return 'NEEDS_DEV_FIX: do not approve clean post-dev scope; route concrete file:line findings to @dev.';
|
|
1303
|
+
}
|
|
1304
|
+
if (verdict === 'NEEDS_SCOPE_DECISION') {
|
|
1305
|
+
return `NEEDS_SCOPE_DECISION: route to ${routeLabel(route)}; do not patch product scope locally.`;
|
|
1306
|
+
}
|
|
1307
|
+
if (verdict === 'NEEDS_QA_RECHECK') {
|
|
1308
|
+
return 'NEEDS_QA_RECHECK: route to @qa after scope alignment is clear.';
|
|
1309
|
+
}
|
|
1310
|
+
if (verdict === 'NEEDS_SECURITY_REVIEW') {
|
|
1311
|
+
return 'NEEDS_SECURITY_REVIEW: preserve the security review owner and route to @pentester.';
|
|
1312
|
+
}
|
|
1313
|
+
return `INCONCLUSIVE: route to the owner of missing evidence (${routeLabel(route)}) when strict verification applies.`;
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
async function findImplementationVerificationReport(targetDir, slug) {
|
|
1317
|
+
const slugResult = validateFeatureSlug(slug);
|
|
1318
|
+
if (!slugResult.ok) return null;
|
|
1319
|
+
|
|
1320
|
+
const latestPath = path.join(featureContextDir(targetDir, slug), 'verification-report.md');
|
|
1321
|
+
if (await exists(latestPath)) {
|
|
1322
|
+
return {
|
|
1323
|
+
absolutePath: latestPath,
|
|
1324
|
+
relativePath: relativeFromRoot(targetDir, latestPath),
|
|
1325
|
+
source: 'latest'
|
|
1326
|
+
};
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
const runsDir = verificationRunsDir(targetDir, slug);
|
|
1330
|
+
let entries = [];
|
|
1331
|
+
try {
|
|
1332
|
+
entries = await fs.readdir(runsDir);
|
|
1333
|
+
} catch {
|
|
1334
|
+
return null;
|
|
1335
|
+
}
|
|
1336
|
+
const reportName = entries
|
|
1337
|
+
.filter((entry) => /-report\.md$/i.test(entry))
|
|
1338
|
+
.sort()
|
|
1339
|
+
.pop();
|
|
1340
|
+
if (!reportName) return null;
|
|
1341
|
+
|
|
1342
|
+
const reportPath = path.join(runsDir, reportName);
|
|
1343
|
+
return {
|
|
1344
|
+
absolutePath: reportPath,
|
|
1345
|
+
relativePath: relativeFromRoot(targetDir, reportPath),
|
|
1346
|
+
source: 'verification-runs'
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
async function buildImplementationVerificationBriefing(targetDir, state, scopeCheckMode, policy) {
|
|
1351
|
+
const mode = inferScopeCheckMode(state, scopeCheckMode);
|
|
1352
|
+
if (
|
|
1353
|
+
state.mode !== 'feature' ||
|
|
1354
|
+
!state.featureSlug ||
|
|
1355
|
+
!['post-dev', 'post-fix', 'final'].includes(mode)
|
|
1356
|
+
) {
|
|
1357
|
+
return null;
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
const slug = state.featureSlug;
|
|
1361
|
+
const latestPath = `.aioson/context/features/${slug}/verification-report.md`;
|
|
1362
|
+
const reportRef = await findImplementationVerificationReport(targetDir, slug);
|
|
1363
|
+
const lines = [
|
|
1364
|
+
'## Implementation verification briefing',
|
|
1365
|
+
`Policy: ${policy}`,
|
|
1366
|
+
`Expected latest report: ${latestPath}`,
|
|
1367
|
+
'Workflow note: this briefing only validates local report artifacts; it never runs `--tool` or any external auditor.'
|
|
1368
|
+
];
|
|
1369
|
+
|
|
1370
|
+
if (!reportRef) {
|
|
1371
|
+
lines.push('Report status: missing');
|
|
1372
|
+
if (state.classification === 'MICRO') {
|
|
1373
|
+
lines.push('MICRO: missing report is not a workflow blocker by default; record residual risk only when the dev handoff relied on verification.');
|
|
1374
|
+
} else if (state.classification === 'MEDIUM' && policy === 'strict') {
|
|
1375
|
+
lines.push('Strict MEDIUM guidance: do not issue final clean scope approval until @dev produces a valid report or documents an explicit N/A rationale.');
|
|
1376
|
+
} else {
|
|
1377
|
+
lines.push('Guidance: absence is advisory unless the feature policy or dev handoff made verification strict.');
|
|
1378
|
+
}
|
|
1379
|
+
return {
|
|
1380
|
+
status: 'missing',
|
|
1381
|
+
mode,
|
|
1382
|
+
policy,
|
|
1383
|
+
report_path: null,
|
|
1384
|
+
verdict: 'INCONCLUSIVE',
|
|
1385
|
+
recommended_route: state.classification === 'MICRO' ? state.next || 'qa' : 'dev',
|
|
1386
|
+
briefing: lines.join('\n')
|
|
1387
|
+
};
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
lines.push(`Report path: ${reportRef.relativePath}`);
|
|
1391
|
+
lines.push(`Validate command: aioson verify:implementation . --feature=${slug} --check-report=${reportRef.relativePath} --policy=${policy} --json`);
|
|
1392
|
+
|
|
1393
|
+
const parsed = await parseVerificationReport(targetDir, slug, reportRef.relativePath, policy);
|
|
1394
|
+
if (!parsed.ok) {
|
|
1395
|
+
lines.push(`Report status: invalid (${parsed.reason})`);
|
|
1396
|
+
lines.push('Guidance: treat this as INCONCLUSIVE local evidence; do not treat auditor prose as PASS.');
|
|
1397
|
+
return {
|
|
1398
|
+
status: 'invalid',
|
|
1399
|
+
mode,
|
|
1400
|
+
policy,
|
|
1401
|
+
report_path: reportRef.relativePath,
|
|
1402
|
+
verdict: 'INCONCLUSIVE',
|
|
1403
|
+
recommended_route: 'qa',
|
|
1404
|
+
reason: parsed.reason,
|
|
1405
|
+
briefing: lines.join('\n')
|
|
1406
|
+
};
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
const policyResult = applyPolicy(parsed.report, policy);
|
|
1410
|
+
lines.push('Report status: schema-valid');
|
|
1411
|
+
lines.push(`Report verdict: ${parsed.report.verdict}`);
|
|
1412
|
+
lines.push(`Policy verdict: ${policyResult.verdict}`);
|
|
1413
|
+
lines.push(`Policy route: ${routeLabel(policyResult.recommended_route)}`);
|
|
1414
|
+
lines.push(`Blocking findings: ${policyResult.blocking_findings_count || 0}`);
|
|
1415
|
+
lines.push(`Guidance: ${workflowGuidanceForVerification(policyResult.verdict, policyResult.recommended_route, state.next)}`);
|
|
1416
|
+
lines.push('Scope-check still must inspect the diff and approved plan; a PASS report is not final approval.');
|
|
1417
|
+
|
|
1418
|
+
return {
|
|
1419
|
+
status: 'valid',
|
|
1420
|
+
mode,
|
|
1421
|
+
policy,
|
|
1422
|
+
report_path: reportRef.relativePath,
|
|
1423
|
+
report_source: reportRef.source,
|
|
1424
|
+
verdict: policyResult.verdict,
|
|
1425
|
+
auditor_verdict: parsed.report.verdict,
|
|
1426
|
+
recommended_route: policyResult.recommended_route,
|
|
1427
|
+
blocking_findings_count: policyResult.blocking_findings_count || 0,
|
|
1428
|
+
reason: policyResult.reason,
|
|
1429
|
+
briefing: lines.join('\n')
|
|
1430
|
+
};
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1142
1433
|
function buildStageActivationContext(state, stageName, dependencies, scopeCheckMode = null) {
|
|
1143
1434
|
if (stageName === 'scope-check') {
|
|
1144
1435
|
return buildScopeCheckActivationContext(state, scopeCheckMode);
|
|
@@ -1155,7 +1446,16 @@ function buildStageActivationContext(state, stageName, dependencies, scopeCheckM
|
|
|
1155
1446
|
].join('\n');
|
|
1156
1447
|
}
|
|
1157
1448
|
|
|
1158
|
-
async function activateStage(
|
|
1449
|
+
async function activateStage(
|
|
1450
|
+
targetDir,
|
|
1451
|
+
state,
|
|
1452
|
+
locale,
|
|
1453
|
+
tool,
|
|
1454
|
+
explicitAgent = null,
|
|
1455
|
+
requestedMode = null,
|
|
1456
|
+
scopeCheckMode = null,
|
|
1457
|
+
verificationPolicy = 'standard'
|
|
1458
|
+
) {
|
|
1159
1459
|
const stageName = normalizeAgentName(explicitAgent || state.current || state.next);
|
|
1160
1460
|
if (!stageName) {
|
|
1161
1461
|
return {
|
|
@@ -1270,6 +1570,13 @@ async function activateStage(targetDir, state, locale, tool, explicitAgent = nul
|
|
|
1270
1570
|
|
|
1271
1571
|
const instructionPath = await resolveExistingInstructionPath(targetDir, agent, locale);
|
|
1272
1572
|
const dependencies = await resolveStageDependencies(targetDir, state, stageName, agent);
|
|
1573
|
+
const verificationBriefing = stageName === 'scope-check'
|
|
1574
|
+
? await buildImplementationVerificationBriefing(targetDir, state, scopeCheckMode, verificationPolicy)
|
|
1575
|
+
: null;
|
|
1576
|
+
const activationContext = [
|
|
1577
|
+
buildStageActivationContext(state, stageName, dependencies, scopeCheckMode),
|
|
1578
|
+
verificationBriefing && verificationBriefing.briefing
|
|
1579
|
+
].filter(Boolean).join('\n\n');
|
|
1273
1580
|
let prompt = buildAgentPrompt(agent, tool, {
|
|
1274
1581
|
instructionPath,
|
|
1275
1582
|
targetDir,
|
|
@@ -1278,7 +1585,7 @@ async function activateStage(targetDir, state, locale, tool, explicitAgent = nul
|
|
|
1278
1585
|
capabilitySummary: buildAgentCapabilitySummary(agentManifest, tool),
|
|
1279
1586
|
dependsOn: dependencies,
|
|
1280
1587
|
autoHandoff,
|
|
1281
|
-
activationContext
|
|
1588
|
+
activationContext
|
|
1282
1589
|
});
|
|
1283
1590
|
|
|
1284
1591
|
if (testBriefing) {
|
|
@@ -1316,7 +1623,20 @@ async function activateStage(targetDir, state, locale, tool, explicitAgent = nul
|
|
|
1316
1623
|
agent: stageName,
|
|
1317
1624
|
instructionPath,
|
|
1318
1625
|
prompt,
|
|
1319
|
-
effectiveMode
|
|
1626
|
+
effectiveMode,
|
|
1627
|
+
verification: verificationBriefing
|
|
1628
|
+
? {
|
|
1629
|
+
status: verificationBriefing.status,
|
|
1630
|
+
mode: verificationBriefing.mode,
|
|
1631
|
+
policy: verificationBriefing.policy,
|
|
1632
|
+
report_path: verificationBriefing.report_path,
|
|
1633
|
+
verdict: verificationBriefing.verdict,
|
|
1634
|
+
auditor_verdict: verificationBriefing.auditor_verdict || null,
|
|
1635
|
+
recommended_route: verificationBriefing.recommended_route,
|
|
1636
|
+
blocking_findings_count: verificationBriefing.blocking_findings_count || 0,
|
|
1637
|
+
reason: verificationBriefing.reason || null
|
|
1638
|
+
}
|
|
1639
|
+
: null
|
|
1320
1640
|
};
|
|
1321
1641
|
}
|
|
1322
1642
|
|
|
@@ -1416,7 +1736,8 @@ async function runWorkflowNext({ args, options, logger, t }) {
|
|
|
1416
1736
|
const autoHeal = Boolean(options['auto-heal'] || options.autoHeal);
|
|
1417
1737
|
const isHealabled = autoHeal && (
|
|
1418
1738
|
err.message.includes('[Technical Gate BLOCKED]') ||
|
|
1419
|
-
err.message.includes('[Handoff Contract BLOCKED]')
|
|
1739
|
+
err.message.includes('[Handoff Contract BLOCKED]') ||
|
|
1740
|
+
err.message.includes('[Harness Contract Gate BLOCKED]')
|
|
1420
1741
|
);
|
|
1421
1742
|
if (isHealabled) {
|
|
1422
1743
|
const failedStage = normalizeAgentName(options.complete === true ? state.current || state.next : options.complete);
|
|
@@ -1425,7 +1746,16 @@ async function runWorkflowNext({ args, options, logger, t }) {
|
|
|
1425
1746
|
if (retryCount < require('../self-healing').MAX_RETRIES) {
|
|
1426
1747
|
await require('../self-healing').incrementRetryCount(targetDir, failedStage, err.message.substring(0, 200));
|
|
1427
1748
|
// Build healing activation
|
|
1428
|
-
const baseActivation = await activateStage(
|
|
1749
|
+
const baseActivation = await activateStage(
|
|
1750
|
+
targetDir,
|
|
1751
|
+
state,
|
|
1752
|
+
locale,
|
|
1753
|
+
tool,
|
|
1754
|
+
failedStage,
|
|
1755
|
+
options.mode || null,
|
|
1756
|
+
null,
|
|
1757
|
+
resolveVerificationPolicy(options, state)
|
|
1758
|
+
);
|
|
1429
1759
|
const healingPrompt = buildHealingPrompt(
|
|
1430
1760
|
baseActivation.prompt || '',
|
|
1431
1761
|
failedStage,
|
|
@@ -1552,7 +1882,17 @@ async function runWorkflowNext({ args, options, logger, t }) {
|
|
|
1552
1882
|
const activationAgent = normalizeAgentName(requestedAgent || state.current || state.next);
|
|
1553
1883
|
const scopeCheckMode = activationAgent === 'scope-check' ? getScopeCheckModeOption(options) : null;
|
|
1554
1884
|
const requestedAutonomyMode = scopeCheckMode && activationAgent === 'scope-check' ? null : options.mode || null;
|
|
1555
|
-
const
|
|
1885
|
+
const verificationPolicy = resolveVerificationPolicy(options, state);
|
|
1886
|
+
const activation = await activateStage(
|
|
1887
|
+
targetDir,
|
|
1888
|
+
state,
|
|
1889
|
+
locale,
|
|
1890
|
+
tool,
|
|
1891
|
+
requestedAgent,
|
|
1892
|
+
requestedAutonomyMode,
|
|
1893
|
+
scopeCheckMode,
|
|
1894
|
+
verificationPolicy
|
|
1895
|
+
);
|
|
1556
1896
|
state = activation.state;
|
|
1557
1897
|
|
|
1558
1898
|
// ── Living Memory: if a reflect manifest is pending (created above by the
|
|
@@ -1590,7 +1930,8 @@ async function runWorkflowNext({ args, options, logger, t }) {
|
|
|
1590
1930
|
completed: state.completed,
|
|
1591
1931
|
skipped: state.skipped,
|
|
1592
1932
|
sequence: state.sequence,
|
|
1593
|
-
autonomyMode: activation.effectiveMode || null
|
|
1933
|
+
autonomyMode: activation.effectiveMode || null,
|
|
1934
|
+
verification: activation.verification || null
|
|
1594
1935
|
};
|
|
1595
1936
|
await appendWorkflowEvent(targetDir, eventPayload);
|
|
1596
1937
|
const runtime = await syncWorkflowRuntime(targetDir, {
|
|
@@ -1645,6 +1986,7 @@ async function runWorkflowNext({ args, options, logger, t }) {
|
|
|
1645
1986
|
runtime,
|
|
1646
1987
|
agent: activation.agent,
|
|
1647
1988
|
effectiveMode: activation.effectiveMode || null,
|
|
1989
|
+
verification: activation.verification || null,
|
|
1648
1990
|
instructionPath: activation.instructionPath,
|
|
1649
1991
|
prompt: activation.prompt
|
|
1650
1992
|
};
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
const path = require('node:path');
|
|
4
4
|
const { validateProjectContextFile } = require('../context');
|
|
5
5
|
|
|
6
|
-
const WORKFLOW_BY_CLASSIFICATION = {
|
|
7
|
-
MICRO: ['setup', 'dev'],
|
|
8
|
-
SMALL: ['setup', 'product', '
|
|
9
|
-
MEDIUM: ['setup', 'product', '
|
|
10
|
-
};
|
|
6
|
+
const WORKFLOW_BY_CLASSIFICATION = {
|
|
7
|
+
MICRO: ['setup', 'dev'],
|
|
8
|
+
SMALL: ['setup', 'product', 'sheldon', 'dev', 'qa'],
|
|
9
|
+
MEDIUM: ['setup', 'product', 'orchestrator', 'dev', 'qa']
|
|
10
|
+
};
|
|
11
11
|
|
|
12
12
|
function normalizeClassification(value, fallback = 'MICRO') {
|
|
13
13
|
const text = String(value || '').trim().toUpperCase();
|
package/src/constants.js
CHANGED
|
@@ -46,6 +46,8 @@ const MANAGED_FILES = [
|
|
|
46
46
|
'.aioson/docs/squad/session-operations.md',
|
|
47
47
|
'.aioson/docs/squad/genome-bindings.md',
|
|
48
48
|
'.aioson/docs/feature-expansion-taxonomy.md',
|
|
49
|
+
'.aioson/docs/prototype-contract.md',
|
|
50
|
+
'.aioson/docs/reference-identity.md',
|
|
49
51
|
'.aioson/docs/product/conversation-playbook.md',
|
|
50
52
|
'.aioson/docs/product/research-loop.md',
|
|
51
53
|
'.aioson/docs/product/quality-lens.md',
|
|
@@ -81,6 +83,8 @@ const MANAGED_FILES = [
|
|
|
81
83
|
'.aioson/skills/process/briefing-expansion-scout/SKILL.md',
|
|
82
84
|
'.aioson/skills/process/product-scope-expansion/SKILL.md',
|
|
83
85
|
'.aioson/skills/process/sheldon-expansion-audit/SKILL.md',
|
|
86
|
+
'.aioson/skills/process/prototype-forge/SKILL.md',
|
|
87
|
+
'.aioson/skills/process/reference-identity-extract/SKILL.md',
|
|
84
88
|
'.aioson/skills/static/laravel-conventions.md',
|
|
85
89
|
'.aioson/skills/static/tall-stack-patterns.md',
|
|
86
90
|
'.aioson/skills/static/jetstream-setup.md',
|
|
@@ -31,6 +31,12 @@ function buildBlock(templateContent) {
|
|
|
31
31
|
return `${MARKER_BEGIN}\n${BLOCK_NOTICE}\n\n${body}${MARKER_END}\n`;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
function trimBlankLinesAfterBlock(content, rangeEnd) {
|
|
35
|
+
let end = rangeEnd;
|
|
36
|
+
while (content[end] === '\n' || content[end] === '\r') end += 1;
|
|
37
|
+
return end;
|
|
38
|
+
}
|
|
39
|
+
|
|
34
40
|
function findBlockRange(content) {
|
|
35
41
|
const start = content.indexOf(MARKER_BEGIN);
|
|
36
42
|
if (start === -1) return null;
|
|
@@ -71,7 +77,7 @@ async function mergeGatewayPointer({ templatePath, targetPath, backupRoot, targe
|
|
|
71
77
|
let action;
|
|
72
78
|
if (range) {
|
|
73
79
|
const before = stripLegacyUnmanagedGateway(existing.slice(0, range.start));
|
|
74
|
-
const after = existing.slice(range.end);
|
|
80
|
+
const after = existing.slice(trimBlankLinesAfterBlock(existing, range.end));
|
|
75
81
|
const cleanBefore = before.length === 0 || before.endsWith('\n') ? before : `${before}\n`;
|
|
76
82
|
next = `${cleanBefore}${block}${after}`;
|
|
77
83
|
action = 'block_updated';
|