@rasensio/aidlc 1.20.0 → 1.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/cli.d.ts.map +1 -1
  2. package/dist/cli.js +2 -0
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/roadmap.d.ts +66 -0
  5. package/dist/commands/roadmap.d.ts.map +1 -0
  6. package/dist/commands/roadmap.js +188 -0
  7. package/dist/commands/roadmap.js.map +1 -0
  8. package/dist/menu/roster.d.ts.map +1 -1
  9. package/dist/menu/roster.js +4 -0
  10. package/dist/menu/roster.js.map +1 -1
  11. package/dist/roadmap/sync/body.d.ts +37 -0
  12. package/dist/roadmap/sync/body.d.ts.map +1 -0
  13. package/dist/roadmap/sync/body.js +52 -0
  14. package/dist/roadmap/sync/body.js.map +1 -0
  15. package/dist/roadmap/sync/collect.d.ts +45 -0
  16. package/dist/roadmap/sync/collect.d.ts.map +1 -0
  17. package/dist/roadmap/sync/collect.js +93 -0
  18. package/dist/roadmap/sync/collect.js.map +1 -0
  19. package/dist/roadmap/sync/config.d.ts +95 -0
  20. package/dist/roadmap/sync/config.d.ts.map +1 -0
  21. package/dist/roadmap/sync/config.js +236 -0
  22. package/dist/roadmap/sync/config.js.map +1 -0
  23. package/dist/roadmap/sync/discover.d.ts +56 -0
  24. package/dist/roadmap/sync/discover.d.ts.map +1 -0
  25. package/dist/roadmap/sync/discover.js +126 -0
  26. package/dist/roadmap/sync/discover.js.map +1 -0
  27. package/dist/roadmap/sync/execute.d.ts +41 -0
  28. package/dist/roadmap/sync/execute.d.ts.map +1 -0
  29. package/dist/roadmap/sync/execute.js +164 -0
  30. package/dist/roadmap/sync/execute.js.map +1 -0
  31. package/dist/roadmap/sync/gh.d.ts +64 -0
  32. package/dist/roadmap/sync/gh.d.ts.map +1 -0
  33. package/dist/roadmap/sync/gh.js +86 -0
  34. package/dist/roadmap/sync/gh.js.map +1 -0
  35. package/dist/roadmap/sync/import.d.ts +73 -0
  36. package/dist/roadmap/sync/import.d.ts.map +1 -0
  37. package/dist/roadmap/sync/import.js +269 -0
  38. package/dist/roadmap/sync/import.js.map +1 -0
  39. package/dist/roadmap/sync/labels.d.ts +57 -0
  40. package/dist/roadmap/sync/labels.d.ts.map +1 -0
  41. package/dist/roadmap/sync/labels.js +80 -0
  42. package/dist/roadmap/sync/labels.js.map +1 -0
  43. package/dist/roadmap/sync/marker.d.ts +38 -0
  44. package/dist/roadmap/sync/marker.d.ts.map +1 -0
  45. package/dist/roadmap/sync/marker.js +58 -0
  46. package/dist/roadmap/sync/marker.js.map +1 -0
  47. package/dist/roadmap/sync/plan.d.ts +40 -0
  48. package/dist/roadmap/sync/plan.d.ts.map +1 -0
  49. package/dist/roadmap/sync/plan.js +272 -0
  50. package/dist/roadmap/sync/plan.js.map +1 -0
  51. package/dist/roadmap/sync/render.d.ts +28 -0
  52. package/dist/roadmap/sync/render.d.ts.map +1 -0
  53. package/dist/roadmap/sync/render.js +110 -0
  54. package/dist/roadmap/sync/render.js.map +1 -0
  55. package/dist/roadmap/sync/types.d.ts +111 -0
  56. package/dist/roadmap/sync/types.d.ts.map +1 -0
  57. package/dist/roadmap/sync/types.js +43 -0
  58. package/dist/roadmap/sync/types.js.map +1 -0
  59. package/package.json +2 -2
@@ -0,0 +1,272 @@
1
+ /**
2
+ * The projection planner. Pure — and the purity is the point.
3
+ *
4
+ * This module imports no `node:fs`, no `node:child_process`, and not `gh.ts`. A test
5
+ * pins that over the import graph, which is what makes "planning needs no network" a
6
+ * property of the code rather than a claim about the test suite: hand the command a
7
+ * runner that throws on every invocation and planning still succeeds, because the
8
+ * planner never had one.
9
+ *
10
+ * Refusals are **values**, in a fixed order, so a tree with two problems always yields
11
+ * the same message and the command layer — not this module — decides the exit code.
12
+ *
13
+ * Requirements: roadmap-issue-projection/AC-14 … roadmap-issue-projection/AC-32,
14
+ * roadmap-issue-projection/AC-44
15
+ *
16
+ * @module
17
+ */
18
+ import { ROADMAP_STATUSES } from '../paths.js';
19
+ import { isStatusLabel, statusLabel } from './labels.js';
20
+ import { findMarkerIds } from './marker.js';
21
+ import { WRITE_ORDER, isWrite, } from './types.js';
22
+ /** GitHub's own issue-title limit. */
23
+ export const MAX_ISSUE_TITLE_LENGTH = 256;
24
+ export function planProjection(input) {
25
+ const { config, items, unprojectable, issues, retireIneligible, repoLabel } = input;
26
+ const eligible = new Set(config.statuses);
27
+ // -------------------------------------------------------------------------
28
+ // Refusals, in a fixed order (design: one deterministic message per tree)
29
+ // -------------------------------------------------------------------------
30
+ const duplicateIds = findDuplicateIds(items);
31
+ if (duplicateIds !== null)
32
+ return { kind: 'refused', error: duplicateIds };
33
+ const blockingItems = unprojectable.filter((u) => eligible.has(u.status));
34
+ if (blockingItems.length > 0) {
35
+ return { kind: 'refused', error: unprojectableMessage(blockingItems) };
36
+ }
37
+ const markerIndex = indexByMarker(issues);
38
+ if (markerIndex.kind === 'ambiguous') {
39
+ return { kind: 'refused', error: markerIndex.error };
40
+ }
41
+ const overLong = items.filter((i) => eligible.has(i.status) && i.title.length > MAX_ISSUE_TITLE_LENGTH);
42
+ if (overLong.length > 0) {
43
+ return {
44
+ kind: 'refused',
45
+ error: `these item titles exceed GitHub's ${MAX_ISSUE_TITLE_LENGTH}-character issue title limit ` +
46
+ 'and would fail mid-run, after consent:\n' +
47
+ overLong.map((i) => ` ${i.status}/${i.filename} (${i.title.length} characters)`).join('\n'),
48
+ };
49
+ }
50
+ // -------------------------------------------------------------------------
51
+ // Outcomes
52
+ // -------------------------------------------------------------------------
53
+ const warnings = unprojectable
54
+ .filter((u) => !eligible.has(u.status))
55
+ .map((u) => `${u.label} is not projectable (${u.reason}), and its status is not projected`);
56
+ const byId = markerIndex.byId;
57
+ const lines = [];
58
+ const claimedIssues = new Set();
59
+ for (const item of items) {
60
+ const issue = byId.get(item.id) ?? null;
61
+ if (issue !== null)
62
+ claimedIssues.add(issue.number);
63
+ lines.push(...linesForItem(item, issue, eligible, retireIneligible));
64
+ }
65
+ // Issue-only lines: an issue nothing on disk claims.
66
+ for (const issue of issues) {
67
+ if (claimedIssues.has(issue.number))
68
+ continue;
69
+ const markers = findMarkerIds(issue.body);
70
+ if (markers.length === 0) {
71
+ lines.push(line('label-without-marker', null, issue.number, 'carries the discovery label but no aidlc-item marker'));
72
+ }
73
+ else {
74
+ lines.push(line('orphan-missing', null, issue.number, `marker names ${markers[0]}, which no item on disk has`));
75
+ }
76
+ }
77
+ return { kind: 'ok', plan: assemble(lines, config, repoLabel), warnings };
78
+ }
79
+ // ---------------------------------------------------------------------------
80
+ // Outcome derivation
81
+ // ---------------------------------------------------------------------------
82
+ /**
83
+ * The lines one item produces.
84
+ *
85
+ * A **terminal report** — `state-divergent`, `ineligible-with-issue` — is the only line
86
+ * its item emits. That is not a stylistic choice: collecting outcomes unconditionally
87
+ * would emit `update-title` alongside `state-divergent`, writing to an issue a human
88
+ * deliberately closed. Closing an issue is the primary triage act of the very user this
89
+ * feature is for, so a projection that edits a closed issue relocates the two-writer
90
+ * loop rather than removing it.
91
+ *
92
+ * Non-terminal branches *are* collected, so a `done` item that was also retitled in the
93
+ * same run emits both `update-title` and `close` and converges in one pass.
94
+ */
95
+ function linesForItem(item, issue, eligible, retireIneligible) {
96
+ const isEligible = eligible.has(item.status);
97
+ if (issue === null) {
98
+ if (!isEligible)
99
+ return [];
100
+ if (item.status === 'done') {
101
+ return [line('skip', item, null, 'not-backfilled')];
102
+ }
103
+ return [
104
+ {
105
+ outcome: 'create',
106
+ item,
107
+ issueNumber: null,
108
+ detail: null,
109
+ addLabels: [statusLabel(item.status)],
110
+ removeLabels: [],
111
+ },
112
+ ];
113
+ }
114
+ if (!isEligible) {
115
+ if (retireIneligible) {
116
+ return [line('close', item, issue.number, `${item.status} is not a projected status`)];
117
+ }
118
+ return [
119
+ line('ineligible-with-issue', item, issue.number, `item is in ${item.status}, which is not projected; the issue is still open`),
120
+ ];
121
+ }
122
+ if (item.status !== 'done' && issue.state === 'CLOSED') {
123
+ return [
124
+ line('state-divergent', item, issue.number, `issue is closed but the item is still in ${item.status} — left alone`),
125
+ ];
126
+ }
127
+ const collected = [];
128
+ if (issue.title !== item.title) {
129
+ collected.push(line('update-title', item, issue.number, null));
130
+ }
131
+ const wanted = statusLabel(item.status);
132
+ const stale = issue.labels.filter((l) => isStatusLabel(l) && l !== wanted);
133
+ const hasWanted = issue.labels.includes(wanted);
134
+ if (!hasWanted || stale.length > 0) {
135
+ collected.push({
136
+ outcome: 'update-labels',
137
+ item,
138
+ issueNumber: issue.number,
139
+ detail: null,
140
+ addLabels: hasWanted ? [] : [wanted],
141
+ removeLabels: stale,
142
+ });
143
+ }
144
+ if (item.status === 'done' && issue.state === 'OPEN') {
145
+ collected.push(line('close', item, issue.number, null));
146
+ }
147
+ if (collected.length === 0) {
148
+ return [line('unchanged', item, issue.number, null)];
149
+ }
150
+ return collected;
151
+ }
152
+ function line(outcome, item, issueNumber, detail) {
153
+ return { outcome, item, issueNumber, detail, addLabels: [], removeLabels: [] };
154
+ }
155
+ // ---------------------------------------------------------------------------
156
+ // Indexing and refusal messages
157
+ // ---------------------------------------------------------------------------
158
+ /** Two items sharing an id would plan opposite writes against one issue, every run. */
159
+ function findDuplicateIds(items) {
160
+ const seen = new Map();
161
+ const clashes = [];
162
+ for (const item of items) {
163
+ const prior = seen.get(item.id);
164
+ if (prior === undefined) {
165
+ seen.set(item.id, item);
166
+ continue;
167
+ }
168
+ clashes.push(` ${item.id}\n ${prior.status}/${prior.filename}\n ${item.status}/${item.filename}`);
169
+ }
170
+ if (clashes.length === 0)
171
+ return null;
172
+ return ('two roadmap items share one id, so the projection cannot tell which one an issue ' +
173
+ 'belongs to:\n' +
174
+ clashes.join('\n') +
175
+ '\nRemove or re-id one of each pair, then run again.');
176
+ }
177
+ /**
178
+ * Index issues by the item id in their marker.
179
+ *
180
+ * Two issues carrying one marker is refused rather than resolved: picking either would
181
+ * mean confidently retitling or closing the wrong one.
182
+ */
183
+ function indexByMarker(issues) {
184
+ const byId = new Map();
185
+ const clashes = [];
186
+ for (const issue of issues) {
187
+ for (const id of findMarkerIds(issue.body)) {
188
+ const prior = byId.get(id);
189
+ if (prior === undefined) {
190
+ byId.set(id, issue);
191
+ continue;
192
+ }
193
+ clashes.push(` ${id} — issues #${prior.number} and #${issue.number}`);
194
+ }
195
+ }
196
+ if (clashes.length > 0) {
197
+ return {
198
+ kind: 'ambiguous',
199
+ error: 'more than one issue carries the same aidlc-item marker, so the projection cannot ' +
200
+ 'tell which is canonical:\n' +
201
+ clashes.join('\n') +
202
+ '\nRemove the marker from whichever issue is not the canonical one, then run again.',
203
+ };
204
+ }
205
+ return { kind: 'ok', byId };
206
+ }
207
+ /**
208
+ * The unprojectable-item refusal.
209
+ *
210
+ * Split by class, because the remedies differ and only one of them has a tool. Sending
211
+ * somebody to `aidlc doctor` for a missing `id` would send them to a command that reports
212
+ * nothing at all.
213
+ */
214
+ function unprojectableMessage(blocking) {
215
+ const repairable = blocking.filter((u) => u.cls === 'unparseable');
216
+ const manual = blocking.filter((u) => u.cls === 'missing-id');
217
+ const parts = [
218
+ 'these roadmap items are in a projected status but cannot be read, so the projection ' +
219
+ 'would silently leave them out of both the repo and the tracker:',
220
+ ];
221
+ if (repairable.length > 0) {
222
+ parts.push('', ' frontmatter will not parse — run `aidlc doctor`, which repairs the common case ' +
223
+ '(an unquoted title):', ...repairable.map((u) => ` ${u.label} — ${u.reason}`));
224
+ }
225
+ if (manual.length > 0) {
226
+ parts.push('', ' no `id:` — `aidlc doctor` does not detect this class; add an id by hand:', ...manual.map((u) => ` ${u.label} — ${u.reason}`));
227
+ }
228
+ return parts.join('\n');
229
+ }
230
+ // ---------------------------------------------------------------------------
231
+ // Assembly and ordering
232
+ // ---------------------------------------------------------------------------
233
+ /**
234
+ * Total order (so two runs over identical inputs print byte-identically):
235
+ * item lines by status then filename then execution order; issue-only lines after them,
236
+ * by issue number.
237
+ *
238
+ * The outcome component is compared through `WRITE_ORDER`, not as a string, so two lines
239
+ * for one item appear in the order they will be executed rather than in alphabetical
240
+ * accident.
241
+ */
242
+ function assemble(lines, config, repoLabel) {
243
+ const sorted = [...lines].sort(compareLines);
244
+ const counts = {};
245
+ let writeCount = 0;
246
+ for (const l of sorted) {
247
+ counts[l.outcome] = (counts[l.outcome] ?? 0) + 1;
248
+ if (isWrite(l.outcome))
249
+ writeCount += 1;
250
+ }
251
+ return { repoLabel, repo: config.repo, lines: sorted, counts, writeCount };
252
+ }
253
+ function compareLines(a, b) {
254
+ if (a.item === null && b.item === null)
255
+ return (a.issueNumber ?? 0) - (b.issueNumber ?? 0);
256
+ if (a.item === null)
257
+ return 1;
258
+ if (b.item === null)
259
+ return -1;
260
+ const statusDelta = ROADMAP_STATUSES.indexOf(a.item.status) - ROADMAP_STATUSES.indexOf(b.item.status);
261
+ if (statusDelta !== 0)
262
+ return statusDelta;
263
+ if (a.item.filename !== b.item.filename)
264
+ return a.item.filename < b.item.filename ? -1 : 1;
265
+ return outcomeRank(a.outcome) - outcomeRank(b.outcome);
266
+ }
267
+ /** Execution order first, then everything else in a fixed position. */
268
+ function outcomeRank(outcome) {
269
+ const index = WRITE_ORDER.indexOf(outcome);
270
+ return index === -1 ? WRITE_ORDER.length : index;
271
+ }
272
+ //# sourceMappingURL=plan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan.js","sourceRoot":"","sources":["../../../src/roadmap/sync/plan.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Plan → text.
3
+ *
4
+ * Two rules the plan output has to keep:
5
+ *
6
+ * - **The resolved repository is named in the header.** A well-formed but wrong
7
+ * repository coordinate publishes the roadmap to somebody else's repo, and that is only
8
+ * reversible before the first write. Printing it is the whole mitigation.
9
+ * - **Every outcome prints as a word.** Colour, if ever added, is decoration; the
10
+ * accessibility guidance forbids colour as the sole carrier of meaning, and a piped or
11
+ * logged plan has no colour at all.
12
+ *
13
+ * Requirements: roadmap-issue-projection/AC-40
14
+ *
15
+ * @module
16
+ */
17
+ import { type Plan } from './types.js';
18
+ export declare function renderPlan(plan: Plan): string;
19
+ /**
20
+ * The counts, writes first.
21
+ *
22
+ * Printed before consent so a first run against a mature roadmap — forty creates — is a
23
+ * visible decision rather than a surprise.
24
+ */
25
+ export declare function summary(plan: Plan): string;
26
+ /** The confirmation question. Names the repository again, deliberately. */
27
+ export declare function confirmMessage(plan: Plan): string;
28
+ //# sourceMappingURL=render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../../src/roadmap/sync/render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAyB,KAAK,IAAI,EAAiB,MAAM,YAAY,CAAC;AA8B7E,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAoB7C;AAwBD;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAiB1C;AAED,2EAA2E;AAC3E,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAEjD"}
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Plan → text.
3
+ *
4
+ * Two rules the plan output has to keep:
5
+ *
6
+ * - **The resolved repository is named in the header.** A well-formed but wrong
7
+ * repository coordinate publishes the roadmap to somebody else's repo, and that is only
8
+ * reversible before the first write. Printing it is the whole mitigation.
9
+ * - **Every outcome prints as a word.** Colour, if ever added, is decoration; the
10
+ * accessibility guidance forbids colour as the sole carrier of meaning, and a piped or
11
+ * logged plan has no colour at all.
12
+ *
13
+ * Requirements: roadmap-issue-projection/AC-40
14
+ *
15
+ * @module
16
+ */
17
+ import { isWrite } from './types.js';
18
+ /** Display order for the counts summary: writes first, then reports. */
19
+ const COUNT_ORDER = [
20
+ 'create',
21
+ 'update-title',
22
+ 'update-labels',
23
+ 'close',
24
+ 'unchanged',
25
+ 'skip',
26
+ 'state-divergent',
27
+ 'ineligible-with-issue',
28
+ 'orphan-missing',
29
+ 'label-without-marker',
30
+ ];
31
+ /** One-line human gloss per outcome, so the plan reads without a legend. */
32
+ const GLOSS = {
33
+ create: 'create an issue',
34
+ 'update-title': 'update the issue title',
35
+ 'update-labels': 'update the aidlc: status label',
36
+ close: 'close the issue',
37
+ unchanged: 'already matches',
38
+ skip: 'not projected',
39
+ 'state-divergent': 'closed remotely, item still live — left alone',
40
+ 'ineligible-with-issue': 'status no longer projected, issue still open',
41
+ 'orphan-missing': 'no item on disk',
42
+ 'label-without-marker': 'labelled but not projected by aidlc',
43
+ };
44
+ export function renderPlan(plan) {
45
+ const out = [];
46
+ out.push(`Roadmap projection plan — ${plan.repoLabel}`);
47
+ out.push('');
48
+ if (plan.lines.length === 0) {
49
+ out.push(' nothing to project: no items in a projected status, and no labelled issues.');
50
+ out.push('');
51
+ return out.join('\n');
52
+ }
53
+ for (const line of plan.lines) {
54
+ out.push(` ${renderLine(line)}`);
55
+ }
56
+ out.push('');
57
+ out.push(summary(plan));
58
+ out.push('');
59
+ return out.join('\n');
60
+ }
61
+ function renderLine(line) {
62
+ const subject = line.item === null
63
+ ? `#${line.issueNumber}`
64
+ : `${line.item.status}/${line.item.filename}` +
65
+ (line.issueNumber === null ? '' : ` → #${line.issueNumber}`);
66
+ const parts = [pad(line.outcome), subject];
67
+ const notes = [];
68
+ if (line.addLabels.length > 0)
69
+ notes.push(`+${line.addLabels.join(' +')}`);
70
+ if (line.removeLabels.length > 0)
71
+ notes.push(`-${line.removeLabels.join(' -')}`);
72
+ if (line.detail !== null)
73
+ notes.push(line.detail);
74
+ if (notes.length > 0)
75
+ parts.push(`(${notes.join('; ')})`);
76
+ return parts.join(' ');
77
+ }
78
+ /** Left-pad the outcome word so the subjects line up without a table. */
79
+ function pad(outcome) {
80
+ const width = Math.max(...COUNT_ORDER.map((o) => o.length));
81
+ return outcome.padEnd(width, ' ');
82
+ }
83
+ /**
84
+ * The counts, writes first.
85
+ *
86
+ * Printed before consent so a first run against a mature roadmap — forty creates — is a
87
+ * visible decision rather than a surprise.
88
+ */
89
+ export function summary(plan) {
90
+ const present = COUNT_ORDER.filter((o) => (plan.counts[o] ?? 0) > 0);
91
+ const writes = present.filter(isWrite);
92
+ const reports = present.filter((o) => !isWrite(o));
93
+ const lines = [];
94
+ lines.push(plan.writeCount === 0
95
+ ? ' No writes. Nothing on GitHub would change.'
96
+ : ` ${plan.writeCount} write${plan.writeCount === 1 ? '' : 's'}:`);
97
+ for (const o of writes)
98
+ lines.push(` ${plan.counts[o]} × ${o} — ${GLOSS[o]}`);
99
+ if (reports.length > 0) {
100
+ lines.push(' Reported only:');
101
+ for (const o of reports)
102
+ lines.push(` ${plan.counts[o]} × ${o} — ${GLOSS[o]}`);
103
+ }
104
+ return lines.join('\n');
105
+ }
106
+ /** The confirmation question. Names the repository again, deliberately. */
107
+ export function confirmMessage(plan) {
108
+ return `Apply ${plan.writeCount} write${plan.writeCount === 1 ? '' : 's'} to ${plan.repoLabel}?`;
109
+ }
110
+ //# sourceMappingURL=render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../../../src/roadmap/sync/render.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Shared types for the roadmap → GitHub Issues projection.
3
+ *
4
+ * The projection is one-way: disk is the source of truth and the tracker is a
5
+ * rendering of it. Two consequences show up throughout these types.
6
+ *
7
+ * First, `sync` writes nothing to disk (design DD-1). There is no `remote:`
8
+ * frontmatter field and no cache: the durable link is the item `id`, carried in a
9
+ * body marker on the issue. Creating an issue and writing a local link would be two
10
+ * writes to two systems, and every failure of the second produces a duplicate on the
11
+ * next run.
12
+ *
13
+ * Second, an outcome is a *value*, not an action. `planProjection` returns a plan of
14
+ * outcomes and the command layer decides what to do with it, which is what makes the
15
+ * whole projection testable with no network and no `gh` binary.
16
+ *
17
+ * Requirements: roadmap-issue-projection/AC-14, roadmap-issue-projection/AC-46
18
+ *
19
+ * @module
20
+ */
21
+ import type { RoadmapStatus } from '../paths.js';
22
+ /**
23
+ * What the projection concluded about one item, or one issue.
24
+ *
25
+ * A closed set. The design review found that an open-ended vocabulary made two
26
+ * criteria unachievable — "the second plan contains only X or Y" cannot be checked
27
+ * against a list nobody wrote down.
28
+ */
29
+ export type Outcome = 'create' | 'update-title' | 'update-labels' | 'close' | 'unchanged' | 'skip' | 'state-divergent' | 'ineligible-with-issue' | 'orphan-missing' | 'label-without-marker';
30
+ /** The four outcomes that write. Everything else is a report. */
31
+ export declare const WRITE_OUTCOMES: readonly Outcome[];
32
+ /**
33
+ * Outcomes that suppress every write for their item (design DE-18).
34
+ *
35
+ * `state-divergent` means a human closed the issue while the item is still live.
36
+ * Reopening it would relocate the two-writer loop rather than remove it, and
37
+ * retitling it would write to an issue somebody deliberately closed. So a terminal
38
+ * report is the *only* line its item emits.
39
+ */
40
+ export declare const TERMINAL_REPORTS: readonly Outcome[];
41
+ /** Execution order within one item: edit while the issue is still open, then close. */
42
+ export declare const WRITE_ORDER: readonly Outcome[];
43
+ /** True when this outcome performs a `gh` write. */
44
+ export declare function isWrite(outcome: Outcome): boolean;
45
+ /** An item that can be projected: parseable, with an id. */
46
+ export interface ProjectableItem {
47
+ id: string;
48
+ /** Falls back to the filename slug when frontmatter carries no title. */
49
+ title: string;
50
+ status: RoadmapStatus;
51
+ filename: string;
52
+ /** Repo-relative path, for the issue body. */
53
+ relPath: string;
54
+ }
55
+ /**
56
+ * Why an item cannot be projected.
57
+ *
58
+ * The two classes are distinguished because their remedies differ and only one of
59
+ * them has a tool. `aidlc doctor` repairs an unquoted title; nothing reports an item
60
+ * that parses fine with no `id`, so pointing a user at `doctor` for that class would
61
+ * send them to a command that says nothing (design DE-14).
62
+ */
63
+ export type UnprojectableClass = 'unparseable' | 'missing-id';
64
+ export interface UnprojectableRef {
65
+ /** `<status>/<filename>` — the form doctor output uses. */
66
+ label: string;
67
+ status: RoadmapStatus;
68
+ cls: UnprojectableClass;
69
+ reason: string;
70
+ }
71
+ /**
72
+ * One issue as the projection needs it.
73
+ *
74
+ * `labels` is already flattened to names. `gh issue list --json labels` returns
75
+ * objects (`{id,name,description,color}`), and treating them as strings would make
76
+ * every status-label comparison fail — so every item would plan `update-labels` on
77
+ * every run, forever, and the projection would never converge (design DE-9).
78
+ */
79
+ export interface RemoteIssue {
80
+ number: number;
81
+ title: string;
82
+ state: 'OPEN' | 'CLOSED';
83
+ labels: readonly string[];
84
+ body: string;
85
+ }
86
+ /** One line of a plan. */
87
+ export interface PlanLine {
88
+ outcome: Outcome;
89
+ /** null on issue-only lines (`orphan-missing`, `label-without-marker`). */
90
+ item: ProjectableItem | null;
91
+ issueNumber: number | null;
92
+ /** Short reason, e.g. `not-backfilled`, or the divergence in words. */
93
+ detail: string | null;
94
+ addLabels: readonly string[];
95
+ removeLabels: readonly string[];
96
+ }
97
+ export interface Plan {
98
+ /**
99
+ * Printed in the plan header *and* the confirmation prompt. A projection aimed at
100
+ * the wrong repository is only reversible before the first write, so the resolved
101
+ * target is stated at both moments a human can still stop it.
102
+ */
103
+ repoLabel: string;
104
+ /** `owner/name`, or null when `gh` resolves the current repository. */
105
+ repo: string | null;
106
+ lines: readonly PlanLine[];
107
+ /** Only outcomes actually present appear as keys. */
108
+ counts: Readonly<Partial<Record<Outcome, number>>>;
109
+ writeCount: number;
110
+ }
111
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/roadmap/sync/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD;;;;;;GAMG;AACH,MAAM,MAAM,OAAO,GACf,QAAQ,GACR,cAAc,GACd,eAAe,GACf,OAAO,GACP,WAAW,GACX,MAAM,GACN,iBAAiB,GACjB,uBAAuB,GACvB,gBAAgB,GAChB,sBAAsB,CAAC;AAE3B,iEAAiE;AACjE,eAAO,MAAM,cAAc,EAAE,SAAS,OAAO,EAK5C,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,EAAE,SAAS,OAAO,EAAiD,CAAC;AAEjG,uFAAuF;AACvF,eAAO,MAAM,WAAW,EAAE,SAAS,OAAO,EAAyD,CAAC;AAEpG,oDAAoD;AACpD,wBAAgB,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAEjD;AAED,4DAA4D;AAC5D,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,yEAAyE;IACzE,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,aAAa,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,8CAA8C;IAC9C,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,kBAAkB,GAAG,aAAa,GAAG,YAAY,CAAC;AAE9D,MAAM,WAAW,gBAAgB;IAC/B,2DAA2D;IAC3D,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,aAAa,CAAC;IACtB,GAAG,EAAE,kBAAkB,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,GAAG,QAAQ,CAAC;IACzB,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,0BAA0B;AAC1B,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,2EAA2E;IAC3E,IAAI,EAAE,eAAe,GAAG,IAAI,CAAC;IAC7B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,uEAAuE;IACvE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7B,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;CACjC;AAED,MAAM,WAAW,IAAI;IACnB;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,uEAAuE;IACvE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,KAAK,EAAE,SAAS,QAAQ,EAAE,CAAC;IAC3B,qDAAqD;IACrD,MAAM,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;IACnD,UAAU,EAAE,MAAM,CAAC;CACpB"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Shared types for the roadmap → GitHub Issues projection.
3
+ *
4
+ * The projection is one-way: disk is the source of truth and the tracker is a
5
+ * rendering of it. Two consequences show up throughout these types.
6
+ *
7
+ * First, `sync` writes nothing to disk (design DD-1). There is no `remote:`
8
+ * frontmatter field and no cache: the durable link is the item `id`, carried in a
9
+ * body marker on the issue. Creating an issue and writing a local link would be two
10
+ * writes to two systems, and every failure of the second produces a duplicate on the
11
+ * next run.
12
+ *
13
+ * Second, an outcome is a *value*, not an action. `planProjection` returns a plan of
14
+ * outcomes and the command layer decides what to do with it, which is what makes the
15
+ * whole projection testable with no network and no `gh` binary.
16
+ *
17
+ * Requirements: roadmap-issue-projection/AC-14, roadmap-issue-projection/AC-46
18
+ *
19
+ * @module
20
+ */
21
+ /** The four outcomes that write. Everything else is a report. */
22
+ export const WRITE_OUTCOMES = [
23
+ 'create',
24
+ 'update-title',
25
+ 'update-labels',
26
+ 'close',
27
+ ];
28
+ /**
29
+ * Outcomes that suppress every write for their item (design DE-18).
30
+ *
31
+ * `state-divergent` means a human closed the issue while the item is still live.
32
+ * Reopening it would relocate the two-writer loop rather than remove it, and
33
+ * retitling it would write to an issue somebody deliberately closed. So a terminal
34
+ * report is the *only* line its item emits.
35
+ */
36
+ export const TERMINAL_REPORTS = ['state-divergent', 'ineligible-with-issue'];
37
+ /** Execution order within one item: edit while the issue is still open, then close. */
38
+ export const WRITE_ORDER = ['create', 'update-title', 'update-labels', 'close'];
39
+ /** True when this outcome performs a `gh` write. */
40
+ export function isWrite(outcome) {
41
+ return WRITE_OUTCOMES.includes(outcome);
42
+ }
43
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/roadmap/sync/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAuBH,iEAAiE;AACjE,MAAM,CAAC,MAAM,cAAc,GAAuB;IAChD,QAAQ;IACR,cAAc;IACd,eAAe;IACf,OAAO;CACR,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAuB,CAAC,iBAAiB,EAAE,uBAAuB,CAAC,CAAC;AAEjG,uFAAuF;AACvF,MAAM,CAAC,MAAM,WAAW,GAAuB,CAAC,QAAQ,EAAE,cAAc,EAAE,eAAe,EAAE,OAAO,CAAC,CAAC;AAEpG,oDAAoD;AACpD,MAAM,UAAU,OAAO,CAAC,OAAgB;IACtC,OAAO,cAAc,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;AAC1C,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rasensio/aidlc",
3
- "version": "1.20.0",
3
+ "version": "1.21.0",
4
4
  "description": "AI Development Lifecycle Framework — structured lifecycle guidance for AI coding agents across platforms",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",
@@ -24,7 +24,7 @@
24
24
  "commander": "15.0.0",
25
25
  "picomatch": "4.0.5",
26
26
  "yaml": "2.9.0",
27
- "@rasensio/aidlc-content": "1.20.0"
27
+ "@rasensio/aidlc-content": "1.21.0"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@types/node": "22.15.32",