@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,90 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { execFileSync } = require('node:child_process');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Deterministically detect whether a feature ships a runtime surface, using
|
|
9
|
+
* ONLY signals the framework can locate reliably:
|
|
10
|
+
*
|
|
11
|
+
* - a prototype-manifest (`.aioson/briefings/{slug}/prototype-manifest.md`)
|
|
12
|
+
* ⇒ a clickable prototype whose Core interactions must work on the real
|
|
13
|
+
* stack (the flow-deck failure class);
|
|
14
|
+
* - a migration / Prisma path among `progress.completed_steps` ⇒ a DB feature
|
|
15
|
+
* whose migrations must actually apply.
|
|
16
|
+
*
|
|
17
|
+
* The Play `manifest.json` `has_api` flag lives in the TARGET app and is not
|
|
18
|
+
* reliably locatable from the framework, so that trigger stays with the LLM
|
|
19
|
+
* @validator (harness-contract.md §2c). A `false` result here means "no reliable
|
|
20
|
+
* runtime signal", NOT "proven non-runtime" — callers treat it conservatively.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
// Path segments / extensions that reliably indicate database migration work.
|
|
24
|
+
const MIGRATION_HINTS = [
|
|
25
|
+
/(?:^|[\\/])migrations?(?:[\\/]|$)/i,
|
|
26
|
+
/(?:^|[\\/])prisma(?:[\\/]|$)/i,
|
|
27
|
+
/\.prisma$/i,
|
|
28
|
+
/(?:^|[\\/])migrate(?:[\\/]|$)/i
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
function looksLikeMigration(step) {
|
|
32
|
+
const value = String(step || '');
|
|
33
|
+
return MIGRATION_HINTS.some((re) => re.test(value));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The set of paths git considers changed in the working tree: unstaged,
|
|
38
|
+
* staged, and untracked-but-not-ignored. Read-only and best-effort — a
|
|
39
|
+
* non-git project (or any git failure) yields `[]`, so callers degrade to
|
|
40
|
+
* prototype/progress-based detection. Lives here (not only in the gate) so
|
|
41
|
+
* the standalone `aioson harness:check` detects the same migration surface
|
|
42
|
+
* the workflow finalize / feature:close gate does.
|
|
43
|
+
*/
|
|
44
|
+
function gitChangedFiles(targetDir) {
|
|
45
|
+
const files = new Set();
|
|
46
|
+
const commands = [
|
|
47
|
+
['diff', '--name-only'],
|
|
48
|
+
['diff', '--name-only', '--cached'],
|
|
49
|
+
['ls-files', '--others', '--exclude-standard']
|
|
50
|
+
];
|
|
51
|
+
for (const args of commands) {
|
|
52
|
+
try {
|
|
53
|
+
const output = execFileSync('git', args, {
|
|
54
|
+
cwd: targetDir,
|
|
55
|
+
encoding: 'utf8',
|
|
56
|
+
stdio: ['ignore', 'pipe', 'ignore']
|
|
57
|
+
});
|
|
58
|
+
for (const line of output.split(/\r?\n/)) {
|
|
59
|
+
const trimmed = line.trim();
|
|
60
|
+
if (trimmed) files.add(trimmed);
|
|
61
|
+
}
|
|
62
|
+
} catch {
|
|
63
|
+
// Non-git projects still get prototype/progress based detection.
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return [...files];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function detectRuntimeFeature(targetDir, slug, options = {}) {
|
|
70
|
+
const signals = [];
|
|
71
|
+
|
|
72
|
+
if (slug) {
|
|
73
|
+
try {
|
|
74
|
+
const protoManifest = path.join(
|
|
75
|
+
targetDir, '.aioson', 'briefings', String(slug), 'prototype-manifest.md'
|
|
76
|
+
);
|
|
77
|
+
if (fs.existsSync(protoManifest)) signals.push('prototype-manifest');
|
|
78
|
+
} catch {
|
|
79
|
+
/* ignore — detection is best-effort */
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const completedSteps = Array.isArray(options.completedSteps) ? options.completedSteps : [];
|
|
84
|
+
const changedFiles = Array.isArray(options.changedFiles) ? options.changedFiles : [];
|
|
85
|
+
if ([...completedSteps, ...changedFiles].some(looksLikeMigration)) signals.push('migrations');
|
|
86
|
+
|
|
87
|
+
return { isRuntimeFeature: signals.length > 0, signals };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
module.exports = { detectRuntimeFeature, looksLikeMigration, gitChangedFiles };
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Static-pattern criteria (SG-* — Static Gate) — the build-independent half of
|
|
5
|
+
* the harness contract.
|
|
6
|
+
*
|
|
7
|
+
* Where an RG-* criterion EXECUTES a command (a working build, migrations, a
|
|
8
|
+
* booted app — expensive, run once at the last gate per A5), an SG-* criterion
|
|
9
|
+
* proves a claim by READING the changed files: a required pattern is present
|
|
10
|
+
* (the code was actually written / wired) and a forbidden pattern is absent (no
|
|
11
|
+
* placeholder, stub or anti-pattern snuck in). Pure fs + RegExp +
|
|
12
|
+
* `node --check`/`JSON.parse` — no shell, no build — so it costs ~milliseconds
|
|
13
|
+
* and runs at EVERY gate (@dev-done, @qa-done; advisory in per-phase
|
|
14
|
+
* `harness:check`), cross-platform by construction.
|
|
15
|
+
*
|
|
16
|
+
* Two build-free pattern checks over the declared files:
|
|
17
|
+
* must_match OR-across-files — each pattern must appear in >= 1 file
|
|
18
|
+
* must_not_match absent-in-all — no pattern may appear in ANY file
|
|
19
|
+
* plus a post-write parse-check (the `parseable` gate) to catch a truncated or
|
|
20
|
+
* corrupted write before it reaches a human.
|
|
21
|
+
*
|
|
22
|
+
* An invalid regex degrades to a literal substring test instead of throwing — a
|
|
23
|
+
* contract typo never crashes the gate, it just matches literally.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const fs = require('node:fs');
|
|
27
|
+
const path = require('node:path');
|
|
28
|
+
const { spawnSync } = require('node:child_process');
|
|
29
|
+
|
|
30
|
+
/** A criterion is static when it carries a non-empty must_match or must_not_match. */
|
|
31
|
+
function isStaticCriterion(criterion) {
|
|
32
|
+
if (!criterion || typeof criterion !== 'object') return false;
|
|
33
|
+
return (
|
|
34
|
+
(Array.isArray(criterion.must_match) && criterion.must_match.length > 0) ||
|
|
35
|
+
(Array.isArray(criterion.must_not_match) && criterion.must_not_match.length > 0)
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Compile a pattern to a tester: a real RegExp when the source is valid, else a
|
|
41
|
+
* literal-substring test (a contract typo matches literally, never throws).
|
|
42
|
+
*/
|
|
43
|
+
function patternTester(pattern) {
|
|
44
|
+
const src = String(pattern);
|
|
45
|
+
try {
|
|
46
|
+
const re = new RegExp(src);
|
|
47
|
+
return (text) => re.test(text);
|
|
48
|
+
} catch {
|
|
49
|
+
return (text) => text.includes(src);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Parse-check a file by extension. Returns { checked, ok, reason }.
|
|
55
|
+
* `checked: false` means no stdlib parser applies to this extension (skipped,
|
|
56
|
+
* like the harness skips .ts/.md) — never a failure.
|
|
57
|
+
*/
|
|
58
|
+
function parseCheckFile(absPath) {
|
|
59
|
+
const ext = path.extname(absPath).toLowerCase();
|
|
60
|
+
if (ext === '.json') {
|
|
61
|
+
let content;
|
|
62
|
+
try {
|
|
63
|
+
content = fs.readFileSync(absPath, 'utf8');
|
|
64
|
+
} catch (err) {
|
|
65
|
+
return { checked: true, ok: false, reason: `cannot read file (${err.code || err.message})` };
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
JSON.parse(content);
|
|
69
|
+
return { checked: true, ok: true, reason: null };
|
|
70
|
+
} catch (err) {
|
|
71
|
+
return { checked: true, ok: false, reason: `invalid JSON (${err.message})` };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (ext === '.js' || ext === '.mjs' || ext === '.cjs') {
|
|
75
|
+
// `node --check` is the syntax parser shipped with the runtime AIOSON runs
|
|
76
|
+
// on — guaranteed present, cross-platform, no extra dependency.
|
|
77
|
+
const res = spawnSync(process.execPath, ['--check', absPath], { encoding: 'utf8', timeout: 10000 });
|
|
78
|
+
if (res.status === 0) return { checked: true, ok: true, reason: null };
|
|
79
|
+
const detail = String(res.stderr || res.stdout || '')
|
|
80
|
+
.split('\n')
|
|
81
|
+
.map((l) => l.trim())
|
|
82
|
+
.find((l) => l) || `exit ${res.status}`;
|
|
83
|
+
return { checked: true, ok: false, reason: `syntax error (${detail})` };
|
|
84
|
+
}
|
|
85
|
+
// No stdlib parser for this extension (.ts/.tsx/.md/...): skip, like the harness.
|
|
86
|
+
return { checked: false, ok: true, reason: null };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Evaluate one static criterion against the working tree.
|
|
91
|
+
*
|
|
92
|
+
* @param {object} criterion — a criteria[] entry with must_match/must_not_match/files
|
|
93
|
+
* @param {string} cwd — project root the relative file paths resolve against
|
|
94
|
+
* @returns {{ id, kind:'static', ok, files, missing_files, must_match_misses,
|
|
95
|
+
* must_not_match_hits, parse_failures, detail }}
|
|
96
|
+
*/
|
|
97
|
+
function evaluateStaticCriterion(criterion, cwd) {
|
|
98
|
+
const id = criterion.id;
|
|
99
|
+
const files = Array.isArray(criterion.files) ? criterion.files.slice() : [];
|
|
100
|
+
const mustMatch = Array.isArray(criterion.must_match) ? criterion.must_match : [];
|
|
101
|
+
const mustNotMatch = Array.isArray(criterion.must_not_match) ? criterion.must_not_match : [];
|
|
102
|
+
|
|
103
|
+
const resolved = files.map((rel) => ({ rel, abs: path.resolve(cwd, rel) }));
|
|
104
|
+
const missingFiles = resolved.filter((f) => !fs.existsSync(f.abs)).map((f) => f.rel);
|
|
105
|
+
|
|
106
|
+
// Read each existing file once, and parse-check it for truncation/corruption.
|
|
107
|
+
const contents = new Map();
|
|
108
|
+
const parseFailures = [];
|
|
109
|
+
for (const f of resolved) {
|
|
110
|
+
if (!fs.existsSync(f.abs)) continue;
|
|
111
|
+
let text = '';
|
|
112
|
+
try {
|
|
113
|
+
text = fs.readFileSync(f.abs, 'utf8');
|
|
114
|
+
} catch {
|
|
115
|
+
// Unreadable file is reported by the parse-check below.
|
|
116
|
+
}
|
|
117
|
+
contents.set(f.rel, text);
|
|
118
|
+
const pc = parseCheckFile(f.abs);
|
|
119
|
+
if (pc.checked && !pc.ok) parseFailures.push(`${f.rel}: ${pc.reason}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// must_match — OR across files: each pattern must appear in >= 1 file.
|
|
123
|
+
const mustMatchMisses = [];
|
|
124
|
+
for (const pat of mustMatch) {
|
|
125
|
+
const test = patternTester(pat);
|
|
126
|
+
const hit = [...contents.values()].some((text) => test(text));
|
|
127
|
+
if (!hit) mustMatchMisses.push(String(pat));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// must_not_match — absent in all: no pattern may appear in ANY file.
|
|
131
|
+
const mustNotMatchHits = [];
|
|
132
|
+
for (const pat of mustNotMatch) {
|
|
133
|
+
const test = patternTester(pat);
|
|
134
|
+
for (const [rel, text] of contents) {
|
|
135
|
+
if (test(text)) mustNotMatchHits.push({ pattern: String(pat), file: rel });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const reasons = [];
|
|
140
|
+
if (missingFiles.length) reasons.push(`missing files: ${missingFiles.join(', ')}`);
|
|
141
|
+
if (parseFailures.length) reasons.push(`parse: ${parseFailures.join('; ')}`);
|
|
142
|
+
if (mustMatchMisses.length) reasons.push(`must_match not found: ${mustMatchMisses.map((p) => `/${p}/`).join(', ')}`);
|
|
143
|
+
if (mustNotMatchHits.length) {
|
|
144
|
+
reasons.push(`must_not_match present: ${mustNotMatchHits.map((h) => `/${h.pattern}/ in ${h.file}`).join(', ')}`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const ok =
|
|
148
|
+
missingFiles.length === 0 &&
|
|
149
|
+
parseFailures.length === 0 &&
|
|
150
|
+
mustMatchMisses.length === 0 &&
|
|
151
|
+
mustNotMatchHits.length === 0;
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
id,
|
|
155
|
+
kind: 'static',
|
|
156
|
+
ok,
|
|
157
|
+
files,
|
|
158
|
+
missing_files: missingFiles,
|
|
159
|
+
must_match_misses: mustMatchMisses,
|
|
160
|
+
must_not_match_hits: mustNotMatchHits,
|
|
161
|
+
parse_failures: parseFailures,
|
|
162
|
+
detail: ok ? null : reasons.join(' | ')
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Evaluate every static criterion in a contract's criteria[]. Runtime criteria
|
|
168
|
+
* (those with a `verification` command) are ignored here.
|
|
169
|
+
*
|
|
170
|
+
* @param {object} params
|
|
171
|
+
* @param {Array} params.criteria — criteria[] from the contract
|
|
172
|
+
* @param {string} params.cwd — project root
|
|
173
|
+
* @returns {{ checks: Array, ok: boolean, total: number, failed: number }}
|
|
174
|
+
*/
|
|
175
|
+
function evaluateStaticCriteria({ criteria = [], cwd } = {}) {
|
|
176
|
+
const root = cwd || process.cwd();
|
|
177
|
+
const checks = [];
|
|
178
|
+
for (const criterion of Array.isArray(criteria) ? criteria : []) {
|
|
179
|
+
if (!isStaticCriterion(criterion)) continue;
|
|
180
|
+
checks.push(evaluateStaticCriterion(criterion, root));
|
|
181
|
+
}
|
|
182
|
+
const failed = checks.filter((c) => !c.ok).length;
|
|
183
|
+
return { checks, ok: failed === 0, total: checks.length, failed };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
module.exports = {
|
|
187
|
+
isStaticCriterion,
|
|
188
|
+
patternTester,
|
|
189
|
+
parseCheckFile,
|
|
190
|
+
evaluateStaticCriterion,
|
|
191
|
+
evaluateStaticCriteria
|
|
192
|
+
};
|
package/src/i18n/messages/en.js
CHANGED
|
@@ -13,10 +13,10 @@ module.exports = {
|
|
|
13
13
|
help_setup:
|
|
14
14
|
'aioson setup [path] [--defaults] [--framework=<name>] [--lang=<bcp47-tag>] [--project-name=<name>] [--force] [--dry-run] [--tool=codex|claude|opencode] [--locale=en]',
|
|
15
15
|
help_update: 'aioson update [path] [--dry-run] [--lang=<bcp47-tag>] [--locale=en]',
|
|
16
|
-
help_info: 'aioson info [path] [--json] [--locale=en]',
|
|
17
|
-
help_doctor: 'aioson doctor [path] [--fix] [--dry-run] [--json] [--locale=en]',
|
|
18
|
-
help_hygiene_scan: 'aioson hygiene:scan [path] [--json] [--locale=en]',
|
|
19
|
-
help_i18n_add: 'aioson i18n:add <locale> [--force] [--dry-run] [--locale=en]',
|
|
16
|
+
help_info: 'aioson info [path] [--json] [--locale=en]',
|
|
17
|
+
help_doctor: 'aioson doctor [path] [--fix] [--dry-run] [--json] [--locale=en]',
|
|
18
|
+
help_hygiene_scan: 'aioson hygiene:scan [path] [--json] [--locale=en]',
|
|
19
|
+
help_i18n_add: 'aioson i18n:add <locale> [--force] [--dry-run] [--locale=en]',
|
|
20
20
|
help_agents: 'aioson agents [path] [--lang=<bcp47-tag>] [--locale=en]',
|
|
21
21
|
help_agent_prompt:
|
|
22
22
|
'aioson agent:prompt <agent> [path] [--tool=codex|claude|opencode] [--lang=<bcp47-tag>] [--locale=en]',
|
|
@@ -133,6 +133,8 @@ module.exports = {
|
|
|
133
133
|
'aioson workflow:execute [path] [--feature=<slug>] [--agentic] [--max-dev-qa-cycles=<n>] [--max-tester-cycles=<n>] [--max-pentester-cycles=<n>] [--dry-run] [--lane=<n>] [--json] [--locale=en]',
|
|
134
134
|
help_review_cycle:
|
|
135
135
|
'aioson review-cycle:<status|advance|resolve|reset> [path] --feature=<slug> [--plan=<path>] [--source=qa|tester|pentester] [--json] [--locale=en]',
|
|
136
|
+
help_review_feature:
|
|
137
|
+
'aioson review:feature [path] [--feature=<slug>] [--scope=<target>] [--skip-audit] [--out-dir=<dir>] [--tool=<tool>] [--json] [--locale=en]',
|
|
136
138
|
help_parallel_init:
|
|
137
139
|
'aioson parallel:init [path] [--workers=2..6] [--force] [--dry-run] [--json] [--locale=en]',
|
|
138
140
|
help_parallel_doctor:
|
|
@@ -169,6 +171,8 @@ module.exports = {
|
|
|
169
171
|
'aioson harness:retro [path] --feature=<slug> | --last=<N> [--json] [--locale=en]',
|
|
170
172
|
help_harness_preview:
|
|
171
173
|
'aioson harness:preview <file> [--max-bytes=8192] [--json] [--locale=en]',
|
|
174
|
+
help_verification_plan:
|
|
175
|
+
'aioson verification:plan [path] [--feature=<slug>] [--trigger=per-phase|end-of-feature|sensitive-surface] [--host=claude|codex|opencode] [--classification=MICRO|SMALL|MEDIUM] [--sensitive] [--json] [--locale=en]',
|
|
172
176
|
harnessRetro: {
|
|
173
177
|
need_target: 'harness:retro requires --feature=<slug> or --last=<N>.',
|
|
174
178
|
invalid_slug: 'Invalid slug: {slug} (must match ^[a-z0-9][a-z0-9-]*$).',
|
package/src/i18n/messages/es.js
CHANGED
|
@@ -14,10 +14,10 @@ module.exports = {
|
|
|
14
14
|
'aioson setup [path] [--defaults] [--framework=<nombre>] [--lang=<bcp47-tag>] [--project-name=<nombre>] [--force] [--dry-run] [--tool=codex|claude|opencode] [--locale=es]',
|
|
15
15
|
help_update:
|
|
16
16
|
'aioson update [path] [--dry-run] [--lang=<bcp47-tag>] [--locale=es]',
|
|
17
|
-
help_info: 'aioson info [path] [--json] [--locale=es]',
|
|
18
|
-
help_doctor: 'aioson doctor [path] [--fix] [--dry-run] [--json] [--locale=es]',
|
|
19
|
-
help_hygiene_scan: 'aioson hygiene:scan [path] [--json] [--locale=es]',
|
|
20
|
-
help_i18n_add: 'aioson i18n:add <locale> [--force] [--dry-run] [--locale=es]',
|
|
17
|
+
help_info: 'aioson info [path] [--json] [--locale=es]',
|
|
18
|
+
help_doctor: 'aioson doctor [path] [--fix] [--dry-run] [--json] [--locale=es]',
|
|
19
|
+
help_hygiene_scan: 'aioson hygiene:scan [path] [--json] [--locale=es]',
|
|
20
|
+
help_i18n_add: 'aioson i18n:add <locale> [--force] [--dry-run] [--locale=es]',
|
|
21
21
|
help_agents: 'aioson agents [path] [--lang=<bcp47-tag>] [--locale=es]',
|
|
22
22
|
help_agent_prompt:
|
|
23
23
|
'aioson agent:prompt <agent> [path] [--tool=codex|claude|opencode] [--lang=<bcp47-tag>] [--locale=es]',
|
|
@@ -123,6 +123,8 @@ module.exports = {
|
|
|
123
123
|
'aioson workflow:execute [path] [--feature=<slug>] [--agentic] [--max-dev-qa-cycles=<n>] [--max-tester-cycles=<n>] [--max-pentester-cycles=<n>] [--dry-run] [--lane=<n>] [--json] [--locale=es]',
|
|
124
124
|
help_review_cycle:
|
|
125
125
|
'aioson review-cycle:<status|advance|resolve|reset> [path] --feature=<slug> [--plan=<path>] [--source=qa|tester|pentester] [--json] [--locale=es]',
|
|
126
|
+
help_review_feature:
|
|
127
|
+
'aioson review:feature [path] [--feature=<slug>] [--scope=<target>] [--skip-audit] [--out-dir=<dir>] [--tool=<tool>] [--json] [--locale=es]',
|
|
126
128
|
help_parallel_init:
|
|
127
129
|
'aioson parallel:init [path] [--workers=2..6] [--force] [--dry-run] [--json] [--locale=es]',
|
|
128
130
|
help_parallel_doctor:
|
|
@@ -155,6 +157,8 @@ module.exports = {
|
|
|
155
157
|
'aioson harness:retro [path] --feature=<slug> | --last=<N> [--json] [--locale=es]',
|
|
156
158
|
help_harness_preview:
|
|
157
159
|
'aioson harness:preview <file> [--max-bytes=8192] [--json] [--locale=es]',
|
|
160
|
+
help_verification_plan:
|
|
161
|
+
'aioson verification:plan [path] [--feature=<slug>] [--trigger=per-phase|end-of-feature|sensitive-surface] [--host=claude|codex|opencode] [--classification=MICRO|SMALL|MEDIUM] [--sensitive] [--json] [--locale=es]',
|
|
158
162
|
harnessRetro: {
|
|
159
163
|
need_target: 'harness:retro requiere --feature=<slug> o --last=<N>.',
|
|
160
164
|
invalid_slug: 'Slug inválido: {slug} (debe cumplir ^[a-z0-9][a-z0-9-]*$).',
|
package/src/i18n/messages/fr.js
CHANGED
|
@@ -14,10 +14,10 @@ module.exports = {
|
|
|
14
14
|
'aioson setup [path] [--defaults] [--framework=<nom>] [--lang=<bcp47-tag>] [--project-name=<nom>] [--force] [--dry-run] [--tool=codex|claude|opencode] [--locale=fr]',
|
|
15
15
|
help_update:
|
|
16
16
|
'aioson update [path] [--dry-run] [--lang=<bcp47-tag>] [--locale=fr]',
|
|
17
|
-
help_info: 'aioson info [path] [--json] [--locale=fr]',
|
|
18
|
-
help_doctor: 'aioson doctor [path] [--fix] [--dry-run] [--json] [--locale=fr]',
|
|
19
|
-
help_hygiene_scan: 'aioson hygiene:scan [path] [--json] [--locale=fr]',
|
|
20
|
-
help_i18n_add: 'aioson i18n:add <locale> [--force] [--dry-run] [--locale=fr]',
|
|
17
|
+
help_info: 'aioson info [path] [--json] [--locale=fr]',
|
|
18
|
+
help_doctor: 'aioson doctor [path] [--fix] [--dry-run] [--json] [--locale=fr]',
|
|
19
|
+
help_hygiene_scan: 'aioson hygiene:scan [path] [--json] [--locale=fr]',
|
|
20
|
+
help_i18n_add: 'aioson i18n:add <locale> [--force] [--dry-run] [--locale=fr]',
|
|
21
21
|
help_agents: 'aioson agents [path] [--lang=<bcp47-tag>] [--locale=fr]',
|
|
22
22
|
help_agent_prompt:
|
|
23
23
|
'aioson agent:prompt <agent> [path] [--tool=codex|claude|opencode] [--lang=<bcp47-tag>] [--locale=fr]',
|
|
@@ -123,6 +123,8 @@ module.exports = {
|
|
|
123
123
|
'aioson workflow:execute [path] [--feature=<slug>] [--agentic] [--max-dev-qa-cycles=<n>] [--max-tester-cycles=<n>] [--max-pentester-cycles=<n>] [--dry-run] [--lane=<n>] [--json] [--locale=fr]',
|
|
124
124
|
help_review_cycle:
|
|
125
125
|
'aioson review-cycle:<status|advance|resolve|reset> [path] --feature=<slug> [--plan=<path>] [--source=qa|tester|pentester] [--json] [--locale=fr]',
|
|
126
|
+
help_review_feature:
|
|
127
|
+
'aioson review:feature [path] [--feature=<slug>] [--scope=<target>] [--skip-audit] [--out-dir=<dir>] [--tool=<tool>] [--json] [--locale=fr]',
|
|
126
128
|
help_parallel_init:
|
|
127
129
|
'aioson parallel:init [path] [--workers=2..6] [--force] [--dry-run] [--json] [--locale=fr]',
|
|
128
130
|
help_parallel_doctor:
|
|
@@ -155,6 +157,8 @@ module.exports = {
|
|
|
155
157
|
'aioson harness:retro [path] --feature=<slug> | --last=<N> [--json] [--locale=fr]',
|
|
156
158
|
help_harness_preview:
|
|
157
159
|
'aioson harness:preview <file> [--max-bytes=8192] [--json] [--locale=fr]',
|
|
160
|
+
help_verification_plan:
|
|
161
|
+
'aioson verification:plan [path] [--feature=<slug>] [--trigger=per-phase|end-of-feature|sensitive-surface] [--host=claude|codex|opencode] [--classification=MICRO|SMALL|MEDIUM] [--sensitive] [--json] [--locale=fr]',
|
|
158
162
|
harnessRetro: {
|
|
159
163
|
need_target: 'harness:retro requiert --feature=<slug> ou --last=<N>.',
|
|
160
164
|
invalid_slug: 'Slug invalide : {slug} (doit correspondre à ^[a-z0-9][a-z0-9-]*$).',
|
|
@@ -14,10 +14,10 @@ module.exports = {
|
|
|
14
14
|
'aioson setup [path] [--defaults] [--framework=<nome>] [--lang=<bcp47-tag>] [--project-name=<nome>] [--force] [--dry-run] [--tool=codex|claude|opencode] [--locale=pt-BR]',
|
|
15
15
|
help_update:
|
|
16
16
|
'aioson update [path] [--dry-run] [--lang=<bcp47-tag>] [--locale=pt-BR]',
|
|
17
|
-
help_info: 'aioson info [path] [--json] [--locale=pt-BR]',
|
|
18
|
-
help_doctor: 'aioson doctor [path] [--fix] [--dry-run] [--json] [--locale=pt-BR]',
|
|
19
|
-
help_hygiene_scan: 'aioson hygiene:scan [path] [--json] [--locale=pt-BR]',
|
|
20
|
-
help_i18n_add: 'aioson i18n:add <locale> [--force] [--dry-run] [--locale=pt-BR]',
|
|
17
|
+
help_info: 'aioson info [path] [--json] [--locale=pt-BR]',
|
|
18
|
+
help_doctor: 'aioson doctor [path] [--fix] [--dry-run] [--json] [--locale=pt-BR]',
|
|
19
|
+
help_hygiene_scan: 'aioson hygiene:scan [path] [--json] [--locale=pt-BR]',
|
|
20
|
+
help_i18n_add: 'aioson i18n:add <locale> [--force] [--dry-run] [--locale=pt-BR]',
|
|
21
21
|
help_agents: 'aioson agents [path] [--lang=<bcp47-tag>] [--locale=pt-BR]',
|
|
22
22
|
help_agent_prompt:
|
|
23
23
|
'aioson agent:prompt <agent> [path] [--tool=codex|claude|opencode] [--lang=<bcp47-tag>] [--locale=pt-BR]',
|
|
@@ -135,6 +135,8 @@ module.exports = {
|
|
|
135
135
|
'aioson workflow:execute [path] [--feature=<slug>] [--agentic] [--max-dev-qa-cycles=<n>] [--max-tester-cycles=<n>] [--max-pentester-cycles=<n>] [--dry-run] [--lane=<n>] [--json] [--locale=pt-BR]',
|
|
136
136
|
help_review_cycle:
|
|
137
137
|
'aioson review-cycle:<status|advance|resolve|reset> [path] --feature=<slug> [--plan=<path>] [--source=qa|tester|pentester] [--json] [--locale=pt-BR]',
|
|
138
|
+
help_review_feature:
|
|
139
|
+
'aioson review:feature [path] [--feature=<slug>] [--scope=<target>] [--skip-audit] [--out-dir=<dir>] [--tool=<tool>] [--json] [--locale=pt-BR]',
|
|
138
140
|
help_parallel_init:
|
|
139
141
|
'aioson parallel:init [path] [--workers=2..6] [--force] [--dry-run] [--json] [--locale=pt-BR]',
|
|
140
142
|
help_parallel_doctor:
|
|
@@ -171,6 +173,8 @@ module.exports = {
|
|
|
171
173
|
'aioson harness:retro [path] --feature=<slug> | --last=<N> [--json] [--locale=pt-BR]',
|
|
172
174
|
help_harness_preview:
|
|
173
175
|
'aioson harness:preview <file> [--max-bytes=8192] [--json] [--locale=pt-BR]',
|
|
176
|
+
help_verification_plan:
|
|
177
|
+
'aioson verification:plan [path] [--feature=<slug>] [--trigger=per-phase|end-of-feature|sensitive-surface] [--host=claude|codex|opencode] [--classification=MICRO|SMALL|MEDIUM] [--sensitive] [--json] [--locale=pt-BR]',
|
|
174
178
|
harnessRetro: {
|
|
175
179
|
need_target: 'harness:retro requer --feature=<slug> ou --last=<N>.',
|
|
176
180
|
invalid_slug: 'Slug inválido: {slug} (deve casar ^[a-z0-9][a-z0-9-]*$).',
|
package/src/install-wizard.js
CHANGED
|
@@ -3,17 +3,17 @@
|
|
|
3
3
|
const readline = require('node:readline');
|
|
4
4
|
const { getCliVersionSync } = require('./version');
|
|
5
5
|
|
|
6
|
-
const TOOLS = [
|
|
7
|
-
{ id: 'claude', label: 'Claude Code', desc: 'Slash commands, CLAUDE.md, .claude/' },
|
|
8
|
-
{ id: 'codex', label: 'Codex (OpenAI)', desc: 'AGENTS.md protocol' },
|
|
9
|
-
{ id: 'opencode', label: 'OpenCode', desc: 'OPENCODE.md protocol' }
|
|
10
|
-
];
|
|
6
|
+
const TOOLS = [
|
|
7
|
+
{ id: 'claude', label: 'Claude Code', desc: 'Slash commands, CLAUDE.md, .claude/' },
|
|
8
|
+
{ id: 'codex', label: 'Codex (OpenAI)', desc: 'AGENTS.md protocol' },
|
|
9
|
+
{ id: 'opencode', label: 'OpenCode', desc: 'OPENCODE.md protocol' }
|
|
10
|
+
];
|
|
11
11
|
|
|
12
12
|
const USES = [
|
|
13
13
|
{
|
|
14
14
|
id: 'development',
|
|
15
15
|
label: 'Development',
|
|
16
|
-
desc: 'Agent workflow: setup → analyst → scope-check → architect → dev → qa',
|
|
16
|
+
desc: 'Agent workflow: setup → analyst → scope-check → architect → dev → qa',
|
|
17
17
|
locked: true
|
|
18
18
|
},
|
|
19
19
|
{
|
|
@@ -111,7 +111,7 @@ function renderScreen1(cursor, selected, warn, stdout) {
|
|
|
111
111
|
const tool = TOOLS[i];
|
|
112
112
|
const pointer = i === cursor ? '►' : ' ';
|
|
113
113
|
const check = selected.has(tool.id) ? '✓' : ' ';
|
|
114
|
-
stdout.write(` ${pointer} [${check}] ${tool.label.padEnd(20)} ${tool.desc}\n`);
|
|
114
|
+
stdout.write(` ${pointer} [${check}] ${tool.label.padEnd(20)} ${tool.desc}\n`);
|
|
115
115
|
}
|
|
116
116
|
if (warn) stdout.write('\n ⚠ Select at least one tool to continue.\n');
|
|
117
117
|
stdout.write('\n');
|
|
@@ -161,7 +161,7 @@ function renderScreen4(cursor, stdout) {
|
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
function renderConfirm(tools, uses, design, locale, existingProfile, t, stdout) {
|
|
164
|
-
const TOOL_NAMES = { claude: 'Claude Code', codex: 'Codex', opencode: 'OpenCode' };
|
|
164
|
+
const TOOL_NAMES = { claude: 'Claude Code', codex: 'Codex', opencode: 'OpenCode' };
|
|
165
165
|
const toolNames = tools.map(id => TOOL_NAMES[id] || id).join(', ');
|
|
166
166
|
const modeLabel = uses.includes('squads') ? 'Development + Squads' : 'Development';
|
|
167
167
|
// design can be string (single/id/all) or string[] (multiple)
|
package/src/installer.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const fs = require('node:fs/promises');
|
|
4
4
|
const path = require('node:path');
|
|
5
5
|
const { MANAGED_FILES } = require('./constants');
|
|
6
|
-
const { getCliVersion } = require('./version');
|
|
6
|
+
const { getCliVersion, getGitBuildInfoSync } = require('./version');
|
|
7
7
|
const { exists, ensureDir, copyFileWithDir, nowStamp, toRelativeSafe } = require('./utils');
|
|
8
8
|
const { ensureProjectRuntime } = require('./execution-gateway');
|
|
9
9
|
const { shouldIncludeForProfile } = require('./install-profile');
|
|
@@ -34,10 +34,10 @@ const GITIGNORE_POLICY_LINES = [
|
|
|
34
34
|
'# AIOSON — keep shared project memory and tool contracts',
|
|
35
35
|
'!AGENTS.md',
|
|
36
36
|
'!CLAUDE.md',
|
|
37
|
-
'!OPENCODE.md',
|
|
38
|
-
'!.claude/',
|
|
39
|
-
'!.claude/**',
|
|
40
|
-
'!.aioson/',
|
|
37
|
+
'!OPENCODE.md',
|
|
38
|
+
'!.claude/',
|
|
39
|
+
'!.claude/**',
|
|
40
|
+
'!.aioson/',
|
|
41
41
|
'!.aioson/**',
|
|
42
42
|
'# AIOSON — managed framework files (do not commit)',
|
|
43
43
|
'.aioson/config.md',
|
|
@@ -238,10 +238,17 @@ async function writeInstallMetadata(targetDir, action, frameworkDetection, insta
|
|
|
238
238
|
}
|
|
239
239
|
|
|
240
240
|
const version = await getCliVersion();
|
|
241
|
+
// Stamp the exact framework commit when installing/updating from a git checkout
|
|
242
|
+
// (e.g. an `npm link`ed dev framework). With a plain npm install this is null —
|
|
243
|
+
// template_version alone identifies it. Lets a project record precisely which
|
|
244
|
+
// commit it was last updated from.
|
|
245
|
+
const gitInfo = getGitBuildInfoSync();
|
|
241
246
|
const data = {
|
|
242
247
|
...existing,
|
|
243
248
|
managed_by: 'aioson',
|
|
244
249
|
template_version: version,
|
|
250
|
+
template_git_sha: gitInfo ? gitInfo.sha : null,
|
|
251
|
+
template_git_date: gitInfo ? gitInfo.date : null,
|
|
245
252
|
last_action: action,
|
|
246
253
|
last_action_at: new Date().toISOString(),
|
|
247
254
|
framework_detected: frameworkDetection || existing.framework_detected || null,
|
|
@@ -343,7 +350,7 @@ async function installTemplate(targetDir, options = {}) {
|
|
|
343
350
|
const dest = path.join(targetDir, rel);
|
|
344
351
|
const destExists = await exists(dest);
|
|
345
352
|
|
|
346
|
-
// Gateway pointer files (CLAUDE.md, AGENTS.md, OPENCODE.md)
|
|
353
|
+
// Gateway pointer files (CLAUDE.md, AGENTS.md, OPENCODE.md)
|
|
347
354
|
// get block-merged so that an existing project-authored file keeps its content
|
|
348
355
|
// and only the AIOSON-managed block is created or refreshed in place.
|
|
349
356
|
if (isGatewayPointerPath(rel)) {
|
|
@@ -9,7 +9,16 @@
|
|
|
9
9
|
* fixo (pt-BR, conforme §3.1); `--locale` afeta só as mensagens de stdout.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
const SOURCE_ORDER = [
|
|
12
|
+
const SOURCE_ORDER = [
|
|
13
|
+
'qa_reports',
|
|
14
|
+
'verification_reports',
|
|
15
|
+
'corrections',
|
|
16
|
+
'dossier_trail',
|
|
17
|
+
'execution_events',
|
|
18
|
+
'attempts',
|
|
19
|
+
'failure_signatures',
|
|
20
|
+
'devlogs'
|
|
21
|
+
];
|
|
13
22
|
|
|
14
23
|
// Defesa em profundidade (SF-01): o dossiê vira contexto do @sheldon. Texto livre
|
|
15
24
|
// minerado (títulos) é apresentado como DADO inline, nunca como estrutura Markdown
|
|
@@ -13,9 +13,10 @@
|
|
|
13
13
|
* módulo NUNCA escreve no filesystem.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
const fs = require('node:fs');
|
|
17
|
-
const path = require('node:path');
|
|
18
|
-
const crypto = require('node:crypto');
|
|
16
|
+
const fs = require('node:fs');
|
|
17
|
+
const path = require('node:path');
|
|
18
|
+
const crypto = require('node:crypto');
|
|
19
|
+
const { readVerificationReports } = require('./verification-reports');
|
|
19
20
|
|
|
20
21
|
const FINDING_ID_RE = /\b([A-Z]{1,2}-\d{1,2})\b/;
|
|
21
22
|
const TRAIL_ENTRY_RE = /^\*\*([^*]+)\*\*\s*\|\s*@?([\w.-]+)\s*\|\s*_([^_]+)_\s*$/;
|
|
@@ -445,11 +446,16 @@ function resolveLocations(targetDir, ctxDir, slug) {
|
|
|
445
446
|
path.join(doneRoot, 'plans')
|
|
446
447
|
].filter(dirExists);
|
|
447
448
|
|
|
448
|
-
const dossierCandidates = [
|
|
449
|
-
path.join(ctxDir, 'features', slug, 'dossier.md'),
|
|
450
|
-
path.join(doneRoot, 'features', slug, 'dossier.md'),
|
|
451
|
-
path.join(doneRoot, 'dossier.md')
|
|
452
|
-
];
|
|
449
|
+
const dossierCandidates = [
|
|
450
|
+
path.join(ctxDir, 'features', slug, 'dossier.md'),
|
|
451
|
+
path.join(doneRoot, 'features', slug, 'dossier.md'),
|
|
452
|
+
path.join(doneRoot, 'dossier.md')
|
|
453
|
+
];
|
|
454
|
+
const featureDirs = [
|
|
455
|
+
path.join(ctxDir, 'features', slug),
|
|
456
|
+
path.join(doneRoot, 'features', slug),
|
|
457
|
+
doneRoot
|
|
458
|
+
].filter(dirExists);
|
|
453
459
|
// SF-02: lstat (não statSync) para NÃO seguir symlink — consistente com os
|
|
454
460
|
// demais readers, que pulam symlinks via Dirent.isFile(). Um dossier.md que
|
|
455
461
|
// seja um symlink apontando para fora do workspace é ignorado, não seguido.
|
|
@@ -457,8 +463,8 @@ function resolveLocations(targetDir, ctxDir, slug) {
|
|
|
457
463
|
try { return fs.lstatSync(p).isFile(); } catch { return false; }
|
|
458
464
|
});
|
|
459
465
|
|
|
460
|
-
return { qaDirs, planDirs, dossierFiles, doneRoot };
|
|
461
|
-
}
|
|
466
|
+
return { qaDirs, planDirs, dossierFiles, doneRoot, featureDirs };
|
|
467
|
+
}
|
|
462
468
|
|
|
463
469
|
/**
|
|
464
470
|
* Minera todas as fontes de UMA feature. Best-effort: nunca lança.
|
|
@@ -478,14 +484,15 @@ function collectFeatureSources(targetDir, slug) {
|
|
|
478
484
|
const corr = readCorrections({ rootDir, slug, locations });
|
|
479
485
|
const trail = readDossierTrail({ rootDir, slug, locations });
|
|
480
486
|
const events = readExecutionEvents({ rootDir, targetDir, slug });
|
|
481
|
-
const attempts = readAttempts({ rootDir, slug, locations });
|
|
482
|
-
const sigs = readFailureSignatures({ rootDir, slug, locations });
|
|
483
|
-
const devlogs = readDevlogs({ rootDir, targetDir, slug });
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
487
|
+
const attempts = readAttempts({ rootDir, slug, locations });
|
|
488
|
+
const sigs = readFailureSignatures({ rootDir, slug, locations });
|
|
489
|
+
const devlogs = readDevlogs({ rootDir, targetDir, slug });
|
|
490
|
+
const verification = readVerificationReports({ rootDir, slug, locations, makeFinding, relPath });
|
|
491
|
+
|
|
492
|
+
for (const r of [qa, corr, trail, events, attempts, sigs, devlogs, verification]) {
|
|
493
|
+
findings.push(...r.findings);
|
|
494
|
+
warnings.push(...r.warnings);
|
|
495
|
+
}
|
|
489
496
|
cycles.push(...trail.cycles);
|
|
490
497
|
|
|
491
498
|
const counts = {
|
|
@@ -493,10 +500,11 @@ function collectFeatureSources(targetDir, slug) {
|
|
|
493
500
|
corrections: corr.count,
|
|
494
501
|
dossier_trail: trail.count,
|
|
495
502
|
execution_events: events.count,
|
|
496
|
-
attempts: attempts.count,
|
|
497
|
-
failure_signatures: sigs.count,
|
|
498
|
-
devlogs: devlogs.count
|
|
499
|
-
|
|
503
|
+
attempts: attempts.count,
|
|
504
|
+
failure_signatures: sigs.count,
|
|
505
|
+
devlogs: devlogs.count,
|
|
506
|
+
verification_reports: verification.count
|
|
507
|
+
};
|
|
500
508
|
|
|
501
509
|
const cost = {
|
|
502
510
|
execution_events: events.count,
|
|
@@ -508,10 +516,11 @@ function collectFeatureSources(targetDir, slug) {
|
|
|
508
516
|
};
|
|
509
517
|
|
|
510
518
|
const minedPaths = [];
|
|
511
|
-
for (const d of locations.qaDirs) minedPaths.push(relPath(rootDir, d));
|
|
512
|
-
for (const d of locations.planDirs) minedPaths.push(relPath(rootDir, d));
|
|
513
|
-
for (const f of locations.dossierFiles) minedPaths.push(relPath(rootDir, f));
|
|
514
|
-
minedPaths.
|
|
519
|
+
for (const d of locations.qaDirs) minedPaths.push(relPath(rootDir, d));
|
|
520
|
+
for (const d of locations.planDirs) minedPaths.push(relPath(rootDir, d));
|
|
521
|
+
for (const f of locations.dossierFiles) minedPaths.push(relPath(rootDir, f));
|
|
522
|
+
for (const f of verification.files) minedPaths.push(relPath(rootDir, f));
|
|
523
|
+
minedPaths.sort();
|
|
515
524
|
|
|
516
525
|
return { slug, findings, cycles, counts, cost, minedPaths, warnings };
|
|
517
526
|
}
|
|
@@ -526,7 +535,16 @@ function collectSources(targetDir, slugs) {
|
|
|
526
535
|
const cycles = [];
|
|
527
536
|
const warnings = [];
|
|
528
537
|
const minedPaths = [];
|
|
529
|
-
const counts = {
|
|
538
|
+
const counts = {
|
|
539
|
+
qa_reports: 0,
|
|
540
|
+
verification_reports: 0,
|
|
541
|
+
corrections: 0,
|
|
542
|
+
dossier_trail: 0,
|
|
543
|
+
execution_events: 0,
|
|
544
|
+
attempts: 0,
|
|
545
|
+
failure_signatures: 0,
|
|
546
|
+
devlogs: 0
|
|
547
|
+
};
|
|
530
548
|
const cost = { execution_events: 0, corrections: 0, fail_pass_cycles: 0, corrections_bytes: 0, token_count_available: false, token_total: null };
|
|
531
549
|
const costByFeature = {};
|
|
532
550
|
|