@deftai/directive-core 0.66.2 → 0.68.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 (147) hide show
  1. package/dist/agents-md-advisory/evaluate.d.ts +44 -0
  2. package/dist/agents-md-advisory/evaluate.js +115 -0
  3. package/dist/agents-md-advisory/index.d.ts +2 -0
  4. package/dist/agents-md-advisory/index.js +2 -0
  5. package/dist/agents-md-budget/evaluate.d.ts +38 -0
  6. package/dist/agents-md-budget/evaluate.js +152 -0
  7. package/dist/agents-md-budget/index.d.ts +2 -0
  8. package/dist/agents-md-budget/index.js +2 -0
  9. package/dist/check/index.d.ts +1 -1
  10. package/dist/check/index.js +1 -1
  11. package/dist/check/orchestrator.d.ts +14 -4
  12. package/dist/check/orchestrator.js +30 -6
  13. package/dist/codebase/map.d.ts +1 -0
  14. package/dist/codebase/map.js +5 -2
  15. package/dist/doctor/checks.js +11 -11
  16. package/dist/doctor/constants.d.ts +7 -2
  17. package/dist/doctor/constants.js +17 -1
  18. package/dist/doctor/flags.js +5 -1
  19. package/dist/doctor/main.js +99 -8
  20. package/dist/doctor/payload-staleness.js +15 -6
  21. package/dist/doctor/types.d.ts +7 -0
  22. package/dist/forward-coverage/evaluate.d.ts +55 -0
  23. package/dist/forward-coverage/evaluate.js +254 -0
  24. package/dist/index.d.ts +1 -0
  25. package/dist/index.js +1 -0
  26. package/dist/init-deposit/constants.d.ts +14 -0
  27. package/dist/init-deposit/constants.js +10 -0
  28. package/dist/init-deposit/hygiene.d.ts +50 -0
  29. package/dist/init-deposit/hygiene.js +176 -0
  30. package/dist/init-deposit/init-deposit.d.ts +1 -0
  31. package/dist/init-deposit/init-deposit.js +7 -1
  32. package/dist/init-deposit/refresh.d.ts +2 -0
  33. package/dist/init-deposit/refresh.js +48 -30
  34. package/dist/init-deposit/scaffold.d.ts +3 -4
  35. package/dist/init-deposit/scaffold.js +3 -24
  36. package/dist/intake/github-auth-modes.d.ts +1 -0
  37. package/dist/intake/github-auth-modes.js +1 -0
  38. package/dist/intake/issue-ingest.d.ts +6 -2
  39. package/dist/intake/issue-ingest.js +65 -9
  40. package/dist/integration-e2e/helpers.d.ts +0 -1
  41. package/dist/integration-e2e/helpers.js +3 -7
  42. package/dist/layout/lifecycle-ref.d.ts +15 -0
  43. package/dist/layout/lifecycle-ref.js +55 -0
  44. package/dist/platform/agents-consumer-header.d.ts +13 -0
  45. package/dist/platform/agents-consumer-header.js +57 -0
  46. package/dist/platform/agents-md.d.ts +28 -0
  47. package/dist/platform/agents-md.js +38 -1
  48. package/dist/platform/index.d.ts +1 -0
  49. package/dist/platform/index.js +1 -0
  50. package/dist/policy/agents-md-advisory.d.ts +52 -0
  51. package/dist/policy/agents-md-advisory.js +63 -0
  52. package/dist/policy/agents-md-budget.d.ts +24 -0
  53. package/dist/policy/agents-md-budget.js +89 -0
  54. package/dist/policy/index.d.ts +1 -0
  55. package/dist/policy/index.js +1 -0
  56. package/dist/policy/resolve.js +56 -46
  57. package/dist/pr-merge-readiness/ci-gate.d.ts +28 -0
  58. package/dist/pr-merge-readiness/ci-gate.js +79 -0
  59. package/dist/pr-merge-readiness/compute.d.ts +15 -1
  60. package/dist/pr-merge-readiness/compute.js +129 -9
  61. package/dist/pr-merge-readiness/gh.d.ts +8 -0
  62. package/dist/pr-merge-readiness/gh.js +30 -5
  63. package/dist/pr-merge-readiness/index.d.ts +5 -1
  64. package/dist/pr-merge-readiness/index.js +3 -0
  65. package/dist/pr-merge-readiness/main.d.ts +3 -0
  66. package/dist/pr-merge-readiness/main.js +73 -6
  67. package/dist/pr-merge-readiness/mergeability.d.ts +43 -0
  68. package/dist/pr-merge-readiness/mergeability.js +105 -0
  69. package/dist/pr-merge-readiness/output.js +20 -0
  70. package/dist/pr-merge-readiness/slizard-gate.d.ts +47 -0
  71. package/dist/pr-merge-readiness/slizard-gate.js +119 -0
  72. package/dist/pr-monitor/index.d.ts +1 -1
  73. package/dist/pr-monitor/index.js +1 -1
  74. package/dist/pr-monitor/monitor.d.ts +8 -2
  75. package/dist/pr-monitor/monitor.js +35 -6
  76. package/dist/pr-wait-mergeable/wrappers.d.ts +12 -1
  77. package/dist/pr-wait-mergeable/wrappers.js +13 -5
  78. package/dist/pr-watch/constants.d.ts +33 -0
  79. package/dist/pr-watch/constants.js +33 -0
  80. package/dist/pr-watch/index.d.ts +6 -0
  81. package/dist/pr-watch/index.js +6 -0
  82. package/dist/pr-watch/main.d.ts +21 -0
  83. package/dist/pr-watch/main.js +206 -0
  84. package/dist/pr-watch/probe.d.ts +13 -0
  85. package/dist/pr-watch/probe.js +100 -0
  86. package/dist/pr-watch/types.d.ts +47 -0
  87. package/dist/pr-watch/types.js +2 -0
  88. package/dist/pr-watch/watch.d.ts +12 -0
  89. package/dist/pr-watch/watch.js +122 -0
  90. package/dist/preflight-cache/evaluate.d.ts +1 -0
  91. package/dist/preflight-cache/evaluate.js +17 -10
  92. package/dist/render/project-render.d.ts +16 -2
  93. package/dist/render/project-render.js +155 -97
  94. package/dist/scope/capacity-stamp.d.ts +24 -2
  95. package/dist/scope/capacity-stamp.js +80 -28
  96. package/dist/scope/decomposed-refs.js +2 -2
  97. package/dist/scope/project-definition-sync.js +3 -3
  98. package/dist/scope/vbrief-ref.d.ts +2 -1
  99. package/dist/scope/vbrief-ref.js +3 -2
  100. package/dist/spec-authority/constants.d.ts +6 -0
  101. package/dist/spec-authority/constants.js +11 -0
  102. package/dist/spec-authority/resolver.js +3 -3
  103. package/dist/swarm/finalize-cohort-cli.d.ts +5 -0
  104. package/dist/swarm/finalize-cohort-cli.js +100 -0
  105. package/dist/swarm/finalize-cohort.d.ts +39 -0
  106. package/dist/swarm/finalize-cohort.js +589 -0
  107. package/dist/swarm/index.d.ts +2 -0
  108. package/dist/swarm/index.js +2 -0
  109. package/dist/swarm/launch.d.ts +13 -0
  110. package/dist/swarm/launch.js +19 -7
  111. package/dist/swarm/routing-verify.d.ts +1 -1
  112. package/dist/swarm/routing-verify.js +11 -10
  113. package/dist/swarm/routing.d.ts +9 -0
  114. package/dist/swarm/routing.js +46 -0
  115. package/dist/swarm/verify-review-clean-cli.js +25 -1
  116. package/dist/swarm/verify-review-clean.d.ts +9 -1
  117. package/dist/swarm/verify-review-clean.js +76 -3
  118. package/dist/triage/queue/index.d.ts +1 -0
  119. package/dist/triage/queue/index.js +1 -0
  120. package/dist/triage/queue/project.d.ts +6 -1
  121. package/dist/triage/queue/project.js +9 -4
  122. package/dist/triage/queue/reconcile-live-state.d.ts +42 -0
  123. package/dist/triage/queue/reconcile-live-state.js +54 -0
  124. package/dist/triage/scope/mutations-core.js +91 -81
  125. package/dist/triage/scope/resolve.js +4 -3
  126. package/dist/triage/scope-drift/add-ignore.js +33 -32
  127. package/dist/triage/scope-drift/scope-rules.js +3 -3
  128. package/dist/triage/subscribe/index.js +51 -45
  129. package/dist/triage/welcome/writers.js +79 -64
  130. package/dist/vbrief-build/project-definition-io.js +8 -3
  131. package/dist/vbrief-validate/paths.js +8 -4
  132. package/dist/vbrief-validate/precutover.d.ts +1 -1
  133. package/dist/vbrief-validate/precutover.js +12 -10
  134. package/dist/verify-source/biome-config.d.ts +41 -0
  135. package/dist/verify-source/biome-config.js +97 -0
  136. package/dist/verify-source/index.d.ts +1 -0
  137. package/dist/verify-source/index.js +1 -0
  138. package/dist/xbrief-migrate/agents-header.d.ts +69 -0
  139. package/dist/xbrief-migrate/agents-header.js +179 -0
  140. package/dist/xbrief-migrate/fs-helpers.d.ts +3 -0
  141. package/dist/xbrief-migrate/fs-helpers.js +11 -0
  142. package/dist/xbrief-migrate/index.d.ts +1 -0
  143. package/dist/xbrief-migrate/index.js +1 -0
  144. package/dist/xbrief-migrate/migrate-project.js +29 -10
  145. package/package.json +15 -7
  146. package/dist/install-upgrade/index.d.ts +0 -13
  147. package/dist/install-upgrade/index.js +0 -251
@@ -1,4 +1,5 @@
1
1
  import { basename, relative, resolve, sep } from "node:path";
2
+ import { resolveLifecycleArtifactRef } from "../layout/lifecycle-ref.js";
2
3
  import { hasArtifactSuffix, stripArtifactSuffix } from "../layout/resolve.js";
3
4
  /** Return true when ``child`` resolves under ``parent``. */
4
5
  export function isRelativeTo(child, parent) {
@@ -15,14 +16,17 @@ export function resolveRefPath(uri, vbriefDir) {
15
16
  if (typeof uri !== "string") {
16
17
  return null;
17
18
  }
19
+ let rel;
18
20
  if (uri.startsWith("file://")) {
19
- const rel = uri.slice("file://".length);
20
- return resolve(vbriefDir, rel);
21
+ rel = uri.slice("file://".length);
21
22
  }
22
- if (uri.startsWith("http://") || uri.startsWith("https://") || uri.startsWith("#")) {
23
+ else if (uri.startsWith("http://") || uri.startsWith("https://") || uri.startsWith("#")) {
23
24
  return null;
24
25
  }
25
- return resolve(vbriefDir, uri);
26
+ else {
27
+ rel = uri;
28
+ }
29
+ return resolveLifecycleArtifactRef(rel, vbriefDir);
26
30
  }
27
31
  /** Split like Python ``str.split(sep, maxsplit)`` (maxsplit splits, not result length). */
28
32
  function splitMax(value, sep, maxsplit) {
@@ -4,7 +4,7 @@ export { DEPRECATION_SENTINEL as DEPRECATED_REDIRECT_SENTINEL };
4
4
  export declare function missingLifecycleFolders(projectRoot: string): string[];
5
5
  /** Return true when markdown content is a migration redirect stub. */
6
6
  export declare function isDeprecationRedirect(content: string): boolean;
7
- /** Full-spec generated export (specification.vbrief.json source line). */
7
+ /** Full-spec generated export (layout-resolved specification artifact source line). */
8
8
  export declare function isGeneratedSpecificationExport(projectRoot: string, content: string): boolean;
9
9
  /** Return true for a fully current generated spec export (full-spec or greenfield). */
10
10
  export declare function isCurrentGeneratedSpecification(projectRoot: string, content: string): boolean;
@@ -1,15 +1,15 @@
1
1
  import { existsSync, readFileSync, statSync } from "node:fs";
2
2
  import { join } from "node:path";
3
- import { GENERATED_SPEC_PURPOSE, GENERATED_SPEC_SOURCE_SPEC } from "../spec-authority/constants.js";
3
+ import { resolveLifecycleLayout, resolveLifecycleRoot, resolveSpecArtifactPath, } from "../layout/resolve.js";
4
+ import { contentHasGeneratedSpecSource, GENERATED_SPEC_PURPOSE, } from "../spec-authority/constants.js";
4
5
  import { isFullSpecState, isGreenfieldSpecExport } from "../spec-authority/resolver.js";
5
6
  import { DEPRECATION_SENTINEL } from "../vbrief-build/constants.js";
6
7
  export { DEPRECATION_SENTINEL as DEPRECATED_REDIRECT_SENTINEL };
7
8
  const DEPRECATION_REDIRECT_PURPOSE = "<!-- Purpose: deprecation redirect -->";
8
- const SPEC_SOURCE_RELPATH = join("vbrief", "specification.vbrief.json");
9
9
  const LIFECYCLE_FOLDERS = ["proposed", "pending", "active", "completed", "cancelled"];
10
10
  export function missingLifecycleFolders(projectRoot) {
11
- const vbriefRoot = join(projectRoot, "vbrief");
12
- return LIFECYCLE_FOLDERS.filter((folder) => !existsSync(join(vbriefRoot, folder)));
11
+ const lifecycleRoot = resolveLifecycleRoot(projectRoot);
12
+ return LIFECYCLE_FOLDERS.filter((folder) => !existsSync(join(lifecycleRoot, folder)));
13
13
  }
14
14
  function hasCompleteLifecycle(projectRoot) {
15
15
  return missingLifecycleFolders(projectRoot).length === 0;
@@ -18,11 +18,11 @@ function hasCompleteLifecycle(projectRoot) {
18
18
  export function isDeprecationRedirect(content) {
19
19
  return content.includes(DEPRECATION_SENTINEL) || content.includes(DEPRECATION_REDIRECT_PURPOSE);
20
20
  }
21
- /** Full-spec generated export (specification.vbrief.json source line). */
21
+ /** Full-spec generated export (layout-resolved specification artifact source line). */
22
22
  export function isGeneratedSpecificationExport(projectRoot, content) {
23
23
  return (content.includes(GENERATED_SPEC_PURPOSE) &&
24
- content.includes(GENERATED_SPEC_SOURCE_SPEC) &&
25
- existsSync(join(projectRoot, SPEC_SOURCE_RELPATH)));
24
+ contentHasGeneratedSpecSource(content) &&
25
+ existsSync(resolveSpecArtifactPath(projectRoot)));
26
26
  }
27
27
  /** Return true for a fully current generated spec export (full-spec or greenfield). */
28
28
  export function isCurrentGeneratedSpecification(projectRoot, content) {
@@ -56,6 +56,8 @@ function rootMarkdownIsLegacy(projectRoot, filename, content) {
56
56
  return false;
57
57
  if (isGreenfieldSpecExport(projectRoot))
58
58
  return false;
59
+ if (isFullSpecState(projectRoot))
60
+ return false;
59
61
  }
60
62
  return filename === "SPECIFICATION.md" || filename === "PROJECT.md";
61
63
  }
@@ -89,11 +91,11 @@ export function detectPreCutover(projectRoot) {
89
91
  reasons.push("PROJECT.md is a pre-v0.20 hand-authored doc (not a deprecation redirect)");
90
92
  }
91
93
  }
92
- const vbriefRoot = join(projectRoot, "vbrief");
93
- if (existsSync(vbriefRoot)) {
94
+ const layout = resolveLifecycleLayout(projectRoot);
95
+ if (existsSync(layout.root)) {
94
96
  const missing = missingLifecycleFolders(projectRoot);
95
97
  if (missing.length > 0) {
96
- reasons.push(`vbrief/ is missing lifecycle folder(s): ${missing.join(", ")}`);
98
+ reasons.push(`${layout.artifactDir}/ is missing lifecycle folder(s): ${missing.join(", ")}`);
97
99
  }
98
100
  }
99
101
  return { preCutover: reasons.length > 0, reasons };
@@ -0,0 +1,41 @@
1
+ /**
2
+ * biome-config.ts -- guard against #2190 (biome check . reports
3
+ * recommended-preset diagnostics as errors vs warnings non-deterministically
4
+ * on a pinned biome version).
5
+ *
6
+ * A preset-inherited rule severity can silently change tier across a biome
7
+ * version bump (or, per #2190, apparently even across runs on the identical
8
+ * pinned version). Pinning `noUnusedVariables` / `noNonNullAssertion` to an
9
+ * explicit non-"error" severity in biome.json makes the tier config-owned so
10
+ * it cannot flip a required CI check to failing on pre-existing diagnostics.
11
+ */
12
+ /** The two rules #2190 observed flipping severity in CI (see docs/analysis/2026-07-02-2190-biome-determinism.md). */
13
+ export declare const GUARDED_RULES: ReadonlyArray<{
14
+ readonly group: string;
15
+ readonly rule: string;
16
+ }>;
17
+ export interface RuleSeverityFinding {
18
+ readonly group: string;
19
+ readonly rule: string;
20
+ /** The literal severity value found, or null when the rule has no explicit entry. */
21
+ readonly severity: string | null;
22
+ }
23
+ export interface BiomeConfigGuardResult {
24
+ readonly code: 0 | 1 | 2;
25
+ readonly findings: readonly RuleSeverityFinding[];
26
+ readonly message: string;
27
+ }
28
+ /**
29
+ * Parse a biome.json document (already JSON.parse'd) and report the explicit
30
+ * severity declared for each guarded rule.
31
+ */
32
+ export declare function findRuleSeverities(biomeConfig: unknown, rules?: ReadonlyArray<{
33
+ readonly group: string;
34
+ readonly rule: string;
35
+ }>): RuleSeverityFinding[];
36
+ /**
37
+ * Evaluate biome.json at `projectRoot` and fail closed when a guarded rule
38
+ * has no explicit severity, or an explicit severity of "error".
39
+ */
40
+ export declare function evaluateBiomeConfigGuard(projectRoot?: string): BiomeConfigGuardResult;
41
+ //# sourceMappingURL=biome-config.d.ts.map
@@ -0,0 +1,97 @@
1
+ /**
2
+ * biome-config.ts -- guard against #2190 (biome check . reports
3
+ * recommended-preset diagnostics as errors vs warnings non-deterministically
4
+ * on a pinned biome version).
5
+ *
6
+ * A preset-inherited rule severity can silently change tier across a biome
7
+ * version bump (or, per #2190, apparently even across runs on the identical
8
+ * pinned version). Pinning `noUnusedVariables` / `noNonNullAssertion` to an
9
+ * explicit non-"error" severity in biome.json makes the tier config-owned so
10
+ * it cannot flip a required CI check to failing on pre-existing diagnostics.
11
+ */
12
+ import { readFileSync } from "node:fs";
13
+ import { join } from "node:path";
14
+ /** The two rules #2190 observed flipping severity in CI (see docs/analysis/2026-07-02-2190-biome-determinism.md). */
15
+ export const GUARDED_RULES = [
16
+ { group: "correctness", rule: "noUnusedVariables" },
17
+ { group: "style", rule: "noNonNullAssertion" },
18
+ ];
19
+ /** Severity levels biome accepts that never fail the build. */
20
+ const NON_ERROR_LEVELS = new Set(["warn", "info", "off"]);
21
+ function extractSeverity(rulesNode, group, rule) {
22
+ if (typeof rulesNode !== "object" || rulesNode === null) {
23
+ return null;
24
+ }
25
+ const groupNode = rulesNode[group];
26
+ if (typeof groupNode !== "object" || groupNode === null) {
27
+ return null;
28
+ }
29
+ const ruleNode = groupNode[rule];
30
+ if (typeof ruleNode === "string") {
31
+ return ruleNode;
32
+ }
33
+ if (typeof ruleNode === "object" && ruleNode !== null) {
34
+ const level = ruleNode.level;
35
+ return typeof level === "string" ? level : null;
36
+ }
37
+ return null;
38
+ }
39
+ /**
40
+ * Parse a biome.json document (already JSON.parse'd) and report the explicit
41
+ * severity declared for each guarded rule.
42
+ */
43
+ export function findRuleSeverities(biomeConfig, rules = GUARDED_RULES) {
44
+ const linter = typeof biomeConfig === "object" && biomeConfig !== null
45
+ ? biomeConfig.linter
46
+ : null;
47
+ const rulesNode = typeof linter === "object" && linter !== null
48
+ ? linter.rules
49
+ : null;
50
+ return rules.map(({ group, rule }) => ({
51
+ group,
52
+ rule,
53
+ severity: extractSeverity(rulesNode, group, rule),
54
+ }));
55
+ }
56
+ /**
57
+ * Evaluate biome.json at `projectRoot` and fail closed when a guarded rule
58
+ * has no explicit severity, or an explicit severity of "error".
59
+ */
60
+ export function evaluateBiomeConfigGuard(projectRoot = ".") {
61
+ const path = join(projectRoot, "biome.json");
62
+ let raw;
63
+ try {
64
+ raw = readFileSync(path, { encoding: "utf8" });
65
+ }
66
+ catch (err) {
67
+ const msg = err instanceof Error ? err.message : String(err);
68
+ return { code: 2, findings: [], message: `biome-config: cannot read ${path}: ${msg}` };
69
+ }
70
+ let parsed;
71
+ try {
72
+ parsed = JSON.parse(raw);
73
+ }
74
+ catch (err) {
75
+ const msg = err instanceof Error ? err.message : String(err);
76
+ return { code: 2, findings: [], message: `biome-config: ${path} is not valid JSON: ${msg}` };
77
+ }
78
+ const findings = findRuleSeverities(parsed);
79
+ const bad = findings.filter((f) => f.severity === null || !NON_ERROR_LEVELS.has(f.severity));
80
+ if (bad.length > 0) {
81
+ const lines = bad.map((f) => ` linter.rules.${f.group}.${f.rule}: ${f.severity === null ? "not explicitly set (inherits preset default)" : `"${f.severity}"`}`);
82
+ return {
83
+ code: 1,
84
+ findings,
85
+ message: "biome-config: guarded rule(s) lack an explicit non-error severity in biome.json (#2190 " +
86
+ "-- a preset-inherited severity can flip to error non-deterministically):\n" +
87
+ `${lines.join("\n")}\n` +
88
+ ' Fix: declare "warn" (or "info") explicitly under linter.rules.<group>.<rule> in biome.json.',
89
+ };
90
+ }
91
+ return {
92
+ code: 0,
93
+ findings,
94
+ message: `biome-config: ${findings.length} guarded rule(s) have explicit non-error severities (#2190).`,
95
+ };
96
+ }
97
+ //# sourceMappingURL=biome-config.js.map
@@ -1,3 +1,4 @@
1
+ export * from "./biome-config.js";
1
2
  export * from "./code-structure-validate.js";
2
3
  export * from "./content-manifest.js";
3
4
  export { CANONICAL_SCHEMA_REL, type ContractDriftOptions, type ContractDriftResult, evaluateContractDrift, PUBLISHED_SCHEMA_REL, } from "./contract-drift.js";
@@ -1,3 +1,4 @@
1
+ export * from "./biome-config.js";
1
2
  export * from "./code-structure-validate.js";
2
3
  export * from "./content-manifest.js";
3
4
  export { CANONICAL_SCHEMA_REL, evaluateContractDrift, PUBLISHED_SCHEMA_REL, } from "./contract-drift.js";
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Bounded, ordered set of legacy crossover tokens rewritten in the UNMANAGED
3
+ * region of a consumer AGENTS.md after `migrate:xbrief` (#2154 / Option A).
4
+ *
5
+ * Each entry is a mechanical path / verb literal — NOT freeform prose. The
6
+ * casing-only `vBRIEF format` product-description token from the issue table is
7
+ * intentionally excluded so freeform prose survives untouched. The tokens are
8
+ * disjoint substrings (`.vbrief.json` has no trailing slash, `vbrief:preflight`
9
+ * uses a colon, `vbrief/` requires a slash), so replacement order does not
10
+ * change the result and a second pass is a guaranteed no-op (idempotent).
11
+ */
12
+ export declare const LEGACY_HEADER_TOKENS: ReadonlyArray<{
13
+ readonly legacy: string;
14
+ readonly migrated: string;
15
+ }>;
16
+ export interface HeaderTokenReplacement {
17
+ readonly legacy: string;
18
+ readonly migrated: string;
19
+ readonly count: number;
20
+ }
21
+ export interface HeaderRewriteResult {
22
+ readonly content: string;
23
+ readonly changed: boolean;
24
+ readonly replacements: HeaderTokenReplacement[];
25
+ }
26
+ /**
27
+ * Rewrite known legacy `vbrief` crossover tokens in the UNMANAGED region(s) of
28
+ * an AGENTS.md document, leaving every `<!-- deft:managed-section ... -->` block
29
+ * byte-for-byte intact. Idempotent: running on already-migrated content is a
30
+ * no-op. Managed sections are located by literal markers on the raw text so no
31
+ * line-ending normalisation is performed (UTF-8 / CRLF safe).
32
+ */
33
+ export declare function rewriteUnmanagedHeaderTokens(content: string): HeaderRewriteResult;
34
+ export interface HeaderPatchOutcome {
35
+ readonly kind: "patched" | "clean" | "absent" | "failed";
36
+ readonly path: string;
37
+ readonly replacements: HeaderTokenReplacement[];
38
+ readonly error?: string;
39
+ }
40
+ /**
41
+ * Read AGENTS.md at `projectRoot`, rewrite legacy tokens in the unmanaged
42
+ * header, and write the result back only when something changed. Returns a
43
+ * structured outcome so the caller can log a LEGACY-REPORT-style summary.
44
+ *
45
+ * Non-fatal: a write failure (read-only file, full disk) is captured as a
46
+ * `failed` outcome rather than thrown, so a post-migration header patch can
47
+ * never crash a migration that already succeeded.
48
+ */
49
+ export declare function patchAgentsMdHeader(projectRoot: string, seams?: {
50
+ readText?: (path: string) => string | null;
51
+ writeText?: (path: string, text: string) => void;
52
+ }): HeaderPatchOutcome;
53
+ /** Human-readable one-line summary of a header patch outcome (LEGACY-REPORT style). */
54
+ export declare function renderHeaderPatchSummary(outcome: HeaderPatchOutcome): string;
55
+ export interface StaleHeaderDetection {
56
+ readonly stale: boolean;
57
+ readonly matches: string[];
58
+ }
59
+ /**
60
+ * Detect the #2154 half-migrated state: the `xbrief/` tree exists (lifecycle
61
+ * migration + managed-section refresh already happened) yet the UNMANAGED
62
+ * AGENTS.md header still references legacy `vbrief` path / verb literals — a
63
+ * regression `deft doctor` cannot see because the managed-section byte compare
64
+ * passes (#1308). Returns the matched legacy tokens for the signpost.
65
+ */
66
+ export declare function detectStaleUnmanagedHeader(projectRoot: string, readText?: (path: string) => string | null): StaleHeaderDetection;
67
+ /** One-line doctor / ritual signpost for the #2154 stale-header regression. */
68
+ export declare function renderStaleHeaderLine(projectRoot: string, readText?: (path: string) => string | null): string;
69
+ //# sourceMappingURL=agents-header.d.ts.map
@@ -0,0 +1,179 @@
1
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { iterManagedSections } from "../platform/agents-md.js";
4
+ import { MIGRATED_ARTIFACT_DIR } from "./constants.js";
5
+ import { isDirectory } from "./fs-helpers.js";
6
+ /**
7
+ * Bounded, ordered set of legacy crossover tokens rewritten in the UNMANAGED
8
+ * region of a consumer AGENTS.md after `migrate:xbrief` (#2154 / Option A).
9
+ *
10
+ * Each entry is a mechanical path / verb literal — NOT freeform prose. The
11
+ * casing-only `vBRIEF format` product-description token from the issue table is
12
+ * intentionally excluded so freeform prose survives untouched. The tokens are
13
+ * disjoint substrings (`.vbrief.json` has no trailing slash, `vbrief:preflight`
14
+ * uses a colon, `vbrief/` requires a slash), so replacement order does not
15
+ * change the result and a second pass is a guaranteed no-op (idempotent).
16
+ */
17
+ export const LEGACY_HEADER_TOKENS = [
18
+ { legacy: ".vbrief.json", migrated: ".xbrief.json" },
19
+ { legacy: "vbrief:preflight", migrated: "xbrief:preflight" },
20
+ { legacy: "vbrief/", migrated: "xbrief/" },
21
+ ];
22
+ function countOccurrences(haystack, needle) {
23
+ if (needle.length === 0)
24
+ return 0;
25
+ let count = 0;
26
+ let index = haystack.indexOf(needle);
27
+ while (index !== -1) {
28
+ count += 1;
29
+ index = haystack.indexOf(needle, index + needle.length);
30
+ }
31
+ return count;
32
+ }
33
+ /** Rewrite the bounded legacy tokens inside a single unmanaged text slice. */
34
+ function rewriteSlice(slice, tally) {
35
+ let next = slice;
36
+ for (const { legacy, migrated } of LEGACY_HEADER_TOKENS) {
37
+ const occurrences = countOccurrences(next, legacy);
38
+ if (occurrences === 0)
39
+ continue;
40
+ tally.set(legacy, (tally.get(legacy) ?? 0) + occurrences);
41
+ next = next.replaceAll(legacy, migrated);
42
+ }
43
+ return next;
44
+ }
45
+ /**
46
+ * Rewrite known legacy `vbrief` crossover tokens in the UNMANAGED region(s) of
47
+ * an AGENTS.md document, leaving every `<!-- deft:managed-section ... -->` block
48
+ * byte-for-byte intact. Idempotent: running on already-migrated content is a
49
+ * no-op. Managed sections are located by literal markers on the raw text so no
50
+ * line-ending normalisation is performed (UTF-8 / CRLF safe).
51
+ */
52
+ export function rewriteUnmanagedHeaderTokens(content) {
53
+ const managed = iterManagedSections(content);
54
+ const tally = new Map();
55
+ let out = "";
56
+ let cursor = 0;
57
+ for (const [start, end] of managed) {
58
+ // Unmanaged slice before this managed block: eligible for rewrite.
59
+ out += rewriteSlice(content.slice(cursor, start), tally);
60
+ // Managed block: preserved verbatim.
61
+ out += content.slice(start, end);
62
+ cursor = end;
63
+ }
64
+ // Trailing unmanaged slice after the last managed block (or the whole file
65
+ // when there is no managed section at all).
66
+ out += rewriteSlice(content.slice(cursor), tally);
67
+ const replacements = LEGACY_HEADER_TOKENS.filter((t) => tally.has(t.legacy)).map((t) => ({
68
+ legacy: t.legacy,
69
+ migrated: t.migrated,
70
+ count: tally.get(t.legacy) ?? 0,
71
+ }));
72
+ return { content: out, changed: out !== content, replacements };
73
+ }
74
+ /**
75
+ * Read AGENTS.md at `projectRoot`, rewrite legacy tokens in the unmanaged
76
+ * header, and write the result back only when something changed. Returns a
77
+ * structured outcome so the caller can log a LEGACY-REPORT-style summary.
78
+ *
79
+ * Non-fatal: a write failure (read-only file, full disk) is captured as a
80
+ * `failed` outcome rather than thrown, so a post-migration header patch can
81
+ * never crash a migration that already succeeded.
82
+ */
83
+ export function patchAgentsMdHeader(projectRoot, seams = {}) {
84
+ const agentsPath = join(projectRoot, "AGENTS.md");
85
+ const readText = seams.readText ??
86
+ ((path) => {
87
+ try {
88
+ if (!existsSync(path))
89
+ return null;
90
+ return readFileSync(path, "utf8");
91
+ }
92
+ catch {
93
+ return null;
94
+ }
95
+ });
96
+ const writeText = seams.writeText ?? ((path, text) => writeFileSync(path, text, "utf8"));
97
+ const existing = readText(agentsPath);
98
+ if (existing === null) {
99
+ return { kind: "absent", path: agentsPath, replacements: [] };
100
+ }
101
+ const result = rewriteUnmanagedHeaderTokens(existing);
102
+ if (!result.changed) {
103
+ return { kind: "clean", path: agentsPath, replacements: [] };
104
+ }
105
+ try {
106
+ writeText(agentsPath, result.content);
107
+ }
108
+ catch (err) {
109
+ return {
110
+ kind: "failed",
111
+ path: agentsPath,
112
+ replacements: [],
113
+ error: err instanceof Error ? err.message : String(err),
114
+ };
115
+ }
116
+ return { kind: "patched", path: agentsPath, replacements: result.replacements };
117
+ }
118
+ /** Human-readable one-line summary of a header patch outcome (LEGACY-REPORT style). */
119
+ export function renderHeaderPatchSummary(outcome) {
120
+ if (outcome.kind === "absent") {
121
+ return "AGENTS.md unmanaged header: no AGENTS.md present — nothing to patch.";
122
+ }
123
+ if (outcome.kind === "clean") {
124
+ return "AGENTS.md unmanaged header: no legacy vbrief tokens found — nothing to patch.";
125
+ }
126
+ if (outcome.kind === "failed") {
127
+ return (`AGENTS.md unmanaged header: patch failed (${outcome.error ?? "unknown error"}) — ` +
128
+ "re-run `deft migrate:xbrief` (idempotent) or hand-edit the header.");
129
+ }
130
+ const total = outcome.replacements.reduce((sum, r) => sum + r.count, 0);
131
+ const detail = outcome.replacements.map((r) => `${r.legacy} ×${r.count}`).join(", ");
132
+ return `AGENTS.md unmanaged header: rewrote ${total} legacy vbrief token(s) -> xbrief (${detail}).`;
133
+ }
134
+ /**
135
+ * Detect the #2154 half-migrated state: the `xbrief/` tree exists (lifecycle
136
+ * migration + managed-section refresh already happened) yet the UNMANAGED
137
+ * AGENTS.md header still references legacy `vbrief` path / verb literals — a
138
+ * regression `deft doctor` cannot see because the managed-section byte compare
139
+ * passes (#1308). Returns the matched legacy tokens for the signpost.
140
+ */
141
+ export function detectStaleUnmanagedHeader(projectRoot, readText = (path) => {
142
+ try {
143
+ if (!existsSync(path))
144
+ return null;
145
+ return readFileSync(path, "utf8");
146
+ }
147
+ catch {
148
+ return null;
149
+ }
150
+ }) {
151
+ if (!isDirectory(join(projectRoot, MIGRATED_ARTIFACT_DIR))) {
152
+ return { stale: false, matches: [] };
153
+ }
154
+ const content = readText(join(projectRoot, "AGENTS.md"));
155
+ if (content === null) {
156
+ return { stale: false, matches: [] };
157
+ }
158
+ const managed = iterManagedSections(content);
159
+ let unmanaged = "";
160
+ let cursor = 0;
161
+ for (const [start, end] of managed) {
162
+ unmanaged += content.slice(cursor, start);
163
+ cursor = end;
164
+ }
165
+ unmanaged += content.slice(cursor);
166
+ const matches = LEGACY_HEADER_TOKENS.filter((t) => unmanaged.includes(t.legacy)).map((t) => t.legacy);
167
+ return { stale: matches.length > 0, matches };
168
+ }
169
+ /** One-line doctor / ritual signpost for the #2154 stale-header regression. */
170
+ export function renderStaleHeaderLine(projectRoot, readText) {
171
+ const { stale, matches } = detectStaleUnmanagedHeader(projectRoot, readText);
172
+ if (!stale) {
173
+ return "AGENTS.md header drift: none -- unmanaged header has no legacy vbrief path literals.";
174
+ }
175
+ return (`AGENTS.md header drift: xbrief/ tree present but the unmanaged AGENTS.md header still ` +
176
+ `references legacy token(s) ${matches.join(", ")}. ` +
177
+ "Run `deft migrate:xbrief` (idempotent) to rewrite them, or hand-edit the header.");
178
+ }
179
+ //# sourceMappingURL=agents-header.js.map
@@ -0,0 +1,3 @@
1
+ /** True when `path` exists and is a directory; false on any stat error. */
2
+ export declare function isDirectory(path: string): boolean;
3
+ //# sourceMappingURL=fs-helpers.d.ts.map
@@ -0,0 +1,11 @@
1
+ import { statSync } from "node:fs";
2
+ /** True when `path` exists and is a directory; false on any stat error. */
3
+ export function isDirectory(path) {
4
+ try {
5
+ return statSync(path).isDirectory();
6
+ }
7
+ catch {
8
+ return false;
9
+ }
10
+ }
11
+ //# sourceMappingURL=fs-helpers.js.map
@@ -1,3 +1,4 @@
1
+ export { detectStaleUnmanagedHeader, type HeaderPatchOutcome, type HeaderRewriteResult, type HeaderTokenReplacement, LEGACY_HEADER_TOKENS, patchAgentsMdHeader, renderHeaderPatchSummary, renderStaleHeaderLine, rewriteUnmanagedHeaderTokens, type StaleHeaderDetection, } from "./agents-header.js";
1
2
  export { detectLegacyVbriefLayout, type LegacyVbriefLayoutDetection, } from "./detect.js";
2
3
  export { BUILTIN_ALLOW_LIST, type DriftEvaluateOptions, type DriftEvaluateResult, type DriftFinding, type DriftScanMode, evaluateXbriefDrift, LEGACY_REFERENCE_PREFIX, scanCorpusToken, } from "./drift-gate.js";
3
4
  export { emitXbriefMigration, runXbriefMigration, runXbriefMigrationCli, type XbriefMigrationArgs, type XbriefMigrationIo, type XbriefMigrationOutcome, } from "./migrate-project.js";
@@ -1,3 +1,4 @@
1
+ export { detectStaleUnmanagedHeader, LEGACY_HEADER_TOKENS, patchAgentsMdHeader, renderHeaderPatchSummary, renderStaleHeaderLine, rewriteUnmanagedHeaderTokens, } from "./agents-header.js";
1
2
  export { detectLegacyVbriefLayout, } from "./detect.js";
2
3
  export { BUILTIN_ALLOW_LIST, evaluateXbriefDrift, LEGACY_REFERENCE_PREFIX, scanCorpusToken, } from "./drift-gate.js";
3
4
  export { emitXbriefMigration, runXbriefMigration, runXbriefMigrationCli, } from "./migrate-project.js";
@@ -1,19 +1,13 @@
1
- import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync, } from "node:fs";
1
+ import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync, } from "node:fs";
2
2
  import { dirname, join, relative, resolve } from "node:path";
3
3
  import { checkGitClean } from "../migrate-preflight/index.js";
4
4
  import { agentsRefreshPlan } from "../platform/agents-md.js";
5
+ import { patchAgentsMdHeader, renderHeaderPatchSummary } from "./agents-header.js";
5
6
  import { LEGACY_ARTIFACT_DIR, LEGACY_ARTIFACT_SUFFIX, MIGRATED_ARTIFACT_DIR, MIGRATED_ARTIFACT_SUFFIX, } from "./constants.js";
6
7
  import { detectLegacyVbriefLayout } from "./detect.js";
8
+ import { isDirectory } from "./fs-helpers.js";
7
9
  import { renderXbriefMigrationLine, xbriefMigrationGuidance } from "./signpost.js";
8
10
  import { rewriteEmbeddedTokens, transformArtifactV06ToV08Transactional } from "./transforms.js";
9
- function isDirectory(path) {
10
- try {
11
- return statSync(path).isDirectory();
12
- }
13
- catch {
14
- return false;
15
- }
16
- }
17
11
  function collectFiles(root, acc = []) {
18
12
  if (!isDirectory(root)) {
19
13
  return acc;
@@ -177,6 +171,26 @@ export function emitXbriefMigration(outcome, io, options = {}) {
177
171
  }
178
172
  }
179
173
  }
174
+ /**
175
+ * Rewrite legacy `vbrief` crossover tokens in the UNMANAGED AGENTS.md header
176
+ * after lifecycle migration + agents:refresh (#2154). The managed section is
177
+ * left byte-for-byte intact; only the freeform header/tail path literals are
178
+ * patched. Idempotent and non-fatal — a header with no legacy tokens is a
179
+ * clean no-op, and a write failure surfaces as a `failed` outcome on stderr
180
+ * rather than an exception. Always returns 0; the migration itself already
181
+ * succeeded, so a header-patch hiccup must not fail the whole command.
182
+ */
183
+ function runHeaderPatch(projectRoot, io) {
184
+ const outcome = patchAgentsMdHeader(projectRoot);
185
+ const summary = `${renderHeaderPatchSummary(outcome)}\n`;
186
+ if (outcome.kind === "failed") {
187
+ io.writeErr(summary);
188
+ }
189
+ else {
190
+ io.writeOut(summary);
191
+ }
192
+ return 0;
193
+ }
180
194
  /** End-to-end migrate:xbrief handler including optional agents:refresh (#2110). */
181
195
  export function runXbriefMigrationCli(args, io) {
182
196
  const outcome = runXbriefMigration(args, io);
@@ -184,6 +198,11 @@ export function runXbriefMigrationCli(args, io) {
184
198
  if (code !== 0 || outcome.kind !== "migrated") {
185
199
  return code;
186
200
  }
187
- return runAgentsRefresh(resolve(args.projectRoot), args.frameworkRoot, io);
201
+ const projectRoot = resolve(args.projectRoot);
202
+ const refreshCode = runAgentsRefresh(projectRoot, args.frameworkRoot, io);
203
+ if (refreshCode !== 0) {
204
+ return refreshCode;
205
+ }
206
+ return runHeaderPatch(projectRoot, io);
188
207
  }
189
208
  //# sourceMappingURL=migrate-project.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deftai/directive-core",
3
- "version": "0.66.2",
3
+ "version": "0.68.0",
4
4
  "description": "TypeScript engine core for the Directive framework.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -30,6 +30,14 @@
30
30
  "types": "./dist/wip-cap/index.d.ts",
31
31
  "default": "./dist/wip-cap/index.js"
32
32
  },
33
+ "./agents-md-budget": {
34
+ "types": "./dist/agents-md-budget/index.d.ts",
35
+ "default": "./dist/agents-md-budget/index.js"
36
+ },
37
+ "./agents-md-advisory": {
38
+ "types": "./dist/agents-md-advisory/index.d.ts",
39
+ "default": "./dist/agents-md-advisory/index.js"
40
+ },
33
41
  "./xbrief-migrate": {
34
42
  "types": "./dist/xbrief-migrate/index.d.ts",
35
43
  "default": "./dist/xbrief-migrate/index.js"
@@ -102,6 +110,10 @@
102
110
  "types": "./dist/pr-wait-mergeable/index.d.ts",
103
111
  "default": "./dist/pr-wait-mergeable/index.js"
104
112
  },
113
+ "./pr-watch": {
114
+ "types": "./dist/pr-watch/index.d.ts",
115
+ "default": "./dist/pr-watch/index.js"
116
+ },
105
117
  "./vbrief-build": {
106
118
  "types": "./dist/vbrief-build/index.d.ts",
107
119
  "default": "./dist/vbrief-build/index.js"
@@ -210,10 +222,6 @@
210
222
  "types": "./dist/umbrella-current-shape/index.d.ts",
211
223
  "default": "./dist/umbrella-current-shape/index.js"
212
224
  },
213
- "./install-upgrade": {
214
- "types": "./dist/install-upgrade/index.d.ts",
215
- "default": "./dist/install-upgrade/index.js"
216
- },
217
225
  "./task-surface": {
218
226
  "types": "./dist/task-surface/index.d.ts",
219
227
  "default": "./dist/task-surface/index.js"
@@ -237,8 +245,8 @@
237
245
  "provenance": true
238
246
  },
239
247
  "dependencies": {
240
- "@deftai/directive-content": "^0.66.2",
241
- "@deftai/directive-types": "^0.66.2",
248
+ "@deftai/directive-content": "^0.68.0",
249
+ "@deftai/directive-types": "^0.68.0",
242
250
  "archiver": "^8.0.0"
243
251
  },
244
252
  "scripts": {
@@ -1,13 +0,0 @@
1
- export interface InstallUpgradeArgs {
2
- readonly projectRoot: string;
3
- readonly frameworkRoot: string;
4
- readonly migrate?: boolean;
5
- readonly force?: boolean;
6
- }
7
- export interface InstallUpgradeIo {
8
- writeOut: (text: string) => void;
9
- writeErr: (text: string) => void;
10
- }
11
- /** Port of ``run upgrade`` / ``task install:upgrade`` for the consumer task surface (#1061 / #2022). */
12
- export declare function runInstallUpgrade(args: InstallUpgradeArgs, io: InstallUpgradeIo): number;
13
- //# sourceMappingURL=index.d.ts.map