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 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
- - A TDD skill if testing is relevant
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?.install.plugin_command) {
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("\n Install plugins by running these in Claude Code:"));
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("\n Install plugins by running these in Claude Code:"));
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.0.0");
1382
+ ).version("1.3.0");
1207
1383
  program.addCommand(initCommand);
1208
1384
  program.addCommand(describeCommand);
1209
1385
  program.addCommand(optimizeCommand);