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.
- package/.agents/skills/adapters/SKILL.md +59 -51
- package/.agents/skills/analyzer/SKILL.md +23 -21
- package/.agents/skills/apps/SKILL.md +65 -68
- package/.agents/skills/docs/SKILL.md +10 -25
- package/.agents/skills/milens/SKILL.md +11 -8
- package/.agents/skills/orchestrator/SKILL.md +13 -10
- package/.agents/skills/parser/SKILL.md +15 -12
- package/.agents/skills/root/SKILL.md +30 -29
- package/.agents/skills/scripts/SKILL.md +18 -16
- package/.agents/skills/security/SKILL.md +7 -7
- package/.agents/skills/server/SKILL.md +36 -26
- package/.agents/skills/store/SKILL.md +24 -23
- package/.agents/skills/test/SKILL.md +24 -22
- package/.agents/skills/ui/SKILL.md +6 -1
- package/README.md +12 -3
- package/adapters/README.md +144 -107
- package/adapters/claude-code/.claude/settings.json.hooks-snippet.json +26 -0
- package/adapters/claude-code/.claude-plugin/plugin.json +11 -0
- package/adapters/claude-code/.mcp.json +9 -0
- package/adapters/claude-code/CLAUDE.md +2 -0
- package/adapters/claude-code/hooks/hooks.json +27 -0
- package/adapters/codex/.codex/config.toml +3 -0
- package/adapters/copilot/.vscode/mcp.json +10 -0
- package/adapters/cursor/.cursor/mcp.json +9 -0
- package/adapters/cursor/.cursorrules +69 -9
- package/adapters/gemini/.gemini/settings.json +9 -0
- package/dist/agents-md.d.ts +1 -1
- package/dist/agents-md.js +47 -3
- package/dist/agents-md.js.map +1 -1
- package/dist/analyzer/config.d.ts +0 -1
- package/dist/analyzer/config.js +32 -0
- package/dist/analyzer/config.js.map +1 -1
- package/dist/analyzer/engine.d.ts +0 -1
- package/dist/analyzer/engine.js +67 -41
- package/dist/analyzer/engine.js.map +1 -1
- package/dist/analyzer/enrich.d.ts +0 -1
- package/dist/analyzer/enrich.js +81 -1
- package/dist/analyzer/enrich.js.map +1 -1
- package/dist/analyzer/resolver.d.ts +4 -2
- package/dist/analyzer/resolver.js +283 -36
- package/dist/analyzer/resolver.js.map +1 -1
- package/dist/analyzer/review.d.ts +2 -2
- package/dist/analyzer/review.js +39 -61
- package/dist/analyzer/review.js.map +1 -1
- package/dist/analyzer/risk.d.ts +20 -0
- package/dist/analyzer/risk.js +76 -0
- package/dist/analyzer/risk.js.map +1 -0
- package/dist/analyzer/scanner.d.ts +0 -1
- package/dist/analyzer/scanner.js +5 -4
- package/dist/analyzer/scanner.js.map +1 -1
- package/dist/analyzer/scope-resolver.d.ts +0 -1
- package/dist/analyzer/scope-resolver.js +2 -2
- package/dist/analyzer/scope-resolver.js.map +1 -1
- package/dist/build-info.d.ts +2 -0
- package/dist/build-info.js +4 -0
- package/dist/build-info.js.map +1 -0
- package/dist/cli.d.ts +0 -1
- package/dist/cli.js +225 -30
- package/dist/cli.js.map +1 -1
- package/dist/metrics.d.ts +0 -1
- package/dist/orchestrator/orchestrator.d.ts +1 -2
- package/dist/orchestrator/orchestrator.js +30 -7
- package/dist/orchestrator/orchestrator.js.map +1 -1
- package/dist/orchestrator/reporter.d.ts +0 -1
- package/dist/parser/extract.d.ts +7 -1
- package/dist/parser/extract.js +76 -29
- package/dist/parser/extract.js.map +1 -1
- package/dist/parser/lang-css.d.ts +0 -1
- package/dist/parser/lang-css.js +12 -0
- package/dist/parser/lang-css.js.map +1 -1
- package/dist/parser/lang-go.d.ts +0 -1
- package/dist/parser/lang-html.d.ts +0 -1
- package/dist/parser/lang-java.d.ts +0 -1
- package/dist/parser/lang-js.d.ts +0 -1
- package/dist/parser/lang-js.js +15 -4
- package/dist/parser/lang-js.js.map +1 -1
- package/dist/parser/lang-md.d.ts +0 -1
- package/dist/parser/lang-md.js +1 -0
- package/dist/parser/lang-md.js.map +1 -1
- package/dist/parser/lang-php.d.ts +0 -1
- package/dist/parser/lang-py.d.ts +0 -1
- package/dist/parser/lang-ruby.d.ts +0 -1
- package/dist/parser/lang-rust.d.ts +0 -1
- package/dist/parser/lang-ts.d.ts +0 -1
- package/dist/parser/lang-ts.js +40 -6
- package/dist/parser/lang-ts.js.map +1 -1
- package/dist/parser/lang-vue.d.ts +0 -1
- package/dist/parser/lang-vue.js +0 -21
- package/dist/parser/lang-vue.js.map +1 -1
- package/dist/parser/language-provider.d.ts +0 -1
- package/dist/parser/languages.d.ts +0 -1
- package/dist/parser/loader.d.ts +0 -1
- package/dist/security/deps.d.ts +0 -1
- package/dist/security/rules.d.ts +0 -1
- package/dist/security/rules.js +35 -35
- package/dist/security/rules.js.map +1 -1
- package/dist/server/guard-hook.d.ts +10 -0
- package/dist/server/guard-hook.js +179 -0
- package/dist/server/guard-hook.js.map +1 -0
- package/dist/server/hooks.d.ts +0 -1
- package/dist/server/hooks.js +2 -1
- package/dist/server/hooks.js.map +1 -1
- package/dist/server/mcp-prompts.d.ts +0 -1
- package/dist/server/mcp-prompts.js +509 -523
- package/dist/server/mcp-prompts.js.map +1 -1
- package/dist/server/mcp.d.ts +0 -1
- package/dist/server/mcp.js +324 -681
- package/dist/server/mcp.js.map +1 -1
- package/dist/server/test-plan.d.ts +1 -1
- package/dist/server/test-plan.js +25 -7
- package/dist/server/test-plan.js.map +1 -1
- package/dist/server/tools/deps.d.ts +28 -0
- package/dist/server/tools/deps.js +2 -0
- package/dist/server/tools/deps.js.map +1 -0
- package/dist/server/tools/findings-report.d.ts +3 -0
- package/dist/server/tools/findings-report.js +170 -0
- package/dist/server/tools/findings-report.js.map +1 -0
- package/dist/server/tools/resources.d.ts +3 -0
- package/dist/server/tools/resources.js +103 -0
- package/dist/server/tools/resources.js.map +1 -0
- package/dist/server/tools/security.d.ts +3 -0
- package/dist/server/tools/security.js +232 -0
- package/dist/server/tools/security.js.map +1 -0
- package/dist/server/tools/session.d.ts +3 -0
- package/dist/server/tools/session.js +160 -0
- package/dist/server/tools/session.js.map +1 -0
- package/dist/server/tools/testing.d.ts +3 -0
- package/dist/server/tools/testing.js +247 -0
- package/dist/server/tools/testing.js.map +1 -0
- package/dist/server/watcher.d.ts +0 -1
- package/dist/server/watcher.js +1 -1
- package/dist/server/watcher.js.map +1 -1
- package/dist/skills.d.ts +0 -1
- package/dist/skills.js +23 -11
- package/dist/skills.js.map +1 -1
- package/dist/store/annotations.d.ts +6 -1
- package/dist/store/annotations.js +56 -9
- package/dist/store/annotations.js.map +1 -1
- package/dist/store/confidence.d.ts +0 -1
- package/dist/store/confidence.js +2 -0
- package/dist/store/confidence.js.map +1 -1
- package/dist/store/db.d.ts +22 -1
- package/dist/store/db.js +225 -45
- package/dist/store/db.js.map +1 -1
- package/dist/store/registry.d.ts +1 -1
- package/dist/store/registry.js +17 -6
- package/dist/store/registry.js.map +1 -1
- package/dist/store/schema.sql +2 -1
- package/dist/store/vectors.d.ts +0 -1
- package/dist/types.d.ts +23 -2
- package/dist/ui/progress.d.ts +0 -1
- package/dist/uninstall.d.ts +1 -1
- package/dist/uninstall.js +33 -10
- package/dist/uninstall.js.map +1 -1
- package/dist/utils.d.ts +2 -1
- package/dist/utils.js +37 -2
- package/dist/utils.js.map +1 -1
- package/package.json +11 -8
- package/dist/agents-md.d.ts.map +0 -1
- package/dist/analyzer/config.d.ts.map +0 -1
- package/dist/analyzer/engine.d.ts.map +0 -1
- package/dist/analyzer/enrich.d.ts.map +0 -1
- package/dist/analyzer/resolver.d.ts.map +0 -1
- package/dist/analyzer/review.d.ts.map +0 -1
- package/dist/analyzer/scanner.d.ts.map +0 -1
- package/dist/analyzer/scope-resolver.d.ts.map +0 -1
- package/dist/analyzer/testplan.d.ts +0 -59
- package/dist/analyzer/testplan.d.ts.map +0 -1
- package/dist/analyzer/testplan.js +0 -218
- package/dist/analyzer/testplan.js.map +0 -1
- package/dist/cli.d.ts.map +0 -1
- package/dist/metrics.d.ts.map +0 -1
- package/dist/orchestrator/orchestrator.d.ts.map +0 -1
- package/dist/orchestrator/reporter.d.ts.map +0 -1
- package/dist/parser/extract.d.ts.map +0 -1
- package/dist/parser/lang-css.d.ts.map +0 -1
- package/dist/parser/lang-go.d.ts.map +0 -1
- package/dist/parser/lang-html.d.ts.map +0 -1
- package/dist/parser/lang-java.d.ts.map +0 -1
- package/dist/parser/lang-js.d.ts.map +0 -1
- package/dist/parser/lang-md.d.ts.map +0 -1
- package/dist/parser/lang-php.d.ts.map +0 -1
- package/dist/parser/lang-py.d.ts.map +0 -1
- package/dist/parser/lang-ruby.d.ts.map +0 -1
- package/dist/parser/lang-rust.d.ts.map +0 -1
- package/dist/parser/lang-ts.d.ts.map +0 -1
- package/dist/parser/lang-vue.d.ts.map +0 -1
- package/dist/parser/language-provider.d.ts.map +0 -1
- package/dist/parser/languages.d.ts.map +0 -1
- package/dist/parser/loader.d.ts.map +0 -1
- package/dist/security/deps.d.ts.map +0 -1
- package/dist/security/rules.d.ts.map +0 -1
- package/dist/server/hooks.d.ts.map +0 -1
- package/dist/server/mcp-prompts.d.ts.map +0 -1
- package/dist/server/mcp.d.ts.map +0 -1
- package/dist/server/test-plan.d.ts.map +0 -1
- package/dist/server/watcher.d.ts.map +0 -1
- package/dist/skills.d.ts.map +0 -1
- package/dist/store/annotations.d.ts.map +0 -1
- package/dist/store/confidence.d.ts.map +0 -1
- package/dist/store/db.d.ts.map +0 -1
- package/dist/store/registry.d.ts.map +0 -1
- package/dist/store/vectors.d.ts.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/ui/progress.d.ts.map +0 -1
- package/dist/uninstall.d.ts.map +0 -1
- package/dist/utils.d.ts.map +0 -1
- package/docs/README.md +0 -25
- package/docs/diagram1.svg +0 -1
- package/docs/diagram2.svg +0 -1
- 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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
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(' ⚠
|
|
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
|