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