@h-rig/standard-plugin 0.0.6-alpha.15 → 0.0.6-alpha.151
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/src/blocker-classifier.d.ts +1 -0
- package/dist/src/blocker-classifier.js +18 -0
- package/dist/src/bundle.d.ts +7 -0
- package/dist/src/bundle.js +1859 -0
- package/dist/src/cli-surface.d.ts +1 -0
- package/dist/src/cli-surface.js +12 -0
- package/dist/src/default-lifecycle.d.ts +2 -0
- package/dist/src/default-lifecycle.js +12 -0
- package/dist/src/dependency-graph.d.ts +1 -0
- package/dist/src/dependency-graph.js +22 -0
- package/dist/src/drift/__fixtures__/temp-repo.d.ts +9 -0
- package/dist/src/drift/__fixtures__/temp-repo.js +41 -0
- package/dist/src/drift/detect.d.ts +11 -0
- package/dist/src/drift/detect.js +299 -0
- package/dist/src/drift/extract-refs.d.ts +7 -0
- package/dist/src/drift/extract-refs.js +60 -0
- package/dist/src/drift/git-adapter.d.ts +7 -0
- package/dist/src/drift/git-adapter.js +63 -0
- package/dist/src/drift/judge.d.ts +19 -0
- package/dist/src/drift/judge.js +16 -0
- package/dist/src/drift/metadata.d.ts +13 -0
- package/dist/src/drift/metadata.js +33 -0
- package/dist/src/drift/plugin.d.ts +53 -0
- package/dist/src/drift/plugin.js +507 -0
- package/dist/src/files-source.d.ts +18 -0
- package/dist/src/files-source.js +4 -3
- package/dist/src/github-issues-source.d.ts +80 -0
- package/dist/src/github-issues-source.js +482 -53
- package/dist/src/index.d.ts +13 -0
- package/dist/src/index.js +1369 -68
- package/dist/src/lifecycle-closeout.d.ts +2 -0
- package/dist/src/lifecycle-closeout.js +6 -0
- package/dist/src/planning.d.ts +1 -0
- package/dist/src/planning.js +14 -0
- package/dist/src/plugin.d.ts +24 -0
- package/dist/src/plugin.js +1814 -0
- package/dist/src/product-plugin.d.ts +3 -0
- package/dist/src/product-plugin.js +18 -0
- package/dist/src/run-worker-panels.d.ts +15 -0
- package/dist/src/run-worker-panels.js +53 -0
- package/dist/src/supervisor.d.ts +1 -0
- package/dist/src/supervisor.js +12 -0
- package/dist/src/task-cli.d.ts +1 -0
- package/dist/src/task-cli.js +14 -0
- package/package.json +67 -5
package/dist/src/index.js
CHANGED
|
@@ -1,6 +1,610 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __returnValue = (v) => v;
|
|
4
|
+
function __exportSetter(name, newValue) {
|
|
5
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
6
|
+
}
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, {
|
|
10
|
+
get: all[name],
|
|
11
|
+
enumerable: true,
|
|
12
|
+
configurable: true,
|
|
13
|
+
set: __exportSetter.bind(all, name)
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
17
|
+
|
|
18
|
+
// packages/standard-plugin/src/drift/metadata.ts
|
|
19
|
+
import { Schema } from "effect";
|
|
20
|
+
import { StageMutation as StageMutationSchema } from "@rig/contracts";
|
|
21
|
+
var DOCS_DRIFT_VALIDATOR_ID = "std:docs-drift", DOCS_DRIFT_CLI_ID = "std:drift", DOCS_DRIFT_STAGE_ID = "docs-drift", DOCS_DRIFT_CAPABILITY_ID = "std:docs-drift-capability", DOCS_DRIFT_VALIDATOR, DOCS_DRIFT_STAGE_MUTATION, DOCS_DRIFT_CLI_COMMAND = "rig drift [--docs <csv>] [--ignore <csv>] [--fail-on-drift] [--json]";
|
|
22
|
+
var init_metadata = __esm(() => {
|
|
23
|
+
DOCS_DRIFT_VALIDATOR = {
|
|
24
|
+
id: DOCS_DRIFT_VALIDATOR_ID,
|
|
25
|
+
category: "regression",
|
|
26
|
+
description: "Detect documentation references that drifted from the source tree."
|
|
27
|
+
};
|
|
28
|
+
DOCS_DRIFT_STAGE_MUTATION = Schema.decodeUnknownSync(StageMutationSchema)({
|
|
29
|
+
op: "insert",
|
|
30
|
+
stage: {
|
|
31
|
+
id: DOCS_DRIFT_STAGE_ID,
|
|
32
|
+
kind: "gate",
|
|
33
|
+
before: ["merge-gate"],
|
|
34
|
+
after: ["open-pr"]
|
|
35
|
+
},
|
|
36
|
+
contributedBy: DOCS_DRIFT_STAGE_ID
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// packages/standard-plugin/src/drift/extract-refs.ts
|
|
41
|
+
function stripFenceLines(markdown) {
|
|
42
|
+
const lines = markdown.split(/\r?\n/);
|
|
43
|
+
let fenced = false;
|
|
44
|
+
return lines.map((line) => {
|
|
45
|
+
if (/^\s*(```|~~~)/.test(line)) {
|
|
46
|
+
fenced = !fenced;
|
|
47
|
+
return "";
|
|
48
|
+
}
|
|
49
|
+
return fenced ? "" : line;
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
function normalizeToken(raw) {
|
|
53
|
+
return raw.trim().replace(/^['"]|['"]$/g, "").replace(/[),.;:]+$/g, "").replace(/#L\d+(?:-L\d+)?$/i, "");
|
|
54
|
+
}
|
|
55
|
+
function classifyReference(raw) {
|
|
56
|
+
if (raw.startsWith("@"))
|
|
57
|
+
return null;
|
|
58
|
+
if (PATH_REF.test(raw))
|
|
59
|
+
return "path";
|
|
60
|
+
if (SYMBOL_REF.test(raw))
|
|
61
|
+
return "symbol";
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
function pushReference(refs, seen, raw, line) {
|
|
65
|
+
const value = normalizeToken(raw);
|
|
66
|
+
if (!value)
|
|
67
|
+
return;
|
|
68
|
+
const kind = classifyReference(value);
|
|
69
|
+
if (!kind)
|
|
70
|
+
return;
|
|
71
|
+
const key = `${kind}:${value}:${line}`;
|
|
72
|
+
if (seen.has(key))
|
|
73
|
+
return;
|
|
74
|
+
seen.add(key);
|
|
75
|
+
refs.push({ kind, value, line });
|
|
76
|
+
}
|
|
77
|
+
function extractDriftReferences(markdown) {
|
|
78
|
+
const refs = [];
|
|
79
|
+
const seen = new Set;
|
|
80
|
+
const lines = stripFenceLines(markdown);
|
|
81
|
+
for (const [index, line] of lines.entries()) {
|
|
82
|
+
const lineNumber = index + 1;
|
|
83
|
+
for (const match of line.matchAll(INLINE_CODE)) {
|
|
84
|
+
pushReference(refs, seen, match[1] ?? "", lineNumber);
|
|
85
|
+
}
|
|
86
|
+
for (const match of line.matchAll(MARKDOWN_LINK)) {
|
|
87
|
+
pushReference(refs, seen, match[1] ?? "", lineNumber);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return refs;
|
|
91
|
+
}
|
|
92
|
+
var INLINE_CODE, MARKDOWN_LINK, SYMBOL_REF, PATH_REF;
|
|
93
|
+
var init_extract_refs = __esm(() => {
|
|
94
|
+
INLINE_CODE = /`([^`\n]+)`/g;
|
|
95
|
+
MARKDOWN_LINK = /\[[^\]]+\]\(([^)\s]+)\)/g;
|
|
96
|
+
SYMBOL_REF = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)?$/;
|
|
97
|
+
PATH_REF = /^(?:\.\.?\/)?(?:[A-Za-z0-9_.-]+\/)+[A-Za-z0-9_.-]+$|^[A-Za-z0-9_.-]+\.(?:ts|tsx|js|jsx|mjs|cjs|json|md|mdx|css|scss|html|yml|yaml|toml|rs|go|py|rb|java|kt|swift|c|cc|cpp|h|hpp)$/;
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// packages/standard-plugin/src/drift/git-adapter.ts
|
|
101
|
+
import { execFile } from "child_process";
|
|
102
|
+
import { promisify } from "util";
|
|
103
|
+
function processError(value) {
|
|
104
|
+
return value && typeof value === "object" ? value : null;
|
|
105
|
+
}
|
|
106
|
+
function lineCount(output) {
|
|
107
|
+
const trimmed = output.trim();
|
|
108
|
+
return trimmed ? trimmed.split(/\r?\n/).length : 0;
|
|
109
|
+
}
|
|
110
|
+
function makeDriftGit(projectRoot) {
|
|
111
|
+
async function git(args) {
|
|
112
|
+
const result = await execFileAsync("git", [...args], {
|
|
113
|
+
cwd: projectRoot,
|
|
114
|
+
encoding: "utf8",
|
|
115
|
+
maxBuffer: 10 * 1024 * 1024
|
|
116
|
+
});
|
|
117
|
+
return String(result.stdout);
|
|
118
|
+
}
|
|
119
|
+
async function grepCountAt(symbolOrPath, commit) {
|
|
120
|
+
try {
|
|
121
|
+
return lineCount(await git(["grep", "-F", "-n", "-e", symbolOrPath, commit, "--"]));
|
|
122
|
+
} catch (error) {
|
|
123
|
+
const detail = processError(error);
|
|
124
|
+
if (detail?.code === 1)
|
|
125
|
+
return 0;
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
async lastCommitTouching(path) {
|
|
131
|
+
const commit = (await git(["log", "-n", "1", "--format=%H", "--", path])).trim();
|
|
132
|
+
return commit || "HEAD";
|
|
133
|
+
},
|
|
134
|
+
async grepCount(symbolOrPath) {
|
|
135
|
+
return grepCountAt(symbolOrPath, "HEAD");
|
|
136
|
+
},
|
|
137
|
+
async grepCountAtCommit(symbolOrPath, commit) {
|
|
138
|
+
return grepCountAt(symbolOrPath, commit);
|
|
139
|
+
},
|
|
140
|
+
async wasRenamed(symbolOrPath, sinceCommit) {
|
|
141
|
+
if (!symbolOrPath.includes("/") && !symbolOrPath.includes("."))
|
|
142
|
+
return false;
|
|
143
|
+
try {
|
|
144
|
+
const output = await git(["log", "--name-status", "--format=", `${sinceCommit}..HEAD`]);
|
|
145
|
+
return output.split(/\r?\n/).some((line) => {
|
|
146
|
+
const match = line.match(/^R\d*\s+(.+?)\s+(.+)$/);
|
|
147
|
+
return Boolean(match && (match[1] === symbolOrPath || match[2] === symbolOrPath));
|
|
148
|
+
});
|
|
149
|
+
} catch (error) {
|
|
150
|
+
const detail = processError(error);
|
|
151
|
+
if (detail?.code === 128)
|
|
152
|
+
return false;
|
|
153
|
+
throw error;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
var execFileAsync;
|
|
159
|
+
var init_git_adapter = __esm(() => {
|
|
160
|
+
execFileAsync = promisify(execFile);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// packages/standard-plugin/src/drift/detect.ts
|
|
164
|
+
var exports_detect = {};
|
|
165
|
+
__export(exports_detect, {
|
|
166
|
+
detectStaleAnchors: () => detectStaleAnchors,
|
|
167
|
+
detectDrift: () => detectDrift,
|
|
168
|
+
detectDeletedReferences: () => detectDeletedReferences
|
|
169
|
+
});
|
|
170
|
+
import { existsSync as existsSync3 } from "fs";
|
|
171
|
+
import { readdir, readFile, stat } from "fs/promises";
|
|
172
|
+
import { basename as basename2, extname, relative, resolve as resolve3 } from "path";
|
|
173
|
+
function globLikeMatch(path, pattern) {
|
|
174
|
+
if (pattern === path)
|
|
175
|
+
return true;
|
|
176
|
+
if (pattern.startsWith("**/*"))
|
|
177
|
+
return path.endsWith(pattern.slice(4));
|
|
178
|
+
if (pattern.endsWith("/**"))
|
|
179
|
+
return path.startsWith(pattern.slice(0, -3));
|
|
180
|
+
if (pattern.includes("*")) {
|
|
181
|
+
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
182
|
+
return new RegExp(`^${escaped}$`).test(path);
|
|
183
|
+
}
|
|
184
|
+
return path.startsWith(pattern);
|
|
185
|
+
}
|
|
186
|
+
function isDefaultDoc(path) {
|
|
187
|
+
const lower = basename2(path).toLowerCase();
|
|
188
|
+
return (path.endsWith(".md") || path.endsWith(".mdx")) && !lower.startsWith("changelog") && !lower.includes("generated");
|
|
189
|
+
}
|
|
190
|
+
function isIgnored(path, patterns) {
|
|
191
|
+
return (patterns ?? []).some((pattern) => globLikeMatch(path, pattern));
|
|
192
|
+
}
|
|
193
|
+
async function collectFiles(root, options) {
|
|
194
|
+
const files = [];
|
|
195
|
+
async function visit(dir) {
|
|
196
|
+
for (const entry of await readdir(dir, { withFileTypes: true })) {
|
|
197
|
+
if (entry.isDirectory() && DEFAULT_IGNORED_DIRS[entry.name])
|
|
198
|
+
continue;
|
|
199
|
+
const absolute = resolve3(dir, entry.name);
|
|
200
|
+
const rel = relative(root, absolute).replace(/\\/g, "/");
|
|
201
|
+
if (isIgnored(rel, options.ignore))
|
|
202
|
+
continue;
|
|
203
|
+
if (entry.isDirectory()) {
|
|
204
|
+
await visit(absolute);
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
if (!entry.isFile())
|
|
208
|
+
continue;
|
|
209
|
+
if (options.docs) {
|
|
210
|
+
const matchesConfigured = options.patterns && options.patterns.length > 0 ? options.patterns.some((pattern) => globLikeMatch(rel, pattern)) : isDefaultDoc(rel);
|
|
211
|
+
if (matchesConfigured)
|
|
212
|
+
files.push(rel);
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
if (SOURCE_EXTENSIONS[extname(entry.name)])
|
|
216
|
+
files.push(rel);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
await visit(root);
|
|
220
|
+
return files.sort();
|
|
221
|
+
}
|
|
222
|
+
async function sourceReferenceCount(projectRoot, reference, docPath) {
|
|
223
|
+
if (reference.kind === "path")
|
|
224
|
+
return existsSync3(resolve3(projectRoot, reference.value)) ? 1 : 0;
|
|
225
|
+
let count = 0;
|
|
226
|
+
const sourceFiles = await collectFiles(projectRoot, { docs: false });
|
|
227
|
+
for (const sourceFile of sourceFiles) {
|
|
228
|
+
if (sourceFile === docPath)
|
|
229
|
+
continue;
|
|
230
|
+
const text = await readFile(resolve3(projectRoot, sourceFile), "utf8").catch(() => "");
|
|
231
|
+
if (text.includes(reference.value))
|
|
232
|
+
count += 1;
|
|
233
|
+
}
|
|
234
|
+
return count;
|
|
235
|
+
}
|
|
236
|
+
function deletedReferenceFinding(docPath, reference) {
|
|
237
|
+
return {
|
|
238
|
+
kind: "deleted-reference",
|
|
239
|
+
docPath,
|
|
240
|
+
line: reference.line,
|
|
241
|
+
reference: reference.value,
|
|
242
|
+
detail: `Documented reference "${reference.value}" no longer exists in the source tree.`,
|
|
243
|
+
confidence: "high"
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
function staleAnchorFinding(docPath, reference) {
|
|
247
|
+
return {
|
|
248
|
+
kind: "stale-anchor",
|
|
249
|
+
docPath,
|
|
250
|
+
line: reference.line,
|
|
251
|
+
reference: reference.value,
|
|
252
|
+
detail: `Documented path "${reference.value}" changed after this doc was last updated.`,
|
|
253
|
+
confidence: "medium"
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
async function detectDeletedReferences(projectRoot, docPath, git = makeDriftGit(projectRoot)) {
|
|
257
|
+
const markdown = await readFile(resolve3(projectRoot, docPath), "utf8");
|
|
258
|
+
const docCommit = await git.lastCommitTouching(docPath);
|
|
259
|
+
const findings = [];
|
|
260
|
+
for (const reference of extractDriftReferences(markdown)) {
|
|
261
|
+
if (await sourceReferenceCount(projectRoot, reference, docPath) > 0)
|
|
262
|
+
continue;
|
|
263
|
+
if (await git.wasRenamed(reference.value, docCommit))
|
|
264
|
+
continue;
|
|
265
|
+
findings.push(deletedReferenceFinding(docPath, reference));
|
|
266
|
+
}
|
|
267
|
+
return findings;
|
|
268
|
+
}
|
|
269
|
+
async function detectStaleAnchors(projectRoot, docPath, git = makeDriftGit(projectRoot)) {
|
|
270
|
+
const markdown = await readFile(resolve3(projectRoot, docPath), "utf8");
|
|
271
|
+
const docCommit = await git.lastCommitTouching(docPath);
|
|
272
|
+
const findings = [];
|
|
273
|
+
for (const reference of extractDriftReferences(markdown).filter((ref) => ref.kind === "path")) {
|
|
274
|
+
if (!existsSync3(resolve3(projectRoot, reference.value)))
|
|
275
|
+
continue;
|
|
276
|
+
const sourceStat = await stat(resolve3(projectRoot, reference.value)).catch(() => null);
|
|
277
|
+
if (!sourceStat?.isFile())
|
|
278
|
+
continue;
|
|
279
|
+
const sourceCommit = await git.lastCommitTouching(reference.value);
|
|
280
|
+
if (sourceCommit !== docCommit && !await git.wasRenamed(reference.value, docCommit)) {
|
|
281
|
+
findings.push(staleAnchorFinding(docPath, reference));
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return findings;
|
|
285
|
+
}
|
|
286
|
+
async function detectDrift(options) {
|
|
287
|
+
const git = options.git ?? makeDriftGit(options.projectRoot);
|
|
288
|
+
const docs = await collectFiles(options.projectRoot, {
|
|
289
|
+
docs: true,
|
|
290
|
+
...options.docsGlobs !== undefined ? { patterns: options.docsGlobs } : {},
|
|
291
|
+
...options.ignoreGlobs !== undefined ? { ignore: options.ignoreGlobs } : {}
|
|
292
|
+
});
|
|
293
|
+
const findings = [];
|
|
294
|
+
let degraded = false;
|
|
295
|
+
for (const docPath of docs) {
|
|
296
|
+
try {
|
|
297
|
+
findings.push(...await detectDeletedReferences(options.projectRoot, docPath, git));
|
|
298
|
+
findings.push(...await detectStaleAnchors(options.projectRoot, docPath, git));
|
|
299
|
+
} catch {
|
|
300
|
+
degraded = true;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return {
|
|
304
|
+
generatedAt: new Date().toISOString(),
|
|
305
|
+
scanned: docs.length,
|
|
306
|
+
degraded,
|
|
307
|
+
findings
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
var DEFAULT_IGNORED_DIRS, SOURCE_EXTENSIONS;
|
|
311
|
+
var init_detect = __esm(() => {
|
|
312
|
+
init_extract_refs();
|
|
313
|
+
init_git_adapter();
|
|
314
|
+
DEFAULT_IGNORED_DIRS = {
|
|
315
|
+
".git": true,
|
|
316
|
+
node_modules: true,
|
|
317
|
+
dist: true,
|
|
318
|
+
build: true,
|
|
319
|
+
coverage: true,
|
|
320
|
+
".next": true,
|
|
321
|
+
vendor: true
|
|
322
|
+
};
|
|
323
|
+
SOURCE_EXTENSIONS = {
|
|
324
|
+
".ts": true,
|
|
325
|
+
".tsx": true,
|
|
326
|
+
".js": true,
|
|
327
|
+
".jsx": true,
|
|
328
|
+
".mjs": true,
|
|
329
|
+
".cjs": true,
|
|
330
|
+
".rs": true,
|
|
331
|
+
".go": true,
|
|
332
|
+
".py": true,
|
|
333
|
+
".rb": true,
|
|
334
|
+
".java": true,
|
|
335
|
+
".kt": true,
|
|
336
|
+
".swift": true,
|
|
337
|
+
".c": true,
|
|
338
|
+
".cc": true,
|
|
339
|
+
".cpp": true,
|
|
340
|
+
".h": true,
|
|
341
|
+
".hpp": true,
|
|
342
|
+
".json": true,
|
|
343
|
+
".toml": true,
|
|
344
|
+
".yml": true,
|
|
345
|
+
".yaml": true
|
|
346
|
+
};
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
// packages/standard-plugin/src/drift/plugin.ts
|
|
350
|
+
var exports_plugin = {};
|
|
351
|
+
__export(exports_plugin, {
|
|
352
|
+
runDriftCli: () => runDriftCli,
|
|
353
|
+
runDocsDriftValidation: () => runDocsDriftValidation,
|
|
354
|
+
highConfidenceDriftFindings: () => highConfidenceDriftFindings,
|
|
355
|
+
executeDrift: () => executeDrift,
|
|
356
|
+
driftGateResult: () => driftGateResult,
|
|
357
|
+
createDocsDriftValidator: () => createDocsDriftValidator,
|
|
358
|
+
createDocsDriftRuntimeCliCommand: () => createDocsDriftRuntimeCliCommand,
|
|
359
|
+
createDocsDriftGateStage: () => createDocsDriftGateStage,
|
|
360
|
+
DOCS_DRIFT_VALIDATOR_ID: () => DOCS_DRIFT_VALIDATOR_ID,
|
|
361
|
+
DOCS_DRIFT_VALIDATOR: () => DOCS_DRIFT_VALIDATOR,
|
|
362
|
+
DOCS_DRIFT_STAGE_MUTATION: () => DOCS_DRIFT_STAGE_MUTATION,
|
|
363
|
+
DOCS_DRIFT_STAGE_ID: () => DOCS_DRIFT_STAGE_ID,
|
|
364
|
+
DOCS_DRIFT_RUNTIME_CLI_COMMAND: () => DOCS_DRIFT_RUNTIME_CLI_COMMAND,
|
|
365
|
+
DOCS_DRIFT_CLI_ID: () => DOCS_DRIFT_CLI_ID,
|
|
366
|
+
DOCS_DRIFT_CLI_COMMAND: () => DOCS_DRIFT_CLI_COMMAND,
|
|
367
|
+
DOCS_DRIFT_CAPABILITY_ID: () => DOCS_DRIFT_CAPABILITY_ID
|
|
368
|
+
});
|
|
369
|
+
function highConfidenceDriftFindings(report) {
|
|
370
|
+
return report.findings.filter((finding) => finding.confidence === "high");
|
|
371
|
+
}
|
|
372
|
+
function driftGateResult(report, mode = "enforce") {
|
|
373
|
+
const high = highConfidenceDriftFindings(report);
|
|
374
|
+
if (mode === "enforce" && high.length > 0) {
|
|
375
|
+
return { kind: "block", reason: `${high.length} high-confidence documentation drift finding(s).` };
|
|
376
|
+
}
|
|
377
|
+
return { kind: "allow" };
|
|
378
|
+
}
|
|
379
|
+
function createDocsDriftGateStage(options = {}) {
|
|
380
|
+
return async (ctx) => {
|
|
381
|
+
const projectRoot = typeof ctx.metadata?.projectRoot === "string" ? ctx.metadata.projectRoot : process.cwd();
|
|
382
|
+
const report = await detectDrift({
|
|
383
|
+
projectRoot,
|
|
384
|
+
...options.docsGlobs !== undefined ? { docsGlobs: options.docsGlobs } : {},
|
|
385
|
+
...options.ignoreGlobs !== undefined ? { ignoreGlobs: options.ignoreGlobs } : {}
|
|
386
|
+
});
|
|
387
|
+
return driftGateResult(report, options.failOnDrift ? "enforce" : "observe");
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
async function runDocsDriftValidation(options) {
|
|
391
|
+
const report = await detectDrift(options);
|
|
392
|
+
const high = highConfidenceDriftFindings(report);
|
|
393
|
+
const passed = options.failOnDrift ? high.length === 0 : true;
|
|
394
|
+
const findingWord = report.findings.length === 1 ? "finding" : "findings";
|
|
395
|
+
return {
|
|
396
|
+
id: DOCS_DRIFT_VALIDATOR_ID,
|
|
397
|
+
passed,
|
|
398
|
+
summary: `docs drift scanned ${report.scanned} doc(s), ${report.findings.length} ${findingWord}`,
|
|
399
|
+
details: JSON.stringify(report)
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
function createDocsDriftValidator(options = {}) {
|
|
403
|
+
return {
|
|
404
|
+
...DOCS_DRIFT_VALIDATOR,
|
|
405
|
+
async run(ctx) {
|
|
406
|
+
return runDocsDriftValidation({
|
|
407
|
+
projectRoot: ctx.workspaceRoot,
|
|
408
|
+
...options.docsGlobs !== undefined ? { docsGlobs: options.docsGlobs } : {},
|
|
409
|
+
...options.ignoreGlobs !== undefined ? { ignoreGlobs: options.ignoreGlobs } : {},
|
|
410
|
+
...options.failOnDrift !== undefined ? { failOnDrift: options.failOnDrift } : {}
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
function takeOptionValue(args, index, flag) {
|
|
416
|
+
const value = args[index + 1];
|
|
417
|
+
if (!value)
|
|
418
|
+
throw new Error(`${flag} requires a value`);
|
|
419
|
+
return value;
|
|
420
|
+
}
|
|
421
|
+
function takeFlag(args, flag) {
|
|
422
|
+
const rest = [...args];
|
|
423
|
+
const index = rest.indexOf(flag);
|
|
424
|
+
if (index < 0)
|
|
425
|
+
return { value: false, rest };
|
|
426
|
+
rest.splice(index, 1);
|
|
427
|
+
return { value: true, rest };
|
|
428
|
+
}
|
|
429
|
+
function takeOption(args, flag) {
|
|
430
|
+
const rest = [...args];
|
|
431
|
+
const index = rest.indexOf(flag);
|
|
432
|
+
if (index < 0)
|
|
433
|
+
return { rest };
|
|
434
|
+
const value = rest[index + 1];
|
|
435
|
+
if (!value || value.startsWith("-"))
|
|
436
|
+
throw new Error(`${flag} requires a value.`);
|
|
437
|
+
rest.splice(index, 2);
|
|
438
|
+
return { value, rest };
|
|
439
|
+
}
|
|
440
|
+
function requireNoExtraArgs(args, usage) {
|
|
441
|
+
if (args.length > 0)
|
|
442
|
+
throw new Error(`Unexpected argument: ${args[0]}
|
|
443
|
+
Usage: ${usage}`);
|
|
444
|
+
}
|
|
445
|
+
function parseCsv(value) {
|
|
446
|
+
return value?.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0) ?? [];
|
|
447
|
+
}
|
|
448
|
+
function driftSummary(report) {
|
|
449
|
+
const highConfidence = highConfidenceDriftFindings(report).length;
|
|
450
|
+
return { total: report.findings.length, highConfidence, degraded: report.degraded };
|
|
451
|
+
}
|
|
452
|
+
async function executeDrift(context, args, options = {}) {
|
|
453
|
+
const json = takeFlag(args, "--json");
|
|
454
|
+
const docs = takeOption(json.rest, "--docs");
|
|
455
|
+
const ignore = takeOption(docs.rest, "--ignore");
|
|
456
|
+
const failOnDrift = takeFlag(ignore.rest, "--fail-on-drift");
|
|
457
|
+
requireNoExtraArgs(failOnDrift.rest, "rig drift [--docs <csv>] [--ignore <csv>] [--fail-on-drift] [--json]");
|
|
458
|
+
const docsGlobs = parseCsv(docs.value);
|
|
459
|
+
const ignoreGlobs = parseCsv(ignore.value);
|
|
460
|
+
const effectiveDocsGlobs = docsGlobs.length > 0 ? docsGlobs : options.docsGlobs;
|
|
461
|
+
const effectiveIgnoreGlobs = ignoreGlobs.length > 0 ? ignoreGlobs : options.ignoreGlobs;
|
|
462
|
+
const effectiveFailOnDrift = failOnDrift.value || options.failOnDrift === true;
|
|
463
|
+
const report = await detectDrift({
|
|
464
|
+
projectRoot: context.projectRoot,
|
|
465
|
+
...effectiveDocsGlobs !== undefined ? { docsGlobs: effectiveDocsGlobs } : {},
|
|
466
|
+
...effectiveIgnoreGlobs !== undefined ? { ignoreGlobs: effectiveIgnoreGlobs } : {}
|
|
467
|
+
});
|
|
468
|
+
const failed = effectiveFailOnDrift && highConfidenceDriftFindings(report).length > 0;
|
|
469
|
+
const details = { report, summary: driftSummary(report), failOnDrift: effectiveFailOnDrift, failed };
|
|
470
|
+
if (context.outputMode === "text") {
|
|
471
|
+
if (json.value)
|
|
472
|
+
console.log(JSON.stringify(details, null, 2));
|
|
473
|
+
else
|
|
474
|
+
console.log(report.findings.length === 0 ? `No drift findings across ${report.scanned} documents.` : report.findings.map((finding) => `${finding.docPath}:${finding.line ?? "?"} ${finding.kind} ${finding.confidence} ${finding.detail}`).join(`
|
|
475
|
+
`));
|
|
476
|
+
}
|
|
477
|
+
return { ok: !failed, group: "drift", command: "scan", details };
|
|
478
|
+
}
|
|
479
|
+
function createDocsDriftRuntimeCliCommand(options = {}) {
|
|
480
|
+
return {
|
|
481
|
+
id: DOCS_DRIFT_CLI_ID,
|
|
482
|
+
family: "drift",
|
|
483
|
+
command: DOCS_DRIFT_CLI_COMMAND,
|
|
484
|
+
description: "Scan documentation for stale code references.",
|
|
485
|
+
usage: DOCS_DRIFT_CLI_COMMAND,
|
|
486
|
+
projectRequired: true,
|
|
487
|
+
run: (context, args) => executeDrift(context, args, options)
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
async function runDriftCli(args, options = {}) {
|
|
491
|
+
const docsGlobs = [];
|
|
492
|
+
const ignoreGlobs = [];
|
|
493
|
+
let json = false;
|
|
494
|
+
let failOnDrift = false;
|
|
495
|
+
for (let index = 0;index < args.length; index += 1) {
|
|
496
|
+
const arg = args[index];
|
|
497
|
+
if (arg === "--json") {
|
|
498
|
+
json = true;
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
if (arg === "--fail-on-drift") {
|
|
502
|
+
failOnDrift = true;
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
if (arg === "--docs") {
|
|
506
|
+
docsGlobs.push(takeOptionValue(args, index, arg));
|
|
507
|
+
index += 1;
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
if (arg === "--ignore") {
|
|
511
|
+
ignoreGlobs.push(takeOptionValue(args, index, arg));
|
|
512
|
+
index += 1;
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
515
|
+
throw new Error(`Unknown rig drift argument: ${arg}`);
|
|
516
|
+
}
|
|
517
|
+
const report = await detectDrift({
|
|
518
|
+
projectRoot: options.projectRoot ?? process.cwd(),
|
|
519
|
+
...docsGlobs.length > 0 ? { docsGlobs } : {},
|
|
520
|
+
...ignoreGlobs.length > 0 ? { ignoreGlobs } : {}
|
|
521
|
+
});
|
|
522
|
+
const write = options.write ?? ((message) => console.log(message));
|
|
523
|
+
if (json) {
|
|
524
|
+
write(JSON.stringify(report));
|
|
525
|
+
} else {
|
|
526
|
+
write(`Scanned ${report.scanned} doc(s); ${report.findings.length} drift finding(s).`);
|
|
527
|
+
for (const finding of report.findings) {
|
|
528
|
+
write(`${finding.confidence.toUpperCase()} ${finding.kind} ${finding.docPath}${finding.line ? `:${finding.line}` : ""} ${finding.reference ?? ""} \u2014 ${finding.detail}`);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
const high = highConfidenceDriftFindings(report);
|
|
532
|
+
if (failOnDrift && high.length > 0) {
|
|
533
|
+
options.writeError?.(`${high.length} high-confidence drift finding(s).`);
|
|
534
|
+
return 2;
|
|
535
|
+
}
|
|
536
|
+
return 0;
|
|
537
|
+
}
|
|
538
|
+
var DOCS_DRIFT_RUNTIME_CLI_COMMAND;
|
|
539
|
+
var init_plugin = __esm(() => {
|
|
540
|
+
init_detect();
|
|
541
|
+
init_metadata();
|
|
542
|
+
init_metadata();
|
|
543
|
+
DOCS_DRIFT_RUNTIME_CLI_COMMAND = createDocsDriftRuntimeCliCommand();
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
// packages/standard-plugin/src/bundle.ts
|
|
547
|
+
import { createBlockerClassifierPlugin } from "@rig/blocker-classifier-plugin/plugin";
|
|
548
|
+
import { createDefaultLifecyclePlugin } from "@rig/bundle-default-lifecycle/plugin";
|
|
549
|
+
import { createDependencyGraphPlugin } from "@rig/dependency-graph-plugin/plugin";
|
|
550
|
+
import { createPlanningPlugin } from "@rig/planning-plugin/plugin";
|
|
551
|
+
import { createSupervisorPlugin } from "@rig/supervisor-plugin/plugin";
|
|
552
|
+
|
|
553
|
+
// packages/standard-plugin/src/run-worker-panels.ts
|
|
554
|
+
var RIG_RUN_STOP_PANEL_ACTION = "rig-run:stop";
|
|
555
|
+
var RIG_CAPABILITY_PANEL_SLOT = "capability";
|
|
556
|
+
var RIG_SUPERVISOR_PANEL_ID = "supervisor";
|
|
557
|
+
var RUN_SUPERVISOR_PANEL_REGISTRATION = {
|
|
558
|
+
id: RIG_SUPERVISOR_PANEL_ID,
|
|
559
|
+
slot: RIG_CAPABILITY_PANEL_SLOT,
|
|
560
|
+
title: "Supervisor",
|
|
561
|
+
capabilityId: "run.supervisor",
|
|
562
|
+
description: "Live run status, closeout progress, and operator stop control."
|
|
563
|
+
};
|
|
564
|
+
function buildSupervisorPanelPayload(context) {
|
|
565
|
+
const status = context.folded.status ?? "unknown";
|
|
566
|
+
const taskId = context.folded.record.taskId ?? context.taskIdAtStart;
|
|
567
|
+
const operatorActive = status === "running" || status === "validating" || status === "closing-out" || status === "needs-attention";
|
|
568
|
+
return {
|
|
569
|
+
status,
|
|
570
|
+
currentTask: taskId ? { id: taskId, title: context.runDisplayTitle } : null,
|
|
571
|
+
processed: context.folded.closeoutPhases.length,
|
|
572
|
+
succeeded: context.folded.closeoutPhases.filter((phase) => phase.outcome === "completed").length,
|
|
573
|
+
failed: context.folded.closeoutPhases.filter((phase) => phase.outcome === "failed").length,
|
|
574
|
+
skipped: 0,
|
|
575
|
+
plannedOrder: taskId ? [{ id: taskId, title: context.runDisplayTitle, status }] : [],
|
|
576
|
+
idleReason: operatorActive ? null : status,
|
|
577
|
+
stopActionId: operatorActive ? RIG_RUN_STOP_PANEL_ACTION : null,
|
|
578
|
+
closures: []
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
var RUN_SUPERVISOR_PANEL_PRODUCER = {
|
|
582
|
+
...RUN_SUPERVISOR_PANEL_REGISTRATION,
|
|
583
|
+
produce(context) {
|
|
584
|
+
return buildSupervisorPanelPayload(context);
|
|
585
|
+
}
|
|
586
|
+
};
|
|
587
|
+
var RUN_WORKER_PANEL_PLUGIN = {
|
|
588
|
+
name: "@rig/standard-plugin:run-worker-panels",
|
|
589
|
+
version: "0.0.0-alpha.1",
|
|
590
|
+
contributes: {
|
|
591
|
+
panels: [RUN_SUPERVISOR_PANEL_REGISTRATION]
|
|
592
|
+
},
|
|
593
|
+
__runtime: {
|
|
594
|
+
panels: [RUN_SUPERVISOR_PANEL_PRODUCER]
|
|
595
|
+
}
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
// packages/standard-plugin/src/cli-surface.ts
|
|
599
|
+
import {
|
|
600
|
+
STANDARD_CLI_SURFACE_PLUGIN_NAME,
|
|
601
|
+
createStandardCliSurfacePlugin,
|
|
602
|
+
standardCliSurfacePlugin
|
|
603
|
+
} from "@rig/cli-surface-plugin/plugin";
|
|
604
|
+
|
|
605
|
+
// packages/standard-plugin/src/plugin.ts
|
|
606
|
+
import { resolve as resolve4 } from "path";
|
|
607
|
+
import { definePlugin } from "@rig/core/config";
|
|
4
608
|
|
|
5
609
|
// packages/standard-plugin/src/github-issues-source.ts
|
|
6
610
|
import { spawnSync } from "child_process";
|
|
@@ -16,7 +620,7 @@ function createEnvGitHubCredentialProvider() {
|
|
|
16
620
|
if (input.purpose === "selected-repo") {
|
|
17
621
|
return { token: cleanToken(process.env.RIG_GITHUB_SELECTED_TOKEN ?? null) ?? "", source: "signed-in-user" };
|
|
18
622
|
}
|
|
19
|
-
const token = cleanToken(process.env.GH_TOKEN ?? process.env.GITHUB_TOKEN ?? null);
|
|
623
|
+
const token = cleanToken(process.env.RIG_GITHUB_TOKEN ?? process.env.GH_TOKEN ?? process.env.GITHUB_TOKEN ?? null);
|
|
20
624
|
if (!token) {
|
|
21
625
|
throw new Error("No host GitHub token is configured for admin fallback.");
|
|
22
626
|
}
|
|
@@ -25,34 +629,60 @@ function createEnvGitHubCredentialProvider() {
|
|
|
25
629
|
};
|
|
26
630
|
}
|
|
27
631
|
function createStateGitHubCredentialProvider(options = {}) {
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
if (
|
|
31
|
-
return
|
|
32
|
-
const
|
|
33
|
-
|
|
632
|
+
const addCandidate = (candidates, path) => {
|
|
633
|
+
const trimmed = path?.trim();
|
|
634
|
+
if (!trimmed)
|
|
635
|
+
return;
|
|
636
|
+
const resolved = resolve(trimmed);
|
|
637
|
+
if (!candidates.includes(resolved))
|
|
638
|
+
candidates.push(resolved);
|
|
639
|
+
};
|
|
640
|
+
const addStateDir = (candidates, dir) => {
|
|
641
|
+
const trimmed = dir?.trim();
|
|
642
|
+
if (!trimmed)
|
|
643
|
+
return;
|
|
644
|
+
addCandidate(candidates, resolve(trimmed, "github-auth.json"));
|
|
645
|
+
};
|
|
646
|
+
const addProjectStateDir = (candidates, root) => {
|
|
647
|
+
const trimmed = root?.trim();
|
|
648
|
+
if (!trimmed)
|
|
649
|
+
return;
|
|
650
|
+
addStateDir(candidates, resolve(trimmed, ".rig", "state"));
|
|
651
|
+
};
|
|
652
|
+
const stateFileCandidates = () => {
|
|
653
|
+
const candidates = [];
|
|
654
|
+
addCandidate(candidates, options.stateFile ?? process.env.RIG_GITHUB_AUTH_STATE_FILE);
|
|
655
|
+
addStateDir(candidates, options.stateDir);
|
|
656
|
+
addStateDir(candidates, process.env.RIG_STATE_DIR);
|
|
657
|
+
addProjectStateDir(candidates, process.env.PROJECT_RIG_ROOT);
|
|
658
|
+
addProjectStateDir(candidates, process.env.RIG_PROJECT_ROOT);
|
|
659
|
+
addProjectStateDir(candidates, process.env.RIG_HOST_PROJECT_ROOT);
|
|
660
|
+
addProjectStateDir(candidates, process.cwd());
|
|
661
|
+
return candidates;
|
|
34
662
|
};
|
|
35
663
|
const readToken = () => {
|
|
36
|
-
const stateFile
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
664
|
+
for (const stateFile of stateFileCandidates()) {
|
|
665
|
+
if (!existsSync(stateFile))
|
|
666
|
+
continue;
|
|
667
|
+
try {
|
|
668
|
+
const parsed = JSON.parse(readFileSync(stateFile, "utf8"));
|
|
669
|
+
const token = typeof parsed.token === "string" ? cleanToken(parsed.token) : null;
|
|
670
|
+
if (token)
|
|
671
|
+
return token;
|
|
672
|
+
} catch {}
|
|
44
673
|
}
|
|
674
|
+
return null;
|
|
45
675
|
};
|
|
46
676
|
return {
|
|
47
677
|
async resolveGitHubToken(input) {
|
|
48
678
|
const token = readToken();
|
|
49
679
|
if (input.purpose === "selected-repo") {
|
|
50
|
-
return { token: token ?? "", source: "signed-in-user" };
|
|
680
|
+
return { token: token ?? cleanToken(process.env.RIG_GITHUB_SELECTED_TOKEN ?? null) ?? "", source: "signed-in-user" };
|
|
51
681
|
}
|
|
52
682
|
if (token) {
|
|
53
683
|
return { token, source: "signed-in-user" };
|
|
54
684
|
}
|
|
55
|
-
const fallback = cleanToken(process.env.GH_TOKEN ?? process.env.GITHUB_TOKEN ?? null);
|
|
685
|
+
const fallback = cleanToken(process.env.RIG_GITHUB_TOKEN ?? process.env.GH_TOKEN ?? process.env.GITHUB_TOKEN ?? null);
|
|
56
686
|
if (!fallback) {
|
|
57
687
|
throw new Error("No signed-in GitHub token is stored for Rig and no host admin fallback token is configured.");
|
|
58
688
|
}
|
|
@@ -86,17 +716,57 @@ function statusFor(issue) {
|
|
|
86
716
|
return "cancelled";
|
|
87
717
|
return "open";
|
|
88
718
|
}
|
|
89
|
-
function
|
|
90
|
-
const
|
|
91
|
-
|
|
719
|
+
function parseIssueRefs(raw) {
|
|
720
|
+
const refs = [...raw.matchAll(/(?:^|[^\w/.-])(?:[\w.-]+\/[\w.-]+#|#)?(\d+)\b/g)].map((match) => match[1]).filter((value) => Boolean(value));
|
|
721
|
+
return [...new Set(refs)];
|
|
722
|
+
}
|
|
723
|
+
function metadataKeyPattern(keys) {
|
|
724
|
+
return new RegExp(`^(?:${keys.map((key) => key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")}):\\s*(.*)$`, "i");
|
|
725
|
+
}
|
|
726
|
+
function parseMetadataList(body, keys) {
|
|
727
|
+
const block = body.match(/<!-- rig:metadata:start -->\s*([\s\S]*?)\s*<!-- rig:metadata:end -->/);
|
|
728
|
+
if (!block)
|
|
92
729
|
return [];
|
|
93
|
-
|
|
730
|
+
const lines = block[1].split(/\r?\n/);
|
|
731
|
+
const values = [];
|
|
732
|
+
const keyPattern = metadataKeyPattern(keys);
|
|
733
|
+
for (let index = 0;index < lines.length; index += 1) {
|
|
734
|
+
const line = lines[index];
|
|
735
|
+
const sameLine = line.match(keyPattern);
|
|
736
|
+
if (!sameLine)
|
|
737
|
+
continue;
|
|
738
|
+
const inlineValue = sameLine[1]?.trim() ?? "";
|
|
739
|
+
if (inlineValue) {
|
|
740
|
+
values.push(...parseIssueRefs(inlineValue));
|
|
741
|
+
continue;
|
|
742
|
+
}
|
|
743
|
+
for (let cursor = index + 1;cursor < lines.length; cursor += 1) {
|
|
744
|
+
const item = lines[cursor].match(/^\s*-\s*(.+)$/);
|
|
745
|
+
if (!item)
|
|
746
|
+
break;
|
|
747
|
+
values.push(...parseIssueRefs(item[1]));
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
return [...new Set(values)];
|
|
751
|
+
}
|
|
752
|
+
function bodyWithoutRigMetadataBlock(body) {
|
|
753
|
+
return body.replace(/<!-- rig:metadata:start -->\s*[\s\S]*?\s*<!-- rig:metadata:end -->/g, "");
|
|
754
|
+
}
|
|
755
|
+
function parseBodyKeyRefs(body, keys) {
|
|
756
|
+
const keyPattern = metadataKeyPattern(keys);
|
|
757
|
+
const values = bodyWithoutRigMetadataBlock(body).split(/\r?\n/).flatMap((line) => {
|
|
758
|
+
const match = line.match(keyPattern);
|
|
759
|
+
return match?.[1] ? parseIssueRefs(match[1]) : [];
|
|
760
|
+
});
|
|
761
|
+
return [...new Set(values)];
|
|
762
|
+
}
|
|
763
|
+
function parseDeps(body) {
|
|
764
|
+
const keys = ["depends-on", "deps", "blocked-by", "blocked_by"];
|
|
765
|
+
return [...new Set([...parseBodyKeyRefs(body, keys), ...parseMetadataList(body, keys)])];
|
|
94
766
|
}
|
|
95
767
|
function parseParents(body) {
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
return [];
|
|
99
|
-
return match[1].split(",").map((s) => s.trim()).map((s) => s.replace(/^#/, "").match(/^(\d+)/)?.[1] ?? "").filter((s) => s.length > 0);
|
|
768
|
+
const keys = ["parents", "parent"];
|
|
769
|
+
return [...new Set([...parseBodyKeyRefs(body, keys), ...parseMetadataList(body, keys)])];
|
|
100
770
|
}
|
|
101
771
|
function issueTypeFor(issue) {
|
|
102
772
|
const labels = labelNamesFor(issue);
|
|
@@ -107,7 +777,7 @@ function issueTypeFor(issue) {
|
|
|
107
777
|
return "epic";
|
|
108
778
|
return "task";
|
|
109
779
|
}
|
|
110
|
-
function issueToTask(issue, repo) {
|
|
780
|
+
function issueToTask(issue, repo, nativeDependencies) {
|
|
111
781
|
const labelNames = labelNamesFor(issue);
|
|
112
782
|
const scope = labelNames.filter((l) => l.startsWith("scope:")).map((l) => l.slice("scope:".length));
|
|
113
783
|
const roleLabel = labelNames.find((l) => l.startsWith("role:"));
|
|
@@ -115,10 +785,12 @@ function issueToTask(issue, repo) {
|
|
|
115
785
|
const validators = labelNames.filter((l) => l.startsWith("validator:")).map((l) => l.slice("validator:".length));
|
|
116
786
|
const body = issue.body ?? "";
|
|
117
787
|
const issueNodeId = issue.id ?? issue.nodeId ?? issue.node_id;
|
|
788
|
+
const parsedDeps = parseDeps(body);
|
|
789
|
+
const deps = nativeDependencies?.deps ? [...new Set([...parsedDeps, ...nativeDependencies.deps])] : parsedDeps;
|
|
118
790
|
return {
|
|
119
791
|
id: String(issue.number),
|
|
120
792
|
...typeof issueNodeId === "string" && issueNodeId.trim() ? { issueNodeId: issueNodeId.trim() } : {},
|
|
121
|
-
deps
|
|
793
|
+
deps,
|
|
122
794
|
status: statusFor(issue),
|
|
123
795
|
title: issue.title,
|
|
124
796
|
body,
|
|
@@ -130,6 +802,7 @@ function issueToTask(issue, repo) {
|
|
|
130
802
|
sourceIssueId: `${repo}#${issue.number}`,
|
|
131
803
|
parentChildDeps: parseParents(body),
|
|
132
804
|
labels: labelNames,
|
|
805
|
+
...nativeDependencies?.degraded ? { nativeDependenciesDegraded: true, nativeDependenciesError: nativeDependencies.degraded } : {},
|
|
133
806
|
raw: issue
|
|
134
807
|
};
|
|
135
808
|
}
|
|
@@ -166,17 +839,49 @@ ${rendered}
|
|
|
166
839
|
` : `${rendered}
|
|
167
840
|
`;
|
|
168
841
|
}
|
|
842
|
+
function buildRigStickyStatusComment(input) {
|
|
843
|
+
const lines = [
|
|
844
|
+
RIG_STATUS_COMMENT_MARKER,
|
|
845
|
+
`### Rig status: ${input.status}`,
|
|
846
|
+
"",
|
|
847
|
+
input.summary
|
|
848
|
+
];
|
|
849
|
+
if (input.runId)
|
|
850
|
+
lines.push("", `- Run: ${input.runId}`);
|
|
851
|
+
if (input.prUrl)
|
|
852
|
+
lines.push(`- PR: ${input.prUrl}`);
|
|
853
|
+
for (const detail of input.details ?? [])
|
|
854
|
+
lines.push(`- ${detail}`);
|
|
855
|
+
return lines.join(`
|
|
856
|
+
`);
|
|
857
|
+
}
|
|
169
858
|
function isRigStickyStatusComment(body) {
|
|
170
859
|
return body.includes(RIG_STATUS_COMMENT_MARKER);
|
|
171
860
|
}
|
|
172
861
|
function ghSpawnOptions(extraEnv, timeoutMs) {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
862
|
+
const env = {
|
|
863
|
+
...process.env,
|
|
864
|
+
...process.env.GH_TOKEN !== undefined ? { GH_TOKEN: process.env.GH_TOKEN } : {},
|
|
865
|
+
...process.env.GITHUB_TOKEN !== undefined ? { GITHUB_TOKEN: process.env.GITHUB_TOKEN } : {},
|
|
866
|
+
...process.env.RIG_GITHUB_TOKEN !== undefined ? { RIG_GITHUB_TOKEN: process.env.RIG_GITHUB_TOKEN } : {}
|
|
867
|
+
};
|
|
868
|
+
for (const [key, value] of Object.entries(extraEnv ?? {})) {
|
|
869
|
+
if (value === undefined)
|
|
870
|
+
delete env[key];
|
|
871
|
+
else
|
|
872
|
+
env[key] = value;
|
|
873
|
+
}
|
|
874
|
+
return {
|
|
875
|
+
encoding: "utf-8",
|
|
876
|
+
timeout: timeoutMs,
|
|
877
|
+
env
|
|
878
|
+
};
|
|
176
879
|
}
|
|
177
880
|
function credentialEnv(token) {
|
|
178
881
|
const clean = token?.trim() ?? "";
|
|
179
|
-
|
|
882
|
+
if (clean)
|
|
883
|
+
return { GH_TOKEN: clean, GITHUB_TOKEN: clean, RIG_GITHUB_TOKEN: clean };
|
|
884
|
+
return { GH_TOKEN: undefined, GITHUB_TOKEN: undefined, RIG_GITHUB_TOKEN: undefined };
|
|
180
885
|
}
|
|
181
886
|
async function resolveCredentialEnv(opts, purpose) {
|
|
182
887
|
if (!opts.credentialProvider)
|
|
@@ -191,28 +896,319 @@ async function resolveCredentialEnv(opts, purpose) {
|
|
|
191
896
|
const resolved = await opts.credentialProvider.resolveGitHubToken(input);
|
|
192
897
|
return credentialEnv(resolved.token);
|
|
193
898
|
}
|
|
899
|
+
function tokenDiagnostic(value) {
|
|
900
|
+
const clean = value?.trim() ?? "";
|
|
901
|
+
return clean ? `present(len=${clean.length})` : "missing";
|
|
902
|
+
}
|
|
194
903
|
function runGh(bin, args, spawn, extraEnv, timeoutMs) {
|
|
195
|
-
const
|
|
196
|
-
|
|
904
|
+
const options = ghSpawnOptions(extraEnv, timeoutMs);
|
|
905
|
+
const res = spawn(bin, [...args], options);
|
|
906
|
+
assertGhSuccess(args, res, options.env);
|
|
197
907
|
if (!res.stdout || res.stdout.trim() === "")
|
|
198
908
|
return [];
|
|
199
909
|
return JSON.parse(res.stdout);
|
|
200
910
|
}
|
|
201
911
|
function runGhVoid(bin, args, spawn, extraEnv, timeoutMs) {
|
|
202
|
-
const
|
|
203
|
-
|
|
912
|
+
const options = ghSpawnOptions(extraEnv, timeoutMs);
|
|
913
|
+
const res = spawn(bin, [...args], options);
|
|
914
|
+
assertGhSuccess(args, res, options.env);
|
|
204
915
|
}
|
|
205
|
-
function assertGhSuccess(args, res) {
|
|
916
|
+
function assertGhSuccess(args, res, env) {
|
|
206
917
|
if (res.error) {
|
|
207
918
|
const msg = res.error.message ?? String(res.error);
|
|
208
919
|
throw new Error(`gh CLI not available \u2014 install gh (brew install gh / apt install gh): ${msg}`);
|
|
209
920
|
}
|
|
210
921
|
if (res.status !== 0) {
|
|
211
|
-
throw new Error(`gh ${args.join(" ")} failed (exit ${res.status}): ${res.stderr}
|
|
922
|
+
throw new Error(`gh ${args.join(" ")} failed (exit ${res.status}): ${res.stderr}
|
|
923
|
+
[rig gh env:standard-plugin] GH_TOKEN=${tokenDiagnostic(env.GH_TOKEN)} GITHUB_TOKEN=${tokenDiagnostic(env.GITHUB_TOKEN)} RIG_GITHUB_TOKEN=${tokenDiagnostic(env.RIG_GITHUB_TOKEN)}`);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
var DEFAULT_PROJECT_STATUSES = {
|
|
927
|
+
todo: "Todo",
|
|
928
|
+
running: "In Progress",
|
|
929
|
+
prOpen: "In Review",
|
|
930
|
+
ciFixing: "In Review",
|
|
931
|
+
merging: "In Review",
|
|
932
|
+
done: "Done",
|
|
933
|
+
needsAttention: "Needs Attention"
|
|
934
|
+
};
|
|
935
|
+
function asProjectRecord(value) {
|
|
936
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
937
|
+
}
|
|
938
|
+
function projectString(value) {
|
|
939
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
940
|
+
}
|
|
941
|
+
function projectLifecycleStatusForTaskStatus(status) {
|
|
942
|
+
const normalized = status?.trim().toLowerCase().replace(/[-\s]+/g, "_");
|
|
943
|
+
switch (normalized) {
|
|
944
|
+
case "draft":
|
|
945
|
+
case "open":
|
|
946
|
+
case "queued":
|
|
947
|
+
case "ready":
|
|
948
|
+
return "todo";
|
|
949
|
+
case "running":
|
|
950
|
+
case "in_progress":
|
|
951
|
+
return "running";
|
|
952
|
+
case "under_review":
|
|
953
|
+
case "review":
|
|
954
|
+
case "pr_open":
|
|
955
|
+
return "prOpen";
|
|
956
|
+
case "ci_fixing":
|
|
957
|
+
case "fixing":
|
|
958
|
+
return "ciFixing";
|
|
959
|
+
case "merging":
|
|
960
|
+
case "merge":
|
|
961
|
+
return "merging";
|
|
962
|
+
case "closed":
|
|
963
|
+
case "completed":
|
|
964
|
+
case "done":
|
|
965
|
+
return "done";
|
|
966
|
+
case "blocked":
|
|
967
|
+
case "cancelled":
|
|
968
|
+
case "failed":
|
|
969
|
+
case "needs_attention":
|
|
970
|
+
return "needsAttention";
|
|
971
|
+
default:
|
|
972
|
+
return null;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
function ghGraphQLFetch(bin, spawnFn, extraEnv, timeoutMs) {
|
|
976
|
+
return async (query, variables) => {
|
|
977
|
+
const args = ["api", "graphql", "-f", `query=${query}`];
|
|
978
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
979
|
+
if (value === undefined || value === null)
|
|
980
|
+
continue;
|
|
981
|
+
args.push("-f", `${key}=${String(value)}`);
|
|
982
|
+
}
|
|
983
|
+
const response = runGh(bin, args, spawnFn, extraEnv, timeoutMs);
|
|
984
|
+
return asProjectRecord(response)?.data ?? response;
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
function issueNodeIdFor(issue) {
|
|
988
|
+
const id = issue.id ?? issue.nodeId ?? issue.node_id;
|
|
989
|
+
return typeof id === "string" && id.trim().length > 0 ? id.trim() : null;
|
|
990
|
+
}
|
|
991
|
+
function nativeIssueDependencyRef(value, currentRepo) {
|
|
992
|
+
const record = asProjectRecord(value);
|
|
993
|
+
const number = typeof record?.number === "number" ? String(record.number) : projectString(record?.number);
|
|
994
|
+
if (!number)
|
|
995
|
+
return null;
|
|
996
|
+
const repository = asProjectRecord(record?.repository);
|
|
997
|
+
const owner = projectString(asProjectRecord(repository?.owner)?.login);
|
|
998
|
+
const name = projectString(repository?.name);
|
|
999
|
+
if (!owner || !name || `${owner}/${name}` === currentRepo)
|
|
1000
|
+
return number;
|
|
1001
|
+
return `${owner}/${name}#${number}`;
|
|
1002
|
+
}
|
|
1003
|
+
function nativeDependencyRefsFrom(data, currentRepo) {
|
|
1004
|
+
const issue = asProjectRecord(asProjectRecord(data)?.node);
|
|
1005
|
+
const blockedBy = asProjectRecord(issue?.blockedBy);
|
|
1006
|
+
const nodes = Array.isArray(blockedBy?.nodes) ? blockedBy.nodes : [];
|
|
1007
|
+
return [...new Set(nodes.flatMap((node) => {
|
|
1008
|
+
const ref = nativeIssueDependencyRef(node, currentRepo);
|
|
1009
|
+
return ref ? [ref] : [];
|
|
1010
|
+
}))];
|
|
1011
|
+
}
|
|
1012
|
+
async function readNativeDependenciesForIssue(input) {
|
|
1013
|
+
const issueId = issueNodeIdFor(input.issue);
|
|
1014
|
+
if (!issueId)
|
|
1015
|
+
return { deps: [], degraded: "GitHub issue node id is unavailable." };
|
|
1016
|
+
const query = `
|
|
1017
|
+
query RigIssueNativeDependencies($issueId: ID!) {
|
|
1018
|
+
node(id: $issueId) {
|
|
1019
|
+
... on Issue {
|
|
1020
|
+
blockedBy(first: 100) {
|
|
1021
|
+
nodes {
|
|
1022
|
+
number
|
|
1023
|
+
repository { name owner { login } }
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
`;
|
|
1030
|
+
try {
|
|
1031
|
+
return {
|
|
1032
|
+
deps: nativeDependencyRefsFrom(await input.fetchGraphQL(query, { issueId }, "gh-cli"), input.repo)
|
|
1033
|
+
};
|
|
1034
|
+
} catch (error) {
|
|
1035
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
1036
|
+
return { deps: [], degraded: detail };
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
function formatIssueReference(ref) {
|
|
1040
|
+
const clean = ref.trim().replace(/^#/, "");
|
|
1041
|
+
return /^\d+$/.test(clean) ? `#${clean}` : clean;
|
|
1042
|
+
}
|
|
1043
|
+
function appendReferenceLines(body, deps, parents) {
|
|
1044
|
+
const lines = [];
|
|
1045
|
+
const cleanDeps = (deps ?? []).map(formatIssueReference).filter((ref) => ref.length > 0);
|
|
1046
|
+
const cleanParents = (parents ?? []).map(formatIssueReference).filter((ref) => ref.length > 0);
|
|
1047
|
+
if (cleanDeps.length > 0)
|
|
1048
|
+
lines.push(`depends-on: ${cleanDeps.join(", ")}`);
|
|
1049
|
+
if (cleanParents.length > 0)
|
|
1050
|
+
lines.push(`parents: ${cleanParents.join(", ")}`);
|
|
1051
|
+
if (lines.length === 0)
|
|
1052
|
+
return body;
|
|
1053
|
+
return body.trim().length > 0 ? `${body.trimEnd()}
|
|
1054
|
+
|
|
1055
|
+
${lines.join(`
|
|
1056
|
+
`)}` : lines.join(`
|
|
1057
|
+
`);
|
|
1058
|
+
}
|
|
1059
|
+
function bodyForCreatedTask(input) {
|
|
1060
|
+
const metadata = { ...input.metadata ?? {} };
|
|
1061
|
+
if (input.deps && input.deps.length > 0)
|
|
1062
|
+
metadata["depends-on"] = input.deps.map(formatIssueReference);
|
|
1063
|
+
if (input.parents && input.parents.length > 0)
|
|
1064
|
+
metadata.parents = input.parents.map(formatIssueReference);
|
|
1065
|
+
return updateRigOwnedMetadataBlock(appendReferenceLines(input.body, input.deps, input.parents), metadata);
|
|
1066
|
+
}
|
|
1067
|
+
function projectStatusFieldFrom(data, projectId) {
|
|
1068
|
+
const fields = asProjectRecord(asProjectRecord(asProjectRecord(data)?.node)?.fields)?.nodes;
|
|
1069
|
+
for (const node of Array.isArray(fields) ? fields : []) {
|
|
1070
|
+
const record = asProjectRecord(node);
|
|
1071
|
+
if (projectString(record?.name)?.toLowerCase() !== "status")
|
|
1072
|
+
continue;
|
|
1073
|
+
const id = projectString(record?.id);
|
|
1074
|
+
if (!id)
|
|
1075
|
+
continue;
|
|
1076
|
+
const options = Array.isArray(record?.options) ? record.options.flatMap((option) => {
|
|
1077
|
+
const optionRecord = asProjectRecord(option);
|
|
1078
|
+
const optionId = projectString(optionRecord?.id);
|
|
1079
|
+
const name = projectString(optionRecord?.name);
|
|
1080
|
+
return optionId && name ? [{ id: optionId, name }] : [];
|
|
1081
|
+
}) : [];
|
|
1082
|
+
return { id, name: "Status", options };
|
|
1083
|
+
}
|
|
1084
|
+
throw new Error(`GitHub Project ${projectId} does not expose a Status single-select field.`);
|
|
1085
|
+
}
|
|
1086
|
+
async function resolveProjectStatusField(input) {
|
|
1087
|
+
const query = `
|
|
1088
|
+
query RigProjectStatusField($projectId: ID!) {
|
|
1089
|
+
node(id: $projectId) {
|
|
1090
|
+
... on ProjectV2 {
|
|
1091
|
+
fields(first: 50) {
|
|
1092
|
+
nodes {
|
|
1093
|
+
... on ProjectV2FieldCommon { id name }
|
|
1094
|
+
... on ProjectV2SingleSelectField { id name options { id name } }
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
`;
|
|
1101
|
+
return projectStatusFieldFrom(await input.fetchGraphQL(query, { projectId: input.projectId }, input.token), input.projectId);
|
|
1102
|
+
}
|
|
1103
|
+
async function ensureIssueProjectItem(input) {
|
|
1104
|
+
const query = `
|
|
1105
|
+
query RigFindProjectIssueItem($projectId: ID!) {
|
|
1106
|
+
node(id: $projectId) {
|
|
1107
|
+
... on ProjectV2 {
|
|
1108
|
+
items(first: 100) { nodes { id content { ... on Issue { id } } } }
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
`;
|
|
1113
|
+
const data = await input.fetchGraphQL(query, { projectId: input.projectId }, input.token);
|
|
1114
|
+
const nodes = asProjectRecord(asProjectRecord(asProjectRecord(data)?.node)?.items)?.nodes;
|
|
1115
|
+
for (const node of Array.isArray(nodes) ? nodes : []) {
|
|
1116
|
+
const record = asProjectRecord(node);
|
|
1117
|
+
const content = asProjectRecord(record?.content);
|
|
1118
|
+
if (projectString(content?.id) === input.issueNodeId) {
|
|
1119
|
+
const id2 = projectString(record?.id);
|
|
1120
|
+
if (id2)
|
|
1121
|
+
return { id: id2, created: false };
|
|
1122
|
+
}
|
|
212
1123
|
}
|
|
1124
|
+
const mutation = `
|
|
1125
|
+
mutation RigAddIssueToProject($projectId: ID!, $contentId: ID!) {
|
|
1126
|
+
addProjectV2ItemById(input: { projectId: $projectId, contentId: $contentId }) { item { id } }
|
|
1127
|
+
}
|
|
1128
|
+
`;
|
|
1129
|
+
const created = await input.fetchGraphQL(mutation, { projectId: input.projectId, contentId: input.issueNodeId }, input.token);
|
|
1130
|
+
const addResult = asProjectRecord(asProjectRecord(created)?.addProjectV2ItemById);
|
|
1131
|
+
const id = projectString(asProjectRecord(addResult?.item)?.id);
|
|
1132
|
+
if (!id)
|
|
1133
|
+
throw new Error("GitHub Project item creation did not return an item id.");
|
|
1134
|
+
return { id, created: true };
|
|
1135
|
+
}
|
|
1136
|
+
async function updateIssueProjectStatus(input) {
|
|
1137
|
+
const mutation = `
|
|
1138
|
+
mutation RigUpdateProjectStatus($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) {
|
|
1139
|
+
updateProjectV2ItemFieldValue(input: {
|
|
1140
|
+
projectId: $projectId,
|
|
1141
|
+
itemId: $itemId,
|
|
1142
|
+
fieldId: $fieldId,
|
|
1143
|
+
value: { singleSelectOptionId: $optionId }
|
|
1144
|
+
}) { projectV2Item { id } }
|
|
1145
|
+
}
|
|
1146
|
+
`;
|
|
1147
|
+
await input.fetchGraphQL(mutation, {
|
|
1148
|
+
projectId: input.projectId,
|
|
1149
|
+
itemId: input.itemId,
|
|
1150
|
+
fieldId: input.fieldId,
|
|
1151
|
+
optionId: input.optionId
|
|
1152
|
+
}, input.token);
|
|
1153
|
+
}
|
|
1154
|
+
function fetchIssueNodeId(bin, repo, spawnFn, id, extraEnv, timeoutMs) {
|
|
1155
|
+
const issue = runGh(bin, ["issue", "view", String(id), "--repo", repo, "--json", "id"], spawnFn, extraEnv, timeoutMs);
|
|
1156
|
+
return projectString(issue.id) ?? projectString(issue.nodeId) ?? projectString(issue.node_id);
|
|
1157
|
+
}
|
|
1158
|
+
async function syncGitHubProjectStatus(bin, repo, spawnFn, id, status, projects, extraEnv, timeoutMs) {
|
|
1159
|
+
if (!projects?.enabled)
|
|
1160
|
+
return;
|
|
1161
|
+
const projectId = projectString(projects.projectId);
|
|
1162
|
+
if (!projectId)
|
|
1163
|
+
throw new Error("GitHub Projects status sync is enabled but projectId is missing.");
|
|
1164
|
+
const lifecycleStatus = projectLifecycleStatusForTaskStatus(status);
|
|
1165
|
+
if (!lifecycleStatus)
|
|
1166
|
+
return;
|
|
1167
|
+
const issueNodeId = fetchIssueNodeId(bin, repo, spawnFn, id, extraEnv, timeoutMs);
|
|
1168
|
+
if (!issueNodeId)
|
|
1169
|
+
throw new Error(`GitHub issue ${repo}#${id} did not expose a node id for Projects status sync.`);
|
|
1170
|
+
const projectStatus = projectString(projects.statuses?.[lifecycleStatus]) ?? DEFAULT_PROJECT_STATUSES[lifecycleStatus];
|
|
1171
|
+
const fetchGraphQL = ghGraphQLFetch(bin, spawnFn, extraEnv, timeoutMs);
|
|
1172
|
+
const field = await resolveProjectStatusField({ projectId, token: "gh-cli", fetchGraphQL });
|
|
1173
|
+
const option = field.options.find((candidate) => candidate.name.toLowerCase() === projectStatus.toLowerCase() || candidate.id === projectStatus);
|
|
1174
|
+
if (!option)
|
|
1175
|
+
throw new Error(`GitHub Project ${projectId} Status field does not contain option "${projectStatus}".`);
|
|
1176
|
+
const item = await ensureIssueProjectItem({ projectId, issueNodeId, token: "gh-cli", fetchGraphQL });
|
|
1177
|
+
await updateIssueProjectStatus({
|
|
1178
|
+
projectId,
|
|
1179
|
+
itemId: item.id,
|
|
1180
|
+
fieldId: projectString(projects.statusFieldId) ?? field.id,
|
|
1181
|
+
optionId: option.id,
|
|
1182
|
+
token: "gh-cli",
|
|
1183
|
+
fetchGraphQL
|
|
1184
|
+
});
|
|
1185
|
+
}
|
|
1186
|
+
var TERMINAL_TASK_STATUSES = new Set(["closed", "completed", "merged", "cancelled", "resolved", "done"]);
|
|
1187
|
+
function normalizeTaskStatusToken(status) {
|
|
1188
|
+
return status?.trim().toLowerCase().replace(/[-\s]+/g, "_") ?? "";
|
|
1189
|
+
}
|
|
1190
|
+
function issueUpdatesMode(value) {
|
|
1191
|
+
return value === "off" || value === "minimal" || value === "lifecycle" ? value : "lifecycle";
|
|
1192
|
+
}
|
|
1193
|
+
function isTerminalTaskStatus(status) {
|
|
1194
|
+
return TERMINAL_TASK_STATUSES.has(normalizeTaskStatusToken(status));
|
|
1195
|
+
}
|
|
1196
|
+
function shouldWriteIssueUpdate(mode, status) {
|
|
1197
|
+
if (mode === "off")
|
|
1198
|
+
return false;
|
|
1199
|
+
if (mode === "lifecycle")
|
|
1200
|
+
return true;
|
|
1201
|
+
return isTerminalTaskStatus(status);
|
|
1202
|
+
}
|
|
1203
|
+
function isRunningStatus(status) {
|
|
1204
|
+
return normalizeTaskStatusToken(status) === "running";
|
|
1205
|
+
}
|
|
1206
|
+
function assignRunningIssue(bin, repo, spawnFn, id, assignee, extraEnv, timeoutMs) {
|
|
1207
|
+
runGhVoid(bin, ["issue", "edit", String(id), "--repo", repo, "--add-assignee", assignee?.trim() || "@me"], spawnFn, extraEnv, timeoutMs);
|
|
213
1208
|
}
|
|
214
1209
|
function statusLabelFor(status) {
|
|
215
1210
|
switch (status) {
|
|
1211
|
+
case "running":
|
|
216
1212
|
case "in_progress":
|
|
217
1213
|
return "in-progress";
|
|
218
1214
|
case "blocked":
|
|
@@ -231,6 +1227,8 @@ function statusLabelFor(status) {
|
|
|
231
1227
|
return "under-review";
|
|
232
1228
|
case "needs_attention":
|
|
233
1229
|
return "blocked";
|
|
1230
|
+
case "closed":
|
|
1231
|
+
case "completed":
|
|
234
1232
|
case "open":
|
|
235
1233
|
return null;
|
|
236
1234
|
default:
|
|
@@ -239,11 +1237,13 @@ function statusLabelFor(status) {
|
|
|
239
1237
|
}
|
|
240
1238
|
function rigStatusLabelFor(status) {
|
|
241
1239
|
switch (status) {
|
|
1240
|
+
case "running":
|
|
242
1241
|
case "in_progress":
|
|
243
1242
|
return "rig:running";
|
|
244
1243
|
case "under_review":
|
|
245
1244
|
return "rig:pr-open";
|
|
246
1245
|
case "closed":
|
|
1246
|
+
case "completed":
|
|
247
1247
|
return "rig:done";
|
|
248
1248
|
case "ci_fixing":
|
|
249
1249
|
return "rig:ci-fixing";
|
|
@@ -261,9 +1261,10 @@ function rigStatusLabelFor(status) {
|
|
|
261
1261
|
return null;
|
|
262
1262
|
}
|
|
263
1263
|
}
|
|
264
|
-
function applyIssueStatus(bin, repo, spawnFn, id, status, extraEnv, timeoutMs) {
|
|
265
|
-
const targetLabel = status === "closed" ? null : statusLabelFor(status);
|
|
1264
|
+
async function applyIssueStatus(bin, repo, spawnFn, id, status, projects, runningAssignee, issueUpdates, extraEnv, timeoutMs) {
|
|
1265
|
+
const targetLabel = status === "closed" || status === "completed" ? null : statusLabelFor(status);
|
|
266
1266
|
const targetRigLabel = rigStatusLabelFor(status);
|
|
1267
|
+
const shouldSyncLifecycle = shouldWriteIssueUpdate(issueUpdates, status);
|
|
267
1268
|
for (const l of [...STATUS_LABELS, ...RIG_STATUS_LABELS]) {
|
|
268
1269
|
if (targetLabel !== null && l === targetLabel)
|
|
269
1270
|
continue;
|
|
@@ -285,7 +1286,19 @@ function applyIssueStatus(bin, repo, spawnFn, id, status, extraEnv, timeoutMs) {
|
|
|
285
1286
|
runGhVoid(bin, ["issue", "edit", String(id), "--repo", repo, "--add-label", label], spawnFn, extraEnv, timeoutMs);
|
|
286
1287
|
}
|
|
287
1288
|
}
|
|
288
|
-
if (status
|
|
1289
|
+
if (isRunningStatus(status)) {
|
|
1290
|
+
assignRunningIssue(bin, repo, spawnFn, id, runningAssignee, extraEnv, timeoutMs);
|
|
1291
|
+
if (shouldSyncLifecycle) {
|
|
1292
|
+
upsertRigStickyComment(bin, repo, spawnFn, String(id), buildRigStickyStatusComment({
|
|
1293
|
+
status: "running",
|
|
1294
|
+
summary: "Rig run started."
|
|
1295
|
+
}), extraEnv, timeoutMs);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
if (shouldSyncLifecycle) {
|
|
1299
|
+
await syncGitHubProjectStatus(bin, repo, spawnFn, id, status, projects, extraEnv, timeoutMs);
|
|
1300
|
+
}
|
|
1301
|
+
if (status === "closed" || status === "completed") {
|
|
289
1302
|
runGhVoid(bin, ["issue", "close", String(id), "--repo", repo], spawnFn, extraEnv, timeoutMs);
|
|
290
1303
|
}
|
|
291
1304
|
}
|
|
@@ -355,11 +1368,11 @@ function applyLabels(bin, repo, spawnFn, id, labels, action, extraEnv, timeoutMs
|
|
|
355
1368
|
} catch {}
|
|
356
1369
|
}
|
|
357
1370
|
}
|
|
358
|
-
function applyIssueUpdate(bin, repo, spawnFn, id, update, extraEnv, timeoutMs) {
|
|
1371
|
+
async function applyIssueUpdate(bin, repo, spawnFn, id, update, projects, runningAssignee, issueUpdates, extraEnv, timeoutMs) {
|
|
359
1372
|
if (update.status) {
|
|
360
|
-
applyIssueStatus(bin, repo, spawnFn, id, update.status, extraEnv, timeoutMs);
|
|
1373
|
+
await applyIssueStatus(bin, repo, spawnFn, id, update.status, projects, runningAssignee, issueUpdates, extraEnv, timeoutMs);
|
|
361
1374
|
}
|
|
362
|
-
if (update.comment?.trim()) {
|
|
1375
|
+
if (update.comment?.trim() && shouldWriteIssueUpdate(issueUpdates, update.status)) {
|
|
363
1376
|
if (isRigStickyStatusComment(update.comment)) {
|
|
364
1377
|
upsertRigStickyComment(bin, repo, spawnFn, String(id), update.comment, extraEnv, timeoutMs);
|
|
365
1378
|
} else {
|
|
@@ -385,6 +1398,17 @@ function createGitHubIssuesTaskSource(opts) {
|
|
|
385
1398
|
const spawnFn = opts.spawn ?? spawnSync;
|
|
386
1399
|
const timeoutMs = Math.max(1000, Math.trunc(opts.timeoutMs ?? DEFAULT_GH_TIMEOUT_MS));
|
|
387
1400
|
const listLimit = Math.max(1, Math.trunc(opts.listLimit ?? DEFAULT_GITHUB_ISSUE_LIST_LIMIT));
|
|
1401
|
+
const issueUpdates = issueUpdatesMode(opts.issueUpdates);
|
|
1402
|
+
async function issueToTaskWithOptionalNativeDependencies(issue, env) {
|
|
1403
|
+
if (!opts.useNativeDependencies)
|
|
1404
|
+
return issueToTask(issue, repo);
|
|
1405
|
+
const nativeDependencies = await readNativeDependenciesForIssue({
|
|
1406
|
+
issue,
|
|
1407
|
+
repo,
|
|
1408
|
+
fetchGraphQL: ghGraphQLFetch(bin, spawnFn, env, timeoutMs)
|
|
1409
|
+
});
|
|
1410
|
+
return issueToTask(issue, repo, nativeDependencies);
|
|
1411
|
+
}
|
|
388
1412
|
return {
|
|
389
1413
|
id: "std:github-issues",
|
|
390
1414
|
kind: "github-issues",
|
|
@@ -411,12 +1435,13 @@ function createGitHubIssuesTaskSource(opts) {
|
|
|
411
1435
|
throw new Error(`GitHub issue list for ${repo} reached the configured limit (${listLimit}); refusing to silently truncate matching issues. Increase taskSource.options.listLimit or narrow labels/state/assignee.`);
|
|
412
1436
|
}
|
|
413
1437
|
const issues = rawIssues.filter((issue) => !issue.pull_request);
|
|
414
|
-
return issues.map((
|
|
1438
|
+
return Promise.all(issues.map((issue) => issueToTaskWithOptionalNativeDependencies(issue, env)));
|
|
415
1439
|
},
|
|
416
1440
|
async get(id) {
|
|
1441
|
+
const env = await resolveCredentialEnv(opts, "selected-repo");
|
|
1442
|
+
let issue;
|
|
417
1443
|
try {
|
|
418
|
-
|
|
419
|
-
const issue = runGh(bin, [
|
|
1444
|
+
issue = runGh(bin, [
|
|
420
1445
|
"issue",
|
|
421
1446
|
"view",
|
|
422
1447
|
String(id),
|
|
@@ -425,19 +1450,23 @@ function createGitHubIssuesTaskSource(opts) {
|
|
|
425
1450
|
"--json",
|
|
426
1451
|
"number,title,body,labels,state,url,assignees,id"
|
|
427
1452
|
], spawnFn, env, timeoutMs);
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
1453
|
+
} catch (error) {
|
|
1454
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
1455
|
+
if (/could not resolve to (an? )?(issue|pullrequest)|no issues? (found|matched)|404 not found|gh: not found|gh issue view\b[\s\S]*failed \(exit \d+\): not found\b/i.test(detail)) {
|
|
1456
|
+
return;
|
|
1457
|
+
}
|
|
1458
|
+
throw new Error(`Failed to read task ${id} from GitHub repo ${repo}: ${detail}`);
|
|
431
1459
|
}
|
|
1460
|
+
return issueToTaskWithOptionalNativeDependencies(issue, env);
|
|
432
1461
|
},
|
|
433
1462
|
async updateStatus(id, status) {
|
|
434
1463
|
const env = await resolveCredentialEnv(opts, "selected-repo");
|
|
435
|
-
applyIssueStatus(bin, repo, spawnFn, id, status, env, timeoutMs);
|
|
1464
|
+
await applyIssueStatus(bin, repo, spawnFn, id, status, opts.projects, opts.assignee, issueUpdates, env, timeoutMs);
|
|
436
1465
|
notifyTaskChanged(opts.onTaskChanged, repo, id, status);
|
|
437
1466
|
},
|
|
438
1467
|
async updateTask(id, update) {
|
|
439
1468
|
const env = await resolveCredentialEnv(opts, "selected-repo");
|
|
440
|
-
applyIssueUpdate(bin, repo, spawnFn, id, update, env, timeoutMs);
|
|
1469
|
+
await applyIssueUpdate(bin, repo, spawnFn, id, update, opts.projects, opts.assignee, issueUpdates, env, timeoutMs);
|
|
441
1470
|
notifyTaskChanged(opts.onTaskChanged, repo, id, update.status);
|
|
442
1471
|
},
|
|
443
1472
|
async addLabels(id, labels) {
|
|
@@ -452,6 +1481,7 @@ function createGitHubIssuesTaskSource(opts) {
|
|
|
452
1481
|
},
|
|
453
1482
|
async createIssue(input) {
|
|
454
1483
|
const env = await resolveCredentialEnv(opts, "selected-repo");
|
|
1484
|
+
const body = input.body ?? "";
|
|
455
1485
|
const args = [
|
|
456
1486
|
"api",
|
|
457
1487
|
"-X",
|
|
@@ -460,12 +1490,31 @@ function createGitHubIssuesTaskSource(opts) {
|
|
|
460
1490
|
"-f",
|
|
461
1491
|
`title=${input.title}`,
|
|
462
1492
|
"-f",
|
|
463
|
-
`body=${
|
|
1493
|
+
`body=${body}`,
|
|
464
1494
|
...(input.labels ?? []).flatMap((label) => ["-f", `labels[]=${label}`])
|
|
465
1495
|
];
|
|
466
1496
|
const issue = runGh(bin, args, spawnFn, env, timeoutMs);
|
|
467
1497
|
notifyTaskChanged(opts.onTaskChanged, repo, String(issue.number));
|
|
468
|
-
return issueToTask(issue, repo);
|
|
1498
|
+
return issueToTask({ ...issue, body: issue.body ?? body }, repo);
|
|
1499
|
+
},
|
|
1500
|
+
async create(input) {
|
|
1501
|
+
const env = await resolveCredentialEnv(opts, "selected-repo");
|
|
1502
|
+
const body = bodyForCreatedTask(input);
|
|
1503
|
+
const args = [
|
|
1504
|
+
"api",
|
|
1505
|
+
"-X",
|
|
1506
|
+
"POST",
|
|
1507
|
+
`repos/${repo}/issues`,
|
|
1508
|
+
"-f",
|
|
1509
|
+
`title=${input.title}`,
|
|
1510
|
+
"-f",
|
|
1511
|
+
`body=${body}`,
|
|
1512
|
+
"-f",
|
|
1513
|
+
"labels[]=rig:generated"
|
|
1514
|
+
];
|
|
1515
|
+
const issue = runGh(bin, args, spawnFn, env, timeoutMs);
|
|
1516
|
+
notifyTaskChanged(opts.onTaskChanged, repo, String(issue.number));
|
|
1517
|
+
return issueToTask({ ...issue, body: issue.body ?? body }, repo);
|
|
469
1518
|
},
|
|
470
1519
|
async getIssueBody(id) {
|
|
471
1520
|
const env = await resolveCredentialEnv(opts, "selected-repo");
|
|
@@ -476,7 +1525,7 @@ function createGitHubIssuesTaskSource(opts) {
|
|
|
476
1525
|
|
|
477
1526
|
// packages/standard-plugin/src/files-source.ts
|
|
478
1527
|
import { readFileSync as readFileSync2, readdirSync, existsSync as existsSync2, statSync, writeFileSync } from "fs";
|
|
479
|
-
import { join, basename } from "path";
|
|
1528
|
+
import { join, basename, isAbsolute, resolve as resolve2 } from "path";
|
|
480
1529
|
var DEFAULT_PATTERN = /\.(task\.)?json$/;
|
|
481
1530
|
function readTaskFile(file, pattern) {
|
|
482
1531
|
const raw = JSON.parse(readFileSync2(file, "utf-8"));
|
|
@@ -497,10 +1546,11 @@ function readTaskFile(file, pattern) {
|
|
|
497
1546
|
}
|
|
498
1547
|
function createFilesTaskSource(opts) {
|
|
499
1548
|
const pattern = opts.pattern ?? DEFAULT_PATTERN;
|
|
500
|
-
const
|
|
501
|
-
if (!
|
|
1549
|
+
const configured = opts.path ?? opts.dir;
|
|
1550
|
+
if (!configured) {
|
|
502
1551
|
throw new Error("createFilesTaskSource: either `path` or `dir` must be provided");
|
|
503
1552
|
}
|
|
1553
|
+
const directory = isAbsolute(configured) ? configured : resolve2(opts.projectRoot ?? process.cwd(), configured);
|
|
504
1554
|
const findTaskFile = (id) => {
|
|
505
1555
|
if (!existsSync2(directory))
|
|
506
1556
|
return;
|
|
@@ -577,7 +1627,12 @@ function createFilesTaskSource(opts) {
|
|
|
577
1627
|
};
|
|
578
1628
|
}
|
|
579
1629
|
|
|
580
|
-
// packages/standard-plugin/src/
|
|
1630
|
+
// packages/standard-plugin/src/plugin.ts
|
|
1631
|
+
init_metadata();
|
|
1632
|
+
init_metadata();
|
|
1633
|
+
import { createStandardProductEntrypointPlugin, standardProductEntrypointPlugin } from "@rig/product-entrypoint-plugin/plugin";
|
|
1634
|
+
import { createStandardTaskCliPlugin, standardTaskCliPlugin } from "@rig/task-cli-plugin/plugin";
|
|
1635
|
+
var DOCS_HEALTH_PANEL_ID = "docs-health";
|
|
581
1636
|
function requireStringField(config, field, kind) {
|
|
582
1637
|
const value = config[field];
|
|
583
1638
|
if (!value) {
|
|
@@ -585,9 +1640,145 @@ function requireStringField(config, field, kind) {
|
|
|
585
1640
|
}
|
|
586
1641
|
return value;
|
|
587
1642
|
}
|
|
588
|
-
function
|
|
1643
|
+
function isRecord(value) {
|
|
1644
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
1645
|
+
}
|
|
1646
|
+
function optionalString(value) {
|
|
1647
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
|
1648
|
+
}
|
|
1649
|
+
function parseGitHubProjectsOptions(value) {
|
|
1650
|
+
if (!isRecord(value))
|
|
1651
|
+
return;
|
|
1652
|
+
const statusesSource = isRecord(value.statuses) ? value.statuses : undefined;
|
|
1653
|
+
const statuses = {};
|
|
1654
|
+
for (const key of ["todo", "running", "prOpen", "ciFixing", "merging", "done", "needsAttention"]) {
|
|
1655
|
+
const status = optionalString(statusesSource?.[key]);
|
|
1656
|
+
if (status)
|
|
1657
|
+
statuses[key] = status;
|
|
1658
|
+
}
|
|
1659
|
+
const parsed = {};
|
|
1660
|
+
if (typeof value.enabled === "boolean")
|
|
1661
|
+
parsed.enabled = value.enabled;
|
|
1662
|
+
const projectId = optionalString(value.projectId);
|
|
1663
|
+
if (projectId)
|
|
1664
|
+
parsed.projectId = projectId;
|
|
1665
|
+
const statusFieldId = optionalString(value.statusFieldId);
|
|
1666
|
+
if (statusFieldId)
|
|
1667
|
+
parsed.statusFieldId = statusFieldId;
|
|
1668
|
+
if (Object.keys(statuses).length > 0)
|
|
1669
|
+
parsed.statuses = statuses;
|
|
1670
|
+
return parsed;
|
|
1671
|
+
}
|
|
1672
|
+
function githubProjectsOptionsFromConfig(config, context) {
|
|
1673
|
+
const rigConfig = isRecord(context?.rigConfig) ? context.rigConfig : undefined;
|
|
1674
|
+
const github = isRecord(rigConfig?.github) ? rigConfig.github : undefined;
|
|
1675
|
+
return parseGitHubProjectsOptions(config.options?.projects) ?? parseGitHubProjectsOptions(github?.projects);
|
|
1676
|
+
}
|
|
1677
|
+
function booleanOption(value) {
|
|
1678
|
+
return typeof value === "boolean" ? value : undefined;
|
|
1679
|
+
}
|
|
1680
|
+
function panelProjectRoot(context) {
|
|
1681
|
+
return isRecord(context) && typeof context.projectRoot === "string" && context.projectRoot.length > 0 ? context.projectRoot : null;
|
|
1682
|
+
}
|
|
1683
|
+
function driftFindingPanelId(finding, index) {
|
|
1684
|
+
return `${finding.docPath}:${finding.line ?? index}:${finding.kind}`;
|
|
1685
|
+
}
|
|
1686
|
+
function createDocsHealthPanelProducer(options = {}) {
|
|
1687
|
+
return async (context) => {
|
|
1688
|
+
const projectRoot = panelProjectRoot(context);
|
|
1689
|
+
if (!projectRoot)
|
|
1690
|
+
return;
|
|
1691
|
+
const { detectDrift: detectDrift2 } = await Promise.resolve().then(() => (init_detect(), exports_detect));
|
|
1692
|
+
const report = await detectDrift2({
|
|
1693
|
+
projectRoot,
|
|
1694
|
+
...options.docsGlobs !== undefined ? { docsGlobs: options.docsGlobs } : {},
|
|
1695
|
+
...options.ignoreGlobs !== undefined ? { ignoreGlobs: options.ignoreGlobs } : {}
|
|
1696
|
+
});
|
|
1697
|
+
return {
|
|
1698
|
+
findings: report.findings.map((finding, index) => ({
|
|
1699
|
+
id: driftFindingPanelId(finding, index),
|
|
1700
|
+
docPath: finding.docPath,
|
|
1701
|
+
kind: finding.kind,
|
|
1702
|
+
confidence: finding.confidence,
|
|
1703
|
+
summary: finding.detail,
|
|
1704
|
+
taskId: null
|
|
1705
|
+
})),
|
|
1706
|
+
degraded: report.degraded ? "drift scan degraded" : null
|
|
1707
|
+
};
|
|
1708
|
+
};
|
|
1709
|
+
}
|
|
1710
|
+
function createLazyDocsDriftValidator(options = {}) {
|
|
1711
|
+
return {
|
|
1712
|
+
...DOCS_DRIFT_VALIDATOR,
|
|
1713
|
+
async run(ctx) {
|
|
1714
|
+
const { runDocsDriftValidation: runDocsDriftValidation2 } = await Promise.resolve().then(() => (init_plugin(), exports_plugin));
|
|
1715
|
+
return runDocsDriftValidation2({
|
|
1716
|
+
projectRoot: ctx.workspaceRoot,
|
|
1717
|
+
...options.docsGlobs !== undefined ? { docsGlobs: options.docsGlobs } : {},
|
|
1718
|
+
...options.ignoreGlobs !== undefined ? { ignoreGlobs: options.ignoreGlobs } : {},
|
|
1719
|
+
...options.failOnDrift !== undefined ? { failOnDrift: options.failOnDrift } : {}
|
|
1720
|
+
});
|
|
1721
|
+
}
|
|
1722
|
+
};
|
|
1723
|
+
}
|
|
1724
|
+
function createLazyDocsDriftGateStage(options = {}) {
|
|
1725
|
+
return async (ctx) => {
|
|
1726
|
+
const { createDocsDriftGateStage: createDocsDriftGateStage2 } = await Promise.resolve().then(() => (init_plugin(), exports_plugin));
|
|
1727
|
+
return createDocsDriftGateStage2(options)(ctx);
|
|
1728
|
+
};
|
|
1729
|
+
}
|
|
1730
|
+
function createLazyDocsDriftRuntimeCliCommand(options = {}) {
|
|
1731
|
+
return {
|
|
1732
|
+
id: DOCS_DRIFT_CLI_ID,
|
|
1733
|
+
family: "drift",
|
|
1734
|
+
command: DOCS_DRIFT_CLI_COMMAND,
|
|
1735
|
+
description: "Scan documentation for stale code references.",
|
|
1736
|
+
usage: DOCS_DRIFT_CLI_COMMAND,
|
|
1737
|
+
projectRequired: true,
|
|
1738
|
+
run: async (context, args) => {
|
|
1739
|
+
const { executeDrift: executeDrift2 } = await Promise.resolve().then(() => (init_plugin(), exports_plugin));
|
|
1740
|
+
return executeDrift2(context, args, options);
|
|
1741
|
+
}
|
|
1742
|
+
};
|
|
1743
|
+
}
|
|
1744
|
+
function createStandardDocsDriftPlugin(opts = {}) {
|
|
1745
|
+
return definePlugin({
|
|
1746
|
+
name: "@rig/standard-plugin:docs-drift",
|
|
1747
|
+
version: "0.1.0",
|
|
1748
|
+
contributes: {
|
|
1749
|
+
validators: [DOCS_DRIFT_VALIDATOR],
|
|
1750
|
+
capabilities: [
|
|
1751
|
+
{ id: DOCS_DRIFT_CAPABILITY_ID, title: "Documentation drift detection", commandId: DOCS_DRIFT_CLI_ID, panelId: DOCS_HEALTH_PANEL_ID }
|
|
1752
|
+
],
|
|
1753
|
+
panels: [
|
|
1754
|
+
{ id: DOCS_HEALTH_PANEL_ID, slot: "capability", title: "Documentation drift", capabilityId: DOCS_DRIFT_CAPABILITY_ID }
|
|
1755
|
+
],
|
|
1756
|
+
cliCommands: [
|
|
1757
|
+
{
|
|
1758
|
+
id: DOCS_DRIFT_CLI_ID,
|
|
1759
|
+
family: "drift",
|
|
1760
|
+
command: DOCS_DRIFT_CLI_COMMAND,
|
|
1761
|
+
description: "Scan documentation for stale code references.",
|
|
1762
|
+
projectRequired: true
|
|
1763
|
+
}
|
|
1764
|
+
],
|
|
1765
|
+
stageMutations: [DOCS_DRIFT_STAGE_MUTATION]
|
|
1766
|
+
}
|
|
1767
|
+
}, {
|
|
1768
|
+
validators: [createLazyDocsDriftValidator(opts)],
|
|
1769
|
+
stages: { [DOCS_DRIFT_STAGE_ID]: createLazyDocsDriftGateStage(opts) },
|
|
1770
|
+
featureCapabilities: [
|
|
1771
|
+
{ id: DOCS_DRIFT_CAPABILITY_ID, title: "Documentation drift detection", commandId: DOCS_DRIFT_CLI_ID, panelId: DOCS_HEALTH_PANEL_ID }
|
|
1772
|
+
],
|
|
1773
|
+
panels: [
|
|
1774
|
+
{ id: DOCS_HEALTH_PANEL_ID, slot: "capability", title: "Documentation drift", capabilityId: DOCS_DRIFT_CAPABILITY_ID, produce: createDocsHealthPanelProducer(opts) }
|
|
1775
|
+
],
|
|
1776
|
+
cliCommands: [createLazyDocsDriftRuntimeCliCommand(opts)]
|
|
1777
|
+
});
|
|
1778
|
+
}
|
|
1779
|
+
function createStandardTaskSourcesPlugin(opts = {}) {
|
|
589
1780
|
return definePlugin({
|
|
590
|
-
name: "rig-
|
|
1781
|
+
name: "@rig/standard-plugin:task-sources",
|
|
591
1782
|
version: "0.1.0",
|
|
592
1783
|
contributes: {
|
|
593
1784
|
taskSources: [
|
|
@@ -609,13 +1800,13 @@ function standardPlugin(opts = {}) {
|
|
|
609
1800
|
id: "std:github-issues",
|
|
610
1801
|
kind: "github-issues",
|
|
611
1802
|
description: "GitHub Issues via gh CLI",
|
|
612
|
-
factory(config) {
|
|
1803
|
+
factory(config, context) {
|
|
613
1804
|
const options = {
|
|
614
1805
|
owner: requireStringField(config, "owner", "github-issues"),
|
|
615
1806
|
repo: requireStringField(config, "repo", "github-issues")
|
|
616
1807
|
};
|
|
617
|
-
|
|
618
|
-
|
|
1808
|
+
const credentialProviderOptions = context?.projectRoot ? { stateDir: resolve4(context.projectRoot, ".rig", "state") } : {};
|
|
1809
|
+
options.credentialProvider = opts.githubCredentialProvider ?? createStateGitHubCredentialProvider(credentialProviderOptions);
|
|
619
1810
|
if (opts.githubWorkspaceId)
|
|
620
1811
|
options.workspaceId = opts.githubWorkspaceId;
|
|
621
1812
|
if (opts.githubUserId)
|
|
@@ -637,6 +1828,12 @@ function standardPlugin(opts = {}) {
|
|
|
637
1828
|
const listLimit = typeof config.options?.listLimit === "number" ? config.options.listLimit : undefined;
|
|
638
1829
|
if (listLimit !== undefined)
|
|
639
1830
|
options.listLimit = listLimit;
|
|
1831
|
+
const projects = githubProjectsOptionsFromConfig(config, context);
|
|
1832
|
+
if (projects)
|
|
1833
|
+
options.projects = projects;
|
|
1834
|
+
const useNativeDependencies = booleanOption(config.options?.useNativeDependencies);
|
|
1835
|
+
if (useNativeDependencies !== undefined)
|
|
1836
|
+
options.useNativeDependencies = useNativeDependencies;
|
|
640
1837
|
return createGitHubIssuesTaskSource(options);
|
|
641
1838
|
}
|
|
642
1839
|
},
|
|
@@ -644,19 +1841,123 @@ function standardPlugin(opts = {}) {
|
|
|
644
1841
|
id: "std:files",
|
|
645
1842
|
kind: "files",
|
|
646
1843
|
description: "JSON files in a local directory",
|
|
647
|
-
factory(config) {
|
|
1844
|
+
factory(config, context) {
|
|
648
1845
|
return createFilesTaskSource({
|
|
649
|
-
path: requireStringField(config, "path", "files")
|
|
1846
|
+
path: requireStringField(config, "path", "files"),
|
|
1847
|
+
...context?.projectRoot ? { projectRoot: context.projectRoot } : {}
|
|
650
1848
|
});
|
|
651
1849
|
}
|
|
652
1850
|
}
|
|
653
1851
|
]
|
|
654
1852
|
});
|
|
655
1853
|
}
|
|
1854
|
+
|
|
1855
|
+
// packages/standard-plugin/src/bundle.ts
|
|
1856
|
+
function standardPlugins(options = {}) {
|
|
1857
|
+
return [
|
|
1858
|
+
createDefaultLifecyclePlugin(),
|
|
1859
|
+
createDependencyGraphPlugin(),
|
|
1860
|
+
createBlockerClassifierPlugin(),
|
|
1861
|
+
createPlanningPlugin(),
|
|
1862
|
+
createSupervisorPlugin(),
|
|
1863
|
+
standardCliSurfacePlugin,
|
|
1864
|
+
RUN_WORKER_PANEL_PLUGIN,
|
|
1865
|
+
createStandardTaskSourcesPlugin(options.taskSources),
|
|
1866
|
+
createStandardTaskCliPlugin(),
|
|
1867
|
+
createStandardDocsDriftPlugin(options.drift),
|
|
1868
|
+
createStandardProductEntrypointPlugin()
|
|
1869
|
+
];
|
|
1870
|
+
}
|
|
1871
|
+
// packages/standard-plugin/src/default-lifecycle.ts
|
|
1872
|
+
import {
|
|
1873
|
+
DEFAULT_LIFECYCLE_PLUGIN_ID,
|
|
1874
|
+
createDefaultLifecyclePlugin as createDefaultLifecyclePlugin2,
|
|
1875
|
+
defaultLifecyclePlugin
|
|
1876
|
+
} from "@rig/bundle-default-lifecycle/plugin";
|
|
1877
|
+
// packages/standard-plugin/src/dependency-graph.ts
|
|
1878
|
+
import {
|
|
1879
|
+
DEPENDENCY_GRAPH_CLI_ID,
|
|
1880
|
+
DEPENDENCY_GRAPH_PANEL_ID,
|
|
1881
|
+
EPICS_PANEL_ID,
|
|
1882
|
+
PEOPLE_PANEL_ID,
|
|
1883
|
+
WORKSPACE_STATUS_CLI_ID,
|
|
1884
|
+
WORKSPACE_SUMMARY_CLI_ID,
|
|
1885
|
+
createDependencyGraphPlugin as createDependencyGraphPlugin2,
|
|
1886
|
+
dependencyGraphPlugin
|
|
1887
|
+
} from "@rig/dependency-graph-plugin/plugin";
|
|
1888
|
+
// packages/standard-plugin/src/blocker-classifier.ts
|
|
1889
|
+
import {
|
|
1890
|
+
BLOCKERS_CLI_ID,
|
|
1891
|
+
BLOCKER_CLASSIFIER_PLUGIN_NAME,
|
|
1892
|
+
DEFAULT_BLOCKER_CLASSIFIER_ID,
|
|
1893
|
+
HUMAN_BLOCKERS_PANEL_ID,
|
|
1894
|
+
blockerClassifierPlugin,
|
|
1895
|
+
createBlockerClassifierPlugin as createBlockerClassifierPlugin2
|
|
1896
|
+
} from "@rig/blocker-classifier-plugin/plugin";
|
|
1897
|
+
// packages/standard-plugin/src/planning.ts
|
|
1898
|
+
import {
|
|
1899
|
+
PLANNING_PLAN_PANEL_ID,
|
|
1900
|
+
PLANNING_PLUGIN_NAME,
|
|
1901
|
+
createPlanningPlugin as createPlanningPlugin2,
|
|
1902
|
+
planningPlugin
|
|
1903
|
+
} from "@rig/planning-plugin/plugin";
|
|
1904
|
+
// packages/standard-plugin/src/supervisor.ts
|
|
1905
|
+
import {
|
|
1906
|
+
SUPERVISOR_PLUGIN_NAME,
|
|
1907
|
+
createSupervisorPlugin as createSupervisorPlugin2,
|
|
1908
|
+
supervisorPlugin
|
|
1909
|
+
} from "@rig/supervisor-plugin/plugin";
|
|
656
1910
|
export {
|
|
657
|
-
|
|
1911
|
+
supervisorPlugin,
|
|
1912
|
+
standardTaskCliPlugin,
|
|
1913
|
+
standardProductEntrypointPlugin,
|
|
1914
|
+
standardPlugins,
|
|
1915
|
+
standardCliSurfacePlugin,
|
|
1916
|
+
planningPlugin,
|
|
1917
|
+
dependencyGraphPlugin,
|
|
1918
|
+
defaultLifecyclePlugin,
|
|
1919
|
+
createSupervisorPlugin2 as createSupervisorPlugin,
|
|
658
1920
|
createStateGitHubCredentialProvider,
|
|
1921
|
+
createStandardTaskSourcesPlugin,
|
|
1922
|
+
createStandardTaskCliPlugin,
|
|
1923
|
+
createStandardProductEntrypointPlugin,
|
|
1924
|
+
createStandardDocsDriftPlugin,
|
|
1925
|
+
createStandardCliSurfacePlugin,
|
|
1926
|
+
createPlanningPlugin2 as createPlanningPlugin,
|
|
659
1927
|
createGitHubIssuesTaskSource,
|
|
660
1928
|
createFilesTaskSource,
|
|
661
|
-
createEnvGitHubCredentialProvider
|
|
1929
|
+
createEnvGitHubCredentialProvider,
|
|
1930
|
+
createDependencyGraphPlugin2 as createDependencyGraphPlugin,
|
|
1931
|
+
createDefaultLifecyclePlugin2 as createDefaultLifecyclePlugin,
|
|
1932
|
+
createBlockerClassifierPlugin2 as createBlockerClassifierPlugin,
|
|
1933
|
+
blockerClassifierPlugin,
|
|
1934
|
+
WORKSPACE_SUMMARY_CLI_ID,
|
|
1935
|
+
WORKSPACE_STATUS_CLI_ID,
|
|
1936
|
+
SUPERVISOR_PLUGIN_NAME,
|
|
1937
|
+
STANDARD_CLI_SURFACE_PLUGIN_NAME,
|
|
1938
|
+
RUN_WORKER_PANEL_PLUGIN,
|
|
1939
|
+
RUN_SUPERVISOR_PANEL_REGISTRATION,
|
|
1940
|
+
RUN_SUPERVISOR_PANEL_PRODUCER,
|
|
1941
|
+
RIG_SUPERVISOR_PANEL_ID,
|
|
1942
|
+
RIG_RUN_STOP_PANEL_ACTION,
|
|
1943
|
+
RIG_CAPABILITY_PANEL_SLOT,
|
|
1944
|
+
PLANNING_PLUGIN_NAME,
|
|
1945
|
+
PLANNING_PLAN_PANEL_ID,
|
|
1946
|
+
PEOPLE_PANEL_ID,
|
|
1947
|
+
HUMAN_BLOCKERS_PANEL_ID,
|
|
1948
|
+
EPICS_PANEL_ID,
|
|
1949
|
+
DOCS_HEALTH_PANEL_ID,
|
|
1950
|
+
DOCS_DRIFT_VALIDATOR_ID,
|
|
1951
|
+
DOCS_DRIFT_VALIDATOR,
|
|
1952
|
+
DOCS_DRIFT_STAGE_MUTATION,
|
|
1953
|
+
DOCS_DRIFT_STAGE_ID,
|
|
1954
|
+
DOCS_DRIFT_CLI_ID,
|
|
1955
|
+
DOCS_DRIFT_CLI_COMMAND,
|
|
1956
|
+
DOCS_DRIFT_CAPABILITY_ID,
|
|
1957
|
+
DEPENDENCY_GRAPH_PANEL_ID,
|
|
1958
|
+
DEPENDENCY_GRAPH_CLI_ID,
|
|
1959
|
+
DEFAULT_LIFECYCLE_PLUGIN_ID,
|
|
1960
|
+
DEFAULT_BLOCKER_CLASSIFIER_ID,
|
|
1961
|
+
BLOCKER_CLASSIFIER_PLUGIN_NAME,
|
|
1962
|
+
BLOCKERS_CLI_ID
|
|
662
1963
|
};
|