@vpxa/aikit 0.1.358 → 0.1.360

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 (51) hide show
  1. package/package.json +1 -1
  2. package/packages/analyzers/dist/index.d.ts +1 -1
  3. package/packages/cli/dist/index.js +19 -19
  4. package/packages/cli/dist/init-C_WzeMTD.js +7 -0
  5. package/packages/cli/dist/{templates-Bu7dZtk_.js → templates-DUUaYk_d.js} +13 -12
  6. package/packages/core/dist/index.d.ts +43 -2
  7. package/packages/core/dist/index.js +1 -1
  8. package/packages/present/dist/annotation-layer.js +10 -10
  9. package/packages/server/dist/bin.js +1 -1
  10. package/packages/server/dist/index.js +1 -1
  11. package/packages/server/dist/observations-CfglxBMB.js +6 -0
  12. package/packages/server/dist/observations-CrQU_HVm.js +5 -0
  13. package/packages/server/dist/prelude-CNOjd5qt.js +2 -0
  14. package/packages/server/dist/prelude-Dki25eYn.js +1 -0
  15. package/packages/server/dist/sampling-B8NwOv_z.js +1 -0
  16. package/packages/server/dist/sampling-DOOgTH1B.js +2 -0
  17. package/packages/server/dist/{server-D5eu57oU.js → server-C2n9kQMF.js} +178 -183
  18. package/packages/server/dist/{server-D7gZ4K_H.js → server-CizTllz1.js} +178 -183
  19. package/packages/server/dist/{server-http-AarbD3gm.js → server-http-BaMUtb5Y.js} +1 -1
  20. package/packages/server/dist/{server-http-wnYmWFkC.js → server-http-Dd2uCMBi.js} +1 -1
  21. package/packages/server/dist/{server-stdio-BWoMqXU8.js → server-stdio-B5rk-PAI.js} +1 -1
  22. package/packages/server/dist/{server-stdio-CBGO6XiO.js → server-stdio-CtR4QNT3.js} +1 -1
  23. package/packages/tools/dist/index.js +69 -69
  24. package/scaffold/AUTOMATION-BOUNDARY-ANALYSIS.md +78 -0
  25. package/scaffold/dist/adapters/_shared.mjs +7 -6
  26. package/scaffold/dist/adapters/claude-code.mjs +7 -7
  27. package/scaffold/dist/adapters/claude-desktop.mjs +7 -0
  28. package/scaffold/dist/adapters/gemini.mjs +6 -6
  29. package/scaffold/dist/adapters/hermes.mjs +5 -5
  30. package/scaffold/dist/adapters/hooks.mjs +1 -1
  31. package/scaffold/dist/adapters/opencode.mjs +7 -7
  32. package/scaffold/dist/definitions/exec-hooks.mjs +1 -1
  33. package/scaffold/dist/definitions/platform-capabilities.mjs +1 -0
  34. package/scaffold/dist/definitions/protocols.mjs +7 -6
  35. package/scaffold/dist/definitions/skills/aikit.mjs +4 -4
  36. package/scaffold/general/hooks/scripts/_runtime.mjs +87 -15
  37. package/scaffold/general/hooks/scripts/freshness-signal.mjs +196 -0
  38. package/scaffold/general/hooks/scripts/privacy-guard.mjs +19 -6
  39. package/scaffold/general/hooks/scripts/scope-guard.mjs +160 -0
  40. package/scaffold/general/hooks/scripts/scout-guard.mjs +18 -14
  41. package/packages/cli/dist/init-CWLiK7bC.js +0 -7
  42. package/packages/server/dist/prelude-DetbWo8R.js +0 -1
  43. package/packages/server/dist/prelude-hfAEi93R.js +0 -2
  44. package/packages/server/dist/sampling-CWjnUevo.js +0 -2
  45. package/packages/server/dist/sampling-CnE3owSt.js +0 -1
  46. package/scaffold/general/hooks/scripts/post-edit-check.mjs +0 -36
  47. package/scaffold/general/hooks/scripts/pre-compact-save.mjs +0 -13
  48. package/scaffold/general/hooks/scripts/session-init.mjs +0 -85
  49. package/scaffold/general/hooks/scripts/session-learn.mjs +0 -53
  50. package/scaffold/general/hooks/scripts/session-observer.mjs +0 -77
  51. package/scaffold/general/hooks/scripts/subagent-context.mjs +0 -59
@@ -0,0 +1,196 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * freshness-signal — SessionStart signal for workspace state freshness.
4
+ *
5
+ * Checks: onboard data exists, scaffold deployed, active flows.
6
+ * Returns silence when healthy. Signals only stale or missing state.
7
+ *
8
+ * @category signal
9
+ * @event SessionStart
10
+ */
11
+
12
+ import { createHash } from 'node:crypto';
13
+ import fs from 'node:fs';
14
+ import os from 'node:os';
15
+ import path from 'node:path';
16
+ import { createHook } from './_runtime.mjs';
17
+
18
+ const AIKIT_WORKSPACES = path.join(os.homedir(), '.aikit', 'workspaces');
19
+ const DAY_MS = 86_400_000;
20
+
21
+ const exists = (p) => {
22
+ try {
23
+ return fs.existsSync(p);
24
+ } catch {
25
+ return false;
26
+ }
27
+ };
28
+
29
+ const readJson = (p) => {
30
+ try {
31
+ return JSON.parse(fs.readFileSync(p, 'utf8'));
32
+ } catch {
33
+ return null;
34
+ }
35
+ };
36
+
37
+ /**
38
+ * Check scaffold manifest at known deploy locations.
39
+ * Manifest is stored alongside the scaffold root, not in the workspace cwd.
40
+ */
41
+ function checkScaffold(cwd) {
42
+ const candidates = [
43
+ path.join(cwd, '.github', '.aikit-scaffold.json'),
44
+ path.join(os.homedir(), '.claude', '.aikit-scaffold.json'),
45
+ path.join(os.homedir(), '.copilot', '.aikit-scaffold.json'),
46
+ ];
47
+
48
+ for (const manifestPath of candidates) {
49
+ if (!exists(manifestPath)) continue;
50
+ const manifest = readJson(manifestPath);
51
+ if (!manifest) return { deployed: true, valid: false };
52
+ const installedAt = manifest.installedAt || manifest.generatedAt;
53
+ const age = installedAt ? Date.now() - new Date(installedAt).getTime() : null;
54
+ return {
55
+ deployed: true,
56
+ valid: true,
57
+ age,
58
+ stale: age !== null && age > 30 * DAY_MS,
59
+ };
60
+ }
61
+
62
+ return { deployed: false };
63
+ }
64
+
65
+ /**
66
+ * Compute the same partition key as aikit's computePartitionKey.
67
+ * Partitions are ~/.aikit/workspaces/{basename}-{sha256(cwd).slice(0,8)}/
68
+ *
69
+ * Must match the canonical TypeScript implementation in global-registry.ts:
70
+ * resolve(cwd) → normalizePathForHash (lowercase on win32/darwin only)
71
+ * NO slash normalization — resolve produces native backslashes on Windows.
72
+ */
73
+ function computePartitionKey(wsPath) {
74
+ const absPath = path.resolve(wsPath);
75
+ const name =
76
+ path
77
+ .basename(absPath)
78
+ .toLowerCase()
79
+ .replace(/[^a-z0-9-]/g, '-') || 'workspace';
80
+ const normalized =
81
+ process.platform === 'win32' || process.platform === 'darwin' ? absPath.toLowerCase() : absPath;
82
+ const hash = createHash('sha256').update(normalized).digest('hex').slice(0, 8);
83
+ return `${name}-${hash}`;
84
+ }
85
+
86
+ /**
87
+ * Check workspace state for the specific partition matching this cwd.
88
+ * Returns state when workspace-core.md exists. Returns { partition, missingL0Card: true }
89
+ * when the partition dir exists but the l0-card is absent (partial onboard).
90
+ */
91
+ function findWorkspaceState(cwd) {
92
+ if (!exists(AIKIT_WORKSPACES)) return null;
93
+
94
+ const partition = computePartitionKey(cwd);
95
+ const partitionDir = path.join(AIKIT_WORKSPACES, partition);
96
+ if (!exists(partitionDir)) return null;
97
+
98
+ const cardPath = path.join(partitionDir, 'l0-cards', 'workspace-core.md');
99
+ if (!exists(cardPath)) return { partition, missingL0Card: true };
100
+
101
+ try {
102
+ const stat = fs.statSync(cardPath);
103
+ return { partition, age: Date.now() - stat.mtimeMs };
104
+ } catch {
105
+ return { partition, missingL0Card: true };
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Check for active flows in workspace .flows/ directory.
111
+ * Flow runs are stored as .flows/<slug>/meta.json directories.
112
+ */
113
+ function checkActiveFlows(cwd) {
114
+ const flowsDir = path.join(cwd, '.flows');
115
+ if (!exists(flowsDir)) return null;
116
+
117
+ try {
118
+ const entries = fs.readdirSync(flowsDir);
119
+ const activeFlows = [];
120
+ for (const entry of entries) {
121
+ const metaPath = path.join(flowsDir, entry, 'meta.json');
122
+ if (!exists(metaPath)) continue;
123
+ try {
124
+ const meta = JSON.parse(fs.readFileSync(metaPath, 'utf8'));
125
+ if (meta?.status === 'active') {
126
+ activeFlows.push({ slug: entry, meta, mtime: fs.statSync(metaPath).mtimeMs });
127
+ }
128
+ } catch {
129
+ // Skip unreadable meta files
130
+ }
131
+ }
132
+ if (activeFlows.length === 0) return null;
133
+
134
+ // Most recently modified active flow
135
+ const sorted = activeFlows.sort((a, b) => b.mtime - a.mtime);
136
+ const meta = sorted[0].meta;
137
+ return {
138
+ slug: sorted[0].slug,
139
+ status: meta.status || 'unknown',
140
+ // Flow scope may be in flow.scope, flow.roots, or flow.config.scope
141
+ scope: meta?.scope || meta?.roots || meta?.config?.scope || null,
142
+ };
143
+ } catch {
144
+ return null;
145
+ }
146
+ }
147
+
148
+ /** Signal workspace freshness state — silence when healthy. */
149
+ const handler = async (context) => {
150
+ const signals = [];
151
+
152
+ // 1. Scaffold check — look for manifest at deploy roots
153
+ const scaffold = checkScaffold(context.cwd);
154
+ if (!scaffold.deployed) {
155
+ signals.push('Scaffold not deployed. Run `aikit init` to set up workspace.');
156
+ } else if (!scaffold.valid) {
157
+ signals.push('Scaffold manifest corrupted. Run `aikit init --force` to repair.');
158
+ } else if (scaffold.stale) {
159
+ signals.push(
160
+ `Scaffold last deployed ${Math.round(scaffold.age / DAY_MS)} days ago. Consider \`aikit init\`.`,
161
+ );
162
+ }
163
+
164
+ // 2. Onboard / workspace state
165
+ const ws = findWorkspaceState(context.cwd);
166
+ if (!ws) {
167
+ signals.push(
168
+ 'No onboard data found. Run `status()` or `onboard({ path: "." })` to initialize.',
169
+ );
170
+ } else if (ws.missingL0Card) {
171
+ signals.push(
172
+ 'No workspace-core briefing found, though onboard directory exists. Run `onboard({ path: ".", mode: "generate" })` to generate it.',
173
+ );
174
+ } else if (ws.age > 30 * DAY_MS) {
175
+ signals.push(
176
+ `Onboard data stale (${Math.round(ws.age / DAY_MS)} days). Run \`onboard({ path: ".", update: true })\`.`,
177
+ );
178
+ }
179
+
180
+ // 3. Active flows
181
+ const flow = checkActiveFlows(context.cwd);
182
+ if (flow) {
183
+ signals.push(
184
+ `Active flow: ${flow.slug} (${flow.status}). Run \`flow({ action: 'status' })\` to check.`,
185
+ );
186
+ }
187
+
188
+ // Silence when healthy
189
+ if (signals.length === 0) return { decision: 'allow' };
190
+
191
+ return {
192
+ additionalContext: `[Freshness]\n${signals.join('\n')}`,
193
+ };
194
+ };
195
+
196
+ createHook(handler, { event: 'SessionStart', category: 'signal', outputBudgetLines: 5 });
@@ -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
- /** 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' };
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: `Blocked: reading sensitive file ${blockedPath}. Use environment variables or secrets manager instead.`,
47
+ reason: `Reading sensitive file: ${blockedPath}. Use environment variables or secrets manager.`,
35
48
  }
36
49
  : { decision: 'allow' };
37
50
  };
38
51
 
39
- createHook(privacyGuard);
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
- 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' };
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 normalizedPath = normalize(filePath);
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: `Blocked: accessing ${blockedDir} wastes context. Use search tools or package documentation instead.`,
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(scoutGuard);
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,2 +0,0 @@
1
- #!/usr/bin/env node
2
- import{n as e,t}from"./server-D7gZ4K_H.js";export{t as buildPreludeInjection,e as generatePrelude};
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- import{r as e}from"./server-D7gZ4K_H.js";export{e as createSamplingClient};
@@ -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);