@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.
- 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 +188 -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/sync/body.d.ts +37 -0
- package/dist/roadmap/sync/body.d.ts.map +1 -0
- package/dist/roadmap/sync/body.js +52 -0
- package/dist/roadmap/sync/body.js.map +1 -0
- package/dist/roadmap/sync/collect.d.ts +45 -0
- package/dist/roadmap/sync/collect.d.ts.map +1 -0
- package/dist/roadmap/sync/collect.js +93 -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 +164 -0
- package/dist/roadmap/sync/execute.js.map +1 -0
- package/dist/roadmap/sync/gh.d.ts +64 -0
- package/dist/roadmap/sync/gh.d.ts.map +1 -0
- package/dist/roadmap/sync/gh.js +86 -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/marker.d.ts +38 -0
- package/dist/roadmap/sync/marker.d.ts.map +1 -0
- package/dist/roadmap/sync/marker.js +58 -0
- package/dist/roadmap/sync/marker.js.map +1 -0
- package/dist/roadmap/sync/plan.d.ts +40 -0
- package/dist/roadmap/sync/plan.d.ts.map +1 -0
- package/dist/roadmap/sync/plan.js +272 -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 +110 -0
- package/dist/roadmap/sync/render.js.map +1 -0
- package/dist/roadmap/sync/types.d.ts +111 -0
- package/dist/roadmap/sync/types.d.ts.map +1 -0
- package/dist/roadmap/sync/types.js +43 -0
- package/dist/roadmap/sync/types.js.map +1 -0
- package/package.json +2 -2
|
@@ -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"}
|