frontier-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +104 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +10 -0
- package/dist/bin.js.map +1 -0
- package/dist/domain.d.ts +230 -0
- package/dist/domain.d.ts.map +1 -0
- package/dist/domain.js +23 -0
- package/dist/domain.js.map +1 -0
- package/dist/edges.d.ts +35 -0
- package/dist/edges.d.ts.map +1 -0
- package/dist/edges.js +61 -0
- package/dist/edges.js.map +1 -0
- package/dist/frontier.d.ts +12 -0
- package/dist/frontier.d.ts.map +1 -0
- package/dist/frontier.js +34 -0
- package/dist/frontier.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +29 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +210 -0
- package/dist/server.js.map +1 -0
- package/dist/storage/driver.d.ts +131 -0
- package/dist/storage/driver.d.ts.map +1 -0
- package/dist/storage/driver.js +43 -0
- package/dist/storage/driver.js.map +1 -0
- package/dist/storage/markdown/create.d.ts +52 -0
- package/dist/storage/markdown/create.d.ts.map +1 -0
- package/dist/storage/markdown/create.js +298 -0
- package/dist/storage/markdown/create.js.map +1 -0
- package/dist/storage/markdown/driver.d.ts +7 -0
- package/dist/storage/markdown/driver.d.ts.map +1 -0
- package/dist/storage/markdown/driver.js +492 -0
- package/dist/storage/markdown/driver.js.map +1 -0
- package/dist/storage/markdown/frontmatter.d.ts +17 -0
- package/dist/storage/markdown/frontmatter.d.ts.map +1 -0
- package/dist/storage/markdown/frontmatter.js +36 -0
- package/dist/storage/markdown/frontmatter.js.map +1 -0
- package/dist/storage/markdown/header-doc.d.ts +56 -0
- package/dist/storage/markdown/header-doc.d.ts.map +1 -0
- package/dist/storage/markdown/header-doc.js +287 -0
- package/dist/storage/markdown/header-doc.js.map +1 -0
- package/dist/storage/markdown/legacy.d.ts +16 -0
- package/dist/storage/markdown/legacy.d.ts.map +1 -0
- package/dist/storage/markdown/legacy.js +152 -0
- package/dist/storage/markdown/legacy.js.map +1 -0
- package/dist/storage/markdown/migrate.d.ts +27 -0
- package/dist/storage/markdown/migrate.d.ts.map +1 -0
- package/dist/storage/markdown/migrate.js +158 -0
- package/dist/storage/markdown/migrate.js.map +1 -0
- package/dist/storage/markdown/serialize.d.ts +37 -0
- package/dist/storage/markdown/serialize.d.ts.map +1 -0
- package/dist/storage/markdown/serialize.js +321 -0
- package/dist/storage/markdown/serialize.js.map +1 -0
- package/dist/storage/markdown/ticket.d.ts +12 -0
- package/dist/storage/markdown/ticket.d.ts.map +1 -0
- package/dist/storage/markdown/ticket.js +97 -0
- package/dist/storage/markdown/ticket.js.map +1 -0
- package/dist/storage/markdown/write.d.ts +42 -0
- package/dist/storage/markdown/write.d.ts.map +1 -0
- package/dist/storage/markdown/write.js +134 -0
- package/dist/storage/markdown/write.js.map +1 -0
- package/dist/tools/create-tickets.d.ts +63 -0
- package/dist/tools/create-tickets.d.ts.map +1 -0
- package/dist/tools/create-tickets.js +161 -0
- package/dist/tools/create-tickets.js.map +1 -0
- package/dist/tools/edit-map.d.ts +29 -0
- package/dist/tools/edit-map.d.ts.map +1 -0
- package/dist/tools/edit-map.js +81 -0
- package/dist/tools/edit-map.js.map +1 -0
- package/dist/tools/get-board.d.ts +29 -0
- package/dist/tools/get-board.d.ts.map +1 -0
- package/dist/tools/get-board.js +190 -0
- package/dist/tools/get-board.js.map +1 -0
- package/dist/tools/get-tickets.d.ts +14 -0
- package/dist/tools/get-tickets.d.ts.map +1 -0
- package/dist/tools/get-tickets.js +56 -0
- package/dist/tools/get-tickets.js.map +1 -0
- package/dist/tools/list-efforts.d.ts +16 -0
- package/dist/tools/list-efforts.d.ts.map +1 -0
- package/dist/tools/list-efforts.js +38 -0
- package/dist/tools/list-efforts.js.map +1 -0
- package/dist/tools/migrate-effort.d.ts +11 -0
- package/dist/tools/migrate-effort.d.ts.map +1 -0
- package/dist/tools/migrate-effort.js +54 -0
- package/dist/tools/migrate-effort.js.map +1 -0
- package/dist/tools/spec.d.ts +12 -0
- package/dist/tools/spec.d.ts.map +1 -0
- package/dist/tools/spec.js +27 -0
- package/dist/tools/spec.js.map +1 -0
- package/dist/tools/update-ticket.d.ts +62 -0
- package/dist/tools/update-ticket.d.ts.map +1 -0
- package/dist/tools/update-ticket.js +164 -0
- package/dist/tools/update-ticket.js.map +1 -0
- package/dist/tracker-doc.d.ts +10 -0
- package/dist/tracker-doc.d.ts.map +1 -0
- package/dist/tracker-doc.js +16 -0
- package/dist/tracker-doc.js.map +1 -0
- package/dist/workspace-index.d.ts +39 -0
- package/dist/workspace-index.d.ts.map +1 -0
- package/dist/workspace-index.js +83 -0
- package/dist/workspace-index.js.map +1 -0
- package/dist/workspace-watcher.d.ts +24 -0
- package/dist/workspace-watcher.d.ts.map +1 -0
- package/dist/workspace-watcher.js +87 -0
- package/dist/workspace-watcher.js.map +1 -0
- package/dist/workspace.d.ts +23 -0
- package/dist/workspace.d.ts.map +1 -0
- package/dist/workspace.js +70 -0
- package/dist/workspace.js.map +1 -0
- package/docs/agents/issue-tracker.md +141 -0
- package/package.json +56 -0
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
import { mkdir, readdir, readFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { NoSuchEffort, NoSuchMap, NoSuchSpec, NoSuchTicket, RevisionMismatch } from '../driver.js';
|
|
4
|
+
import { createTicketFiles } from './create.js';
|
|
5
|
+
import { migrateEffortFiles } from './migrate.js';
|
|
6
|
+
import { applyEdit } from './serialize.js';
|
|
7
|
+
import { currentRevision, GuardHeld, withGuard, writeAtomically } from './write.js';
|
|
8
|
+
import { applyMapEdit, readMapDocument, readSection, readSpecOpening, scaffoldMap, SECTION_HEADINGS, } from './header-doc.js';
|
|
9
|
+
import { parseTicket } from './ticket.js';
|
|
10
|
+
/**
|
|
11
|
+
* The layout this driver serves, per AGENTS.md:
|
|
12
|
+
*
|
|
13
|
+
* .scratch/<effort>/
|
|
14
|
+
* ├── map.md # Header doc — wayfinder
|
|
15
|
+
* ├── spec.md # Header doc — to-spec
|
|
16
|
+
* └── issues/
|
|
17
|
+
* └── NN-slug.md # Tickets
|
|
18
|
+
*
|
|
19
|
+
* Anything else in an Effort directory is ignored, never an error.
|
|
20
|
+
*/
|
|
21
|
+
const SCRATCH_DIR = '.scratch';
|
|
22
|
+
const ISSUES_DIR = 'issues';
|
|
23
|
+
const MAP_FILE = 'map.md';
|
|
24
|
+
const SPEC_FILE = 'spec.md';
|
|
25
|
+
const HEADER_DOCS = [
|
|
26
|
+
['map', MAP_FILE],
|
|
27
|
+
['spec', SPEC_FILE],
|
|
28
|
+
];
|
|
29
|
+
/**
|
|
30
|
+
* The markdown driver — the only driver in v1. Bound to `root`, the resolved
|
|
31
|
+
* workspace, so that no path crosses the {@link StorageDriver} interface.
|
|
32
|
+
*/
|
|
33
|
+
export function createMarkdownDriver(root) {
|
|
34
|
+
const scratch = join(root, SCRATCH_DIR);
|
|
35
|
+
let writes = Promise.resolve();
|
|
36
|
+
const walk = async () => {
|
|
37
|
+
// A repo that has never used the tracker is a supported starting state,
|
|
38
|
+
// 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)));
|
|
41
|
+
return scanned
|
|
42
|
+
.filter((entry) => entry !== undefined)
|
|
43
|
+
.toSorted((a, b) => a.effort.slug.localeCompare(b.effort.slug));
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
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.
|
|
49
|
+
*
|
|
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.
|
|
53
|
+
*/
|
|
54
|
+
let inFlight;
|
|
55
|
+
const scan = () => {
|
|
56
|
+
inFlight ??= walk().finally(() => {
|
|
57
|
+
inFlight = undefined;
|
|
58
|
+
});
|
|
59
|
+
return inFlight;
|
|
60
|
+
};
|
|
61
|
+
return {
|
|
62
|
+
async listEfforts() {
|
|
63
|
+
return (await scan()).map(entry => entry.effort);
|
|
64
|
+
},
|
|
65
|
+
async listTickets() {
|
|
66
|
+
return (await scan()).flatMap(entry => entry.tickets);
|
|
67
|
+
},
|
|
68
|
+
updateTicket(handle, edit, expectedRevision) {
|
|
69
|
+
return serialized(() => write(scratch, handle, edit, expectedRevision));
|
|
70
|
+
},
|
|
71
|
+
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));
|
|
76
|
+
},
|
|
77
|
+
readMap(effort) {
|
|
78
|
+
return serialized(() => loadMap(scratch, effort));
|
|
79
|
+
},
|
|
80
|
+
editMap(effort, edit, expectedRevision, options) {
|
|
81
|
+
return serialized(() => saveMap(scratch, effort, edit, expectedRevision, options));
|
|
82
|
+
},
|
|
83
|
+
readSpec(effort) {
|
|
84
|
+
return serialized(() => loadSpec(scratch, effort));
|
|
85
|
+
},
|
|
86
|
+
putSpec(effort, body, expectedRevision, options) {
|
|
87
|
+
return serialized(() => saveSpec(scratch, effort, body, expectedRevision, options));
|
|
88
|
+
},
|
|
89
|
+
migrateEffort(effort, options) {
|
|
90
|
+
return serialized(() => migrate(scratch, effort, options, walk));
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Writes are serialized per workspace. Two calls racing on one Ticket would
|
|
95
|
+
* otherwise both pass the revision check before either renamed, and the
|
|
96
|
+
* loser's edit would be lost rather than refused. The queue is in-memory and
|
|
97
|
+
* dies with the process — no lock file can outlive a crash.
|
|
98
|
+
*
|
|
99
|
+
* Queueing continues after a failure: one refused write must not stop the next.
|
|
100
|
+
*/
|
|
101
|
+
function serialized(operation) {
|
|
102
|
+
const next = writes.then(operation, operation);
|
|
103
|
+
writes = next.catch(() => undefined);
|
|
104
|
+
return next;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async function migrate(scratch, effort, options, walk) {
|
|
108
|
+
const dir = join(scratch, effort);
|
|
109
|
+
if ((await readEffort(scratch, effort)) === undefined)
|
|
110
|
+
throw new NoSuchEffort(effort);
|
|
111
|
+
const entries = await readTicketFiles(dir, effort);
|
|
112
|
+
const allTickets = (await walk()).flatMap(entry => entry.tickets);
|
|
113
|
+
const issues = join(dir, ISSUES_DIR);
|
|
114
|
+
return migrateEffortFiles({
|
|
115
|
+
scratch,
|
|
116
|
+
effort,
|
|
117
|
+
issues,
|
|
118
|
+
entries,
|
|
119
|
+
allTickets,
|
|
120
|
+
preview: options.preview,
|
|
121
|
+
rename: options.rename,
|
|
122
|
+
// Allocation must not share a walk started before the guards — same reason
|
|
123
|
+
// createTickets builds its own rescan.
|
|
124
|
+
rescan: async () => (await walk()).flatMap(entry => entry.tickets),
|
|
125
|
+
readContents: filename => readFile(join(issues, filename), 'utf8'),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
async function create(scratch, effort, drafts, options, walk) {
|
|
129
|
+
const dir = join(scratch, effort);
|
|
130
|
+
const missing = (await readEffort(scratch, effort)) === undefined;
|
|
131
|
+
if (missing && !options.createEffort)
|
|
132
|
+
throw new NoSuchEffort(effort);
|
|
133
|
+
// The directory is made inside, after validation — an Effort created here and
|
|
134
|
+
// then refused would show up in list_efforts as an empty one nobody asked for.
|
|
135
|
+
const filenames = await createTicketFiles({
|
|
136
|
+
scratch,
|
|
137
|
+
effort,
|
|
138
|
+
dir,
|
|
139
|
+
issues: join(dir, ISSUES_DIR),
|
|
140
|
+
drafts,
|
|
141
|
+
rescan: async () => (await walk()).flatMap(entry => entry.tickets),
|
|
142
|
+
validate: options.validate ?? (() => { }),
|
|
143
|
+
createEffort: missing,
|
|
144
|
+
});
|
|
145
|
+
const written = await readTicketFiles(dir, effort);
|
|
146
|
+
const byFilename = new Map(written.map(entry => [entry.filename, entry.ticket]));
|
|
147
|
+
return filenames.map(filename => {
|
|
148
|
+
const ticket = byFilename.get(filename);
|
|
149
|
+
if (ticket === undefined)
|
|
150
|
+
throw new Error(`${filename} was written but could not be read back.`);
|
|
151
|
+
return ticket;
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
async function write(scratch, handle, edit, expectedRevision) {
|
|
155
|
+
const located = await locate(scratch, handle);
|
|
156
|
+
if (located === undefined)
|
|
157
|
+
throw new NoSuchTicket(handle);
|
|
158
|
+
const { dir, effort, filename, ticket } = located;
|
|
159
|
+
const path = join(dir, ISSUES_DIR, filename);
|
|
160
|
+
// The index that produced expectedRevision may be stale, so the check is
|
|
161
|
+
// against the file as it is right now, immediately before the write.
|
|
162
|
+
if ((await currentRevision(path)) !== expectedRevision)
|
|
163
|
+
throw new RevisionMismatch(handle);
|
|
164
|
+
const contents = await readFile(path, 'utf8');
|
|
165
|
+
const updated = applyEdit(contents, edit, defaultsFor(ticket));
|
|
166
|
+
try {
|
|
167
|
+
await withGuard(path, expectedRevision, async () => {
|
|
168
|
+
// Re-check inside the guard: holding it means nobody else can be writing
|
|
169
|
+
// this revision, so a mismatch now is a genuinely earlier write.
|
|
170
|
+
if ((await currentRevision(path)) !== expectedRevision)
|
|
171
|
+
throw new RevisionMismatch(handle);
|
|
172
|
+
await writeAtomically(path, updated);
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
// Losing the guard and losing the revision race mean the same thing to a
|
|
177
|
+
// caller: somebody else got there first, and nothing of theirs was touched.
|
|
178
|
+
if (error instanceof GuardHeld)
|
|
179
|
+
throw new RevisionMismatch(handle);
|
|
180
|
+
throw error;
|
|
181
|
+
}
|
|
182
|
+
const written = (await readTicketFiles(dir, effort)).find(entry => entry.filename === filename)?.ticket;
|
|
183
|
+
if (written === undefined)
|
|
184
|
+
throw new NoSuchTicket(handle);
|
|
185
|
+
// Resolving or dropping moves what the Map's derived blocks must show. Refresh
|
|
186
|
+
// them here so a session that never touches edit_map still leaves the file
|
|
187
|
+
// truthful for a human reading it on GitHub. Best-effort: the Ticket Status is
|
|
188
|
+
// already on disk, so a refresh failure must not make the lifecycle change look
|
|
189
|
+
// like it failed — the Map catches up on a later successful refresh or edit.
|
|
190
|
+
const warnings = [];
|
|
191
|
+
if (edit.status === 'resolved' || edit.status === 'dropped') {
|
|
192
|
+
try {
|
|
193
|
+
await refreshMapDerived(scratch, effort);
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
warnings.push('Decisions-so-far / dropped Out-of-scope may be stale — Map derived refresh failed after the Ticket write.');
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return { ticket: written, warnings };
|
|
200
|
+
}
|
|
201
|
+
async function loadMap(scratch, effort) {
|
|
202
|
+
const path = join(scratch, effort, MAP_FILE);
|
|
203
|
+
const revision = await currentRevision(path);
|
|
204
|
+
if (revision === undefined)
|
|
205
|
+
throw new NoSuchMap(effort);
|
|
206
|
+
return readMapDocument(await readFile(path, 'utf8'), revision);
|
|
207
|
+
}
|
|
208
|
+
async function saveMap(scratch, effort, edit, expectedRevision, options) {
|
|
209
|
+
const dir = join(scratch, effort);
|
|
210
|
+
const path = join(dir, MAP_FILE);
|
|
211
|
+
const current = await currentRevision(path);
|
|
212
|
+
const handle = `map:${effort}`;
|
|
213
|
+
if (current === undefined) {
|
|
214
|
+
// createEffort only guards starting a new Effort. An Effort that already
|
|
215
|
+
// holds a Spec or issues/ must be able to gain a Map without that flag —
|
|
216
|
+
// that is wayfinder's handoff, not a mistyped slug.
|
|
217
|
+
if ((await readEffort(scratch, effort)) === undefined && !options.createEffort) {
|
|
218
|
+
throw new NoSuchEffort(effort);
|
|
219
|
+
}
|
|
220
|
+
await mkdir(join(dir, ISSUES_DIR), { recursive: true });
|
|
221
|
+
const entries = await readTicketFiles(dir, effort);
|
|
222
|
+
const seeded = applyMapEdit(scaffoldMap(edit), {}, ticketPointers(entries, 'resolved'), ticketPointers(entries, 'dropped'));
|
|
223
|
+
await writeAtomically(path, seeded);
|
|
224
|
+
return readMapDocument(seeded, await requireRevision(path));
|
|
225
|
+
}
|
|
226
|
+
if (expectedRevision === undefined || current !== expectedRevision) {
|
|
227
|
+
throw new RevisionMismatch(handle);
|
|
228
|
+
}
|
|
229
|
+
const contents = await readFile(path, 'utf8');
|
|
230
|
+
const entries = await readTicketFiles(dir, effort);
|
|
231
|
+
const updated = applyMapEdit(contents, edit, ticketPointers(entries, 'resolved'), ticketPointers(entries, 'dropped'));
|
|
232
|
+
await guardedWrite(path, current, updated, handle);
|
|
233
|
+
return readMapDocument(updated, await requireRevision(path));
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* How many times a resolve/drop may re-read and rewrite the Map's derived
|
|
237
|
+
* blocks when a parallel edit_map holds the guard. This path is server-owned
|
|
238
|
+
* regeneration, not a caller CAS — swallowing GuardHeld left Decisions stale
|
|
239
|
+
* when the winner had scanned Tickets before the resolve landed.
|
|
240
|
+
*/
|
|
241
|
+
const DERIVED_REFRESH_ATTEMPTS = 16;
|
|
242
|
+
/**
|
|
243
|
+
* Rewrite the Map's generated blocks from the Effort's Tickets. No-op when the
|
|
244
|
+
* Effort has no Map — a Spec-only Effort has nowhere to put them.
|
|
245
|
+
*
|
|
246
|
+
* Retries on GuardHeld / lost revision: re-read Map + Tickets and regenerate.
|
|
247
|
+
* Invents the GENERATED-bearing headings when absent so a slim Map still gets
|
|
248
|
+
* a truthful Decisions / Out-of-scope cache after resolve or drop.
|
|
249
|
+
*/
|
|
250
|
+
async function refreshMapDerived(scratch, effort) {
|
|
251
|
+
return refreshMapDerivedOnce(scratch, effort, DERIVED_REFRESH_ATTEMPTS);
|
|
252
|
+
}
|
|
253
|
+
async function refreshMapDerivedOnce(scratch, effort, remaining) {
|
|
254
|
+
const path = join(scratch, effort, MAP_FILE);
|
|
255
|
+
const revision = await currentRevision(path);
|
|
256
|
+
if (revision === undefined)
|
|
257
|
+
return;
|
|
258
|
+
const contents = await readFile(path, 'utf8');
|
|
259
|
+
const entries = await readTicketFiles(join(scratch, effort), effort);
|
|
260
|
+
const updated = applyMapEdit(contents, {}, ticketPointers(entries, 'resolved'), ticketPointers(entries, 'dropped'), { ensureDerivedSections: true });
|
|
261
|
+
if (updated === contents)
|
|
262
|
+
return;
|
|
263
|
+
try {
|
|
264
|
+
await withGuard(path, revision, async () => {
|
|
265
|
+
if ((await currentRevision(path)) !== revision) {
|
|
266
|
+
throw new DerivedRefreshLost();
|
|
267
|
+
}
|
|
268
|
+
await writeAtomically(path, updated);
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
catch (error) {
|
|
272
|
+
// Server-owned derived regeneration may retry; caller CAS mismatches never do.
|
|
273
|
+
if (!(error instanceof GuardHeld || error instanceof DerivedRefreshLost))
|
|
274
|
+
throw error;
|
|
275
|
+
if (remaining <= 1) {
|
|
276
|
+
throw new Error(`Could not refresh Map derived blocks for ${effort} after concurrent edits.`, { cause: error });
|
|
277
|
+
}
|
|
278
|
+
await new Promise(wait => setTimeout(wait, 10));
|
|
279
|
+
return refreshMapDerivedOnce(scratch, effort, remaining - 1);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
/** Internal signal: another writer changed the Map while we held its guard. */
|
|
283
|
+
class DerivedRefreshLost extends Error {
|
|
284
|
+
constructor() {
|
|
285
|
+
super('Map revision changed during derived refresh');
|
|
286
|
+
this.name = 'DerivedRefreshLost';
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
async function loadSpec(scratch, effort) {
|
|
290
|
+
const path = join(scratch, effort, SPEC_FILE);
|
|
291
|
+
const revision = await currentRevision(path);
|
|
292
|
+
if (revision === undefined)
|
|
293
|
+
throw new NoSuchSpec(effort);
|
|
294
|
+
return { body: await readFile(path, 'utf8'), revision };
|
|
295
|
+
}
|
|
296
|
+
async function saveSpec(scratch, effort, body, expectedRevision, options) {
|
|
297
|
+
const dir = join(scratch, effort);
|
|
298
|
+
const path = join(dir, SPEC_FILE);
|
|
299
|
+
const current = await currentRevision(path);
|
|
300
|
+
const handle = `spec:${effort}`;
|
|
301
|
+
// Opaque bytes — store what the caller sent. No BOM strip, no trailing
|
|
302
|
+
// newline inject, no frontmatter reshape (ADR 0001; Spec has no typed body).
|
|
303
|
+
if (current === undefined) {
|
|
304
|
+
// Same rule as Maps: createEffort starts an Effort; putting a Spec onto an
|
|
305
|
+
// Effort that already has a Map needs no flag.
|
|
306
|
+
if ((await readEffort(scratch, effort)) === undefined && !options.createEffort) {
|
|
307
|
+
throw new NoSuchEffort(effort);
|
|
308
|
+
}
|
|
309
|
+
await mkdir(join(dir, ISSUES_DIR), { recursive: true });
|
|
310
|
+
await writeAtomically(path, body);
|
|
311
|
+
return { body, revision: await requireRevision(path) };
|
|
312
|
+
}
|
|
313
|
+
if (expectedRevision === undefined || current !== expectedRevision) {
|
|
314
|
+
throw new RevisionMismatch(handle);
|
|
315
|
+
}
|
|
316
|
+
await guardedWrite(path, current, body, handle);
|
|
317
|
+
return { body, revision: await requireRevision(path) };
|
|
318
|
+
}
|
|
319
|
+
async function guardedWrite(path, revision, contents, handle) {
|
|
320
|
+
try {
|
|
321
|
+
await withGuard(path, revision, async () => {
|
|
322
|
+
if ((await currentRevision(path)) !== revision)
|
|
323
|
+
throw new RevisionMismatch(handle);
|
|
324
|
+
await writeAtomically(path, contents);
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
catch (error) {
|
|
328
|
+
if (error instanceof GuardHeld)
|
|
329
|
+
throw new RevisionMismatch(handle);
|
|
330
|
+
throw error;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
async function requireRevision(path) {
|
|
334
|
+
const revision = await currentRevision(path);
|
|
335
|
+
if (revision === undefined) {
|
|
336
|
+
throw new Error(`Wrote ${path} but could not read its revision back.`);
|
|
337
|
+
}
|
|
338
|
+
return revision;
|
|
339
|
+
}
|
|
340
|
+
function ticketPointers(entries, status) {
|
|
341
|
+
return entries.flatMap(entry => {
|
|
342
|
+
const { ticket, filename } = entry;
|
|
343
|
+
if (ticket.status !== status || ticket.id === undefined)
|
|
344
|
+
return [];
|
|
345
|
+
const gist = status === 'resolved' ? ticket.answerGist : ticket.droppedReason;
|
|
346
|
+
return [
|
|
347
|
+
{
|
|
348
|
+
id: ticket.id,
|
|
349
|
+
title: ticket.title,
|
|
350
|
+
link: `${ISSUES_DIR}/${filename}`,
|
|
351
|
+
gist: gist ?? '',
|
|
352
|
+
},
|
|
353
|
+
];
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
/** 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) })));
|
|
360
|
+
for (const { slug, entries } of perEffort) {
|
|
361
|
+
const found = entries.find(entry => entry.ticket.id === handle || entry.ticket.handle === handle);
|
|
362
|
+
if (found !== undefined) {
|
|
363
|
+
return {
|
|
364
|
+
dir: join(scratch, slug),
|
|
365
|
+
effort: slug,
|
|
366
|
+
filename: found.filename,
|
|
367
|
+
ticket: found.ticket,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return undefined;
|
|
372
|
+
}
|
|
373
|
+
function defaultsFor(ticket) {
|
|
374
|
+
return {
|
|
375
|
+
id: ticket.id,
|
|
376
|
+
title: ticket.title,
|
|
377
|
+
kind: ticket.kind,
|
|
378
|
+
type: ticket.type,
|
|
379
|
+
status: ticket.status,
|
|
380
|
+
triage: ticket.triage,
|
|
381
|
+
blockedBy: ticket.blockedBy,
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
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.
|
|
388
|
+
*/
|
|
389
|
+
async function readTicketFiles(dir, effort) {
|
|
390
|
+
const issues = join(dir, ISSUES_DIR);
|
|
391
|
+
const entries = await readDir(issues);
|
|
392
|
+
const filenames = entries
|
|
393
|
+
.filter(entry => entry.isFile() && entry.name.endsWith('.md'))
|
|
394
|
+
.map(entry => entry.name)
|
|
395
|
+
.toSorted();
|
|
396
|
+
const parsed = await Promise.all(filenames.map(async (filename, position) => {
|
|
397
|
+
const path = join(issues, filename);
|
|
398
|
+
const [contents, revision] = await Promise.all([
|
|
399
|
+
readFile(path, 'utf8'),
|
|
400
|
+
currentRevision(path),
|
|
401
|
+
]);
|
|
402
|
+
// A file with no `NN-` prefix still needs a position; its place in the
|
|
403
|
+
// sorted listing is the only order it has.
|
|
404
|
+
return {
|
|
405
|
+
filename,
|
|
406
|
+
ticket: parseTicket(effort, { filename, contents }, position + 1, revision ?? ''),
|
|
407
|
+
};
|
|
408
|
+
}));
|
|
409
|
+
return withUniqueHandles(parsed.toSorted((a, b) => a.ticket.order - b.ticket.order));
|
|
410
|
+
}
|
|
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
|
+
/**
|
|
416
|
+
* Two files can share an `NN-` prefix, which would give two id-less Tickets the
|
|
417
|
+
* same `<effort>#<order>` handle and make one of them unfetchable. Later
|
|
418
|
+
* collisions get a suffix, so every Ticket stays individually addressable.
|
|
419
|
+
*/
|
|
420
|
+
function withUniqueHandles(entries) {
|
|
421
|
+
const seen = new Set();
|
|
422
|
+
return entries.map(entry => {
|
|
423
|
+
const { ticket } = entry;
|
|
424
|
+
if (!seen.has(ticket.handle)) {
|
|
425
|
+
seen.add(ticket.handle);
|
|
426
|
+
return entry;
|
|
427
|
+
}
|
|
428
|
+
let suffix = 2;
|
|
429
|
+
while (seen.has(`${ticket.handle}.${String(suffix)}`))
|
|
430
|
+
suffix += 1;
|
|
431
|
+
const handle = `${ticket.handle}.${String(suffix)}`;
|
|
432
|
+
seen.add(handle);
|
|
433
|
+
return { filename: entry.filename, ticket: { ...ticket, handle } };
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
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.
|
|
441
|
+
*/
|
|
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));
|
|
446
|
+
const headerDocs = HEADER_DOCS.filter(([, filename]) => files.has(filename)).map(([doc]) => doc);
|
|
447
|
+
const hasIssues = entries.some(entry => entry.isDirectory() && entry.name === ISSUES_DIR);
|
|
448
|
+
if (headerDocs.length === 0 && !hasIssues)
|
|
449
|
+
return undefined;
|
|
450
|
+
const [tickets, destination, specOpening] = await Promise.all([
|
|
451
|
+
readTickets(dir, slug),
|
|
452
|
+
headerDocs.includes('map')
|
|
453
|
+
? readText(join(dir, 'map.md')).then(contents => readSection(contents, SECTION_HEADINGS.destination))
|
|
454
|
+
: undefined,
|
|
455
|
+
headerDocs.includes('spec') ? readText(join(dir, 'spec.md')).then(readSpecOpening) : undefined,
|
|
456
|
+
]);
|
|
457
|
+
return {
|
|
458
|
+
effort: { slug, headerDocs, ticketCount: tickets.length, destination, specOpening },
|
|
459
|
+
tickets,
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
/** A file that vanished between the scan and the read is read as empty, not as a failure. */
|
|
463
|
+
async function readText(path) {
|
|
464
|
+
try {
|
|
465
|
+
return await readFile(path, 'utf8');
|
|
466
|
+
}
|
|
467
|
+
catch (error) {
|
|
468
|
+
if (isMissing(error))
|
|
469
|
+
return '';
|
|
470
|
+
throw error;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
async function readDirectories(dir) {
|
|
474
|
+
const entries = await readDir(dir);
|
|
475
|
+
return entries.filter(entry => entry.isDirectory()).map(entry => entry.name);
|
|
476
|
+
}
|
|
477
|
+
/** A missing directory reads as empty — the layout is optional at every level. */
|
|
478
|
+
async function readDir(dir) {
|
|
479
|
+
try {
|
|
480
|
+
return await readdir(dir, { withFileTypes: true });
|
|
481
|
+
}
|
|
482
|
+
catch (error) {
|
|
483
|
+
if (isMissing(error))
|
|
484
|
+
return [];
|
|
485
|
+
throw error;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
function isMissing(error) {
|
|
489
|
+
const code = error?.code;
|
|
490
|
+
return code === 'ENOENT' || code === 'ENOTDIR';
|
|
491
|
+
}
|
|
492
|
+
//# sourceMappingURL=driver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"driver.js","sourceRoot":"","sources":["../../../src/storage/markdown/driver.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface Split {
|
|
2
|
+
/**
|
|
3
|
+
* Whether the file opened with a `---` fence at all. Distinct from
|
|
4
|
+
* {@link Split.fields} being undefined, which also happens when a fence is
|
|
5
|
+
* present but its YAML will not parse — a write must normalize that file
|
|
6
|
+
* rather than treat the fence as body prose and duplicate it.
|
|
7
|
+
*/
|
|
8
|
+
readonly hasFence: boolean;
|
|
9
|
+
/** The fence's raw YAML, unparsed. Undefined when there was no fence. */
|
|
10
|
+
readonly raw: string | undefined;
|
|
11
|
+
/** The parsed frontmatter, or undefined when there is none or it is malformed. */
|
|
12
|
+
readonly fields: Record<string, unknown> | undefined;
|
|
13
|
+
/** Everything after the fence, verbatim. The whole file when there is none. */
|
|
14
|
+
readonly body: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function splitFrontmatter(contents: string): Split;
|
|
17
|
+
//# sourceMappingURL=frontmatter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frontmatter.d.ts","sourceRoot":"","sources":["../../../src/storage/markdown/frontmatter.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,KAAK;IACpB;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,yEAAyE;IACzE,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,kFAAkF;IAClF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IACrD,+EAA+E;IAC/E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,KAAK,CAYxD"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { parse } from 'yaml';
|
|
2
|
+
/**
|
|
3
|
+
* The `---` fence split, which is all a frontmatter library would have given us.
|
|
4
|
+
* Per ADR 0003 the writes in T3 mutate a parsed YAML document rather than
|
|
5
|
+
* re-emitting an object, so this file deliberately stops at splitting.
|
|
6
|
+
*/
|
|
7
|
+
const FENCE = /^---\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)/;
|
|
8
|
+
export function splitFrontmatter(contents) {
|
|
9
|
+
const match = FENCE.exec(contents);
|
|
10
|
+
if (match?.[1] === undefined) {
|
|
11
|
+
return { hasFence: false, raw: undefined, fields: undefined, body: contents };
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
hasFence: true,
|
|
15
|
+
raw: match[1],
|
|
16
|
+
fields: parseFields(match[1]),
|
|
17
|
+
body: contents.slice(match[0].length),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Reads are lenient: frontmatter that is not a YAML mapping — malformed, or a
|
|
22
|
+
* scalar, or a list — reads as no frontmatter at all, and the file falls through
|
|
23
|
+
* to the Legacy parser rather than failing the whole scan.
|
|
24
|
+
*/
|
|
25
|
+
function parseFields(yaml) {
|
|
26
|
+
try {
|
|
27
|
+
const parsed = parse(yaml);
|
|
28
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
29
|
+
return undefined;
|
|
30
|
+
return parsed;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=frontmatter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frontmatter.js","sourceRoot":"","sources":["../../../src/storage/markdown/frontmatter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,MAAM,CAAC;AAE7B;;;;GAIG;AACH,MAAM,KAAK,GAAG,8CAA8C,CAAC;AAkB7D,MAAM,UAAU,gBAAgB,CAAC,QAAgB;IAC/C,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACnC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,SAAS,EAAE,CAAC;QAC7B,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IAChF,CAAC;IAED,OAAO;QACL,QAAQ,EAAE,IAAI;QACd,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC;QACb,MAAM,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC7B,IAAI,EAAE,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;KACtC,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,SAAS,WAAW,CAAC,IAAY;IAC/B,IAAI,CAAC;QACH,MAAM,MAAM,GAAY,KAAK,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;YAAE,OAAO,SAAS,CAAC;QAC7F,OAAO,MAAiC,CAAC;IAC3C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { MapDocument, MapEdit } from '../../domain.ts';
|
|
2
|
+
export declare const SECTION_HEADINGS: {
|
|
3
|
+
readonly destination: 'Destination';
|
|
4
|
+
readonly notes: 'Notes';
|
|
5
|
+
readonly decisions: 'Decisions so far';
|
|
6
|
+
readonly fog: 'Not yet specified';
|
|
7
|
+
readonly outOfScope: 'Out of scope';
|
|
8
|
+
};
|
|
9
|
+
/** Markers stating that their contents are overwritten — ADR 0002. */
|
|
10
|
+
export declare const GENERATED_OPEN = "<!-- GENERATED: overwritten on every mutation through the server. Do not hand-edit. -->";
|
|
11
|
+
export declare const GENERATED_CLOSE = "<!-- /GENERATED -->";
|
|
12
|
+
/**
|
|
13
|
+
* One derived pointer line: a resolved or dropped Ticket as it appears in a
|
|
14
|
+
* generated Map block. The link is relative to the Map file.
|
|
15
|
+
*/
|
|
16
|
+
export interface DerivedPointer {
|
|
17
|
+
readonly id: string;
|
|
18
|
+
readonly title: string;
|
|
19
|
+
readonly link: string;
|
|
20
|
+
readonly gist: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The opening paragraph of a Spec — the first prose under its title. A Spec has
|
|
24
|
+
* no Destination section, so this is the orienting text a Spec-only Effort has,
|
|
25
|
+
* and a Board that opens with nothing leaves every Ticket line uninterpretable.
|
|
26
|
+
*/
|
|
27
|
+
export declare function readSpecOpening(contents: string): string | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* Typed sections a caller can read without paying for the whole Map body —
|
|
30
|
+
* Decisions-so-far is deliberately absent; it is never read from the file.
|
|
31
|
+
*/
|
|
32
|
+
export declare function readMapDocument(contents: string, revision: string): MapDocument;
|
|
33
|
+
export interface ApplyMapEditOptions {
|
|
34
|
+
/**
|
|
35
|
+
* When true, invent the Decisions / Out-of-scope headings (with empty
|
|
36
|
+
* GENERATED blocks) if absent. Used by resolve/drop refresh so a slim Map
|
|
37
|
+
* still gets a truthful derived cache. Ordinary edit_map section edits leave
|
|
38
|
+
* the Map shape alone — they never invent unrelated headings.
|
|
39
|
+
*/
|
|
40
|
+
readonly ensureDerivedSections?: boolean;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Apply a section edit and rewrite the derived blocks. Content outside the
|
|
44
|
+
* GENERATED markers is left alone; the blocks themselves are replaced whole.
|
|
45
|
+
* When Decisions has no markers, its whole body is replaced by a GENERATED
|
|
46
|
+
* block (unfenced stale prose must not remain). Out of scope without markers
|
|
47
|
+
* keeps unfenced bullets as hand-owned and installs the GENERATED block under
|
|
48
|
+
* them. Missing headings are not invented unless
|
|
49
|
+
* {@link ApplyMapEditOptions.ensureDerivedSections} is set.
|
|
50
|
+
*/
|
|
51
|
+
export declare function applyMapEdit(contents: string, edit: MapEdit, decisions: readonly DerivedPointer[], dropped: readonly DerivedPointer[], options?: ApplyMapEditOptions): string;
|
|
52
|
+
/** A brand-new Map file, with empty generated blocks ready for the first write. */
|
|
53
|
+
export declare function scaffoldMap(edit: MapEdit): string;
|
|
54
|
+
export declare function ensureTrailingNewline(text: string): string;
|
|
55
|
+
export declare function readSection(contents: string, heading: string): string | undefined;
|
|
56
|
+
//# sourceMappingURL=header-doc.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"header-doc.d.ts","sourceRoot":"","sources":["../../../src/storage/markdown/header-doc.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAS5D,eAAO,MAAM,gBAAgB;aAC3B,WAAW,EAAE,aAAa;aAC1B,KAAK,EAAE,OAAO;aACd,SAAS,EAAE,kBAAkB;aAC7B,GAAG,EAAE,mBAAmB;aACxB,UAAU,EAAE,cAAc;CAClB,CAAC;AAEX,sEAAsE;AACtE,eAAO,MAAM,cAAc,4FACgE,CAAC;AAC5F,eAAO,MAAM,eAAe,wBAAwB,CAAC;AAOrD;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAOpE;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,WAAW,CAQ/E;AAED,MAAM,WAAW,mBAAmB;IAClC;;;;;OAKG;IACH,QAAQ,CAAC,qBAAqB,CAAC,EAAE,OAAO,CAAC;CAC1C;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,OAAO,EACb,SAAS,EAAE,SAAS,cAAc,EAAE,EACpC,OAAO,EAAE,SAAS,cAAc,EAAE,EAClC,OAAO,GAAE,mBAAwB,GAChC,MAAM,CAgCR;AAED,mFAAmF;AACnF,wBAAgB,WAAW,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CA0CjD;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE1D;AAYD,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAKjF"}
|