@deftai/directive-core 0.66.2 → 0.67.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.
- package/dist/agents-md-advisory/evaluate.d.ts +44 -0
- package/dist/agents-md-advisory/evaluate.js +115 -0
- package/dist/agents-md-advisory/index.d.ts +2 -0
- package/dist/agents-md-advisory/index.js +2 -0
- package/dist/agents-md-budget/evaluate.d.ts +38 -0
- package/dist/agents-md-budget/evaluate.js +152 -0
- package/dist/agents-md-budget/index.d.ts +2 -0
- package/dist/agents-md-budget/index.js +2 -0
- package/dist/codebase/map.d.ts +1 -0
- package/dist/codebase/map.js +5 -2
- package/dist/doctor/constants.d.ts +5 -2
- package/dist/doctor/constants.js +15 -1
- package/dist/doctor/flags.js +5 -1
- package/dist/doctor/main.js +99 -8
- package/dist/doctor/types.d.ts +7 -0
- package/dist/intake/github-auth-modes.d.ts +1 -0
- package/dist/intake/github-auth-modes.js +1 -0
- package/dist/intake/issue-ingest.d.ts +6 -2
- package/dist/intake/issue-ingest.js +65 -9
- package/dist/platform/agents-consumer-header.d.ts +13 -0
- package/dist/platform/agents-consumer-header.js +57 -0
- package/dist/platform/agents-md.js +4 -1
- package/dist/platform/index.d.ts +1 -0
- package/dist/platform/index.js +1 -0
- package/dist/policy/agents-md-advisory.d.ts +52 -0
- package/dist/policy/agents-md-advisory.js +63 -0
- package/dist/policy/agents-md-budget.d.ts +24 -0
- package/dist/policy/agents-md-budget.js +89 -0
- package/dist/policy/index.d.ts +1 -0
- package/dist/policy/index.js +1 -0
- package/dist/pr-merge-readiness/ci-gate.d.ts +28 -0
- package/dist/pr-merge-readiness/ci-gate.js +79 -0
- package/dist/pr-merge-readiness/compute.d.ts +5 -1
- package/dist/pr-merge-readiness/compute.js +90 -6
- package/dist/pr-merge-readiness/gh.d.ts +8 -0
- package/dist/pr-merge-readiness/gh.js +30 -5
- package/dist/pr-merge-readiness/index.d.ts +3 -0
- package/dist/pr-merge-readiness/index.js +2 -0
- package/dist/pr-merge-readiness/main.d.ts +3 -0
- package/dist/pr-merge-readiness/main.js +73 -6
- package/dist/pr-merge-readiness/output.js +20 -0
- package/dist/pr-merge-readiness/slizard-gate.d.ts +47 -0
- package/dist/pr-merge-readiness/slizard-gate.js +119 -0
- package/dist/preflight-cache/evaluate.d.ts +1 -0
- package/dist/preflight-cache/evaluate.js +17 -10
- package/dist/render/project-render.d.ts +16 -2
- package/dist/render/project-render.js +55 -22
- package/dist/swarm/launch.d.ts +6 -0
- package/dist/swarm/launch.js +2 -2
- package/dist/swarm/routing-verify.d.ts +1 -1
- package/dist/swarm/routing-verify.js +11 -10
- package/dist/swarm/routing.d.ts +9 -0
- package/dist/swarm/routing.js +46 -0
- package/dist/swarm/verify-review-clean-cli.js +25 -1
- package/dist/swarm/verify-review-clean.d.ts +9 -1
- package/dist/swarm/verify-review-clean.js +76 -3
- package/dist/verify-source/biome-config.d.ts +41 -0
- package/dist/verify-source/biome-config.js +97 -0
- package/dist/verify-source/index.d.ts +1 -0
- package/dist/verify-source/index.js +1 -0
- package/dist/xbrief-migrate/agents-header.d.ts +69 -0
- package/dist/xbrief-migrate/agents-header.js +179 -0
- package/dist/xbrief-migrate/fs-helpers.d.ts +3 -0
- package/dist/xbrief-migrate/fs-helpers.js +11 -0
- package/dist/xbrief-migrate/index.d.ts +1 -0
- package/dist/xbrief-migrate/index.js +1 -0
- package/dist/xbrief-migrate/migrate-project.js +29 -10
- package/package.json +11 -3
|
@@ -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";
|
|
@@ -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,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,
|
|
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
|
-
|
|
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.
|
|
3
|
+
"version": "0.67.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"
|
|
@@ -237,8 +245,8 @@
|
|
|
237
245
|
"provenance": true
|
|
238
246
|
},
|
|
239
247
|
"dependencies": {
|
|
240
|
-
"@deftai/directive-content": "^0.
|
|
241
|
-
"@deftai/directive-types": "^0.
|
|
248
|
+
"@deftai/directive-content": "^0.67.0",
|
|
249
|
+
"@deftai/directive-types": "^0.67.0",
|
|
242
250
|
"archiver": "^8.0.0"
|
|
243
251
|
},
|
|
244
252
|
"scripts": {
|