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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/cli.js CHANGED
@@ -1101,7 +1101,6 @@ var init_agent = __esm({
1101
1101
  taskify: "explore",
1102
1102
  plan: "explore",
1103
1103
  build: "build",
1104
- autofix: "build",
1105
1104
  "review-fix": "build",
1106
1105
  review: "review"
1107
1106
  };
@@ -2979,13 +2978,11 @@ async function main() {
2979
2978
  if (!proxyRunning) {
2980
2979
  litellmProcess2 = await tryStartLitellm(config2.agent.litellmUrl, projectDir);
2981
2980
  if (!litellmProcess2) {
2982
- logger.warn("LiteLLM not available \u2014 falling back to Anthropic models");
2983
- config2.agent.litellmUrl = void 0;
2981
+ logger.error("LiteLLM is configured (litellmUrl) but could not be started. Install it with: pip install 'litellm[proxy]'");
2982
+ process.exit(1);
2984
2983
  }
2985
2984
  }
2986
- if (config2.agent.litellmUrl) {
2987
- process.env.ANTHROPIC_BASE_URL = config2.agent.litellmUrl;
2988
- }
2985
+ process.env.ANTHROPIC_BASE_URL = config2.agent.litellmUrl;
2989
2986
  }
2990
2987
  const runners2 = createRunners(config2);
2991
2988
  const defaultRunnerName2 = config2.agent.defaultRunner ?? Object.keys(runners2)[0] ?? "claude";
@@ -3097,16 +3094,14 @@ ${input.feedback}` : reviewContext;
3097
3094
  if (!proxyRunning) {
3098
3095
  litellmProcess = await tryStartLitellm(config.agent.litellmUrl, projectDir);
3099
3096
  if (!litellmProcess) {
3100
- logger.warn("LiteLLM not available \u2014 falling back to Anthropic models");
3101
- config.agent.litellmUrl = void 0;
3097
+ logger.error("LiteLLM is configured (litellmUrl) but could not be started. Install it with: pip install 'litellm[proxy]'");
3098
+ process.exit(1);
3102
3099
  }
3103
3100
  } else {
3104
3101
  logger.info(`LiteLLM proxy already running at ${config.agent.litellmUrl}`);
3105
3102
  }
3106
- if (config.agent.litellmUrl) {
3107
- process.env.ANTHROPIC_BASE_URL = config.agent.litellmUrl;
3108
- logger.info(`ANTHROPIC_BASE_URL set to ${config.agent.litellmUrl}`);
3109
- }
3103
+ process.env.ANTHROPIC_BASE_URL = config.agent.litellmUrl;
3104
+ logger.info(`ANTHROPIC_BASE_URL set to ${config.agent.litellmUrl}`);
3110
3105
  }
3111
3106
  const runners = createRunners(config);
3112
3107
  const defaultRunnerName = config.agent.defaultRunner ?? Object.keys(runners)[0] ?? "claude";
@@ -3310,135 +3305,6 @@ function checkGhSecret(repoSlug, secretName) {
3310
3305
  };
3311
3306
  }
3312
3307
  }
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
3308
  function detectBasicConfig(cwd) {
3443
3309
  let pm = "pnpm";
3444
3310
  if (fs21.existsSync(path20.join(cwd, "yarn.lock"))) pm = "yarn";
@@ -3483,193 +3349,7 @@ function detectBasicConfig(cwd) {
3483
3349
  }
3484
3350
  return { defaultBranch, owner, repo, pm };
3485
3351
  }
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) {
3352
+ function buildConfig(cwd, basic) {
3673
3353
  const pkg = (() => {
3674
3354
  try {
3675
3355
  return JSON.parse(fs21.readFileSync(path20.join(cwd, "package.json"), "utf-8"));
@@ -3713,6 +3393,7 @@ function initCommand(opts) {
3713
3393
  `);
3714
3394
  console.log("\u2500\u2500 Files \u2500\u2500");
3715
3395
  const templatesDir = path20.join(PKG_ROOT, "templates");
3396
+ const basic = detectBasicConfig(cwd);
3716
3397
  const workflowSrc = path20.join(templatesDir, "kody.yml");
3717
3398
  const workflowDest = path20.join(cwd, ".github", "workflows", "kody.yml");
3718
3399
  if (!fs21.existsSync(workflowSrc)) {
@@ -3727,10 +3408,9 @@ function initCommand(opts) {
3727
3408
  console.log(" \u2713 .github/workflows/kody.yml");
3728
3409
  }
3729
3410
  const configDest = path20.join(cwd, "kody.config.json");
3730
- let smartResult = null;
3731
3411
  if (!fs21.existsSync(configDest) || opts.force) {
3732
- smartResult = smartInit(cwd);
3733
- fs21.writeFileSync(configDest, JSON.stringify(smartResult.config, null, 2) + "\n");
3412
+ const config = buildConfig(cwd, basic);
3413
+ fs21.writeFileSync(configDest, JSON.stringify(config, null, 2) + "\n");
3734
3414
  console.log(" \u2713 kody.config.json (auto-configured)");
3735
3415
  } else {
3736
3416
  console.log(" \u25CB kody.config.json (exists)");
@@ -3748,12 +3428,10 @@ function initCommand(opts) {
3748
3428
  }
3749
3429
  console.log("\n\u2500\u2500 Prerequisites \u2500\u2500");
3750
3430
  const checks = [
3751
- checkCommand2("claude", ["--version"], "Install: npm i -g @anthropic-ai/claude-code"),
3752
3431
  checkCommand2("gh", ["--version"], "Install: https://cli.github.com"),
3753
3432
  checkCommand2("git", ["--version"], "Install git"),
3754
3433
  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")
3434
+ checkFile(path20.join(cwd, "package.json"), "package.json", `Run: ${basic.pm} init`)
3757
3435
  ];
3758
3436
  for (const c of checks) {
3759
3437
  if (c.ok) {
@@ -3767,8 +3445,9 @@ function initCommand(opts) {
3767
3445
  console.log(ghAuth.ok ? ` \u2713 ${ghAuth.name} (${ghAuth.detail})` : ` \u2717 ${ghAuth.name} \u2014 ${ghAuth.fix}`);
3768
3446
  const ghRepo = checkGhRepoAccess(cwd);
3769
3447
  console.log(ghRepo.ok ? ` \u2713 ${ghRepo.name} (${ghRepo.detail})` : ` \u2717 ${ghRepo.name} \u2014 ${ghRepo.fix}`);
3448
+ let repoSlug = "";
3770
3449
  if (ghRepo.ok && ghRepo.detail) {
3771
- const repoSlug = ghRepo.detail;
3450
+ repoSlug = ghRepo.detail;
3772
3451
  const secretChecks = [
3773
3452
  checkGhSecret(repoSlug, "ANTHROPIC_API_KEY")
3774
3453
  ];
@@ -3858,133 +3537,366 @@ function initCommand(opts) {
3858
3537
  console.log(" \u2717 kody.config.json \u2014 invalid JSON");
3859
3538
  }
3860
3539
  }
3861
- console.log("\n\u2500\u2500 Project Memory \u2500\u2500");
3540
+ console.log("\n\u2500\u2500 Git \u2500\u2500");
3541
+ const filesToCommit = [
3542
+ ".github/workflows/kody.yml",
3543
+ "kody.config.json"
3544
+ ].filter((f) => fs21.existsSync(path20.join(cwd, f)));
3545
+ if (filesToCommit.length > 0) {
3546
+ try {
3547
+ const fullPaths = filesToCommit.map((f) => path20.join(cwd, f));
3548
+ execFileSync11("npx", ["prettier", "--write", ...fullPaths], {
3549
+ cwd,
3550
+ encoding: "utf-8",
3551
+ timeout: 3e4,
3552
+ stdio: ["pipe", "pipe", "pipe"]
3553
+ });
3554
+ } catch {
3555
+ }
3556
+ }
3557
+ if (filesToCommit.length > 0) {
3558
+ try {
3559
+ execFileSync11("git", ["add", ...filesToCommit], { cwd, stdio: "pipe" });
3560
+ const staged = execFileSync11("git", ["diff", "--cached", "--name-only"], { cwd, encoding: "utf-8" }).trim();
3561
+ if (staged) {
3562
+ 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" });
3563
+ console.log(` \u2713 Committed: ${filesToCommit.join(", ")}`);
3564
+ try {
3565
+ execFileSync11("git", ["push"], { cwd, stdio: "pipe", timeout: 6e4 });
3566
+ console.log(" \u2713 Pushed to origin");
3567
+ } catch {
3568
+ console.log(" \u25CB Push failed \u2014 run 'git push' manually");
3569
+ }
3570
+ } else {
3571
+ console.log(" \u25CB No new changes to commit");
3572
+ }
3573
+ } catch (err) {
3574
+ console.log(` \u25CB Git commit skipped: ${err instanceof Error ? err.message : err}`);
3575
+ }
3576
+ }
3577
+ const allChecks = [...checks, ghAuth, ghRepo];
3578
+ const failed = allChecks.filter((c) => !c.ok);
3579
+ console.log("\n\u2500\u2500 Summary \u2500\u2500");
3580
+ if (failed.length === 0) {
3581
+ console.log(" \u2713 All checks passed! Ready to use.");
3582
+ console.log(`
3583
+ \u2500\u2500 Getting Started \u2500\u2500
3584
+
3585
+ 1. Bootstrap (optional but recommended):
3586
+ Create a GitHub issue comment with '@kody bootstrap'
3587
+ \u2192 Kody will analyze your repo and generate project-specific config
3588
+
3589
+ 2. First task:
3590
+ Create a GitHub issue describing work to do, then comment '@kody'
3591
+ \u2192 Kody picks it up and runs the full pipeline
3592
+
3593
+ Commands:
3594
+ @kody Run full pipeline on an issue
3595
+ @kody bootstrap Analyze repo, generate memory + step files
3596
+ @kody fix Fix build failures
3597
+ @kody review Review a PR
3598
+ `);
3599
+ } else {
3600
+ console.log(` \u26A0 ${failed.length} issue(s) to fix:`);
3601
+ for (const c of failed) {
3602
+ console.log(` \u2022 ${c.name}: ${c.fix}`);
3603
+ }
3604
+ console.log("");
3605
+ }
3606
+ }
3607
+ var STEP_STAGES = ["taskify", "plan", "build", "review", "review-fix"];
3608
+ function gatherSampleSourceFiles(cwd, maxFiles = 3, maxCharsEach = 2e3) {
3609
+ const srcDir = path20.join(cwd, "src");
3610
+ const baseDir = fs21.existsSync(srcDir) ? srcDir : cwd;
3611
+ const results = [];
3612
+ function walk(dir) {
3613
+ const entries = [];
3614
+ try {
3615
+ for (const entry of fs21.readdirSync(dir, { withFileTypes: true })) {
3616
+ if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
3617
+ const full = path20.join(dir, entry.name);
3618
+ if (entry.isDirectory()) {
3619
+ entries.push(...walk(full));
3620
+ } else if (/\.(ts|js)$/.test(entry.name) && !/\.(test|spec|config|d)\.(ts|js)$/.test(entry.name)) {
3621
+ try {
3622
+ const stat = fs21.statSync(full);
3623
+ if (stat.size >= 200 && stat.size <= 5e3) {
3624
+ entries.push({ filePath: full, size: stat.size });
3625
+ }
3626
+ } catch {
3627
+ }
3628
+ }
3629
+ }
3630
+ } catch {
3631
+ }
3632
+ return entries;
3633
+ }
3634
+ const files = walk(baseDir).sort((a, b) => b.size - a.size).slice(0, maxFiles);
3635
+ for (const { filePath } of files) {
3636
+ const rel = path20.relative(cwd, filePath);
3637
+ const content = fs21.readFileSync(filePath, "utf-8").slice(0, maxCharsEach);
3638
+ results.push(`### File: ${rel}
3639
+ \`\`\`typescript
3640
+ ${content}
3641
+ \`\`\``);
3642
+ }
3643
+ return results.join("\n\n");
3644
+ }
3645
+ function ghComment(issueNumber, body, cwd) {
3646
+ try {
3647
+ let repoSlug = "";
3648
+ try {
3649
+ const configPath = path20.join(cwd, "kody.config.json");
3650
+ if (fs21.existsSync(configPath)) {
3651
+ const config = JSON.parse(fs21.readFileSync(configPath, "utf-8"));
3652
+ if (config.github?.owner && config.github?.repo) {
3653
+ repoSlug = `${config.github.owner}/${config.github.repo}`;
3654
+ }
3655
+ }
3656
+ } catch {
3657
+ }
3658
+ if (!repoSlug) return;
3659
+ execFileSync11("gh", [
3660
+ "issue",
3661
+ "comment",
3662
+ String(issueNumber),
3663
+ "--repo",
3664
+ repoSlug,
3665
+ "--body",
3666
+ body
3667
+ ], {
3668
+ cwd,
3669
+ encoding: "utf-8",
3670
+ timeout: 15e3,
3671
+ stdio: ["pipe", "pipe", "pipe"]
3672
+ });
3673
+ } catch {
3674
+ }
3675
+ }
3676
+ function bootstrapCommand() {
3677
+ const cwd = process.cwd();
3678
+ const issueNumber = parseInt(process.env.ISSUE_NUMBER ?? "", 10) || 0;
3679
+ console.log(`
3680
+ \u{1F527} Kody Bootstrap \u2014 Generating project memory + step files
3681
+ `);
3682
+ if (issueNumber) {
3683
+ ghComment(issueNumber, "\u{1F527} **Bootstrap started** \u2014 analyzing project and generating configuration...", cwd);
3684
+ }
3685
+ const readIfExists = (rel, maxChars = 3e3) => {
3686
+ const p = path20.join(cwd, rel);
3687
+ if (fs21.existsSync(p)) return fs21.readFileSync(p, "utf-8").slice(0, maxChars);
3688
+ return null;
3689
+ };
3690
+ let repoContext = "";
3691
+ const pkgJson = readIfExists("package.json");
3692
+ if (pkgJson) repoContext += `## package.json
3693
+ ${pkgJson}
3694
+
3695
+ `;
3696
+ const tsconfig = readIfExists("tsconfig.json", 1e3);
3697
+ if (tsconfig) repoContext += `## tsconfig.json
3698
+ ${tsconfig}
3699
+
3700
+ `;
3701
+ const readme = readIfExists("README.md", 2e3);
3702
+ if (readme) repoContext += `## README.md (first 2000 chars)
3703
+ ${readme}
3704
+
3705
+ `;
3706
+ const claudeMd = readIfExists("CLAUDE.md", 3e3);
3707
+ if (claudeMd) repoContext += `## CLAUDE.md
3708
+ ${claudeMd}
3709
+
3710
+ `;
3711
+ const agentsMd = readIfExists("AGENTS.md", 3e3);
3712
+ if (agentsMd) repoContext += `## AGENTS.md
3713
+ ${agentsMd}
3714
+
3715
+ `;
3716
+ const sampleFiles = gatherSampleSourceFiles(cwd);
3717
+ if (sampleFiles) repoContext += `## Sample Source Files
3718
+ ${sampleFiles}
3719
+
3720
+ `;
3721
+ try {
3722
+ const topDirs = fs21.readdirSync(cwd, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules").map((e) => e.name);
3723
+ repoContext += `## Top-level directories
3724
+ ${topDirs.join(", ")}
3725
+
3726
+ `;
3727
+ const srcDir = path20.join(cwd, "src");
3728
+ if (fs21.existsSync(srcDir)) {
3729
+ const srcDirs = fs21.readdirSync(srcDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
3730
+ if (srcDirs.length > 0) repoContext += `## src/ subdirectories
3731
+ ${srcDirs.join(", ")}
3732
+
3733
+ `;
3734
+ }
3735
+ } catch {
3736
+ }
3737
+ const existingFiles = [];
3738
+ 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"]) {
3739
+ if (fs21.existsSync(path20.join(cwd, f))) existingFiles.push(f);
3740
+ }
3741
+ if (existingFiles.length) repoContext += `## Config files present
3742
+ ${existingFiles.join(", ")}
3743
+
3744
+ `;
3745
+ console.log("\u2500\u2500 Project Memory \u2500\u2500");
3862
3746
  const memoryDir = path20.join(cwd, ".kody", "memory");
3863
3747
  fs21.mkdirSync(memoryDir, { recursive: true });
3864
3748
  const archPath = path20.join(memoryDir, "architecture.md");
3865
3749
  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) {
3750
+ const memoryPrompt = `You are analyzing a project to generate documentation for an autonomous SDLC pipeline.
3751
+
3752
+ Given this project context, output ONLY a JSON object with EXACTLY this structure:
3753
+
3754
+ {
3755
+ "architecture": "# Architecture\\n\\n<markdown content>",
3756
+ "conventions": "# Conventions\\n\\n<markdown content>"
3757
+ }
3758
+
3759
+ Rules for architecture (markdown string):
3760
+ - Be specific about THIS project
3761
+ - Include: framework, language, database, testing, key directories, data flow
3762
+ - Reference CLAUDE.md and .ai-docs/ if they exist
3763
+ - Keep under 50 lines
3764
+
3765
+ Rules for conventions (markdown string):
3766
+ - Extract actual patterns from the project
3767
+ - If CLAUDE.md exists, reference it
3768
+ - Keep under 30 lines
3769
+
3770
+ Output ONLY valid JSON. No markdown fences. No explanation.
3771
+
3772
+ ${repoContext}`;
3773
+ console.log(" \u23F3 Analyzing project...");
3774
+ try {
3775
+ const output = execFileSync11("claude", [
3776
+ "--print",
3777
+ "--model",
3778
+ "haiku",
3779
+ "--dangerously-skip-permissions",
3780
+ memoryPrompt
3781
+ ], {
3782
+ encoding: "utf-8",
3783
+ timeout: 9e4,
3784
+ cwd,
3785
+ stdio: ["pipe", "pipe", "pipe"]
3786
+ }).trim();
3787
+ const cleaned = output.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
3788
+ const parsed = JSON.parse(cleaned);
3789
+ if (parsed.architecture) {
3790
+ fs21.writeFileSync(archPath, parsed.architecture);
3791
+ const lineCount = parsed.architecture.split("\n").length;
3792
+ console.log(` \u2713 .kody/memory/architecture.md (${lineCount} lines)`);
3793
+ }
3794
+ if (parsed.conventions) {
3795
+ fs21.writeFileSync(conventionsPath, parsed.conventions);
3796
+ const lineCount = parsed.conventions.split("\n").length;
3797
+ console.log(` \u2713 .kody/memory/conventions.md (${lineCount} lines)`);
3798
+ }
3799
+ } catch {
3800
+ console.log(" \u26A0 LLM analysis failed \u2014 creating basic memory files");
3801
+ const detected = detectArchitectureBasic(cwd);
3802
+ if (detected.length > 0) {
3875
3803
  const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
3876
3804
  fs21.writeFileSync(archPath, `# Architecture (auto-detected ${timestamp2})
3877
3805
 
3878
3806
  ## Overview
3879
- ${archItems.join("\n")}
3807
+ ${detected.join("\n")}
3880
3808
  `);
3881
- console.log(` \u2713 .kody/memory/architecture.md (${archItems.length} items, basic detection)`);
3882
- } else {
3883
- console.log(" \u25CB No architecture detected");
3809
+ console.log(` \u2713 .kody/memory/architecture.md (${detected.length} items, basic detection)`);
3884
3810
  }
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
3811
  fs21.writeFileSync(conventionsPath, "# Conventions\n\n<!-- Auto-learned conventions will be appended here -->\n");
3894
3812
  console.log(" \u2713 .kody/memory/conventions.md (seed)");
3895
3813
  }
3896
3814
  console.log("\n\u2500\u2500 Step Files \u2500\u2500");
3897
3815
  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}
3816
+ fs21.mkdirSync(stepsDir, { recursive: true });
3817
+ const arch = fs21.existsSync(archPath) ? fs21.readFileSync(archPath, "utf-8") : "";
3818
+ const conv = fs21.existsSync(conventionsPath) ? fs21.readFileSync(conventionsPath, "utf-8") : "";
3819
+ console.log(" \u23F3 Customizing step files...");
3820
+ let stepCount = 0;
3821
+ for (const stage of STEP_STAGES) {
3822
+ const templatePath = path20.join(PKG_ROOT, "prompts", `${stage}.md`);
3823
+ if (!fs21.existsSync(templatePath)) {
3824
+ console.log(` \u2717 ${stage}.md \u2014 template not found in engine`);
3825
+ continue;
3826
+ }
3827
+ const defaultPrompt = fs21.readFileSync(templatePath, "utf-8");
3828
+ const contextPlaceholder = "{{TASK_CONTEXT}}";
3829
+ const placeholderIdx = defaultPrompt.indexOf(contextPlaceholder);
3830
+ if (placeholderIdx === -1) {
3831
+ fs21.copyFileSync(templatePath, path20.join(stepsDir, `${stage}.md`));
3832
+ stepCount++;
3833
+ console.log(` \u2713 ${stage}.md`);
3834
+ continue;
3835
+ }
3836
+ const beforePlaceholder = defaultPrompt.slice(0, placeholderIdx).trimEnd();
3837
+ const afterPlaceholder = defaultPrompt.slice(placeholderIdx);
3838
+ const customizationPrompt = `You are customizing a Kody pipeline prompt for a specific repository.
3912
3839
 
3913
- `;
3914
- const readmeForSteps = readIfExistsForSteps("README.md", 2e3);
3915
- if (readmeForSteps) repoContext += `## README.md
3916
- ${readmeForSteps}
3840
+ ## Your Task
3841
+ Take the prompt template below and APPEND repository-specific sections to it.
3917
3842
 
3918
- `;
3919
- const claudeMdForSteps = readIfExistsForSteps("CLAUDE.md", 3e3);
3920
- if (claudeMdForSteps) repoContext += `## CLAUDE.md
3921
- ${claudeMdForSteps}
3843
+ ## Rules
3844
+ 1. Output the ENTIRE original prompt template UNCHANGED first \u2014 copy it exactly, character for character.
3845
+ 2. Then APPEND these three new sections at the end:
3846
+ - ## 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.
3847
+ - ## Improvement Areas \u2014 Gaps or anti-patterns found in the codebase. Be specific with file paths.
3848
+ - ## Acceptance Criteria \u2014 A concrete checklist (markdown checkboxes) for "done" in this repo.
3849
+ 3. Be SPECIFIC \u2014 reference actual file paths, function names, and conventions from the repo context.
3850
+ 4. Keep each appended section concise (10-20 lines max).
3851
+ 5. Output ONLY the customized prompt markdown. No explanation before or after.
3852
+ 6. Do NOT include the text "${contextPlaceholder}" \u2014 it will be appended automatically after your output.
3922
3853
 
3923
- `;
3924
- const agentsMdForSteps = readIfExistsForSteps("AGENTS.md", 3e3);
3925
- if (agentsMdForSteps) repoContext += `## AGENTS.md
3926
- ${agentsMdForSteps}
3854
+ ## Stage Being Customized
3855
+ Stage: ${stage}
3927
3856
 
3928
- `;
3929
- const sampleFiles = gatherSampleSourceFiles(cwd);
3930
- if (sampleFiles) repoContext += `## Sample Source Files
3931
- ${sampleFiles}
3857
+ ## Prompt Template (output this EXACTLY, then append your sections)
3858
+ ${beforePlaceholder}
3932
3859
 
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(", ")}
3860
+ ## Repository Context
3938
3861
 
3939
- `;
3862
+ ### Architecture
3863
+ ${arch}
3864
+
3865
+ ### Conventions
3866
+ ${conv}
3867
+
3868
+ ### Project Details
3869
+ ${repoContext}
3870
+
3871
+ REMINDER: Output the full prompt template first (unchanged), then your three appended sections. Do NOT include "${contextPlaceholder}".`;
3872
+ try {
3873
+ const output = execFileSync11("claude", [
3874
+ "--print",
3875
+ "--model",
3876
+ "haiku",
3877
+ "--dangerously-skip-permissions",
3878
+ customizationPrompt
3879
+ ], {
3880
+ encoding: "utf-8",
3881
+ timeout: 9e4,
3882
+ cwd,
3883
+ stdio: ["pipe", "pipe", "pipe"]
3884
+ }).trim();
3885
+ let cleaned = output.replace(/^```(?:markdown|md)?\s*\n?/, "").replace(/\n?```\s*$/, "");
3886
+ cleaned = cleaned.replace(/\n*\{\{TASK_CONTEXT\}\}\s*$/, "").trimEnd();
3887
+ const finalPrompt = cleaned + "\n\n" + afterPlaceholder;
3888
+ fs21.writeFileSync(path20.join(stepsDir, `${stage}.md`), finalPrompt);
3889
+ stepCount++;
3890
+ console.log(` \u2713 ${stage}.md`);
3940
3891
  } catch {
3892
+ console.log(` \u26A0 ${stage}.md \u2014 customization failed, using default template`);
3893
+ fs21.copyFileSync(templatePath, path20.join(stepsDir, `${stage}.md`));
3894
+ stepCount++;
3941
3895
  }
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
3896
  }
3897
+ console.log(` \u2713 Generated ${stepCount} step files in .kody/steps/`);
3984
3898
  console.log("\n\u2500\u2500 Git \u2500\u2500");
3985
3899
  const filesToCommit = [
3986
- ".github/workflows/kody.yml",
3987
- "kody.config.json",
3988
3900
  ".kody/memory/architecture.md",
3989
3901
  ".kody/memory/conventions.md"
3990
3902
  ].filter((f) => fs21.existsSync(path20.join(cwd, f)));
@@ -3996,44 +3908,148 @@ ${srcEntries.join(", ")}
3996
3908
  }
3997
3909
  if (filesToCommit.length > 0) {
3998
3910
  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");
3911
+ const fullPaths = filesToCommit.map((f) => path20.join(cwd, f));
3912
+ for (let pass = 0; pass < 2; pass++) {
3913
+ execFileSync11("npx", ["prettier", "--write", ...fullPaths], {
3914
+ cwd,
3915
+ encoding: "utf-8",
3916
+ timeout: 3e4,
3917
+ stdio: ["pipe", "pipe", "pipe"]
3918
+ });
3919
+ }
3920
+ console.log(" \u2713 Formatted files with Prettier");
3921
+ } catch {
3922
+ }
3923
+ }
3924
+ const isCI3 = !!process.env.GITHUB_ACTIONS;
3925
+ if (filesToCommit.length > 0) {
3926
+ try {
3927
+ if (isCI3) {
3928
+ const branchName = `kody/bootstrap-${Date.now()}`;
3929
+ execFileSync11("git", ["checkout", "-b", branchName], { cwd, stdio: "pipe" });
3930
+ execFileSync11("git", ["add", ...filesToCommit], { cwd, stdio: "pipe" });
3931
+ const staged = execFileSync11("git", ["diff", "--cached", "--name-only"], { cwd, encoding: "utf-8" }).trim();
3932
+ if (staged) {
3933
+ 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" });
3934
+ execFileSync11("git", ["push", "-u", "origin", branchName], { cwd, stdio: "pipe", timeout: 6e4 });
3935
+ console.log(` \u2713 Pushed branch: ${branchName}`);
3936
+ let baseBranch = "main";
3937
+ try {
3938
+ const configPath = path20.join(cwd, "kody.config.json");
3939
+ if (fs21.existsSync(configPath)) {
3940
+ const config = JSON.parse(fs21.readFileSync(configPath, "utf-8"));
3941
+ baseBranch = config.git?.defaultBranch ?? "main";
3942
+ }
3943
+ } catch {
3944
+ }
3945
+ try {
3946
+ const prUrl = execFileSync11("gh", [
3947
+ "pr",
3948
+ "create",
3949
+ "--title",
3950
+ "chore: Bootstrap Kody Engine",
3951
+ "--body",
3952
+ "## Summary\n\n- Add project memory (architecture + conventions)\n- Add customized pipeline step files\n\nGenerated by `@kody bootstrap`.",
3953
+ "--base",
3954
+ baseBranch,
3955
+ "--head",
3956
+ branchName
3957
+ ], {
3958
+ cwd,
3959
+ encoding: "utf-8",
3960
+ timeout: 3e4,
3961
+ stdio: ["pipe", "pipe", "pipe"]
3962
+ }).trim();
3963
+ console.log(` \u2713 Created PR: ${prUrl}`);
3964
+ if (issueNumber) {
3965
+ ghComment(issueNumber, `\u2705 **Bootstrap complete** \u2014 PR created: ${prUrl}
3966
+
3967
+ Review and merge to activate project-specific pipeline configuration.`, cwd);
3968
+ }
3969
+ } catch (prErr) {
3970
+ console.log(` \u25CB PR creation failed: ${prErr instanceof Error ? prErr.message : prErr}`);
3971
+ if (issueNumber) {
3972
+ ghComment(issueNumber, `\u26A0\uFE0F **Bootstrap complete** \u2014 files generated and pushed to branch \`${branchName}\`, but PR creation failed. Create it manually.`, cwd);
3973
+ }
3974
+ }
3975
+ } else {
3976
+ console.log(" \u25CB No new changes to commit");
4009
3977
  }
4010
3978
  } else {
4011
- console.log(" \u25CB No new changes to commit");
3979
+ execFileSync11("git", ["add", ...filesToCommit], { cwd, stdio: "pipe" });
3980
+ const staged = execFileSync11("git", ["diff", "--cached", "--name-only"], { cwd, encoding: "utf-8" }).trim();
3981
+ if (staged) {
3982
+ 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" });
3983
+ console.log(` \u2713 Committed: ${filesToCommit.join(", ")}`);
3984
+ try {
3985
+ execFileSync11("git", ["push"], { cwd, stdio: "pipe", timeout: 6e4 });
3986
+ console.log(" \u2713 Pushed to origin");
3987
+ } catch {
3988
+ console.log(" \u25CB Push failed \u2014 run 'git push' manually");
3989
+ }
3990
+ } else {
3991
+ console.log(" \u25CB No new changes to commit");
3992
+ }
4012
3993
  }
4013
3994
  } catch (err) {
4014
3995
  console.log(` \u25CB Git commit skipped: ${err instanceof Error ? err.message : err}`);
3996
+ if (issueNumber) {
3997
+ ghComment(issueNumber, `\u274C **Bootstrap failed** \u2014 git operation error: ${err instanceof Error ? err.message : err}`, cwd);
3998
+ }
4015
3999
  }
4016
4000
  }
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}`);
4001
+ console.log("\n\u2500\u2500 Done \u2500\u2500");
4002
+ console.log(" \u2713 Project bootstrap complete!");
4003
+ console.log(" Kody now has project-specific memory and customized step files.\n");
4004
+ }
4005
+ function detectArchitectureBasic(cwd) {
4006
+ const detected = [];
4007
+ const pkgPath = path20.join(cwd, "package.json");
4008
+ if (fs21.existsSync(pkgPath)) {
4009
+ try {
4010
+ const pkg = JSON.parse(fs21.readFileSync(pkgPath, "utf-8"));
4011
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
4012
+ if (allDeps.next) detected.push(`- Framework: Next.js ${allDeps.next}`);
4013
+ else if (allDeps.react) detected.push(`- Framework: React ${allDeps.react}`);
4014
+ else if (allDeps.express) detected.push(`- Framework: Express ${allDeps.express}`);
4015
+ else if (allDeps.fastify) detected.push(`- Framework: Fastify ${allDeps.fastify}`);
4016
+ else if (allDeps.hono) detected.push(`- Framework: Hono ${allDeps.hono}`);
4017
+ if (allDeps.typescript) detected.push(`- Language: TypeScript ${allDeps.typescript}`);
4018
+ if (allDeps.vitest) detected.push(`- Testing: vitest ${allDeps.vitest}`);
4019
+ else if (allDeps.jest) detected.push(`- Testing: jest ${allDeps.jest}`);
4020
+ if (allDeps.eslint) detected.push(`- Linting: eslint ${allDeps.eslint}`);
4021
+ if (allDeps.prettier) detected.push(`- Formatting: prettier ${allDeps.prettier}`);
4022
+ if (allDeps.prisma || allDeps["@prisma/client"]) detected.push("- ORM: Prisma");
4023
+ if (allDeps["drizzle-orm"]) detected.push("- ORM: Drizzle");
4024
+ if (allDeps.payload || allDeps["@payloadcms/next"]) detected.push("- CMS: Payload CMS");
4025
+ if (allDeps.tailwindcss) detected.push(`- CSS: Tailwind CSS ${allDeps.tailwindcss}`);
4026
+ if (fs21.existsSync(path20.join(cwd, "pnpm-lock.yaml"))) detected.push("- Package manager: pnpm");
4027
+ else if (fs21.existsSync(path20.join(cwd, "yarn.lock"))) detected.push("- Package manager: yarn");
4028
+ else if (fs21.existsSync(path20.join(cwd, "bun.lockb"))) detected.push("- Package manager: bun");
4029
+ else if (fs21.existsSync(path20.join(cwd, "package-lock.json"))) detected.push("- Package manager: npm");
4030
+ } catch {
4027
4031
  }
4028
4032
  }
4029
- console.log("");
4033
+ return detected;
4030
4034
  }
4031
4035
  var args = process.argv.slice(2);
4032
4036
  var command = args[0];
4033
4037
  if (command === "init") {
4034
4038
  initCommand({ force: args.includes("--force") });
4039
+ } else if (command === "bootstrap") {
4040
+ bootstrapCommand();
4035
4041
  } else if (command === "version" || command === "--version" || command === "-v") {
4036
4042
  console.log(getVersion());
4037
4043
  } else {
4038
4044
  Promise.resolve().then(() => init_entry());
4039
4045
  }
4046
+ export {
4047
+ buildConfig,
4048
+ checkCommand2 as checkCommand,
4049
+ checkFile,
4050
+ checkGhAuth,
4051
+ checkGhRepoAccess,
4052
+ checkGhSecret,
4053
+ detectArchitectureBasic,
4054
+ detectBasicConfig
4055
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine-lite",
3
- "version": "0.1.55",
3
+ "version": "0.1.57",
4
4
  "description": "Autonomous SDLC pipeline: Kody orchestration + Claude Code + LiteLLM",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -109,7 +109,7 @@ jobs:
109
109
 
110
110
  # Validate mode
111
111
  case "$MODE" in
112
- full|rerun|fix|status|approve|review) ;;
112
+ full|rerun|fix|status|approve|review|bootstrap) ;;
113
113
  *)
114
114
  # If first arg isn't a mode, it might be a task-id or nothing
115
115
  if [ -n "$MODE" ] && [ "$MODE" != "" ]; then
@@ -139,6 +139,11 @@ jobs:
139
139
  # Leave TASK_ID empty — entry.ts finds latest task for issue
140
140
  fi
141
141
 
142
+ # Bootstrap mode: set task-id and skip normal pipeline
143
+ if [ "$MODE" = "bootstrap" ]; then
144
+ TASK_ID="bootstrap-$(date +%y%m%d-%H%M%S)"
145
+ fi
146
+
142
147
  # Auto-generate task-id if not provided (only for full mode)
143
148
  if [ -z "$TASK_ID" ] && [ "$MODE" = "full" ]; then
144
149
  TASK_ID="${ISSUE_NUM}-$(date +%y%m%d-%H%M%S)"
@@ -237,17 +242,22 @@ jobs:
237
242
  DRY_RUN: ${{ github.event.inputs.dry_run || 'false' }}
238
243
  RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
239
244
  run: |
240
- CMD="run"
241
- [ "$MODE" = "rerun" ] && CMD="rerun"
242
- [ "$MODE" = "fix" ] && CMD="fix"
243
- [ "$MODE" = "review" ] && CMD="review"
244
- ARGS="--issue-number $ISSUE_NUMBER"
245
- [ -n "$TASK_ID" ] && ARGS="$ARGS --task-id $TASK_ID"
246
- [ -n "$PR_NUMBER" ] && ARGS="$ARGS --pr-number $PR_NUMBER"
247
- [ -n "$FROM_STAGE" ] && ARGS="$ARGS --from $FROM_STAGE"
248
- [ -n "$FEEDBACK" ] && ARGS="$ARGS --feedback \"$FEEDBACK\""
249
- [ "$DRY_RUN" = "true" ] && ARGS="$ARGS --dry-run"
250
- kody-engine-lite $CMD $ARGS
245
+ if [ "$MODE" = "bootstrap" ]; then
246
+ echo "Running bootstrap..."
247
+ kody-engine-lite bootstrap
248
+ else
249
+ CMD="run"
250
+ [ "$MODE" = "rerun" ] && CMD="rerun"
251
+ [ "$MODE" = "fix" ] && CMD="fix"
252
+ [ "$MODE" = "review" ] && CMD="review"
253
+ ARGS="--issue-number $ISSUE_NUMBER"
254
+ [ -n "$TASK_ID" ] && ARGS="$ARGS --task-id $TASK_ID"
255
+ [ -n "$PR_NUMBER" ] && ARGS="$ARGS --pr-number $PR_NUMBER"
256
+ [ -n "$FROM_STAGE" ] && ARGS="$ARGS --from $FROM_STAGE"
257
+ # FEEDBACK is passed via env var, not CLI arg (avoids shell escaping issues)
258
+ [ "$DRY_RUN" = "true" ] && ARGS="$ARGS --dry-run"
259
+ kody-engine-lite $CMD $ARGS
260
+ fi
251
261
 
252
262
  - name: Pipeline summary
253
263
  if: always()