@riconext/hermes-repo 0.13.2 → 0.15.0

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
@@ -621,6 +621,90 @@ ${digest}`
621
621
  }
622
622
  }
623
623
 
624
+ // src/capture/convergence.ts
625
+ function analyzeMessagePattern(session) {
626
+ let userCorrections = 0;
627
+ let lastUserMessage = "";
628
+ for (let i = session.messages.length - 1; i >= 0; i--) {
629
+ const msg = session.messages[i];
630
+ if (msg.role === "user") {
631
+ lastUserMessage = msg.text;
632
+ for (const userMsg of session.messages) {
633
+ if (userMsg.role === "user" && /不对|错了|改|改成|改为|应该是|应该用/i.test(userMsg.text)) {
634
+ userCorrections++;
635
+ }
636
+ }
637
+ break;
638
+ }
639
+ }
640
+ const hasFinalApproval = /好的|可以|就这样|同意|对|yes|ok|looks good|perfect|确认|同意/i.test(
641
+ lastUserMessage
642
+ );
643
+ const hasUncertainty = /不明白|还是|有问题|不太对|感觉|好像|可能|应该|不是很|似乎/i.test(
644
+ lastUserMessage
645
+ );
646
+ return {
647
+ userCorrections,
648
+ lastUserMessage,
649
+ hasFinalApproval,
650
+ hasUncertainty
651
+ };
652
+ }
653
+ function isConvergent(session) {
654
+ const pattern = analyzeMessagePattern(session);
655
+ if (pattern.hasFinalApproval) {
656
+ return true;
657
+ }
658
+ if (pattern.userCorrections > 1 && !pattern.hasFinalApproval) {
659
+ if (pattern.hasUncertainty) {
660
+ return false;
661
+ }
662
+ return true;
663
+ }
664
+ return true;
665
+ }
666
+
667
+ // src/capture/externalSignals.ts
668
+ function analyzeExternalSignals(session) {
669
+ return {
670
+ hasCIFailed: session.ciStatus === "failed",
671
+ hasCIPassed: session.ciStatus === "passed",
672
+ hasUserDislike: session.userEmoji === "\u{1F44E}",
673
+ hasUserLike: session.userEmoji === "\u{1F44D}"
674
+ };
675
+ }
676
+ function scoreExternalSignals(session) {
677
+ const signals = analyzeExternalSignals(session);
678
+ let score = 0;
679
+ if (signals.hasUserLike) {
680
+ score += 2;
681
+ }
682
+ if (signals.hasUserDislike) {
683
+ score -= 2;
684
+ }
685
+ if (signals.hasCIPassed && session.fileChanges > 0) {
686
+ score += 1;
687
+ }
688
+ if (signals.hasCIFailed && session.fileChanges > 0) {
689
+ score -= 1.5;
690
+ }
691
+ return score;
692
+ }
693
+ function shouldRejectByExternalSignals(session) {
694
+ if (!session.ciStatus && !session.userEmoji) {
695
+ return false;
696
+ }
697
+ const signals = analyzeExternalSignals(session);
698
+ const score = scoreExternalSignals(session);
699
+ if (signals.hasCIFailed && session.fileChanges > 0 && score < -0.5) {
700
+ return true;
701
+ }
702
+ if (signals.hasUserDislike) {
703
+ return true;
704
+ }
705
+ return false;
706
+ }
707
+
624
708
  // src/capture/shouldCapture.ts
625
709
  var CHINESE_STRONG_SIGNALS = [
626
710
  "\u4FEE\u590D",
@@ -649,9 +733,6 @@ var ENGLISH_STRONG_SIGNAL_PATTERNS = [
649
733
  ];
650
734
  var CORRECTION_RE = /不对|错了|不是这样|不应该|别用|stop|wrong|incorrect|改成|修正/i;
651
735
  var SEMANTIC_SIGNAL_RE = /约定|必须|架构|决策|规范|convention|pattern|always|never/i;
652
- function countUserMessages(session) {
653
- return session.messages.filter((m) => m.role === "user").length;
654
- }
655
736
  function hasStrongSignal(text) {
656
737
  const lower = text.toLowerCase();
657
738
  if (CHINESE_STRONG_SIGNALS.some((w) => lower.includes(w.toLowerCase()))) {
@@ -665,14 +746,26 @@ function hasUserCorrection(session) {
665
746
  );
666
747
  }
667
748
  function shouldCapture(session) {
668
- if (session.messages.length < 3) {
749
+ if (shouldRejectByExternalSignals(session)) {
669
750
  return false;
670
751
  }
671
- if (countUserMessages(session) < 2 && session.toolCalls <= 1) {
752
+ if (hasStrongSignal(session.text) || hasUserCorrection(session)) {
753
+ if (hasUserCorrection(session) && !isConvergent(session)) {
754
+ return false;
755
+ }
756
+ return true;
757
+ }
758
+ if (session.toolCalls > 5) {
759
+ return true;
760
+ }
761
+ if (session.fileChanges > 0 && session.messages.length >= 3) {
762
+ return true;
763
+ }
764
+ const isGreetingOnly = session.messages.length <= 2 && session.toolCalls === 0 && session.fileChanges === 0;
765
+ if (isGreetingOnly) {
672
766
  return false;
673
767
  }
674
- const hasComplexTask = session.toolCalls > 5;
675
- return hasStrongSignal(session.text) || hasUserCorrection(session) || hasComplexTask;
768
+ return false;
676
769
  }
677
770
  function inferCaptureType(session) {
678
771
  if (SEMANTIC_SIGNAL_RE.test(session.text)) {
@@ -3735,27 +3828,101 @@ var claudeCodeAdapter = {
3735
3828
  }
3736
3829
  };
3737
3830
 
3738
- // src/init/assistants/codebuddy.ts
3831
+ // src/init/assistants/codex.ts
3739
3832
  import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync14 } from "fs";
3740
3833
  import { join as join27 } from "path";
3741
3834
 
3742
- // src/init/mergeCodebuddySettings.ts
3835
+ // src/init/mergeCodexConfig.ts
3743
3836
  import { existsSync as existsSync24, readFileSync as readFileSync24 } from "fs";
3744
3837
  import { join as join26 } from "path";
3838
+ var CODEX_CONFIG_REL = ".codex/config.toml";
3839
+ var CODEX_HERMES_START_MARKER = "# >>> hermes-repo codex (do not edit this block manually)";
3840
+ var CODEX_HERMES_END_MARKER = "# <<< hermes-repo codex";
3841
+ function buildCodexHermesBlock() {
3842
+ return [
3843
+ CODEX_HERMES_START_MARKER,
3844
+ "# Hermes uses AGENTS.md as the shared Codex project guidance entry.",
3845
+ "# Run `npx @riconext/hermes-repo search <keyword>` to inspect project memory.",
3846
+ '# Run `npx @riconext/hermes-repo ref --capture <path> --reason "..."` after using memory.',
3847
+ CODEX_HERMES_END_MARKER
3848
+ ].join("\n");
3849
+ }
3850
+ function codexConfigPath(repoRoot) {
3851
+ return join26(repoRoot, ".codex", "config.toml");
3852
+ }
3853
+ function spliceHermesBlock(existing, block) {
3854
+ const startIdx = existing.indexOf(CODEX_HERMES_START_MARKER);
3855
+ const endIdx = existing.indexOf(CODEX_HERMES_END_MARKER);
3856
+ if (startIdx >= 0 && endIdx >= startIdx) {
3857
+ const before = existing.slice(0, startIdx).trimEnd();
3858
+ const after = existing.slice(endIdx + CODEX_HERMES_END_MARKER.length).trimStart();
3859
+ return `${before ? `${before}
3860
+
3861
+ ` : ""}${block}${after ? `
3862
+
3863
+ ${after}` : ""}
3864
+ `;
3865
+ }
3866
+ const trimmed = existing.trimEnd();
3867
+ return `${trimmed ? `${trimmed}
3868
+
3869
+ ` : ""}${block}
3870
+ `;
3871
+ }
3872
+ function mergeCodexConfig(repoRoot) {
3873
+ const configPath = codexConfigPath(repoRoot);
3874
+ const existed = existsSync24(configPath);
3875
+ const block = buildCodexHermesBlock();
3876
+ if (!existed) {
3877
+ return {
3878
+ content: `${block}
3879
+ `,
3880
+ action: "created"
3881
+ };
3882
+ }
3883
+ const existing = readFileSync24(configPath, "utf8");
3884
+ const hasBlock = existing.includes(CODEX_HERMES_START_MARKER) && existing.includes(CODEX_HERMES_END_MARKER);
3885
+ return {
3886
+ content: spliceHermesBlock(existing, block),
3887
+ action: hasBlock ? "replaced" : "appended"
3888
+ };
3889
+ }
3890
+
3891
+ // src/init/assistants/codex.ts
3892
+ var codexAdapter = {
3893
+ id: "codex",
3894
+ label: "OpenAI Codex\uFF08AGENTS.md + .codex/config.toml\uFF09",
3895
+ available: true,
3896
+ scaffoldPaths: [CODEX_CONFIG_REL],
3897
+ write(ctx) {
3898
+ mkdirSync9(join27(ctx.repoRoot, ".codex"), { recursive: true });
3899
+ const { content, action } = mergeCodexConfig(ctx.repoRoot);
3900
+ writeFileSync14(codexConfigPath(ctx.repoRoot), content, "utf8");
3901
+ ctx.report.files.push({ path: CODEX_CONFIG_REL, action });
3902
+ }
3903
+ };
3904
+
3905
+ // src/init/assistants/codebuddy.ts
3906
+ import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync15 } from "fs";
3907
+ import { join as join29 } from "path";
3908
+
3909
+ // src/init/mergeCodebuddySettings.ts
3910
+ import { existsSync as existsSync25, readFileSync as readFileSync25 } from "fs";
3911
+ import { join as join28 } from "path";
3745
3912
  var CODEBUDDY_SETTINGS_LOCAL_REL = ".codebuddy/settings.local.json";
3746
3913
  function codebuddySettingsLocalPath(repoRoot) {
3747
- return join26(repoRoot, ".codebuddy", "settings.local.json");
3914
+ return join28(repoRoot, ".codebuddy", "settings.local.json");
3748
3915
  }
3749
3916
  function mergeCodebuddyLocalSettings(repoRoot) {
3750
3917
  const settingsPath = codebuddySettingsLocalPath(repoRoot);
3751
- const existed = existsSync24(settingsPath);
3918
+ const existed = existsSync25(settingsPath);
3752
3919
  const templateParsed = JSON.parse(
3753
3920
  renderTemplate("hooks.codebuddy.json.tpl")
3754
3921
  );
3755
3922
  let existing = {};
3756
3923
  if (existed) {
3757
3924
  try {
3758
- existing = JSON.parse(readFileSync24(settingsPath, "utf8"));
3925
+ existing = JSON.parse(readFileSync25(settingsPath, "utf8"));
3759
3926
  } catch {
3760
3927
  existing = {};
3761
3928
  }
@@ -3783,32 +3950,32 @@ var codebuddyAdapter = {
3783
3950
  available: true,
3784
3951
  scaffoldPaths: [CODEBUDDY_SETTINGS_LOCAL_REL],
3785
3952
  write(ctx) {
3786
- mkdirSync9(join27(ctx.repoRoot, ".codebuddy"), { recursive: true });
3953
+ mkdirSync10(join29(ctx.repoRoot, ".codebuddy"), { recursive: true });
3787
3954
  const { content, action } = mergeCodebuddyLocalSettings(ctx.repoRoot);
3788
- writeFileSync14(codebuddySettingsLocalPath(ctx.repoRoot), content, "utf8");
3955
+ writeFileSync15(codebuddySettingsLocalPath(ctx.repoRoot), content, "utf8");
3789
3956
  ctx.report.files.push({ path: CODEBUDDY_SETTINGS_LOCAL_REL, action });
3790
3957
  }
3791
3958
  };
3792
3959
 
3793
3960
  // src/init/assistants/cursor.ts
3794
- import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync15 } from "fs";
3795
- import { join as join29 } from "path";
3961
+ import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync16 } from "fs";
3962
+ import { join as join31 } from "path";
3796
3963
 
3797
3964
  // src/init/mergeCursorHooks.ts
3798
- import { existsSync as existsSync25, readFileSync as readFileSync25 } from "fs";
3799
- import { join as join28 } from "path";
3965
+ import { existsSync as existsSync26, readFileSync as readFileSync26 } from "fs";
3966
+ import { join as join30 } from "path";
3800
3967
  var CURSOR_HOOKS_REL = ".cursor/hooks.json";
3801
3968
  function cursorHooksPath(repoRoot) {
3802
- return join28(repoRoot, ".cursor", "hooks.json");
3969
+ return join30(repoRoot, ".cursor", "hooks.json");
3803
3970
  }
3804
3971
  function mergeCursorHooks(repoRoot) {
3805
3972
  const hooksPath = cursorHooksPath(repoRoot);
3806
- const existed = existsSync25(hooksPath);
3973
+ const existed = existsSync26(hooksPath);
3807
3974
  const templateParsed = JSON.parse(renderTemplate("hooks.cursor.json.tpl"));
3808
3975
  let existing = {};
3809
3976
  if (existed) {
3810
3977
  try {
3811
- existing = JSON.parse(readFileSync25(hooksPath, "utf8"));
3978
+ existing = JSON.parse(readFileSync26(hooksPath, "utf8"));
3812
3979
  } catch {
3813
3980
  existing = {};
3814
3981
  }
@@ -3837,9 +4004,9 @@ var cursorAdapter = {
3837
4004
  available: true,
3838
4005
  scaffoldPaths: [CURSOR_HOOKS_REL],
3839
4006
  write(ctx) {
3840
- mkdirSync10(join29(ctx.repoRoot, ".cursor"), { recursive: true });
4007
+ mkdirSync11(join31(ctx.repoRoot, ".cursor"), { recursive: true });
3841
4008
  const { content, action } = mergeCursorHooks(ctx.repoRoot);
3842
- writeFileSync15(cursorHooksPath(ctx.repoRoot), content, "utf8");
4009
+ writeFileSync16(cursorHooksPath(ctx.repoRoot), content, "utf8");
3843
4010
  ctx.report.files.push({ path: CURSOR_HOOKS_REL, action });
3844
4011
  }
3845
4012
  };
@@ -3849,7 +4016,8 @@ var DEFAULT_ASSISTANT_IDS = ["claude-code"];
3849
4016
  var ALL_ADAPTERS = [
3850
4017
  claudeCodeAdapter,
3851
4018
  cursorAdapter,
3852
- codebuddyAdapter
4019
+ codebuddyAdapter,
4020
+ codexAdapter
3853
4021
  ];
3854
4022
  var ADAPTER_BY_ID = new Map(
3855
4023
  ALL_ADAPTERS.map((a) => [a.id, a])
@@ -3898,30 +4066,30 @@ function validateAssistantSelection(ids) {
3898
4066
  }
3899
4067
 
3900
4068
  // src/init/ensureDirs.ts
3901
- import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync16 } from "fs";
3902
- import { join as join30 } from "path";
4069
+ import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync17 } from "fs";
4070
+ import { join as join32 } from "path";
3903
4071
  function ensureMemoryTree(repoRoot) {
3904
- const memoryRoot = join30(repoRoot, MEMORY_DIR);
3905
- mkdirSync11(memoryRoot, { recursive: true });
4072
+ const memoryRoot = join32(repoRoot, MEMORY_DIR);
4073
+ mkdirSync12(memoryRoot, { recursive: true });
3906
4074
  for (const sub of MEMORY_SUBDIRS) {
3907
- mkdirSync11(join30(memoryRoot, sub), { recursive: true });
4075
+ mkdirSync12(join32(memoryRoot, sub), { recursive: true });
3908
4076
  }
3909
- mkdirSync11(join30(repoRoot, ".claude"), { recursive: true });
4077
+ mkdirSync12(join32(repoRoot, ".claude"), { recursive: true });
3910
4078
  for (const sub of GITKEEP_DIRS) {
3911
- const keepPath = join30(memoryRoot, sub, ".gitkeep");
3912
- writeFileSync16(keepPath, "", { flag: "a" });
4079
+ const keepPath = join32(memoryRoot, sub, ".gitkeep");
4080
+ writeFileSync17(keepPath, "", { flag: "a" });
3913
4081
  }
3914
4082
  }
3915
4083
 
3916
4084
  // src/init/mergeAssistants.ts
3917
- import { existsSync as existsSync26, readFileSync as readFileSync26 } from "fs";
4085
+ import { existsSync as existsSync27, readFileSync as readFileSync27 } from "fs";
3918
4086
  function readExistingAssistants(repoRoot) {
3919
4087
  const configPath = memoryPath(repoRoot, "config.json");
3920
- if (!existsSync26(configPath)) {
4088
+ if (!existsSync27(configPath)) {
3921
4089
  return [];
3922
4090
  }
3923
4091
  try {
3924
- const config = JSON.parse(readFileSync26(configPath, "utf8"));
4092
+ const config = JSON.parse(readFileSync27(configPath, "utf8"));
3925
4093
  if (!Array.isArray(config.assistants)) {
3926
4094
  return [];
3927
4095
  }
@@ -3936,17 +4104,17 @@ function mergeAssistants(repoRoot, selected) {
3936
4104
  }
3937
4105
 
3938
4106
  // src/init/mergeGitignore.ts
3939
- import { existsSync as existsSync27, readFileSync as readFileSync27, writeFileSync as writeFileSync17 } from "fs";
3940
- import { join as join31 } from "path";
4107
+ import { existsSync as existsSync28, readFileSync as readFileSync28, writeFileSync as writeFileSync18 } from "fs";
4108
+ import { join as join33 } from "path";
3941
4109
  var START_MARKER = "# >>> hermes-repo memory (do not edit this block manually)";
3942
4110
  var END_MARKER = "# <<< hermes-repo memory";
3943
4111
  function mergeHermesGitignore(repoRoot) {
3944
4112
  const block = readTemplate("gitignore-block.txt").trimEnd() + "\n";
3945
- const gitignorePath = join31(repoRoot, ".gitignore");
3946
- const contentBefore = existsSync27(gitignorePath) ? readFileSync27(gitignorePath, "utf8") : "";
4113
+ const gitignorePath = join33(repoRoot, ".gitignore");
4114
+ const contentBefore = existsSync28(gitignorePath) ? readFileSync28(gitignorePath, "utf8") : "";
3947
4115
  const warnBroadMemoryIgnore = contentBefore.length > 0 && !contentBefore.includes(START_MARKER) && /(^|\n)\.memory\/\s*$/m.test(contentBefore);
3948
- if (!existsSync27(gitignorePath)) {
3949
- writeFileSync17(gitignorePath, `${block}
4116
+ if (!existsSync28(gitignorePath)) {
4117
+ writeFileSync18(gitignorePath, `${block}
3950
4118
  `, "utf8");
3951
4119
  return { action: "created", warnBroadMemoryIgnore: false };
3952
4120
  }
@@ -3956,7 +4124,7 @@ function mergeHermesGitignore(repoRoot) {
3956
4124
  const before = contentBefore.slice(0, startIdx);
3957
4125
  const after = contentBefore.slice(endIdx + END_MARKER.length);
3958
4126
  const next = `${before}${block}${after}`.replace(/\n{3,}/g, "\n\n");
3959
- writeFileSync17(gitignorePath, next.endsWith("\n") ? next : `${next}
4127
+ writeFileSync18(gitignorePath, next.endsWith("\n") ? next : `${next}
3960
4128
  `, "utf8");
3961
4129
  return { action: "replaced", warnBroadMemoryIgnore };
3962
4130
  }
@@ -3964,11 +4132,11 @@ function mergeHermesGitignore(repoRoot) {
3964
4132
  const before = contentBefore.slice(0, startIdx);
3965
4133
  const next = `${before}${block}
3966
4134
  `;
3967
- writeFileSync17(gitignorePath, next, "utf8");
4135
+ writeFileSync18(gitignorePath, next, "utf8");
3968
4136
  return { action: "updated", warnBroadMemoryIgnore };
3969
4137
  }
3970
4138
  const separator = contentBefore.endsWith("\n") || contentBefore.length === 0 ? "\n" : "\n\n";
3971
- writeFileSync17(gitignorePath, `${contentBefore}${separator}${block}
4139
+ writeFileSync18(gitignorePath, `${contentBefore}${separator}${block}
3972
4140
  `, "utf8");
3973
4141
  return { action: "appended", warnBroadMemoryIgnore };
3974
4142
  }
@@ -4039,8 +4207,8 @@ async function withSpinnerProgress(label, fn, formatDone, stream = process.stder
4039
4207
  }
4040
4208
 
4041
4209
  // src/coldstart/collectors/existingRules.ts
4042
- import { existsSync as existsSync28, readFileSync as readFileSync28, readdirSync as readdirSync9 } from "fs";
4043
- import { join as join32 } from "path";
4210
+ import { existsSync as existsSync29, readFileSync as readFileSync29, readdirSync as readdirSync9 } from "fs";
4211
+ import { join as join34 } from "path";
4044
4212
  var MAX_EXCERPT = 3500;
4045
4213
  var RULE_CANDIDATES = [
4046
4214
  "AGENTS.md",
@@ -4048,12 +4216,12 @@ var RULE_CANDIDATES = [
4048
4216
  ".cursorrules"
4049
4217
  ];
4050
4218
  function readExcerpt(repoRoot, rel) {
4051
- const full = join32(repoRoot, rel);
4052
- if (!existsSync28(full)) {
4219
+ const full = join34(repoRoot, rel);
4220
+ if (!existsSync29(full)) {
4053
4221
  return null;
4054
4222
  }
4055
4223
  try {
4056
- const raw = readFileSync28(full, "utf8");
4224
+ const raw = readFileSync29(full, "utf8");
4057
4225
  return raw.length > MAX_EXCERPT ? `${raw.slice(0, MAX_EXCERPT)}
4058
4226
  ...(truncated)` : raw;
4059
4227
  } catch {
@@ -4061,8 +4229,8 @@ function readExcerpt(repoRoot, rel) {
4061
4229
  }
4062
4230
  }
4063
4231
  function collectCursorRules(repoRoot) {
4064
- const dir = join32(repoRoot, ".cursor", "rules");
4065
- if (!existsSync28(dir)) {
4232
+ const dir = join34(repoRoot, ".cursor", "rules");
4233
+ if (!existsSync29(dir)) {
4066
4234
  return [];
4067
4235
  }
4068
4236
  const out = [];
@@ -4113,8 +4281,8 @@ function collectGitLog(repoRoot, limit = 50) {
4113
4281
  }
4114
4282
 
4115
4283
  // src/coldstart/collectors/packageJson.ts
4116
- import { existsSync as existsSync29, readFileSync as readFileSync29 } from "fs";
4117
- import { join as join33 } from "path";
4284
+ import { existsSync as existsSync30, readFileSync as readFileSync30 } from "fs";
4285
+ import { join as join35 } from "path";
4118
4286
  var STACK_HINTS = {
4119
4287
  react: "react",
4120
4288
  vue: "vue",
@@ -4147,12 +4315,12 @@ function inferStackTags(deps) {
4147
4315
  return [...tags];
4148
4316
  }
4149
4317
  function collectPackageJson(repoRoot) {
4150
- const path = join33(repoRoot, "package.json");
4151
- if (!existsSync29(path)) {
4318
+ const path = join35(repoRoot, "package.json");
4319
+ if (!existsSync30(path)) {
4152
4320
  return null;
4153
4321
  }
4154
4322
  try {
4155
- const raw = JSON.parse(readFileSync29(path, "utf8"));
4323
+ const raw = JSON.parse(readFileSync30(path, "utf8"));
4156
4324
  return {
4157
4325
  name: raw.name,
4158
4326
  dependencies: depKeys(raw.dependencies),
@@ -4164,8 +4332,8 @@ function collectPackageJson(repoRoot) {
4164
4332
  }
4165
4333
 
4166
4334
  // src/coldstart/collectors/repoSignals.ts
4167
- import { existsSync as existsSync30, readdirSync as readdirSync10 } from "fs";
4168
- import { join as join34 } from "path";
4335
+ import { existsSync as existsSync31, readdirSync as readdirSync10 } from "fs";
4336
+ import { join as join36 } from "path";
4169
4337
  var SIGNAL_FILES = [
4170
4338
  "Dockerfile",
4171
4339
  "Makefile",
@@ -4177,12 +4345,12 @@ var SIGNAL_FILES = [
4177
4345
  function collectRepoSignals(repoRoot) {
4178
4346
  const signals = [];
4179
4347
  for (const rel of SIGNAL_FILES) {
4180
- const full = join34(repoRoot, rel);
4181
- if (existsSync30(full)) {
4348
+ const full = join36(repoRoot, rel);
4349
+ if (existsSync31(full)) {
4182
4350
  signals.push(rel);
4183
4351
  }
4184
4352
  }
4185
- if (existsSync30(join34(repoRoot, ".gitignore"))) {
4353
+ if (existsSync31(join36(repoRoot, ".gitignore"))) {
4186
4354
  signals.push(".gitignore");
4187
4355
  }
4188
4356
  try {
@@ -4327,7 +4495,7 @@ function runProjectScan(repoRoot) {
4327
4495
 
4328
4496
  // src/init/prompts.ts
4329
4497
  import { checkbox, confirm, input, password } from "@inquirer/prompts";
4330
- import { existsSync as existsSync31, readFileSync as readFileSync30 } from "fs";
4498
+ import { existsSync as existsSync32, readFileSync as readFileSync31 } from "fs";
4331
4499
  import { resolve as resolve6 } from "path";
4332
4500
 
4333
4501
  // src/coldstart/countCaptures.ts
@@ -4338,11 +4506,11 @@ function countExistingCaptures(repoRoot) {
4338
4506
  // src/init/prompts.ts
4339
4507
  function isInitialized(targetDir) {
4340
4508
  const configPath = memoryPath(targetDir, "config.json");
4341
- if (!existsSync31(configPath)) {
4509
+ if (!existsSync32(configPath)) {
4342
4510
  return false;
4343
4511
  }
4344
4512
  try {
4345
- const config = JSON.parse(readFileSync30(configPath, "utf8"));
4513
+ const config = JSON.parse(readFileSync31(configPath, "utf8"));
4346
4514
  return config.version === 1;
4347
4515
  } catch {
4348
4516
  return false;
@@ -4441,7 +4609,7 @@ async function promptLlmFields(defaults = {}, options = {}) {
4441
4609
  }
4442
4610
  async function gatherLlmInitInput(targetDir) {
4443
4611
  const llmPath = memoryPath(targetDir, "llm.json");
4444
- if (existsSync31(llmPath)) {
4612
+ if (existsSync32(llmPath)) {
4445
4613
  const overwrite = await confirm({
4446
4614
  message: "\u68C0\u6D4B\u5230\u5DF2\u6709 .memory/llm.json\uFF0C\u662F\u5426\u8986\u76D6\u5E76\u91CD\u65B0\u914D\u7F6E\uFF1F",
4447
4615
  default: false
@@ -4472,17 +4640,17 @@ async function gatherLlmInitInput(targetDir) {
4472
4640
  }
4473
4641
 
4474
4642
  // src/init/writeScaffoldFile.ts
4475
- import { copyFileSync, writeFileSync as writeFileSync21 } from "fs";
4643
+ import { copyFileSync, writeFileSync as writeFileSync22 } from "fs";
4476
4644
 
4477
4645
  // src/init/mergeConfig.ts
4478
- import { existsSync as existsSync32, readFileSync as readFileSync31 } from "fs";
4646
+ import { existsSync as existsSync33, readFileSync as readFileSync32 } from "fs";
4479
4647
  function mergeConfigForInit(repoRoot, assistants) {
4480
4648
  const configPath = memoryPath(repoRoot, "config.json");
4481
- const existed = existsSync32(configPath);
4649
+ const existed = existsSync33(configPath);
4482
4650
  let existing = {};
4483
4651
  if (existed) {
4484
4652
  try {
4485
- existing = JSON.parse(readFileSync31(configPath, "utf8"));
4653
+ existing = JSON.parse(readFileSync32(configPath, "utf8"));
4486
4654
  } catch {
4487
4655
  existing = {};
4488
4656
  }
@@ -4506,8 +4674,8 @@ function mergeConfigForInit(repoRoot, assistants) {
4506
4674
  }
4507
4675
 
4508
4676
  // src/init/mergeAgentsMd.ts
4509
- import { existsSync as existsSync33, readFileSync as readFileSync32, writeFileSync as writeFileSync18 } from "fs";
4510
- import { join as join35 } from "path";
4677
+ import { existsSync as existsSync34, readFileSync as readFileSync33, writeFileSync as writeFileSync19 } from "fs";
4678
+ import { join as join37 } from "path";
4511
4679
  var HERMES_AGENTS_START_MARKER = "<!-- >>> hermes-repo agents (do not edit this block manually) -->";
4512
4680
  var HERMES_AGENTS_END_MARKER = "<!-- <<< hermes-repo agents -->";
4513
4681
  function buildHermesAgentsBlockBody() {
@@ -4544,7 +4712,7 @@ function withGapBeforeHermesBlock(prefix) {
4544
4712
  }
4545
4713
  return `${trimmed}${GAP_BEFORE_HERMES_BLOCK}`;
4546
4714
  }
4547
- function spliceHermesBlock(existing, block) {
4715
+ function spliceHermesBlock2(existing, block) {
4548
4716
  const startIdx = existing.indexOf(HERMES_AGENTS_START_MARKER);
4549
4717
  const endIdx = existing.indexOf(HERMES_AGENTS_END_MARKER);
4550
4718
  if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
@@ -4563,32 +4731,32 @@ function spliceHermesBlock(existing, block) {
4563
4731
  `;
4564
4732
  }
4565
4733
  function mergeAgentsMd(repoRoot, force) {
4566
- const agentsPath = join35(repoRoot, "AGENTS.md");
4734
+ const agentsPath = join37(repoRoot, "AGENTS.md");
4567
4735
  const block = buildHermesAgentsMarkedBlock();
4568
- if (!existsSync33(agentsPath)) {
4569
- writeFileSync18(agentsPath, buildNewAgentsMd(), "utf8");
4736
+ if (!existsSync34(agentsPath)) {
4737
+ writeFileSync19(agentsPath, buildNewAgentsMd(), "utf8");
4570
4738
  return "created";
4571
4739
  }
4572
- const content = readFileSync32(agentsPath, "utf8");
4740
+ const content = readFileSync33(agentsPath, "utf8");
4573
4741
  if (agentsMdHasHermesBlock(content)) {
4574
4742
  if (!force) {
4575
4743
  return "skipped";
4576
4744
  }
4577
- writeFileSync18(agentsPath, spliceHermesBlock(content, block), "utf8");
4745
+ writeFileSync19(agentsPath, spliceHermesBlock2(content, block), "utf8");
4578
4746
  return "replaced";
4579
4747
  }
4580
4748
  if (agentsMdHasLegacyHermesContent(content)) {
4581
4749
  return "skipped";
4582
4750
  }
4583
- writeFileSync18(agentsPath, spliceHermesBlock(content, block), "utf8");
4751
+ writeFileSync19(agentsPath, spliceHermesBlock2(content, block), "utf8");
4584
4752
  return "appended";
4585
4753
  }
4586
4754
 
4587
4755
  // src/init/mergeLlmConfig.ts
4588
- import { existsSync as existsSync34, writeFileSync as writeFileSync19 } from "fs";
4756
+ import { existsSync as existsSync35, writeFileSync as writeFileSync20 } from "fs";
4589
4757
  function mergeLlmConfigForInit(repoRoot, input2) {
4590
4758
  const llmPath = memoryPath(repoRoot, "llm.json");
4591
- const existed = existsSync34(llmPath);
4759
+ const existed = existsSync35(llmPath);
4592
4760
  let existing = {};
4593
4761
  if (existed) {
4594
4762
  try {
@@ -4617,14 +4785,14 @@ function mergeLlmConfigForInit(repoRoot, input2) {
4617
4785
  }
4618
4786
  function writeLlmJson(repoRoot, input2) {
4619
4787
  const { content, action } = mergeLlmConfigForInit(repoRoot, input2);
4620
- writeFileSync19(memoryPath(repoRoot, "llm.json"), content, "utf8");
4788
+ writeFileSync20(memoryPath(repoRoot, "llm.json"), content, "utf8");
4621
4789
  return action;
4622
4790
  }
4623
4791
 
4624
4792
  // src/init/scaffoldWrite.ts
4625
- import { existsSync as existsSync35, writeFileSync as writeFileSync20 } from "fs";
4793
+ import { existsSync as existsSync36, writeFileSync as writeFileSync21 } from "fs";
4626
4794
  function shouldWriteFile(absolutePath, force) {
4627
- if (!existsSync35(absolutePath)) {
4795
+ if (!existsSync36(absolutePath)) {
4628
4796
  return { write: true, action: "created" };
4629
4797
  }
4630
4798
  if (force) {
@@ -4638,7 +4806,7 @@ function writeIfAllowed(report, absolutePath, relativePath, content, force) {
4638
4806
  report.files.push({ path: relativePath, action });
4639
4807
  return;
4640
4808
  }
4641
- writeFileSync20(absolutePath, content, "utf8");
4809
+ writeFileSync21(absolutePath, content, "utf8");
4642
4810
  report.files.push({ path: relativePath, action });
4643
4811
  }
4644
4812
 
@@ -4646,7 +4814,7 @@ function writeIfAllowed(report, absolutePath, relativePath, content, force) {
4646
4814
  function writeConfigJson(report, repoRoot, assistants) {
4647
4815
  const { content, action } = mergeConfigForInit(repoRoot, assistants);
4648
4816
  const absolutePath = memoryPath(repoRoot, "config.json");
4649
- writeFileSync21(absolutePath, content, "utf8");
4817
+ writeFileSync22(absolutePath, content, "utf8");
4650
4818
  report.files.push({ path: ".memory/config.json", action });
4651
4819
  }
4652
4820
  function copyTemplateIfAllowed(report, templateName, destAbsolute, relativePath, force) {
@@ -4933,10 +5101,10 @@ async function runInitCommand(opts) {
4933
5101
  }
4934
5102
 
4935
5103
  // src/feedback/writeRef.ts
4936
- import { existsSync as existsSync36, mkdirSync as mkdirSync12, writeFileSync as writeFileSync22 } from "fs";
4937
- import { join as join36 } from "path";
5104
+ import { existsSync as existsSync37, mkdirSync as mkdirSync13, writeFileSync as writeFileSync23 } from "fs";
5105
+ import { join as join38 } from "path";
4938
5106
  function targetExists(repoRoot, target) {
4939
- return existsSync36(join36(repoRoot, ".memory", target));
5107
+ return existsSync37(join38(repoRoot, ".memory", target));
4940
5108
  }
4941
5109
  function writeRef(opts) {
4942
5110
  const { repoRoot, reason, session } = opts;
@@ -4958,16 +5126,16 @@ function writeRef(opts) {
4958
5126
  date,
4959
5127
  ...session ? { session } : {}
4960
5128
  };
4961
- mkdirSync12(refsDir(repoRoot), { recursive: true });
5129
+ mkdirSync13(refsDir(repoRoot), { recursive: true });
4962
5130
  const filePath = refFilePath(repoRoot, target, date);
4963
5131
  const base = filePath.replace(/\.json$/, "");
4964
5132
  let finalPath = `${filePath}`;
4965
5133
  let n = 0;
4966
- while (existsSync36(finalPath)) {
5134
+ while (existsSync37(finalPath)) {
4967
5135
  n++;
4968
5136
  finalPath = `${base}-${n}.json`;
4969
5137
  }
4970
- writeFileSync22(finalPath, `${JSON.stringify(record, null, 2)}
5138
+ writeFileSync23(finalPath, `${JSON.stringify(record, null, 2)}
4971
5139
  `, "utf8");
4972
5140
  const file = finalPath.split("/refs/").pop() ?? finalPath;
4973
5141
  return { target, file: `refs/${file}` };
@@ -5007,15 +5175,15 @@ function runRefCommand(opts) {
5007
5175
  }
5008
5176
 
5009
5177
  // src/search/runSearch.ts
5010
- import { existsSync as existsSync37, readdirSync as readdirSync11, readFileSync as readFileSync34 } from "fs";
5011
- import { join as join37 } from "path";
5178
+ import { existsSync as existsSync38, readdirSync as readdirSync11, readFileSync as readFileSync35 } from "fs";
5179
+ import { join as join39 } from "path";
5012
5180
  function walkMdFiles(dir) {
5013
- if (!existsSync37(dir)) {
5181
+ if (!existsSync38(dir)) {
5014
5182
  return [];
5015
5183
  }
5016
5184
  const out = [];
5017
5185
  for (const name of readdirSync11(dir)) {
5018
- const full = join37(dir, name);
5186
+ const full = join39(dir, name);
5019
5187
  if (name.endsWith(".md")) {
5020
5188
  out.push(full);
5021
5189
  } else if (!name.startsWith(".")) {
@@ -5024,7 +5192,7 @@ function walkMdFiles(dir) {
5024
5192
  if (Array.isArray(st)) {
5025
5193
  for (const child of readdirSync11(full)) {
5026
5194
  if (child.endsWith(".md")) {
5027
- out.push(join37(full, child));
5195
+ out.push(join39(full, child));
5028
5196
  }
5029
5197
  }
5030
5198
  }
@@ -5036,13 +5204,13 @@ function walkMdFiles(dir) {
5036
5204
  }
5037
5205
  function listSkillMd(repoRoot) {
5038
5206
  const skillsDir = memoryPath(repoRoot, "skills");
5039
- if (!existsSync37(skillsDir)) {
5207
+ if (!existsSync38(skillsDir)) {
5040
5208
  return [];
5041
5209
  }
5042
5210
  const out = [];
5043
5211
  for (const slug of readdirSync11(skillsDir)) {
5044
- const f = join37(skillsDir, slug, "SKILL.md");
5045
- if (existsSync37(f)) {
5212
+ const f = join39(skillsDir, slug, "SKILL.md");
5213
+ if (existsSync38(f)) {
5046
5214
  out.push(f);
5047
5215
  }
5048
5216
  }
@@ -5050,7 +5218,7 @@ function listSkillMd(repoRoot) {
5050
5218
  }
5051
5219
  function summaryFromFile(absPath, relFromMemory) {
5052
5220
  try {
5053
- const content = readFileSync34(absPath, "utf8");
5221
+ const content = readFileSync35(absPath, "utf8");
5054
5222
  if (relFromMemory.startsWith("captures/")) {
5055
5223
  const parsed = parseCaptureMarkdown(content, relFromMemory, absPath);
5056
5224
  if (parsed) {
@@ -5073,10 +5241,10 @@ function runSearch(opts) {
5073
5241
  const memRoot = memoryPath(opts.repoRoot);
5074
5242
  const captureTypes = opts.type ? [opts.type] : ["semantic", "episodic", "procedural"];
5075
5243
  for (const t of captureTypes) {
5076
- const dir = join37(memRoot, "captures", t);
5244
+ const dir = join39(memRoot, "captures", t);
5077
5245
  for (const abs of walkMdFiles(dir)) {
5078
5246
  const rel = abs.replace(memRoot + "/", "").replace(/\\/g, "/");
5079
- const content = readFileSync34(abs, "utf8").toLowerCase();
5247
+ const content = readFileSync35(abs, "utf8").toLowerCase();
5080
5248
  if (content.includes(kw)) {
5081
5249
  hits.push({
5082
5250
  path: rel,
@@ -5088,10 +5256,10 @@ function runSearch(opts) {
5088
5256
  }
5089
5257
  }
5090
5258
  }
5091
- const topicsDir = join37(memRoot, "topics");
5259
+ const topicsDir = join39(memRoot, "topics");
5092
5260
  for (const abs of walkMdFiles(topicsDir)) {
5093
5261
  const rel = abs.replace(memRoot + "/", "").replace(/\\/g, "/");
5094
- if (readFileSync34(abs, "utf8").toLowerCase().includes(kw)) {
5262
+ if (readFileSync35(abs, "utf8").toLowerCase().includes(kw)) {
5095
5263
  hits.push({ path: rel, summary: summaryFromFile(abs, rel) });
5096
5264
  }
5097
5265
  if (hits.length >= limit) {
@@ -5100,7 +5268,7 @@ function runSearch(opts) {
5100
5268
  }
5101
5269
  for (const abs of listSkillMd(opts.repoRoot)) {
5102
5270
  const rel = abs.replace(memRoot + "/", "").replace(/\\/g, "/");
5103
- if (readFileSync34(abs, "utf8").toLowerCase().includes(kw)) {
5271
+ if (readFileSync35(abs, "utf8").toLowerCase().includes(kw)) {
5104
5272
  hits.push({ path: rel, summary: summaryFromFile(abs, rel) });
5105
5273
  }
5106
5274
  if (hits.length >= limit) {
@@ -5153,7 +5321,7 @@ function runSearchCommand(opts) {
5153
5321
  }
5154
5322
 
5155
5323
  // src/stats/runStats.ts
5156
- import { existsSync as existsSync38, statSync as statSync6 } from "fs";
5324
+ import { existsSync as existsSync39, statSync as statSync6 } from "fs";
5157
5325
  function collectStats(repoRoot, nowMs = Date.now()) {
5158
5326
  const all = listAllCaptures(repoRoot);
5159
5327
  const active = filterActiveCaptures(all);
@@ -5183,7 +5351,7 @@ function collectStats(repoRoot, nowMs = Date.now()) {
5183
5351
  }
5184
5352
  }
5185
5353
  const memoryFile = memoryPath(repoRoot, "MEMORY.md");
5186
- const memoryBytes = existsSync38(memoryFile) ? statSync6(memoryFile).size : 0;
5354
+ const memoryBytes = existsSync39(memoryFile) ? statSync6(memoryFile).size : 0;
5187
5355
  const allSkills = listSkillIndex(repoRoot);
5188
5356
  const skillUsage = readSkillUsage(repoRoot);
5189
5357
  const inMemory = filterSkillIndexForMemory(allSkills, skillUsage, repoRoot, nowMs);
@@ -5250,11 +5418,11 @@ function runStatsCommand(opts) {
5250
5418
  }
5251
5419
 
5252
5420
  // src/promote/runPromote.ts
5253
- import { existsSync as existsSync44, mkdirSync as mkdirSync14, writeFileSync as writeFileSync24 } from "fs";
5421
+ import { existsSync as existsSync45, mkdirSync as mkdirSync15, writeFileSync as writeFileSync25 } from "fs";
5254
5422
  import { resolve as resolve8 } from "path";
5255
5423
 
5256
5424
  // src/promote/buildTopicDraft.ts
5257
- import { existsSync as existsSync39, readFileSync as readFileSync35 } from "fs";
5425
+ import { existsSync as existsSync40, readFileSync as readFileSync36 } from "fs";
5258
5426
  function ruleTopicDraftBody(tag, captures, existing) {
5259
5427
  const lines = captures.map(
5260
5428
  (c) => `- [${c.date}] [${c.type}] ${c.summary.slice(0, 120)} (${c.path}) [\u664B\u5347\u5019\u9009]`
@@ -5281,9 +5449,9 @@ async function buildTopicDraftBody(repoRoot, captures, llm) {
5281
5449
  const slug = tagToSlug(tag);
5282
5450
  const abs = memoryPath(repoRoot, "topics", `${slug}.md`);
5283
5451
  let existing = "";
5284
- if (existsSync39(abs)) {
5452
+ if (existsSync40(abs)) {
5285
5453
  try {
5286
- existing = readFileSync35(abs, "utf8");
5454
+ existing = readFileSync36(abs, "utf8");
5287
5455
  } catch {
5288
5456
  existing = "";
5289
5457
  }
@@ -5308,7 +5476,7 @@ ${c.findings.slice(0, 300)}`
5308
5476
  }
5309
5477
 
5310
5478
  // src/promote/detectTopicConflict.ts
5311
- import { existsSync as existsSync40, readFileSync as readFileSync36 } from "fs";
5479
+ import { existsSync as existsSync41, readFileSync as readFileSync37 } from "fs";
5312
5480
  var MUTEX_PAIRS2 = [
5313
5481
  ["localstorage", "httponly"],
5314
5482
  ["local storage", "httponly"],
@@ -5338,12 +5506,12 @@ function detectTopicConflict(repoRoot, capture) {
5338
5506
  const slug = tagToSlug(tag);
5339
5507
  const topicPath = memoryPath(repoRoot, "topics", `${slug}.md`);
5340
5508
  const relTopic = `topics/${slug}.md`;
5341
- if (!existsSync40(topicPath)) {
5509
+ if (!existsSync41(topicPath)) {
5342
5510
  return { hasConflict: false, reason: "none" };
5343
5511
  }
5344
5512
  let existing = "";
5345
5513
  try {
5346
- existing = readFileSync36(topicPath, "utf8");
5514
+ existing = readFileSync37(topicPath, "utf8");
5347
5515
  } catch {
5348
5516
  return { hasConflict: false, reason: "none" };
5349
5517
  }
@@ -5509,17 +5677,17 @@ async function buildMergedStagingDrafts(repoRoot, analyses, llm) {
5509
5677
 
5510
5678
  // src/promote/applyDecisions.ts
5511
5679
  import {
5512
- existsSync as existsSync41,
5513
- mkdirSync as mkdirSync13,
5680
+ existsSync as existsSync42,
5681
+ mkdirSync as mkdirSync14,
5514
5682
  readdirSync as readdirSync12,
5515
- readFileSync as readFileSync37,
5683
+ readFileSync as readFileSync38,
5516
5684
  unlinkSync as unlinkSync3,
5517
- writeFileSync as writeFileSync23
5685
+ writeFileSync as writeFileSync24
5518
5686
  } from "fs";
5519
- import { join as join39 } from "path";
5687
+ import { join as join41 } from "path";
5520
5688
 
5521
5689
  // src/promote/paths.ts
5522
- import { join as join38 } from "path";
5690
+ import { join as join40 } from "path";
5523
5691
  function promoteDir(repoRoot) {
5524
5692
  return memoryPath(repoRoot, "promote");
5525
5693
  }
@@ -5537,7 +5705,7 @@ function resolvePromoteTemplatePath(repoRoot) {
5537
5705
  return inMemory;
5538
5706
  }
5539
5707
  function stagingTopicPath(repoRoot, slug) {
5540
- return join38(promoteStagingTopicsDir(repoRoot), `${slug}.md`);
5708
+ return join40(promoteStagingTopicsDir(repoRoot), `${slug}.md`);
5541
5709
  }
5542
5710
 
5543
5711
  // src/promote/applyDecisions.ts
@@ -5573,17 +5741,17 @@ function buildManifestTemplate(capturePaths, dateIso) {
5573
5741
  }
5574
5742
  function removePromoteMarker(repoRoot, capturePath) {
5575
5743
  const marker = promoteMarkerPath(repoRoot, capturePath);
5576
- if (existsSync41(marker)) {
5744
+ if (existsSync42(marker)) {
5577
5745
  unlinkSync3(marker);
5578
5746
  }
5579
5747
  }
5580
5748
  function annotateReject(repoRoot, capturePath, note) {
5581
- const abs = join39(repoRoot, ".memory", capturePath);
5582
- if (!existsSync41(abs)) {
5749
+ const abs = join41(repoRoot, ".memory", capturePath);
5750
+ if (!existsSync42(abs)) {
5583
5751
  return;
5584
5752
  }
5585
5753
  try {
5586
- let content = readFileSync37(abs, "utf8");
5754
+ let content = readFileSync38(abs, "utf8");
5587
5755
  const fields = {
5588
5756
  promote_rejected_at: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)
5589
5757
  };
@@ -5591,22 +5759,22 @@ function annotateReject(repoRoot, capturePath, note) {
5591
5759
  fields.promote_note = note.trim().slice(0, 200);
5592
5760
  }
5593
5761
  content = setFrontmatterScalars(content, fields);
5594
- writeFileSync23(abs, content, "utf8");
5762
+ writeFileSync24(abs, content, "utf8");
5595
5763
  } catch {
5596
5764
  }
5597
5765
  }
5598
5766
  function mergeTopicFromStaging(repoRoot, slug, dryRun) {
5599
5767
  const staging = stagingTopicPath(repoRoot, slug);
5600
- if (!existsSync41(staging)) {
5768
+ if (!existsSync42(staging)) {
5601
5769
  return null;
5602
5770
  }
5603
- const draft = readFileSync37(staging, "utf8");
5771
+ const draft = readFileSync38(staging, "utf8");
5604
5772
  const dest = memoryPath(repoRoot, "topics", `${slug}.md`);
5605
5773
  if (dryRun) {
5606
5774
  return `topics/${slug}.md`;
5607
5775
  }
5608
- mkdirSync13(memoryPath(repoRoot, "topics"), { recursive: true });
5609
- writeFileSync23(dest, draft.endsWith("\n") ? draft : `${draft}
5776
+ mkdirSync14(memoryPath(repoRoot, "topics"), { recursive: true });
5777
+ writeFileSync24(dest, draft.endsWith("\n") ? draft : `${draft}
5610
5778
  `, "utf8");
5611
5779
  return `topics/${slug}.md`;
5612
5780
  }
@@ -5642,7 +5810,7 @@ function applyDecisions(repoRoot, manifest, opts) {
5642
5810
  }
5643
5811
  if (slugsToMerge.size === 0 && result.approved.length > 0) {
5644
5812
  const stagingDir = promoteStagingTopicsDir(repoRoot);
5645
- if (existsSync41(stagingDir)) {
5813
+ if (existsSync42(stagingDir)) {
5646
5814
  for (const name of readdirSync12(stagingDir)) {
5647
5815
  if (name.endsWith(".md")) {
5648
5816
  slugsToMerge.add(name.replace(/\.md$/, ""));
@@ -5659,21 +5827,21 @@ function applyDecisions(repoRoot, manifest, opts) {
5659
5827
  return result;
5660
5828
  }
5661
5829
  function readManifestFile(manifestPath) {
5662
- const raw = readFileSync37(manifestPath, "utf8");
5830
+ const raw = readFileSync38(manifestPath, "utf8");
5663
5831
  return parseManifestJson(raw);
5664
5832
  }
5665
5833
  function writeManifestTemplate(repoRoot, capturePaths) {
5666
5834
  const dateIso = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
5667
5835
  const manifest = buildManifestTemplate(capturePaths, dateIso);
5668
5836
  const path = decisionsTemplatePath(repoRoot);
5669
- mkdirSync13(memoryPath(repoRoot, "promote"), { recursive: true });
5670
- writeFileSync23(path, `${JSON.stringify(manifest, null, 2)}
5837
+ mkdirSync14(memoryPath(repoRoot, "promote"), { recursive: true });
5838
+ writeFileSync24(path, `${JSON.stringify(manifest, null, 2)}
5671
5839
  `, "utf8");
5672
5840
  return path;
5673
5841
  }
5674
5842
 
5675
5843
  // src/promote/buildPrBody.ts
5676
- import { existsSync as existsSync42, readFileSync as readFileSync38 } from "fs";
5844
+ import { existsSync as existsSync43, readFileSync as readFileSync39 } from "fs";
5677
5845
  function actionLabel(action) {
5678
5846
  if (action === "approve") {
5679
5847
  return "\u6279\u51C6\u664B\u5347";
@@ -5713,9 +5881,9 @@ ${conflictLine}
5713
5881
  }
5714
5882
  function loadTemplate(repoRoot) {
5715
5883
  const path = resolvePromoteTemplatePath(repoRoot);
5716
- if (existsSync42(path)) {
5884
+ if (existsSync43(path)) {
5717
5885
  try {
5718
- return readFileSync38(path, "utf8");
5886
+ return readFileSync39(path, "utf8");
5719
5887
  } catch {
5720
5888
  }
5721
5889
  }
@@ -5763,7 +5931,7 @@ ${items}
5763
5931
  }
5764
5932
 
5765
5933
  // src/promote/listPromoteCandidates.ts
5766
- import { existsSync as existsSync43, readdirSync as readdirSync13 } from "fs";
5934
+ import { existsSync as existsSync44, readdirSync as readdirSync13 } from "fs";
5767
5935
  var TYPES2 = ["semantic", "episodic", "procedural"];
5768
5936
  function normalizeCapturePath(input2) {
5769
5937
  let p = input2.replace(/\\/g, "/").trim();
@@ -5780,7 +5948,7 @@ function listPromoteCandidates(repoRoot, filterPaths) {
5780
5948
  const results = [];
5781
5949
  for (const type of TYPES2) {
5782
5950
  const dir = memoryPath(repoRoot, "captures", type);
5783
- if (!existsSync43(dir)) {
5951
+ if (!existsSync44(dir)) {
5784
5952
  continue;
5785
5953
  }
5786
5954
  for (const name of readdirSync13(dir)) {
@@ -5807,8 +5975,8 @@ function listPromoteCandidates(repoRoot, filterPaths) {
5807
5975
 
5808
5976
  // src/promote/runPromote.ts
5809
5977
  function ensurePromoteDirs(repoRoot) {
5810
- mkdirSync14(promoteDir(repoRoot), { recursive: true });
5811
- mkdirSync14(promoteStagingTopicsDir(repoRoot), { recursive: true });
5978
+ mkdirSync15(promoteDir(repoRoot), { recursive: true });
5979
+ mkdirSync15(promoteStagingTopicsDir(repoRoot), { recursive: true });
5812
5980
  }
5813
5981
  function formatPreviewTable(analyses) {
5814
5982
  const lines = [
@@ -5852,15 +6020,15 @@ async function runPromote(opts) {
5852
6020
  const stagingTopicPaths = [];
5853
6021
  for (const [slug, body] of mergedDrafts) {
5854
6022
  const path = stagingTopicPath(repoRoot, slug);
5855
- writeFileSync24(path, body, "utf8");
6023
+ writeFileSync25(path, body, "utf8");
5856
6024
  stagingTopicPaths.push(
5857
6025
  `.memory/promote/staging/topics/${slug}.md`
5858
6026
  );
5859
6027
  }
5860
6028
  const prBody = buildPrBody(repoRoot, analyses);
5861
6029
  const prBodyPath = opts.outPath ? resolve8(opts.outPath) : defaultPrBodyPath(repoRoot, dateIso);
5862
- mkdirSync14(memoryPath(repoRoot, "promote"), { recursive: true });
5863
- writeFileSync24(prBodyPath, prBody, "utf8");
6030
+ mkdirSync15(memoryPath(repoRoot, "promote"), { recursive: true });
6031
+ writeFileSync25(prBodyPath, prBody, "utf8");
5864
6032
  const manifestTemplatePath = writeManifestTemplate(
5865
6033
  repoRoot,
5866
6034
  candidates.map((c) => c.path)