@jaimevalasek/aioson 1.30.2 → 1.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/README.md +19 -6
  3. package/docs/en/1-understand/ecosystem-map.md +45 -29
  4. package/docs/en/1-understand/glossary.md +5 -5
  5. package/docs/en/1-understand/what-is-aioson.md +5 -5
  6. package/docs/en/2-start/existing-project.md +7 -7
  7. package/docs/en/2-start/first-project.md +32 -38
  8. package/docs/en/2-start/initial-decisions.md +18 -17
  9. package/docs/en/3-recipes/README.md +2 -2
  10. package/docs/en/3-recipes/continuity-between-sessions.md +2 -2
  11. package/docs/en/3-recipes/from-idea-to-prd-via-briefing.md +5 -2
  12. package/docs/en/3-recipes/full-feature-with-sheldon.md +327 -338
  13. package/docs/en/4-agents/README.md +28 -14
  14. package/docs/en/4-agents/discovery-design-doc.md +20 -8
  15. package/docs/en/4-agents/forge-run.md +3 -2
  16. package/docs/en/5-reference/cli-reference.md +51 -48
  17. package/docs/en/5-reference/executable-verification.md +10 -7
  18. package/docs/en/5-reference/parallel.md +2 -0
  19. package/docs/en/5-reference/qa-browser.md +2 -2
  20. package/docs/en/README.md +3 -3
  21. package/docs/pt/1-entender/glossario.md +4 -4
  22. package/docs/pt/1-entender/mapa-do-ecossistema.md +32 -21
  23. package/docs/pt/2-comecar/decisoes-iniciais.md +16 -16
  24. package/docs/pt/2-comecar/primeiro-projeto.md +1 -1
  25. package/docs/pt/3-receitas/README.md +1 -1
  26. package/docs/pt/3-receitas/app-saas-do-zero.md +35 -122
  27. package/docs/pt/3-receitas/feature-completa-com-sheldon.md +289 -338
  28. package/docs/pt/4-agentes/analyst.md +9 -5
  29. package/docs/pt/4-agentes/architect.md +9 -5
  30. package/docs/pt/4-agentes/dev.md +19 -7
  31. package/docs/pt/4-agentes/discovery-design-doc.md +25 -23
  32. package/docs/pt/4-agentes/orchestrator.md +164 -118
  33. package/docs/pt/4-agentes/pentester.md +5 -5
  34. package/docs/pt/4-agentes/pm.md +15 -7
  35. package/docs/pt/4-agentes/sheldon.md +166 -99
  36. package/docs/pt/4-agentes/ux-ui.md +7 -4
  37. package/docs/pt/5-referencia/agent-chain-continuity.md +14 -5
  38. package/docs/pt/5-referencia/comandos-cli.md +1 -1
  39. package/docs/pt/5-referencia/harness-retro.md +2 -1
  40. package/docs/pt/5-referencia/sdd-automation-scripts.md +6 -6
  41. package/docs/pt/5-referencia/sdd-framework.md +53 -16
  42. package/docs/pt/5-referencia/sdd-planos-e-estrutura.md +27 -15
  43. package/docs/pt/README.md +4 -4
  44. package/docs/pt/agentes.md +48 -50
  45. package/package.json +2 -2
  46. package/src/artifact-kinds.js +110 -0
  47. package/src/cli.js +82 -42
  48. package/src/commands/agent-epilogue.js +251 -186
  49. package/src/commands/agents.js +104 -48
  50. package/src/commands/audit-code.js +344 -0
  51. package/src/commands/classify.js +75 -13
  52. package/src/commands/feature-close.js +43 -18
  53. package/src/commands/harness-check.js +259 -175
  54. package/src/commands/harness-retro-promote.js +387 -0
  55. package/src/commands/live.js +24 -0
  56. package/src/commands/prototype-check.js +163 -0
  57. package/src/commands/review-feature.js +189 -0
  58. package/src/commands/runtime.js +81 -66
  59. package/src/commands/sync-agents-copy.js +115 -0
  60. package/src/commands/verification-plan.js +116 -0
  61. package/src/commands/verify-artifact.js +530 -0
  62. package/src/commands/verify-implementation.js +428 -0
  63. package/src/commands/workflow-execute.js +309 -309
  64. package/src/commands/workflow-next.js +361 -19
  65. package/src/commands/workflow-plan.js +5 -5
  66. package/src/constants.js +4 -0
  67. package/src/gateway-pointer-merge.js +7 -1
  68. package/src/handoff-contract.js +267 -172
  69. package/src/harness/contract-integrity-gate.js +172 -0
  70. package/src/harness/contract-integrity.js +111 -0
  71. package/src/harness/contract-schema.js +377 -332
  72. package/src/harness/detect-runtime-feature.js +90 -0
  73. package/src/harness/static-criteria.js +192 -0
  74. package/src/i18n/messages/en.js +8 -4
  75. package/src/i18n/messages/es.js +8 -4
  76. package/src/i18n/messages/fr.js +8 -4
  77. package/src/i18n/messages/pt-BR.js +8 -4
  78. package/src/install-wizard.js +8 -8
  79. package/src/installer.js +13 -6
  80. package/src/lib/retro/retro-render.js +10 -1
  81. package/src/lib/retro/retro-sources.js +45 -27
  82. package/src/lib/retro/verification-reports.js +230 -0
  83. package/src/parser.js +6 -0
  84. package/src/preflight-engine.js +12 -12
  85. package/src/runtime-store.js +13 -9
  86. package/src/verification/evidence-bundle.js +251 -0
  87. package/src/verification/ledger-store.js +221 -0
  88. package/src/verification/path-policy.js +74 -0
  89. package/src/verification/policy-engine.js +95 -0
  90. package/src/verification/prompt-package.js +314 -0
  91. package/src/verification/redaction.js +77 -0
  92. package/src/verification/report-parser.js +132 -0
  93. package/src/verification/report-store.js +97 -0
  94. package/src/verification/result.js +16 -0
  95. package/src/verification/runners/index.js +319 -0
  96. package/src/verification/runtime-telemetry.js +144 -0
  97. package/src/verification/schema.js +276 -0
  98. package/src/verification/source-discovery.js +153 -0
  99. package/src/verification-policy.js +398 -0
  100. package/src/version.js +52 -1
  101. package/template/.aioson/agents/analyst.md +9 -6
  102. package/template/.aioson/agents/architect.md +34 -5
  103. package/template/.aioson/agents/briefing-refiner.md +25 -0
  104. package/template/.aioson/agents/briefing.md +69 -12
  105. package/template/.aioson/agents/committer.md +2 -1
  106. package/template/.aioson/agents/copywriter.md +30 -21
  107. package/template/.aioson/agents/design-hybrid-forge.md +28 -15
  108. package/template/.aioson/agents/dev.md +28 -10
  109. package/template/.aioson/agents/deyvin.md +3 -2
  110. package/template/.aioson/agents/discover.md +12 -3
  111. package/template/.aioson/agents/discovery-design-doc.md +7 -2
  112. package/template/.aioson/agents/genome.md +19 -10
  113. package/template/.aioson/agents/neo.md +30 -30
  114. package/template/.aioson/agents/orache.md +20 -11
  115. package/template/.aioson/agents/orchestrator.md +84 -7
  116. package/template/.aioson/agents/pm.md +8 -8
  117. package/template/.aioson/agents/product.md +15 -11
  118. package/template/.aioson/agents/profiler-enricher.md +20 -11
  119. package/template/.aioson/agents/profiler-forge.md +20 -11
  120. package/template/.aioson/agents/profiler-researcher.md +20 -11
  121. package/template/.aioson/agents/qa.md +87 -17
  122. package/template/.aioson/agents/scope-check.md +19 -5
  123. package/template/.aioson/agents/setup.md +12 -1
  124. package/template/.aioson/agents/sheldon.md +92 -9
  125. package/template/.aioson/agents/site-forge.md +11 -2
  126. package/template/.aioson/agents/squad.md +20 -5
  127. package/template/.aioson/agents/ux-ui.md +4 -2
  128. package/template/.aioson/agents/validator.md +33 -1
  129. package/template/.aioson/config/verification.json +61 -0
  130. package/template/.aioson/config.md +13 -9
  131. package/template/.aioson/docs/LAYERS.md +2 -2
  132. package/template/.aioson/docs/autopilot-handoff.md +10 -10
  133. package/template/.aioson/docs/dev/execution-discipline.md +41 -0
  134. package/template/.aioson/docs/dev/phase-loop.md +47 -0
  135. package/template/.aioson/docs/feature-expansion-taxonomy.md +31 -3
  136. package/template/.aioson/docs/presets/workflow.config.full-merged.json +15 -0
  137. package/template/.aioson/docs/presets/workflow.config.lean.json +15 -0
  138. package/template/.aioson/docs/product/prd-contract.md +12 -12
  139. package/template/.aioson/docs/prototype-contract.md +98 -0
  140. package/template/.aioson/docs/reference-identity.md +94 -0
  141. package/template/.aioson/docs/sheldon/harness-contract.md +155 -28
  142. package/template/.aioson/docs/verification-config.md +82 -0
  143. package/template/.aioson/docs/verify-artifact-gates.md +91 -0
  144. package/template/.aioson/docs/workflow-lean-lane.md +129 -0
  145. package/template/.aioson/skills/design/interface-design/references/intent-and-domain.md +2 -0
  146. package/template/.aioson/skills/process/aioson-spec-driven/SKILL.md +16 -14
  147. package/template/.aioson/skills/process/aioson-spec-driven/references/approval-gates.md +2 -2
  148. package/template/.aioson/skills/process/aioson-spec-driven/references/classification-map.md +4 -4
  149. package/template/.aioson/skills/process/aioson-spec-driven/references/dev.md +15 -15
  150. package/template/.aioson/skills/process/briefing-expansion-scout/SKILL.md +25 -3
  151. package/template/.aioson/skills/process/design-hybrid-forge/SKILL.md +5 -3
  152. package/template/.aioson/skills/process/design-hybrid-forge/references/external-source-ingestion.md +89 -0
  153. package/template/.aioson/skills/process/product-scope-expansion/SKILL.md +29 -2
  154. package/template/.aioson/skills/process/prototype-forge/SKILL.md +98 -0
  155. package/template/.aioson/skills/process/reference-identity-extract/SKILL.md +164 -0
  156. package/template/.aioson/skills/process/sheldon-expansion-audit/SKILL.md +23 -2
  157. package/template/.aioson/skills/static/multi-agent-patterns.md +5 -5
  158. package/template/CLAUDE.md +15 -11
@@ -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
+ };
@@ -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-]*$).',
@@ -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-]*$).',
@@ -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-]*$).',
@@ -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 = ['qa_reports', 'corrections', 'dossier_trail', 'execution_events', 'attempts', 'failure_signatures', 'devlogs'];
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
- for (const r of [qa, corr, trail, events, attempts, sigs, devlogs]) {
486
- findings.push(...r.findings);
487
- warnings.push(...r.warnings);
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.sort();
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 = { qa_reports: 0, corrections: 0, dossier_trail: 0, execution_events: 0, attempts: 0, failure_signatures: 0, devlogs: 0 };
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