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