@wazir-dev/cli 1.1.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.
- package/CHANGELOG.md +73 -4
- package/README.md +6 -6
- package/docs/concepts/architecture.md +1 -1
- package/docs/concepts/roles-and-workflows.md +2 -0
- package/docs/concepts/why-wazir.md +59 -0
- package/docs/decisions/2026-03-19-deferred-items.md +564 -0
- package/docs/decisions/2026-03-19-enhancement-decisions.md +300 -0
- package/docs/readmes/INDEX.md +21 -5
- package/docs/readmes/features/expertise/README.md +2 -2
- package/docs/readmes/features/exports/README.md +2 -2
- package/docs/readmes/features/schemas/README.md +3 -0
- package/docs/readmes/features/skills/README.md +17 -0
- package/docs/readmes/features/skills/clarifier.md +5 -0
- package/docs/readmes/features/skills/claude-cli.md +5 -0
- package/docs/readmes/features/skills/codex-cli.md +5 -0
- package/docs/readmes/features/skills/dispatching-parallel-agents.md +5 -0
- package/docs/readmes/features/skills/executing-plans.md +5 -0
- package/docs/readmes/features/skills/executor.md +5 -0
- package/docs/readmes/features/skills/finishing-a-development-branch.md +5 -0
- package/docs/readmes/features/skills/gemini-cli.md +5 -0
- package/docs/readmes/features/skills/humanize.md +5 -0
- package/docs/readmes/features/skills/init-pipeline.md +5 -0
- package/docs/readmes/features/skills/receiving-code-review.md +5 -0
- package/docs/readmes/features/skills/requesting-code-review.md +5 -0
- package/docs/readmes/features/skills/reviewer.md +5 -0
- package/docs/readmes/features/skills/subagent-driven-development.md +5 -0
- package/docs/readmes/features/skills/using-git-worktrees.md +5 -0
- package/docs/readmes/features/skills/wazir.md +5 -0
- package/docs/readmes/features/skills/writing-skills.md +5 -0
- package/docs/readmes/features/workflows/prepare-next.md +1 -1
- package/docs/reference/configuration-reference.md +47 -6
- package/docs/reference/launch-checklist.md +4 -4
- package/docs/reference/review-loop-pattern.md +117 -8
- package/docs/reference/roles-reference.md +1 -0
- package/docs/reference/skill-tiers.md +147 -0
- package/docs/reference/tooling-cli.md +3 -1
- package/docs/truth-claims.yaml +12 -0
- package/expertise/antipatterns/process/ai-coding-antipatterns.md +97 -1
- package/exports/hosts/claude/.claude/settings.json +9 -0
- package/exports/hosts/claude/CLAUDE.md +1 -1
- package/exports/hosts/claude/export.manifest.json +4 -2
- package/exports/hosts/claude/host-package.json +3 -1
- package/exports/hosts/codex/AGENTS.md +1 -1
- package/exports/hosts/codex/export.manifest.json +4 -2
- package/exports/hosts/codex/host-package.json +3 -1
- package/exports/hosts/cursor/.cursor/hooks.json +4 -0
- package/exports/hosts/cursor/.cursor/rules/wazir-core.mdc +1 -1
- package/exports/hosts/cursor/export.manifest.json +4 -2
- package/exports/hosts/cursor/host-package.json +3 -1
- package/exports/hosts/gemini/GEMINI.md +1 -1
- package/exports/hosts/gemini/export.manifest.json +4 -2
- package/exports/hosts/gemini/host-package.json +3 -1
- package/hooks/context-mode-router +191 -0
- package/hooks/definitions/context_mode_router.yaml +19 -0
- package/hooks/hooks.json +31 -6
- package/hooks/protected-path-write-guard +8 -0
- package/hooks/routing-matrix.json +45 -0
- package/hooks/session-start +62 -1
- package/llms-full.txt +905 -132
- package/package.json +2 -3
- package/schemas/hook.schema.json +2 -1
- package/schemas/phase-report.schema.json +80 -0
- package/schemas/usage.schema.json +25 -1
- package/schemas/wazir-manifest.schema.json +19 -0
- package/skills/brainstorming/SKILL.md +18 -155
- package/skills/clarifier/SKILL.md +122 -98
- package/skills/claude-cli/SKILL.md +320 -0
- package/skills/codex-cli/SKILL.md +260 -0
- package/skills/debugging/SKILL.md +13 -0
- package/skills/design/SKILL.md +13 -0
- package/skills/dispatching-parallel-agents/SKILL.md +13 -0
- package/skills/executing-plans/SKILL.md +13 -0
- package/skills/executor/SKILL.md +72 -19
- package/skills/finishing-a-development-branch/SKILL.md +13 -0
- package/skills/gemini-cli/SKILL.md +260 -0
- package/skills/humanize/SKILL.md +13 -0
- package/skills/init-pipeline/SKILL.md +73 -164
- package/skills/prepare-next/SKILL.md +81 -10
- package/skills/receiving-code-review/SKILL.md +13 -0
- package/skills/requesting-code-review/SKILL.md +13 -0
- package/skills/reviewer/SKILL.md +287 -15
- package/skills/run-audit/SKILL.md +13 -0
- package/skills/scan-project/SKILL.md +13 -0
- package/skills/self-audit/SKILL.md +197 -16
- package/skills/subagent-driven-development/SKILL.md +13 -0
- package/skills/subagent-driven-development/code-quality-reviewer-prompt.md +2 -0
- package/skills/subagent-driven-development/implementer-prompt.md +8 -0
- package/skills/subagent-driven-development/spec-reviewer-prompt.md +7 -0
- package/skills/tdd/SKILL.md +13 -0
- package/skills/using-git-worktrees/SKILL.md +13 -0
- package/skills/using-skills/SKILL.md +13 -0
- package/skills/verification/SKILL.md +13 -0
- package/skills/wazir/SKILL.md +194 -377
- package/skills/writing-plans/SKILL.md +14 -1
- package/skills/writing-skills/SKILL.md +13 -0
- package/templates/artifacts/implementation-plan.md +3 -0
- package/templates/artifacts/tasks-template.md +133 -0
- package/templates/examples/phase-report.example.json +48 -0
- package/tooling/src/adapters/composition-engine.js +256 -0
- package/tooling/src/adapters/model-router.js +84 -0
- package/tooling/src/capture/command.js +24 -1
- package/tooling/src/capture/run-config.js +3 -1
- package/tooling/src/capture/store.js +24 -0
- package/tooling/src/capture/usage.js +106 -0
- package/tooling/src/checks/ac-matrix.js +256 -0
- package/tooling/src/checks/command-registry.js +12 -0
- package/tooling/src/checks/docs-truth.js +1 -1
- package/tooling/src/checks/skills.js +111 -0
- package/tooling/src/cli.js +9 -0
- package/tooling/src/commands/stats.js +161 -0
- package/tooling/src/commands/validate.js +5 -1
- package/tooling/src/export/compiler.js +33 -37
- package/tooling/src/gating/agent.js +145 -0
- package/tooling/src/guards/phase-prerequisite-guard.js +127 -0
- package/tooling/src/hooks/routing-logic.js +69 -0
- package/tooling/src/init/auto-detect.js +260 -0
- package/tooling/src/init/command.js +95 -135
- package/tooling/src/input/scanner.js +46 -0
- package/tooling/src/reports/command.js +103 -0
- package/tooling/src/reports/phase-report.js +323 -0
- package/tooling/src/state/command.js +160 -0
- package/tooling/src/state/db.js +287 -0
- package/tooling/src/status/command.js +53 -1
- package/wazir.manifest.yaml +26 -14
|
@@ -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
|
+
}
|
|
@@ -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',
|
|
@@ -34,4 +36,14 @@ export const SUPPORTED_COMMAND_SUBJECTS = new Set([
|
|
|
34
36
|
'wazir capture summary',
|
|
35
37
|
'wazir capture usage',
|
|
36
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',
|
|
37
49
|
]);
|
|
@@ -5,7 +5,7 @@ import { readJsonFile, readYamlFile } from '../loaders.js';
|
|
|
5
5
|
import { validateAgainstSchema } from '../schema-validator.js';
|
|
6
6
|
import { SUPPORTED_COMMAND_SUBJECTS } from './command-registry.js';
|
|
7
7
|
|
|
8
|
-
const EXCLUDED_DOC_DIRS = new Set(['plans', 'research', 'audit']);
|
|
8
|
+
const EXCLUDED_DOC_DIRS = new Set(['plans', 'research', 'audit', 'decisions']);
|
|
9
9
|
|
|
10
10
|
function walkMarkdownFiles(dirPath, files = []) {
|
|
11
11
|
for (const entry of fs.readdirSync(dirPath, { withFileTypes: true })) {
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Known superpowers skill names that conflict with Wazir skill names.
|
|
6
|
+
* A Wazir skill may share a name with a superpowers skill ONLY if it uses
|
|
7
|
+
* the wz: prefix — the Augment tier (CONTEXT.md companion) is not
|
|
8
|
+
* implementable (concluded in Task 18 R2) and is no longer supported.
|
|
9
|
+
*/
|
|
10
|
+
const SUPERPOWERS_SKILL_NAMES = new Set([
|
|
11
|
+
'brainstorming',
|
|
12
|
+
'dispatching-parallel-agents',
|
|
13
|
+
'executing-plans',
|
|
14
|
+
'finishing-a-development-branch',
|
|
15
|
+
'receiving-code-review',
|
|
16
|
+
'requesting-code-review',
|
|
17
|
+
'subagent-driven-development',
|
|
18
|
+
'using-git-worktrees',
|
|
19
|
+
'verification',
|
|
20
|
+
'writing-plans',
|
|
21
|
+
'writing-skills',
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
function parseFrontmatter(content) {
|
|
25
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
26
|
+
if (!match) return null;
|
|
27
|
+
|
|
28
|
+
const fields = {};
|
|
29
|
+
for (const line of match[1].split('\n')) {
|
|
30
|
+
const colonIndex = line.indexOf(':');
|
|
31
|
+
if (colonIndex === -1) continue;
|
|
32
|
+
const key = line.slice(0, colonIndex).trim();
|
|
33
|
+
const value = line.slice(colonIndex + 1).trim();
|
|
34
|
+
fields[key] = value;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return fields;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function listSkillDirs(skillsDir) {
|
|
41
|
+
if (!fs.existsSync(skillsDir)) return [];
|
|
42
|
+
|
|
43
|
+
return fs.readdirSync(skillsDir, { withFileTypes: true })
|
|
44
|
+
.filter((entry) => entry.isDirectory())
|
|
45
|
+
.map((entry) => entry.name)
|
|
46
|
+
.sort();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function validateSkillsAtProjectRoot(projectRoot) {
|
|
50
|
+
const skillsDir = path.join(projectRoot, 'skills');
|
|
51
|
+
const errors = [];
|
|
52
|
+
const skillDirs = listSkillDirs(skillsDir);
|
|
53
|
+
let checkedCount = 0;
|
|
54
|
+
|
|
55
|
+
for (const dirName of skillDirs) {
|
|
56
|
+
const skillMdPath = path.join(skillsDir, dirName, 'SKILL.md');
|
|
57
|
+
|
|
58
|
+
if (!fs.existsSync(skillMdPath)) continue;
|
|
59
|
+
|
|
60
|
+
checkedCount++;
|
|
61
|
+
const content = fs.readFileSync(skillMdPath, 'utf8');
|
|
62
|
+
const frontmatter = parseFrontmatter(content);
|
|
63
|
+
|
|
64
|
+
if (!frontmatter) {
|
|
65
|
+
errors.push(`${dirName}: SKILL.md missing YAML frontmatter`);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!frontmatter.name) {
|
|
70
|
+
errors.push(`${dirName}: SKILL.md missing name field in frontmatter`);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const skillName = frontmatter.name;
|
|
75
|
+
const baseName = skillName.startsWith('wz:') ? skillName.slice(3) : skillName;
|
|
76
|
+
const hasWzPrefix = skillName.startsWith('wz:');
|
|
77
|
+
const conflictsWithSuperpowers = SUPERPOWERS_SKILL_NAMES.has(baseName);
|
|
78
|
+
const contextMdPath = path.join(skillsDir, dirName, 'CONTEXT.md');
|
|
79
|
+
const hasContextMd = fs.existsSync(contextMdPath);
|
|
80
|
+
|
|
81
|
+
// Check: conflicting name without wz: prefix must be flagged.
|
|
82
|
+
// Augment tier is not supported — the only resolution is the wz: prefix.
|
|
83
|
+
if (conflictsWithSuperpowers && !hasWzPrefix) {
|
|
84
|
+
errors.push(
|
|
85
|
+
`${dirName}: skill name "${skillName}" conflicts with superpowers:${baseName} — ` +
|
|
86
|
+
'add wz: prefix to resolve the conflict',
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Check: CONTEXT.md files are stale — augment tier is not implementable
|
|
91
|
+
// (concluded in Task 18 R2). Flag any remaining CONTEXT.md as an error.
|
|
92
|
+
if (hasContextMd) {
|
|
93
|
+
errors.push(
|
|
94
|
+
`${dirName}: CONTEXT.md is not supported — augment tier was removed. ` +
|
|
95
|
+
'Delete CONTEXT.md; all Wazir skills use the Own tier with wz: prefix.',
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (errors.length > 0) {
|
|
101
|
+
return {
|
|
102
|
+
exitCode: 1,
|
|
103
|
+
stderr: `Skill validation failed:\n- ${errors.join('\n- ')}\n`,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
exitCode: 0,
|
|
109
|
+
stdout: `Skill validation passed. Checked ${checkedCount} skills.\n`,
|
|
110
|
+
};
|
|
111
|
+
}
|
package/tooling/src/cli.js
CHANGED
|
@@ -10,6 +10,9 @@ import { runExportCommand as runGeneratedExportCommand } from './export/command.
|
|
|
10
10
|
import { runIndexCommand } from './index/command.js';
|
|
11
11
|
import { runInitCommand } from './init/command.js';
|
|
12
12
|
import { runRecallCommand } from './recall/command.js';
|
|
13
|
+
import { runReportCommand } from './reports/command.js';
|
|
14
|
+
import { runStateCommand } from './state/command.js';
|
|
15
|
+
import { runStatsCommand } from './commands/stats.js';
|
|
13
16
|
import { runStatusCommand } from './status/command.js';
|
|
14
17
|
|
|
15
18
|
const COMMAND_FAMILIES = [
|
|
@@ -19,7 +22,10 @@ const COMMAND_FAMILIES = [
|
|
|
19
22
|
'index',
|
|
20
23
|
'init',
|
|
21
24
|
'recall',
|
|
25
|
+
'report',
|
|
26
|
+
'state',
|
|
22
27
|
'status',
|
|
28
|
+
'stats',
|
|
23
29
|
'capture'
|
|
24
30
|
];
|
|
25
31
|
|
|
@@ -30,7 +36,10 @@ const COMMAND_HANDLERS = {
|
|
|
30
36
|
index: runIndexCommand,
|
|
31
37
|
init: runInitCommand,
|
|
32
38
|
recall: runRecallCommand,
|
|
39
|
+
report: runReportCommand,
|
|
40
|
+
state: runStateCommand,
|
|
33
41
|
status: runStatusCommand,
|
|
42
|
+
stats: runStatsCommand,
|
|
34
43
|
capture: runCaptureCommand,
|
|
35
44
|
};
|
|
36
45
|
|