@deftai/directive-core 0.65.0 → 0.66.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/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.js +11 -8
- 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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { pyStrRepr, pythonTypeName } from "../triage/scope/python-repr.js";
|
|
2
|
-
import { PROJECT_DEF_EXPECTED_NARRATIVES,
|
|
2
|
+
import { PROJECT_DEF_EXPECTED_NARRATIVES, VALID_INFO_ROOT_KEYS, VALID_ITEM_STATUSES, VALID_PLAN_ITEM_TYPES, VALID_PLAN_STATUSES, VALID_VBRIEF_VERSIONS, } from "./constants.js";
|
|
3
3
|
function validateNarratives(narratives, path, errors) {
|
|
4
4
|
if (typeof narratives !== "object" || narratives === null || Array.isArray(narratives)) {
|
|
5
5
|
errors.push(`${path} must be an object`);
|
|
@@ -11,6 +11,30 @@ function validateNarratives(narratives, path, errors) {
|
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
|
+
function validatePlanRefs(planRefs, path, errors) {
|
|
15
|
+
if (!Array.isArray(planRefs)) {
|
|
16
|
+
errors.push(`${path}.planRefs must be an array`);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
for (let i = 0; i < planRefs.length; i += 1) {
|
|
20
|
+
if (typeof planRefs[i] !== "string") {
|
|
21
|
+
errors.push(`${path}.planRefs[${i}] must be a string, got ${pythonTypeName(planRefs[i])}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function resolveInfoBlock(data) {
|
|
26
|
+
for (const key of VALID_INFO_ROOT_KEYS) {
|
|
27
|
+
if (!(key in data)) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
const info = data[key];
|
|
31
|
+
if (typeof info !== "object" || info === null || Array.isArray(info)) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
return { key: key, info: info };
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
14
38
|
function validatePlanItem(item, path, errors) {
|
|
15
39
|
const itemId = typeof item.id === "string" ? item.id : "<no-id>";
|
|
16
40
|
const itemPath = `${path}[${itemId}]`;
|
|
@@ -20,9 +44,18 @@ function validatePlanItem(item, path, errors) {
|
|
|
20
44
|
if (!("status" in item)) {
|
|
21
45
|
errors.push(`${itemPath} missing 'status'`);
|
|
22
46
|
}
|
|
23
|
-
else if (!
|
|
47
|
+
else if (!VALID_ITEM_STATUSES.has(String(item.status))) {
|
|
24
48
|
errors.push(`${itemPath} invalid status: ${pyStrRepr(String(item.status))}`);
|
|
25
49
|
}
|
|
50
|
+
if ("type" in item && !VALID_PLAN_ITEM_TYPES.has(String(item.type))) {
|
|
51
|
+
errors.push(`${itemPath} invalid type: ${pyStrRepr(String(item.type))}`);
|
|
52
|
+
}
|
|
53
|
+
if ("summary" in item && typeof item.summary !== "string") {
|
|
54
|
+
errors.push(`${itemPath}.summary must be a string, got ${pythonTypeName(item.summary)}`);
|
|
55
|
+
}
|
|
56
|
+
if ("planRefs" in item) {
|
|
57
|
+
validatePlanRefs(item.planRefs, itemPath, errors);
|
|
58
|
+
}
|
|
26
59
|
if ("narrative" in item) {
|
|
27
60
|
validateNarratives(item.narrative, `${itemPath}.narrative`, errors);
|
|
28
61
|
}
|
|
@@ -57,25 +90,35 @@ function validatePlanItem(item, path, errors) {
|
|
|
57
90
|
}
|
|
58
91
|
}
|
|
59
92
|
}
|
|
60
|
-
/** Validate vBRIEF structural requirements (v0.6). */
|
|
93
|
+
/** Validate vBRIEF/xBRIEF structural requirements (v0.6 + v0.8 additive). */
|
|
61
94
|
export function validateVbriefSchema(data, filepath) {
|
|
62
95
|
const errors = [];
|
|
63
|
-
|
|
64
|
-
|
|
96
|
+
const resolved = resolveInfoBlock(data);
|
|
97
|
+
if (resolved === null) {
|
|
98
|
+
let infoShapeError = false;
|
|
99
|
+
for (const key of VALID_INFO_ROOT_KEYS) {
|
|
100
|
+
if (!(key in data)) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const info = data[key];
|
|
104
|
+
if (info === null || Array.isArray(info) || typeof info !== "object") {
|
|
105
|
+
errors.push(`${filepath}: '${key}' must be an object`);
|
|
106
|
+
infoShapeError = true;
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (!infoShapeError) {
|
|
111
|
+
errors.push(`${filepath}: missing required top-level key 'vBRIEFInfo' or 'xBRIEFInfo'`);
|
|
112
|
+
}
|
|
65
113
|
}
|
|
66
114
|
else {
|
|
67
|
-
const
|
|
68
|
-
if (
|
|
69
|
-
errors.push(`${filepath}: '
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
errors.push(`${filepath}: 'vBRIEFInfo.version' must be '0.6' ` +
|
|
75
|
-
`(canonical v0.6 schema, #533), got ` +
|
|
76
|
-
`${pyStrRepr(String(version))}. Run \`task migrate:vbrief\` to ` +
|
|
77
|
-
`upgrade pre-existing v0.5 vBRIEFs in-place.`);
|
|
78
|
-
}
|
|
115
|
+
const version = resolved.info.version;
|
|
116
|
+
if (!VALID_VBRIEF_VERSIONS.has(String(version))) {
|
|
117
|
+
errors.push(`${filepath}: '${resolved.key}.version' must be one of ` +
|
|
118
|
+
`${[...VALID_VBRIEF_VERSIONS].map((v) => `'${v}'`).join(", ")} ` +
|
|
119
|
+
`(canonical v0.6/v0.8 schema, #2107), got ` +
|
|
120
|
+
`${pyStrRepr(String(version))}. Run \`task migrate:vbrief\` to ` +
|
|
121
|
+
`upgrade pre-existing v0.5 vBRIEFs in-place.`);
|
|
79
122
|
}
|
|
80
123
|
}
|
|
81
124
|
if (!("plan" in data)) {
|
|
@@ -96,8 +139,8 @@ export function validateVbriefSchema(data, filepath) {
|
|
|
96
139
|
if ("title" in planObj && (typeof planObj.title !== "string" || !planObj.title)) {
|
|
97
140
|
errors.push(`${filepath}: 'plan.title' must be a non-empty string`);
|
|
98
141
|
}
|
|
99
|
-
if ("status" in planObj && !
|
|
100
|
-
const sorted = [...
|
|
142
|
+
if ("status" in planObj && !VALID_PLAN_STATUSES.has(String(planObj.status))) {
|
|
143
|
+
const sorted = [...VALID_PLAN_STATUSES]
|
|
101
144
|
.sort()
|
|
102
145
|
.map((s) => `'${s}'`)
|
|
103
146
|
.join(", ");
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
2
|
-
import { join, resolve } from "node:path";
|
|
2
|
+
import { basename, join, resolve } from "node:path";
|
|
3
|
+
import { hasArtifactSuffix, MIGRATED_ARTIFACT_DIR } from "../layout/resolve.js";
|
|
3
4
|
import { LIFECYCLE_FOLDERS } from "./constants.js";
|
|
4
5
|
import { validateNoRootDecompositionDrafts } from "./decomposition.js";
|
|
5
6
|
import { validateEpicStoryLinks } from "./epic-links.js";
|
|
@@ -36,7 +37,7 @@ export function discoverVbriefs(vbriefDir) {
|
|
|
36
37
|
continue;
|
|
37
38
|
}
|
|
38
39
|
const names = readdirSync(folderPath)
|
|
39
|
-
.filter((name) => name
|
|
40
|
+
.filter((name) => hasArtifactSuffix(name))
|
|
40
41
|
.sort();
|
|
41
42
|
for (const name of names) {
|
|
42
43
|
files.push({
|
|
@@ -89,8 +90,15 @@ export function validateAll(vbriefDir, options = {}) {
|
|
|
89
90
|
warnings.push(...validateOriginProvenance(display, data, vbriefDir, strictOriginTypes));
|
|
90
91
|
}
|
|
91
92
|
const normalizedDir = normalizeVbriefDir(vbriefDir);
|
|
92
|
-
|
|
93
|
-
|
|
93
|
+
// Layout-aware (#2109 part 1): key the project-definition suffix to the
|
|
94
|
+
// resolved lifecycle directory's identity (its basename) rather than probing
|
|
95
|
+
// the filesystem, so a stray xbrief-named file in a legacy tree cannot flip
|
|
96
|
+
// selection -- this stays consistent with resolveLifecycleLayout's output.
|
|
97
|
+
const projectDefName = basename(normalizedDir) === MIGRATED_ARTIFACT_DIR
|
|
98
|
+
? "PROJECT-DEFINITION.xbrief.json"
|
|
99
|
+
: "PROJECT-DEFINITION.vbrief.json";
|
|
100
|
+
const projectDefDisplay = `${normalizedDir}/${projectDefName}`;
|
|
101
|
+
const projectDefAbsolute = join(vbriefDir, projectDefName);
|
|
94
102
|
if (existsSync(projectDefAbsolute)) {
|
|
95
103
|
const { data, error } = loadVbrief(projectDefAbsolute);
|
|
96
104
|
if (error !== null) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
|
+
import { hasArtifactSuffix, projectDefinitionRelPath, resolveLifecycleRoot, resolveProjectDefinitionPath, } from "../layout/resolve.js";
|
|
3
4
|
export const CODE_STRUCTURE_VERSION = "0.1";
|
|
4
5
|
export const DIRECTIVE_HOME = "x-directive/architecture.codeStructure";
|
|
5
6
|
export const PLAN_HOME = "plan.architecture.codeStructure";
|
|
@@ -495,7 +496,9 @@ export function validateFile(path, options = {}) {
|
|
|
495
496
|
}
|
|
496
497
|
if (projectRoot !== null && !allowStandalone) {
|
|
497
498
|
const relPath = projectRelative(path, projectRoot);
|
|
498
|
-
|
|
499
|
+
// Layout-aware (#2109 part 2a): compare against the resolved lifecycle's
|
|
500
|
+
// PROJECT-DEFINITION rel path (vbrief/... on today's tree).
|
|
501
|
+
if (relPath !== projectDefinitionRelPath(projectRoot) && homes.length > 0) {
|
|
499
502
|
errors.push(finding("CS-HOME", "canonical codeStructure metadata must live in vbrief/PROJECT-DEFINITION.vbrief.json; sibling files must be generated projections", path));
|
|
500
503
|
}
|
|
501
504
|
}
|
|
@@ -529,7 +532,8 @@ export function validateFile(path, options = {}) {
|
|
|
529
532
|
}
|
|
530
533
|
export function discoverCodeStructurePaths(projectRoot) {
|
|
531
534
|
const paths = new Map();
|
|
532
|
-
|
|
535
|
+
// Layout-aware (#2109 part 2a): resolve PROJECT-DEFINITION + lifecycle root.
|
|
536
|
+
const projectDef = resolveProjectDefinitionPath(projectRoot);
|
|
533
537
|
if (existsSync(projectDef)) {
|
|
534
538
|
try {
|
|
535
539
|
const data = loadJsonFile(projectDef);
|
|
@@ -541,7 +545,7 @@ export function discoverCodeStructurePaths(projectRoot) {
|
|
|
541
545
|
paths.set(projectDef.replace(/\\/g, "/"), projectDef);
|
|
542
546
|
}
|
|
543
547
|
}
|
|
544
|
-
const vbriefRoot =
|
|
548
|
+
const vbriefRoot = resolveLifecycleRoot(projectRoot);
|
|
545
549
|
if (existsSync(vbriefRoot)) {
|
|
546
550
|
const stack = [vbriefRoot];
|
|
547
551
|
while (stack.length > 0) {
|
|
@@ -562,7 +566,7 @@ export function discoverCodeStructurePaths(projectRoot) {
|
|
|
562
566
|
stack.push(full);
|
|
563
567
|
continue;
|
|
564
568
|
}
|
|
565
|
-
if (!name
|
|
569
|
+
if (!hasArtifactSuffix(name) || full === projectDef) {
|
|
566
570
|
continue;
|
|
567
571
|
}
|
|
568
572
|
try {
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* contract-drift.ts — deterministic gate for the public contract layer (#1799).
|
|
2
|
+
* contract-drift.ts — deterministic gate for the public contract layer (#1799, #2107).
|
|
3
3
|
*
|
|
4
4
|
* Ensures:
|
|
5
5
|
* 1. packages/types/schemas/vbrief-core-0.6.schema.json matches the canonical
|
|
6
6
|
* content/vbrief/schemas/vbrief-core.schema.json byte-for-byte.
|
|
7
|
-
* 2.
|
|
8
|
-
*
|
|
7
|
+
* 2. packages/types/schemas/xbrief-core-0.8.schema.json matches the canonical
|
|
8
|
+
* content/vbrief/schemas/xbrief-core-0.8.schema.json byte-for-byte.
|
|
9
|
+
* 3. @deftai/directive-types VALID_STATUSES matches v0.6 $defs.Status enum.
|
|
10
|
+
* 4. @deftai/directive-types VBRIEF_VERSION matches v0.8 $defs.xBRIEFInfo.version const.
|
|
9
11
|
*
|
|
10
12
|
* Exit codes: 0 clean / 1 drift / 2 config error.
|
|
11
13
|
*/
|
|
@@ -15,6 +17,8 @@ export declare const EXIT_DRIFT = 1;
|
|
|
15
17
|
export declare const EXIT_CONFIG_ERROR = 2;
|
|
16
18
|
export declare const CANONICAL_SCHEMA_REL = "content/vbrief/schemas/vbrief-core.schema.json";
|
|
17
19
|
export declare const PUBLISHED_SCHEMA_REL = "packages/types/schemas/vbrief-core-0.6.schema.json";
|
|
20
|
+
export declare const XBRIEF_CANONICAL_SCHEMA_REL = "content/vbrief/schemas/xbrief-core-0.8.schema.json";
|
|
21
|
+
export declare const XBRIEF_PUBLISHED_SCHEMA_REL = "packages/types/schemas/xbrief-core-0.8.schema.json";
|
|
18
22
|
export interface ContractDriftResult {
|
|
19
23
|
readonly code: GateExitCode;
|
|
20
24
|
readonly message: string;
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* contract-drift.ts — deterministic gate for the public contract layer (#1799).
|
|
2
|
+
* contract-drift.ts — deterministic gate for the public contract layer (#1799, #2107).
|
|
3
3
|
*
|
|
4
4
|
* Ensures:
|
|
5
5
|
* 1. packages/types/schemas/vbrief-core-0.6.schema.json matches the canonical
|
|
6
6
|
* content/vbrief/schemas/vbrief-core.schema.json byte-for-byte.
|
|
7
|
-
* 2.
|
|
8
|
-
*
|
|
7
|
+
* 2. packages/types/schemas/xbrief-core-0.8.schema.json matches the canonical
|
|
8
|
+
* content/vbrief/schemas/xbrief-core-0.8.schema.json byte-for-byte.
|
|
9
|
+
* 3. @deftai/directive-types VALID_STATUSES matches v0.6 $defs.Status enum.
|
|
10
|
+
* 4. @deftai/directive-types VBRIEF_VERSION matches v0.8 $defs.xBRIEFInfo.version const.
|
|
9
11
|
*
|
|
10
12
|
* Exit codes: 0 clean / 1 drift / 2 config error.
|
|
11
13
|
*/
|
|
@@ -17,6 +19,8 @@ export const EXIT_DRIFT = 1;
|
|
|
17
19
|
export const EXIT_CONFIG_ERROR = 2;
|
|
18
20
|
export const CANONICAL_SCHEMA_REL = "content/vbrief/schemas/vbrief-core.schema.json";
|
|
19
21
|
export const PUBLISHED_SCHEMA_REL = "packages/types/schemas/vbrief-core-0.6.schema.json";
|
|
22
|
+
export const XBRIEF_CANONICAL_SCHEMA_REL = "content/vbrief/schemas/xbrief-core-0.8.schema.json";
|
|
23
|
+
export const XBRIEF_PUBLISHED_SCHEMA_REL = "packages/types/schemas/xbrief-core-0.8.schema.json";
|
|
20
24
|
function defaultReadText(path) {
|
|
21
25
|
return readFileSync(path, "utf8");
|
|
22
26
|
}
|
|
@@ -48,38 +52,35 @@ function schemaStatusEnum(schema) {
|
|
|
48
52
|
}
|
|
49
53
|
return [...enumValues];
|
|
50
54
|
}
|
|
51
|
-
function
|
|
55
|
+
function xbriefVersionConst(schema) {
|
|
52
56
|
const defs = schema.$defs;
|
|
53
57
|
if (typeof defs !== "object" || defs === null || Array.isArray(defs)) {
|
|
54
58
|
throw new Error("schema missing $defs");
|
|
55
59
|
}
|
|
56
|
-
const info = defs.
|
|
60
|
+
const info = defs.xBRIEFInfo;
|
|
57
61
|
if (typeof info !== "object" || info === null || Array.isArray(info)) {
|
|
58
|
-
throw new Error("schema missing $defs.
|
|
62
|
+
throw new Error("schema missing $defs.xBRIEFInfo");
|
|
59
63
|
}
|
|
60
64
|
const properties = info.properties;
|
|
61
65
|
if (typeof properties !== "object" || properties === null || Array.isArray(properties)) {
|
|
62
|
-
throw new Error("schema missing $defs.
|
|
66
|
+
throw new Error("schema missing $defs.xBRIEFInfo.properties");
|
|
63
67
|
}
|
|
64
68
|
const version = properties.version;
|
|
65
69
|
if (typeof version !== "object" || version === null || Array.isArray(version)) {
|
|
66
|
-
throw new Error("schema missing $defs.
|
|
70
|
+
throw new Error("schema missing $defs.xBRIEFInfo.properties.version");
|
|
67
71
|
}
|
|
68
72
|
const constValue = version.const;
|
|
69
73
|
if (typeof constValue !== "string") {
|
|
70
|
-
throw new Error("schema $defs.
|
|
74
|
+
throw new Error("schema $defs.xBRIEFInfo.properties.version.const must be a string");
|
|
71
75
|
}
|
|
72
76
|
return constValue;
|
|
73
77
|
}
|
|
74
78
|
function sortedStrings(values) {
|
|
75
79
|
return [...values].sort();
|
|
76
80
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
const readText = options.readText ?? defaultReadText;
|
|
81
|
-
const canonicalPath = join(root, CANONICAL_SCHEMA_REL);
|
|
82
|
-
const publishedPath = join(root, PUBLISHED_SCHEMA_REL);
|
|
81
|
+
function schemaPairInSync(root, readText, canonicalRel, publishedRel) {
|
|
82
|
+
const canonicalPath = join(root, canonicalRel);
|
|
83
|
+
const publishedPath = join(root, publishedRel);
|
|
83
84
|
let canonicalText;
|
|
84
85
|
let publishedText;
|
|
85
86
|
try {
|
|
@@ -97,14 +98,31 @@ export function evaluateContractDrift(projectRoot, options = {}) {
|
|
|
97
98
|
if (canonicalText !== publishedText) {
|
|
98
99
|
return {
|
|
99
100
|
code: EXIT_DRIFT,
|
|
100
|
-
message: `contract-drift: ${
|
|
101
|
+
message: `contract-drift: ${publishedRel} is out of sync with ${canonicalRel}. ` +
|
|
101
102
|
"Run: pnpm --prefix packages/types run prebuild",
|
|
102
103
|
stream: "stderr",
|
|
103
104
|
};
|
|
104
105
|
}
|
|
105
|
-
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
/** Evaluate contract drift for the directive source tree. */
|
|
109
|
+
export function evaluateContractDrift(projectRoot, options = {}) {
|
|
110
|
+
const root = resolve(projectRoot);
|
|
111
|
+
const readText = options.readText ?? defaultReadText;
|
|
112
|
+
for (const [canonicalRel, publishedRel] of [
|
|
113
|
+
[CANONICAL_SCHEMA_REL, PUBLISHED_SCHEMA_REL],
|
|
114
|
+
[XBRIEF_CANONICAL_SCHEMA_REL, XBRIEF_PUBLISHED_SCHEMA_REL],
|
|
115
|
+
]) {
|
|
116
|
+
const syncResult = schemaPairInSync(root, readText, canonicalRel, publishedRel);
|
|
117
|
+
if (syncResult !== null) {
|
|
118
|
+
return syncResult;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
let v06Schema;
|
|
122
|
+
let v08Schema;
|
|
106
123
|
try {
|
|
107
|
-
|
|
124
|
+
v06Schema = loadSchemaJson(readText(join(root, CANONICAL_SCHEMA_REL)), CANONICAL_SCHEMA_REL);
|
|
125
|
+
v08Schema = loadSchemaJson(readText(join(root, XBRIEF_CANONICAL_SCHEMA_REL)), XBRIEF_CANONICAL_SCHEMA_REL);
|
|
108
126
|
}
|
|
109
127
|
catch (error) {
|
|
110
128
|
const detail = error instanceof Error ? error.message : String(error);
|
|
@@ -115,7 +133,7 @@ export function evaluateContractDrift(projectRoot, options = {}) {
|
|
|
115
133
|
};
|
|
116
134
|
}
|
|
117
135
|
try {
|
|
118
|
-
const schemaStatuses = sortedStrings(schemaStatusEnum(
|
|
136
|
+
const schemaStatuses = sortedStrings(schemaStatusEnum(v06Schema));
|
|
119
137
|
const typeStatuses = sortedStrings(VALID_STATUSES);
|
|
120
138
|
if (schemaStatuses.join("|") !== typeStatuses.join("|")) {
|
|
121
139
|
return {
|
|
@@ -125,12 +143,12 @@ export function evaluateContractDrift(projectRoot, options = {}) {
|
|
|
125
143
|
stream: "stderr",
|
|
126
144
|
};
|
|
127
145
|
}
|
|
128
|
-
const schemaVersion =
|
|
146
|
+
const schemaVersion = xbriefVersionConst(v08Schema);
|
|
129
147
|
if (schemaVersion !== VBRIEF_VERSION) {
|
|
130
148
|
return {
|
|
131
149
|
code: EXIT_DRIFT,
|
|
132
150
|
message: "contract-drift: VBRIEF_VERSION in @deftai/directive-types diverges from " +
|
|
133
|
-
"schema
|
|
151
|
+
"schema xBRIEFInfo.version const.",
|
|
134
152
|
stream: "stderr",
|
|
135
153
|
};
|
|
136
154
|
}
|
|
@@ -145,7 +163,7 @@ export function evaluateContractDrift(projectRoot, options = {}) {
|
|
|
145
163
|
}
|
|
146
164
|
return {
|
|
147
165
|
code: EXIT_OK,
|
|
148
|
-
message: "contract-drift: canonical
|
|
166
|
+
message: "contract-drift: canonical schemas, published copies, and TS contract constants are in sync.",
|
|
149
167
|
stream: "stdout",
|
|
150
168
|
};
|
|
151
169
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { LEGACY_VBRIEF_VERSION, VBRIEF_REFERENCE_PREFIX, VBRIEF_VERSION, XBRIEF_REFERENCE_PREFIX } from "@deftai/directive-types";
|
|
2
|
+
export { LEGACY_VBRIEF_VERSION, VBRIEF_REFERENCE_PREFIX, VBRIEF_VERSION, XBRIEF_REFERENCE_PREFIX };
|
|
3
|
+
/** PlanItem container `type` values introduced in xBRIEF v0.8 (excludes `task`). */
|
|
4
|
+
export declare const V08_CONTAINER_ITEM_TYPES: Set<string>;
|
|
5
|
+
export declare const LEGACY_ARTIFACT_DIR: "vbrief";
|
|
6
|
+
export declare const MIGRATED_ARTIFACT_DIR: "xbrief";
|
|
7
|
+
export declare const LEGACY_ARTIFACT_SUFFIX: ".vbrief.json";
|
|
8
|
+
export declare const MIGRATED_ARTIFACT_SUFFIX: ".xbrief.json";
|
|
9
|
+
export declare const LEGACY_INFO_ROOT_KEY: "vBRIEFInfo";
|
|
10
|
+
export declare const MIGRATED_INFO_ROOT_KEY: "xBRIEFInfo";
|
|
11
|
+
/** Item-only status value added in xBRIEF v0.8. */
|
|
12
|
+
export declare const V08_ITEM_STATUS_AUTO: "auto";
|
|
13
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { LEGACY_VBRIEF_VERSION, VBRIEF_REFERENCE_PREFIX, VBRIEF_VERSION, XBRIEF_REFERENCE_PREFIX, } from "@deftai/directive-types";
|
|
2
|
+
import { VALID_PLAN_ITEM_TYPES } from "../vbrief-validate/constants.js";
|
|
3
|
+
export { LEGACY_VBRIEF_VERSION, VBRIEF_REFERENCE_PREFIX, VBRIEF_VERSION, XBRIEF_REFERENCE_PREFIX };
|
|
4
|
+
/** PlanItem container `type` values introduced in xBRIEF v0.8 (excludes `task`). */
|
|
5
|
+
export const V08_CONTAINER_ITEM_TYPES = new Set([...VALID_PLAN_ITEM_TYPES].filter((t) => t !== "task"));
|
|
6
|
+
export const LEGACY_ARTIFACT_DIR = "vbrief";
|
|
7
|
+
export const MIGRATED_ARTIFACT_DIR = "xbrief";
|
|
8
|
+
export const LEGACY_ARTIFACT_SUFFIX = ".vbrief.json";
|
|
9
|
+
export const MIGRATED_ARTIFACT_SUFFIX = ".xbrief.json";
|
|
10
|
+
export const LEGACY_INFO_ROOT_KEY = "vBRIEFInfo";
|
|
11
|
+
export const MIGRATED_INFO_ROOT_KEY = "xBRIEFInfo";
|
|
12
|
+
/** Item-only status value added in xBRIEF v0.8. */
|
|
13
|
+
export const V08_ITEM_STATUS_AUTO = "auto";
|
|
14
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Structured result of a legacy vbrief layout probe (#2108 / #2034). */
|
|
2
|
+
export interface LegacyVbriefLayoutDetection {
|
|
3
|
+
legacyLayout: boolean;
|
|
4
|
+
reasons: string[];
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Detect whether `projectRoot` still uses the legacy vbrief on-disk layout.
|
|
8
|
+
* Mirrors the structured-reasons pattern from `detectPreCutover` (#793).
|
|
9
|
+
*/
|
|
10
|
+
export declare function detectLegacyVbriefLayout(projectRoot: string): LegacyVbriefLayoutDetection;
|
|
11
|
+
//# sourceMappingURL=detect.d.ts.map
|
|
@@ -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
|