milens 0.7.0 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. package/.agents/skills/adapters/SKILL.md +59 -51
  2. package/.agents/skills/analyzer/SKILL.md +23 -21
  3. package/.agents/skills/apps/SKILL.md +65 -68
  4. package/.agents/skills/docs/SKILL.md +10 -25
  5. package/.agents/skills/milens/SKILL.md +11 -8
  6. package/.agents/skills/orchestrator/SKILL.md +13 -10
  7. package/.agents/skills/parser/SKILL.md +15 -12
  8. package/.agents/skills/root/SKILL.md +30 -29
  9. package/.agents/skills/scripts/SKILL.md +18 -16
  10. package/.agents/skills/security/SKILL.md +7 -7
  11. package/.agents/skills/server/SKILL.md +36 -26
  12. package/.agents/skills/store/SKILL.md +24 -23
  13. package/.agents/skills/test/SKILL.md +24 -22
  14. package/.agents/skills/ui/SKILL.md +6 -1
  15. package/README.md +12 -3
  16. package/adapters/README.md +144 -107
  17. package/adapters/claude-code/.claude/settings.json.hooks-snippet.json +26 -0
  18. package/adapters/claude-code/.claude-plugin/plugin.json +11 -0
  19. package/adapters/claude-code/.mcp.json +9 -0
  20. package/adapters/claude-code/CLAUDE.md +2 -0
  21. package/adapters/claude-code/hooks/hooks.json +27 -0
  22. package/adapters/codex/.codex/config.toml +3 -0
  23. package/adapters/copilot/.vscode/mcp.json +10 -0
  24. package/adapters/cursor/.cursor/mcp.json +9 -0
  25. package/adapters/cursor/.cursorrules +69 -9
  26. package/adapters/gemini/.gemini/settings.json +9 -0
  27. package/dist/agents-md.d.ts +1 -1
  28. package/dist/agents-md.js +47 -3
  29. package/dist/agents-md.js.map +1 -1
  30. package/dist/analyzer/config.d.ts +0 -1
  31. package/dist/analyzer/config.js +32 -0
  32. package/dist/analyzer/config.js.map +1 -1
  33. package/dist/analyzer/engine.d.ts +0 -1
  34. package/dist/analyzer/engine.js +67 -41
  35. package/dist/analyzer/engine.js.map +1 -1
  36. package/dist/analyzer/enrich.d.ts +0 -1
  37. package/dist/analyzer/enrich.js +81 -1
  38. package/dist/analyzer/enrich.js.map +1 -1
  39. package/dist/analyzer/resolver.d.ts +4 -2
  40. package/dist/analyzer/resolver.js +283 -36
  41. package/dist/analyzer/resolver.js.map +1 -1
  42. package/dist/analyzer/review.d.ts +2 -2
  43. package/dist/analyzer/review.js +39 -61
  44. package/dist/analyzer/review.js.map +1 -1
  45. package/dist/analyzer/risk.d.ts +20 -0
  46. package/dist/analyzer/risk.js +76 -0
  47. package/dist/analyzer/risk.js.map +1 -0
  48. package/dist/analyzer/scanner.d.ts +0 -1
  49. package/dist/analyzer/scanner.js +5 -4
  50. package/dist/analyzer/scanner.js.map +1 -1
  51. package/dist/analyzer/scope-resolver.d.ts +0 -1
  52. package/dist/analyzer/scope-resolver.js +2 -2
  53. package/dist/analyzer/scope-resolver.js.map +1 -1
  54. package/dist/build-info.d.ts +2 -0
  55. package/dist/build-info.js +4 -0
  56. package/dist/build-info.js.map +1 -0
  57. package/dist/cli.d.ts +0 -1
  58. package/dist/cli.js +225 -30
  59. package/dist/cli.js.map +1 -1
  60. package/dist/metrics.d.ts +0 -1
  61. package/dist/orchestrator/orchestrator.d.ts +1 -2
  62. package/dist/orchestrator/orchestrator.js +30 -7
  63. package/dist/orchestrator/orchestrator.js.map +1 -1
  64. package/dist/orchestrator/reporter.d.ts +0 -1
  65. package/dist/parser/extract.d.ts +7 -1
  66. package/dist/parser/extract.js +76 -29
  67. package/dist/parser/extract.js.map +1 -1
  68. package/dist/parser/lang-css.d.ts +0 -1
  69. package/dist/parser/lang-css.js +12 -0
  70. package/dist/parser/lang-css.js.map +1 -1
  71. package/dist/parser/lang-go.d.ts +0 -1
  72. package/dist/parser/lang-html.d.ts +0 -1
  73. package/dist/parser/lang-java.d.ts +0 -1
  74. package/dist/parser/lang-js.d.ts +0 -1
  75. package/dist/parser/lang-js.js +15 -4
  76. package/dist/parser/lang-js.js.map +1 -1
  77. package/dist/parser/lang-md.d.ts +0 -1
  78. package/dist/parser/lang-md.js +1 -0
  79. package/dist/parser/lang-md.js.map +1 -1
  80. package/dist/parser/lang-php.d.ts +0 -1
  81. package/dist/parser/lang-py.d.ts +0 -1
  82. package/dist/parser/lang-ruby.d.ts +0 -1
  83. package/dist/parser/lang-rust.d.ts +0 -1
  84. package/dist/parser/lang-ts.d.ts +0 -1
  85. package/dist/parser/lang-ts.js +40 -6
  86. package/dist/parser/lang-ts.js.map +1 -1
  87. package/dist/parser/lang-vue.d.ts +0 -1
  88. package/dist/parser/lang-vue.js +0 -21
  89. package/dist/parser/lang-vue.js.map +1 -1
  90. package/dist/parser/language-provider.d.ts +0 -1
  91. package/dist/parser/languages.d.ts +0 -1
  92. package/dist/parser/loader.d.ts +0 -1
  93. package/dist/security/deps.d.ts +0 -1
  94. package/dist/security/rules.d.ts +0 -1
  95. package/dist/security/rules.js +35 -35
  96. package/dist/security/rules.js.map +1 -1
  97. package/dist/server/guard-hook.d.ts +10 -0
  98. package/dist/server/guard-hook.js +179 -0
  99. package/dist/server/guard-hook.js.map +1 -0
  100. package/dist/server/hooks.d.ts +0 -1
  101. package/dist/server/hooks.js +2 -1
  102. package/dist/server/hooks.js.map +1 -1
  103. package/dist/server/mcp-prompts.d.ts +0 -1
  104. package/dist/server/mcp-prompts.js +509 -523
  105. package/dist/server/mcp-prompts.js.map +1 -1
  106. package/dist/server/mcp.d.ts +0 -1
  107. package/dist/server/mcp.js +324 -681
  108. package/dist/server/mcp.js.map +1 -1
  109. package/dist/server/test-plan.d.ts +1 -1
  110. package/dist/server/test-plan.js +25 -7
  111. package/dist/server/test-plan.js.map +1 -1
  112. package/dist/server/tools/deps.d.ts +28 -0
  113. package/dist/server/tools/deps.js +2 -0
  114. package/dist/server/tools/deps.js.map +1 -0
  115. package/dist/server/tools/findings-report.d.ts +3 -0
  116. package/dist/server/tools/findings-report.js +170 -0
  117. package/dist/server/tools/findings-report.js.map +1 -0
  118. package/dist/server/tools/resources.d.ts +3 -0
  119. package/dist/server/tools/resources.js +103 -0
  120. package/dist/server/tools/resources.js.map +1 -0
  121. package/dist/server/tools/security.d.ts +3 -0
  122. package/dist/server/tools/security.js +232 -0
  123. package/dist/server/tools/security.js.map +1 -0
  124. package/dist/server/tools/session.d.ts +3 -0
  125. package/dist/server/tools/session.js +160 -0
  126. package/dist/server/tools/session.js.map +1 -0
  127. package/dist/server/tools/testing.d.ts +3 -0
  128. package/dist/server/tools/testing.js +247 -0
  129. package/dist/server/tools/testing.js.map +1 -0
  130. package/dist/server/watcher.d.ts +0 -1
  131. package/dist/server/watcher.js +1 -1
  132. package/dist/server/watcher.js.map +1 -1
  133. package/dist/skills.d.ts +0 -1
  134. package/dist/skills.js +23 -11
  135. package/dist/skills.js.map +1 -1
  136. package/dist/store/annotations.d.ts +6 -1
  137. package/dist/store/annotations.js +56 -9
  138. package/dist/store/annotations.js.map +1 -1
  139. package/dist/store/confidence.d.ts +0 -1
  140. package/dist/store/confidence.js +2 -0
  141. package/dist/store/confidence.js.map +1 -1
  142. package/dist/store/db.d.ts +22 -1
  143. package/dist/store/db.js +225 -45
  144. package/dist/store/db.js.map +1 -1
  145. package/dist/store/registry.d.ts +1 -1
  146. package/dist/store/registry.js +17 -6
  147. package/dist/store/registry.js.map +1 -1
  148. package/dist/store/schema.sql +2 -1
  149. package/dist/store/vectors.d.ts +0 -1
  150. package/dist/types.d.ts +23 -2
  151. package/dist/ui/progress.d.ts +0 -1
  152. package/dist/uninstall.d.ts +1 -1
  153. package/dist/uninstall.js +33 -10
  154. package/dist/uninstall.js.map +1 -1
  155. package/dist/utils.d.ts +2 -1
  156. package/dist/utils.js +37 -2
  157. package/dist/utils.js.map +1 -1
  158. package/package.json +11 -8
  159. package/dist/agents-md.d.ts.map +0 -1
  160. package/dist/analyzer/config.d.ts.map +0 -1
  161. package/dist/analyzer/engine.d.ts.map +0 -1
  162. package/dist/analyzer/enrich.d.ts.map +0 -1
  163. package/dist/analyzer/resolver.d.ts.map +0 -1
  164. package/dist/analyzer/review.d.ts.map +0 -1
  165. package/dist/analyzer/scanner.d.ts.map +0 -1
  166. package/dist/analyzer/scope-resolver.d.ts.map +0 -1
  167. package/dist/analyzer/testplan.d.ts +0 -59
  168. package/dist/analyzer/testplan.d.ts.map +0 -1
  169. package/dist/analyzer/testplan.js +0 -218
  170. package/dist/analyzer/testplan.js.map +0 -1
  171. package/dist/cli.d.ts.map +0 -1
  172. package/dist/metrics.d.ts.map +0 -1
  173. package/dist/orchestrator/orchestrator.d.ts.map +0 -1
  174. package/dist/orchestrator/reporter.d.ts.map +0 -1
  175. package/dist/parser/extract.d.ts.map +0 -1
  176. package/dist/parser/lang-css.d.ts.map +0 -1
  177. package/dist/parser/lang-go.d.ts.map +0 -1
  178. package/dist/parser/lang-html.d.ts.map +0 -1
  179. package/dist/parser/lang-java.d.ts.map +0 -1
  180. package/dist/parser/lang-js.d.ts.map +0 -1
  181. package/dist/parser/lang-md.d.ts.map +0 -1
  182. package/dist/parser/lang-php.d.ts.map +0 -1
  183. package/dist/parser/lang-py.d.ts.map +0 -1
  184. package/dist/parser/lang-ruby.d.ts.map +0 -1
  185. package/dist/parser/lang-rust.d.ts.map +0 -1
  186. package/dist/parser/lang-ts.d.ts.map +0 -1
  187. package/dist/parser/lang-vue.d.ts.map +0 -1
  188. package/dist/parser/language-provider.d.ts.map +0 -1
  189. package/dist/parser/languages.d.ts.map +0 -1
  190. package/dist/parser/loader.d.ts.map +0 -1
  191. package/dist/security/deps.d.ts.map +0 -1
  192. package/dist/security/rules.d.ts.map +0 -1
  193. package/dist/server/hooks.d.ts.map +0 -1
  194. package/dist/server/mcp-prompts.d.ts.map +0 -1
  195. package/dist/server/mcp.d.ts.map +0 -1
  196. package/dist/server/test-plan.d.ts.map +0 -1
  197. package/dist/server/watcher.d.ts.map +0 -1
  198. package/dist/skills.d.ts.map +0 -1
  199. package/dist/store/annotations.d.ts.map +0 -1
  200. package/dist/store/confidence.d.ts.map +0 -1
  201. package/dist/store/db.d.ts.map +0 -1
  202. package/dist/store/registry.d.ts.map +0 -1
  203. package/dist/store/vectors.d.ts.map +0 -1
  204. package/dist/types.d.ts.map +0 -1
  205. package/dist/ui/progress.d.ts.map +0 -1
  206. package/dist/uninstall.d.ts.map +0 -1
  207. package/dist/utils.d.ts.map +0 -1
  208. package/docs/README.md +0 -25
  209. package/docs/diagram1.svg +0 -1
  210. package/docs/diagram2.svg +0 -1
  211. package/docs/diagram3.svg +0 -1
package/dist/cli.js CHANGED
@@ -1,13 +1,49 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from 'commander';
3
3
  import { resolve, join, dirname, basename } from 'node:path';
4
- import { mkdirSync, readFileSync, rmSync, existsSync } from 'node:fs';
5
- import { createHash } from 'node:crypto';
4
+ import { mkdirSync, readFileSync, rmSync, existsSync, cpSync } from 'node:fs';
5
+ import { createHash, randomUUID } from 'node:crypto';
6
6
  import { fileURLToPath } from 'node:url';
7
7
  import { loadAliases } from './analyzer/config.js';
8
+ const nodeMajor = parseInt(process.versions.node.split('.')[0], 10);
9
+ if (nodeMajor < 20) {
10
+ process.stderr.write(`milens requires Node.js >= 20.0.0 (current: ${process.version})\n`);
11
+ process.exit(1);
12
+ }
8
13
  const program = new Command();
9
14
  const __filename = fileURLToPath(import.meta.url);
10
15
  const PKG_VERSION = process.env.MILENS_VERSION ?? JSON.parse(readFileSync(join(dirname(__filename), '..', 'package.json'), 'utf-8')).version;
16
+ // Files each harness adapter contributes, relative to adapters/<harness>/ (src) and
17
+ // the project root (dest). Copying never overwrites a file that already exists at dest.
18
+ const HARNESS_ADAPTER_FILES = {
19
+ 'claude-code': [
20
+ { src: '.claude/mcp.json', dest: '.claude/mcp.json' },
21
+ { src: 'CLAUDE.md', dest: 'CLAUDE.md' },
22
+ ],
23
+ 'opencode': [
24
+ { src: '.opencode/config.json', dest: '.opencode/config.json' },
25
+ { src: 'AGENTS.md', dest: 'AGENTS.md' },
26
+ ],
27
+ 'codex': [
28
+ { src: '.codex/config.toml', dest: '.codex/config.toml' },
29
+ { src: '.codex/codex.md', dest: '.codex/codex.md' },
30
+ ],
31
+ 'cursor': [
32
+ { src: '.cursor/mcp.json', dest: '.cursor/mcp.json' },
33
+ { src: '.cursorrules', dest: '.cursorrules' },
34
+ ],
35
+ 'copilot': [
36
+ { src: '.vscode/mcp.json', dest: '.vscode/mcp.json' },
37
+ { src: '.github/copilot-instructions.md', dest: '.github/copilot-instructions.md' },
38
+ ],
39
+ 'gemini': [
40
+ { src: '.gemini/settings.json', dest: '.gemini/settings.json' },
41
+ { src: '.gemini/context.md', dest: '.gemini/context.md' },
42
+ ],
43
+ 'zed': [
44
+ { src: '.zed/settings.json', dest: '.zed/settings.json' },
45
+ ],
46
+ };
11
47
  program
12
48
  .name('milens')
13
49
  .description('Code intelligence engine — analyze codebases, build knowledge graphs, serve via MCP')
@@ -52,7 +88,6 @@ program
52
88
  files: opts.files,
53
89
  onProgress: reporter,
54
90
  });
55
- // Register in global registry
56
91
  const contentHash = createHash('sha256').update(JSON.stringify(stats)).digest('hex').slice(0, 12);
57
92
  const { RepoRegistry } = await import('./store/registry.js');
58
93
  new RepoRegistry().register(rootPath, dbPath, contentHash);
@@ -176,17 +211,23 @@ program
176
211
  program
177
212
  .command('serve')
178
213
  .description('Start MCP server')
179
- .option('-p, --path <path>', 'Repository root path', '.')
214
+ .option('-p, --path <path>', 'Repository root path (defaults to $CLAUDE_PROJECT_DIR, then cwd)')
180
215
  .option('--http', 'Use HTTP transport instead of stdio')
181
216
  .option('--port <port>', 'HTTP port', '3100')
182
217
  .action(async (opts) => {
218
+ // Project-scoped .mcp.json always passes -p explicitly (highest priority, unchanged
219
+ // behavior). Claude Code plugin-scoped MCP servers omit -p and rely on
220
+ // CLAUDE_PROJECT_DIR, which Claude Code injects into the subprocess env — unlike
221
+ // ${CLAUDE_PLUGIN_ROOT}, which resolves to the plugin's own install dir, not the
222
+ // user's project (see adapters/claude-code/README notes).
223
+ const repoPath = opts.path ?? process.env.CLAUDE_PROJECT_DIR ?? '.';
183
224
  if (opts.http) {
184
225
  const { startHttp } = await import('./server/mcp.js');
185
- await startHttp(parseInt(opts.port), resolve(opts.path));
226
+ await startHttp(parseInt(opts.port), resolve(repoPath));
186
227
  }
187
228
  else {
188
229
  const { startStdio } = await import('./server/mcp.js');
189
- await startStdio(resolve(opts.path));
230
+ await startStdio(resolve(repoPath));
190
231
  }
191
232
  });
192
233
  program
@@ -194,6 +235,7 @@ program
194
235
  .description('Show index status')
195
236
  .option('-p, --path <path>', 'Repository root path', '.')
196
237
  .action(async (opts) => {
238
+ const { existsSync } = await import('node:fs');
197
239
  const { Database } = await import('./store/db.js');
198
240
  const { RepoRegistry } = await import('./store/registry.js');
199
241
  const reg = new RepoRegistry();
@@ -202,6 +244,10 @@ program
202
244
  console.log('Not indexed.');
203
245
  return;
204
246
  }
247
+ if (!existsSync(entry.dbPath)) {
248
+ console.log(`Not indexed. Run \`milens analyze\` first.\n(Registry entry stale — directory may have been deleted: ${entry.rootPath})`);
249
+ return;
250
+ }
205
251
  const db = new Database(entry.dbPath);
206
252
  const stats = db.getStats();
207
253
  console.log(`Repository: ${entry.rootPath}`);
@@ -294,7 +340,6 @@ program
294
340
  console.log('║ Step 1/4: Scanning project... ║');
295
341
  console.log('╚══════════════════════════════════════════╝');
296
342
  console.log();
297
- // Run initial scan
298
343
  const initialScan = uninstall({
299
344
  rootPath,
300
345
  dryRun: false,
@@ -304,7 +349,6 @@ program
304
349
  keepConfigs: !!opts.keepConfigs,
305
350
  scanOnly: true,
306
351
  });
307
- // Collect all items for display
308
352
  const allScanned = [];
309
353
  for (const item of initialScan.autoRemoved) {
310
354
  allScanned.push(`${item.file} (${item.action})`);
@@ -329,7 +373,6 @@ program
329
373
  console.log('║ Step 2/4: Auto-removing... ║');
330
374
  console.log('╚══════════════════════════════════════════╝');
331
375
  console.log();
332
- // Run auto-remove (without interactive items)
333
376
  const autoResult = uninstall({
334
377
  rootPath,
335
378
  dryRun: false,
@@ -339,7 +382,6 @@ program
339
382
  keepConfigs: !!opts.keepConfigs,
340
383
  scanOnly: false,
341
384
  });
342
- // Show only auto-removed items
343
385
  for (const item of autoResult.autoRemoved) {
344
386
  console.log(` ✓ ${item.file} — ${item.action}`);
345
387
  }
@@ -554,7 +596,6 @@ program
554
596
  }
555
597
  const { Database } = await import('./store/db.js');
556
598
  const db = new Database(trackDbPath);
557
- // Determine repo filter from --path
558
599
  let repoFilter;
559
600
  if (opts.path) {
560
601
  repoFilter = resolve(opts.path);
@@ -1006,7 +1047,7 @@ program
1006
1047
  const rules = loadRules().filter(r => r.enabled);
1007
1048
  console.log(`Loaded ${rules.length} active security rules\n`);
1008
1049
  const findings = [];
1009
- const MAX_FILE_SIZE = 200 * 1024;
1050
+ const MAX_FILE_SIZE = 200 * 1024; // skip files >200KB to avoid regex timeout
1010
1051
  function scanDir(dir) {
1011
1052
  try {
1012
1053
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
@@ -1064,7 +1105,6 @@ program
1064
1105
  console.log(` ${s}: ${bySeverity[s]}`);
1065
1106
  }
1066
1107
  console.log();
1067
- // Show top findings by severity
1068
1108
  const sorted = [...findings].sort((a, b) => {
1069
1109
  const order = { CRITICAL: 0, HIGH: 1, MEDIUM: 2, LOW: 3 };
1070
1110
  return (order[a.severity] ?? 4) - (order[b.severity] ?? 4);
@@ -1171,6 +1211,7 @@ program
1171
1211
  .option('-p, --path <path>', 'Repository root path', '.')
1172
1212
  .option('--profile <profile>', 'minimal|standard|full', 'standard')
1173
1213
  .option('--with <modules>', 'Comma-separated extra modules (security,ci,hooks)')
1214
+ .option('--target <harnesses>', `Comma-separated harnesses to install adapters for (${Object.keys(HARNESS_ADAPTER_FILES).join(',')},all)`)
1174
1215
  .option('--interactive', 'Interactive install mode')
1175
1216
  .action(async (opts) => {
1176
1217
  const root = resolve(opts.path);
@@ -1211,6 +1252,7 @@ program
1211
1252
  console.log(' claude-code, opencode, codex, cursor, copilot, gemini, zed, all');
1212
1253
  const harnessChoice = await ask('Harnesses [all]: ');
1213
1254
  const harnesses = harnessChoice.trim() || 'all';
1255
+ opts.target = harnesses;
1214
1256
  // Generate command
1215
1257
  const withFlags = opts.with ? `--with ${opts.with}` : '';
1216
1258
  const harnessFlag = harnesses === 'all' ? '' : `--target ${harnesses}`;
@@ -1242,10 +1284,11 @@ program
1242
1284
  console.log(' (run `milens analyze -p . --force` manually if this fails)');
1243
1285
  }
1244
1286
  console.log('Step 2/4: Generating AGENTS.md...');
1287
+ let db = null;
1245
1288
  try {
1246
1289
  const dbPath = new RepoRegistry().findDbPath(root);
1247
1290
  if (dbPath) {
1248
- const db = new Database(dbPath);
1291
+ db = new Database(dbPath);
1249
1292
  const { generateAgentsMd } = await import('./agents-md.js');
1250
1293
  const agentsMd = generateAgentsMd(db, root);
1251
1294
  const { writeFileSync, mkdirSync, existsSync } = await import('node:fs');
@@ -1253,7 +1296,6 @@ program
1253
1296
  mkdirSync(root, { recursive: true });
1254
1297
  writeFileSync(resolve(root, 'AGENTS.md'), agentsMd);
1255
1298
  console.log(' ✓ AGENTS.md created');
1256
- db.close();
1257
1299
  }
1258
1300
  }
1259
1301
  catch (e) {
@@ -1262,33 +1304,46 @@ program
1262
1304
  if (opts.profile !== 'minimal') {
1263
1305
  console.log('Step 3/4: Installing skill files...');
1264
1306
  try {
1265
- const { cpSync, existsSync, mkdirSync } = await import('node:fs');
1266
- const { join: pathJoin } = await import('node:path');
1267
- const { fileURLToPath } = await import('node:url');
1268
- const skillSrc = pathJoin(resolve(fileURLToPath(import.meta.url), '..', '..'), '.agents', 'skills');
1269
- const skillDst = pathJoin(root, '.agents', 'skills');
1270
- if (existsSync(skillSrc)) {
1271
- if (!existsSync(skillDst))
1272
- mkdirSync(skillDst, { recursive: true });
1273
- cpSync(skillSrc, skillDst, { recursive: true });
1274
- console.log(' ✓ Skill files installed');
1307
+ if (db) {
1308
+ const { generateSkills } = await import('./skills.js');
1309
+ const result = generateSkills(db, root, ['agents']);
1310
+ console.log(` ✓ Generated ${result.count} skill files`);
1311
+ for (const d of result.dirs)
1312
+ console.log(` ${d}`);
1275
1313
  }
1276
1314
  else {
1277
- console.log(' ⚠ Skill template not found (run from milens repo)');
1315
+ console.log(' ⚠ Skills skipped: no database available (run analyze first)');
1278
1316
  }
1279
1317
  }
1280
1318
  catch (e) {
1281
1319
  console.log(` ⚠ Skill install skipped: ${e.message}`);
1282
1320
  }
1283
1321
  }
1322
+ if (db)
1323
+ db.close();
1284
1324
  if (opts.profile === 'full' || (opts.with && opts.with.includes('hooks'))) {
1285
1325
  console.log('Step 4/4: Installing git hooks...');
1286
1326
  try {
1287
- const { writeFileSync, existsSync, mkdirSync, chmodSync } = await import('node:fs');
1327
+ const { writeFileSync, existsSync, mkdirSync, chmodSync, readFileSync } = await import('node:fs');
1288
1328
  const hooksDir = resolve(root, '.git', 'hooks');
1289
1329
  if (!existsSync(hooksDir)) {
1290
1330
  mkdirSync(hooksDir, { recursive: true });
1291
1331
  }
1332
+ const hookPath = resolve(hooksDir, 'pre-commit');
1333
+ // Check for existing non-milens hook (e.g. Husky) before overwriting
1334
+ if (existsSync(hookPath)) {
1335
+ const existing = readFileSync(hookPath, 'utf-8');
1336
+ if (!existing.includes('milens') && !existing.includes('Auto-installed by milens')) {
1337
+ const isHusky = existing.includes('husky') || existing.includes('.husky');
1338
+ if (isHusky) {
1339
+ console.log(' ⚠ Skipped pre-commit hook — Husky hook already present (.husky/). Install manually with "milens hooks install".');
1340
+ }
1341
+ else {
1342
+ console.log(' ⚠ Skipped pre-commit hook — existing hook found. Install manually with "milens hooks install".');
1343
+ }
1344
+ return;
1345
+ }
1346
+ }
1292
1347
  const preCommitContent = `#!/bin/bash
1293
1348
  # Auto-installed by milens init
1294
1349
  echo "Milens: Pre-commit check..."
@@ -1324,6 +1379,52 @@ echo "Milens: Done."
1324
1379
  console.log(` ⚠ Hook install skipped: ${e.message}`);
1325
1380
  }
1326
1381
  }
1382
+ if (opts.target) {
1383
+ console.log('Installing harness adapters...');
1384
+ const targetTokens = opts.target.split(',').map((h) => h.trim()).filter(Boolean);
1385
+ const requested = targetTokens.includes('all')
1386
+ ? Object.keys(HARNESS_ADAPTER_FILES)
1387
+ : targetTokens;
1388
+ const adaptersRoot = join(dirname(__filename), '..', 'adapters');
1389
+ for (const harness of requested) {
1390
+ const files = HARNESS_ADAPTER_FILES[harness];
1391
+ if (!files) {
1392
+ console.log(` ⚠ Unknown harness "${harness}" — skipped. Valid: ${Object.keys(HARNESS_ADAPTER_FILES).join(', ')}, all`);
1393
+ continue;
1394
+ }
1395
+ for (const { src, dest } of files) {
1396
+ const srcPath = join(adaptersRoot, harness, src);
1397
+ const destPath = resolve(root, dest);
1398
+ if (!existsSync(srcPath)) {
1399
+ console.log(` ⚠ ${harness}: template not found (${src}) — run from an npm install of milens`);
1400
+ continue;
1401
+ }
1402
+ if (existsSync(destPath)) {
1403
+ console.log(` - ${dest} already exists — skipped (not overwritten)`);
1404
+ continue;
1405
+ }
1406
+ mkdirSync(dirname(destPath), { recursive: true });
1407
+ cpSync(srcPath, destPath);
1408
+ console.log(` ✓ ${dest} (${harness})`);
1409
+ }
1410
+ }
1411
+ }
1412
+ // Ensure .milens/ is gitignored
1413
+ try {
1414
+ const { existsSync, readFileSync, appendFileSync, writeFileSync } = await import('node:fs');
1415
+ const gitignorePath = resolve(root, '.gitignore');
1416
+ const entry = '\n# milens code intelligence index (local, machine-specific)\n.milens/\n';
1417
+ if (existsSync(gitignorePath)) {
1418
+ const content = readFileSync(gitignorePath, 'utf-8');
1419
+ if (!content.includes('.milens')) {
1420
+ appendFileSync(gitignorePath, entry);
1421
+ }
1422
+ }
1423
+ else {
1424
+ writeFileSync(gitignorePath, entry.trimStart());
1425
+ }
1426
+ }
1427
+ catch { /* best-effort */ }
1327
1428
  console.log(`\n✓ Milens ${opts.profile} profile bootstrapped for ${root}`);
1328
1429
  console.log('Next steps:');
1329
1430
  console.log(' 1. Open project in your AI coding agent (Claude Code, OpenCode, etc.)');
@@ -1334,12 +1435,72 @@ program
1334
1435
  .command('hooks <action>')
1335
1436
  .description('Manage milens hook system')
1336
1437
  .option('-p, --path <path>', 'Repository root path', '.')
1438
+ .option('--repo <path>', 'Repository root path (alias for --path)')
1439
+ .option('--agent <name>', 'Agent name (for session-start/session-end)')
1337
1440
  .option('--hook <hook>', 'Hook name (sessionStart, sessionEnd, preCommit, fileChange, preCompact, postCompact)')
1441
+ .option('--mode <mode>', 'Enforcement mode (warn | strict) for guard-set-mode')
1338
1442
  .action(async (action, opts) => {
1339
- const { HookManager } = await import('./server/hooks.js');
1443
+ const { HookManager, defaultOnSessionStart, defaultOnSessionEnd, defaultOnPreCompact, defaultOnPostCompact } = await import('./server/hooks.js');
1340
1444
  const manager = new HookManager();
1341
- const projectPath = resolve(opts.path);
1445
+ const projectPath = resolve(opts.repo || opts.path);
1342
1446
  switch (action) {
1447
+ case 'session-start': {
1448
+ const agent = opts.agent || 'cli';
1449
+ const rootPath = projectPath;
1450
+ const dbPath = join(rootPath, '.milens', 'milens.db');
1451
+ const sessionId = randomUUID();
1452
+ const ctx = { agent, sessionId, rootPath };
1453
+ try {
1454
+ const output = await defaultOnSessionStart(ctx, dbPath);
1455
+ console.log(output);
1456
+ }
1457
+ catch (e) {
1458
+ console.error(`Session start hook failed: ${e.message || e}`);
1459
+ process.exit(1);
1460
+ }
1461
+ break;
1462
+ }
1463
+ case 'session-end': {
1464
+ const agent = opts.agent || 'cli';
1465
+ const rootPath = projectPath;
1466
+ const dbPath = join(rootPath, '.milens', 'milens.db');
1467
+ const sessionId = randomUUID();
1468
+ const ctx = { agent, sessionId, rootPath };
1469
+ try {
1470
+ const output = await defaultOnSessionEnd(ctx, dbPath);
1471
+ console.log(output);
1472
+ }
1473
+ catch (e) {
1474
+ console.error(`Session end hook failed: ${e.message || e}`);
1475
+ process.exit(1);
1476
+ }
1477
+ break;
1478
+ }
1479
+ case 'pre-compact': {
1480
+ const rootPath = projectPath;
1481
+ const dbPath = join(rootPath, '.milens', 'milens.db');
1482
+ try {
1483
+ const output = await defaultOnPreCompact(rootPath, dbPath);
1484
+ console.log(output);
1485
+ }
1486
+ catch (e) {
1487
+ console.error(`Pre-compact hook failed: ${e.message || e}`);
1488
+ process.exit(1);
1489
+ }
1490
+ break;
1491
+ }
1492
+ case 'post-compact': {
1493
+ const rootPath = projectPath;
1494
+ try {
1495
+ const output = await defaultOnPostCompact(rootPath);
1496
+ console.log(output);
1497
+ }
1498
+ catch (e) {
1499
+ console.error(`Post-compact hook failed: ${e.message || e}`);
1500
+ process.exit(1);
1501
+ }
1502
+ break;
1503
+ }
1343
1504
  case 'enable': {
1344
1505
  if (opts.hook) {
1345
1506
  manager.enableHook(opts.hook, projectPath);
@@ -1410,8 +1571,42 @@ program
1410
1571
  console.log(`Hook profile set to "${profileName}" for ${projectPath}`);
1411
1572
  break;
1412
1573
  }
1574
+ case 'guard-mark-checked': {
1575
+ const chunks = [];
1576
+ for await (const chunk of process.stdin) {
1577
+ chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);
1578
+ }
1579
+ const rawStdin = Buffer.concat(chunks).toString();
1580
+ const { handleMarkChecked } = await import('./server/guard-hook.js');
1581
+ handleMarkChecked(rawStdin, projectPath);
1582
+ break;
1583
+ }
1584
+ case 'guard-check-edit': {
1585
+ const chunks = [];
1586
+ for await (const chunk of process.stdin) {
1587
+ chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);
1588
+ }
1589
+ const rawStdin = Buffer.concat(chunks).toString();
1590
+ const { handleCheckEdit } = await import('./server/guard-hook.js');
1591
+ const result = handleCheckEdit(rawStdin, projectPath);
1592
+ process.stdout.write(result.stdout + '\n');
1593
+ if (result.exitCode !== 0)
1594
+ process.exit(result.exitCode);
1595
+ break;
1596
+ }
1597
+ case 'guard-set-mode': {
1598
+ const mode = opts.mode;
1599
+ if (mode !== 'warn' && mode !== 'strict') {
1600
+ console.error(`Invalid mode "${mode}". Use "warn" or "strict".`);
1601
+ process.exit(1);
1602
+ }
1603
+ const { writeMode } = await import('./server/guard-hook.js');
1604
+ writeMode(projectPath, mode);
1605
+ console.log(`Guard enforcement mode set to "${mode}" for ${projectPath}`);
1606
+ break;
1607
+ }
1413
1608
  default:
1414
- console.log(`Unknown action: ${action}. Use: enable, disable, list, profile`);
1609
+ console.log(`Unknown action: ${action}. Use: enable, disable, list, profile, session-start, session-end, pre-compact, post-compact, guard-mark-checked, guard-check-edit, guard-set-mode`);
1415
1610
  }
1416
1611
  });
1417
1612
  const securityCmd = program