karajan-code 4.33.0 → 4.34.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/package.json +1 -1
- package/src/brain/one-shot-policy.js +26 -0
- package/src/brain/with-brain-recovery.js +4 -1
- package/src/checks/release-check.js +51 -5
- package/src/cli/register-meta.js +42 -2
- package/src/commands/code.js +18 -16
- package/src/commands/harden.js +9 -2
- package/src/commands/policy.js +21 -14
- package/src/commands/report.js +28 -0
- package/src/commands/review-gate.js +13 -11
- package/src/environment/panel.js +33 -0
- package/src/harden/guidelines-engine.js +7 -1
- package/src/harden/guidelines-templates.js +10 -1
- package/src/harden/harness-hooks.js +6 -0
- package/src/harden/sentinel-hooks.js +227 -24
- package/src/harden/supervisor-commit.js +41 -1
- package/src/harden/workflow-templates.js +9 -3
- package/src/policy/supervisor-verify.js +45 -11
- package/src/review/board-pending.js +68 -0
- package/src/review/loc-budget.js +97 -0
- package/src/roles/audit-role.js +28 -2
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What counts toward the PR size budget (KJC-BUG-0205).
|
|
3
|
+
*
|
|
4
|
+
* The budget exists to cap CODE growth. Two implementations of that rule had
|
|
5
|
+
* drifted apart: CI excluded lockfiles, build output and the docs tree, while
|
|
6
|
+
* the local gate summed every line of the diff. A doc-only PR was warned at 234
|
|
7
|
+
* lines locally and counted 104 in CI, and a gate that cries wolf in one place
|
|
8
|
+
* and not the other teaches people to believe neither.
|
|
9
|
+
*
|
|
10
|
+
* Two rules decide what is exempt, and they are not the same rule:
|
|
11
|
+
* - GENERATED output never counts: nobody wrote it, and the reviewer reads the
|
|
12
|
+
* source that produced it.
|
|
13
|
+
* - HUMAN documentation never counts either. Rationing docs is how a project
|
|
14
|
+
* ends up shipping features nobody can discover (which is exactly what
|
|
15
|
+
* happened in 4.33.0, see KJC-TSK-0871).
|
|
16
|
+
*
|
|
17
|
+
* And one thing that DOES count, on purpose: AI-rule files (CLAUDE.md,
|
|
18
|
+
* AGENTS.md, templates/**). They enter the agent's context on every run, so
|
|
19
|
+
* unbounded growth there dilutes the signal the agent receives.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const EXEMPT = [
|
|
23
|
+
// Generated or vendored: nobody typed it.
|
|
24
|
+
/(^|\/)dist\//,
|
|
25
|
+
/(^|\/)build\//,
|
|
26
|
+
/(^|\/)coverage\//,
|
|
27
|
+
/(^|\/)node_modules\//,
|
|
28
|
+
/(^|\/)\.astro\//,
|
|
29
|
+
/(^|\/)public\/docs\//,
|
|
30
|
+
/\.min\.(js|css)$/,
|
|
31
|
+
/\.map$/,
|
|
32
|
+
/\.snap(shot)?$/,
|
|
33
|
+
/(^|\/)(package-lock\.json|pnpm-lock\.yaml|yarn\.lock|npm-shrinkwrap\.json)$/,
|
|
34
|
+
/\.lock$/,
|
|
35
|
+
/(^|\/)tests\/_diet\//,
|
|
36
|
+
// Human documentation, wherever the project keeps it. The landing's docs
|
|
37
|
+
// moved into the monorepo (MONO-3) and the old root-anchored `docs/` rule
|
|
38
|
+
// stopped matching them, which is how documenting started costing budget.
|
|
39
|
+
/(^|\/)docs\/.*\.(md|mdx|txt|rst)$/,
|
|
40
|
+
/^(CHANGELOG|README|CODE_OF_CONDUCT|CONTRIBUTING|SECURITY)(\.[a-z-]+)?\.md$/,
|
|
41
|
+
/^(MIGRATION|TODO).*\.md$/,
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
// The exceptions to the exception: these ARE the agent's context.
|
|
45
|
+
const AI_RULES = [/^CLAUDE\.md$/, /^AGENTS\.md$/, /^GEMINI\.md$/, /(^|\/)templates\//];
|
|
46
|
+
|
|
47
|
+
/** @param {string} file @returns {boolean} */
|
|
48
|
+
export function countsTowardBudget(file) {
|
|
49
|
+
if (!file) return false;
|
|
50
|
+
if (AI_RULES.some((re) => re.test(file))) return true;
|
|
51
|
+
return !EXEMPT.some((re) => re.test(file));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* KJC-BUG-0208 — the policy invariant `net_lines_added` summed the whole
|
|
56
|
+
* numstat, so the same diff printed 174 in the size gate and 202 in the policy,
|
|
57
|
+
* two lines apart. One rule, one number: the metric is netted over the files
|
|
58
|
+
* that count, and what does not count is still said out loud.
|
|
59
|
+
*
|
|
60
|
+
* @param {string} numstat output of `git diff --numstat`
|
|
61
|
+
* @returns {{net: number, exempt: number}}
|
|
62
|
+
*/
|
|
63
|
+
export function budgetedNet(numstat) {
|
|
64
|
+
let net = 0;
|
|
65
|
+
let exempt = 0;
|
|
66
|
+
for (const line of String(numstat || "").split("\n")) {
|
|
67
|
+
if (!line.trim()) continue;
|
|
68
|
+
const [a, r, ...rest] = line.split("\t");
|
|
69
|
+
const added = Number(a);
|
|
70
|
+
const removed = Number(r);
|
|
71
|
+
if (!Number.isFinite(added) || !Number.isFinite(removed)) continue; // binary
|
|
72
|
+
if (!countsTowardBudget(rest.join("\t"))) { exempt += added; continue; }
|
|
73
|
+
net += added - removed;
|
|
74
|
+
}
|
|
75
|
+
return { net, exempt };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @param {string} numstat output of `git diff --numstat`
|
|
80
|
+
* @returns {{added: number, exempt: number, testAdded: number}}
|
|
81
|
+
*/
|
|
82
|
+
export function budgetedAdded(numstat) {
|
|
83
|
+
let added = 0;
|
|
84
|
+
let exempt = 0;
|
|
85
|
+
let testAdded = 0;
|
|
86
|
+
for (const line of String(numstat || "").split("\n")) {
|
|
87
|
+
if (!line.trim()) continue;
|
|
88
|
+
const [a, , ...rest] = line.split("\t");
|
|
89
|
+
const n = Number(a);
|
|
90
|
+
if (!Number.isFinite(n)) continue; // binary files emit '-'
|
|
91
|
+
const file = rest.join("\t");
|
|
92
|
+
if (!countsTowardBudget(file)) { exempt += n; continue; }
|
|
93
|
+
added += n;
|
|
94
|
+
if (/\/tests?\/|__tests__\/|\.test\.|\.spec\./.test(`/${file}`)) testAdded += n;
|
|
95
|
+
}
|
|
96
|
+
return { added, exempt, testAdded };
|
|
97
|
+
}
|
package/src/roles/audit-role.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
3
|
import { AgentRole } from "./agent-role.js";
|
|
4
|
+
import { withBrainRecovery } from "../brain/with-brain-recovery.js";
|
|
5
|
+
import { buildRoleFallbackChain } from "../brain/role-fallback-chain.js";
|
|
6
|
+
import { ONE_SHOT_POLICY } from "../brain/one-shot-policy.js";
|
|
4
7
|
import { securityAuditMarkerPath } from "../steward/invariants.js";
|
|
5
8
|
import { buildAuditPrompt, parseAuditOutput, AUDIT_DIMENSIONS } from "../prompts/audit.js";
|
|
6
9
|
import { measureBasalCost, loadPreviousAudit, saveAuditSnapshot, computeGrowthDelta } from "../audit/basal-cost.js";
|
|
@@ -193,13 +196,36 @@ export class AuditRole extends AgentRole {
|
|
|
193
196
|
const runArgs = { prompt, role: "audit" };
|
|
194
197
|
if (onOutput) runArgs.onOutput = onOutput;
|
|
195
198
|
const startedAt = Date.now();
|
|
196
|
-
|
|
199
|
+
// KJC-BUG-0194: this used to call agent.runTask() directly, which made
|
|
200
|
+
// audit the ONE role that bypassed the brain. No classification (so an MCP
|
|
201
|
+
// caller saw a quota wall as category "unknown") and no declared chain
|
|
202
|
+
// (so roles.audit.fallback was ignored even after KJC-TSK-0859 wired it,
|
|
203
|
+
// because the wiring lives in AgentRole.execute and audit overrides it).
|
|
204
|
+
// The one-shot policy matters here: `kj audit` must take the next
|
|
205
|
+
// candidate at once rather than hibernate for hours inside a command.
|
|
206
|
+
const result = await withBrainRecovery({
|
|
207
|
+
agent: { runTask: (args) => agent.runTask(args), provider, model: this.config?.roles?.audit?.model ?? null },
|
|
208
|
+
taskArgs: runArgs,
|
|
209
|
+
role: "audit",
|
|
210
|
+
provider,
|
|
211
|
+
logger: this.logger,
|
|
212
|
+
emitter: this.emitter,
|
|
213
|
+
policy: ONE_SHOT_POLICY,
|
|
214
|
+
fallback: buildRoleFallbackChain({ config: this.config, role: "audit", createAgentFn: this._createAgent, logger: this.logger }),
|
|
215
|
+
});
|
|
197
216
|
const durationMs = Date.now() - startedAt;
|
|
198
217
|
|
|
199
218
|
const usage = extractUsage(result, { provider, durationMs });
|
|
200
219
|
|
|
201
220
|
if (!result.ok) {
|
|
202
|
-
|
|
221
|
+
// The classification and the itinerary travel with the failure, so a
|
|
222
|
+
// caller (CLI or MCP) can say WHY it stopped and what was tried.
|
|
223
|
+
return {
|
|
224
|
+
ok: false,
|
|
225
|
+
result: { error: result.error || result.output || "Audit failed", provider, recovery: result.recovery || null, tried: result.tried || null },
|
|
226
|
+
summary: `Audit failed: ${result.recovery?.class ? `${result.recovery.class} — ` : ""}${result.error || "unknown error"}`,
|
|
227
|
+
usage,
|
|
228
|
+
};
|
|
203
229
|
}
|
|
204
230
|
|
|
205
231
|
try {
|