From 12887107a639c47cc2372d32e8b2df3554616565 Mon Sep 17 00:00:00 2001 From: Lan Nguyen Si Date: Wed, 5 Aug 2026 12:51:04 +0200 Subject: [PATCH 1/3] fix(memory-sync): template carries machine-state entry, guard test derives profiles structurally linux.example.json now includes a placeholder machine-state syncPaths entry (mirroring linux.json/macbook.json's lockstep form) so a fourth machine copied from the template starts with it present instead of repeating the hand-patch divergence closed for the third machine. The cross-machine guard test derives its profile file list via readdirSync instead of a hardcoded array, so the machine-state loop now runs over every committed profile including the template, and a new invariant loop pins rootDir/stateDir as absolute, un-expanded, and stateDir outside rootDir across all profiles. docs/machine-setup.md sections c/e/f updated to match; no more "template does not yet include a machine-state entry" language. --- .../agent-memory-sync/docs/machine-setup.md | 24 ++-- .../profiles/linux.example.json | 14 +++ .../cross-machine-profiles.test.ts | 103 +++++++++++++++--- 3 files changed, 117 insertions(+), 24 deletions(-) diff --git a/packages/agent-memory-sync/docs/machine-setup.md b/packages/agent-memory-sync/docs/machine-setup.md index 3947814..9f580ba 100644 --- a/packages/agent-memory-sync/docs/machine-setup.md +++ b/packages/agent-memory-sync/docs/machine-setup.md @@ -227,7 +227,10 @@ empty/stale local workspace as if it were authoritative. Claude Code slug for `rootDir` — do not guess it from the other profiles' slugs, since it encodes this machine's own OS username and checkout path (see the namespace-divergence note inside any existing - profile file). + profile file). The template's `syncPaths` already includes the + `machine-state` entry (see (e) below) alongside `memory` and + `frictions` — fill in its `` placeholder like the rest + of the file rather than adding the entry by hand. 4. First pull so the machine starts from the mini's current state: ```bash agent-memory-sync run --config profiles/linux.json --mode pull @@ -333,9 +336,10 @@ bullet at the top of this document. ## e) machine-state payload (toolchain snapshots) Every committed machine profile (`profiles/mac-mini.json`, -`profiles/macbook.json`, `profiles/linux.json`) -carries a **second, independent** `syncPaths` entry alongside the `memory` -one described at the top of this document: +`profiles/macbook.json`, `profiles/linux.json`) — and the template, +`profiles/linux.example.json` — carries a **second, independent** +`syncPaths` entry alongside the `memory` one described at the top of this +document: ```json { "source": "/Users//.harness/machine-state", "destination": "machine-state", "kind": "directory" } @@ -350,7 +354,11 @@ treats an absolute `source` as-is instead of resolving it against `rootDir`, so this entry syncs on its own schedule independent of the memory tree; both entries still land under the same shared `pandora/` remote tree (`repositorySubdir`), just under different top-level destinations -(`pandora/memory/...` vs. `pandora/machine-state/...`). +(`pandora/memory/...` vs. `pandora/machine-state/...`). In +`profiles/linux.example.json` the entry's `source` uses the same +`` placeholder as `rootDir`/`stateDir` (see (c) above) — +copying the template and filling in that one placeholder is all a new +machine needs to do here, rather than adding this entry from scratch. **Payload convention — one file per machine, owner-writes-only.** Each machine writes exactly one JSON file under its own `machine-state/`, @@ -403,9 +411,9 @@ exist ahead of any sync. Every committed profile (`profiles/mac-mini.json`, `profiles/macbook.json`, `profiles/linux.json`, `profiles/linux.example.json`) also carries an **independent** `syncPaths` entry pointing at `~/.harness/frictions` — the -third entry in `mac-mini.json`/`macbook.json`/`linux.json` (after `memory` -and `machine-state`), and the second in `linux.example.json` (that template -does not yet include a `machine-state` entry): +third entry in every one of them, after `memory` and `machine-state` (see +(e) above; the template carries a placeholder `machine-state` entry too, so +its `syncPaths` array has the same three-entry shape as the real profiles): ```json { "source": "/Users//.harness/frictions", "destination": "frictions", "kind": "directory" } diff --git a/packages/agent-memory-sync/profiles/linux.example.json b/packages/agent-memory-sync/profiles/linux.example.json index 22b85b1..f73c7a2 100644 --- a/packages/agent-memory-sync/profiles/linux.example.json +++ b/packages/agent-memory-sync/profiles/linux.example.json @@ -51,6 +51,19 @@ "see isHiddenEntryName in src/memory-sync/config.ts and", "src/memory-sync/git-client.ts.", "", + "syncPaths' SECOND entry is machine-state (absolute source", + "/home//.harness/machine-state, destination", + "'machine-state'), carrying this machine's toolchain snapshot for the", + "cross-machine parity check run by the harness companion 'session-start", + "toolchain-parity' — see docs/machine-setup.md section e) for the full", + "payload convention (one file per machine, owner-writes-only) and", + "section f) for the THIRD entry, frictions. It sets \"ownerScoped\": true,", + "matching the real linux.json/macbook.json/mac-mini.json profiles, so", + "push only ever offers this machine's own .json under", + "machine-state/ and never a peer's file this machine merely pulled —", + "see collectLocalSyncFiles' ownerFilter option in", + "src/memory-sync/config.ts.", + "", "stateDir is set OUTSIDE rootDir (a separate ~/.agent-memory-sync tree),", "which matters now that syncPaths covers the whole rootDir: if stateDir", "stayed nested inside rootDir, the recursive walk would also pick up", @@ -82,6 +95,7 @@ "reachabilityTimeoutMs": 5000, "syncPaths": [ { "source": ".", "destination": "memory", "kind": "directory" }, + { "source": "/home//.harness/machine-state", "destination": "machine-state", "kind": "directory", "ownerScoped": true }, { "source": "/home//.harness/frictions", "destination": "frictions", "kind": "directory" } ] } diff --git a/packages/agent-memory-sync/tests/integration/cross-machine-profiles.test.ts b/packages/agent-memory-sync/tests/integration/cross-machine-profiles.test.ts index 05d6542..02e0cd5 100644 --- a/packages/agent-memory-sync/tests/integration/cross-machine-profiles.test.ts +++ b/packages/agent-memory-sync/tests/integration/cross-machine-profiles.test.ts @@ -23,6 +23,18 @@ const { cloneRemote, createSandbox, fileExists, initBareRemote, readText, runCli const PROFILES_DIR = path.resolve(process.cwd(), "profiles"); +// Derives the committed profile file list structurally (readdirSync over +// profiles/*.json) instead of a hand-maintained array, so a future profile +// — including a new machine or a template edit — is picked up by every test +// below that iterates "all committed profiles" without a matching edit +// here. Deliberately includes linux.example.json: the template is a +// committed *.json file under profiles/ like any other. +function listProfileFiles(): string[] { + return readdirSync(PROFILES_DIR) + .filter((name: string) => name.endsWith(".json")) + .sort(); +} + function machineArgs( profileName: string, configPath: string, @@ -129,13 +141,14 @@ test("macbook and mac-mini profiles share one remote tree and see each other's p test("all committed profiles (macbook, mac-mini, linux, linux.example) declare the same repositorySubdir", () => { // A narrower, faster companion to the end-to-end test above: pins the // specific config field that caused the divergence directly against the - // committed files, independent of any CLI/git plumbing. Includes - // linux.example.json — the copy-paste source for any new machine — so a - // future template edit that reintroduces a per-machine placeholder (as - // this template originally had, mirroring the pre-fix macbook/mac-mini - // profiles) is caught here too, not just on the profiles already in - // active use. - const profileFiles = ["macbook.json", "mac-mini.json", "linux.json", "linux.example.json"]; + // committed files, independent of any CLI/git plumbing. profileFiles is + // derived structurally (readdirSync, see listProfileFiles above), so it + // always includes linux.example.json — the copy-paste source for any new + // machine — without listing filenames by hand: a future template edit + // that reintroduces a per-machine placeholder (as this template + // originally had, mirroring the pre-fix macbook/mac-mini profiles) is + // caught here too, not just on the profiles already in active use. + const profileFiles = listProfileFiles(); const settingsByFile = Object.fromEntries( profileFiles.map((file) => [file, JSON.parse(readText(path.join(PROFILES_DIR, file)))]) ); @@ -195,11 +208,16 @@ test("all committed profiles (macbook, mac-mini, linux, linux.example) declare t ); } - // Same pin for the pre-existing machine-state entry, closing the identical - // #64 coverage gap — every committed machine profile, but not - // linux.example.json, since the template has never carried a machine-state - // entry (linux.json adds it deliberately; see machine-setup.md section f). - for (const file of ["macbook.json", "mac-mini.json", "linux.json"]) { + // Same pin for the machine-state entry, closing the identical #64 + // coverage gap — every committed profile, including linux.example.json: + // the template now carries the entry too (placeholder + // source), precisely so a future third machine copied from it starts with + // the entry present instead of repeating the hand-patch divergence + // linux.json/macbook.json/mac-mini.json needed before this fix (agent-tasks + // 10df0d9d; see machine-setup.md section e)/f)). The endsWith check below + // tolerates the template's placeholder segment (the path is still + // absolute-shaped and still ends with the literal suffix). + for (const file of profileFiles) { const machineStateEntries = findEntriesByDestination(settingsByFile[file].syncPaths, "machine-state"); assert.equal( machineStateEntries.length, @@ -219,16 +237,69 @@ test("all committed profiles (macbook, mac-mini, linux, linux.example) declare t } }); +// Pins the three path invariants documented in every profile's "//" field +// (real profiles' "rootDir/stateDir use a resolved absolute path, not '~'" +// paragraph; the template's identical paragraph): rootDir/stateDir must be +// absolute, must not start with '~' (agent-memory-sync's config loader +// never expands it — see resolveRunConfig() in src/config/loader.ts, which +// treats a leading '~' as a literal path segment, not the home directory), +// and stateDir must sit OUTSIDE rootDir (otherwise the recursive '.' +// syncPaths walk would also pick up this tool's own state — queue/base/tmp +// — and try to sync it as memory content). profileFiles is the same +// structurally-derived list used above, so linux.example.json is included: +// its placeholder segments (e.g. '', '') +// keep both paths absolute-shaped (still start with '/') and un-prefixed by +// '~', and its stateDir/rootDir still resolve to different subtrees, so all +// three checks hold for the template's placeholder values with no +// special-casing — these are pure string checks, never filesystem lookups, +// so an unresolved placeholder segment does not make them fail or need to +// be skipped. +test("all committed profiles keep rootDir/stateDir absolute, un-expanded (no leading '~'), and stateDir outside rootDir", () => { + const profileFiles = listProfileFiles(); + const settingsByFile = Object.fromEntries( + profileFiles.map((file) => [file, JSON.parse(readText(path.join(PROFILES_DIR, file)))]) + ); + + for (const file of profileFiles) { + const { rootDir, stateDir } = settingsByFile[file]; + + assert.ok( + typeof rootDir === "string" && path.isAbsolute(rootDir), + `profiles/${file} rootDir must be an absolute path, got: ${JSON.stringify(rootDir)}` + ); + assert.ok( + typeof stateDir === "string" && path.isAbsolute(stateDir), + `profiles/${file} stateDir must be an absolute path, got: ${JSON.stringify(stateDir)}` + ); + assert.ok( + !(rootDir as string).startsWith("~"), + `profiles/${file} rootDir must not start with '~' (the config loader never expands it), got: ${rootDir}` + ); + assert.ok( + !(stateDir as string).startsWith("~"), + `profiles/${file} stateDir must not start with '~' (the config loader never expands it), got: ${stateDir}` + ); + + const rootDirWithSep = (rootDir as string).endsWith(path.sep) ? (rootDir as string) : (rootDir as string) + path.sep; + assert.ok( + stateDir !== rootDir && !(stateDir as string).startsWith(rootDirWithSep), + `profiles/${file} stateDir must sit OUTSIDE rootDir, got rootDir: ${rootDir}, stateDir: ${stateDir}` + ); + } +}); + // Pins Defect B's fix (agent-tasks 06d09cde / .ai/runs/2026-08-03-sync-conflict-markers-echo, // D-002/D-003): machine-state and frictions are one-owner-file-per-machine // destinations, so push must never re-offer a peer's file it only pulled — // ownerScoped: true on both entries in every real machine profile is what // makes collectLocalSyncFiles' ownerFilter (src/memory-sync/config.ts) // actually engage. Deliberately scoped to the same 3 real profiles as the -// machine-state pin above, not linux.example.json — the template documents -// the convention but was never a live sync target, so it carries no -// machine-state entry at all and this task's brief only requires the flag on -// "die 3 committeten Profilen". +// machine-state pin above, not linux.example.json — the template's +// machine-state entry does set ownerScoped: true (mirroring the real +// profiles, agent-tasks 10df0d9d), but its frictions entry still does not, +// so including the template here would fail on that entry, not the one this +// comment is about; this task's brief only requires the flag on "die 3 +// committeten Profilen". test("macbook, mac-mini, and linux profiles set ownerScoped: true on both their machine-state and frictions entries", () => { const profileFiles = ["macbook.json", "mac-mini.json", "linux.json"]; const settingsByFile = Object.fromEntries( From c4d9a66c0c7e341b41fd00fc129b12217844e346 Mon Sep 17 00:00:00 2001 From: Lan Nguyen Si Date: Wed, 5 Aug 2026 13:19:41 +0200 Subject: [PATCH 2/3] fix(memory-sync): harden profile guard test per review Adds a non-vacuity guard to listProfileFiles() so an emptied or renamed profiles/ directory fails loudly instead of silently shrinking test coverage. Rewords the template's syncPaths comment to match the real profiles verbatim, adds ownerScoped: true to the template's frictions entry (closing the gap that forced the ownerScoped test to hand-exclude the template), and switches the ownerScoped test and the stateDir-outside-rootDir check to derive their inputs structurally. Brings the docs snippets and prose in line with the ownerScoped shape. --- .../agent-memory-sync/docs/machine-setup.md | 11 ++-- .../profiles/linux.example.json | 16 +++--- .../cross-machine-profiles.test.ts | 50 ++++++++++++++----- 3 files changed, 53 insertions(+), 24 deletions(-) diff --git a/packages/agent-memory-sync/docs/machine-setup.md b/packages/agent-memory-sync/docs/machine-setup.md index 9f580ba..a2bad78 100644 --- a/packages/agent-memory-sync/docs/machine-setup.md +++ b/packages/agent-memory-sync/docs/machine-setup.md @@ -342,7 +342,7 @@ Every committed machine profile (`profiles/mac-mini.json`, document: ```json -{ "source": "/Users//.harness/machine-state", "destination": "machine-state", "kind": "directory" } +{ "source": "/Users//.harness/machine-state", "destination": "machine-state", "kind": "directory", "ownerScoped": true } ``` Unlike the `memory` entry, `source` here is an **absolute path outside @@ -363,7 +363,12 @@ machine needs to do here, rather than adding this entry from scratch. **Payload convention — one file per machine, owner-writes-only.** Each machine writes exactly one JSON file under its own `machine-state/`, named after its own profile (`machine-state/mac-mini.json`, -`machine-state/macbook.json`, ...). A machine never writes to another +`machine-state/macbook.json`, ...). This is enforced, not just documented: +the syncPaths entry above sets `"ownerScoped": true`, which is the +mechanism that makes push only ever offer this machine's own +`.json`, never a peer's file this machine merely pulled (see +`collectLocalSyncFiles`'s `ownerFilter` option in +`src/memory-sync/config.ts`). A machine never writes to another machine's file — this makes *content* conflicts on this path structurally impossible (`inline-markers` conflict resolution is never invoked here in practice, unlike the `memory` tree where concurrent edits are expected). @@ -416,7 +421,7 @@ third entry in every one of them, after `memory` and `machine-state` (see its `syncPaths` array has the same three-entry shape as the real profiles): ```json -{ "source": "/Users//.harness/frictions", "destination": "frictions", "kind": "directory" } +{ "source": "/Users//.harness/frictions", "destination": "frictions", "kind": "directory", "ownerScoped": true } ``` Same convention as the machine-state payload in section e) above: one file diff --git a/packages/agent-memory-sync/profiles/linux.example.json b/packages/agent-memory-sync/profiles/linux.example.json index f73c7a2..d948909 100644 --- a/packages/agent-memory-sync/profiles/linux.example.json +++ b/packages/agent-memory-sync/profiles/linux.example.json @@ -39,7 +39,7 @@ "recorded as a cosmetic label inside this machine's state.json. Fill it", "in anyway, for a self-documenting invocation and a correct label.", "", - "syncPaths is a single directory entry covering the ENTIRE rootDir", + "syncPaths' FIRST entry is a directory entry covering the ENTIRE rootDir", "(source '.'), so every file agent-memory-sync finds there is synced,", "including future subdirectories, without listing individual", "files/folders by name. destination is the non-'.' literal 'memory', not", @@ -57,12 +57,12 @@ "cross-machine parity check run by the harness companion 'session-start", "toolchain-parity' — see docs/machine-setup.md section e) for the full", "payload convention (one file per machine, owner-writes-only) and", - "section f) for the THIRD entry, frictions. It sets \"ownerScoped\": true,", - "matching the real linux.json/macbook.json/mac-mini.json profiles, so", - "push only ever offers this machine's own .json under", - "machine-state/ and never a peer's file this machine merely pulled —", - "see collectLocalSyncFiles' ownerFilter option in", - "src/memory-sync/config.ts.", + "section f) for the THIRD entry, frictions. Both entries set", + "\"ownerScoped\": true, matching the real linux.json/macbook.json/", + "mac-mini.json profiles, so push only ever offers this machine's own", + ".json under machine-state/ or frictions/ and never a peer's", + "file this machine merely pulled; see collectLocalSyncFiles'", + "ownerFilter option in src/memory-sync/config.ts.", "", "stateDir is set OUTSIDE rootDir (a separate ~/.agent-memory-sync tree),", "which matters now that syncPaths covers the whole rootDir: if stateDir", @@ -96,6 +96,6 @@ "syncPaths": [ { "source": ".", "destination": "memory", "kind": "directory" }, { "source": "/home//.harness/machine-state", "destination": "machine-state", "kind": "directory", "ownerScoped": true }, - { "source": "/home//.harness/frictions", "destination": "frictions", "kind": "directory" } + { "source": "/home//.harness/frictions", "destination": "frictions", "kind": "directory", "ownerScoped": true } ] } diff --git a/packages/agent-memory-sync/tests/integration/cross-machine-profiles.test.ts b/packages/agent-memory-sync/tests/integration/cross-machine-profiles.test.ts index 02e0cd5..5a66dfa 100644 --- a/packages/agent-memory-sync/tests/integration/cross-machine-profiles.test.ts +++ b/packages/agent-memory-sync/tests/integration/cross-machine-profiles.test.ts @@ -29,10 +29,31 @@ const PROFILES_DIR = path.resolve(process.cwd(), "profiles"); // below that iterates "all committed profiles" without a matching edit // here. Deliberately includes linux.example.json: the template is a // committed *.json file under profiles/ like any other. +// +// A purely structural derivation loses deletion/rename detection though: +// an empty or partially-emptied profiles/ directory would silently make +// every test below iterate over fewer (or zero) files and report green +// instead of catching the loss. The non-vacuity guard below restores that +// detection while still auto-picking-up any future profile: it requires +// at least the 4 known files to be present, by name, on every call. function listProfileFiles(): string[] { - return readdirSync(PROFILES_DIR) + const files = readdirSync(PROFILES_DIR) .filter((name: string) => name.endsWith(".json")) .sort(); + + const knownProfiles = ["macbook.json", "mac-mini.json", "linux.json", "linux.example.json"]; + assert.ok( + files.length >= 4, + `profiles/ must contain at least the ${knownProfiles.length} known machine profiles, found ${files.length}: ${JSON.stringify(files)} (PROFILES_DIR: ${PROFILES_DIR})` + ); + const missing = knownProfiles.filter((name) => !files.includes(name)); + assert.equal( + missing.length, + 0, + `profiles/ is missing known machine profile(s): ${JSON.stringify(missing)}, found: ${JSON.stringify(files)} (PROFILES_DIR: ${PROFILES_DIR})` + ); + + return files; } function machineArgs( @@ -280,9 +301,9 @@ test("all committed profiles keep rootDir/stateDir absolute, un-expanded (no lea `profiles/${file} stateDir must not start with '~' (the config loader never expands it), got: ${stateDir}` ); - const rootDirWithSep = (rootDir as string).endsWith(path.sep) ? (rootDir as string) : (rootDir as string) + path.sep; + const rel = path.posix.relative(path.posix.normalize(rootDir as string), path.posix.normalize(stateDir as string)); assert.ok( - stateDir !== rootDir && !(stateDir as string).startsWith(rootDirWithSep), + rel !== "" && (rel.startsWith("../") || path.posix.isAbsolute(rel)), `profiles/${file} stateDir must sit OUTSIDE rootDir, got rootDir: ${rootDir}, stateDir: ${stateDir}` ); } @@ -291,17 +312,20 @@ test("all committed profiles keep rootDir/stateDir absolute, un-expanded (no lea // Pins Defect B's fix (agent-tasks 06d09cde / .ai/runs/2026-08-03-sync-conflict-markers-echo, // D-002/D-003): machine-state and frictions are one-owner-file-per-machine // destinations, so push must never re-offer a peer's file it only pulled — -// ownerScoped: true on both entries in every real machine profile is what +// ownerScoped: true on both entries in every committed profile is what // makes collectLocalSyncFiles' ownerFilter (src/memory-sync/config.ts) -// actually engage. Deliberately scoped to the same 3 real profiles as the -// machine-state pin above, not linux.example.json — the template's -// machine-state entry does set ownerScoped: true (mirroring the real -// profiles, agent-tasks 10df0d9d), but its frictions entry still does not, -// so including the template here would fail on that entry, not the one this -// comment is about; this task's brief only requires the flag on "die 3 -// committeten Profilen". -test("macbook, mac-mini, and linux profiles set ownerScoped: true on both their machine-state and frictions entries", () => { - const profileFiles = ["macbook.json", "mac-mini.json", "linux.json"]; +// actually engage. profileFiles is the same structurally-derived list used +// by the tests above (see listProfileFiles), so it now includes +// linux.example.json too: an earlier revision of this test hand-scoped the +// list to the 3 real profiles and excluded the template, because the +// template's machine-state entry set ownerScoped: true (mirroring the real +// profiles, agent-tasks 10df0d9d) while its frictions entry still did not; +// including the template would have failed on that entry alone. The +// template's frictions entry now sets ownerScoped: true too, closing that +// gap, so the exclusion is no longer needed and a future regression in the +// template is caught here like any real profile. +test("all committed profiles set ownerScoped: true on both their machine-state and frictions entries", () => { + const profileFiles = listProfileFiles(); const settingsByFile = Object.fromEntries( profileFiles.map((file) => [file, JSON.parse(readText(path.join(PROFILES_DIR, file)))]) ); From a3d7ae805c0adaf26b7ebb14347f3139ca539782 Mon Sep 17 00:00:00 2001 From: Lan Nguyen Si Date: Wed, 5 Aug 2026 13:22:52 +0200 Subject: [PATCH 3/3] style(memory-sync): replace em dashes in newly added comment prose --- packages/agent-memory-sync/docs/machine-setup.md | 8 ++++---- .../profiles/linux.example.json | 2 +- .../integration/cross-machine-profiles.test.ts | 16 ++++++++-------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/agent-memory-sync/docs/machine-setup.md b/packages/agent-memory-sync/docs/machine-setup.md index a2bad78..75beb18 100644 --- a/packages/agent-memory-sync/docs/machine-setup.md +++ b/packages/agent-memory-sync/docs/machine-setup.md @@ -229,7 +229,7 @@ empty/stale local workspace as if it were authoritative. checkout path (see the namespace-divergence note inside any existing profile file). The template's `syncPaths` already includes the `machine-state` entry (see (e) below) alongside `memory` and - `frictions` — fill in its `` placeholder like the rest + `frictions`; fill in its `` placeholder like the rest of the file rather than adding the entry by hand. 4. First pull so the machine starts from the mini's current state: ```bash @@ -336,8 +336,8 @@ bullet at the top of this document. ## e) machine-state payload (toolchain snapshots) Every committed machine profile (`profiles/mac-mini.json`, -`profiles/macbook.json`, `profiles/linux.json`) — and the template, -`profiles/linux.example.json` — carries a **second, independent** +`profiles/macbook.json`, `profiles/linux.json`), and the template, +`profiles/linux.example.json`, carries a **second, independent** `syncPaths` entry alongside the `memory` one described at the top of this document: @@ -356,7 +356,7 @@ entries still land under the same shared `pandora/` remote tree (`repositorySubdir`), just under different top-level destinations (`pandora/memory/...` vs. `pandora/machine-state/...`). In `profiles/linux.example.json` the entry's `source` uses the same -`` placeholder as `rootDir`/`stateDir` (see (c) above) — +`` placeholder as `rootDir`/`stateDir` (see (c) above); copying the template and filling in that one placeholder is all a new machine needs to do here, rather than adding this entry from scratch. diff --git a/packages/agent-memory-sync/profiles/linux.example.json b/packages/agent-memory-sync/profiles/linux.example.json index d948909..f1ae633 100644 --- a/packages/agent-memory-sync/profiles/linux.example.json +++ b/packages/agent-memory-sync/profiles/linux.example.json @@ -55,7 +55,7 @@ "/home//.harness/machine-state, destination", "'machine-state'), carrying this machine's toolchain snapshot for the", "cross-machine parity check run by the harness companion 'session-start", - "toolchain-parity' — see docs/machine-setup.md section e) for the full", + "toolchain-parity'; see docs/machine-setup.md section e) for the full", "payload convention (one file per machine, owner-writes-only) and", "section f) for the THIRD entry, frictions. Both entries set", "\"ownerScoped\": true, matching the real linux.json/macbook.json/", diff --git a/packages/agent-memory-sync/tests/integration/cross-machine-profiles.test.ts b/packages/agent-memory-sync/tests/integration/cross-machine-profiles.test.ts index 5a66dfa..285e785 100644 --- a/packages/agent-memory-sync/tests/integration/cross-machine-profiles.test.ts +++ b/packages/agent-memory-sync/tests/integration/cross-machine-profiles.test.ts @@ -25,7 +25,7 @@ const PROFILES_DIR = path.resolve(process.cwd(), "profiles"); // Derives the committed profile file list structurally (readdirSync over // profiles/*.json) instead of a hand-maintained array, so a future profile -// — including a new machine or a template edit — is picked up by every test +// (including a new machine or a template edit) is picked up by every test // below that iterates "all committed profiles" without a matching edit // here. Deliberately includes linux.example.json: the template is a // committed *.json file under profiles/ like any other. @@ -164,8 +164,8 @@ test("all committed profiles (macbook, mac-mini, linux, linux.example) declare t // specific config field that caused the divergence directly against the // committed files, independent of any CLI/git plumbing. profileFiles is // derived structurally (readdirSync, see listProfileFiles above), so it - // always includes linux.example.json — the copy-paste source for any new - // machine — without listing filenames by hand: a future template edit + // always includes linux.example.json (the copy-paste source for any new + // machine) without listing filenames by hand: a future template edit // that reintroduces a per-machine placeholder (as this template // originally had, mirroring the pre-fix macbook/mac-mini profiles) is // caught here too, not just on the profiles already in active use. @@ -230,7 +230,7 @@ test("all committed profiles (macbook, mac-mini, linux, linux.example) declare t } // Same pin for the machine-state entry, closing the identical #64 - // coverage gap — every committed profile, including linux.example.json: + // coverage gap for every committed profile, including linux.example.json: // the template now carries the entry too (placeholder // source), precisely so a future third machine copied from it starts with // the entry present instead of repeating the hand-patch divergence @@ -262,17 +262,17 @@ test("all committed profiles (macbook, mac-mini, linux, linux.example) declare t // (real profiles' "rootDir/stateDir use a resolved absolute path, not '~'" // paragraph; the template's identical paragraph): rootDir/stateDir must be // absolute, must not start with '~' (agent-memory-sync's config loader -// never expands it — see resolveRunConfig() in src/config/loader.ts, which +// never expands it; see resolveRunConfig() in src/config/loader.ts, which // treats a leading '~' as a literal path segment, not the home directory), // and stateDir must sit OUTSIDE rootDir (otherwise the recursive '.' -// syncPaths walk would also pick up this tool's own state — queue/base/tmp -// — and try to sync it as memory content). profileFiles is the same +// syncPaths walk would also pick up this tool's own state (queue/base/tmp) +// and try to sync it as memory content). profileFiles is the same // structurally-derived list used above, so linux.example.json is included: // its placeholder segments (e.g. '', '') // keep both paths absolute-shaped (still start with '/') and un-prefixed by // '~', and its stateDir/rootDir still resolve to different subtrees, so all // three checks hold for the template's placeholder values with no -// special-casing — these are pure string checks, never filesystem lookups, +// special-casing: these are pure string checks, never filesystem lookups, // so an unresolved placeholder segment does not make them fail or need to // be skipped. test("all committed profiles keep rootDir/stateDir absolute, un-expanded (no leading '~'), and stateDir outside rootDir", () => {