@kody-ade/kody-engine-lite 0.1.34 → 0.1.36
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 +177 -126
- package/package.json +1 -1
package/dist/bin/cli.js
CHANGED
|
@@ -414,6 +414,14 @@ function getIssue(issueNumber) {
|
|
|
414
414
|
return null;
|
|
415
415
|
}
|
|
416
416
|
}
|
|
417
|
+
function getIssueLabels(issueNumber) {
|
|
418
|
+
try {
|
|
419
|
+
const output = gh(["issue", "view", String(issueNumber), "--json", "labels", "--jq", ".labels[].name"]);
|
|
420
|
+
return output.split("\n").filter(Boolean);
|
|
421
|
+
} catch {
|
|
422
|
+
return [];
|
|
423
|
+
}
|
|
424
|
+
}
|
|
417
425
|
function setLabel(issueNumber, label) {
|
|
418
426
|
try {
|
|
419
427
|
gh(["issue", "edit", String(issueNumber), "--add-label", label]);
|
|
@@ -2516,15 +2524,78 @@ var init_task_resolution = __esm({
|
|
|
2516
2524
|
}
|
|
2517
2525
|
});
|
|
2518
2526
|
|
|
2519
|
-
// src/
|
|
2520
|
-
var entry_exports = {};
|
|
2527
|
+
// src/cli/task-state.ts
|
|
2521
2528
|
import * as fs18 from "fs";
|
|
2522
2529
|
import * as path17 from "path";
|
|
2530
|
+
function resolveTaskAction(issueNumber, existingTaskId, existingState) {
|
|
2531
|
+
if (!existingTaskId || !existingState) {
|
|
2532
|
+
return { action: "start-fresh", taskId: `${issueNumber}-${generateTaskId()}` };
|
|
2533
|
+
}
|
|
2534
|
+
if (existingState.state === "completed") {
|
|
2535
|
+
return { action: "already-completed", taskId: existingTaskId };
|
|
2536
|
+
}
|
|
2537
|
+
if (existingState.state === "running") {
|
|
2538
|
+
return { action: "already-running", taskId: existingTaskId };
|
|
2539
|
+
}
|
|
2540
|
+
if (existingState.state === "failed") {
|
|
2541
|
+
for (const stageName of STAGE_ORDER) {
|
|
2542
|
+
const stage = existingState.stages[stageName];
|
|
2543
|
+
if (!stage) continue;
|
|
2544
|
+
if (stage.error?.includes("paused")) {
|
|
2545
|
+
const idx = STAGE_ORDER.indexOf(stageName);
|
|
2546
|
+
if (idx < STAGE_ORDER.length - 1) {
|
|
2547
|
+
return { action: "resume", taskId: existingTaskId, fromStage: STAGE_ORDER[idx + 1] };
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
if (stage.state === "failed" || stage.state === "pending") {
|
|
2551
|
+
return { action: "resume", taskId: existingTaskId, fromStage: stageName };
|
|
2552
|
+
}
|
|
2553
|
+
}
|
|
2554
|
+
return { action: "resume", taskId: existingTaskId, fromStage: "taskify" };
|
|
2555
|
+
}
|
|
2556
|
+
return { action: "start-fresh", taskId: `${issueNumber}-${generateTaskId()}` };
|
|
2557
|
+
}
|
|
2558
|
+
function resolveForIssue(issueNumber, projectDir) {
|
|
2559
|
+
const existingTaskId = findLatestTaskForIssue(issueNumber, projectDir);
|
|
2560
|
+
if (existingTaskId) {
|
|
2561
|
+
const statusPath = path17.join(projectDir, ".tasks", existingTaskId, "status.json");
|
|
2562
|
+
let existingState = null;
|
|
2563
|
+
if (fs18.existsSync(statusPath)) {
|
|
2564
|
+
try {
|
|
2565
|
+
existingState = JSON.parse(fs18.readFileSync(statusPath, "utf-8"));
|
|
2566
|
+
} catch {
|
|
2567
|
+
}
|
|
2568
|
+
}
|
|
2569
|
+
return resolveTaskAction(issueNumber, existingTaskId, existingState);
|
|
2570
|
+
}
|
|
2571
|
+
try {
|
|
2572
|
+
const labels = getIssueLabels(issueNumber);
|
|
2573
|
+
if (labels.includes("kody:done")) {
|
|
2574
|
+
return { action: "already-completed", taskId: `${issueNumber}-unknown` };
|
|
2575
|
+
}
|
|
2576
|
+
} catch {
|
|
2577
|
+
}
|
|
2578
|
+
return resolveTaskAction(issueNumber, null, null);
|
|
2579
|
+
}
|
|
2580
|
+
var STAGE_ORDER;
|
|
2581
|
+
var init_task_state = __esm({
|
|
2582
|
+
"src/cli/task-state.ts"() {
|
|
2583
|
+
"use strict";
|
|
2584
|
+
init_task_resolution();
|
|
2585
|
+
init_github_api();
|
|
2586
|
+
STAGE_ORDER = ["taskify", "plan", "build", "verify", "review", "review-fix", "ship"];
|
|
2587
|
+
}
|
|
2588
|
+
});
|
|
2589
|
+
|
|
2590
|
+
// src/entry.ts
|
|
2591
|
+
var entry_exports = {};
|
|
2592
|
+
import * as fs19 from "fs";
|
|
2593
|
+
import * as path18 from "path";
|
|
2523
2594
|
async function main() {
|
|
2524
2595
|
const input = parseArgs();
|
|
2525
|
-
const projectDir = input.cwd ?
|
|
2596
|
+
const projectDir = input.cwd ? path18.resolve(input.cwd) : process.cwd();
|
|
2526
2597
|
if (input.cwd) {
|
|
2527
|
-
if (!
|
|
2598
|
+
if (!fs19.existsSync(projectDir)) {
|
|
2528
2599
|
console.error(`--cwd path does not exist: ${projectDir}`);
|
|
2529
2600
|
process.exit(1);
|
|
2530
2601
|
}
|
|
@@ -2534,16 +2605,35 @@ async function main() {
|
|
|
2534
2605
|
}
|
|
2535
2606
|
let taskId = input.taskId;
|
|
2536
2607
|
if (!taskId) {
|
|
2537
|
-
if (
|
|
2538
|
-
const
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2608
|
+
if (input.issueNumber) {
|
|
2609
|
+
const taskAction = resolveForIssue(input.issueNumber, projectDir);
|
|
2610
|
+
logger.info(`Task action: ${taskAction.action}`);
|
|
2611
|
+
if (taskAction.action === "already-completed") {
|
|
2612
|
+
logger.info(`Issue #${input.issueNumber} already completed (task ${taskAction.taskId})`);
|
|
2613
|
+
if (!input.local) {
|
|
2614
|
+
try {
|
|
2615
|
+
postComment(input.issueNumber, `\u2705 Issue #${input.issueNumber} already completed (task \`${taskAction.taskId}\`)`);
|
|
2616
|
+
} catch {
|
|
2617
|
+
}
|
|
2618
|
+
}
|
|
2619
|
+
process.exit(0);
|
|
2620
|
+
}
|
|
2621
|
+
if (taskAction.action === "already-running") {
|
|
2622
|
+
logger.info(`Issue #${input.issueNumber} already running (task ${taskAction.taskId})`);
|
|
2623
|
+
if (!input.local) {
|
|
2624
|
+
try {
|
|
2625
|
+
postComment(input.issueNumber, `\u23F3 Pipeline already running for issue #${input.issueNumber} (task \`${taskAction.taskId}\`)`);
|
|
2626
|
+
} catch {
|
|
2627
|
+
}
|
|
2628
|
+
}
|
|
2629
|
+
process.exit(0);
|
|
2630
|
+
}
|
|
2631
|
+
taskId = taskAction.taskId;
|
|
2632
|
+
if (taskAction.action === "resume") {
|
|
2633
|
+
input.fromStage = taskAction.fromStage;
|
|
2634
|
+
input.command = "rerun";
|
|
2635
|
+
logger.info(`Resuming task ${taskId} from ${taskAction.fromStage}`);
|
|
2542
2636
|
}
|
|
2543
|
-
taskId = found;
|
|
2544
|
-
logger.info(`Found latest task for issue #${input.issueNumber}: ${taskId}`);
|
|
2545
|
-
} else if (input.issueNumber) {
|
|
2546
|
-
taskId = `${input.issueNumber}-${generateTaskId()}`;
|
|
2547
2637
|
} else if (input.command === "run" && input.task) {
|
|
2548
2638
|
taskId = generateTaskId();
|
|
2549
2639
|
} else {
|
|
@@ -2551,8 +2641,8 @@ async function main() {
|
|
|
2551
2641
|
process.exit(1);
|
|
2552
2642
|
}
|
|
2553
2643
|
}
|
|
2554
|
-
const taskDir =
|
|
2555
|
-
|
|
2644
|
+
const taskDir = path18.join(projectDir, ".tasks", taskId);
|
|
2645
|
+
fs19.mkdirSync(taskDir, { recursive: true });
|
|
2556
2646
|
if (input.command === "status") {
|
|
2557
2647
|
printStatus(taskId, taskDir);
|
|
2558
2648
|
return;
|
|
@@ -2560,67 +2650,27 @@ async function main() {
|
|
|
2560
2650
|
logger.info("Preflight checks:");
|
|
2561
2651
|
runPreflight();
|
|
2562
2652
|
if (input.task) {
|
|
2563
|
-
|
|
2653
|
+
fs19.writeFileSync(path18.join(taskDir, "task.md"), input.task);
|
|
2564
2654
|
}
|
|
2565
|
-
const taskMdPath =
|
|
2566
|
-
if (!
|
|
2655
|
+
const taskMdPath = path18.join(taskDir, "task.md");
|
|
2656
|
+
if (!fs19.existsSync(taskMdPath) && input.issueNumber) {
|
|
2567
2657
|
logger.info(`Fetching issue #${input.issueNumber} body as task...`);
|
|
2568
2658
|
const issue = getIssue(input.issueNumber);
|
|
2569
2659
|
if (issue) {
|
|
2570
2660
|
const taskContent = `# ${issue.title}
|
|
2571
2661
|
|
|
2572
2662
|
${issue.body ?? ""}`;
|
|
2573
|
-
|
|
2663
|
+
fs19.writeFileSync(taskMdPath, taskContent);
|
|
2574
2664
|
logger.info(` Task loaded from issue #${input.issueNumber}: ${issue.title}`);
|
|
2575
2665
|
}
|
|
2576
2666
|
}
|
|
2577
|
-
if (
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
process.exit(1);
|
|
2581
|
-
}
|
|
2667
|
+
if (!fs19.existsSync(taskMdPath)) {
|
|
2668
|
+
console.error("No task.md found. Provide --task, --issue-number, or ensure .tasks/<id>/task.md exists.");
|
|
2669
|
+
process.exit(1);
|
|
2582
2670
|
}
|
|
2583
2671
|
if (input.command === "fix" && !input.fromStage) {
|
|
2584
2672
|
input.fromStage = "build";
|
|
2585
2673
|
}
|
|
2586
|
-
if (input.command === "rerun" && !input.fromStage) {
|
|
2587
|
-
const statusPath = path17.join(taskDir, "status.json");
|
|
2588
|
-
if (fs18.existsSync(statusPath)) {
|
|
2589
|
-
try {
|
|
2590
|
-
const status = JSON.parse(fs18.readFileSync(statusPath, "utf-8"));
|
|
2591
|
-
const stageNames = ["taskify", "plan", "build", "verify", "review", "review-fix", "ship"];
|
|
2592
|
-
let foundPaused = false;
|
|
2593
|
-
for (const name of stageNames) {
|
|
2594
|
-
const s = status.stages[name];
|
|
2595
|
-
if (s?.error?.includes("paused")) {
|
|
2596
|
-
const idx = stageNames.indexOf(name);
|
|
2597
|
-
if (idx < stageNames.length - 1) {
|
|
2598
|
-
input.fromStage = stageNames[idx + 1];
|
|
2599
|
-
foundPaused = true;
|
|
2600
|
-
logger.info(`Auto-detected resume from: ${input.fromStage} (after paused ${name})`);
|
|
2601
|
-
break;
|
|
2602
|
-
}
|
|
2603
|
-
}
|
|
2604
|
-
if (s?.state === "failed" || s?.state === "pending") {
|
|
2605
|
-
input.fromStage = name;
|
|
2606
|
-
foundPaused = true;
|
|
2607
|
-
logger.info(`Auto-detected resume from: ${input.fromStage}`);
|
|
2608
|
-
break;
|
|
2609
|
-
}
|
|
2610
|
-
}
|
|
2611
|
-
if (!foundPaused) {
|
|
2612
|
-
input.fromStage = "taskify";
|
|
2613
|
-
logger.info("No paused/failed stage found, resuming from taskify");
|
|
2614
|
-
}
|
|
2615
|
-
} catch {
|
|
2616
|
-
console.error("--from <stage> is required (could not read status.json)");
|
|
2617
|
-
process.exit(1);
|
|
2618
|
-
}
|
|
2619
|
-
} else {
|
|
2620
|
-
logger.info("No status.json found \u2014 running full pipeline with feedback");
|
|
2621
|
-
input.command = "run";
|
|
2622
|
-
}
|
|
2623
|
-
}
|
|
2624
2674
|
const config = getProjectConfig();
|
|
2625
2675
|
let litellmProcess = null;
|
|
2626
2676
|
const cleanupLitellm = () => {
|
|
@@ -2694,7 +2744,7 @@ To rerun: \`@kody rerun ${taskId} --from <stage>\``
|
|
|
2694
2744
|
}
|
|
2695
2745
|
}
|
|
2696
2746
|
const state = await runPipeline(ctx);
|
|
2697
|
-
const files =
|
|
2747
|
+
const files = fs19.readdirSync(taskDir);
|
|
2698
2748
|
console.log(`
|
|
2699
2749
|
Artifacts in ${taskDir}:`);
|
|
2700
2750
|
for (const f of files) {
|
|
@@ -2738,6 +2788,7 @@ var init_entry = __esm({
|
|
|
2738
2788
|
init_args();
|
|
2739
2789
|
init_litellm();
|
|
2740
2790
|
init_task_resolution();
|
|
2791
|
+
init_task_state();
|
|
2741
2792
|
main().catch(async (err) => {
|
|
2742
2793
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2743
2794
|
console.error(msg);
|
|
@@ -2755,15 +2806,15 @@ var init_entry = __esm({
|
|
|
2755
2806
|
});
|
|
2756
2807
|
|
|
2757
2808
|
// src/bin/cli.ts
|
|
2758
|
-
import * as
|
|
2759
|
-
import * as
|
|
2809
|
+
import * as fs20 from "fs";
|
|
2810
|
+
import * as path19 from "path";
|
|
2760
2811
|
import { execFileSync as execFileSync11 } from "child_process";
|
|
2761
2812
|
import { fileURLToPath } from "url";
|
|
2762
|
-
var __dirname =
|
|
2763
|
-
var PKG_ROOT =
|
|
2813
|
+
var __dirname = path19.dirname(fileURLToPath(import.meta.url));
|
|
2814
|
+
var PKG_ROOT = path19.resolve(__dirname, "..", "..");
|
|
2764
2815
|
function getVersion() {
|
|
2765
|
-
const pkgPath =
|
|
2766
|
-
const pkg = JSON.parse(
|
|
2816
|
+
const pkgPath = path19.join(PKG_ROOT, "package.json");
|
|
2817
|
+
const pkg = JSON.parse(fs20.readFileSync(pkgPath, "utf-8"));
|
|
2767
2818
|
return pkg.version;
|
|
2768
2819
|
}
|
|
2769
2820
|
function checkCommand2(name, args2, fix) {
|
|
@@ -2779,7 +2830,7 @@ function checkCommand2(name, args2, fix) {
|
|
|
2779
2830
|
}
|
|
2780
2831
|
}
|
|
2781
2832
|
function checkFile(filePath, description, fix) {
|
|
2782
|
-
if (
|
|
2833
|
+
if (fs20.existsSync(filePath)) {
|
|
2783
2834
|
return { name: description, ok: true, detail: filePath };
|
|
2784
2835
|
}
|
|
2785
2836
|
return { name: description, ok: false, fix };
|
|
@@ -2851,10 +2902,10 @@ function checkGhSecret(repoSlug, secretName) {
|
|
|
2851
2902
|
}
|
|
2852
2903
|
function detectArchitecture(cwd) {
|
|
2853
2904
|
const detected = [];
|
|
2854
|
-
const pkgPath =
|
|
2855
|
-
if (
|
|
2905
|
+
const pkgPath = path19.join(cwd, "package.json");
|
|
2906
|
+
if (fs20.existsSync(pkgPath)) {
|
|
2856
2907
|
try {
|
|
2857
|
-
const pkg = JSON.parse(
|
|
2908
|
+
const pkg = JSON.parse(fs20.readFileSync(pkgPath, "utf-8"));
|
|
2858
2909
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
2859
2910
|
if (allDeps.next) detected.push(`- Framework: Next.js ${allDeps.next}`);
|
|
2860
2911
|
else if (allDeps.react) detected.push(`- Framework: React ${allDeps.react}`);
|
|
@@ -2877,41 +2928,41 @@ function detectArchitecture(cwd) {
|
|
|
2877
2928
|
if (allDeps.tailwindcss) detected.push(`- CSS: Tailwind CSS ${allDeps.tailwindcss}`);
|
|
2878
2929
|
if (pkg.type === "module") detected.push("- Module system: ESM");
|
|
2879
2930
|
else detected.push("- Module system: CommonJS");
|
|
2880
|
-
if (
|
|
2881
|
-
else if (
|
|
2882
|
-
else if (
|
|
2883
|
-
else if (
|
|
2931
|
+
if (fs20.existsSync(path19.join(cwd, "pnpm-lock.yaml"))) detected.push("- Package manager: pnpm");
|
|
2932
|
+
else if (fs20.existsSync(path19.join(cwd, "yarn.lock"))) detected.push("- Package manager: yarn");
|
|
2933
|
+
else if (fs20.existsSync(path19.join(cwd, "bun.lockb"))) detected.push("- Package manager: bun");
|
|
2934
|
+
else if (fs20.existsSync(path19.join(cwd, "package-lock.json"))) detected.push("- Package manager: npm");
|
|
2884
2935
|
} catch {
|
|
2885
2936
|
}
|
|
2886
2937
|
}
|
|
2887
2938
|
try {
|
|
2888
|
-
const entries =
|
|
2939
|
+
const entries = fs20.readdirSync(cwd, { withFileTypes: true });
|
|
2889
2940
|
const dirs = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules").map((e) => e.name);
|
|
2890
2941
|
if (dirs.length > 0) detected.push(`- Top-level directories: ${dirs.join(", ")}`);
|
|
2891
2942
|
} catch {
|
|
2892
2943
|
}
|
|
2893
|
-
const srcDir =
|
|
2894
|
-
if (
|
|
2944
|
+
const srcDir = path19.join(cwd, "src");
|
|
2945
|
+
if (fs20.existsSync(srcDir)) {
|
|
2895
2946
|
try {
|
|
2896
|
-
const srcEntries =
|
|
2947
|
+
const srcEntries = fs20.readdirSync(srcDir, { withFileTypes: true });
|
|
2897
2948
|
const srcDirs = srcEntries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
2898
2949
|
if (srcDirs.length > 0) detected.push(`- src/ structure: ${srcDirs.join(", ")}`);
|
|
2899
2950
|
} catch {
|
|
2900
2951
|
}
|
|
2901
2952
|
}
|
|
2902
2953
|
const configs = [];
|
|
2903
|
-
if (
|
|
2904
|
-
if (
|
|
2905
|
-
if (
|
|
2906
|
-
if (
|
|
2954
|
+
if (fs20.existsSync(path19.join(cwd, "tsconfig.json"))) configs.push("tsconfig.json");
|
|
2955
|
+
if (fs20.existsSync(path19.join(cwd, "docker-compose.yml")) || fs20.existsSync(path19.join(cwd, "docker-compose.yaml"))) configs.push("docker-compose");
|
|
2956
|
+
if (fs20.existsSync(path19.join(cwd, "Dockerfile"))) configs.push("Dockerfile");
|
|
2957
|
+
if (fs20.existsSync(path19.join(cwd, ".env")) || fs20.existsSync(path19.join(cwd, ".env.local"))) configs.push(".env");
|
|
2907
2958
|
if (configs.length > 0) detected.push(`- Config files: ${configs.join(", ")}`);
|
|
2908
2959
|
return detected;
|
|
2909
2960
|
}
|
|
2910
2961
|
function detectBasicConfig(cwd) {
|
|
2911
2962
|
let pm = "pnpm";
|
|
2912
|
-
if (
|
|
2913
|
-
else if (
|
|
2914
|
-
else if (!
|
|
2963
|
+
if (fs20.existsSync(path19.join(cwd, "yarn.lock"))) pm = "yarn";
|
|
2964
|
+
else if (fs20.existsSync(path19.join(cwd, "bun.lockb"))) pm = "bun";
|
|
2965
|
+
else if (!fs20.existsSync(path19.join(cwd, "pnpm-lock.yaml")) && fs20.existsSync(path19.join(cwd, "package-lock.json"))) pm = "npm";
|
|
2915
2966
|
let defaultBranch = "main";
|
|
2916
2967
|
try {
|
|
2917
2968
|
const ref = execFileSync11("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
|
|
@@ -2955,9 +3006,9 @@ function smartInit(cwd) {
|
|
|
2955
3006
|
const basic = detectBasicConfig(cwd);
|
|
2956
3007
|
let context = "";
|
|
2957
3008
|
const readIfExists = (rel, maxChars = 3e3) => {
|
|
2958
|
-
const p =
|
|
2959
|
-
if (
|
|
2960
|
-
const content =
|
|
3009
|
+
const p = path19.join(cwd, rel);
|
|
3010
|
+
if (fs20.existsSync(p)) {
|
|
3011
|
+
const content = fs20.readFileSync(p, "utf-8");
|
|
2961
3012
|
return content.slice(0, maxChars);
|
|
2962
3013
|
}
|
|
2963
3014
|
return null;
|
|
@@ -2983,14 +3034,14 @@ ${claudeMd}
|
|
|
2983
3034
|
|
|
2984
3035
|
`;
|
|
2985
3036
|
try {
|
|
2986
|
-
const topDirs =
|
|
3037
|
+
const topDirs = fs20.readdirSync(cwd, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules").map((e) => e.name);
|
|
2987
3038
|
context += `## Top-level directories
|
|
2988
3039
|
${topDirs.join(", ")}
|
|
2989
3040
|
|
|
2990
3041
|
`;
|
|
2991
|
-
const srcDir =
|
|
2992
|
-
if (
|
|
2993
|
-
const srcDirs =
|
|
3042
|
+
const srcDir = path19.join(cwd, "src");
|
|
3043
|
+
if (fs20.existsSync(srcDir)) {
|
|
3044
|
+
const srcDirs = fs20.readdirSync(srcDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
2994
3045
|
context += `## src/ subdirectories
|
|
2995
3046
|
${srcDirs.join(", ")}
|
|
2996
3047
|
|
|
@@ -3000,7 +3051,7 @@ ${srcDirs.join(", ")}
|
|
|
3000
3051
|
}
|
|
3001
3052
|
const existingFiles = [];
|
|
3002
3053
|
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"]) {
|
|
3003
|
-
if (
|
|
3054
|
+
if (fs20.existsSync(path19.join(cwd, f))) existingFiles.push(f);
|
|
3004
3055
|
}
|
|
3005
3056
|
if (existingFiles.length) context += `## Config files present
|
|
3006
3057
|
${existingFiles.join(", ")}
|
|
@@ -3106,7 +3157,7 @@ ${context}`;
|
|
|
3106
3157
|
function validateQualityCommands(cwd, config, pm) {
|
|
3107
3158
|
let scripts = {};
|
|
3108
3159
|
try {
|
|
3109
|
-
const pkg = JSON.parse(
|
|
3160
|
+
const pkg = JSON.parse(fs20.readFileSync(path19.join(cwd, "package.json"), "utf-8"));
|
|
3110
3161
|
scripts = pkg.scripts ?? {};
|
|
3111
3162
|
} catch {
|
|
3112
3163
|
return;
|
|
@@ -3140,7 +3191,7 @@ function validateQualityCommands(cwd, config, pm) {
|
|
|
3140
3191
|
function buildFallbackConfig(cwd, basic) {
|
|
3141
3192
|
const pkg = (() => {
|
|
3142
3193
|
try {
|
|
3143
|
-
return JSON.parse(
|
|
3194
|
+
return JSON.parse(fs20.readFileSync(path19.join(cwd, "package.json"), "utf-8"));
|
|
3144
3195
|
} catch {
|
|
3145
3196
|
return {};
|
|
3146
3197
|
}
|
|
@@ -3180,34 +3231,34 @@ function initCommand(opts) {
|
|
|
3180
3231
|
console.log(`Project: ${cwd}
|
|
3181
3232
|
`);
|
|
3182
3233
|
console.log("\u2500\u2500 Files \u2500\u2500");
|
|
3183
|
-
const templatesDir =
|
|
3184
|
-
const workflowSrc =
|
|
3185
|
-
const workflowDest =
|
|
3186
|
-
if (!
|
|
3234
|
+
const templatesDir = path19.join(PKG_ROOT, "templates");
|
|
3235
|
+
const workflowSrc = path19.join(templatesDir, "kody.yml");
|
|
3236
|
+
const workflowDest = path19.join(cwd, ".github", "workflows", "kody.yml");
|
|
3237
|
+
if (!fs20.existsSync(workflowSrc)) {
|
|
3187
3238
|
console.error(" \u2717 Template kody.yml not found in package");
|
|
3188
3239
|
process.exit(1);
|
|
3189
3240
|
}
|
|
3190
|
-
if (
|
|
3241
|
+
if (fs20.existsSync(workflowDest) && !opts.force) {
|
|
3191
3242
|
console.log(" \u25CB .github/workflows/kody.yml (exists, use --force to overwrite)");
|
|
3192
3243
|
} else {
|
|
3193
|
-
|
|
3194
|
-
|
|
3244
|
+
fs20.mkdirSync(path19.dirname(workflowDest), { recursive: true });
|
|
3245
|
+
fs20.copyFileSync(workflowSrc, workflowDest);
|
|
3195
3246
|
console.log(" \u2713 .github/workflows/kody.yml");
|
|
3196
3247
|
}
|
|
3197
|
-
const configDest =
|
|
3248
|
+
const configDest = path19.join(cwd, "kody.config.json");
|
|
3198
3249
|
let smartResult = null;
|
|
3199
|
-
if (!
|
|
3250
|
+
if (!fs20.existsSync(configDest) || opts.force) {
|
|
3200
3251
|
smartResult = smartInit(cwd);
|
|
3201
|
-
|
|
3252
|
+
fs20.writeFileSync(configDest, JSON.stringify(smartResult.config, null, 2) + "\n");
|
|
3202
3253
|
console.log(" \u2713 kody.config.json (auto-configured)");
|
|
3203
3254
|
} else {
|
|
3204
3255
|
console.log(" \u25CB kody.config.json (exists)");
|
|
3205
3256
|
}
|
|
3206
|
-
const gitignorePath =
|
|
3207
|
-
if (
|
|
3208
|
-
const content =
|
|
3257
|
+
const gitignorePath = path19.join(cwd, ".gitignore");
|
|
3258
|
+
if (fs20.existsSync(gitignorePath)) {
|
|
3259
|
+
const content = fs20.readFileSync(gitignorePath, "utf-8");
|
|
3209
3260
|
if (!content.includes(".tasks/")) {
|
|
3210
|
-
|
|
3261
|
+
fs20.appendFileSync(gitignorePath, "\n.tasks/\n");
|
|
3211
3262
|
console.log(" \u2713 .gitignore (added .tasks/)");
|
|
3212
3263
|
} else {
|
|
3213
3264
|
console.log(" \u25CB .gitignore (.tasks/ already present)");
|
|
@@ -3220,7 +3271,7 @@ function initCommand(opts) {
|
|
|
3220
3271
|
checkCommand2("git", ["--version"], "Install git"),
|
|
3221
3272
|
checkCommand2("node", ["--version"], "Install Node.js >= 22"),
|
|
3222
3273
|
checkCommand2("pnpm", ["--version"], "Install: npm i -g pnpm"),
|
|
3223
|
-
checkFile(
|
|
3274
|
+
checkFile(path19.join(cwd, "package.json"), "package.json", "Run: pnpm init")
|
|
3224
3275
|
];
|
|
3225
3276
|
for (const c of checks) {
|
|
3226
3277
|
if (c.ok) {
|
|
@@ -3297,9 +3348,9 @@ function initCommand(opts) {
|
|
|
3297
3348
|
}
|
|
3298
3349
|
}
|
|
3299
3350
|
console.log("\n\u2500\u2500 Config \u2500\u2500");
|
|
3300
|
-
if (
|
|
3351
|
+
if (fs20.existsSync(configDest)) {
|
|
3301
3352
|
try {
|
|
3302
|
-
const config = JSON.parse(
|
|
3353
|
+
const config = JSON.parse(fs20.readFileSync(configDest, "utf-8"));
|
|
3303
3354
|
const configChecks = [];
|
|
3304
3355
|
if (config.github?.owner && config.github?.repo) {
|
|
3305
3356
|
configChecks.push({ name: "github.owner/repo", ok: true, detail: `${config.github.owner}/${config.github.repo}` });
|
|
@@ -3326,21 +3377,21 @@ function initCommand(opts) {
|
|
|
3326
3377
|
}
|
|
3327
3378
|
}
|
|
3328
3379
|
console.log("\n\u2500\u2500 Project Memory \u2500\u2500");
|
|
3329
|
-
const memoryDir =
|
|
3330
|
-
|
|
3331
|
-
const archPath =
|
|
3332
|
-
const conventionsPath =
|
|
3333
|
-
if (
|
|
3380
|
+
const memoryDir = path19.join(cwd, ".kody", "memory");
|
|
3381
|
+
fs20.mkdirSync(memoryDir, { recursive: true });
|
|
3382
|
+
const archPath = path19.join(memoryDir, "architecture.md");
|
|
3383
|
+
const conventionsPath = path19.join(memoryDir, "conventions.md");
|
|
3384
|
+
if (fs20.existsSync(archPath) && !opts.force) {
|
|
3334
3385
|
console.log(" \u25CB .kody/memory/architecture.md (exists, use --force to regenerate)");
|
|
3335
3386
|
} else if (smartResult?.architecture) {
|
|
3336
|
-
|
|
3387
|
+
fs20.writeFileSync(archPath, smartResult.architecture);
|
|
3337
3388
|
const lineCount = smartResult.architecture.split("\n").length;
|
|
3338
3389
|
console.log(` \u2713 .kody/memory/architecture.md (${lineCount} lines, LLM-generated)`);
|
|
3339
3390
|
} else {
|
|
3340
3391
|
const archItems = detectArchitecture(cwd);
|
|
3341
3392
|
if (archItems.length > 0) {
|
|
3342
3393
|
const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
3343
|
-
|
|
3394
|
+
fs20.writeFileSync(archPath, `# Architecture (auto-detected ${timestamp2})
|
|
3344
3395
|
|
|
3345
3396
|
## Overview
|
|
3346
3397
|
${archItems.join("\n")}
|
|
@@ -3350,14 +3401,14 @@ ${archItems.join("\n")}
|
|
|
3350
3401
|
console.log(" \u25CB No architecture detected");
|
|
3351
3402
|
}
|
|
3352
3403
|
}
|
|
3353
|
-
if (
|
|
3404
|
+
if (fs20.existsSync(conventionsPath) && !opts.force) {
|
|
3354
3405
|
console.log(" \u25CB .kody/memory/conventions.md (exists, use --force to regenerate)");
|
|
3355
3406
|
} else if (smartResult?.conventions) {
|
|
3356
|
-
|
|
3407
|
+
fs20.writeFileSync(conventionsPath, smartResult.conventions);
|
|
3357
3408
|
const lineCount = smartResult.conventions.split("\n").length;
|
|
3358
3409
|
console.log(` \u2713 .kody/memory/conventions.md (${lineCount} lines, LLM-generated)`);
|
|
3359
3410
|
} else {
|
|
3360
|
-
|
|
3411
|
+
fs20.writeFileSync(conventionsPath, "# Conventions\n\n<!-- Auto-learned conventions will be appended here -->\n");
|
|
3361
3412
|
console.log(" \u2713 .kody/memory/conventions.md (seed)");
|
|
3362
3413
|
}
|
|
3363
3414
|
console.log("\n\u2500\u2500 Git \u2500\u2500");
|
|
@@ -3366,7 +3417,7 @@ ${archItems.join("\n")}
|
|
|
3366
3417
|
"kody.config.json",
|
|
3367
3418
|
".kody/memory/architecture.md",
|
|
3368
3419
|
".kody/memory/conventions.md"
|
|
3369
|
-
].filter((f) =>
|
|
3420
|
+
].filter((f) => fs20.existsSync(path19.join(cwd, f)));
|
|
3370
3421
|
if (filesToCommit.length > 0) {
|
|
3371
3422
|
try {
|
|
3372
3423
|
execFileSync11("git", ["add", ...filesToCommit], { cwd, stdio: "pipe" });
|