@riconext/hermes-repo 0.1.0 → 0.2.5

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/dist/cli.js CHANGED
@@ -1,41 +1,1199 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { readFileSync } from "fs";
5
- import { dirname, join } from "path";
6
- import { fileURLToPath } from "url";
4
+ import { Command } from "commander";
5
+
6
+ // src/capture/claude-code/resolveSession.ts
7
+ import { existsSync, readdirSync, readFileSync, statSync } from "fs";
8
+ import { homedir } from "os";
9
+ import { basename, join, resolve } from "path";
10
+ function encodeClaudeProjectDir(absPath) {
11
+ return resolve(absPath).replace(/\//g, "-");
12
+ }
13
+ function resolveSessionJsonlPath(repoRoot, options = {}) {
14
+ const override = process.env.HERMES_SESSION_JSONL;
15
+ if (override && existsSync(override)) {
16
+ return resolve(override);
17
+ }
18
+ const fromHook = options.transcriptPath;
19
+ if (fromHook && existsSync(fromHook)) {
20
+ return resolve(fromHook);
21
+ }
22
+ const sessionId = process.env.CLAUDE_SESSION_ID ?? process.env.CLAUDE_CODE_SESSION_ID ?? process.env.SESSION_ID;
23
+ const claudeHome = process.env.CLAUDE_CONFIG_DIR ? resolve(process.env.CLAUDE_CONFIG_DIR) : join(homedir(), ".claude");
24
+ const projectsRoot = join(claudeHome, "projects");
25
+ if (!existsSync(projectsRoot)) {
26
+ return null;
27
+ }
28
+ const cwd = resolve(options.cwd ?? repoRoot);
29
+ const preferredProjectDir = encodeClaudeProjectDir(cwd);
30
+ const preferredPath = join(projectsRoot, preferredProjectDir);
31
+ if (existsSync(preferredPath)) {
32
+ const hit = pickNewestJsonl(preferredPath, sessionId);
33
+ if (hit) {
34
+ return hit;
35
+ }
36
+ const legacySessions = join(preferredPath, "sessions");
37
+ if (existsSync(legacySessions)) {
38
+ const legacyHit = pickNewestJsonl(legacySessions, sessionId);
39
+ if (legacyHit) {
40
+ return legacyHit;
41
+ }
42
+ }
43
+ }
44
+ const candidates = [];
45
+ for (const projectDir of readdirSync(projectsRoot, { withFileTypes: true })) {
46
+ if (!projectDir.isDirectory()) continue;
47
+ const projectPath = join(projectsRoot, projectDir.name);
48
+ collectJsonlCandidates(projectPath, sessionId, candidates);
49
+ const legacySessions = join(projectPath, "sessions");
50
+ if (existsSync(legacySessions)) {
51
+ collectJsonlCandidates(legacySessions, sessionId, candidates);
52
+ }
53
+ }
54
+ if (candidates.length === 0) {
55
+ return null;
56
+ }
57
+ candidates.sort((a, b) => b.mtime - a.mtime);
58
+ return candidates[0]?.path ?? null;
59
+ }
60
+ function readHookTranscriptPathSync() {
61
+ if (process.stdin.isTTY) {
62
+ return null;
63
+ }
64
+ try {
65
+ const raw = readFileSync(0, "utf8").trim();
66
+ if (!raw) {
67
+ return null;
68
+ }
69
+ const parsed = JSON.parse(raw);
70
+ const p = parsed.transcript_path;
71
+ if (typeof p === "string" && existsSync(p)) {
72
+ return resolve(p);
73
+ }
74
+ } catch {
75
+ return null;
76
+ }
77
+ return null;
78
+ }
79
+ function pickNewestJsonl(dir, sessionId) {
80
+ const candidates = [];
81
+ collectJsonlCandidates(dir, sessionId, candidates);
82
+ if (candidates.length === 0) {
83
+ return null;
84
+ }
85
+ candidates.sort((a, b) => b.mtime - a.mtime);
86
+ return candidates[0]?.path ?? null;
87
+ }
88
+ function collectJsonlCandidates(dir, sessionId, out) {
89
+ if (!existsSync(dir)) {
90
+ return;
91
+ }
92
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
93
+ if (!entry.isFile() || !entry.name.endsWith(".jsonl")) {
94
+ continue;
95
+ }
96
+ const fullPath = join(dir, entry.name);
97
+ if (sessionId && !entry.name.includes(sessionId) && basename(entry.name, ".jsonl") !== sessionId) {
98
+ continue;
99
+ }
100
+ try {
101
+ const st = statSync(fullPath);
102
+ out.push({ path: fullPath, mtime: st.mtimeMs });
103
+ } catch {
104
+ }
105
+ }
106
+ }
107
+
108
+ // src/config/debugLog.ts
109
+ import { appendFileSync, mkdirSync } from "fs";
110
+ import { dirname } from "path";
111
+
112
+ // src/init/paths.ts
113
+ import { join as join2 } from "path";
114
+ var MEMORY_DIR = ".memory";
115
+ var MEMORY_SUBDIRS = [
116
+ "captures/semantic",
117
+ "captures/episodic",
118
+ "captures/procedural",
119
+ "personal",
120
+ "sessions",
121
+ "refs",
122
+ "topics",
123
+ "skills",
124
+ "team/decisions",
125
+ "team/conflict-resolutions",
126
+ "templates",
127
+ ".archive"
128
+ ];
129
+ var GITKEEP_DIRS = [
130
+ "topics",
131
+ "skills",
132
+ "team/decisions",
133
+ "team/conflict-resolutions"
134
+ ];
135
+ var EXAMPLE_TEMPLATE_FILES = [
136
+ "capture-semantic.example.md",
137
+ "capture-episodic.example.md",
138
+ "capture-procedural.example.md",
139
+ "PROMOTE_PR.md"
140
+ ];
141
+ function memoryPath(root, ...segments) {
142
+ return join2(root, MEMORY_DIR, ...segments);
143
+ }
144
+
145
+ // src/config/debugLog.ts
146
+ var DEBUG_LOG_FILE = "hermes-debug.log";
147
+ var logFilePath = null;
148
+ function configureDebugLogging(repoRoot, enabled) {
149
+ if (!enabled || !repoRoot) {
150
+ logFilePath = null;
151
+ return;
152
+ }
153
+ logFilePath = memoryPath(repoRoot, DEBUG_LOG_FILE);
154
+ }
155
+ function formatLine(phase, message) {
156
+ return `${(/* @__PURE__ */ new Date()).toISOString()} hermes-repo [${phase}] ${message}`;
157
+ }
158
+ function writeToLogFile(line) {
159
+ if (!logFilePath) {
160
+ return;
161
+ }
162
+ mkdirSync(dirname(logFilePath), { recursive: true });
163
+ appendFileSync(logFilePath, `${line}
164
+ `, "utf8");
165
+ }
166
+ function debugLog(enabled, phase, message) {
167
+ if (!enabled) {
168
+ return;
169
+ }
170
+ const line = formatLine(phase, message);
171
+ console.error(line);
172
+ writeToLogFile(line);
173
+ }
174
+ function debugFromContext(ctx, phase, message) {
175
+ debugLog(ctx?.config.debug === true, phase, message);
176
+ }
177
+
178
+ // src/config/readConfig.ts
179
+ import { readFileSync as readFileSync2 } from "fs";
180
+ import { join as join4 } from "path";
181
+
182
+ // src/config/findRepoRoot.ts
183
+ import { existsSync as existsSync2 } from "fs";
184
+ import { dirname as dirname2, join as join3, resolve as resolve2 } from "path";
185
+ var CONFIG_REL = join3(".memory", "config.json");
186
+ function findRepoRoot(startDir) {
187
+ let dir = resolve2(startDir ?? process.cwd());
188
+ while (true) {
189
+ if (existsSync2(join3(dir, CONFIG_REL))) {
190
+ return dir;
191
+ }
192
+ const parent = dirname2(dir);
193
+ if (parent === dir) {
194
+ return null;
195
+ }
196
+ dir = parent;
197
+ }
198
+ }
199
+
200
+ // src/config/readConfig.ts
201
+ function isAssistantId(value) {
202
+ return typeof value === "string";
203
+ }
204
+ function readConfigAtRepo(repoRoot) {
205
+ const configPath = join4(repoRoot, ".memory", "config.json");
206
+ try {
207
+ const raw = JSON.parse(readFileSync2(configPath, "utf8"));
208
+ if (raw.version !== 1 || raw.storage?.backend !== "file") {
209
+ return null;
210
+ }
211
+ const assistants = Array.isArray(raw.assistants) ? raw.assistants.filter(isAssistantId) : [];
212
+ return {
213
+ version: 1,
214
+ storage: { backend: "file" },
215
+ assistants,
216
+ debug: raw.debug === true
217
+ };
218
+ } catch {
219
+ return null;
220
+ }
221
+ }
222
+ function loadRepoContext(cwd) {
223
+ const repoRoot = findRepoRoot(cwd);
224
+ if (!repoRoot) {
225
+ return null;
226
+ }
227
+ const config = readConfigAtRepo(repoRoot);
228
+ if (!config) {
229
+ return null;
230
+ }
231
+ return { repoRoot, config };
232
+ }
233
+
234
+ // src/capture/shouldCapture.ts
235
+ var STRONG_SIGNALS = [
236
+ "\u4FEE\u590D",
237
+ "\u56E0\u4E3A",
238
+ "\u6539\u6210",
239
+ "\u6CE8\u610F",
240
+ "\u7EA6\u5B9A",
241
+ "\u4E0D\u8981",
242
+ "\u5FC5\u987B",
243
+ "\u6700\u4F73\u5B9E\u8DF5",
244
+ "\u6839\u56E0",
245
+ "\u539F\u56E0",
246
+ "fix",
247
+ "because",
248
+ "change to",
249
+ "note",
250
+ "convention",
251
+ "never",
252
+ "always",
253
+ "root cause",
254
+ "pattern",
255
+ "\u67B6\u6784",
256
+ "\u51B3\u7B56"
257
+ ];
258
+ var CORRECTION_RE = /不对|错了|不是这样|不应该|别用|stop|wrong|incorrect|改成|修正/i;
259
+ var SEMANTIC_SIGNAL_RE = /约定|必须|架构|决策|规范|convention|pattern|always|never/i;
260
+ function shouldCapture(session) {
261
+ if (session.messages.length < 3) {
262
+ return false;
263
+ }
264
+ const hasStrongSignal = STRONG_SIGNALS.some(
265
+ (w) => session.text.toLowerCase().includes(w.toLowerCase())
266
+ );
267
+ const hasUserCorrection = session.messages.some(
268
+ (m) => m.role === "user" && CORRECTION_RE.test(m.text)
269
+ );
270
+ const hasComplexTask = session.toolCalls > 5;
271
+ return hasStrongSignal || hasUserCorrection || hasComplexTask;
272
+ }
273
+ function inferCaptureType(session) {
274
+ if (SEMANTIC_SIGNAL_RE.test(session.text)) {
275
+ return "semantic";
276
+ }
277
+ return "episodic";
278
+ }
279
+
280
+ // src/capture/sessionsIndex.ts
281
+ import { readFileSync as readFileSync3, writeFileSync } from "fs";
282
+ import { join as join5 } from "path";
283
+ function readSessionsIndex(repoRoot) {
284
+ const indexPath = memoryPath(repoRoot, "sessions", "index.json");
285
+ try {
286
+ const data = JSON.parse(readFileSync3(indexPath, "utf8"));
287
+ if (data.version === 1 && Array.isArray(data.sessions)) {
288
+ return data;
289
+ }
290
+ } catch {
291
+ }
292
+ return { version: 1, sessions: [] };
293
+ }
294
+ function appendSessionIndex(repoRoot, entry) {
295
+ const index = readSessionsIndex(repoRoot);
296
+ index.sessions.push(entry);
297
+ const indexPath = memoryPath(repoRoot, "sessions", "index.json");
298
+ writeFileSync(indexPath, `${JSON.stringify(index, null, 2)}
299
+ `, "utf8");
300
+ }
301
+ function relativeCapturePath(type, filename) {
302
+ return join5(".memory", "captures", type, filename).replace(/\\/g, "/");
303
+ }
304
+
305
+ // src/capture/writeCapture.ts
306
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync as readdirSync2, writeFileSync as writeFileSync2 } from "fs";
307
+ function todayString() {
308
+ return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
309
+ }
310
+ function nextCaptureFilename(repoRoot, type) {
311
+ const dir = memoryPath(repoRoot, "captures", type);
312
+ mkdirSync2(dir, { recursive: true });
313
+ const date = todayString();
314
+ const prefix = `capture-${date}-`;
315
+ let max = 0;
316
+ if (existsSync3(dir)) {
317
+ for (const name of readdirSync2(dir)) {
318
+ if (name.startsWith(prefix) && name.endsWith(".md")) {
319
+ const num = Number.parseInt(name.slice(prefix.length, -3), 10);
320
+ if (!Number.isNaN(num) && num > max) {
321
+ max = num;
322
+ }
323
+ }
324
+ }
325
+ }
326
+ const seq = String(max + 1).padStart(3, "0");
327
+ return `capture-${date}-${seq}.md`;
328
+ }
329
+ function buildBody(session) {
330
+ const recent = session.messages.slice(-6);
331
+ const context = recent.map((m) => `**${m.role}**: ${m.text.slice(0, 500)}`).join("\n\n");
332
+ return `## \u4E0A\u4E0B\u6587
333
+
334
+ \u81EA\u52A8\u6355\u83B7\u81EA Claude Code \u4F1A\u8BDD \`${session.sessionId}\`\u3002
335
+
336
+ ## \u53D1\u73B0
337
+
338
+ ${context || "\uFF08\u65E0\u63D0\u53D6\u5185\u5BB9\uFF09"}
339
+
340
+ ## \u5F71\u54CD
341
+
342
+ \uFF08\u5F85 consolidate \u6216\u4EBA\u5DE5\u8865\u5145\uFF09
343
+ `;
344
+ }
345
+ function writeCaptureFile(repoRoot, session, type) {
346
+ const filename = nextCaptureFilename(repoRoot, type);
347
+ const absolutePath = memoryPath(repoRoot, "captures", type, filename);
348
+ const date = todayString();
349
+ const content = `---
350
+ type: ${type}
351
+ date: ${date}
352
+ session: ${session.sessionId}
353
+ tags: [auto-capture, claude-code]
354
+ scope: all
355
+ confidence: pending
356
+ ---
357
+
358
+ ${buildBody(session)}
359
+ `;
360
+ writeFileSync2(absolutePath, content, "utf8");
361
+ return { absolutePath, filename };
362
+ }
363
+
364
+ // src/capture/claude-code/parseJsonl.ts
365
+ import { readFileSync as readFileSync4 } from "fs";
366
+ import { basename as basename2 } from "path";
367
+ var FILE_CHANGE_TOOLS = /^(Write|Edit|MultiEdit|NotebookEdit|write|edit)$/i;
368
+ function extractText(record) {
369
+ if (typeof record.content === "string") {
370
+ return record.content;
371
+ }
372
+ const message = record.message;
373
+ if (message && typeof message === "object") {
374
+ const msg = message;
375
+ if (typeof msg.content === "string") {
376
+ return msg.content;
377
+ }
378
+ if (Array.isArray(msg.content)) {
379
+ return msg.content.map((part) => {
380
+ if (part && typeof part === "object" && "text" in part) {
381
+ return String(part.text);
382
+ }
383
+ return "";
384
+ }).join("\n");
385
+ }
386
+ }
387
+ return "";
388
+ }
389
+ function inferRole(record) {
390
+ if (typeof record.role === "string") {
391
+ return record.role;
392
+ }
393
+ if (typeof record.type === "string") {
394
+ const t = record.type.toLowerCase();
395
+ if (t === "user" || t === "human") return "user";
396
+ if (t === "assistant") return "assistant";
397
+ }
398
+ return "unknown";
399
+ }
400
+ function isToolUse(record) {
401
+ const t = String(record.type ?? "").toLowerCase();
402
+ return t === "tool_use" || t === "tool";
403
+ }
404
+ function toolName(record) {
405
+ if (typeof record.name === "string") return record.name;
406
+ const tool = record.tool;
407
+ if (tool && typeof tool === "object" && "name" in tool) {
408
+ return String(tool.name);
409
+ }
410
+ return "";
411
+ }
412
+ function parseJsonlFile(jsonlPath) {
413
+ const sessionId = basename2(jsonlPath, ".jsonl");
414
+ const raw = readFileSync4(jsonlPath, "utf8");
415
+ const messages = [];
416
+ let fileChanges = 0;
417
+ let toolCalls = 0;
418
+ for (const line of raw.split("\n")) {
419
+ const trimmed = line.trim();
420
+ if (!trimmed) continue;
421
+ try {
422
+ const record = JSON.parse(trimmed);
423
+ if (isToolUse(record)) {
424
+ toolCalls += 1;
425
+ const name = toolName(record);
426
+ if (FILE_CHANGE_TOOLS.test(name)) {
427
+ fileChanges += 1;
428
+ }
429
+ continue;
430
+ }
431
+ const role = inferRole(record);
432
+ const text2 = extractText(record);
433
+ if (text2) {
434
+ messages.push({ role, text: text2 });
435
+ }
436
+ } catch {
437
+ }
438
+ }
439
+ const text = messages.map((m) => m.text).join("\n");
440
+ return {
441
+ sessionId,
442
+ messages,
443
+ text,
444
+ fileChanges,
445
+ toolCalls
446
+ };
447
+ }
448
+
449
+ // src/capture/claude-code/run.ts
450
+ function runClaudeCodeCapture(repoRoot, cwd, dryRun, options) {
451
+ const jsonlPath = resolveSessionJsonlPath(repoRoot, {
452
+ cwd,
453
+ transcriptPath: options?.transcriptPath
454
+ });
455
+ if (!jsonlPath) {
456
+ return { written: false, reason: "no session jsonl found" };
457
+ }
458
+ const session = parseJsonlFile(jsonlPath);
459
+ if (!shouldCapture(session)) {
460
+ return {
461
+ written: false,
462
+ reason: `heuristic rejected (messages=${session.messages.length}, toolCalls=${session.toolCalls})`,
463
+ jsonlPath
464
+ };
465
+ }
466
+ const type = inferCaptureType(session);
467
+ if (dryRun) {
468
+ debugLog(
469
+ options?.debug === true,
470
+ "capture",
471
+ `[dry-run] would capture ${type} session=${session.sessionId} from ${jsonlPath}`
472
+ );
473
+ return { written: false, reason: "dry-run", jsonlPath };
474
+ }
475
+ const { filename } = writeCaptureFile(repoRoot, session, type);
476
+ const captureFile = relativeCapturePath(type, filename);
477
+ appendSessionIndex(repoRoot, {
478
+ id: session.sessionId,
479
+ capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
480
+ captureFile,
481
+ assistant: "claude-code"
482
+ });
483
+ return { written: true, capturePath: captureFile };
484
+ }
485
+
486
+ // src/capture/router.ts
487
+ function routeCapture(ctx, options) {
488
+ if (!ctx.config.assistants.includes("claude-code")) {
489
+ return { written: false, reason: "claude-code not in assistants" };
490
+ }
491
+ return runClaudeCodeCapture(ctx.repoRoot, options.cwd, options.dryRun, {
492
+ transcriptPath: options.transcriptPath,
493
+ debug: ctx.config.debug === true
494
+ });
495
+ }
496
+
497
+ // src/capture/runCapture.ts
498
+ function logCaptureResult(debug, result) {
499
+ if (!debug) {
500
+ return;
501
+ }
502
+ if (result.written && result.capturePath) {
503
+ debugLog(true, "capture", `ok: ${result.capturePath}`);
504
+ return;
505
+ }
506
+ const parts = [result.reason ?? "unknown"];
507
+ if (result.jsonlPath) {
508
+ parts.push(`jsonl=${result.jsonlPath}`);
509
+ }
510
+ debugLog(true, "capture", `skip: ${parts.join(", ")}`);
511
+ }
512
+ function runCapture(options = {}) {
513
+ const ctx = loadRepoContext(options.cwd);
514
+ if (!ctx) {
515
+ return { written: false, reason: "not initialized" };
516
+ }
517
+ const result = routeCapture(ctx, {
518
+ cwd: options.cwd,
519
+ dryRun: options.dryRun,
520
+ transcriptPath: options.transcriptPath
521
+ });
522
+ logCaptureResult(ctx.config.debug, result);
523
+ return result;
524
+ }
525
+
526
+ // src/hookExit.ts
527
+ function finalizeHookCommand(fn, strict, debug) {
528
+ void (async () => {
529
+ try {
530
+ await fn();
531
+ process.exit(0);
532
+ } catch (error) {
533
+ debugLog(debug === true, "hook", error instanceof Error ? error.message : String(error));
534
+ process.exit(strict ? 1 : 0);
535
+ }
536
+ })();
537
+ }
538
+
539
+ // src/commands/capture.ts
540
+ function runCaptureCommand(opts) {
541
+ const ctx = loadRepoContext(opts.cwd);
542
+ const debug = ctx?.config.debug === true;
543
+ configureDebugLogging(ctx?.repoRoot ?? null, debug);
544
+ const transcriptPath = readHookTranscriptPathSync();
545
+ finalizeHookCommand(() => {
546
+ runCapture({
547
+ cwd: opts.cwd,
548
+ dryRun: opts.dryRun,
549
+ strict: opts.strict,
550
+ transcriptPath: transcriptPath ?? void 0
551
+ });
552
+ }, opts.strict, debug);
553
+ }
554
+
555
+ // src/inject/runInject.ts
556
+ import { existsSync as existsSync4, readFileSync as readFileSync5 } from "fs";
557
+
558
+ // src/inject/constants.ts
559
+ var INJECT_MAX_CHARS = 2200;
560
+
561
+ // src/inject/runInject.ts
562
+ function runInject(cwd) {
563
+ const ctx = loadRepoContext(cwd);
564
+ if (!ctx) {
565
+ return { injected: false, chars: 0 };
566
+ }
567
+ const memoryFile = memoryPathOnDisk(ctx.repoRoot);
568
+ if (!existsSync4(memoryFile)) {
569
+ debugFromContext(ctx, "inject", "skip: MEMORY.md missing");
570
+ return { injected: false, chars: 0 };
571
+ }
572
+ const contentRaw = readFileSync5(memoryFile, "utf8");
573
+ if (!contentRaw.trim()) {
574
+ debugFromContext(ctx, "inject", "skip: MEMORY.md empty");
575
+ return { injected: false, chars: 0 };
576
+ }
577
+ let content = contentRaw;
578
+ if (content.length > INJECT_MAX_CHARS) {
579
+ content = `${content.slice(0, INJECT_MAX_CHARS)}
580
+
581
+ ...(truncated)`;
582
+ }
583
+ process.stdout.write(content);
584
+ if (!content.endsWith("\n")) {
585
+ process.stdout.write("\n");
586
+ }
587
+ debugFromContext(ctx, "inject", `ok: injected ${content.length} chars`);
588
+ return { injected: true, chars: content.length };
589
+ }
590
+ function memoryPathOnDisk(repoRoot) {
591
+ return memoryPath(repoRoot, "MEMORY.md");
592
+ }
593
+
594
+ // src/commands/inject.ts
595
+ function runInjectCommand(opts) {
596
+ const ctx = loadRepoContext(opts.cwd);
597
+ const debug = ctx?.config.debug === true;
598
+ configureDebugLogging(ctx?.repoRoot ?? null, debug);
599
+ finalizeHookCommand(() => {
600
+ runInject(opts.cwd);
601
+ }, opts.strict, debug);
602
+ }
603
+
604
+ // src/init/runInit.ts
605
+ import { spawnSync } from "child_process";
606
+ import { resolve as resolve4 } from "path";
607
+
608
+ // src/init/assistants/claude-code.ts
609
+ import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
610
+ import { join as join9 } from "path";
611
+
612
+ // src/init/mergeClaudeSettings.ts
613
+ import { existsSync as existsSync6, readFileSync as readFileSync8 } from "fs";
614
+ import { join as join8 } from "path";
615
+
616
+ // src/init/templateDir.ts
617
+ import { existsSync as existsSync5, readFileSync as readFileSync7 } from "fs";
618
+ import { dirname as dirname4, join as join7 } from "path";
619
+ import { fileURLToPath as fileURLToPath2 } from "url";
7
620
 
8
621
  // src/index.ts
622
+ import { readFileSync as readFileSync6 } from "fs";
623
+ import { dirname as dirname3, join as join6 } from "path";
624
+ import { fileURLToPath } from "url";
9
625
  var PACKAGE_NAME = "@riconext/hermes-repo";
10
-
11
- // src/cli.ts
626
+ var __dirname = dirname3(fileURLToPath(import.meta.url));
12
627
  function readPkgVersion() {
13
- const root = join(dirname(fileURLToPath(import.meta.url)), "..", "package.json");
14
- const raw = readFileSync(root, "utf8");
15
- const pkg = JSON.parse(raw);
16
- return typeof pkg.version === "string" ? pkg.version : "0.0.0";
17
- }
18
- var version = readPkgVersion();
19
- var argv = process.argv.slice(2);
20
- var cmd = argv[0];
21
- if (cmd === "-v" || cmd === "--version") {
22
- process.stdout.write(`${version}
23
- `);
24
- process.exit(0);
628
+ const pkgPath = join6(__dirname, "..", "package.json");
629
+ const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
630
+ return pkg.version;
631
+ }
632
+
633
+ // src/init/templateDir.ts
634
+ function resolveTemplateDir() {
635
+ const here = dirname4(fileURLToPath2(import.meta.url));
636
+ const candidates = [
637
+ join7(here, "templates"),
638
+ join7(here, "..", "..", "templates")
639
+ ];
640
+ for (const dir of candidates) {
641
+ if (existsSync5(dir)) {
642
+ return dir;
643
+ }
644
+ }
645
+ return join7(here, "templates");
646
+ }
647
+ var templateDir = resolveTemplateDir();
648
+ function resolveTemplatePath(name) {
649
+ return join7(templateDir, name);
650
+ }
651
+ function readTemplate(name) {
652
+ return readFileSync7(resolveTemplatePath(name), "utf8");
653
+ }
654
+ function renderTemplate(name) {
655
+ const raw = readTemplate(name);
656
+ return raw.replaceAll("__PACKAGE_NAME__", PACKAGE_NAME);
25
657
  }
26
- if (cmd === "-h" || cmd === "--help" || cmd === void 0) {
27
- process.stdout.write(`${PACKAGE_NAME} v${version}
28
658
 
29
- \u7528\u6CD5:
30
- hermes-repo \u663E\u793A\u672C\u5E2E\u52A9
31
- hermes-repo --version \u663E\u793A\u7248\u672C
659
+ // src/init/mergeClaudeSettings.ts
660
+ var CLAUDE_SETTINGS_LOCAL_REL = ".claude/settings.local.json";
661
+ function claudeSettingsLocalPath(repoRoot) {
662
+ return join8(repoRoot, ".claude", "settings.local.json");
663
+ }
664
+ function mergeClaudeLocalSettings(repoRoot) {
665
+ const settingsPath = claudeSettingsLocalPath(repoRoot);
666
+ const existed = existsSync6(settingsPath);
667
+ const templateParsed = JSON.parse(renderTemplate("hooks.json.tpl"));
668
+ let existing = {};
669
+ if (existed) {
670
+ try {
671
+ existing = JSON.parse(readFileSync8(settingsPath, "utf8"));
672
+ } catch {
673
+ existing = {};
674
+ }
675
+ }
676
+ const prevHooks = existing.hooks && typeof existing.hooks === "object" && !Array.isArray(existing.hooks) ? existing.hooks : {};
677
+ const merged = {
678
+ ...existing,
679
+ hooks: {
680
+ ...prevHooks,
681
+ Stop: templateParsed.hooks.Stop,
682
+ SessionStart: templateParsed.hooks.SessionStart
683
+ }
684
+ };
685
+ return {
686
+ content: `${JSON.stringify(merged, null, 2)}
687
+ `,
688
+ action: existed ? "overwritten" : "created"
689
+ };
690
+ }
32
691
 
33
- \u540E\u7EED\u5C06\u63D0\u4F9B init / validate / doctor \u7B49\u5B50\u547D\u4EE4\uFF08\u89C1\u4ED3\u5E93 docs\uFF09\u3002
34
- `);
35
- process.exit(cmd === void 0 ? 0 : 0);
692
+ // src/init/assistants/claude-code.ts
693
+ var claudeCodeAdapter = {
694
+ id: "claude-code",
695
+ label: "Claude Code\uFF08Stop / SessionStart hooks\uFF09",
696
+ available: true,
697
+ scaffoldPaths: [CLAUDE_SETTINGS_LOCAL_REL],
698
+ write(ctx) {
699
+ mkdirSync3(join9(ctx.repoRoot, ".claude"), { recursive: true });
700
+ const { content, action } = mergeClaudeLocalSettings(ctx.repoRoot);
701
+ writeFileSync3(claudeSettingsLocalPath(ctx.repoRoot), content, "utf8");
702
+ ctx.report.files.push({ path: CLAUDE_SETTINGS_LOCAL_REL, action });
703
+ }
704
+ };
705
+
706
+ // src/init/assistants/cursor.ts
707
+ var cursorAdapter = {
708
+ id: "cursor",
709
+ label: "Cursor",
710
+ available: false,
711
+ scaffoldPaths: [],
712
+ write(_ctx) {
713
+ }
714
+ };
715
+
716
+ // src/init/assistants/registry.ts
717
+ var DEFAULT_ASSISTANT_IDS = ["claude-code"];
718
+ var ALL_ADAPTERS = [claudeCodeAdapter, cursorAdapter];
719
+ var ADAPTER_BY_ID = new Map(
720
+ ALL_ADAPTERS.map((a) => [a.id, a])
721
+ );
722
+ function getAdapter(id) {
723
+ const adapter = ADAPTER_BY_ID.get(id);
724
+ if (!adapter) {
725
+ throw new Error(`Unknown assistant id: ${id}`);
726
+ }
727
+ return adapter;
728
+ }
729
+ function listAvailable() {
730
+ return ALL_ADAPTERS.filter((a) => a.available);
731
+ }
732
+ function isKnownAssistantId(id) {
733
+ return ADAPTER_BY_ID.has(id);
734
+ }
735
+ function parseToolsArg(tools) {
736
+ const ids = tools.split(",").map((s) => s.trim()).filter(Boolean);
737
+ if (ids.length === 0) {
738
+ throw new Error("init --tools requires at least one assistant id");
739
+ }
740
+ const result = [];
741
+ for (const id of ids) {
742
+ if (!isKnownAssistantId(id)) {
743
+ throw new Error(`Unknown assistant id: ${id}`);
744
+ }
745
+ const adapter = getAdapter(id);
746
+ if (!adapter.available) {
747
+ throw new Error(`Assistant not available yet: ${id}`);
748
+ }
749
+ result.push(id);
750
+ }
751
+ return result;
752
+ }
753
+ function validateAssistantSelection(ids) {
754
+ if (ids.length === 0) {
755
+ throw new Error("At least one assistant must be selected");
756
+ }
757
+ for (const id of ids) {
758
+ const adapter = getAdapter(id);
759
+ if (!adapter.available) {
760
+ throw new Error(`Assistant not available yet: ${id}`);
761
+ }
762
+ }
763
+ }
764
+
765
+ // src/init/ensureDirs.ts
766
+ import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
767
+ import { join as join10 } from "path";
768
+ function ensureMemoryTree(repoRoot) {
769
+ const memoryRoot = join10(repoRoot, MEMORY_DIR);
770
+ mkdirSync4(memoryRoot, { recursive: true });
771
+ for (const sub of MEMORY_SUBDIRS) {
772
+ mkdirSync4(join10(memoryRoot, sub), { recursive: true });
773
+ }
774
+ mkdirSync4(join10(repoRoot, ".claude"), { recursive: true });
775
+ for (const sub of GITKEEP_DIRS) {
776
+ const keepPath = join10(memoryRoot, sub, ".gitkeep");
777
+ writeFileSync4(keepPath, "", { flag: "a" });
778
+ }
36
779
  }
37
- process.stderr.write(`\u672A\u77E5\u547D\u4EE4: ${cmd}
38
- \u8BF7\u4F7F\u7528 hermes-repo --help
780
+
781
+ // src/init/mergeAssistants.ts
782
+ import { existsSync as existsSync7, readFileSync as readFileSync9 } from "fs";
783
+ function readExistingAssistants(repoRoot) {
784
+ const configPath = memoryPath(repoRoot, "config.json");
785
+ if (!existsSync7(configPath)) {
786
+ return [];
787
+ }
788
+ try {
789
+ const config = JSON.parse(readFileSync9(configPath, "utf8"));
790
+ if (!Array.isArray(config.assistants)) {
791
+ return [];
792
+ }
793
+ return config.assistants.filter((id) => typeof id === "string");
794
+ } catch {
795
+ return [];
796
+ }
797
+ }
798
+ function mergeAssistants(repoRoot, selected) {
799
+ const existing = readExistingAssistants(repoRoot);
800
+ return [.../* @__PURE__ */ new Set([...existing, ...selected])];
801
+ }
802
+
803
+ // src/init/mergeGitignore.ts
804
+ import { existsSync as existsSync8, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "fs";
805
+ import { join as join11 } from "path";
806
+ var START_MARKER = "# >>> hermes-repo memory (do not edit this block manually)";
807
+ var END_MARKER = "# <<< hermes-repo memory";
808
+ function mergeHermesGitignore(repoRoot) {
809
+ const block = readTemplate("gitignore-block.txt").trimEnd() + "\n";
810
+ const gitignorePath = join11(repoRoot, ".gitignore");
811
+ const contentBefore = existsSync8(gitignorePath) ? readFileSync10(gitignorePath, "utf8") : "";
812
+ const warnBroadMemoryIgnore = contentBefore.length > 0 && !contentBefore.includes(START_MARKER) && /(^|\n)\.memory\/\s*$/m.test(contentBefore);
813
+ if (!existsSync8(gitignorePath)) {
814
+ writeFileSync5(gitignorePath, `${block}
815
+ `, "utf8");
816
+ return { action: "created", warnBroadMemoryIgnore: false };
817
+ }
818
+ const startIdx = contentBefore.indexOf(START_MARKER);
819
+ const endIdx = contentBefore.indexOf(END_MARKER);
820
+ if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
821
+ const before = contentBefore.slice(0, startIdx);
822
+ const after = contentBefore.slice(endIdx + END_MARKER.length);
823
+ const next = `${before}${block}${after}`.replace(/\n{3,}/g, "\n\n");
824
+ writeFileSync5(gitignorePath, next.endsWith("\n") ? next : `${next}
825
+ `, "utf8");
826
+ return { action: "replaced", warnBroadMemoryIgnore };
827
+ }
828
+ if (startIdx !== -1) {
829
+ const before = contentBefore.slice(0, startIdx);
830
+ const next = `${before}${block}
831
+ `;
832
+ writeFileSync5(gitignorePath, next, "utf8");
833
+ return { action: "updated", warnBroadMemoryIgnore };
834
+ }
835
+ const separator = contentBefore.endsWith("\n") || contentBefore.length === 0 ? "\n" : "\n\n";
836
+ writeFileSync5(gitignorePath, `${contentBefore}${separator}${block}
837
+ `, "utf8");
838
+ return { action: "appended", warnBroadMemoryIgnore };
839
+ }
840
+
841
+ // src/init/prompts.ts
842
+ import { checkbox, confirm, input } from "@inquirer/prompts";
843
+ import { existsSync as existsSync9, readFileSync as readFileSync11 } from "fs";
844
+ import { resolve as resolve3 } from "path";
845
+ function isInitialized(targetDir) {
846
+ const configPath = memoryPath(targetDir, "config.json");
847
+ if (!existsSync9(configPath)) {
848
+ return false;
849
+ }
850
+ try {
851
+ const config = JSON.parse(readFileSync11(configPath, "utf8"));
852
+ return config.version === 1;
853
+ } catch {
854
+ return false;
855
+ }
856
+ }
857
+ async function gatherInitOptions(opts) {
858
+ const defaultDir = resolve3(opts.cwd ?? process.cwd());
859
+ const targetInput = await input({
860
+ message: "\u76EE\u6807\u76EE\u5F55\uFF08Git \u4ED3\u5E93\u6839\uFF09",
861
+ default: defaultDir
862
+ });
863
+ const targetDir = resolve3(targetInput);
864
+ const assistants = await checkbox({
865
+ message: "\u9009\u62E9\u8981\u63A5\u5165\u7684\u7F16\u7A0B\u52A9\u624B\uFF08\u53EF\u591A\u9009\uFF09",
866
+ choices: listAvailable().map((a) => ({
867
+ name: a.label,
868
+ value: a.id,
869
+ checked: a.id === "claude-code"
870
+ })),
871
+ validate: (value) => value.length > 0 || "\u8BF7\u81F3\u5C11\u9009\u62E9\u4E00\u9879"
872
+ });
873
+ const includeExampleTemplates = await confirm({
874
+ message: "\u662F\u5426\u5199\u5165 capture \u793A\u4F8B\u6A21\u677F\u5230 .memory/templates/\uFF1F",
875
+ default: true
876
+ });
877
+ if (isInitialized(targetDir)) {
878
+ const continueInit = await confirm({
879
+ message: "\u68C0\u6D4B\u5230\u5DF2\u6709 .memory/config.json\uFF0C\u662F\u5426\u4EC5\u8865\u5168\u7F3A\u5931\u9879\uFF1F",
880
+ default: true
881
+ });
882
+ if (!continueInit) {
883
+ return {
884
+ targetDir,
885
+ force: false,
886
+ includeExampleTemplates,
887
+ assistants,
888
+ cancelled: true
889
+ };
890
+ }
891
+ }
892
+ return {
893
+ targetDir,
894
+ force: Boolean(opts.force),
895
+ includeExampleTemplates,
896
+ assistants,
897
+ cancelled: false
898
+ };
899
+ }
900
+
901
+ // src/init/writeScaffoldFile.ts
902
+ import { copyFileSync, writeFileSync as writeFileSync7 } from "fs";
903
+ import { join as join12 } from "path";
904
+
905
+ // src/init/mergeConfig.ts
906
+ import { existsSync as existsSync10, readFileSync as readFileSync12 } from "fs";
907
+ function mergeConfigForInit(repoRoot, assistants) {
908
+ const configPath = memoryPath(repoRoot, "config.json");
909
+ const existed = existsSync10(configPath);
910
+ let existing = {};
911
+ if (existed) {
912
+ try {
913
+ existing = JSON.parse(readFileSync12(configPath, "utf8"));
914
+ } catch {
915
+ existing = {};
916
+ }
917
+ }
918
+ const prevStorage = existing.storage && typeof existing.storage === "object" && !Array.isArray(existing.storage) ? existing.storage : {};
919
+ const merged = {
920
+ ...existing,
921
+ version: 1,
922
+ storage: {
923
+ ...prevStorage,
924
+ backend: "file"
925
+ },
926
+ assistants,
927
+ debug: existing.debug === true
928
+ };
929
+ return {
930
+ content: `${JSON.stringify(merged, null, 2)}
931
+ `,
932
+ action: existed ? "overwritten" : "created"
933
+ };
934
+ }
935
+
936
+ // src/init/scaffoldWrite.ts
937
+ import { existsSync as existsSync11, writeFileSync as writeFileSync6 } from "fs";
938
+ function shouldWriteFile(absolutePath, force) {
939
+ if (!existsSync11(absolutePath)) {
940
+ return { write: true, action: "created" };
941
+ }
942
+ if (force) {
943
+ return { write: true, action: "overwritten" };
944
+ }
945
+ return { write: false, action: "skipped" };
946
+ }
947
+ function writeIfAllowed(report, absolutePath, relativePath, content, force) {
948
+ const { write, action } = shouldWriteFile(absolutePath, force);
949
+ if (!write) {
950
+ report.files.push({ path: relativePath, action });
951
+ return;
952
+ }
953
+ writeFileSync6(absolutePath, content, "utf8");
954
+ report.files.push({ path: relativePath, action });
955
+ }
956
+
957
+ // src/init/writeScaffoldFile.ts
958
+ function writeConfigJson(report, repoRoot, assistants) {
959
+ const { content, action } = mergeConfigForInit(repoRoot, assistants);
960
+ const absolutePath = memoryPath(repoRoot, "config.json");
961
+ writeFileSync7(absolutePath, content, "utf8");
962
+ report.files.push({ path: ".memory/config.json", action });
963
+ }
964
+ function copyTemplateIfAllowed(report, templateName, destAbsolute, relativePath, force) {
965
+ const { write, action } = shouldWriteFile(destAbsolute, force);
966
+ if (!write) {
967
+ report.files.push({ path: relativePath, action });
968
+ return;
969
+ }
970
+ copyFileSync(resolveTemplatePath(templateName), destAbsolute);
971
+ report.files.push({ path: relativePath, action });
972
+ }
973
+ function writeScaffoldFiles(repoRoot, opts, report) {
974
+ const { force, includeExampleTemplates, assistants } = opts;
975
+ writeConfigJson(report, repoRoot, assistants);
976
+ writeIfAllowed(
977
+ report,
978
+ memoryPath(repoRoot, "MEMORY.md"),
979
+ ".memory/MEMORY.md",
980
+ renderTemplate("MEMORY.md.tpl"),
981
+ force
982
+ );
983
+ writeIfAllowed(
984
+ report,
985
+ memoryPath(repoRoot, "sessions", "index.json"),
986
+ ".memory/sessions/index.json",
987
+ `${JSON.stringify({ version: 1, sessions: [] }, null, 2)}
988
+ `,
989
+ force
990
+ );
991
+ writeIfAllowed(
992
+ report,
993
+ memoryPath(repoRoot, "team", "steward-log.md"),
994
+ ".memory/team/steward-log.md",
995
+ renderTemplate("steward-log.md.tpl"),
996
+ force
997
+ );
998
+ writeIfAllowed(
999
+ report,
1000
+ join12(repoRoot, "AGENTS.md"),
1001
+ "AGENTS.md",
1002
+ renderTemplate("AGENTS.md.tpl"),
1003
+ force
1004
+ );
1005
+ for (const id of assistants) {
1006
+ getAdapter(id).write({ repoRoot, force, report });
1007
+ }
1008
+ if (includeExampleTemplates) {
1009
+ for (const name of EXAMPLE_TEMPLATE_FILES) {
1010
+ const dest = memoryPath(repoRoot, "templates", name);
1011
+ copyTemplateIfAllowed(
1012
+ report,
1013
+ name,
1014
+ dest,
1015
+ `.memory/templates/${name}`,
1016
+ force
1017
+ );
1018
+ }
1019
+ }
1020
+ }
1021
+
1022
+ // src/init/runInit.ts
1023
+ function resolveTargetDir(cwd) {
1024
+ const targetDir = resolve4(cwd ?? process.cwd());
1025
+ const gitCheck = spawnSync("git", ["rev-parse", "--is-inside-work-tree"], {
1026
+ cwd: targetDir,
1027
+ encoding: "utf8"
1028
+ });
1029
+ if (gitCheck.status !== 0) {
1030
+ console.warn(
1031
+ "warn: \u5F53\u524D\u76EE\u5F55\u53EF\u80FD\u4E0D\u662F Git \u4ED3\u5E93\uFF0Cinit \u4ECD\u4F1A\u7EE7\u7EED\uFF08\u5EFA\u8BAE\u5728\u6709 git \u7684\u9879\u76EE\u6839\u76EE\u5F55\u6267\u884C\uFF09"
1032
+ );
1033
+ }
1034
+ return targetDir;
1035
+ }
1036
+ function resolveSelectedAssistants(opts) {
1037
+ if (opts.assistants) {
1038
+ validateAssistantSelection(opts.assistants);
1039
+ return opts.assistants;
1040
+ }
1041
+ if (opts.tools) {
1042
+ if (!opts.yes) {
1043
+ console.error("init --tools requires -y in non-interactive mode");
1044
+ process.exit(1);
1045
+ }
1046
+ return parseToolsArg(opts.tools);
1047
+ }
1048
+ if (opts.yes) {
1049
+ return [...DEFAULT_ASSISTANT_IDS];
1050
+ }
1051
+ return [];
1052
+ }
1053
+ function printInitReport(report) {
1054
+ const created = report.files.filter((f) => f.action === "created");
1055
+ const skipped = report.files.filter((f) => f.action === "skipped");
1056
+ const overwritten = report.files.filter((f) => f.action === "overwritten");
1057
+ console.log(`
1058
+ hermes-repo init \u5B8C\u6210 \u2192 ${report.targetDir}
39
1059
  `);
40
- process.exit(1);
1060
+ console.log(`\u5DF2\u542F\u7528\u52A9\u624B: ${report.assistants.join(", ")}
1061
+ `);
1062
+ if (created.length > 0) {
1063
+ console.log(`\u5DF2\u521B\u5EFA (${created.length}):`);
1064
+ for (const f of created) {
1065
+ console.log(` + ${f.path}`);
1066
+ }
1067
+ }
1068
+ if (overwritten.length > 0) {
1069
+ console.log(`\u5DF2\u8986\u76D6 (${overwritten.length}):`);
1070
+ for (const f of overwritten) {
1071
+ console.log(` ~ ${f.path}`);
1072
+ }
1073
+ }
1074
+ if (skipped.length > 0) {
1075
+ console.log(`\u5DF2\u8DF3\u8FC7 (${skipped.length}):`);
1076
+ for (const f of skipped) {
1077
+ console.log(` - ${f.path}`);
1078
+ }
1079
+ }
1080
+ if (report.gitignoreAction) {
1081
+ console.log(`
1082
+ .gitignore: ${report.gitignoreAction} hermes-repo \u6807\u8BB0\u5757`);
1083
+ }
1084
+ for (const warning of report.warnings) {
1085
+ console.warn(`warn: ${warning}`);
1086
+ }
1087
+ console.log("");
1088
+ }
1089
+ async function runInit(opts) {
1090
+ if (!process.stdin.isTTY && !opts.yes) {
1091
+ console.error("init requires -y in non-interactive environments");
1092
+ process.exit(1);
1093
+ }
1094
+ if (opts.tools && !opts.yes) {
1095
+ console.error("init --tools requires -y");
1096
+ process.exit(1);
1097
+ }
1098
+ let resolved;
1099
+ if (opts.yes) {
1100
+ const targetDir = resolveTargetDir(opts.cwd);
1101
+ const selected = resolveSelectedAssistants(opts);
1102
+ resolved = {
1103
+ targetDir,
1104
+ force: Boolean(opts.force),
1105
+ includeExampleTemplates: opts.includeExampleTemplates ?? true,
1106
+ assistants: mergeAssistants(targetDir, selected),
1107
+ cancelled: false
1108
+ };
1109
+ } else {
1110
+ const gathered = await gatherInitOptions(opts);
1111
+ if (gathered.cancelled) {
1112
+ console.log("init \u5DF2\u53D6\u6D88");
1113
+ process.exit(0);
1114
+ }
1115
+ resolved = {
1116
+ ...gathered,
1117
+ assistants: mergeAssistants(gathered.targetDir, gathered.assistants)
1118
+ };
1119
+ }
1120
+ const report = {
1121
+ targetDir: resolved.targetDir,
1122
+ assistants: resolved.assistants,
1123
+ files: [],
1124
+ warnings: []
1125
+ };
1126
+ ensureMemoryTree(resolved.targetDir);
1127
+ writeScaffoldFiles(resolved.targetDir, resolved, report);
1128
+ const gitignore = mergeHermesGitignore(resolved.targetDir);
1129
+ report.gitignoreAction = gitignore.action;
1130
+ if (gitignore.warnBroadMemoryIgnore) {
1131
+ report.warnings.push(
1132
+ ".gitignore \u4E2D\u5B58\u5728\u5BF9\u6574\u4E2A .memory/ \u7684\u5FFD\u7565\u89C4\u5219\uFF0C\u53EF\u80FD\u4E0E\u56E2\u961F\u5C42\u653E\u884C\u51B2\u7A81\uFF0C\u8BF7\u624B\u52A8\u68C0\u67E5"
1133
+ );
1134
+ }
1135
+ printInitReport(report);
1136
+ return report;
1137
+ }
1138
+
1139
+ // src/commands/init.ts
1140
+ async function runInitCommand(opts) {
1141
+ try {
1142
+ await runInit(opts);
1143
+ } catch (error) {
1144
+ console.error(error instanceof Error ? error.message : String(error));
1145
+ process.exit(1);
1146
+ }
1147
+ }
1148
+
1149
+ // src/cli.ts
1150
+ var MIN_NODE_MAJOR = 20;
1151
+ function assertNodeVersion() {
1152
+ const major = Number.parseInt(process.versions.node.split(".")[0] ?? "0", 10);
1153
+ if (major < MIN_NODE_MAJOR) {
1154
+ console.error(
1155
+ `hermes-repo requires Node.js >= ${MIN_NODE_MAJOR}. Current: ${process.versions.node}`
1156
+ );
1157
+ process.exit(1);
1158
+ }
1159
+ }
1160
+ function main() {
1161
+ assertNodeVersion();
1162
+ const program = new Command();
1163
+ program.name("hermes-repo").description(
1164
+ "\u8DE8\u7F16\u7A0B\u52A9\u624B\u7684\u9879\u76EE\u7EA7\u8BB0\u5FC6\u7CFB\u7EDF\uFF1A\u5728 Git \u4ED3\u5E93\u4E2D\u6C89\u6DC0\u7EA6\u5B9A\u3001\u8E29\u5751\u4E0E\u53EF\u590D\u7528\u6D41\u7A0B"
1165
+ ).version(readPkgVersion(), "-V, --version", "\u663E\u793A\u7248\u672C\u53F7");
1166
+ program.command("init").description("\u5728\u5F53\u524D Git \u4ED3\u5E93\u521D\u59CB\u5316 .memory/ \u8BB0\u5FC6\u811A\u624B\u67B6").option("-y, --yes", "\u975E\u4EA4\u4E92\u6A21\u5F0F\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u9009\u9879").option("-f, --force", "\u8986\u76D6\u5DF2\u5B58\u5728\u7684\u811A\u624B\u67B6\u6587\u4EF6\uFF08\u4E0D\u5220\u9664 captures \u7B49\u5185\u5BB9\uFF09").option("-C, --cwd <dir>", "\u76EE\u6807\u76EE\u5F55\uFF0C\u9ED8\u8BA4 process.cwd()").option(
1167
+ "--tools <ids>",
1168
+ "\u9017\u53F7\u5206\u9694\u7684\u52A9\u624B id\uFF0C\u5982 claude-code\uFF08\u987B\u4E0E -y \u5408\u7528\uFF09"
1169
+ ).action(
1170
+ (options) => {
1171
+ void runInitCommand({
1172
+ yes: options.yes,
1173
+ force: options.force,
1174
+ cwd: options.cwd,
1175
+ tools: options.tools
1176
+ });
1177
+ }
1178
+ );
1179
+ program.command("capture").description("Stop hook\uFF1A\u6355\u83B7\u5F53\u524D Claude Code \u4F1A\u8BDD\u5230 .memory/captures/").option("-C, --cwd <dir>", "\u76EE\u6807\u4ED3\u5E93\u6839\u76EE\u5F55").option("--dry-run", "\u4EC5\u9884\u89C8\uFF0C\u4E0D\u5199\u5165\u6587\u4EF6").option("--strict", "\u5931\u8D25\u65F6 exit 1\uFF08hook \u9ED8\u8BA4 exit 0\uFF09").action((options) => {
1180
+ runCaptureCommand({
1181
+ cwd: options.cwd,
1182
+ dryRun: options.dryRun,
1183
+ strict: options.strict
1184
+ });
1185
+ });
1186
+ program.command("inject").description("SessionStart hook\uFF1A\u5C06 MEMORY.md \u6458\u8981\u8F93\u51FA\u5230 stdout").option("-C, --cwd <dir>", "\u76EE\u6807\u4ED3\u5E93\u6839\u76EE\u5F55").option("--strict", "\u5931\u8D25\u65F6 exit 1\uFF08hook \u9ED8\u8BA4 exit 0\uFF09").action((options) => {
1187
+ runInjectCommand({
1188
+ cwd: options.cwd,
1189
+ strict: options.strict
1190
+ });
1191
+ });
1192
+ if (process.argv.length <= 2) {
1193
+ program.outputHelp();
1194
+ process.exit(0);
1195
+ }
1196
+ program.parse(process.argv);
1197
+ }
1198
+ main();
41
1199
  //# sourceMappingURL=cli.js.map