@deftai/directive-core 0.71.1 → 0.73.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 (94) hide show
  1. package/dist/category-b-namespace/index.js +16 -2
  2. package/dist/codebase/default-extractor.js +9 -1
  3. package/dist/codebase/map.js +1 -1
  4. package/dist/doctor/checks.d.ts +9 -0
  5. package/dist/doctor/checks.js +85 -5
  6. package/dist/doctor/main.js +2 -1
  7. package/dist/eval/health.js +7 -1
  8. package/dist/eval/readback.js +7 -1
  9. package/dist/events/attribution-enrichment.d.ts +35 -0
  10. package/dist/events/attribution-enrichment.js +74 -0
  11. package/dist/events/attribution-ledger.js +6 -1
  12. package/dist/init-deposit/gitignore.js +6 -0
  13. package/dist/init-deposit/hygiene.d.ts +24 -0
  14. package/dist/init-deposit/hygiene.js +55 -0
  15. package/dist/init-deposit/refresh.js +22 -6
  16. package/dist/init-deposit/scaffold.d.ts +11 -1
  17. package/dist/init-deposit/scaffold.js +4 -4
  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/index.js +3 -3
  27. package/dist/policy/resolve.d.ts +2 -2
  28. package/dist/policy/resolve.js +11 -3
  29. package/dist/policy/value-feedback-autoenable.d.ts +33 -0
  30. package/dist/policy/value-feedback-autoenable.js +67 -0
  31. package/dist/policy/value-feedback.d.ts +30 -5
  32. package/dist/policy/value-feedback.js +52 -34
  33. package/dist/policy/wip.d.ts +1 -1
  34. package/dist/policy/wip.js +15 -3
  35. package/dist/preflight-cache/evaluate.js +1 -1
  36. package/dist/release/native-steps.js +14 -2
  37. package/dist/render/export-spec.js +2 -2
  38. package/dist/render/project-render.js +40 -9
  39. package/dist/scope/decompose.js +12 -2
  40. package/dist/session/resume-conditions.js +2 -2
  41. package/dist/session/ritual-sentinel.d.ts +1 -1
  42. package/dist/session/ritual-sentinel.js +6 -4
  43. package/dist/slice/constants.d.ts +1 -1
  44. package/dist/slice/constants.js +1 -1
  45. package/dist/slice/project-context.js +1 -1
  46. package/dist/spec-authority/resolver.js +9 -2
  47. package/dist/swarm/complete-cohort.js +14 -1
  48. package/dist/swarm/readiness.js +33 -3
  49. package/dist/task-surface/index.js +3 -3
  50. package/dist/triage/actions/candidates-log.d.ts +1 -1
  51. package/dist/triage/bootstrap/gitignore.js +22 -10
  52. package/dist/triage/bootstrap/index.d.ts +1 -1
  53. package/dist/triage/bootstrap/index.js +9 -3
  54. package/dist/triage/cache-path.d.ts +1 -1
  55. package/dist/triage/cache-path.js +32 -6
  56. package/dist/triage/classify/index.d.ts +1 -1
  57. package/dist/triage/classify/index.js +11 -4
  58. package/dist/triage/help/registry-data.d.ts +2 -2
  59. package/dist/triage/help/registry-data.js +2 -2
  60. package/dist/triage/queue/constants.d.ts +4 -4
  61. package/dist/triage/queue/constants.js +4 -4
  62. package/dist/triage/queue/scope-walk.js +7 -1
  63. package/dist/triage/reconcile/reconcile.js +23 -3
  64. package/dist/triage/reconcile/types.d.ts +1 -1
  65. package/dist/triage/reconcile/types.js +1 -1
  66. package/dist/triage/refresh/refresh.js +9 -2
  67. package/dist/triage/scope/constants.d.ts +2 -2
  68. package/dist/triage/scope/constants.js +2 -2
  69. package/dist/triage/scope/renderers.js +7 -1
  70. package/dist/triage/smoketest/index.js +9 -6
  71. package/dist/triage/subscribe/index.d.ts +2 -2
  72. package/dist/triage/subscribe/index.js +2 -2
  73. package/dist/triage/summary/index.d.ts +2 -2
  74. package/dist/triage/summary/index.js +1 -1
  75. package/dist/triage/summary/reconcilable.d.ts +1 -1
  76. package/dist/triage/welcome/constants.d.ts +2 -2
  77. package/dist/triage/welcome/constants.js +2 -2
  78. package/dist/triage/welcome/onboard.js +8 -1
  79. package/dist/triage/welcome/prior-state.js +14 -2
  80. package/dist/triage/welcome/summary.js +14 -2
  81. package/dist/triage/welcome/writers.js +7 -1
  82. package/dist/validate-content/validate-strategy-output.js +11 -1
  83. package/dist/vbrief-build/parity-scenarios.js +3 -3
  84. package/dist/vbrief-reconcile/labels.js +16 -1
  85. package/dist/vbrief-reconcile/parity-scenarios.js +10 -10
  86. package/dist/vbrief-reconcile/umbrellas.js +16 -1
  87. package/dist/vbrief-validate/main.js +13 -1
  88. package/dist/vbrief-validate/precutover.js +33 -5
  89. package/dist/vbrief-validate/staleness.d.ts +1 -1
  90. package/dist/vbrief-validate/staleness.js +5 -2
  91. package/dist/verify-source/code-structure-validate.js +7 -1
  92. package/dist/xbrief-migrate/signpost.d.ts +4 -4
  93. package/dist/xbrief-migrate/signpost.js +11 -15
  94. package/package.json +3 -3
@@ -1,3 +1,4 @@
1
+ import { type OrgAutoEnableOptions } from "./value-feedback-autoenable.js";
1
2
  /** Canonical registered policy field name (matches other FIELD_* dotted paths). */
2
3
  export declare const FIELD_VALUE_FEEDBACK = "plan.policy.valueFeedback";
3
4
  /** Short alias accepted by `policy:show --field=valueFeedback` (#1709). */
@@ -16,7 +17,7 @@ export interface ValueFeedbackConfig {
16
17
  readonly sessionLine: boolean;
17
18
  readonly upstreamPrompt: boolean;
18
19
  }
19
- export type ValueFeedbackSource = "typed" | "default" | "default-on-error";
20
+ export type ValueFeedbackSource = "typed" | "org-auto" | "default" | "default-on-error";
20
21
  export interface ValueFeedbackResolved extends ValueFeedbackConfig {
21
22
  readonly source: ValueFeedbackSource;
22
23
  readonly error: string | null;
@@ -24,8 +25,20 @@ export interface ValueFeedbackResolved extends ValueFeedbackConfig {
24
25
  export declare const VALUE_FEEDBACK_CAPABILITY_COST_DISCLOSURE: string;
25
26
  /** Validate a `plan.policy.valueFeedback` payload. */
26
27
  export declare function validateValueFeedback(value: unknown): string[];
27
- /** Resolve `plan.policy.valueFeedback` from PROJECT-DEFINITION (#1709). */
28
- export declare function resolveValueFeedback(projectRoot: string): ValueFeedbackResolved;
28
+ export interface ResolveValueFeedbackOptions {
29
+ /** Test seam for origin-org auto-enable resolution (#2376). */
30
+ readonly autoEnable?: OrgAutoEnableOptions;
31
+ }
32
+ /**
33
+ * Resolve `plan.policy.valueFeedback` from PROJECT-DEFINITION (#1709).
34
+ *
35
+ * Precedence (#2376): an explicit typed `valueFeedback` block always wins
36
+ * (including `enabled: false`). Only when the typed flag is ABSENT does the
37
+ * trusted-org auto-enable layer apply -- for company-owned (deftai) repos it
38
+ * turns LOCAL emit + session readback ON while leaving network/upstream OFF.
39
+ * Any other repo (or no origin remote) stays OFF.
40
+ */
41
+ export declare function resolveValueFeedback(projectRoot: string, options?: ResolveValueFeedbackOptions): ValueFeedbackResolved;
29
42
  /** Master gate: when `enabled` is false, every downstream path is rejected. */
30
43
  export declare function isValueFeedbackPathAllowed(path: ValueFeedbackSubFlag, policy: ValueFeedbackResolved): boolean;
31
44
  /** Resolved per-path gate booleans for policy:show and enable status output. */
@@ -38,8 +51,20 @@ export interface ValueFeedbackPolicyField {
38
51
  readonly default: ValueFeedbackConfig;
39
52
  readonly source: string;
40
53
  }
41
- /** Inspector row for `policy:show --field=valueFeedback`. */
42
- export declare function inspectValueFeedback(data: Record<string, unknown> | null): ValueFeedbackPolicyField;
54
+ export interface InspectValueFeedbackOptions {
55
+ /** Test seam for origin-org auto-enable resolution (#2376). */
56
+ readonly autoEnable?: OrgAutoEnableOptions;
57
+ }
58
+ /**
59
+ * Inspector row for `policy:show --field=valueFeedback`.
60
+ *
61
+ * When `projectRoot` is supplied this MUST mirror {@link resolveValueFeedback}'s
62
+ * precedence exactly (#2377 review): with no explicit typed block, a trusted-org
63
+ * checkout resolves to `org-auto`/ON so `policy:show` never reports OFF while the
64
+ * ledger is actively collecting. Omitting `projectRoot` keeps the legacy
65
+ * data-only behavior for callers that only need the field name/shape.
66
+ */
67
+ export declare function inspectValueFeedback(data: Record<string, unknown> | null, projectRoot?: string, options?: InspectValueFeedbackOptions): ValueFeedbackPolicyField;
43
68
  export interface EnableValueFeedbackOptions {
44
69
  readonly confirm: boolean;
45
70
  readonly actor?: string;
@@ -2,6 +2,7 @@ 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
4
  import { appendAuditLog, loadProjectDefinition, projectDefinitionPath } from "./resolve.js";
5
+ import { isTrustedOrgAutoEnable } from "./value-feedback-autoenable.js";
5
6
  /** Canonical registered policy field name (matches other FIELD_* dotted paths). */
6
7
  export const FIELD_VALUE_FEEDBACK = "plan.policy.valueFeedback";
7
8
  /** Short alias accepted by `policy:show --field=valueFeedback` (#1709). */
@@ -33,6 +34,20 @@ function defaultResolved(source, error = null) {
33
34
  error,
34
35
  };
35
36
  }
37
+ /**
38
+ * Trusted-org auto-enable resolution (#2376): LOCAL emit + session readback ON,
39
+ * network/upstream OFF. Applies only when the typed flag is absent.
40
+ */
41
+ function orgAutoResolved() {
42
+ return {
43
+ enabled: true,
44
+ emitEvents: VALUE_FEEDBACK_SUBFLAG_DEFAULTS_WHEN_ENABLED.emitEvents,
45
+ sessionLine: VALUE_FEEDBACK_SUBFLAG_DEFAULTS_WHEN_ENABLED.sessionLine,
46
+ upstreamPrompt: false,
47
+ source: "org-auto",
48
+ error: null,
49
+ };
50
+ }
36
51
  function readSubFlag(block, key, masterEnabled) {
37
52
  if (!masterEnabled) {
38
53
  return false;
@@ -88,8 +103,16 @@ function resolveFromPolicyBlock(raw) {
88
103
  error: null,
89
104
  };
90
105
  }
91
- /** Resolve `plan.policy.valueFeedback` from PROJECT-DEFINITION (#1709). */
92
- export function resolveValueFeedback(projectRoot) {
106
+ /**
107
+ * Resolve `plan.policy.valueFeedback` from PROJECT-DEFINITION (#1709).
108
+ *
109
+ * Precedence (#2376): an explicit typed `valueFeedback` block always wins
110
+ * (including `enabled: false`). Only when the typed flag is ABSENT does the
111
+ * trusted-org auto-enable layer apply -- for company-owned (deftai) repos it
112
+ * turns LOCAL emit + session readback ON while leaving network/upstream OFF.
113
+ * Any other repo (or no origin remote) stays OFF.
114
+ */
115
+ export function resolveValueFeedback(projectRoot, options = {}) {
93
116
  const [data, err] = loadProjectDefinition(projectRoot);
94
117
  if (data === null) {
95
118
  return defaultResolved("default-on-error", err);
@@ -99,6 +122,9 @@ export function resolveValueFeedback(projectRoot) {
99
122
  policyBlock === null ||
100
123
  Array.isArray(policyBlock) ||
101
124
  !("valueFeedback" in policyBlock)) {
125
+ if (isTrustedOrgAutoEnable(projectRoot, options.autoEnable)) {
126
+ return orgAutoResolved();
127
+ }
102
128
  return defaultResolved("default");
103
129
  }
104
130
  return resolveFromPolicyBlock(policyBlock.valueFeedback);
@@ -126,57 +152,49 @@ export function formatValueFeedbackStatusLine(policy) {
126
152
  `sessionLine=${String(policy.sessionLine)} (path=${String(gates.sessionLine)}) ` +
127
153
  `upstreamPrompt=${String(policy.upstreamPrompt)} (path=${String(gates.upstreamPrompt)}).`);
128
154
  }
129
- function defaultInspectorConfig() {
130
- const defaults = defaultResolved("default");
131
- return {
132
- enabled: defaults.enabled,
133
- emitEvents: defaults.emitEvents,
134
- sessionLine: defaults.sessionLine,
135
- upstreamPrompt: defaults.upstreamPrompt,
136
- };
137
- }
138
- function buildDefaultInspectorField() {
155
+ function fieldFromResolved(resolved) {
139
156
  return {
140
157
  name: FIELD_VALUE_FEEDBACK,
141
- current: defaultInspectorConfig(),
158
+ current: {
159
+ enabled: resolved.enabled,
160
+ emitEvents: resolved.emitEvents,
161
+ sessionLine: resolved.sessionLine,
162
+ upstreamPrompt: resolved.upstreamPrompt,
163
+ },
142
164
  default: {
143
165
  enabled: DEFAULT_VALUE_FEEDBACK_ENABLED,
144
166
  emitEvents: false,
145
167
  sessionLine: false,
146
168
  upstreamPrompt: false,
147
169
  },
148
- source: "default",
170
+ source: resolved.source,
149
171
  };
150
172
  }
151
- /** Inspector row for `policy:show --field=valueFeedback`. */
152
- export function inspectValueFeedback(data) {
173
+ /**
174
+ * Inspector row for `policy:show --field=valueFeedback`.
175
+ *
176
+ * When `projectRoot` is supplied this MUST mirror {@link resolveValueFeedback}'s
177
+ * precedence exactly (#2377 review): with no explicit typed block, a trusted-org
178
+ * checkout resolves to `org-auto`/ON so `policy:show` never reports OFF while the
179
+ * ledger is actively collecting. Omitting `projectRoot` keeps the legacy
180
+ * data-only behavior for callers that only need the field name/shape.
181
+ */
182
+ export function inspectValueFeedback(data, projectRoot, options = {}) {
153
183
  if (data === null) {
154
- return buildDefaultInspectorField();
184
+ return fieldFromResolved(defaultResolved("default"));
155
185
  }
156
186
  const policyBlock = readPlanPolicy(data.plan);
157
187
  if (typeof policyBlock !== "object" ||
158
188
  policyBlock === null ||
159
189
  Array.isArray(policyBlock) ||
160
190
  !("valueFeedback" in policyBlock)) {
161
- return buildDefaultInspectorField();
191
+ if (projectRoot !== undefined && isTrustedOrgAutoEnable(projectRoot, options.autoEnable)) {
192
+ return fieldFromResolved(orgAutoResolved());
193
+ }
194
+ return fieldFromResolved(defaultResolved("default"));
162
195
  }
163
196
  const resolved = resolveFromPolicyBlock(policyBlock.valueFeedback);
164
- return {
165
- name: FIELD_VALUE_FEEDBACK,
166
- current: {
167
- enabled: resolved.enabled,
168
- emitEvents: resolved.emitEvents,
169
- sessionLine: resolved.sessionLine,
170
- upstreamPrompt: resolved.upstreamPrompt,
171
- },
172
- default: {
173
- enabled: DEFAULT_VALUE_FEEDBACK_ENABLED,
174
- emitEvents: false,
175
- sessionLine: false,
176
- upstreamPrompt: false,
177
- },
178
- source: resolved.source,
179
- };
197
+ return fieldFromResolved(resolved);
180
198
  }
181
199
  /** Persist `valueFeedback.enabled=true` after capability-cost disclosure (#1709). */
182
200
  export function enableValueFeedback(projectRoot, options) {
@@ -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);
@@ -9,7 +9,7 @@
9
9
  import { accessSync, constants, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
10
10
  import { basename, dirname, isAbsolute, join, resolve } from "node:path";
11
11
  import { referenceTypeMatches } from "@deftai/directive-types";
12
- import { hasArtifactSuffix, resolveLifecycleRoot } from "../layout/resolve.js";
12
+ import { hasArtifactSuffix, LEGACY_ARTIFACT_DIR, MIGRATED_ARTIFACT_DIR, resolveLifecycleRoot, } from "../layout/resolve.js";
13
13
  import { referenceWithDefaultTrust, slugify } from "../vbrief-build/build.js";
14
14
  import { EMITTED_VBRIEF_VERSION } from "../vbrief-build/constants.js";
15
15
  import { formatVbriefJson } from "./vbrief-json.js";
@@ -400,7 +400,17 @@ export function storyQualityIssues(opts) {
400
400
  // Path helpers
401
401
  // ---------------------------------------------------------------------------
402
402
  function vbriefDir(projectRoot) {
403
- return resolveLifecycleRoot(projectRoot);
403
+ try {
404
+ return resolveLifecycleRoot(projectRoot);
405
+ }
406
+ catch (err) {
407
+ // Re-throw if only vbrief/ exists (legacy-only); fall back to canonical xbrief/ path otherwise.
408
+ if (existsSync(join(projectRoot, LEGACY_ARTIFACT_DIR)) &&
409
+ !existsSync(join(projectRoot, MIGRATED_ARTIFACT_DIR))) {
410
+ throw err;
411
+ }
412
+ return join(projectRoot, MIGRATED_ARTIFACT_DIR);
413
+ }
404
414
  }
405
415
  function relToVbrief(vbriefDirPath, path) {
406
416
  const resolvedPath = resolve(path);
@@ -1,7 +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
+ import { hasArtifactSuffix, MIGRATED_ARTIFACT_DIR } from "../layout/resolve.js";
5
5
  export const RESUME_ELIGIBLE_DECISION = "resume-eligible";
6
6
  export const EVALUATOR_ACTOR = "agent:resume-evaluator";
7
7
  export const CACHE_DIR_NAME = ".deft-cache";
@@ -258,7 +258,7 @@ export function evaluate(expr, ctx) {
258
258
  throw new ResumeGrammarError(`unknown composition op ${JSON.stringify(expr.op)}`);
259
259
  }
260
260
  function countPending(projectRoot) {
261
- const folder = join(resolve(projectRoot), "vbrief", PENDING_LIFECYCLE_DIR);
261
+ const folder = join(resolve(projectRoot), MIGRATED_ARTIFACT_DIR, PENDING_LIFECYCLE_DIR);
262
262
  try {
263
263
  if (!statSync(folder).isDirectory())
264
264
  return 0;
@@ -3,7 +3,7 @@ export declare const RITUAL_STATE_SCHEMA_VERSION = 1;
3
3
  export declare const SENTINEL_RELPATH: readonly [".deft", "last-session.json"];
4
4
  export declare const RITUAL_STATE_RELPATH: readonly [".deft", "ritual-state.json"];
5
5
  export declare const MIN_RESUME_AGE_MS: number;
6
- export declare const ACTIVE_VBRIEF_PREFIX = "vbrief/active/";
6
+ export declare const ACTIVE_VBRIEF_PREFIX: string;
7
7
  export interface Sentinel {
8
8
  readonly schemaVersion: number;
9
9
  readonly deftVersion: string;
@@ -1,6 +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
+ import { hasArtifactSuffix, LEGACY_ARTIFACT_DIR, MIGRATED_ARTIFACT_DIR, } from "../layout/resolve.js";
4
4
  import { stableJson } from "./json.js";
5
5
  import { parseTimestamp, timestampIso } from "./time.js";
6
6
  export const SCHEMA_VERSION = 1;
@@ -8,7 +8,7 @@ export const RITUAL_STATE_SCHEMA_VERSION = 1;
8
8
  export const SENTINEL_RELPATH = [".deft", "last-session.json"];
9
9
  export const RITUAL_STATE_RELPATH = [".deft", "ritual-state.json"];
10
10
  export const MIN_RESUME_AGE_MS = 2 * 60 * 60 * 1000;
11
- export const ACTIVE_VBRIEF_PREFIX = "vbrief/active/";
11
+ export const ACTIVE_VBRIEF_PREFIX = `${MIGRATED_ARTIFACT_DIR}/active/`;
12
12
  export function ritualStatePath(projectRoot) {
13
13
  return join(resolve(projectRoot), ...RITUAL_STATE_RELPATH);
14
14
  }
@@ -249,7 +249,9 @@ export function writeSentinel(projectRoot, input) {
249
249
  schemaVersion: SCHEMA_VERSION,
250
250
  deftVersion: input.deftVersion,
251
251
  timestamp: timestampIso(instant),
252
- lastActiveVbrief: input.lastActiveVbrief.replace(/\\/g, "/"),
252
+ lastActiveVbrief: input.lastActiveVbrief
253
+ .replace(/\\/g, "/")
254
+ .replace(`${LEGACY_ARTIFACT_DIR}/active/`, `${MIGRATED_ARTIFACT_DIR}/active/`),
253
255
  lastBranch: input.lastBranch,
254
256
  };
255
257
  atomicWriteJson(sentinelFile, payload, ".last-session.");
@@ -290,7 +292,7 @@ export function computeResumeSignal(sentinel, now, projectRoot) {
290
292
  `${elapsedLabel} ago. Resume? Run \`task vbrief:show ${lastActive}\`.`);
291
293
  }
292
294
  export function detectLatestActiveVbrief(projectRoot) {
293
- const activeDir = join(resolve(projectRoot), "vbrief", "active");
295
+ const activeDir = join(resolve(projectRoot), MIGRATED_ARTIFACT_DIR, "active");
294
296
  try {
295
297
  if (!existsSync(activeDir)) {
296
298
  return null;
@@ -14,5 +14,5 @@ export declare const CHILD_ALLOWED_FIELDS: Set<"n" | "url" | "wave" | "role">;
14
14
  export declare const UUID_RE: RegExp;
15
15
  export declare const ISO8601_RE: RegExp;
16
16
  export declare const WAVE_FLAG_RE: RegExp;
17
- export declare const PROJECT_ROOT_SENTINELS: readonly ["vbrief", ".git"];
17
+ export declare const PROJECT_ROOT_SENTINELS: readonly ["xbrief", "vbrief", ".git"];
18
18
  //# sourceMappingURL=constants.d.ts.map
@@ -26,5 +26,5 @@ export const CHILD_ALLOWED_FIELDS = new Set(CHILD_REQUIRED_FIELDS);
26
26
  export const UUID_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
27
27
  export const ISO8601_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/;
28
28
  export const WAVE_FLAG_RE = /^--wave-(\d+)(?:=(.*))?$/;
29
- export const PROJECT_ROOT_SENTINELS = ["vbrief", ".git"];
29
+ export const PROJECT_ROOT_SENTINELS = ["xbrief", "vbrief", ".git"];
30
30
  //# sourceMappingURL=constants.js.map
@@ -77,7 +77,7 @@ export function resolveRootAndRepo(cliProjectRoot, cliRepo, requireRepo) {
77
77
  export function formatMissingRootError() {
78
78
  return ("error: cannot determine project root. Pass --project-root PATH, " +
79
79
  "set $DEFT_PROJECT_ROOT, or run from inside a directory tree that " +
80
- "contains vbrief/ or .git/ (#535).");
80
+ "contains xbrief/ or .git/ (#535).");
81
81
  }
82
82
  export function formatMissingRepoError() {
83
83
  return ("error: cannot determine repo slug. Pass --repo OWNER/NAME, " +
@@ -5,8 +5,15 @@ import { SPEC_RENDER_BANNER } from "../render/constants.js";
5
5
  import { contentHasGeneratedPdSource, contentHasGeneratedSpecSource, EXPORT_SPEC_PD_BANNER, GENERATED_SPEC_PURPOSE, } from "./constants.js";
6
6
  export function resolveSpecAuthority(projectRoot) {
7
7
  const root = projectRoot;
8
- const layout = resolveLifecycleLayout(root);
9
- const vbriefDir = resolveLifecycleRoot(root);
8
+ let layout;
9
+ let vbriefDir;
10
+ try {
11
+ layout = resolveLifecycleLayout(root);
12
+ vbriefDir = resolveLifecycleRoot(root);
13
+ }
14
+ catch {
15
+ return null; // No xbrief/ layout; spec authority unavailable.
16
+ }
10
17
  const projectDefPath = resolveProjectDefinitionPath(root);
11
18
  if (!existsSync(projectDefPath))
12
19
  return null;
@@ -230,7 +230,20 @@ function completeParent(parentPath, vbriefDir, projectRoot, settled, dryRun) {
230
230
  };
231
231
  }
232
232
  export function sweepCohort(storyPaths, projectRoot, dryRun) {
233
- const vbriefDir = resolveLifecycleRoot(projectRoot);
233
+ let vbriefDir;
234
+ try {
235
+ vbriefDir = resolveLifecycleRoot(projectRoot);
236
+ }
237
+ catch {
238
+ return {
239
+ project_root: resolve(projectRoot),
240
+ dry_run: dryRun,
241
+ stories: [],
242
+ parents: [],
243
+ errors: [],
244
+ ok: true,
245
+ };
246
+ }
234
247
  const result = {
235
248
  project_root: resolve(projectRoot),
236
249
  dry_run: dryRun,
@@ -1,7 +1,7 @@
1
1
  import { existsSync, readdirSync, readFileSync } from "node:fs";
2
2
  import { basename, join, resolve } from "node:path";
3
3
  import { referenceTypeMatches } from "@deftai/directive-types";
4
- import { ARTIFACT_SUFFIXES, hasArtifactSuffix, LIFECYCLE_DIR_NAMES, resolveLifecycleLayout, resolveLifecycleRoot, stripArtifactSuffix, } from "../layout/resolve.js";
4
+ import { ARTIFACT_SUFFIXES, hasArtifactSuffix, LEGACY_ARTIFACT_DIR, LIFECYCLE_DIR_NAMES, MIGRATED_ARTIFACT_DIR, MIGRATED_ARTIFACT_SUFFIX, MIGRATED_INFO_ROOT_KEY, resolveLifecycleLayout, resolveLifecycleRoot, stripArtifactSuffix, } from "../layout/resolve.js";
5
5
  import { acceptanceTextsFromItems, asStrList, deprecatedSubitemsIssues, itemHasTraces, itemsHaveAcceptance, missingRequiredSwarmFields, storyQualityIssues, } from "../vbrief-validation/story-quality.js";
6
6
  import { LIFECYCLE_FOLDERS, READY } from "./constants.js";
7
7
  function loadJson(path) {
@@ -26,7 +26,31 @@ function projectRel(projectRoot, path) {
26
26
  }
27
27
  }
28
28
  function expandPaths(projectRoot, patterns) {
29
- const layout = resolveLifecycleLayout(projectRoot);
29
+ // Use the canonical xbrief layout; re-throw only if a legacy vbrief/-only tree is detected (#2112).
30
+ let layout;
31
+ try {
32
+ layout = resolveLifecycleLayout(projectRoot);
33
+ }
34
+ catch (err) {
35
+ const xbriefDir = join(projectRoot, MIGRATED_ARTIFACT_DIR);
36
+ if (existsSync(xbriefDir)) {
37
+ // xbrief/ dir exists but has no artifacts yet -- treat as valid (empty) layout.
38
+ layout = {
39
+ root: xbriefDir,
40
+ artifactDir: MIGRATED_ARTIFACT_DIR,
41
+ artifactSuffix: MIGRATED_ARTIFACT_SUFFIX,
42
+ infoRootKey: MIGRATED_INFO_ROOT_KEY,
43
+ migrated: true,
44
+ };
45
+ }
46
+ else if (existsSync(join(projectRoot, LEGACY_ARTIFACT_DIR))) {
47
+ throw err; // Legacy-only project: operator must run deft migrate:xbrief.
48
+ }
49
+ else {
50
+ // Neither exists -- return empty list.
51
+ return [];
52
+ }
53
+ }
30
54
  const usePatterns = patterns.length > 0 ? patterns : [`${layout.artifactDir}/active/*${layout.artifactSuffix}`];
31
55
  const out = [];
32
56
  const seen = new Set();
@@ -210,7 +234,13 @@ function acceptanceCountJustification(plan, swarm) {
210
234
  }
211
235
  function allScopeIds(projectRoot) {
212
236
  const ids = new Map();
213
- const vbriefDir = resolveLifecycleRoot(projectRoot);
237
+ let vbriefDir;
238
+ try {
239
+ vbriefDir = resolveLifecycleRoot(projectRoot);
240
+ }
241
+ catch {
242
+ return ids; // No xbrief/ layout; no scope IDs.
243
+ }
214
244
  for (const folder of LIFECYCLE_FOLDERS) {
215
245
  const dir = join(vbriefDir, folder);
216
246
  if (!existsSync(dir)) {
@@ -72,10 +72,10 @@ export function runChangeInit(projectRoot, name, io) {
72
72
  return 1;
73
73
  }
74
74
  mkdirSync(join(base, "specs"), { recursive: true });
75
- writeFileSync(join(base, "proposal.vbrief.json"), `${JSON.stringify(proposalTemplate(trimmed), null, 2)}\n`, "utf8");
76
- writeFileSync(join(base, "tasks.vbrief.json"), `${JSON.stringify(tasksTemplate(trimmed), null, 2)}\n`, "utf8");
75
+ writeFileSync(join(base, "proposal.xbrief.json"), `${JSON.stringify(proposalTemplate(trimmed), null, 2)}\n`, "utf8");
76
+ writeFileSync(join(base, "tasks.xbrief.json"), `${JSON.stringify(tasksTemplate(trimmed), null, 2)}\n`, "utf8");
77
77
  io.writeOut(`OK: Created change proposal at ${base}/\n`);
78
- for (const file of ["proposal.vbrief.json", "tasks.vbrief.json", "specs/"]) {
78
+ for (const file of ["proposal.xbrief.json", "tasks.xbrief.json", "specs/"]) {
79
79
  io.writeOut(` - ${file}\n`);
80
80
  }
81
81
  return 0;
@@ -1,7 +1,7 @@
1
1
  import { resolveCandidatesLogPath } from "../cache-path.js";
2
2
  import type { AuditEntry, CandidatesLog } from "./types.js";
3
3
  /** Display/back-compat constant; resolution flows through resolveTriageCachePath (#1703). */
4
- export declare const AUDIT_LOG_REL_PATH = "vbrief/.triage-cache/candidates.jsonl";
4
+ export declare const AUDIT_LOG_REL_PATH = "xbrief/.triage-cache/candidates.jsonl";
5
5
  export { resolveCandidatesLogPath };
6
6
  /** Read every well-formed audit-log row in insertion order (#1698 shared reader). */
7
7
  export declare function readAuditLog(logPath: string, repo?: string | null): AuditEntry[];