@deftai/directive-core 0.66.2 → 0.68.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents-md-advisory/evaluate.d.ts +44 -0
- package/dist/agents-md-advisory/evaluate.js +115 -0
- package/dist/agents-md-advisory/index.d.ts +2 -0
- package/dist/agents-md-advisory/index.js +2 -0
- package/dist/agents-md-budget/evaluate.d.ts +38 -0
- package/dist/agents-md-budget/evaluate.js +152 -0
- package/dist/agents-md-budget/index.d.ts +2 -0
- package/dist/agents-md-budget/index.js +2 -0
- package/dist/check/index.d.ts +1 -1
- package/dist/check/index.js +1 -1
- package/dist/check/orchestrator.d.ts +14 -4
- package/dist/check/orchestrator.js +30 -6
- package/dist/codebase/map.d.ts +1 -0
- package/dist/codebase/map.js +5 -2
- package/dist/doctor/checks.js +11 -11
- package/dist/doctor/constants.d.ts +7 -2
- package/dist/doctor/constants.js +17 -1
- package/dist/doctor/flags.js +5 -1
- package/dist/doctor/main.js +99 -8
- package/dist/doctor/payload-staleness.js +15 -6
- package/dist/doctor/types.d.ts +7 -0
- package/dist/forward-coverage/evaluate.d.ts +55 -0
- package/dist/forward-coverage/evaluate.js +254 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/init-deposit/constants.d.ts +14 -0
- package/dist/init-deposit/constants.js +10 -0
- package/dist/init-deposit/hygiene.d.ts +50 -0
- package/dist/init-deposit/hygiene.js +176 -0
- package/dist/init-deposit/init-deposit.d.ts +1 -0
- package/dist/init-deposit/init-deposit.js +7 -1
- package/dist/init-deposit/refresh.d.ts +2 -0
- package/dist/init-deposit/refresh.js +48 -30
- package/dist/init-deposit/scaffold.d.ts +3 -4
- package/dist/init-deposit/scaffold.js +3 -24
- package/dist/intake/github-auth-modes.d.ts +1 -0
- package/dist/intake/github-auth-modes.js +1 -0
- package/dist/intake/issue-ingest.d.ts +6 -2
- package/dist/intake/issue-ingest.js +65 -9
- package/dist/integration-e2e/helpers.d.ts +0 -1
- package/dist/integration-e2e/helpers.js +3 -7
- package/dist/layout/lifecycle-ref.d.ts +15 -0
- package/dist/layout/lifecycle-ref.js +55 -0
- package/dist/platform/agents-consumer-header.d.ts +13 -0
- package/dist/platform/agents-consumer-header.js +57 -0
- package/dist/platform/agents-md.d.ts +28 -0
- package/dist/platform/agents-md.js +38 -1
- package/dist/platform/index.d.ts +1 -0
- package/dist/platform/index.js +1 -0
- package/dist/policy/agents-md-advisory.d.ts +52 -0
- package/dist/policy/agents-md-advisory.js +63 -0
- package/dist/policy/agents-md-budget.d.ts +24 -0
- package/dist/policy/agents-md-budget.js +89 -0
- package/dist/policy/index.d.ts +1 -0
- package/dist/policy/index.js +1 -0
- package/dist/policy/resolve.js +56 -46
- package/dist/pr-merge-readiness/ci-gate.d.ts +28 -0
- package/dist/pr-merge-readiness/ci-gate.js +79 -0
- package/dist/pr-merge-readiness/compute.d.ts +15 -1
- package/dist/pr-merge-readiness/compute.js +129 -9
- package/dist/pr-merge-readiness/gh.d.ts +8 -0
- package/dist/pr-merge-readiness/gh.js +30 -5
- package/dist/pr-merge-readiness/index.d.ts +5 -1
- package/dist/pr-merge-readiness/index.js +3 -0
- package/dist/pr-merge-readiness/main.d.ts +3 -0
- package/dist/pr-merge-readiness/main.js +73 -6
- package/dist/pr-merge-readiness/mergeability.d.ts +43 -0
- package/dist/pr-merge-readiness/mergeability.js +105 -0
- package/dist/pr-merge-readiness/output.js +20 -0
- package/dist/pr-merge-readiness/slizard-gate.d.ts +47 -0
- package/dist/pr-merge-readiness/slizard-gate.js +119 -0
- package/dist/pr-monitor/index.d.ts +1 -1
- package/dist/pr-monitor/index.js +1 -1
- package/dist/pr-monitor/monitor.d.ts +8 -2
- package/dist/pr-monitor/monitor.js +35 -6
- package/dist/pr-wait-mergeable/wrappers.d.ts +12 -1
- package/dist/pr-wait-mergeable/wrappers.js +13 -5
- package/dist/pr-watch/constants.d.ts +33 -0
- package/dist/pr-watch/constants.js +33 -0
- package/dist/pr-watch/index.d.ts +6 -0
- package/dist/pr-watch/index.js +6 -0
- package/dist/pr-watch/main.d.ts +21 -0
- package/dist/pr-watch/main.js +206 -0
- package/dist/pr-watch/probe.d.ts +13 -0
- package/dist/pr-watch/probe.js +100 -0
- package/dist/pr-watch/types.d.ts +47 -0
- package/dist/pr-watch/types.js +2 -0
- package/dist/pr-watch/watch.d.ts +12 -0
- package/dist/pr-watch/watch.js +122 -0
- package/dist/preflight-cache/evaluate.d.ts +1 -0
- package/dist/preflight-cache/evaluate.js +17 -10
- package/dist/render/project-render.d.ts +16 -2
- package/dist/render/project-render.js +155 -97
- package/dist/scope/capacity-stamp.d.ts +24 -2
- package/dist/scope/capacity-stamp.js +80 -28
- package/dist/scope/decomposed-refs.js +2 -2
- package/dist/scope/project-definition-sync.js +3 -3
- package/dist/scope/vbrief-ref.d.ts +2 -1
- package/dist/scope/vbrief-ref.js +3 -2
- package/dist/spec-authority/constants.d.ts +6 -0
- package/dist/spec-authority/constants.js +11 -0
- package/dist/spec-authority/resolver.js +3 -3
- package/dist/swarm/finalize-cohort-cli.d.ts +5 -0
- package/dist/swarm/finalize-cohort-cli.js +100 -0
- package/dist/swarm/finalize-cohort.d.ts +39 -0
- package/dist/swarm/finalize-cohort.js +589 -0
- package/dist/swarm/index.d.ts +2 -0
- package/dist/swarm/index.js +2 -0
- package/dist/swarm/launch.d.ts +13 -0
- package/dist/swarm/launch.js +19 -7
- package/dist/swarm/routing-verify.d.ts +1 -1
- package/dist/swarm/routing-verify.js +11 -10
- package/dist/swarm/routing.d.ts +9 -0
- package/dist/swarm/routing.js +46 -0
- package/dist/swarm/verify-review-clean-cli.js +25 -1
- package/dist/swarm/verify-review-clean.d.ts +9 -1
- package/dist/swarm/verify-review-clean.js +76 -3
- package/dist/triage/queue/index.d.ts +1 -0
- package/dist/triage/queue/index.js +1 -0
- package/dist/triage/queue/project.d.ts +6 -1
- package/dist/triage/queue/project.js +9 -4
- package/dist/triage/queue/reconcile-live-state.d.ts +42 -0
- package/dist/triage/queue/reconcile-live-state.js +54 -0
- package/dist/triage/scope/mutations-core.js +91 -81
- package/dist/triage/scope/resolve.js +4 -3
- package/dist/triage/scope-drift/add-ignore.js +33 -32
- package/dist/triage/scope-drift/scope-rules.js +3 -3
- package/dist/triage/subscribe/index.js +51 -45
- package/dist/triage/welcome/writers.js +79 -64
- package/dist/vbrief-build/project-definition-io.js +8 -3
- package/dist/vbrief-validate/paths.js +8 -4
- package/dist/vbrief-validate/precutover.d.ts +1 -1
- package/dist/vbrief-validate/precutover.js +12 -10
- package/dist/verify-source/biome-config.d.ts +41 -0
- package/dist/verify-source/biome-config.js +97 -0
- package/dist/verify-source/index.d.ts +1 -0
- package/dist/verify-source/index.js +1 -0
- package/dist/xbrief-migrate/agents-header.d.ts +69 -0
- package/dist/xbrief-migrate/agents-header.js +179 -0
- package/dist/xbrief-migrate/fs-helpers.d.ts +3 -0
- package/dist/xbrief-migrate/fs-helpers.js +11 -0
- package/dist/xbrief-migrate/index.d.ts +1 -0
- package/dist/xbrief-migrate/index.js +1 -0
- package/dist/xbrief-migrate/migrate-project.js +29 -10
- package/package.json +15 -7
- package/dist/install-upgrade/index.d.ts +0 -13
- package/dist/install-upgrade/index.js +0 -251
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { defaultRunGh } from "../pr-merge-readiness/gh.js";
|
|
4
|
+
import { DEFAULT_MAX_WAIT_MINUTES, DEFAULT_POLL_SECONDS, EXIT_TERMINAL_ERROR, } from "./constants.js";
|
|
5
|
+
import { watch } from "./watch.js";
|
|
6
|
+
function fail(base, error) {
|
|
7
|
+
return { ...base, error };
|
|
8
|
+
}
|
|
9
|
+
export function parseWatchArgs(argv) {
|
|
10
|
+
const acc = {
|
|
11
|
+
prNumber: null,
|
|
12
|
+
repo: null,
|
|
13
|
+
maxWaitMinutes: DEFAULT_MAX_WAIT_MINUTES,
|
|
14
|
+
pollSeconds: DEFAULT_POLL_SECONDS,
|
|
15
|
+
oneShot: false,
|
|
16
|
+
emitJson: false,
|
|
17
|
+
projectRoot: null,
|
|
18
|
+
};
|
|
19
|
+
let prNumber = null;
|
|
20
|
+
let repo = null;
|
|
21
|
+
let maxWaitMinutes = DEFAULT_MAX_WAIT_MINUTES;
|
|
22
|
+
let pollSeconds = DEFAULT_POLL_SECONDS;
|
|
23
|
+
let oneShot = false;
|
|
24
|
+
let emitJson = false;
|
|
25
|
+
let projectRoot = null;
|
|
26
|
+
const takePositive = (label, raw) => {
|
|
27
|
+
if (raw === undefined) {
|
|
28
|
+
return { error: `argument ${label}: expected one argument` };
|
|
29
|
+
}
|
|
30
|
+
const parsed = Number(raw);
|
|
31
|
+
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
32
|
+
return { error: `invalid ${label} value: ${raw}` };
|
|
33
|
+
}
|
|
34
|
+
return { value: parsed };
|
|
35
|
+
};
|
|
36
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
37
|
+
const arg = argv[i];
|
|
38
|
+
if (arg === "--json") {
|
|
39
|
+
emitJson = true;
|
|
40
|
+
}
|
|
41
|
+
else if (arg === "--one-shot") {
|
|
42
|
+
oneShot = true;
|
|
43
|
+
}
|
|
44
|
+
else if (arg === "--repo") {
|
|
45
|
+
const value = argv[i + 1];
|
|
46
|
+
if (value === undefined) {
|
|
47
|
+
return fail(acc, "argument --repo: expected one argument");
|
|
48
|
+
}
|
|
49
|
+
repo = value;
|
|
50
|
+
i += 1;
|
|
51
|
+
}
|
|
52
|
+
else if (arg?.startsWith("--repo=")) {
|
|
53
|
+
repo = arg.slice("--repo=".length);
|
|
54
|
+
}
|
|
55
|
+
else if (arg === "--max-wait-minutes") {
|
|
56
|
+
const r = takePositive("--max-wait-minutes", argv[i + 1]);
|
|
57
|
+
if ("error" in r)
|
|
58
|
+
return fail(acc, r.error);
|
|
59
|
+
maxWaitMinutes = r.value;
|
|
60
|
+
i += 1;
|
|
61
|
+
}
|
|
62
|
+
else if (arg?.startsWith("--max-wait-minutes=")) {
|
|
63
|
+
const r = takePositive("--max-wait-minutes", arg.slice("--max-wait-minutes=".length));
|
|
64
|
+
if ("error" in r)
|
|
65
|
+
return fail(acc, r.error);
|
|
66
|
+
maxWaitMinutes = r.value;
|
|
67
|
+
}
|
|
68
|
+
else if (arg === "--poll-seconds") {
|
|
69
|
+
const r = takePositive("--poll-seconds", argv[i + 1]);
|
|
70
|
+
if ("error" in r)
|
|
71
|
+
return fail(acc, r.error);
|
|
72
|
+
pollSeconds = r.value;
|
|
73
|
+
i += 1;
|
|
74
|
+
}
|
|
75
|
+
else if (arg?.startsWith("--poll-seconds=")) {
|
|
76
|
+
const r = takePositive("--poll-seconds", arg.slice("--poll-seconds=".length));
|
|
77
|
+
if ("error" in r)
|
|
78
|
+
return fail(acc, r.error);
|
|
79
|
+
pollSeconds = r.value;
|
|
80
|
+
}
|
|
81
|
+
else if (arg === "--project-root") {
|
|
82
|
+
const value = argv[i + 1];
|
|
83
|
+
if (value === undefined) {
|
|
84
|
+
return fail(acc, "argument --project-root: expected one argument");
|
|
85
|
+
}
|
|
86
|
+
projectRoot = value;
|
|
87
|
+
i += 1;
|
|
88
|
+
}
|
|
89
|
+
else if (arg?.startsWith("--project-root=")) {
|
|
90
|
+
projectRoot = arg.slice("--project-root=".length);
|
|
91
|
+
}
|
|
92
|
+
else if (arg?.startsWith("-")) {
|
|
93
|
+
return fail(acc, `unrecognized arguments: ${arg}`);
|
|
94
|
+
}
|
|
95
|
+
else if (prNumber === null) {
|
|
96
|
+
const n = Number(arg);
|
|
97
|
+
if (!Number.isInteger(n) || n <= 0) {
|
|
98
|
+
return fail(acc, `invalid PR number: ${arg}`);
|
|
99
|
+
}
|
|
100
|
+
prNumber = n;
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
return fail(acc, `unrecognized arguments: ${arg}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (prNumber === null) {
|
|
107
|
+
return fail(acc, "the following arguments are required: pr_number");
|
|
108
|
+
}
|
|
109
|
+
return { prNumber, repo, maxWaitMinutes, pollSeconds, oneShot, emitJson, projectRoot };
|
|
110
|
+
}
|
|
111
|
+
/** Match Python json.dumps(..., indent=2) default ensure_ascii=True. */
|
|
112
|
+
function pythonJsonDumps(value) {
|
|
113
|
+
const json = JSON.stringify(value, null, 2);
|
|
114
|
+
return json.replace(/[\u007f-\uffff]/g, (ch) => {
|
|
115
|
+
const code = ch.charCodeAt(0);
|
|
116
|
+
return `\\u${code.toString(16).padStart(4, "0")}`;
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
/** AC-4 stable --json shape (same field set as the #1039 Tier-1 instrumentation line). */
|
|
120
|
+
export function watchResultToJson(result) {
|
|
121
|
+
const p = result.probe;
|
|
122
|
+
return {
|
|
123
|
+
verdict: result.verdict,
|
|
124
|
+
pr_number: result.prNumber,
|
|
125
|
+
head_sha: p.headSha,
|
|
126
|
+
last_reviewed_sha: p.lastReviewedSha,
|
|
127
|
+
sha_match: p.shaMatch,
|
|
128
|
+
confidence: p.confidence,
|
|
129
|
+
p0_count: p.p0Count,
|
|
130
|
+
p1_count: p.p1Count,
|
|
131
|
+
errored: p.errored,
|
|
132
|
+
ci_failures: p.ciFailures,
|
|
133
|
+
is_clean: p.isClean,
|
|
134
|
+
clean_gate_holdout: p.cleanGateHoldout,
|
|
135
|
+
elapsed_seconds: result.elapsedSeconds,
|
|
136
|
+
poll_count: result.pollCount,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
export function emitWatchJson(result) {
|
|
140
|
+
return `${pythonJsonDumps(watchResultToJson(result))}\n`;
|
|
141
|
+
}
|
|
142
|
+
export function printWatchHuman(result) {
|
|
143
|
+
const p = result.probe;
|
|
144
|
+
const lines = [];
|
|
145
|
+
lines.push(`PR #${result.prNumber} pr:watch verdict: ${result.verdict}`);
|
|
146
|
+
lines.push(` HEAD SHA: ${p.headSha ?? "<unknown>"}`);
|
|
147
|
+
lines.push(` Greptile reviewed: ${p.lastReviewedSha ?? "<not parsed>"}`);
|
|
148
|
+
lines.push(` SHA match: ${p.shaMatch}`);
|
|
149
|
+
lines.push(` Confidence: ${p.confidence !== null ? String(p.confidence) : "<not parsed>"}/5`);
|
|
150
|
+
lines.push(` Findings: P0=${p.p0Count} P1=${p.p1Count}`);
|
|
151
|
+
lines.push(` Errored sentinel: ${p.errored}`);
|
|
152
|
+
lines.push(` CI failures: ${p.ciFailures}`);
|
|
153
|
+
if (p.cleanGateHoldout !== null) {
|
|
154
|
+
lines.push(` Clean-gate holdout: ${p.cleanGateHoldout}`);
|
|
155
|
+
}
|
|
156
|
+
if (p.error !== null) {
|
|
157
|
+
lines.push(` Error: ${p.error}`);
|
|
158
|
+
}
|
|
159
|
+
lines.push(` Polls / elapsed: ${result.pollCount} poll(s) / ${result.elapsedSeconds}s`);
|
|
160
|
+
return `${lines.join("\n")}\n`;
|
|
161
|
+
}
|
|
162
|
+
export function runWatch(argv, options = {}) {
|
|
163
|
+
const args = parseWatchArgs(argv);
|
|
164
|
+
if (args.error !== undefined) {
|
|
165
|
+
process.stderr.write(`pr_watch: ${args.error}\n`);
|
|
166
|
+
return EXIT_TERMINAL_ERROR;
|
|
167
|
+
}
|
|
168
|
+
let restoreCwd = null;
|
|
169
|
+
if (args.projectRoot !== null) {
|
|
170
|
+
const target = resolve(args.projectRoot);
|
|
171
|
+
if (!existsSync(target)) {
|
|
172
|
+
process.stderr.write(`pr_watch: --project-root does not exist: ${target}\n`);
|
|
173
|
+
return EXIT_TERMINAL_ERROR;
|
|
174
|
+
}
|
|
175
|
+
restoreCwd = process.cwd();
|
|
176
|
+
process.chdir(target);
|
|
177
|
+
}
|
|
178
|
+
try {
|
|
179
|
+
const result = watch(args.prNumber, args.repo ?? process.env.GH_REPO ?? null, {
|
|
180
|
+
maxWaitMinutes: args.maxWaitMinutes,
|
|
181
|
+
pollSeconds: args.pollSeconds,
|
|
182
|
+
oneShot: args.oneShot,
|
|
183
|
+
runGh: options.runGh ?? defaultRunGh,
|
|
184
|
+
sleepFn: options.sleepFn,
|
|
185
|
+
clockFn: options.clockFn,
|
|
186
|
+
probeFn: options.probeFn,
|
|
187
|
+
stallThreshold: options.stallThreshold,
|
|
188
|
+
});
|
|
189
|
+
if (args.emitJson) {
|
|
190
|
+
process.stdout.write(emitWatchJson(result));
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
process.stdout.write(printWatchHuman(result));
|
|
194
|
+
}
|
|
195
|
+
return result.exitCode;
|
|
196
|
+
}
|
|
197
|
+
finally {
|
|
198
|
+
if (restoreCwd !== null) {
|
|
199
|
+
process.chdir(restoreCwd);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
export function cmdPrWatch(argv, options = {}) {
|
|
204
|
+
return runWatch(argv, options);
|
|
205
|
+
}
|
|
206
|
+
//# sourceMappingURL=main.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { RunGhFn } from "../pr-merge-readiness/types.js";
|
|
2
|
+
import type { WatchProbe } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Run one PR-verdict probe: resolve HEAD, fetch the latest Greptile/SLizard
|
|
5
|
+
* rolling-summary body, and score it through the CANONICAL shared detector
|
|
6
|
+
* (`detect` / `parseConfidence` / `parseLastReviewedSha*` / `evaluateCleanGate`
|
|
7
|
+
* from content-contracts/skills/greptile-detector.ts -- the same module the
|
|
8
|
+
* swarm poller template and its #910/#1035/#1039 tests consume). No second
|
|
9
|
+
* detector (#1056 AC-2). All gh access routes through the injectable RunGhFn
|
|
10
|
+
* seam, which defaults to the UTF-8-safe execFile capture (#1366).
|
|
11
|
+
*/
|
|
12
|
+
export declare function probeOnce(prNumber: number, repoArg: string | null, runGh?: RunGhFn): WatchProbe;
|
|
13
|
+
//# sourceMappingURL=probe.d.ts.map
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { detect, evaluateCleanGate, parseConfidence, parseLastReviewedShaMarkdownLink, parseLastReviewedShaNaiveInline, } from "../content-contracts/skills/greptile-detector.js";
|
|
2
|
+
import { evaluateCiGate } from "../pr-merge-readiness/ci-gate.js";
|
|
3
|
+
import { GREPTILE_ERRORED_SENTINEL } from "../pr-merge-readiness/constants.js";
|
|
4
|
+
import { defaultRunGh, fetchCheckRunsRest, fetchGreptileBodyRest, fetchGreptileCommentBody, fetchPrHeadSha, fetchPrHeadShaRest, resolveRepo, } from "../pr-merge-readiness/gh.js";
|
|
5
|
+
function errorProbe(headSha, message) {
|
|
6
|
+
return {
|
|
7
|
+
found: false,
|
|
8
|
+
headSha,
|
|
9
|
+
lastReviewedSha: null,
|
|
10
|
+
shaMatch: false,
|
|
11
|
+
confidence: null,
|
|
12
|
+
p0Count: 0,
|
|
13
|
+
p1Count: 0,
|
|
14
|
+
hasBlocking: false,
|
|
15
|
+
errored: false,
|
|
16
|
+
ciFailures: 0,
|
|
17
|
+
terminalCheckRun: false,
|
|
18
|
+
isClean: false,
|
|
19
|
+
cleanGateHoldout: null,
|
|
20
|
+
error: message,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Run one PR-verdict probe: resolve HEAD, fetch the latest Greptile/SLizard
|
|
25
|
+
* rolling-summary body, and score it through the CANONICAL shared detector
|
|
26
|
+
* (`detect` / `parseConfidence` / `parseLastReviewedSha*` / `evaluateCleanGate`
|
|
27
|
+
* from content-contracts/skills/greptile-detector.ts -- the same module the
|
|
28
|
+
* swarm poller template and its #910/#1035/#1039 tests consume). No second
|
|
29
|
+
* detector (#1056 AC-2). All gh access routes through the injectable RunGhFn
|
|
30
|
+
* seam, which defaults to the UTF-8-safe execFile capture (#1366).
|
|
31
|
+
*/
|
|
32
|
+
export function probeOnce(prNumber, repoArg, runGh = defaultRunGh) {
|
|
33
|
+
const resolved = resolveRepo(repoArg, runGh);
|
|
34
|
+
const repo = resolved.repo;
|
|
35
|
+
// 1. HEAD SHA -- primary `gh pr view`, then REST fallback when a repo resolved.
|
|
36
|
+
let headSha = fetchPrHeadSha(prNumber, repo, runGh);
|
|
37
|
+
if (headSha === null && repo !== null) {
|
|
38
|
+
headSha = fetchPrHeadShaRest(prNumber, repo, runGh).sha;
|
|
39
|
+
}
|
|
40
|
+
if (headSha === null) {
|
|
41
|
+
const detail = repo === null
|
|
42
|
+
? `could not resolve repo (${resolved.error}); run inside a repo or pass --repo OWNER/REPO`
|
|
43
|
+
: "could not resolve PR HEAD sha (gh pr view + REST both failed)";
|
|
44
|
+
return errorProbe(null, detail);
|
|
45
|
+
}
|
|
46
|
+
// 2. Latest Greptile body -- primary jq path, then REST fallback.
|
|
47
|
+
let body = fetchGreptileCommentBody(prNumber, repo, runGh);
|
|
48
|
+
if (body === null && repo !== null) {
|
|
49
|
+
body = fetchGreptileBodyRest(prNumber, repo, runGh).body;
|
|
50
|
+
}
|
|
51
|
+
if (body === null) {
|
|
52
|
+
return errorProbe(headSha, "could not fetch Greptile comment body (primary + REST both failed)");
|
|
53
|
+
}
|
|
54
|
+
const trimmed = body.trim();
|
|
55
|
+
const found = trimmed.length > 0;
|
|
56
|
+
const errored = trimmed.startsWith(GREPTILE_ERRORED_SENTINEL);
|
|
57
|
+
const findings = detect(body);
|
|
58
|
+
const confidence = parseConfidence(body);
|
|
59
|
+
const lastReviewedSha = parseLastReviewedShaMarkdownLink(body) ?? parseLastReviewedShaNaiveInline(body);
|
|
60
|
+
// 3. CI failures (best-effort). When check-runs are unreachable we degrade to
|
|
61
|
+
// ci_failures=0 / terminal so the Greptile verdict drives; the merge button
|
|
62
|
+
// still owns the hard CI gate via pr:merge-ready (#796).
|
|
63
|
+
let ciFailures = 0;
|
|
64
|
+
let terminalCheckRun = true;
|
|
65
|
+
if (repo !== null) {
|
|
66
|
+
const check = fetchCheckRunsRest(headSha, repo, runGh);
|
|
67
|
+
if (check.summary !== null) {
|
|
68
|
+
const ci = evaluateCiGate(check.checkRuns, {});
|
|
69
|
+
ciFailures = ci.summary.failed_required.length;
|
|
70
|
+
terminalCheckRun = ci.summary.pending_required.length === 0;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const shaMatch = lastReviewedSha !== null && lastReviewedSha === headSha;
|
|
74
|
+
const [isClean, cleanGateHoldout] = evaluateCleanGate({
|
|
75
|
+
lastReviewedSha,
|
|
76
|
+
headSha,
|
|
77
|
+
hasBlocking: findings.has_blocking,
|
|
78
|
+
confidence,
|
|
79
|
+
ciFailures,
|
|
80
|
+
errored,
|
|
81
|
+
terminalCheckRun,
|
|
82
|
+
});
|
|
83
|
+
return {
|
|
84
|
+
found,
|
|
85
|
+
headSha,
|
|
86
|
+
lastReviewedSha,
|
|
87
|
+
shaMatch,
|
|
88
|
+
confidence,
|
|
89
|
+
p0Count: findings.p0_count,
|
|
90
|
+
p1Count: findings.p1_count,
|
|
91
|
+
hasBlocking: findings.has_blocking,
|
|
92
|
+
errored,
|
|
93
|
+
ciFailures,
|
|
94
|
+
terminalCheckRun,
|
|
95
|
+
isClean,
|
|
96
|
+
cleanGateHoldout,
|
|
97
|
+
error: null,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=probe.js.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { RunGhFn } from "../pr-merge-readiness/types.js";
|
|
2
|
+
/** One resolved poll snapshot -- the parsed verdict + SHA-match gate for a PR. */
|
|
3
|
+
export interface WatchProbe {
|
|
4
|
+
/** A Greptile/SLizard rolling-summary comment was present (non-empty body). */
|
|
5
|
+
readonly found: boolean;
|
|
6
|
+
readonly headSha: string | null;
|
|
7
|
+
readonly lastReviewedSha: string | null;
|
|
8
|
+
/** last_reviewed_sha === head_sha -- the SHA-match gate (#1056 stale-review guard). */
|
|
9
|
+
readonly shaMatch: boolean;
|
|
10
|
+
readonly confidence: number | null;
|
|
11
|
+
readonly p0Count: number;
|
|
12
|
+
readonly p1Count: number;
|
|
13
|
+
readonly hasBlocking: boolean;
|
|
14
|
+
readonly errored: boolean;
|
|
15
|
+
readonly ciFailures: number;
|
|
16
|
+
/** All required CI check-runs have a terminal conclusion (none pending). */
|
|
17
|
+
readonly terminalCheckRun: boolean;
|
|
18
|
+
readonly isClean: boolean;
|
|
19
|
+
/** First unmet clean-gate condition (evaluateCleanGate holdout), or null when clean. */
|
|
20
|
+
readonly cleanGateHoldout: string | null;
|
|
21
|
+
/** Non-null when the probe hit an external/config fault (unresolvable repo/HEAD, gh down). */
|
|
22
|
+
readonly error: string | null;
|
|
23
|
+
}
|
|
24
|
+
export type SleepFn = (seconds: number) => void;
|
|
25
|
+
export interface MonotonicClock {
|
|
26
|
+
now(): number;
|
|
27
|
+
}
|
|
28
|
+
export type ProbeFn = (prNumber: number, repo: string | null, runGh: RunGhFn) => WatchProbe;
|
|
29
|
+
export interface WatchOptions {
|
|
30
|
+
readonly maxWaitMinutes?: number;
|
|
31
|
+
readonly pollSeconds?: number;
|
|
32
|
+
readonly oneShot?: boolean;
|
|
33
|
+
readonly stallThreshold?: number;
|
|
34
|
+
readonly runGh?: RunGhFn;
|
|
35
|
+
readonly sleepFn?: SleepFn;
|
|
36
|
+
readonly clockFn?: MonotonicClock;
|
|
37
|
+
readonly probeFn?: ProbeFn;
|
|
38
|
+
}
|
|
39
|
+
export interface WatchResult {
|
|
40
|
+
readonly verdict: string;
|
|
41
|
+
readonly exitCode: number;
|
|
42
|
+
readonly prNumber: number;
|
|
43
|
+
readonly probe: WatchProbe;
|
|
44
|
+
readonly elapsedSeconds: number;
|
|
45
|
+
readonly pollCount: number;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { WatchOptions, WatchProbe, WatchResult } from "./types.js";
|
|
2
|
+
/** One-line stderr status mirror per poll (Tier 1 instrumentation shape, #1039). */
|
|
3
|
+
export declare function formatWatchStatus(poll: number, maxPolls: number, probe: WatchProbe, elapsedSeconds: number): string;
|
|
4
|
+
/**
|
|
5
|
+
* Blocking-by-default poll to a terminal three-state verdict (#1056). Default
|
|
6
|
+
* behaviour is a synchronous wait, so a linear caller (human shell, CI step,
|
|
7
|
+
* single-process agent) gets the verdict for free; --one-shot degrades to a
|
|
8
|
+
* single probe. SHA-match gating means a stale review posted BEFORE your push
|
|
9
|
+
* is never read as the verdict.
|
|
10
|
+
*/
|
|
11
|
+
export declare function watch(prNumber: number, repo: string | null, options?: WatchOptions): WatchResult;
|
|
12
|
+
//# sourceMappingURL=watch.d.ts.map
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { defaultRunGh } from "../pr-merge-readiness/gh.js";
|
|
2
|
+
import { DEFAULT_MAX_WAIT_MINUTES, DEFAULT_POLL_SECONDS, DEFAULT_STALL_THRESHOLD, EXIT_CLEAN, EXIT_NEW_P0_P1, EXIT_TERMINAL_ERROR, VERDICT_CLEAN, VERDICT_CONFIG, VERDICT_ERRORED, VERDICT_NEW_P0_P1, VERDICT_PENDING, VERDICT_STALL, VERDICT_TIMEOUT, } from "./constants.js";
|
|
3
|
+
import { probeOnce } from "./probe.js";
|
|
4
|
+
const systemMonotonicClock = {
|
|
5
|
+
now() {
|
|
6
|
+
return performance.now() / 1000;
|
|
7
|
+
},
|
|
8
|
+
};
|
|
9
|
+
/** Synchronous, non-CPU-spinning sleep (Atomics.wait); injectable in tests. */
|
|
10
|
+
function defaultSleep(seconds) {
|
|
11
|
+
const ms = Math.max(0, Math.trunc(seconds * 1000));
|
|
12
|
+
if (ms === 0) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const shared = new Int32Array(new SharedArrayBuffer(4));
|
|
16
|
+
Atomics.wait(shared, 0, 0, ms);
|
|
17
|
+
}
|
|
18
|
+
function shortSha(sha) {
|
|
19
|
+
return sha === null ? "<none>" : sha.slice(0, 12);
|
|
20
|
+
}
|
|
21
|
+
/** One-line stderr status mirror per poll (Tier 1 instrumentation shape, #1039). */
|
|
22
|
+
export function formatWatchStatus(poll, maxPolls, probe, elapsedSeconds) {
|
|
23
|
+
if (probe.error !== null) {
|
|
24
|
+
return `[pr:watch poll ${poll}/${maxPolls}] CONFIG-ERROR elapsed=${elapsedSeconds}s -- ${probe.error}`;
|
|
25
|
+
}
|
|
26
|
+
return (`[pr:watch poll ${poll}/${maxPolls}] head=${shortSha(probe.headSha)} ` +
|
|
27
|
+
`last_reviewed=${shortSha(probe.lastReviewedSha)} sha_match=${probe.shaMatch} ` +
|
|
28
|
+
`confidence=${probe.confidence} p0=${probe.p0Count} p1=${probe.p1Count} ` +
|
|
29
|
+
`errored=${probe.errored} ci_failures=${probe.ciFailures} is_clean=${probe.isClean} ` +
|
|
30
|
+
`clean_gate_holdout=${probe.cleanGateHoldout} elapsed=${elapsedSeconds}s`);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Blocking-by-default poll to a terminal three-state verdict (#1056). Default
|
|
34
|
+
* behaviour is a synchronous wait, so a linear caller (human shell, CI step,
|
|
35
|
+
* single-process agent) gets the verdict for free; --one-shot degrades to a
|
|
36
|
+
* single probe. SHA-match gating means a stale review posted BEFORE your push
|
|
37
|
+
* is never read as the verdict.
|
|
38
|
+
*/
|
|
39
|
+
export function watch(prNumber, repo, options = {}) {
|
|
40
|
+
const maxWaitMinutes = options.maxWaitMinutes ?? DEFAULT_MAX_WAIT_MINUTES;
|
|
41
|
+
const pollSeconds = Math.max(1, options.pollSeconds ?? DEFAULT_POLL_SECONDS);
|
|
42
|
+
const oneShot = options.oneShot ?? false;
|
|
43
|
+
const stallThreshold = options.stallThreshold ?? DEFAULT_STALL_THRESHOLD;
|
|
44
|
+
const runGh = options.runGh ?? defaultRunGh;
|
|
45
|
+
const clockFn = options.clockFn ?? systemMonotonicClock;
|
|
46
|
+
const sleepFn = options.sleepFn ?? defaultSleep;
|
|
47
|
+
const probeFn = options.probeFn ?? probeOnce;
|
|
48
|
+
const capSeconds = Math.max(0, maxWaitMinutes * 60);
|
|
49
|
+
// Bounded loop (time is still the authority via the cap check below) so no
|
|
50
|
+
// unbounded `while (true)`; +1 covers the trailing poll after the final wait.
|
|
51
|
+
const maxPolls = oneShot ? 1 : Math.max(1, Math.ceil(capSeconds / pollSeconds) + 1);
|
|
52
|
+
const startedAt = clockFn.now();
|
|
53
|
+
let lastProbe = null;
|
|
54
|
+
let stallStreak = 0;
|
|
55
|
+
const build = (verdict, exitCode, probe, poll) => ({
|
|
56
|
+
verdict,
|
|
57
|
+
exitCode,
|
|
58
|
+
prNumber,
|
|
59
|
+
probe,
|
|
60
|
+
elapsedSeconds: Math.round(clockFn.now() - startedAt),
|
|
61
|
+
pollCount: poll,
|
|
62
|
+
});
|
|
63
|
+
for (let poll = 1; poll <= maxPolls; poll += 1) {
|
|
64
|
+
const probe = probeFn(prNumber, repo, runGh);
|
|
65
|
+
lastProbe = probe;
|
|
66
|
+
const elapsed = Math.round(clockFn.now() - startedAt);
|
|
67
|
+
process.stderr.write(`${formatWatchStatus(poll, maxPolls, probe, elapsed)}\n`);
|
|
68
|
+
if (probe.error !== null) {
|
|
69
|
+
return build(VERDICT_CONFIG, EXIT_TERMINAL_ERROR, probe, poll);
|
|
70
|
+
}
|
|
71
|
+
if (probe.isClean) {
|
|
72
|
+
return build(VERDICT_CLEAN, EXIT_CLEAN, probe, poll);
|
|
73
|
+
}
|
|
74
|
+
// SHA-match gate: blocking findings only count as a verdict when the review
|
|
75
|
+
// is on the CURRENT HEAD -- a stale pre-push review is not read as NEW_P0_P1.
|
|
76
|
+
if (probe.hasBlocking && probe.shaMatch) {
|
|
77
|
+
return build(VERDICT_NEW_P0_P1, EXIT_NEW_P0_P1, probe, poll);
|
|
78
|
+
}
|
|
79
|
+
if (probe.errored) {
|
|
80
|
+
return build(VERDICT_ERRORED, EXIT_TERMINAL_ERROR, probe, poll);
|
|
81
|
+
}
|
|
82
|
+
// Stall = a review IS present but stuck on a non-HEAD commit; surface it
|
|
83
|
+
// rather than burning the whole cap waiting for a re-review that isn't coming.
|
|
84
|
+
if (probe.found && !probe.shaMatch) {
|
|
85
|
+
stallStreak += 1;
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
stallStreak = 0;
|
|
89
|
+
}
|
|
90
|
+
if (stallStreak >= stallThreshold) {
|
|
91
|
+
return build(VERDICT_STALL, EXIT_TERMINAL_ERROR, probe, poll);
|
|
92
|
+
}
|
|
93
|
+
if (oneShot) {
|
|
94
|
+
return build(VERDICT_PENDING, EXIT_TERMINAL_ERROR, probe, poll);
|
|
95
|
+
}
|
|
96
|
+
const elapsedNow = clockFn.now() - startedAt;
|
|
97
|
+
if (poll >= maxPolls || elapsedNow + pollSeconds >= capSeconds) {
|
|
98
|
+
return build(VERDICT_TIMEOUT, EXIT_TERMINAL_ERROR, probe, poll);
|
|
99
|
+
}
|
|
100
|
+
sleepFn(pollSeconds);
|
|
101
|
+
}
|
|
102
|
+
// Unreachable in practice (the in-loop cap check returns TIMEOUT first); kept
|
|
103
|
+
// as a total-function fallback so the return type is honoured.
|
|
104
|
+
const probe = lastProbe ?? {
|
|
105
|
+
found: false,
|
|
106
|
+
headSha: null,
|
|
107
|
+
lastReviewedSha: null,
|
|
108
|
+
shaMatch: false,
|
|
109
|
+
confidence: null,
|
|
110
|
+
p0Count: 0,
|
|
111
|
+
p1Count: 0,
|
|
112
|
+
hasBlocking: false,
|
|
113
|
+
errored: false,
|
|
114
|
+
ciFailures: 0,
|
|
115
|
+
terminalCheckRun: false,
|
|
116
|
+
isClean: false,
|
|
117
|
+
cleanGateHoldout: null,
|
|
118
|
+
error: null,
|
|
119
|
+
};
|
|
120
|
+
return build(VERDICT_TIMEOUT, EXIT_TERMINAL_ERROR, probe, maxPolls);
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=watch.js.map
|
|
@@ -34,6 +34,7 @@ export interface EvaluateOptions {
|
|
|
34
34
|
/** Injectable drift probe for tests. */
|
|
35
35
|
probeDriftFn?: (repo: string, cacheRoot: string, source: string) => CacheDriftProbeResult | null;
|
|
36
36
|
}
|
|
37
|
+
export declare function normaliseRepoUrl(url: string): string | null;
|
|
37
38
|
/**
|
|
38
39
|
* Branch-aware recovery hint (#1953 Option 3).
|
|
39
40
|
* Age-stale (or age+drift mixed) → --force bypasses TTL; drift-only → plain refetch.
|
|
@@ -44,19 +44,26 @@ function inferRepoFromGit(projectRoot) {
|
|
|
44
44
|
return null;
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
|
-
function normaliseRepoUrl(url) {
|
|
47
|
+
export function normaliseRepoUrl(url) {
|
|
48
48
|
if (!url)
|
|
49
49
|
return null;
|
|
50
|
-
const cleaned = url
|
|
51
|
-
|
|
50
|
+
const cleaned = url
|
|
51
|
+
.trim()
|
|
52
|
+
.replace(/\/$/, "")
|
|
53
|
+
.replace(/\.git$/, "");
|
|
54
|
+
// Match `github.com` only as the HOST component of the remote URL: right
|
|
55
|
+
// after an optional scheme and optional `user@`, and immediately followed by
|
|
56
|
+
// `:` or `/`. A plain `includes("github.com")` would also accept spoofed
|
|
57
|
+
// hosts like `github.com.evil.com` or `evil.com/github.com/...` (CodeQL
|
|
58
|
+
// js/incomplete-url-substring-sanitization). Supports https/ssh/git schemes
|
|
59
|
+
// and the scp-like `git@github.com:owner/repo` form.
|
|
60
|
+
const match = cleaned.match(/^(?:(?:https?|ssh|git):\/\/)?(?:[^/@]+@)?github\.com[:/]+([^/]+)\/([^/]+)/i);
|
|
61
|
+
if (!match)
|
|
52
62
|
return null;
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const parts = tail.split("/");
|
|
58
|
-
if (parts.length >= 2 && parts[0] && parts[1]) {
|
|
59
|
-
return `${parts[0]}/${parts[1]}`;
|
|
63
|
+
const owner = match[1];
|
|
64
|
+
const repo = match[2];
|
|
65
|
+
if (owner && repo) {
|
|
66
|
+
return `${owner}/${repo}`;
|
|
60
67
|
}
|
|
61
68
|
return null;
|
|
62
69
|
}
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
+
import { LEGACY_INFO_ROOT_KEY, MIGRATED_INFO_ROOT_KEY } from "../xbrief-migrate/constants.js";
|
|
1
2
|
type JsonObject = Record<string, unknown>;
|
|
3
|
+
/** PROJECT-DEFINITION artifact shape (filename + envelope key + emitted version). */
|
|
4
|
+
interface ProjectDefinitionLayout {
|
|
5
|
+
readonly filename: string;
|
|
6
|
+
readonly infoRootKey: typeof MIGRATED_INFO_ROOT_KEY | typeof LEGACY_INFO_ROOT_KEY;
|
|
7
|
+
readonly infoVersion: string;
|
|
8
|
+
}
|
|
2
9
|
/** Durable review state for PROJECT-DEFINITION narrative staleness (#640). */
|
|
3
10
|
export interface StalenessReviewMetadata {
|
|
4
11
|
/** ISO-8601 UTC when narratives were last reviewed/acknowledged. */
|
|
@@ -24,12 +31,19 @@ export declare function buildStalenessAcknowledgement(completedItems: readonly L
|
|
|
24
31
|
existing?: StalenessReviewMetadata | null;
|
|
25
32
|
}): StalenessReviewMetadata;
|
|
26
33
|
export declare function computeStalenessFlags(narratives: Record<string, string>, completedItems: readonly LifecycleItem[], review?: StalenessReviewMetadata | null): string[];
|
|
27
|
-
export declare function createSkeleton(items: LifecycleItem[], now: string): JsonObject;
|
|
34
|
+
export declare function createSkeleton(items: LifecycleItem[], now: string, layout: ProjectDefinitionLayout): JsonObject;
|
|
28
35
|
export interface RenderProjectOptions {
|
|
29
36
|
readonly now?: Date;
|
|
30
37
|
}
|
|
31
38
|
export type RenderProjectResult = readonly [boolean, string];
|
|
32
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* Regenerate the PROJECT-DEFINITION artifact for `vbriefDir`.
|
|
41
|
+
*
|
|
42
|
+
* Layout-aware (#2149): on a migrated `xbrief/` root it targets
|
|
43
|
+
* `PROJECT-DEFINITION.xbrief.json` with an `xBRIEFInfo` envelope; on a legacy `vbrief/`
|
|
44
|
+
* root it keeps `PROJECT-DEFINITION.vbrief.json` + `vBRIEFInfo`.
|
|
45
|
+
* (Mirrors ``scripts/project_render.render_project_definition``.)
|
|
46
|
+
*/
|
|
33
47
|
export declare function renderProjectDefinition(vbriefDir: string, options?: RenderProjectOptions): RenderProjectResult;
|
|
34
48
|
/**
|
|
35
49
|
* Mark current completed scopes as reviewed for PROJECT-DEFINITION narratives.
|