frontier-mcp 0.1.0 → 0.2.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 (59) hide show
  1. package/README.md +10 -5
  2. package/dist/driver-registry.d.ts +15 -0
  3. package/dist/driver-registry.d.ts.map +1 -0
  4. package/dist/driver-registry.js +19 -0
  5. package/dist/driver-registry.js.map +1 -0
  6. package/dist/server.d.ts +7 -3
  7. package/dist/server.d.ts.map +1 -1
  8. package/dist/server.js +57 -38
  9. package/dist/server.js.map +1 -1
  10. package/dist/storage/driver.d.ts +41 -8
  11. package/dist/storage/driver.d.ts.map +1 -1
  12. package/dist/storage/driver.js.map +1 -1
  13. package/dist/storage/markdown/create.d.ts +5 -5
  14. package/dist/storage/markdown/create.d.ts.map +1 -1
  15. package/dist/storage/markdown/create.js +17 -17
  16. package/dist/storage/markdown/create.js.map +1 -1
  17. package/dist/storage/markdown/driver.d.ts +35 -1
  18. package/dist/storage/markdown/driver.d.ts.map +1 -1
  19. package/dist/storage/markdown/driver.js +155 -84
  20. package/dist/storage/markdown/driver.js.map +1 -1
  21. package/dist/storage/markdown/migrate.d.ts +4 -4
  22. package/dist/storage/markdown/migrate.d.ts.map +1 -1
  23. package/dist/storage/markdown/migrate.js +1 -1
  24. package/dist/storage/markdown/migrate.js.map +1 -1
  25. package/dist/storage/markdown/ticket.d.ts +16 -5
  26. package/dist/storage/markdown/ticket.d.ts.map +1 -1
  27. package/dist/storage/markdown/ticket.js +19 -7
  28. package/dist/storage/markdown/ticket.js.map +1 -1
  29. package/dist/storage/markdown/watcher.d.ts +33 -0
  30. package/dist/storage/markdown/watcher.d.ts.map +1 -0
  31. package/dist/storage/markdown/watcher.js +142 -0
  32. package/dist/storage/markdown/watcher.js.map +1 -0
  33. package/dist/tools/create-tickets.d.ts +4 -4
  34. package/dist/tools/create-tickets.d.ts.map +1 -1
  35. package/dist/tools/create-tickets.js.map +1 -1
  36. package/dist/tools/list-efforts.d.ts.map +1 -1
  37. package/dist/tools/list-efforts.js +2 -1
  38. package/dist/tools/list-efforts.js.map +1 -1
  39. package/dist/tools/update-ticket.d.ts +3 -3
  40. package/dist/tools/update-ticket.d.ts.map +1 -1
  41. package/dist/tools/update-ticket.js.map +1 -1
  42. package/dist/tools/workspace-line.d.ts +18 -0
  43. package/dist/tools/workspace-line.d.ts.map +1 -0
  44. package/dist/tools/workspace-line.js +22 -0
  45. package/dist/tools/workspace-line.js.map +1 -0
  46. package/dist/workspace.d.ts +5 -1
  47. package/dist/workspace.d.ts.map +1 -1
  48. package/dist/workspace.js +42 -5
  49. package/dist/workspace.js.map +1 -1
  50. package/docs/agents/issue-tracker.md +14 -2
  51. package/package.json +1 -1
  52. package/dist/workspace-index.d.ts +0 -39
  53. package/dist/workspace-index.d.ts.map +0 -1
  54. package/dist/workspace-index.js +0 -83
  55. package/dist/workspace-index.js.map +0 -1
  56. package/dist/workspace-watcher.d.ts +0 -24
  57. package/dist/workspace-watcher.d.ts.map +0 -1
  58. package/dist/workspace-watcher.js +0 -87
  59. package/dist/workspace-watcher.js.map +0 -1
@@ -34,8 +34,8 @@ const CANDIDATE_HEADROOM = 1000;
34
34
  * write X. See ADR 0005.
35
35
  */
36
36
  export async function createTicketFiles(request) {
37
- const { scratch, effort, dir, issues, drafts, rescan, validate, createEffort } = request;
38
- const { reservations, tickets } = await reserve(scratch, drafts.length, rescan);
37
+ const { storage, effort, dir, issues, drafts, rescan, validate, createEffort } = request;
38
+ const { reservations, tickets } = await reserve(storage, drafts.length, rescan);
39
39
  try {
40
40
  const ids = reservations.map(entry => entry.id);
41
41
  // The last rule that can only be checked now: the ids are real, and nothing
@@ -65,10 +65,10 @@ export async function createTicketFiles(request) {
65
65
  * Hold exclusive id guards for `count` fresh ids, run `use`, then release.
66
66
  * Migration mints through this so a parallel create_tickets cannot collide.
67
67
  */
68
- export async function withIdReservations(scratch, count, rescan, use) {
68
+ export async function withIdReservations(storage, count, rescan, use) {
69
69
  if (count === 0)
70
70
  return use([]);
71
- const { reservations } = await reserve(scratch, count, rescan);
71
+ const { reservations } = await reserve(storage, count, rescan);
72
72
  try {
73
73
  return await use(reservations.map(entry => entry.id));
74
74
  }
@@ -172,9 +172,9 @@ async function writeAll(issues, files) {
172
172
  throw lost.reason;
173
173
  }
174
174
  }
175
- async function reserve(scratch, count, rescan, attempt = 1) {
175
+ async function reserve(storage, count, rescan, attempt = 1) {
176
176
  const used = idsOf(await rescan());
177
- const reservations = await claim(scratch, used, count, highestMinted(used) + 1, []);
177
+ const reservations = await claim(storage, used, count, highestMinted(used) + 1, []);
178
178
  let settled;
179
179
  try {
180
180
  settled = await rescan();
@@ -192,7 +192,7 @@ async function reserve(scratch, count, rescan, attempt = 1) {
192
192
  throw new Error(`Could not reserve ${String(count)} Ticket ids after ${String(MAX_ATTEMPTS)} attempts — ` +
193
193
  'another session is creating Tickets continuously. Retry in a moment.');
194
194
  }
195
- return reserve(scratch, count, rescan, attempt + 1);
195
+ return reserve(storage, count, rescan, attempt + 1);
196
196
  }
197
197
  function release(reservations) {
198
198
  return Promise.allSettled(reservations.map(entry => unlink(entry.guard).catch(() => { })));
@@ -202,23 +202,23 @@ function release(reservations) {
202
202
  * each attempt depends on the one before it, and a bumped candidate is the
203
203
  * normal case rather than an error.
204
204
  */
205
- async function claim(scratch, used, count, candidate, taken) {
205
+ async function claim(storage, used, count, candidate, taken) {
206
206
  if (taken.length === count)
207
207
  return taken;
208
208
  if (candidate > highestMinted(used) + CANDIDATE_HEADROOM + count) {
209
209
  await release(taken);
210
210
  throw new Error(`No free Ticket id within ${String(CANDIDATE_HEADROOM)} of the highest in use. ` +
211
- 'Sweep the abandoned .frontier-id-*.guard files under .scratch/.');
211
+ `Sweep the abandoned .frontier-id-*.guard files under ${storage}.`);
212
212
  }
213
213
  const id = `T${String(candidate)}`;
214
214
  if (used.has(id))
215
- return claim(scratch, used, count, candidate + 1, taken);
216
- const guard = guardFor(scratch, id);
215
+ return claim(storage, used, count, candidate + 1, taken);
216
+ const guard = guardFor(storage, id);
217
217
  // A guard we cannot take belongs to a session that is still allocating. Bump
218
218
  // rather than wait: an id may be skipped, never reused.
219
219
  if (!(await hold(guard)))
220
- return claim(scratch, used, count, candidate + 1, taken);
221
- return claim(scratch, used, count, candidate + 1, [...taken, { id, guard }]);
220
+ return claim(storage, used, count, candidate + 1, taken);
221
+ return claim(storage, used, count, candidate + 1, [...taken, { id, guard }]);
222
222
  }
223
223
  /**
224
224
  * Take a guard, or report that somebody else has it.
@@ -251,11 +251,11 @@ async function hold(guard) {
251
251
  /**
252
252
  * Beside the Efforts rather than inside one, because the counter is repo-global:
253
253
  * two sessions creating into different Efforts must still collide here. Hidden
254
- * and not `.md`, so no scan mistakes it for a Ticket, and `.scratch/` yields it
255
- * no Effort because it is not a directory.
254
+ * and not `.md`, so no scan mistakes it for a Ticket, and the storage scan
255
+ * yields it no Effort because it is not a directory.
256
256
  */
257
- function guardFor(scratch, id) {
258
- return join(scratch, `.frontier-id-${id}.guard`);
257
+ function guardFor(storage, id) {
258
+ return join(storage, `.frontier-id-${id}.guard`);
259
259
  }
260
260
  function idsOf(tickets) {
261
261
  return new Set(tickets.map(entry => entry.id).filter(id => id !== undefined));
@@ -1 +1 @@
1
- {"version":3,"file":"create.js","sourceRoot":"","sources":["../../../src/storage/markdown/create.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"create.js","sourceRoot":"","sources":["../../../src/storage/markdown/create.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,41 @@
1
1
  import type { StorageDriver } from '../driver.ts';
2
+ export interface MarkdownDriverOptions {
3
+ /**
4
+ * The directory under `root` this driver keeps its Efforts in, relative to
5
+ * the workspace root and one segment deep. Defaults to `.scratch`.
6
+ *
7
+ * It is a parameter rather than a constant because it is this driver's answer
8
+ * to "what do you call your storage", which the seam exists to keep from
9
+ * being everybody's.
10
+ */
11
+ readonly storageDir?: string;
12
+ /** Debounce filesystem watcher events before dropping the scan. */
13
+ readonly watcherDebounceMs?: number;
14
+ /**
15
+ * When the watcher drops the scan unconditionally after attaching, to cover
16
+ * the events an `fs.watch` loses while it is still warming up — see
17
+ * {@link watchStorage}. It exists so a test can pin those moments and assert
18
+ * on them; the default schedule is the right one for real use.
19
+ */
20
+ readonly watcherSettleMs?: readonly number[];
21
+ }
2
22
  /**
3
23
  * The markdown driver — the only driver in v1. Bound to `root`, the resolved
4
24
  * workspace, so that no path crosses the {@link StorageDriver} interface.
25
+ *
26
+ * It is the workspace *cached and live*: it holds the scan a Board is built
27
+ * from, drops it when it writes, and watches the storage directory so a change
28
+ * another process made drops it too. Both are markdown policy — a full scan is
29
+ * cheap enough to cache wholesale, and `node:fs.watch` is how this physical
30
+ * model learns it moved. A driver over a database would cache and notice
31
+ * neither the same way, which is why nothing above the seam does it for them.
32
+ * "Live" arrives slightly after construction, though: a watch is not delivering
33
+ * events the instant it is created, and what it misses in that window is gone
34
+ * rather than delayed, so the driver drops its scan a few times while the watch
35
+ * warms.
36
+ *
37
+ * Close it to stop the watcher; a process that keeps a workspace open forever
38
+ * never needs to.
5
39
  */
6
- export declare function createMarkdownDriver(root: string): StorageDriver;
40
+ export declare function createMarkdownDriver(root: string, { storageDir, watcherDebounceMs, watcherSettleMs, }?: MarkdownDriverOptions): StorageDriver;
7
41
  //# sourceMappingURL=driver.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"driver.d.ts","sourceRoot":"","sources":["../../../src/storage/markdown/driver.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAGV,aAAa,EAEd,MAAM,cAAc,CAAC;AAsCtB;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CAsFhE"}
1
+ {"version":3,"file":"driver.d.ts","sourceRoot":"","sources":["../../../src/storage/markdown/driver.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAGV,aAAa,EAEd,MAAM,cAAc,CAAC;AAwCtB,MAAM,WAAW,qBAAqB;IACpC;;;;;;;OAOG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,mEAAmE;IACnE,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACpC;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC9C;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,EACE,UAAgC,EAChC,iBAAiB,EACjB,eAAe,GAChB,GAAE,qBAA0B,GAC5B,aAAa,CA+Hf"}
@@ -6,11 +6,13 @@ import { migrateEffortFiles } from './migrate.js';
6
6
  import { applyEdit } from './serialize.js';
7
7
  import { currentRevision, GuardHeld, withGuard, writeAtomically } from './write.js';
8
8
  import { applyMapEdit, readMapDocument, readSection, readSpecOpening, scaffoldMap, SECTION_HEADINGS, } from './header-doc.js';
9
- import { parseTicket } from './ticket.js';
9
+ import { parseTicketSummary, ticketBody } from './ticket.js';
10
+ import { watchStorage } from './watcher.js';
10
11
  /**
11
- * The layout this driver serves, per AGENTS.md:
12
+ * The layout this driver serves, per AGENTS.md, under the storage directory it
13
+ * was constructed with:
12
14
  *
13
- * .scratch/<effort>/
15
+ * <storage>/<effort>/
14
16
  * ├── map.md # Header doc — wayfinder
15
17
  * ├── spec.md # Header doc — to-spec
16
18
  * └── issues/
@@ -18,7 +20,7 @@ import { parseTicket } from './ticket.js';
18
20
  *
19
21
  * Anything else in an Effort directory is ignored, never an error.
20
22
  */
21
- const SCRATCH_DIR = '.scratch';
23
+ const DEFAULT_STORAGE_DIR = '.scratch';
22
24
  const ISSUES_DIR = 'issues';
23
25
  const MAP_FILE = 'map.md';
24
26
  const SPEC_FILE = 'spec.md';
@@ -29,65 +31,112 @@ const HEADER_DOCS = [
29
31
  /**
30
32
  * The markdown driver — the only driver in v1. Bound to `root`, the resolved
31
33
  * workspace, so that no path crosses the {@link StorageDriver} interface.
34
+ *
35
+ * It is the workspace *cached and live*: it holds the scan a Board is built
36
+ * from, drops it when it writes, and watches the storage directory so a change
37
+ * another process made drops it too. Both are markdown policy — a full scan is
38
+ * cheap enough to cache wholesale, and `node:fs.watch` is how this physical
39
+ * model learns it moved. A driver over a database would cache and notice
40
+ * neither the same way, which is why nothing above the seam does it for them.
41
+ * "Live" arrives slightly after construction, though: a watch is not delivering
42
+ * events the instant it is created, and what it misses in that window is gone
43
+ * rather than delayed, so the driver drops its scan a few times while the watch
44
+ * warms.
45
+ *
46
+ * Close it to stop the watcher; a process that keeps a workspace open forever
47
+ * never needs to.
32
48
  */
33
- export function createMarkdownDriver(root) {
34
- const scratch = join(root, SCRATCH_DIR);
49
+ export function createMarkdownDriver(root, { storageDir = DEFAULT_STORAGE_DIR, watcherDebounceMs, watcherSettleMs, } = {}) {
50
+ const storage = join(root, storageDir);
35
51
  let writes = Promise.resolve();
36
52
  const walk = async () => {
37
53
  // A repo that has never used the tracker is a supported starting state,
38
54
  // not an error: it simply holds no Efforts.
39
- const slugs = await readDirectories(scratch);
40
- const scanned = await Promise.all(slugs.map(slug => readEffort(scratch, slug)));
55
+ const slugs = await readDirectories(storage);
56
+ const scanned = await Promise.all(slugs.map(slug => readEffort(storage, slug)));
41
57
  return scanned
42
58
  .filter((entry) => entry !== undefined)
43
59
  .toSorted((a, b) => a.effort.slug.localeCompare(b.effort.slug));
44
60
  };
45
61
  /**
46
- * Both methods are always called together to build one index, and each walk
47
- * reads every `issues/` directory. Sharing the in-flight walk makes that one
48
- * pass instead of two.
62
+ * Hold the in-flight walk, not just its result, so concurrent callers share
63
+ * one scan instead of racing several. A failed scan is never cached as the
64
+ * answer.
49
65
  *
50
- * The share lasts only as long as the walk itself — the driver holds no
51
- * results, because deciding when a scan is stale belongs to the index above
52
- * it and to the watcher T8 attaches there.
66
+ * The whole scan is one cache rather than one per method: `listEfforts` and
67
+ * `listTickets` are two readings of a single walk, and every caller that
68
+ * wants a Board wants both.
53
69
  */
54
- let inFlight;
70
+ let cachedScan;
55
71
  const scan = () => {
56
- inFlight ??= walk().finally(() => {
57
- inFlight = undefined;
72
+ cachedScan ??= walk().catch((error) => {
73
+ cachedScan = undefined;
74
+ throw error;
58
75
  });
59
- return inFlight;
76
+ return cachedScan;
60
77
  };
78
+ /** Drop the scan, so the next read rebuilds from disk. */
79
+ const invalidate = () => {
80
+ cachedScan = undefined;
81
+ };
82
+ /**
83
+ * A write moves the workspace on whether or not it succeeded partway, so the
84
+ * next read rebuilds rather than trusting a scan taken before it.
85
+ */
86
+ const moved = async (operation) => {
87
+ try {
88
+ return await operation();
89
+ }
90
+ finally {
91
+ invalidate();
92
+ }
93
+ };
94
+ const watcher = watchStorage(root, storageDir, invalidate, {
95
+ debounceMs: watcherDebounceMs,
96
+ settleMs: watcherSettleMs,
97
+ });
61
98
  return {
62
99
  async listEfforts() {
63
100
  return (await scan()).map(entry => entry.effort);
64
101
  },
65
102
  async listTickets() {
66
- return (await scan()).flatMap(entry => entry.tickets);
103
+ return (await scan()).flatMap(entry => summariesOf(entry));
104
+ },
105
+ async readTickets(handles) {
106
+ // Deliberately the fresh walk rather than the cached one. A body is read
107
+ // for the few Tickets being worked, and reading it against a scan taken
108
+ // before another process wrote is exactly the staleness `get_tickets`
109
+ // exists not to serve. It costs a walk a concurrent Board would have
110
+ // shared — the price of an answer nothing can have staled underneath it.
111
+ return readBodies(storage, await walk(), handles);
67
112
  },
68
113
  updateTicket(handle, edit, expectedRevision) {
69
- return serialized(() => write(scratch, handle, edit, expectedRevision));
114
+ return moved(() => serialized(() => write(storage, handle, edit, expectedRevision)));
70
115
  },
71
116
  createTickets(effort, drafts, options) {
72
- // The scan is deliberately not the shared one: allocation re-reads under
73
- // its own guards, and an in-flight walk started before them would defeat
74
- // the check it exists to make.
75
- return serialized(() => create(scratch, effort, drafts, options, walk));
117
+ // The scan is deliberately not the cached one: allocation re-reads under
118
+ // its own guards, and a walk started before them would defeat the check
119
+ // it exists to make.
120
+ return moved(() => serialized(() => create(storage, effort, drafts, options, walk)));
76
121
  },
77
122
  readMap(effort) {
78
- return serialized(() => loadMap(scratch, effort));
123
+ return serialized(() => loadMap(storage, effort));
79
124
  },
80
125
  editMap(effort, edit, expectedRevision, options) {
81
- return serialized(() => saveMap(scratch, effort, edit, expectedRevision, options));
126
+ return moved(() => serialized(() => saveMap(storage, effort, edit, expectedRevision, options)));
82
127
  },
83
128
  readSpec(effort) {
84
- return serialized(() => loadSpec(scratch, effort));
129
+ return serialized(() => loadSpec(storage, effort));
85
130
  },
86
131
  putSpec(effort, body, expectedRevision, options) {
87
- return serialized(() => saveSpec(scratch, effort, body, expectedRevision, options));
132
+ return moved(() => serialized(() => saveSpec(storage, effort, body, expectedRevision, options)));
88
133
  },
89
134
  migrateEffort(effort, options) {
90
- return serialized(() => migrate(scratch, effort, options, walk));
135
+ return moved(() => serialized(() => migrate(storage, effort, options, walk)));
136
+ },
137
+ close() {
138
+ watcher.close();
139
+ invalidate();
91
140
  },
92
141
  };
93
142
  /**
@@ -104,15 +153,15 @@ export function createMarkdownDriver(root) {
104
153
  return next;
105
154
  }
106
155
  }
107
- async function migrate(scratch, effort, options, walk) {
108
- const dir = join(scratch, effort);
109
- if ((await readEffort(scratch, effort)) === undefined)
156
+ async function migrate(storage, effort, options, walk) {
157
+ const dir = join(storage, effort);
158
+ if ((await readEffort(storage, effort)) === undefined)
110
159
  throw new NoSuchEffort(effort);
111
160
  const entries = await readTicketFiles(dir, effort);
112
- const allTickets = (await walk()).flatMap(entry => entry.tickets);
161
+ const allTickets = (await walk()).flatMap(summariesOf);
113
162
  const issues = join(dir, ISSUES_DIR);
114
163
  return migrateEffortFiles({
115
- scratch,
164
+ storage,
116
165
  effort,
117
166
  issues,
118
167
  entries,
@@ -121,24 +170,24 @@ async function migrate(scratch, effort, options, walk) {
121
170
  rename: options.rename,
122
171
  // Allocation must not share a walk started before the guards — same reason
123
172
  // createTickets builds its own rescan.
124
- rescan: async () => (await walk()).flatMap(entry => entry.tickets),
173
+ rescan: async () => (await walk()).flatMap(summariesOf),
125
174
  readContents: filename => readFile(join(issues, filename), 'utf8'),
126
175
  });
127
176
  }
128
- async function create(scratch, effort, drafts, options, walk) {
129
- const dir = join(scratch, effort);
130
- const missing = (await readEffort(scratch, effort)) === undefined;
177
+ async function create(storage, effort, drafts, options, walk) {
178
+ const dir = join(storage, effort);
179
+ const missing = (await readEffort(storage, effort)) === undefined;
131
180
  if (missing && !options.createEffort)
132
181
  throw new NoSuchEffort(effort);
133
182
  // The directory is made inside, after validation — an Effort created here and
134
183
  // then refused would show up in list_efforts as an empty one nobody asked for.
135
184
  const filenames = await createTicketFiles({
136
- scratch,
185
+ storage,
137
186
  effort,
138
187
  dir,
139
188
  issues: join(dir, ISSUES_DIR),
140
189
  drafts,
141
- rescan: async () => (await walk()).flatMap(entry => entry.tickets),
190
+ rescan: async () => (await walk()).flatMap(summariesOf),
142
191
  validate: options.validate ?? (() => { }),
143
192
  createEffort: missing,
144
193
  });
@@ -151,8 +200,8 @@ async function create(scratch, effort, drafts, options, walk) {
151
200
  return ticket;
152
201
  });
153
202
  }
154
- async function write(scratch, handle, edit, expectedRevision) {
155
- const located = await locate(scratch, handle);
203
+ async function write(storage, handle, edit, expectedRevision) {
204
+ const located = await locate(storage, handle);
156
205
  if (located === undefined)
157
206
  throw new NoSuchTicket(handle);
158
207
  const { dir, effort, filename, ticket } = located;
@@ -190,7 +239,7 @@ async function write(scratch, handle, edit, expectedRevision) {
190
239
  const warnings = [];
191
240
  if (edit.status === 'resolved' || edit.status === 'dropped') {
192
241
  try {
193
- await refreshMapDerived(scratch, effort);
242
+ await refreshMapDerived(storage, effort);
194
243
  }
195
244
  catch {
196
245
  warnings.push('Decisions-so-far / dropped Out-of-scope may be stale — Map derived refresh failed after the Ticket write.');
@@ -198,15 +247,15 @@ async function write(scratch, handle, edit, expectedRevision) {
198
247
  }
199
248
  return { ticket: written, warnings };
200
249
  }
201
- async function loadMap(scratch, effort) {
202
- const path = join(scratch, effort, MAP_FILE);
250
+ async function loadMap(storage, effort) {
251
+ const path = join(storage, effort, MAP_FILE);
203
252
  const revision = await currentRevision(path);
204
253
  if (revision === undefined)
205
254
  throw new NoSuchMap(effort);
206
255
  return readMapDocument(await readFile(path, 'utf8'), revision);
207
256
  }
208
- async function saveMap(scratch, effort, edit, expectedRevision, options) {
209
- const dir = join(scratch, effort);
257
+ async function saveMap(storage, effort, edit, expectedRevision, options) {
258
+ const dir = join(storage, effort);
210
259
  const path = join(dir, MAP_FILE);
211
260
  const current = await currentRevision(path);
212
261
  const handle = `map:${effort}`;
@@ -214,7 +263,7 @@ async function saveMap(scratch, effort, edit, expectedRevision, options) {
214
263
  // createEffort only guards starting a new Effort. An Effort that already
215
264
  // holds a Spec or issues/ must be able to gain a Map without that flag —
216
265
  // that is wayfinder's handoff, not a mistyped slug.
217
- if ((await readEffort(scratch, effort)) === undefined && !options.createEffort) {
266
+ if ((await readEffort(storage, effort)) === undefined && !options.createEffort) {
218
267
  throw new NoSuchEffort(effort);
219
268
  }
220
269
  await mkdir(join(dir, ISSUES_DIR), { recursive: true });
@@ -247,16 +296,16 @@ const DERIVED_REFRESH_ATTEMPTS = 16;
247
296
  * Invents the GENERATED-bearing headings when absent so a slim Map still gets
248
297
  * a truthful Decisions / Out-of-scope cache after resolve or drop.
249
298
  */
250
- async function refreshMapDerived(scratch, effort) {
251
- return refreshMapDerivedOnce(scratch, effort, DERIVED_REFRESH_ATTEMPTS);
299
+ async function refreshMapDerived(storage, effort) {
300
+ return refreshMapDerivedOnce(storage, effort, DERIVED_REFRESH_ATTEMPTS);
252
301
  }
253
- async function refreshMapDerivedOnce(scratch, effort, remaining) {
254
- const path = join(scratch, effort, MAP_FILE);
302
+ async function refreshMapDerivedOnce(storage, effort, remaining) {
303
+ const path = join(storage, effort, MAP_FILE);
255
304
  const revision = await currentRevision(path);
256
305
  if (revision === undefined)
257
306
  return;
258
307
  const contents = await readFile(path, 'utf8');
259
- const entries = await readTicketFiles(join(scratch, effort), effort);
308
+ const entries = await readTicketFiles(join(storage, effort), effort);
260
309
  const updated = applyMapEdit(contents, {}, ticketPointers(entries, 'resolved'), ticketPointers(entries, 'dropped'), { ensureDerivedSections: true });
261
310
  if (updated === contents)
262
311
  return;
@@ -276,7 +325,7 @@ async function refreshMapDerivedOnce(scratch, effort, remaining) {
276
325
  throw new Error(`Could not refresh Map derived blocks for ${effort} after concurrent edits.`, { cause: error });
277
326
  }
278
327
  await new Promise(wait => setTimeout(wait, 10));
279
- return refreshMapDerivedOnce(scratch, effort, remaining - 1);
328
+ return refreshMapDerivedOnce(storage, effort, remaining - 1);
280
329
  }
281
330
  }
282
331
  /** Internal signal: another writer changed the Map while we held its guard. */
@@ -286,15 +335,15 @@ class DerivedRefreshLost extends Error {
286
335
  this.name = 'DerivedRefreshLost';
287
336
  }
288
337
  }
289
- async function loadSpec(scratch, effort) {
290
- const path = join(scratch, effort, SPEC_FILE);
338
+ async function loadSpec(storage, effort) {
339
+ const path = join(storage, effort, SPEC_FILE);
291
340
  const revision = await currentRevision(path);
292
341
  if (revision === undefined)
293
342
  throw new NoSuchSpec(effort);
294
343
  return { body: await readFile(path, 'utf8'), revision };
295
344
  }
296
- async function saveSpec(scratch, effort, body, expectedRevision, options) {
297
- const dir = join(scratch, effort);
345
+ async function saveSpec(storage, effort, body, expectedRevision, options) {
346
+ const dir = join(storage, effort);
298
347
  const path = join(dir, SPEC_FILE);
299
348
  const current = await currentRevision(path);
300
349
  const handle = `spec:${effort}`;
@@ -303,7 +352,7 @@ async function saveSpec(scratch, effort, body, expectedRevision, options) {
303
352
  if (current === undefined) {
304
353
  // Same rule as Maps: createEffort starts an Effort; putting a Spec onto an
305
354
  // Effort that already has a Map needs no flag.
306
- if ((await readEffort(scratch, effort)) === undefined && !options.createEffort) {
355
+ if ((await readEffort(storage, effort)) === undefined && !options.createEffort) {
307
356
  throw new NoSuchEffort(effort);
308
357
  }
309
358
  await mkdir(join(dir, ISSUES_DIR), { recursive: true });
@@ -354,14 +403,14 @@ function ticketPointers(entries, status) {
354
403
  });
355
404
  }
356
405
  /** Find the file backing a Ticket, by id or by its `<effort>#<order>` handle. */
357
- async function locate(scratch, handle) {
358
- const slugs = await readDirectories(scratch);
359
- const perEffort = await Promise.all(slugs.map(async (slug) => ({ slug, entries: await readTicketFiles(join(scratch, slug), slug) })));
406
+ async function locate(storage, handle) {
407
+ const slugs = await readDirectories(storage);
408
+ const perEffort = await Promise.all(slugs.map(async (slug) => ({ slug, entries: await readTicketFiles(join(storage, slug), slug) })));
360
409
  for (const { slug, entries } of perEffort) {
361
410
  const found = entries.find(entry => entry.ticket.id === handle || entry.ticket.handle === handle);
362
411
  if (found !== undefined) {
363
412
  return {
364
- dir: join(scratch, slug),
413
+ dir: join(storage, slug),
365
414
  effort: slug,
366
415
  filename: found.filename,
367
416
  ticket: found.ticket,
@@ -381,10 +430,36 @@ function defaultsFor(ticket) {
381
430
  blockedBy: ticket.blockedBy,
382
431
  };
383
432
  }
433
+ /** The Tickets of one scanned Effort, without the files behind them. */
434
+ function summariesOf(scanned) {
435
+ return scanned.entries.map(entry => entry.ticket);
436
+ }
384
437
  /**
385
- * Every Ticket in one Effort with the file behind it, in sort order. Paths never
386
- * cross the driver interface, so this pairing stays inside the driver — it is
387
- * how a write finds the file a Ticket came from.
438
+ * The named Tickets with their prose, read from disk now. The scan located the
439
+ * files; their bodies are read here rather than carried by every scan, so the
440
+ * cost is paid per Ticket worked instead of per Ticket in the workspace.
441
+ *
442
+ * A name matching nothing yields nothing — naming what was missing belongs to
443
+ * the caller, which is the only side that knows what it asked for.
444
+ */
445
+ async function readBodies(storage, scanned, handles) {
446
+ const wanted = new Set(handles);
447
+ const found = scanned.flatMap(entry => entry.entries
448
+ .filter(({ ticket }) => wanted.has(ticket.handle) || (ticket.id !== undefined && wanted.has(ticket.id)))
449
+ .map(({ filename, ticket }) => ({
450
+ ticket,
451
+ path: join(storage, entry.effort.slug, ISSUES_DIR, filename),
452
+ })));
453
+ return Promise.all(found.map(async ({ ticket, path }) => withBody(ticket, ticketBody(await readText(path)))));
454
+ }
455
+ function withBody(ticket, body) {
456
+ return { ...ticket, body };
457
+ }
458
+ /**
459
+ * Every Ticket in one Effort with the file behind it, in sort order, each read
460
+ * for its fields and not for its prose. Paths never cross the driver interface,
461
+ * so this pairing stays inside the driver — it is how a write finds the file a
462
+ * Ticket came from, and how a body fetch finds the one it must open.
388
463
  */
389
464
  async function readTicketFiles(dir, effort) {
390
465
  const issues = join(dir, ISSUES_DIR);
@@ -403,15 +478,11 @@ async function readTicketFiles(dir, effort) {
403
478
  // sorted listing is the only order it has.
404
479
  return {
405
480
  filename,
406
- ticket: parseTicket(effort, { filename, contents }, position + 1, revision ?? ''),
481
+ ticket: parseTicketSummary(effort, { filename, contents }, position + 1, revision ?? ''),
407
482
  };
408
483
  }));
409
484
  return withUniqueHandles(parsed.toSorted((a, b) => a.ticket.order - b.ticket.order));
410
485
  }
411
- /** Every Ticket in one Effort, in sort order. A missing `issues/` yields none. */
412
- async function readTickets(dir, effort) {
413
- return (await readTicketFiles(dir, effort)).map(entry => entry.ticket);
414
- }
415
486
  /**
416
487
  * Two files can share an `NN-` prefix, which would give two id-less Tickets the
417
488
  * same `<effort>#<order>` handle and make one of them unfetchable. Later
@@ -434,29 +505,29 @@ function withUniqueHandles(entries) {
434
505
  });
435
506
  }
436
507
  /**
437
- * A directory under `.scratch/` is an Effort only once it holds something the
438
- * schema recognizes — a Header doc or an `issues/` directory. `.scratch/` is
439
- * also where these repos keep loose scratch output, and a directory of research
440
- * notes is not an empty Effort.
508
+ * A directory under the storage directory is an Effort only once it holds
509
+ * something the schema recognizes — a Header doc or an `issues/` directory.
510
+ * That directory is also where these repos keep loose scratch output, and a
511
+ * directory of research notes is not an empty Effort.
441
512
  */
442
- async function readEffort(scratch, slug) {
443
- const dir = join(scratch, slug);
444
- const entries = await readDir(dir);
445
- const files = new Set(entries.filter(entry => entry.isFile()).map(entry => entry.name));
513
+ async function readEffort(storage, slug) {
514
+ const dir = join(storage, slug);
515
+ const listing = await readDir(dir);
516
+ const files = new Set(listing.filter(entry => entry.isFile()).map(entry => entry.name));
446
517
  const headerDocs = HEADER_DOCS.filter(([, filename]) => files.has(filename)).map(([doc]) => doc);
447
- const hasIssues = entries.some(entry => entry.isDirectory() && entry.name === ISSUES_DIR);
518
+ const hasIssues = listing.some(entry => entry.isDirectory() && entry.name === ISSUES_DIR);
448
519
  if (headerDocs.length === 0 && !hasIssues)
449
520
  return undefined;
450
- const [tickets, destination, specOpening] = await Promise.all([
451
- readTickets(dir, slug),
521
+ const [entries, destination, specOpening] = await Promise.all([
522
+ readTicketFiles(dir, slug),
452
523
  headerDocs.includes('map')
453
524
  ? readText(join(dir, 'map.md')).then(contents => readSection(contents, SECTION_HEADINGS.destination))
454
525
  : undefined,
455
526
  headerDocs.includes('spec') ? readText(join(dir, 'spec.md')).then(readSpecOpening) : undefined,
456
527
  ]);
457
528
  return {
458
- effort: { slug, headerDocs, ticketCount: tickets.length, destination, specOpening },
459
- tickets,
529
+ effort: { slug, headerDocs, ticketCount: entries.length, destination, specOpening },
530
+ entries,
460
531
  };
461
532
  }
462
533
  /** A file that vanished between the scan and the read is read as empty, not as a failure. */