@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,185 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ import { readYamlFile } from '../loaders.js';
5
+ import { getRunPaths, readPhaseExitEvents } from '../capture/store.js';
6
+
7
+ /**
8
+ * Validates that every enabled workflow has a phase_exit event
9
+ * in the run's events.ndjson before the run can be marked complete.
10
+ *
11
+ * If a run-config with workflow_policy exists, only workflows with
12
+ * enabled: true are checked. Otherwise falls back to the manifest list.
13
+ */
14
+ export function validateRunCompletion(runDir, manifestPath) {
15
+ const manifest = readYamlFile(manifestPath);
16
+ const declaredWorkflows = manifest.workflows ?? [];
17
+
18
+ if (declaredWorkflows.length === 0) {
19
+ return { complete: true, missing: [] };
20
+ }
21
+
22
+ // Filter to enabled workflows if run-config exists
23
+ const runConfigPath = path.join(runDir, 'run-config.yaml');
24
+ let enabledWorkflows = declaredWorkflows;
25
+ if (fs.existsSync(runConfigPath)) {
26
+ try {
27
+ const runConfig = readYamlFile(runConfigPath);
28
+ const policy = runConfig.workflow_policy;
29
+ if (policy && typeof policy === 'object') {
30
+ enabledWorkflows = declaredWorkflows.filter(w => {
31
+ const wPolicy = policy[w] ?? policy[w.replace(/_/g, '-')];
32
+ // If no policy entry, assume enabled; if entry exists, check enabled field
33
+ return wPolicy ? (wPolicy.enabled !== false) : true;
34
+ });
35
+ }
36
+ } catch {
37
+ // If run-config can't be read, fall back to full manifest list
38
+ }
39
+ }
40
+
41
+ const eventsPath = path.join(runDir, 'events.ndjson');
42
+ const completedWorkflows = new Set();
43
+
44
+ if (fs.existsSync(eventsPath)) {
45
+ const content = fs.readFileSync(eventsPath, 'utf8');
46
+ for (const line of content.split('\n')) {
47
+ const trimmed = line.trim();
48
+ if (!trimmed) continue;
49
+ try {
50
+ const event = JSON.parse(trimmed);
51
+ if (event.event === 'phase_exit' && event.status === 'completed' && event.phase) {
52
+ completedWorkflows.add(event.phase);
53
+ }
54
+ } catch {
55
+ // Skip malformed lines
56
+ }
57
+ }
58
+ }
59
+
60
+ const missing = enabledWorkflows.filter(w => !completedWorkflows.has(w));
61
+
62
+ return { complete: missing.length === 0, missing };
63
+ }
64
+
65
+ export function evaluateScopeCoverageGuard(payload) {
66
+ const { input_item_count: inputCount, plan_task_count: planCount, user_approved_reduction: userApproved } = payload;
67
+
68
+ const safeInputCount = inputCount ?? 0;
69
+ const safePlanCount = planCount ?? 0;
70
+
71
+ if (safeInputCount === 0) {
72
+ return {
73
+ allowed: true,
74
+ reason: 'No input items to check against.',
75
+ input_count: safeInputCount,
76
+ plan_count: safePlanCount,
77
+ };
78
+ }
79
+
80
+ if (safePlanCount >= safeInputCount) {
81
+ return {
82
+ allowed: true,
83
+ reason: `Plan covers all input items (${safePlanCount} tasks >= ${safeInputCount} items).`,
84
+ input_count: safeInputCount,
85
+ plan_count: safePlanCount,
86
+ };
87
+ }
88
+
89
+ if (userApproved === true) {
90
+ return {
91
+ allowed: true,
92
+ reason: `User explicitly approved scope reduction (${safePlanCount} tasks < ${safeInputCount} items).`,
93
+ input_count: safeInputCount,
94
+ plan_count: safePlanCount,
95
+ };
96
+ }
97
+
98
+ return {
99
+ allowed: false,
100
+ reason: `Scope reduction detected: plan has ${safePlanCount} tasks but input has ${safeInputCount} items. User approval required.`,
101
+ input_count: safeInputCount,
102
+ plan_count: safePlanCount,
103
+ };
104
+ }
105
+
106
+ export function evaluatePhasePrerequisiteGuard(payload) {
107
+ const { run_id: runId, phase, state_root: stateRoot, project_root: projectRoot } = payload;
108
+
109
+ if (!runId) {
110
+ throw new Error('run_id is required');
111
+ }
112
+
113
+ if (!phase) {
114
+ throw new Error('phase is required');
115
+ }
116
+
117
+ if (!stateRoot) {
118
+ throw new Error('state_root is required');
119
+ }
120
+
121
+ if (!projectRoot) {
122
+ throw new Error('project_root is required');
123
+ }
124
+
125
+ const runPaths = getRunPaths(stateRoot, runId);
126
+
127
+ if (!fs.existsSync(runPaths.statusPath)) {
128
+ throw new Error(`status.json not found for run ${runId}`);
129
+ }
130
+
131
+ const manifestPath = path.join(projectRoot, 'wazir.manifest.yaml');
132
+ const manifest = readYamlFile(manifestPath);
133
+ const prerequisites = manifest.phase_prerequisites?.[phase];
134
+
135
+ if (!prerequisites || (Object.keys(prerequisites).length === 0)) {
136
+ return {
137
+ allowed: true,
138
+ reason: `No prerequisites defined for phase ${phase}.`,
139
+ };
140
+ }
141
+
142
+ const requiredArtifacts = prerequisites.required_artifacts ?? [];
143
+ const requiredPhaseExits = prerequisites.required_phase_exits ?? [];
144
+
145
+ const missingArtifacts = [];
146
+ for (const artifact of requiredArtifacts) {
147
+ const artifactPath = path.join(runPaths.runRoot, artifact);
148
+ if (!fs.existsSync(artifactPath)) {
149
+ missingArtifacts.push(artifact);
150
+ }
151
+ }
152
+
153
+ const completedPhases = readPhaseExitEvents(runPaths);
154
+ const missingPhaseExits = [];
155
+ for (const requiredPhase of requiredPhaseExits) {
156
+ if (!completedPhases.includes(requiredPhase)) {
157
+ missingPhaseExits.push(requiredPhase);
158
+ }
159
+ }
160
+
161
+ // OR-logic for resumed runs: if all artifacts exist, pass even without phase_exit events.
162
+ // Artifacts are the hard evidence; phase_exits are supplementary.
163
+ // But if artifacts are missing, phase_exits alone are not sufficient.
164
+ if (missingArtifacts.length === 0) {
165
+ return {
166
+ allowed: true,
167
+ reason: `All prerequisite artifacts present for phase ${phase}.`,
168
+ };
169
+ }
170
+
171
+ const reasons = [];
172
+ if (missingArtifacts.length > 0) {
173
+ reasons.push(`Missing artifacts: ${missingArtifacts.join(', ')}`);
174
+ }
175
+ if (missingPhaseExits.length > 0) {
176
+ reasons.push(`Missing phase exits: ${missingPhaseExits.join(', ')}`);
177
+ }
178
+
179
+ return {
180
+ allowed: false,
181
+ reason: reasons.join('. '),
182
+ missing_artifacts: missingArtifacts.length > 0 ? missingArtifacts : undefined,
183
+ missing_phase_exits: missingPhaseExits.length > 0 ? missingPhaseExits : undefined,
184
+ };
185
+ }
@@ -0,0 +1,69 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+
4
+ function firstToken(cmd) {
5
+ return cmd.split(/\s+/)[0] || '';
6
+ }
7
+
8
+ function hasPipe(cmd) {
9
+ return /(?<![\\])\|/.test(cmd);
10
+ }
11
+
12
+ function hasRedirect(cmd) {
13
+ return /(?<![\\])[>]/.test(cmd);
14
+ }
15
+
16
+ export function loadRoutingMatrix(projectRoot) {
17
+ const matrixPath = join(projectRoot, 'hooks', 'routing-matrix.json');
18
+ return JSON.parse(readFileSync(matrixPath, 'utf8'));
19
+ }
20
+
21
+ export function classifyCommand(cmd, matrix) {
22
+ const command = (cmd || '').trim();
23
+
24
+ if (!matrix) {
25
+ return { route: 'small', reason: 'matrix missing — safe fallback' };
26
+ }
27
+
28
+ // 1. Explicit context-mode marker always wins
29
+ if (command.includes('# wazir:context-mode')) {
30
+ return { route: 'large', reason: 'explicit context-mode marker' };
31
+ }
32
+
33
+ // 2. Check large patterns FIRST — large commands are never downgraded
34
+ for (const pattern of matrix.large) {
35
+ if (command === pattern || command.startsWith(pattern + ' ') || command.startsWith(pattern + '\t')) {
36
+ return { route: 'large', reason: `matched large pattern: ${pattern}` };
37
+ }
38
+ }
39
+
40
+ // 3. Passthrough marker — only honoured when command is NOT large
41
+ if (command.includes('# wazir:passthrough')) {
42
+ return { route: 'small', reason: 'explicit passthrough marker' };
43
+ }
44
+
45
+ // 4. Check small patterns
46
+ for (const pattern of matrix.small) {
47
+ if (command === pattern || command.startsWith(pattern + ' ') || command.startsWith(pattern + '\t')) {
48
+ return { route: 'small', reason: `matched small pattern: ${pattern}` };
49
+ }
50
+ }
51
+
52
+ // 5. Ambiguous heuristics
53
+ const heuristic = matrix.ambiguous_heuristic || {};
54
+
55
+ if (heuristic.pipe_detected && hasPipe(command)) {
56
+ return { route: 'ambiguous', reason: 'pipe detected' };
57
+ }
58
+ if (heuristic.redirect_detected && hasRedirect(command)) {
59
+ return { route: 'ambiguous', reason: 'redirect detected' };
60
+ }
61
+
62
+ const bin = firstToken(command);
63
+ if (Array.isArray(heuristic.verbose_binaries) && heuristic.verbose_binaries.includes(bin)) {
64
+ return { route: 'ambiguous', reason: `verbose binary: ${bin}` };
65
+ }
66
+
67
+ // Default: unknown commands pass through
68
+ return { route: 'small', reason: 'no pattern matched — default passthrough' };
69
+ }
@@ -0,0 +1,258 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+
5
+ /**
6
+ * Detect which AI host CLI is running this process.
7
+ * Checks environment variables, process ancestry, and known file markers.
8
+ *
9
+ * @returns {{ host: string, confidence: string, signals: string[] }}
10
+ */
11
+ export function detectHost() {
12
+ const signals = [];
13
+
14
+ // Claude Code detection
15
+ if (process.env.CLAUDE_CODE || process.env.CLAUDE_CODE_ENTRYPOINT) {
16
+ signals.push('CLAUDE_CODE env var');
17
+ return { host: 'claude', confidence: 'high', signals };
18
+ }
19
+ if (fs.existsSync(path.join(os.homedir(), '.claude'))) {
20
+ signals.push('~/.claude directory exists');
21
+ }
22
+
23
+ // Codex detection
24
+ if (process.env.CODEX_CLI || process.env.OPENAI_API_KEY) {
25
+ signals.push('CODEX_CLI or OPENAI_API_KEY env var');
26
+ }
27
+ if (process.env.CODEX_SANDBOX_MODE) {
28
+ signals.push('CODEX_SANDBOX_MODE env var');
29
+ return { host: 'codex', confidence: 'high', signals };
30
+ }
31
+
32
+ // Gemini detection
33
+ if (process.env.GEMINI_API_KEY || process.env.GOOGLE_AI_API_KEY) {
34
+ signals.push('Gemini API key env var');
35
+ }
36
+ if (process.env.GEMINI_CLI) {
37
+ signals.push('GEMINI_CLI env var');
38
+ return { host: 'gemini', confidence: 'high', signals };
39
+ }
40
+
41
+ // Cursor detection
42
+ if (process.env.CURSOR_SESSION || process.env.CURSOR_TRACE_ID) {
43
+ signals.push('Cursor session env var');
44
+ return { host: 'cursor', confidence: 'high', signals };
45
+ }
46
+
47
+ // Fallback: check for marker files
48
+ const cwd = process.cwd();
49
+ if (fs.existsSync(path.join(cwd, '.claude', 'settings.json'))) {
50
+ signals.push('.claude/settings.json exists in project');
51
+ return { host: 'claude', confidence: 'medium', signals };
52
+ }
53
+ if (fs.existsSync(path.join(cwd, 'AGENTS.md'))) {
54
+ signals.push('AGENTS.md exists (Codex marker)');
55
+ return { host: 'codex', confidence: 'medium', signals };
56
+ }
57
+ if (fs.existsSync(path.join(cwd, 'GEMINI.md'))) {
58
+ signals.push('GEMINI.md exists (Gemini marker)');
59
+ return { host: 'gemini', confidence: 'medium', signals };
60
+ }
61
+ if (fs.existsSync(path.join(cwd, '.cursorrules'))) {
62
+ signals.push('.cursorrules exists (Cursor marker)');
63
+ return { host: 'cursor', confidence: 'medium', signals };
64
+ }
65
+
66
+ // Default: assume Claude Code (most common)
67
+ if (signals.includes('~/.claude directory exists')) {
68
+ return { host: 'claude', confidence: 'low', signals };
69
+ }
70
+
71
+ return { host: 'claude', confidence: 'low', signals: ['no host detected, defaulting to claude'] };
72
+ }
73
+
74
+ /**
75
+ * Auto-detect project stack from package files, config, and structure.
76
+ *
77
+ * @param {string} projectRoot
78
+ * @returns {{ language: string, framework: string|null, stack: string[] }}
79
+ */
80
+ export function detectProjectStack(projectRoot) {
81
+ const stack = [];
82
+ let language = 'unknown';
83
+ let framework = null;
84
+
85
+ // Node.js / JavaScript
86
+ if (fs.existsSync(path.join(projectRoot, 'package.json'))) {
87
+ language = 'javascript';
88
+ stack.push('node');
89
+
90
+ try {
91
+ const pkg = JSON.parse(fs.readFileSync(path.join(projectRoot, 'package.json'), 'utf8'));
92
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
93
+
94
+ if (allDeps.next) { framework = 'nextjs'; stack.push('next'); }
95
+ else if (allDeps.react) { framework = 'react'; stack.push('react'); }
96
+ else if (allDeps.vue) { framework = 'vue'; stack.push('vue'); }
97
+ else if (allDeps.angular || allDeps['@angular/core']) { framework = 'angular'; stack.push('angular'); }
98
+ else if (allDeps.express || allDeps.fastify || allDeps.koa) { framework = 'node-api'; stack.push('node-api'); }
99
+
100
+ if (allDeps.typescript) { language = 'typescript'; stack.push('typescript'); }
101
+ } catch { /* ignore parse errors */ }
102
+ }
103
+
104
+ // Python
105
+ if (fs.existsSync(path.join(projectRoot, 'pyproject.toml')) ||
106
+ fs.existsSync(path.join(projectRoot, 'requirements.txt')) ||
107
+ fs.existsSync(path.join(projectRoot, 'setup.py'))) {
108
+ language = 'python';
109
+ stack.push('python');
110
+
111
+ if (fs.existsSync(path.join(projectRoot, 'manage.py'))) { framework = 'django'; stack.push('django'); }
112
+ }
113
+
114
+ // Go
115
+ if (fs.existsSync(path.join(projectRoot, 'go.mod'))) {
116
+ language = 'go';
117
+ stack.push('go');
118
+ }
119
+
120
+ // Rust
121
+ if (fs.existsSync(path.join(projectRoot, 'Cargo.toml'))) {
122
+ language = 'rust';
123
+ stack.push('rust');
124
+ }
125
+
126
+ // Flutter / Dart
127
+ if (fs.existsSync(path.join(projectRoot, 'pubspec.yaml'))) {
128
+ language = 'dart';
129
+ framework = 'flutter';
130
+ stack.push('dart', 'flutter');
131
+ }
132
+
133
+ // Java
134
+ if (fs.existsSync(path.join(projectRoot, 'pom.xml')) ||
135
+ fs.existsSync(path.join(projectRoot, 'build.gradle'))) {
136
+ language = 'java';
137
+ stack.push('java');
138
+ }
139
+
140
+ return { language, framework, stack };
141
+ }
142
+
143
+ /**
144
+ * Infer intent from request text using keyword matching.
145
+ *
146
+ * @param {string} requestText
147
+ * @returns {string} One of: bugfix, refactor, docs, spike, feature
148
+ */
149
+ export function inferIntent(requestText) {
150
+ if (!requestText) return 'feature';
151
+ const lower = requestText.toLowerCase();
152
+
153
+ const patterns = [
154
+ { keywords: ['fix', 'bug', 'broken', 'crash', 'error', 'issue', 'wrong'], intent: 'bugfix' },
155
+ { keywords: ['refactor', 'clean', 'restructure', 'reorganize', 'rename', 'simplify'], intent: 'refactor' },
156
+ { keywords: ['doc', 'document', 'readme', 'guide', 'explain'], intent: 'docs' },
157
+ { keywords: ['research', 'spike', 'explore', 'investigate', 'prototype'], intent: 'spike' },
158
+ ];
159
+
160
+ for (const { keywords, intent } of patterns) {
161
+ if (keywords.some((kw) => lower.includes(kw))) return intent;
162
+ }
163
+
164
+ return 'feature';
165
+ }
166
+
167
+ /**
168
+ * Parse inline depth modifiers from request text.
169
+ *
170
+ * @param {string} requestText
171
+ * @returns {{ depth: string, cleanedText: string }}
172
+ */
173
+ export function parseDepthModifier(requestText) {
174
+ if (!requestText) return { depth: 'standard', cleanedText: '' };
175
+
176
+ const match = requestText.match(/^\s*(quick|deep)\s+/i);
177
+ if (match) {
178
+ return {
179
+ depth: match[1].toLowerCase(),
180
+ cleanedText: requestText.slice(match[0].length),
181
+ };
182
+ }
183
+
184
+ return { depth: 'standard', cleanedText: requestText };
185
+ }
186
+
187
+ /**
188
+ * Run zero-config auto-initialization.
189
+ * Creates .wazir directories, detects host, scans project, writes config.
190
+ * No interactive prompts — everything is inferred.
191
+ *
192
+ * @param {string} projectRoot
193
+ * @param {object} [opts]
194
+ * @param {object} [opts.context] - Runtime context (availableTools, etc.)
195
+ * @param {boolean} [opts.force] - Force reinitialize even if config exists
196
+ * @returns {{ config: object, host: object, stack: object, filesCreated: string[] }}
197
+ */
198
+ export function autoInit(projectRoot, opts = {}) {
199
+ const wazirDir = path.join(projectRoot, '.wazir');
200
+ const configPath = path.join(wazirDir, 'state', 'config.json');
201
+
202
+ // If already initialized and not forced, return existing config
203
+ if (fs.existsSync(configPath) && !opts.force) {
204
+ const existing = JSON.parse(fs.readFileSync(configPath, 'utf8'));
205
+ return {
206
+ config: existing,
207
+ host: detectHost(),
208
+ stack: detectProjectStack(projectRoot),
209
+ filesCreated: [],
210
+ alreadyInitialized: true,
211
+ };
212
+ }
213
+
214
+ // Create directories
215
+ for (const dir of ['input', 'state', 'runs']) {
216
+ fs.mkdirSync(path.join(wazirDir, dir), { recursive: true });
217
+ }
218
+
219
+ const host = detectHost();
220
+ const stack = detectProjectStack(projectRoot);
221
+
222
+ // Detect context-mode MCP
223
+ const contextMode = { enabled: false, has_execute_file: false };
224
+ if (opts.context?.availableTools) {
225
+ const prefix = 'mcp__plugin_context-mode_context-mode__';
226
+ const hasExecute = opts.context.availableTools.includes(`${prefix}execute`);
227
+ const hasFetchAndIndex = opts.context.availableTools.includes(`${prefix}fetch_and_index`);
228
+ const hasSearch = opts.context.availableTools.includes(`${prefix}search`);
229
+ const hasExecuteFile = opts.context.availableTools.includes(`${prefix}execute_file`);
230
+ if (hasExecute && hasFetchAndIndex && hasSearch) {
231
+ contextMode.enabled = true;
232
+ contextMode.has_execute_file = hasExecuteFile;
233
+ }
234
+ } else {
235
+ const pluginDir = path.join(os.homedir(), '.claude', 'plugins', 'cache', 'context-mode');
236
+ if (fs.existsSync(pluginDir)) {
237
+ contextMode.enabled = true;
238
+ contextMode.has_execute_file = true;
239
+ }
240
+ }
241
+
242
+ // Sensible defaults — no questions
243
+ const config = {
244
+ model_mode: 'claude-only',
245
+ default_depth: 'standard',
246
+ default_intent: 'feature',
247
+ context_mode: contextMode,
248
+ detected_host: host.host,
249
+ detected_stack: stack,
250
+ auto_initialized: true,
251
+ };
252
+
253
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
254
+
255
+ const filesCreated = ['.wazir/input/', '.wazir/state/', '.wazir/runs/', '.wazir/state/config.json'];
256
+
257
+ return { config, host, stack, filesCreated, alreadyInitialized: false };
258
+ }