@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
|
@@ -0,0 +1,260 @@
|
|
|
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
|
+
team_mode: 'sequential',
|
|
248
|
+
parallel_backend: 'none',
|
|
249
|
+
context_mode: contextMode,
|
|
250
|
+
detected_host: host.host,
|
|
251
|
+
detected_stack: stack,
|
|
252
|
+
auto_initialized: true,
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
256
|
+
|
|
257
|
+
const filesCreated = ['.wazir/input/', '.wazir/state/', '.wazir/runs/', '.wazir/state/config.json'];
|
|
258
|
+
|
|
259
|
+
return { config, host, stack, filesCreated, alreadyInitialized: false };
|
|
260
|
+
}
|
|
@@ -1,117 +1,136 @@
|
|
|
1
|
-
import { execFileSync } from 'node:child_process';
|
|
2
1
|
import fs from 'node:fs';
|
|
3
2
|
import path from 'node:path';
|
|
4
|
-
import { select } from '@inquirer/prompts';
|
|
5
3
|
|
|
4
|
+
import { autoInit, detectHost, detectProjectStack } from './auto-detect.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* wazir init [--auto|--interactive|--force]
|
|
8
|
+
*
|
|
9
|
+
* Default: --auto (zero-config, no prompts, infer everything)
|
|
10
|
+
* --interactive: legacy mode with @inquirer/prompts (may fail in non-TTY)
|
|
11
|
+
* --force: reinitialize even if already initialized
|
|
12
|
+
*/
|
|
6
13
|
export async function runInitCommand(parsed, context = {}) {
|
|
7
14
|
const cwd = context.cwd ?? process.cwd();
|
|
8
15
|
const wazirDir = path.join(cwd, '.wazir');
|
|
9
16
|
const configPath = path.join(wazirDir, 'state', 'config.json');
|
|
17
|
+
const isForce = parsed.args.includes('--force');
|
|
18
|
+
const isInteractive = parsed.args.includes('--interactive');
|
|
10
19
|
|
|
11
|
-
|
|
20
|
+
// Already initialized check
|
|
21
|
+
if (fs.existsSync(configPath) && !isForce) {
|
|
12
22
|
return {
|
|
13
23
|
exitCode: 1,
|
|
14
24
|
stderr: 'Pipeline already initialized. Use --force to reinitialize.\n',
|
|
15
25
|
};
|
|
16
26
|
}
|
|
17
27
|
|
|
28
|
+
// Interactive mode — legacy prompts (may fail in non-TTY environments like Claude Code)
|
|
29
|
+
if (isInteractive) {
|
|
30
|
+
return runInteractiveInit(parsed, context);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Default: auto mode — zero-config
|
|
34
|
+
try {
|
|
35
|
+
const result = autoInit(cwd, { context, force: isForce });
|
|
36
|
+
|
|
37
|
+
if (result.alreadyInitialized && !isForce) {
|
|
38
|
+
return {
|
|
39
|
+
exitCode: 0,
|
|
40
|
+
stdout: `Already initialized. Host: ${result.host.host}, Stack: ${result.stack.language}\n`,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Auto-export for detected host
|
|
45
|
+
let exportNote = '';
|
|
46
|
+
try {
|
|
47
|
+
const { buildHostExports } = await import('../export/compiler.js');
|
|
48
|
+
buildHostExports(cwd);
|
|
49
|
+
exportNote = ` Exports: generated for ${result.host.host}\n`;
|
|
50
|
+
} catch {
|
|
51
|
+
exportNote = ' Exports: skipped (run `wazir export build` manually)\n';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const lines = [
|
|
55
|
+
'',
|
|
56
|
+
'Wazir initialized (zero-config).',
|
|
57
|
+
'',
|
|
58
|
+
` Host: ${result.host.host} (${result.host.confidence} confidence)`,
|
|
59
|
+
` Stack: ${result.stack.language}${result.stack.framework ? ` / ${result.stack.framework}` : ''}`,
|
|
60
|
+
` Mode: ${result.config.model_mode}`,
|
|
61
|
+
` Depth: ${result.config.default_depth}`,
|
|
62
|
+
exportNote,
|
|
63
|
+
'Files created:',
|
|
64
|
+
...result.filesCreated.map((f) => ` - ${f}`),
|
|
65
|
+
'',
|
|
66
|
+
'Next: /wazir <what you want to build>',
|
|
67
|
+
'',
|
|
68
|
+
'Power users: `wazir init --interactive` for manual config.',
|
|
69
|
+
'Override: `wazir config set model_mode multi-tool`',
|
|
70
|
+
'',
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
return { exitCode: 0, stdout: lines.join('\n') };
|
|
74
|
+
} catch (error) {
|
|
75
|
+
return { exitCode: 1, stderr: `Auto-init failed: ${error.message}\n` };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Legacy interactive init with @inquirer/prompts.
|
|
81
|
+
* Kept for power users who want manual control.
|
|
82
|
+
* Will fail in non-TTY environments (Claude Code Bash tool).
|
|
83
|
+
*/
|
|
84
|
+
async function runInteractiveInit(parsed, context = {}) {
|
|
85
|
+
const cwd = context.cwd ?? process.cwd();
|
|
86
|
+
const wazirDir = path.join(cwd, '.wazir');
|
|
87
|
+
const configPath = path.join(wazirDir, 'state', 'config.json');
|
|
88
|
+
|
|
18
89
|
try {
|
|
19
|
-
|
|
90
|
+
const { select } = await import('@inquirer/prompts');
|
|
91
|
+
|
|
20
92
|
for (const dir of ['input', 'state', 'runs']) {
|
|
21
93
|
fs.mkdirSync(path.join(wazirDir, dir), { recursive: true });
|
|
22
94
|
}
|
|
23
95
|
|
|
24
|
-
// Pipeline mode
|
|
25
96
|
const modelMode = await select({
|
|
26
97
|
message: 'How should Wazir run in this project?',
|
|
27
98
|
choices: [
|
|
28
|
-
{ name: 'Single model (Recommended)
|
|
29
|
-
{ name: 'Multi-model
|
|
30
|
-
{ name: 'Multi-tool
|
|
99
|
+
{ name: 'Single model (Recommended)', value: 'claude-only' },
|
|
100
|
+
{ name: 'Multi-model (Haiku/Sonnet/Opus routing)', value: 'multi-model' },
|
|
101
|
+
{ name: 'Multi-tool (current model + external reviewers)', value: 'multi-tool' },
|
|
31
102
|
],
|
|
32
103
|
default: 'claude-only',
|
|
33
104
|
});
|
|
34
105
|
|
|
35
|
-
// Multi-tool tools (conditional)
|
|
36
106
|
let multiToolTools = [];
|
|
37
107
|
if (modelMode === 'multi-tool') {
|
|
38
108
|
const toolChoice = await select({
|
|
39
|
-
message: 'Which external tools
|
|
109
|
+
message: 'Which external tools for reviews?',
|
|
40
110
|
choices: [
|
|
41
|
-
{ name: 'Codex
|
|
42
|
-
{ name: 'Gemini
|
|
43
|
-
{ name: 'Both
|
|
111
|
+
{ name: 'Codex', value: 'codex' },
|
|
112
|
+
{ name: 'Gemini', value: 'gemini' },
|
|
113
|
+
{ name: 'Both', value: 'both' },
|
|
44
114
|
],
|
|
45
115
|
});
|
|
46
116
|
multiToolTools = toolChoice === 'both' ? ['codex', 'gemini'] : [toolChoice];
|
|
47
117
|
}
|
|
48
118
|
|
|
49
|
-
// Codex model (conditional)
|
|
50
119
|
let codexModel = null;
|
|
51
120
|
if (multiToolTools.includes('codex')) {
|
|
52
121
|
codexModel = await select({
|
|
53
|
-
message: '
|
|
122
|
+
message: 'Codex model?',
|
|
54
123
|
choices: [
|
|
55
|
-
{ name: 'gpt-5.3-codex-spark (Recommended)
|
|
56
|
-
{ name: 'gpt-5.4
|
|
124
|
+
{ name: 'gpt-5.3-codex-spark (Recommended)', value: 'gpt-5.3-codex-spark' },
|
|
125
|
+
{ name: 'gpt-5.4', value: 'gpt-5.4' },
|
|
57
126
|
],
|
|
58
127
|
default: 'gpt-5.3-codex-spark',
|
|
59
128
|
});
|
|
60
129
|
}
|
|
61
130
|
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
message: 'What default depth should runs use?',
|
|
65
|
-
choices: [
|
|
66
|
-
{ name: 'Quick — minimal research, single-pass review', value: 'quick' },
|
|
67
|
-
{ name: 'Standard (Recommended) — balanced research, multi-pass hardening', value: 'standard' },
|
|
68
|
-
{ name: 'Deep — extended research, strict review thresholds', value: 'deep' },
|
|
69
|
-
],
|
|
70
|
-
default: 'standard',
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
// Default intent
|
|
74
|
-
const defaultIntent = await select({
|
|
75
|
-
message: 'What kind of work does this project mostly involve?',
|
|
76
|
-
choices: [
|
|
77
|
-
{ name: 'Feature (Recommended) — new functionality or enhancement', value: 'feature' },
|
|
78
|
-
{ name: 'Bugfix — fix broken behavior', value: 'bugfix' },
|
|
79
|
-
{ name: 'Refactor — restructure without changing behavior', value: 'refactor' },
|
|
80
|
-
{ name: 'Docs — documentation only', value: 'docs' },
|
|
81
|
-
{ name: 'Spike — research and exploration', value: 'spike' },
|
|
82
|
-
],
|
|
83
|
-
default: 'feature',
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
// Agent Teams (conditional)
|
|
87
|
-
let teamMode = 'sequential';
|
|
88
|
-
let parallelBackend = 'none';
|
|
131
|
+
const host = detectHost();
|
|
132
|
+
const stack = detectProjectStack(cwd);
|
|
89
133
|
|
|
90
|
-
const depthAllows = defaultDepth === 'standard' || defaultDepth === 'deep';
|
|
91
|
-
const intentAllows = defaultIntent === 'feature' || defaultIntent === 'refactor';
|
|
92
|
-
|
|
93
|
-
if (depthAllows && intentAllows) {
|
|
94
|
-
const useTeams = await select({
|
|
95
|
-
message: 'Would you like to use Agent Teams for parallel execution?',
|
|
96
|
-
choices: [
|
|
97
|
-
{ name: 'No (Recommended) — sequential, predictable, lower cost', value: 'sequential' },
|
|
98
|
-
{ name: 'Yes — parallel teammates, faster but experimental (Opus only)', value: 'parallel' },
|
|
99
|
-
],
|
|
100
|
-
default: 'sequential',
|
|
101
|
-
});
|
|
102
|
-
teamMode = useTeams;
|
|
103
|
-
parallelBackend = useTeams === 'parallel' ? 'claude_teams' : 'none';
|
|
104
|
-
|
|
105
|
-
if (teamMode === 'parallel') {
|
|
106
|
-
try {
|
|
107
|
-
execFileSync('claude', ['config', 'set', 'env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS', '1'], { stdio: 'pipe' });
|
|
108
|
-
} catch {
|
|
109
|
-
// claude CLI not available — user will need to set it manually
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Write config
|
|
115
134
|
const config = {
|
|
116
135
|
model_mode: modelMode,
|
|
117
136
|
...(modelMode === 'multi-tool' && {
|
|
@@ -120,77 +139,18 @@ export async function runInitCommand(parsed, context = {}) {
|
|
|
120
139
|
...(codexModel && { codex: { model: codexModel } }),
|
|
121
140
|
},
|
|
122
141
|
}),
|
|
123
|
-
default_depth:
|
|
124
|
-
default_intent:
|
|
125
|
-
team_mode:
|
|
126
|
-
parallel_backend:
|
|
142
|
+
default_depth: 'standard',
|
|
143
|
+
default_intent: 'feature',
|
|
144
|
+
team_mode: 'sequential',
|
|
145
|
+
parallel_backend: 'none',
|
|
146
|
+
detected_host: host.host,
|
|
147
|
+
detected_stack: stack,
|
|
127
148
|
};
|
|
128
149
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
129
150
|
|
|
130
|
-
// Runtime-specific setup
|
|
131
|
-
const filesCreated = ['.wazir/input/', '.wazir/state/', '.wazir/runs/', '.wazir/state/config.json'];
|
|
132
|
-
|
|
133
|
-
if (multiToolTools.includes('codex')) {
|
|
134
|
-
const content = [
|
|
135
|
-
'# Wazir Pipeline',
|
|
136
|
-
'',
|
|
137
|
-
'Agent protocols are at `~/.claude/agents/` (global).',
|
|
138
|
-
'',
|
|
139
|
-
'## Running the Pipeline',
|
|
140
|
-
'1. Clarifier: read and follow `~/.claude/agents/clarifier.md` — tasks are in `.wazir/input/`',
|
|
141
|
-
'2. Orchestrator: read and follow `~/.claude/agents/orchestrator.md` — start from task 1',
|
|
142
|
-
'3. Opus Reviewer: read and follow `~/.claude/agents/opus-reviewer.md` — run all phases',
|
|
143
|
-
'',
|
|
144
|
-
'## Review Mode',
|
|
145
|
-
'This project uses Codex as a secondary reviewer. Review artifacts are in `.wazir/reviews/`.',
|
|
146
|
-
'',
|
|
147
|
-
].join('\n');
|
|
148
|
-
fs.writeFileSync(path.join(cwd, 'AGENTS.md'), content);
|
|
149
|
-
filesCreated.push('AGENTS.md');
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
if (multiToolTools.includes('gemini')) {
|
|
153
|
-
const content = [
|
|
154
|
-
'# Wazir Pipeline',
|
|
155
|
-
'',
|
|
156
|
-
'Agent protocols are at `~/.claude/agents/` (global).',
|
|
157
|
-
'',
|
|
158
|
-
'## Running the Pipeline',
|
|
159
|
-
'1. Clarifier: read and follow `~/.claude/agents/clarifier.md` — tasks are in `.wazir/input/`',
|
|
160
|
-
'2. Orchestrator: read and follow `~/.claude/agents/orchestrator.md` — start from task 1',
|
|
161
|
-
'3. Opus Reviewer: read and follow `~/.claude/agents/opus-reviewer.md` — run all phases',
|
|
162
|
-
'',
|
|
163
|
-
'## Review Mode',
|
|
164
|
-
'This project uses Gemini as a secondary reviewer. Review artifacts are in `.wazir/reviews/`.',
|
|
165
|
-
'',
|
|
166
|
-
].join('\n');
|
|
167
|
-
fs.writeFileSync(path.join(cwd, 'GEMINI.md'), content);
|
|
168
|
-
filesCreated.push('GEMINI.md');
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const lines = [
|
|
172
|
-
'',
|
|
173
|
-
'\u2705 Pipeline initialized!',
|
|
174
|
-
'',
|
|
175
|
-
` Mode: ${modelMode}`,
|
|
176
|
-
` Depth: ${defaultDepth}`,
|
|
177
|
-
` Intent: ${defaultIntent}`,
|
|
178
|
-
` Teams: ${teamMode}`,
|
|
179
|
-
'',
|
|
180
|
-
'Files created:',
|
|
181
|
-
...filesCreated.map((f) => ` - ${f}`),
|
|
182
|
-
'',
|
|
183
|
-
'You can now use:',
|
|
184
|
-
' /wazir <your request> \u2014 Run the full pipeline',
|
|
185
|
-
' /clarifier \u2014 Research, clarify, plan',
|
|
186
|
-
' /executor \u2014 Autonomous execution',
|
|
187
|
-
' /reviewer \u2014 Final review and scoring',
|
|
188
|
-
'',
|
|
189
|
-
];
|
|
190
|
-
|
|
191
151
|
return {
|
|
192
152
|
exitCode: 0,
|
|
193
|
-
stdout:
|
|
153
|
+
stdout: `\nInitialized (${modelMode}). Host: ${host.host}. Next: /wazir <request>\n`,
|
|
194
154
|
};
|
|
195
155
|
} catch (error) {
|
|
196
156
|
if (error.name === 'ExitPromptError') {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { readdirSync, existsSync } from 'node:fs';
|
|
2
|
+
import { join, resolve, basename } from 'node:path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Scan input directories for briefing materials.
|
|
6
|
+
* Globs input/*.md and .wazir/input/*.md (flat, not recursive).
|
|
7
|
+
* Excludes README.md.
|
|
8
|
+
*
|
|
9
|
+
* @param {string} projectRoot - Absolute path to project root
|
|
10
|
+
* @returns {Array<{path: string, auto: boolean}>} Found files with auto flag
|
|
11
|
+
*/
|
|
12
|
+
export function scanInputDirectories(projectRoot) {
|
|
13
|
+
const root = resolve(projectRoot);
|
|
14
|
+
const dirs = [
|
|
15
|
+
join(root, 'input'),
|
|
16
|
+
join(root, '.wazir', 'input'),
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
const results = [];
|
|
20
|
+
|
|
21
|
+
for (const dir of dirs) {
|
|
22
|
+
if (!existsSync(dir)) continue;
|
|
23
|
+
|
|
24
|
+
let entries;
|
|
25
|
+
try {
|
|
26
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
27
|
+
} catch {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
for (const entry of entries) {
|
|
32
|
+
if (!entry.isFile()) continue;
|
|
33
|
+
if (!entry.name.endsWith('.md')) continue;
|
|
34
|
+
if (basename(entry.name).toLowerCase() === 'readme.md') continue;
|
|
35
|
+
|
|
36
|
+
results.push({ path: join(dir, entry.name), auto: false });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Single file auto-use
|
|
41
|
+
if (results.length === 1) {
|
|
42
|
+
results[0].auto = true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return results;
|
|
46
|
+
}
|