@rasensio/aidlc 1.20.0 → 1.22.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/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/roadmap.d.ts +66 -0
- package/dist/commands/roadmap.d.ts.map +1 -0
- package/dist/commands/roadmap.js +206 -0
- package/dist/commands/roadmap.js.map +1 -0
- package/dist/menu/roster.d.ts.map +1 -1
- package/dist/menu/roster.js +4 -0
- package/dist/menu/roster.js.map +1 -1
- package/dist/roadmap/paths.d.ts +21 -0
- package/dist/roadmap/paths.d.ts.map +1 -1
- package/dist/roadmap/paths.js +17 -1
- package/dist/roadmap/paths.js.map +1 -1
- package/dist/roadmap/sync/body.d.ts +123 -0
- package/dist/roadmap/sync/body.d.ts.map +1 -0
- package/dist/roadmap/sync/body.js +289 -0
- package/dist/roadmap/sync/body.js.map +1 -0
- package/dist/roadmap/sync/collect.d.ts +54 -0
- package/dist/roadmap/sync/collect.d.ts.map +1 -0
- package/dist/roadmap/sync/collect.js +138 -0
- package/dist/roadmap/sync/collect.js.map +1 -0
- package/dist/roadmap/sync/config.d.ts +95 -0
- package/dist/roadmap/sync/config.d.ts.map +1 -0
- package/dist/roadmap/sync/config.js +236 -0
- package/dist/roadmap/sync/config.js.map +1 -0
- package/dist/roadmap/sync/discover.d.ts +56 -0
- package/dist/roadmap/sync/discover.d.ts.map +1 -0
- package/dist/roadmap/sync/discover.js +126 -0
- package/dist/roadmap/sync/discover.js.map +1 -0
- package/dist/roadmap/sync/execute.d.ts +41 -0
- package/dist/roadmap/sync/execute.d.ts.map +1 -0
- package/dist/roadmap/sync/execute.js +174 -0
- package/dist/roadmap/sync/execute.js.map +1 -0
- package/dist/roadmap/sync/gh.d.ts +74 -0
- package/dist/roadmap/sync/gh.d.ts.map +1 -0
- package/dist/roadmap/sync/gh.js +110 -0
- package/dist/roadmap/sync/gh.js.map +1 -0
- package/dist/roadmap/sync/import.d.ts +73 -0
- package/dist/roadmap/sync/import.d.ts.map +1 -0
- package/dist/roadmap/sync/import.js +269 -0
- package/dist/roadmap/sync/import.js.map +1 -0
- package/dist/roadmap/sync/labels.d.ts +57 -0
- package/dist/roadmap/sync/labels.d.ts.map +1 -0
- package/dist/roadmap/sync/labels.js +80 -0
- package/dist/roadmap/sync/labels.js.map +1 -0
- package/dist/roadmap/sync/markdown.d.ts +79 -0
- package/dist/roadmap/sync/markdown.d.ts.map +1 -0
- package/dist/roadmap/sync/markdown.js +165 -0
- package/dist/roadmap/sync/markdown.js.map +1 -0
- package/dist/roadmap/sync/marker.d.ts +47 -0
- package/dist/roadmap/sync/marker.d.ts.map +1 -0
- package/dist/roadmap/sync/marker.js +73 -0
- package/dist/roadmap/sync/marker.js.map +1 -0
- package/dist/roadmap/sync/plan.d.ts +47 -0
- package/dist/roadmap/sync/plan.d.ts.map +1 -0
- package/dist/roadmap/sync/plan.js +290 -0
- package/dist/roadmap/sync/plan.js.map +1 -0
- package/dist/roadmap/sync/render.d.ts +28 -0
- package/dist/roadmap/sync/render.d.ts.map +1 -0
- package/dist/roadmap/sync/render.js +112 -0
- package/dist/roadmap/sync/render.js.map +1 -0
- package/dist/roadmap/sync/sanitize.d.ts +56 -0
- package/dist/roadmap/sync/sanitize.d.ts.map +1 -0
- package/dist/roadmap/sync/sanitize.js +140 -0
- package/dist/roadmap/sync/sanitize.js.map +1 -0
- package/dist/roadmap/sync/types.d.ts +139 -0
- package/dist/roadmap/sync/types.d.ts.map +1 -0
- package/dist/roadmap/sync/types.js +50 -0
- package/dist/roadmap/sync/types.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The body of a projected issue.
|
|
3
|
+
*
|
|
4
|
+
* v1.21.0 emitted a fixed six-line notice carrying no item prose at all, on the grounds
|
|
5
|
+
* that projecting prose risked marker injection, mention notifications, and the 65,536
|
|
6
|
+
* character field limit. The hazards were real; the conclusion was not. What shipped was a
|
|
7
|
+
* redirect — thirty issues on a board with thirty copies of one paragraph, unreadable to
|
|
8
|
+
* exactly the person the feature was for: somebody triaging in the tracker who will not
|
|
9
|
+
* clone the repository. `richer-issue-bodies` answers all three hazards instead of avoiding
|
|
10
|
+
* them: `sanitize.ts` neutralises comments and autolink triggers code-awarely, and the
|
|
11
|
+
* budget below bounds the field.
|
|
12
|
+
*
|
|
13
|
+
* Two structural decisions carry the rest of the design.
|
|
14
|
+
*
|
|
15
|
+
* **The region is delimited, and everything outside it is somebody else's.** A body is
|
|
16
|
+
* regenerated on every sync, so it must be possible to say precisely which bytes the
|
|
17
|
+
* projection owns. Text before `BODY_BEGIN` or after `BODY_END` survives untouched, which
|
|
18
|
+
* is what makes a projected issue still usable as a tracker: a reader can add a note to the
|
|
19
|
+
* description and keep it.
|
|
20
|
+
*
|
|
21
|
+
* **The body is rewritten, not written once.** v1.21.0 froze it at creation, reasoning that
|
|
22
|
+
* an update "would delete the context around [comments]". That does not hold — GitHub issue
|
|
23
|
+
* comments are separate entities from the body, and a body edit keeps its own revision
|
|
24
|
+
* history — and the cost of freezing is that every issue created before this change stays
|
|
25
|
+
* thin forever while every later item edit makes its issue quietly wrong. The rejected
|
|
26
|
+
* alternative was a one-off migration command; this project has a recorded lesson that a
|
|
27
|
+
* capability needing a manual post-deploy command will be un-run.
|
|
28
|
+
*
|
|
29
|
+
* Nothing volatile may enter this module's output. A timestamp, a commit sha or a metric
|
|
30
|
+
* would make the rendered region differ from the stored one on every single run, so every
|
|
31
|
+
* item would plan `update-body` forever and the projection would never converge — the same
|
|
32
|
+
* defect class as `discover.ts`'s labels-are-objects bug. That is why the region is a pure
|
|
33
|
+
* function of the item and the repository coordinates, and why a test pins that this file
|
|
34
|
+
* imports neither `node:fs` nor `node:child_process`.
|
|
35
|
+
*
|
|
36
|
+
* Requirements: richer-issue-bodies/AC-1 … richer-issue-bodies/AC-4,
|
|
37
|
+
* richer-issue-bodies/AC-6, richer-issue-bodies/AC-7, richer-issue-bodies/AC-16,
|
|
38
|
+
* richer-issue-bodies/AC-17, richer-issue-bodies/AC-21 … richer-issue-bodies/AC-24,
|
|
39
|
+
* richer-issue-bodies/AC-28
|
|
40
|
+
*
|
|
41
|
+
* @module
|
|
42
|
+
*/
|
|
43
|
+
import { lastProseBreakBefore } from './markdown.js';
|
|
44
|
+
import { renderMarker } from './marker.js';
|
|
45
|
+
import { sanitizeItemBody } from './sanitize.js';
|
|
46
|
+
/** Start of the projection-owned region. */
|
|
47
|
+
export const BODY_BEGIN = '<!-- aidlc-body:begin -->';
|
|
48
|
+
/** End of the projection-owned region. */
|
|
49
|
+
export const BODY_END = '<!-- aidlc-body:end -->';
|
|
50
|
+
/**
|
|
51
|
+
* The ceiling this module will not cross.
|
|
52
|
+
*
|
|
53
|
+
* GitHub's own limit is 65,536. The margin is not decoration: the largest real item in this
|
|
54
|
+
* project's roadmap is 44,481 characters, so nothing today comes close, and the headroom is
|
|
55
|
+
* what keeps a future item from failing a write *after* consent was given — the one moment
|
|
56
|
+
* in the projection where a failure is expensive.
|
|
57
|
+
*/
|
|
58
|
+
export const MAX_BODY_LENGTH = 60_000;
|
|
59
|
+
/**
|
|
60
|
+
* A permalink to a file on the default branch.
|
|
61
|
+
*
|
|
62
|
+
* `blob/HEAD/` rather than a resolved branch name, and this was measured rather than
|
|
63
|
+
* reasoned: on 2026-09-07, `github.com/cli/cli/blob/HEAD/README.md` returned 200 while
|
|
64
|
+
* `blob/main/README.md` returned 404, because that repository's default branch is `trunk`.
|
|
65
|
+
* Hardcoding `main` would ship broken links to every project that renamed its default
|
|
66
|
+
* branch. `HEAD` also needs no extra `gh` call and puts no remote state in the body.
|
|
67
|
+
*/
|
|
68
|
+
export function fileUrlFor(repo, relPath) {
|
|
69
|
+
return `https://github.com/${repo.owner}/${repo.name}/blob/HEAD/${relPath}`;
|
|
70
|
+
}
|
|
71
|
+
/** The context's URL builder, or null when coordinates are unknown. */
|
|
72
|
+
function urlBuilder(ctx) {
|
|
73
|
+
const { repo } = ctx;
|
|
74
|
+
return repo === null ? null : (relPath) => fileUrlFor(repo, relPath);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The projection-owned region, delimiters included.
|
|
78
|
+
*
|
|
79
|
+
* Facts first, because a triager reads the top of a body the way they read the top of an
|
|
80
|
+
* item. The marker sits *inside* the region, so replacing the region can never orphan or
|
|
81
|
+
* duplicate it.
|
|
82
|
+
*/
|
|
83
|
+
export function renderGeneratedRegion(item, ctx) {
|
|
84
|
+
const url = urlBuilder(ctx);
|
|
85
|
+
// Assemble around a prose slot, so the fixed cost is measured rather than estimated.
|
|
86
|
+
const assemble = (prose) => [
|
|
87
|
+
BODY_BEGIN,
|
|
88
|
+
'',
|
|
89
|
+
renderFacts(item, ctx, url),
|
|
90
|
+
'',
|
|
91
|
+
'---',
|
|
92
|
+
'',
|
|
93
|
+
prose,
|
|
94
|
+
'',
|
|
95
|
+
'---',
|
|
96
|
+
'',
|
|
97
|
+
renderFooter(),
|
|
98
|
+
'',
|
|
99
|
+
renderMarker(item.id),
|
|
100
|
+
BODY_END,
|
|
101
|
+
// No trailing newline. `extractRegion` ends AT `BODY_END`, so a region that ended one
|
|
102
|
+
// byte later would never compare equal to the one read back off an issue — every item
|
|
103
|
+
// would plan `update-body` on every run and the projection would never converge. The
|
|
104
|
+
// convergence test is what caught it; the two functions have to agree byte for byte.
|
|
105
|
+
].join('\n');
|
|
106
|
+
// Trimmed: an item body starts with the newline that followed its closing `---`, and the
|
|
107
|
+
// assembly already puts blank lines around the prose slot. Without this the region renders
|
|
108
|
+
// with a double blank line above the first heading — visible in the first real sample,
|
|
109
|
+
// invisible in every fixture, which is why generated prose gets read as rendered output.
|
|
110
|
+
const sanitized = sanitizeItemBody(item.body, { itemPaths: ctx.itemPaths, fileUrl: url }).trim();
|
|
111
|
+
return assemble(fitProse(sanitized, item, url, assemble('').length));
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* The facts block.
|
|
115
|
+
*
|
|
116
|
+
* `not recorded` for a key the frontmatter does not carry — an admitted gap a reader can
|
|
117
|
+
* tell apart from a real value, which an empty cell cannot. `promoted_to` is the one
|
|
118
|
+
* exception: absent and explicitly `null` both mean the same thing there, and *not
|
|
119
|
+
* promoted* is what that means.
|
|
120
|
+
*/
|
|
121
|
+
function renderFacts(item, ctx, url) {
|
|
122
|
+
const { facts } = item;
|
|
123
|
+
const value = (v) => (v === null ? '_not recorded_' : v);
|
|
124
|
+
const depends = facts.dependsOn.length === 0
|
|
125
|
+
? '_none_'
|
|
126
|
+
: facts.dependsOn.map((id) => renderItemRef(id, ctx, url)).join(', ');
|
|
127
|
+
const filePath = url === null ? `\`${item.relPath}\`` : `[${item.relPath}](${url(item.relPath)})`;
|
|
128
|
+
// A bullet list, not backslash hard breaks: it survives any renderer, and a screen
|
|
129
|
+
// reader announces four labelled facts rather than one run-on paragraph.
|
|
130
|
+
return [
|
|
131
|
+
`- **Status:** \`${item.status}\``,
|
|
132
|
+
`- **Created:** ${value(facts.createdAt)}`,
|
|
133
|
+
`- **Author:** ${value(facts.author)}`,
|
|
134
|
+
`- **Source:** ${value(facts.source)}`,
|
|
135
|
+
`- **Depends on:** ${depends}`,
|
|
136
|
+
`- **Promoted to:** ${facts.promotedTo === null ? '_not promoted_' : `\`${facts.promotedTo}\``}`,
|
|
137
|
+
`- **Roadmap file:** ${filePath}`,
|
|
138
|
+
].join('\n');
|
|
139
|
+
}
|
|
140
|
+
/** One `depends_on` entry: a link when the item exists, a named gap when it does not. */
|
|
141
|
+
function renderItemRef(id, ctx, url) {
|
|
142
|
+
const relPath = ctx.itemPaths.get(id);
|
|
143
|
+
if (relPath === undefined)
|
|
144
|
+
return `\`${id}\` (no such item)`;
|
|
145
|
+
if (url === null)
|
|
146
|
+
return `\`${id}\``;
|
|
147
|
+
return `[\`${id}\`](${url(relPath)})`;
|
|
148
|
+
}
|
|
149
|
+
/** The standing explanation, identical on every issue. */
|
|
150
|
+
function renderFooter() {
|
|
151
|
+
return [
|
|
152
|
+
'Projected from this repository’s AIDLC roadmap by `aidlc roadmap sync`. The roadmap',
|
|
153
|
+
'file named above is the source of truth — edit it there. This region is regenerated on',
|
|
154
|
+
'every sync; anything written outside it is preserved. Comments are never copied back',
|
|
155
|
+
'into the repository.',
|
|
156
|
+
].join('\n');
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Fit sanitized prose inside the remaining budget.
|
|
160
|
+
*
|
|
161
|
+
* The cut point is the last newline before the budget that sits in **prose**, which is what
|
|
162
|
+
* stops truncation leaving a fence open — a body ending mid-fence renders everything after
|
|
163
|
+
* it as one code block. When no safe break exists the prose is dropped entirely rather than
|
|
164
|
+
* cut mid-construct, and the notice says so; that case cannot arise from any real item
|
|
165
|
+
* today and a silent hard slice would be the worse failure if it ever did.
|
|
166
|
+
*/
|
|
167
|
+
function fitProse(prose, item, url, fixedLength) {
|
|
168
|
+
const budget = MAX_BODY_LENGTH - fixedLength;
|
|
169
|
+
if (prose.length <= budget)
|
|
170
|
+
return prose;
|
|
171
|
+
const link = url === null ? `\`${item.relPath}\`` : `[${item.relPath}](${url(item.relPath)})`;
|
|
172
|
+
// Reserve room for the notice itself, which is why the break is sought below a reduced
|
|
173
|
+
// limit rather than at the budget.
|
|
174
|
+
const notice = (omitted) => `\n_… ${omitted.toLocaleString('en-US')} further characters are not shown. Read the whole item at ${link}._`;
|
|
175
|
+
const reserve = notice(prose.length).length;
|
|
176
|
+
const cut = lastProseBreakBefore(prose, Math.max(0, budget - reserve));
|
|
177
|
+
if (cut === -1)
|
|
178
|
+
return notice(prose.length).trimStart();
|
|
179
|
+
return prose.slice(0, cut) + notice(prose.length - cut);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* The region bytes of an existing body, or null when it carries no region.
|
|
183
|
+
*
|
|
184
|
+
* Delimiters included, so the value can be compared directly against
|
|
185
|
+
* `renderGeneratedRegion`'s output — comparing the insides would let a delimiter change go
|
|
186
|
+
* undetected.
|
|
187
|
+
*/
|
|
188
|
+
export function extractRegion(body) {
|
|
189
|
+
const start = body.indexOf(BODY_BEGIN);
|
|
190
|
+
if (start === -1)
|
|
191
|
+
return null;
|
|
192
|
+
const end = body.indexOf(BODY_END, start);
|
|
193
|
+
if (end === -1)
|
|
194
|
+
return null;
|
|
195
|
+
return body.slice(start, end + BODY_END.length);
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* v1.21.0's body, reproduced exactly.
|
|
199
|
+
*
|
|
200
|
+
* **Frozen history, not a template.** Its only job is to be recognised so it can be
|
|
201
|
+
* replaced, so it must keep emitting the bytes v1.21.0 emitted even as the real renderer
|
|
202
|
+
* changes. A test pins it as a literal for that reason: edit it and issues created before
|
|
203
|
+
* this change stop being recognised, and every one of them gains a new region below a stale
|
|
204
|
+
* notice instead of replacing it.
|
|
205
|
+
*/
|
|
206
|
+
export function renderLegacyIssueBody(item) {
|
|
207
|
+
return [
|
|
208
|
+
'Projected from this repository’s AIDLC roadmap.',
|
|
209
|
+
'',
|
|
210
|
+
`- **Item:** \`${item.id}\``,
|
|
211
|
+
`- **File:** \`${item.relPath}\``,
|
|
212
|
+
'',
|
|
213
|
+
'The roadmap file is the source of truth. Edit it there — this issue is a rendering',
|
|
214
|
+
'of it, and its title and `aidlc:` labels are overwritten by `aidlc roadmap sync`.',
|
|
215
|
+
'Comments here are never copied back into the repository, and this description is',
|
|
216
|
+
'never rewritten after the issue is created.',
|
|
217
|
+
'',
|
|
218
|
+
renderMarker(item.id),
|
|
219
|
+
'',
|
|
220
|
+
].join('\n');
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* The body to write: a fresh region merged into whatever the issue already has.
|
|
224
|
+
*
|
|
225
|
+
* Four cases, first match wins:
|
|
226
|
+
*
|
|
227
|
+
* 1. **Both delimiters present** — replace the span between them and keep everything else.
|
|
228
|
+
* This is the steady state, and the reason a human's note survives a sync.
|
|
229
|
+
* 2. **Exactly the legacy notice** — the region alone. An issue created by v1.21.0 stops
|
|
230
|
+
* being thin.
|
|
231
|
+
* 3. **Legacy notice plus other text** — replace only the recognised notice. Whatever
|
|
232
|
+
* somebody added stays.
|
|
233
|
+
* 4. **Anything else** — append the region below the existing body, after stripping any
|
|
234
|
+
* marker naming *this* item, so the body ends up with exactly one.
|
|
235
|
+
*
|
|
236
|
+
* Case 4 deliberately leaves markers naming *other* ids alone. Those are the ambiguity
|
|
237
|
+
* `indexByMarker` exists to refuse, and quietly deleting one would hide a genuine
|
|
238
|
+
* two-issue collision rather than resolve it.
|
|
239
|
+
*/
|
|
240
|
+
export function mergeBody(existing, region, itemId) {
|
|
241
|
+
const start = existing.indexOf(BODY_BEGIN);
|
|
242
|
+
if (start !== -1) {
|
|
243
|
+
const end = existing.indexOf(BODY_END, start);
|
|
244
|
+
if (end !== -1) {
|
|
245
|
+
return existing.slice(0, start) + region + existing.slice(end + BODY_END.length);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
const legacy = legacyNoticeFor(itemId, existing);
|
|
249
|
+
if (legacy !== null) {
|
|
250
|
+
if (existing === legacy)
|
|
251
|
+
return region;
|
|
252
|
+
const at = existing.indexOf(legacy);
|
|
253
|
+
if (at !== -1)
|
|
254
|
+
return existing.slice(0, at) + region + existing.slice(at + legacy.length);
|
|
255
|
+
}
|
|
256
|
+
const kept = stripOwnMarkers(existing, itemId).trimEnd();
|
|
257
|
+
return kept.length === 0 ? region : `${kept}\n\n${region}`;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* The legacy notice for this item, if the body contains one.
|
|
261
|
+
*
|
|
262
|
+
* `renderLegacyIssueBody` needs a whole `ProjectableItem`, but a merge only knows the id
|
|
263
|
+
* and the body. The file path is recoverable from the notice itself, which is the one place
|
|
264
|
+
* v1.21.0 recorded it — so the notice is rebuilt from what it claims rather than from what
|
|
265
|
+
* the item currently says. That matters: an item that has since moved between statuses has
|
|
266
|
+
* a different `relPath` today, and rebuilding from the item would fail to match its own
|
|
267
|
+
* issue.
|
|
268
|
+
*/
|
|
269
|
+
function legacyNoticeFor(itemId, existing) {
|
|
270
|
+
const match = /- \*\*File:\*\* `([^`\n]+)`/.exec(existing);
|
|
271
|
+
if (match === null)
|
|
272
|
+
return null;
|
|
273
|
+
return renderLegacyIssueBody({
|
|
274
|
+
id: itemId,
|
|
275
|
+
relPath: match[1],
|
|
276
|
+
// Unused by the legacy renderer; present only to satisfy the shape.
|
|
277
|
+
title: '',
|
|
278
|
+
status: 'backlog',
|
|
279
|
+
filename: '',
|
|
280
|
+
body: '',
|
|
281
|
+
facts: { createdAt: null, author: null, source: null, promotedTo: null, dependsOn: [] },
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
/** Remove markers naming this item, leaving markers for other ids in place. */
|
|
285
|
+
function stripOwnMarkers(body, itemId) {
|
|
286
|
+
const escaped = itemId.replace(/[.*+?^${}()|[\]\\-]/g, '\\$&');
|
|
287
|
+
return body.replace(new RegExp(String.raw `<!--\s*aidlc-item:\s*${escaped}\s*-->\n?`, 'g'), '');
|
|
288
|
+
}
|
|
289
|
+
//# sourceMappingURL=body.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"body.js","sourceRoot":"","sources":["../../../src/roadmap/sync/body.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAGjD,4CAA4C;AAC5C,MAAM,CAAC,MAAM,UAAU,GAAG,2BAA2B,CAAC;AAEtD,0CAA0C;AAC1C,MAAM,CAAC,MAAM,QAAQ,GAAG,yBAAyB,CAAC;AAElD;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,MAAM,CAAC;AAetC;;;;;;;;GAQG;AACH,MAAM,UAAU,UAAU,CAAC,IAAgB,EAAE,OAAe;IAC1D,OAAO,sBAAsB,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,IAAI,cAAc,OAAO,EAAE,CAAC;AAC9E,CAAC;AAED,uEAAuE;AACvE,SAAS,UAAU,CAAC,GAAgB;IAClC,MAAM,EAAE,IAAI,EAAE,GAAG,GAAG,CAAC;IACrB,OAAO,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAe,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AAC/E,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAAqB,EAAE,GAAgB;IAC3E,MAAM,GAAG,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;IAE5B,qFAAqF;IACrF,MAAM,QAAQ,GAAG,CAAC,KAAa,EAAU,EAAE,CACzC;QACE,UAAU;QACV,EAAE;QACF,WAAW,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC;QAC3B,EAAE;QACF,KAAK;QACL,EAAE;QACF,KAAK;QACL,EAAE;QACF,KAAK;QACL,EAAE;QACF,YAAY,EAAE;QACd,EAAE;QACF,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC;QACrB,QAAQ;QACR,sFAAsF;QACtF,sFAAsF;QACtF,qFAAqF;QACrF,qFAAqF;KACtF,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEf,yFAAyF;IACzF,2FAA2F;IAC3F,uFAAuF;IACvF,yFAAyF;IACzF,MAAM,SAAS,GAAG,gBAAgB,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACjG,OAAO,QAAQ,CAAC,QAAQ,CAAC,SAAS,EAAE,IAAI,EAAE,GAAG,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;AACvE,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,WAAW,CAClB,IAAqB,EACrB,GAAgB,EAChB,GAAyC;IAEzC,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC;IACvB,MAAM,KAAK,GAAG,CAAC,CAAgB,EAAU,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAEhF,MAAM,OAAO,GACX,KAAK,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC;QAC1B,CAAC,CAAC,QAAQ;QACV,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,aAAa,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAE1E,MAAM,QAAQ,GACZ,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,OAAO,KAAK,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC;IAEnF,mFAAmF;IACnF,yEAAyE;IACzE,OAAO;QACL,mBAAmB,IAAI,CAAC,MAAM,IAAI;QAClC,kBAAkB,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE;QAC1C,iBAAiB,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE;QACtC,iBAAiB,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE;QACtC,qBAAqB,OAAO,EAAE;QAC9B,sBAAsB,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,UAAU,IAAI,EAAE;QAChG,uBAAuB,QAAQ,EAAE;KAClC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,yFAAyF;AACzF,SAAS,aAAa,CACpB,EAAU,EACV,GAAgB,EAChB,GAAyC;IAEzC,MAAM,OAAO,GAAG,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACtC,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,KAAK,EAAE,mBAAmB,CAAC;IAC7D,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,KAAK,EAAE,IAAI,CAAC;IACrC,OAAO,MAAM,EAAE,OAAO,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC;AACxC,CAAC;AAED,0DAA0D;AAC1D,SAAS,YAAY;IACnB,OAAO;QACL,qFAAqF;QACrF,wFAAwF;QACxF,sFAAsF;QACtF,sBAAsB;KACvB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,QAAQ,CACf,KAAa,EACb,IAAqB,EACrB,GAAyC,EACzC,WAAmB;IAEnB,MAAM,MAAM,GAAG,eAAe,GAAG,WAAW,CAAC;IAC7C,IAAI,KAAK,CAAC,MAAM,IAAI,MAAM;QAAE,OAAO,KAAK,CAAC;IAEzC,MAAM,IAAI,GAAG,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,OAAO,KAAK,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC;IAC9F,uFAAuF;IACvF,mCAAmC;IACnC,MAAM,MAAM,GAAG,CAAC,OAAe,EAAU,EAAE,CACzC,QAAQ,OAAO,CAAC,cAAc,CAAC,OAAO,CAAC,6DAA6D,IAAI,IAAI,CAAC;IAC/G,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC;IAE5C,MAAM,GAAG,GAAG,oBAAoB,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;IACvE,IAAI,GAAG,KAAK,CAAC,CAAC;QAAE,OAAO,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,SAAS,EAAE,CAAC;IAExD,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC;AAC1D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IACvC,IAAI,KAAK,KAAK,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAC9B,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAC1C,IAAI,GAAG,KAAK,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5B,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;AAClD,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAAqB;IACzD,OAAO;QACL,iDAAiD;QACjD,EAAE;QACF,iBAAiB,IAAI,CAAC,EAAE,IAAI;QAC5B,iBAAiB,IAAI,CAAC,OAAO,IAAI;QACjC,EAAE;QACF,oFAAoF;QACpF,mFAAmF;QACnF,kFAAkF;QAClF,6CAA6C;QAC7C,EAAE;QACF,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC;QACrB,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,SAAS,CAAC,QAAgB,EAAE,MAAc,EAAE,MAAc;IACxE,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAC3C,IAAI,KAAK,KAAK,CAAC,CAAC,EAAE,CAAC;QACjB,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;QAC9C,IAAI,GAAG,KAAK,CAAC,CAAC,EAAE,CAAC;YACf,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;QACnF,CAAC;IACH,CAAC;IAED,MAAM,MAAM,GAAG,eAAe,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACjD,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QACpB,IAAI,QAAQ,KAAK,MAAM;YAAE,OAAO,MAAM,CAAC;QACvC,MAAM,EAAE,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACpC,IAAI,EAAE,KAAK,CAAC,CAAC;YAAE,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IAC5F,CAAC;IAED,MAAM,IAAI,GAAG,eAAe,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,OAAO,EAAE,CAAC;IACzD,OAAO,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI,OAAO,MAAM,EAAE,CAAC;AAC7D,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,eAAe,CAAC,MAAc,EAAE,QAAgB;IACvD,MAAM,KAAK,GAAG,6BAA6B,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC3D,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAChC,OAAO,qBAAqB,CAAC;QAC3B,EAAE,EAAE,MAAM;QACV,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;QACjB,oEAAoE;QACpE,KAAK,EAAE,EAAE;QACT,MAAM,EAAE,SAAS;QACjB,QAAQ,EAAE,EAAE;QACZ,IAAI,EAAE,EAAE;QACR,KAAK,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE;KACxF,CAAC,CAAC;AACL,CAAC;AAED,+EAA+E;AAC/E,SAAS,eAAe,CAAC,IAAY,EAAE,MAAc;IACnD,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,sBAAsB,EAAE,MAAM,CAAC,CAAC;IAC/D,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,GAAG,CAAA,wBAAwB,OAAO,WAAW,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;AACjG,CAAC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read the roadmap off disk into what the planner needs.
|
|
3
|
+
*
|
|
4
|
+
* Reads only. It does not judge: every refusal lives in `planProjection`, in one tested
|
|
5
|
+
* order, so a tree with two problems always produces the same message.
|
|
6
|
+
*
|
|
7
|
+
* Two things this fixes about the obvious implementation:
|
|
8
|
+
*
|
|
9
|
+
* - `listStatus` silently skips an item whose frontmatter will not parse, because
|
|
10
|
+
* `readItem` returns null for both "absent" and "unparseable". A projection built on it
|
|
11
|
+
* alone would inherit that: an item invisible in the repo *and* on the board, with
|
|
12
|
+
* nothing anywhere saying so. Five of this project's first 49 items were invisible for
|
|
13
|
+
* up to ten days from exactly this. So `unparseableItems` is read alongside it.
|
|
14
|
+
* - An item can parse perfectly and still carry no `id` — `readItem` returns `id: null`
|
|
15
|
+
* for a missing or empty key. `unparseableItems` does not detect that class, so
|
|
16
|
+
* `aidlc doctor` is silent about it. It is collected here as its own class so the
|
|
17
|
+
* refusal message can send the user somewhere useful.
|
|
18
|
+
*
|
|
19
|
+
* Everything resolves through `primaryCheckout`. A roadmap read from a worktree's copy is
|
|
20
|
+
* a roadmap stranded on that branch, and for an inbox that is indistinguishable from
|
|
21
|
+
* losing it.
|
|
22
|
+
*
|
|
23
|
+
* Requirements: roadmap-issue-projection/AC-33, roadmap-issue-projection/AC-34,
|
|
24
|
+
* roadmap-issue-projection/AC-51
|
|
25
|
+
*
|
|
26
|
+
* @module
|
|
27
|
+
*/
|
|
28
|
+
import type { ProjectableItem, UnprojectableRef } from './types.js';
|
|
29
|
+
export interface Collected {
|
|
30
|
+
/** The primary checkout's working tree — everything below is relative to it. */
|
|
31
|
+
root: string;
|
|
32
|
+
items: readonly ProjectableItem[];
|
|
33
|
+
unprojectable: readonly UnprojectableRef[];
|
|
34
|
+
/**
|
|
35
|
+
* Every item id → its repo-relative path, across **all five statuses**.
|
|
36
|
+
*
|
|
37
|
+
* Not restricted to the eligible ones. A `backlog` item routinely names the four `hold`
|
|
38
|
+
* items it supersedes, and those references are the most useful links in the body
|
|
39
|
+
* precisely because a reader cannot guess where a parked item went. Restricting this map
|
|
40
|
+
* to projected statuses would drop them silently.
|
|
41
|
+
*/
|
|
42
|
+
itemPaths: ReadonlyMap<string, string>;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A title for an item whose frontmatter has none.
|
|
46
|
+
*
|
|
47
|
+
* The filename slug, de-hyphenated. Refusing would be worse: the item is perfectly
|
|
48
|
+
* projectable and the slug is a serviceable title, so a missing title is a cosmetic gap
|
|
49
|
+
* and not a reason to block the whole run.
|
|
50
|
+
*/
|
|
51
|
+
export declare function titleFromFilename(filename: string): string;
|
|
52
|
+
/** Read every status directory of the primary checkout containing `cwd`. */
|
|
53
|
+
export declare function collectItems(cwd: string): Collected;
|
|
54
|
+
//# sourceMappingURL=collect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collect.d.ts","sourceRoot":"","sources":["../../../src/roadmap/sync/collect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAaH,OAAO,KAAK,EAAa,eAAe,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE/E,MAAM,WAAW,SAAS;IACxB,gFAAgF;IAChF,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,SAAS,eAAe,EAAE,CAAC;IAClC,aAAa,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC3C;;;;;;;OAOG;IACH,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACxC;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAI1D;AAED,4EAA4E;AAC5E,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAmDnD"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read the roadmap off disk into what the planner needs.
|
|
3
|
+
*
|
|
4
|
+
* Reads only. It does not judge: every refusal lives in `planProjection`, in one tested
|
|
5
|
+
* order, so a tree with two problems always produces the same message.
|
|
6
|
+
*
|
|
7
|
+
* Two things this fixes about the obvious implementation:
|
|
8
|
+
*
|
|
9
|
+
* - `listStatus` silently skips an item whose frontmatter will not parse, because
|
|
10
|
+
* `readItem` returns null for both "absent" and "unparseable". A projection built on it
|
|
11
|
+
* alone would inherit that: an item invisible in the repo *and* on the board, with
|
|
12
|
+
* nothing anywhere saying so. Five of this project's first 49 items were invisible for
|
|
13
|
+
* up to ten days from exactly this. So `unparseableItems` is read alongside it.
|
|
14
|
+
* - An item can parse perfectly and still carry no `id` — `readItem` returns `id: null`
|
|
15
|
+
* for a missing or empty key. `unparseableItems` does not detect that class, so
|
|
16
|
+
* `aidlc doctor` is silent about it. It is collected here as its own class so the
|
|
17
|
+
* refusal message can send the user somewhere useful.
|
|
18
|
+
*
|
|
19
|
+
* Everything resolves through `primaryCheckout`. A roadmap read from a worktree's copy is
|
|
20
|
+
* a roadmap stranded on that branch, and for an inbox that is indistinguishable from
|
|
21
|
+
* losing it.
|
|
22
|
+
*
|
|
23
|
+
* Requirements: roadmap-issue-projection/AC-33, roadmap-issue-projection/AC-34,
|
|
24
|
+
* roadmap-issue-projection/AC-51
|
|
25
|
+
*
|
|
26
|
+
* @module
|
|
27
|
+
*/
|
|
28
|
+
import { ITEM_FILENAME_RE, ROADMAP_DIR, ROADMAP_STATUSES, frontmatter, itemBody, listStatus, primaryCheckout, unparseableItems, } from '../paths.js';
|
|
29
|
+
/**
|
|
30
|
+
* A title for an item whose frontmatter has none.
|
|
31
|
+
*
|
|
32
|
+
* The filename slug, de-hyphenated. Refusing would be worse: the item is perfectly
|
|
33
|
+
* projectable and the slug is a serviceable title, so a missing title is a cosmetic gap
|
|
34
|
+
* and not a reason to block the whole run.
|
|
35
|
+
*/
|
|
36
|
+
export function titleFromFilename(filename) {
|
|
37
|
+
const match = ITEM_FILENAME_RE.exec(filename);
|
|
38
|
+
const slug = match?.[2] ?? filename.replace(/\.md$/, '');
|
|
39
|
+
return slug.split('-').join(' ');
|
|
40
|
+
}
|
|
41
|
+
/** Read every status directory of the primary checkout containing `cwd`. */
|
|
42
|
+
export function collectItems(cwd) {
|
|
43
|
+
const root = primaryCheckout(cwd);
|
|
44
|
+
const items = [];
|
|
45
|
+
const unprojectable = [];
|
|
46
|
+
const itemPaths = new Map();
|
|
47
|
+
// Unparseable first, so its entries keep their own reasons rather than being
|
|
48
|
+
// overwritten by the weaker "no id" classification below.
|
|
49
|
+
const unparseableLabels = new Set();
|
|
50
|
+
for (const bad of unparseableItems(root)) {
|
|
51
|
+
unparseableLabels.add(bad.label);
|
|
52
|
+
unprojectable.push({
|
|
53
|
+
label: bad.label,
|
|
54
|
+
status: bad.status,
|
|
55
|
+
cls: 'unparseable',
|
|
56
|
+
reason: bad.detail === null ? bad.reason : `${bad.reason}: ${bad.detail}`,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
for (const status of ROADMAP_STATUSES) {
|
|
60
|
+
for (const item of listStatus(root, status)) {
|
|
61
|
+
const label = `${status}/${item.filename}`;
|
|
62
|
+
if (unparseableLabels.has(label))
|
|
63
|
+
continue;
|
|
64
|
+
if (item.id === null) {
|
|
65
|
+
unprojectable.push({
|
|
66
|
+
label,
|
|
67
|
+
status,
|
|
68
|
+
cls: 'missing-id',
|
|
69
|
+
reason: 'frontmatter has no `id:` value',
|
|
70
|
+
});
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const relPath = relPathOf(status, item.filename);
|
|
74
|
+
// First writer wins, matching `findByPromotedTo` and `indexByMarker`: a duplicate id
|
|
75
|
+
// is refused by `planProjection` anyway, so silently preferring one here cannot
|
|
76
|
+
// change an outcome — it only keeps this map from depending on directory order.
|
|
77
|
+
if (!itemPaths.has(item.id))
|
|
78
|
+
itemPaths.set(item.id, relPath);
|
|
79
|
+
items.push({
|
|
80
|
+
id: item.id,
|
|
81
|
+
title: item.title ?? titleFromFilename(item.filename),
|
|
82
|
+
status,
|
|
83
|
+
filename: item.filename,
|
|
84
|
+
relPath,
|
|
85
|
+
body: itemBody(item.raw),
|
|
86
|
+
facts: factsOf(item.raw),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return { root, items, unprojectable, itemPaths };
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* The frontmatter facts, from the `raw` `readItem` already returned.
|
|
94
|
+
*
|
|
95
|
+
* No second disk read, and no second parse of the whole file: `readItem` keeps `raw`
|
|
96
|
+
* precisely so a caller can work from it. Every value is validated at this boundary and
|
|
97
|
+
* becomes `null` on anything unexpected — a wrong-typed `author` is a cosmetic gap, not a
|
|
98
|
+
* reason to make the whole item unprojectable, which is the same call the existing
|
|
99
|
+
* missing-`title` fallback already makes.
|
|
100
|
+
*/
|
|
101
|
+
function factsOf(raw) {
|
|
102
|
+
const data = frontmatter(raw)?.data ?? {};
|
|
103
|
+
const str = (key) => {
|
|
104
|
+
const value = data[key];
|
|
105
|
+
if (typeof value === 'string' && value.trim().length > 0)
|
|
106
|
+
return value.trim();
|
|
107
|
+
// `created_at: 2026-09-05` parses as a Date under YAML 1.1 timestamp rules, so a
|
|
108
|
+
// string check alone would drop the one fact every item has.
|
|
109
|
+
if (value instanceof Date)
|
|
110
|
+
return value.toISOString().slice(0, 10);
|
|
111
|
+
return null;
|
|
112
|
+
};
|
|
113
|
+
const dependsOn = [];
|
|
114
|
+
if (Array.isArray(data.depends_on)) {
|
|
115
|
+
for (const entry of data.depends_on) {
|
|
116
|
+
if (typeof entry === 'string' && entry.trim().length > 0)
|
|
117
|
+
dependsOn.push(entry.trim());
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
createdAt: str('created_at'),
|
|
122
|
+
author: str('author'),
|
|
123
|
+
source: str('source'),
|
|
124
|
+
promotedTo: str('promoted_to'),
|
|
125
|
+
dependsOn,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Repo-relative path of an item, with forward slashes on every platform.
|
|
130
|
+
*
|
|
131
|
+
* Built from the known layout rather than by `relative()` over the item's absolute path,
|
|
132
|
+
* so the result is identical on Windows without a separator dance. The value ends up in
|
|
133
|
+
* an issue body, where a backslash would render wrong.
|
|
134
|
+
*/
|
|
135
|
+
function relPathOf(status, filename) {
|
|
136
|
+
return `${ROADMAP_DIR}/${status}/${filename}`;
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=collect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collect.js","sourceRoot":"","sources":["../../../src/roadmap/sync/collect.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `roadmap.sync` section of `.aidlc/config.yaml`.
|
|
3
|
+
*
|
|
4
|
+
* Follows the convention every other module's config reader in this package follows —
|
|
5
|
+
* there is no central config loader here, ten call sites each parse the file inline —
|
|
6
|
+
* with one boundary drawn explicitly, because the requirements review found revision 1
|
|
7
|
+
* contradicting itself about it:
|
|
8
|
+
*
|
|
9
|
+
* - A malformed **container** reads as *not configured*: unparseable YAML, `roadmap`
|
|
10
|
+
* not a mapping, `roadmap.sync` not a mapping. One bad section must never break an
|
|
11
|
+
* unrelated command.
|
|
12
|
+
* - Anything wrong **inside** a well-formed `sync` mapping is a *named refusal*,
|
|
13
|
+
* including a wrong-typed value. `provider: 123` is a mistake somebody made on
|
|
14
|
+
* purpose and wants told about; silently reading it as "you have not configured this"
|
|
15
|
+
* would send them looking in the wrong place.
|
|
16
|
+
*
|
|
17
|
+
* The section is read from the **primary checkout**, the same tree the items come from.
|
|
18
|
+
* Items and eligibility are one fact; reading them from two trees would let a feature
|
|
19
|
+
* branch change what a colleague's `sync` publishes.
|
|
20
|
+
*
|
|
21
|
+
* Requirements: roadmap-issue-projection/AC-1 … roadmap-issue-projection/AC-9
|
|
22
|
+
*
|
|
23
|
+
* @module
|
|
24
|
+
*/
|
|
25
|
+
import { type RoadmapStatus } from '../paths.js';
|
|
26
|
+
/** The `roadmap.sync` section, validated. */
|
|
27
|
+
export interface SyncConfig {
|
|
28
|
+
provider: 'github';
|
|
29
|
+
/** `owner/name`, or null when `gh` resolves the current repository. */
|
|
30
|
+
repo: string | null;
|
|
31
|
+
statuses: readonly RoadmapStatus[];
|
|
32
|
+
label: string;
|
|
33
|
+
}
|
|
34
|
+
export type SyncConfigResult = {
|
|
35
|
+
kind: 'ok';
|
|
36
|
+
config: SyncConfig;
|
|
37
|
+
} | {
|
|
38
|
+
kind: 'not-configured';
|
|
39
|
+
} | {
|
|
40
|
+
kind: 'invalid';
|
|
41
|
+
error: string;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* The default eligible statuses.
|
|
45
|
+
*
|
|
46
|
+
* The line falls between "thinking" and "work". `inbox/` means captured, not committed
|
|
47
|
+
* to — projecting it puts issues on a board for decisions nobody has made. `hold/`
|
|
48
|
+
* holds parked and declined work, which on this project means pricing thinking and a
|
|
49
|
+
* competitor teardown; a markdown file in a repo and an indexed, notification-generating
|
|
50
|
+
* issue are not the same exposure even when both are technically public.
|
|
51
|
+
*/
|
|
52
|
+
export declare const DEFAULT_STATUSES: readonly RoadmapStatus[];
|
|
53
|
+
/**
|
|
54
|
+
* Validate a repository coordinate. Shared by the config value and `--repo`, so the two
|
|
55
|
+
* can never diverge.
|
|
56
|
+
*/
|
|
57
|
+
export declare function validateRepo(raw: unknown, source: string): {
|
|
58
|
+
ok: true;
|
|
59
|
+
repo: string;
|
|
60
|
+
} | {
|
|
61
|
+
ok: false;
|
|
62
|
+
error: string;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Validate the discovery label.
|
|
66
|
+
*
|
|
67
|
+
* A comma is refused explicitly rather than folded into a charset message, because the
|
|
68
|
+
* failure it causes is silent and expensive: `gh`'s `--label` is a comma-split slice, so
|
|
69
|
+
* `aidlc,urgent` would AND two labels in discovery, return an empty issue map, and
|
|
70
|
+
* re-create every issue on every run. The discovery label *is* the identity of a
|
|
71
|
+
* projected issue, so a wrong one is a duplicate-issue generator, not a cosmetic error.
|
|
72
|
+
*/
|
|
73
|
+
export declare function validateLabel(raw: unknown): {
|
|
74
|
+
ok: true;
|
|
75
|
+
label: string;
|
|
76
|
+
} | {
|
|
77
|
+
ok: false;
|
|
78
|
+
error: string;
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Read and validate the `roadmap.sync` section from a checkout's config.
|
|
82
|
+
*
|
|
83
|
+
* @param primaryCheckoutRoot - The primary checkout's working tree.
|
|
84
|
+
*/
|
|
85
|
+
export declare function readSyncConfig(primaryCheckoutRoot: string): SyncConfigResult;
|
|
86
|
+
/**
|
|
87
|
+
* Apply a `--repo` flag over a config, with identical validation.
|
|
88
|
+
*
|
|
89
|
+
* The flag wins. It is the likelier source of a wrong value — typed by hand, once —
|
|
90
|
+
* which is why it goes through the same validator rather than a looser one.
|
|
91
|
+
*/
|
|
92
|
+
export declare function withRepoOverride(config: SyncConfig, repo: string | undefined): SyncConfigResult;
|
|
93
|
+
/** The message for a project that has not opted in. */
|
|
94
|
+
export declare function notConfiguredMessage(): string;
|
|
95
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/roadmap/sync/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAMH,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAGnE,6CAA6C;AAC7C,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,QAAQ,CAAC;IACnB,uEAAuE;IACvE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,QAAQ,EAAE,SAAS,aAAa,EAAE,CAAC;IACnC,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,UAAU,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,gBAAgB,CAAA;CAAE,GAC1B;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvC;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB,EAAE,SAAS,aAAa,EAAuC,CAAC;AAkC7F;;;GAGG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAmBpH;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CA0BtG;AA+BD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,mBAAmB,EAAE,MAAM,GAAG,gBAAgB,CAiD5E;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,gBAAgB,CAK/F;AAED,uDAAuD;AACvD,wBAAgB,oBAAoB,IAAI,MAAM,CAW7C"}
|