@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,398 @@
1
+ 'use strict';
2
+
3
+ // Verification sub-agent policy (.aioson/config/verification.json).
4
+ //
5
+ // Piece 1 of the "smart harness, fewer agents doing more" redesign. This is the
6
+ // reader the @dev phase-loop (auto-continue) and the post-dev review cycle
7
+ // consult to decide WHICH verification sub-agent runs, WHEN, and on WHICH model
8
+ // — resolved per host harness.
9
+ //
10
+ // Two dispatch modes per agent, keyed by the host harness (claude|codex|opencode):
11
+ // - native: in-harness sub-agent. On Claude Code the Task tool runs a Claude
12
+ // model tier (e.g. sonnet-4.6); codex/opencode run their own
13
+ // configured model. You CANNOT run a codex/gpt model as a native
14
+ // Claude Code sub-agent — that is what `external` is for.
15
+ // - external: spawn a different vendor CLI as a read-only auditor
16
+ // (`aioson verify:implementation --tool=...`). The only way to bring
17
+ // a cross-vendor model in while hosted elsewhere — see `cross_check`.
18
+ //
19
+ // The file is auto-generated from template/.aioson/config/verification.json by the
20
+ // installer (isConfigMergePath additive merge) and is hand-editable: user values
21
+ // survive `aioson update`, only `version` is template-owned. A missing or
22
+ // malformed file degrades to buildDefaultVerificationConfig() — never throws.
23
+
24
+ const fs = require('node:fs/promises');
25
+ const path = require('node:path');
26
+ const { exists } = require('./utils');
27
+
28
+ const VERIFICATION_CONFIG_RELATIVE_PATH = '.aioson/config/verification.json';
29
+ const KNOWN_HOSTS = ['claude', 'codex', 'opencode'];
30
+ const DISPATCH_MODES = ['native', 'external'];
31
+ const VERIFICATION_AGENTS = ['qa', 'tester', 'pentester', 'validator'];
32
+ const TRIGGERS = ['per-phase', 'end-of-feature', 'sensitive-surface'];
33
+ const DEFAULT_HOST = 'claude';
34
+ const DEFAULT_MODEL = 'configured-default';
35
+
36
+ // ---------------------------------------------------------------------------
37
+ // Defaults
38
+ // ---------------------------------------------------------------------------
39
+
40
+ function defaultTriggers(agentId) {
41
+ switch (agentId) {
42
+ case 'qa': return ['per-phase', 'end-of-feature'];
43
+ case 'pentester': return ['sensitive-surface', 'end-of-feature'];
44
+ default: return ['end-of-feature'];
45
+ }
46
+ }
47
+
48
+ function defaultReport(agentId) {
49
+ switch (agentId) {
50
+ case 'qa': return 'qa-report-{slug}.md';
51
+ case 'tester': return 'test-report-{slug}.md';
52
+ case 'pentester': return 'security-findings-{slug}.json';
53
+ case 'validator': return 'validator-report-{slug}.json';
54
+ default: return `${agentId}-report-{slug}.md`;
55
+ }
56
+ }
57
+
58
+ function buildDefaultAgent(agentId) {
59
+ // pentester/validator default to the strongest tier; qa/tester to a cheaper
60
+ // tier — the per-phase verifier should be cheap, the security/contract
61
+ // verdicts should not be.
62
+ const claudeModel = (agentId === 'pentester' || agentId === 'validator') ? 'opus-4.8' : 'sonnet-4.6';
63
+ const agent = {
64
+ enabled: (agentId === 'qa' || agentId === 'validator') ? true : 'auto',
65
+ triggers: defaultTriggers(agentId),
66
+ dispatch: {
67
+ claude: { mode: 'native', model: claudeModel },
68
+ codex: { mode: 'native', model: DEFAULT_MODEL },
69
+ opencode: { mode: 'native', model: DEFAULT_MODEL }
70
+ },
71
+ report: defaultReport(agentId)
72
+ };
73
+ if (agentId === 'validator') {
74
+ agent.cross_check = { enabled: false, mode: 'external', tool: 'codex', model: DEFAULT_MODEL };
75
+ }
76
+ return agent;
77
+ }
78
+
79
+ function buildDefaultVerificationConfig() {
80
+ const agents = {};
81
+ for (const id of VERIFICATION_AGENTS) agents[id] = buildDefaultAgent(id);
82
+ return {
83
+ version: '1.0',
84
+ host: 'auto',
85
+ agents,
86
+ budget: {
87
+ max_subagents_per_phase: 1,
88
+ skip_on_micro: true,
89
+ full_smoke: 'end-of-feature-only'
90
+ },
91
+ phase_loop: {
92
+ auto_continue: true,
93
+ compact_between_phases: true,
94
+ max_fix_retries_per_phase: 2
95
+ },
96
+ // Deterministic build-free code-quality scan (`aioson audit:code`) wired into
97
+ // the tracked workflow:next dev/qa done-gate. `tracked_gate`: 'advisory'
98
+ // records + emits but never blocks (default — audit:code is a heuristic
99
+ // opinion, not the feature's declared contract); 'block' makes a HIGH finding
100
+ // in scope a hard gate; 'off' skips. `scope`: 'changed' = the git diff (fast),
101
+ // 'full' = the whole tree.
102
+ audit_code: {
103
+ tracked_gate: 'advisory',
104
+ scope: 'changed'
105
+ }
106
+ };
107
+ }
108
+
109
+ // ---------------------------------------------------------------------------
110
+ // Normalization
111
+ // ---------------------------------------------------------------------------
112
+
113
+ // Collapse an arbitrary value to a known host, falling back when unrecognized.
114
+ function normalizeHost(host, fallback = DEFAULT_HOST) {
115
+ const safe = String(host || '').trim().toLowerCase();
116
+ return KNOWN_HOSTS.includes(safe) ? safe : fallback;
117
+ }
118
+
119
+ // The top-level `host` setting additionally allows the literal 'auto'.
120
+ function normalizeHostSetting(host) {
121
+ const safe = String(host || 'auto').trim().toLowerCase();
122
+ if (safe === 'auto') return 'auto';
123
+ return KNOWN_HOSTS.includes(safe) ? safe : 'auto';
124
+ }
125
+
126
+ function normalizeEnabled(value, fallback = 'auto') {
127
+ if (value === true || value === false) return value;
128
+ if (String(value).trim().toLowerCase() === 'auto') return 'auto';
129
+ return fallback;
130
+ }
131
+
132
+ function normalizeDispatchMode(mode, fallback = 'native') {
133
+ const safe = String(mode || '').trim().toLowerCase();
134
+ return DISPATCH_MODES.includes(safe) ? safe : fallback;
135
+ }
136
+
137
+ function normalizeModel(model, fallback = DEFAULT_MODEL) {
138
+ return typeof model === 'string' && model.trim() ? model.trim() : fallback;
139
+ }
140
+
141
+ function normalizeDispatchEntry(entry, fallbackEntry) {
142
+ const safe = entry && typeof entry === 'object' ? entry : {};
143
+ return {
144
+ mode: normalizeDispatchMode(safe.mode, fallbackEntry.mode),
145
+ model: normalizeModel(safe.model, fallbackEntry.model)
146
+ };
147
+ }
148
+
149
+ function normalizeTriggers(triggers, fallback) {
150
+ if (!Array.isArray(triggers)) return [...fallback];
151
+ const cleaned = triggers
152
+ .map((t) => String(t || '').trim().toLowerCase())
153
+ .filter((t) => TRIGGERS.includes(t));
154
+ return cleaned.length ? Array.from(new Set(cleaned)) : [...fallback];
155
+ }
156
+
157
+ function normalizeAgent(parsed, agentId) {
158
+ const def = buildDefaultAgent(agentId);
159
+ const raw = parsed && typeof parsed === 'object' ? parsed : {};
160
+ const rawDispatch = raw.dispatch && typeof raw.dispatch === 'object' ? raw.dispatch : {};
161
+ const dispatch = {};
162
+ for (const host of KNOWN_HOSTS) {
163
+ dispatch[host] = normalizeDispatchEntry(rawDispatch[host], def.dispatch[host]);
164
+ }
165
+ const agent = {
166
+ enabled: normalizeEnabled(raw.enabled, def.enabled),
167
+ triggers: normalizeTriggers(raw.triggers, def.triggers),
168
+ dispatch,
169
+ report: normalizeModel(raw.report, def.report)
170
+ };
171
+ if (def.cross_check) {
172
+ const rawCc = raw.cross_check && typeof raw.cross_check === 'object' ? raw.cross_check : {};
173
+ agent.cross_check = {
174
+ enabled: typeof rawCc.enabled === 'boolean' ? rawCc.enabled : def.cross_check.enabled,
175
+ mode: normalizeDispatchMode(rawCc.mode, def.cross_check.mode),
176
+ tool: normalizeHost(rawCc.tool, def.cross_check.tool),
177
+ model: normalizeModel(rawCc.model, def.cross_check.model)
178
+ };
179
+ }
180
+ return agent;
181
+ }
182
+
183
+ function normalizeBudget(parsed) {
184
+ const def = buildDefaultVerificationConfig().budget;
185
+ const raw = parsed && typeof parsed === 'object' ? parsed : {};
186
+ return {
187
+ max_subagents_per_phase: Number.isInteger(raw.max_subagents_per_phase) && raw.max_subagents_per_phase >= 0
188
+ ? raw.max_subagents_per_phase
189
+ : def.max_subagents_per_phase,
190
+ skip_on_micro: typeof raw.skip_on_micro === 'boolean' ? raw.skip_on_micro : def.skip_on_micro,
191
+ full_smoke: normalizeModel(raw.full_smoke, def.full_smoke)
192
+ };
193
+ }
194
+
195
+ function normalizePhaseLoop(parsed) {
196
+ const def = buildDefaultVerificationConfig().phase_loop;
197
+ const raw = parsed && typeof parsed === 'object' ? parsed : {};
198
+ return {
199
+ auto_continue: typeof raw.auto_continue === 'boolean' ? raw.auto_continue : def.auto_continue,
200
+ compact_between_phases: typeof raw.compact_between_phases === 'boolean'
201
+ ? raw.compact_between_phases
202
+ : def.compact_between_phases,
203
+ max_fix_retries_per_phase: Number.isInteger(raw.max_fix_retries_per_phase) && raw.max_fix_retries_per_phase >= 0
204
+ ? raw.max_fix_retries_per_phase
205
+ : def.max_fix_retries_per_phase
206
+ };
207
+ }
208
+
209
+ function normalizeAuditCode(parsed) {
210
+ const def = buildDefaultVerificationConfig().audit_code;
211
+ const raw = parsed && typeof parsed === 'object' ? parsed : {};
212
+ const gate = String(raw.tracked_gate || '').trim().toLowerCase();
213
+ const scope = String(raw.scope || '').trim().toLowerCase();
214
+ return {
215
+ tracked_gate: ['block', 'advisory', 'off'].includes(gate) ? gate : def.tracked_gate,
216
+ scope: ['changed', 'full'].includes(scope) ? scope : def.scope
217
+ };
218
+ }
219
+
220
+ function normalizeVerificationConfig(parsed) {
221
+ const def = buildDefaultVerificationConfig();
222
+ const raw = parsed && typeof parsed === 'object' ? parsed : {};
223
+ const rawAgents = raw.agents && typeof raw.agents === 'object' ? raw.agents : {};
224
+ const agents = {};
225
+ // Always materialize the four known agents (defaults fill any gaps)...
226
+ for (const id of VERIFICATION_AGENTS) agents[id] = normalizeAgent(rawAgents[id], id);
227
+ // ...and preserve any user-defined extra agents (best-effort, generic shape).
228
+ for (const id of Object.keys(rawAgents)) {
229
+ if (!VERIFICATION_AGENTS.includes(id)) agents[id] = normalizeAgent(rawAgents[id], id);
230
+ }
231
+ return {
232
+ version: typeof raw.version === 'string' ? raw.version : def.version,
233
+ host: normalizeHostSetting(raw.host),
234
+ agents,
235
+ budget: normalizeBudget(raw.budget),
236
+ phase_loop: normalizePhaseLoop(raw.phase_loop),
237
+ audit_code: normalizeAuditCode(raw.audit_code)
238
+ };
239
+ }
240
+
241
+ // ---------------------------------------------------------------------------
242
+ // Read
243
+ // ---------------------------------------------------------------------------
244
+
245
+ async function readVerificationConfig(targetDir) {
246
+ const configPath = path.join(targetDir, VERIFICATION_CONFIG_RELATIVE_PATH);
247
+ if (!(await exists(configPath))) return buildDefaultVerificationConfig();
248
+ try {
249
+ const rawText = await fs.readFile(configPath, 'utf8');
250
+ return normalizeVerificationConfig(JSON.parse(rawText));
251
+ } catch {
252
+ return buildDefaultVerificationConfig();
253
+ }
254
+ }
255
+
256
+ // ---------------------------------------------------------------------------
257
+ // Resolution / getters
258
+ // ---------------------------------------------------------------------------
259
+
260
+ // Precedence: explicit arg > config.host (when not 'auto') > env > default.
261
+ function resolveHost(config, explicitHost = null, env = process.env) {
262
+ const explicit = String(explicitHost || '').trim().toLowerCase();
263
+ if (KNOWN_HOSTS.includes(explicit)) return explicit;
264
+
265
+ const configured = config && typeof config.host === 'string' ? config.host.trim().toLowerCase() : 'auto';
266
+ if (KNOWN_HOSTS.includes(configured)) return configured;
267
+
268
+ const envTool = String((env && (env.AIOSON_RUNNER_TOOL || env.AIOSON_TOOL)) || '').trim().toLowerCase();
269
+ if (KNOWN_HOSTS.includes(envTool)) return envTool;
270
+
271
+ return DEFAULT_HOST;
272
+ }
273
+
274
+ function getAgentConfig(config, agentId) {
275
+ const id = String(agentId || '').trim();
276
+ if (config && config.agents && typeof config.agents === 'object' && config.agents[id]) {
277
+ return config.agents[id];
278
+ }
279
+ if (VERIFICATION_AGENTS.includes(id)) return buildDefaultAgent(id);
280
+ return null;
281
+ }
282
+
283
+ // Resolve the {mode, model, host} a verification sub-agent should run on for the
284
+ // active host harness. Falls back to the default-host entry, then to built-ins.
285
+ function getAgentDispatch(config, agentId, host = null) {
286
+ const agent = getAgentConfig(config, agentId);
287
+ if (!agent) return null;
288
+ const resolvedHost = resolveHost(config, host);
289
+ const dispatch = agent.dispatch && typeof agent.dispatch === 'object' ? agent.dispatch : {};
290
+ const entry = dispatch[resolvedHost] || dispatch[DEFAULT_HOST];
291
+ if (entry && typeof entry === 'object') {
292
+ return { host: resolvedHost, mode: normalizeDispatchMode(entry.mode), model: normalizeModel(entry.model) };
293
+ }
294
+ const def = buildDefaultAgent(agentId);
295
+ const defEntry = def.dispatch[resolvedHost] || def.dispatch[DEFAULT_HOST];
296
+ return { host: resolvedHost, mode: defEntry.mode, model: defEntry.model };
297
+ }
298
+
299
+ // Resolve enabled:true|false|'auto' to a concrete boolean given run context.
300
+ // 'auto' lets the framework decide: pentester only on a sensitive surface,
301
+ // tester on anything above MICRO, everything else on.
302
+ function resolveAgentEnabled(config, agentId, context = {}) {
303
+ const agent = getAgentConfig(config, agentId);
304
+ if (!agent) return false;
305
+ if (agent.enabled === true) return true;
306
+ if (agent.enabled === false) return false;
307
+ const classification = String(context.classification || '').trim().toUpperCase();
308
+ const sensitiveSurface = Boolean(context.sensitiveSurface);
309
+ switch (String(agentId).trim()) {
310
+ case 'pentester': return sensitiveSurface;
311
+ case 'tester': return classification !== 'MICRO';
312
+ default: return true;
313
+ }
314
+ }
315
+
316
+ function getAgentTriggers(config, agentId) {
317
+ const agent = getAgentConfig(config, agentId);
318
+ return agent && Array.isArray(agent.triggers) ? [...agent.triggers] : [];
319
+ }
320
+
321
+ function agentHasTrigger(config, agentId, trigger) {
322
+ return getAgentTriggers(config, agentId).includes(String(trigger || '').trim().toLowerCase());
323
+ }
324
+
325
+ function getAgentReportTemplate(config, agentId) {
326
+ const agent = getAgentConfig(config, agentId);
327
+ return agent && typeof agent.report === 'string' ? agent.report : '';
328
+ }
329
+
330
+ function resolveAgentReportPath(config, agentId, slug) {
331
+ return getAgentReportTemplate(config, agentId).replace(/\{slug\}/g, String(slug || '').trim());
332
+ }
333
+
334
+ function getCrossCheck(config, agentId) {
335
+ const agent = getAgentConfig(config, agentId);
336
+ if (!agent || !agent.cross_check || typeof agent.cross_check !== 'object') return null;
337
+ const cc = agent.cross_check;
338
+ return {
339
+ enabled: Boolean(cc.enabled),
340
+ mode: normalizeDispatchMode(cc.mode, 'external'),
341
+ tool: normalizeHost(cc.tool, 'codex'),
342
+ model: normalizeModel(cc.model)
343
+ };
344
+ }
345
+
346
+ function getBudget(config) {
347
+ return normalizeBudget(config && config.budget);
348
+ }
349
+
350
+ // Phase-loop behavior: whether @dev auto-continues across phases, compacts
351
+ // between them, and how many in-phase fix retries are allowed before stopping.
352
+ function getPhaseLoop(config) {
353
+ return normalizePhaseLoop(config && config.phase_loop);
354
+ }
355
+
356
+ // audit:code tracked-gate policy: 'block' | 'advisory' | 'off' + 'changed' | 'full'.
357
+ function getAuditCodePolicy(config) {
358
+ return normalizeAuditCode(config && config.audit_code);
359
+ }
360
+
361
+ // Composed decision: should `agentId` run for `context.trigger` right now?
362
+ // Combines trigger membership + enabled resolution + the skip-on-micro budget
363
+ // guard (per-phase verification is suppressed on MICRO to save tokens).
364
+ function shouldRunForTrigger(config, agentId, context = {}) {
365
+ const trigger = String(context.trigger || '').trim().toLowerCase();
366
+ if (!agentHasTrigger(config, agentId, trigger)) return false;
367
+ if (!resolveAgentEnabled(config, agentId, context)) return false;
368
+ const classification = String(context.classification || '').trim().toUpperCase();
369
+ if (getBudget(config).skip_on_micro && classification === 'MICRO' && trigger === 'per-phase') return false;
370
+ return true;
371
+ }
372
+
373
+ module.exports = {
374
+ VERIFICATION_CONFIG_RELATIVE_PATH,
375
+ KNOWN_HOSTS,
376
+ DISPATCH_MODES,
377
+ VERIFICATION_AGENTS,
378
+ TRIGGERS,
379
+ DEFAULT_HOST,
380
+ DEFAULT_MODEL,
381
+ buildDefaultVerificationConfig,
382
+ buildDefaultAgent,
383
+ normalizeVerificationConfig,
384
+ readVerificationConfig,
385
+ resolveHost,
386
+ getAgentConfig,
387
+ getAgentDispatch,
388
+ resolveAgentEnabled,
389
+ getAgentTriggers,
390
+ agentHasTrigger,
391
+ getAgentReportTemplate,
392
+ resolveAgentReportPath,
393
+ getCrossCheck,
394
+ getBudget,
395
+ getPhaseLoop,
396
+ getAuditCodePolicy,
397
+ shouldRunForTrigger
398
+ };
package/src/version.js CHANGED
@@ -3,11 +3,14 @@
3
3
  const fs = require('node:fs');
4
4
  const fsPromises = require('node:fs/promises');
5
5
  const path = require('node:path');
6
+ const { execFileSync } = require('node:child_process');
6
7
 
7
- const PACKAGE_JSON_PATH = path.join(__dirname, '..', 'package.json');
8
+ const ROOT_DIR = path.join(__dirname, '..');
9
+ const PACKAGE_JSON_PATH = path.join(ROOT_DIR, 'package.json');
8
10
  const DEFAULT_VERSION = '0.0.0';
9
11
 
10
12
  let cachedVersion = null;
13
+ let cachedGitInfo; // undefined = not computed yet; null = no git build info
11
14
 
12
15
  function parseVersionFromPackageJson(text) {
13
16
  try {
@@ -43,8 +46,56 @@ async function getCliVersion() {
43
46
  }
44
47
  }
45
48
 
49
+ function runGitSync(args) {
50
+ return execFileSync('git', ['-C', ROOT_DIR, ...args], {
51
+ encoding: 'utf8',
52
+ stdio: ['ignore', 'pipe', 'ignore']
53
+ }).trim();
54
+ }
55
+
56
+ /**
57
+ * Short git build info for the package's OWN checkout (e.g. an `npm link`ed dev
58
+ * install). Lets `aioson --version` and the per-project install stamp report the
59
+ * exact commit a linked framework is running, so you don't need a semver bump
60
+ * per commit to know what's installed. Returns null when the package is not a
61
+ * git checkout (a normal npm install) or git is unavailable. Best-effort.
62
+ */
63
+ function getGitBuildInfoSync() {
64
+ if (cachedGitInfo !== undefined) return cachedGitInfo;
65
+ try {
66
+ const sha = runGitSync(['rev-parse', '--short', 'HEAD']);
67
+ if (!sha) {
68
+ cachedGitInfo = null;
69
+ return cachedGitInfo;
70
+ }
71
+ let date = null;
72
+ try {
73
+ date = runGitSync(['show', '-s', '--format=%cs', 'HEAD']) || null;
74
+ } catch {
75
+ /* date is optional */
76
+ }
77
+ cachedGitInfo = { sha, date };
78
+ } catch {
79
+ cachedGitInfo = null;
80
+ }
81
+ return cachedGitInfo;
82
+ }
83
+
84
+ /**
85
+ * Display label: the semver, plus `(sha, date)` when running from a git checkout.
86
+ * e.g. "1.35.0 (<sha>, <date>)" linked, or "1.35.0" from an npm install.
87
+ */
88
+ function getCliVersionLabelSync() {
89
+ const version = getCliVersionSync();
90
+ const git = getGitBuildInfoSync();
91
+ if (!git) return version;
92
+ return git.date ? `${version} (${git.sha}, ${git.date})` : `${version} (${git.sha})`;
93
+ }
94
+
46
95
  module.exports = {
47
96
  getCliVersion,
48
97
  getCliVersionSync,
98
+ getGitBuildInfoSync,
99
+ getCliVersionLabelSync,
49
100
  parseVersionFromPackageJson
50
101
  };
@@ -9,7 +9,7 @@ Evaluate this immediately after reading this file and before loading any other c
9
9
  If the user only activates `@analyst` without naming a feature, PRD, or concrete analysis task:
10
10
 
11
11
  1. When the CLI is available, run `aioson workflow:status .` and `aioson context:select . --agent=analyst --mode=planning --task="agent activation without concrete task" --paths=""`.
12
- 2. Load only: `.aioson/context/project.context.md` and a filename listing of `.aioson/context/prd*.md` / `requirements-*.md` (names only — no contents).
12
+ 2. Load only: `.aioson/context/project.context.md` and a filename listing of `.aioson/context/prd*.md` / `requirements-*.md` (names only — no contents).
13
13
  3. Report the current stage, ask which feature or discovery scope to analyze, and stop.
14
14
 
15
15
  Do NOT load on activation: PRD/requirements contents, `discovery.md`, `spec*.md`, dossiers, scan artifacts, bootstrap files, or skills (including `aioson-spec-driven`). Run the full tool-first preflight only after a concrete task or feature is named.
@@ -75,7 +75,7 @@ If the CLI is not available, compare modification dates manually:
75
75
 
76
76
  ## Mode detection
77
77
 
78
- Resolve the active feature first: run `aioson feature:current . 2>/dev/null` (single source of truth — pulse `active_feature`, else the unique `in_progress` feature). A non-empty slug pins feature mode to that `{slug}`; this disambiguates when several `prd-{slug}.md` files coexist. If it returns `ambiguous: true` (`--json`), ask which feature before loading. Without the CLI, read `active_feature` from `.aioson/context/project-pulse.md`. Then check:
78
+ Resolve the active feature first: run `aioson feature:current . 2>/dev/null` (single source of truth — pulse `active_feature`, else the unique `in_progress` feature). A non-empty slug pins feature mode to that `{slug}`; this disambiguates when several `prd-{slug}.md` files coexist. If it returns `ambiguous: true` (`--json`), ask which feature before loading. Without the CLI, read `active_feature` from `.aioson/context/project-pulse.md`. Then check:
79
79
 
80
80
  **Feature mode** — a `prd-{slug}.md` file exists in `.aioson/context/`:
81
81
  - Read `prd-{slug}.md` to understand the feature scope.
@@ -144,7 +144,7 @@ Run after Sheldon enrichment context check. Check the frontmatter of the PRD bei
144
144
 
145
145
  ## Context integrity
146
146
 
147
- Read `.aioson/context/project.context.md` before starting discovery.
147
+ Read `.aioson/context/project.context.md` before starting discovery.
148
148
 
149
149
  Rules:
150
150
  - If the file is inconsistent with the scope artifacts already present (`prd.md`, `prd-{slug}.md`, `discovery.md`, `spec.md`, `features.md`), fix the objectively inferable metadata inside the workflow before proceeding.
@@ -154,7 +154,7 @@ Rules:
154
154
 
155
155
  ## Brownfield pre-flight
156
156
 
157
- Check `framework_installed` in `.aioson/context/project.context.md` before starting any phase.
157
+ Check `framework_installed` in `.aioson/context/project.context.md` before starting any phase.
158
158
 
159
159
  **If `framework_installed=true` AND `.aioson/context/discovery.md` exists:**
160
160
  - Skip Phases 1–3 below.
@@ -292,6 +292,9 @@ For each new or modified entity, produce field-level detail (same format as Phas
292
292
  ### Output contract — feature mode
293
293
 
294
294
  **`requirements-{slug}.md`** — implementation spec for the feature:
295
+
296
+ > When `prd-{slug}.md` has a `## Prototype reference`, load `.aioson/docs/prototype-contract.md` and turn the prototype's Core screens and interactions into explicit acceptance criteria (e.g. "add card persists and re-renders", "board has a management surface"). This is how the prototype reaches @validator — as binary criteria, not a file it reads. After writing `requirements-{slug}.md`, run `aioson prototype:check . --feature={slug}` and resolve any `fail`/`warn` before handoff — it deterministically verifies every Core interaction in the prototype manifest is echoed by an acceptance criterion.
297
+
295
298
  1. Feature summary (1–2 lines from prd-{slug}.md)
296
299
  2. Requirement IDs (`REQ-{slug}-01...`) with source references
297
300
  3. Acceptance criteria IDs (`AC-{slug}-01...`) mapped to requirement IDs
@@ -379,7 +382,7 @@ Generate `.aioson/context/discovery.md` with the following sections:
379
382
 
380
383
  ## Dev handoff producer
381
384
 
382
- Before the final `agent:epilogue`/`agent:done` call, when the next agent in the workflow is `@dev`, produce `.aioson/context/dev-state.md` so the next `/aioson:agent:dev` session auto-resumes on cold start instead of pinging the user for context:
385
+ Before the final `agent:epilogue`/`agent:done` call, when the next agent in the workflow is `@dev`, produce `.aioson/context/dev-state.md` so the next `/aioson:agent:dev` session auto-resumes on cold start instead of pinging the user for context:
383
386
 
384
387
  ```bash
385
388
  aioson dev:state:write . --feature={slug} --phase=1 \
@@ -389,7 +392,7 @@ aioson dev:state:write . --feature={slug} --phase=1 \
389
392
 
390
393
  `--context` accepts canonical tokens (`prd`, `requirements`, `spec`, `architecture`, `impl-plan`, `sheldon`, `design-doc`, `readiness`, `ui-spec`, `dossier`, `simple-plan`), max 4 entries total; missing files emit a warning and are skipped. Always include the artifacts @dev will need to start the first slice — typically `spec` + `requirements` for SMALL features. Idempotent: re-running with the same args does not duplicate state.
391
394
 
392
- If any workflow stage remains before `@dev` (`@scope-check`, `@architect`, `@discovery-design-doc`, or `@pm`), do not guess the final implementation package here. The last pre-dev stage writes the final `.aioson/context/dev-state.md`; `@analyst` only produces it for direct-to-dev shortcuts.
395
+ If any workflow stage remains before `@dev` (`@scope-check`, `@architect`, `@discovery-design-doc`, or `@pm`), do not guess the final implementation package here. The last pre-dev stage writes the final `.aioson/context/dev-state.md`; `@analyst` only produces it for direct-to-dev shortcuts.
393
396
 
394
397
  **Handoff message:**
395
398
  ```
@@ -4,8 +4,8 @@
4
4
 
5
5
  ## Context loading modes
6
6
 
7
- Before concrete `context:select`, run discovery: `aioson context:search . --query="<task>" --agent=architect --mode=<mode> --task="<task>" --paths="<paths>" --json 2>/dev/null || true`. Hits are hints only.
8
-
7
+ Before concrete `context:select`, run discovery: `aioson context:search . --query="<task>" --agent=architect --mode=<mode> --task="<task>" --paths="<paths>" --json 2>/dev/null || true`. Hits are hints only.
8
+
9
9
  Use two explicit modes. Architecture needs enough evidence to decide structure, but not every rule, doc, or memory file.
10
10
 
11
11
  - **PLANNING** — inspect workflow status, project context, Gate A status, artifact frontmatter, dossier/code-map, and `context:select` output. Do not load full `.aioson/rules/`, `.aioson/docs/`, `.aioson/design-docs/`, or bootstrap folders.
@@ -45,7 +45,7 @@ Full templates: `.aioson/docs/dossier/agent-templates.md`
45
45
 
46
46
  ## Activation guard
47
47
 
48
- If activated without a feature slug or concrete task: read only `.aioson/context/project.context.md` + `.aioson/context/project-pulse.md` (or run `aioson context:select . --agent=architect --mode=planning --task="agent activation without concrete task"`), report the current stage, ask what to design, and stop. Do not load discovery, specs, or governance before that answer.
48
+ If activated without a feature slug or concrete task: read only `.aioson/context/project.context.md` + `.aioson/context/project-pulse.md` (or run `aioson context:select . --agent=architect --mode=planning --task="agent activation without concrete task"`), report the current stage, ask what to design, and stop. Do not load discovery, specs, or governance before that answer.
49
49
 
50
50
  ## Required input
51
51
 
@@ -131,10 +131,39 @@ aioson gate:approve . --feature={slug} --gate=B 2>/dev/null || true
131
131
  ```
132
132
  Architecture defined: .aioson/context/architecture.md
133
133
  Gate B: {approved|blocked}
134
- Next agent: from the workflow state machine (usually @discovery-design-doc, then @pm on MEDIUM features, then @scope-check before @dev)
134
+ Next agent: from the workflow state machine (usually @discovery-design-doc, then @pm on MEDIUM features, then @scope-check before @dev). In **merged mode** (sequence omits @discovery-design-doc) produce design-doc + readiness + dev-state here and hand off to @dev directly.
135
135
  Action: aioson workflow:next . --complete=architect --tool=<tool>
136
136
  ```
137
- > Recommended: `/compact` before activating the next same-feature agent. Use `/clear` only for a hard reset, feature switch, polluted context, or security-sensitive reset.
137
+ > Recommended: `/compact` before activating the next same-feature agent. Use `/clear` only for a hard reset, feature switch, polluted context, or security-sensitive reset.
138
+
139
+ ## Architect merged mode (absorbs @discovery-design-doc)
140
+
141
+ Activate this mode whenever the active sequence omits `@discovery-design-doc` — which is now the **default for
142
+ MEDIUM** (ddd was demoted to an opt-in detour), as well as the lean lane and the "full-merged" preset. In that
143
+ case `@discovery-design-doc` does not run as its own stage, so produce design-doc + readiness + dev-state here so
144
+ `@dev`'s SMALL/MEDIUM preflight is satisfied. Leave this OFF only when the active sequence explicitly includes
145
+ `@discovery-design-doc` (an opt-in detour / older config) — then ddd owns these artifacts and producing them here
146
+ would collide.
147
+
148
+ In merged mode you additionally produce what `@discovery-design-doc` would have, so `@dev`'s SMALL/MEDIUM
149
+ preflight (which requires the design-doc + readiness pair) is satisfied:
150
+
151
+ 1. **Design-doc** — `.aioson/context/design-doc-{slug}.md` (project mode: `design-doc.md`): scope/approach
152
+ decisions, exact implementation paths (create/modify/reuse/retire), and componentization/split notes.
153
+ 2. **Readiness** — `.aioson/context/readiness-{slug}.md` (project mode: `readiness.md`): the readiness verdict
154
+ (`ready`/`ready_with_warnings`/`blocked`), exact downstream agent, artifacts consumed, blockers, assumptions.
155
+ **Keep this gate** — it is the cheap, valuable checkpoint; do not drop it just because the agent merged.
156
+ 3. **Dev-state handoff** — write the cold-start packet so a fresh `@dev` starts without chat history:
157
+ `aioson dev:state:write . --feature={slug} --phase=1 --next="<first slice>" --context=spec,design-doc,readiness`.
158
+
159
+ Then hand off to `@dev` (not `@discovery-design-doc`). Keep the artifacts proportional to classification — the
160
+ merge removes a hop, it does not license heavier documents.
161
+
162
+ > Note: at `@architect` completion the tracked workflow only gates `architecture.md` + Gate B structurally — the
163
+ > design-doc + readiness you produce in merged mode are enforced by `@dev`'s SMALL/MEDIUM preflight (it halts at
164
+ > activation if either is absent), not by a separate handoff contract. Produce both, or `@dev` stops. (The
165
+ > full-merged preset keeps `@analyst`, so `spec-{slug}.md` and Gate C are satisfied normally — unlike the lean
166
+ > lane, where `@sheldon` must write the spec itself.)
138
167
 
139
168
  ## Autopilot handoff
140
169
 
@@ -73,6 +73,20 @@ If the user declines application, leave `briefings.md` unchanged and record skip
73
73
 
74
74
  If feedback contains unresolved blocking items, do not hand off as ready for `@product`. The next action is to resolve blockers or rerun refinement.
75
75
 
76
+ ### Generate prototype (optional visual refinement)
77
+
78
+ Use this when the user asks to see the solution visually, or when a rich-surface briefing (workspaces, boards, cards, pipelines, CRM/Kanban, dashboards, admin/management surfaces, repeated-use CRUD) would benefit from validating screens and interactions before the PRD. It is optional and user-invoked — never block the briefing path on it.
79
+
80
+ 1. Resolve the briefing slug and read `briefings.md` plus the operational surface map (`solution-options.md` / `expansion-scout.md`, falling back to the surface map in `.aioson/docs/feature-expansion-taxonomy.md`).
81
+ 2. Resolve the visual route from `design_skill` in `.aioson/context/project.context.md`:
82
+ - **interface-design + reference images (recommended for a specific, premium look):** ask the user to drop reference images into `.aioson/briefings/{slug}/references/identity/` (brand: color, type, feel) and — for a system with specific components — into `.aioson/briefings/{slug}/references/structure/` (a board, a table, a screen). Then load `.aioson/skills/process/reference-identity-extract/SKILL.md`, extract them once into `.aioson/briefings/{slug}/identity.md`, and self-gate: `aioson verify:artifact . --kind=identity --file=.aioson/briefings/{slug}/identity.md --advisory 2>/dev/null || true`. The prototype's look comes from `identity.md`; its `## Component structure notes` feed the surface map. If the user has no images, skip extraction and let `interface-design` run intent-first — never block on images.
83
+ - **a fixed preset:** if `design_skill` names an installed preset, use it. If `design_skill` is blank for a `site`/`web_app` and the user does not want the reference-image route, ask which installed design skill to use — do not auto-pick one.
84
+ 3. Load `.aioson/skills/process/prototype-forge/SKILL.md` and follow its build contract.
85
+ 4. Write `.aioson/briefings/{slug}/prototype.html` and `.aioson/briefings/{slug}/prototype-manifest.md`.
86
+ 5. Tell the user the prototype is **mock-only** (refresh resets, no backend) and that it is a `draft` until @product/@sheldon freeze scope, at which point it is re-synced and locked as the development reference.
87
+
88
+ The prototype never edits `briefings.md` and never becomes canonical feedback; structured JSON from the review flow remains the only source of applied changes.
89
+
76
90
  ## Review HTML contract
77
91
 
78
92
  `review.html` must be static, local, and self-contained:
@@ -94,6 +108,7 @@ If feedback contains unresolved blocking items, do not hand off as ready for `@p
94
108
  - Never approve a briefing automatically.
95
109
  - Never route directly to `@product` while blocking items remain unresolved.
96
110
  - Never treat edited HTML or DOM state as canonical feedback.
111
+ - Never treat `prototype.html` as the briefing source of truth or as applied feedback; it is a visual reference only.
97
112
  - Never write refinement JSON into `.aioson/context/`.
98
113
  - Never refine a briefing with `prd_generated` set unless the user explicitly chooses a new PRD/enrichment route outside this agent.
99
114
  - Never drop mandatory briefing sections.
@@ -110,6 +125,14 @@ Review generation writes:
110
125
  .aioson/briefings/{slug}/expansion-scout.md # only when expansion scout is triggered
111
126
  ```
112
127
 
128
+ Prototype generation (optional) writes:
129
+
130
+ ```text
131
+ .aioson/briefings/{slug}/identity.md # only when reference images were extracted
132
+ .aioson/briefings/{slug}/prototype.html
133
+ .aioson/briefings/{slug}/prototype-manifest.md
134
+ ```
135
+
113
136
  Confirmed application updates:
114
137
 
115
138
  ```text
@@ -123,6 +146,8 @@ Confirmed application updates:
123
146
  - If review was generated: user opens `review.html`, saves/exports feedback, then reactivates `@briefing-refiner`.
124
147
  - If changes were applied and no blockers remain: user runs `aioson briefing:approve . --slug={slug}`, then activates `@product`.
125
148
  - If blockers remain: user resolves them in the review and reactivates `@briefing-refiner`.
149
+ - If a prototype was generated: user opens `prototype.html` to validate screens/interactions, requests visual changes if needed, then proceeds to `@product` — the PRD references the prototype, and it is locked as the development reference once scope is frozen.
150
+ - **Rich-surface recommendation (non-blocking):** if the briefing has a rich operational surface (workspaces, boards, cards, pipelines, CRM/Kanban, dashboards, admin/management, repeated-use CRUD) and no prototype exists yet, recommend running `@briefing-refiner` prototype mode before `@product` — it surfaces missing management screens and broken interactions before the PRD. The deterministic trigger is `aioson classify . --feature={slug}` reporting `recommend_prototype: true` (rich operational surface detected, EN or pt-BR); surface that to the user as the reason. Recommend only; never block the route to `@product`.
126
151
 
127
152
  ## Observability
128
153