@deftai/directive-core 0.104.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 +526 -42
- package/dist/check/cached-orchestrator.d.ts +1 -1
- package/dist/check/cached-orchestrator.js +2 -2
- package/dist/check/named-cause.d.ts +1 -1
- package/dist/check/named-cause.js +33 -3
- 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 -19
- package/dist/doctor/types.d.ts +9 -0
- package/dist/fs/contained-write.d.ts +115 -1
- package/dist/fs/contained-write.js +241 -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 +1 -0
- package/dist/index.js +2 -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 +38 -1
- package/dist/intake/clause-derivation.js +211 -9
- package/dist/intake/issue-ingest.d.ts +3 -1
- package/dist/intake/issue-ingest.js +35 -6
- 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 +4 -0
- package/dist/preflight/evaluate.js +35 -1
- package/dist/preflight/index.d.ts +4 -0
- 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 +4 -1
- package/dist/product-first-done-gate/empty-resolution.js +12 -1
- package/dist/product-first-done-gate/evaluate.d.ts +12 -0
- package/dist/product-first-done-gate/evaluate.js +254 -31
- package/dist/product-first-done-gate/index.d.ts +2 -0
- package/dist/product-first-done-gate/index.js +1 -0
- package/dist/release/build-dist.d.ts +101 -0
- package/dist/release/build-dist.js +428 -46
- package/dist/run-summary/emit.d.ts +16 -10
- package/dist/run-summary/emit.js +26 -15
- 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/types.d.ts +29 -4
- package/dist/run-summary/types.js +1 -0
- package/dist/scope/acceptance-evidence.d.ts +19 -7
- package/dist/scope/acceptance-evidence.js +38 -11
- package/dist/scope/delivery-evidence.d.ts +3 -3
- package/dist/scope/delivery-evidence.js +22 -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 +1 -1
- 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/git.js +19 -4
- package/dist/session/index.d.ts +4 -0
- package/dist/session/index.js +4 -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 +71 -7
- 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 +9 -3
- package/dist/slice/lock.d.ts +16 -1
- package/dist/slice/lock.js +137 -11
- 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/fake-trial.js +10 -0
- package/dist/telemetry-coverage/kinds.js +2 -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 +1 -1
- package/dist/vbrief-reconcile/index.js +1 -1
- 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/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,20 @@
|
|
|
15
15
|
* onto this API over bespoke per-sink checks when equivalent. See
|
|
16
16
|
* `docs/reference/contained-write.md`.
|
|
17
17
|
*
|
|
18
|
-
*
|
|
18
|
+
* Delete coverage (#3392 / #3456): real dest deletes MUST go through
|
|
19
|
+
* {@link containedRemove} so a bound ledger (and ADR-004 record mode) records
|
|
20
|
+
* them. Raw `rmSync` on a dest path is a chokepoint bypass.
|
|
21
|
+
*
|
|
22
|
+
* Plan-mode zero-mutation (#3456 / ADR-004): `deft update --dry-run` binds
|
|
23
|
+
* this port in record mode (`runInPortRecordMode`). Dest write / remove /
|
|
24
|
+
* chmod / dest-mutating exec record and skip dest IO. Temp-file cleanups
|
|
25
|
+
* either use this chokepoint or live outside the project root.
|
|
26
|
+
*
|
|
27
|
+
* `tryCleanupLegacyDeftTree` is not on the update/dry-run path (classify
|
|
28
|
+
* refuses `migration-required` first). Its default removeDir still routes
|
|
29
|
+
* through containedRemove so the site is not a silent exception.
|
|
30
|
+
*
|
|
31
|
+
* Refs #2951 / #3392 / #3456.
|
|
19
32
|
*/
|
|
20
33
|
/** Stable machine-readable error codes for contained-write refusals. */
|
|
21
34
|
export declare const ContainedWriteErrorCode: {
|
|
@@ -78,6 +91,38 @@ export interface ContainedWriteInput {
|
|
|
78
91
|
* {@link assertWriteTargetSafe}.
|
|
79
92
|
*/
|
|
80
93
|
readonly followSymlinks?: boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Ledger side-effect (#3392). Default records `wrote` when a ledger is bound.
|
|
96
|
+
* `false` skips. `kind`/`path` override the recorded entry (atomic tmp+rename).
|
|
97
|
+
*/
|
|
98
|
+
readonly mutation?: false | {
|
|
99
|
+
readonly kind?: "wrote" | "stripped";
|
|
100
|
+
readonly path?: string;
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
export interface ContainedRemoveInput {
|
|
104
|
+
/** Absolute containment root (project root, deposit root, or sandbox). */
|
|
105
|
+
readonly root: string;
|
|
106
|
+
/**
|
|
107
|
+
* Remove target. Relative paths are resolved under `root`. Absolute paths
|
|
108
|
+
* must stay under `root` after normalization.
|
|
109
|
+
*/
|
|
110
|
+
readonly target: string;
|
|
111
|
+
/**
|
|
112
|
+
* Ledger side-effect (#3392). Default records `deleted` when a ledger is bound.
|
|
113
|
+
* `false` skips. `path` overrides the recorded path.
|
|
114
|
+
*/
|
|
115
|
+
readonly mutation?: false | {
|
|
116
|
+
readonly path?: string;
|
|
117
|
+
};
|
|
118
|
+
/** When true, remove directories recursively. Default `false`. */
|
|
119
|
+
readonly recursive?: boolean;
|
|
120
|
+
}
|
|
121
|
+
export interface ContainedRemoveResult {
|
|
122
|
+
/** Absolute path considered for removal. */
|
|
123
|
+
readonly path: string;
|
|
124
|
+
/** True when a file existed and was removed. */
|
|
125
|
+
readonly removed: boolean;
|
|
81
126
|
}
|
|
82
127
|
export interface ContainedWriteResult {
|
|
83
128
|
/** Absolute path written. */
|
|
@@ -86,6 +131,37 @@ export interface ContainedWriteResult {
|
|
|
86
131
|
readonly bytesWritten: number;
|
|
87
132
|
readonly mode: ContainedWriteMode;
|
|
88
133
|
}
|
|
134
|
+
export interface ContainedChmodInput {
|
|
135
|
+
readonly root: string;
|
|
136
|
+
readonly target: string;
|
|
137
|
+
readonly mode: number;
|
|
138
|
+
readonly mutation?: false | {
|
|
139
|
+
readonly path?: string;
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
export interface ContainedRenameInput {
|
|
143
|
+
readonly root: string;
|
|
144
|
+
readonly from: string;
|
|
145
|
+
readonly to: string;
|
|
146
|
+
/**
|
|
147
|
+
* Default records `wrote` on `to`. `false` skips (caller already ledgered).
|
|
148
|
+
*/
|
|
149
|
+
readonly mutation?: false | {
|
|
150
|
+
readonly kind?: "wrote" | "stripped";
|
|
151
|
+
readonly path?: string;
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
export interface ContainedDestExecInput {
|
|
155
|
+
readonly root: string;
|
|
156
|
+
/** Dest path this exec mutates (e.g. `.git/config`). */
|
|
157
|
+
readonly destTarget: string;
|
|
158
|
+
readonly file: string;
|
|
159
|
+
readonly args: readonly string[];
|
|
160
|
+
}
|
|
161
|
+
export interface ContainedDestExecResult {
|
|
162
|
+
readonly ok: boolean;
|
|
163
|
+
readonly stdout: string;
|
|
164
|
+
}
|
|
89
165
|
/**
|
|
90
166
|
* Resolve `target` under `root`. Relative targets join root; absolute targets
|
|
91
167
|
* must already be nested under root (path-segment containment).
|
|
@@ -98,4 +174,42 @@ export declare function resolveContainedTarget(root: string, target: string): st
|
|
|
98
174
|
* @throws {ContainedWriteError} on containment refusal or mode violation
|
|
99
175
|
*/
|
|
100
176
|
export declare function containedWrite(input: ContainedWriteInput): ContainedWriteResult;
|
|
177
|
+
/**
|
|
178
|
+
* Contained remove: resolve under root, refuse parent-path symlink / out-of-root,
|
|
179
|
+
* then delete. An in-root leaf symlink is unlinked (not followed, not refused).
|
|
180
|
+
* Missing targets are a no-op (not ledgered).
|
|
181
|
+
*
|
|
182
|
+
* @throws {ContainedWriteError} on containment refusal
|
|
183
|
+
*/
|
|
184
|
+
export declare function containedRemove(input: ContainedRemoveInput): ContainedRemoveResult;
|
|
185
|
+
/**
|
|
186
|
+
* Contained mkdir: nest under root. Record mode skips dest IO (dirs are not
|
|
187
|
+
* dest-file membership). Live path creates recursively.
|
|
188
|
+
*/
|
|
189
|
+
export declare function containedMkdir(input: {
|
|
190
|
+
readonly root: string;
|
|
191
|
+
readonly target: string;
|
|
192
|
+
}): {
|
|
193
|
+
path: string;
|
|
194
|
+
};
|
|
195
|
+
/**
|
|
196
|
+
* Contained chmod: resolve under root, refuse parent-path symlink / out-of-root,
|
|
197
|
+
* then set mode. Record mode records `chmod` and skips dest IO.
|
|
198
|
+
*/
|
|
199
|
+
export declare function containedChmod(input: ContainedChmodInput): {
|
|
200
|
+
path: string;
|
|
201
|
+
};
|
|
202
|
+
/**
|
|
203
|
+
* Contained rename: both paths must nest under root. Record mode records
|
|
204
|
+
* `wrote` on `to` (unless `mutation: false`) and skips dest IO.
|
|
205
|
+
*/
|
|
206
|
+
export declare function containedRename(input: ContainedRenameInput): {
|
|
207
|
+
from: string;
|
|
208
|
+
to: string;
|
|
209
|
+
};
|
|
210
|
+
/**
|
|
211
|
+
* Dest-mutating exec (git config, git add, …). Record mode records `exec` on
|
|
212
|
+
* `destTarget` and skips the child process. Allowlisted as the port impl.
|
|
213
|
+
*/
|
|
214
|
+
export declare function containedDestExec(input: ContainedDestExecInput): ContainedDestExecResult;
|
|
101
215
|
//# sourceMappingURL=contained-write.d.ts.map
|
|
@@ -15,10 +15,25 @@
|
|
|
15
15
|
* onto this API over bespoke per-sink checks when equivalent. See
|
|
16
16
|
* `docs/reference/contained-write.md`.
|
|
17
17
|
*
|
|
18
|
-
*
|
|
18
|
+
* Delete coverage (#3392 / #3456): real dest deletes MUST go through
|
|
19
|
+
* {@link containedRemove} so a bound ledger (and ADR-004 record mode) records
|
|
20
|
+
* them. Raw `rmSync` on a dest path is a chokepoint bypass.
|
|
21
|
+
*
|
|
22
|
+
* Plan-mode zero-mutation (#3456 / ADR-004): `deft update --dry-run` binds
|
|
23
|
+
* this port in record mode (`runInPortRecordMode`). Dest write / remove /
|
|
24
|
+
* chmod / dest-mutating exec record and skip dest IO. Temp-file cleanups
|
|
25
|
+
* either use this chokepoint or live outside the project root.
|
|
26
|
+
*
|
|
27
|
+
* `tryCleanupLegacyDeftTree` is not on the update/dry-run path (classify
|
|
28
|
+
* refuses `migration-required` first). Its default removeDir still routes
|
|
29
|
+
* through containedRemove so the site is not a silent exception.
|
|
30
|
+
*
|
|
31
|
+
* Refs #2951 / #3392 / #3456.
|
|
19
32
|
*/
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
33
|
+
import { execFileSync } from "node:child_process";
|
|
34
|
+
import { chmodSync, closeSync, constants, existsSync, fsyncSync, lstatSync, mkdirSync, openSync, realpathSync, renameSync, rmSync, unlinkSync, writeSync, } from "node:fs";
|
|
35
|
+
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
36
|
+
import { isAtomicWriteTemp, isPortRecordMode, recordActiveMutation, } from "./mutation-ledger.js";
|
|
22
37
|
import { assertWriteTargetSafe, ProjectionContainmentError } from "./projection-containment.js";
|
|
23
38
|
/** Stable machine-readable error codes for contained-write refusals. */
|
|
24
39
|
export const ContainedWriteErrorCode = {
|
|
@@ -133,6 +148,68 @@ function ensureParents(rootAbs, targetAbs) {
|
|
|
133
148
|
function toBuffer(data, encoding) {
|
|
134
149
|
return Buffer.isBuffer(data) ? data : Buffer.from(data, encoding);
|
|
135
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* Remove resolver: lexical nest + refuse parent-path symlinks. The leaf may be
|
|
153
|
+
* a symlink — unlink the link itself (never follow, never abort).
|
|
154
|
+
*/
|
|
155
|
+
function resolveExistingRootForRemove(root, target) {
|
|
156
|
+
const rootAbs = resolve(root);
|
|
157
|
+
try {
|
|
158
|
+
realpathSync(rootAbs);
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
throw new ContainedWriteError(`contained write refused: root ${rootAbs} does not exist`, {
|
|
162
|
+
code: ContainedWriteErrorCode.ROOT_MISSING,
|
|
163
|
+
root: rootAbs,
|
|
164
|
+
target: String(target),
|
|
165
|
+
offendingPath: rootAbs,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
const targetAbs = resolveContainedTarget(rootAbs, target);
|
|
169
|
+
const parentAbs = dirname(targetAbs);
|
|
170
|
+
if (parentAbs !== rootAbs && parentAbs !== targetAbs) {
|
|
171
|
+
const rel = relative(rootAbs, parentAbs);
|
|
172
|
+
if (rel.length > 0 && !rel.startsWith("..") && !isAbsolute(rel)) {
|
|
173
|
+
let current = rootAbs;
|
|
174
|
+
for (const segment of rel.split(/[\\/]+/).filter((part) => part.length > 0)) {
|
|
175
|
+
current = join(current, segment);
|
|
176
|
+
let info;
|
|
177
|
+
try {
|
|
178
|
+
info = lstatSync(current);
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
if (info.isSymbolicLink()) {
|
|
184
|
+
throw new ContainedWriteError(`contained write refused: ${current} is a symlink on the remove parent path`, {
|
|
185
|
+
code: ContainedWriteErrorCode.SYMLINK,
|
|
186
|
+
root: rootAbs,
|
|
187
|
+
target: targetAbs,
|
|
188
|
+
offendingPath: current,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return { rootAbs, targetAbs };
|
|
195
|
+
}
|
|
196
|
+
function recordWriteMutation(targetAbs, mutation) {
|
|
197
|
+
if (mutation === false)
|
|
198
|
+
return;
|
|
199
|
+
const kind = mutation?.kind ?? "wrote";
|
|
200
|
+
const path = mutation?.path ?? targetAbs;
|
|
201
|
+
if (mutation?.path === undefined && isAtomicWriteTemp(path))
|
|
202
|
+
return;
|
|
203
|
+
recordActiveMutation(kind, path);
|
|
204
|
+
}
|
|
205
|
+
function recordRemoveMutation(targetAbs, mutation) {
|
|
206
|
+
if (mutation === false)
|
|
207
|
+
return;
|
|
208
|
+
const path = mutation?.path ?? targetAbs;
|
|
209
|
+
if (mutation?.path === undefined && isAtomicWriteTemp(path))
|
|
210
|
+
return;
|
|
211
|
+
recordActiveMutation("deleted", path);
|
|
212
|
+
}
|
|
136
213
|
function writeNoFollow(targetAbs, buf, flags) {
|
|
137
214
|
const fd = openSync(targetAbs, flags, 0o644);
|
|
138
215
|
try {
|
|
@@ -194,6 +271,27 @@ export function containedWrite(input) {
|
|
|
194
271
|
}
|
|
195
272
|
throw err;
|
|
196
273
|
}
|
|
274
|
+
const encoding = input.encoding ?? "utf8";
|
|
275
|
+
const buf = toBuffer(input.data, encoding);
|
|
276
|
+
if (isPortRecordMode()) {
|
|
277
|
+
if (input.mode === "create") {
|
|
278
|
+
try {
|
|
279
|
+
lstatSync(targetAbs);
|
|
280
|
+
throw new ContainedWriteError(`contained write refused: target ${targetAbs} already exists (mode=create)`, {
|
|
281
|
+
code: ContainedWriteErrorCode.EXISTS,
|
|
282
|
+
root: rootAbs,
|
|
283
|
+
target: targetAbs,
|
|
284
|
+
offendingPath: targetAbs,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
catch (err) {
|
|
288
|
+
if (err instanceof ContainedWriteError)
|
|
289
|
+
throw err;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
recordWriteMutation(targetAbs, input.mutation);
|
|
293
|
+
return { path: targetAbs, bytesWritten: buf.length, mode: input.mode };
|
|
294
|
+
}
|
|
197
295
|
const doMkdir = input.mkdir !== false;
|
|
198
296
|
if (doMkdir) {
|
|
199
297
|
ensureParents(rootAbs, targetAbs);
|
|
@@ -208,8 +306,6 @@ export function containedWrite(input) {
|
|
|
208
306
|
}
|
|
209
307
|
throw err;
|
|
210
308
|
}
|
|
211
|
-
const encoding = input.encoding ?? "utf8";
|
|
212
|
-
const buf = toBuffer(input.data, encoding);
|
|
213
309
|
let exists = false;
|
|
214
310
|
try {
|
|
215
311
|
lstatSync(targetAbs);
|
|
@@ -239,6 +335,7 @@ export function containedWrite(input) {
|
|
|
239
335
|
// replace
|
|
240
336
|
bytesWritten = writeNoFollow(targetAbs, buf, constants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC | constants.O_NOFOLLOW);
|
|
241
337
|
}
|
|
338
|
+
recordWriteMutation(targetAbs, input.mutation);
|
|
242
339
|
return { path: targetAbs, bytesWritten, mode: input.mode };
|
|
243
340
|
}
|
|
244
341
|
catch (err) {
|
|
@@ -279,4 +376,143 @@ export function containedWrite(input) {
|
|
|
279
376
|
});
|
|
280
377
|
}
|
|
281
378
|
}
|
|
379
|
+
/**
|
|
380
|
+
* Contained remove: resolve under root, refuse parent-path symlink / out-of-root,
|
|
381
|
+
* then delete. An in-root leaf symlink is unlinked (not followed, not refused).
|
|
382
|
+
* Missing targets are a no-op (not ledgered).
|
|
383
|
+
*
|
|
384
|
+
* @throws {ContainedWriteError} on containment refusal
|
|
385
|
+
*/
|
|
386
|
+
export function containedRemove(input) {
|
|
387
|
+
const { rootAbs, targetAbs } = resolveExistingRootForRemove(input.root, input.target);
|
|
388
|
+
let info;
|
|
389
|
+
try {
|
|
390
|
+
info = lstatSync(targetAbs);
|
|
391
|
+
}
|
|
392
|
+
catch (err) {
|
|
393
|
+
const code = typeof err === "object" && err !== null && "code" in err
|
|
394
|
+
? err.code
|
|
395
|
+
: undefined;
|
|
396
|
+
if (code === "ENOENT") {
|
|
397
|
+
return { path: targetAbs, removed: false };
|
|
398
|
+
}
|
|
399
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
400
|
+
throw new ContainedWriteError(`contained write I/O failed: ${msg}`, {
|
|
401
|
+
code: ContainedWriteErrorCode.IO,
|
|
402
|
+
root: rootAbs,
|
|
403
|
+
target: targetAbs,
|
|
404
|
+
offendingPath: targetAbs,
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
if (isPortRecordMode()) {
|
|
408
|
+
recordRemoveMutation(targetAbs, input.mutation);
|
|
409
|
+
return { path: targetAbs, removed: true };
|
|
410
|
+
}
|
|
411
|
+
try {
|
|
412
|
+
if (info.isSymbolicLink()) {
|
|
413
|
+
unlinkSync(targetAbs);
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
rmSync(targetAbs, { force: true, recursive: input.recursive === true });
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
catch (err) {
|
|
420
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
421
|
+
throw new ContainedWriteError(`contained write I/O failed: ${msg}`, {
|
|
422
|
+
code: ContainedWriteErrorCode.IO,
|
|
423
|
+
root: rootAbs,
|
|
424
|
+
target: targetAbs,
|
|
425
|
+
offendingPath: targetAbs,
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
recordRemoveMutation(targetAbs, input.mutation);
|
|
429
|
+
return { path: targetAbs, removed: true };
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Contained mkdir: nest under root. Record mode skips dest IO (dirs are not
|
|
433
|
+
* dest-file membership). Live path creates recursively.
|
|
434
|
+
*/
|
|
435
|
+
export function containedMkdir(input) {
|
|
436
|
+
const targetAbs = resolveContainedTarget(input.root, input.target);
|
|
437
|
+
if (isPortRecordMode()) {
|
|
438
|
+
return { path: targetAbs };
|
|
439
|
+
}
|
|
440
|
+
mkdirSync(targetAbs, { recursive: true });
|
|
441
|
+
return { path: targetAbs };
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Contained chmod: resolve under root, refuse parent-path symlink / out-of-root,
|
|
445
|
+
* then set mode. Record mode records `chmod` and skips dest IO.
|
|
446
|
+
*/
|
|
447
|
+
export function containedChmod(input) {
|
|
448
|
+
const { rootAbs, targetAbs } = resolveExistingRootForRemove(input.root, input.target);
|
|
449
|
+
if (input.mutation !== false) {
|
|
450
|
+
recordActiveMutation("chmod", input.mutation?.path ?? targetAbs);
|
|
451
|
+
}
|
|
452
|
+
if (isPortRecordMode()) {
|
|
453
|
+
return { path: targetAbs };
|
|
454
|
+
}
|
|
455
|
+
try {
|
|
456
|
+
chmodSync(targetAbs, input.mode);
|
|
457
|
+
}
|
|
458
|
+
catch (err) {
|
|
459
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
460
|
+
throw new ContainedWriteError(`contained write I/O failed: ${msg}`, {
|
|
461
|
+
code: ContainedWriteErrorCode.IO,
|
|
462
|
+
root: rootAbs,
|
|
463
|
+
target: targetAbs,
|
|
464
|
+
offendingPath: targetAbs,
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
return { path: targetAbs };
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Contained rename: both paths must nest under root. Record mode records
|
|
471
|
+
* `wrote` on `to` (unless `mutation: false`) and skips dest IO.
|
|
472
|
+
*/
|
|
473
|
+
export function containedRename(input) {
|
|
474
|
+
const fromAbs = resolveContainedTarget(input.root, input.from);
|
|
475
|
+
const toAbs = resolveContainedTarget(input.root, input.to);
|
|
476
|
+
if (input.mutation !== false) {
|
|
477
|
+
const kind = input.mutation?.kind ?? "wrote";
|
|
478
|
+
recordActiveMutation(kind, input.mutation?.path ?? toAbs);
|
|
479
|
+
}
|
|
480
|
+
if (isPortRecordMode()) {
|
|
481
|
+
return { from: fromAbs, to: toAbs };
|
|
482
|
+
}
|
|
483
|
+
try {
|
|
484
|
+
renameSync(fromAbs, toAbs);
|
|
485
|
+
}
|
|
486
|
+
catch (err) {
|
|
487
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
488
|
+
throw new ContainedWriteError(`contained write I/O failed: ${msg}`, {
|
|
489
|
+
code: ContainedWriteErrorCode.IO,
|
|
490
|
+
root: resolve(input.root),
|
|
491
|
+
target: toAbs,
|
|
492
|
+
offendingPath: toAbs,
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
return { from: fromAbs, to: toAbs };
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Dest-mutating exec (git config, git add, …). Record mode records `exec` on
|
|
499
|
+
* `destTarget` and skips the child process. Allowlisted as the port impl.
|
|
500
|
+
*/
|
|
501
|
+
export function containedDestExec(input) {
|
|
502
|
+
const targetAbs = resolveContainedTarget(input.root, input.destTarget);
|
|
503
|
+
recordActiveMutation("exec", targetAbs);
|
|
504
|
+
if (isPortRecordMode()) {
|
|
505
|
+
return { ok: true, stdout: "" };
|
|
506
|
+
}
|
|
507
|
+
try {
|
|
508
|
+
const stdout = execFileSync(input.file, [...input.args], {
|
|
509
|
+
encoding: "utf8",
|
|
510
|
+
cwd: resolve(input.root),
|
|
511
|
+
});
|
|
512
|
+
return { ok: true, stdout };
|
|
513
|
+
}
|
|
514
|
+
catch {
|
|
515
|
+
return { ok: false, stdout: "" };
|
|
516
|
+
}
|
|
517
|
+
}
|
|
282
518
|
//# sourceMappingURL=contained-write.js.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mutation-ledger.ts — typed deposit mutation log at the write chokepoint (#3392).
|
|
3
|
+
*
|
|
4
|
+
* A bound ledger records wrote / stripped / deleted as a side effect of
|
|
5
|
+
* containedWrite / containedRemove. Callers cannot discard mutations by
|
|
6
|
+
* ignoring a writer return value.
|
|
7
|
+
*
|
|
8
|
+
* Refs #3392 / #3378.
|
|
9
|
+
*/
|
|
10
|
+
export declare const MUTATION_KINDS: readonly ["wrote", "stripped", "deleted", "chmod", "exec"];
|
|
11
|
+
export type MutationKind = (typeof MUTATION_KINDS)[number];
|
|
12
|
+
export interface MutationEntry {
|
|
13
|
+
readonly kind: MutationKind;
|
|
14
|
+
readonly path: string;
|
|
15
|
+
}
|
|
16
|
+
export interface MutationSummary {
|
|
17
|
+
readonly wrote: readonly string[];
|
|
18
|
+
readonly stripped: readonly string[];
|
|
19
|
+
readonly deleted: readonly string[];
|
|
20
|
+
readonly chmod: readonly string[];
|
|
21
|
+
readonly exec: readonly string[];
|
|
22
|
+
readonly mutations: readonly MutationEntry[];
|
|
23
|
+
}
|
|
24
|
+
export declare function isAtomicWriteTemp(path: string): boolean;
|
|
25
|
+
/** Posix-relative path under `root`, or empty when the target is not nested. */
|
|
26
|
+
export declare function toLedgerPath(root: string, target: string): string;
|
|
27
|
+
export declare function emptyMutationSummary(): MutationSummary;
|
|
28
|
+
/** In-memory ledger for one deposit/refresh run. */
|
|
29
|
+
export declare class MutationLedger {
|
|
30
|
+
readonly root: string;
|
|
31
|
+
private readonly recorded;
|
|
32
|
+
constructor(root: string);
|
|
33
|
+
record(kind: MutationKind, target: string): void;
|
|
34
|
+
entries(): readonly MutationEntry[];
|
|
35
|
+
summarize(): MutationSummary;
|
|
36
|
+
}
|
|
37
|
+
export declare function activeMutationLedger(): MutationLedger | undefined;
|
|
38
|
+
/** ADR-004: dest-mutating port calls record and skip dest IO. */
|
|
39
|
+
export declare function runInPortRecordMode<T>(fn: () => T): T;
|
|
40
|
+
export declare function isPortRecordMode(): boolean;
|
|
41
|
+
/** Bind a ledger for the duration of `fn` (including awaited continuations). */
|
|
42
|
+
export declare function runWithMutationLedger<T>(root: string, fn: () => T): T;
|
|
43
|
+
export declare function recordActiveMutation(kind: MutationKind, target: string): void;
|
|
44
|
+
export declare function snapshotMutationSummary(): MutationSummary;
|
|
45
|
+
/** Human summary: `Removed:` plus wrote/stripped. Empty when nothing mutated. */
|
|
46
|
+
export declare function formatMutationSummary(summary: MutationSummary): string;
|
|
47
|
+
export declare function mutationSummaryJson(summary: MutationSummary): {
|
|
48
|
+
wrote: string[];
|
|
49
|
+
stripped: string[];
|
|
50
|
+
deleted: string[];
|
|
51
|
+
chmod: string[];
|
|
52
|
+
exec: string[];
|
|
53
|
+
};
|
|
54
|
+
//# sourceMappingURL=mutation-ledger.d.ts.map
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mutation-ledger.ts — typed deposit mutation log at the write chokepoint (#3392).
|
|
3
|
+
*
|
|
4
|
+
* A bound ledger records wrote / stripped / deleted as a side effect of
|
|
5
|
+
* containedWrite / containedRemove. Callers cannot discard mutations by
|
|
6
|
+
* ignoring a writer return value.
|
|
7
|
+
*
|
|
8
|
+
* Refs #3392 / #3378.
|
|
9
|
+
*/
|
|
10
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
11
|
+
import { isAbsolute, relative, resolve } from "node:path";
|
|
12
|
+
export const MUTATION_KINDS = ["wrote", "stripped", "deleted", "chmod", "exec"];
|
|
13
|
+
/** Atomic-replace temps (`name.deft-<pid>.tmp`) are not logical mutations. */
|
|
14
|
+
const ATOMIC_TMP = /\.deft-\d+\.tmp$/u;
|
|
15
|
+
export function isAtomicWriteTemp(path) {
|
|
16
|
+
return ATOMIC_TMP.test(path.replace(/\\/g, "/"));
|
|
17
|
+
}
|
|
18
|
+
/** Posix-relative path under `root`, or empty when the target is not nested. */
|
|
19
|
+
export function toLedgerPath(root, target) {
|
|
20
|
+
const rootAbs = resolve(root);
|
|
21
|
+
const targetAbs = isAbsolute(target) ? resolve(target) : resolve(rootAbs, target);
|
|
22
|
+
const rel = relative(rootAbs, targetAbs);
|
|
23
|
+
if (rel.length === 0 || rel.startsWith("..") || isAbsolute(rel))
|
|
24
|
+
return "";
|
|
25
|
+
return rel.split("\\").join("/");
|
|
26
|
+
}
|
|
27
|
+
export function emptyMutationSummary() {
|
|
28
|
+
return { wrote: [], stripped: [], deleted: [], chmod: [], exec: [], mutations: [] };
|
|
29
|
+
}
|
|
30
|
+
function summarizeEntries(entries) {
|
|
31
|
+
const latest = new Map();
|
|
32
|
+
const order = [];
|
|
33
|
+
for (const entry of entries) {
|
|
34
|
+
if (!latest.has(entry.path))
|
|
35
|
+
order.push(entry.path);
|
|
36
|
+
latest.set(entry.path, entry.kind);
|
|
37
|
+
}
|
|
38
|
+
const wrote = [];
|
|
39
|
+
const stripped = [];
|
|
40
|
+
const deleted = [];
|
|
41
|
+
const chmod = [];
|
|
42
|
+
const exec = [];
|
|
43
|
+
const mutations = [];
|
|
44
|
+
for (const path of order) {
|
|
45
|
+
const kind = latest.get(path);
|
|
46
|
+
if (kind === undefined)
|
|
47
|
+
continue;
|
|
48
|
+
mutations.push({ kind, path });
|
|
49
|
+
if (kind === "wrote")
|
|
50
|
+
wrote.push(path);
|
|
51
|
+
else if (kind === "stripped")
|
|
52
|
+
stripped.push(path);
|
|
53
|
+
else if (kind === "chmod")
|
|
54
|
+
chmod.push(path);
|
|
55
|
+
else if (kind === "exec")
|
|
56
|
+
exec.push(path);
|
|
57
|
+
else
|
|
58
|
+
deleted.push(path);
|
|
59
|
+
}
|
|
60
|
+
return { wrote, stripped, deleted, chmod, exec, mutations };
|
|
61
|
+
}
|
|
62
|
+
/** In-memory ledger for one deposit/refresh run. */
|
|
63
|
+
export class MutationLedger {
|
|
64
|
+
root;
|
|
65
|
+
recorded = [];
|
|
66
|
+
constructor(root) {
|
|
67
|
+
this.root = resolve(root);
|
|
68
|
+
}
|
|
69
|
+
record(kind, target) {
|
|
70
|
+
const path = toLedgerPath(this.root, target);
|
|
71
|
+
if (path.length === 0)
|
|
72
|
+
return;
|
|
73
|
+
this.recorded.push({ kind, path });
|
|
74
|
+
}
|
|
75
|
+
entries() {
|
|
76
|
+
return this.recorded;
|
|
77
|
+
}
|
|
78
|
+
summarize() {
|
|
79
|
+
return summarizeEntries(this.recorded);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const storage = new AsyncLocalStorage();
|
|
83
|
+
const recordModeStorage = new AsyncLocalStorage();
|
|
84
|
+
export function activeMutationLedger() {
|
|
85
|
+
return storage.getStore();
|
|
86
|
+
}
|
|
87
|
+
/** ADR-004: dest-mutating port calls record and skip dest IO. */
|
|
88
|
+
export function runInPortRecordMode(fn) {
|
|
89
|
+
return recordModeStorage.run(true, fn);
|
|
90
|
+
}
|
|
91
|
+
export function isPortRecordMode() {
|
|
92
|
+
return recordModeStorage.getStore() === true;
|
|
93
|
+
}
|
|
94
|
+
/** Bind a ledger for the duration of `fn` (including awaited continuations). */
|
|
95
|
+
export function runWithMutationLedger(root, fn) {
|
|
96
|
+
return storage.run(new MutationLedger(root), fn);
|
|
97
|
+
}
|
|
98
|
+
export function recordActiveMutation(kind, target) {
|
|
99
|
+
activeMutationLedger()?.record(kind, target);
|
|
100
|
+
}
|
|
101
|
+
export function snapshotMutationSummary() {
|
|
102
|
+
return activeMutationLedger()?.summarize() ?? emptyMutationSummary();
|
|
103
|
+
}
|
|
104
|
+
/** Human summary: `Removed:` plus wrote/stripped. Empty when nothing mutated. */
|
|
105
|
+
export function formatMutationSummary(summary) {
|
|
106
|
+
const lines = [];
|
|
107
|
+
if (summary.deleted.length > 0) {
|
|
108
|
+
lines.push(`Removed: ${summary.deleted.join(", ")}`);
|
|
109
|
+
}
|
|
110
|
+
if (summary.wrote.length > 0) {
|
|
111
|
+
lines.push(`wrote: ${summary.wrote.join(", ")}`);
|
|
112
|
+
}
|
|
113
|
+
if (summary.stripped.length > 0) {
|
|
114
|
+
lines.push(`stripped: ${summary.stripped.join(", ")}`);
|
|
115
|
+
}
|
|
116
|
+
if (summary.chmod.length > 0) {
|
|
117
|
+
lines.push(`chmod: ${summary.chmod.join(", ")}`);
|
|
118
|
+
}
|
|
119
|
+
if (summary.exec.length > 0) {
|
|
120
|
+
lines.push(`exec: ${summary.exec.join(", ")}`);
|
|
121
|
+
}
|
|
122
|
+
return lines.length > 0 ? `${lines.join("\n")}\n` : "";
|
|
123
|
+
}
|
|
124
|
+
export function mutationSummaryJson(summary) {
|
|
125
|
+
return {
|
|
126
|
+
wrote: [...summary.wrote],
|
|
127
|
+
stripped: [...summary.stripped],
|
|
128
|
+
deleted: [...summary.deleted],
|
|
129
|
+
chmod: [...summary.chmod],
|
|
130
|
+
exec: [...summary.exec],
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=mutation-ledger.js.map
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* non-product-dirs.ts — one source of truth for the "not product source"
|
|
3
|
+
* directory basenames shared by every Directive filesystem walk (#3487).
|
|
4
|
+
*
|
|
5
|
+
* Directive shipped four independently hand-maintained exclusion sets:
|
|
6
|
+
*
|
|
7
|
+
* - `EXCLUDE_DIRS` — `verify-source/verify-stubs.ts`
|
|
8
|
+
* - `EXCLUDE_DIRS` — `validate-content/validate-links.ts`
|
|
9
|
+
* - `SKIP_DIRS` — `codebase/default-extractor.ts`
|
|
10
|
+
* - `DEFAULT_EXCLUDES` — `release/build-dist.ts`
|
|
11
|
+
*
|
|
12
|
+
* They drifted from each other, which is exactly how #2953 added
|
|
13
|
+
* `.deft-scratch` / `swarm-worktrees` to some walks and missed the
|
|
14
|
+
* `verify-stubs` walk (#3481), and how none of them ever learned about agent
|
|
15
|
+
* working directories even though Directive ships first-class support for
|
|
16
|
+
* several agent hosts. Because `@deftai/directive-core` is published, a
|
|
17
|
+
* consumer cannot patch any of the four.
|
|
18
|
+
*
|
|
19
|
+
* `NON_PRODUCT_DIRS` is the common core each walk now extends. It carries only
|
|
20
|
+
* basenames that are never product source in any repo: VCS metadata,
|
|
21
|
+
* dependency trees, build output, tool caches, and agent working directories.
|
|
22
|
+
* Walk-specific entries (`tests`, `specs`, `.planning`, `htmlcov`, …) stay with
|
|
23
|
+
* the walk that needs them — this constant is a floor, not a ceiling.
|
|
24
|
+
*
|
|
25
|
+
* Adding a directory here is a one-line change all four walks pick up, and
|
|
26
|
+
* `non-product-dirs.test.ts` fails if any of them stops agreeing.
|
|
27
|
+
*
|
|
28
|
+
* Refs #3487, #3481, #2953, #2954, #1656.
|
|
29
|
+
*/
|
|
30
|
+
/**
|
|
31
|
+
* Swarm / agent scratch worktree roots (#2953, #1656).
|
|
32
|
+
*
|
|
33
|
+
* `.deft-scratch` is the current layout (`.deft-scratch/worktrees/<story-id>`,
|
|
34
|
+
* built by `swarm/launch.ts`); `swarm-worktrees` is the pre-`.deft-scratch`
|
|
35
|
+
* name, kept for repos that still carry one.
|
|
36
|
+
*/
|
|
37
|
+
export declare const AGENT_SCRATCH_DIRS: readonly string[];
|
|
38
|
+
/**
|
|
39
|
+
* Agent-host directories that hold host *working* state rather than product
|
|
40
|
+
* source (#3487).
|
|
41
|
+
*
|
|
42
|
+
* Only names with evidence of a real working directory belong here. Directive
|
|
43
|
+
* knows several host directories (`.claude`, `.cursor`, `.grok`, `.codex`,
|
|
44
|
+
* `.github/skills`, `.agents`), but most of them hold nothing except
|
|
45
|
+
* Directive's own committed deposits — `commands.md` § Native multi-host
|
|
46
|
+
* registration (LockedDecision L8) says to *commit* those, so blanket-excluding
|
|
47
|
+
* them would hide committed team surface for no measured gain.
|
|
48
|
+
*
|
|
49
|
+
* `.claude` is different in kind: the Claude Code host keeps live session state
|
|
50
|
+
* in the same directory it keeps config — `settings.local.json`, and agent
|
|
51
|
+
* worktrees under `.claude/worktrees/<agent-id>`, each a full nested checkout.
|
|
52
|
+
* Three such worktrees added ~11,000 walkable entries to this repo
|
|
53
|
+
* (9,128 → 20,393) that no Directive walk skipped. Its Directive-managed
|
|
54
|
+
* contents (`.claude/settings.json`, `.claude/commands/*.md`,
|
|
55
|
+
* `.claude/skills/`) are deposits generated from `content/`, which every walk
|
|
56
|
+
* still visits at the authoring source.
|
|
57
|
+
*
|
|
58
|
+
* Deliberately **not** listed:
|
|
59
|
+
* - `.openclaw` — there is no project-level `.openclaw/`; the host state dir
|
|
60
|
+
* is `~/.openclaw` in `$HOME` (see `doctor/openclaw-skills.ts`), and
|
|
61
|
+
* inventing a project one is explicitly forbidden by
|
|
62
|
+
* `content/docs/openclaw-agent-host.md` and asserted against in
|
|
63
|
+
* `slash/openclaw-adapter.test.ts`.
|
|
64
|
+
* - `.cursor`, `.grok`, `.codex`, `.github` — committed Directive deposits
|
|
65
|
+
* (`.cursor/hooks.json`, `.grok/commands/`, `.codex/prompts/`,
|
|
66
|
+
* `.github/skills/`) with no known working/worktree subtree. No evidence
|
|
67
|
+
* of a scratch convention was found for any of them.
|
|
68
|
+
*/
|
|
69
|
+
export declare const AGENT_HOST_WORKING_DIRS: readonly string[];
|
|
70
|
+
/**
|
|
71
|
+
* Directory basenames no Directive walk should descend into.
|
|
72
|
+
*
|
|
73
|
+
* Every walk extends this set; none may shrink it. Keep entries to basenames
|
|
74
|
+
* that cannot be product source in any repo — a name that is merely *usually*
|
|
75
|
+
* uninteresting to one gate (`tests`, `scripts`, `specs`) belongs to that
|
|
76
|
+
* walk's own set, not here.
|
|
77
|
+
*/
|
|
78
|
+
export declare const NON_PRODUCT_DIRS: ReadonlySet<string>;
|
|
79
|
+
//# sourceMappingURL=non-product-dirs.d.ts.map
|