@deftai/directive-core 0.72.0 → 0.73.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 (103) hide show
  1. package/dist/branch/evaluate.js +2 -1
  2. package/dist/category-b-namespace/index.js +16 -2
  3. package/dist/codebase/default-extractor.js +9 -1
  4. package/dist/codebase/map.js +1 -1
  5. package/dist/deposit/copy-tree.js +31 -7
  6. package/dist/doctor/checks.d.ts +9 -0
  7. package/dist/doctor/checks.js +85 -5
  8. package/dist/doctor/main.js +2 -1
  9. package/dist/eval/health.js +7 -1
  10. package/dist/eval/readback.js +7 -1
  11. package/dist/init-deposit/gitignore.js +6 -0
  12. package/dist/init-deposit/hygiene.d.ts +30 -0
  13. package/dist/init-deposit/hygiene.js +60 -1
  14. package/dist/init-deposit/refresh.d.ts +16 -2
  15. package/dist/init-deposit/refresh.js +162 -52
  16. package/dist/init-deposit/scaffold.d.ts +11 -1
  17. package/dist/init-deposit/scaffold.js +9 -8
  18. package/dist/integration-e2e/helpers.js +7 -7
  19. package/dist/layout/index.d.ts +1 -1
  20. package/dist/layout/index.js +1 -1
  21. package/dist/layout/resolve.d.ts +47 -31
  22. package/dist/layout/resolve.js +93 -36
  23. package/dist/lifecycle/lifecycle-hygiene.d.ts +1 -1
  24. package/dist/lifecycle/lifecycle-hygiene.js +1 -1
  25. package/dist/migrate-preflight/index.js +10 -10
  26. package/dist/policy/disclosure.d.ts +1 -0
  27. package/dist/policy/disclosure.js +1 -0
  28. package/dist/policy/index.d.ts +1 -0
  29. package/dist/policy/index.js +1 -0
  30. package/dist/policy/policy-invocation.d.ts +5 -0
  31. package/dist/policy/policy-invocation.js +9 -0
  32. package/dist/policy/resolve.d.ts +2 -2
  33. package/dist/policy/resolve.js +15 -5
  34. package/dist/policy/value-feedback.js +6 -3
  35. package/dist/policy/wip.d.ts +1 -1
  36. package/dist/policy/wip.js +15 -3
  37. package/dist/preflight-cache/evaluate.js +1 -1
  38. package/dist/release/native-steps.js +14 -2
  39. package/dist/render/export-spec.js +2 -2
  40. package/dist/render/project-render.js +40 -9
  41. package/dist/scope/decompose.js +12 -2
  42. package/dist/session/resume-conditions.js +2 -2
  43. package/dist/session/ritual-sentinel.d.ts +1 -1
  44. package/dist/session/ritual-sentinel.js +6 -4
  45. package/dist/slice/constants.d.ts +1 -1
  46. package/dist/slice/constants.js +1 -1
  47. package/dist/slice/project-context.js +1 -1
  48. package/dist/spec-authority/resolver.js +9 -2
  49. package/dist/swarm/complete-cohort.js +14 -1
  50. package/dist/swarm/constants.d.ts +1 -1
  51. package/dist/swarm/constants.js +2 -1
  52. package/dist/swarm/readiness.js +33 -3
  53. package/dist/swarm/subagent-backend.js +2 -1
  54. package/dist/task-surface/index.js +3 -3
  55. package/dist/triage/actions/candidates-log.d.ts +1 -1
  56. package/dist/triage/bootstrap/gitignore.js +22 -10
  57. package/dist/triage/bootstrap/index.d.ts +1 -1
  58. package/dist/triage/bootstrap/index.js +9 -3
  59. package/dist/triage/cache-path.d.ts +1 -1
  60. package/dist/triage/cache-path.js +32 -6
  61. package/dist/triage/classify/index.d.ts +1 -1
  62. package/dist/triage/classify/index.js +11 -4
  63. package/dist/triage/help/registry-data.d.ts +2 -2
  64. package/dist/triage/help/registry-data.js +2 -2
  65. package/dist/triage/queue/constants.d.ts +4 -4
  66. package/dist/triage/queue/constants.js +4 -4
  67. package/dist/triage/queue/scope-walk.js +7 -1
  68. package/dist/triage/reconcile/reconcile.js +23 -3
  69. package/dist/triage/reconcile/types.d.ts +1 -1
  70. package/dist/triage/reconcile/types.js +1 -1
  71. package/dist/triage/refresh/refresh.js +9 -2
  72. package/dist/triage/scope/constants.d.ts +2 -2
  73. package/dist/triage/scope/constants.js +2 -2
  74. package/dist/triage/scope/renderers.js +7 -1
  75. package/dist/triage/smoketest/index.js +9 -6
  76. package/dist/triage/subscribe/index.d.ts +2 -2
  77. package/dist/triage/subscribe/index.js +2 -2
  78. package/dist/triage/summary/index.d.ts +2 -2
  79. package/dist/triage/summary/index.js +1 -1
  80. package/dist/triage/summary/reconcilable.d.ts +1 -1
  81. package/dist/triage/welcome/constants.d.ts +2 -2
  82. package/dist/triage/welcome/constants.js +2 -2
  83. package/dist/triage/welcome/onboard.js +8 -1
  84. package/dist/triage/welcome/prior-state.js +14 -2
  85. package/dist/triage/welcome/summary.js +14 -2
  86. package/dist/triage/welcome/writers.js +7 -1
  87. package/dist/ts-check-lane/run-lane.d.ts +4 -0
  88. package/dist/ts-check-lane/run-lane.js +20 -6
  89. package/dist/validate-content/validate-strategy-output.js +11 -1
  90. package/dist/value/feedback-file.js +3 -2
  91. package/dist/value/readback.js +3 -2
  92. package/dist/vbrief-build/parity-scenarios.js +3 -3
  93. package/dist/vbrief-reconcile/labels.js +16 -1
  94. package/dist/vbrief-reconcile/parity-scenarios.js +10 -10
  95. package/dist/vbrief-reconcile/umbrellas.js +16 -1
  96. package/dist/vbrief-validate/main.js +13 -1
  97. package/dist/vbrief-validate/precutover.js +33 -5
  98. package/dist/vbrief-validate/staleness.d.ts +1 -1
  99. package/dist/vbrief-validate/staleness.js +5 -2
  100. package/dist/verify-source/code-structure-validate.js +7 -1
  101. package/dist/xbrief-migrate/signpost.d.ts +4 -4
  102. package/dist/xbrief-migrate/signpost.js +11 -15
  103. package/package.json +3 -3
@@ -1,15 +1,18 @@
1
1
  import { readdirSync, statSync } from "node:fs";
2
2
  import { join } from "node:path";
3
- import { LEGACY_ARTIFACT_DIR, LEGACY_ARTIFACT_SUFFIX, LEGACY_INFO_ROOT_KEY, MIGRATED_ARTIFACT_DIR, MIGRATED_ARTIFACT_SUFFIX, MIGRATED_INFO_ROOT_KEY, } from "../xbrief-migrate/constants.js";
4
- export { LEGACY_ARTIFACT_DIR, LEGACY_ARTIFACT_SUFFIX, LEGACY_INFO_ROOT_KEY, MIGRATED_ARTIFACT_DIR, MIGRATED_ARTIFACT_SUFFIX, MIGRATED_INFO_ROOT_KEY, };
3
+ import { LEGACY_ARTIFACT_DIR, LEGACY_ARTIFACT_SUFFIX, MIGRATED_ARTIFACT_DIR, MIGRATED_ARTIFACT_SUFFIX, MIGRATED_INFO_ROOT_KEY, } from "../xbrief-migrate/constants.js";
4
+ export { LEGACY_ARTIFACT_DIR, MIGRATED_ARTIFACT_DIR, MIGRATED_ARTIFACT_SUFFIX, MIGRATED_INFO_ROOT_KEY, };
5
5
  /**
6
- * Lifecycle directory names recognized by the layout-aware resolver, in
7
- * preference order (migrated `xbrief` first, legacy `vbrief` second).
6
+ * Lifecycle directory names recognized by the layout-aware resolver and path
7
+ * validators. Includes both `xbrief` (canonical) and `vbrief` (legacy) so that
8
+ * path-validation helpers remain accurate during and after migration.
8
9
  */
9
10
  export const LIFECYCLE_DIR_NAMES = [MIGRATED_ARTIFACT_DIR, LEGACY_ARTIFACT_DIR];
10
11
  /**
11
- * Artifact filename suffixes recognized by the layout-aware resolver, in
12
- * preference order (`.xbrief.json` first, `.vbrief.json` second).
12
+ * Artifact filename suffixes recognized by the layout-aware resolver and path
13
+ * validators. Includes both `.xbrief.json` (canonical) and `.vbrief.json`
14
+ * (legacy) so that path-validation helpers remain accurate during and after
15
+ * migration.
13
16
  */
14
17
  export const ARTIFACT_SUFFIXES = [MIGRATED_ARTIFACT_SUFFIX, LEGACY_ARTIFACT_SUFFIX];
15
18
  function isDirectory(path) {
@@ -20,6 +23,25 @@ function isDirectory(path) {
20
23
  return false;
21
24
  }
22
25
  }
26
+ /**
27
+ * Returns the layout root for non-layout-sensitive helpers (#2112).
28
+ *
29
+ * - If a valid `xbrief/` layout exists, returns its root.
30
+ * - If only `vbrief/` exists (legacy-only), throws so the caller surfaces the migrate hint.
31
+ * - If neither exists (empty/new project), falls back to `<projectRoot>/xbrief/` canonical path.
32
+ */
33
+ export function resolveLayoutRootOrCanonical(projectRoot) {
34
+ try {
35
+ return resolveLifecycleRoot(projectRoot);
36
+ }
37
+ catch (err) {
38
+ if (isDirectory(join(projectRoot, LEGACY_ARTIFACT_DIR)) &&
39
+ !isDirectory(join(projectRoot, MIGRATED_ARTIFACT_DIR))) {
40
+ throw err; // Legacy-only project: operator must run deft migrate:xbrief.
41
+ }
42
+ return join(projectRoot, MIGRATED_ARTIFACT_DIR); // New/empty project; use canonical path.
43
+ }
44
+ }
23
45
  /** True when `name` ends with any recognized artifact suffix (.xbrief.json or .vbrief.json). */
24
46
  export function hasArtifactSuffix(name) {
25
47
  return ARTIFACT_SUFFIXES.some((suffix) => name.endsWith(suffix));
@@ -37,8 +59,10 @@ export function stripArtifactSuffix(name) {
37
59
  return name;
38
60
  }
39
61
  /**
40
- * True when a POSIX-style path is a lifecycle artifact under either layout
41
- * root (`xbrief/` or `vbrief/`) carrying either artifact suffix.
62
+ * True when a POSIX-style path is a lifecycle artifact path carrying a
63
+ * recognized lifecycle directory prefix (`xbrief/` or `vbrief/`) and a
64
+ * recognized artifact suffix. The `vbrief/` prefix and `.vbrief.json` suffix
65
+ * remain recognized for path-validation purposes during and after migration.
42
66
  */
43
67
  export function isLifecycleArtifactPath(posix) {
44
68
  const underLifecycleDir = LIFECYCLE_DIR_NAMES.some((dir) => posix.startsWith(`${dir}/`));
@@ -64,12 +88,16 @@ function containsMigratedArtifact(root) {
64
88
  return false;
65
89
  }
66
90
  /**
67
- * Resolve the active lifecycle layout for `projectRoot` (#2109 part 1).
91
+ * Resolve the active lifecycle layout for `projectRoot` (#2109 part 1 / #2112).
92
+ *
93
+ * Requires the migrated `xbrief/` layout: the `xbrief/` directory must exist
94
+ * AND contain at least one `.xbrief.json` artifact. If no such layout is found,
95
+ * throws with a clear error directing the operator to run `deft migrate:xbrief`.
68
96
  *
69
- * Prefers the migrated `xbrief/` layout only when BOTH the `xbrief/` directory
70
- * exists AND it contains at least one `.xbrief.json` artifact; otherwise falls
71
- * back to the legacy `vbrief/` layout. With only `vbrief/` present (today's
72
- * repo) the result is the legacy layout, so existing behavior is unchanged.
97
+ * The legacy `vbrief/` read-path fallback was removed in #2112 (0.73.0 MINOR).
98
+ * Projects that have not migrated must run `deft migrate:xbrief` first.
99
+ *
100
+ * @throws {Error} When no `xbrief/` layout with `.xbrief.json` artifacts exists.
73
101
  */
74
102
  export function resolveLifecycleLayout(projectRoot) {
75
103
  const migratedRoot = join(projectRoot, MIGRATED_ARTIFACT_DIR);
@@ -82,28 +110,26 @@ export function resolveLifecycleLayout(projectRoot) {
82
110
  migrated: true,
83
111
  };
84
112
  }
85
- return {
86
- artifactDir: LEGACY_ARTIFACT_DIR,
87
- artifactSuffix: LEGACY_ARTIFACT_SUFFIX,
88
- infoRootKey: LEGACY_INFO_ROOT_KEY,
89
- root: join(projectRoot, LEGACY_ARTIFACT_DIR),
90
- migrated: false,
91
- };
113
+ throw new Error(`No xbrief/ layout found at ${projectRoot}. ` +
114
+ "Run `deft migrate:xbrief` to convert your project from the legacy vbrief/ layout.");
92
115
  }
93
116
  /** Convenience accessor for the absolute resolved lifecycle root directory. */
94
117
  export function resolveLifecycleRoot(projectRoot) {
95
118
  return resolveLifecycleLayout(projectRoot).root;
96
119
  }
97
120
  /**
98
- * Absolute path to a lifecycle folder under the resolved layout root
99
- * (e.g. `<root>/<xbrief|vbrief>/active`). Layout-aware (#2109 part 2a).
121
+ * Absolute path to a lifecycle folder under the resolved `xbrief/` layout root
122
+ * (e.g. `<root>/xbrief/active`). Layout-aware (#2109 part 2a / #2112).
100
123
  */
101
124
  export function resolveLifecycleFolder(projectRoot, folder) {
102
- return join(resolveLifecycleRoot(projectRoot), folder);
125
+ // Throws on a pure vbrief/-only tree (#2112). Falls back to xbrief/ when neither layout exists.
126
+ return join(resolveLayoutRootOrCanonical(projectRoot), folder);
103
127
  }
104
128
  /** Absolute path to the layout-aware `.eval/` directory for version-eval results (#1703). */
105
129
  export function resolveEvalDir(projectRoot) {
106
- return join(resolveLifecycleRoot(projectRoot), ".eval");
130
+ // Throws on a pure vbrief/-only tree (#2112). Falls back to xbrief/ when neither layout exists.
131
+ const layoutRoot = resolveLayoutRootOrCanonical(projectRoot);
132
+ return join(layoutRoot, ".eval");
107
133
  }
108
134
  /** Absolute path under `.eval/` for version-eval artefacts (not triage working-set). */
109
135
  export function resolveEvalPath(projectRoot, ...segments) {
@@ -111,7 +137,9 @@ export function resolveEvalPath(projectRoot, ...segments) {
111
137
  }
112
138
  /** Absolute path to the resolved lifecycle `.audit` directory (#2109 part 2a). */
113
139
  export function resolveAuditDir(projectRoot) {
114
- return join(resolveLifecycleRoot(projectRoot), ".audit");
140
+ // Throws on a pure vbrief/-only tree (#2112). Falls back to xbrief/ when neither layout exists.
141
+ const layoutRoot = resolveLayoutRootOrCanonical(projectRoot);
142
+ return join(layoutRoot, ".audit");
115
143
  }
116
144
  /** Absolute path to a file or subpath under the resolved `.audit` directory. */
117
145
  export function resolveAuditPath(projectRoot, ...segments) {
@@ -122,27 +150,56 @@ export function resolveAuditPath(projectRoot, ...segments) {
122
150
  * (`PROJECT-DEFINITION.xbrief.json` when migrated, else `.vbrief.json`).
123
151
  */
124
152
  export function projectDefinitionFilename(projectRoot) {
125
- return `PROJECT-DEFINITION${resolveLifecycleLayout(projectRoot).artifactSuffix}`;
153
+ // Throws on a pure vbrief/-only tree (#2112). Falls back to xbrief/ when neither layout exists.
154
+ try {
155
+ return `PROJECT-DEFINITION${resolveLifecycleLayout(projectRoot).artifactSuffix}`;
156
+ }
157
+ catch (err) {
158
+ if (isDirectory(join(projectRoot, LEGACY_ARTIFACT_DIR)) &&
159
+ !isDirectory(join(projectRoot, MIGRATED_ARTIFACT_DIR))) {
160
+ throw err;
161
+ }
162
+ return `PROJECT-DEFINITION${MIGRATED_ARTIFACT_SUFFIX}`;
163
+ }
126
164
  }
127
165
  /** Absolute path to the resolved PROJECT-DEFINITION artifact (#2109 part 2a). */
128
166
  export function resolveProjectDefinitionPath(projectRoot) {
129
- const layout = resolveLifecycleLayout(projectRoot);
130
- return join(layout.root, `PROJECT-DEFINITION${layout.artifactSuffix}`);
167
+ // Throws on a pure vbrief/-only tree (#2112). Falls back to xbrief/ when neither layout exists.
168
+ try {
169
+ const layout = resolveLifecycleLayout(projectRoot);
170
+ return join(layout.root, `PROJECT-DEFINITION${layout.artifactSuffix}`);
171
+ }
172
+ catch (err) {
173
+ if (isDirectory(join(projectRoot, LEGACY_ARTIFACT_DIR)) &&
174
+ !isDirectory(join(projectRoot, MIGRATED_ARTIFACT_DIR))) {
175
+ throw err; // Legacy-only project: operator must run deft migrate:xbrief.
176
+ }
177
+ return join(projectRoot, MIGRATED_ARTIFACT_DIR, `PROJECT-DEFINITION${MIGRATED_ARTIFACT_SUFFIX}`);
178
+ }
131
179
  }
132
180
  /**
133
181
  * POSIX-style display path to the resolved PROJECT-DEFINITION artifact relative
134
- * to the project root (e.g. `vbrief/PROJECT-DEFINITION.vbrief.json`).
182
+ * to the project root (e.g. `xbrief/PROJECT-DEFINITION.xbrief.json`).
135
183
  */
136
184
  export function projectDefinitionRelPath(projectRoot) {
137
- const layout = resolveLifecycleLayout(projectRoot);
138
- return `${layout.artifactDir}/PROJECT-DEFINITION${layout.artifactSuffix}`;
185
+ // Throws on a pure vbrief/-only tree (#2112). Falls back to xbrief/ when neither layout exists.
186
+ try {
187
+ const layout = resolveLifecycleLayout(projectRoot);
188
+ return `${layout.artifactDir}/PROJECT-DEFINITION${layout.artifactSuffix}`;
189
+ }
190
+ catch (err) {
191
+ if (isDirectory(join(projectRoot, LEGACY_ARTIFACT_DIR)) &&
192
+ !isDirectory(join(projectRoot, MIGRATED_ARTIFACT_DIR))) {
193
+ throw err;
194
+ }
195
+ return `${MIGRATED_ARTIFACT_DIR}/PROJECT-DEFINITION${MIGRATED_ARTIFACT_SUFFIX}`;
196
+ }
139
197
  }
140
198
  /**
141
- * Absolute path to the resolved specification artifact (#2132).
199
+ * Absolute path to the resolved specification artifact (#2132 / #2112).
142
200
  *
143
- * Prefers `xbrief/specification.xbrief.json` on a migrated tree; falls back to
144
- * `vbrief/specification.vbrief.json` on an unmigrated tree. Consistent with the
145
- * layout resolver used by the #2109 Part-2a call-site sweep.
201
+ * Returns `xbrief/specification.xbrief.json`; throws when no migrated xbrief/
202
+ * layout is present (see `resolveLifecycleLayout`).
146
203
  */
147
204
  export function resolveSpecArtifactPath(projectRoot) {
148
205
  const layout = resolveLifecycleLayout(projectRoot);
@@ -150,7 +207,7 @@ export function resolveSpecArtifactPath(projectRoot) {
150
207
  }
151
208
  /**
152
209
  * POSIX-style display path to the resolved specification artifact relative to
153
- * the project root (e.g. `xbrief/specification.xbrief.json`).
210
+ * the project root (`xbrief/specification.xbrief.json`).
154
211
  */
155
212
  export function specArtifactRelPath(projectRoot) {
156
213
  const layout = resolveLifecycleLayout(projectRoot);
@@ -11,7 +11,7 @@ export declare const TERMINAL_STATUSES: Set<string>;
11
11
  /** Child reference type that marks an epic as decomposed. */
12
12
  export declare const CHILD_REF_TYPE = "x-xbrief/plan";
13
13
  /** Durable tech-debt acceptance ledger relative path segments. */
14
- export declare const TECH_DEBT_LEDGER_RELPATH: readonly ["vbrief", ".audit", "epic-tech-debt-accepted.jsonl"];
14
+ export declare const TECH_DEBT_LEDGER_RELPATH: readonly ["xbrief", ".audit", "epic-tech-debt-accepted.jsonl"];
15
15
  /** Session-start nudge tiers (rate-of-harm ranking). */
16
16
  export declare const TIER_STRANDED = 1;
17
17
  export declare const TIER_STALE_EPIC = 2;
@@ -26,7 +26,7 @@ export const TERMINAL_STATUSES = new Set(["completed", "cancelled", "failed"]);
26
26
  export const CHILD_REF_TYPE = "x-xbrief/plan";
27
27
  /** Durable tech-debt acceptance ledger relative path segments. */
28
28
  export const TECH_DEBT_LEDGER_RELPATH = [
29
- "vbrief",
29
+ "xbrief",
30
30
  ".audit",
31
31
  "epic-tech-debt-accepted.jsonl",
32
32
  ];
@@ -14,7 +14,7 @@ function resolveContentRoot(frameworkRoot) {
14
14
  return frameworkRoot;
15
15
  }
16
16
  export function checkLayout(deftRoot, projectRoot) {
17
- const schemasDir = join(resolveContentRoot(deftRoot), "vbrief", "schemas");
17
+ const schemasDir = join(resolveContentRoot(deftRoot), "xbrief", "schemas");
18
18
  if (!existsSync(schemasDir) || !statSync(schemasDir).isDirectory()) {
19
19
  return {
20
20
  name: "layout",
@@ -22,18 +22,18 @@ export function checkLayout(deftRoot, projectRoot) {
22
22
  message: `Framework schemas dir missing at ${schemasDir}. Refresh the deft checkout (see deft/QUICK-START.md).`,
23
23
  };
24
24
  }
25
- const projectVbrief = join(projectRoot, "vbrief");
26
- if (!existsSync(projectVbrief)) {
25
+ const projectXbrief = join(projectRoot, "xbrief");
26
+ if (!existsSync(projectXbrief)) {
27
27
  return {
28
28
  name: "layout",
29
29
  status: "WARN",
30
- message: `Project vbrief/ not present at ${projectVbrief} -- expected for greenfield projects.`,
30
+ message: `Project xbrief/ not present at ${projectXbrief} -- expected for greenfield projects.`,
31
31
  };
32
32
  }
33
33
  return {
34
34
  name: "layout",
35
35
  status: "PASS",
36
- message: `Framework schemas present; project vbrief/ at ${projectVbrief}.`,
36
+ message: `Framework schemas present; project xbrief/ at ${projectXbrief}.`,
37
37
  };
38
38
  }
39
39
  export function checkGitClean(projectRoot) {
@@ -91,7 +91,7 @@ export function checkDocumentModel(projectRoot) {
91
91
  return {
92
92
  name: "document-model",
93
93
  status: "FAIL",
94
- message: `Generated SPECIFICATION.md detected (source: vbrief/specification.vbrief.json); repair missing lifecycle folder(s) instead of migrating: ${missing.join(", ")}.`,
94
+ message: `Generated SPECIFICATION.md detected (source: xbrief/specification.xbrief.json); repair missing lifecycle folder(s) instead of migrating: ${missing.join(", ")}.`,
95
95
  };
96
96
  }
97
97
  }
@@ -99,18 +99,18 @@ export function checkDocumentModel(projectRoot) {
99
99
  return {
100
100
  name: "document-model",
101
101
  status: "PASS",
102
- message: "Current generated SPECIFICATION.md detected (source: vbrief/specification.vbrief.json); pre-v0.20 migration is not needed.",
102
+ message: "Current generated SPECIFICATION.md detected (source: xbrief/specification.xbrief.json); pre-v0.20 migration is not needed.",
103
103
  };
104
104
  }
105
105
  }
106
- const vbriefRoot = join(projectRoot, "vbrief");
107
- if (existsSync(vbriefRoot)) {
106
+ const xbriefRoot = join(projectRoot, "xbrief");
107
+ if (existsSync(xbriefRoot)) {
108
108
  const missing = missingLifecycleFolders(projectRoot);
109
109
  if (missing.length > 0) {
110
110
  return {
111
111
  name: "document-model",
112
112
  status: "FAIL",
113
- message: `Partial vBRIEF layout detected; missing lifecycle folder(s): ${missing.join(", ")}. Create the folders or follow ${frozenPreCutoverMigrationGuidance()}`,
113
+ message: `Partial xBRIEF layout detected; missing lifecycle folder(s): ${missing.join(", ")}. Create the folders or follow ${frozenPreCutoverMigrationGuidance()}`,
114
114
  };
115
115
  }
116
116
  }
@@ -1,4 +1,5 @@
1
1
  import type { PolicyResult } from "./resolve.js";
2
+ export { policyColonInvocation, policySetInvocation } from "./policy-invocation.js";
2
3
  /** One-liner disclosure phrasing for AGENTS.md / setup interview echo. */
3
4
  export declare function disclosureLine(result: PolicyResult): string;
4
5
  //# sourceMappingURL=disclosure.d.ts.map
@@ -1,4 +1,5 @@
1
1
  import { ENV_BYPASS } from "./resolve.js";
2
+ export { policyColonInvocation, policySetInvocation } from "./policy-invocation.js";
2
3
  /** One-liner disclosure phrasing for AGENTS.md / setup interview echo. */
3
4
  export function disclosureLine(result) {
4
5
  if (result.allowDirectCommits) {
@@ -4,6 +4,7 @@ export * from "./capacity.js";
4
4
  export * from "./decisions.js";
5
5
  export * from "./disclosure.js";
6
6
  export * from "./plan-extensions.js";
7
+ export * from "./policy-invocation.js";
7
8
  export * from "./resolve.js";
8
9
  export * from "./value-feedback.js";
9
10
  export * from "./wip.js";
@@ -8,6 +8,7 @@ export * from "./capacity.js";
8
8
  export * from "./decisions.js";
9
9
  export * from "./disclosure.js";
10
10
  export * from "./plan-extensions.js";
11
+ export * from "./policy-invocation.js";
11
12
  export * from "./resolve.js";
12
13
  export * from "./value-feedback.js";
13
14
  export * from "./wip.js";
@@ -0,0 +1,5 @@
1
+ /** User-facing colon-form policy verb for consumer `deft` disclosures (#2367). */
2
+ export declare function policyColonInvocation(subcommand: string, trailing?: string): string;
3
+ /** User-facing `deft policy set <cmd>` form for policy-set disclosures (#2367). */
4
+ export declare function policySetInvocation(subcommand: string, trailing?: string): string;
5
+ //# sourceMappingURL=policy-invocation.d.ts.map
@@ -0,0 +1,9 @@
1
+ /** User-facing colon-form policy verb for consumer `deft` disclosures (#2367). */
2
+ export function policyColonInvocation(subcommand, trailing = "") {
3
+ return `deft policy:${subcommand}${trailing}`;
4
+ }
5
+ /** User-facing `deft policy set <cmd>` form for policy-set disclosures (#2367). */
6
+ export function policySetInvocation(subcommand, trailing = "") {
7
+ return `deft policy set ${subcommand}${trailing}`;
8
+ }
9
+ //# sourceMappingURL=policy-invocation.js.map
@@ -1,5 +1,5 @@
1
- /** Filesystem-relative location of the project-definition vBRIEF (display/back-compat). */
2
- export declare const PROJECT_DEFINITION_REL_PATH = "vbrief/PROJECT-DEFINITION.vbrief.json";
1
+ /** Filesystem-relative location of the project-definition xBRIEF (display/back-compat). */
2
+ export declare const PROJECT_DEFINITION_REL_PATH = "xbrief/PROJECT-DEFINITION.xbrief.json";
3
3
  /** Environment variable emergency bypass for branch protection (#747). */
4
4
  export declare const ENV_BYPASS = "DEFT_ALLOW_DEFAULT_BRANCH_COMMIT";
5
5
  /** Legacy narrative key replaced by the typed flag (#746). */
@@ -3,8 +3,9 @@ import { join, resolve as pathResolve } from "node:path";
3
3
  import { resolveProjectDefinitionPath } from "../layout/resolve.js";
4
4
  import { atomicWriteProjectDefinition, projectDefinitionMutationLock, } from "../vbrief-build/project-definition-io.js";
5
5
  import { migrateLegacyPolicyKey, PLAN_POLICY_KEY, readPlanPolicy } from "./plan-extensions.js";
6
- /** Filesystem-relative location of the project-definition vBRIEF (display/back-compat). */
7
- export const PROJECT_DEFINITION_REL_PATH = "vbrief/PROJECT-DEFINITION.vbrief.json";
6
+ import { policyColonInvocation } from "./policy-invocation.js";
7
+ /** Filesystem-relative location of the project-definition xBRIEF (display/back-compat). */
8
+ export const PROJECT_DEFINITION_REL_PATH = "xbrief/PROJECT-DEFINITION.xbrief.json";
8
9
  /** Environment variable emergency bypass for branch protection (#747). */
9
10
  export const ENV_BYPASS = "DEFT_ALLOW_DEFAULT_BRANCH_COMMIT";
10
11
  /** Legacy narrative key replaced by the typed flag (#746). */
@@ -18,7 +19,15 @@ const TRUTHY = new Set(["1", "true", "yes", "on"]);
18
19
  * else PROJECT-DEFINITION.vbrief.json (unchanged on today's tree).
19
20
  */
20
21
  export function projectDefinitionPath(projectRoot) {
21
- return resolveProjectDefinitionPath(pathResolve(projectRoot));
22
+ const root = pathResolve(projectRoot);
23
+ try {
24
+ return resolveProjectDefinitionPath(root);
25
+ }
26
+ catch {
27
+ // No xbrief/ layout; return canonical xbrief path so callers get a predictable
28
+ // "not found" result rather than a thrown error.
29
+ return join(root, PROJECT_DEFINITION_REL_PATH);
30
+ }
22
31
  }
23
32
  function envBypassActive() {
24
33
  const raw = process.env[ENV_BYPASS] ?? "";
@@ -146,8 +155,9 @@ export function resolvePolicy(projectRoot) {
146
155
  const warn = `DEPRECATED: PROJECT-DEFINITION uses the legacy narrative key ` +
147
156
  `'${LEGACY_NARRATIVE_KEY}' (${pythonRepr(raw)}). Migrate to typed ` +
148
157
  `plan.policy.allowDirectCommitsToMaster (#746). Run ` +
149
- `\`task policy:enforce-branches\` or \`task policy:allow-direct-commits ` +
150
- `-- --confirm\` to set the typed flag explicitly.`;
158
+ `\`${policyColonInvocation("enforce-branches")}\` or ` +
159
+ `\`${policyColonInvocation("allow-direct-commits", " -- --confirm")}\` ` +
160
+ `to set the typed flag explicitly.`;
151
161
  return {
152
162
  allowDirectCommits: allow,
153
163
  source: "legacy-narrative",
@@ -1,6 +1,7 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { atomicWriteProjectDefinition, projectDefinitionMutationLock, } from "../vbrief-build/project-definition-io.js";
3
3
  import { migrateLegacyPolicyKey, PLAN_POLICY_KEY, readPlanPolicy } from "./plan-extensions.js";
4
+ import { policyColonInvocation } from "./policy-invocation.js";
4
5
  import { appendAuditLog, loadProjectDefinition, projectDefinitionPath } from "./resolve.js";
5
6
  import { isTrustedOrgAutoEnable } from "./value-feedback-autoenable.js";
6
7
  /** Canonical registered policy field name (matches other FIELD_* dotted paths). */
@@ -21,7 +22,9 @@ export const VALUE_FEEDBACK_CAPABILITY_COST_DISCLOSURE = "\u26a0 Capability-cost
21
22
  " \u2022 A budgeted session one-liner may appear when concrete attributed value exists.\n" +
22
23
  " \u2022 Upstream gap-escalation prompts stay OFF unless you explicitly enable " +
23
24
  "`upstreamPrompt` (GitHub attention + token cost).\n" +
24
- " \u2022 Inspect current state: `task policy:show --field=valueFeedback`.\n" +
25
+ " \u2022 Inspect current state: `" +
26
+ policyColonInvocation("show", " --field=valueFeedback") +
27
+ "`.\n" +
25
28
  " \u2022 Reversible: set `enabled: false` under the typed policy block in PROJECT-DEFINITION.\n" +
26
29
  " \u2022 Changes are recorded to meta/policy-changes.log for auditability.";
27
30
  function defaultResolved(source, error = null) {
@@ -202,7 +205,7 @@ export function enableValueFeedback(projectRoot, options) {
202
205
  return {
203
206
  exitCode: 1,
204
207
  stdout: `${VALUE_FEEDBACK_CAPABILITY_COST_DISCLOSURE}\n\n` +
205
- "Re-run with --confirm to apply: task policy:enable-value-feedback -- --confirm\n",
208
+ `Re-run with --confirm to apply: ${policyColonInvocation("enable-value-feedback", " -- --confirm")}\n`,
206
209
  changed: false,
207
210
  };
208
211
  }
@@ -265,7 +268,7 @@ export function enableValueFeedback(projectRoot, options) {
265
268
  if (changedFlag) {
266
269
  atomicWriteProjectDefinition(path, data);
267
270
  }
268
- const actor = options.actor ?? "task policy:enable-value-feedback";
271
+ const actor = options.actor ?? policyColonInvocation("enable-value-feedback");
269
272
  const note = options.note ?? "";
270
273
  const parts = [
271
274
  `actor=${actor}`,
@@ -10,6 +10,6 @@ export interface WipCapResult {
10
10
  }
11
11
  /** Resolve plan.policy.wipCap from PROJECT-DEFINITION (#1124). */
12
12
  export declare function resolveWipCap(projectRoot: string): WipCapResult;
13
- /** Count *.vbrief.json files in vbrief/pending/ + vbrief/active/ (#1124). */
13
+ /** Count *.xbrief.json files in xbrief/pending/ + xbrief/active/ (#1124). */
14
14
  export declare function countVbriefWip(projectRoot: string): number;
15
15
  //# sourceMappingURL=wip.d.ts.map
@@ -1,6 +1,6 @@
1
1
  import { existsSync, readdirSync } from "node:fs";
2
2
  import { join } from "node:path";
3
- import { hasArtifactSuffix, resolveLifecycleRoot } from "../layout/resolve.js";
3
+ import { hasArtifactSuffix, LEGACY_ARTIFACT_DIR, MIGRATED_ARTIFACT_DIR, resolveLifecycleRoot, } from "../layout/resolve.js";
4
4
  import { readPlanPolicy } from "./plan-extensions.js";
5
5
  import { loadProjectDefinition } from "./resolve.js";
6
6
  /** Framework default WIP cap (#2319; raised from 10 per #1124 / umbrella #1119). */
@@ -62,10 +62,22 @@ export function resolveWipCap(projectRoot) {
62
62
  }
63
63
  return { cap: raw, source: "typed", error: null };
64
64
  }
65
- /** Count *.vbrief.json files in vbrief/pending/ + vbrief/active/ (#1124). */
65
+ /** Count *.xbrief.json files in xbrief/pending/ + xbrief/active/ (#1124). */
66
66
  export function countVbriefWip(projectRoot) {
67
67
  let total = 0;
68
- const vbriefRoot = resolveLifecycleRoot(projectRoot);
68
+ let vbriefRoot;
69
+ try {
70
+ vbriefRoot = resolveLifecycleRoot(projectRoot);
71
+ }
72
+ catch (err) {
73
+ // If xbrief/ dir exists (even without artifacts), there is no WIP to count.
74
+ // If only vbrief/ exists, this is a legacy project that must be migrated -- re-throw.
75
+ if (!existsSync(join(projectRoot, MIGRATED_ARTIFACT_DIR)) &&
76
+ existsSync(join(projectRoot, LEGACY_ARTIFACT_DIR))) {
77
+ throw err; // Legacy-only project: operator must run deft migrate:xbrief.
78
+ }
79
+ return 0; // No layout found at all; no WIP files to count.
80
+ }
69
81
  for (const sub of WIP_LIFECYCLE_DIRS) {
70
82
  const folder = join(vbriefRoot, sub);
71
83
  if (!existsSync(folder)) {
@@ -23,7 +23,7 @@ import { resolveCandidatesLogPath } from "../triage/cache-path.js";
23
23
  // ---------------------------------------------------------------------------
24
24
  export const CACHE_DIR_NAME = ".deft-cache";
25
25
  export const DEFAULT_SOURCE = "github-issue";
26
- export const CANDIDATES_RELPATH = join("vbrief", ".triage-cache", "candidates.jsonl");
26
+ export const CANDIDATES_RELPATH = join("xbrief", ".triage-cache", "candidates.jsonl");
27
27
  export const DEFAULT_MAX_AGE_HOURS = 24;
28
28
  export const ENV_MAX_AGE_HOURS = "DEFT_CACHE_MAX_AGE_HOURS";
29
29
  export const ENV_TRIAGE_REPO = "DEFT_TRIAGE_REPO";
@@ -3,10 +3,11 @@
3
3
  * Replaces python-steps.ts (scripts/ roadmap_render, reconcile_issues, build_dist).
4
4
  */
5
5
  import { spawnSync } from "node:child_process";
6
+ import { existsSync } from "node:fs";
6
7
  import { dirname, join } from "node:path";
7
8
  import { fileURLToPath } from "node:url";
8
9
  import { fetchIssueStates, isTerminalLifecyclePath, reconcile, scanVbriefDir, } from "../intake/reconcile-issues.js";
9
- import { resolveLifecycleFolder, resolveLifecycleRoot } from "../layout/resolve.js";
10
+ import { LEGACY_ARTIFACT_DIR, MIGRATED_ARTIFACT_DIR, resolveLifecycleFolder, resolveLifecycleRoot, } from "../layout/resolve.js";
10
11
  import { renderRoadmap } from "../render/roadmap-render.js";
11
12
  const BUILD_DIST_RUNNER = join(dirname(fileURLToPath(import.meta.url)), "build-dist-runner.js");
12
13
  export function refreshRoadmapNative(projectRoot) {
@@ -20,7 +21,18 @@ export function refreshRoadmapNative(projectRoot) {
20
21
  return [true, "ROADMAP.md re-rendered"];
21
22
  }
22
23
  export function checkVbriefLifecycleSyncNative(projectRoot, repo) {
23
- const vbriefDir = resolveLifecycleRoot(projectRoot);
24
+ let vbriefDir;
25
+ try {
26
+ vbriefDir = resolveLifecycleRoot(projectRoot);
27
+ }
28
+ catch (_err) {
29
+ // Re-throw if this is a legacy-only vbrief/ project; fall back to canonical path otherwise.
30
+ if (existsSync(join(projectRoot, LEGACY_ARTIFACT_DIR)) &&
31
+ !existsSync(join(projectRoot, MIGRATED_ARTIFACT_DIR))) {
32
+ return [false, 0, "no xbrief/ layout found; skipping lifecycle sync check"];
33
+ }
34
+ vbriefDir = join(projectRoot, MIGRATED_ARTIFACT_DIR);
35
+ }
24
36
  try {
25
37
  const issueToVbriefs = scanVbriefDir(vbriefDir);
26
38
  const issueStateMap = fetchIssueStates(repo, new Set(issueToVbriefs.keys()), {
@@ -26,7 +26,7 @@ export function exportSpec(options = {}) {
26
26
  const includeProposed = audience === "internal";
27
27
  const authority = resolveSpecAuthority(projectRoot);
28
28
  if (!authority) {
29
- return [false, "✗ Missing vbrief/PROJECT-DEFINITION.vbrief.json — cannot export spec."];
29
+ return [false, "✗ Missing xbrief/PROJECT-DEFINITION.xbrief.json — cannot export spec."];
30
30
  }
31
31
  if (authority.kind === "full-spec" && authority.specPath) {
32
32
  const [ok, msg] = validateSpec(authority.specPath);
@@ -36,7 +36,7 @@ export function exportSpec(options = {}) {
36
36
  else if (!greenfieldOverviewNonEmpty(authority)) {
37
37
  return [
38
38
  false,
39
- "⚠ PROJECT-DEFINITION.vbrief.json Overview narrative is empty (D3). Populate Overview before export.",
39
+ "⚠ PROJECT-DEFINITION.xbrief.json Overview narrative is empty (D3). Populate Overview before export.",
40
40
  ];
41
41
  }
42
42
  const narratives = resolveExportNarratives(authority);
@@ -1,6 +1,6 @@
1
1
  import { existsSync, mkdirSync, readdirSync, readFileSync } from "node:fs";
2
2
  import { basename, join, resolve } from "node:path";
3
- import { hasArtifactSuffix, resolveLifecycleRoot, stripArtifactSuffix } from "../layout/resolve.js";
3
+ import { hasArtifactSuffix, resolveLayoutRootOrCanonical, stripArtifactSuffix, } from "../layout/resolve.js";
4
4
  import { EMITTED_VBRIEF_VERSION } from "../vbrief-build/constants.js";
5
5
  import { atomicWriteProjectDefinition, projectDefinitionMutationLock, } from "../vbrief-build/project-definition-io.js";
6
6
  import { deriveRegistryItemStatus, registryMetadataReferencesFromScope, } from "../vbrief-validate/registry-status.js";
@@ -317,30 +317,61 @@ export function acknowledgeProjectDefinitionStaleness(vbriefDir, options = {}) {
317
317
  ];
318
318
  });
319
319
  }
320
+ const USAGE = "Usage: project-render [--help] [--acknowledge-staleness] [--project-root <dir>]\n";
320
321
  /** CLI entry (mirrors ``scripts/project_render.main``). */
321
322
  export function main(argv) {
322
- const acknowledge = argv[0] === "--acknowledge-staleness";
323
- const rest = acknowledge ? argv.slice(1) : argv;
323
+ // Handle --help / -h before any other parsing so the flag is never treated
324
+ // as a positional output path and no stray ./--help/ directory is created
325
+ // (#2236).
326
+ if (argv.includes("--help") || argv.includes("-h")) {
327
+ process.stdout.write(USAGE);
328
+ return 0;
329
+ }
330
+ // Parse all flags in a single positional-independent pass so that
331
+ // --acknowledge-staleness is recognised at any position (not only argv[0]).
332
+ let acknowledge = false;
324
333
  let projectRoot;
325
334
  const positional = [];
326
- for (let i = 0; i < rest.length; i += 1) {
327
- const arg = rest[i];
328
- if (arg === "--project-root") {
329
- projectRoot = rest[i + 1];
335
+ for (let i = 0; i < argv.length; i += 1) {
336
+ const arg = argv[i];
337
+ if (arg === "--acknowledge-staleness") {
338
+ acknowledge = true;
339
+ }
340
+ else if (arg === "--project-root") {
341
+ projectRoot = argv[i + 1];
330
342
  i += 1;
331
343
  }
332
344
  else if (arg.startsWith("--project-root=")) {
333
345
  projectRoot = arg.slice("--project-root=".length);
334
346
  }
347
+ else if (arg.startsWith("-")) {
348
+ // Reject unknown flags rather than consuming them as positional output
349
+ // paths (would otherwise create stray directories like ./--help/).
350
+ process.stderr.write(`Unknown flag: ${arg}\n${USAGE}`);
351
+ return 2;
352
+ }
335
353
  else {
336
354
  positional.push(arg);
337
355
  }
338
356
  }
339
357
  if (positional.length > 1) {
340
- process.stderr.write("Usage: project-render [--acknowledge-staleness] [--project-root <dir>] [vbrief_dir]\n");
358
+ process.stderr.write(USAGE);
341
359
  return 2;
342
360
  }
343
- const vbriefDir = positional[0] ?? resolveLifecycleRoot(resolve(projectRoot !== undefined ? projectRoot : "."));
361
+ let vbriefDir;
362
+ if (positional[0] !== undefined) {
363
+ vbriefDir = positional[0];
364
+ }
365
+ else {
366
+ const effectiveRoot = resolve(projectRoot !== undefined ? projectRoot : ".");
367
+ try {
368
+ vbriefDir = resolveLayoutRootOrCanonical(effectiveRoot);
369
+ }
370
+ catch {
371
+ process.stderr.write(`No xbrief/ layout found at ${effectiveRoot}. Run \`deft migrate:xbrief\` to convert your project from the legacy vbrief/ layout.\n`);
372
+ return 2;
373
+ }
374
+ }
344
375
  const [ok, message] = acknowledge
345
376
  ? acknowledgeProjectDefinitionStaleness(vbriefDir)
346
377
  : renderProjectDefinition(vbriefDir);