@kiwidata/grimoire 0.2.0 → 0.2.1
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/AGENTS.md +2 -2
- package/dist/core/check-complexity.d.ts +4 -0
- package/dist/core/check-complexity.d.ts.map +1 -0
- package/dist/core/check-complexity.js +79 -0
- package/dist/core/check-complexity.js.map +1 -0
- package/dist/core/check-llm.d.ts +5 -0
- package/dist/core/check-llm.d.ts.map +1 -0
- package/dist/core/check-llm.js +108 -0
- package/dist/core/check-llm.js.map +1 -0
- package/dist/core/check.d.ts +1 -2
- package/dist/core/check.d.ts.map +1 -1
- package/dist/core/check.js +2 -179
- package/dist/core/check.js.map +1 -1
- package/dist/core/init-config.d.ts +22 -0
- package/dist/core/init-config.d.ts.map +1 -0
- package/dist/core/init-config.js +118 -0
- package/dist/core/init-config.js.map +1 -0
- package/dist/core/init-prompts.d.ts +8 -0
- package/dist/core/init-prompts.d.ts.map +1 -0
- package/dist/core/init-prompts.js +166 -0
- package/dist/core/init-prompts.js.map +1 -0
- package/dist/core/init.d.ts +0 -1
- package/dist/core/init.d.ts.map +1 -1
- package/dist/core/init.js +3 -279
- package/dist/core/init.js.map +1 -1
- package/package.json +1 -1
- package/skills/grimoire-apply/SKILL.md +3 -2
- package/skills/grimoire-draft/SKILL.md +5 -3
- package/skills/grimoire-plan/SKILL.md +4 -1
- package/skills/grimoire-pr/SKILL.md +14 -15
- package/skills/grimoire-refactor/SKILL.md +3 -1
- package/skills/references/code-quality.md +1 -1
- package/skills/references/principles.md +4 -3
- package/skills/references/refactor-scan-categories.md +15 -0
- package/skills/references/review-personas.md +30 -13
- package/skills/references/testing-contracts.md +19 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
export const PROMPT_SURFACES = [
|
|
2
|
+
"tui",
|
|
3
|
+
"web",
|
|
4
|
+
"mobile",
|
|
5
|
+
"api",
|
|
6
|
+
"mixed",
|
|
7
|
+
];
|
|
8
|
+
export function buildMinimalConfig() {
|
|
9
|
+
return {
|
|
10
|
+
version: 1,
|
|
11
|
+
project: {
|
|
12
|
+
commit_style: "conventional",
|
|
13
|
+
comment_lint: "block",
|
|
14
|
+
},
|
|
15
|
+
features_dir: "features",
|
|
16
|
+
decisions_dir: ".grimoire/decisions",
|
|
17
|
+
tools: {},
|
|
18
|
+
checks: [
|
|
19
|
+
"lint",
|
|
20
|
+
"format",
|
|
21
|
+
"duplicates",
|
|
22
|
+
"complexity",
|
|
23
|
+
"dead_code",
|
|
24
|
+
"unit_test",
|
|
25
|
+
"bdd_test",
|
|
26
|
+
"security",
|
|
27
|
+
"dep_audit",
|
|
28
|
+
"secrets",
|
|
29
|
+
"best_practices",
|
|
30
|
+
],
|
|
31
|
+
llm: {
|
|
32
|
+
thinking: { command: "claude" },
|
|
33
|
+
coding: { command: "claude" },
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function confidenceRank(c) {
|
|
38
|
+
return c === "high" ? 3 : c === "medium" ? 2 : 1;
|
|
39
|
+
}
|
|
40
|
+
export function bestByCategory(detections) {
|
|
41
|
+
const byCategory = new Map();
|
|
42
|
+
for (const d of detections) {
|
|
43
|
+
const existing = byCategory.get(d.category);
|
|
44
|
+
if (!existing || confidenceRank(d.confidence) > confidenceRank(existing.confidence)) {
|
|
45
|
+
byCategory.set(d.category, d);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return byCategory;
|
|
49
|
+
}
|
|
50
|
+
export function applyProjectDetections(config, byCategory) {
|
|
51
|
+
const projectFields = [
|
|
52
|
+
["language", "language"],
|
|
53
|
+
["package_manager", "package_manager"],
|
|
54
|
+
["doc_tool", "doc_tool"],
|
|
55
|
+
["comment_style", "comment_style"],
|
|
56
|
+
];
|
|
57
|
+
for (const [cat, field] of projectFields) {
|
|
58
|
+
const d = byCategory.get(cat);
|
|
59
|
+
if (d)
|
|
60
|
+
config.project[field] = d.name;
|
|
61
|
+
}
|
|
62
|
+
const projectCategories = new Set(["language", "package_manager", "doc_tool", "comment_style"]);
|
|
63
|
+
for (const [category, detection] of byCategory) {
|
|
64
|
+
if (projectCategories.has(category))
|
|
65
|
+
continue;
|
|
66
|
+
const tool = { name: detection.name };
|
|
67
|
+
if (detection.command)
|
|
68
|
+
tool.command = detection.command;
|
|
69
|
+
if (detection.check_command)
|
|
70
|
+
tool.check_command = detection.check_command;
|
|
71
|
+
config.tools[category] = tool;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
export function applyLlmFallbacks(config, byCategory) {
|
|
75
|
+
if (!byCategory.has("security")) {
|
|
76
|
+
config.tools.security = { name: "llm", prompt: "Review these changed files for security vulnerabilities. Tag each finding with OWASP Top 10 category and CWE ID. Check for: SQL injection (CWE-89), XSS (CWE-79), broken auth (CWE-287), insecure crypto (CWE-327), SSRF (CWE-918), path traversal (CWE-22), insecure deserialization (CWE-502), missing access control (CWE-862), CSRF (CWE-352), hardcoded secrets (CWE-798)." };
|
|
77
|
+
}
|
|
78
|
+
if (!byCategory.has("dep_audit")) {
|
|
79
|
+
config.tools.dep_audit = { name: "llm", prompt: "Review these changed files for newly added dependencies or imports. Flag potential typosquatting (CWE-1357), packages you cannot verify as real, and packages with known security advisories. Check for misspellings (e.g., 'reqeusts' instead of 'requests')." };
|
|
80
|
+
}
|
|
81
|
+
if (!byCategory.has("secrets")) {
|
|
82
|
+
config.tools.secrets = { name: "llm", prompt: "Review these changed files for hardcoded secrets (CWE-798), API keys, passwords, tokens, private keys, or credentials (CWE-312). Flag any string that looks like a secret value rather than a placeholder or environment variable reference." };
|
|
83
|
+
}
|
|
84
|
+
if (!byCategory.has("dead_code")) {
|
|
85
|
+
config.tools.dead_code = { name: "llm", prompt: "Review these changed files for dead code: unused functions, unreachable branches, unused imports, unused variables, and exports that are never imported elsewhere. Only flag code that is clearly dead, not code that might be used dynamically." };
|
|
86
|
+
}
|
|
87
|
+
config.tools.best_practices = { name: "llm", prompt: "Review these changed files for best practices violations" };
|
|
88
|
+
if (!config.tools.duplicates) {
|
|
89
|
+
config.tools.duplicates = { name: "jscpd", command: "npx jscpd --reporters console" };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
export function surfaceFromDetection(d) {
|
|
93
|
+
if (!d)
|
|
94
|
+
return undefined;
|
|
95
|
+
return PROMPT_SURFACES.includes(d.name)
|
|
96
|
+
? d.name
|
|
97
|
+
: undefined;
|
|
98
|
+
}
|
|
99
|
+
export function buildIntegrationFlags(initialFlags, config) {
|
|
100
|
+
return {
|
|
101
|
+
codebaseMemoryMcp: initialFlags.codebaseMemoryMcp ?? config.project.integrations?.codebase_memory_mcp,
|
|
102
|
+
cavemanPlugin: initialFlags.cavemanPlugin ?? config.project.integrations?.caveman_plugin,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
const SECRET_PATTERN = /(.*_TOKEN|.*_KEY|.*_SECRET|.*_PASSWORD)\s*[:=]\s*[^$\s].*/i;
|
|
106
|
+
export function scanForSecrets(serialized) {
|
|
107
|
+
for (const line of serialized.split("\n")) {
|
|
108
|
+
const m = line.match(SECRET_PATTERN);
|
|
109
|
+
if (!m)
|
|
110
|
+
continue;
|
|
111
|
+
const value = line.slice(line.search(/[:=]/) + 1).trim();
|
|
112
|
+
if (value.startsWith("${"))
|
|
113
|
+
continue;
|
|
114
|
+
throw new Error(`Refusing to write a secret to .grimoire/config.yaml: ${line.trim()}\n` +
|
|
115
|
+
`Use \${ENV_VAR} references instead, then export the value in your shell.`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=init-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init-config.js","sourceRoot":"","sources":["../../src/core/init-config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type Detection } from "./detect.js";
|
|
2
|
+
import type { GrimoireConfig } from "../utils/config.js";
|
|
3
|
+
import { type EssentialPrefill } from "./init-config.js";
|
|
4
|
+
export declare function buildDetectedConfig(root: string, prefill?: EssentialPrefill): Promise<{
|
|
5
|
+
config: GrimoireConfig;
|
|
6
|
+
detection: Detection | null;
|
|
7
|
+
}>;
|
|
8
|
+
//# sourceMappingURL=init-prompts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init-prompts.d.ts","sourceRoot":"","sources":["../../src/core/init-prompts.ts"],"names":[],"mappings":"AACA,OAAO,EAAe,KAAK,SAAS,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAgC,MAAM,oBAAoB,CAAC;AAGvF,OAAO,EAOL,KAAK,gBAAgB,EACtB,MAAM,kBAAkB,CAAC;AAkC1B,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC;IAAE,MAAM,EAAE,cAAc,CAAC;IAAC,SAAS,EAAE,SAAS,GAAG,IAAI,CAAA;CAAE,CAAC,CAyClE"}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { detectTools } from "./detect.js";
|
|
3
|
+
import { detectAgentFiles } from "./shared-setup.js";
|
|
4
|
+
import { runSections } from "./configure.js";
|
|
5
|
+
import { buildMinimalConfig, bestByCategory, applyProjectDetections, applyLlmFallbacks, surfaceFromDetection, PROMPT_SURFACES, } from "./init-config.js";
|
|
6
|
+
const CATEGORY_LABELS = {
|
|
7
|
+
language: "Language",
|
|
8
|
+
package_manager: "Pkg manager",
|
|
9
|
+
lint: "Linter",
|
|
10
|
+
format: "Formatter",
|
|
11
|
+
unit_test: "Unit tests",
|
|
12
|
+
bdd_test: "BDD tests",
|
|
13
|
+
complexity: "Complexity",
|
|
14
|
+
security: "Security",
|
|
15
|
+
dep_audit: "Dep audit",
|
|
16
|
+
secrets: "Secrets",
|
|
17
|
+
dead_code: "Dead code",
|
|
18
|
+
doc_tool: "Doc tool",
|
|
19
|
+
comment_style: "Comment style",
|
|
20
|
+
};
|
|
21
|
+
const CATEGORY_ORDER = [
|
|
22
|
+
"language",
|
|
23
|
+
"package_manager",
|
|
24
|
+
"lint",
|
|
25
|
+
"format",
|
|
26
|
+
"unit_test",
|
|
27
|
+
"bdd_test",
|
|
28
|
+
"complexity",
|
|
29
|
+
"security",
|
|
30
|
+
"dep_audit",
|
|
31
|
+
"secrets",
|
|
32
|
+
"dead_code",
|
|
33
|
+
"doc_tool",
|
|
34
|
+
"comment_style",
|
|
35
|
+
];
|
|
36
|
+
export async function buildDetectedConfig(root, prefill = {}) {
|
|
37
|
+
console.log(chalk.bold("\nDetecting project tools...\n"));
|
|
38
|
+
const detections = await detectTools(root);
|
|
39
|
+
const config = buildMinimalConfig();
|
|
40
|
+
if (detections.length === 0) {
|
|
41
|
+
console.log(chalk.dim(" No tools detected. Using minimal config.\n"));
|
|
42
|
+
return { config: await askEssentialPreferences(config, root, prefill), detection: null };
|
|
43
|
+
}
|
|
44
|
+
const byCategory = bestByCategory(detections);
|
|
45
|
+
console.log(chalk.bold(" Detected tools:\n"));
|
|
46
|
+
for (const cat of CATEGORY_ORDER) {
|
|
47
|
+
const label = (CATEGORY_LABELS[cat] ?? cat).padEnd(14);
|
|
48
|
+
const d = byCategory.get(cat);
|
|
49
|
+
if (d)
|
|
50
|
+
console.log(` ${label} ${chalk.cyan(d.name.padEnd(16))} ${chalk.dim(`(${d.signal})`)}`);
|
|
51
|
+
else
|
|
52
|
+
console.log(` ${label} ${chalk.dim("(none detected)")}`);
|
|
53
|
+
}
|
|
54
|
+
const readline = await import("node:readline/promises");
|
|
55
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
56
|
+
console.log();
|
|
57
|
+
const answer = await rl.question(" Accept detected tools? (Y/n/edit) ");
|
|
58
|
+
const prefillWithSurface = { ...prefill, detectedSurface: surfaceFromDetection(byCategory.get("surface")) };
|
|
59
|
+
if (answer.toLowerCase() === "n") {
|
|
60
|
+
rl.close();
|
|
61
|
+
console.log(chalk.dim(" Skipping tool detection.\n"));
|
|
62
|
+
return { config: await askEssentialPreferences(config, root, prefillWithSurface), detection: null };
|
|
63
|
+
}
|
|
64
|
+
if (answer.toLowerCase() === "edit")
|
|
65
|
+
await editDetections(rl, byCategory);
|
|
66
|
+
rl.close();
|
|
67
|
+
applyProjectDetections(config, byCategory);
|
|
68
|
+
applyLlmFallbacks(config, byCategory);
|
|
69
|
+
return { config: await askEssentialPreferences(config, root, prefillWithSurface), detection: byCategory.get("language") ?? null };
|
|
70
|
+
}
|
|
71
|
+
async function editDetections(rl, byCategory) {
|
|
72
|
+
console.log(chalk.dim("\n For each tool, press Enter to accept, 'n' to skip, or type a custom name.\n"));
|
|
73
|
+
for (const cat of CATEGORY_ORDER) {
|
|
74
|
+
const label = CATEGORY_LABELS[cat] ?? cat;
|
|
75
|
+
const d = byCategory.get(cat);
|
|
76
|
+
const current = d ? d.name : "none";
|
|
77
|
+
const answer = await rl.question(` ${label} [${current}]: `);
|
|
78
|
+
const trimmed = answer.trim();
|
|
79
|
+
if (trimmed.toLowerCase() === "n") {
|
|
80
|
+
byCategory.delete(cat);
|
|
81
|
+
}
|
|
82
|
+
else if (trimmed && trimmed !== current) {
|
|
83
|
+
byCategory.set(cat, {
|
|
84
|
+
category: cat,
|
|
85
|
+
name: trimmed,
|
|
86
|
+
confidence: "low",
|
|
87
|
+
signal: "user input",
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async function askEssentialPreferences(config, root, prefill = {}) {
|
|
93
|
+
const readline = await import("node:readline/promises");
|
|
94
|
+
const rl = readline.createInterface({
|
|
95
|
+
input: process.stdin,
|
|
96
|
+
output: process.stdout,
|
|
97
|
+
});
|
|
98
|
+
// 1. AI agents
|
|
99
|
+
console.log(chalk.bold("\n AI agents:\n"));
|
|
100
|
+
console.log(chalk.dim(" Which AI coding tools will use these skills?"));
|
|
101
|
+
console.log(chalk.dim(" Skills install per tool: claude→.claude/skills, opencode→.opencode/skills, codex→.agents/skills."));
|
|
102
|
+
console.log(chalk.dim(" cursor/copilot use AGENTS.md-derived files (no skills).\n"));
|
|
103
|
+
const detectedAgents = await detectAgentFiles(root).catch(() => []);
|
|
104
|
+
const defaultAgents = detectedAgents.length > 0 ? detectedAgents.join(",") : "claude";
|
|
105
|
+
const agentsAnswer = await rl.question(` AI agents (comma-separated: claude/opencode/codex/cursor/copilot) [${defaultAgents}]: `);
|
|
106
|
+
const rawAgents = agentsAnswer.trim() || defaultAgents;
|
|
107
|
+
config.project.agents = rawAgents
|
|
108
|
+
.split(",")
|
|
109
|
+
.map((s) => s.trim().toLowerCase())
|
|
110
|
+
.filter(Boolean);
|
|
111
|
+
// 2. Recommended integrations
|
|
112
|
+
console.log(chalk.bold("\n Recommended integrations:\n"));
|
|
113
|
+
console.log(chalk.dim(" These are optional but recommended. Saying yes records the intent"));
|
|
114
|
+
console.log(chalk.dim(" in config and prints install commands at the end.\n"));
|
|
115
|
+
const integrations = {};
|
|
116
|
+
if (prefill.codebaseMemoryMcp === undefined) {
|
|
117
|
+
const cbmAnswer = await rl.question(" Install codebase-memory-mcp (call graphs, code intelligence)? (Y/n) ");
|
|
118
|
+
integrations.codebase_memory_mcp =
|
|
119
|
+
cbmAnswer.trim().toLowerCase() !== "n";
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
integrations.codebase_memory_mcp = prefill.codebaseMemoryMcp;
|
|
123
|
+
}
|
|
124
|
+
if (prefill.cavemanPlugin === undefined) {
|
|
125
|
+
const cavemanPluginAnswer = await rl.question(" Install caveman skill plugin (Claude Code marketplace)? (y/N) ");
|
|
126
|
+
integrations.caveman_plugin =
|
|
127
|
+
cavemanPluginAnswer.trim().toLowerCase() === "y";
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
integrations.caveman_plugin = prefill.cavemanPlugin;
|
|
131
|
+
}
|
|
132
|
+
config.project.integrations = integrations;
|
|
133
|
+
// 3. Surface
|
|
134
|
+
await askSurface(rl, config, prefill.detectedSurface);
|
|
135
|
+
// 4. Caveman level
|
|
136
|
+
const currentCaveman = config.project.caveman ?? "lite";
|
|
137
|
+
const cavemanAnswer = await rl.question(` Token optimization (caveman)? (none/lite/full/ultra) [${currentCaveman}]: `);
|
|
138
|
+
config.project.caveman = (cavemanAnswer.trim() ? cavemanAnswer.trim().toLowerCase() : currentCaveman);
|
|
139
|
+
// 5. Commit style
|
|
140
|
+
const commitAnswer = await rl.question(` Commit style? (conventional/angular/custom) [${config.project.commit_style}]: `);
|
|
141
|
+
if (commitAnswer.trim()) {
|
|
142
|
+
config.project.commit_style = commitAnswer.trim();
|
|
143
|
+
}
|
|
144
|
+
// 6. Design tool + brand capture
|
|
145
|
+
await runSections(rl, config, root, ["design"]);
|
|
146
|
+
rl.close();
|
|
147
|
+
console.log();
|
|
148
|
+
return config;
|
|
149
|
+
}
|
|
150
|
+
async function askSurface(rl, config, detected) {
|
|
151
|
+
const prompt = detected
|
|
152
|
+
? ` Project surface: ${detected} — confirm or override (tui/web/mobile/api/mixed/Enter to accept): `
|
|
153
|
+
: ` Project surface? (tui/web/mobile/api/mixed/skip) `;
|
|
154
|
+
const answer = (await rl.question(prompt)).trim().toLowerCase();
|
|
155
|
+
if (!answer) {
|
|
156
|
+
if (detected)
|
|
157
|
+
config.project.surface = detected;
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (answer === "skip")
|
|
161
|
+
return;
|
|
162
|
+
if (PROMPT_SURFACES.includes(answer)) {
|
|
163
|
+
config.project.surface = answer;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
//# sourceMappingURL=init-prompts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init-prompts.js","sourceRoot":"","sources":["../../src/core/init-prompts.ts"],"names":[],"mappings":"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"}
|
package/dist/core/init.d.ts
CHANGED
|
@@ -8,6 +8,5 @@ interface InitOptions {
|
|
|
8
8
|
installCavemanPlugin?: boolean;
|
|
9
9
|
}
|
|
10
10
|
export declare function initProject(projectPath: string, options: InitOptions): Promise<void>;
|
|
11
|
-
export declare function scanForSecrets(serialized: string): void;
|
|
12
11
|
export {};
|
|
13
12
|
//# sourceMappingURL=init.d.ts.map
|
package/dist/core/init.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/core/init.ts"],"names":[],"mappings":"AA4BA,UAAU,WAAW;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,IAAI,EAAE,OAAO,CAAC;IACd,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/core/init.ts"],"names":[],"mappings":"AA4BA,UAAU,WAAW;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,IAAI,EAAE,OAAO,CAAC;IACd,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAoID,wBAAsB,WAAW,CAC/B,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,IAAI,CAAC,CAgBf"}
|
package/dist/core/init.js
CHANGED
|
@@ -3,43 +3,14 @@ import { join, dirname } from "node:path";
|
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { stringify as yamlStringify } from "yaml";
|
|
5
5
|
import chalk from "chalk";
|
|
6
|
-
import { detectTools } from "./detect.js";
|
|
7
6
|
import { setupHooks } from "./hooks.js";
|
|
8
7
|
import { fileExists } from "../utils/fs.js";
|
|
9
|
-
import { upsertAgentsFile, installSkillFiles, SKILL_NAMES, GRIMOIRE_DIRS, TEMPLATE_FILES, generateAgentFiles,
|
|
8
|
+
import { upsertAgentsFile, installSkillFiles, SKILL_NAMES, GRIMOIRE_DIRS, TEMPLATE_FILES, generateAgentFiles, } from "./shared-setup.js";
|
|
10
9
|
import { runSections } from "./configure.js";
|
|
10
|
+
import { buildMinimalConfig, buildIntegrationFlags, scanForSecrets, } from "./init-config.js";
|
|
11
|
+
import { buildDetectedConfig } from "./init-prompts.js";
|
|
11
12
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
13
|
const PACKAGE_ROOT = join(__dirname, "..", "..");
|
|
13
|
-
const CATEGORY_LABELS = {
|
|
14
|
-
language: "Language",
|
|
15
|
-
package_manager: "Pkg manager",
|
|
16
|
-
lint: "Linter",
|
|
17
|
-
format: "Formatter",
|
|
18
|
-
unit_test: "Unit tests",
|
|
19
|
-
bdd_test: "BDD tests",
|
|
20
|
-
complexity: "Complexity",
|
|
21
|
-
security: "Security",
|
|
22
|
-
dep_audit: "Dep audit",
|
|
23
|
-
secrets: "Secrets",
|
|
24
|
-
dead_code: "Dead code",
|
|
25
|
-
doc_tool: "Doc tool",
|
|
26
|
-
comment_style: "Comment style",
|
|
27
|
-
};
|
|
28
|
-
const CATEGORY_ORDER = [
|
|
29
|
-
"language",
|
|
30
|
-
"package_manager",
|
|
31
|
-
"lint",
|
|
32
|
-
"format",
|
|
33
|
-
"unit_test",
|
|
34
|
-
"bdd_test",
|
|
35
|
-
"complexity",
|
|
36
|
-
"security",
|
|
37
|
-
"dep_audit",
|
|
38
|
-
"secrets",
|
|
39
|
-
"dead_code",
|
|
40
|
-
"doc_tool",
|
|
41
|
-
"comment_style",
|
|
42
|
-
];
|
|
43
14
|
async function runFullConfigSections(root, config) {
|
|
44
15
|
const ALL_SECTIONS = ["tools", "compliance", "llm", "trackers", "testing"];
|
|
45
16
|
const readline = await import("node:readline/promises");
|
|
@@ -51,12 +22,6 @@ async function runFullConfigSections(root, config) {
|
|
|
51
22
|
scanForSecrets(fullSerialized);
|
|
52
23
|
await writeFile(join(root, ".grimoire", "config.yaml"), fullSerialized);
|
|
53
24
|
}
|
|
54
|
-
function buildIntegrationFlags(initialFlags, config) {
|
|
55
|
-
return {
|
|
56
|
-
codebaseMemoryMcp: initialFlags.codebaseMemoryMcp ?? config.project.integrations?.codebase_memory_mcp,
|
|
57
|
-
cavemanPlugin: initialFlags.cavemanPlugin ?? config.project.integrations?.caveman_plugin,
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
25
|
async function createGrimoireConfig(root, options, initialFlags) {
|
|
61
26
|
let projectDetection = null;
|
|
62
27
|
let config;
|
|
@@ -194,247 +159,6 @@ function printIntegrationInstructions(flags) {
|
|
|
194
159
|
console.log(` Restart your agent. The MCP server will spawn via the command in config.yaml.\n`);
|
|
195
160
|
}
|
|
196
161
|
}
|
|
197
|
-
const SECRET_PATTERN = /(.*_TOKEN|.*_KEY|.*_SECRET|.*_PASSWORD)\s*[:=]\s*[^$\s].*/i;
|
|
198
|
-
export function scanForSecrets(serialized) {
|
|
199
|
-
for (const line of serialized.split("\n")) {
|
|
200
|
-
const m = line.match(SECRET_PATTERN);
|
|
201
|
-
if (!m)
|
|
202
|
-
continue;
|
|
203
|
-
const value = line.slice(line.search(/[:=]/) + 1).trim();
|
|
204
|
-
if (value.startsWith("${"))
|
|
205
|
-
continue;
|
|
206
|
-
throw new Error(`Refusing to write a secret to .grimoire/config.yaml: ${line.trim()}\n` +
|
|
207
|
-
`Use \${ENV_VAR} references instead, then export the value in your shell.`);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
function buildMinimalConfig() {
|
|
211
|
-
return {
|
|
212
|
-
version: 1,
|
|
213
|
-
project: {
|
|
214
|
-
commit_style: "conventional",
|
|
215
|
-
comment_lint: "block",
|
|
216
|
-
},
|
|
217
|
-
features_dir: "features",
|
|
218
|
-
decisions_dir: ".grimoire/decisions",
|
|
219
|
-
tools: {},
|
|
220
|
-
checks: [
|
|
221
|
-
"lint",
|
|
222
|
-
"format",
|
|
223
|
-
"duplicates",
|
|
224
|
-
"complexity",
|
|
225
|
-
"dead_code",
|
|
226
|
-
"unit_test",
|
|
227
|
-
"bdd_test",
|
|
228
|
-
"security",
|
|
229
|
-
"dep_audit",
|
|
230
|
-
"secrets",
|
|
231
|
-
"best_practices",
|
|
232
|
-
],
|
|
233
|
-
llm: {
|
|
234
|
-
thinking: { command: "claude" },
|
|
235
|
-
coding: { command: "claude" },
|
|
236
|
-
},
|
|
237
|
-
};
|
|
238
|
-
}
|
|
239
|
-
function bestByCategory(detections) {
|
|
240
|
-
const byCategory = new Map();
|
|
241
|
-
for (const d of detections) {
|
|
242
|
-
const existing = byCategory.get(d.category);
|
|
243
|
-
if (!existing || confidenceRank(d.confidence) > confidenceRank(existing.confidence)) {
|
|
244
|
-
byCategory.set(d.category, d);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
return byCategory;
|
|
248
|
-
}
|
|
249
|
-
function applyProjectDetections(config, byCategory) {
|
|
250
|
-
const projectFields = [
|
|
251
|
-
["language", "language"],
|
|
252
|
-
["package_manager", "package_manager"],
|
|
253
|
-
["doc_tool", "doc_tool"],
|
|
254
|
-
["comment_style", "comment_style"],
|
|
255
|
-
];
|
|
256
|
-
for (const [cat, field] of projectFields) {
|
|
257
|
-
const d = byCategory.get(cat);
|
|
258
|
-
if (d)
|
|
259
|
-
config.project[field] = d.name;
|
|
260
|
-
}
|
|
261
|
-
const projectCategories = new Set(["language", "package_manager", "doc_tool", "comment_style"]);
|
|
262
|
-
for (const [category, detection] of byCategory) {
|
|
263
|
-
if (projectCategories.has(category))
|
|
264
|
-
continue;
|
|
265
|
-
const tool = { name: detection.name };
|
|
266
|
-
if (detection.command)
|
|
267
|
-
tool.command = detection.command;
|
|
268
|
-
if (detection.check_command)
|
|
269
|
-
tool.check_command = detection.check_command;
|
|
270
|
-
config.tools[category] = tool;
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
function applyLlmFallbacks(config, byCategory) {
|
|
274
|
-
if (!byCategory.has("security")) {
|
|
275
|
-
config.tools.security = { name: "llm", prompt: "Review these changed files for security vulnerabilities. Tag each finding with OWASP Top 10 category and CWE ID. Check for: SQL injection (CWE-89), XSS (CWE-79), broken auth (CWE-287), insecure crypto (CWE-327), SSRF (CWE-918), path traversal (CWE-22), insecure deserialization (CWE-502), missing access control (CWE-862), CSRF (CWE-352), hardcoded secrets (CWE-798)." };
|
|
276
|
-
}
|
|
277
|
-
if (!byCategory.has("dep_audit")) {
|
|
278
|
-
config.tools.dep_audit = { name: "llm", prompt: "Review these changed files for newly added dependencies or imports. Flag potential typosquatting (CWE-1357), packages you cannot verify as real, and packages with known security advisories. Check for misspellings (e.g., 'reqeusts' instead of 'requests')." };
|
|
279
|
-
}
|
|
280
|
-
if (!byCategory.has("secrets")) {
|
|
281
|
-
config.tools.secrets = { name: "llm", prompt: "Review these changed files for hardcoded secrets (CWE-798), API keys, passwords, tokens, private keys, or credentials (CWE-312). Flag any string that looks like a secret value rather than a placeholder or environment variable reference." };
|
|
282
|
-
}
|
|
283
|
-
if (!byCategory.has("dead_code")) {
|
|
284
|
-
config.tools.dead_code = { name: "llm", prompt: "Review these changed files for dead code: unused functions, unreachable branches, unused imports, unused variables, and exports that are never imported elsewhere. Only flag code that is clearly dead, not code that might be used dynamically." };
|
|
285
|
-
}
|
|
286
|
-
config.tools.best_practices = { name: "llm", prompt: "Review these changed files for best practices violations" };
|
|
287
|
-
if (!config.tools.duplicates) {
|
|
288
|
-
config.tools.duplicates = { name: "jscpd", command: "npx jscpd --reporters console" };
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
async function buildDetectedConfig(root, prefill = {}) {
|
|
292
|
-
console.log(chalk.bold("\nDetecting project tools...\n"));
|
|
293
|
-
const detections = await detectTools(root);
|
|
294
|
-
const config = buildMinimalConfig();
|
|
295
|
-
if (detections.length === 0) {
|
|
296
|
-
console.log(chalk.dim(" No tools detected. Using minimal config.\n"));
|
|
297
|
-
return { config: await askEssentialPreferences(config, root, prefill), detection: null };
|
|
298
|
-
}
|
|
299
|
-
const byCategory = bestByCategory(detections);
|
|
300
|
-
console.log(chalk.bold(" Detected tools:\n"));
|
|
301
|
-
for (const cat of CATEGORY_ORDER) {
|
|
302
|
-
const label = (CATEGORY_LABELS[cat] ?? cat).padEnd(14);
|
|
303
|
-
const d = byCategory.get(cat);
|
|
304
|
-
if (d)
|
|
305
|
-
console.log(` ${label} ${chalk.cyan(d.name.padEnd(16))} ${chalk.dim(`(${d.signal})`)}`);
|
|
306
|
-
else
|
|
307
|
-
console.log(` ${label} ${chalk.dim("(none detected)")}`);
|
|
308
|
-
}
|
|
309
|
-
const readline = await import("node:readline/promises");
|
|
310
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
311
|
-
console.log();
|
|
312
|
-
const answer = await rl.question(" Accept detected tools? (Y/n/edit) ");
|
|
313
|
-
const prefillWithSurface = { ...prefill, detectedSurface: surfaceFromDetection(byCategory.get("surface")) };
|
|
314
|
-
if (answer.toLowerCase() === "n") {
|
|
315
|
-
rl.close();
|
|
316
|
-
console.log(chalk.dim(" Skipping tool detection.\n"));
|
|
317
|
-
return { config: await askEssentialPreferences(config, root, prefillWithSurface), detection: null };
|
|
318
|
-
}
|
|
319
|
-
if (answer.toLowerCase() === "edit")
|
|
320
|
-
await editDetections(rl, byCategory);
|
|
321
|
-
rl.close();
|
|
322
|
-
applyProjectDetections(config, byCategory);
|
|
323
|
-
applyLlmFallbacks(config, byCategory);
|
|
324
|
-
return { config: await askEssentialPreferences(config, root, prefillWithSurface), detection: byCategory.get("language") ?? null };
|
|
325
|
-
}
|
|
326
|
-
const PROMPT_SURFACES = [
|
|
327
|
-
"tui",
|
|
328
|
-
"web",
|
|
329
|
-
"mobile",
|
|
330
|
-
"api",
|
|
331
|
-
"mixed",
|
|
332
|
-
];
|
|
333
|
-
function surfaceFromDetection(d) {
|
|
334
|
-
if (!d)
|
|
335
|
-
return undefined;
|
|
336
|
-
return PROMPT_SURFACES.includes(d.name)
|
|
337
|
-
? d.name
|
|
338
|
-
: undefined;
|
|
339
|
-
}
|
|
340
|
-
async function editDetections(rl, byCategory) {
|
|
341
|
-
console.log(chalk.dim("\n For each tool, press Enter to accept, 'n' to skip, or type a custom name.\n"));
|
|
342
|
-
for (const cat of CATEGORY_ORDER) {
|
|
343
|
-
const label = CATEGORY_LABELS[cat] ?? cat;
|
|
344
|
-
const d = byCategory.get(cat);
|
|
345
|
-
const current = d ? d.name : "none";
|
|
346
|
-
const answer = await rl.question(` ${label} [${current}]: `);
|
|
347
|
-
const trimmed = answer.trim();
|
|
348
|
-
if (trimmed.toLowerCase() === "n") {
|
|
349
|
-
byCategory.delete(cat);
|
|
350
|
-
}
|
|
351
|
-
else if (trimmed && trimmed !== current) {
|
|
352
|
-
byCategory.set(cat, {
|
|
353
|
-
category: cat,
|
|
354
|
-
name: trimmed,
|
|
355
|
-
confidence: "low",
|
|
356
|
-
signal: "user input",
|
|
357
|
-
});
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
async function askEssentialPreferences(config, root, prefill = {}) {
|
|
362
|
-
const readline = await import("node:readline/promises");
|
|
363
|
-
const rl = readline.createInterface({
|
|
364
|
-
input: process.stdin,
|
|
365
|
-
output: process.stdout,
|
|
366
|
-
});
|
|
367
|
-
// 1. AI agents
|
|
368
|
-
console.log(chalk.bold("\n AI agents:\n"));
|
|
369
|
-
console.log(chalk.dim(" Which AI coding tools will use these skills?"));
|
|
370
|
-
console.log(chalk.dim(" Skills install per tool: claude→.claude/skills, opencode→.opencode/skills, codex→.agents/skills."));
|
|
371
|
-
console.log(chalk.dim(" cursor/copilot use AGENTS.md-derived files (no skills).\n"));
|
|
372
|
-
const detectedAgents = await detectAgentFiles(root).catch(() => []);
|
|
373
|
-
const defaultAgents = detectedAgents.length > 0 ? detectedAgents.join(",") : "claude";
|
|
374
|
-
const agentsAnswer = await rl.question(` AI agents (comma-separated: claude/opencode/codex/cursor/copilot) [${defaultAgents}]: `);
|
|
375
|
-
const rawAgents = agentsAnswer.trim() || defaultAgents;
|
|
376
|
-
config.project.agents = rawAgents
|
|
377
|
-
.split(",")
|
|
378
|
-
.map((s) => s.trim().toLowerCase())
|
|
379
|
-
.filter(Boolean);
|
|
380
|
-
// 2. Recommended integrations
|
|
381
|
-
console.log(chalk.bold("\n Recommended integrations:\n"));
|
|
382
|
-
console.log(chalk.dim(" These are optional but recommended. Saying yes records the intent"));
|
|
383
|
-
console.log(chalk.dim(" in config and prints install commands at the end.\n"));
|
|
384
|
-
const integrations = {};
|
|
385
|
-
if (prefill.codebaseMemoryMcp === undefined) {
|
|
386
|
-
const cbmAnswer = await rl.question(" Install codebase-memory-mcp (call graphs, code intelligence)? (Y/n) ");
|
|
387
|
-
integrations.codebase_memory_mcp =
|
|
388
|
-
cbmAnswer.trim().toLowerCase() !== "n";
|
|
389
|
-
}
|
|
390
|
-
else {
|
|
391
|
-
integrations.codebase_memory_mcp = prefill.codebaseMemoryMcp;
|
|
392
|
-
}
|
|
393
|
-
if (prefill.cavemanPlugin === undefined) {
|
|
394
|
-
const cavemanPluginAnswer = await rl.question(" Install caveman skill plugin (Claude Code marketplace)? (y/N) ");
|
|
395
|
-
integrations.caveman_plugin =
|
|
396
|
-
cavemanPluginAnswer.trim().toLowerCase() === "y";
|
|
397
|
-
}
|
|
398
|
-
else {
|
|
399
|
-
integrations.caveman_plugin = prefill.cavemanPlugin;
|
|
400
|
-
}
|
|
401
|
-
config.project.integrations = integrations;
|
|
402
|
-
// 3. Surface
|
|
403
|
-
await askSurface(rl, config, prefill.detectedSurface);
|
|
404
|
-
// 4. Caveman level
|
|
405
|
-
const currentCaveman = config.project.caveman ?? "lite";
|
|
406
|
-
const cavemanAnswer = await rl.question(` Token optimization (caveman)? (none/lite/full/ultra) [${currentCaveman}]: `);
|
|
407
|
-
config.project.caveman = (cavemanAnswer.trim() ? cavemanAnswer.trim().toLowerCase() : currentCaveman);
|
|
408
|
-
// 5. Commit style
|
|
409
|
-
const commitAnswer = await rl.question(` Commit style? (conventional/angular/custom) [${config.project.commit_style}]: `);
|
|
410
|
-
if (commitAnswer.trim()) {
|
|
411
|
-
config.project.commit_style = commitAnswer.trim();
|
|
412
|
-
}
|
|
413
|
-
// 6. Design tool + brand capture
|
|
414
|
-
await runSections(rl, config, root, ["design"]);
|
|
415
|
-
rl.close();
|
|
416
|
-
console.log();
|
|
417
|
-
return config;
|
|
418
|
-
}
|
|
419
|
-
async function askSurface(rl, config, detected) {
|
|
420
|
-
const prompt = detected
|
|
421
|
-
? ` Project surface: ${detected} — confirm or override (tui/web/mobile/api/mixed/Enter to accept): `
|
|
422
|
-
: ` Project surface? (tui/web/mobile/api/mixed/skip) `;
|
|
423
|
-
const answer = (await rl.question(prompt)).trim().toLowerCase();
|
|
424
|
-
if (!answer) {
|
|
425
|
-
if (detected)
|
|
426
|
-
config.project.surface = detected;
|
|
427
|
-
return;
|
|
428
|
-
}
|
|
429
|
-
if (answer === "skip")
|
|
430
|
-
return;
|
|
431
|
-
if (PROMPT_SURFACES.includes(answer)) {
|
|
432
|
-
config.project.surface = answer;
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
function confidenceRank(c) {
|
|
436
|
-
return c === "high" ? 3 : c === "medium" ? 2 : 1;
|
|
437
|
-
}
|
|
438
162
|
async function setupAgentsFile(root, caveman) {
|
|
439
163
|
await upsertAgentsFile(root, PACKAGE_ROOT, "created", caveman);
|
|
440
164
|
}
|