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.
Files changed (221) hide show
  1. package/README.md +150 -271
  2. package/dist/agents/claude.d.ts +4 -4
  3. package/dist/agents/claude.d.ts.map +1 -1
  4. package/dist/agents/claude.js +158 -178
  5. package/dist/agents/claude.js.map +1 -1
  6. package/dist/agents/cursor.d.ts +3 -3
  7. package/dist/agents/cursor.d.ts.map +1 -1
  8. package/dist/agents/cursor.js +97 -143
  9. package/dist/agents/cursor.js.map +1 -1
  10. package/dist/agents/home-skills.d.ts +46 -0
  11. package/dist/agents/home-skills.d.ts.map +1 -0
  12. package/dist/agents/home-skills.js +83 -0
  13. package/dist/agents/home-skills.js.map +1 -0
  14. package/dist/agents/hook-fragment.d.ts +52 -0
  15. package/dist/agents/hook-fragment.d.ts.map +1 -0
  16. package/dist/agents/hook-fragment.js +76 -0
  17. package/dist/agents/hook-fragment.js.map +1 -0
  18. package/dist/agents/hook-install.d.ts +62 -0
  19. package/dist/agents/hook-install.d.ts.map +1 -0
  20. package/dist/agents/hook-install.js +74 -0
  21. package/dist/agents/hook-install.js.map +1 -0
  22. package/dist/agents/hook-runtime.d.ts +80 -13
  23. package/dist/agents/hook-runtime.d.ts.map +1 -1
  24. package/dist/agents/hook-runtime.js +278 -74
  25. package/dist/agents/hook-runtime.js.map +1 -1
  26. package/dist/agents/index.d.ts +8 -14
  27. package/dist/agents/index.d.ts.map +1 -1
  28. package/dist/agents/index.js +13 -19
  29. package/dist/agents/index.js.map +1 -1
  30. package/dist/agents/types.d.ts +56 -16
  31. package/dist/agents/types.d.ts.map +1 -1
  32. package/dist/cli.js +15 -10
  33. package/dist/cli.js.map +1 -1
  34. package/dist/commands/apply.d.ts +69 -0
  35. package/dist/commands/apply.d.ts.map +1 -0
  36. package/dist/commands/apply.js +238 -0
  37. package/dist/commands/apply.js.map +1 -0
  38. package/dist/commands/doctor.d.ts.map +1 -1
  39. package/dist/commands/doctor.js +197 -101
  40. package/dist/commands/doctor.js.map +1 -1
  41. package/dist/commands/handoff/list.d.ts +23 -13
  42. package/dist/commands/handoff/list.d.ts.map +1 -1
  43. package/dist/commands/handoff/list.js +28 -16
  44. package/dist/commands/handoff/list.js.map +1 -1
  45. package/dist/commands/handoff/peek.d.ts +1 -1
  46. package/dist/commands/handoff/peek.d.ts.map +1 -1
  47. package/dist/commands/handoff/peek.js +28 -12
  48. package/dist/commands/handoff/peek.js.map +1 -1
  49. package/dist/commands/handoff.d.ts +2 -0
  50. package/dist/commands/handoff.d.ts.map +1 -1
  51. package/dist/commands/handoff.js +10 -1
  52. package/dist/commands/handoff.js.map +1 -1
  53. package/dist/commands/install-drift.d.ts +34 -0
  54. package/dist/commands/install-drift.d.ts.map +1 -0
  55. package/dist/commands/install-drift.js +72 -0
  56. package/dist/commands/install-drift.js.map +1 -0
  57. package/dist/commands/link.d.ts +12 -0
  58. package/dist/commands/link.d.ts.map +1 -0
  59. package/dist/commands/{repo/init.js → link.js} +35 -27
  60. package/dist/commands/link.js.map +1 -0
  61. package/dist/commands/migrate.d.ts +11 -3
  62. package/dist/commands/migrate.d.ts.map +1 -1
  63. package/dist/commands/migrate.js +66 -34
  64. package/dist/commands/migrate.js.map +1 -1
  65. package/dist/commands/note/list.d.ts +17 -9
  66. package/dist/commands/note/list.d.ts.map +1 -1
  67. package/dist/commands/note/list.js +19 -14
  68. package/dist/commands/note/list.js.map +1 -1
  69. package/dist/commands/note.d.ts +2 -0
  70. package/dist/commands/note.d.ts.map +1 -1
  71. package/dist/commands/note.js +10 -1
  72. package/dist/commands/note.js.map +1 -1
  73. package/dist/commands/output.d.ts +22 -2
  74. package/dist/commands/output.d.ts.map +1 -1
  75. package/dist/commands/output.js +18 -6
  76. package/dist/commands/output.js.map +1 -1
  77. package/dist/commands/overview.d.ts +33 -0
  78. package/dist/commands/overview.d.ts.map +1 -0
  79. package/dist/commands/overview.js +150 -0
  80. package/dist/commands/overview.js.map +1 -0
  81. package/dist/commands/plan/list.d.ts +20 -12
  82. package/dist/commands/plan/list.d.ts.map +1 -1
  83. package/dist/commands/plan/list.js +22 -17
  84. package/dist/commands/plan/list.js.map +1 -1
  85. package/dist/commands/plan.d.ts +2 -0
  86. package/dist/commands/plan.d.ts.map +1 -1
  87. package/dist/commands/plan.js +12 -2
  88. package/dist/commands/plan.js.map +1 -1
  89. package/dist/commands/render-artifact-table.d.ts +85 -0
  90. package/dist/commands/render-artifact-table.d.ts.map +1 -0
  91. package/dist/commands/render-artifact-table.js +232 -0
  92. package/dist/commands/render-artifact-table.js.map +1 -0
  93. package/dist/commands/require-linked.js +2 -2
  94. package/dist/commands/require-linked.js.map +1 -1
  95. package/dist/commands/search.d.ts +23 -0
  96. package/dist/commands/search.d.ts.map +1 -0
  97. package/dist/commands/search.js +288 -0
  98. package/dist/commands/search.js.map +1 -0
  99. package/dist/commands/{vault/init.d.ts → setup.d.ts} +5 -4
  100. package/dist/commands/setup.d.ts.map +1 -0
  101. package/dist/commands/setup.js +210 -0
  102. package/dist/commands/setup.js.map +1 -0
  103. package/dist/commands/status.d.ts +7 -0
  104. package/dist/commands/status.d.ts.map +1 -1
  105. package/dist/commands/status.js +273 -83
  106. package/dist/commands/status.js.map +1 -1
  107. package/dist/commands/upgrade-banner.d.ts +2 -2
  108. package/dist/commands/upgrade-banner.js +2 -2
  109. package/dist/connector/home.d.ts +12 -0
  110. package/dist/connector/home.d.ts.map +1 -1
  111. package/dist/connector/home.js +26 -2
  112. package/dist/connector/home.js.map +1 -1
  113. package/dist/connector/ledger-migrations.d.ts +55 -0
  114. package/dist/connector/ledger-migrations.d.ts.map +1 -0
  115. package/dist/connector/ledger-migrations.js +94 -0
  116. package/dist/connector/ledger-migrations.js.map +1 -0
  117. package/dist/connector/state.d.ts +97 -60
  118. package/dist/connector/state.d.ts.map +1 -1
  119. package/dist/connector/state.js +165 -107
  120. package/dist/connector/state.js.map +1 -1
  121. package/dist/connector/vault.d.ts +2 -0
  122. package/dist/connector/vault.d.ts.map +1 -1
  123. package/dist/connector/vault.js +6 -1
  124. package/dist/connector/vault.js.map +1 -1
  125. package/dist/help.d.ts +3 -3
  126. package/dist/help.d.ts.map +1 -1
  127. package/dist/help.js +97 -56
  128. package/dist/help.js.map +1 -1
  129. package/dist/reconcile/apply.d.ts +31 -0
  130. package/dist/reconcile/apply.d.ts.map +1 -0
  131. package/dist/reconcile/apply.js +97 -0
  132. package/dist/reconcile/apply.js.map +1 -0
  133. package/dist/reconcile/core.d.ts +82 -0
  134. package/dist/reconcile/core.d.ts.map +1 -0
  135. package/dist/reconcile/core.js +163 -0
  136. package/dist/reconcile/core.js.map +1 -0
  137. package/dist/reconcile/disk.d.ts +13 -0
  138. package/dist/reconcile/disk.d.ts.map +1 -0
  139. package/dist/reconcile/disk.js +28 -0
  140. package/dist/reconcile/disk.js.map +1 -0
  141. package/dist/util/frontmatter.d.ts +9 -7
  142. package/dist/util/frontmatter.d.ts.map +1 -1
  143. package/dist/util/frontmatter.js +29 -4
  144. package/dist/util/frontmatter.js.map +1 -1
  145. package/dist/util/fs.d.ts +14 -0
  146. package/dist/util/fs.d.ts.map +1 -1
  147. package/dist/util/fs.js +27 -1
  148. package/dist/util/fs.js.map +1 -1
  149. package/dist/util/merge-json.d.ts +13 -2
  150. package/dist/util/merge-json.d.ts.map +1 -1
  151. package/dist/util/merge-json.js +24 -10
  152. package/dist/util/merge-json.js.map +1 -1
  153. package/dist/util/path.d.ts +23 -0
  154. package/dist/util/path.d.ts.map +1 -1
  155. package/dist/util/path.js +39 -0
  156. package/dist/util/path.js.map +1 -1
  157. package/dist/vault/list-handoffs.d.ts +14 -2
  158. package/dist/vault/list-handoffs.d.ts.map +1 -1
  159. package/dist/vault/list-handoffs.js +40 -23
  160. package/dist/vault/list-handoffs.js.map +1 -1
  161. package/dist/vault/list-markdown.d.ts +8 -0
  162. package/dist/vault/list-markdown.d.ts.map +1 -0
  163. package/dist/vault/list-markdown.js +35 -0
  164. package/dist/vault/list-markdown.js.map +1 -0
  165. package/dist/vault/list-notes.d.ts +15 -6
  166. package/dist/vault/list-notes.d.ts.map +1 -1
  167. package/dist/vault/list-notes.js +23 -27
  168. package/dist/vault/list-notes.js.map +1 -1
  169. package/dist/vault/list-plans.d.ts +12 -3
  170. package/dist/vault/list-plans.d.ts.map +1 -1
  171. package/dist/vault/list-plans.js +20 -24
  172. package/dist/vault/list-plans.js.map +1 -1
  173. package/dist/vault/search.d.ts +55 -0
  174. package/dist/vault/search.d.ts.map +1 -0
  175. package/dist/vault/search.js +432 -0
  176. package/dist/vault/search.js.map +1 -0
  177. package/dist/vault/write-handoff.d.ts +2 -0
  178. package/dist/vault/write-handoff.d.ts.map +1 -1
  179. package/dist/vault/write-handoff.js +5 -0
  180. package/dist/vault/write-handoff.js.map +1 -1
  181. package/dist/vault/write-note.d.ts +2 -0
  182. package/dist/vault/write-note.d.ts.map +1 -1
  183. package/dist/vault/write-note.js +9 -1
  184. package/dist/vault/write-note.js.map +1 -1
  185. package/dist/vault/write-plan.d.ts +4 -0
  186. package/dist/vault/write-plan.d.ts.map +1 -1
  187. package/dist/vault/write-plan.js +14 -1
  188. package/dist/vault/write-plan.js.map +1 -1
  189. package/package.json +11 -4
  190. package/templates/agents/claude/skills/grounder-handoff/SKILL.md +73 -0
  191. package/templates/agents/claude/{commands/grounder-note.md → skills/grounder-note/SKILL.md} +12 -2
  192. package/templates/agents/claude/skills/grounder-overview/SKILL.md +21 -0
  193. package/templates/agents/claude/{commands/grounder-plan.md → skills/grounder-plan/SKILL.md} +13 -5
  194. package/templates/agents/claude/skills/grounder-recall/SKILL.md +34 -0
  195. package/templates/agents/claude/skills/grounder-search/SKILL.md +152 -0
  196. package/templates/agents/cursor/skills/grounder-handoff/SKILL.md +73 -0
  197. package/templates/agents/cursor/{commands/grounder-note.md → skills/grounder-note/SKILL.md} +12 -2
  198. package/templates/agents/cursor/skills/grounder-overview/SKILL.md +21 -0
  199. package/templates/agents/cursor/{commands/grounder-plan.md → skills/grounder-plan/SKILL.md} +13 -5
  200. package/templates/agents/cursor/skills/grounder-recall/SKILL.md +34 -0
  201. package/templates/agents/cursor/skills/grounder-search/SKILL.md +152 -0
  202. package/templates/vault/plan.md +1 -1
  203. package/templates/vault/session-handoff.md +2 -1
  204. package/dist/agents/install-command.d.ts +0 -35
  205. package/dist/agents/install-command.d.ts.map +0 -1
  206. package/dist/agents/install-command.js +0 -65
  207. package/dist/agents/install-command.js.map +0 -1
  208. package/dist/commands/apply-agent-installs.d.ts +0 -34
  209. package/dist/commands/apply-agent-installs.d.ts.map +0 -1
  210. package/dist/commands/apply-agent-installs.js +0 -137
  211. package/dist/commands/apply-agent-installs.js.map +0 -1
  212. package/dist/commands/repo/init.d.ts +0 -11
  213. package/dist/commands/repo/init.d.ts.map +0 -1
  214. package/dist/commands/repo/init.js.map +0 -1
  215. package/dist/commands/vault/init.d.ts.map +0 -1
  216. package/dist/commands/vault/init.js +0 -90
  217. package/dist/commands/vault/init.js.map +0 -1
  218. package/templates/agents/claude/commands/grounder-task-handoff.md +0 -54
  219. package/templates/agents/claude/commands/grounder-task.md +0 -22
  220. package/templates/agents/cursor/commands/grounder-task-handoff.md +0 -54
  221. package/templates/agents/cursor/commands/grounder-task.md +0 -22
@@ -1,19 +1,15 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
- import { fileURLToPath } from "node:url";
4
3
  import { resolveHomeDir } from "../connector/home.js";
5
4
  import { fileExists } from "../util/fs.js";
6
- import { mergeJsonFile } from "../util/merge-json.js";
7
- import { installHookRuntime, isGrounderPeekHookCommand, isHookRuntimeStale, peekHookCommand, } from "./hook-runtime.js";
8
- import { installCommandFile, recordCommandFileHashes } from "./install-command.js";
9
- const pkgRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
10
- const templateDir = path.join(pkgRoot, "templates", "agents", "cursor", "commands");
11
- const COMMANDS = [
12
- "grounder-note.md",
13
- "grounder-plan.md",
14
- "grounder-task-handoff.md",
15
- "grounder-task.md",
16
- ];
5
+ import { homeSkillsLayout } from "./home-skills.js";
6
+ import { isAlreadyConverged, readEventEntries, readHooksObject, removeMatchingEntries, } from "./hook-fragment.js";
7
+ import { installHookFragment, removeHookFragment } from "./hook-install.js";
8
+ import { isGrounderPeekHookCommand, isHookRuntimeStale, peekHookCommand } from "./hook-runtime.js";
9
+ /** Whole-file artifacts: `~/.cursor/skills/` plus the retired `~/.cursor/commands/`. */
10
+ const layout = homeSkillsLayout({ id: "cursor", agentDir: ".cursor" });
11
+ /** Hook event Grounder owns in `hooks.json`. */
12
+ const SESSION_START_EVENT = "sessionStart";
17
13
  /**
18
14
  * Canonical sessionStart command for Cursor (home-local runtime, not `npx`).
19
15
  * Always includes `--json` so stdout matches Cursor's `additional_context` contract.
@@ -22,27 +18,27 @@ const COMMANDS = [
22
18
  export function cursorPeekHookCommand(homeDir, extraArgs = ["--json"]) {
23
19
  return peekHookCommand(homeDir, extraArgs);
24
20
  }
25
- export function cursorCommandsDir(homeDir) {
26
- return path.join(resolveHomeDir(homeDir), ".cursor", "commands");
21
+ export function cursorSkillsDir(homeDir) {
22
+ return layout.skillsDir(homeDir);
27
23
  }
28
24
  /** Absolute path to Cursor's shared hooks config (`~/.cursor/hooks.json`). */
29
25
  export function cursorHooksJsonPath(homeDir) {
30
26
  return path.join(resolveHomeDir(homeDir), ".cursor", "hooks.json");
31
27
  }
32
28
  export function grounderNoteCommandPath(homeDir) {
33
- return path.join(cursorCommandsDir(homeDir), "grounder-note.md");
29
+ return layout.skillPath("grounder-note", homeDir);
34
30
  }
35
31
  export function grounderPlanCommandPath(homeDir) {
36
- return path.join(cursorCommandsDir(homeDir), "grounder-plan.md");
32
+ return layout.skillPath("grounder-plan", homeDir);
37
33
  }
38
- export function grounderTaskHandoffCommandPath(homeDir) {
39
- return path.join(cursorCommandsDir(homeDir), "grounder-task-handoff.md");
34
+ export function grounderHandoffCommandPath(homeDir) {
35
+ return layout.skillPath("grounder-handoff", homeDir);
40
36
  }
41
- export function grounderTaskCommandPath(homeDir) {
42
- return path.join(cursorCommandsDir(homeDir), "grounder-task.md");
37
+ export function grounderRecallCommandPath(homeDir) {
38
+ return layout.skillPath("grounder-recall", homeDir);
43
39
  }
44
40
  export function expectedArtifacts(homeDir) {
45
- return COMMANDS.map((filename) => path.join(cursorCommandsDir(homeDir), filename));
41
+ return layout.expectedArtifacts(homeDir);
46
42
  }
47
43
  /** Paths of hook config this adapter touches — currently just `hooks.json`. */
48
44
  export function expectedHookArtifacts(homeDir) {
@@ -71,37 +67,35 @@ export function expectedHookArtifacts(homeDir) {
71
67
  // - hook entry → { command: string }
72
68
  //
73
69
  // Unlike Claude Code, Cursor has no matcher groups — sessionStart is a flat
74
- // array. Idempotency: {@link isGrounderPeekHookCommand} (runtime path or legacy npx).
70
+ // array. Idempotency / recognizer: {@link isGrounderPeekHookCommand} (runtime
71
+ // path or legacy npx). Always-converge (Ansible `blockinfile` / Kubernetes
72
+ // Server-Side Apply sole-owner model): every recognizer match is removed and
73
+ // replaced with exactly one canonical entry — no conflict / `--force` gate.
75
74
  // ---------------------------------------------------------------------------
76
- function findPeekHookIndex(sessionStart) {
77
- return sessionStart.findIndex((item) => item &&
75
+ function isCursorPeekEntry(item) {
76
+ return (item !== null &&
78
77
  typeof item === "object" &&
79
78
  !Array.isArray(item) &&
80
79
  isGrounderPeekHookCommand(item.command));
81
80
  }
82
- /** Whether `hooks.json` already lists any Grounder peek command (for status labeling). */
83
- async function peekHookHadGrounderEntry(filePath) {
81
+ /** `hooks.sessionStart` from a hooks.json on disk; `null` if absent or unreadable. */
82
+ async function readSessionStart(filePath) {
84
83
  try {
85
- const parsed = JSON.parse(await readFile(filePath, "utf8"));
86
- if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
87
- return false;
88
- }
89
- const hooks = parsed.hooks;
90
- if (!hooks || typeof hooks !== "object" || Array.isArray(hooks)) {
91
- return false;
92
- }
93
- const sessionStart = hooks.sessionStart;
94
- return Array.isArray(sessionStart) && findPeekHookIndex(sessionStart) >= 0;
84
+ return readEventEntries(JSON.parse(await readFile(filePath, "utf8")), SESSION_START_EVENT);
95
85
  }
96
86
  catch {
97
- return false;
87
+ return null;
98
88
  }
99
89
  }
90
+ /** Whether `hooks.json` already lists any Grounder peek command (for status labeling). */
91
+ async function peekHookHadGrounderEntry(filePath) {
92
+ return ((await readSessionStart(filePath)) ?? []).some(isCursorPeekEntry);
93
+ }
100
94
  /**
101
- * Skip only when the canonical command is already present *and* the runtime is
102
- * current for the running grounder version/source. Legacy `npx` entries or a
103
- * stale runtime (missing, or symlinked/copied from a different source) always
104
- * refresh no `--force` required to migrate or to pick up an upgrade.
95
+ * Skip only when exactly one canonical entry is already present *and* the
96
+ * runtime is current for the running grounder version/source. Anything else
97
+ * no entry, a legacy `npx` form, a drifted command, more than one match —
98
+ * always converges. No `--force` required to migrate or pick up an upgrade.
105
99
  */
106
100
  async function peekHookUpToDate(filePath, homeDir) {
107
101
  if (!(await fileExists(filePath))) {
@@ -110,39 +104,22 @@ async function peekHookUpToDate(filePath, homeDir) {
110
104
  if (await isHookRuntimeStale(homeDir)) {
111
105
  return false;
112
106
  }
113
- try {
114
- const parsed = JSON.parse(await readFile(filePath, "utf8"));
115
- if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
116
- return false;
117
- }
118
- const hooks = parsed.hooks;
119
- if (!hooks || typeof hooks !== "object" || Array.isArray(hooks)) {
120
- return false;
121
- }
122
- const sessionStart = hooks.sessionStart;
123
- if (!Array.isArray(sessionStart)) {
124
- return false;
125
- }
126
- const idx = findPeekHookIndex(sessionStart);
127
- if (idx < 0) {
128
- return false;
129
- }
130
- const command = sessionStart[idx].command;
131
- return command === cursorPeekHookCommand(homeDir);
132
- }
133
- catch {
107
+ const sessionStart = await readSessionStart(filePath);
108
+ if (!sessionStart) {
134
109
  return false;
135
110
  }
111
+ return isAlreadyConverged(sessionStart, isCursorPeekEntry, {
112
+ command: cursorPeekHookCommand(homeDir),
113
+ });
136
114
  }
137
115
  /**
138
- * Deep-merge Grounder's sessionStart hook into an existing hooks.json object.
139
- *
140
- * Preserves every key except the nested path it owns. Strategy:
141
- * 1. If a Grounder peek hook already exists (runtime or legacy npx) → replace in place.
142
- * 2. Else → append Grounder's entry to `hooks.sessionStart`.
116
+ * Converge Grounder's sessionStart hook into an existing hooks.json object:
117
+ * remove every recognizer match (runtime-form or legacy npx, however many),
118
+ * append exactly one canonical entry.
143
119
  *
144
- * On a brand-new file (`isFreshFile`), also sets `version: 1` when absent.
145
- * Existing files keep whatever `version` (or lack of it) they already have.
120
+ * Preserves every key except the nested path it owns. On a brand-new file
121
+ * (`isFreshFile`), also sets `version: 1` when absent. Existing files keep
122
+ * whatever `version` (or lack of it) they already have.
146
123
  *
147
124
  * @param current - Parsed hooks.json root (object). Other top-level keys untouched.
148
125
  * @param isFreshFile - True when the file did not exist before this write
@@ -154,91 +131,68 @@ function mergeCursorHooks(current, isFreshFile, homeDir) {
154
131
  if (isFreshFile && next.version === undefined) {
155
132
  next.version = 1;
156
133
  }
157
- const hooks = next.hooks && typeof next.hooks === "object" && !Array.isArray(next.hooks)
158
- ? { ...next.hooks }
159
- : {};
160
- const existing = Array.isArray(hooks.sessionStart) ? [...hooks.sessionStart] : [];
161
- const entry = { command: cursorPeekHookCommand(homeDir) };
162
- const idx = findPeekHookIndex(existing);
163
- if (idx >= 0) {
164
- // Path 1: refresh existing Grounder hook entry in place
165
- existing[idx] = entry;
166
- }
167
- else {
168
- // Path 2: append — first install, or file had other sessionStart hooks only
169
- existing.push(entry);
170
- }
171
- next.hooks = { ...hooks, sessionStart: existing };
134
+ const hooks = readHooksObject(next, cursorHooksJsonPath(homeDir));
135
+ const raw = hooks[SESSION_START_EVENT];
136
+ const existing = Array.isArray(raw) ? raw : [];
137
+ const cleaned = removeMatchingEntries(existing, isCursorPeekEntry);
138
+ cleaned.push({ command: cursorPeekHookCommand(homeDir) });
139
+ next.hooks = { ...hooks, [SESSION_START_EVENT]: cleaned };
172
140
  return next;
173
141
  }
174
142
  /**
175
- * Install (or refresh) Grounder's sessionStart teaser hook into `~/.cursor/hooks.json`.
176
- *
177
- * Also materializes `~/.grounder/runtime` (see {@link installHookRuntime}).
178
- *
179
- * Force semantics:
180
- * - Up-to-date canonical entry + fresh runtime and `force` false → skip
181
- * - Otherwise → refresh runtime + merge host config
182
- *
183
- * Unparseable hooks.json: {@link mergeJsonFile} backs off and this throws (never clobbers).
143
+ * Remove every Grounder peek entry from `hooks.sessionStart`, touching
144
+ * nothing else. Returns `current` verbatim (no restructuring at all) when
145
+ * there's no Grounder entry to remove, so `mergeJsonFile` sees no change and
146
+ * leaves an unrelated `hooks.json` untouched instead of reformatting it.
184
147
  */
185
- async function installHooks(opts) {
186
- const dest = cursorHooksJsonPath(opts.homeDir);
187
- const upToDate = await peekHookUpToDate(dest, opts.homeDir);
188
- if (upToDate && !opts.force) {
189
- return { artifacts: { [dest]: "skipped" } };
148
+ function removeCursorHooks(current) {
149
+ const hooks = current.hooks && typeof current.hooks === "object" && !Array.isArray(current.hooks)
150
+ ? current.hooks
151
+ : undefined;
152
+ const raw = hooks?.[SESSION_START_EVENT];
153
+ const existing = Array.isArray(raw) ? raw : [];
154
+ const cleaned = removeMatchingEntries(existing, isCursorPeekEntry);
155
+ if (cleaned.length === existing.length) {
156
+ return current;
190
157
  }
191
- if (opts.dryRun) {
192
- const fileExisted = await fileExists(dest);
193
- const hadGrounderEntry = fileExisted && (await peekHookHadGrounderEntry(dest));
194
- const status = hadGrounderEntry ? "overwritten" : "created";
195
- return { artifacts: { [dest]: status } };
196
- }
197
- await installHookRuntime({ homeDir: opts.homeDir });
198
- const fileExisted = await fileExists(dest);
199
- const hadGrounderEntry = fileExisted && (await peekHookHadGrounderEntry(dest));
200
- const result = await mergeJsonFile(dest, (current) => mergeCursorHooks(current, !fileExisted, opts.homeDir));
201
- if (!result.ok) {
202
- throw new Error(result.message);
203
- }
204
- const status = hadGrounderEntry ? "overwritten" : "created";
205
- return { artifacts: { [dest]: status } };
158
+ return {
159
+ ...current,
160
+ hooks: { ...hooks, [SESSION_START_EVENT]: cleaned },
161
+ };
162
+ }
163
+ /**
164
+ * Install (or converge) Grounder's sessionStart teaser hook into `~/.cursor/hooks.json`.
165
+ * @see {@link installHookFragment} for the shared install/report scaffolding.
166
+ */
167
+ async function installHooks(opts) {
168
+ return installHookFragment({
169
+ dest: cursorHooksJsonPath(opts.homeDir),
170
+ isUpToDate: (filePath) => peekHookUpToDate(filePath, opts.homeDir),
171
+ hasGrounderEntry: peekHookHadGrounderEntry,
172
+ merge: (current, fileExisted) => mergeCursorHooks(current, !fileExisted, opts.homeDir),
173
+ }, opts);
174
+ }
175
+ /**
176
+ * Remove Grounder's sessionStart hook entry entirely (`--no-hooks`) — the
177
+ * opt-out must also remove the fragment, not just flip `hooksEnabled` false,
178
+ * or the session hook keeps firing and the next plain `migrate` would find
179
+ * nothing to converge against (an absent entry looks like "never installed",
180
+ * which is exactly what makes the tri-state's `false` sticky in the first
181
+ * place).
182
+ */
183
+ async function removeHooks(opts) {
184
+ return removeHookFragment(cursorHooksJsonPath(opts.homeDir), removeCursorHooks, opts);
206
185
  }
207
186
  export const cursor = {
208
187
  id: "cursor",
209
188
  name: "Cursor",
210
- commandsSchema: 2,
211
- hooksSchema: 1,
212
- async isInstalled() {
213
- return fileExists(path.join(resolveHomeDir(), ".cursor"));
214
- },
189
+ isInstalled: layout.isInstalled,
215
190
  expectedArtifacts,
216
191
  expectedHookArtifacts,
217
- async install(opts) {
218
- const artifacts = {};
219
- const files = {};
220
- for (const filename of COMMANDS) {
221
- const { dest, status, hash } = await installCommandFile({
222
- ...opts,
223
- agentId: cursor.id,
224
- templateDir,
225
- commandsDir: cursorCommandsDir(opts.homeDir),
226
- filename,
227
- });
228
- artifacts[dest] = status;
229
- if (hash) {
230
- files[dest] = { hash };
231
- }
232
- }
233
- await recordCommandFileHashes({
234
- agentId: cursor.id,
235
- commandsSchema: cursor.commandsSchema,
236
- files,
237
- homeDir: opts.homeDir,
238
- dryRun: opts.dryRun,
239
- });
240
- return { artifacts };
241
- },
192
+ desiredArtifacts: layout.desiredArtifacts,
193
+ tombstones: layout.tombstones,
194
+ ownedPrefixes: layout.ownedPrefixes,
242
195
  installHooks,
196
+ removeHooks,
243
197
  };
244
198
  //# sourceMappingURL=cursor.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"cursor.js","sourceRoot":"","sources":["../../src/agents/cursor.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cursor.js","sourceRoot":"","sources":["../../src/agents/cursor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The whole-file install layout Cursor and Claude Code share: Grounder's
3
+ * skills live at `~/<agentDir>/skills/<skill>/SKILL.md`, and the
4
+ * pre-Agent-Skills command files they replaced lived at
5
+ * `~/<agentDir>/commands/<skill>.md`. Give {@link homeSkillsLayout} the two
6
+ * names that differ between the two agents and it derives every path, template
7
+ * render, tombstone, and owned prefix an adapter needs for those artifacts.
8
+ *
9
+ * Hook config is deliberately not here. `~/.cursor/hooks.json` and
10
+ * `~/.claude/settings.json` differ in filename, in JSON shape (a flat array
11
+ * vs. matcher groups), and in what "already converged" even means, so each
12
+ * adapter keeps its own fragment reconciler; `hook-fragment.ts` and
13
+ * `hook-install.ts` hold the parts of *that* job which genuinely are shared.
14
+ */
15
+ /** Skill directory names. Both adapters ship the same set. */
16
+ declare const SKILL_NAMES: readonly ["grounder-note", "grounder-search", "grounder-overview", "grounder-plan", "grounder-handoff", "grounder-recall"];
17
+ export type SkillName = (typeof SKILL_NAMES)[number];
18
+ export interface HomeSkillsLayout {
19
+ /** `~/<agentDir>/skills` — where today's `SKILL.md` files live. */
20
+ skillsDir(homeDir?: string): string;
21
+ /** `~/<agentDir>/commands` — where the retired pre-skill command files lived. */
22
+ legacyCommandsDir(homeDir?: string): string;
23
+ /** Absolute path to one skill's `SKILL.md`. */
24
+ skillPath(skill: SkillName, homeDir?: string): string;
25
+ /** @see AgentAdapter.expectedArtifacts */
26
+ expectedArtifacts(homeDir?: string): string[];
27
+ /** @see AgentAdapter.desiredArtifacts */
28
+ desiredArtifacts(homeDir?: string, options?: {
29
+ invocation?: string;
30
+ }): Promise<Record<string, string>>;
31
+ /** @see AgentAdapter.tombstones */
32
+ tombstones(homeDir?: string): string[];
33
+ /** @see AgentAdapter.ownedPrefixes */
34
+ ownedPrefixes(homeDir?: string): string[];
35
+ /** @see AgentAdapter.isInstalled */
36
+ isInstalled(): Promise<boolean>;
37
+ }
38
+ export interface HomeSkillsLayoutOptions {
39
+ /** Adapter id — names the template directory (`templates/agents/<id>/skills`). */
40
+ id: string;
41
+ /** This agent's config directory, relative to home (e.g. `.cursor`). */
42
+ agentDir: string;
43
+ }
44
+ export declare function homeSkillsLayout(options: HomeSkillsLayoutOptions): HomeSkillsLayout;
45
+ export {};
46
+ //# sourceMappingURL=home-skills.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"home-skills.d.ts","sourceRoot":"","sources":["../../src/agents/home-skills.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAYH,8DAA8D;AAC9D,QAAA,MAAM,WAAW,4HAOP,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAsBrD,MAAM,WAAW,gBAAgB;IAC/B,mEAAmE;IACnE,SAAS,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACpC,iFAAiF;IACjF,iBAAiB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5C,+CAA+C;IAC/C,SAAS,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACtD,0CAA0C;IAC1C,iBAAiB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC9C,yCAAyC;IACzC,gBAAgB,CACd,OAAO,CAAC,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,GAChC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACnC,mCAAmC;IACnC,UAAU,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACvC,sCAAsC;IACtC,aAAa,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC1C,oCAAoC;IACpC,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,uBAAuB;IACtC,kFAAkF;IAClF,EAAE,EAAE,MAAM,CAAC;IACX,wEAAwE;IACxE,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,gBAAgB,CA4CnF"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * The whole-file install layout Cursor and Claude Code share: Grounder's
3
+ * skills live at `~/<agentDir>/skills/<skill>/SKILL.md`, and the
4
+ * pre-Agent-Skills command files they replaced lived at
5
+ * `~/<agentDir>/commands/<skill>.md`. Give {@link homeSkillsLayout} the two
6
+ * names that differ between the two agents and it derives every path, template
7
+ * render, tombstone, and owned prefix an adapter needs for those artifacts.
8
+ *
9
+ * Hook config is deliberately not here. `~/.cursor/hooks.json` and
10
+ * `~/.claude/settings.json` differ in filename, in JSON shape (a flat array
11
+ * vs. matcher groups), and in what "already converged" even means, so each
12
+ * adapter keeps its own fragment reconciler; `hook-fragment.ts` and
13
+ * `hook-install.ts` hold the parts of *that* job which genuinely are shared.
14
+ */
15
+ import { readFile } from "node:fs/promises";
16
+ import path from "node:path";
17
+ import { fileURLToPath } from "node:url";
18
+ import { resolveHomeDir } from "../connector/home.js";
19
+ import { fileExists } from "../util/fs.js";
20
+ import { runtimeInvocation } from "./hook-runtime.js";
21
+ /** Package root (`packages/grounder`) when running from `dist/agents/`. */
22
+ const pkgRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
23
+ /** Skill directory names. Both adapters ship the same set. */
24
+ const SKILL_NAMES = [
25
+ "grounder-note",
26
+ "grounder-search",
27
+ "grounder-overview",
28
+ "grounder-plan",
29
+ "grounder-handoff",
30
+ "grounder-recall",
31
+ ];
32
+ /** Relative `<skill>/SKILL.md` paths, resolved under whichever agent's skills dir. */
33
+ const SKILL_FILES = SKILL_NAMES.map((name) => path.join(name, "SKILL.md"));
34
+ /**
35
+ * Frozen historical fact about the schema-3 (pre-skill) install layout —
36
+ * deliberately spelled out rather than derived from {@link SKILL_NAMES}, which
37
+ * describes today's Agent Skills layout. Deriving it would rewrite history the
38
+ * moment a skill is added or renamed, retiring paths that never existed while
39
+ * forgetting one that does. Safe to delete once schema-3 installs are assumed
40
+ * extinct in the wild — a maintainer call, not something to automate via a
41
+ * version check.
42
+ */
43
+ const LEGACY_COMMAND_FILENAMES = [
44
+ "grounder-note.md",
45
+ "grounder-search.md",
46
+ "grounder-plan.md",
47
+ "grounder-task-handoff.md",
48
+ "grounder-task.md",
49
+ ];
50
+ export function homeSkillsLayout(options) {
51
+ const templateDir = path.join(pkgRoot, "templates", "agents", options.id, "skills");
52
+ const agentRoot = (homeDir) => path.join(resolveHomeDir(homeDir), options.agentDir);
53
+ const skillsDir = (homeDir) => path.join(agentRoot(homeDir), "skills");
54
+ const legacyCommandsDir = (homeDir) => path.join(agentRoot(homeDir), "commands");
55
+ return {
56
+ skillsDir,
57
+ legacyCommandsDir,
58
+ skillPath: (skill, homeDir) => path.join(skillsDir(homeDir), skill, "SKILL.md"),
59
+ expectedArtifacts: (homeDir) => SKILL_FILES.map((filename) => path.join(skillsDir(homeDir), filename)),
60
+ // Arrow function, not a shorthand method — every member of this object
61
+ // must be reachable with no bound `this` (see the file-level doc comment
62
+ // on why `cursor.ts`/`claude.ts` detach these off the closure), and a
63
+ // shorthand method is the one member shape that can silently start using
64
+ // `this` and type-check fine while breaking only at those detached call
65
+ // sites.
66
+ desiredArtifacts: async (homeDir, renderOptions) => {
67
+ const cli = renderOptions?.invocation ?? runtimeInvocation(homeDir);
68
+ const dir = skillsDir(homeDir);
69
+ const templates = await Promise.all(SKILL_FILES.map((filename) => readFile(path.join(templateDir, filename), "utf8")));
70
+ const desired = {};
71
+ SKILL_FILES.forEach((filename, i) => {
72
+ desired[path.join(dir, filename)] = templates[i].replaceAll("{{GROUNDER_CLI}}", cli);
73
+ });
74
+ return desired;
75
+ },
76
+ tombstones: (homeDir) => LEGACY_COMMAND_FILENAMES.map((filename) => path.join(legacyCommandsDir(homeDir), filename)),
77
+ ownedPrefixes: (homeDir) => [skillsDir(homeDir), legacyCommandsDir(homeDir)],
78
+ // No `homeDir` argument on purpose: detection runs before any per-call
79
+ // home override exists, off `GROUNDER_HOME` / `withHomeDir` alone.
80
+ isInstalled: async () => fileExists(agentRoot()),
81
+ };
82
+ }
83
+ //# sourceMappingURL=home-skills.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"home-skills.js","sourceRoot":"","sources":["../../src/agents/home-skills.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAEtD,2EAA2E;AAC3E,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;AAEpF,8DAA8D;AAC9D,MAAM,WAAW,GAAG;IAClB,eAAe;IACf,iBAAiB;IACjB,mBAAmB;IACnB,eAAe;IACf,kBAAkB;IAClB,iBAAiB;CACT,CAAC;AAIX,sFAAsF;AACtF,MAAM,WAAW,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC;AAE3E;;;;;;;;GAQG;AACH,MAAM,wBAAwB,GAAG;IAC/B,kBAAkB;IAClB,oBAAoB;IACpB,kBAAkB;IAClB,0BAA0B;IAC1B,kBAAkB;CACV,CAAC;AA+BX,MAAM,UAAU,gBAAgB,CAAC,OAAgC;IAC/D,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;IACpF,MAAM,SAAS,GAAG,CAAC,OAAgB,EAAU,EAAE,CAC7C,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;IACvD,MAAM,SAAS,GAAG,CAAC,OAAgB,EAAU,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,CAAC;IACxF,MAAM,iBAAiB,GAAG,CAAC,OAAgB,EAAU,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,UAAU,CAAC,CAAC;IAElG,OAAO;QACL,SAAS;QACT,iBAAiB;QAEjB,SAAS,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,UAAU,CAAC;QAE/E,iBAAiB,EAAE,CAAC,OAAO,EAAE,EAAE,CAC7B,WAAW,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,CAAC;QAExE,uEAAuE;QACvE,yEAAyE;QACzE,sEAAsE;QACtE,yEAAyE;QACzE,wEAAwE;QACxE,SAAS;QACT,gBAAgB,EAAE,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,EAAE;YACjD,MAAM,GAAG,GAAG,aAAa,EAAE,UAAU,IAAI,iBAAiB,CAAC,OAAO,CAAC,CAAC;YACpE,MAAM,GAAG,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;YAC/B,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,GAAG,CACjC,WAAW,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,CAAC,CAClF,CAAC;YACF,MAAM,OAAO,GAA2B,EAAE,CAAC;YAC3C,WAAW,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAC,EAAE,EAAE;gBAClC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,kBAAkB,EAAE,GAAG,CAAC,CAAC;YACvF,CAAC,CAAC,CAAC;YACH,OAAO,OAAO,CAAC;QACjB,CAAC;QAED,UAAU,EAAE,CAAC,OAAO,EAAE,EAAE,CACtB,wBAAwB,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,CAAC;QAE7F,aAAa,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,iBAAiB,CAAC,OAAO,CAAC,CAAC;QAE5E,uEAAuE;QACvE,mEAAmE;QACnE,WAAW,EAAE,KAAK,IAAI,EAAE,CAAC,UAAU,CAAC,SAAS,EAAE,CAAC;KACjD,CAAC;AACJ,CAAC"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Generic pieces of the session-hook fragment reconciler shared by
3
+ * `cursor.ts` (flat `hooks.sessionStart` array) and `claude.ts` (nested
4
+ * `hooks.SessionStart` matcher groups, each with its own `hooks` array).
5
+ *
6
+ * Model: Ansible `blockinfile` / Kubernetes Server-Side Apply's sole-owner
7
+ * case — locate every entry a recognizer predicate matches (there may be
8
+ * more than one: a legacy `npx grounder handoff peek` entry alongside a
9
+ * runtime-form one, say), remove all of them, and always converge to exactly
10
+ * one canonical entry. No conflict / `--force` gate — Grounder solely owns
11
+ * this one nested entry, there is nothing a user could have "locally edited"
12
+ * the way a whole skill file can be.
13
+ */
14
+ /**
15
+ * The entries registered for one hook event (`hooks.<event>`) in an
16
+ * already-parsed config root, or `null` when the file has no such array —
17
+ * a non-object root, no `hooks` key, a `hooks` that isn't an object, or an
18
+ * event key that is absent or not an array.
19
+ *
20
+ * `null` rather than `[]` because the two callers want opposite things from
21
+ * "nothing there": counting existing entries treats it as empty, while
22
+ * "already converged?" must answer `false` and go on to converge.
23
+ */
24
+ export declare function readEventEntries(parsed: unknown, event: string): unknown[] | null;
25
+ /**
26
+ * Grounder's fragment lives at `hooks.<event>`, so a merge has to read `hooks`
27
+ * as a JSON object. A present-but-non-object `hooks` (an array, a string, a
28
+ * number) is refused rather than replaced: these are shared config files whose
29
+ * unrelated keys Grounder promises to preserve, and silently dropping a value
30
+ * it can't interpret is the one way a merge could destroy user data. Refusing
31
+ * matches how {@link mergeJsonFile} already backs off an unparseable root or a
32
+ * non-object top level — same contract, one level deeper.
33
+ *
34
+ * `null` and absent both mean "nothing there yet" and yield a fresh `{}` —
35
+ * neither carries content a user could lose.
36
+ *
37
+ * Returns a shallow copy, so callers can mutate the result without touching
38
+ * the parsed original (`mergeJsonFile` compares by reference to decide whether
39
+ * anything changed).
40
+ */
41
+ export declare function readHooksObject(current: Record<string, unknown>, filePath: string): Record<string, unknown>;
42
+ /** Remove every entry `isMatch` accepts, preserving order of the rest. */
43
+ export declare function removeMatchingEntries<T>(entries: readonly T[], isMatch: (entry: T) => boolean): T[];
44
+ /**
45
+ * True when `entries` already holds exactly one match and it is byte-for-byte
46
+ * the canonical entry (same keys, same order) — the only case a fragment
47
+ * reconciler should report as already-converged (no write needed). Two
48
+ * matches, zero matches, or a single match with drifted content all count as
49
+ * "not yet converged."
50
+ */
51
+ export declare function isAlreadyConverged<T>(entries: readonly T[], isMatch: (entry: T) => boolean, canonical: T): boolean;
52
+ //# sourceMappingURL=hook-fragment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook-fragment.d.ts","sourceRoot":"","sources":["../../src/agents/hook-fragment.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,EAAE,GAAG,IAAI,CAUjF;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,QAAQ,EAAE,MAAM,GACf,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CASzB;AAED,0EAA0E;AAC1E,wBAAgB,qBAAqB,CAAC,CAAC,EACrC,OAAO,EAAE,SAAS,CAAC,EAAE,EACrB,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,OAAO,GAC7B,CAAC,EAAE,CAEL;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAClC,OAAO,EAAE,SAAS,CAAC,EAAE,EACrB,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,OAAO,EAC9B,SAAS,EAAE,CAAC,GACX,OAAO,CAGT"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Generic pieces of the session-hook fragment reconciler shared by
3
+ * `cursor.ts` (flat `hooks.sessionStart` array) and `claude.ts` (nested
4
+ * `hooks.SessionStart` matcher groups, each with its own `hooks` array).
5
+ *
6
+ * Model: Ansible `blockinfile` / Kubernetes Server-Side Apply's sole-owner
7
+ * case — locate every entry a recognizer predicate matches (there may be
8
+ * more than one: a legacy `npx grounder handoff peek` entry alongside a
9
+ * runtime-form one, say), remove all of them, and always converge to exactly
10
+ * one canonical entry. No conflict / `--force` gate — Grounder solely owns
11
+ * this one nested entry, there is nothing a user could have "locally edited"
12
+ * the way a whole skill file can be.
13
+ */
14
+ /**
15
+ * The entries registered for one hook event (`hooks.<event>`) in an
16
+ * already-parsed config root, or `null` when the file has no such array —
17
+ * a non-object root, no `hooks` key, a `hooks` that isn't an object, or an
18
+ * event key that is absent or not an array.
19
+ *
20
+ * `null` rather than `[]` because the two callers want opposite things from
21
+ * "nothing there": counting existing entries treats it as empty, while
22
+ * "already converged?" must answer `false` and go on to converge.
23
+ */
24
+ export function readEventEntries(parsed, event) {
25
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
26
+ return null;
27
+ }
28
+ const hooks = parsed.hooks;
29
+ if (!hooks || typeof hooks !== "object" || Array.isArray(hooks)) {
30
+ return null;
31
+ }
32
+ const entries = hooks[event];
33
+ return Array.isArray(entries) ? entries : null;
34
+ }
35
+ /**
36
+ * Grounder's fragment lives at `hooks.<event>`, so a merge has to read `hooks`
37
+ * as a JSON object. A present-but-non-object `hooks` (an array, a string, a
38
+ * number) is refused rather than replaced: these are shared config files whose
39
+ * unrelated keys Grounder promises to preserve, and silently dropping a value
40
+ * it can't interpret is the one way a merge could destroy user data. Refusing
41
+ * matches how {@link mergeJsonFile} already backs off an unparseable root or a
42
+ * non-object top level — same contract, one level deeper.
43
+ *
44
+ * `null` and absent both mean "nothing there yet" and yield a fresh `{}` —
45
+ * neither carries content a user could lose.
46
+ *
47
+ * Returns a shallow copy, so callers can mutate the result without touching
48
+ * the parsed original (`mergeJsonFile` compares by reference to decide whether
49
+ * anything changed).
50
+ */
51
+ export function readHooksObject(current, filePath) {
52
+ const hooks = current.hooks;
53
+ if (hooks === undefined || hooks === null) {
54
+ return {};
55
+ }
56
+ if (typeof hooks !== "object" || Array.isArray(hooks)) {
57
+ throw new Error(`Refusing to modify ${filePath}: "hooks" must be a JSON object`);
58
+ }
59
+ return { ...hooks };
60
+ }
61
+ /** Remove every entry `isMatch` accepts, preserving order of the rest. */
62
+ export function removeMatchingEntries(entries, isMatch) {
63
+ return entries.filter((entry) => !isMatch(entry));
64
+ }
65
+ /**
66
+ * True when `entries` already holds exactly one match and it is byte-for-byte
67
+ * the canonical entry (same keys, same order) — the only case a fragment
68
+ * reconciler should report as already-converged (no write needed). Two
69
+ * matches, zero matches, or a single match with drifted content all count as
70
+ * "not yet converged."
71
+ */
72
+ export function isAlreadyConverged(entries, isMatch, canonical) {
73
+ const matches = entries.filter(isMatch);
74
+ return matches.length === 1 && JSON.stringify(matches[0]) === JSON.stringify(canonical);
75
+ }
76
+ //# sourceMappingURL=hook-fragment.js.map