@deftai/directive-core 0.68.1 → 0.70.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/deposit/contain.d.ts +41 -0
- package/dist/deposit/contain.js +113 -0
- package/dist/deposit/copy-tree.js +8 -2
- package/dist/doctor/checks.d.ts +11 -0
- package/dist/doctor/checks.js +75 -2
- package/dist/doctor/constants.d.ts +9 -0
- package/dist/doctor/constants.js +11 -0
- package/dist/doctor/main.d.ts +47 -1
- package/dist/doctor/main.js +406 -3
- package/dist/doctor/manifest.d.ts +20 -0
- package/dist/doctor/manifest.js +22 -0
- package/dist/doctor/payload-staleness.d.ts +8 -0
- package/dist/doctor/payload-staleness.js +11 -8
- package/dist/doctor/types.d.ts +56 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/init-deposit/gitignore.d.ts +18 -0
- package/dist/init-deposit/gitignore.js +94 -15
- package/dist/init-deposit/headless-manifest.d.ts +87 -0
- package/dist/init-deposit/headless-manifest.js +261 -0
- package/dist/init-deposit/index.d.ts +3 -0
- package/dist/init-deposit/index.js +3 -0
- package/dist/init-deposit/init-deposit.js +6 -0
- package/dist/init-deposit/init-dispatch.d.ts +90 -0
- package/dist/init-deposit/init-dispatch.js +170 -0
- package/dist/init-deposit/refresh.d.ts +79 -2
- package/dist/init-deposit/refresh.js +182 -5
- package/dist/init-deposit/scaffold.d.ts +25 -0
- package/dist/init-deposit/scaffold.js +57 -0
- package/dist/init-deposit/untrack-core.d.ts +84 -0
- package/dist/init-deposit/untrack-core.js +150 -0
- package/dist/intake/issue-ingest.d.ts +11 -0
- package/dist/intake/issue-ingest.js +86 -11
- package/dist/policy/plan-extensions.d.ts +31 -0
- package/dist/policy/plan-extensions.js +45 -0
- package/dist/policy/wip.d.ts +2 -2
- package/dist/policy/wip.js +2 -2
- package/dist/resolution/classify.d.ts +41 -0
- package/dist/resolution/classify.js +138 -0
- package/dist/resolution/engine-ladder.d.ts +98 -0
- package/dist/resolution/engine-ladder.js +185 -0
- package/dist/resolution/index.d.ts +19 -0
- package/dist/resolution/index.js +19 -0
- package/dist/resolution/integrity.d.ts +48 -0
- package/dist/resolution/integrity.js +80 -0
- package/dist/resolution/package-manager.d.ts +77 -0
- package/dist/resolution/package-manager.js +103 -0
- package/dist/resolution/pin.d.ts +73 -0
- package/dist/resolution/pin.js +169 -0
- package/dist/resolution/plan.d.ts +66 -0
- package/dist/resolution/plan.js +219 -0
- package/dist/resolution/skew-policy.d.ts +46 -0
- package/dist/resolution/skew-policy.js +120 -0
- package/dist/session/session-start.d.ts +2 -0
- package/dist/session/session-start.js +28 -1
- package/dist/swarm/routing.js +10 -3
- package/dist/triage/bulk/index.d.ts +11 -2
- package/dist/triage/bulk/index.js +41 -4
- package/dist/triage/help/registry-data.d.ts +5 -5
- package/dist/triage/help/registry-data.js +18 -5
- package/dist/triage/scope/cli.d.ts +2 -1
- package/dist/triage/scope/cli.js +30 -6
- package/dist/triage/scope/mutations.d.ts +10 -0
- package/dist/triage/scope/mutations.js +22 -0
- package/dist/triage/welcome/constants.d.ts +1 -2
- package/dist/triage/welcome/constants.js +1 -2
- package/dist/triage/welcome/index.d.ts +1 -0
- package/dist/triage/welcome/index.js +1 -0
- package/dist/triage/welcome/onboard.d.ts +35 -0
- package/dist/triage/welcome/onboard.js +94 -0
- package/dist/umbrella-current-shape/index.d.ts +25 -1
- package/dist/umbrella-current-shape/index.js +84 -4
- package/dist/user-config/index.d.ts +2 -0
- package/dist/user-config/index.js +2 -0
- package/dist/user-config/resolve-user-md.d.ts +76 -0
- package/dist/user-config/resolve-user-md.js +120 -0
- package/dist/vbrief-build/project-definition-io.d.ts +6 -0
- package/dist/vbrief-build/project-definition-io.js +7 -2
- package/dist/vbrief-validate/precutover.js +7 -3
- package/dist/xbrief-migrate/constants.d.ts +10 -0
- package/dist/xbrief-migrate/constants.js +19 -0
- package/dist/xbrief-migrate/detect.d.ts +34 -0
- package/dist/xbrief-migrate/detect.js +33 -0
- package/dist/xbrief-migrate/fs-helpers.d.ts +13 -0
- package/dist/xbrief-migrate/fs-helpers.js +46 -1
- package/dist/xbrief-migrate/index.d.ts +4 -2
- package/dist/xbrief-migrate/index.js +4 -2
- package/dist/xbrief-migrate/migrate-project.d.ts +23 -1
- package/dist/xbrief-migrate/migrate-project.js +97 -8
- package/dist/xbrief-migrate/signpost.d.ts +7 -1
- package/dist/xbrief-migrate/signpost.js +18 -3
- package/package.json +7 -3
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { LEGACY_ARTIFACT_DIR, LEGACY_ARTIFACT_SUFFIX, LEGACY_INFO_ROOT_KEY, LEGACY_VBRIEF_VERSION, MIGRATED_ARTIFACT_DIR, VBRIEF_REFERENCE_PREFIX, } from "./constants.js";
|
|
4
|
+
import { hasVbriefDeprecationMarker, isEffectivelyEmptyDir } from "./fs-helpers.js";
|
|
4
5
|
function isDirectory(path) {
|
|
5
6
|
try {
|
|
6
7
|
return statSync(path).isDirectory();
|
|
@@ -78,4 +79,36 @@ export function detectLegacyVbriefLayout(projectRoot) {
|
|
|
78
79
|
const reasonList = [...reasons];
|
|
79
80
|
return { legacyLayout: reasonList.length > 0, reasons: reasonList };
|
|
80
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* Classify the convergence state of the `vbrief/` and `xbrief/` lifecycle roots
|
|
84
|
+
* so the orchestrator can converge to a single unambiguous root (#2270). Unlike
|
|
85
|
+
* {@link detectLegacyVbriefLayout} (which only asks "is there anything legacy?"),
|
|
86
|
+
* this probe distinguishes the ambiguous dual-empty-root case from an already
|
|
87
|
+
* unambiguous layout, and never falls back to a `run migrate:xbrief` dead end
|
|
88
|
+
* for a `vbrief/` that is merely an empty leftover.
|
|
89
|
+
*/
|
|
90
|
+
export function detectXbriefConvergence(projectRoot) {
|
|
91
|
+
const legacyDir = join(projectRoot, LEGACY_ARTIFACT_DIR);
|
|
92
|
+
const migratedDir = join(projectRoot, MIGRATED_ARTIFACT_DIR);
|
|
93
|
+
const vbriefPresent = isDirectory(legacyDir);
|
|
94
|
+
const vbriefHasMarker = vbriefPresent && hasVbriefDeprecationMarker(legacyDir);
|
|
95
|
+
const vbriefEmpty = vbriefPresent && !vbriefHasMarker && isEffectivelyEmptyDir(legacyDir);
|
|
96
|
+
const xbriefPresent = isDirectory(migratedDir);
|
|
97
|
+
const xbriefHasContent = xbriefPresent && !isEffectivelyEmptyDir(migratedDir);
|
|
98
|
+
let state;
|
|
99
|
+
if (vbriefHasMarker) {
|
|
100
|
+
state = "xbrief-marker";
|
|
101
|
+
}
|
|
102
|
+
else if (!vbriefPresent) {
|
|
103
|
+
state = xbriefHasContent ? "xbrief-only" : "none";
|
|
104
|
+
}
|
|
105
|
+
else if (vbriefEmpty) {
|
|
106
|
+
state = "empty-vbrief";
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
// vbrief/ holds real content and carries no marker.
|
|
110
|
+
state = xbriefHasContent ? "dual-populated" : "legacy-only";
|
|
111
|
+
}
|
|
112
|
+
return { state, vbriefPresent, vbriefEmpty, vbriefHasMarker, xbriefPresent, xbriefHasContent };
|
|
113
|
+
}
|
|
81
114
|
//# sourceMappingURL=detect.js.map
|
|
@@ -1,3 +1,16 @@
|
|
|
1
1
|
/** True when `path` exists and is a directory; false on any stat error. */
|
|
2
2
|
export declare function isDirectory(path: string): boolean;
|
|
3
|
+
/**
|
|
4
|
+
* True when `dir` holds no meaningful content at any depth — a "fully migrated"
|
|
5
|
+
* lifecycle root. Empty subdirectories and VCS placeholder files (`.gitkeep`,
|
|
6
|
+
* `.keep`) are ignored, so a scaffolded-but-empty `vbrief/proposed/` tree still
|
|
7
|
+
* counts as empty. A missing directory is trivially empty (#2270).
|
|
8
|
+
*/
|
|
9
|
+
export declare function isEffectivelyEmptyDir(dir: string): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* True when `dir` carries a deft-written deprecation marker identifying it as a
|
|
12
|
+
* retained-for-read-compat legacy root rather than an active source of truth
|
|
13
|
+
* (#2270). Matched on the sentinel so an unrelated `DEPRECATED.md` is ignored.
|
|
14
|
+
*/
|
|
15
|
+
export declare function hasVbriefDeprecationMarker(dir: string): boolean;
|
|
3
16
|
//# sourceMappingURL=fs-helpers.d.ts.map
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { statSync } from "node:fs";
|
|
1
|
+
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { VBRIEF_DEPRECATION_MARKER_FILENAME, VBRIEF_DEPRECATION_MARKER_SENTINEL, } from "./constants.js";
|
|
2
4
|
/** True when `path` exists and is a directory; false on any stat error. */
|
|
3
5
|
export function isDirectory(path) {
|
|
4
6
|
try {
|
|
@@ -8,4 +10,47 @@ export function isDirectory(path) {
|
|
|
8
10
|
return false;
|
|
9
11
|
}
|
|
10
12
|
}
|
|
13
|
+
/** Placeholder files that do not count as real content when judging emptiness. */
|
|
14
|
+
const PLACEHOLDER_FILES = new Set([".gitkeep", ".keep"]);
|
|
15
|
+
/**
|
|
16
|
+
* True when `dir` holds no meaningful content at any depth — a "fully migrated"
|
|
17
|
+
* lifecycle root. Empty subdirectories and VCS placeholder files (`.gitkeep`,
|
|
18
|
+
* `.keep`) are ignored, so a scaffolded-but-empty `vbrief/proposed/` tree still
|
|
19
|
+
* counts as empty. A missing directory is trivially empty (#2270).
|
|
20
|
+
*/
|
|
21
|
+
export function isEffectivelyEmptyDir(dir) {
|
|
22
|
+
if (!isDirectory(dir)) {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
26
|
+
if (entry.isFile()) {
|
|
27
|
+
if (PLACEHOLDER_FILES.has(entry.name)) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
// A symlink (to a file or dir) is real content: `isFile`/`isDirectory` both
|
|
33
|
+
// report false for symlink dirents, so treat it explicitly as non-empty.
|
|
34
|
+
if (entry.isSymbolicLink()) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
if (entry.isDirectory() && !isEffectivelyEmptyDir(join(dir, entry.name))) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* True when `dir` carries a deft-written deprecation marker identifying it as a
|
|
45
|
+
* retained-for-read-compat legacy root rather than an active source of truth
|
|
46
|
+
* (#2270). Matched on the sentinel so an unrelated `DEPRECATED.md` is ignored.
|
|
47
|
+
*/
|
|
48
|
+
export function hasVbriefDeprecationMarker(dir) {
|
|
49
|
+
try {
|
|
50
|
+
return readFileSync(join(dir, VBRIEF_DEPRECATION_MARKER_FILENAME), "utf8").includes(VBRIEF_DEPRECATION_MARKER_SENTINEL);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
11
56
|
//# sourceMappingURL=fs-helpers.js.map
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
export { detectStaleUnmanagedHeader, type HeaderPatchOutcome, type HeaderRewriteResult, type HeaderTokenReplacement, LEGACY_HEADER_TOKENS, patchAgentsMdHeader, renderHeaderPatchSummary, renderStaleHeaderLine, rewriteUnmanagedHeaderTokens, type StaleHeaderDetection, } from "./agents-header.js";
|
|
2
|
-
export {
|
|
2
|
+
export { VBRIEF_DEPRECATION_MARKER_BODY, VBRIEF_DEPRECATION_MARKER_FILENAME, VBRIEF_DEPRECATION_MARKER_SENTINEL, } from "./constants.js";
|
|
3
|
+
export { detectLegacyVbriefLayout, detectXbriefConvergence, type LegacyVbriefLayoutDetection, type XbriefConvergenceDetection, type XbriefConvergenceState, } from "./detect.js";
|
|
3
4
|
export { BUILTIN_ALLOW_LIST, type DriftEvaluateOptions, type DriftEvaluateResult, type DriftFinding, type DriftScanMode, evaluateXbriefDrift, LEGACY_REFERENCE_PREFIX, scanCorpusToken, } from "./drift-gate.js";
|
|
4
|
-
export {
|
|
5
|
+
export { hasVbriefDeprecationMarker, isDirectory, isEffectivelyEmptyDir, } from "./fs-helpers.js";
|
|
6
|
+
export { convergeLegacyVbriefRoot, emitXbriefMigration, runXbriefMigration, runXbriefMigrationCli, type VbriefConvergeAction, type XbriefMigrationArgs, type XbriefMigrationIo, type XbriefMigrationOutcome, } from "./migrate-project.js";
|
|
5
7
|
export { renderXbriefMigrationLine, xbriefMigrationGuidance } from "./signpost.js";
|
|
6
8
|
export { assertFeatureEmissionAllowed, assertLayoutAwareWritePath, FeatureEmissionRejectedError, type JsonObject, type JsonValue, readDeclaredArtifactVersion, resolveLayoutAwareRelativePath, rewriteEmbeddedTokens, SplitLayoutRejectedError, TransformError, transformArtifactV06ToV08, transformArtifactV06ToV08Transactional, } from "./transforms.js";
|
|
7
9
|
export { isPatchOnlyUpgrade, parseSemverPrefix } from "./version.js";
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
export { detectStaleUnmanagedHeader, LEGACY_HEADER_TOKENS, patchAgentsMdHeader, renderHeaderPatchSummary, renderStaleHeaderLine, rewriteUnmanagedHeaderTokens, } from "./agents-header.js";
|
|
2
|
-
export {
|
|
2
|
+
export { VBRIEF_DEPRECATION_MARKER_BODY, VBRIEF_DEPRECATION_MARKER_FILENAME, VBRIEF_DEPRECATION_MARKER_SENTINEL, } from "./constants.js";
|
|
3
|
+
export { detectLegacyVbriefLayout, detectXbriefConvergence, } from "./detect.js";
|
|
3
4
|
export { BUILTIN_ALLOW_LIST, evaluateXbriefDrift, LEGACY_REFERENCE_PREFIX, scanCorpusToken, } from "./drift-gate.js";
|
|
4
|
-
export {
|
|
5
|
+
export { hasVbriefDeprecationMarker, isDirectory, isEffectivelyEmptyDir, } from "./fs-helpers.js";
|
|
6
|
+
export { convergeLegacyVbriefRoot, emitXbriefMigration, runXbriefMigration, runXbriefMigrationCli, } from "./migrate-project.js";
|
|
5
7
|
export { renderXbriefMigrationLine, xbriefMigrationGuidance } from "./signpost.js";
|
|
6
8
|
export { assertFeatureEmissionAllowed, assertLayoutAwareWritePath, FeatureEmissionRejectedError, readDeclaredArtifactVersion, resolveLayoutAwareRelativePath, rewriteEmbeddedTokens, SplitLayoutRejectedError, TransformError, transformArtifactV06ToV08, transformArtifactV06ToV08Transactional, } from "./transforms.js";
|
|
7
9
|
export { isPatchOnlyUpgrade, parseSemverPrefix } from "./version.js";
|
|
@@ -2,11 +2,18 @@ export interface XbriefMigrationArgs {
|
|
|
2
2
|
readonly projectRoot: string;
|
|
3
3
|
readonly frameworkRoot?: string;
|
|
4
4
|
readonly force?: boolean;
|
|
5
|
+
/**
|
|
6
|
+
* Retain a fully-migrated `vbrief/` for read-compatibility behind an explicit
|
|
7
|
+
* deprecation marker instead of removing it (#2270). Default: remove.
|
|
8
|
+
*/
|
|
9
|
+
readonly keepLegacy?: boolean;
|
|
5
10
|
}
|
|
6
11
|
export interface XbriefMigrationIo {
|
|
7
12
|
writeOut: (text: string) => void;
|
|
8
13
|
writeErr: (text: string) => void;
|
|
9
14
|
}
|
|
15
|
+
/** How the legacy `vbrief/` root was converged to an unambiguous state (#2270). */
|
|
16
|
+
export type VbriefConvergeAction = "removed" | "marker";
|
|
10
17
|
export type XbriefMigrationOutcome = {
|
|
11
18
|
readonly kind: "noop";
|
|
12
19
|
readonly message: string;
|
|
@@ -17,11 +24,26 @@ export type XbriefMigrationOutcome = {
|
|
|
17
24
|
readonly kind: "migrated";
|
|
18
25
|
readonly backupDir: string;
|
|
19
26
|
readonly files: number;
|
|
27
|
+
} | {
|
|
28
|
+
readonly kind: "converged";
|
|
29
|
+
readonly action: VbriefConvergeAction;
|
|
30
|
+
readonly already: boolean;
|
|
31
|
+
readonly message: string;
|
|
20
32
|
} | {
|
|
21
33
|
readonly kind: "config";
|
|
22
34
|
readonly message: string;
|
|
23
35
|
};
|
|
24
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* Converge a leftover legacy `vbrief/` root to an unambiguous state (#2270).
|
|
38
|
+
* An effectively-empty tree is removed by default (single canonical root); a
|
|
39
|
+
* tree with real content — or an empty tree when `retain` is requested — is
|
|
40
|
+
* kept for read-compat behind an explicit deprecation marker. Idempotent: a
|
|
41
|
+
* missing dir or an already-marked dir is a no-op.
|
|
42
|
+
*/
|
|
43
|
+
export declare function convergeLegacyVbriefRoot(projectRoot: string, options: {
|
|
44
|
+
retain: boolean;
|
|
45
|
+
}): VbriefConvergeAction;
|
|
46
|
+
/** Core orchestrator for the consumer xbrief rename (#2110) + convergence (#2270). */
|
|
25
47
|
export declare function runXbriefMigration(args: XbriefMigrationArgs, _io: XbriefMigrationIo): XbriefMigrationOutcome;
|
|
26
48
|
/** Map a migration outcome to a process exit code and emit operator guidance. */
|
|
27
49
|
export declare function emitXbriefMigration(outcome: XbriefMigrationOutcome, io: XbriefMigrationIo, options?: {
|
|
@@ -3,9 +3,9 @@ import { dirname, join, relative, resolve } from "node:path";
|
|
|
3
3
|
import { checkGitClean } from "../migrate-preflight/index.js";
|
|
4
4
|
import { agentsRefreshPlan } from "../platform/agents-md.js";
|
|
5
5
|
import { patchAgentsMdHeader, renderHeaderPatchSummary } from "./agents-header.js";
|
|
6
|
-
import { LEGACY_ARTIFACT_DIR, LEGACY_ARTIFACT_SUFFIX, MIGRATED_ARTIFACT_DIR, MIGRATED_ARTIFACT_SUFFIX, } from "./constants.js";
|
|
7
|
-
import { detectLegacyVbriefLayout } from "./detect.js";
|
|
8
|
-
import { isDirectory } from "./fs-helpers.js";
|
|
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
|
+
import { detectLegacyVbriefLayout, detectXbriefConvergence } from "./detect.js";
|
|
8
|
+
import { hasVbriefDeprecationMarker, isDirectory, isEffectivelyEmptyDir } from "./fs-helpers.js";
|
|
9
9
|
import { renderXbriefMigrationLine, xbriefMigrationGuidance } from "./signpost.js";
|
|
10
10
|
import { rewriteEmbeddedTokens, transformArtifactV06ToV08Transactional } from "./transforms.js";
|
|
11
11
|
function collectFiles(root, acc = []) {
|
|
@@ -52,7 +52,7 @@ function backupLegacyTree(projectRoot, legacyDir) {
|
|
|
52
52
|
cpSync(legacyDir, join(backupRoot, LEGACY_ARTIFACT_DIR), { recursive: true });
|
|
53
53
|
return backupRoot;
|
|
54
54
|
}
|
|
55
|
-
function migrateLegacyTree(projectRoot, legacyDir) {
|
|
55
|
+
function migrateLegacyTree(projectRoot, legacyDir, options) {
|
|
56
56
|
const migratedDir = join(projectRoot, MIGRATED_ARTIFACT_DIR);
|
|
57
57
|
if (existsSync(migratedDir)) {
|
|
58
58
|
throw new Error(`refusing to migrate: '${MIGRATED_ARTIFACT_DIR}/' already exists alongside '${LEGACY_ARTIFACT_DIR}/'`);
|
|
@@ -71,7 +71,15 @@ function migrateLegacyTree(projectRoot, legacyDir) {
|
|
|
71
71
|
writeMigratedFile(srcPath, destPath);
|
|
72
72
|
}
|
|
73
73
|
renameOrReplace(stagedDir, migratedDir);
|
|
74
|
-
|
|
74
|
+
// Converge to a single unambiguous root: the fully-migrated legacy tree is
|
|
75
|
+
// either removed (default) or retained for read-compat behind an explicit
|
|
76
|
+
// deprecation marker so it never looks like an active source of truth (#2270).
|
|
77
|
+
if (options.keepLegacy) {
|
|
78
|
+
writeVbriefDeprecationMarker(legacyDir);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
rmSync(legacyDir, { recursive: true, force: true });
|
|
82
|
+
}
|
|
75
83
|
return { backupDir, files: files.length };
|
|
76
84
|
}
|
|
77
85
|
catch (err) {
|
|
@@ -79,6 +87,36 @@ function migrateLegacyTree(projectRoot, legacyDir) {
|
|
|
79
87
|
throw err;
|
|
80
88
|
}
|
|
81
89
|
}
|
|
90
|
+
/** Idempotently write the legacy-root deprecation marker (#2270). */
|
|
91
|
+
function writeVbriefDeprecationMarker(legacyDir) {
|
|
92
|
+
mkdirSync(legacyDir, { recursive: true });
|
|
93
|
+
if (hasVbriefDeprecationMarker(legacyDir)) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
writeFileSync(join(legacyDir, VBRIEF_DEPRECATION_MARKER_FILENAME), VBRIEF_DEPRECATION_MARKER_BODY, "utf8");
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Converge a leftover legacy `vbrief/` root to an unambiguous state (#2270).
|
|
100
|
+
* An effectively-empty tree is removed by default (single canonical root); a
|
|
101
|
+
* tree with real content — or an empty tree when `retain` is requested — is
|
|
102
|
+
* kept for read-compat behind an explicit deprecation marker. Idempotent: a
|
|
103
|
+
* missing dir or an already-marked dir is a no-op.
|
|
104
|
+
*/
|
|
105
|
+
export function convergeLegacyVbriefRoot(projectRoot, options) {
|
|
106
|
+
const legacyDir = join(projectRoot, LEGACY_ARTIFACT_DIR);
|
|
107
|
+
if (!isDirectory(legacyDir)) {
|
|
108
|
+
return "removed";
|
|
109
|
+
}
|
|
110
|
+
if (hasVbriefDeprecationMarker(legacyDir)) {
|
|
111
|
+
return "marker";
|
|
112
|
+
}
|
|
113
|
+
if (isEffectivelyEmptyDir(legacyDir) && !options.retain) {
|
|
114
|
+
rmSync(legacyDir, { recursive: true, force: true });
|
|
115
|
+
return "removed";
|
|
116
|
+
}
|
|
117
|
+
writeVbriefDeprecationMarker(legacyDir);
|
|
118
|
+
return "marker";
|
|
119
|
+
}
|
|
82
120
|
function renameOrReplace(src, dest) {
|
|
83
121
|
if (existsSync(dest)) {
|
|
84
122
|
rmSync(dest, { recursive: true, force: true });
|
|
@@ -107,9 +145,58 @@ function runAgentsRefresh(projectRoot, frameworkRoot, io) {
|
|
|
107
145
|
io.writeOut(`AGENTS.md updated (state=${state}).\n`);
|
|
108
146
|
return 0;
|
|
109
147
|
}
|
|
110
|
-
/** Core orchestrator for the consumer xbrief rename (#2110). */
|
|
148
|
+
/** Core orchestrator for the consumer xbrief rename (#2110) + convergence (#2270). */
|
|
111
149
|
export function runXbriefMigration(args, _io) {
|
|
112
150
|
const projectRoot = resolve(args.projectRoot);
|
|
151
|
+
const legacyDir = join(projectRoot, LEGACY_ARTIFACT_DIR);
|
|
152
|
+
const keepLegacy = args.keepLegacy ?? false;
|
|
153
|
+
const convergence = detectXbriefConvergence(projectRoot);
|
|
154
|
+
switch (convergence.state) {
|
|
155
|
+
// Already converged: legacy root is retained behind an explicit marker.
|
|
156
|
+
// Rerun is a pure no-op (no re-removal, no duplicate marker) — idempotent.
|
|
157
|
+
case "xbrief-marker":
|
|
158
|
+
return {
|
|
159
|
+
kind: "converged",
|
|
160
|
+
action: "marker",
|
|
161
|
+
already: true,
|
|
162
|
+
message: `Legacy '${LEGACY_ARTIFACT_DIR}/' already carries a deprecation marker — layout already converged.`,
|
|
163
|
+
};
|
|
164
|
+
// Ambiguous dual-empty root: a fully-migrated empty legacy tree. Converge to
|
|
165
|
+
// a single canonical root by removing it (or marking it when read-compat
|
|
166
|
+
// retention is requested); never leave two indistinguishable empty roots.
|
|
167
|
+
// Read-compat retention is only honored when a canonical xbrief/ actually
|
|
168
|
+
// has content — marking an empty legacy root with no canonical replacement
|
|
169
|
+
// would strand the project behind a marker that claims a migration that
|
|
170
|
+
// never happened, so that case always removes the stray empty root.
|
|
171
|
+
case "empty-vbrief": {
|
|
172
|
+
const retain = keepLegacy && convergence.xbriefHasContent;
|
|
173
|
+
const action = convergeLegacyVbriefRoot(projectRoot, { retain });
|
|
174
|
+
let message;
|
|
175
|
+
if (action === "removed") {
|
|
176
|
+
message = convergence.xbriefHasContent
|
|
177
|
+
? `Converged layout: removed empty legacy '${LEGACY_ARTIFACT_DIR}/' — single '${MIGRATED_ARTIFACT_DIR}/' root.`
|
|
178
|
+
: `Converged layout: removed empty legacy '${LEGACY_ARTIFACT_DIR}/' — no canonical '${MIGRATED_ARTIFACT_DIR}/' root to migrate to yet.`;
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
message = `Converged layout: wrote deprecation marker to legacy '${LEGACY_ARTIFACT_DIR}/' (retained for read-compat).`;
|
|
182
|
+
}
|
|
183
|
+
return { kind: "converged", action, already: false, message };
|
|
184
|
+
}
|
|
185
|
+
// Legacy content coexisting with a populated canonical xbrief/. We never
|
|
186
|
+
// destructively merge; converge non-destructively by marking the legacy
|
|
187
|
+
// tree deprecated so it no longer looks like an active source of truth.
|
|
188
|
+
case "dual-populated": {
|
|
189
|
+
convergeLegacyVbriefRoot(projectRoot, { retain: true });
|
|
190
|
+
return {
|
|
191
|
+
kind: "converged",
|
|
192
|
+
action: "marker",
|
|
193
|
+
already: false,
|
|
194
|
+
message: `Converged layout: '${MIGRATED_ARTIFACT_DIR}/' is canonical; wrote deprecation marker to legacy '${LEGACY_ARTIFACT_DIR}/' (retained for read-compat).`,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
default:
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
113
200
|
const detection = detectLegacyVbriefLayout(projectRoot);
|
|
114
201
|
if (!detection.legacyLayout) {
|
|
115
202
|
return {
|
|
@@ -117,7 +204,6 @@ export function runXbriefMigration(args, _io) {
|
|
|
117
204
|
message: "Project is already on the xbrief layout — nothing to migrate.",
|
|
118
205
|
};
|
|
119
206
|
}
|
|
120
|
-
const legacyDir = join(projectRoot, LEGACY_ARTIFACT_DIR);
|
|
121
207
|
if (!isDirectory(legacyDir)) {
|
|
122
208
|
return {
|
|
123
209
|
kind: "config",
|
|
@@ -134,7 +220,7 @@ export function runXbriefMigration(args, _io) {
|
|
|
134
220
|
}
|
|
135
221
|
}
|
|
136
222
|
try {
|
|
137
|
-
const { backupDir, files } = migrateLegacyTree(projectRoot, legacyDir);
|
|
223
|
+
const { backupDir, files } = migrateLegacyTree(projectRoot, legacyDir, { keepLegacy });
|
|
138
224
|
return { kind: "migrated", backupDir, files };
|
|
139
225
|
}
|
|
140
226
|
catch (err) {
|
|
@@ -165,6 +251,9 @@ export function emitXbriefMigration(outcome, io, options = {}) {
|
|
|
165
251
|
io.writeOut(`Migrated ${outcome.files} file(s) from ${LEGACY_ARTIFACT_DIR}/ to ${MIGRATED_ARTIFACT_DIR}/.\n` +
|
|
166
252
|
`Backup written to ${outcome.backupDir}.\n`);
|
|
167
253
|
return 0;
|
|
254
|
+
case "converged":
|
|
255
|
+
io.writeOut(`${outcome.message}\n`);
|
|
256
|
+
return 0;
|
|
168
257
|
default: {
|
|
169
258
|
const _exhaustive = outcome;
|
|
170
259
|
return _exhaustive;
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
/** Operator guidance for the TS-native xbrief rename (#2034 / #2110). */
|
|
2
2
|
export declare function xbriefMigrationGuidance(): string;
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* One-line doctor / ritual signpost mirroring `renderPrecutoverLine` (#2110),
|
|
5
|
+
* reporting an unambiguous convergence state (#2270). A migrated project reads
|
|
6
|
+
* as `xbrief active` plus either `vbrief legacy marker` or `vbrief removed`; a
|
|
7
|
+
* stray empty legacy root is called out as a pending convergence rather than a
|
|
8
|
+
* generic "run migrate" dead end — never a dual-empty-root ambiguity.
|
|
9
|
+
*/
|
|
4
10
|
export declare function renderXbriefMigrationLine(projectRoot: string): string;
|
|
5
11
|
//# sourceMappingURL=signpost.d.ts.map
|
|
@@ -1,13 +1,28 @@
|
|
|
1
|
-
import { detectLegacyVbriefLayout } from "./detect.js";
|
|
1
|
+
import { detectLegacyVbriefLayout, detectXbriefConvergence } from "./detect.js";
|
|
2
2
|
/** Operator guidance for the TS-native xbrief rename (#2034 / #2110). */
|
|
3
3
|
export function xbriefMigrationGuidance() {
|
|
4
4
|
return "Run `deft migrate:xbrief` (or `task migrate:xbrief`) to convert vbrief/ to xbrief/ safely.";
|
|
5
5
|
}
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* One-line doctor / ritual signpost mirroring `renderPrecutoverLine` (#2110),
|
|
8
|
+
* reporting an unambiguous convergence state (#2270). A migrated project reads
|
|
9
|
+
* as `xbrief active` plus either `vbrief legacy marker` or `vbrief removed`; a
|
|
10
|
+
* stray empty legacy root is called out as a pending convergence rather than a
|
|
11
|
+
* generic "run migrate" dead end — never a dual-empty-root ambiguity.
|
|
12
|
+
*/
|
|
7
13
|
export function renderXbriefMigrationLine(projectRoot) {
|
|
14
|
+
const convergence = detectXbriefConvergence(projectRoot);
|
|
15
|
+
// Converged: legacy vbrief/ retained for read-compat behind an explicit marker.
|
|
16
|
+
if (convergence.state === "xbrief-marker") {
|
|
17
|
+
return "xBrief migration: converged -- xbrief active, vbrief legacy marker (read-compat).";
|
|
18
|
+
}
|
|
19
|
+
// Ambiguous dual-empty root: canonical xbrief/ (or none) plus a stray empty vbrief/.
|
|
20
|
+
if (convergence.state === "empty-vbrief") {
|
|
21
|
+
return `xBrief migration: converge pending -- xbrief active, empty legacy vbrief/ present. ${xbriefMigrationGuidance()}`;
|
|
22
|
+
}
|
|
8
23
|
const { legacyLayout, reasons } = detectLegacyVbriefLayout(projectRoot);
|
|
9
24
|
if (!legacyLayout) {
|
|
10
|
-
return "xBrief migration: none --
|
|
25
|
+
return "xBrief migration: none -- xbrief active, vbrief removed.";
|
|
11
26
|
}
|
|
12
27
|
const maxReasons = 3;
|
|
13
28
|
const shown = reasons.slice(0, maxReasons);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deftai/directive-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.70.0",
|
|
4
4
|
"description": "TypeScript engine core for the Directive framework.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -90,6 +90,10 @@
|
|
|
90
90
|
"types": "./dist/release-e2e/index.d.ts",
|
|
91
91
|
"default": "./dist/release-e2e/index.js"
|
|
92
92
|
},
|
|
93
|
+
"./resolution": {
|
|
94
|
+
"types": "./dist/resolution/index.d.ts",
|
|
95
|
+
"default": "./dist/resolution/index.js"
|
|
96
|
+
},
|
|
93
97
|
"./pr-merge-readiness": {
|
|
94
98
|
"types": "./dist/pr-merge-readiness/index.d.ts",
|
|
95
99
|
"default": "./dist/pr-merge-readiness/index.js"
|
|
@@ -245,8 +249,8 @@
|
|
|
245
249
|
"provenance": true
|
|
246
250
|
},
|
|
247
251
|
"dependencies": {
|
|
248
|
-
"@deftai/directive-content": "^0.
|
|
249
|
-
"@deftai/directive-types": "^0.
|
|
252
|
+
"@deftai/directive-content": "^0.70.0",
|
|
253
|
+
"@deftai/directive-types": "^0.70.0",
|
|
250
254
|
"archiver": "^8.0.0"
|
|
251
255
|
},
|
|
252
256
|
"scripts": {
|