@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,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 {
|
|
@@ -144,6 +221,7 @@ function writeNoFollow(targetAbs, buf, flags) {
|
|
|
144
221
|
}
|
|
145
222
|
offset += n;
|
|
146
223
|
}
|
|
224
|
+
fsyncSync(fd);
|
|
147
225
|
return offset;
|
|
148
226
|
}
|
|
149
227
|
finally {
|
|
@@ -193,6 +271,27 @@ export function containedWrite(input) {
|
|
|
193
271
|
}
|
|
194
272
|
throw err;
|
|
195
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
|
+
}
|
|
196
295
|
const doMkdir = input.mkdir !== false;
|
|
197
296
|
if (doMkdir) {
|
|
198
297
|
ensureParents(rootAbs, targetAbs);
|
|
@@ -207,8 +306,6 @@ export function containedWrite(input) {
|
|
|
207
306
|
}
|
|
208
307
|
throw err;
|
|
209
308
|
}
|
|
210
|
-
const encoding = input.encoding ?? "utf8";
|
|
211
|
-
const buf = toBuffer(input.data, encoding);
|
|
212
309
|
let exists = false;
|
|
213
310
|
try {
|
|
214
311
|
lstatSync(targetAbs);
|
|
@@ -238,6 +335,7 @@ export function containedWrite(input) {
|
|
|
238
335
|
// replace
|
|
239
336
|
bytesWritten = writeNoFollow(targetAbs, buf, constants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC | constants.O_NOFOLLOW);
|
|
240
337
|
}
|
|
338
|
+
recordWriteMutation(targetAbs, input.mutation);
|
|
241
339
|
return { path: targetAbs, bytesWritten, mode: input.mode };
|
|
242
340
|
}
|
|
243
341
|
catch (err) {
|
|
@@ -278,4 +376,143 @@ export function containedWrite(input) {
|
|
|
278
376
|
});
|
|
279
377
|
}
|
|
280
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
|
+
}
|
|
281
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
|
|
@@ -0,0 +1,96 @@
|
|
|
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 const AGENT_SCRATCH_DIRS = [".deft-scratch", "swarm-worktrees"];
|
|
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 const AGENT_HOST_WORKING_DIRS = [".claude"];
|
|
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 const NON_PRODUCT_DIRS = new Set([
|
|
79
|
+
// Version-control metadata.
|
|
80
|
+
".git",
|
|
81
|
+
// Dependency trees and language virtualenvs.
|
|
82
|
+
"node_modules",
|
|
83
|
+
".venv",
|
|
84
|
+
// Build output and operator backups.
|
|
85
|
+
"dist",
|
|
86
|
+
"backup",
|
|
87
|
+
// Tool caches.
|
|
88
|
+
"__pycache__",
|
|
89
|
+
".mypy_cache",
|
|
90
|
+
".pytest_cache",
|
|
91
|
+
".ruff_cache",
|
|
92
|
+
// Agent working directories.
|
|
93
|
+
...AGENT_SCRATCH_DIRS,
|
|
94
|
+
...AGENT_HOST_WORKING_DIRS,
|
|
95
|
+
]);
|
|
96
|
+
//# sourceMappingURL=non-product-dirs.js.map
|
|
@@ -21,7 +21,9 @@ export type HookDecisionCode = "session-start" | "session-start-disabled" | "ses
|
|
|
21
21
|
/** Enforcement skipped: root `.deft-directive-disable` test kill-switch (#3039). */
|
|
22
22
|
| "directive-disabled" | "session-compact-rearm" | "session-compact-rearm-degraded" | "session-compact-noop" | "not-direct-write" | "invalid-input"
|
|
23
23
|
/** Host closed stdin with zero bytes — integration failure, not a policy gate (#2864). */
|
|
24
|
-
| "stdin-empty" | "ritual-not-ready"
|
|
24
|
+
| "stdin-empty" | "ritual-not-ready"
|
|
25
|
+
/** Product-path write while another live session occupies this worktree (#3433). */
|
|
26
|
+
| "occupancy-occupied" | "scope-not-ready" | "write-propose-ready"
|
|
25
27
|
/** Allowlisted assist/scratch write without active xBRIEF (#1802). */
|
|
26
28
|
| "write-assist-scratch-ready" | "write-ready" | "read-only-deny" | "spawn-explore-ready"
|
|
27
29
|
/** Non-lifecycle assist/docs spawn allowed without active xBRIEF (#3080). */
|