@deftai/directive-core 0.78.0 → 0.79.1
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/cache/empty-populate.d.ts +44 -0
- package/dist/cache/empty-populate.js +117 -0
- package/dist/cache/index.d.ts +1 -0
- package/dist/cache/index.js +1 -0
- package/dist/check-updates/index.d.ts +10 -0
- package/dist/check-updates/index.js +81 -1
- package/dist/doctor/main.js +13 -2
- package/dist/doctor/payload-staleness.js +7 -1
- package/dist/hooks/dispatcher.d.ts +14 -2
- package/dist/hooks/dispatcher.js +77 -20
- package/dist/init-deposit/agent-hooks.d.ts +1 -1
- package/dist/init-deposit/agent-hooks.js +10 -0
- package/dist/init-deposit/hygiene.js +1 -0
- package/dist/init-deposit/init-deposit.js +2 -0
- package/dist/init-deposit/prettierignore.d.ts +21 -0
- package/dist/init-deposit/prettierignore.js +83 -0
- package/dist/init-deposit/refresh.d.ts +3 -1
- package/dist/init-deposit/refresh.js +29 -40
- package/dist/init-deposit/scaffold.js +6 -12
- package/dist/init-deposit/xbrief-projections.d.ts +18 -0
- package/dist/init-deposit/xbrief-projections.js +117 -0
- package/dist/intake/reconcile-issues.d.ts +10 -0
- package/dist/intake/reconcile-issues.js +121 -3
- package/dist/issue-sync/index.d.ts +2 -0
- package/dist/issue-sync/index.js +2 -0
- package/dist/issue-sync/sync-from-xbrief-cli.d.ts +11 -0
- package/dist/issue-sync/sync-from-xbrief-cli.js +44 -0
- package/dist/issue-sync/sync-from-xbrief.d.ts +46 -0
- package/dist/issue-sync/sync-from-xbrief.js +208 -0
- package/dist/platform/index.d.ts +1 -0
- package/dist/platform/index.js +1 -0
- package/dist/platform/platform-capabilities.d.ts +5 -0
- package/dist/platform/platform-capabilities.js +9 -0
- package/dist/platform/shell-context.d.ts +30 -0
- package/dist/platform/shell-context.js +111 -0
- package/dist/pr-monitor/cadence.d.ts +6 -0
- package/dist/pr-monitor/cadence.js +13 -0
- package/dist/pr-monitor/index.d.ts +1 -1
- package/dist/pr-monitor/index.js +1 -1
- package/dist/pr-monitor/monitor.d.ts +7 -0
- package/dist/pr-monitor/monitor.js +59 -13
- package/dist/pr-wait-mergeable/wrappers.d.ts +9 -0
- package/dist/pr-wait-mergeable/wrappers.js +31 -2
- package/dist/preflight-cache/evaluate.d.ts +3 -8
- package/dist/preflight-cache/evaluate.js +19 -19
- package/dist/release/constants.d.ts +2 -0
- package/dist/release/constants.js +10 -1
- package/dist/release/flags.js +26 -0
- package/dist/release/issue-state-fetch.d.ts +27 -0
- package/dist/release/issue-state-fetch.js +146 -0
- package/dist/release/main.js +1 -0
- package/dist/release/native-steps.js +6 -4
- package/dist/release/pipeline.js +15 -5
- package/dist/release/preflight.d.ts +6 -2
- package/dist/release/preflight.js +19 -5
- package/dist/release/types.d.ts +3 -1
- package/dist/release-e2e/constants.js +6 -6
- package/dist/release-e2e/flags.d.ts +2 -0
- package/dist/release-e2e/flags.js +8 -4
- package/dist/release-e2e/main.js +15 -9
- package/dist/release-e2e/types.d.ts +4 -2
- package/dist/resolution/classify.d.ts +1 -1
- package/dist/resolution/classify.js +36 -11
- package/dist/scope/project-definition-sync.js +2 -150
- package/dist/scope/registry-artifact-sync.d.ts +3 -0
- package/dist/scope/registry-artifact-sync.js +159 -0
- package/dist/scope/specification-sync.d.ts +3 -0
- package/dist/scope/specification-sync.js +16 -0
- package/dist/scope/transition.js +40 -12
- package/dist/session/cache-recovery.d.ts +19 -0
- package/dist/session/cache-recovery.js +26 -0
- package/dist/session/session-start.d.ts +2 -0
- package/dist/session/session-start.js +14 -3
- package/dist/session/verify-session-ritual.d.ts +1 -0
- package/dist/session/verify-session-ritual.js +1 -0
- package/dist/triage/welcome/default-mode.js +2 -0
- package/dist/triage/welcome/onboard.js +2 -0
- package/dist/ts-check-lane/run-lane.d.ts +5 -0
- package/dist/ts-check-lane/run-lane.js +9 -0
- package/dist/vbrief-reconcile/labels.d.ts +3 -0
- package/dist/vbrief-reconcile/labels.js +14 -0
- package/dist/vbrief-reconcile/main.d.ts +1 -0
- package/dist/vbrief-reconcile/main.js +13 -3
- package/dist/vbrief-reconcile/parity-scenarios.js +18 -4
- package/dist/vbrief-reconcile/repo-guard.d.ts +17 -0
- package/dist/vbrief-reconcile/repo-guard.js +33 -0
- package/dist/vbrief-reconcile/umbrellas.d.ts +3 -0
- package/dist/vbrief-reconcile/umbrellas.js +17 -1
- package/dist/verify-env/agent-hooks.js +4 -3
- package/dist/verify-env/verify-hooks-installed.js +5 -5
- package/dist/vitest-runner/coverage-debt-teardown.d.ts +3 -0
- package/dist/vitest-runner/coverage-debt-teardown.js +33 -0
- package/dist/vitest-runner/coverage-debt.d.ts +50 -0
- package/dist/vitest-runner/coverage-debt.js +155 -0
- package/dist/vitest-runner/win32-coverage-tmp-setup.d.ts +12 -0
- package/dist/vitest-runner/win32-coverage-tmp-setup.js +24 -0
- package/dist/xbrief-migrate/detect.js +46 -1
- package/dist/xbrief-migrate/migrate-project.js +19 -4
- package/dist/xbrief-migrate/migration-containment.d.ts +6 -0
- package/dist/xbrief-migrate/migration-containment.js +37 -0
- package/package.json +7 -3
|
@@ -3,6 +3,10 @@ import { REPO_SLUG_PREFIX } from "./constants.js";
|
|
|
3
3
|
export function emit(label, status) {
|
|
4
4
|
process.stderr.write(`[e2e] ${label}... ${status}\n`);
|
|
5
5
|
}
|
|
6
|
+
/** Copy-paste cleanup hint for leftover release-e2e temp repos (#2572). */
|
|
7
|
+
export function formatTempRepoCleanupHint(owner, slug) {
|
|
8
|
+
return `gh repo delete ${owner}/${slug} --yes`;
|
|
9
|
+
}
|
|
6
10
|
export function generateRepoSlug(seams = {}) {
|
|
7
11
|
if (seams.generateRepoSlug) {
|
|
8
12
|
return seams.generateRepoSlug();
|
|
@@ -22,7 +26,7 @@ export function parseE2EFlags(argv) {
|
|
|
22
26
|
let help = false;
|
|
23
27
|
let owner = "deftai";
|
|
24
28
|
let dryRun = false;
|
|
25
|
-
let
|
|
29
|
+
let destroyRepo = false;
|
|
26
30
|
let projectRoot = null;
|
|
27
31
|
let skipNpm = false;
|
|
28
32
|
let legacyBridge = false;
|
|
@@ -37,8 +41,8 @@ export function parseE2EFlags(argv) {
|
|
|
37
41
|
else if (arg === "--dry-run") {
|
|
38
42
|
dryRun = true;
|
|
39
43
|
}
|
|
40
|
-
else if (arg === "--
|
|
41
|
-
|
|
44
|
+
else if (arg === "--destroy-repo") {
|
|
45
|
+
destroyRepo = true;
|
|
42
46
|
}
|
|
43
47
|
else if (arg === "--skip-npm") {
|
|
44
48
|
skipNpm = true;
|
|
@@ -79,6 +83,6 @@ export function parseE2EFlags(argv) {
|
|
|
79
83
|
unknown.push(arg);
|
|
80
84
|
}
|
|
81
85
|
}
|
|
82
|
-
return { help, owner, dryRun,
|
|
86
|
+
return { help, owner, dryRun, destroyRepo, projectRoot, skipNpm, legacyBridge, unknown };
|
|
83
87
|
}
|
|
84
88
|
//# sourceMappingURL=flags.js.map
|
package/dist/release-e2e/main.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { resolveProjectRoot } from "../release/paths.js";
|
|
2
2
|
import { EXIT_CONFIG_ERROR, EXIT_OK, EXIT_VIOLATION, RELEASE_E2E_HELP } from "./constants.js";
|
|
3
|
-
import { emit, generateRepoSlug, parseE2EFlags } from "./flags.js";
|
|
3
|
+
import { emit, formatTempRepoCleanupHint, generateRepoSlug, parseE2EFlags } from "./flags.js";
|
|
4
4
|
import { destroyTempRepo, provisionTempRepo } from "./gh-ops.js";
|
|
5
5
|
import { runLegacyBridgeLeg } from "./legacy-bridge-leg.js";
|
|
6
6
|
import { runRehearsal } from "./rehearsal.js";
|
|
@@ -18,7 +18,13 @@ export function runE2e(config, seams = {}) {
|
|
|
18
18
|
emit("Legacy-bridge leg", "DRYRUN (would run the pinned legacy -> bridge -> npm-hybrid migration leg; reads the " +
|
|
19
19
|
"Tier-0 SoT lastGoInstaller(), pending-pin while null)");
|
|
20
20
|
}
|
|
21
|
-
|
|
21
|
+
const cleanupHint = formatTempRepoCleanupHint(owner, slug);
|
|
22
|
+
if (config.destroyRepo) {
|
|
23
|
+
emit("Destroy temp repo", `DRYRUN (would run \`${cleanupHint}\`)`);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
emit("Temp repo cleanup", `DRYRUN (would keep ${owner}/${slug}; manual cleanup: ${cleanupHint})`);
|
|
27
|
+
}
|
|
22
28
|
return EXIT_OK;
|
|
23
29
|
}
|
|
24
30
|
// Opt-in #1912 leg: local + self-contained (no temp GitHub repo), so run it
|
|
@@ -49,19 +55,19 @@ export function runE2e(config, seams = {}) {
|
|
|
49
55
|
}
|
|
50
56
|
}
|
|
51
57
|
finally {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
`gh repo delete ${owner}/${slug} --yes)`);
|
|
55
|
-
}
|
|
56
|
-
else {
|
|
58
|
+
const cleanupHint = formatTempRepoCleanupHint(owner, slug);
|
|
59
|
+
if (config.destroyRepo) {
|
|
57
60
|
const [destroyOk, destroyReason] = destroyTempRepo(owner, slug, seams);
|
|
58
61
|
if (destroyOk) {
|
|
59
62
|
emit(`Destroy ${owner}/${slug}`, `OK (${destroyReason})`);
|
|
60
63
|
}
|
|
61
64
|
else {
|
|
62
|
-
emit(`Destroy ${owner}/${slug}`, `WARN (${destroyReason}); manual cleanup
|
|
65
|
+
emit(`Destroy ${owner}/${slug}`, `WARN (${destroyReason}); manual cleanup: ${cleanupHint}`);
|
|
63
66
|
}
|
|
64
67
|
}
|
|
68
|
+
else {
|
|
69
|
+
emit(`Temp repo ${owner}/${slug}`, `KEEP (manual cleanup: ${cleanupHint})`);
|
|
70
|
+
}
|
|
65
71
|
}
|
|
66
72
|
return rehearsalRc !== EXIT_OK ? rehearsalRc : legacyBridgeRc;
|
|
67
73
|
}
|
|
@@ -84,7 +90,7 @@ export function cmdReleaseE2e(args, seams = {}) {
|
|
|
84
90
|
owner: flags.owner,
|
|
85
91
|
projectRoot,
|
|
86
92
|
dryRun: flags.dryRun,
|
|
87
|
-
|
|
93
|
+
destroyRepo: flags.destroyRepo,
|
|
88
94
|
skipNpm: flags.skipNpm,
|
|
89
95
|
legacyBridge: flags.legacyBridge,
|
|
90
96
|
repoSlug: null,
|
|
@@ -3,7 +3,8 @@ export interface E2EConfig {
|
|
|
3
3
|
owner: string;
|
|
4
4
|
projectRoot: string;
|
|
5
5
|
dryRun: boolean;
|
|
6
|
-
|
|
6
|
+
/** When true, attempt gh repo delete after rehearsal (#2572 opt-in privileged mode). */
|
|
7
|
+
destroyRepo: boolean;
|
|
7
8
|
/** When true, skip the npm publish dry-run rehearsal step (#1910). */
|
|
8
9
|
skipNpm: boolean;
|
|
9
10
|
/**
|
|
@@ -20,7 +21,8 @@ export interface ParsedE2EFlags {
|
|
|
20
21
|
help: boolean;
|
|
21
22
|
owner: string;
|
|
22
23
|
dryRun: boolean;
|
|
23
|
-
|
|
24
|
+
/** When true, attempt gh repo delete after rehearsal (#2572 opt-in privileged mode). */
|
|
25
|
+
destroyRepo: boolean;
|
|
24
26
|
projectRoot: string | null;
|
|
25
27
|
skipNpm: boolean;
|
|
26
28
|
/** Opt-in: run the pinned legacy->bridge->npm-hybrid migration leg (#1912). */
|
|
@@ -30,7 +30,7 @@ export interface ClassifySeams {
|
|
|
30
30
|
/** Pre-cutover probe; defaults to the shared `detectPreCutover` detector. */
|
|
31
31
|
readonly preCutoverProbe?: (cwd: string) => boolean;
|
|
32
32
|
}
|
|
33
|
-
/** Default engine probe:
|
|
33
|
+
/** Default engine probe: in-process identity, then `directive` / `deft` on PATH. */
|
|
34
34
|
export declare function defaultEngineProbe(): EngineProbeResult;
|
|
35
35
|
/**
|
|
36
36
|
* Classify a project directory into the orthogonal resolution fact-set. All I/O
|
|
@@ -11,13 +11,16 @@
|
|
|
11
11
|
* - deposited payload version-> `../doctor/manifest.ts` + `../init-deposit/constants.ts`
|
|
12
12
|
* - committed pin -> `./pin.ts`
|
|
13
13
|
*/
|
|
14
|
-
import { execFileSync } from "node:child_process";
|
|
15
14
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
16
|
-
import { join } from "node:path";
|
|
15
|
+
import { dirname, join } from "node:path";
|
|
16
|
+
import { fileURLToPath } from "node:url";
|
|
17
17
|
import { locateManifest, manifestTagToVersion, parseInstallManifest } from "../doctor/manifest.js";
|
|
18
|
+
import { readCorePackageVersion } from "../engine-version.js";
|
|
18
19
|
import { CANONICAL_INSTALL_ROOT } from "../init-deposit/constants.js";
|
|
19
20
|
import { extractManagedSection, parseManagedSectionAttrs } from "../platform/agents-md.js";
|
|
21
|
+
import { ENV_VAR } from "../platform/constants.js";
|
|
20
22
|
import { detectPreCutover } from "../vbrief-validate/precutover.js";
|
|
23
|
+
import { resolveCommandOnPath, spawnCommandText } from "../verify-env/command-spawn.js";
|
|
21
24
|
import { readPin } from "./pin.js";
|
|
22
25
|
/** App-source markers used for the `hasAppCode` heuristic. */
|
|
23
26
|
const APP_CODE_MARKERS = [
|
|
@@ -57,22 +60,44 @@ function defaultReadText(p) {
|
|
|
57
60
|
return null;
|
|
58
61
|
}
|
|
59
62
|
}
|
|
60
|
-
function
|
|
63
|
+
function parseVersionFromOutput(out) {
|
|
64
|
+
const match = SEMVER_IN_TEXT_RE.exec(out);
|
|
65
|
+
return match?.[1] ?? null;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Prefer the engine version from the already-loaded core package when doctor /
|
|
69
|
+
* update are executing inside `@deftai/directive` (#2606).
|
|
70
|
+
*/
|
|
71
|
+
function readInProcessEngineVersion() {
|
|
72
|
+
const envVersion = process.env[ENV_VAR]?.trim();
|
|
73
|
+
if (envVersion) {
|
|
74
|
+
return parseVersionFromOutput(envVersion) ?? envVersion;
|
|
75
|
+
}
|
|
61
76
|
try {
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const match = SEMVER_IN_TEXT_RE.exec(out);
|
|
68
|
-
return match?.[1] ?? null;
|
|
77
|
+
const pkgPath = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "package.json");
|
|
78
|
+
if (!existsSync(pkgPath)) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
return readCorePackageVersion();
|
|
69
82
|
}
|
|
70
83
|
catch {
|
|
71
84
|
return null;
|
|
72
85
|
}
|
|
73
86
|
}
|
|
74
|
-
|
|
87
|
+
function probeEngineVersion(binary) {
|
|
88
|
+
const resolved = resolveCommandOnPath(binary);
|
|
89
|
+
const result = spawnCommandText(resolved ?? binary, ["--version"], { timeoutMs: 5000 });
|
|
90
|
+
if (result.status !== 0) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
return parseVersionFromOutput(result.stdout);
|
|
94
|
+
}
|
|
95
|
+
/** Default engine probe: in-process identity, then `directive` / `deft` on PATH. */
|
|
75
96
|
export function defaultEngineProbe() {
|
|
97
|
+
const inProcess = readInProcessEngineVersion();
|
|
98
|
+
if (inProcess !== null) {
|
|
99
|
+
return { reachable: true, version: inProcess };
|
|
100
|
+
}
|
|
76
101
|
for (const binary of ["directive", "deft"]) {
|
|
77
102
|
const version = probeEngineVersion(binary);
|
|
78
103
|
if (version !== null) {
|
|
@@ -1,158 +1,10 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
1
|
import { dirname, resolve } from "node:path";
|
|
3
|
-
import { referenceTypeMatches } from "@deftai/directive-types";
|
|
4
2
|
import { resolveProjectDefinitionPath } from "../layout/resolve.js";
|
|
5
|
-
import {
|
|
6
|
-
import { relativeToVbrief, resolveVbriefRef, scopeIdsForFilename } from "./vbrief-ref.js";
|
|
7
|
-
function rewriteProjectDefinitionPlanReference(ref, oldResolved, newRel, vbriefRoot) {
|
|
8
|
-
if (typeof ref !== "object" || ref === null || Array.isArray(ref)) {
|
|
9
|
-
return false;
|
|
10
|
-
}
|
|
11
|
-
const r = ref;
|
|
12
|
-
if (!referenceTypeMatches(String(r.type ?? ""), "plan")) {
|
|
13
|
-
return false;
|
|
14
|
-
}
|
|
15
|
-
const resolved = resolveVbriefRef(r.uri, vbriefRoot);
|
|
16
|
-
if (resolved === null || resolve(resolved) !== resolve(oldResolved)) {
|
|
17
|
-
return false;
|
|
18
|
-
}
|
|
19
|
-
const uri = r.uri;
|
|
20
|
-
const newUri = typeof uri === "string" && uri.startsWith("file://") ? `file://${newRel}` : newRel;
|
|
21
|
-
if (newUri === uri) {
|
|
22
|
-
return false;
|
|
23
|
-
}
|
|
24
|
-
r.uri = newUri;
|
|
25
|
-
return true;
|
|
26
|
-
}
|
|
27
|
-
function projectItemReferencesScope(item, oldResolved, newResolved, vbriefRoot) {
|
|
28
|
-
const metadata = item.metadata;
|
|
29
|
-
if (typeof metadata === "object" && metadata !== null && !Array.isArray(metadata)) {
|
|
30
|
-
const meta = metadata;
|
|
31
|
-
const sourcePath = meta.source_path;
|
|
32
|
-
if (typeof sourcePath === "string") {
|
|
33
|
-
const resolved = resolveVbriefRef(sourcePath, vbriefRoot);
|
|
34
|
-
if (resolved !== null && [oldResolved, newResolved].includes(resolve(resolved))) {
|
|
35
|
-
return true;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
const metadataRefs = meta.references;
|
|
39
|
-
if (Array.isArray(metadataRefs)) {
|
|
40
|
-
for (const ref of metadataRefs) {
|
|
41
|
-
if (typeof ref !== "object" || ref === null || Array.isArray(ref)) {
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
const r = ref;
|
|
45
|
-
if (!referenceTypeMatches(String(r.type ?? ""), "plan")) {
|
|
46
|
-
continue;
|
|
47
|
-
}
|
|
48
|
-
const resolved = resolveVbriefRef(r.uri, vbriefRoot);
|
|
49
|
-
if (resolved !== null && [oldResolved, newResolved].includes(resolve(resolved))) {
|
|
50
|
-
return true;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
const refs = item.references;
|
|
56
|
-
if (Array.isArray(refs)) {
|
|
57
|
-
for (const ref of refs) {
|
|
58
|
-
if (typeof ref !== "object" || ref === null || Array.isArray(ref)) {
|
|
59
|
-
continue;
|
|
60
|
-
}
|
|
61
|
-
const r = ref;
|
|
62
|
-
if (!referenceTypeMatches(String(r.type ?? ""), "plan")) {
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
65
|
-
const resolved = resolveVbriefRef(r.uri, vbriefRoot);
|
|
66
|
-
if (resolved !== null && [oldResolved, newResolved].includes(resolve(resolved))) {
|
|
67
|
-
return true;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
function projectItemMatchesScope(item, scopeData, oldPath, newPath, vbriefRoot) {
|
|
74
|
-
const oldResolved = resolve(oldPath);
|
|
75
|
-
const newResolved = resolve(newPath);
|
|
76
|
-
if (projectItemReferencesScope(item, oldResolved, newResolved, vbriefRoot)) {
|
|
77
|
-
return true;
|
|
78
|
-
}
|
|
79
|
-
const itemId = item.id;
|
|
80
|
-
if (typeof itemId === "string" &&
|
|
81
|
-
scopeIdsForFilename(resolve(newPath).split(/[/\\]/).pop() ?? "").has(itemId)) {
|
|
82
|
-
return true;
|
|
83
|
-
}
|
|
84
|
-
const scopePlan = scopeData.plan;
|
|
85
|
-
const scopeTitle = typeof scopePlan === "object" && scopePlan !== null && !Array.isArray(scopePlan)
|
|
86
|
-
? scopePlan.title
|
|
87
|
-
: undefined;
|
|
88
|
-
const itemTitle = item.title;
|
|
89
|
-
return (typeof scopeTitle === "string" && typeof itemTitle === "string" && itemTitle === scopeTitle);
|
|
90
|
-
}
|
|
3
|
+
import { syncRegistryArtifactAfterScopeMove } from "./registry-artifact-sync.js";
|
|
91
4
|
/** Best-effort sync of PROJECT-DEFINITION after a lifecycle move (#1527). */
|
|
92
5
|
export function syncProjectDefinitionAfterScopeMove(scopeData, oldPath, newPath, vbriefRoot, targetStatus) {
|
|
93
|
-
const newRel = relativeToVbrief(newPath, vbriefRoot);
|
|
94
|
-
if (newRel === null) {
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
6
|
const projectRoot = dirname(resolve(vbriefRoot));
|
|
98
7
|
const projectDefPath = resolveProjectDefinitionPath(projectRoot);
|
|
99
|
-
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
try {
|
|
103
|
-
const projectDef = JSON.parse(readFileSync(projectDefPath, "utf8"));
|
|
104
|
-
const plan = projectDef.plan;
|
|
105
|
-
if (typeof plan !== "object" || plan === null || Array.isArray(plan)) {
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
const p = plan;
|
|
109
|
-
let changed = false;
|
|
110
|
-
const oldResolved = resolve(oldPath);
|
|
111
|
-
const refs = p.references;
|
|
112
|
-
if (Array.isArray(refs)) {
|
|
113
|
-
for (const ref of refs) {
|
|
114
|
-
if (rewriteProjectDefinitionPlanReference(ref, oldResolved, newRel, vbriefRoot)) {
|
|
115
|
-
changed = true;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
const items = p.items;
|
|
120
|
-
if (Array.isArray(items)) {
|
|
121
|
-
for (const item of items) {
|
|
122
|
-
if (typeof item !== "object" || item === null || Array.isArray(item)) {
|
|
123
|
-
continue;
|
|
124
|
-
}
|
|
125
|
-
const i = item;
|
|
126
|
-
if (!projectItemMatchesScope(i, scopeData, oldPath, newPath, vbriefRoot)) {
|
|
127
|
-
continue;
|
|
128
|
-
}
|
|
129
|
-
if (i.status !== targetStatus) {
|
|
130
|
-
i.status = targetStatus;
|
|
131
|
-
changed = true;
|
|
132
|
-
}
|
|
133
|
-
let metadata = i.metadata;
|
|
134
|
-
if (typeof metadata !== "object" || metadata === null || Array.isArray(metadata)) {
|
|
135
|
-
metadata = {};
|
|
136
|
-
i.metadata = metadata;
|
|
137
|
-
}
|
|
138
|
-
const meta = metadata;
|
|
139
|
-
const targetFolder = dirname(resolve(newPath)).split(/[/\\]/).pop() ?? "";
|
|
140
|
-
if (meta.source_path !== newRel) {
|
|
141
|
-
meta.source_path = newRel;
|
|
142
|
-
changed = true;
|
|
143
|
-
}
|
|
144
|
-
if (meta.lifecycle_folder !== targetFolder) {
|
|
145
|
-
meta.lifecycle_folder = targetFolder;
|
|
146
|
-
changed = true;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
if (changed) {
|
|
151
|
-
writeFileSync(projectDefPath, formatVbriefJson(projectDef), "utf8");
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
catch {
|
|
155
|
-
/* best-effort */
|
|
156
|
-
}
|
|
8
|
+
syncRegistryArtifactAfterScopeMove(projectDefPath, scopeData, oldPath, newPath, vbriefRoot, targetStatus);
|
|
157
9
|
}
|
|
158
10
|
//# sourceMappingURL=project-definition-sync.js.map
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/** Best-effort sync of a registry artifact (PROJECT-DEFINITION, specification, etc.) after a lifecycle move. */
|
|
2
|
+
export declare function syncRegistryArtifactAfterScopeMove(registryPath: string, scopeData: Record<string, unknown>, oldPath: string, newPath: string, vbriefRoot: string, targetStatus: string): void;
|
|
3
|
+
//# sourceMappingURL=registry-artifact-sync.d.ts.map
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { referenceTypeMatches } from "@deftai/directive-types";
|
|
4
|
+
import { formatVbriefJson } from "./vbrief-json.js";
|
|
5
|
+
import { relativeToVbrief, resolveVbriefRef, scopeIdsForFilename } from "./vbrief-ref.js";
|
|
6
|
+
function rewriteRegistryPlanReference(ref, oldResolved, newRel, vbriefRoot) {
|
|
7
|
+
if (typeof ref !== "object" || ref === null || Array.isArray(ref)) {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
const r = ref;
|
|
11
|
+
if (!referenceTypeMatches(String(r.type ?? ""), "plan")) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
const resolved = resolveVbriefRef(r.uri, vbriefRoot);
|
|
15
|
+
if (resolved === null || resolve(resolved) !== resolve(oldResolved)) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
const uri = r.uri;
|
|
19
|
+
const newUri = typeof uri === "string" && uri.startsWith("file://") ? `file://${newRel}` : newRel;
|
|
20
|
+
if (newUri === uri) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
r.uri = newUri;
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
function registryItemReferencesScope(item, oldResolved, newResolved, vbriefRoot) {
|
|
27
|
+
const metadata = item.metadata;
|
|
28
|
+
if (typeof metadata === "object" && metadata !== null && !Array.isArray(metadata)) {
|
|
29
|
+
const meta = metadata;
|
|
30
|
+
const sourcePath = meta.source_path;
|
|
31
|
+
if (typeof sourcePath === "string") {
|
|
32
|
+
const resolved = resolveVbriefRef(sourcePath, vbriefRoot);
|
|
33
|
+
if (resolved !== null && [oldResolved, newResolved].includes(resolve(resolved))) {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const metadataRefs = meta.references;
|
|
38
|
+
if (Array.isArray(metadataRefs)) {
|
|
39
|
+
for (const ref of metadataRefs) {
|
|
40
|
+
if (typeof ref !== "object" || ref === null || Array.isArray(ref)) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const r = ref;
|
|
44
|
+
if (!referenceTypeMatches(String(r.type ?? ""), "plan")) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const resolved = resolveVbriefRef(r.uri, vbriefRoot);
|
|
48
|
+
if (resolved !== null && [oldResolved, newResolved].includes(resolve(resolved))) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const refs = item.references;
|
|
55
|
+
if (Array.isArray(refs)) {
|
|
56
|
+
for (const ref of refs) {
|
|
57
|
+
if (typeof ref !== "object" || ref === null || Array.isArray(ref)) {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const r = ref;
|
|
61
|
+
if (!referenceTypeMatches(String(r.type ?? ""), "plan")) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const resolved = resolveVbriefRef(r.uri, vbriefRoot);
|
|
65
|
+
if (resolved !== null && [oldResolved, newResolved].includes(resolve(resolved))) {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
function registryItemMatchesScope(item, scopeData, oldPath, newPath, vbriefRoot) {
|
|
73
|
+
const oldResolved = resolve(oldPath);
|
|
74
|
+
const newResolved = resolve(newPath);
|
|
75
|
+
if (registryItemReferencesScope(item, oldResolved, newResolved, vbriefRoot)) {
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
const itemId = item.id;
|
|
79
|
+
if (typeof itemId === "string" &&
|
|
80
|
+
scopeIdsForFilename(resolve(newPath).split(/[/\\]/).pop() ?? "").has(itemId)) {
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
const scopePlan = scopeData.plan;
|
|
84
|
+
const scopeTitle = typeof scopePlan === "object" && scopePlan !== null && !Array.isArray(scopePlan)
|
|
85
|
+
? scopePlan.title
|
|
86
|
+
: undefined;
|
|
87
|
+
const itemTitle = item.title;
|
|
88
|
+
return (typeof scopeTitle === "string" && typeof itemTitle === "string" && itemTitle === scopeTitle);
|
|
89
|
+
}
|
|
90
|
+
/** Best-effort sync of a registry artifact (PROJECT-DEFINITION, specification, etc.) after a lifecycle move. */
|
|
91
|
+
export function syncRegistryArtifactAfterScopeMove(registryPath, scopeData, oldPath, newPath, vbriefRoot, targetStatus) {
|
|
92
|
+
const newRel = relativeToVbrief(newPath, vbriefRoot);
|
|
93
|
+
if (newRel === null) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (!existsSync(registryPath)) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
const parsed = JSON.parse(readFileSync(registryPath, "utf8"));
|
|
101
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const registry = parsed;
|
|
105
|
+
const plan = registry.plan;
|
|
106
|
+
if (typeof plan !== "object" || plan === null || Array.isArray(plan)) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const p = plan;
|
|
110
|
+
let changed = false;
|
|
111
|
+
const oldResolved = resolve(oldPath);
|
|
112
|
+
const refs = p.references;
|
|
113
|
+
if (Array.isArray(refs)) {
|
|
114
|
+
for (const ref of refs) {
|
|
115
|
+
if (rewriteRegistryPlanReference(ref, oldResolved, newRel, vbriefRoot)) {
|
|
116
|
+
changed = true;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const items = p.items;
|
|
121
|
+
if (Array.isArray(items)) {
|
|
122
|
+
for (const item of items) {
|
|
123
|
+
if (typeof item !== "object" || item === null || Array.isArray(item)) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const i = item;
|
|
127
|
+
if (!registryItemMatchesScope(i, scopeData, oldPath, newPath, vbriefRoot)) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (i.status !== targetStatus) {
|
|
131
|
+
i.status = targetStatus;
|
|
132
|
+
changed = true;
|
|
133
|
+
}
|
|
134
|
+
let metadata = i.metadata;
|
|
135
|
+
if (typeof metadata !== "object" || metadata === null || Array.isArray(metadata)) {
|
|
136
|
+
metadata = {};
|
|
137
|
+
i.metadata = metadata;
|
|
138
|
+
}
|
|
139
|
+
const meta = metadata;
|
|
140
|
+
const targetFolder = dirname(resolve(newPath)).split(/[/\\]/).pop() ?? "";
|
|
141
|
+
if (meta.source_path !== newRel) {
|
|
142
|
+
meta.source_path = newRel;
|
|
143
|
+
changed = true;
|
|
144
|
+
}
|
|
145
|
+
if (meta.lifecycle_folder !== targetFolder) {
|
|
146
|
+
meta.lifecycle_folder = targetFolder;
|
|
147
|
+
changed = true;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (changed) {
|
|
152
|
+
writeFileSync(registryPath, formatVbriefJson(registry), "utf8");
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
/* best-effort */
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=registry-artifact-sync.js.map
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/** Best-effort sync of specification.xbrief.json after a lifecycle move (#2566). */
|
|
2
|
+
export declare function syncSpecificationAfterScopeMove(scopeData: Record<string, unknown>, oldPath: string, newPath: string, vbriefRoot: string, targetStatus: string): void;
|
|
3
|
+
//# sourceMappingURL=specification-sync.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { dirname, resolve } from "node:path";
|
|
2
|
+
import { resolveSpecArtifactPath } from "../layout/resolve.js";
|
|
3
|
+
import { syncRegistryArtifactAfterScopeMove } from "./registry-artifact-sync.js";
|
|
4
|
+
/** Best-effort sync of specification.xbrief.json after a lifecycle move (#2566). */
|
|
5
|
+
export function syncSpecificationAfterScopeMove(scopeData, oldPath, newPath, vbriefRoot, targetStatus) {
|
|
6
|
+
const projectRoot = dirname(resolve(vbriefRoot));
|
|
7
|
+
let specPath;
|
|
8
|
+
try {
|
|
9
|
+
specPath = resolveSpecArtifactPath(projectRoot);
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
syncRegistryArtifactAfterScopeMove(specPath, scopeData, oldPath, newPath, vbriefRoot, targetStatus);
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=specification-sync.js.map
|
package/dist/scope/transition.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync,
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, unlinkSync } from "node:fs";
|
|
2
2
|
import { dirname, join, resolve } from "node:path";
|
|
3
3
|
import { InstrumentedVbriefCrud, persistCrudMetrics } from "../eval/crud-telemetry.js";
|
|
4
4
|
import { assertProjectionContained, ProjectionContainmentError, } from "../fs/projection-containment.js";
|
|
@@ -8,6 +8,7 @@ import { stampCompletionMetadata } from "./capacity-stamp.js";
|
|
|
8
8
|
import { LIFECYCLE_FOLDERS, MOVE_LABELS, STATUS_PRECONDITIONS, STAY_LABELS, TRANSITIONS, } from "./constants.js";
|
|
9
9
|
import { detectLifecycleFolder, updateDecomposedChildBackReferences, updateDecomposedParentBackReferences, } from "./decomposed-refs.js";
|
|
10
10
|
import { syncProjectDefinitionAfterScopeMove } from "./project-definition-sync.js";
|
|
11
|
+
import { syncSpecificationAfterScopeMove } from "./specification-sync.js";
|
|
11
12
|
import { formatVbriefJson, utcNowIso } from "./vbrief-json.js";
|
|
12
13
|
export function runTransition(action, filePath, now = new Date()) {
|
|
13
14
|
if (!(action in TRANSITIONS)) {
|
|
@@ -101,30 +102,57 @@ export function runTransition(action, filePath, now = new Date()) {
|
|
|
101
102
|
}
|
|
102
103
|
const formatted = formatVbriefJson(data);
|
|
103
104
|
const crud = new InstrumentedVbriefCrud({ now: () => now });
|
|
104
|
-
const writeResult = crud.update(resolvedPath, formatted, { trustedWrite: true });
|
|
105
|
-
if (!writeResult.ok) {
|
|
106
|
-
return { ok: false, message: writeResult.error ?? `CRUD update failed for ${resolvedPath}` };
|
|
107
|
-
}
|
|
108
|
-
try {
|
|
109
|
-
persistCrudMetrics(projectRoot, crud.getMetrics());
|
|
110
|
-
}
|
|
111
|
-
catch {
|
|
112
|
-
/* best-effort telemetry persistence */
|
|
113
|
-
}
|
|
114
105
|
if (targetFolder !== null) {
|
|
115
106
|
const destDir = join(vbriefRoot, targetFolder);
|
|
116
107
|
mkdirSync(destDir, { recursive: true });
|
|
117
108
|
const destPath = join(destDir, basename);
|
|
118
|
-
|
|
109
|
+
if (existsSync(destPath)) {
|
|
110
|
+
return { ok: false, message: `Target already exists: ${destPath}` };
|
|
111
|
+
}
|
|
112
|
+
// #2578: stamp terminal status at the destination path in the same write as
|
|
113
|
+
// folder placement — never leave a non-terminal status under completed/.
|
|
114
|
+
const writeResult = crud.update(destPath, formatted, { trustedWrite: true });
|
|
115
|
+
if (!writeResult.ok) {
|
|
116
|
+
return { ok: false, message: writeResult.error ?? `CRUD update failed for ${destPath}` };
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
unlinkSync(resolvedPath);
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
try {
|
|
123
|
+
unlinkSync(destPath);
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
/* best-effort rollback */
|
|
127
|
+
}
|
|
128
|
+
return { ok: false, message: `Failed to remove source after move: ${String(err)}` };
|
|
129
|
+
}
|
|
130
|
+
try {
|
|
131
|
+
persistCrudMetrics(projectRoot, crud.getMetrics());
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
/* best-effort telemetry persistence */
|
|
135
|
+
}
|
|
119
136
|
updateDecomposedParentBackReferences(data, resolvedPath, destPath, vbriefRoot);
|
|
120
137
|
updateDecomposedChildBackReferences(data, resolvedPath, destPath, vbriefRoot);
|
|
121
138
|
syncProjectDefinitionAfterScopeMove(data, resolvedPath, destPath, vbriefRoot, targetStatus);
|
|
139
|
+
syncSpecificationAfterScopeMove(data, resolvedPath, destPath, vbriefRoot, targetStatus);
|
|
122
140
|
const actionLabel = MOVE_LABELS[act] ?? act.charAt(0).toUpperCase() + act.slice(1);
|
|
123
141
|
return {
|
|
124
142
|
ok: true,
|
|
125
143
|
message: `${actionLabel} ${basename}: ${currentFolder}/ -> ${targetFolder}/ (status: ${targetStatus})`,
|
|
126
144
|
};
|
|
127
145
|
}
|
|
146
|
+
const writeResult = crud.update(resolvedPath, formatted, { trustedWrite: true });
|
|
147
|
+
if (!writeResult.ok) {
|
|
148
|
+
return { ok: false, message: writeResult.error ?? `CRUD update failed for ${resolvedPath}` };
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
persistCrudMetrics(projectRoot, crud.getMetrics());
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
/* best-effort telemetry persistence */
|
|
155
|
+
}
|
|
128
156
|
const actionLabel = STAY_LABELS[act] ?? act.charAt(0).toUpperCase() + act.slice(1);
|
|
129
157
|
return {
|
|
130
158
|
ok: true,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format a consumer-runnable `deft cache fetch-all` recovery command (#2574).
|
|
3
|
+
*
|
|
4
|
+
* Uses the space-separated CLI surface (not the colon task alias) and always
|
|
5
|
+
* includes required `--source` / `--repo` flags so following the hint literally
|
|
6
|
+
* reaches the handler instead of `unknown verb 'cache:fetch-all'`.
|
|
7
|
+
*/
|
|
8
|
+
export declare function formatCacheFetchAllRecoveryCommand(repo: string | null, options?: {
|
|
9
|
+
force?: boolean;
|
|
10
|
+
}): string;
|
|
11
|
+
/**
|
|
12
|
+
* Branch-aware recovery hint (#1953 Option 3, #2574 argv fix).
|
|
13
|
+
* Age-stale (or age+drift mixed) → --force bypasses TTL; drift-only → plain refetch.
|
|
14
|
+
*/
|
|
15
|
+
export declare function recoveryHintForStaleFailure(causes: Readonly<{
|
|
16
|
+
ageStale: boolean;
|
|
17
|
+
driftDetected: boolean;
|
|
18
|
+
}>, repo?: string | null): string;
|
|
19
|
+
//# sourceMappingURL=cache-recovery.d.ts.map
|