kairn-cli 1.1.0 → 1.3.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/cli.js +183 -7
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/src/registry/tools.json +586 -46
package/dist/cli.js
CHANGED
|
@@ -217,6 +217,37 @@ You must output a JSON object matching the EnvironmentSpec schema.
|
|
|
217
217
|
- **Concise CLAUDE.md.** Under 100 lines. No generic text like "be helpful." Include build/test commands, reference docs/ and skills/.
|
|
218
218
|
- **Security by default.** Always include deny rules for destructive commands and secret file access.
|
|
219
219
|
|
|
220
|
+
## CLAUDE.md Template (mandatory structure)
|
|
221
|
+
|
|
222
|
+
The \`claude_md\` field MUST follow this exact structure (max 100 lines):
|
|
223
|
+
|
|
224
|
+
\`\`\`
|
|
225
|
+
# {Project Name}
|
|
226
|
+
|
|
227
|
+
## Purpose
|
|
228
|
+
{one-line description}
|
|
229
|
+
|
|
230
|
+
## Tech Stack
|
|
231
|
+
{bullet list of frameworks/languages}
|
|
232
|
+
|
|
233
|
+
## Commands
|
|
234
|
+
{concrete build/test/lint/dev commands}
|
|
235
|
+
|
|
236
|
+
## Architecture
|
|
237
|
+
{brief folder structure, max 10 lines}
|
|
238
|
+
|
|
239
|
+
## Conventions
|
|
240
|
+
{3-5 specific coding rules}
|
|
241
|
+
|
|
242
|
+
## Key Commands
|
|
243
|
+
{list /project: commands with descriptions}
|
|
244
|
+
|
|
245
|
+
## Output
|
|
246
|
+
{where results go, key files}
|
|
247
|
+
\`\`\`
|
|
248
|
+
|
|
249
|
+
Do not add generic filler. Every line must be specific to the user's workflow.
|
|
250
|
+
|
|
220
251
|
## What You Must Always Include
|
|
221
252
|
|
|
222
253
|
1. A concise, workflow-specific \`claude_md\` (the CLAUDE.md content)
|
|
@@ -228,6 +259,95 @@ You must output a JSON object matching the EnvironmentSpec schema.
|
|
|
228
259
|
7. A \`rules/continuity.md\` rule encouraging updates to DECISIONS.md and LEARNINGS.md
|
|
229
260
|
8. A \`rules/security.md\` rule with essential security instructions
|
|
230
261
|
9. settings.json with deny rules for \`rm -rf\`, \`curl|sh\`, reading \`.env\` and \`secrets/\`
|
|
262
|
+
10. A \`/project:status\` command for code projects (uses ! for live git/test output)
|
|
263
|
+
11. A \`/project:fix\` command for code projects (uses $ARGUMENTS for issue number)
|
|
264
|
+
|
|
265
|
+
## Shell-Integrated Commands
|
|
266
|
+
|
|
267
|
+
Commands that reference live project state should use Claude Code's \`!\` prefix for shell output:
|
|
268
|
+
|
|
269
|
+
\`\`\`markdown
|
|
270
|
+
# Example: .claude/commands/review.md
|
|
271
|
+
Review the staged changes for quality and security:
|
|
272
|
+
|
|
273
|
+
!git diff --staged
|
|
274
|
+
|
|
275
|
+
Run tests and check for failures:
|
|
276
|
+
|
|
277
|
+
!npm test 2>&1 | tail -20
|
|
278
|
+
|
|
279
|
+
Focus on: security, error handling, test coverage.
|
|
280
|
+
\`\`\`
|
|
281
|
+
|
|
282
|
+
Use \`!\` when a command needs: git status, test results, build output, or file listings.
|
|
283
|
+
|
|
284
|
+
## Path-Scoped Rules
|
|
285
|
+
|
|
286
|
+
For code projects with multiple domains (API, frontend, tests), generate path-scoped rules using YAML frontmatter:
|
|
287
|
+
|
|
288
|
+
\`\`\`markdown
|
|
289
|
+
# Example: rules/api.md
|
|
290
|
+
---
|
|
291
|
+
paths:
|
|
292
|
+
- "src/api/**"
|
|
293
|
+
- "src/routes/**"
|
|
294
|
+
---
|
|
295
|
+
- All handlers return { data, error } shape
|
|
296
|
+
- Use Zod for request validation
|
|
297
|
+
- Log errors with request ID context
|
|
298
|
+
\`\`\`
|
|
299
|
+
|
|
300
|
+
\`\`\`markdown
|
|
301
|
+
# Example: rules/testing.md
|
|
302
|
+
---
|
|
303
|
+
paths:
|
|
304
|
+
- "tests/**"
|
|
305
|
+
- "**/*.test.*"
|
|
306
|
+
- "**/*.spec.*"
|
|
307
|
+
---
|
|
308
|
+
- Use AAA pattern: Arrange-Act-Assert
|
|
309
|
+
- One assertion per test when possible
|
|
310
|
+
- Mock external dependencies, never real APIs
|
|
311
|
+
\`\`\`
|
|
312
|
+
|
|
313
|
+
Keep \`security.md\` and \`continuity.md\` as unconditional (no paths frontmatter).
|
|
314
|
+
Only generate scoped rules when the workflow involves multiple code domains.
|
|
315
|
+
|
|
316
|
+
## Hooks
|
|
317
|
+
|
|
318
|
+
Generate hooks in settings.json based on project type:
|
|
319
|
+
|
|
320
|
+
**All code projects** \u2014 block destructive commands:
|
|
321
|
+
\`\`\`json
|
|
322
|
+
{
|
|
323
|
+
"hooks": {
|
|
324
|
+
"PreToolUse": [{
|
|
325
|
+
"matcher": "Bash",
|
|
326
|
+
"hooks": [{
|
|
327
|
+
"type": "command",
|
|
328
|
+
"command": "CMD=$(cat | jq -r '.tool_input.command // empty') && echo \\"$CMD\\" | grep -qiE 'rm\\\\s+-rf\\\\s+/|DROP\\\\s+TABLE|curl.*\\\\|\\\\s*sh' && echo 'Blocked destructive command' >&2 && exit 2 || true"
|
|
329
|
+
}]
|
|
330
|
+
}]
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
\`\`\`
|
|
334
|
+
|
|
335
|
+
**Projects with Prettier/ESLint/Black** \u2014 auto-format on write:
|
|
336
|
+
\`\`\`json
|
|
337
|
+
{
|
|
338
|
+
"hooks": {
|
|
339
|
+
"PostToolUse": [{
|
|
340
|
+
"matcher": "Edit|Write",
|
|
341
|
+
"hooks": [{
|
|
342
|
+
"type": "command",
|
|
343
|
+
"command": "FILE=$(cat | jq -r '.tool_input.file_path // empty') && [ -n \\"$FILE\\" ] && npx prettier --write \\"$FILE\\" 2>/dev/null || true"
|
|
344
|
+
}]
|
|
345
|
+
}]
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
\`\`\`
|
|
349
|
+
|
|
350
|
+
Merge hooks into the \`settings\` object alongside permissions. Choose the formatter hook based on detected dependencies (Prettier \u2192 prettier, ESLint \u2192 eslint, Black \u2192 black).
|
|
231
351
|
|
|
232
352
|
## Tool Selection Rules
|
|
233
353
|
|
|
@@ -244,7 +364,13 @@ You must output a JSON object matching the EnvironmentSpec schema.
|
|
|
244
364
|
- \`/project:review\` command (review changes)
|
|
245
365
|
- \`/project:test\` command (run and fix tests)
|
|
246
366
|
- \`/project:commit\` command (conventional commits)
|
|
247
|
-
-
|
|
367
|
+
- \`/project:status\` command (live git status, recent commits, TODO overview using ! prefix)
|
|
368
|
+
- \`/project:fix\` command (takes $ARGUMENTS as issue number, plans fix, implements, tests, commits)
|
|
369
|
+
- A TDD skill using the 3-phase isolation pattern (RED \u2192 GREEN \u2192 REFACTOR):
|
|
370
|
+
- RED: Write failing test only. Verify it FAILS.
|
|
371
|
+
- GREEN: Write MINIMUM code to pass. Nothing extra.
|
|
372
|
+
- REFACTOR: Improve while keeping tests green.
|
|
373
|
+
Rules: never write tests and implementation in same step, AAA pattern, one assertion per test.
|
|
248
374
|
- A reviewer agent (read-only, Sonnet model)
|
|
249
375
|
|
|
250
376
|
## For Research Projects, Additionally Include
|
|
@@ -284,7 +410,9 @@ Return ONLY valid JSON matching this structure:
|
|
|
284
410
|
},
|
|
285
411
|
"commands": {
|
|
286
412
|
"help": "markdown content for /project:help",
|
|
287
|
-
"tasks": "markdown content for /project:tasks"
|
|
413
|
+
"tasks": "markdown content for /project:tasks",
|
|
414
|
+
"status": "Show project status:\\n\\n!git status --short\\n\\n!git log --oneline -5\\n\\nRead TODO.md and summarize progress.",
|
|
415
|
+
"fix": "Fix issue #$ARGUMENTS:\\n\\n1. Read the issue and understand the problem\\n2. Plan the fix\\n3. Implement the fix\\n4. Run tests:\\n\\n!npm test 2>&1 | tail -20\\n\\n5. Commit with: fix: resolve #$ARGUMENTS"
|
|
288
416
|
},
|
|
289
417
|
"rules": {
|
|
290
418
|
"continuity": "markdown content for continuity rule",
|
|
@@ -524,11 +652,23 @@ async function writeEnvironment(spec, targetDir) {
|
|
|
524
652
|
}
|
|
525
653
|
function summarizeSpec(spec, registry) {
|
|
526
654
|
const pluginCommands = [];
|
|
655
|
+
const envSetup = [];
|
|
527
656
|
for (const selected of spec.tools) {
|
|
528
657
|
const tool = registry.find((t) => t.id === selected.tool_id);
|
|
529
|
-
if (tool
|
|
658
|
+
if (!tool) continue;
|
|
659
|
+
if (tool.install.plugin_command) {
|
|
530
660
|
pluginCommands.push(tool.install.plugin_command);
|
|
531
661
|
}
|
|
662
|
+
if (tool.env_vars) {
|
|
663
|
+
for (const ev of tool.env_vars) {
|
|
664
|
+
envSetup.push({
|
|
665
|
+
toolName: tool.name,
|
|
666
|
+
envVar: ev.name,
|
|
667
|
+
description: ev.description,
|
|
668
|
+
signupUrl: tool.signup_url
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
}
|
|
532
672
|
}
|
|
533
673
|
return {
|
|
534
674
|
toolCount: spec.tools.length,
|
|
@@ -536,7 +676,8 @@ function summarizeSpec(spec, registry) {
|
|
|
536
676
|
ruleCount: Object.keys(spec.harness.rules || {}).length,
|
|
537
677
|
skillCount: Object.keys(spec.harness.skills || {}).length,
|
|
538
678
|
agentCount: Object.keys(spec.harness.agents || {}).length,
|
|
539
|
-
pluginCommands
|
|
679
|
+
pluginCommands,
|
|
680
|
+
envSetup
|
|
540
681
|
};
|
|
541
682
|
}
|
|
542
683
|
|
|
@@ -629,8 +770,22 @@ var describeCommand = new Command2("describe").description("Describe your workfl
|
|
|
629
770
|
for (const file of written) {
|
|
630
771
|
console.log(chalk2.dim(` ${file}`));
|
|
631
772
|
}
|
|
773
|
+
if (summary.envSetup.length > 0) {
|
|
774
|
+
console.log(chalk2.yellow("\n API keys needed (set these environment variables):\n"));
|
|
775
|
+
const seen = /* @__PURE__ */ new Set();
|
|
776
|
+
for (const env of summary.envSetup) {
|
|
777
|
+
if (seen.has(env.envVar)) continue;
|
|
778
|
+
seen.add(env.envVar);
|
|
779
|
+
console.log(chalk2.bold(` export ${env.envVar}="your-key-here"`));
|
|
780
|
+
console.log(chalk2.dim(` ${env.description}`));
|
|
781
|
+
if (env.signupUrl) {
|
|
782
|
+
console.log(chalk2.dim(` Get one at: ${env.signupUrl}`));
|
|
783
|
+
}
|
|
784
|
+
console.log("");
|
|
785
|
+
}
|
|
786
|
+
}
|
|
632
787
|
if (summary.pluginCommands.length > 0) {
|
|
633
|
-
console.log(chalk2.yellow("
|
|
788
|
+
console.log(chalk2.yellow(" Install plugins by running these in Claude Code:"));
|
|
634
789
|
for (const cmd of summary.pluginCommands) {
|
|
635
790
|
console.log(chalk2.bold(` ${cmd}`));
|
|
636
791
|
}
|
|
@@ -1053,6 +1208,10 @@ ${profile.existingClaudeMd}`);
|
|
|
1053
1208
|
parts.push("- Are security rules present?");
|
|
1054
1209
|
parts.push("- Is there a continuity rule for session memory?");
|
|
1055
1210
|
parts.push("- Are there unnecessary MCP servers adding context bloat?");
|
|
1211
|
+
parts.push("- Are hooks configured in settings.json for destructive command blocking?");
|
|
1212
|
+
parts.push("- Are there path-scoped rules for different code domains (api, testing, frontend)?");
|
|
1213
|
+
parts.push("- Does the project have a /project:status command with live git output?");
|
|
1214
|
+
parts.push("- Is there a /project:fix command for issue-driven development?");
|
|
1056
1215
|
if (profile.claudeMdLineCount > 200) {
|
|
1057
1216
|
parts.push(`- CLAUDE.md is ${profile.claudeMdLineCount} lines \u2014 needs aggressive trimming`);
|
|
1058
1217
|
}
|
|
@@ -1109,6 +1268,9 @@ var optimizeCommand = new Command6("optimize").description("Scan an existing pro
|
|
|
1109
1268
|
if (profile.mcpServerCount === 0 && profile.dependencies.length > 0) issues.push("No MCP servers configured");
|
|
1110
1269
|
if (profile.hasTests && !profile.existingCommands.includes("test")) issues.push("Has tests but no /project:test command");
|
|
1111
1270
|
if (!profile.existingCommands.includes("tasks")) issues.push("Missing /project:tasks command");
|
|
1271
|
+
if (!profile.existingSettings?.hooks) issues.push("No hooks configured \u2014 missing destructive command blocking");
|
|
1272
|
+
const scopedRules = profile.existingRules.filter((r) => r !== "security" && r !== "continuity");
|
|
1273
|
+
if (profile.hasSrc && scopedRules.length === 0) issues.push("No path-scoped rules \u2014 consider adding api.md, testing.md, or frontend.md rules");
|
|
1112
1274
|
if (issues.length > 0) {
|
|
1113
1275
|
console.log(chalk6.yellow("\n Issues Found:\n"));
|
|
1114
1276
|
for (const issue of issues) {
|
|
@@ -1188,8 +1350,22 @@ var optimizeCommand = new Command6("optimize").description("Scan an existing pro
|
|
|
1188
1350
|
for (const file of written) {
|
|
1189
1351
|
console.log(chalk6.dim(` ${file}`));
|
|
1190
1352
|
}
|
|
1353
|
+
if (summary.envSetup.length > 0) {
|
|
1354
|
+
console.log(chalk6.yellow("\n API keys needed (set these environment variables):\n"));
|
|
1355
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1356
|
+
for (const env of summary.envSetup) {
|
|
1357
|
+
if (seen.has(env.envVar)) continue;
|
|
1358
|
+
seen.add(env.envVar);
|
|
1359
|
+
console.log(chalk6.bold(` export ${env.envVar}="your-key-here"`));
|
|
1360
|
+
console.log(chalk6.dim(` ${env.description}`));
|
|
1361
|
+
if (env.signupUrl) {
|
|
1362
|
+
console.log(chalk6.dim(` Get one at: ${env.signupUrl}`));
|
|
1363
|
+
}
|
|
1364
|
+
console.log("");
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1191
1367
|
if (summary.pluginCommands.length > 0) {
|
|
1192
|
-
console.log(chalk6.yellow("
|
|
1368
|
+
console.log(chalk6.yellow(" Install plugins by running these in Claude Code:"));
|
|
1193
1369
|
for (const cmd of summary.pluginCommands) {
|
|
1194
1370
|
console.log(chalk6.bold(` ${cmd}`));
|
|
1195
1371
|
}
|
|
@@ -1203,7 +1379,7 @@ var optimizeCommand = new Command6("optimize").description("Scan an existing pro
|
|
|
1203
1379
|
var program = new Command7();
|
|
1204
1380
|
program.name("kairn").description(
|
|
1205
1381
|
"Compile natural language intent into optimized Claude Code environments"
|
|
1206
|
-
).version("1.
|
|
1382
|
+
).version("1.3.0");
|
|
1207
1383
|
program.addCommand(initCommand);
|
|
1208
1384
|
program.addCommand(describeCommand);
|
|
1209
1385
|
program.addCommand(optimizeCommand);
|