@kiwidata/grimoire 0.2.0 → 0.2.2

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.
Files changed (37) hide show
  1. package/AGENTS.md +9 -4
  2. package/dist/core/check-complexity.d.ts +4 -0
  3. package/dist/core/check-complexity.d.ts.map +1 -0
  4. package/dist/core/check-complexity.js +79 -0
  5. package/dist/core/check-complexity.js.map +1 -0
  6. package/dist/core/check-llm.d.ts +5 -0
  7. package/dist/core/check-llm.d.ts.map +1 -0
  8. package/dist/core/check-llm.js +108 -0
  9. package/dist/core/check-llm.js.map +1 -0
  10. package/dist/core/check.d.ts +1 -2
  11. package/dist/core/check.d.ts.map +1 -1
  12. package/dist/core/check.js +2 -179
  13. package/dist/core/check.js.map +1 -1
  14. package/dist/core/init-config.d.ts +22 -0
  15. package/dist/core/init-config.d.ts.map +1 -0
  16. package/dist/core/init-config.js +118 -0
  17. package/dist/core/init-config.js.map +1 -0
  18. package/dist/core/init-prompts.d.ts +8 -0
  19. package/dist/core/init-prompts.d.ts.map +1 -0
  20. package/dist/core/init-prompts.js +166 -0
  21. package/dist/core/init-prompts.js.map +1 -0
  22. package/dist/core/init.d.ts +0 -1
  23. package/dist/core/init.d.ts.map +1 -1
  24. package/dist/core/init.js +3 -279
  25. package/dist/core/init.js.map +1 -1
  26. package/package.json +1 -1
  27. package/skills/grimoire-apply/SKILL.md +65 -12
  28. package/skills/grimoire-draft/SKILL.md +5 -3
  29. package/skills/grimoire-plan/SKILL.md +4 -1
  30. package/skills/grimoire-pr/SKILL.md +14 -15
  31. package/skills/grimoire-refactor/SKILL.md +3 -1
  32. package/skills/references/code-quality.md +1 -1
  33. package/skills/references/principles.md +4 -3
  34. package/skills/references/refactor-scan-categories.md +15 -0
  35. package/skills/references/review-personas.md +30 -13
  36. package/skills/references/testing-contracts.md +19 -0
  37. package/templates/learnings.md +40 -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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"}
@@ -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
@@ -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;AA8KD,wBAAsB,WAAW,CAC/B,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,IAAI,CAAC,CAgBf;AA0DD,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAWvD"}
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, detectAgentFiles, } from "./shared-setup.js";
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
  }