@retaw106/mmc-oc 1.2.3 → 2.0.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.
Files changed (227) hide show
  1. package/dist/cli.js +7095 -0
  2. package/dist/config/index.d.ts +0 -12
  3. package/dist/config/index.d.ts.map +1 -1
  4. package/dist/config/legacy-harness-blocks.d.ts +27 -0
  5. package/dist/config/legacy-harness-blocks.d.ts.map +1 -0
  6. package/dist/config/live-run-config.d.ts.map +1 -1
  7. package/dist/config/live-snapshot.d.ts +2 -1
  8. package/dist/config/live-snapshot.d.ts.map +1 -1
  9. package/dist/config/project-security.d.ts +4 -5
  10. package/dist/config/project-security.d.ts.map +1 -1
  11. package/dist/config/raw-loader.d.ts +7 -26
  12. package/dist/config/raw-loader.d.ts.map +1 -1
  13. package/dist/config/schema/magic-context.d.ts +164 -259
  14. package/dist/config/schema/magic-context.d.ts.map +1 -1
  15. package/dist/config/variable.d.ts +8 -0
  16. package/dist/config/variable.d.ts.map +1 -1
  17. package/dist/config-init.js +5595 -0
  18. package/dist/{context-a6px04s0.js → context-95zkksn3.js} +2471 -2438
  19. package/dist/{derive-budgets-fhk00zv8.js → derive-budgets-rjjz5f7y.js} +2 -2
  20. package/dist/features/magic-context/compaction-marker.d.ts.map +1 -1
  21. package/dist/features/magic-context/message-index.d.ts +2 -0
  22. package/dist/features/magic-context/message-index.d.ts.map +1 -1
  23. package/dist/features/magic-context/migration-v3-perf-schema.d.ts +8 -0
  24. package/dist/features/magic-context/migration-v3-perf-schema.d.ts.map +1 -0
  25. package/dist/features/magic-context/migration-v4-temporal-decisions.d.ts +12 -0
  26. package/dist/features/magic-context/migration-v4-temporal-decisions.d.ts.map +1 -0
  27. package/dist/features/magic-context/migrations.d.ts.map +1 -1
  28. package/dist/features/magic-context/overflow-detection.d.ts +6 -0
  29. package/dist/features/magic-context/overflow-detection.d.ts.map +1 -1
  30. package/dist/features/magic-context/reclaim-protection.d.ts +15 -10
  31. package/dist/features/magic-context/reclaim-protection.d.ts.map +1 -1
  32. package/dist/features/magic-context/session-cache-ttl.d.ts +4 -2
  33. package/dist/features/magic-context/session-cache-ttl.d.ts.map +1 -1
  34. package/dist/features/magic-context/storage-db.d.ts +1 -1
  35. package/dist/features/magic-context/storage-db.d.ts.map +1 -1
  36. package/dist/features/magic-context/storage-historian-runs.d.ts +2 -0
  37. package/dist/features/magic-context/storage-historian-runs.d.ts.map +1 -1
  38. package/dist/features/magic-context/storage-ops.d.ts +2 -0
  39. package/dist/features/magic-context/storage-ops.d.ts.map +1 -1
  40. package/dist/features/magic-context/storage-session-tables.d.ts.map +1 -1
  41. package/dist/features/magic-context/storage-tags.d.ts +15 -2
  42. package/dist/features/magic-context/storage-tags.d.ts.map +1 -1
  43. package/dist/features/magic-context/storage.d.ts +2 -2
  44. package/dist/features/magic-context/storage.d.ts.map +1 -1
  45. package/dist/features/magic-context/temporal-decisions.d.ts +10 -0
  46. package/dist/features/magic-context/temporal-decisions.d.ts.map +1 -0
  47. package/dist/features/magic-context/tool-definition-tokens.d.ts +3 -0
  48. package/dist/features/magic-context/tool-definition-tokens.d.ts.map +1 -1
  49. package/dist/hooks/magic-context/active-anthropic-turn.d.ts +8 -0
  50. package/dist/hooks/magic-context/active-anthropic-turn.d.ts.map +1 -0
  51. package/dist/hooks/magic-context/apply-operations.d.ts +15 -0
  52. package/dist/hooks/magic-context/apply-operations.d.ts.map +1 -1
  53. package/dist/hooks/magic-context/auto-search-deadline.d.ts +10 -3
  54. package/dist/hooks/magic-context/auto-search-deadline.d.ts.map +1 -1
  55. package/dist/hooks/magic-context/auto-search-runner.d.ts +1 -1
  56. package/dist/hooks/magic-context/auto-search-runner.d.ts.map +1 -1
  57. package/dist/hooks/magic-context/caveman-cleanup.d.ts.map +1 -1
  58. package/dist/hooks/magic-context/channel2-cycle.d.ts +2 -0
  59. package/dist/hooks/magic-context/channel2-cycle.d.ts.map +1 -1
  60. package/dist/hooks/magic-context/compaction-marker-manager.d.ts.map +1 -1
  61. package/dist/hooks/magic-context/compartment-render-epoch.d.ts +8 -0
  62. package/dist/hooks/magic-context/compartment-render-epoch.d.ts.map +1 -1
  63. package/dist/hooks/magic-context/compartment-runner-partial-recomp.d.ts.map +1 -1
  64. package/dist/hooks/magic-context/compartment-runner-recomp.d.ts.map +1 -1
  65. package/dist/hooks/magic-context/compartment-trigger.d.ts +3 -1
  66. package/dist/hooks/magic-context/compartment-trigger.d.ts.map +1 -1
  67. package/dist/hooks/magic-context/ctx-reduce-availability.d.ts +12 -0
  68. package/dist/hooks/magic-context/ctx-reduce-availability.d.ts.map +1 -1
  69. package/dist/hooks/magic-context/decision-calibration.d.ts +3 -0
  70. package/dist/hooks/magic-context/decision-calibration.d.ts.map +1 -1
  71. package/dist/hooks/magic-context/emergency-drop.d.ts +2 -0
  72. package/dist/hooks/magic-context/emergency-drop.d.ts.map +1 -1
  73. package/dist/hooks/magic-context/emergency-fail-closed.d.ts +21 -1
  74. package/dist/hooks/magic-context/emergency-fail-closed.d.ts.map +1 -1
  75. package/dist/hooks/magic-context/final-wire-token-estimate.d.ts +30 -1
  76. package/dist/hooks/magic-context/final-wire-token-estimate.d.ts.map +1 -1
  77. package/dist/hooks/magic-context/heuristic-cleanup.d.ts +2 -0
  78. package/dist/hooks/magic-context/heuristic-cleanup.d.ts.map +1 -1
  79. package/dist/hooks/magic-context/inject-compartments.d.ts +10 -8
  80. package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
  81. package/dist/hooks/magic-context/latest-assistant-turn.d.ts +25 -0
  82. package/dist/hooks/magic-context/latest-assistant-turn.d.ts.map +1 -0
  83. package/dist/hooks/magic-context/lkg-measured-request.d.ts +69 -0
  84. package/dist/hooks/magic-context/lkg-measured-request.d.ts.map +1 -0
  85. package/dist/hooks/magic-context/read-session-formatting.d.ts +3 -0
  86. package/dist/hooks/magic-context/read-session-formatting.d.ts.map +1 -1
  87. package/dist/hooks/magic-context/reasoning-budget-status.d.ts +15 -0
  88. package/dist/hooks/magic-context/reasoning-budget-status.d.ts.map +1 -0
  89. package/dist/hooks/magic-context/reasoning-budget.d.ts +40 -0
  90. package/dist/hooks/magic-context/reasoning-budget.d.ts.map +1 -0
  91. package/dist/hooks/magic-context/reasoning-removal.d.ts +6 -6
  92. package/dist/hooks/magic-context/reasoning-removal.d.ts.map +1 -1
  93. package/dist/hooks/magic-context/recomp-orchestrator.d.ts +2 -0
  94. package/dist/hooks/magic-context/recomp-orchestrator.d.ts.map +1 -1
  95. package/dist/hooks/magic-context/sentinel.d.ts +2 -0
  96. package/dist/hooks/magic-context/sentinel.d.ts.map +1 -1
  97. package/dist/hooks/magic-context/strip-content.d.ts +7 -3
  98. package/dist/hooks/magic-context/strip-content.d.ts.map +1 -1
  99. package/dist/hooks/magic-context/supersession-reclaim.d.ts +4 -0
  100. package/dist/hooks/magic-context/supersession-reclaim.d.ts.map +1 -1
  101. package/dist/hooks/magic-context/tag-messages.d.ts +13 -0
  102. package/dist/hooks/magic-context/tag-messages.d.ts.map +1 -1
  103. package/dist/hooks/magic-context/tail-hygiene-walk.d.ts +5 -0
  104. package/dist/hooks/magic-context/tail-hygiene-walk.d.ts.map +1 -1
  105. package/dist/hooks/magic-context/temporal-awareness.d.ts +6 -3
  106. package/dist/hooks/magic-context/temporal-awareness.d.ts.map +1 -1
  107. package/dist/hooks/magic-context/temporal-served-projection.d.ts +4 -0
  108. package/dist/hooks/magic-context/temporal-served-projection.d.ts.map +1 -0
  109. package/dist/hooks/magic-context/tool-reclaim.d.ts +2 -0
  110. package/dist/hooks/magic-context/tool-reclaim.d.ts.map +1 -1
  111. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +34 -12
  112. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  113. package/dist/hooks/magic-context/transform.d.ts +11 -5
  114. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  115. package/dist/hooks/magic-context/wrapup-orchestrator.d.ts.map +1 -1
  116. package/dist/{index-wgt4gamw.js → index-1mjhzqq7.js} +422 -31
  117. package/dist/index-33gprt9g.js +30 -0
  118. package/dist/{index-zx63j2e2.js → index-5cep2r9n.js} +3 -2
  119. package/dist/{index-by6h2pav.js → index-bdt9my9y.js} +374 -239
  120. package/dist/{index-3wagyrnj.js → index-x37vg474.js} +5 -1
  121. package/dist/{index-1vs6anwz.js → index-xt7y10x6.js} +2 -1
  122. package/dist/{index-me3zcj5t.js → index-z2g20msd.js} +4 -8
  123. package/dist/index.js +1 -1
  124. package/dist/{message-time-backfill-5ewk0axe.js → message-time-backfill-zfernxbw.js} +1 -1
  125. package/dist/{model-resolution-5haaxnpt.js → model-resolution-wazv26n0.js} +1 -1
  126. package/dist/plugin/rpc-handlers.d.ts +4 -0
  127. package/dist/plugin/rpc-handlers.d.ts.map +1 -1
  128. package/dist/{read-session-chunk-apjwrttc.js → read-session-chunk-hp464g13.js} +2 -2
  129. package/dist/{recomp-orchestrator-gyeevxaw.js → recomp-orchestrator-3qxeqwb9.js} +6 -6
  130. package/dist/shared/auto-search-lifecycle.d.ts +7 -0
  131. package/dist/shared/auto-search-lifecycle.d.ts.map +1 -0
  132. package/dist/shared/cache-ttl-display.d.ts +6 -4
  133. package/dist/shared/cache-ttl-display.d.ts.map +1 -1
  134. package/dist/shared/model-cache-ttl.d.ts +1 -1
  135. package/dist/shared/model-cache-ttl.d.ts.map +1 -1
  136. package/dist/shared/model-resolution.d.ts +5 -2
  137. package/dist/shared/model-resolution.d.ts.map +1 -1
  138. package/dist/shared/model-suggestion-retry.d.ts +2 -0
  139. package/dist/shared/model-suggestion-retry.d.ts.map +1 -1
  140. package/dist/shared/protected-tools-policy.d.ts +6 -0
  141. package/dist/shared/protected-tools-policy.d.ts.map +1 -0
  142. package/dist/shared/provider-output-failure.d.ts +19 -0
  143. package/dist/shared/provider-output-failure.d.ts.map +1 -0
  144. package/dist/shared/temporal-upgrade-fixture.d.ts +19 -0
  145. package/dist/shared/temporal-upgrade-fixture.d.ts.map +1 -0
  146. package/dist/shared/window-geometry.d.ts +4 -0
  147. package/dist/shared/window-geometry.d.ts.map +1 -1
  148. package/dist/shared/write-transaction-timing.d.ts +8 -1
  149. package/dist/shared/write-transaction-timing.d.ts.map +1 -1
  150. package/dist/tools/ctx-reduce/tools.d.ts +2 -0
  151. package/dist/tools/ctx-reduce/tools.d.ts.map +1 -1
  152. package/dist/v2/hidden-child-native.d.ts +7 -0
  153. package/dist/v2/hidden-child-native.d.ts.map +1 -1
  154. package/dist/v2/hidden-child-record.d.ts +13 -0
  155. package/dist/v2/hidden-child-record.d.ts.map +1 -1
  156. package/dist/v2/hidden-completion.d.ts.map +1 -1
  157. package/dist/v2/hooks/context.d.ts.map +1 -1
  158. package/dist/v2/hooks/hidden-child.d.ts +46 -2
  159. package/dist/v2/hooks/hidden-child.d.ts.map +1 -1
  160. package/dist/v2/hooks/payload.d.ts.map +1 -1
  161. package/dist/v2/hooks/store.d.ts.map +1 -1
  162. package/dist/v2/hooks/tools.d.ts.map +1 -1
  163. package/dist/v2/hooks/types.d.ts +8 -0
  164. package/dist/v2/hooks/types.d.ts.map +1 -1
  165. package/dist/v2/server.js +4540 -3889
  166. package/dist/v2/tool-result.d.ts +8 -0
  167. package/dist/v2/tool-result.d.ts.map +1 -0
  168. package/dist/web/api.d.ts +6 -0
  169. package/dist/web/api.d.ts.map +1 -1
  170. package/dist/web/context-store.d.ts +13 -0
  171. package/dist/web/context-store.d.ts.map +1 -1
  172. package/dist/web/display-path.d.ts +28 -0
  173. package/dist/web/display-path.d.ts.map +1 -0
  174. package/dist/web/entry-args.d.ts +33 -0
  175. package/dist/web/entry-args.d.ts.map +1 -0
  176. package/dist/web/host-pi.d.ts +2 -0
  177. package/dist/web/host-pi.d.ts.map +1 -1
  178. package/dist/web/main.d.ts +4 -6
  179. package/dist/web/main.d.ts.map +1 -1
  180. package/dist/web/main.js +772 -77
  181. package/dist/web/message-stats.d.ts +45 -0
  182. package/dist/web/message-stats.d.ts.map +1 -0
  183. package/dist/web/ui/index.html +1 -0
  184. package/dist/web/ui/panel.css +5 -0
  185. package/dist/web/ui/panel.js +220 -70
  186. package/dist/web/views.d.ts +23 -0
  187. package/dist/web/views.d.ts.map +1 -1
  188. package/dist/{wrapup-orchestrator-z9sqpqjf.js → wrapup-orchestrator-wgb2nd19.js} +7 -6
  189. package/package.json +7 -2
  190. package/src/config/index.test.ts +257 -126
  191. package/src/config/index.ts +45 -39
  192. package/src/config/legacy-harness-blocks.test.ts +78 -0
  193. package/src/config/legacy-harness-blocks.ts +71 -0
  194. package/src/config/live-run-config.test.ts +49 -5
  195. package/src/config/live-run-config.ts +1 -0
  196. package/src/config/live-snapshot.ts +5 -1
  197. package/src/config/profiles.ts +1 -1
  198. package/src/config/project-config-trust-boundary.test.ts +2 -2
  199. package/src/config/project-security.hostile-matrix.test.ts +51 -65
  200. package/src/config/project-security.test.ts +6 -1
  201. package/src/config/project-security.ts +13 -12
  202. package/src/config/protected-tokens.test.ts +1 -1
  203. package/src/config/raw-loader.test.ts +29 -352
  204. package/src/config/raw-loader.ts +17 -406
  205. package/src/config/removed-config-keys.test.ts +1 -1
  206. package/src/config/schema/magic-context.test.ts +60 -64
  207. package/src/config/schema/magic-context.ts +105 -137
  208. package/src/config/variable.ts +21 -12
  209. package/src/shared/auto-search-lifecycle.ts +27 -0
  210. package/src/shared/cache-ttl-display.test.ts +1 -1
  211. package/src/shared/cache-ttl-display.ts +17 -16
  212. package/src/shared/model-cache-ttl.test.ts +2 -2
  213. package/src/shared/model-cache-ttl.ts +8 -4
  214. package/src/shared/model-resolution.test.ts +22 -32
  215. package/src/shared/model-resolution.ts +13 -18
  216. package/src/shared/model-suggestion-retry.ts +63 -7
  217. package/src/shared/protected-tools-policy.ts +25 -0
  218. package/src/shared/provider-output-failure.ts +176 -0
  219. package/src/shared/status-detail-text.test.ts +1 -1
  220. package/src/shared/status-view.test.ts +1 -1
  221. package/src/shared/temporal-upgrade-fixture.ts +50 -0
  222. package/src/shared/window-geometry.ts +10 -0
  223. package/src/shared/write-transaction-timing.ts +25 -1
  224. package/dist/features/magic-context/window-report-ledger.d.ts +0 -70
  225. package/dist/features/magic-context/window-report-ledger.d.ts.map +0 -1
  226. package/dist/index-e1q8002h.js +0 -16
  227. package/src/config/misplaced-agent-model.test.ts +0 -55
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  DEFAULT_EXECUTE_THRESHOLD_PERCENTAGE,
3
- PER_HARNESS_MIGRATION_INVENTORY,
4
- PER_HARNESS_MODEL_KEYS,
3
+ HISTORIAN_MODEL_RESOLUTION_FIELDS,
4
+ LEGACY_HARNESS_BLOCK_KEYS,
5
5
  } from "./schema/magic-context";
6
6
 
7
7
  /**
@@ -19,11 +19,13 @@ import {
19
19
 
20
20
  /** Hidden agents that run with elevated/autonomous capability. */
21
21
  const HIDDEN_AGENT_KEYS = ["historian"] as const;
22
- const HARNESS_KEYS = PER_HARNESS_MODEL_KEYS;
23
- /** Every historian model-resolution field, including per-harness qualifiers.
24
- * Variant and thinking_level merge onto the user's historian model at resolve
25
- * time, so leaving them would let a cloned repo force extra spend. */
26
- const HISTORIAN_USER_ONLY_FIELDS = PER_HARNESS_MIGRATION_INVENTORY.historian.migrated_execution;
22
+ /** Removed per-harness historian blocks; iterated only to keep stripping
23
+ * escalation fields out of a legacy block a cloned repo might still supply. */
24
+ const HARNESS_KEYS = LEGACY_HARNESS_BLOCK_KEYS;
25
+ /** Every flat historian model-resolution field, including the per-harness
26
+ * qualifiers. Variant and thinking_level merge onto the user's historian model
27
+ * at resolve time, so leaving them would let a cloned repo force extra spend. */
28
+ const HISTORIAN_USER_ONLY_FIELDS = HISTORIAN_MODEL_RESOLUTION_FIELDS;
27
29
  const PROMPT_SURFACE_USER_ONLY_FIELDS = ["guidance_override_path", "tool_descriptions"] as const;
28
30
 
29
31
  /**
@@ -316,11 +318,10 @@ function makeProjectThresholdWarning(field: string, reason: string): string {
316
318
  * owner-private to group-readable changes every session and memory's local
317
319
  * confidentiality. Only the machine operator's user config may opt into an
318
320
  * externally managed trusted-group deployment.
319
- * - historian model-resolution fields (model, fallback_models, variant,
320
- * thinking_level), including both per-harness blocks — historian model
321
- * spend is user-level only. Qualifiers merge onto the user's historian
322
- * model at resolve time, so a cloned repo cannot force extra thinking or
323
- * variant cost.
321
+ * - historian flat model-resolution fields (model, fallback_models, variant,
322
+ * thinking_level), plus any legacy per-harness block — historian model spend
323
+ * is user-level only. Qualifiers merge onto the user's historian model at
324
+ * resolve time, so a cloned repo cannot force extra thinking or variant cost.
324
325
  * - `pi.subagent_extensions` — a cloned repo must not choose which extensions
325
326
  * the user's Pi child processes load.
326
327
  * - `prompt_surface.guidance_override_path` / `tool_descriptions` — a repository
@@ -177,7 +177,7 @@ describe("protected_tokens config and derivation", () => {
177
177
 
178
178
  describe("root configuration reference", () => {
179
179
  const reference = readFileSync(
180
- resolve(import.meta.dir, "../../../../CONFIGURATION.md"),
180
+ resolve(import.meta.dir, "../../../../docs/guides/配置说明.md"),
181
181
  "utf8",
182
182
  );
183
183
 
@@ -1,389 +1,66 @@
1
1
  import { afterEach, describe, expect, it } from "bun:test";
2
- import {
3
- chmodSync,
4
- lstatSync,
5
- readdirSync,
6
- readFileSync,
7
- readlinkSync,
8
- rmSync,
9
- statSync,
10
- symlinkSync,
11
- writeFileSync,
12
- } from "node:fs";
2
+ import { existsSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
13
3
  import { tmpdir } from "node:os";
14
4
  import { join } from "node:path";
15
5
 
16
- import { parseJsonc } from "../shared/jsonc-parser";
17
6
  import { createTestTempDirFromPath } from "../shared/test-temp-dir";
18
- import { hasFlatKeys, loadRawConfigFile, migrateFlatDetailed } from "./raw-loader";
19
- import { PER_HARNESS_MIGRATION_INVENTORY } from "./schema/magic-context";
7
+ import { loadRawConfigFile } from "./raw-loader";
20
8
 
21
9
  const temporaryDirectories: string[] = [];
22
10
 
23
11
  function temporaryDirectory(): string {
24
- const directory = createTestTempDirFromPath(join(tmpdir(), "mc-per-harness-"));
12
+ const directory = createTestTempDirFromPath(join(tmpdir(), "mc-raw-loader-"));
25
13
  temporaryDirectories.push(directory);
26
14
  return directory;
27
15
  }
28
16
 
29
- function parse(text: string): Record<string, unknown> {
30
- const withoutBom = text.startsWith("\uFEFF") ? text.slice(1) : text;
31
- return parseJsonc<Record<string, unknown>>(withoutBom);
32
- }
33
-
34
17
  afterEach(() => {
35
18
  for (const directory of temporaryDirectories.splice(0)) {
36
19
  rmSync(directory, { recursive: true, force: true });
37
20
  }
38
21
  });
39
22
 
40
- describe("per-harness raw config migration", () => {
41
- it("maps flat agent and task fields without moving scheduling fields", () => {
42
- const input = `\uFEFF{\r
43
- // preserve this comment\r
44
- "historian": {\r
45
- "model": { "model": "provider/historian", "variant": "fast", "thinking_level": "high" }, // historian model note\r
46
- "fallback_models": [{ "model": "provider/fallback", "variant": "careful", "thinking_level": "low" }],\r
47
- "variant": "block-variant",\r
48
- "thinking_level": "medium"\r
49
- },\r
50
- "unrelated": { "keep": true }\r
51
- }\r
52
- `;
53
-
54
- const migrated = migrateFlatDetailed(Buffer.from(input, "utf-8"));
55
- const repeated = migrateFlatDetailed(Buffer.from(input, "utf-8"));
56
- const idempotent = migrateFlatDetailed(migrated.bytes);
57
- const output = migrated.bytes.toString("utf-8");
58
- const config = parse(output);
59
- const historian = config.historian as Record<string, unknown>;
60
- const opencodeHistorian = historian.opencode as Record<string, unknown>;
61
- const piHistorian = historian.pi as Record<string, unknown>;
62
-
63
- expect(migrated.hasFlatKeys).toBe(true);
64
- expect(repeated.bytes).toEqual(migrated.bytes);
65
- expect(repeated.diagnostics).toEqual(migrated.diagnostics);
66
- expect(idempotent.bytes).toEqual(migrated.bytes);
67
- expect(idempotent.hasFlatKeys).toBe(false);
68
- expect(idempotent.diagnostics).toEqual([]);
69
- expect(hasFlatKeys(migrated.bytes)).toBe(false);
70
- expect(output.startsWith("\uFEFF")).toBe(true);
71
- expect(output).toContain("// preserve this comment");
72
- expect(output).toContain("// historian model note");
73
- expect(output).toContain("\r\n");
74
- expect(config.unrelated).toEqual({ keep: true });
75
- expect(historian.model).toBeUndefined();
76
- expect(opencodeHistorian).toEqual({
77
- model: { model: "provider/historian", variant: "fast" },
78
- fallback_models: [{ model: "provider/fallback", variant: "careful" }],
79
- variant: "block-variant",
80
- });
81
- expect(piHistorian).toEqual({
82
- model: { model: "provider/historian", thinking_level: "high" },
83
- fallback_models: [{ model: "provider/fallback", thinking_level: "low" }],
84
- thinking_level: "medium",
85
- });
86
- });
87
-
88
- it("turns a singleton fallback into one entry without changing its spelling", () => {
89
- const migrated = migrateFlatDetailed(
90
- Buffer.from(
91
- JSON.stringify({
92
- historian: { fallback_models: "Vendor/Model:Exact-Spelling" },
93
- }),
94
- ),
95
- );
96
- const historian = parse(migrated.bytes.toString("utf-8")).historian as Record<
97
- string,
98
- Record<string, unknown>
99
- >;
100
-
101
- expect(historian.opencode.fallback_models).toEqual(["Vendor/Model:Exact-Spelling"]);
102
- expect(historian.pi.fallback_models).toEqual(["Vendor/Model:Exact-Spelling"]);
103
- });
104
-
105
- it("preserves every schema-enumerated retained field at its original level", () => {
106
- const historianRetained = {
107
- temperature: 0.1,
108
- top_p: 0.9,
109
- prompt: "historian prompt",
110
- tools: { read: true },
111
- disable: false,
112
- description: "historian description",
113
- mode: "subagent",
114
- color: "blue",
115
- maxSteps: 11,
116
- permission: { edit: "deny" },
117
- maxTokens: 4096,
118
- two_pass: true,
119
- disallowed_tools: ["bash"],
120
- };
121
- expect(Object.keys(historianRetained)).toEqual(
122
- PER_HARNESS_MIGRATION_INVENTORY.historian.retained,
123
- );
124
-
125
- const migrated = migrateFlatDetailed(
126
- Buffer.from(
127
- JSON.stringify({
128
- historian: { ...historianRetained, model: "provider/historian" },
129
- }),
130
- ),
131
- );
132
- const config = parse(migrated.bytes.toString("utf-8"));
133
- const historian = config.historian as Record<string, unknown>;
134
-
135
- for (const [field, value] of Object.entries(historianRetained)) {
136
- expect(historian[field]).toEqual(value);
137
- }
138
- });
139
-
140
- it("fills absent destinations and silently removes equal flat sources", () => {
141
- const migrated = migrateFlatDetailed(
142
- Buffer.from(`{
143
- "historian": {
144
- "model": "provider/same",
145
- "opencode": { "model": "provider/same" },
146
- "pi": {}
147
- }
148
- }`),
149
- );
150
- const historian = parse(migrated.bytes.toString("utf-8")).historian as Record<
151
- string,
152
- unknown
153
- >;
154
-
155
- expect(historian.model).toBeUndefined();
156
- expect(historian.opencode).toEqual({ model: "provider/same" });
157
- expect(historian.pi).toEqual({ model: "provider/same" });
158
- expect(migrated.diagnostics).toEqual([]);
159
- });
160
-
161
- it("keeps new-shape destinations on conflicts and names both paths in warnings", () => {
162
- const input = `{
163
- "historian": {
164
- "model": "provider/flat",
165
- "fallback_models": "provider/fallback",
166
- "opencode": { "model": "provider/opencode", "fallback_models": ["provider/fallback"] },
167
- "pi": { "model": "provider/pi", "fallback_models": ["provider/fallback"] }
168
- }
169
- }`;
170
-
171
- const migrated = migrateFlatDetailed(Buffer.from(input));
172
- const config = parse(migrated.bytes.toString("utf-8"));
173
- const historian = config.historian as Record<string, unknown>;
174
-
175
- expect(historian.model).toBeUndefined();
176
- expect(historian.fallback_models).toBeUndefined();
177
- expect(historian.opencode).toEqual({
178
- model: "provider/opencode",
179
- fallback_models: ["provider/fallback"],
180
- });
181
- expect(historian.pi).toEqual({
182
- model: "provider/pi",
183
- fallback_models: ["provider/fallback"],
184
- });
185
- expect(migrated.diagnostics.map((diagnostic) => diagnostic.path)).toEqual([
186
- "historian.model",
187
- "historian.model",
188
- ]);
189
- expect(migrated.diagnostics[0]?.message).toContain("historian.opencode.model");
190
- expect(migrated.diagnostics[1]?.message).toContain("historian.pi.model");
191
- });
192
-
193
- it("processes flat keys re-added after migration while existing blocks keep winning", () => {
194
- const first = migrateFlatDetailed(
195
- Buffer.from(JSON.stringify({ historian: { model: "provider/original" } })),
196
- );
197
- const reintroduced = parse(first.bytes.toString("utf-8"));
198
- (reintroduced.historian as Record<string, unknown>).model = "provider/re-added";
199
-
200
- const second = migrateFlatDetailed(Buffer.from(JSON.stringify(reintroduced)));
201
- const historian = parse(second.bytes.toString("utf-8")).historian as Record<
202
- string,
203
- unknown
204
- >;
205
-
206
- expect(historian.model).toBeUndefined();
207
- expect(historian.opencode).toEqual({ model: "provider/original" });
208
- expect(historian.pi).toEqual({ model: "provider/original" });
209
- expect(second.diagnostics.map((diagnostic) => diagnostic.message)).toEqual([
210
- expect.stringContaining("historian.opencode.model"),
211
- expect.stringContaining("historian.pi.model"),
212
- ]);
213
- });
214
-
215
- it("rewrites user config atomically with one exact-byte restricted backup", () => {
23
+ describe("loadRawConfigFile", () => {
24
+ it("returns the file byte-for-byte without rewriting it", () => {
216
25
  const directory = temporaryDirectory();
217
26
  const configPath = join(directory, "mmc.jsonc");
218
- const original = Buffer.from('{\n "historian": { "model": "provider/model" }\n}\n');
219
- writeFileSync(configPath, original);
220
- chmodSync(configPath, 0o600);
221
- const expected = migrateFlatDetailed(original).bytes;
222
- let temporaryNamesDuringWrite: string[] = [];
223
-
224
- const first = loadRawConfigFile({
225
- configPath,
226
- tier: "user",
227
- afterTemporaryWrite: () => {
228
- expect(readFileSync(configPath)).toEqual(original);
229
- temporaryNamesDuringWrite = readdirSync(directory).filter((name) =>
230
- name.endsWith(".tmp"),
231
- );
232
- expect(temporaryNamesDuringWrite).toHaveLength(1);
233
- expect(
234
- readFileSync(join(directory, temporaryNamesDuringWrite[0] as string)),
235
- ).toEqual(expected);
236
- },
237
- });
238
- const second = loadRawConfigFile({ configPath, tier: "user" });
239
- const backupPath = `${configPath}.pre-per-harness.bak`;
240
-
241
- expect(first?.migrated).toBe(true);
242
- expect(first?.warnings).toContain(
243
- "Migrated flat historian model config to per-harness blocks.",
244
- );
245
- expect(readFileSync(backupPath)).toEqual(original);
246
- expect(readFileSync(configPath)).toEqual(first?.bytes);
247
- expect(temporaryNamesDuringWrite).toHaveLength(1);
248
- expect(readdirSync(directory).filter((name) => name.endsWith(".tmp"))).toEqual([]);
249
- expect(statSync(configPath).mode & 0o777).toBe(0o600);
250
- expect(statSync(backupPath).mode & 0o777).toBe(0o600);
251
- expect(second?.migrated).toBe(false);
252
- expect(second?.warnings).toEqual([]);
253
- });
254
-
255
- it("repairs a truncated migration backup left by the direct-write protocol", () => {
256
- const directory = temporaryDirectory();
257
- const configPath = join(directory, "mmc.jsonc");
258
- const backupPath = `${configPath}.pre-per-harness.bak`;
259
- const original = Buffer.from('{ "historian": { "model": "provider/model" } }\n');
260
- writeFileSync(configPath, original);
261
- writeFileSync(backupPath, original.subarray(0, 17));
27
+ const original = `{\n // keep this comment\n "historian": { "model": "provider/model" }\n}\n`;
28
+ writeFileSync(configPath, original, "utf-8");
29
+ const before = readFileSync(configPath);
262
30
 
263
31
  const loaded = loadRawConfigFile({ configPath, tier: "user" });
264
32
 
265
- expect(loaded?.migrated).toBe(true);
266
- expect(readFileSync(backupPath)).toEqual(original);
267
- });
268
-
269
- it("leaves a completed migration backup with different bytes untouched", () => {
270
- const directory = temporaryDirectory();
271
- const configPath = join(directory, "mmc.jsonc");
272
- const backupPath = `${configPath}.pre-per-harness.bak`;
273
- const sentinel = Buffer.from("not config: backup is opaque and permanent\n");
274
- writeFileSync(configPath, '{ "historian": { "model": "provider/model" } }');
275
- writeFileSync(backupPath, sentinel);
276
-
277
- const first = loadRawConfigFile({ configPath, tier: "user" });
278
- const second = loadRawConfigFile({ configPath, tier: "user" });
279
-
280
- expect(first?.migrated).toBe(true);
281
- expect(second?.migrated).toBe(false);
282
- expect(readFileSync(backupPath)).toEqual(sentinel);
283
- });
284
-
285
- it("accepts a matching migration backup without rewriting it", () => {
286
- const directory = temporaryDirectory();
287
- const configPath = join(directory, "mmc.jsonc");
288
- const backupPath = `${configPath}.pre-per-harness.bak`;
289
- const original = Buffer.from('{ "historian": { "model": "provider/model" } }\n');
290
- writeFileSync(configPath, original);
291
- writeFileSync(backupPath, original);
292
- const backupInode = statSync(backupPath).ino;
293
-
294
- const loaded = loadRawConfigFile({ configPath, tier: "user" });
295
-
296
- expect(loaded?.migrated).toBe(true);
297
- expect(readFileSync(backupPath)).toEqual(original);
298
- expect(statSync(backupPath).ino).toBe(backupInode);
299
- });
300
-
301
- it("creates user migration backups and replacement configs owner-only", () => {
302
- if (process.platform === "win32") return;
303
- const directory = temporaryDirectory();
304
- const configPath = join(directory, "mmc.jsonc");
305
- const original = Buffer.from('{ "historian": { "model": "provider/model" } }\n');
306
- writeFileSync(configPath, original);
307
- chmodSync(configPath, 0o644);
308
-
309
- const loaded = loadRawConfigFile({ configPath, tier: "user" });
33
+ expect(loaded).not.toBeNull();
34
+ expect(loaded?.text).toBe(original);
35
+ expect(loaded?.bytes.equals(before)).toBe(true);
36
+ expect(loaded?.warnings).toEqual([]);
37
+ expect(loaded?.migrated).toBe(false);
310
38
 
311
- expect(loaded?.migrated).toBe(true);
312
- expect(statSync(configPath).mode & 0o777).toBe(0o600);
313
- expect(statSync(`${configPath}.pre-per-harness.bak`).mode & 0o777).toBe(0o600);
39
+ // No rewrite, no `.pre-per-harness.bak` backup, no temp file left behind.
40
+ expect(readFileSync(configPath).equals(before)).toBe(true);
41
+ expect(existsSync(`${configPath}.pre-per-harness.bak`)).toBe(false);
42
+ expect(readdirSync(directory)).toEqual(["mmc.jsonc"]);
314
43
  });
315
44
 
316
- // stow, chezmoi and home-manager link the user config into a dotfiles repo.
317
- // Replacing the link with a regular file would silently stop later edits
318
- // from reaching that repo, so the migration writes through to the target.
319
- it("keeps a symlinked user config a symlink and migrates its target", () => {
45
+ it("does not rewrite a file that still uses the removed per-harness blocks", () => {
320
46
  const directory = temporaryDirectory();
321
- const dotfiles = temporaryDirectory();
322
- const targetPath = join(dotfiles, "mmc.jsonc");
323
47
  const configPath = join(directory, "mmc.jsonc");
324
- const original = Buffer.from('{ "historian": { "model": "provider/model" } }\n');
325
- writeFileSync(targetPath, original);
326
- chmodSync(targetPath, 0o600);
327
- symlinkSync(targetPath, configPath);
48
+ const original = `{\n "historian": { "opencode": { "model": "provider/model" } }\n}\n`;
49
+ writeFileSync(configPath, original, "utf-8");
50
+ const before = readFileSync(configPath);
328
51
 
329
52
  const loaded = loadRawConfigFile({ configPath, tier: "user" });
330
53
 
331
- expect(loaded?.migrated).toBe(true);
332
- expect(lstatSync(configPath).isSymbolicLink()).toBe(true);
333
- expect(readlinkSync(configPath)).toBe(targetPath);
334
- expect(readFileSync(targetPath)).toEqual(loaded?.bytes);
335
- expect(hasFlatKeys(readFileSync(targetPath))).toBe(false);
336
- expect(statSync(targetPath).mode & 0o777).toBe(0o600);
337
- expect(readFileSync(`${configPath}.pre-per-harness.bak`)).toEqual(original);
338
- expect(readdirSync(dotfiles).filter((name) => name.endsWith(".tmp"))).toEqual([]);
339
- expect(readdirSync(directory).filter((name) => name.endsWith(".tmp"))).toEqual([]);
340
- });
341
-
342
- it("reloads the winning candidate when a concurrent loader replaces the target", () => {
343
- const directory = temporaryDirectory();
344
- const configPath = join(directory, "mmc.jsonc");
345
- const original = Buffer.from('{\n "historian": { "model": "provider/model" }\n}\n');
346
- writeFileSync(configPath, original);
347
-
348
- let competing: ReturnType<typeof loadRawConfigFile> | undefined;
349
- const first = loadRawConfigFile({
350
- configPath,
351
- tier: "user",
352
- afterTemporaryWrite: () => {
353
- competing ??= loadRawConfigFile({ configPath, tier: "user" });
354
- },
355
- });
356
-
357
- expect([first?.migrated, competing?.migrated].filter(Boolean)).toHaveLength(1);
358
- expect(competing?.migrated).toBe(true);
359
- expect(first?.migrated).toBe(false);
360
- expect(first?.warnings).toEqual([]);
361
- expect(competing?.warnings).toEqual([
362
- "Migrated flat historian model config to per-harness blocks.",
363
- ]);
364
- expect(first?.bytes).toEqual(competing?.bytes);
365
- expect(parse(first?.text ?? "{}")).toEqual(parse(competing?.text ?? "{}"));
366
- expect(readFileSync(configPath)).toEqual(competing?.bytes);
367
- expect(readFileSync(`${configPath}.pre-per-harness.bak`)).toEqual(original);
54
+ expect(loaded?.text).toBe(original);
55
+ expect(loaded?.migrated).toBe(false);
56
+ expect(readFileSync(configPath).equals(before)).toBe(true);
57
+ expect(readdirSync(directory)).toEqual(["mmc.jsonc"]);
368
58
  });
369
59
 
370
- it("adapts project config in memory without changing bytes or mtime", () => {
60
+ it("returns null when the file does not exist", () => {
371
61
  const directory = temporaryDirectory();
372
- const configPath = join(directory, "mmc.jsonc");
373
- const original = Buffer.from('{\n "historian": { "model": "provider/model" }\n}\n');
374
- writeFileSync(configPath, original);
375
- const before = statSync(configPath);
376
-
377
- const loaded = loadRawConfigFile({ configPath, tier: "project" });
378
- const after = statSync(configPath);
379
- const config = parse(loaded?.text ?? "{}");
380
-
381
- expect(loaded?.migrated).toBe(false);
382
- expect(loaded?.warnings[0]).toContain("Adapted flat model config in memory");
383
- expect(readFileSync(configPath)).toEqual(original);
384
- expect(after.mtimeMs).toBe(before.mtimeMs);
385
- expect((config.historian as Record<string, unknown>).opencode).toEqual({
386
- model: "provider/model",
387
- });
62
+ expect(
63
+ loadRawConfigFile({ configPath: join(directory, "absent.jsonc"), tier: "user" }),
64
+ ).toBeNull();
388
65
  });
389
66
  });