@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,44 @@
|
|
|
1
|
+
import { type RegionCounts } from "../agents-md-budget/evaluate.js";
|
|
2
|
+
import { type AgentsMdAdvisorySource } from "../policy/agents-md-advisory.js";
|
|
3
|
+
export type OutputStream = "stdout" | "stderr" | "none";
|
|
4
|
+
/**
|
|
5
|
+
* Result of the consumer AGENTS.md advisory evaluation (#2155).
|
|
6
|
+
*
|
|
7
|
+
* `code` is 0 in the DEFAULT advisory posture no matter what -- the advisory
|
|
8
|
+
* MUST NEVER fail-close a consumer build. Non-zero codes are only ever produced
|
|
9
|
+
* in the explicit `--enforce` opt-in posture (1 = over the hard cap the
|
|
10
|
+
* consumer asked for; 2 = a config problem that blocks an enforced check).
|
|
11
|
+
*/
|
|
12
|
+
export interface AdvisoryEvaluateResult {
|
|
13
|
+
readonly code: 0 | 1 | 2;
|
|
14
|
+
readonly message: string;
|
|
15
|
+
readonly stream: OutputStream;
|
|
16
|
+
/** True when the unmanaged region exceeds the soft budget. */
|
|
17
|
+
readonly over: boolean;
|
|
18
|
+
/** Region counts, or null when AGENTS.md was missing / unreadable / malformed. */
|
|
19
|
+
readonly counts: RegionCounts | null;
|
|
20
|
+
/** The resolved soft budget the unmanaged region was compared against. */
|
|
21
|
+
readonly softMaxLines: number;
|
|
22
|
+
/** Provenance of the soft budget (typed / default / default-on-error). */
|
|
23
|
+
readonly source: AgentsMdAdvisorySource;
|
|
24
|
+
}
|
|
25
|
+
export interface AdvisoryEvaluateOptions {
|
|
26
|
+
/**
|
|
27
|
+
* Opt-in hard-cap posture (#1419 enforce tier). When true, an over-budget
|
|
28
|
+
* unmanaged region exits 1 and a config problem exits 2. Default (false) is
|
|
29
|
+
* the advisory posture that always exits 0.
|
|
30
|
+
*/
|
|
31
|
+
readonly enforce?: boolean;
|
|
32
|
+
/** Suppress the within-budget "OK" line (used by aggregate callers). */
|
|
33
|
+
readonly quiet?: boolean;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Evaluate the consumer AGENTS.md against its soft, unmanaged-focused budget.
|
|
37
|
+
*
|
|
38
|
+
* Reuses the #645 region counter (`countRegions`) so the framework-owned
|
|
39
|
+
* managed section is EXCLUDED from the comparison -- only the consumer-authored
|
|
40
|
+
* unmanaged region is measured. In the default advisory posture the result code
|
|
41
|
+
* is always 0; `--enforce` promotes over-budget / config problems to non-zero.
|
|
42
|
+
*/
|
|
43
|
+
export declare function evaluate(projectRoot: string, options?: AdvisoryEvaluateOptions): AdvisoryEvaluateResult;
|
|
44
|
+
//# sourceMappingURL=evaluate.d.ts.map
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { countRegions } from "../agents-md-budget/evaluate.js";
|
|
4
|
+
import { resolveAgentsMdAdvisory, } from "../policy/agents-md-advisory.js";
|
|
5
|
+
const GUIDANCE_LINE = " AGENTS.md is a map, not a manual (#1882): push project detail into a\n" +
|
|
6
|
+
" reference doc and leave a pointer, rather than growing AGENTS.md. See\n" +
|
|
7
|
+
" content/docs/good-agents-md.md.";
|
|
8
|
+
function overMessage(counts, softMax, enforce, fieldName) {
|
|
9
|
+
const over = counts.unmanaged - softMax;
|
|
10
|
+
if (enforce) {
|
|
11
|
+
return (`❌ agents-md-advisory: AGENTS.md unmanaged (project-authored) region is ` +
|
|
12
|
+
`${counts.unmanaged} lines, over the enforced cap of ${softMax} (OVER by ${over}).\n` +
|
|
13
|
+
`${GUIDANCE_LINE}\n` +
|
|
14
|
+
` Raise ${fieldName} in PROJECT-DEFINITION to accept the growth.`);
|
|
15
|
+
}
|
|
16
|
+
return (`⚠ agents-md-advisory: AGENTS.md unmanaged (project-authored) region is ` +
|
|
17
|
+
`${counts.unmanaged} lines, over the soft budget of ${softMax} (OVER by ${over}). ` +
|
|
18
|
+
"This is advisory only -- your build is NOT affected.\n" +
|
|
19
|
+
`${GUIDANCE_LINE}\n` +
|
|
20
|
+
` This is your knob: raise ${fieldName} in PROJECT-DEFINITION to accept the\n` +
|
|
21
|
+
" growth and silence this note.");
|
|
22
|
+
}
|
|
23
|
+
const FIELD_NAME = "plan.policy.agentsMdAdvisory.unmanagedSoftMaxLines";
|
|
24
|
+
/**
|
|
25
|
+
* Evaluate the consumer AGENTS.md against its soft, unmanaged-focused budget.
|
|
26
|
+
*
|
|
27
|
+
* Reuses the #645 region counter (`countRegions`) so the framework-owned
|
|
28
|
+
* managed section is EXCLUDED from the comparison -- only the consumer-authored
|
|
29
|
+
* unmanaged region is measured. In the default advisory posture the result code
|
|
30
|
+
* is always 0; `--enforce` promotes over-budget / config problems to non-zero.
|
|
31
|
+
*/
|
|
32
|
+
export function evaluate(projectRoot, options = {}) {
|
|
33
|
+
const root = resolve(projectRoot);
|
|
34
|
+
const enforce = options.enforce ?? false;
|
|
35
|
+
const quiet = options.quiet ?? false;
|
|
36
|
+
const advisory = resolveAgentsMdAdvisory(root);
|
|
37
|
+
const softMax = advisory.config.unmanagedSoftMaxLines;
|
|
38
|
+
const agentsPath = join(root, "AGENTS.md");
|
|
39
|
+
if (!existsSync(agentsPath)) {
|
|
40
|
+
const message = `agents-md-advisory: no AGENTS.md found at ${agentsPath}`;
|
|
41
|
+
return {
|
|
42
|
+
code: enforce ? 2 : 0,
|
|
43
|
+
message: enforce ? `❌ ${message}` : quiet ? "" : `${message} (advisory; skipped).`,
|
|
44
|
+
stream: enforce ? "stderr" : quiet ? "none" : "stdout",
|
|
45
|
+
over: false,
|
|
46
|
+
counts: null,
|
|
47
|
+
softMaxLines: softMax,
|
|
48
|
+
source: advisory.source,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
let text;
|
|
52
|
+
try {
|
|
53
|
+
text = readFileSync(agentsPath, { encoding: "utf8" });
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
const message = `agents-md-advisory: AGENTS.md at ${agentsPath} cannot be read: ${String(err)}`;
|
|
57
|
+
return {
|
|
58
|
+
code: enforce ? 2 : 0,
|
|
59
|
+
message: enforce ? `❌ ${message}` : quiet ? "" : `${message} (advisory; skipped).`,
|
|
60
|
+
stream: enforce ? "stderr" : quiet ? "none" : "stdout",
|
|
61
|
+
over: false,
|
|
62
|
+
counts: null,
|
|
63
|
+
softMaxLines: softMax,
|
|
64
|
+
source: advisory.source,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const regionResult = countRegions(text);
|
|
68
|
+
if ("error" in regionResult) {
|
|
69
|
+
const message = `agents-md-advisory: ${regionResult.error}`;
|
|
70
|
+
return {
|
|
71
|
+
code: enforce ? 2 : 0,
|
|
72
|
+
message: enforce ? `❌ ${message}` : quiet ? "" : `${message} (advisory; skipped).`,
|
|
73
|
+
stream: enforce ? "stderr" : quiet ? "none" : "stdout",
|
|
74
|
+
over: false,
|
|
75
|
+
counts: null,
|
|
76
|
+
softMaxLines: softMax,
|
|
77
|
+
source: advisory.source,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const counts = regionResult.counts;
|
|
81
|
+
const over = counts.unmanaged > softMax;
|
|
82
|
+
if (!over) {
|
|
83
|
+
if (quiet) {
|
|
84
|
+
return {
|
|
85
|
+
code: 0,
|
|
86
|
+
message: "",
|
|
87
|
+
stream: "none",
|
|
88
|
+
over: false,
|
|
89
|
+
counts,
|
|
90
|
+
softMaxLines: softMax,
|
|
91
|
+
source: advisory.source,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
code: 0,
|
|
96
|
+
message: `✓ agents-md-advisory: AGENTS.md unmanaged region ${counts.unmanaged}/${softMax} lines ` +
|
|
97
|
+
`within soft budget (managed ${counts.managed} excluded; advisory).`,
|
|
98
|
+
stream: "stdout",
|
|
99
|
+
over: false,
|
|
100
|
+
counts,
|
|
101
|
+
softMaxLines: softMax,
|
|
102
|
+
source: advisory.source,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
code: enforce ? 1 : 0,
|
|
107
|
+
message: overMessage(counts, softMax, enforce, FIELD_NAME),
|
|
108
|
+
stream: "stderr",
|
|
109
|
+
over: true,
|
|
110
|
+
counts,
|
|
111
|
+
softMaxLines: softMax,
|
|
112
|
+
source: advisory.source,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=evaluate.js.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export type OutputStream = "stdout" | "stderr" | "none";
|
|
2
|
+
/** Result of verify:agents-md-budget evaluation; three-state exit contract. */
|
|
3
|
+
export interface EvaluateResult {
|
|
4
|
+
readonly code: 0 | 1 | 2;
|
|
5
|
+
readonly message: string;
|
|
6
|
+
readonly stream: OutputStream;
|
|
7
|
+
}
|
|
8
|
+
export interface EvaluateOptions {
|
|
9
|
+
readonly quiet?: boolean;
|
|
10
|
+
}
|
|
11
|
+
/** Per-region line counts of an AGENTS.md file. */
|
|
12
|
+
export interface RegionCounts {
|
|
13
|
+
readonly total: number;
|
|
14
|
+
readonly managed: number;
|
|
15
|
+
readonly unmanaged: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Split AGENTS.md into managed / unmanaged line counts.
|
|
19
|
+
*
|
|
20
|
+
* Returns `{ counts }` on success, or `{ error }` when the managed markers are
|
|
21
|
+
* malformed (exactly one marker present, or close-before-open). A file with no
|
|
22
|
+
* markers at all is valid: the whole file counts as unmanaged.
|
|
23
|
+
*/
|
|
24
|
+
export declare function countRegions(text: string): {
|
|
25
|
+
counts: RegionCounts;
|
|
26
|
+
} | {
|
|
27
|
+
error: string;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Pure evaluator for the AGENTS.md line-budget ratchet gate (#645).
|
|
31
|
+
*
|
|
32
|
+
* Counts the managed section and the unmanaged region separately (the #1309
|
|
33
|
+
* propagation duplicates content across the marker) and fails when either
|
|
34
|
+
* region exceeds its typed per-region budget. Ships green because the budget is
|
|
35
|
+
* seeded at current size; growth past the ratchet fails.
|
|
36
|
+
*/
|
|
37
|
+
export declare function evaluate(projectRoot: string, options?: EvaluateOptions): EvaluateResult;
|
|
38
|
+
//# sourceMappingURL=evaluate.d.ts.map
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { AGENTS_MANAGED_CLOSE } from "../platform/constants.js";
|
|
4
|
+
import { resolveAgentsMdBudget } from "../policy/agents-md-budget.js";
|
|
5
|
+
const OPEN_MARKER_PREFIX = "<!-- deft:managed-section";
|
|
6
|
+
/**
|
|
7
|
+
* Split AGENTS.md into managed / unmanaged line counts.
|
|
8
|
+
*
|
|
9
|
+
* Returns `{ counts }` on success, or `{ error }` when the managed markers are
|
|
10
|
+
* malformed (exactly one marker present, or close-before-open). A file with no
|
|
11
|
+
* markers at all is valid: the whole file counts as unmanaged.
|
|
12
|
+
*/
|
|
13
|
+
export function countRegions(text) {
|
|
14
|
+
const lines = text.replace(/\r\n/g, "\n").split("\n");
|
|
15
|
+
// Ignore the trailing empty element produced by a final newline.
|
|
16
|
+
if (lines.length > 0 && lines[lines.length - 1] === "") {
|
|
17
|
+
lines.pop();
|
|
18
|
+
}
|
|
19
|
+
const total = lines.length;
|
|
20
|
+
const openLine = lines.findIndex((l) => l.startsWith(OPEN_MARKER_PREFIX));
|
|
21
|
+
const closeLine = lines.findIndex((l) => l.trim().startsWith(AGENTS_MANAGED_CLOSE));
|
|
22
|
+
const openCount = lines.filter((l) => l.startsWith(OPEN_MARKER_PREFIX)).length;
|
|
23
|
+
const closeCount = lines.filter((l) => l.trim().startsWith(AGENTS_MANAGED_CLOSE)).length;
|
|
24
|
+
if (openLine === -1 && closeLine === -1) {
|
|
25
|
+
return { counts: { total, managed: 0, unmanaged: total } };
|
|
26
|
+
}
|
|
27
|
+
// Malformed if a marker is missing, close precedes open, or either marker is
|
|
28
|
+
// duplicated -- the contract promises exactly one open/close pair.
|
|
29
|
+
if (openLine === -1 ||
|
|
30
|
+
closeLine === -1 ||
|
|
31
|
+
closeLine < openLine ||
|
|
32
|
+
openCount > 1 ||
|
|
33
|
+
closeCount > 1) {
|
|
34
|
+
return {
|
|
35
|
+
error: "AGENTS.md managed-section markers are malformed " +
|
|
36
|
+
`(open@${openLine === -1 ? "none" : openLine + 1}×${openCount}, ` +
|
|
37
|
+
`close@${closeLine === -1 ? "none" : closeLine + 1}×${closeCount}); ` +
|
|
38
|
+
"expected a single <!-- deft:managed-section ... --> ... " +
|
|
39
|
+
"<!-- /deft:managed-section --> pair.",
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
const managed = closeLine - openLine + 1;
|
|
43
|
+
return { counts: { total, managed, unmanaged: total - managed } };
|
|
44
|
+
}
|
|
45
|
+
function formatRefusal(counts, managedMax, unmanagedMax, projectRoot) {
|
|
46
|
+
const over = [];
|
|
47
|
+
if (counts.managed > managedMax) {
|
|
48
|
+
over.push(` managed region: ${counts.managed}/${managedMax} lines (OVER by ${counts.managed - managedMax})`);
|
|
49
|
+
}
|
|
50
|
+
if (counts.unmanaged > unmanagedMax) {
|
|
51
|
+
over.push(` unmanaged region: ${counts.unmanaged}/${unmanagedMax} lines (OVER by ${counts.unmanaged - unmanagedMax})`);
|
|
52
|
+
}
|
|
53
|
+
return (`❌ verify:agents-md-budget: AGENTS.md grew past its ratchet ` +
|
|
54
|
+
`(project_root=${projectRoot}).\n` +
|
|
55
|
+
`${over.join("\n")}\n` +
|
|
56
|
+
" AGENTS.md is a map, not a manual (#1882): push detail into a\n" +
|
|
57
|
+
" reference doc (main.md / a pack / docs/) and leave a pointer,\n" +
|
|
58
|
+
" rather than expanding AGENTS.md. See REFERENCES.md.\n" +
|
|
59
|
+
" If the growth is deliberate, raise the matching line in\n" +
|
|
60
|
+
" plan.policy.agentsMdBudget in PROJECT-DEFINITION (a reviewed diff). (#645)");
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Pure evaluator for the AGENTS.md line-budget ratchet gate (#645).
|
|
64
|
+
*
|
|
65
|
+
* Counts the managed section and the unmanaged region separately (the #1309
|
|
66
|
+
* propagation duplicates content across the marker) and fails when either
|
|
67
|
+
* region exceeds its typed per-region budget. Ships green because the budget is
|
|
68
|
+
* seeded at current size; growth past the ratchet fails.
|
|
69
|
+
*/
|
|
70
|
+
export function evaluate(projectRoot, options = {}) {
|
|
71
|
+
const root = resolve(projectRoot);
|
|
72
|
+
const quiet = options.quiet ?? false;
|
|
73
|
+
const budgetResult = resolveAgentsMdBudget(root);
|
|
74
|
+
if (budgetResult.source === "default-on-error") {
|
|
75
|
+
return {
|
|
76
|
+
code: 2,
|
|
77
|
+
message: `❌ verify:agents-md-budget: PROJECT-DEFINITION malformed: ${budgetResult.error}`,
|
|
78
|
+
stream: "stderr",
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
const agentsPath = join(root, "AGENTS.md");
|
|
82
|
+
if (!existsSync(agentsPath)) {
|
|
83
|
+
return {
|
|
84
|
+
code: 2,
|
|
85
|
+
message: `❌ verify:agents-md-budget: AGENTS.md not found at ${agentsPath}`,
|
|
86
|
+
stream: "stderr",
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
let text;
|
|
90
|
+
try {
|
|
91
|
+
text = readFileSync(agentsPath, { encoding: "utf8" });
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
return {
|
|
95
|
+
code: 2,
|
|
96
|
+
message: `❌ verify:agents-md-budget: AGENTS.md at ${agentsPath} cannot be read: ${String(err)}`,
|
|
97
|
+
stream: "stderr",
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
const regionResult = countRegions(text);
|
|
101
|
+
if ("error" in regionResult) {
|
|
102
|
+
return {
|
|
103
|
+
code: 2,
|
|
104
|
+
message: `❌ verify:agents-md-budget: ${regionResult.error}`,
|
|
105
|
+
stream: "stderr",
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
const counts = regionResult.counts;
|
|
109
|
+
if (budgetResult.source === "unset") {
|
|
110
|
+
if (quiet) {
|
|
111
|
+
return { code: 0, message: "", stream: "none" };
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
code: 0,
|
|
115
|
+
message: "⚠ verify:agents-md-budget: no plan.policy.agentsMdBudget configured " +
|
|
116
|
+
`(managed=${counts.managed}, unmanaged=${counts.unmanaged} lines).\n` +
|
|
117
|
+
" Seed a ratchet at current size to freeze growth (#645): set\n" +
|
|
118
|
+
" plan.policy.agentsMdBudget.{managedMaxLines,unmanagedMaxLines} in " +
|
|
119
|
+
"PROJECT-DEFINITION.",
|
|
120
|
+
stream: "stderr",
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/* v8 ignore start -- defensive: source "typed" always carries a non-null budget. */
|
|
124
|
+
if (budgetResult.budget === null) {
|
|
125
|
+
return {
|
|
126
|
+
code: 2,
|
|
127
|
+
message: "❌ verify:agents-md-budget: unexpected null budget for typed source",
|
|
128
|
+
stream: "stderr",
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
/* v8 ignore stop */
|
|
132
|
+
const budget = budgetResult.budget;
|
|
133
|
+
const overManaged = counts.managed > budget.managedMaxLines;
|
|
134
|
+
const overUnmanaged = counts.unmanaged > budget.unmanagedMaxLines;
|
|
135
|
+
if (!overManaged && !overUnmanaged) {
|
|
136
|
+
if (quiet) {
|
|
137
|
+
return { code: 0, message: "", stream: "none" };
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
code: 0,
|
|
141
|
+
message: `✓ verify:agents-md-budget: managed ${counts.managed}/${budget.managedMaxLines}, ` +
|
|
142
|
+
`unmanaged ${counts.unmanaged}/${budget.unmanagedMaxLines} lines (within ratchet).`,
|
|
143
|
+
stream: "stdout",
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
code: 1,
|
|
148
|
+
message: formatRefusal(counts, budget.managedMaxLines, budget.unmanagedMaxLines, root),
|
|
149
|
+
stream: "stderr",
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=evaluate.js.map
|
package/dist/codebase/map.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export declare const GENERATED_SENTINEL = "AUTO-GENERATED by task codebase:map";
|
|
2
2
|
export declare const DEFAULT_OUTPUT_PATH = ".planning/codebase/MAP.md";
|
|
3
3
|
export declare const BANNER_TEMPLATE: string;
|
|
4
|
+
export declare function markdownText(value: unknown): string;
|
|
4
5
|
export declare function projectionOutputPath(projectRoot: string, explicitOutput?: string | null): string;
|
|
5
6
|
export declare function renderCodebaseMap(artifact: Record<string, unknown>): string;
|
|
6
7
|
export declare function isDeftGenerated(path: string): boolean;
|
package/dist/codebase/map.js
CHANGED
|
@@ -13,9 +13,12 @@ export const BANNER_TEMPLATE = "<!-- AUTO-GENERATED by task codebase:map -- DO N
|
|
|
13
13
|
"<!-- Source of truth: vbrief/PROJECT-DEFINITION.vbrief.json " +
|
|
14
14
|
"plan.architecture.codeStructure -->\n" +
|
|
15
15
|
"<!-- Regenerate with: task codebase:map -->\n";
|
|
16
|
-
function markdownText(value) {
|
|
16
|
+
export function markdownText(value) {
|
|
17
17
|
const text = value === null || value === undefined ? "" : String(value);
|
|
18
|
-
|
|
18
|
+
// Escape the backslash escape character FIRST, then the `|` table delimiter.
|
|
19
|
+
// Escaping `|` alone lets a pre-existing backslash bypass the delimiter
|
|
20
|
+
// escape (CodeQL js/incomplete-sanitization).
|
|
21
|
+
return text.replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\n/g, " ").trim();
|
|
19
22
|
}
|
|
20
23
|
function code(value) {
|
|
21
24
|
const text = markdownText(value);
|
|
@@ -6,7 +6,11 @@ export declare const DEPRECATED_REDIRECT_SENTINEL = "<!-- deft:deprecated-redire
|
|
|
6
6
|
export declare const DEPRECATED_SKILL_REDIRECT_SENTINEL = "<!-- deft:deprecated-skill-redirect -->";
|
|
7
7
|
export declare const REDIRECT_STUB_HEADER_LINES = 8;
|
|
8
8
|
export declare const TASKFILE_INCLUDE_SNIPPET = "version: '3'\n\nincludes:\n deft:\n taskfile: ./.deft/core/Taskfile.yml\n optional: true\n";
|
|
9
|
-
export declare const DOCTOR_ALLOWED_FLAGS: readonly ["--session", "--fix", "--repair", "--repair-taskfile", "--json", "--quiet", "--full", "--project-root", "-h", "--help"];
|
|
9
|
+
export declare const DOCTOR_ALLOWED_FLAGS: readonly ["--session", "--fix", "--repair", "--repair-taskfile", "--json", "--quiet", "--full", "--network", "--project-root", "-h", "--help"];
|
|
10
|
+
/** npm consumer deposit after #2022 Phase 3 -- Python scripts/ tree is intentionally absent. */
|
|
11
|
+
export declare const NPM_PACKAGE_NAME = "@deftai/directive";
|
|
12
|
+
export declare const NETWORK_DISCLOSURE_LINE: string;
|
|
13
|
+
export declare const PAYLOAD_STALENESS_OFFLINE_SKIP_MESSAGE: string;
|
|
10
14
|
export declare const EXPECTED_FRAMEWORK_DIRS: readonly ["tasks", "scripts", "vbrief"];
|
|
11
15
|
/** npm consumer deposit after #2022 Phase 3 -- Python scripts/ tree is intentionally absent. */
|
|
12
16
|
export declare const CONSUMER_FRAMEWORK_DIRS: readonly ["tasks", "vbrief"];
|
|
@@ -14,7 +18,6 @@ export declare const DEFT_REPO_POSITIVE_MARKERS: readonly ["content/templates/ag
|
|
|
14
18
|
export declare const EXPECTED_CONTENT_DIRS: readonly ["languages", "strategies", "skills", "templates"];
|
|
15
19
|
/** Post-freeze canonical upgrade path (#1997 / #2003 / #1912). */
|
|
16
20
|
export declare const CANONICAL_UPGRADE_COMMAND = "npm i -g @deftai/directive@latest";
|
|
17
|
-
export declare const NPM_PACKAGE_NAME = "@deftai/directive";
|
|
18
21
|
export declare const CLEAN_WINDOW_HOURS = 24;
|
|
19
22
|
export declare const DIRTY_WINDOW_HOURS = 4;
|
|
20
23
|
export declare const ENV_STATE_PATH = "DEFT_DOCTOR_STATE_PATH";
|
package/dist/doctor/constants.js
CHANGED
|
@@ -20,10 +20,25 @@ export const DOCTOR_ALLOWED_FLAGS = [
|
|
|
20
20
|
"--json",
|
|
21
21
|
"--quiet",
|
|
22
22
|
"--full",
|
|
23
|
+
"--network",
|
|
23
24
|
"--project-root",
|
|
24
25
|
"-h",
|
|
25
26
|
"--help",
|
|
26
27
|
];
|
|
28
|
+
/** npm consumer deposit after #2022 Phase 3 -- Python scripts/ tree is intentionally absent. */
|
|
29
|
+
export const NPM_PACKAGE_NAME = "@deftai/directive";
|
|
30
|
+
// #2182: payload-staleness is the only doctor check that can reach a network
|
|
31
|
+
// endpoint (git ls-remote against the framework's git remote, falling back to
|
|
32
|
+
// `npm view` against the npm registry). It is OFF by default (offline tier)
|
|
33
|
+
// and requires the explicit `--network` flag; this line discloses exactly
|
|
34
|
+
// which tool + registry class it may contact BEFORE the check runs, and the
|
|
35
|
+
// skip line tells an offline run how to opt in.
|
|
36
|
+
export const NETWORK_DISCLOSURE_LINE = "[deft doctor] --network: this run may contact your git remote (framework " +
|
|
37
|
+
"repo host) and, as a fallback, the npm registry (registry.npmjs.org) to " +
|
|
38
|
+
`look up the latest ${NPM_PACKAGE_NAME} version.`;
|
|
39
|
+
export const PAYLOAD_STALENESS_OFFLINE_SKIP_MESSAGE = "skip -- offline tier (default). Run `deft doctor --network` to check " +
|
|
40
|
+
"framework currency against your git remote and the npm registry " +
|
|
41
|
+
"(discloses tool + registry before contacting either).";
|
|
27
42
|
// Engine / lifecycle dirs that stay at the framework root (NOT relocated by
|
|
28
43
|
// #1875). Shippable-content dirs moved under content/ -- see EXPECTED_CONTENT_DIRS.
|
|
29
44
|
export const EXPECTED_FRAMEWORK_DIRS = ["tasks", "scripts", "vbrief"];
|
|
@@ -45,7 +60,6 @@ export const DEFT_REPO_POSITIVE_MARKERS = [
|
|
|
45
60
|
export const EXPECTED_CONTENT_DIRS = ["languages", "strategies", "skills", "templates"];
|
|
46
61
|
/** Post-freeze canonical upgrade path (#1997 / #2003 / #1912). */
|
|
47
62
|
export const CANONICAL_UPGRADE_COMMAND = "npm i -g @deftai/directive@latest";
|
|
48
|
-
export const NPM_PACKAGE_NAME = "@deftai/directive";
|
|
49
63
|
export const CLEAN_WINDOW_HOURS = 24;
|
|
50
64
|
export const DIRTY_WINDOW_HOURS = 4;
|
|
51
65
|
export const ENV_STATE_PATH = "DEFT_DOCTOR_STATE_PATH";
|
package/dist/doctor/flags.js
CHANGED
|
@@ -5,6 +5,7 @@ export function parseDoctorFlags(args) {
|
|
|
5
5
|
let json = false;
|
|
6
6
|
let quiet = false;
|
|
7
7
|
let full = false;
|
|
8
|
+
let network = false;
|
|
8
9
|
let help = false;
|
|
9
10
|
let projectRoot = null;
|
|
10
11
|
const unknown = [];
|
|
@@ -26,6 +27,9 @@ export function parseDoctorFlags(args) {
|
|
|
26
27
|
else if (token === "--full") {
|
|
27
28
|
full = true;
|
|
28
29
|
}
|
|
30
|
+
else if (token === "--network") {
|
|
31
|
+
network = true;
|
|
32
|
+
}
|
|
29
33
|
else if (token === "-h" || token === "--help") {
|
|
30
34
|
help = true;
|
|
31
35
|
}
|
|
@@ -52,7 +56,7 @@ export function parseDoctorFlags(args) {
|
|
|
52
56
|
}
|
|
53
57
|
i += 1;
|
|
54
58
|
}
|
|
55
|
-
return { session, fix, json, quiet, full, help, projectRoot, unknown };
|
|
59
|
+
return { session, fix, json, quiet, full, network, help, projectRoot, unknown };
|
|
56
60
|
}
|
|
57
61
|
export function formatUnknownFlagsError(unknown) {
|
|
58
62
|
return `Unknown flag(s): ${unknown.join(", ")}`;
|
package/dist/doctor/main.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
|
+
import { evaluate as evaluateAgentsMdAdvisory } from "../agents-md-advisory/evaluate.js";
|
|
3
4
|
import { contentRoot } from "../content-root.js";
|
|
4
5
|
import { agentsRefreshPlan, hasV3ManagedMarker } from "./agents-md.js";
|
|
5
6
|
import { runChecks } from "./checks.js";
|
|
6
|
-
import { CONSUMER_FRAMEWORK_DIRS, EXPECTED_CONTENT_DIRS, EXPECTED_FRAMEWORK_DIRS, TASKFILE_INCLUDE_SNIPPET, UV_INSTALL_URL, } from "./constants.js";
|
|
7
|
+
import { CONSUMER_FRAMEWORK_DIRS, EXPECTED_CONTENT_DIRS, EXPECTED_FRAMEWORK_DIRS, NETWORK_DISCLOSURE_LINE, PAYLOAD_STALENESS_OFFLINE_SKIP_MESSAGE, TASKFILE_INCLUDE_SNIPPET, UV_INSTALL_URL, } from "./constants.js";
|
|
7
8
|
import { decideThrottle, formatIsoZ, readState, renderDoctorStatusLine, writeState, } from "./doctor-state.js";
|
|
8
9
|
import { formatAllowedFlagsHint, formatUnknownFlagsError, parseDoctorFlags } from "./flags.js";
|
|
9
10
|
import { pythonJsonDump } from "./json.js";
|
|
@@ -130,13 +131,38 @@ export function cmdDoctor(args, seams = {}) {
|
|
|
130
131
|
if (!jsonMode) {
|
|
131
132
|
sink.blank();
|
|
132
133
|
}
|
|
133
|
-
sink.info("Checking
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
134
|
+
sink.info("Checking AGENTS.md legibility (advisory)...");
|
|
135
|
+
runAgentsMdAdvisoryCheck(projectRoot, sink, addFinding, seams);
|
|
136
|
+
if (!jsonMode) {
|
|
137
|
+
sink.blank();
|
|
138
|
+
}
|
|
139
|
+
// #2182: payload-staleness is the only doctor check that can reach a
|
|
140
|
+
// registry (git remote, then npm as a fallback). It stays in the OFFLINE
|
|
141
|
+
// tier (skipped) unless the operator explicitly opts into the NETWORK tier
|
|
142
|
+
// via `--network`, and the tool + registry class is disclosed BEFORE the
|
|
143
|
+
// check runs -- never silently, never as a side effect of a read-only
|
|
144
|
+
// `deft doctor` invocation (e.g. from the gated session-ritual step).
|
|
145
|
+
if (flags.network) {
|
|
146
|
+
sink.info(NETWORK_DISCLOSURE_LINE);
|
|
147
|
+
sink.info("Checking payload staleness from install manifest...");
|
|
148
|
+
runPayloadStalenessCheck(projectRoot, sink, addFinding, {
|
|
149
|
+
frameworkRoot,
|
|
150
|
+
readText: seams.readText,
|
|
151
|
+
isFile: seams.isFile,
|
|
152
|
+
runGitLsRemote: seams.runGitLsRemote,
|
|
153
|
+
runNpmViewVersion: seams.runNpmViewVersion,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
sink.info(`payload-staleness: ${PAYLOAD_STALENESS_OFFLINE_SKIP_MESSAGE}`);
|
|
158
|
+
addFinding({
|
|
159
|
+
severity: "skip",
|
|
160
|
+
message: PAYLOAD_STALENESS_OFFLINE_SKIP_MESSAGE,
|
|
161
|
+
check: "payload-staleness",
|
|
162
|
+
status: "skip",
|
|
163
|
+
reason: "offline-tier",
|
|
164
|
+
});
|
|
165
|
+
}
|
|
140
166
|
if (!jsonMode) {
|
|
141
167
|
sink.blank();
|
|
142
168
|
}
|
|
@@ -346,6 +372,71 @@ function runAgentsMdFreshnessCheck(projectRoot, sink, addFinding, seams) {
|
|
|
346
372
|
addFinding({ severity: "warning", message, check: checkName });
|
|
347
373
|
}
|
|
348
374
|
}
|
|
375
|
+
/**
|
|
376
|
+
* Advisory (never-failing) consumer AGENTS.md legibility signal (#2155).
|
|
377
|
+
*
|
|
378
|
+
* Reports the unmanaged (project-authored) region size against the soft budget
|
|
379
|
+
* (`plan.policy.agentsMdAdvisory.unmanagedSoftMaxLines`, generous by default).
|
|
380
|
+
* The managed section is EXCLUDED. This is the consumer companion to the
|
|
381
|
+
* maintainer-only #645 ratchet and follows the advise -> observe -> enforce
|
|
382
|
+
* posture (#1419): it emits at most a `warning` finding, NEVER an `error`, so
|
|
383
|
+
* `deft doctor` (and the `check:consumer` aggregate that depends on it) can
|
|
384
|
+
* never fail-close on a judgment call about the consumer's own file.
|
|
385
|
+
*/
|
|
386
|
+
function runAgentsMdAdvisoryCheck(projectRoot, sink, addFinding, seams) {
|
|
387
|
+
const checkName = "agents-md-advisory";
|
|
388
|
+
// Only meaningful for consumer installs (managed markers present). In the
|
|
389
|
+
// maintainer repo the #645 ratchet owns this file, so skip -- mirrors the
|
|
390
|
+
// freshness check's guard above.
|
|
391
|
+
if (runningInsideDeftRepo(projectRoot, seams) ||
|
|
392
|
+
!hasV3ManagedMarker(projectRoot, seams.readText)) {
|
|
393
|
+
const skipReason = "no managed-section markers (likely maintainer repo)";
|
|
394
|
+
sink.info(`${checkName}: skip -- ${skipReason}`);
|
|
395
|
+
addFinding({ severity: "skip", message: skipReason, check: checkName, status: "skip" });
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
try {
|
|
399
|
+
const evalFn = seams.agentsMdAdvisoryEvaluate ?? ((root) => evaluateAgentsMdAdvisory(root));
|
|
400
|
+
const result = evalFn(projectRoot);
|
|
401
|
+
if (result.over && result.counts !== null) {
|
|
402
|
+
const message = `AGENTS.md unmanaged (project-authored) region is ${result.counts.unmanaged} lines, ` +
|
|
403
|
+
`over the soft budget of ${result.softMaxLines} (advisory only -- your build is NOT ` +
|
|
404
|
+
"affected). AGENTS.md is a map, not a manual (#1882): push detail into a reference doc " +
|
|
405
|
+
"and leave a pointer (see content/docs/good-agents-md.md). Raise " +
|
|
406
|
+
"plan.policy.agentsMdAdvisory.unmanagedSoftMaxLines to accept the growth and silence this.";
|
|
407
|
+
sink.warn(message);
|
|
408
|
+
addFinding({
|
|
409
|
+
severity: "warning",
|
|
410
|
+
message,
|
|
411
|
+
check: checkName,
|
|
412
|
+
status: "over-soft-budget",
|
|
413
|
+
suggestion: "content/docs/good-agents-md.md",
|
|
414
|
+
});
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
if (result.counts !== null) {
|
|
418
|
+
sink.success(`${checkName}: unmanaged region ${result.counts.unmanaged}/${result.softMaxLines} lines (within soft budget)`);
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
// No counts (missing / unreadable / malformed AGENTS.md): advisory stays
|
|
422
|
+
// silent-but-informational; never a doctor error.
|
|
423
|
+
sink.info(`${checkName}: skip -- AGENTS.md not measurable`);
|
|
424
|
+
addFinding({
|
|
425
|
+
severity: "skip",
|
|
426
|
+
message: "AGENTS.md not measurable",
|
|
427
|
+
check: checkName,
|
|
428
|
+
status: "skip",
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
catch (exc) {
|
|
432
|
+
// Sanitize newlines so an error string can't break out of the markdown
|
|
433
|
+
// bullet when findings are rendered (CWE-116).
|
|
434
|
+
const detail = `${exc instanceof Error ? exc.name : "Error"}: ${exc}`.replace(/\r?\n/g, " ");
|
|
435
|
+
const message = `${checkName}: probe failed -- ${detail}`;
|
|
436
|
+
sink.warn(message);
|
|
437
|
+
addFinding({ severity: "warning", message, check: checkName });
|
|
438
|
+
}
|
|
439
|
+
}
|
|
349
440
|
function runTaskfileIncludeCheck(projectRoot, fixMode, jsonMode, sink, addFinding, seams) {
|
|
350
441
|
if (runningInsideDeftRepo(projectRoot, seams)) {
|
|
351
442
|
sink.info("Skipping Taskfile include check -- running inside the deft framework repo (the repo's own Taskfile.yml is the surface).");
|
package/dist/doctor/types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AdvisoryEvaluateResult } from "../agents-md-advisory/evaluate.js";
|
|
1
2
|
export declare const EXIT_CLEAN = 0;
|
|
2
3
|
export declare const EXIT_DRIFT = 1;
|
|
3
4
|
export declare const EXIT_CONFIG_ERROR = 2;
|
|
@@ -30,6 +31,7 @@ export interface DoctorFlags {
|
|
|
30
31
|
readonly json: boolean;
|
|
31
32
|
readonly quiet: boolean;
|
|
32
33
|
readonly full: boolean;
|
|
34
|
+
readonly network: boolean;
|
|
33
35
|
readonly help: boolean;
|
|
34
36
|
readonly projectRoot: string | null;
|
|
35
37
|
readonly unknown: readonly string[];
|
|
@@ -60,7 +62,12 @@ export interface DoctorSeams {
|
|
|
60
62
|
ok: boolean;
|
|
61
63
|
stdout: string;
|
|
62
64
|
};
|
|
65
|
+
readonly runNpmViewVersion?: () => {
|
|
66
|
+
ok: boolean;
|
|
67
|
+
version: string;
|
|
68
|
+
};
|
|
63
69
|
readonly agentsRefreshPlan?: (projectRoot: string) => Record<string, unknown>;
|
|
70
|
+
readonly agentsMdAdvisoryEvaluate?: (projectRoot: string) => AdvisoryEvaluateResult;
|
|
64
71
|
readonly readState?: (projectRoot: string) => DoctorState | null;
|
|
65
72
|
readonly writeState?: (projectRoot: string, payload: {
|
|
66
73
|
exitCode: number;
|
|
@@ -48,6 +48,7 @@ export interface GitHubAuthModesCliArgs {
|
|
|
48
48
|
githubAuthMode?: string | null;
|
|
49
49
|
repo?: string;
|
|
50
50
|
json?: boolean;
|
|
51
|
+
runGh?: GhRunner;
|
|
51
52
|
}
|
|
52
53
|
export declare function githubAuthModesMain(args: GitHubAuthModesCliArgs): number;
|
|
53
54
|
export { probeRuntimeCapabilities };
|
|
@@ -257,6 +257,7 @@ export function resultToDict(result) {
|
|
|
257
257
|
export function githubAuthModesMain(args) {
|
|
258
258
|
const result = validateGithubAuthForWorker(args.githubAuthMode ?? null, {
|
|
259
259
|
repo: args.repo ?? DEFAULT_VALIDATION_REPO,
|
|
260
|
+
runGh: args.runGh,
|
|
260
261
|
});
|
|
261
262
|
if (args.json) {
|
|
262
263
|
process.stdout.write(`${JSON.stringify(resultToDict(result), null, 2)}\n`);
|