@wazir-dev/cli 1.0.0 → 1.2.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 (163) hide show
  1. package/CHANGELOG.md +100 -2
  2. package/README.md +6 -6
  3. package/docs/concepts/architecture.md +1 -1
  4. package/docs/concepts/roles-and-workflows.md +2 -0
  5. package/docs/concepts/why-wazir.md +59 -0
  6. package/docs/decisions/2026-03-19-deferred-items.md +564 -0
  7. package/docs/decisions/2026-03-19-enhancement-decisions.md +300 -0
  8. package/docs/plans/2026-03-15-cli-pipeline-integration-plan.md +1 -1
  9. package/docs/readmes/INDEX.md +21 -5
  10. package/docs/readmes/features/expertise/README.md +2 -2
  11. package/docs/readmes/features/exports/README.md +2 -2
  12. package/docs/readmes/features/schemas/README.md +3 -0
  13. package/docs/readmes/features/skills/README.md +17 -0
  14. package/docs/readmes/features/skills/clarifier.md +5 -0
  15. package/docs/readmes/features/skills/claude-cli.md +5 -0
  16. package/docs/readmes/features/skills/codex-cli.md +5 -0
  17. package/docs/readmes/features/skills/dispatching-parallel-agents.md +5 -0
  18. package/docs/readmes/features/skills/executing-plans.md +5 -0
  19. package/docs/readmes/features/skills/executor.md +5 -0
  20. package/docs/readmes/features/skills/finishing-a-development-branch.md +5 -0
  21. package/docs/readmes/features/skills/gemini-cli.md +5 -0
  22. package/docs/readmes/features/skills/humanize.md +5 -0
  23. package/docs/readmes/features/skills/init-pipeline.md +5 -0
  24. package/docs/readmes/features/skills/receiving-code-review.md +5 -0
  25. package/docs/readmes/features/skills/requesting-code-review.md +5 -0
  26. package/docs/readmes/features/skills/reviewer.md +5 -0
  27. package/docs/readmes/features/skills/subagent-driven-development.md +5 -0
  28. package/docs/readmes/features/skills/using-git-worktrees.md +5 -0
  29. package/docs/readmes/features/skills/wazir.md +5 -0
  30. package/docs/readmes/features/skills/writing-skills.md +5 -0
  31. package/docs/readmes/features/workflows/prepare-next.md +1 -1
  32. package/docs/reference/configuration-reference.md +47 -6
  33. package/docs/reference/launch-checklist.md +4 -4
  34. package/docs/reference/review-loop-pattern.md +538 -0
  35. package/docs/reference/roles-reference.md +1 -0
  36. package/docs/reference/skill-tiers.md +147 -0
  37. package/docs/reference/tooling-cli.md +5 -1
  38. package/docs/truth-claims.yaml +18 -0
  39. package/expertise/antipatterns/process/ai-coding-antipatterns.md +97 -1
  40. package/exports/hosts/claude/.claude/agents/clarifier.md +3 -0
  41. package/exports/hosts/claude/.claude/agents/designer.md +3 -0
  42. package/exports/hosts/claude/.claude/agents/executor.md +2 -0
  43. package/exports/hosts/claude/.claude/agents/planner.md +3 -0
  44. package/exports/hosts/claude/.claude/agents/researcher.md +2 -0
  45. package/exports/hosts/claude/.claude/agents/reviewer.md +5 -1
  46. package/exports/hosts/claude/.claude/agents/specifier.md +3 -0
  47. package/exports/hosts/claude/.claude/commands/clarify.md +4 -0
  48. package/exports/hosts/claude/.claude/commands/design-review.md +4 -0
  49. package/exports/hosts/claude/.claude/commands/design.md +4 -0
  50. package/exports/hosts/claude/.claude/commands/discover.md +4 -0
  51. package/exports/hosts/claude/.claude/commands/execute.md +4 -0
  52. package/exports/hosts/claude/.claude/commands/plan-review.md +4 -0
  53. package/exports/hosts/claude/.claude/commands/plan.md +4 -0
  54. package/exports/hosts/claude/.claude/commands/spec-challenge.md +4 -0
  55. package/exports/hosts/claude/.claude/commands/specify.md +4 -0
  56. package/exports/hosts/claude/.claude/commands/verify.md +4 -0
  57. package/exports/hosts/claude/.claude/settings.json +9 -0
  58. package/exports/hosts/claude/CLAUDE.md +1 -1
  59. package/exports/hosts/claude/export.manifest.json +22 -20
  60. package/exports/hosts/claude/host-package.json +3 -1
  61. package/exports/hosts/codex/AGENTS.md +1 -1
  62. package/exports/hosts/codex/export.manifest.json +22 -20
  63. package/exports/hosts/codex/host-package.json +3 -1
  64. package/exports/hosts/cursor/.cursor/hooks.json +4 -0
  65. package/exports/hosts/cursor/.cursor/rules/wazir-core.mdc +1 -1
  66. package/exports/hosts/cursor/export.manifest.json +22 -20
  67. package/exports/hosts/cursor/host-package.json +3 -1
  68. package/exports/hosts/gemini/GEMINI.md +1 -1
  69. package/exports/hosts/gemini/export.manifest.json +22 -20
  70. package/exports/hosts/gemini/host-package.json +3 -1
  71. package/hooks/context-mode-router +191 -0
  72. package/hooks/definitions/context_mode_router.yaml +19 -0
  73. package/hooks/definitions/loop_cap_guard.yaml +1 -1
  74. package/hooks/hooks.json +43 -0
  75. package/hooks/protected-path-write-guard +8 -0
  76. package/hooks/routing-matrix.json +45 -0
  77. package/hooks/session-start +62 -1
  78. package/llms-full.txt +905 -132
  79. package/package.json +3 -3
  80. package/roles/clarifier.md +3 -0
  81. package/roles/designer.md +3 -0
  82. package/roles/executor.md +2 -0
  83. package/roles/planner.md +3 -0
  84. package/roles/researcher.md +2 -0
  85. package/roles/reviewer.md +5 -1
  86. package/roles/specifier.md +3 -0
  87. package/schemas/hook.schema.json +2 -1
  88. package/schemas/phase-report.schema.json +80 -0
  89. package/schemas/usage.schema.json +25 -1
  90. package/schemas/wazir-manifest.schema.json +19 -0
  91. package/skills/brainstorming/SKILL.md +20 -56
  92. package/skills/clarifier/SKILL.md +243 -0
  93. package/skills/claude-cli/SKILL.md +320 -0
  94. package/skills/codex-cli/SKILL.md +260 -0
  95. package/skills/debugging/SKILL.md +24 -1
  96. package/skills/design/SKILL.md +13 -0
  97. package/skills/dispatching-parallel-agents/SKILL.md +13 -0
  98. package/skills/executing-plans/SKILL.md +28 -2
  99. package/skills/executor/SKILL.md +129 -0
  100. package/skills/finishing-a-development-branch/SKILL.md +13 -0
  101. package/skills/gemini-cli/SKILL.md +260 -0
  102. package/skills/humanize/SKILL.md +13 -0
  103. package/skills/init-pipeline/SKILL.md +76 -78
  104. package/skills/prepare-next/SKILL.md +81 -10
  105. package/skills/receiving-code-review/SKILL.md +21 -0
  106. package/skills/requesting-code-review/SKILL.md +38 -5
  107. package/skills/reviewer/SKILL.md +423 -0
  108. package/skills/run-audit/SKILL.md +13 -0
  109. package/skills/scan-project/SKILL.md +13 -0
  110. package/skills/self-audit/SKILL.md +197 -16
  111. package/skills/subagent-driven-development/SKILL.md +38 -2
  112. package/skills/subagent-driven-development/code-quality-reviewer-prompt.md +2 -0
  113. package/skills/subagent-driven-development/implementer-prompt.md +8 -0
  114. package/skills/subagent-driven-development/spec-reviewer-prompt.md +7 -0
  115. package/skills/tdd/SKILL.md +21 -0
  116. package/skills/using-git-worktrees/SKILL.md +13 -0
  117. package/skills/using-skills/SKILL.md +13 -0
  118. package/skills/verification/SKILL.md +13 -0
  119. package/skills/wazir/SKILL.md +286 -262
  120. package/skills/writing-plans/SKILL.md +44 -4
  121. package/skills/writing-skills/SKILL.md +13 -0
  122. package/templates/artifacts/implementation-plan.md +3 -0
  123. package/templates/artifacts/tasks-template.md +133 -0
  124. package/templates/examples/phase-report.example.json +48 -0
  125. package/templates/examples/wazir-manifest.example.yaml +1 -1
  126. package/tooling/src/adapters/composition-engine.js +256 -0
  127. package/tooling/src/adapters/model-router.js +84 -0
  128. package/tooling/src/capture/command.js +111 -2
  129. package/tooling/src/capture/run-config.js +23 -0
  130. package/tooling/src/capture/store.js +24 -0
  131. package/tooling/src/capture/usage.js +106 -0
  132. package/tooling/src/checks/ac-matrix.js +256 -0
  133. package/tooling/src/checks/brand-truth.js +3 -6
  134. package/tooling/src/checks/command-registry.js +13 -0
  135. package/tooling/src/checks/docs-truth.js +1 -1
  136. package/tooling/src/checks/runtime-surface.js +3 -7
  137. package/tooling/src/checks/skills.js +111 -0
  138. package/tooling/src/cli.js +17 -3
  139. package/tooling/src/commands/stats.js +161 -0
  140. package/tooling/src/commands/validate.js +5 -1
  141. package/tooling/src/export/compiler.js +33 -37
  142. package/tooling/src/gating/agent.js +145 -0
  143. package/tooling/src/guards/phase-prerequisite-guard.js +127 -0
  144. package/tooling/src/hooks/routing-logic.js +69 -0
  145. package/tooling/src/init/auto-detect.js +260 -0
  146. package/tooling/src/init/command.js +161 -0
  147. package/tooling/src/input/scanner.js +46 -0
  148. package/tooling/src/reports/command.js +103 -0
  149. package/tooling/src/reports/phase-report.js +323 -0
  150. package/tooling/src/state/command.js +160 -0
  151. package/tooling/src/state/db.js +287 -0
  152. package/tooling/src/status/command.js +53 -1
  153. package/wazir.manifest.yaml +26 -17
  154. package/workflows/clarify.md +4 -0
  155. package/workflows/design-review.md +4 -0
  156. package/workflows/design.md +4 -0
  157. package/workflows/discover.md +4 -0
  158. package/workflows/execute.md +4 -0
  159. package/workflows/plan-review.md +4 -0
  160. package/workflows/plan.md +4 -0
  161. package/workflows/spec-challenge.md +4 -0
  162. package/workflows/specify.md +4 -0
  163. package/workflows/verify.md +4 -0
@@ -16,7 +16,10 @@ import {
16
16
  writeStatus,
17
17
  writeSummary,
18
18
  } from './store.js';
19
+ import { readRunConfig, getPhaseLoopCap } from './run-config.js';
19
20
  import { readUsage, generateReport, initUsage, recordCaptureSavings, recordPhaseUsage } from './usage.js';
21
+ import { evaluateLoopCapGuard } from '../guards/loop-cap-guard.js';
22
+ import { evaluatePhasePrerequisiteGuard } from '../guards/phase-prerequisite-guard.js';
20
23
 
21
24
  function formatResult(payload, options = {}) {
22
25
  if (options.json) {
@@ -68,6 +71,7 @@ function resolveCaptureContext(parsed, context = {}) {
68
71
  'capture-path',
69
72
  'command',
70
73
  'exit-code',
74
+ 'task-id',
71
75
  ],
72
76
  });
73
77
  const stateRoot = resolveStateRoot(projectRoot, manifest, {
@@ -157,12 +161,34 @@ function handleInit(parsed, context = {}) {
157
161
  }
158
162
 
159
163
  function handleEvent(parsed, context = {}) {
160
- const { stateRoot, options } = resolveCaptureContext(parsed, context);
164
+ const { projectRoot, stateRoot, options } = resolveCaptureContext(parsed, context);
161
165
 
162
166
  requireOption(options, 'run', 'Usage: wazir capture event --run <id> --event <name> [--phase <phase>] [--status <status>] [--loop-count <n>] [--message <text>] [--state-root <path>] [--json]');
163
167
  requireOption(options, 'event', 'Usage: wazir capture event --run <id> --event <name> [--phase <phase>] [--status <status>] [--loop-count <n>] [--message <text>] [--state-root <path>] [--json]');
164
168
 
165
169
  const runPaths = getRunPaths(stateRoot, options.run);
170
+
171
+ // Phase prerequisite gate — block phase_enter if prerequisites not met (exit 44)
172
+ if (options.event === 'phase_enter') {
173
+ if (!fs.existsSync(runPaths.statusPath)) {
174
+ // Standalone mode — allow without guard check (matches handleLoopCheck pattern)
175
+ } else {
176
+ const guardResult = evaluatePhasePrerequisiteGuard({
177
+ run_id: options.run,
178
+ phase: options.phase,
179
+ state_root: stateRoot,
180
+ project_root: projectRoot,
181
+ });
182
+ if (!guardResult.allowed) {
183
+ return {
184
+ exitCode: 44,
185
+ stderr: `Phase prerequisite gate failed (exit 44): ${guardResult.reason}\n`,
186
+ stdout: options.json ? `${JSON.stringify(guardResult, null, 2)}\n` : '',
187
+ };
188
+ }
189
+ }
190
+ }
191
+
166
192
  const status = readStatus(runPaths);
167
193
  const event = createBaseEvent(options.event, {
168
194
  run_id: options.run,
@@ -346,6 +372,87 @@ function handleUsage(parsed, context = {}) {
346
372
  };
347
373
  }
348
374
 
375
+ function handleLoopCheck(parsed, context = {}) {
376
+ const { stateRoot, options } = resolveCaptureContext(parsed, context);
377
+
378
+ requireOption(options, 'run', 'Usage: wazir capture loop-check --run <id> --phase <phase> --loop-count <n> [--task-id <id>] [--state-root <path>] [--json]');
379
+ requireOption(options, 'phase', 'Usage: wazir capture loop-check --run <id> --phase <phase> --loop-count <n> [--task-id <id>] [--state-root <path>] [--json]');
380
+ requireOption(options, 'loopCount', 'Usage: wazir capture loop-check --run <id> --phase <phase> --loop-count <n> [--task-id <id>] [--state-root <path>] [--json]');
381
+
382
+ const runPaths = getRunPaths(stateRoot, options.run);
383
+
384
+ // Standalone mode: if status.json doesn't exist, allow (exit 0)
385
+ if (!fs.existsSync(runPaths.statusPath)) {
386
+ const notice = 'loop-check: standalone mode (no status.json), allowing.\n';
387
+ return {
388
+ exitCode: 0,
389
+ stdout: options.json ? `${JSON.stringify({ allowed: true, reason: 'standalone mode' }, null, 2)}\n` : '',
390
+ stderr: options.json ? '' : notice,
391
+ };
392
+ }
393
+
394
+ // Record the event and update loop count in status.json
395
+ const status = readStatus(runPaths);
396
+ const loopCount = parsePositiveInteger(options.loopCount, '--loop-count');
397
+ const loopPhase = options.phase;
398
+ const loopKey = options.taskId ? `${loopPhase}:${options.taskId}` : loopPhase;
399
+
400
+ status.phase_loop_counts = {
401
+ ...(status.phase_loop_counts ?? {}),
402
+ [loopKey]: loopCount,
403
+ };
404
+
405
+ const event = createBaseEvent('loop_iteration', {
406
+ run_id: options.run,
407
+ phase: loopPhase,
408
+ status: status.status,
409
+ loop_count: loopCount,
410
+ loop_key: loopKey,
411
+ });
412
+
413
+ if (options.taskId) {
414
+ event.task_id = options.taskId;
415
+ }
416
+
417
+ status.updated_at = event.created_at;
418
+ status.last_event = 'loop_iteration';
419
+
420
+ appendEvent(runPaths, event);
421
+ writeStatus(runPaths, status);
422
+
423
+ // Read run-config for loop_cap
424
+ const runConfig = readRunConfig(runPaths);
425
+ const loopCap = getPhaseLoopCap(runConfig, loopPhase);
426
+
427
+ // Evaluate the guard using loopKey (task-scoped or phase-scoped).
428
+ // Cap is per-phase but counts are per-task — each task gets its own
429
+ // budget up to the phase cap. This is intentional: task-scoped tracking
430
+ // prevents parallel tasks from sharing a single counter.
431
+ const guardResult = evaluateLoopCapGuard({
432
+ run_id: options.run,
433
+ phase: loopKey,
434
+ state_root: stateRoot,
435
+ loop_cap: loopCap,
436
+ });
437
+
438
+ if (!guardResult.allowed) {
439
+ return {
440
+ exitCode: 43,
441
+ stderr: `${guardResult.reason}\n`,
442
+ stdout: options.json ? `${JSON.stringify(guardResult, null, 2)}\n` : '',
443
+ };
444
+ }
445
+
446
+ return formatResult({
447
+ run_id: options.run,
448
+ phase: loopPhase,
449
+ loop_key: loopKey,
450
+ loop_count: loopCount,
451
+ loop_cap: loopCap,
452
+ allowed: true,
453
+ }, { json: options.json });
454
+ }
455
+
349
456
  export function runCaptureCommand(parsed, context = {}) {
350
457
  try {
351
458
  switch (parsed.subcommand) {
@@ -361,10 +468,12 @@ export function runCaptureCommand(parsed, context = {}) {
361
468
  return handleSummary(parsed, context);
362
469
  case 'usage':
363
470
  return handleUsage(parsed, context);
471
+ case 'loop-check':
472
+ return handleLoopCheck(parsed, context);
364
473
  default:
365
474
  return {
366
475
  exitCode: 1,
367
- stderr: 'Usage: wazir capture <init|event|route|output|summary|usage> ...\n',
476
+ stderr: 'Usage: wazir capture <init|event|route|output|summary|usage|loop-check> ...\n',
368
477
  };
369
478
  }
370
479
  } catch (error) {
@@ -0,0 +1,23 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { readYamlFile } from '../loaders.js';
4
+
5
+ const DEFAULT_PHASE_POLICY = {
6
+ loop_cap: 10,
7
+ enabled: true,
8
+ };
9
+
10
+ export function readRunConfig(runPaths) {
11
+ const configPath = path.join(runPaths.runRoot, 'run-config.yaml');
12
+ if (!fs.existsSync(configPath)) {
13
+ return { phase_policy: {} };
14
+ }
15
+ return readYamlFile(configPath);
16
+ }
17
+
18
+ export function getPhaseLoopCap(runConfig, phase) {
19
+ // Support both workflow_policy (new) and phase_policy (legacy)
20
+ const policyMap = runConfig?.workflow_policy ?? runConfig?.phase_policy ?? {};
21
+ const policy = policyMap[phase] ?? DEFAULT_PHASE_POLICY;
22
+ return policy.loop_cap ?? DEFAULT_PHASE_POLICY.loop_cap;
23
+ }
@@ -92,6 +92,30 @@ export function writeCaptureOutput(targetPath, content) {
92
92
  fs.writeFileSync(targetPath, content);
93
93
  }
94
94
 
95
+ export function readPhaseExitEvents(runPaths) {
96
+ if (!fs.existsSync(runPaths.eventsPath)) {
97
+ return [];
98
+ }
99
+
100
+ const content = fs.readFileSync(runPaths.eventsPath, 'utf8');
101
+ const completedPhases = [];
102
+
103
+ for (const line of content.split('\n')) {
104
+ const trimmed = line.trim();
105
+ if (!trimmed) continue;
106
+ try {
107
+ const event = JSON.parse(trimmed);
108
+ if (event.event === 'phase_exit' && event.status === 'completed' && event.phase) {
109
+ completedPhases.push(event.phase);
110
+ }
111
+ } catch {
112
+ // Skip malformed lines
113
+ }
114
+ }
115
+
116
+ return completedPhases;
117
+ }
118
+
95
119
  export function writeSummary(runPaths, content) {
96
120
  ensureRunDirectories(runPaths);
97
121
  fs.writeFileSync(runPaths.summaryPath, content);
@@ -1,4 +1,5 @@
1
1
  import fs from 'node:fs';
2
+ import path from 'node:path';
2
3
 
3
4
  export function estimateTokens(bytes) {
4
5
  if (bytes < 0) {
@@ -37,6 +38,19 @@ function createDefaultUsage(runId) {
37
38
  pre_compaction_tokens_est: 0,
38
39
  post_compaction_tokens_est: 0,
39
40
  },
41
+ index_queries: {
42
+ count: 0,
43
+ total_raw_bytes: 0,
44
+ total_summary_bytes: 0,
45
+ estimated_tokens_saved: 0,
46
+ bytes_avoided: 0,
47
+ },
48
+ },
49
+ routing: {
50
+ total_commands: 0,
51
+ context_mode_routed: 0,
52
+ passthrough: 0,
53
+ by_category: {},
40
54
  },
41
55
  totals: {
42
56
  total_events: 0,
@@ -184,6 +198,98 @@ export function recordCompaction(runPaths, preTokens, postTokens) {
184
198
  writeUsageAtomic(runPaths, usage);
185
199
  }
186
200
 
201
+ /**
202
+ * Record a single index query's savings.
203
+ * Called by the pipeline's capture hooks during phase execution
204
+ * (e.g. via `wazir capture index-query`), not by the CLI directly.
205
+ */
206
+ export function recordIndexQuery(runPaths, { query, file_count_in_results, median_file_size, summary_bytes }) {
207
+ const usage = readUsage(runPaths);
208
+ const rawBytes = file_count_in_results * median_file_size;
209
+ const iq = usage.savings.index_queries;
210
+
211
+ iq.count += 1;
212
+ iq.total_raw_bytes += rawBytes;
213
+ iq.total_summary_bytes += summary_bytes;
214
+ iq.bytes_avoided = iq.total_raw_bytes - iq.total_summary_bytes;
215
+ iq.estimated_tokens_saved = estimateTokens(iq.bytes_avoided);
216
+
217
+ writeUsageAtomic(runPaths, usage);
218
+ }
219
+
220
+ export function consumeRoutingLog(runPaths) {
221
+ // Derive stateRoot from runPaths.runRoot (stateRoot/runs/runId -> stateRoot)
222
+ const stateRoot = path.resolve(runPaths.runRoot, '..', '..');
223
+ const logPath = path.join(stateRoot, 'logs', 'routing.ndjson');
224
+
225
+ if (!fs.existsSync(logPath)) {
226
+ return;
227
+ }
228
+
229
+ const raw = fs.readFileSync(logPath, 'utf8').trim();
230
+ if (!raw) {
231
+ return;
232
+ }
233
+
234
+ const usage = readUsage(runPaths);
235
+
236
+ // Determine run start time for scoping log entries to this run
237
+ let runStartTime = null;
238
+ try {
239
+ const configPath = path.join(runPaths.runRoot, 'run-config.yaml');
240
+ if (fs.existsSync(configPath)) {
241
+ const configRaw = fs.readFileSync(configPath, 'utf8');
242
+ const match = configRaw.match(/created_at:\s*["']?([^"'\n]+)/);
243
+ if (match) runStartTime = new Date(match[1].trim()).toISOString();
244
+ }
245
+ } catch { /* fall through — include all entries if no config */ }
246
+
247
+ // Ensure routing section exists (for older usage.json files)
248
+ if (!usage.routing) {
249
+ usage.routing = {
250
+ total_commands: 0,
251
+ context_mode_routed: 0,
252
+ passthrough: 0,
253
+ by_category: {},
254
+ };
255
+ }
256
+
257
+ // Reset counts before re-aggregating (scoped to this run)
258
+ usage.routing.total_commands = 0;
259
+ usage.routing.context_mode_routed = 0;
260
+ usage.routing.passthrough = 0;
261
+ usage.routing.by_category = {};
262
+
263
+ const lines = raw.split('\n');
264
+ for (const line of lines) {
265
+ if (!line.trim()) continue;
266
+
267
+ let entry;
268
+ try {
269
+ entry = JSON.parse(line);
270
+ } catch {
271
+ continue; // skip malformed lines
272
+ }
273
+
274
+ // Scope to current run: skip entries before this run started
275
+ if (runStartTime && entry.ts && entry.ts < runStartTime) continue;
276
+
277
+ usage.routing.total_commands += 1;
278
+
279
+ const route = entry.route || 'passthrough';
280
+ if (route === 'context-mode') {
281
+ usage.routing.context_mode_routed += 1;
282
+ } else {
283
+ usage.routing.passthrough += 1;
284
+ }
285
+
286
+ const category = entry.category || 'unknown';
287
+ usage.routing.by_category[category] = (usage.routing.by_category[category] || 0) + 1;
288
+ }
289
+
290
+ writeUsageAtomic(runPaths, usage);
291
+ }
292
+
187
293
  function formatNumber(n) {
188
294
  return n.toLocaleString('en-US');
189
295
  }
@@ -0,0 +1,256 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ const ROOT = path.resolve(import.meta.dirname, '..', '..', '..');
5
+
6
+ function resolve(rel) {
7
+ return path.join(ROOT, rel);
8
+ }
9
+
10
+ function fileExists(rel) {
11
+ return fs.existsSync(resolve(rel));
12
+ }
13
+
14
+ function fileContains(rel, pattern) {
15
+ if (!fileExists(rel)) return false;
16
+ const content = fs.readFileSync(resolve(rel), 'utf8');
17
+ if (typeof pattern === 'string') return content.includes(pattern);
18
+ return pattern.test(content);
19
+ }
20
+
21
+ function sectionContains(rel, sectionHeading, pattern) {
22
+ if (!fileExists(rel)) return false;
23
+ const content = fs.readFileSync(resolve(rel), 'utf8');
24
+ const lines = content.split('\n');
25
+ let inSection = false;
26
+ let sectionContent = '';
27
+ const headingLevel = sectionHeading.match(/^#+/)?.[0]?.length || 2;
28
+ for (const line of lines) {
29
+ // Match exact heading: "## Step 2:" should NOT match "## Step 2.5:" or "## Step 2.6:"
30
+ // Use word boundary check: heading text must be followed by end-of-line, colon, or space
31
+ const trimmed = line.replace(/^#+\s*/, '');
32
+ const probe = sectionHeading.replace(/^#+\s*/, '');
33
+ if (trimmed === probe || trimmed.startsWith(probe + ':') || trimmed.startsWith(probe + ' ')) {
34
+ inSection = true;
35
+ sectionContent = '';
36
+ continue;
37
+ }
38
+ if (inSection) {
39
+ const match = line.match(/^(#{1,6})\s/);
40
+ if (match && match[1].length <= headingLevel) break;
41
+ sectionContent += line + '\n';
42
+ }
43
+ }
44
+ if (typeof pattern === 'string') return sectionContent.includes(pattern);
45
+ return pattern.test(sectionContent);
46
+ }
47
+
48
+ function countMatches(rel, pattern) {
49
+ if (!fileExists(rel)) return 0;
50
+ const content = fs.readFileSync(resolve(rel), 'utf8');
51
+ if (typeof pattern === 'string') {
52
+ return content.split(pattern).length - 1;
53
+ }
54
+ return (content.match(pattern) || []).length;
55
+ }
56
+
57
+ const CHECKS = [
58
+ // Item 8: Spec-kit task template
59
+ { id: 'AC8.1', item: 8, tier: 1, desc: 'tasks-template.md exists', check: () => fileExists('templates/artifacts/tasks-template.md') },
60
+ { id: 'AC8.2', item: 8, tier: 1, desc: 'Contains T001', check: () => fileContains('templates/artifacts/tasks-template.md', 'T001') },
61
+ { id: 'AC8.3', item: 8, tier: 1, desc: 'Contains [P]', check: () => fileContains('templates/artifacts/tasks-template.md', '[P]') },
62
+ { id: 'AC8.4', item: 8, tier: 1, desc: 'Contains Phase 1: Setup', check: () => fileContains('templates/artifacts/tasks-template.md', /Phase\s*1.*Setup|Setup.*Phase\s*1/i) },
63
+ { id: 'AC8.5', item: 8, tier: 1, desc: 'Contains Phase 2: Foundational', check: () => fileContains('templates/artifacts/tasks-template.md', /Phase\s*2.*Foundational|Foundational.*Phase\s*2/i) },
64
+ { id: 'AC8.6', item: 8, tier: 1, desc: 'Contains MVP', check: () => fileContains('templates/artifacts/tasks-template.md', /mvp/i) },
65
+ { id: 'AC8.7', item: 8, tier: 1, desc: 'Contains dependency', check: () => fileContains('templates/artifacts/tasks-template.md', /dependenc|depend/i) },
66
+ { id: 'AC8.8', item: 8, tier: 1, desc: 'implementation-plan.md references tasks-template', check: () => fileContains('templates/artifacts/implementation-plan.md', 'tasks-template') },
67
+ { id: 'AC8.9', item: 8, tier: 1, desc: 'Contains [US', check: () => fileContains('templates/artifacts/tasks-template.md', '[US') },
68
+ { id: 'AC8.10', item: 8, tier: 2, desc: 'Story phase has goal, test criteria, tasks', check: () => fileContains('templates/artifacts/tasks-template.md', /goal/i) && fileContains('templates/artifacts/tasks-template.md', /test.*criteria|criteria.*test/i) },
69
+
70
+ // Item 12: Fix-and-loop
71
+ { id: 'AC12.1', item: 12, tier: 1, desc: 'Describes re-submission after fixes', check: () => fileContains('docs/reference/review-loop-pattern.md', /re-s(end|ubmit)|loop/i) },
72
+ { id: 'AC12.2', item: 12, tier: 1, desc: 'Pass caps per depth', check: () => fileContains('docs/reference/review-loop-pattern.md', /quick.*3|standard.*5|deep.*7/) },
73
+ { id: 'AC12.3', item: 12, tier: 1, desc: 'At cap user chooses', check: () => fileContains('docs/reference/review-loop-pattern.md', /user|approve|escalat/i) },
74
+ { id: 'AC12.4', item: 12, tier: 1, desc: 'No fix-and-continue path', check: () => !fileContains('docs/reference/review-loop-pattern.md', /fix and continue without/i) || fileContains('docs/reference/review-loop-pattern.md', /prohibit|must not|never.*fix and continue/i) },
75
+ { id: 'AC12.5', item: 12, tier: 1, desc: 'Clarifier references review-loop-pattern.md', check: () => fileContains('skills/clarifier/SKILL.md', 'review-loop-pattern') },
76
+ { id: 'AC12.6', item: 12, tier: 1, desc: 'Escalation offers 3 options', check: () => fileContains('docs/reference/review-loop-pattern.md', /approve.*issues|fix.*manually|abort/i) },
77
+
78
+ // Item 13: Reviewer skill invocation
79
+ { id: 'AC13.1', item: 13, tier: 1, desc: 'Clarifier references wz:reviewer', check: () => fileContains('skills/clarifier/SKILL.md', /wz:reviewer|reviewer skill/i) },
80
+ { id: 'AC13.2', item: 13, tier: 2, desc: 'Clarifier no bare codex exec/review', check: () => { /* Manual: check codex exec/review only in code blocks */ return null; } },
81
+ { id: 'AC13.3', item: 13, tier: 1, desc: 'writing-plans references wz:reviewer --mode plan-review', check: () => fileContains('skills/writing-plans/SKILL.md', /wz:reviewer.*plan-review|plan-review.*wz:reviewer/) },
82
+ { id: 'AC13.4', item: 13, tier: 2, desc: 'writing-plans no bare codex exec/review', check: () => { return null; } },
83
+ { id: 'AC13.5', item: 13, tier: 1, desc: 'Clarifier uses all 3 modes', check: () => fileContains('skills/clarifier/SKILL.md', 'clarification-review') && fileContains('skills/clarifier/SKILL.md', 'spec-challenge') && fileContains('skills/clarifier/SKILL.md', 'plan-review') },
84
+ { id: 'AC13.6', item: 13, tier: 1, desc: 'Reviewer documents 4 responsibilities', check: () => fileContains('skills/reviewer/SKILL.md', /Codex.*integration|integration.*Codex/i) && fileContains('skills/reviewer/SKILL.md', /dimension/i) },
85
+
86
+ // Item 1: Input preservation
87
+ { id: 'AC1.1', item: 1, tier: 2, desc: 'wc -l plan >= wc -l input', check: () => null },
88
+ { id: 'AC1.2', item: 1, tier: 2, desc: 'Criteria in corresponding task description', check: () => null },
89
+ { id: 'AC1.3', item: 1, tier: 2, desc: 'Endpoints/hex/dimensions in relevant section', check: () => null },
90
+ { id: 'AC1.4', item: 1, tier: 2, desc: 'Works with empty input/tasks/', check: () => null },
91
+
92
+ // Item 2: Spec-kit plan format
93
+ { id: 'AC2.1', item: 2, tier: 1, desc: 'Clarifier produces execution-plan.md', check: () => fileContains('skills/clarifier/SKILL.md', 'execution-plan.md') },
94
+ { id: 'AC2.2', item: 2, tier: 1, desc: 'Plan has T0XX checkboxes', check: () => fileContains('skills/clarifier/SKILL.md', /T\d{3}|T0XX/) },
95
+ { id: 'AC2.3', item: 2, tier: 1, desc: 'Plan has [P] markers', check: () => fileContains('skills/clarifier/SKILL.md', '[P]') || fileContains('skills/writing-plans/SKILL.md', '[P]') },
96
+ { id: 'AC2.4', item: 2, tier: 1, desc: 'Phase headings Setup/Foundational', check: () => fileContains('skills/clarifier/SKILL.md', /Setup|Foundational/) || fileContains('skills/writing-plans/SKILL.md', /Setup|Foundational/) },
97
+ { id: 'AC2.5', item: 2, tier: 2, desc: 'Every task has file path', check: () => null },
98
+ { id: 'AC2.6', item: 2, tier: 1, desc: 'No tasks/task-NNN/spec.md created', check: () => !fileExists('.wazir/runs/latest/tasks/task-001/spec.md') },
99
+ { id: 'AC2.7', item: 2, tier: 1, desc: 'Contains [US] labels', check: () => fileContains('skills/clarifier/SKILL.md', '[US') || fileContains('skills/writing-plans/SKILL.md', '[US') },
100
+ { id: 'AC2.8', item: 2, tier: 2, desc: 'Story phases have goal, criteria, tasks', check: () => null },
101
+
102
+ // Item 3: Gap analysis
103
+ { id: 'AC3.1', item: 3, tier: 2, desc: 'plan-review-pass-N.md files exist', check: () => null },
104
+ { id: 'AC3.2', item: 3, tier: 1, desc: 'Gap analysis reads input/ AND user-feedback.md', check: () => fileContains('skills/clarifier/SKILL.md', 'input/') && fileContains('skills/clarifier/SKILL.md', 'user-feedback.md') },
105
+ { id: 'AC3.3', item: 3, tier: 1, desc: 'Uses wz:reviewer --mode plan-review', check: () => fileContains('skills/clarifier/SKILL.md', /wz:reviewer.*plan-review/) },
106
+ { id: 'AC3.4', item: 3, tier: 2, desc: 'Terminal state CLEAN or user-approved-with-issues', check: () => null },
107
+ { id: 'AC3.5', item: 3, tier: 2, desc: 'Clarifier doesnt produce review files', check: () => null },
108
+
109
+ // Item 6: Context-mode
110
+ { id: 'AC6.1', item: 6, tier: 1, desc: 'init-pipeline references context_mode', check: () => fileContains('skills/init-pipeline/SKILL.md', /context.mode|context-mode/) },
111
+ { id: 'AC6.2', item: 6, tier: 1, desc: 'Config stores as object', check: () => fileContains('skills/init-pipeline/SKILL.md', /enabled.*true|\{.*enabled/) },
112
+ { id: 'AC6.3', item: 6, tier: 1, desc: 'Clarifier references fetch_and_index', check: () => fileContains('skills/clarifier/SKILL.md', 'fetch_and_index') },
113
+ { id: 'AC6.4', item: 6, tier: 1, desc: 'Clarifier has WebFetch fallback', check: () => fileContains('skills/clarifier/SKILL.md', 'WebFetch') },
114
+ { id: 'AC6.5', item: 6, tier: 1, desc: 'Detection checks tools under correct prefix', check: () => fileContains('skills/init-pipeline/SKILL.md', 'mcp__plugin_context-mode_context-mode__') || fileContains('skills/init-pipeline/SKILL.md', /execute.*fetch_and_index.*search/) },
115
+ { id: 'AC6.6', item: 6, tier: 1, desc: 'command.js references context_mode', check: () => fileContains('tooling/src/init/command.js', 'context_mode') || fileContains('tooling/src/init/command.js', 'context-mode') },
116
+ { id: 'AC6.7', item: 6, tier: 1, desc: 'Clarifier references execute/execute_file for large outputs', check: () => fileContains('skills/clarifier/SKILL.md', /execute_file|execute.*large/i) },
117
+ { id: 'AC6.8', item: 6, tier: 1, desc: 'Bash fallback documented', check: () => fileContains('skills/clarifier/SKILL.md', 'Bash') },
118
+ { id: 'AC6.9', item: 6, tier: 1, desc: 'Wazir records context-mode in run-config', check: () => fileContains('skills/wazir/SKILL.md', /context.mode|context-mode/) },
119
+
120
+ // Item 9: Online research
121
+ { id: 'AC9.1', item: 9, tier: 1, desc: 'Phase 0 keyword extraction', check: () => fileContains('skills/clarifier/SKILL.md', /extract.*keyword|keyword.*extract|extract.*concept|concept.*extract/i) },
122
+ { id: 'AC9.2', item: 9, tier: 1, desc: 'Decision criteria documented', check: () => fileContains('skills/clarifier/SKILL.md', /when to research|decision.*criteria/i) },
123
+ { id: 'AC9.3', item: 9, tier: 1, desc: 'Both fetch_and_index and WebFetch', check: () => fileContains('skills/clarifier/SKILL.md', 'fetch_and_index') && fileContains('skills/clarifier/SKILL.md', 'WebFetch') },
124
+ { id: 'AC9.4', item: 9, tier: 1, desc: 'Error handling for 404, rate limit, no URL', check: () => fileContains('skills/clarifier/SKILL.md', /404|failed.*fetch|error.*handling/i) },
125
+ { id: 'AC9.5', item: 9, tier: 1, desc: 'Content saved to sources/', check: () => fileContains('skills/clarifier/SKILL.md', 'sources/') },
126
+ { id: 'AC9.6', item: 9, tier: 1, desc: 'Manifest tracks status', check: () => fileContains('skills/clarifier/SKILL.md', /manifest|status.*track/i) },
127
+
128
+ // Item 17: Codex output protection
129
+ { id: 'AC17.1', item: 17, tier: 1, desc: 'Pattern doc describes tee + execute_file', check: () => fileContains('docs/reference/review-loop-pattern.md', 'tee') && fileContains('docs/reference/review-loop-pattern.md', 'execute_file') },
130
+ { id: 'AC17.2', item: 17, tier: 1, desc: 'Reviewer shows execute_file after Codex', check: () => fileContains('skills/reviewer/SKILL.md', 'execute_file') },
131
+ { id: 'AC17.3', item: 17, tier: 1, desc: 'Fallback uses tac-based extraction', check: () => fileContains('docs/reference/review-loop-pattern.md', 'tac') || fileContains('skills/reviewer/SKILL.md', 'tac') },
132
+ { id: 'AC17.4', item: 17, tier: 2, desc: 'Raw trace preserved in file only', check: () => null },
133
+ { id: 'AC17.5', item: 17, tier: 1, desc: 'Clarifier doesnt call codex directly', check: () => true /* covered by AC13.2 */ },
134
+
135
+ // Item 4: Resume
136
+ { id: 'AC4.1', item: 4, tier: 1, desc: 'Resume copies clarified/ except user-feedback', check: () => fileContains('skills/wazir/SKILL.md', /clarified.*user-feedback|user-feedback.*exclude/i) },
137
+ { id: 'AC4.2', item: 4, tier: 2, desc: 'Start-fresh creates empty clarified/', check: () => null },
138
+ { id: 'AC4.3', item: 4, tier: 1, desc: 'tasks/ NOT copied', check: () => fileContains('skills/wazir/SKILL.md', /tasks.*not.*cop|do not.*copy.*tasks/i) || !fileContains('skills/wazir/SKILL.md', /copy.*tasks\//) },
139
+ { id: 'AC4.4', item: 4, tier: 1, desc: 'Staleness checks ALL input/ files', check: () => fileContains('skills/wazir/SKILL.md', /stale|input.*newer|mtime/i) },
140
+ { id: 'AC4.5', item: 4, tier: 1, desc: 'User explicitly chooses Resume', check: () => fileContains('skills/wazir/SKILL.md', /Resume.*Start fresh|choose.*resume/i) },
141
+ { id: 'AC4.6', item: 4, tier: 2, desc: 'Staleness warning with file names + interactive', check: () => null },
142
+ { id: 'AC4.7', item: 4, tier: 1, desc: 'Resume resumes from last completed phase', check: () => fileContains('skills/wazir/SKILL.md', /last.*completed.*phase|resume.*phase/i) },
143
+
144
+ // Item 5: CHANGELOG + gitflow
145
+ { id: 'AC5.1', item: 5, tier: 1, desc: 'wazir has validate changelog --require-entries', check: () => fileContains('skills/wazir/SKILL.md', 'validate changelog') && fileContains('skills/wazir/SKILL.md', '--require-entries') },
146
+ { id: 'AC5.2', item: 5, tier: 1, desc: 'wazir has validate commits', check: () => fileContains('skills/wazir/SKILL.md', 'validate commits') },
147
+ { id: 'AC5.3', item: 5, tier: 1, desc: 'Executor references CHANGELOG + [Unreleased]', check: () => fileContains('skills/executor/SKILL.md', 'CHANGELOG') && fileContains('skills/executor/SKILL.md', 'Unreleased') },
148
+ { id: 'AC5.4', item: 5, tier: 1, desc: 'Reviewer flags CHANGELOG as [warning]', check: () => fileContains('skills/reviewer/SKILL.md', 'CHANGELOG') && fileContains('skills/reviewer/SKILL.md', /warning/i) },
149
+ { id: 'AC5.5', item: 5, tier: 1, desc: 'Hard gate stops pipeline', check: () => fileContains('skills/wazir/SKILL.md', /hard gate|must fix before/i) },
150
+ { id: 'AC5.6', item: 5, tier: 1, desc: 'All 6 keepachangelog types', check: () => ['Added', 'Changed', 'Fixed', 'Removed', 'Deprecated', 'Security'].every(t => fileContains('skills/executor/SKILL.md', t)) },
151
+ { id: 'AC5.7', item: 5, tier: 1, desc: 'Reviewer binds to task-review AND final', check: () => fileContains('skills/reviewer/SKILL.md', 'task-review') && fileContains('skills/reviewer/SKILL.md', 'final') },
152
+
153
+ // Item 7: Usage reports
154
+ { id: 'AC7.1', item: 7, tier: 1, desc: 'wazir contains capture usage', check: () => fileContains('skills/wazir/SKILL.md', 'capture usage') },
155
+ { id: 'AC7.2', item: 7, tier: 2, desc: 'Present at EVERY phase_exit block', check: () => null },
156
+ { id: 'AC7.3', item: 7, tier: 1, desc: 'Output path includes run-id and phase', check: () => fileContains('skills/wazir/SKILL.md', /usage.*phase|phase.*usage/i) },
157
+
158
+ // Item 10: Interactive
159
+ { id: 'AC10.1', item: 10, tier: 2, desc: 'Clarifier Checkpoint 0 has pattern', check: () => sectionContains('skills/clarifier/SKILL.md', 'Checkpoint 0', '(Recommended)') },
160
+ { id: 'AC10.2', item: 10, tier: 2, desc: 'Clarifier Checkpoint 1A has pattern', check: () => sectionContains('skills/clarifier/SKILL.md', 'Checkpoint 1A', '(Recommended)') },
161
+ { id: 'AC10.3', item: 10, tier: 2, desc: 'Clarifier Checkpoint 1A+ has pattern', check: () => sectionContains('skills/clarifier/SKILL.md', 'Checkpoint 1A+', '(Recommended)') },
162
+ { id: 'AC10.4', item: 10, tier: 2, desc: 'Clarifier Checkpoint 1B has pattern', check: () => sectionContains('skills/clarifier/SKILL.md', 'Checkpoint 1B', '(Recommended)') },
163
+ { id: 'AC10.5', item: 10, tier: 2, desc: 'Clarifier Checkpoint 1C has pattern', check: () => sectionContains('skills/clarifier/SKILL.md', 'Checkpoint 1C', '(Recommended)') },
164
+ { id: 'AC10.6a', item: 10, tier: 2, desc: 'wazir Step 2 has pattern', check: () => sectionContains('skills/wazir/SKILL.md', 'Step 2', '(Recommended)') },
165
+ { id: 'AC10.6b', item: 10, tier: 2, desc: 'wazir Step 3 has 3 option blocks', check: () => null },
166
+ { id: 'AC10.6c', item: 10, tier: 2, desc: 'wazir Step 4 has pattern', check: () => sectionContains('skills/wazir/SKILL.md', 'Step 4', '(Recommended)') },
167
+ { id: 'AC10.6d', item: 10, tier: 2, desc: 'wazir Step 5 has pattern', check: () => sectionContains('skills/wazir/SKILL.md', 'Step 5', '(Recommended)') },
168
+ { id: 'AC10.7', item: 10, tier: 2, desc: 'ALL executor prompts use numbered options', check: () => null },
169
+ { id: 'AC10.8', item: 10, tier: 2, desc: 'ALL reviewer prompts use numbered options', check: () => null },
170
+ { id: 'AC10.9', item: 10, tier: 1, desc: 'No AskUserQuestion in 4 skills', check: () => !fileContains('skills/clarifier/SKILL.md', 'AskUserQuestion') && !fileContains('skills/wazir/SKILL.md', 'AskUserQuestion') && !fileContains('skills/executor/SKILL.md', 'AskUserQuestion') && !fileContains('skills/reviewer/SKILL.md', 'AskUserQuestion') },
171
+ { id: 'AC10.10', item: 10, tier: 2, desc: 'No open-ended questions', check: () => null },
172
+
173
+ // Item 11: User feedback
174
+ { id: 'AC11.1', item: 11, tier: 1, desc: 'Clarifier references user-feedback.md at runs/ path', check: () => fileContains('skills/clarifier/SKILL.md', 'user-feedback.md') && fileContains('skills/clarifier/SKILL.md', /runs\//) },
175
+ { id: 'AC11.2', item: 11, tier: 2, desc: 'Checkpoint routes corrections', check: () => null },
176
+ { id: 'AC11.3', item: 11, tier: 2, desc: 'File starts empty on new runs', check: () => null },
177
+ { id: 'AC11.4', item: 11, tier: 1, desc: 'Feedback is timestamped', check: () => fileContains('skills/clarifier/SKILL.md', /timestamp/i) },
178
+
179
+ // Item 14: Briefing updates
180
+ { id: 'AC14.1', item: 14, tier: 1, desc: 'Clarifier references User Additions', check: () => fileContains('skills/clarifier/SKILL.md', 'User Additions') },
181
+ { id: 'AC14.2', item: 14, tier: 2, desc: 'Checkpoint distinguishes scope vs correction', check: () => null },
182
+ { id: 'AC14.3', item: 14, tier: 1, desc: 'Gap analysis reads input/ and user-feedback.md', check: () => true /* covered by AC3.2 */ },
183
+ { id: 'AC14.4', item: 14, tier: 2, desc: 'Routing question uses numbered options', check: () => null },
184
+
185
+ // Item 15: Phase scoring
186
+ { id: 'AC15.1', item: 15, tier: 1, desc: 'Pattern doc defines canonical dimension sets', check: () => fileContains('docs/reference/review-loop-pattern.md', /canonical.*dimension|dimension.*set.*per.*phase/i) },
187
+ { id: 'AC15.2', item: 15, tier: 1, desc: 'Same dimensions + delta', check: () => fileContains('docs/reference/review-loop-pattern.md', /same.*dimension|delta/i) },
188
+ { id: 'AC15.3', item: 15, tier: 1, desc: 'Report includes Quality Delta', check: () => fileContains('docs/reference/review-loop-pattern.md', 'Quality Delta') || fileContains('skills/wazir/SKILL.md', 'Quality Delta') },
189
+ { id: 'AC15.4', item: 15, tier: 1, desc: 'Delta format with arrow', check: () => fileContains('docs/reference/review-loop-pattern.md', /\d+\/10.*→|→.*\d+\/10/) || fileContains('docs/reference/review-loop-pattern.md', /\+\d+\)/) },
190
+ { id: 'AC15.5', item: 15, tier: 1, desc: 'Reviewer pass files record dimension set', check: () => fileContains('skills/reviewer/SKILL.md', /dimension.*set|record.*dimension/i) },
191
+
192
+ // Item 16: Full report
193
+ { id: 'AC16.1', item: 16, tier: 1, desc: 'Report path reviews/<phase>-report.md', check: () => fileContains('skills/wazir/SKILL.md', /report\.md|phase.*report/) },
194
+ { id: 'AC16.2', item: 16, tier: 1, desc: 'Contains ## Summary', check: () => fileContains('skills/wazir/SKILL.md', 'Summary') || fileContains('docs/reference/review-loop-pattern.md', 'Summary') },
195
+ { id: 'AC16.3', item: 16, tier: 1, desc: 'Contains Key Changes', check: () => fileContains('skills/wazir/SKILL.md', 'Key Changes') || fileContains('docs/reference/review-loop-pattern.md', 'Key Changes') },
196
+ { id: 'AC16.4', item: 16, tier: 1, desc: 'Contains Quality Delta', check: () => fileContains('skills/wazir/SKILL.md', 'Quality Delta') || fileContains('docs/reference/review-loop-pattern.md', 'Quality Delta') },
197
+ { id: 'AC16.5', item: 16, tier: 2, desc: 'Findings Log with per-pass severity', check: () => null },
198
+ { id: 'AC16.6', item: 16, tier: 1, desc: 'Contains Usage', check: () => fileContains('skills/wazir/SKILL.md', /Usage|capture usage/) },
199
+ { id: 'AC16.7', item: 16, tier: 1, desc: 'Context Savings conditional', check: () => fileContains('skills/wazir/SKILL.md', /context.*sav|context.mode/i) || fileContains('docs/reference/review-loop-pattern.md', /context.*sav/i) },
200
+ { id: 'AC16.8', item: 16, tier: 1, desc: 'Time Spent section', check: () => fileContains('skills/wazir/SKILL.md', /time.*spent|time.*phase/i) || fileContains('docs/reference/review-loop-pattern.md', /time.*spent/i) },
201
+ { id: 'AC16.9', item: 16, tier: 2, desc: 'Bound to EVERY phase_exit', check: () => null },
202
+ ];
203
+
204
+ export function runAcMatrix() {
205
+ let pass = 0;
206
+ let fail = 0;
207
+ let manual = 0;
208
+ const results = [];
209
+
210
+ for (const ac of CHECKS) {
211
+ let result;
212
+ try {
213
+ result = ac.check();
214
+ } catch {
215
+ result = false;
216
+ }
217
+
218
+ if (result === null) {
219
+ manual++;
220
+ results.push({ ...ac, status: 'MANUAL' });
221
+ } else if (result) {
222
+ pass++;
223
+ results.push({ ...ac, status: 'PASS' });
224
+ } else {
225
+ fail++;
226
+ results.push({ ...ac, status: 'FAIL' });
227
+ }
228
+ }
229
+
230
+ return { pass, fail, manual, total: CHECKS.length, results };
231
+ }
232
+
233
+ // CLI entry point
234
+ if (import.meta.url === `file://${process.argv[1]}`) {
235
+ const { pass, fail, manual, total, results } = runAcMatrix();
236
+
237
+ console.log(`\nAC Matrix: ${pass} PASS / ${fail} FAIL / ${manual} MANUAL / ${total} total\n`);
238
+
239
+ const failing = results.filter(r => r.status === 'FAIL');
240
+ if (failing.length > 0) {
241
+ console.log('FAILING:');
242
+ for (const f of failing) {
243
+ console.log(` ${f.id} (Item ${f.item}): ${f.desc}`);
244
+ }
245
+ }
246
+
247
+ const manualChecks = results.filter(r => r.status === 'MANUAL');
248
+ if (manualChecks.length > 0) {
249
+ console.log('\nMANUAL VERIFICATION NEEDED:');
250
+ for (const m of manualChecks) {
251
+ console.log(` ${m.id} (Item ${m.item}): ${m.desc}`);
252
+ }
253
+ }
254
+
255
+ process.exit(fail > 0 ? 1 : 0);
256
+ }
@@ -7,17 +7,14 @@ const EXCLUDED_DOC_FILES = new Set([
7
7
  ]);
8
8
 
9
9
  const BRAND_PATTERNS = [
10
- { label: 'Agent OS', regex: /\bAgent OS\b/g },
11
- { label: 'agent-os', regex: /\bagent-os\b/g },
12
- { label: 'Symphony', regex: /\bSymphony\b/g },
13
10
  { label: 'Wazir OS', regex: /\bWazir OS\b/g },
14
11
  ];
15
12
 
16
13
  function normalizeAllowedLegacyReferences(content) {
17
14
  return content
18
- .replace(/archive\/legacy-agent-os\/[^\s)`]*/g, 'archive/<legacy>')
19
- .replace(/archive\/v5\.1-agent-os-daemon\/[^\s)`]*/g, 'archive/<legacy>')
20
- .replace(/migration\/v5\.1-agent-os-to-wazir\.md/g, 'migration/<legacy>');
15
+ .replace(/archive\/legacy-wazir\/[^\s)`]*/g, 'archive/<legacy>')
16
+ .replace(/archive\/v5\.1-wazir-daemon\/[^\s)`]*/g, 'archive/<legacy>')
17
+ .replace(/migration\/v5\.1-wazir-rename\.md/g, 'migration/<legacy>');
21
18
  }
22
19
 
23
20
  function walkMarkdownFiles(dirPath, files = []) {
@@ -10,6 +10,7 @@ export const SUPPORTED_COMMAND_SUBJECTS = new Set([
10
10
  'wazir validate commits',
11
11
  'wazir validate changelog',
12
12
  'wazir validate docs-drift',
13
+ 'wazir validate skills',
13
14
  'wazir export',
14
15
  'wazir export build',
15
16
  'wazir export --check',
@@ -26,6 +27,7 @@ export const SUPPORTED_COMMAND_SUBJECTS = new Set([
26
27
  'wazir recall symbol',
27
28
  'wazir doctor',
28
29
  'wazir status',
30
+ 'wazir stats',
29
31
  'wazir capture',
30
32
  'wazir capture init',
31
33
  'wazir capture event',
@@ -33,4 +35,15 @@ export const SUPPORTED_COMMAND_SUBJECTS = new Set([
33
35
  'wazir capture output',
34
36
  'wazir capture summary',
35
37
  'wazir capture usage',
38
+ 'wazir capture loop-check',
39
+ 'wazir init',
40
+ 'wazir config',
41
+ 'wazir config set',
42
+ 'wazir report',
43
+ 'wazir report phase',
44
+ 'wazir state',
45
+ 'wazir state stats',
46
+ 'wazir state learnings',
47
+ 'wazir state findings',
48
+ 'wazir state trend',
36
49
  ]);