@jaimevalasek/aioson 1.33.1 → 1.37.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 (162) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/docs/en/1-understand/ecosystem-map.md +45 -29
  3. package/docs/en/1-understand/glossary.md +6 -6
  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 +33 -39
  7. package/docs/en/2-start/initial-decisions.md +22 -19
  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 +13 -3
  11. package/docs/en/3-recipes/full-feature-with-sheldon.md +329 -338
  12. package/docs/en/4-agents/README.md +33 -16
  13. package/docs/en/4-agents/briefing-refiner.md +146 -0
  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/README.md +1 -0
  17. package/docs/en/5-reference/autopilot-handoff.md +286 -0
  18. package/docs/en/5-reference/cli-reference.md +57 -48
  19. package/docs/en/5-reference/executable-verification.md +10 -7
  20. package/docs/en/5-reference/parallel.md +2 -0
  21. package/docs/en/5-reference/qa-browser.md +2 -2
  22. package/docs/en/README.md +3 -3
  23. package/docs/pt/1-entender/glossario.md +5 -5
  24. package/docs/pt/1-entender/mapa-do-ecossistema.md +33 -22
  25. package/docs/pt/2-comecar/decisoes-iniciais.md +20 -18
  26. package/docs/pt/2-comecar/primeiro-projeto.md +2 -2
  27. package/docs/pt/3-receitas/README.md +1 -1
  28. package/docs/pt/3-receitas/app-saas-do-zero.md +35 -122
  29. package/docs/pt/3-receitas/da-ideia-ao-prd-via-briefing.md +8 -1
  30. package/docs/pt/3-receitas/feature-completa-com-sheldon.md +291 -338
  31. package/docs/pt/4-agentes/README.md +13 -11
  32. package/docs/pt/4-agentes/analyst.md +9 -5
  33. package/docs/pt/4-agentes/architect.md +9 -5
  34. package/docs/pt/4-agentes/briefing-refiner.md +64 -40
  35. package/docs/pt/4-agentes/briefing.md +6 -1
  36. package/docs/pt/4-agentes/dev.md +38 -8
  37. package/docs/pt/4-agentes/deyvin.md +4 -0
  38. package/docs/pt/4-agentes/discover.md +4 -0
  39. package/docs/pt/4-agentes/discovery-design-doc.md +25 -23
  40. package/docs/pt/4-agentes/neo.md +4 -0
  41. package/docs/pt/4-agentes/orache.md +6 -0
  42. package/docs/pt/4-agentes/orchestrator.md +176 -118
  43. package/docs/pt/4-agentes/pentester.md +11 -5
  44. package/docs/pt/4-agentes/pm.md +15 -7
  45. package/docs/pt/4-agentes/product.md +19 -1
  46. package/docs/pt/4-agentes/qa.md +10 -2
  47. package/docs/pt/4-agentes/setup.md +3 -1
  48. package/docs/pt/4-agentes/sheldon.md +178 -99
  49. package/docs/pt/4-agentes/tester.md +6 -0
  50. package/docs/pt/4-agentes/ux-ui.md +9 -5
  51. package/docs/pt/5-referencia/README.md +1 -1
  52. package/docs/pt/5-referencia/agent-chain-continuity.md +14 -5
  53. package/docs/pt/5-referencia/autopilot-handoff.md +191 -74
  54. package/docs/pt/5-referencia/comandos-cli.md +16 -7
  55. package/docs/pt/5-referencia/sdd-automation-scripts.md +6 -6
  56. package/docs/pt/5-referencia/sdd-framework.md +53 -16
  57. package/docs/pt/5-referencia/sdd-planos-e-estrutura.md +27 -15
  58. package/docs/pt/5-referencia/skills.md +2 -0
  59. package/docs/pt/README.md +4 -4
  60. package/docs/pt/agentes.md +50 -50
  61. package/package.json +2 -2
  62. package/src/agents.js +1 -1
  63. package/src/artifact-kinds.js +111 -0
  64. package/src/autopilot-signal.js +71 -0
  65. package/src/cli.js +112 -81
  66. package/src/commands/agent-epilogue.js +251 -186
  67. package/src/commands/agents.js +122 -50
  68. package/src/commands/audit-code.js +344 -0
  69. package/src/commands/briefing.js +337 -1
  70. package/src/commands/classify.js +389 -389
  71. package/src/commands/feature-close.js +122 -4
  72. package/src/commands/harness-check.js +259 -175
  73. package/src/commands/live.js +71 -11
  74. package/src/commands/review-feature.js +189 -0
  75. package/src/commands/runtime.js +81 -66
  76. package/src/commands/sync-agents-copy.js +115 -0
  77. package/src/commands/update.js +5 -1
  78. package/src/commands/verification-plan.js +143 -0
  79. package/src/commands/verify-artifact.js +593 -0
  80. package/src/commands/workflow-execute.js +434 -316
  81. package/src/commands/workflow-next.js +193 -20
  82. package/src/commands/workflow-plan.js +5 -5
  83. package/src/constants.js +2 -0
  84. package/src/doctor.js +4 -2
  85. package/src/gateway-pointer-merge.js +7 -1
  86. package/src/handoff-contract.js +267 -172
  87. package/src/harness/contract-integrity-gate.js +172 -0
  88. package/src/harness/contract-integrity.js +111 -0
  89. package/src/harness/contract-schema.js +377 -332
  90. package/src/harness/detect-runtime-feature.js +90 -0
  91. package/src/harness/static-criteria.js +192 -0
  92. package/src/i18n/messages/en.js +10 -5
  93. package/src/i18n/messages/es.js +10 -5
  94. package/src/i18n/messages/fr.js +10 -5
  95. package/src/i18n/messages/pt-BR.js +10 -5
  96. package/src/install-wizard.js +8 -8
  97. package/src/installer.js +13 -6
  98. package/src/lib/briefing-refiner/apply-feedback.js +18 -4
  99. package/src/lib/briefing-refiner/feedback-schema.js +73 -4
  100. package/src/lib/briefing-refiner/refinement-report.js +11 -0
  101. package/src/lib/briefing-refiner/review-html.js +388 -68
  102. package/src/parser.js +12 -0
  103. package/src/preflight-engine.js +12 -12
  104. package/src/verification/policy-engine.js +95 -95
  105. package/src/verification-policy.js +398 -0
  106. package/src/version.js +52 -1
  107. package/template/.aioson/agents/architect.md +34 -5
  108. package/template/.aioson/agents/briefing-refiner.md +91 -48
  109. package/template/.aioson/agents/briefing.md +4 -0
  110. package/template/.aioson/agents/committer.md +2 -1
  111. package/template/.aioson/agents/copywriter.md +30 -21
  112. package/template/.aioson/agents/design-hybrid-forge.md +10 -1
  113. package/template/.aioson/agents/dev.md +35 -23
  114. package/template/.aioson/agents/deyvin.md +4 -0
  115. package/template/.aioson/agents/discover.md +16 -3
  116. package/template/.aioson/agents/discovery-design-doc.md +7 -2
  117. package/template/.aioson/agents/genome.md +19 -10
  118. package/template/.aioson/agents/neo.md +34 -30
  119. package/template/.aioson/agents/orache.md +24 -11
  120. package/template/.aioson/agents/orchestrator.md +100 -7
  121. package/template/.aioson/agents/pentester.md +4 -0
  122. package/template/.aioson/agents/pm.md +8 -8
  123. package/template/.aioson/agents/product.md +25 -1
  124. package/template/.aioson/agents/profiler-enricher.md +20 -11
  125. package/template/.aioson/agents/profiler-forge.md +20 -11
  126. package/template/.aioson/agents/profiler-researcher.md +20 -11
  127. package/template/.aioson/agents/qa.md +96 -31
  128. package/template/.aioson/agents/setup.md +12 -1
  129. package/template/.aioson/agents/sheldon.md +103 -13
  130. package/template/.aioson/agents/site-forge.md +11 -2
  131. package/template/.aioson/agents/squad.md +20 -5
  132. package/template/.aioson/agents/tester.md +4 -0
  133. package/template/.aioson/agents/ux-ui.md +2 -1
  134. package/template/.aioson/agents/validator.md +33 -1
  135. package/template/.aioson/config/verification.json +61 -0
  136. package/template/.aioson/config.md +13 -9
  137. package/template/.aioson/docs/LAYERS.md +2 -2
  138. package/template/.aioson/docs/agent-help.md +126 -0
  139. package/template/.aioson/docs/autopilot-handoff.md +30 -20
  140. package/template/.aioson/docs/dev/execution-discipline.md +41 -0
  141. package/template/.aioson/docs/dev/phase-loop.md +50 -0
  142. package/template/.aioson/docs/play/llm-data-and-bindings.md +70 -7
  143. package/template/.aioson/docs/presets/workflow.config.full-merged.json +15 -0
  144. package/template/.aioson/docs/presets/workflow.config.lean.json +15 -0
  145. package/template/.aioson/docs/product/prd-contract.md +12 -12
  146. package/template/.aioson/docs/prototype-contract.md +21 -4
  147. package/template/.aioson/docs/reference-identity.md +94 -0
  148. package/template/.aioson/docs/sheldon/harness-contract.md +155 -28
  149. package/template/.aioson/docs/verification-config.md +82 -0
  150. package/template/.aioson/docs/verify-artifact-gates.md +91 -0
  151. package/template/.aioson/docs/workflow-lean-lane.md +129 -0
  152. package/template/.aioson/skills/design/interface-design/SKILL.md +17 -0
  153. package/template/.aioson/skills/design/interface-design/references/intent-and-domain.md +2 -0
  154. package/template/.aioson/skills/process/aioson-spec-driven/SKILL.md +16 -14
  155. package/template/.aioson/skills/process/aioson-spec-driven/references/approval-gates.md +2 -2
  156. package/template/.aioson/skills/process/aioson-spec-driven/references/classification-map.md +4 -4
  157. package/template/.aioson/skills/process/aioson-spec-driven/references/dev.md +15 -15
  158. package/template/.aioson/skills/process/prototype-forge/SKILL.md +6 -0
  159. package/template/.aioson/skills/process/reference-identity-extract/SKILL.md +164 -0
  160. package/template/.aioson/skills/static/multi-agent-patterns.md +5 -5
  161. package/template/AGENTS.md +36 -36
  162. 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-]*$).',
@@ -408,6 +412,7 @@ module.exports = {
408
412
  update: {
409
413
  not_installed: 'No AIOSON installation found in {targetDir}.',
410
414
  done_at: 'Update completed at: {targetDir}',
415
+ template_version: 'Template version applied: {version}',
411
416
  files_updated: 'Files updated: {count}',
412
417
  backups_created: 'Backups created: {count}',
413
418
  profile_renamed: 'i Profile `beginner` renamed to `creator` in project.context.md to better describe the user. Behavior unchanged. Edit the file to switch to `developer` if desired.',
@@ -487,7 +492,7 @@ module.exports = {
487
492
  features_dir_present: 'Features directory present (.aioson/context/features/)',
488
493
  features_dir_present_hint: 'Create .aioson/context/features/ to host per-feature dossiers (doctor --fix will create it).',
489
494
  auto_handoff_declared: 'Autopilot handoff flag declared (auto_handoff in project.context.md)',
490
- auto_handoff_declared_hint: 'The autopilot-handoff protocol is installed but auto_handoff is not set in project.context.md frontmatter autopilot stays inactive. Set auto_handoff: true to enable it, or auto_handoff: false to silence this warning.',
495
+ auto_handoff_declared_hint: 'The autopilot-handoff protocol is installed and auto_handoff is not set @product asks the run mode on screen at each feature kickoff (Autopilot / Step by step / Always). Set auto_handoff: true to always autopilot and skip the question, or false to always go step-by-step.',
491
496
  claude_commands_present: 'Claude slash commands present ({missing} missing of {required})',
492
497
  claude_commands_present_hint: 'Missing: {paths}. Run `aioson doctor . --fix` to restore them from the template.',
493
498
  version_drift: 'CLI version matches project.context.md (context: {context}, CLI: {cli})',
@@ -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-]*$).',
@@ -276,6 +280,7 @@ module.exports = {
276
280
  update: {
277
281
  not_installed: 'No se encontro instalacion de AIOSON en {targetDir}.',
278
282
  done_at: 'Actualizacion completada en: {targetDir}',
283
+ template_version: 'Version de template aplicada: {version}',
279
284
  files_updated: 'Archivos actualizados: {count}',
280
285
  backups_created: 'Backups creados: {count}',
281
286
  profile_renamed: 'i Perfil `beginner` renombrado a `creator` en project.context.md para describir mejor al usuario. Comportamiento sin cambios. Edita el archivo para cambiar a `developer` si lo prefieres.',
@@ -361,7 +366,7 @@ module.exports = {
361
366
  features_dir_present: 'Directorio de features presente (.aioson/context/features/)',
362
367
  features_dir_present_hint: 'Cree .aioson/context/features/ para hospedar dossiers por feature (doctor --fix lo crea).',
363
368
  auto_handoff_declared: 'Flag de autopilot handoff declarada (auto_handoff en project.context.md)',
364
- auto_handoff_declared_hint: 'El protocolo autopilot-handoff esta instalado pero auto_handoff no esta definido en el frontmatter de project.context.md el autopilot queda inactivo. Defina auto_handoff: true para activarlo, o auto_handoff: false para silenciar este aviso.',
369
+ auto_handoff_declared_hint: 'El protocolo autopilot-handoff esta instalado y auto_handoff no esta definido @product pregunta el modo en pantalla al inicio de cada feature (Autopilot / Paso a paso / Siempre). Defina auto_handoff: true para siempre autopilot y omitir la pregunta, o false para siempre paso a paso.',
365
370
  claude_commands_present: 'Slash commands de Claude presentes ({missing} ausentes de {required})',
366
371
  claude_commands_present_hint: 'Ausentes: {paths}. Ejecute `aioson doctor . --fix` para restaurarlos.',
367
372
  version_drift: 'Version del CLI coincide con project.context.md (contexto: {context}, CLI: {cli})',
@@ -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-]*$).',
@@ -275,6 +279,7 @@ module.exports = {
275
279
  update: {
276
280
  not_installed: 'Aucune installation AIOSON trouvee dans {targetDir}.',
277
281
  done_at: 'Mise a jour terminee dans : {targetDir}',
282
+ template_version: 'Version du template appliquee : {version}',
278
283
  files_updated: 'Fichiers mis a jour : {count}',
279
284
  backups_created: 'Sauvegardes creees : {count}',
280
285
  profile_renamed: 'i Profil `beginner` renomme en `creator` dans project.context.md pour mieux decrire l utilisateur. Comportement inchange. Modifiez le fichier pour passer a `developer` si vous le souhaitez.',
@@ -360,7 +365,7 @@ module.exports = {
360
365
  features_dir_present: 'Repertoire features present (.aioson/context/features/)',
361
366
  features_dir_present_hint: 'Creez .aioson/context/features/ pour heberger les dossiers par feature (doctor --fix le cree).',
362
367
  auto_handoff_declared: 'Flag autopilot handoff declare (auto_handoff dans project.context.md)',
363
- auto_handoff_declared_hint: 'Le protocole autopilot-handoff est installe mais auto_handoff n\'est pas defini dans le frontmatter de project.context.md l\'autopilot reste inactif. Definissez auto_handoff: true pour l\'activer, ou auto_handoff: false pour faire taire cet avertissement.',
368
+ auto_handoff_declared_hint: 'Le protocole autopilot-handoff est installe et auto_handoff n\'est pas defini @product demande le mode a l\'ecran au debut de chaque feature (Autopilot / Pas a pas / Toujours). Definissez auto_handoff: true pour toujours autopilot et sauter la question, ou false pour toujours pas a pas.',
364
369
  claude_commands_present: 'Slash commands de Claude presents ({missing} absents sur {required})',
365
370
  claude_commands_present_hint: 'Absents : {paths}. Executez `aioson doctor . --fix` pour les restaurer.',
366
371
  version_drift: 'Version du CLI conforme a project.context.md (contexte : {context}, CLI : {cli})',
@@ -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-]*$).',
@@ -375,6 +379,7 @@ module.exports = {
375
379
  update: {
376
380
  not_installed: 'Nenhuma instalacao do AIOSON encontrada em {targetDir}.',
377
381
  done_at: 'Atualizacao concluida em: {targetDir}',
382
+ template_version: 'Versao do template aplicada: {version}',
378
383
  files_updated: 'Arquivos atualizados: {count}',
379
384
  backups_created: 'Backups criados: {count}',
380
385
  profile_renamed: 'i Perfil `beginner` renomeado para `creator` em project.context.md para descrever melhor o usuario. Comportamento inalterado. Edite o arquivo para mudar para `developer` se preferir.',
@@ -461,7 +466,7 @@ module.exports = {
461
466
  features_dir_present: 'Diretorio de features presente (.aioson/context/features/)',
462
467
  features_dir_present_hint: 'Crie .aioson/context/features/ para hospedar dossies por feature (doctor --fix cria automaticamente).',
463
468
  auto_handoff_declared: 'Flag de autopilot handoff declarada (auto_handoff no project.context.md)',
464
- auto_handoff_declared_hint: 'O protocolo autopilot-handoff esta instalado mas auto_handoff nao esta definido no frontmatter do project.context.md o autopilot fica inativo. Defina auto_handoff: true para ativar, ou auto_handoff: false para silenciar este aviso.',
469
+ auto_handoff_declared_hint: 'O protocolo autopilot-handoff esta instalado e auto_handoff nao esta definido — o @product pergunta o modo na tela no inicio de cada feature (Autopilot / Passo a passo / Sempre). Defina auto_handoff: true para sempre autopilot e pular a pergunta, ou false para sempre passo a passo.',
465
470
  claude_commands_present: 'Slash commands do Claude presentes ({missing} ausentes de {required})',
466
471
  claude_commands_present_hint: 'Ausentes: {paths}. Rode `aioson doctor . --fix` para restaurar a partir do template.',
467
472
  version_drift: 'Versao do CLI bate com project.context.md (contexto: {context}, CLI: {cli})',
@@ -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)) {
@@ -39,7 +39,10 @@ function summarizeSkippedChanges(feedback) {
39
39
  return [...sectionChanges, ...decisionChanges];
40
40
  }
41
41
 
42
- async function applyDeclinedFeedback(projectDir, slug, feedback, { allowStale = false } = {}) {
42
+ // Declining never writes to briefings.md, so a stale source hash is irrelevant
43
+ // to safety — validating it would only dead-end the user whose briefing moved
44
+ // on. Hence allowStale defaults to TRUE here (and only here).
45
+ async function applyDeclinedFeedback(projectDir, slug, feedback, { allowStale = true } = {}) {
43
46
  const briefingPath = resolveBriefingPath(projectDir, slug, 'briefings.md');
44
47
  const currentMarkdown = await fs.readFile(briefingPath, 'utf8');
45
48
  const currentSourceHash = hashText(currentMarkdown);
@@ -57,10 +60,12 @@ async function applyDeclinedFeedback(projectDir, slug, feedback, { allowStale =
57
60
  source_hash: feedback.source_hash,
58
61
  applied_hash: currentSourceHash,
59
62
  status: 'declined',
63
+ round: feedback.round || 1,
60
64
  applied_changes: [],
61
65
  skipped_changes: skippedChanges,
62
66
  unresolved_comments: unresolvedComments,
63
67
  blocking_items: feedback.blocking_items || [],
68
+ findings: feedback.findings || [],
64
69
  next_action: 'rerun_review'
65
70
  });
66
71
 
@@ -104,15 +109,22 @@ async function applyConfirmedFeedback(projectDir, slug, feedback, { confirmed =
104
109
  if (returnedToDraft) {
105
110
  returnApprovedBriefingToDraft(registry, slug);
106
111
  }
112
+ // A blocking finding the user left pending blocks the PRD the same way an
113
+ // explicit blocking item does — even if the export step never mirrored it
114
+ // into blocking_items (hand-written feedback).
115
+ const pendingBlockingFindings = (feedback.findings || []).filter(
116
+ (finding) => finding.blocking && finding.status === 'pending'
117
+ );
118
+ const hasBlockers = (feedback.blocking_items || []).length > 0 || pendingBlockingFindings.length > 0;
107
119
  markRefinementState(registry, slug, {
108
- refinement_status: feedback.blocking_items.length > 0 ? 'blocked' : 'applied',
120
+ refinement_status: hasBlockers ? 'blocked' : 'applied',
109
121
  review_html: `.aioson/briefings/${slug}/review.html`,
110
122
  refinement_report: `.aioson/briefings/${slug}/refinement-report.md`
111
123
  });
112
124
  await writeBriefingRegistry(projectDir, registry);
113
125
 
114
126
  const unresolvedComments = (feedback.comments || []).filter((comment) => !comment.resolved);
115
- const nextAction = feedback.blocking_items.length > 0 ? 'resolve_blockers' : 'approve_briefing';
127
+ const nextAction = hasBlockers ? 'resolve_blockers' : 'approve_briefing';
116
128
  const report = buildRefinementReport({
117
129
  briefing_slug: slug,
118
130
  source_briefing_path: `.aioson/briefings/${slug}/briefings.md`,
@@ -120,15 +132,17 @@ async function applyConfirmedFeedback(projectDir, slug, feedback, { confirmed =
120
132
  source_hash: feedback.source_hash,
121
133
  applied_hash: appliedHash,
122
134
  status: 'applied',
135
+ round: feedback.round || 1,
123
136
  applied_changes: appliedChanges,
124
137
  skipped_changes: [],
125
138
  unresolved_comments: unresolvedComments,
126
139
  blocking_items: feedback.blocking_items || [],
140
+ findings: feedback.findings || [],
127
141
  next_action: nextAction
128
142
  });
129
143
  await fs.writeFile(resolveBriefingPath(projectDir, slug, 'refinement-report.md'), report, 'utf8');
130
144
 
131
- return { ok: true, appliedChanges, nextAction, appliedHash, returnedToDraft };
145
+ return { ok: true, appliedChanges, nextAction, appliedHash, returnedToDraft, pendingBlockingFindings: pendingBlockingFindings.length };
132
146
  }
133
147
 
134
148
  module.exports = { applyConfirmedFeedback, applyDeclinedFeedback };