@deftai/directive-core 0.79.1 → 0.79.3
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/cache/fetch.js +14 -4
- package/dist/capacity/backfill.js +4 -2
- package/dist/category-b-namespace/index.js +31 -6
- package/dist/check/orchestrator.d.ts +4 -0
- package/dist/check/orchestrator.js +9 -1
- package/dist/content-contracts/skills/skill-frontmatter.js +6 -2
- package/dist/eval/health.js +8 -1
- package/dist/eval/run.js +2 -0
- package/dist/fs/projection-containment.d.ts +11 -0
- package/dist/fs/projection-containment.js +62 -1
- package/dist/hooks/dispatcher.d.ts +1 -1
- package/dist/hooks/dispatcher.js +66 -7
- package/dist/init-deposit/hygiene.js +3 -0
- package/dist/intake/reconcile-issues.d.ts +1 -1
- package/dist/intake/reconcile-issues.js +25 -2
- package/dist/issue-sync/sync-from-xbrief-cli.d.ts +1 -0
- package/dist/issue-sync/sync-from-xbrief-cli.js +3 -0
- package/dist/issue-sync/sync-from-xbrief.d.ts +4 -0
- package/dist/issue-sync/sync-from-xbrief.js +12 -0
- package/dist/lifecycle/event.d.ts +15 -0
- package/dist/lifecycle/event.js +163 -0
- package/dist/lifecycle/index.d.ts +1 -0
- package/dist/lifecycle/index.js +1 -0
- package/dist/orchestration/verify-judgment-gates.js +18 -2
- package/dist/platform/resolve-version.d.ts +2 -6
- package/dist/platform/resolve-version.js +3 -129
- package/dist/pr-merge-readiness/compute.d.ts +2 -1
- package/dist/pr-merge-readiness/compute.js +23 -6
- package/dist/pr-merge-readiness/evaluate.d.ts +2 -1
- package/dist/pr-merge-readiness/evaluate.js +46 -31
- package/dist/pr-merge-readiness/gh.js +13 -5
- package/dist/pr-merge-readiness/greptile-inline.d.ts +27 -0
- package/dist/pr-merge-readiness/greptile-inline.js +239 -0
- package/dist/pr-merge-readiness/index.d.ts +1 -0
- package/dist/pr-merge-readiness/index.js +1 -0
- package/dist/pr-merge-readiness/mergeability.d.ts +2 -1
- package/dist/pr-merge-readiness/mergeability.js +7 -1
- package/dist/pr-merge-readiness/test-gh-fixtures.helpers.d.ts +18 -0
- package/dist/pr-merge-readiness/test-gh-fixtures.helpers.js +76 -0
- package/dist/pr-monitor/monitor.js +10 -1
- package/dist/pr-watch/constants.d.ts +5 -0
- package/dist/pr-watch/constants.js +27 -0
- package/dist/pr-watch/index.d.ts +1 -1
- package/dist/pr-watch/index.js +1 -1
- package/dist/pr-watch/main.d.ts +3 -0
- package/dist/pr-watch/main.js +29 -3
- package/dist/release/constants.d.ts +5 -0
- package/dist/release/constants.js +14 -2
- package/dist/release/flags.js +16 -0
- package/dist/release/main.js +7 -0
- package/dist/release/pipeline.js +7 -3
- package/dist/release/preflight.js +8 -1
- package/dist/release/skip-ci-incident.d.ts +22 -0
- package/dist/release/skip-ci-incident.js +68 -0
- package/dist/release/types.d.ts +2 -0
- package/dist/release/version.js +68 -32
- package/dist/release-e2e/entrypoint-worker.js +1 -0
- package/dist/release-e2e/entrypoint.js +13 -0
- package/dist/review-monitor/constants.d.ts +14 -0
- package/dist/review-monitor/constants.js +55 -0
- package/dist/review-monitor/index.d.ts +5 -0
- package/dist/review-monitor/index.js +5 -0
- package/dist/review-monitor/record.d.ts +50 -0
- package/dist/review-monitor/record.js +178 -0
- package/dist/review-monitor/tier-detection.d.ts +15 -0
- package/dist/review-monitor/tier-detection.js +61 -0
- package/dist/review-monitor/verify.d.ts +32 -0
- package/dist/review-monitor/verify.js +171 -0
- package/dist/scope/demote.js +20 -0
- package/dist/swarm/worktrees.d.ts +3 -0
- package/dist/swarm/worktrees.js +18 -2
- package/dist/task-surface/index.js +3 -2
- package/dist/vitest-runner/win32-coverage-tmp-setup.d.ts +16 -8
- package/dist/vitest-runner/win32-coverage-tmp-setup.js +41 -14
- package/dist/xbrief-migrate/migration-containment.js +9 -25
- package/package.json +7 -3
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { closeSync, existsSync, fdatasyncSync, mkdirSync, openSync, readFileSync, renameSync, writeSync, } from "node:fs";
|
|
3
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
4
|
+
import { DEFAULT_STALE_MINUTES, REVIEW_MONITOR_FILENAME, SCHEMA_VERSION } from "./constants.js";
|
|
5
|
+
function resolveMainWorktreeRoot(startDir) {
|
|
6
|
+
let root = resolve(startDir);
|
|
7
|
+
try {
|
|
8
|
+
const out = execFileSync("git", ["rev-parse", "--git-common-dir"], {
|
|
9
|
+
cwd: startDir,
|
|
10
|
+
encoding: "utf8",
|
|
11
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
12
|
+
}).trim();
|
|
13
|
+
if (out.length > 0) {
|
|
14
|
+
const commonDir = isAbsolute(out) ? out : resolve(startDir, out);
|
|
15
|
+
root = dirname(commonDir);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// Not a git work tree -- fall back to startDir.
|
|
20
|
+
}
|
|
21
|
+
return root;
|
|
22
|
+
}
|
|
23
|
+
export function reviewMonitorPath(projectRoot) {
|
|
24
|
+
const root = resolveMainWorktreeRoot(projectRoot);
|
|
25
|
+
return join(root, ".deft", REVIEW_MONITOR_FILENAME);
|
|
26
|
+
}
|
|
27
|
+
function atomicWriteJson(path, payload) {
|
|
28
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
29
|
+
const tmp = `${path}.tmp-${process.pid}`;
|
|
30
|
+
const text = `${JSON.stringify(payload, null, 2)}\n`;
|
|
31
|
+
const fd = openSync(tmp, "w");
|
|
32
|
+
try {
|
|
33
|
+
writeSync(fd, text, undefined, "utf8");
|
|
34
|
+
fdatasyncSync(fd);
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
closeSync(fd);
|
|
38
|
+
}
|
|
39
|
+
renameSync(tmp, path);
|
|
40
|
+
}
|
|
41
|
+
export function emptyReviewMonitorFile() {
|
|
42
|
+
return { schema_version: SCHEMA_VERSION, records: [] };
|
|
43
|
+
}
|
|
44
|
+
export function readReviewMonitorFile(path) {
|
|
45
|
+
if (!existsSync(path)) {
|
|
46
|
+
return { data: emptyReviewMonitorFile(), error: null };
|
|
47
|
+
}
|
|
48
|
+
let parsed;
|
|
49
|
+
try {
|
|
50
|
+
parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
51
|
+
}
|
|
52
|
+
catch (exc) {
|
|
53
|
+
return { data: null, error: `${path}: invalid JSON (${String(exc)}).` };
|
|
54
|
+
}
|
|
55
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
56
|
+
return { data: null, error: `${path}: review-monitor file must be a JSON object.` };
|
|
57
|
+
}
|
|
58
|
+
const obj = parsed;
|
|
59
|
+
const recordsRaw = obj.records;
|
|
60
|
+
const records = [];
|
|
61
|
+
if (Array.isArray(recordsRaw)) {
|
|
62
|
+
for (const entry of recordsRaw) {
|
|
63
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const e = entry;
|
|
67
|
+
const pr = e.pr;
|
|
68
|
+
const monitorAgentId = e.monitor_agent_id;
|
|
69
|
+
const platformPrimitive = e.platform_primitive;
|
|
70
|
+
const startedAt = e.started_at;
|
|
71
|
+
const worktreePath = e.worktree_path;
|
|
72
|
+
if (typeof pr !== "number" ||
|
|
73
|
+
!Number.isInteger(pr) ||
|
|
74
|
+
pr <= 0 ||
|
|
75
|
+
typeof monitorAgentId !== "string" ||
|
|
76
|
+
monitorAgentId.trim().length === 0 ||
|
|
77
|
+
typeof platformPrimitive !== "string" ||
|
|
78
|
+
typeof startedAt !== "string" ||
|
|
79
|
+
typeof worktreePath !== "string") {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
records.push({
|
|
83
|
+
pr,
|
|
84
|
+
repo: typeof e.repo === "string" ? e.repo : null,
|
|
85
|
+
head_sha: typeof e.head_sha === "string" ? e.head_sha : null,
|
|
86
|
+
platform_primitive: platformPrimitive,
|
|
87
|
+
monitor_agent_id: monitorAgentId.trim(),
|
|
88
|
+
started_at: startedAt,
|
|
89
|
+
worktree_path: worktreePath,
|
|
90
|
+
parent_session_id: typeof e.parent_session_id === "string" ? e.parent_session_id : null,
|
|
91
|
+
ended_at: typeof e.ended_at === "string" ? e.ended_at : null,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
data: {
|
|
97
|
+
schema_version: typeof obj.schema_version === "number" ? obj.schema_version : SCHEMA_VERSION,
|
|
98
|
+
records,
|
|
99
|
+
},
|
|
100
|
+
error: null,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
export function writeReviewMonitorFile(path, data) {
|
|
104
|
+
atomicWriteJson(path, data);
|
|
105
|
+
}
|
|
106
|
+
export function registerReviewMonitor(input) {
|
|
107
|
+
const path = reviewMonitorPath(input.projectRoot);
|
|
108
|
+
const { data, error } = readReviewMonitorFile(path);
|
|
109
|
+
if (data === null) {
|
|
110
|
+
throw new Error(error ?? "could not read review-monitor file");
|
|
111
|
+
}
|
|
112
|
+
const startedAt = (input.startedAt ?? new Date()).toISOString();
|
|
113
|
+
const record = {
|
|
114
|
+
pr: input.pr,
|
|
115
|
+
repo: input.repo ?? null,
|
|
116
|
+
head_sha: input.headSha ?? null,
|
|
117
|
+
platform_primitive: input.platformPrimitive,
|
|
118
|
+
monitor_agent_id: input.monitorAgentId.trim(),
|
|
119
|
+
started_at: startedAt,
|
|
120
|
+
worktree_path: resolve(input.projectRoot),
|
|
121
|
+
parent_session_id: input.parentSessionId ?? null,
|
|
122
|
+
ended_at: null,
|
|
123
|
+
};
|
|
124
|
+
const active = data.records.filter((r) => r.ended_at === null && r.pr !== input.pr);
|
|
125
|
+
writeReviewMonitorFile(path, {
|
|
126
|
+
schema_version: SCHEMA_VERSION,
|
|
127
|
+
records: [...active, record],
|
|
128
|
+
});
|
|
129
|
+
return { path, record };
|
|
130
|
+
}
|
|
131
|
+
export function parseIso8601Utc(value) {
|
|
132
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
const trimmed = value.trim();
|
|
136
|
+
let candidate = trimmed;
|
|
137
|
+
if (trimmed.endsWith("Z")) {
|
|
138
|
+
candidate = `${trimmed.slice(0, -1)}+00:00`;
|
|
139
|
+
}
|
|
140
|
+
if (!candidate.endsWith("+00:00")) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
const parsed = new Date(candidate);
|
|
144
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
return parsed;
|
|
148
|
+
}
|
|
149
|
+
export function isRecordActive(record, options) {
|
|
150
|
+
if (record.ended_at !== null) {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
const started = parseIso8601Utc(record.started_at);
|
|
154
|
+
if (started === null) {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
const now = options.now ?? new Date();
|
|
158
|
+
const staleMinutes = options.staleMinutes ?? DEFAULT_STALE_MINUTES;
|
|
159
|
+
const ageMs = now.getTime() - started.getTime();
|
|
160
|
+
if (ageMs > staleMinutes * 60 * 1000) {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
if (options.headSha !== undefined && options.headSha !== null && record.head_sha !== null) {
|
|
164
|
+
return record.head_sha === options.headSha;
|
|
165
|
+
}
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
export function findActiveMonitorForPr(file, pr, options) {
|
|
169
|
+
const matches = file.records.filter((r) => r.pr === pr && isRecordActive(r, options));
|
|
170
|
+
if (matches.length === 0) {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
return matches.sort((a, b) => b.started_at.localeCompare(a.started_at))[0] ?? null;
|
|
174
|
+
}
|
|
175
|
+
export function defaultSubagentStatusDir(projectRoot) {
|
|
176
|
+
return join(resolve(projectRoot), ".deft-scratch", "subagent-status");
|
|
177
|
+
}
|
|
178
|
+
//# sourceMappingURL=record.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { MONITORING_TIER_1, MONITORING_TIER_2, MONITORING_TIER_3 } from "./constants.js";
|
|
2
|
+
export type PlatformPrimitive = "start_agent" | "spawn_subagent" | "cursor-task";
|
|
3
|
+
export interface MonitoringTierProbe {
|
|
4
|
+
readonly tier: typeof MONITORING_TIER_1 | typeof MONITORING_TIER_2 | typeof MONITORING_TIER_3;
|
|
5
|
+
readonly primitive: PlatformPrimitive | null;
|
|
6
|
+
readonly descriptor: string | null;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Inline Tier-1 detection aligned with the swarm Phase 3 / review-cycle matrix
|
|
10
|
+
* (#1877 / #2655). Prefer `task platform:capabilities` when available (#1357);
|
|
11
|
+
* this probe does not block MVP.
|
|
12
|
+
*/
|
|
13
|
+
export declare function probeMonitoringTier(environ?: NodeJS.ProcessEnv): MonitoringTierProbe;
|
|
14
|
+
export declare function isTier1(probe: MonitoringTierProbe): boolean;
|
|
15
|
+
//# sourceMappingURL=tier-detection.d.ts.map
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { MONITORING_TIER_1, MONITORING_TIER_2, MONITORING_TIER_3 } from "./constants.js";
|
|
2
|
+
const TRUTHY = new Set(["1", "true", "yes", "on"]);
|
|
3
|
+
function envTruthy(environ, name) {
|
|
4
|
+
return TRUTHY.has((environ[name] ?? "").trim().toLowerCase());
|
|
5
|
+
}
|
|
6
|
+
function probeOverride(environ) {
|
|
7
|
+
const raw = (environ.DEFT_MONITOR_TIER ?? environ.DEFT_MONITOR_TIER_OVERRIDE ?? "").trim();
|
|
8
|
+
if (raw === "1" || raw.toLowerCase() === "tier1") {
|
|
9
|
+
const primitive = environ.DEFT_MONITOR_TIER1_PRIMITIVE ?? "cursor-task";
|
|
10
|
+
return { tier: MONITORING_TIER_1, primitive, descriptor: "override-tier1" };
|
|
11
|
+
}
|
|
12
|
+
if (raw === "3" || raw.toLowerCase() === "tier3") {
|
|
13
|
+
return { tier: MONITORING_TIER_3, primitive: null, descriptor: "generic-terminal" };
|
|
14
|
+
}
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Inline Tier-1 detection aligned with the swarm Phase 3 / review-cycle matrix
|
|
19
|
+
* (#1877 / #2655). Prefer `task platform:capabilities` when available (#1357);
|
|
20
|
+
* this probe does not block MVP.
|
|
21
|
+
*/
|
|
22
|
+
export function probeMonitoringTier(environ = process.env) {
|
|
23
|
+
const override = probeOverride(environ);
|
|
24
|
+
if (override !== null) {
|
|
25
|
+
return override;
|
|
26
|
+
}
|
|
27
|
+
if (envTruthy(environ, "DEFT_PROBE_START_AGENT") || envTruthy(environ, "DEFT_HAS_START_AGENT")) {
|
|
28
|
+
return { tier: MONITORING_TIER_1, primitive: "start_agent", descriptor: "warp-orchestrated" };
|
|
29
|
+
}
|
|
30
|
+
if (envTruthy(environ, "WARP_IS_WARP_TERMINAL") || envTruthy(environ, "WARP_TERMINAL_SESSION")) {
|
|
31
|
+
return { tier: MONITORING_TIER_1, primitive: "start_agent", descriptor: "warp-manual" };
|
|
32
|
+
}
|
|
33
|
+
if (envTruthy(environ, "CURSOR_COMPOSER")) {
|
|
34
|
+
return { tier: MONITORING_TIER_1, primitive: "cursor-task", descriptor: "cursor-composer" };
|
|
35
|
+
}
|
|
36
|
+
if (envTruthy(environ, "CURSOR_AGENT")) {
|
|
37
|
+
return {
|
|
38
|
+
tier: MONITORING_TIER_1,
|
|
39
|
+
primitive: "cursor-task",
|
|
40
|
+
descriptor: "cursor-cloud-agent",
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const runtime = (environ.DEFT_AGENT_RUNTIME ?? "").trim().toLowerCase();
|
|
44
|
+
if (envTruthy(environ, "DEFT_PROBE_GROK_BUILD") ||
|
|
45
|
+
envTruthy(environ, "GROK_BUILD") ||
|
|
46
|
+
runtime === "grok-build") {
|
|
47
|
+
return { tier: MONITORING_TIER_1, primitive: "spawn_subagent", descriptor: "grok-build" };
|
|
48
|
+
}
|
|
49
|
+
if (envTruthy(environ, "DEFT_PROBE_SPAWN_SUBAGENT") ||
|
|
50
|
+
envTruthy(environ, "DEFT_HAS_SPAWN_SUBAGENT")) {
|
|
51
|
+
return { tier: MONITORING_TIER_1, primitive: "spawn_subagent", descriptor: "grok-build" };
|
|
52
|
+
}
|
|
53
|
+
if (envTruthy(environ, "DEFT_MONITOR_TIER2") || envTruthy(environ, "DEFT_HAS_AUTO_REINVOKE")) {
|
|
54
|
+
return { tier: MONITORING_TIER_2, primitive: null, descriptor: "yield-between-polls" };
|
|
55
|
+
}
|
|
56
|
+
return { tier: MONITORING_TIER_3, primitive: null, descriptor: "generic-terminal" };
|
|
57
|
+
}
|
|
58
|
+
export function isTier1(probe) {
|
|
59
|
+
return probe.tier === MONITORING_TIER_1;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=tier-detection.js.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { EXIT_CONFIG_ERROR, EXIT_NOT_READY, EXIT_READY, MONITORING_TIER_1, MONITORING_TIER_3 } from "./constants.js";
|
|
2
|
+
import { type ReviewMonitorRecord } from "./record.js";
|
|
3
|
+
import { type MonitoringTierProbe } from "./tier-detection.js";
|
|
4
|
+
export type ReviewMonitorCallSite = "solo" | "swarm-phase5-6" | "swarm-phase6-cascade" | "unspecified";
|
|
5
|
+
export interface VerifyReviewMonitorArgs {
|
|
6
|
+
readonly pr: number;
|
|
7
|
+
readonly projectRoot: string;
|
|
8
|
+
readonly repo?: string | null;
|
|
9
|
+
readonly headSha?: string | null;
|
|
10
|
+
readonly callSite?: ReviewMonitorCallSite;
|
|
11
|
+
readonly approach3?: boolean;
|
|
12
|
+
readonly approach3Warned?: boolean;
|
|
13
|
+
readonly staleMinutes?: number;
|
|
14
|
+
readonly now?: Date;
|
|
15
|
+
readonly environ?: NodeJS.ProcessEnv;
|
|
16
|
+
}
|
|
17
|
+
export interface VerifyReviewMonitorResult {
|
|
18
|
+
readonly exitCode: typeof EXIT_READY | typeof EXIT_NOT_READY | typeof EXIT_CONFIG_ERROR;
|
|
19
|
+
readonly message: string;
|
|
20
|
+
readonly tier: MonitoringTierProbe;
|
|
21
|
+
readonly monitorRecord: ReviewMonitorRecord | null;
|
|
22
|
+
readonly heartbeatActive: boolean;
|
|
23
|
+
readonly callSite: ReviewMonitorCallSite;
|
|
24
|
+
}
|
|
25
|
+
export declare function hasActivePollingHeartbeat(projectRoot: string, pr: number, options?: {
|
|
26
|
+
now?: Date;
|
|
27
|
+
staleMinutes?: number;
|
|
28
|
+
}): boolean;
|
|
29
|
+
export declare function evaluateReviewMonitorGate(args: VerifyReviewMonitorArgs): VerifyReviewMonitorResult;
|
|
30
|
+
export declare function verifyResultToJson(result: VerifyReviewMonitorResult): Record<string, unknown>;
|
|
31
|
+
export { EXIT_CONFIG_ERROR, EXIT_NOT_READY, EXIT_READY, MONITORING_TIER_1, MONITORING_TIER_3 };
|
|
32
|
+
//# sourceMappingURL=verify.d.ts.map
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { existsSync, statSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { sweepScratchDirs } from "../orchestration/subagent-monitor.js";
|
|
4
|
+
import { EXIT_CONFIG_ERROR, EXIT_NOT_READY, EXIT_READY, MONITORING_TIER_1, MONITORING_TIER_3, } from "./constants.js";
|
|
5
|
+
import { defaultSubagentStatusDir, findActiveMonitorForPr, readReviewMonitorFile, reviewMonitorPath, } from "./record.js";
|
|
6
|
+
import { isTier1, probeMonitoringTier } from "./tier-detection.js";
|
|
7
|
+
function spawnRedirect(probe) {
|
|
8
|
+
const primitive = probe.primitive ?? "sub-agent";
|
|
9
|
+
return (`Spawn an Approach 1 review-monitor via ${primitive} (background), include ` +
|
|
10
|
+
"`templates/agent-prompt-preamble.md` and `templates/swarm-greptile-poller-prompt.md`, " +
|
|
11
|
+
"then register:\n" +
|
|
12
|
+
" task review-monitor:register -- --pr <N> --monitor-agent-id <id> " +
|
|
13
|
+
`--platform-primitive ${primitive}\n` +
|
|
14
|
+
"Re-run: task verify:review-monitor -- --pr <N>");
|
|
15
|
+
}
|
|
16
|
+
export function hasActivePollingHeartbeat(projectRoot, pr, options = {}) {
|
|
17
|
+
const dir = defaultSubagentStatusDir(projectRoot);
|
|
18
|
+
if (!existsSync(dir)) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
if (!statSync(dir).isDirectory()) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
const result = sweepScratchDirs([{ readPath: dir, label: dir }], {
|
|
30
|
+
thresholdMinutes: options.staleMinutes ?? 30,
|
|
31
|
+
now: options.now,
|
|
32
|
+
});
|
|
33
|
+
return result.records.some((rec) => rec.pr_number === pr &&
|
|
34
|
+
rec.failures.length === 0 &&
|
|
35
|
+
!rec.is_stale &&
|
|
36
|
+
!rec.is_terminal &&
|
|
37
|
+
(rec.phase === "polling" || rec.phase === "starting"));
|
|
38
|
+
}
|
|
39
|
+
export function evaluateReviewMonitorGate(args) {
|
|
40
|
+
const projectRoot = resolve(args.projectRoot);
|
|
41
|
+
let isDir = false;
|
|
42
|
+
try {
|
|
43
|
+
isDir = statSync(projectRoot).isDirectory();
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
isDir = false;
|
|
47
|
+
}
|
|
48
|
+
if (!isDir) {
|
|
49
|
+
return {
|
|
50
|
+
exitCode: EXIT_CONFIG_ERROR,
|
|
51
|
+
message: `verify_review_monitor: --project-root is not a directory: ${projectRoot}`,
|
|
52
|
+
tier: probeMonitoringTier(args.environ),
|
|
53
|
+
monitorRecord: null,
|
|
54
|
+
heartbeatActive: false,
|
|
55
|
+
callSite: args.callSite ?? "unspecified",
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const tier = probeMonitoringTier(args.environ);
|
|
59
|
+
const callSite = args.callSite ?? "unspecified";
|
|
60
|
+
const staleMinutes = args.staleMinutes ?? 30;
|
|
61
|
+
const now = args.now ?? new Date();
|
|
62
|
+
if (args.approach3 === true) {
|
|
63
|
+
if (isTier1(tier)) {
|
|
64
|
+
return {
|
|
65
|
+
exitCode: EXIT_NOT_READY,
|
|
66
|
+
message: "verify_review_monitor: Approach 3 blocking poll is forbidden when Tier 1 is available (#2655).\n" +
|
|
67
|
+
` Detected tier=${tier.tier} descriptor=${tier.descriptor ?? "unknown"} primitive=${tier.primitive ?? "none"}.\n` +
|
|
68
|
+
` ${spawnRedirect(tier)}`,
|
|
69
|
+
tier,
|
|
70
|
+
monitorRecord: null,
|
|
71
|
+
heartbeatActive: false,
|
|
72
|
+
callSite,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
if (tier.tier === MONITORING_TIER_3 && args.approach3Warned !== true) {
|
|
76
|
+
return {
|
|
77
|
+
exitCode: EXIT_NOT_READY,
|
|
78
|
+
message: "verify_review_monitor: Approach 3 requires explicit user warning acknowledgment (#2655).\n" +
|
|
79
|
+
" Warn the operator that the conversation pane will lock during polling, then re-run with --approach3-warned.",
|
|
80
|
+
tier,
|
|
81
|
+
monitorRecord: null,
|
|
82
|
+
heartbeatActive: false,
|
|
83
|
+
callSite,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
exitCode: EXIT_READY,
|
|
88
|
+
message: `verify_review_monitor: Tier 3 Approach 3 path allowed (call-site=${callSite}).`,
|
|
89
|
+
tier,
|
|
90
|
+
monitorRecord: null,
|
|
91
|
+
heartbeatActive: false,
|
|
92
|
+
callSite,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
if (!isTier1(tier)) {
|
|
96
|
+
return {
|
|
97
|
+
exitCode: EXIT_READY,
|
|
98
|
+
message: `verify_review_monitor: Tier ${tier.tier} (${tier.descriptor ?? "unknown"}) — ` +
|
|
99
|
+
"no active review-monitor required (#2655).",
|
|
100
|
+
tier,
|
|
101
|
+
monitorRecord: null,
|
|
102
|
+
heartbeatActive: false,
|
|
103
|
+
callSite,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const path = reviewMonitorPath(projectRoot);
|
|
107
|
+
const { data, error } = readReviewMonitorFile(path);
|
|
108
|
+
if (data === null) {
|
|
109
|
+
return {
|
|
110
|
+
exitCode: EXIT_CONFIG_ERROR,
|
|
111
|
+
message: `verify_review_monitor: ${error ?? "could not read review-monitor state"}`,
|
|
112
|
+
tier,
|
|
113
|
+
monitorRecord: null,
|
|
114
|
+
heartbeatActive: false,
|
|
115
|
+
callSite,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
const monitorRecord = findActiveMonitorForPr(data, args.pr, {
|
|
119
|
+
now,
|
|
120
|
+
staleMinutes,
|
|
121
|
+
headSha: args.headSha ?? null,
|
|
122
|
+
});
|
|
123
|
+
const heartbeatActive = hasActivePollingHeartbeat(projectRoot, args.pr, {
|
|
124
|
+
now,
|
|
125
|
+
staleMinutes,
|
|
126
|
+
});
|
|
127
|
+
if (monitorRecord !== null || heartbeatActive) {
|
|
128
|
+
const via = monitorRecord !== null ? "review-monitor record" : "subagent heartbeat";
|
|
129
|
+
return {
|
|
130
|
+
exitCode: EXIT_READY,
|
|
131
|
+
message: `verify_review_monitor: active review-monitor for PR #${args.pr} via ${via} ` +
|
|
132
|
+
`(call-site=${callSite}, tier=1, descriptor=${tier.descriptor ?? "unknown"}).`,
|
|
133
|
+
tier,
|
|
134
|
+
monitorRecord,
|
|
135
|
+
heartbeatActive,
|
|
136
|
+
callSite,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
const siteHint = callSite === "swarm-phase5-6"
|
|
140
|
+
? "Swarm Phase 5→6 handoff (#1386)"
|
|
141
|
+
: callSite === "swarm-phase6-cascade"
|
|
142
|
+
? "Swarm Phase 6 post force-push (#380)"
|
|
143
|
+
: "Solo drive-to merge-ready / review-cycle ownership";
|
|
144
|
+
return {
|
|
145
|
+
exitCode: EXIT_NOT_READY,
|
|
146
|
+
message: `verify_review_monitor: Tier 1 available but no active review-monitor for PR #${args.pr} (#2655).\n` +
|
|
147
|
+
` Call site: ${siteHint}.\n` +
|
|
148
|
+
` Detected descriptor=${tier.descriptor ?? "unknown"} primitive=${tier.primitive ?? "none"}.\n` +
|
|
149
|
+
` ${spawnRedirect(tier)}`,
|
|
150
|
+
tier,
|
|
151
|
+
monitorRecord: null,
|
|
152
|
+
heartbeatActive: false,
|
|
153
|
+
callSite,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
export function verifyResultToJson(result) {
|
|
157
|
+
return {
|
|
158
|
+
call_site: result.callSite,
|
|
159
|
+
exit_code: result.exitCode,
|
|
160
|
+
heartbeat_active: result.heartbeatActive,
|
|
161
|
+
message: result.message,
|
|
162
|
+
monitor_agent_id: result.monitorRecord?.monitor_agent_id ?? null,
|
|
163
|
+
monitor_record: result.monitorRecord,
|
|
164
|
+
ready: result.exitCode === EXIT_READY,
|
|
165
|
+
tier: result.tier.tier,
|
|
166
|
+
tier_descriptor: result.tier.descriptor,
|
|
167
|
+
tier_primitive: result.tier.primitive,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
export { EXIT_CONFIG_ERROR, EXIT_NOT_READY, EXIT_READY, MONITORING_TIER_1, MONITORING_TIER_3 };
|
|
171
|
+
//# sourceMappingURL=verify.js.map
|
package/dist/scope/demote.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, statSync, writeFileSync, } from "node:fs";
|
|
2
2
|
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
3
|
+
import { assertWriteTargetSafe, ProjectionContainmentError } from "../fs/projection-containment.js";
|
|
3
4
|
import { hasArtifactSuffix, resolveLifecycleFolder } from "../layout/resolve.js";
|
|
4
5
|
import { stripTrailingPathSeparators } from "../text/redos-safe.js";
|
|
5
6
|
import { append, canonicalLogPath, latestForPath, newDecisionId } from "./audit-log.js";
|
|
@@ -51,6 +52,15 @@ export function demoteOne(filePath, projectRoot, reason, options = {}) {
|
|
|
51
52
|
auditEntry: null,
|
|
52
53
|
};
|
|
53
54
|
}
|
|
55
|
+
try {
|
|
56
|
+
assertWriteTargetSafe(projectRoot, resolved);
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
if (err instanceof ProjectionContainmentError) {
|
|
60
|
+
return { ok: false, message: err.message, auditEntry: null };
|
|
61
|
+
}
|
|
62
|
+
throw err;
|
|
63
|
+
}
|
|
54
64
|
let data;
|
|
55
65
|
try {
|
|
56
66
|
data = JSON.parse(readFileSync(resolved, "utf8"));
|
|
@@ -129,6 +139,16 @@ export function batchDemote(projectRoot, olderThanDays, options = {}) {
|
|
|
129
139
|
.sort();
|
|
130
140
|
for (const name of files) {
|
|
131
141
|
const candidate = join(pendingDir, name);
|
|
142
|
+
try {
|
|
143
|
+
assertWriteTargetSafe(projectRoot, candidate);
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
if (err instanceof ProjectionContainmentError) {
|
|
147
|
+
skipped.push(`${name}: ${err.message}`);
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
throw err;
|
|
151
|
+
}
|
|
132
152
|
let data;
|
|
133
153
|
try {
|
|
134
154
|
data = JSON.parse(readFileSync(candidate, "utf8"));
|
|
@@ -18,6 +18,9 @@ export declare class DuplicateStoryError extends WorktreeMapError {
|
|
|
18
18
|
export declare class WorktreeMapConfigError extends Error {
|
|
19
19
|
name: string;
|
|
20
20
|
}
|
|
21
|
+
export declare class WorktreePathEscapeError extends WorktreeMapError {
|
|
22
|
+
name: string;
|
|
23
|
+
}
|
|
21
24
|
export type GitRunner = (args: readonly string[], cwd: string) => TextCaptureResult;
|
|
22
25
|
export declare const defaultGitRunner: GitRunner;
|
|
23
26
|
/** Case-normalized comparison key for worktree-path equality. */
|
package/dist/swarm/worktrees.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { mkdirSync, readFileSync } from "node:fs";
|
|
2
|
-
import { resolve as pathResolve } from "node:path";
|
|
2
|
+
import { isAbsolute, resolve as pathResolve } from "node:path";
|
|
3
|
+
import { assertProjectionContained, ProjectionContainmentError, } from "../fs/projection-containment.js";
|
|
3
4
|
import { C3_FIELDS } from "./constants.js";
|
|
4
5
|
import { runText } from "./subprocess.js";
|
|
5
6
|
export class WorktreeMapError extends Error {
|
|
@@ -20,11 +21,25 @@ export class DuplicateStoryError extends WorktreeMapError {
|
|
|
20
21
|
export class WorktreeMapConfigError extends Error {
|
|
21
22
|
name = "WorktreeMapConfigError";
|
|
22
23
|
}
|
|
24
|
+
export class WorktreePathEscapeError extends WorktreeMapError {
|
|
25
|
+
name = "WorktreePathEscapeError";
|
|
26
|
+
}
|
|
23
27
|
export const defaultGitRunner = (args, cwd) => runText(["git", ...args], { cwd });
|
|
24
28
|
function resolvePath(raw, repoRoot) {
|
|
25
|
-
const candidate = raw
|
|
29
|
+
const candidate = isAbsolute(raw) ? raw : pathResolve(repoRoot, raw);
|
|
26
30
|
return pathResolve(candidate);
|
|
27
31
|
}
|
|
32
|
+
function assertWorktreePathContained(repoRoot, worktreePath) {
|
|
33
|
+
try {
|
|
34
|
+
assertProjectionContained(repoRoot, worktreePath);
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
if (err instanceof ProjectionContainmentError) {
|
|
38
|
+
throw new WorktreePathEscapeError(`worktree_path must stay under the repository root: ${err.message}`);
|
|
39
|
+
}
|
|
40
|
+
throw err;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
28
43
|
/** Case-normalized comparison key for worktree-path equality. */
|
|
29
44
|
export function compareKey(pathStr) {
|
|
30
45
|
return pathStr.replace(/\\/g, "/").toLowerCase();
|
|
@@ -119,6 +134,7 @@ export function resolveWorktreeMap(mapping, baseBranch, createMissing = true, op
|
|
|
119
134
|
}
|
|
120
135
|
}
|
|
121
136
|
const worktreePath = resolvePath(rawPath.trim(), root);
|
|
137
|
+
assertWorktreePathContained(root, worktreePath);
|
|
122
138
|
const key = compareKey(worktreePath);
|
|
123
139
|
const posixPath = worktreePath.replace(/\\/g, "/");
|
|
124
140
|
if (seenPaths.has(key)) {
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
|
+
import { LEGACY_VBRIEF_VERSION } from "@deftai/directive-types";
|
|
4
5
|
const UNRELEASED_RE = /## \[Unreleased\][ \t]*\n([\s\S]*?)(?=\n## \[|$)/;
|
|
5
6
|
const CHANGE_NAME_RE = /^[\w][\w-]*$/;
|
|
6
7
|
const COMMIT_TYPES = "feat|fix|docs|chore|refactor|test|style|perf|ci|build|revert";
|
|
7
8
|
const COMMIT_SUBJECT_RE = new RegExp(`^(${COMMIT_TYPES})(\\(.+\\))?!?: .+`);
|
|
8
9
|
function proposalTemplate(name) {
|
|
9
10
|
return {
|
|
10
|
-
vBRIEFInfo: { version:
|
|
11
|
+
vBRIEFInfo: { version: LEGACY_VBRIEF_VERSION },
|
|
11
12
|
plan: {
|
|
12
13
|
title: name,
|
|
13
14
|
status: "draft",
|
|
@@ -26,7 +27,7 @@ function proposalTemplate(name) {
|
|
|
26
27
|
}
|
|
27
28
|
function tasksTemplate(name) {
|
|
28
29
|
return {
|
|
29
|
-
vBRIEFInfo: { version:
|
|
30
|
+
vBRIEFInfo: { version: LEGACY_VBRIEF_VERSION },
|
|
30
31
|
plan: { title: name, status: "draft", items: [], edges: [] },
|
|
31
32
|
};
|
|
32
33
|
}
|
|
@@ -1,12 +1,20 @@
|
|
|
1
|
+
/** Matches Vitest v8 chunk paths such as coverage/.tmp/coverage-0.json (#2580 / #2634). */
|
|
2
|
+
export declare const COVERAGE_TMP_CHUNK_RE: RegExp;
|
|
3
|
+
export declare function isCoverageTmpChunkPath(filePath: string): boolean;
|
|
4
|
+
export declare function ensureCoverageTmpDir(coverageTmpDir?: string): void;
|
|
1
5
|
/**
|
|
2
|
-
*
|
|
6
|
+
* Vitest 3.2.x writes coverage chunks without mkdir'ing the parent directory
|
|
7
|
+
* (fixed upstream in vitest 4.x — vitest-dev/vitest#10117). On Windows the
|
|
8
|
+
* directory can disappear between clean() and writeFile under release-scale
|
|
9
|
+
* load; mkdir immediately before chunk writes closes that race without
|
|
10
|
+
* soft-failing real threshold failures (#2634).
|
|
11
|
+
*/
|
|
12
|
+
export declare function installCoverageTmpWriteGuard(): () => void;
|
|
13
|
+
/**
|
|
14
|
+
* Win32 globalSetup for coverage runs (#2580, hardened #2634).
|
|
3
15
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* disappear mid-suite, surfacing as ENOENT after an otherwise green run.
|
|
7
|
-
* Keep the directory present for the coordinator process; late ENOENT flakes
|
|
8
|
-
* are tolerated via vitest dangerouslyIgnoreUnhandledErrors (#2546) without
|
|
9
|
-
* soft-failing real coverage threshold failures.
|
|
16
|
+
* Keepalive mkdir plus a writeFile guard mirror vitest 4's defensive mkdir until
|
|
17
|
+
* directive upgrades past vitest@3 (see vitest.config.ts #2634 upgrade note).
|
|
10
18
|
*/
|
|
11
|
-
export default function setup(): void;
|
|
19
|
+
export default function setup(): () => void;
|
|
12
20
|
//# sourceMappingURL=win32-coverage-tmp-setup.d.ts.map
|
|
@@ -1,24 +1,51 @@
|
|
|
1
|
-
import { mkdirSync } from "node:fs";
|
|
2
|
-
import { resolve } from "node:path";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import { promises as fsPromises, mkdirSync } from "node:fs";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
/** Matches Vitest v8 chunk paths such as coverage/.tmp/coverage-0.json (#2580 / #2634). */
|
|
4
|
+
export const COVERAGE_TMP_CHUNK_RE = /[/\\]coverage[/\\]\.tmp[/\\]coverage-\d+\.json$/;
|
|
5
|
+
const defaultCoverageTmp = resolve(process.cwd(), "coverage", ".tmp");
|
|
6
|
+
export function isCoverageTmpChunkPath(filePath) {
|
|
7
|
+
return COVERAGE_TMP_CHUNK_RE.test(filePath.replace(/\\/g, "/"));
|
|
8
|
+
}
|
|
9
|
+
export function ensureCoverageTmpDir(coverageTmpDir = defaultCoverageTmp) {
|
|
10
|
+
mkdirSync(coverageTmpDir, { recursive: true });
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Vitest 3.2.x writes coverage chunks without mkdir'ing the parent directory
|
|
14
|
+
* (fixed upstream in vitest 4.x — vitest-dev/vitest#10117). On Windows the
|
|
15
|
+
* directory can disappear between clean() and writeFile under release-scale
|
|
16
|
+
* load; mkdir immediately before chunk writes closes that race without
|
|
17
|
+
* soft-failing real threshold failures (#2634).
|
|
18
|
+
*/
|
|
19
|
+
export function installCoverageTmpWriteGuard() {
|
|
20
|
+
const originalWriteFile = fsPromises.writeFile.bind(fsPromises);
|
|
21
|
+
const patchedWriteFile = async (path, ...args) => {
|
|
22
|
+
const target = typeof path === "string" ? path : String(path);
|
|
23
|
+
if (isCoverageTmpChunkPath(target)) {
|
|
24
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
25
|
+
}
|
|
26
|
+
return originalWriteFile(path, ...args);
|
|
27
|
+
};
|
|
28
|
+
fsPromises.writeFile = patchedWriteFile;
|
|
29
|
+
return () => {
|
|
30
|
+
fsPromises.writeFile = originalWriteFile;
|
|
31
|
+
};
|
|
6
32
|
}
|
|
7
33
|
/**
|
|
8
|
-
* Win32 globalSetup for coverage runs (#2580).
|
|
34
|
+
* Win32 globalSetup for coverage runs (#2580, hardened #2634).
|
|
9
35
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* disappear mid-suite, surfacing as ENOENT after an otherwise green run.
|
|
13
|
-
* Keep the directory present for the coordinator process; late ENOENT flakes
|
|
14
|
-
* are tolerated via vitest dangerouslyIgnoreUnhandledErrors (#2546) without
|
|
15
|
-
* soft-failing real coverage threshold failures.
|
|
36
|
+
* Keepalive mkdir plus a writeFile guard mirror vitest 4's defensive mkdir until
|
|
37
|
+
* directive upgrades past vitest@3 (see vitest.config.ts #2634 upgrade note).
|
|
16
38
|
*/
|
|
17
39
|
export default function setup() {
|
|
18
40
|
if (process.platform !== "win32")
|
|
19
|
-
return;
|
|
41
|
+
return () => { };
|
|
20
42
|
ensureCoverageTmpDir();
|
|
21
|
-
const
|
|
43
|
+
const uninstallWriteGuard = installCoverageTmpWriteGuard();
|
|
44
|
+
const keepalive = setInterval(() => ensureCoverageTmpDir(), 50);
|
|
22
45
|
keepalive.unref?.();
|
|
46
|
+
return () => {
|
|
47
|
+
clearInterval(keepalive);
|
|
48
|
+
uninstallWriteGuard();
|
|
49
|
+
};
|
|
23
50
|
}
|
|
24
51
|
//# sourceMappingURL=win32-coverage-tmp-setup.js.map
|