@deftai/directive-core 0.65.0 → 0.66.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) 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.d.ts +24 -0
  23. package/dist/intake/issue-ingest.js +113 -16
  24. package/dist/intake/reconcile-issues.js +11 -4
  25. package/dist/layout/index.d.ts +2 -0
  26. package/dist/layout/index.js +2 -0
  27. package/dist/layout/resolve.d.ts +87 -0
  28. package/dist/layout/resolve.js +159 -0
  29. package/dist/lifecycle/lifecycle-hygiene.d.ts +1 -1
  30. package/dist/lifecycle/lifecycle-hygiene.js +8 -5
  31. package/dist/orchestration/judgment-policy.js +4 -6
  32. package/dist/orchestration/verify-judgment-gates.d.ts +1 -0
  33. package/dist/orchestration/verify-judgment-gates.js +4 -2
  34. package/dist/policy/autonomy.js +4 -6
  35. package/dist/policy/capacity.js +4 -6
  36. package/dist/policy/decisions.d.ts +1 -0
  37. package/dist/policy/decisions.js +4 -2
  38. package/dist/policy/index.d.ts +1 -0
  39. package/dist/policy/index.js +3 -1
  40. package/dist/policy/plan-extensions.d.ts +35 -0
  41. package/dist/policy/plan-extensions.js +57 -0
  42. package/dist/policy/resolve.d.ts +6 -2
  43. package/dist/policy/resolve.js +18 -8
  44. package/dist/policy/wip.js +6 -4
  45. package/dist/preflight-cache/evaluate.js +8 -5
  46. package/dist/release/native-steps.js +4 -3
  47. package/dist/render/framework-commands.js +2 -1
  48. package/dist/render/prd-render.js +2 -2
  49. package/dist/render/project-render.js +23 -5
  50. package/dist/render/roadmap-render.js +36 -8
  51. package/dist/render/scope-outlook.js +3 -2
  52. package/dist/scope/audit-log.js +3 -2
  53. package/dist/scope/capacity-stamp.js +2 -1
  54. package/dist/scope/decompose.js +6 -4
  55. package/dist/scope/decomposed-refs.js +2 -1
  56. package/dist/scope/demote.js +5 -4
  57. package/dist/scope/project-context.js +2 -1
  58. package/dist/scope/project-definition-sync.js +4 -3
  59. package/dist/scope/transition.js +6 -2
  60. package/dist/scope/undo.js +2 -1
  61. package/dist/scope/vbrief-ref.js +5 -3
  62. package/dist/session/resume-conditions.js +2 -1
  63. package/dist/session/ritual-sentinel.js +2 -1
  64. package/dist/session/staleness.js +2 -1
  65. package/dist/slice/record.js +4 -4
  66. package/dist/spec-authority/resolver.js +5 -5
  67. package/dist/swarm/complete-cohort.js +5 -4
  68. package/dist/swarm/launch.js +6 -7
  69. package/dist/swarm/readiness.js +22 -20
  70. package/dist/swarm/subagent-backend.js +2 -5
  71. package/dist/swarm/verify-review-clean.js +2 -1
  72. package/dist/triage/actions/candidates-log.d.ts +3 -0
  73. package/dist/triage/actions/candidates-log.js +8 -2
  74. package/dist/triage/bootstrap/gitignore.js +22 -13
  75. package/dist/triage/bootstrap/index.js +7 -4
  76. package/dist/triage/bulk/index.js +2 -1
  77. package/dist/triage/classify/index.js +13 -11
  78. package/dist/triage/queue/audit.js +3 -3
  79. package/dist/triage/queue/cache.js +3 -2
  80. package/dist/triage/queue/ranking-labels.js +2 -1
  81. package/dist/triage/queue/scope-walk.js +8 -6
  82. package/dist/triage/reconcile/audit.js +2 -1
  83. package/dist/triage/reconcile/parse-uri.js +2 -1
  84. package/dist/triage/reconcile/reconcile.js +8 -7
  85. package/dist/triage/refresh/extract.js +4 -2
  86. package/dist/triage/refresh/refresh.js +3 -2
  87. package/dist/triage/scope/mutations-core.js +13 -9
  88. package/dist/triage/scope/renderers.js +5 -3
  89. package/dist/triage/scope/resolve.js +6 -5
  90. package/dist/triage/scope/validate.js +3 -6
  91. package/dist/triage/scope-drift/add-ignore.js +4 -2
  92. package/dist/triage/scope-drift/scope-rules.js +3 -2
  93. package/dist/triage/subscribe/index.js +13 -7
  94. package/dist/triage/summary/index.js +7 -5
  95. package/dist/triage/summary/reconcilable.js +6 -4
  96. package/dist/triage/summary/scope-drift.js +2 -1
  97. package/dist/triage/welcome/prior-state.js +9 -9
  98. package/dist/triage/welcome/summary.js +9 -8
  99. package/dist/triage/welcome/writers.js +12 -10
  100. package/dist/validate-content/capacity-policy.js +5 -4
  101. package/dist/validate-content/capacity-show.js +5 -3
  102. package/dist/validate-content/filename.d.ts +1 -1
  103. package/dist/validate-content/filename.js +5 -4
  104. package/dist/validate-content/validate-strategy-output.js +14 -5
  105. package/dist/vbrief-build/build.js +1 -1
  106. package/dist/vbrief-build/constants.js +7 -0
  107. package/dist/vbrief-build/speckit.js +3 -2
  108. package/dist/vbrief-reconcile/graph.js +3 -2
  109. package/dist/vbrief-reconcile/labels.js +4 -3
  110. package/dist/vbrief-reconcile/reconciliation.js +5 -1
  111. package/dist/vbrief-reconcile/swarm-deps.js +6 -5
  112. package/dist/vbrief-reconcile/umbrellas.d.ts +1 -1
  113. package/dist/vbrief-reconcile/umbrellas.js +9 -7
  114. package/dist/vbrief-validate/conformance.d.ts +1 -2
  115. package/dist/vbrief-validate/conformance.js +29 -14
  116. package/dist/vbrief-validate/constants.d.ts +18 -1
  117. package/dist/vbrief-validate/constants.js +32 -3
  118. package/dist/vbrief-validate/epic-links.js +3 -2
  119. package/dist/vbrief-validate/filename.js +5 -3
  120. package/dist/vbrief-validate/index.d.ts +3 -1
  121. package/dist/vbrief-validate/index.js +2 -1
  122. package/dist/vbrief-validate/main.js +17 -6
  123. package/dist/vbrief-validate/origin.js +1 -1
  124. package/dist/vbrief-validate/paths.js +2 -1
  125. package/dist/vbrief-validate/plan-hooks.js +4 -3
  126. package/dist/vbrief-validate/registry-status.d.ts +1 -0
  127. package/dist/vbrief-validate/registry-status.js +3 -1
  128. package/dist/vbrief-validate/roundtrip.d.ts +36 -0
  129. package/dist/vbrief-validate/roundtrip.js +168 -0
  130. package/dist/vbrief-validate/schema.d.ts +1 -1
  131. package/dist/vbrief-validate/schema.js +62 -19
  132. package/dist/vbrief-validate/validate-all.js +12 -4
  133. package/dist/verify-source/code-structure-validate.js +8 -4
  134. package/dist/verify-source/contract-drift.d.ts +7 -3
  135. package/dist/verify-source/contract-drift.js +40 -22
  136. package/dist/xbrief-migrate/constants.d.ts +13 -0
  137. package/dist/xbrief-migrate/constants.js +14 -0
  138. package/dist/xbrief-migrate/detect.d.ts +11 -0
  139. package/dist/xbrief-migrate/detect.js +81 -0
  140. package/dist/xbrief-migrate/drift-gate.d.ts +56 -0
  141. package/dist/xbrief-migrate/drift-gate.js +205 -0
  142. package/dist/xbrief-migrate/index.d.ts +7 -0
  143. package/dist/xbrief-migrate/index.js +7 -0
  144. package/dist/xbrief-migrate/migrate-project.d.ts +33 -0
  145. package/dist/xbrief-migrate/migrate-project.js +189 -0
  146. package/dist/xbrief-migrate/signpost.d.ts +5 -0
  147. package/dist/xbrief-migrate/signpost.js +19 -0
  148. package/dist/xbrief-migrate/transforms.d.ts +46 -0
  149. package/dist/xbrief-migrate/transforms.js +182 -0
  150. package/dist/xbrief-migrate/version.d.ts +8 -0
  151. package/dist/xbrief-migrate/version.js +25 -0
  152. package/package.json +11 -3
@@ -1,5 +1,6 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { join, resolve } from "node:path";
3
+ import { readPlanPolicy } from "../policy/plan-extensions.js";
3
4
  import { PROJECT_DEFINITION_REL_PATH } from "../policy/resolve.js";
4
5
  function resolveDefaultCapacityBucket(projectRoot) {
5
6
  try {
@@ -10,7 +11,7 @@ function resolveDefaultCapacityBucket(projectRoot) {
10
11
  if (typeof plan !== "object" || plan === null || Array.isArray(plan)) {
11
12
  return "";
12
13
  }
13
- const policy = plan.policy;
14
+ const policy = readPlanPolicy(plan);
14
15
  if (typeof policy !== "object" || policy === null || Array.isArray(policy)) {
15
16
  return "";
16
17
  }
@@ -8,6 +8,8 @@
8
8
  */
9
9
  import { accessSync, constants, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
10
10
  import { basename, dirname, isAbsolute, join, resolve } from "node:path";
11
+ import { referenceTypeMatches } from "@deftai/directive-types";
12
+ import { hasArtifactSuffix, resolveLifecycleRoot } from "../layout/resolve.js";
11
13
  import { referenceWithDefaultTrust, slugify } from "../vbrief-build/build.js";
12
14
  import { EMITTED_VBRIEF_VERSION } from "../vbrief-build/constants.js";
13
15
  import { formatVbriefJson } from "./vbrief-json.js";
@@ -398,7 +400,7 @@ export function storyQualityIssues(opts) {
398
400
  // Path helpers
399
401
  // ---------------------------------------------------------------------------
400
402
  function vbriefDir(projectRoot) {
401
- return join(resolve(projectRoot), "vbrief");
403
+ return resolveLifecycleRoot(projectRoot);
402
404
  }
403
405
  function relToVbrief(vbriefDirPath, path) {
404
406
  const resolvedPath = resolve(path);
@@ -507,7 +509,7 @@ function storyHasTraces(story, items, sw) {
507
509
  if (Array.isArray(refs)) {
508
510
  for (const ref of refs) {
509
511
  if (typeof ref === "object" && ref !== null && !Array.isArray(ref)) {
510
- if (ref.type === "x-vbrief/spec-section")
512
+ if (referenceTypeMatches(String(ref.type ?? ""), "spec-section"))
511
513
  return true;
512
514
  }
513
515
  }
@@ -752,7 +754,7 @@ function storyNarratives(story) {
752
754
  }
753
755
  function childFilename(story, stId, title, date) {
754
756
  const fn = story.filename;
755
- if (typeof fn === "string" && fn.endsWith(".vbrief.json"))
757
+ if (typeof fn === "string" && hasArtifactSuffix(fn))
756
758
  return fn;
757
759
  const sl = slugify(title) || slugify(stId) || "story";
758
760
  return `${date}-${sl}.vbrief.json`;
@@ -891,7 +893,7 @@ export function applyDecomposition(opts) {
891
893
  for (const { target, title } of childPaths) {
892
894
  references.push(referenceWithDefaultTrust({
893
895
  uri: relToVbrief(vbriefDirPath, target),
894
- type: "x-vbrief/plan",
896
+ type: "x-xbrief/plan",
895
897
  title,
896
898
  }));
897
899
  }
@@ -1,5 +1,6 @@
1
1
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { dirname, relative, resolve } from "node:path";
3
+ import { referenceTypeMatches } from "@deftai/directive-types";
3
4
  import { formatVbriefJson } from "./vbrief-json.js";
4
5
  import { collectChildUris, collectPlanRefs, resolveVbriefRef } from "./vbrief-ref.js";
5
6
  function rewriteOnePlanRef(value, oldParentResolved, newParentRel, vbriefDir) {
@@ -36,7 +37,7 @@ function rewriteParentChildReference(parentPath, oldChildResolved, newChildRel,
36
37
  continue;
37
38
  }
38
39
  const r = ref;
39
- if (r.type !== "x-vbrief/plan") {
40
+ if (!referenceTypeMatches(String(r.type ?? ""), "plan")) {
40
41
  continue;
41
42
  }
42
43
  const resolved = resolveVbriefRef(r.uri, vbriefDir);
@@ -1,5 +1,6 @@
1
1
  import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, statSync, writeFileSync, } from "node:fs";
2
2
  import { dirname, isAbsolute, join, resolve } from "node:path";
3
+ import { hasArtifactSuffix, resolveLifecycleFolder } from "../layout/resolve.js";
3
4
  import { stripTrailingPathSeparators } from "../text/redos-safe.js";
4
5
  import { append, canonicalLogPath, latestForPath, newDecisionId } from "./audit-log.js";
5
6
  import { resolveProjectRoot } from "./project-context.js";
@@ -35,10 +36,10 @@ export function demoteOne(filePath, projectRoot, reason, options = {}) {
35
36
  return { ok: false, message: `File not found: ${resolved}`, auditEntry: null };
36
37
  }
37
38
  const basename = resolved.split(/[/\\]/).pop() ?? "";
38
- if (!basename.endsWith(".vbrief.json")) {
39
+ if (!hasArtifactSuffix(basename)) {
39
40
  return {
40
41
  ok: false,
41
- message: `Not a vBRIEF file (expected .vbrief.json): ${basename}`,
42
+ message: `Not a vBRIEF file (expected .vbrief.json or .xbrief.json): ${basename}`,
42
43
  auditEntry: null,
43
44
  };
44
45
  }
@@ -114,7 +115,7 @@ export function batchDemote(projectRoot, olderThanDays, options = {}) {
114
115
  throw new Error(`--older-than-days must be >= 0, got ${olderThanDays}`);
115
116
  }
116
117
  const now = options.now ?? new Date();
117
- const pendingDir = join(resolve(projectRoot), "vbrief", SOURCE_FOLDER);
118
+ const pendingDir = resolveLifecycleFolder(projectRoot, SOURCE_FOLDER);
118
119
  if (!existsSync(pendingDir)) {
119
120
  return [0, [], []];
120
121
  }
@@ -124,7 +125,7 @@ export function batchDemote(projectRoot, olderThanDays, options = {}) {
124
125
  const skipped = [];
125
126
  let demoted = 0;
126
127
  const files = readdirSync(pendingDir)
127
- .filter((name) => name.endsWith(".vbrief.json"))
128
+ .filter((name) => hasArtifactSuffix(name))
128
129
  .sort();
129
130
  for (const name of files) {
130
131
  const candidate = join(pendingDir, name);
@@ -1,6 +1,7 @@
1
1
  import { existsSync, statSync } from "node:fs";
2
2
  import { dirname, join, resolve } from "node:path";
3
- const PROJECT_ROOT_SENTINELS = ["vbrief", ".git"];
3
+ // Layout-aware (#2109 part 1): an xbrief-only tree is also a valid project root.
4
+ const PROJECT_ROOT_SENTINELS = ["xbrief", "vbrief", ".git"];
4
5
  function isProjectRoot(candidate) {
5
6
  return PROJECT_ROOT_SENTINELS.some((sentinel) => existsSync(join(candidate, sentinel)));
6
7
  }
@@ -1,5 +1,6 @@
1
1
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { dirname, join, resolve } from "node:path";
3
+ import { referenceTypeMatches } from "@deftai/directive-types";
3
4
  import { PROJECT_DEFINITION_REL_PATH } from "../policy/resolve.js";
4
5
  import { formatVbriefJson } from "./vbrief-json.js";
5
6
  import { relativeToVbrief, resolveVbriefRef, scopeIdsForFilename } from "./vbrief-ref.js";
@@ -8,7 +9,7 @@ function rewriteProjectDefinitionPlanReference(ref, oldResolved, newRel, vbriefR
8
9
  return false;
9
10
  }
10
11
  const r = ref;
11
- if (r.type !== "x-vbrief/plan") {
12
+ if (!referenceTypeMatches(String(r.type ?? ""), "plan")) {
12
13
  return false;
13
14
  }
14
15
  const resolved = resolveVbriefRef(r.uri, vbriefRoot);
@@ -41,7 +42,7 @@ function projectItemReferencesScope(item, oldResolved, newResolved, vbriefRoot)
41
42
  continue;
42
43
  }
43
44
  const r = ref;
44
- if (r.type !== "x-vbrief/plan") {
45
+ if (!referenceTypeMatches(String(r.type ?? ""), "plan")) {
45
46
  continue;
46
47
  }
47
48
  const resolved = resolveVbriefRef(r.uri, vbriefRoot);
@@ -58,7 +59,7 @@ function projectItemReferencesScope(item, oldResolved, newResolved, vbriefRoot)
58
59
  continue;
59
60
  }
60
61
  const r = ref;
61
- if (r.type !== "x-vbrief/plan") {
62
+ if (!referenceTypeMatches(String(r.type ?? ""), "plan")) {
62
63
  continue;
63
64
  }
64
65
  const resolved = resolveVbriefRef(r.uri, vbriefRoot);
@@ -1,5 +1,6 @@
1
1
  import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
2
2
  import { dirname, join, resolve } from "node:path";
3
+ import { hasArtifactSuffix } from "../layout/resolve.js";
3
4
  import { append, canonicalLogPath, newDecisionId } from "./audit-log.js";
4
5
  import { stampCompletionMetadata } from "./capacity-stamp.js";
5
6
  import { LIFECYCLE_FOLDERS, MOVE_LABELS, STATUS_PRECONDITIONS, STAY_LABELS, TRANSITIONS, } from "./constants.js";
@@ -17,8 +18,11 @@ export function runTransition(action, filePath, now = new Date()) {
17
18
  return { ok: false, message: `File not found: ${resolvedPath}` };
18
19
  }
19
20
  const basename = resolvedPath.split(/[/\\]/).pop() ?? "";
20
- if (!basename.endsWith(".vbrief.json")) {
21
- return { ok: false, message: `Not a vBRIEF file (expected .vbrief.json): ${basename}` };
21
+ if (!hasArtifactSuffix(basename)) {
22
+ return {
23
+ ok: false,
24
+ message: `Not a vBRIEF file (expected .vbrief.json or .xbrief.json): ${basename}`,
25
+ };
22
26
  }
23
27
  const currentFolder = detectLifecycleFolder(resolvedPath);
24
28
  if (currentFolder === null) {
@@ -1,10 +1,11 @@
1
1
  import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
2
2
  import { basename, join, relative, resolve } from "node:path";
3
+ import { resolveLifecycleRoot } from "../layout/resolve.js";
3
4
  import { append, canonicalLogPath, newDecisionId, readAll } from "./audit-log.js";
4
5
  import { REVERSIBLE_ACTIONS, TERMINAL_ACTIONS } from "./constants.js";
5
6
  import { formatVbriefJson, utcNowIso } from "./vbrief-json.js";
6
7
  function vbriefRoot(projectRoot) {
7
- return join(resolve(projectRoot), "vbrief");
8
+ return resolveLifecycleRoot(projectRoot);
8
9
  }
9
10
  function absForEntryPath(projectRoot, vbriefPath) {
10
11
  return resolve(projectRoot, vbriefPath);
@@ -1,4 +1,6 @@
1
1
  import { resolve } from "node:path";
2
+ import { referenceTypeMatches } from "@deftai/directive-types";
3
+ import { hasArtifactSuffix, stripArtifactSuffix } from "../layout/resolve.js";
2
4
  /** Resolve a vBRIEF reference URI to an absolute path, or null. */
3
5
  export function resolveVbriefRef(uri, vbriefDir) {
4
6
  if (typeof uri !== "string" || uri.length === 0) {
@@ -48,7 +50,7 @@ export function collectChildUris(plan) {
48
50
  continue;
49
51
  }
50
52
  const r = ref;
51
- if (r.type !== "x-vbrief/plan") {
53
+ if (!referenceTypeMatches(String(r.type ?? ""), "plan")) {
52
54
  continue;
53
55
  }
54
56
  const uri = r.uri;
@@ -61,8 +63,8 @@ export function collectChildUris(plan) {
61
63
  /** Return registry IDs that may name a vBRIEF filename. */
62
64
  export function scopeIdsForFilename(filename) {
63
65
  let fullId;
64
- if (filename.endsWith(".vbrief.json")) {
65
- fullId = filename.slice(0, -".vbrief.json".length);
66
+ if (hasArtifactSuffix(filename)) {
67
+ fullId = stripArtifactSuffix(filename);
66
68
  }
67
69
  else {
68
70
  fullId = filename.replace(/\.[^.]+$/, "");
@@ -1,6 +1,7 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
3
3
  import { join, resolve } from "node:path";
4
+ import { hasArtifactSuffix } from "../layout/resolve.js";
4
5
  export const RESUME_ELIGIBLE_DECISION = "resume-eligible";
5
6
  export const EVALUATOR_ACTOR = "agent:resume-evaluator";
6
7
  export const CACHE_DIR_NAME = ".deft-cache";
@@ -270,7 +271,7 @@ function countPending(projectRoot) {
270
271
  for (const name of readdirSync(folder)) {
271
272
  const full = join(folder, name);
272
273
  try {
273
- if (statSync(full).isFile() && name.endsWith(".vbrief.json")) {
274
+ if (statSync(full).isFile() && hasArtifactSuffix(name)) {
274
275
  count += 1;
275
276
  }
276
277
  }
@@ -1,5 +1,6 @@
1
1
  import { closeSync, existsSync, fdatasyncSync, mkdirSync, openSync, readdirSync, readFileSync, renameSync, statSync, writeSync, } from "node:fs";
2
2
  import { join, relative, resolve } from "node:path";
3
+ import { hasArtifactSuffix } from "../layout/resolve.js";
3
4
  import { stableJson } from "./json.js";
4
5
  import { parseTimestamp, timestampIso } from "./time.js";
5
6
  export const SCHEMA_VERSION = 1;
@@ -307,7 +308,7 @@ export function detectLatestActiveVbrief(projectRoot) {
307
308
  }
308
309
  const candidates = [];
309
310
  for (const name of children) {
310
- if (!name.endsWith(".vbrief.json")) {
311
+ if (!hasArtifactSuffix(name)) {
311
312
  continue;
312
313
  }
313
314
  const full = join(activeDir, name);
@@ -1,3 +1,4 @@
1
+ import { readPlanPolicy } from "../policy/plan-extensions.js";
1
2
  import { loadProjectDefinition } from "../policy/resolve.js";
2
3
  const DEFAULT_SESSION_RITUAL_STALENESS_HOURS = 4;
3
4
  function pythonTypeName(value) {
@@ -59,7 +60,7 @@ export function resolveSessionRitualStalenessHours(projectRoot) {
59
60
  error: "PROJECT-DEFINITION 'plan' is not an object",
60
61
  };
61
62
  }
62
- const policyBlock = plan.policy;
63
+ const policyBlock = readPlanPolicy(plan);
63
64
  if (typeof policyBlock !== "object" ||
64
65
  policyBlock === null ||
65
66
  Array.isArray(policyBlock) ||
@@ -1,7 +1,7 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { appendFileSync, closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, } from "node:fs";
3
- import { dirname, join, resolve } from "node:path";
4
- import { DEFAULT_SLICES_LOG_REL_PATH } from "./constants.js";
3
+ import { dirname, resolve } from "node:path";
4
+ import { resolveEvalPath } from "../layout/resolve.js";
5
5
  import { pythonJsonStringify } from "./json.js";
6
6
  import { withAppendLock } from "./lock.js";
7
7
  import { validateRecord } from "./validate.js";
@@ -16,7 +16,7 @@ const defaultDeps = {
16
16
  withLock: withAppendLock,
17
17
  };
18
18
  function resolvePath(path) {
19
- return path !== undefined ? resolve(path) : resolve(DEFAULT_SLICES_LOG_REL_PATH);
19
+ return path !== undefined ? resolve(path) : resolveEvalPath(process.cwd(), "slices.jsonl");
20
20
  }
21
21
  export function newSliceId() {
22
22
  return randomUUID();
@@ -134,6 +134,6 @@ export function findByUmbrella(umbrella, options = {}) {
134
134
  return readAll(options).filter((record) => record.umbrella === umbrella);
135
135
  }
136
136
  export function slicesPath(projectRoot) {
137
- return join(resolve(projectRoot), DEFAULT_SLICES_LOG_REL_PATH);
137
+ return resolveEvalPath(projectRoot, "slices.jsonl");
138
138
  }
139
139
  //# sourceMappingURL=record.js.map
@@ -1,16 +1,16 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
+ import { resolveLifecycleLayout, resolveLifecycleRoot, resolveProjectDefinitionPath, } from "../layout/resolve.js";
3
4
  import { SPEC_RENDER_BANNER } from "../render/constants.js";
4
5
  import { EXPORT_SPEC_PD_BANNER, GENERATED_SPEC_PURPOSE, GENERATED_SPEC_SOURCE_PD, GENERATED_SPEC_SOURCE_SPEC, } from "./constants.js";
5
- const SPEC_REL = join("vbrief", "specification.vbrief.json");
6
- const PD_REL = join("vbrief", "PROJECT-DEFINITION.vbrief.json");
7
6
  export function resolveSpecAuthority(projectRoot) {
8
7
  const root = projectRoot;
9
- const vbriefDir = join(root, "vbrief");
10
- const projectDefPath = join(root, PD_REL);
8
+ const layout = resolveLifecycleLayout(root);
9
+ const vbriefDir = resolveLifecycleRoot(root);
10
+ const projectDefPath = resolveProjectDefinitionPath(root);
11
11
  if (!existsSync(projectDefPath))
12
12
  return null;
13
- const specPath = join(root, SPEC_REL);
13
+ const specPath = join(vbriefDir, `specification${layout.artifactSuffix}`);
14
14
  const hasSpec = existsSync(specPath);
15
15
  const kind = hasSpec ? "full-spec" : "greenfield";
16
16
  const banner = kind === "full-spec" ? SPEC_RENDER_BANNER : EXPORT_SPEC_PD_BANNER;
@@ -1,5 +1,6 @@
1
1
  import { existsSync, readdirSync, readFileSync } from "node:fs";
2
2
  import { join, resolve } from "node:path";
3
+ import { hasArtifactSuffix, resolveLifecycleRoot } from "../layout/resolve.js";
3
4
  import { detectLifecycleFolder } from "../scope/decomposed-refs.js";
4
5
  import { runTransition } from "../scope/transition.js";
5
6
  import { collectChildUris, collectPlanRefs, resolveVbriefRef } from "../scope/vbrief-ref.js";
@@ -41,7 +42,7 @@ function globResolve(pattern, projectRoot) {
41
42
  return [];
42
43
  }
43
44
  return readdirSync(dir)
44
- .filter((name) => name.endsWith(".vbrief.json") || glob === name)
45
+ .filter((name) => hasArtifactSuffix(name) || glob === name)
45
46
  .map((name) => resolve(dir, name))
46
47
  .filter((p) => existsSync(p));
47
48
  }
@@ -229,7 +230,7 @@ function completeParent(parentPath, vbriefDir, projectRoot, settled, dryRun) {
229
230
  };
230
231
  }
231
232
  export function sweepCohort(storyPaths, projectRoot, dryRun) {
232
- const vbriefDir = join(projectRoot, "vbrief");
233
+ const vbriefDir = resolveLifecycleRoot(projectRoot);
233
234
  const result = {
234
235
  project_root: resolve(projectRoot),
235
236
  dry_run: dryRun,
@@ -243,7 +244,7 @@ export function sweepCohort(storyPaths, projectRoot, dryRun) {
243
244
  const termDir = join(vbriefDir, term);
244
245
  if (existsSync(termDir)) {
245
246
  for (const name of readdirSync(termDir)) {
246
- if (name.endsWith(".vbrief.json")) {
247
+ if (hasArtifactSuffix(name)) {
247
248
  settled.add(resolve(join(termDir, name)));
248
249
  }
249
250
  }
@@ -358,7 +359,7 @@ export function completeCohort(args) {
358
359
  stderr: `Error: project root does not exist: ${projectRoot}\n`,
359
360
  };
360
361
  }
361
- if (!existsSync(join(projectRoot, "vbrief"))) {
362
+ if (!existsSync(resolveLifecycleRoot(projectRoot))) {
362
363
  return {
363
364
  exitCode: 2,
364
365
  stdout: "",
@@ -2,6 +2,7 @@ import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { basename, join, resolve } from "node:path";
3
3
  import { inferGithubAuthMode } from "../intake/github-auth-modes.js";
4
4
  import { getPlatformCapabilities } from "../intake/platform-capabilities.js";
5
+ import { hasArtifactSuffix, resolveLifecycleFolder, stripArtifactSuffix, } from "../layout/resolve.js";
5
6
  import { evaluate as preflightEvaluate } from "../preflight/evaluate.js";
6
7
  import { issueNumbersFromPlan, scopeMetadataRank } from "../triage/queue/scope-walk.js";
7
8
  import { selectionOrderingKey } from "../triage/queue/selection.js";
@@ -45,9 +46,7 @@ function storyId(path, plan) {
45
46
  return value.trim();
46
47
  }
47
48
  const name = basename(path);
48
- return name.endsWith(".vbrief.json")
49
- ? name.slice(0, -".vbrief.json".length)
50
- : name.replace(/\.[^.]+$/, "");
49
+ return hasArtifactSuffix(name) ? stripArtifactSuffix(name) : name.replace(/\.[^.]+$/, "");
51
50
  }
52
51
  function extractHashNumbers(text) {
53
52
  const out = [];
@@ -128,13 +127,13 @@ function projectRel(projectRoot, path) {
128
127
  }
129
128
  }
130
129
  function indexActiveStories(projectRoot) {
131
- const activeDir = join(projectRoot, "vbrief", "active");
130
+ const activeDir = resolveLifecycleFolder(projectRoot, "active");
132
131
  const index = [];
133
132
  if (!existsSync(activeDir)) {
134
133
  return index;
135
134
  }
136
135
  for (const name of readdirSync(activeDir).sort()) {
137
- if (!name.endsWith(".vbrief.json")) {
136
+ if (!hasArtifactSuffix(name)) {
138
137
  continue;
139
138
  }
140
139
  const path = join(activeDir, name);
@@ -151,7 +150,7 @@ export function looksLikePath(token) {
151
150
  return (token.endsWith(".json") ||
152
151
  token.includes("/") ||
153
152
  token.includes("\\") ||
154
- (existsSync(token) && basename(token).endsWith(".vbrief.json")));
153
+ (existsSync(token) && hasArtifactSuffix(basename(token))));
155
154
  }
156
155
  function resolveOne(token, projectRoot, idMap, issueMap) {
157
156
  if (looksLikePath(token)) {
@@ -411,7 +410,7 @@ export function swarmLaunch(args) {
411
410
  stderr: "Error: no stories supplied. Pass --stories <ids|paths> and/or --paths <paths>.\n",
412
411
  };
413
412
  }
414
- if (!existsSync(join(projectRoot, "vbrief", "active"))) {
413
+ if (!existsSync(resolveLifecycleFolder(projectRoot, "active"))) {
415
414
  return {
416
415
  exitCode: EXIT_CONFIG_ERROR,
417
416
  stdout: "",
@@ -1,5 +1,7 @@
1
1
  import { existsSync, readdirSync, readFileSync } from "node:fs";
2
2
  import { basename, join, resolve } from "node:path";
3
+ import { referenceTypeMatches } from "@deftai/directive-types";
4
+ import { ARTIFACT_SUFFIXES, hasArtifactSuffix, LIFECYCLE_DIR_NAMES, resolveLifecycleLayout, resolveLifecycleRoot, stripArtifactSuffix, } from "../layout/resolve.js";
3
5
  import { acceptanceTextsFromItems, asStrList, deprecatedSubitemsIssues, itemHasTraces, itemsHaveAcceptance, missingRequiredSwarmFields, storyQualityIssues, } from "../vbrief-validation/story-quality.js";
4
6
  import { LIFECYCLE_FOLDERS, READY } from "./constants.js";
5
7
  function loadJson(path) {
@@ -24,7 +26,8 @@ function projectRel(projectRoot, path) {
24
26
  }
25
27
  }
26
28
  function expandPaths(projectRoot, patterns) {
27
- const usePatterns = patterns.length > 0 ? patterns : ["vbrief/active/*.vbrief.json"];
29
+ const layout = resolveLifecycleLayout(projectRoot);
30
+ const usePatterns = patterns.length > 0 ? patterns : [`${layout.artifactDir}/active/*${layout.artifactSuffix}`];
28
31
  const out = [];
29
32
  const seen = new Set();
30
33
  for (const pattern of usePatterns) {
@@ -33,13 +36,14 @@ function expandPaths(projectRoot, patterns) {
33
36
  const globPart = pattern.includes("/")
34
37
  ? pattern.slice(pattern.lastIndexOf("/") + 1)
35
38
  : pattern;
36
- if (existsSync(join(projectRoot, "vbrief", "active")) && pattern.startsWith("vbrief/")) {
37
- const activeDir = join(projectRoot, pattern.split("/")[0] ?? "vbrief", pattern.split("/")[1] ?? "active");
38
- if (existsSync(activeDir)) {
39
- for (const name of readdirSync(activeDir)) {
40
- if (matchGlob(name, globPart)) {
41
- addPath(join(activeDir, name), seen, out);
42
- }
39
+ const firstSeg = pattern.split("/")[0] ?? "";
40
+ const secondSeg = pattern.split("/")[1] ?? "";
41
+ const lifecyclePattern = LIFECYCLE_DIR_NAMES.includes(firstSeg);
42
+ if (lifecyclePattern && existsSync(join(projectRoot, firstSeg, secondSeg))) {
43
+ const activeDir = join(projectRoot, firstSeg, secondSeg);
44
+ for (const name of readdirSync(activeDir)) {
45
+ if (matchGlob(name, globPart)) {
46
+ addPath(join(activeDir, name), seen, out);
43
47
  }
44
48
  }
45
49
  }
@@ -69,8 +73,10 @@ function dirnamePattern(projectRoot, pattern) {
69
73
  return idx >= 0 ? join(projectRoot, pattern.slice(0, idx)) : projectRoot;
70
74
  }
71
75
  function matchGlob(name, glob) {
72
- if (glob === "*.vbrief.json") {
73
- return name.endsWith(".vbrief.json");
76
+ // Layout-aware (#2109 part 1): an artifact-suffix glob matches either suffix,
77
+ // so a mixed/either lifecycle tree resolves the same way.
78
+ if (ARTIFACT_SUFFIXES.some((suffix) => glob === `*${suffix}`)) {
79
+ return hasArtifactSuffix(name);
74
80
  }
75
81
  if (glob.startsWith("*.")) {
76
82
  return name.endsWith(glob.slice(1));
@@ -115,7 +121,7 @@ function hasChildPlanRefs(plan) {
115
121
  return refs.some((ref) => typeof ref === "object" &&
116
122
  ref !== null &&
117
123
  !Array.isArray(ref) &&
118
- ref.type === "x-vbrief/plan");
124
+ referenceTypeMatches(String(ref.type ?? ""), "plan"));
119
125
  }
120
126
  function looksLikePhase(path, plan) {
121
127
  const title = String(plan.title ?? "");
@@ -153,9 +159,7 @@ function storyId(path, plan) {
153
159
  return value.trim();
154
160
  }
155
161
  const name = basename(path);
156
- return name.endsWith(".vbrief.json")
157
- ? name.slice(0, -".vbrief.json".length)
158
- : name.replace(/\.[^.]+$/, "");
162
+ return hasArtifactSuffix(name) ? stripArtifactSuffix(name) : name.replace(/\.[^.]+$/, "");
159
163
  }
160
164
  function hasTraces(plan, swarm) {
161
165
  const narratives = plan.narratives;
@@ -182,7 +186,7 @@ function hasTraces(plan, swarm) {
182
186
  if (typeof ref === "object" &&
183
187
  ref !== null &&
184
188
  !Array.isArray(ref) &&
185
- ref.type === "x-vbrief/spec-section") {
189
+ referenceTypeMatches(String(ref.type ?? ""), "spec-section")) {
186
190
  return true;
187
191
  }
188
192
  }
@@ -206,14 +210,14 @@ function acceptanceCountJustification(plan, swarm) {
206
210
  }
207
211
  function allScopeIds(projectRoot) {
208
212
  const ids = new Map();
209
- const vbriefDir = join(projectRoot, "vbrief");
213
+ const vbriefDir = resolveLifecycleRoot(projectRoot);
210
214
  for (const folder of LIFECYCLE_FOLDERS) {
211
215
  const dir = join(vbriefDir, folder);
212
216
  if (!existsSync(dir)) {
213
217
  continue;
214
218
  }
215
219
  for (const name of readdirSync(dir).sort()) {
216
- if (!name.endsWith(".vbrief.json")) {
220
+ if (!hasArtifactSuffix(name)) {
217
221
  continue;
218
222
  }
219
223
  const path = join(dir, name);
@@ -224,9 +228,7 @@ function allScopeIds(projectRoot) {
224
228
  const plan = planOf(data);
225
229
  const sid = storyId(path, plan);
226
230
  ids.set(sid, [path, String(plan.status ?? "")]);
227
- const stem = name.endsWith(".vbrief.json")
228
- ? name.slice(0, -".vbrief.json".length)
229
- : name.replace(/\.[^.]+$/, "");
231
+ const stem = stripArtifactSuffix(name);
230
232
  if (!ids.has(stem)) {
231
233
  ids.set(stem, [path, String(plan.status ?? "")]);
232
234
  }
@@ -11,6 +11,7 @@
11
11
  * @see {@link https://github.com/deftai/directive/issues/1739} Superseding PR
12
12
  * @see {@link https://github.com/deftai/directive/issues/1860} Hard deletion tracking
13
13
  */
14
+ import { readPlanPolicy } from "../policy/plan-extensions.js";
14
15
  import { loadProjectDefinition } from "../policy/resolve.js";
15
16
  import { LEAF_CODING_WORKER_ROLE, SUBAGENT_BACKEND_SET_CMD } from "./constants.js";
16
17
  const TRUTHY = new Set(["1", "true", "yes", "on"]);
@@ -63,11 +64,7 @@ export function probeSubagentBackends(environ = process.env) {
63
64
  });
64
65
  }
65
66
  function getPolicyBlock(data) {
66
- const plan = data.plan;
67
- if (typeof plan !== "object" || plan === null || Array.isArray(plan)) {
68
- return {};
69
- }
70
- const policy = plan.policy;
67
+ const policy = readPlanPolicy(data.plan);
71
68
  if (typeof policy !== "object" || policy === null || Array.isArray(policy)) {
72
69
  return {};
73
70
  }
@@ -1,5 +1,6 @@
1
1
  import { existsSync, readdirSync, readFileSync } from "node:fs";
2
2
  import { resolve } from "node:path";
3
+ import { hasArtifactSuffix } from "../layout/resolve.js";
3
4
  import { defaultRunGh, fetchGreptileCommentBody, fetchPrHeadSha, } from "../pr-merge-readiness/gh.js";
4
5
  import { evaluateGates, parseGreptileBody } from "../pr-merge-readiness/index.js";
5
6
  import { EXIT_EXTERNAL_ERROR, EXIT_OK, EXIT_UNCLEAN } from "./constants.js";
@@ -34,7 +35,7 @@ function globPaths(pattern) {
34
35
  return [];
35
36
  }
36
37
  return readdirSync(dir)
37
- .filter((name) => (glob === "*.vbrief.json" ? name.endsWith(".vbrief.json") : name === glob))
38
+ .filter((name) => (glob === "*.vbrief.json" ? hasArtifactSuffix(name) : name === glob))
38
39
  .map((name) => resolve(dir, name));
39
40
  }
40
41
  return [resolve(pattern)];
@@ -1,5 +1,8 @@
1
1
  import type { AuditEntry, CandidatesLog } from "./types.js";
2
+ /** Display/back-compat constant; resolution flows through resolveEvalPath (#2109). */
2
3
  export declare const AUDIT_LOG_REL_PATH = "vbrief/.eval/candidates.jsonl";
4
+ /** Layout-aware candidates audit-log path (xbrief when migrated, else vbrief). */
5
+ export declare function resolveCandidatesLogPath(projectRoot: string): string;
3
6
  /** Read every well-formed audit-log row in insertion order (#1698 shared reader). */
4
7
  export declare function readAuditLog(logPath: string, repo?: string | null): AuditEntry[];
5
8
  /** Return every entry for ``(repo, issue_number)`` in insertion order. */
@@ -1,8 +1,14 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
3
  import { join } from "node:path";
4
+ import { resolveEvalPath } from "../../layout/resolve.js";
4
5
  import { CandidatesLogError } from "./errors.js";
6
+ /** Display/back-compat constant; resolution flows through resolveEvalPath (#2109). */
5
7
  export const AUDIT_LOG_REL_PATH = "vbrief/.eval/candidates.jsonl";
8
+ /** Layout-aware candidates audit-log path (xbrief when migrated, else vbrief). */
9
+ export function resolveCandidatesLogPath(projectRoot) {
10
+ return resolveEvalPath(projectRoot, "candidates.jsonl");
11
+ }
6
12
  const VALID_DECISIONS = new Set([
7
13
  "accept",
8
14
  "reject",
@@ -103,7 +109,7 @@ function resolveLogPath(projectRoot, override) {
103
109
  if (override !== undefined) {
104
110
  return override;
105
111
  }
106
- return join(projectRoot, AUDIT_LOG_REL_PATH);
112
+ return resolveCandidatesLogPath(projectRoot);
107
113
  }
108
114
  function stableStringify(entry) {
109
115
  const sortedKeys = Object.keys(entry).sort();
@@ -227,6 +233,6 @@ export function rollbackAuditEntry(decisionId, projectRoot, logPathOverride) {
227
233
  return removed;
228
234
  }
229
235
  export function resolveAuditLogPath(projectRoot) {
230
- return join(projectRoot, AUDIT_LOG_REL_PATH);
236
+ return resolveCandidatesLogPath(projectRoot);
231
237
  }
232
238
  //# sourceMappingURL=candidates-log.js.map