@deftai/directive-core 0.105.0 → 0.107.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/authz/classify.js +591 -34
- package/dist/check/gate-lists.js +2 -0
- package/dist/check/named-cause.js +1 -1
- package/dist/design-critique/exclusive-chip.d.ts +33 -0
- package/dist/design-critique/exclusive-chip.js +68 -0
- package/dist/design-critique/parent-audit.d.ts +59 -0
- package/dist/design-critique/parent-audit.js +121 -0
- package/dist/doctor/checks.d.ts +6 -0
- package/dist/doctor/checks.js +35 -0
- package/dist/forward-coverage/diff-coverage.d.ts +39 -0
- package/dist/forward-coverage/diff-coverage.js +112 -0
- package/dist/forward-coverage/diff-lines.d.ts +19 -0
- package/dist/forward-coverage/diff-lines.js +173 -0
- package/dist/forward-coverage/evaluate.d.ts +24 -9
- package/dist/forward-coverage/evaluate.js +125 -19
- package/dist/hooks/dest-form.d.ts +46 -0
- package/dist/hooks/dest-form.js +575 -0
- package/dist/hooks/dispatcher.d.ts +17 -1
- package/dist/hooks/dispatcher.js +82 -11
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.js +1 -0
- package/dist/init-deposit/agent-hooks.d.ts +1 -1
- package/dist/init-deposit/agent-hooks.js +1 -1
- package/dist/intake/clause-derivation.d.ts +9 -0
- package/dist/intake/clause-derivation.js +30 -3
- package/dist/intake/github-auth-modes-cli.js +14 -2
- package/dist/intake/github-auth-modes.d.ts +54 -9
- package/dist/intake/github-auth-modes.js +326 -123
- package/dist/lifecycle/completed-tracked-on-delivery.d.ts +26 -0
- package/dist/lifecycle/completed-tracked-on-delivery.js +37 -14
- package/dist/lifecycle/completed-write-guard.d.ts +48 -0
- package/dist/lifecycle/completed-write-guard.js +373 -0
- package/dist/lifecycle/index.d.ts +1 -0
- package/dist/lifecycle/index.js +1 -0
- package/dist/lifecycle-visible/evaluate.d.ts +98 -0
- package/dist/lifecycle-visible/evaluate.js +710 -0
- package/dist/lifecycle-visible/index.d.ts +2 -0
- package/dist/lifecycle-visible/index.js +2 -0
- package/dist/literal-acceptance/capture.d.ts +16 -0
- package/dist/literal-acceptance/capture.js +78 -10
- package/dist/literal-acceptance/evaluate.js +25 -3
- package/dist/literal-acceptance/index.d.ts +2 -2
- package/dist/literal-acceptance/index.js +2 -2
- package/dist/literal-acceptance/run.js +9 -3
- package/dist/literal-acceptance/types.d.ts +6 -0
- package/dist/literal-acceptance/types.js +6 -0
- package/dist/orchestration/judgment-policy.d.ts +13 -0
- package/dist/orchestration/judgment-policy.js +15 -0
- package/dist/orchestration/probe-session.js +13 -8
- package/dist/orchestration/verify-judgment-gates.d.ts +4 -0
- package/dist/orchestration/verify-judgment-gates.js +43 -11
- package/dist/policy/ceremony-dial.js +7 -7
- package/dist/policy/host-hooks.d.ts +20 -0
- package/dist/policy/host-hooks.js +121 -2
- package/dist/policy/org-force-on-migration.js +2 -1
- package/dist/policy/plan-extensions.d.ts +1 -1
- package/dist/policy/plan-extensions.js +2 -0
- package/dist/policy/product-signal.js +6 -7
- package/dist/policy/require-human-merge.js +8 -6
- package/dist/policy/resolve.d.ts +7 -2
- package/dist/policy/resolve.js +25 -8
- package/dist/policy/runtime-authority.d.ts +18 -0
- package/dist/policy/runtime-authority.js +11 -0
- package/dist/policy/value-feedback.js +7 -8
- package/dist/policy/write-fence.js +3 -0
- package/dist/preflight-cache/evaluate.d.ts +12 -0
- package/dist/preflight-cache/evaluate.js +24 -4
- package/dist/preflight-cache/index.d.ts +1 -1
- package/dist/preflight-cache/index.js +1 -1
- package/dist/product-first-done-gate/evaluate.d.ts +4 -0
- package/dist/product-first-done-gate/evaluate.js +82 -10
- package/dist/release/closed-verb-gate.d.ts +12 -0
- package/dist/release/closed-verb-gate.js +51 -0
- package/dist/release/index.d.ts +1 -0
- package/dist/release/index.js +1 -0
- package/dist/release/pipeline.js +7 -0
- package/dist/release/types.d.ts +12 -0
- package/dist/render/constants.d.ts +2 -1
- package/dist/render/constants.js +2 -1
- package/dist/render/framework-commands.js +4 -0
- package/dist/render/spec-validate.js +23 -12
- package/dist/run-summary/types.d.ts +4 -0
- package/dist/scm/design-critique-chip.d.ts +37 -0
- package/dist/scm/design-critique-chip.js +171 -0
- package/dist/scm/index.d.ts +1 -0
- package/dist/scm/index.js +1 -0
- package/dist/scm/main.d.ts +3 -0
- package/dist/scm/main.js +15 -1
- package/dist/scm/readiness-cli.d.ts +2 -0
- package/dist/scm/readiness-cli.js +42 -1
- package/dist/scm/readiness.d.ts +2 -1
- package/dist/scm/readiness.js +7 -1
- package/dist/scope/decompose.js +2 -1
- package/dist/scope/delivery-evidence.d.ts +27 -1
- package/dist/scope/index.d.ts +1 -0
- package/dist/scope/index.js +1 -0
- package/dist/scope/lifecycle-write.d.ts +24 -0
- package/dist/scope/lifecycle-write.js +64 -0
- package/dist/scope/main.js +18 -6
- package/dist/scope/promote-from-issue.js +5 -5
- package/dist/scope/transition.js +90 -11
- package/dist/scope-provenance/evaluate.js +8 -4
- package/dist/session/ac-pass-banking.d.ts +5 -0
- package/dist/session/ac-pass-banking.js +3 -2
- package/dist/session/git.d.ts +18 -0
- package/dist/session/git.js +81 -1
- package/dist/session/occupancy.d.ts +2 -1
- package/dist/session/occupancy.js +11 -4
- package/dist/session/process-cost.d.ts +32 -10
- package/dist/session/process-cost.js +189 -1
- package/dist/session/product-state-hash.js +41 -24
- package/dist/session/ritual-entrypoint.js +16 -1
- package/dist/session/session-start.d.ts +18 -0
- package/dist/session/session-start.js +49 -2
- package/dist/session/verify-session-ritual.d.ts +46 -4
- package/dist/session/verify-session-ritual.js +118 -10
- package/dist/swarm/finalize-cohort-cli.d.ts +7 -2
- package/dist/swarm/finalize-cohort-cli.js +165 -35
- package/dist/swarm/finalize-cohort.d.ts +1 -0
- package/dist/swarm/finalize-cohort.js +14 -2
- package/dist/swarm/launch.d.ts +69 -0
- package/dist/swarm/launch.js +191 -1
- package/dist/swarm/routing-set-cli.js +2 -0
- package/dist/swarm/routing-verify.d.ts +1 -1
- package/dist/swarm/routing-verify.js +2 -2
- package/dist/triage/actions/index.d.ts +4 -0
- package/dist/triage/actions/index.js +6 -0
- package/dist/triage/bootstrap/index.js +6 -6
- package/dist/triage/evaluate/evaluate.d.ts +11 -0
- package/dist/triage/evaluate/evaluate.js +169 -0
- package/dist/triage/evaluate/github.d.ts +16 -0
- package/dist/triage/evaluate/github.js +153 -0
- package/dist/triage/evaluate/index.d.ts +9 -0
- package/dist/triage/evaluate/index.js +8 -0
- package/dist/triage/evaluate/paths.d.ts +7 -0
- package/dist/triage/evaluate/paths.js +25 -0
- package/dist/triage/evaluate/sink.d.ts +5 -0
- package/dist/triage/evaluate/sink.js +52 -0
- package/dist/triage/evaluate/types.d.ts +93 -0
- package/dist/triage/evaluate/types.js +13 -0
- package/dist/triage/evaluate/validity.d.ts +8 -0
- package/dist/triage/evaluate/validity.js +87 -0
- package/dist/triage/evaluate/value.d.ts +12 -0
- package/dist/triage/evaluate/value.js +33 -0
- package/dist/triage/evaluate/wip-census.d.ts +5 -0
- package/dist/triage/evaluate/wip-census.js +39 -0
- package/dist/triage/evaluate/worktrees.d.ts +8 -0
- package/dist/triage/evaluate/worktrees.js +68 -0
- package/dist/triage/evaluate/xbrief-refs.d.ts +8 -0
- package/dist/triage/evaluate/xbrief-refs.js +60 -0
- package/dist/triage/help/registry-data.d.ts +22 -8
- package/dist/triage/help/registry-data.js +54 -6
- package/dist/triage/index.d.ts +1 -0
- package/dist/triage/index.js +1 -0
- package/dist/triage/welcome/writers.d.ts +1 -1
- package/dist/triage/welcome/writers.js +10 -5
- package/dist/value/feedback-file.d.ts +17 -2
- package/dist/value/feedback-file.js +80 -6
- package/dist/value/readback.d.ts +4 -1
- package/dist/value/readback.js +51 -12
- package/dist/vbrief-reconcile/labels.d.ts +1 -0
- package/dist/vbrief-reconcile/labels.js +30 -0
- package/dist/vbrief-validate/schema.d.ts +4 -0
- package/dist/vbrief-validate/schema.js +1 -1
- package/dist/vbrief-validation/story-quality.d.ts +7 -0
- package/dist/vbrief-validation/story-quality.js +8 -1
- package/dist/verify-env/agent-hook-readiness.d.ts +2 -2
- package/dist/verify-env/agent-hook-readiness.js +12 -8
- package/dist/verify-env/agent-hooks-live-probe.d.ts +5 -1
- package/dist/verify-env/agent-hooks-live-probe.js +26 -5
- package/dist/verify-env/agent-hooks.js +3 -4
- package/dist/xbrief-migrate/agents-header.js +69 -7
- package/package.json +7 -3
|
@@ -17,6 +17,7 @@ import { containedWrite } from "../fs/contained-write.js";
|
|
|
17
17
|
import { DEFAULT_SURPLUS_THRESHOLD, resolveAcPassBanking, } from "../policy/ac-pass-banking.js";
|
|
18
18
|
import { RunSummaryEmitter } from "../run-summary/emit.js";
|
|
19
19
|
import { detectHardEffortBudget, formatDeepeningSkippedNote, recommendVerificationDepth, } from "./effort-budget.js";
|
|
20
|
+
import { gitHead } from "./git.js";
|
|
20
21
|
/** Durable bank ledger directory (gitignored under `.deft/`). */
|
|
21
22
|
export const AC_PASS_BANK_DIR = ".deft/ac-pass-banks";
|
|
22
23
|
export const AC_PASS_BANK_SCHEMA_VERSION = 1;
|
|
@@ -682,7 +683,7 @@ export function resolveBankingConfigForRoot(projectRoot, environ) {
|
|
|
682
683
|
* Throws on I/O failure so verify:ac can fail closed (checkpoint mandatory).
|
|
683
684
|
*/
|
|
684
685
|
export function maybeBankOnAcPass(input) {
|
|
685
|
-
if (input.executableRuns <= 0) {
|
|
686
|
+
if (input.executableRuns <= 0 && input.verifiedPass !== true) {
|
|
686
687
|
return { banked: false, decision: null, bank: null, notes: [] };
|
|
687
688
|
}
|
|
688
689
|
const budget = input.budget ?? detectHardEffortBudget({ environ: input.environ ?? process.env });
|
|
@@ -699,7 +700,7 @@ export function maybeBankOnAcPass(input) {
|
|
|
699
700
|
budget,
|
|
700
701
|
surplus: decision.surplus,
|
|
701
702
|
nextAction,
|
|
702
|
-
headSha: input.headSha
|
|
703
|
+
headSha: input.headSha !== undefined ? input.headSha : gitHead(input.projectRoot).head,
|
|
703
704
|
productStateHash: input.productStateHash ?? null,
|
|
704
705
|
now: input.now,
|
|
705
706
|
environ: input.environ,
|
package/dist/session/git.d.ts
CHANGED
|
@@ -12,5 +12,23 @@ export declare function gitHead(projectRoot: string, runGit?: GitRunner): {
|
|
|
12
12
|
export declare function worktreePath(projectRoot: string, runGit?: GitRunner): string;
|
|
13
13
|
/** True when `ancestor` is reachable from `descendant` (same commit counts). */
|
|
14
14
|
export declare function gitIsAncestor(projectRoot: string, ancestor: string, descendant: string, runGit?: GitRunner): boolean | null;
|
|
15
|
+
/**
|
|
16
|
+
* Ceiling for one `git cat-file --batch` payload. The directive terminal
|
|
17
|
+
* corpus is ~8 MiB today; this leaves headroom without reintroducing
|
|
18
|
+
* per-blob `git show` on a truncated read.
|
|
19
|
+
*/
|
|
20
|
+
export declare const GIT_CAT_FILE_BATCH_MAX_BUFFER: number;
|
|
21
|
+
/**
|
|
22
|
+
* Parse `git cat-file --batch` stdout for `paths.length` objects, in the
|
|
23
|
+
* same order the names were written to stdin. Returns null if the stream
|
|
24
|
+
* is truncated or a header is malformed so callers can fall back.
|
|
25
|
+
*/
|
|
26
|
+
export declare function parseGitCatFileBatch(stdout: Buffer, paths: readonly string[]): Map<string, string | null> | null;
|
|
27
|
+
/**
|
|
28
|
+
* Read many `tip:path` blobs in one `git cat-file --batch` process.
|
|
29
|
+
* Falls back to per-path `git show` only when the batch stream cannot be
|
|
30
|
+
* parsed, so verdicts stay content-authoritative.
|
|
31
|
+
*/
|
|
32
|
+
export declare function showBlobsBatch(projectRoot: string, tip: string, paths: readonly string[], runGit?: GitRunner): Map<string, string | null>;
|
|
15
33
|
export declare function detectBranch(projectRoot: string, runGit?: GitRunner): string | null;
|
|
16
34
|
//# sourceMappingURL=git.d.ts.map
|
package/dist/session/git.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { execFileSync } from "node:child_process";
|
|
1
|
+
import { execFileSync, spawnSync } from "node:child_process";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
function coerceGitBytes(value) {
|
|
4
4
|
if (Buffer.isBuffer(value))
|
|
@@ -64,6 +64,86 @@ export function gitIsAncestor(projectRoot, ancestor, descendant, runGit = defaul
|
|
|
64
64
|
}
|
|
65
65
|
return null;
|
|
66
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Ceiling for one `git cat-file --batch` payload. The directive terminal
|
|
69
|
+
* corpus is ~8 MiB today; this leaves headroom without reintroducing
|
|
70
|
+
* per-blob `git show` on a truncated read.
|
|
71
|
+
*/
|
|
72
|
+
export const GIT_CAT_FILE_BATCH_MAX_BUFFER = 64 * 1024 * 1024;
|
|
73
|
+
/**
|
|
74
|
+
* Parse `git cat-file --batch` stdout for `paths.length` objects, in the
|
|
75
|
+
* same order the names were written to stdin. Returns null if the stream
|
|
76
|
+
* is truncated or a header is malformed so callers can fall back.
|
|
77
|
+
*/
|
|
78
|
+
export function parseGitCatFileBatch(stdout, paths) {
|
|
79
|
+
const out = new Map();
|
|
80
|
+
let offset = 0;
|
|
81
|
+
for (const path of paths) {
|
|
82
|
+
const nl = stdout.indexOf(0x0a, offset);
|
|
83
|
+
if (nl < 0) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
const header = stdout.subarray(offset, nl).toString("utf8");
|
|
87
|
+
offset = nl + 1;
|
|
88
|
+
if (header.endsWith(" missing") || header.endsWith(" ambiguous")) {
|
|
89
|
+
out.set(path, null);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const match = /^[0-9a-fA-F]+ \S+ (\d+)$/.exec(header);
|
|
93
|
+
if (match === null) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
const size = Number(match[1]);
|
|
97
|
+
if (!Number.isInteger(size) || size < 0 || offset + size > stdout.length) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
const content = stdout.subarray(offset, offset + size);
|
|
101
|
+
offset += size;
|
|
102
|
+
if (offset < stdout.length && stdout[offset] === 0x0a) {
|
|
103
|
+
offset += 1;
|
|
104
|
+
}
|
|
105
|
+
else if (offset !== stdout.length) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
out.set(path, content.toString("utf8"));
|
|
109
|
+
}
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
function showBlobViaRunner(projectRoot, tip, path, runGit) {
|
|
113
|
+
const result = runGit(projectRoot, ["show", `${tip}:${path}`]);
|
|
114
|
+
if (result.code !== 0) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
return result.stdout;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Read many `tip:path` blobs in one `git cat-file --batch` process.
|
|
121
|
+
* Falls back to per-path `git show` only when the batch stream cannot be
|
|
122
|
+
* parsed, so verdicts stay content-authoritative.
|
|
123
|
+
*/
|
|
124
|
+
export function showBlobsBatch(projectRoot, tip, paths, runGit = defaultGitRunner) {
|
|
125
|
+
const out = new Map();
|
|
126
|
+
if (paths.length === 0) {
|
|
127
|
+
return out;
|
|
128
|
+
}
|
|
129
|
+
const input = Buffer.from(`${paths.map((path) => `${tip}:${path}`).join("\n")}\n`, "utf8");
|
|
130
|
+
const result = spawnSync("git", ["cat-file", "--batch"], {
|
|
131
|
+
cwd: projectRoot,
|
|
132
|
+
input,
|
|
133
|
+
maxBuffer: GIT_CAT_FILE_BATCH_MAX_BUFFER,
|
|
134
|
+
windowsHide: true,
|
|
135
|
+
});
|
|
136
|
+
if (result.error === undefined && result.status === 0 && result.stdout !== undefined) {
|
|
137
|
+
const parsed = parseGitCatFileBatch(coerceGitBytes(result.stdout), paths);
|
|
138
|
+
if (parsed !== null) {
|
|
139
|
+
return parsed;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
for (const path of paths) {
|
|
143
|
+
out.set(path, showBlobViaRunner(projectRoot, tip, path, runGit));
|
|
144
|
+
}
|
|
145
|
+
return out;
|
|
146
|
+
}
|
|
67
147
|
export function detectBranch(projectRoot, runGit = defaultGitRunner) {
|
|
68
148
|
const sym = runGit(projectRoot, ["symbolic-ref", "--short", "HEAD"]);
|
|
69
149
|
if (sym.code === 0 && sym.stdout.trim()) {
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Ritual-state is "this session completed ceremony." Occupancy is "who may
|
|
5
5
|
* mutate this tree right now." Those lifetimes differ; do not overload
|
|
6
|
-
* ritual-state.json.
|
|
6
|
+
* ritual-state.json. Ordinary end is occupancy:release / session:end (#3604).
|
|
7
|
+
* Join negotiation (`occupancy:request`) is out of scope.
|
|
7
8
|
*
|
|
8
9
|
* Concurrency model:
|
|
9
10
|
* - Assumptions: local filesystem; cooperating processes on one machine.
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Ritual-state is "this session completed ceremony." Occupancy is "who may
|
|
5
5
|
* mutate this tree right now." Those lifetimes differ; do not overload
|
|
6
|
-
* ritual-state.json.
|
|
6
|
+
* ritual-state.json. Ordinary end is occupancy:release / session:end (#3604).
|
|
7
|
+
* Join negotiation (`occupancy:request`) is out of scope.
|
|
7
8
|
*
|
|
8
9
|
* Concurrency model:
|
|
9
10
|
* - Assumptions: local filesystem; cooperating processes on one machine.
|
|
@@ -35,11 +36,16 @@ export function heartbeatAgeSeconds(record, now = new Date()) {
|
|
|
35
36
|
export function isOccupancyExpired(record, now = new Date(), ttlMs = OCCUPANCY_TTL_MS) {
|
|
36
37
|
return now.getTime() - record.heartbeatAt.getTime() > ttlMs;
|
|
37
38
|
}
|
|
39
|
+
function occupancyClockLine(record) {
|
|
40
|
+
return `claimed_at=${timestampIso(record.claimedAt)} heartbeat_at=${timestampIso(record.heartbeatAt)}`;
|
|
41
|
+
}
|
|
38
42
|
export function formatOccupancyRemediation(record, now = new Date()) {
|
|
39
43
|
const age = heartbeatAgeSeconds(record, now);
|
|
40
|
-
return (`Worktree occupied by session ${record.sessionId} (intent=${record.intent}, heartbeat ${age}s ago
|
|
44
|
+
return (`Worktree occupied by session ${record.sessionId} (intent=${record.intent}, heartbeat ${age}s ago, ` +
|
|
45
|
+
`${occupancyClockLine(record)}).\n` +
|
|
41
46
|
"Stay read-only (`session:start --read-only`), use another worktree,\n" +
|
|
42
|
-
"queue a join (`occupancy:request`), or steal (`occupancy:steal --confirm`)
|
|
47
|
+
"queue a join (`occupancy:request`), or steal (`occupancy:steal --confirm`).\n" +
|
|
48
|
+
"The occupant may release (`occupancy:release` / `session:end`).");
|
|
43
49
|
}
|
|
44
50
|
export function resolveOccupancySessionId(input = {}) {
|
|
45
51
|
const explicit = input.sessionId?.trim();
|
|
@@ -194,6 +200,7 @@ export function stealOccupancy(projectRoot, input = {}) {
|
|
|
194
200
|
};
|
|
195
201
|
}
|
|
196
202
|
const incoming = resolveOccupancySessionId(input);
|
|
203
|
+
const priorClock = existingLocked !== null ? ` (${occupancyClockLine(existingLocked)})` : "";
|
|
197
204
|
const record = writeOccupancyRecord(projectRoot, {
|
|
198
205
|
sessionId: incoming,
|
|
199
206
|
worktreePath: resolve(projectRoot),
|
|
@@ -210,7 +217,7 @@ export function stealOccupancy(projectRoot, input = {}) {
|
|
|
210
217
|
sessionId: record.sessionId,
|
|
211
218
|
record,
|
|
212
219
|
path,
|
|
213
|
-
message: `occupancy stolen from ${named}; writer is now session ${record.sessionId}`,
|
|
220
|
+
message: `occupancy stolen from ${named}${priorClock}; writer is now session ${record.sessionId}`,
|
|
214
221
|
code: 0,
|
|
215
222
|
};
|
|
216
223
|
}, input.lockDeps);
|
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Local process-cost ceremony events (#2994).
|
|
3
|
-
*
|
|
4
|
-
* Always-on, best-effort appends to `.deft-cache/events.jsonl`.
|
|
5
|
-
* Not gated on valueFeedback (distinct from #1709 attribution).
|
|
6
|
-
* No remote / Product Insights upload (#2603 not required).
|
|
7
|
-
*
|
|
8
|
-
* Types are declared locally (not imported from session-start) so this module
|
|
9
|
-
* does not form an import cycle with session-start call sites.
|
|
10
|
-
*/
|
|
11
1
|
import { type BehavioralEventRecord } from "../lifecycle/events.js";
|
|
12
2
|
export { PROCESS_COST_EVENT_NAMES, PROCESS_COST_REQUIRED_PAYLOAD, type ProcessCostEventName, } from "./process-cost-constants.js";
|
|
13
3
|
/** Ceremony tier labels mirror session-start cold|rearm (#2992 / #2994). */
|
|
@@ -50,4 +40,36 @@ export declare function emitSessionStartProcessCost(input: SessionStartProcessCo
|
|
|
50
40
|
* Returns null on any failure (telemetry must not change deny verdict).
|
|
51
41
|
*/
|
|
52
42
|
export declare function emitSessionRitualBlockedProcessCost(input: SessionRitualBlockedProcessCostInput, options: EmitProcessCostOptions): BehavioralEventRecord | null;
|
|
43
|
+
export interface CeremonyCostRollup {
|
|
44
|
+
/** Discriminator: CLI process time, not agent-turn wall clock (#3500 / #3508). */
|
|
45
|
+
readonly kind: "cli_process_time";
|
|
46
|
+
readonly windowLabel: string;
|
|
47
|
+
readonly lastColdDurationMs: number | null;
|
|
48
|
+
readonly lastRearmDurationMs: number | null;
|
|
49
|
+
readonly lastColdSteps: readonly ProcessCostStepTiming[];
|
|
50
|
+
readonly lastRearmSteps: readonly ProcessCostStepTiming[];
|
|
51
|
+
readonly blockedRitualCount: number;
|
|
52
|
+
readonly recoveryTierDistribution: Readonly<Record<string, number>>;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Roll up ceremony cost from `.deft-cache/events.jsonl` (#3508).
|
|
56
|
+
* Measures CLI process time, not agent-turn wall clock (#3500).
|
|
57
|
+
*/
|
|
58
|
+
export declare function computeCeremonyCostRollup(options: {
|
|
59
|
+
projectRoot: string;
|
|
60
|
+
logPath?: string | null;
|
|
61
|
+
windowMs?: number;
|
|
62
|
+
now?: Date;
|
|
63
|
+
windowLabel?: string;
|
|
64
|
+
}): CeremonyCostRollup;
|
|
65
|
+
/**
|
|
66
|
+
* Human rollup for the composed `value:show` reader (#3508).
|
|
67
|
+
* CLI process time, not agent-turn wall clock (#3500).
|
|
68
|
+
*/
|
|
69
|
+
export declare function formatCeremonyCostReport(rollup: CeremonyCostRollup): string;
|
|
70
|
+
/**
|
|
71
|
+
* Operator-visible mutation `session:start` line (#3508).
|
|
72
|
+
* CLI process time only. ⊗ not #3286 Later graduation input.
|
|
73
|
+
*/
|
|
74
|
+
export declare function formatSessionStartCeremonyCostLine(ceremonyTier: ProcessCostCeremonyTier, durationMs: number): string;
|
|
53
75
|
//# sourceMappingURL=process-cost.d.ts.map
|
|
@@ -7,8 +7,13 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Types are declared locally (not imported from session-start) so this module
|
|
9
9
|
* does not form an import cycle with session-start call sites.
|
|
10
|
+
*
|
|
11
|
+
* #3508: composed readers live on `task value:show` (`value/readback.ts`).
|
|
12
|
+
* This module measures CLI process time, not agent-turn wall clock (#3500).
|
|
13
|
+
* ⊗ Do not use a printed CLI duration as #3286 Later graduation input.
|
|
10
14
|
*/
|
|
11
|
-
import {
|
|
15
|
+
import { resolve } from "node:path";
|
|
16
|
+
import { DEFAULT_EVENT_LOG, emit, readEvents, } from "../lifecycle/events.js";
|
|
12
17
|
import { PROCESS_COST_EVENT_NAMES } from "./process-cost-constants.js";
|
|
13
18
|
export { PROCESS_COST_EVENT_NAMES, PROCESS_COST_REQUIRED_PAYLOAD, } from "./process-cost-constants.js";
|
|
14
19
|
/**
|
|
@@ -79,4 +84,187 @@ export function emitSessionRitualBlockedProcessCost(input, options) {
|
|
|
79
84
|
return null;
|
|
80
85
|
}
|
|
81
86
|
}
|
|
87
|
+
function isRecordPayload(payload) {
|
|
88
|
+
return payload !== null && typeof payload === "object" && !Array.isArray(payload);
|
|
89
|
+
}
|
|
90
|
+
function parseDetectedAt(record) {
|
|
91
|
+
const raw = record.detected_at;
|
|
92
|
+
if (typeof raw !== "string" || raw.trim().length === 0) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
let text = raw.trim();
|
|
96
|
+
if (text.endsWith("Z")) {
|
|
97
|
+
text = `${text.slice(0, -1)}+00:00`;
|
|
98
|
+
}
|
|
99
|
+
const parsed = new Date(text);
|
|
100
|
+
return Number.isNaN(parsed.getTime()) ? null : parsed;
|
|
101
|
+
}
|
|
102
|
+
function payloadNumber(payload, key) {
|
|
103
|
+
const raw = payload[key];
|
|
104
|
+
return typeof raw === "number" && Number.isFinite(raw) ? raw : null;
|
|
105
|
+
}
|
|
106
|
+
function resolveProcessCostLogPath(projectRoot, logPath) {
|
|
107
|
+
if (logPath !== undefined && logPath !== null) {
|
|
108
|
+
return resolve(logPath);
|
|
109
|
+
}
|
|
110
|
+
return resolve(projectRoot, DEFAULT_EVENT_LOG);
|
|
111
|
+
}
|
|
112
|
+
function parseSteps(payload) {
|
|
113
|
+
const raw = payload?.steps;
|
|
114
|
+
if (!Array.isArray(raw)) {
|
|
115
|
+
return [];
|
|
116
|
+
}
|
|
117
|
+
const out = [];
|
|
118
|
+
for (const item of raw) {
|
|
119
|
+
if (item === null || typeof item !== "object" || Array.isArray(item)) {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const rec = item;
|
|
123
|
+
if (typeof rec.name !== "string" || typeof rec.duration_ms !== "number") {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (!Number.isFinite(rec.duration_ms)) {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
const step = {
|
|
130
|
+
name: rec.name,
|
|
131
|
+
duration_ms: rec.duration_ms,
|
|
132
|
+
};
|
|
133
|
+
if (rec.skipped === true) {
|
|
134
|
+
out.push({ ...step, skipped: true });
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
out.push(step);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return out;
|
|
141
|
+
}
|
|
142
|
+
function lastSessionStartForTier(events, tier) {
|
|
143
|
+
for (let i = events.length - 1; i >= 0; i -= 1) {
|
|
144
|
+
const record = events[i];
|
|
145
|
+
if (record === undefined || record.event !== PROCESS_COST_EVENT_NAMES.sessionStart) {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const payload = isRecordPayload(record.payload) ? record.payload : null;
|
|
149
|
+
if (payload?.ceremony_tier === tier) {
|
|
150
|
+
return record;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
function durationFrom(record) {
|
|
156
|
+
if (record === null || !isRecordPayload(record.payload)) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
return payloadNumber(record.payload, "duration_ms");
|
|
160
|
+
}
|
|
161
|
+
function formatWindowLabel(windowMs) {
|
|
162
|
+
const days = Math.round(windowMs / 86_400_000);
|
|
163
|
+
if (days >= 1 && days * 86_400_000 === windowMs) {
|
|
164
|
+
return `${days}d`;
|
|
165
|
+
}
|
|
166
|
+
const hours = Math.round(windowMs / 3_600_000);
|
|
167
|
+
if (hours >= 1 && hours * 3_600_000 === windowMs) {
|
|
168
|
+
return `${hours}h`;
|
|
169
|
+
}
|
|
170
|
+
return `${windowMs}ms`;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Roll up ceremony cost from `.deft-cache/events.jsonl` (#3508).
|
|
174
|
+
* Measures CLI process time, not agent-turn wall clock (#3500).
|
|
175
|
+
*/
|
|
176
|
+
export function computeCeremonyCostRollup(options) {
|
|
177
|
+
const windowMs = options.windowMs ?? 7 * 86_400_000;
|
|
178
|
+
const now = options.now ?? new Date();
|
|
179
|
+
const sinceMs = now.getTime() - windowMs;
|
|
180
|
+
const logPath = resolveProcessCostLogPath(options.projectRoot, options.logPath);
|
|
181
|
+
let records = [];
|
|
182
|
+
try {
|
|
183
|
+
records = readEvents(logPath).filter((record) => {
|
|
184
|
+
if (record.event !== PROCESS_COST_EVENT_NAMES.sessionStart &&
|
|
185
|
+
record.event !== PROCESS_COST_EVENT_NAMES.sessionRitualBlocked) {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
const at = parseDetectedAt(record);
|
|
189
|
+
if (at === null) {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
return at.getTime() >= sinceMs;
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
records = [];
|
|
197
|
+
}
|
|
198
|
+
const lastCold = lastSessionStartForTier(records, "cold");
|
|
199
|
+
const lastRearm = lastSessionStartForTier(records, "rearm");
|
|
200
|
+
const recoveryTierDistribution = {};
|
|
201
|
+
let blockedRitualCount = 0;
|
|
202
|
+
for (const record of records) {
|
|
203
|
+
if (record.event !== PROCESS_COST_EVENT_NAMES.sessionRitualBlocked) {
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
blockedRitualCount += 1;
|
|
207
|
+
const payload = isRecordPayload(record.payload) ? record.payload : null;
|
|
208
|
+
const rawTier = payload?.recovery_tier;
|
|
209
|
+
const tier = typeof rawTier === "string" && rawTier.trim().length > 0 ? rawTier.trim() : "unspecified";
|
|
210
|
+
recoveryTierDistribution[tier] = (recoveryTierDistribution[tier] ?? 0) + 1;
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
kind: "cli_process_time",
|
|
214
|
+
windowLabel: options.windowLabel ?? formatWindowLabel(windowMs),
|
|
215
|
+
lastColdDurationMs: durationFrom(lastCold),
|
|
216
|
+
lastRearmDurationMs: durationFrom(lastRearm),
|
|
217
|
+
lastColdSteps: parseSteps(lastCold !== null && isRecordPayload(lastCold.payload) ? lastCold.payload : null),
|
|
218
|
+
lastRearmSteps: parseSteps(lastRearm !== null && isRecordPayload(lastRearm.payload) ? lastRearm.payload : null),
|
|
219
|
+
blockedRitualCount,
|
|
220
|
+
recoveryTierDistribution,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
function formatMs(value) {
|
|
224
|
+
return value === null ? "none" : `${value}ms`;
|
|
225
|
+
}
|
|
226
|
+
function formatSteps(steps) {
|
|
227
|
+
if (steps.length === 0) {
|
|
228
|
+
return "none";
|
|
229
|
+
}
|
|
230
|
+
return steps
|
|
231
|
+
.map((step) => {
|
|
232
|
+
const skip = step.skipped === true ? " skipped" : "";
|
|
233
|
+
return `${step.name}=${step.duration_ms}ms${skip}`;
|
|
234
|
+
})
|
|
235
|
+
.join(", ");
|
|
236
|
+
}
|
|
237
|
+
function formatRecovery(distribution) {
|
|
238
|
+
const parts = Object.entries(distribution)
|
|
239
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
240
|
+
.map(([tier, count]) => `${tier}=${count}`);
|
|
241
|
+
return parts.length === 0 ? "none" : parts.join(", ");
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Human rollup for the composed `value:show` reader (#3508).
|
|
245
|
+
* CLI process time, not agent-turn wall clock (#3500).
|
|
246
|
+
*/
|
|
247
|
+
export function formatCeremonyCostReport(rollup) {
|
|
248
|
+
const lines = [
|
|
249
|
+
`[ceremony] CLI process time (not agent-turn wall clock; #3508 / #2994) ` +
|
|
250
|
+
`(${rollup.windowLabel}):`,
|
|
251
|
+
` last cold: ${formatMs(rollup.lastColdDurationMs)}`,
|
|
252
|
+
` last re-arm: ${formatMs(rollup.lastRearmDurationMs)}`,
|
|
253
|
+
` steps (last cold ${PROCESS_COST_EVENT_NAMES.sessionStart}): ` +
|
|
254
|
+
formatSteps(rollup.lastColdSteps),
|
|
255
|
+
` steps (last re-arm ${PROCESS_COST_EVENT_NAMES.sessionStart}): ` +
|
|
256
|
+
formatSteps(rollup.lastRearmSteps),
|
|
257
|
+
` blocked-ritual (${PROCESS_COST_EVENT_NAMES.sessionRitualBlocked}): ` +
|
|
258
|
+
String(rollup.blockedRitualCount),
|
|
259
|
+
` recovery-tier: ${formatRecovery(rollup.recoveryTierDistribution)}`,
|
|
260
|
+
];
|
|
261
|
+
return `${lines.join("\n")}\n`;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Operator-visible mutation `session:start` line (#3508).
|
|
265
|
+
* CLI process time only. ⊗ not #3286 Later graduation input.
|
|
266
|
+
*/
|
|
267
|
+
export function formatSessionStartCeremonyCostLine(ceremonyTier, durationMs) {
|
|
268
|
+
return `[deft session] ceremony ${ceremonyTier} ${durationMs}ms`;
|
|
269
|
+
}
|
|
82
270
|
//# sourceMappingURL=process-cost.js.map
|
|
@@ -19,6 +19,8 @@ const EXCLUDED_DIR_NAMES = new Set([
|
|
|
19
19
|
".planning",
|
|
20
20
|
]);
|
|
21
21
|
const EXCLUDED_PATH_PREFIXES = ["xbrief/", "vbrief/", ".deft/", ".git/"];
|
|
22
|
+
/** Repo-root run-summary telemetry must not invalidate a green bank (#3558). */
|
|
23
|
+
const ROOT_RUN_SUMMARY_REL = ".deft-run-summary.json";
|
|
22
24
|
function asRecord(value) {
|
|
23
25
|
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
24
26
|
return value;
|
|
@@ -39,13 +41,15 @@ function stableJson(value) {
|
|
|
39
41
|
function toPosix(rel) {
|
|
40
42
|
return rel.replace(/\\/g, "/");
|
|
41
43
|
}
|
|
42
|
-
function isExcludedRel(rel) {
|
|
44
|
+
function isExcludedRel(rel, options) {
|
|
43
45
|
const posix = toPosix(rel);
|
|
44
46
|
if (posix === "." || posix.length === 0)
|
|
45
47
|
return false;
|
|
46
48
|
const first = posix.split("/")[0] ?? "";
|
|
47
49
|
if (EXCLUDED_DIR_NAMES.has(first))
|
|
48
50
|
return true;
|
|
51
|
+
if (!options?.keepTelemetryFiles && posix === ROOT_RUN_SUMMARY_REL)
|
|
52
|
+
return true;
|
|
49
53
|
return EXCLUDED_PATH_PREFIXES.some((prefix) => posix === prefix.slice(0, -1) || posix.startsWith(prefix));
|
|
50
54
|
}
|
|
51
55
|
function hashBytes(buf) {
|
|
@@ -130,7 +134,7 @@ function relFromRootBytes(root, abs) {
|
|
|
130
134
|
}
|
|
131
135
|
return toPosix(relative(root, abs.toString("utf8")));
|
|
132
136
|
}
|
|
133
|
-
function walkFiles(root, dir, out, seen = new Set()) {
|
|
137
|
+
function walkFiles(root, dir, out, seen = new Set(), keepTelemetryFiles = false) {
|
|
134
138
|
const dirBuf = asPathBuf(dir);
|
|
135
139
|
let real;
|
|
136
140
|
try {
|
|
@@ -153,7 +157,7 @@ function walkFiles(root, dir, out, seen = new Set()) {
|
|
|
153
157
|
const nameLatin1 = nameBuf.toString("latin1");
|
|
154
158
|
const abs = joinNameBytes(dirBuf, nameBuf);
|
|
155
159
|
const rel = relFromRootBytes(root, abs);
|
|
156
|
-
if (isExcludedRel(rel))
|
|
160
|
+
if (isExcludedRel(rel, { keepTelemetryFiles }))
|
|
157
161
|
continue;
|
|
158
162
|
let st;
|
|
159
163
|
try {
|
|
@@ -165,7 +169,7 @@ function walkFiles(root, dir, out, seen = new Set()) {
|
|
|
165
169
|
if (st.isDirectory()) {
|
|
166
170
|
if (EXCLUDED_DIR_NAMES.has(nameLatin1))
|
|
167
171
|
continue;
|
|
168
|
-
walkFiles(root, abs, out, seen);
|
|
172
|
+
walkFiles(root, abs, out, seen, keepTelemetryFiles);
|
|
169
173
|
continue;
|
|
170
174
|
}
|
|
171
175
|
if (st.isFile())
|
|
@@ -409,7 +413,7 @@ function literalDirPrefix(rel) {
|
|
|
409
413
|
}
|
|
410
414
|
return literal.join("/");
|
|
411
415
|
}
|
|
412
|
-
function expandPath(root, relOrGlob) {
|
|
416
|
+
function expandPath(root, relOrGlob, keepTelemetryFiles = false) {
|
|
413
417
|
const rel = toPosix(relOrGlob).replace(/^\.\//, "");
|
|
414
418
|
if (looksLikeGlob(rel)) {
|
|
415
419
|
try {
|
|
@@ -418,8 +422,11 @@ function expandPath(root, relOrGlob) {
|
|
|
418
422
|
for (const pattern of globPatternsIncludingDotfiles(rel)) {
|
|
419
423
|
for (const match of globSync(pattern, { cwd: root })) {
|
|
420
424
|
const posix = toPosix(relative(root, resolve(root, match)));
|
|
421
|
-
if (posix.length === 0 ||
|
|
425
|
+
if (posix.length === 0 ||
|
|
426
|
+
isExcludedRel(posix, { keepTelemetryFiles }) ||
|
|
427
|
+
seen.has(posix)) {
|
|
422
428
|
continue;
|
|
429
|
+
}
|
|
423
430
|
seen.add(posix);
|
|
424
431
|
const absMatch = resolve(root, posix);
|
|
425
432
|
let st;
|
|
@@ -432,7 +439,7 @@ function expandPath(root, relOrGlob) {
|
|
|
432
439
|
if (st.isFile())
|
|
433
440
|
out.push(posix);
|
|
434
441
|
else if (st.isDirectory())
|
|
435
|
-
walkFiles(root, absMatch, out);
|
|
442
|
+
walkFiles(root, absMatch, out, new Set(), keepTelemetryFiles);
|
|
436
443
|
}
|
|
437
444
|
}
|
|
438
445
|
{
|
|
@@ -448,7 +455,7 @@ function expandPath(root, relOrGlob) {
|
|
|
448
455
|
}
|
|
449
456
|
if (st?.isDirectory()) {
|
|
450
457
|
const walked = [];
|
|
451
|
-
walkFiles(root, startDir, walked);
|
|
458
|
+
walkFiles(root, startDir, walked, new Set(), keepTelemetryFiles);
|
|
452
459
|
const re = globToRegExp(rel);
|
|
453
460
|
for (const file of walked) {
|
|
454
461
|
if (seen.has(file) || !re.test(file))
|
|
@@ -472,7 +479,7 @@ function expandPath(root, relOrGlob) {
|
|
|
472
479
|
const st = statSync(abs);
|
|
473
480
|
if (st.isDirectory()) {
|
|
474
481
|
const out = [];
|
|
475
|
-
walkFiles(root, abs, out);
|
|
482
|
+
walkFiles(root, abs, out, new Set(), keepTelemetryFiles);
|
|
476
483
|
return out;
|
|
477
484
|
}
|
|
478
485
|
if (st.isFile())
|
|
@@ -629,45 +636,55 @@ export function hashProductState(input) {
|
|
|
629
636
|
const runGit = input.runGit ?? defaultGitRunner;
|
|
630
637
|
const files = new Set();
|
|
631
638
|
let statusOk = true;
|
|
639
|
+
const scope = fileScopePaths(input.plan);
|
|
640
|
+
const specifiedSurface = input.productPaths !== undefined || scope.length > 0;
|
|
641
|
+
const keepTelemetryFiles = specifiedSurface;
|
|
632
642
|
if (input.productPaths !== undefined) {
|
|
633
643
|
for (const rel of input.productPaths) {
|
|
634
|
-
for (const expanded of expandPath(root, rel))
|
|
644
|
+
for (const expanded of expandPath(root, rel, true))
|
|
635
645
|
files.add(expanded);
|
|
636
646
|
}
|
|
637
647
|
}
|
|
638
|
-
else {
|
|
639
|
-
const
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
for (const expanded of expandPath(root, entry))
|
|
643
|
-
files.add(expanded);
|
|
644
|
-
}
|
|
648
|
+
else if (scope.length > 0) {
|
|
649
|
+
for (const entry of scope) {
|
|
650
|
+
for (const expanded of expandPath(root, entry, keepTelemetryFiles))
|
|
651
|
+
files.add(expanded);
|
|
645
652
|
}
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
653
|
+
}
|
|
654
|
+
else {
|
|
655
|
+
const git = existsSync(join(root, ".git"));
|
|
656
|
+
if (git) {
|
|
657
|
+
const dirty = dirtyProductFiles(root, runGit);
|
|
658
|
+
if (dirty.ok) {
|
|
659
|
+
statusOk = true;
|
|
651
660
|
for (const rel of dirty.files) {
|
|
652
661
|
for (const expanded of expandIfDirectory(root, rel))
|
|
653
662
|
files.add(expanded);
|
|
654
663
|
}
|
|
655
664
|
}
|
|
656
665
|
else {
|
|
666
|
+
// git status failed (containers, missing git, safe.directory).
|
|
667
|
+
// Walk product files instead of miss-forever (#3558 / #3549).
|
|
657
668
|
const walked = [];
|
|
658
669
|
walkFiles(root, root, walked);
|
|
659
670
|
for (const rel of walked)
|
|
660
671
|
files.add(rel);
|
|
672
|
+
statusOk = true;
|
|
661
673
|
}
|
|
662
674
|
}
|
|
675
|
+
else {
|
|
676
|
+
const walked = [];
|
|
677
|
+
walkFiles(root, root, walked);
|
|
678
|
+
for (const rel of walked)
|
|
679
|
+
files.add(rel);
|
|
680
|
+
}
|
|
663
681
|
}
|
|
664
|
-
const sorted = [...files].sort();
|
|
682
|
+
const sorted = [...files].filter((rel) => !isExcludedRel(rel, { keepTelemetryFiles })).sort();
|
|
665
683
|
const fileHashes = {};
|
|
666
684
|
for (const rel of sorted) {
|
|
667
685
|
fileHashes[rel] = hashProductRel(root, rel, runGit);
|
|
668
686
|
}
|
|
669
687
|
const head = existsSync(join(root, ".git")) ? gitHead(root, runGit).head : null;
|
|
670
|
-
const specifiedSurface = input.productPaths !== undefined || fileScopePaths(input.plan).length > 0;
|
|
671
688
|
const hasSurface = specifiedSurface
|
|
672
689
|
? sorted.length > 0
|
|
673
690
|
: statusOk && (sorted.length > 0 || head !== null);
|
|
@@ -46,12 +46,27 @@ function parseProjectRoot(argv) {
|
|
|
46
46
|
}
|
|
47
47
|
return ".";
|
|
48
48
|
}
|
|
49
|
+
function parseOptionalForIssue(argv) {
|
|
50
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
51
|
+
if (argv[i] !== "--for-issue")
|
|
52
|
+
continue;
|
|
53
|
+
const next = argv[i + 1];
|
|
54
|
+
if (next === undefined || next.startsWith("-"))
|
|
55
|
+
return null;
|
|
56
|
+
const parsed = Number.parseInt(next, 10);
|
|
57
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
49
61
|
/** CLI-shaped cache-fresh entrypoint (mirrors scripts/preflight_cache.py main). */
|
|
50
62
|
export function runCacheFreshMain(argv) {
|
|
51
63
|
const projectRoot = parseProjectRoot(argv);
|
|
52
64
|
const allowMissingBootstrap = argv.includes("--allow-missing-bootstrap");
|
|
53
65
|
const quiet = argv.includes("--quiet");
|
|
54
|
-
const
|
|
66
|
+
const workSelection = argv.includes("--work-selection");
|
|
67
|
+
const skipDriftProbe = argv.includes("--skip-drift-probe") && !workSelection;
|
|
68
|
+
const forIssue = parseOptionalForIssue(argv);
|
|
69
|
+
const result = evaluate(projectRoot, { allowMissingBootstrap, skipDriftProbe, forIssue });
|
|
55
70
|
if (result.code === 0) {
|
|
56
71
|
if (!quiet) {
|
|
57
72
|
if (result.message.startsWith("⚠")) {
|