@vpxa/aikit 0.1.357 → 0.1.359
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/package.json +1 -1
- package/packages/cli/dist/index.js +19 -19
- package/packages/cli/dist/init-Cvys5TD4.js +7 -0
- package/packages/cli/dist/{templates-Bu7dZtk_.js → templates-d2UQHjpi.js} +13 -12
- package/packages/server/dist/bin.js +1 -1
- package/packages/server/dist/index.js +1 -1
- package/packages/server/dist/prelude-CfXBHLpw.js +1 -0
- package/packages/server/dist/prelude-D33q42vQ.js +2 -0
- package/packages/server/dist/sampling-Cs9MoNYX.js +2 -0
- package/packages/server/dist/sampling-DFogBKrL.js +1 -0
- package/packages/server/dist/{server-D5eu57oU.js → server-BZVPmRGV.js} +160 -160
- package/packages/server/dist/{server-D7gZ4K_H.js → server-CICqmfQ_.js} +160 -160
- package/packages/server/dist/{server-http-AarbD3gm.js → server-http-CPU9_eVT.js} +1 -1
- package/packages/server/dist/{server-http-wnYmWFkC.js → server-http-D3DK1H19.js} +1 -1
- package/packages/server/dist/{server-stdio-BWoMqXU8.js → server-stdio-BRKxFWyO.js} +1 -1
- package/packages/server/dist/{server-stdio-CBGO6XiO.js → server-stdio-l8I0RW4l.js} +1 -1
- package/scaffold/dist/adapters/hooks.mjs +1 -1
- package/scaffold/dist/definitions/exec-hooks.mjs +1 -1
- package/scaffold/dist/definitions/protocols.mjs +7 -6
- package/scaffold/dist/definitions/skills/aikit.mjs +4 -4
- package/scaffold/general/hooks/scripts/_runtime.mjs +87 -15
- package/scaffold/general/hooks/scripts/freshness-signal.mjs +196 -0
- package/scaffold/general/hooks/scripts/privacy-guard.mjs +19 -6
- package/scaffold/general/hooks/scripts/scope-guard.mjs +160 -0
- package/scaffold/general/hooks/scripts/scout-guard.mjs +18 -14
- package/packages/cli/dist/init-CWLiK7bC.js +0 -7
- package/packages/server/dist/prelude-DetbWo8R.js +0 -1
- package/packages/server/dist/prelude-hfAEi93R.js +0 -2
- package/packages/server/dist/sampling-CWjnUevo.js +0 -2
- package/packages/server/dist/sampling-CnE3owSt.js +0 -1
- package/scaffold/general/hooks/scripts/post-edit-check.mjs +0 -36
- package/scaffold/general/hooks/scripts/pre-compact-save.mjs +0 -13
- package/scaffold/general/hooks/scripts/session-init.mjs +0 -85
- package/scaffold/general/hooks/scripts/session-learn.mjs +0 -53
- package/scaffold/general/hooks/scripts/session-observer.mjs +0 -77
- package/scaffold/general/hooks/scripts/subagent-context.mjs +0 -59
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* privacy-guard — PreToolUse guard against secret/key material reads.
|
|
4
|
+
*
|
|
5
|
+
* Denies reads of .env, .pem, .key, .ssh/*, credentials, token files, .netrc, .pgpass.
|
|
6
|
+
* Uses deterministic path matching — no LLM, no config files, no external state.
|
|
7
|
+
*
|
|
8
|
+
* @category guard
|
|
9
|
+
* @event PreToolUse
|
|
10
|
+
*/
|
|
11
|
+
|
|
2
12
|
import { createHook } from './_runtime.mjs';
|
|
3
13
|
|
|
4
14
|
const BLOCKED = [
|
|
@@ -17,23 +27,26 @@ const BLOCKED = [
|
|
|
17
27
|
'**/.pgpass',
|
|
18
28
|
'**/secret.*',
|
|
19
29
|
'**/secrets.*',
|
|
30
|
+
'**/*.token',
|
|
31
|
+
'**/*.tkn',
|
|
20
32
|
];
|
|
21
33
|
|
|
22
34
|
const READ_TOOLS = new Set(['Read', 'read_file', 'readFile']);
|
|
23
35
|
|
|
24
|
-
/**
|
|
25
|
-
|
|
26
|
-
if (
|
|
27
|
-
|
|
36
|
+
/** Deny reads matching blocked secret patterns. */
|
|
37
|
+
const handler = async (context) => {
|
|
38
|
+
if (!READ_TOOLS.has(context.toolName)) return { decision: 'allow' };
|
|
39
|
+
|
|
28
40
|
const blockedPath = context.filePaths.find((filePath) =>
|
|
29
41
|
context.matchesPattern(filePath, BLOCKED),
|
|
30
42
|
);
|
|
43
|
+
|
|
31
44
|
return blockedPath
|
|
32
45
|
? {
|
|
33
46
|
decision: 'deny',
|
|
34
|
-
reason: `
|
|
47
|
+
reason: `Reading sensitive file: ${blockedPath}. Use environment variables or secrets manager.`,
|
|
35
48
|
}
|
|
36
49
|
: { decision: 'allow' };
|
|
37
50
|
};
|
|
38
51
|
|
|
39
|
-
createHook(
|
|
52
|
+
createHook(handler, { event: 'PreToolUse', category: 'guard' });
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* scope-guard — PostToolUse signal for edit drift detection.
|
|
5
|
+
*
|
|
6
|
+
* After file writes, checks:
|
|
7
|
+
* - File change count vs threshold (default 5)
|
|
8
|
+
* - Active flow scope boundary
|
|
9
|
+
* - Scaffold source/dist mismatch
|
|
10
|
+
*
|
|
11
|
+
* Returns silence when healthy. Signals only actionable drift.
|
|
12
|
+
*
|
|
13
|
+
* @category signal
|
|
14
|
+
* @event PostToolUse
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { execSync } from 'node:child_process';
|
|
18
|
+
import fs from 'node:fs';
|
|
19
|
+
import path from 'node:path';
|
|
20
|
+
import { createHook } from './_runtime.mjs';
|
|
21
|
+
|
|
22
|
+
const DIFF_THRESHOLD = 5;
|
|
23
|
+
|
|
24
|
+
const exists = (p) => {
|
|
25
|
+
try {
|
|
26
|
+
return fs.existsSync(p);
|
|
27
|
+
} catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Run git diff covering working-tree, staged, and untracked files.
|
|
34
|
+
* Returns array of relative paths. Empty on error or no changes.
|
|
35
|
+
*/
|
|
36
|
+
function getChangedFiles(cwd) {
|
|
37
|
+
try {
|
|
38
|
+
// Tracked working-tree changes + staged changes
|
|
39
|
+
const tracked = execSync('git diff --name-only && git diff --cached --name-only', {
|
|
40
|
+
cwd,
|
|
41
|
+
encoding: 'utf-8',
|
|
42
|
+
timeout: 3000,
|
|
43
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
44
|
+
});
|
|
45
|
+
// Untracked files that are not ignored
|
|
46
|
+
let untracked = '';
|
|
47
|
+
try {
|
|
48
|
+
untracked = execSync('git ls-files --others --exclude-standard', {
|
|
49
|
+
cwd,
|
|
50
|
+
encoding: 'utf-8',
|
|
51
|
+
timeout: 3000,
|
|
52
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
53
|
+
});
|
|
54
|
+
} catch {
|
|
55
|
+
// ls-files may fail in detached or bare repos
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const allFiles = [...tracked.split('\n'), ...untracked.split('\n')];
|
|
59
|
+
return [...new Set(allFiles.map((s) => s.trim()).filter(Boolean))];
|
|
60
|
+
} catch {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Read active flow scope from .flows/<slug>/meta.json.
|
|
67
|
+
* Flow runs are stored as directories under .flows/, each with a meta.json.
|
|
68
|
+
*/
|
|
69
|
+
function getFlowScope(cwd) {
|
|
70
|
+
const flowsDir = path.join(cwd, '.flows');
|
|
71
|
+
if (!exists(flowsDir)) return null;
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const entries = fs.readdirSync(flowsDir);
|
|
75
|
+
for (const entry of entries) {
|
|
76
|
+
const metaPath = path.join(flowsDir, entry, 'meta.json');
|
|
77
|
+
if (!exists(metaPath)) continue;
|
|
78
|
+
try {
|
|
79
|
+
const meta = JSON.parse(fs.readFileSync(metaPath, 'utf8'));
|
|
80
|
+
if (meta?.status === 'active') {
|
|
81
|
+
const scope = meta?.scope || meta?.roots || meta?.config?.scope || null;
|
|
82
|
+
return {
|
|
83
|
+
slug: entry,
|
|
84
|
+
scope: Array.isArray(scope) ? scope : scope ? [scope] : null,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
} catch {
|
|
88
|
+
// Skip unreadable meta files
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
} catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Check if modified files include scaffold definitions but no regenerate.
|
|
99
|
+
*/
|
|
100
|
+
function checkScaffoldDrift(changedFiles) {
|
|
101
|
+
const scaffoldDefs = changedFiles.filter(
|
|
102
|
+
(f) => f.startsWith('scaffold/') && (f.endsWith('.mjs') || f.endsWith('.json')),
|
|
103
|
+
);
|
|
104
|
+
if (scaffoldDefs.length === 0) return null;
|
|
105
|
+
|
|
106
|
+
const hasDist = changedFiles.some((f) => f.startsWith('scaffold/dist/'));
|
|
107
|
+
const hasPreview = changedFiles.some((f) => f.startsWith('scaffold/_preview/'));
|
|
108
|
+
|
|
109
|
+
if (!hasDist && !hasPreview) {
|
|
110
|
+
return `Scaffold source changed (${scaffoldDefs.length} files) but dist not regenerated. Run \`node scaffold/generate.mjs\`.`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Signal edit drift — silence when under threshold and no scope issues. */
|
|
117
|
+
const handler = async (context) => {
|
|
118
|
+
const signals = [];
|
|
119
|
+
|
|
120
|
+
// 1. Get changed files list (working-tree + staged + untracked)
|
|
121
|
+
const changedFiles = getChangedFiles(context.cwd);
|
|
122
|
+
if (changedFiles.length === 0) return { decision: 'allow' };
|
|
123
|
+
|
|
124
|
+
// 2. Count check
|
|
125
|
+
if (changedFiles.length > DIFF_THRESHOLD) {
|
|
126
|
+
signals.push(
|
|
127
|
+
`Modified ${changedFiles.length} files this session (threshold: ${DIFF_THRESHOLD}).`,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// 3. Flow scope boundary check
|
|
132
|
+
const flow = getFlowScope(context.cwd);
|
|
133
|
+
if (flow?.scope) {
|
|
134
|
+
const outOfScope = changedFiles.filter((f) => {
|
|
135
|
+
const normalizedFile = f.replace(/\\/g, '/');
|
|
136
|
+
return !flow.scope.some((s) => {
|
|
137
|
+
const normalizedScope = s.replace(/\\/g, '/').replace(/\/$/, '');
|
|
138
|
+
return normalizedFile.startsWith(normalizedScope);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
if (outOfScope.length > 0) {
|
|
142
|
+
signals.push(
|
|
143
|
+
`Files outside active flow "${flow.slug}" scope: ${outOfScope.slice(0, 5).join(', ')}${outOfScope.length > 5 ? ` (+${outOfScope.length - 5} more)` : ''}.`,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// 4. Scaffold drift
|
|
149
|
+
const drift = checkScaffoldDrift(changedFiles);
|
|
150
|
+
if (drift) signals.push(drift);
|
|
151
|
+
|
|
152
|
+
// Silence when healthy
|
|
153
|
+
if (signals.length === 0) return { decision: 'allow' };
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
additionalContext: `[Scope]\n${signals.join('\n')}`,
|
|
157
|
+
};
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
createHook(handler, { event: 'PostToolUse', category: 'signal', outputBudgetLines: 5 });
|
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* scout-guard — PreToolUse guard against low-value context reads.
|
|
4
|
+
*
|
|
5
|
+
* Denies reads/searches inside generated, dependency, and git object directories.
|
|
6
|
+
* Pushes agents toward file_summary, search, symbol, and digest instead.
|
|
7
|
+
*
|
|
8
|
+
* @category guard
|
|
9
|
+
* @event PreToolUse
|
|
10
|
+
*/
|
|
11
|
+
|
|
2
12
|
import { createHook } from './_runtime.mjs';
|
|
3
13
|
|
|
4
14
|
const BLOCKED_DIRS = [
|
|
@@ -15,31 +25,25 @@ const BLOCKED_DIRS = [
|
|
|
15
25
|
'target/release/',
|
|
16
26
|
'.gradle/',
|
|
17
27
|
'Pods/',
|
|
28
|
+
'.cache/',
|
|
18
29
|
];
|
|
19
30
|
|
|
20
|
-
|
|
21
|
-
|
|
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' };
|
|
31
|
+
/** Deny when any file path falls inside a blocked directory. */
|
|
32
|
+
const handler = async (context) => {
|
|
28
33
|
const blockedDir = context.filePaths.reduce((match, filePath) => {
|
|
29
34
|
if (match) return match;
|
|
30
|
-
const
|
|
35
|
+
const normalized = context.normalizePath(filePath);
|
|
31
36
|
return (
|
|
32
|
-
BLOCKED_DIRS.find(
|
|
33
|
-
(dir) => normalizedPath.startsWith(dir) || normalizedPath.includes(`/${dir}`),
|
|
34
|
-
) ?? ''
|
|
37
|
+
BLOCKED_DIRS.find((dir) => normalized.startsWith(dir) || normalized.includes(`/${dir}`)) ?? ''
|
|
35
38
|
);
|
|
36
39
|
}, '');
|
|
40
|
+
|
|
37
41
|
return blockedDir
|
|
38
42
|
? {
|
|
39
43
|
decision: 'deny',
|
|
40
|
-
reason: `
|
|
44
|
+
reason: `Reading ${blockedDir} wastes context. Use file_summary, search, symbol, or digest instead.`,
|
|
41
45
|
}
|
|
42
46
|
: { decision: 'allow' };
|
|
43
47
|
};
|
|
44
48
|
|
|
45
|
-
createHook(
|
|
49
|
+
createHook(handler, { event: 'PreToolUse', category: 'guard' });
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import{c as e,l as t,n,o as r,r as i,t as a}from"./scaffold-BNPHP-QC.js";import{_ as o,c as s,l as c,n as l,o as u,r as d,s as f,t as p}from"./templates-Bu7dZtk_.js";import{appendFileSync as m,existsSync as h,mkdirSync as g,readFileSync as _,unlinkSync as v,writeFileSync as y}from"node:fs";import{basename as b,dirname as x,resolve as S}from"node:path";import{homedir as C}from"node:os";import{AIKIT_PATHS as w,EMBEDDING_DEFAULTS as T,isUserInstalled as E}from"../../core/dist/index.js";function D(e){return h(S(e,`.cursor`))?`cursor`:h(S(e,`.claude`))?`claude-code`:h(S(e,`.windsurf`))?`windsurf`:h(S(e,`.zed`))?`zed`:h(S(e,`.idea`))?`intellij`:h(S(e,`.opencode`))?`opencode`:h(S(e,`AGENTS.md`))?`hermes`:`copilot`}function O(e){let t=[];return h(S(e,`.cursor`))&&t.push(`cursor`),(h(S(e,`.claude`))||h(S(e,`CLAUDE.md`)))&&t.push(`claude-code`),h(S(e,`.windsurf`))&&t.push(`windsurf`),h(S(e,`.zed`))&&t.push(`zed`),h(S(e,`.idea`))&&t.push(`intellij`),h(S(e,`.gemini`))&&t.push(`gemini-cli`),(h(S(e,`.codex`))||h(S(e,`codex.md`)))&&t.push(`codex-cli`),(h(S(e,`.opencode`))||h(S(e,`OPENCODE.md`)))&&t.push(`opencode`),t.push(`copilot`),t.push(`hermes`),[...new Set(t)]}function k(e){return{servers:{[e]:{...f}}}}function A(e){let{type:t,...n}=f;return{mcpServers:{[e]:n}}}function j(e){let{type:t,...n}=f;return{context_servers:{[e]:{...n}}}}function M(e){let{type:t,...n}=f;return{mcp:{[e]:{type:`local`,command:[n.command,...n.args]}}}}const N={scaffoldDir:`general`,writeMcpConfig(e,t){let n=S(e,`.vscode`),r=S(n,`mcp.json`);h(r)||(g(n,{recursive:!0}),y(r,`${JSON.stringify(k(t),null,2)}\n`,`utf-8`),console.log(` Created .vscode/mcp.json`))},writeInstructions(e,t){let n=S(e,`.github`),r=S(n,`copilot-instructions.md`);g(n,{recursive:!0}),y(r,l(b(e),t),`utf-8`),console.log(` Updated .github/copilot-instructions.md`)},writeAgentsMd(e,t){y(S(e,`AGENTS.md`),p(b(e),t),`utf-8`),console.log(` Updated AGENTS.md`)}},P=`Orchestrator`;function F(e){let{command:t,args:n}=f;return{[e]:{command:t,args:[...n],type:`stdio`,env:{}}}}function I(e,t){let n=t??S(C(),`.claude`),r=S(n,`settings.json`),i=S(n,`..`,`.claude.json`),a=F(e),o;if(!h(i))o={};else try{o=JSON.parse(_(i,`utf-8`))}catch{console.warn(` ⚠ ${i} invalid JSON — overwriting`),o={}}g(x(i),{recursive:!0}),o.mcpServers={...o.mcpServers||{},...a},y(i,`${JSON.stringify(o,null,2)}\n`,`utf-8`),console.log(` Updated ${i} — ${e} MCP server`);let s;if(!h(r))g(n,{recursive:!0}),s={agent:P};else{try{s=JSON.parse(_(r,`utf-8`))}catch{console.warn(` ⚠ ${r} invalid JSON — overwriting`),s={}}delete s.mcpServers}s.agent=P;let c=S(n,`hooks`,`scripts`).replace(/\\/g,`/`);try{let e=d(`claude`,c);e.hooks&&Object.keys(e.hooks).length>0&&(s.hooks=e.hooks)}catch{console.warn(` ⚠ Failed to generate hooks block`)}y(r,`${JSON.stringify(s,null,2)}\n`,`utf-8`),console.log(` Updated ${r} — default agent + hooks`)}const L={scaffoldDir:`general`,writeMcpConfig(e,t){I(t);let n=S(e,`.mcp.json`);h(n)||(y(n,`${JSON.stringify(A(t),null,2)}\n`,`utf-8`),console.log(` Created .mcp.json`))},writeInstructions(e,t){let n=S(e,`CLAUDE.md`),r=b(e);y(n,`${l(r,t)}\n---\n\n${p(r,t)}`,`utf-8`),console.log(` Updated CLAUDE.md`)},writeAgentsMd(e,t){}},R={scaffoldDir:`general`,writeMcpConfig(e,t){let n=S(e,`.cursor`),r=S(n,`mcp.json`);h(r)||(g(n,{recursive:!0}),y(r,`${JSON.stringify(A(t),null,2)}\n`,`utf-8`),console.log(` Created .cursor/mcp.json`))},writeInstructions(e,t){let n=S(e,`.cursor`,`rules`),r=S(n,`aikit.mdc`);g(n,{recursive:!0});let i=b(e);y(r,`${l(i,t)}\n---\n\n${p(i,t)}`,`utf-8`),console.log(` Updated .cursor/rules/aikit.mdc`);let a=S(n,`kb.mdc`);h(a)&&a!==r&&(v(a),console.log(` Removed legacy .cursor/rules/kb.mdc`))},writeAgentsMd(e,t){}},z={scaffoldDir:`general`,writeMcpConfig(e,t){let n=S(e,`.vscode`),r=S(n,`mcp.json`);h(r)||(g(n,{recursive:!0}),y(r,`${JSON.stringify(k(t),null,2)}\n`,`utf-8`),console.log(` Created .vscode/mcp.json (Windsurf-compatible)`))},writeInstructions(e,t){let n=S(e,`.windsurfrules`),r=b(e);y(n,`${l(r,t)}\n---\n\n${p(r,t)}`,`utf-8`),console.log(` Updated .windsurfrules`)},writeAgentsMd(e,t){}},B={scaffoldDir:`general`,writeMcpConfig(e,t){let n=S(e,`.zed`),r=S(n,`settings.json`);if(g(n,{recursive:!0}),!h(r))y(r,`${JSON.stringify(j(t),null,2)}\n`,`utf-8`),console.log(` Created .zed/settings.json`);else{let e;try{e=JSON.parse(_(r,`utf-8`))}catch{console.warn(` ⚠ .zed/settings.json contains invalid JSON — skipping MCP config merge`);return}e.context_servers?.[t]||(e.context_servers={...e.context_servers,...j(t).context_servers},y(r,`${JSON.stringify(e,null,2)}\n`,`utf-8`),console.log(` Updated .zed/settings.json with context_servers`))}},writeInstructions(e,t){let n=S(e,`.rules`),r=b(e);y(n,`${l(r,t)}\n---\n\n${p(r,t)}`,`utf-8`),console.log(` Updated .rules`)},writeAgentsMd(e,t){}},V={scaffoldDir:`general`,writeMcpConfig(e,t){let n=S(e,`mcp.json`);h(n)||(y(n,`${JSON.stringify(A(t),null,2)}\n`,`utf-8`),console.log(` Created mcp.json`))},writeInstructions(e,t){let n=S(e,`.aiassistant`,`rules`),r=S(n,`aikit.md`);g(n,{recursive:!0});let i=b(e);y(r,`${l(i,t)}\n---\n\n${p(i,t)}`,`utf-8`),console.log(` Updated .aiassistant/rules/aikit.md`)},writeAgentsMd(e,t){}},H={scaffoldDir:`general`,writeMcpConfig(e,t){let n=S(e,`.opencode`),r=S(n,`opencode.json`);h(r)||(g(n,{recursive:!0}),y(r,`${JSON.stringify(M(t),null,2)}\n`,`utf-8`),console.log(` Created .opencode/opencode.json`))},writeInstructions(e,t){let n=S(e,`OPENCODE.md`),r=b(e);y(n,`${l(r,t)}\n---\n\n${p(r,t)}`,`utf-8`),console.log(` Updated OPENCODE.md`)},writeAgentsMd(e,t){}},U={scaffoldDir:`general`,writeMcpConfig(e,t){},writeInstructions(e,t){let n=S(e,`AGENTS.md`),r=b(e);y(n,`${l(r,t)}\n---\n\n${p(r,t)}`,`utf-8`),console.log(` Updated AGENTS.md (Hermes)`)},writeAgentsMd(e,t){}};function W(e){switch(e){case`copilot`:return N;case`claude-code`:return L;case`cursor`:return R;case`windsurf`:return z;case`zed`:return B;case`intellij`:return V;case`opencode`:return H;case`hermes`:return U;case`gemini-cli`:case`codex-cli`:return N}}const G={serverName:s,sources:[{path:`.`,excludePatterns:[`**/node_modules/**`,`**/dist/**`,`**/build/**`,`**/.git/**`,`**/${w.data}/**`,`**/coverage/**`,`**/*.min.js`,`**/package-lock.json`,`**/pnpm-lock.yaml`]}],indexing:{chunkSize:1500,chunkOverlap:200,minChunkSize:100},embedding:{model:T.model,dimensions:T.dimensions},store:{backend:`sqlite-vec`,path:w.data},curated:{path:w.aiCurated}};function K(e,t){let n=S(e,`aikit.config.json`);return h(n)&&!t?(console.log(`aikit.config.json already exists. Use --force to overwrite.`),!1):(y(n,`${JSON.stringify(G,null,2)}\n`,`utf-8`),console.log(` Created aikit.config.json`),!0)}function q(e){let t=S(e,`.gitignore`),n=[{dir:`.flows/`,label:`AI Kit flow runs`}];if(h(t)){let e=_(t,`utf-8`),r=n.filter(t=>!e.includes(t.dir));r.length>0&&(m(t,`\n${r.map(e=>`# ${e.label}\n${e.dir}`).join(`
|
|
2
|
-
`)}\n`,`utf-8`),console.log(` Added ${r.map(e=>e.dir).join(`, `)} to .gitignore`))}else y(t,`${n.map(e=>`# ${e.label}\n${e.dir}`).join(`
|
|
3
|
-
`)}\n`,`utf-8`),console.log(` Created .gitignore with AI Kit entries`)}function J(){return G.serverName}const Y=[`decisions`,`patterns`,`conventions`,`troubleshooting`];function X(e){let t=S(e,`.ai`,`curated`);h(t)||(g(t,{recursive:!0}),console.log(` Created .ai/curated/`));for(let e of Y){let n=S(t,e);h(n)||g(n,{recursive:!0})}console.log(` Created .ai/curated/{${Y.join(`,`)}}/`)}function Z(e){switch(e){case`zed`:return`zed`;case`intellij`:return`intellij`;case`claude-code`:return`claude-code`;case`gemini-cli`:return`gemini`;case`codex-cli`:return`codex`;case`opencode`:return`opencode`;case`hermes`:return`hermes`;default:return`copilot`}}async function Q(n){let i=process.cwd();if(S(i)===S(C())){console.log(` Skipping workspace init: cannot scaffold the home directory.`),console.log(" Use `aikit init` (without --workspace) for user-level setup.");return}if(!K(i,n.force))return;q(i);let a=J(),s=W(D(i));s.writeMcpConfig(i,a),s.writeInstructions(i,a),s.writeAgentsMd(i,a);let l=o(),d=JSON.parse(_(S(l,`package.json`),`utf-8`)).version;await t(i,l,[...c],d,n.force),await r(i,l,[...u],d,n.force);let f=O(i),p=new Set;for(let t of f){let r=Z(t);p.has(r)||(p.add(r),await e(i,l,r,d,n.force))}X(i),console.log(`
|
|
4
|
-
AI Kit initialized! Next steps:`),console.log(` aikit reindex Index your codebase`),console.log(` aikit search Search indexed content`),console.log(` aikit serve Start MCP server for IDE integration`),E()&&console.log(`
|
|
5
|
-
Note: User-level AI Kit is also installed. This workspace uses its own local data store.`)}async function ee(e){E()?await $(e):await Q(e)}async function $(t){let n=process.cwd();if(S(n)===S(C())){console.log(` Skipping workspace scaffold: cannot scaffold the home directory.`);return}let i=J(),a=W(D(n));a.writeInstructions(n,i),a.writeAgentsMd(n,i);let s=o(),c=JSON.parse(_(S(s,`package.json`),`utf-8`)).version,l=O(n),d=new Set;for(let r of l){let i=Z(r);d.has(i)||(d.add(i),await e(n,s,i,c,t.force))}await r(n,s,[...u],c,t.force),X(n),q(n),console.log(`
|
|
6
|
-
Workspace scaffolded for user-level AI Kit! Files added:`),console.log(` Instruction files (AGENTS.md, copilot-instructions.md, etc.)`),console.log(` .ai/curated/ directories`),console.log(` .github/agents/ & .github/prompts/`),console.log(`
|
|
7
|
-
The user-level AI Kit server will auto-index this workspace when opened in your IDE.`)}async function te(){let e=process.cwd(),t=O(e),r=o(),s=[...await i(e,r,[...c])],l=new Set;for(let i of t){let t=Z(i);l.has(t)||(l.add(t),s.push(...await n(e,r,t)))}s.push(...await a(e,r,[...u]));let d={summary:{total:s.length,new:s.filter(e=>e.status===`new`).length,outdated:s.filter(e=>e.status===`outdated`).length,current:s.filter(e=>e.status===`current`).length},files:s};console.log(JSON.stringify(d,null,2))}export{te as guideProject,Q as initProject,$ as initScaffoldOnly,ee as initSmart};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{n as e,t}from"./server-D5eu57oU.js";export{t as buildPreludeInjection,e as generatePrelude};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{r as e}from"./server-D5eu57oU.js";export{e as createSamplingClient};
|
|
@@ -1,36 +0,0 @@
|
|
|
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);
|
|
@@ -1,13 +0,0 @@
|
|
|
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);
|
|
@@ -1,85 +0,0 @@
|
|
|
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);
|
|
@@ -1,53 +0,0 @@
|
|
|
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);
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* session-observer — PostToolUse hook for autonomous learning.
|
|
4
|
-
*
|
|
5
|
-
* Captures tool usage observations to a JSONL buffer for later pattern analysis.
|
|
6
|
-
* Fire-and-forget writes (<5ms). Nudges agent when batch threshold reached.
|
|
7
|
-
*
|
|
8
|
-
* @tier efficiency
|
|
9
|
-
* @event PostToolUse
|
|
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 BATCH_THRESHOLD = 20;
|
|
20
|
-
const EXCLUDE = new Set([
|
|
21
|
-
'knowledge',
|
|
22
|
-
'replay',
|
|
23
|
-
'stash',
|
|
24
|
-
'checkpoint',
|
|
25
|
-
'session_digest',
|
|
26
|
-
'reindex',
|
|
27
|
-
'status',
|
|
28
|
-
'flow',
|
|
29
|
-
'evidence_map',
|
|
30
|
-
'present',
|
|
31
|
-
]);
|
|
32
|
-
|
|
33
|
-
const countLines = (filePath) => {
|
|
34
|
-
try {
|
|
35
|
-
return fs.readFileSync(filePath, 'utf8').split('\n').filter(Boolean).length;
|
|
36
|
-
} catch {
|
|
37
|
-
return 0;
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const summarize = (input, maxLen = 200) => {
|
|
42
|
-
if (!input) return '';
|
|
43
|
-
const value = typeof input === 'string' ? input : JSON.stringify(input);
|
|
44
|
-
return value.length > maxLen ? `${value.slice(0, maxLen)}...` : value;
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
/** Captures tool-use observations and nudges lesson extraction at batch thresholds. */
|
|
48
|
-
export const sessionObserver = async (context) => {
|
|
49
|
-
if (context.event !== 'PostToolUse') return { decision: 'allow' };
|
|
50
|
-
const toolBase = context.toolName.replace(/^mcp_aikit_/, '');
|
|
51
|
-
if (EXCLUDE.has(toolBase)) return { decision: 'allow' };
|
|
52
|
-
|
|
53
|
-
const entry = JSON.stringify({
|
|
54
|
-
ts: Date.now(),
|
|
55
|
-
tool: context.toolName,
|
|
56
|
-
input: summarize(context.toolInput),
|
|
57
|
-
files: context.filePaths.slice(0, 5),
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
try {
|
|
61
|
-
fs.appendFileSync(bufferPath, `${entry}\n`);
|
|
62
|
-
} catch {}
|
|
63
|
-
|
|
64
|
-
const count = countLines(bufferPath);
|
|
65
|
-
if (count > 0 && count >= BATCH_THRESHOLD && count % BATCH_THRESHOLD === 0) {
|
|
66
|
-
return {
|
|
67
|
-
additionalContext: [
|
|
68
|
-
`[Observer] ${count} tool calls captured this session.`,
|
|
69
|
-
'Consider: knowledge({ action: "lesson", subAction: "auto-observe" })',
|
|
70
|
-
].join(' '),
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return { decision: 'allow' };
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
createHook(sessionObserver);
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import fs from 'node:fs';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import { createHook } from './_runtime.mjs';
|
|
5
|
-
|
|
6
|
-
const readJson = (cwd, name) => {
|
|
7
|
-
try {
|
|
8
|
-
return JSON.parse(fs.readFileSync(path.join(cwd, name), 'utf8'));
|
|
9
|
-
} catch {
|
|
10
|
-
return null;
|
|
11
|
-
}
|
|
12
|
-
};
|
|
13
|
-
const truncate = (value, limit) =>
|
|
14
|
-
String(value ?? '')
|
|
15
|
-
.replace(/\s+/g, ' ')
|
|
16
|
-
.trim()
|
|
17
|
-
.slice(0, limit);
|
|
18
|
-
const detectStack = (manifest, pkg) => {
|
|
19
|
-
const explicit = manifest?.stack ?? manifest?.techStack ?? manifest?.project?.stack;
|
|
20
|
-
if (explicit) return Array.isArray(explicit) ? explicit.join(', ') : String(explicit);
|
|
21
|
-
const deps = { ...pkg?.dependencies, ...pkg?.devDependencies };
|
|
22
|
-
const found = [];
|
|
23
|
-
if (pkg) found.push('Node.js');
|
|
24
|
-
if (deps.react) found.push('React');
|
|
25
|
-
if (deps.vue) found.push('Vue');
|
|
26
|
-
if (deps.next) found.push('Next.js');
|
|
27
|
-
if (deps.express) found.push('Express');
|
|
28
|
-
if (deps.fastify) found.push('Fastify');
|
|
29
|
-
if (deps['@nestjs/core']) found.push('NestJS');
|
|
30
|
-
return found.join(', ');
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
/** Injects compact project metadata into every subagent start. */
|
|
34
|
-
export const subagentContext = async (context) => {
|
|
35
|
-
if (context.event !== 'SubagentStart') return { decision: 'allow' };
|
|
36
|
-
const manifest = readJson(context.cwd, '.aikit-scaffold.json');
|
|
37
|
-
const pkg = readJson(context.cwd, 'package.json');
|
|
38
|
-
if (!manifest && !pkg) return { decision: 'allow' };
|
|
39
|
-
const conventions =
|
|
40
|
-
manifest?.conventions ?? manifest?.keyConventions ?? manifest?.project?.conventions;
|
|
41
|
-
const conventionText = Array.isArray(conventions)
|
|
42
|
-
? conventions.slice(0, 3).join('; ')
|
|
43
|
-
: conventions;
|
|
44
|
-
const name = truncate(pkg?.name ?? manifest?.name ?? path.basename(context.cwd), 60);
|
|
45
|
-
const description = truncate(
|
|
46
|
-
pkg?.description ?? manifest?.description ?? manifest?.project?.description,
|
|
47
|
-
120,
|
|
48
|
-
);
|
|
49
|
-
const stack = truncate(detectStack(manifest, pkg) || 'unknown', 80);
|
|
50
|
-
const contextText = [
|
|
51
|
-
`Project: ${name}${description ? ` — ${description}` : ''}`,
|
|
52
|
-
`Stack: ${stack}`,
|
|
53
|
-
`Conventions: ${truncate(conventionText || 'Reuse existing scaffold patterns.', 120)}`,
|
|
54
|
-
'Use aikit search/file_summary — NOT raw read_file for understanding code.',
|
|
55
|
-
].join('\n');
|
|
56
|
-
return { additionalContext: contextText };
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
createHook(subagentContext);
|