@wazir-dev/cli 1.2.0 → 1.4.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 +54 -44
- package/README.md +13 -13
- 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/why-wazir.md +1 -1
- package/docs/readmes/INDEX.md +1 -1
- package/docs/readmes/features/expertise/README.md +1 -1
- package/docs/readmes/features/hooks/pre-compact-summary.md +1 -1
- package/docs/reference/hooks.md +1 -0
- package/docs/reference/launch-checklist.md +3 -3
- package/docs/reference/review-loop-pattern.md +3 -2
- package/docs/reference/skill-tiers.md +2 -2
- package/docs/research/2026-03-20-agents/a18fb002157904af5.txt +187 -0
- package/docs/research/2026-03-20-agents/a1d0ac79ac2f11e6f.txt +2 -0
- package/docs/research/2026-03-20-agents/a324079de037abd7c.txt +198 -0
- package/docs/research/2026-03-20-agents/a357586bccfafb0e5.txt +256 -0
- package/docs/research/2026-03-20-agents/a4365394e4d753105.txt +137 -0
- package/docs/research/2026-03-20-agents/a492af28bc52d3613.txt +136 -0
- package/docs/research/2026-03-20-agents/a4984db0b6a8eee07.txt +124 -0
- package/docs/research/2026-03-20-agents/a5b30e59d34bbb062.txt +214 -0
- package/docs/research/2026-03-20-agents/a5cf7829dab911586.txt +165 -0
- package/docs/research/2026-03-20-agents/a607157c30dd97c9e.txt +96 -0
- package/docs/research/2026-03-20-agents/a60b68b1e19d1e16b.txt +115 -0
- package/docs/research/2026-03-20-agents/a722af01c5594aba0.txt +166 -0
- package/docs/research/2026-03-20-agents/a787bdc516faa5829.txt +181 -0
- package/docs/research/2026-03-20-agents/a7c46d1bba1056ed2.txt +132 -0
- package/docs/research/2026-03-20-agents/a7e5abbab2b281a0d.txt +100 -0
- package/docs/research/2026-03-20-agents/a8dbadc66cd0d7d5a.txt +95 -0
- package/docs/research/2026-03-20-agents/a904d9f45d6b86a6d.txt +75 -0
- package/docs/research/2026-03-20-agents/a927659a942ee7f60.txt +102 -0
- package/docs/research/2026-03-20-agents/a962cb569191f7583.txt +125 -0
- package/docs/research/2026-03-20-agents/aab6decea538aac41.txt +148 -0
- package/docs/research/2026-03-20-agents/abd58b853dd938a1b.txt +295 -0
- package/docs/research/2026-03-20-agents/ac009da573eff7f65.txt +100 -0
- package/docs/research/2026-03-20-agents/ac1bc783364405e5f.txt +190 -0
- package/docs/research/2026-03-20-agents/aca5e2b57fde152a0.txt +132 -0
- package/docs/research/2026-03-20-agents/ad849b8c0a7e95b8b.txt +176 -0
- package/docs/research/2026-03-20-agents/adc2b12a4da32c962.txt +258 -0
- package/docs/research/2026-03-20-agents/af97caaaa9a80e4cb.txt +146 -0
- package/docs/research/2026-03-20-agents/afc5faceee368b3ca.txt +111 -0
- package/docs/research/2026-03-20-agents/afdb282d866e3c1e4.txt +164 -0
- package/docs/research/2026-03-20-agents/afe9d1f61c02b1e8d.txt +299 -0
- package/docs/research/2026-03-20-agents/b4hmkwril.txt +1856 -0
- package/docs/research/2026-03-20-agents/b80ptk89g.txt +1856 -0
- package/docs/research/2026-03-20-agents/bf54s1jss.txt +1150 -0
- package/docs/research/2026-03-20-agents/bhd6kq2kx.txt +1856 -0
- package/docs/research/2026-03-20-agents/bmb2fodyr.txt +988 -0
- package/docs/research/2026-03-20-agents/bmmsrij8i.txt +826 -0
- package/docs/research/2026-03-20-agents/bn4t2ywpu.txt +2175 -0
- package/docs/research/2026-03-20-agents/bu22t9f1z.txt +0 -0
- package/docs/research/2026-03-20-agents/bwvl98v2p.txt +738 -0
- package/docs/research/2026-03-20-agents/psych-a3697a7fd06eb64fd.txt +135 -0
- package/docs/research/2026-03-20-agents/psych-a37776fabc870feae.txt +123 -0
- package/docs/research/2026-03-20-agents/psych-a5b1fe05c0589efaf.txt +2 -0
- package/docs/research/2026-03-20-agents/psych-a95c15b1f29424435.txt +76 -0
- package/docs/research/2026-03-20-agents/psych-a9c26f4d9172dde7c.txt +2 -0
- package/docs/research/2026-03-20-agents/psych-aa19c69f0ca2c5ad3.txt +2 -0
- package/docs/research/2026-03-20-agents/psych-aa4e4cb70e1be5ecb.txt +95 -0
- package/docs/research/2026-03-20-agents/psych-ab5b302f26a554663.txt +102 -0
- package/docs/research/2026-03-20-deep-research-complete.md +101 -0
- package/docs/research/2026-03-20-deep-research-status.md +38 -0
- package/docs/research/2026-03-20-enforcement-research.md +107 -0
- package/expertise/antipatterns/process/ai-coding-antipatterns.md +117 -0
- package/expertise/composition-map.yaml +27 -8
- package/expertise/digests/reviewer/ai-coding-digest.md +83 -0
- package/expertise/digests/reviewer/architectural-thinking-digest.md +63 -0
- package/expertise/digests/reviewer/architecture-antipatterns-digest.md +49 -0
- package/expertise/digests/reviewer/code-smells-digest.md +53 -0
- package/expertise/digests/reviewer/coupling-cohesion-digest.md +54 -0
- package/expertise/digests/reviewer/ddd-digest.md +60 -0
- package/expertise/digests/reviewer/dependency-risk-digest.md +40 -0
- package/expertise/digests/reviewer/error-handling-digest.md +55 -0
- package/expertise/digests/reviewer/review-methodology-digest.md +49 -0
- package/exports/hosts/claude/.claude/commands/learn.md +61 -8
- 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 +7 -6
- package/exports/hosts/claude/export.manifest.json +8 -5
- package/exports/hosts/claude/host-package.json +3 -0
- package/exports/hosts/codex/export.manifest.json +8 -5
- package/exports/hosts/codex/host-package.json +3 -0
- package/exports/hosts/cursor/.cursor/hooks.json +6 -6
- package/exports/hosts/cursor/export.manifest.json +8 -5
- package/exports/hosts/cursor/host-package.json +3 -0
- package/exports/hosts/gemini/export.manifest.json +8 -5
- package/exports/hosts/gemini/host-package.json +3 -0
- package/hooks/definitions/pretooluse_dispatcher.yaml +26 -0
- package/hooks/definitions/pretooluse_pipeline_guard.yaml +22 -0
- package/hooks/definitions/stop_pipeline_gate.yaml +22 -0
- package/hooks/hooks.json +7 -6
- package/hooks/pretooluse-dispatcher +84 -0
- package/hooks/pretooluse-pipeline-guard +9 -0
- package/hooks/stop-pipeline-gate +9 -0
- package/llms-full.txt +48 -18
- package/package.json +2 -3
- package/schemas/decision.schema.json +15 -0
- package/schemas/hook.schema.json +4 -1
- package/schemas/phase-report.schema.json +9 -0
- package/skills/TEMPLATE-3-ZONE.md +160 -0
- package/skills/brainstorming/SKILL.md +137 -21
- package/skills/clarifier/SKILL.md +364 -53
- package/skills/claude-cli/SKILL.md +91 -12
- package/skills/codex-cli/SKILL.md +91 -12
- package/skills/debugging/SKILL.md +133 -38
- package/skills/design/SKILL.md +173 -37
- package/skills/dispatching-parallel-agents/SKILL.md +129 -31
- package/skills/executing-plans/SKILL.md +113 -25
- package/skills/executor/SKILL.md +252 -21
- package/skills/finishing-a-development-branch/SKILL.md +107 -18
- package/skills/gemini-cli/SKILL.md +91 -12
- package/skills/humanize/SKILL.md +92 -13
- package/skills/init-pipeline/SKILL.md +90 -18
- package/skills/prepare-next/SKILL.md +93 -24
- package/skills/receiving-code-review/SKILL.md +90 -16
- package/skills/requesting-code-review/SKILL.md +100 -24
- package/skills/requesting-code-review/code-reviewer.md +29 -17
- package/skills/reviewer/SKILL.md +270 -57
- package/skills/run-audit/SKILL.md +92 -15
- package/skills/scan-project/SKILL.md +93 -14
- package/skills/self-audit/SKILL.md +133 -39
- package/skills/skill-research/SKILL.md +275 -0
- package/skills/subagent-driven-development/SKILL.md +129 -30
- package/skills/subagent-driven-development/code-quality-reviewer-prompt.md +30 -2
- package/skills/subagent-driven-development/implementer-prompt.md +40 -27
- package/skills/subagent-driven-development/spec-reviewer-prompt.md +25 -12
- package/skills/tdd/SKILL.md +125 -20
- package/skills/using-git-worktrees/SKILL.md +118 -28
- package/skills/using-skills/SKILL.md +116 -29
- package/skills/verification/SKILL.md +160 -17
- package/skills/wazir/SKILL.md +750 -120
- package/skills/writing-plans/SKILL.md +134 -28
- package/skills/writing-skills/SKILL.md +91 -13
- package/skills/writing-skills/anthropic-best-practices.md +104 -64
- package/skills/writing-skills/persuasion-principles.md +100 -34
- package/tooling/src/capture/command.js +46 -2
- package/tooling/src/capture/decision.js +40 -0
- package/tooling/src/capture/store.js +33 -0
- package/tooling/src/capture/user-input.js +66 -0
- package/tooling/src/checks/security-sensitivity.js +69 -0
- package/tooling/src/cli.js +28 -26
- package/tooling/src/config/depth-table.js +60 -0
- package/tooling/src/export/compiler.js +7 -8
- package/tooling/src/guards/guardrail-functions.js +131 -0
- package/tooling/src/guards/phase-prerequisite-guard.js +97 -3
- package/tooling/src/hooks/pretooluse-dispatcher.js +300 -0
- package/tooling/src/hooks/pretooluse-pipeline-guard.js +141 -0
- package/tooling/src/hooks/stop-pipeline-gate.js +92 -0
- package/tooling/src/init/auto-detect.js +0 -2
- package/tooling/src/init/command.js +3 -95
- package/tooling/src/learn/pipeline.js +177 -0
- package/tooling/src/state/db.js +251 -2
- package/tooling/src/state/pipeline-state.js +262 -0
- package/tooling/src/status/command.js +6 -1
- package/tooling/src/verify/proof-collector.js +299 -0
- package/wazir.manifest.yaml +3 -0
- package/workflows/learn.md +61 -8
- package/workflows/plan-review.md +3 -1
- package/workflows/verify.md +30 -1
|
@@ -2,49 +2,115 @@
|
|
|
2
2
|
|
|
3
3
|
## Overview
|
|
4
4
|
|
|
5
|
-
LLMs
|
|
5
|
+
LLMs exhibit statistical compliance biases that can be leveraged to improve instruction following. This is not psychology applied to machines — it is empirical prompt engineering grounded in attention mechanics, training distribution effects, and measured compliance rates.
|
|
6
6
|
|
|
7
|
-
**Research foundation:** Meincke et al. (2025) tested 7 persuasion principles with N=28,000 AI conversations.
|
|
7
|
+
**Research foundation:** Meincke et al. (2025) tested 7 persuasion principles with N=28,000 AI conversations. Commitment priming approached 100% compliance. Positive directive framing consistently outperformed negative framing. Authority framing lifted compliance by ~40pp.
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## Principles Ranked by Evidence Strength
|
|
10
10
|
|
|
11
|
-
### 1
|
|
12
|
-
- Imperative language: "YOU MUST", "Never", "Always"
|
|
13
|
-
- Non-negotiable framing: "No exceptions"
|
|
14
|
-
- Eliminates decision fatigue and rationalization
|
|
11
|
+
### Tier 1: Strong Evidence, Large Effect
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
13
|
+
**1. Commitment Priming (highest impact)**
|
|
14
|
+
- Have the model announce its plan before executing
|
|
15
|
+
- Autoregressive consistency: once the model generates "I will do X", it is statistically more likely to do X
|
|
16
|
+
- Implementation: "Before executing, state which steps you will perform"
|
|
17
|
+
- Measured: near-100% compliance after self-commitment in Meincke et al.
|
|
20
18
|
|
|
21
|
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
19
|
+
**2. Positive Directive Framing**
|
|
20
|
+
- "Always do X" consistently outperforms "Never do Y"
|
|
21
|
+
- Token generation selects what to produce, not what to avoid
|
|
22
|
+
- Negative instructions ("do NOT mention X") can paradoxically increase mentions
|
|
23
|
+
- Use negative framing ONLY for critical guardrails with a positive alternative: "Do NOT skip review. Instead, run review quickly."
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
25
|
+
**3. Structural Isolation (XML Tags)**
|
|
26
|
+
- Claude is fine-tuned to attend to XML tag boundaries
|
|
27
|
+
- Tags create attention-weight spikes and trust boundaries
|
|
28
|
+
- Use `<rules>`, `<instructions>`, `<output_format>` for hard boundaries
|
|
29
|
+
- Hybrid XML+markdown is optimal: XML for structure, markdown for formatting within sections
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
31
|
+
**4. Positional Privilege (Primacy + Recency)**
|
|
32
|
+
- First ~500 tokens: ~95% compliance (primacy zone)
|
|
33
|
+
- Last ~500 tokens: ~85% compliance (recency zone)
|
|
34
|
+
- Middle of long context: ~65-75% compliance (lost in the middle)
|
|
35
|
+
- Critical rules go at beginning AND end. Never only in the middle.
|
|
34
36
|
|
|
35
|
-
###
|
|
36
|
-
- Obligation to return favors
|
|
37
|
-
- "I'll give you full context, you give me honest assessment"
|
|
37
|
+
### Tier 2: Strong Evidence, Moderate Effect
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
-
|
|
41
|
-
-
|
|
39
|
+
**5. Authority / Role Assignment**
|
|
40
|
+
- "You are a senior security auditor responsible for..." activates domain-specific patterns
|
|
41
|
+
- +40pp lift in Meincke et al.
|
|
42
|
+
- Expert personas produce more accurate, more disciplined output
|
|
43
|
+
|
|
44
|
+
**6. Consequence Framing**
|
|
45
|
+
- "Skipping this step causes silent regressions that waste hours of debugging"
|
|
46
|
+
- Provides reasoning context for why compliance matters
|
|
47
|
+
- More effective than abstract rules ("always follow the process")
|
|
48
|
+
|
|
49
|
+
**7. Implementation Intentions (IF-THEN rules)**
|
|
50
|
+
- "IF user says skip → THEN say 'Running it quickly' and execute"
|
|
51
|
+
- Pre-decides the response — no judgment call needed at runtime
|
|
52
|
+
- d=0.65 across 94 psychology studies (Gollwitzer). Maps directly to LLM prompt design.
|
|
53
|
+
- Single most actionable technique for skill authors
|
|
54
|
+
|
|
55
|
+
**8. Redundant Reinforcement**
|
|
56
|
+
- State the rule, show an example, reference it in the output format, add a constraint tag
|
|
57
|
+
- Multiple encoding paths survive when any single one fails
|
|
58
|
+
- Paraphrased repetition (2-3x) outperforms verbatim repetition
|
|
59
|
+
|
|
60
|
+
### Tier 3: Context-Dependent Effect
|
|
61
|
+
|
|
62
|
+
**9. Social Proof**
|
|
63
|
+
- "Standard practice is..." or "All production systems follow this pattern"
|
|
64
|
+
- Effective when baseline compliance is already moderate (+6pp)
|
|
65
|
+
|
|
66
|
+
**10. Urgency / Scarcity**
|
|
67
|
+
- "This must be done correctly the first time; there is no retry"
|
|
68
|
+
- Increases both compliance and output variance — use sparingly
|
|
69
|
+
|
|
70
|
+
**11. Moral / Ethical Framing**
|
|
71
|
+
- "Omitting this would produce misleading output"
|
|
72
|
+
- Effective for Claude specifically due to Constitutional AI training
|
|
73
|
+
- Frame positively (good outcome of compliance) not negatively
|
|
74
|
+
|
|
75
|
+
## Anti-Patterns
|
|
76
|
+
|
|
77
|
+
| Pattern | Problem |
|
|
78
|
+
|---------|---------|
|
|
79
|
+
| Negative instructions without alternatives | "Don't do X" fails — model must activate X to evaluate constraint |
|
|
80
|
+
| Instruction overload (>12 constraints) | Steep compliance drop after ~12 accumulated constraints |
|
|
81
|
+
| Threats without specifics | "You will be punished" increases variance without improving median |
|
|
82
|
+
| Reciprocity framing | "I helped you, now help me" — weakest principle, only +11pp |
|
|
83
|
+
| Relying solely on alignment | 80% of enterprises reported injection incidents. Structural defenses needed. |
|
|
42
84
|
|
|
43
85
|
## Principle Combinations by Skill Type
|
|
44
86
|
|
|
45
|
-
| Skill Type |
|
|
46
|
-
|
|
47
|
-
| Discipline-enforcing |
|
|
48
|
-
|
|
|
49
|
-
| Collaborative |
|
|
50
|
-
| Reference |
|
|
87
|
+
| Skill Type | Primary Techniques | Avoid |
|
|
88
|
+
|------------|-------------------|-------|
|
|
89
|
+
| Discipline-enforcing (TDD, verification) | Commitment + Implementation Intentions + Positional Privilege + Authority | Liking, Reciprocity |
|
|
90
|
+
| Process-governing (clarifier, executor) | Commitment + Consequence Framing + Structural Isolation | Heavy emotional framing |
|
|
91
|
+
| Collaborative (brainstorming, design) | Moderate Authority + Implementation Intentions | Over-constraining creative steps |
|
|
92
|
+
| Reference (docs, guides) | Structural Isolation + Positional Privilege | All persuasion — clarity only |
|
|
93
|
+
|
|
94
|
+
## The 3-Zone Architecture
|
|
95
|
+
|
|
96
|
+
Apply these principles through the 3-zone skill layout:
|
|
97
|
+
|
|
98
|
+
- **Zone 1 (Primacy):** Identity + Iron Laws + Priority Stack — leverages positional privilege + authority + commitment
|
|
99
|
+
- **Zone 2 (Process):** IF-THEN rules + decision tables + gate functions — leverages implementation intentions + structural isolation
|
|
100
|
+
- **Zone 3 (Recency):** Restated laws + Red Flags + meta-instruction — leverages recency + redundant reinforcement + consequence framing
|
|
101
|
+
|
|
102
|
+
## Temporal Testing Advisory
|
|
103
|
+
|
|
104
|
+
Prompt engineering techniques lose effectiveness as models improve. Re-test skill compliance every major model version. Include a "last verified" date on persuasion-dependent skills.
|
|
105
|
+
|
|
106
|
+
**Last verified:** Claude Opus 4.6, March 2026
|
|
107
|
+
|
|
108
|
+
## Sources
|
|
109
|
+
|
|
110
|
+
- Meincke et al. (2025). "Call Me A Jerk: Persuading AI to Comply" (N=28,000, SSRN)
|
|
111
|
+
- Liu et al. (2024). "Lost in the Middle" (TACL, arXiv:2307.03172)
|
|
112
|
+
- Wallace et al. (2024). "The Instruction Hierarchy" (OpenAI, arXiv:2404.13208)
|
|
113
|
+
- Gollwitzer (1999). Implementation Intentions (d=0.65, 94 studies meta-analysis)
|
|
114
|
+
- EmotionPrompt (2023). Emotional framing effects (arXiv:2307.11760)
|
|
115
|
+
- Zhou et al. (2023). IFEval benchmark (arXiv:2311.07911)
|
|
116
|
+
- Anthropic (2024). Claude Model Spec — instruction hierarchy documentation
|
|
@@ -3,6 +3,7 @@ import path from 'node:path';
|
|
|
3
3
|
|
|
4
4
|
import { parseCommandOptions, parsePositiveInteger } from '../command-options.js';
|
|
5
5
|
import { readYamlFile } from '../loaders.js';
|
|
6
|
+
import { validateRunCompletion } from '../guards/phase-prerequisite-guard.js';
|
|
6
7
|
import { findProjectRoot } from '../project-root.js';
|
|
7
8
|
import { resolveStateRoot } from '../state-root.js';
|
|
8
9
|
import {
|
|
@@ -18,6 +19,7 @@ import {
|
|
|
18
19
|
} from './store.js';
|
|
19
20
|
import { readRunConfig, getPhaseLoopCap } from './run-config.js';
|
|
20
21
|
import { readUsage, generateReport, initUsage, recordCaptureSavings, recordPhaseUsage } from './usage.js';
|
|
22
|
+
import { appendDecision } from './decision.js';
|
|
21
23
|
import { evaluateLoopCapGuard } from '../guards/loop-cap-guard.js';
|
|
22
24
|
import { evaluatePhasePrerequisiteGuard } from '../guards/phase-prerequisite-guard.js';
|
|
23
25
|
|
|
@@ -57,7 +59,7 @@ function resolveCaptureContext(parsed, context = {}) {
|
|
|
57
59
|
const projectRoot = findProjectRoot(context.cwd ?? process.cwd());
|
|
58
60
|
const manifest = readYamlFile(path.join(projectRoot, 'wazir.manifest.yaml'));
|
|
59
61
|
const { options } = parseCommandOptions(parsed.args, {
|
|
60
|
-
boolean: ['json'],
|
|
62
|
+
boolean: ['json', 'complete'],
|
|
61
63
|
string: [
|
|
62
64
|
'run',
|
|
63
65
|
'phase',
|
|
@@ -72,6 +74,8 @@ function resolveCaptureContext(parsed, context = {}) {
|
|
|
72
74
|
'command',
|
|
73
75
|
'exit-code',
|
|
74
76
|
'task-id',
|
|
77
|
+
'decision',
|
|
78
|
+
'reason',
|
|
75
79
|
],
|
|
76
80
|
});
|
|
77
81
|
const stateRoot = resolveStateRoot(projectRoot, manifest, {
|
|
@@ -326,6 +330,21 @@ function handleSummary(parsed, context = {}) {
|
|
|
326
330
|
|
|
327
331
|
const runPaths = getRunPaths(stateRoot, options.run);
|
|
328
332
|
const status = readStatus(runPaths);
|
|
333
|
+
|
|
334
|
+
// Enforce workflow completion before allowing summary to finalize
|
|
335
|
+
if (options.complete) {
|
|
336
|
+
const projectRoot = findProjectRoot();
|
|
337
|
+
const manifestPath = path.join(projectRoot, 'wazir.manifest.yaml');
|
|
338
|
+
const result = validateRunCompletion(runPaths.runRoot, manifestPath);
|
|
339
|
+
if (!result.complete) {
|
|
340
|
+
const msg = `Run incomplete: ${result.missing.length} workflow(s) not finished: ${result.missing.join(', ')}`;
|
|
341
|
+
if (options.json) {
|
|
342
|
+
return { exitCode: 1, stdout: JSON.stringify({ run_id: options.run, complete: false, missing_workflows: result.missing, error: msg }, null, 2) + '\n' };
|
|
343
|
+
}
|
|
344
|
+
return { exitCode: 1, stderr: msg + '\n' };
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
329
348
|
const eventName = options.event ?? 'pre_compact_summary';
|
|
330
349
|
const summaryContent = readInput();
|
|
331
350
|
const summaryPath = writeSummary(runPaths, summaryContent);
|
|
@@ -372,6 +391,29 @@ function handleUsage(parsed, context = {}) {
|
|
|
372
391
|
};
|
|
373
392
|
}
|
|
374
393
|
|
|
394
|
+
function handleDecision(parsed, context = {}) {
|
|
395
|
+
const { stateRoot, options } = resolveCaptureContext(parsed, context);
|
|
396
|
+
|
|
397
|
+
requireOption(options, 'run', 'Usage: wazir capture decision --run <id> --phase <phase> --decision "<text>" --reason "<text>" [--task-id <id>] [--state-root <path>] [--json]');
|
|
398
|
+
requireOption(options, 'phase', 'Usage: wazir capture decision --run <id> --phase <phase> --decision "<text>" --reason "<text>" [--task-id <id>] [--state-root <path>] [--json]');
|
|
399
|
+
requireOption(options, 'decision', 'Usage: wazir capture decision --run <id> --phase <phase> --decision "<text>" --reason "<text>" [--task-id <id>] [--state-root <path>] [--json]');
|
|
400
|
+
requireOption(options, 'reason', 'Usage: wazir capture decision --run <id> --phase <phase> --decision "<text>" --reason "<text>" [--task-id <id>] [--state-root <path>] [--json]');
|
|
401
|
+
|
|
402
|
+
const runPaths = getRunPaths(stateRoot, options.run);
|
|
403
|
+
appendDecision(runPaths, {
|
|
404
|
+
phase: options.phase,
|
|
405
|
+
decision: options.decision,
|
|
406
|
+
reason: options.reason,
|
|
407
|
+
task_id: options.taskId,
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
return formatResult({
|
|
411
|
+
run_id: options.run,
|
|
412
|
+
event: 'decision',
|
|
413
|
+
decisions_path: runPaths.decisionsPath,
|
|
414
|
+
}, { json: options.json });
|
|
415
|
+
}
|
|
416
|
+
|
|
375
417
|
function handleLoopCheck(parsed, context = {}) {
|
|
376
418
|
const { stateRoot, options } = resolveCaptureContext(parsed, context);
|
|
377
419
|
|
|
@@ -470,10 +512,12 @@ export function runCaptureCommand(parsed, context = {}) {
|
|
|
470
512
|
return handleUsage(parsed, context);
|
|
471
513
|
case 'loop-check':
|
|
472
514
|
return handleLoopCheck(parsed, context);
|
|
515
|
+
case 'decision':
|
|
516
|
+
return handleDecision(parsed, context);
|
|
473
517
|
default:
|
|
474
518
|
return {
|
|
475
519
|
exitCode: 1,
|
|
476
|
-
stderr: 'Usage: wazir capture <init|event|route|output|summary|usage|loop-check> ...\n',
|
|
520
|
+
stderr: 'Usage: wazir capture <init|event|route|output|summary|usage|loop-check|decision> ...\n',
|
|
477
521
|
};
|
|
478
522
|
}
|
|
479
523
|
} catch (error) {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Append a decision entry to the run's NDJSON log.
|
|
5
|
+
*
|
|
6
|
+
* @param {object} runPaths - Run paths object (must include decisionsPath)
|
|
7
|
+
* @param {object} entry - { phase, decision, reason, task_id? }
|
|
8
|
+
*/
|
|
9
|
+
export function appendDecision(runPaths, { phase, decision, reason, task_id }) {
|
|
10
|
+
const record = {
|
|
11
|
+
timestamp: new Date().toISOString(),
|
|
12
|
+
phase: phase ?? 'unknown',
|
|
13
|
+
decision: decision ?? '',
|
|
14
|
+
reason: reason ?? '',
|
|
15
|
+
};
|
|
16
|
+
if (task_id) {
|
|
17
|
+
record.task_id = task_id;
|
|
18
|
+
}
|
|
19
|
+
fs.appendFileSync(runPaths.decisionsPath, JSON.stringify(record) + '\n');
|
|
20
|
+
return runPaths.decisionsPath;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Read all entries from a run's decisions log.
|
|
25
|
+
*
|
|
26
|
+
* @param {object} runPaths - Run paths object (must include decisionsPath)
|
|
27
|
+
* @returns {Array<object>}
|
|
28
|
+
*/
|
|
29
|
+
export function readDecisions(runPaths) {
|
|
30
|
+
if (!fs.existsSync(runPaths.decisionsPath)) return [];
|
|
31
|
+
|
|
32
|
+
return fs.readFileSync(runPaths.decisionsPath, 'utf8')
|
|
33
|
+
.split('\n')
|
|
34
|
+
.filter(line => line.trim())
|
|
35
|
+
.map(line => {
|
|
36
|
+
try { return JSON.parse(line); }
|
|
37
|
+
catch { return null; }
|
|
38
|
+
})
|
|
39
|
+
.filter(Boolean);
|
|
40
|
+
}
|
|
@@ -19,6 +19,7 @@ export function getRunPaths(stateRoot, runId) {
|
|
|
19
19
|
capturesDir,
|
|
20
20
|
statusPath: path.join(runRoot, 'status.json'),
|
|
21
21
|
eventsPath: path.join(runRoot, 'events.ndjson'),
|
|
22
|
+
decisionsPath: path.join(runRoot, 'decisions.ndjson'),
|
|
22
23
|
summaryPath: path.join(runRoot, 'summary.md'),
|
|
23
24
|
usagePath: path.join(runRoot, 'usage.json'),
|
|
24
25
|
};
|
|
@@ -116,6 +117,38 @@ export function readPhaseExitEvents(runPaths) {
|
|
|
116
117
|
return completedPhases;
|
|
117
118
|
}
|
|
118
119
|
|
|
120
|
+
/**
|
|
121
|
+
* Read phase exit events with full two-level detail (parent_phase + workflow).
|
|
122
|
+
*/
|
|
123
|
+
export function readPhaseExitEventsDetailed(runPaths) {
|
|
124
|
+
if (!fs.existsSync(runPaths.eventsPath)) {
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const content = fs.readFileSync(runPaths.eventsPath, 'utf8');
|
|
129
|
+
const events = [];
|
|
130
|
+
|
|
131
|
+
for (const line of content.split('\n')) {
|
|
132
|
+
const trimmed = line.trim();
|
|
133
|
+
if (!trimmed) continue;
|
|
134
|
+
try {
|
|
135
|
+
const event = JSON.parse(trimmed);
|
|
136
|
+
if (event.event === 'phase_exit' && event.phase) {
|
|
137
|
+
events.push({
|
|
138
|
+
phase: event.phase,
|
|
139
|
+
parent_phase: event.parent_phase ?? event.phase,
|
|
140
|
+
workflow: event.workflow ?? event.phase,
|
|
141
|
+
status: event.status,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
} catch {
|
|
145
|
+
// Skip malformed lines
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return events;
|
|
150
|
+
}
|
|
151
|
+
|
|
119
152
|
export function writeSummary(runPaths, content) {
|
|
120
153
|
ensureRunDirectories(runPaths);
|
|
121
154
|
fs.writeFileSync(runPaths.summaryPath, content);
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Append a user input entry to the run's NDJSON log.
|
|
6
|
+
*
|
|
7
|
+
* @param {string} runDir - Absolute path to the run directory
|
|
8
|
+
* @param {object} entry - { phase, type, content, context }
|
|
9
|
+
* type: 'instruction' | 'approval' | 'correction' | 'rejection' | 'redirect'
|
|
10
|
+
*/
|
|
11
|
+
export function captureUserInput(runDir, { phase, type, content, context }) {
|
|
12
|
+
const logPath = path.join(runDir, 'user-input-log.ndjson');
|
|
13
|
+
const record = {
|
|
14
|
+
timestamp: new Date().toISOString(),
|
|
15
|
+
phase: phase ?? 'unknown',
|
|
16
|
+
type: type ?? 'instruction',
|
|
17
|
+
content: content ?? '',
|
|
18
|
+
context: context ?? '',
|
|
19
|
+
};
|
|
20
|
+
fs.appendFileSync(logPath, JSON.stringify(record) + '\n');
|
|
21
|
+
return logPath;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Read all entries from a run's user input log.
|
|
26
|
+
*/
|
|
27
|
+
export function readUserInputLog(runDir) {
|
|
28
|
+
const logPath = path.join(runDir, 'user-input-log.ndjson');
|
|
29
|
+
if (!fs.existsSync(logPath)) return [];
|
|
30
|
+
|
|
31
|
+
return fs.readFileSync(logPath, 'utf8')
|
|
32
|
+
.split('\n')
|
|
33
|
+
.filter(line => line.trim())
|
|
34
|
+
.map(line => {
|
|
35
|
+
try { return JSON.parse(line); }
|
|
36
|
+
catch { return null; }
|
|
37
|
+
})
|
|
38
|
+
.filter(Boolean);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Prune old user-input-log.ndjson files, keeping the most recent `keep` runs.
|
|
43
|
+
*
|
|
44
|
+
* @param {string} stateRoot - Absolute path to the state root (e.g. ~/.wazir/projects/foo)
|
|
45
|
+
* @param {number} keep - Number of recent runs to keep (default 10)
|
|
46
|
+
*/
|
|
47
|
+
export function pruneOldInputLogs(stateRoot, keep = 10) {
|
|
48
|
+
const runsDir = path.join(stateRoot, 'runs');
|
|
49
|
+
if (!fs.existsSync(runsDir)) return { pruned: 0 };
|
|
50
|
+
|
|
51
|
+
const entries = fs.readdirSync(runsDir)
|
|
52
|
+
.filter(name => name.startsWith('run-') && fs.statSync(path.join(runsDir, name)).isDirectory())
|
|
53
|
+
.sort()
|
|
54
|
+
.reverse();
|
|
55
|
+
|
|
56
|
+
let pruned = 0;
|
|
57
|
+
for (let i = keep; i < entries.length; i++) {
|
|
58
|
+
const logPath = path.join(runsDir, entries[i], 'user-input-log.ndjson');
|
|
59
|
+
if (fs.existsSync(logPath)) {
|
|
60
|
+
fs.unlinkSync(logPath);
|
|
61
|
+
pruned++;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return { pruned };
|
|
66
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const SECURITY_PATTERNS = [
|
|
2
|
+
'auth', 'password', 'passwd', 'token', 'query', 'sql',
|
|
3
|
+
'fetch', 'upload', 'secret', 'env', 'api[._-]?key',
|
|
4
|
+
'session', 'cookie', 'cors', 'csrf', 'jwt', 'oauth',
|
|
5
|
+
'encrypt', 'decrypt', 'hash', 'salt', 'credential',
|
|
6
|
+
'private[._-]?key', 'access[._-]?token', 'refresh[._-]?token',
|
|
7
|
+
];
|
|
8
|
+
|
|
9
|
+
const PATTERN_REGEX = new RegExp(
|
|
10
|
+
`\\b(${SECURITY_PATTERNS.join('|')})\\b`,
|
|
11
|
+
'gi'
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Scan diff text for security-sensitive patterns.
|
|
16
|
+
* Returns which patterns were found and in which files.
|
|
17
|
+
*/
|
|
18
|
+
export function detectSecurityPatterns(diffText) {
|
|
19
|
+
if (!diffText || typeof diffText !== 'string') {
|
|
20
|
+
return { triggered: false, patterns: [], files: [] };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const matchedPatterns = new Set();
|
|
24
|
+
const matchedFiles = new Set();
|
|
25
|
+
let currentFile = null;
|
|
26
|
+
|
|
27
|
+
for (const line of diffText.split('\n')) {
|
|
28
|
+
// Track current file from diff headers
|
|
29
|
+
const fileMatch = line.match(/^(?:diff --git a\/\S+ b\/|[+]{3} b\/)(.+)/);
|
|
30
|
+
if (fileMatch) {
|
|
31
|
+
currentFile = fileMatch[1];
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Only scan added/modified lines (starting with +, not +++)
|
|
36
|
+
if (!line.startsWith('+') || line.startsWith('+++')) continue;
|
|
37
|
+
|
|
38
|
+
const lineMatches = line.match(PATTERN_REGEX);
|
|
39
|
+
if (lineMatches) {
|
|
40
|
+
for (const m of lineMatches) {
|
|
41
|
+
matchedPatterns.add(m.toLowerCase());
|
|
42
|
+
}
|
|
43
|
+
if (currentFile) {
|
|
44
|
+
matchedFiles.add(currentFile);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const patterns = [...matchedPatterns].sort();
|
|
50
|
+
const files = [...matchedFiles].sort();
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
triggered: patterns.length > 0,
|
|
54
|
+
patterns,
|
|
55
|
+
files,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Security review dimensions to add when patterns are detected.
|
|
61
|
+
*/
|
|
62
|
+
export const SECURITY_REVIEW_DIMENSIONS = [
|
|
63
|
+
'Injection (SQL, command, template, header)',
|
|
64
|
+
'Authentication bypass',
|
|
65
|
+
'Data exposure (PII, secrets, tokens in logs/responses)',
|
|
66
|
+
'CSRF / SSRF',
|
|
67
|
+
'XSS (stored, reflected, DOM)',
|
|
68
|
+
'Secrets leakage (hardcoded keys, env vars in client code)',
|
|
69
|
+
];
|
package/tooling/src/cli.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
// Suppress Node.js ExperimentalWarning for built-in SQLite (node:sqlite).
|
|
4
|
+
// Must run before any module that transitively imports node:sqlite loads,
|
|
5
|
+
// so command handlers are lazy-imported below instead of using static imports.
|
|
6
|
+
const _originalEmit = process.emit;
|
|
7
|
+
process.emit = function (event, ...args) {
|
|
8
|
+
if (event === 'warning' && args[0]?.name === 'ExperimentalWarning' &&
|
|
9
|
+
args[0]?.message?.includes('SQLite')) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
return _originalEmit.apply(this, [event, ...args]);
|
|
13
|
+
};
|
|
14
|
+
|
|
3
15
|
import fs from 'node:fs';
|
|
4
16
|
import { fileURLToPath } from 'node:url';
|
|
5
17
|
|
|
6
|
-
import { runCaptureCommand } from './capture/command.js';
|
|
7
|
-
import { runValidateCommand } from './commands/validate.js';
|
|
8
|
-
import { runDoctorCommand } from './doctor/command.js';
|
|
9
|
-
import { runExportCommand as runGeneratedExportCommand } from './export/command.js';
|
|
10
|
-
import { runIndexCommand } from './index/command.js';
|
|
11
|
-
import { runInitCommand } from './init/command.js';
|
|
12
|
-
import { runRecallCommand } from './recall/command.js';
|
|
13
|
-
import { runReportCommand } from './reports/command.js';
|
|
14
|
-
import { runStateCommand } from './state/command.js';
|
|
15
|
-
import { runStatsCommand } from './commands/stats.js';
|
|
16
|
-
import { runStatusCommand } from './status/command.js';
|
|
17
|
-
|
|
18
18
|
const COMMAND_FAMILIES = [
|
|
19
19
|
'export',
|
|
20
20
|
'validate',
|
|
@@ -29,18 +29,19 @@ const COMMAND_FAMILIES = [
|
|
|
29
29
|
'capture'
|
|
30
30
|
];
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
32
|
+
// Lazy-load command handlers so the warning filter is active before node:sqlite loads
|
|
33
|
+
const COMMAND_LOADERS = {
|
|
34
|
+
export: () => import('./export/command.js').then(m => m.runExportCommand),
|
|
35
|
+
validate: () => import('./commands/validate.js').then(m => m.runValidateCommand),
|
|
36
|
+
doctor: () => import('./doctor/command.js').then(m => m.runDoctorCommand),
|
|
37
|
+
index: () => import('./index/command.js').then(m => m.runIndexCommand),
|
|
38
|
+
init: () => import('./init/command.js').then(m => m.runInitCommand),
|
|
39
|
+
recall: () => import('./recall/command.js').then(m => m.runRecallCommand),
|
|
40
|
+
report: () => import('./reports/command.js').then(m => m.runReportCommand),
|
|
41
|
+
state: () => import('./state/command.js').then(m => m.runStateCommand),
|
|
42
|
+
status: () => import('./status/command.js').then(m => m.runStatusCommand),
|
|
43
|
+
stats: () => import('./commands/stats.js').then(m => m.runStatsCommand),
|
|
44
|
+
capture: () => import('./capture/command.js').then(m => m.runCaptureCommand),
|
|
44
45
|
};
|
|
45
46
|
|
|
46
47
|
export function parseArgs(argv) {
|
|
@@ -88,9 +89,9 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
88
89
|
return 1;
|
|
89
90
|
}
|
|
90
91
|
|
|
91
|
-
const
|
|
92
|
+
const loader = COMMAND_LOADERS[parsed.command];
|
|
92
93
|
|
|
93
|
-
if (!
|
|
94
|
+
if (!loader) {
|
|
94
95
|
console.error(`wazir ${parsed.command} is not implemented yet`);
|
|
95
96
|
return 2;
|
|
96
97
|
}
|
|
@@ -98,6 +99,7 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
98
99
|
let result;
|
|
99
100
|
|
|
100
101
|
try {
|
|
102
|
+
const handler = await loader();
|
|
101
103
|
result = await handler(parsed);
|
|
102
104
|
} catch (error) {
|
|
103
105
|
console.error(error.message);
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical depth parameter table.
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for all depth-dependent behavior across the pipeline.
|
|
5
|
+
* Skills reference these values conceptually; hooks and tooling import directly.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export const DEPTH_LEVELS = new Set(['quick', 'standard', 'deep']);
|
|
9
|
+
|
|
10
|
+
export const DEPTH_TABLE = {
|
|
11
|
+
quick: {
|
|
12
|
+
review_passes: 3,
|
|
13
|
+
loop_cap: 5,
|
|
14
|
+
heartbeat_max_silence_s: 180,
|
|
15
|
+
research_intensity: 'minimal',
|
|
16
|
+
challenge_intensity: 'surface',
|
|
17
|
+
spec_hardening_passes: 1,
|
|
18
|
+
design_review_passes: 1,
|
|
19
|
+
time_estimate_label: '~15-30 min',
|
|
20
|
+
},
|
|
21
|
+
standard: {
|
|
22
|
+
review_passes: 5,
|
|
23
|
+
loop_cap: 10,
|
|
24
|
+
heartbeat_max_silence_s: 120,
|
|
25
|
+
research_intensity: 'balanced',
|
|
26
|
+
challenge_intensity: 'balanced',
|
|
27
|
+
spec_hardening_passes: 3,
|
|
28
|
+
design_review_passes: 3,
|
|
29
|
+
time_estimate_label: '~45-90 min',
|
|
30
|
+
},
|
|
31
|
+
deep: {
|
|
32
|
+
review_passes: 7,
|
|
33
|
+
loop_cap: 15,
|
|
34
|
+
heartbeat_max_silence_s: 90,
|
|
35
|
+
research_intensity: 'thorough',
|
|
36
|
+
challenge_intensity: 'adversarial',
|
|
37
|
+
spec_hardening_passes: 5,
|
|
38
|
+
design_review_passes: 5,
|
|
39
|
+
time_estimate_label: '~2-3 hrs',
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Get a specific depth parameter value.
|
|
45
|
+
*
|
|
46
|
+
* @param {string} depth — 'quick' | 'standard' | 'deep' (defaults to 'standard')
|
|
47
|
+
* @param {string} param — parameter name from the depth table
|
|
48
|
+
* @returns {*} the parameter value
|
|
49
|
+
*/
|
|
50
|
+
export function getDepthParam(depth, param) {
|
|
51
|
+
const level = depth ?? 'standard';
|
|
52
|
+
if (!DEPTH_LEVELS.has(level)) {
|
|
53
|
+
throw new Error(`Unknown depth level: "${level}". Valid levels: ${[...DEPTH_LEVELS].join(', ')}`);
|
|
54
|
+
}
|
|
55
|
+
const entry = DEPTH_TABLE[level];
|
|
56
|
+
if (!(param in entry)) {
|
|
57
|
+
throw new Error(`Unknown depth parameter: "${param}". Valid params: ${Object.keys(entry).join(', ')}`);
|
|
58
|
+
}
|
|
59
|
+
return entry[param];
|
|
60
|
+
}
|
|
@@ -91,8 +91,7 @@ function renderCommonInstructions(host, manifest) {
|
|
|
91
91
|
const DEFAULT_CLAUDE_HOOKS = {
|
|
92
92
|
hooks: {
|
|
93
93
|
PreToolUse: [
|
|
94
|
-
{ matcher: 'Write|Edit', hooks: [{ type: 'command', command: './hooks/
|
|
95
|
-
{ matcher: 'Bash', hooks: [{ type: 'command', command: './hooks/context-mode-router' }] },
|
|
94
|
+
{ matcher: 'Write|Edit|Bash', hooks: [{ type: 'command', command: './hooks/pretooluse-dispatcher' }] },
|
|
96
95
|
],
|
|
97
96
|
SessionStart: [
|
|
98
97
|
{ hooks: [{ type: 'command', command: './hooks/loop-cap-guard' }] },
|
|
@@ -115,21 +114,21 @@ function renderCursorHooks() {
|
|
|
115
114
|
return JSON.stringify({
|
|
116
115
|
hooks: [
|
|
117
116
|
{
|
|
118
|
-
name: '
|
|
119
|
-
command: './hooks/
|
|
117
|
+
name: 'pretooluse-dispatcher',
|
|
118
|
+
command: './hooks/pretooluse-dispatcher',
|
|
120
119
|
},
|
|
121
120
|
{
|
|
122
121
|
name: 'loop-cap-guard',
|
|
123
122
|
command: './hooks/loop-cap-guard',
|
|
124
123
|
},
|
|
125
|
-
{
|
|
126
|
-
name: 'context-mode-router',
|
|
127
|
-
command: './hooks/context-mode-router',
|
|
128
|
-
},
|
|
129
124
|
{
|
|
130
125
|
name: 'session-start',
|
|
131
126
|
command: './hooks/session-start',
|
|
132
127
|
},
|
|
128
|
+
{
|
|
129
|
+
name: 'stop-pipeline-gate',
|
|
130
|
+
command: './hooks/stop-pipeline-gate',
|
|
131
|
+
},
|
|
133
132
|
],
|
|
134
133
|
}, null, 2);
|
|
135
134
|
}
|