@deftai/directive-core 0.87.0 → 0.89.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/actions.d.ts +55 -0
- package/dist/authz/actions.js +125 -0
- package/dist/authz/classify.d.ts +23 -0
- package/dist/authz/classify.js +217 -0
- package/dist/authz/closed-verb.d.ts +42 -0
- package/dist/authz/closed-verb.js +279 -0
- package/dist/authz/evaluate.d.ts +41 -0
- package/dist/authz/evaluate.js +298 -0
- package/dist/authz/index.d.ts +15 -0
- package/dist/authz/index.js +15 -0
- package/dist/authz/origin.d.ts +28 -0
- package/dist/authz/origin.js +64 -0
- package/dist/authz/paths.d.ts +6 -0
- package/dist/authz/paths.js +19 -0
- package/dist/authz/store.d.ts +40 -0
- package/dist/authz/store.js +317 -0
- package/dist/authz/templates.d.ts +139 -0
- package/dist/authz/templates.js +241 -0
- package/dist/authz/types.d.ts +111 -0
- package/dist/authz/types.js +41 -0
- package/dist/authz/verb-classification.d.ts +44 -0
- package/dist/authz/verb-classification.js +237 -0
- package/dist/branch/evaluate.js +6 -1
- package/dist/cache/fetch.js +10 -5
- package/dist/cache/io.d.ts +9 -2
- package/dist/cache/io.js +30 -10
- package/dist/cache/scanner.d.ts +11 -1
- package/dist/cache/scanner.js +29 -4
- package/dist/cache/task-cache/store.js +11 -7
- package/dist/capacity/backfill.js +10 -4
- package/dist/category-b-namespace/index.js +10 -4
- package/dist/check/gate-lists.js +1 -0
- package/dist/codebase/default-extractor.js +3 -0
- package/dist/codebase/map.js +11 -4
- package/dist/content-contracts/skills/helpers.d.ts +10 -0
- package/dist/content-contracts/skills/helpers.js +35 -0
- package/dist/deposit/copy-tree.d.ts +19 -1
- package/dist/deposit/copy-tree.js +134 -5
- package/dist/doctor/doctor-state.js +28 -4
- package/dist/doctor/main.d.ts +10 -0
- package/dist/doctor/main.js +208 -0
- package/dist/doctor/types.d.ts +11 -0
- package/dist/escalation/actions.d.ts +63 -0
- package/dist/escalation/actions.js +137 -0
- package/dist/escalation/index.d.ts +8 -0
- package/dist/escalation/index.js +8 -0
- package/dist/escalation/paths.d.ts +3 -0
- package/dist/escalation/paths.js +10 -0
- package/dist/escalation/store.d.ts +17 -0
- package/dist/escalation/store.js +172 -0
- package/dist/escalation/types.d.ts +73 -0
- package/dist/escalation/types.js +43 -0
- package/dist/eval/crud-telemetry.js +30 -10
- package/dist/eval/health.js +22 -7
- package/dist/eval/readback.js +11 -10
- package/dist/eval/run.js +32 -16
- package/dist/events/attribution-enrichment.js +10 -4
- package/dist/finish-loop/directive-finish-loop.d.ts +37 -0
- package/dist/finish-loop/directive-finish-loop.js +239 -0
- package/dist/finish-loop/grant-gate.d.ts +31 -0
- package/dist/finish-loop/grant-gate.js +169 -0
- package/dist/finish-loop/index.d.ts +11 -0
- package/dist/finish-loop/index.js +11 -0
- package/dist/finish-loop/main.d.ts +35 -0
- package/dist/finish-loop/main.js +365 -0
- package/dist/finish-loop/pr-finish-loop.d.ts +34 -0
- package/dist/finish-loop/pr-finish-loop.js +217 -0
- package/dist/finish-loop/progress.d.ts +17 -0
- package/dist/finish-loop/progress.js +45 -0
- package/dist/finish-loop/queue.d.ts +16 -0
- package/dist/finish-loop/queue.js +65 -0
- package/dist/finish-loop/types.d.ts +52 -0
- package/dist/finish-loop/types.js +10 -0
- package/dist/fs/contained-write.d.ts +101 -0
- package/dist/fs/contained-write.js +281 -0
- package/dist/fs/projection-containment.d.ts +18 -0
- package/dist/fs/projection-containment.js +40 -0
- package/dist/hooks/classify/classify.d.ts +10 -0
- package/dist/hooks/classify/classify.js +37 -0
- package/dist/hooks/classify/index.d.ts +14 -0
- package/dist/hooks/classify/index.js +13 -0
- package/dist/hooks/classify/paths.d.ts +22 -0
- package/dist/hooks/classify/paths.js +72 -0
- package/dist/hooks/classify/payload.d.ts +16 -0
- package/dist/hooks/classify/payload.js +45 -0
- package/dist/hooks/classify/stdin.d.ts +12 -0
- package/dist/hooks/classify/stdin.js +63 -0
- package/dist/hooks/classify/tool-name.d.ts +18 -0
- package/dist/hooks/classify/tool-name.js +85 -0
- package/dist/hooks/classify/types.d.ts +41 -0
- package/dist/hooks/classify/types.js +7 -0
- package/dist/hooks/dispatcher.d.ts +30 -15
- package/dist/hooks/dispatcher.js +326 -134
- package/dist/hooks/fixtures/cases.d.ts +44 -0
- package/dist/hooks/fixtures/cases.js +595 -0
- package/dist/hooks/fixtures/index.d.ts +2 -0
- package/dist/hooks/fixtures/index.js +2 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.js +2 -0
- package/dist/hooks/tools.d.ts +31 -0
- package/dist/hooks/tools.js +74 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +5 -0
- package/dist/init-deposit/agent-hooks.d.ts +1 -1
- package/dist/init-deposit/agent-hooks.js +65 -10
- package/dist/init-deposit/gitignore.js +11 -3
- package/dist/init-deposit/headless-manifest.js +12 -4
- package/dist/init-deposit/hygiene.d.ts +16 -0
- package/dist/init-deposit/hygiene.js +26 -0
- package/dist/init-deposit/init-dispatch.js +28 -0
- package/dist/init-deposit/migrate.d.ts +1 -1
- package/dist/init-deposit/migrate.js +13 -3
- package/dist/init-deposit/prettierignore.js +12 -5
- package/dist/init-deposit/refresh.js +38 -7
- package/dist/init-deposit/scaffold.js +42 -29
- package/dist/init-deposit/xbrief-projections.js +17 -10
- package/dist/intake/candidates-log.js +46 -35
- package/dist/intake/github-body-cli.d.ts +6 -0
- package/dist/intake/github-body-cli.js +17 -0
- package/dist/intake/github-body.d.ts +46 -3
- package/dist/intake/github-body.js +191 -20
- package/dist/intake/issue-emit.d.ts +1 -0
- package/dist/intake/issue-emit.js +41 -23
- package/dist/intake/issue-ingest.js +11 -2
- package/dist/lifecycle/event-detect.js +12 -4
- package/dist/lifecycle/events.js +29 -20
- package/dist/lifecycle/lifecycle-hygiene.js +9 -3
- package/dist/packs/pack-render.d.ts +33 -0
- package/dist/packs/pack-render.js +172 -13
- package/dist/packs/quarantine-ext.d.ts +10 -0
- package/dist/packs/quarantine-ext.js +26 -2
- package/dist/plan-sequence/store.js +11 -4
- package/dist/policy/hotfix-criteria.d.ts +79 -0
- package/dist/policy/hotfix-criteria.js +197 -0
- package/dist/policy/index.d.ts +5 -0
- package/dist/policy/index.js +32 -1
- package/dist/policy/intent-ceiling.d.ts +79 -0
- package/dist/policy/intent-ceiling.js +181 -0
- package/dist/policy/no-deft-directive.d.ts +59 -0
- package/dist/policy/no-deft-directive.js +110 -0
- package/dist/policy/org-force-on-migration.d.ts +52 -0
- package/dist/policy/org-force-on-migration.js +269 -27
- package/dist/policy/require-human-merge.d.ts +75 -0
- package/dist/policy/require-human-merge.js +324 -0
- package/dist/policy/resolve.js +17 -6
- package/dist/policy/runtime-authority.d.ts +56 -2
- package/dist/policy/runtime-authority.js +297 -3
- package/dist/policy/write-fence.d.ts +78 -0
- package/dist/policy/write-fence.js +164 -0
- package/dist/pr-wait-mergeable/cascade.d.ts +10 -0
- package/dist/pr-wait-mergeable/cascade.js +28 -0
- package/dist/preflight/evaluate.js +10 -0
- package/dist/product-signal/consent.js +21 -5
- package/dist/product-signal/submit.js +22 -12
- package/dist/release/build-dist-runner.js +5 -2
- package/dist/release/build-dist.d.ts +19 -1
- package/dist/release/build-dist.js +50 -2
- package/dist/release/native-steps.js +7 -2
- package/dist/release-publish/pipeline.d.ts +13 -0
- package/dist/release-publish/pipeline.js +46 -1
- package/dist/scope/audit-log.js +19 -24
- package/dist/scope/decompose.js +10 -5
- package/dist/scope/decomposed-refs.js +18 -3
- package/dist/scope/demote.js +9 -2
- package/dist/scope/undo.js +9 -2
- package/dist/session/release-availability.js +26 -6
- package/dist/session/ritual-sentinel.js +19 -12
- package/dist/session/session-start-hook.d.ts +3 -0
- package/dist/session/session-start-hook.js +15 -0
- package/dist/session/session-start.js +37 -0
- package/dist/staleness-tickler/state.js +26 -6
- package/dist/swarm/launch.js +13 -3
- package/dist/swarm/routing.js +9 -3
- package/dist/task-surface/index.js +24 -9
- package/dist/triage/bootstrap/gitignore.js +53 -10
- package/dist/triage/cache-path.js +10 -3
- package/dist/triage/welcome/summary.js +11 -4
- package/dist/triage/welcome/writers.js +45 -16
- package/dist/validate-content/validate-links.js +5 -0
- package/dist/value/readback.js +11 -6
- package/dist/vbrief-activate/activate.js +12 -4
- package/dist/vbrief-build/project-definition-io.js +14 -6
- package/dist/verify-source/contained-writes.d.ts +59 -0
- package/dist/verify-source/contained-writes.js +272 -0
- package/dist/verify-source/cursor-tier1.js +7 -2
- package/dist/verify-source/index.d.ts +1 -0
- package/dist/verify-source/index.js +1 -0
- package/dist/verify-source/openclaw-tier1.js +19 -1
- package/dist/verify-source/verify-stubs.js +3 -0
- package/dist/xbrief-migrate/migrate-project.js +26 -12
- package/dist/xbrief-migrate/transforms.d.ts +4 -2
- package/dist/xbrief-migrate/transforms.js +37 -14
- package/package.json +16 -3
package/dist/branch/evaluate.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { recordBypassSignal, recordGateCatch } from "../events/attribution-ledger.js";
|
|
2
2
|
import { disclosureLine } from "../policy/disclosure.js";
|
|
3
3
|
import { policyColonInvocation } from "../policy/policy-invocation.js";
|
|
4
|
+
import { humanMergeBranchNote, resolveHumanMergePolicy } from "../policy/require-human-merge.js";
|
|
4
5
|
import { ENV_BYPASS, resolvePolicy } from "../policy/resolve.js";
|
|
5
6
|
import { currentBranch, GitNotFoundError } from "./git.js";
|
|
6
7
|
export const DEFAULT_BRANCHES = new Set(["master", "main"]);
|
|
@@ -93,9 +94,13 @@ export function evaluate(projectRoot, options = {}) {
|
|
|
93
94
|
};
|
|
94
95
|
}
|
|
95
96
|
if (!isDefaultBranch(branch, defaultBranches)) {
|
|
97
|
+
// Surface (2) human-merge gate: advisory note on feature branches (#1193).
|
|
98
|
+
const hm = resolveHumanMergePolicy(projectRoot);
|
|
99
|
+
const hmNote = humanMergeBranchNote(hm);
|
|
100
|
+
const base = `✓ deft branch-protection: feature branch '${branch}' -- proceeding.`;
|
|
96
101
|
return {
|
|
97
102
|
exitCode: 0,
|
|
98
|
-
message:
|
|
103
|
+
message: hmNote !== null ? `${base}\n${hmNote}` : base,
|
|
99
104
|
};
|
|
100
105
|
}
|
|
101
106
|
const result = resolvePolicy(projectRoot);
|
package/dist/cache/fetch.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { existsSync, readdirSync, readFileSync
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { ContainedWriteError, containedWrite } from "../fs/contained-write.js";
|
|
4
4
|
import { GhRestError, InvalidRepoError, restIssueView, runGhApi, } from "../scm/gh-rest.js";
|
|
5
5
|
import { DEFAULT_BATCH_SIZE, DEFAULT_DELAY_MS } from "./constants.js";
|
|
6
6
|
import { CacheError, CacheFetchError } from "./errors.js";
|
|
@@ -622,8 +622,13 @@ function readSelfHealState(cacheRoot) {
|
|
|
622
622
|
}
|
|
623
623
|
function writeSelfHealState(projectRoot, cacheRoot, when) {
|
|
624
624
|
const path = join(resolve(cacheRoot), SELF_HEAL_STATE_FILENAME);
|
|
625
|
-
|
|
626
|
-
|
|
625
|
+
// #2980 wave C: product write sink routes through containedWrite.
|
|
626
|
+
containedWrite({
|
|
627
|
+
root: resolve(projectRoot),
|
|
628
|
+
target: path,
|
|
629
|
+
data: `${JSON.stringify({ last_reconcile_at: when.toISOString() })}\n`,
|
|
630
|
+
mode: "replace",
|
|
631
|
+
});
|
|
627
632
|
}
|
|
628
633
|
/** TTL-bounded closed-reconcile for session ritual / triage:welcome self-healing (#1886). */
|
|
629
634
|
export function maybeSelfHealCache(projectRoot, options = {}) {
|
|
@@ -679,7 +684,7 @@ export function maybeSelfHealCache(projectRoot, options = {}) {
|
|
|
679
684
|
writeSelfHealState(projectRoot, cacheRoot, now);
|
|
680
685
|
}
|
|
681
686
|
catch (err) {
|
|
682
|
-
if (err instanceof
|
|
687
|
+
if (err instanceof ContainedWriteError) {
|
|
683
688
|
return { skipped: true, skipReason: "containment-refused", drift, refresh: null };
|
|
684
689
|
}
|
|
685
690
|
throw err;
|
package/dist/cache/io.d.ts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { tmpdir } from "node:os";
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* Write text via tempfile + rename (mirrors Python `_atomic_write_text`).
|
|
4
|
+
* #2951 Phase 2: temp payload write uses containedWrite under the parent dir.
|
|
5
|
+
*/
|
|
3
6
|
export declare function atomicWriteText(path: string, text: string): void;
|
|
4
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Append one JSON audit record (mirrors `_append_audit`).
|
|
9
|
+
* #2951 Phase 2: product write sink routes through containedWrite.
|
|
10
|
+
* Create cacheRoot first — containedWrite requires the containment root to exist.
|
|
11
|
+
*/
|
|
5
12
|
export declare function appendAudit(record: Record<string, unknown>, cacheRoot: string): void;
|
|
6
13
|
/** Touch mtime for LRU signal; failures swallowed. */
|
|
7
14
|
export declare function touchMtime(path: string): void;
|
package/dist/cache/io.js
CHANGED
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
|
-
import {
|
|
2
|
+
import { mkdirSync, renameSync, rmSync, statSync, utimesSync } from "node:fs";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
|
-
import { dirname, join } from "node:path";
|
|
4
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
5
|
+
import { containedWrite } from "../fs/contained-write.js";
|
|
5
6
|
import { pythonJsonLine } from "./json.js";
|
|
6
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Write text via tempfile + rename (mirrors Python `_atomic_write_text`).
|
|
9
|
+
* #2951 Phase 2: temp payload write uses containedWrite under the parent dir.
|
|
10
|
+
*/
|
|
7
11
|
export function atomicWriteText(path, text) {
|
|
8
|
-
|
|
9
|
-
|
|
12
|
+
const dir = dirname(path);
|
|
13
|
+
mkdirSync(dir, { recursive: true });
|
|
14
|
+
const tmpName = `${basename(path)}.${randomBytes(4).toString("hex")}.tmp`;
|
|
15
|
+
const tmp = join(dir, tmpName);
|
|
10
16
|
try {
|
|
11
|
-
|
|
17
|
+
containedWrite({
|
|
18
|
+
root: resolve(dir),
|
|
19
|
+
target: tmpName,
|
|
20
|
+
data: text,
|
|
21
|
+
mode: "create",
|
|
22
|
+
});
|
|
12
23
|
renameSync(tmp, path);
|
|
13
24
|
}
|
|
14
25
|
catch (err) {
|
|
@@ -21,11 +32,20 @@ export function atomicWriteText(path, text) {
|
|
|
21
32
|
throw err;
|
|
22
33
|
}
|
|
23
34
|
}
|
|
24
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* Append one JSON audit record (mirrors `_append_audit`).
|
|
37
|
+
* #2951 Phase 2: product write sink routes through containedWrite.
|
|
38
|
+
* Create cacheRoot first — containedWrite requires the containment root to exist.
|
|
39
|
+
*/
|
|
25
40
|
export function appendAudit(record, cacheRoot) {
|
|
26
|
-
const
|
|
27
|
-
mkdirSync(
|
|
28
|
-
|
|
41
|
+
const root = resolve(cacheRoot);
|
|
42
|
+
mkdirSync(root, { recursive: true });
|
|
43
|
+
containedWrite({
|
|
44
|
+
root,
|
|
45
|
+
target: "quarantine-audit.jsonl",
|
|
46
|
+
data: `${pythonJsonLine(record)}\n`,
|
|
47
|
+
mode: "append",
|
|
48
|
+
});
|
|
29
49
|
}
|
|
30
50
|
/** Touch mtime for LRU signal; failures swallowed. */
|
|
31
51
|
export function touchMtime(path) {
|
package/dist/cache/scanner.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Quarantine scanner v2 port (mirrors `scripts/cache_scanner.py`).
|
|
3
3
|
* SCANNER_VERSION must stay in lockstep with the Python module.
|
|
4
4
|
*/
|
|
5
|
-
export declare const SCANNER_VERSION = "2.
|
|
5
|
+
export declare const SCANNER_VERSION = "2.2.0";
|
|
6
6
|
export interface ScanFlag {
|
|
7
7
|
category: string;
|
|
8
8
|
severity: string;
|
|
@@ -21,6 +21,16 @@ export interface ScanResult {
|
|
|
21
21
|
* Scans the line once; byte-identical match semantics to the prior regex.
|
|
22
22
|
*/
|
|
23
23
|
export declare function lineHasShellVector(line: string): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Neutralize a body line so it cannot act as a CommonMark fence open/close.
|
|
26
|
+
* Used on content placed inside a ```quarantined wrapper (#2915 / cache-quarantine-03).
|
|
27
|
+
*
|
|
28
|
+
* A nested bare ``` (or longer run, optional ≤3-space indent) would otherwise
|
|
29
|
+
* early-close the outer fence and let following attacker text render outside
|
|
30
|
+
* the quarantined info-string. Break the delimiter run by inserting a backslash
|
|
31
|
+
* after the first fence character: ``` → `\`` , ~~~ → ~\~~ .
|
|
32
|
+
*/
|
|
33
|
+
export declare function neutralizeFenceLine(line: string): string;
|
|
24
34
|
/** Run scanner v2 over content markdown. */
|
|
25
35
|
export declare function scan(contentMd: string, scannedAt?: string): ScanResult;
|
|
26
36
|
/** Map scan flags for meta.json (omit match_count when zero). */
|
package/dist/cache/scanner.js
CHANGED
|
@@ -3,9 +3,12 @@ import { parseMarkdownHeading } from "../text/redos-safe.js";
|
|
|
3
3
|
* Quarantine scanner v2 port (mirrors `scripts/cache_scanner.py`).
|
|
4
4
|
* SCANNER_VERSION must stay in lockstep with the Python module.
|
|
5
5
|
*/
|
|
6
|
-
export const SCANNER_VERSION = "2.
|
|
6
|
+
export const SCANNER_VERSION = "2.2.0";
|
|
7
7
|
const CREDENTIAL_PATTERNS = [
|
|
8
8
|
["github-pat", /\bgh[pousr]_[A-Za-z0-9]{30,}\b/],
|
|
9
|
+
// #2910: fine-grained PATs (`github_pat_...`) — aligned with product-signal
|
|
10
|
+
// SECRET_PATTERNS so cache/ingest fail-closes on modern GitHub tokens.
|
|
11
|
+
["github-fine-grained-pat", /\bgithub_pat_[A-Za-z0-9_]{20,}\b/],
|
|
9
12
|
["anthropic-api-key", /\bsk-ant-[A-Za-z0-9_-]{20,}\b/],
|
|
10
13
|
["openai-api-key", /\bsk-[A-Za-z0-9]{20,}\b/],
|
|
11
14
|
["slack-token", /\bxox[bp]-[A-Za-z0-9-]{20,}\b/],
|
|
@@ -151,6 +154,26 @@ export function lineHasShellVector(line) {
|
|
|
151
154
|
const FENCE_RE = /^(```|~~~)/;
|
|
152
155
|
const QUARANTINE_FENCE_OPEN = "```quarantined";
|
|
153
156
|
const QUARANTINE_FENCE_CLOSE = "```";
|
|
157
|
+
/**
|
|
158
|
+
* Neutralize a body line so it cannot act as a CommonMark fence open/close.
|
|
159
|
+
* Used on content placed inside a ```quarantined wrapper (#2915 / cache-quarantine-03).
|
|
160
|
+
*
|
|
161
|
+
* A nested bare ``` (or longer run, optional ≤3-space indent) would otherwise
|
|
162
|
+
* early-close the outer fence and let following attacker text render outside
|
|
163
|
+
* the quarantined info-string. Break the delimiter run by inserting a backslash
|
|
164
|
+
* after the first fence character: ``` → `\`` , ~~~ → ~\~~ .
|
|
165
|
+
*/
|
|
166
|
+
export function neutralizeFenceLine(line) {
|
|
167
|
+
const match = /^( {0,3})(`{3,}|~{3,})(.*)$/.exec(line);
|
|
168
|
+
if (match === null)
|
|
169
|
+
return line;
|
|
170
|
+
const indent = match[1] ?? "";
|
|
171
|
+
const delim = match[2] ?? "";
|
|
172
|
+
const rest = match[3] ?? "";
|
|
173
|
+
// One backslash after the first fence char breaks the delimiter run.
|
|
174
|
+
// Concat (not a template literal) so "\\" is unambiguously a single "\".
|
|
175
|
+
return indent + delim[0] + "\\" + delim.slice(1) + rest;
|
|
176
|
+
}
|
|
154
177
|
function isInvisible(ch) {
|
|
155
178
|
const cp = ch.codePointAt(0);
|
|
156
179
|
if (cp === undefined)
|
|
@@ -277,7 +300,8 @@ function detectInjectionHeading(text) {
|
|
|
277
300
|
if (hSignal || bSignal) {
|
|
278
301
|
out.push(QUARANTINE_FENCE_OPEN);
|
|
279
302
|
for (let j = i; j < sectionEnd; j += 1) {
|
|
280
|
-
|
|
303
|
+
// #2915: neutralize nested fence lines so they cannot early-close.
|
|
304
|
+
out.push(neutralizeFenceLine(lines[j] ?? ""));
|
|
281
305
|
}
|
|
282
306
|
out.push(QUARANTINE_FENCE_CLOSE);
|
|
283
307
|
sectionsWrapped += 1;
|
|
@@ -292,7 +316,8 @@ function detectInjectionHeading(text) {
|
|
|
292
316
|
}
|
|
293
317
|
if (headingSignal(line) || lineHasShellVector(line)) {
|
|
294
318
|
out.push(QUARANTINE_FENCE_OPEN);
|
|
295
|
-
|
|
319
|
+
// #2915: neutralize in case the single line itself is fence-shaped.
|
|
320
|
+
out.push(neutralizeFenceLine(line));
|
|
296
321
|
out.push(QUARANTINE_FENCE_CLOSE);
|
|
297
322
|
sectionsWrapped += 1;
|
|
298
323
|
i += 1;
|
|
@@ -310,7 +335,7 @@ function detectInjectionHeading(text) {
|
|
|
310
335
|
{
|
|
311
336
|
category: "injection-heading",
|
|
312
337
|
severity: "fence-and-pass",
|
|
313
|
-
detail: `wrapped ${sectionsWrapped} injection-shaped section(s) in \`quarantined\` fence (v2.
|
|
338
|
+
detail: `wrapped ${sectionsWrapped} injection-shaped section(s) in \`quarantined\` fence (v2.2.0 strict-signal policy)`,
|
|
314
339
|
match_count: sectionsWrapped,
|
|
315
340
|
},
|
|
316
341
|
];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { existsSync,
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { existsSync, readFileSync, rmSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { containedWrite } from "../../fs/contained-write.js";
|
|
4
4
|
import { DEFAULT_TASK_CACHE_ROOT, TASK_CACHE_MANIFEST } from "./constants.js";
|
|
5
5
|
function entryDir(projectRoot, cacheKey, cacheRoot = DEFAULT_TASK_CACHE_ROOT) {
|
|
6
6
|
return join(resolve(projectRoot), cacheRoot, cacheKey.slice(0, 2), cacheKey);
|
|
@@ -22,12 +22,16 @@ export function writeCachedTaskRecord(projectRoot, cacheKey, record, cacheRoot =
|
|
|
22
22
|
if (record.exitCode !== 0) {
|
|
23
23
|
return;
|
|
24
24
|
}
|
|
25
|
+
const root = resolve(projectRoot);
|
|
25
26
|
const dir = entryDir(projectRoot, cacheKey, cacheRoot);
|
|
26
27
|
const manifest = join(dir, TASK_CACHE_MANIFEST);
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
// #2951 Phase 2: product write sink routes through containedWrite (mkdir + replace).
|
|
29
|
+
containedWrite({
|
|
30
|
+
root,
|
|
31
|
+
target: manifest,
|
|
32
|
+
data: `${JSON.stringify(record, null, 2)}\n`,
|
|
33
|
+
mode: "replace",
|
|
34
|
+
});
|
|
31
35
|
}
|
|
32
36
|
export function clearTaskCache(projectRoot, cacheRoot = DEFAULT_TASK_CACHE_ROOT) {
|
|
33
37
|
const root = join(resolve(projectRoot), cacheRoot);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
|
-
import { existsSync, readdirSync, readFileSync
|
|
2
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
4
|
import { referenceTypeMatches } from "@deftai/directive-types";
|
|
5
5
|
import { sortedStringifyCompact } from "../codebase/json.js";
|
|
6
|
-
import {
|
|
6
|
+
import { containedWrite } from "../fs/contained-write.js";
|
|
7
7
|
import { hasArtifactSuffix, resolveLifecycleFolder } from "../layout/resolve.js";
|
|
8
8
|
import { classifyBucket, loadBucketMatchers, resolveCapacityAllocation, SOURCE_MATCH, } from "../policy/capacity.js";
|
|
9
9
|
export const COMPLETED_FOLDER = "completed";
|
|
@@ -139,7 +139,7 @@ function inWindow(completedAt, windowDays, now) {
|
|
|
139
139
|
return ageDays >= 0 && ageDays <= windowDays;
|
|
140
140
|
}
|
|
141
141
|
function writeMetadata(projectRoot, path, data, plan, metadata, options) {
|
|
142
|
-
|
|
142
|
+
// #2980 wave D: product write sink routes through containedWrite.
|
|
143
143
|
if (typeof plan.metadata !== "object" || plan.metadata === null || Array.isArray(plan.metadata)) {
|
|
144
144
|
plan.metadata = metadata;
|
|
145
145
|
}
|
|
@@ -152,7 +152,13 @@ function writeMetadata(projectRoot, path, data, plan, metadata, options) {
|
|
|
152
152
|
metadata.capacityBucketSource = options.source;
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
|
-
|
|
155
|
+
const root = resolve(projectRoot);
|
|
156
|
+
containedWrite({
|
|
157
|
+
root,
|
|
158
|
+
target: path,
|
|
159
|
+
data: `${JSON.stringify(data, null, 2)}\n`,
|
|
160
|
+
mode: "replace",
|
|
161
|
+
});
|
|
156
162
|
}
|
|
157
163
|
/** Backfill capacityBucket / completedAt on completed vBRIEFs. */
|
|
158
164
|
export async function backfill(projectRoot, options = {}) {
|
|
@@ -9,9 +9,10 @@
|
|
|
9
9
|
* order-preserving (the namespaced key takes the legacy key's slot, keeping
|
|
10
10
|
* artifact diffs minimal).
|
|
11
11
|
*/
|
|
12
|
-
import { existsSync, lstatSync, readdirSync, readFileSync
|
|
12
|
+
import { existsSync, lstatSync, readdirSync, readFileSync } from "node:fs";
|
|
13
13
|
import { join, relative, resolve } from "node:path";
|
|
14
|
-
import {
|
|
14
|
+
import { containedWrite } from "../fs/contained-write.js";
|
|
15
|
+
import { assertDirectoryNotSymlink } from "../fs/projection-containment.js";
|
|
15
16
|
import { hasArtifactSuffix, resolveLifecycleRoot } from "../layout/resolve.js";
|
|
16
17
|
import { LEGACY_PLAN_COMPLETED_NOTE_KEY, LEGACY_PLAN_POLICY_KEY, PLAN_COMPLETED_NOTE_KEY, PLAN_POLICY_KEY, } from "../policy/plan-extensions.js";
|
|
17
18
|
/** The locked Category B renames (#1650): directive's own config, not xbrief data. */
|
|
@@ -141,7 +142,13 @@ export function migrateCategoryBCorpus(projectRoot) {
|
|
|
141
142
|
}
|
|
142
143
|
if (result.changed) {
|
|
143
144
|
try {
|
|
144
|
-
|
|
145
|
+
// #2980 wave D: product write sink routes through containedWrite.
|
|
146
|
+
containedWrite({
|
|
147
|
+
root,
|
|
148
|
+
target: file,
|
|
149
|
+
data: `${JSON.stringify(result.doc, null, 2)}\n`,
|
|
150
|
+
mode: "replace",
|
|
151
|
+
});
|
|
145
152
|
}
|
|
146
153
|
catch (err) {
|
|
147
154
|
conflicts.push({
|
|
@@ -150,7 +157,6 @@ export function migrateCategoryBCorpus(projectRoot) {
|
|
|
150
157
|
});
|
|
151
158
|
continue;
|
|
152
159
|
}
|
|
153
|
-
writeFileSync(file, `${JSON.stringify(result.doc, null, 2)}\n`, "utf8");
|
|
154
160
|
changed.push(relPath);
|
|
155
161
|
}
|
|
156
162
|
}
|
package/dist/check/gate-lists.js
CHANGED
|
@@ -20,6 +20,9 @@ export const SKIP_DIRS = new Set([
|
|
|
20
20
|
"build",
|
|
21
21
|
"dist",
|
|
22
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).
|
|
23
26
|
"swarm-worktrees",
|
|
24
27
|
]);
|
|
25
28
|
export const LANGUAGE_BY_SUFFIX = {
|
package/dist/codebase/map.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { existsSync,
|
|
2
|
-
import {
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
3
|
+
import { containedWrite } from "../fs/contained-write.js";
|
|
3
4
|
import { assertProjectionContained } from "../fs/projection-containment.js";
|
|
4
5
|
import { resolveProjectDefinitionPath } from "../layout/resolve.js";
|
|
5
6
|
import { extractCodeStructure, loadJsonFile } from "../verify-source/code-structure-validate.js";
|
|
@@ -249,8 +250,14 @@ export function writeCodebaseMap(projectRoot, options) {
|
|
|
249
250
|
artifactPath: options.artifactPath,
|
|
250
251
|
});
|
|
251
252
|
const document = renderCodebaseMap(selection.artifact);
|
|
252
|
-
|
|
253
|
-
|
|
253
|
+
// #2980 wave D: product write sink routes through containedWrite.
|
|
254
|
+
const root = resolve(projectRoot);
|
|
255
|
+
containedWrite({
|
|
256
|
+
root,
|
|
257
|
+
target: resolvedOutput,
|
|
258
|
+
data: document,
|
|
259
|
+
mode: "replace",
|
|
260
|
+
});
|
|
254
261
|
return resolvedOutput;
|
|
255
262
|
}
|
|
256
263
|
export function runCodebaseMapCli(argv) {
|
|
@@ -13,6 +13,16 @@ export declare function resolveRepoPath(relPath: string): string;
|
|
|
13
13
|
export declare function readRepoFile(relPath: string): string;
|
|
14
14
|
export declare function repoFileExists(relPath: string): boolean;
|
|
15
15
|
export declare function readSkill(relPath: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Progressive-disclosure surface for deft-directive-swarm (#2928).
|
|
18
|
+
* Thin SKILL.md is the dispatch card; operative depth lives under references/.
|
|
19
|
+
* Content contracts assert against this ordered join so host adapters can leave
|
|
20
|
+
* the always-loaded SKILL without dropping coverage.
|
|
21
|
+
*/
|
|
22
|
+
export declare const SWARM_SKILL_REL = "skills/deft-directive-swarm/SKILL.md";
|
|
23
|
+
/** Stable load order: core phases, then host launch adapters, then ops. */
|
|
24
|
+
export declare const SWARM_REFERENCE_ORDER: readonly ["core-phase-0.md", "core-phase-1-2.md", "core-phase-3.md", "host-warp.md", "host-generic.md", "host-grok-build.md", "host-cursor.md", "host-openclaw.md", "core-phase-4.md", "core-phase-5-6.md", "core-ops.md"];
|
|
25
|
+
export declare function readSwarmSkillSurface(): string;
|
|
16
26
|
export declare function readAgentsMd(): string;
|
|
17
27
|
/** Slice the first `## Returning Sessions` section body out of AGENTS.md. */
|
|
18
28
|
export declare function returningSessionsSection(): string;
|
|
@@ -32,6 +32,41 @@ export function repoFileExists(relPath) {
|
|
|
32
32
|
export function readSkill(relPath) {
|
|
33
33
|
return readRepoFile(relPath);
|
|
34
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Progressive-disclosure surface for deft-directive-swarm (#2928).
|
|
37
|
+
* Thin SKILL.md is the dispatch card; operative depth lives under references/.
|
|
38
|
+
* Content contracts assert against this ordered join so host adapters can leave
|
|
39
|
+
* the always-loaded SKILL without dropping coverage.
|
|
40
|
+
*/
|
|
41
|
+
export const SWARM_SKILL_REL = "skills/deft-directive-swarm/SKILL.md";
|
|
42
|
+
/** Stable load order: core phases, then host launch adapters, then ops. */
|
|
43
|
+
export const SWARM_REFERENCE_ORDER = [
|
|
44
|
+
"core-phase-0.md",
|
|
45
|
+
"core-phase-1-2.md",
|
|
46
|
+
"core-phase-3.md",
|
|
47
|
+
"host-warp.md",
|
|
48
|
+
"host-generic.md",
|
|
49
|
+
"host-grok-build.md",
|
|
50
|
+
"host-cursor.md",
|
|
51
|
+
"host-openclaw.md",
|
|
52
|
+
"core-phase-4.md",
|
|
53
|
+
"core-phase-5-6.md",
|
|
54
|
+
"core-ops.md",
|
|
55
|
+
];
|
|
56
|
+
export function readSwarmSkillSurface() {
|
|
57
|
+
const parts = [readRepoFile(SWARM_SKILL_REL)];
|
|
58
|
+
for (const name of SWARM_REFERENCE_ORDER) {
|
|
59
|
+
const rel = `skills/deft-directive-swarm/references/${name}`;
|
|
60
|
+
// Fail-loud: SWARM_REFERENCE_ORDER is the complete shipped surface (#2928).
|
|
61
|
+
// Silently skipping a missing reference lets incomplete packs pass contracts
|
|
62
|
+
// that never assert markers unique to the omitted file (Greptile on #2936).
|
|
63
|
+
if (!repoFileExists(rel)) {
|
|
64
|
+
throw new Error(`readSwarmSkillSurface: missing declared reference ${rel}`);
|
|
65
|
+
}
|
|
66
|
+
parts.push(readRepoFile(rel));
|
|
67
|
+
}
|
|
68
|
+
return parts.join("\n\n");
|
|
69
|
+
}
|
|
35
70
|
export function readAgentsMd() {
|
|
36
71
|
return readRepoFile("AGENTS.md");
|
|
37
72
|
}
|
|
@@ -5,12 +5,30 @@
|
|
|
5
5
|
* directories are created mode 0o755; files keep their source permission bits
|
|
6
6
|
* (including the executable bit for hooks and the `run` launcher).
|
|
7
7
|
*
|
|
8
|
-
*
|
|
8
|
+
* `replaceTree` is the npm-path counterpart of Go `swapInCore` (#2913): full
|
|
9
|
+
* destination replace so dst-only agent content cannot survive a refresh.
|
|
10
|
+
*
|
|
11
|
+
* Refs #1942 S1, #1477, #2913.
|
|
9
12
|
*/
|
|
10
13
|
/**
|
|
11
14
|
* Recursively copy `src` into `dst`, preserving nested structure and file modes.
|
|
12
15
|
*
|
|
13
16
|
* The contents of `src` are placed under `dst` (equivalent to Go `copyDir`).
|
|
17
|
+
* This is **additive** — pre-existing destination entries not present in `src`
|
|
18
|
+
* survive. Prefer {@link replaceTree} for deposit refresh integrity (#2913).
|
|
14
19
|
*/
|
|
15
20
|
export declare function copyTree(src: string, dst: string): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Full-tree replace of `dst` with the contents of `src` (Go `swapInCore` parity).
|
|
23
|
+
*
|
|
24
|
+
* Stages the new tree out-of-line, moves any existing `dst` aside, then moves
|
|
25
|
+
* the staged tree into place. On failure after the old tree was moved, restores
|
|
26
|
+
* the previous payload. Destination-only files (stale or malicious) do **not**
|
|
27
|
+
* survive — unlike additive {@link copyTree}.
|
|
28
|
+
*
|
|
29
|
+
* Refuses to operate when `dst` itself is a symlink (#2305 / #2912).
|
|
30
|
+
*
|
|
31
|
+
* Refs #2913, #2904 (install-deposit-06).
|
|
32
|
+
*/
|
|
33
|
+
export declare function replaceTree(src: string, dst: string): Promise<void>;
|
|
16
34
|
//# sourceMappingURL=copy-tree.d.ts.map
|
|
@@ -5,22 +5,27 @@
|
|
|
5
5
|
* directories are created mode 0o755; files keep their source permission bits
|
|
6
6
|
* (including the executable bit for hooks and the `run` launcher).
|
|
7
7
|
*
|
|
8
|
-
*
|
|
8
|
+
* `replaceTree` is the npm-path counterpart of Go `swapInCore` (#2913): full
|
|
9
|
+
* destination replace so dst-only agent content cannot survive a refresh.
|
|
10
|
+
*
|
|
11
|
+
* Refs #1942 S1, #1477, #2913.
|
|
9
12
|
*/
|
|
10
13
|
import { constants } from "node:fs";
|
|
11
|
-
import { lstat, mkdir, open, readdir, readFile, stat } from "node:fs/promises";
|
|
14
|
+
import { lstat, mkdir, mkdtemp, open, readdir, readFile, rename, rm, stat } from "node:fs/promises";
|
|
15
|
+
import { tmpdir } from "node:os";
|
|
12
16
|
import { dirname, join } from "node:path";
|
|
13
17
|
const DEFAULT_FILE_MODE = 0o644;
|
|
14
18
|
const DEFAULT_DIR_MODE = 0o755;
|
|
15
|
-
async function assertDestinationIsNotSymlink(path) {
|
|
19
|
+
async function assertDestinationIsNotSymlink(path, label = "copyTree") {
|
|
16
20
|
try {
|
|
17
21
|
const info = await lstat(path);
|
|
18
22
|
if (info.isSymbolicLink()) {
|
|
19
|
-
throw new Error(
|
|
23
|
+
throw new Error(`${label}: refusing to write through destination symlink ${path}`);
|
|
20
24
|
}
|
|
21
25
|
}
|
|
22
26
|
catch (err) {
|
|
23
|
-
if (err instanceof Error &&
|
|
27
|
+
if (err instanceof Error &&
|
|
28
|
+
err.message.includes("refusing to write through destination symlink")) {
|
|
24
29
|
throw err;
|
|
25
30
|
}
|
|
26
31
|
if (err.code === "ENOENT") {
|
|
@@ -77,6 +82,8 @@ async function copyDirContents(src, dst) {
|
|
|
77
82
|
* Recursively copy `src` into `dst`, preserving nested structure and file modes.
|
|
78
83
|
*
|
|
79
84
|
* The contents of `src` are placed under `dst` (equivalent to Go `copyDir`).
|
|
85
|
+
* This is **additive** — pre-existing destination entries not present in `src`
|
|
86
|
+
* survive. Prefer {@link replaceTree} for deposit refresh integrity (#2913).
|
|
80
87
|
*/
|
|
81
88
|
export async function copyTree(src, dst) {
|
|
82
89
|
const srcInfo = await stat(src);
|
|
@@ -85,4 +92,126 @@ export async function copyTree(src, dst) {
|
|
|
85
92
|
}
|
|
86
93
|
await copyDirContents(src, dst);
|
|
87
94
|
}
|
|
95
|
+
async function pathExists(path) {
|
|
96
|
+
try {
|
|
97
|
+
await lstat(path);
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
if (err.code === "ENOENT")
|
|
102
|
+
return false;
|
|
103
|
+
throw err;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Move `src` to `dst`, falling back to copy+remove across devices (EXDEV),
|
|
108
|
+
* mirroring Go `movePayload` used by `swapInCore`.
|
|
109
|
+
*/
|
|
110
|
+
async function moveTree(src, dst) {
|
|
111
|
+
await mkdir(dirname(dst), { recursive: true, mode: DEFAULT_DIR_MODE });
|
|
112
|
+
try {
|
|
113
|
+
await rename(src, dst);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
const code = err.code;
|
|
118
|
+
if (code !== "EXDEV" && code !== "EPERM") {
|
|
119
|
+
// On some platforms rename across volumes raises EXDEV; fall through only
|
|
120
|
+
// for cross-device / permission cases that still allow a copy fallback.
|
|
121
|
+
if (code !== "EINVAL")
|
|
122
|
+
throw err;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
await copyDirContents(src, dst);
|
|
126
|
+
await rm(src, { recursive: true, force: true });
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Full-tree replace of `dst` with the contents of `src` (Go `swapInCore` parity).
|
|
130
|
+
*
|
|
131
|
+
* Stages the new tree out-of-line, moves any existing `dst` aside, then moves
|
|
132
|
+
* the staged tree into place. On failure after the old tree was moved, restores
|
|
133
|
+
* the previous payload. Destination-only files (stale or malicious) do **not**
|
|
134
|
+
* survive — unlike additive {@link copyTree}.
|
|
135
|
+
*
|
|
136
|
+
* Refuses to operate when `dst` itself is a symlink (#2305 / #2912).
|
|
137
|
+
*
|
|
138
|
+
* Refs #2913, #2904 (install-deposit-06).
|
|
139
|
+
*/
|
|
140
|
+
export async function replaceTree(src, dst) {
|
|
141
|
+
const srcInfo = await stat(src);
|
|
142
|
+
if (!srcInfo.isDirectory()) {
|
|
143
|
+
throw new Error(`replaceTree: source ${src} is not a directory`);
|
|
144
|
+
}
|
|
145
|
+
await assertDestinationIsNotSymlink(dst, "replaceTree");
|
|
146
|
+
const parent = dirname(dst);
|
|
147
|
+
await mkdir(parent, { recursive: true, mode: DEFAULT_DIR_MODE });
|
|
148
|
+
const staging = await mkdtemp(join(tmpdir(), "deft-core-stage-"));
|
|
149
|
+
let backup = null;
|
|
150
|
+
/** When true, leave `backup` on disk so an operator can recover after dual failure. */
|
|
151
|
+
let preserveBackupOnExit = false;
|
|
152
|
+
try {
|
|
153
|
+
await copyDirContents(src, staging);
|
|
154
|
+
if (await pathExists(dst)) {
|
|
155
|
+
backup = await mkdtemp(join(tmpdir(), "deft-core-bak-"));
|
|
156
|
+
// mkdtemp created an empty dir; remove it so moveTree can rename onto the path.
|
|
157
|
+
await rm(backup, { recursive: true, force: true });
|
|
158
|
+
try {
|
|
159
|
+
await moveTree(dst, backup);
|
|
160
|
+
}
|
|
161
|
+
catch (asideErr) {
|
|
162
|
+
// Greptile P1: moveTree may fall back to copy+remove. If removal fails after a
|
|
163
|
+
// successful copy (or after a partial delete of dst), `backup` is the only full
|
|
164
|
+
// recovery copy while dst may already be damaged. Do NOT let `finally` delete it.
|
|
165
|
+
if (await pathExists(backup)) {
|
|
166
|
+
preserveBackupOnExit = true;
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
backup = null;
|
|
170
|
+
}
|
|
171
|
+
const asideMsg = asideErr instanceof Error ? asideErr.message : String(asideErr);
|
|
172
|
+
throw new Error(`replaceTree: failed to move existing destination aside (${asideMsg})` +
|
|
173
|
+
(backup ? ` — recovery copy at ${backup}` : ""));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
await moveTree(staging, dst);
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
if (backup !== null) {
|
|
181
|
+
try {
|
|
182
|
+
if (await pathExists(dst)) {
|
|
183
|
+
await rm(dst, { recursive: true, force: true });
|
|
184
|
+
}
|
|
185
|
+
await moveTree(backup, dst);
|
|
186
|
+
backup = null;
|
|
187
|
+
}
|
|
188
|
+
catch (restoreErr) {
|
|
189
|
+
preserveBackupOnExit = true;
|
|
190
|
+
const installMsg = err instanceof Error ? err.message : String(err);
|
|
191
|
+
const restoreMsg = restoreErr instanceof Error ? restoreErr.message : String(restoreErr);
|
|
192
|
+
throw new Error(`replaceTree: install new payload failed (${installMsg}); ROLLBACK ALSO FAILED (${restoreMsg})` +
|
|
193
|
+
(backup ? ` — previous payload preserved at ${backup}` : ""));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
throw err instanceof Error
|
|
197
|
+
? err
|
|
198
|
+
: new Error(`replaceTree: install new payload failed: ${String(err)}`);
|
|
199
|
+
}
|
|
200
|
+
// Successful install — best-effort drop the backup (Go keeps it for operator
|
|
201
|
+
// rollback; the npm path does not surface a backup path today and must not
|
|
202
|
+
// litter TEMP). Cleanup MUST NOT fail the replace: the new payload is already
|
|
203
|
+
// live at `dst`. A thrown rm here would reject replaceTree and skip the
|
|
204
|
+
// VERSION stamp in runRefreshDeposit → version drift (Greptile P1).
|
|
205
|
+
if (backup !== null) {
|
|
206
|
+
await rm(backup, { recursive: true, force: true }).catch(() => undefined);
|
|
207
|
+
backup = null;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
finally {
|
|
211
|
+
await rm(staging, { recursive: true, force: true }).catch(() => undefined);
|
|
212
|
+
if (backup !== null && !preserveBackupOnExit) {
|
|
213
|
+
await rm(backup, { recursive: true, force: true }).catch(() => undefined);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
88
217
|
//# sourceMappingURL=copy-tree.js.map
|