@h-rig/standard-plugin 0.0.6-alpha.142 → 0.0.6-alpha.144
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 +6 -5
- package/dist/src/bundle.js +1554 -1305
- package/dist/src/cli-surface.d.ts +5 -0
- package/dist/src/cli-surface.js +55 -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/metadata.d.ts +13 -0
- package/dist/src/drift/metadata.js +33 -0
- package/dist/src/drift/plugin.d.ts +4 -14
- package/dist/src/drift/plugin.js +4 -5
- package/dist/src/github-issues-source.js +73 -30
- package/dist/src/index.d.ts +7 -3
- package/dist/src/index.js +1636 -1397
- 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 +9 -11
- package/dist/src/plugin.js +1515 -1386
- package/dist/src/product-entrypoint.d.ts +3 -0
- package/dist/src/product-entrypoint.js +6 -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 +54 -8
package/dist/src/index.js
CHANGED
|
@@ -1,1488 +1,1630 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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);
|
|
5
17
|
|
|
6
|
-
// packages/standard-plugin/src/
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
return {
|
|
16
|
-
async resolveGitHubToken(input) {
|
|
17
|
-
if (input.purpose === "selected-repo") {
|
|
18
|
-
return { token: cleanToken(process.env.RIG_GITHUB_SELECTED_TOKEN ?? process.env.RIG_GITHUB_TOKEN ?? null) ?? "", source: "signed-in-user" };
|
|
19
|
-
}
|
|
20
|
-
const token = cleanToken(process.env.RIG_GITHUB_TOKEN ?? process.env.GH_TOKEN ?? process.env.GITHUB_TOKEN ?? null);
|
|
21
|
-
if (!token) {
|
|
22
|
-
throw new Error("No host GitHub token is configured for admin fallback.");
|
|
23
|
-
}
|
|
24
|
-
return { token, source: "host-admin-fallback" };
|
|
25
|
-
}
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
function createStateGitHubCredentialProvider(options = {}) {
|
|
29
|
-
const stateFileCandidates = () => {
|
|
30
|
-
const candidates = [];
|
|
31
|
-
const explicitFile = options.stateFile ?? process.env.RIG_GITHUB_AUTH_STATE_FILE;
|
|
32
|
-
if (explicitFile?.trim())
|
|
33
|
-
candidates.push(resolve(explicitFile.trim()));
|
|
34
|
-
for (const dir of [options.stateDir, process.env.RIG_STATE_DIR]) {
|
|
35
|
-
if (dir?.trim())
|
|
36
|
-
candidates.push(resolve(dir.trim(), "github-auth.json"));
|
|
37
|
-
}
|
|
38
|
-
return candidates;
|
|
39
|
-
};
|
|
40
|
-
const readToken = () => {
|
|
41
|
-
for (const stateFile of stateFileCandidates()) {
|
|
42
|
-
if (!existsSync(stateFile))
|
|
43
|
-
continue;
|
|
44
|
-
try {
|
|
45
|
-
const parsed = JSON.parse(readFileSync(stateFile, "utf8"));
|
|
46
|
-
const token = typeof parsed.token === "string" ? cleanToken(parsed.token) : null;
|
|
47
|
-
if (token)
|
|
48
|
-
return token;
|
|
49
|
-
} catch {}
|
|
50
|
-
}
|
|
51
|
-
return null;
|
|
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."
|
|
52
27
|
};
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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 "";
|
|
67
48
|
}
|
|
68
|
-
|
|
49
|
+
return fenced ? "" : line;
|
|
50
|
+
});
|
|
69
51
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
var RIG_METADATA_START = "<!-- rig:metadata:start -->";
|
|
73
|
-
var RIG_METADATA_END = "<!-- rig:metadata:end -->";
|
|
74
|
-
var RIG_STATUS_LABELS = new Set(["rig:running", "rig:pr-open", "rig:ci-fixing", "rig:merging", "rig:done", "rig:needs-attention"]);
|
|
75
|
-
var DEFAULT_GH_TIMEOUT_MS = 15000;
|
|
76
|
-
var DEFAULT_GITHUB_ISSUE_LIST_LIMIT = 1000;
|
|
77
|
-
function statusFor(issue) {
|
|
78
|
-
const state = (issue.state ?? "").toUpperCase();
|
|
79
|
-
if (state === "CLOSED")
|
|
80
|
-
return "closed";
|
|
81
|
-
const labelNames = labelNamesFor(issue);
|
|
82
|
-
if (labelNames.includes("in-progress"))
|
|
83
|
-
return "in_progress";
|
|
84
|
-
if (labelNames.includes("blocked"))
|
|
85
|
-
return "blocked";
|
|
86
|
-
if (labelNames.includes("ready"))
|
|
87
|
-
return "ready";
|
|
88
|
-
if (labelNames.includes("under-review"))
|
|
89
|
-
return "under_review";
|
|
90
|
-
if (labelNames.includes("failed"))
|
|
91
|
-
return "failed";
|
|
92
|
-
if (labelNames.includes("cancelled"))
|
|
93
|
-
return "cancelled";
|
|
94
|
-
return "open";
|
|
52
|
+
function normalizeToken(raw) {
|
|
53
|
+
return raw.trim().replace(/^['"]|['"]$/g, "").replace(/[),.;:]+$/g, "").replace(/#L\d+(?:-L\d+)?$/i, "");
|
|
95
54
|
}
|
|
96
|
-
function
|
|
97
|
-
|
|
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;
|
|
98
63
|
}
|
|
99
|
-
function
|
|
100
|
-
const
|
|
101
|
-
if (!
|
|
102
|
-
return
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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);
|
|
111
85
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
for (let cursor = index + 1;cursor < lines.length; cursor += 1) {
|
|
115
|
-
const item = lines[cursor].match(/^\s*-\s*(.+)$/);
|
|
116
|
-
if (!item)
|
|
117
|
-
break;
|
|
118
|
-
values.push(...parseIssueRefs(item[1]));
|
|
86
|
+
for (const match of line.matchAll(MARKDOWN_LINK)) {
|
|
87
|
+
pushReference(refs, seen, match[1] ?? "", lineNumber);
|
|
119
88
|
}
|
|
120
89
|
}
|
|
121
|
-
return
|
|
122
|
-
}
|
|
123
|
-
function parseDeps(body) {
|
|
124
|
-
const match = body.match(/^depends-on:\s*([^\n]+)/im);
|
|
125
|
-
const bodyRefs = match ? parseIssueRefs(match[1]) : [];
|
|
126
|
-
return [...new Set([...bodyRefs, ...parseMetadataList(body, "depends-on")])];
|
|
90
|
+
return refs;
|
|
127
91
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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;
|
|
132
105
|
}
|
|
133
|
-
function
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
if (typed)
|
|
137
|
-
return typed.slice("type:".length);
|
|
138
|
-
if (labels.includes("epic"))
|
|
139
|
-
return "epic";
|
|
140
|
-
return "task";
|
|
106
|
+
function lineCount(output) {
|
|
107
|
+
const trimmed = output.trim();
|
|
108
|
+
return trimmed ? trimmed.split(/\r?\n/).length : 0;
|
|
141
109
|
}
|
|
142
|
-
function
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
+
}
|
|
152
129
|
return {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
+
}
|
|
169
156
|
};
|
|
170
157
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
return typeof label.name === "string" ? [label.name] : [];
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
function yamlScalar(value) {
|
|
179
|
-
if (Array.isArray(value)) {
|
|
180
|
-
return value.length === 0 ? "[]" : `
|
|
181
|
-
${value.map((entry) => ` - ${String(entry)}`).join(`
|
|
182
|
-
`)}`;
|
|
183
|
-
}
|
|
184
|
-
if (value && typeof value === "object")
|
|
185
|
-
return JSON.stringify(value);
|
|
186
|
-
return String(value);
|
|
187
|
-
}
|
|
188
|
-
function updateRigOwnedMetadataBlock(body, metadata) {
|
|
189
|
-
const rendered = [
|
|
190
|
-
RIG_METADATA_START,
|
|
191
|
-
...Object.entries(metadata).map(([key, value]) => Array.isArray(value) ? `${key}:${yamlScalar(value)}` : `${key}: ${yamlScalar(value)}`),
|
|
192
|
-
RIG_METADATA_END
|
|
193
|
-
].join(`
|
|
194
|
-
`);
|
|
195
|
-
const pattern = new RegExp(`${RIG_METADATA_START.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*[\\s\\S]*?\\s*${RIG_METADATA_END.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`);
|
|
196
|
-
if (pattern.test(body))
|
|
197
|
-
return body.replace(pattern, rendered);
|
|
198
|
-
return body.trim().length > 0 ? `${body.trimEnd()}
|
|
158
|
+
var execFileAsync;
|
|
159
|
+
var init_git_adapter = __esm(() => {
|
|
160
|
+
execFileAsync = promisify(execFile);
|
|
161
|
+
});
|
|
199
162
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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);
|
|
203
185
|
}
|
|
204
|
-
function
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
`### Rig status: ${input.status}`,
|
|
208
|
-
"",
|
|
209
|
-
input.summary
|
|
210
|
-
];
|
|
211
|
-
if (input.runId)
|
|
212
|
-
lines.push("", `- Run: ${input.runId}`);
|
|
213
|
-
if (input.prUrl)
|
|
214
|
-
lines.push(`- PR: ${input.prUrl}`);
|
|
215
|
-
for (const detail of input.details ?? [])
|
|
216
|
-
lines.push(`- ${detail}`);
|
|
217
|
-
return lines.join(`
|
|
218
|
-
`);
|
|
186
|
+
function isDefaultDoc(path) {
|
|
187
|
+
const lower = basename2(path).toLowerCase();
|
|
188
|
+
return (path.endsWith(".md") || path.endsWith(".mdx")) && !lower.startsWith("changelog") && !lower.includes("generated");
|
|
219
189
|
}
|
|
220
|
-
function
|
|
221
|
-
return
|
|
190
|
+
function isIgnored(path, patterns) {
|
|
191
|
+
return (patterns ?? []).some((pattern) => globLikeMatch(path, pattern));
|
|
222
192
|
}
|
|
223
|
-
function
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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);
|
|
233
217
|
}
|
|
234
|
-
}
|
|
218
|
+
}
|
|
219
|
+
await visit(root);
|
|
220
|
+
return files.sort();
|
|
235
221
|
}
|
|
236
|
-
function
|
|
237
|
-
|
|
238
|
-
|
|
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;
|
|
239
235
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
purpose
|
|
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"
|
|
249
244
|
};
|
|
250
|
-
const resolved = await opts.credentialProvider.resolveGitHubToken(input);
|
|
251
|
-
return credentialEnv(resolved.token);
|
|
252
245
|
}
|
|
253
|
-
function
|
|
254
|
-
|
|
255
|
-
|
|
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
|
+
};
|
|
256
255
|
}
|
|
257
|
-
function
|
|
258
|
-
const
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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;
|
|
264
268
|
}
|
|
265
|
-
function
|
|
266
|
-
const
|
|
267
|
-
const
|
|
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;
|
|
269
285
|
}
|
|
270
|
-
function
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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
|
+
}
|
|
274
302
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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).` };
|
|
278
376
|
}
|
|
377
|
+
return { kind: "allow" };
|
|
279
378
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
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
|
+
};
|
|
291
389
|
}
|
|
292
|
-
function
|
|
293
|
-
|
|
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
|
+
};
|
|
294
401
|
}
|
|
295
|
-
function
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
return "running";
|
|
306
|
-
case "under_review":
|
|
307
|
-
case "review":
|
|
308
|
-
case "pr_open":
|
|
309
|
-
return "prOpen";
|
|
310
|
-
case "ci_fixing":
|
|
311
|
-
case "fixing":
|
|
312
|
-
return "ciFixing";
|
|
313
|
-
case "merging":
|
|
314
|
-
case "merge":
|
|
315
|
-
return "merging";
|
|
316
|
-
case "closed":
|
|
317
|
-
case "completed":
|
|
318
|
-
case "done":
|
|
319
|
-
return "done";
|
|
320
|
-
case "blocked":
|
|
321
|
-
case "cancelled":
|
|
322
|
-
case "failed":
|
|
323
|
-
case "needs_attention":
|
|
324
|
-
return "needsAttention";
|
|
325
|
-
default:
|
|
326
|
-
return null;
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
function ghGraphQLFetch(bin, spawnFn, extraEnv, timeoutMs) {
|
|
330
|
-
return async (query, variables) => {
|
|
331
|
-
const args = ["api", "graphql", "-f", `query=${query}`];
|
|
332
|
-
for (const [key, value] of Object.entries(variables)) {
|
|
333
|
-
if (value === undefined || value === null)
|
|
334
|
-
continue;
|
|
335
|
-
args.push("-f", `${key}=${String(value)}`);
|
|
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
|
+
});
|
|
336
412
|
}
|
|
337
|
-
const response = runGh(bin, args, spawnFn, extraEnv, timeoutMs);
|
|
338
|
-
return asProjectRecord(response)?.data ?? response;
|
|
339
413
|
};
|
|
340
414
|
}
|
|
341
|
-
function
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
const record = asProjectRecord(value);
|
|
347
|
-
const number = typeof record?.number === "number" ? String(record.number) : projectString(record?.number);
|
|
348
|
-
if (!number)
|
|
349
|
-
return null;
|
|
350
|
-
const repository = asProjectRecord(record?.repository);
|
|
351
|
-
const owner = projectString(asProjectRecord(repository?.owner)?.login);
|
|
352
|
-
const name = projectString(repository?.name);
|
|
353
|
-
if (!owner || !name || `${owner}/${name}` === currentRepo)
|
|
354
|
-
return number;
|
|
355
|
-
return `${owner}/${name}#${number}`;
|
|
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;
|
|
356
420
|
}
|
|
357
|
-
function
|
|
358
|
-
const
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
}))];
|
|
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 };
|
|
365
428
|
}
|
|
366
|
-
|
|
367
|
-
const
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
number
|
|
377
|
-
repository { name owner { login } }
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
`;
|
|
384
|
-
try {
|
|
385
|
-
return {
|
|
386
|
-
deps: nativeDependencyRefsFrom(await input.fetchGraphQL(query, { issueId }, "gh-cli"), input.repo)
|
|
387
|
-
};
|
|
388
|
-
} catch (error) {
|
|
389
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
390
|
-
return { deps: [], degraded: detail };
|
|
391
|
-
}
|
|
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 };
|
|
392
439
|
}
|
|
393
|
-
function
|
|
394
|
-
|
|
395
|
-
|
|
440
|
+
function requireNoExtraArgs(args, usage) {
|
|
441
|
+
if (args.length > 0)
|
|
442
|
+
throw new Error(`Unexpected argument: ${args[0]}
|
|
443
|
+
Usage: ${usage}`);
|
|
396
444
|
}
|
|
397
|
-
function
|
|
398
|
-
|
|
399
|
-
const cleanDeps = (deps ?? []).map(formatIssueReference).filter((ref) => ref.length > 0);
|
|
400
|
-
const cleanParents = (parents ?? []).map(formatIssueReference).filter((ref) => ref.length > 0);
|
|
401
|
-
if (cleanDeps.length > 0)
|
|
402
|
-
lines.push(`depends-on: ${cleanDeps.join(", ")}`);
|
|
403
|
-
if (cleanParents.length > 0)
|
|
404
|
-
lines.push(`parents: ${cleanParents.join(", ")}`);
|
|
405
|
-
if (lines.length === 0)
|
|
406
|
-
return body;
|
|
407
|
-
return body.trim().length > 0 ? `${body.trimEnd()}
|
|
408
|
-
|
|
409
|
-
${lines.join(`
|
|
410
|
-
`)}` : lines.join(`
|
|
411
|
-
`);
|
|
445
|
+
function parseCsv(value) {
|
|
446
|
+
return value?.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0) ?? [];
|
|
412
447
|
}
|
|
413
|
-
function
|
|
414
|
-
const
|
|
415
|
-
|
|
416
|
-
metadata["depends-on"] = input.deps.map(formatIssueReference);
|
|
417
|
-
if (input.parents && input.parents.length > 0)
|
|
418
|
-
metadata.parents = input.parents.map(formatIssueReference);
|
|
419
|
-
return updateRigOwnedMetadataBlock(appendReferenceLines(input.body, input.deps, input.parents), metadata);
|
|
448
|
+
function driftSummary(report) {
|
|
449
|
+
const highConfidence = highConfidenceDriftFindings(report).length;
|
|
450
|
+
return { total: report.findings.length, highConfidence, degraded: report.degraded };
|
|
420
451
|
}
|
|
421
|
-
function
|
|
422
|
-
const
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
}
|
|
436
|
-
|
|
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
|
+
`));
|
|
437
476
|
}
|
|
438
|
-
|
|
477
|
+
return { ok: !failed, group: "drift", command: "scan", details };
|
|
439
478
|
}
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
`;
|
|
455
|
-
return projectStatusFieldFrom(await input.fetchGraphQL(query, { projectId: input.projectId }, input.token), input.projectId);
|
|
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
|
+
};
|
|
456
489
|
}
|
|
457
|
-
async function
|
|
458
|
-
const
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
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;
|
|
465
500
|
}
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
for (const node of Array.isArray(nodes) ? nodes : []) {
|
|
470
|
-
const record = asProjectRecord(node);
|
|
471
|
-
const content = asProjectRecord(record?.content);
|
|
472
|
-
if (projectString(content?.id) === input.issueNodeId) {
|
|
473
|
-
const id2 = projectString(record?.id);
|
|
474
|
-
if (id2)
|
|
475
|
-
return { id: id2, created: false };
|
|
501
|
+
if (arg === "--fail-on-drift") {
|
|
502
|
+
failOnDrift = true;
|
|
503
|
+
continue;
|
|
476
504
|
}
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
505
|
+
if (arg === "--docs") {
|
|
506
|
+
docsGlobs.push(takeOptionValue(args, index, arg));
|
|
507
|
+
index += 1;
|
|
508
|
+
continue;
|
|
481
509
|
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
if (!id)
|
|
487
|
-
throw new Error("GitHub Project item creation did not return an item id.");
|
|
488
|
-
return { id, created: true };
|
|
489
|
-
}
|
|
490
|
-
async function updateIssueProjectStatus(input) {
|
|
491
|
-
const mutation = `
|
|
492
|
-
mutation RigUpdateProjectStatus($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) {
|
|
493
|
-
updateProjectV2ItemFieldValue(input: {
|
|
494
|
-
projectId: $projectId,
|
|
495
|
-
itemId: $itemId,
|
|
496
|
-
fieldId: $fieldId,
|
|
497
|
-
value: { singleSelectOptionId: $optionId }
|
|
498
|
-
}) { projectV2Item { id } }
|
|
510
|
+
if (arg === "--ignore") {
|
|
511
|
+
ignoreGlobs.push(takeOptionValue(args, index, arg));
|
|
512
|
+
index += 1;
|
|
513
|
+
continue;
|
|
499
514
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
}, input.token);
|
|
507
|
-
}
|
|
508
|
-
function fetchIssueNodeId(bin, repo, spawnFn, id, extraEnv, timeoutMs) {
|
|
509
|
-
const issue = runGh(bin, ["issue", "view", String(id), "--repo", repo, "--json", "id"], spawnFn, extraEnv, timeoutMs);
|
|
510
|
-
return projectString(issue.id) ?? projectString(issue.nodeId) ?? projectString(issue.node_id);
|
|
511
|
-
}
|
|
512
|
-
async function syncGitHubProjectStatus(bin, repo, spawnFn, id, status, projects, extraEnv, timeoutMs) {
|
|
513
|
-
if (!projects?.enabled)
|
|
514
|
-
return;
|
|
515
|
-
const projectId = projectString(projects.projectId);
|
|
516
|
-
if (!projectId)
|
|
517
|
-
throw new Error("GitHub Projects status sync is enabled but projectId is missing.");
|
|
518
|
-
const lifecycleStatus = projectLifecycleStatusForTaskStatus(status);
|
|
519
|
-
if (!lifecycleStatus)
|
|
520
|
-
return;
|
|
521
|
-
const issueNodeId = fetchIssueNodeId(bin, repo, spawnFn, id, extraEnv, timeoutMs);
|
|
522
|
-
if (!issueNodeId)
|
|
523
|
-
throw new Error(`GitHub issue ${repo}#${id} did not expose a node id for Projects status sync.`);
|
|
524
|
-
const projectStatus = projectString(projects.statuses?.[lifecycleStatus]) ?? DEFAULT_PROJECT_STATUSES[lifecycleStatus];
|
|
525
|
-
const fetchGraphQL = ghGraphQLFetch(bin, spawnFn, extraEnv, timeoutMs);
|
|
526
|
-
const field = await resolveProjectStatusField({ projectId, token: "gh-cli", fetchGraphQL });
|
|
527
|
-
const option = field.options.find((candidate) => candidate.name.toLowerCase() === projectStatus.toLowerCase() || candidate.id === projectStatus);
|
|
528
|
-
if (!option)
|
|
529
|
-
throw new Error(`GitHub Project ${projectId} Status field does not contain option "${projectStatus}".`);
|
|
530
|
-
const item = await ensureIssueProjectItem({ projectId, issueNodeId, token: "gh-cli", fetchGraphQL });
|
|
531
|
-
await updateIssueProjectStatus({
|
|
532
|
-
projectId,
|
|
533
|
-
itemId: item.id,
|
|
534
|
-
fieldId: projectString(projects.statusFieldId) ?? field.id,
|
|
535
|
-
optionId: option.id,
|
|
536
|
-
token: "gh-cli",
|
|
537
|
-
fetchGraphQL
|
|
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 } : {}
|
|
538
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;
|
|
539
537
|
}
|
|
540
|
-
var
|
|
541
|
-
|
|
542
|
-
|
|
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
|
+
};
|
|
543
580
|
}
|
|
544
|
-
|
|
545
|
-
|
|
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/plugin.ts
|
|
599
|
+
import { resolve as resolve4 } from "path";
|
|
600
|
+
import { definePlugin } from "@rig/core/config";
|
|
601
|
+
|
|
602
|
+
// packages/standard-plugin/src/github-issues-source.ts
|
|
603
|
+
import { spawnSync } from "child_process";
|
|
604
|
+
import { existsSync, readFileSync } from "fs";
|
|
605
|
+
import { resolve } from "path";
|
|
606
|
+
function cleanToken(value) {
|
|
607
|
+
const trimmed = value?.trim() ?? "";
|
|
608
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
546
609
|
}
|
|
547
|
-
function
|
|
548
|
-
return
|
|
610
|
+
function createEnvGitHubCredentialProvider() {
|
|
611
|
+
return {
|
|
612
|
+
async resolveGitHubToken(input) {
|
|
613
|
+
if (input.purpose === "selected-repo") {
|
|
614
|
+
return { token: cleanToken(process.env.RIG_GITHUB_SELECTED_TOKEN ?? null) ?? "", source: "signed-in-user" };
|
|
615
|
+
}
|
|
616
|
+
const token = cleanToken(process.env.RIG_GITHUB_TOKEN ?? process.env.GH_TOKEN ?? process.env.GITHUB_TOKEN ?? null);
|
|
617
|
+
if (!token) {
|
|
618
|
+
throw new Error("No host GitHub token is configured for admin fallback.");
|
|
619
|
+
}
|
|
620
|
+
return { token, source: "host-admin-fallback" };
|
|
621
|
+
}
|
|
622
|
+
};
|
|
549
623
|
}
|
|
550
|
-
function
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
624
|
+
function createStateGitHubCredentialProvider(options = {}) {
|
|
625
|
+
const addCandidate = (candidates, path) => {
|
|
626
|
+
const trimmed = path?.trim();
|
|
627
|
+
if (!trimmed)
|
|
628
|
+
return;
|
|
629
|
+
const resolved = resolve(trimmed);
|
|
630
|
+
if (!candidates.includes(resolved))
|
|
631
|
+
candidates.push(resolved);
|
|
632
|
+
};
|
|
633
|
+
const addStateDir = (candidates, dir) => {
|
|
634
|
+
const trimmed = dir?.trim();
|
|
635
|
+
if (!trimmed)
|
|
636
|
+
return;
|
|
637
|
+
addCandidate(candidates, resolve(trimmed, "github-auth.json"));
|
|
638
|
+
};
|
|
639
|
+
const addProjectStateDir = (candidates, root) => {
|
|
640
|
+
const trimmed = root?.trim();
|
|
641
|
+
if (!trimmed)
|
|
642
|
+
return;
|
|
643
|
+
addStateDir(candidates, resolve(trimmed, ".rig", "state"));
|
|
644
|
+
};
|
|
645
|
+
const stateFileCandidates = () => {
|
|
646
|
+
const candidates = [];
|
|
647
|
+
addCandidate(candidates, options.stateFile ?? process.env.RIG_GITHUB_AUTH_STATE_FILE);
|
|
648
|
+
addStateDir(candidates, options.stateDir);
|
|
649
|
+
addStateDir(candidates, process.env.RIG_STATE_DIR);
|
|
650
|
+
addProjectStateDir(candidates, process.env.PROJECT_RIG_ROOT);
|
|
651
|
+
addProjectStateDir(candidates, process.env.RIG_PROJECT_ROOT);
|
|
652
|
+
addProjectStateDir(candidates, process.env.RIG_HOST_PROJECT_ROOT);
|
|
653
|
+
addProjectStateDir(candidates, process.cwd());
|
|
654
|
+
return candidates;
|
|
655
|
+
};
|
|
656
|
+
const readToken = () => {
|
|
657
|
+
for (const stateFile of stateFileCandidates()) {
|
|
658
|
+
if (!existsSync(stateFile))
|
|
659
|
+
continue;
|
|
660
|
+
try {
|
|
661
|
+
const parsed = JSON.parse(readFileSync(stateFile, "utf8"));
|
|
662
|
+
const token = typeof parsed.token === "string" ? cleanToken(parsed.token) : null;
|
|
663
|
+
if (token)
|
|
664
|
+
return token;
|
|
665
|
+
} catch {}
|
|
666
|
+
}
|
|
667
|
+
return null;
|
|
668
|
+
};
|
|
669
|
+
return {
|
|
670
|
+
async resolveGitHubToken(input) {
|
|
671
|
+
const token = readToken();
|
|
672
|
+
if (input.purpose === "selected-repo") {
|
|
673
|
+
return { token: token ?? cleanToken(process.env.RIG_GITHUB_SELECTED_TOKEN ?? null) ?? "", source: "signed-in-user" };
|
|
674
|
+
}
|
|
675
|
+
if (token) {
|
|
676
|
+
return { token, source: "signed-in-user" };
|
|
677
|
+
}
|
|
678
|
+
const fallback = cleanToken(process.env.RIG_GITHUB_TOKEN ?? process.env.GH_TOKEN ?? process.env.GITHUB_TOKEN ?? null);
|
|
679
|
+
if (!fallback) {
|
|
680
|
+
throw new Error("No signed-in GitHub token is stored for Rig and no host admin fallback token is configured.");
|
|
681
|
+
}
|
|
682
|
+
return { token: fallback, source: "host-admin-fallback" };
|
|
683
|
+
}
|
|
684
|
+
};
|
|
556
685
|
}
|
|
557
|
-
|
|
558
|
-
|
|
686
|
+
var STATUS_LABELS = new Set(["ready", "blocked", "in-progress", "under-review", "failed", "cancelled"]);
|
|
687
|
+
var RIG_STATUS_COMMENT_MARKER = "<!-- rig:status-comment -->";
|
|
688
|
+
var RIG_METADATA_START = "<!-- rig:metadata:start -->";
|
|
689
|
+
var RIG_METADATA_END = "<!-- rig:metadata:end -->";
|
|
690
|
+
var RIG_STATUS_LABELS = new Set(["rig:running", "rig:pr-open", "rig:ci-fixing", "rig:merging", "rig:done", "rig:needs-attention"]);
|
|
691
|
+
var DEFAULT_GH_TIMEOUT_MS = 15000;
|
|
692
|
+
var DEFAULT_GITHUB_ISSUE_LIST_LIMIT = 1000;
|
|
693
|
+
function statusFor(issue) {
|
|
694
|
+
const state = (issue.state ?? "").toUpperCase();
|
|
695
|
+
if (state === "CLOSED")
|
|
696
|
+
return "closed";
|
|
697
|
+
const labelNames = labelNamesFor(issue);
|
|
698
|
+
if (labelNames.includes("in-progress"))
|
|
699
|
+
return "in_progress";
|
|
700
|
+
if (labelNames.includes("blocked"))
|
|
701
|
+
return "blocked";
|
|
702
|
+
if (labelNames.includes("ready"))
|
|
703
|
+
return "ready";
|
|
704
|
+
if (labelNames.includes("under-review"))
|
|
705
|
+
return "under_review";
|
|
706
|
+
if (labelNames.includes("failed"))
|
|
707
|
+
return "failed";
|
|
708
|
+
if (labelNames.includes("cancelled"))
|
|
709
|
+
return "cancelled";
|
|
710
|
+
return "open";
|
|
559
711
|
}
|
|
560
|
-
function
|
|
561
|
-
|
|
712
|
+
function parseIssueRefs(raw) {
|
|
713
|
+
const refs = [...raw.matchAll(/(?:^|[^\w/.-])(?:[\w.-]+\/[\w.-]+#|#)?(\d+)\b/g)].map((match) => match[1]).filter((value) => Boolean(value));
|
|
714
|
+
return [...new Set(refs)];
|
|
562
715
|
}
|
|
563
|
-
function
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
throw new Error(`unsupported status: ${status}`);
|
|
716
|
+
function metadataKeyPattern(keys) {
|
|
717
|
+
return new RegExp(`^(?:${keys.map((key) => key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")}):\\s*(.*)$`, "i");
|
|
718
|
+
}
|
|
719
|
+
function parseMetadataList(body, keys) {
|
|
720
|
+
const block = body.match(/<!-- rig:metadata:start -->\s*([\s\S]*?)\s*<!-- rig:metadata:end -->/);
|
|
721
|
+
if (!block)
|
|
722
|
+
return [];
|
|
723
|
+
const lines = block[1].split(/\r?\n/);
|
|
724
|
+
const values = [];
|
|
725
|
+
const keyPattern = metadataKeyPattern(keys);
|
|
726
|
+
for (let index = 0;index < lines.length; index += 1) {
|
|
727
|
+
const line = lines[index];
|
|
728
|
+
const sameLine = line.match(keyPattern);
|
|
729
|
+
if (!sameLine)
|
|
730
|
+
continue;
|
|
731
|
+
const inlineValue = sameLine[1]?.trim() ?? "";
|
|
732
|
+
if (inlineValue) {
|
|
733
|
+
values.push(...parseIssueRefs(inlineValue));
|
|
734
|
+
continue;
|
|
735
|
+
}
|
|
736
|
+
for (let cursor = index + 1;cursor < lines.length; cursor += 1) {
|
|
737
|
+
const item = lines[cursor].match(/^\s*-\s*(.+)$/);
|
|
738
|
+
if (!item)
|
|
739
|
+
break;
|
|
740
|
+
values.push(...parseIssueRefs(item[1]));
|
|
741
|
+
}
|
|
590
742
|
}
|
|
743
|
+
return [...new Set(values)];
|
|
591
744
|
}
|
|
592
|
-
function
|
|
593
|
-
|
|
594
|
-
case "running":
|
|
595
|
-
case "in_progress":
|
|
596
|
-
return "rig:running";
|
|
597
|
-
case "under_review":
|
|
598
|
-
return "rig:pr-open";
|
|
599
|
-
case "closed":
|
|
600
|
-
case "completed":
|
|
601
|
-
return "rig:done";
|
|
602
|
-
case "ci_fixing":
|
|
603
|
-
return "rig:ci-fixing";
|
|
604
|
-
case "merging":
|
|
605
|
-
return "rig:merging";
|
|
606
|
-
case "needs_attention":
|
|
607
|
-
case "failed":
|
|
608
|
-
case "blocked":
|
|
609
|
-
return "rig:needs-attention";
|
|
610
|
-
case "ready":
|
|
611
|
-
case "cancelled":
|
|
612
|
-
case "open":
|
|
613
|
-
return null;
|
|
614
|
-
default:
|
|
615
|
-
return null;
|
|
616
|
-
}
|
|
745
|
+
function bodyWithoutRigMetadataBlock(body) {
|
|
746
|
+
return body.replace(/<!-- rig:metadata:start -->\s*[\s\S]*?\s*<!-- rig:metadata:end -->/g, "");
|
|
617
747
|
}
|
|
618
|
-
|
|
619
|
-
const
|
|
620
|
-
const
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
if (targetRigLabel !== null && l === targetRigLabel)
|
|
626
|
-
continue;
|
|
627
|
-
try {
|
|
628
|
-
runGhVoid(bin, ["issue", "edit", String(id), "--repo", repo, "--remove-label", l], spawnFn, extraEnv, timeoutMs);
|
|
629
|
-
} catch {}
|
|
630
|
-
}
|
|
631
|
-
for (const label of [targetLabel, targetRigLabel].filter((value) => Boolean(value))) {
|
|
632
|
-
try {
|
|
633
|
-
runGhVoid(bin, ["issue", "edit", String(id), "--repo", repo, "--add-label", label], spawnFn, extraEnv, timeoutMs);
|
|
634
|
-
} catch (error) {
|
|
635
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
636
|
-
if (!/not found/i.test(message)) {
|
|
637
|
-
throw error;
|
|
638
|
-
}
|
|
639
|
-
ensureStatusLabel(bin, repo, spawnFn, label, extraEnv, timeoutMs);
|
|
640
|
-
runGhVoid(bin, ["issue", "edit", String(id), "--repo", repo, "--add-label", label], spawnFn, extraEnv, timeoutMs);
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
if (isRunningStatus(status)) {
|
|
644
|
-
assignRunningIssue(bin, repo, spawnFn, id, runningAssignee, extraEnv, timeoutMs);
|
|
645
|
-
if (shouldSyncLifecycle) {
|
|
646
|
-
upsertRigStickyComment(bin, repo, spawnFn, String(id), buildRigStickyStatusComment({
|
|
647
|
-
status: "running",
|
|
648
|
-
summary: "Rig run started."
|
|
649
|
-
}), extraEnv, timeoutMs);
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
if (shouldSyncLifecycle) {
|
|
653
|
-
await syncGitHubProjectStatus(bin, repo, spawnFn, id, status, projects, extraEnv, timeoutMs);
|
|
654
|
-
}
|
|
655
|
-
if (status === "closed" || status === "completed") {
|
|
656
|
-
runGhVoid(bin, ["issue", "close", String(id), "--repo", repo], spawnFn, extraEnv, timeoutMs);
|
|
657
|
-
}
|
|
748
|
+
function parseBodyKeyRefs(body, keys) {
|
|
749
|
+
const keyPattern = metadataKeyPattern(keys);
|
|
750
|
+
const values = bodyWithoutRigMetadataBlock(body).split(/\r?\n/).flatMap((line) => {
|
|
751
|
+
const match = line.match(keyPattern);
|
|
752
|
+
return match?.[1] ? parseIssueRefs(match[1]) : [];
|
|
753
|
+
});
|
|
754
|
+
return [...new Set(values)];
|
|
658
755
|
}
|
|
659
|
-
function
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
"label",
|
|
663
|
-
"create",
|
|
664
|
-
label,
|
|
665
|
-
"--repo",
|
|
666
|
-
repo,
|
|
667
|
-
"--color",
|
|
668
|
-
"6f42c1",
|
|
669
|
-
"--description",
|
|
670
|
-
"Task status managed by Rig"
|
|
671
|
-
], spawnFn, extraEnv, timeoutMs);
|
|
672
|
-
} catch (error) {
|
|
673
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
674
|
-
if (!/already exists/i.test(message)) {
|
|
675
|
-
throw error;
|
|
676
|
-
}
|
|
677
|
-
}
|
|
756
|
+
function parseDeps(body) {
|
|
757
|
+
const keys = ["depends-on", "deps", "blocked-by", "blocked_by"];
|
|
758
|
+
return [...new Set([...parseBodyKeyRefs(body, keys), ...parseMetadataList(body, keys)])];
|
|
678
759
|
}
|
|
679
|
-
function
|
|
680
|
-
const
|
|
681
|
-
|
|
682
|
-
if (existing) {
|
|
683
|
-
runGhVoid(bin, ["api", "-X", "PATCH", `repos/${repo}/issues/comments/${existing.id}`, "-f", `body=${body}`], spawnFn, extraEnv, timeoutMs);
|
|
684
|
-
return;
|
|
685
|
-
}
|
|
686
|
-
runGhVoid(bin, ["api", "-X", "POST", `repos/${repo}/issues/${id}/comments`, "-f", `body=${body}`], spawnFn, extraEnv, timeoutMs);
|
|
760
|
+
function parseParents(body) {
|
|
761
|
+
const keys = ["parents", "parent"];
|
|
762
|
+
return [...new Set([...parseBodyKeyRefs(body, keys), ...parseMetadataList(body, keys)])];
|
|
687
763
|
}
|
|
688
|
-
function
|
|
689
|
-
|
|
764
|
+
function issueTypeFor(issue) {
|
|
765
|
+
const labels = labelNamesFor(issue);
|
|
766
|
+
const typed = labels.find((l) => l.startsWith("type:"));
|
|
767
|
+
if (typed)
|
|
768
|
+
return typed.slice("type:".length);
|
|
769
|
+
if (labels.includes("epic"))
|
|
770
|
+
return "epic";
|
|
771
|
+
return "task";
|
|
690
772
|
}
|
|
691
|
-
function
|
|
692
|
-
const
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
return
|
|
773
|
+
function issueToTask(issue, repo, nativeDependencies) {
|
|
774
|
+
const labelNames = labelNamesFor(issue);
|
|
775
|
+
const scope = labelNames.filter((l) => l.startsWith("scope:")).map((l) => l.slice("scope:".length));
|
|
776
|
+
const roleLabel = labelNames.find((l) => l.startsWith("role:"));
|
|
777
|
+
const role = roleLabel ? roleLabel.slice("role:".length) : undefined;
|
|
778
|
+
const validators = labelNames.filter((l) => l.startsWith("validator:")).map((l) => l.slice("validator:".length));
|
|
779
|
+
const body = issue.body ?? "";
|
|
780
|
+
const issueNodeId = issue.id ?? issue.nodeId ?? issue.node_id;
|
|
781
|
+
const parsedDeps = parseDeps(body);
|
|
782
|
+
const deps = nativeDependencies?.deps ? [...new Set([...parsedDeps, ...nativeDependencies.deps])] : parsedDeps;
|
|
783
|
+
return {
|
|
784
|
+
id: String(issue.number),
|
|
785
|
+
...typeof issueNodeId === "string" && issueNodeId.trim() ? { issueNodeId: issueNodeId.trim() } : {},
|
|
786
|
+
deps,
|
|
787
|
+
status: statusFor(issue),
|
|
788
|
+
title: issue.title,
|
|
789
|
+
body,
|
|
790
|
+
scope,
|
|
791
|
+
role,
|
|
792
|
+
validators,
|
|
793
|
+
url: issue.url ?? issue.html_url,
|
|
794
|
+
issueType: issueTypeFor(issue),
|
|
795
|
+
sourceIssueId: `${repo}#${issue.number}`,
|
|
796
|
+
parentChildDeps: parseParents(body),
|
|
797
|
+
labels: labelNames,
|
|
798
|
+
...nativeDependencies?.degraded ? { nativeDependenciesDegraded: true, nativeDependenciesError: nativeDependencies.degraded } : {},
|
|
799
|
+
raw: issue
|
|
800
|
+
};
|
|
702
801
|
}
|
|
703
|
-
function
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
try {
|
|
710
|
-
runGhVoid(bin, ["issue", "edit", String(id), "--repo", repo, action, label], spawnFn, extraEnv, timeoutMs);
|
|
711
|
-
} catch (error) {
|
|
712
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
713
|
-
if (!/not found/i.test(message))
|
|
714
|
-
throw error;
|
|
715
|
-
ensureStatusLabel(bin, repo, spawnFn, label, extraEnv, timeoutMs);
|
|
716
|
-
runGhVoid(bin, ["issue", "edit", String(id), "--repo", repo, action, label], spawnFn, extraEnv, timeoutMs);
|
|
717
|
-
}
|
|
718
|
-
continue;
|
|
719
|
-
}
|
|
720
|
-
try {
|
|
721
|
-
runGhVoid(bin, ["issue", "edit", String(id), "--repo", repo, action, label], spawnFn, extraEnv, timeoutMs);
|
|
722
|
-
} catch {}
|
|
723
|
-
}
|
|
802
|
+
function labelNamesFor(issue) {
|
|
803
|
+
return (issue.labels ?? []).flatMap((label) => {
|
|
804
|
+
if (typeof label === "string")
|
|
805
|
+
return [label];
|
|
806
|
+
return typeof label.name === "string" ? [label.name] : [];
|
|
807
|
+
});
|
|
724
808
|
}
|
|
725
|
-
|
|
726
|
-
if (
|
|
727
|
-
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
if (isRigStickyStatusComment(update.comment)) {
|
|
731
|
-
upsertRigStickyComment(bin, repo, spawnFn, String(id), update.comment, extraEnv, timeoutMs);
|
|
732
|
-
} else {
|
|
733
|
-
runGhVoid(bin, ["issue", "comment", String(id), "--repo", repo, "--body", update.comment], spawnFn, extraEnv, timeoutMs);
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
const editArgs = ["issue", "edit", String(id), "--repo", repo];
|
|
737
|
-
if (update.title?.trim()) {
|
|
738
|
-
editArgs.push("--title", update.title.trim());
|
|
739
|
-
}
|
|
740
|
-
const nextBody = update.metadata ? updateRigOwnedMetadataBlock(update.body ?? fetchIssueBody(bin, repo, spawnFn, id, extraEnv, timeoutMs) ?? "", update.metadata) : update.body;
|
|
741
|
-
if (nextBody !== undefined) {
|
|
742
|
-
editArgs.push("--body", nextBody);
|
|
743
|
-
}
|
|
744
|
-
if (editArgs.length > 5) {
|
|
745
|
-
runGhVoid(bin, editArgs, spawnFn, extraEnv, timeoutMs);
|
|
809
|
+
function yamlScalar(value) {
|
|
810
|
+
if (Array.isArray(value)) {
|
|
811
|
+
return value.length === 0 ? "[]" : `
|
|
812
|
+
${value.map((entry) => ` - ${String(entry)}`).join(`
|
|
813
|
+
`)}`;
|
|
746
814
|
}
|
|
815
|
+
if (value && typeof value === "object")
|
|
816
|
+
return JSON.stringify(value);
|
|
817
|
+
return String(value);
|
|
747
818
|
}
|
|
748
|
-
function
|
|
749
|
-
const
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
const
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
819
|
+
function updateRigOwnedMetadataBlock(body, metadata) {
|
|
820
|
+
const rendered = [
|
|
821
|
+
RIG_METADATA_START,
|
|
822
|
+
...Object.entries(metadata).map(([key, value]) => Array.isArray(value) ? `${key}:${yamlScalar(value)}` : `${key}: ${yamlScalar(value)}`),
|
|
823
|
+
RIG_METADATA_END
|
|
824
|
+
].join(`
|
|
825
|
+
`);
|
|
826
|
+
const pattern = new RegExp(`${RIG_METADATA_START.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*[\\s\\S]*?\\s*${RIG_METADATA_END.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`);
|
|
827
|
+
if (pattern.test(body))
|
|
828
|
+
return body.replace(pattern, rendered);
|
|
829
|
+
return body.trim().length > 0 ? `${body.trimEnd()}
|
|
830
|
+
|
|
831
|
+
${rendered}
|
|
832
|
+
` : `${rendered}
|
|
833
|
+
`;
|
|
834
|
+
}
|
|
835
|
+
function buildRigStickyStatusComment(input) {
|
|
836
|
+
const lines = [
|
|
837
|
+
RIG_STATUS_COMMENT_MARKER,
|
|
838
|
+
`### Rig status: ${input.status}`,
|
|
839
|
+
"",
|
|
840
|
+
input.summary
|
|
841
|
+
];
|
|
842
|
+
if (input.runId)
|
|
843
|
+
lines.push("", `- Run: ${input.runId}`);
|
|
844
|
+
if (input.prUrl)
|
|
845
|
+
lines.push(`- PR: ${input.prUrl}`);
|
|
846
|
+
for (const detail of input.details ?? [])
|
|
847
|
+
lines.push(`- ${detail}`);
|
|
848
|
+
return lines.join(`
|
|
849
|
+
`);
|
|
850
|
+
}
|
|
851
|
+
function isRigStickyStatusComment(body) {
|
|
852
|
+
return body.includes(RIG_STATUS_COMMENT_MARKER);
|
|
853
|
+
}
|
|
854
|
+
function ghSpawnOptions(extraEnv, timeoutMs) {
|
|
855
|
+
const env = {
|
|
856
|
+
...process.env,
|
|
857
|
+
...process.env.GH_TOKEN !== undefined ? { GH_TOKEN: process.env.GH_TOKEN } : {},
|
|
858
|
+
...process.env.GITHUB_TOKEN !== undefined ? { GITHUB_TOKEN: process.env.GITHUB_TOKEN } : {},
|
|
859
|
+
...process.env.RIG_GITHUB_TOKEN !== undefined ? { RIG_GITHUB_TOKEN: process.env.RIG_GITHUB_TOKEN } : {}
|
|
860
|
+
};
|
|
861
|
+
for (const [key, value] of Object.entries(extraEnv ?? {})) {
|
|
862
|
+
if (value === undefined)
|
|
863
|
+
delete env[key];
|
|
864
|
+
else
|
|
865
|
+
env[key] = value;
|
|
765
866
|
}
|
|
766
867
|
return {
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
const labelArg = opts.labels && opts.labels.length > 0 ? ["--label", opts.labels.join(",")] : [];
|
|
771
|
-
const assigneeArg = opts.assignee?.trim() ? ["--assignee", opts.assignee.trim()] : [];
|
|
772
|
-
const args = [
|
|
773
|
-
"issue",
|
|
774
|
-
"list",
|
|
775
|
-
"--repo",
|
|
776
|
-
repo,
|
|
777
|
-
...labelArg,
|
|
778
|
-
...assigneeArg,
|
|
779
|
-
"--state",
|
|
780
|
-
state,
|
|
781
|
-
"--limit",
|
|
782
|
-
String(listLimit),
|
|
783
|
-
"--json",
|
|
784
|
-
"number,title,body,labels,state,url,assignees,id"
|
|
785
|
-
];
|
|
786
|
-
const env = await resolveCredentialEnv(opts, "selected-repo");
|
|
787
|
-
const rawIssues = runGh(bin, args, spawnFn, env, timeoutMs);
|
|
788
|
-
if (rawIssues.length >= listLimit) {
|
|
789
|
-
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.`);
|
|
790
|
-
}
|
|
791
|
-
const issues = rawIssues.filter((issue) => !issue.pull_request);
|
|
792
|
-
return Promise.all(issues.map((issue) => issueToTaskWithOptionalNativeDependencies(issue, env)));
|
|
793
|
-
},
|
|
794
|
-
async get(id) {
|
|
795
|
-
const env = await resolveCredentialEnv(opts, "selected-repo");
|
|
796
|
-
let issue;
|
|
797
|
-
try {
|
|
798
|
-
issue = runGh(bin, [
|
|
799
|
-
"issue",
|
|
800
|
-
"view",
|
|
801
|
-
String(id),
|
|
802
|
-
"--repo",
|
|
803
|
-
repo,
|
|
804
|
-
"--json",
|
|
805
|
-
"number,title,body,labels,state,url,assignees,id"
|
|
806
|
-
], spawnFn, env, timeoutMs);
|
|
807
|
-
} catch (error) {
|
|
808
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
809
|
-
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)) {
|
|
810
|
-
return;
|
|
811
|
-
}
|
|
812
|
-
throw new Error(`Failed to read task ${id} from GitHub repo ${repo}: ${detail}`);
|
|
813
|
-
}
|
|
814
|
-
return issueToTaskWithOptionalNativeDependencies(issue, env);
|
|
815
|
-
},
|
|
816
|
-
async updateStatus(id, status) {
|
|
817
|
-
const env = await resolveCredentialEnv(opts, "selected-repo");
|
|
818
|
-
await applyIssueStatus(bin, repo, spawnFn, id, status, opts.projects, opts.assignee, issueUpdates, env, timeoutMs);
|
|
819
|
-
notifyTaskChanged(opts.onTaskChanged, repo, id, status);
|
|
820
|
-
},
|
|
821
|
-
async updateTask(id, update) {
|
|
822
|
-
const env = await resolveCredentialEnv(opts, "selected-repo");
|
|
823
|
-
await applyIssueUpdate(bin, repo, spawnFn, id, update, opts.projects, opts.assignee, issueUpdates, env, timeoutMs);
|
|
824
|
-
notifyTaskChanged(opts.onTaskChanged, repo, id, update.status);
|
|
825
|
-
},
|
|
826
|
-
async addLabels(id, labels) {
|
|
827
|
-
const env = await resolveCredentialEnv(opts, "selected-repo");
|
|
828
|
-
applyLabels(bin, repo, spawnFn, id, labels, "--add-label", env, timeoutMs);
|
|
829
|
-
notifyTaskChanged(opts.onTaskChanged, repo, id);
|
|
830
|
-
},
|
|
831
|
-
async removeLabels(id, labels) {
|
|
832
|
-
const env = await resolveCredentialEnv(opts, "selected-repo");
|
|
833
|
-
applyLabels(bin, repo, spawnFn, id, labels, "--remove-label", env, timeoutMs);
|
|
834
|
-
notifyTaskChanged(opts.onTaskChanged, repo, id);
|
|
835
|
-
},
|
|
836
|
-
async createIssue(input) {
|
|
837
|
-
const env = await resolveCredentialEnv(opts, "selected-repo");
|
|
838
|
-
const body = input.body ?? "";
|
|
839
|
-
const args = [
|
|
840
|
-
"api",
|
|
841
|
-
"-X",
|
|
842
|
-
"POST",
|
|
843
|
-
`repos/${repo}/issues`,
|
|
844
|
-
"-f",
|
|
845
|
-
`title=${input.title}`,
|
|
846
|
-
"-f",
|
|
847
|
-
`body=${body}`,
|
|
848
|
-
...(input.labels ?? []).flatMap((label) => ["-f", `labels[]=${label}`])
|
|
849
|
-
];
|
|
850
|
-
const issue = runGh(bin, args, spawnFn, env, timeoutMs);
|
|
851
|
-
notifyTaskChanged(opts.onTaskChanged, repo, String(issue.number));
|
|
852
|
-
return issueToTask({ ...issue, body: issue.body ?? body }, repo);
|
|
853
|
-
},
|
|
854
|
-
async create(input) {
|
|
855
|
-
const env = await resolveCredentialEnv(opts, "selected-repo");
|
|
856
|
-
const body = bodyForCreatedTask(input);
|
|
857
|
-
const args = [
|
|
858
|
-
"api",
|
|
859
|
-
"-X",
|
|
860
|
-
"POST",
|
|
861
|
-
`repos/${repo}/issues`,
|
|
862
|
-
"-f",
|
|
863
|
-
`title=${input.title}`,
|
|
864
|
-
"-f",
|
|
865
|
-
`body=${body}`,
|
|
866
|
-
"-f",
|
|
867
|
-
"labels[]=rig:generated"
|
|
868
|
-
];
|
|
869
|
-
const issue = runGh(bin, args, spawnFn, env, timeoutMs);
|
|
870
|
-
notifyTaskChanged(opts.onTaskChanged, repo, String(issue.number));
|
|
871
|
-
return issueToTask({ ...issue, body: issue.body ?? body }, repo);
|
|
872
|
-
},
|
|
873
|
-
async getIssueBody(id) {
|
|
874
|
-
const env = await resolveCredentialEnv(opts, "selected-repo");
|
|
875
|
-
return fetchIssueBody(bin, repo, spawnFn, id, env, timeoutMs);
|
|
876
|
-
}
|
|
868
|
+
encoding: "utf-8",
|
|
869
|
+
timeout: timeoutMs,
|
|
870
|
+
env
|
|
877
871
|
};
|
|
878
872
|
}
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
const
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
status: raw["status"] ?? "ready",
|
|
895
|
-
...scope.length > 0 ? { scope } : {},
|
|
896
|
-
...roleLabel ? { role: roleLabel.slice("role:".length) } : {},
|
|
897
|
-
...validators.length > 0 ? { validators, validation: validators } : {},
|
|
898
|
-
...raw
|
|
873
|
+
function credentialEnv(token) {
|
|
874
|
+
const clean = token?.trim() ?? "";
|
|
875
|
+
if (clean)
|
|
876
|
+
return { GH_TOKEN: clean, GITHUB_TOKEN: clean, RIG_GITHUB_TOKEN: clean };
|
|
877
|
+
return { GH_TOKEN: undefined, GITHUB_TOKEN: undefined, RIG_GITHUB_TOKEN: undefined };
|
|
878
|
+
}
|
|
879
|
+
async function resolveCredentialEnv(opts, purpose) {
|
|
880
|
+
if (!opts.credentialProvider)
|
|
881
|
+
return;
|
|
882
|
+
const input = {
|
|
883
|
+
owner: opts.owner,
|
|
884
|
+
repo: opts.repo,
|
|
885
|
+
workspaceId: opts.workspaceId ?? `${opts.owner}/${opts.repo}`,
|
|
886
|
+
...opts.userId ? { userId: opts.userId } : {},
|
|
887
|
+
purpose
|
|
899
888
|
};
|
|
889
|
+
const resolved = await opts.credentialProvider.resolveGitHubToken(input);
|
|
890
|
+
return credentialEnv(resolved.token);
|
|
900
891
|
}
|
|
901
|
-
function
|
|
902
|
-
const
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
892
|
+
function tokenDiagnostic(value) {
|
|
893
|
+
const clean = value?.trim() ?? "";
|
|
894
|
+
return clean ? `present(len=${clean.length})` : "missing";
|
|
895
|
+
}
|
|
896
|
+
function runGh(bin, args, spawn, extraEnv, timeoutMs) {
|
|
897
|
+
const options = ghSpawnOptions(extraEnv, timeoutMs);
|
|
898
|
+
const res = spawn(bin, [...args], options);
|
|
899
|
+
assertGhSuccess(args, res, options.env);
|
|
900
|
+
if (!res.stdout || res.stdout.trim() === "")
|
|
901
|
+
return [];
|
|
902
|
+
return JSON.parse(res.stdout);
|
|
903
|
+
}
|
|
904
|
+
function runGhVoid(bin, args, spawn, extraEnv, timeoutMs) {
|
|
905
|
+
const options = ghSpawnOptions(extraEnv, timeoutMs);
|
|
906
|
+
const res = spawn(bin, [...args], options);
|
|
907
|
+
assertGhSuccess(args, res, options.env);
|
|
908
|
+
}
|
|
909
|
+
function assertGhSuccess(args, res, env) {
|
|
910
|
+
if (res.error) {
|
|
911
|
+
const msg = res.error.message ?? String(res.error);
|
|
912
|
+
throw new Error(`gh CLI not available \u2014 install gh (brew install gh / apt install gh): ${msg}`);
|
|
906
913
|
}
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
914
|
+
if (res.status !== 0) {
|
|
915
|
+
throw new Error(`gh ${args.join(" ")} failed (exit ${res.status}): ${res.stderr}
|
|
916
|
+
[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)}`);
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
var DEFAULT_PROJECT_STATUSES = {
|
|
920
|
+
todo: "Todo",
|
|
921
|
+
running: "In Progress",
|
|
922
|
+
prOpen: "In Review",
|
|
923
|
+
ciFixing: "In Review",
|
|
924
|
+
merging: "In Review",
|
|
925
|
+
done: "Done",
|
|
926
|
+
needsAttention: "Needs Attention"
|
|
927
|
+
};
|
|
928
|
+
function asProjectRecord(value) {
|
|
929
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
930
|
+
}
|
|
931
|
+
function projectString(value) {
|
|
932
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
933
|
+
}
|
|
934
|
+
function projectLifecycleStatusForTaskStatus(status) {
|
|
935
|
+
const normalized = status?.trim().toLowerCase().replace(/[-\s]+/g, "_");
|
|
936
|
+
switch (normalized) {
|
|
937
|
+
case "draft":
|
|
938
|
+
case "open":
|
|
939
|
+
case "queued":
|
|
940
|
+
case "ready":
|
|
941
|
+
return "todo";
|
|
942
|
+
case "running":
|
|
943
|
+
case "in_progress":
|
|
944
|
+
return "running";
|
|
945
|
+
case "under_review":
|
|
946
|
+
case "review":
|
|
947
|
+
case "pr_open":
|
|
948
|
+
return "prOpen";
|
|
949
|
+
case "ci_fixing":
|
|
950
|
+
case "fixing":
|
|
951
|
+
return "ciFixing";
|
|
952
|
+
case "merging":
|
|
953
|
+
case "merge":
|
|
954
|
+
return "merging";
|
|
955
|
+
case "closed":
|
|
956
|
+
case "completed":
|
|
957
|
+
case "done":
|
|
958
|
+
return "done";
|
|
959
|
+
case "blocked":
|
|
960
|
+
case "cancelled":
|
|
961
|
+
case "failed":
|
|
962
|
+
case "needs_attention":
|
|
963
|
+
return "needsAttention";
|
|
964
|
+
default:
|
|
965
|
+
return null;
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
function ghGraphQLFetch(bin, spawnFn, extraEnv, timeoutMs) {
|
|
969
|
+
return async (query, variables) => {
|
|
970
|
+
const args = ["api", "graphql", "-f", `query=${query}`];
|
|
971
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
972
|
+
if (value === undefined || value === null)
|
|
913
973
|
continue;
|
|
914
|
-
|
|
915
|
-
try {
|
|
916
|
-
if (!statSync(p).isFile())
|
|
917
|
-
continue;
|
|
918
|
-
if (readTaskFile(p, pattern).id === id)
|
|
919
|
-
return p;
|
|
920
|
-
} catch {}
|
|
974
|
+
args.push("-f", `${key}=${String(value)}`);
|
|
921
975
|
}
|
|
922
|
-
|
|
976
|
+
const response = runGh(bin, args, spawnFn, extraEnv, timeoutMs);
|
|
977
|
+
return asProjectRecord(response)?.data ?? response;
|
|
923
978
|
};
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
979
|
+
}
|
|
980
|
+
function issueNodeIdFor(issue) {
|
|
981
|
+
const id = issue.id ?? issue.nodeId ?? issue.node_id;
|
|
982
|
+
return typeof id === "string" && id.trim().length > 0 ? id.trim() : null;
|
|
983
|
+
}
|
|
984
|
+
function nativeIssueDependencyRef(value, currentRepo) {
|
|
985
|
+
const record = asProjectRecord(value);
|
|
986
|
+
const number = typeof record?.number === "number" ? String(record.number) : projectString(record?.number);
|
|
987
|
+
if (!number)
|
|
988
|
+
return null;
|
|
989
|
+
const repository = asProjectRecord(record?.repository);
|
|
990
|
+
const owner = projectString(asProjectRecord(repository?.owner)?.login);
|
|
991
|
+
const name = projectString(repository?.name);
|
|
992
|
+
if (!owner || !name || `${owner}/${name}` === currentRepo)
|
|
993
|
+
return number;
|
|
994
|
+
return `${owner}/${name}#${number}`;
|
|
995
|
+
}
|
|
996
|
+
function nativeDependencyRefsFrom(data, currentRepo) {
|
|
997
|
+
const issue = asProjectRecord(asProjectRecord(data)?.node);
|
|
998
|
+
const blockedBy = asProjectRecord(issue?.blockedBy);
|
|
999
|
+
const nodes = Array.isArray(blockedBy?.nodes) ? blockedBy.nodes : [];
|
|
1000
|
+
return [...new Set(nodes.flatMap((node) => {
|
|
1001
|
+
const ref = nativeIssueDependencyRef(node, currentRepo);
|
|
1002
|
+
return ref ? [ref] : [];
|
|
1003
|
+
}))];
|
|
1004
|
+
}
|
|
1005
|
+
async function readNativeDependenciesForIssue(input) {
|
|
1006
|
+
const issueId = issueNodeIdFor(input.issue);
|
|
1007
|
+
if (!issueId)
|
|
1008
|
+
return { deps: [], degraded: "GitHub issue node id is unavailable." };
|
|
1009
|
+
const query = `
|
|
1010
|
+
query RigIssueNativeDependencies($issueId: ID!) {
|
|
1011
|
+
node(id: $issueId) {
|
|
1012
|
+
... on Issue {
|
|
1013
|
+
blockedBy(first: 100) {
|
|
1014
|
+
nodes {
|
|
1015
|
+
number
|
|
1016
|
+
repository { name owner { login } }
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
928
1021
|
}
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
1022
|
+
`;
|
|
1023
|
+
try {
|
|
1024
|
+
return {
|
|
1025
|
+
deps: nativeDependencyRefsFrom(await input.fetchGraphQL(query, { issueId }, "gh-cli"), input.repo)
|
|
1026
|
+
};
|
|
1027
|
+
} catch (error) {
|
|
1028
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
1029
|
+
return { deps: [], degraded: detail };
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
function formatIssueReference(ref) {
|
|
1033
|
+
const clean = ref.trim().replace(/^#/, "");
|
|
1034
|
+
return /^\d+$/.test(clean) ? `#${clean}` : clean;
|
|
1035
|
+
}
|
|
1036
|
+
function appendReferenceLines(body, deps, parents) {
|
|
1037
|
+
const lines = [];
|
|
1038
|
+
const cleanDeps = (deps ?? []).map(formatIssueReference).filter((ref) => ref.length > 0);
|
|
1039
|
+
const cleanParents = (parents ?? []).map(formatIssueReference).filter((ref) => ref.length > 0);
|
|
1040
|
+
if (cleanDeps.length > 0)
|
|
1041
|
+
lines.push(`depends-on: ${cleanDeps.join(", ")}`);
|
|
1042
|
+
if (cleanParents.length > 0)
|
|
1043
|
+
lines.push(`parents: ${cleanParents.join(", ")}`);
|
|
1044
|
+
if (lines.length === 0)
|
|
1045
|
+
return body;
|
|
1046
|
+
return body.trim().length > 0 ? `${body.trimEnd()}
|
|
1047
|
+
|
|
1048
|
+
${lines.join(`
|
|
1049
|
+
`)}` : lines.join(`
|
|
1050
|
+
`);
|
|
1051
|
+
}
|
|
1052
|
+
function bodyForCreatedTask(input) {
|
|
1053
|
+
const metadata = { ...input.metadata ?? {} };
|
|
1054
|
+
if (input.deps && input.deps.length > 0)
|
|
1055
|
+
metadata["depends-on"] = input.deps.map(formatIssueReference);
|
|
1056
|
+
if (input.parents && input.parents.length > 0)
|
|
1057
|
+
metadata.parents = input.parents.map(formatIssueReference);
|
|
1058
|
+
return updateRigOwnedMetadataBlock(appendReferenceLines(input.body, input.deps, input.parents), metadata);
|
|
1059
|
+
}
|
|
1060
|
+
function projectStatusFieldFrom(data, projectId) {
|
|
1061
|
+
const fields = asProjectRecord(asProjectRecord(asProjectRecord(data)?.node)?.fields)?.nodes;
|
|
1062
|
+
for (const node of Array.isArray(fields) ? fields : []) {
|
|
1063
|
+
const record = asProjectRecord(node);
|
|
1064
|
+
if (projectString(record?.name)?.toLowerCase() !== "status")
|
|
1065
|
+
continue;
|
|
1066
|
+
const id = projectString(record?.id);
|
|
1067
|
+
if (!id)
|
|
1068
|
+
continue;
|
|
1069
|
+
const options = Array.isArray(record?.options) ? record.options.flatMap((option) => {
|
|
1070
|
+
const optionRecord = asProjectRecord(option);
|
|
1071
|
+
const optionId = projectString(optionRecord?.id);
|
|
1072
|
+
const name = projectString(optionRecord?.name);
|
|
1073
|
+
return optionId && name ? [{ id: optionId, name }] : [];
|
|
1074
|
+
}) : [];
|
|
1075
|
+
return { id, name: "Status", options };
|
|
1076
|
+
}
|
|
1077
|
+
throw new Error(`GitHub Project ${projectId} does not expose a Status single-select field.`);
|
|
1078
|
+
}
|
|
1079
|
+
async function resolveProjectStatusField(input) {
|
|
1080
|
+
const query = `
|
|
1081
|
+
query RigProjectStatusField($projectId: ID!) {
|
|
1082
|
+
node(id: $projectId) {
|
|
1083
|
+
... on ProjectV2 {
|
|
1084
|
+
fields(first: 50) {
|
|
1085
|
+
nodes {
|
|
1086
|
+
... on ProjectV2FieldCommon { id name }
|
|
1087
|
+
... on ProjectV2SingleSelectField { id name options { id name } }
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
944
1090
|
}
|
|
945
|
-
|
|
1091
|
+
}
|
|
946
1092
|
}
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
const out = [];
|
|
957
|
-
for (const name of readdirSync(directory)) {
|
|
958
|
-
if (!pattern.test(name))
|
|
959
|
-
continue;
|
|
960
|
-
const p = join(directory, name);
|
|
961
|
-
try {
|
|
962
|
-
if (!statSync(p).isFile())
|
|
963
|
-
continue;
|
|
964
|
-
out.push(readTaskFile(p, pattern));
|
|
965
|
-
} catch (err) {
|
|
966
|
-
console.warn(`[files-source] skipped ${name}: ${err.message}`);
|
|
1093
|
+
`;
|
|
1094
|
+
return projectStatusFieldFrom(await input.fetchGraphQL(query, { projectId: input.projectId }, input.token), input.projectId);
|
|
1095
|
+
}
|
|
1096
|
+
async function ensureIssueProjectItem(input) {
|
|
1097
|
+
const query = `
|
|
1098
|
+
query RigFindProjectIssueItem($projectId: ID!) {
|
|
1099
|
+
node(id: $projectId) {
|
|
1100
|
+
... on ProjectV2 {
|
|
1101
|
+
items(first: 100) { nodes { id content { ... on Issue { id } } } }
|
|
967
1102
|
}
|
|
968
1103
|
}
|
|
969
|
-
return out;
|
|
970
|
-
},
|
|
971
|
-
async get(id) {
|
|
972
|
-
const all = await this.list();
|
|
973
|
-
return all.find((t) => t.id === id);
|
|
974
|
-
},
|
|
975
|
-
async updateStatus(id, status) {
|
|
976
|
-
applyUpdate(id, { status });
|
|
977
|
-
},
|
|
978
|
-
async updateTask(id, update) {
|
|
979
|
-
applyUpdate(id, update);
|
|
980
1104
|
}
|
|
981
|
-
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
import { basename as basename2, extname, relative, resolve as resolve3 } from "path";
|
|
992
|
-
|
|
993
|
-
// packages/standard-plugin/src/drift/extract-refs.ts
|
|
994
|
-
var INLINE_CODE = /`([^`\n]+)`/g;
|
|
995
|
-
var MARKDOWN_LINK = /\[[^\]]+\]\(([^)\s]+)\)/g;
|
|
996
|
-
var SYMBOL_REF = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)?$/;
|
|
997
|
-
var 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)$/;
|
|
998
|
-
function stripFenceLines(markdown) {
|
|
999
|
-
const lines = markdown.split(/\r?\n/);
|
|
1000
|
-
let fenced = false;
|
|
1001
|
-
return lines.map((line) => {
|
|
1002
|
-
if (/^\s*(```|~~~)/.test(line)) {
|
|
1003
|
-
fenced = !fenced;
|
|
1004
|
-
return "";
|
|
1105
|
+
`;
|
|
1106
|
+
const data = await input.fetchGraphQL(query, { projectId: input.projectId }, input.token);
|
|
1107
|
+
const nodes = asProjectRecord(asProjectRecord(asProjectRecord(data)?.node)?.items)?.nodes;
|
|
1108
|
+
for (const node of Array.isArray(nodes) ? nodes : []) {
|
|
1109
|
+
const record = asProjectRecord(node);
|
|
1110
|
+
const content = asProjectRecord(record?.content);
|
|
1111
|
+
if (projectString(content?.id) === input.issueNodeId) {
|
|
1112
|
+
const id2 = projectString(record?.id);
|
|
1113
|
+
if (id2)
|
|
1114
|
+
return { id: id2, created: false };
|
|
1005
1115
|
}
|
|
1006
|
-
|
|
1007
|
-
|
|
1116
|
+
}
|
|
1117
|
+
const mutation = `
|
|
1118
|
+
mutation RigAddIssueToProject($projectId: ID!, $contentId: ID!) {
|
|
1119
|
+
addProjectV2ItemById(input: { projectId: $projectId, contentId: $contentId }) { item { id } }
|
|
1120
|
+
}
|
|
1121
|
+
`;
|
|
1122
|
+
const created = await input.fetchGraphQL(mutation, { projectId: input.projectId, contentId: input.issueNodeId }, input.token);
|
|
1123
|
+
const addResult = asProjectRecord(asProjectRecord(created)?.addProjectV2ItemById);
|
|
1124
|
+
const id = projectString(asProjectRecord(addResult?.item)?.id);
|
|
1125
|
+
if (!id)
|
|
1126
|
+
throw new Error("GitHub Project item creation did not return an item id.");
|
|
1127
|
+
return { id, created: true };
|
|
1008
1128
|
}
|
|
1009
|
-
function
|
|
1010
|
-
|
|
1129
|
+
async function updateIssueProjectStatus(input) {
|
|
1130
|
+
const mutation = `
|
|
1131
|
+
mutation RigUpdateProjectStatus($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) {
|
|
1132
|
+
updateProjectV2ItemFieldValue(input: {
|
|
1133
|
+
projectId: $projectId,
|
|
1134
|
+
itemId: $itemId,
|
|
1135
|
+
fieldId: $fieldId,
|
|
1136
|
+
value: { singleSelectOptionId: $optionId }
|
|
1137
|
+
}) { projectV2Item { id } }
|
|
1138
|
+
}
|
|
1139
|
+
`;
|
|
1140
|
+
await input.fetchGraphQL(mutation, {
|
|
1141
|
+
projectId: input.projectId,
|
|
1142
|
+
itemId: input.itemId,
|
|
1143
|
+
fieldId: input.fieldId,
|
|
1144
|
+
optionId: input.optionId
|
|
1145
|
+
}, input.token);
|
|
1011
1146
|
}
|
|
1012
|
-
function
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
if (PATH_REF.test(raw))
|
|
1016
|
-
return "path";
|
|
1017
|
-
if (SYMBOL_REF.test(raw))
|
|
1018
|
-
return "symbol";
|
|
1019
|
-
return null;
|
|
1147
|
+
function fetchIssueNodeId(bin, repo, spawnFn, id, extraEnv, timeoutMs) {
|
|
1148
|
+
const issue = runGh(bin, ["issue", "view", String(id), "--repo", repo, "--json", "id"], spawnFn, extraEnv, timeoutMs);
|
|
1149
|
+
return projectString(issue.id) ?? projectString(issue.nodeId) ?? projectString(issue.node_id);
|
|
1020
1150
|
}
|
|
1021
|
-
function
|
|
1022
|
-
|
|
1023
|
-
if (!value)
|
|
1024
|
-
return;
|
|
1025
|
-
const kind = classifyReference(value);
|
|
1026
|
-
if (!kind)
|
|
1151
|
+
async function syncGitHubProjectStatus(bin, repo, spawnFn, id, status, projects, extraEnv, timeoutMs) {
|
|
1152
|
+
if (!projects?.enabled)
|
|
1027
1153
|
return;
|
|
1028
|
-
const
|
|
1029
|
-
if (
|
|
1154
|
+
const projectId = projectString(projects.projectId);
|
|
1155
|
+
if (!projectId)
|
|
1156
|
+
throw new Error("GitHub Projects status sync is enabled but projectId is missing.");
|
|
1157
|
+
const lifecycleStatus = projectLifecycleStatusForTaskStatus(status);
|
|
1158
|
+
if (!lifecycleStatus)
|
|
1030
1159
|
return;
|
|
1031
|
-
|
|
1032
|
-
|
|
1160
|
+
const issueNodeId = fetchIssueNodeId(bin, repo, spawnFn, id, extraEnv, timeoutMs);
|
|
1161
|
+
if (!issueNodeId)
|
|
1162
|
+
throw new Error(`GitHub issue ${repo}#${id} did not expose a node id for Projects status sync.`);
|
|
1163
|
+
const projectStatus = projectString(projects.statuses?.[lifecycleStatus]) ?? DEFAULT_PROJECT_STATUSES[lifecycleStatus];
|
|
1164
|
+
const fetchGraphQL = ghGraphQLFetch(bin, spawnFn, extraEnv, timeoutMs);
|
|
1165
|
+
const field = await resolveProjectStatusField({ projectId, token: "gh-cli", fetchGraphQL });
|
|
1166
|
+
const option = field.options.find((candidate) => candidate.name.toLowerCase() === projectStatus.toLowerCase() || candidate.id === projectStatus);
|
|
1167
|
+
if (!option)
|
|
1168
|
+
throw new Error(`GitHub Project ${projectId} Status field does not contain option "${projectStatus}".`);
|
|
1169
|
+
const item = await ensureIssueProjectItem({ projectId, issueNodeId, token: "gh-cli", fetchGraphQL });
|
|
1170
|
+
await updateIssueProjectStatus({
|
|
1171
|
+
projectId,
|
|
1172
|
+
itemId: item.id,
|
|
1173
|
+
fieldId: projectString(projects.statusFieldId) ?? field.id,
|
|
1174
|
+
optionId: option.id,
|
|
1175
|
+
token: "gh-cli",
|
|
1176
|
+
fetchGraphQL
|
|
1177
|
+
});
|
|
1033
1178
|
}
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1179
|
+
var TERMINAL_TASK_STATUSES = new Set(["closed", "completed", "merged", "cancelled", "resolved", "done"]);
|
|
1180
|
+
function normalizeTaskStatusToken(status) {
|
|
1181
|
+
return status?.trim().toLowerCase().replace(/[-\s]+/g, "_") ?? "";
|
|
1182
|
+
}
|
|
1183
|
+
function issueUpdatesMode(value) {
|
|
1184
|
+
return value === "off" || value === "minimal" || value === "lifecycle" ? value : "lifecycle";
|
|
1185
|
+
}
|
|
1186
|
+
function isTerminalTaskStatus(status) {
|
|
1187
|
+
return TERMINAL_TASK_STATUSES.has(normalizeTaskStatusToken(status));
|
|
1188
|
+
}
|
|
1189
|
+
function shouldWriteIssueUpdate(mode, status) {
|
|
1190
|
+
if (mode === "off")
|
|
1191
|
+
return false;
|
|
1192
|
+
if (mode === "lifecycle")
|
|
1193
|
+
return true;
|
|
1194
|
+
return isTerminalTaskStatus(status);
|
|
1195
|
+
}
|
|
1196
|
+
function isRunningStatus(status) {
|
|
1197
|
+
return normalizeTaskStatusToken(status) === "running";
|
|
1198
|
+
}
|
|
1199
|
+
function assignRunningIssue(bin, repo, spawnFn, id, assignee, extraEnv, timeoutMs) {
|
|
1200
|
+
runGhVoid(bin, ["issue", "edit", String(id), "--repo", repo, "--add-assignee", assignee?.trim() || "@me"], spawnFn, extraEnv, timeoutMs);
|
|
1201
|
+
}
|
|
1202
|
+
function statusLabelFor(status) {
|
|
1203
|
+
switch (status) {
|
|
1204
|
+
case "running":
|
|
1205
|
+
case "in_progress":
|
|
1206
|
+
return "in-progress";
|
|
1207
|
+
case "blocked":
|
|
1208
|
+
return "blocked";
|
|
1209
|
+
case "ready":
|
|
1210
|
+
return "ready";
|
|
1211
|
+
case "under_review":
|
|
1212
|
+
return "under-review";
|
|
1213
|
+
case "failed":
|
|
1214
|
+
return "failed";
|
|
1215
|
+
case "cancelled":
|
|
1216
|
+
return "cancelled";
|
|
1217
|
+
case "ci_fixing":
|
|
1218
|
+
return "under-review";
|
|
1219
|
+
case "merging":
|
|
1220
|
+
return "under-review";
|
|
1221
|
+
case "needs_attention":
|
|
1222
|
+
return "blocked";
|
|
1223
|
+
case "closed":
|
|
1224
|
+
case "completed":
|
|
1225
|
+
case "open":
|
|
1226
|
+
return null;
|
|
1227
|
+
default:
|
|
1228
|
+
throw new Error(`unsupported status: ${status}`);
|
|
1046
1229
|
}
|
|
1047
|
-
return refs;
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
// packages/standard-plugin/src/drift/git-adapter.ts
|
|
1051
|
-
import { execFile } from "child_process";
|
|
1052
|
-
import { promisify } from "util";
|
|
1053
|
-
var execFileAsync = promisify(execFile);
|
|
1054
|
-
function processError(value) {
|
|
1055
|
-
return value && typeof value === "object" ? value : null;
|
|
1056
1230
|
}
|
|
1057
|
-
function
|
|
1058
|
-
|
|
1059
|
-
|
|
1231
|
+
function rigStatusLabelFor(status) {
|
|
1232
|
+
switch (status) {
|
|
1233
|
+
case "running":
|
|
1234
|
+
case "in_progress":
|
|
1235
|
+
return "rig:running";
|
|
1236
|
+
case "under_review":
|
|
1237
|
+
return "rig:pr-open";
|
|
1238
|
+
case "closed":
|
|
1239
|
+
case "completed":
|
|
1240
|
+
return "rig:done";
|
|
1241
|
+
case "ci_fixing":
|
|
1242
|
+
return "rig:ci-fixing";
|
|
1243
|
+
case "merging":
|
|
1244
|
+
return "rig:merging";
|
|
1245
|
+
case "needs_attention":
|
|
1246
|
+
case "failed":
|
|
1247
|
+
case "blocked":
|
|
1248
|
+
return "rig:needs-attention";
|
|
1249
|
+
case "ready":
|
|
1250
|
+
case "cancelled":
|
|
1251
|
+
case "open":
|
|
1252
|
+
return null;
|
|
1253
|
+
default:
|
|
1254
|
+
return null;
|
|
1255
|
+
}
|
|
1060
1256
|
}
|
|
1061
|
-
function
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1257
|
+
async function applyIssueStatus(bin, repo, spawnFn, id, status, projects, runningAssignee, issueUpdates, extraEnv, timeoutMs) {
|
|
1258
|
+
const targetLabel = status === "closed" || status === "completed" ? null : statusLabelFor(status);
|
|
1259
|
+
const targetRigLabel = rigStatusLabelFor(status);
|
|
1260
|
+
const shouldSyncLifecycle = shouldWriteIssueUpdate(issueUpdates, status);
|
|
1261
|
+
for (const l of [...STATUS_LABELS, ...RIG_STATUS_LABELS]) {
|
|
1262
|
+
if (targetLabel !== null && l === targetLabel)
|
|
1263
|
+
continue;
|
|
1264
|
+
if (targetRigLabel !== null && l === targetRigLabel)
|
|
1265
|
+
continue;
|
|
1266
|
+
try {
|
|
1267
|
+
runGhVoid(bin, ["issue", "edit", String(id), "--repo", repo, "--remove-label", l], spawnFn, extraEnv, timeoutMs);
|
|
1268
|
+
} catch {}
|
|
1069
1269
|
}
|
|
1070
|
-
|
|
1270
|
+
for (const label of [targetLabel, targetRigLabel].filter((value) => Boolean(value))) {
|
|
1071
1271
|
try {
|
|
1072
|
-
|
|
1272
|
+
runGhVoid(bin, ["issue", "edit", String(id), "--repo", repo, "--add-label", label], spawnFn, extraEnv, timeoutMs);
|
|
1073
1273
|
} catch (error) {
|
|
1074
|
-
const
|
|
1075
|
-
if (
|
|
1076
|
-
return 0;
|
|
1077
|
-
throw error;
|
|
1078
|
-
}
|
|
1079
|
-
}
|
|
1080
|
-
return {
|
|
1081
|
-
async lastCommitTouching(path) {
|
|
1082
|
-
const commit = (await git(["log", "-n", "1", "--format=%H", "--", path])).trim();
|
|
1083
|
-
return commit || "HEAD";
|
|
1084
|
-
},
|
|
1085
|
-
async grepCount(symbolOrPath) {
|
|
1086
|
-
return grepCountAt(symbolOrPath, "HEAD");
|
|
1087
|
-
},
|
|
1088
|
-
async grepCountAtCommit(symbolOrPath, commit) {
|
|
1089
|
-
return grepCountAt(symbolOrPath, commit);
|
|
1090
|
-
},
|
|
1091
|
-
async wasRenamed(symbolOrPath, sinceCommit) {
|
|
1092
|
-
if (!symbolOrPath.includes("/") && !symbolOrPath.includes("."))
|
|
1093
|
-
return false;
|
|
1094
|
-
try {
|
|
1095
|
-
const output = await git(["log", "--name-status", "--format=", `${sinceCommit}..HEAD`]);
|
|
1096
|
-
return output.split(/\r?\n/).some((line) => {
|
|
1097
|
-
const match = line.match(/^R\d*\s+(.+?)\s+(.+)$/);
|
|
1098
|
-
return Boolean(match && (match[1] === symbolOrPath || match[2] === symbolOrPath));
|
|
1099
|
-
});
|
|
1100
|
-
} catch (error) {
|
|
1101
|
-
const detail = processError(error);
|
|
1102
|
-
if (detail?.code === 128)
|
|
1103
|
-
return false;
|
|
1274
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1275
|
+
if (!/not found/i.test(message)) {
|
|
1104
1276
|
throw error;
|
|
1105
1277
|
}
|
|
1278
|
+
ensureStatusLabel(bin, repo, spawnFn, label, extraEnv, timeoutMs);
|
|
1279
|
+
runGhVoid(bin, ["issue", "edit", String(id), "--repo", repo, "--add-label", label], spawnFn, extraEnv, timeoutMs);
|
|
1106
1280
|
}
|
|
1107
|
-
};
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1110
|
-
// packages/standard-plugin/src/drift/detect.ts
|
|
1111
|
-
var DEFAULT_IGNORED_DIRS = {
|
|
1112
|
-
".git": true,
|
|
1113
|
-
node_modules: true,
|
|
1114
|
-
dist: true,
|
|
1115
|
-
build: true,
|
|
1116
|
-
coverage: true,
|
|
1117
|
-
".next": true,
|
|
1118
|
-
vendor: true
|
|
1119
|
-
};
|
|
1120
|
-
var SOURCE_EXTENSIONS = {
|
|
1121
|
-
".ts": true,
|
|
1122
|
-
".tsx": true,
|
|
1123
|
-
".js": true,
|
|
1124
|
-
".jsx": true,
|
|
1125
|
-
".mjs": true,
|
|
1126
|
-
".cjs": true,
|
|
1127
|
-
".rs": true,
|
|
1128
|
-
".go": true,
|
|
1129
|
-
".py": true,
|
|
1130
|
-
".rb": true,
|
|
1131
|
-
".java": true,
|
|
1132
|
-
".kt": true,
|
|
1133
|
-
".swift": true,
|
|
1134
|
-
".c": true,
|
|
1135
|
-
".cc": true,
|
|
1136
|
-
".cpp": true,
|
|
1137
|
-
".h": true,
|
|
1138
|
-
".hpp": true,
|
|
1139
|
-
".json": true,
|
|
1140
|
-
".toml": true,
|
|
1141
|
-
".yml": true,
|
|
1142
|
-
".yaml": true
|
|
1143
|
-
};
|
|
1144
|
-
function globLikeMatch(path, pattern) {
|
|
1145
|
-
if (pattern === path)
|
|
1146
|
-
return true;
|
|
1147
|
-
if (pattern.startsWith("**/*"))
|
|
1148
|
-
return path.endsWith(pattern.slice(4));
|
|
1149
|
-
if (pattern.endsWith("/**"))
|
|
1150
|
-
return path.startsWith(pattern.slice(0, -3));
|
|
1151
|
-
if (pattern.includes("*")) {
|
|
1152
|
-
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
1153
|
-
return new RegExp(`^${escaped}$`).test(path);
|
|
1154
1281
|
}
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
return (patterns ?? []).some((pattern) => globLikeMatch(path, pattern));
|
|
1163
|
-
}
|
|
1164
|
-
async function collectFiles(root, options) {
|
|
1165
|
-
const files = [];
|
|
1166
|
-
async function visit(dir) {
|
|
1167
|
-
for (const entry of await readdir(dir, { withFileTypes: true })) {
|
|
1168
|
-
if (entry.isDirectory() && DEFAULT_IGNORED_DIRS[entry.name])
|
|
1169
|
-
continue;
|
|
1170
|
-
const absolute = resolve3(dir, entry.name);
|
|
1171
|
-
const rel = relative(root, absolute).replace(/\\/g, "/");
|
|
1172
|
-
if (isIgnored(rel, options.ignore))
|
|
1173
|
-
continue;
|
|
1174
|
-
if (entry.isDirectory()) {
|
|
1175
|
-
await visit(absolute);
|
|
1176
|
-
continue;
|
|
1177
|
-
}
|
|
1178
|
-
if (!entry.isFile())
|
|
1179
|
-
continue;
|
|
1180
|
-
if (options.docs) {
|
|
1181
|
-
const matchesConfigured = options.patterns && options.patterns.length > 0 ? options.patterns.some((pattern) => globLikeMatch(rel, pattern)) : isDefaultDoc(rel);
|
|
1182
|
-
if (matchesConfigured)
|
|
1183
|
-
files.push(rel);
|
|
1184
|
-
continue;
|
|
1185
|
-
}
|
|
1186
|
-
if (SOURCE_EXTENSIONS[extname(entry.name)])
|
|
1187
|
-
files.push(rel);
|
|
1282
|
+
if (isRunningStatus(status)) {
|
|
1283
|
+
assignRunningIssue(bin, repo, spawnFn, id, runningAssignee, extraEnv, timeoutMs);
|
|
1284
|
+
if (shouldSyncLifecycle) {
|
|
1285
|
+
upsertRigStickyComment(bin, repo, spawnFn, String(id), buildRigStickyStatusComment({
|
|
1286
|
+
status: "running",
|
|
1287
|
+
summary: "Rig run started."
|
|
1288
|
+
}), extraEnv, timeoutMs);
|
|
1188
1289
|
}
|
|
1189
1290
|
}
|
|
1190
|
-
|
|
1191
|
-
|
|
1291
|
+
if (shouldSyncLifecycle) {
|
|
1292
|
+
await syncGitHubProjectStatus(bin, repo, spawnFn, id, status, projects, extraEnv, timeoutMs);
|
|
1293
|
+
}
|
|
1294
|
+
if (status === "closed" || status === "completed") {
|
|
1295
|
+
runGhVoid(bin, ["issue", "close", String(id), "--repo", repo], spawnFn, extraEnv, timeoutMs);
|
|
1296
|
+
}
|
|
1192
1297
|
}
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1298
|
+
function ensureStatusLabel(bin, repo, spawnFn, label, extraEnv, timeoutMs) {
|
|
1299
|
+
try {
|
|
1300
|
+
runGhVoid(bin, [
|
|
1301
|
+
"label",
|
|
1302
|
+
"create",
|
|
1303
|
+
label,
|
|
1304
|
+
"--repo",
|
|
1305
|
+
repo,
|
|
1306
|
+
"--color",
|
|
1307
|
+
"6f42c1",
|
|
1308
|
+
"--description",
|
|
1309
|
+
"Task status managed by Rig"
|
|
1310
|
+
], spawnFn, extraEnv, timeoutMs);
|
|
1311
|
+
} catch (error) {
|
|
1312
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1313
|
+
if (!/already exists/i.test(message)) {
|
|
1314
|
+
throw error;
|
|
1315
|
+
}
|
|
1204
1316
|
}
|
|
1205
|
-
return count;
|
|
1206
1317
|
}
|
|
1207
|
-
function
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
};
|
|
1318
|
+
function upsertRigStickyComment(bin, repo, spawnFn, id, body, extraEnv, timeoutMs) {
|
|
1319
|
+
const comments = runGh(bin, ["api", `repos/${repo}/issues/${id}/comments`, "--paginate"], spawnFn, extraEnv, timeoutMs);
|
|
1320
|
+
const existing = comments.find((comment) => typeof comment.body === "string" && comment.body.includes(RIG_STATUS_COMMENT_MARKER));
|
|
1321
|
+
if (existing) {
|
|
1322
|
+
runGhVoid(bin, ["api", "-X", "PATCH", `repos/${repo}/issues/comments/${existing.id}`, "-f", `body=${body}`], spawnFn, extraEnv, timeoutMs);
|
|
1323
|
+
return;
|
|
1324
|
+
}
|
|
1325
|
+
runGhVoid(bin, ["api", "-X", "POST", `repos/${repo}/issues/${id}/comments`, "-f", `body=${body}`], spawnFn, extraEnv, timeoutMs);
|
|
1216
1326
|
}
|
|
1217
|
-
function
|
|
1218
|
-
|
|
1219
|
-
kind: "stale-anchor",
|
|
1220
|
-
docPath,
|
|
1221
|
-
line: reference.line,
|
|
1222
|
-
reference: reference.value,
|
|
1223
|
-
detail: `Documented path "${reference.value}" changed after this doc was last updated.`,
|
|
1224
|
-
confidence: "medium"
|
|
1225
|
-
};
|
|
1327
|
+
function notifyTaskChanged(onTaskChanged, repo, id, status) {
|
|
1328
|
+
onTaskChanged?.({ repo, id, ...status ? { status } : {}, reason: "github-issue-updated" });
|
|
1226
1329
|
}
|
|
1227
|
-
|
|
1228
|
-
const
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
return findings;
|
|
1330
|
+
function fetchIssueBody(bin, repo, spawnFn, id, extraEnv, timeoutMs) {
|
|
1331
|
+
const issue = runGh(bin, [
|
|
1332
|
+
"issue",
|
|
1333
|
+
"view",
|
|
1334
|
+
String(id),
|
|
1335
|
+
"--repo",
|
|
1336
|
+
repo,
|
|
1337
|
+
"--json",
|
|
1338
|
+
"body"
|
|
1339
|
+
], spawnFn, extraEnv, timeoutMs);
|
|
1340
|
+
return typeof issue.body === "string" ? issue.body : undefined;
|
|
1239
1341
|
}
|
|
1240
|
-
|
|
1241
|
-
const
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
if (
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1342
|
+
function applyLabels(bin, repo, spawnFn, id, labels, action, extraEnv, timeoutMs) {
|
|
1343
|
+
for (const rawLabel of labels) {
|
|
1344
|
+
const label = rawLabel.trim();
|
|
1345
|
+
if (!label)
|
|
1346
|
+
continue;
|
|
1347
|
+
if (action === "--add-label") {
|
|
1348
|
+
try {
|
|
1349
|
+
runGhVoid(bin, ["issue", "edit", String(id), "--repo", repo, action, label], spawnFn, extraEnv, timeoutMs);
|
|
1350
|
+
} catch (error) {
|
|
1351
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1352
|
+
if (!/not found/i.test(message))
|
|
1353
|
+
throw error;
|
|
1354
|
+
ensureStatusLabel(bin, repo, spawnFn, label, extraEnv, timeoutMs);
|
|
1355
|
+
runGhVoid(bin, ["issue", "edit", String(id), "--repo", repo, action, label], spawnFn, extraEnv, timeoutMs);
|
|
1356
|
+
}
|
|
1249
1357
|
continue;
|
|
1250
|
-
const sourceCommit = await git.lastCommitTouching(reference.value);
|
|
1251
|
-
if (sourceCommit !== docCommit && !await git.wasRenamed(reference.value, docCommit)) {
|
|
1252
|
-
findings.push(staleAnchorFinding(docPath, reference));
|
|
1253
1358
|
}
|
|
1359
|
+
try {
|
|
1360
|
+
runGhVoid(bin, ["issue", "edit", String(id), "--repo", repo, action, label], spawnFn, extraEnv, timeoutMs);
|
|
1361
|
+
} catch {}
|
|
1254
1362
|
}
|
|
1255
|
-
return findings;
|
|
1256
1363
|
}
|
|
1257
|
-
async function
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
for (const docPath of docs) {
|
|
1267
|
-
try {
|
|
1268
|
-
findings.push(...await detectDeletedReferences(options.projectRoot, docPath, git));
|
|
1269
|
-
findings.push(...await detectStaleAnchors(options.projectRoot, docPath, git));
|
|
1270
|
-
} catch {
|
|
1271
|
-
degraded = true;
|
|
1364
|
+
async function applyIssueUpdate(bin, repo, spawnFn, id, update, projects, runningAssignee, issueUpdates, extraEnv, timeoutMs) {
|
|
1365
|
+
if (update.status) {
|
|
1366
|
+
await applyIssueStatus(bin, repo, spawnFn, id, update.status, projects, runningAssignee, issueUpdates, extraEnv, timeoutMs);
|
|
1367
|
+
}
|
|
1368
|
+
if (update.comment?.trim() && shouldWriteIssueUpdate(issueUpdates, update.status)) {
|
|
1369
|
+
if (isRigStickyStatusComment(update.comment)) {
|
|
1370
|
+
upsertRigStickyComment(bin, repo, spawnFn, String(id), update.comment, extraEnv, timeoutMs);
|
|
1371
|
+
} else {
|
|
1372
|
+
runGhVoid(bin, ["issue", "comment", String(id), "--repo", repo, "--body", update.comment], spawnFn, extraEnv, timeoutMs);
|
|
1272
1373
|
}
|
|
1273
1374
|
}
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
var DOCS_DRIFT_CLI_ID = "std:drift";
|
|
1285
|
-
var DOCS_DRIFT_STAGE_ID = "docs-drift";
|
|
1286
|
-
var DOCS_DRIFT_CAPABILITY_ID = "std:docs-drift-capability";
|
|
1287
|
-
var DOCS_DRIFT_VALIDATOR = {
|
|
1288
|
-
id: DOCS_DRIFT_VALIDATOR_ID,
|
|
1289
|
-
category: "regression",
|
|
1290
|
-
description: "Detect documentation references that drifted from the source tree."
|
|
1291
|
-
};
|
|
1292
|
-
var DOCS_DRIFT_STAGE_MUTATION = Schema.decodeUnknownSync(StageMutationSchema)({
|
|
1293
|
-
op: "insert",
|
|
1294
|
-
stage: {
|
|
1295
|
-
id: DOCS_DRIFT_STAGE_ID,
|
|
1296
|
-
kind: "gate",
|
|
1297
|
-
before: ["merge-gate"],
|
|
1298
|
-
after: ["open-pr"]
|
|
1299
|
-
},
|
|
1300
|
-
contributedBy: DOCS_DRIFT_STAGE_ID
|
|
1301
|
-
});
|
|
1302
|
-
var DOCS_DRIFT_CLI_COMMAND = "rig drift [--docs <csv>] [--ignore <csv>] [--fail-on-drift] [--json]";
|
|
1303
|
-
function highConfidenceDriftFindings(report) {
|
|
1304
|
-
return report.findings.filter((finding) => finding.confidence === "high");
|
|
1305
|
-
}
|
|
1306
|
-
function driftGateResult(report, mode = "enforce") {
|
|
1307
|
-
const high = highConfidenceDriftFindings(report);
|
|
1308
|
-
if (mode === "enforce" && high.length > 0) {
|
|
1309
|
-
return { kind: "block", reason: `${high.length} high-confidence documentation drift finding(s).` };
|
|
1375
|
+
const editArgs = ["issue", "edit", String(id), "--repo", repo];
|
|
1376
|
+
if (update.title?.trim()) {
|
|
1377
|
+
editArgs.push("--title", update.title.trim());
|
|
1378
|
+
}
|
|
1379
|
+
const nextBody = update.metadata ? updateRigOwnedMetadataBlock(update.body ?? fetchIssueBody(bin, repo, spawnFn, id, extraEnv, timeoutMs) ?? "", update.metadata) : update.body;
|
|
1380
|
+
if (nextBody !== undefined) {
|
|
1381
|
+
editArgs.push("--body", nextBody);
|
|
1382
|
+
}
|
|
1383
|
+
if (editArgs.length > 5) {
|
|
1384
|
+
runGhVoid(bin, editArgs, spawnFn, extraEnv, timeoutMs);
|
|
1310
1385
|
}
|
|
1311
|
-
return { kind: "allow" };
|
|
1312
1386
|
}
|
|
1313
|
-
function
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1387
|
+
function createGitHubIssuesTaskSource(opts) {
|
|
1388
|
+
const bin = opts.ghBinary ?? "gh";
|
|
1389
|
+
const state = opts.state ?? "open";
|
|
1390
|
+
const repo = `${opts.owner}/${opts.repo}`;
|
|
1391
|
+
const spawnFn = opts.spawn ?? spawnSync;
|
|
1392
|
+
const timeoutMs = Math.max(1000, Math.trunc(opts.timeoutMs ?? DEFAULT_GH_TIMEOUT_MS));
|
|
1393
|
+
const listLimit = Math.max(1, Math.trunc(opts.listLimit ?? DEFAULT_GITHUB_ISSUE_LIST_LIMIT));
|
|
1394
|
+
const issueUpdates = issueUpdatesMode(opts.issueUpdates);
|
|
1395
|
+
async function issueToTaskWithOptionalNativeDependencies(issue, env) {
|
|
1396
|
+
if (!opts.useNativeDependencies)
|
|
1397
|
+
return issueToTask(issue, repo);
|
|
1398
|
+
const nativeDependencies = await readNativeDependenciesForIssue({
|
|
1399
|
+
issue,
|
|
1400
|
+
repo,
|
|
1401
|
+
fetchGraphQL: ghGraphQLFetch(bin, spawnFn, env, timeoutMs)
|
|
1320
1402
|
});
|
|
1321
|
-
return
|
|
1322
|
-
};
|
|
1323
|
-
}
|
|
1324
|
-
async function runDocsDriftValidation(options) {
|
|
1325
|
-
const report = await detectDrift(options);
|
|
1326
|
-
const high = highConfidenceDriftFindings(report);
|
|
1327
|
-
const passed = options.failOnDrift ? high.length === 0 : true;
|
|
1328
|
-
const findingWord = report.findings.length === 1 ? "finding" : "findings";
|
|
1329
|
-
return {
|
|
1330
|
-
id: DOCS_DRIFT_VALIDATOR_ID,
|
|
1331
|
-
passed,
|
|
1332
|
-
summary: `docs drift scanned ${report.scanned} doc(s), ${report.findings.length} ${findingWord}`,
|
|
1333
|
-
details: JSON.stringify(report)
|
|
1334
|
-
};
|
|
1335
|
-
}
|
|
1336
|
-
function createDocsDriftValidator(options = {}) {
|
|
1337
|
-
return {
|
|
1338
|
-
...DOCS_DRIFT_VALIDATOR,
|
|
1339
|
-
async run(ctx) {
|
|
1340
|
-
return runDocsDriftValidation({
|
|
1341
|
-
projectRoot: ctx.workspaceRoot,
|
|
1342
|
-
...options.docsGlobs !== undefined ? { docsGlobs: options.docsGlobs } : {},
|
|
1343
|
-
...options.ignoreGlobs !== undefined ? { ignoreGlobs: options.ignoreGlobs } : {},
|
|
1344
|
-
...options.failOnDrift !== undefined ? { failOnDrift: options.failOnDrift } : {}
|
|
1345
|
-
});
|
|
1346
|
-
}
|
|
1347
|
-
};
|
|
1348
|
-
}
|
|
1349
|
-
function takeOptionValue(args, index, flag) {
|
|
1350
|
-
const value = args[index + 1];
|
|
1351
|
-
if (!value)
|
|
1352
|
-
throw new Error(`${flag} requires a value`);
|
|
1353
|
-
return value;
|
|
1354
|
-
}
|
|
1355
|
-
function takeFlag(args, flag) {
|
|
1356
|
-
const rest = [...args];
|
|
1357
|
-
const index = rest.indexOf(flag);
|
|
1358
|
-
if (index < 0)
|
|
1359
|
-
return { value: false, rest };
|
|
1360
|
-
rest.splice(index, 1);
|
|
1361
|
-
return { value: true, rest };
|
|
1362
|
-
}
|
|
1363
|
-
function takeOption(args, flag) {
|
|
1364
|
-
const rest = [...args];
|
|
1365
|
-
const index = rest.indexOf(flag);
|
|
1366
|
-
if (index < 0)
|
|
1367
|
-
return { rest };
|
|
1368
|
-
const value = rest[index + 1];
|
|
1369
|
-
if (!value || value.startsWith("-"))
|
|
1370
|
-
throw new Error(`${flag} requires a value.`);
|
|
1371
|
-
rest.splice(index, 2);
|
|
1372
|
-
return { value, rest };
|
|
1373
|
-
}
|
|
1374
|
-
function requireNoExtraArgs(args, usage) {
|
|
1375
|
-
if (args.length > 0)
|
|
1376
|
-
throw new Error(`Unexpected argument: ${args[0]}
|
|
1377
|
-
Usage: ${usage}`);
|
|
1378
|
-
}
|
|
1379
|
-
function parseCsv(value) {
|
|
1380
|
-
return value?.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0) ?? [];
|
|
1381
|
-
}
|
|
1382
|
-
function driftSummary(report) {
|
|
1383
|
-
const highConfidence = highConfidenceDriftFindings(report).length;
|
|
1384
|
-
return { total: report.findings.length, highConfidence, degraded: report.degraded };
|
|
1385
|
-
}
|
|
1386
|
-
async function executeDrift(context, args, options = {}) {
|
|
1387
|
-
const json = takeFlag(args, "--json");
|
|
1388
|
-
const docs = takeOption(json.rest, "--docs");
|
|
1389
|
-
const ignore = takeOption(docs.rest, "--ignore");
|
|
1390
|
-
const failOnDrift = takeFlag(ignore.rest, "--fail-on-drift");
|
|
1391
|
-
requireNoExtraArgs(failOnDrift.rest, "rig drift [--docs <csv>] [--ignore <csv>] [--fail-on-drift] [--json]");
|
|
1392
|
-
const docsGlobs = parseCsv(docs.value);
|
|
1393
|
-
const ignoreGlobs = parseCsv(ignore.value);
|
|
1394
|
-
const effectiveDocsGlobs = docsGlobs.length > 0 ? docsGlobs : options.docsGlobs;
|
|
1395
|
-
const effectiveIgnoreGlobs = ignoreGlobs.length > 0 ? ignoreGlobs : options.ignoreGlobs;
|
|
1396
|
-
const effectiveFailOnDrift = failOnDrift.value || options.failOnDrift === true;
|
|
1397
|
-
const report = await detectDrift({
|
|
1398
|
-
projectRoot: context.projectRoot,
|
|
1399
|
-
...effectiveDocsGlobs !== undefined ? { docsGlobs: effectiveDocsGlobs } : {},
|
|
1400
|
-
...effectiveIgnoreGlobs !== undefined ? { ignoreGlobs: effectiveIgnoreGlobs } : {}
|
|
1401
|
-
});
|
|
1402
|
-
const failed = effectiveFailOnDrift && highConfidenceDriftFindings(report).length > 0;
|
|
1403
|
-
const details = { report, summary: driftSummary(report), failOnDrift: effectiveFailOnDrift, failed };
|
|
1404
|
-
if (context.outputMode === "text") {
|
|
1405
|
-
if (json.value)
|
|
1406
|
-
console.log(JSON.stringify(details, null, 2));
|
|
1407
|
-
else
|
|
1408
|
-
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(`
|
|
1409
|
-
`));
|
|
1403
|
+
return issueToTask(issue, repo, nativeDependencies);
|
|
1410
1404
|
}
|
|
1411
|
-
return {
|
|
1405
|
+
return {
|
|
1406
|
+
id: "std:github-issues",
|
|
1407
|
+
kind: "github-issues",
|
|
1408
|
+
async list() {
|
|
1409
|
+
const labelArg = opts.labels && opts.labels.length > 0 ? ["--label", opts.labels.join(",")] : [];
|
|
1410
|
+
const assigneeArg = opts.assignee?.trim() ? ["--assignee", opts.assignee.trim()] : [];
|
|
1411
|
+
const args = [
|
|
1412
|
+
"issue",
|
|
1413
|
+
"list",
|
|
1414
|
+
"--repo",
|
|
1415
|
+
repo,
|
|
1416
|
+
...labelArg,
|
|
1417
|
+
...assigneeArg,
|
|
1418
|
+
"--state",
|
|
1419
|
+
state,
|
|
1420
|
+
"--limit",
|
|
1421
|
+
String(listLimit),
|
|
1422
|
+
"--json",
|
|
1423
|
+
"number,title,body,labels,state,url,assignees,id"
|
|
1424
|
+
];
|
|
1425
|
+
const env = await resolveCredentialEnv(opts, "selected-repo");
|
|
1426
|
+
const rawIssues = runGh(bin, args, spawnFn, env, timeoutMs);
|
|
1427
|
+
if (rawIssues.length >= listLimit) {
|
|
1428
|
+
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.`);
|
|
1429
|
+
}
|
|
1430
|
+
const issues = rawIssues.filter((issue) => !issue.pull_request);
|
|
1431
|
+
return Promise.all(issues.map((issue) => issueToTaskWithOptionalNativeDependencies(issue, env)));
|
|
1432
|
+
},
|
|
1433
|
+
async get(id) {
|
|
1434
|
+
const env = await resolveCredentialEnv(opts, "selected-repo");
|
|
1435
|
+
let issue;
|
|
1436
|
+
try {
|
|
1437
|
+
issue = runGh(bin, [
|
|
1438
|
+
"issue",
|
|
1439
|
+
"view",
|
|
1440
|
+
String(id),
|
|
1441
|
+
"--repo",
|
|
1442
|
+
repo,
|
|
1443
|
+
"--json",
|
|
1444
|
+
"number,title,body,labels,state,url,assignees,id"
|
|
1445
|
+
], spawnFn, env, timeoutMs);
|
|
1446
|
+
} catch (error) {
|
|
1447
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
1448
|
+
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)) {
|
|
1449
|
+
return;
|
|
1450
|
+
}
|
|
1451
|
+
throw new Error(`Failed to read task ${id} from GitHub repo ${repo}: ${detail}`);
|
|
1452
|
+
}
|
|
1453
|
+
return issueToTaskWithOptionalNativeDependencies(issue, env);
|
|
1454
|
+
},
|
|
1455
|
+
async updateStatus(id, status) {
|
|
1456
|
+
const env = await resolveCredentialEnv(opts, "selected-repo");
|
|
1457
|
+
await applyIssueStatus(bin, repo, spawnFn, id, status, opts.projects, opts.assignee, issueUpdates, env, timeoutMs);
|
|
1458
|
+
notifyTaskChanged(opts.onTaskChanged, repo, id, status);
|
|
1459
|
+
},
|
|
1460
|
+
async updateTask(id, update) {
|
|
1461
|
+
const env = await resolveCredentialEnv(opts, "selected-repo");
|
|
1462
|
+
await applyIssueUpdate(bin, repo, spawnFn, id, update, opts.projects, opts.assignee, issueUpdates, env, timeoutMs);
|
|
1463
|
+
notifyTaskChanged(opts.onTaskChanged, repo, id, update.status);
|
|
1464
|
+
},
|
|
1465
|
+
async addLabels(id, labels) {
|
|
1466
|
+
const env = await resolveCredentialEnv(opts, "selected-repo");
|
|
1467
|
+
applyLabels(bin, repo, spawnFn, id, labels, "--add-label", env, timeoutMs);
|
|
1468
|
+
notifyTaskChanged(opts.onTaskChanged, repo, id);
|
|
1469
|
+
},
|
|
1470
|
+
async removeLabels(id, labels) {
|
|
1471
|
+
const env = await resolveCredentialEnv(opts, "selected-repo");
|
|
1472
|
+
applyLabels(bin, repo, spawnFn, id, labels, "--remove-label", env, timeoutMs);
|
|
1473
|
+
notifyTaskChanged(opts.onTaskChanged, repo, id);
|
|
1474
|
+
},
|
|
1475
|
+
async createIssue(input) {
|
|
1476
|
+
const env = await resolveCredentialEnv(opts, "selected-repo");
|
|
1477
|
+
const body = input.body ?? "";
|
|
1478
|
+
const args = [
|
|
1479
|
+
"api",
|
|
1480
|
+
"-X",
|
|
1481
|
+
"POST",
|
|
1482
|
+
`repos/${repo}/issues`,
|
|
1483
|
+
"-f",
|
|
1484
|
+
`title=${input.title}`,
|
|
1485
|
+
"-f",
|
|
1486
|
+
`body=${body}`,
|
|
1487
|
+
...(input.labels ?? []).flatMap((label) => ["-f", `labels[]=${label}`])
|
|
1488
|
+
];
|
|
1489
|
+
const issue = runGh(bin, args, spawnFn, env, timeoutMs);
|
|
1490
|
+
notifyTaskChanged(opts.onTaskChanged, repo, String(issue.number));
|
|
1491
|
+
return issueToTask({ ...issue, body: issue.body ?? body }, repo);
|
|
1492
|
+
},
|
|
1493
|
+
async create(input) {
|
|
1494
|
+
const env = await resolveCredentialEnv(opts, "selected-repo");
|
|
1495
|
+
const body = bodyForCreatedTask(input);
|
|
1496
|
+
const args = [
|
|
1497
|
+
"api",
|
|
1498
|
+
"-X",
|
|
1499
|
+
"POST",
|
|
1500
|
+
`repos/${repo}/issues`,
|
|
1501
|
+
"-f",
|
|
1502
|
+
`title=${input.title}`,
|
|
1503
|
+
"-f",
|
|
1504
|
+
`body=${body}`,
|
|
1505
|
+
"-f",
|
|
1506
|
+
"labels[]=rig:generated"
|
|
1507
|
+
];
|
|
1508
|
+
const issue = runGh(bin, args, spawnFn, env, timeoutMs);
|
|
1509
|
+
notifyTaskChanged(opts.onTaskChanged, repo, String(issue.number));
|
|
1510
|
+
return issueToTask({ ...issue, body: issue.body ?? body }, repo);
|
|
1511
|
+
},
|
|
1512
|
+
async getIssueBody(id) {
|
|
1513
|
+
const env = await resolveCredentialEnv(opts, "selected-repo");
|
|
1514
|
+
return fetchIssueBody(bin, repo, spawnFn, id, env, timeoutMs);
|
|
1515
|
+
}
|
|
1516
|
+
};
|
|
1412
1517
|
}
|
|
1413
|
-
|
|
1518
|
+
|
|
1519
|
+
// packages/standard-plugin/src/files-source.ts
|
|
1520
|
+
import { readFileSync as readFileSync2, readdirSync, existsSync as existsSync2, statSync, writeFileSync } from "fs";
|
|
1521
|
+
import { join, basename, isAbsolute, resolve as resolve2 } from "path";
|
|
1522
|
+
var DEFAULT_PATTERN = /\.(task\.)?json$/;
|
|
1523
|
+
function readTaskFile(file, pattern) {
|
|
1524
|
+
const raw = JSON.parse(readFileSync2(file, "utf-8"));
|
|
1525
|
+
const inferredId = basename(file).replace(pattern, "");
|
|
1526
|
+
const labels = Array.isArray(raw.labels) ? raw.labels.filter((label) => typeof label === "string") : [];
|
|
1527
|
+
const scope = labels.filter((label) => label.startsWith("scope:")).map((label) => label.slice("scope:".length));
|
|
1528
|
+
const validators = labels.filter((label) => label.startsWith("validator:")).map((label) => label.slice("validator:".length));
|
|
1529
|
+
const roleLabel = labels.find((label) => label.startsWith("role:"));
|
|
1414
1530
|
return {
|
|
1415
|
-
id:
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1531
|
+
id: raw["id"] ?? inferredId,
|
|
1532
|
+
deps: raw["deps"] ?? raw["depends_on"] ?? [],
|
|
1533
|
+
status: raw["status"] ?? "ready",
|
|
1534
|
+
...scope.length > 0 ? { scope } : {},
|
|
1535
|
+
...roleLabel ? { role: roleLabel.slice("role:".length) } : {},
|
|
1536
|
+
...validators.length > 0 ? { validators, validation: validators } : {},
|
|
1537
|
+
...raw
|
|
1422
1538
|
};
|
|
1423
1539
|
}
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
const
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
if (
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1540
|
+
function createFilesTaskSource(opts) {
|
|
1541
|
+
const pattern = opts.pattern ?? DEFAULT_PATTERN;
|
|
1542
|
+
const configured = opts.path ?? opts.dir;
|
|
1543
|
+
if (!configured) {
|
|
1544
|
+
throw new Error("createFilesTaskSource: either `path` or `dir` must be provided");
|
|
1545
|
+
}
|
|
1546
|
+
const directory = isAbsolute(configured) ? configured : resolve2(opts.projectRoot ?? process.cwd(), configured);
|
|
1547
|
+
const findTaskFile = (id) => {
|
|
1548
|
+
if (!existsSync2(directory))
|
|
1549
|
+
return;
|
|
1550
|
+
for (const name of readdirSync(directory)) {
|
|
1551
|
+
if (!pattern.test(name))
|
|
1552
|
+
continue;
|
|
1553
|
+
const p = join(directory, name);
|
|
1554
|
+
try {
|
|
1555
|
+
if (!statSync(p).isFile())
|
|
1556
|
+
continue;
|
|
1557
|
+
if (readTaskFile(p, pattern).id === id)
|
|
1558
|
+
return p;
|
|
1559
|
+
} catch {}
|
|
1439
1560
|
}
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1561
|
+
return;
|
|
1562
|
+
};
|
|
1563
|
+
const applyUpdate = (id, update) => {
|
|
1564
|
+
const file = findTaskFile(id);
|
|
1565
|
+
if (!file) {
|
|
1566
|
+
throw new Error(`files task not found: ${id}`);
|
|
1444
1567
|
}
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1568
|
+
const raw = JSON.parse(readFileSync2(file, "utf-8"));
|
|
1569
|
+
if (update.status)
|
|
1570
|
+
raw.status = update.status;
|
|
1571
|
+
if (update.title !== undefined)
|
|
1572
|
+
raw.title = update.title;
|
|
1573
|
+
if (update.body !== undefined)
|
|
1574
|
+
raw.body = update.body;
|
|
1575
|
+
if (update.comment?.trim()) {
|
|
1576
|
+
const existing = Array.isArray(raw.comments) ? raw.comments : [];
|
|
1577
|
+
raw.comments = [
|
|
1578
|
+
...existing,
|
|
1579
|
+
{
|
|
1580
|
+
body: update.comment,
|
|
1581
|
+
createdAt: new Date().toISOString(),
|
|
1582
|
+
source: "rig"
|
|
1583
|
+
}
|
|
1584
|
+
];
|
|
1449
1585
|
}
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1586
|
+
writeFileSync(file, `${JSON.stringify(raw, null, 2)}
|
|
1587
|
+
`, "utf-8");
|
|
1588
|
+
};
|
|
1589
|
+
return {
|
|
1590
|
+
id: "std:files",
|
|
1591
|
+
kind: "files",
|
|
1592
|
+
async list() {
|
|
1593
|
+
if (!existsSync2(directory))
|
|
1594
|
+
return [];
|
|
1595
|
+
const out = [];
|
|
1596
|
+
for (const name of readdirSync(directory)) {
|
|
1597
|
+
if (!pattern.test(name))
|
|
1598
|
+
continue;
|
|
1599
|
+
const p = join(directory, name);
|
|
1600
|
+
try {
|
|
1601
|
+
if (!statSync(p).isFile())
|
|
1602
|
+
continue;
|
|
1603
|
+
out.push(readTaskFile(p, pattern));
|
|
1604
|
+
} catch (err) {
|
|
1605
|
+
console.warn(`[files-source] skipped ${name}: ${err.message}`);
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
return out;
|
|
1609
|
+
},
|
|
1610
|
+
async get(id) {
|
|
1611
|
+
const all = await this.list();
|
|
1612
|
+
return all.find((t) => t.id === id);
|
|
1613
|
+
},
|
|
1614
|
+
async updateStatus(id, status) {
|
|
1615
|
+
applyUpdate(id, { status });
|
|
1616
|
+
},
|
|
1617
|
+
async updateTask(id, update) {
|
|
1618
|
+
applyUpdate(id, update);
|
|
1464
1619
|
}
|
|
1465
|
-
}
|
|
1466
|
-
const high = highConfidenceDriftFindings(report);
|
|
1467
|
-
if (failOnDrift && high.length > 0) {
|
|
1468
|
-
options.writeError?.(`${high.length} high-confidence drift finding(s).`);
|
|
1469
|
-
return 2;
|
|
1470
|
-
}
|
|
1471
|
-
return 0;
|
|
1472
|
-
}
|
|
1473
|
-
// packages/standard-plugin/src/drift/judge.ts
|
|
1474
|
-
async function judgeDocumentationDrift(provider, input) {
|
|
1475
|
-
const result = await provider.judge(input);
|
|
1476
|
-
return result.mismatches.map((mismatch) => ({
|
|
1477
|
-
kind: "semantic-mismatch",
|
|
1478
|
-
docPath: input.docPath,
|
|
1479
|
-
line: mismatch.line ?? null,
|
|
1480
|
-
reference: mismatch.reference ?? input.reference ?? null,
|
|
1481
|
-
detail: mismatch.detail,
|
|
1482
|
-
confidence: mismatch.confidence ?? "medium"
|
|
1483
|
-
}));
|
|
1620
|
+
};
|
|
1484
1621
|
}
|
|
1622
|
+
|
|
1485
1623
|
// packages/standard-plugin/src/plugin.ts
|
|
1624
|
+
init_metadata();
|
|
1625
|
+
init_metadata();
|
|
1626
|
+
import { createStandardProductEntrypointPlugin, standardProductEntrypointPlugin } from "@rig/product-entrypoint-plugin/plugin";
|
|
1627
|
+
import { createStandardTaskCliPlugin, standardTaskCliPlugin } from "@rig/task-cli-plugin/plugin";
|
|
1486
1628
|
var DOCS_HEALTH_PANEL_ID = "docs-health";
|
|
1487
1629
|
function requireStringField(config, field, kind) {
|
|
1488
1630
|
const value = config[field];
|
|
@@ -1539,7 +1681,8 @@ function createDocsHealthPanelProducer(options = {}) {
|
|
|
1539
1681
|
const projectRoot = panelProjectRoot(context);
|
|
1540
1682
|
if (!projectRoot)
|
|
1541
1683
|
return;
|
|
1542
|
-
const
|
|
1684
|
+
const { detectDrift: detectDrift2 } = await Promise.resolve().then(() => (init_detect(), exports_detect));
|
|
1685
|
+
const report = await detectDrift2({
|
|
1543
1686
|
projectRoot,
|
|
1544
1687
|
...options.docsGlobs !== undefined ? { docsGlobs: options.docsGlobs } : {},
|
|
1545
1688
|
...options.ignoreGlobs !== undefined ? { ignoreGlobs: options.ignoreGlobs } : {}
|
|
@@ -1557,24 +1700,46 @@ function createDocsHealthPanelProducer(options = {}) {
|
|
|
1557
1700
|
};
|
|
1558
1701
|
};
|
|
1559
1702
|
}
|
|
1560
|
-
function
|
|
1703
|
+
function createLazyDocsDriftValidator(options = {}) {
|
|
1704
|
+
return {
|
|
1705
|
+
...DOCS_DRIFT_VALIDATOR,
|
|
1706
|
+
async run(ctx) {
|
|
1707
|
+
const { runDocsDriftValidation: runDocsDriftValidation2 } = await Promise.resolve().then(() => (init_plugin(), exports_plugin));
|
|
1708
|
+
return runDocsDriftValidation2({
|
|
1709
|
+
projectRoot: ctx.workspaceRoot,
|
|
1710
|
+
...options.docsGlobs !== undefined ? { docsGlobs: options.docsGlobs } : {},
|
|
1711
|
+
...options.ignoreGlobs !== undefined ? { ignoreGlobs: options.ignoreGlobs } : {},
|
|
1712
|
+
...options.failOnDrift !== undefined ? { failOnDrift: options.failOnDrift } : {}
|
|
1713
|
+
});
|
|
1714
|
+
}
|
|
1715
|
+
};
|
|
1716
|
+
}
|
|
1717
|
+
function createLazyDocsDriftGateStage(options = {}) {
|
|
1718
|
+
return async (ctx) => {
|
|
1719
|
+
const { createDocsDriftGateStage: createDocsDriftGateStage2 } = await Promise.resolve().then(() => (init_plugin(), exports_plugin));
|
|
1720
|
+
return createDocsDriftGateStage2(options)(ctx);
|
|
1721
|
+
};
|
|
1722
|
+
}
|
|
1723
|
+
function createLazyDocsDriftRuntimeCliCommand(options = {}) {
|
|
1724
|
+
return {
|
|
1725
|
+
id: DOCS_DRIFT_CLI_ID,
|
|
1726
|
+
family: "drift",
|
|
1727
|
+
command: DOCS_DRIFT_CLI_COMMAND,
|
|
1728
|
+
description: "Scan documentation for stale code references.",
|
|
1729
|
+
usage: DOCS_DRIFT_CLI_COMMAND,
|
|
1730
|
+
projectRequired: true,
|
|
1731
|
+
run: async (context, args) => {
|
|
1732
|
+
const { executeDrift: executeDrift2 } = await Promise.resolve().then(() => (init_plugin(), exports_plugin));
|
|
1733
|
+
return executeDrift2(context, args, options);
|
|
1734
|
+
}
|
|
1735
|
+
};
|
|
1736
|
+
}
|
|
1737
|
+
function createStandardDocsDriftPlugin(opts = {}) {
|
|
1561
1738
|
return definePlugin({
|
|
1562
|
-
name: "rig-
|
|
1739
|
+
name: "@rig/standard-plugin:docs-drift",
|
|
1563
1740
|
version: "0.1.0",
|
|
1564
1741
|
contributes: {
|
|
1565
1742
|
validators: [DOCS_DRIFT_VALIDATOR],
|
|
1566
|
-
taskSources: [
|
|
1567
|
-
{
|
|
1568
|
-
id: "std:github-issues",
|
|
1569
|
-
kind: "github-issues",
|
|
1570
|
-
description: "GitHub Issues via gh CLI"
|
|
1571
|
-
},
|
|
1572
|
-
{
|
|
1573
|
-
id: "std:files",
|
|
1574
|
-
kind: "files",
|
|
1575
|
-
description: "JSON files in a local directory"
|
|
1576
|
-
}
|
|
1577
|
-
],
|
|
1578
1743
|
capabilities: [
|
|
1579
1744
|
{ id: DOCS_DRIFT_CAPABILITY_ID, title: "Documentation drift detection", commandId: DOCS_DRIFT_CLI_ID, panelId: DOCS_HEALTH_PANEL_ID }
|
|
1580
1745
|
],
|
|
@@ -1593,15 +1758,36 @@ function standardPlugin(opts = {}) {
|
|
|
1593
1758
|
stageMutations: [DOCS_DRIFT_STAGE_MUTATION]
|
|
1594
1759
|
}
|
|
1595
1760
|
}, {
|
|
1596
|
-
validators: [
|
|
1597
|
-
stages: { [DOCS_DRIFT_STAGE_ID]:
|
|
1761
|
+
validators: [createLazyDocsDriftValidator(opts)],
|
|
1762
|
+
stages: { [DOCS_DRIFT_STAGE_ID]: createLazyDocsDriftGateStage(opts) },
|
|
1598
1763
|
featureCapabilities: [
|
|
1599
1764
|
{ id: DOCS_DRIFT_CAPABILITY_ID, title: "Documentation drift detection", commandId: DOCS_DRIFT_CLI_ID, panelId: DOCS_HEALTH_PANEL_ID }
|
|
1600
1765
|
],
|
|
1601
1766
|
panels: [
|
|
1602
|
-
{ id: DOCS_HEALTH_PANEL_ID, slot: "capability", title: "Documentation drift", capabilityId: DOCS_DRIFT_CAPABILITY_ID, produce: createDocsHealthPanelProducer(opts
|
|
1767
|
+
{ id: DOCS_HEALTH_PANEL_ID, slot: "capability", title: "Documentation drift", capabilityId: DOCS_DRIFT_CAPABILITY_ID, produce: createDocsHealthPanelProducer(opts) }
|
|
1603
1768
|
],
|
|
1604
|
-
cliCommands: [
|
|
1769
|
+
cliCommands: [createLazyDocsDriftRuntimeCliCommand(opts)]
|
|
1770
|
+
});
|
|
1771
|
+
}
|
|
1772
|
+
function createStandardTaskSourcesPlugin(opts = {}) {
|
|
1773
|
+
return definePlugin({
|
|
1774
|
+
name: "@rig/standard-plugin:task-sources",
|
|
1775
|
+
version: "0.1.0",
|
|
1776
|
+
contributes: {
|
|
1777
|
+
taskSources: [
|
|
1778
|
+
{
|
|
1779
|
+
id: "std:github-issues",
|
|
1780
|
+
kind: "github-issues",
|
|
1781
|
+
description: "GitHub Issues via gh CLI"
|
|
1782
|
+
},
|
|
1783
|
+
{
|
|
1784
|
+
id: "std:files",
|
|
1785
|
+
kind: "files",
|
|
1786
|
+
description: "JSON files in a local directory"
|
|
1787
|
+
}
|
|
1788
|
+
]
|
|
1789
|
+
}
|
|
1790
|
+
}, {
|
|
1605
1791
|
taskSources: [
|
|
1606
1792
|
{
|
|
1607
1793
|
id: "std:github-issues",
|
|
@@ -1658,45 +1844,98 @@ function standardPlugin(opts = {}) {
|
|
|
1658
1844
|
]
|
|
1659
1845
|
});
|
|
1660
1846
|
}
|
|
1847
|
+
|
|
1661
1848
|
// packages/standard-plugin/src/bundle.ts
|
|
1662
|
-
|
|
1663
|
-
import { createDefaultLifecyclePlugin } from "@rig/bundle-default-lifecycle/plugin";
|
|
1664
|
-
import { createDependencyGraphPlugin } from "@rig/dependency-graph-plugin/plugin";
|
|
1665
|
-
import { createPlanningPlugin } from "@rig/planning-plugin/plugin";
|
|
1666
|
-
import { createSupervisorPlugin } from "@rig/supervisor-plugin/plugin";
|
|
1667
|
-
function standardProjectPlugins(options = {}) {
|
|
1849
|
+
function standardPlugins(options = {}) {
|
|
1668
1850
|
return [
|
|
1669
1851
|
createDefaultLifecyclePlugin(),
|
|
1670
1852
|
createDependencyGraphPlugin(),
|
|
1671
1853
|
createBlockerClassifierPlugin(),
|
|
1672
1854
|
createPlanningPlugin(),
|
|
1673
1855
|
createSupervisorPlugin(),
|
|
1674
|
-
|
|
1856
|
+
RUN_WORKER_PANEL_PLUGIN,
|
|
1857
|
+
createStandardTaskSourcesPlugin(options.taskSources),
|
|
1858
|
+
createStandardTaskCliPlugin(),
|
|
1859
|
+
createStandardDocsDriftPlugin(options.drift),
|
|
1860
|
+
createStandardProductEntrypointPlugin()
|
|
1675
1861
|
];
|
|
1676
1862
|
}
|
|
1863
|
+
// packages/standard-plugin/src/cli-surface.ts
|
|
1864
|
+
var STANDARD_CLI_SURFACE_PLUGIN_NAME = "@rig/standard-plugin:cli-surface";
|
|
1865
|
+
function standardCliCommand(family, options) {
|
|
1866
|
+
return {
|
|
1867
|
+
id: `${STANDARD_CLI_SURFACE_PLUGIN_NAME}:${family}`,
|
|
1868
|
+
family,
|
|
1869
|
+
description: options.description,
|
|
1870
|
+
...options.usage ? { usage: options.usage } : {},
|
|
1871
|
+
...options.aliases ? { aliases: [...options.aliases] } : {},
|
|
1872
|
+
projectRequired: options.projectRequired ?? false
|
|
1873
|
+
};
|
|
1874
|
+
}
|
|
1875
|
+
var STANDARD_CLI_SURFACE_COMMAND_METADATA = [
|
|
1876
|
+
standardCliCommand("init", { description: "Initialize Rig project configuration." }),
|
|
1877
|
+
standardCliCommand("setup", { description: "Install and inspect local Rig setup." }),
|
|
1878
|
+
standardCliCommand("check", { description: "Run Rig setup checks.", usage: "rig check" }),
|
|
1879
|
+
standardCliCommand("install", { description: "Install Rig distribution artifacts.", usage: "rig install [options]" }),
|
|
1880
|
+
standardCliCommand("github", { description: "Manage GitHub auth and integration state." }),
|
|
1881
|
+
standardCliCommand("doctor", { description: "Inspect Rig environment health." }),
|
|
1882
|
+
standardCliCommand("repo", { description: "Manage repository state.", projectRequired: true }),
|
|
1883
|
+
standardCliCommand("profile", { description: "Manage runtime profile settings." }),
|
|
1884
|
+
standardCliCommand("review", { description: "Run review helpers.", projectRequired: true }),
|
|
1885
|
+
standardCliCommand("git", { description: "Run Rig git automation.", projectRequired: true }),
|
|
1886
|
+
standardCliCommand("harness", { description: "Inspect native harness integration." }),
|
|
1887
|
+
standardCliCommand("pi", { description: "Inspect Pi/OMP runtime integration." }),
|
|
1888
|
+
standardCliCommand("plugin", { description: "Inspect and run plugin compatibility commands." }),
|
|
1889
|
+
standardCliCommand("queue", { description: "Inspect dispatch queue state.", projectRequired: true }),
|
|
1890
|
+
standardCliCommand("agent", { description: "Run agent lifecycle helpers.", projectRequired: true }),
|
|
1891
|
+
standardCliCommand("dist", { description: "Build and inspect Rig distributions." }),
|
|
1892
|
+
standardCliCommand("workspace", { description: "Inspect workspace topology." }),
|
|
1893
|
+
standardCliCommand("remote", { description: "Manage remote endpoints." }),
|
|
1894
|
+
standardCliCommand("test", { description: "Run Rig test helpers." }),
|
|
1895
|
+
standardCliCommand("inspect", { description: "Inspect live/project Rig state." }),
|
|
1896
|
+
standardCliCommand("run", { description: "Dispatch and inspect runs.", projectRequired: true }),
|
|
1897
|
+
standardCliCommand("task", { description: "List and inspect task-source tasks.", projectRequired: true }),
|
|
1898
|
+
standardCliCommand("inbox", { description: "Inspect and resolve run inbox items.", projectRequired: true }),
|
|
1899
|
+
standardCliCommand("stats", { description: "Inspect run statistics.", projectRequired: true }),
|
|
1900
|
+
standardCliCommand("server", { description: "Manage selected local/remote execution server.", projectRequired: true }),
|
|
1901
|
+
standardCliCommand("config", { description: "Inspect Rig configuration." }),
|
|
1902
|
+
standardCliCommand("triage", { description: "Run issue triage helpers.", projectRequired: true })
|
|
1903
|
+
];
|
|
1904
|
+
var standardCliSurfacePlugin = {
|
|
1905
|
+
name: STANDARD_CLI_SURFACE_PLUGIN_NAME,
|
|
1906
|
+
version: "0.0.0-alpha.1",
|
|
1907
|
+
contributes: {
|
|
1908
|
+
capabilities: [{ id: "surface.cli", title: "Rig CLI surface", description: "Registry-dispatched command-line surface." }],
|
|
1909
|
+
cliCommands: STANDARD_CLI_SURFACE_COMMAND_METADATA
|
|
1910
|
+
}
|
|
1911
|
+
};
|
|
1677
1912
|
export {
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
judgeDocumentationDrift,
|
|
1683
|
-
extractDriftReferences,
|
|
1684
|
-
executeDrift,
|
|
1685
|
-
driftGateResult,
|
|
1686
|
-
detectStaleAnchors,
|
|
1687
|
-
detectDrift,
|
|
1688
|
-
detectDeletedReferences,
|
|
1689
|
-
standardPlugin as default,
|
|
1913
|
+
standardTaskCliPlugin,
|
|
1914
|
+
standardProductEntrypointPlugin,
|
|
1915
|
+
standardPlugins,
|
|
1916
|
+
standardCliSurfacePlugin,
|
|
1690
1917
|
createStateGitHubCredentialProvider,
|
|
1918
|
+
createStandardTaskSourcesPlugin,
|
|
1919
|
+
createStandardTaskCliPlugin,
|
|
1920
|
+
createStandardProductEntrypointPlugin,
|
|
1921
|
+
createStandardDocsDriftPlugin,
|
|
1691
1922
|
createGitHubIssuesTaskSource,
|
|
1692
1923
|
createFilesTaskSource,
|
|
1693
1924
|
createEnvGitHubCredentialProvider,
|
|
1694
|
-
|
|
1695
|
-
|
|
1925
|
+
STANDARD_CLI_SURFACE_PLUGIN_NAME,
|
|
1926
|
+
STANDARD_CLI_SURFACE_COMMAND_METADATA,
|
|
1927
|
+
RUN_WORKER_PANEL_PLUGIN,
|
|
1928
|
+
RUN_SUPERVISOR_PANEL_REGISTRATION,
|
|
1929
|
+
RUN_SUPERVISOR_PANEL_PRODUCER,
|
|
1930
|
+
RIG_SUPERVISOR_PANEL_ID,
|
|
1931
|
+
RIG_RUN_STOP_PANEL_ACTION,
|
|
1932
|
+
RIG_CAPABILITY_PANEL_SLOT,
|
|
1696
1933
|
DOCS_HEALTH_PANEL_ID,
|
|
1697
1934
|
DOCS_DRIFT_VALIDATOR_ID,
|
|
1935
|
+
DOCS_DRIFT_VALIDATOR,
|
|
1936
|
+
DOCS_DRIFT_STAGE_MUTATION,
|
|
1698
1937
|
DOCS_DRIFT_STAGE_ID,
|
|
1699
|
-
DOCS_DRIFT_RUNTIME_CLI_COMMAND,
|
|
1700
1938
|
DOCS_DRIFT_CLI_ID,
|
|
1939
|
+
DOCS_DRIFT_CLI_COMMAND,
|
|
1701
1940
|
DOCS_DRIFT_CAPABILITY_ID
|
|
1702
1941
|
};
|