@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
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
/** Shared coverage goal (vitest thresholds). */
|
|
4
|
+
export const COVERAGE_GOAL = {
|
|
5
|
+
lines: 85,
|
|
6
|
+
functions: 85,
|
|
7
|
+
branches: 85,
|
|
8
|
+
statements: 85,
|
|
9
|
+
};
|
|
10
|
+
const DEBT_FLAG = "--allow-coverage-debt";
|
|
11
|
+
const DEBT_ENV = "DEFT_ALLOW_COVERAGE_DEBT";
|
|
12
|
+
const RELEASE_PREFLIGHT_ENV = "DEFT_RELEASE_PREFLIGHT";
|
|
13
|
+
/** Parse `#2573`, `2573`, or bare numeric strings. */
|
|
14
|
+
export function parseCoverageDebtIssueNumber(raw) {
|
|
15
|
+
const trimmed = raw.trim();
|
|
16
|
+
if (!trimmed)
|
|
17
|
+
return null;
|
|
18
|
+
const normalized = trimmed.startsWith("#") ? trimmed.slice(1) : trimmed;
|
|
19
|
+
if (!/^\d+$/.test(normalized))
|
|
20
|
+
return null;
|
|
21
|
+
const issue = Number.parseInt(normalized, 10);
|
|
22
|
+
return Number.isFinite(issue) && issue > 0 ? issue : null;
|
|
23
|
+
}
|
|
24
|
+
/** Parse `--allow-coverage-debt=#N` tokens from argv. */
|
|
25
|
+
export function parseCoverageDebtArgv(argv) {
|
|
26
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
27
|
+
const token = argv[i] ?? "";
|
|
28
|
+
if (token === DEBT_FLAG) {
|
|
29
|
+
const next = argv[i + 1];
|
|
30
|
+
if (next === undefined || next.startsWith("-")) {
|
|
31
|
+
return { kind: "invalid", reason: `${DEBT_FLAG} requires an issue number (#N)` };
|
|
32
|
+
}
|
|
33
|
+
const issue = parseCoverageDebtIssueNumber(next);
|
|
34
|
+
return issue === null
|
|
35
|
+
? { kind: "invalid", reason: `${DEBT_FLAG} value must be #N or N` }
|
|
36
|
+
: { kind: "valid", issue };
|
|
37
|
+
}
|
|
38
|
+
if (token.startsWith(`${DEBT_FLAG}=`)) {
|
|
39
|
+
const value = token.slice(DEBT_FLAG.length + 1);
|
|
40
|
+
const issue = parseCoverageDebtIssueNumber(value);
|
|
41
|
+
return issue === null
|
|
42
|
+
? { kind: "invalid", reason: `${DEBT_FLAG}= value must be #N or N` }
|
|
43
|
+
: { kind: "valid", issue };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return { kind: "none" };
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Resolve coverage-debt issue from argv or release-scoped env (#2573 / #1553).
|
|
50
|
+
* Raw env bypass is accepted only during release Step-5 preflight.
|
|
51
|
+
*/
|
|
52
|
+
export function resolveCoverageDebtIssue(argv, env = process.env) {
|
|
53
|
+
const fromArgv = parseCoverageDebtArgv(argv);
|
|
54
|
+
if (fromArgv.kind !== "none")
|
|
55
|
+
return fromArgv;
|
|
56
|
+
const rawEnv = env[DEBT_ENV];
|
|
57
|
+
if (rawEnv && env[RELEASE_PREFLIGHT_ENV] === "1") {
|
|
58
|
+
const issue = parseCoverageDebtIssueNumber(rawEnv);
|
|
59
|
+
return issue === null
|
|
60
|
+
? { kind: "invalid", reason: `${DEBT_ENV} must be a positive integer` }
|
|
61
|
+
: { kind: "valid", issue };
|
|
62
|
+
}
|
|
63
|
+
return { kind: "none" };
|
|
64
|
+
}
|
|
65
|
+
/** Aggregate global coverage percentages from istanbul coverage-final.json. */
|
|
66
|
+
export function summarizeCoverageFinal(coverageFinal) {
|
|
67
|
+
let stmtTotal = 0;
|
|
68
|
+
let stmtCovered = 0;
|
|
69
|
+
let fnTotal = 0;
|
|
70
|
+
let fnCovered = 0;
|
|
71
|
+
let branchTotal = 0;
|
|
72
|
+
let branchCovered = 0;
|
|
73
|
+
for (const file of Object.values(coverageFinal)) {
|
|
74
|
+
if (file.s) {
|
|
75
|
+
for (const hits of Object.values(file.s)) {
|
|
76
|
+
stmtTotal += 1;
|
|
77
|
+
if (hits > 0)
|
|
78
|
+
stmtCovered += 1;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (file.f) {
|
|
82
|
+
for (const hits of Object.values(file.f)) {
|
|
83
|
+
fnTotal += 1;
|
|
84
|
+
if (hits > 0)
|
|
85
|
+
fnCovered += 1;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (file.b) {
|
|
89
|
+
for (const paths of Object.values(file.b)) {
|
|
90
|
+
for (const hits of paths) {
|
|
91
|
+
branchTotal += 1;
|
|
92
|
+
if (hits > 0)
|
|
93
|
+
branchCovered += 1;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const pct = (covered, total) => total === 0 ? 100 : (covered / total) * 100;
|
|
99
|
+
const statements = pct(stmtCovered, stmtTotal);
|
|
100
|
+
const functions = pct(fnCovered, fnTotal);
|
|
101
|
+
const branches = pct(branchCovered, branchTotal);
|
|
102
|
+
// Istanbul coverage-final.json does not carry pre-aggregated line hit counts —
|
|
103
|
+
// `l` (line map) is optional and its format differs from `s`/`f`/`b`. Using
|
|
104
|
+
// statements as a proxy may overstate line coverage when multiple statements
|
|
105
|
+
// share a single uncovered line.
|
|
106
|
+
return {
|
|
107
|
+
statements,
|
|
108
|
+
functions,
|
|
109
|
+
branches,
|
|
110
|
+
lines: statements, // approximation: actual line coverage may differ
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
export function readCoverageTotalsFromReport(coverageDir) {
|
|
114
|
+
const finalPath = join(coverageDir, "coverage-final.json");
|
|
115
|
+
try {
|
|
116
|
+
const raw = readFileSync(finalPath, "utf8");
|
|
117
|
+
const parsed = JSON.parse(raw);
|
|
118
|
+
return summarizeCoverageFinal(parsed);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
export function metricsBelowGoal(totals) {
|
|
125
|
+
const missed = [];
|
|
126
|
+
for (const metric of Object.keys(COVERAGE_GOAL)) {
|
|
127
|
+
if (totals[metric] + 1e-9 < COVERAGE_GOAL[metric]) {
|
|
128
|
+
missed.push(metric);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return missed;
|
|
132
|
+
}
|
|
133
|
+
const sanitizeMetric = (value) => String(value).replace(/\r?\n/g, " ");
|
|
134
|
+
export function formatCoverageAttribution(issue, totals) {
|
|
135
|
+
const lines = [
|
|
136
|
+
`coverage-debt: soft-pass acknowledged for issue #${issue}`,
|
|
137
|
+
` measured: branches ${sanitizeMetric(totals.branches.toFixed(2))}% | lines ${sanitizeMetric(totals.lines.toFixed(2))}% | functions ${sanitizeMetric(totals.functions.toFixed(2))}% | statements ${sanitizeMetric(totals.statements.toFixed(2))}%`,
|
|
138
|
+
` goal: branches ${sanitizeMetric(COVERAGE_GOAL.branches)}% | lines ${sanitizeMetric(COVERAGE_GOAL.lines)}% | functions ${sanitizeMetric(COVERAGE_GOAL.functions)}% | statements ${sanitizeMetric(COVERAGE_GOAL.statements)}%`,
|
|
139
|
+
];
|
|
140
|
+
return lines.join("\n");
|
|
141
|
+
}
|
|
142
|
+
/** Count recent release sections citing coverage-debt (CHANGELOG scan). */
|
|
143
|
+
export function countRecentCoverageDebtMentions(changelog, maxSections = 5) {
|
|
144
|
+
const versionHeader = /^## \[(?!Unreleased)/m;
|
|
145
|
+
const sections = changelog.split(versionHeader).slice(1, maxSections + 1);
|
|
146
|
+
const pattern = /coverage[- ]debt soft[- ]pass|allow-coverage-debt=#?\d/i;
|
|
147
|
+
return sections.filter((section) => pattern.test(section)).length;
|
|
148
|
+
}
|
|
149
|
+
export function formatOveruseWarning(recentCount) {
|
|
150
|
+
if (recentCount < 2)
|
|
151
|
+
return null;
|
|
152
|
+
return (`coverage-debt WARN: ${recentCount} of the last release sections already cite coverage debt — ` +
|
|
153
|
+
"consider restoring real coverage instead of reusing the escape hatch.");
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=coverage-debt.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Win32 globalSetup for coverage runs (#2580).
|
|
3
|
+
*
|
|
4
|
+
* Vitest's v8 provider writes per-suite JSON under coverage/.tmp without always
|
|
5
|
+
* re-mkdir'ing before writeFile. Under parallel fork load the directory can
|
|
6
|
+
* disappear mid-suite, surfacing as ENOENT after an otherwise green run.
|
|
7
|
+
* Keep the directory present for the coordinator process; late ENOENT flakes
|
|
8
|
+
* are tolerated via vitest dangerouslyIgnoreUnhandledErrors (#2546) without
|
|
9
|
+
* soft-failing real coverage threshold failures.
|
|
10
|
+
*/
|
|
11
|
+
export default function setup(): void;
|
|
12
|
+
//# sourceMappingURL=win32-coverage-tmp-setup.d.ts.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { mkdirSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
const COVERAGE_TMP = resolve(process.cwd(), "coverage", ".tmp");
|
|
4
|
+
function ensureCoverageTmpDir() {
|
|
5
|
+
mkdirSync(COVERAGE_TMP, { recursive: true });
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Win32 globalSetup for coverage runs (#2580).
|
|
9
|
+
*
|
|
10
|
+
* Vitest's v8 provider writes per-suite JSON under coverage/.tmp without always
|
|
11
|
+
* re-mkdir'ing before writeFile. Under parallel fork load the directory can
|
|
12
|
+
* disappear mid-suite, surfacing as ENOENT after an otherwise green run.
|
|
13
|
+
* Keep the directory present for the coordinator process; late ENOENT flakes
|
|
14
|
+
* are tolerated via vitest dangerouslyIgnoreUnhandledErrors (#2546) without
|
|
15
|
+
* soft-failing real coverage threshold failures.
|
|
16
|
+
*/
|
|
17
|
+
export default function setup() {
|
|
18
|
+
if (process.platform !== "win32")
|
|
19
|
+
return;
|
|
20
|
+
ensureCoverageTmpDir();
|
|
21
|
+
const keepalive = setInterval(ensureCoverageTmpDir, 100);
|
|
22
|
+
keepalive.unref?.();
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=win32-coverage-tmp-setup.js.map
|
|
@@ -33,6 +33,51 @@ function walkJsonFiles(root, acc = []) {
|
|
|
33
33
|
}
|
|
34
34
|
return acc;
|
|
35
35
|
}
|
|
36
|
+
/** Cache support state may contain only ordinary files/directories, never links or special files. */
|
|
37
|
+
function containsUnsupportedCacheEntry(root) {
|
|
38
|
+
if (!isDirectory(root))
|
|
39
|
+
return false;
|
|
40
|
+
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
41
|
+
if (entry.isSymbolicLink())
|
|
42
|
+
return true;
|
|
43
|
+
if (entry.isDirectory()) {
|
|
44
|
+
if (containsUnsupportedCacheEntry(join(root, entry.name)))
|
|
45
|
+
return true;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (!entry.isFile())
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* A top-level `.triage-cache/` is operational support state, not evidence that
|
|
55
|
+
* project artifacts have already migrated. Everything else keeps the generic
|
|
56
|
+
* empty-tree semantics; unknown files and symlinks remain real content.
|
|
57
|
+
*/
|
|
58
|
+
function xbriefHasProjectContent(root) {
|
|
59
|
+
if (!isDirectory(root))
|
|
60
|
+
return false;
|
|
61
|
+
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
62
|
+
const full = join(root, entry.name);
|
|
63
|
+
if (entry.name === ".triage-cache" && entry.isDirectory()) {
|
|
64
|
+
if (containsUnsupportedCacheEntry(full))
|
|
65
|
+
return true;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (entry.isSymbolicLink())
|
|
69
|
+
return true;
|
|
70
|
+
if (entry.isDirectory()) {
|
|
71
|
+
if (!isEffectivelyEmptyDir(full))
|
|
72
|
+
return true;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (entry.isFile() && (entry.name === ".gitkeep" || entry.name === ".keep"))
|
|
76
|
+
continue;
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
36
81
|
function scanFileContent(path, content, reasons) {
|
|
37
82
|
if (path.endsWith(LEGACY_ARTIFACT_SUFFIX)) {
|
|
38
83
|
reasons.add(`legacy artifact filename: ${path}`);
|
|
@@ -94,7 +139,7 @@ export function detectXbriefConvergence(projectRoot) {
|
|
|
94
139
|
const vbriefHasMarker = vbriefPresent && hasVbriefDeprecationMarker(legacyDir);
|
|
95
140
|
const vbriefEmpty = vbriefPresent && !vbriefHasMarker && isEffectivelyEmptyDir(legacyDir);
|
|
96
141
|
const xbriefPresent = isDirectory(migratedDir);
|
|
97
|
-
const xbriefHasContent = xbriefPresent &&
|
|
142
|
+
const xbriefHasContent = xbriefPresent && xbriefHasProjectContent(migratedDir);
|
|
98
143
|
let state;
|
|
99
144
|
if (vbriefHasMarker) {
|
|
100
145
|
state = "xbrief-marker";
|
|
@@ -6,6 +6,7 @@ import { patchAgentsMdHeader, renderHeaderPatchSummary } from "./agents-header.j
|
|
|
6
6
|
import { LEGACY_ARTIFACT_DIR, LEGACY_ARTIFACT_SUFFIX, MIGRATED_ARTIFACT_DIR, MIGRATED_ARTIFACT_SUFFIX, VBRIEF_DEPRECATION_MARKER_BODY, VBRIEF_DEPRECATION_MARKER_FILENAME, } from "./constants.js";
|
|
7
7
|
import { detectLegacyVbriefLayout, detectXbriefConvergence } from "./detect.js";
|
|
8
8
|
import { hasVbriefDeprecationMarker, isDirectory, isEffectivelyEmptyDir } from "./fs-helpers.js";
|
|
9
|
+
import { assertMigrationSourceSafe } from "./migration-containment.js";
|
|
9
10
|
import { renderXbriefMigrationLine, xbriefMigrationGuidance } from "./signpost.js";
|
|
10
11
|
import { rewriteEmbeddedTokens, transformArtifactV06ToV08Transactional } from "./transforms.js";
|
|
11
12
|
function collectFiles(root, acc = []) {
|
|
@@ -45,19 +46,32 @@ function writeMigratedFile(srcPath, destPath) {
|
|
|
45
46
|
const raw = readFileSync(srcPath, "utf8");
|
|
46
47
|
writeFileSync(destPath, rewriteEmbeddedTokens(raw), "utf8");
|
|
47
48
|
}
|
|
48
|
-
function
|
|
49
|
+
function backupMigrationInputs(projectRoot, legacyDir, migratedDir) {
|
|
49
50
|
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
50
51
|
const backupRoot = join(projectRoot, ".deft", `xbrief-migrate-backup-${stamp}`);
|
|
51
52
|
mkdirSync(backupRoot, { recursive: true });
|
|
52
53
|
cpSync(legacyDir, join(backupRoot, LEGACY_ARTIFACT_DIR), { recursive: true });
|
|
54
|
+
if (existsSync(migratedDir)) {
|
|
55
|
+
cpSync(migratedDir, join(backupRoot, MIGRATED_ARTIFACT_DIR), { recursive: true });
|
|
56
|
+
}
|
|
53
57
|
return backupRoot;
|
|
54
58
|
}
|
|
59
|
+
/** Overlay the already-canonical cache last so it wins collisions. */
|
|
60
|
+
function overlayCanonicalTriageCache(migratedDir, stagedDir) {
|
|
61
|
+
const source = join(migratedDir, ".triage-cache");
|
|
62
|
+
if (!isDirectory(source))
|
|
63
|
+
return;
|
|
64
|
+
cpSync(source, join(stagedDir, ".triage-cache"), { recursive: true, force: true });
|
|
65
|
+
}
|
|
55
66
|
function migrateLegacyTree(projectRoot, legacyDir, options) {
|
|
56
67
|
const migratedDir = join(projectRoot, MIGRATED_ARTIFACT_DIR);
|
|
57
|
-
|
|
58
|
-
|
|
68
|
+
const convergence = detectXbriefConvergence(projectRoot);
|
|
69
|
+
const hasCanonicalCache = isDirectory(join(migratedDir, ".triage-cache"));
|
|
70
|
+
if (existsSync(migratedDir) && (convergence.xbriefHasContent || !hasCanonicalCache)) {
|
|
71
|
+
throw new Error(`refusing to migrate: '${MIGRATED_ARTIFACT_DIR}/' already exists alongside '${LEGACY_ARTIFACT_DIR}/' and is not a cache-only support tree`);
|
|
59
72
|
}
|
|
60
|
-
|
|
73
|
+
assertMigrationSourceSafe(projectRoot, legacyDir);
|
|
74
|
+
const backupDir = backupMigrationInputs(projectRoot, legacyDir, migratedDir);
|
|
61
75
|
const stagedDir = join(projectRoot, `.${MIGRATED_ARTIFACT_DIR}.migrate-staging`);
|
|
62
76
|
if (existsSync(stagedDir)) {
|
|
63
77
|
rmSync(stagedDir, { recursive: true, force: true });
|
|
@@ -70,6 +84,7 @@ function migrateLegacyTree(projectRoot, legacyDir, options) {
|
|
|
70
84
|
const destPath = join(stagedDir, mapRelativePath(rel));
|
|
71
85
|
writeMigratedFile(srcPath, destPath);
|
|
72
86
|
}
|
|
87
|
+
overlayCanonicalTriageCache(migratedDir, stagedDir);
|
|
73
88
|
renameOrReplace(stagedDir, migratedDir);
|
|
74
89
|
// Converge to a single unambiguous root: the fully-migrated legacy tree is
|
|
75
90
|
// either removed (default) or retained for read-compat behind an explicit
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Refuse migrate:xbrief when legacy `vbrief/` (or any traversed entry) escapes
|
|
3
|
+
* the project tree via symlinks (#2601).
|
|
4
|
+
*/
|
|
5
|
+
export declare function assertMigrationSourceSafe(projectRoot: string, legacyDir: string): void;
|
|
6
|
+
//# sourceMappingURL=migration-containment.d.ts.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { lstatSync, readdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { assertProjectionContained } from "../fs/projection-containment.js";
|
|
4
|
+
/**
|
|
5
|
+
* Refuse migrate:xbrief when legacy `vbrief/` (or any traversed entry) escapes
|
|
6
|
+
* the project tree via symlinks (#2601).
|
|
7
|
+
*/
|
|
8
|
+
export function assertMigrationSourceSafe(projectRoot, legacyDir) {
|
|
9
|
+
assertProjectionContained(projectRoot, legacyDir);
|
|
10
|
+
walkMigrationTreeRejectSymlinks(legacyDir);
|
|
11
|
+
}
|
|
12
|
+
function walkMigrationTreeRejectSymlinks(root) {
|
|
13
|
+
let entries;
|
|
14
|
+
try {
|
|
15
|
+
entries = readdirSync(root, { withFileTypes: true });
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
for (const entry of entries) {
|
|
21
|
+
const full = join(root, entry.name);
|
|
22
|
+
let info;
|
|
23
|
+
try {
|
|
24
|
+
info = lstatSync(full);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (info.isSymbolicLink()) {
|
|
30
|
+
throw new Error(`refusing to migrate: symlink on migration path: ${full}`);
|
|
31
|
+
}
|
|
32
|
+
if (info.isDirectory()) {
|
|
33
|
+
walkMigrationTreeRejectSymlinks(full);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=migration-containment.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deftai/directive-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.79.1",
|
|
4
4
|
"description": "TypeScript engine core for the Directive framework.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -278,6 +278,10 @@
|
|
|
278
278
|
"types": "./dist/umbrella-current-shape/index.d.ts",
|
|
279
279
|
"default": "./dist/umbrella-current-shape/index.js"
|
|
280
280
|
},
|
|
281
|
+
"./issue-sync": {
|
|
282
|
+
"types": "./dist/issue-sync/index.d.ts",
|
|
283
|
+
"default": "./dist/issue-sync/index.js"
|
|
284
|
+
},
|
|
281
285
|
"./task-surface": {
|
|
282
286
|
"types": "./dist/task-surface/index.d.ts",
|
|
283
287
|
"default": "./dist/task-surface/index.js"
|
|
@@ -301,8 +305,8 @@
|
|
|
301
305
|
"provenance": true
|
|
302
306
|
},
|
|
303
307
|
"dependencies": {
|
|
304
|
-
"@deftai/directive-content": "^0.
|
|
305
|
-
"@deftai/directive-types": "^0.
|
|
308
|
+
"@deftai/directive-content": "^0.79.1",
|
|
309
|
+
"@deftai/directive-types": "^0.79.1",
|
|
306
310
|
"archiver": "^8.0.0"
|
|
307
311
|
},
|
|
308
312
|
"scripts": {
|