@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.
Files changed (147) hide show
  1. package/dist/agents-md-advisory/evaluate.d.ts +44 -0
  2. package/dist/agents-md-advisory/evaluate.js +115 -0
  3. package/dist/agents-md-advisory/index.d.ts +2 -0
  4. package/dist/agents-md-advisory/index.js +2 -0
  5. package/dist/agents-md-budget/evaluate.d.ts +38 -0
  6. package/dist/agents-md-budget/evaluate.js +152 -0
  7. package/dist/agents-md-budget/index.d.ts +2 -0
  8. package/dist/agents-md-budget/index.js +2 -0
  9. package/dist/check/index.d.ts +1 -1
  10. package/dist/check/index.js +1 -1
  11. package/dist/check/orchestrator.d.ts +14 -4
  12. package/dist/check/orchestrator.js +30 -6
  13. package/dist/codebase/map.d.ts +1 -0
  14. package/dist/codebase/map.js +5 -2
  15. package/dist/doctor/checks.js +11 -11
  16. package/dist/doctor/constants.d.ts +7 -2
  17. package/dist/doctor/constants.js +17 -1
  18. package/dist/doctor/flags.js +5 -1
  19. package/dist/doctor/main.js +99 -8
  20. package/dist/doctor/payload-staleness.js +15 -6
  21. package/dist/doctor/types.d.ts +7 -0
  22. package/dist/forward-coverage/evaluate.d.ts +55 -0
  23. package/dist/forward-coverage/evaluate.js +254 -0
  24. package/dist/index.d.ts +1 -0
  25. package/dist/index.js +1 -0
  26. package/dist/init-deposit/constants.d.ts +14 -0
  27. package/dist/init-deposit/constants.js +10 -0
  28. package/dist/init-deposit/hygiene.d.ts +50 -0
  29. package/dist/init-deposit/hygiene.js +176 -0
  30. package/dist/init-deposit/init-deposit.d.ts +1 -0
  31. package/dist/init-deposit/init-deposit.js +7 -1
  32. package/dist/init-deposit/refresh.d.ts +2 -0
  33. package/dist/init-deposit/refresh.js +48 -30
  34. package/dist/init-deposit/scaffold.d.ts +3 -4
  35. package/dist/init-deposit/scaffold.js +3 -24
  36. package/dist/intake/github-auth-modes.d.ts +1 -0
  37. package/dist/intake/github-auth-modes.js +1 -0
  38. package/dist/intake/issue-ingest.d.ts +6 -2
  39. package/dist/intake/issue-ingest.js +65 -9
  40. package/dist/integration-e2e/helpers.d.ts +0 -1
  41. package/dist/integration-e2e/helpers.js +3 -7
  42. package/dist/layout/lifecycle-ref.d.ts +15 -0
  43. package/dist/layout/lifecycle-ref.js +55 -0
  44. package/dist/platform/agents-consumer-header.d.ts +13 -0
  45. package/dist/platform/agents-consumer-header.js +57 -0
  46. package/dist/platform/agents-md.d.ts +28 -0
  47. package/dist/platform/agents-md.js +38 -1
  48. package/dist/platform/index.d.ts +1 -0
  49. package/dist/platform/index.js +1 -0
  50. package/dist/policy/agents-md-advisory.d.ts +52 -0
  51. package/dist/policy/agents-md-advisory.js +63 -0
  52. package/dist/policy/agents-md-budget.d.ts +24 -0
  53. package/dist/policy/agents-md-budget.js +89 -0
  54. package/dist/policy/index.d.ts +1 -0
  55. package/dist/policy/index.js +1 -0
  56. package/dist/policy/resolve.js +56 -46
  57. package/dist/pr-merge-readiness/ci-gate.d.ts +28 -0
  58. package/dist/pr-merge-readiness/ci-gate.js +79 -0
  59. package/dist/pr-merge-readiness/compute.d.ts +15 -1
  60. package/dist/pr-merge-readiness/compute.js +129 -9
  61. package/dist/pr-merge-readiness/gh.d.ts +8 -0
  62. package/dist/pr-merge-readiness/gh.js +30 -5
  63. package/dist/pr-merge-readiness/index.d.ts +5 -1
  64. package/dist/pr-merge-readiness/index.js +3 -0
  65. package/dist/pr-merge-readiness/main.d.ts +3 -0
  66. package/dist/pr-merge-readiness/main.js +73 -6
  67. package/dist/pr-merge-readiness/mergeability.d.ts +43 -0
  68. package/dist/pr-merge-readiness/mergeability.js +105 -0
  69. package/dist/pr-merge-readiness/output.js +20 -0
  70. package/dist/pr-merge-readiness/slizard-gate.d.ts +47 -0
  71. package/dist/pr-merge-readiness/slizard-gate.js +119 -0
  72. package/dist/pr-monitor/index.d.ts +1 -1
  73. package/dist/pr-monitor/index.js +1 -1
  74. package/dist/pr-monitor/monitor.d.ts +8 -2
  75. package/dist/pr-monitor/monitor.js +35 -6
  76. package/dist/pr-wait-mergeable/wrappers.d.ts +12 -1
  77. package/dist/pr-wait-mergeable/wrappers.js +13 -5
  78. package/dist/pr-watch/constants.d.ts +33 -0
  79. package/dist/pr-watch/constants.js +33 -0
  80. package/dist/pr-watch/index.d.ts +6 -0
  81. package/dist/pr-watch/index.js +6 -0
  82. package/dist/pr-watch/main.d.ts +21 -0
  83. package/dist/pr-watch/main.js +206 -0
  84. package/dist/pr-watch/probe.d.ts +13 -0
  85. package/dist/pr-watch/probe.js +100 -0
  86. package/dist/pr-watch/types.d.ts +47 -0
  87. package/dist/pr-watch/types.js +2 -0
  88. package/dist/pr-watch/watch.d.ts +12 -0
  89. package/dist/pr-watch/watch.js +122 -0
  90. package/dist/preflight-cache/evaluate.d.ts +1 -0
  91. package/dist/preflight-cache/evaluate.js +17 -10
  92. package/dist/render/project-render.d.ts +16 -2
  93. package/dist/render/project-render.js +155 -97
  94. package/dist/scope/capacity-stamp.d.ts +24 -2
  95. package/dist/scope/capacity-stamp.js +80 -28
  96. package/dist/scope/decomposed-refs.js +2 -2
  97. package/dist/scope/project-definition-sync.js +3 -3
  98. package/dist/scope/vbrief-ref.d.ts +2 -1
  99. package/dist/scope/vbrief-ref.js +3 -2
  100. package/dist/spec-authority/constants.d.ts +6 -0
  101. package/dist/spec-authority/constants.js +11 -0
  102. package/dist/spec-authority/resolver.js +3 -3
  103. package/dist/swarm/finalize-cohort-cli.d.ts +5 -0
  104. package/dist/swarm/finalize-cohort-cli.js +100 -0
  105. package/dist/swarm/finalize-cohort.d.ts +39 -0
  106. package/dist/swarm/finalize-cohort.js +589 -0
  107. package/dist/swarm/index.d.ts +2 -0
  108. package/dist/swarm/index.js +2 -0
  109. package/dist/swarm/launch.d.ts +13 -0
  110. package/dist/swarm/launch.js +19 -7
  111. package/dist/swarm/routing-verify.d.ts +1 -1
  112. package/dist/swarm/routing-verify.js +11 -10
  113. package/dist/swarm/routing.d.ts +9 -0
  114. package/dist/swarm/routing.js +46 -0
  115. package/dist/swarm/verify-review-clean-cli.js +25 -1
  116. package/dist/swarm/verify-review-clean.d.ts +9 -1
  117. package/dist/swarm/verify-review-clean.js +76 -3
  118. package/dist/triage/queue/index.d.ts +1 -0
  119. package/dist/triage/queue/index.js +1 -0
  120. package/dist/triage/queue/project.d.ts +6 -1
  121. package/dist/triage/queue/project.js +9 -4
  122. package/dist/triage/queue/reconcile-live-state.d.ts +42 -0
  123. package/dist/triage/queue/reconcile-live-state.js +54 -0
  124. package/dist/triage/scope/mutations-core.js +91 -81
  125. package/dist/triage/scope/resolve.js +4 -3
  126. package/dist/triage/scope-drift/add-ignore.js +33 -32
  127. package/dist/triage/scope-drift/scope-rules.js +3 -3
  128. package/dist/triage/subscribe/index.js +51 -45
  129. package/dist/triage/welcome/writers.js +79 -64
  130. package/dist/vbrief-build/project-definition-io.js +8 -3
  131. package/dist/vbrief-validate/paths.js +8 -4
  132. package/dist/vbrief-validate/precutover.d.ts +1 -1
  133. package/dist/vbrief-validate/precutover.js +12 -10
  134. package/dist/verify-source/biome-config.d.ts +41 -0
  135. package/dist/verify-source/biome-config.js +97 -0
  136. package/dist/verify-source/index.d.ts +1 -0
  137. package/dist/verify-source/index.js +1 -0
  138. package/dist/xbrief-migrate/agents-header.d.ts +69 -0
  139. package/dist/xbrief-migrate/agents-header.js +179 -0
  140. package/dist/xbrief-migrate/fs-helpers.d.ts +3 -0
  141. package/dist/xbrief-migrate/fs-helpers.js +11 -0
  142. package/dist/xbrief-migrate/index.d.ts +1 -0
  143. package/dist/xbrief-migrate/index.js +1 -0
  144. package/dist/xbrief-migrate/migrate-project.js +29 -10
  145. package/package.json +15 -7
  146. package/dist/install-upgrade/index.d.ts +0 -13
  147. 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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.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.replace(/\/$/, "").replace(/\.git$/, "");
51
- if (!cleaned.includes("github.com"))
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 tail = cleaned
54
- .split("github.com")
55
- .pop()
56
- ?.replace(/^[:/]+/, "") ?? "";
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
- /** Regenerate PROJECT-DEFINITION.vbrief.json (mirrors ``scripts/project_render.render_project_definition``). */
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.