@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
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* aioson review:feature [path] [--feature=<slug>] [--scope=<target>]
|
|
5
|
+
* [--skip-audit] [--out-dir=<dir>] [--tool=<tool>] [--json]
|
|
6
|
+
*
|
|
7
|
+
* One-shot "review an already-implemented feature" helper. It does NOT replace
|
|
8
|
+
* the workflow gates — it is the post-close / standalone path for re-running the
|
|
9
|
+
* adversarial + test passes against a feature:
|
|
10
|
+
*
|
|
11
|
+
* 1. runs the deterministic `security:audit` for the slug (writes/refreshes
|
|
12
|
+
* security-findings-{slug}.json),
|
|
13
|
+
* 2. prepares the `@pentester` and `@tester` activation prompts (read-only,
|
|
14
|
+
* headless — no runtime session, no state mutation),
|
|
15
|
+
* 3. prints a copy-pasteable plan (or saves the prompts to files / emits JSON).
|
|
16
|
+
*
|
|
17
|
+
* Slug resolution: explicit `--feature`/`--slug` wins; otherwise the active
|
|
18
|
+
* feature (pulse / unique in_progress). After `feature:close` there is no active
|
|
19
|
+
* feature, so the caller must pass `--feature` — the command says so explicitly.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const path = require('node:path');
|
|
23
|
+
const fs = require('node:fs');
|
|
24
|
+
|
|
25
|
+
const { runSecurityAudit } = require('./security-audit');
|
|
26
|
+
const { runAgentPrompt } = require('./agents');
|
|
27
|
+
const { resolveActiveFeature } = require('./feature-current');
|
|
28
|
+
|
|
29
|
+
function silentLogger() {
|
|
30
|
+
return { log() {}, error() {} };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Build an agent activation prompt without side effects (headless skips workflow
|
|
34
|
+
// routing, runtime bootstrap and telemetry). Returns the prompt string, or an
|
|
35
|
+
// `{ error }` object when the prompt could not be built.
|
|
36
|
+
async function buildAgentPromptText(agentName, targetDir, agentOptions, t) {
|
|
37
|
+
try {
|
|
38
|
+
const res = await runAgentPrompt({
|
|
39
|
+
args: [agentName, targetDir],
|
|
40
|
+
options: { ...agentOptions, headless: true },
|
|
41
|
+
logger: silentLogger(),
|
|
42
|
+
t
|
|
43
|
+
});
|
|
44
|
+
return res && res.ok && typeof res.prompt === 'string' ? res.prompt : { error: 'prompt_unavailable' };
|
|
45
|
+
} catch (err) {
|
|
46
|
+
return { error: err.message };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function runReviewFeature({ args, options = {}, logger, t }) {
|
|
51
|
+
const targetDir = path.resolve(process.cwd(), args?.[0] || '.');
|
|
52
|
+
const tool = options.tool || 'claude';
|
|
53
|
+
|
|
54
|
+
// 1. Resolve the feature slug.
|
|
55
|
+
let slug = String(options.feature || options.slug || '').trim();
|
|
56
|
+
let slugSource = slug ? 'explicit' : null;
|
|
57
|
+
if (!slug) {
|
|
58
|
+
const active = await resolveActiveFeature(targetDir);
|
|
59
|
+
if (active.ambiguous) {
|
|
60
|
+
const msg = `review:feature: ${active.candidates.length} features are in_progress (${active.candidates.join(', ')}). Pass --feature=<slug>.`;
|
|
61
|
+
if (options.json) return { ok: false, reason: 'ambiguous_feature', candidates: active.candidates };
|
|
62
|
+
logger.error(msg);
|
|
63
|
+
return { ok: false, reason: 'ambiguous_feature' };
|
|
64
|
+
}
|
|
65
|
+
if (!active.slug) {
|
|
66
|
+
const msg = 'review:feature: no active feature found — pass --feature=<slug> (a closed feature is no longer the active one).';
|
|
67
|
+
if (options.json) return { ok: false, reason: 'missing_feature' };
|
|
68
|
+
logger.error(msg);
|
|
69
|
+
return { ok: false, reason: 'missing_feature' };
|
|
70
|
+
}
|
|
71
|
+
slug = active.slug;
|
|
72
|
+
slugSource = active.source;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 2. Deterministic security audit (non-fatal — the review still produces the
|
|
76
|
+
// agent prompts even when the audit cannot run, e.g. archived artifacts).
|
|
77
|
+
let audit = null;
|
|
78
|
+
const skipAudit = Boolean(options['skip-audit'] || options.skipAudit);
|
|
79
|
+
if (!skipAudit) {
|
|
80
|
+
// security:audit sets process.exitCode by finding severity. review:feature
|
|
81
|
+
// is a prep/report command — surfacing findings is its job, not a failure —
|
|
82
|
+
// so capture the audit's verdict in the result but do not let it set this
|
|
83
|
+
// command's exit code.
|
|
84
|
+
const prevExitCode = process.exitCode;
|
|
85
|
+
audit = await runSecurityAudit({
|
|
86
|
+
args: [targetDir],
|
|
87
|
+
options: { feature: slug, json: true, ...(options.now ? { now: options.now } : {}) },
|
|
88
|
+
logger: silentLogger()
|
|
89
|
+
});
|
|
90
|
+
process.exitCode = prevExitCode;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// 3. Prepare the @pentester + @tester prompts.
|
|
94
|
+
const scope = String(options.scope || '').trim();
|
|
95
|
+
const pentesterPrompt = await buildAgentPromptText('pentester', targetDir, {
|
|
96
|
+
tool,
|
|
97
|
+
feature: slug,
|
|
98
|
+
...(scope ? { mode: 'app_target', scope } : {})
|
|
99
|
+
}, t);
|
|
100
|
+
const testerPrompt = await buildAgentPromptText('tester', targetDir, { tool, feature: slug }, t);
|
|
101
|
+
|
|
102
|
+
// optional: persist the prompts to files
|
|
103
|
+
const outDirOpt = options['out-dir'] || options.outDir;
|
|
104
|
+
const saved = {};
|
|
105
|
+
if (outDirOpt) {
|
|
106
|
+
const dir = path.resolve(targetDir, String(outDirOpt));
|
|
107
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
108
|
+
if (typeof pentesterPrompt === 'string') {
|
|
109
|
+
const p = path.join(dir, `review-pentester-${slug}.prompt.md`);
|
|
110
|
+
fs.writeFileSync(p, pentesterPrompt, 'utf8');
|
|
111
|
+
saved.pentester = p;
|
|
112
|
+
}
|
|
113
|
+
if (typeof testerPrompt === 'string') {
|
|
114
|
+
const p = path.join(dir, `review-tester-${slug}.prompt.md`);
|
|
115
|
+
fs.writeFileSync(p, testerPrompt, 'utf8');
|
|
116
|
+
saved.tester = p;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const result = {
|
|
121
|
+
ok: true,
|
|
122
|
+
slug,
|
|
123
|
+
slug_source: slugSource,
|
|
124
|
+
audit: audit
|
|
125
|
+
? {
|
|
126
|
+
ok: audit.ok,
|
|
127
|
+
exitCode: audit.exitCode,
|
|
128
|
+
classification: audit.classification || null,
|
|
129
|
+
summary: audit.summary || null,
|
|
130
|
+
findingsCount: audit.findingsCount ?? null,
|
|
131
|
+
artifactPath: audit.artifactPath || null,
|
|
132
|
+
reason: audit.reason || null
|
|
133
|
+
}
|
|
134
|
+
: null,
|
|
135
|
+
prompts: {
|
|
136
|
+
pentester: typeof pentesterPrompt === 'string' ? pentesterPrompt : null,
|
|
137
|
+
tester: typeof testerPrompt === 'string' ? testerPrompt : null
|
|
138
|
+
},
|
|
139
|
+
prompt_errors: {
|
|
140
|
+
pentester: pentesterPrompt && pentesterPrompt.error ? pentesterPrompt.error : null,
|
|
141
|
+
tester: testerPrompt && testerPrompt.error ? testerPrompt.error : null
|
|
142
|
+
},
|
|
143
|
+
saved,
|
|
144
|
+
commands: {
|
|
145
|
+
pentester: `aioson agent:prompt pentester . --tool=${tool} --mode=app_target --feature=${slug} --scope=<target>`,
|
|
146
|
+
tester: `aioson agent:prompt tester . --tool=${tool} --feature=${slug}`
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
if (options.json) return result;
|
|
151
|
+
|
|
152
|
+
// ── human output ──
|
|
153
|
+
const resolvedNote = slugSource && slugSource !== 'explicit' ? ` (resolved from ${slugSource})` : '';
|
|
154
|
+
logger.log(`Review plan — feature: ${slug}${resolvedNote}`);
|
|
155
|
+
logger.log('');
|
|
156
|
+
|
|
157
|
+
if (!skipAudit && audit) {
|
|
158
|
+
const s = audit.summary;
|
|
159
|
+
if (s) {
|
|
160
|
+
logger.log(`1. security:audit (deterministic) — ${audit.classification || '?'}: ${s.critical || 0} critical, ${s.high || 0} high, ${s.medium || 0} medium, ${s.low || 0} low, ${s.inconclusive || 0} inconclusive`);
|
|
161
|
+
if (audit.artifactPath) logger.log(` findings → ${path.relative(targetDir, audit.artifactPath)}`);
|
|
162
|
+
} else {
|
|
163
|
+
logger.log(`1. security:audit — could not run (${audit.reason || 'unknown'}); proceeding with the agent prompts.`);
|
|
164
|
+
}
|
|
165
|
+
} else {
|
|
166
|
+
logger.log('1. security:audit — skipped (--skip-audit)');
|
|
167
|
+
}
|
|
168
|
+
logger.log('');
|
|
169
|
+
|
|
170
|
+
logger.log('2. @pentester — adversarial review (vulnerabilities, threat surface):');
|
|
171
|
+
if (saved.pentester) logger.log(` saved → ${path.relative(targetDir, saved.pentester)}`);
|
|
172
|
+
if (!scope) logger.log(' tip: pass --scope=<target> for a precise app_target review contract');
|
|
173
|
+
logger.log('');
|
|
174
|
+
logger.log(typeof pentesterPrompt === 'string'
|
|
175
|
+
? pentesterPrompt
|
|
176
|
+
: ` [could not build @pentester prompt: ${pentesterPrompt.error}]`);
|
|
177
|
+
logger.log('');
|
|
178
|
+
|
|
179
|
+
logger.log('3. @tester — engineering test pass (missing tests, regressions):');
|
|
180
|
+
if (saved.tester) logger.log(` saved → ${path.relative(targetDir, saved.tester)}`);
|
|
181
|
+
logger.log('');
|
|
182
|
+
logger.log(typeof testerPrompt === 'string'
|
|
183
|
+
? testerPrompt
|
|
184
|
+
: ` [could not build @tester prompt: ${testerPrompt.error}]`);
|
|
185
|
+
|
|
186
|
+
return result;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
module.exports = { runReviewFeature };
|
package/src/commands/runtime.js
CHANGED
|
@@ -1171,6 +1171,12 @@ async function runRuntimeLog({ args, options = {}, logger, t }) {
|
|
|
1171
1171
|
*
|
|
1172
1172
|
* Intended to be called ONCE at the very end of an agent session, after delivering the main artifact.
|
|
1173
1173
|
*/
|
|
1174
|
+
function logVerifyArtifactLine(logger, va) {
|
|
1175
|
+
if (!va) return;
|
|
1176
|
+
const marker = va.skipped ? 'hint' : va.ok ? 'ok' : 'advisory';
|
|
1177
|
+
logger.log(`agent:done — verify:artifact (${va.kind}): ${marker}${va.reason ? ` — ${va.reason}` : ''}`);
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1174
1180
|
async function runAgentDone({ args, options = {}, logger, t }) {
|
|
1175
1181
|
const targetDir = resolveTargetDir(args);
|
|
1176
1182
|
const agentName = String(options.agent || '').trim();
|
|
@@ -1187,6 +1193,13 @@ async function runAgentDone({ args, options = {}, logger, t }) {
|
|
|
1187
1193
|
? String(options.artifacts).split(',').map((p) => p.trim()).filter(Boolean)
|
|
1188
1194
|
: [];
|
|
1189
1195
|
|
|
1196
|
+
// Build-free artifact done-gate (advisory): for the peripheral agents that
|
|
1197
|
+
// produce a non-code artifact, prove it is complete/well-formed at the same
|
|
1198
|
+
// session-end call they always make, instead of relying on each agent to run
|
|
1199
|
+
// its `## Done gate` line. Resolved once here; surfaced on every return path.
|
|
1200
|
+
const { verifyAgentArtifact } = require('../artifact-kinds');
|
|
1201
|
+
const verifyArtifact = await verifyAgentArtifact({ targetDir, agent: normalizedAgent, options });
|
|
1202
|
+
|
|
1190
1203
|
const { db, dbPath, runtimeDir } = await openRuntimeDb(targetDir);
|
|
1191
1204
|
|
|
1192
1205
|
try {
|
|
@@ -1224,7 +1237,7 @@ async function runAgentDone({ args, options = {}, logger, t }) {
|
|
|
1224
1237
|
}
|
|
1225
1238
|
|
|
1226
1239
|
// F2 (workflow-handoff-integrity v1.9.5) — best-effort auto-advance workflow pointer
|
|
1227
|
-
const autoAdvance = await maybeAutoAdvanceWorkflow({ targetDir, normalizedAgent, options, logger, t });
|
|
1240
|
+
const autoAdvance = await maybeAutoAdvanceWorkflow({ targetDir, normalizedAgent, options, logger, t });
|
|
1228
1241
|
|
|
1229
1242
|
if (isDocCreatingAgent(normalizedAgent)) {
|
|
1230
1243
|
backupAiosonDocs(targetDir).catch(() => {});
|
|
@@ -1252,7 +1265,8 @@ async function runAgentDone({ args, options = {}, logger, t }) {
|
|
|
1252
1265
|
});
|
|
1253
1266
|
} catch { /* ignore — never blocks agent_done */ }
|
|
1254
1267
|
|
|
1255
|
-
|
|
1268
|
+
if (!options.json) logVerifyArtifactLine(logger, verifyArtifact);
|
|
1269
|
+
return { ok: true, targetDir, dbPath, agent: normalizedAgent, mode: 'live_event', runKey: session.runKey, auto_advance: autoAdvance, verify_artifact: verifyArtifact };
|
|
1256
1270
|
}
|
|
1257
1271
|
|
|
1258
1272
|
// No active session — create a standalone task+run and immediately complete it.
|
|
@@ -1297,7 +1311,7 @@ async function runAgentDone({ args, options = {}, logger, t }) {
|
|
|
1297
1311
|
}
|
|
1298
1312
|
|
|
1299
1313
|
// F2 (workflow-handoff-integrity v1.9.5) — best-effort auto-advance workflow pointer
|
|
1300
|
-
const autoAdvance = await maybeAutoAdvanceWorkflow({ targetDir, normalizedAgent, options, logger, t });
|
|
1314
|
+
const autoAdvance = await maybeAutoAdvanceWorkflow({ targetDir, normalizedAgent, options, logger, t });
|
|
1301
1315
|
|
|
1302
1316
|
if (isDocCreatingAgent(normalizedAgent)) {
|
|
1303
1317
|
backupAiosonDocs(targetDir).catch(() => {});
|
|
@@ -1325,7 +1339,8 @@ async function runAgentDone({ args, options = {}, logger, t }) {
|
|
|
1325
1339
|
});
|
|
1326
1340
|
} catch { /* ignore — never blocks agent_done */ }
|
|
1327
1341
|
|
|
1328
|
-
|
|
1342
|
+
if (!options.json) logVerifyArtifactLine(logger, verifyArtifact);
|
|
1343
|
+
return { ok: true, targetDir, dbPath, agent: normalizedAgent, mode: 'standalone', runKey, taskKey, auto_advance: autoAdvance, verify_artifact: verifyArtifact };
|
|
1329
1344
|
} finally {
|
|
1330
1345
|
db.close();
|
|
1331
1346
|
}
|
|
@@ -2026,9 +2041,9 @@ function parseDurationMs(value, defaultHours = 24) {
|
|
|
2026
2041
|
*
|
|
2027
2042
|
* Sources checked:
|
|
2028
2043
|
* 1. Session files in .aioson/.sessions/ with finished=false older than threshold.
|
|
2029
|
-
* 2. agent_runs rows with status='running'/'queued' older than threshold
|
|
2030
|
-
* that have no corresponding live session file (orphaned DB records).
|
|
2031
|
-
* 3. workflow tasks/runs left running after a workflow finished or was abandoned.
|
|
2044
|
+
* 2. agent_runs rows with status='running'/'queued' older than threshold
|
|
2045
|
+
* that have no corresponding live session file (orphaned DB records).
|
|
2046
|
+
* 3. workflow tasks/runs left running after a workflow finished or was abandoned.
|
|
2032
2047
|
*
|
|
2033
2048
|
* --older-than Duration threshold. Accepts: 24h (default), 8h, 30m, 7d.
|
|
2034
2049
|
* --dry-run Report what would be recovered without making any changes.
|
|
@@ -2109,31 +2124,31 @@ async function runAgentRecover({ args, options = {}, logger }) {
|
|
|
2109
2124
|
recovered.push({ source: 'session_file', agent: agentName, runKey, taskKey, startedAt: session.startedAt });
|
|
2110
2125
|
}
|
|
2111
2126
|
|
|
2112
|
-
// ── 2. Scan DB for orphaned running runs (no session file) ────────────────
|
|
2113
|
-
const orphanedRuns = db.prepare(`
|
|
2114
|
-
SELECT run_key, task_key, agent_name, source, started_at
|
|
2115
|
-
FROM agent_runs
|
|
2116
|
-
WHERE status IN ('running', 'queued')
|
|
2117
|
-
AND COALESCE(started_at, updated_at, '') < ?
|
|
2118
|
-
`).all(cutoffIso);
|
|
2119
|
-
|
|
2120
|
-
for (const run of orphanedRuns) {
|
|
2121
|
-
// Skip if already recovered via session file
|
|
2122
|
-
if (recovered.some((r) => r.runKey === run.run_key)) continue;
|
|
2123
|
-
const source = run.source === 'workflow' ? 'workflow_run' : 'orphaned_run';
|
|
2124
|
-
const summary = source === 'workflow_run'
|
|
2125
|
-
? 'Recovered: stale workflow run left running'
|
|
2126
|
-
: 'Recovered: orphaned run with no session file';
|
|
2127
|
-
|
|
2128
|
-
if (!dryRun) {
|
|
2129
|
-
db.prepare(`
|
|
2130
|
-
UPDATE agent_runs
|
|
2131
|
-
SET status = 'abandoned', summary = ?, updated_at = ?, finished_at = ?
|
|
2132
|
-
WHERE run_key = ?
|
|
2133
|
-
`).run(summary, now, now, run.run_key);
|
|
2134
|
-
|
|
2135
|
-
if (run.task_key) {
|
|
2136
|
-
const taskRow = db.prepare('SELECT task_key, status FROM tasks WHERE task_key = ?').get(run.task_key);
|
|
2127
|
+
// ── 2. Scan DB for orphaned running runs (no session file) ────────────────
|
|
2128
|
+
const orphanedRuns = db.prepare(`
|
|
2129
|
+
SELECT run_key, task_key, agent_name, source, started_at
|
|
2130
|
+
FROM agent_runs
|
|
2131
|
+
WHERE status IN ('running', 'queued')
|
|
2132
|
+
AND COALESCE(started_at, updated_at, '') < ?
|
|
2133
|
+
`).all(cutoffIso);
|
|
2134
|
+
|
|
2135
|
+
for (const run of orphanedRuns) {
|
|
2136
|
+
// Skip if already recovered via session file
|
|
2137
|
+
if (recovered.some((r) => r.runKey === run.run_key)) continue;
|
|
2138
|
+
const source = run.source === 'workflow' ? 'workflow_run' : 'orphaned_run';
|
|
2139
|
+
const summary = source === 'workflow_run'
|
|
2140
|
+
? 'Recovered: stale workflow run left running'
|
|
2141
|
+
: 'Recovered: orphaned run with no session file';
|
|
2142
|
+
|
|
2143
|
+
if (!dryRun) {
|
|
2144
|
+
db.prepare(`
|
|
2145
|
+
UPDATE agent_runs
|
|
2146
|
+
SET status = 'abandoned', summary = ?, updated_at = ?, finished_at = ?
|
|
2147
|
+
WHERE run_key = ?
|
|
2148
|
+
`).run(summary, now, now, run.run_key);
|
|
2149
|
+
|
|
2150
|
+
if (run.task_key) {
|
|
2151
|
+
const taskRow = db.prepare('SELECT task_key, status FROM tasks WHERE task_key = ?').get(run.task_key);
|
|
2137
2152
|
if (taskRow && (taskRow.status === 'running' || taskRow.status === 'queued')) {
|
|
2138
2153
|
db.prepare(`
|
|
2139
2154
|
UPDATE tasks
|
|
@@ -2141,40 +2156,40 @@ async function runAgentRecover({ args, options = {}, logger }) {
|
|
|
2141
2156
|
WHERE task_key = ?
|
|
2142
2157
|
`).run(now, now, run.task_key);
|
|
2143
2158
|
}
|
|
2144
|
-
}
|
|
2145
|
-
}
|
|
2146
|
-
|
|
2147
|
-
recovered.push({ source, agent: run.agent_name, runKey: run.run_key, taskKey: run.task_key, startedAt: run.started_at });
|
|
2148
|
-
}
|
|
2149
|
-
|
|
2150
|
-
// ── 3. Scan DB for stale workflow tasks without a recovered run ───────────
|
|
2151
|
-
const staleWorkflowTasks = db.prepare(`
|
|
2152
|
-
SELECT task_key, title, created_by, session_key, created_at, updated_at
|
|
2153
|
-
FROM tasks
|
|
2154
|
-
WHERE status IN ('running', 'queued')
|
|
2155
|
-
AND (created_by = '@workflow' OR session_key LIKE 'workflow:%')
|
|
2156
|
-
AND COALESCE(updated_at, created_at, '') < ?
|
|
2157
|
-
`).all(cutoffIso);
|
|
2158
|
-
|
|
2159
|
-
for (const task of staleWorkflowTasks) {
|
|
2160
|
-
if (recovered.some((r) => r.taskKey === task.task_key)) continue;
|
|
2161
|
-
|
|
2162
|
-
if (!dryRun) {
|
|
2163
|
-
db.prepare(`
|
|
2164
|
-
UPDATE tasks
|
|
2165
|
-
SET status = 'abandoned', updated_at = ?, finished_at = ?
|
|
2166
|
-
WHERE task_key = ?
|
|
2167
|
-
`).run(now, now, task.task_key);
|
|
2168
|
-
}
|
|
2169
|
-
|
|
2170
|
-
recovered.push({
|
|
2171
|
-
source: 'workflow_task',
|
|
2172
|
-
agent: task.created_by || '@workflow',
|
|
2173
|
-
runKey: null,
|
|
2174
|
-
taskKey: task.task_key,
|
|
2175
|
-
startedAt: task.updated_at || task.created_at
|
|
2176
|
-
});
|
|
2177
|
-
}
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
|
|
2162
|
+
recovered.push({ source, agent: run.agent_name, runKey: run.run_key, taskKey: run.task_key, startedAt: run.started_at });
|
|
2163
|
+
}
|
|
2164
|
+
|
|
2165
|
+
// ── 3. Scan DB for stale workflow tasks without a recovered run ───────────
|
|
2166
|
+
const staleWorkflowTasks = db.prepare(`
|
|
2167
|
+
SELECT task_key, title, created_by, session_key, created_at, updated_at
|
|
2168
|
+
FROM tasks
|
|
2169
|
+
WHERE status IN ('running', 'queued')
|
|
2170
|
+
AND (created_by = '@workflow' OR session_key LIKE 'workflow:%')
|
|
2171
|
+
AND COALESCE(updated_at, created_at, '') < ?
|
|
2172
|
+
`).all(cutoffIso);
|
|
2173
|
+
|
|
2174
|
+
for (const task of staleWorkflowTasks) {
|
|
2175
|
+
if (recovered.some((r) => r.taskKey === task.task_key)) continue;
|
|
2176
|
+
|
|
2177
|
+
if (!dryRun) {
|
|
2178
|
+
db.prepare(`
|
|
2179
|
+
UPDATE tasks
|
|
2180
|
+
SET status = 'abandoned', updated_at = ?, finished_at = ?
|
|
2181
|
+
WHERE task_key = ?
|
|
2182
|
+
`).run(now, now, task.task_key);
|
|
2183
|
+
}
|
|
2184
|
+
|
|
2185
|
+
recovered.push({
|
|
2186
|
+
source: 'workflow_task',
|
|
2187
|
+
agent: task.created_by || '@workflow',
|
|
2188
|
+
runKey: null,
|
|
2189
|
+
taskKey: task.task_key,
|
|
2190
|
+
startedAt: task.updated_at || task.created_at
|
|
2191
|
+
});
|
|
2192
|
+
}
|
|
2178
2193
|
|
|
2179
2194
|
// ── Output ────────────────────────────────────────────────────────────────
|
|
2180
2195
|
const olderThanLabel = options['older-than'] || options.olderThan || '24h';
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Cross-platform replacement for the legacy `rsync -av template/ ./` step in the
|
|
4
|
+
// `sync:agents` npm script. rsync is not available on Windows, which silently
|
|
5
|
+
// left the workspace un-synced. This mirrors template/ into the repo root using
|
|
6
|
+
// Node's fs, replicating the original rsync excludes:
|
|
7
|
+
// --exclude='config.md' --exclude='runtime/' --exclude='backups/' --exclude='mcp/servers.local.json'
|
|
8
|
+
// rsync matched those names at any depth; we replicate that (basename / dir
|
|
9
|
+
// segment / path suffix).
|
|
10
|
+
//
|
|
11
|
+
// We also exclude the live project-state files (project-pulse.md, project-map.md,
|
|
12
|
+
// learning-loop.json, git-guard.json): the dogfooding workspace owns its own
|
|
13
|
+
// evolving state, so a template sync must never overwrite it with the template
|
|
14
|
+
// seeds. External project installs (`aioson update`) are a separate code path and
|
|
15
|
+
// still receive the seeds.
|
|
16
|
+
//
|
|
17
|
+
// Gateway-pointer files (CLAUDE.md / AGENTS.md / OPENCODE.md) carry an AIOSON
|
|
18
|
+
// managed block (<!-- AIOSON:BEGIN --> … <!-- AIOSON:END -->) when a project was
|
|
19
|
+
// installed/updated. A blind copy of the raw template body STRIPS that block (and
|
|
20
|
+
// any project content around it). So when the destination already has a managed
|
|
21
|
+
// block, we refresh it in place via mergeGatewayPointer (same path the installer
|
|
22
|
+
// uses) instead of clobbering it. When the destination has no block we keep the
|
|
23
|
+
// plain copy — that leaves the framework repo's own raw CLAUDE.md untouched.
|
|
24
|
+
|
|
25
|
+
const fs = require('node:fs/promises');
|
|
26
|
+
const path = require('node:path');
|
|
27
|
+
const {
|
|
28
|
+
isGatewayPointerPath,
|
|
29
|
+
findBlockRange,
|
|
30
|
+
mergeGatewayPointer
|
|
31
|
+
} = require('../gateway-pointer-merge');
|
|
32
|
+
|
|
33
|
+
const EXCLUDE_BASENAMES = new Set([
|
|
34
|
+
'config.md',
|
|
35
|
+
// live project-state — owned by the workspace, never clobbered by template seeds
|
|
36
|
+
'project-pulse.md',
|
|
37
|
+
'project-map.md',
|
|
38
|
+
'learning-loop.json',
|
|
39
|
+
'git-guard.json'
|
|
40
|
+
]);
|
|
41
|
+
const EXCLUDE_DIR_NAMES = new Set(['runtime', 'backups']);
|
|
42
|
+
const EXCLUDE_SUFFIXES = [path.join('mcp', 'servers.local.json')];
|
|
43
|
+
|
|
44
|
+
// `relPath` is relative to template/ and uses the OS separator.
|
|
45
|
+
function isExcluded(relPath) {
|
|
46
|
+
const segments = relPath.split(path.sep).filter(Boolean);
|
|
47
|
+
const base = segments[segments.length - 1];
|
|
48
|
+
if (EXCLUDE_BASENAMES.has(base)) return true;
|
|
49
|
+
// a directory named runtime/ or backups/ anywhere in the path (or the dir itself)
|
|
50
|
+
if (segments.some((seg) => EXCLUDE_DIR_NAMES.has(seg))) return true;
|
|
51
|
+
if (EXCLUDE_SUFFIXES.some((suf) => relPath === suf || relPath.endsWith(path.sep + suf))) return true;
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Refresh an AIOSON-managed block in place when the destination already has one;
|
|
56
|
+
// otherwise fall back to a plain copy. Keeps markers + any project-authored
|
|
57
|
+
// content around the block intact (the blind copy would have stripped them).
|
|
58
|
+
async function copyGatewayPointer(srcChild, destChild) {
|
|
59
|
+
await fs.mkdir(path.dirname(destChild), { recursive: true });
|
|
60
|
+
let hasBlock = false;
|
|
61
|
+
try {
|
|
62
|
+
const existing = await fs.readFile(destChild, 'utf8');
|
|
63
|
+
hasBlock = findBlockRange(existing) !== null;
|
|
64
|
+
} catch {
|
|
65
|
+
hasBlock = false; // destination missing/unreadable → plain copy
|
|
66
|
+
}
|
|
67
|
+
if (hasBlock) {
|
|
68
|
+
await mergeGatewayPointer({ templatePath: srcChild, targetPath: destChild });
|
|
69
|
+
} else {
|
|
70
|
+
await fs.copyFile(srcChild, destChild);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function copyTree(srcRoot, destRoot, relPath = '') {
|
|
75
|
+
const entries = await fs.readdir(path.join(srcRoot, relPath), { withFileTypes: true });
|
|
76
|
+
let copied = 0;
|
|
77
|
+
for (const entry of entries) {
|
|
78
|
+
const childRel = path.join(relPath, entry.name);
|
|
79
|
+
if (isExcluded(childRel)) continue;
|
|
80
|
+
const srcChild = path.join(srcRoot, childRel);
|
|
81
|
+
const destChild = path.join(destRoot, childRel);
|
|
82
|
+
if (entry.isDirectory()) {
|
|
83
|
+
await fs.mkdir(destChild, { recursive: true });
|
|
84
|
+
copied += await copyTree(srcRoot, destRoot, childRel);
|
|
85
|
+
} else if (entry.isFile()) {
|
|
86
|
+
if (isGatewayPointerPath(childRel)) {
|
|
87
|
+
await copyGatewayPointer(srcChild, destChild);
|
|
88
|
+
} else {
|
|
89
|
+
await fs.mkdir(path.dirname(destChild), { recursive: true });
|
|
90
|
+
await fs.copyFile(srcChild, destChild);
|
|
91
|
+
}
|
|
92
|
+
copied += 1;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return copied;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function syncAgentsCopy(rootDir = process.cwd()) {
|
|
99
|
+
const templateDir = path.join(rootDir, 'template');
|
|
100
|
+
const copied = await copyTree(templateDir, rootDir);
|
|
101
|
+
return { copied };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = { syncAgentsCopy, isExcluded };
|
|
105
|
+
|
|
106
|
+
if (require.main === module) {
|
|
107
|
+
syncAgentsCopy(process.cwd())
|
|
108
|
+
.then(({ copied }) => {
|
|
109
|
+
console.log(`[sync:agents] mirrored template/ -> ./ (${copied} files, cross-platform copy; rsync not required)`);
|
|
110
|
+
})
|
|
111
|
+
.catch((err) => {
|
|
112
|
+
console.error(`[sync:agents] copy failed: ${err.message}`);
|
|
113
|
+
process.exitCode = 1;
|
|
114
|
+
});
|
|
115
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// `aioson verification:plan` — deterministic bridge between verification.json and
|
|
4
|
+
// the agents. Given a slug + trigger + run context, it resolves WHICH verification
|
|
5
|
+
// sub-agents should run, on WHICH host/model, and the phase-loop settings — so
|
|
6
|
+
// @dev (per-phase) and the post-dev cycle (end-of-feature) follow a computed plan
|
|
7
|
+
// instead of re-interpreting the config in prose. Read-only; never mutates state.
|
|
8
|
+
|
|
9
|
+
const fs = require('node:fs/promises');
|
|
10
|
+
const path = require('node:path');
|
|
11
|
+
const {
|
|
12
|
+
readVerificationConfig,
|
|
13
|
+
resolveHost,
|
|
14
|
+
getAgentDispatch,
|
|
15
|
+
shouldRunForTrigger,
|
|
16
|
+
resolveAgentReportPath,
|
|
17
|
+
getCrossCheck,
|
|
18
|
+
getBudget,
|
|
19
|
+
getPhaseLoop,
|
|
20
|
+
VERIFICATION_AGENTS,
|
|
21
|
+
TRIGGERS
|
|
22
|
+
} = require('../verification-policy');
|
|
23
|
+
|
|
24
|
+
const BAR = '─'.repeat(60);
|
|
25
|
+
const CLASSIFICATIONS = ['MICRO', 'SMALL', 'MEDIUM'];
|
|
26
|
+
|
|
27
|
+
async function readFileSafe(filePath) {
|
|
28
|
+
try {
|
|
29
|
+
return await fs.readFile(filePath, 'utf8');
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Best-effort classification: prd-{slug}.md frontmatter wins, then project.context.md.
|
|
36
|
+
async function detectClassification(targetDir, slug) {
|
|
37
|
+
const dir = path.join(targetDir, '.aioson', 'context');
|
|
38
|
+
const candidates = [];
|
|
39
|
+
if (slug) candidates.push(path.join(dir, `prd-${slug}.md`));
|
|
40
|
+
candidates.push(path.join(dir, 'project.context.md'));
|
|
41
|
+
for (const file of candidates) {
|
|
42
|
+
const text = await readFileSafe(file);
|
|
43
|
+
if (!text) continue;
|
|
44
|
+
const m = text.match(/^\s*classification:\s*["']?([A-Za-z]+)["']?\s*$/m);
|
|
45
|
+
if (m && CLASSIFICATIONS.includes(m[1].toUpperCase())) return m[1].toUpperCase();
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function runVerificationPlan({ args, options = {}, logger }) {
|
|
51
|
+
const targetDir = path.resolve(process.cwd(), args[0] || '.');
|
|
52
|
+
const slug = (options.feature || options.slug || '').toString().trim() || null;
|
|
53
|
+
|
|
54
|
+
const triggerRaw = (options.trigger || 'per-phase').toString().trim().toLowerCase();
|
|
55
|
+
const trigger = TRIGGERS.includes(triggerRaw) ? triggerRaw : 'per-phase';
|
|
56
|
+
|
|
57
|
+
const config = await readVerificationConfig(targetDir);
|
|
58
|
+
const host = resolveHost(config, options.host);
|
|
59
|
+
|
|
60
|
+
let classification = (options.classification || '').toString().trim().toUpperCase();
|
|
61
|
+
if (!CLASSIFICATIONS.includes(classification)) {
|
|
62
|
+
classification = (await detectClassification(targetDir, slug)) || 'SMALL';
|
|
63
|
+
}
|
|
64
|
+
const sensitiveSurface = Boolean(options.sensitive);
|
|
65
|
+
|
|
66
|
+
const context = { trigger, classification, sensitiveSurface };
|
|
67
|
+
const agents = [];
|
|
68
|
+
for (const agentId of VERIFICATION_AGENTS) {
|
|
69
|
+
const dispatch = getAgentDispatch(config, agentId, host);
|
|
70
|
+
const entry = {
|
|
71
|
+
agent: agentId,
|
|
72
|
+
run: shouldRunForTrigger(config, agentId, context),
|
|
73
|
+
mode: dispatch ? dispatch.mode : 'native',
|
|
74
|
+
model: dispatch ? dispatch.model : 'configured-default',
|
|
75
|
+
report: resolveAgentReportPath(config, agentId, slug || '{slug}')
|
|
76
|
+
};
|
|
77
|
+
if (agentId === 'validator') {
|
|
78
|
+
const cc = getCrossCheck(config, agentId);
|
|
79
|
+
if (cc) entry.cross_check = cc;
|
|
80
|
+
}
|
|
81
|
+
agents.push(entry);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const result = {
|
|
85
|
+
ok: true,
|
|
86
|
+
feature_slug: slug,
|
|
87
|
+
host,
|
|
88
|
+
trigger,
|
|
89
|
+
classification,
|
|
90
|
+
sensitive_surface: sensitiveSurface,
|
|
91
|
+
agents,
|
|
92
|
+
phase_loop: getPhaseLoop(config),
|
|
93
|
+
budget: getBudget(config)
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
if (options.json) return result;
|
|
97
|
+
|
|
98
|
+
logger.log('');
|
|
99
|
+
logger.log(`Verification plan${slug ? ` — ${slug}` : ''}`);
|
|
100
|
+
logger.log(BAR);
|
|
101
|
+
logger.log(`Host: ${host} Trigger: ${trigger} Class: ${classification}${sensitiveSurface ? ' [sensitive]' : ''}`);
|
|
102
|
+
logger.log(BAR);
|
|
103
|
+
for (const a of agents) {
|
|
104
|
+
const mark = a.run ? 'RUN ' : 'skip';
|
|
105
|
+
const cc = a.cross_check && a.cross_check.enabled ? ` + cross_check(${a.cross_check.tool}/${a.cross_check.model})` : '';
|
|
106
|
+
logger.log(` [${mark}] ${a.agent.padEnd(10)} ${a.mode}/${a.model}${a.run ? ` → ${a.report}` : ''}${cc}`);
|
|
107
|
+
}
|
|
108
|
+
logger.log(BAR);
|
|
109
|
+
const pl = result.phase_loop;
|
|
110
|
+
logger.log(`Phase loop: auto_continue=${pl.auto_continue} compact_between_phases=${pl.compact_between_phases} max_fix_retries=${pl.max_fix_retries_per_phase}`);
|
|
111
|
+
logger.log('');
|
|
112
|
+
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
module.exports = { runVerificationPlan };
|