grounder 0.4.0 → 0.6.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/README.md +150 -271
- package/dist/agents/claude.d.ts +4 -4
- package/dist/agents/claude.d.ts.map +1 -1
- package/dist/agents/claude.js +158 -178
- package/dist/agents/claude.js.map +1 -1
- package/dist/agents/cursor.d.ts +3 -3
- package/dist/agents/cursor.d.ts.map +1 -1
- package/dist/agents/cursor.js +97 -143
- package/dist/agents/cursor.js.map +1 -1
- package/dist/agents/home-skills.d.ts +46 -0
- package/dist/agents/home-skills.d.ts.map +1 -0
- package/dist/agents/home-skills.js +83 -0
- package/dist/agents/home-skills.js.map +1 -0
- package/dist/agents/hook-fragment.d.ts +52 -0
- package/dist/agents/hook-fragment.d.ts.map +1 -0
- package/dist/agents/hook-fragment.js +76 -0
- package/dist/agents/hook-fragment.js.map +1 -0
- package/dist/agents/hook-install.d.ts +62 -0
- package/dist/agents/hook-install.d.ts.map +1 -0
- package/dist/agents/hook-install.js +74 -0
- package/dist/agents/hook-install.js.map +1 -0
- package/dist/agents/hook-runtime.d.ts +80 -13
- package/dist/agents/hook-runtime.d.ts.map +1 -1
- package/dist/agents/hook-runtime.js +278 -74
- package/dist/agents/hook-runtime.js.map +1 -1
- package/dist/agents/index.d.ts +8 -14
- package/dist/agents/index.d.ts.map +1 -1
- package/dist/agents/index.js +13 -19
- package/dist/agents/index.js.map +1 -1
- package/dist/agents/types.d.ts +56 -16
- package/dist/agents/types.d.ts.map +1 -1
- package/dist/cli.js +15 -10
- package/dist/cli.js.map +1 -1
- package/dist/commands/apply.d.ts +69 -0
- package/dist/commands/apply.d.ts.map +1 -0
- package/dist/commands/apply.js +238 -0
- package/dist/commands/apply.js.map +1 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +197 -101
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/handoff/list.d.ts +23 -13
- package/dist/commands/handoff/list.d.ts.map +1 -1
- package/dist/commands/handoff/list.js +28 -16
- package/dist/commands/handoff/list.js.map +1 -1
- package/dist/commands/handoff/peek.d.ts +1 -1
- package/dist/commands/handoff/peek.d.ts.map +1 -1
- package/dist/commands/handoff/peek.js +28 -12
- package/dist/commands/handoff/peek.js.map +1 -1
- package/dist/commands/handoff.d.ts +2 -0
- package/dist/commands/handoff.d.ts.map +1 -1
- package/dist/commands/handoff.js +10 -1
- package/dist/commands/handoff.js.map +1 -1
- package/dist/commands/install-drift.d.ts +34 -0
- package/dist/commands/install-drift.d.ts.map +1 -0
- package/dist/commands/install-drift.js +72 -0
- package/dist/commands/install-drift.js.map +1 -0
- package/dist/commands/link.d.ts +12 -0
- package/dist/commands/link.d.ts.map +1 -0
- package/dist/commands/{repo/init.js → link.js} +35 -27
- package/dist/commands/link.js.map +1 -0
- package/dist/commands/migrate.d.ts +11 -3
- package/dist/commands/migrate.d.ts.map +1 -1
- package/dist/commands/migrate.js +66 -34
- package/dist/commands/migrate.js.map +1 -1
- package/dist/commands/note/list.d.ts +17 -9
- package/dist/commands/note/list.d.ts.map +1 -1
- package/dist/commands/note/list.js +19 -14
- package/dist/commands/note/list.js.map +1 -1
- package/dist/commands/note.d.ts +2 -0
- package/dist/commands/note.d.ts.map +1 -1
- package/dist/commands/note.js +10 -1
- package/dist/commands/note.js.map +1 -1
- package/dist/commands/output.d.ts +22 -2
- package/dist/commands/output.d.ts.map +1 -1
- package/dist/commands/output.js +18 -6
- package/dist/commands/output.js.map +1 -1
- package/dist/commands/overview.d.ts +33 -0
- package/dist/commands/overview.d.ts.map +1 -0
- package/dist/commands/overview.js +150 -0
- package/dist/commands/overview.js.map +1 -0
- package/dist/commands/plan/list.d.ts +20 -12
- package/dist/commands/plan/list.d.ts.map +1 -1
- package/dist/commands/plan/list.js +22 -17
- package/dist/commands/plan/list.js.map +1 -1
- package/dist/commands/plan.d.ts +2 -0
- package/dist/commands/plan.d.ts.map +1 -1
- package/dist/commands/plan.js +12 -2
- package/dist/commands/plan.js.map +1 -1
- package/dist/commands/render-artifact-table.d.ts +85 -0
- package/dist/commands/render-artifact-table.d.ts.map +1 -0
- package/dist/commands/render-artifact-table.js +232 -0
- package/dist/commands/render-artifact-table.js.map +1 -0
- package/dist/commands/require-linked.js +2 -2
- package/dist/commands/require-linked.js.map +1 -1
- package/dist/commands/search.d.ts +23 -0
- package/dist/commands/search.d.ts.map +1 -0
- package/dist/commands/search.js +288 -0
- package/dist/commands/search.js.map +1 -0
- package/dist/commands/{vault/init.d.ts → setup.d.ts} +5 -4
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +210 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/commands/status.d.ts +7 -0
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +273 -83
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/upgrade-banner.d.ts +2 -2
- package/dist/commands/upgrade-banner.js +2 -2
- package/dist/connector/home.d.ts +12 -0
- package/dist/connector/home.d.ts.map +1 -1
- package/dist/connector/home.js +26 -2
- package/dist/connector/home.js.map +1 -1
- package/dist/connector/ledger-migrations.d.ts +55 -0
- package/dist/connector/ledger-migrations.d.ts.map +1 -0
- package/dist/connector/ledger-migrations.js +94 -0
- package/dist/connector/ledger-migrations.js.map +1 -0
- package/dist/connector/state.d.ts +97 -60
- package/dist/connector/state.d.ts.map +1 -1
- package/dist/connector/state.js +165 -107
- package/dist/connector/state.js.map +1 -1
- package/dist/connector/vault.d.ts +2 -0
- package/dist/connector/vault.d.ts.map +1 -1
- package/dist/connector/vault.js +6 -1
- package/dist/connector/vault.js.map +1 -1
- package/dist/help.d.ts +3 -3
- package/dist/help.d.ts.map +1 -1
- package/dist/help.js +97 -56
- package/dist/help.js.map +1 -1
- package/dist/reconcile/apply.d.ts +31 -0
- package/dist/reconcile/apply.d.ts.map +1 -0
- package/dist/reconcile/apply.js +97 -0
- package/dist/reconcile/apply.js.map +1 -0
- package/dist/reconcile/core.d.ts +82 -0
- package/dist/reconcile/core.d.ts.map +1 -0
- package/dist/reconcile/core.js +163 -0
- package/dist/reconcile/core.js.map +1 -0
- package/dist/reconcile/disk.d.ts +13 -0
- package/dist/reconcile/disk.d.ts.map +1 -0
- package/dist/reconcile/disk.js +28 -0
- package/dist/reconcile/disk.js.map +1 -0
- package/dist/util/frontmatter.d.ts +9 -7
- package/dist/util/frontmatter.d.ts.map +1 -1
- package/dist/util/frontmatter.js +29 -4
- package/dist/util/frontmatter.js.map +1 -1
- package/dist/util/fs.d.ts +14 -0
- package/dist/util/fs.d.ts.map +1 -1
- package/dist/util/fs.js +27 -1
- package/dist/util/fs.js.map +1 -1
- package/dist/util/merge-json.d.ts +13 -2
- package/dist/util/merge-json.d.ts.map +1 -1
- package/dist/util/merge-json.js +24 -10
- package/dist/util/merge-json.js.map +1 -1
- package/dist/util/path.d.ts +23 -0
- package/dist/util/path.d.ts.map +1 -1
- package/dist/util/path.js +39 -0
- package/dist/util/path.js.map +1 -1
- package/dist/vault/list-handoffs.d.ts +14 -2
- package/dist/vault/list-handoffs.d.ts.map +1 -1
- package/dist/vault/list-handoffs.js +40 -23
- package/dist/vault/list-handoffs.js.map +1 -1
- package/dist/vault/list-markdown.d.ts +8 -0
- package/dist/vault/list-markdown.d.ts.map +1 -0
- package/dist/vault/list-markdown.js +35 -0
- package/dist/vault/list-markdown.js.map +1 -0
- package/dist/vault/list-notes.d.ts +15 -6
- package/dist/vault/list-notes.d.ts.map +1 -1
- package/dist/vault/list-notes.js +23 -27
- package/dist/vault/list-notes.js.map +1 -1
- package/dist/vault/list-plans.d.ts +12 -3
- package/dist/vault/list-plans.d.ts.map +1 -1
- package/dist/vault/list-plans.js +20 -24
- package/dist/vault/list-plans.js.map +1 -1
- package/dist/vault/search.d.ts +55 -0
- package/dist/vault/search.d.ts.map +1 -0
- package/dist/vault/search.js +432 -0
- package/dist/vault/search.js.map +1 -0
- package/dist/vault/write-handoff.d.ts +2 -0
- package/dist/vault/write-handoff.d.ts.map +1 -1
- package/dist/vault/write-handoff.js +5 -0
- package/dist/vault/write-handoff.js.map +1 -1
- package/dist/vault/write-note.d.ts +2 -0
- package/dist/vault/write-note.d.ts.map +1 -1
- package/dist/vault/write-note.js +9 -1
- package/dist/vault/write-note.js.map +1 -1
- package/dist/vault/write-plan.d.ts +4 -0
- package/dist/vault/write-plan.d.ts.map +1 -1
- package/dist/vault/write-plan.js +14 -1
- package/dist/vault/write-plan.js.map +1 -1
- package/package.json +11 -4
- package/templates/agents/claude/skills/grounder-handoff/SKILL.md +73 -0
- package/templates/agents/claude/{commands/grounder-note.md → skills/grounder-note/SKILL.md} +12 -2
- package/templates/agents/claude/skills/grounder-overview/SKILL.md +21 -0
- package/templates/agents/claude/{commands/grounder-plan.md → skills/grounder-plan/SKILL.md} +13 -5
- package/templates/agents/claude/skills/grounder-recall/SKILL.md +34 -0
- package/templates/agents/claude/skills/grounder-search/SKILL.md +152 -0
- package/templates/agents/cursor/skills/grounder-handoff/SKILL.md +73 -0
- package/templates/agents/cursor/{commands/grounder-note.md → skills/grounder-note/SKILL.md} +12 -2
- package/templates/agents/cursor/skills/grounder-overview/SKILL.md +21 -0
- package/templates/agents/cursor/{commands/grounder-plan.md → skills/grounder-plan/SKILL.md} +13 -5
- package/templates/agents/cursor/skills/grounder-recall/SKILL.md +34 -0
- package/templates/agents/cursor/skills/grounder-search/SKILL.md +152 -0
- package/templates/vault/plan.md +1 -1
- package/templates/vault/session-handoff.md +2 -1
- package/dist/agents/install-command.d.ts +0 -35
- package/dist/agents/install-command.d.ts.map +0 -1
- package/dist/agents/install-command.js +0 -65
- package/dist/agents/install-command.js.map +0 -1
- package/dist/commands/apply-agent-installs.d.ts +0 -34
- package/dist/commands/apply-agent-installs.d.ts.map +0 -1
- package/dist/commands/apply-agent-installs.js +0 -137
- package/dist/commands/apply-agent-installs.js.map +0 -1
- package/dist/commands/repo/init.d.ts +0 -11
- package/dist/commands/repo/init.d.ts.map +0 -1
- package/dist/commands/repo/init.js.map +0 -1
- package/dist/commands/vault/init.d.ts.map +0 -1
- package/dist/commands/vault/init.js +0 -90
- package/dist/commands/vault/init.js.map +0 -1
- package/templates/agents/claude/commands/grounder-task-handoff.md +0 -54
- package/templates/agents/claude/commands/grounder-task.md +0 -22
- package/templates/agents/cursor/commands/grounder-task-handoff.md +0 -54
- package/templates/agents/cursor/commands/grounder-task.md +0 -22
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
/**
|
|
3
|
+
* Pure reconciliation core — no I/O anywhere in this file.
|
|
4
|
+
*
|
|
5
|
+
* Two shapes, per the design: {@link desiredDrift} answers "would migrate
|
|
6
|
+
* change something" cheaply (ledger vs. rendered template hashes only, no
|
|
7
|
+
* disk read) for `peek`/`status`; {@link reconcile} is the full three-way
|
|
8
|
+
* (desired vs. ledger vs. on-disk) plan `setup`/`migrate`/`doctor` execute or
|
|
9
|
+
* preview. Modeled on chezmoi's source/destination/target state and dpkg's
|
|
10
|
+
* old-pristine/new-pristine/on-disk compare.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* True when `filePath` sits under one of `prefixes` (a directory, matched
|
|
14
|
+
* exactly or via a `prefix + separator` boundary — never a bare substring
|
|
15
|
+
* match, and never a `..`-segment escape). Callers use this to keep a
|
|
16
|
+
* ledger's extra/stray entries — a hand-edited or corrupted `state.json`,
|
|
17
|
+
* say — from ever being treated as delete candidates for paths outside the
|
|
18
|
+
* directories an adapter actually manages. Comparison is lexical
|
|
19
|
+
* (`path.resolve` + `path.relative`, no symlink following): both sides are
|
|
20
|
+
* resolved to absolute form first, so a `../` segment in `filePath` cannot
|
|
21
|
+
* walk back out of a prefix it superficially starts with.
|
|
22
|
+
* `reconcile()` itself stays agnostic to this (see its own docs); callers
|
|
23
|
+
* filter `ledger` before passing it in.
|
|
24
|
+
*/
|
|
25
|
+
export function isUnderOwnedPrefix(filePath, prefixes) {
|
|
26
|
+
const target = path.resolve(filePath);
|
|
27
|
+
return prefixes.some((prefix) => {
|
|
28
|
+
const root = path.resolve(prefix);
|
|
29
|
+
if (root === target) {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
const relative = path.relative(root, target);
|
|
33
|
+
return (relative !== "" &&
|
|
34
|
+
relative !== ".." &&
|
|
35
|
+
!relative.startsWith(`..${path.sep}`) &&
|
|
36
|
+
!path.isAbsolute(relative));
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Compare desired (rendered template) hashes against one agent's last-applied
|
|
41
|
+
* ledger manifest. No disk I/O — the caller renders + hashes templates, and
|
|
42
|
+
* passes in the ledger's `files` map already read from `state.json`.
|
|
43
|
+
*
|
|
44
|
+
* `ledger === undefined` means this agent has no ledger entry at all (never
|
|
45
|
+
* installed) — returns no drift. Scoping to ledger-recorded agents lives
|
|
46
|
+
* here, not in every caller: otherwise a detected-but-never-installed agent
|
|
47
|
+
* would tease `migrate` forever with no command able to silence it.
|
|
48
|
+
*
|
|
49
|
+
* Within a ledger-recorded agent, a desired path missing from its `files` map
|
|
50
|
+
* IS drift (`"create"`) — that's how a newly added skill file surfaces here.
|
|
51
|
+
*/
|
|
52
|
+
export function desiredDrift(desired, ledger) {
|
|
53
|
+
if (!ledger) {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
const out = [];
|
|
57
|
+
for (const [path, hash] of Object.entries(desired)) {
|
|
58
|
+
const recorded = ledger[path]?.hash;
|
|
59
|
+
if (recorded === undefined) {
|
|
60
|
+
out.push({ path, kind: "create" });
|
|
61
|
+
}
|
|
62
|
+
else if (recorded !== hash) {
|
|
63
|
+
out.push({ path, kind: "update" });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Full three-way reconcile for one agent's whole-file artifacts: desired
|
|
70
|
+
* (current templates) vs. ledger (last-applied) vs. disk (actual). Pure —
|
|
71
|
+
* `disk`/`ledger` are already-read snapshots, not live filesystem access.
|
|
72
|
+
*
|
|
73
|
+
* `tombstones` are extra "previously desired, no longer desired" paths
|
|
74
|
+
* (pre-skill command files, etc.) unioned into the retirement side of the
|
|
75
|
+
* diff even when the ledger never recorded them — the case a pure
|
|
76
|
+
* ledger-manifest diff is blind to (pre-hash-tracking installs).
|
|
77
|
+
*
|
|
78
|
+
* Safety: only ever call this for a known agent id whose `desired` this
|
|
79
|
+
* binary can actually compute — never for an unrecognized ledger entry,
|
|
80
|
+
* which would treat its entire recorded file set as delete candidates.
|
|
81
|
+
*/
|
|
82
|
+
export function reconcile(desired, tombstones, ledger, disk, force) {
|
|
83
|
+
const ledgerFiles = ledger ?? {};
|
|
84
|
+
const allPaths = new Set([
|
|
85
|
+
...Object.keys(desired),
|
|
86
|
+
...Object.keys(ledgerFiles),
|
|
87
|
+
...tombstones,
|
|
88
|
+
]);
|
|
89
|
+
const entries = [];
|
|
90
|
+
for (const path of allPaths) {
|
|
91
|
+
const desiredHash = desired[path];
|
|
92
|
+
const ledgerHash = ledgerFiles[path]?.hash;
|
|
93
|
+
const onDisk = disk[path];
|
|
94
|
+
if (desiredHash !== undefined) {
|
|
95
|
+
// Whole-file artifact currently wanted.
|
|
96
|
+
if (onDisk === undefined) {
|
|
97
|
+
entries.push({ path, action: "create" });
|
|
98
|
+
}
|
|
99
|
+
else if (onDisk === desiredHash) {
|
|
100
|
+
entries.push({ path, action: "noop" });
|
|
101
|
+
}
|
|
102
|
+
else if (force || (ledgerHash !== undefined && ledgerHash === onDisk)) {
|
|
103
|
+
entries.push({ path, action: "update" });
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
entries.push({ path, action: "conflict", blockedAction: "overwrite" });
|
|
107
|
+
}
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
// Not in the current desired set — retirement candidate (tombstone, or a
|
|
111
|
+
// path dropped from the manifest by a schema change).
|
|
112
|
+
if (onDisk === undefined) {
|
|
113
|
+
// Already gone — noop, not a recurring delete (doctor would otherwise
|
|
114
|
+
// warn about an already-cleaned-up legacy path forever). If the ledger
|
|
115
|
+
// still holds a stale hash for it (removed outside `migrate`), that's
|
|
116
|
+
// a ledger-only cleanup ("forget"), not a file action.
|
|
117
|
+
entries.push({ path, action: ledgerHash !== undefined ? "forget" : "noop" });
|
|
118
|
+
}
|
|
119
|
+
else if (force || (ledgerHash !== undefined && ledgerHash === onDisk)) {
|
|
120
|
+
entries.push({ path, action: "delete" });
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
entries.push({ path, action: "conflict", blockedAction: "delete" });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return entries;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Would applying `plan` actually change the ledger? Pure — mirrors exactly
|
|
130
|
+
* what `applyPlan()`'s per-artifact writes do (each has its own no-op guard):
|
|
131
|
+
* `create`/`update`/`noop` only write when the recorded hash would actually
|
|
132
|
+
* change; `delete` only forgets an entry that exists; `forget` always writes
|
|
133
|
+
* (by construction it only exists when there's something to forget);
|
|
134
|
+
* `conflict` never touches the ledger. Used so `--dry-run` and a real run
|
|
135
|
+
* predict/report the exact same thing for the exact same reason.
|
|
136
|
+
*/
|
|
137
|
+
export function planChangesLedger(plan, ledger, desired) {
|
|
138
|
+
const ledgerFiles = ledger ?? {};
|
|
139
|
+
for (const entry of plan) {
|
|
140
|
+
switch (entry.action) {
|
|
141
|
+
case "create":
|
|
142
|
+
case "update":
|
|
143
|
+
case "noop": {
|
|
144
|
+
const desiredHash = desired[entry.path];
|
|
145
|
+
if (desiredHash !== undefined && ledgerFiles[entry.path]?.hash !== desiredHash) {
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
case "delete":
|
|
151
|
+
if (ledgerFiles[entry.path]?.hash !== undefined) {
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
break;
|
|
155
|
+
case "forget":
|
|
156
|
+
return true;
|
|
157
|
+
case "conflict":
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=core.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core.js","sourceRoot":"","sources":["../../src/reconcile/core.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { DiskHashes } from "./core.js";
|
|
2
|
+
/**
|
|
3
|
+
* Read + hash each path; a missing file maps to `undefined`. Any other read
|
|
4
|
+
* failure (EACCES, EISDIR, …) propagates instead of being folded into
|
|
5
|
+
* "missing" — an unreadable-but-present file must never be silently planned
|
|
6
|
+
* as a `create`, writing blind over content this binary never actually saw.
|
|
7
|
+
* Both callers already have somewhere for that to land gracefully: doctor's
|
|
8
|
+
* `computeAgentPlansSafe` catches per-agent and turns it into a "could not
|
|
9
|
+
* verify skill drift" warning; `setup`/`migrate` catch it around
|
|
10
|
+
* `applyAgentInstalls` and report it on stderr.
|
|
11
|
+
*/
|
|
12
|
+
export declare function readDiskHashes(paths: Iterable<string>): Promise<DiskHashes>;
|
|
13
|
+
//# sourceMappingURL=disk.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"disk.d.ts","sourceRoot":"","sources":["../../src/reconcile/disk.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAE5C;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC,CAajF"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { hashContent } from "../util/hash.js";
|
|
3
|
+
/**
|
|
4
|
+
* Read + hash each path; a missing file maps to `undefined`. Any other read
|
|
5
|
+
* failure (EACCES, EISDIR, …) propagates instead of being folded into
|
|
6
|
+
* "missing" — an unreadable-but-present file must never be silently planned
|
|
7
|
+
* as a `create`, writing blind over content this binary never actually saw.
|
|
8
|
+
* Both callers already have somewhere for that to land gracefully: doctor's
|
|
9
|
+
* `computeAgentPlansSafe` catches per-agent and turns it into a "could not
|
|
10
|
+
* verify skill drift" warning; `setup`/`migrate` catch it around
|
|
11
|
+
* `applyAgentInstalls` and report it on stderr.
|
|
12
|
+
*/
|
|
13
|
+
export async function readDiskHashes(paths) {
|
|
14
|
+
const out = {};
|
|
15
|
+
for (const p of paths) {
|
|
16
|
+
try {
|
|
17
|
+
out[p] = hashContent(await readFile(p, "utf8"));
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
if (error.code !== "ENOENT") {
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
out[p] = undefined;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=disk.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"disk.js","sourceRoot":"","sources":["../../src/reconcile/disk.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAG9C;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,KAAuB;IAC1D,MAAM,GAAG,GAAe,EAAE,CAAC;IAC3B,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,IAAI,CAAC;YACH,GAAG,CAAC,CAAC,CAAC,GAAG,WAAW,CAAC,MAAM,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;QAClD,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACvD,MAAM,KAAK,CAAC;YACd,CAAC;YACD,GAAG,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;QACrB,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
export interface ParsedFrontmatter {
|
|
2
|
+
title?: string;
|
|
3
|
+
created?: string;
|
|
4
|
+
topics?: string[];
|
|
5
|
+
}
|
|
1
6
|
/**
|
|
2
|
-
* Parse `title` / `created` from Grounder
|
|
3
|
-
* Accepts the quoted `key: "value"` shape
|
|
4
|
-
* unquoted `key: value` from earlier
|
|
7
|
+
* Parse `title` / `created` / `topics` from Grounder frontmatter.
|
|
8
|
+
* Accepts the quoted `key: "value"` shape write commands produce, and
|
|
9
|
+
* unquoted `key: value` from earlier files. Not a general YAML parser.
|
|
5
10
|
* Returns `{}` on anything unexpected; never throws.
|
|
6
11
|
*/
|
|
7
|
-
export declare function parseHandoffFrontmatter(content: string):
|
|
8
|
-
title?: string;
|
|
9
|
-
created?: string;
|
|
10
|
-
};
|
|
12
|
+
export declare function parseHandoffFrontmatter(content: string): ParsedFrontmatter;
|
|
11
13
|
//# sourceMappingURL=frontmatter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"frontmatter.d.ts","sourceRoot":"","sources":["../../src/util/frontmatter.ts"],"names":[],"mappings":"AA6BA
|
|
1
|
+
{"version":3,"file":"frontmatter.d.ts","sourceRoot":"","sources":["../../src/util/frontmatter.ts"],"names":[],"mappings":"AA6BA,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAoBD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,iBAAiB,CA2C1E"}
|
package/dist/util/frontmatter.js
CHANGED
|
@@ -27,9 +27,28 @@ function unescapeYamlDoubleQuoted(value) {
|
|
|
27
27
|
return result;
|
|
28
28
|
}
|
|
29
29
|
/**
|
|
30
|
-
* Parse `
|
|
31
|
-
*
|
|
32
|
-
|
|
30
|
+
* Parse a YAML flow sequence like `["a", "b"]` or `[a, b]` into a string array.
|
|
31
|
+
* Returns `undefined` on anything unexpected.
|
|
32
|
+
*/
|
|
33
|
+
function parseYamlFlowSequence(raw) {
|
|
34
|
+
const trimmed = raw.trim();
|
|
35
|
+
if (!trimmed.startsWith("[") || !trimmed.endsWith("]"))
|
|
36
|
+
return undefined;
|
|
37
|
+
const inner = trimmed.slice(1, -1);
|
|
38
|
+
if (!inner.trim())
|
|
39
|
+
return [];
|
|
40
|
+
return inner.split(",").map((item) => {
|
|
41
|
+
const s = item.trim();
|
|
42
|
+
if (s.startsWith('"') && s.endsWith('"')) {
|
|
43
|
+
return unescapeYamlDoubleQuoted(s.slice(1, -1));
|
|
44
|
+
}
|
|
45
|
+
return s;
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Parse `title` / `created` / `topics` from Grounder frontmatter.
|
|
50
|
+
* Accepts the quoted `key: "value"` shape write commands produce, and
|
|
51
|
+
* unquoted `key: value` from earlier files. Not a general YAML parser.
|
|
33
52
|
* Returns `{}` on anything unexpected; never throws.
|
|
34
53
|
*/
|
|
35
54
|
export function parseHandoffFrontmatter(content) {
|
|
@@ -44,13 +63,19 @@ export function parseHandoffFrontmatter(content) {
|
|
|
44
63
|
if (line === "---") {
|
|
45
64
|
break;
|
|
46
65
|
}
|
|
66
|
+
const topicsMatch = /^topics:\s*(\[.*\])\s*$/.exec(line);
|
|
67
|
+
if (topicsMatch) {
|
|
68
|
+
const parsed = parseYamlFlowSequence(topicsMatch[1] ?? "");
|
|
69
|
+
if (parsed)
|
|
70
|
+
result.topics = parsed;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
47
73
|
const quoted = /^(title|created):\s*"(.*)"\s*$/.exec(line);
|
|
48
74
|
if (quoted) {
|
|
49
75
|
const key = quoted[1];
|
|
50
76
|
result[key] = unescapeYamlDoubleQuoted(quoted[2] ?? "");
|
|
51
77
|
continue;
|
|
52
78
|
}
|
|
53
|
-
// Opening quote without a closing pair — ignore rather than treat as unquoted.
|
|
54
79
|
if (/^(title|created):\s*"/.test(line)) {
|
|
55
80
|
continue;
|
|
56
81
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"frontmatter.js","sourceRoot":"","sources":["../../src/util/frontmatter.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,SAAS,wBAAwB,CAAC,KAAa;IAC7C,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;YAC9C,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAC1B,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;gBAClC,MAAM,IAAI,IAAI,CAAC;gBACf,CAAC,EAAE,CAAC;gBACJ,SAAS;YACX,CAAC;YACD,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;gBACjB,MAAM,IAAI,IAAI,CAAC;gBACf,CAAC,EAAE,CAAC;gBACJ,SAAS;YACX,CAAC;YACD,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;gBACjB,MAAM,IAAI,IAAI,CAAC;gBACf,CAAC,EAAE,CAAC;gBACJ,SAAS;YACX,CAAC;QACH,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC;IACrB,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,OAAe;IACrD,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACrC,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK,EAAE,CAAC;YACvB,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,MAAM,MAAM,
|
|
1
|
+
{"version":3,"file":"frontmatter.js","sourceRoot":"","sources":["../../src/util/frontmatter.ts"],"names":[],"mappings":"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"}
|
package/dist/util/fs.d.ts
CHANGED
|
@@ -5,6 +5,20 @@ export declare function fileExists(filePath: string): Promise<boolean>;
|
|
|
5
5
|
* not oversell POSIX execute-bit semantics there.
|
|
6
6
|
*/
|
|
7
7
|
export declare function isExecutable(filePath: string): Promise<boolean>;
|
|
8
|
+
/**
|
|
9
|
+
* Write `content` to `filePath` via tmp file + rename, so a crash mid-write
|
|
10
|
+
* never leaves a truncated file at the real path. Creates the destination
|
|
11
|
+
* directory if needed; best-effort removes the tmp file if `rename` itself
|
|
12
|
+
* throws, so a failed write doesn't leave an orphaned `.tmp-*` sibling behind.
|
|
13
|
+
* `mode` (e.g. the pre-existing file's permission bits), when given, is
|
|
14
|
+
* applied to the tmp file before the rename — otherwise the replaced file
|
|
15
|
+
* would pick up the tmp file's default umask permissions instead of keeping
|
|
16
|
+
* whatever the file already had, which matters for host-owned config files
|
|
17
|
+
* edited by other tools.
|
|
18
|
+
*/
|
|
19
|
+
export declare function writeFileAtomic(filePath: string, content: string, options?: {
|
|
20
|
+
mode?: number;
|
|
21
|
+
}): Promise<void>;
|
|
8
22
|
/**
|
|
9
23
|
* Creates a new markdown file under `dir`; never overwrites.
|
|
10
24
|
* Tries `basename.md`, then `basename_02.md`, `_03`, … with `wx` (O_EXCL)
|
package/dist/util/fs.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../../src/util/fs.ts"],"names":[],"mappings":"AAIA,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOnE;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOrE;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,CAAC,CAgBjB"}
|
|
1
|
+
{"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../../src/util/fs.ts"],"names":[],"mappings":"AAIA,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOnE;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOrE;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,GAC9B,OAAO,CAAC,IAAI,CAAC,CAaf;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,CAAC,CAgBjB"}
|
package/dist/util/fs.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { access, constants, writeFile } from "node:fs/promises";
|
|
1
|
+
import { access, chmod, constants, mkdir, rename, unlink, writeFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { collisionSuffix } from "./timestamp-slug.js";
|
|
4
4
|
export async function fileExists(filePath) {
|
|
@@ -24,6 +24,32 @@ export async function isExecutable(filePath) {
|
|
|
24
24
|
return false;
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Write `content` to `filePath` via tmp file + rename, so a crash mid-write
|
|
29
|
+
* never leaves a truncated file at the real path. Creates the destination
|
|
30
|
+
* directory if needed; best-effort removes the tmp file if `rename` itself
|
|
31
|
+
* throws, so a failed write doesn't leave an orphaned `.tmp-*` sibling behind.
|
|
32
|
+
* `mode` (e.g. the pre-existing file's permission bits), when given, is
|
|
33
|
+
* applied to the tmp file before the rename — otherwise the replaced file
|
|
34
|
+
* would pick up the tmp file's default umask permissions instead of keeping
|
|
35
|
+
* whatever the file already had, which matters for host-owned config files
|
|
36
|
+
* edited by other tools.
|
|
37
|
+
*/
|
|
38
|
+
export async function writeFileAtomic(filePath, content, options = {}) {
|
|
39
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
40
|
+
const tmpPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
41
|
+
await writeFile(tmpPath, content, "utf8");
|
|
42
|
+
if (options.mode !== undefined) {
|
|
43
|
+
await chmod(tmpPath, options.mode);
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
await rename(tmpPath, filePath);
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
await unlink(tmpPath).catch(() => { });
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
27
53
|
/**
|
|
28
54
|
* Creates a new markdown file under `dir`; never overwrites.
|
|
29
55
|
* Tries `basename.md`, then `basename_02.md`, `_03`, … with `wx` (O_EXCL)
|
package/dist/util/fs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fs.js","sourceRoot":"","sources":["../../src/util/fs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"fs.js","sourceRoot":"","sources":["../../src/util/fs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC9F,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,QAAgB;IAC/C,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC;QACvB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,QAAgB;IACjD,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,QAAQ,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC;QACvC,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,QAAgB,EAChB,OAAe,EACf,UAA6B,EAAE;IAE/B,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACzD,MAAM,OAAO,GAAG,GAAG,QAAQ,QAAQ,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;IAC/D,MAAM,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IAC1C,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC/B,MAAM,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,CAAC;IACD,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IAClC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACtC,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,GAAW,EACX,QAAgB,EAChB,OAAe;IAEf,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,SAAS,CAAC;QACR,MAAM,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,QAAQ,GAAG,eAAe,CAAC,CAAC,CAAC,EAAE,CAAC;QACrE,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,KAAK,CAAC,CAAC;QAC9C,IAAI,CAAC;YACH,MAAM,SAAS,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YACrE,OAAO,QAAQ,CAAC;QAClB,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACvD,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;gBACxB,SAAS;YACX,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;AACH,CAAC"}
|
|
@@ -1,15 +1,26 @@
|
|
|
1
1
|
export type MergeJsonResult = {
|
|
2
2
|
ok: true;
|
|
3
3
|
created: boolean;
|
|
4
|
+
changed: boolean;
|
|
4
5
|
} | {
|
|
5
6
|
ok: false;
|
|
6
7
|
error: "unparseable";
|
|
7
8
|
message: string;
|
|
8
9
|
};
|
|
10
|
+
export interface MergeJsonFileOptions {
|
|
11
|
+
/** Compute the result without writing to disk. */
|
|
12
|
+
dryRun?: boolean;
|
|
13
|
+
}
|
|
9
14
|
/**
|
|
10
|
-
* Read a JSON object file (default `{}` if missing), apply `merge`, write pretty-printed
|
|
15
|
+
* Read a JSON object file (default `{}` if missing), apply `merge`, write pretty-printed —
|
|
16
|
+
* only when the merged content actually differs from what's on disk, so callers can trust
|
|
17
|
+
* `changed` to mean a real content change rather than "we ran the merge function."
|
|
18
|
+
* A `merge` that returns its `current` argument by reference is treated as a no-op and
|
|
19
|
+
* never writes, even if the file's on-disk formatting (indentation, line endings, key
|
|
20
|
+
* order) differs from `JSON.stringify(current, null, 2)` — callers rely on this to leave
|
|
21
|
+
* a file with nothing to change byte-for-byte untouched instead of reformatting it.
|
|
11
22
|
* On parse failure or non-object root: leaves the file untouched and returns an error
|
|
12
23
|
* so callers can warn without clobbering shared config.
|
|
13
24
|
*/
|
|
14
|
-
export declare function mergeJsonFile(filePath: string, merge: (current: Record<string, unknown>) => Record<string, unknown
|
|
25
|
+
export declare function mergeJsonFile(filePath: string, merge: (current: Record<string, unknown>) => Record<string, unknown>, options?: MergeJsonFileOptions): Promise<MergeJsonResult>;
|
|
15
26
|
//# sourceMappingURL=merge-json.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"merge-json.d.ts","sourceRoot":"","sources":["../../src/util/merge-json.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"merge-json.d.ts","sourceRoot":"","sources":["../../src/util/merge-json.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,eAAe,GACvB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,GAChD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,aAAa,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzD,MAAM,WAAW,oBAAoB;IACnC,kDAAkD;IAClD,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,aAAa,CACjC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpE,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,eAAe,CAAC,CA2C1B"}
|
package/dist/util/merge-json.js
CHANGED
|
@@ -1,19 +1,25 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import { fileExists } from "./fs.js";
|
|
1
|
+
import { readFile, stat } from "node:fs/promises";
|
|
2
|
+
import { fileExists, writeFileAtomic } from "./fs.js";
|
|
4
3
|
/**
|
|
5
|
-
* Read a JSON object file (default `{}` if missing), apply `merge`, write pretty-printed
|
|
4
|
+
* Read a JSON object file (default `{}` if missing), apply `merge`, write pretty-printed —
|
|
5
|
+
* only when the merged content actually differs from what's on disk, so callers can trust
|
|
6
|
+
* `changed` to mean a real content change rather than "we ran the merge function."
|
|
7
|
+
* A `merge` that returns its `current` argument by reference is treated as a no-op and
|
|
8
|
+
* never writes, even if the file's on-disk formatting (indentation, line endings, key
|
|
9
|
+
* order) differs from `JSON.stringify(current, null, 2)` — callers rely on this to leave
|
|
10
|
+
* a file with nothing to change byte-for-byte untouched instead of reformatting it.
|
|
6
11
|
* On parse failure or non-object root: leaves the file untouched and returns an error
|
|
7
12
|
* so callers can warn without clobbering shared config.
|
|
8
13
|
*/
|
|
9
|
-
export async function mergeJsonFile(filePath, merge) {
|
|
14
|
+
export async function mergeJsonFile(filePath, merge, options = {}) {
|
|
10
15
|
const existed = await fileExists(filePath);
|
|
11
16
|
let current = {};
|
|
17
|
+
let originalRaw;
|
|
12
18
|
if (existed) {
|
|
13
|
-
|
|
19
|
+
originalRaw = await readFile(filePath, "utf8");
|
|
14
20
|
let parsed;
|
|
15
21
|
try {
|
|
16
|
-
parsed = JSON.parse(
|
|
22
|
+
parsed = JSON.parse(originalRaw);
|
|
17
23
|
}
|
|
18
24
|
catch (err) {
|
|
19
25
|
const detail = err instanceof Error ? err.message : String(err);
|
|
@@ -33,8 +39,16 @@ export async function mergeJsonFile(filePath, merge) {
|
|
|
33
39
|
current = parsed;
|
|
34
40
|
}
|
|
35
41
|
const next = merge(current);
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
42
|
+
const nextRaw = `${JSON.stringify(next, null, 2)}\n`;
|
|
43
|
+
const changed = next !== current && nextRaw !== originalRaw;
|
|
44
|
+
if (changed && !options.dryRun) {
|
|
45
|
+
// Preserve the existing file's permission bits across the tmp+rename
|
|
46
|
+
// swap — these are host-owned config files (Cursor/Claude Code), not
|
|
47
|
+
// Grounder's own state, so a rename shouldn't reset them to the tmp
|
|
48
|
+
// file's default umask.
|
|
49
|
+
const mode = existed ? (await stat(filePath)).mode & 0o777 : undefined;
|
|
50
|
+
await writeFileAtomic(filePath, nextRaw, mode !== undefined ? { mode } : {});
|
|
51
|
+
}
|
|
52
|
+
return { ok: true, created: !existed, changed };
|
|
39
53
|
}
|
|
40
54
|
//# sourceMappingURL=merge-json.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"merge-json.js","sourceRoot":"","sources":["../../src/util/merge-json.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"merge-json.js","sourceRoot":"","sources":["../../src/util/merge-json.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAWtD;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,QAAgB,EAChB,KAAoE,EACpE,UAAgC,EAAE;IAElC,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC3C,IAAI,OAAO,GAA4B,EAAE,CAAC;IAC1C,IAAI,WAA+B,CAAC;IAEpC,IAAI,OAAO,EAAE,CAAC;QACZ,WAAW,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAC/C,IAAI,MAAe,CAAC;QACpB,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;QACnC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,MAAM,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAChE,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,KAAK,EAAE,aAAa;gBACpB,OAAO,EAAE,sBAAsB,QAAQ,mBAAmB,MAAM,GAAG;aACpE,CAAC;QACJ,CAAC;QAED,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAC3E,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,KAAK,EAAE,aAAa;gBACpB,OAAO,EAAE,sBAAsB,QAAQ,oCAAoC;aAC5E,CAAC;QACJ,CAAC;QAED,OAAO,GAAG,MAAiC,CAAC;IAC9C,CAAC;IAED,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC;IAC5B,MAAM,OAAO,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;IACrD,MAAM,OAAO,GAAG,IAAI,KAAK,OAAO,IAAI,OAAO,KAAK,WAAW,CAAC;IAE5D,IAAI,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;QAC/B,qEAAqE;QACrE,qEAAqE;QACrE,oEAAoE;QACpE,wBAAwB;QACxB,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;QACvE,MAAM,eAAe,CAAC,QAAQ,EAAE,OAAO,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAC/E,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,CAAC;AAClD,CAAC"}
|
package/dist/util/path.d.ts
CHANGED
|
@@ -17,4 +17,27 @@ export declare function isPathInside(parent: string, child: string): boolean;
|
|
|
17
17
|
* Returns `null` if either path cannot be resolved (e.g. missing).
|
|
18
18
|
*/
|
|
19
19
|
export declare function isRealPathInside(parent: string, child: string): Promise<boolean | null>;
|
|
20
|
+
/** `file://` href for an absolute filesystem path (spaces percent-encoded). */
|
|
21
|
+
export declare function toFileUri(filePath: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* Escape text for a Markdown link label (`[label](...)`) so `\`, `[`, and `]`
|
|
24
|
+
* do not break the link syntax.
|
|
25
|
+
*/
|
|
26
|
+
export declare function escapeMarkdownLinkLabel(text: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* `[label](fileUri)` for an absolute path. Escapes special chars in the label;
|
|
29
|
+
* percent-encodes `(` / `)` in the URI so they do not close the destination.
|
|
30
|
+
*/
|
|
31
|
+
export declare function formatMarkdownFileLink(label: string, filePath: string): string;
|
|
32
|
+
/**
|
|
33
|
+
* Path relative to a vault/project root, always with `/` separators
|
|
34
|
+
* (stable for markdown links and JSON across platforms).
|
|
35
|
+
*/
|
|
36
|
+
export declare function vaultRelativePath(rootDir: string, filePath: string): string;
|
|
37
|
+
/**
|
|
38
|
+
* Plain list title: path relative to `rootDir` with a trailing `.md` stripped
|
|
39
|
+
* (e.g. `migration/phase-1`). Falls back to the filename stem when `rootDir`
|
|
40
|
+
* is omitted.
|
|
41
|
+
*/
|
|
42
|
+
export declare function vaultItemPlainTitle(filePath: string, rootDir?: string): string;
|
|
20
43
|
//# sourceMappingURL=path.d.ts.map
|
package/dist/util/path.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../src/util/path.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../src/util/path.ts"],"names":[],"mappings":"AAKA;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQhD;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,GAAE,MAAsB,GAAG,MAAM,CAElF;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAanE;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAQ7F;AAED,+EAA+E;AAC/E,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAElD;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAG9E;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE3E;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAM9E"}
|
package/dist/util/path.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { realpath } from "node:fs/promises";
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
4
5
|
/**
|
|
5
6
|
* Expand a leading `~` / `~/…` to the user home directory.
|
|
6
7
|
* Leaves other paths unchanged (including a mid-path `~`, which is not home).
|
|
@@ -50,4 +51,42 @@ export async function isRealPathInside(parent, child) {
|
|
|
50
51
|
return null;
|
|
51
52
|
}
|
|
52
53
|
}
|
|
54
|
+
/** `file://` href for an absolute filesystem path (spaces percent-encoded). */
|
|
55
|
+
export function toFileUri(filePath) {
|
|
56
|
+
return pathToFileURL(filePath).href;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Escape text for a Markdown link label (`[label](...)`) so `\`, `[`, and `]`
|
|
60
|
+
* do not break the link syntax.
|
|
61
|
+
*/
|
|
62
|
+
export function escapeMarkdownLinkLabel(text) {
|
|
63
|
+
return text.replace(/\\/g, "\\\\").replace(/\[/g, "\\[").replace(/\]/g, "\\]");
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* `[label](fileUri)` for an absolute path. Escapes special chars in the label;
|
|
67
|
+
* percent-encodes `(` / `)` in the URI so they do not close the destination.
|
|
68
|
+
*/
|
|
69
|
+
export function formatMarkdownFileLink(label, filePath) {
|
|
70
|
+
const uri = toFileUri(filePath).replace(/\(/g, "%28").replace(/\)/g, "%29");
|
|
71
|
+
return `[${escapeMarkdownLinkLabel(label)}](${uri})`;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Path relative to a vault/project root, always with `/` separators
|
|
75
|
+
* (stable for markdown links and JSON across platforms).
|
|
76
|
+
*/
|
|
77
|
+
export function vaultRelativePath(rootDir, filePath) {
|
|
78
|
+
return path.relative(rootDir, filePath).split(path.sep).join("/");
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Plain list title: path relative to `rootDir` with a trailing `.md` stripped
|
|
82
|
+
* (e.g. `migration/phase-1`). Falls back to the filename stem when `rootDir`
|
|
83
|
+
* is omitted.
|
|
84
|
+
*/
|
|
85
|
+
export function vaultItemPlainTitle(filePath, rootDir) {
|
|
86
|
+
if (rootDir === undefined) {
|
|
87
|
+
return path.basename(filePath, ".md");
|
|
88
|
+
}
|
|
89
|
+
const rel = vaultRelativePath(rootDir, filePath);
|
|
90
|
+
return rel.endsWith(".md") ? rel.slice(0, -3) : rel;
|
|
91
|
+
}
|
|
53
92
|
//# sourceMappingURL=path.js.map
|
package/dist/util/path.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path.js","sourceRoot":"","sources":["../../src/util/path.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"path.js","sourceRoot":"","sources":["../../src/util/path.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,9 +2,21 @@ export interface ListHandoffsOptions {
|
|
|
2
2
|
/** Max paths to return (newest first). Omit to return all. */
|
|
3
3
|
limit?: number;
|
|
4
4
|
}
|
|
5
|
+
/** One handoff with its mtime (see {@link listHandoffsDetailed}). */
|
|
6
|
+
export interface HandoffEntry {
|
|
7
|
+
path: string;
|
|
8
|
+
mtimeMs: number;
|
|
9
|
+
}
|
|
5
10
|
/**
|
|
6
|
-
* Lists handoff markdown files under `logsDir
|
|
7
|
-
*
|
|
11
|
+
* Lists handoff markdown files under `logsDir` recursively, newest basename
|
|
12
|
+
* first (timestamp-prefixed names sort correctly), with each entry's mtime
|
|
13
|
+
* attached. Same basename in different subfolders ties break by
|
|
14
|
+
* vault-relative path descending. Missing or empty dirs yield `[]`.
|
|
15
|
+
*
|
|
16
|
+
* Ranking never reads mtime (filenames already sort correctly), so this
|
|
17
|
+
* stats only the paths a `limit` actually keeps, not the whole bucket.
|
|
8
18
|
*/
|
|
19
|
+
export declare function listHandoffsDetailed(logsDir: string, options?: ListHandoffsOptions): Promise<HandoffEntry[]>;
|
|
20
|
+
/** Same ranking as {@link listHandoffsDetailed}, paths only (no stat calls). */
|
|
9
21
|
export declare function listHandoffs(logsDir: string, options?: ListHandoffsOptions): Promise<string[]>;
|
|
10
22
|
//# sourceMappingURL=list-handoffs.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list-handoffs.d.ts","sourceRoot":"","sources":["../../src/vault/list-handoffs.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"list-handoffs.d.ts","sourceRoot":"","sources":["../../src/vault/list-handoffs.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,mBAAmB;IAClC,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qEAAqE;AACrE,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AA6BD;;;;;;;;GAQG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,YAAY,EAAE,CAAC,CAMzB;AAED,gFAAgF;AAChF,wBAAsB,YAAY,CAChC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,MAAM,EAAE,CAAC,CAGnB"}
|