@deftai/directive-core 0.99.0 → 0.101.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 (166) hide show
  1. package/dist/authz/actions.d.ts +6 -0
  2. package/dist/authz/actions.js +3 -0
  3. package/dist/authz/classify.js +241 -30
  4. package/dist/authz/decompose-apply.d.ts +81 -0
  5. package/dist/authz/decompose-apply.js +377 -0
  6. package/dist/authz/index.d.ts +3 -1
  7. package/dist/authz/index.js +3 -1
  8. package/dist/authz/store.d.ts +53 -0
  9. package/dist/authz/store.js +219 -1
  10. package/dist/authz/types.d.ts +19 -2
  11. package/dist/authz/types.js +5 -1
  12. package/dist/check/cached-orchestrator.d.ts +26 -5
  13. package/dist/check/cached-orchestrator.js +228 -6
  14. package/dist/check/gate-lists.d.ts +10 -4
  15. package/dist/check/gate-lists.js +19 -5
  16. package/dist/check/index.d.ts +3 -2
  17. package/dist/check/index.js +2 -1
  18. package/dist/check/named-cause.d.ts +45 -0
  19. package/dist/check/named-cause.js +121 -0
  20. package/dist/check/orchestrator.js +6 -1
  21. package/dist/doctor/checks.d.ts +44 -0
  22. package/dist/doctor/checks.js +428 -2
  23. package/dist/doctor/help.d.ts +5 -0
  24. package/dist/doctor/help.js +31 -0
  25. package/dist/doctor/index.d.ts +2 -0
  26. package/dist/doctor/index.js +2 -0
  27. package/dist/doctor/main.d.ts +6 -7
  28. package/dist/doctor/main.js +130 -79
  29. package/dist/doctor/session-coda.d.ts +82 -0
  30. package/dist/doctor/session-coda.js +151 -0
  31. package/dist/doctor/types.d.ts +16 -0
  32. package/dist/doctor/which.d.ts +20 -1
  33. package/dist/doctor/which.js +67 -1
  34. package/dist/finish-loop/pr-finish-loop.d.ts +13 -2
  35. package/dist/finish-loop/pr-finish-loop.js +116 -5
  36. package/dist/finish-loop/types.d.ts +1 -1
  37. package/dist/hooks/dispatcher.d.ts +9 -5
  38. package/dist/hooks/dispatcher.js +23 -15
  39. package/dist/hooks/readonly.d.ts +6 -3
  40. package/dist/hooks/readonly.js +44 -25
  41. package/dist/index.d.ts +3 -0
  42. package/dist/index.js +3 -0
  43. package/dist/init-deposit/agent-hooks.d.ts +20 -0
  44. package/dist/init-deposit/agent-hooks.js +51 -31
  45. package/dist/init-deposit/gitignore.js +2 -0
  46. package/dist/intake/issue-ingest.js +36 -0
  47. package/dist/lifecycle/completed-consistency.d.ts +60 -0
  48. package/dist/lifecycle/completed-consistency.js +443 -0
  49. package/dist/lifecycle/completed-tracked-on-delivery.d.ts +47 -0
  50. package/dist/lifecycle/completed-tracked-on-delivery.js +415 -0
  51. package/dist/lifecycle/index.d.ts +2 -0
  52. package/dist/lifecycle/index.js +2 -0
  53. package/dist/literal-acceptance/capture.d.ts +52 -0
  54. package/dist/literal-acceptance/capture.js +658 -0
  55. package/dist/literal-acceptance/evaluate.d.ts +47 -0
  56. package/dist/literal-acceptance/evaluate.js +193 -0
  57. package/dist/literal-acceptance/index.d.ts +12 -0
  58. package/dist/literal-acceptance/index.js +12 -0
  59. package/dist/literal-acceptance/run.d.ts +38 -0
  60. package/dist/literal-acceptance/run.js +216 -0
  61. package/dist/literal-acceptance/safety.d.ts +24 -0
  62. package/dist/literal-acceptance/safety.js +251 -0
  63. package/dist/literal-acceptance/types.d.ts +82 -0
  64. package/dist/literal-acceptance/types.js +26 -0
  65. package/dist/policy/ac-pass-banking.d.ts +58 -0
  66. package/dist/policy/ac-pass-banking.js +150 -0
  67. package/dist/policy/ceremony-dial.d.ts +96 -11
  68. package/dist/policy/ceremony-dial.js +250 -23
  69. package/dist/policy/index.d.ts +2 -0
  70. package/dist/policy/index.js +16 -1
  71. package/dist/policy/merge-approval-head.d.ts +129 -0
  72. package/dist/policy/merge-approval-head.js +461 -0
  73. package/dist/policy/require-human-merge.d.ts +5 -0
  74. package/dist/policy/require-human-merge.js +5 -0
  75. package/dist/pr-merge-readiness/ci-gate.d.ts +20 -3
  76. package/dist/pr-merge-readiness/ci-gate.js +65 -5
  77. package/dist/pr-merge-readiness/compute.d.ts +13 -0
  78. package/dist/pr-merge-readiness/compute.js +99 -14
  79. package/dist/pr-merge-readiness/gh.d.ts +49 -0
  80. package/dist/pr-merge-readiness/gh.js +268 -0
  81. package/dist/pr-merge-readiness/index.d.ts +2 -2
  82. package/dist/pr-merge-readiness/index.js +2 -2
  83. package/dist/pr-merge-readiness/test-gh-fixtures.helpers.js +4 -0
  84. package/dist/pr-wait-mergeable/cascade.d.ts +11 -0
  85. package/dist/pr-wait-mergeable/cascade.js +115 -1
  86. package/dist/pr-wait-mergeable/main.d.ts +4 -0
  87. package/dist/pr-wait-mergeable/main.js +4 -0
  88. package/dist/pr-wait-mergeable/types.d.ts +3 -1
  89. package/dist/pr-wait-mergeable/wrappers.d.ts +6 -1
  90. package/dist/pr-wait-mergeable/wrappers.js +7 -1
  91. package/dist/preflight/evaluate.d.ts +15 -1
  92. package/dist/preflight/evaluate.js +33 -2
  93. package/dist/preflight-cache/evaluate.d.ts +3 -0
  94. package/dist/preflight-cache/evaluate.js +3 -0
  95. package/dist/product-first-done-gate/acceptance.d.ts +25 -0
  96. package/dist/product-first-done-gate/acceptance.js +247 -0
  97. package/dist/product-first-done-gate/check-mode.d.ts +45 -0
  98. package/dist/product-first-done-gate/check-mode.js +148 -0
  99. package/dist/product-first-done-gate/evaluate.d.ts +50 -0
  100. package/dist/product-first-done-gate/evaluate.js +255 -0
  101. package/dist/product-first-done-gate/index.d.ts +11 -0
  102. package/dist/product-first-done-gate/index.js +11 -0
  103. package/dist/product-first-done-gate/types.d.ts +64 -0
  104. package/dist/product-first-done-gate/types.js +61 -0
  105. package/dist/render/framework-commands.js +4 -0
  106. package/dist/run-summary/emit.d.ts +56 -0
  107. package/dist/run-summary/emit.js +171 -0
  108. package/dist/run-summary/index.d.ts +4 -0
  109. package/dist/run-summary/index.js +4 -0
  110. package/dist/run-summary/path.d.ts +25 -0
  111. package/dist/run-summary/path.js +78 -0
  112. package/dist/run-summary/types.d.ts +76 -0
  113. package/dist/run-summary/types.js +21 -0
  114. package/dist/scope/acceptance-evidence.d.ts +110 -0
  115. package/dist/scope/acceptance-evidence.js +422 -0
  116. package/dist/scope/coverage-map.d.ts +98 -0
  117. package/dist/scope/coverage-map.js +558 -0
  118. package/dist/scope/decompose.d.ts +5 -0
  119. package/dist/scope/decompose.js +134 -11
  120. package/dist/scope/index.d.ts +3 -0
  121. package/dist/scope/index.js +3 -0
  122. package/dist/scope/parent-lineage.d.ts +125 -0
  123. package/dist/scope/parent-lineage.js +626 -0
  124. package/dist/scope/transition.d.ts +8 -0
  125. package/dist/scope/transition.js +61 -2
  126. package/dist/session/ac-pass-banking.d.ts +221 -0
  127. package/dist/session/ac-pass-banking.js +761 -0
  128. package/dist/session/active-cli.d.ts +79 -0
  129. package/dist/session/active-cli.js +382 -0
  130. package/dist/session/deposit-sha.d.ts +49 -0
  131. package/dist/session/deposit-sha.js +121 -0
  132. package/dist/session/effort-budget.d.ts +136 -0
  133. package/dist/session/effort-budget.js +352 -0
  134. package/dist/session/index.d.ts +7 -0
  135. package/dist/session/index.js +7 -0
  136. package/dist/session/orientation-compression.d.ts +127 -0
  137. package/dist/session/orientation-compression.js +425 -0
  138. package/dist/session/orientation-state.d.ts +29 -0
  139. package/dist/session/orientation-state.js +85 -0
  140. package/dist/session/session-start.d.ts +31 -0
  141. package/dist/session/session-start.js +228 -2
  142. package/dist/session/toolchain-preflight.d.ts +64 -0
  143. package/dist/session/toolchain-preflight.js +151 -0
  144. package/dist/session/verify-session-ritual.d.ts +14 -0
  145. package/dist/session/verify-session-ritual.js +33 -0
  146. package/dist/story-ready/evaluate.d.ts +11 -0
  147. package/dist/story-ready/evaluate.js +41 -3
  148. package/dist/triage/actions/candidates-log.d.ts +1 -2
  149. package/dist/triage/actions/candidates-log.js +37 -6
  150. package/dist/triage/bootstrap/gitignore.d.ts +1 -1
  151. package/dist/triage/bootstrap/gitignore.js +8 -2
  152. package/dist/triage/bootstrap/index.js +15 -3
  153. package/dist/triage/classify/index.d.ts +1 -0
  154. package/dist/triage/classify/index.js +2 -0
  155. package/dist/triage/classify/label-mirror.js +16 -0
  156. package/dist/triage/classify/mirror-discovery-tip.d.ts +95 -0
  157. package/dist/triage/classify/mirror-discovery-tip.js +234 -0
  158. package/dist/triage/reconcile/reconcile.js +28 -6
  159. package/dist/triage/summary/index.js +12 -4
  160. package/dist/triage/welcome/default-mode.js +16 -0
  161. package/dist/ts-check-lane/run-lane.js +21 -4
  162. package/dist/vbrief-validate/conformance.js +7 -0
  163. package/dist/vitest-runner/coverage-debt-teardown.js +16 -4
  164. package/dist/xbrief-migrate/migrate-project.d.ts +4 -0
  165. package/dist/xbrief-migrate/migrate-project.js +92 -3
  166. package/package.json +15 -3
@@ -1,7 +1,8 @@
1
1
  import { execFileSync } from "node:child_process";
2
2
  import { randomUUID } from "node:crypto";
3
- import { appendFileSync, mkdirSync } from "node:fs";
4
- import { dirname, resolve } from "node:path";
3
+ import { mkdirSync } from "node:fs";
4
+ import { dirname, isAbsolute, relative, resolve } from "node:path";
5
+ import { containedWrite } from "../../fs/contained-write.js";
5
6
  import { resolveLifecycleFolder } from "../../layout/resolve.js";
6
7
  import { resolveCandidatesLogPath } from "../cache-path.js";
7
8
  import { auditKey, existingAuditRefs, scanLifecycleRefs } from "./audit.js";
@@ -127,9 +128,30 @@ function buildReconcileEntry(repo, issueNumber, sourceFolder) {
127
128
  "with a github-issue reference but no prior decision in the audit log",
128
129
  };
129
130
  }
130
- function appendAuditEntry(auditPath, entry) {
131
- mkdirSync(dirname(auditPath), { recursive: true });
132
- appendFileSync(auditPath, `${JSON.stringify(entry)}\n`, "utf8");
131
+ /**
132
+ * Containment root for reconcile audit appends (#3288 / #3245).
133
+ * Prefer project root when the log is nested under it; otherwise parent dir.
134
+ */
135
+ function containmentRootForAudit(projectRoot, auditPath) {
136
+ const rootAbs = resolve(projectRoot);
137
+ const logAbs = resolve(auditPath);
138
+ const parent = dirname(logAbs);
139
+ mkdirSync(parent, { recursive: true });
140
+ const rel = relative(rootAbs, logAbs);
141
+ if (rel.length > 0 && !rel.startsWith("..") && !isAbsolute(rel)) {
142
+ return rootAbs;
143
+ }
144
+ return resolve(parent);
145
+ }
146
+ /** #3288 / #3245 / #2980: product append refuses leaf symlink follow. */
147
+ function appendAuditEntry(projectRoot, auditPath, entry) {
148
+ const root = containmentRootForAudit(projectRoot, auditPath);
149
+ containedWrite({
150
+ root,
151
+ target: auditPath,
152
+ data: `${JSON.stringify(entry)}\n`,
153
+ mode: "append",
154
+ });
133
155
  }
134
156
  export function reconcile(projectRoot, options = {}) {
135
157
  const root = resolve(projectRoot);
@@ -158,7 +180,7 @@ export function reconcile(projectRoot, options = {}) {
158
180
  for (const item of items) {
159
181
  const entry = buildReconcileEntry(item.repo, item.issueNumber, item.folder);
160
182
  try {
161
- appendAuditEntry(auditPath, entry);
183
+ appendAuditEntry(root, auditPath, entry);
162
184
  }
163
185
  catch (err) {
164
186
  return {
@@ -1,6 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
- import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync } from "node:fs";
3
- import { join, resolve as pathResolve } from "node:path";
2
+ import { existsSync, mkdirSync, readdirSync, readFileSync } from "node:fs";
3
+ import { dirname, join, resolve as pathResolve } from "node:path";
4
+ import { containedWrite } from "../../fs/contained-write.js";
4
5
  import { hasArtifactSuffix, resolveLifecycleFolder, resolveProjectDefinitionPath, } from "../../layout/resolve.js";
5
6
  import { readPlanPolicy } from "../../policy/plan-extensions.js";
6
7
  import { loadProjectDefinition, PROJECT_DEFINITION_REL_PATH } from "../../policy/resolve.js";
@@ -542,8 +543,15 @@ export function appendHistory(historyPath, result, line, options = {}) {
542
543
  });
543
544
  const payload = pythonStyleStringify(record);
544
545
  try {
545
- mkdirSync(join(historyPath, ".."), { recursive: true });
546
- appendFileSync(historyPath, `${payload}\n`, { encoding: "utf8" });
546
+ // #3288 / #3245 / #2980: refuse leaf symlink follow (in-tree diversion).
547
+ const parent = dirname(historyPath);
548
+ mkdirSync(parent, { recursive: true });
549
+ containedWrite({
550
+ root: pathResolve(parent),
551
+ target: historyPath,
552
+ data: `${payload}\n`,
553
+ mode: "append",
554
+ });
547
555
  }
548
556
  catch {
549
557
  // observability only — never crash the ritual
@@ -1,6 +1,7 @@
1
1
  import { resolve } from "node:path";
2
2
  import { ensureTriageCacheHydrated } from "../../cache/empty-populate.js";
3
3
  import { maybeSelfHealCache } from "../../cache/fetch.js";
4
+ import { maybeFormatMirrorDiscoveryTip } from "../classify/mirror-discovery-tip.js";
4
5
  import { FIRST_TIME_NUDGE, INCOMPLETE_NUDGE_TEMPLATE } from "./constants.js";
5
6
  import { classifyOnboarding, detectPriorState } from "./prior-state.js";
6
7
  import { emitOneliner } from "./summary.js";
@@ -52,6 +53,21 @@ export function runDefaultMode(projectRoot, options = {}) {
52
53
  else if (label === "incomplete") {
53
54
  out(INCOMPLETE_NUDGE_TEMPLATE.replace("{missing}", missing.join(" + ")).replace(canonicalOnboard, onboardCommand));
54
55
  }
56
+ // #3124: throttled SCM label-mirror discovery tip (existence + get-the-most).
57
+ // Cold session:start runs this welcome path; re-arm skips welcome so tip is not spammed.
58
+ try {
59
+ const tip = maybeFormatMirrorDiscoveryTip(projectRoot, {
60
+ ...(options.now !== undefined ? { now: options.now } : {}),
61
+ });
62
+ if (tip.length > 0) {
63
+ for (const line of tip.trimEnd().split(/\r?\n/)) {
64
+ out(line);
65
+ }
66
+ }
67
+ }
68
+ catch {
69
+ // Discovery tip is advisory — never fail welcome.
70
+ }
55
71
  return outcome;
56
72
  }
57
73
  //# sourceMappingURL=default-mode.js.map
@@ -51,12 +51,12 @@ export function shouldUseShellForCommand(command, platform = process.platform) {
51
51
  return platform === "win32" && /\.(?:cmd|bat)$/i.test(command);
52
52
  }
53
53
  /** Default runner: an inherited-stdio pnpm invocation. */
54
- function defaultRunner(argv, cwd) {
54
+ function defaultRunner(argv, cwd, envOverride) {
55
55
  const [command, ...rest] = argv;
56
56
  const commandPath = command ?? "";
57
57
  const result = spawnSync(commandPath, rest, {
58
58
  cwd,
59
- env: sanitizeTsLaneEnv(process.env),
59
+ env: envOverride ?? sanitizeTsLaneEnv(process.env),
60
60
  stdio: "inherit",
61
61
  shell: shouldUseShellForCommand(commandPath),
62
62
  });
@@ -108,10 +108,27 @@ export function runTsLane(projectRoot, options) {
108
108
  }
109
109
  for (const command of LANE_COMMANDS) {
110
110
  const argv = [pnpm, ...command];
111
+ // Soft-pass via lane-private env (vitest 3 CAC rejects unknown CLI debt tokens).
112
+ // vitest.config reads DEFT_TS_LANE_COVERAGE_DEBT without DEFT_RELEASE_PREFLIGHT
113
+ // (sanitizeTsLaneEnv strips preflight). Refs #2573 / #2618.
114
+ const prevDebt = process.env.DEFT_TS_LANE_COVERAGE_DEBT;
111
115
  if (debtIssue !== null && command[1] === "test") {
112
- argv.push("--", `--allow-coverage-debt=${debtIssue}`);
116
+ process.env.DEFT_TS_LANE_COVERAGE_DEBT = String(debtIssue);
117
+ }
118
+ let result;
119
+ try {
120
+ result = runner(argv, projectRoot);
121
+ }
122
+ finally {
123
+ if (debtIssue !== null && command[1] === "test") {
124
+ if (prevDebt === undefined) {
125
+ delete process.env.DEFT_TS_LANE_COVERAGE_DEBT;
126
+ }
127
+ else {
128
+ process.env.DEFT_TS_LANE_COVERAGE_DEBT = prevDebt;
129
+ }
130
+ }
113
131
  }
114
- const result = runner(argv, projectRoot);
115
132
  const code = result.status;
116
133
  // A null status means the child was terminated by a signal (SIGKILL / OOM /
117
134
  // SIGTERM) before it could exit. Mapping that to 0 would silently pass a
@@ -28,6 +28,8 @@ export const PLAN_CORE = new Set([
28
28
  "changeLog",
29
29
  "sequence",
30
30
  "fork",
31
+ // Product-first done-gate (#3284): plan.acceptance.commands / none_stated / source_rung.
32
+ "acceptance",
31
33
  ]);
32
34
  export const ITEM_CORE = new Set([
33
35
  "id",
@@ -62,6 +64,11 @@ export const ITEM_CORE = new Set([
62
64
  "lastModifiedBy",
63
65
  "lockedBy",
64
66
  "items",
67
+ // Optional PlanItem.effort enum S/M/L/XL (#1581) — schema-validated, core not extension.
68
+ "effort",
69
+ // #3305 Option B: acceptance evidence/disposition are NOT ITEM_CORE.
70
+ // Canonical keys: plan.items[].x-directive/evidence and x-directive/disposition
71
+ // (accepted via EXTENSION_PREFIXES). Bare evidence/disposition fail #1620.
65
72
  ]);
66
73
  export const EXTENSION_PREFIXES = ["x-directive/", "x-vbrief/", "x-xbrief/"];
67
74
  export function renderFinding(finding) {
@@ -1,14 +1,26 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { COVERAGE_GOAL, countRecentCoverageDebtMentions, formatCoverageAttribution, formatOveruseWarning, metricsBelowGoal, readCoverageTotalsFromReport, resolveCoverageDebtIssue, } from "./coverage-debt.js";
4
- /** Vitest globalTeardown: enforce coverage goal or debt soft-pass (#2573). */
5
- export default async function coverageDebtTeardown() {
4
+ function resolveDebtIssueForTeardown() {
6
5
  const resolution = resolveCoverageDebtIssue(process.argv, process.env);
7
- if (resolution.kind === "none")
8
- return;
6
+ if (resolution.kind === "valid")
7
+ return resolution;
9
8
  if (resolution.kind === "invalid") {
10
9
  throw new Error(`coverage-debt: ${resolution.reason}`);
11
10
  }
11
+ const laneRaw = process.env.DEFT_TS_LANE_COVERAGE_DEBT;
12
+ if (laneRaw !== undefined && /^\d+$/.test(laneRaw.trim())) {
13
+ const issue = Number.parseInt(laneRaw.trim(), 10);
14
+ if (issue > 0)
15
+ return { kind: "valid", issue };
16
+ }
17
+ return { kind: "none" };
18
+ }
19
+ /** Vitest globalTeardown: enforce coverage goal or debt soft-pass (#2573). */
20
+ export default async function coverageDebtTeardown() {
21
+ const resolution = resolveDebtIssueForTeardown();
22
+ if (resolution.kind === "none")
23
+ return;
12
24
  const repoRoot = join(import.meta.dirname, "..", "..", "..", "..");
13
25
  const totals = readCoverageTotalsFromReport(join(repoRoot, "coverage"));
14
26
  if (totals === null) {
@@ -24,6 +24,10 @@ export type XbriefMigrationOutcome = {
24
24
  readonly kind: "migrated";
25
25
  readonly backupDir: string;
26
26
  readonly files: number;
27
+ } | {
28
+ readonly kind: "rewritten";
29
+ readonly files: number;
30
+ readonly message: string;
27
31
  } | {
28
32
  readonly kind: "converged";
29
33
  readonly action: VbriefConvergeAction;
@@ -5,12 +5,12 @@ import { assertWriteTargetSafe, ProjectionContainmentError } from "../fs/project
5
5
  import { checkGitClean } from "../migrate-preflight/index.js";
6
6
  import { applyAgentsRefresh } from "../platform/agents-md.js";
7
7
  import { patchAgentsMdHeader, renderHeaderPatchSummary } from "./agents-header.js";
8
- import { LEGACY_ARTIFACT_DIR, LEGACY_ARTIFACT_SUFFIX, MIGRATED_ARTIFACT_DIR, MIGRATED_ARTIFACT_SUFFIX, OBSOLETE_FRAMEWORK_NARRATIVE_FILENAME, VBRIEF_DEPRECATION_MARKER_BODY, VBRIEF_DEPRECATION_MARKER_FILENAME, } from "./constants.js";
8
+ import { LEGACY_ARTIFACT_DIR, LEGACY_ARTIFACT_SUFFIX, LEGACY_VBRIEF_VERSION, MIGRATED_ARTIFACT_DIR, MIGRATED_ARTIFACT_SUFFIX, OBSOLETE_FRAMEWORK_NARRATIVE_FILENAME, VBRIEF_DEPRECATION_MARKER_BODY, VBRIEF_DEPRECATION_MARKER_FILENAME, } from "./constants.js";
9
9
  import { detectLegacyVbriefLayout, detectXbriefConvergence } from "./detect.js";
10
10
  import { hasVbriefDeprecationMarker, isDirectory, isEffectivelyEmptyDir } from "./fs-helpers.js";
11
11
  import { assertMigrationSourceSafe } from "./migration-containment.js";
12
12
  import { renderXbriefMigrationLine, xbriefMigrationGuidance } from "./signpost.js";
13
- import { rewriteEmbeddedTokens, transformArtifactV06ToV08Transactional } from "./transforms.js";
13
+ import { readDeclaredArtifactVersion, rewriteEmbeddedTokens, transformArtifactV06ToV08Transactional, } from "./transforms.js";
14
14
  function collectFiles(root, acc = []) {
15
15
  if (!isDirectory(root)) {
16
16
  return acc;
@@ -74,6 +74,65 @@ function writeMigratedFile(projectRoot, srcPath, destPath) {
74
74
  mode: "replace",
75
75
  });
76
76
  }
77
+ /**
78
+ * Plan in-place hybrid envelope rewrites for xbrief lifecycle `.xbrief.json`
79
+ * artifacts that still declare `xBRIEFInfo@0.6` after the layout rename
80
+ * (#3236 / #2974). Pure probe — no writes. Already-0.8 artifacts, schema
81
+ * deposits, and non-0.6 JSON are skipped so residual #2368 schema-only markers
82
+ * still route to `directive update`.
83
+ */
84
+ function planHybridEnvelopeRewrites(projectRoot) {
85
+ const migratedDir = join(projectRoot, MIGRATED_ARTIFACT_DIR);
86
+ const artifactPaths = collectFiles(migratedDir).filter((path) => path.endsWith(MIGRATED_ARTIFACT_SUFFIX));
87
+ const pending = [];
88
+ for (const filePath of artifactPaths) {
89
+ let parsed;
90
+ try {
91
+ parsed = JSON.parse(readFileSync(filePath, "utf8"));
92
+ }
93
+ catch (err) {
94
+ const detail = err instanceof Error ? err.message : String(err);
95
+ return {
96
+ ok: false,
97
+ error: `failed to parse hybrid envelope candidate ${relative(projectRoot, filePath)}: ${detail}`,
98
+ };
99
+ }
100
+ // Only residual v0.6 envelopes are rewrite candidates; skip 0.8 and unknown.
101
+ if (readDeclaredArtifactVersion(parsed) !== LEGACY_VBRIEF_VERSION) {
102
+ continue;
103
+ }
104
+ const result = transformArtifactV06ToV08Transactional(parsed);
105
+ if (!result.ok) {
106
+ return {
107
+ ok: false,
108
+ error: `failed to transform hybrid envelope ${relative(projectRoot, filePath)}: ${result.error}`,
109
+ };
110
+ }
111
+ if (!result.changed) {
112
+ continue;
113
+ }
114
+ pending.push({
115
+ path: filePath,
116
+ body: `${JSON.stringify(result.artifact, null, 2)}\n`,
117
+ });
118
+ }
119
+ return { ok: true, pending };
120
+ }
121
+ /**
122
+ * Apply a previously planned hybrid-envelope rewrite set via containedWrite (#3236).
123
+ */
124
+ function applyHybridEnvelopeRewrites(projectRoot, pending) {
125
+ const root = resolve(projectRoot);
126
+ for (const entry of pending) {
127
+ containedWrite({
128
+ root,
129
+ target: entry.path,
130
+ data: entry.body,
131
+ mode: "replace",
132
+ });
133
+ }
134
+ return pending.length;
135
+ }
77
136
  function backupMigrationInputs(projectRoot, legacyDir, migratedDir) {
78
137
  const stamp = new Date().toISOString().replace(/[:.]/g, "-");
79
138
  const backupRoot = join(projectRoot, ".deft", `xbrief-migrate-backup-${stamp}`);
@@ -256,6 +315,33 @@ export function runXbriefMigration(args, _io) {
256
315
  default:
257
316
  break;
258
317
  }
318
+ // #3236: on already-xbrief trees with no vbrief/, rewrite residual hybrid
319
+ // xBRIEFInfo@0.6 envelopes in place (transform accepts hybrid — #2974).
320
+ // Run before the layout-detection short-circuit so compact `"version":"0.6"`
321
+ // JSON that the string-scan may miss still gets rewritten.
322
+ if (convergence.xbriefHasContent && !convergence.vbriefPresent) {
323
+ const plan = planHybridEnvelopeRewrites(projectRoot);
324
+ if (!plan.ok) {
325
+ return { kind: "config", message: plan.error };
326
+ }
327
+ if (plan.pending.length > 0) {
328
+ if (!args.force) {
329
+ const git = checkGitClean(projectRoot);
330
+ if (git.status === "WARN") {
331
+ return {
332
+ kind: "refused",
333
+ message: `${git.message} ${xbriefMigrationGuidance()} Pass --force to override.`,
334
+ };
335
+ }
336
+ }
337
+ const files = applyHybridEnvelopeRewrites(projectRoot, plan.pending);
338
+ return {
339
+ kind: "rewritten",
340
+ files,
341
+ message: `Rewrote ${files} hybrid xBRIEFInfo@0.6 envelope(s) in place to xBRIEFInfo@0.8 under '${MIGRATED_ARTIFACT_DIR}/'.`,
342
+ };
343
+ }
344
+ }
259
345
  const detection = detectLegacyVbriefLayout(projectRoot);
260
346
  if (!detection.legacyLayout) {
261
347
  return {
@@ -266,7 +352,7 @@ export function runXbriefMigration(args, _io) {
266
352
  if (!isDirectory(legacyDir)) {
267
353
  // Already on canonical xbrief/ with no vbrief/ tree — residual markers (e.g. a
268
354
  // stale deposited v0.6 schema under xbrief/schemas/) are refreshed by update,
269
- // not migrate:xbrief (#2368).
355
+ // not migrate:xbrief (#2368). Hybrid 0.6 envelopes were handled above (#3236).
270
356
  if (convergence.xbriefHasContent && !convergence.vbriefPresent) {
271
357
  return {
272
358
  kind: "noop",
@@ -319,6 +405,9 @@ export function emitXbriefMigration(outcome, io, options = {}) {
319
405
  io.writeOut(`Migrated ${outcome.files} file(s) from ${LEGACY_ARTIFACT_DIR}/ to ${MIGRATED_ARTIFACT_DIR}/.\n` +
320
406
  `Backup written to ${outcome.backupDir}.\n`);
321
407
  return 0;
408
+ case "rewritten":
409
+ io.writeOut(`${outcome.message}\n`);
410
+ return 0;
322
411
  case "converged":
323
412
  io.writeOut(`${outcome.message}\n`);
324
413
  return 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deftai/directive-core",
3
- "version": "0.99.0",
3
+ "version": "0.101.0",
4
4
  "description": "TypeScript engine core for the Directive framework.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -283,6 +283,14 @@
283
283
  "types": "./dist/lifecycle/index.d.ts",
284
284
  "default": "./dist/lifecycle/index.js"
285
285
  },
286
+ "./literal-acceptance": {
287
+ "types": "./dist/literal-acceptance/index.d.ts",
288
+ "default": "./dist/literal-acceptance/index.js"
289
+ },
290
+ "./product-first-done-gate": {
291
+ "types": "./dist/product-first-done-gate/index.d.ts",
292
+ "default": "./dist/product-first-done-gate/index.js"
293
+ },
286
294
  "./packs": {
287
295
  "types": "./dist/packs/index.d.ts",
288
296
  "default": "./dist/packs/index.js"
@@ -311,6 +319,10 @@
311
319
  "types": "./dist/check/index.d.ts",
312
320
  "default": "./dist/check/index.js"
313
321
  },
322
+ "./run-summary": {
323
+ "types": "./dist/run-summary/index.d.ts",
324
+ "default": "./dist/run-summary/index.js"
325
+ },
314
326
  "./preflight-gh": {
315
327
  "types": "./dist/preflight-gh/index.d.ts",
316
328
  "default": "./dist/preflight-gh/index.js"
@@ -366,8 +378,8 @@
366
378
  "provenance": true
367
379
  },
368
380
  "dependencies": {
369
- "@deftai/directive-content": "^0.99.0",
370
- "@deftai/directive-types": "^0.99.0",
381
+ "@deftai/directive-content": "^0.101.0",
382
+ "@deftai/directive-types": "^0.101.0",
371
383
  "archiver": "^8.0.0"
372
384
  },
373
385
  "scripts": {