@vpxa/aikit 0.1.208 → 0.1.210

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.
Files changed (55) hide show
  1. package/package.json +1 -1
  2. package/packages/cli/dist/index.js +13 -13
  3. package/packages/cli/dist/{init-BoKsQg2-.js → init-CYp6FjZO.js} +1 -1
  4. package/packages/cli/dist/{templates-D-McT4sX.js → templates-CeowBw5E.js} +1 -1
  5. package/packages/core/dist/index.d.ts +2 -0
  6. package/packages/server/dist/auth-Bz5dmZgR.js +1 -0
  7. package/packages/server/dist/bin.js +12 -6
  8. package/packages/server/dist/config-C4mVyqAF.js +2 -0
  9. package/packages/server/dist/config-jGZ91cRx.js +1 -0
  10. package/packages/server/dist/curated-manager-D1u5qOwK.js +7 -0
  11. package/packages/server/dist/evolution-9hXRopDC.js +3 -0
  12. package/packages/server/dist/evolution-DJhTM6nu.js +2 -0
  13. package/packages/server/dist/index.d.ts +28 -1
  14. package/packages/server/dist/index.js +1 -1
  15. package/packages/server/dist/lessons-B05P_TOl.js +3 -0
  16. package/packages/server/dist/lessons-D7sdHa2e.js +2 -0
  17. package/packages/server/dist/promotion-Bd_YB7E1.js +3 -0
  18. package/packages/server/dist/promotion-OY53YCsT.js +2 -0
  19. package/packages/server/dist/proxy.js +1 -1
  20. package/packages/server/dist/replay-interceptor-CGLyom5f.js +7 -0
  21. package/packages/server/dist/retention-B4ITAs7F.js +1 -0
  22. package/packages/server/dist/retention-C3tsarCT.js +2 -0
  23. package/packages/server/dist/rolldown-runtime-DT7IzrpZ.js +1 -0
  24. package/packages/server/dist/{server-BaMsrcyc.js → server-BA1mIjBc.js} +163 -139
  25. package/packages/server/dist/{server-BhQwVWsr.js → server-CtFr8YsZ.js} +162 -140
  26. package/packages/server/dist/supersession-9edUDEQ8.js +1 -0
  27. package/packages/server/dist/{version-check-BgHzxxCW.js → version-check-D_uN0n0Y.js} +1 -1
  28. package/packages/store/dist/index.js +30 -30
  29. package/packages/tools/dist/index.d.ts +9 -1
  30. package/packages/tools/dist/index.js +78 -79
  31. package/packages/tools/package.json +5 -5
  32. package/scaffold/dist/adapters/claude-code.mjs +10 -10
  33. package/scaffold/dist/adapters/copilot.mjs +17 -17
  34. package/scaffold/dist/adapters/hooks.mjs +1 -0
  35. package/scaffold/dist/definitions/bodies.mjs +20 -114
  36. package/scaffold/dist/definitions/exec-hooks.mjs +1 -0
  37. package/scaffold/dist/definitions/flows.mjs +81 -345
  38. package/scaffold/dist/definitions/protocols.mjs +21 -4
  39. package/scaffold/dist/definitions/skills/aikit.mjs +35 -55
  40. package/scaffold/dist/definitions/skills/lesson-learned.mjs +46 -0
  41. package/scaffold/general/hooks/scripts/_runtime.mjs +161 -0
  42. package/scaffold/general/hooks/scripts/post-edit-check.mjs +36 -0
  43. package/scaffold/general/hooks/scripts/pre-compact-save.mjs +13 -0
  44. package/scaffold/general/hooks/scripts/privacy-guard.mjs +39 -0
  45. package/scaffold/general/hooks/scripts/scout-guard.mjs +45 -0
  46. package/scaffold/general/hooks/scripts/session-init.mjs +85 -0
  47. package/scaffold/general/hooks/scripts/session-learn.mjs +53 -0
  48. package/scaffold/general/hooks/scripts/session-observer.mjs +77 -0
  49. package/scaffold/general/hooks/scripts/subagent-context.mjs +59 -0
  50. package/packages/server/dist/auth-BfqgawfR.js +0 -1
  51. package/packages/server/dist/config-DAnAxrUW.js +0 -1
  52. package/packages/server/dist/config-PfoXsIC3.js +0 -2
  53. package/packages/server/dist/curated-manager-BnP6VqvL.js +0 -7
  54. package/packages/server/dist/supersession-BIV-v6JG.js +0 -3
  55. package/packages/server/dist/supersession-DJQGXMWm.js +0 -2
@@ -12,7 +12,9 @@ metadata:
12
12
 
13
13
  # @vpxa/aikit — AI Kit
14
14
 
15
- Local-first AI developer toolkit for search, analysis, compression, validation, memory, flows, and coordination.
15
+ > This skill provides DEEP GUIDANCE beyond base instructions. For tool routing rules, see your base instructions.
16
+
17
+ Local-first AI developer toolkit with 60+ tools for search, analysis, compression, validation, memory, flows, and coordination.
16
18
 
17
19
  ## When to Use
18
20
 
@@ -45,40 +47,6 @@ If you are unsure which tool fits, ask AI Kit for the live catalog with \`list_t
45
47
  4. Reuse previous context before searching again.
46
48
  5. Use reference docs only when the main routing logic is not enough.
47
49
 
48
- ## Tool Selection (Decision Tree)
49
-
50
- ~~~text
51
- Need to understand code?
52
- ├─ Just structure? → file_summary (exports, imports — ~50 tokens)
53
- ├─ Specific section? → compact({ path, query }) — 5-20x token reduction
54
- ├─ Multiple files? → digest (multi-source compression)
55
- ├─ Need relationships? → graph (find_nodes → neighbors)
56
- └─ Need exact lines? → read_file (ONLY for editing)
57
-
58
- Need to find something?
59
- ├─ Code/symbols? → search (hybrid — default)
60
- ├─ Symbol definition? → symbol (definition + refs + call context)
61
- ├─ Usage examples? → find({ mode: 'examples', query })
62
- ├─ Cross-file flow? → trace (forward/backward/both)
63
- └─ Change impact? → blast_radius
64
-
65
- Need to validate?
66
- ├─ Type errors? → check (typecheck + lint combined)
67
- ├─ Tests pass? → test_run (structured output)
68
- └─ Full audit? → audit (structure + deps + health)
69
-
70
- Need to remember?
71
- ├─ Store decision? → knowledge({ action: 'remember' })
72
- ├─ Find past decision? → search({ query, origin: 'curated' })
73
- ├─ Session state? → stash (ephemeral) or checkpoint (persistent)
74
- └─ Cross-session? → knowledge (curated memory)
75
-
76
- Need tool discovery?
77
- ├─ Full live catalog? → list_tools
78
- ├─ Know capability, not name? → search_tools
79
- └─ Need details first? → describe_tool or guide
80
- ~~~
81
-
82
50
  **When tools return empty:**
83
51
  - \`search\` → 0 results? Broaden query terms OR fall back to \`find({ pattern })\` with regex
84
52
  - \`symbol\` → not found? Check spelling, try \`search\` with partial name
@@ -118,7 +86,6 @@ Without session discipline, agents repeat work, miss context, and make decisions
118
86
  - NEVER \`search\` then immediately \`read_file\` the result — search already returns content snippets
119
87
  - NEVER call \`compact\` on a file you just \`file_summary\`'d — pick one retrieval depth
120
88
  - NEVER stash >10 items without \`checkpoint\` — stash has no TTL, checkpoints do
121
- - NEVER \`read_file\` a file >50 lines to "understand" it — \`file_summary\` → \`compact\` decision tree
122
89
  - NEVER run \`reindex\` mid-implementation — wait until all edits are done
123
90
  - NEVER skip \`search\` before implementing — past decisions may exist that constrain your approach
124
91
  - NEVER echo full subagent output to user — compress with \`stash\` + brief summary
@@ -193,6 +160,38 @@ Capture reusable engineering insights via \`knowledge({ action: "lesson", subAct
193
160
 
194
161
  See the \`lesson-learned\` skill for the full extraction workflow.
195
162
 
163
+ ### Lesson Lifecycle Management
164
+
165
+ | SubAction | Purpose | Default |
166
+ |-----------|---------|---------|
167
+ | \`prune\` | Archive stale lessons (confidence < 15, idle > 30d) | dryRun: true |
168
+ | \`group\` | Cluster similar lessons, add group tags | dryRun: true |
169
+ | \`promote\` | Cross-workspace promotion to global store | dryRun: true |
170
+ | \`demote\` | Remove from global store | -- |
171
+ | \`auto-observe\` | Pattern detection from tool outputs | automatic |
172
+
173
+ **Maintenance workflow** (periodic):
174
+ \`\`\`
175
+ knowledge({ action: "lesson", subAction: "prune" }) // review stale
176
+ knowledge({ action: "lesson", subAction: "group" }) // organize
177
+ knowledge({ action: "lesson", subAction: "promote" }) // share universal
178
+ \`\`\`
179
+
180
+ ### Session Prelude
181
+ Request context-primed session start:
182
+ \`\`\`
183
+ status({ includePrelude: true })
184
+ \`\`\`
185
+ Returns: top 3 lessons (by decayed confidence), top 2 conventions (by recency), last session checkpoint.
186
+ Use at session start for immediate situational awareness.
187
+
188
+ ### Confidence Decay
189
+ Lesson confidence decays over time via Ebbinghaus curve:
190
+ - Accessing lessons (via withdraw, list-lessons) implicitly reinforces them
191
+ - Unaccessed lessons gradually lose confidence
192
+ - Below threshold 15 + idle > 30 days -> eligible for pruning
193
+ - \`knowledge({ action: "flagged" })\` surfaces decayed entries for review
194
+
196
195
  ### Supersession (automatic dedup)
197
196
  When you \`remember()\`, similar entries are detected automatically:
198
197
  - Jaccard > 0.7 → flagged for review
@@ -216,18 +215,6 @@ Load these only when the main skill is not enough:
216
215
  - \`references/forge-protocol.md\` — tiering, evidence, gates
217
216
  - \`references/search-patterns.md\` — search, trace, graph, compression patterns
218
217
 
219
- ## NEVER
220
-
221
- - NEVER use \`read_file\` to "understand" a file. \`file_summary\` gives structure in ~50 tokens instead of hundreds.
222
- - NEVER use \`grep_search\` or \`semantic_search\` when \`search\` is available. \`search\` combines both strategies and ranks them.
223
- - NEVER run tsc, lint, or tests through the terminal when \`check()\` and \`test_run()\` exist. Structured output beats terminal noise.
224
- - NEVER skip \`status()\` at session start. If index state is unknown, every later choice is lower quality.
225
- - NEVER call \`knowledge({ action: 'remember' })\` for trivial facts. Memory is for decisions, conventions, lessons, and durable findings.
226
- - NEVER search the same thing twice without checking \`stash\` or prior results.
227
- - NEVER use a long flat tool catalog as your primary routing aid. Use runtime discovery when you need exact tool metadata.
228
- - NEVER jump to \`analyze\` for simple local questions. Start with cheaper retrieval and escalate only if needed.
229
- - NEVER leave structural changes unindexed. Run \`reindex\` when symbols, files, or imports change.
230
-
231
218
  ## Complementary Skills
232
219
 
233
220
  - Load \`typescript\` before TypeScript-heavy implementation work.
@@ -235,13 +222,6 @@ Load these only when the main skill is not enough:
235
222
  - Load \`session-handoff\` when context is filling up or a pause is imminent.
236
223
  - Load \`repo-access\` when repo auth fails.
237
224
 
238
- ## Practical Defaults
239
-
240
- - Default search mode: \`search\` with hybrid ranking.
241
- - Default read path: \`file_summary\` then \`compact\`.
242
- - Default validation pair: \`check\` then \`test_run\`.
243
- - Default persistence: \`knowledge remember\` for durable findings, \`stash\` for everything temporary.
244
-
245
225
  ## Self-Dogfooding
246
226
 
247
227
  When developing AI Kit itself, rebuild generated output before trusting runtime behavior, and reindex after structural changes so the toolkit can see its own new shape.
@@ -272,6 +272,8 @@ knowledge({ action: "lesson", subAction: "create", context: "<what happened —
272
272
  - 80-90: Pattern confirmed across multiple PRs/sessions
273
273
  - 90+: Fundamental principle validated repeatedly -- treat as convention
274
274
 
275
+ Confidence decays over time based on access recency (Ebbinghaus curve). Accessing a lesson via \`withdraw\` or \`list-lessons\` implicitly reinforces it.
276
+
275
277
  If you encounter a lesson that CONFIRMS a previously stored one:
276
278
  ~~~
277
279
  knowledge({ action: "lesson", subAction: "confirm", id: "<lesson-path>" })
@@ -285,6 +287,50 @@ knowledge({ action: "lesson", subAction: "contradict", id: "<lesson-path>", reas
285
287
  Before creating a new lesson, check existing ones: \`knowledge({ action: "lesson", subAction: "list-lessons" })\`
286
288
  Only create if no existing lesson already covers this insight.
287
289
 
290
+ ### Passive Learning (auto-observe)
291
+ The server automatically observes tool output patterns and can detect:
292
+ - Repeated error -> fix cycles (suggests lessons about common pitfalls)
293
+ - Consistent code patterns across files (suggests convention lessons)
294
+ - Recurring search queries (suggests documentation gaps)
295
+
296
+ Trigger manually: \`knowledge({ action: "lesson", subAction: "auto-observe", buffer: "<tool output>" })\`
297
+ Usually runs automatically via exec-hooks -- agents don't need to call this directly.
298
+
299
+ ## Phase 6: Lifecycle Maintenance (Periodic)
300
+
301
+ Run these periodically (every few sessions) to maintain knowledge quality:
302
+
303
+ ### Prune Stale Lessons
304
+ \`\`\`
305
+ knowledge({ action: "lesson", subAction: "prune" }) // dry-run: shows candidates
306
+ knowledge({ action: "lesson", subAction: "prune", dryRun: false }) // execute: archives stale lessons
307
+ \`\`\`
308
+ Archives lessons where: decayed confidence < 15 AND last accessed > 30 days ago.
309
+ Safety: minimum 3 lessons always retained, dry-run by default.
310
+
311
+ ### Group Similar Lessons
312
+ \`\`\`
313
+ knowledge({ action: "lesson", subAction: "group" }) // dry-run: shows proposed groups
314
+ knowledge({ action: "lesson", subAction: "group", dryRun: false }) // execute: adds group tags
315
+ \`\`\`
316
+ Clusters lessons with similar titles (Jaccard > 0.7), adds \`group:<label>\` tags.
317
+ Non-destructive -- only adds tags, never modifies content.
318
+
319
+ ### Cross-Project Promotion
320
+ \`\`\`
321
+ knowledge({ action: "lesson", subAction: "promote" }) // dry-run: shows candidates
322
+ knowledge({ action: "lesson", subAction: "promote", dryRun: false }) // execute: copies to global
323
+ knowledge({ action: "lesson", subAction: "demote", path: "<path>" }) // remove from global
324
+ \`\`\`
325
+ Requires user-level install. Scans all workspaces for similar lessons (Jaccard > 0.7 on insight text).
326
+ Promotes when: found in 2+ workspaces AND average confidence >= 80.
327
+ Global lessons available to ALL workspaces via \`workspaces: ["*"]\`.
328
+
329
+ ### Recommended Maintenance Cadence
330
+ - **Every 5 sessions**: \`prune\` (dry-run review)
331
+ - **Every 10 sessions**: \`group\` to organize
332
+ - **Monthly**: \`promote\` to share universal lessons across projects
333
+
288
334
  ## What NOT to Do
289
335
 
290
336
  | Avoid | Why | Instead |
@@ -0,0 +1,161 @@
1
+ import path from 'node:path';
2
+ import process from 'node:process';
3
+
4
+ const FILE_KEYS = new Set([
5
+ 'dirPath',
6
+ 'file',
7
+ 'filePath',
8
+ 'filePaths',
9
+ 'files',
10
+ 'from',
11
+ 'path',
12
+ 'paths',
13
+ 'targetPath',
14
+ 'to',
15
+ 'uri',
16
+ 'uris',
17
+ ]);
18
+
19
+ const escapeRegex = (value) => value.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
20
+
21
+ const normalizePath = (value) => {
22
+ if (typeof value !== 'string') return '';
23
+ let next = value.trim();
24
+ if (next.startsWith('file://')) {
25
+ try {
26
+ next = decodeURIComponent(new URL(next).pathname);
27
+ } catch {
28
+ return '';
29
+ }
30
+ }
31
+ if (/^\/[a-z]:/i.test(next)) next = next.slice(1);
32
+ return path.normalize(next).replace(/\\/g, '/').toLowerCase();
33
+ };
34
+
35
+ /**
36
+ * Creates a hook entrypoint that normalizes stdin and writes a platform-neutral response.
37
+ * @param {(context: ReturnType<typeof normalizeContext>) => Promise<object> | object} handler
38
+ */
39
+ export async function createHook(handler) {
40
+ try {
41
+ const raw = await readStdin();
42
+ const result = (await handler(normalizeContext(raw))) ?? { decision: 'allow' };
43
+ respond(result);
44
+ } catch (error) {
45
+ respond({ decision: 'deny', reason: error instanceof Error ? error.message : String(error) });
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Reads and parses the full stdin payload as JSON.
51
+ * @returns {Promise<object>}
52
+ */
53
+ export function readStdin() {
54
+ return new Promise((resolve, reject) => {
55
+ let input = '';
56
+ process.stdin.setEncoding('utf8');
57
+ process.stdin.on('data', (chunk) => {
58
+ input += chunk;
59
+ });
60
+ process.stdin.on('end', () => {
61
+ if (!input.trim()) return resolve({});
62
+ try {
63
+ resolve(JSON.parse(input));
64
+ } catch (error) {
65
+ reject(error);
66
+ }
67
+ });
68
+ process.stdin.on('error', reject);
69
+ });
70
+ }
71
+
72
+ /**
73
+ * Normalizes platform-specific hook payloads into one shared shape.
74
+ * @param {object} raw
75
+ */
76
+ export function normalizeContext(raw = {}) {
77
+ const platform = raw.tool_name
78
+ ? 'claude'
79
+ : raw.toolName
80
+ ? 'copilot'
81
+ : raw.tool
82
+ ? 'copilot-cli'
83
+ : 'copilot';
84
+ const toolInput =
85
+ raw.toolInput ?? raw.tool_input ?? raw.input ?? raw.arguments ?? raw.params ?? {};
86
+ return {
87
+ raw,
88
+ platform,
89
+ event: raw.event ?? raw.hookEvent ?? raw.hook_event ?? 'SessionStart',
90
+ toolName: raw.toolName ?? raw.tool_name ?? raw.tool ?? '',
91
+ toolInput,
92
+ filePaths: extractFilePaths(toolInput),
93
+ env: raw.env ?? raw.environment ?? raw.processEnv ?? {},
94
+ cwd: raw.cwd ?? raw.workingDirectory ?? raw.working_directory ?? process.cwd(),
95
+ matchesPattern,
96
+ normalizePath,
97
+ };
98
+ }
99
+
100
+ /**
101
+ * Writes a normalized hook response to stdout and sets the exit code.
102
+ * @param {{ decision?: 'allow' | 'deny', reason?: string, additionalContext?: string, continue?: boolean }} result
103
+ */
104
+ export function respond(result = { decision: 'allow' }) {
105
+ const decision = result.decision ?? 'allow';
106
+ const payload =
107
+ decision === 'deny'
108
+ ? { decision: 'deny', reason: result.reason ?? 'Hook denied request.' }
109
+ : {
110
+ ...(result.additionalContext ? { additionalContext: result.additionalContext } : {}),
111
+ ...(result.continue === false ? { continue: false } : {}),
112
+ };
113
+ if (Object.keys(payload).length > 0) process.stdout.write(`${JSON.stringify(payload)}\n`);
114
+ process.exitCode = decision === 'deny' ? 2 : 0;
115
+ }
116
+
117
+ /**
118
+ * Extracts file paths from common tool input field names.
119
+ * @param {unknown} toolInput
120
+ * @returns {string[]}
121
+ */
122
+ export function extractFilePaths(toolInput) {
123
+ const found = new Set();
124
+ const visit = (value, key = '') => {
125
+ if (Array.isArray(value)) {
126
+ value.forEach((item) => {
127
+ visit(item, key);
128
+ });
129
+ return;
130
+ }
131
+ if (value && typeof value === 'object') {
132
+ Object.entries(value).forEach(([nextKey, nextValue]) => {
133
+ visit(nextValue, nextKey);
134
+ });
135
+ return;
136
+ }
137
+ if (typeof value === 'string' && FILE_KEYS.has(key)) found.add(normalizePath(value));
138
+ };
139
+ visit(toolInput);
140
+ return [...found].filter(Boolean);
141
+ }
142
+
143
+ /**
144
+ * Matches a normalized path against simple wildcard and directory-prefix patterns.
145
+ * @param {string} filePath
146
+ * @param {string[]} patterns
147
+ */
148
+ export function matchesPattern(filePath, patterns) {
149
+ const target = normalizePath(filePath);
150
+ return patterns.some((pattern) => {
151
+ const normalizedPattern = normalizePath(pattern);
152
+ if (!normalizedPattern) return false;
153
+ if (normalizedPattern.endsWith('/')) {
154
+ const prefix = normalizedPattern.slice(0, -1);
155
+ return target === prefix || target.includes(`/${prefix}/`) || target.startsWith(`${prefix}/`);
156
+ }
157
+ return new RegExp(`(^|/)${normalizedPattern.split('*').map(escapeRegex).join('.*')}$`).test(
158
+ target,
159
+ );
160
+ });
161
+ }
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import process from 'node:process';
6
+ import { createHook } from './_runtime.mjs';
7
+
8
+ const EDIT_TOOLS = new Set(['Edit', 'Write', 'create_file', 'editFiles', 'replace_string_in_file']);
9
+ const counterPath = path.join(os.tmpdir(), `aikit-edit-count-${process.ppid}.json`);
10
+ const readCount = () => {
11
+ try {
12
+ return JSON.parse(fs.readFileSync(counterPath, 'utf8')).count || 0;
13
+ } catch {
14
+ return 0;
15
+ }
16
+ };
17
+ const writeCount = (count) => {
18
+ try {
19
+ fs.writeFileSync(counterPath, JSON.stringify({ count }), 'utf8');
20
+ } catch {}
21
+ };
22
+
23
+ /** Tracks edit counts and nudges validation after repeated file changes. */
24
+ export const postEditCheck = async (context) => {
25
+ if (context.event !== 'PostToolUse' || !EDIT_TOOLS.has(context.toolName))
26
+ return { decision: 'allow' };
27
+ const count = readCount() + 1;
28
+ writeCount(count);
29
+ if (count < 5 || count % 5 !== 0) return { decision: 'allow' };
30
+ const prefix = count >= 10 ? 'Strongly consider' : 'Consider';
31
+ return {
32
+ additionalContext: `📋 You've made ${count} file edits this session. ${prefix} running check({}) and test_run({}) to validate changes before continuing.`,
33
+ };
34
+ };
35
+
36
+ createHook(postEditCheck);
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ import { createHook } from './_runtime.mjs';
3
+
4
+ const REMINDER = `⚠️ Context compaction imminent. Before proceeding:
5
+ 1. If you have uncommitted decisions, call: knowledge({ action: "remember", title: "Pre-compact save", content: "<key decisions and state>", category: "session" })
6
+ 2. If a flow is active, call: stash({ action: "set", key: "pre-compact-state", value: "<current progress>" })
7
+ 3. After compaction, call: search({ query: "SESSION CHECKPOINT", origin: "curated" }) to recover context.`;
8
+
9
+ /** Injects a save-state reminder immediately before context compaction. */
10
+ export const preCompactSave = async (context) =>
11
+ context.event === 'PreCompact' ? { additionalContext: REMINDER } : { decision: 'allow' };
12
+
13
+ createHook(preCompactSave);
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env node
2
+ import { createHook } from './_runtime.mjs';
3
+
4
+ const BLOCKED = [
5
+ '**/.env',
6
+ '**/.env.*',
7
+ '**/*.pem',
8
+ '**/*.key',
9
+ '**/*.p12',
10
+ '**/*.pfx',
11
+ '**/id_rsa*',
12
+ '**/id_ed25519*',
13
+ '**/.ssh/*',
14
+ '**/*credentials*',
15
+ '**/*.secret',
16
+ '**/.netrc',
17
+ '**/.pgpass',
18
+ '**/secret.*',
19
+ '**/secrets.*',
20
+ ];
21
+
22
+ const READ_TOOLS = new Set(['Read', 'read_file', 'readFile']);
23
+
24
+ /** Blocks reads of secret-bearing files and key material. */
25
+ export const privacyGuard = async (context) => {
26
+ if (context.event !== 'PreToolUse' || !READ_TOOLS.has(context.toolName))
27
+ return { decision: 'allow' };
28
+ const blockedPath = context.filePaths.find((filePath) =>
29
+ context.matchesPattern(filePath, BLOCKED),
30
+ );
31
+ return blockedPath
32
+ ? {
33
+ decision: 'deny',
34
+ reason: `Blocked: reading sensitive file ${blockedPath}. Use environment variables or secrets manager instead.`,
35
+ }
36
+ : { decision: 'allow' };
37
+ };
38
+
39
+ createHook(privacyGuard);
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env node
2
+ import { createHook } from './_runtime.mjs';
3
+
4
+ const BLOCKED_DIRS = [
5
+ 'node_modules/',
6
+ '.git/objects/',
7
+ 'dist/',
8
+ 'build/',
9
+ 'vendor/',
10
+ '.next/',
11
+ 'coverage/',
12
+ '__pycache__/',
13
+ '.tox/',
14
+ 'target/debug/',
15
+ 'target/release/',
16
+ '.gradle/',
17
+ 'Pods/',
18
+ ];
19
+
20
+ const normalize = (value) =>
21
+ String(value ?? '')
22
+ .replace(/\\/g, '/')
23
+ .toLowerCase();
24
+
25
+ /** Blocks reads and searches inside generated or dependency-heavy directories. */
26
+ export const scoutGuard = async (context) => {
27
+ if (context.event !== 'PreToolUse') return { decision: 'allow' };
28
+ const blockedDir = context.filePaths.reduce((match, filePath) => {
29
+ if (match) return match;
30
+ const normalizedPath = normalize(filePath);
31
+ return (
32
+ BLOCKED_DIRS.find(
33
+ (dir) => normalizedPath.startsWith(dir) || normalizedPath.includes(`/${dir}`),
34
+ ) ?? ''
35
+ );
36
+ }, '');
37
+ return blockedDir
38
+ ? {
39
+ decision: 'deny',
40
+ reason: `Blocked: accessing ${blockedDir} wastes context. Use search tools or package documentation instead.`,
41
+ }
42
+ : { decision: 'allow' };
43
+ };
44
+
45
+ createHook(scoutGuard);
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import process from 'node:process';
5
+ import { createHook } from './_runtime.mjs';
6
+
7
+ const exists = (cwd, name) => {
8
+ try {
9
+ return fs.existsSync(path.join(cwd, name));
10
+ } catch {
11
+ return false;
12
+ }
13
+ };
14
+ const list = (cwd) => {
15
+ try {
16
+ return fs.readdirSync(cwd);
17
+ } catch {
18
+ return [];
19
+ }
20
+ };
21
+ const readJson = (cwd, name) => {
22
+ try {
23
+ return JSON.parse(fs.readFileSync(path.join(cwd, name), 'utf8'));
24
+ } catch {
25
+ return null;
26
+ }
27
+ };
28
+ const detectPackageManager = (cwd, pkg) =>
29
+ pkg?.packageManager?.split('@')[0] ||
30
+ (exists(cwd, 'pnpm-lock.yaml') && 'pnpm') ||
31
+ (exists(cwd, 'yarn.lock') && 'yarn') ||
32
+ ((exists(cwd, 'bun.lockb') || exists(cwd, 'bun.lock')) && 'bun') ||
33
+ (exists(cwd, 'package-lock.json') && 'npm') ||
34
+ 'unknown';
35
+
36
+ /** Detects workspace metadata and injects stack context at session start. */
37
+ export const sessionInit = async (context) => {
38
+ if (context.event !== 'SessionStart') return { decision: 'allow' };
39
+ const pkg = readJson(context.cwd, 'package.json');
40
+ const deps = { ...pkg?.dependencies, ...pkg?.devDependencies };
41
+ const entries = list(context.cwd);
42
+ const framework = deps.react
43
+ ? 'React'
44
+ : deps.vue
45
+ ? 'Vue'
46
+ : deps.angular
47
+ ? 'Angular'
48
+ : deps.next
49
+ ? 'Next.js'
50
+ : deps.express
51
+ ? 'Express'
52
+ : deps.fastify
53
+ ? 'Fastify'
54
+ : '';
55
+ const stack = pkg
56
+ ? `Node.js${framework ? `, ${framework}` : ''}`
57
+ : exists(context.cwd, 'go.mod')
58
+ ? 'Go'
59
+ : exists(context.cwd, 'Cargo.toml')
60
+ ? 'Rust'
61
+ : exists(context.cwd, 'requirements.txt') || exists(context.cwd, 'pyproject.toml')
62
+ ? 'Python'
63
+ : entries.some((name) => /\.(sln|csproj)$/i.test(name))
64
+ ? '.NET'
65
+ : exists(context.cwd, 'pom.xml') || exists(context.cwd, 'build.gradle')
66
+ ? 'Java/Kotlin'
67
+ : 'Unknown';
68
+ const monorepo = exists(context.cwd, 'pnpm-workspace.yaml')
69
+ ? 'yes (pnpm-workspace)'
70
+ : exists(context.cwd, 'lerna.json')
71
+ ? 'yes (lerna)'
72
+ : exists(context.cwd, 'packages')
73
+ ? 'yes (packages/)'
74
+ : 'no';
75
+ const metadata = [
76
+ `Workspace: ${path.basename(context.cwd)}`,
77
+ `Stack: ${stack}`,
78
+ `Monorepo: ${monorepo}`,
79
+ `Package manager: ${detectPackageManager(context.cwd, pkg)}`,
80
+ ...(pkg ? [`Node: ${process.version}`] : []),
81
+ ].join('\n');
82
+ return { additionalContext: metadata };
83
+ };
84
+
85
+ createHook(sessionInit);
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * session-learn — Stop event hook for autonomous learning.
4
+ *
5
+ * Fires at session end. Nudges agent to process buffered observations
6
+ * before context is lost. Cleans up buffer if too small for analysis.
7
+ *
8
+ * @tier efficiency
9
+ * @event Stop
10
+ * @scope user
11
+ */
12
+ import fs from 'node:fs';
13
+ import os from 'node:os';
14
+ import path from 'node:path';
15
+ import process from 'node:process';
16
+ import { createHook } from './_runtime.mjs';
17
+
18
+ const bufferPath = path.join(os.tmpdir(), `aikit-obs-${process.ppid}.jsonl`);
19
+ const MIN_OBSERVATIONS = 10;
20
+
21
+ const getLineCount = (filePath) => {
22
+ try {
23
+ return fs.readFileSync(filePath, 'utf8').split('\n').filter(Boolean).length;
24
+ } catch {
25
+ return 0;
26
+ }
27
+ };
28
+
29
+ const cleanup = () => {
30
+ try {
31
+ fs.unlinkSync(bufferPath);
32
+ } catch {}
33
+ };
34
+
35
+ /** Nudges end-of-session lesson extraction and cleans up small buffers. */
36
+ export const sessionLearn = async (context) => {
37
+ if (context.event !== 'Stop') return { decision: 'allow' };
38
+
39
+ const count = getLineCount(bufferPath);
40
+ if (count < MIN_OBSERVATIONS) {
41
+ cleanup();
42
+ return { decision: 'allow' };
43
+ }
44
+
45
+ return {
46
+ additionalContext: [
47
+ `[Observer] Session ending with ${count} observations buffered.`,
48
+ 'Run knowledge({ action: "lesson", subAction: "auto-observe" }) to extract learning patterns.',
49
+ ].join(' '),
50
+ };
51
+ };
52
+
53
+ createHook(sessionLearn);