@wazir-dev/cli 1.1.0 → 1.3.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 (138) hide show
  1. package/CHANGELOG.md +74 -10
  2. package/README.md +15 -15
  3. package/assets/demo.cast +47 -0
  4. package/assets/demo.gif +0 -0
  5. package/docs/anti-patterns/AP-23-skipping-enabled-workflows.md +28 -0
  6. package/docs/anti-patterns/AP-24-clarifier-deciding-scope.md +34 -0
  7. package/docs/concepts/architecture.md +1 -1
  8. package/docs/concepts/roles-and-workflows.md +2 -0
  9. package/docs/concepts/why-wazir.md +59 -0
  10. package/docs/decisions/2026-03-19-deferred-items.md +564 -0
  11. package/docs/decisions/2026-03-19-enhancement-decisions.md +300 -0
  12. package/docs/readmes/INDEX.md +21 -5
  13. package/docs/readmes/features/expertise/README.md +2 -2
  14. package/docs/readmes/features/exports/README.md +2 -2
  15. package/docs/readmes/features/hooks/pre-compact-summary.md +1 -1
  16. package/docs/readmes/features/schemas/README.md +3 -0
  17. package/docs/readmes/features/skills/README.md +17 -0
  18. package/docs/readmes/features/skills/clarifier.md +5 -0
  19. package/docs/readmes/features/skills/claude-cli.md +5 -0
  20. package/docs/readmes/features/skills/codex-cli.md +5 -0
  21. package/docs/readmes/features/skills/dispatching-parallel-agents.md +5 -0
  22. package/docs/readmes/features/skills/executing-plans.md +5 -0
  23. package/docs/readmes/features/skills/executor.md +5 -0
  24. package/docs/readmes/features/skills/finishing-a-development-branch.md +5 -0
  25. package/docs/readmes/features/skills/gemini-cli.md +5 -0
  26. package/docs/readmes/features/skills/humanize.md +5 -0
  27. package/docs/readmes/features/skills/init-pipeline.md +5 -0
  28. package/docs/readmes/features/skills/receiving-code-review.md +5 -0
  29. package/docs/readmes/features/skills/requesting-code-review.md +5 -0
  30. package/docs/readmes/features/skills/reviewer.md +5 -0
  31. package/docs/readmes/features/skills/subagent-driven-development.md +5 -0
  32. package/docs/readmes/features/skills/using-git-worktrees.md +5 -0
  33. package/docs/readmes/features/skills/wazir.md +5 -0
  34. package/docs/readmes/features/skills/writing-skills.md +5 -0
  35. package/docs/readmes/features/workflows/prepare-next.md +1 -1
  36. package/docs/reference/configuration-reference.md +47 -6
  37. package/docs/reference/hooks.md +1 -0
  38. package/docs/reference/launch-checklist.md +4 -4
  39. package/docs/reference/review-loop-pattern.md +119 -9
  40. package/docs/reference/roles-reference.md +1 -0
  41. package/docs/reference/skill-tiers.md +147 -0
  42. package/docs/reference/tooling-cli.md +3 -1
  43. package/docs/truth-claims.yaml +12 -0
  44. package/expertise/antipatterns/process/ai-coding-antipatterns.md +214 -1
  45. package/exports/hosts/claude/.claude/commands/plan-review.md +3 -1
  46. package/exports/hosts/claude/.claude/commands/verify.md +30 -1
  47. package/exports/hosts/claude/.claude/settings.json +9 -0
  48. package/exports/hosts/claude/CLAUDE.md +1 -1
  49. package/exports/hosts/claude/export.manifest.json +6 -4
  50. package/exports/hosts/claude/host-package.json +3 -1
  51. package/exports/hosts/codex/AGENTS.md +1 -1
  52. package/exports/hosts/codex/export.manifest.json +6 -4
  53. package/exports/hosts/codex/host-package.json +3 -1
  54. package/exports/hosts/cursor/.cursor/hooks.json +4 -0
  55. package/exports/hosts/cursor/.cursor/rules/wazir-core.mdc +1 -1
  56. package/exports/hosts/cursor/export.manifest.json +6 -4
  57. package/exports/hosts/cursor/host-package.json +3 -1
  58. package/exports/hosts/gemini/GEMINI.md +1 -1
  59. package/exports/hosts/gemini/export.manifest.json +6 -4
  60. package/exports/hosts/gemini/host-package.json +3 -1
  61. package/hooks/context-mode-router +191 -0
  62. package/hooks/definitions/context_mode_router.yaml +19 -0
  63. package/hooks/hooks.json +31 -6
  64. package/hooks/protected-path-write-guard +8 -0
  65. package/hooks/routing-matrix.json +45 -0
  66. package/hooks/session-start +62 -1
  67. package/llms-full.txt +937 -134
  68. package/package.json +2 -4
  69. package/schemas/hook.schema.json +2 -1
  70. package/schemas/phase-report.schema.json +89 -0
  71. package/schemas/usage.schema.json +25 -1
  72. package/schemas/wazir-manifest.schema.json +19 -0
  73. package/skills/brainstorming/SKILL.md +32 -157
  74. package/skills/clarifier/SKILL.md +289 -111
  75. package/skills/claude-cli/SKILL.md +320 -0
  76. package/skills/codex-cli/SKILL.md +260 -0
  77. package/skills/debugging/SKILL.md +13 -0
  78. package/skills/design/SKILL.md +13 -0
  79. package/skills/dispatching-parallel-agents/SKILL.md +13 -0
  80. package/skills/executing-plans/SKILL.md +13 -0
  81. package/skills/executor/SKILL.md +139 -19
  82. package/skills/finishing-a-development-branch/SKILL.md +13 -0
  83. package/skills/gemini-cli/SKILL.md +260 -0
  84. package/skills/humanize/SKILL.md +13 -0
  85. package/skills/init-pipeline/SKILL.md +72 -164
  86. package/skills/prepare-next/SKILL.md +81 -10
  87. package/skills/receiving-code-review/SKILL.md +13 -0
  88. package/skills/requesting-code-review/SKILL.md +13 -0
  89. package/skills/reviewer/SKILL.md +369 -24
  90. package/skills/run-audit/SKILL.md +13 -0
  91. package/skills/scan-project/SKILL.md +13 -0
  92. package/skills/self-audit/SKILL.md +217 -16
  93. package/skills/skill-research/SKILL.md +188 -0
  94. package/skills/subagent-driven-development/SKILL.md +13 -0
  95. package/skills/subagent-driven-development/code-quality-reviewer-prompt.md +2 -0
  96. package/skills/subagent-driven-development/implementer-prompt.md +8 -0
  97. package/skills/subagent-driven-development/spec-reviewer-prompt.md +7 -0
  98. package/skills/tdd/SKILL.md +13 -0
  99. package/skills/using-git-worktrees/SKILL.md +13 -0
  100. package/skills/using-skills/SKILL.md +13 -0
  101. package/skills/verification/SKILL.md +54 -3
  102. package/skills/wazir/SKILL.md +464 -381
  103. package/skills/writing-plans/SKILL.md +14 -1
  104. package/skills/writing-skills/SKILL.md +13 -0
  105. package/templates/artifacts/implementation-plan.md +3 -0
  106. package/templates/artifacts/tasks-template.md +133 -0
  107. package/templates/examples/phase-report.example.json +48 -0
  108. package/tooling/src/adapters/composition-engine.js +256 -0
  109. package/tooling/src/adapters/model-router.js +84 -0
  110. package/tooling/src/capture/command.js +41 -2
  111. package/tooling/src/capture/run-config.js +3 -1
  112. package/tooling/src/capture/store.js +56 -0
  113. package/tooling/src/capture/usage.js +106 -0
  114. package/tooling/src/capture/user-input.js +66 -0
  115. package/tooling/src/checks/ac-matrix.js +256 -0
  116. package/tooling/src/checks/command-registry.js +12 -0
  117. package/tooling/src/checks/docs-truth.js +1 -1
  118. package/tooling/src/checks/security-sensitivity.js +69 -0
  119. package/tooling/src/checks/skills.js +111 -0
  120. package/tooling/src/cli.js +31 -20
  121. package/tooling/src/commands/stats.js +161 -0
  122. package/tooling/src/commands/validate.js +5 -1
  123. package/tooling/src/export/compiler.js +33 -37
  124. package/tooling/src/gating/agent.js +145 -0
  125. package/tooling/src/guards/phase-prerequisite-guard.js +185 -0
  126. package/tooling/src/hooks/routing-logic.js +69 -0
  127. package/tooling/src/init/auto-detect.js +258 -0
  128. package/tooling/src/init/command.js +38 -170
  129. package/tooling/src/input/scanner.js +46 -0
  130. package/tooling/src/reports/command.js +103 -0
  131. package/tooling/src/reports/phase-report.js +323 -0
  132. package/tooling/src/state/command.js +160 -0
  133. package/tooling/src/state/db.js +287 -0
  134. package/tooling/src/status/command.js +58 -1
  135. package/tooling/src/verify/proof-collector.js +299 -0
  136. package/wazir.manifest.yaml +26 -14
  137. package/workflows/plan-review.md +3 -1
  138. package/workflows/verify.md +30 -1
@@ -0,0 +1,191 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { appendFileSync, existsSync, mkdirSync, readFileSync } from 'node:fs';
4
+ import { dirname, join, resolve, basename } from 'node:path';
5
+ import { homedir } from 'node:os';
6
+ import { fileURLToPath } from 'node:url';
7
+
8
+ const hooksDir = dirname(fileURLToPath(import.meta.url));
9
+ const projectRoot = dirname(hooksDir);
10
+ const matrixPath = join(hooksDir, 'routing-matrix.json');
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // Helpers
14
+ // ---------------------------------------------------------------------------
15
+
16
+ function loadRoutingMatrix() {
17
+ return JSON.parse(readFileSync(matrixPath, 'utf8'));
18
+ }
19
+
20
+ function parseCommand(raw) {
21
+ return (raw || '').trim();
22
+ }
23
+
24
+ function firstToken(cmd) {
25
+ return cmd.split(/\s+/)[0] || '';
26
+ }
27
+
28
+ function hasPipe(cmd) {
29
+ return /(?<![\\])\|/.test(cmd);
30
+ }
31
+
32
+ function hasRedirect(cmd) {
33
+ return /(?<![\\])[>]/.test(cmd);
34
+ }
35
+
36
+ // ---------------------------------------------------------------------------
37
+ // Classification
38
+ // ---------------------------------------------------------------------------
39
+
40
+ function classify(cmd, matrix) {
41
+ // 1. Explicit context-mode marker always wins
42
+ if (cmd.includes('# wazir:context-mode')) return 'large';
43
+
44
+ // 2. Check large patterns FIRST — AC-3.1: large commands are never
45
+ // downgraded, even with a passthrough marker (AC-1.5).
46
+ for (const pattern of matrix.large) {
47
+ if (cmd === pattern || cmd.startsWith(pattern + ' ') || cmd.startsWith(pattern + '\t')) {
48
+ return 'large';
49
+ }
50
+ }
51
+
52
+ // 3. Passthrough marker — restricted to heuristic false positives (AC-1.5).
53
+ // Only honoured when the command is NOT in the Large category (checked above).
54
+ if (cmd.includes('# wazir:passthrough')) return 'small';
55
+
56
+ // 4. Check small patterns
57
+ for (const pattern of matrix.small) {
58
+ if (cmd === pattern || cmd.startsWith(pattern + ' ') || cmd.startsWith(pattern + '\t')) {
59
+ return 'small';
60
+ }
61
+ }
62
+
63
+ // 5. Ambiguous heuristics
64
+ const heuristic = matrix.ambiguous_heuristic || {};
65
+
66
+ if (heuristic.pipe_detected && hasPipe(cmd)) return 'ambiguous';
67
+ if (heuristic.redirect_detected && hasRedirect(cmd)) return 'ambiguous';
68
+
69
+ const bin = firstToken(cmd);
70
+ if (Array.isArray(heuristic.verbose_binaries) && heuristic.verbose_binaries.includes(bin)) {
71
+ return 'ambiguous';
72
+ }
73
+
74
+ // Default: treat unknown commands as small (passthrough)
75
+ return 'small';
76
+ }
77
+
78
+ // ---------------------------------------------------------------------------
79
+ // Context-mode enabled check
80
+ // ---------------------------------------------------------------------------
81
+
82
+ function isContextModeEnabled() {
83
+ // Check WAZIR_CONTEXT_MODE env var first
84
+ const envVal = process.env.WAZIR_CONTEXT_MODE;
85
+ if (envVal !== undefined) {
86
+ return envVal === '1' || envVal === 'true';
87
+ }
88
+
89
+ // Fall back to manifest adapter setting
90
+ try {
91
+ const manifestPath = join(projectRoot, 'wazir.manifest.yaml');
92
+ const manifestText = readFileSync(manifestPath, 'utf8');
93
+ // Simple YAML parse for enabled_by_default under context_mode
94
+ const match = manifestText.match(/context_mode:[\s\S]*?enabled_by_default:\s*(true|false)/);
95
+ if (match) return match[1] === 'true';
96
+ } catch {
97
+ // ignore
98
+ }
99
+
100
+ return false;
101
+ }
102
+
103
+ // ---------------------------------------------------------------------------
104
+ // One-per-session warning when context-mode is disabled
105
+ // ---------------------------------------------------------------------------
106
+
107
+ const SESSION_WARNING_KEY = 'WAZIR_CM_WARNED';
108
+
109
+ function emitDisabledWarning() {
110
+ if (process.env[SESSION_WARNING_KEY]) return;
111
+
112
+ process.stderr.write(
113
+ '[wazir:context-mode-router] context_mode adapter is disabled. ' +
114
+ 'All commands pass through without routing. ' +
115
+ 'Enable via WAZIR_CONTEXT_MODE=1 or set adapters.context_mode.enabled_by_default: true in manifest.\n',
116
+ );
117
+
118
+ // Mark so downstream child processes see it (best-effort session dedup)
119
+ process.env[SESSION_WARNING_KEY] = '1';
120
+ }
121
+
122
+ // ---------------------------------------------------------------------------
123
+ // Logging
124
+ // ---------------------------------------------------------------------------
125
+
126
+ function deriveStateRoot() {
127
+ if (process.env.WAZIR_STATE_ROOT) return process.env.WAZIR_STATE_ROOT;
128
+ try {
129
+ const manifestPath = join(projectRoot, 'wazir.manifest.yaml');
130
+ const raw = readFileSync(manifestPath, 'utf8');
131
+ const nameMatch = raw.match(/^\s+name:\s*(.+)$/m);
132
+ const templateMatch = raw.match(/state_root_default:\s*(.+)$/m);
133
+ if (nameMatch && templateMatch) {
134
+ const slug = nameMatch[1].trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'wazir-project';
135
+ const template = templateMatch[1].trim();
136
+ const expanded = template.startsWith('~/') ? join(homedir(), template.slice(2)) : template;
137
+ return resolve(expanded.replace('{project_slug}', slug));
138
+ }
139
+ } catch { /* fall through */ }
140
+ return join(homedir(), '.wazir', 'projects', '_default');
141
+ }
142
+
143
+ function logDecision(decision) {
144
+ const stateRoot = deriveStateRoot();
145
+ const logDir = join(stateRoot, 'logs');
146
+
147
+ try {
148
+ if (!existsSync(logDir)) mkdirSync(logDir, { recursive: true });
149
+ const entry = JSON.stringify({
150
+ ts: new Date().toISOString(),
151
+ hook: 'context_mode_router',
152
+ ...decision,
153
+ });
154
+ appendFileSync(join(logDir, 'routing.ndjson'), entry + '\n');
155
+ } catch {
156
+ // Logging is best-effort; never fail the hook over it
157
+ }
158
+ }
159
+
160
+ // ---------------------------------------------------------------------------
161
+ // Main
162
+ // ---------------------------------------------------------------------------
163
+
164
+ try {
165
+ const input = readFileSync(0, 'utf8');
166
+ const payload = input.trim() ? JSON.parse(input) : {};
167
+ const cmd = parseCommand(payload.command || payload.tool_input?.command || '');
168
+ const matrix = loadRoutingMatrix();
169
+ const category = classify(cmd, matrix);
170
+ const contextModeEnabled = isContextModeEnabled();
171
+
172
+ let route = 'passthrough';
173
+
174
+ if (!contextModeEnabled) {
175
+ emitDisabledWarning();
176
+ route = 'passthrough';
177
+ } else if (category === 'large' || category === 'ambiguous') {
178
+ route = 'context-mode';
179
+ }
180
+
181
+ const decision = { command: cmd, category, route, context_mode_enabled: contextModeEnabled };
182
+
183
+ logDecision(decision);
184
+ process.stdout.write(`${JSON.stringify({ routing_decision: decision })}\n`);
185
+
186
+ // Exit 0 = passthrough, non-zero = route to context-mode
187
+ process.exit(route === 'passthrough' ? 0 : 1);
188
+ } catch (error) {
189
+ process.stderr.write(`${error.message}\n`);
190
+ process.exit(0); // failure_behavior.mode = warn → always allow on error
191
+ }
@@ -0,0 +1,19 @@
1
+ id: context_mode_router
2
+ trigger: context_mode_router
3
+ description: Route large command output through context-mode tools to avoid flooding model context.
4
+ input_contract:
5
+ required:
6
+ - command
7
+ allowed_side_effects:
8
+ - route_to_context_mode
9
+ output_contract:
10
+ produces:
11
+ - routing_decision
12
+ failure_behavior:
13
+ mode: warn
14
+ exit_code: 0
15
+ host_fallback:
16
+ claude: native_hook
17
+ codex: wrapper_command
18
+ gemini: wrapper_command
19
+ cursor: native_or_wrapper
package/hooks/hooks.json CHANGED
@@ -1,17 +1,42 @@
1
1
  {
2
2
  "hooks": {
3
- "beforeFileEdit": [
3
+ "PreToolUse": [
4
4
  {
5
- "command": "./hooks/protected-path-write-guard",
6
- "matcher": "*.md|*.yaml|*.json"
5
+ "matcher": "Write|Edit",
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "./hooks/protected-path-write-guard"
10
+ }
11
+ ]
12
+ },
13
+ {
14
+ "matcher": "Bash",
15
+ "hooks": [
16
+ {
17
+ "type": "command",
18
+ "command": "./hooks/context-mode-router"
19
+ }
20
+ ]
7
21
  }
8
22
  ],
9
- "sessionStart": [
23
+ "SessionStart": [
10
24
  {
11
- "command": "./hooks/loop-cap-guard"
25
+ "hooks": [
26
+ {
27
+ "type": "command",
28
+ "command": "./hooks/loop-cap-guard"
29
+ }
30
+ ]
12
31
  },
13
32
  {
14
- "command": "./hooks/session-start"
33
+ "matcher": "startup|resume|clear|compact",
34
+ "hooks": [
35
+ {
36
+ "type": "command",
37
+ "command": "./hooks/session-start"
38
+ }
39
+ ]
15
40
  }
16
41
  ]
17
42
  }
@@ -7,6 +7,14 @@ import { evaluateProtectedPathWriteGuard } from '../tooling/src/guards/protected
7
7
  try {
8
8
  const input = readFileSync(0, 'utf8');
9
9
  const payload = input.trim() ? JSON.parse(input) : {};
10
+
11
+ // Map Claude Code payload format to guard format (target_path)
12
+ // Claude may send file_path at top level or nested under tool_input
13
+ if (!payload.target_path) {
14
+ payload.target_path = payload.file_path
15
+ ?? payload.tool_input?.file_path;
16
+ }
17
+
10
18
  const decision = evaluateProtectedPathWriteGuard(payload);
11
19
 
12
20
  process.stdout.write(`${JSON.stringify({ guard_decision: decision })}\n`);
@@ -0,0 +1,45 @@
1
+ {
2
+ "large": [
3
+ "npm test",
4
+ "vitest",
5
+ "jest",
6
+ "pytest",
7
+ "npm run build",
8
+ "tsc --noEmit",
9
+ "npm ls",
10
+ "pip list",
11
+ "eslint .",
12
+ "prettier --check .",
13
+ "tail -f"
14
+ ],
15
+ "small": [
16
+ "git status",
17
+ "git log",
18
+ "git branch",
19
+ "git rev-parse",
20
+ "ls",
21
+ "pwd",
22
+ "mkdir",
23
+ "cp",
24
+ "mv",
25
+ "rm",
26
+ "wazir doctor",
27
+ "wazir index",
28
+ "wazir capture",
29
+ "wazir validate",
30
+ "which",
31
+ "echo"
32
+ ],
33
+ "ambiguous_heuristic": {
34
+ "pipe_detected": true,
35
+ "redirect_detected": true,
36
+ "verbose_binaries": [
37
+ "find",
38
+ "rg",
39
+ "grep",
40
+ "awk",
41
+ "sed",
42
+ "curl"
43
+ ]
44
+ }
45
+ }
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { existsSync, readFileSync } from 'node:fs';
2
+ import { existsSync, readFileSync, statSync } from 'node:fs';
3
+ import { execFileSync } from 'node:child_process';
3
4
  import { dirname, join } from 'node:path';
4
5
  import { fileURLToPath } from 'node:url';
5
6
 
@@ -26,6 +27,66 @@ if (existsSync(skillFile)) {
26
27
  );
27
28
  }
28
29
 
30
+ // ---------------------------------------------------------------------------
31
+ // Index freshness check (AC-D2.1 through AC-D2.3)
32
+ // ---------------------------------------------------------------------------
33
+
34
+ const FRESHNESS_THRESHOLD_MS = 3600 * 1000; // 1 hour
35
+
36
+ function refreshIndex() {
37
+ try {
38
+ const wazirPath = execFileSync('which', ['wazir'], { encoding: 'utf8', timeout: 5000 }).trim();
39
+ if (!wazirPath) return;
40
+
41
+ // Check index freshness via stats
42
+ let needsRefresh = true;
43
+ try {
44
+ const statsOut = execFileSync(wazirPath, ['index', 'stats', '--json'], {
45
+ encoding: 'utf8',
46
+ timeout: 10000,
47
+ cwd: projectRoot,
48
+ });
49
+ const stats = JSON.parse(statsOut);
50
+ if (stats.database_path) {
51
+ const mtime = statSync(stats.database_path).mtimeMs;
52
+ const age = Date.now() - mtime;
53
+ if (age < FRESHNESS_THRESHOLD_MS) {
54
+ needsRefresh = false;
55
+ }
56
+ }
57
+ } catch {
58
+ // No index or stats failed — needs build
59
+ }
60
+
61
+ if (!needsRefresh) return;
62
+
63
+ // Run refresh (or build if no index)
64
+ try {
65
+ execFileSync(wazirPath, ['index', 'refresh'], {
66
+ encoding: 'utf8',
67
+ timeout: 30000,
68
+ cwd: projectRoot,
69
+ });
70
+ process.stderr.write('[wazir:session-start] Index refreshed.\n');
71
+ } catch {
72
+ try {
73
+ execFileSync(wazirPath, ['index', 'build'], {
74
+ encoding: 'utf8',
75
+ timeout: 30000,
76
+ cwd: projectRoot,
77
+ });
78
+ process.stderr.write('[wazir:session-start] Index built.\n');
79
+ } catch (err) {
80
+ process.stderr.write(`[wazir:session-start] WARN: index refresh/build failed: ${err.message}\n`);
81
+ }
82
+ }
83
+ } catch {
84
+ process.stderr.write('[wazir:session-start] WARN: wazir CLI not found, skipping index refresh.\n');
85
+ }
86
+ }
87
+
88
+ refreshIndex();
89
+
29
90
  process.stdout.write(
30
91
  `<cli-bootstrap-guidance>\n` +
31
92
  `## CLI Bootstrap\n\n` +