@deftai/directive-core 0.64.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 (165) 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 +3 -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 +17 -14
  23. package/dist/intake/markdown-scanners.d.ts +3 -1
  24. package/dist/intake/markdown-scanners.js +10 -3
  25. package/dist/intake/reconcile-issues.js +13 -4
  26. package/dist/layout/index.d.ts +2 -0
  27. package/dist/layout/index.js +2 -0
  28. package/dist/layout/resolve.d.ts +87 -0
  29. package/dist/layout/resolve.js +159 -0
  30. package/dist/lifecycle/lifecycle-hygiene.d.ts +1 -1
  31. package/dist/lifecycle/lifecycle-hygiene.js +8 -5
  32. package/dist/orchestration/judgment-policy.js +4 -6
  33. package/dist/orchestration/verify-judgment-gates.d.ts +1 -0
  34. package/dist/orchestration/verify-judgment-gates.js +4 -2
  35. package/dist/policy/autonomy.js +4 -6
  36. package/dist/policy/capacity.js +4 -6
  37. package/dist/policy/decisions.d.ts +1 -0
  38. package/dist/policy/decisions.js +4 -2
  39. package/dist/policy/index.d.ts +1 -0
  40. package/dist/policy/index.js +3 -1
  41. package/dist/policy/plan-extensions.d.ts +35 -0
  42. package/dist/policy/plan-extensions.js +57 -0
  43. package/dist/policy/resolve.d.ts +6 -2
  44. package/dist/policy/resolve.js +18 -8
  45. package/dist/policy/wip.js +6 -4
  46. package/dist/preflight-cache/evaluate.js +11 -45
  47. package/dist/release/native-steps.js +4 -3
  48. package/dist/render/framework-commands.js +2 -1
  49. package/dist/render/prd-render.js +2 -2
  50. package/dist/render/project-render.js +23 -5
  51. package/dist/render/roadmap-render.js +36 -8
  52. package/dist/render/scope-outlook.js +3 -2
  53. package/dist/scope/audit-log.js +3 -2
  54. package/dist/scope/capacity-stamp.js +2 -1
  55. package/dist/scope/decompose.js +6 -4
  56. package/dist/scope/decomposed-refs.js +2 -1
  57. package/dist/scope/demote.js +5 -4
  58. package/dist/scope/project-context.js +2 -1
  59. package/dist/scope/project-definition-sync.js +4 -3
  60. package/dist/scope/transition.js +6 -2
  61. package/dist/scope/undo.js +2 -1
  62. package/dist/scope/vbrief-ref.js +5 -3
  63. package/dist/session/resume-conditions.js +2 -1
  64. package/dist/session/ritual-sentinel.js +2 -1
  65. package/dist/session/staleness.js +2 -1
  66. package/dist/slice/record.js +4 -4
  67. package/dist/spec-authority/resolver.js +5 -5
  68. package/dist/swarm/complete-cohort.js +5 -4
  69. package/dist/swarm/launch.js +6 -7
  70. package/dist/swarm/readiness.js +22 -20
  71. package/dist/swarm/subagent-backend.js +2 -5
  72. package/dist/swarm/verify-review-clean.js +2 -1
  73. package/dist/triage/actions/candidates-log.d.ts +3 -0
  74. package/dist/triage/actions/candidates-log.js +8 -2
  75. package/dist/triage/bootstrap/gitignore.js +22 -13
  76. package/dist/triage/bootstrap/index.js +7 -4
  77. package/dist/triage/bulk/index.js +2 -1
  78. package/dist/triage/classify/index.js +13 -11
  79. package/dist/triage/queue/audit.js +3 -3
  80. package/dist/triage/queue/build-queue.js +6 -0
  81. package/dist/triage/queue/cache.js +12 -2
  82. package/dist/triage/queue/index.d.ts +1 -0
  83. package/dist/triage/queue/index.js +1 -0
  84. package/dist/triage/queue/ranking-labels.js +2 -1
  85. package/dist/triage/queue/scope-ignores-filter.d.ts +12 -0
  86. package/dist/triage/queue/scope-ignores-filter.js +50 -0
  87. package/dist/triage/queue/scope-walk.js +8 -6
  88. package/dist/triage/queue/types.d.ts +4 -0
  89. package/dist/triage/reconcile/audit.js +2 -1
  90. package/dist/triage/reconcile/parse-uri.js +2 -1
  91. package/dist/triage/reconcile/reconcile.js +8 -7
  92. package/dist/triage/refresh/extract.js +4 -2
  93. package/dist/triage/refresh/refresh.js +3 -2
  94. package/dist/triage/scope/mutations-core.js +13 -9
  95. package/dist/triage/scope/renderers.js +5 -3
  96. package/dist/triage/scope/resolve.js +6 -5
  97. package/dist/triage/scope/validate.js +3 -6
  98. package/dist/triage/scope-drift/add-ignore.js +4 -2
  99. package/dist/triage/scope-drift/scope-rules.js +3 -2
  100. package/dist/triage/subscribe/index.js +14 -8
  101. package/dist/triage/summary/index.d.ts +33 -0
  102. package/dist/triage/summary/index.js +180 -6
  103. package/dist/triage/summary/reconcilable.js +6 -4
  104. package/dist/triage/summary/scope-drift.js +2 -1
  105. package/dist/triage/welcome/default-mode.d.ts +1 -0
  106. package/dist/triage/welcome/default-mode.js +1 -0
  107. package/dist/triage/welcome/prior-state.js +9 -9
  108. package/dist/triage/welcome/summary.d.ts +2 -0
  109. package/dist/triage/welcome/summary.js +15 -8
  110. package/dist/triage/welcome/writers.js +12 -10
  111. package/dist/validate-content/capacity-policy.js +5 -4
  112. package/dist/validate-content/capacity-show.js +5 -3
  113. package/dist/validate-content/filename.d.ts +1 -1
  114. package/dist/validate-content/filename.js +5 -4
  115. package/dist/validate-content/validate-strategy-output.js +14 -5
  116. package/dist/vbrief-build/build.js +1 -1
  117. package/dist/vbrief-build/constants.js +7 -0
  118. package/dist/vbrief-build/speckit.js +3 -2
  119. package/dist/vbrief-reconcile/graph.js +3 -2
  120. package/dist/vbrief-reconcile/labels.js +4 -3
  121. package/dist/vbrief-reconcile/reconciliation.js +5 -1
  122. package/dist/vbrief-reconcile/swarm-deps.js +6 -5
  123. package/dist/vbrief-reconcile/umbrellas.d.ts +1 -1
  124. package/dist/vbrief-reconcile/umbrellas.js +9 -7
  125. package/dist/vbrief-validate/conformance.d.ts +1 -2
  126. package/dist/vbrief-validate/conformance.js +29 -14
  127. package/dist/vbrief-validate/constants.d.ts +18 -1
  128. package/dist/vbrief-validate/constants.js +32 -3
  129. package/dist/vbrief-validate/epic-links.js +3 -2
  130. package/dist/vbrief-validate/filename.js +5 -3
  131. package/dist/vbrief-validate/index.d.ts +3 -1
  132. package/dist/vbrief-validate/index.js +2 -1
  133. package/dist/vbrief-validate/main.js +17 -6
  134. package/dist/vbrief-validate/origin.js +1 -1
  135. package/dist/vbrief-validate/paths.js +2 -1
  136. package/dist/vbrief-validate/plan-hooks.js +4 -3
  137. package/dist/vbrief-validate/registry-status.d.ts +1 -0
  138. package/dist/vbrief-validate/registry-status.js +3 -1
  139. package/dist/vbrief-validate/roundtrip.d.ts +36 -0
  140. package/dist/vbrief-validate/roundtrip.js +168 -0
  141. package/dist/vbrief-validate/schema.d.ts +1 -1
  142. package/dist/vbrief-validate/schema.js +62 -19
  143. package/dist/vbrief-validate/validate-all.js +12 -4
  144. package/dist/verify-source/code-structure-validate.js +8 -4
  145. package/dist/verify-source/contract-drift.d.ts +33 -0
  146. package/dist/verify-source/contract-drift.js +170 -0
  147. package/dist/verify-source/index.d.ts +1 -0
  148. package/dist/verify-source/index.js +1 -0
  149. package/dist/xbrief-migrate/constants.d.ts +13 -0
  150. package/dist/xbrief-migrate/constants.js +14 -0
  151. package/dist/xbrief-migrate/detect.d.ts +11 -0
  152. package/dist/xbrief-migrate/detect.js +81 -0
  153. package/dist/xbrief-migrate/drift-gate.d.ts +56 -0
  154. package/dist/xbrief-migrate/drift-gate.js +205 -0
  155. package/dist/xbrief-migrate/index.d.ts +7 -0
  156. package/dist/xbrief-migrate/index.js +7 -0
  157. package/dist/xbrief-migrate/migrate-project.d.ts +33 -0
  158. package/dist/xbrief-migrate/migrate-project.js +189 -0
  159. package/dist/xbrief-migrate/signpost.d.ts +5 -0
  160. package/dist/xbrief-migrate/signpost.js +19 -0
  161. package/dist/xbrief-migrate/transforms.d.ts +46 -0
  162. package/dist/xbrief-migrate/transforms.js +182 -0
  163. package/dist/xbrief-migrate/version.d.ts +8 -0
  164. package/dist/xbrief-migrate/version.js +25 -0
  165. package/package.json +11 -3
@@ -15,6 +15,7 @@
15
15
  import { spawnSync } from "node:child_process";
16
16
  import { existsSync, readFileSync, statSync } from "node:fs";
17
17
  import { basename, extname, join, resolve } from "node:path";
18
+ import { hasArtifactSuffix, LIFECYCLE_DIR_NAMES } from "../layout/resolve.js";
18
19
  // ---------------------------------------------------------------------------
19
20
  // Classification constants
20
21
  // ---------------------------------------------------------------------------
@@ -699,11 +700,12 @@ export function scanDiff(diffText) {
699
700
  function changedStoryRecords(projectRoot, changedPaths) {
700
701
  const records = [];
701
702
  for (const rel of changedPaths) {
702
- if (!rel.endsWith(".vbrief.json"))
703
+ if (!hasArtifactSuffix(rel))
703
704
  continue;
704
- if (!rel.startsWith("vbrief/active/") &&
705
- !rel.startsWith("vbrief/pending/") &&
706
- !rel.startsWith("vbrief/proposed/"))
705
+ const inStoryFolder = LIFECYCLE_DIR_NAMES.some((dir) => rel.startsWith(`${dir}/active/`) ||
706
+ rel.startsWith(`${dir}/pending/`) ||
707
+ rel.startsWith(`${dir}/proposed/`));
708
+ if (!inStoryFolder)
707
709
  continue;
708
710
  const path = join(resolve(projectRoot), rel);
709
711
  const [payload, error] = loadJsonFile(path);
@@ -1,7 +1,9 @@
1
1
  import { execFileSync } from "node:child_process";
2
2
  import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
3
3
  import { join, resolve } from "node:path";
4
+ import { referenceTypeMatches } from "@deftai/directive-types";
4
5
  import { sortedStringifyCompact } from "../codebase/json.js";
6
+ import { hasArtifactSuffix, resolveLifecycleFolder } from "../layout/resolve.js";
5
7
  import { classifyBucket, loadBucketMatchers, resolveCapacityAllocation, SOURCE_MATCH, } from "../policy/capacity.js";
6
8
  export const COMPLETED_FOLDER = "completed";
7
9
  export const CACHE_RELPATH = [".deft-cache", "github-issue"];
@@ -32,7 +34,7 @@ export function extractIssueRef(plan) {
32
34
  continue;
33
35
  }
34
36
  const rec = ref;
35
- if (rec.type !== "x-vbrief/github-issue") {
37
+ if (!referenceTypeMatches(String(rec.type ?? ""), "github-issue")) {
36
38
  continue;
37
39
  }
38
40
  const uri = rec.uri;
@@ -186,14 +188,14 @@ export async function backfill(projectRoot, options = {}) {
186
188
  result.exit_code = 2;
187
189
  return result;
188
190
  }
189
- const completedDir = join(resolve(projectRoot), "vbrief", COMPLETED_FOLDER);
191
+ const completedDir = resolveLifecycleFolder(projectRoot, COMPLETED_FOLDER);
190
192
  if (!existsSync(completedDir)) {
191
193
  return result;
192
194
  }
193
195
  let paths;
194
196
  try {
195
197
  paths = readdirSync(completedDir)
196
- .filter((name) => name.endsWith(".vbrief.json"))
198
+ .filter((name) => hasArtifactSuffix(name))
197
199
  .sort();
198
200
  }
199
201
  catch {
@@ -1,5 +1,7 @@
1
1
  import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
2
2
  import { join, resolve } from "node:path";
3
+ import { referenceTypeMatches } from "@deftai/directive-types";
4
+ import { hasArtifactSuffix, resolveLifecycleRoot } from "../layout/resolve.js";
3
5
  import { recommendAutonomyLevel, resolveAutonomy } from "../policy/autonomy.js";
4
6
  import { CAPACITY_UNIT_COST, DEFAULT_CAPACITY_UNIT, resolveCapacityAllocation, } from "../policy/capacity.js";
5
7
  import { DEFAULT_PENDING_DECISIONS_THRESHOLD, pendingDecisionsNudgeLine, summarizeDecisionBacklog, } from "../policy/decisions.js";
@@ -25,7 +27,7 @@ function planHasChildren(plan) {
25
27
  }
26
28
  return refs.some((ref) => typeof ref === "object" &&
27
29
  ref !== null &&
28
- ref.type === "x-vbrief/plan");
30
+ referenceTypeMatches(String(ref.type ?? ""), "plan"));
29
31
  }
30
32
  function coerceCost(value) {
31
33
  if (typeof value === "boolean") {
@@ -109,7 +111,7 @@ export function iterVbriefPlans(vbriefRoot) {
109
111
  continue;
110
112
  }
111
113
  for (const name of names.sort()) {
112
- if (!name.endsWith(".vbrief.json")) {
114
+ if (!hasArtifactSuffix(name)) {
113
115
  continue;
114
116
  }
115
117
  const child = join(folderPath, name);
@@ -158,7 +160,7 @@ function resolveEffectiveUnit(unitRequested, costEligible, costWithActual) {
158
160
  export function computeReport(projectRoot, options = {}) {
159
161
  const now = options.now ?? new Date();
160
162
  const allocation = options.allocation ?? resolveCapacityAllocation(projectRoot);
161
- const vbriefRoot = join(resolve(projectRoot), "vbrief");
163
+ const vbriefRoot = resolveLifecycleRoot(projectRoot);
162
164
  const records = iterVbriefPlans(vbriefRoot).map(([folder, plan]) => classifyRecord(plan, folder, allocation, now));
163
165
  const tallies = new Map();
164
166
  for (const bucket of allocation.buckets) {
@@ -0,0 +1,39 @@
1
+ type JsonObject = Record<string, unknown>;
2
+ export interface CategoryBKeyRename {
3
+ readonly legacyKey: string;
4
+ readonly namespacedKey: string;
5
+ }
6
+ /** The locked Category B renames (#1650): directive's own config, not xbrief data. */
7
+ export declare const CATEGORY_B_KEY_RENAMES: readonly CategoryBKeyRename[];
8
+ /** Raised when both the bare and namespaced form of a Category B key exist. */
9
+ export declare class CategoryBConflictError extends Error {
10
+ constructor(legacyKey: string, namespacedKey: string);
11
+ }
12
+ export interface NamespaceResult {
13
+ readonly doc: JsonObject;
14
+ readonly changed: boolean;
15
+ readonly renamedKeys: readonly string[];
16
+ }
17
+ /**
18
+ * Idempotently rename Category B bare plan keys to their namespaced form.
19
+ * Mutates and returns the supplied document. Throws {@link CategoryBConflictError}
20
+ * when a plan carries both the bare and namespaced form of the same key.
21
+ */
22
+ export declare function namespaceCategoryBPlan(doc: unknown): NamespaceResult;
23
+ export interface CorpusMigrationConflict {
24
+ readonly path: string;
25
+ readonly message: string;
26
+ }
27
+ export interface CorpusMigrationResult {
28
+ readonly scanned: number;
29
+ readonly changed: readonly string[];
30
+ readonly conflicts: readonly CorpusMigrationConflict[];
31
+ }
32
+ /**
33
+ * Walk `vbrief/**\/*.vbrief.json` under projectRoot and namespace every
34
+ * Category B key in place. Idempotent: a second run reports zero changes.
35
+ * Files with a bare/namespaced conflict are reported, not rewritten.
36
+ */
37
+ export declare function migrateCategoryBCorpus(projectRoot: string): CorpusMigrationResult;
38
+ export {};
39
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Category B corpus codemod (#1650 / #2034).
3
+ *
4
+ * Renames the two grandfathered bare directive-config plan keys (`policy`,
5
+ * `completedNote`) to their `x-directive/` namespaced form across the vBRIEF
6
+ * corpus so the #1620 conformance gate can drop its temporary ALLOW_LIST.
7
+ *
8
+ * The transform is idempotent (already-namespaced artifacts are unchanged) and
9
+ * order-preserving (the namespaced key takes the legacy key's slot, keeping
10
+ * artifact diffs minimal).
11
+ */
12
+ import { readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
13
+ import { join, relative, resolve } from "node:path";
14
+ import { hasArtifactSuffix, resolveLifecycleRoot } from "../layout/resolve.js";
15
+ import { LEGACY_PLAN_COMPLETED_NOTE_KEY, LEGACY_PLAN_POLICY_KEY, PLAN_COMPLETED_NOTE_KEY, PLAN_POLICY_KEY, } from "../policy/plan-extensions.js";
16
+ /** The locked Category B renames (#1650): directive's own config, not xbrief data. */
17
+ export const CATEGORY_B_KEY_RENAMES = [
18
+ { legacyKey: LEGACY_PLAN_POLICY_KEY, namespacedKey: PLAN_POLICY_KEY },
19
+ { legacyKey: LEGACY_PLAN_COMPLETED_NOTE_KEY, namespacedKey: PLAN_COMPLETED_NOTE_KEY },
20
+ ];
21
+ /** Raised when both the bare and namespaced form of a Category B key exist. */
22
+ export class CategoryBConflictError extends Error {
23
+ constructor(legacyKey, namespacedKey) {
24
+ super(`plan has both bare '${legacyKey}' and namespaced '${namespacedKey}' -- ` +
25
+ "resolve the conflict by hand before migrating.");
26
+ this.name = "CategoryBConflictError";
27
+ }
28
+ }
29
+ function isPlainObject(value) {
30
+ return typeof value === "object" && value !== null && !Array.isArray(value);
31
+ }
32
+ /**
33
+ * Idempotently rename Category B bare plan keys to their namespaced form.
34
+ * Mutates and returns the supplied document. Throws {@link CategoryBConflictError}
35
+ * when a plan carries both the bare and namespaced form of the same key.
36
+ */
37
+ export function namespaceCategoryBPlan(doc) {
38
+ if (!isPlainObject(doc)) {
39
+ return { doc: doc, changed: false, renamedKeys: [] };
40
+ }
41
+ const plan = doc.plan;
42
+ if (!isPlainObject(plan)) {
43
+ return { doc, changed: false, renamedKeys: [] };
44
+ }
45
+ const renames = CATEGORY_B_KEY_RENAMES.filter((rename) => rename.legacyKey in plan);
46
+ if (renames.length === 0) {
47
+ return { doc, changed: false, renamedKeys: [] };
48
+ }
49
+ for (const { legacyKey, namespacedKey } of renames) {
50
+ if (namespacedKey in plan) {
51
+ throw new CategoryBConflictError(legacyKey, namespacedKey);
52
+ }
53
+ }
54
+ const renameMap = new Map(renames.map((r) => [r.legacyKey, r.namespacedKey]));
55
+ const rebuilt = {};
56
+ for (const [key, value] of Object.entries(plan)) {
57
+ rebuilt[renameMap.get(key) ?? key] = value;
58
+ }
59
+ doc.plan = rebuilt;
60
+ return { doc, changed: true, renamedKeys: renames.map((r) => r.namespacedKey) };
61
+ }
62
+ function collectVbriefFiles(dir, acc = []) {
63
+ let entries;
64
+ try {
65
+ entries = readdirSync(dir, { withFileTypes: true });
66
+ }
67
+ catch {
68
+ return acc;
69
+ }
70
+ for (const entry of entries) {
71
+ const full = join(dir, entry.name);
72
+ if (entry.isDirectory()) {
73
+ collectVbriefFiles(full, acc);
74
+ }
75
+ else if (entry.isFile() && hasArtifactSuffix(entry.name)) {
76
+ acc.push(full);
77
+ }
78
+ }
79
+ return acc;
80
+ }
81
+ /**
82
+ * Walk `vbrief/**\/*.vbrief.json` under projectRoot and namespace every
83
+ * Category B key in place. Idempotent: a second run reports zero changes.
84
+ * Files with a bare/namespaced conflict are reported, not rewritten.
85
+ */
86
+ export function migrateCategoryBCorpus(projectRoot) {
87
+ const root = resolve(projectRoot);
88
+ const vbriefDir = resolveLifecycleRoot(root);
89
+ let scanned = 0;
90
+ const changed = [];
91
+ const conflicts = [];
92
+ for (const file of collectVbriefFiles(vbriefDir)) {
93
+ if (!statSync(file).isFile()) {
94
+ continue;
95
+ }
96
+ scanned += 1;
97
+ let parsed;
98
+ try {
99
+ parsed = JSON.parse(readFileSync(file, "utf8"));
100
+ }
101
+ catch {
102
+ continue;
103
+ }
104
+ const relPath = relative(root, file).replace(/\\/g, "/");
105
+ let result;
106
+ try {
107
+ result = namespaceCategoryBPlan(parsed);
108
+ }
109
+ catch (err) {
110
+ conflicts.push({ path: relPath, message: err instanceof Error ? err.message : String(err) });
111
+ continue;
112
+ }
113
+ if (result.changed) {
114
+ writeFileSync(file, `${JSON.stringify(result.doc, null, 2)}\n`, "utf8");
115
+ changed.push(relPath);
116
+ }
117
+ }
118
+ return { scanned, changed: changed.sort(), conflicts };
119
+ }
120
+ //# sourceMappingURL=index.js.map
@@ -1,6 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { existsSync, globSync, readdirSync, readFileSync, realpathSync, statSync, } from "node:fs";
3
3
  import { isAbsolute, join, relative, resolve } from "node:path";
4
+ import { resolveProjectDefinitionPath } from "../layout/resolve.js";
4
5
  import { extractCodeStructure, loadJsonFile } from "../verify-source/code-structure-validate.js";
5
6
  import { sortedStringifyPretty } from "./json.js";
6
7
  import { CODEBASE_MAP_FORMAT_VERSION, CODEBASE_MAP_KIND, CODEBASE_PROVIDER_CONTRACT_VERSION, } from "./projection-registry.js";
@@ -100,7 +101,7 @@ function sourcePathRel(projectRoot, sourcePath) {
100
101
  }
101
102
  /** Return the authored codeStructure source path used by the default extractor. */
102
103
  export function defaultCodeStructurePath(projectRoot, codeStructurePath) {
103
- return codeStructurePath ?? join(projectRoot, "vbrief", "PROJECT-DEFINITION.vbrief.json");
104
+ return codeStructurePath ?? resolveProjectDefinitionPath(projectRoot);
104
105
  }
105
106
  function stableId(value) {
106
107
  let slug = "";
@@ -1,5 +1,6 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { dirname, isAbsolute, join, relative, resolve } from "node:path";
3
+ import { resolveProjectDefinitionPath } from "../layout/resolve.js";
3
4
  import { extractCodeStructure, loadJsonFile } from "../verify-source/code-structure-validate.js";
4
5
  import { CodeStructureConfigError, configErrorToDict, defaultCodeStructurePath, } from "./default-extractor.js";
5
6
  import { sortedStringifyPretty } from "./json.js";
@@ -73,7 +74,7 @@ export function projectionOutputPath(projectRoot, explicitOutput) {
73
74
  if (explicitOutput !== undefined && explicitOutput !== null && explicitOutput.length > 0) {
74
75
  return explicitOutput;
75
76
  }
76
- const projectDefinition = join(projectRoot, "vbrief", "PROJECT-DEFINITION.vbrief.json");
77
+ const projectDefinition = resolveProjectDefinitionPath(projectRoot);
77
78
  if (!existsSync(projectDefinition)) {
78
79
  return DEFAULT_OUTPUT_PATH;
79
80
  }
@@ -4,6 +4,8 @@ import { existsSync, readFileSync, statSync } from "node:fs";
4
4
  import { dirname, isAbsolute, join, posix, resolve } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { contentRoot } from "../content-root.js";
7
+ import { resolveProjectDefinitionPath } from "../layout/resolve.js";
8
+ import { readPlanPolicy } from "../policy/plan-extensions.js";
7
9
  import { loadJsonFile } from "../verify-source/code-structure-validate.js";
8
10
  import { CODEBASE_MAP_SCHEMA_PATH } from "./constants.js";
9
11
  import { buildCodebaseMap, CodeStructureConfigError, configErrorToDict, defaultCodeStructurePath, fileSha256, } from "./default-extractor.js";
@@ -254,7 +256,7 @@ function isSafeRelativePath(value) {
254
256
  return !text.split("/").includes("..");
255
257
  }
256
258
  function projectDefinitionPath(projectRoot) {
257
- return join(projectRoot, "vbrief", "PROJECT-DEFINITION.vbrief.json");
259
+ return resolveProjectDefinitionPath(projectRoot);
258
260
  }
259
261
  function expectValue(expect, ...keys) {
260
262
  if (typeof expect !== "object" || expect === null || Array.isArray(expect)) {
@@ -282,7 +284,7 @@ export function loadProviderArtifactPolicy(projectRoot, kind = CODEBASE_MAP_KIND
282
284
  if (typeof plan !== "object" || plan === null || Array.isArray(plan)) {
283
285
  return providerArtifactPolicyEmpty();
284
286
  }
285
- const policy = plan.policy;
287
+ const policy = readPlanPolicy(plan);
286
288
  if (typeof policy !== "object" || policy === null || Array.isArray(policy)) {
287
289
  return providerArtifactPolicyEmpty();
288
290
  }
@@ -63,12 +63,14 @@ const SKIP_DIRS = new Set([
63
63
  "dist",
64
64
  "tests",
65
65
  ".deft-cache",
66
+ // Swarm worktrees under .deft-scratch/ are not framework content (#1656).
67
+ ".deft-scratch",
66
68
  // node_modules holds third-party markdown (dependency READMEs) that is not
67
69
  // framework content; scanning it produced volatile, version-pinned cases that
68
70
  // broke CI when dependency versions drifted (#1993 follow-up).
69
71
  "node_modules",
70
72
  ]);
71
- const MIGRATION_ARTIFACT_TREES = ["vbrief/migration"];
73
+ const MIGRATION_ARTIFACT_TREES = ["xbrief/migration", "vbrief/migration"];
72
74
  export function isMigrationArtifact(rel) {
73
75
  if (MIGRATION_ARTIFACT_TREES.some((t) => rel.startsWith(`${t}/`))) {
74
76
  return true;
@@ -1,6 +1,7 @@
1
1
  import { join } from "node:path";
2
2
  import { detectLegacyLayout, legacyLayoutSignpostLine, } from "../init-deposit/legacy-detect.js";
3
3
  import { detectCanonicalVendoredManifest, isNpmManaged, NPM_MANAGED_SENTINEL_KEY, NPM_MANAGED_SENTINEL_VALUE, } from "../init-deposit/migrate.js";
4
+ import { resolveLifecycleRoot } from "../layout/resolve.js";
4
5
  import { findSkillPathsInText } from "../text/redos-safe.js";
5
6
  import { CANONICAL_UPGRADE_COMMAND, GO_BRIDGE_RELEASES_URL, UPGRADING_DOC_URL, } from "./constants.js";
6
7
  import { isDeprecationRedirectStub, locateManifest, manifestCandidatePaths, manifestTagToVersion, parseInstallManifest, parseInstallRootFromAgentsMd, parseManifest, } from "./manifest.js";
@@ -127,7 +128,7 @@ export function checkManifestAgreement(projectRoot, installRoot, seams = {}) {
127
128
  const manifestPath = locateManifest(projectRoot, installRoot, isFile);
128
129
  const expectedManifestPath = manifestPath ?? manifestCandidatePaths(projectRoot, installRoot)[0];
129
130
  const bareCandidates = [
130
- join(projectRoot, "vbrief", ".deft-version"),
131
+ join(resolveLifecycleRoot(projectRoot), ".deft-version"),
131
132
  join(projectRoot, ".deft-version"),
132
133
  ];
133
134
  const barePath = bareCandidates.find((p) => isFile(p)) ?? null;
@@ -1,7 +1,7 @@
1
1
  import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { dirname, join } from "node:path";
3
+ import { resolveEvalPath } from "../layout/resolve.js";
3
4
  import { CLEAN_WINDOW_HOURS, DIRTY_WINDOW_HOURS, ENV_STATE_PATH } from "./constants.js";
4
- const STATE_PARENT = join("vbrief", ".eval");
5
5
  const STATE_FILENAME = "doctor-state.json";
6
6
  export function statePath(projectRoot) {
7
7
  const override = process.env[ENV_STATE_PATH]?.trim();
@@ -10,7 +10,7 @@ export function statePath(projectRoot) {
10
10
  ? join(process.env.HOME ?? projectRoot, override.slice(1))
11
11
  : override;
12
12
  }
13
- return join(projectRoot, STATE_PARENT, STATE_FILENAME);
13
+ return resolveEvalPath(projectRoot, STATE_FILENAME);
14
14
  }
15
15
  function parseIso(ts) {
16
16
  if (typeof ts !== "string" || !ts) {
@@ -78,6 +78,25 @@ export const SCANNABLE_EXTENSIONS = new Set([
78
78
  * POSIX form. Ported verbatim from the Python `BUILTIN_ALLOW_LIST`.
79
79
  */
80
80
  export const BUILTIN_ALLOW_LIST = [
81
+ // Migrated xbrief/ layout (#2109). The #798 vBRIEF legitimately carries
82
+ // mojibake byte sequences as its subject matter; keep it allow-listed under
83
+ // the canonical layout.
84
+ "xbrief/active/*-798-*.xbrief.json",
85
+ "xbrief/completed/*-798-*.xbrief.json",
86
+ "xbrief/cancelled/*-798-*.xbrief.json",
87
+ "xbrief/pending/*-798-*.xbrief.json",
88
+ "xbrief/proposed/*-798-*.xbrief.json",
89
+ ".deft/core/xbrief/active/*-798-*.xbrief.json",
90
+ ".deft/core/xbrief/completed/*-798-*.xbrief.json",
91
+ ".deft/core/xbrief/cancelled/*-798-*.xbrief.json",
92
+ ".deft/core/xbrief/pending/*-798-*.xbrief.json",
93
+ ".deft/core/xbrief/proposed/*-798-*.xbrief.json",
94
+ "deft/xbrief/active/*-798-*.xbrief.json",
95
+ "deft/xbrief/completed/*-798-*.xbrief.json",
96
+ "deft/xbrief/cancelled/*-798-*.xbrief.json",
97
+ "deft/xbrief/pending/*-798-*.xbrief.json",
98
+ "deft/xbrief/proposed/*-798-*.xbrief.json",
99
+ // Legacy vbrief/ layout (back-compat read support; removed by #2112).
81
100
  "vbrief/active/*-798-*.vbrief.json",
82
101
  "vbrief/completed/*-798-*.vbrief.json",
83
102
  "vbrief/cancelled/*-798-*.vbrief.json",
@@ -1,4 +1,5 @@
1
1
  import { readFileSync } from "node:fs";
2
+ import { hasArtifactSuffix, LIFECYCLE_DIR_NAMES } from "../layout/resolve.js";
2
3
  import { MOJIBAKE_PATTERNS, NO_BOM_EXTENSIONS, REPLACEMENT_CHAR, UTF8_BOM, VBRIEF_CONTROL_CHAR_LABELS, } from "./patterns.js";
3
4
  import { pythonSplitlines, stripMarkdownQuotes } from "./text.js";
4
5
  /** Render a finding as the Python oracle does: ` path:line [label] context`. */
@@ -95,11 +96,11 @@ export function scanFile(relPath, fullPath) {
95
96
  }
96
97
  /** Return true for in-flight vBRIEF files that may receive issue ingest. */
97
98
  export function isVbriefNarrativeControlScope(relPath) {
98
- if (!relPath.endsWith(".vbrief.json")) {
99
+ if (!hasArtifactSuffix(relPath)) {
99
100
  return false;
100
101
  }
101
102
  const normalized = `/${relPath.replace(/\\/g, "/")}`;
102
- return normalized.includes("/vbrief/proposed/") || normalized.includes("/vbrief/active/");
103
+ return LIFECYCLE_DIR_NAMES.some((dir) => normalized.includes(`/${dir}/proposed/`) || normalized.includes(`/${dir}/active/`));
103
104
  }
104
105
  function tabIsNonIndentation(value, index) {
105
106
  const lineStart = value.lastIndexOf("\n", index - 1) + 1;
package/dist/index.d.ts CHANGED
@@ -16,6 +16,7 @@ export * as codebase from "./codebase/index.js";
16
16
  export * as doctor from "./doctor/index.js";
17
17
  export * from "./encoding/index.js";
18
18
  export * as intake from "./intake/index.js";
19
+ export * as layout from "./layout/index.js";
19
20
  export * as legacyBridge from "./legacy-bridge/index.js";
20
21
  export * as lifecycle from "./lifecycle/index.js";
21
22
  export * as orchestration from "./orchestration/index.js";
@@ -49,6 +50,7 @@ export * as vbriefValidation from "./vbrief-validation/index.js";
49
50
  export * as verifyEnv from "./verify-env/index.js";
50
51
  export * as verifySource from "./verify-source/index.js";
51
52
  export * as wipCap from "./wip-cap/index.js";
53
+ export * as xbriefMigrate from "./xbrief-migrate/index.js";
52
54
  export declare const CORE_PACKAGE: "@deftai/directive-core";
53
55
  /** Returns identifying metadata for the core engine package. */
54
56
  export declare function engineInfo(): EngineInfo;
package/dist/index.js CHANGED
@@ -16,6 +16,7 @@ export * as codebase from "./codebase/index.js";
16
16
  export * as doctor from "./doctor/index.js";
17
17
  export * from "./encoding/index.js";
18
18
  export * as intake from "./intake/index.js";
19
+ export * as layout from "./layout/index.js";
19
20
  export * as legacyBridge from "./legacy-bridge/index.js";
20
21
  export * as lifecycle from "./lifecycle/index.js";
21
22
  export * as orchestration from "./orchestration/index.js";
@@ -49,6 +50,7 @@ export * as vbriefValidation from "./vbrief-validation/index.js";
49
50
  export * as verifyEnv from "./verify-env/index.js";
50
51
  export * as verifySource from "./verify-source/index.js";
51
52
  export * as wipCap from "./wip-cap/index.js";
53
+ export * as xbriefMigrate from "./xbrief-migrate/index.js";
52
54
  export const CORE_PACKAGE = "@deftai/directive-core";
53
55
  /** Returns identifying metadata for the core engine package. */
54
56
  export function engineInfo() {
@@ -14,6 +14,7 @@ import { prunePythonArtifactsFromDeposit } from "../deposit/python-free.js";
14
14
  import { resolveInstalledContentRoot } from "../deposit/resolve-content.js";
15
15
  import { manifestTagToVersion, parseInstallManifest } from "../doctor/manifest.js";
16
16
  import { readCorePackageVersion } from "../engine-version.js";
17
+ import { resolveLifecycleRoot } from "../layout/resolve.js";
17
18
  import { DEV_FALLBACK } from "../platform/constants.js";
18
19
  import { gitPorcelain } from "../story-ready/git.js";
19
20
  import { parseInitArgv } from "./init-deposit.js";
@@ -92,7 +93,7 @@ function syncBareVersionMarker(projectDir, version) {
92
93
  const normalized = normalizeVersion(version);
93
94
  if (!normalized || normalized === DEV_FALLBACK)
94
95
  return;
95
- const vbriefDir = join(projectDir, "vbrief");
96
+ const vbriefDir = resolveLifecycleRoot(projectDir);
96
97
  let targetDir = projectDir;
97
98
  try {
98
99
  if (statSync(vbriefDir).isDirectory())
@@ -10,6 +10,7 @@ import { mkdir, readdir, rm, stat, writeFile } from "node:fs/promises";
10
10
  import { platform } from "node:os";
11
11
  import { dirname, join, relative } from "node:path";
12
12
  import { copyTree } from "../deposit/copy-tree.js";
13
+ import { resolveLifecycleRoot } from "../layout/resolve.js";
13
14
  import { agentsRefreshPlan } from "../platform/agents-md.js";
14
15
  export const CANONICAL_INSTALL_ROOT = ".deft/core";
15
16
  export const CORE_GLOB = ".deft/core/**";
@@ -262,7 +263,7 @@ function vbriefLifecycleDirsPresent(consumerVbrief) {
262
263
  });
263
264
  }
264
265
  export async function writeConsumerVbrief(projectDir, deftDir, io) {
265
- const consumerVbrief = join(projectDir, "vbrief");
266
+ const consumerVbrief = resolveLifecycleRoot(projectDir);
266
267
  const schemasDst = join(consumerVbrief, "schemas");
267
268
  const vbriefMdDst = join(consumerVbrief, "vbrief.md");
268
269
  const schemasPresent = existsSync(schemasDst) && statSync(schemasDst).isDirectory();
@@ -1,6 +1,8 @@
1
1
  export interface InstallUpgradeArgs {
2
2
  readonly projectRoot: string;
3
3
  readonly frameworkRoot: string;
4
+ readonly migrate?: boolean;
5
+ readonly force?: boolean;
4
6
  }
5
7
  export interface InstallUpgradeIo {
6
8
  writeOut: (text: string) => void;
@@ -3,10 +3,12 @@ import { join, relative, resolve } from "node:path";
3
3
  import { locateManifest, manifestTagToVersion, parseInstallManifest } from "../doctor/manifest.js";
4
4
  import { readCorePackageVersion } from "../engine-version.js";
5
5
  import { buildInstallManifestText, CANONICAL_INSTALL_ROOT, } from "../init-deposit/scaffold.js";
6
+ import { resolveLifecycleRoot } from "../layout/resolve.js";
6
7
  import { agentsRefreshPlan } from "../platform/agents-md.js";
7
8
  import { DEV_FALLBACK } from "../platform/constants.js";
8
9
  import { resolveVersion } from "../platform/resolve-version.js";
9
10
  import { detectPreCutoverLegacy, frozenPreCutoverMigrationGuidance, } from "../vbrief-validate/precutover.js";
11
+ import { detectLegacyVbriefLayout, emitXbriefMigration, isPatchOnlyUpgrade, renderXbriefMigrationLine, runXbriefMigration, } from "../xbrief-migrate/index.js";
10
12
  const ENGINE_PACKAGE_FALLBACK = "0.0.0";
11
13
  /**
12
14
  * Resolve the framework version for an upgrade (#2053).
@@ -71,7 +73,10 @@ function readManagedByAt(installRoot) {
71
73
  }
72
74
  }
73
75
  function versionMarkerPaths(projectRoot) {
74
- return [join(projectRoot, "vbrief", ".deft-version"), join(projectRoot, ".deft-version")];
76
+ return [
77
+ join(resolveLifecycleRoot(projectRoot), ".deft-version"),
78
+ join(projectRoot, ".deft-version"),
79
+ ];
75
80
  }
76
81
  function readVersionMarker(projectRoot) {
77
82
  for (const candidate of versionMarkerPaths(projectRoot)) {
@@ -173,6 +178,24 @@ function runAgentsRefresh(projectRoot, frameworkRoot, io) {
173
178
  io.writeOut(`AGENTS.md updated (state=${state}).\n`);
174
179
  return 0;
175
180
  }
181
+ function handleLegacyXbriefLayout(args, io, recorded, normalizedVersion) {
182
+ const detection = detectLegacyVbriefLayout(args.projectRoot);
183
+ if (!detection.legacyLayout) {
184
+ return 0;
185
+ }
186
+ io.writeOut(`${renderXbriefMigrationLine(args.projectRoot)}\n`);
187
+ const patchInert = isPatchOnlyUpgrade(recorded, normalizedVersion);
188
+ if (!args.migrate || patchInert) {
189
+ return 0;
190
+ }
191
+ const outcome = runXbriefMigration({
192
+ projectRoot: args.projectRoot,
193
+ frameworkRoot: args.frameworkRoot,
194
+ force: args.force,
195
+ }, io);
196
+ const code = emitXbriefMigration(outcome, io, { projectRoot: args.projectRoot });
197
+ return code;
198
+ }
176
199
  /** Port of ``run upgrade`` / ``task install:upgrade`` for the consumer task surface (#1061 / #2022). */
177
200
  export function runInstallUpgrade(args, io) {
178
201
  const projectRoot = resolve(args.projectRoot);
@@ -189,13 +212,16 @@ export function runInstallUpgrade(args, io) {
189
212
  const recorded = readVersionMarker(projectRoot);
190
213
  if (recorded === normalizedVersion) {
191
214
  io.writeOut(`Project already at ${normalizedVersion}. Nothing to do.\n`);
215
+ const xbriefCode = handleLegacyXbriefLayout(args, io, recorded, normalizedVersion);
216
+ if (xbriefCode !== 0)
217
+ return xbriefCode;
192
218
  return runAgentsRefresh(projectRoot, agentsRoot, io);
193
219
  }
194
220
  const legacy = detectPreCutoverLegacy(projectRoot);
195
221
  if (legacy.length > 0) {
196
222
  io.writeOut(`Pre-v0.20 document model detected (${legacy.join(", ")}). ${frozenPreCutoverMigrationGuidance()}\n`);
197
223
  }
198
- const vbriefDir = join(projectRoot, "vbrief");
224
+ const vbriefDir = resolveLifecycleRoot(projectRoot);
199
225
  const targetDir = existsSync(vbriefDir) && statSync(vbriefDir).isDirectory() ? vbriefDir : projectRoot;
200
226
  writeVersionMarker(targetDir, normalizedVersion);
201
227
  const writtenManifestPath = installRoot !== null
@@ -217,6 +243,9 @@ export function runInstallUpgrade(args, io) {
217
243
  io.writeOut(`Updated .deft-version from ${recorded} to ${normalizedVersion}.\n`);
218
244
  }
219
245
  io.writeOut(`If legacy SPECIFICATION.md or PROJECT.md content remains, see UPGRADING.md § Frozen pre-v0.20 document-model migration (#2068).\n`);
246
+ const xbriefCode = handleLegacyXbriefLayout(args, io, recorded, normalizedVersion);
247
+ if (xbriefCode !== 0)
248
+ return xbriefCode;
220
249
  return runAgentsRefresh(projectRoot, agentsRoot, io);
221
250
  }
222
251
  //# sourceMappingURL=index.js.map
@@ -1,5 +1,5 @@
1
1
  import type { ScmCallFn } from "./reconcile-issues.js";
2
- export declare const GITHUB_ISSUE_REF_TYPE = "x-vbrief/github-issue";
2
+ export declare const GITHUB_ISSUE_REF_TYPE = "x-xbrief/github-issue";
3
3
  export declare const EXTERNAL_TRUST_LEVEL = "external";
4
4
  export declare class IssueEmitError extends Error {
5
5
  constructor(message: string);
@@ -1,10 +1,11 @@
1
1
  import { globSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
2
  import { tmpdir } from "node:os";
3
3
  import { join, relative, resolve } from "node:path";
4
+ import { referenceTypeMatches } from "@deftai/directive-types";
4
5
  import { call } from "../scm/call.js";
5
6
  import { resolveProjectRoot } from "../scope/project-context.js";
6
7
  import { resolveProjectRepo } from "../slice/project-context.js";
7
- export const GITHUB_ISSUE_REF_TYPE = "x-vbrief/github-issue";
8
+ export const GITHUB_ISSUE_REF_TYPE = "x-xbrief/github-issue";
8
9
  export const EXTERNAL_TRUST_LEVEL = "external";
9
10
  const ISSUE_URL_PATTERN = /https?:\/\/\S+?\/issues\/\d+/;
10
11
  export class IssueEmitError extends Error {
@@ -44,7 +45,7 @@ export function existingGithubIssueRef(data) {
44
45
  for (const ref of refs) {
45
46
  if (ref !== null && typeof ref === "object" && !Array.isArray(ref)) {
46
47
  const obj = ref;
47
- if (obj.type === GITHUB_ISSUE_REF_TYPE) {
48
+ if (referenceTypeMatches(String(obj.type ?? ""), "github-issue")) {
48
49
  const uri = obj.uri ?? obj.url;
49
50
  return typeof uri === "string" && uri.length > 0 ? uri : "";
50
51
  }