@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.
- package/CHANGELOG.md +74 -10
- package/README.md +15 -15
- package/assets/demo.cast +47 -0
- package/assets/demo.gif +0 -0
- package/docs/anti-patterns/AP-23-skipping-enabled-workflows.md +28 -0
- package/docs/anti-patterns/AP-24-clarifier-deciding-scope.md +34 -0
- 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/hooks/pre-compact-summary.md +1 -1
- 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/hooks.md +1 -0
- package/docs/reference/launch-checklist.md +4 -4
- package/docs/reference/review-loop-pattern.md +119 -9
- 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 +214 -1
- package/exports/hosts/claude/.claude/commands/plan-review.md +3 -1
- package/exports/hosts/claude/.claude/commands/verify.md +30 -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 +6 -4
- package/exports/hosts/claude/host-package.json +3 -1
- package/exports/hosts/codex/AGENTS.md +1 -1
- package/exports/hosts/codex/export.manifest.json +6 -4
- 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 +6 -4
- package/exports/hosts/cursor/host-package.json +3 -1
- package/exports/hosts/gemini/GEMINI.md +1 -1
- package/exports/hosts/gemini/export.manifest.json +6 -4
- 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 +937 -134
- package/package.json +2 -4
- package/schemas/hook.schema.json +2 -1
- package/schemas/phase-report.schema.json +89 -0
- package/schemas/usage.schema.json +25 -1
- package/schemas/wazir-manifest.schema.json +19 -0
- package/skills/brainstorming/SKILL.md +32 -157
- package/skills/clarifier/SKILL.md +289 -111
- 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 +139 -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 +72 -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 +369 -24
- package/skills/run-audit/SKILL.md +13 -0
- package/skills/scan-project/SKILL.md +13 -0
- package/skills/self-audit/SKILL.md +217 -16
- package/skills/skill-research/SKILL.md +188 -0
- 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 +54 -3
- package/skills/wazir/SKILL.md +464 -381
- 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 +41 -2
- package/tooling/src/capture/run-config.js +3 -1
- package/tooling/src/capture/store.js +56 -0
- package/tooling/src/capture/usage.js +106 -0
- package/tooling/src/capture/user-input.js +66 -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/security-sensitivity.js +69 -0
- package/tooling/src/checks/skills.js +111 -0
- package/tooling/src/cli.js +31 -20
- 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 +185 -0
- package/tooling/src/hooks/routing-logic.js +69 -0
- package/tooling/src/init/auto-detect.js +258 -0
- package/tooling/src/init/command.js +38 -170
- 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 +58 -1
- package/tooling/src/verify/proof-collector.js +299 -0
- package/wazir.manifest.yaml +26 -14
- package/workflows/plan-review.md +3 -1
- 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
|
-
"
|
|
3
|
+
"PreToolUse": [
|
|
4
4
|
{
|
|
5
|
-
"
|
|
6
|
-
"
|
|
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
|
-
"
|
|
23
|
+
"SessionStart": [
|
|
10
24
|
{
|
|
11
|
-
"
|
|
25
|
+
"hooks": [
|
|
26
|
+
{
|
|
27
|
+
"type": "command",
|
|
28
|
+
"command": "./hooks/loop-cap-guard"
|
|
29
|
+
}
|
|
30
|
+
]
|
|
12
31
|
},
|
|
13
32
|
{
|
|
14
|
-
"
|
|
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
|
+
}
|
package/hooks/session-start
CHANGED
|
@@ -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` +
|