@polderlabs/bizar 10.7.0 → 10.7.2
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/.claude/agents/_shared/AGENT_BASELINE.md +266 -0
- package/.claude/agents/_shared/CLAUDE_TOOLS.md +412 -0
- package/.claude/agents/_shared/SKILLS.md +109 -0
- package/.claude/agents/brand-designer.md +55 -0
- package/.claude/agents/exec-assistant.md +34 -0
- package/.claude/agents/help-desk.md +44 -0
- package/.claude/agents/it-lead.md +53 -0
- package/.claude/agents/knowledge-manager.md +49 -0
- package/.claude/agents/office-coordinator.md +39 -0
- package/.claude/agents/office-greeter.md +53 -0
- package/.claude/agents/office-manager.md +287 -0
- package/.claude/agents/principal-engineer.md +58 -0
- package/.claude/agents/qa-reviewer.md +51 -0
- package/.claude/agents/research-analyst.md +53 -0
- package/.claude/agents/senior-engineer.md +55 -0
- package/.claude/agents/support-tech.md +87 -0
- package/.claude/agents/vp-engineering.md +54 -0
- package/.claude/commands/audit.md +48 -0
- package/.claude/commands/bizar.md +22 -0
- package/.claude/commands/cron.md +36 -0
- package/.claude/commands/explain.md +17 -0
- package/.claude/commands/goal.md +99 -0
- package/.claude/commands/init.md +15 -0
- package/.claude/commands/learn.md +46 -0
- package/.claude/commands/plan.md +35 -0
- package/.claude/commands/plow-through.md +50 -0
- package/.claude/commands/pr-review.md +49 -0
- package/.claude/commands/setup-provider.md +96 -0
- package/.claude/commands/spec.md +47 -0
- package/.claude/commands/sprint.md +43 -0
- package/.claude/commands/tailscale-serve.md +100 -0
- package/.claude/commands/team.md +132 -0
- package/.claude/commands/test.md +62 -0
- package/.claude/commands/validate.md +68 -0
- package/.claude/commands/visual-plan.md +24 -0
- package/.claude/hooks/README.md +108 -0
- package/.claude/hooks/__tests__/pretooluse-editwrite.test.mjs +146 -0
- package/.claude/hooks/__tests__/sessionend-recall.test.mjs +256 -0
- package/.claude/hooks/__tests__/sessionstart-prime.test.mjs +325 -0
- package/.claude/hooks/__tests__/thinking-route.test.mjs +319 -0
- package/.claude/hooks/auto-instinct.sh +81 -0
- package/.claude/hooks/learning-extract.mjs +92 -0
- package/.claude/hooks/post-merge-audit.sh +93 -0
- package/.claude/hooks/posttooluse-editwrite.mjs +91 -0
- package/.claude/hooks/pretooluse-bash.mjs +87 -0
- package/.claude/hooks/pretooluse-editwrite.mjs +117 -0
- package/.claude/hooks/sessionend-recall.mjs +384 -0
- package/.claude/hooks/sessionstart-prime.mjs +278 -0
- package/.claude/hooks/thinking-route.mjs +314 -0
- package/.claude/hooks/worker-suggest.mjs +110 -0
- package/.claude/settings.json +167 -0
- package/.claude/skills/9router/SKILL.md +80 -0
- package/.claude/skills/9router-chat/SKILL.md +73 -0
- package/.claude/skills/9router-embeddings/SKILL.md +69 -0
- package/.claude/skills/9router-image/SKILL.md +86 -0
- package/.claude/skills/9router-stt/SKILL.md +79 -0
- package/.claude/skills/9router-tts/SKILL.md +80 -0
- package/.claude/skills/9router-web-fetch/SKILL.md +99 -0
- package/.claude/skills/9router-web-search/SKILL.md +91 -0
- package/.claude/skills/agent-browser/SKILL.md +64 -0
- package/.claude/skills/bizar/README.md +9 -0
- package/.claude/skills/bizar/SKILL.md +447 -0
- package/.claude/skills/cpp-coding-standards/README.md +28 -0
- package/.claude/skills/cpp-coding-standards/SKILL.md +634 -0
- package/.claude/skills/cpp-coding-standards/references/concurrency.md +320 -0
- package/.claude/skills/cpp-coding-standards/references/error-handling.md +229 -0
- package/.claude/skills/cpp-coding-standards/references/memory-safety.md +216 -0
- package/.claude/skills/cpp-coding-standards/references/modern-idioms.md +282 -0
- package/.claude/skills/cpp-coding-standards/references/review-checklist.md +96 -0
- package/.claude/skills/cpp-testing/README.md +28 -0
- package/.claude/skills/cpp-testing/SKILL.md +304 -0
- package/.claude/skills/cpp-testing/references/coverage.md +370 -0
- package/.claude/skills/cpp-testing/references/framework-compare.md +175 -0
- package/.claude/skills/cpp-testing/references/host-test-for-embedded.md +499 -0
- package/.claude/skills/cpp-testing/references/mocking.md +364 -0
- package/.claude/skills/cpp-testing/references/tdd-workflow.md +308 -0
- package/.claude/skills/cubesandbox/SKILL.md +148 -0
- package/.claude/skills/de-sloppify/SKILL.md +38 -0
- package/.claude/skills/de-sloppify/cleanup.mjs +253 -0
- package/.claude/skills/de-sloppify/cleanup.test.mjs +189 -0
- package/.claude/skills/embedded-esp-idf/README.md +41 -0
- package/.claude/skills/embedded-esp-idf/SKILL.md +439 -0
- package/.claude/skills/embedded-esp-idf/references/freertos-patterns.md +214 -0
- package/.claude/skills/embedded-esp-idf/references/host-tests.md +164 -0
- package/.claude/skills/embedded-esp-idf/references/idf-py-commands.md +157 -0
- package/.claude/skills/embedded-esp-idf/references/kconfig.md +159 -0
- package/.claude/skills/embedded-esp-idf/references/logging-discipline.md +118 -0
- package/.claude/skills/embedded-esp-idf/references/memory-and-iram.md +137 -0
- package/.claude/skills/embedded-esp-idf/references/nvs.md +121 -0
- package/.claude/skills/embedded-esp-idf/references/packed-structs.md +192 -0
- package/.claude/skills/embedded-esp-idf/scripts/idf_env.sh +47 -0
- package/.claude/skills/embedded-esp-idf/scripts/size_check.sh +77 -0
- package/.claude/skills/glyph/SKILL.md +163 -0
- package/.claude/skills/harness-engineering/SKILL.md +142 -0
- package/.claude/skills/lightrag/SKILL.md +81 -0
- package/.claude/skills/memory-protocol/SKILL.md +105 -0
- package/.claude/skills/obsidian/SKILL.md +306 -0
- package/.claude/skills/read-the-damn-docs/SKILL.md +113 -0
- package/.claude/skills/self-improvement/SKILL.md +64 -0
- package/.claude/skills/skillopt/SKILL.md +129 -0
- package/.claude/skills/thinking-archetypes/SKILL.md +90 -0
- package/.claude/skills/thinking-bayesian/SKILL.md +267 -0
- package/.claude/skills/thinking-bounded-rationality/SKILL.md +406 -0
- package/.claude/skills/thinking-circle-of-competence/SKILL.md +216 -0
- package/.claude/skills/thinking-cynefin/SKILL.md +70 -0
- package/.claude/skills/thinking-debiasing/SKILL.md +192 -0
- package/.claude/skills/thinking-dual-process/SKILL.md +282 -0
- package/.claude/skills/thinking-effectuation/SKILL.md +366 -0
- package/.claude/skills/thinking-feedback-loops/SKILL.md +464 -0
- package/.claude/skills/thinking-fermi-estimation/SKILL.md +263 -0
- package/.claude/skills/thinking-first-principles/SKILL.md +167 -0
- package/.claude/skills/thinking-five-whys-plus/SKILL.md +139 -0
- package/.claude/skills/thinking-inversion/SKILL.md +195 -0
- package/.claude/skills/thinking-jobs-to-be-done/SKILL.md +363 -0
- package/.claude/skills/thinking-kepner-tregoe/SKILL.md +154 -0
- package/.claude/skills/thinking-leverage-points/SKILL.md +390 -0
- package/.claude/skills/thinking-lindy-effect/SKILL.md +331 -0
- package/.claude/skills/thinking-map-territory/SKILL.md +111 -0
- package/.claude/skills/thinking-margin-of-safety/SKILL.md +330 -0
- package/.claude/skills/thinking-model-combination/SKILL.md +406 -0
- package/.claude/skills/thinking-model-router/SKILL.md +360 -0
- package/.claude/skills/thinking-model-selection/SKILL.md +341 -0
- package/.claude/skills/thinking-occams-razor/SKILL.md +129 -0
- package/.claude/skills/thinking-ooda/SKILL.md +127 -0
- package/.claude/skills/thinking-opportunity-cost/SKILL.md +360 -0
- package/.claude/skills/thinking-pre-mortem/SKILL.md +170 -0
- package/.claude/skills/thinking-probabilistic/SKILL.md +324 -0
- package/.claude/skills/thinking-red-team/SKILL.md +142 -0
- package/.claude/skills/thinking-regret-minimization/SKILL.md +335 -0
- package/.claude/skills/thinking-reversibility/SKILL.md +326 -0
- package/.claude/skills/thinking-scientific-method/SKILL.md +162 -0
- package/.claude/skills/thinking-second-order/SKILL.md +184 -0
- package/.claude/skills/thinking-socratic/SKILL.md +198 -0
- package/.claude/skills/thinking-steel-manning/SKILL.md +332 -0
- package/.claude/skills/thinking-systems/SKILL.md +238 -0
- package/.claude/skills/thinking-theory-of-constraints/SKILL.md +338 -0
- package/.claude/skills/thinking-thought-experiment/SKILL.md +354 -0
- package/.claude/skills/thinking-triz/SKILL.md +171 -0
- package/.claude/skills/thinking-via-negativa/SKILL.md +358 -0
- package/bizar-dash/skills/publishing/SKILL.md +2 -1
- package/cli/install/postinstall.mjs +54 -28
- package/cli/install/postinstall.test.mjs +98 -0
- package/cli/provision.mjs +29 -0
- package/install.sh +7 -0
- package/package.json +7 -2
- package/scripts/git-hooks/__tests__/commit-msg.test.mjs +61 -0
- package/scripts/git-hooks/commit-msg +38 -0
- package/scripts/install-hooks.sh +9 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* sessionstart-prime.mjs — Claude Code SessionStart hook.
|
|
4
|
+
*
|
|
5
|
+
* Primes every Bizar session with project-state context so the first turn
|
|
6
|
+
* can ship instead of spending 5 turns orienting.
|
|
7
|
+
*
|
|
8
|
+
* Reads (lazily, best-effort — never throws):
|
|
9
|
+
* 1. PROGRESS.md → "Current State" line + last `## In Progress` paragraph
|
|
10
|
+
* 2. feature_list.json → totals + active feature (WIP=1 guard)
|
|
11
|
+
* 3. git log --oneline -10 → recent commits
|
|
12
|
+
* 4. .bizar/PROJECT.md → project name + one-line summary
|
|
13
|
+
*
|
|
14
|
+
* Branches on `source`:
|
|
15
|
+
* - startup → all 4 sources, full briefing
|
|
16
|
+
* - clear → PROGRESS.md + last commit only
|
|
17
|
+
* - resume → reads .bizar/session-state.json (handoff from SessionEnd)
|
|
18
|
+
*
|
|
19
|
+
* Claude Code SessionStart input:
|
|
20
|
+
* { session_id, transcript_path, cwd, hook_event_name, source }
|
|
21
|
+
*
|
|
22
|
+
* Claude Code SessionStart output:
|
|
23
|
+
* { hookSpecificOutput: { hookEventName, additionalContext: <briefing> } }
|
|
24
|
+
*
|
|
25
|
+
* Always exits 0 — priming is a hint, not a gate.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
'use strict';
|
|
29
|
+
|
|
30
|
+
import { readFileSync, existsSync, readdirSync } from 'node:fs';
|
|
31
|
+
import { spawnSync } from 'node:child_process';
|
|
32
|
+
import { join, dirname } from 'node:path';
|
|
33
|
+
import { fileURLToPath } from 'node:url';
|
|
34
|
+
|
|
35
|
+
const MAX_BRIEFING = 800; // hard cap, characters
|
|
36
|
+
const PROJECT_NAME = 'BizarHarness';
|
|
37
|
+
const HOOK_LOG_DIR = '.config/bizar/hook-logs';
|
|
38
|
+
const SESSION_STATE = '.bizar/session-state.json';
|
|
39
|
+
|
|
40
|
+
// ── Tiny helpers (no external deps) ────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
function readIfExists(absPath) {
|
|
43
|
+
try {
|
|
44
|
+
if (!existsSync(absPath)) return null;
|
|
45
|
+
return readFileSync(absPath, 'utf8');
|
|
46
|
+
} catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function clip(s, n) {
|
|
52
|
+
if (!s) return '';
|
|
53
|
+
const oneLine = s.replace(/\s+/g, ' ').trim();
|
|
54
|
+
return oneLine.length <= n ? oneLine : oneLine.slice(0, n - 1) + '…';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function lineAfter(src, marker) {
|
|
58
|
+
if (!src) return '';
|
|
59
|
+
const lines = src.split('\n');
|
|
60
|
+
const i = lines.findIndex((l) => l.startsWith(marker));
|
|
61
|
+
return i >= 0 && i + 1 < lines.length ? clip(lines[i + 1], 240) : '';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function firstParagraphAfter(src, marker) {
|
|
65
|
+
if (!src) return '';
|
|
66
|
+
const lines = src.split('\n');
|
|
67
|
+
const i = lines.findIndex((l) => l.startsWith(marker));
|
|
68
|
+
if (i < 0) return '';
|
|
69
|
+
const buf = [];
|
|
70
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
71
|
+
const line = lines[j].trim();
|
|
72
|
+
if (line === '' || line.startsWith('## ')) break;
|
|
73
|
+
if (line.startsWith('#') || line.startsWith('-')) continue;
|
|
74
|
+
buf.push(line);
|
|
75
|
+
if (buf.length >= 4) break;
|
|
76
|
+
}
|
|
77
|
+
return clip(buf.filter(Boolean).join(' '), 240);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function logLifecycle(cwd, sessionId, source) {
|
|
81
|
+
try {
|
|
82
|
+
const dir = join(cwd, HOOK_LOG_DIR);
|
|
83
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
84
|
+
const logFile = join(dir, `task-start-${today}.jsonl`);
|
|
85
|
+
require('node:fs').mkdirSync(dir, { recursive: true });
|
|
86
|
+
require('node:fs').appendFileSync(
|
|
87
|
+
logFile,
|
|
88
|
+
JSON.stringify({
|
|
89
|
+
ts: new Date().toISOString(),
|
|
90
|
+
sessionId: sessionId || null,
|
|
91
|
+
source,
|
|
92
|
+
}) + '\n',
|
|
93
|
+
);
|
|
94
|
+
} catch {
|
|
95
|
+
/* best-effort */
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function gitRecent(cwd, n = 10) {
|
|
100
|
+
try {
|
|
101
|
+
const r = spawnSync('git', ['log', '--oneline', `-${n}`], {
|
|
102
|
+
cwd,
|
|
103
|
+
encoding: 'utf8',
|
|
104
|
+
timeout: 3000,
|
|
105
|
+
});
|
|
106
|
+
if (r.status !== 0 || !r.stdout) return [];
|
|
107
|
+
return r.stdout
|
|
108
|
+
.trim()
|
|
109
|
+
.split('\n')
|
|
110
|
+
.filter(Boolean)
|
|
111
|
+
.map((line) => clip(line, 80));
|
|
112
|
+
} catch {
|
|
113
|
+
return [];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function projectSummary(cwd) {
|
|
118
|
+
const src = readIfExists(join(cwd, '.bizar', 'PROJECT.md'));
|
|
119
|
+
if (!src) return '';
|
|
120
|
+
const lines = src.split('\n');
|
|
121
|
+
// First heading + first non-heading line (one-line summary).
|
|
122
|
+
let summary = '';
|
|
123
|
+
for (const line of lines.slice(0, 10)) {
|
|
124
|
+
const t = line.trim();
|
|
125
|
+
if (!t || t.startsWith('#')) continue;
|
|
126
|
+
summary = clip(t, 200);
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
return summary;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function featureListBrief(cwd) {
|
|
133
|
+
const path = join(cwd, 'feature_list.json');
|
|
134
|
+
const src = readIfExists(path);
|
|
135
|
+
if (!src) return null;
|
|
136
|
+
try {
|
|
137
|
+
const data = JSON.parse(src);
|
|
138
|
+
const features = Array.isArray(data.features) ? data.features : [];
|
|
139
|
+
const total = features.length;
|
|
140
|
+
const passing = features.filter((f) => f && f.state === 'passing').length;
|
|
141
|
+
const active = features
|
|
142
|
+
.filter((f) => f && f.state === 'active')
|
|
143
|
+
.map((f) => ({ id: f.id, behavior: clip(f.behavior || '', 120) }));
|
|
144
|
+
return { total, passing, active };
|
|
145
|
+
} catch {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function sessionState(cwd) {
|
|
151
|
+
const path = join(cwd, SESSION_STATE);
|
|
152
|
+
const src = readIfExists(path);
|
|
153
|
+
if (!src) return null;
|
|
154
|
+
try {
|
|
155
|
+
return JSON.parse(src);
|
|
156
|
+
} catch {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ── Briefing builders ──────────────────────────────────────────────────────
|
|
162
|
+
|
|
163
|
+
function startupBriefing(cwd, featureBrief, recentCommits, projectLine, progressLast) {
|
|
164
|
+
const lines = ['Bizar SessionStart (startup):'];
|
|
165
|
+
if (projectLine) lines.push(`- Project: ${projectLine}.`);
|
|
166
|
+
if (featureBrief) {
|
|
167
|
+
if (featureBrief.active.length === 0) {
|
|
168
|
+
lines.push(
|
|
169
|
+
`- Features: ${featureBrief.passing}/${featureBrief.total} passing. No active feature — pick next from not_started.`,
|
|
170
|
+
);
|
|
171
|
+
} else if (featureBrief.active.length === 1) {
|
|
172
|
+
const a = featureBrief.active[0];
|
|
173
|
+
lines.push(`- Active feature: ${a.id} — ${a.behavior}.`);
|
|
174
|
+
} else {
|
|
175
|
+
lines.push(
|
|
176
|
+
`- WIP VIOLATION: ${featureBrief.active.length} active features (${featureBrief.active.map((a) => a.id).join(', ')}). WIP=1 requires resolving extras to passing/not_started.`,
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (recentCommits.length > 0) {
|
|
181
|
+
lines.push(`- Last commit: ${recentCommits[0]}.`);
|
|
182
|
+
if (recentCommits.length > 1) {
|
|
183
|
+
lines.push(`- Recent: ${recentCommits.slice(1, 6).join(' | ')}.`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (progressLast) lines.push(`- Progress: ${progressLast}.`);
|
|
187
|
+
lines.push('- Rules: AGENT_BASELINE.md §4b thinking defaults active; WIP=1 honored.');
|
|
188
|
+
lines.push('- First move: confirm scope, then read PROGRESS.md and feature_list.json.');
|
|
189
|
+
return lines.join('\n');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function clearBriefing(cwd, recentCommits, progressLast) {
|
|
193
|
+
const lines = ['Bizar SessionStart (clear):'];
|
|
194
|
+
if (progressLast) lines.push(`- Progress: ${progressLast}.`);
|
|
195
|
+
if (recentCommits.length > 0) lines.push(`- Last commit: ${recentCommits[0]}.`);
|
|
196
|
+
lines.push('- Context preserved in same repo / cwd — only the model turn was reset.');
|
|
197
|
+
lines.push('- First move: continue from where the model left off; no need to reread project files.');
|
|
198
|
+
return lines.join('\n');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function resumeBriefing(cwd, state) {
|
|
202
|
+
const lines = ['Bizar SessionStart (resume):'];
|
|
203
|
+
if (state) {
|
|
204
|
+
if (state.activeFeature) lines.push(`- Last active feature: ${state.activeFeature}.`);
|
|
205
|
+
if (state.reason) lines.push(`- Last session ended with: ${state.reason}.`);
|
|
206
|
+
if (state.nextStep) lines.push(`- Last nextStep: ${state.nextStep}.`);
|
|
207
|
+
if (Array.isArray(state.filesTouched) && state.filesTouched.length > 0) {
|
|
208
|
+
lines.push(`- Last files touched: ${state.filesTouched.slice(0, 5).join(', ')}.`);
|
|
209
|
+
}
|
|
210
|
+
if (Array.isArray(state.blockers) && state.blockers.length > 0) {
|
|
211
|
+
lines.push(`- Open blockers: ${state.blockers.join(' | ')}.`);
|
|
212
|
+
}
|
|
213
|
+
if (state.lastSessionEnd) lines.push(`- Last session end: ${state.lastSessionEnd}.`);
|
|
214
|
+
lines.push('- WARNING: context may have been compacted since the last run; verify scope before continuing.');
|
|
215
|
+
} else {
|
|
216
|
+
lines.push('- No prior session-state.json found — treating as fresh start.');
|
|
217
|
+
lines.push('- First move: read PROGRESS.md and feature_list.json to orient.');
|
|
218
|
+
}
|
|
219
|
+
return lines.join('\n');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ── Main ───────────────────────────────────────────────────────────────────
|
|
223
|
+
|
|
224
|
+
function buildBriefing(input) {
|
|
225
|
+
const source = String(input.source || 'startup');
|
|
226
|
+
const cwd = String(input.cwd || process.cwd());
|
|
227
|
+
|
|
228
|
+
if (source === 'resume') {
|
|
229
|
+
const state = sessionState(cwd);
|
|
230
|
+
return clip(resumeBriefing(cwd, state), MAX_BRIEFING);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const progressSrc = readIfExists(join(cwd, 'PROGRESS.md'));
|
|
234
|
+
const progressLast = firstParagraphAfter(progressSrc, '## In Progress');
|
|
235
|
+
const recentCommits = gitRecent(cwd, 10);
|
|
236
|
+
const featureBrief = featureListBrief(cwd);
|
|
237
|
+
const projectLine = projectSummary(cwd);
|
|
238
|
+
|
|
239
|
+
if (source === 'clear') {
|
|
240
|
+
return clip(clearBriefing(cwd, recentCommits, progressLast), MAX_BRIEFING);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Default: startup.
|
|
244
|
+
return clip(
|
|
245
|
+
startupBriefing(cwd, featureBrief, recentCommits, projectLine, progressLast),
|
|
246
|
+
MAX_BRIEFING,
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
let raw = '';
|
|
251
|
+
process.stdin.setEncoding('utf8');
|
|
252
|
+
process.stdin.on('data', (chunk) => {
|
|
253
|
+
raw += chunk;
|
|
254
|
+
});
|
|
255
|
+
process.stdin.on('end', () => {
|
|
256
|
+
let input = {};
|
|
257
|
+
try {
|
|
258
|
+
input = JSON.parse(raw || '{}');
|
|
259
|
+
} catch {
|
|
260
|
+
input = {};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const source = String(input.source || 'startup');
|
|
264
|
+
const sessionId = String(input.session_id || '');
|
|
265
|
+
const cwd = String(input.cwd || process.cwd());
|
|
266
|
+
|
|
267
|
+
logLifecycle(cwd, sessionId, source);
|
|
268
|
+
|
|
269
|
+
const briefing = buildBriefing(input);
|
|
270
|
+
|
|
271
|
+
const out = {
|
|
272
|
+
hookSpecificOutput: {
|
|
273
|
+
hookEventName: 'SessionStart',
|
|
274
|
+
additionalContext: briefing,
|
|
275
|
+
},
|
|
276
|
+
};
|
|
277
|
+
process.stdout.write(JSON.stringify(out) + '\n');
|
|
278
|
+
});
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* .claude/hooks/thinking-route.mjs
|
|
4
|
+
*
|
|
5
|
+
* Bizar Thinking-Route — UserPromptSubmit hook.
|
|
6
|
+
*
|
|
7
|
+
* Runs on every user prompt. Performs a deterministic keyword/regex match
|
|
8
|
+
* against the prompt and emits a one-line suggestion pointing the agent at
|
|
9
|
+
* the most relevant `thinking-*` mental-model skill.
|
|
10
|
+
*
|
|
11
|
+
* No LLM call here — this is a *hint*, not a router call. The router skill
|
|
12
|
+
* itself (thinking-model-router) does LLM-driven routing when invoked. The
|
|
13
|
+
* hook just primes the next turn with a likely-relevant model name.
|
|
14
|
+
*
|
|
15
|
+
* Claude Code stdin shape (UserPromptSubmit):
|
|
16
|
+
* { "user_prompt": "raw prompt text", ... }
|
|
17
|
+
*
|
|
18
|
+
* Claude Code stdout shape (hookSpecificOutput.additionalContext):
|
|
19
|
+
* { "hookSpecificOutput": {
|
|
20
|
+
* "hookEventName": "UserPromptSubmit",
|
|
21
|
+
* "additionalContext": "Suggested mental model: ..."
|
|
22
|
+
* }}
|
|
23
|
+
*
|
|
24
|
+
* Always exits 0 — suggestion, not gate.
|
|
25
|
+
*/
|
|
26
|
+
'use strict';
|
|
27
|
+
|
|
28
|
+
// First-match wins. Order matters: more specific patterns come first.
|
|
29
|
+
const RULES = [
|
|
30
|
+
{
|
|
31
|
+
id: 'scientific-method',
|
|
32
|
+
pattern: /\b(debug|failing|broken|error|crash|exception|stack\s*trace|stacktrace|500|404|throws?|threw)\b/i,
|
|
33
|
+
rationale: 'a debugging symptom that needs ranking of falsifiable hypotheses before patching',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: 'five-whys-plus',
|
|
37
|
+
pattern: /\b(root\s*cause|why\s+is|why\s+does|why\s+did|why\s+are|why\s+won'?t|why\s+can'?t|recurring|keeps?\s+(happening|breaking|failing))\b/i,
|
|
38
|
+
rationale: 'an iterative drill into a recurring root cause',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: 'inversion',
|
|
42
|
+
pattern: /\b(invert|opposite|reverse|worst\s*case|how\s+(could|might)\s+(this|it)\s+fail|what\s+would\s+(cause|break)\s+(this|it))\b/i,
|
|
43
|
+
rationale: 'inverting the problem to find hidden failure modes',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: 'reversibility',
|
|
47
|
+
pattern: /\b(should\s+I|which\s+(to|should)|choose\s+between|pick\s+between|decide\s+between|type\s*1|type\s*2|one[- ]way\s+door|two[- ]way\s+door|reversib|irreversib)\b/i,
|
|
48
|
+
rationale: 'a Type 1 vs Type 2 decision that hinges on reversibility',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: 'regret-minimization',
|
|
52
|
+
pattern: /\b(regret|80[- ]year[- ]old|90[- ]year[- ]old|on\s+my\s+deathbed|future\s+me|will\s+I\s+(regret|wish))\b/i,
|
|
53
|
+
rationale: 'a high-stakes choice where minimizing long-term regret matters',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: 'opportunity-cost',
|
|
57
|
+
pattern: /\b(opportunity\s*cost|alternative|instead\s+of|foregone|trade[- ]off|tradeoff|giving\s+up|cost\s+of\s+not|what\s+am\s+I\s+(giving\s+up|missing))\b/i,
|
|
58
|
+
rationale: 'a choice where the value of the next-best alternative matters',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: 'bayesian',
|
|
62
|
+
pattern: /\b(prior|posterior|likelihood|update\s+(my|our|the)\s+(belief|probability|estimate)|bayesian|conditional\s+probability)\b/i,
|
|
63
|
+
rationale: 'updating a belief given new evidence',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: 'probabilistic',
|
|
67
|
+
pattern: /\b(odds|probability|likely|chance|expect(ed|ation)|confidence\s+interval|monte\s*carlo)\b/i,
|
|
68
|
+
rationale: 'reasoning under quantified uncertainty',
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
id: 'fermi-estimation',
|
|
72
|
+
pattern: /\b(estimate|how\s+(big|many|much|often)|rough\s+(number|estimate|order|size)|back[- ]of[- ]the[- ]envelope|order\s+of\s+magnitude)\b/i,
|
|
73
|
+
rationale: 'a number you can\'t measure but can decompose and bound',
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: 'first-principles',
|
|
77
|
+
pattern: /\b(first\s*principles|from\s+(scratch|first)|fundamental(s|ly)?|rethink|re[- ]?build\s+from\s+scratch|ground\s*up|underlying\s+truth)\b/i,
|
|
78
|
+
rationale: 'breaking an assumption to rebuild from fundamentals',
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: 'pre-mortem',
|
|
82
|
+
pattern: /\b(pre[- ]?mortem|assume\s+(it|we)\s+(failed|failed\b)|imagining\s+(it|we)\s+(fail|failure)|why\s+might\s+(this|it)\s+fail|what\s+could\s+go\s+wrong)\b/i,
|
|
83
|
+
rationale: 'imagining failure before launch to surface risks early',
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
id: 'red-team',
|
|
87
|
+
pattern: /\b(security|attack|attacker|vulnerab|injection|xss|csrf|auth\s*bypass|privilege\s*escalation|exploit|cve)\b/i,
|
|
88
|
+
rationale: 'a security review that needs an attacker mindset with reproducible attack paths',
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
id: 'kepner-tregoe',
|
|
92
|
+
pattern: /\b(kepner[- ]?tregoe|kt\s+analysis|is[/-]?is\s+(not|analysis)|force[- ]?field|swot|comparison\s+matrix)\b/i,
|
|
93
|
+
rationale: 'a structured decision or problem analysis across multiple alternatives',
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
id: 'systems',
|
|
97
|
+
pattern: /\b(system\s+(behavior|thinking|dynamics|as\s+a\s+whole)|feedback\s+loop|reinforcing\s+loop|balancing\s+loop|stock\s+and\s+flow)\b/i,
|
|
98
|
+
rationale: 'system-level behavior driven by feedback dynamics',
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
id: 'feedback-loops',
|
|
102
|
+
pattern: /\b(loop|self[- ]?reinforc|vi(cious|rtuous)|snowball|runaway|escalat\w*|amplif\w*)\b/i,
|
|
103
|
+
rationale: 'a reinforcing/balancing feedback loop driving the behavior',
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
id: 'archetypes',
|
|
107
|
+
pattern: /\b(archetype|fixes[- ]that[- ]fail|shifting\s+the\s+burden|tragedy\s+of\s+the\s+commons|drifting\s+goals|escalat\w+\s+to\s+the\s+top|success\s+to\s+the\s+successful)\b/i,
|
|
108
|
+
rationale: 'a recognizable system archetype pattern',
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
id: 'ooda',
|
|
112
|
+
pattern: /\b(ooda|observe[- ]orient[- ]decide[- ]act|rapid\s+iteration|fast\s+feedback\s+loop|competitive\s+cycle)\b/i,
|
|
113
|
+
rationale: 'rapid observe–orient–decide–act cycles under competitive pressure',
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
id: 'leverage-points',
|
|
117
|
+
pattern: /\b(leverage\s+point|where\s+to\s+intervene|small\s+change.*big\s+(effect|impact)|intervention\s+point)\b/i,
|
|
118
|
+
rationale: 'finding where a small change produces a large system-level effect',
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
id: 'theory-of-constraints',
|
|
122
|
+
pattern: /\b(bottleneck|constraint\s+theory|throughput\s+limit|what\'?s\s+(the\s+)?bottleneck|where\s+is\s+(the\s+)?bottleneck|five\s+focusing\s+steps)\b/i,
|
|
123
|
+
rationale: 'identifying the single constraint that limits throughput',
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
id: 'cynefin',
|
|
127
|
+
pattern: /\b(cynefin|complicated\s+vs\s+complex|clear\s+vs\s+complicated|which\s+domain|chaotic\s+context|complex\s+adaptive)\b/i,
|
|
128
|
+
rationale: 'classifying the problem domain to pick the right response approach',
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
id: 'jobs-to-be-done',
|
|
132
|
+
pattern: /\b(job(s)?\s+to\s+be\s+done|jtbd|hire\s+(the\s+)?product|what\s+progress|why\s+(do\s+(they|users|customers))\s+(hire|use|buy))\b/i,
|
|
133
|
+
rationale: 'reframing the feature around the user\'s underlying job',
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
id: 'effectuation',
|
|
137
|
+
pattern: /\b(effectuat|affordable\s+loss|bird\s+in\s+hand|crazy\s+quilt|pilot\s+in\s+the\s+plane|lemonade\s+(from\s+lemons)?)\b/i,
|
|
138
|
+
rationale: 'starting from means (not goals) under genuine uncertainty',
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
id: 'margin-of-safety',
|
|
142
|
+
pattern: /\b(margin\s+of\s+safety|buffer|headroom|over[- ]?provision|capacity\s+buffer|slack\s+in\s+the\s+(system|estimate))\b/i,
|
|
143
|
+
rationale: 'sizing a buffer to the cost of being wrong under uncertainty',
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
id: 'lindy-effect',
|
|
147
|
+
pattern: /\b(lindy|how\s+long\s+has\s+(it|this)\s+(been\s+around|existed|survived)|battle[- ]tested|proven\s+over\s+time|mature\s+(tech|technology|library))\b/i,
|
|
148
|
+
rationale: 'favoring the proven technology under a longevity lens',
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
id: 'occams-razor',
|
|
152
|
+
pattern: /\b(occam'?s?\s+razor|simpler\s+explanation|simplest\s+explanation|why\s+assume\s+complexity|least\s+assumptions)\b/i,
|
|
153
|
+
rationale: 'picking the simplest explanation consistent with the evidence',
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
id: 'map-territory',
|
|
157
|
+
pattern: /\b(map\s+(is\s+(not|isn\'?t)\s+(the\s+)?territory)|model\s+is\s+not|mental\s+model\s+limitations)\b/i,
|
|
158
|
+
rationale: 'recognizing that every model is a simplification of reality',
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
id: 'circle-of-competence',
|
|
162
|
+
pattern: /\b(circle\s+of\s+competence|in\s+my\s+wheelhouse|out\s+of\s+(my\s+)?depth|beyond\s+(my\s+)?expertise|i\s+don\'?t\s+know\s+enough)\b/i,
|
|
163
|
+
rationale: 'checking whether the question sits inside your expertise',
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
id: 'triz',
|
|
167
|
+
pattern: /\b(triz|technical\s+contradiction| inventive\s+principle|ideality|separation\s+principle)\b/i,
|
|
168
|
+
rationale: 'resolving a technical contradiction via inventive principles',
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
id: 'socratic',
|
|
172
|
+
pattern: /\b(socratic|what\s+do\s+you\s+(actually|really)\s+mean|challenge\s+(my|our)\s+(assumption|belief))\b/i,
|
|
173
|
+
rationale: 'using disciplined questioning to surface hidden assumptions',
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
id: 'steel-manning',
|
|
177
|
+
pattern: /\b(steel[\s-]*man(ned|ning)?|strongest\s+version|counter[- ]?argument|why\s+might\s+they\s+be\s+right|best\s+case\s+against)\b/i,
|
|
178
|
+
rationale: 'rebuilding the opposing argument in its strongest form before rebutting',
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
id: 'dual-process',
|
|
182
|
+
pattern: /\b(system\s*1\s+(vs|and)\s+system\s*2|dual\s+process|fast\s+(vs|and)\s+slow\s+thinking|intuition\s+vs\s+analysis)\b/i,
|
|
183
|
+
rationale: 'separating intuitive (fast) from analytical (slow) reasoning modes',
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
id: 'bounded-rationality',
|
|
187
|
+
pattern: /\b(bounded\s+rationality|satisfic|good\s*enough\s+(answer|decision)|stopping\s+rule|when\s+to\s+stop\s+(searching|optimizing))\b/i,
|
|
188
|
+
rationale: 'choosing a good-enough answer under search-cost limits',
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
id: 'debiasing',
|
|
192
|
+
pattern: /\b(bias|biased|cognitive\s+(bias|trap)|confirmation|anchoring|availability\s+heuristic|sunk\s+cost|status\s*quo\s+bias)\b/i,
|
|
193
|
+
rationale: 'naming the bias at risk and neutralizing it',
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
id: 'thought-experiment',
|
|
197
|
+
pattern: /\b(thought\s+experiment|imagine\s+(if|that)|what\s+if\s+we\s+had|10x\s+(traffic|scale|users)|scaling\s+to\s+\d+x)\b/i,
|
|
198
|
+
rationale: 'imagining a scenario to walk the consequence chain',
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
id: 'second-order',
|
|
202
|
+
pattern: /\b(second[- ]?order|and\s+then\s+what|downstream\s+(effect|consequence)|cascad\w+|what\s+happens\s+next)\b/i,
|
|
203
|
+
rationale: 'tracing second- and third-order consequences of a decision',
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
id: 'via-negativa',
|
|
207
|
+
pattern: /\b(via\s+negativa|what\s+(should\s+)?(we|i)\s+(remove|stop|delete|cut)|subtract\w*|simplif\w+|less\s+is\s+more|remove\s+instead\s+of\s+add)\b/i,
|
|
208
|
+
rationale: 'asking what to remove before adding anything new',
|
|
209
|
+
},
|
|
210
|
+
];
|
|
211
|
+
|
|
212
|
+
function route(prompt) {
|
|
213
|
+
if (!prompt) return null;
|
|
214
|
+
for (const rule of RULES) {
|
|
215
|
+
if (rule.pattern.test(prompt)) return rule;
|
|
216
|
+
}
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
let raw = '';
|
|
221
|
+
process.stdin.setEncoding('utf8');
|
|
222
|
+
process.stdin.on('data', (chunk) => { raw += chunk; });
|
|
223
|
+
process.stdin.on('end', () => {
|
|
224
|
+
let input = {};
|
|
225
|
+
try {
|
|
226
|
+
input = JSON.parse(raw || '{}');
|
|
227
|
+
} catch (err) {
|
|
228
|
+
process.stderr.write(
|
|
229
|
+
`[bizar.thinking-route] WARN: malformed JSON on stdin: ${
|
|
230
|
+
err && err.message ? err.message : String(err)
|
|
231
|
+
}\n`,
|
|
232
|
+
);
|
|
233
|
+
input = {};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const prompt = String(input.user_prompt || '').trim();
|
|
237
|
+
|
|
238
|
+
// Empty / whitespace-only → silent. No commit, no model.
|
|
239
|
+
if (prompt.length === 0) {
|
|
240
|
+
process.stdout.write(JSON.stringify({
|
|
241
|
+
hookSpecificOutput: {
|
|
242
|
+
hookEventName: 'UserPromptSubmit',
|
|
243
|
+
additionalContext: '',
|
|
244
|
+
},
|
|
245
|
+
}) + '\n');
|
|
246
|
+
process.exit(0);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Slash-command routing — fires BEFORE the mental-model router.
|
|
251
|
+
// First-match-wins: longer / more specific commands listed first.
|
|
252
|
+
const SLASH = [
|
|
253
|
+
{ cmd: '/plow-through', note: 'Autonomous mode — dispatch 2+ parallel agents when possible; run /test before claiming done.' },
|
|
254
|
+
{ cmd: '/pr-review', note: 'Review a PR — read the diff, score against the rubric, write findings.' },
|
|
255
|
+
{ cmd: '/setup-provider', note: 'Configure a provider (Anthropic / OpenRouter / Bedrock / Vertex / Foundry) for this Bizar install.' },
|
|
256
|
+
{ cmd: '/validate', note: 'Runs the 21-point Bizar install health check.' },
|
|
257
|
+
{ cmd: '/team', note: 'Odin spawns a coordinated team — confirm disjoint file scopes before dispatch.' },
|
|
258
|
+
{ cmd: '/plan', note: 'Enter plan mode — no edits until ExitPlanMode; write the plan file first.' },
|
|
259
|
+
{ cmd: '/audit', note: 'Run Forseti-style code-review audit on the current diff.' },
|
|
260
|
+
{ cmd: '/test', note: 'Auto-detects runner (jest/vitest/bun/pytest/cargo/go); streams output.' },
|
|
261
|
+
{ cmd: '/explain', note: 'Read-only explanation — dispatch susan (or general-purpose) to look up the answer with file:line refs.' },
|
|
262
|
+
{ cmd: '/visual-plan', note: 'Toggle the Bizar visual plan canvas or check its status.' },
|
|
263
|
+
{ cmd: '/tailscale-serve', note: 'Authenticate Tailscale and configure `tailscale serve` for a local port.' },
|
|
264
|
+
{ cmd: '/bizar', note: 'Bizar SDK passthrough — `npx bizar <subcommand>` (memory, plan, loops, graph).' },
|
|
265
|
+
{ cmd: '/init', note: 'Run `bizar init` to scaffold memory vault, project context, and graph index in this repo.' },
|
|
266
|
+
{ cmd: '/cron', note: 'Manage scheduled tasks via the Bizar SDK cron API — list / add / remove / pause.' },
|
|
267
|
+
{ cmd: '/spec', note: 'Generate a 5-section spec (Goal, Scope, DoD, Out-of-Scope, Open Questions); checks for duplicates first.' },
|
|
268
|
+
{ cmd: '/sprint', note: 'Read the next goal from `.bizar/PROGRESS.md` and pre-fill `templates/sprint-contract.md`.' },
|
|
269
|
+
{ cmd: '/goal', note: 'Add or update a long-term goal — Heimdall writes it to `.bizar/GOALS.md`.' },
|
|
270
|
+
{ cmd: '/learn', note: 'Heimdall reviews recent instincts + session transcripts and writes a self-improvement entry.' },
|
|
271
|
+
];
|
|
272
|
+
|
|
273
|
+
let slashNote = '';
|
|
274
|
+
for (const s of SLASH) {
|
|
275
|
+
if (prompt === s.cmd || prompt.startsWith(s.cmd + ' ') || prompt.startsWith(s.cmd + '\t')) {
|
|
276
|
+
slashNote = `[slash: ${s.cmd}] ${s.note}`;
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
let rule;
|
|
282
|
+
try {
|
|
283
|
+
rule = route(prompt);
|
|
284
|
+
} catch (err) {
|
|
285
|
+
process.stderr.write(
|
|
286
|
+
`[bizar.thinking-route] WARN: route() threw: ${
|
|
287
|
+
err && err.message ? err.message : String(err)
|
|
288
|
+
}\n`,
|
|
289
|
+
);
|
|
290
|
+
rule = null;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
let modelNote;
|
|
294
|
+
if (rule) {
|
|
295
|
+
modelNote = `Suggested mental model: thinking-${rule.id} — ${rule.rationale}. ` +
|
|
296
|
+
`Invoke via the Skill tool, or run thinking-model-router for a fuller classification.`;
|
|
297
|
+
} else if (slashNote) {
|
|
298
|
+
modelNote = 'Slash command detected — model router not invoked.';
|
|
299
|
+
} else {
|
|
300
|
+
modelNote = 'No keyword match. Use thinking-model-router (the upstream meta-skill) to classify this prompt and pick a thinking-* skill.';
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const note = slashNote
|
|
304
|
+
? `${slashNote} ${modelNote}`
|
|
305
|
+
: modelNote;
|
|
306
|
+
|
|
307
|
+
process.stdout.write(JSON.stringify({
|
|
308
|
+
hookSpecificOutput: {
|
|
309
|
+
hookEventName: 'UserPromptSubmit',
|
|
310
|
+
additionalContext: note,
|
|
311
|
+
},
|
|
312
|
+
}) + '\n');
|
|
313
|
+
process.exit(0);
|
|
314
|
+
});
|