@deftai/directive-core 0.103.0 → 0.105.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 +682 -35
- package/dist/check/cached-orchestrator.d.ts +1 -1
- package/dist/check/cached-orchestrator.js +2 -2
- package/dist/check/gate-lists.d.ts +3 -2
- package/dist/check/gate-lists.js +5 -2
- package/dist/check/index.d.ts +1 -0
- package/dist/check/index.js +1 -0
- package/dist/check/named-cause.d.ts +1 -1
- package/dist/check/named-cause.js +33 -3
- package/dist/check/session-completed-ac.d.ts +39 -0
- package/dist/check/session-completed-ac.js +198 -0
- package/dist/codebase/default-extractor.d.ts +5 -0
- package/dist/codebase/default-extractor.js +7 -15
- package/dist/deposit/copy-tree.js +60 -0
- package/dist/deposit/python-free.js +26 -18
- package/dist/doctor/checks.d.ts +8 -0
- package/dist/doctor/checks.js +37 -25
- package/dist/doctor/doctor-state.d.ts +4 -0
- package/dist/doctor/doctor-state.js +7 -1
- package/dist/doctor/main.js +9 -14
- package/dist/doctor/types.d.ts +9 -0
- package/dist/fs/contained-write.d.ts +115 -1
- package/dist/fs/contained-write.js +242 -5
- package/dist/fs/mutation-ledger.d.ts +54 -0
- package/dist/fs/mutation-ledger.js +133 -0
- package/dist/fs/non-product-dirs.d.ts +79 -0
- package/dist/fs/non-product-dirs.js +96 -0
- package/dist/hooks/dispatcher.d.ts +3 -1
- package/dist/hooks/dispatcher.js +14 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/init-deposit/agent-hooks.js +17 -15
- package/dist/init-deposit/core-guard-pin-content.d.ts +7 -0
- package/dist/init-deposit/core-guard-pin-content.js +393 -0
- package/dist/init-deposit/gitignore.js +9 -0
- package/dist/init-deposit/hygiene.d.ts +47 -11
- package/dist/init-deposit/hygiene.js +172 -37
- package/dist/init-deposit/legacy-detect.js +6 -2
- package/dist/init-deposit/refresh.d.ts +21 -1
- package/dist/init-deposit/refresh.js +106 -9
- package/dist/init-deposit/scaffold.js +28 -279
- package/dist/init-deposit/slash-deposit.js +13 -7
- package/dist/init-deposit/xbrief-projections.d.ts +8 -1
- package/dist/init-deposit/xbrief-projections.js +24 -11
- package/dist/intake/clause-derivation.d.ts +74 -0
- package/dist/intake/clause-derivation.js +409 -0
- package/dist/intake/index.d.ts +1 -0
- package/dist/intake/index.js +1 -0
- package/dist/intake/issue-ingest.d.ts +3 -1
- package/dist/intake/issue-ingest.js +36 -43
- package/dist/lifecycle/completed-tracked-on-delivery.d.ts +5 -0
- package/dist/lifecycle/completed-tracked-on-delivery.js +85 -18
- package/dist/literal-acceptance/capture.d.ts +10 -0
- package/dist/literal-acceptance/capture.js +224 -75
- package/dist/literal-acceptance/evaluate.d.ts +29 -4
- package/dist/literal-acceptance/evaluate.js +70 -2
- package/dist/literal-acceptance/index.d.ts +3 -3
- package/dist/literal-acceptance/index.js +3 -3
- package/dist/literal-acceptance/safety.d.ts +41 -0
- package/dist/literal-acceptance/safety.js +126 -2
- package/dist/literal-acceptance/types.d.ts +8 -0
- package/dist/orphan-active/evaluate.d.ts +7 -1
- package/dist/orphan-active/evaluate.js +102 -23
- package/dist/platform/platform-capabilities.js +2 -3
- package/dist/policy/base-branch.d.ts +37 -0
- package/dist/policy/base-branch.js +114 -0
- package/dist/policy/branch-sync.d.ts +80 -0
- package/dist/policy/branch-sync.js +211 -0
- package/dist/policy/ceremony-dial-escalation.js +1 -0
- package/dist/policy/ceremony-dial.js +4 -14
- package/dist/policy/delivery-branch.d.ts +5 -3
- package/dist/policy/delivery-branch.js +7 -3
- package/dist/policy/file-size-thresholds.d.ts +24 -0
- package/dist/policy/file-size-thresholds.js +23 -0
- package/dist/policy/forge-outage-retry.d.ts +47 -0
- package/dist/policy/forge-outage-retry.js +157 -0
- package/dist/policy/index.d.ts +7 -0
- package/dist/policy/index.js +66 -7
- package/dist/policy/project-invariants.d.ts +50 -0
- package/dist/policy/project-invariants.js +205 -0
- package/dist/policy/sync-default.d.ts +129 -0
- package/dist/policy/sync-default.js +452 -0
- package/dist/policy/sync-max-files.d.ts +93 -0
- package/dist/policy/sync-max-files.js +192 -0
- package/dist/pr-monitor/absent-required.d.ts +11 -0
- package/dist/pr-monitor/absent-required.js +61 -0
- package/dist/pr-monitor/constants.d.ts +7 -0
- package/dist/pr-monitor/constants.js +7 -0
- package/dist/pr-monitor/index.d.ts +2 -1
- package/dist/pr-monitor/index.js +2 -1
- package/dist/pr-monitor/main.js +4 -0
- package/dist/pr-monitor/monitor.js +24 -1
- package/dist/pr-wait-mergeable/classify.js +6 -0
- package/dist/preflight/evaluate.d.ts +9 -0
- package/dist/preflight/evaluate.js +49 -1
- package/dist/preflight/index.d.ts +5 -1
- package/dist/preflight/index.js +2 -0
- package/dist/preflight/intended-placement.d.ts +62 -0
- package/dist/preflight/intended-placement.js +275 -0
- package/dist/preflight/project-invariants-gate.d.ts +29 -0
- package/dist/preflight/project-invariants-gate.js +82 -0
- package/dist/product-first-done-gate/acceptance-resolver.d.ts +128 -0
- package/dist/product-first-done-gate/acceptance-resolver.js +144 -0
- package/dist/product-first-done-gate/acceptance.d.ts +7 -4
- package/dist/product-first-done-gate/acceptance.js +79 -8
- package/dist/product-first-done-gate/empty-resolution.d.ts +5 -2
- package/dist/product-first-done-gate/empty-resolution.js +13 -2
- package/dist/product-first-done-gate/evaluate.d.ts +29 -0
- package/dist/product-first-done-gate/evaluate.js +307 -33
- package/dist/product-first-done-gate/index.d.ts +3 -1
- package/dist/product-first-done-gate/index.js +2 -1
- package/dist/release/build-dist.d.ts +101 -0
- package/dist/release/build-dist.js +428 -46
- package/dist/run-summary/emit.d.ts +50 -4
- package/dist/run-summary/emit.js +262 -26
- package/dist/run-summary/index.d.ts +3 -2
- package/dist/run-summary/index.js +2 -1
- package/dist/run-summary/session-id.d.ts +21 -0
- package/dist/run-summary/session-id.js +92 -0
- package/dist/run-summary/share.d.ts +1 -1
- package/dist/run-summary/share.js +2 -2
- package/dist/run-summary/types.d.ts +38 -6
- package/dist/run-summary/types.js +1 -0
- package/dist/scope/acceptance-activate-gate.d.ts +1 -1
- package/dist/scope/acceptance-activate-gate.js +4 -4
- package/dist/scope/acceptance-evidence.d.ts +27 -0
- package/dist/scope/acceptance-evidence.js +66 -0
- package/dist/scope/capacity-stamp.d.ts +2 -0
- package/dist/scope/capacity-stamp.js +4 -0
- package/dist/scope/delivery-evidence.d.ts +9 -3
- package/dist/scope/delivery-evidence.js +39 -27
- package/dist/scope/index.d.ts +1 -0
- package/dist/scope/index.js +1 -0
- package/dist/scope/parent-lineage.js +7 -6
- package/dist/scope/project-invariant-coverage.d.ts +54 -0
- package/dist/scope/project-invariant-coverage.js +322 -0
- package/dist/scope/transition.js +66 -4
- package/dist/scope-provenance/compare-intent.d.ts +27 -0
- package/dist/scope-provenance/compare-intent.js +233 -0
- package/dist/scope-provenance/digest.d.ts +18 -1
- package/dist/scope-provenance/digest.js +16 -11
- package/dist/scope-provenance/evaluate.d.ts +5 -1
- package/dist/scope-provenance/evaluate.js +64 -4
- package/dist/scope-provenance/extract-intent.d.ts +43 -0
- package/dist/scope-provenance/extract-intent.js +262 -0
- package/dist/scope-provenance/index.d.ts +7 -1
- package/dist/scope-provenance/index.js +7 -1
- package/dist/scope-provenance/intent-digest.d.ts +12 -0
- package/dist/scope-provenance/intent-digest.js +33 -0
- package/dist/scope-provenance/intent-evaluate.d.ts +43 -0
- package/dist/scope-provenance/intent-evaluate.js +149 -0
- package/dist/scope-provenance/json-tokenizer.d.ts +19 -0
- package/dist/scope-provenance/json-tokenizer.js +225 -0
- package/dist/scope-provenance/known-machine.d.ts +38 -0
- package/dist/scope-provenance/known-machine.js +157 -0
- package/dist/scope-provenance/mint-artifacts.d.ts +102 -0
- package/dist/scope-provenance/mint-artifacts.js +425 -0
- package/dist/session/ac-pass-banking.d.ts +4 -36
- package/dist/session/ac-pass-banking.js +30 -77
- package/dist/session/ac-pass-reuse.d.ts +53 -0
- package/dist/session/ac-pass-reuse.js +95 -0
- package/dist/session/ceremony-dial-evidence.d.ts +42 -0
- package/dist/session/ceremony-dial-evidence.js +194 -0
- package/dist/session/git.js +19 -4
- package/dist/session/index.d.ts +5 -0
- package/dist/session/index.js +5 -0
- package/dist/session/occupancy-stress-worker.d.ts +7 -0
- package/dist/session/occupancy-stress-worker.js +36 -0
- package/dist/session/occupancy.d.ts +97 -0
- package/dist/session/occupancy.js +422 -0
- package/dist/session/openclaw-soft-rebind-deposit.js +6 -6
- package/dist/session/product-state-hash.d.ts +26 -0
- package/dist/session/product-state-hash.js +683 -0
- package/dist/session/session-ready.d.ts +2 -0
- package/dist/session/session-ready.js +52 -30
- package/dist/session/session-start.d.ts +7 -0
- package/dist/session/session-start.js +88 -5
- package/dist/session/verify-ac-session-cache.d.ts +42 -0
- package/dist/session/verify-ac-session-cache.js +126 -0
- package/dist/session/verify-session-ritual.d.ts +5 -0
- package/dist/session/verify-session-ritual.js +9 -0
- package/dist/slash/openclaw-deposit.js +13 -9
- package/dist/slice/constants.d.ts +2 -2
- package/dist/slice/constants.js +2 -2
- package/dist/slice/existing.js +21 -4
- package/dist/slice/lock.d.ts +16 -1
- package/dist/slice/lock.js +137 -11
- package/dist/slice/record.d.ts +8 -5
- package/dist/slice/record.js +21 -13
- package/dist/story-ready/evaluate.d.ts +2 -0
- package/dist/story-ready/evaluate.js +13 -0
- package/dist/swarm/complete-cohort.d.ts +3 -0
- package/dist/swarm/complete-cohort.js +59 -11
- package/dist/swarm/finalize-cohort.js +33 -2
- package/dist/swarm/launch.d.ts +22 -0
- package/dist/swarm/launch.js +155 -0
- package/dist/swarm/routing-set-cli.d.ts +11 -1
- package/dist/swarm/routing-set-cli.js +13 -3
- package/dist/swarm/routing-verify.d.ts +12 -0
- package/dist/swarm/routing-verify.js +59 -20
- package/dist/swarm/routing.d.ts +14 -0
- package/dist/swarm/routing.js +53 -3
- package/dist/telemetry-coverage/evaluate.d.ts +42 -0
- package/dist/telemetry-coverage/evaluate.js +148 -0
- package/dist/telemetry-coverage/fake-trial.d.ts +45 -0
- package/dist/telemetry-coverage/fake-trial.js +204 -0
- package/dist/telemetry-coverage/index.d.ts +5 -0
- package/dist/telemetry-coverage/index.js +5 -0
- package/dist/telemetry-coverage/kinds.d.ts +19 -0
- package/dist/telemetry-coverage/kinds.js +54 -0
- package/dist/telemetry-coverage/scan-callers.d.ts +25 -0
- package/dist/telemetry-coverage/scan-callers.js +168 -0
- package/dist/triage/actions/index.js +4 -1
- package/dist/ts-check-lane/index.d.ts +3 -0
- package/dist/ts-check-lane/index.js +2 -0
- package/dist/ts-check-lane/progress-reporter.d.ts +17 -0
- package/dist/ts-check-lane/progress-reporter.js +38 -0
- package/dist/ts-check-lane/progress.d.ts +32 -0
- package/dist/ts-check-lane/progress.js +57 -0
- package/dist/ts-check-lane/run-lane.d.ts +3 -1
- package/dist/ts-check-lane/run-lane.js +8 -6
- package/dist/validate-content/validate-links.d.ts +8 -0
- package/dist/validate-content/validate-links.js +9 -15
- package/dist/value/feedback-file.js +12 -2
- package/dist/vbrief-build/index.d.ts +1 -0
- package/dist/vbrief-build/index.js +1 -0
- package/dist/vbrief-build/project-invariants-io.d.ts +16 -0
- package/dist/vbrief-build/project-invariants-io.js +41 -0
- package/dist/vbrief-reconcile/graph.js +18 -1
- package/dist/vbrief-reconcile/index.d.ts +3 -1
- package/dist/vbrief-reconcile/index.js +2 -1
- package/dist/vbrief-reconcile/origin-freshness.d.ts +44 -0
- package/dist/vbrief-reconcile/origin-freshness.js +221 -0
- package/dist/vbrief-reconcile/types.d.ts +13 -0
- package/dist/vbrief-reconcile/umbrellas.d.ts +14 -0
- package/dist/vbrief-reconcile/umbrellas.js +261 -26
- package/dist/vbrief-validate/index.d.ts +1 -1
- package/dist/vbrief-validate/index.js +1 -1
- package/dist/vbrief-validate/plan-hooks.d.ts +4 -0
- package/dist/vbrief-validate/plan-hooks.js +68 -0
- package/dist/verify-ac/evaluate.d.ts +7 -1
- package/dist/verify-ac/evaluate.js +34 -26
- package/dist/verify-ac/flag.d.ts +4 -0
- package/dist/verify-ac/flag.js +16 -0
- package/dist/verify-ac/index.d.ts +2 -2
- package/dist/verify-ac/index.js +2 -2
- package/dist/verify-source/contained-writes.js +2 -0
- package/dist/verify-source/index.d.ts +1 -0
- package/dist/verify-source/index.js +1 -0
- package/dist/verify-source/verify-stubs.d.ts +15 -1
- package/dist/verify-source/verify-stubs.js +20 -16
- package/dist/vitest-runner/win32-coverage-tmp-setup.d.ts +5 -6
- package/dist/vitest-runner/win32-coverage-tmp-setup.js +5 -6
- package/dist/xbrief-migrate/migrate-project.js +2 -5
- package/package.json +3 -3
|
@@ -15,7 +15,7 @@ export interface CachedCheckOptions extends CheckOrchestratorSeams {
|
|
|
15
15
|
* Pass `null` to skip preflight entirely (legacy test paths).
|
|
16
16
|
*/
|
|
17
17
|
readonly preflight?: ToolchainPreflightResult | null;
|
|
18
|
-
/** #3282: session id for run-summary lines (default:
|
|
18
|
+
/** #3282 / #3399: session id for run-summary lines (default: resolver). */
|
|
19
19
|
readonly sessionId?: string;
|
|
20
20
|
/** #3282: disable run-summary emission (tests). */
|
|
21
21
|
readonly emitRunSummary?: boolean;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
|
-
import { randomUUID } from "node:crypto";
|
|
3
2
|
import { join, resolve } from "node:path";
|
|
4
3
|
import { lintShippedRegistry, resolveTaskContract, runWithCache, } from "../cache/task-cache/index.js";
|
|
5
4
|
import { defaultWhich } from "../doctor/which.js";
|
|
@@ -73,7 +72,7 @@ export function dispatchCachedTaskCheck(frameworkRoot, projectRoot, options = {}
|
|
|
73
72
|
const baseGates = gatesForCheckTarget(target);
|
|
74
73
|
const gates = applyProductFirstGateMode(baseGates, modeResolution.mode, checkGateId);
|
|
75
74
|
const codeVersion = readCorePackageVersion();
|
|
76
|
-
const sessionId = options.sessionId
|
|
75
|
+
const sessionId = options.sessionId;
|
|
77
76
|
const gateOutcomes = [];
|
|
78
77
|
const emitSummary = (exitCode, degraded) => {
|
|
79
78
|
if (options.emitRunSummary === false)
|
|
@@ -84,6 +83,7 @@ export function dispatchCachedTaskCheck(frameworkRoot, projectRoot, options = {}
|
|
|
84
83
|
sessionId,
|
|
85
84
|
frameworkVersion: codeVersion,
|
|
86
85
|
env: options.env,
|
|
86
|
+
component: "check",
|
|
87
87
|
});
|
|
88
88
|
emitter.emitCheckInvocation({
|
|
89
89
|
target,
|
|
@@ -10,8 +10,9 @@ export type CheckGateSpec = string | {
|
|
|
10
10
|
readonly args?: readonly string[];
|
|
11
11
|
};
|
|
12
12
|
/**
|
|
13
|
-
* Product-first AC gate (#3284): always first; soft-missing
|
|
14
|
-
*
|
|
13
|
+
* Product-first AC gate (#3284): always first; `--soft-missing-xbrief` skips
|
|
14
|
+
* only when no story was active this session. A same-session complete targets
|
|
15
|
+
* xbrief/completed instead of skipping (#3357).
|
|
15
16
|
*/
|
|
16
17
|
export declare const PRODUCT_FIRST_AC_GATE: CheckGateSpec;
|
|
17
18
|
export declare function checkGateId(spec: CheckGateSpec): string;
|
package/dist/check/gate-lists.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/** Gate execution order mirrors Taskfile.yml check targets (#1713 dogfood). */
|
|
2
2
|
import { PRODUCT_AC_GATE_ID } from "../product-first-done-gate/types.js";
|
|
3
3
|
/**
|
|
4
|
-
* Product-first AC gate (#3284): always first; soft-missing
|
|
5
|
-
*
|
|
4
|
+
* Product-first AC gate (#3284): always first; `--soft-missing-xbrief` skips
|
|
5
|
+
* only when no story was active this session. A same-session complete targets
|
|
6
|
+
* xbrief/completed instead of skipping (#3357).
|
|
6
7
|
*/
|
|
7
8
|
export const PRODUCT_FIRST_AC_GATE = {
|
|
8
9
|
task: PRODUCT_AC_GATE_ID,
|
|
@@ -81,6 +82,8 @@ export const FRAMEWORK_CHECK_GATES = [
|
|
|
81
82
|
"verify:test-boundary",
|
|
82
83
|
"verify:scope-provenance",
|
|
83
84
|
"verify:consumer-check-contract",
|
|
85
|
+
// #3362: dead-surface detector (warn-only this release; no --enforce)
|
|
86
|
+
"verify:telemetry-coverage",
|
|
84
87
|
"verify:vbrief-conformance",
|
|
85
88
|
"verify:destructive-gh-verbs",
|
|
86
89
|
"verify:scm-boundary",
|
package/dist/check/index.d.ts
CHANGED
|
@@ -6,4 +6,5 @@ export { extractGateCause, formatDegradedSkipReport, formatNamedCauseFailure, ty
|
|
|
6
6
|
export type { CheckOrchestratorOptions, CheckOrchestratorSeams } from "./orchestrator.js";
|
|
7
7
|
export { dispatchTaskCheck, isFrameworkRepoRoot, isFrameworkSourceContext, resolveCheckTarget, } from "./orchestrator.js";
|
|
8
8
|
export { detectTestRunner, type RunnerDetectResult, runnerDetectionTable, type TestRunnerKind, } from "./runner-detect.js";
|
|
9
|
+
export { type ResolveSessionCompletedAcInput, resolveSessionCompletedVerifyAcTarget, SESSION_COMPLETED_AC_REMEDIATION, SESSION_COMPLETED_MARKER_REL, type SessionCompletedAcTarget, type SessionCompletedMarker, sessionCompletedMarkerPath, writeSessionCompletedMarker, } from "./session-completed-ac.js";
|
|
9
10
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/check/index.js
CHANGED
|
@@ -5,4 +5,5 @@ export { CONSUMER_CHECK_GATES, checkGateId, checkGateSpawnArgs, FRAMEWORK_CHECK_
|
|
|
5
5
|
export { extractGateCause, formatDegradedSkipReport, formatNamedCauseFailure, remedyForGate, } from "./named-cause.js";
|
|
6
6
|
export { dispatchTaskCheck, isFrameworkRepoRoot, isFrameworkSourceContext, resolveCheckTarget, } from "./orchestrator.js";
|
|
7
7
|
export { detectTestRunner, runnerDetectionTable, } from "./runner-detect.js";
|
|
8
|
+
export { resolveSessionCompletedVerifyAcTarget, SESSION_COMPLETED_AC_REMEDIATION, SESSION_COMPLETED_MARKER_REL, sessionCompletedMarkerPath, writeSessionCompletedMarker, } from "./session-completed-ac.js";
|
|
8
9
|
//# sourceMappingURL=index.js.map
|
|
@@ -15,7 +15,7 @@ export interface NamedCauseMessage {
|
|
|
15
15
|
* Extract a short cause from gate stdout/stderr without leaking env values.
|
|
16
16
|
* Strips lines that look like KEY=value assignments.
|
|
17
17
|
*/
|
|
18
|
-
export declare function extractGateCause(stdout: string, stderr: string, exitCode: number, spawnError?: string): string;
|
|
18
|
+
export declare function extractGateCause(stdout: string, stderr: string, exitCode: number, spawnError?: string, gateId?: string): string;
|
|
19
19
|
export declare function remedyForGate(gateId: string, cause: string): string;
|
|
20
20
|
/**
|
|
21
21
|
* Format named-cause failure lines for a single gate.
|
|
@@ -31,7 +31,7 @@ const CLI_SPAWN_ERROR_REMEDY = "Install: npm i -g @deftai/directive@latest";
|
|
|
31
31
|
* Extract a short cause from gate stdout/stderr without leaking env values.
|
|
32
32
|
* Strips lines that look like KEY=value assignments.
|
|
33
33
|
*/
|
|
34
|
-
export function extractGateCause(stdout, stderr, exitCode, spawnError) {
|
|
34
|
+
export function extractGateCause(stdout, stderr, exitCode, spawnError, gateId) {
|
|
35
35
|
if (spawnError !== undefined && spawnError.length > 0) {
|
|
36
36
|
// Normalize common missing-binary messages without path dumps.
|
|
37
37
|
if (/ENOENT|not found|not recognized/i.test(spawnError)) {
|
|
@@ -46,15 +46,45 @@ export function extractGateCause(stdout, stderr, exitCode, spawnError) {
|
|
|
46
46
|
.split(/\r?\n/)
|
|
47
47
|
.map((l) => l.trim())
|
|
48
48
|
.filter((l) => l.length > 0);
|
|
49
|
+
const useful = [];
|
|
49
50
|
for (const line of combined) {
|
|
50
51
|
if (looksLikeEnvLeak(line))
|
|
51
52
|
continue;
|
|
52
53
|
if (line.startsWith("check:"))
|
|
53
54
|
continue;
|
|
54
|
-
|
|
55
|
+
if (isGoTaskWrapperNoise(line))
|
|
56
|
+
continue;
|
|
57
|
+
useful.push(line);
|
|
58
|
+
}
|
|
59
|
+
const gateHint = gateId?.trim() ?? "";
|
|
60
|
+
if (gateHint.length > 0) {
|
|
61
|
+
const named = useful.find((line) => line.includes(gateHint));
|
|
62
|
+
if (named !== undefined) {
|
|
63
|
+
return sanitizeCauseLine(named);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (useful.length > 0) {
|
|
67
|
+
return sanitizeCauseLine(useful[0]);
|
|
55
68
|
}
|
|
56
69
|
return `gate exited ${exitCode} without a diagnostic message`;
|
|
57
70
|
}
|
|
71
|
+
/** go-task echoes `task: [engine:_ts-build] set -eu` plus the script body (#3449). */
|
|
72
|
+
function isGoTaskWrapperNoise(line) {
|
|
73
|
+
if (/^task: \[/.test(line))
|
|
74
|
+
return true;
|
|
75
|
+
if (/^set -eu$/.test(line))
|
|
76
|
+
return true;
|
|
77
|
+
if (/^: #/.test(line))
|
|
78
|
+
return true;
|
|
79
|
+
if (/^# /.test(line) || /^#\t/.test(line))
|
|
80
|
+
return true;
|
|
81
|
+
if (/^(bin|root_pkg|is_buildable_source|first_token|is_runtime_verb|global_cli)=/.test(line)) {
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
if (/^(if |elif |else$|fi$|then$)/.test(line))
|
|
85
|
+
return true;
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
58
88
|
function looksLikeEnvLeak(line) {
|
|
59
89
|
// Avoid echoing DEFT_*= or generic env dumps.
|
|
60
90
|
if (/^[A-Z][A-Z0-9_]*=/.test(line))
|
|
@@ -91,7 +121,7 @@ export function remedyForGate(gateId, cause) {
|
|
|
91
121
|
* Format named-cause failure lines for a single gate.
|
|
92
122
|
*/
|
|
93
123
|
export function formatNamedCauseFailure(input) {
|
|
94
|
-
const cause = extractGateCause(input.stdout ?? "", input.stderr ?? "", input.exitCode, input.spawnError);
|
|
124
|
+
const cause = extractGateCause(input.stdout ?? "", input.stderr ?? "", input.exitCode, input.spawnError, input.gateId);
|
|
95
125
|
const remedy = remedyForGate(input.gateId, cause);
|
|
96
126
|
const lines = [
|
|
97
127
|
`check: gate ${input.gateId} failed (exit ${input.exitCode})`,
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check composition: verify:ac must not soft-skip after a same-session complete (#3357).
|
|
3
|
+
*
|
|
4
|
+
* "No story ever active this session" remains a legitimate skip.
|
|
5
|
+
* "A story completed this session" targets xbrief/completed (most recent).
|
|
6
|
+
* One remediation string when that target cannot be used.
|
|
7
|
+
*/
|
|
8
|
+
/** One remediation when check cannot target the just-completed brief (#3357). */
|
|
9
|
+
export declare const SESSION_COMPLETED_AC_REMEDIATION = "Run task verify:ac -- xbrief/completed/<just-completed>.xbrief.json (a story completed this session; check must not soft-skip) (#3357)";
|
|
10
|
+
/** Project-relative marker written by scope:complete for this session (#3357). */
|
|
11
|
+
export declare const SESSION_COMPLETED_MARKER_REL: readonly [".deft", "last-completed.json"];
|
|
12
|
+
export interface SessionCompletedMarker {
|
|
13
|
+
readonly path: string;
|
|
14
|
+
readonly sessionId: string;
|
|
15
|
+
readonly completedAt: string;
|
|
16
|
+
}
|
|
17
|
+
export type SessionCompletedAcTarget = {
|
|
18
|
+
readonly kind: "none";
|
|
19
|
+
} | {
|
|
20
|
+
readonly kind: "target";
|
|
21
|
+
readonly path: string;
|
|
22
|
+
} | {
|
|
23
|
+
readonly kind: "cannot";
|
|
24
|
+
readonly message: string;
|
|
25
|
+
};
|
|
26
|
+
export interface ResolveSessionCompletedAcInput {
|
|
27
|
+
readonly projectRoot: string;
|
|
28
|
+
readonly sessionId?: string | null;
|
|
29
|
+
readonly sessionStartedAt?: Date | null;
|
|
30
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
31
|
+
}
|
|
32
|
+
export declare function sessionCompletedMarkerPath(projectRoot: string): string;
|
|
33
|
+
/** Persist the just-completed brief so check can fail closed if that file is unreadable. */
|
|
34
|
+
export declare function writeSessionCompletedMarker(projectRoot: string, marker: SessionCompletedMarker): void;
|
|
35
|
+
/**
|
|
36
|
+
* Resolve the just-completed brief for this session, if check must target it.
|
|
37
|
+
*/
|
|
38
|
+
export declare function resolveSessionCompletedVerifyAcTarget(input: ResolveSessionCompletedAcInput): SessionCompletedAcTarget;
|
|
39
|
+
//# sourceMappingURL=session-completed-ac.d.ts.map
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check composition: verify:ac must not soft-skip after a same-session complete (#3357).
|
|
3
|
+
*
|
|
4
|
+
* "No story ever active this session" remains a legitimate skip.
|
|
5
|
+
* "A story completed this session" targets xbrief/completed (most recent).
|
|
6
|
+
* One remediation string when that target cannot be used.
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
9
|
+
import { join, resolve } from "node:path";
|
|
10
|
+
import { containedWrite } from "../fs/contained-write.js";
|
|
11
|
+
import { readRitualState } from "../session/ritual-sentinel.js";
|
|
12
|
+
import { parseTimestamp } from "../session/time.js";
|
|
13
|
+
/** One remediation when check cannot target the just-completed brief (#3357). */
|
|
14
|
+
export const SESSION_COMPLETED_AC_REMEDIATION = "Run task verify:ac -- xbrief/completed/<just-completed>.xbrief.json (a story completed this session; check must not soft-skip) (#3357)";
|
|
15
|
+
/** Project-relative marker written by scope:complete for this session (#3357). */
|
|
16
|
+
export const SESSION_COMPLETED_MARKER_REL = [".deft", "last-completed.json"];
|
|
17
|
+
function asRecord(value) {
|
|
18
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
export function sessionCompletedMarkerPath(projectRoot) {
|
|
24
|
+
return join(resolve(projectRoot), ...SESSION_COMPLETED_MARKER_REL);
|
|
25
|
+
}
|
|
26
|
+
/** Persist the just-completed brief so check can fail closed if that file is unreadable. */
|
|
27
|
+
export function writeSessionCompletedMarker(projectRoot, marker) {
|
|
28
|
+
const root = resolve(projectRoot);
|
|
29
|
+
const target = sessionCompletedMarkerPath(root);
|
|
30
|
+
containedWrite({
|
|
31
|
+
root,
|
|
32
|
+
target,
|
|
33
|
+
data: `${JSON.stringify(marker, null, 2)}\n`,
|
|
34
|
+
mode: "replace",
|
|
35
|
+
mkdir: true,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
function readSessionCompletedMarker(projectRoot) {
|
|
39
|
+
const path = sessionCompletedMarkerPath(projectRoot);
|
|
40
|
+
if (!existsSync(path)) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
45
|
+
const rec = asRecord(parsed);
|
|
46
|
+
const briefPath = typeof rec?.path === "string" ? rec.path.trim() : "";
|
|
47
|
+
const sessionId = typeof rec?.sessionId === "string" ? rec.sessionId.trim() : "";
|
|
48
|
+
const completedAt = typeof rec?.completedAt === "string" ? rec.completedAt.trim() : "";
|
|
49
|
+
if (briefPath.length === 0 || sessionId.length === 0) {
|
|
50
|
+
return "invalid";
|
|
51
|
+
}
|
|
52
|
+
return { path: briefPath, sessionId, completedAt };
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return "invalid";
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function resolveSession(input) {
|
|
59
|
+
const env = input.env ?? process.env;
|
|
60
|
+
const injected = input.sessionId?.trim() ?? "";
|
|
61
|
+
if (injected.length > 0) {
|
|
62
|
+
return { sessionId: injected, startedAt: input.sessionStartedAt ?? null };
|
|
63
|
+
}
|
|
64
|
+
const fromEnv = typeof env.DEFT_SESSION_ID === "string" ? env.DEFT_SESSION_ID.trim() : "";
|
|
65
|
+
const [state] = readRitualState(resolve(input.projectRoot));
|
|
66
|
+
const ritualId = state?.sessionId.trim() ?? "";
|
|
67
|
+
const sessionId = fromEnv.length > 0 ? fromEnv : ritualId;
|
|
68
|
+
// Do not pair DEFT_SESSION_ID with a different ritual-state startedAt.
|
|
69
|
+
const ritualStartedAt = ritualId.length > 0 && ritualId === sessionId ? (state?.startedAt ?? null) : null;
|
|
70
|
+
return {
|
|
71
|
+
sessionId: sessionId.length > 0 ? sessionId : null,
|
|
72
|
+
startedAt: input.sessionStartedAt ?? ritualStartedAt,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function listCompletedBriefPaths(projectRoot) {
|
|
76
|
+
const paths = [];
|
|
77
|
+
for (const dirName of ["xbrief", "vbrief"]) {
|
|
78
|
+
const completed = join(projectRoot, dirName, "completed");
|
|
79
|
+
if (!existsSync(completed))
|
|
80
|
+
continue;
|
|
81
|
+
let names = [];
|
|
82
|
+
try {
|
|
83
|
+
names = readdirSync(completed)
|
|
84
|
+
.filter((n) => n.endsWith(".xbrief.json") || n.endsWith(".vbrief.json"))
|
|
85
|
+
.sort();
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
for (const name of names) {
|
|
91
|
+
paths.push(join(completed, name));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return paths;
|
|
95
|
+
}
|
|
96
|
+
function readCompletedCandidate(path) {
|
|
97
|
+
if (!existsSync(path)) {
|
|
98
|
+
return "unreadable";
|
|
99
|
+
}
|
|
100
|
+
let parsed;
|
|
101
|
+
try {
|
|
102
|
+
parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return "unreadable";
|
|
106
|
+
}
|
|
107
|
+
const data = asRecord(parsed);
|
|
108
|
+
const plan = asRecord(data?.plan);
|
|
109
|
+
if (plan === null) {
|
|
110
|
+
return "unreadable";
|
|
111
|
+
}
|
|
112
|
+
const meta = asRecord(plan.metadata);
|
|
113
|
+
if (meta === null) {
|
|
114
|
+
// Historical completed briefs (pre-#3357) often have no plan.metadata.
|
|
115
|
+
// That is not an unreadable this-session target (#3375).
|
|
116
|
+
return { path, completedAtMs: 0, sessionId: null };
|
|
117
|
+
}
|
|
118
|
+
const sessionRaw = typeof meta.completedSessionId === "string" ? meta.completedSessionId.trim() : "";
|
|
119
|
+
const completedAt = parseTimestamp(meta.completedAt);
|
|
120
|
+
return {
|
|
121
|
+
path,
|
|
122
|
+
completedAtMs: completedAt?.getTime() ?? 0,
|
|
123
|
+
sessionId: sessionRaw.length > 0 ? sessionRaw : null,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function isSameSession(candidate, sessionId, startedAt) {
|
|
127
|
+
if (sessionId !== null && candidate.sessionId === sessionId) {
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
// Timestamp fallback only for pre-#3357 briefs that never stamped a session id.
|
|
131
|
+
// A brief stamped for a different session must not replace this session's target.
|
|
132
|
+
if (candidate.sessionId === null &&
|
|
133
|
+
startedAt !== null &&
|
|
134
|
+
candidate.completedAtMs >= startedAt.getTime()) {
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Resolve the just-completed brief for this session, if check must target it.
|
|
141
|
+
*/
|
|
142
|
+
export function resolveSessionCompletedVerifyAcTarget(input) {
|
|
143
|
+
const projectRoot = resolve(input.projectRoot);
|
|
144
|
+
const { sessionId, startedAt } = resolveSession(input);
|
|
145
|
+
const marker = readSessionCompletedMarker(projectRoot);
|
|
146
|
+
if (marker === "invalid" && sessionId !== null) {
|
|
147
|
+
return { kind: "cannot", message: SESSION_COMPLETED_AC_REMEDIATION };
|
|
148
|
+
}
|
|
149
|
+
if (marker !== null &&
|
|
150
|
+
marker !== "invalid" &&
|
|
151
|
+
sessionId !== null &&
|
|
152
|
+
marker.sessionId === sessionId) {
|
|
153
|
+
const candidate = readCompletedCandidate(marker.path);
|
|
154
|
+
if (candidate === "unreadable") {
|
|
155
|
+
return { kind: "cannot", message: SESSION_COMPLETED_AC_REMEDIATION };
|
|
156
|
+
}
|
|
157
|
+
return { kind: "target", path: candidate.path };
|
|
158
|
+
}
|
|
159
|
+
const paths = listCompletedBriefPaths(projectRoot);
|
|
160
|
+
if (paths.length === 0 || (sessionId === null && startedAt === null)) {
|
|
161
|
+
return { kind: "none" };
|
|
162
|
+
}
|
|
163
|
+
const matched = [];
|
|
164
|
+
let unreadableCount = 0;
|
|
165
|
+
for (const path of paths) {
|
|
166
|
+
const candidate = readCompletedCandidate(path);
|
|
167
|
+
if (candidate === "unreadable") {
|
|
168
|
+
unreadableCount += 1;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
if (isSameSession(candidate, sessionId, startedAt)) {
|
|
172
|
+
matched.push(candidate);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (matched.length === 0) {
|
|
176
|
+
// Any unreadable completed brief may be this session's target.
|
|
177
|
+
if (unreadableCount > 0) {
|
|
178
|
+
return { kind: "cannot", message: SESSION_COMPLETED_AC_REMEDIATION };
|
|
179
|
+
}
|
|
180
|
+
return { kind: "none" };
|
|
181
|
+
}
|
|
182
|
+
matched.sort((a, b) => {
|
|
183
|
+
if (a.completedAtMs !== b.completedAtMs) {
|
|
184
|
+
return b.completedAtMs - a.completedAtMs;
|
|
185
|
+
}
|
|
186
|
+
return a.path < b.path ? 1 : a.path > b.path ? -1 : 0;
|
|
187
|
+
});
|
|
188
|
+
const latest = matched[0];
|
|
189
|
+
if (latest === undefined) {
|
|
190
|
+
return { kind: "none" };
|
|
191
|
+
}
|
|
192
|
+
const verified = readCompletedCandidate(latest.path);
|
|
193
|
+
if (verified === "unreadable") {
|
|
194
|
+
return { kind: "cannot", message: SESSION_COMPLETED_AC_REMEDIATION };
|
|
195
|
+
}
|
|
196
|
+
return { kind: "target", path: latest.path };
|
|
197
|
+
}
|
|
198
|
+
//# sourceMappingURL=session-completed-ac.js.map
|
|
@@ -3,6 +3,11 @@ export declare const DEFAULT_PROVIDER_VERSION = "0.1";
|
|
|
3
3
|
export declare const MAX_IMPORT_SCAN_BYTES = 262144;
|
|
4
4
|
export declare const MAX_FILES_PER_MODULE = 100;
|
|
5
5
|
export declare const MAX_EVIDENCE_PER_EDGE = 5;
|
|
6
|
+
/**
|
|
7
|
+
* Shared "not product source" core (#3487) plus `build`, which only the
|
|
8
|
+
* codebase extractor treats as non-source. Agent worktrees are not source
|
|
9
|
+
* modules (#2953, #1656).
|
|
10
|
+
*/
|
|
6
11
|
export declare const SKIP_DIRS: Set<string>;
|
|
7
12
|
export declare const LANGUAGE_BY_SUFFIX: Readonly<Record<string, string>>;
|
|
8
13
|
export declare const ENTRYPOINT_NAMES: Set<string>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { existsSync, globSync, readdirSync, readFileSync, realpathSync, statSync, } from "node:fs";
|
|
3
3
|
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
4
|
+
import { NON_PRODUCT_DIRS } from "../fs/non-product-dirs.js";
|
|
4
5
|
import { resolveProjectDefinitionPath } from "../layout/resolve.js";
|
|
5
6
|
import { extractCodeStructure, loadJsonFile } from "../verify-source/code-structure-validate.js";
|
|
6
7
|
import { sortedStringifyPretty } from "./json.js";
|
|
@@ -10,21 +11,12 @@ export const DEFAULT_PROVIDER_VERSION = "0.1";
|
|
|
10
11
|
export const MAX_IMPORT_SCAN_BYTES = 262_144;
|
|
11
12
|
export const MAX_FILES_PER_MODULE = 100;
|
|
12
13
|
export const MAX_EVIDENCE_PER_EDGE = 5;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"__pycache__",
|
|
20
|
-
"build",
|
|
21
|
-
"dist",
|
|
22
|
-
"node_modules",
|
|
23
|
-
// Swarm / agent worktrees are not source modules (#2953, #1656).
|
|
24
|
-
".deft-scratch",
|
|
25
|
-
// Legacy swarm worktree root name (pre-.deft-scratch layout).
|
|
26
|
-
"swarm-worktrees",
|
|
27
|
-
]);
|
|
14
|
+
/**
|
|
15
|
+
* Shared "not product source" core (#3487) plus `build`, which only the
|
|
16
|
+
* codebase extractor treats as non-source. Agent worktrees are not source
|
|
17
|
+
* modules (#2953, #1656).
|
|
18
|
+
*/
|
|
19
|
+
export const SKIP_DIRS = new Set([...NON_PRODUCT_DIRS, "build"]);
|
|
28
20
|
export const LANGUAGE_BY_SUFFIX = {
|
|
29
21
|
".go": "Go",
|
|
30
22
|
".js": "JavaScript",
|
|
@@ -14,6 +14,8 @@ import { constants } from "node:fs";
|
|
|
14
14
|
import { lstat, mkdir, mkdtemp, open, readdir, readFile, rename, rm, stat } from "node:fs/promises";
|
|
15
15
|
import { tmpdir } from "node:os";
|
|
16
16
|
import { dirname, join } from "node:path";
|
|
17
|
+
import { containedRemove, containedWrite } from "../fs/contained-write.js";
|
|
18
|
+
import { isPortRecordMode, recordActiveMutation } from "../fs/mutation-ledger.js";
|
|
17
19
|
const DEFAULT_FILE_MODE = 0o644;
|
|
18
20
|
const DEFAULT_DIR_MODE = 0o755;
|
|
19
21
|
async function assertDestinationIsNotSymlink(path, label = "copyTree") {
|
|
@@ -103,6 +105,51 @@ async function pathExists(path) {
|
|
|
103
105
|
throw err;
|
|
104
106
|
}
|
|
105
107
|
}
|
|
108
|
+
/** Posix-relative file and symlink paths under `root` (directories are walked). */
|
|
109
|
+
async function listRelativeFilePaths(root) {
|
|
110
|
+
const out = [];
|
|
111
|
+
async function walk(dir, rel) {
|
|
112
|
+
let entries;
|
|
113
|
+
try {
|
|
114
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
for (const entry of entries) {
|
|
120
|
+
const childRel = rel.length > 0 ? `${rel}/${entry.name}` : entry.name;
|
|
121
|
+
if (entry.isDirectory() && !entry.isSymbolicLink()) {
|
|
122
|
+
await walk(join(dir, entry.name), childRel);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
out.push(childRel.replace(/\\/g, "/"));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
await walk(root, "");
|
|
130
|
+
return out;
|
|
131
|
+
}
|
|
132
|
+
async function destOnlyRelativeFiles(src, dst) {
|
|
133
|
+
const srcSet = new Set(await listRelativeFilePaths(src));
|
|
134
|
+
return (await listRelativeFilePaths(dst)).filter((rel) => !srcSet.has(rel));
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Ledger dest-only deletes and dest writes after a successful swap (#3392).
|
|
138
|
+
* Observational: dest is already live. Failures must not reject replaceTree.
|
|
139
|
+
*/
|
|
140
|
+
async function ledgerReplacedTree(dst, destOnly) {
|
|
141
|
+
try {
|
|
142
|
+
for (const rel of destOnly) {
|
|
143
|
+
recordActiveMutation("deleted", join(dst, ...rel.split("/")));
|
|
144
|
+
}
|
|
145
|
+
for (const rel of await listRelativeFilePaths(dst)) {
|
|
146
|
+
recordActiveMutation("wrote", join(dst, ...rel.split("/")));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
// Ledger is observational; dest is already live.
|
|
151
|
+
}
|
|
152
|
+
}
|
|
106
153
|
/**
|
|
107
154
|
* Move `src` to `dst`, falling back to copy+remove across devices (EXDEV),
|
|
108
155
|
* mirroring Go `movePayload` used by `swapInCore`.
|
|
@@ -143,15 +190,27 @@ export async function replaceTree(src, dst) {
|
|
|
143
190
|
throw new Error(`replaceTree: source ${src} is not a directory`);
|
|
144
191
|
}
|
|
145
192
|
await assertDestinationIsNotSymlink(dst, "replaceTree");
|
|
193
|
+
if (isPortRecordMode()) {
|
|
194
|
+
const destOnly = (await pathExists(dst)) ? await destOnlyRelativeFiles(src, dst) : [];
|
|
195
|
+
for (const rel of destOnly) {
|
|
196
|
+
containedRemove({ root: dst, target: rel });
|
|
197
|
+
}
|
|
198
|
+
for (const rel of await listRelativeFilePaths(src)) {
|
|
199
|
+
containedWrite({ root: dst, target: rel, data: "", mode: "replace" });
|
|
200
|
+
}
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
146
203
|
const parent = dirname(dst);
|
|
147
204
|
await mkdir(parent, { recursive: true, mode: DEFAULT_DIR_MODE });
|
|
148
205
|
const staging = await mkdtemp(join(tmpdir(), "deft-core-stage-"));
|
|
149
206
|
let backup = null;
|
|
150
207
|
/** When true, leave `backup` on disk so an operator can recover after dual failure. */
|
|
151
208
|
let preserveBackupOnExit = false;
|
|
209
|
+
let destOnly = [];
|
|
152
210
|
try {
|
|
153
211
|
await copyDirContents(src, staging);
|
|
154
212
|
if (await pathExists(dst)) {
|
|
213
|
+
destOnly = await destOnlyRelativeFiles(src, dst);
|
|
155
214
|
backup = await mkdtemp(join(tmpdir(), "deft-core-bak-"));
|
|
156
215
|
// mkdtemp created an empty dir; remove it so moveTree can rename onto the path.
|
|
157
216
|
await rm(backup, { recursive: true, force: true });
|
|
@@ -206,6 +265,7 @@ export async function replaceTree(src, dst) {
|
|
|
206
265
|
await rm(backup, { recursive: true, force: true }).catch(() => undefined);
|
|
207
266
|
backup = null;
|
|
208
267
|
}
|
|
268
|
+
await ledgerReplacedTree(dst, destOnly);
|
|
209
269
|
}
|
|
210
270
|
finally {
|
|
211
271
|
await rm(staging, { recursive: true, force: true }).catch(() => undefined);
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
* `scripts/` tree, `.py` files, and Python `run` shims.
|
|
7
7
|
*/
|
|
8
8
|
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
9
|
-
import { readdir, readFile,
|
|
9
|
+
import { readdir, readFile, stat } from "node:fs/promises";
|
|
10
10
|
import { join, relative } from "node:path";
|
|
11
|
+
import { containedRemove } from "../fs/contained-write.js";
|
|
11
12
|
const PY_SUFFIX = ".py";
|
|
12
13
|
const PYC_SUFFIX = ".pyc";
|
|
13
14
|
function isPythonRunShim(_path, head) {
|
|
@@ -81,19 +82,24 @@ export function isRepoRootPythonRunShim(projectDir) {
|
|
|
81
82
|
return true;
|
|
82
83
|
}
|
|
83
84
|
}
|
|
85
|
+
function removeContained(projectDir, target, recursive = false) {
|
|
86
|
+
return containedRemove({ root: projectDir, target, recursive }).removed;
|
|
87
|
+
}
|
|
84
88
|
/** Remove Python-only trees/files from the consumer deposit and repo-root run shim. */
|
|
85
89
|
export async function prunePythonArtifactsFromDeposit(depositDir, projectDir, io) {
|
|
86
90
|
let removed = 0;
|
|
87
91
|
const scriptsDir = join(depositDir, "scripts");
|
|
88
92
|
try {
|
|
89
93
|
if ((await stat(scriptsDir)).isDirectory()) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
94
|
+
if (removeContained(projectDir, scriptsDir, true)) {
|
|
95
|
+
removed += 1;
|
|
96
|
+
io?.printf("Removed Python scripts/ tree from the consumer deposit (#2022 Phase 3).\n");
|
|
97
|
+
}
|
|
93
98
|
}
|
|
94
99
|
}
|
|
95
|
-
catch {
|
|
96
|
-
|
|
100
|
+
catch (err) {
|
|
101
|
+
if (err.code !== "ENOENT")
|
|
102
|
+
throw err;
|
|
97
103
|
}
|
|
98
104
|
async function walkRemovePy(root) {
|
|
99
105
|
let entries;
|
|
@@ -107,8 +113,8 @@ export async function prunePythonArtifactsFromDeposit(depositDir, projectDir, io
|
|
|
107
113
|
const full = join(root, entry.name);
|
|
108
114
|
if (entry.isDirectory()) {
|
|
109
115
|
if (entry.name === "__pycache__") {
|
|
110
|
-
|
|
111
|
-
|
|
116
|
+
if (removeContained(projectDir, full, true))
|
|
117
|
+
removed += 1;
|
|
112
118
|
continue;
|
|
113
119
|
}
|
|
114
120
|
await walkRemovePy(full);
|
|
@@ -117,8 +123,8 @@ export async function prunePythonArtifactsFromDeposit(depositDir, projectDir, io
|
|
|
117
123
|
if (!entry.isFile())
|
|
118
124
|
continue;
|
|
119
125
|
if (entry.name.endsWith(PY_SUFFIX) || entry.name.endsWith(PYC_SUFFIX)) {
|
|
120
|
-
|
|
121
|
-
|
|
126
|
+
if (removeContained(projectDir, full))
|
|
127
|
+
removed += 1;
|
|
122
128
|
}
|
|
123
129
|
}
|
|
124
130
|
}
|
|
@@ -128,21 +134,23 @@ export async function prunePythonArtifactsFromDeposit(depositDir, projectDir, io
|
|
|
128
134
|
try {
|
|
129
135
|
const head = await readFile(depositRun, "utf8");
|
|
130
136
|
if (isPythonRunShim(depositRun, head.slice(0, 200))) {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
137
|
+
if (removeContained(projectDir, depositRun)) {
|
|
138
|
+
removed += 1;
|
|
139
|
+
io?.printf("Removed Python run shim from .deft/core (#2022 Phase 3).\n");
|
|
140
|
+
}
|
|
134
141
|
}
|
|
135
142
|
}
|
|
136
143
|
catch {
|
|
137
|
-
|
|
138
|
-
|
|
144
|
+
if (removeContained(projectDir, depositRun))
|
|
145
|
+
removed += 1;
|
|
139
146
|
}
|
|
140
147
|
}
|
|
141
148
|
const projectRun = join(projectDir, "run");
|
|
142
149
|
if (isRepoRootPythonRunShim(projectDir)) {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
150
|
+
if (removeContained(projectDir, projectRun)) {
|
|
151
|
+
removed += 1;
|
|
152
|
+
io?.printf("Removed repo-root Python run shim (#2022 Phase 3).\n");
|
|
153
|
+
}
|
|
146
154
|
}
|
|
147
155
|
return removed;
|
|
148
156
|
}
|
package/dist/doctor/checks.d.ts
CHANGED
|
@@ -4,6 +4,14 @@ import type { CheckResult } from "./types.js";
|
|
|
4
4
|
export declare const XBRIEF_ENVELOPE_MIGRATE_COMMAND: "deft migrate:xbrief";
|
|
5
5
|
/** Doctor check name for envelope major mismatch (Q5 Option 2, #3243). */
|
|
6
6
|
export declare const XBRIEF_ENVELOPE_MAJOR_CHECK: "xbrief-envelope-version";
|
|
7
|
+
/**
|
|
8
|
+
* One SoT for advisory (exit-exempt) check fails (#3379).
|
|
9
|
+
* Union of the previous `deriveExitCode` / `cmdDoctor` name lists.
|
|
10
|
+
* A fail is advisory when `data.advisory === true` or the name is in this set.
|
|
11
|
+
*/
|
|
12
|
+
export declare const DOCTOR_ADVISORY_FAIL_CHECKS: ReadonlySet<string>;
|
|
13
|
+
/** True when a check fail must stay a warning (not lastErrorCount / exit 1). */
|
|
14
|
+
export declare function isDoctorAdvisoryFail(name: string, data?: Readonly<Record<string, unknown>> | null): boolean;
|
|
7
15
|
export interface CheckSeams {
|
|
8
16
|
readonly readText?: (path: string) => string | null;
|
|
9
17
|
readonly isFile?: (path: string) => boolean;
|