felenova-backlog 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 (147) hide show
  1. package/README.md +58 -0
  2. package/bin/backlog.js +2 -0
  3. package/dist/_core/criteria.d.ts +13 -0
  4. package/dist/_core/criteria.js +44 -0
  5. package/dist/_core/criteria.js.map +1 -0
  6. package/dist/_core/errors.d.ts +53 -0
  7. package/dist/_core/errors.js +91 -0
  8. package/dist/_core/errors.js.map +1 -0
  9. package/dist/_core/index.d.ts +24 -0
  10. package/dist/_core/index.js +27 -0
  11. package/dist/_core/index.js.map +1 -0
  12. package/dist/_core/loop.d.ts +73 -0
  13. package/dist/_core/loop.js +46 -0
  14. package/dist/_core/loop.js.map +1 -0
  15. package/dist/_core/markdown/items.d.ts +35 -0
  16. package/dist/_core/markdown/items.js +256 -0
  17. package/dist/_core/markdown/items.js.map +1 -0
  18. package/dist/_core/markdown/matrix.d.ts +20 -0
  19. package/dist/_core/markdown/matrix.js +147 -0
  20. package/dist/_core/markdown/matrix.js.map +1 -0
  21. package/dist/_core/markdown/track.d.ts +71 -0
  22. package/dist/_core/markdown/track.js +79 -0
  23. package/dist/_core/markdown/track.js.map +1 -0
  24. package/dist/_core/matrix-status.d.ts +9 -0
  25. package/dist/_core/matrix-status.js +31 -0
  26. package/dist/_core/matrix-status.js.map +1 -0
  27. package/dist/_core/matrix.d.ts +46 -0
  28. package/dist/_core/matrix.js +0 -0
  29. package/dist/_core/matrix.js.map +1 -0
  30. package/dist/_core/permissions.d.ts +14 -0
  31. package/dist/_core/permissions.js +87 -0
  32. package/dist/_core/permissions.js.map +1 -0
  33. package/dist/_core/promote.d.ts +20 -0
  34. package/dist/_core/promote.js +47 -0
  35. package/dist/_core/promote.js.map +1 -0
  36. package/dist/_core/ready.d.ts +8 -0
  37. package/dist/_core/ready.js +22 -0
  38. package/dist/_core/ready.js.map +1 -0
  39. package/dist/_core/review.d.ts +49 -0
  40. package/dist/_core/review.js +137 -0
  41. package/dist/_core/review.js.map +1 -0
  42. package/dist/_core/schema.d.ts +636 -0
  43. package/dist/_core/schema.js +304 -0
  44. package/dist/_core/schema.js.map +1 -0
  45. package/dist/_core/ship.d.ts +23 -0
  46. package/dist/_core/ship.js +31 -0
  47. package/dist/_core/ship.js.map +1 -0
  48. package/dist/_core/spec-diff.d.ts +19 -0
  49. package/dist/_core/spec-diff.js +25 -0
  50. package/dist/_core/spec-diff.js.map +1 -0
  51. package/dist/_core/status.d.ts +7 -0
  52. package/dist/_core/status.js +25 -0
  53. package/dist/_core/status.js.map +1 -0
  54. package/dist/_core/util/deep-equal.d.ts +8 -0
  55. package/dist/_core/util/deep-equal.js +31 -0
  56. package/dist/_core/util/deep-equal.js.map +1 -0
  57. package/dist/api.d.ts +46 -0
  58. package/dist/api.js +104 -0
  59. package/dist/api.js.map +1 -0
  60. package/dist/cache.d.ts +53 -0
  61. package/dist/cache.js +114 -0
  62. package/dist/cache.js.map +1 -0
  63. package/dist/commands/common.d.ts +59 -0
  64. package/dist/commands/common.js +94 -0
  65. package/dist/commands/common.js.map +1 -0
  66. package/dist/commands/export.d.ts +20 -0
  67. package/dist/commands/export.js +56 -0
  68. package/dist/commands/export.js.map +1 -0
  69. package/dist/commands/matrix.d.ts +16 -0
  70. package/dist/commands/matrix.js +48 -0
  71. package/dist/commands/matrix.js.map +1 -0
  72. package/dist/commands/next.d.ts +14 -0
  73. package/dist/commands/next.js +30 -0
  74. package/dist/commands/next.js.map +1 -0
  75. package/dist/commands/promote.d.ts +15 -0
  76. package/dist/commands/promote.js +172 -0
  77. package/dist/commands/promote.js.map +1 -0
  78. package/dist/commands/report.d.ts +18 -0
  79. package/dist/commands/report.js +45 -0
  80. package/dist/commands/report.js.map +1 -0
  81. package/dist/commands/ship.d.ts +15 -0
  82. package/dist/commands/ship.js +231 -0
  83. package/dist/commands/ship.js.map +1 -0
  84. package/dist/commands/status.d.ts +13 -0
  85. package/dist/commands/status.js +141 -0
  86. package/dist/commands/status.js.map +1 -0
  87. package/dist/commands/sync.d.ts +17 -0
  88. package/dist/commands/sync.js +90 -0
  89. package/dist/commands/sync.js.map +1 -0
  90. package/dist/conductor/registry.d.ts +45 -0
  91. package/dist/conductor/registry.js +164 -0
  92. package/dist/conductor/registry.js.map +1 -0
  93. package/dist/config.d.ts +47 -0
  94. package/dist/config.js +137 -0
  95. package/dist/config.js.map +1 -0
  96. package/dist/doctor.d.ts +28 -0
  97. package/dist/doctor.js +377 -0
  98. package/dist/doctor.js.map +1 -0
  99. package/dist/errors.d.ts +23 -0
  100. package/dist/errors.js +114 -0
  101. package/dist/errors.js.map +1 -0
  102. package/dist/export.d.ts +1 -0
  103. package/dist/export.js +9 -0
  104. package/dist/export.js.map +1 -0
  105. package/dist/import.d.ts +3 -0
  106. package/dist/import.js +89 -0
  107. package/dist/import.js.map +1 -0
  108. package/dist/index.d.ts +1 -0
  109. package/dist/index.js +183 -0
  110. package/dist/index.js.map +1 -0
  111. package/dist/init/auth.d.ts +85 -0
  112. package/dist/init/auth.js +194 -0
  113. package/dist/init/auth.js.map +1 -0
  114. package/dist/init/claudemd.d.ts +16 -0
  115. package/dist/init/claudemd.js +62 -0
  116. package/dist/init/claudemd.js.map +1 -0
  117. package/dist/init/index.d.ts +47 -0
  118. package/dist/init/index.js +159 -0
  119. package/dist/init/index.js.map +1 -0
  120. package/dist/init/mcp.d.ts +18 -0
  121. package/dist/init/mcp.js +97 -0
  122. package/dist/init/mcp.js.map +1 -0
  123. package/dist/init/plugin.d.ts +60 -0
  124. package/dist/init/plugin.js +120 -0
  125. package/dist/init/plugin.js.map +1 -0
  126. package/dist/init/scaffold.d.ts +19 -0
  127. package/dist/init/scaffold.js +46 -0
  128. package/dist/init/scaffold.js.map +1 -0
  129. package/dist/init/templates.d.ts +48 -0
  130. package/dist/init/templates.js +147 -0
  131. package/dist/init/templates.js.map +1 -0
  132. package/dist/plugin/.claude-plugin/plugin.json +11 -0
  133. package/dist/plugin/README.md +117 -0
  134. package/dist/plugin/commands/next.md +28 -0
  135. package/dist/plugin/commands/promote.md +30 -0
  136. package/dist/plugin/commands/report.md +26 -0
  137. package/dist/plugin/commands/ship.md +30 -0
  138. package/dist/plugin/skills/backlog-management/SKILL.md +63 -0
  139. package/dist/plugin/skills/backlog-query/SKILL.md +57 -0
  140. package/dist/plugin/skills/feature-kickoff/SKILL.md +112 -0
  141. package/dist/snapshot.d.ts +24 -0
  142. package/dist/snapshot.js +2 -0
  143. package/dist/snapshot.js.map +1 -0
  144. package/dist/token.d.ts +29 -0
  145. package/dist/token.js +99 -0
  146. package/dist/token.js.map +1 -0
  147. package/package.json +52 -0
@@ -0,0 +1,256 @@
1
+ import YAML from 'yaml';
2
+ import { Item } from '../schema.js';
3
+ import { deepEqual } from '../util/deep-equal.js';
4
+ export class ItemParseError extends Error {
5
+ path;
6
+ problems;
7
+ constructor(path, problems) {
8
+ super(`${path}: ${problems.length} problem(s)\n - ${problems.join('\n - ')}`);
9
+ this.path = path;
10
+ this.problems = problems;
11
+ this.name = 'ItemParseError';
12
+ }
13
+ }
14
+ /** §4.1 field order — also the canonical output order. */
15
+ export const FRONTMATTER_FIELDS = [
16
+ 'id',
17
+ 'title',
18
+ 'status',
19
+ 'domain',
20
+ 'priority',
21
+ 'origin',
22
+ 'created',
23
+ 'updated',
24
+ 'prerequisites',
25
+ 'blocks',
26
+ 'criteria_status',
27
+ 'revisit_when',
28
+ 'track',
29
+ 'shipped_commit',
30
+ 'production_date',
31
+ ];
32
+ export const REQUIRED_SECTIONS = [
33
+ 'What',
34
+ 'Why',
35
+ 'Acceptance criteria',
36
+ 'Critique / risks',
37
+ 'Out of scope',
38
+ 'Dependencies & sequencing',
39
+ ];
40
+ /**
41
+ * Narrow alias table for headings real liblib-meet items use for a required section.
42
+ * Parsing only — canonical export always writes the canonical heading, and the verbatim
43
+ * path re-emits the raw body, so an aliased file stays byte-exact until it is edited.
44
+ */
45
+ const SECTION_ALIASES = [
46
+ ['Critique / shape', 'Critique / risks'],
47
+ ];
48
+ function splitFrontmatter(text, path) {
49
+ if (!text.startsWith('---\n'))
50
+ throw new ItemParseError(path, ['no frontmatter (file must start with ---)']);
51
+ const end = text.indexOf('\n---\n', 4);
52
+ if (end < 0)
53
+ throw new ItemParseError(path, ['unterminated frontmatter']);
54
+ return { frontmatterRaw: text.slice(4, end), bodyRaw: text.slice(end + 5) };
55
+ }
56
+ function finishSection(c) {
57
+ const matches = (name) => c.heading === name || c.heading.startsWith(`${name} (`);
58
+ const canonical = REQUIRED_SECTIONS.find(matches) ??
59
+ SECTION_ALIASES.find(([alias]) => matches(alias))?.[1] ??
60
+ null;
61
+ return { heading: c.heading, canonical, text: c.lines.join('\n').trim() };
62
+ }
63
+ /** Split a body on `## ` headings. A required heading matches `## Name` or `## Name (…`. */
64
+ function splitSections(body) {
65
+ const lines = body.split('\n');
66
+ const sections = [];
67
+ const preamble = [];
68
+ let current = null;
69
+ for (const line of lines) {
70
+ const m = /^## (.+?)\s*$/.exec(line);
71
+ if (m?.[1] !== undefined) {
72
+ if (current)
73
+ sections.push(finishSection(current));
74
+ current = { heading: m[1], lines: [] };
75
+ }
76
+ else if (current)
77
+ current.lines.push(line);
78
+ else
79
+ preamble.push(line);
80
+ }
81
+ if (current)
82
+ sections.push(finishSection(current));
83
+ return { sections, preamble: preamble.join('\n') };
84
+ }
85
+ const CHECKBOX = /^\s*-\s*\[([ xX])\]\s*(.+?)\s*$/;
86
+ /**
87
+ * Parse an `## Acceptance criteria` section into its checkboxes and the prose around them.
88
+ *
89
+ * Real items wrap a criterion over indented continuation lines and hang sub-bullets or a
90
+ * numbered precedence list under it (liblib-meet RP-004, RP-032): an indented line that is not
91
+ * itself a checkbox continues the criterion above it and is folded in with a single space.
92
+ * Every other non-checkbox line is section prose (liblib-meet AI-001's `*Pending — …*` note,
93
+ * RP-032's `### ` group headings) and is kept in `notes` so canonical export loses nothing.
94
+ */
95
+ export function parseCriteriaSection(sectionText) {
96
+ const criteria = [];
97
+ const notes = [];
98
+ let open = false; // the previous line was a criterion or one of its continuation lines
99
+ for (const line of sectionText.split('\n')) {
100
+ const m = CHECKBOX.exec(line);
101
+ if (m?.[2] !== undefined) {
102
+ criteria.push({ index: criteria.length + 1, text: m[2], done: m[1] !== ' ' });
103
+ open = true;
104
+ continue;
105
+ }
106
+ const last = criteria[criteria.length - 1];
107
+ if (open && last && /^\s+\S/.test(line)) {
108
+ last.text = `${last.text} ${line.trim()}`;
109
+ continue;
110
+ }
111
+ open = false;
112
+ notes.push(line);
113
+ }
114
+ return { criteria, notes: notes.join('\n').trim() };
115
+ }
116
+ export function parseCriteria(sectionText) {
117
+ return parseCriteriaSection(sectionText).criteria;
118
+ }
119
+ export function parseItem(text, path) {
120
+ const { frontmatterRaw, bodyRaw } = splitFrontmatter(text, path);
121
+ const problems = [];
122
+ let fm = {};
123
+ try {
124
+ const parsed = YAML.parse(frontmatterRaw, { schema: 'core' });
125
+ if (parsed && typeof parsed === 'object')
126
+ fm = parsed;
127
+ else
128
+ problems.push('frontmatter is not a mapping');
129
+ }
130
+ catch (e) {
131
+ problems.push(`frontmatter YAML: ${e.message}`);
132
+ }
133
+ for (const f of FRONTMATTER_FIELDS)
134
+ if (!(f in fm))
135
+ problems.push(`missing frontmatter field: ${f}`);
136
+ const filenameId = path.replace(/^.*\//, '').replace(/\.md$/, '');
137
+ if (fm.id !== undefined && String(fm.id) !== filenameId)
138
+ problems.push(`id ${String(fm.id)} does not match filename ${filenameId}`);
139
+ const { sections, preamble } = splitSections(bodyRaw);
140
+ const found = {};
141
+ const extra = [];
142
+ for (const s of sections) {
143
+ if (s.canonical && !found[s.canonical])
144
+ found[s.canonical] = s;
145
+ else
146
+ extra.push({ heading: s.heading, text: s.text });
147
+ }
148
+ for (const name of REQUIRED_SECTIONS)
149
+ if (!found[name])
150
+ problems.push(`missing section: ## ${name}`);
151
+ const order = REQUIRED_SECTIONS.map((n) => sections.findIndex((s) => s === found[n])).filter((i) => i >= 0);
152
+ if (order.some((v, i) => i > 0 && v < (order[i - 1] ?? -1)))
153
+ problems.push('required sections are out of order');
154
+ if (problems.length)
155
+ throw new ItemParseError(path, problems);
156
+ const str = (v) => (v == null ? '' : String(v));
157
+ const nul = (v) => (v == null ? null : String(v));
158
+ const section = (name) => found[name]?.text ?? '';
159
+ const ac = parseCriteriaSection(section('Acceptance criteria'));
160
+ const candidate = {
161
+ id: str(fm.id),
162
+ title: str(fm.title),
163
+ status: fm.status,
164
+ domain: str(fm.domain),
165
+ priority: fm.priority,
166
+ origin: str(fm.origin),
167
+ created: str(fm.created),
168
+ updated: str(fm.updated),
169
+ prerequisites: Array.isArray(fm.prerequisites) ? fm.prerequisites.map(String) : [],
170
+ blocks: Array.isArray(fm.blocks) ? fm.blocks.map(String) : [],
171
+ criteria_status: fm.criteria_status,
172
+ criteria_version: typeof fm.criteria_version === 'number' ? fm.criteria_version : 1,
173
+ revisit_when: str(fm.revisit_when),
174
+ track: nul(fm.track),
175
+ shipped_commit: nul(fm.shipped_commit),
176
+ production_date: nul(fm.production_date),
177
+ // liblib-meet predates the field: a rejected item keeps its rationale in the body (RP-003).
178
+ rejected_reason: nul(fm.rejected_reason) ?? (fm.status === 'rejected' ? 'see item body' : null),
179
+ what: section('What'),
180
+ why: section('Why'),
181
+ criteria: ac.criteria,
182
+ criteria_notes: ac.notes,
183
+ critique: section('Critique / risks'),
184
+ out_of_scope: section('Out of scope'),
185
+ dependencies_note: section('Dependencies & sequencing'),
186
+ extra_sections: extra,
187
+ preamble: preamble.trim(),
188
+ };
189
+ const result = Item.safeParse(candidate);
190
+ if (!result.success) {
191
+ throw new ItemParseError(path, result.error.issues.map((i) => `${i.path.join('.') || '(item)'}: ${i.message}`));
192
+ }
193
+ return { item: result.data, source: { path, frontmatterRaw, bodyRaw } };
194
+ }
195
+ // ---- serialisation -------------------------------------------------------------
196
+ const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
197
+ const RESERVED_WORDS = new Set(['null', 'true', 'false', '~', 'yes', 'no', 'on', 'off']);
198
+ /** Needs double quotes: empty, YAML-reserved words, a `: ` or newline inside, or an indicator/digit first char. */
199
+ const NEEDS_QUOTES = /^["'#\-[\]{}&*!|>%@`?,+.]|^\d|: | #|^\s|\s$|\n/;
200
+ function yamlScalar(v) {
201
+ if (v === null)
202
+ return 'null';
203
+ if (v === '')
204
+ return '""';
205
+ if (ISO_DATE.test(v))
206
+ return v;
207
+ // YAML 1.1 reserved words are case-insensitive: `True`, `NULL` and `Off` are booleans/null too.
208
+ if (RESERVED_WORDS.has(v.toLowerCase()))
209
+ return JSON.stringify(v);
210
+ return NEEDS_QUOTES.test(v) ? JSON.stringify(v) : v;
211
+ }
212
+ function yamlList(xs) {
213
+ return `[${xs.join(', ')}]`;
214
+ }
215
+ export function serializeItemCanonical(item) {
216
+ const fm = [];
217
+ for (const f of FRONTMATTER_FIELDS) {
218
+ const v = item[f];
219
+ fm.push(`${f}: ${Array.isArray(v) ? yamlList(v) : yamlScalar(v)}`);
220
+ }
221
+ if (item.criteria_version > 1)
222
+ fm.push(`criteria_version: ${item.criteria_version}`);
223
+ if (item.rejected_reason !== null)
224
+ fm.push(`rejected_reason: ${yamlScalar(item.rejected_reason)}`);
225
+ const boxes = item.criteria.map((c) => `- [${c.done ? 'x' : ' '}] ${c.text}`).join('\n');
226
+ const crit = item.criteria_notes ? `${item.criteria_notes}\n\n${boxes}`.trimEnd() : boxes;
227
+ const sections = [
228
+ ['What', item.what],
229
+ ['Why', item.why],
230
+ ['Acceptance criteria', crit],
231
+ ['Critique / risks', item.critique],
232
+ ['Out of scope', item.out_of_scope],
233
+ ['Dependencies & sequencing', item.dependencies_note],
234
+ ...item.extra_sections.map((s) => [s.heading, s.text]),
235
+ ];
236
+ const body = sections.map(([h, t]) => `## ${h}\n\n${t}`.trimEnd()).join('\n\n');
237
+ const head = item.preamble ? `${item.preamble}\n\n` : '';
238
+ return `---\n${fm.join('\n')}\n---\n\n${head}${body}\n`;
239
+ }
240
+ export function itemMatchesSource(item, source) {
241
+ try {
242
+ const again = parseItem(`---\n${source.frontmatterRaw}\n---\n${source.bodyRaw}`, source.path).item;
243
+ // Structural, not JSON-text: an item that has been through `jsonb` comes back with
244
+ // Postgres's key order, and a byte-identical item must still count as pristine.
245
+ return deepEqual(again, item);
246
+ }
247
+ catch {
248
+ return false;
249
+ }
250
+ }
251
+ export function serializeItem(item, source) {
252
+ if (source && itemMatchesSource(item, source))
253
+ return `---\n${source.frontmatterRaw}\n---\n${source.bodyRaw}`;
254
+ return serializeItemCanonical(item);
255
+ }
256
+ //# sourceMappingURL=items.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"items.js","sourceRoot":"","sources":["../../src/markdown/items.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import { type MatrixRow } from '../schema.js';
2
+ export interface MatrixDoc {
3
+ path: string;
4
+ slug: string | null;
5
+ rows: MatrixRow[];
6
+ warnings: string[];
7
+ raw: string;
8
+ }
9
+ export declare function parseMatrixRowId(id: string): {
10
+ item: string;
11
+ ac: number;
12
+ m: number;
13
+ } | null;
14
+ export declare function parseMatrix(text: string, path: string): MatrixDoc;
15
+ /** Ported from liblib-meet scripts/backlog/matrix.ts buildMatrixMd, grouped by item id in first-seen order. */
16
+ export declare function buildMatrixMarkdown(slug: string, type: string, rows: MatrixRow[], generatedOn: string): string;
17
+ export declare function serializeMatrix(doc: MatrixDoc, rows?: MatrixRow[], opts?: {
18
+ type?: string;
19
+ generatedOn?: string;
20
+ }): string;
@@ -0,0 +1,147 @@
1
+ import { MATRIX_STATUSES } from '../schema.js';
2
+ import { deepEqual } from '../util/deep-equal.js';
3
+ const ROW_ID = /^([A-Z][A-Z0-9]*-\d{3,})-AC(\d+)\.M(\d+)$/;
4
+ const LEGACY_ID = /^[A-Z]\d+$/;
5
+ /** `A1`/`A10` belong to section `A`, `B1` to section `B` — the checklist's own grouping. */
6
+ const LEGACY_LETTER = /^([A-Z])\d+$/;
7
+ const STATUS_SET = new Set(MATRIX_STATUSES);
8
+ export function parseMatrixRowId(id) {
9
+ const m = ROW_ID.exec(id);
10
+ return m ? { item: m[1], ac: Number(m[2]), m: Number(m[3]) } : null;
11
+ }
12
+ function splitCells(line) {
13
+ // Strip exactly ONE leading pipe (plus one optional space) and exactly ONE trailing pipe.
14
+ // A greedy trailing strip would eat the delimiter of an empty last cell, so a row ending
15
+ // `… | ☐ | |` would lose its Notes cell and shift the status scan.
16
+ // Unescape "\|" produced by the canonical writer.
17
+ const trimmed = line.replace(/\s+$/, '');
18
+ const body = trimmed.endsWith('|') ? trimmed.slice(0, -1) : trimmed;
19
+ const inner = body.replace(/^\| ?/, '');
20
+ return inner.split(/ \| /).map((c) => c.replace(/\\\|/g, '|').trim());
21
+ }
22
+ export function parseMatrix(text, path) {
23
+ const slugMatch = /^# QA Matrix — `([^`]+)`/m.exec(text);
24
+ const rows = [];
25
+ const warnings = [];
26
+ for (const line of text.split('\n')) {
27
+ if (!line.startsWith('| '))
28
+ continue;
29
+ const cells = splitCells(line);
30
+ const id = cells[0] ?? '';
31
+ const parsedId = parseMatrixRowId(id);
32
+ const isLegacy = LEGACY_ID.test(id);
33
+ if (!parsedId && !isLegacy)
34
+ continue; // header, separator, summary tables
35
+ if (cells.length < 6) {
36
+ warnings.push(`${path}: row ${id}: only ${cells.length} cells — skipped`);
37
+ continue;
38
+ }
39
+ let statusIdx = -1;
40
+ for (let i = cells.length - 1; i >= 4; i--) {
41
+ if (STATUS_SET.has(cells[i]) || cells[i] === 'N/A') {
42
+ statusIdx = i;
43
+ break;
44
+ }
45
+ }
46
+ // A generated row has exactly 7 columns. More cells means an unescaped pipe inside one of
47
+ // them; parsing still works by position (status found by symbol), but say so out loud.
48
+ if (parsedId && cells.length > 7 && statusIdx !== 5)
49
+ warnings.push(`${path}: row ${id}: ${cells.length} cells (expected 7) — pipe inside a cell? parsed by position`);
50
+ const link = (linkedText) => parsedId && !/^§/.test(linkedText)
51
+ ? { item: parsedId.item, ac: parsedId.ac }
52
+ : { legacy: linkedText || id };
53
+ if (statusIdx < 0) {
54
+ // Nothing in the row looks like a status. Say exactly that and guess no column.
55
+ warnings.push(`${path}: row ${id}: no status symbol in any cell; treated as unparsed (status null)`);
56
+ const linkedText = cells.slice(4, cells.length - 1).join(' | ');
57
+ rows.push({
58
+ id,
59
+ what_to_verify: cells[1] ?? '',
60
+ how_to_verify: cells[2] ?? '',
61
+ test_type: cells[3] ?? '',
62
+ linked_criterion: link(linkedText),
63
+ linked_text: linkedText,
64
+ environment: null,
65
+ status: null,
66
+ status_raw: '',
67
+ notes: cells[cells.length - 1] ?? '',
68
+ evidence: [],
69
+ });
70
+ continue;
71
+ }
72
+ const statusRaw = cells[statusIdx];
73
+ const status = STATUS_SET.has(statusRaw) ? statusRaw : null;
74
+ if (status === null)
75
+ warnings.push(`${path}: row ${id}: unrecognised status "${statusRaw}" kept as raw`);
76
+ const linkedText = cells.slice(4, statusIdx).join(' | ');
77
+ // linked_text keeps the cell verbatim; linked_criterion is the structured bridge (§5.6).
78
+ rows.push({
79
+ id,
80
+ what_to_verify: cells[1] ?? '',
81
+ how_to_verify: cells[2] ?? '',
82
+ test_type: cells[3] ?? '',
83
+ linked_criterion: link(linkedText),
84
+ linked_text: linkedText,
85
+ environment: null,
86
+ status,
87
+ status_raw: statusRaw,
88
+ notes: cells.slice(statusIdx + 1).join(' | '),
89
+ evidence: [],
90
+ });
91
+ }
92
+ return { path, slug: slugMatch ? slugMatch[1] : null, rows, warnings, raw: text };
93
+ }
94
+ const esc = (s) => s.replace(/\|/g, '\\|');
95
+ /** Ported from liblib-meet scripts/backlog/matrix.ts buildMatrixMd, grouped by item id in first-seen order. */
96
+ export function buildMatrixMarkdown(slug, type, rows, generatedOn) {
97
+ const byItem = new Map();
98
+ for (const r of rows) {
99
+ const key = 'item' in r.linked_criterion
100
+ ? r.linked_criterion.item
101
+ : (LEGACY_LETTER.exec(r.id)?.[1] ?? 'legacy');
102
+ byItem.set(key, [...(byItem.get(key) ?? []), r]);
103
+ }
104
+ // Legacy sections keep their own letter; item sections take the next free one.
105
+ const taken = new Set([...byItem.keys()].filter((k) => /^[A-Z]$/.test(k)));
106
+ let next = 0;
107
+ const sections = [...byItem.entries()].map(([key, rs]) => {
108
+ const legacySection = /^[A-Z]$/.test(key);
109
+ let letter = key;
110
+ if (!legacySection) {
111
+ do {
112
+ letter = String.fromCharCode(65 + next++);
113
+ } while (taken.has(letter));
114
+ taken.add(letter);
115
+ }
116
+ const header = legacySection ? `## ${letter}. Legacy rows` : `## ${letter}. ${key}`;
117
+ const table = [
118
+ '| ID | What to verify | How to verify | Test type | Linked criterion | Status | Notes |',
119
+ '|---|---|---|---|---|---|---|',
120
+ ...rs.map((r) => {
121
+ const linked = r.linked_text ||
122
+ ('item' in r.linked_criterion
123
+ ? `${r.linked_criterion.item} AC${r.linked_criterion.ac}`
124
+ : r.linked_criterion.legacy);
125
+ return `| ${r.id} | ${esc(r.what_to_verify)} | ${esc(r.how_to_verify)} | ${esc(r.test_type)} | ${esc(linked)} | ${r.status ?? r.status_raw} | ${esc(r.notes)} |`;
126
+ }),
127
+ ].join('\n');
128
+ return `${header}\n\n${table}\n`;
129
+ });
130
+ return `# QA Matrix — \`${slug}\`
131
+
132
+ **Track type:** ${type}
133
+ **Generated:** ${generatedOn} by \`backlog matrix\`
134
+
135
+ **Status legend (per \`docs/backlog/_schema.md\` §10.5):** ☐ not started · 🟡 in progress · ✅ implemented · 🔬 verified in staging · 🚀 verified in production · ⏭️ skipped (reason in Notes).
136
+
137
+ ---
138
+
139
+ ${sections.join('\n---\n\n')}`;
140
+ }
141
+ export function serializeMatrix(doc, rows, opts = {}) {
142
+ // Structural compare: rows round-tripped through `jsonb` keep their values but not key order.
143
+ if (!rows || deepEqual(rows, doc.rows))
144
+ return doc.raw;
145
+ return buildMatrixMarkdown(doc.slug ?? 'matrix', opts.type ?? 'feature', rows, opts.generatedOn ?? '1970-01-01');
146
+ }
147
+ //# sourceMappingURL=matrix.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matrix.js","sourceRoot":"","sources":["../../src/markdown/matrix.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAqC,MAAM,cAAc,CAAC;AAClF,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAUlD,MAAM,MAAM,GAAG,2CAA2C,CAAC;AAC3D,MAAM,SAAS,GAAG,YAAY,CAAC;AAC/B,4FAA4F;AAC5F,MAAM,aAAa,GAAG,cAAc,CAAC;AACrC,MAAM,UAAU,GAAG,IAAI,GAAG,CAAS,eAAe,CAAC,CAAC;AAEpD,MAAM,UAAU,gBAAgB,CAAC,EAAU;IACzC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC1B,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AACtE,CAAC;AAED,SAAS,UAAU,CAAC,IAAY;IAC9B,0FAA0F;IAC1F,yFAAyF;IACzF,mEAAmE;IACnE,kDAAkD;IAClD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACzC,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IACpE,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IACxC,OAAO,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;AACxE,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,IAAY;IACpD,MAAM,SAAS,GAAG,2BAA2B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzD,MAAM,IAAI,GAAgB,EAAE,CAAC;IAC7B,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACpC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,SAAS;QACrC,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;QAC/B,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC1B,MAAM,QAAQ,GAAG,gBAAgB,CAAC,EAAE,CAAC,CAAC;QACtC,MAAM,QAAQ,GAAG,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACpC,IAAI,CAAC,QAAQ,IAAI,CAAC,QAAQ;YAAE,SAAS,CAAC,oCAAoC;QAC1E,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrB,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,SAAS,EAAE,UAAU,KAAK,CAAC,MAAM,kBAAkB,CAAC,CAAC;YAC1E,SAAS;QACX,CAAC;QACD,IAAI,SAAS,GAAG,CAAC,CAAC,CAAC;QACnB,KAAK,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC3C,IAAI,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK,EAAE,CAAC;gBACnD,SAAS,GAAG,CAAC,CAAC;gBACd,MAAM;YACR,CAAC;QACH,CAAC;QACD,0FAA0F;QAC1F,uFAAuF;QACvF,IAAI,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,SAAS,KAAK,CAAC;YACjD,QAAQ,CAAC,IAAI,CACX,GAAG,IAAI,SAAS,EAAE,KAAK,KAAK,CAAC,MAAM,8DAA8D,CAClG,CAAC;QACJ,MAAM,IAAI,GAAG,CAAC,UAAkB,EAAE,EAAE,CAClC,QAAQ,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC;YAChC,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,EAAE,EAAE,QAAQ,CAAC,EAAE,EAAE;YAC1C,CAAC,CAAC,EAAE,MAAM,EAAE,UAAU,IAAI,EAAE,EAAE,CAAC;QACnC,IAAI,SAAS,GAAG,CAAC,EAAE,CAAC;YAClB,gFAAgF;YAChF,QAAQ,CAAC,IAAI,CACX,GAAG,IAAI,SAAS,EAAE,mEAAmE,CACtF,CAAC;YACF,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAChE,IAAI,CAAC,IAAI,CAAC;gBACR,EAAE;gBACF,cAAc,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE;gBAC9B,aAAa,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE;gBAC7B,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE;gBACzB,gBAAgB,EAAE,IAAI,CAAC,UAAU,CAAC;gBAClC,WAAW,EAAE,UAAU;gBACvB,WAAW,EAAE,IAAI;gBACjB,MAAM,EAAE,IAAI;gBACZ,UAAU,EAAE,EAAE;gBACd,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE;gBACpC,QAAQ,EAAE,EAAE;aACb,CAAC,CAAC;YACH,SAAS;QACX,CAAC;QACD,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC;QACnC,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAE,SAA0B,CAAC,CAAC,CAAC,IAAI,CAAC;QAC9E,IAAI,MAAM,KAAK,IAAI;YACjB,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,SAAS,EAAE,0BAA0B,SAAS,eAAe,CAAC,CAAC;QACtF,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACzD,yFAAyF;QACzF,IAAI,CAAC,IAAI,CAAC;YACR,EAAE;YACF,cAAc,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE;YAC9B,aAAa,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE;YAC7B,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE;YACzB,gBAAgB,EAAE,IAAI,CAAC,UAAU,CAAC;YAClC,WAAW,EAAE,UAAU;YACvB,WAAW,EAAE,IAAI;YACjB,MAAM;YACN,UAAU,EAAE,SAAS;YACrB,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC;YAC7C,QAAQ,EAAE,EAAE;SACb,CAAC,CAAC;IACL,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;AACpF,CAAC;AAED,MAAM,GAAG,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;AAEnD,+GAA+G;AAC/G,MAAM,UAAU,mBAAmB,CACjC,IAAY,EACZ,IAAY,EACZ,IAAiB,EACjB,WAAmB;IAEnB,MAAM,MAAM,GAAG,IAAI,GAAG,EAAuB,CAAC;IAC9C,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,MAAM,GAAG,GACP,MAAM,IAAI,CAAC,CAAC,gBAAgB;YAC1B,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,IAAI;YACzB,CAAC,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,CAAC;QAClD,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACnD,CAAC;IACD,+EAA+E;IAC/E,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3E,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,MAAM,QAAQ,GAAG,CAAC,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE;QACvD,MAAM,aAAa,GAAG,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC1C,IAAI,MAAM,GAAG,GAAG,CAAC;QACjB,IAAI,CAAC,aAAa,EAAE,CAAC;YACnB,GAAG,CAAC;gBACF,MAAM,GAAG,MAAM,CAAC,YAAY,CAAC,EAAE,GAAG,IAAI,EAAE,CAAC,CAAC;YAC5C,CAAC,QAAQ,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE;YAC5B,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACpB,CAAC;QACD,MAAM,MAAM,GAAG,aAAa,CAAC,CAAC,CAAC,MAAM,MAAM,eAAe,CAAC,CAAC,CAAC,MAAM,MAAM,KAAK,GAAG,EAAE,CAAC;QACpF,MAAM,KAAK,GAAG;YACZ,yFAAyF;YACzF,+BAA+B;YAC/B,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;gBACd,MAAM,MAAM,GACV,CAAC,CAAC,WAAW;oBACb,CAAC,MAAM,IAAI,CAAC,CAAC,gBAAgB;wBAC3B,CAAC,CAAC,GAAG,CAAC,CAAC,gBAAgB,CAAC,IAAI,MAAM,CAAC,CAAC,gBAAgB,CAAC,EAAE,EAAE;wBACzD,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC;gBACjC,OAAO,KAAK,CAAC,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,UAAU,MAAM,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC;YACnK,CAAC,CAAC;SACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACb,OAAO,GAAG,MAAM,OAAO,KAAK,IAAI,CAAC;IACnC,CAAC,CAAC,CAAC;IACH,OAAO,mBAAmB,IAAI;;kBAEd,IAAI;iBACL,WAAW;;;;;;EAM1B,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,eAAe,CAC7B,GAAc,EACd,IAAkB,EAClB,OAAgD,EAAE;IAElD,8FAA8F;IAC9F,IAAI,CAAC,IAAI,IAAI,SAAS,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,GAAG,CAAC,GAAG,CAAC;IACvD,OAAO,mBAAmB,CACxB,GAAG,CAAC,IAAI,IAAI,QAAQ,EACpB,IAAI,CAAC,IAAI,IAAI,SAAS,EACtB,IAAI,EACJ,IAAI,CAAC,WAAW,IAAI,YAAY,CACjC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,71 @@
1
+ import type { Criterion } from '../schema.js';
2
+ /**
3
+ * The three files a promoted conductor track carries, rendered from the register's own
4
+ * `TrackDetail` payload (DECISION-028). Pure: no `node:*`, no clock — `created` is an
5
+ * argument — so the CLI, the service and the tests all render the same bytes.
6
+ *
7
+ * The shape is `conductor/tracks/verification_20260912/` and nothing else: conductor is not
8
+ * modified by the plugin (§7.3), so the renderer follows the convention rather than proposing
9
+ * one. `metadata.json` is v3 exactly (§7).
10
+ */
11
+ /** What promotion copies into `spec.md` — mirrors `apps/service` `TrackSpecEntry`. */
12
+ export interface TrackSpecEntry {
13
+ id: string;
14
+ title: string;
15
+ criteria: Criterion[];
16
+ /** The item's `## Out of scope` body. Copied **verbatim**; never re-wrapped or re-bulleted. */
17
+ out_of_scope: string;
18
+ criteria_version: number;
19
+ }
20
+ /** The subset of `TrackDetail` the renderer reads. Extra fields are ignored, not rejected. */
21
+ export interface TrackRenderPayload {
22
+ project?: string;
23
+ track: {
24
+ slug: string;
25
+ type: string;
26
+ backlog_ids?: string[];
27
+ qa_enforcement?: string;
28
+ };
29
+ spec: TrackSpecEntry[];
30
+ }
31
+ export interface TrackSpecOptions {
32
+ /** The track's human name. Only the base name reaches the title line. */
33
+ name?: string | undefined;
34
+ }
35
+ export interface TrackPlanOptions {
36
+ created?: string | undefined;
37
+ /** `--plan <path>`; otherwise the conventional `docs/superpowers/plans/<created>-<base>.md`. */
38
+ planPath?: string | undefined;
39
+ }
40
+ export interface TrackMetadataOptions {
41
+ name?: string | undefined;
42
+ /** The highest priority across the promoted items — the CLI resolves it, not the renderer. */
43
+ priority?: string | undefined;
44
+ /** `YYYY-MM-DD`. Required: the renderer never reads a clock. */
45
+ created: string;
46
+ /** Every promoted ref, in argument order. Defaults to the track's own list, then the spec. */
47
+ backlogIds?: string[] | undefined;
48
+ qaEnforcement?: string | undefined;
49
+ }
50
+ export type TrackRenderOptions = TrackSpecOptions & TrackPlanOptions & TrackMetadataOptions;
51
+ export interface TrackFiles {
52
+ 'spec.md': string;
53
+ 'plan.md': string;
54
+ 'metadata.json': string;
55
+ }
56
+ /** The three names, in the order `promote` writes (and prints) them. */
57
+ export declare const TRACK_FILENAMES: readonly ["spec.md", "plan.md", "metadata.json"];
58
+ /** `verification_20260912` → `verification`. The date is the promotion stamp, not the name. */
59
+ export declare function trackBaseName(slug: string): string;
60
+ /**
61
+ * `spec.md`. The criteria are rendered **unticked** whatever the item says: this file is the
62
+ * contract as promoted, and the tick state lives in the register (and in the QA matrix), not
63
+ * in a copy on disk that nothing reconciles.
64
+ */
65
+ export declare function renderTrackSpec(payload: TrackRenderPayload, opts?: TrackSpecOptions): string;
66
+ /** `plan.md` — one line, pointing at the plan. Conductor owns the file; we only seed it. */
67
+ export declare function renderTrackPlan(payload: TrackRenderPayload, opts?: TrackPlanOptions): string;
68
+ /** `metadata.json` — v3 (§7), two-space indent, trailing newline. */
69
+ export declare function renderTrackMetadata(payload: TrackRenderPayload, opts: TrackMetadataOptions): string;
70
+ /** All three, keyed by filename — what `promote` writes into the track directory. */
71
+ export declare function renderTrackFiles(payload: TrackRenderPayload, opts: TrackRenderOptions): TrackFiles;
@@ -0,0 +1,79 @@
1
+ /** The three names, in the order `promote` writes (and prints) them. */
2
+ export const TRACK_FILENAMES = ['spec.md', 'plan.md', 'metadata.json'];
3
+ const EMPTY_SECTION = '_none recorded_';
4
+ /** `verification_20260912` → `verification`. The date is the promotion stamp, not the name. */
5
+ export function trackBaseName(slug) {
6
+ return slug.replace(/_\d{8}$/, '');
7
+ }
8
+ /** The name as the title line wants it: the track's own word, not the full metadata sentence. */
9
+ function titleName(payload, opts) {
10
+ const base = trackBaseName(payload.track.slug);
11
+ if (base !== '')
12
+ return base;
13
+ return (opts.name ?? payload.track.slug).trim();
14
+ }
15
+ function backlogIds(payload, opts) {
16
+ const given = opts.backlogIds ?? payload.track.backlog_ids;
17
+ if (given && given.length > 0)
18
+ return [...given];
19
+ return payload.spec.map((entry) => entry.id);
20
+ }
21
+ /**
22
+ * `spec.md`. The criteria are rendered **unticked** whatever the item says: this file is the
23
+ * contract as promoted, and the tick state lives in the register (and in the QA matrix), not
24
+ * in a copy on disk that nothing reconciles.
25
+ */
26
+ export function renderTrackSpec(payload, opts = {}) {
27
+ const blocks = [`# ${titleName(payload, opts)} — spec`, '## Acceptance criteria'];
28
+ for (const entry of payload.spec) {
29
+ blocks.push(`### ${entry.id}`);
30
+ blocks.push(entry.criteria.length === 0
31
+ ? EMPTY_SECTION
32
+ : entry.criteria.map((c) => `- [ ] ${c.text}`).join('\n'));
33
+ }
34
+ blocks.push('## Non-goals');
35
+ for (const entry of payload.spec) {
36
+ blocks.push(`### ${entry.id}`);
37
+ // Verbatim: no trim, no re-wrap, no bullet normalisation. An empty section is the one
38
+ // substitution, because a blank block would read as a rendering bug.
39
+ blocks.push(entry.out_of_scope === '' ? EMPTY_SECTION : entry.out_of_scope);
40
+ }
41
+ const text = blocks.join('\n\n');
42
+ return text.endsWith('\n') ? text : `${text}\n`;
43
+ }
44
+ /** `plan.md` — one line, pointing at the plan. Conductor owns the file; we only seed it. */
45
+ export function renderTrackPlan(payload, opts = {}) {
46
+ const given = opts.planPath?.trim();
47
+ if (given)
48
+ return `${given}`.startsWith('See ') ? `${given}\n` : `See ${given}\n`;
49
+ const base = trackBaseName(payload.track.slug);
50
+ if (opts.created)
51
+ return `See docs/superpowers/plans/${opts.created}-${base}.md\n`;
52
+ return `No plan recorded for ${payload.track.slug} yet — add one under docs/superpowers/plans/.\n`;
53
+ }
54
+ /** `metadata.json` — v3 (§7), two-space indent, trailing newline. */
55
+ export function renderTrackMetadata(payload, opts) {
56
+ const doc = {
57
+ version: 3,
58
+ track_id: payload.track.slug,
59
+ name: (opts.name ?? trackBaseName(payload.track.slug)).trim(),
60
+ type: payload.track.type,
61
+ priority: opts.priority ?? 'P1',
62
+ created: opts.created,
63
+ status: 'in_progress',
64
+ backlog_ids: backlogIds(payload, opts),
65
+ follow_up_backlog: [],
66
+ qa_enforcement: opts.qaEnforcement ?? payload.track.qa_enforcement ?? 'permissive',
67
+ loop_state: { current_step: 'PLAN', step_status: 'IN_PROGRESS' },
68
+ };
69
+ return `${JSON.stringify(doc, null, 2)}\n`;
70
+ }
71
+ /** All three, keyed by filename — what `promote` writes into the track directory. */
72
+ export function renderTrackFiles(payload, opts) {
73
+ return {
74
+ 'spec.md': renderTrackSpec(payload, opts),
75
+ 'plan.md': renderTrackPlan(payload, opts),
76
+ 'metadata.json': renderTrackMetadata(payload, opts),
77
+ };
78
+ }
79
+ //# sourceMappingURL=track.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"track.js","sourceRoot":"","sources":["../../src/markdown/track.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { MatrixRow, MatrixStatus } from './schema.js';
2
+ /** §5.6: one-way, one step at a time; ⏭️ from any state with a reason. */
3
+ export declare const MATRIX_FORWARD: readonly MatrixStatus[];
4
+ export declare function canTransitionMatrix(from: MatrixStatus | null, to: MatrixStatus): boolean;
5
+ export declare function transitionMatrixRow(row: MatrixRow, to: MatrixStatus, opts?: {
6
+ reason?: string;
7
+ }): MatrixRow;
8
+ /** Which §3.1 action a target status needs — Engineers implement, QA verifies, Product+ skips. */
9
+ export declare function matrixActionFor(to: MatrixStatus): 'matrix_mark_implemented' | 'matrix_verify' | 'matrix_skip';