@deftai/directive-core 0.104.0 → 0.105.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/authz/classify.js +526 -42
- package/dist/check/cached-orchestrator.d.ts +1 -1
- package/dist/check/cached-orchestrator.js +2 -2
- package/dist/check/named-cause.d.ts +1 -1
- package/dist/check/named-cause.js +33 -3
- package/dist/codebase/default-extractor.d.ts +5 -0
- package/dist/codebase/default-extractor.js +7 -15
- package/dist/deposit/copy-tree.js +60 -0
- package/dist/deposit/python-free.js +26 -18
- package/dist/doctor/checks.d.ts +8 -0
- package/dist/doctor/checks.js +37 -25
- package/dist/doctor/doctor-state.d.ts +4 -0
- package/dist/doctor/doctor-state.js +7 -1
- package/dist/doctor/main.js +9 -19
- package/dist/doctor/types.d.ts +9 -0
- package/dist/fs/contained-write.d.ts +115 -1
- package/dist/fs/contained-write.js +241 -5
- package/dist/fs/mutation-ledger.d.ts +54 -0
- package/dist/fs/mutation-ledger.js +133 -0
- package/dist/fs/non-product-dirs.d.ts +79 -0
- package/dist/fs/non-product-dirs.js +96 -0
- package/dist/hooks/dispatcher.d.ts +3 -1
- package/dist/hooks/dispatcher.js +14 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/init-deposit/agent-hooks.js +17 -15
- package/dist/init-deposit/core-guard-pin-content.d.ts +7 -0
- package/dist/init-deposit/core-guard-pin-content.js +393 -0
- package/dist/init-deposit/gitignore.js +9 -0
- package/dist/init-deposit/hygiene.d.ts +47 -11
- package/dist/init-deposit/hygiene.js +172 -37
- package/dist/init-deposit/legacy-detect.js +6 -2
- package/dist/init-deposit/refresh.d.ts +21 -1
- package/dist/init-deposit/refresh.js +106 -9
- package/dist/init-deposit/scaffold.js +28 -279
- package/dist/init-deposit/slash-deposit.js +13 -7
- package/dist/init-deposit/xbrief-projections.d.ts +8 -1
- package/dist/init-deposit/xbrief-projections.js +24 -11
- package/dist/intake/clause-derivation.d.ts +38 -1
- package/dist/intake/clause-derivation.js +211 -9
- package/dist/intake/issue-ingest.d.ts +3 -1
- package/dist/intake/issue-ingest.js +35 -6
- package/dist/lifecycle/completed-tracked-on-delivery.d.ts +5 -0
- package/dist/lifecycle/completed-tracked-on-delivery.js +85 -18
- package/dist/literal-acceptance/capture.d.ts +10 -0
- package/dist/literal-acceptance/capture.js +224 -75
- package/dist/literal-acceptance/evaluate.d.ts +29 -4
- package/dist/literal-acceptance/evaluate.js +70 -2
- package/dist/literal-acceptance/index.d.ts +3 -3
- package/dist/literal-acceptance/index.js +3 -3
- package/dist/literal-acceptance/safety.d.ts +41 -0
- package/dist/literal-acceptance/safety.js +126 -2
- package/dist/literal-acceptance/types.d.ts +8 -0
- package/dist/orphan-active/evaluate.d.ts +7 -1
- package/dist/orphan-active/evaluate.js +102 -23
- package/dist/platform/platform-capabilities.js +2 -3
- package/dist/policy/base-branch.d.ts +37 -0
- package/dist/policy/base-branch.js +114 -0
- package/dist/policy/branch-sync.d.ts +80 -0
- package/dist/policy/branch-sync.js +211 -0
- package/dist/policy/ceremony-dial-escalation.js +1 -0
- package/dist/policy/ceremony-dial.js +4 -14
- package/dist/policy/delivery-branch.d.ts +5 -3
- package/dist/policy/delivery-branch.js +7 -3
- package/dist/policy/file-size-thresholds.d.ts +24 -0
- package/dist/policy/file-size-thresholds.js +23 -0
- package/dist/policy/forge-outage-retry.d.ts +47 -0
- package/dist/policy/forge-outage-retry.js +157 -0
- package/dist/policy/index.d.ts +7 -0
- package/dist/policy/index.js +66 -7
- package/dist/policy/project-invariants.d.ts +50 -0
- package/dist/policy/project-invariants.js +205 -0
- package/dist/policy/sync-default.d.ts +129 -0
- package/dist/policy/sync-default.js +452 -0
- package/dist/policy/sync-max-files.d.ts +93 -0
- package/dist/policy/sync-max-files.js +192 -0
- package/dist/pr-monitor/absent-required.d.ts +11 -0
- package/dist/pr-monitor/absent-required.js +61 -0
- package/dist/pr-monitor/constants.d.ts +7 -0
- package/dist/pr-monitor/constants.js +7 -0
- package/dist/pr-monitor/index.d.ts +2 -1
- package/dist/pr-monitor/index.js +2 -1
- package/dist/pr-monitor/main.js +4 -0
- package/dist/pr-monitor/monitor.js +24 -1
- package/dist/pr-wait-mergeable/classify.js +6 -0
- package/dist/preflight/evaluate.d.ts +4 -0
- package/dist/preflight/evaluate.js +35 -1
- package/dist/preflight/index.d.ts +4 -0
- package/dist/preflight/index.js +2 -0
- package/dist/preflight/intended-placement.d.ts +62 -0
- package/dist/preflight/intended-placement.js +275 -0
- package/dist/preflight/project-invariants-gate.d.ts +29 -0
- package/dist/preflight/project-invariants-gate.js +82 -0
- package/dist/product-first-done-gate/acceptance-resolver.d.ts +128 -0
- package/dist/product-first-done-gate/acceptance-resolver.js +144 -0
- package/dist/product-first-done-gate/acceptance.d.ts +7 -4
- package/dist/product-first-done-gate/acceptance.js +79 -8
- package/dist/product-first-done-gate/empty-resolution.d.ts +4 -1
- package/dist/product-first-done-gate/empty-resolution.js +12 -1
- package/dist/product-first-done-gate/evaluate.d.ts +12 -0
- package/dist/product-first-done-gate/evaluate.js +254 -31
- package/dist/product-first-done-gate/index.d.ts +2 -0
- package/dist/product-first-done-gate/index.js +1 -0
- package/dist/release/build-dist.d.ts +101 -0
- package/dist/release/build-dist.js +428 -46
- package/dist/run-summary/emit.d.ts +16 -10
- package/dist/run-summary/emit.js +26 -15
- package/dist/run-summary/index.d.ts +3 -2
- package/dist/run-summary/index.js +2 -1
- package/dist/run-summary/session-id.d.ts +21 -0
- package/dist/run-summary/session-id.js +92 -0
- package/dist/run-summary/types.d.ts +29 -4
- package/dist/run-summary/types.js +1 -0
- package/dist/scope/acceptance-evidence.d.ts +19 -7
- package/dist/scope/acceptance-evidence.js +38 -11
- package/dist/scope/delivery-evidence.d.ts +3 -3
- package/dist/scope/delivery-evidence.js +22 -27
- package/dist/scope/index.d.ts +1 -0
- package/dist/scope/index.js +1 -0
- package/dist/scope/parent-lineage.js +7 -6
- package/dist/scope/project-invariant-coverage.d.ts +54 -0
- package/dist/scope/project-invariant-coverage.js +322 -0
- package/dist/scope/transition.js +1 -1
- package/dist/scope-provenance/compare-intent.d.ts +27 -0
- package/dist/scope-provenance/compare-intent.js +233 -0
- package/dist/scope-provenance/digest.d.ts +18 -1
- package/dist/scope-provenance/digest.js +16 -11
- package/dist/scope-provenance/evaluate.d.ts +5 -1
- package/dist/scope-provenance/evaluate.js +64 -4
- package/dist/scope-provenance/extract-intent.d.ts +43 -0
- package/dist/scope-provenance/extract-intent.js +262 -0
- package/dist/scope-provenance/index.d.ts +7 -1
- package/dist/scope-provenance/index.js +7 -1
- package/dist/scope-provenance/intent-digest.d.ts +12 -0
- package/dist/scope-provenance/intent-digest.js +33 -0
- package/dist/scope-provenance/intent-evaluate.d.ts +43 -0
- package/dist/scope-provenance/intent-evaluate.js +149 -0
- package/dist/scope-provenance/json-tokenizer.d.ts +19 -0
- package/dist/scope-provenance/json-tokenizer.js +225 -0
- package/dist/scope-provenance/known-machine.d.ts +38 -0
- package/dist/scope-provenance/known-machine.js +157 -0
- package/dist/scope-provenance/mint-artifacts.d.ts +102 -0
- package/dist/scope-provenance/mint-artifacts.js +425 -0
- package/dist/session/ac-pass-banking.d.ts +4 -36
- package/dist/session/ac-pass-banking.js +30 -77
- package/dist/session/ac-pass-reuse.d.ts +53 -0
- package/dist/session/ac-pass-reuse.js +95 -0
- package/dist/session/git.js +19 -4
- package/dist/session/index.d.ts +4 -0
- package/dist/session/index.js +4 -0
- package/dist/session/occupancy-stress-worker.d.ts +7 -0
- package/dist/session/occupancy-stress-worker.js +36 -0
- package/dist/session/occupancy.d.ts +97 -0
- package/dist/session/occupancy.js +422 -0
- package/dist/session/openclaw-soft-rebind-deposit.js +6 -6
- package/dist/session/product-state-hash.d.ts +26 -0
- package/dist/session/product-state-hash.js +683 -0
- package/dist/session/session-ready.d.ts +2 -0
- package/dist/session/session-ready.js +52 -30
- package/dist/session/session-start.d.ts +7 -0
- package/dist/session/session-start.js +71 -7
- package/dist/session/verify-ac-session-cache.d.ts +42 -0
- package/dist/session/verify-ac-session-cache.js +126 -0
- package/dist/session/verify-session-ritual.d.ts +5 -0
- package/dist/session/verify-session-ritual.js +9 -0
- package/dist/slash/openclaw-deposit.js +13 -9
- package/dist/slice/constants.d.ts +2 -2
- package/dist/slice/constants.js +2 -2
- package/dist/slice/existing.js +9 -3
- package/dist/slice/lock.d.ts +16 -1
- package/dist/slice/lock.js +137 -11
- package/dist/story-ready/evaluate.d.ts +2 -0
- package/dist/story-ready/evaluate.js +13 -0
- package/dist/swarm/complete-cohort.d.ts +3 -0
- package/dist/swarm/complete-cohort.js +59 -11
- package/dist/swarm/finalize-cohort.js +33 -2
- package/dist/swarm/launch.d.ts +22 -0
- package/dist/swarm/launch.js +155 -0
- package/dist/swarm/routing-set-cli.d.ts +11 -1
- package/dist/swarm/routing-set-cli.js +13 -3
- package/dist/swarm/routing-verify.d.ts +12 -0
- package/dist/swarm/routing-verify.js +59 -20
- package/dist/swarm/routing.d.ts +14 -0
- package/dist/swarm/routing.js +53 -3
- package/dist/telemetry-coverage/fake-trial.js +10 -0
- package/dist/telemetry-coverage/kinds.js +2 -0
- package/dist/triage/actions/index.js +4 -1
- package/dist/ts-check-lane/index.d.ts +3 -0
- package/dist/ts-check-lane/index.js +2 -0
- package/dist/ts-check-lane/progress-reporter.d.ts +17 -0
- package/dist/ts-check-lane/progress-reporter.js +38 -0
- package/dist/ts-check-lane/progress.d.ts +32 -0
- package/dist/ts-check-lane/progress.js +57 -0
- package/dist/ts-check-lane/run-lane.d.ts +3 -1
- package/dist/ts-check-lane/run-lane.js +8 -6
- package/dist/validate-content/validate-links.d.ts +8 -0
- package/dist/validate-content/validate-links.js +9 -15
- package/dist/value/feedback-file.js +12 -2
- package/dist/vbrief-build/index.d.ts +1 -0
- package/dist/vbrief-build/index.js +1 -0
- package/dist/vbrief-build/project-invariants-io.d.ts +16 -0
- package/dist/vbrief-build/project-invariants-io.js +41 -0
- package/dist/vbrief-reconcile/graph.js +18 -1
- package/dist/vbrief-reconcile/index.d.ts +1 -1
- package/dist/vbrief-reconcile/index.js +1 -1
- package/dist/vbrief-reconcile/types.d.ts +13 -0
- package/dist/vbrief-reconcile/umbrellas.d.ts +14 -0
- package/dist/vbrief-reconcile/umbrellas.js +261 -26
- package/dist/vbrief-validate/index.d.ts +1 -1
- package/dist/vbrief-validate/index.js +1 -1
- package/dist/vbrief-validate/plan-hooks.d.ts +4 -0
- package/dist/vbrief-validate/plan-hooks.js +68 -0
- package/dist/verify-ac/evaluate.d.ts +7 -1
- package/dist/verify-ac/evaluate.js +34 -26
- package/dist/verify-ac/flag.d.ts +4 -0
- package/dist/verify-ac/flag.js +16 -0
- package/dist/verify-ac/index.d.ts +2 -2
- package/dist/verify-ac/index.js +2 -2
- package/dist/verify-source/contained-writes.js +2 -0
- package/dist/verify-source/verify-stubs.d.ts +15 -1
- package/dist/verify-source/verify-stubs.js +20 -16
- package/dist/vitest-runner/win32-coverage-tmp-setup.d.ts +5 -6
- package/dist/vitest-runner/win32-coverage-tmp-setup.js +5 -6
- package/dist/xbrief-migrate/migrate-project.js +2 -5
- package/package.json +3 -3
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* Refs #1942, #1430, #1671.
|
|
9
9
|
*/
|
|
10
10
|
import { execFileSync } from "node:child_process";
|
|
11
|
-
import { existsSync, readFileSync,
|
|
11
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
12
12
|
import { platform as osPlatform } from "node:os";
|
|
13
13
|
import { join, resolve } from "node:path";
|
|
14
14
|
import { assertDepositContained } from "../deposit/contain.js";
|
|
@@ -18,6 +18,8 @@ import { resolveInstalledContentRoot } from "../deposit/resolve-content.js";
|
|
|
18
18
|
import { manifestTagToVersion, parseInstallManifest } from "../doctor/manifest.js";
|
|
19
19
|
import { readCorePackageVersion } from "../engine-version.js";
|
|
20
20
|
import { readLiveGeneration, stampLiveGeneration } from "../freshness/generation.js";
|
|
21
|
+
import { containedRename } from "../fs/contained-write.js";
|
|
22
|
+
import { activeMutationLedger, formatMutationSummary, mutationSummaryJson, runInPortRecordMode, runWithMutationLedger, snapshotMutationSummary, } from "../fs/mutation-ledger.js";
|
|
21
23
|
import { resolveLifecycleRoot } from "../layout/resolve.js";
|
|
22
24
|
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";
|
|
23
25
|
import { runOrgForceOnMigration } from "../policy/org-force-on-migration.js";
|
|
@@ -189,7 +191,11 @@ function migrateLegacyInstallManifest(projectDir, canonicalManifestPath) {
|
|
|
189
191
|
const canonicalVersion = manifestTagToVersion(parseInstallManifest(readFileSync(canonical, "utf8")));
|
|
190
192
|
if (legacyVersion !== null && legacyVersion === canonicalVersion)
|
|
191
193
|
return;
|
|
192
|
-
|
|
194
|
+
containedRename({
|
|
195
|
+
root: projectDir,
|
|
196
|
+
from: legacy,
|
|
197
|
+
to: join(projectDir, ".deft", "VERSION.premigrate"),
|
|
198
|
+
});
|
|
193
199
|
}
|
|
194
200
|
catch {
|
|
195
201
|
// best-effort
|
|
@@ -312,8 +318,8 @@ export function printRefreshSideEffects(io, effects) {
|
|
|
312
318
|
}
|
|
313
319
|
io.printf("\nAGENTS.md refresh side effects (#1671): the refresh and framework payload swap\n");
|
|
314
320
|
io.printf("left these framework files with uncommitted changes -- they belong in the\n");
|
|
315
|
-
io.printf("framework deposit commit (the installer stages
|
|
316
|
-
io.printf("
|
|
321
|
+
io.printf("framework deposit commit (the installer stages this-run installer-managed\n");
|
|
322
|
+
io.printf("paths; ledger remainder stays unstaged):\n");
|
|
317
323
|
for (const file of effects.files) {
|
|
318
324
|
io.printf(` ${file}\n`);
|
|
319
325
|
}
|
|
@@ -335,6 +341,32 @@ export function buildVersionSkewNotice(engineVersion, contentVersion, previousDe
|
|
|
335
341
|
}
|
|
336
342
|
return null;
|
|
337
343
|
}
|
|
344
|
+
function displayVersion(version) {
|
|
345
|
+
return version === null || version.trim() === "" ? "unknown" : normalizeVersion(version);
|
|
346
|
+
}
|
|
347
|
+
/** True when recorded deposit version and content package version disagree (#3437). */
|
|
348
|
+
export function depositRefreshPending(previousDepositVersion, contentVersion) {
|
|
349
|
+
if (previousDepositVersion === null || previousDepositVersion.trim() === "")
|
|
350
|
+
return true;
|
|
351
|
+
return normalizeVersion(previousDepositVersion) !== normalizeVersion(contentVersion);
|
|
352
|
+
}
|
|
353
|
+
/** First-line dry-run headline when deposit manifest and content disagree (#3437). */
|
|
354
|
+
export function buildDepositVersionSkewHeadline(previousDepositVersion, contentVersion) {
|
|
355
|
+
if (!depositRefreshPending(previousDepositVersion, contentVersion))
|
|
356
|
+
return null;
|
|
357
|
+
return (`[deft update] Version skew: deposit manifest ${displayVersion(previousDepositVersion)}` +
|
|
358
|
+
` -> content ${displayVersion(contentVersion)}`);
|
|
359
|
+
}
|
|
360
|
+
async function readDryRunVersions(projectDir, seams = {}) {
|
|
361
|
+
const deftDir = join(resolve(projectDir), CANONICAL_INSTALL_ROOT);
|
|
362
|
+
const resolveContent = seams.resolveContentRoot ?? resolveInstalledContentRoot;
|
|
363
|
+
const readPackageVersion = seams.readPackageVersion ?? readCorePackageVersion;
|
|
364
|
+
const contentRoot = await resolveContent();
|
|
365
|
+
return {
|
|
366
|
+
previousVersion: readRecordedDepositVersion(deftDir),
|
|
367
|
+
contentVersion: readContentPackageVersion(contentRoot, readPackageVersion),
|
|
368
|
+
};
|
|
369
|
+
}
|
|
338
370
|
export function buildUpdateSummaryJson(input) {
|
|
339
371
|
const { result, options, updateState, readiness } = input;
|
|
340
372
|
return {
|
|
@@ -363,6 +395,8 @@ export function buildUpdateSummaryJson(input) {
|
|
|
363
395
|
dirty_tree: false,
|
|
364
396
|
dirty_files: [],
|
|
365
397
|
staged_paths: result.stagedPaths,
|
|
398
|
+
mutations: mutationSummaryJson(result.mutations),
|
|
399
|
+
prettier_sensitive_rewrites: prettierSensitiveRewrites(result.mutations),
|
|
366
400
|
backup_path: "",
|
|
367
401
|
previous_version: result.previousDepositVersion ?? "",
|
|
368
402
|
content_version: result.contentVersion,
|
|
@@ -370,6 +404,19 @@ export function buildUpdateSummaryJson(input) {
|
|
|
370
404
|
agents_md_updated: result.agentsMdUpdated,
|
|
371
405
|
};
|
|
372
406
|
}
|
|
407
|
+
/** Consumer-owned prefixes whose rewrite can fail a repo Prettier/fmt gate (#3395). */
|
|
408
|
+
export const PRETTIER_SENSITIVE_CONSUMER_PREFIXES = ["xbrief/schemas/"];
|
|
409
|
+
export const PRETTIER_SENSITIVE_FMT_HINT = "run `task fmt` or your repo formatter before the upgrade PR";
|
|
410
|
+
/** Ledger `wrote` paths under prettier-sensitive consumer-owned prefixes. */
|
|
411
|
+
export function prettierSensitiveRewrites(summary) {
|
|
412
|
+
return summary.wrote.filter((path) => PRETTIER_SENSITIVE_CONSUMER_PREFIXES.some((prefix) => path === prefix.slice(0, -1) || path.startsWith(prefix)));
|
|
413
|
+
}
|
|
414
|
+
export function formatPrettierSensitiveAnnounce(paths) {
|
|
415
|
+
if (paths.length === 0)
|
|
416
|
+
return "";
|
|
417
|
+
return (`Rewritten consumer-owned paths (${PRETTIER_SENSITIVE_FMT_HINT}):\n` +
|
|
418
|
+
paths.map((path) => ` ${path}\n`).join(""));
|
|
419
|
+
}
|
|
373
420
|
export function printUpdateComplete(result, io, updateState) {
|
|
374
421
|
io.printf(result.alreadyCurrent
|
|
375
422
|
? "\nOK Deft framework payload already current.\n\n"
|
|
@@ -384,11 +431,22 @@ export function printUpdateComplete(result, io, updateState) {
|
|
|
384
431
|
if (result.versionSkewNotice) {
|
|
385
432
|
io.printf(`\n${result.versionSkewNotice}\n`);
|
|
386
433
|
}
|
|
434
|
+
const mutationText = formatMutationSummary(result.mutations);
|
|
435
|
+
if (mutationText.length > 0) {
|
|
436
|
+
io.printf(`\n${mutationText}`);
|
|
437
|
+
}
|
|
438
|
+
const prettierText = formatPrettierSensitiveAnnounce(prettierSensitiveRewrites(result.mutations));
|
|
439
|
+
if (prettierText.length > 0) {
|
|
440
|
+
io.printf(`\n${prettierText}`);
|
|
441
|
+
}
|
|
387
442
|
printMigrateNudgeIfNeeded(result.projectDir, io);
|
|
388
443
|
io.printf("\n");
|
|
389
444
|
}
|
|
390
445
|
export async function runRefreshDeposit(args, io, seams = {}) {
|
|
391
446
|
const projectDir = resolve(args.projectDir);
|
|
447
|
+
if (activeMutationLedger() === undefined) {
|
|
448
|
+
return runWithMutationLedger(projectDir, () => runRefreshDeposit(args, io, seams));
|
|
449
|
+
}
|
|
392
450
|
const deftDir = join(projectDir, CANONICAL_INSTALL_ROOT);
|
|
393
451
|
// #1912: refuse a legacy on-disk layout BEFORE any refresh. The npm CLI never
|
|
394
452
|
// migrates -- the frozen Go bridge does (stage 1), then the npm path (stage 2).
|
|
@@ -545,6 +603,7 @@ export async function runRefreshDeposit(args, io, seams = {}) {
|
|
|
545
603
|
const stagedResult = depositStagePaths(projectDir, {
|
|
546
604
|
includeTaskfile: taskfileWired,
|
|
547
605
|
includeCore: !alreadyCurrent,
|
|
606
|
+
printf: (text) => io.printf(text),
|
|
548
607
|
});
|
|
549
608
|
stagedPaths = stagedResult.stagedPaths;
|
|
550
609
|
if (!alreadyCurrent) {
|
|
@@ -552,7 +611,7 @@ export async function runRefreshDeposit(args, io, seams = {}) {
|
|
|
552
611
|
}
|
|
553
612
|
else if (stagedResult.stagedPaths.length > 0) {
|
|
554
613
|
io.printf("\nUpdated installer-managed projections for the current framework deposit:\n");
|
|
555
|
-
io.printf(` git add ${stagedResult.stagedPaths.join(" ")}\n`);
|
|
614
|
+
io.printf(` git add -- ${stagedResult.stagedPaths.join(" ")}\n`);
|
|
556
615
|
}
|
|
557
616
|
}
|
|
558
617
|
printRefreshSideEffects(io, { ...effects, payloadSwapped: !alreadyCurrent });
|
|
@@ -572,6 +631,7 @@ export async function runRefreshDeposit(args, io, seams = {}) {
|
|
|
572
631
|
legacyLayout: false,
|
|
573
632
|
taskfileWired,
|
|
574
633
|
stagedPaths,
|
|
634
|
+
mutations: snapshotMutationSummary(),
|
|
575
635
|
};
|
|
576
636
|
}
|
|
577
637
|
function buildRefusalJson(state, projectDir, message, command) {
|
|
@@ -602,10 +662,24 @@ function emitMigrationRequired(options, io, projectDir, classification) {
|
|
|
602
662
|
}
|
|
603
663
|
return UPDATE_REFUSED_EXIT_CODE;
|
|
604
664
|
}
|
|
605
|
-
/**
|
|
606
|
-
|
|
665
|
+
/**
|
|
666
|
+
* Dest effects the port cannot yet capture (ADR-004 condition 3).
|
|
667
|
+
* Shrink as sites fold; never drop the printed label.
|
|
668
|
+
*/
|
|
669
|
+
export const UPDATE_DRY_RUN_EXCLUSIONS = ["openclaw-home-skills"];
|
|
670
|
+
export const UPDATE_DRY_RUN_EXCLUSIONS_LABEL = "Excluded from dest plan (not yet captured by the port): OpenClaw $HOME skills.";
|
|
671
|
+
/** Emit the classified plan plus recorded port dest mutations (ADR-004). */
|
|
672
|
+
async function emitDryRunPlan(options, io, projectDir, classification) {
|
|
673
|
+
const { previousVersion, contentVersion } = await readDryRunVersions(projectDir, options.seams ?? {});
|
|
674
|
+
const refreshPending = depositRefreshPending(previousVersion, contentVersion);
|
|
675
|
+
const skewHeadline = buildDepositVersionSkewHeadline(previousVersion, contentVersion);
|
|
607
676
|
const { state, plan: resolutionPlan } = classification;
|
|
608
|
-
|
|
677
|
+
const silentIo = { printf: () => undefined };
|
|
678
|
+
const result = await runInPortRecordMode(() => runRefreshDeposit(options, silentIo, options.seams ?? {}));
|
|
679
|
+
if (skewHeadline) {
|
|
680
|
+
io.printf(`${skewHeadline}\n`);
|
|
681
|
+
}
|
|
682
|
+
io.printf(`\n[deft update] dry-run -- dest plan (recorded port calls, no changes written):\n`);
|
|
609
683
|
io.printf(` State : ${state}\n`);
|
|
610
684
|
io.printf(` Mode : ${resolutionPlan.mode}\n`);
|
|
611
685
|
io.printf(` Root cause : ${resolutionPlan.nextAction.rootCause}\n`);
|
|
@@ -613,6 +687,14 @@ function emitDryRunPlan(options, io, projectDir, classification) {
|
|
|
613
687
|
for (const warning of resolutionPlan.warnings) {
|
|
614
688
|
io.printf(` Warning : ${warning}\n`);
|
|
615
689
|
}
|
|
690
|
+
const mutationText = formatMutationSummary(result.mutations);
|
|
691
|
+
if (mutationText.length > 0) {
|
|
692
|
+
io.printf(`\n${mutationText}`);
|
|
693
|
+
}
|
|
694
|
+
else {
|
|
695
|
+
io.printf(`\nNo dest mutations recorded.\n`);
|
|
696
|
+
}
|
|
697
|
+
io.printf(`\n${UPDATE_DRY_RUN_EXCLUSIONS_LABEL}\n`);
|
|
616
698
|
if (options.jsonOut) {
|
|
617
699
|
options.writeOut(`${JSON.stringify({
|
|
618
700
|
success: true,
|
|
@@ -621,8 +703,13 @@ function emitDryRunPlan(options, io, projectDir, classification) {
|
|
|
621
703
|
update_state: state,
|
|
622
704
|
mode: resolutionPlan.mode,
|
|
623
705
|
project_dir: projectDir,
|
|
706
|
+
previous_version: previousVersion ?? "",
|
|
707
|
+
content_version: contentVersion,
|
|
708
|
+
deposit_refresh_pending: refreshPending,
|
|
624
709
|
next_action: resolutionPlan.nextAction,
|
|
625
710
|
warnings: resolutionPlan.warnings,
|
|
711
|
+
mutations: mutationSummaryJson(result.mutations),
|
|
712
|
+
exclusions: [...UPDATE_DRY_RUN_EXCLUSIONS],
|
|
626
713
|
}, null, 2)}\n`);
|
|
627
714
|
}
|
|
628
715
|
return 0;
|
|
@@ -678,7 +765,17 @@ export async function runRefreshDepositCli(options) {
|
|
|
678
765
|
return emitMigrationRequired(options, io, projectDir, classification);
|
|
679
766
|
}
|
|
680
767
|
if (options.dryRun) {
|
|
681
|
-
|
|
768
|
+
try {
|
|
769
|
+
return await emitDryRunPlan(options, io, projectDir, classification);
|
|
770
|
+
}
|
|
771
|
+
catch (cause) {
|
|
772
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
773
|
+
options.writeErr(`directive update: ${message}\n`);
|
|
774
|
+
if (options.jsonOut) {
|
|
775
|
+
options.writeOut(`${JSON.stringify({ success: false, error: message, error_code: "refresh_deposit_failed" }, null, 2)}\n`);
|
|
776
|
+
}
|
|
777
|
+
return 1;
|
|
778
|
+
}
|
|
682
779
|
}
|
|
683
780
|
// #2266 a3: self-heal a mismatched / unreachable engine via the keystone
|
|
684
781
|
// global-first ladder before the refresh proceeds.
|
|
@@ -5,15 +5,17 @@
|
|
|
5
5
|
* Refs #1942, #1430, #1463, #1179.
|
|
6
6
|
*/
|
|
7
7
|
import { execFileSync } from "node:child_process";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
9
|
+
import { readdir, stat } from "node:fs/promises";
|
|
10
10
|
import { platform } from "node:os";
|
|
11
11
|
import { join, relative, resolve } from "node:path";
|
|
12
|
-
import { containedWrite } from "../fs/contained-write.js";
|
|
12
|
+
import { containedChmod, containedDestExec, containedMkdir, containedRemove, containedWrite, } from "../fs/contained-write.js";
|
|
13
13
|
import { assertDestinationNotSymlink, ProjectionContainmentError, } from "../fs/projection-containment.js";
|
|
14
14
|
import { agentsRefreshPlan } from "../platform/agents-md.js";
|
|
15
|
+
import { renderCoreGuardBranchSyncIfBlock } from "../policy/branch-sync.js";
|
|
15
16
|
import { MIGRATED_ARTIFACT_DIR } from "../xbrief-migrate/constants.js";
|
|
16
17
|
import { CANONICAL_INSTALL_ROOT } from "./constants.js";
|
|
18
|
+
import { CORE_GUARD_PIN_CONTENT_PYTHON } from "./core-guard-pin-content.js";
|
|
17
19
|
import { assertInstallerAllowlistHonors1430, installerManagedGuardErePatterns } from "./hygiene.js";
|
|
18
20
|
import { writeAgentsSkillsFromInventory } from "./skill-discovery-deposit.js";
|
|
19
21
|
import { syncConsumerXbriefSchemas } from "./xbrief-projections.js";
|
|
@@ -204,7 +206,7 @@ export function writeAgentsMd(projectDir, deftDir, io) {
|
|
|
204
206
|
async function ensureVbriefLifecycleDirs(projectDir) {
|
|
205
207
|
for (const sub of VBRIEF_LIFECYCLE_DIRS) {
|
|
206
208
|
const dir = projectionTarget(projectDir, MIGRATED_ARTIFACT_DIR, sub);
|
|
207
|
-
|
|
209
|
+
containedMkdir({ root: projectDir, target: dir });
|
|
208
210
|
const gitkeep = join(dir, ".gitkeep");
|
|
209
211
|
try {
|
|
210
212
|
await stat(gitkeep);
|
|
@@ -243,11 +245,11 @@ export async function writeConsumerVbrief(projectDir, deftDir, io) {
|
|
|
243
245
|
: "vbrief/ already present at project root — skipping.\n");
|
|
244
246
|
return schemasChanged;
|
|
245
247
|
}
|
|
246
|
-
|
|
248
|
+
containedMkdir({ root: projectDir, target: consumerVbrief });
|
|
247
249
|
if (!vbriefMdPresent) {
|
|
248
250
|
const fwVbriefMd = join(deftDir, "vbrief", "vbrief.md");
|
|
249
251
|
if (existsSync(fwVbriefMd)) {
|
|
250
|
-
|
|
252
|
+
containedProjectWrite(projectDir, vbriefMdDst, readFileSync(fwVbriefMd));
|
|
251
253
|
}
|
|
252
254
|
else {
|
|
253
255
|
containedProjectWrite(projectDir, vbriefMdDst, VBRIEF_README_BODY);
|
|
@@ -373,7 +375,7 @@ export function writeConsumerGitHooks(projectDir, deftDir, io, seams = {}) {
|
|
|
373
375
|
try {
|
|
374
376
|
const mode = statSync(dst).mode & 0o777;
|
|
375
377
|
if ((mode & 0o111) === 0) {
|
|
376
|
-
|
|
378
|
+
containedChmod({ root: projectDir, target: dst, mode: HOOK_FILE_MODE });
|
|
377
379
|
filesDeposited = true;
|
|
378
380
|
}
|
|
379
381
|
}
|
|
@@ -394,15 +396,12 @@ export function writeConsumerGitHooks(projectDir, deftDir, io, seams = {}) {
|
|
|
394
396
|
}
|
|
395
397
|
});
|
|
396
398
|
const setHooksPath = seams.setHooksPath ??
|
|
397
|
-
((dir, value) => {
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
return false;
|
|
404
|
-
}
|
|
405
|
-
});
|
|
399
|
+
((dir, value) => containedDestExec({
|
|
400
|
+
root: dir,
|
|
401
|
+
destTarget: join(".git", "config"),
|
|
402
|
+
file: "git",
|
|
403
|
+
args: ["-C", dir, "config", "core.hooksPath", value],
|
|
404
|
+
}).ok);
|
|
406
405
|
const target = ".githooks";
|
|
407
406
|
const current = getHooksPath(projectDir) ?? "";
|
|
408
407
|
let configWired = false;
|
|
@@ -550,274 +549,20 @@ export function assertCoreGuardWorkflowLoadable(content) {
|
|
|
550
549
|
}
|
|
551
550
|
/**
|
|
552
551
|
* Embedded python3 content check for package.json / lockfiles when co-travelling
|
|
553
|
-
* with .deft/core/** (#3193). Mirrors TS `isUpgradePinPathContentAllowed`.
|
|
552
|
+
* with .deft/core/** (#3193 / #3427). Mirrors TS `isUpgradePinPathContentAllowed`.
|
|
554
553
|
* Uses a single-quoted heredoc so GHA does not interpolate shell variables
|
|
555
|
-
* inside the Python source (BASE/HEAD are argv).
|
|
554
|
+
* inside the Python source (BASE/HEAD are argv). Body is
|
|
555
|
+
* {@link CORE_GUARD_PIN_CONTENT_PYTHON} (twin of the Go embed).
|
|
556
556
|
*
|
|
557
557
|
* Every emitted line is indented to {@link CORE_GUARD_RUN_INDENT} so the YAML
|
|
558
558
|
* `run: |` block stays intact (#3345). After GHA strips the common indent, the
|
|
559
559
|
* shell heredoc body reaches python3 with correct relative indentation.
|
|
560
560
|
*/
|
|
561
561
|
function coreGuardPinContentPython() {
|
|
562
|
-
//
|
|
563
|
-
|
|
564
|
-
"import json, re, subprocess, sys",
|
|
565
|
-
"base_sha, head_sha = sys.argv[1], sys.argv[2]",
|
|
566
|
-
"def git_show(sha, path):",
|
|
567
|
-
" try:",
|
|
568
|
-
" return subprocess.check_output(['git','show',f'{sha}:{path}'], text=True, stderr=subprocess.DEVNULL)",
|
|
569
|
-
" except subprocess.CalledProcessError:",
|
|
570
|
-
" return ''",
|
|
571
|
-
"def changed_names():",
|
|
572
|
-
" out = subprocess.check_output(['git','diff','--name-only',base_sha,head_sha], text=True)",
|
|
573
|
-
" return [p for p in out.splitlines() if p]",
|
|
574
|
-
"def is_dir_key(n):",
|
|
575
|
-
" return n.startswith('@deftai/directive')",
|
|
576
|
-
"DEP_FIELDS = ('dependencies','devDependencies','optionalDependencies','peerDependencies')",
|
|
577
|
-
"def deep_eq(a,b):",
|
|
578
|
-
" if a is b: return True",
|
|
579
|
-
" if type(a) is not type(b): return False",
|
|
580
|
-
" if isinstance(a, dict):",
|
|
581
|
-
" if set(a) != set(b): return False",
|
|
582
|
-
" return all(deep_eq(a[k], b[k]) for k in a)",
|
|
583
|
-
" if isinstance(a, list):",
|
|
584
|
-
" return len(a)==len(b) and all(deep_eq(x,y) for x,y in zip(a,b))",
|
|
585
|
-
" return a == b",
|
|
586
|
-
"def strip_pins(obj):",
|
|
587
|
-
" if not isinstance(obj, dict): return obj",
|
|
588
|
-
" out = {}",
|
|
589
|
-
" for k,v in obj.items():",
|
|
590
|
-
" if k in DEP_FIELDS and isinstance(v, dict):",
|
|
591
|
-
" out[k] = {dk:dv for dk,dv in v.items() if not is_dir_key(dk)}",
|
|
592
|
-
" else:",
|
|
593
|
-
" out[k] = v",
|
|
594
|
-
" return out",
|
|
595
|
-
"def pkg_pin_only(base, head):",
|
|
596
|
-
" try:",
|
|
597
|
-
" b, h = json.loads(base or '{}'), json.loads(head or '{}')",
|
|
598
|
-
" except json.JSONDecodeError:",
|
|
599
|
-
" return False",
|
|
600
|
-
" return deep_eq(strip_pins(b), strip_pins(h))",
|
|
601
|
-
"def collect_deps(pkg):",
|
|
602
|
-
" out = {}",
|
|
603
|
-
" if not isinstance(pkg, dict): return out",
|
|
604
|
-
" for f in DEP_FIELDS:",
|
|
605
|
-
" block = pkg.get(f)",
|
|
606
|
-
" if isinstance(block, dict):",
|
|
607
|
-
" for k,v in block.items():",
|
|
608
|
-
" if isinstance(v, str): out[k]=v",
|
|
609
|
-
" elif isinstance(v, dict) and isinstance(v.get('version'), str): out[k]=v['version']",
|
|
610
|
-
" elif v is not None: out[k]=str(v)",
|
|
611
|
-
" return out",
|
|
612
|
-
"def only_dir_diff(bd, hd):",
|
|
613
|
-
" keys = set(bd)|set(hd)",
|
|
614
|
-
" for k in keys:",
|
|
615
|
-
" if is_dir_key(k): continue",
|
|
616
|
-
" if bd.get(k) != hd.get(k): return False",
|
|
617
|
-
" return True",
|
|
618
|
-
"def npm_root_deps(lock):",
|
|
619
|
-
" pkgs = lock.get('packages') if isinstance(lock, dict) else None",
|
|
620
|
-
" if isinstance(pkgs, dict) and isinstance(pkgs.get(''), dict):",
|
|
621
|
-
" return collect_deps(pkgs[''])",
|
|
622
|
-
" deps = lock.get('dependencies') if isinstance(lock, dict) else None",
|
|
623
|
-
" out = {}",
|
|
624
|
-
" if isinstance(deps, dict):",
|
|
625
|
-
" for k,v in deps.items():",
|
|
626
|
-
" if isinstance(v, dict): out[k]=str(v.get('version', v))",
|
|
627
|
-
" elif isinstance(v, str): out[k]=v",
|
|
628
|
-
" return out",
|
|
629
|
-
"def npm_lock_ok(base, head):",
|
|
630
|
-
" try:",
|
|
631
|
-
" b, h = json.loads(base or '{}'), json.loads(head or '{}')",
|
|
632
|
-
" except json.JSONDecodeError:",
|
|
633
|
-
" return False",
|
|
634
|
-
" br, hr = npm_root_deps(b), npm_root_deps(h)",
|
|
635
|
-
" if not only_dir_diff(br, hr): return False",
|
|
636
|
-
" bp, hp = b.get('packages') or {}, h.get('packages') or {}",
|
|
637
|
-
" if not isinstance(bp, dict): bp = {}",
|
|
638
|
-
" if not isinstance(hp, dict): hp = {}",
|
|
639
|
-
" all_keys = set(bp)|set(hp)",
|
|
640
|
-
" for key in all_keys:",
|
|
641
|
-
" if key == '': continue",
|
|
642
|
-
" if 'node_modules/@deftai/directive' in key or '/@deftai/directive/' in key: continue",
|
|
643
|
-
" if not deep_eq(bp.get(key), hp.get(key)): return False",
|
|
644
|
-
" if not bp and not hp:",
|
|
645
|
-
" def strip_dir(v):",
|
|
646
|
-
" if not isinstance(v, dict): return v",
|
|
647
|
-
" out = {}",
|
|
648
|
-
" for k, val in v.items():",
|
|
649
|
-
" if is_dir_key(k): continue",
|
|
650
|
-
" if isinstance(val, dict):",
|
|
651
|
-
" nxt = dict(val)",
|
|
652
|
-
" if 'dependencies' in val: nxt['dependencies'] = strip_dir(val['dependencies'])",
|
|
653
|
-
" out[k] = nxt",
|
|
654
|
-
" else: out[k] = val",
|
|
655
|
-
" return out",
|
|
656
|
-
" if not deep_eq(strip_dir(b.get('dependencies')), strip_dir(h.get('dependencies'))): return False",
|
|
657
|
-
" return True",
|
|
658
|
-
"def pnpm_root_deps(raw):",
|
|
659
|
-
" out = {}",
|
|
660
|
-
" in_imp = in_root = in_dep = False",
|
|
661
|
-
" cur = None",
|
|
662
|
-
" def unq(s):",
|
|
663
|
-
" s=s.strip()",
|
|
664
|
-
' if len(s)>=2 and s[0]==s[-1] and s[0] in "\'\\"": return s[1:-1]',
|
|
665
|
-
" return s",
|
|
666
|
-
" for line in raw.splitlines():",
|
|
667
|
-
" if re.match(r'^importers:\\s*$', line):",
|
|
668
|
-
" in_imp, in_root, in_dep, cur = True, False, False, None",
|
|
669
|
-
" continue",
|
|
670
|
-
" if not in_imp: continue",
|
|
671
|
-
" if re.match(r'^[^\\s#]', line) and not line.startswith('importers'):",
|
|
672
|
-
" break",
|
|
673
|
-
' if re.match(r"^ {2}(?:\\.|\'\\.\'|\\"\\.\\"):\\s*$", line):',
|
|
674
|
-
" in_root, in_dep, cur = True, False, None",
|
|
675
|
-
" continue",
|
|
676
|
-
" if in_root and re.match(r'^ {2}\\S', line) and not re.match(r'^ {2}\\.', line):",
|
|
677
|
-
" in_root, in_dep, cur = False, False, None",
|
|
678
|
-
" continue",
|
|
679
|
-
" if not in_root: continue",
|
|
680
|
-
" if re.match(r'^ {4}(?:dependencies|devDependencies|optionalDependencies|peerDependencies):\\s*$', line):",
|
|
681
|
-
" in_dep, cur = True, None",
|
|
682
|
-
" continue",
|
|
683
|
-
" if in_dep and re.match(r'^ {4}\\S', line):",
|
|
684
|
-
" in_dep, cur = False, None",
|
|
685
|
-
" continue",
|
|
686
|
-
" if not in_dep: continue",
|
|
687
|
-
" m = re.match(r'^ {6}(.+?):\\s*$', line)",
|
|
688
|
-
" if m:",
|
|
689
|
-
" cur = unq(m.group(1)); continue",
|
|
690
|
-
" if cur:",
|
|
691
|
-
" vm = re.match(r'^ {8}version:\\s*(.+?)\\s*$', line)",
|
|
692
|
-
" if vm:",
|
|
693
|
-
" out[cur] = unq(vm.group(1)); cur = None",
|
|
694
|
-
" return out",
|
|
695
|
-
"def pnpm_packages_by_name(raw):",
|
|
696
|
-
" blocks, out = {}, {}",
|
|
697
|
-
" in_pkg, cur, buf = False, None, []",
|
|
698
|
-
" def flush():",
|
|
699
|
-
" nonlocal cur, buf",
|
|
700
|
-
" if cur is None: return",
|
|
701
|
-
" blocks.setdefault(cur, []).append('\\n'.join(buf))",
|
|
702
|
-
" cur, buf = None, []",
|
|
703
|
-
" def unq(s):",
|
|
704
|
-
" s=s.strip()",
|
|
705
|
-
' if len(s)>=2 and s[0]==s[-1] and s[0] in "\'\\"": return s[1:-1]',
|
|
706
|
-
" return s",
|
|
707
|
-
" for line in raw.splitlines():",
|
|
708
|
-
" if re.match(r'^packages:\\s*$', line):",
|
|
709
|
-
" flush(); in_pkg = True; continue",
|
|
710
|
-
" if not in_pkg: continue",
|
|
711
|
-
" if re.match(r'^[^\\s#]', line) and not line.startswith('packages'):",
|
|
712
|
-
" flush(); break",
|
|
713
|
-
" m = re.match(r'^ {2}(.+?):\\s*$', line)",
|
|
714
|
-
" if m:",
|
|
715
|
-
" flush()",
|
|
716
|
-
" key = unq(m.group(1))",
|
|
717
|
-
" at = key.find('@', 1) if key.startswith('@') else key.find('@')",
|
|
718
|
-
" cur = key[:at] if at > 0 else key",
|
|
719
|
-
" buf = [line]; continue",
|
|
720
|
-
" if cur is not None: buf.append(line)",
|
|
721
|
-
" flush()",
|
|
722
|
-
" for name, blist in blocks.items():",
|
|
723
|
-
" out[name] = '\\n---\\n'.join(sorted(blist))",
|
|
724
|
-
" return out",
|
|
725
|
-
"def pnpm_section_by_name(raw, section):",
|
|
726
|
-
" blocks, out = {}, {}",
|
|
727
|
-
" in_sec, cur, buf = False, None, []",
|
|
728
|
-
" sec_re = re.compile(r'^' + re.escape(section) + r':\\s*$')",
|
|
729
|
-
" def flush():",
|
|
730
|
-
" nonlocal cur, buf",
|
|
731
|
-
" if cur is None: return",
|
|
732
|
-
" blocks.setdefault(cur, []).append('\\n'.join(buf))",
|
|
733
|
-
" cur, buf = None, []",
|
|
734
|
-
" def unq(s):",
|
|
735
|
-
" s=s.strip()",
|
|
736
|
-
' if len(s)>=2 and s[0]==s[-1] and s[0] in "\'\\"": return s[1:-1]',
|
|
737
|
-
" return s",
|
|
738
|
-
" for line in raw.splitlines():",
|
|
739
|
-
" if sec_re.match(line):",
|
|
740
|
-
" flush(); in_sec = True; continue",
|
|
741
|
-
" if not in_sec: continue",
|
|
742
|
-
" if re.match(r'^[^\\s#]', line) and not line.startswith(section):",
|
|
743
|
-
" flush(); break",
|
|
744
|
-
" m = re.match(r'^ {2}(.+?):\\s*$', line)",
|
|
745
|
-
" if m:",
|
|
746
|
-
" flush()",
|
|
747
|
-
" key = unq(m.group(1))",
|
|
748
|
-
" at = key.find('@', 1) if key.startswith('@') else key.find('@')",
|
|
749
|
-
" cur = key[:at] if at > 0 else key",
|
|
750
|
-
" buf = [line]; continue",
|
|
751
|
-
" if cur is not None: buf.append(line)",
|
|
752
|
-
" flush()",
|
|
753
|
-
" for name, blist in blocks.items():",
|
|
754
|
-
" out[name] = '\\n---\\n'.join(sorted(blist))",
|
|
755
|
-
" return out",
|
|
756
|
-
"def pnpm_ok(base, head):",
|
|
757
|
-
" br, hr = pnpm_root_deps(base), pnpm_root_deps(head)",
|
|
758
|
-
" if not only_dir_diff(br, hr): return False",
|
|
759
|
-
" for section in ('packages', 'snapshots'):",
|
|
760
|
-
" bp, hp = pnpm_section_by_name(base, section), pnpm_section_by_name(head, section)",
|
|
761
|
-
" for name in set(bp)|set(hp):",
|
|
762
|
-
" if is_dir_key(name): continue",
|
|
763
|
-
" if bp.get(name) != hp.get(name): return False",
|
|
764
|
-
" return True",
|
|
765
|
-
"def yarn_blocks(raw):",
|
|
766
|
-
" blocks = {}",
|
|
767
|
-
" names, buf = [], []",
|
|
768
|
-
" def flush():",
|
|
769
|
-
" nonlocal names, buf",
|
|
770
|
-
" if not names: return",
|
|
771
|
-
" body = '\\n'.join(buf)",
|
|
772
|
-
" for n in names: blocks[n] = body",
|
|
773
|
-
" names, buf = [], []",
|
|
774
|
-
" for line in raw.splitlines():",
|
|
775
|
-
" if line == '' or line.startswith('#'):",
|
|
776
|
-
" if names and line == '': flush()",
|
|
777
|
-
" continue",
|
|
778
|
-
" if not re.match(r'^\\s', line) and line.endswith(':'):",
|
|
779
|
-
" flush()",
|
|
780
|
-
" header = line[:-1]",
|
|
781
|
-
" names = []",
|
|
782
|
-
" for part in header.split(','):",
|
|
783
|
-
" p = part.strip()",
|
|
784
|
-
' if len(p)>=2 and p[0]==p[-1] and p[0] in "\'\\"": p = p[1:-1]',
|
|
785
|
-
" at = p.find('@', 1) if p.startswith('@') else p.find('@')",
|
|
786
|
-
" names.append(p[:at] if at > 0 else p)",
|
|
787
|
-
" buf = [line]",
|
|
788
|
-
" continue",
|
|
789
|
-
" if names: buf.append(line)",
|
|
790
|
-
" flush()",
|
|
791
|
-
" return blocks",
|
|
792
|
-
"def yarn_ok(base, head):",
|
|
793
|
-
" bb, hb = yarn_blocks(base), yarn_blocks(head)",
|
|
794
|
-
" for name in set(bb)|set(hb):",
|
|
795
|
-
" if is_dir_key(name): continue",
|
|
796
|
-
" if bb.get(name) != hb.get(name): return False",
|
|
797
|
-
" return True",
|
|
798
|
-
"paths = changed_names()",
|
|
799
|
-
"core = [p for p in paths if p == '.deft/core' or p.startswith('.deft/core/')]",
|
|
800
|
-
"if not core:",
|
|
801
|
-
" sys.exit(0)",
|
|
802
|
-
"checks = {",
|
|
803
|
-
" 'package.json': pkg_pin_only,",
|
|
804
|
-
" 'package-lock.json': npm_lock_ok,",
|
|
805
|
-
" 'pnpm-lock.yaml': pnpm_ok,",
|
|
806
|
-
" 'yarn.lock': yarn_ok,",
|
|
807
|
-
"}",
|
|
808
|
-
"bad = []",
|
|
809
|
-
"for path, fn in checks.items():",
|
|
810
|
-
" if path not in paths: continue",
|
|
811
|
-
" if not fn(git_show(base_sha, path), git_show(head_sha, path)):",
|
|
812
|
-
" bad.append(path)",
|
|
813
|
-
"if bad:",
|
|
814
|
-
" print('::error title=deft-core guard (#3193)::package/lock co-travel with .deft/core/** must be @deftai/directive* pin-only + lock follow-through. Rejected: ' + ', '.join(bad))",
|
|
815
|
-
" print('--- content-rejected pin/lock paths ---')",
|
|
816
|
-
" print('\\n'.join(bad))",
|
|
817
|
-
" sys.exit(1)",
|
|
818
|
-
"print('OK: package/lock content is Directive pin unit (#3193).')",
|
|
819
|
-
];
|
|
562
|
+
// Shared SoT with cmd/deft-install/core_guard_pin_content.embed (#3427).
|
|
563
|
+
// Consumer deposit stays a python3 heredoc; this repo has no .py file.
|
|
820
564
|
const run = CORE_GUARD_RUN_INDENT;
|
|
565
|
+
const pyBody = CORE_GUARD_PIN_CONTENT_PYTHON.replace(/\n$/, "").split("\n");
|
|
821
566
|
return [
|
|
822
567
|
`${run}python3 - "$BASE_SHA" "$HEAD_SHA" <<'PY'`,
|
|
823
568
|
...pyBody.map((line) => `${run}${line}`),
|
|
@@ -848,8 +593,9 @@ function coreGuardWorkflowContent() {
|
|
|
848
593
|
assertInstallerAllowlistHonors1430();
|
|
849
594
|
const baseSha = githubActionsExpr("github.event.pull_request.base.sha");
|
|
850
595
|
const headSha = githubActionsExpr("github.event.pull_request.head.sha");
|
|
596
|
+
const baseRef = githubActionsExpr("github.event.pull_request.base.ref");
|
|
851
597
|
return ("name: deft-core-guard\n\n" +
|
|
852
|
-
"# Deft framework guard (#1430 / #3127 / #3193 / #3345): a single PR should not mix changes to the\n" +
|
|
598
|
+
"# Deft framework guard (#1430 / #3127 / #3193 / #3345 / #3388): a single PR should not mix changes to the\n" +
|
|
853
599
|
"# vendored framework payload (.deft/core/**) with true application/product files.\n" +
|
|
854
600
|
"# One upgrade PR MAY include deposit + installer-managed paths + package.json pin/lock\n" +
|
|
855
601
|
"# + .deft/GENERATION.json when package.json is @deftai/directive* dependency-key pin-only\n" +
|
|
@@ -871,6 +617,7 @@ function coreGuardWorkflowContent() {
|
|
|
871
617
|
" env:\n" +
|
|
872
618
|
` BASE_SHA: ${baseSha}\n` +
|
|
873
619
|
` HEAD_SHA: ${headSha}\n` +
|
|
620
|
+
` BASE_REF: ${baseRef}\n` +
|
|
874
621
|
" run: |\n" +
|
|
875
622
|
" set -eu\n" +
|
|
876
623
|
' changed=$(git diff --name-only "$BASE_SHA" "$HEAD_SHA")\n' +
|
|
@@ -879,6 +626,8 @@ function coreGuardWorkflowContent() {
|
|
|
879
626
|
" core=$(printf '%s\\n' \"$changed\" | grep -E '^\\.deft/core/' || true)\n" +
|
|
880
627
|
`${coreGuardAllowlistShell()}\n` +
|
|
881
628
|
' if [ -n "$core" ] && [ -n "$app" ]; then\n' +
|
|
629
|
+
" # Evidence-based branch-sync exemption (#3388). Not name-only.\n" +
|
|
630
|
+
`${renderCoreGuardBranchSyncIfBlock(CORE_GUARD_RUN_INDENT)}\n` +
|
|
882
631
|
' echo "::error title=deft-core guard (#1430)::This PR changes the vendored framework payload (.deft/core/**) AND non-framework files. Split the framework update into its own PR."\n' +
|
|
883
632
|
' echo "--- framework (.deft/core/**) changes ---"; printf \'%s\\n\' "$core"\n' +
|
|
884
633
|
' echo "--- non-framework changes ---"; printf \'%s\\n\' "$app"\n' +
|
|
@@ -1053,7 +802,7 @@ export async function pruneFrameworkSelfTests(projectDir, io) {
|
|
|
1053
802
|
catch {
|
|
1054
803
|
return false;
|
|
1055
804
|
}
|
|
1056
|
-
|
|
805
|
+
containedRemove({ root: projectDir, target: path, recursive: true });
|
|
1057
806
|
io.printf(`Removed vendored framework self-tests (${FRAMEWORK_SELF_TEST_REL}) from the consumer deposit (#1474).\n`);
|
|
1058
807
|
return true;
|
|
1059
808
|
}
|
|
@@ -1074,7 +823,7 @@ export async function pruneVendoredTsTests(projectDir, io) {
|
|
|
1074
823
|
await walk(full);
|
|
1075
824
|
}
|
|
1076
825
|
else if (entry.isFile() && VENDORED_TS_TEST_RE.test(entry.name)) {
|
|
1077
|
-
|
|
826
|
+
containedRemove({ root: projectDir, target: full });
|
|
1078
827
|
removed += 1;
|
|
1079
828
|
}
|
|
1080
829
|
}
|