@wazir-dev/cli 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +73 -4
- package/README.md +6 -6
- package/docs/concepts/architecture.md +1 -1
- package/docs/concepts/roles-and-workflows.md +2 -0
- package/docs/concepts/why-wazir.md +59 -0
- package/docs/decisions/2026-03-19-deferred-items.md +564 -0
- package/docs/decisions/2026-03-19-enhancement-decisions.md +300 -0
- package/docs/readmes/INDEX.md +21 -5
- package/docs/readmes/features/expertise/README.md +2 -2
- package/docs/readmes/features/exports/README.md +2 -2
- package/docs/readmes/features/schemas/README.md +3 -0
- package/docs/readmes/features/skills/README.md +17 -0
- package/docs/readmes/features/skills/clarifier.md +5 -0
- package/docs/readmes/features/skills/claude-cli.md +5 -0
- package/docs/readmes/features/skills/codex-cli.md +5 -0
- package/docs/readmes/features/skills/dispatching-parallel-agents.md +5 -0
- package/docs/readmes/features/skills/executing-plans.md +5 -0
- package/docs/readmes/features/skills/executor.md +5 -0
- package/docs/readmes/features/skills/finishing-a-development-branch.md +5 -0
- package/docs/readmes/features/skills/gemini-cli.md +5 -0
- package/docs/readmes/features/skills/humanize.md +5 -0
- package/docs/readmes/features/skills/init-pipeline.md +5 -0
- package/docs/readmes/features/skills/receiving-code-review.md +5 -0
- package/docs/readmes/features/skills/requesting-code-review.md +5 -0
- package/docs/readmes/features/skills/reviewer.md +5 -0
- package/docs/readmes/features/skills/subagent-driven-development.md +5 -0
- package/docs/readmes/features/skills/using-git-worktrees.md +5 -0
- package/docs/readmes/features/skills/wazir.md +5 -0
- package/docs/readmes/features/skills/writing-skills.md +5 -0
- package/docs/readmes/features/workflows/prepare-next.md +1 -1
- package/docs/reference/configuration-reference.md +47 -6
- package/docs/reference/launch-checklist.md +4 -4
- package/docs/reference/review-loop-pattern.md +117 -8
- package/docs/reference/roles-reference.md +1 -0
- package/docs/reference/skill-tiers.md +147 -0
- package/docs/reference/tooling-cli.md +3 -1
- package/docs/truth-claims.yaml +12 -0
- package/expertise/antipatterns/process/ai-coding-antipatterns.md +97 -1
- package/exports/hosts/claude/.claude/settings.json +9 -0
- package/exports/hosts/claude/CLAUDE.md +1 -1
- package/exports/hosts/claude/export.manifest.json +4 -2
- package/exports/hosts/claude/host-package.json +3 -1
- package/exports/hosts/codex/AGENTS.md +1 -1
- package/exports/hosts/codex/export.manifest.json +4 -2
- package/exports/hosts/codex/host-package.json +3 -1
- package/exports/hosts/cursor/.cursor/hooks.json +4 -0
- package/exports/hosts/cursor/.cursor/rules/wazir-core.mdc +1 -1
- package/exports/hosts/cursor/export.manifest.json +4 -2
- package/exports/hosts/cursor/host-package.json +3 -1
- package/exports/hosts/gemini/GEMINI.md +1 -1
- package/exports/hosts/gemini/export.manifest.json +4 -2
- package/exports/hosts/gemini/host-package.json +3 -1
- package/hooks/context-mode-router +191 -0
- package/hooks/definitions/context_mode_router.yaml +19 -0
- package/hooks/hooks.json +31 -6
- package/hooks/protected-path-write-guard +8 -0
- package/hooks/routing-matrix.json +45 -0
- package/hooks/session-start +62 -1
- package/llms-full.txt +905 -132
- package/package.json +2 -3
- package/schemas/hook.schema.json +2 -1
- package/schemas/phase-report.schema.json +80 -0
- package/schemas/usage.schema.json +25 -1
- package/schemas/wazir-manifest.schema.json +19 -0
- package/skills/brainstorming/SKILL.md +18 -155
- package/skills/clarifier/SKILL.md +122 -98
- package/skills/claude-cli/SKILL.md +320 -0
- package/skills/codex-cli/SKILL.md +260 -0
- package/skills/debugging/SKILL.md +13 -0
- package/skills/design/SKILL.md +13 -0
- package/skills/dispatching-parallel-agents/SKILL.md +13 -0
- package/skills/executing-plans/SKILL.md +13 -0
- package/skills/executor/SKILL.md +72 -19
- package/skills/finishing-a-development-branch/SKILL.md +13 -0
- package/skills/gemini-cli/SKILL.md +260 -0
- package/skills/humanize/SKILL.md +13 -0
- package/skills/init-pipeline/SKILL.md +73 -164
- package/skills/prepare-next/SKILL.md +81 -10
- package/skills/receiving-code-review/SKILL.md +13 -0
- package/skills/requesting-code-review/SKILL.md +13 -0
- package/skills/reviewer/SKILL.md +287 -15
- package/skills/run-audit/SKILL.md +13 -0
- package/skills/scan-project/SKILL.md +13 -0
- package/skills/self-audit/SKILL.md +197 -16
- package/skills/subagent-driven-development/SKILL.md +13 -0
- package/skills/subagent-driven-development/code-quality-reviewer-prompt.md +2 -0
- package/skills/subagent-driven-development/implementer-prompt.md +8 -0
- package/skills/subagent-driven-development/spec-reviewer-prompt.md +7 -0
- package/skills/tdd/SKILL.md +13 -0
- package/skills/using-git-worktrees/SKILL.md +13 -0
- package/skills/using-skills/SKILL.md +13 -0
- package/skills/verification/SKILL.md +13 -0
- package/skills/wazir/SKILL.md +194 -377
- package/skills/writing-plans/SKILL.md +14 -1
- package/skills/writing-skills/SKILL.md +13 -0
- package/templates/artifacts/implementation-plan.md +3 -0
- package/templates/artifacts/tasks-template.md +133 -0
- package/templates/examples/phase-report.example.json +48 -0
- package/tooling/src/adapters/composition-engine.js +256 -0
- package/tooling/src/adapters/model-router.js +84 -0
- package/tooling/src/capture/command.js +24 -1
- package/tooling/src/capture/run-config.js +3 -1
- package/tooling/src/capture/store.js +24 -0
- package/tooling/src/capture/usage.js +106 -0
- package/tooling/src/checks/ac-matrix.js +256 -0
- package/tooling/src/checks/command-registry.js +12 -0
- package/tooling/src/checks/docs-truth.js +1 -1
- package/tooling/src/checks/skills.js +111 -0
- package/tooling/src/cli.js +9 -0
- package/tooling/src/commands/stats.js +161 -0
- package/tooling/src/commands/validate.js +5 -1
- package/tooling/src/export/compiler.js +33 -37
- package/tooling/src/gating/agent.js +145 -0
- package/tooling/src/guards/phase-prerequisite-guard.js +127 -0
- package/tooling/src/hooks/routing-logic.js +69 -0
- package/tooling/src/init/auto-detect.js +260 -0
- package/tooling/src/init/command.js +95 -135
- package/tooling/src/input/scanner.js +46 -0
- package/tooling/src/reports/command.js +103 -0
- package/tooling/src/reports/phase-report.js +323 -0
- package/tooling/src/state/command.js +160 -0
- package/tooling/src/state/db.js +287 -0
- package/tooling/src/status/command.js +53 -1
- package/wazir.manifest.yaml +26 -14
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"host": "codex",
|
|
3
3
|
"source_hashes": {
|
|
4
|
-
"wazir.manifest.yaml": "
|
|
4
|
+
"wazir.manifest.yaml": "f00776eb08ed3332b8f855001a2fd0b866cd0b81d009c6fbb316149c398c51ca",
|
|
5
5
|
"roles/clarifier.md": "1e1b8a2c05f1070fdcef485963cfcbffff62c4b2703a8d73fe51ac52d056e573",
|
|
6
6
|
"roles/content-author.md": "cc20b80bd70ab68b3239a9cf56bf1ffc2c06843d38afc6b190844b35a1d73c3e",
|
|
7
7
|
"roles/designer.md": "76cff5bda82975cfb4074de71681e7c8ba284e2e49d0cc98f90208642fef74fc",
|
|
@@ -27,12 +27,14 @@
|
|
|
27
27
|
"workflows/spec-challenge.md": "dc99137c28c49a6f8312924709afb6077754d128e90466dc911150ce15737897",
|
|
28
28
|
"workflows/specify.md": "53b84e74871f6dbd93cae22a881cc5907e398b29501d0a1fa08c7ed69df705cb",
|
|
29
29
|
"workflows/verify.md": "45f9c189520dfe9d24c0bc340a15e6a80c988fca1b84dc187627032a6dbaee16",
|
|
30
|
+
"hooks/definitions/context_mode_router.yaml": "a10dc927418bc130b447eb33faf0f45669ecd9c7917f56947ddd74850a4e0e37",
|
|
30
31
|
"hooks/definitions/loop_cap_guard.yaml": "f0fd220e028ab6fad3d8fd650602884fe500ca4899eff6e428cf217af058618d",
|
|
31
32
|
"hooks/definitions/post_tool_capture.yaml": "a773cd6e18972dee8eef3b7cb06fd1d319a71de4588897cebfbe643f6781a3b2",
|
|
32
33
|
"hooks/definitions/pre_compact_summary.yaml": "daa0175d79f3e0127c5ce86a7a2f8df0be3f58b5c94fe749da715a17c7b2d04e",
|
|
33
34
|
"hooks/definitions/pre_tool_capture_route.yaml": "3c2663380ff3cd09f09de5b96bcf6123266fa74d8a03dfb2d6fbe40a43fb13cf",
|
|
34
35
|
"hooks/definitions/protected_path_write_guard.yaml": "6683d41778b823e2a4e606065597569aa04363f091e135e165de9732f1fc2171",
|
|
35
36
|
"hooks/definitions/session_start.yaml": "9383fcf1f8304c87e57726478a461706c0fc73dc62bcc4d8661f2eeffa43a82d",
|
|
36
|
-
"hooks/definitions/stop_handoff_harvest.yaml": "67a3c0a8bb7cb66b88e77dc79e748082e964d278c47935662c453922a846482b"
|
|
37
|
+
"hooks/definitions/stop_handoff_harvest.yaml": "67a3c0a8bb7cb66b88e77dc79e748082e964d278c47935662c453922a846482b",
|
|
38
|
+
"hooks/hooks.json": "f255345793951b5cf6f6d8c9a8b6a6ad2d3140023453410127a6f70d8e110c26"
|
|
37
39
|
}
|
|
38
40
|
}
|
|
@@ -27,13 +27,15 @@
|
|
|
27
27
|
"workflows/spec-challenge.md",
|
|
28
28
|
"workflows/specify.md",
|
|
29
29
|
"workflows/verify.md",
|
|
30
|
+
"hooks/definitions/context_mode_router.yaml",
|
|
30
31
|
"hooks/definitions/loop_cap_guard.yaml",
|
|
31
32
|
"hooks/definitions/post_tool_capture.yaml",
|
|
32
33
|
"hooks/definitions/pre_compact_summary.yaml",
|
|
33
34
|
"hooks/definitions/pre_tool_capture_route.yaml",
|
|
34
35
|
"hooks/definitions/protected_path_write_guard.yaml",
|
|
35
36
|
"hooks/definitions/session_start.yaml",
|
|
36
|
-
"hooks/definitions/stop_handoff_harvest.yaml"
|
|
37
|
+
"hooks/definitions/stop_handoff_harvest.yaml",
|
|
38
|
+
"hooks/hooks.json"
|
|
37
39
|
],
|
|
38
40
|
"files": [
|
|
39
41
|
"AGENTS.md"
|
|
@@ -6,7 +6,7 @@ This host package is generated from the canonical Wazir sources.
|
|
|
6
6
|
|
|
7
7
|
- project: Wazir
|
|
8
8
|
- hosts: claude, codex, gemini, cursor
|
|
9
|
-
- phases:
|
|
9
|
+
- phases: init, clarifier, executor, final_review
|
|
10
10
|
- roles: clarifier, researcher, specifier, content-author, designer, planner, executor, verifier, reviewer, learner
|
|
11
11
|
- protected paths: input, roles, workflows, schemas, exports/hosts
|
|
12
12
|
- state root default: ~/.wazir/projects/{project_slug}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"host": "cursor",
|
|
3
3
|
"source_hashes": {
|
|
4
|
-
"wazir.manifest.yaml": "
|
|
4
|
+
"wazir.manifest.yaml": "f00776eb08ed3332b8f855001a2fd0b866cd0b81d009c6fbb316149c398c51ca",
|
|
5
5
|
"roles/clarifier.md": "1e1b8a2c05f1070fdcef485963cfcbffff62c4b2703a8d73fe51ac52d056e573",
|
|
6
6
|
"roles/content-author.md": "cc20b80bd70ab68b3239a9cf56bf1ffc2c06843d38afc6b190844b35a1d73c3e",
|
|
7
7
|
"roles/designer.md": "76cff5bda82975cfb4074de71681e7c8ba284e2e49d0cc98f90208642fef74fc",
|
|
@@ -27,12 +27,14 @@
|
|
|
27
27
|
"workflows/spec-challenge.md": "dc99137c28c49a6f8312924709afb6077754d128e90466dc911150ce15737897",
|
|
28
28
|
"workflows/specify.md": "53b84e74871f6dbd93cae22a881cc5907e398b29501d0a1fa08c7ed69df705cb",
|
|
29
29
|
"workflows/verify.md": "45f9c189520dfe9d24c0bc340a15e6a80c988fca1b84dc187627032a6dbaee16",
|
|
30
|
+
"hooks/definitions/context_mode_router.yaml": "a10dc927418bc130b447eb33faf0f45669ecd9c7917f56947ddd74850a4e0e37",
|
|
30
31
|
"hooks/definitions/loop_cap_guard.yaml": "f0fd220e028ab6fad3d8fd650602884fe500ca4899eff6e428cf217af058618d",
|
|
31
32
|
"hooks/definitions/post_tool_capture.yaml": "a773cd6e18972dee8eef3b7cb06fd1d319a71de4588897cebfbe643f6781a3b2",
|
|
32
33
|
"hooks/definitions/pre_compact_summary.yaml": "daa0175d79f3e0127c5ce86a7a2f8df0be3f58b5c94fe749da715a17c7b2d04e",
|
|
33
34
|
"hooks/definitions/pre_tool_capture_route.yaml": "3c2663380ff3cd09f09de5b96bcf6123266fa74d8a03dfb2d6fbe40a43fb13cf",
|
|
34
35
|
"hooks/definitions/protected_path_write_guard.yaml": "6683d41778b823e2a4e606065597569aa04363f091e135e165de9732f1fc2171",
|
|
35
36
|
"hooks/definitions/session_start.yaml": "9383fcf1f8304c87e57726478a461706c0fc73dc62bcc4d8661f2eeffa43a82d",
|
|
36
|
-
"hooks/definitions/stop_handoff_harvest.yaml": "67a3c0a8bb7cb66b88e77dc79e748082e964d278c47935662c453922a846482b"
|
|
37
|
+
"hooks/definitions/stop_handoff_harvest.yaml": "67a3c0a8bb7cb66b88e77dc79e748082e964d278c47935662c453922a846482b",
|
|
38
|
+
"hooks/hooks.json": "f255345793951b5cf6f6d8c9a8b6a6ad2d3140023453410127a6f70d8e110c26"
|
|
37
39
|
}
|
|
38
40
|
}
|
|
@@ -27,13 +27,15 @@
|
|
|
27
27
|
"workflows/spec-challenge.md",
|
|
28
28
|
"workflows/specify.md",
|
|
29
29
|
"workflows/verify.md",
|
|
30
|
+
"hooks/definitions/context_mode_router.yaml",
|
|
30
31
|
"hooks/definitions/loop_cap_guard.yaml",
|
|
31
32
|
"hooks/definitions/post_tool_capture.yaml",
|
|
32
33
|
"hooks/definitions/pre_compact_summary.yaml",
|
|
33
34
|
"hooks/definitions/pre_tool_capture_route.yaml",
|
|
34
35
|
"hooks/definitions/protected_path_write_guard.yaml",
|
|
35
36
|
"hooks/definitions/session_start.yaml",
|
|
36
|
-
"hooks/definitions/stop_handoff_harvest.yaml"
|
|
37
|
+
"hooks/definitions/stop_handoff_harvest.yaml",
|
|
38
|
+
"hooks/hooks.json"
|
|
37
39
|
],
|
|
38
40
|
"files": [
|
|
39
41
|
".cursor/hooks.json",
|
|
@@ -6,7 +6,7 @@ This host package is generated from the canonical Wazir sources.
|
|
|
6
6
|
|
|
7
7
|
- project: Wazir
|
|
8
8
|
- hosts: claude, codex, gemini, cursor
|
|
9
|
-
- phases:
|
|
9
|
+
- phases: init, clarifier, executor, final_review
|
|
10
10
|
- roles: clarifier, researcher, specifier, content-author, designer, planner, executor, verifier, reviewer, learner
|
|
11
11
|
- protected paths: input, roles, workflows, schemas, exports/hosts
|
|
12
12
|
- state root default: ~/.wazir/projects/{project_slug}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"host": "gemini",
|
|
3
3
|
"source_hashes": {
|
|
4
|
-
"wazir.manifest.yaml": "
|
|
4
|
+
"wazir.manifest.yaml": "f00776eb08ed3332b8f855001a2fd0b866cd0b81d009c6fbb316149c398c51ca",
|
|
5
5
|
"roles/clarifier.md": "1e1b8a2c05f1070fdcef485963cfcbffff62c4b2703a8d73fe51ac52d056e573",
|
|
6
6
|
"roles/content-author.md": "cc20b80bd70ab68b3239a9cf56bf1ffc2c06843d38afc6b190844b35a1d73c3e",
|
|
7
7
|
"roles/designer.md": "76cff5bda82975cfb4074de71681e7c8ba284e2e49d0cc98f90208642fef74fc",
|
|
@@ -27,12 +27,14 @@
|
|
|
27
27
|
"workflows/spec-challenge.md": "dc99137c28c49a6f8312924709afb6077754d128e90466dc911150ce15737897",
|
|
28
28
|
"workflows/specify.md": "53b84e74871f6dbd93cae22a881cc5907e398b29501d0a1fa08c7ed69df705cb",
|
|
29
29
|
"workflows/verify.md": "45f9c189520dfe9d24c0bc340a15e6a80c988fca1b84dc187627032a6dbaee16",
|
|
30
|
+
"hooks/definitions/context_mode_router.yaml": "a10dc927418bc130b447eb33faf0f45669ecd9c7917f56947ddd74850a4e0e37",
|
|
30
31
|
"hooks/definitions/loop_cap_guard.yaml": "f0fd220e028ab6fad3d8fd650602884fe500ca4899eff6e428cf217af058618d",
|
|
31
32
|
"hooks/definitions/post_tool_capture.yaml": "a773cd6e18972dee8eef3b7cb06fd1d319a71de4588897cebfbe643f6781a3b2",
|
|
32
33
|
"hooks/definitions/pre_compact_summary.yaml": "daa0175d79f3e0127c5ce86a7a2f8df0be3f58b5c94fe749da715a17c7b2d04e",
|
|
33
34
|
"hooks/definitions/pre_tool_capture_route.yaml": "3c2663380ff3cd09f09de5b96bcf6123266fa74d8a03dfb2d6fbe40a43fb13cf",
|
|
34
35
|
"hooks/definitions/protected_path_write_guard.yaml": "6683d41778b823e2a4e606065597569aa04363f091e135e165de9732f1fc2171",
|
|
35
36
|
"hooks/definitions/session_start.yaml": "9383fcf1f8304c87e57726478a461706c0fc73dc62bcc4d8661f2eeffa43a82d",
|
|
36
|
-
"hooks/definitions/stop_handoff_harvest.yaml": "67a3c0a8bb7cb66b88e77dc79e748082e964d278c47935662c453922a846482b"
|
|
37
|
+
"hooks/definitions/stop_handoff_harvest.yaml": "67a3c0a8bb7cb66b88e77dc79e748082e964d278c47935662c453922a846482b",
|
|
38
|
+
"hooks/hooks.json": "f255345793951b5cf6f6d8c9a8b6a6ad2d3140023453410127a6f70d8e110c26"
|
|
37
39
|
}
|
|
38
40
|
}
|
|
@@ -27,13 +27,15 @@
|
|
|
27
27
|
"workflows/spec-challenge.md",
|
|
28
28
|
"workflows/specify.md",
|
|
29
29
|
"workflows/verify.md",
|
|
30
|
+
"hooks/definitions/context_mode_router.yaml",
|
|
30
31
|
"hooks/definitions/loop_cap_guard.yaml",
|
|
31
32
|
"hooks/definitions/post_tool_capture.yaml",
|
|
32
33
|
"hooks/definitions/pre_compact_summary.yaml",
|
|
33
34
|
"hooks/definitions/pre_tool_capture_route.yaml",
|
|
34
35
|
"hooks/definitions/protected_path_write_guard.yaml",
|
|
35
36
|
"hooks/definitions/session_start.yaml",
|
|
36
|
-
"hooks/definitions/stop_handoff_harvest.yaml"
|
|
37
|
+
"hooks/definitions/stop_handoff_harvest.yaml",
|
|
38
|
+
"hooks/hooks.json"
|
|
37
39
|
],
|
|
38
40
|
"files": [
|
|
39
41
|
"GEMINI.md"
|
|
@@ -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` +
|