@deftai/directive-core 0.65.0 → 0.66.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/architecture/sor-preflight.js +6 -4
- package/dist/capacity/backfill.js +5 -3
- package/dist/capacity/show.js +5 -3
- package/dist/category-b-namespace/index.d.ts +39 -0
- package/dist/category-b-namespace/index.js +120 -0
- package/dist/codebase/default-extractor.js +2 -1
- package/dist/codebase/map.js +2 -1
- package/dist/codebase/provider.js +4 -2
- package/dist/content-contracts/standards/_helpers.js +1 -1
- package/dist/doctor/checks.js +2 -1
- package/dist/doctor/doctor-state.js +2 -2
- package/dist/encoding/patterns.js +19 -0
- package/dist/encoding/scan.js +3 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/init-deposit/refresh.js +2 -1
- package/dist/init-deposit/scaffold.js +2 -1
- package/dist/install-upgrade/index.d.ts +2 -0
- package/dist/install-upgrade/index.js +31 -2
- package/dist/intake/issue-emit.d.ts +1 -1
- package/dist/intake/issue-emit.js +3 -2
- package/dist/intake/issue-ingest.d.ts +24 -0
- package/dist/intake/issue-ingest.js +113 -16
- package/dist/intake/reconcile-issues.js +11 -4
- package/dist/layout/index.d.ts +2 -0
- package/dist/layout/index.js +2 -0
- package/dist/layout/resolve.d.ts +87 -0
- package/dist/layout/resolve.js +159 -0
- package/dist/lifecycle/lifecycle-hygiene.d.ts +1 -1
- package/dist/lifecycle/lifecycle-hygiene.js +8 -5
- package/dist/orchestration/judgment-policy.js +4 -6
- package/dist/orchestration/verify-judgment-gates.d.ts +1 -0
- package/dist/orchestration/verify-judgment-gates.js +4 -2
- package/dist/policy/autonomy.js +4 -6
- package/dist/policy/capacity.js +4 -6
- package/dist/policy/decisions.d.ts +1 -0
- package/dist/policy/decisions.js +4 -2
- package/dist/policy/index.d.ts +1 -0
- package/dist/policy/index.js +3 -1
- package/dist/policy/plan-extensions.d.ts +35 -0
- package/dist/policy/plan-extensions.js +57 -0
- package/dist/policy/resolve.d.ts +6 -2
- package/dist/policy/resolve.js +18 -8
- package/dist/policy/wip.js +6 -4
- package/dist/preflight-cache/evaluate.js +8 -5
- package/dist/release/native-steps.js +4 -3
- package/dist/render/framework-commands.js +2 -1
- package/dist/render/prd-render.js +2 -2
- package/dist/render/project-render.js +23 -5
- package/dist/render/roadmap-render.js +36 -8
- package/dist/render/scope-outlook.js +3 -2
- package/dist/scope/audit-log.js +3 -2
- package/dist/scope/capacity-stamp.js +2 -1
- package/dist/scope/decompose.js +6 -4
- package/dist/scope/decomposed-refs.js +2 -1
- package/dist/scope/demote.js +5 -4
- package/dist/scope/project-context.js +2 -1
- package/dist/scope/project-definition-sync.js +4 -3
- package/dist/scope/transition.js +6 -2
- package/dist/scope/undo.js +2 -1
- package/dist/scope/vbrief-ref.js +5 -3
- package/dist/session/resume-conditions.js +2 -1
- package/dist/session/ritual-sentinel.js +2 -1
- package/dist/session/staleness.js +2 -1
- package/dist/slice/record.js +4 -4
- package/dist/spec-authority/resolver.js +5 -5
- package/dist/swarm/complete-cohort.js +5 -4
- package/dist/swarm/launch.js +6 -7
- package/dist/swarm/readiness.js +22 -20
- package/dist/swarm/subagent-backend.js +2 -5
- package/dist/swarm/verify-review-clean.js +2 -1
- package/dist/triage/actions/candidates-log.d.ts +3 -0
- package/dist/triage/actions/candidates-log.js +8 -2
- package/dist/triage/bootstrap/gitignore.js +22 -13
- package/dist/triage/bootstrap/index.js +7 -4
- package/dist/triage/bulk/index.js +2 -1
- package/dist/triage/classify/index.js +13 -11
- package/dist/triage/queue/audit.js +3 -3
- package/dist/triage/queue/cache.js +3 -2
- package/dist/triage/queue/ranking-labels.js +2 -1
- package/dist/triage/queue/scope-walk.js +8 -6
- package/dist/triage/reconcile/audit.js +2 -1
- package/dist/triage/reconcile/parse-uri.js +2 -1
- package/dist/triage/reconcile/reconcile.js +8 -7
- package/dist/triage/refresh/extract.js +4 -2
- package/dist/triage/refresh/refresh.js +3 -2
- package/dist/triage/scope/mutations-core.js +13 -9
- package/dist/triage/scope/renderers.js +5 -3
- package/dist/triage/scope/resolve.js +6 -5
- package/dist/triage/scope/validate.js +3 -6
- package/dist/triage/scope-drift/add-ignore.js +4 -2
- package/dist/triage/scope-drift/scope-rules.js +3 -2
- package/dist/triage/subscribe/index.js +13 -7
- package/dist/triage/summary/index.js +7 -5
- package/dist/triage/summary/reconcilable.js +6 -4
- package/dist/triage/summary/scope-drift.js +2 -1
- package/dist/triage/welcome/prior-state.js +9 -9
- package/dist/triage/welcome/summary.js +9 -8
- package/dist/triage/welcome/writers.js +12 -10
- package/dist/validate-content/capacity-policy.js +5 -4
- package/dist/validate-content/capacity-show.js +5 -3
- package/dist/validate-content/filename.d.ts +1 -1
- package/dist/validate-content/filename.js +5 -4
- package/dist/validate-content/validate-strategy-output.js +14 -5
- package/dist/vbrief-build/build.js +1 -1
- package/dist/vbrief-build/constants.js +7 -0
- package/dist/vbrief-build/speckit.js +3 -2
- package/dist/vbrief-reconcile/graph.js +3 -2
- package/dist/vbrief-reconcile/labels.js +4 -3
- package/dist/vbrief-reconcile/reconciliation.js +5 -1
- package/dist/vbrief-reconcile/swarm-deps.js +6 -5
- package/dist/vbrief-reconcile/umbrellas.d.ts +1 -1
- package/dist/vbrief-reconcile/umbrellas.js +9 -7
- package/dist/vbrief-validate/conformance.d.ts +1 -2
- package/dist/vbrief-validate/conformance.js +29 -14
- package/dist/vbrief-validate/constants.d.ts +18 -1
- package/dist/vbrief-validate/constants.js +32 -3
- package/dist/vbrief-validate/epic-links.js +3 -2
- package/dist/vbrief-validate/filename.js +5 -3
- package/dist/vbrief-validate/index.d.ts +3 -1
- package/dist/vbrief-validate/index.js +2 -1
- package/dist/vbrief-validate/main.js +17 -6
- package/dist/vbrief-validate/origin.js +1 -1
- package/dist/vbrief-validate/paths.js +2 -1
- package/dist/vbrief-validate/plan-hooks.js +4 -3
- package/dist/vbrief-validate/registry-status.d.ts +1 -0
- package/dist/vbrief-validate/registry-status.js +3 -1
- package/dist/vbrief-validate/roundtrip.d.ts +36 -0
- package/dist/vbrief-validate/roundtrip.js +168 -0
- package/dist/vbrief-validate/schema.d.ts +1 -1
- package/dist/vbrief-validate/schema.js +62 -19
- package/dist/vbrief-validate/validate-all.js +12 -4
- package/dist/verify-source/code-structure-validate.js +8 -4
- package/dist/verify-source/contract-drift.d.ts +7 -3
- package/dist/verify-source/contract-drift.js +40 -22
- package/dist/xbrief-migrate/constants.d.ts +13 -0
- package/dist/xbrief-migrate/constants.js +14 -0
- package/dist/xbrief-migrate/detect.d.ts +11 -0
- package/dist/xbrief-migrate/detect.js +81 -0
- package/dist/xbrief-migrate/drift-gate.d.ts +56 -0
- package/dist/xbrief-migrate/drift-gate.js +205 -0
- package/dist/xbrief-migrate/index.d.ts +7 -0
- package/dist/xbrief-migrate/index.js +7 -0
- package/dist/xbrief-migrate/migrate-project.d.ts +33 -0
- package/dist/xbrief-migrate/migrate-project.js +189 -0
- package/dist/xbrief-migrate/signpost.d.ts +5 -0
- package/dist/xbrief-migrate/signpost.js +19 -0
- package/dist/xbrief-migrate/transforms.d.ts +46 -0
- package/dist/xbrief-migrate/transforms.js +182 -0
- package/dist/xbrief-migrate/version.d.ts +8 -0
- package/dist/xbrief-migrate/version.js +25 -0
- package/package.json +11 -3
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
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
|
+
function isDirectory(path) {
|
|
5
|
+
try {
|
|
6
|
+
return statSync(path).isDirectory();
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function safeReadText(path) {
|
|
13
|
+
try {
|
|
14
|
+
return readFileSync(path, "utf8");
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return "";
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function walkJsonFiles(root, acc = []) {
|
|
21
|
+
if (!isDirectory(root)) {
|
|
22
|
+
return acc;
|
|
23
|
+
}
|
|
24
|
+
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
25
|
+
const full = join(root, entry.name);
|
|
26
|
+
if (entry.isDirectory()) {
|
|
27
|
+
walkJsonFiles(full, acc);
|
|
28
|
+
}
|
|
29
|
+
else if (entry.isFile() && entry.name.endsWith(".json")) {
|
|
30
|
+
acc.push(full);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return acc;
|
|
34
|
+
}
|
|
35
|
+
function scanFileContent(path, content, reasons) {
|
|
36
|
+
if (path.endsWith(LEGACY_ARTIFACT_SUFFIX)) {
|
|
37
|
+
reasons.add(`legacy artifact filename: ${path}`);
|
|
38
|
+
}
|
|
39
|
+
if (content.includes(`"${LEGACY_INFO_ROOT_KEY}"`)) {
|
|
40
|
+
reasons.add(`legacy info root key in ${path}`);
|
|
41
|
+
}
|
|
42
|
+
if (content.includes(`"version": "${LEGACY_VBRIEF_VERSION}"`)) {
|
|
43
|
+
reasons.add(`declared version ${LEGACY_VBRIEF_VERSION} in ${path}`);
|
|
44
|
+
}
|
|
45
|
+
if (content.includes(VBRIEF_REFERENCE_PREFIX)) {
|
|
46
|
+
reasons.add(`legacy reference prefix ${VBRIEF_REFERENCE_PREFIX} in ${path}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Detect whether `projectRoot` still uses the legacy vbrief on-disk layout.
|
|
51
|
+
* Mirrors the structured-reasons pattern from `detectPreCutover` (#793).
|
|
52
|
+
*/
|
|
53
|
+
export function detectLegacyVbriefLayout(projectRoot) {
|
|
54
|
+
const reasons = new Set();
|
|
55
|
+
const legacyRoot = join(projectRoot, LEGACY_ARTIFACT_DIR);
|
|
56
|
+
const migratedRoot = join(projectRoot, MIGRATED_ARTIFACT_DIR);
|
|
57
|
+
if (isDirectory(legacyRoot)) {
|
|
58
|
+
reasons.add(`${LEGACY_ARTIFACT_DIR}/ directory present`);
|
|
59
|
+
for (const jsonPath of walkJsonFiles(legacyRoot)) {
|
|
60
|
+
scanFileContent(jsonPath, safeReadText(jsonPath), reasons);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (isDirectory(migratedRoot)) {
|
|
64
|
+
for (const jsonPath of walkJsonFiles(migratedRoot)) {
|
|
65
|
+
scanFileContent(jsonPath, safeReadText(jsonPath), reasons);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Root-level legacy artifacts (unusual but covered by acceptance probes).
|
|
69
|
+
if (existsSync(projectRoot) && isDirectory(projectRoot)) {
|
|
70
|
+
for (const entry of readdirSync(projectRoot, { withFileTypes: true })) {
|
|
71
|
+
if (!entry.isFile() || !entry.name.endsWith(LEGACY_ARTIFACT_SUFFIX)) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const full = join(projectRoot, entry.name);
|
|
75
|
+
scanFileContent(full, safeReadText(full), reasons);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const reasonList = [...reasons];
|
|
79
|
+
return { legacyLayout: reasonList.length > 0, reasons: reasonList };
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=detect.js.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #2109 Part 2 data-plane drift gate.
|
|
3
|
+
*
|
|
4
|
+
* After the canonical lifecycle tree is renamed `vbrief/` -> `xbrief/` and every
|
|
5
|
+
* `*.vbrief.json` artifact -> `*.xbrief.json`, this gate FAILS when a NEW
|
|
6
|
+
* legacy-layout token is reintroduced into the DATA PLANE:
|
|
7
|
+
*
|
|
8
|
+
* 1. a tracked `*.vbrief.json` artifact path (legacy suffix), or
|
|
9
|
+
* 2. a tracked file under a top-level `vbrief/` lifecycle directory, or
|
|
10
|
+
* 3. a bare `x-vbrief/` reference type inside a canonical corpus artifact
|
|
11
|
+
* (`xbrief/ ** / *.xbrief.json`).
|
|
12
|
+
*
|
|
13
|
+
* It is a DATA-PLANE gate by construction: it only inspects artifact PATHS and
|
|
14
|
+
* the JSON content of canonical `*.xbrief.json` corpus files. The sanctioned
|
|
15
|
+
* back-compat SHIMS therefore never trip it:
|
|
16
|
+
*
|
|
17
|
+
* - the Part 1 layout-resolver fallback (packages/core/src/layout/resolve.ts),
|
|
18
|
+
* - the EXTENSION_PREFIXES legacy `x-vbrief/` entry
|
|
19
|
+
* (packages/core/src/vbrief-validate/conformance.ts),
|
|
20
|
+
* - the #2110 migrate path (packages/core/src/xbrief-migrate/),
|
|
21
|
+
* - the #1650 legacy policy fallback,
|
|
22
|
+
*
|
|
23
|
+
* are TypeScript source files -- not `*.vbrief.json` artifacts, not under a
|
|
24
|
+
* top-level `vbrief/` dir, and not `xbrief/ ** / *.xbrief.json` corpus files --
|
|
25
|
+
* so they are outside the scanned candidate set and keep their intentional
|
|
26
|
+
* "vbrief" mentions. The DATA-PLANE fixtures that legitimately retain the legacy
|
|
27
|
+
* layout for back-compat regression coverage (test fixtures, the shipped
|
|
28
|
+
* `content/vbrief/` surface, forensic doc templates, archived history, and the
|
|
29
|
+
* framework's own `xbrief/migration/` RESULT artifacts) are explicitly
|
|
30
|
+
* allowlisted below.
|
|
31
|
+
*/
|
|
32
|
+
/** Bare legacy reference-namespace prefix (kept ACCEPTED by EXTENSION_PREFIXES; forbidden in NEW corpus artifacts). */
|
|
33
|
+
export declare const LEGACY_REFERENCE_PREFIX: "x-vbrief/";
|
|
34
|
+
/** Default allowlist: sanctioned data-plane trees that legitimately retain legacy tokens for back-compat. */
|
|
35
|
+
export declare const BUILTIN_ALLOW_LIST: readonly string[];
|
|
36
|
+
export type DriftScanMode = "all" | "staged";
|
|
37
|
+
export interface DriftFinding {
|
|
38
|
+
readonly path: string;
|
|
39
|
+
readonly kind: "legacy-suffix" | "legacy-lifecycle-dir" | "legacy-reference-token";
|
|
40
|
+
readonly detail: string;
|
|
41
|
+
}
|
|
42
|
+
export interface DriftEvaluateOptions {
|
|
43
|
+
readonly mode?: DriftScanMode;
|
|
44
|
+
readonly allowListPath?: string | null;
|
|
45
|
+
readonly quiet?: boolean;
|
|
46
|
+
}
|
|
47
|
+
export interface DriftEvaluateResult {
|
|
48
|
+
readonly code: 0 | 1 | 2;
|
|
49
|
+
readonly findings: readonly DriftFinding[];
|
|
50
|
+
readonly message: string;
|
|
51
|
+
readonly stream: "stdout" | "stderr";
|
|
52
|
+
}
|
|
53
|
+
/** Scan a canonical corpus artifact's content for the bare legacy reference token. */
|
|
54
|
+
export declare function scanCorpusToken(fullPath: string): boolean;
|
|
55
|
+
export declare function evaluateXbriefDrift(projectRoot: string, options?: DriftEvaluateOptions): DriftEvaluateResult;
|
|
56
|
+
//# sourceMappingURL=drift-gate.d.ts.map
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { GitCommandError, GitNotFoundError, gitStagedFiles, gitTrackedFiles, } from "../encoding/git.js";
|
|
4
|
+
import { fnmatchCase } from "../encoding/text.js";
|
|
5
|
+
import { LEGACY_ARTIFACT_DIR, LEGACY_ARTIFACT_SUFFIX, MIGRATED_ARTIFACT_DIR, MIGRATED_ARTIFACT_SUFFIX, } from "./constants.js";
|
|
6
|
+
/**
|
|
7
|
+
* #2109 Part 2 data-plane drift gate.
|
|
8
|
+
*
|
|
9
|
+
* After the canonical lifecycle tree is renamed `vbrief/` -> `xbrief/` and every
|
|
10
|
+
* `*.vbrief.json` artifact -> `*.xbrief.json`, this gate FAILS when a NEW
|
|
11
|
+
* legacy-layout token is reintroduced into the DATA PLANE:
|
|
12
|
+
*
|
|
13
|
+
* 1. a tracked `*.vbrief.json` artifact path (legacy suffix), or
|
|
14
|
+
* 2. a tracked file under a top-level `vbrief/` lifecycle directory, or
|
|
15
|
+
* 3. a bare `x-vbrief/` reference type inside a canonical corpus artifact
|
|
16
|
+
* (`xbrief/ ** / *.xbrief.json`).
|
|
17
|
+
*
|
|
18
|
+
* It is a DATA-PLANE gate by construction: it only inspects artifact PATHS and
|
|
19
|
+
* the JSON content of canonical `*.xbrief.json` corpus files. The sanctioned
|
|
20
|
+
* back-compat SHIMS therefore never trip it:
|
|
21
|
+
*
|
|
22
|
+
* - the Part 1 layout-resolver fallback (packages/core/src/layout/resolve.ts),
|
|
23
|
+
* - the EXTENSION_PREFIXES legacy `x-vbrief/` entry
|
|
24
|
+
* (packages/core/src/vbrief-validate/conformance.ts),
|
|
25
|
+
* - the #2110 migrate path (packages/core/src/xbrief-migrate/),
|
|
26
|
+
* - the #1650 legacy policy fallback,
|
|
27
|
+
*
|
|
28
|
+
* are TypeScript source files -- not `*.vbrief.json` artifacts, not under a
|
|
29
|
+
* top-level `vbrief/` dir, and not `xbrief/ ** / *.xbrief.json` corpus files --
|
|
30
|
+
* so they are outside the scanned candidate set and keep their intentional
|
|
31
|
+
* "vbrief" mentions. The DATA-PLANE fixtures that legitimately retain the legacy
|
|
32
|
+
* layout for back-compat regression coverage (test fixtures, the shipped
|
|
33
|
+
* `content/vbrief/` surface, forensic doc templates, archived history, and the
|
|
34
|
+
* framework's own `xbrief/migration/` RESULT artifacts) are explicitly
|
|
35
|
+
* allowlisted below.
|
|
36
|
+
*/
|
|
37
|
+
/** Bare legacy reference-namespace prefix (kept ACCEPTED by EXTENSION_PREFIXES; forbidden in NEW corpus artifacts). */
|
|
38
|
+
export const LEGACY_REFERENCE_PREFIX = "x-vbrief/";
|
|
39
|
+
/** Default allowlist: sanctioned data-plane trees that legitimately retain legacy tokens for back-compat. */
|
|
40
|
+
export const BUILTIN_ALLOW_LIST = [
|
|
41
|
+
// Shipped vBRIEF surface (#1875 C3) + conformance round-trip fixtures (#715).
|
|
42
|
+
"content/vbrief/**",
|
|
43
|
+
// Test fixtures exercising the legacy read path + pre-cutover migration (#2108 / #2110)
|
|
44
|
+
// and this gate's own regression fixtures.
|
|
45
|
+
"tests/**",
|
|
46
|
+
// Forensic-research doc templates ship a `.vbrief.json` example.
|
|
47
|
+
"docs/**",
|
|
48
|
+
// Archived forensic / onboarding records.
|
|
49
|
+
"history/**",
|
|
50
|
+
// The framework's own migration RESULT artifacts may reference the legacy layout by design.
|
|
51
|
+
`${MIGRATED_ARTIFACT_DIR}/migration/**`,
|
|
52
|
+
];
|
|
53
|
+
function loadAllowList(path) {
|
|
54
|
+
if (path === null) {
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
if (!existsSync(path)) {
|
|
58
|
+
return {
|
|
59
|
+
error: `verify_xbrief_drift: --allow-list file not found: [Errno 2] No such file or directory: '${path}'\n` +
|
|
60
|
+
" Recovery: pass an existing path or omit the flag.",
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
const raw = readFileSync(path, { encoding: "utf8" });
|
|
65
|
+
const out = [];
|
|
66
|
+
for (const line of raw.split("\n")) {
|
|
67
|
+
const stripped = line.trim();
|
|
68
|
+
if (!stripped || stripped.startsWith("#")) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
out.push(stripped);
|
|
72
|
+
}
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
77
|
+
return {
|
|
78
|
+
error: `verify_xbrief_drift: --allow-list unreadable: ${msg}\n` +
|
|
79
|
+
" Recovery: check file permissions.",
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function isAllowListed(relPath, patterns) {
|
|
84
|
+
return patterns.some((pat) => fnmatchCase(relPath, pat));
|
|
85
|
+
}
|
|
86
|
+
function listFiles(projectRoot, mode) {
|
|
87
|
+
try {
|
|
88
|
+
const files = mode === "staged" ? gitStagedFiles(projectRoot) : gitTrackedFiles(projectRoot);
|
|
89
|
+
return files.map((p) => p.replace(/\\/g, "/"));
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
if (err instanceof GitNotFoundError) {
|
|
93
|
+
return {
|
|
94
|
+
error: "verify_xbrief_drift: 'git' executable not found on PATH.\n" +
|
|
95
|
+
" Recovery: install git or run inside a git working tree.",
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
if (err instanceof GitCommandError) {
|
|
99
|
+
return {
|
|
100
|
+
error: `verify_xbrief_drift: git failed: ${err.message}\n` +
|
|
101
|
+
" Recovery: run inside a git working tree.",
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
105
|
+
return { error: `verify_xbrief_drift: ${msg}` };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/** Scan a canonical corpus artifact's content for the bare legacy reference token. */
|
|
109
|
+
export function scanCorpusToken(fullPath) {
|
|
110
|
+
let source;
|
|
111
|
+
try {
|
|
112
|
+
source = readFileSync(fullPath, { encoding: "utf8" });
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
// Reference types appear as JSON string values, e.g. `"type": "x-vbrief/..."`.
|
|
118
|
+
// The bare prefix as a quoted-string boundary is the structural token we forbid.
|
|
119
|
+
return source.includes(`"${LEGACY_REFERENCE_PREFIX}`) || source.includes(LEGACY_REFERENCE_PREFIX);
|
|
120
|
+
}
|
|
121
|
+
export function evaluateXbriefDrift(projectRoot, options = {}) {
|
|
122
|
+
const root = resolve(projectRoot);
|
|
123
|
+
let isDir = false;
|
|
124
|
+
try {
|
|
125
|
+
isDir = statSync(root).isDirectory();
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
isDir = false;
|
|
129
|
+
}
|
|
130
|
+
if (!isDir) {
|
|
131
|
+
return {
|
|
132
|
+
code: 2,
|
|
133
|
+
findings: [],
|
|
134
|
+
message: `verify_xbrief_drift: --project-root is not a directory: ${root}\n` +
|
|
135
|
+
" Recovery: pass an existing directory path.",
|
|
136
|
+
stream: "stderr",
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
const allowLoaded = loadAllowList(options.allowListPath ?? null);
|
|
140
|
+
if (!Array.isArray(allowLoaded)) {
|
|
141
|
+
return { code: 2, findings: [], message: allowLoaded.error, stream: "stderr" };
|
|
142
|
+
}
|
|
143
|
+
const allowGlobs = [...BUILTIN_ALLOW_LIST, ...allowLoaded];
|
|
144
|
+
const mode = options.mode ?? "all";
|
|
145
|
+
const listed = listFiles(root, mode);
|
|
146
|
+
if (!Array.isArray(listed)) {
|
|
147
|
+
return { code: 2, findings: [], message: listed.error, stream: "stderr" };
|
|
148
|
+
}
|
|
149
|
+
const legacyDirPrefix = `${LEGACY_ARTIFACT_DIR}/`;
|
|
150
|
+
const corpusPrefix = `${MIGRATED_ARTIFACT_DIR}/`;
|
|
151
|
+
const findings = [];
|
|
152
|
+
for (const rel of listed) {
|
|
153
|
+
if (isAllowListed(rel, allowGlobs)) {
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (rel === LEGACY_ARTIFACT_DIR || rel.startsWith(legacyDirPrefix)) {
|
|
157
|
+
findings.push({
|
|
158
|
+
path: rel,
|
|
159
|
+
kind: "legacy-lifecycle-dir",
|
|
160
|
+
detail: `tracked under the legacy top-level \`${legacyDirPrefix}\` lifecycle directory (canonical: \`${corpusPrefix}\`)`,
|
|
161
|
+
});
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (rel.endsWith(LEGACY_ARTIFACT_SUFFIX)) {
|
|
165
|
+
findings.push({
|
|
166
|
+
path: rel,
|
|
167
|
+
kind: "legacy-suffix",
|
|
168
|
+
detail: `legacy artifact suffix \`${LEGACY_ARTIFACT_SUFFIX}\` (canonical: \`${MIGRATED_ARTIFACT_SUFFIX}\`)`,
|
|
169
|
+
});
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
if (rel.startsWith(corpusPrefix) && rel.endsWith(MIGRATED_ARTIFACT_SUFFIX)) {
|
|
173
|
+
if (scanCorpusToken(join(root, rel))) {
|
|
174
|
+
findings.push({
|
|
175
|
+
path: rel,
|
|
176
|
+
kind: "legacy-reference-token",
|
|
177
|
+
detail: `bare legacy reference token \`${LEGACY_REFERENCE_PREFIX}\` in a canonical corpus artifact (canonical: \`x-${MIGRATED_ARTIFACT_DIR}/\`)`,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (findings.length > 0) {
|
|
183
|
+
const header = `verify_xbrief_drift: detected ${findings.length} legacy-layout token(s) reintroduced into the data plane (#2109).\n` +
|
|
184
|
+
" Root cause: the canonical lifecycle layout is `xbrief/ ** / *.xbrief.json` with `x-xbrief/` reference types.\n" +
|
|
185
|
+
" Fix: rename the artifact to `.xbrief.json`, move it under `xbrief/`, and use the `x-xbrief/` reference prefix.\n" +
|
|
186
|
+
" Sanctioned back-compat fixtures are allowlisted; add a documented exception via `--allow-list <path>`\n" +
|
|
187
|
+
" (file with newline-separated glob patterns).";
|
|
188
|
+
const body = findings
|
|
189
|
+
.slice(0, 50)
|
|
190
|
+
.map((f) => ` ${f.path} [${f.kind}] ${f.detail}`)
|
|
191
|
+
.join("\n");
|
|
192
|
+
const more = findings.length > 50 ? `\n ... and ${findings.length - 50} more` : "";
|
|
193
|
+
return { code: 1, findings, message: `${header}\n${body}${more}`, stream: "stderr" };
|
|
194
|
+
}
|
|
195
|
+
if (options.quiet === true) {
|
|
196
|
+
return { code: 0, findings: [], message: "", stream: "stdout" };
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
code: 0,
|
|
200
|
+
findings: [],
|
|
201
|
+
message: `verify_xbrief_drift: no legacy-layout drift -- \`${corpusPrefix}\` is canonical (#2109).`,
|
|
202
|
+
stream: "stdout",
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
//# sourceMappingURL=drift-gate.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { detectLegacyVbriefLayout, type LegacyVbriefLayoutDetection, } from "./detect.js";
|
|
2
|
+
export { BUILTIN_ALLOW_LIST, type DriftEvaluateOptions, type DriftEvaluateResult, type DriftFinding, type DriftScanMode, evaluateXbriefDrift, LEGACY_REFERENCE_PREFIX, scanCorpusToken, } from "./drift-gate.js";
|
|
3
|
+
export { emitXbriefMigration, runXbriefMigration, runXbriefMigrationCli, type XbriefMigrationArgs, type XbriefMigrationIo, type XbriefMigrationOutcome, } from "./migrate-project.js";
|
|
4
|
+
export { renderXbriefMigrationLine, xbriefMigrationGuidance } from "./signpost.js";
|
|
5
|
+
export { assertFeatureEmissionAllowed, assertLayoutAwareWritePath, FeatureEmissionRejectedError, type JsonObject, type JsonValue, readDeclaredArtifactVersion, resolveLayoutAwareRelativePath, rewriteEmbeddedTokens, SplitLayoutRejectedError, TransformError, transformArtifactV06ToV08, transformArtifactV06ToV08Transactional, } from "./transforms.js";
|
|
6
|
+
export { isPatchOnlyUpgrade, parseSemverPrefix } from "./version.js";
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { detectLegacyVbriefLayout, } from "./detect.js";
|
|
2
|
+
export { BUILTIN_ALLOW_LIST, evaluateXbriefDrift, LEGACY_REFERENCE_PREFIX, scanCorpusToken, } from "./drift-gate.js";
|
|
3
|
+
export { emitXbriefMigration, runXbriefMigration, runXbriefMigrationCli, } from "./migrate-project.js";
|
|
4
|
+
export { renderXbriefMigrationLine, xbriefMigrationGuidance } from "./signpost.js";
|
|
5
|
+
export { assertFeatureEmissionAllowed, assertLayoutAwareWritePath, FeatureEmissionRejectedError, readDeclaredArtifactVersion, resolveLayoutAwareRelativePath, rewriteEmbeddedTokens, SplitLayoutRejectedError, TransformError, transformArtifactV06ToV08, transformArtifactV06ToV08Transactional, } from "./transforms.js";
|
|
6
|
+
export { isPatchOnlyUpgrade, parseSemverPrefix } from "./version.js";
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface XbriefMigrationArgs {
|
|
2
|
+
readonly projectRoot: string;
|
|
3
|
+
readonly frameworkRoot?: string;
|
|
4
|
+
readonly force?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export interface XbriefMigrationIo {
|
|
7
|
+
writeOut: (text: string) => void;
|
|
8
|
+
writeErr: (text: string) => void;
|
|
9
|
+
}
|
|
10
|
+
export type XbriefMigrationOutcome = {
|
|
11
|
+
readonly kind: "noop";
|
|
12
|
+
readonly message: string;
|
|
13
|
+
} | {
|
|
14
|
+
readonly kind: "refused";
|
|
15
|
+
readonly message: string;
|
|
16
|
+
} | {
|
|
17
|
+
readonly kind: "migrated";
|
|
18
|
+
readonly backupDir: string;
|
|
19
|
+
readonly files: number;
|
|
20
|
+
} | {
|
|
21
|
+
readonly kind: "config";
|
|
22
|
+
readonly message: string;
|
|
23
|
+
};
|
|
24
|
+
/** Core orchestrator for the consumer xbrief rename (#2110). */
|
|
25
|
+
export declare function runXbriefMigration(args: XbriefMigrationArgs, _io: XbriefMigrationIo): XbriefMigrationOutcome;
|
|
26
|
+
/** Map a migration outcome to a process exit code and emit operator guidance. */
|
|
27
|
+
export declare function emitXbriefMigration(outcome: XbriefMigrationOutcome, io: XbriefMigrationIo, options?: {
|
|
28
|
+
signpostOnly?: boolean;
|
|
29
|
+
projectRoot?: string;
|
|
30
|
+
}): number;
|
|
31
|
+
/** End-to-end migrate:xbrief handler including optional agents:refresh (#2110). */
|
|
32
|
+
export declare function runXbriefMigrationCli(args: XbriefMigrationArgs, io: XbriefMigrationIo): number;
|
|
33
|
+
//# sourceMappingURL=migrate-project.d.ts.map
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync, } from "node:fs";
|
|
2
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
3
|
+
import { checkGitClean } from "../migrate-preflight/index.js";
|
|
4
|
+
import { agentsRefreshPlan } from "../platform/agents-md.js";
|
|
5
|
+
import { LEGACY_ARTIFACT_DIR, LEGACY_ARTIFACT_SUFFIX, MIGRATED_ARTIFACT_DIR, MIGRATED_ARTIFACT_SUFFIX, } from "./constants.js";
|
|
6
|
+
import { detectLegacyVbriefLayout } from "./detect.js";
|
|
7
|
+
import { renderXbriefMigrationLine, xbriefMigrationGuidance } from "./signpost.js";
|
|
8
|
+
import { rewriteEmbeddedTokens, transformArtifactV06ToV08Transactional } from "./transforms.js";
|
|
9
|
+
function isDirectory(path) {
|
|
10
|
+
try {
|
|
11
|
+
return statSync(path).isDirectory();
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function collectFiles(root, acc = []) {
|
|
18
|
+
if (!isDirectory(root)) {
|
|
19
|
+
return acc;
|
|
20
|
+
}
|
|
21
|
+
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
22
|
+
const full = join(root, entry.name);
|
|
23
|
+
if (entry.isDirectory()) {
|
|
24
|
+
collectFiles(full, acc);
|
|
25
|
+
}
|
|
26
|
+
else if (entry.isFile()) {
|
|
27
|
+
acc.push(full);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return acc;
|
|
31
|
+
}
|
|
32
|
+
function mapRelativePath(relativePath) {
|
|
33
|
+
return relativePath
|
|
34
|
+
.split(/[/\\]/)
|
|
35
|
+
.map((segment) => segment.endsWith(LEGACY_ARTIFACT_SUFFIX)
|
|
36
|
+
? segment.slice(0, -LEGACY_ARTIFACT_SUFFIX.length) + MIGRATED_ARTIFACT_SUFFIX
|
|
37
|
+
: segment)
|
|
38
|
+
.join("/");
|
|
39
|
+
}
|
|
40
|
+
function writeMigratedFile(srcPath, destPath) {
|
|
41
|
+
mkdirSync(dirname(destPath), { recursive: true });
|
|
42
|
+
if (srcPath.endsWith(LEGACY_ARTIFACT_SUFFIX)) {
|
|
43
|
+
const parsed = JSON.parse(readFileSync(srcPath, "utf8"));
|
|
44
|
+
const result = transformArtifactV06ToV08Transactional(parsed);
|
|
45
|
+
if (!result.ok) {
|
|
46
|
+
throw new Error(result.error);
|
|
47
|
+
}
|
|
48
|
+
writeFileSync(destPath, `${JSON.stringify(result.artifact, null, 2)}\n`, "utf8");
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const raw = readFileSync(srcPath, "utf8");
|
|
52
|
+
writeFileSync(destPath, rewriteEmbeddedTokens(raw), "utf8");
|
|
53
|
+
}
|
|
54
|
+
function backupLegacyTree(projectRoot, legacyDir) {
|
|
55
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
56
|
+
const backupRoot = join(projectRoot, ".deft", `xbrief-migrate-backup-${stamp}`);
|
|
57
|
+
mkdirSync(backupRoot, { recursive: true });
|
|
58
|
+
cpSync(legacyDir, join(backupRoot, LEGACY_ARTIFACT_DIR), { recursive: true });
|
|
59
|
+
return backupRoot;
|
|
60
|
+
}
|
|
61
|
+
function migrateLegacyTree(projectRoot, legacyDir) {
|
|
62
|
+
const migratedDir = join(projectRoot, MIGRATED_ARTIFACT_DIR);
|
|
63
|
+
if (existsSync(migratedDir)) {
|
|
64
|
+
throw new Error(`refusing to migrate: '${MIGRATED_ARTIFACT_DIR}/' already exists alongside '${LEGACY_ARTIFACT_DIR}/'`);
|
|
65
|
+
}
|
|
66
|
+
const backupDir = backupLegacyTree(projectRoot, legacyDir);
|
|
67
|
+
const stagedDir = join(projectRoot, `.${MIGRATED_ARTIFACT_DIR}.migrate-staging`);
|
|
68
|
+
if (existsSync(stagedDir)) {
|
|
69
|
+
rmSync(stagedDir, { recursive: true, force: true });
|
|
70
|
+
}
|
|
71
|
+
mkdirSync(stagedDir, { recursive: true });
|
|
72
|
+
const files = collectFiles(legacyDir);
|
|
73
|
+
try {
|
|
74
|
+
for (const srcPath of files) {
|
|
75
|
+
const rel = relative(legacyDir, srcPath);
|
|
76
|
+
const destPath = join(stagedDir, mapRelativePath(rel));
|
|
77
|
+
writeMigratedFile(srcPath, destPath);
|
|
78
|
+
}
|
|
79
|
+
renameOrReplace(stagedDir, migratedDir);
|
|
80
|
+
rmSync(legacyDir, { recursive: true, force: true });
|
|
81
|
+
return { backupDir, files: files.length };
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
rmSync(stagedDir, { recursive: true, force: true });
|
|
85
|
+
throw err;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function renameOrReplace(src, dest) {
|
|
89
|
+
if (existsSync(dest)) {
|
|
90
|
+
rmSync(dest, { recursive: true, force: true });
|
|
91
|
+
}
|
|
92
|
+
cpSync(src, dest, { recursive: true });
|
|
93
|
+
rmSync(src, { recursive: true, force: true });
|
|
94
|
+
}
|
|
95
|
+
function runAgentsRefresh(projectRoot, frameworkRoot, io) {
|
|
96
|
+
const plan = agentsRefreshPlan(projectRoot, { frameworkRoot });
|
|
97
|
+
const state = String(plan.state ?? "unknown");
|
|
98
|
+
if (state === "current") {
|
|
99
|
+
io.writeOut("AGENTS.md managed section is current — no changes.\n");
|
|
100
|
+
return 0;
|
|
101
|
+
}
|
|
102
|
+
if (state === "template-missing" || state === "template-malformed" || state === "unreadable") {
|
|
103
|
+
io.writeErr(`agents:refresh failed: ${state}\n`);
|
|
104
|
+
return 2;
|
|
105
|
+
}
|
|
106
|
+
const newContent = plan.new_content;
|
|
107
|
+
if (typeof newContent !== "string") {
|
|
108
|
+
io.writeErr("agents:refresh failed: plan produced no new_content\n");
|
|
109
|
+
return 2;
|
|
110
|
+
}
|
|
111
|
+
const path = String(plan.path ?? join(projectRoot, "AGENTS.md"));
|
|
112
|
+
writeFileSync(path, newContent, "utf8");
|
|
113
|
+
io.writeOut(`AGENTS.md updated (state=${state}).\n`);
|
|
114
|
+
return 0;
|
|
115
|
+
}
|
|
116
|
+
/** Core orchestrator for the consumer xbrief rename (#2110). */
|
|
117
|
+
export function runXbriefMigration(args, _io) {
|
|
118
|
+
const projectRoot = resolve(args.projectRoot);
|
|
119
|
+
const detection = detectLegacyVbriefLayout(projectRoot);
|
|
120
|
+
if (!detection.legacyLayout) {
|
|
121
|
+
return {
|
|
122
|
+
kind: "noop",
|
|
123
|
+
message: "Project is already on the xbrief layout — nothing to migrate.",
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
const legacyDir = join(projectRoot, LEGACY_ARTIFACT_DIR);
|
|
127
|
+
if (!isDirectory(legacyDir)) {
|
|
128
|
+
return {
|
|
129
|
+
kind: "config",
|
|
130
|
+
message: `Legacy markers detected but '${LEGACY_ARTIFACT_DIR}/' directory is missing.`,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
if (!args.force) {
|
|
134
|
+
const git = checkGitClean(projectRoot);
|
|
135
|
+
if (git.status === "WARN") {
|
|
136
|
+
return {
|
|
137
|
+
kind: "refused",
|
|
138
|
+
message: `${git.message} ${xbriefMigrationGuidance()} Pass --force to override.`,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
const { backupDir, files } = migrateLegacyTree(projectRoot, legacyDir);
|
|
144
|
+
return { kind: "migrated", backupDir, files };
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
return {
|
|
148
|
+
kind: "config",
|
|
149
|
+
message: err instanceof Error ? err.message : String(err),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/** Map a migration outcome to a process exit code and emit operator guidance. */
|
|
154
|
+
export function emitXbriefMigration(outcome, io, options = {}) {
|
|
155
|
+
if (options.signpostOnly) {
|
|
156
|
+
const root = options.projectRoot ?? process.cwd();
|
|
157
|
+
io.writeOut(`${renderXbriefMigrationLine(root)}\n`);
|
|
158
|
+
return 0;
|
|
159
|
+
}
|
|
160
|
+
switch (outcome.kind) {
|
|
161
|
+
case "noop":
|
|
162
|
+
io.writeOut(`${outcome.message}\n`);
|
|
163
|
+
return 0;
|
|
164
|
+
case "refused":
|
|
165
|
+
io.writeErr(`migrate:xbrief refused: ${outcome.message}\n`);
|
|
166
|
+
return 1;
|
|
167
|
+
case "config":
|
|
168
|
+
io.writeErr(`migrate:xbrief: ${outcome.message}\n`);
|
|
169
|
+
return 2;
|
|
170
|
+
case "migrated":
|
|
171
|
+
io.writeOut(`Migrated ${outcome.files} file(s) from ${LEGACY_ARTIFACT_DIR}/ to ${MIGRATED_ARTIFACT_DIR}/.\n` +
|
|
172
|
+
`Backup written to ${outcome.backupDir}.\n`);
|
|
173
|
+
return 0;
|
|
174
|
+
default: {
|
|
175
|
+
const _exhaustive = outcome;
|
|
176
|
+
return _exhaustive;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/** End-to-end migrate:xbrief handler including optional agents:refresh (#2110). */
|
|
181
|
+
export function runXbriefMigrationCli(args, io) {
|
|
182
|
+
const outcome = runXbriefMigration(args, io);
|
|
183
|
+
const code = emitXbriefMigration(outcome, io);
|
|
184
|
+
if (code !== 0 || outcome.kind !== "migrated") {
|
|
185
|
+
return code;
|
|
186
|
+
}
|
|
187
|
+
return runAgentsRefresh(resolve(args.projectRoot), args.frameworkRoot, io);
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=migrate-project.js.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Operator guidance for the TS-native xbrief rename (#2034 / #2110). */
|
|
2
|
+
export declare function xbriefMigrationGuidance(): string;
|
|
3
|
+
/** One-line doctor / ritual signpost mirroring `renderPrecutoverLine` (#2110). */
|
|
4
|
+
export declare function renderXbriefMigrationLine(projectRoot: string): string;
|
|
5
|
+
//# sourceMappingURL=signpost.d.ts.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { detectLegacyVbriefLayout } from "./detect.js";
|
|
2
|
+
/** Operator guidance for the TS-native xbrief rename (#2034 / #2110). */
|
|
3
|
+
export function xbriefMigrationGuidance() {
|
|
4
|
+
return "Run `deft migrate:xbrief` (or `task migrate:xbrief`) to convert vbrief/ to xbrief/ safely.";
|
|
5
|
+
}
|
|
6
|
+
/** One-line doctor / ritual signpost mirroring `renderPrecutoverLine` (#2110). */
|
|
7
|
+
export function renderXbriefMigrationLine(projectRoot) {
|
|
8
|
+
const { legacyLayout, reasons } = detectLegacyVbriefLayout(projectRoot);
|
|
9
|
+
if (!legacyLayout) {
|
|
10
|
+
return "xBrief migration: none -- project is on the xbrief layout.";
|
|
11
|
+
}
|
|
12
|
+
const maxReasons = 3;
|
|
13
|
+
const shown = reasons.slice(0, maxReasons);
|
|
14
|
+
const remainder = reasons.length - shown.length;
|
|
15
|
+
const summary = shown.join("; ").replace(/\r?\n/g, " ");
|
|
16
|
+
const tail = remainder > 0 ? `${summary}; …and ${remainder} more marker(s)` : summary;
|
|
17
|
+
return `xBrief migration: legacy vbrief layout detected -- ${tail}. ${xbriefMigrationGuidance()}`;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=signpost.js.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export type JsonObject = Record<string, unknown>;
|
|
2
|
+
export type JsonValue = string | number | boolean | null | JsonObject | JsonValue[];
|
|
3
|
+
export declare class TransformError extends Error {
|
|
4
|
+
constructor(message: string);
|
|
5
|
+
}
|
|
6
|
+
export declare class FeatureEmissionRejectedError extends Error {
|
|
7
|
+
constructor(message: string);
|
|
8
|
+
}
|
|
9
|
+
export declare class SplitLayoutRejectedError extends Error {
|
|
10
|
+
constructor(message: string);
|
|
11
|
+
}
|
|
12
|
+
/** Return the artifact's declared `*BRIEFInfo.version`, if present. */
|
|
13
|
+
export declare function readDeclaredArtifactVersion(artifact: JsonObject): string | null;
|
|
14
|
+
/** Rewrite embedded path and reference tokens inside a string (idempotent). */
|
|
15
|
+
export declare function rewriteEmbeddedTokens(value: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Convert a single v0.6 in-document artifact to v0.8 semantics.
|
|
18
|
+
* Idempotent: v0.8 artifacts are returned unchanged (deep-cloned).
|
|
19
|
+
* Transactional: on failure the original input object is not mutated.
|
|
20
|
+
*/
|
|
21
|
+
export declare function transformArtifactV06ToV08(input: JsonObject): JsonObject;
|
|
22
|
+
/**
|
|
23
|
+
* Apply `transformArtifactV06ToV08` inside a try/catch boundary.
|
|
24
|
+
* On error, returns the original artifact reference unchanged.
|
|
25
|
+
*/
|
|
26
|
+
export declare function transformArtifactV06ToV08Transactional(input: JsonObject): {
|
|
27
|
+
ok: true;
|
|
28
|
+
artifact: JsonObject;
|
|
29
|
+
changed: boolean;
|
|
30
|
+
} | {
|
|
31
|
+
ok: false;
|
|
32
|
+
artifact: JsonObject;
|
|
33
|
+
error: string;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Locked rule #1 (#2034): refuse v0.8-only constructs unless the artifact declares v0.8.
|
|
37
|
+
* Fail closed when the version block is missing or unreadable.
|
|
38
|
+
*/
|
|
39
|
+
export declare function assertFeatureEmissionAllowed(targetArtifact: JsonObject, emission: JsonObject): void;
|
|
40
|
+
/** Map a desired migrated-relative path to the legacy tree when required. */
|
|
41
|
+
export declare function resolveLayoutAwareRelativePath(legacyLayout: boolean, relativePath: string): string;
|
|
42
|
+
/**
|
|
43
|
+
* Locked rule #2 (#2034): never target `xbrief/` when the project still uses `vbrief/`.
|
|
44
|
+
*/
|
|
45
|
+
export declare function assertLayoutAwareWritePath(projectRoot: string, relativePath: string, legacyLayout: boolean): string;
|
|
46
|
+
//# sourceMappingURL=transforms.d.ts.map
|