@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
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
* Refs #1942, #1430, #1463, #1179.
|
|
6
6
|
*/
|
|
7
7
|
import { execFileSync } from "node:child_process";
|
|
8
|
-
import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, statSync
|
|
9
|
-
import { mkdir, readdir, rm, stat
|
|
8
|
+
import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, statSync } from "node:fs";
|
|
9
|
+
import { mkdir, readdir, rm, stat } from "node:fs/promises";
|
|
10
10
|
import { platform } from "node:os";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
11
|
+
import { join, relative, resolve } from "node:path";
|
|
12
|
+
import { containedWrite } from "../fs/contained-write.js";
|
|
13
|
+
import { assertDestinationNotSymlink, ProjectionContainmentError, } from "../fs/projection-containment.js";
|
|
13
14
|
import { agentsRefreshPlan } from "../platform/agents-md.js";
|
|
14
15
|
import { MIGRATED_ARTIFACT_DIR } from "../xbrief-migrate/constants.js";
|
|
15
16
|
import { CANONICAL_INSTALL_ROOT } from "./constants.js";
|
|
@@ -17,12 +18,29 @@ import { installerManagedGuardEre } from "./hygiene.js";
|
|
|
17
18
|
import { syncConsumerXbriefSchemas } from "./xbrief-projections.js";
|
|
18
19
|
export { CANONICAL_INSTALL_ROOT };
|
|
19
20
|
export const CORE_GLOB = ".deft/core/**";
|
|
20
|
-
/**
|
|
21
|
+
/**
|
|
22
|
+
* Refuse init/update projection writes that escape via repo-controlled symlinks
|
|
23
|
+
* (#2446) OR that would follow an in-tree destination symlink on the write path
|
|
24
|
+
* (#2912). Every consumer projection sink in this module routes through here.
|
|
25
|
+
*/
|
|
21
26
|
function projectionTarget(projectDir, ...relSegments) {
|
|
22
27
|
const target = join(projectDir, ...relSegments);
|
|
23
|
-
|
|
28
|
+
assertDestinationNotSymlink(projectDir, target);
|
|
24
29
|
return target;
|
|
25
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Product write sink: containment root is the project, then containedWrite
|
|
33
|
+
* (#2951 / #2980 wave A). Call after projectionTarget / assertDestinationNotSymlink
|
|
34
|
+
* when the early ProjectionContainmentError type is required by tests.
|
|
35
|
+
*/
|
|
36
|
+
function containedProjectWrite(projectDir, target, data) {
|
|
37
|
+
containedWrite({
|
|
38
|
+
root: resolve(projectDir),
|
|
39
|
+
target,
|
|
40
|
+
data,
|
|
41
|
+
mode: "replace",
|
|
42
|
+
});
|
|
43
|
+
}
|
|
26
44
|
const CODEQL_CONFIG_REL = ".github/codeql/codeql-config.yml";
|
|
27
45
|
const CORE_GUARD_WORKFLOW_REL = ".github/workflows/deft-core-guard.yml";
|
|
28
46
|
const FRAMEWORK_SELF_TEST_REL = ".deft/core/tests";
|
|
@@ -215,9 +233,9 @@ export function writeInstallManifest(projectDir, deftDir, fields) {
|
|
|
215
233
|
relative(projectDir, deftDir).split("\\").join("/") ||
|
|
216
234
|
CANONICAL_INSTALL_ROOT;
|
|
217
235
|
const body = buildInstallManifestText({ ...fields, installRoot });
|
|
218
|
-
mkdirSync(deftDir, { recursive: true });
|
|
219
236
|
const path = join(deftDir, "VERSION");
|
|
220
|
-
|
|
237
|
+
// #2980 wave A: product write sink routes through containedWrite.
|
|
238
|
+
containedProjectWrite(projectDir, path, body);
|
|
221
239
|
return path;
|
|
222
240
|
}
|
|
223
241
|
/** Canonical committed pin dependency name (mirrors resolution/pin.ts). */
|
|
@@ -271,7 +289,7 @@ export function ensurePackageJsonPin(projectDir, version, io) {
|
|
|
271
289
|
}
|
|
272
290
|
devDeps[PIN_DEPENDENCY_NAME] = pinVersion;
|
|
273
291
|
pkg.devDependencies = devDeps;
|
|
274
|
-
|
|
292
|
+
containedProjectWrite(projectDir, path, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
275
293
|
io.printf(existed
|
|
276
294
|
? `package.json updated: pinned ${PIN_DEPENDENCY_NAME}@${pinVersion} (exact).\n`
|
|
277
295
|
: `package.json created: pinned ${PIN_DEPENDENCY_NAME}@${pinVersion} (exact, private).\n`);
|
|
@@ -292,7 +310,7 @@ export function writeAgentsMd(projectDir, deftDir, io) {
|
|
|
292
310
|
throw new Error("AGENTS.md render produced no content");
|
|
293
311
|
}
|
|
294
312
|
const path = projectionTarget(projectDir, "AGENTS.md");
|
|
295
|
-
|
|
313
|
+
containedProjectWrite(projectDir, path, newContent);
|
|
296
314
|
if (state === "absent") {
|
|
297
315
|
io.printf("AGENTS.md created.\n");
|
|
298
316
|
}
|
|
@@ -316,7 +334,7 @@ async function ensureVbriefLifecycleDirs(projectDir) {
|
|
|
316
334
|
const entries = await readdir(dir);
|
|
317
335
|
if (entries.length > 0)
|
|
318
336
|
continue;
|
|
319
|
-
|
|
337
|
+
containedProjectWrite(projectDir, gitkeep, VBRIEF_LIFECYCLE_GITKEEP);
|
|
320
338
|
}
|
|
321
339
|
}
|
|
322
340
|
function vbriefLifecycleDirsPresent(consumerVbrief) {
|
|
@@ -350,7 +368,7 @@ export async function writeConsumerVbrief(projectDir, deftDir, io) {
|
|
|
350
368
|
copyFileSync(fwVbriefMd, vbriefMdDst);
|
|
351
369
|
}
|
|
352
370
|
else {
|
|
353
|
-
|
|
371
|
+
containedProjectWrite(projectDir, vbriefMdDst, VBRIEF_README_BODY);
|
|
354
372
|
}
|
|
355
373
|
}
|
|
356
374
|
await ensureVbriefLifecycleDirs(projectDir);
|
|
@@ -365,12 +383,10 @@ export function writeAgentsSkills(projectDir, io) {
|
|
|
365
383
|
return false;
|
|
366
384
|
}
|
|
367
385
|
for (const skill of AGENTS_SKILLS) {
|
|
368
|
-
const dir = projectionTarget(projectDir, ".agents", "skills", skill.dir);
|
|
369
|
-
mkdirSync(dir, { recursive: true });
|
|
370
386
|
const path = projectionTarget(projectDir, ".agents", "skills", skill.dir, "SKILL.md");
|
|
371
387
|
if (existsSync(path))
|
|
372
388
|
continue;
|
|
373
|
-
|
|
389
|
+
containedProjectWrite(projectDir, path, skill.content);
|
|
374
390
|
}
|
|
375
391
|
io.printf(".agents/skills/ created — deft skills will be auto-discovered.\n");
|
|
376
392
|
return true;
|
|
@@ -455,21 +471,20 @@ export function ensureTaskfile(projectDir, io) {
|
|
|
455
471
|
" optional: true\n";
|
|
456
472
|
io.printf("Appended new `includes:` block with deft entry to Taskfile.yml (Epic-4).\n");
|
|
457
473
|
}
|
|
458
|
-
|
|
474
|
+
containedProjectWrite(projectDir, path, resultText);
|
|
459
475
|
return true;
|
|
460
476
|
}
|
|
461
477
|
const HOOK_FILENAMES = ["pre-commit", "pre-push"];
|
|
462
478
|
const HOOK_SUPPORT_FILENAMES = ["_deft-run.sh"];
|
|
463
479
|
const HOOK_FILE_MODE = 0o755;
|
|
464
|
-
const HOOK_SUPPORT_FILE_MODE = 0o644;
|
|
465
480
|
export function writeConsumerGitHooks(projectDir, deftDir, io, seams = {}) {
|
|
466
481
|
const srcDir = join(deftDir, ".githooks");
|
|
467
482
|
if (!existsSync(srcDir) || !statSync(srcDir).isDirectory()) {
|
|
468
483
|
io.printf(`git hooks source ${srcDir} absent — skipping hook wiring.\n`);
|
|
469
484
|
return false;
|
|
470
485
|
}
|
|
471
|
-
|
|
472
|
-
|
|
486
|
+
// Ensure parent path is containment-checked before any file write.
|
|
487
|
+
projectionTarget(projectDir, ".githooks");
|
|
473
488
|
let filesDeposited = false;
|
|
474
489
|
for (const name of [...HOOK_FILENAMES, ...HOOK_SUPPORT_FILENAMES]) {
|
|
475
490
|
const src = join(srcDir, name);
|
|
@@ -480,8 +495,8 @@ export function writeConsumerGitHooks(projectDir, deftDir, io, seams = {}) {
|
|
|
480
495
|
const existing = existsSync(dst) ? readFileSync(dst) : null;
|
|
481
496
|
const isHookScript = HOOK_FILENAMES.includes(name);
|
|
482
497
|
if (!existing?.equals(data)) {
|
|
483
|
-
|
|
484
|
-
|
|
498
|
+
// #2980 wave A: containedWrite (mode is applied via chmod below for hooks).
|
|
499
|
+
containedProjectWrite(projectDir, dst, data);
|
|
485
500
|
filesDeposited = true;
|
|
486
501
|
}
|
|
487
502
|
if (platform() !== "win32" && isHookScript) {
|
|
@@ -678,7 +693,7 @@ export function ensureGitattributes(projectDir, io) {
|
|
|
678
693
|
for (const add of additions) {
|
|
679
694
|
body += `${add}\n`;
|
|
680
695
|
}
|
|
681
|
-
|
|
696
|
+
containedProjectWrite(projectDir, path, body);
|
|
682
697
|
io.printf(`.gitattributes updated with Deft core markers: ${additions.join(", ")}\n`);
|
|
683
698
|
return true;
|
|
684
699
|
}
|
|
@@ -721,7 +736,7 @@ export function ensureGreptileIgnore(projectDir, io) {
|
|
|
721
736
|
return false;
|
|
722
737
|
}
|
|
723
738
|
obj.ignorePatterns = appendGreptilePattern(patterns, CORE_GLOB);
|
|
724
|
-
|
|
739
|
+
containedProjectWrite(projectDir, path, `${JSON.stringify(obj, null, 2)}\n`);
|
|
725
740
|
io.printf(fileExisted
|
|
726
741
|
? `greptile.json updated: bot review now ignores ${CORE_GLOB}.\n`
|
|
727
742
|
: `greptile.json created: bot review ignores ${CORE_GLOB}.\n`);
|
|
@@ -770,8 +785,7 @@ function insertCodeqlPathsIgnore(content, glob) {
|
|
|
770
785
|
export function ensureCodeqlPathsIgnore(projectDir, io) {
|
|
771
786
|
const path = projectionTarget(projectDir, CODEQL_CONFIG_REL);
|
|
772
787
|
if (!existsSync(path)) {
|
|
773
|
-
|
|
774
|
-
writeFileSync(path, codeqlConfigDefault(), "utf8");
|
|
788
|
+
containedProjectWrite(projectDir, path, codeqlConfigDefault());
|
|
775
789
|
io.printf(`${CODEQL_CONFIG_REL} created: CodeQL ignores ${CORE_GLOB}.\n`);
|
|
776
790
|
return true;
|
|
777
791
|
}
|
|
@@ -784,7 +798,7 @@ export function ensureCodeqlPathsIgnore(projectDir, io) {
|
|
|
784
798
|
const updated = inserted.ok
|
|
785
799
|
? inserted.content
|
|
786
800
|
: `${existing}${existing.endsWith("\n") ? "" : "\n"}paths-ignore:\n - '${CORE_GLOB}'\n`;
|
|
787
|
-
|
|
801
|
+
containedProjectWrite(projectDir, path, updated);
|
|
788
802
|
io.printf(`${CODEQL_CONFIG_REL} updated: CodeQL now ignores ${CORE_GLOB}.\n`);
|
|
789
803
|
return true;
|
|
790
804
|
}
|
|
@@ -802,12 +816,11 @@ export function ensureCoreGuardWorkflow(projectDir, io) {
|
|
|
802
816
|
io.printf(`${CORE_GUARD_WORKFLOW_REL} already current — skipping.\n`);
|
|
803
817
|
return false;
|
|
804
818
|
}
|
|
805
|
-
|
|
819
|
+
containedProjectWrite(projectDir, path, refreshed);
|
|
806
820
|
io.printf(`${CORE_GUARD_WORKFLOW_REL} refreshed: deft-core-guard allowlist updated (#1478).\n`);
|
|
807
821
|
return true;
|
|
808
822
|
}
|
|
809
|
-
|
|
810
|
-
writeFileSync(path, desired, "utf8");
|
|
823
|
+
containedProjectWrite(projectDir, path, desired);
|
|
811
824
|
io.printf(`${CORE_GUARD_WORKFLOW_REL} created: CI refuses PRs mixing ${CORE_GLOB} with app files.\n`);
|
|
812
825
|
return true;
|
|
813
826
|
}
|
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
* freshness. A current `.deft/core/VERSION` therefore cannot short-circuit
|
|
6
6
|
* these repairs.
|
|
7
7
|
*/
|
|
8
|
-
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync
|
|
9
|
-
import { join, relative } from "node:path";
|
|
10
|
-
import {
|
|
8
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync } from "node:fs";
|
|
9
|
+
import { join, relative, resolve } from "node:path";
|
|
10
|
+
import { containedWrite } from "../fs/contained-write.js";
|
|
11
|
+
import { assertDestinationNotSymlink } from "../fs/projection-containment.js";
|
|
11
12
|
import { resolveLifecycleRoot } from "../layout/resolve.js";
|
|
12
13
|
import { DEV_FALLBACK } from "../platform/constants.js";
|
|
13
14
|
import { MIGRATED_ARTIFACT_DIR } from "../xbrief-migrate/constants.js";
|
|
@@ -54,7 +55,7 @@ export function rewriteProjectedSchemaContent(content) {
|
|
|
54
55
|
* Upstream vbrief/schemas/ may keep legacy paths; consumer copies must not (#2670).
|
|
55
56
|
*/
|
|
56
57
|
export function assertProjectedSchemaDescriptionsRooted(projectDir, destinationDir) {
|
|
57
|
-
|
|
58
|
+
assertDestinationNotSymlink(projectDir, destinationDir);
|
|
58
59
|
if (!isDirectory(destinationDir))
|
|
59
60
|
return;
|
|
60
61
|
for (const rel of collectSchemaFiles(destinationDir)) {
|
|
@@ -66,7 +67,8 @@ export function assertProjectedSchemaDescriptionsRooted(projectDir, destinationD
|
|
|
66
67
|
}
|
|
67
68
|
}
|
|
68
69
|
function writeFileIfChanged(projectDir, target, content) {
|
|
69
|
-
|
|
70
|
+
// Keep early containment so ProjectionContainmentError type is preserved for callers/tests.
|
|
71
|
+
assertDestinationNotSymlink(projectDir, target);
|
|
70
72
|
const desired = Buffer.isBuffer(content) ? content : Buffer.from(content, "utf8");
|
|
71
73
|
try {
|
|
72
74
|
if (readFileSync(target).equals(desired))
|
|
@@ -75,8 +77,13 @@ function writeFileIfChanged(projectDir, target, content) {
|
|
|
75
77
|
catch {
|
|
76
78
|
// Missing or unreadable target is replaced below.
|
|
77
79
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
+
// #2980 wave A: product write sink routes through containedWrite.
|
|
81
|
+
containedWrite({
|
|
82
|
+
root: resolve(projectDir),
|
|
83
|
+
target,
|
|
84
|
+
data: desired,
|
|
85
|
+
mode: "replace",
|
|
86
|
+
});
|
|
80
87
|
return true;
|
|
81
88
|
}
|
|
82
89
|
/**
|
|
@@ -91,7 +98,7 @@ export function syncConsumerXbriefSchemas(projectDir, deftDir) {
|
|
|
91
98
|
throw new Error(`cannot project xbrief schemas: framework payload is missing ${CURRENT_CORE_SCHEMA}`);
|
|
92
99
|
}
|
|
93
100
|
const destinationDir = join(projectDir, MIGRATED_ARTIFACT_DIR, "schemas");
|
|
94
|
-
|
|
101
|
+
assertDestinationNotSymlink(projectDir, destinationDir);
|
|
95
102
|
mkdirSync(destinationDir, { recursive: true });
|
|
96
103
|
let changed = false;
|
|
97
104
|
for (const rel of collectSchemaFiles(sourceDir)) {
|
|
@@ -103,7 +110,7 @@ export function syncConsumerXbriefSchemas(projectDir, deftDir) {
|
|
|
103
110
|
changed = writeFileIfChanged(projectDir, destination, projected) || changed;
|
|
104
111
|
}
|
|
105
112
|
const obsoleteDestination = join(destinationDir, OBSOLETE_CORE_SCHEMA);
|
|
106
|
-
|
|
113
|
+
assertDestinationNotSymlink(projectDir, obsoleteDestination);
|
|
107
114
|
if (existsSync(obsoleteDestination)) {
|
|
108
115
|
rmSync(obsoleteDestination, { force: true });
|
|
109
116
|
changed = true;
|
|
@@ -117,7 +124,7 @@ function syncBareVersionMarkerWithPolicy(projectDir, version, allowRootFallback)
|
|
|
117
124
|
return false;
|
|
118
125
|
const canonicalRoot = join(projectDir, MIGRATED_ARTIFACT_DIR);
|
|
119
126
|
if (existsSync(canonicalRoot)) {
|
|
120
|
-
|
|
127
|
+
assertDestinationNotSymlink(projectDir, join(canonicalRoot, ".deft-version"));
|
|
121
128
|
}
|
|
122
129
|
let targetDir = projectDir;
|
|
123
130
|
try {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, unlinkSync } from "node:fs";
|
|
3
|
+
import { basename, dirname, resolve } from "node:path";
|
|
4
|
+
import { ContainedWriteError, ContainedWriteErrorCode, containedWrite, } from "../fs/contained-write.js";
|
|
4
5
|
import { pyRepr } from "../scm/py-format.js";
|
|
5
6
|
import { resolveCandidatesLogPath } from "../triage/cache-path.js";
|
|
6
7
|
function defaultLogPath() {
|
|
@@ -110,40 +111,45 @@ function validateEntry(entry) {
|
|
|
110
111
|
function resolvePath(path) {
|
|
111
112
|
return path !== undefined && path !== null ? resolve(path) : defaultLogPath();
|
|
112
113
|
}
|
|
113
|
-
|
|
114
|
+
/**
|
|
115
|
+
* Cross-process exclusive lock via containedWrite mode=create (O_EXCL).
|
|
116
|
+
* #2980 wave B: lock + product appends route through containedWrite.
|
|
117
|
+
*/
|
|
118
|
+
function acquireAppendLock(logPath, containmentRoot) {
|
|
114
119
|
while (threadLocked) {
|
|
115
120
|
// spin-wait for in-process serialization
|
|
116
121
|
}
|
|
117
122
|
threadLocked = true;
|
|
118
|
-
const lockPath =
|
|
123
|
+
const lockPath = resolve(dirname(logPath), `${basename(logPath)}.lock`);
|
|
119
124
|
mkdirSync(dirname(lockPath), { recursive: true });
|
|
120
125
|
const deadline = Date.now() + 30_000;
|
|
121
|
-
|
|
122
|
-
while (fd === null) {
|
|
126
|
+
while (true) {
|
|
123
127
|
try {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
128
|
+
containedWrite({
|
|
129
|
+
root: containmentRoot,
|
|
130
|
+
target: lockPath,
|
|
131
|
+
data: "\0",
|
|
132
|
+
mode: "create",
|
|
133
|
+
});
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
if (err instanceof ContainedWriteError && err.code === ContainedWriteErrorCode.EXISTS) {
|
|
138
|
+
if (Date.now() > deadline) {
|
|
139
|
+
threadLocked = false;
|
|
140
|
+
throw new CandidatesLogError("timed out acquiring candidates log lock");
|
|
141
|
+
}
|
|
142
|
+
const spinEnd = Date.now() + 20;
|
|
143
|
+
while (Date.now() < spinEnd) {
|
|
144
|
+
// brief spin
|
|
145
|
+
}
|
|
146
|
+
continue;
|
|
134
147
|
}
|
|
148
|
+
threadLocked = false;
|
|
149
|
+
throw err;
|
|
135
150
|
}
|
|
136
151
|
}
|
|
137
|
-
writeSync(fd, Buffer.from("\0"));
|
|
138
152
|
return () => {
|
|
139
|
-
try {
|
|
140
|
-
if (fd !== null) {
|
|
141
|
-
closeSync(fd);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
catch {
|
|
145
|
-
// ignore
|
|
146
|
-
}
|
|
147
153
|
try {
|
|
148
154
|
unlinkSync(lockPath);
|
|
149
155
|
}
|
|
@@ -153,24 +159,29 @@ function acquireAppendLock(logPath) {
|
|
|
153
159
|
threadLocked = false;
|
|
154
160
|
};
|
|
155
161
|
}
|
|
162
|
+
/** Ensure parent exists and return it as the containment root for log writes. */
|
|
163
|
+
function containmentRootForLog(logPath) {
|
|
164
|
+
const parent = dirname(logPath);
|
|
165
|
+
mkdirSync(parent, { recursive: true });
|
|
166
|
+
return resolve(parent);
|
|
167
|
+
}
|
|
156
168
|
export function validateCandidatesEntry(entry) {
|
|
157
169
|
validateEntry(entry);
|
|
158
170
|
}
|
|
159
171
|
export function append(entry, options = {}) {
|
|
160
172
|
validateEntry(entry);
|
|
161
173
|
const logPath = resolvePath(options.path);
|
|
162
|
-
|
|
174
|
+
const root = containmentRootForLog(logPath);
|
|
163
175
|
const line = JSON.stringify(entry, Object.keys(entry).sort());
|
|
164
|
-
const release = acquireAppendLock(logPath);
|
|
176
|
+
const release = acquireAppendLock(logPath, root);
|
|
165
177
|
try {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
178
|
+
// #2980 wave B: product write sink routes through containedWrite.
|
|
179
|
+
containedWrite({
|
|
180
|
+
root,
|
|
181
|
+
target: logPath,
|
|
182
|
+
data: `${line}\n`,
|
|
183
|
+
mode: "append",
|
|
184
|
+
});
|
|
174
185
|
}
|
|
175
186
|
finally {
|
|
176
187
|
release();
|
|
@@ -1,3 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Production entry for `task scm:body:*` / `directive github-body <subcommand>`.
|
|
4
|
+
* Dispatch peels the `github-body` verb; argv[0] is the subcommand
|
|
5
|
+
* (`issue-lint`, `pr-lint`, `issue-edit`, …). Wired through githubBodyMain
|
|
6
|
+
* so create/edit fail closed and lint commands share one encoding gate (#2960).
|
|
7
|
+
*/
|
|
2
8
|
export declare function mainEntry(argv?: string[]): number;
|
|
3
9
|
//# sourceMappingURL=github-body-cli.d.ts.map
|
|
@@ -19,9 +19,26 @@ function parseArgs(argv) {
|
|
|
19
19
|
out.bodyFile = argv[++i];
|
|
20
20
|
else if (arg === "--out-file")
|
|
21
21
|
out.outFile = argv[++i];
|
|
22
|
+
else if (/^\d+$/.test(arg)) {
|
|
23
|
+
// Positional number: issue-lint / issue-* use --issue; pr-lint / pr-* use --pr (#2960).
|
|
24
|
+
const n = Number.parseInt(arg, 10);
|
|
25
|
+
if (out.command.startsWith("pr-")) {
|
|
26
|
+
if (out.pr === undefined)
|
|
27
|
+
out.pr = n;
|
|
28
|
+
}
|
|
29
|
+
else if (out.issue === undefined) {
|
|
30
|
+
out.issue = n;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
22
33
|
}
|
|
23
34
|
return out;
|
|
24
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Production entry for `task scm:body:*` / `directive github-body <subcommand>`.
|
|
38
|
+
* Dispatch peels the `github-body` verb; argv[0] is the subcommand
|
|
39
|
+
* (`issue-lint`, `pr-lint`, `issue-edit`, …). Wired through githubBodyMain
|
|
40
|
+
* so create/edit fail closed and lint commands share one encoding gate (#2960).
|
|
41
|
+
*/
|
|
25
42
|
export function mainEntry(argv = process.argv.slice(2)) {
|
|
26
43
|
const parsed = parseArgs(argv);
|
|
27
44
|
return githubBodyMain(parsed);
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
+
import { type Finding } from "../encoding/scan.js";
|
|
1
2
|
export declare const DEFAULT_TIMEOUT_SECONDS = 60;
|
|
3
|
+
/** Stable error code for CP1252/CP437-as-UTF-8 / U+FFFD body corruption (#2960). */
|
|
4
|
+
export declare const SCM_BODY_ENCODING_CODE = "scm-body-encoding";
|
|
2
5
|
export declare class GitHubBodyError extends Error {
|
|
3
|
-
|
|
6
|
+
readonly code?: string;
|
|
7
|
+
constructor(message: string, options?: {
|
|
8
|
+
code?: string;
|
|
9
|
+
});
|
|
4
10
|
}
|
|
5
11
|
export type RunGhApiFn = (args: readonly string[], options?: {
|
|
6
12
|
inputText?: string | null;
|
|
@@ -9,7 +15,22 @@ export type RunGhApiFn = (args: readonly string[], options?: {
|
|
|
9
15
|
/** Resolve the live GitHub CLI used for writes and mutation read-back. */
|
|
10
16
|
export declare function resolveLiveGh(): string;
|
|
11
17
|
export declare function readBody(bodyFile: string, stdinText?: string | null): string;
|
|
12
|
-
/**
|
|
18
|
+
/** Strip a leading UTF-8 BOM so body-file writers stay UTF-8 no BOM (#2960 / #798). */
|
|
19
|
+
export declare function stripUtf8Bom(text: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* Scan body text with the same mojibake pattern set as verify:encoding
|
|
22
|
+
* (`packages/core/src/encoding/patterns.ts` / scanLine). Issue/PR bodies are
|
|
23
|
+
* Markdown: strip fenced blocks and inline code spans so documented examples
|
|
24
|
+
* of the corruption class (e.g. `#2960` fixtures) are not false positives —
|
|
25
|
+
* matches `scanFile` .md behavior.
|
|
26
|
+
*/
|
|
27
|
+
export declare function scanBodyText(body: string, source?: string): Finding[];
|
|
28
|
+
/**
|
|
29
|
+
* Fail closed when body text already contains CP1252/CP437-as-UTF-8 mojibake
|
|
30
|
+
* or U+FFFD (before PATCH and as a live-body diagnostic).
|
|
31
|
+
*/
|
|
32
|
+
export declare function assertBodyEncoding(body: string, phase: string): void;
|
|
33
|
+
/** Fail closed when live read-back body is flattened or mojibaked vs intended payload (#2607 / #2960). */
|
|
13
34
|
export declare function verifyBodyPostcondition(intended: string, live: string): void;
|
|
14
35
|
export declare function createIssue(repo: string, options: {
|
|
15
36
|
title: string;
|
|
@@ -41,10 +62,29 @@ export declare function fetchIssueBody(repo: string, issue: number, options?: {
|
|
|
41
62
|
binary?: string | null;
|
|
42
63
|
runFn?: RunGhApiFn;
|
|
43
64
|
}): string;
|
|
65
|
+
export declare function fetchPrBody(repo: string, pr: number, options?: {
|
|
66
|
+
binary?: string | null;
|
|
67
|
+
runFn?: RunGhApiFn;
|
|
68
|
+
}): string;
|
|
44
69
|
export declare function writeIssueBodyToFile(repo: string, issue: number, outFile: string, options?: {
|
|
45
70
|
binary?: string | null;
|
|
46
71
|
runFn?: RunGhApiFn;
|
|
47
72
|
}): string;
|
|
73
|
+
/**
|
|
74
|
+
* Lint a live issue body for mojibake. Returns findings (empty = clean).
|
|
75
|
+
* Does not throw on hits — callers decide exit code.
|
|
76
|
+
*/
|
|
77
|
+
export declare function lintIssueBody(repo: string, issue: number, options?: {
|
|
78
|
+
binary?: string | null;
|
|
79
|
+
runFn?: RunGhApiFn;
|
|
80
|
+
}): Finding[];
|
|
81
|
+
/**
|
|
82
|
+
* Lint a live PR body for mojibake. Returns findings (empty = clean).
|
|
83
|
+
*/
|
|
84
|
+
export declare function lintPrBody(repo: string, pr: number, options?: {
|
|
85
|
+
binary?: string | null;
|
|
86
|
+
runFn?: RunGhApiFn;
|
|
87
|
+
}): Finding[];
|
|
48
88
|
export interface GitHubBodyCliArgs {
|
|
49
89
|
command: string;
|
|
50
90
|
repo?: string;
|
|
@@ -55,5 +95,8 @@ export interface GitHubBodyCliArgs {
|
|
|
55
95
|
bodyFile?: string;
|
|
56
96
|
outFile?: string;
|
|
57
97
|
}
|
|
58
|
-
export declare function githubBodyMain(args: GitHubBodyCliArgs
|
|
98
|
+
export declare function githubBodyMain(args: GitHubBodyCliArgs, options?: {
|
|
99
|
+
runFn?: RunGhApiFn;
|
|
100
|
+
binary?: string | null;
|
|
101
|
+
}): number;
|
|
59
102
|
//# sourceMappingURL=github-body.d.ts.map
|