@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.
Files changed (151) hide show
  1. package/dist/architecture/sor-preflight.js +6 -4
  2. package/dist/capacity/backfill.js +5 -3
  3. package/dist/capacity/show.js +5 -3
  4. package/dist/category-b-namespace/index.d.ts +39 -0
  5. package/dist/category-b-namespace/index.js +120 -0
  6. package/dist/codebase/default-extractor.js +2 -1
  7. package/dist/codebase/map.js +2 -1
  8. package/dist/codebase/provider.js +4 -2
  9. package/dist/content-contracts/standards/_helpers.js +1 -1
  10. package/dist/doctor/checks.js +2 -1
  11. package/dist/doctor/doctor-state.js +2 -2
  12. package/dist/encoding/patterns.js +19 -0
  13. package/dist/encoding/scan.js +3 -2
  14. package/dist/index.d.ts +2 -0
  15. package/dist/index.js +2 -0
  16. package/dist/init-deposit/refresh.js +2 -1
  17. package/dist/init-deposit/scaffold.js +2 -1
  18. package/dist/install-upgrade/index.d.ts +2 -0
  19. package/dist/install-upgrade/index.js +31 -2
  20. package/dist/intake/issue-emit.d.ts +1 -1
  21. package/dist/intake/issue-emit.js +3 -2
  22. package/dist/intake/issue-ingest.js +11 -8
  23. package/dist/intake/reconcile-issues.js +11 -4
  24. package/dist/layout/index.d.ts +2 -0
  25. package/dist/layout/index.js +2 -0
  26. package/dist/layout/resolve.d.ts +87 -0
  27. package/dist/layout/resolve.js +159 -0
  28. package/dist/lifecycle/lifecycle-hygiene.d.ts +1 -1
  29. package/dist/lifecycle/lifecycle-hygiene.js +8 -5
  30. package/dist/orchestration/judgment-policy.js +4 -6
  31. package/dist/orchestration/verify-judgment-gates.d.ts +1 -0
  32. package/dist/orchestration/verify-judgment-gates.js +4 -2
  33. package/dist/policy/autonomy.js +4 -6
  34. package/dist/policy/capacity.js +4 -6
  35. package/dist/policy/decisions.d.ts +1 -0
  36. package/dist/policy/decisions.js +4 -2
  37. package/dist/policy/index.d.ts +1 -0
  38. package/dist/policy/index.js +3 -1
  39. package/dist/policy/plan-extensions.d.ts +35 -0
  40. package/dist/policy/plan-extensions.js +57 -0
  41. package/dist/policy/resolve.d.ts +6 -2
  42. package/dist/policy/resolve.js +18 -8
  43. package/dist/policy/wip.js +6 -4
  44. package/dist/preflight-cache/evaluate.js +8 -5
  45. package/dist/release/native-steps.js +4 -3
  46. package/dist/render/framework-commands.js +2 -1
  47. package/dist/render/prd-render.js +2 -2
  48. package/dist/render/project-render.js +23 -5
  49. package/dist/render/roadmap-render.js +36 -8
  50. package/dist/render/scope-outlook.js +3 -2
  51. package/dist/scope/audit-log.js +3 -2
  52. package/dist/scope/capacity-stamp.js +2 -1
  53. package/dist/scope/decompose.js +6 -4
  54. package/dist/scope/decomposed-refs.js +2 -1
  55. package/dist/scope/demote.js +5 -4
  56. package/dist/scope/project-context.js +2 -1
  57. package/dist/scope/project-definition-sync.js +4 -3
  58. package/dist/scope/transition.js +6 -2
  59. package/dist/scope/undo.js +2 -1
  60. package/dist/scope/vbrief-ref.js +5 -3
  61. package/dist/session/resume-conditions.js +2 -1
  62. package/dist/session/ritual-sentinel.js +2 -1
  63. package/dist/session/staleness.js +2 -1
  64. package/dist/slice/record.js +4 -4
  65. package/dist/spec-authority/resolver.js +5 -5
  66. package/dist/swarm/complete-cohort.js +5 -4
  67. package/dist/swarm/launch.js +6 -7
  68. package/dist/swarm/readiness.js +22 -20
  69. package/dist/swarm/subagent-backend.js +2 -5
  70. package/dist/swarm/verify-review-clean.js +2 -1
  71. package/dist/triage/actions/candidates-log.d.ts +3 -0
  72. package/dist/triage/actions/candidates-log.js +8 -2
  73. package/dist/triage/bootstrap/gitignore.js +22 -13
  74. package/dist/triage/bootstrap/index.js +7 -4
  75. package/dist/triage/bulk/index.js +2 -1
  76. package/dist/triage/classify/index.js +13 -11
  77. package/dist/triage/queue/audit.js +3 -3
  78. package/dist/triage/queue/cache.js +3 -2
  79. package/dist/triage/queue/ranking-labels.js +2 -1
  80. package/dist/triage/queue/scope-walk.js +8 -6
  81. package/dist/triage/reconcile/audit.js +2 -1
  82. package/dist/triage/reconcile/parse-uri.js +2 -1
  83. package/dist/triage/reconcile/reconcile.js +8 -7
  84. package/dist/triage/refresh/extract.js +4 -2
  85. package/dist/triage/refresh/refresh.js +3 -2
  86. package/dist/triage/scope/mutations-core.js +13 -9
  87. package/dist/triage/scope/renderers.js +5 -3
  88. package/dist/triage/scope/resolve.js +6 -5
  89. package/dist/triage/scope/validate.js +3 -6
  90. package/dist/triage/scope-drift/add-ignore.js +4 -2
  91. package/dist/triage/scope-drift/scope-rules.js +3 -2
  92. package/dist/triage/subscribe/index.js +13 -7
  93. package/dist/triage/summary/index.js +7 -5
  94. package/dist/triage/summary/reconcilable.js +6 -4
  95. package/dist/triage/summary/scope-drift.js +2 -1
  96. package/dist/triage/welcome/prior-state.js +9 -9
  97. package/dist/triage/welcome/summary.js +9 -8
  98. package/dist/triage/welcome/writers.js +12 -10
  99. package/dist/validate-content/capacity-policy.js +5 -4
  100. package/dist/validate-content/capacity-show.js +5 -3
  101. package/dist/validate-content/filename.d.ts +1 -1
  102. package/dist/validate-content/filename.js +5 -4
  103. package/dist/validate-content/validate-strategy-output.js +14 -5
  104. package/dist/vbrief-build/build.js +1 -1
  105. package/dist/vbrief-build/constants.js +7 -0
  106. package/dist/vbrief-build/speckit.js +3 -2
  107. package/dist/vbrief-reconcile/graph.js +3 -2
  108. package/dist/vbrief-reconcile/labels.js +4 -3
  109. package/dist/vbrief-reconcile/reconciliation.js +5 -1
  110. package/dist/vbrief-reconcile/swarm-deps.js +6 -5
  111. package/dist/vbrief-reconcile/umbrellas.d.ts +1 -1
  112. package/dist/vbrief-reconcile/umbrellas.js +9 -7
  113. package/dist/vbrief-validate/conformance.d.ts +1 -2
  114. package/dist/vbrief-validate/conformance.js +29 -14
  115. package/dist/vbrief-validate/constants.d.ts +18 -1
  116. package/dist/vbrief-validate/constants.js +32 -3
  117. package/dist/vbrief-validate/epic-links.js +3 -2
  118. package/dist/vbrief-validate/filename.js +5 -3
  119. package/dist/vbrief-validate/index.d.ts +3 -1
  120. package/dist/vbrief-validate/index.js +2 -1
  121. package/dist/vbrief-validate/main.js +17 -6
  122. package/dist/vbrief-validate/origin.js +1 -1
  123. package/dist/vbrief-validate/paths.js +2 -1
  124. package/dist/vbrief-validate/plan-hooks.js +4 -3
  125. package/dist/vbrief-validate/registry-status.d.ts +1 -0
  126. package/dist/vbrief-validate/registry-status.js +3 -1
  127. package/dist/vbrief-validate/roundtrip.d.ts +36 -0
  128. package/dist/vbrief-validate/roundtrip.js +168 -0
  129. package/dist/vbrief-validate/schema.d.ts +1 -1
  130. package/dist/vbrief-validate/schema.js +62 -19
  131. package/dist/vbrief-validate/validate-all.js +12 -4
  132. package/dist/verify-source/code-structure-validate.js +8 -4
  133. package/dist/verify-source/contract-drift.d.ts +7 -3
  134. package/dist/verify-source/contract-drift.js +40 -22
  135. package/dist/xbrief-migrate/constants.d.ts +13 -0
  136. package/dist/xbrief-migrate/constants.js +14 -0
  137. package/dist/xbrief-migrate/detect.d.ts +11 -0
  138. package/dist/xbrief-migrate/detect.js +81 -0
  139. package/dist/xbrief-migrate/drift-gate.d.ts +56 -0
  140. package/dist/xbrief-migrate/drift-gate.js +205 -0
  141. package/dist/xbrief-migrate/index.d.ts +7 -0
  142. package/dist/xbrief-migrate/index.js +7 -0
  143. package/dist/xbrief-migrate/migrate-project.d.ts +33 -0
  144. package/dist/xbrief-migrate/migrate-project.js +189 -0
  145. package/dist/xbrief-migrate/signpost.d.ts +5 -0
  146. package/dist/xbrief-migrate/signpost.js +19 -0
  147. package/dist/xbrief-migrate/transforms.d.ts +46 -0
  148. package/dist/xbrief-migrate/transforms.js +182 -0
  149. package/dist/xbrief-migrate/version.d.ts +8 -0
  150. package/dist/xbrief-migrate/version.js +25 -0
  151. 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, VALID_STATUSES, VALID_VBRIEF_VERSIONS, } from "./constants.js";
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 (!VALID_STATUSES.has(String(item.status))) {
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
- if (!("vBRIEFInfo" in data)) {
64
- errors.push(`${filepath}: missing required top-level key 'vBRIEFInfo'`);
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 info = data.vBRIEFInfo;
68
- if (typeof info !== "object" || info === null || Array.isArray(info)) {
69
- errors.push(`${filepath}: 'vBRIEFInfo' must be an object`);
70
- }
71
- else {
72
- const version = info.version;
73
- if (!VALID_VBRIEF_VERSIONS.has(String(version))) {
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 && !VALID_STATUSES.has(String(planObj.status))) {
100
- const sorted = [...VALID_STATUSES]
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.endsWith(".vbrief.json"))
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
- const projectDefDisplay = `${normalizedDir}/PROJECT-DEFINITION.vbrief.json`;
93
- const projectDefAbsolute = join(vbriefDir, "PROJECT-DEFINITION.vbrief.json");
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
- if (relPath !== PROJECT_DEFINITION_PATH && homes.length > 0) {
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
- const projectDef = join(projectRoot, "vbrief", "PROJECT-DEFINITION.vbrief.json");
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 = join(projectRoot, "vbrief");
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.endsWith(".vbrief.json") || full === projectDef) {
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. @deftai/directive-types Status enum matches the schema Status enum.
8
- * 3. @deftai/directive-types VBRIEF_VERSION matches the schema version const.
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. @deftai/directive-types Status enum matches the schema Status enum.
8
- * 3. @deftai/directive-types VBRIEF_VERSION matches the schema version const.
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 schemaVersionConst(schema) {
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.vBRIEFInfo;
60
+ const info = defs.xBRIEFInfo;
57
61
  if (typeof info !== "object" || info === null || Array.isArray(info)) {
58
- throw new Error("schema missing $defs.vBRIEFInfo");
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.vBRIEFInfo.properties");
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.vBRIEFInfo.properties.version");
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.vBRIEFInfo.properties.version.const must be a string");
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
- /** Evaluate contract drift for the directive source tree. */
78
- export function evaluateContractDrift(projectRoot, options = {}) {
79
- const root = resolve(projectRoot);
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: ${PUBLISHED_SCHEMA_REL} is out of sync with ${CANONICAL_SCHEMA_REL}. ` +
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
- let schema;
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
- schema = loadSchemaJson(canonicalText, CANONICAL_SCHEMA_REL);
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(schema));
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 = schemaVersionConst(schema);
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 vBRIEFInfo.version const.",
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 schema, published copy, and TS contract constants are in sync.",
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