karajan-code 3.7.2 → 3.8.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/README.md +14 -0
- package/package.json +1 -1
- package/src/cli/advanced-commands.js +61 -0
- package/src/cli/register-meta.js +49 -0
- package/src/cli.js +17 -4
- package/src/commands/advanced.js +31 -0
- package/src/commands/harden.js +2 -1
- package/src/commands/mutate.js +94 -0
- package/src/commands/start.js +117 -0
- package/src/config/defaults.js +2 -1
- package/src/config/schema.js +1 -0
- package/src/guards/secret-redactor.js +71 -0
- package/src/harden/workflow-engine.js +4 -2
- package/src/harden/workflow-templates.js +52 -0
- package/src/mutate/diff-scope.js +101 -0
- package/src/mutate/reviewer-signal.js +76 -0
- package/src/mutate/runner.js +72 -0
- package/src/mutate/tool-registry.js +90 -0
- package/src/orchestrator/stages/reviewer-stage.js +9 -3
- package/src/prompts/start-decision.js +52 -0
- package/src/roles/reviewer-role.js +7 -0
- package/src/start/assessment.js +74 -0
- package/src/start/maturity.js +73 -0
- package/src/start/start-decider-role.js +79 -0
- package/src/start/sweep.js +112 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// KJC-TSK-0568 (Onboard A) — read-only sweep orchestrator for the Brain.
|
|
2
|
+
//
|
|
3
|
+
// Runs the READ-ONLY signals fitting the project's maturity and aggregates them
|
|
4
|
+
// into one structured bundle for synthesis (Onboard B, 0569). Writes nothing.
|
|
5
|
+
// No LLM: maturity is deterministic and the costly audit is deferred — legacy
|
|
6
|
+
// only flags `deepHealthRecommended` (0570 decides). Subset by maturity: new =
|
|
7
|
+
// brief+tests; existing/legacy = + drift, harden advisory, rag + qmd status.
|
|
8
|
+
// Collaborators are injected (opts.deps) so it stays trivially testable.
|
|
9
|
+
import { existsSync } from "node:fs";
|
|
10
|
+
import { execFileSync } from "node:child_process";
|
|
11
|
+
|
|
12
|
+
import { collectAll } from "../onboarder/collectors/index.js";
|
|
13
|
+
import { checkHarden } from "../harden/check.js";
|
|
14
|
+
import { compareHarden } from "../harden/advisory.js";
|
|
15
|
+
import { detectTestFramework } from "../utils/project-detect.js";
|
|
16
|
+
import { detectQmd } from "../utils/qmd-detect.js";
|
|
17
|
+
import {
|
|
18
|
+
dbPath, openVecStore, projectSlug, countChunks, getLastIndexedCommit,
|
|
19
|
+
} from "../rag/vec-store.js";
|
|
20
|
+
import { classifyMaturity } from "./maturity.js";
|
|
21
|
+
|
|
22
|
+
const CODE_EXT = new Set([
|
|
23
|
+
".js", ".mjs", ".cjs", ".ts", ".tsx", ".jsx", ".py", ".go",
|
|
24
|
+
".rs", ".java", ".php", ".rb", ".c", ".cc", ".cpp", ".cs",
|
|
25
|
+
]);
|
|
26
|
+
const SCAFFOLD_MAX_FILES = 3;
|
|
27
|
+
const CI_MARKERS = [".github/workflows", ".gitlab-ci.yml"];
|
|
28
|
+
|
|
29
|
+
const safe = async (fn) => { try { return await fn(); } catch { return null; } };
|
|
30
|
+
|
|
31
|
+
/** Count real source files in a tree bundle, skipping tests. */
|
|
32
|
+
export function countCodeFiles(tree = []) {
|
|
33
|
+
let n = 0;
|
|
34
|
+
for (const node of tree) {
|
|
35
|
+
if (node.kind === "dir") n += countCodeFiles(node.children);
|
|
36
|
+
else if (node.kind === "file") {
|
|
37
|
+
const ext = node.path.slice(node.path.lastIndexOf("."));
|
|
38
|
+
const isTest = /\.(test|spec)\./.test(node.path) || /(^|\/)(test|tests|__tests__)\//.test(node.path);
|
|
39
|
+
if (CODE_EXT.has(ext) && !isTest) n += 1;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return n;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Age of the last commit in days, or null on non-git / no commits. */
|
|
46
|
+
function lastCommitAgeDays(projectDir) {
|
|
47
|
+
try {
|
|
48
|
+
const out = execFileSync("git", ["log", "-1", "--format=%ct"], {
|
|
49
|
+
cwd: projectDir, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"],
|
|
50
|
+
}).trim();
|
|
51
|
+
if (!out) return null;
|
|
52
|
+
return Math.floor((Date.now() - Number(out) * 1000) / 86_400_000);
|
|
53
|
+
} catch { return null; }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Read-only RAG index status; null when no index file exists yet. */
|
|
57
|
+
function readRagStatus(projectDir) {
|
|
58
|
+
try {
|
|
59
|
+
if (!existsSync(dbPath())) return { indexed: false, chunks: 0, lastIndexedCommit: null };
|
|
60
|
+
const db = openVecStore();
|
|
61
|
+
return {
|
|
62
|
+
indexed: true,
|
|
63
|
+
chunks: countChunks(db, { kind: "code" }),
|
|
64
|
+
lastIndexedCommit: getLastIndexedCommit(db, projectSlug(projectDir)),
|
|
65
|
+
};
|
|
66
|
+
} catch { return null; }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @param {string} projectDir
|
|
71
|
+
* @param {{declared?: ("new"|"existing"|"legacy"|null), profile?: string, deps?: object}} [opts]
|
|
72
|
+
* declared = user-declared maturity; deps = injected collaborators (defaults = real collectors).
|
|
73
|
+
*/
|
|
74
|
+
export async function runReadOnlySweep(projectDir, { declared = null, profile = "standard", deps = {} } = {}) {
|
|
75
|
+
const d = {
|
|
76
|
+
collectAll, detectTestFramework, checkHarden, compareHarden,
|
|
77
|
+
detectQmd, ragStatus: readRagStatus, gitAgeDays: lastCommitAgeDays, ...deps,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const brief = await safe(() => d.collectAll(projectDir));
|
|
81
|
+
const tests = await safe(() => d.detectTestFramework(projectDir));
|
|
82
|
+
|
|
83
|
+
const tree = brief?.tree ?? [];
|
|
84
|
+
const present = brief?.configs?.present ?? [];
|
|
85
|
+
const codeFiles = countCodeFiles(tree);
|
|
86
|
+
const commitCount = brief?.git?.commitCount ?? 0;
|
|
87
|
+
|
|
88
|
+
const signals = {
|
|
89
|
+
hasSourceCode: codeFiles > 0,
|
|
90
|
+
scaffoldingOnly: codeFiles > 0 && codeFiles <= SCAFFOLD_MAX_FILES && commitCount <= 1,
|
|
91
|
+
hasTests: Boolean(tests?.hasTests),
|
|
92
|
+
hasCI: CI_MARKERS.some((m) => present.includes(m)),
|
|
93
|
+
commitCount,
|
|
94
|
+
staleDays: d.gitAgeDays(projectDir),
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const maturity = classifyMaturity({ declared, ...signals });
|
|
98
|
+
const deep = maturity.maturity !== "new";
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
projectDir,
|
|
102
|
+
maturity,
|
|
103
|
+
signals,
|
|
104
|
+
brief,
|
|
105
|
+
tests,
|
|
106
|
+
drift: deep ? await safe(() => d.checkHarden({ projectDir, profile })) : null,
|
|
107
|
+
improvements: deep ? await safe(() => d.compareHarden({ projectDir, profile })) : null,
|
|
108
|
+
rag: deep ? await safe(() => d.ragStatus(projectDir)) : null,
|
|
109
|
+
qmd: deep ? await safe(() => d.detectQmd()) : null,
|
|
110
|
+
collectedAt: new Date().toISOString(),
|
|
111
|
+
};
|
|
112
|
+
}
|