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.
Files changed (115) hide show
  1. package/README.md +104 -0
  2. package/dist/bin.d.ts +3 -0
  3. package/dist/bin.d.ts.map +1 -0
  4. package/dist/bin.js +10 -0
  5. package/dist/bin.js.map +1 -0
  6. package/dist/domain.d.ts +230 -0
  7. package/dist/domain.d.ts.map +1 -0
  8. package/dist/domain.js +23 -0
  9. package/dist/domain.js.map +1 -0
  10. package/dist/edges.d.ts +35 -0
  11. package/dist/edges.d.ts.map +1 -0
  12. package/dist/edges.js +61 -0
  13. package/dist/edges.js.map +1 -0
  14. package/dist/frontier.d.ts +12 -0
  15. package/dist/frontier.d.ts.map +1 -0
  16. package/dist/frontier.js +34 -0
  17. package/dist/frontier.js.map +1 -0
  18. package/dist/index.d.ts +5 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +2 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/server.d.ts +29 -0
  23. package/dist/server.d.ts.map +1 -0
  24. package/dist/server.js +210 -0
  25. package/dist/server.js.map +1 -0
  26. package/dist/storage/driver.d.ts +131 -0
  27. package/dist/storage/driver.d.ts.map +1 -0
  28. package/dist/storage/driver.js +43 -0
  29. package/dist/storage/driver.js.map +1 -0
  30. package/dist/storage/markdown/create.d.ts +52 -0
  31. package/dist/storage/markdown/create.d.ts.map +1 -0
  32. package/dist/storage/markdown/create.js +298 -0
  33. package/dist/storage/markdown/create.js.map +1 -0
  34. package/dist/storage/markdown/driver.d.ts +7 -0
  35. package/dist/storage/markdown/driver.d.ts.map +1 -0
  36. package/dist/storage/markdown/driver.js +492 -0
  37. package/dist/storage/markdown/driver.js.map +1 -0
  38. package/dist/storage/markdown/frontmatter.d.ts +17 -0
  39. package/dist/storage/markdown/frontmatter.d.ts.map +1 -0
  40. package/dist/storage/markdown/frontmatter.js +36 -0
  41. package/dist/storage/markdown/frontmatter.js.map +1 -0
  42. package/dist/storage/markdown/header-doc.d.ts +56 -0
  43. package/dist/storage/markdown/header-doc.d.ts.map +1 -0
  44. package/dist/storage/markdown/header-doc.js +287 -0
  45. package/dist/storage/markdown/header-doc.js.map +1 -0
  46. package/dist/storage/markdown/legacy.d.ts +16 -0
  47. package/dist/storage/markdown/legacy.d.ts.map +1 -0
  48. package/dist/storage/markdown/legacy.js +152 -0
  49. package/dist/storage/markdown/legacy.js.map +1 -0
  50. package/dist/storage/markdown/migrate.d.ts +27 -0
  51. package/dist/storage/markdown/migrate.d.ts.map +1 -0
  52. package/dist/storage/markdown/migrate.js +158 -0
  53. package/dist/storage/markdown/migrate.js.map +1 -0
  54. package/dist/storage/markdown/serialize.d.ts +37 -0
  55. package/dist/storage/markdown/serialize.d.ts.map +1 -0
  56. package/dist/storage/markdown/serialize.js +321 -0
  57. package/dist/storage/markdown/serialize.js.map +1 -0
  58. package/dist/storage/markdown/ticket.d.ts +12 -0
  59. package/dist/storage/markdown/ticket.d.ts.map +1 -0
  60. package/dist/storage/markdown/ticket.js +97 -0
  61. package/dist/storage/markdown/ticket.js.map +1 -0
  62. package/dist/storage/markdown/write.d.ts +42 -0
  63. package/dist/storage/markdown/write.d.ts.map +1 -0
  64. package/dist/storage/markdown/write.js +134 -0
  65. package/dist/storage/markdown/write.js.map +1 -0
  66. package/dist/tools/create-tickets.d.ts +63 -0
  67. package/dist/tools/create-tickets.d.ts.map +1 -0
  68. package/dist/tools/create-tickets.js +161 -0
  69. package/dist/tools/create-tickets.js.map +1 -0
  70. package/dist/tools/edit-map.d.ts +29 -0
  71. package/dist/tools/edit-map.d.ts.map +1 -0
  72. package/dist/tools/edit-map.js +81 -0
  73. package/dist/tools/edit-map.js.map +1 -0
  74. package/dist/tools/get-board.d.ts +29 -0
  75. package/dist/tools/get-board.d.ts.map +1 -0
  76. package/dist/tools/get-board.js +190 -0
  77. package/dist/tools/get-board.js.map +1 -0
  78. package/dist/tools/get-tickets.d.ts +14 -0
  79. package/dist/tools/get-tickets.d.ts.map +1 -0
  80. package/dist/tools/get-tickets.js +56 -0
  81. package/dist/tools/get-tickets.js.map +1 -0
  82. package/dist/tools/list-efforts.d.ts +16 -0
  83. package/dist/tools/list-efforts.d.ts.map +1 -0
  84. package/dist/tools/list-efforts.js +38 -0
  85. package/dist/tools/list-efforts.js.map +1 -0
  86. package/dist/tools/migrate-effort.d.ts +11 -0
  87. package/dist/tools/migrate-effort.d.ts.map +1 -0
  88. package/dist/tools/migrate-effort.js +54 -0
  89. package/dist/tools/migrate-effort.js.map +1 -0
  90. package/dist/tools/spec.d.ts +12 -0
  91. package/dist/tools/spec.d.ts.map +1 -0
  92. package/dist/tools/spec.js +27 -0
  93. package/dist/tools/spec.js.map +1 -0
  94. package/dist/tools/update-ticket.d.ts +62 -0
  95. package/dist/tools/update-ticket.d.ts.map +1 -0
  96. package/dist/tools/update-ticket.js +164 -0
  97. package/dist/tools/update-ticket.js.map +1 -0
  98. package/dist/tracker-doc.d.ts +10 -0
  99. package/dist/tracker-doc.d.ts.map +1 -0
  100. package/dist/tracker-doc.js +16 -0
  101. package/dist/tracker-doc.js.map +1 -0
  102. package/dist/workspace-index.d.ts +39 -0
  103. package/dist/workspace-index.d.ts.map +1 -0
  104. package/dist/workspace-index.js +83 -0
  105. package/dist/workspace-index.js.map +1 -0
  106. package/dist/workspace-watcher.d.ts +24 -0
  107. package/dist/workspace-watcher.d.ts.map +1 -0
  108. package/dist/workspace-watcher.js +87 -0
  109. package/dist/workspace-watcher.js.map +1 -0
  110. package/dist/workspace.d.ts +23 -0
  111. package/dist/workspace.d.ts.map +1 -0
  112. package/dist/workspace.js +70 -0
  113. package/dist/workspace.js.map +1 -0
  114. package/docs/agents/issue-tracker.md +141 -0
  115. package/package.json +56 -0
@@ -0,0 +1,158 @@
1
+ import { rename as renameFile, unlink } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { peekMintedIds, ticketFilename, withIdReservations } from './create.js';
4
+ import { normalizeTicket } from './serialize.js';
5
+ import { stage, unstage, writeAtomically } from './write.js';
6
+ /**
7
+ * Normalize Legacy Tickets in one Effort, mint ids for those without, and
8
+ * optionally rename. Preview plans mint ids from the scan and writes nothing.
9
+ *
10
+ * Bare sort-order Edges (`Blocked by: 01`) are rewritten to the id the target
11
+ * Ticket carries after this call — preserved or newly minted — so migration
12
+ * does not leave a Board full of unresolvable `01` references.
13
+ */
14
+ export async function migrateEffortFiles(request) {
15
+ const { effort, entries, preview, rename } = request;
16
+ const candidates = entries.filter(entry => entry.ticket.legacy || entry.ticket.id === undefined);
17
+ if (candidates.length === 0) {
18
+ return { effort, preview, renamed: rename, changes: [] };
19
+ }
20
+ const needMint = candidates.filter(entry => entry.ticket.id === undefined).length;
21
+ const used = idsOf(request.allTickets);
22
+ if (preview) {
23
+ const plan = planChanges(candidates, peekMintedIds(used, needMint), entries, rename);
24
+ return { effort, preview: true, renamed: rename, changes: plan.map(file => file.change) };
25
+ }
26
+ return withIdReservations(request.scratch, needMint, request.rescan, async (minted) => {
27
+ const plan = planChanges(candidates, minted, entries, rename);
28
+ await applyPlan(request, plan);
29
+ return {
30
+ effort,
31
+ preview: false,
32
+ renamed: rename,
33
+ changes: plan.map(file => file.change),
34
+ };
35
+ });
36
+ }
37
+ function planChanges(candidates, minted, entries, rename) {
38
+ let mintAt = 0;
39
+ const idByOrder = new Map();
40
+ // Every Ticket in the Effort contributes to Edge remapping — a blocker that
41
+ // already had an id must still be findable by its sort order.
42
+ for (const entry of entries) {
43
+ if (entry.ticket.id !== undefined)
44
+ idByOrder.set(entry.ticket.order, entry.ticket.id);
45
+ }
46
+ const assigned = new Map();
47
+ for (const entry of candidates) {
48
+ if (entry.ticket.id !== undefined) {
49
+ assigned.set(entry.ticket.handle, entry.ticket.id);
50
+ idByOrder.set(entry.ticket.order, entry.ticket.id);
51
+ continue;
52
+ }
53
+ const id = minted[mintAt++];
54
+ if (id === undefined)
55
+ throw new Error('Reserved fewer ids than Tickets needing one.');
56
+ assigned.set(entry.ticket.handle, id);
57
+ idByOrder.set(entry.ticket.order, id);
58
+ }
59
+ return candidates.map(entry => {
60
+ const id = assigned.get(entry.ticket.handle);
61
+ if (id === undefined)
62
+ throw new Error(`No id planned for ${entry.ticket.handle}.`);
63
+ const to = rename ? ticketFilename(entry.ticket.order, id, entry.ticket.title) : entry.filename;
64
+ const blockedBy = remapEdges(entry.ticket.blockedBy, idByOrder);
65
+ return {
66
+ from: entry.filename,
67
+ to,
68
+ defaults: {
69
+ id,
70
+ title: entry.ticket.title,
71
+ kind: entry.ticket.kind,
72
+ type: entry.ticket.type,
73
+ status: entry.ticket.status,
74
+ triage: entry.ticket.triage,
75
+ blockedBy,
76
+ },
77
+ change: {
78
+ beforeHandle: entry.ticket.handle,
79
+ id,
80
+ title: entry.ticket.title,
81
+ minted: entry.ticket.id === undefined,
82
+ normalized: entry.ticket.legacy,
83
+ fromName: to === entry.filename ? undefined : entry.filename,
84
+ toName: to === entry.filename ? undefined : to,
85
+ blockedBy,
86
+ },
87
+ };
88
+ });
89
+ }
90
+ async function applyPlan(request, plan) {
91
+ const targets = new Set(plan.map(file => file.to));
92
+ const sources = new Set(plan.map(file => file.from));
93
+ for (const file of plan) {
94
+ if (file.to !== file.from && !sources.has(file.to)) {
95
+ // A name that already exists and is not being vacated would destroy a Ticket.
96
+ const clash = request.entries.some(entry => entry.filename === file.to);
97
+ if (clash) {
98
+ throw new Error(`Cannot rename to ${file.to}: that name is already taken in ${request.effort}.`);
99
+ }
100
+ }
101
+ }
102
+ // Detect two Tickets collapsing onto one target name.
103
+ if (targets.size !== plan.length) {
104
+ throw new Error(`Rename would collide on a filename inside Effort '${request.effort}'.`);
105
+ }
106
+ const prepared = await Promise.all(plan.map(async (file) => ({
107
+ ...file,
108
+ contents: normalizeTicket(await request.readContents(file.from), file.defaults),
109
+ })));
110
+ // Same-name updates are per-file atomic. Renames stage every new file first,
111
+ // land them, then remove the vacated names — so a failure mid-batch never
112
+ // deletes a source whose replacement did not land.
113
+ const inPlace = prepared.filter(file => file.from === file.to);
114
+ const moved = prepared.filter(file => file.from !== file.to);
115
+ await Promise.all(inPlace.map(file => writeAtomically(join(request.issues, file.to), file.contents)));
116
+ if (moved.length === 0)
117
+ return;
118
+ const staged = await Promise.allSettled(moved.map(async (file) => ({
119
+ temporary: await stage(join(request.issues, file.to), file.contents),
120
+ target: join(request.issues, file.to),
121
+ from: join(request.issues, file.from),
122
+ })));
123
+ const ready = staged.filter(entry => entry.status === 'fulfilled').map(entry => entry.value);
124
+ const failed = staged.find(entry => entry.status === 'rejected');
125
+ if (failed !== undefined) {
126
+ await Promise.allSettled(ready.map(entry => unstage(entry.temporary)));
127
+ throw failed.reason;
128
+ }
129
+ const landed = await Promise.allSettled(ready.map(async (entry) => {
130
+ await renameFile(entry.temporary, entry.target);
131
+ return entry;
132
+ }));
133
+ const written = landed.filter(entry => entry.status === 'fulfilled').map(entry => entry.value);
134
+ const lost = landed.find(entry => entry.status === 'rejected');
135
+ if (lost !== undefined) {
136
+ await Promise.allSettled([
137
+ ...written.map(entry => unlink(entry.target).catch(() => { })),
138
+ ...ready.map(entry => unstage(entry.temporary)),
139
+ ]);
140
+ throw lost.reason;
141
+ }
142
+ await Promise.all(written.map(entry => unlink(entry.from)));
143
+ }
144
+ /**
145
+ * A bare sort-order Edge becomes the id of the Ticket at that order. Real ids
146
+ * and anything else pass through unchanged — dangling Edges stay Board warnings.
147
+ */
148
+ function remapEdges(edges, idByOrder) {
149
+ return edges.map(edge => {
150
+ if (!/^\d+$/.test(edge))
151
+ return edge;
152
+ return idByOrder.get(Number(edge)) ?? edge;
153
+ });
154
+ }
155
+ function idsOf(tickets) {
156
+ return new Set(tickets.map(entry => entry.id).filter(id => id !== undefined));
157
+ }
158
+ //# sourceMappingURL=migrate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate.js","sourceRoot":"","sources":["../../../src/storage/markdown/migrate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ import type { TicketEdit } from '../../domain.ts';
2
+ export interface Defaults {
3
+ readonly id: string | undefined;
4
+ readonly title: string;
5
+ readonly kind: string;
6
+ readonly type: string | undefined;
7
+ readonly status: string;
8
+ readonly triage: string | undefined;
9
+ readonly blockedBy: readonly string[];
10
+ }
11
+ /**
12
+ * Normalize a Ticket file to schema frontmatter. A Legacy file with no fence
13
+ * gets a full block from {@link defaults}; a file that already has frontmatter
14
+ * keeps key order and only gains a missing id plus an updated Edge list (so a
15
+ * bare sort-order Edge can become the id migration just minted). Body prose is
16
+ * kept verbatim either way.
17
+ */
18
+ export declare function normalizeTicket(contents: string, defaults: Defaults): string;
19
+ /**
20
+ * Apply an edit to a Ticket file, returning the new contents.
21
+ *
22
+ * Per ADR 0003 the frontmatter is mutated as a parsed YAML document rather than
23
+ * rebuilt from an object, so comments, key order and quoting style survive on
24
+ * every field the edit did not name. The body is a string this only ever
25
+ * appends to or replaces one known section of.
26
+ */
27
+ export declare function applyEdit(contents: string, edit: TicketEdit, defaults: Defaults): string;
28
+ /**
29
+ * A Ticket file as first written. Everything is a field this driver is creating,
30
+ * so the whole block is in schema order — the one case where that is allowed,
31
+ * since there is no author's ordering to preserve.
32
+ *
33
+ * The body is the caller's prose, stored verbatim. A draft with none produces a
34
+ * Ticket with none rather than a heading nobody asked for.
35
+ */
36
+ export declare function serializeNew(fields: Defaults, body: string | undefined): string;
37
+ //# sourceMappingURL=serialize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serialize.d.ts","sourceRoot":"","sources":["../../../src/storage/markdown/serialize.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AA4BlD,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;CACvC;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAiB5E;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAsBxF;AA0LD;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAK/E"}
@@ -0,0 +1,321 @@
1
+ import { Document, isSeq, parseDocument } from 'yaml';
2
+ import { splitFrontmatter } from './frontmatter.js';
3
+ /**
4
+ * Field order for frontmatter this driver *creates*. An existing file is never
5
+ * reordered — a new field is appended, because moving keys a write did not name
6
+ * is exactly the reformatting ADR 0003 forbids.
7
+ */
8
+ const FIELD_ORDER = [
9
+ 'id',
10
+ 'title',
11
+ 'kind',
12
+ 'type',
13
+ 'status',
14
+ 'triage',
15
+ 'blocked_by',
16
+ 'claimed_by',
17
+ 'claimed_at',
18
+ 'answer_gist',
19
+ 'dropped_reason',
20
+ ];
21
+ /** The body sections a write may touch. Everything else is opaque prose. */
22
+ const ANSWER_HEADING = '## Answer';
23
+ const COMMENTS_HEADING = '## Comments';
24
+ /** Any `##` heading, so replacing the answer stops at the next section. */
25
+ const NEXT_SECTION = /^##[ \t]/m;
26
+ /**
27
+ * Normalize a Ticket file to schema frontmatter. A Legacy file with no fence
28
+ * gets a full block from {@link defaults}; a file that already has frontmatter
29
+ * keeps key order and only gains a missing id plus an updated Edge list (so a
30
+ * bare sort-order Edge can become the id migration just minted). Body prose is
31
+ * kept verbatim either way.
32
+ */
33
+ export function normalizeTicket(contents, defaults) {
34
+ const split = splitFrontmatter(contents);
35
+ if (!split.hasFence)
36
+ return applyEdit(contents, {}, defaults);
37
+ const document = existingDocument(split.raw);
38
+ if (document === undefined)
39
+ return applyEdit(contents, {}, defaults);
40
+ if (defaults.id !== undefined) {
41
+ const existing = document.get('id');
42
+ if (existing === undefined || existing === null || String(existing).trim() === '') {
43
+ document.set('id', defaults.id);
44
+ }
45
+ }
46
+ setEdges(document, defaults.blockedBy);
47
+ const body = split.body.replace(/^\s+/, '').replace(/\n+$/, '');
48
+ return `---\n${render(document)}\n---\n\n${body}\n`;
49
+ }
50
+ /**
51
+ * Apply an edit to a Ticket file, returning the new contents.
52
+ *
53
+ * Per ADR 0003 the frontmatter is mutated as a parsed YAML document rather than
54
+ * rebuilt from an object, so comments, key order and quoting style survive on
55
+ * every field the edit did not name. The body is a string this only ever
56
+ * appends to or replaces one known section of.
57
+ */
58
+ export function applyEdit(contents, edit, defaults) {
59
+ const split = splitFrontmatter(contents);
60
+ const document = existingDocument(split.hasFence ? split.raw : undefined) ?? schemaFor(defaults);
61
+ for (const [field, value] of fieldsOf(edit)) {
62
+ if (value === null)
63
+ document.delete(field);
64
+ else
65
+ document.set(field, value);
66
+ }
67
+ if (edit.blockedBy !== undefined)
68
+ setEdges(document, edit.blockedBy);
69
+ const frontmatter = render(document);
70
+ // Only the outer edges are trimmed, and only newlines at the end — trailing
71
+ // spaces on the last line of a comment are part of what the author wrote.
72
+ let body = split.body.replace(/^\s+/, '').replace(/\n+$/, '');
73
+ body = withTicks(body, edit.tick);
74
+ // The answer goes in before the comment log so the log stays at the bottom,
75
+ // which is where the skills append and where a reader looks for it.
76
+ body = withAnswer(body, edit.answer);
77
+ body = withComment(body, edit.comment);
78
+ return `---\n${frontmatter}\n---\n\n${body}\n`;
79
+ }
80
+ /**
81
+ * The Edge list, replaced whole rather than merged — a caller changing Edges
82
+ * knows which ones it wants, and a merge would leave no way to remove one.
83
+ *
84
+ * The existing collection style survives: a Ticket written `blocked_by: [T3]`
85
+ * must not come back as a block list just because its Edges changed. That is the
86
+ * same rule as the rest of ADR 0003, applied to the one field being named.
87
+ */
88
+ function setEdges(document, blockedBy) {
89
+ const existing = document.get('blocked_by', true);
90
+ const flow = isSeq(existing) ? existing.flow === true : true;
91
+ document.set('blocked_by', document.createNode([...blockedBy], { flow }));
92
+ }
93
+ /** Where a `##` section ends: the next `##` heading, or the end of the body. */
94
+ function endOfSection(body, from) {
95
+ const after = body.slice(from);
96
+ const next = NEXT_SECTION.exec(after);
97
+ return next === null ? body.length : from + next.index;
98
+ }
99
+ /**
100
+ * Tick acceptance criteria by their text. Only the checkbox marker changes —
101
+ * the line keeps its own indentation and wording, and every other line of the
102
+ * body is untouched, so progress is visible without rewriting the body around
103
+ * it. Wrapped criteria match both as `get_tickets` returns them and re-joined
104
+ * onto one line; comparison collapses whitespace so neither form needs special
105
+ * casing.
106
+ */
107
+ function withTicks(body, tick) {
108
+ if (tick === undefined || tick.length === 0)
109
+ return body;
110
+ const lines = body.split('\n');
111
+ const criteria = findCriteria(lines);
112
+ // Resolve every reference before changing anything, so an ambiguous or
113
+ // unmatched one leaves the file exactly as it was.
114
+ const targets = tick.map(asked => ({ asked, line: resolveCriterion(asked, criteria) }));
115
+ const ticked = [...lines];
116
+ for (const { line } of targets) {
117
+ const box = CHECKBOX.exec(ticked[line] ?? '');
118
+ if (box !== null)
119
+ ticked[line] = `${box[1]}x${box[3]}${box[4]}`;
120
+ }
121
+ return ticked.join('\n');
122
+ }
123
+ const CHECKBOX = /^(\s*[-*]\s*\[)( |x|X)(\]\s*)(.*)$/;
124
+ /** A fenced code block, where a `- [ ]` is illustration rather than a criterion. */
125
+ const FENCE_LINE = /^\s*(?:```|~~~)/;
126
+ /**
127
+ * Collapse runs of whitespace (including newlines and continuation indent) so
128
+ * a wrapped criterion matches both the `get_tickets` form and a single-line
129
+ * rejoin of the same words.
130
+ */
131
+ function collapseCriterionText(text) {
132
+ return text.replace(/\s+/g, ' ').trim().toLowerCase();
133
+ }
134
+ /**
135
+ * A continuation of the criterion above: indented prose that is not itself a
136
+ * checkbox, heading, or fence. Blank lines end the wrap — house style never
137
+ * leaves a hole inside a criterion.
138
+ */
139
+ function isCriterionContinuation(line) {
140
+ if (line.trim() === '')
141
+ return false;
142
+ if (CHECKBOX.test(line))
143
+ return false;
144
+ if (FENCE_LINE.test(line))
145
+ return false;
146
+ if (/^#{1,6}\s/.test(line))
147
+ return false;
148
+ return /^\s+\S/.test(line);
149
+ }
150
+ /** Checkbox lines outside any code fence — the ones that are really criteria. */
151
+ function findCriteria(lines) {
152
+ const criteria = [];
153
+ let fenced = false;
154
+ for (let index = 0; index < lines.length; index += 1) {
155
+ const line = lines[index] ?? '';
156
+ if (FENCE_LINE.test(line)) {
157
+ fenced = !fenced;
158
+ continue;
159
+ }
160
+ if (fenced)
161
+ continue;
162
+ const box = CHECKBOX.exec(line);
163
+ if (box === null)
164
+ continue;
165
+ const checkboxLine = index;
166
+ const parts = [box[4] ?? ''];
167
+ while (index + 1 < lines.length && isCriterionContinuation(lines[index + 1] ?? '')) {
168
+ index += 1;
169
+ parts.push(lines[index] ?? '');
170
+ }
171
+ criteria.push({ line: checkboxLine, text: collapseCriterionText(parts.join('\n')) });
172
+ }
173
+ return criteria;
174
+ }
175
+ /**
176
+ * Exact text wins. Otherwise a substring is accepted only when it names exactly
177
+ * one criterion — a reference that fits several is a question, not an
178
+ * instruction, and ticking all of them would be answering it on the caller's
179
+ * behalf.
180
+ */
181
+ function resolveCriterion(asked, criteria) {
182
+ const target = collapseCriterionText(asked);
183
+ const exact = criteria.filter(criterion => criterion.text === target);
184
+ if (exact.length === 1)
185
+ return exact[0]?.line ?? -1;
186
+ if (exact.length > 1)
187
+ throw new Error(`"${asked}" matches ${String(exact.length)} criteria.`);
188
+ const partial = criteria.filter(criterion => criterion.text.includes(target));
189
+ if (partial.length === 1)
190
+ return partial[0]?.line ?? -1;
191
+ if (partial.length > 1) {
192
+ throw new Error(`"${asked}" matches ${String(partial.length)} criteria — name one exactly: ` +
193
+ partial.map(criterion => `"${criterion.text}"`).join(', '));
194
+ }
195
+ throw new Error(`No acceptance criterion matches "${asked}".`);
196
+ }
197
+ /**
198
+ * Append to the comment log, creating its heading the first time. The comment
199
+ * is stored exactly as written — nothing is prepended, appended, or reformatted
200
+ * around it, including `/triage`'s mandatory disclaimer, which is the skill's
201
+ * own to write and not ours to inject.
202
+ */
203
+ function withComment(body, comment) {
204
+ if (comment === undefined)
205
+ return body;
206
+ const heading = /^##[ \t]+Comments[ \t]*$/m.exec(body);
207
+ if (heading === null)
208
+ return `${body}\n\n${COMMENTS_HEADING}\n\n${comment}`;
209
+ // At the end of the Comments section, not the end of the body — a Ticket
210
+ // whose log is followed by another section would otherwise take the comment
211
+ // into whichever section happens to come last.
212
+ const end = endOfSection(body, heading.index + heading[0].length);
213
+ const before = body.slice(0, end).replace(/\n+$/, '');
214
+ const rest = body.slice(end);
215
+ return rest === '' ? `${before}\n\n${comment}` : `${before}\n\n${comment}\n\n${rest}`;
216
+ }
217
+ /**
218
+ * The existing frontmatter, or undefined when there is none to preserve. A fence
219
+ * whose YAML does not parse cannot be round-tripped — `yaml` refuses to
220
+ * stringify a document carrying errors — so it is rebuilt from the schema
221
+ * instead, which is what "strict on write" means for a file this broken.
222
+ */
223
+ function existingDocument(raw) {
224
+ if (raw === undefined)
225
+ return undefined;
226
+ const parsed = parseDocument(raw);
227
+ return parsed.errors.length > 0 ? undefined : parsed;
228
+ }
229
+ function fieldsOf(edit) {
230
+ const pairs = [];
231
+ if (edit.status !== undefined)
232
+ pairs.push(['status', edit.status]);
233
+ if (edit.triage !== undefined)
234
+ pairs.push(['triage', edit.triage]);
235
+ if (edit.claimedBy !== undefined)
236
+ pairs.push(['claimed_by', edit.claimedBy]);
237
+ if (edit.claimedAt !== undefined)
238
+ pairs.push(['claimed_at', edit.claimedAt]);
239
+ if (edit.answerGist !== undefined)
240
+ pairs.push(['answer_gist', edit.answerGist]);
241
+ if (edit.droppedReason !== undefined)
242
+ pairs.push(['dropped_reason', edit.droppedReason]);
243
+ return pairs;
244
+ }
245
+ /**
246
+ * A Ticket file as first written. Everything is a field this driver is creating,
247
+ * so the whole block is in schema order — the one case where that is allowed,
248
+ * since there is no author's ordering to preserve.
249
+ *
250
+ * The body is the caller's prose, stored verbatim. A draft with none produces a
251
+ * Ticket with none rather than a heading nobody asked for.
252
+ */
253
+ export function serializeNew(fields, body) {
254
+ const frontmatter = render(schemaFor(fields));
255
+ const prose = body?.trim() ?? '';
256
+ return prose === '' ? `---\n${frontmatter}\n---\n` : `---\n${frontmatter}\n---\n\n${prose}\n`;
257
+ }
258
+ /**
259
+ * `lineWidth: 0` stops long values being re-wrapped, and without
260
+ * `flowCollectionPadding: false` an untouched `[a, b]` comes back `[ a, b ]` —
261
+ * both would be reformatting a field the edit never named.
262
+ */
263
+ function render(document) {
264
+ return document.toString({ lineWidth: 0, flowCollectionPadding: false }).trimEnd();
265
+ }
266
+ /**
267
+ * A Legacy file has no frontmatter, so a write is where it gets one — the
268
+ * "strict on write" half of the contract: the tracker converts itself as it is
269
+ * worked rather than in one risky pass. Only a created block gets to be in the
270
+ * schema's field order.
271
+ */
272
+ function schemaFor(defaults) {
273
+ const document = new Document({});
274
+ const values = {
275
+ ...(defaults.id === undefined ? {} : { id: defaults.id }),
276
+ title: defaults.title,
277
+ kind: defaults.kind,
278
+ ...(defaults.type === undefined ? {} : { type: defaults.type }),
279
+ status: defaults.status,
280
+ ...(defaults.triage === undefined ? {} : { triage: defaults.triage }),
281
+ // Flow, because that is how every Ticket in these repos is written by hand
282
+ // and a normalized file should not stand out from the ones beside it.
283
+ blocked_by: document.createNode([...defaults.blockedBy], { flow: true }),
284
+ };
285
+ for (const field of FIELD_ORDER) {
286
+ const value = values[field];
287
+ if (value !== undefined)
288
+ document.set(field, value);
289
+ }
290
+ return document;
291
+ }
292
+ /**
293
+ * The answer replaces the Answer section and nothing else. Slicing to the end of
294
+ * the body would take the comment log with it — a Ticket body is opaque apart
295
+ * from the three places the schema says are edited.
296
+ */
297
+ function withAnswer(body, answer) {
298
+ if (answer === undefined)
299
+ return body;
300
+ const section = `${ANSWER_HEADING}\n\n${answer.trim()}`;
301
+ const start = findAnswerHeading(body);
302
+ if (start === -1) {
303
+ // No Answer section yet. It goes above the comment log if there is one, so
304
+ // the log stays at the bottom where it is appended and read.
305
+ const comments = /^##[ \t]+Comments[ \t]*$/m.exec(body);
306
+ if (comments === null)
307
+ return `${body}\n\n${section}`;
308
+ const head = body.slice(0, comments.index).replace(/\n+$/, '');
309
+ return `${head}\n\n${section}\n\n${body.slice(comments.index)}`;
310
+ }
311
+ const end = endOfSection(body, start + ANSWER_HEADING.length);
312
+ const head = body.slice(0, start).replace(/\n+$/, '');
313
+ const rest = body.slice(end).replace(/^\n+/, '');
314
+ return rest === '' ? `${head}\n\n${section}` : `${head}\n\n${section}\n\n${rest}`;
315
+ }
316
+ /** `## Answer` at the start of a line, and not the start of `## Answerable`. */
317
+ function findAnswerHeading(body) {
318
+ const match = /^##[ \t]+Answer[ \t]*$/m.exec(body);
319
+ return match?.index ?? -1;
320
+ }
321
+ //# sourceMappingURL=serialize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serialize.js","sourceRoot":"","sources":["../../../src/storage/markdown/serialize.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { Ticket } from '../../domain.ts';
2
+ export interface TicketFile {
3
+ readonly filename: string;
4
+ readonly contents: string;
5
+ }
6
+ /**
7
+ * Parse one Ticket file. Schema-conformant files read from their frontmatter;
8
+ * everything else falls through to the Legacy parser and is flagged, because a
9
+ * server that is useless until its repo is migrated never gets installed.
10
+ */
11
+ export declare function parseTicket(effort: string, file: TicketFile, fallbackOrder: number, revision: string): Ticket;
12
+ //# sourceMappingURL=ticket.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ticket.d.ts","sourceRoot":"","sources":["../../../src/storage/markdown/ticket.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgB,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAQ5D,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,UAAU,EAChB,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,GACf,MAAM,CA6BR"}