@deftai/directive-core 0.57.0 → 0.59.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/resolve-content.d.ts +6 -0
- package/dist/deposit/resolve-content.js +7 -1
- package/dist/doctor/checks.js +2 -2
- package/dist/legacy-bridge/sot.js +1 -1
- package/dist/release/index.d.ts +2 -1
- package/dist/release/index.js +2 -1
- package/dist/release/pipeline.js +8 -3
- package/dist/release/preflight.d.ts +27 -0
- package/dist/release/preflight.js +27 -0
- package/dist/release/{python-bridge.d.ts → python-steps.d.ts} +1 -2
- package/dist/release/{python-bridge.js → python-steps.js} +13 -14
- package/dist/triage/actions/index.js +1 -1
- package/dist/vbrief-validate/precutover.d.ts +24 -0
- package/dist/vbrief-validate/precutover.js +66 -1
- package/dist/vbrief-validate/project-definition.js +24 -15
- package/dist/vbrief-validate/registry-status.d.ts +3 -3
- package/dist/vbrief-validate/registry-status.js +3 -38
- package/package.json +3 -3
|
@@ -22,6 +22,12 @@ export declare function contentPackageRootFromResolvedEntry(resolvedEntry: strin
|
|
|
22
22
|
/**
|
|
23
23
|
* Resolve the installed {@link CONTENT_PACKAGE_NAME} package root via Node module
|
|
24
24
|
* resolution (`import.meta.resolve`).
|
|
25
|
+
*
|
|
26
|
+
* Resolves the package's `package.json` subpath rather than its bare specifier:
|
|
27
|
+
* the content package ships no entry point (no `main`, `exports`, or `index.js`),
|
|
28
|
+
* so a bare-specifier resolve throws. The `package.json` subpath always resolves,
|
|
29
|
+
* and {@link contentPackageRootFromResolvedEntry} walks up from it to the package
|
|
30
|
+
* root. Refs #2023.
|
|
25
31
|
*/
|
|
26
32
|
export declare function resolveInstalledContentRoot(resolveSpecifier?: ResolveSpecifier): Promise<string>;
|
|
27
33
|
export {};
|
|
@@ -52,11 +52,17 @@ export function contentPackageRootFromResolvedEntry(resolvedEntry) {
|
|
|
52
52
|
/**
|
|
53
53
|
* Resolve the installed {@link CONTENT_PACKAGE_NAME} package root via Node module
|
|
54
54
|
* resolution (`import.meta.resolve`).
|
|
55
|
+
*
|
|
56
|
+
* Resolves the package's `package.json` subpath rather than its bare specifier:
|
|
57
|
+
* the content package ships no entry point (no `main`, `exports`, or `index.js`),
|
|
58
|
+
* so a bare-specifier resolve throws. The `package.json` subpath always resolves,
|
|
59
|
+
* and {@link contentPackageRootFromResolvedEntry} walks up from it to the package
|
|
60
|
+
* root. Refs #2023.
|
|
55
61
|
*/
|
|
56
62
|
export async function resolveInstalledContentRoot(resolveSpecifier = defaultResolveSpecifier) {
|
|
57
63
|
let resolvedEntry;
|
|
58
64
|
try {
|
|
59
|
-
resolvedEntry = await resolveSpecifier(CONTENT_PACKAGE_NAME);
|
|
65
|
+
resolvedEntry = await resolveSpecifier(`${CONTENT_PACKAGE_NAME}/package.json`);
|
|
60
66
|
}
|
|
61
67
|
catch (cause) {
|
|
62
68
|
const detail = cause instanceof Error ? cause.message : String(cause);
|
package/dist/doctor/checks.js
CHANGED
|
@@ -253,7 +253,7 @@ export function checkInstallPathConsistency(projectRoot, installRoot, seams = {}
|
|
|
253
253
|
return {
|
|
254
254
|
name: "install-path-consistency",
|
|
255
255
|
status: "fail",
|
|
256
|
-
detail: `Install root is recorded as '${effectiveInstallRoot}' (source: ${source}) but ${claimedDir} is not a directory. Pick one of two repair paths: (a) run \`.deft/core/run agents:refresh\` (Unix) / \`.deft\\core\\run agents:refresh\` (Windows) to rewrite AGENTS.md to match the on-disk framework -- pick this if the framework on disk is correct; OR (b) run \`
|
|
256
|
+
detail: `Install root is recorded as '${effectiveInstallRoot}' (source: ${source}) but ${claimedDir} is not a directory. Pick one of two repair paths: (a) run \`.deft/core/run agents:refresh\` (Unix) / \`.deft\\core\\run agents:refresh\` (Windows) to rewrite AGENTS.md to match the on-disk framework -- pick this if the framework on disk is correct; OR (b) run \`npx @deftai/directive update\` to (re)deposit the framework at the path AGENTS.md / the manifest claims (the npm CLI project deposit, #1912) -- pick this if AGENTS.md is correct. The YAML manifest (if present) is authoritative for the install-layout contract. See UPGRADING.md for the canonical drift-repair walkthrough.`,
|
|
257
257
|
data: {
|
|
258
258
|
claimed_install_root: installRoot,
|
|
259
259
|
effective_install_root: effectiveInstallRoot,
|
|
@@ -262,7 +262,7 @@ export function checkInstallPathConsistency(projectRoot, installRoot, seams = {}
|
|
|
262
262
|
claimed_dir_exists: false,
|
|
263
263
|
fallback_info_note: fallbackInfoNote || null,
|
|
264
264
|
suggested_fix: ".deft/core/run agents:refresh",
|
|
265
|
-
suggested_fix_alt: "
|
|
265
|
+
suggested_fix_alt: "npx @deftai/directive update",
|
|
266
266
|
},
|
|
267
267
|
};
|
|
268
268
|
}
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
* at freeze time. Leave it `null` otherwise. This is the only edit required to
|
|
38
38
|
* pin the bridge version; the freeze + drift gates pick it up automatically.
|
|
39
39
|
*/
|
|
40
|
-
export const LAST_GO_INSTALLER = "v0.
|
|
40
|
+
export const LAST_GO_INSTALLER = "v0.58.0";
|
|
41
41
|
/** Returns the frozen final Go-installer tag, or `null` while unfrozen. */
|
|
42
42
|
export function lastGoInstaller() {
|
|
43
43
|
return LAST_GO_INSTALLER;
|
package/dist/release/index.d.ts
CHANGED
|
@@ -5,9 +5,10 @@ export * from "./git.js";
|
|
|
5
5
|
export * from "./main.js";
|
|
6
6
|
export * from "./paths.js";
|
|
7
7
|
export * from "./pipeline.js";
|
|
8
|
+
export * from "./preflight.js";
|
|
8
9
|
export * from "./pyproject.js";
|
|
9
10
|
export * from "./pyproject-sync.js";
|
|
10
|
-
export * from "./python-
|
|
11
|
+
export * from "./python-steps.js";
|
|
11
12
|
export * from "./spawn.js";
|
|
12
13
|
export * from "./types.js";
|
|
13
14
|
export * from "./version.js";
|
package/dist/release/index.js
CHANGED
|
@@ -6,9 +6,10 @@ export * from "./git.js";
|
|
|
6
6
|
export * from "./main.js";
|
|
7
7
|
export * from "./paths.js";
|
|
8
8
|
export * from "./pipeline.js";
|
|
9
|
+
export * from "./preflight.js";
|
|
9
10
|
export * from "./pyproject.js";
|
|
10
11
|
export * from "./pyproject-sync.js";
|
|
11
|
-
export * from "./python-
|
|
12
|
+
export * from "./python-steps.js";
|
|
12
13
|
export * from "./spawn.js";
|
|
13
14
|
export * from "./types.js";
|
|
14
15
|
export * from "./version.js";
|
package/dist/release/pipeline.js
CHANGED
|
@@ -5,8 +5,9 @@ import { EXIT_CONFIG_ERROR, EXIT_OK, EXIT_VIOLATION, RELEASE_ARTIFACTS, TOTAL_ST
|
|
|
5
5
|
import { checkTagAvailable, createGithubRelease, readTextFile, verifyReleaseDraft } from "./gh.js";
|
|
6
6
|
import { checkGitClean, commitReleaseArtifacts, createTag, currentBranch, pushRelease, releaseCommitSubject, } from "./git.js";
|
|
7
7
|
import { resolveScriptsDir, todayIso } from "./paths.js";
|
|
8
|
+
import { runReleaseCheck } from "./preflight.js";
|
|
8
9
|
import { pyprojectPathFor, syncPyprojectForRelease } from "./pyproject-sync.js";
|
|
9
|
-
import { checkVbriefLifecycleSync, refreshRoadmap, runBuild,
|
|
10
|
+
import { checkVbriefLifecycleSync, refreshRoadmap, runBuild, runUvLock } from "./python-steps.js";
|
|
10
11
|
import { isPrereleaseTag } from "./version.js";
|
|
11
12
|
export function emit(step, label, status, target = process.stderr) {
|
|
12
13
|
target.write(`[${step}/${TOTAL_STEPS}] ${label}... ${status}\n`);
|
|
@@ -20,7 +21,7 @@ export function runPipeline(config, seams = {}) {
|
|
|
20
21
|
const readFile = seams.readFile ?? ((p) => readFileSync(p, "utf8"));
|
|
21
22
|
const writeFile = seams.writeFile ?? ((p, c) => writeFileSync(p, c, "utf8"));
|
|
22
23
|
const fileExists = seams.fileExists ?? ((p) => existsSync(p));
|
|
23
|
-
const runCiFn = seams.runCi ?? ((root) =>
|
|
24
|
+
const runCiFn = seams.runCi ?? ((root) => runReleaseCheck(root));
|
|
24
25
|
const refreshRoadmapFn = seams.refreshRoadmap ?? ((root) => refreshRoadmap(root, scriptsDir, seams));
|
|
25
26
|
const checkVbriefFn = seams.checkVbriefLifecycleSync ??
|
|
26
27
|
((root, repo) => checkVbriefLifecycleSync(root, repo, scriptsDir, seams));
|
|
@@ -100,7 +101,11 @@ export function runPipeline(config, seams = {}) {
|
|
|
100
101
|
return EXIT_VIOLATION;
|
|
101
102
|
}
|
|
102
103
|
}
|
|
103
|
-
// Step 5: CI.
|
|
104
|
+
// Step 5: CI pre-flight. The functional path now invokes the native
|
|
105
|
+
// TypeScript `task check` (via `runReleaseCheck`, #2022 Phase 1) instead of
|
|
106
|
+
// the ci_local.py bridge, but the emitted label/dry-run text is kept
|
|
107
|
+
// byte-identical to the Python oracle (scripts/release.py) so the #1729
|
|
108
|
+
// golden-diff release-parity gate stays green until the oracle is retired.
|
|
104
109
|
label = "Pre-flight CI (task ci:local | fallback task check)";
|
|
105
110
|
if (config.skipCi) {
|
|
106
111
|
emit(5, label, "SKIP (--skip-ci)");
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* preflight.ts -- Native TypeScript release Step-5 pre-flight (#2022 Phase 1).
|
|
3
|
+
*
|
|
4
|
+
* Promotes the context-aware `task check` orchestrator (check/orchestrator.ts)
|
|
5
|
+
* to the primary release pre-flight, replacing the removed `ci_local.py`
|
|
6
|
+
* python-bridge shim. The maintainer release runs in the framework-source
|
|
7
|
+
* context, so the framework root equals the project root and the orchestrator
|
|
8
|
+
* dispatches `check:framework-source`.
|
|
9
|
+
*/
|
|
10
|
+
import { type CheckOrchestratorSeams } from "../check/orchestrator.js";
|
|
11
|
+
/** Seams for test isolation of the native release pre-flight. */
|
|
12
|
+
export interface ReleasePreflightSeams {
|
|
13
|
+
/** Override the check dispatcher (default: dispatchTaskCheck from check/orchestrator). */
|
|
14
|
+
readonly dispatchCheck?: (frameworkRoot: string, projectRoot: string, seams?: CheckOrchestratorSeams) => number;
|
|
15
|
+
/** Seams forwarded to the underlying check orchestrator (e.g. taskBin, spawnFn). */
|
|
16
|
+
readonly checkSeams?: CheckOrchestratorSeams;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Run the native TypeScript `task check` as the release pre-flight.
|
|
20
|
+
*
|
|
21
|
+
* Returns the pipeline's standard `[ok, message]` tuple. The maintainer release
|
|
22
|
+
* cuts the framework itself, so we pass `projectRoot` as both the framework root
|
|
23
|
+
* and the project root -- the orchestrator then resolves the framework-source
|
|
24
|
+
* context and runs `check:framework-source`.
|
|
25
|
+
*/
|
|
26
|
+
export declare function runReleaseCheck(projectRoot: string, seams?: ReleasePreflightSeams): [boolean, string];
|
|
27
|
+
//# sourceMappingURL=preflight.d.ts.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* preflight.ts -- Native TypeScript release Step-5 pre-flight (#2022 Phase 1).
|
|
3
|
+
*
|
|
4
|
+
* Promotes the context-aware `task check` orchestrator (check/orchestrator.ts)
|
|
5
|
+
* to the primary release pre-flight, replacing the removed `ci_local.py`
|
|
6
|
+
* python-bridge shim. The maintainer release runs in the framework-source
|
|
7
|
+
* context, so the framework root equals the project root and the orchestrator
|
|
8
|
+
* dispatches `check:framework-source`.
|
|
9
|
+
*/
|
|
10
|
+
import { dispatchTaskCheck } from "../check/orchestrator.js";
|
|
11
|
+
/**
|
|
12
|
+
* Run the native TypeScript `task check` as the release pre-flight.
|
|
13
|
+
*
|
|
14
|
+
* Returns the pipeline's standard `[ok, message]` tuple. The maintainer release
|
|
15
|
+
* cuts the framework itself, so we pass `projectRoot` as both the framework root
|
|
16
|
+
* and the project root -- the orchestrator then resolves the framework-source
|
|
17
|
+
* context and runs `check:framework-source`.
|
|
18
|
+
*/
|
|
19
|
+
export function runReleaseCheck(projectRoot, seams = {}) {
|
|
20
|
+
const dispatch = seams.dispatchCheck ?? dispatchTaskCheck;
|
|
21
|
+
const code = dispatch(projectRoot, projectRoot, seams.checkSeams);
|
|
22
|
+
if (code === 0) {
|
|
23
|
+
return [true, "ran native TypeScript task check"];
|
|
24
|
+
}
|
|
25
|
+
return [false, `task check failed (exit ${code})`];
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=preflight.js.map
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { ReleaseSeams } from "./types.js";
|
|
2
|
-
export declare function runCi(projectRoot: string, scriptsDir: string, seams?: ReleaseSeams): [boolean, string];
|
|
3
2
|
export declare function refreshRoadmap(projectRoot: string, scriptsDir: string, seams?: ReleaseSeams): [boolean, string];
|
|
4
3
|
export declare function checkVbriefLifecycleSync(projectRoot: string, repo: string, scriptsDir: string, seams?: ReleaseSeams): [boolean, number, string];
|
|
5
4
|
export declare function runBuild(projectRoot: string, scriptsDir: string, version: string | null, seams?: ReleaseSeams): [boolean, string];
|
|
6
5
|
export declare function runUvLock(projectRoot: string, seams?: ReleaseSeams): [boolean, string];
|
|
7
|
-
//# sourceMappingURL=python-
|
|
6
|
+
//# sourceMappingURL=python-steps.d.ts.map
|
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* python-steps.ts -- Residual Python-backed release pipeline steps.
|
|
3
|
+
*
|
|
4
|
+
* #2022 Phase 1 removed the `ci_local.py` Step-5 pre-flight bridge (now native
|
|
5
|
+
* TypeScript via release/preflight.ts), retiring the former `python-bridge.ts`
|
|
6
|
+
* module. These remaining steps still shell into bundled Python and stay here
|
|
7
|
+
* until their own purge phases land:
|
|
8
|
+
* - refreshRoadmap (Step 7) -> scripts/roadmap_render.py
|
|
9
|
+
* - checkVbriefLifecycleSync (Step 3) -> scripts/reconcile_issues.py
|
|
10
|
+
* - runBuild (Step 8) -> scripts/build_dist.py (Bucket B, deleted by #1860)
|
|
11
|
+
* - runUvLock (Step 6) -> `uv lock`
|
|
12
|
+
*/
|
|
1
13
|
import { existsSync, statSync } from "node:fs";
|
|
2
14
|
import { join } from "node:path";
|
|
3
15
|
import { defaultWhich, spawnText } from "./spawn.js";
|
|
@@ -9,19 +21,6 @@ function runUvPython(_scriptsDir, code, cwd, env = process.env, seams = {}) {
|
|
|
9
21
|
timeoutMs: 300_000,
|
|
10
22
|
});
|
|
11
23
|
}
|
|
12
|
-
export function runCi(projectRoot, scriptsDir, seams = {}) {
|
|
13
|
-
const code = [
|
|
14
|
-
"import sys",
|
|
15
|
-
`sys.path.insert(0, ${JSON.stringify(scriptsDir)})`,
|
|
16
|
-
"import ci_local",
|
|
17
|
-
`sys.exit(ci_local.main(['--root', ${JSON.stringify(projectRoot)}]))`,
|
|
18
|
-
].join("\n");
|
|
19
|
-
const result = runUvPython(scriptsDir, code, scriptsDir, process.env, seams);
|
|
20
|
-
if (result.status !== 0) {
|
|
21
|
-
return [false, `ci:local failed (exit ${result.status})`];
|
|
22
|
-
}
|
|
23
|
-
return [true, "ran ci:local"];
|
|
24
|
-
}
|
|
25
24
|
export function refreshRoadmap(projectRoot, scriptsDir, seams = {}) {
|
|
26
25
|
const pending = join(projectRoot, "vbrief", "pending");
|
|
27
26
|
const roadmap = join(projectRoot, "ROADMAP.md");
|
|
@@ -141,4 +140,4 @@ export function runUvLock(projectRoot, seams = {}) {
|
|
|
141
140
|
}
|
|
142
141
|
return [true, "uv.lock regenerated"];
|
|
143
142
|
}
|
|
144
|
-
//# sourceMappingURL=python-
|
|
143
|
+
//# sourceMappingURL=python-steps.js.map
|
|
@@ -37,7 +37,7 @@ function defaultScmRunner() {
|
|
|
37
37
|
call(source, verb, args, options = {}) {
|
|
38
38
|
try {
|
|
39
39
|
const result = call(source, verb, args, {
|
|
40
|
-
check:
|
|
40
|
+
check: false,
|
|
41
41
|
captureOutput: true,
|
|
42
42
|
});
|
|
43
43
|
if ((options.check ?? false) && result.returncode !== 0) {
|
|
@@ -4,4 +4,28 @@ export { DEPRECATION_SENTINEL as DEPRECATED_REDIRECT_SENTINEL };
|
|
|
4
4
|
export declare function isDeprecationRedirect(content: string): boolean;
|
|
5
5
|
/** Return true for a fully current ``task spec:render`` root export. */
|
|
6
6
|
export declare function isCurrentGeneratedSpecification(projectRoot: string, content: string): boolean;
|
|
7
|
+
/** Structured result of a pre-cutover (pre-v0.20 document model) probe. */
|
|
8
|
+
export interface PrecutoverDetection {
|
|
9
|
+
/** True when any legacy pre-v0.20 artifact still needs migration. */
|
|
10
|
+
preCutover: boolean;
|
|
11
|
+
/** Human-readable reasons; empty when the project is on the current model. */
|
|
12
|
+
reasons: string[];
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Detect whether ``projectRoot`` still carries pre-v0.20 (pre-cutover) document-model
|
|
16
|
+
* artifacts that need migration. Mirrors the AGENTS.md "Pre-Cutover Check" rule and the
|
|
17
|
+
* legacy ``scripts/_precutover.py`` helper: a project is pre-cutover when any of the
|
|
18
|
+
* following hold:
|
|
19
|
+
*
|
|
20
|
+
* - ``SPECIFICATION.md`` exists and is neither a deprecation redirect nor a current
|
|
21
|
+
* generated spec export;
|
|
22
|
+
* - ``PROJECT.md`` exists and is not a deprecation redirect;
|
|
23
|
+
* - ``vbrief/`` exists but is missing one or more lifecycle folders.
|
|
24
|
+
*/
|
|
25
|
+
export declare function detectPreCutover(projectRoot: string): PrecutoverDetection;
|
|
26
|
+
/**
|
|
27
|
+
* Render the single-line pre-cutover status string surfaced by ``deft doctor``.
|
|
28
|
+
* Flags the migration-needed state with reasons, or reports a clean current-model state.
|
|
29
|
+
*/
|
|
30
|
+
export declare function renderPrecutoverLine(projectRoot: string): string;
|
|
7
31
|
//# sourceMappingURL=precutover.d.ts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
1
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { DEPRECATION_SENTINEL } from "../vbrief-build/constants.js";
|
|
4
4
|
export { DEPRECATION_SENTINEL as DEPRECATED_REDIRECT_SENTINEL };
|
|
@@ -27,4 +27,69 @@ function isGeneratedSpecificationExport(projectRoot, content) {
|
|
|
27
27
|
export function isCurrentGeneratedSpecification(projectRoot, content) {
|
|
28
28
|
return isGeneratedSpecificationExport(projectRoot, content) && hasCompleteLifecycle(projectRoot);
|
|
29
29
|
}
|
|
30
|
+
function isFile(path) {
|
|
31
|
+
try {
|
|
32
|
+
return statSync(path).isFile();
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function safeReadText(path) {
|
|
39
|
+
try {
|
|
40
|
+
return readFileSync(path, "utf8");
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return "";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Detect whether ``projectRoot`` still carries pre-v0.20 (pre-cutover) document-model
|
|
48
|
+
* artifacts that need migration. Mirrors the AGENTS.md "Pre-Cutover Check" rule and the
|
|
49
|
+
* legacy ``scripts/_precutover.py`` helper: a project is pre-cutover when any of the
|
|
50
|
+
* following hold:
|
|
51
|
+
*
|
|
52
|
+
* - ``SPECIFICATION.md`` exists and is neither a deprecation redirect nor a current
|
|
53
|
+
* generated spec export;
|
|
54
|
+
* - ``PROJECT.md`` exists and is not a deprecation redirect;
|
|
55
|
+
* - ``vbrief/`` exists but is missing one or more lifecycle folders.
|
|
56
|
+
*/
|
|
57
|
+
export function detectPreCutover(projectRoot) {
|
|
58
|
+
const reasons = [];
|
|
59
|
+
const specPath = join(projectRoot, "SPECIFICATION.md");
|
|
60
|
+
if (isFile(specPath)) {
|
|
61
|
+
const content = safeReadText(specPath);
|
|
62
|
+
if (!isDeprecationRedirect(content) && !isCurrentGeneratedSpecification(projectRoot, content)) {
|
|
63
|
+
reasons.push("SPECIFICATION.md is a pre-v0.20 hand-authored doc (not a deprecation redirect or current generated export)");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const projectMdPath = join(projectRoot, "PROJECT.md");
|
|
67
|
+
if (isFile(projectMdPath)) {
|
|
68
|
+
const content = safeReadText(projectMdPath);
|
|
69
|
+
if (!isDeprecationRedirect(content)) {
|
|
70
|
+
reasons.push("PROJECT.md is a pre-v0.20 hand-authored doc (not a deprecation redirect)");
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const vbriefRoot = join(projectRoot, "vbrief");
|
|
74
|
+
if (existsSync(vbriefRoot)) {
|
|
75
|
+
const missing = missingLifecycleFolders(projectRoot);
|
|
76
|
+
if (missing.length > 0) {
|
|
77
|
+
reasons.push(`vbrief/ is missing lifecycle folder(s): ${missing.join(", ")}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return { preCutover: reasons.length > 0, reasons };
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Render the single-line pre-cutover status string surfaced by ``deft doctor``.
|
|
84
|
+
* Flags the migration-needed state with reasons, or reports a clean current-model state.
|
|
85
|
+
*/
|
|
86
|
+
export function renderPrecutoverLine(projectRoot) {
|
|
87
|
+
const { preCutover, reasons } = detectPreCutover(projectRoot);
|
|
88
|
+
if (!preCutover) {
|
|
89
|
+
return "Pre-cutover: none -- project is on the current vBRIEF document model.";
|
|
90
|
+
}
|
|
91
|
+
// Collapse any embedded newlines so the status line stays a single line (CWE-116).
|
|
92
|
+
const summary = reasons.join("; ").replace(/\r?\n/g, " ");
|
|
93
|
+
return `Pre-cutover: migration needed -- ${summary}. Run \`deft migrate:vbrief\` to migrate.`;
|
|
94
|
+
}
|
|
30
95
|
//# sourceMappingURL=precutover.js.map
|
|
@@ -4,14 +4,27 @@ import { isRelativeTo, resolveRefPath } from "./paths.js";
|
|
|
4
4
|
import { runProjectDefinitionHooks } from "./plan-hooks.js";
|
|
5
5
|
import { formatRegistryStatusMismatch, registryStatusScopeUris } from "./registry-status.js";
|
|
6
6
|
import { validateProjectDefNarratives } from "./schema.js";
|
|
7
|
-
function
|
|
7
|
+
function isJsonObject(value) {
|
|
8
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9
|
+
}
|
|
10
|
+
function readJsonObject(path) {
|
|
11
|
+
let parsed;
|
|
12
|
+
try {
|
|
13
|
+
parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
return isJsonObject(parsed) ? parsed : null;
|
|
19
|
+
}
|
|
20
|
+
function validateProjectRegistryScopeStatus(item, itemIndex, filepath, vbriefDir) {
|
|
8
21
|
const errors = [];
|
|
9
22
|
const itemStatus = item.status;
|
|
10
23
|
if (typeof itemStatus !== "string") {
|
|
11
24
|
return errors;
|
|
12
25
|
}
|
|
13
26
|
const resolvedRoot = resolve(vbriefDir);
|
|
14
|
-
for (const uri of registryStatusScopeUris(item
|
|
27
|
+
for (const uri of registryStatusScopeUris(item)) {
|
|
15
28
|
if (uri.startsWith("http://") || uri.startsWith("https://") || uri.startsWith("#")) {
|
|
16
29
|
continue;
|
|
17
30
|
}
|
|
@@ -22,15 +35,12 @@ function validateProjectRegistryScopeStatus(item, itemIndex, plan, filepath, vbr
|
|
|
22
35
|
if (!isRelativeTo(scopePath, resolvedRoot) || !existsSync(scopePath)) {
|
|
23
36
|
continue;
|
|
24
37
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
scopeData = JSON.parse(readFileSync(scopePath, "utf8"));
|
|
28
|
-
}
|
|
29
|
-
catch {
|
|
38
|
+
const parsedScope = readJsonObject(scopePath);
|
|
39
|
+
if (parsedScope === null) {
|
|
30
40
|
continue;
|
|
31
41
|
}
|
|
32
|
-
const scopePlan =
|
|
33
|
-
if (
|
|
42
|
+
const scopePlan = parsedScope.plan;
|
|
43
|
+
if (!isJsonObject(scopePlan)) {
|
|
34
44
|
continue;
|
|
35
45
|
}
|
|
36
46
|
const scopeStatus = scopePlan.status;
|
|
@@ -45,7 +55,7 @@ export function validateProjectDefinition(filepath, data, vbriefDir) {
|
|
|
45
55
|
const errors = [];
|
|
46
56
|
const resolvedRoot = resolve(vbriefDir);
|
|
47
57
|
const plan = data.plan;
|
|
48
|
-
if (
|
|
58
|
+
if (!isJsonObject(plan)) {
|
|
49
59
|
return errors;
|
|
50
60
|
}
|
|
51
61
|
const planObj = plan;
|
|
@@ -55,19 +65,18 @@ export function validateProjectDefinition(filepath, data, vbriefDir) {
|
|
|
55
65
|
if (Array.isArray(items)) {
|
|
56
66
|
for (let i = 0; i < items.length; i += 1) {
|
|
57
67
|
const item = items[i];
|
|
58
|
-
if (
|
|
68
|
+
if (!isJsonObject(item)) {
|
|
59
69
|
continue;
|
|
60
70
|
}
|
|
61
71
|
const itemObj = item;
|
|
62
|
-
errors.push(...validateProjectRegistryScopeStatus(itemObj, i,
|
|
72
|
+
errors.push(...validateProjectRegistryScopeStatus(itemObj, i, filepath, vbriefDir));
|
|
63
73
|
const rawRefs = itemObj.references;
|
|
64
74
|
const refs = Array.isArray(rawRefs) ? rawRefs : [];
|
|
65
75
|
for (const ref of refs) {
|
|
66
|
-
if (
|
|
76
|
+
if (!isJsonObject(ref)) {
|
|
67
77
|
continue;
|
|
68
78
|
}
|
|
69
|
-
const
|
|
70
|
-
const uriRaw = refObj.uri;
|
|
79
|
+
const uriRaw = ref.uri;
|
|
71
80
|
const uri = typeof uriRaw === "string" ? uriRaw : "";
|
|
72
81
|
if (uri && uri.startsWith("file://")) {
|
|
73
82
|
const refPath = uri.replace("file://", "");
|
|
@@ -12,9 +12,9 @@ export declare function deriveRegistryItemStatus(planStatus: unknown, lifecycleF
|
|
|
12
12
|
*/
|
|
13
13
|
export declare function registryMetadataReferencesFromScope(references: unknown): unknown[];
|
|
14
14
|
/**
|
|
15
|
-
* Local
|
|
16
|
-
* status (D3).
|
|
15
|
+
* Local source URI whose ``plan.status`` must agree with the registry item
|
|
16
|
+
* status (D3). Child plan links can move through the lifecycle independently.
|
|
17
17
|
*/
|
|
18
|
-
export declare function registryStatusScopeUris(item: JsonObject
|
|
18
|
+
export declare function registryStatusScopeUris(item: JsonObject): string[];
|
|
19
19
|
export declare function formatRegistryStatusMismatch(filepath: string, itemIndex: number, itemStatus: string, uri: string, scopeStatus: string): string;
|
|
20
20
|
//# sourceMappingURL=registry-status.d.ts.map
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { scopeIdsForRefUri } from "./paths.js";
|
|
2
1
|
export const REGISTRY_SCOPE_LINK_TYPE = "x-vbrief/plan";
|
|
3
2
|
/** Derive the registry item status from a scope vBRIEF (render side). */
|
|
4
3
|
export function deriveRegistryItemStatus(planStatus, lifecycleFolder) {
|
|
@@ -27,20 +26,11 @@ export function registryMetadataReferencesFromScope(references) {
|
|
|
27
26
|
}
|
|
28
27
|
return references.filter((ref) => !isScopeLinkRef(ref));
|
|
29
28
|
}
|
|
30
|
-
function pushPlanScopeLink(uris, ref) {
|
|
31
|
-
if (!isScopeLinkRef(ref)) {
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
const uri = ref.uri;
|
|
35
|
-
if (typeof uri === "string" && uri.length > 0) {
|
|
36
|
-
uris.push(uri);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
29
|
/**
|
|
40
|
-
* Local
|
|
41
|
-
* status (D3).
|
|
30
|
+
* Local source URI whose ``plan.status`` must agree with the registry item
|
|
31
|
+
* status (D3). Child plan links can move through the lifecycle independently.
|
|
42
32
|
*/
|
|
43
|
-
export function registryStatusScopeUris(item
|
|
33
|
+
export function registryStatusScopeUris(item) {
|
|
44
34
|
const uris = [];
|
|
45
35
|
const metadata = item.metadata;
|
|
46
36
|
if (typeof metadata === "object" && metadata !== null && !Array.isArray(metadata)) {
|
|
@@ -50,31 +40,6 @@ export function registryStatusScopeUris(item, plan) {
|
|
|
50
40
|
uris.push(sourcePath);
|
|
51
41
|
}
|
|
52
42
|
}
|
|
53
|
-
const itemRefs = item.references;
|
|
54
|
-
if (Array.isArray(itemRefs)) {
|
|
55
|
-
for (const ref of itemRefs) {
|
|
56
|
-
pushPlanScopeLink(uris, ref);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
const itemId = item.id;
|
|
60
|
-
const itemTitle = item.title;
|
|
61
|
-
const planRefs = plan.references;
|
|
62
|
-
if (Array.isArray(planRefs)) {
|
|
63
|
-
for (const ref of planRefs) {
|
|
64
|
-
if (!isScopeLinkRef(ref)) {
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
const uri = ref.uri;
|
|
68
|
-
if (typeof uri !== "string" || uri.length === 0) {
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
const titleMatches = typeof itemTitle === "string" && ref.title === itemTitle;
|
|
72
|
-
const idMatches = typeof itemId === "string" && scopeIdsForRefUri(uri).has(itemId);
|
|
73
|
-
if (titleMatches || idMatches) {
|
|
74
|
-
uris.push(uri);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
43
|
return [...new Set(uris)];
|
|
79
44
|
}
|
|
80
45
|
export function formatRegistryStatusMismatch(filepath, itemIndex, itemStatus, uri, scopeStatus) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deftai/directive-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.59.0",
|
|
4
4
|
"description": "TypeScript engine core for the Directive framework.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -209,8 +209,8 @@
|
|
|
209
209
|
"provenance": true
|
|
210
210
|
},
|
|
211
211
|
"dependencies": {
|
|
212
|
-
"@deftai/directive-content": "^0.
|
|
213
|
-
"@deftai/directive-types": "^0.
|
|
212
|
+
"@deftai/directive-content": "^0.59.0",
|
|
213
|
+
"@deftai/directive-types": "^0.59.0"
|
|
214
214
|
},
|
|
215
215
|
"scripts": {
|
|
216
216
|
"build": "tsc -b",
|