grounder 0.2.0 → 0.3.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 (149) hide show
  1. package/README.md +81 -32
  2. package/dist/agents/claude.d.ts +1 -0
  3. package/dist/agents/claude.d.ts.map +1 -1
  4. package/dist/agents/claude.js +38 -13
  5. package/dist/agents/claude.js.map +1 -1
  6. package/dist/agents/cursor.d.ts +1 -0
  7. package/dist/agents/cursor.d.ts.map +1 -1
  8. package/dist/agents/cursor.js +38 -13
  9. package/dist/agents/cursor.js.map +1 -1
  10. package/dist/agents/hook-runtime.d.ts +65 -21
  11. package/dist/agents/hook-runtime.d.ts.map +1 -1
  12. package/dist/agents/hook-runtime.js +172 -31
  13. package/dist/agents/hook-runtime.js.map +1 -1
  14. package/dist/agents/index.d.ts +18 -0
  15. package/dist/agents/index.d.ts.map +1 -1
  16. package/dist/agents/index.js +27 -4
  17. package/dist/agents/index.js.map +1 -1
  18. package/dist/agents/install-command.d.ts +37 -0
  19. package/dist/agents/install-command.d.ts.map +1 -0
  20. package/dist/agents/install-command.js +65 -0
  21. package/dist/agents/install-command.js.map +1 -0
  22. package/dist/agents/types.d.ts +19 -1
  23. package/dist/agents/types.d.ts.map +1 -1
  24. package/dist/cli.js +58 -60
  25. package/dist/cli.js.map +1 -1
  26. package/dist/commands/apply-agent-installs.d.ts +34 -0
  27. package/dist/commands/apply-agent-installs.d.ts.map +1 -0
  28. package/dist/commands/apply-agent-installs.js +137 -0
  29. package/dist/commands/apply-agent-installs.js.map +1 -0
  30. package/dist/commands/doctor.d.ts.map +1 -1
  31. package/dist/commands/doctor.js +202 -51
  32. package/dist/commands/doctor.js.map +1 -1
  33. package/dist/commands/handoff/list.d.ts.map +1 -1
  34. package/dist/commands/handoff/list.js +5 -0
  35. package/dist/commands/handoff/list.js.map +1 -1
  36. package/dist/commands/handoff/peek.d.ts +5 -2
  37. package/dist/commands/handoff/peek.d.ts.map +1 -1
  38. package/dist/commands/handoff/peek.js +47 -6
  39. package/dist/commands/handoff/peek.js.map +1 -1
  40. package/dist/commands/handoff.d.ts.map +1 -1
  41. package/dist/commands/handoff.js +5 -0
  42. package/dist/commands/handoff.js.map +1 -1
  43. package/dist/commands/migrate.d.ts +23 -0
  44. package/dist/commands/migrate.d.ts.map +1 -0
  45. package/dist/commands/migrate.js +104 -0
  46. package/dist/commands/migrate.js.map +1 -0
  47. package/dist/commands/note.d.ts.map +1 -1
  48. package/dist/commands/note.js +5 -0
  49. package/dist/commands/note.js.map +1 -1
  50. package/dist/commands/package-version-notice.d.ts +18 -0
  51. package/dist/commands/package-version-notice.d.ts.map +1 -0
  52. package/dist/commands/package-version-notice.js +39 -0
  53. package/dist/commands/package-version-notice.js.map +1 -0
  54. package/dist/commands/path/logs.d.ts +1 -1
  55. package/dist/commands/path/logs.d.ts.map +1 -1
  56. package/dist/commands/path/logs.js +6 -1
  57. package/dist/commands/path/logs.js.map +1 -1
  58. package/dist/commands/path/notes.d.ts +1 -1
  59. package/dist/commands/path/notes.d.ts.map +1 -1
  60. package/dist/commands/path/notes.js +6 -1
  61. package/dist/commands/path/notes.js.map +1 -1
  62. package/dist/commands/path/plans.d.ts +7 -0
  63. package/dist/commands/path/plans.d.ts.map +1 -0
  64. package/dist/commands/path/plans.js +22 -0
  65. package/dist/commands/path/plans.js.map +1 -0
  66. package/dist/commands/plan/list.d.ts +23 -0
  67. package/dist/commands/plan/list.d.ts.map +1 -0
  68. package/dist/commands/plan/list.js +69 -0
  69. package/dist/commands/plan/list.js.map +1 -0
  70. package/dist/commands/plan.d.ts +34 -0
  71. package/dist/commands/plan.d.ts.map +1 -0
  72. package/dist/commands/plan.js +121 -0
  73. package/dist/commands/plan.js.map +1 -0
  74. package/dist/commands/repo/init.d.ts.map +1 -1
  75. package/dist/commands/repo/init.js +12 -1
  76. package/dist/commands/repo/init.js.map +1 -1
  77. package/dist/commands/status.d.ts +1 -1
  78. package/dist/commands/status.d.ts.map +1 -1
  79. package/dist/commands/status.js +52 -9
  80. package/dist/commands/status.js.map +1 -1
  81. package/dist/commands/upgrade-banner.d.ts +9 -0
  82. package/dist/commands/upgrade-banner.d.ts.map +1 -0
  83. package/dist/commands/upgrade-banner.js +27 -0
  84. package/dist/commands/upgrade-banner.js.map +1 -0
  85. package/dist/commands/vault/init.d.ts.map +1 -1
  86. package/dist/commands/vault/init.js +18 -29
  87. package/dist/commands/vault/init.js.map +1 -1
  88. package/dist/connector/repo.d.ts +2 -0
  89. package/dist/connector/repo.d.ts.map +1 -1
  90. package/dist/connector/repo.js +12 -1
  91. package/dist/connector/repo.js.map +1 -1
  92. package/dist/connector/state.d.ts +95 -0
  93. package/dist/connector/state.d.ts.map +1 -0
  94. package/dist/connector/state.js +189 -0
  95. package/dist/connector/state.js.map +1 -0
  96. package/dist/connector/unsupported-schema.d.ts +9 -0
  97. package/dist/connector/unsupported-schema.d.ts.map +1 -0
  98. package/dist/connector/unsupported-schema.js +14 -0
  99. package/dist/connector/unsupported-schema.js.map +1 -0
  100. package/dist/connector/vault.d.ts +2 -0
  101. package/dist/connector/vault.d.ts.map +1 -1
  102. package/dist/connector/vault.js +6 -1
  103. package/dist/connector/vault.js.map +1 -1
  104. package/dist/help.d.ts +45 -0
  105. package/dist/help.d.ts.map +1 -0
  106. package/dist/help.js +355 -0
  107. package/dist/help.js.map +1 -0
  108. package/dist/util/fs.d.ts +6 -0
  109. package/dist/util/fs.d.ts.map +1 -1
  110. package/dist/util/fs.js +15 -1
  111. package/dist/util/fs.js.map +1 -1
  112. package/dist/util/hash.d.ts +3 -0
  113. package/dist/util/hash.d.ts.map +1 -0
  114. package/dist/util/hash.js +6 -0
  115. package/dist/util/hash.js.map +1 -0
  116. package/dist/util/path.d.ts +10 -0
  117. package/dist/util/path.d.ts.map +1 -1
  118. package/dist/util/path.js +31 -0
  119. package/dist/util/path.js.map +1 -1
  120. package/dist/util/plan-name.d.ts +10 -0
  121. package/dist/util/plan-name.d.ts.map +1 -0
  122. package/dist/util/plan-name.js +18 -0
  123. package/dist/util/plan-name.js.map +1 -0
  124. package/dist/util/semver.d.ts +10 -0
  125. package/dist/util/semver.d.ts.map +1 -0
  126. package/dist/util/semver.js +40 -0
  127. package/dist/util/semver.js.map +1 -0
  128. package/dist/vault/layout.d.ts +2 -0
  129. package/dist/vault/layout.d.ts.map +1 -1
  130. package/dist/vault/layout.js +4 -0
  131. package/dist/vault/layout.js.map +1 -1
  132. package/dist/vault/list-plans.d.ts +11 -0
  133. package/dist/vault/list-plans.d.ts.map +1 -0
  134. package/dist/vault/list-plans.js +40 -0
  135. package/dist/vault/list-plans.js.map +1 -0
  136. package/dist/vault/write-plan.d.ts +28 -0
  137. package/dist/vault/write-plan.d.ts.map +1 -0
  138. package/dist/vault/write-plan.js +61 -0
  139. package/dist/vault/write-plan.js.map +1 -0
  140. package/package.json +1 -1
  141. package/templates/agents/claude/commands/grounder-note.md +1 -1
  142. package/templates/agents/claude/commands/grounder-plan.md +65 -0
  143. package/templates/agents/claude/commands/grounder-task-handoff.md +3 -3
  144. package/templates/agents/claude/commands/grounder-task.md +2 -2
  145. package/templates/agents/cursor/commands/grounder-note.md +1 -1
  146. package/templates/agents/cursor/commands/grounder-plan.md +65 -0
  147. package/templates/agents/cursor/commands/grounder-task-handoff.md +3 -3
  148. package/templates/agents/cursor/commands/grounder-task.md +2 -2
  149. package/templates/vault/plan.md +16 -0
package/dist/help.js ADDED
@@ -0,0 +1,355 @@
1
+ /** Help tiers and command metadata for the grounder CLI. */
2
+ const GROUP_ORDER = ["Setup", "Write", "Paths", "Maintain", "Advanced"];
3
+ const QUICKSTART = `Quickstart:
4
+ grounder vault init <path-to-your-vault>
5
+ grounder init
6
+ grounder note "my first note"
7
+ grounder handoff $'# Handoff\\n\\n## Next\\n1. …'
8
+ grounder handoff list
9
+ grounder plan $'# Goal\\n\\nShip it' --title phase-1
10
+ grounder plan list`;
11
+ /**
12
+ * Single registry driving synopsis, full help, and per-command help so they
13
+ * cannot drift from each other (or from the dispatcher ids).
14
+ */
15
+ export const COMMANDS = [
16
+ {
17
+ id: "vault",
18
+ group: "Setup",
19
+ summary: "Vault setup commands",
20
+ listUsage: "vault init <path>",
21
+ usage: "grounder vault init <path> [--yes] [--force] [--agent <id>] [--hooks]",
22
+ list: false,
23
+ flags: `Subcommands:
24
+ vault init Initialize vault + home config (once per machine)`,
25
+ },
26
+ {
27
+ id: "vault init",
28
+ group: "Setup",
29
+ summary: "Initialize vault + home config (once per machine)",
30
+ listUsage: "vault init <path>",
31
+ usage: "grounder vault init <path> [--yes] [--force] [--agent <id>] [--hooks]",
32
+ flags: `Flags:
33
+ --yes Skip confirmation prompts
34
+ --force Overwrite existing home config / generated files
35
+ --agent <id> Install for a specific agent (repeatable; default: auto-detect)
36
+ Supported: cursor, claude
37
+ --hooks Also install session-start teaser hooks`,
38
+ },
39
+ {
40
+ id: "init",
41
+ group: "Setup",
42
+ summary: "Connect the current repo to your vault",
43
+ listUsage: "init",
44
+ usage: "grounder init [--yes] [--force] [--id <id>] [--vault <path>]",
45
+ flags: `Flags:
46
+ --yes Skip confirmation prompts
47
+ --force Overwrite an existing link marker
48
+ --id <id> Override detected project id
49
+ --vault <path> Override home vault root for this run`,
50
+ },
51
+ {
52
+ id: "note",
53
+ group: "Write",
54
+ summary: "Write a note to the vault",
55
+ listUsage: "note <text>",
56
+ usage: "grounder note <text> [--title <slug>]",
57
+ flags: `Flags:
58
+ --title <slug> Short slug in filename (default: slugified first line)`,
59
+ },
60
+ {
61
+ id: "handoff",
62
+ group: "Write",
63
+ summary: "Write a session handoff to vault logs/",
64
+ listUsage: "handoff <text>",
65
+ usage: "grounder handoff <text> [--title <slug>]",
66
+ flags: `Flags:
67
+ --title <slug> Short slug in filename (default: slugified first line)
68
+
69
+ Subcommands:
70
+ handoff list Print recent handoff paths (newest first)
71
+ handoff peek One-line latest-handoff teaser (session hooks)`,
72
+ },
73
+ {
74
+ id: "handoff list",
75
+ group: "Write",
76
+ summary: "Print recent handoff paths (newest first)",
77
+ listUsage: "handoff list",
78
+ usage: "grounder handoff list [--limit <n>] [--head]",
79
+ flags: `Flags:
80
+ --limit <n> Max paths to print (default: 5)
81
+ --head Print only the newest usable path
82
+ (skips empty/unreadable files; same pick as handoff peek)`,
83
+ },
84
+ {
85
+ id: "plan",
86
+ group: "Write",
87
+ summary: "Write/update a named plan under vault plans/",
88
+ listUsage: "plan <text>",
89
+ usage: "grounder plan <text> (--title <name> [--force] | --path <file>)",
90
+ flags: `Flags:
91
+ --title <name> Required filename stem when creating/updating by name
92
+ (trailing .md ok; sanitized, max 80 chars)
93
+ --path <file> Update an existing plan by path (must resolve under this
94
+ project's plans/; no title sanitization; always overwrites)
95
+ --force With --title: overwrite an existing plan (preserves created)
96
+
97
+ Subcommands:
98
+ plan list Print recent plan paths (newest first)`,
99
+ },
100
+ {
101
+ id: "plan list",
102
+ group: "Write",
103
+ summary: "Print recent plan paths (newest first)",
104
+ listUsage: "plan list",
105
+ usage: "grounder plan list [--limit <n>]",
106
+ flags: `Flags:
107
+ --limit <n> Max paths to print (default: 5)`,
108
+ },
109
+ {
110
+ id: "path notes",
111
+ group: "Paths",
112
+ summary: "Print resolved notes directory",
113
+ listUsage: "path notes",
114
+ usage: "grounder path notes",
115
+ },
116
+ {
117
+ id: "path logs",
118
+ group: "Paths",
119
+ summary: "Print resolved logs directory",
120
+ listUsage: "path logs",
121
+ usage: "grounder path logs",
122
+ },
123
+ {
124
+ id: "path plans",
125
+ group: "Paths",
126
+ summary: "Print resolved plans directory",
127
+ listUsage: "path plans",
128
+ usage: "grounder path plans",
129
+ },
130
+ {
131
+ id: "path",
132
+ group: "Paths",
133
+ summary: "Print a resolved vault directory for the linked project",
134
+ listUsage: "path <notes|logs|plans>",
135
+ usage: "grounder path <notes|logs|plans>",
136
+ list: false,
137
+ flags: `Subcommands:
138
+ path notes Print resolved notes directory
139
+ path logs Print resolved logs directory
140
+ path plans Print resolved plans directory`,
141
+ },
142
+ {
143
+ id: "status",
144
+ group: "Maintain",
145
+ summary: "Snapshot of machine + project link + resolved paths",
146
+ listUsage: "status",
147
+ usage: "grounder status",
148
+ },
149
+ {
150
+ id: "doctor",
151
+ group: "Maintain",
152
+ summary: "Health checks with fix hints",
153
+ listUsage: "doctor",
154
+ usage: "grounder doctor [--global]",
155
+ flags: `Flags:
156
+ --global Machine-only checks (skip project/link checks)`,
157
+ },
158
+ {
159
+ id: "migrate",
160
+ group: "Maintain",
161
+ summary: "Refresh agent install after upgrading grounder",
162
+ listUsage: "migrate",
163
+ usage: "grounder migrate [--force] [--dry-run] [--agent <id>] [--hooks]",
164
+ flags: `Flags:
165
+ --force Overwrite command files you edited locally; also needed
166
+ once when upgrading from Grounder before 0.3
167
+ --dry-run Preview without writing
168
+ --agent <id> Limit to a specific agent (repeatable)
169
+ --hooks Also install hooks if not previously installed`,
170
+ },
171
+ {
172
+ id: "handoff peek",
173
+ group: "Advanced",
174
+ summary: "One-line latest-handoff teaser (used by session hooks)",
175
+ listUsage: "handoff peek",
176
+ usage: "grounder handoff peek [--json]",
177
+ flags: `Flags:
178
+ --json Emit one JSON line for Cursor sessionStart
179
+ ({ additional_context } or {})`,
180
+ },
181
+ {
182
+ id: "help",
183
+ group: "Maintain",
184
+ summary: "Show help (full reference / per-command)",
185
+ listUsage: "help [<command>]",
186
+ usage: "grounder help [<command>]",
187
+ list: false,
188
+ flags: `With no args, prints the full reference (same as --help).
189
+ With a command id, prints that command's usage and flags.
190
+
191
+ Examples:
192
+ grounder help
193
+ grounder help note
194
+ grounder help handoff list
195
+ grounder help vault init`,
196
+ },
197
+ ];
198
+ /**
199
+ * Command ids the CLI dispatches (including parent topics). Keep in sync with
200
+ * `cli.ts` / `helpExitCode(...)` call sites — the invariant test locks this.
201
+ */
202
+ export const DISPATCHED_COMMAND_IDS = [
203
+ "vault",
204
+ "vault init",
205
+ "init",
206
+ "note",
207
+ "handoff",
208
+ "handoff list",
209
+ "handoff peek",
210
+ "plan",
211
+ "plan list",
212
+ "path",
213
+ "path notes",
214
+ "path logs",
215
+ "path plans",
216
+ "status",
217
+ "doctor",
218
+ "migrate",
219
+ "help",
220
+ ];
221
+ const byId = new Map(COMMANDS.map((c) => [c.id, c]));
222
+ /** True when argv contains an exact `-h` or `--help` token. */
223
+ export function wantsHelp(argv) {
224
+ return argv.some((arg) => arg === "-h" || arg === "--help");
225
+ }
226
+ /**
227
+ * Resolve a help topic from argv segments after `help`, longest match first
228
+ * (e.g. `handoff list` before `handoff`).
229
+ */
230
+ export function resolveCommandHelp(segments) {
231
+ if (segments.length === 0) {
232
+ return undefined;
233
+ }
234
+ for (let len = segments.length; len >= 1; len--) {
235
+ const id = segments.slice(0, len).join(" ");
236
+ const meta = byId.get(id);
237
+ if (meta) {
238
+ return meta;
239
+ }
240
+ }
241
+ return undefined;
242
+ }
243
+ export function printCommandHelp(meta) {
244
+ let out = `Usage: ${meta.usage}\n`;
245
+ if (meta.summary) {
246
+ out += `\n${meta.summary}\n`;
247
+ }
248
+ if (meta.flags) {
249
+ out += `\n${meta.flags}\n`;
250
+ }
251
+ process.stdout.write(out);
252
+ }
253
+ export function printCommandHelpById(id) {
254
+ const meta = byId.get(id);
255
+ if (!meta) {
256
+ throw new Error(`Unknown help id: ${id}`);
257
+ }
258
+ printCommandHelp(meta);
259
+ }
260
+ /** If argv asks for help, print per-command help and return 0; else null. */
261
+ export function helpExitCode(argv, commandId) {
262
+ if (!wantsHelp(argv)) {
263
+ return null;
264
+ }
265
+ printCommandHelpById(commandId);
266
+ return 0;
267
+ }
268
+ function listedCommands() {
269
+ return COMMANDS.filter((c) => c.list !== false);
270
+ }
271
+ function formatGroupedLists() {
272
+ const listed = listedCommands();
273
+ const width = Math.max(...listed.map((c) => c.listUsage.length));
274
+ const blocks = [];
275
+ for (const group of GROUP_ORDER) {
276
+ const cmds = listed.filter((c) => c.group === group);
277
+ if (cmds.length === 0) {
278
+ continue;
279
+ }
280
+ const lines = cmds.map((c) => ` ${c.listUsage.padEnd(width)} ${c.summary}`);
281
+ blocks.push(`${group}:\n${lines.join("\n")}`);
282
+ }
283
+ return blocks.join("\n\n");
284
+ }
285
+ function formatCommandDetail(c) {
286
+ const lines = [`${c.id}`, ` Usage: ${c.usage}`];
287
+ if (c.flags) {
288
+ for (const line of c.flags.split("\n")) {
289
+ lines.push(line.length > 0 ? ` ${line}` : "");
290
+ }
291
+ }
292
+ return lines.join("\n");
293
+ }
294
+ function formatFullCommandDetails() {
295
+ const listed = listedCommands().filter((c) => c.id !== "help");
296
+ const blocks = [];
297
+ for (const group of GROUP_ORDER) {
298
+ const cmds = listed.filter((c) => c.group === group);
299
+ if (cmds.length === 0) {
300
+ continue;
301
+ }
302
+ blocks.push(`${group}\n${cmds.map(formatCommandDetail).join("\n\n")}`);
303
+ }
304
+ return blocks.join("\n\n");
305
+ }
306
+ /** Bare `grounder` / `-h` — short grouped synopsis (no flag encyclopedia). */
307
+ export function printSynopsis() {
308
+ process.stdout.write(`grounder — connect git projects to Obsidian dev vaults for AI agents
309
+
310
+ ${formatGroupedLists()}
311
+
312
+ Global options:
313
+ -h Short synopsis (this text)
314
+ --help Full reference (all commands + flags)
315
+ -v, --version Show version
316
+
317
+ Run \`grounder help <command>\` for one command's flags.
318
+ Run \`grounder --help\` for the full reference.
319
+
320
+ ${QUICKSTART}
321
+ `);
322
+ }
323
+ /** `--help` / `grounder help` with no args — one-shot reference for humans/agents. */
324
+ export function printFullHelp() {
325
+ process.stdout.write(`grounder — connect git projects to Obsidian dev vaults for AI agents
326
+
327
+ ${formatGroupedLists()}
328
+
329
+ Global options:
330
+ -h Short synopsis
331
+ --help This full reference
332
+ -v, --version Show version
333
+
334
+ Commands:
335
+ ${formatFullCommandDetails()}
336
+
337
+ ${QUICKSTART}
338
+ `);
339
+ }
340
+ /** `grounder help [topic…]` — full help or per-command. */
341
+ export function runHelp(argv) {
342
+ if (argv.length === 0 || wantsHelp(argv)) {
343
+ printFullHelp();
344
+ return 0;
345
+ }
346
+ const meta = resolveCommandHelp(argv);
347
+ if (!meta) {
348
+ process.stderr.write(`Unknown help topic: ${argv.join(" ")}\n`);
349
+ process.stderr.write("Run `grounder --help` for a list of commands.\n");
350
+ return 1;
351
+ }
352
+ printCommandHelp(meta);
353
+ return 0;
354
+ }
355
+ //# sourceMappingURL=help.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"help.js","sourceRoot":"","sources":["../src/help.ts"],"names":[],"mappings":"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"}
package/dist/util/fs.d.ts CHANGED
@@ -1,4 +1,10 @@
1
1
  export declare function fileExists(filePath: string): Promise<boolean>;
2
+ /**
3
+ * True when `filePath` is present and executable (`X_OK`).
4
+ * On Windows, `X_OK` effectively degrades toward existence — callers should
5
+ * not oversell POSIX execute-bit semantics there.
6
+ */
7
+ export declare function isExecutable(filePath: string): Promise<boolean>;
2
8
  /**
3
9
  * Creates a new markdown file under `dir`; never overwrites.
4
10
  * Tries `basename.md`, then `basename_02.md`, `_03`, … with `wx` (O_EXCL)
@@ -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,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;;;;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, writeFile } from "node:fs/promises";
1
+ import { access, constants, 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) {
@@ -10,6 +10,20 @@ export async function fileExists(filePath) {
10
10
  return false;
11
11
  }
12
12
  }
13
+ /**
14
+ * True when `filePath` is present and executable (`X_OK`).
15
+ * On Windows, `X_OK` effectively degrades toward existence — callers should
16
+ * not oversell POSIX execute-bit semantics there.
17
+ */
18
+ export async function isExecutable(filePath) {
19
+ try {
20
+ await access(filePath, constants.X_OK);
21
+ return true;
22
+ }
23
+ catch {
24
+ return false;
25
+ }
26
+ }
13
27
  /**
14
28
  * Creates a new markdown file under `dir`; never overwrites.
15
29
  * Tries `basename.md`, then `basename_02.md`, `_03`, … with `wx` (O_EXCL)
@@ -1 +1 @@
1
- {"version":3,"file":"fs.js","sourceRoot":"","sources":["../../src/util/fs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACrD,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,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
+ {"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;AAChE,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;;;;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"}
@@ -0,0 +1,3 @@
1
+ /** SHA-256 of UTF-8 content, prefixed `sha256:` for ledger storage. */
2
+ export declare function hashContent(content: string): string;
3
+ //# sourceMappingURL=hash.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../../src/util/hash.ts"],"names":[],"mappings":"AAEA,uEAAuE;AACvE,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEnD"}
@@ -0,0 +1,6 @@
1
+ import { createHash } from "node:crypto";
2
+ /** SHA-256 of UTF-8 content, prefixed `sha256:` for ledger storage. */
3
+ export function hashContent(content) {
4
+ return `sha256:${createHash("sha256").update(content, "utf8").digest("hex")}`;
5
+ }
6
+ //# sourceMappingURL=hash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash.js","sourceRoot":"","sources":["../../src/util/hash.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,uEAAuE;AACvE,MAAM,UAAU,WAAW,CAAC,OAAe;IACzC,OAAO,UAAU,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;AAChF,CAAC"}
@@ -7,4 +7,14 @@ export declare function expandHome(input: string): string;
7
7
  * Expand a leading `~`, then resolve to an absolute path (relative to `cwd` if given).
8
8
  */
9
9
  export declare function resolveUserPath(input: string, cwd?: string): string;
10
+ /**
11
+ * True when `child` is inside `parent` (or equal), using lexical `path.resolve` only
12
+ * (does not follow symlinks). Rejects `..` escape and cross-root relatives.
13
+ */
14
+ export declare function isPathInside(parent: string, child: string): boolean;
15
+ /**
16
+ * True when `child`'s real path is strictly inside `parent` (symlinks followed).
17
+ * Returns `null` if either path cannot be resolved (e.g. missing).
18
+ */
19
+ export declare function isRealPathInside(parent: string, child: string): Promise<boolean | null>;
10
20
  //# sourceMappingURL=path.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../src/util/path.ts"],"names":[],"mappings":"AAGA;;;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"}
1
+ {"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../src/util/path.ts"],"names":[],"mappings":"AAIA;;;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"}
package/dist/util/path.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { realpath } from "node:fs/promises";
1
2
  import os from "node:os";
2
3
  import path from "node:path";
3
4
  /**
@@ -19,4 +20,34 @@ export function expandHome(input) {
19
20
  export function resolveUserPath(input, cwd = process.cwd()) {
20
21
  return path.resolve(cwd, expandHome(input));
21
22
  }
23
+ /**
24
+ * True when `child` is inside `parent` (or equal), using lexical `path.resolve` only
25
+ * (does not follow symlinks). Rejects `..` escape and cross-root relatives.
26
+ */
27
+ export function isPathInside(parent, child) {
28
+ const root = path.resolve(parent);
29
+ const target = path.resolve(child);
30
+ if (root === target) {
31
+ return true;
32
+ }
33
+ const relative = path.relative(root, target);
34
+ return (relative !== "" &&
35
+ relative !== ".." &&
36
+ !relative.startsWith(`..${path.sep}`) &&
37
+ !path.isAbsolute(relative));
38
+ }
39
+ /**
40
+ * True when `child`'s real path is strictly inside `parent` (symlinks followed).
41
+ * Returns `null` if either path cannot be resolved (e.g. missing).
42
+ */
43
+ export async function isRealPathInside(parent, child) {
44
+ try {
45
+ const root = await realpath(parent);
46
+ const target = await realpath(child);
47
+ return root !== target && isPathInside(root, target);
48
+ }
49
+ catch {
50
+ return null;
51
+ }
52
+ }
22
53
  //# sourceMappingURL=path.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"path.js","sourceRoot":"","sources":["../../src/util/path.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,IAAI,KAAK,KAAK,GAAG,EAAE,CAAC;QAClB,OAAO,EAAE,CAAC,OAAO,EAAE,CAAC;IACtB,CAAC;IACD,IAAI,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC;QAC/D,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACjD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,KAAa,EAAE,MAAc,OAAO,CAAC,GAAG,EAAE;IACxE,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC;AAC9C,CAAC"}
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;AAE7B;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,IAAI,KAAK,KAAK,GAAG,EAAE,CAAC;QAClB,OAAO,EAAE,CAAC,OAAO,EAAE,CAAC;IACtB,CAAC;IACD,IAAI,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC;QAC/D,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACjD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,KAAa,EAAE,MAAc,OAAO,CAAC,GAAG,EAAE;IACxE,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC;AAC9C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,MAAc,EAAE,KAAa;IACxD,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAClC,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IACnC,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;QACpB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC7C,OAAO,CACL,QAAQ,KAAK,EAAE;QACf,QAAQ,KAAK,IAAI;QACjB,CAAC,QAAQ,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC,GAAG,EAAE,CAAC;QACrC,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAC3B,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,MAAc,EAAE,KAAa;IAClE,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAC;QACpC,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,CAAC;QACrC,OAAO,IAAI,KAAK,MAAM,IAAI,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACvD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
@@ -0,0 +1,10 @@
1
+ /** Generous cap for plan filenames — real plan names often exceed note/handoff's 20-char slug. */
2
+ export declare const MAX_PLAN_NAME_LENGTH = 80;
3
+ /**
4
+ * Sanitize a `--title` value into a filename-safe plan name.
5
+ * Trims, strips a trailing `.md` (case-insensitive), applies {@link sanitizeProjectId}
6
+ * character rules, and caps at {@link MAX_PLAN_NAME_LENGTH}.
7
+ * Returns `""` when the result is empty / unusable.
8
+ */
9
+ export declare function sanitizePlanName(rawTitle: string): string;
10
+ //# sourceMappingURL=plan-name.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan-name.d.ts","sourceRoot":"","sources":["../../src/util/plan-name.ts"],"names":[],"mappings":"AAEA,kGAAkG;AAClG,eAAO,MAAM,oBAAoB,KAAK,CAAC;AAEvC;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAOzD"}
@@ -0,0 +1,18 @@
1
+ import { sanitizeProjectId } from "./project-id.js";
2
+ /** Generous cap for plan filenames — real plan names often exceed note/handoff's 20-char slug. */
3
+ export const MAX_PLAN_NAME_LENGTH = 80;
4
+ /**
5
+ * Sanitize a `--title` value into a filename-safe plan name.
6
+ * Trims, strips a trailing `.md` (case-insensitive), applies {@link sanitizeProjectId}
7
+ * character rules, and caps at {@link MAX_PLAN_NAME_LENGTH}.
8
+ * Returns `""` when the result is empty / unusable.
9
+ */
10
+ export function sanitizePlanName(rawTitle) {
11
+ const trimmed = rawTitle.trim().replace(/\.md$/i, "");
12
+ const sanitized = sanitizeProjectId(trimmed);
13
+ if (!sanitized) {
14
+ return "";
15
+ }
16
+ return sanitized.slice(0, MAX_PLAN_NAME_LENGTH).replace(/-+$/g, "");
17
+ }
18
+ //# sourceMappingURL=plan-name.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan-name.js","sourceRoot":"","sources":["../../src/util/plan-name.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEpD,kGAAkG;AAClG,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,CAAC;AAEvC;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAgB;IAC/C,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IACtD,MAAM,SAAS,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAC7C,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,OAAO,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,oBAAoB,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AACtE,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Compare npm-style `major.minor.patch` prefixes.
3
+ * Returns negative / zero / positive like `strcmp`, or `null` if either side
4
+ * lacks a leading `x.y.z` (prerelease/build suffixes after the triple are ignored).
5
+ */
6
+ export declare function compareSemver(a: string, b: string): number | null;
7
+ /** How the running package version relates to the last-recorded ledger version. */
8
+ export type PackageVersionRelation = "match" | "ahead" | "behind" | "differs";
9
+ export declare function packageVersionRelation(running: string, recorded: string): PackageVersionRelation;
10
+ //# sourceMappingURL=semver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"semver.d.ts","sourceRoot":"","sources":["../../src/util/semver.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAwBjE;AAED,mFAAmF;AACnF,MAAM,MAAM,sBAAsB,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE9E,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,sBAAsB,CAUhG"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Compare npm-style `major.minor.patch` prefixes.
3
+ * Returns negative / zero / positive like `strcmp`, or `null` if either side
4
+ * lacks a leading `x.y.z` (prerelease/build suffixes after the triple are ignored).
5
+ */
6
+ export function compareSemver(a, b) {
7
+ const parse = (v) => {
8
+ const match = /^(\d+)\.(\d+)\.(\d+)/.exec(v.trim());
9
+ if (!match) {
10
+ return null;
11
+ }
12
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
13
+ };
14
+ const left = parse(a);
15
+ const right = parse(b);
16
+ if (!left || !right) {
17
+ return null;
18
+ }
19
+ for (let i = 0; i < 3; i++) {
20
+ if (left[i] < right[i]) {
21
+ return -1;
22
+ }
23
+ if (left[i] > right[i]) {
24
+ return 1;
25
+ }
26
+ }
27
+ return 0;
28
+ }
29
+ export function packageVersionRelation(running, recorded) {
30
+ if (running === recorded) {
31
+ return "match";
32
+ }
33
+ const cmp = compareSemver(running, recorded);
34
+ if (cmp === null || cmp === 0) {
35
+ // Unparseable, or same x.y.z with different suffix/string form.
36
+ return "differs";
37
+ }
38
+ return cmp > 0 ? "ahead" : "behind";
39
+ }
40
+ //# sourceMappingURL=semver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"semver.js","sourceRoot":"","sources":["../../src/util/semver.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,CAAS,EAAE,CAAS;IAChD,MAAM,KAAK,GAAG,CAAC,CAAS,EAAmC,EAAE;QAC3D,MAAM,KAAK,GAAG,sBAAsB,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QACpD,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAChE,CAAC,CAAC;IAEF,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IACtB,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IACvB,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QACpB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC3B,IAAI,IAAI,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;YACvB,OAAO,CAAC,CAAC,CAAC;QACZ,CAAC;QACD,IAAI,IAAI,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;YACvB,OAAO,CAAC,CAAC;QACX,CAAC;IACH,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAKD,MAAM,UAAU,sBAAsB,CAAC,OAAe,EAAE,QAAgB;IACtE,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;QACzB,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,MAAM,GAAG,GAAG,aAAa,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IAC7C,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,CAAC,EAAE,CAAC;QAC9B,gEAAgE;QAChE,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC;AACtC,CAAC"}
@@ -10,4 +10,6 @@ export declare function projectDir(vaultRoot: string, projectId: string): string
10
10
  export declare function notesDir(vaultRoot: string, projectId: string): string;
11
11
  /** `<vault>/10-Projects/{projectId}/logs` — session handoffs. */
12
12
  export declare function logsDir(vaultRoot: string, projectId: string): string;
13
+ /** `<vault>/10-Projects/{projectId}/plans` — named, updatable plan docs. */
14
+ export declare function plansDir(vaultRoot: string, projectId: string): string;
13
15
  //# sourceMappingURL=layout.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../../src/vault/layout.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,eAAO,MAAM,YAAY,gBAAgB,CAAC;AAE1C,6DAA6D;AAC7D,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAExD;AAED,yCAAyC;AACzC,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAEvE;AAED,+CAA+C;AAC/C,wBAAgB,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAErE;AAED,iEAAiE;AACjE,wBAAgB,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAEpE"}
1
+ {"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../../src/vault/layout.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,eAAO,MAAM,YAAY,gBAAgB,CAAC;AAE1C,6DAA6D;AAC7D,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAExD;AAED,yCAAyC;AACzC,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAEvE;AAED,+CAA+C;AAC/C,wBAAgB,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAErE;AAED,iEAAiE;AACjE,wBAAgB,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAEpE;AAED,4EAA4E;AAC5E,wBAAgB,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAErE"}
@@ -19,4 +19,8 @@ export function notesDir(vaultRoot, projectId) {
19
19
  export function logsDir(vaultRoot, projectId) {
20
20
  return path.join(vaultRoot, PROJECTS_DIR, projectId, "logs");
21
21
  }
22
+ /** `<vault>/10-Projects/{projectId}/plans` — named, updatable plan docs. */
23
+ export function plansDir(vaultRoot, projectId) {
24
+ return path.join(vaultRoot, PROJECTS_DIR, projectId, "plans");
25
+ }
22
26
  //# sourceMappingURL=layout.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"layout.js","sourceRoot":"","sources":["../../src/vault/layout.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,MAAM,CAAC,MAAM,YAAY,GAAG,aAAa,CAAC;AAE1C,6DAA6D;AAC7D,MAAM,UAAU,cAAc,CAAC,SAAiB;IAC9C,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;AAC5C,CAAC;AAED,yCAAyC;AACzC,MAAM,UAAU,UAAU,CAAC,SAAiB,EAAE,SAAiB;IAC7D,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,YAAY,EAAE,SAAS,CAAC,CAAC;AACvD,CAAC;AAED,+CAA+C;AAC/C,MAAM,UAAU,QAAQ,CAAC,SAAiB,EAAE,SAAiB;IAC3D,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,YAAY,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;AAChE,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,OAAO,CAAC,SAAiB,EAAE,SAAiB;IAC1D,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;AAC/D,CAAC"}
1
+ {"version":3,"file":"layout.js","sourceRoot":"","sources":["../../src/vault/layout.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,MAAM,CAAC,MAAM,YAAY,GAAG,aAAa,CAAC;AAE1C,6DAA6D;AAC7D,MAAM,UAAU,cAAc,CAAC,SAAiB;IAC9C,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;AAC5C,CAAC;AAED,yCAAyC;AACzC,MAAM,UAAU,UAAU,CAAC,SAAiB,EAAE,SAAiB;IAC7D,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,YAAY,EAAE,SAAS,CAAC,CAAC;AACvD,CAAC;AAED,+CAA+C;AAC/C,MAAM,UAAU,QAAQ,CAAC,SAAiB,EAAE,SAAiB;IAC3D,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,YAAY,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;AAChE,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,OAAO,CAAC,SAAiB,EAAE,SAAiB;IAC1D,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;AAC/D,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,QAAQ,CAAC,SAAiB,EAAE,SAAiB;IAC3D,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,YAAY,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;AAChE,CAAC"}