harnery 0.12.1 → 0.12.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,6 +13,14 @@
13
13
  */
14
14
  export declare function isSafeInstanceId(value: unknown): value is string;
15
15
  export declare function assertSafeInstanceId(value: unknown): asserts value is string;
16
+ /**
17
+ * Resolve one direct-child filename beneath a trusted coordination directory.
18
+ *
19
+ * This is a second boundary behind the instance-ID allowlist. Normalizing the
20
+ * candidate and proving its directory prefix prevents traversal even if a new
21
+ * caller constructs a filename from a different untrusted source.
22
+ */
23
+ export declare function resolveContainedFile(directory: string, fileName: string): string;
16
24
  export interface Heartbeat {
17
25
  instance_id: string;
18
26
  name?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"coord-client.d.ts","sourceRoot":"","sources":["../../../src/core/agents/coord-client.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAWH;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAWhE;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,MAAM,CAI5E;AAED,MAAM,WAAW,SAAS;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oGAAoG;IACpG,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,4EAA4E;IAC5E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,0IAA0I;IAC1I,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,sFAAsF;IACtF,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,mEAAmE;IACnE,uBAAuB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,IAAI,MAAM,GAAG,IAAI,CA0C5C;AAED,qEAAqE;AACrE,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAIvD;AAED,+EAA+E;AAC/E,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAI1D;AAaD;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,IAAI,MAAM,GAAG,IAAI,CAE5C;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,IAAI;IACxC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,KAAK,GAAG,QAAQ,GAAG,iBAAiB,GAAG,aAAa,GAAG,kBAAkB,GAAG,MAAM,CAAC;CAC5F,CAkEA;AA4CD;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA2BpE;AAED;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAyBpE;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAKlE;AAgBD;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAOtD;AAED;;;GAGG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAIxC"}
1
+ {"version":3,"file":"coord-client.d.ts","sourceRoot":"","sources":["../../../src/core/agents/coord-client.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAWH;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAWhE;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,MAAM,CAI5E;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAOhF;AAED,MAAM,WAAW,SAAS;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oGAAoG;IACpG,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,4EAA4E;IAC5E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,0IAA0I;IAC1I,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,sFAAsF;IACtF,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,mEAAmE;IACnE,uBAAuB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,IAAI,MAAM,GAAG,IAAI,CA0C5C;AAED,qEAAqE;AACrE,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAIvD;AAED,+EAA+E;AAC/E,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAI1D;AAaD;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,IAAI,MAAM,GAAG,IAAI,CAE5C;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,IAAI;IACxC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,KAAK,GAAG,QAAQ,GAAG,iBAAiB,GAAG,aAAa,GAAG,kBAAkB,GAAG,MAAM,CAAC;CAC5F,CAkEA;AA4CD;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA2BpE;AAED;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAyBpE;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAMlE;AAgBD;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAOtD;AAED;;;GAGG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAIxC"}
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import { spawnSync } from "node:child_process";
10
10
  import { existsSync, readdirSync, readFileSync } from "node:fs";
11
- import { resolve } from "node:path";
11
+ import { dirname, resolve, sep } from "node:path";
12
12
  // NOTE: kept dependency-free (node builtins only); this file is vendored verbatim into
13
13
  // a downstream consumer, so it cannot import the coordEnv helper.
14
14
  const MAX_INSTANCE_ID_LENGTH = 128;
@@ -35,6 +35,21 @@ export function assertSafeInstanceId(value) {
35
35
  throw new Error("instance_id must be 1-128 ASCII letters, digits, hyphens, or underscores");
36
36
  }
37
37
  }
38
+ /**
39
+ * Resolve one direct-child filename beneath a trusted coordination directory.
40
+ *
41
+ * This is a second boundary behind the instance-ID allowlist. Normalizing the
42
+ * candidate and proving its directory prefix prevents traversal even if a new
43
+ * caller constructs a filename from a different untrusted source.
44
+ */
45
+ export function resolveContainedFile(directory, fileName) {
46
+ const root = resolve(directory);
47
+ const candidate = resolve(root, fileName);
48
+ if (!candidate.startsWith(`${root}${sep}`) || dirname(candidate) !== root) {
49
+ throw new Error("coordination filename must resolve directly beneath its root");
50
+ }
51
+ return candidate;
52
+ }
38
53
  /**
39
54
  * Resolve the monorepo root for coord-state purposes.
40
55
  *
@@ -334,7 +349,8 @@ export function readHeartbeat(instanceId) {
334
349
  const root = monorepoRoot();
335
350
  if (!root)
336
351
  return null;
337
- return readJsonHeartbeatFile(resolve(root, ".harnery", "active", `${instanceId}.json`));
352
+ const path = resolveContainedFile(resolve(root, ".harnery", "active"), `${instanceId}.json`);
353
+ return readJsonHeartbeatFile(path);
338
354
  }
339
355
  function readJsonHeartbeatFile(path) {
340
356
  if (!existsSync(path))
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/scratch/index.ts"],"names":[],"mappings":"AAmBA;;;;;;;;;;;;GAYG;AAEH,eAAO,MAAM,kBAAkB,iFAQrB,CAAC;AAEX,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC;AAElE,eAAO,MAAM,iBAAiB,QAAY,CAAC;AAC3C,eAAO,MAAM,kBAAkB,QAAY,CAAC;AAC5C,eAAO,MAAM,sBAAsB,IAAI,CAAC;AAExC,6DAA6D;AAC7D,eAAO,MAAM,eAAe,QACyF,CAAC;AAEtH,mFAAmF;AACnF,eAAO,MAAM,wBAAwB,yBAAyB,CAAC;AAE/D,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,eAAe,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;CACf;AAID,wBAAgB,UAAU,IAAI,MAAM,CAMnC;AAED,wBAAgB,UAAU,IAAI,MAAM,CAInC;AAED,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAGtD;AAeD,8EAA8E;AAC9E,wBAAgB,aAAa,CAAC,CAAC,GAAE,IAAiB,GAAG,MAAM,CAW1D;AAID,wEAAwE;AACxE,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,CAwEtE;AAED,+CAA+C;AAC/C,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,UAAU,GAAG,MAAM,CAkBxD;AAmBD,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,SAAoB,GAAG,SAAS,EAAE,CA2DrF;AAID,wBAAgB,WAAW,CACzB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,eAAe,EACzB,IAAI,EAAE,MAAM,GACX,UAAU,CAgEZ;AAID,wGAAwG;AACxG,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQhE;AAED,iEAAiE;AACjE,wBAAgB,aAAa,CAAC,IAAI,SAAyB,GAAG,MAAM,CAkBnE;AAED,uFAAuF;AACvF,wBAAgB,sBAAsB,IAAI,MAAM,EAAE,CAajD;AAED,qDAAqD;AACrD,wBAAgB,YAAY,IAAI;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf,EAAE,CAWF;AAID,wBAAgB,mBAAmB,IAAI,MAAM,CAM5C;AAED,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAIjE;AAED,2FAA2F;AAC3F,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAoB9D"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/scratch/index.ts"],"names":[],"mappings":"AAoBA;;;;;;;;;;;;GAYG;AAEH,eAAO,MAAM,kBAAkB,iFAQrB,CAAC;AAEX,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC;AAElE,eAAO,MAAM,iBAAiB,QAAY,CAAC;AAC3C,eAAO,MAAM,kBAAkB,QAAY,CAAC;AAC5C,eAAO,MAAM,sBAAsB,IAAI,CAAC;AAExC,6DAA6D;AAC7D,eAAO,MAAM,eAAe,QACyF,CAAC;AAEtH,mFAAmF;AACnF,eAAO,MAAM,wBAAwB,yBAAyB,CAAC;AAE/D,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,eAAe,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;CACf;AAID,wBAAgB,UAAU,IAAI,MAAM,CAMnC;AAED,wBAAgB,UAAU,IAAI,MAAM,CAInC;AAED,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAGtD;AAeD,8EAA8E;AAC9E,wBAAgB,aAAa,CAAC,CAAC,GAAE,IAAiB,GAAG,MAAM,CAW1D;AAID,wEAAwE;AACxE,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,CAwEtE;AAED,+CAA+C;AAC/C,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,UAAU,GAAG,MAAM,CAkBxD;AAmBD,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,SAAoB,GAAG,SAAS,EAAE,CA2DrF;AAID,wBAAgB,WAAW,CACzB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,eAAe,EACzB,IAAI,EAAE,MAAM,GACX,UAAU,CAgEZ;AAID,wGAAwG;AACxG,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQhE;AAED,iEAAiE;AACjE,wBAAgB,aAAa,CAAC,IAAI,SAAyB,GAAG,MAAM,CAkBnE;AAED,uFAAuF;AACvF,wBAAgB,sBAAsB,IAAI,MAAM,EAAE,CAajD;AAED,qDAAqD;AACrD,wBAAgB,YAAY,IAAI;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf,EAAE,CAWF;AAID,wBAAgB,mBAAmB,IAAI,MAAM,CAM5C;AAED,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAIjE;AAED,2FAA2F;AAC3F,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAoB9D"}
@@ -1,7 +1,7 @@
1
1
  import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
2
2
  import { dirname, resolve } from "node:path";
3
3
  import { resolveMachineLabel } from "../../lib/machine.js";
4
- import { assertSafeInstanceId, monorepoRoot, readHeartbeat, resolveOwner, } from "../agents/index.js";
4
+ import { assertSafeInstanceId, monorepoRoot, readHeartbeat, resolveContainedFile, resolveOwner, } from "../agents/index.js";
5
5
  /**
6
6
  * Agent scratchpad: per-agent markdown journal at `.harnery/scratch/<instance_id>.md`.
7
7
  *
@@ -47,7 +47,7 @@ export function archiveDir() {
47
47
  }
48
48
  export function scratchPath(instanceId) {
49
49
  assertSafeInstanceId(instanceId);
50
- return resolve(scratchDir(), `${instanceId}.md`);
50
+ return resolveContainedFile(scratchDir(), `${instanceId}.md`);
51
51
  }
52
52
  // ─── Chicago time formatting ──────────────────────────────────────────────
53
53
  const CHICAGO_FORMATTER = new Intl.DateTimeFormat("en-US", {
@@ -307,7 +307,7 @@ export function archiveScratch(instanceId) {
307
307
  if (!existsSync(src))
308
308
  return null;
309
309
  const ts = new Date().toISOString().replace(/[:.]/g, "-");
310
- const dest = resolve(archiveDir(), `${instanceId}-${ts}.md`);
310
+ const dest = resolveContainedFile(archiveDir(), `${instanceId}-${ts}.md`);
311
311
  renameSync(src, dest);
312
312
  return dest;
313
313
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "harnery",
3
- "version": "0.12.1",
3
+ "version": "0.12.2",
4
4
  "description": "Multi-agent coordination for AI coding agents - Claude Code, Cursor, and Codex.",
5
5
  "license": "MIT",
6
6
  "author": "Ryan Kelly",
@@ -9,7 +9,7 @@
9
9
 
10
10
  import { spawnSync } from "node:child_process";
11
11
  import { existsSync, readdirSync, readFileSync } from "node:fs";
12
- import { resolve } from "node:path";
12
+ import { dirname, resolve, sep } from "node:path";
13
13
 
14
14
  // NOTE: kept dependency-free (node builtins only); this file is vendored verbatim into
15
15
  // a downstream consumer, so it cannot import the coordEnv helper.
@@ -40,6 +40,22 @@ export function assertSafeInstanceId(value: unknown): asserts value is string {
40
40
  }
41
41
  }
42
42
 
43
+ /**
44
+ * Resolve one direct-child filename beneath a trusted coordination directory.
45
+ *
46
+ * This is a second boundary behind the instance-ID allowlist. Normalizing the
47
+ * candidate and proving its directory prefix prevents traversal even if a new
48
+ * caller constructs a filename from a different untrusted source.
49
+ */
50
+ export function resolveContainedFile(directory: string, fileName: string): string {
51
+ const root = resolve(directory);
52
+ const candidate = resolve(root, fileName);
53
+ if (!candidate.startsWith(`${root}${sep}`) || dirname(candidate) !== root) {
54
+ throw new Error("coordination filename must resolve directly beneath its root");
55
+ }
56
+ return candidate;
57
+ }
58
+
43
59
  export interface Heartbeat {
44
60
  instance_id: string;
45
61
  name?: string;
@@ -369,7 +385,8 @@ export function readHeartbeat(instanceId: string): Heartbeat | null {
369
385
  if (!isSafeInstanceId(instanceId)) return null;
370
386
  const root = monorepoRoot();
371
387
  if (!root) return null;
372
- return readJsonHeartbeatFile(resolve(root, ".harnery", "active", `${instanceId}.json`));
388
+ const path = resolveContainedFile(resolve(root, ".harnery", "active"), `${instanceId}.json`);
389
+ return readJsonHeartbeatFile(path);
373
390
  }
374
391
 
375
392
  function readJsonHeartbeatFile(path: string): Heartbeat | null {
@@ -14,6 +14,7 @@ import {
14
14
  assertSafeInstanceId,
15
15
  monorepoRoot,
16
16
  readHeartbeat,
17
+ resolveContainedFile,
17
18
  resolveOwner,
18
19
  } from "../agents/index.ts";
19
20
 
@@ -94,7 +95,7 @@ export function archiveDir(): string {
94
95
 
95
96
  export function scratchPath(instanceId: string): string {
96
97
  assertSafeInstanceId(instanceId);
97
- return resolve(scratchDir(), `${instanceId}.md`);
98
+ return resolveContainedFile(scratchDir(), `${instanceId}.md`);
98
99
  }
99
100
 
100
101
  // ─── Chicago time formatting ──────────────────────────────────────────────
@@ -385,7 +386,7 @@ export function archiveScratch(instanceId: string): string | null {
385
386
  const src = scratchPath(instanceId);
386
387
  if (!existsSync(src)) return null;
387
388
  const ts = new Date().toISOString().replace(/[:.]/g, "-");
388
- const dest = resolve(archiveDir(), `${instanceId}-${ts}.md`);
389
+ const dest = resolveContainedFile(archiveDir(), `${instanceId}-${ts}.md`);
389
390
  renameSync(src, dest);
390
391
  return dest;
391
392
  }