@rasensio/aidlc 1.19.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 (96) hide show
  1. package/dist/change/cross-check.d.ts +28 -0
  2. package/dist/change/cross-check.d.ts.map +1 -0
  3. package/dist/change/cross-check.js +53 -0
  4. package/dist/change/cross-check.js.map +1 -0
  5. package/dist/change/impact.d.ts +57 -0
  6. package/dist/change/impact.d.ts.map +1 -0
  7. package/dist/change/impact.js +288 -0
  8. package/dist/change/impact.js.map +1 -0
  9. package/dist/change/index.d.ts +12 -0
  10. package/dist/change/index.d.ts.map +1 -0
  11. package/dist/change/index.js +11 -0
  12. package/dist/change/index.js.map +1 -0
  13. package/dist/change/parser.d.ts +52 -0
  14. package/dist/change/parser.d.ts.map +1 -0
  15. package/dist/change/parser.js +339 -0
  16. package/dist/change/parser.js.map +1 -0
  17. package/dist/change/reopen.d.ts +80 -0
  18. package/dist/change/reopen.d.ts.map +1 -0
  19. package/dist/change/reopen.js +227 -0
  20. package/dist/change/reopen.js.map +1 -0
  21. package/dist/change/types.d.ts +144 -0
  22. package/dist/change/types.d.ts.map +1 -0
  23. package/dist/change/types.js +8 -0
  24. package/dist/change/types.js.map +1 -0
  25. package/dist/cli.d.ts.map +1 -1
  26. package/dist/cli.js +4 -0
  27. package/dist/cli.js.map +1 -1
  28. package/dist/commands/amend.d.ts +22 -0
  29. package/dist/commands/amend.d.ts.map +1 -0
  30. package/dist/commands/amend.js +355 -0
  31. package/dist/commands/amend.js.map +1 -0
  32. package/dist/commands/docs.d.ts.map +1 -1
  33. package/dist/commands/docs.js +6 -0
  34. package/dist/commands/docs.js.map +1 -1
  35. package/dist/commands/roadmap.d.ts +66 -0
  36. package/dist/commands/roadmap.d.ts.map +1 -0
  37. package/dist/commands/roadmap.js +188 -0
  38. package/dist/commands/roadmap.js.map +1 -0
  39. package/dist/core/lifecycle.d.ts +3 -0
  40. package/dist/core/lifecycle.d.ts.map +1 -1
  41. package/dist/core/lifecycle.js +13 -2
  42. package/dist/core/lifecycle.js.map +1 -1
  43. package/dist/core/types.d.ts +37 -2
  44. package/dist/core/types.d.ts.map +1 -1
  45. package/dist/menu/roster.d.ts.map +1 -1
  46. package/dist/menu/roster.js +8 -0
  47. package/dist/menu/roster.js.map +1 -1
  48. package/dist/roadmap/sync/body.d.ts +37 -0
  49. package/dist/roadmap/sync/body.d.ts.map +1 -0
  50. package/dist/roadmap/sync/body.js +52 -0
  51. package/dist/roadmap/sync/body.js.map +1 -0
  52. package/dist/roadmap/sync/collect.d.ts +45 -0
  53. package/dist/roadmap/sync/collect.d.ts.map +1 -0
  54. package/dist/roadmap/sync/collect.js +93 -0
  55. package/dist/roadmap/sync/collect.js.map +1 -0
  56. package/dist/roadmap/sync/config.d.ts +95 -0
  57. package/dist/roadmap/sync/config.d.ts.map +1 -0
  58. package/dist/roadmap/sync/config.js +236 -0
  59. package/dist/roadmap/sync/config.js.map +1 -0
  60. package/dist/roadmap/sync/discover.d.ts +56 -0
  61. package/dist/roadmap/sync/discover.d.ts.map +1 -0
  62. package/dist/roadmap/sync/discover.js +126 -0
  63. package/dist/roadmap/sync/discover.js.map +1 -0
  64. package/dist/roadmap/sync/execute.d.ts +41 -0
  65. package/dist/roadmap/sync/execute.d.ts.map +1 -0
  66. package/dist/roadmap/sync/execute.js +164 -0
  67. package/dist/roadmap/sync/execute.js.map +1 -0
  68. package/dist/roadmap/sync/gh.d.ts +64 -0
  69. package/dist/roadmap/sync/gh.d.ts.map +1 -0
  70. package/dist/roadmap/sync/gh.js +86 -0
  71. package/dist/roadmap/sync/gh.js.map +1 -0
  72. package/dist/roadmap/sync/import.d.ts +73 -0
  73. package/dist/roadmap/sync/import.d.ts.map +1 -0
  74. package/dist/roadmap/sync/import.js +269 -0
  75. package/dist/roadmap/sync/import.js.map +1 -0
  76. package/dist/roadmap/sync/labels.d.ts +57 -0
  77. package/dist/roadmap/sync/labels.d.ts.map +1 -0
  78. package/dist/roadmap/sync/labels.js +80 -0
  79. package/dist/roadmap/sync/labels.js.map +1 -0
  80. package/dist/roadmap/sync/marker.d.ts +38 -0
  81. package/dist/roadmap/sync/marker.d.ts.map +1 -0
  82. package/dist/roadmap/sync/marker.js +58 -0
  83. package/dist/roadmap/sync/marker.js.map +1 -0
  84. package/dist/roadmap/sync/plan.d.ts +40 -0
  85. package/dist/roadmap/sync/plan.d.ts.map +1 -0
  86. package/dist/roadmap/sync/plan.js +272 -0
  87. package/dist/roadmap/sync/plan.js.map +1 -0
  88. package/dist/roadmap/sync/render.d.ts +28 -0
  89. package/dist/roadmap/sync/render.d.ts.map +1 -0
  90. package/dist/roadmap/sync/render.js +110 -0
  91. package/dist/roadmap/sync/render.js.map +1 -0
  92. package/dist/roadmap/sync/types.d.ts +111 -0
  93. package/dist/roadmap/sync/types.d.ts.map +1 -0
  94. package/dist/roadmap/sync/types.js +43 -0
  95. package/dist/roadmap/sync/types.js.map +1 -0
  96. package/package.json +2 -2
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Build the id → issue map the planner reconciles against.
3
+ *
4
+ * One `gh issue list` invocation over the discovery label. Three details are load-bearing
5
+ * and all three were wrong in the first draft:
6
+ *
7
+ * - **`--limit` must be explicit.** `gh issue list` defaults to 30 results. A single call
8
+ * without a limit therefore returns at most 30 issues on a repo that has more, and every
9
+ * item missing from the map plans a `create` — producing exactly the duplicate issues the
10
+ * whole design exists to prevent, on every run, undetectably.
11
+ * - **Truncation must be a refusal, not a shrug.** A response of N issues is otherwise
12
+ * indistinguishable from "N is all there are". So the requested limit travels with the
13
+ * result and `count === limit` refuses before anything can plan a create.
14
+ * - **`labels` are objects.** `gh issue list --json labels` returns
15
+ * `{id,name,description,color}`, not strings. Treating them as strings makes every
16
+ * comparison against `aidlc:<status>` fail, so every item plans `update-labels` on every
17
+ * run and the projection never converges. They are flattened to names here, at the
18
+ * boundary, once.
19
+ *
20
+ * Requirements: roadmap-issue-projection/AC-11, roadmap-issue-projection/AC-12,
21
+ * roadmap-issue-projection/AC-13
22
+ *
23
+ * @module
24
+ */
25
+ import { firstLine, repoArgs } from './gh.js';
26
+ /**
27
+ * How many issues one map may hold.
28
+ *
29
+ * `gh` pages internally up to `--limit`, so this is one paginated sequence rather than a
30
+ * cap on a single page. A repo that reaches it gets a named refusal; that is a better
31
+ * outcome than a partial map, and better than reimplementing cursors against an interface
32
+ * (`gh issue list`) that exposes none.
33
+ */
34
+ export const ISSUE_LIST_LIMIT = 1000;
35
+ /** Read every labelled issue, open and closed. */
36
+ export function buildIssueMap(gh, opts) {
37
+ const limit = opts.limit ?? ISSUE_LIST_LIMIT;
38
+ const result = gh.run([
39
+ 'issue',
40
+ 'list',
41
+ ...repoArgs(opts.repo),
42
+ '--label',
43
+ opts.label,
44
+ '--state',
45
+ 'all',
46
+ '--limit',
47
+ String(limit),
48
+ '--json',
49
+ 'number,title,state,labels,body',
50
+ ]);
51
+ if (!result.ok) {
52
+ return { kind: 'failed', error: firstLine(result.error) };
53
+ }
54
+ let parsed;
55
+ try {
56
+ parsed = JSON.parse(result.stdout);
57
+ }
58
+ catch (err) {
59
+ return { kind: 'failed', error: `gh returned output that is not JSON: ${err.message}` };
60
+ }
61
+ if (!Array.isArray(parsed)) {
62
+ return { kind: 'failed', error: `gh returned ${typeof parsed} where a list of issues was expected` };
63
+ }
64
+ const issues = [];
65
+ for (const raw of parsed) {
66
+ const issue = toRemoteIssue(raw);
67
+ if (issue === null) {
68
+ return {
69
+ kind: 'failed',
70
+ error: 'gh returned an issue this version does not understand — expected number, title, ' +
71
+ `state, labels[].name and body, got keys ${describeKeys(raw)}`,
72
+ };
73
+ }
74
+ issues.push(issue);
75
+ }
76
+ // Deliberately after parsing, so a truncated response is still shape-checked: a limit
77
+ // hit alongside a shape change should report the shape change too on the next run.
78
+ if (issues.length === limit) {
79
+ return { kind: 'truncated', count: issues.length, limit };
80
+ }
81
+ return { kind: 'ok', issues };
82
+ }
83
+ /**
84
+ * Narrow one `gh` issue object, or null when the shape is not what this version expects.
85
+ *
86
+ * Refusing beats coercing. A `title` read as `undefined` would compare unequal to every
87
+ * item title and plan an `update-title` for the whole roadmap.
88
+ */
89
+ function toRemoteIssue(raw) {
90
+ if (typeof raw !== 'object' || raw === null || Array.isArray(raw))
91
+ return null;
92
+ const r = raw;
93
+ if (typeof r.number !== 'number')
94
+ return null;
95
+ if (typeof r.title !== 'string')
96
+ return null;
97
+ if (typeof r.body !== 'string')
98
+ return null;
99
+ if (r.state !== 'OPEN' && r.state !== 'CLOSED')
100
+ return null;
101
+ if (!Array.isArray(r.labels))
102
+ return null;
103
+ const labels = [];
104
+ for (const entry of r.labels) {
105
+ if (typeof entry !== 'object' || entry === null)
106
+ return null;
107
+ const name = entry.name;
108
+ if (typeof name !== 'string')
109
+ return null;
110
+ labels.push(name);
111
+ }
112
+ return { number: r.number, title: r.title, state: r.state, labels, body: r.body };
113
+ }
114
+ /** Key list of an unexpected payload, for the shape-failure message. */
115
+ function describeKeys(raw) {
116
+ if (typeof raw !== 'object' || raw === null)
117
+ return typeof raw;
118
+ return Object.keys(raw).sort().join(', ') || '(none)';
119
+ }
120
+ /** The message for a truncated map. */
121
+ export function truncatedMessage(count, limit) {
122
+ return (`the issue map may be incomplete: gh returned ${count} issues, which is the limit ` +
123
+ `requested (${limit}). Refusing rather than planning creates for issues that may already ` +
124
+ 'exist — a partial map turns every missing issue into a duplicate.');
125
+ }
126
+ //# sourceMappingURL=discover.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discover.js","sourceRoot":"","sources":["../../../src/roadmap/sync/discover.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Apply a plan. The only module in this feature that writes anything to GitHub.
3
+ *
4
+ * It imports no `node:fs` — deliberately, and pinned by a test. `sync` writes nothing to
5
+ * disk, and the cheapest way to keep that true is for the writing module to have no
6
+ * ability to. Snapshotting a directory around a run would be the weaker check: cost
7
+ * capture writes into `.aidlc/` during a session, so a whole-tree snapshot is either
8
+ * flaky or has to exclude so much that it stops proving anything.
9
+ *
10
+ * A `create` is **one** invocation carrying the discovery label, the status label, and the
11
+ * body marker. If the label were applied by a second call, a process death between the two
12
+ * would strand an issue invisible to discovery — which the next run would duplicate. One
13
+ * invocation makes the issue discoverable the instant it exists, and that atomicity is
14
+ * what the whole no-local-state design rests on.
15
+ *
16
+ * Execution **aborts on the first failure**. A rate limit or an authorization failure will
17
+ * fail everything after it too, so continuing would turn one legible error into forty.
18
+ *
19
+ * Requirements: roadmap-issue-projection/AC-23, roadmap-issue-projection/AC-41,
20
+ * roadmap-issue-projection/AC-42, roadmap-issue-projection/AC-43,
21
+ * roadmap-issue-projection/AC-46
22
+ *
23
+ * @module
24
+ */
25
+ import { type GhRunner } from './gh.js';
26
+ import { type Plan, type PlanLine } from './types.js';
27
+ export interface ExecReport {
28
+ applied: readonly PlanLine[];
29
+ /** Set when a write failed; everything in `notAttempted` was skipped because of it. */
30
+ failure: {
31
+ line: PlanLine;
32
+ error: string;
33
+ } | null;
34
+ notAttempted: readonly PlanLine[];
35
+ }
36
+ export declare function applyPlan(gh: GhRunner, plan: Plan, discoveryLabel: string): ExecReport;
37
+ /** The `gh` argv for one write outcome. */
38
+ export declare function invocationFor(plan: Plan, line: PlanLine, discoveryLabel: string): string[];
39
+ /** Render an execution report for stdout. */
40
+ export declare function renderExecReport(report: ExecReport): string;
41
+ //# sourceMappingURL=execute.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"execute.d.ts","sourceRoot":"","sources":["../../../src/roadmap/sync/execute.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,EAAuB,KAAK,QAAQ,EAAE,MAAM,SAAS,CAAC;AAE7D,OAAO,EAAW,KAAK,IAAI,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE/D,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,SAAS,QAAQ,EAAE,CAAC;IAC7B,uFAAuF;IACvF,OAAO,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAClD,YAAY,EAAE,SAAS,QAAQ,EAAE,CAAC;CACnC;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,GAAG,UAAU,CAyBtF;AAED,2CAA2C;AAC3C,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,GAAG,MAAM,EAAE,CA6C1F;AAyCD,6CAA6C;AAC7C,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAuB3D"}
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Apply a plan. The only module in this feature that writes anything to GitHub.
3
+ *
4
+ * It imports no `node:fs` — deliberately, and pinned by a test. `sync` writes nothing to
5
+ * disk, and the cheapest way to keep that true is for the writing module to have no
6
+ * ability to. Snapshotting a directory around a run would be the weaker check: cost
7
+ * capture writes into `.aidlc/` during a session, so a whole-tree snapshot is either
8
+ * flaky or has to exclude so much that it stops proving anything.
9
+ *
10
+ * A `create` is **one** invocation carrying the discovery label, the status label, and the
11
+ * body marker. If the label were applied by a second call, a process death between the two
12
+ * would strand an issue invisible to discovery — which the next run would duplicate. One
13
+ * invocation makes the issue discoverable the instant it exists, and that atomicity is
14
+ * what the whole no-local-state design rests on.
15
+ *
16
+ * Execution **aborts on the first failure**. A rate limit or an authorization failure will
17
+ * fail everything after it too, so continuing would turn one legible error into forty.
18
+ *
19
+ * Requirements: roadmap-issue-projection/AC-23, roadmap-issue-projection/AC-41,
20
+ * roadmap-issue-projection/AC-42, roadmap-issue-projection/AC-43,
21
+ * roadmap-issue-projection/AC-46
22
+ *
23
+ * @module
24
+ */
25
+ import { renderIssueBody } from './body.js';
26
+ import { firstLine, repoArgs } from './gh.js';
27
+ import { colorFor, descriptionFor } from './labels.js';
28
+ import { isWrite } from './types.js';
29
+ export function applyPlan(gh, plan, discoveryLabel) {
30
+ const writes = plan.lines.filter((l) => isWrite(l.outcome));
31
+ const applied = [];
32
+ const ensured = new Set();
33
+ for (let i = 0; i < writes.length; i++) {
34
+ const line = writes[i];
35
+ const labelFailure = ensureLabels(gh, plan, line, discoveryLabel, ensured);
36
+ if (labelFailure !== null) {
37
+ return { applied, failure: { line, error: labelFailure }, notAttempted: writes.slice(i) };
38
+ }
39
+ const result = gh.run(invocationFor(plan, line, discoveryLabel));
40
+ if (!result.ok) {
41
+ return {
42
+ applied,
43
+ failure: { line, error: firstLine(result.error) },
44
+ notAttempted: writes.slice(i + 1),
45
+ };
46
+ }
47
+ applied.push(line);
48
+ }
49
+ return { applied, failure: null, notAttempted: [] };
50
+ }
51
+ /** The `gh` argv for one write outcome. */
52
+ export function invocationFor(plan, line, discoveryLabel) {
53
+ const repo = [...repoArgs(plan.repo)];
54
+ switch (line.outcome) {
55
+ case 'create': {
56
+ if (line.item === null)
57
+ throw new Error('create without an item');
58
+ return [
59
+ 'issue',
60
+ 'create',
61
+ ...repo,
62
+ '--title',
63
+ line.item.title,
64
+ '--body',
65
+ renderIssueBody(line.item),
66
+ '--label',
67
+ discoveryLabel,
68
+ ...line.addLabels.flatMap((l) => ['--label', l]),
69
+ ];
70
+ }
71
+ case 'update-title': {
72
+ if (line.item === null || line.issueNumber === null) {
73
+ throw new Error('update-title without an item and issue');
74
+ }
75
+ return ['issue', 'edit', String(line.issueNumber), ...repo, '--title', line.item.title];
76
+ }
77
+ case 'update-labels': {
78
+ if (line.issueNumber === null)
79
+ throw new Error('update-labels without an issue');
80
+ // --add-label / --remove-label, never a wholesale --label: a human's labels on a
81
+ // projected issue are theirs, and replacing the set would silently delete them.
82
+ return [
83
+ 'issue',
84
+ 'edit',
85
+ String(line.issueNumber),
86
+ ...repo,
87
+ ...line.addLabels.flatMap((l) => ['--add-label', l]),
88
+ ...line.removeLabels.flatMap((l) => ['--remove-label', l]),
89
+ ];
90
+ }
91
+ case 'close': {
92
+ if (line.issueNumber === null)
93
+ throw new Error('close without an issue');
94
+ return ['issue', 'close', String(line.issueNumber), ...repo];
95
+ }
96
+ default:
97
+ throw new Error(`not a write outcome: ${line.outcome}`);
98
+ }
99
+ }
100
+ /**
101
+ * Create every label this line needs, once per run.
102
+ *
103
+ * `gh label create` **exits 1 when the label already exists**, so bare creation plus
104
+ * abort-on-first-failure would kill every run after the first. `--force` fixes that — but
105
+ * `gh` chooses a *random* colour when none is given, so `--force` alone would repaint the
106
+ * label on every run. The colour and description are therefore always passed, from a fixed
107
+ * table, which makes the ensure genuinely idempotent rather than merely non-failing.
108
+ */
109
+ function ensureLabels(gh, plan, line, discoveryLabel, ensured) {
110
+ const needed = line.outcome === 'create' ? [discoveryLabel, ...line.addLabels] : [...line.addLabels];
111
+ for (const name of needed) {
112
+ if (ensured.has(name))
113
+ continue;
114
+ const result = gh.run([
115
+ 'label',
116
+ 'create',
117
+ name,
118
+ ...repoArgs(plan.repo),
119
+ '--color',
120
+ colorFor(name),
121
+ '--description',
122
+ descriptionFor(name),
123
+ '--force',
124
+ ]);
125
+ if (!result.ok) {
126
+ return `could not create the label ${JSON.stringify(name)}: ${firstLine(result.error)}`;
127
+ }
128
+ ensured.add(name);
129
+ }
130
+ return null;
131
+ }
132
+ /** Render an execution report for stdout. */
133
+ export function renderExecReport(report) {
134
+ const out = [];
135
+ if (report.applied.length > 0) {
136
+ out.push(`Applied ${report.applied.length} write${report.applied.length === 1 ? '' : 's'}:`);
137
+ for (const l of report.applied)
138
+ out.push(` ${l.outcome} ${subjectOf(l)}`);
139
+ }
140
+ else {
141
+ out.push('No writes were applied.');
142
+ }
143
+ if (report.failure !== null) {
144
+ out.push('');
145
+ out.push(`Stopped at ${report.failure.line.outcome} ${subjectOf(report.failure.line)}`);
146
+ out.push(` gh said: ${report.failure.error}`);
147
+ if (report.notAttempted.length > 0) {
148
+ out.push(` ${report.notAttempted.length} further write(s) were not attempted:`);
149
+ for (const l of report.notAttempted)
150
+ out.push(` ${l.outcome} ${subjectOf(l)}`);
151
+ }
152
+ out.push('');
153
+ out.push('Re-run once the cause is fixed — the projection is idempotent, so completed writes are not repeated.');
154
+ }
155
+ return out.join('\n');
156
+ }
157
+ function subjectOf(line) {
158
+ if (line.item !== null) {
159
+ return `${line.item.status}/${line.item.filename}` +
160
+ (line.issueNumber === null ? '' : ` (#${line.issueNumber})`);
161
+ }
162
+ return `#${line.issueNumber}`;
163
+ }
164
+ //# sourceMappingURL=execute.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"execute.js","sourceRoot":"","sources":["../../../src/roadmap/sync/execute.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * The single subprocess boundary between this feature and the `gh` CLI.
3
+ *
4
+ * Every `gh` invocation in the projection goes through `GhRunner.run(args)`, and
5
+ * `args` is an array. That is structural rather than habitual on purpose: item titles
6
+ * and config values come from tracked files, so in a cloned repo they are the repo
7
+ * author's strings, not the user's. With one seam there is no call site for somebody
8
+ * in a hurry to add a template string or `shell: true` to, and the fake runner used in
9
+ * tests records every argv — which is what lets the token-secrecy property be checked
10
+ * by *value* (set `GH_TOKEN` to a sentinel, assert it appears in no recorded argv)
11
+ * rather than by grepping the source for a variable name. This project has broken
12
+ * prose-scoped greps six separate times.
13
+ *
14
+ * Nothing here reads `GH_TOKEN` or `GITHUB_TOKEN`. `gh` owns the credential; this
15
+ * module owns the argv.
16
+ *
17
+ * Requirements: roadmap-issue-projection/AC-30, roadmap-issue-projection/AC-50
18
+ *
19
+ * @module
20
+ */
21
+ export type GhResult = {
22
+ ok: true;
23
+ stdout: string;
24
+ } | {
25
+ ok: false;
26
+ error: string;
27
+ exitCode: number | null;
28
+ };
29
+ export interface GhRunner {
30
+ run(args: readonly string[]): GhResult;
31
+ }
32
+ /**
33
+ * 256 MiB.
34
+ *
35
+ * `execFileSync` defaults to 1 MiB, and discovery asks for every projected issue's
36
+ * body so the marker can be read. Measured: the default throws `ENOBUFS` at roughly
37
+ * 2 MiB of output, which a few hundred issues with ordinary bodies will exceed — two
38
+ * orders of magnitude below the 1000-issue map this feature otherwise supports. The
39
+ * failure is a thrown error rather than a truncated read, so it would be loud; it
40
+ * would also be entirely avoidable, which is why the limit is stated.
41
+ */
42
+ export declare const GH_MAX_BUFFER: number;
43
+ /** The real runner. Argument array, no shell, explicit buffer. */
44
+ export declare function realGhRunner(): GhRunner;
45
+ /**
46
+ * Check that `gh` is present and authenticated.
47
+ *
48
+ * **Authentication only.** `gh auth status` cannot tell whether the token may write
49
+ * issues on the target repository, so a token that is authenticated but not authorized
50
+ * passes here and fails on the first write — where `applyPlan` names the item and
51
+ * prints `gh`'s own message. Claiming otherwise would put the check in the wrong
52
+ * place and make the first-run failure less legible, not more.
53
+ */
54
+ export declare function ghPreflight(gh: GhRunner): {
55
+ ok: true;
56
+ } | {
57
+ ok: false;
58
+ error: string;
59
+ };
60
+ /** First non-empty line of a `gh` message, for one-line error reporting. */
61
+ export declare function firstLine(text: string): string;
62
+ /** `['--repo', repo]` when a coordinate was resolved, else `[]`. */
63
+ export declare function repoArgs(repo: string | null): readonly string[];
64
+ //# sourceMappingURL=gh.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gh.d.ts","sourceRoot":"","sources":["../../../src/roadmap/sync/gh.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,MAAM,MAAM,QAAQ,GAChB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC5B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAE1D,MAAM,WAAW,QAAQ;IACvB,GAAG,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,QAAQ,CAAC;CACxC;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,aAAa,QAAoB,CAAC;AAE/C,kEAAkE;AAClE,wBAAgB,YAAY,IAAI,QAAQ,CAkBvC;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,QAAQ,GAAG;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CASrF;AAED,4EAA4E;AAC5E,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAM9C;AAED,oEAAoE;AACpE,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,MAAM,EAAE,CAE/D"}
@@ -0,0 +1,86 @@
1
+ /**
2
+ * The single subprocess boundary between this feature and the `gh` CLI.
3
+ *
4
+ * Every `gh` invocation in the projection goes through `GhRunner.run(args)`, and
5
+ * `args` is an array. That is structural rather than habitual on purpose: item titles
6
+ * and config values come from tracked files, so in a cloned repo they are the repo
7
+ * author's strings, not the user's. With one seam there is no call site for somebody
8
+ * in a hurry to add a template string or `shell: true` to, and the fake runner used in
9
+ * tests records every argv — which is what lets the token-secrecy property be checked
10
+ * by *value* (set `GH_TOKEN` to a sentinel, assert it appears in no recorded argv)
11
+ * rather than by grepping the source for a variable name. This project has broken
12
+ * prose-scoped greps six separate times.
13
+ *
14
+ * Nothing here reads `GH_TOKEN` or `GITHUB_TOKEN`. `gh` owns the credential; this
15
+ * module owns the argv.
16
+ *
17
+ * Requirements: roadmap-issue-projection/AC-30, roadmap-issue-projection/AC-50
18
+ *
19
+ * @module
20
+ */
21
+ import { execFileSync } from 'node:child_process';
22
+ /**
23
+ * 256 MiB.
24
+ *
25
+ * `execFileSync` defaults to 1 MiB, and discovery asks for every projected issue's
26
+ * body so the marker can be read. Measured: the default throws `ENOBUFS` at roughly
27
+ * 2 MiB of output, which a few hundred issues with ordinary bodies will exceed — two
28
+ * orders of magnitude below the 1000-issue map this feature otherwise supports. The
29
+ * failure is a thrown error rather than a truncated read, so it would be loud; it
30
+ * would also be entirely avoidable, which is why the limit is stated.
31
+ */
32
+ export const GH_MAX_BUFFER = 256 * 1024 * 1024;
33
+ /** The real runner. Argument array, no shell, explicit buffer. */
34
+ export function realGhRunner() {
35
+ return {
36
+ run(args) {
37
+ try {
38
+ const stdout = execFileSync('gh', [...args], {
39
+ encoding: 'utf8',
40
+ stdio: ['ignore', 'pipe', 'pipe'],
41
+ maxBuffer: GH_MAX_BUFFER,
42
+ });
43
+ return { ok: true, stdout };
44
+ }
45
+ catch (err) {
46
+ const e = err;
47
+ const stderr = typeof e.stderr === 'string' ? e.stderr : e.stderr?.toString('utf8');
48
+ const message = (stderr ?? e.message ?? 'gh invocation failed').trim();
49
+ return { ok: false, error: message, exitCode: e.status ?? null };
50
+ }
51
+ },
52
+ };
53
+ }
54
+ /**
55
+ * Check that `gh` is present and authenticated.
56
+ *
57
+ * **Authentication only.** `gh auth status` cannot tell whether the token may write
58
+ * issues on the target repository, so a token that is authenticated but not authorized
59
+ * passes here and fails on the first write — where `applyPlan` names the item and
60
+ * prints `gh`'s own message. Claiming otherwise would put the check in the wrong
61
+ * place and make the first-run failure less legible, not more.
62
+ */
63
+ export function ghPreflight(gh) {
64
+ const result = gh.run(['auth', 'status']);
65
+ if (result.ok)
66
+ return { ok: true };
67
+ return {
68
+ ok: false,
69
+ error: 'gh is not available or not authenticated — install the GitHub CLI and run ' +
70
+ `\`gh auth login\`, then try again.\n gh said: ${firstLine(result.error)}`,
71
+ };
72
+ }
73
+ /** First non-empty line of a `gh` message, for one-line error reporting. */
74
+ export function firstLine(text) {
75
+ for (const line of text.split('\n')) {
76
+ const trimmed = line.trim();
77
+ if (trimmed.length > 0)
78
+ return trimmed;
79
+ }
80
+ return text.trim();
81
+ }
82
+ /** `['--repo', repo]` when a coordinate was resolved, else `[]`. */
83
+ export function repoArgs(repo) {
84
+ return repo === null ? [] : ['--repo', repo];
85
+ }
86
+ //# sourceMappingURL=gh.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gh.js","sourceRoot":"","sources":["../../../src/roadmap/sync/gh.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAUlD;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC;AAE/C,kEAAkE;AAClE,MAAM,UAAU,YAAY;IAC1B,OAAO;QACL,GAAG,CAAC,IAAuB;YACzB,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC,EAAE;oBAC3C,QAAQ,EAAE,MAAM;oBAChB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;oBACjC,SAAS,EAAE,aAAa;iBACzB,CAAC,CAAC;gBACH,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;YAC9B,CAAC;YAAC,OAAO,GAAY,EAAE,CAAC;gBACtB,MAAM,CAAC,GAAG,GAA6E,CAAC;gBACxF,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;gBACpF,MAAM,OAAO,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC,OAAO,IAAI,sBAAsB,CAAC,CAAC,IAAI,EAAE,CAAC;gBACvE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC,MAAM,IAAI,IAAI,EAAE,CAAC;YACnE,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CAAC,EAAY;IACtC,MAAM,MAAM,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC1C,IAAI,MAAM,CAAC,EAAE;QAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IACnC,OAAO;QACL,EAAE,EAAE,KAAK;QACT,KAAK,EACH,4EAA4E;YAC5E,kDAAkD,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE;KAC9E,CAAC;AACJ,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACpC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,OAAO,CAAC;IACzC,CAAC;IACD,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC;AACrB,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,QAAQ,CAAC,IAAmB;IAC1C,OAAO,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;AAC/C,CAAC"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * One-shot import: a GitHub issue seeds a roadmap item.
3
+ *
4
+ * Import, not sync. Nothing reconciles afterwards, nothing is written back to the issue,
5
+ * and running it twice over the same issue is reported rather than duplicated.
6
+ *
7
+ * The `title` is **always** double-quoted, with no judgement call about whether this one
8
+ * needs it. An unquoted YAML plain scalar may not begin with a reserved indicator
9
+ * character, so the single most natural way to name a code defect — an identifier in
10
+ * backticks — does not parse; and nothing reports it, because an item that will not parse
11
+ * and an item that was never written look identical. Five of this project's first 49 items
12
+ * were lost that way for up to ten days, and GitHub issue titles are precisely the corpus
13
+ * that produces them.
14
+ *
15
+ * Requirements: roadmap-issue-projection/AC-51 … roadmap-issue-projection/AC-60
16
+ *
17
+ * @module
18
+ */
19
+ import { type GhRunner } from './gh.js';
20
+ /** A GitHub issue, reduced to what identifies it. */
21
+ export interface IssueCoord {
22
+ host: string;
23
+ owner: string;
24
+ repo: string;
25
+ number: number;
26
+ }
27
+ export type ImportResult = {
28
+ kind: 'written';
29
+ path: string;
30
+ } | {
31
+ kind: 'duplicate';
32
+ existing: string;
33
+ coord: IssueCoord;
34
+ } | {
35
+ kind: 'refused';
36
+ error: string;
37
+ };
38
+ /** Slug used when a title yields nothing usable. */
39
+ export declare const FALLBACK_SLUG_PREFIX = "imported-issue";
40
+ /**
41
+ * Parse an issue reference into a comparable tuple.
42
+ *
43
+ * Normalising is the point: `https://github.com/o/r/issues/12`, the same with a trailing
44
+ * slash, with a `#issuecomment-…` fragment, with `www.`, and the
45
+ * `api.github.com/repos/o/r/issues/12` form all denote one issue. Comparing raw strings
46
+ * would report false duplicates in one direction and miss real ones in the other — and a
47
+ * naive substring compare makes `issues/1` match `issues/12`.
48
+ */
49
+ export declare function parseIssueUrl(raw: string): IssueCoord | null;
50
+ /** Canonical string form of a coordinate, for map keys. */
51
+ export declare function coordKey(coord: IssueCoord): string;
52
+ /**
53
+ * Every issue already linked by a roadmap item, keyed by coordinate.
54
+ *
55
+ * This reads item **bodies**, which the roadmap skill's frontmatter-only rule otherwise
56
+ * discourages. That rule governs *browse*, which is a list and must stay cheap enough to
57
+ * run over the whole roadmap. Import is neither a list nor cheap-by-contract, and there is
58
+ * nowhere else the local half of a duplicate check could come from — DD-1 means no
59
+ * `remote:` field exists to consult.
60
+ */
61
+ export declare function linkedCoords(cwd: string): Map<string, string>;
62
+ /**
63
+ * A filename-safe slug from an issue title.
64
+ *
65
+ * Must satisfy `ITEM_FILENAME_RE`'s `[a-z0-9][a-z0-9-]*`, which a title of punctuation or
66
+ * of a non-Latin script will not survive — hence the fallback rather than a refusal. An
67
+ * issue that cannot be imported because of its title would be a strange thing to tell
68
+ * somebody.
69
+ */
70
+ export declare function slugFromTitle(title: string, number: number): string;
71
+ /** Import one issue into `inbox/`. */
72
+ export declare function importIssue(gh: GhRunner, cwd: string, url: string): ImportResult;
73
+ //# sourceMappingURL=import.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"import.d.ts","sourceRoot":"","sources":["../../../src/roadmap/sync/import.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAYH,OAAO,EAAa,KAAK,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEnD,qDAAqD;AACrD,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACjC;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,UAAU,CAAA;CAAE,GAC1D;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvC,oDAAoD;AACpD,eAAO,MAAM,oBAAoB,mBAAmB,CAAC;AAKrD;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CA2B5D;AAQD,2DAA2D;AAC3D,wBAAgB,QAAQ,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAElD;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CA8B7D;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAYnE;AAuBD,sCAAsC;AACtC,wBAAgB,WAAW,CAAC,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,YAAY,CA+ChF"}