@jaimevalasek/aioson 1.33.1 → 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 (125) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/docs/en/1-understand/ecosystem-map.md +45 -29
  3. package/docs/en/1-understand/glossary.md +5 -5
  4. package/docs/en/1-understand/what-is-aioson.md +5 -5
  5. package/docs/en/2-start/existing-project.md +7 -7
  6. package/docs/en/2-start/first-project.md +32 -38
  7. package/docs/en/2-start/initial-decisions.md +18 -17
  8. package/docs/en/3-recipes/README.md +2 -2
  9. package/docs/en/3-recipes/continuity-between-sessions.md +2 -2
  10. package/docs/en/3-recipes/from-idea-to-prd-via-briefing.md +5 -2
  11. package/docs/en/3-recipes/full-feature-with-sheldon.md +327 -338
  12. package/docs/en/4-agents/README.md +28 -14
  13. package/docs/en/4-agents/discovery-design-doc.md +20 -8
  14. package/docs/en/4-agents/forge-run.md +3 -2
  15. package/docs/en/5-reference/cli-reference.md +51 -48
  16. package/docs/en/5-reference/executable-verification.md +10 -7
  17. package/docs/en/5-reference/parallel.md +2 -0
  18. package/docs/en/5-reference/qa-browser.md +2 -2
  19. package/docs/en/README.md +3 -3
  20. package/docs/pt/1-entender/glossario.md +4 -4
  21. package/docs/pt/1-entender/mapa-do-ecossistema.md +32 -21
  22. package/docs/pt/2-comecar/decisoes-iniciais.md +16 -16
  23. package/docs/pt/2-comecar/primeiro-projeto.md +1 -1
  24. package/docs/pt/3-receitas/README.md +1 -1
  25. package/docs/pt/3-receitas/app-saas-do-zero.md +35 -122
  26. package/docs/pt/3-receitas/feature-completa-com-sheldon.md +289 -338
  27. package/docs/pt/4-agentes/analyst.md +9 -5
  28. package/docs/pt/4-agentes/architect.md +9 -5
  29. package/docs/pt/4-agentes/dev.md +19 -7
  30. package/docs/pt/4-agentes/discovery-design-doc.md +25 -23
  31. package/docs/pt/4-agentes/orchestrator.md +164 -118
  32. package/docs/pt/4-agentes/pentester.md +5 -5
  33. package/docs/pt/4-agentes/pm.md +15 -7
  34. package/docs/pt/4-agentes/sheldon.md +166 -99
  35. package/docs/pt/4-agentes/ux-ui.md +7 -4
  36. package/docs/pt/5-referencia/agent-chain-continuity.md +14 -5
  37. package/docs/pt/5-referencia/sdd-automation-scripts.md +6 -6
  38. package/docs/pt/5-referencia/sdd-framework.md +53 -16
  39. package/docs/pt/5-referencia/sdd-planos-e-estrutura.md +27 -15
  40. package/docs/pt/README.md +4 -4
  41. package/docs/pt/agentes.md +48 -50
  42. package/package.json +2 -2
  43. package/src/artifact-kinds.js +110 -0
  44. package/src/cli.js +103 -80
  45. package/src/commands/agent-epilogue.js +251 -186
  46. package/src/commands/agents.js +104 -48
  47. package/src/commands/audit-code.js +344 -0
  48. package/src/commands/classify.js +389 -389
  49. package/src/commands/feature-close.js +43 -18
  50. package/src/commands/harness-check.js +259 -175
  51. package/src/commands/live.js +24 -0
  52. package/src/commands/review-feature.js +189 -0
  53. package/src/commands/runtime.js +81 -66
  54. package/src/commands/sync-agents-copy.js +115 -0
  55. package/src/commands/verification-plan.js +116 -0
  56. package/src/commands/verify-artifact.js +530 -0
  57. package/src/commands/workflow-execute.js +309 -309
  58. package/src/commands/workflow-next.js +139 -10
  59. package/src/commands/workflow-plan.js +5 -5
  60. package/src/constants.js +2 -0
  61. package/src/gateway-pointer-merge.js +7 -1
  62. package/src/handoff-contract.js +267 -172
  63. package/src/harness/contract-integrity-gate.js +172 -0
  64. package/src/harness/contract-integrity.js +111 -0
  65. package/src/harness/contract-schema.js +377 -332
  66. package/src/harness/detect-runtime-feature.js +90 -0
  67. package/src/harness/static-criteria.js +192 -0
  68. package/src/i18n/messages/en.js +8 -4
  69. package/src/i18n/messages/es.js +8 -4
  70. package/src/i18n/messages/fr.js +8 -4
  71. package/src/i18n/messages/pt-BR.js +8 -4
  72. package/src/install-wizard.js +8 -8
  73. package/src/installer.js +13 -6
  74. package/src/parser.js +6 -0
  75. package/src/preflight-engine.js +12 -12
  76. package/src/verification/policy-engine.js +95 -95
  77. package/src/verification-policy.js +398 -0
  78. package/src/version.js +52 -1
  79. package/template/.aioson/agents/architect.md +34 -5
  80. package/template/.aioson/agents/briefing-refiner.md +4 -1
  81. package/template/.aioson/agents/committer.md +2 -1
  82. package/template/.aioson/agents/copywriter.md +30 -21
  83. package/template/.aioson/agents/design-hybrid-forge.md +10 -1
  84. package/template/.aioson/agents/dev.md +27 -22
  85. package/template/.aioson/agents/discover.md +12 -3
  86. package/template/.aioson/agents/discovery-design-doc.md +7 -2
  87. package/template/.aioson/agents/genome.md +19 -10
  88. package/template/.aioson/agents/neo.md +30 -30
  89. package/template/.aioson/agents/orache.md +20 -11
  90. package/template/.aioson/agents/orchestrator.md +84 -7
  91. package/template/.aioson/agents/pm.md +8 -8
  92. package/template/.aioson/agents/profiler-enricher.md +20 -11
  93. package/template/.aioson/agents/profiler-forge.md +20 -11
  94. package/template/.aioson/agents/profiler-researcher.md +20 -11
  95. package/template/.aioson/agents/qa.md +91 -30
  96. package/template/.aioson/agents/setup.md +12 -1
  97. package/template/.aioson/agents/sheldon.md +94 -12
  98. package/template/.aioson/agents/site-forge.md +11 -2
  99. package/template/.aioson/agents/squad.md +20 -5
  100. package/template/.aioson/agents/ux-ui.md +1 -0
  101. package/template/.aioson/agents/validator.md +33 -1
  102. package/template/.aioson/config/verification.json +61 -0
  103. package/template/.aioson/config.md +13 -9
  104. package/template/.aioson/docs/LAYERS.md +2 -2
  105. package/template/.aioson/docs/autopilot-handoff.md +10 -10
  106. package/template/.aioson/docs/dev/execution-discipline.md +41 -0
  107. package/template/.aioson/docs/dev/phase-loop.md +47 -0
  108. package/template/.aioson/docs/presets/workflow.config.full-merged.json +15 -0
  109. package/template/.aioson/docs/presets/workflow.config.lean.json +15 -0
  110. package/template/.aioson/docs/product/prd-contract.md +12 -12
  111. package/template/.aioson/docs/prototype-contract.md +21 -4
  112. package/template/.aioson/docs/reference-identity.md +94 -0
  113. package/template/.aioson/docs/sheldon/harness-contract.md +155 -28
  114. package/template/.aioson/docs/verification-config.md +82 -0
  115. package/template/.aioson/docs/verify-artifact-gates.md +91 -0
  116. package/template/.aioson/docs/workflow-lean-lane.md +129 -0
  117. package/template/.aioson/skills/design/interface-design/references/intent-and-domain.md +2 -0
  118. package/template/.aioson/skills/process/aioson-spec-driven/SKILL.md +16 -14
  119. package/template/.aioson/skills/process/aioson-spec-driven/references/approval-gates.md +2 -2
  120. package/template/.aioson/skills/process/aioson-spec-driven/references/classification-map.md +4 -4
  121. package/template/.aioson/skills/process/aioson-spec-driven/references/dev.md +15 -15
  122. package/template/.aioson/skills/process/prototype-forge/SKILL.md +6 -0
  123. package/template/.aioson/skills/process/reference-identity-extract/SKILL.md +164 -0
  124. package/template/.aioson/skills/static/multi-agent-patterns.md +5 -5
  125. 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)) {
package/src/parser.js CHANGED
@@ -38,6 +38,12 @@ function parseArgv(argv) {
38
38
  'flatten', 'no-index',
39
39
  // harness:validate — pure boolean; `--no-diff .` must not swallow the path.
40
40
  'no-diff',
41
+ // audit:code / harness:check — pure booleans; `--changed .` / `--strict .`
42
+ // must not swallow the trailing path positional.
43
+ 'changed', 'strict',
44
+ // verify:artifact — pure booleans; `--advisory .` / `--no-build .` must
45
+ // not swallow the path positional.
46
+ 'advisory', 'no-build',
41
47
  // `--resume` alone means "resume last"; `--resume=<id>` carries a value
42
48
  // and is handled by the `=` branch above. Without this entry, `--resume`
43
49
  // followed by `--tool=claude` would swallow the next token as its value.
@@ -257,11 +257,11 @@ async function scanArtifacts(targetDir, slug) {
257
257
  : [path.join(dir, 'readiness.md')];
258
258
 
259
259
  const results = {
260
- project_context: await check('project.context', path.join(dir, 'project.context.md')),
261
- prd: slug ? await check('prd', path.join(dir, `prd-${slug}.md`)) : { exists: false },
262
- sheldon_enrichment: slug ? await check('sheldon', path.join(dir, `sheldon-enrichment-${slug}.md`)) : { exists: false },
263
- sheldon_validation: slug ? await check('sheldon-validation', path.join(dir, `sheldon-validation-${slug}.md`)) : { exists: false },
264
- requirements: slug ? await check('requirements', path.join(dir, `requirements-${slug}.md`)) : { exists: false },
260
+ project_context: await check('project.context', path.join(dir, 'project.context.md')),
261
+ prd: slug ? await check('prd', path.join(dir, `prd-${slug}.md`)) : { exists: false },
262
+ sheldon_enrichment: slug ? await check('sheldon', path.join(dir, `sheldon-enrichment-${slug}.md`)) : { exists: false },
263
+ sheldon_validation: slug ? await check('sheldon-validation', path.join(dir, `sheldon-validation-${slug}.md`)) : { exists: false },
264
+ requirements: slug ? await check('requirements', path.join(dir, `requirements-${slug}.md`)) : { exists: false },
265
265
  spec: slug ? await check('spec', path.join(dir, `spec-${slug}.md`)) : await check('spec', path.join(dir, 'spec.md')),
266
266
  architecture: await check('architecture', path.join(dir, 'architecture.md')),
267
267
  design_doc: await checkFirst('design-doc', designDocCandidates),
@@ -480,11 +480,11 @@ function buildContextPackage(agent, slug, classification, artifacts, devState, m
480
480
  const downstreamAgents = ['discovery-design-doc', 'pm', 'orchestrator', 'dev', 'deyvin', 'qa'];
481
481
  const shouldCarryFullFeatureContext = downstreamAgents.includes(agent);
482
482
 
483
- if (shouldCarryFullFeatureContext && artifacts.prd.exists) pkg.push(artifacts.prd.path);
484
- if (shouldCarryFullFeatureContext && artifacts.sheldon_enrichment.exists) pkg.push(artifacts.sheldon_enrichment.path);
485
- if (shouldCarryFullFeatureContext && artifacts.sheldon_validation && artifacts.sheldon_validation.exists) {
486
- pkg.push(artifacts.sheldon_validation.path);
487
- }
483
+ if (shouldCarryFullFeatureContext && artifacts.prd.exists) pkg.push(artifacts.prd.path);
484
+ if (shouldCarryFullFeatureContext && artifacts.sheldon_enrichment.exists) pkg.push(artifacts.sheldon_enrichment.path);
485
+ if (shouldCarryFullFeatureContext && artifacts.sheldon_validation && artifacts.sheldon_validation.exists) {
486
+ pkg.push(artifacts.sheldon_validation.path);
487
+ }
488
488
  if ((shouldCarryFullFeatureContext || ['analyst', 'architect'].includes(agent)) && artifacts.requirements.exists) {
489
489
  pkg.push(artifacts.requirements.path);
490
490
  }
@@ -685,10 +685,10 @@ function evaluateReadiness(artifacts, phaseGates, classification, agent, devStat
685
685
  if (!artifacts.spec.exists) blockers.push('spec file missing');
686
686
  if (classification && classification !== 'MICRO') {
687
687
  if (!designDoc.exists) {
688
- blockers.push('design-doc.md missing — @discovery-design-doc must run before implementation');
688
+ blockers.push('design-doc.md missing — @architect (merged mode) or @sheldon must produce it before implementation');
689
689
  }
690
690
  if (!readiness.exists) {
691
- blockers.push('readiness.md missing — @discovery-design-doc must run before implementation');
691
+ blockers.push('readiness.md missing — @architect (merged mode) or @sheldon must produce it before implementation');
692
692
  }
693
693
  }
694
694
  if (classification === 'MEDIUM') {