@deftai/directive-core 0.68.1 → 0.69.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/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 +352 -2
- package/dist/doctor/payload-staleness.d.ts +8 -0
- package/dist/doctor/payload-staleness.js +11 -8
- package/dist/doctor/types.d.ts +48 -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-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 +177 -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/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/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/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
|
@@ -4,6 +4,7 @@ import { MIGRATE_COMPLETION_NUDGE, shouldEmitMigrateNudge } from "../init-deposi
|
|
|
4
4
|
import { disclosureLine } from "../policy/disclosure.js";
|
|
5
5
|
import { resolvePolicy } from "../policy/resolve.js";
|
|
6
6
|
import { runDefaultMode } from "../triage/welcome/default-mode.js";
|
|
7
|
+
import { resolveUserMdPath } from "../user-config/resolve-user-md.js";
|
|
7
8
|
import { verifyRequiredTools } from "../verify-env/verify-tools.js";
|
|
8
9
|
import { defaultGitRunner, gitHead, worktreePath } from "./git.js";
|
|
9
10
|
import { newRitualStatePayload, ritualStatePath, ritualStep, writeRitualState, } from "./ritual-sentinel.js";
|
|
@@ -194,10 +195,30 @@ export function runSessionStart(projectRoot, options = {}) {
|
|
|
194
195
|
const quickSteps = recordDeferredSteps(QUICK_STEPS, deferrals, instant);
|
|
195
196
|
const gatedSteps = recordDeferredSteps(GATED_STEPS, deferrals, instant);
|
|
196
197
|
const lines = [];
|
|
198
|
+
// Resolve USER.md via the shared first-hit-wins resolver so the alignment
|
|
199
|
+
// step finds preferences automatically in mismatched / headless sandboxes
|
|
200
|
+
// with zero manual DEFT_USER_PATH (#2271 / #2124). Never throws: an absent
|
|
201
|
+
// USER.md degrades to a clear diagnostic below.
|
|
202
|
+
const resolveUserMd = options.resolveUserMd ?? ((root) => resolveUserMdPath({ projectRoot: root }));
|
|
203
|
+
const userMd = resolveUserMd(projectRoot);
|
|
197
204
|
if (!quickSteps.alignment) {
|
|
198
205
|
const message = "Deft Directive active -- AGENTS.md loaded.";
|
|
199
|
-
|
|
206
|
+
// Sanitize newlines on the data-derived path/diagnostic before they land in
|
|
207
|
+
// the ritual-step message / terminal output (matches doctor's CWE-116
|
|
208
|
+
// handling); DEFT_USER_PATH is only trimmed, so an embedded newline could
|
|
209
|
+
// otherwise survive into multi-line output.
|
|
210
|
+
const safePath = userMd.path.replace(/\r?\n/g, " ");
|
|
211
|
+
const safeDiagnostic = userMd.diagnostic.replace(/\r?\n/g, " ");
|
|
212
|
+
const userMdLine = userMd.found
|
|
213
|
+
? `USER.md resolved (${userMd.rung}): ${safePath}`
|
|
214
|
+
: safeDiagnostic;
|
|
215
|
+
quickSteps.alignment = ritualStep({
|
|
216
|
+
ok: true,
|
|
217
|
+
ts: instant,
|
|
218
|
+
message: `${message} ${userMdLine}`,
|
|
219
|
+
});
|
|
200
220
|
lines.push(message);
|
|
221
|
+
lines.push(userMdLine);
|
|
201
222
|
}
|
|
202
223
|
if (!quickSteps.branch_policy) {
|
|
203
224
|
const result = resolvePolicy(projectRoot);
|
|
@@ -287,6 +308,12 @@ export function runSessionStart(projectRoot, options = {}) {
|
|
|
287
308
|
state_path: statePath,
|
|
288
309
|
quick_steps: quickSteps,
|
|
289
310
|
gated_steps: gatedSteps,
|
|
311
|
+
user_md: {
|
|
312
|
+
path: userMd.path,
|
|
313
|
+
rung: userMd.rung,
|
|
314
|
+
found: userMd.found,
|
|
315
|
+
diagnostic: userMd.diagnostic,
|
|
316
|
+
},
|
|
290
317
|
message: code === 0 ? "session ritual recorded" : "session ritual failed",
|
|
291
318
|
};
|
|
292
319
|
return { code, payload: resultPayload, lines };
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared USER.md resolver (#2271).
|
|
3
|
+
*
|
|
4
|
+
* Resolves the USER.md user-config file across an explicit first-hit-wins
|
|
5
|
+
* search order so session-start, the CLI bootstrap, and doctor all share a
|
|
6
|
+
* single source of truth for "where does USER.md live?" — and so it resolves
|
|
7
|
+
* automatically in mismatched / headless sandboxes where `$HOME` is not a
|
|
8
|
+
* persistent mount (the #2124 Cowork gap) with zero manual `DEFT_USER_PATH`.
|
|
9
|
+
*
|
|
10
|
+
* Search order (first hit wins):
|
|
11
|
+
* 1. `DEFT_USER_PATH` — explicit override, highest precedence, points at the
|
|
12
|
+
* USER.md file directly. Always wins as the resolved path whether or not
|
|
13
|
+
* the file exists yet (presence is reported separately via `found`).
|
|
14
|
+
* 2. Workspace-local bridged config (`<projectRoot>/.deft/USER.md`) — a
|
|
15
|
+
* persistent location inside the workspace that survives a non-persistent
|
|
16
|
+
* `$HOME`.
|
|
17
|
+
* 3. Bridged platform config dir (`%APPDATA%\deft\USER.md` on Windows,
|
|
18
|
+
* `~/.config/deft/USER.md` elsewhere) — the historical default location.
|
|
19
|
+
* 4. Sensible default (the rung-3 path) + a `no USER.md found; using
|
|
20
|
+
* defaults` diagnostic. This branch NEVER throws.
|
|
21
|
+
*/
|
|
22
|
+
/** Exact diagnostic phrase emitted when USER.md is absent on every rung. */
|
|
23
|
+
export declare const NO_USER_MD_DIAGNOSTIC = "no USER.md found; using defaults";
|
|
24
|
+
/** The USER.md filename joined onto every candidate config directory. */
|
|
25
|
+
export declare const USER_MD_FILENAME = "USER.md";
|
|
26
|
+
/**
|
|
27
|
+
* Workspace-local config directory (relative to the project root). This is the
|
|
28
|
+
* bridged, persistent location inside the workspace that keeps preferences
|
|
29
|
+
* reachable even when the platform config dir under `$HOME` is not mounted.
|
|
30
|
+
*/
|
|
31
|
+
export declare const WORKSPACE_LOCAL_CONFIG_DIR = ".deft";
|
|
32
|
+
/** Which search rung produced the resolved path. */
|
|
33
|
+
export type UserMdRung = "env-override" | "workspace-local" | "platform-config" | "default";
|
|
34
|
+
export interface ResolveUserMdResult {
|
|
35
|
+
/**
|
|
36
|
+
* The resolved USER.md path. For rungs 2-3 this is always an existing file.
|
|
37
|
+
* For rung 1 (`env-override`) it is the override path whether or not the file
|
|
38
|
+
* exists yet (check `found`). For the `default` rung it is the sensible
|
|
39
|
+
* default location (which does not exist).
|
|
40
|
+
*/
|
|
41
|
+
readonly path: string;
|
|
42
|
+
/** Which search rung matched. */
|
|
43
|
+
readonly rung: UserMdRung;
|
|
44
|
+
/** True when a USER.md file actually exists at `path`. */
|
|
45
|
+
readonly found: boolean;
|
|
46
|
+
/** One-line human-readable diagnostic describing the resolution boundary. */
|
|
47
|
+
readonly diagnostic: string;
|
|
48
|
+
/** Candidate paths inspected, in search order (for diagnostics / tests). */
|
|
49
|
+
readonly searched: readonly string[];
|
|
50
|
+
}
|
|
51
|
+
export interface ResolveUserMdOptions {
|
|
52
|
+
/** Workspace root for the workspace-local rung. Defaults to `process.cwd()`. */
|
|
53
|
+
readonly projectRoot?: string;
|
|
54
|
+
/** Environment source (reads `DEFT_USER_PATH` / `APPDATA`). Defaults to `process.env`. */
|
|
55
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
56
|
+
/** Platform id for the platform-config rung. Defaults to `process.platform`. */
|
|
57
|
+
readonly platform?: NodeJS.Platform;
|
|
58
|
+
/** Home directory for the platform-config rung. Defaults to `os.homedir()`. */
|
|
59
|
+
readonly homeDir?: string;
|
|
60
|
+
/** File-existence probe. Defaults to an `existsSync` + `isFile` check. */
|
|
61
|
+
readonly fileExists?: (path: string) => boolean;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Resolve the platform config directory (`%APPDATA%\deft` / `~/.config/deft`).
|
|
65
|
+
* Mirrors `init-deposit`'s `userConfigDir()` platform branch but takes the
|
|
66
|
+
* platform / env / home as injectable inputs and deliberately does NOT honor
|
|
67
|
+
* `DEFT_USER_PATH` — that override is rung 1, handled by the caller.
|
|
68
|
+
*/
|
|
69
|
+
export declare function platformUserConfigDir(platform: NodeJS.Platform, env: NodeJS.ProcessEnv, homeDir: string): string;
|
|
70
|
+
/**
|
|
71
|
+
* Resolve the USER.md path across the first-hit-wins search order. Never
|
|
72
|
+
* throws: an absent USER.md degrades to the sensible default location with a
|
|
73
|
+
* clear `no USER.md found; using defaults` diagnostic.
|
|
74
|
+
*/
|
|
75
|
+
export declare function resolveUserMdPath(options?: ResolveUserMdOptions): ResolveUserMdResult;
|
|
76
|
+
//# sourceMappingURL=resolve-user-md.d.ts.map
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared USER.md resolver (#2271).
|
|
3
|
+
*
|
|
4
|
+
* Resolves the USER.md user-config file across an explicit first-hit-wins
|
|
5
|
+
* search order so session-start, the CLI bootstrap, and doctor all share a
|
|
6
|
+
* single source of truth for "where does USER.md live?" — and so it resolves
|
|
7
|
+
* automatically in mismatched / headless sandboxes where `$HOME` is not a
|
|
8
|
+
* persistent mount (the #2124 Cowork gap) with zero manual `DEFT_USER_PATH`.
|
|
9
|
+
*
|
|
10
|
+
* Search order (first hit wins):
|
|
11
|
+
* 1. `DEFT_USER_PATH` — explicit override, highest precedence, points at the
|
|
12
|
+
* USER.md file directly. Always wins as the resolved path whether or not
|
|
13
|
+
* the file exists yet (presence is reported separately via `found`).
|
|
14
|
+
* 2. Workspace-local bridged config (`<projectRoot>/.deft/USER.md`) — a
|
|
15
|
+
* persistent location inside the workspace that survives a non-persistent
|
|
16
|
+
* `$HOME`.
|
|
17
|
+
* 3. Bridged platform config dir (`%APPDATA%\deft\USER.md` on Windows,
|
|
18
|
+
* `~/.config/deft/USER.md` elsewhere) — the historical default location.
|
|
19
|
+
* 4. Sensible default (the rung-3 path) + a `no USER.md found; using
|
|
20
|
+
* defaults` diagnostic. This branch NEVER throws.
|
|
21
|
+
*/
|
|
22
|
+
import { statSync } from "node:fs";
|
|
23
|
+
import { homedir } from "node:os";
|
|
24
|
+
import { join, resolve } from "node:path";
|
|
25
|
+
/** Exact diagnostic phrase emitted when USER.md is absent on every rung. */
|
|
26
|
+
export const NO_USER_MD_DIAGNOSTIC = "no USER.md found; using defaults";
|
|
27
|
+
/** The USER.md filename joined onto every candidate config directory. */
|
|
28
|
+
export const USER_MD_FILENAME = "USER.md";
|
|
29
|
+
/**
|
|
30
|
+
* Workspace-local config directory (relative to the project root). This is the
|
|
31
|
+
* bridged, persistent location inside the workspace that keeps preferences
|
|
32
|
+
* reachable even when the platform config dir under `$HOME` is not mounted.
|
|
33
|
+
*/
|
|
34
|
+
export const WORKSPACE_LOCAL_CONFIG_DIR = ".deft";
|
|
35
|
+
function defaultFileExists(path) {
|
|
36
|
+
try {
|
|
37
|
+
return statSync(path).isFile();
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// Any stat error (ENOENT, permission, ...) means "not a resolvable USER.md
|
|
41
|
+
// file". Deliberately NOT falling back to existsSync, which cannot tell a
|
|
42
|
+
// directory from a file and would report a dir at this path as a found file.
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Resolve the platform config directory (`%APPDATA%\deft` / `~/.config/deft`).
|
|
48
|
+
* Mirrors `init-deposit`'s `userConfigDir()` platform branch but takes the
|
|
49
|
+
* platform / env / home as injectable inputs and deliberately does NOT honor
|
|
50
|
+
* `DEFT_USER_PATH` — that override is rung 1, handled by the caller.
|
|
51
|
+
*/
|
|
52
|
+
export function platformUserConfigDir(platform, env, homeDir) {
|
|
53
|
+
if (platform === "win32") {
|
|
54
|
+
const appData = env.APPDATA?.trim();
|
|
55
|
+
return appData ? join(appData, "deft") : join(homeDir, "AppData", "Roaming", "deft");
|
|
56
|
+
}
|
|
57
|
+
return join(homeDir, ".config", "deft");
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Resolve the USER.md path across the first-hit-wins search order. Never
|
|
61
|
+
* throws: an absent USER.md degrades to the sensible default location with a
|
|
62
|
+
* clear `no USER.md found; using defaults` diagnostic.
|
|
63
|
+
*/
|
|
64
|
+
export function resolveUserMdPath(options = {}) {
|
|
65
|
+
const env = options.env ?? process.env;
|
|
66
|
+
const platform = options.platform ?? process.platform;
|
|
67
|
+
const homeDir = options.homeDir ?? homedir();
|
|
68
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
69
|
+
const fileExists = options.fileExists ?? defaultFileExists;
|
|
70
|
+
const searched = [];
|
|
71
|
+
// Rung 1: DEFT_USER_PATH override (full file path); always wins.
|
|
72
|
+
const override = env.DEFT_USER_PATH?.trim();
|
|
73
|
+
if (override) {
|
|
74
|
+
const overridePath = resolve(override);
|
|
75
|
+
searched.push(overridePath);
|
|
76
|
+
const found = fileExists(overridePath);
|
|
77
|
+
return {
|
|
78
|
+
path: overridePath,
|
|
79
|
+
rung: "env-override",
|
|
80
|
+
found,
|
|
81
|
+
diagnostic: found
|
|
82
|
+
? `USER.md resolved from DEFT_USER_PATH: ${overridePath}`
|
|
83
|
+
: `USER.md path from DEFT_USER_PATH does not exist yet: ${overridePath}`,
|
|
84
|
+
searched,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
// Rung 2: workspace-local bridged config.
|
|
88
|
+
const workspacePath = resolve(join(projectRoot, WORKSPACE_LOCAL_CONFIG_DIR, USER_MD_FILENAME));
|
|
89
|
+
searched.push(workspacePath);
|
|
90
|
+
if (fileExists(workspacePath)) {
|
|
91
|
+
return {
|
|
92
|
+
path: workspacePath,
|
|
93
|
+
rung: "workspace-local",
|
|
94
|
+
found: true,
|
|
95
|
+
diagnostic: `USER.md resolved from workspace-local config: ${workspacePath}`,
|
|
96
|
+
searched,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
// Rung 3: bridged platform config dir.
|
|
100
|
+
const platformPath = join(platformUserConfigDir(platform, env, homeDir), USER_MD_FILENAME);
|
|
101
|
+
searched.push(platformPath);
|
|
102
|
+
if (fileExists(platformPath)) {
|
|
103
|
+
return {
|
|
104
|
+
path: platformPath,
|
|
105
|
+
rung: "platform-config",
|
|
106
|
+
found: true,
|
|
107
|
+
diagnostic: `USER.md resolved from platform config dir: ${platformPath}`,
|
|
108
|
+
searched,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
// Rung 4: sensible default + diagnostic (never throws).
|
|
112
|
+
return {
|
|
113
|
+
path: platformPath,
|
|
114
|
+
rung: "default",
|
|
115
|
+
found: false,
|
|
116
|
+
diagnostic: `${NO_USER_MD_DIAGNOSTIC} (searched: ${searched.join(", ")})`,
|
|
117
|
+
searched,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=resolve-user-md.js.map
|
|
@@ -10,4 +10,14 @@ export declare const LEGACY_INFO_ROOT_KEY: "vBRIEFInfo";
|
|
|
10
10
|
export declare const MIGRATED_INFO_ROOT_KEY: "xBRIEFInfo";
|
|
11
11
|
/** Item-only status value added in xBRIEF v0.8. */
|
|
12
12
|
export declare const V08_ITEM_STATUS_AUTO: "auto";
|
|
13
|
+
/**
|
|
14
|
+
* Filename of the explicit deprecation marker written into a legacy `vbrief/`
|
|
15
|
+
* root that is retained for read-compatibility after convergence (#2270). Its
|
|
16
|
+
* presence is what tells `doctor` the folder is NOT an active source of truth.
|
|
17
|
+
*/
|
|
18
|
+
export declare const VBRIEF_DEPRECATION_MARKER_FILENAME: "DEPRECATED.md";
|
|
19
|
+
/** Sentinel line identifying a deft-written `vbrief/` deprecation marker (#2270). */
|
|
20
|
+
export declare const VBRIEF_DEPRECATION_MARKER_SENTINEL: "<!-- deft:vbrief-deprecated -->";
|
|
21
|
+
/** Body written into the retained legacy `vbrief/` deprecation marker (#2270). */
|
|
22
|
+
export declare const VBRIEF_DEPRECATION_MARKER_BODY: "<!-- deft:vbrief-deprecated -->\n# Deprecated: legacy `vbrief/` root\n\nThis project has migrated to the `xbrief/` lifecycle layout. This `vbrief/`\ndirectory is retained only for read-compatibility and is **not** an active\nsource of truth. Do not add new scope work here — use `xbrief/` instead.\n\nOnce you no longer need read-compatibility with the legacy layout, delete this\n`vbrief/` directory. Re-running `deft migrate:xbrief` re-checks convergence.\n";
|
|
13
23
|
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -11,4 +11,23 @@ export const LEGACY_INFO_ROOT_KEY = "vBRIEFInfo";
|
|
|
11
11
|
export const MIGRATED_INFO_ROOT_KEY = "xBRIEFInfo";
|
|
12
12
|
/** Item-only status value added in xBRIEF v0.8. */
|
|
13
13
|
export const V08_ITEM_STATUS_AUTO = "auto";
|
|
14
|
+
/**
|
|
15
|
+
* Filename of the explicit deprecation marker written into a legacy `vbrief/`
|
|
16
|
+
* root that is retained for read-compatibility after convergence (#2270). Its
|
|
17
|
+
* presence is what tells `doctor` the folder is NOT an active source of truth.
|
|
18
|
+
*/
|
|
19
|
+
export const VBRIEF_DEPRECATION_MARKER_FILENAME = "DEPRECATED.md";
|
|
20
|
+
/** Sentinel line identifying a deft-written `vbrief/` deprecation marker (#2270). */
|
|
21
|
+
export const VBRIEF_DEPRECATION_MARKER_SENTINEL = "<!-- deft:vbrief-deprecated -->";
|
|
22
|
+
/** Body written into the retained legacy `vbrief/` deprecation marker (#2270). */
|
|
23
|
+
export const VBRIEF_DEPRECATION_MARKER_BODY = `${VBRIEF_DEPRECATION_MARKER_SENTINEL}
|
|
24
|
+
# Deprecated: legacy \`vbrief/\` root
|
|
25
|
+
|
|
26
|
+
This project has migrated to the \`xbrief/\` lifecycle layout. This \`vbrief/\`
|
|
27
|
+
directory is retained only for read-compatibility and is **not** an active
|
|
28
|
+
source of truth. Do not add new scope work here — use \`xbrief/\` instead.
|
|
29
|
+
|
|
30
|
+
Once you no longer need read-compatibility with the legacy layout, delete this
|
|
31
|
+
\`vbrief/\` directory. Re-running \`deft migrate:xbrief\` re-checks convergence.
|
|
32
|
+
`;
|
|
14
33
|
//# sourceMappingURL=constants.js.map
|
|
@@ -3,9 +3,43 @@ export interface LegacyVbriefLayoutDetection {
|
|
|
3
3
|
legacyLayout: boolean;
|
|
4
4
|
reasons: string[];
|
|
5
5
|
}
|
|
6
|
+
/**
|
|
7
|
+
* Discriminated convergence state of the `vbrief/` + `xbrief/` lifecycle roots
|
|
8
|
+
* (#2270). Distinguishes the ambiguous dual-empty-root case that `migrate:xbrief`
|
|
9
|
+
* must converge from the states that are already unambiguous.
|
|
10
|
+
*
|
|
11
|
+
* - `none` — neither root carries content (nothing to converge).
|
|
12
|
+
* - `xbrief-only` — canonical `xbrief/` with content and no `vbrief/`.
|
|
13
|
+
* - `xbrief-marker` — `vbrief/` retained behind an explicit deprecation marker.
|
|
14
|
+
* - `empty-vbrief` — a stray, fully-migrated empty `vbrief/` (the ambiguous root
|
|
15
|
+
* to clean up: remove it, or mark it when read-compat retention is requested).
|
|
16
|
+
* - `legacy-only` — `vbrief/` holds content and there is no canonical `xbrief/`;
|
|
17
|
+
* a full migration is required.
|
|
18
|
+
* - `dual-populated` — both roots hold content with no marker; converge by
|
|
19
|
+
* marking the legacy tree deprecated (never destructively merged).
|
|
20
|
+
*/
|
|
21
|
+
export type XbriefConvergenceState = "none" | "xbrief-only" | "xbrief-marker" | "empty-vbrief" | "legacy-only" | "dual-populated";
|
|
22
|
+
/** Structured convergence probe of the two lifecycle roots (#2270). */
|
|
23
|
+
export interface XbriefConvergenceDetection {
|
|
24
|
+
state: XbriefConvergenceState;
|
|
25
|
+
vbriefPresent: boolean;
|
|
26
|
+
vbriefEmpty: boolean;
|
|
27
|
+
vbriefHasMarker: boolean;
|
|
28
|
+
xbriefPresent: boolean;
|
|
29
|
+
xbriefHasContent: boolean;
|
|
30
|
+
}
|
|
6
31
|
/**
|
|
7
32
|
* Detect whether `projectRoot` still uses the legacy vbrief on-disk layout.
|
|
8
33
|
* Mirrors the structured-reasons pattern from `detectPreCutover` (#793).
|
|
9
34
|
*/
|
|
10
35
|
export declare function detectLegacyVbriefLayout(projectRoot: string): LegacyVbriefLayoutDetection;
|
|
36
|
+
/**
|
|
37
|
+
* Classify the convergence state of the `vbrief/` and `xbrief/` lifecycle roots
|
|
38
|
+
* so the orchestrator can converge to a single unambiguous root (#2270). Unlike
|
|
39
|
+
* {@link detectLegacyVbriefLayout} (which only asks "is there anything legacy?"),
|
|
40
|
+
* this probe distinguishes the ambiguous dual-empty-root case from an already
|
|
41
|
+
* unambiguous layout, and never falls back to a `run migrate:xbrief` dead end
|
|
42
|
+
* for a `vbrief/` that is merely an empty leftover.
|
|
43
|
+
*/
|
|
44
|
+
export declare function detectXbriefConvergence(projectRoot: string): XbriefConvergenceDetection;
|
|
11
45
|
//# sourceMappingURL=detect.d.ts.map
|
|
@@ -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?: {
|