@kody-ade/kody-engine-lite 0.1.54 → 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 -473
  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 +9 -8
  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);
@@ -3051,7 +3036,7 @@ ${issue.body ?? ""}`;
3051
3036
  }
3052
3037
  }
3053
3038
  if (!fs20.existsSync(taskMdPath)) {
3054
- 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.");
3055
3040
  process.exit(1);
3056
3041
  }
3057
3042
  if (input.command === "fix" && !input.fromStage) {
@@ -3306,135 +3291,6 @@ function checkGhSecret(repoSlug, secretName) {
3306
3291
  };
3307
3292
  }
3308
3293
  }
3309
- function detectArchitecture(cwd) {
3310
- const detected = [];
3311
- const pkgPath = path20.join(cwd, "package.json");
3312
- if (fs21.existsSync(pkgPath)) {
3313
- try {
3314
- const pkg = JSON.parse(fs21.readFileSync(pkgPath, "utf-8"));
3315
- const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
3316
- if (allDeps.next) detected.push(`- Framework: Next.js ${allDeps.next}`);
3317
- else if (allDeps.react) detected.push(`- Framework: React ${allDeps.react}`);
3318
- else if (allDeps.express) detected.push(`- Framework: Express ${allDeps.express}`);
3319
- else if (allDeps.fastify) detected.push(`- Framework: Fastify ${allDeps.fastify}`);
3320
- else if (allDeps.hono) detected.push(`- Framework: Hono ${allDeps.hono}`);
3321
- if (allDeps.typescript) detected.push(`- Language: TypeScript ${allDeps.typescript}`);
3322
- if (allDeps.vitest) detected.push(`- Testing: vitest ${allDeps.vitest}`);
3323
- else if (allDeps.jest) detected.push(`- Testing: jest ${allDeps.jest}`);
3324
- else if (allDeps.mocha) detected.push(`- Testing: mocha ${allDeps.mocha}`);
3325
- if (allDeps.eslint) detected.push(`- Linting: eslint ${allDeps.eslint}`);
3326
- if (allDeps.prettier) detected.push(`- Formatting: prettier ${allDeps.prettier}`);
3327
- if (allDeps.biome || allDeps["@biomejs/biome"]) detected.push("- Formatting: biome");
3328
- if (allDeps.prisma || allDeps["@prisma/client"]) detected.push("- ORM: Prisma");
3329
- if (allDeps["drizzle-orm"]) detected.push("- ORM: Drizzle");
3330
- if (allDeps.pg || allDeps.postgres) detected.push("- Database: PostgreSQL");
3331
- if (allDeps.mongodb || allDeps.mongoose) detected.push("- Database: MongoDB");
3332
- if (allDeps.redis || allDeps.ioredis) detected.push("- Cache: Redis");
3333
- if (allDeps.payload || allDeps["@payloadcms/next"]) detected.push("- CMS: Payload CMS");
3334
- if (allDeps.tailwindcss) detected.push(`- CSS: Tailwind CSS ${allDeps.tailwindcss}`);
3335
- if (pkg.type === "module") detected.push("- Module system: ESM");
3336
- else detected.push("- Module system: CommonJS");
3337
- if (fs21.existsSync(path20.join(cwd, "pnpm-lock.yaml"))) detected.push("- Package manager: pnpm");
3338
- else if (fs21.existsSync(path20.join(cwd, "yarn.lock"))) detected.push("- Package manager: yarn");
3339
- else if (fs21.existsSync(path20.join(cwd, "bun.lockb"))) detected.push("- Package manager: bun");
3340
- else if (fs21.existsSync(path20.join(cwd, "package-lock.json"))) detected.push("- Package manager: npm");
3341
- } catch {
3342
- }
3343
- }
3344
- try {
3345
- const entries = fs21.readdirSync(cwd, { withFileTypes: true });
3346
- const dirs = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules").map((e) => e.name);
3347
- if (dirs.length > 0) detected.push(`- Top-level directories: ${dirs.join(", ")}`);
3348
- } catch {
3349
- }
3350
- const srcDir = path20.join(cwd, "src");
3351
- if (fs21.existsSync(srcDir)) {
3352
- try {
3353
- const srcEntries = fs21.readdirSync(srcDir, { withFileTypes: true });
3354
- const srcDirs = srcEntries.filter((e) => e.isDirectory()).map((e) => e.name);
3355
- if (srcDirs.length > 0) detected.push(`- src/ structure: ${srcDirs.join(", ")}`);
3356
- } catch {
3357
- }
3358
- }
3359
- const configs = [];
3360
- if (fs21.existsSync(path20.join(cwd, "tsconfig.json"))) configs.push("tsconfig.json");
3361
- if (fs21.existsSync(path20.join(cwd, "docker-compose.yml")) || fs21.existsSync(path20.join(cwd, "docker-compose.yaml"))) configs.push("docker-compose");
3362
- if (fs21.existsSync(path20.join(cwd, "Dockerfile"))) configs.push("Dockerfile");
3363
- if (fs21.existsSync(path20.join(cwd, ".env")) || fs21.existsSync(path20.join(cwd, ".env.local"))) configs.push(".env");
3364
- if (configs.length > 0) detected.push(`- Config files: ${configs.join(", ")}`);
3365
- return detected;
3366
- }
3367
- var STEP_STAGES = ["taskify", "plan", "build", "autofix", "review", "review-fix"];
3368
- function gatherSampleSourceFiles(cwd, maxFiles = 3, maxCharsEach = 2e3) {
3369
- const srcDir = path20.join(cwd, "src");
3370
- const baseDir = fs21.existsSync(srcDir) ? srcDir : cwd;
3371
- const results = [];
3372
- function walk(dir) {
3373
- const entries = [];
3374
- try {
3375
- for (const entry of fs21.readdirSync(dir, { withFileTypes: true })) {
3376
- if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
3377
- const full = path20.join(dir, entry.name);
3378
- if (entry.isDirectory()) {
3379
- entries.push(...walk(full));
3380
- } else if (/\.(ts|js)$/.test(entry.name) && !/\.(test|spec|config|d)\.(ts|js)$/.test(entry.name)) {
3381
- try {
3382
- const stat = fs21.statSync(full);
3383
- if (stat.size >= 200 && stat.size <= 5e3) {
3384
- entries.push({ filePath: full, size: stat.size });
3385
- }
3386
- } catch {
3387
- }
3388
- }
3389
- }
3390
- } catch {
3391
- }
3392
- return entries;
3393
- }
3394
- const files = walk(baseDir).sort((a, b) => b.size - a.size).slice(0, maxFiles);
3395
- for (const { filePath } of files) {
3396
- const rel = path20.relative(cwd, filePath);
3397
- const content = fs21.readFileSync(filePath, "utf-8").slice(0, maxCharsEach);
3398
- results.push(`### File: ${rel}
3399
- \`\`\`typescript
3400
- ${content}
3401
- \`\`\``);
3402
- }
3403
- return results.join("\n\n");
3404
- }
3405
- function buildStepCustomizationPrompt(stageName, defaultPrompt, repoContext, architecture, conventions) {
3406
- return `You are customizing a Kody pipeline prompt for a specific repository.
3407
-
3408
- ## Your Task
3409
- Take the default prompt template below and produce a CUSTOMIZED version tailored to this specific repository.
3410
-
3411
- ## Rules
3412
- 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.
3413
- 2. APPEND three new sections after the original content but BEFORE the {{TASK_CONTEXT}} line:
3414
- - ## 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.
3415
- - ## 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.
3416
- - ## Acceptance Criteria \u2014 A concrete checklist (using markdown checkboxes) that defines "done" for this stage in this specific repo.
3417
- 3. Be SPECIFIC \u2014 reference actual file paths, function names, and conventions from the repo context provided below.
3418
- 4. Keep each appended section concise (10-20 lines max).
3419
- 5. Output ONLY the complete customized prompt markdown. No explanation before or after.
3420
-
3421
- ## Stage Being Customized
3422
- Stage: ${stageName}
3423
-
3424
- ## Default Prompt Template
3425
- ${defaultPrompt}
3426
-
3427
- ## Repository Context
3428
-
3429
- ### Architecture
3430
- ${architecture}
3431
-
3432
- ### Conventions
3433
- ${conventions}
3434
-
3435
- ### Project Details
3436
- ${repoContext}`;
3437
- }
3438
3294
  function detectBasicConfig(cwd) {
3439
3295
  let pm = "pnpm";
3440
3296
  if (fs21.existsSync(path20.join(cwd, "yarn.lock"))) pm = "yarn";
@@ -3479,193 +3335,7 @@ function detectBasicConfig(cwd) {
3479
3335
  }
3480
3336
  return { defaultBranch, owner, repo, pm };
3481
3337
  }
3482
- function smartInit(cwd) {
3483
- const basic = detectBasicConfig(cwd);
3484
- let context = "";
3485
- const readIfExists = (rel, maxChars = 3e3) => {
3486
- const p = path20.join(cwd, rel);
3487
- if (fs21.existsSync(p)) {
3488
- const content = fs21.readFileSync(p, "utf-8");
3489
- return content.slice(0, maxChars);
3490
- }
3491
- return null;
3492
- };
3493
- const pkgJson = readIfExists("package.json");
3494
- if (pkgJson) context += `## package.json
3495
- ${pkgJson}
3496
-
3497
- `;
3498
- const tsconfig = readIfExists("tsconfig.json", 1e3);
3499
- if (tsconfig) context += `## tsconfig.json
3500
- ${tsconfig}
3501
-
3502
- `;
3503
- const readme = readIfExists("README.md", 2e3);
3504
- if (readme) context += `## README.md (first 2000 chars)
3505
- ${readme}
3506
-
3507
- `;
3508
- const claudeMd = readIfExists("CLAUDE.md", 3e3);
3509
- if (claudeMd) context += `## CLAUDE.md
3510
- ${claudeMd}
3511
-
3512
- `;
3513
- try {
3514
- const topDirs = fs21.readdirSync(cwd, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules").map((e) => e.name);
3515
- context += `## Top-level directories
3516
- ${topDirs.join(", ")}
3517
-
3518
- `;
3519
- const srcDir = path20.join(cwd, "src");
3520
- if (fs21.existsSync(srcDir)) {
3521
- const srcDirs = fs21.readdirSync(srcDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
3522
- context += `## src/ subdirectories
3523
- ${srcDirs.join(", ")}
3524
-
3525
- `;
3526
- }
3527
- } catch {
3528
- }
3529
- const existingFiles = [];
3530
- 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"]) {
3531
- if (fs21.existsSync(path20.join(cwd, f))) existingFiles.push(f);
3532
- }
3533
- if (existingFiles.length) context += `## Config files present
3534
- ${existingFiles.join(", ")}
3535
-
3536
- `;
3537
- context += `## Detected: package manager=${basic.pm}, default branch=${basic.defaultBranch}, github=${basic.owner}/${basic.repo}
3538
- `;
3539
- const prompt = `You are analyzing a project to configure Kody (an autonomous SDLC pipeline).
3540
-
3541
- Given this project context, output ONLY a JSON object with EXACTLY this structure:
3542
-
3543
- {
3544
- "config": {
3545
- "quality": {
3546
- "typecheck": "${basic.pm} <script or command>",
3547
- "lint": "${basic.pm} <script or command>",
3548
- "lintFix": "${basic.pm} <script or command>",
3549
- "format": "${basic.pm} <script or command>",
3550
- "formatFix": "${basic.pm} <script or command>",
3551
- "testUnit": "${basic.pm} <script or command>"
3552
- },
3553
- "git": { "defaultBranch": "${basic.defaultBranch}" },
3554
- "github": { "owner": "${basic.owner}", "repo": "${basic.repo}" },
3555
- "paths": { "taskDir": ".kody/tasks" },
3556
- "agent": {
3557
- "runner": "${"claude-code"}",
3558
- "defaultRunner": "${"claude"}",
3559
- "modelMap": { "cheap": "haiku", "mid": "sonnet", "strong": "opus" }
3560
- }
3561
- },
3562
- "architecture": "# Architecture\\n\\n<markdown content>",
3563
- "conventions": "# Conventions\\n\\n<markdown content>"
3564
- }
3565
-
3566
- CRITICAL rules for config.quality:
3567
- - Every command MUST start with "${basic.pm}" (e.g., "${basic.pm} typecheck", "${basic.pm} lint")
3568
- - Look at the package.json "scripts" section to find the correct script names
3569
- - 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.
3570
- - If a script doesn't exist and can't be inferred, set the value to ""
3571
- - Do NOT invent commands that don't exist in package.json scripts
3572
-
3573
- Rules for architecture (markdown string):
3574
- - Be specific about THIS project
3575
- - Include: framework, language, database, testing, key directories, data flow
3576
- - Reference CLAUDE.md and .ai-docs/ if they exist
3577
- - Keep under 50 lines
3578
-
3579
- Rules for conventions (markdown string):
3580
- - Extract actual patterns from the project
3581
- - If CLAUDE.md exists, reference it
3582
- - If .ai-docs/ exists, reference it
3583
- - Keep under 30 lines
3584
-
3585
- Output ONLY valid JSON. No markdown fences. No explanation before or after.
3586
-
3587
- ${context}`;
3588
- console.log(" \u23F3 Analyzing project with Claude Code...");
3589
- try {
3590
- const output = execFileSync11("claude", [
3591
- "--print",
3592
- "--model",
3593
- "haiku",
3594
- "--dangerously-skip-permissions",
3595
- prompt
3596
- ], {
3597
- encoding: "utf-8",
3598
- timeout: 12e4,
3599
- cwd,
3600
- stdio: ["pipe", "pipe", "pipe"]
3601
- }).trim();
3602
- const cleaned = output.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
3603
- const parsed = JSON.parse(cleaned);
3604
- const config = parsed.config ?? {};
3605
- if (!config.git) config.git = {};
3606
- if (!config.github) config.github = {};
3607
- if (!config.paths) config.paths = {};
3608
- if (!config.agent) config.agent = {};
3609
- config["$schema"] = "https://raw.githubusercontent.com/aharonyaircohen/Kody-Engine-Lite/main/kody.config.schema.json";
3610
- config.git.defaultBranch = config.git.defaultBranch || basic.defaultBranch;
3611
- config.github.owner = config.github.owner || basic.owner;
3612
- config.github.repo = config.github.repo || basic.repo;
3613
- config.paths.taskDir = config.paths.taskDir || ".kody/tasks";
3614
- config.agent.runner = config.agent.runner || "claude-code";
3615
- config.agent.defaultRunner = config.agent.defaultRunner || "claude";
3616
- if (!config.agent.modelMap) {
3617
- config.agent.modelMap = { cheap: "haiku", mid: "sonnet", strong: "opus" };
3618
- }
3619
- validateQualityCommands(cwd, config, basic.pm);
3620
- return {
3621
- config,
3622
- architecture: parsed.architecture ?? "",
3623
- conventions: parsed.conventions ?? ""
3624
- };
3625
- } catch (err) {
3626
- console.log(" \u26A0 Smart detection failed, falling back to basic detection");
3627
- return {
3628
- config: buildFallbackConfig(cwd, basic),
3629
- architecture: "",
3630
- conventions: ""
3631
- };
3632
- }
3633
- }
3634
- function validateQualityCommands(cwd, config, pm) {
3635
- let scripts = {};
3636
- try {
3637
- const pkg = JSON.parse(fs21.readFileSync(path20.join(cwd, "package.json"), "utf-8"));
3638
- scripts = pkg.scripts ?? {};
3639
- } catch {
3640
- return;
3641
- }
3642
- const quality = config.quality ?? {};
3643
- const overrides = [
3644
- { key: "typecheck", preferred: ["typecheck", "type-check"] },
3645
- { key: "lint", preferred: ["lint"] },
3646
- { key: "lintFix", preferred: ["lint:fix", "lint-fix"] },
3647
- { key: "format", preferred: ["format:check", "format-check", "prettier:check"] },
3648
- { key: "formatFix", preferred: ["format", "format:fix", "format-fix"] },
3649
- { key: "testUnit", preferred: ["test:unit", "test-unit", "test:ci"] }
3650
- ];
3651
- for (const { key, preferred } of overrides) {
3652
- const match = preferred.find((s) => scripts[s]);
3653
- if (match) {
3654
- const correct = `${pm} ${match}`;
3655
- if (quality[key] !== correct) {
3656
- quality[key] = correct;
3657
- }
3658
- }
3659
- if (quality[key]) {
3660
- const scriptName = quality[key].replace(`${pm} `, "");
3661
- if (scriptName && !scripts[scriptName] && !scriptName.includes(" ")) {
3662
- quality[key] = "";
3663
- }
3664
- }
3665
- }
3666
- config.quality = quality;
3667
- }
3668
- function buildFallbackConfig(cwd, basic) {
3338
+ function buildConfig(cwd, basic) {
3669
3339
  const pkg = (() => {
3670
3340
  try {
3671
3341
  return JSON.parse(fs21.readFileSync(path20.join(cwd, "package.json"), "utf-8"));
@@ -3692,7 +3362,7 @@ function buildFallbackConfig(cwd, basic) {
3692
3362
  },
3693
3363
  git: { defaultBranch: basic.defaultBranch },
3694
3364
  github: { owner: basic.owner, repo: basic.repo },
3695
- paths: { taskDir: ".kody/tasks" },
3365
+ paths: { taskDir: ".tasks" },
3696
3366
  agent: {
3697
3367
  runner: "claude-code",
3698
3368
  defaultRunner: "claude",
@@ -3709,6 +3379,7 @@ function initCommand(opts) {
3709
3379
  `);
3710
3380
  console.log("\u2500\u2500 Files \u2500\u2500");
3711
3381
  const templatesDir = path20.join(PKG_ROOT, "templates");
3382
+ const basic = detectBasicConfig(cwd);
3712
3383
  const workflowSrc = path20.join(templatesDir, "kody.yml");
3713
3384
  const workflowDest = path20.join(cwd, ".github", "workflows", "kody.yml");
3714
3385
  if (!fs21.existsSync(workflowSrc)) {
@@ -3723,10 +3394,9 @@ function initCommand(opts) {
3723
3394
  console.log(" \u2713 .github/workflows/kody.yml");
3724
3395
  }
3725
3396
  const configDest = path20.join(cwd, "kody.config.json");
3726
- let smartResult = null;
3727
3397
  if (!fs21.existsSync(configDest) || opts.force) {
3728
- smartResult = smartInit(cwd);
3729
- 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");
3730
3400
  console.log(" \u2713 kody.config.json (auto-configured)");
3731
3401
  } else {
3732
3402
  console.log(" \u25CB kody.config.json (exists)");
@@ -3734,22 +3404,19 @@ function initCommand(opts) {
3734
3404
  const gitignorePath = path20.join(cwd, ".gitignore");
3735
3405
  if (fs21.existsSync(gitignorePath)) {
3736
3406
  const content = fs21.readFileSync(gitignorePath, "utf-8");
3737
- if (content.includes(".tasks/")) {
3738
- const updated = content.replace(/\n?\.tasks\/\n?/g, "\n");
3739
- fs21.writeFileSync(gitignorePath, updated);
3740
- 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/)");
3741
3410
  } else {
3742
- console.log(" \u25CB .gitignore (ok)");
3411
+ console.log(" \u25CB .gitignore (.tasks/ already present)");
3743
3412
  }
3744
3413
  }
3745
3414
  console.log("\n\u2500\u2500 Prerequisites \u2500\u2500");
3746
3415
  const checks = [
3747
- checkCommand2("claude", ["--version"], "Install: npm i -g @anthropic-ai/claude-code"),
3748
3416
  checkCommand2("gh", ["--version"], "Install: https://cli.github.com"),
3749
3417
  checkCommand2("git", ["--version"], "Install git"),
3750
3418
  checkCommand2("node", ["--version"], "Install Node.js >= 22"),
3751
- checkCommand2("pnpm", ["--version"], "Install: npm i -g pnpm"),
3752
- checkFile(path20.join(cwd, "package.json"), "package.json", "Run: pnpm init")
3419
+ checkFile(path20.join(cwd, "package.json"), "package.json", `Run: ${basic.pm} init`)
3753
3420
  ];
3754
3421
  for (const c of checks) {
3755
3422
  if (c.ok) {
@@ -3763,8 +3430,9 @@ function initCommand(opts) {
3763
3430
  console.log(ghAuth.ok ? ` \u2713 ${ghAuth.name} (${ghAuth.detail})` : ` \u2717 ${ghAuth.name} \u2014 ${ghAuth.fix}`);
3764
3431
  const ghRepo = checkGhRepoAccess(cwd);
3765
3432
  console.log(ghRepo.ok ? ` \u2713 ${ghRepo.name} (${ghRepo.detail})` : ` \u2717 ${ghRepo.name} \u2014 ${ghRepo.fix}`);
3433
+ let repoSlug = "";
3766
3434
  if (ghRepo.ok && ghRepo.detail) {
3767
- const repoSlug = ghRepo.detail;
3435
+ repoSlug = ghRepo.detail;
3768
3436
  const secretChecks = [
3769
3437
  checkGhSecret(repoSlug, "ANTHROPIC_API_KEY")
3770
3438
  ];
@@ -3854,133 +3522,331 @@ function initCommand(opts) {
3854
3522
  console.log(" \u2717 kody.config.json \u2014 invalid JSON");
3855
3523
  }
3856
3524
  }
3857
- 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");
3858
3696
  const memoryDir = path20.join(cwd, ".kody", "memory");
3859
3697
  fs21.mkdirSync(memoryDir, { recursive: true });
3860
3698
  const archPath = path20.join(memoryDir, "architecture.md");
3861
3699
  const conventionsPath = path20.join(memoryDir, "conventions.md");
3862
- if (fs21.existsSync(archPath) && !opts.force) {
3863
- console.log(" \u25CB .kody/memory/architecture.md (exists, use --force to regenerate)");
3864
- } else if (smartResult?.architecture) {
3865
- fs21.writeFileSync(archPath, smartResult.architecture);
3866
- const lineCount = smartResult.architecture.split("\n").length;
3867
- console.log(` \u2713 .kody/memory/architecture.md (${lineCount} lines, LLM-generated)`);
3868
- } else {
3869
- const archItems = detectArchitecture(cwd);
3870
- 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) {
3871
3753
  const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
3872
3754
  fs21.writeFileSync(archPath, `# Architecture (auto-detected ${timestamp2})
3873
3755
 
3874
3756
  ## Overview
3875
- ${archItems.join("\n")}
3757
+ ${detected.join("\n")}
3876
3758
  `);
3877
- console.log(` \u2713 .kody/memory/architecture.md (${archItems.length} items, basic detection)`);
3878
- } else {
3879
- console.log(" \u25CB No architecture detected");
3759
+ console.log(` \u2713 .kody/memory/architecture.md (${detected.length} items, basic detection)`);
3880
3760
  }
3881
- }
3882
- if (fs21.existsSync(conventionsPath) && !opts.force) {
3883
- console.log(" \u25CB .kody/memory/conventions.md (exists, use --force to regenerate)");
3884
- } else if (smartResult?.conventions) {
3885
- fs21.writeFileSync(conventionsPath, smartResult.conventions);
3886
- const lineCount = smartResult.conventions.split("\n").length;
3887
- console.log(` \u2713 .kody/memory/conventions.md (${lineCount} lines, LLM-generated)`);
3888
- } else {
3889
3761
  fs21.writeFileSync(conventionsPath, "# Conventions\n\n<!-- Auto-learned conventions will be appended here -->\n");
3890
3762
  console.log(" \u2713 .kody/memory/conventions.md (seed)");
3891
3763
  }
3892
3764
  console.log("\n\u2500\u2500 Step Files \u2500\u2500");
3893
3765
  const stepsDir = path20.join(cwd, ".kody", "steps");
3894
- const stepsExist = fs21.existsSync(stepsDir) && fs21.readdirSync(stepsDir).some((f) => f.endsWith(".md"));
3895
- if (stepsExist && !opts.force) {
3896
- console.log(" \u25CB .kody/steps/ (exists, use --force to regenerate)");
3897
- } else {
3898
- fs21.mkdirSync(stepsDir, { recursive: true });
3899
- const readIfExistsForSteps = (rel, maxChars = 3e3) => {
3900
- const p = path20.join(cwd, rel);
3901
- if (fs21.existsSync(p)) return fs21.readFileSync(p, "utf-8").slice(0, maxChars);
3902
- return null;
3903
- };
3904
- let repoContext = "";
3905
- const pkgForSteps = readIfExistsForSteps("package.json");
3906
- if (pkgForSteps) repoContext += `## package.json
3907
- ${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.
3908
3789
 
3909
- `;
3910
- const readmeForSteps = readIfExistsForSteps("README.md", 2e3);
3911
- if (readmeForSteps) repoContext += `## README.md
3912
- ${readmeForSteps}
3790
+ ## Your Task
3791
+ Take the prompt template below and APPEND repository-specific sections to it.
3913
3792
 
3914
- `;
3915
- const claudeMdForSteps = readIfExistsForSteps("CLAUDE.md", 3e3);
3916
- if (claudeMdForSteps) repoContext += `## CLAUDE.md
3917
- ${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.
3918
3803
 
3919
- `;
3920
- const agentsMdForSteps = readIfExistsForSteps("AGENTS.md", 3e3);
3921
- if (agentsMdForSteps) repoContext += `## AGENTS.md
3922
- ${agentsMdForSteps}
3804
+ ## Stage Being Customized
3805
+ Stage: ${stage}
3923
3806
 
3924
- `;
3925
- const sampleFiles = gatherSampleSourceFiles(cwd);
3926
- if (sampleFiles) repoContext += `## Sample Source Files
3927
- ${sampleFiles}
3807
+ ## Prompt Template (output this EXACTLY, then append your sections)
3808
+ ${beforePlaceholder}
3928
3809
 
3929
- `;
3930
- try {
3931
- const srcEntries = fs21.readdirSync(path20.join(cwd, "src"), { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
3932
- if (srcEntries.length > 0) repoContext += `## src/ structure
3933
- ${srcEntries.join(", ")}
3810
+ ## Repository Context
3934
3811
 
3935
- `;
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`);
3936
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++;
3937
3845
  }
3938
- const arch = fs21.existsSync(archPath) ? fs21.readFileSync(archPath, "utf-8") : "";
3939
- const conv = fs21.existsSync(conventionsPath) ? fs21.readFileSync(conventionsPath, "utf-8") : "";
3940
- console.log(" \u23F3 Customizing step files with Claude (sonnet)...");
3941
- let stepCount = 0;
3942
- for (const stage of STEP_STAGES) {
3943
- const templatePath = path20.join(PKG_ROOT, "prompts", `${stage}.md`);
3944
- if (!fs21.existsSync(templatePath)) {
3945
- console.log(` \u2717 ${stage}.md \u2014 template not found in engine`);
3946
- continue;
3947
- }
3948
- const defaultPrompt = fs21.readFileSync(templatePath, "utf-8");
3949
- const customizationPrompt = buildStepCustomizationPrompt(stage, defaultPrompt, repoContext, arch, conv);
3950
- try {
3951
- const output = execFileSync11("claude", [
3952
- "--print",
3953
- "--model",
3954
- "sonnet",
3955
- "--dangerously-skip-permissions",
3956
- customizationPrompt
3957
- ], {
3958
- encoding: "utf-8",
3959
- timeout: 12e4,
3960
- cwd,
3961
- stdio: ["pipe", "pipe", "pipe"]
3962
- }).trim();
3963
- const cleaned = output.replace(/^```(?:markdown|md)?\s*\n?/, "").replace(/\n?```\s*$/, "");
3964
- if (!cleaned.includes("{{TASK_CONTEXT}}")) {
3965
- console.log(` \u26A0 ${stage}.md \u2014 AI dropped {{TASK_CONTEXT}}, using default template`);
3966
- fs21.writeFileSync(path20.join(stepsDir, `${stage}.md`), defaultPrompt);
3967
- } else {
3968
- fs21.writeFileSync(path20.join(stepsDir, `${stage}.md`), cleaned);
3969
- }
3970
- stepCount++;
3971
- console.log(` \u2713 ${stage}.md`);
3972
- } catch (err) {
3973
- console.log(` \u26A0 ${stage}.md \u2014 customization failed, using default template`);
3974
- fs21.copyFileSync(templatePath, path20.join(stepsDir, `${stage}.md`));
3975
- stepCount++;
3976
- }
3977
- }
3978
- console.log(` \u2713 Generated ${stepCount} step files in .kody/steps/`);
3979
3846
  }
3847
+ console.log(` \u2713 Generated ${stepCount} step files in .kody/steps/`);
3980
3848
  console.log("\n\u2500\u2500 Git \u2500\u2500");
3981
3849
  const filesToCommit = [
3982
- ".github/workflows/kody.yml",
3983
- "kody.config.json",
3984
3850
  ".kody/memory/architecture.md",
3985
3851
  ".kody/memory/conventions.md"
3986
3852
  ].filter((f) => fs21.existsSync(path20.join(cwd, f)));
@@ -3992,42 +3858,125 @@ ${srcEntries.join(", ")}
3992
3858
  }
3993
3859
  if (filesToCommit.length > 0) {
3994
3860
  try {
3995
- execFileSync11("git", ["add", ...filesToCommit], { cwd, stdio: "pipe" });
3996
- const staged = execFileSync11("git", ["diff", "--cached", "--name-only"], { cwd, encoding: "utf-8" }).trim();
3997
- if (staged) {
3998
- execFileSync11("git", ["commit", "--no-gpg-sign", "-m", "chore: add kody engine"], { cwd, stdio: "pipe" });
3999
- console.log(` \u2713 Committed: ${filesToCommit.join(", ")}`);
4000
- try {
4001
- execFileSync11("git", ["push"], { cwd, stdio: "pipe", timeout: 3e4 });
4002
- console.log(" \u2713 Pushed to origin");
4003
- } catch {
4004
- 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");
4005
3919
  }
4006
3920
  } else {
4007
- 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
+ }
4008
3935
  }
4009
3936
  } catch (err) {
4010
3937
  console.log(` \u25CB Git commit skipped: ${err instanceof Error ? err.message : err}`);
4011
3938
  }
4012
3939
  }
4013
- const allChecks = [...checks, ghAuth, ghRepo];
4014
- const failed = allChecks.filter((c) => !c.ok);
4015
- console.log("\n\u2500\u2500 Summary \u2500\u2500");
4016
- if (failed.length === 0) {
4017
- console.log(" \u2713 All checks passed! Ready to use.");
4018
- console.log("\n Next: Comment '@kody' on a GitHub issue");
4019
- } else {
4020
- console.log(` \u26A0 ${failed.length} issue(s) to fix:`);
4021
- for (const c of failed) {
4022
- 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 {
4023
3970
  }
4024
3971
  }
4025
- console.log("");
3972
+ return detected;
4026
3973
  }
4027
3974
  var args = process.argv.slice(2);
4028
3975
  var command = args[0];
4029
3976
  if (command === "init") {
4030
3977
  initCommand({ force: args.includes("--force") });
3978
+ } else if (command === "bootstrap") {
3979
+ bootstrapCommand();
4031
3980
  } else if (command === "version" || command === "--version" || command === "-v") {
4032
3981
  console.log(getVersion());
4033
3982
  } else {