@deftai/directive-core 0.79.2 → 0.79.4

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 (64) hide show
  1. package/dist/check/orchestrator.d.ts +4 -0
  2. package/dist/check/orchestrator.js +9 -1
  3. package/dist/coverage-hotspots/evaluate.d.ts +49 -0
  4. package/dist/coverage-hotspots/evaluate.js +262 -0
  5. package/dist/coverage-hotspots/index.d.ts +3 -0
  6. package/dist/coverage-hotspots/index.js +3 -0
  7. package/dist/coverage-hotspots/thresholds.d.ts +5 -0
  8. package/dist/coverage-hotspots/thresholds.js +48 -0
  9. package/dist/hooks/dispatcher.d.ts +7 -0
  10. package/dist/hooks/dispatcher.js +44 -4
  11. package/dist/index.d.ts +1 -0
  12. package/dist/index.js +1 -0
  13. package/dist/init-deposit/xbrief-projections.d.ts +7 -0
  14. package/dist/init-deposit/xbrief-projections.js +29 -1
  15. package/dist/integration-e2e/bootstrap-cache-module.d.ts +2 -4
  16. package/dist/integration-e2e/bootstrap-cache-module.js +2 -20
  17. package/dist/platform/resolve-version.d.ts +2 -6
  18. package/dist/platform/resolve-version.js +3 -129
  19. package/dist/pr-merge-readiness/gh.js +13 -5
  20. package/dist/pr-merge-readiness/test-gh-fixtures.helpers.d.ts +18 -0
  21. package/dist/pr-merge-readiness/test-gh-fixtures.helpers.js +76 -0
  22. package/dist/pr-monitor/monitor.js +10 -1
  23. package/dist/pr-wait-mergeable/cascade.js +8 -1
  24. package/dist/pr-wait-mergeable/classify.js +5 -1
  25. package/dist/pr-wait-mergeable/wrappers.js +15 -4
  26. package/dist/pr-watch/constants.d.ts +15 -0
  27. package/dist/pr-watch/constants.js +37 -0
  28. package/dist/pr-watch/index.d.ts +1 -1
  29. package/dist/pr-watch/index.js +1 -1
  30. package/dist/pr-watch/main.d.ts +3 -0
  31. package/dist/pr-watch/main.js +33 -3
  32. package/dist/pr-watch/probe.js +5 -1
  33. package/dist/pr-watch/types.d.ts +2 -0
  34. package/dist/pr-watch/watch.js +17 -1
  35. package/dist/release/constants.d.ts +5 -0
  36. package/dist/release/constants.js +14 -2
  37. package/dist/release/flags.js +16 -0
  38. package/dist/release/main.js +7 -0
  39. package/dist/release/pipeline.js +7 -3
  40. package/dist/release/preflight.js +8 -1
  41. package/dist/release/skip-ci-incident.d.ts +22 -0
  42. package/dist/release/skip-ci-incident.js +68 -0
  43. package/dist/release/types.d.ts +2 -0
  44. package/dist/release/version.js +68 -32
  45. package/dist/release-e2e/entrypoint-worker.js +1 -0
  46. package/dist/release-e2e/entrypoint.js +13 -0
  47. package/dist/review-monitor/constants.d.ts +14 -0
  48. package/dist/review-monitor/constants.js +55 -0
  49. package/dist/review-monitor/index.d.ts +5 -0
  50. package/dist/review-monitor/index.js +5 -0
  51. package/dist/review-monitor/record.d.ts +50 -0
  52. package/dist/review-monitor/record.js +178 -0
  53. package/dist/review-monitor/tier-detection.d.ts +15 -0
  54. package/dist/review-monitor/tier-detection.js +61 -0
  55. package/dist/review-monitor/verify.d.ts +32 -0
  56. package/dist/review-monitor/verify.js +171 -0
  57. package/dist/scope/undo.js +12 -2
  58. package/dist/triage/bootstrap/cache-module.d.ts +25 -0
  59. package/dist/triage/bootstrap/cache-module.js +39 -0
  60. package/dist/triage/bootstrap/index.d.ts +1 -0
  61. package/dist/triage/bootstrap/index.js +7 -82
  62. package/dist/xbrief-migrate/agents-header.d.ts +0 -11
  63. package/dist/xbrief-migrate/agents-header.js +10 -1
  64. package/package.json +7 -3
@@ -0,0 +1,50 @@
1
+ import type { PlatformPrimitive } from "./tier-detection.js";
2
+ export interface ReviewMonitorRecord {
3
+ readonly pr: number;
4
+ readonly repo: string | null;
5
+ readonly head_sha: string | null;
6
+ readonly platform_primitive: PlatformPrimitive;
7
+ readonly monitor_agent_id: string;
8
+ readonly started_at: string;
9
+ readonly worktree_path: string;
10
+ readonly parent_session_id: string | null;
11
+ readonly ended_at: string | null;
12
+ }
13
+ export interface ReviewMonitorFile {
14
+ readonly schema_version: number;
15
+ readonly records: ReviewMonitorRecord[];
16
+ }
17
+ export declare function reviewMonitorPath(projectRoot: string): string;
18
+ export declare function emptyReviewMonitorFile(): ReviewMonitorFile;
19
+ export declare function readReviewMonitorFile(path: string): {
20
+ data: ReviewMonitorFile | null;
21
+ error: string | null;
22
+ };
23
+ export declare function writeReviewMonitorFile(path: string, data: ReviewMonitorFile): void;
24
+ export interface RegisterReviewMonitorInput {
25
+ readonly pr: number;
26
+ readonly repo?: string | null;
27
+ readonly headSha?: string | null;
28
+ readonly platformPrimitive: PlatformPrimitive;
29
+ readonly monitorAgentId: string;
30
+ readonly projectRoot: string;
31
+ readonly parentSessionId?: string | null;
32
+ readonly startedAt?: Date;
33
+ }
34
+ export declare function registerReviewMonitor(input: RegisterReviewMonitorInput): {
35
+ path: string;
36
+ record: ReviewMonitorRecord;
37
+ };
38
+ export declare function parseIso8601Utc(value: string): Date | null;
39
+ export declare function isRecordActive(record: ReviewMonitorRecord, options: {
40
+ now?: Date;
41
+ staleMinutes?: number;
42
+ headSha?: string | null;
43
+ }): boolean;
44
+ export declare function findActiveMonitorForPr(file: ReviewMonitorFile, pr: number, options: {
45
+ now?: Date;
46
+ staleMinutes?: number;
47
+ headSha?: string | null;
48
+ }): ReviewMonitorRecord | null;
49
+ export declare function defaultSubagentStatusDir(projectRoot: string): string;
50
+ //# sourceMappingURL=record.d.ts.map
@@ -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
@@ -1,5 +1,6 @@
1
1
  import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
2
2
  import { basename, join, relative, resolve } from "node:path";
3
+ import { assertWriteTargetSafe, ProjectionContainmentError } from "../fs/projection-containment.js";
3
4
  import { resolveLifecycleRoot } from "../layout/resolve.js";
4
5
  import { append, canonicalLogPath, newDecisionId, readAll } from "./audit-log.js";
5
6
  import { REVERSIBLE_ACTIONS, TERMINAL_ACTIONS } from "./constants.js";
@@ -157,7 +158,7 @@ function inversePlan(entry, logEntries) {
157
158
  }
158
159
  return null;
159
160
  }
160
- function moveAndFlip(srcFile, destFolder, newStatus, timestamp) {
161
+ function moveAndFlip(srcFile, destFolder, newStatus, timestamp, projectRoot) {
161
162
  if (!existsSync(srcFile)) {
162
163
  return [false, `File not found: ${srcFile}`, null];
163
164
  }
@@ -172,6 +173,15 @@ function moveAndFlip(srcFile, destFolder, newStatus, timestamp) {
172
173
  if (typeof plan !== "object" || plan === null || Array.isArray(plan)) {
173
174
  return [false, `Missing or invalid 'plan' object in ${srcFile}`, null];
174
175
  }
176
+ try {
177
+ assertWriteTargetSafe(projectRoot, srcFile);
178
+ }
179
+ catch (err) {
180
+ if (err instanceof ProjectionContainmentError) {
181
+ return [false, err.message, null];
182
+ }
183
+ throw err;
184
+ }
175
185
  const planObj = plan;
176
186
  planObj.status = newStatus;
177
187
  planObj.updated = timestamp;
@@ -262,7 +272,7 @@ export function undoOne(entry, projectRoot, options = {}) {
262
272
  });
263
273
  return { ok: true, message: msg, auditEntry: preview };
264
274
  }
265
- const [ok, fsMsg, destPath] = moveAndFlip(srcPath, destFolderPath, plan.newStatus, timestamp);
275
+ const [ok, fsMsg, destPath] = moveAndFlip(srcPath, destFolderPath, plan.newStatus, timestamp, projectRoot);
266
276
  if (!ok || destPath === null) {
267
277
  return { ok: false, message: fsMsg, auditEntry: null };
268
278
  }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * TypeScript default cache module for triage:bootstrap (#2684).
3
+ *
4
+ * npm / packaged consumers never ship scripts/cache.py; bootstrap must use the
5
+ * same cacheFetchAll path as empty-cache auto-populate (#2575).
6
+ */
7
+ import { type FetchAllReportImpl } from "../../cache/fetch.js";
8
+ import type { CacheModule } from "./types.js";
9
+ export type CacheFetchAllFn = (options: {
10
+ source: string;
11
+ repo: string;
12
+ batchSize?: number;
13
+ delayMs?: number;
14
+ state?: string;
15
+ limit?: number;
16
+ labels?: readonly string[];
17
+ author?: string | null;
18
+ cacheRoot?: string;
19
+ force?: boolean;
20
+ }) => FetchAllReportImpl;
21
+ /** Adapt TS cache reports to the bootstrap `FetchAllReport` shape. */
22
+ export declare function bootstrapCacheModule(fetchAll?: CacheFetchAllFn): CacheModule;
23
+ /** Default module for packaged and source checkouts — never gates on Python. */
24
+ export declare function loadDefaultCacheModule(): CacheModule;
25
+ //# sourceMappingURL=cache-module.d.ts.map
@@ -0,0 +1,39 @@
1
+ /**
2
+ * TypeScript default cache module for triage:bootstrap (#2684).
3
+ *
4
+ * npm / packaged consumers never ship scripts/cache.py; bootstrap must use the
5
+ * same cacheFetchAll path as empty-cache auto-populate (#2575).
6
+ */
7
+ import { cacheFetchAll } from "../../cache/fetch.js";
8
+ /** Adapt TS cache reports to the bootstrap `FetchAllReport` shape. */
9
+ export function bootstrapCacheModule(fetchAll = cacheFetchAll) {
10
+ return {
11
+ cacheFetchAll(kwargs) {
12
+ const report = fetchAll({
13
+ source: kwargs.source,
14
+ repo: kwargs.repo,
15
+ cacheRoot: kwargs.cacheRoot,
16
+ force: true,
17
+ ...(kwargs.batchSize !== undefined ? { batchSize: kwargs.batchSize } : {}),
18
+ ...(kwargs.delayMs !== undefined ? { delayMs: kwargs.delayMs } : {}),
19
+ ...(kwargs.state !== undefined ? { state: kwargs.state } : {}),
20
+ ...(kwargs.limit !== undefined ? { limit: kwargs.limit } : {}),
21
+ ...(kwargs.labels !== undefined && kwargs.labels.length > 0
22
+ ? { labels: kwargs.labels }
23
+ : {}),
24
+ ...(kwargs.author !== undefined ? { author: kwargs.author } : {}),
25
+ });
26
+ return Promise.resolve({
27
+ succeeded: report.issuesWritten,
28
+ failed: report.issuesFailed,
29
+ skipped: report.alreadyFresh,
30
+ summaryLine: (source, repo) => report.summaryLine(source, repo),
31
+ });
32
+ },
33
+ };
34
+ }
35
+ /** Default module for packaged and source checkouts — never gates on Python. */
36
+ export function loadDefaultCacheModule() {
37
+ return bootstrapCacheModule();
38
+ }
39
+ //# sourceMappingURL=cache-module.js.map
@@ -1,4 +1,5 @@
1
1
  import type { BootstrapResult, CacheModule, RunBootstrapOptions, StepOutcome } from "./types.js";
2
+ export * from "./cache-module.js";
2
3
  export * from "./gitignore.js";
3
4
  export * from "./types.js";
4
5
  export declare const CACHE_DIR_NAME = ".deft-cache";