@deftai/directive-core 0.99.0 → 0.100.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 (102) 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 +157 -23
  4. package/dist/authz/decompose-apply.d.ts +67 -0
  5. package/dist/authz/decompose-apply.js +302 -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/doctor/checks.d.ts +44 -0
  13. package/dist/doctor/checks.js +428 -2
  14. package/dist/doctor/main.d.ts +6 -7
  15. package/dist/doctor/main.js +72 -79
  16. package/dist/doctor/which.d.ts +20 -1
  17. package/dist/doctor/which.js +67 -1
  18. package/dist/finish-loop/pr-finish-loop.d.ts +13 -2
  19. package/dist/finish-loop/pr-finish-loop.js +116 -5
  20. package/dist/finish-loop/types.d.ts +1 -1
  21. package/dist/hooks/dispatcher.d.ts +9 -5
  22. package/dist/hooks/dispatcher.js +23 -15
  23. package/dist/hooks/readonly.d.ts +6 -3
  24. package/dist/hooks/readonly.js +44 -25
  25. package/dist/index.d.ts +1 -0
  26. package/dist/index.js +1 -0
  27. package/dist/init-deposit/agent-hooks.d.ts +20 -0
  28. package/dist/init-deposit/agent-hooks.js +51 -31
  29. package/dist/intake/issue-ingest.js +29 -0
  30. package/dist/lifecycle/completed-consistency.d.ts +60 -0
  31. package/dist/lifecycle/completed-consistency.js +443 -0
  32. package/dist/lifecycle/completed-tracked-on-delivery.d.ts +47 -0
  33. package/dist/lifecycle/completed-tracked-on-delivery.js +415 -0
  34. package/dist/lifecycle/index.d.ts +2 -0
  35. package/dist/lifecycle/index.js +2 -0
  36. package/dist/literal-acceptance/capture.d.ts +52 -0
  37. package/dist/literal-acceptance/capture.js +658 -0
  38. package/dist/literal-acceptance/evaluate.d.ts +47 -0
  39. package/dist/literal-acceptance/evaluate.js +193 -0
  40. package/dist/literal-acceptance/index.d.ts +12 -0
  41. package/dist/literal-acceptance/index.js +12 -0
  42. package/dist/literal-acceptance/run.d.ts +38 -0
  43. package/dist/literal-acceptance/run.js +216 -0
  44. package/dist/literal-acceptance/safety.d.ts +24 -0
  45. package/dist/literal-acceptance/safety.js +251 -0
  46. package/dist/literal-acceptance/types.d.ts +82 -0
  47. package/dist/literal-acceptance/types.js +26 -0
  48. package/dist/policy/ceremony-dial.d.ts +67 -11
  49. package/dist/policy/ceremony-dial.js +174 -23
  50. package/dist/policy/index.d.ts +1 -0
  51. package/dist/policy/index.js +1 -0
  52. package/dist/policy/merge-approval-head.d.ts +129 -0
  53. package/dist/policy/merge-approval-head.js +461 -0
  54. package/dist/policy/require-human-merge.d.ts +5 -0
  55. package/dist/policy/require-human-merge.js +5 -0
  56. package/dist/pr-merge-readiness/ci-gate.d.ts +20 -3
  57. package/dist/pr-merge-readiness/ci-gate.js +65 -5
  58. package/dist/pr-merge-readiness/compute.d.ts +13 -0
  59. package/dist/pr-merge-readiness/compute.js +99 -14
  60. package/dist/pr-merge-readiness/gh.d.ts +49 -0
  61. package/dist/pr-merge-readiness/gh.js +268 -0
  62. package/dist/pr-merge-readiness/index.d.ts +2 -2
  63. package/dist/pr-merge-readiness/index.js +2 -2
  64. package/dist/pr-merge-readiness/test-gh-fixtures.helpers.js +4 -0
  65. package/dist/pr-wait-mergeable/cascade.d.ts +11 -0
  66. package/dist/pr-wait-mergeable/cascade.js +115 -1
  67. package/dist/pr-wait-mergeable/main.d.ts +4 -0
  68. package/dist/pr-wait-mergeable/main.js +4 -0
  69. package/dist/pr-wait-mergeable/types.d.ts +3 -1
  70. package/dist/pr-wait-mergeable/wrappers.d.ts +6 -1
  71. package/dist/pr-wait-mergeable/wrappers.js +7 -1
  72. package/dist/preflight/evaluate.d.ts +15 -1
  73. package/dist/preflight/evaluate.js +33 -2
  74. package/dist/render/framework-commands.js +4 -0
  75. package/dist/scope/acceptance-evidence.d.ts +76 -0
  76. package/dist/scope/acceptance-evidence.js +348 -0
  77. package/dist/scope/coverage-map.d.ts +98 -0
  78. package/dist/scope/coverage-map.js +558 -0
  79. package/dist/scope/decompose.js +125 -11
  80. package/dist/scope/index.d.ts +3 -0
  81. package/dist/scope/index.js +3 -0
  82. package/dist/scope/parent-lineage.d.ts +125 -0
  83. package/dist/scope/parent-lineage.js +626 -0
  84. package/dist/scope/transition.d.ts +8 -0
  85. package/dist/scope/transition.js +61 -2
  86. package/dist/session/active-cli.d.ts +79 -0
  87. package/dist/session/active-cli.js +382 -0
  88. package/dist/session/effort-budget.d.ts +130 -0
  89. package/dist/session/effort-budget.js +334 -0
  90. package/dist/session/index.d.ts +2 -0
  91. package/dist/session/index.js +2 -0
  92. package/dist/session/session-start.d.ts +6 -0
  93. package/dist/session/session-start.js +53 -2
  94. package/dist/session/verify-session-ritual.d.ts +14 -0
  95. package/dist/session/verify-session-ritual.js +33 -0
  96. package/dist/story-ready/evaluate.d.ts +11 -0
  97. package/dist/story-ready/evaluate.js +41 -3
  98. package/dist/triage/actions/candidates-log.d.ts +1 -2
  99. package/dist/triage/actions/candidates-log.js +37 -6
  100. package/dist/xbrief-migrate/migrate-project.d.ts +4 -0
  101. package/dist/xbrief-migrate/migrate-project.js +92 -3
  102. package/package.json +7 -3
@@ -1,5 +1,7 @@
1
1
  import { existsSync } from "node:fs";
2
+ import { readCorePackageVersion } from "../engine-version.js";
2
3
  import { formatFrameworkCommand } from "../render/framework-commands.js";
4
+ import { checkActiveCliAgainstTarget, } from "./active-cli.js";
3
5
  import { defaultGitRunner, gitHead, gitIsAncestor, worktreePath } from "./git.js";
4
6
  import { pythonJsonDump } from "./json.js";
5
7
  import { ENV_SESSION_POSTURE, readOnlyPostureMessage, resolveSessionPosture, ritualStateIsPostureAuthority, } from "./posture.js";
@@ -389,6 +391,37 @@ export function verifySessionRitual(projectRoot, options = {}) {
389
391
  ritualStateRequired,
390
392
  };
391
393
  }
394
+ // #3233: after gated entrypoints, verify the shell-active deft/directive
395
+ // is not a stale higher-precedence shadow of a multi-prefix install.
396
+ // Independent target = explicit option, else in-process publishable engine
397
+ // (never the active CLI's own version — see resolveDefaultActiveCliTarget).
398
+ const targetEngineVersion = options.targetEngineVersion ?? null;
399
+ const checkActiveCli = options.checkActiveCli ?? checkActiveCliAgainstTarget;
400
+ let inProcessVersion = null;
401
+ try {
402
+ inProcessVersion = readCorePackageVersion();
403
+ }
404
+ catch {
405
+ inProcessVersion = null;
406
+ }
407
+ const activeCliSeams = {
408
+ inProcessVersion,
409
+ ...options.activeCliSeams,
410
+ };
411
+ const activeCli = checkActiveCli(targetEngineVersion, activeCliSeams);
412
+ if (!activeCli.ok) {
413
+ return {
414
+ code: activeCli.code === 0 ? 1 : activeCli.code,
415
+ message: activeCli.lines.length > 0 ? activeCli.lines.join("\n") : activeCli.message,
416
+ tier,
417
+ statePath,
418
+ bypassed: false,
419
+ wouldFailCode: null,
420
+ posture,
421
+ ritualStateRequired,
422
+ recoveryTier: "cold",
423
+ };
424
+ }
392
425
  }
393
426
  const evaluated = evaluateLoadedState(projectRoot, state, {
394
427
  tier,
@@ -1,4 +1,5 @@
1
1
  import { type PathLike } from "node:fs";
2
+ import { type ParentLineageResult } from "../scope/parent-lineage.js";
2
3
  import { type ParsedAllocation, parseAllocationSection, SOLO_KIND, SWARM_COHORT_KIND } from "./allocation.js";
3
4
  export declare const ACTIVE_FOLDER = "active";
4
5
  export declare const ELIGIBLE_STATUS = "running";
@@ -7,17 +8,27 @@ export interface EvaluateResult {
7
8
  readonly exitCode: 0 | 1 | 2;
8
9
  readonly message: string;
9
10
  readonly dispatchKind: string | null;
11
+ /** #3241 parent-lineage probe (present when vBRIEF checks ran). */
12
+ readonly parentLineage?: ParentLineageResult;
10
13
  }
11
14
  export interface EvaluateOptions {
12
15
  readonly gitStatus?: string | null;
13
16
  readonly allocationContext?: string | null;
14
17
  readonly allowDirty?: boolean;
15
18
  readonly parsed?: ParsedAllocation;
19
+ /** Project root for resolving child planRef → parent (#3241). */
20
+ readonly projectRoot?: string;
21
+ /** Skip parent-lineage check (tests / opt-out). Default false. */
22
+ readonly skipParentLineage?: boolean;
16
23
  }
17
24
  /**
18
25
  * Pure evaluator — returns exit code + human message. Faithful to
19
26
  * `scripts/preflight_story_start.evaluate`.
27
+ *
28
+ * #3241: after structural active+running checks, re-check parent requirement
29
+ * lineage (coverage map + behavioral deltas) when the parent authors IDs.
20
30
  */
21
31
  export declare function evaluate(vbriefPath: PathLike, options?: EvaluateOptions): EvaluateResult;
32
+ export { evaluateParentLineage, formatParentLineageLine, type ParentLineageResult, } from "../scope/parent-lineage.js";
22
33
  export { parseAllocationSection, SOLO_KIND, SWARM_COHORT_KIND };
23
34
  //# sourceMappingURL=evaluate.d.ts.map
@@ -1,5 +1,6 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { resolve } from "node:path";
3
+ import { evaluateParentLineage, formatParentLineageLine, } from "../scope/parent-lineage.js";
3
4
  import { parseAllocationSection, SOLO_KIND, SWARM_COHORT_KIND, VALID_DISPATCH_KINDS, } from "./allocation.js";
4
5
  export const ACTIVE_FOLDER = "active";
5
6
  export const ELIGIBLE_STATUS = "running";
@@ -66,7 +67,7 @@ function checkVbrief(vbriefPath) {
66
67
  "is eligible for a story start",
67
68
  };
68
69
  }
69
- return { ok: true };
70
+ return { ok: true, path, payload: payload };
70
71
  }
71
72
  function readyMessage(treeNote, suffix) {
72
73
  return `OK: ready to start -- ${treeNote}, vBRIEF active+running, ${suffix}`;
@@ -116,6 +117,9 @@ function classifyAllocation(fields, treeNote) {
116
117
  /**
117
118
  * Pure evaluator — returns exit code + human message. Faithful to
118
119
  * `scripts/preflight_story_start.evaluate`.
120
+ *
121
+ * #3241: after structural active+running checks, re-check parent requirement
122
+ * lineage (coverage map + behavioral deltas) when the parent authors IDs.
119
123
  */
120
124
  export function evaluate(vbriefPath, options = {}) {
121
125
  const gitStatus = options.gitStatus ?? null;
@@ -147,15 +151,49 @@ export function evaluate(vbriefPath, options = {}) {
147
151
  message: `not ready: ${vbriefCheck.reason}.`,
148
152
  };
149
153
  }
154
+ // #3241 parent lineage (fail closed when parent authors IDs and child lacks coverage).
155
+ const lineage = evaluateParentLineage({
156
+ child: vbriefCheck.payload,
157
+ childPath: vbriefCheck.path,
158
+ projectRoot: options.projectRoot,
159
+ skip: options.skipParentLineage === true,
160
+ });
161
+ if (!lineage.ok) {
162
+ return {
163
+ exitCode: 1,
164
+ dispatchKind: null,
165
+ parentLineage: lineage,
166
+ message: `not ready: ${lineage.message}` +
167
+ (lineage.defect_class !== null ? ` [defect_class=${lineage.defect_class}]` : "") +
168
+ `\n${formatParentLineageLine(lineage)}`,
169
+ };
170
+ }
171
+ const lineageNote = lineage.applicable
172
+ ? `parent lineage OK (${lineage.parent_requirement_ids.length} req IDs` +
173
+ (lineage.negative_invariant_ids.length > 0
174
+ ? `, ${lineage.negative_invariant_ids.length} negative invariants`
175
+ : "") +
176
+ ")"
177
+ : null;
150
178
  const [found, fields] = options.parsed ?? parseAllocationSection(options.allocationContext ?? null);
151
179
  if (!found) {
180
+ const base = readyMessage(treeNote, "no `## Allocation context` section (solo path, #1371 carve-out).");
152
181
  return {
153
182
  exitCode: 0,
154
183
  dispatchKind: null,
155
- message: readyMessage(treeNote, "no `## Allocation context` section (solo path, #1371 carve-out)."),
184
+ parentLineage: lineage,
185
+ message: lineageNote !== null ? `${base.replace(/\.$/, "")}; ${lineageNote}.` : base,
156
186
  };
157
187
  }
158
- return classifyAllocation(fields, treeNote);
188
+ const classified = classifyAllocation(fields, treeNote);
189
+ return {
190
+ ...classified,
191
+ parentLineage: lineage,
192
+ message: classified.exitCode === 0 && lineageNote !== null
193
+ ? `${classified.message.replace(/\.$/, "")}; ${lineageNote}.`
194
+ : classified.message,
195
+ };
159
196
  }
197
+ export { evaluateParentLineage, formatParentLineageLine, } from "../scope/parent-lineage.js";
160
198
  export { parseAllocationSection, SOLO_KIND, SWARM_COHORT_KIND };
161
199
  //# sourceMappingURL=evaluate.js.map
@@ -1,6 +1,5 @@
1
1
  import { resolveCandidatesLogPath } from "../cache-path.js";
2
- import type { AuditEntry, CandidatesLog } from "./types.js";
3
- /** Display/back-compat constant; resolution flows through resolveTriageCachePath (#1703). */
2
+ import type { AuditEntry, CandidatesLog } from "./types.js"; /** Display/back-compat constant; resolution flows through resolveTriageCachePath (#1703). */
4
3
  export declare const AUDIT_LOG_REL_PATH = "xbrief/.triage-cache/candidates.jsonl";
5
4
  export { resolveCandidatesLogPath };
6
5
  /** Read every well-formed audit-log row in insertion order (#1698 shared reader). */
@@ -1,9 +1,9 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
- import { join } from "node:path";
2
+ import { existsSync, mkdirSync, readFileSync } from "node:fs";
3
+ import { dirname, isAbsolute, relative, resolve } from "node:path";
4
+ import { containedWrite } from "../../fs/contained-write.js";
4
5
  import { resolveCandidatesLogPath, TRIAGE_CANDIDATES_LOG_REL_PATH } from "../cache-path.js";
5
6
  import { CandidatesLogError } from "./errors.js";
6
- /** Display/back-compat constant; resolution flows through resolveTriageCachePath (#1703). */
7
7
  export const AUDIT_LOG_REL_PATH = TRIAGE_CANDIDATES_LOG_REL_PATH;
8
8
  export { resolveCandidatesLogPath };
9
9
  const VALID_DECISIONS = new Set([
@@ -108,6 +108,32 @@ function resolveLogPath(projectRoot, override) {
108
108
  }
109
109
  return resolveCandidatesLogPath(projectRoot);
110
110
  }
111
+ /**
112
+ * Containment root for candidates-log product writes (#3245).
113
+ * Prefer project root when the log is nested under it; otherwise parent dir
114
+ * (matches intake candidates-log parity for override paths outside the tree).
115
+ */
116
+ function containmentRootForLog(projectRoot, logPath) {
117
+ const rootAbs = resolve(projectRoot);
118
+ const logAbs = resolve(logPath);
119
+ const parent = dirname(logAbs);
120
+ mkdirSync(parent, { recursive: true });
121
+ const rel = relative(rootAbs, logAbs);
122
+ if (rel.length > 0 && !rel.startsWith("..") && !isAbsolute(rel)) {
123
+ return rootAbs;
124
+ }
125
+ return resolve(parent);
126
+ }
127
+ /** #3245 / #2980: product append refuses leaf symlink follow (in-tree or escape). */
128
+ function appendAuditLine(projectRoot, logPath, line) {
129
+ const root = containmentRootForLog(projectRoot, logPath);
130
+ containedWrite({
131
+ root,
132
+ target: logPath,
133
+ data: line.endsWith("\n") ? line : `${line}\n`,
134
+ mode: "append",
135
+ });
136
+ }
111
137
  function stableStringify(entry) {
112
138
  const sortedKeys = Object.keys(entry).sort();
113
139
  const sorted = {};
@@ -186,8 +212,7 @@ export function createCandidatesLog(defaultProjectRoot) {
186
212
  append(entry, options) {
187
213
  validateEntry(entry);
188
214
  const logPath = resolveLogPath(defaultProjectRoot, options?.path);
189
- mkdirSync(join(logPath, ".."), { recursive: true });
190
- appendFileSync(logPath, `${stableStringify(entry)}\n`, { encoding: "utf8" });
215
+ appendAuditLine(defaultProjectRoot, logPath, `${stableStringify(entry)}\n`);
191
216
  return entry.decision_id;
192
217
  },
193
218
  latestDecision(issueNumber, repo, options) {
@@ -225,7 +250,13 @@ export function rollbackAuditEntry(decisionId, projectRoot, logPathOverride) {
225
250
  }
226
251
  }
227
252
  if (removed) {
228
- writeFileSync(path, kept.join(""), { encoding: "utf8" });
253
+ // #3245: replace rewrite also refuses leaf symlink follow.
254
+ containedWrite({
255
+ root: containmentRootForLog(projectRoot, path),
256
+ target: path,
257
+ data: kept.join(""),
258
+ mode: "replace",
259
+ });
229
260
  }
230
261
  return removed;
231
262
  }
@@ -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.100.0",
4
4
  "description": "TypeScript engine core for the Directive framework.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -283,6 +283,10 @@
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
+ },
286
290
  "./packs": {
287
291
  "types": "./dist/packs/index.d.ts",
288
292
  "default": "./dist/packs/index.js"
@@ -366,8 +370,8 @@
366
370
  "provenance": true
367
371
  },
368
372
  "dependencies": {
369
- "@deftai/directive-content": "^0.99.0",
370
- "@deftai/directive-types": "^0.99.0",
373
+ "@deftai/directive-content": "^0.100.0",
374
+ "@deftai/directive-types": "^0.100.0",
371
375
  "archiver": "^8.0.0"
372
376
  },
373
377
  "scripts": {