@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
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import { detectNoDeftDirective } from "../policy/no-deft-directive.js";
|
|
1
2
|
import { writeSentinel } from "./ritual-sentinel.js";
|
|
2
3
|
export interface SessionStartHookOptions {
|
|
3
4
|
readonly resolveVersionFn?: () => string;
|
|
4
5
|
readonly detectBranchFn?: (projectRoot: string) => string | null;
|
|
5
6
|
readonly detectLatestActiveVbriefFn?: (projectRoot: string) => string | null;
|
|
6
7
|
readonly writeSentinelFn?: typeof writeSentinel;
|
|
8
|
+
/** Test seam for #2926 opt-out detection. */
|
|
9
|
+
readonly detectNoDeftDirectiveFn?: typeof detectNoDeftDirective;
|
|
7
10
|
}
|
|
8
11
|
/** Write ``.deft/last-session.json`` from current git state (#1269). */
|
|
9
12
|
export declare function runSessionStartHookWrite(projectRoot: string, options?: SessionStartHookOptions): {
|
|
@@ -1,8 +1,23 @@
|
|
|
1
1
|
import { resolveVersion } from "../doctor/paths.js";
|
|
2
|
+
import { detectNoDeftDirective, NO_DEFT_DIRECTIVE_DISABLED_MESSAGE, NO_DEFT_DIRECTIVE_INCONSISTENT_MESSAGE, } from "../policy/no-deft-directive.js";
|
|
2
3
|
import { detectBranch } from "./git.js";
|
|
3
4
|
import { detectLatestActiveVbrief, writeSentinel } from "./ritual-sentinel.js";
|
|
4
5
|
/** Write ``.deft/last-session.json`` from current git state (#1269). */
|
|
5
6
|
export function runSessionStartHookWrite(projectRoot, options = {}) {
|
|
7
|
+
const detectOptOut = options.detectNoDeftDirectiveFn ?? detectNoDeftDirective;
|
|
8
|
+
// #2926: root opt-out wins — host SessionStart must not write ritual bookkeeping.
|
|
9
|
+
const optOut = detectOptOut(projectRoot);
|
|
10
|
+
if (optOut.present) {
|
|
11
|
+
const lines = [NO_DEFT_DIRECTIVE_DISABLED_MESSAGE];
|
|
12
|
+
if (optOut.inconsistent) {
|
|
13
|
+
lines.push(NO_DEFT_DIRECTIVE_INCONSISTENT_MESSAGE);
|
|
14
|
+
}
|
|
15
|
+
return {
|
|
16
|
+
code: optOut.inconsistent ? 1 : 0,
|
|
17
|
+
stdout: `${lines.join("\n")}\n`,
|
|
18
|
+
stderr: optOut.inconsistent ? `${NO_DEFT_DIRECTIVE_INCONSISTENT_MESSAGE}\n` : "",
|
|
19
|
+
};
|
|
20
|
+
}
|
|
6
21
|
const detectBranchFn = options.detectBranchFn ?? detectBranch;
|
|
7
22
|
const detectVbriefFn = options.detectLatestActiveVbriefFn ?? detectLatestActiveVbrief;
|
|
8
23
|
const resolveVersionFn = options.resolveVersionFn ?? resolveVersion;
|
|
@@ -4,6 +4,8 @@ import { emitSessionEvalReadback } from "../eval/readback.js";
|
|
|
4
4
|
import { MIGRATE_COMPLETION_NUDGE, shouldEmitMigrateNudge } from "../init-deposit/migrate.js";
|
|
5
5
|
import { detectEnvironmentContext, environmentContextToDict, formatEnvironmentContext, } from "../platform/shell-context.js";
|
|
6
6
|
import { disclosureLine } from "../policy/disclosure.js";
|
|
7
|
+
import { detectNoDeftDirective, NO_DEFT_DIRECTIVE_DISABLED_MESSAGE, NO_DEFT_DIRECTIVE_FLAG_NAME, NO_DEFT_DIRECTIVE_INCONSISTENT_MESSAGE, NO_DEFT_DIRECTIVE_INCONSISTENT_POLICY, } from "../policy/no-deft-directive.js";
|
|
8
|
+
import { humanMergeDisclosureLine, resolveHumanMergePolicy, } from "../policy/require-human-merge.js";
|
|
7
9
|
import { resolvePolicy } from "../policy/resolve.js";
|
|
8
10
|
import { maybeFormatProductSignalConsentPrompt } from "../product-signal/consent-prompt.js";
|
|
9
11
|
import { maybeRunStalenessTickler } from "../staleness-tickler/run.js";
|
|
@@ -233,6 +235,35 @@ export function runSessionStart(projectRoot, options = {}) {
|
|
|
233
235
|
const deferrals = options.deferrals ?? {};
|
|
234
236
|
const runGit = options.runGit ?? defaultGitRunner;
|
|
235
237
|
const environment = (options.probeEnvironment ?? detectEnvironmentContext)();
|
|
238
|
+
// #2926: official root opt-out wins locally — skip Directive session ritual.
|
|
239
|
+
// disabled = skip ritual (exit 0 clean / 1 inconsistent). ready stays false so
|
|
240
|
+
// automation does not treat opt-out as "session fully initialized for work".
|
|
241
|
+
const optOut = detectNoDeftDirective(projectRoot);
|
|
242
|
+
if (optOut.present) {
|
|
243
|
+
const lines = [NO_DEFT_DIRECTIVE_DISABLED_MESSAGE];
|
|
244
|
+
if (optOut.inconsistent) {
|
|
245
|
+
lines.push(NO_DEFT_DIRECTIVE_INCONSISTENT_MESSAGE);
|
|
246
|
+
}
|
|
247
|
+
const code = optOut.inconsistent ? 1 : 0;
|
|
248
|
+
return {
|
|
249
|
+
code,
|
|
250
|
+
payload: {
|
|
251
|
+
ready: false,
|
|
252
|
+
exit_code: code,
|
|
253
|
+
disabled: true,
|
|
254
|
+
disabled_via: NO_DEFT_DIRECTIVE_FLAG_NAME,
|
|
255
|
+
inconsistent: optOut.inconsistent,
|
|
256
|
+
inconsistent_policy: optOut.inconsistent
|
|
257
|
+
? NO_DEFT_DIRECTIVE_INCONSISTENT_POLICY
|
|
258
|
+
: undefined,
|
|
259
|
+
deposit_present: optOut.depositPresent,
|
|
260
|
+
posture,
|
|
261
|
+
environment: environmentContextToDict(environment),
|
|
262
|
+
message: NO_DEFT_DIRECTIVE_DISABLED_MESSAGE,
|
|
263
|
+
},
|
|
264
|
+
lines,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
236
267
|
if (posture === READ_ONLY_POSTURE) {
|
|
237
268
|
return runReadOnlySessionStart(projectRoot, options, instant, environment);
|
|
238
269
|
}
|
|
@@ -289,6 +320,12 @@ export function runSessionStart(projectRoot, options = {}) {
|
|
|
289
320
|
exitCode: ok ? 0 : 2,
|
|
290
321
|
});
|
|
291
322
|
lines.push(message);
|
|
323
|
+
// Human merge gate disclosure (#1193) — surface when ON (or env bypass active).
|
|
324
|
+
const humanMerge = resolveHumanMergePolicy(projectRoot);
|
|
325
|
+
const humanMergeLine = humanMergeDisclosureLine(humanMerge);
|
|
326
|
+
if (humanMergeLine !== null) {
|
|
327
|
+
lines.push(humanMergeLine);
|
|
328
|
+
}
|
|
292
329
|
const branchSync = defaultBranchSync(projectRoot, runGit);
|
|
293
330
|
if (branchSync.warning) {
|
|
294
331
|
lines.push(branchSync.warning);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { mkdirSync, readFileSync, renameSync,
|
|
2
|
-
import { dirname, join } from "node:path";
|
|
1
|
+
import { mkdirSync, readFileSync, renameSync, rmSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
3
|
+
import { containedWrite } from "../fs/contained-write.js";
|
|
3
4
|
import { resolveTriageCachePath } from "../triage/cache-path.js";
|
|
4
5
|
export const STATE_RELATIVE_PATH = join("xbrief", ".triage-cache", "staleness-tickler-state.json");
|
|
5
6
|
const STATE_FILE_NAME = "staleness-tickler-state.json";
|
|
@@ -15,10 +16,29 @@ function defaultReadText(path) {
|
|
|
15
16
|
}
|
|
16
17
|
}
|
|
17
18
|
function defaultWriteText(path, content) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
const dir = dirname(path);
|
|
20
|
+
mkdirSync(dir, { recursive: true });
|
|
21
|
+
const tmpBase = `${basename(path)}.${process.pid}.tmp`;
|
|
22
|
+
const temporary = join(dir, tmpBase);
|
|
23
|
+
try {
|
|
24
|
+
// #2980 wave D: product write sink routes through containedWrite.
|
|
25
|
+
containedWrite({
|
|
26
|
+
root: resolve(dir),
|
|
27
|
+
target: tmpBase,
|
|
28
|
+
data: content,
|
|
29
|
+
mode: "create",
|
|
30
|
+
});
|
|
31
|
+
renameSync(temporary, path);
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
try {
|
|
35
|
+
rmSync(temporary, { force: true });
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
/* best-effort cleanup */
|
|
39
|
+
}
|
|
40
|
+
throw err;
|
|
41
|
+
}
|
|
22
42
|
}
|
|
23
43
|
export function parseStalenessTicklerState(text) {
|
|
24
44
|
if (text === null) {
|
package/dist/swarm/launch.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { existsSync, readdirSync, readFileSync
|
|
2
|
-
import { basename, isAbsolute, join, resolve } from "node:path";
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
3
|
+
import { containedWrite } from "../fs/contained-write.js";
|
|
3
4
|
import { inferGithubAuthMode } from "../intake/github-auth-modes.js";
|
|
4
5
|
import { getPlatformCapabilities } from "../intake/platform-capabilities.js";
|
|
5
6
|
import { hasArtifactSuffix, resolveLifecycleFolder, stripArtifactSuffix, } from "../layout/resolve.js";
|
|
@@ -581,7 +582,16 @@ export function swarmLaunch(args) {
|
|
|
581
582
|
const rendered = `${JSON.stringify(manifest, null, 2)}\n`;
|
|
582
583
|
if (args.output !== undefined && args.output !== null) {
|
|
583
584
|
try {
|
|
584
|
-
|
|
585
|
+
// #2980 wave D: product write sink routes through containedWrite.
|
|
586
|
+
const abs = resolve(args.output);
|
|
587
|
+
const dir = dirname(abs);
|
|
588
|
+
mkdirSync(dir, { recursive: true });
|
|
589
|
+
containedWrite({
|
|
590
|
+
root: dir,
|
|
591
|
+
target: basename(abs),
|
|
592
|
+
data: rendered,
|
|
593
|
+
mode: "replace",
|
|
594
|
+
});
|
|
585
595
|
}
|
|
586
596
|
catch (exc) {
|
|
587
597
|
return {
|
package/dist/swarm/routing.js
CHANGED
|
@@ -14,8 +14,9 @@
|
|
|
14
14
|
* explicit default.
|
|
15
15
|
*/
|
|
16
16
|
import { execFileSync } from "node:child_process";
|
|
17
|
-
import { existsSync,
|
|
17
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
18
18
|
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
19
|
+
import { containedWrite } from "../fs/contained-write.js";
|
|
19
20
|
import { assertWriteTargetSafe } from "../fs/projection-containment.js";
|
|
20
21
|
/**
|
|
21
22
|
* The fixed worker-role vocabulary (reused from #1531). No separate tier
|
|
@@ -234,7 +235,12 @@ export function writeModelDecision(projectRoot, path, provider, role, decision)
|
|
|
234
235
|
decidedAt: decision.decidedAt ?? new Date().toISOString(),
|
|
235
236
|
};
|
|
236
237
|
file[provider] = block;
|
|
237
|
-
|
|
238
|
-
|
|
238
|
+
// #2980 wave D: product write sink routes through containedWrite.
|
|
239
|
+
containedWrite({
|
|
240
|
+
root: resolve(projectRoot),
|
|
241
|
+
target: path,
|
|
242
|
+
data: `${JSON.stringify(file, null, 2)}\n`,
|
|
243
|
+
mode: "replace",
|
|
244
|
+
});
|
|
239
245
|
}
|
|
240
246
|
//# sourceMappingURL=routing.js.map
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
|
-
import { existsSync, mkdirSync, readFileSync, statSync
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, statSync } from "node:fs";
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
4
|
import { LEGACY_VBRIEF_VERSION } from "@deftai/directive-types";
|
|
5
|
-
import {
|
|
5
|
+
import { ContainedWriteError, containedWrite } from "../fs/contained-write.js";
|
|
6
|
+
import { assertProjectionContained, ProjectionContainmentError, } from "../fs/projection-containment.js";
|
|
6
7
|
const UNRELEASED_RE = /## \[Unreleased\][ \t]*\n([\s\S]*?)(?=\n## \[|$)/;
|
|
7
8
|
const CHANGE_NAME_RE = /^[\w][\w-]*$/;
|
|
8
9
|
const COMMIT_TYPES = "feat|fix|docs|chore|refactor|test|style|perf|ci|build|revert";
|
|
@@ -14,7 +15,7 @@ function projectionTarget(projectDir, ...relSegments) {
|
|
|
14
15
|
return target;
|
|
15
16
|
}
|
|
16
17
|
function containmentExitCode(io, err) {
|
|
17
|
-
if (err instanceof ProjectionContainmentError) {
|
|
18
|
+
if (err instanceof ProjectionContainmentError || err instanceof ContainedWriteError) {
|
|
18
19
|
io.writeErr(`FAIL: ${err.message}\n`);
|
|
19
20
|
return 2;
|
|
20
21
|
}
|
|
@@ -93,10 +94,19 @@ export function runChangeInit(projectRoot, name, io) {
|
|
|
93
94
|
mkdirSync(specsDir, { recursive: true });
|
|
94
95
|
const proposalPath = join(base, "proposal.xbrief.json");
|
|
95
96
|
const tasksPath = join(base, "tasks.xbrief.json");
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
// #2980 wave D: product write sinks route through containedWrite.
|
|
98
|
+
containedWrite({
|
|
99
|
+
root: projectAbs,
|
|
100
|
+
target: proposalPath,
|
|
101
|
+
data: `${JSON.stringify(proposalTemplate(trimmed), null, 2)}\n`,
|
|
102
|
+
mode: "create",
|
|
103
|
+
});
|
|
104
|
+
containedWrite({
|
|
105
|
+
root: projectAbs,
|
|
106
|
+
target: tasksPath,
|
|
107
|
+
data: `${JSON.stringify(tasksTemplate(trimmed), null, 2)}\n`,
|
|
108
|
+
mode: "create",
|
|
109
|
+
});
|
|
100
110
|
io.writeOut(`OK: Created change proposal at ${base}/\n`);
|
|
101
111
|
for (const file of ["proposal.xbrief.json", "tasks.xbrief.json", "specs/"]) {
|
|
102
112
|
io.writeOut(` - ${file}\n`);
|
|
@@ -150,8 +160,13 @@ export function runInstallUninstall(projectRoot, io) {
|
|
|
150
160
|
const next = filtered.join("");
|
|
151
161
|
if (next !== original) {
|
|
152
162
|
try {
|
|
153
|
-
|
|
154
|
-
|
|
163
|
+
// #2980 wave D: product write sink routes through containedWrite.
|
|
164
|
+
containedWrite({
|
|
165
|
+
root: resolve(projectRoot),
|
|
166
|
+
target: path,
|
|
167
|
+
data: next,
|
|
168
|
+
mode: "replace",
|
|
169
|
+
});
|
|
155
170
|
io.writeOut("Removed deft entry from AGENTS.md\n");
|
|
156
171
|
}
|
|
157
172
|
catch (err) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync
|
|
2
|
-
import { dirname, relative } from "node:path";
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, relative, resolve } from "node:path";
|
|
3
|
+
import { containedWrite } from "../../fs/contained-write.js";
|
|
3
4
|
import { assertProjectionContained, ProjectionContainmentError, } from "../../fs/projection-containment.js";
|
|
4
5
|
import { MIGRATED_ARTIFACT_DIR, resolveLifecycleLayout } from "../../layout/resolve.js";
|
|
5
6
|
import { resolveCandidatesLogPath, resolveTriageCachePath, TRIAGE_CACHE_DIR_NAME, triageCacheRelPath, } from "../cache-path.js";
|
|
@@ -197,7 +198,14 @@ function ensureGitignoreLine(projectRoot, gitignorePath, line, stepName, createI
|
|
|
197
198
|
return stepOutcome(stepName, false, `.gitignore not present after the prior gitignore step; ${line} not written -- re-run bootstrap to retry`, { created: false, appended: false, skipped: "no-gitignore" }, "prior gitignore step did not create .gitignore");
|
|
198
199
|
}
|
|
199
200
|
try {
|
|
200
|
-
|
|
201
|
+
// #2980 wave D: product write sink routes through containedWrite.
|
|
202
|
+
const root = resolve(projectRoot);
|
|
203
|
+
containedWrite({
|
|
204
|
+
root,
|
|
205
|
+
target: gitignorePath,
|
|
206
|
+
data: `${line}\n`,
|
|
207
|
+
mode: "create",
|
|
208
|
+
});
|
|
201
209
|
}
|
|
202
210
|
catch (exc) {
|
|
203
211
|
return stepOutcome(stepName, false, "could not create .gitignore", {}, String(exc));
|
|
@@ -232,7 +240,13 @@ function ensureGitignoreLine(projectRoot, gitignorePath, line, stepName, createI
|
|
|
232
240
|
const suffix = existing.endsWith("\n") || existing === "" ? "" : "\n";
|
|
233
241
|
const newContent = `${existing + suffix + rationaleBlock + line}\n`;
|
|
234
242
|
try {
|
|
235
|
-
|
|
243
|
+
// #2980 wave D: product write sink routes through containedWrite.
|
|
244
|
+
containedWrite({
|
|
245
|
+
root: resolve(projectRoot),
|
|
246
|
+
target: gitignorePath,
|
|
247
|
+
data: newContent,
|
|
248
|
+
mode: "replace",
|
|
249
|
+
});
|
|
236
250
|
}
|
|
237
251
|
catch (exc) {
|
|
238
252
|
return stepOutcome(stepName, false, "could not write .gitignore", {}, String(exc));
|
|
@@ -303,7 +317,13 @@ function ensureGitignoreSelectiveEntries(projectRoot, stepName, entries) {
|
|
|
303
317
|
: `${EVAL_ENTRIES_RATIONALE}${missing.join("\n")}\n`;
|
|
304
318
|
const newContent = existing + suffix + appendedBlock;
|
|
305
319
|
try {
|
|
306
|
-
|
|
320
|
+
// #2980 wave D: product write sink routes through containedWrite.
|
|
321
|
+
containedWrite({
|
|
322
|
+
root: resolve(projectRoot),
|
|
323
|
+
target: gitignorePath,
|
|
324
|
+
data: newContent,
|
|
325
|
+
mode: "replace",
|
|
326
|
+
});
|
|
307
327
|
}
|
|
308
328
|
catch (exc) {
|
|
309
329
|
return stepOutcome(stepName, false, "could not write .gitignore", { gitignore_appended_lines: 0 }, String(exc));
|
|
@@ -341,7 +361,13 @@ function ensureGitattributesMergeUnion(projectRoot, stepName, glob, ruleLine) {
|
|
|
341
361
|
const suffix = existing.endsWith("\n") || existing === "" ? "" : "\n";
|
|
342
362
|
const newContent = `${existing + suffix + GITATTRIBUTES_EVAL_RATIONALE + ruleLine}\n`;
|
|
343
363
|
try {
|
|
344
|
-
|
|
364
|
+
// #2980 wave D: product write sink routes through containedWrite.
|
|
365
|
+
containedWrite({
|
|
366
|
+
root: resolve(projectRoot),
|
|
367
|
+
target: gitattributesPath,
|
|
368
|
+
data: newContent,
|
|
369
|
+
mode: "replace",
|
|
370
|
+
});
|
|
345
371
|
}
|
|
346
372
|
catch (exc) {
|
|
347
373
|
return stepOutcome(stepName, false, "could not write .gitattributes", { gitattributes_appended: false }, String(exc));
|
|
@@ -353,7 +379,13 @@ function ensureGitattributesMergeUnion(projectRoot, stepName, glob, ruleLine) {
|
|
|
353
379
|
}
|
|
354
380
|
const newContent = `${GITATTRIBUTES_EVAL_RATIONALE + ruleLine}\n`;
|
|
355
381
|
try {
|
|
356
|
-
|
|
382
|
+
// #2980 wave D: product write sink routes through containedWrite.
|
|
383
|
+
containedWrite({
|
|
384
|
+
root: resolve(projectRoot),
|
|
385
|
+
target: gitattributesPath,
|
|
386
|
+
data: newContent,
|
|
387
|
+
mode: "create",
|
|
388
|
+
});
|
|
357
389
|
}
|
|
358
390
|
catch (exc) {
|
|
359
391
|
return stepOutcome(stepName, false, "could not create .gitattributes", { gitattributes_appended: false }, String(exc));
|
|
@@ -382,8 +414,13 @@ function ensureEvalReadme(options) {
|
|
|
382
414
|
// create below
|
|
383
415
|
}
|
|
384
416
|
try {
|
|
385
|
-
|
|
386
|
-
|
|
417
|
+
// #2980 wave D: product write sink routes through containedWrite.
|
|
418
|
+
containedWrite({
|
|
419
|
+
root: resolve(projectRoot),
|
|
420
|
+
target: readmePath,
|
|
421
|
+
data: generateTriageCacheReadmeBody(projectRoot),
|
|
422
|
+
mode: "create",
|
|
423
|
+
});
|
|
387
424
|
}
|
|
388
425
|
catch (exc) {
|
|
389
426
|
return stepOutcome(stepName, false, `could not create ${readmePath}`, { readme_created: false }, String(exc));
|
|
@@ -474,7 +511,13 @@ export function stepSeedCandidatesLog(projectRoot) {
|
|
|
474
511
|
// create below
|
|
475
512
|
}
|
|
476
513
|
try {
|
|
477
|
-
|
|
514
|
+
// #2980 wave D: product write sink routes through containedWrite.
|
|
515
|
+
containedWrite({
|
|
516
|
+
root: resolve(projectRoot),
|
|
517
|
+
target: auditPath,
|
|
518
|
+
data: "",
|
|
519
|
+
mode: "create",
|
|
520
|
+
});
|
|
478
521
|
}
|
|
479
522
|
catch (exc) {
|
|
480
523
|
return stepOutcome("seed_candidates_log", false, `could not seed ${auditPath}`, {}, String(exc));
|
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* Triage append-only logs and scratch dirs live under `.triage-cache/` so the
|
|
5
5
|
* `.eval/` namespace can be reclaimed for version-eval results (#1703 Tier 2).
|
|
6
6
|
*/
|
|
7
|
-
import { existsSync, mkdirSync, renameSync, unlinkSync
|
|
8
|
-
import { join, relative } from "node:path";
|
|
7
|
+
import { existsSync, mkdirSync, renameSync, unlinkSync } from "node:fs";
|
|
8
|
+
import { join, relative, resolve } from "node:path";
|
|
9
|
+
import { containedWrite } from "../fs/contained-write.js";
|
|
9
10
|
import { assertProjectionContained } from "../fs/projection-containment.js";
|
|
10
11
|
import { MIGRATED_ARTIFACT_DIR, resolveEvalDir, resolveLifecycleLayout, resolveLifecycleRoot, } from "../layout/resolve.js";
|
|
11
12
|
import { generateTriageCacheReadmeBody } from "./bootstrap/gitignore.js";
|
|
@@ -99,7 +100,13 @@ export function migrateLegacyTriageCacheFromEval(projectRoot) {
|
|
|
99
100
|
removedLegacyFiles.push(name);
|
|
100
101
|
}
|
|
101
102
|
else {
|
|
102
|
-
|
|
103
|
+
// #2980 wave D: product write sink routes through containedWrite.
|
|
104
|
+
containedWrite({
|
|
105
|
+
root: resolve(projectRoot),
|
|
106
|
+
target: targetPath,
|
|
107
|
+
data: generateTriageCacheReadmeBody(projectRoot),
|
|
108
|
+
mode: "create",
|
|
109
|
+
});
|
|
103
110
|
unlinkSync(legacyPath);
|
|
104
111
|
regeneratedFiles.push(name);
|
|
105
112
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { join, resolve } from "node:path";
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
3
|
+
import { containedWrite } from "../../fs/contained-write.js";
|
|
3
4
|
import { hasArtifactSuffix, resolveLifecycleFolder, resolveProjectDefinitionPath, } from "../../layout/resolve.js";
|
|
4
5
|
import { readPlanPolicy } from "../../policy/plan-extensions.js";
|
|
5
6
|
import { countVbriefWip, resolveWipCap } from "../../policy/wip.js";
|
|
@@ -296,8 +297,14 @@ export function appendHistory(historyPath, result, line) {
|
|
|
296
297
|
reconcilable: result.reconcilable,
|
|
297
298
|
};
|
|
298
299
|
try {
|
|
299
|
-
|
|
300
|
-
|
|
300
|
+
// #2980 wave D: product write sink routes through containedWrite.
|
|
301
|
+
const dir = dirname(historyPath);
|
|
302
|
+
containedWrite({
|
|
303
|
+
root: resolve(dir),
|
|
304
|
+
target: basename(historyPath),
|
|
305
|
+
data: `${JSON.stringify(record)}\n`,
|
|
306
|
+
mode: "append",
|
|
307
|
+
});
|
|
301
308
|
}
|
|
302
309
|
catch {
|
|
303
310
|
// observability only
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync, } from "node:fs";
|
|
2
2
|
import { dirname, join, resolve } from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { containedWrite } from "../../fs/contained-write.js";
|
|
4
4
|
import { hasArtifactSuffix, resolveLifecycleFolder, resolveProjectDefinitionPath, } from "../../layout/resolve.js";
|
|
5
5
|
import { migrateLegacyPolicyKey, PLAN_POLICY_KEY } from "../../policy/plan-extensions.js";
|
|
6
6
|
import { projectDefinitionMutationLock } from "../../vbrief-build/project-definition-io.js";
|
|
@@ -12,22 +12,51 @@ function utcIso() {
|
|
|
12
12
|
return new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
13
13
|
}
|
|
14
14
|
export function appendAuditEntry(projectRoot, entry) {
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
const root = resolve(projectRoot);
|
|
16
|
+
const logPath = join(root, AUDIT_LOG_REL_PATH);
|
|
17
|
+
// #2980 wave D: product write sink routes through containedWrite.
|
|
18
|
+
mkdirSync(root, { recursive: true });
|
|
19
19
|
if (!existsSync(logPath)) {
|
|
20
|
-
|
|
20
|
+
containedWrite({
|
|
21
|
+
root,
|
|
22
|
+
target: AUDIT_LOG_REL_PATH,
|
|
23
|
+
data: "# meta/policy-changes.log -- audit trail for PROJECT-DEFINITION plan.policy.* mutations (#746 / #1143)\n",
|
|
24
|
+
mode: "create",
|
|
25
|
+
});
|
|
21
26
|
}
|
|
22
|
-
|
|
27
|
+
containedWrite({
|
|
28
|
+
root,
|
|
29
|
+
target: AUDIT_LOG_REL_PATH,
|
|
30
|
+
data: `${utcIso()} ${entry}\n`,
|
|
31
|
+
mode: "append",
|
|
32
|
+
});
|
|
23
33
|
return logPath;
|
|
24
34
|
}
|
|
25
|
-
function atomicWrite(path, data) {
|
|
26
|
-
|
|
35
|
+
function atomicWrite(_projectRoot, path, data) {
|
|
36
|
+
const dir = dirname(path);
|
|
37
|
+
mkdirSync(dir, { recursive: true });
|
|
27
38
|
const payload = `${JSON.stringify(data, null, 2)}\n`;
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
39
|
+
const tmpName = `.${Date.now()}.tmp`;
|
|
40
|
+
const tmp = join(dir, tmpName);
|
|
41
|
+
try {
|
|
42
|
+
// #2980 wave D: temp payload write uses containedWrite under the parent dir.
|
|
43
|
+
containedWrite({
|
|
44
|
+
root: resolve(dir),
|
|
45
|
+
target: tmpName,
|
|
46
|
+
data: payload,
|
|
47
|
+
mode: "create",
|
|
48
|
+
});
|
|
49
|
+
renameSync(tmp, path);
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
try {
|
|
53
|
+
rmSync(tmp, { force: true });
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
/* best-effort cleanup */
|
|
57
|
+
}
|
|
58
|
+
throw err;
|
|
59
|
+
}
|
|
31
60
|
}
|
|
32
61
|
export function writeTriageScope(projectRoot, rules, options = { presetLabel: "custom" }) {
|
|
33
62
|
// Serialise read-modify-write + audit append under the shared lock (#1260).
|
|
@@ -54,7 +83,7 @@ export function writeTriageScope(projectRoot, rules, options = { presetLabel: "c
|
|
|
54
83
|
const policyRec = policy;
|
|
55
84
|
const previous = policyRec.triageScope;
|
|
56
85
|
policyRec.triageScope = rules;
|
|
57
|
-
atomicWrite(path, data);
|
|
86
|
+
atomicWrite(projectRoot, path, data);
|
|
58
87
|
const changed = JSON.stringify(previous) !== JSON.stringify(rules);
|
|
59
88
|
const actor = options.actor ?? WELCOME_AUDIT_TAG;
|
|
60
89
|
const auditEntry = [
|
|
@@ -101,14 +130,14 @@ export function writeWipCap(projectRoot, wipCap, options = {}) {
|
|
|
101
130
|
}
|
|
102
131
|
if (previous !== undefined && wipCap === DEFAULT_WIP_CAP) {
|
|
103
132
|
delete policyRec.wipCap;
|
|
104
|
-
atomicWrite(path, data);
|
|
133
|
+
atomicWrite(projectRoot, path, data);
|
|
105
134
|
const auditEntry = `actor=${actor} field=plan.policy.wipCap action=cleared-to-default value=${wipCap} ` +
|
|
106
135
|
`previous=${JSON.stringify(previous)} changed=true`;
|
|
107
136
|
appendAuditEntry(projectRoot, auditEntry);
|
|
108
137
|
return [true, auditEntry];
|
|
109
138
|
}
|
|
110
139
|
policyRec.wipCap = wipCap;
|
|
111
|
-
atomicWrite(path, data);
|
|
140
|
+
atomicWrite(projectRoot, path, data);
|
|
112
141
|
const changed = previous !== wipCap;
|
|
113
142
|
const auditEntry = `actor=${actor} field=plan.policy.wipCap value=${wipCap} previous=${JSON.stringify(previous)} ` +
|
|
114
143
|
`changed=${changed ? "true" : "false"}`;
|
|
@@ -10,6 +10,11 @@ const EXCLUDE_DIRS = new Set([
|
|
|
10
10
|
"dist",
|
|
11
11
|
".planning",
|
|
12
12
|
"specs",
|
|
13
|
+
// Swarm / agent worktrees are not framework content; walking them
|
|
14
|
+
// during release Step 5 / task check is pure wall-clock waste (#2953, #1656).
|
|
15
|
+
".deft-scratch",
|
|
16
|
+
// Legacy swarm worktree root name (pre-.deft-scratch layout).
|
|
17
|
+
"swarm-worktrees",
|
|
13
18
|
]);
|
|
14
19
|
function shouldSkipPath(parts) {
|
|
15
20
|
if (parts.some((p) => EXCLUDE_DIRS.has(p)))
|
package/dist/value/readback.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
3
|
import { runningInsideDeftRepo } from "../doctor/paths.js";
|
|
4
4
|
import { ALL_ATTRIBUTION_EVENT_NAMES } from "../events/attribution-constants.js";
|
|
5
|
-
import {
|
|
5
|
+
import { ContainedWriteError, containedWrite } from "../fs/contained-write.js";
|
|
6
|
+
import { ProjectionContainmentError } from "../fs/projection-containment.js";
|
|
6
7
|
import { DEFAULT_EVENT_LOG, readEvents } from "../lifecycle/events.js";
|
|
7
8
|
import { policyColonInvocation } from "../policy/policy-invocation.js";
|
|
8
9
|
import { isValueFeedbackPathAllowed, resolveValueFeedback, } from "../policy/value-feedback.js";
|
|
@@ -230,12 +231,16 @@ function appendReadbackHistory(projectRoot, eventId, line, options = {}) {
|
|
|
230
231
|
line,
|
|
231
232
|
};
|
|
232
233
|
try {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
234
|
+
// #2980 wave D: product write sink routes through containedWrite.
|
|
235
|
+
containedWrite({
|
|
236
|
+
root: resolve(projectRoot),
|
|
237
|
+
target: path,
|
|
238
|
+
data: `${JSON.stringify(record)}\n`,
|
|
239
|
+
mode: "append",
|
|
240
|
+
});
|
|
236
241
|
}
|
|
237
242
|
catch (err) {
|
|
238
|
-
if (err instanceof ProjectionContainmentError) {
|
|
243
|
+
if (err instanceof ProjectionContainmentError || err instanceof ContainedWriteError) {
|
|
239
244
|
throw err;
|
|
240
245
|
}
|
|
241
246
|
// observability only
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, renameSync, statSync, unlinkSync,
|
|
2
|
-
import { basename, dirname } from "node:path";
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, unlinkSync, } from "node:fs";
|
|
2
|
+
import { basename, dirname, resolve } from "node:path";
|
|
3
|
+
import { containedWrite } from "../fs/contained-write.js";
|
|
3
4
|
import { utcNowIso } from "../scope/vbrief-json.js";
|
|
4
5
|
import { pythonJsonPretty } from "../vbrief-build/json.js";
|
|
5
6
|
import { ACTIVE_FOLDER, ELIGIBLE_STATUSES_FOR_FLIP, formatEligibleStatusList, SOURCE_FOLDERS, TARGET_STATUS, } from "./constants.js";
|
|
@@ -150,15 +151,22 @@ export function activate(vbriefPath, options = {}) {
|
|
|
150
151
|
"collision manually before re-running `task vbrief:activate`.",
|
|
151
152
|
};
|
|
152
153
|
}
|
|
154
|
+
const tmpBase = `${fileName}.tmp`;
|
|
153
155
|
const tmp = `${dest}.tmp`;
|
|
154
156
|
try {
|
|
155
|
-
|
|
157
|
+
// #2980 wave D: product write sink routes through containedWrite.
|
|
158
|
+
containedWrite({
|
|
159
|
+
root: resolve(activeDir),
|
|
160
|
+
target: tmpBase,
|
|
161
|
+
data: pythonJsonPretty(payload),
|
|
162
|
+
mode: "create",
|
|
163
|
+
});
|
|
156
164
|
renameSync(tmp, dest);
|
|
157
165
|
}
|
|
158
166
|
catch (err) {
|
|
159
167
|
const e = err;
|
|
160
168
|
try {
|
|
161
|
-
|
|
169
|
+
rmSync(tmp, { force: true });
|
|
162
170
|
}
|
|
163
171
|
catch {
|
|
164
172
|
/* best-effort cleanup */
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
|
-
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync,
|
|
3
|
-
import { dirname, join, resolve } from "node:path";
|
|
2
|
+
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync, writeSync, } from "node:fs";
|
|
3
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
4
|
+
import { containedWrite } from "../fs/contained-write.js";
|
|
4
5
|
import { resolveProjectDefinitionPath } from "../layout/resolve.js";
|
|
5
6
|
import { pythonJsonPretty } from "./json.js";
|
|
6
7
|
import { ProjectDefinitionIOError } from "./types.js";
|
|
@@ -105,12 +106,19 @@ export function loadProjectDefinitionForMutation(projectRoot) {
|
|
|
105
106
|
}
|
|
106
107
|
/** Atomically write ``data`` to ``path`` as pretty-printed JSON. */
|
|
107
108
|
export function atomicWriteProjectDefinition(path, data) {
|
|
108
|
-
|
|
109
|
+
// #2980 wave C: product write sink routes through containedWrite (temp under parent).
|
|
110
|
+
const dir = dirname(path);
|
|
111
|
+
mkdirSync(dir, { recursive: true });
|
|
109
112
|
const payload = pythonJsonPretty(data).replace(/\n$/, "");
|
|
110
|
-
const
|
|
113
|
+
const body = payload.endsWith("\n") ? payload : `${payload}\n`;
|
|
114
|
+
const tmpName = `${basename(path)}.${randomBytes(4).toString("hex")}.tmp`;
|
|
115
|
+
const tmp = join(dir, tmpName);
|
|
111
116
|
try {
|
|
112
|
-
|
|
113
|
-
|
|
117
|
+
containedWrite({
|
|
118
|
+
root: resolve(dir),
|
|
119
|
+
target: tmpName,
|
|
120
|
+
data: body,
|
|
121
|
+
mode: "create",
|
|
114
122
|
});
|
|
115
123
|
renameSync(tmp, path);
|
|
116
124
|
}
|