specmem-hardwicksoftware 3.7.35 → 3.7.38
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 +34 -0
- package/README.md +11 -15
- package/bin/specmem-autoclaude.cjs +12 -1
- package/bin/specmem-cli.cjs +1077 -11
- package/bin/specmem-console.cjs +890 -63
- package/bootstrap.cjs +10 -2
- package/claude-hooks/agent-loading-hook.cjs +16 -16
- package/claude-hooks/agent-loading-hook.js +28 -21
- package/claude-hooks/agent-type-matcher.js +1 -1
- package/claude-hooks/background-completion-silencer.js +1 -1
- package/claude-hooks/file-claim-enforcer.cjs +37 -36
- package/claude-hooks/output-cleaner.cjs +1 -1
- package/claude-hooks/refusal-detector-hook.cjs +53 -0
- package/claude-hooks/settings.json +64 -4
- package/claude-hooks/smart-search-interceptor.js +1 -1
- package/claude-hooks/specmem-search-enforcer.cjs +2 -11
- package/claude-hooks/specmem-team-member-inject.js +1 -1
- package/claude-hooks/specmem-unified-hook.py +1 -1
- package/claude-hooks/subagent-loading-hook.cjs +1 -1
- package/claude-hooks/task-progress-hook.cjs +7 -7
- package/claude-hooks/task-progress-hook.js +3 -3
- package/claude-hooks/team-comms-enforcer.cjs +113 -47
- package/claude-hooks/use-code-pointers.cjs +1 -1
- package/dist/claude-sessions/sessionParser.js +5 -0
- package/dist/cli/deploy-to-claude.js +9 -2
- package/dist/codebase/codebaseIndexer.js +48 -17
- package/dist/codebase/exclusions.js +3 -4
- package/dist/codebase/index.js +4 -0
- package/dist/codebase/pdfExtractor.js +298 -0
- package/dist/dashboard/api/taskTeamMembers.js +2 -2
- package/dist/db/bigBrainMigrations.js +29 -0
- package/dist/hooks/hookManager.js +4 -4
- package/dist/hooks/teamFramingCli.js +1 -1
- package/dist/hooks/teamMemberPrepromptHook.js +5 -5
- package/dist/index.js +49 -12
- package/dist/init/claudeConfigInjector.js +27 -8
- package/dist/installer/autoInstall.js +7 -1
- package/dist/mcp/compactionProxy.js +1052 -192
- package/dist/mcp/compactionProxyDaemon.js +112 -37
- package/dist/mcp/contextVault.js +439 -0
- package/dist/mcp/embeddingServerManager.js +151 -17
- package/dist/mcp/mcpProtocolHandler.js +6 -1
- package/dist/mcp/miniCOTServerManager.js +82 -8
- package/dist/mcp/specMemServer.js +45 -10
- package/dist/mcp/toolRegistry.js +6 -0
- package/dist/startup/startupIndexing.js +14 -0
- package/dist/team-members/taskOrchestrator.js +3 -3
- package/dist/team-members/taskTeamMemberLogger.js +2 -2
- package/dist/tools/goofy/deployTeamMember.js +3 -3
- package/dist/tools/goofy/digInTheVault.js +81 -0
- package/dist/tools/goofy/findCodePointers.js +17 -0
- package/dist/tools/goofy/findWhatISaid.js +19 -0
- package/dist/tools/goofy/stashTheGoods.js +56 -0
- package/dist/tools/teamMemberDeployer.js +2 -2
- package/dist/watcher/changeHandler.js +65 -8
- package/dist/watcher/changeQueue.js +20 -1
- package/embedding-sandbox/frankenstein-embeddings.py +4 -3
- package/embedding-sandbox/mini-cot-service.py +11 -13
- package/embedding-sandbox/pdf-text-extract.py +208 -0
- package/package.json +1 -1
- package/scripts/deploy-hooks.cjs +12 -4
- package/scripts/fast-batch-embedder.cjs +2 -2
- package/scripts/force-retry.cjs +34 -0
- package/scripts/global-postinstall.cjs +97 -4
- package/scripts/poetic-abliteration.cjs +379 -0
- package/scripts/refusal-enforcer.cjs +88 -0
- package/scripts/specmem-init.cjs +222 -41
- package/specmem/model-config.json +6 -6
- package/specmem/supervisord.conf +1 -1
- package/svg-sections/readme-token-compaction.svg +246 -0
- package/claude-hooks/agent-chooser-hook.js +0 -179
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 1020" width="800" height="1020">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="bgGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
4
|
+
<stop offset="0%" style="stop-color:#0d1117"/>
|
|
5
|
+
<stop offset="100%" style="stop-color:#161b22"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
<linearGradient id="cardGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
8
|
+
<stop offset="0%" style="stop-color:#1c2128;stop-opacity:0.9"/>
|
|
9
|
+
<stop offset="100%" style="stop-color:#21262d;stop-opacity:0.85"/>
|
|
10
|
+
</linearGradient>
|
|
11
|
+
<linearGradient id="titleGrad" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
12
|
+
<stop offset="0%" style="stop-color:#00bfff"/>
|
|
13
|
+
<stop offset="50%" style="stop-color:#a855f7"/>
|
|
14
|
+
<stop offset="100%" style="stop-color:#00bfff"/>
|
|
15
|
+
</linearGradient>
|
|
16
|
+
<linearGradient id="cyanPurple" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
17
|
+
<stop offset="0%" style="stop-color:#00bfff"/>
|
|
18
|
+
<stop offset="100%" style="stop-color:#a855f7"/>
|
|
19
|
+
</linearGradient>
|
|
20
|
+
<linearGradient id="greenGrad" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
21
|
+
<stop offset="0%" style="stop-color:#22c55e"/>
|
|
22
|
+
<stop offset="100%" style="stop-color:#4ade80"/>
|
|
23
|
+
</linearGradient>
|
|
24
|
+
<linearGradient id="orangeGrad" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
25
|
+
<stop offset="0%" style="stop-color:#f59e0b"/>
|
|
26
|
+
<stop offset="100%" style="stop-color:#f78534"/>
|
|
27
|
+
</linearGradient>
|
|
28
|
+
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
|
|
29
|
+
<feGaussianBlur in="SourceAlpha" stdDeviation="3" result="blur"/>
|
|
30
|
+
<feFlood flood-color="#00bfff" flood-opacity="0.3"/>
|
|
31
|
+
<feComposite in2="blur" operator="in"/>
|
|
32
|
+
<feMerge><feMergeNode/><feMergeNode in="SourceGraphic"/></feMerge>
|
|
33
|
+
</filter>
|
|
34
|
+
<filter id="glowGreen" x="-20%" y="-20%" width="140%" height="140%">
|
|
35
|
+
<feGaussianBlur in="SourceAlpha" stdDeviation="4" result="blur"/>
|
|
36
|
+
<feFlood flood-color="#22c55e" flood-opacity="0.4"/>
|
|
37
|
+
<feComposite in2="blur" operator="in"/>
|
|
38
|
+
<feMerge><feMergeNode/><feMergeNode in="SourceGraphic"/></feMerge>
|
|
39
|
+
</filter>
|
|
40
|
+
</defs>
|
|
41
|
+
|
|
42
|
+
<!-- Background -->
|
|
43
|
+
<rect width="800" height="1020" rx="16" ry="16" fill="url(#bgGrad)"/>
|
|
44
|
+
<rect width="800" height="1020" rx="16" ry="16" fill="none" stroke="url(#cyanPurple)" stroke-width="1" stroke-opacity="0.2"/>
|
|
45
|
+
|
|
46
|
+
<!-- Title Section -->
|
|
47
|
+
<text x="400" y="36" text-anchor="middle" font-family="system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif" font-size="20" font-weight="700" fill="url(#titleGrad)">Token Compaction Pipeline</text>
|
|
48
|
+
<text x="400" y="56" text-anchor="middle" font-family="system-ui, sans-serif" font-size="11" fill="#6e7681">5-layer MITM proxy between Claude Code and Anthropic API. Every request gets optimized.</text>
|
|
49
|
+
|
|
50
|
+
<!-- Big stat banner -->
|
|
51
|
+
<rect x="40" y="72" width="720" height="70" rx="12" fill="url(#cardGrad)" stroke="#22c55e" stroke-width="1.5" filter="url(#glowGreen)"/>
|
|
52
|
+
<text x="200" y="103" text-anchor="middle" font-family="system-ui, sans-serif" font-size="36" font-weight="800" fill="#22c55e">~60%</text>
|
|
53
|
+
<text x="200" y="125" text-anchor="middle" font-family="system-ui, sans-serif" font-size="11" fill="#6e7681">input token reduction</text>
|
|
54
|
+
<text x="400" y="103" text-anchor="middle" font-family="system-ui, sans-serif" font-size="36" font-weight="800" fill="#00bfff">5</text>
|
|
55
|
+
<text x="400" y="125" text-anchor="middle" font-family="system-ui, sans-serif" font-size="11" fill="#6e7681">compression layers</text>
|
|
56
|
+
<text x="600" y="103" text-anchor="middle" font-family="system-ui, sans-serif" font-size="36" font-weight="800" fill="#a855f7">0ms</text>
|
|
57
|
+
<text x="600" y="125" text-anchor="middle" font-family="system-ui, sans-serif" font-size="11" fill="#6e7681">added latency (async)</text>
|
|
58
|
+
|
|
59
|
+
<!-- Pipeline flow label -->
|
|
60
|
+
<text x="400" y="170" text-anchor="middle" font-family="system-ui, sans-serif" font-size="14" font-weight="600" fill="#e6edf3">Request Flow: Claude Code → Compaction Proxy → Anthropic API</text>
|
|
61
|
+
|
|
62
|
+
<!-- ═══════════════════════════════════════════════════════ -->
|
|
63
|
+
<!-- LAYER 1: Context Vault (y=188, h=126) -->
|
|
64
|
+
<!-- ═══════════════════════════════════════════════════════ -->
|
|
65
|
+
<g transform="translate(30, 188)">
|
|
66
|
+
<rect width="740" height="126" rx="12" fill="url(#cardGrad)" stroke="#22c55e" stroke-width="1.5"/>
|
|
67
|
+
<rect width="740" height="3" rx="1.5" fill="url(#greenGrad)"/>
|
|
68
|
+
|
|
69
|
+
<circle cx="36" cy="30" r="14" fill="none" stroke="#22c55e" stroke-width="1.5" stroke-opacity="0.7"/>
|
|
70
|
+
<text x="36" y="35" text-anchor="middle" font-family="system-ui, sans-serif" font-size="13" font-weight="700" fill="#22c55e">1</text>
|
|
71
|
+
|
|
72
|
+
<text x="60" y="34" font-family="system-ui, sans-serif" font-size="14" font-weight="700" fill="#4ade80">🏦 Context Vault</text>
|
|
73
|
+
<rect x="210" y="20" width="90" height="20" rx="10" fill="#22c55e" fill-opacity="0.15" stroke="#22c55e" stroke-width="0.8" stroke-opacity="0.4"/>
|
|
74
|
+
<text x="255" y="34" text-anchor="middle" font-family="'SF Mono', 'Fira Code', Consolas, monospace" font-size="10" font-weight="600" fill="#4ade80">99% savings</text>
|
|
75
|
+
|
|
76
|
+
<text font-family="system-ui, sans-serif" font-size="11" fill="#8b949e">
|
|
77
|
+
<tspan x="24" y="58">Tool results larger than 5KB are auto-stashed to PostgreSQL with full-text indexing (tsvector).</tspan>
|
|
78
|
+
<tspan x="24" dy="15">Claude receives a compact receipt (~800 bytes) with preview, vocabulary, and retrieval commands.</tspan>
|
|
79
|
+
<tspan x="24" dy="15">On-demand retrieval via BM25-ranked search. 24h TTL with automatic expiry.</tspan>
|
|
80
|
+
</text>
|
|
81
|
+
|
|
82
|
+
<rect x="24" y="106" width="80" height="14" rx="3" fill="#ef4444" fill-opacity="0.2" stroke="#ef4444" stroke-width="0.8" stroke-opacity="0.4"/>
|
|
83
|
+
<text x="64" y="117" text-anchor="middle" font-family="'SF Mono', monospace" font-size="8" fill="#f87171">315KB result</text>
|
|
84
|
+
<text x="118" y="117" font-family="system-ui, sans-serif" font-size="11" fill="#484f58">→</text>
|
|
85
|
+
<rect x="138" y="106" width="68" height="14" rx="3" fill="#22c55e" fill-opacity="0.2" stroke="#22c55e" stroke-width="0.8" stroke-opacity="0.4"/>
|
|
86
|
+
<text x="172" y="117" text-anchor="middle" font-family="'SF Mono', monospace" font-size="8" fill="#4ade80">800B receipt</text>
|
|
87
|
+
<text x="225" y="117" font-family="'SF Mono', monospace" font-size="8" fill="#484f58">stash_the_goods() / dig_in_the_vault()</text>
|
|
88
|
+
</g>
|
|
89
|
+
|
|
90
|
+
<!-- Connector 1→2 -->
|
|
91
|
+
<line x1="66" y1="314" x2="66" y2="330" stroke="#30363d" stroke-width="2" stroke-dasharray="3,3"/>
|
|
92
|
+
<polygon points="62,328 66,336 70,328" fill="#30363d"/>
|
|
93
|
+
|
|
94
|
+
<!-- ═══════════════════════════════════════════════════════ -->
|
|
95
|
+
<!-- LAYER 2: Steno + YCC (y=336, h=180) -->
|
|
96
|
+
<!-- ═══════════════════════════════════════════════════════ -->
|
|
97
|
+
<g transform="translate(30, 336)">
|
|
98
|
+
<rect width="740" height="180" rx="12" fill="url(#cardGrad)" stroke="#00bfff" stroke-width="1.5"/>
|
|
99
|
+
<rect width="740" height="3" rx="1.5" fill="#00bfff"/>
|
|
100
|
+
|
|
101
|
+
<circle cx="36" cy="30" r="14" fill="none" stroke="#00bfff" stroke-width="1.5" stroke-opacity="0.7"/>
|
|
102
|
+
<text x="36" y="35" text-anchor="middle" font-family="system-ui, sans-serif" font-size="13" font-weight="700" fill="#00bfff">2</text>
|
|
103
|
+
|
|
104
|
+
<text x="60" y="34" font-family="system-ui, sans-serif" font-size="14" font-weight="700" fill="#00bfff">🤖 Steno Compression</text>
|
|
105
|
+
<rect x="240" y="20" width="90" height="20" rx="10" fill="#00bfff" fill-opacity="0.12" stroke="#00bfff" stroke-width="0.8" stroke-opacity="0.4"/>
|
|
106
|
+
<text x="285" y="34" text-anchor="middle" font-family="'SF Mono', 'Fira Code', Consolas, monospace" font-size="10" font-weight="600" fill="#00bfff">~40% savings</text>
|
|
107
|
+
|
|
108
|
+
<text font-family="system-ui, sans-serif" font-size="11" fill="#8b949e">
|
|
109
|
+
<tspan x="24" y="58">Strips ~80 filler words (the, a, is, are, was, were, of, to, in, for ...) that carry minimal semantic</tspan>
|
|
110
|
+
<tspan x="24" dy="15">meaning. Applies ~100 programming abbreviations: function→fn, configuration→cfg, database→db,</tspan>
|
|
111
|
+
<tspan x="24" dy="15">implementation→impl, authentication→auth, environment→env, repository→repo, and more.</tspan>
|
|
112
|
+
</text>
|
|
113
|
+
|
|
114
|
+
<!-- Before/after example -->
|
|
115
|
+
<rect x="24" y="104" width="340" height="14" rx="3" fill="#1a1f26" stroke="#21262d" stroke-width="0.8"/>
|
|
116
|
+
<text x="30" y="115" font-family="'SF Mono', monospace" font-size="8" fill="#6e7681">"the function configuration is required"</text>
|
|
117
|
+
<text x="372" y="115" font-family="system-ui, sans-serif" font-size="11" fill="#484f58">→</text>
|
|
118
|
+
<rect x="390" y="104" width="130" height="14" rx="3" fill="#00bfff" fill-opacity="0.1" stroke="#00bfff" stroke-width="0.8" stroke-opacity="0.3"/>
|
|
119
|
+
<text x="396" y="115" font-family="'SF Mono', monospace" font-size="8" fill="#58a6ff">"fn cfg reqd"</text>
|
|
120
|
+
<text x="540" y="115" font-family="system-ui, sans-serif" font-size="8" fill="#484f58">KEEP_WORDS: not, no, never, must, all</text>
|
|
121
|
+
|
|
122
|
+
<!-- ── YCC Sub-layer ── -->
|
|
123
|
+
<line x1="24" y1="128" x2="716" y2="128" stroke="#30363d" stroke-width="0.8" stroke-dasharray="4,3"/>
|
|
124
|
+
|
|
125
|
+
<!-- YC Logo -->
|
|
126
|
+
<rect x="24" y="137" width="36" height="36" rx="8" fill="#a855f7" fill-opacity="0.2" stroke="#a855f7" stroke-width="1.5" stroke-opacity="0.6"/>
|
|
127
|
+
<text x="42" y="161" text-anchor="middle" font-family="system-ui, sans-serif" font-size="16" font-weight="900" fill="#c084fc">YC</text>
|
|
128
|
+
|
|
129
|
+
<!-- Yung Cracka Compress title + badge -->
|
|
130
|
+
<text x="72" y="152" font-family="system-ui, sans-serif" font-size="13" font-weight="700" fill="#c084fc">🥷🏻 Yung Cracka Compress</text>
|
|
131
|
+
<rect x="260" y="140" width="68" height="18" rx="9" fill="#a855f7" fill-opacity="0.12" stroke="#a855f7" stroke-width="0.8" stroke-opacity="0.4"/>
|
|
132
|
+
<text x="294" y="153" text-anchor="middle" font-family="'SF Mono', monospace" font-size="9" font-weight="600" fill="#c084fc">opt-in</text>
|
|
133
|
+
<text x="340" y="153" font-family="'SF Mono', monospace" font-size="8" fill="#484f58">SLANG_ENABLED: false</text>
|
|
134
|
+
|
|
135
|
+
<!-- YCC description -->
|
|
136
|
+
<text x="72" y="172" font-family="system-ui, sans-serif" font-size="10" fill="#6e7681">DMV x Florida slang layer. 90+ entries LLMs already understand. definitely→def, probably→prolly, through→thru</text>
|
|
137
|
+
</g>
|
|
138
|
+
|
|
139
|
+
<!-- Connector 2→3 -->
|
|
140
|
+
<line x1="66" y1="516" x2="66" y2="532" stroke="#30363d" stroke-width="2" stroke-dasharray="3,3"/>
|
|
141
|
+
<polygon points="62,530 66,538 70,530" fill="#30363d"/>
|
|
142
|
+
|
|
143
|
+
<!-- ═══════════════════════════════════════════════════════ -->
|
|
144
|
+
<!-- LAYER 3: Neural MT (y=538, h=142) -->
|
|
145
|
+
<!-- ═══════════════════════════════════════════════════════ -->
|
|
146
|
+
<g transform="translate(30, 538)">
|
|
147
|
+
<rect width="740" height="142" rx="12" fill="url(#cardGrad)" stroke="#a855f7" stroke-width="1.5"/>
|
|
148
|
+
<rect width="740" height="3" rx="1.5" fill="#a855f7"/>
|
|
149
|
+
|
|
150
|
+
<circle cx="36" cy="30" r="14" fill="none" stroke="#a855f7" stroke-width="1.5" stroke-opacity="0.7"/>
|
|
151
|
+
<text x="36" y="35" text-anchor="middle" font-family="system-ui, sans-serif" font-size="13" font-weight="700" fill="#a855f7">3</text>
|
|
152
|
+
|
|
153
|
+
<text x="60" y="34" font-family="system-ui, sans-serif" font-size="14" font-weight="700" fill="#a855f7">🧑‍💻 Neural MT (EN→ZH)</text>
|
|
154
|
+
<rect x="270" y="20" width="105" height="20" rx="10" fill="#a855f7" fill-opacity="0.12" stroke="#a855f7" stroke-width="0.8" stroke-opacity="0.4"/>
|
|
155
|
+
<text x="322" y="34" text-anchor="middle" font-family="'SF Mono', 'Fira Code', Consolas, monospace" font-size="10" font-weight="600" fill="#a855f7">+30% on top</text>
|
|
156
|
+
|
|
157
|
+
<text font-family="system-ui, sans-serif" font-size="11" fill="#8b949e">
|
|
158
|
+
<tspan x="24" y="58">Steno'd tool_result text is translated EN→ZH via local Argos Translate (air-gapped, no API calls).</tspan>
|
|
159
|
+
<tspan x="24" dy="15">Loop-back verification: ZH→EN back-translation compared via LCS Dice + Jaccard (threshold 0.65).</tspan>
|
|
160
|
+
<tspan x="24" dy="15">Failed verifications fall back to steno-only. Passthrough vocab for untranslatable tech terms (webpack,</tspan>
|
|
161
|
+
<tspan x="24" dy="15">nginx, docker, etc). Word-level TM cache grows over time — repeated words skip the socket entirely.</tspan>
|
|
162
|
+
</text>
|
|
163
|
+
|
|
164
|
+
<!-- Verification flow diagram -->
|
|
165
|
+
<rect x="24" y="120" width="58" height="14" rx="3" fill="#00bfff" fill-opacity="0.1" stroke="#00bfff" stroke-width="0.8" stroke-opacity="0.3"/>
|
|
166
|
+
<text x="53" y="131" text-anchor="middle" font-family="'SF Mono', monospace" font-size="8" fill="#58a6ff">steno'd</text>
|
|
167
|
+
<text x="90" y="131" font-family="system-ui, sans-serif" font-size="9" fill="#484f58">→</text>
|
|
168
|
+
<rect x="102" y="120" width="34" height="14" rx="3" fill="#a855f7" fill-opacity="0.15" stroke="#a855f7" stroke-width="0.8" stroke-opacity="0.4"/>
|
|
169
|
+
<text x="119" y="131" text-anchor="middle" font-family="'SF Mono', monospace" font-size="8" fill="#c084fc">ZH</text>
|
|
170
|
+
<text x="144" y="131" font-family="system-ui, sans-serif" font-size="9" fill="#484f58">→</text>
|
|
171
|
+
<rect x="156" y="120" width="64" height="14" rx="3" fill="#f59e0b" fill-opacity="0.12" stroke="#f59e0b" stroke-width="0.8" stroke-opacity="0.3"/>
|
|
172
|
+
<text x="188" y="131" text-anchor="middle" font-family="'SF Mono', monospace" font-size="8" fill="#fbbf24">back-EN</text>
|
|
173
|
+
<text x="228" y="131" font-family="system-ui, sans-serif" font-size="9" fill="#484f58">→</text>
|
|
174
|
+
<rect x="242" y="120" width="48" height="14" rx="3" fill="#22c55e" fill-opacity="0.15" stroke="#22c55e" stroke-width="0.8" stroke-opacity="0.4"/>
|
|
175
|
+
<text x="266" y="131" text-anchor="middle" font-family="'SF Mono', monospace" font-size="8" fill="#4ade80">>=0.65</text>
|
|
176
|
+
<text x="298" y="131" font-family="system-ui, sans-serif" font-size="9" fill="#4ade80">use ZH</text>
|
|
177
|
+
<text x="350" y="131" font-family="system-ui, sans-serif" font-size="9" fill="#484f58">|</text>
|
|
178
|
+
<rect x="364" y="120" width="48" height="14" rx="3" fill="#ef4444" fill-opacity="0.15" stroke="#ef4444" stroke-width="0.8" stroke-opacity="0.4"/>
|
|
179
|
+
<text x="388" y="131" text-anchor="middle" font-family="'SF Mono', monospace" font-size="8" fill="#f87171"><0.65</text>
|
|
180
|
+
<text x="420" y="131" font-family="system-ui, sans-serif" font-size="9" fill="#f87171">keep steno</text>
|
|
181
|
+
<text x="490" y="131" font-family="system-ui, sans-serif" font-size="8" fill="#484f58">TM cache + synonym learning from failures</text>
|
|
182
|
+
</g>
|
|
183
|
+
|
|
184
|
+
<!-- Connector 3→4 -->
|
|
185
|
+
<line x1="66" y1="680" x2="66" y2="696" stroke="#30363d" stroke-width="2" stroke-dasharray="3,3"/>
|
|
186
|
+
<polygon points="62,694 66,702 70,694" fill="#30363d"/>
|
|
187
|
+
|
|
188
|
+
<!-- ═══════════════════════════════════════════════════════ -->
|
|
189
|
+
<!-- LAYER 4: Old-Message Stripping (y=702, h=114) -->
|
|
190
|
+
<!-- ═══════════════════════════════════════════════════════ -->
|
|
191
|
+
<g transform="translate(30, 702)">
|
|
192
|
+
<rect width="740" height="114" rx="12" fill="url(#cardGrad)" stroke="#f59e0b" stroke-width="1.5"/>
|
|
193
|
+
<rect width="740" height="3" rx="1.5" fill="url(#orangeGrad)"/>
|
|
194
|
+
|
|
195
|
+
<circle cx="36" cy="30" r="14" fill="none" stroke="#f59e0b" stroke-width="1.5" stroke-opacity="0.7"/>
|
|
196
|
+
<text x="36" y="35" text-anchor="middle" font-family="system-ui, sans-serif" font-size="13" font-weight="700" fill="#f59e0b">4</text>
|
|
197
|
+
|
|
198
|
+
<text x="60" y="34" font-family="system-ui, sans-serif" font-size="14" font-weight="700" fill="#fbbf24">🗑️ Old-Message Stripping</text>
|
|
199
|
+
<rect x="272" y="20" width="100" height="20" rx="10" fill="#f59e0b" fill-opacity="0.12" stroke="#f59e0b" stroke-width="0.8" stroke-opacity="0.4"/>
|
|
200
|
+
<text x="322" y="34" text-anchor="middle" font-family="'SF Mono', 'Fira Code', Consolas, monospace" font-size="10" font-weight="600" fill="#fbbf24">85%+ savings</text>
|
|
201
|
+
|
|
202
|
+
<text font-family="system-ui, sans-serif" font-size="11" fill="#8b949e">
|
|
203
|
+
<tspan x="24" y="58">tool_result content from messages outside the recent window (last 3 messages preserved) gets</tspan>
|
|
204
|
+
<tspan x="24" dy="15">replaced with a 200-char preview summary. Stale file reads, grep outputs, and old assistant text</tspan>
|
|
205
|
+
<tspan x="24" dy="15">blocks are trimmed to first-line anchors. SpecMem hook injections (SM-* markers) also stripped.</tspan>
|
|
206
|
+
</text>
|
|
207
|
+
|
|
208
|
+
<text x="24" y="106" font-family="'SF Mono', monospace" font-size="8" fill="#484f58">threshold: 100 chars | preview: 200 chars | preserves: tool_use_id, is_error, cache_control</text>
|
|
209
|
+
</g>
|
|
210
|
+
|
|
211
|
+
<!-- Connector 4→5 -->
|
|
212
|
+
<line x1="66" y1="816" x2="66" y2="832" stroke="#30363d" stroke-width="2" stroke-dasharray="3,3"/>
|
|
213
|
+
<polygon points="62,830 66,838 70,830" fill="#30363d"/>
|
|
214
|
+
|
|
215
|
+
<!-- ═══════════════════════════════════════════════════════ -->
|
|
216
|
+
<!-- LAYER 5: System-Reminder Stripping (y=838, h=114) -->
|
|
217
|
+
<!-- ═══════════════════════════════════════════════════════ -->
|
|
218
|
+
<g transform="translate(30, 838)">
|
|
219
|
+
<rect width="740" height="114" rx="12" fill="url(#cardGrad)" stroke="#ef4444" stroke-width="1.5"/>
|
|
220
|
+
<rect width="740" height="3" rx="1.5" fill="#ef4444"/>
|
|
221
|
+
|
|
222
|
+
<circle cx="36" cy="30" r="14" fill="none" stroke="#ef4444" stroke-width="1.5" stroke-opacity="0.7"/>
|
|
223
|
+
<text x="36" y="35" text-anchor="middle" font-family="system-ui, sans-serif" font-size="13" font-weight="700" fill="#ef4444">5</text>
|
|
224
|
+
|
|
225
|
+
<text x="60" y="34" font-family="system-ui, sans-serif" font-size="14" font-weight="700" fill="#f87171">🧹 System-Reminder Stripping</text>
|
|
226
|
+
<rect x="298" y="20" width="100" height="20" rx="10" fill="#ef4444" fill-opacity="0.12" stroke="#ef4444" stroke-width="0.8" stroke-opacity="0.4"/>
|
|
227
|
+
<text x="348" y="34" text-anchor="middle" font-family="'SF Mono', 'Fira Code', Consolas, monospace" font-size="10" font-weight="600" fill="#f87171">variable</text>
|
|
228
|
+
|
|
229
|
+
<text font-family="system-ui, sans-serif" font-size="11" fill="#8b949e">
|
|
230
|
+
<tspan x="24" y="58">Duplicate <system-reminder> tags stripped from messages. First instance is preserved (sets up</tspan>
|
|
231
|
+
<tspan x="24" dy="15">context/style), all subsequent identical reminders removed. System prompt dedup tracks content</tspan>
|
|
232
|
+
<tspan x="24" dy="15">hash — same prompt across requests is sent once, then stripped on repeat.</tspan>
|
|
233
|
+
</text>
|
|
234
|
+
|
|
235
|
+
<text x="24" y="106" font-family="'SF Mono', monospace" font-size="8" fill="#484f58">regex: /<system-reminder>[\s\S]*?<\/system-reminder>/g | keeps first, strips rest</text>
|
|
236
|
+
</g>
|
|
237
|
+
|
|
238
|
+
<!-- ═══════════════════════════════════════════════════════ -->
|
|
239
|
+
<!-- Result bar (y=970, h=44) -->
|
|
240
|
+
<!-- ═══════════════════════════════════════════════════════ -->
|
|
241
|
+
<rect x="30" y="968" width="740" height="44" rx="12" fill="#22c55e" fill-opacity="0.08" stroke="#22c55e" stroke-width="1.5" stroke-opacity="0.4"/>
|
|
242
|
+
<circle cx="66" cy="990" r="12" fill="none" stroke="#22c55e" stroke-width="2"/>
|
|
243
|
+
<path d="M60 990 L64 995 L74 984" fill="none" stroke="#4ade80" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
244
|
+
<text x="88" y="986" font-family="system-ui, sans-serif" font-size="13" font-weight="600" fill="#4ade80">Combined: ~57-60% total input token reduction.</text>
|
|
245
|
+
<text x="88" y="1003" font-family="system-ui, sans-serif" font-size="10" fill="#6e7681">All layers run on every request. Falls back gracefully — proxy never breaks the API call. Toggle via /config.</text>
|
|
246
|
+
</svg>
|
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Agent Chooser Hook - Interactive Agent Deployment
|
|
4
|
-
* ==================================================
|
|
5
|
-
*
|
|
6
|
-
* PreToolUse hook that INTERCEPTS Task tool calls and:
|
|
7
|
-
* 1. BLOCKS automatic deployment
|
|
8
|
-
* 2. Injects instructions for to ask user for preferences
|
|
9
|
-
* 3. User chooses agent type, model, settings PER DEPLOYMENT
|
|
10
|
-
*
|
|
11
|
-
* This gives users FULL CONTROL over every agent that gets deployed!
|
|
12
|
-
*
|
|
13
|
-
* Hook Event: PreToolUse
|
|
14
|
-
* Matcher: Task
|
|
15
|
-
*
|
|
16
|
-
* To disable interactive mode, set SPECMEM_AGENT_AUTO=1
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
const fs = require('fs');
|
|
20
|
-
const path = require('path');
|
|
21
|
-
|
|
22
|
-
// Check if auto mode is enabled (skip interactive)
|
|
23
|
-
const AUTO_MODE = process.env.SPECMEM_AGENT_AUTO === '1' || process.env.SPECMEM_AGENT_AUTO === 'true';
|
|
24
|
-
|
|
25
|
-
// Check if chooser is disabled
|
|
26
|
-
const CHOOSER_DISABLED = process.env.SPECMEM_NO_CHOOSER === '1' || process.env.SPECMEM_NO_CHOOSER === 'true';
|
|
27
|
-
|
|
28
|
-
// Marker to prevent re-processing
|
|
29
|
-
const CHOOSER_MARKER = '[AGENT_CHOOSER_CONFIRMED]';
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Load user config
|
|
33
|
-
*/
|
|
34
|
-
function loadConfig() {
|
|
35
|
-
const configPaths = [
|
|
36
|
-
path.join(process.cwd(), '.specmem', 'agent-config.json'),
|
|
37
|
-
path.join(process.env.HOME || '', '.specmem', 'agent-config.json')
|
|
38
|
-
];
|
|
39
|
-
|
|
40
|
-
for (const p of configPaths) {
|
|
41
|
-
try {
|
|
42
|
-
if (fs.existsSync(p)) {
|
|
43
|
-
return JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
44
|
-
}
|
|
45
|
-
} catch (e) {}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return {
|
|
49
|
-
defaults: { model: 'sonnet', background: true },
|
|
50
|
-
agents: {}
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Build the chooser context that instructs to ask the user
|
|
56
|
-
*/
|
|
57
|
-
function buildChooserContext(description, currentType, currentModel, config) {
|
|
58
|
-
const agentTypes = [
|
|
59
|
-
{ name: 'general-purpose', desc: 'Full toolset for complex multi-step tasks' },
|
|
60
|
-
{ name: 'Explore', desc: 'Fast codebase exploration and search' },
|
|
61
|
-
{ name: 'feature-dev:code-explorer', desc: 'Deep code analysis with tracing' },
|
|
62
|
-
{ name: 'feature-dev:code-architect', desc: 'Architecture design blueprints' },
|
|
63
|
-
{ name: 'feature-dev:code-reviewer', desc: 'Code review with confidence filtering' },
|
|
64
|
-
{ name: 'Bash', desc: 'Shell/git/CLI command specialist' },
|
|
65
|
-
{ name: 'Plan', desc: 'Architecture and implementation planning' }
|
|
66
|
-
];
|
|
67
|
-
|
|
68
|
-
const models = [
|
|
69
|
-
{ name: 'haiku', desc: 'Fastest, cheapest - simple tasks' },
|
|
70
|
-
{ name: 'sonnet', desc: 'Balanced speed and quality (default)' },
|
|
71
|
-
{ name: 'opus', desc: 'Deepest thinking - complex analysis' }
|
|
72
|
-
];
|
|
73
|
-
|
|
74
|
-
return `
|
|
75
|
-
[AGENT-CHOOSER]
|
|
76
|
-
🎛️ AGENT DEPLOYMENT PAUSED - Confirm settings
|
|
77
|
-
|
|
78
|
-
Task: "${description}"
|
|
79
|
-
Agent: ${currentType || 'general-purpose'} | Model: ${currentModel || config.defaults?.model || 'sonnet'}
|
|
80
|
-
|
|
81
|
-
Call AskUserQuestion NOW:
|
|
82
|
-
{
|
|
83
|
-
"questions": [
|
|
84
|
-
{
|
|
85
|
-
"question": "Agent for: ${description.slice(0, 40)}...?",
|
|
86
|
-
"header": "Agent",
|
|
87
|
-
"options": [
|
|
88
|
-
{"label": "${currentType || 'general-purpose'}", "description": "Current"},
|
|
89
|
-
{"label": "Explore", "description": "Fast search"},
|
|
90
|
-
{"label": "feature-dev:code-explorer", "description": "Deep analysis"},
|
|
91
|
-
{"label": "Plan", "description": "Architecture"}
|
|
92
|
-
],
|
|
93
|
-
"multiSelect": false
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
"question": "Model?",
|
|
97
|
-
"header": "Model",
|
|
98
|
-
"options": [
|
|
99
|
-
{"label": "sonnet", "description": "Balanced (recommended)"},
|
|
100
|
-
{"label": "opus", "description": "Deepest thinking"},
|
|
101
|
-
{"label": "haiku", "description": "Fastest"}
|
|
102
|
-
],
|
|
103
|
-
"multiSelect": false
|
|
104
|
-
}
|
|
105
|
-
]
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
After response: Re-deploy Task with choices + "${CHOOSER_MARKER}" in prompt.
|
|
109
|
-
[/AGENT-CHOOSER]
|
|
110
|
-
`;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
async function main() {
|
|
114
|
-
let input = '';
|
|
115
|
-
process.stdin.setEncoding('utf8');
|
|
116
|
-
for await (const chunk of process.stdin) {
|
|
117
|
-
input += chunk;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
try {
|
|
121
|
-
const data = JSON.parse(input);
|
|
122
|
-
const toolName = data.tool_name || '';
|
|
123
|
-
const toolInput = data.tool_input || {};
|
|
124
|
-
|
|
125
|
-
// Only intercept Task tool
|
|
126
|
-
if (toolName !== 'Task') {
|
|
127
|
-
process.exit(0);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Skip if chooser is disabled
|
|
131
|
-
if (CHOOSER_DISABLED) {
|
|
132
|
-
process.exit(0); // Let other hooks handle it
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Skip if auto mode is enabled
|
|
136
|
-
if (AUTO_MODE) {
|
|
137
|
-
process.exit(0); // Let other hooks handle it
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const prompt = toolInput.prompt || '';
|
|
141
|
-
const description = toolInput.description || 'agent task';
|
|
142
|
-
const agentType = toolInput.subagent_type || '';
|
|
143
|
-
const model = toolInput.model || '';
|
|
144
|
-
|
|
145
|
-
// Skip if already confirmed by user (has our marker)
|
|
146
|
-
if (prompt.includes(CHOOSER_MARKER)) {
|
|
147
|
-
// User confirmed - let it proceed (other hooks will process)
|
|
148
|
-
process.exit(0);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Load config for defaults
|
|
152
|
-
const config = loadConfig();
|
|
153
|
-
|
|
154
|
-
// Build the chooser context
|
|
155
|
-
const chooserContext = buildChooserContext(description, agentType, model, config);
|
|
156
|
-
|
|
157
|
-
// BLOCK the deployment and inject instructions for to ask user
|
|
158
|
-
console.log(JSON.stringify({
|
|
159
|
-
continue: false, // Block the tool call
|
|
160
|
-
stopReason: `Agent deployment requires user confirmation. Use AskUserQuestion to let user choose settings.`,
|
|
161
|
-
hookSpecificOutput: {
|
|
162
|
-
hookEventName: 'PreToolUse',
|
|
163
|
-
permissionDecision: 'deny',
|
|
164
|
-
permissionDecisionReason: `🛑 Agent chooser: Awaiting user confirmation for "${description.slice(0, 40)}"`,
|
|
165
|
-
additionalContext: chooserContext
|
|
166
|
-
}
|
|
167
|
-
}));
|
|
168
|
-
|
|
169
|
-
} catch (e) {
|
|
170
|
-
// LOW-44 FIX: Log errors before exit
|
|
171
|
-
console.error('[agent-chooser-hook] Error:', e.message || e);
|
|
172
|
-
process.exit(0);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
main().catch((e) => {
|
|
177
|
-
console.error('[agent-chooser-hook] Unhandled error:', e.message || e);
|
|
178
|
-
process.exit(0);
|
|
179
|
-
});
|