@kody-ade/kody-engine-lite 0.1.55 → 0.1.56

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 (74) hide show
  1. package/dist/agent-runner.d.ts +4 -0
  2. package/dist/agent-runner.js +122 -0
  3. package/dist/bin/cli.js +422 -477
  4. package/dist/ci/parse-inputs.d.ts +6 -0
  5. package/dist/ci/parse-inputs.js +76 -0
  6. package/dist/ci/parse-safety.d.ts +6 -0
  7. package/dist/ci/parse-safety.js +22 -0
  8. package/dist/cli/args.d.ts +13 -0
  9. package/dist/cli/args.js +42 -0
  10. package/dist/cli/litellm.d.ts +2 -0
  11. package/dist/cli/litellm.js +85 -0
  12. package/dist/cli/task-resolution.d.ts +2 -0
  13. package/dist/cli/task-resolution.js +41 -0
  14. package/dist/config.d.ts +49 -0
  15. package/dist/config.js +72 -0
  16. package/dist/context.d.ts +4 -0
  17. package/dist/context.js +83 -0
  18. package/dist/definitions.d.ts +3 -0
  19. package/dist/definitions.js +59 -0
  20. package/dist/entry.d.ts +1 -0
  21. package/dist/entry.js +236 -0
  22. package/dist/git-utils.d.ts +13 -0
  23. package/dist/git-utils.js +174 -0
  24. package/dist/github-api.d.ts +14 -0
  25. package/dist/github-api.js +114 -0
  26. package/dist/kody-utils.d.ts +1 -0
  27. package/dist/kody-utils.js +9 -0
  28. package/dist/learning/auto-learn.d.ts +2 -0
  29. package/dist/learning/auto-learn.js +169 -0
  30. package/dist/logger.d.ts +14 -0
  31. package/dist/logger.js +51 -0
  32. package/dist/memory.d.ts +1 -0
  33. package/dist/memory.js +20 -0
  34. package/dist/observer.d.ts +9 -0
  35. package/dist/observer.js +80 -0
  36. package/dist/pipeline/complexity.d.ts +3 -0
  37. package/dist/pipeline/complexity.js +12 -0
  38. package/dist/pipeline/executor-registry.d.ts +3 -0
  39. package/dist/pipeline/executor-registry.js +20 -0
  40. package/dist/pipeline/hooks.d.ts +17 -0
  41. package/dist/pipeline/hooks.js +110 -0
  42. package/dist/pipeline/questions.d.ts +2 -0
  43. package/dist/pipeline/questions.js +44 -0
  44. package/dist/pipeline/runner-selection.d.ts +2 -0
  45. package/dist/pipeline/runner-selection.js +13 -0
  46. package/dist/pipeline/state.d.ts +4 -0
  47. package/dist/pipeline/state.js +37 -0
  48. package/dist/pipeline.d.ts +3 -0
  49. package/dist/pipeline.js +213 -0
  50. package/dist/preflight.d.ts +1 -0
  51. package/dist/preflight.js +69 -0
  52. package/dist/retrospective.d.ts +26 -0
  53. package/dist/retrospective.js +211 -0
  54. package/dist/stages/agent.d.ts +2 -0
  55. package/dist/stages/agent.js +94 -0
  56. package/dist/stages/gate.d.ts +2 -0
  57. package/dist/stages/gate.js +32 -0
  58. package/dist/stages/review.d.ts +2 -0
  59. package/dist/stages/review.js +32 -0
  60. package/dist/stages/ship.d.ts +3 -0
  61. package/dist/stages/ship.js +154 -0
  62. package/dist/stages/verify.d.ts +2 -0
  63. package/dist/stages/verify.js +94 -0
  64. package/dist/types.d.ts +61 -0
  65. package/dist/types.js +1 -0
  66. package/dist/validators.d.ts +8 -0
  67. package/dist/validators.js +42 -0
  68. package/dist/verify-runner.d.ts +11 -0
  69. package/dist/verify-runner.js +110 -0
  70. package/kody.config.schema.json +2 -2
  71. package/package.json +1 -1
  72. package/prompts/autofix.md +9 -27
  73. package/prompts/review.md +16 -83
  74. package/templates/kody.yml +29 -19
package/dist/bin/cli.js CHANGED
@@ -300,7 +300,7 @@ var init_config = __esm({
300
300
  repo: ""
301
301
  },
302
302
  paths: {
303
- taskDir: ".kody/tasks"
303
+ taskDir: ".tasks"
304
304
  },
305
305
  agent: {
306
306
  runner: "claude-code",
@@ -1601,20 +1601,6 @@ function executeShipStage(ctx, _def) {
1601
1601
  try {
1602
1602
  const head = getCurrentBranch(ctx.projectDir);
1603
1603
  const base = getDefaultBranch(ctx.projectDir);
1604
- try {
1605
- execFileSync7("git", ["add", ctx.taskDir], {
1606
- cwd: ctx.projectDir,
1607
- env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
1608
- stdio: "pipe"
1609
- });
1610
- execFileSync7("git", ["commit", "--no-gpg-sign", "-m", `chore: add kody task artifacts [skip ci]`], {
1611
- cwd: ctx.projectDir,
1612
- env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
1613
- stdio: "pipe"
1614
- });
1615
- logger.info(" Committed task artifacts");
1616
- } catch {
1617
- }
1618
1604
  pushBranch(ctx.projectDir);
1619
1605
  const config = getProjectConfig();
1620
1606
  let owner = config.github?.owner;
@@ -1712,7 +1698,6 @@ Failed: ${msg}
1712
1698
  var init_ship = __esm({
1713
1699
  "src/stages/ship.ts"() {
1714
1700
  "use strict";
1715
- init_logger();
1716
1701
  init_git_utils();
1717
1702
  init_github_api();
1718
1703
  init_config();
@@ -2541,7 +2526,7 @@ import * as fs16 from "fs";
2541
2526
  import * as path15 from "path";
2542
2527
  import { execFileSync as execFileSync9 } from "child_process";
2543
2528
  function findLatestTaskForIssue(issueNumber, projectDir) {
2544
- const tasksDir = path15.join(projectDir, ".kody", "tasks");
2529
+ const tasksDir = path15.join(projectDir, ".tasks");
2545
2530
  if (!fs16.existsSync(tasksDir)) return null;
2546
2531
  const allDirs = fs16.readdirSync(tasksDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort().reverse();
2547
2532
  const prefix = `${issueNumber}-`;
@@ -2602,7 +2587,7 @@ Or comment on the specific PR: \`@kody review\``
2602
2587
  }
2603
2588
  async function runStandaloneReview(input) {
2604
2589
  const taskId = input.taskId ?? `review-${generateTaskId()}`;
2605
- const taskDir = path16.join(input.projectDir, ".kody", "tasks", taskId);
2590
+ const taskDir = path16.join(input.projectDir, ".tasks", taskId);
2606
2591
  fs17.mkdirSync(taskDir, { recursive: true });
2607
2592
  const taskContent = `# ${input.prTitle}
2608
2593
 
@@ -2845,7 +2830,7 @@ function resolveTaskAction(issueNumber, existingTaskId, existingState) {
2845
2830
  function resolveForIssue(issueNumber, projectDir) {
2846
2831
  const existingTaskId = findLatestTaskForIssue(issueNumber, projectDir);
2847
2832
  if (existingTaskId) {
2848
- const statusPath = path18.join(projectDir, ".kody", "tasks", existingTaskId, "status.json");
2833
+ const statusPath = path18.join(projectDir, ".tasks", existingTaskId, "status.json");
2849
2834
  let existingState = null;
2850
2835
  if (fs19.existsSync(statusPath)) {
2851
2836
  try {
@@ -2939,7 +2924,7 @@ async function main() {
2939
2924
  process.exit(1);
2940
2925
  }
2941
2926
  }
2942
- const taskDir = path19.join(projectDir, ".kody", "tasks", taskId);
2927
+ const taskDir = path19.join(projectDir, ".tasks", taskId);
2943
2928
  fs20.mkdirSync(taskDir, { recursive: true });
2944
2929
  if (input.command === "status") {
2945
2930
  printStatus(taskId, taskDir);
@@ -2978,10 +2963,6 @@ async function main() {
2978
2963
  const proxyRunning = await checkLitellmHealth(config2.agent.litellmUrl);
2979
2964
  if (!proxyRunning) {
2980
2965
  litellmProcess2 = await tryStartLitellm(config2.agent.litellmUrl, projectDir);
2981
- if (!litellmProcess2) {
2982
- logger.warn("LiteLLM not available \u2014 falling back to Anthropic models");
2983
- config2.agent.litellmUrl = void 0;
2984
- }
2985
2966
  }
2986
2967
  if (config2.agent.litellmUrl) {
2987
2968
  process.env.ANTHROPIC_BASE_URL = config2.agent.litellmUrl;
@@ -3055,7 +3036,7 @@ ${issue.body ?? ""}`;
3055
3036
  }
3056
3037
  }
3057
3038
  if (!fs20.existsSync(taskMdPath)) {
3058
- console.error("No task.md found. Provide --task, --issue-number, or ensure .kody/tasks/<id>/task.md exists.");
3039
+ console.error("No task.md found. Provide --task, --issue-number, or ensure .tasks/<id>/task.md exists.");
3059
3040
  process.exit(1);
3060
3041
  }
3061
3042
  if (input.command === "fix" && !input.fromStage) {
@@ -3310,135 +3291,6 @@ function checkGhSecret(repoSlug, secretName) {
3310
3291
  };
3311
3292
  }
3312
3293
  }
3313
- function detectArchitecture(cwd) {
3314
- const detected = [];
3315
- const pkgPath = path20.join(cwd, "package.json");
3316
- if (fs21.existsSync(pkgPath)) {
3317
- try {
3318
- const pkg = JSON.parse(fs21.readFileSync(pkgPath, "utf-8"));
3319
- const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
3320
- if (allDeps.next) detected.push(`- Framework: Next.js ${allDeps.next}`);
3321
- else if (allDeps.react) detected.push(`- Framework: React ${allDeps.react}`);
3322
- else if (allDeps.express) detected.push(`- Framework: Express ${allDeps.express}`);
3323
- else if (allDeps.fastify) detected.push(`- Framework: Fastify ${allDeps.fastify}`);
3324
- else if (allDeps.hono) detected.push(`- Framework: Hono ${allDeps.hono}`);
3325
- if (allDeps.typescript) detected.push(`- Language: TypeScript ${allDeps.typescript}`);
3326
- if (allDeps.vitest) detected.push(`- Testing: vitest ${allDeps.vitest}`);
3327
- else if (allDeps.jest) detected.push(`- Testing: jest ${allDeps.jest}`);
3328
- else if (allDeps.mocha) detected.push(`- Testing: mocha ${allDeps.mocha}`);
3329
- if (allDeps.eslint) detected.push(`- Linting: eslint ${allDeps.eslint}`);
3330
- if (allDeps.prettier) detected.push(`- Formatting: prettier ${allDeps.prettier}`);
3331
- if (allDeps.biome || allDeps["@biomejs/biome"]) detected.push("- Formatting: biome");
3332
- if (allDeps.prisma || allDeps["@prisma/client"]) detected.push("- ORM: Prisma");
3333
- if (allDeps["drizzle-orm"]) detected.push("- ORM: Drizzle");
3334
- if (allDeps.pg || allDeps.postgres) detected.push("- Database: PostgreSQL");
3335
- if (allDeps.mongodb || allDeps.mongoose) detected.push("- Database: MongoDB");
3336
- if (allDeps.redis || allDeps.ioredis) detected.push("- Cache: Redis");
3337
- if (allDeps.payload || allDeps["@payloadcms/next"]) detected.push("- CMS: Payload CMS");
3338
- if (allDeps.tailwindcss) detected.push(`- CSS: Tailwind CSS ${allDeps.tailwindcss}`);
3339
- if (pkg.type === "module") detected.push("- Module system: ESM");
3340
- else detected.push("- Module system: CommonJS");
3341
- if (fs21.existsSync(path20.join(cwd, "pnpm-lock.yaml"))) detected.push("- Package manager: pnpm");
3342
- else if (fs21.existsSync(path20.join(cwd, "yarn.lock"))) detected.push("- Package manager: yarn");
3343
- else if (fs21.existsSync(path20.join(cwd, "bun.lockb"))) detected.push("- Package manager: bun");
3344
- else if (fs21.existsSync(path20.join(cwd, "package-lock.json"))) detected.push("- Package manager: npm");
3345
- } catch {
3346
- }
3347
- }
3348
- try {
3349
- const entries = fs21.readdirSync(cwd, { withFileTypes: true });
3350
- const dirs = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules").map((e) => e.name);
3351
- if (dirs.length > 0) detected.push(`- Top-level directories: ${dirs.join(", ")}`);
3352
- } catch {
3353
- }
3354
- const srcDir = path20.join(cwd, "src");
3355
- if (fs21.existsSync(srcDir)) {
3356
- try {
3357
- const srcEntries = fs21.readdirSync(srcDir, { withFileTypes: true });
3358
- const srcDirs = srcEntries.filter((e) => e.isDirectory()).map((e) => e.name);
3359
- if (srcDirs.length > 0) detected.push(`- src/ structure: ${srcDirs.join(", ")}`);
3360
- } catch {
3361
- }
3362
- }
3363
- const configs = [];
3364
- if (fs21.existsSync(path20.join(cwd, "tsconfig.json"))) configs.push("tsconfig.json");
3365
- if (fs21.existsSync(path20.join(cwd, "docker-compose.yml")) || fs21.existsSync(path20.join(cwd, "docker-compose.yaml"))) configs.push("docker-compose");
3366
- if (fs21.existsSync(path20.join(cwd, "Dockerfile"))) configs.push("Dockerfile");
3367
- if (fs21.existsSync(path20.join(cwd, ".env")) || fs21.existsSync(path20.join(cwd, ".env.local"))) configs.push(".env");
3368
- if (configs.length > 0) detected.push(`- Config files: ${configs.join(", ")}`);
3369
- return detected;
3370
- }
3371
- var STEP_STAGES = ["taskify", "plan", "build", "autofix", "review", "review-fix"];
3372
- function gatherSampleSourceFiles(cwd, maxFiles = 3, maxCharsEach = 2e3) {
3373
- const srcDir = path20.join(cwd, "src");
3374
- const baseDir = fs21.existsSync(srcDir) ? srcDir : cwd;
3375
- const results = [];
3376
- function walk(dir) {
3377
- const entries = [];
3378
- try {
3379
- for (const entry of fs21.readdirSync(dir, { withFileTypes: true })) {
3380
- if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
3381
- const full = path20.join(dir, entry.name);
3382
- if (entry.isDirectory()) {
3383
- entries.push(...walk(full));
3384
- } else if (/\.(ts|js)$/.test(entry.name) && !/\.(test|spec|config|d)\.(ts|js)$/.test(entry.name)) {
3385
- try {
3386
- const stat = fs21.statSync(full);
3387
- if (stat.size >= 200 && stat.size <= 5e3) {
3388
- entries.push({ filePath: full, size: stat.size });
3389
- }
3390
- } catch {
3391
- }
3392
- }
3393
- }
3394
- } catch {
3395
- }
3396
- return entries;
3397
- }
3398
- const files = walk(baseDir).sort((a, b) => b.size - a.size).slice(0, maxFiles);
3399
- for (const { filePath } of files) {
3400
- const rel = path20.relative(cwd, filePath);
3401
- const content = fs21.readFileSync(filePath, "utf-8").slice(0, maxCharsEach);
3402
- results.push(`### File: ${rel}
3403
- \`\`\`typescript
3404
- ${content}
3405
- \`\`\``);
3406
- }
3407
- return results.join("\n\n");
3408
- }
3409
- function buildStepCustomizationPrompt(stageName, defaultPrompt, repoContext, architecture, conventions) {
3410
- return `You are customizing a Kody pipeline prompt for a specific repository.
3411
-
3412
- ## Your Task
3413
- Take the default prompt template below and produce a CUSTOMIZED version tailored to this specific repository.
3414
-
3415
- ## Rules
3416
- 1. KEEP the entire original prompt intact \u2014 its role definition, rules, output format, and {{TASK_CONTEXT}} placeholder. Do not remove or rephrase any existing content.
3417
- 2. APPEND three new sections after the original content but BEFORE the {{TASK_CONTEXT}} line:
3418
- - ## Repo Patterns \u2014 Real code examples from this repo that demonstrate the patterns to follow. Include specific file paths, function signatures, and brief code snippets. Show what GOOD looks like in this repo.
3419
- - ## Improvement Areas \u2014 Gaps, anti-patterns, or inconsistencies found in the codebase that this stage should address when touching related code. Be specific with file paths and what to fix. Do NOT refactor unrelated code \u2014 only improve what the task touches.
3420
- - ## Acceptance Criteria \u2014 A concrete checklist (using markdown checkboxes) that defines "done" for this stage in this specific repo.
3421
- 3. Be SPECIFIC \u2014 reference actual file paths, function names, and conventions from the repo context provided below.
3422
- 4. Keep each appended section concise (10-20 lines max).
3423
- 5. Output ONLY the complete customized prompt markdown. No explanation before or after.
3424
-
3425
- ## Stage Being Customized
3426
- Stage: ${stageName}
3427
-
3428
- ## Default Prompt Template
3429
- ${defaultPrompt}
3430
-
3431
- ## Repository Context
3432
-
3433
- ### Architecture
3434
- ${architecture}
3435
-
3436
- ### Conventions
3437
- ${conventions}
3438
-
3439
- ### Project Details
3440
- ${repoContext}`;
3441
- }
3442
3294
  function detectBasicConfig(cwd) {
3443
3295
  let pm = "pnpm";
3444
3296
  if (fs21.existsSync(path20.join(cwd, "yarn.lock"))) pm = "yarn";
@@ -3483,193 +3335,7 @@ function detectBasicConfig(cwd) {
3483
3335
  }
3484
3336
  return { defaultBranch, owner, repo, pm };
3485
3337
  }
3486
- function smartInit(cwd) {
3487
- const basic = detectBasicConfig(cwd);
3488
- let context = "";
3489
- const readIfExists = (rel, maxChars = 3e3) => {
3490
- const p = path20.join(cwd, rel);
3491
- if (fs21.existsSync(p)) {
3492
- const content = fs21.readFileSync(p, "utf-8");
3493
- return content.slice(0, maxChars);
3494
- }
3495
- return null;
3496
- };
3497
- const pkgJson = readIfExists("package.json");
3498
- if (pkgJson) context += `## package.json
3499
- ${pkgJson}
3500
-
3501
- `;
3502
- const tsconfig = readIfExists("tsconfig.json", 1e3);
3503
- if (tsconfig) context += `## tsconfig.json
3504
- ${tsconfig}
3505
-
3506
- `;
3507
- const readme = readIfExists("README.md", 2e3);
3508
- if (readme) context += `## README.md (first 2000 chars)
3509
- ${readme}
3510
-
3511
- `;
3512
- const claudeMd = readIfExists("CLAUDE.md", 3e3);
3513
- if (claudeMd) context += `## CLAUDE.md
3514
- ${claudeMd}
3515
-
3516
- `;
3517
- try {
3518
- const topDirs = fs21.readdirSync(cwd, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules").map((e) => e.name);
3519
- context += `## Top-level directories
3520
- ${topDirs.join(", ")}
3521
-
3522
- `;
3523
- const srcDir = path20.join(cwd, "src");
3524
- if (fs21.existsSync(srcDir)) {
3525
- const srcDirs = fs21.readdirSync(srcDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
3526
- context += `## src/ subdirectories
3527
- ${srcDirs.join(", ")}
3528
-
3529
- `;
3530
- }
3531
- } catch {
3532
- }
3533
- const existingFiles = [];
3534
- for (const f of [".env.example", "CLAUDE.md", ".ai-docs", "vitest.config.ts", "vitest.config.mts", "jest.config.ts", "playwright.config.ts", ".eslintrc.js", "eslint.config.mjs", ".prettierrc"]) {
3535
- if (fs21.existsSync(path20.join(cwd, f))) existingFiles.push(f);
3536
- }
3537
- if (existingFiles.length) context += `## Config files present
3538
- ${existingFiles.join(", ")}
3539
-
3540
- `;
3541
- context += `## Detected: package manager=${basic.pm}, default branch=${basic.defaultBranch}, github=${basic.owner}/${basic.repo}
3542
- `;
3543
- const prompt = `You are analyzing a project to configure Kody (an autonomous SDLC pipeline).
3544
-
3545
- Given this project context, output ONLY a JSON object with EXACTLY this structure:
3546
-
3547
- {
3548
- "config": {
3549
- "quality": {
3550
- "typecheck": "${basic.pm} <script or command>",
3551
- "lint": "${basic.pm} <script or command>",
3552
- "lintFix": "${basic.pm} <script or command>",
3553
- "format": "${basic.pm} <script or command>",
3554
- "formatFix": "${basic.pm} <script or command>",
3555
- "testUnit": "${basic.pm} <script or command>"
3556
- },
3557
- "git": { "defaultBranch": "${basic.defaultBranch}" },
3558
- "github": { "owner": "${basic.owner}", "repo": "${basic.repo}" },
3559
- "paths": { "taskDir": ".kody/tasks" },
3560
- "agent": {
3561
- "runner": "${"claude-code"}",
3562
- "defaultRunner": "${"claude"}",
3563
- "modelMap": { "cheap": "haiku", "mid": "sonnet", "strong": "opus" }
3564
- }
3565
- },
3566
- "architecture": "# Architecture\\n\\n<markdown content>",
3567
- "conventions": "# Conventions\\n\\n<markdown content>"
3568
- }
3569
-
3570
- CRITICAL rules for config.quality:
3571
- - Every command MUST start with "${basic.pm}" (e.g., "${basic.pm} typecheck", "${basic.pm} lint")
3572
- - Look at the package.json "scripts" section to find the correct script names
3573
- - testUnit must run ONLY unit tests \u2014 exclude integration and e2e tests. If there's a "test:unit" script use it. Otherwise use "test" but add exclude flags for int/e2e.
3574
- - If a script doesn't exist and can't be inferred, set the value to ""
3575
- - Do NOT invent commands that don't exist in package.json scripts
3576
-
3577
- Rules for architecture (markdown string):
3578
- - Be specific about THIS project
3579
- - Include: framework, language, database, testing, key directories, data flow
3580
- - Reference CLAUDE.md and .ai-docs/ if they exist
3581
- - Keep under 50 lines
3582
-
3583
- Rules for conventions (markdown string):
3584
- - Extract actual patterns from the project
3585
- - If CLAUDE.md exists, reference it
3586
- - If .ai-docs/ exists, reference it
3587
- - Keep under 30 lines
3588
-
3589
- Output ONLY valid JSON. No markdown fences. No explanation before or after.
3590
-
3591
- ${context}`;
3592
- console.log(" \u23F3 Analyzing project with Claude Code...");
3593
- try {
3594
- const output = execFileSync11("claude", [
3595
- "--print",
3596
- "--model",
3597
- "haiku",
3598
- "--dangerously-skip-permissions",
3599
- prompt
3600
- ], {
3601
- encoding: "utf-8",
3602
- timeout: 12e4,
3603
- cwd,
3604
- stdio: ["pipe", "pipe", "pipe"]
3605
- }).trim();
3606
- const cleaned = output.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
3607
- const parsed = JSON.parse(cleaned);
3608
- const config = parsed.config ?? {};
3609
- if (!config.git) config.git = {};
3610
- if (!config.github) config.github = {};
3611
- if (!config.paths) config.paths = {};
3612
- if (!config.agent) config.agent = {};
3613
- config["$schema"] = "https://raw.githubusercontent.com/aharonyaircohen/Kody-Engine-Lite/main/kody.config.schema.json";
3614
- config.git.defaultBranch = config.git.defaultBranch || basic.defaultBranch;
3615
- config.github.owner = config.github.owner || basic.owner;
3616
- config.github.repo = config.github.repo || basic.repo;
3617
- config.paths.taskDir = config.paths.taskDir || ".kody/tasks";
3618
- config.agent.runner = config.agent.runner || "claude-code";
3619
- config.agent.defaultRunner = config.agent.defaultRunner || "claude";
3620
- if (!config.agent.modelMap) {
3621
- config.agent.modelMap = { cheap: "haiku", mid: "sonnet", strong: "opus" };
3622
- }
3623
- validateQualityCommands(cwd, config, basic.pm);
3624
- return {
3625
- config,
3626
- architecture: parsed.architecture ?? "",
3627
- conventions: parsed.conventions ?? ""
3628
- };
3629
- } catch (err) {
3630
- console.log(" \u26A0 Smart detection failed, falling back to basic detection");
3631
- return {
3632
- config: buildFallbackConfig(cwd, basic),
3633
- architecture: "",
3634
- conventions: ""
3635
- };
3636
- }
3637
- }
3638
- function validateQualityCommands(cwd, config, pm) {
3639
- let scripts = {};
3640
- try {
3641
- const pkg = JSON.parse(fs21.readFileSync(path20.join(cwd, "package.json"), "utf-8"));
3642
- scripts = pkg.scripts ?? {};
3643
- } catch {
3644
- return;
3645
- }
3646
- const quality = config.quality ?? {};
3647
- const overrides = [
3648
- { key: "typecheck", preferred: ["typecheck", "type-check"] },
3649
- { key: "lint", preferred: ["lint"] },
3650
- { key: "lintFix", preferred: ["lint:fix", "lint-fix"] },
3651
- { key: "format", preferred: ["format:check", "format-check", "prettier:check"] },
3652
- { key: "formatFix", preferred: ["format", "format:fix", "format-fix"] },
3653
- { key: "testUnit", preferred: ["test:unit", "test-unit", "test:ci"] }
3654
- ];
3655
- for (const { key, preferred } of overrides) {
3656
- const match = preferred.find((s) => scripts[s]);
3657
- if (match) {
3658
- const correct = `${pm} ${match}`;
3659
- if (quality[key] !== correct) {
3660
- quality[key] = correct;
3661
- }
3662
- }
3663
- if (quality[key]) {
3664
- const scriptName = quality[key].replace(`${pm} `, "");
3665
- if (scriptName && !scripts[scriptName] && !scriptName.includes(" ")) {
3666
- quality[key] = "";
3667
- }
3668
- }
3669
- }
3670
- config.quality = quality;
3671
- }
3672
- function buildFallbackConfig(cwd, basic) {
3338
+ function buildConfig(cwd, basic) {
3673
3339
  const pkg = (() => {
3674
3340
  try {
3675
3341
  return JSON.parse(fs21.readFileSync(path20.join(cwd, "package.json"), "utf-8"));
@@ -3696,7 +3362,7 @@ function buildFallbackConfig(cwd, basic) {
3696
3362
  },
3697
3363
  git: { defaultBranch: basic.defaultBranch },
3698
3364
  github: { owner: basic.owner, repo: basic.repo },
3699
- paths: { taskDir: ".kody/tasks" },
3365
+ paths: { taskDir: ".tasks" },
3700
3366
  agent: {
3701
3367
  runner: "claude-code",
3702
3368
  defaultRunner: "claude",
@@ -3713,6 +3379,7 @@ function initCommand(opts) {
3713
3379
  `);
3714
3380
  console.log("\u2500\u2500 Files \u2500\u2500");
3715
3381
  const templatesDir = path20.join(PKG_ROOT, "templates");
3382
+ const basic = detectBasicConfig(cwd);
3716
3383
  const workflowSrc = path20.join(templatesDir, "kody.yml");
3717
3384
  const workflowDest = path20.join(cwd, ".github", "workflows", "kody.yml");
3718
3385
  if (!fs21.existsSync(workflowSrc)) {
@@ -3727,10 +3394,9 @@ function initCommand(opts) {
3727
3394
  console.log(" \u2713 .github/workflows/kody.yml");
3728
3395
  }
3729
3396
  const configDest = path20.join(cwd, "kody.config.json");
3730
- let smartResult = null;
3731
3397
  if (!fs21.existsSync(configDest) || opts.force) {
3732
- smartResult = smartInit(cwd);
3733
- fs21.writeFileSync(configDest, JSON.stringify(smartResult.config, null, 2) + "\n");
3398
+ const config = buildConfig(cwd, basic);
3399
+ fs21.writeFileSync(configDest, JSON.stringify(config, null, 2) + "\n");
3734
3400
  console.log(" \u2713 kody.config.json (auto-configured)");
3735
3401
  } else {
3736
3402
  console.log(" \u25CB kody.config.json (exists)");
@@ -3738,22 +3404,19 @@ function initCommand(opts) {
3738
3404
  const gitignorePath = path20.join(cwd, ".gitignore");
3739
3405
  if (fs21.existsSync(gitignorePath)) {
3740
3406
  const content = fs21.readFileSync(gitignorePath, "utf-8");
3741
- if (content.includes(".tasks/")) {
3742
- const updated = content.replace(/\n?\.tasks\/\n?/g, "\n");
3743
- fs21.writeFileSync(gitignorePath, updated);
3744
- console.log(" \u2713 .gitignore (removed legacy .tasks/ \u2014 tasks now committed in .kody/tasks/)");
3407
+ if (!content.includes(".tasks/")) {
3408
+ fs21.appendFileSync(gitignorePath, "\n.tasks/\n");
3409
+ console.log(" \u2713 .gitignore (added .tasks/)");
3745
3410
  } else {
3746
- console.log(" \u25CB .gitignore (ok)");
3411
+ console.log(" \u25CB .gitignore (.tasks/ already present)");
3747
3412
  }
3748
3413
  }
3749
3414
  console.log("\n\u2500\u2500 Prerequisites \u2500\u2500");
3750
3415
  const checks = [
3751
- checkCommand2("claude", ["--version"], "Install: npm i -g @anthropic-ai/claude-code"),
3752
3416
  checkCommand2("gh", ["--version"], "Install: https://cli.github.com"),
3753
3417
  checkCommand2("git", ["--version"], "Install git"),
3754
3418
  checkCommand2("node", ["--version"], "Install Node.js >= 22"),
3755
- checkCommand2("pnpm", ["--version"], "Install: npm i -g pnpm"),
3756
- checkFile(path20.join(cwd, "package.json"), "package.json", "Run: pnpm init")
3419
+ checkFile(path20.join(cwd, "package.json"), "package.json", `Run: ${basic.pm} init`)
3757
3420
  ];
3758
3421
  for (const c of checks) {
3759
3422
  if (c.ok) {
@@ -3767,8 +3430,9 @@ function initCommand(opts) {
3767
3430
  console.log(ghAuth.ok ? ` \u2713 ${ghAuth.name} (${ghAuth.detail})` : ` \u2717 ${ghAuth.name} \u2014 ${ghAuth.fix}`);
3768
3431
  const ghRepo = checkGhRepoAccess(cwd);
3769
3432
  console.log(ghRepo.ok ? ` \u2713 ${ghRepo.name} (${ghRepo.detail})` : ` \u2717 ${ghRepo.name} \u2014 ${ghRepo.fix}`);
3433
+ let repoSlug = "";
3770
3434
  if (ghRepo.ok && ghRepo.detail) {
3771
- const repoSlug = ghRepo.detail;
3435
+ repoSlug = ghRepo.detail;
3772
3436
  const secretChecks = [
3773
3437
  checkGhSecret(repoSlug, "ANTHROPIC_API_KEY")
3774
3438
  ];
@@ -3858,133 +3522,331 @@ function initCommand(opts) {
3858
3522
  console.log(" \u2717 kody.config.json \u2014 invalid JSON");
3859
3523
  }
3860
3524
  }
3861
- console.log("\n\u2500\u2500 Project Memory \u2500\u2500");
3525
+ console.log("\n\u2500\u2500 Git \u2500\u2500");
3526
+ const filesToCommit = [
3527
+ ".github/workflows/kody.yml",
3528
+ "kody.config.json"
3529
+ ].filter((f) => fs21.existsSync(path20.join(cwd, f)));
3530
+ if (filesToCommit.length > 0) {
3531
+ try {
3532
+ const fullPaths = filesToCommit.map((f) => path20.join(cwd, f));
3533
+ execFileSync11("npx", ["prettier", "--write", ...fullPaths], {
3534
+ cwd,
3535
+ encoding: "utf-8",
3536
+ timeout: 3e4,
3537
+ stdio: ["pipe", "pipe", "pipe"]
3538
+ });
3539
+ } catch {
3540
+ }
3541
+ }
3542
+ if (filesToCommit.length > 0) {
3543
+ try {
3544
+ execFileSync11("git", ["add", ...filesToCommit], { cwd, stdio: "pipe" });
3545
+ const staged = execFileSync11("git", ["diff", "--cached", "--name-only"], { cwd, encoding: "utf-8" }).trim();
3546
+ if (staged) {
3547
+ execFileSync11("git", ["commit", "-m", "chore: Add Kody Engine workflow and config\n\nAdd GitHub Actions workflow and auto-detected configuration for Kody Engine Lite."], { cwd, stdio: "pipe" });
3548
+ console.log(` \u2713 Committed: ${filesToCommit.join(", ")}`);
3549
+ try {
3550
+ execFileSync11("git", ["push"], { cwd, stdio: "pipe", timeout: 6e4 });
3551
+ console.log(" \u2713 Pushed to origin");
3552
+ } catch {
3553
+ console.log(" \u25CB Push failed \u2014 run 'git push' manually");
3554
+ }
3555
+ } else {
3556
+ console.log(" \u25CB No new changes to commit");
3557
+ }
3558
+ } catch (err) {
3559
+ console.log(` \u25CB Git commit skipped: ${err instanceof Error ? err.message : err}`);
3560
+ }
3561
+ }
3562
+ const allChecks = [...checks, ghAuth, ghRepo];
3563
+ const failed = allChecks.filter((c) => !c.ok);
3564
+ console.log("\n\u2500\u2500 Summary \u2500\u2500");
3565
+ if (failed.length === 0) {
3566
+ console.log(" \u2713 All checks passed! Ready to use.");
3567
+ console.log(`
3568
+ \u2500\u2500 Getting Started \u2500\u2500
3569
+
3570
+ 1. Bootstrap (optional but recommended):
3571
+ Create a GitHub issue comment with '@kody bootstrap'
3572
+ \u2192 Kody will analyze your repo and generate project-specific config
3573
+
3574
+ 2. First task:
3575
+ Create a GitHub issue describing work to do, then comment '@kody'
3576
+ \u2192 Kody picks it up and runs the full pipeline
3577
+
3578
+ Commands:
3579
+ @kody Run full pipeline on an issue
3580
+ @kody bootstrap Analyze repo, generate memory + step files
3581
+ @kody fix Fix build failures
3582
+ @kody review Review a PR
3583
+ `);
3584
+ } else {
3585
+ console.log(` \u26A0 ${failed.length} issue(s) to fix:`);
3586
+ for (const c of failed) {
3587
+ console.log(` \u2022 ${c.name}: ${c.fix}`);
3588
+ }
3589
+ console.log("");
3590
+ }
3591
+ }
3592
+ var STEP_STAGES = ["taskify", "plan", "build", "autofix", "review", "review-fix"];
3593
+ function gatherSampleSourceFiles(cwd, maxFiles = 3, maxCharsEach = 2e3) {
3594
+ const srcDir = path20.join(cwd, "src");
3595
+ const baseDir = fs21.existsSync(srcDir) ? srcDir : cwd;
3596
+ const results = [];
3597
+ function walk(dir) {
3598
+ const entries = [];
3599
+ try {
3600
+ for (const entry of fs21.readdirSync(dir, { withFileTypes: true })) {
3601
+ if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
3602
+ const full = path20.join(dir, entry.name);
3603
+ if (entry.isDirectory()) {
3604
+ entries.push(...walk(full));
3605
+ } else if (/\.(ts|js)$/.test(entry.name) && !/\.(test|spec|config|d)\.(ts|js)$/.test(entry.name)) {
3606
+ try {
3607
+ const stat = fs21.statSync(full);
3608
+ if (stat.size >= 200 && stat.size <= 5e3) {
3609
+ entries.push({ filePath: full, size: stat.size });
3610
+ }
3611
+ } catch {
3612
+ }
3613
+ }
3614
+ }
3615
+ } catch {
3616
+ }
3617
+ return entries;
3618
+ }
3619
+ const files = walk(baseDir).sort((a, b) => b.size - a.size).slice(0, maxFiles);
3620
+ for (const { filePath } of files) {
3621
+ const rel = path20.relative(cwd, filePath);
3622
+ const content = fs21.readFileSync(filePath, "utf-8").slice(0, maxCharsEach);
3623
+ results.push(`### File: ${rel}
3624
+ \`\`\`typescript
3625
+ ${content}
3626
+ \`\`\``);
3627
+ }
3628
+ return results.join("\n\n");
3629
+ }
3630
+ function bootstrapCommand() {
3631
+ const cwd = process.cwd();
3632
+ console.log(`
3633
+ \u{1F527} Kody Bootstrap \u2014 Generating project memory + step files
3634
+ `);
3635
+ const readIfExists = (rel, maxChars = 3e3) => {
3636
+ const p = path20.join(cwd, rel);
3637
+ if (fs21.existsSync(p)) return fs21.readFileSync(p, "utf-8").slice(0, maxChars);
3638
+ return null;
3639
+ };
3640
+ let repoContext = "";
3641
+ const pkgJson = readIfExists("package.json");
3642
+ if (pkgJson) repoContext += `## package.json
3643
+ ${pkgJson}
3644
+
3645
+ `;
3646
+ const tsconfig = readIfExists("tsconfig.json", 1e3);
3647
+ if (tsconfig) repoContext += `## tsconfig.json
3648
+ ${tsconfig}
3649
+
3650
+ `;
3651
+ const readme = readIfExists("README.md", 2e3);
3652
+ if (readme) repoContext += `## README.md (first 2000 chars)
3653
+ ${readme}
3654
+
3655
+ `;
3656
+ const claudeMd = readIfExists("CLAUDE.md", 3e3);
3657
+ if (claudeMd) repoContext += `## CLAUDE.md
3658
+ ${claudeMd}
3659
+
3660
+ `;
3661
+ const agentsMd = readIfExists("AGENTS.md", 3e3);
3662
+ if (agentsMd) repoContext += `## AGENTS.md
3663
+ ${agentsMd}
3664
+
3665
+ `;
3666
+ const sampleFiles = gatherSampleSourceFiles(cwd);
3667
+ if (sampleFiles) repoContext += `## Sample Source Files
3668
+ ${sampleFiles}
3669
+
3670
+ `;
3671
+ try {
3672
+ const topDirs = fs21.readdirSync(cwd, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules").map((e) => e.name);
3673
+ repoContext += `## Top-level directories
3674
+ ${topDirs.join(", ")}
3675
+
3676
+ `;
3677
+ const srcDir = path20.join(cwd, "src");
3678
+ if (fs21.existsSync(srcDir)) {
3679
+ const srcDirs = fs21.readdirSync(srcDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
3680
+ if (srcDirs.length > 0) repoContext += `## src/ subdirectories
3681
+ ${srcDirs.join(", ")}
3682
+
3683
+ `;
3684
+ }
3685
+ } catch {
3686
+ }
3687
+ const existingFiles = [];
3688
+ for (const f of [".env.example", "CLAUDE.md", ".ai-docs", "vitest.config.ts", "vitest.config.mts", "jest.config.ts", "playwright.config.ts", ".eslintrc.js", "eslint.config.mjs", ".prettierrc"]) {
3689
+ if (fs21.existsSync(path20.join(cwd, f))) existingFiles.push(f);
3690
+ }
3691
+ if (existingFiles.length) repoContext += `## Config files present
3692
+ ${existingFiles.join(", ")}
3693
+
3694
+ `;
3695
+ console.log("\u2500\u2500 Project Memory \u2500\u2500");
3862
3696
  const memoryDir = path20.join(cwd, ".kody", "memory");
3863
3697
  fs21.mkdirSync(memoryDir, { recursive: true });
3864
3698
  const archPath = path20.join(memoryDir, "architecture.md");
3865
3699
  const conventionsPath = path20.join(memoryDir, "conventions.md");
3866
- if (fs21.existsSync(archPath) && !opts.force) {
3867
- console.log(" \u25CB .kody/memory/architecture.md (exists, use --force to regenerate)");
3868
- } else if (smartResult?.architecture) {
3869
- fs21.writeFileSync(archPath, smartResult.architecture);
3870
- const lineCount = smartResult.architecture.split("\n").length;
3871
- console.log(` \u2713 .kody/memory/architecture.md (${lineCount} lines, LLM-generated)`);
3872
- } else {
3873
- const archItems = detectArchitecture(cwd);
3874
- if (archItems.length > 0) {
3700
+ const memoryPrompt = `You are analyzing a project to generate documentation for an autonomous SDLC pipeline.
3701
+
3702
+ Given this project context, output ONLY a JSON object with EXACTLY this structure:
3703
+
3704
+ {
3705
+ "architecture": "# Architecture\\n\\n<markdown content>",
3706
+ "conventions": "# Conventions\\n\\n<markdown content>"
3707
+ }
3708
+
3709
+ Rules for architecture (markdown string):
3710
+ - Be specific about THIS project
3711
+ - Include: framework, language, database, testing, key directories, data flow
3712
+ - Reference CLAUDE.md and .ai-docs/ if they exist
3713
+ - Keep under 50 lines
3714
+
3715
+ Rules for conventions (markdown string):
3716
+ - Extract actual patterns from the project
3717
+ - If CLAUDE.md exists, reference it
3718
+ - Keep under 30 lines
3719
+
3720
+ Output ONLY valid JSON. No markdown fences. No explanation.
3721
+
3722
+ ${repoContext}`;
3723
+ console.log(" \u23F3 Analyzing project...");
3724
+ try {
3725
+ const output = execFileSync11("claude", [
3726
+ "--print",
3727
+ "--model",
3728
+ "haiku",
3729
+ "--dangerously-skip-permissions",
3730
+ memoryPrompt
3731
+ ], {
3732
+ encoding: "utf-8",
3733
+ timeout: 9e4,
3734
+ cwd,
3735
+ stdio: ["pipe", "pipe", "pipe"]
3736
+ }).trim();
3737
+ const cleaned = output.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
3738
+ const parsed = JSON.parse(cleaned);
3739
+ if (parsed.architecture) {
3740
+ fs21.writeFileSync(archPath, parsed.architecture);
3741
+ const lineCount = parsed.architecture.split("\n").length;
3742
+ console.log(` \u2713 .kody/memory/architecture.md (${lineCount} lines)`);
3743
+ }
3744
+ if (parsed.conventions) {
3745
+ fs21.writeFileSync(conventionsPath, parsed.conventions);
3746
+ const lineCount = parsed.conventions.split("\n").length;
3747
+ console.log(` \u2713 .kody/memory/conventions.md (${lineCount} lines)`);
3748
+ }
3749
+ } catch {
3750
+ console.log(" \u26A0 LLM analysis failed \u2014 creating basic memory files");
3751
+ const detected = detectArchitectureBasic(cwd);
3752
+ if (detected.length > 0) {
3875
3753
  const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
3876
3754
  fs21.writeFileSync(archPath, `# Architecture (auto-detected ${timestamp2})
3877
3755
 
3878
3756
  ## Overview
3879
- ${archItems.join("\n")}
3757
+ ${detected.join("\n")}
3880
3758
  `);
3881
- console.log(` \u2713 .kody/memory/architecture.md (${archItems.length} items, basic detection)`);
3882
- } else {
3883
- console.log(" \u25CB No architecture detected");
3759
+ console.log(` \u2713 .kody/memory/architecture.md (${detected.length} items, basic detection)`);
3884
3760
  }
3885
- }
3886
- if (fs21.existsSync(conventionsPath) && !opts.force) {
3887
- console.log(" \u25CB .kody/memory/conventions.md (exists, use --force to regenerate)");
3888
- } else if (smartResult?.conventions) {
3889
- fs21.writeFileSync(conventionsPath, smartResult.conventions);
3890
- const lineCount = smartResult.conventions.split("\n").length;
3891
- console.log(` \u2713 .kody/memory/conventions.md (${lineCount} lines, LLM-generated)`);
3892
- } else {
3893
3761
  fs21.writeFileSync(conventionsPath, "# Conventions\n\n<!-- Auto-learned conventions will be appended here -->\n");
3894
3762
  console.log(" \u2713 .kody/memory/conventions.md (seed)");
3895
3763
  }
3896
3764
  console.log("\n\u2500\u2500 Step Files \u2500\u2500");
3897
3765
  const stepsDir = path20.join(cwd, ".kody", "steps");
3898
- const stepsExist = fs21.existsSync(stepsDir) && fs21.readdirSync(stepsDir).some((f) => f.endsWith(".md"));
3899
- if (stepsExist && !opts.force) {
3900
- console.log(" \u25CB .kody/steps/ (exists, use --force to regenerate)");
3901
- } else {
3902
- fs21.mkdirSync(stepsDir, { recursive: true });
3903
- const readIfExistsForSteps = (rel, maxChars = 3e3) => {
3904
- const p = path20.join(cwd, rel);
3905
- if (fs21.existsSync(p)) return fs21.readFileSync(p, "utf-8").slice(0, maxChars);
3906
- return null;
3907
- };
3908
- let repoContext = "";
3909
- const pkgForSteps = readIfExistsForSteps("package.json");
3910
- if (pkgForSteps) repoContext += `## package.json
3911
- ${pkgForSteps}
3766
+ fs21.mkdirSync(stepsDir, { recursive: true });
3767
+ const arch = fs21.existsSync(archPath) ? fs21.readFileSync(archPath, "utf-8") : "";
3768
+ const conv = fs21.existsSync(conventionsPath) ? fs21.readFileSync(conventionsPath, "utf-8") : "";
3769
+ console.log(" \u23F3 Customizing step files...");
3770
+ let stepCount = 0;
3771
+ for (const stage of STEP_STAGES) {
3772
+ const templatePath = path20.join(PKG_ROOT, "prompts", `${stage}.md`);
3773
+ if (!fs21.existsSync(templatePath)) {
3774
+ console.log(` \u2717 ${stage}.md \u2014 template not found in engine`);
3775
+ continue;
3776
+ }
3777
+ const defaultPrompt = fs21.readFileSync(templatePath, "utf-8");
3778
+ const contextPlaceholder = "{{TASK_CONTEXT}}";
3779
+ const placeholderIdx = defaultPrompt.indexOf(contextPlaceholder);
3780
+ if (placeholderIdx === -1) {
3781
+ fs21.copyFileSync(templatePath, path20.join(stepsDir, `${stage}.md`));
3782
+ stepCount++;
3783
+ console.log(` \u2713 ${stage}.md`);
3784
+ continue;
3785
+ }
3786
+ const beforePlaceholder = defaultPrompt.slice(0, placeholderIdx).trimEnd();
3787
+ const afterPlaceholder = defaultPrompt.slice(placeholderIdx);
3788
+ const customizationPrompt = `You are customizing a Kody pipeline prompt for a specific repository.
3912
3789
 
3913
- `;
3914
- const readmeForSteps = readIfExistsForSteps("README.md", 2e3);
3915
- if (readmeForSteps) repoContext += `## README.md
3916
- ${readmeForSteps}
3790
+ ## Your Task
3791
+ Take the prompt template below and APPEND repository-specific sections to it.
3917
3792
 
3918
- `;
3919
- const claudeMdForSteps = readIfExistsForSteps("CLAUDE.md", 3e3);
3920
- if (claudeMdForSteps) repoContext += `## CLAUDE.md
3921
- ${claudeMdForSteps}
3793
+ ## Rules
3794
+ 1. Output the ENTIRE original prompt template UNCHANGED first \u2014 copy it exactly, character for character.
3795
+ 2. Then APPEND these three new sections at the end:
3796
+ - ## Repo Patterns \u2014 Real code examples from this repo that demonstrate the patterns to follow. Include specific file paths, function signatures, and brief code snippets.
3797
+ - ## Improvement Areas \u2014 Gaps or anti-patterns found in the codebase. Be specific with file paths.
3798
+ - ## Acceptance Criteria \u2014 A concrete checklist (markdown checkboxes) for "done" in this repo.
3799
+ 3. Be SPECIFIC \u2014 reference actual file paths, function names, and conventions from the repo context.
3800
+ 4. Keep each appended section concise (10-20 lines max).
3801
+ 5. Output ONLY the customized prompt markdown. No explanation before or after.
3802
+ 6. Do NOT include the text "${contextPlaceholder}" \u2014 it will be appended automatically after your output.
3922
3803
 
3923
- `;
3924
- const agentsMdForSteps = readIfExistsForSteps("AGENTS.md", 3e3);
3925
- if (agentsMdForSteps) repoContext += `## AGENTS.md
3926
- ${agentsMdForSteps}
3804
+ ## Stage Being Customized
3805
+ Stage: ${stage}
3927
3806
 
3928
- `;
3929
- const sampleFiles = gatherSampleSourceFiles(cwd);
3930
- if (sampleFiles) repoContext += `## Sample Source Files
3931
- ${sampleFiles}
3807
+ ## Prompt Template (output this EXACTLY, then append your sections)
3808
+ ${beforePlaceholder}
3932
3809
 
3933
- `;
3934
- try {
3935
- const srcEntries = fs21.readdirSync(path20.join(cwd, "src"), { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
3936
- if (srcEntries.length > 0) repoContext += `## src/ structure
3937
- ${srcEntries.join(", ")}
3810
+ ## Repository Context
3938
3811
 
3939
- `;
3812
+ ### Architecture
3813
+ ${arch}
3814
+
3815
+ ### Conventions
3816
+ ${conv}
3817
+
3818
+ ### Project Details
3819
+ ${repoContext}
3820
+
3821
+ REMINDER: Output the full prompt template first (unchanged), then your three appended sections. Do NOT include "${contextPlaceholder}".`;
3822
+ try {
3823
+ const output = execFileSync11("claude", [
3824
+ "--print",
3825
+ "--model",
3826
+ "haiku",
3827
+ "--dangerously-skip-permissions",
3828
+ customizationPrompt
3829
+ ], {
3830
+ encoding: "utf-8",
3831
+ timeout: 9e4,
3832
+ cwd,
3833
+ stdio: ["pipe", "pipe", "pipe"]
3834
+ }).trim();
3835
+ let cleaned = output.replace(/^```(?:markdown|md)?\s*\n?/, "").replace(/\n?```\s*$/, "");
3836
+ cleaned = cleaned.replace(/\n*\{\{TASK_CONTEXT\}\}\s*$/, "").trimEnd();
3837
+ const finalPrompt = cleaned + "\n\n" + afterPlaceholder;
3838
+ fs21.writeFileSync(path20.join(stepsDir, `${stage}.md`), finalPrompt);
3839
+ stepCount++;
3840
+ console.log(` \u2713 ${stage}.md`);
3940
3841
  } catch {
3842
+ console.log(` \u26A0 ${stage}.md \u2014 customization failed, using default template`);
3843
+ fs21.copyFileSync(templatePath, path20.join(stepsDir, `${stage}.md`));
3844
+ stepCount++;
3941
3845
  }
3942
- const arch = fs21.existsSync(archPath) ? fs21.readFileSync(archPath, "utf-8") : "";
3943
- const conv = fs21.existsSync(conventionsPath) ? fs21.readFileSync(conventionsPath, "utf-8") : "";
3944
- console.log(" \u23F3 Customizing step files with Claude (sonnet)...");
3945
- let stepCount = 0;
3946
- for (const stage of STEP_STAGES) {
3947
- const templatePath = path20.join(PKG_ROOT, "prompts", `${stage}.md`);
3948
- if (!fs21.existsSync(templatePath)) {
3949
- console.log(` \u2717 ${stage}.md \u2014 template not found in engine`);
3950
- continue;
3951
- }
3952
- const defaultPrompt = fs21.readFileSync(templatePath, "utf-8");
3953
- const customizationPrompt = buildStepCustomizationPrompt(stage, defaultPrompt, repoContext, arch, conv);
3954
- try {
3955
- const output = execFileSync11("claude", [
3956
- "--print",
3957
- "--model",
3958
- "sonnet",
3959
- "--dangerously-skip-permissions",
3960
- customizationPrompt
3961
- ], {
3962
- encoding: "utf-8",
3963
- timeout: 12e4,
3964
- cwd,
3965
- stdio: ["pipe", "pipe", "pipe"]
3966
- }).trim();
3967
- const cleaned = output.replace(/^```(?:markdown|md)?\s*\n?/, "").replace(/\n?```\s*$/, "");
3968
- if (!cleaned.includes("{{TASK_CONTEXT}}")) {
3969
- console.log(` \u26A0 ${stage}.md \u2014 AI dropped {{TASK_CONTEXT}}, using default template`);
3970
- fs21.writeFileSync(path20.join(stepsDir, `${stage}.md`), defaultPrompt);
3971
- } else {
3972
- fs21.writeFileSync(path20.join(stepsDir, `${stage}.md`), cleaned);
3973
- }
3974
- stepCount++;
3975
- console.log(` \u2713 ${stage}.md`);
3976
- } catch (err) {
3977
- console.log(` \u26A0 ${stage}.md \u2014 customization failed, using default template`);
3978
- fs21.copyFileSync(templatePath, path20.join(stepsDir, `${stage}.md`));
3979
- stepCount++;
3980
- }
3981
- }
3982
- console.log(` \u2713 Generated ${stepCount} step files in .kody/steps/`);
3983
3846
  }
3847
+ console.log(` \u2713 Generated ${stepCount} step files in .kody/steps/`);
3984
3848
  console.log("\n\u2500\u2500 Git \u2500\u2500");
3985
3849
  const filesToCommit = [
3986
- ".github/workflows/kody.yml",
3987
- "kody.config.json",
3988
3850
  ".kody/memory/architecture.md",
3989
3851
  ".kody/memory/conventions.md"
3990
3852
  ].filter((f) => fs21.existsSync(path20.join(cwd, f)));
@@ -3996,42 +3858,125 @@ ${srcEntries.join(", ")}
3996
3858
  }
3997
3859
  if (filesToCommit.length > 0) {
3998
3860
  try {
3999
- execFileSync11("git", ["add", ...filesToCommit], { cwd, stdio: "pipe" });
4000
- const staged = execFileSync11("git", ["diff", "--cached", "--name-only"], { cwd, encoding: "utf-8" }).trim();
4001
- if (staged) {
4002
- execFileSync11("git", ["commit", "--no-gpg-sign", "-m", "chore: add kody engine"], { cwd, stdio: "pipe" });
4003
- console.log(` \u2713 Committed: ${filesToCommit.join(", ")}`);
4004
- try {
4005
- execFileSync11("git", ["push"], { cwd, stdio: "pipe", timeout: 3e4 });
4006
- console.log(" \u2713 Pushed to origin");
4007
- } catch {
4008
- console.log(" \u25CB Push failed \u2014 run 'git push' manually");
3861
+ const fullPaths = filesToCommit.map((f) => path20.join(cwd, f));
3862
+ for (let pass = 0; pass < 2; pass++) {
3863
+ execFileSync11("npx", ["prettier", "--write", ...fullPaths], {
3864
+ cwd,
3865
+ encoding: "utf-8",
3866
+ timeout: 3e4,
3867
+ stdio: ["pipe", "pipe", "pipe"]
3868
+ });
3869
+ }
3870
+ console.log(" \u2713 Formatted files with Prettier");
3871
+ } catch {
3872
+ }
3873
+ }
3874
+ const isCI3 = !!process.env.GITHUB_ACTIONS;
3875
+ if (filesToCommit.length > 0) {
3876
+ try {
3877
+ if (isCI3) {
3878
+ const branchName = `kody/bootstrap-${Date.now()}`;
3879
+ execFileSync11("git", ["checkout", "-b", branchName], { cwd, stdio: "pipe" });
3880
+ execFileSync11("git", ["add", ...filesToCommit], { cwd, stdio: "pipe" });
3881
+ const staged = execFileSync11("git", ["diff", "--cached", "--name-only"], { cwd, encoding: "utf-8" }).trim();
3882
+ if (staged) {
3883
+ execFileSync11("git", ["commit", "-m", "chore: Add Kody project memory and step files\n\nBootstrap Kody Engine with project-specific architecture, conventions, and pipeline step files."], { cwd, stdio: "pipe" });
3884
+ execFileSync11("git", ["push", "-u", "origin", branchName], { cwd, stdio: "pipe", timeout: 6e4 });
3885
+ console.log(` \u2713 Pushed branch: ${branchName}`);
3886
+ let baseBranch = "main";
3887
+ try {
3888
+ const configPath = path20.join(cwd, "kody.config.json");
3889
+ if (fs21.existsSync(configPath)) {
3890
+ const config = JSON.parse(fs21.readFileSync(configPath, "utf-8"));
3891
+ baseBranch = config.git?.defaultBranch ?? "main";
3892
+ }
3893
+ } catch {
3894
+ }
3895
+ try {
3896
+ const prUrl = execFileSync11("gh", [
3897
+ "pr",
3898
+ "create",
3899
+ "--title",
3900
+ "chore: Bootstrap Kody Engine",
3901
+ "--body",
3902
+ "## Summary\n\n- Add project memory (architecture + conventions)\n- Add customized pipeline step files\n\nGenerated by `@kody bootstrap`.",
3903
+ "--base",
3904
+ baseBranch,
3905
+ "--head",
3906
+ branchName
3907
+ ], {
3908
+ cwd,
3909
+ encoding: "utf-8",
3910
+ timeout: 3e4,
3911
+ stdio: ["pipe", "pipe", "pipe"]
3912
+ }).trim();
3913
+ console.log(` \u2713 Created PR: ${prUrl}`);
3914
+ } catch (prErr) {
3915
+ console.log(` \u25CB PR creation failed: ${prErr instanceof Error ? prErr.message : prErr}`);
3916
+ }
3917
+ } else {
3918
+ console.log(" \u25CB No new changes to commit");
4009
3919
  }
4010
3920
  } else {
4011
- console.log(" \u25CB No new changes to commit");
3921
+ execFileSync11("git", ["add", ...filesToCommit], { cwd, stdio: "pipe" });
3922
+ const staged = execFileSync11("git", ["diff", "--cached", "--name-only"], { cwd, encoding: "utf-8" }).trim();
3923
+ if (staged) {
3924
+ execFileSync11("git", ["commit", "-m", "chore: Add Kody project memory and step files\n\nBootstrap Kody Engine with project-specific architecture, conventions, and pipeline step files."], { cwd, stdio: "pipe" });
3925
+ console.log(` \u2713 Committed: ${filesToCommit.join(", ")}`);
3926
+ try {
3927
+ execFileSync11("git", ["push"], { cwd, stdio: "pipe", timeout: 6e4 });
3928
+ console.log(" \u2713 Pushed to origin");
3929
+ } catch {
3930
+ console.log(" \u25CB Push failed \u2014 run 'git push' manually");
3931
+ }
3932
+ } else {
3933
+ console.log(" \u25CB No new changes to commit");
3934
+ }
4012
3935
  }
4013
3936
  } catch (err) {
4014
3937
  console.log(` \u25CB Git commit skipped: ${err instanceof Error ? err.message : err}`);
4015
3938
  }
4016
3939
  }
4017
- const allChecks = [...checks, ghAuth, ghRepo];
4018
- const failed = allChecks.filter((c) => !c.ok);
4019
- console.log("\n\u2500\u2500 Summary \u2500\u2500");
4020
- if (failed.length === 0) {
4021
- console.log(" \u2713 All checks passed! Ready to use.");
4022
- console.log("\n Next: Comment '@kody' on a GitHub issue");
4023
- } else {
4024
- console.log(` \u26A0 ${failed.length} issue(s) to fix:`);
4025
- for (const c of failed) {
4026
- console.log(` \u2022 ${c.name}: ${c.fix}`);
3940
+ console.log("\n\u2500\u2500 Done \u2500\u2500");
3941
+ console.log(" \u2713 Project bootstrap complete!");
3942
+ console.log(" Kody now has project-specific memory and customized step files.\n");
3943
+ }
3944
+ function detectArchitectureBasic(cwd) {
3945
+ const detected = [];
3946
+ const pkgPath = path20.join(cwd, "package.json");
3947
+ if (fs21.existsSync(pkgPath)) {
3948
+ try {
3949
+ const pkg = JSON.parse(fs21.readFileSync(pkgPath, "utf-8"));
3950
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
3951
+ if (allDeps.next) detected.push(`- Framework: Next.js ${allDeps.next}`);
3952
+ else if (allDeps.react) detected.push(`- Framework: React ${allDeps.react}`);
3953
+ else if (allDeps.express) detected.push(`- Framework: Express ${allDeps.express}`);
3954
+ else if (allDeps.fastify) detected.push(`- Framework: Fastify ${allDeps.fastify}`);
3955
+ else if (allDeps.hono) detected.push(`- Framework: Hono ${allDeps.hono}`);
3956
+ if (allDeps.typescript) detected.push(`- Language: TypeScript ${allDeps.typescript}`);
3957
+ if (allDeps.vitest) detected.push(`- Testing: vitest ${allDeps.vitest}`);
3958
+ else if (allDeps.jest) detected.push(`- Testing: jest ${allDeps.jest}`);
3959
+ if (allDeps.eslint) detected.push(`- Linting: eslint ${allDeps.eslint}`);
3960
+ if (allDeps.prettier) detected.push(`- Formatting: prettier ${allDeps.prettier}`);
3961
+ if (allDeps.prisma || allDeps["@prisma/client"]) detected.push("- ORM: Prisma");
3962
+ if (allDeps["drizzle-orm"]) detected.push("- ORM: Drizzle");
3963
+ if (allDeps.payload || allDeps["@payloadcms/next"]) detected.push("- CMS: Payload CMS");
3964
+ if (allDeps.tailwindcss) detected.push(`- CSS: Tailwind CSS ${allDeps.tailwindcss}`);
3965
+ if (fs21.existsSync(path20.join(cwd, "pnpm-lock.yaml"))) detected.push("- Package manager: pnpm");
3966
+ else if (fs21.existsSync(path20.join(cwd, "yarn.lock"))) detected.push("- Package manager: yarn");
3967
+ else if (fs21.existsSync(path20.join(cwd, "bun.lockb"))) detected.push("- Package manager: bun");
3968
+ else if (fs21.existsSync(path20.join(cwd, "package-lock.json"))) detected.push("- Package manager: npm");
3969
+ } catch {
4027
3970
  }
4028
3971
  }
4029
- console.log("");
3972
+ return detected;
4030
3973
  }
4031
3974
  var args = process.argv.slice(2);
4032
3975
  var command = args[0];
4033
3976
  if (command === "init") {
4034
3977
  initCommand({ force: args.includes("--force") });
3978
+ } else if (command === "bootstrap") {
3979
+ bootstrapCommand();
4035
3980
  } else if (command === "version" || command === "--version" || command === "-v") {
4036
3981
  console.log(getVersion());
4037
3982
  } else {