@kody-ade/kody-engine 0.4.30 → 0.4.32
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/kody.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// package.json
|
|
4
4
|
var package_default = {
|
|
5
5
|
name: "@kody-ade/kody-engine",
|
|
6
|
-
version: "0.4.
|
|
6
|
+
version: "0.4.32",
|
|
7
7
|
description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
|
|
8
8
|
license: "MIT",
|
|
9
9
|
type: "module",
|
|
@@ -51,9 +51,9 @@ var package_default = {
|
|
|
51
51
|
};
|
|
52
52
|
|
|
53
53
|
// src/chat-cli.ts
|
|
54
|
-
import { execFileSync as
|
|
55
|
-
import * as
|
|
56
|
-
import * as
|
|
54
|
+
import { execFileSync as execFileSync32 } from "child_process";
|
|
55
|
+
import * as fs30 from "fs";
|
|
56
|
+
import * as path28 from "path";
|
|
57
57
|
|
|
58
58
|
// src/chat/events.ts
|
|
59
59
|
import * as fs from "fs";
|
|
@@ -912,9 +912,9 @@ async function emit2(sink, type, sessionId, suffix, payload) {
|
|
|
912
912
|
}
|
|
913
913
|
|
|
914
914
|
// src/kody-cli.ts
|
|
915
|
-
import { execFileSync as
|
|
916
|
-
import * as
|
|
917
|
-
import * as
|
|
915
|
+
import { execFileSync as execFileSync31 } from "child_process";
|
|
916
|
+
import * as fs29 from "fs";
|
|
917
|
+
import * as path27 from "path";
|
|
918
918
|
|
|
919
919
|
// src/dispatch.ts
|
|
920
920
|
import * as fs7 from "fs";
|
|
@@ -1299,9 +1299,9 @@ function coerceBare(spec, value) {
|
|
|
1299
1299
|
}
|
|
1300
1300
|
|
|
1301
1301
|
// src/executor.ts
|
|
1302
|
-
import { execFileSync as
|
|
1303
|
-
import * as
|
|
1304
|
-
import * as
|
|
1302
|
+
import { execFileSync as execFileSync30, spawn as spawn5 } from "child_process";
|
|
1303
|
+
import * as fs28 from "fs";
|
|
1304
|
+
import * as path26 from "path";
|
|
1305
1305
|
|
|
1306
1306
|
// src/issue.ts
|
|
1307
1307
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
@@ -2749,6 +2749,301 @@ function defaultLabelMap() {
|
|
|
2749
2749
|
};
|
|
2750
2750
|
}
|
|
2751
2751
|
|
|
2752
|
+
// src/goal/operations.ts
|
|
2753
|
+
import { execFileSync as execFileSync9 } from "child_process";
|
|
2754
|
+
|
|
2755
|
+
// src/goal/labels.ts
|
|
2756
|
+
function goalLabel(goalId) {
|
|
2757
|
+
return `goal:${goalId}`;
|
|
2758
|
+
}
|
|
2759
|
+
var DISPATCHED_LABEL = "goal-runner:dispatched";
|
|
2760
|
+
var FAILED_LABEL = "goal-runner:failed";
|
|
2761
|
+
var UMBRELLA_BUILDING_LABEL = "kody:building";
|
|
2762
|
+
var TICK_LABELS = [
|
|
2763
|
+
{
|
|
2764
|
+
name: DISPATCHED_LABEL,
|
|
2765
|
+
color: "ededed",
|
|
2766
|
+
description: "kody goal-runner: already dispatched this tick"
|
|
2767
|
+
},
|
|
2768
|
+
{
|
|
2769
|
+
name: FAILED_LABEL,
|
|
2770
|
+
color: "b60205",
|
|
2771
|
+
description: "kody goal-runner: task failed; needs human attention"
|
|
2772
|
+
}
|
|
2773
|
+
];
|
|
2774
|
+
|
|
2775
|
+
// src/goal/operations.ts
|
|
2776
|
+
function fail(err) {
|
|
2777
|
+
if (err instanceof Error) {
|
|
2778
|
+
const lines = err.message.split("\n").filter(Boolean);
|
|
2779
|
+
return { ok: false, error: lines[0] ?? err.message };
|
|
2780
|
+
}
|
|
2781
|
+
return { ok: false, error: String(err) };
|
|
2782
|
+
}
|
|
2783
|
+
function listGoalIssues(goalId, excludeIssueNumber, cwd) {
|
|
2784
|
+
try {
|
|
2785
|
+
const out = gh(
|
|
2786
|
+
[
|
|
2787
|
+
"api",
|
|
2788
|
+
`repos/{owner}/{repo}/issues?labels=${goalLabel(goalId)}&state=all&per_page=100`,
|
|
2789
|
+
"--jq",
|
|
2790
|
+
"[.[] | select(.pull_request == null) | {number, state: (.state | ascii_upcase), labels: [.labels[].name]}]"
|
|
2791
|
+
],
|
|
2792
|
+
{ cwd }
|
|
2793
|
+
);
|
|
2794
|
+
const arr = JSON.parse(out);
|
|
2795
|
+
const filtered = excludeIssueNumber !== void 0 ? arr.filter((i) => i.number !== excludeIssueNumber) : arr;
|
|
2796
|
+
return { ok: true, value: filtered };
|
|
2797
|
+
} catch (err) {
|
|
2798
|
+
return fail(err);
|
|
2799
|
+
}
|
|
2800
|
+
}
|
|
2801
|
+
function ensureLabel(name, color, description, cwd) {
|
|
2802
|
+
try {
|
|
2803
|
+
gh(["label", "create", name, "--color", color, "--description", description, "--force"], { cwd });
|
|
2804
|
+
return { ok: true };
|
|
2805
|
+
} catch (err) {
|
|
2806
|
+
return fail(err);
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
function addLabel2(issueNumber, label, cwd) {
|
|
2810
|
+
try {
|
|
2811
|
+
gh(["issue", "edit", String(issueNumber), "--add-label", label], { cwd });
|
|
2812
|
+
return { ok: true };
|
|
2813
|
+
} catch (err) {
|
|
2814
|
+
return fail(err);
|
|
2815
|
+
}
|
|
2816
|
+
}
|
|
2817
|
+
function commentOnIssue(issueNumber, body, cwd) {
|
|
2818
|
+
try {
|
|
2819
|
+
gh(["issue", "comment", String(issueNumber), "--body", body], { cwd });
|
|
2820
|
+
return { ok: true };
|
|
2821
|
+
} catch (err) {
|
|
2822
|
+
return fail(err);
|
|
2823
|
+
}
|
|
2824
|
+
}
|
|
2825
|
+
function closeIssue(issueNumber, options, cwd) {
|
|
2826
|
+
try {
|
|
2827
|
+
if (options.comment) {
|
|
2828
|
+
gh(["issue", "comment", String(issueNumber), "--body", options.comment], { cwd });
|
|
2829
|
+
}
|
|
2830
|
+
const args = ["issue", "close", String(issueNumber)];
|
|
2831
|
+
if (options.reason) args.push("--reason", options.reason);
|
|
2832
|
+
gh(args, { cwd });
|
|
2833
|
+
return { ok: true };
|
|
2834
|
+
} catch (err) {
|
|
2835
|
+
return fail(err);
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2838
|
+
function getIssueState(issueNumber, cwd) {
|
|
2839
|
+
try {
|
|
2840
|
+
const out = gh(["issue", "view", String(issueNumber), "--json", "state", "--jq", ".state"], {
|
|
2841
|
+
cwd
|
|
2842
|
+
});
|
|
2843
|
+
const norm = out.trim().toUpperCase();
|
|
2844
|
+
if (norm !== "OPEN" && norm !== "CLOSED") {
|
|
2845
|
+
return { ok: false, error: `unexpected state: ${out}` };
|
|
2846
|
+
}
|
|
2847
|
+
return { ok: true, value: norm };
|
|
2848
|
+
} catch (err) {
|
|
2849
|
+
return fail(err);
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
function findUmbrellaByTitle(goalId, title, cwd) {
|
|
2853
|
+
try {
|
|
2854
|
+
const out = gh(
|
|
2855
|
+
[
|
|
2856
|
+
"api",
|
|
2857
|
+
`repos/{owner}/{repo}/issues?labels=${goalLabel(goalId)}&state=all&per_page=100`,
|
|
2858
|
+
"--jq",
|
|
2859
|
+
`[.[] | select(.pull_request == null) | select(.title == "${title.replace(/"/g, '\\"')}")] | (map(select(.state == "open")) + map(select(.state != "open")))[0].number // empty`
|
|
2860
|
+
],
|
|
2861
|
+
{ cwd }
|
|
2862
|
+
);
|
|
2863
|
+
const trimmed = out.trim();
|
|
2864
|
+
if (!trimmed) return { ok: true, value: null };
|
|
2865
|
+
const n = Number.parseInt(trimmed, 10);
|
|
2866
|
+
if (!Number.isFinite(n)) return { ok: true, value: null };
|
|
2867
|
+
return { ok: true, value: n };
|
|
2868
|
+
} catch (err) {
|
|
2869
|
+
return fail(err);
|
|
2870
|
+
}
|
|
2871
|
+
}
|
|
2872
|
+
function createIssue(args, cwd) {
|
|
2873
|
+
try {
|
|
2874
|
+
const cliArgs = ["issue", "create", "--title", args.title, "--body", args.body];
|
|
2875
|
+
for (const l of args.labels) cliArgs.push("--label", l);
|
|
2876
|
+
const url = gh(cliArgs, { cwd });
|
|
2877
|
+
const match = url.match(/\/issues\/(\d+)/);
|
|
2878
|
+
if (!match?.[1]) return { ok: false, error: `couldn't parse issue number from URL: ${url}` };
|
|
2879
|
+
return { ok: true, value: Number.parseInt(match[1], 10) };
|
|
2880
|
+
} catch (err) {
|
|
2881
|
+
return fail(err);
|
|
2882
|
+
}
|
|
2883
|
+
}
|
|
2884
|
+
function listPrsByBase(base, state, cwd) {
|
|
2885
|
+
try {
|
|
2886
|
+
const out = gh(
|
|
2887
|
+
[
|
|
2888
|
+
"pr",
|
|
2889
|
+
"list",
|
|
2890
|
+
"--base",
|
|
2891
|
+
base,
|
|
2892
|
+
"--state",
|
|
2893
|
+
state,
|
|
2894
|
+
"--limit",
|
|
2895
|
+
"50",
|
|
2896
|
+
"--json",
|
|
2897
|
+
"number,isDraft,mergeable,mergeStateStatus,url,headRefName,body"
|
|
2898
|
+
],
|
|
2899
|
+
{ cwd }
|
|
2900
|
+
);
|
|
2901
|
+
return { ok: true, value: JSON.parse(out) };
|
|
2902
|
+
} catch (err) {
|
|
2903
|
+
return fail(err);
|
|
2904
|
+
}
|
|
2905
|
+
}
|
|
2906
|
+
function listPrsByHead(head, state, cwd) {
|
|
2907
|
+
try {
|
|
2908
|
+
const out = gh(
|
|
2909
|
+
[
|
|
2910
|
+
"pr",
|
|
2911
|
+
"list",
|
|
2912
|
+
"--head",
|
|
2913
|
+
head,
|
|
2914
|
+
"--state",
|
|
2915
|
+
state,
|
|
2916
|
+
"--json",
|
|
2917
|
+
"number,isDraft,mergeable,mergeStateStatus,url,headRefName,body"
|
|
2918
|
+
],
|
|
2919
|
+
{ cwd }
|
|
2920
|
+
);
|
|
2921
|
+
return { ok: true, value: JSON.parse(out) };
|
|
2922
|
+
} catch (err) {
|
|
2923
|
+
return fail(err);
|
|
2924
|
+
}
|
|
2925
|
+
}
|
|
2926
|
+
function mergePrSquash(prNumber, cwd) {
|
|
2927
|
+
try {
|
|
2928
|
+
gh(["pr", "merge", String(prNumber), "--squash", "--delete-branch"], { cwd });
|
|
2929
|
+
return { ok: true };
|
|
2930
|
+
} catch (err) {
|
|
2931
|
+
return fail(err);
|
|
2932
|
+
}
|
|
2933
|
+
}
|
|
2934
|
+
function closePr(prNumber, comment, cwd) {
|
|
2935
|
+
try {
|
|
2936
|
+
gh(["pr", "close", String(prNumber), "--comment", comment], { cwd });
|
|
2937
|
+
return { ok: true };
|
|
2938
|
+
} catch (err) {
|
|
2939
|
+
return fail(err);
|
|
2940
|
+
}
|
|
2941
|
+
}
|
|
2942
|
+
function createPr(args, cwd) {
|
|
2943
|
+
try {
|
|
2944
|
+
const cli = ["pr", "create", "--head", args.head, "--base", args.base, "--title", args.title, "--body", args.body];
|
|
2945
|
+
if (args.draft) cli.push("--draft");
|
|
2946
|
+
const url = gh(cli, { cwd });
|
|
2947
|
+
if (!url.includes("/pull/")) return { ok: false, error: `gh pr create returned unexpected output: ${url}` };
|
|
2948
|
+
return { ok: true, value: url.trim() };
|
|
2949
|
+
} catch (err) {
|
|
2950
|
+
return fail(err);
|
|
2951
|
+
}
|
|
2952
|
+
}
|
|
2953
|
+
function editPrBody(prNumber, body, cwd) {
|
|
2954
|
+
try {
|
|
2955
|
+
gh(["pr", "edit", String(prNumber), "--body", body], { cwd });
|
|
2956
|
+
return { ok: true };
|
|
2957
|
+
} catch (err) {
|
|
2958
|
+
return fail(err);
|
|
2959
|
+
}
|
|
2960
|
+
}
|
|
2961
|
+
function markPrReady(prNumber, cwd) {
|
|
2962
|
+
try {
|
|
2963
|
+
gh(["pr", "ready", String(prNumber)], { cwd });
|
|
2964
|
+
return { ok: true };
|
|
2965
|
+
} catch (err) {
|
|
2966
|
+
return fail(err);
|
|
2967
|
+
}
|
|
2968
|
+
}
|
|
2969
|
+
function ghTokenEnv() {
|
|
2970
|
+
const token = process.env.GH_PAT?.trim() || process.env.GH_TOKEN;
|
|
2971
|
+
return token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
|
|
2972
|
+
}
|
|
2973
|
+
function remoteBranchExists(ref, cwd) {
|
|
2974
|
+
try {
|
|
2975
|
+
execFileSync9("git", ["rev-parse", "--verify", "--quiet", `refs/remotes/origin/${ref}`], {
|
|
2976
|
+
cwd,
|
|
2977
|
+
stdio: "pipe",
|
|
2978
|
+
env: ghTokenEnv()
|
|
2979
|
+
});
|
|
2980
|
+
return true;
|
|
2981
|
+
} catch {
|
|
2982
|
+
return false;
|
|
2983
|
+
}
|
|
2984
|
+
}
|
|
2985
|
+
function fetchOrigin(cwd) {
|
|
2986
|
+
try {
|
|
2987
|
+
execFileSync9("git", ["fetch", "origin", "--quiet"], { cwd, stdio: "pipe", env: ghTokenEnv() });
|
|
2988
|
+
} catch {
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2991
|
+
function createBranchFrom(branch, base, cwd) {
|
|
2992
|
+
try {
|
|
2993
|
+
execFileSync9("git", ["push", "origin", `refs/remotes/origin/${base}:refs/heads/${branch}`, "--quiet"], {
|
|
2994
|
+
cwd,
|
|
2995
|
+
stdio: "pipe",
|
|
2996
|
+
env: ghTokenEnv()
|
|
2997
|
+
});
|
|
2998
|
+
return { ok: true };
|
|
2999
|
+
} catch (err) {
|
|
3000
|
+
return fail(err);
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
function inferLinkedIssue(pr) {
|
|
3004
|
+
const body = pr.body ?? "";
|
|
3005
|
+
const m = body.match(/\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s+#(\d+)\b/i);
|
|
3006
|
+
if (m?.[1]) return Number.parseInt(m[1], 10);
|
|
3007
|
+
const ref = pr.headRefName ?? "";
|
|
3008
|
+
const bm = ref.match(/^(\d+)-/);
|
|
3009
|
+
if (bm?.[1]) return Number.parseInt(bm[1], 10);
|
|
3010
|
+
return void 0;
|
|
3011
|
+
}
|
|
3012
|
+
|
|
3013
|
+
// src/scripts/closeMergedTaskIssues.ts
|
|
3014
|
+
var closeMergedTaskIssues = async (ctx) => {
|
|
3015
|
+
const goal = ctx.data.goal;
|
|
3016
|
+
if (!goal) return;
|
|
3017
|
+
const merged = listPrsByBase(goal.goalBranch, "merged", ctx.cwd);
|
|
3018
|
+
if (!merged.ok) {
|
|
3019
|
+
process.stderr.write(`[goal-tick] closeMergedTaskIssues: list failed: ${merged.error}
|
|
3020
|
+
`);
|
|
3021
|
+
return;
|
|
3022
|
+
}
|
|
3023
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3024
|
+
for (const pr of merged.value ?? []) {
|
|
3025
|
+
const linked = inferLinkedIssue(pr);
|
|
3026
|
+
if (linked === void 0 || seen.has(linked)) continue;
|
|
3027
|
+
seen.add(linked);
|
|
3028
|
+
const stateRes = getIssueState(linked, ctx.cwd);
|
|
3029
|
+
if (!stateRes.ok || stateRes.value !== "OPEN") continue;
|
|
3030
|
+
process.stdout.write(`[goal-tick] closing #${linked} (PR merged into ${goal.goalBranch})
|
|
3031
|
+
`);
|
|
3032
|
+
const r = closeIssue(
|
|
3033
|
+
linked,
|
|
3034
|
+
{
|
|
3035
|
+
comment: `_Closed by goal-tick: PR for this task merged into \`${goal.goalBranch}\`._`,
|
|
3036
|
+
reason: "completed"
|
|
3037
|
+
},
|
|
3038
|
+
ctx.cwd
|
|
3039
|
+
);
|
|
3040
|
+
if (!r.ok) {
|
|
3041
|
+
process.stderr.write(`[goal-tick] failed to close #${linked}: ${r.error} (continuing)
|
|
3042
|
+
`);
|
|
3043
|
+
}
|
|
3044
|
+
}
|
|
3045
|
+
};
|
|
3046
|
+
|
|
2752
3047
|
// src/scripts/commitAndPush.ts
|
|
2753
3048
|
var DEFAULT_COMMIT_MESSAGE = "chore: kody changes";
|
|
2754
3049
|
var commitAndPush2 = async (ctx) => {
|
|
@@ -2792,17 +3087,69 @@ var commitAndPush2 = async (ctx) => {
|
|
|
2792
3087
|
ctx.data.hasCommitsAhead = hasCommitsAhead(branch, ctx.config.git.defaultBranch, ctx.cwd);
|
|
2793
3088
|
};
|
|
2794
3089
|
|
|
3090
|
+
// src/scripts/commitGoalState.ts
|
|
3091
|
+
import { execFileSync as execFileSync10 } from "child_process";
|
|
3092
|
+
import * as path12 from "path";
|
|
3093
|
+
var commitGoalState = async (ctx) => {
|
|
3094
|
+
const goal = ctx.data.goal;
|
|
3095
|
+
if (!goal) return;
|
|
3096
|
+
const stateRel = path12.posix.join(".kody", "goals", goal.id, "state.json");
|
|
3097
|
+
try {
|
|
3098
|
+
execFileSync10("git", ["add", stateRel], { cwd: ctx.cwd, stdio: "pipe" });
|
|
3099
|
+
} catch (err) {
|
|
3100
|
+
process.stderr.write(
|
|
3101
|
+
`[goal-tick] commitGoalState: git add failed: ${err instanceof Error ? err.message : String(err)}
|
|
3102
|
+
`
|
|
3103
|
+
);
|
|
3104
|
+
return;
|
|
3105
|
+
}
|
|
3106
|
+
try {
|
|
3107
|
+
execFileSync10("git", ["diff", "--cached", "--quiet"], { cwd: ctx.cwd, stdio: "pipe" });
|
|
3108
|
+
return;
|
|
3109
|
+
} catch {
|
|
3110
|
+
}
|
|
3111
|
+
const msg = describeCommitMessage(goal);
|
|
3112
|
+
try {
|
|
3113
|
+
execFileSync10("git", ["commit", "-m", msg, "--quiet"], { cwd: ctx.cwd, stdio: "pipe" });
|
|
3114
|
+
} catch (err) {
|
|
3115
|
+
process.stderr.write(
|
|
3116
|
+
`[goal-tick] commitGoalState: git commit failed: ${err instanceof Error ? err.message : String(err)}
|
|
3117
|
+
`
|
|
3118
|
+
);
|
|
3119
|
+
return;
|
|
3120
|
+
}
|
|
3121
|
+
try {
|
|
3122
|
+
execFileSync10("git", ["push", "--quiet"], { cwd: ctx.cwd, stdio: "pipe" });
|
|
3123
|
+
} catch {
|
|
3124
|
+
process.stderr.write("[goal-tick] commitGoalState: push failed (will retry next tick)\n");
|
|
3125
|
+
}
|
|
3126
|
+
};
|
|
3127
|
+
function describeCommitMessage(goal) {
|
|
3128
|
+
if (goal.state === "closed") return `chore(goals): abandon ${goal.id} (cleanup complete)`;
|
|
3129
|
+
if (goal.state === "done") return `chore(goals): mark ${goal.id} done`;
|
|
3130
|
+
if (goal.lastDispatchedIssue !== void 0) {
|
|
3131
|
+
return `chore(goals): dispatched #${goal.lastDispatchedIssue} for ${goal.id}`;
|
|
3132
|
+
}
|
|
3133
|
+
if (goal.phase === "in-flight") {
|
|
3134
|
+
return `chore(goals): tick ${goal.id} (waiting for in-flight task)`;
|
|
3135
|
+
}
|
|
3136
|
+
if (goal.phase === "blocked-by-failure") {
|
|
3137
|
+
return `chore(goals): tick ${goal.id} (blocked by failed task)`;
|
|
3138
|
+
}
|
|
3139
|
+
return `chore(goals): tick ${goal.id} (idle)`;
|
|
3140
|
+
}
|
|
3141
|
+
|
|
2795
3142
|
// src/scripts/composePrompt.ts
|
|
2796
3143
|
import * as fs13 from "fs";
|
|
2797
|
-
import * as
|
|
3144
|
+
import * as path13 from "path";
|
|
2798
3145
|
var MUSTACHE = /\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g;
|
|
2799
3146
|
var composePrompt = async (ctx, profile) => {
|
|
2800
3147
|
const explicit = ctx.data.promptTemplate;
|
|
2801
3148
|
const mode = ctx.args.mode;
|
|
2802
3149
|
const candidates = [
|
|
2803
|
-
explicit ?
|
|
2804
|
-
mode ?
|
|
2805
|
-
|
|
3150
|
+
explicit ? path13.join(profile.dir, explicit) : null,
|
|
3151
|
+
mode ? path13.join(profile.dir, "prompts", `${mode}.md`) : null,
|
|
3152
|
+
path13.join(profile.dir, "prompt.md")
|
|
2806
3153
|
].filter(Boolean);
|
|
2807
3154
|
let templatePath = "";
|
|
2808
3155
|
for (const c of candidates) {
|
|
@@ -2891,9 +3238,9 @@ function formatToolsUsage(profile) {
|
|
|
2891
3238
|
}
|
|
2892
3239
|
|
|
2893
3240
|
// src/scripts/createQaGoal.ts
|
|
2894
|
-
import { execFileSync as
|
|
3241
|
+
import { execFileSync as execFileSync11 } from "child_process";
|
|
2895
3242
|
import * as fs14 from "fs";
|
|
2896
|
-
import * as
|
|
3243
|
+
import * as path14 from "path";
|
|
2897
3244
|
|
|
2898
3245
|
// src/scripts/postReviewResult.ts
|
|
2899
3246
|
function detectVerdict(body) {
|
|
@@ -3075,7 +3422,7 @@ ${json}
|
|
|
3075
3422
|
${MANIFEST_END}
|
|
3076
3423
|
`;
|
|
3077
3424
|
}
|
|
3078
|
-
function
|
|
3425
|
+
function ensureLabel2(name, color, description, cwd) {
|
|
3079
3426
|
try {
|
|
3080
3427
|
gh(["label", "create", name, "--color", color, "--description", description, "--force"], { cwd });
|
|
3081
3428
|
} catch {
|
|
@@ -3095,7 +3442,7 @@ function ensureSeverityLabels(findings, cwd) {
|
|
|
3095
3442
|
for (const f of findings) {
|
|
3096
3443
|
if (seen.has(f.severity)) continue;
|
|
3097
3444
|
seen.add(f.severity);
|
|
3098
|
-
|
|
3445
|
+
ensureLabel2(severityLabel(f.severity), SEVERITY_COLORS[f.severity], `kody QA finding severity ${f.severity}`, cwd);
|
|
3099
3446
|
}
|
|
3100
3447
|
}
|
|
3101
3448
|
function buildIssueBody(f, goalId, parentManifestNumber) {
|
|
@@ -3129,7 +3476,7 @@ function buildIssueBody(f, goalId, parentManifestNumber) {
|
|
|
3129
3476
|
return lines.join("\n");
|
|
3130
3477
|
}
|
|
3131
3478
|
function createOrUpdateManifestIssue(number, manifest, cwd) {
|
|
3132
|
-
|
|
3479
|
+
ensureLabel2(MANIFEST_LABEL, "8b5cf6", "kody: goals manifest", cwd);
|
|
3133
3480
|
const body = serializeManifestBody(manifest);
|
|
3134
3481
|
if (number !== null) {
|
|
3135
3482
|
gh(["issue", "edit", String(number), "--body-file", "-"], { input: body, cwd });
|
|
@@ -3145,7 +3492,7 @@ function createOrUpdateManifestIssue(number, manifest, cwd) {
|
|
|
3145
3492
|
return { number: Number(m[1]), created: true };
|
|
3146
3493
|
}
|
|
3147
3494
|
function writeStateFile(cwd, goalId, lastDispatchedIssue) {
|
|
3148
|
-
const dir =
|
|
3495
|
+
const dir = path14.join(cwd, ".kody", "goals", goalId);
|
|
3149
3496
|
fs14.mkdirSync(dir, { recursive: true });
|
|
3150
3497
|
const state = {
|
|
3151
3498
|
version: 1,
|
|
@@ -3154,7 +3501,7 @@ function writeStateFile(cwd, goalId, lastDispatchedIssue) {
|
|
|
3154
3501
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3155
3502
|
...typeof lastDispatchedIssue === "number" ? { lastDispatchedIssue } : {}
|
|
3156
3503
|
};
|
|
3157
|
-
const filePath =
|
|
3504
|
+
const filePath = path14.join(dir, "state.json");
|
|
3158
3505
|
fs14.writeFileSync(filePath, `${JSON.stringify(state, null, 2)}
|
|
3159
3506
|
`);
|
|
3160
3507
|
return filePath;
|
|
@@ -3162,7 +3509,7 @@ function writeStateFile(cwd, goalId, lastDispatchedIssue) {
|
|
|
3162
3509
|
function gitTry(args, cwd) {
|
|
3163
3510
|
const env = { ...process.env, SKIP_HOOKS: "1", HUSKY: "0" };
|
|
3164
3511
|
try {
|
|
3165
|
-
|
|
3512
|
+
execFileSync11("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"], env });
|
|
3166
3513
|
return { ok: true, stderr: "" };
|
|
3167
3514
|
} catch (err) {
|
|
3168
3515
|
const e = err;
|
|
@@ -3244,8 +3591,8 @@ ${tail}
|
|
|
3244
3591
|
}
|
|
3245
3592
|
function createTaskIssue(finding, goalId, manifestNumber, cwd) {
|
|
3246
3593
|
const labels = [`goal:${goalId}`, severityLabel(finding.severity), FINDING_LABEL];
|
|
3247
|
-
|
|
3248
|
-
|
|
3594
|
+
ensureLabel2(`goal:${goalId}`, "1d76db", `goal: ${goalId}`, cwd);
|
|
3595
|
+
ensureLabel2(FINDING_LABEL, "ededed", "kody: QA finding", cwd);
|
|
3249
3596
|
const title = `[${finding.severity}] ${finding.title}`.slice(0, 240);
|
|
3250
3597
|
const body = buildIssueBody(finding, goalId, manifestNumber);
|
|
3251
3598
|
const args = ["issue", "create", "--title", title, "--body-file", "-"];
|
|
@@ -3304,7 +3651,7 @@ QA_REPORT_POSTED=https://github.com/${ctx.config.github.owner}/${ctx.config.gith
|
|
|
3304
3651
|
ctx.output.exitCode = verdict === "FAIL" ? 1 : 0;
|
|
3305
3652
|
return;
|
|
3306
3653
|
}
|
|
3307
|
-
|
|
3654
|
+
ensureLabel2(FINDING_LABEL, "ededed", "kody: QA finding", ctx.cwd);
|
|
3308
3655
|
const scope2 = ctx.args.scope;
|
|
3309
3656
|
const title = `QA [${verdict}]: ${scope2?.trim() || "smoke"} \u2014 ${todayIso()}`.slice(0, 240);
|
|
3310
3657
|
let url = "";
|
|
@@ -3434,14 +3781,57 @@ QA_GOAL_TARGETED=(no manifest issue) (id: ${goalId}, verdict: ${verdict})
|
|
|
3434
3781
|
ctx.output.exitCode = verdict === "FAIL" ? 1 : 0;
|
|
3435
3782
|
};
|
|
3436
3783
|
|
|
3784
|
+
// src/goal/phase.ts
|
|
3785
|
+
function derivePhase(snap) {
|
|
3786
|
+
if (snap.lifecycleState === void 0) return "missing";
|
|
3787
|
+
if (snap.lifecycleState === "abandoned") return "abandoned";
|
|
3788
|
+
if (snap.lifecycleState === "closed" || snap.lifecycleState === "done") return "terminal";
|
|
3789
|
+
if (snap.childTasks.length === 0) return "no-tasks";
|
|
3790
|
+
const allClosed = snap.childTasks.every((t) => t.state === "CLOSED");
|
|
3791
|
+
if (allClosed) return "all-done";
|
|
3792
|
+
const anyFailed = snap.childTasks.some((t) => t.labels.includes(FAILED_LABEL));
|
|
3793
|
+
if (anyFailed) return "blocked-by-failure";
|
|
3794
|
+
const inFlight = snap.childTasks.some((t) => t.state === "OPEN" && t.labels.includes(DISPATCHED_LABEL));
|
|
3795
|
+
if (inFlight) return "in-flight";
|
|
3796
|
+
const dispatchable = snap.childTasks.some((t) => t.state === "OPEN" && !t.labels.includes(DISPATCHED_LABEL));
|
|
3797
|
+
if (dispatchable) return "ready-to-dispatch";
|
|
3798
|
+
return "idle";
|
|
3799
|
+
}
|
|
3800
|
+
function pickNextDispatchable(snap) {
|
|
3801
|
+
const candidates = snap.childTasks.filter((t) => t.state === "OPEN" && !t.labels.includes(DISPATCHED_LABEL)).sort((a, b) => a.number - b.number);
|
|
3802
|
+
return candidates[0];
|
|
3803
|
+
}
|
|
3804
|
+
|
|
3805
|
+
// src/scripts/deriveGoalPhase.ts
|
|
3806
|
+
var deriveGoalPhase = async (ctx) => {
|
|
3807
|
+
const goal = ctx.data.goal;
|
|
3808
|
+
if (!goal) return;
|
|
3809
|
+
const issues = listGoalIssues(goal.id, goal.goalIssueNumber, ctx.cwd);
|
|
3810
|
+
if (!issues.ok) {
|
|
3811
|
+
process.stderr.write(`[goal-tick] deriveGoalPhase: list failed: ${issues.error}
|
|
3812
|
+
`);
|
|
3813
|
+
goal.childTasks = [];
|
|
3814
|
+
goal.phase = "idle";
|
|
3815
|
+
return;
|
|
3816
|
+
}
|
|
3817
|
+
const childTasks = issues.value ?? [];
|
|
3818
|
+
goal.childTasks = childTasks;
|
|
3819
|
+
goal.phase = derivePhase({
|
|
3820
|
+
lifecycleState: goal.state,
|
|
3821
|
+
childTasks
|
|
3822
|
+
});
|
|
3823
|
+
process.stdout.write(`[goal-tick] phase=${goal.phase} goal=${goal.id} tasks=${childTasks.length}
|
|
3824
|
+
`);
|
|
3825
|
+
};
|
|
3826
|
+
|
|
3437
3827
|
// src/scripts/diagMcp.ts
|
|
3438
|
-
import { execFileSync as
|
|
3828
|
+
import { execFileSync as execFileSync12 } from "child_process";
|
|
3439
3829
|
import * as fs15 from "fs";
|
|
3440
3830
|
import * as os3 from "os";
|
|
3441
|
-
import * as
|
|
3831
|
+
import * as path15 from "path";
|
|
3442
3832
|
var diagMcp = async (_ctx) => {
|
|
3443
3833
|
const home = os3.homedir();
|
|
3444
|
-
const cacheDir =
|
|
3834
|
+
const cacheDir = path15.join(home, ".cache", "ms-playwright");
|
|
3445
3835
|
let entries = [];
|
|
3446
3836
|
try {
|
|
3447
3837
|
entries = fs15.readdirSync(cacheDir);
|
|
@@ -3455,7 +3845,7 @@ var diagMcp = async (_ctx) => {
|
|
|
3455
3845
|
process.stderr.write(`[kody diag] chromium present: ${hasChromium ? "yes" : "no"}
|
|
3456
3846
|
`);
|
|
3457
3847
|
try {
|
|
3458
|
-
const v =
|
|
3848
|
+
const v = execFileSync12("npx", ["-y", "--package=@playwright/mcp@latest", "--", "playwright-mcp", "--version"], {
|
|
3459
3849
|
stdio: "pipe",
|
|
3460
3850
|
timeout: 6e4,
|
|
3461
3851
|
encoding: "utf8"
|
|
@@ -3471,16 +3861,16 @@ var diagMcp = async (_ctx) => {
|
|
|
3471
3861
|
|
|
3472
3862
|
// src/scripts/discoverQaContext.ts
|
|
3473
3863
|
import * as fs17 from "fs";
|
|
3474
|
-
import * as
|
|
3864
|
+
import * as path17 from "path";
|
|
3475
3865
|
|
|
3476
3866
|
// src/scripts/frameworkDetectors.ts
|
|
3477
3867
|
import * as fs16 from "fs";
|
|
3478
|
-
import * as
|
|
3868
|
+
import * as path16 from "path";
|
|
3479
3869
|
function detectFrameworks(cwd) {
|
|
3480
3870
|
const out = [];
|
|
3481
3871
|
let deps = {};
|
|
3482
3872
|
try {
|
|
3483
|
-
const pkg = JSON.parse(fs16.readFileSync(
|
|
3873
|
+
const pkg = JSON.parse(fs16.readFileSync(path16.join(cwd, "package.json"), "utf-8"));
|
|
3484
3874
|
deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
3485
3875
|
} catch {
|
|
3486
3876
|
return out;
|
|
@@ -3517,7 +3907,7 @@ function detectFrameworks(cwd) {
|
|
|
3517
3907
|
}
|
|
3518
3908
|
function findFile(cwd, candidates) {
|
|
3519
3909
|
for (const c of candidates) {
|
|
3520
|
-
if (fs16.existsSync(
|
|
3910
|
+
if (fs16.existsSync(path16.join(cwd, c))) return c;
|
|
3521
3911
|
}
|
|
3522
3912
|
return null;
|
|
3523
3913
|
}
|
|
@@ -3530,7 +3920,7 @@ var COLLECTION_DIRS = [
|
|
|
3530
3920
|
function discoverPayloadCollections(cwd) {
|
|
3531
3921
|
const out = [];
|
|
3532
3922
|
for (const dir of COLLECTION_DIRS) {
|
|
3533
|
-
const full =
|
|
3923
|
+
const full = path16.join(cwd, dir);
|
|
3534
3924
|
if (!fs16.existsSync(full)) continue;
|
|
3535
3925
|
let files;
|
|
3536
3926
|
try {
|
|
@@ -3540,7 +3930,7 @@ function discoverPayloadCollections(cwd) {
|
|
|
3540
3930
|
}
|
|
3541
3931
|
for (const file of files) {
|
|
3542
3932
|
try {
|
|
3543
|
-
const filePath =
|
|
3933
|
+
const filePath = path16.join(full, file);
|
|
3544
3934
|
const content = fs16.readFileSync(filePath, "utf-8").slice(0, 1e4);
|
|
3545
3935
|
const slugMatch = content.match(/slug:\s*['"]([a-z0-9-]+)['"]/);
|
|
3546
3936
|
if (!slugMatch) continue;
|
|
@@ -3555,7 +3945,7 @@ function discoverPayloadCollections(cwd) {
|
|
|
3555
3945
|
out.push({
|
|
3556
3946
|
name,
|
|
3557
3947
|
slug,
|
|
3558
|
-
filePath:
|
|
3948
|
+
filePath: path16.relative(cwd, filePath),
|
|
3559
3949
|
fields: fields.slice(0, 20),
|
|
3560
3950
|
hasAdmin
|
|
3561
3951
|
});
|
|
@@ -3569,7 +3959,7 @@ var ADMIN_COMPONENT_DIRS = ["src/ui/admin", "src/admin/components", "src/compone
|
|
|
3569
3959
|
function discoverAdminComponents(cwd, collections) {
|
|
3570
3960
|
const out = [];
|
|
3571
3961
|
for (const dir of ADMIN_COMPONENT_DIRS) {
|
|
3572
|
-
const full =
|
|
3962
|
+
const full = path16.join(cwd, dir);
|
|
3573
3963
|
if (!fs16.existsSync(full)) continue;
|
|
3574
3964
|
let entries;
|
|
3575
3965
|
try {
|
|
@@ -3578,19 +3968,19 @@ function discoverAdminComponents(cwd, collections) {
|
|
|
3578
3968
|
continue;
|
|
3579
3969
|
}
|
|
3580
3970
|
for (const entry of entries) {
|
|
3581
|
-
const entryPath =
|
|
3971
|
+
const entryPath = path16.join(full, entry.name);
|
|
3582
3972
|
let name;
|
|
3583
3973
|
let filePath;
|
|
3584
3974
|
if (entry.isDirectory()) {
|
|
3585
3975
|
const indexFile = ["index.tsx", "index.ts", "index.jsx", "index.js"].find(
|
|
3586
|
-
(f) => fs16.existsSync(
|
|
3976
|
+
(f) => fs16.existsSync(path16.join(entryPath, f))
|
|
3587
3977
|
);
|
|
3588
3978
|
if (!indexFile) continue;
|
|
3589
3979
|
name = entry.name;
|
|
3590
|
-
filePath =
|
|
3980
|
+
filePath = path16.relative(cwd, path16.join(entryPath, indexFile));
|
|
3591
3981
|
} else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
|
|
3592
3982
|
name = entry.name.replace(/\.(tsx?|jsx?)$/, "");
|
|
3593
|
-
filePath =
|
|
3983
|
+
filePath = path16.relative(cwd, entryPath);
|
|
3594
3984
|
} else {
|
|
3595
3985
|
continue;
|
|
3596
3986
|
}
|
|
@@ -3598,7 +3988,7 @@ function discoverAdminComponents(cwd, collections) {
|
|
|
3598
3988
|
if (collections) {
|
|
3599
3989
|
for (const col of collections) {
|
|
3600
3990
|
try {
|
|
3601
|
-
const colContent = fs16.readFileSync(
|
|
3991
|
+
const colContent = fs16.readFileSync(path16.join(cwd, col.filePath), "utf-8");
|
|
3602
3992
|
if (colContent.includes(name)) {
|
|
3603
3993
|
usedInCollection = col.slug;
|
|
3604
3994
|
break;
|
|
@@ -3617,7 +4007,7 @@ function scanApiRoutes(cwd) {
|
|
|
3617
4007
|
const out = [];
|
|
3618
4008
|
const appDirs = ["src/app", "app"];
|
|
3619
4009
|
for (const appDir of appDirs) {
|
|
3620
|
-
const apiDir =
|
|
4010
|
+
const apiDir = path16.join(cwd, appDir, "api");
|
|
3621
4011
|
if (!fs16.existsSync(apiDir)) continue;
|
|
3622
4012
|
walkApiRoutes(apiDir, "/api", cwd, out);
|
|
3623
4013
|
break;
|
|
@@ -3634,7 +4024,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
|
|
|
3634
4024
|
const routeFile = entries.find((e) => e.isFile() && /^route\.(ts|js|tsx|jsx)$/.test(e.name));
|
|
3635
4025
|
if (routeFile) {
|
|
3636
4026
|
try {
|
|
3637
|
-
const content = fs16.readFileSync(
|
|
4027
|
+
const content = fs16.readFileSync(path16.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
|
|
3638
4028
|
const methods = HTTP_METHODS.filter(
|
|
3639
4029
|
(m) => new RegExp(`export\\s+(?:async\\s+)?function\\s+${m}\\b`).test(content)
|
|
3640
4030
|
);
|
|
@@ -3642,7 +4032,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
|
|
|
3642
4032
|
out.push({
|
|
3643
4033
|
path: prefix,
|
|
3644
4034
|
methods,
|
|
3645
|
-
filePath:
|
|
4035
|
+
filePath: path16.relative(cwd, path16.join(dir, routeFile.name))
|
|
3646
4036
|
});
|
|
3647
4037
|
}
|
|
3648
4038
|
} catch {
|
|
@@ -3653,7 +4043,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
|
|
|
3653
4043
|
if (entry.name === "node_modules" || entry.name === ".next") continue;
|
|
3654
4044
|
let segment = entry.name;
|
|
3655
4045
|
if (segment.startsWith("(") && segment.endsWith(")")) {
|
|
3656
|
-
walkApiRoutes(
|
|
4046
|
+
walkApiRoutes(path16.join(dir, entry.name), prefix, cwd, out);
|
|
3657
4047
|
continue;
|
|
3658
4048
|
}
|
|
3659
4049
|
if (segment.startsWith("[[") && segment.endsWith("]]")) {
|
|
@@ -3661,7 +4051,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
|
|
|
3661
4051
|
} else if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
3662
4052
|
segment = `:${segment.slice(1, -1)}`;
|
|
3663
4053
|
}
|
|
3664
|
-
walkApiRoutes(
|
|
4054
|
+
walkApiRoutes(path16.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
|
|
3665
4055
|
}
|
|
3666
4056
|
}
|
|
3667
4057
|
var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
|
|
@@ -3681,7 +4071,7 @@ var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
|
|
|
3681
4071
|
function scanEnvVars(cwd) {
|
|
3682
4072
|
const candidates = [".env.example", ".env.local.example", ".env.template"];
|
|
3683
4073
|
for (const envFile of candidates) {
|
|
3684
|
-
const envPath =
|
|
4074
|
+
const envPath = path16.join(cwd, envFile);
|
|
3685
4075
|
if (!fs16.existsSync(envPath)) continue;
|
|
3686
4076
|
try {
|
|
3687
4077
|
const content = fs16.readFileSync(envPath, "utf-8");
|
|
@@ -3732,9 +4122,9 @@ function runQaDiscovery(cwd) {
|
|
|
3732
4122
|
}
|
|
3733
4123
|
function detectDevServer(cwd, out) {
|
|
3734
4124
|
try {
|
|
3735
|
-
const pkg = JSON.parse(fs17.readFileSync(
|
|
4125
|
+
const pkg = JSON.parse(fs17.readFileSync(path17.join(cwd, "package.json"), "utf-8"));
|
|
3736
4126
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
3737
|
-
const pm = fs17.existsSync(
|
|
4127
|
+
const pm = fs17.existsSync(path17.join(cwd, "pnpm-lock.yaml")) ? "pnpm" : fs17.existsSync(path17.join(cwd, "yarn.lock")) ? "yarn" : fs17.existsSync(path17.join(cwd, "bun.lockb")) ? "bun" : "npm";
|
|
3738
4128
|
if (pkg.scripts?.dev) out.devCommand = `${pm} dev`;
|
|
3739
4129
|
if (allDeps.next || allDeps.nuxt) out.devPort = 3e3;
|
|
3740
4130
|
else if (allDeps.vite) out.devPort = 5173;
|
|
@@ -3744,7 +4134,7 @@ function detectDevServer(cwd, out) {
|
|
|
3744
4134
|
function scanFrontendRoutes(cwd, out) {
|
|
3745
4135
|
const appDirs = ["src/app", "app"];
|
|
3746
4136
|
for (const appDir of appDirs) {
|
|
3747
|
-
const full =
|
|
4137
|
+
const full = path17.join(cwd, appDir);
|
|
3748
4138
|
if (!fs17.existsSync(full)) continue;
|
|
3749
4139
|
walkFrontendRoutes(full, "", out);
|
|
3750
4140
|
break;
|
|
@@ -3770,7 +4160,7 @@ function walkFrontendRoutes(dir, prefix, out) {
|
|
|
3770
4160
|
if (entry.name === "node_modules" || entry.name === ".next") continue;
|
|
3771
4161
|
let segment = entry.name;
|
|
3772
4162
|
if (segment.startsWith("(") && segment.endsWith(")")) {
|
|
3773
|
-
walkFrontendRoutes(
|
|
4163
|
+
walkFrontendRoutes(path17.join(dir, entry.name), prefix, out);
|
|
3774
4164
|
continue;
|
|
3775
4165
|
}
|
|
3776
4166
|
if (segment.startsWith("[[") && segment.endsWith("]]")) {
|
|
@@ -3778,7 +4168,7 @@ function walkFrontendRoutes(dir, prefix, out) {
|
|
|
3778
4168
|
} else if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
3779
4169
|
segment = `:${segment.slice(1, -1)}`;
|
|
3780
4170
|
}
|
|
3781
|
-
walkFrontendRoutes(
|
|
4171
|
+
walkFrontendRoutes(path17.join(dir, entry.name), `${prefix}/${segment}`, out);
|
|
3782
4172
|
}
|
|
3783
4173
|
}
|
|
3784
4174
|
function detectAuthFiles(cwd, out) {
|
|
@@ -3795,13 +4185,13 @@ function detectAuthFiles(cwd, out) {
|
|
|
3795
4185
|
"src/app/api/oauth"
|
|
3796
4186
|
];
|
|
3797
4187
|
for (const c of candidates) {
|
|
3798
|
-
if (fs17.existsSync(
|
|
4188
|
+
if (fs17.existsSync(path17.join(cwd, c))) out.authFiles.push(c);
|
|
3799
4189
|
}
|
|
3800
4190
|
}
|
|
3801
4191
|
function detectRoles(cwd, out) {
|
|
3802
4192
|
const rolePaths = ["src/types", "src/lib", "src/utils", "src/constants", "src/access", "src/collections"];
|
|
3803
4193
|
for (const rp of rolePaths) {
|
|
3804
|
-
const dir =
|
|
4194
|
+
const dir = path17.join(cwd, rp);
|
|
3805
4195
|
if (!fs17.existsSync(dir)) continue;
|
|
3806
4196
|
let files;
|
|
3807
4197
|
try {
|
|
@@ -3811,7 +4201,7 @@ function detectRoles(cwd, out) {
|
|
|
3811
4201
|
}
|
|
3812
4202
|
for (const f of files) {
|
|
3813
4203
|
try {
|
|
3814
|
-
const content = fs17.readFileSync(
|
|
4204
|
+
const content = fs17.readFileSync(path17.join(dir, f), "utf-8").slice(0, 5e3);
|
|
3815
4205
|
const roleMatches = content.match(/(?:role|Role|ROLE)\s*[=:]\s*['"](\w+)['"]/g);
|
|
3816
4206
|
if (roleMatches) {
|
|
3817
4207
|
for (const m of roleMatches) {
|
|
@@ -3957,7 +4347,7 @@ var discoverQaContext = async (ctx) => {
|
|
|
3957
4347
|
};
|
|
3958
4348
|
|
|
3959
4349
|
// src/scripts/dispatch.ts
|
|
3960
|
-
import { execFileSync as
|
|
4350
|
+
import { execFileSync as execFileSync13 } from "child_process";
|
|
3961
4351
|
var API_TIMEOUT_MS4 = 3e4;
|
|
3962
4352
|
var dispatch = async (ctx, _profile, _agentResult, args) => {
|
|
3963
4353
|
const next = args?.next;
|
|
@@ -3993,7 +4383,7 @@ var dispatch = async (ctx, _profile, _agentResult, args) => {
|
|
|
3993
4383
|
const sub = usePr ? "pr" : "issue";
|
|
3994
4384
|
const body = `@kody ${next}`;
|
|
3995
4385
|
try {
|
|
3996
|
-
|
|
4386
|
+
execFileSync13("gh", [sub, "comment", String(targetNumber), "--body", body], {
|
|
3997
4387
|
timeout: API_TIMEOUT_MS4,
|
|
3998
4388
|
cwd: ctx.cwd,
|
|
3999
4389
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -4013,7 +4403,7 @@ function parsePr(url) {
|
|
|
4013
4403
|
}
|
|
4014
4404
|
|
|
4015
4405
|
// src/scripts/dispatchClassified.ts
|
|
4016
|
-
import { execFileSync as
|
|
4406
|
+
import { execFileSync as execFileSync14 } from "child_process";
|
|
4017
4407
|
var API_TIMEOUT_MS5 = 3e4;
|
|
4018
4408
|
var VALID_CLASSES2 = /* @__PURE__ */ new Set(["feature", "bug", "spec", "chore"]);
|
|
4019
4409
|
var dispatchClassified = async (ctx) => {
|
|
@@ -4022,7 +4412,7 @@ var dispatchClassified = async (ctx) => {
|
|
|
4022
4412
|
const classification = ctx.data.classification;
|
|
4023
4413
|
if (!classification || !VALID_CLASSES2.has(classification)) return;
|
|
4024
4414
|
try {
|
|
4025
|
-
|
|
4415
|
+
execFileSync14("gh", ["issue", "comment", String(issueNumber), "--body", `@kody ${classification}`], {
|
|
4026
4416
|
cwd: ctx.cwd,
|
|
4027
4417
|
timeout: API_TIMEOUT_MS5,
|
|
4028
4418
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -4043,7 +4433,7 @@ function failedAction3(reason) {
|
|
|
4043
4433
|
|
|
4044
4434
|
// src/scripts/dispatchJobFileTicks.ts
|
|
4045
4435
|
import * as fs19 from "fs";
|
|
4046
|
-
import * as
|
|
4436
|
+
import * as path19 from "path";
|
|
4047
4437
|
|
|
4048
4438
|
// src/scripts/jobFrontmatter.ts
|
|
4049
4439
|
var SCHEDULE_EVERY_VALUES = [
|
|
@@ -4295,7 +4685,7 @@ var ContentsApiBackend = class {
|
|
|
4295
4685
|
|
|
4296
4686
|
// src/scripts/jobState/localFileBackend.ts
|
|
4297
4687
|
import * as fs18 from "fs";
|
|
4298
|
-
import * as
|
|
4688
|
+
import * as path18 from "path";
|
|
4299
4689
|
var LocalFileBackend = class {
|
|
4300
4690
|
name = "local-file";
|
|
4301
4691
|
cwd;
|
|
@@ -4310,7 +4700,7 @@ var LocalFileBackend = class {
|
|
|
4310
4700
|
if (!opts.owner || !opts.repo) throw new Error("LocalFileBackend: owner and repo are required");
|
|
4311
4701
|
this.cwd = opts.cwd;
|
|
4312
4702
|
this.jobsDir = opts.jobsDir;
|
|
4313
|
-
this.absDir =
|
|
4703
|
+
this.absDir = path18.join(opts.cwd, opts.jobsDir);
|
|
4314
4704
|
this.owner = opts.owner;
|
|
4315
4705
|
this.repo = opts.repo;
|
|
4316
4706
|
this.cache = opts.cache ?? defaultCacheAdapter();
|
|
@@ -4370,7 +4760,7 @@ var LocalFileBackend = class {
|
|
|
4370
4760
|
}
|
|
4371
4761
|
load(slug) {
|
|
4372
4762
|
const relPath = stateFilePath(this.jobsDir, slug);
|
|
4373
|
-
const absPath =
|
|
4763
|
+
const absPath = path18.join(this.cwd, relPath);
|
|
4374
4764
|
if (!fs18.existsSync(absPath)) {
|
|
4375
4765
|
return { path: relPath, handle: null, state: initialStateEnvelope("seed"), created: true };
|
|
4376
4766
|
}
|
|
@@ -4391,8 +4781,8 @@ var LocalFileBackend = class {
|
|
|
4391
4781
|
if (!loaded.created && isStateUnchanged(loaded.state, next)) {
|
|
4392
4782
|
return false;
|
|
4393
4783
|
}
|
|
4394
|
-
const absPath =
|
|
4395
|
-
fs18.mkdirSync(
|
|
4784
|
+
const absPath = path18.join(this.cwd, loaded.path);
|
|
4785
|
+
fs18.mkdirSync(path18.dirname(absPath), { recursive: true });
|
|
4396
4786
|
const body = JSON.stringify(next, null, 2) + "\n";
|
|
4397
4787
|
fs18.writeFileSync(absPath, body, "utf-8");
|
|
4398
4788
|
return true;
|
|
@@ -4471,7 +4861,7 @@ var dispatchJobFileTicks = async (ctx, _profile, args) => {
|
|
|
4471
4861
|
await backend.hydrate();
|
|
4472
4862
|
}
|
|
4473
4863
|
try {
|
|
4474
|
-
const slugs = listJobSlugs(
|
|
4864
|
+
const slugs = listJobSlugs(path19.join(ctx.cwd, jobsDir));
|
|
4475
4865
|
ctx.data.jobSlugCount = slugs.length;
|
|
4476
4866
|
if (slugs.length === 0) {
|
|
4477
4867
|
process.stdout.write(`[jobs] no job files in ${jobsDir}
|
|
@@ -4570,7 +4960,7 @@ function formatAgo(ms) {
|
|
|
4570
4960
|
}
|
|
4571
4961
|
function readJobFrontmatter(cwd, jobsDir, slug) {
|
|
4572
4962
|
try {
|
|
4573
|
-
const raw = fs19.readFileSync(
|
|
4963
|
+
const raw = fs19.readFileSync(path19.join(cwd, jobsDir, `${slug}.md`), "utf-8");
|
|
4574
4964
|
return splitFrontmatter(raw).frontmatter;
|
|
4575
4965
|
} catch {
|
|
4576
4966
|
return {};
|
|
@@ -4650,6 +5040,134 @@ function listIssuesByLabel(label, cwd) {
|
|
|
4650
5040
|
return list.filter((x) => typeof x.number === "number" && typeof x.title === "string").map((x) => ({ number: x.number, title: x.title }));
|
|
4651
5041
|
}
|
|
4652
5042
|
|
|
5043
|
+
// src/scripts/dispatchNextTask.ts
|
|
5044
|
+
var dispatchNextTask = async (ctx) => {
|
|
5045
|
+
const goal = ctx.data.goal;
|
|
5046
|
+
if (!goal?.childTasks) return;
|
|
5047
|
+
const next = pickNextDispatchable({
|
|
5048
|
+
lifecycleState: goal.state,
|
|
5049
|
+
childTasks: goal.childTasks
|
|
5050
|
+
});
|
|
5051
|
+
if (!next) {
|
|
5052
|
+
process.stdout.write("[goal-tick] no undispatched open task \u2014 idle\n");
|
|
5053
|
+
return;
|
|
5054
|
+
}
|
|
5055
|
+
process.stdout.write(`[goal-tick] dispatching @kody on task #${next.number} (--base ${goal.goalBranch})
|
|
5056
|
+
`);
|
|
5057
|
+
const comment = commentOnIssue(next.number, `@kody --base ${goal.goalBranch}`, ctx.cwd);
|
|
5058
|
+
if (!comment.ok) {
|
|
5059
|
+
process.stderr.write(`[goal-tick] dispatchNextTask: comment failed on #${next.number}: ${comment.error}
|
|
5060
|
+
`);
|
|
5061
|
+
return;
|
|
5062
|
+
}
|
|
5063
|
+
const label = addLabel2(next.number, DISPATCHED_LABEL, ctx.cwd);
|
|
5064
|
+
if (!label.ok) {
|
|
5065
|
+
process.stderr.write(
|
|
5066
|
+
`[goal-tick] dispatchNextTask: add-label failed on #${next.number}: ${label.error} (continuing \u2014 comment already posted)
|
|
5067
|
+
`
|
|
5068
|
+
);
|
|
5069
|
+
}
|
|
5070
|
+
goal.lastDispatchedIssue = next.number;
|
|
5071
|
+
};
|
|
5072
|
+
|
|
5073
|
+
// src/scripts/ensureGoalBranch.ts
|
|
5074
|
+
var ensureGoalBranch = async (ctx) => {
|
|
5075
|
+
const goal = ctx.data.goal;
|
|
5076
|
+
if (!goal) return;
|
|
5077
|
+
fetchOrigin(ctx.cwd);
|
|
5078
|
+
if (remoteBranchExists(goal.goalBranch, ctx.cwd)) {
|
|
5079
|
+
process.stdout.write(`[goal-tick] origin/${goal.goalBranch} already exists \u2014 leaving as-is
|
|
5080
|
+
`);
|
|
5081
|
+
return;
|
|
5082
|
+
}
|
|
5083
|
+
if (!remoteBranchExists(goal.defaultBranch, ctx.cwd)) {
|
|
5084
|
+
process.stderr.write(`[goal-tick] cannot create goal branch: origin/${goal.defaultBranch} missing
|
|
5085
|
+
`);
|
|
5086
|
+
return;
|
|
5087
|
+
}
|
|
5088
|
+
process.stdout.write(`[goal-tick] creating origin/${goal.goalBranch} from origin/${goal.defaultBranch}
|
|
5089
|
+
`);
|
|
5090
|
+
const r = createBranchFrom(goal.goalBranch, goal.defaultBranch, ctx.cwd);
|
|
5091
|
+
if (!r.ok) {
|
|
5092
|
+
process.stderr.write(
|
|
5093
|
+
`[goal-tick] push of ${goal.goalBranch} failed: ${r.error} \u2014 task dispatch will fall back to defaultBranch
|
|
5094
|
+
`
|
|
5095
|
+
);
|
|
5096
|
+
}
|
|
5097
|
+
};
|
|
5098
|
+
|
|
5099
|
+
// src/scripts/ensureGoalPr.ts
|
|
5100
|
+
var ensureGoalPr = async (ctx) => {
|
|
5101
|
+
const goal = ctx.data.goal;
|
|
5102
|
+
if (!goal) return;
|
|
5103
|
+
if (goal.goalPrUrl) return;
|
|
5104
|
+
if (!remoteBranchExists(goal.goalBranch, ctx.cwd)) return;
|
|
5105
|
+
const existing = listPrsByHead(goal.goalBranch, "open", ctx.cwd);
|
|
5106
|
+
if (existing.ok && existing.value && existing.value.length > 0) {
|
|
5107
|
+
goal.goalPrUrl = existing.value[0].url;
|
|
5108
|
+
return;
|
|
5109
|
+
}
|
|
5110
|
+
const title = `goal: ${goal.id}`;
|
|
5111
|
+
const body = goal.goalIssueNumber ? `Tracking integration PR for goal **${goal.id}**.
|
|
5112
|
+
|
|
5113
|
+
Child task PRs merge into \`${goal.goalBranch}\`. This PR is held in **draft** until every task is complete, then promoted to ready-for-review by goal-tick.
|
|
5114
|
+
|
|
5115
|
+
Closes #${goal.goalIssueNumber}
|
|
5116
|
+
` : `Tracking integration PR for goal **${goal.id}**.
|
|
5117
|
+
|
|
5118
|
+
Child task PRs merge into \`${goal.goalBranch}\`. Held in **draft** until every task is complete.
|
|
5119
|
+
`;
|
|
5120
|
+
const created = createPr(
|
|
5121
|
+
{
|
|
5122
|
+
head: goal.goalBranch,
|
|
5123
|
+
base: goal.defaultBranch,
|
|
5124
|
+
title,
|
|
5125
|
+
body,
|
|
5126
|
+
draft: true
|
|
5127
|
+
},
|
|
5128
|
+
ctx.cwd
|
|
5129
|
+
);
|
|
5130
|
+
if (!created.ok) {
|
|
5131
|
+
process.stderr.write(
|
|
5132
|
+
`[goal-tick] ensureGoalPr: gh pr create failed: ${created.error} (continuing without goal PR)
|
|
5133
|
+
`
|
|
5134
|
+
);
|
|
5135
|
+
return;
|
|
5136
|
+
}
|
|
5137
|
+
process.stdout.write(`[goal-tick] opened draft goal PR ${created.value} for ${goal.id}
|
|
5138
|
+
`);
|
|
5139
|
+
goal.goalPrUrl = created.value;
|
|
5140
|
+
};
|
|
5141
|
+
|
|
5142
|
+
// src/scripts/ensureLifecycleLabels.ts
|
|
5143
|
+
var ensureLifecycleLabels = async (ctx) => {
|
|
5144
|
+
const goal = ctx.data.goal;
|
|
5145
|
+
if (!goal) return;
|
|
5146
|
+
for (const spec of TICK_LABELS) {
|
|
5147
|
+
const r2 = ensureLabel(spec.name, spec.color, spec.description, ctx.cwd);
|
|
5148
|
+
if (!r2.ok) {
|
|
5149
|
+
process.stderr.write(`[goal-tick] ensureLifecycleLabels: ${spec.name}: ${r2.error}
|
|
5150
|
+
`);
|
|
5151
|
+
}
|
|
5152
|
+
}
|
|
5153
|
+
const goalLbl = goalLabel(goal.id);
|
|
5154
|
+
const r = ensureLabel(goalLbl, "0e8a16", `kody goal task: belongs to goal ${goal.id}`, ctx.cwd);
|
|
5155
|
+
if (!r.ok) {
|
|
5156
|
+
process.stderr.write(`[goal-tick] ensureLifecycleLabels: ${goalLbl}: ${r.error}
|
|
5157
|
+
`);
|
|
5158
|
+
}
|
|
5159
|
+
const u = ensureLabel(
|
|
5160
|
+
UMBRELLA_BUILDING_LABEL,
|
|
5161
|
+
"1d76db",
|
|
5162
|
+
"kody: in-flight (work being assembled on a branch)",
|
|
5163
|
+
ctx.cwd
|
|
5164
|
+
);
|
|
5165
|
+
if (!u.ok) {
|
|
5166
|
+
process.stderr.write(`[goal-tick] ensureLifecycleLabels: ${UMBRELLA_BUILDING_LABEL}: ${u.error}
|
|
5167
|
+
`);
|
|
5168
|
+
}
|
|
5169
|
+
};
|
|
5170
|
+
|
|
4653
5171
|
// src/pr.ts
|
|
4654
5172
|
var TITLE_MAX = 72;
|
|
4655
5173
|
function stripTitlePrefixes(raw) {
|
|
@@ -4786,20 +5304,37 @@ function ensurePr(opts) {
|
|
|
4786
5304
|
}
|
|
4787
5305
|
|
|
4788
5306
|
// src/scripts/ensurePr.ts
|
|
5307
|
+
function setOutcome(ctx, outcome) {
|
|
5308
|
+
ctx.data.prResult = outcome;
|
|
5309
|
+
if (outcome.kind === "created" || outcome.kind === "updated") {
|
|
5310
|
+
ctx.output.prUrl = outcome.url;
|
|
5311
|
+
}
|
|
5312
|
+
}
|
|
4789
5313
|
var ensurePr2 = async (ctx) => {
|
|
4790
5314
|
if (ctx.skipAgent && ctx.output.exitCode !== void 0) {
|
|
5315
|
+
setOutcome(ctx, { kind: "skipped", reason: "preflight short-circuited (skipAgent)" });
|
|
4791
5316
|
return;
|
|
4792
5317
|
}
|
|
4793
5318
|
const commitResult = ctx.data.commitResult;
|
|
4794
5319
|
const hasCommits = Boolean(ctx.data.hasCommitsAhead);
|
|
4795
5320
|
if (!commitResult?.committed && !hasCommits) {
|
|
5321
|
+
setOutcome(ctx, { kind: "skipped", reason: "no commits to ship" });
|
|
4796
5322
|
return;
|
|
4797
5323
|
}
|
|
4798
5324
|
if (commitResult?.committed && commitResult.pushed === false) {
|
|
5325
|
+
setOutcome(ctx, { kind: "skipped", reason: "local commit succeeded but push failed" });
|
|
5326
|
+
return;
|
|
5327
|
+
}
|
|
5328
|
+
if (ctx.data.verifyOk === false) {
|
|
5329
|
+
const reason = `verify failed: ${ctx.data.verifyReason ?? "unknown"}`;
|
|
5330
|
+
setOutcome(ctx, { kind: "skipped", reason });
|
|
4799
5331
|
return;
|
|
4800
5332
|
}
|
|
4801
5333
|
const branch = ctx.data.branch;
|
|
4802
|
-
if (!branch)
|
|
5334
|
+
if (!branch) {
|
|
5335
|
+
setOutcome(ctx, { kind: "skipped", reason: "no branch context (ctx.data.branch missing)" });
|
|
5336
|
+
return;
|
|
5337
|
+
}
|
|
4803
5338
|
const failureReason = computeFailureReason(ctx);
|
|
4804
5339
|
const isFailure = failureReason.length > 0;
|
|
4805
5340
|
const changedFiles = ctx.data.changedFiles ?? [];
|
|
@@ -4821,13 +5356,26 @@ var ensurePr2 = async (ctx) => {
|
|
|
4821
5356
|
baseBranch,
|
|
4822
5357
|
cwd: ctx.cwd
|
|
4823
5358
|
});
|
|
4824
|
-
|
|
4825
|
-
|
|
5359
|
+
if (!result.url || result.url.trim().length === 0) {
|
|
5360
|
+
const reason = `gh pr create returned empty URL (action=${result.action}); refusing to claim success`;
|
|
5361
|
+
ctx.data.prCrashReason = reason;
|
|
5362
|
+
ctx.output.exitCode = 4;
|
|
5363
|
+
ctx.output.reason = reason;
|
|
5364
|
+
setOutcome(ctx, { kind: "crashed", reason });
|
|
5365
|
+
return;
|
|
5366
|
+
}
|
|
5367
|
+
setOutcome(ctx, {
|
|
5368
|
+
kind: result.action === "created" ? "created" : "updated",
|
|
5369
|
+
url: result.url,
|
|
5370
|
+
number: result.number,
|
|
5371
|
+
draft: result.draft
|
|
5372
|
+
});
|
|
4826
5373
|
} catch (err) {
|
|
4827
5374
|
const reason = `PR creation failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
4828
5375
|
ctx.data.prCrashReason = reason;
|
|
4829
5376
|
ctx.output.exitCode = 4;
|
|
4830
5377
|
ctx.output.reason = reason;
|
|
5378
|
+
setOutcome(ctx, { kind: "crashed", reason });
|
|
4831
5379
|
}
|
|
4832
5380
|
};
|
|
4833
5381
|
function computeFailureReason(ctx) {
|
|
@@ -4863,8 +5411,203 @@ function collectExpectedTests(raw) {
|
|
|
4863
5411
|
return out;
|
|
4864
5412
|
}
|
|
4865
5413
|
|
|
5414
|
+
// src/scripts/ensureUmbrellaIssue.ts
|
|
5415
|
+
var ensureUmbrellaIssue = async (ctx) => {
|
|
5416
|
+
const goal = ctx.data.goal;
|
|
5417
|
+
if (!goal) return;
|
|
5418
|
+
if (goal.goalIssueNumber !== void 0) return;
|
|
5419
|
+
const title = `goal: ${goal.id}`;
|
|
5420
|
+
const body = `Umbrella issue for goal **${goal.id}**.
|
|
5421
|
+
|
|
5422
|
+
Closed automatically when the goal PR (\`${goal.goalBranch}\` \u2192 \`${goal.defaultBranch}\`) merges.
|
|
5423
|
+
`;
|
|
5424
|
+
const existing = findUmbrellaByTitle(goal.id, title, ctx.cwd);
|
|
5425
|
+
if (existing.ok && existing.value !== null && existing.value !== void 0) {
|
|
5426
|
+
process.stdout.write(`[goal-tick] adopted existing umbrella issue #${existing.value} for ${goal.id}
|
|
5427
|
+
`);
|
|
5428
|
+
goal.goalIssueNumber = existing.value;
|
|
5429
|
+
return;
|
|
5430
|
+
}
|
|
5431
|
+
const created = createIssue(
|
|
5432
|
+
{
|
|
5433
|
+
title,
|
|
5434
|
+
body,
|
|
5435
|
+
labels: [goalLabel(goal.id), UMBRELLA_BUILDING_LABEL]
|
|
5436
|
+
},
|
|
5437
|
+
ctx.cwd
|
|
5438
|
+
);
|
|
5439
|
+
if (!created.ok) {
|
|
5440
|
+
process.stderr.write(
|
|
5441
|
+
`[goal-tick] ensureUmbrellaIssue: gh issue create failed: ${created.error} \u2014 continuing without umbrella issue
|
|
5442
|
+
`
|
|
5443
|
+
);
|
|
5444
|
+
return;
|
|
5445
|
+
}
|
|
5446
|
+
process.stdout.write(`[goal-tick] opened umbrella issue #${created.value} for ${goal.id}
|
|
5447
|
+
`);
|
|
5448
|
+
goal.goalIssueNumber = created.value;
|
|
5449
|
+
};
|
|
5450
|
+
|
|
5451
|
+
// src/goal/state.ts
|
|
5452
|
+
import * as fs20 from "fs";
|
|
5453
|
+
import * as path20 from "path";
|
|
5454
|
+
var VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "done"]);
|
|
5455
|
+
var GoalStateError = class extends Error {
|
|
5456
|
+
constructor(path29, message) {
|
|
5457
|
+
super(`Invalid goal state at ${path29}:
|
|
5458
|
+
${message}`);
|
|
5459
|
+
this.path = path29;
|
|
5460
|
+
this.name = "GoalStateError";
|
|
5461
|
+
}
|
|
5462
|
+
path;
|
|
5463
|
+
};
|
|
5464
|
+
function parseGoalState(filePath, raw) {
|
|
5465
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
5466
|
+
throw new GoalStateError(filePath, "must be a JSON object");
|
|
5467
|
+
}
|
|
5468
|
+
const r = raw;
|
|
5469
|
+
const stateValue = r.state;
|
|
5470
|
+
if (typeof stateValue !== "string" || !VALID_STATES.has(stateValue)) {
|
|
5471
|
+
throw new GoalStateError(
|
|
5472
|
+
filePath,
|
|
5473
|
+
`"state" is required and must be one of: ${[...VALID_STATES].join(" | ")} (got ${JSON.stringify(stateValue)})`
|
|
5474
|
+
);
|
|
5475
|
+
}
|
|
5476
|
+
const parsed = {
|
|
5477
|
+
state: stateValue,
|
|
5478
|
+
extra: {}
|
|
5479
|
+
};
|
|
5480
|
+
if (typeof r.goalIssueNumber === "number" && Number.isFinite(r.goalIssueNumber)) {
|
|
5481
|
+
parsed.goalIssueNumber = r.goalIssueNumber;
|
|
5482
|
+
}
|
|
5483
|
+
if (typeof r.lastDispatchedIssue === "number" && Number.isFinite(r.lastDispatchedIssue)) {
|
|
5484
|
+
parsed.lastDispatchedIssue = r.lastDispatchedIssue;
|
|
5485
|
+
}
|
|
5486
|
+
if (typeof r.goalPrUrl === "string" && r.goalPrUrl.length > 0) {
|
|
5487
|
+
parsed.goalPrUrl = r.goalPrUrl;
|
|
5488
|
+
}
|
|
5489
|
+
for (const ts of ["updatedAt", "createdAt", "startedAt", "completedAt"]) {
|
|
5490
|
+
const v = r[ts];
|
|
5491
|
+
if (typeof v === "string" && v.length > 0) parsed[ts] = v;
|
|
5492
|
+
}
|
|
5493
|
+
const known = /* @__PURE__ */ new Set([
|
|
5494
|
+
"state",
|
|
5495
|
+
"goalIssueNumber",
|
|
5496
|
+
"lastDispatchedIssue",
|
|
5497
|
+
"goalPrUrl",
|
|
5498
|
+
"updatedAt",
|
|
5499
|
+
"createdAt",
|
|
5500
|
+
"startedAt",
|
|
5501
|
+
"completedAt"
|
|
5502
|
+
]);
|
|
5503
|
+
for (const [k, v] of Object.entries(r)) {
|
|
5504
|
+
if (!known.has(k)) parsed.extra[k] = v;
|
|
5505
|
+
}
|
|
5506
|
+
return parsed;
|
|
5507
|
+
}
|
|
5508
|
+
function serializeGoalState(s) {
|
|
5509
|
+
const obj = { ...s.extra, state: s.state };
|
|
5510
|
+
if (s.goalIssueNumber !== void 0) obj.goalIssueNumber = s.goalIssueNumber;
|
|
5511
|
+
if (s.lastDispatchedIssue !== void 0) obj.lastDispatchedIssue = s.lastDispatchedIssue;
|
|
5512
|
+
if (s.goalPrUrl !== void 0) obj.goalPrUrl = s.goalPrUrl;
|
|
5513
|
+
if (s.createdAt !== void 0) obj.createdAt = s.createdAt;
|
|
5514
|
+
if (s.startedAt !== void 0) obj.startedAt = s.startedAt;
|
|
5515
|
+
if (s.completedAt !== void 0) obj.completedAt = s.completedAt;
|
|
5516
|
+
if (s.updatedAt !== void 0) obj.updatedAt = s.updatedAt;
|
|
5517
|
+
return `${JSON.stringify(obj, null, 2)}
|
|
5518
|
+
`;
|
|
5519
|
+
}
|
|
5520
|
+
function goalStatePath(cwd, goalId) {
|
|
5521
|
+
return path20.join(cwd, ".kody", "goals", goalId, "state.json");
|
|
5522
|
+
}
|
|
5523
|
+
function readGoalState(cwd, goalId) {
|
|
5524
|
+
const file = goalStatePath(cwd, goalId);
|
|
5525
|
+
if (!fs20.existsSync(file)) {
|
|
5526
|
+
throw new GoalStateError(file, "file not found");
|
|
5527
|
+
}
|
|
5528
|
+
let raw;
|
|
5529
|
+
try {
|
|
5530
|
+
raw = JSON.parse(fs20.readFileSync(file, "utf-8"));
|
|
5531
|
+
} catch (err) {
|
|
5532
|
+
throw new GoalStateError(file, `invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
5533
|
+
}
|
|
5534
|
+
return parseGoalState(file, raw);
|
|
5535
|
+
}
|
|
5536
|
+
function writeGoalState(cwd, goalId, state) {
|
|
5537
|
+
const file = goalStatePath(cwd, goalId);
|
|
5538
|
+
fs20.mkdirSync(path20.dirname(file), { recursive: true });
|
|
5539
|
+
fs20.writeFileSync(file, serializeGoalState(state), "utf-8");
|
|
5540
|
+
}
|
|
5541
|
+
function nowIso() {
|
|
5542
|
+
return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
5543
|
+
}
|
|
5544
|
+
|
|
5545
|
+
// src/scripts/finalizeGoal.ts
|
|
5546
|
+
var finalizeGoal = async (ctx) => {
|
|
5547
|
+
const goal = ctx.data.goal;
|
|
5548
|
+
if (!goal) return;
|
|
5549
|
+
process.stdout.write(`[goal-tick] all task(s) closed \u2014 finalising goal ${goal.id}
|
|
5550
|
+
`);
|
|
5551
|
+
if (!remoteBranchExists(goal.goalBranch, ctx.cwd)) {
|
|
5552
|
+
process.stderr.write(`[goal-tick] goal branch ${goal.goalBranch} not found on origin \u2014 skipping final PR
|
|
5553
|
+
`);
|
|
5554
|
+
finishState(goal);
|
|
5555
|
+
return;
|
|
5556
|
+
}
|
|
5557
|
+
const title = `goal: ${goal.id}`;
|
|
5558
|
+
const closesLine = goal.goalIssueNumber ? `
|
|
5559
|
+
|
|
5560
|
+
Closes #${goal.goalIssueNumber}
|
|
5561
|
+
` : "\n";
|
|
5562
|
+
const body = `Final integration PR for goal **${goal.id}**.
|
|
5563
|
+
|
|
5564
|
+
All task issues are closed and merged into \`${goal.goalBranch}\`. Ready for review.${closesLine}`;
|
|
5565
|
+
const existing = listPrsByHead(goal.goalBranch, "open", ctx.cwd);
|
|
5566
|
+
if (existing.ok && existing.value && existing.value.length > 0) {
|
|
5567
|
+
const pr = existing.value[0];
|
|
5568
|
+
goal.goalPrUrl = pr.url;
|
|
5569
|
+
const edit = editPrBody(pr.number, body, ctx.cwd);
|
|
5570
|
+
if (!edit.ok) {
|
|
5571
|
+
process.stderr.write(`[goal-tick] finalizeGoal: editPrBody failed: ${edit.error}
|
|
5572
|
+
`);
|
|
5573
|
+
}
|
|
5574
|
+
if (pr.isDraft) {
|
|
5575
|
+
process.stdout.write(`[goal-tick] promoting draft goal PR #${pr.number} to ready-for-review
|
|
5576
|
+
`);
|
|
5577
|
+
const ready = markPrReady(pr.number, ctx.cwd);
|
|
5578
|
+
if (!ready.ok) {
|
|
5579
|
+
process.stderr.write(`[goal-tick] finalizeGoal: markPrReady failed: ${ready.error}
|
|
5580
|
+
`);
|
|
5581
|
+
}
|
|
5582
|
+
}
|
|
5583
|
+
} else {
|
|
5584
|
+
const created = createPr(
|
|
5585
|
+
{
|
|
5586
|
+
head: goal.goalBranch,
|
|
5587
|
+
base: goal.defaultBranch,
|
|
5588
|
+
title,
|
|
5589
|
+
body,
|
|
5590
|
+
// ready-for-review (not draft) since we're finalizing.
|
|
5591
|
+
draft: false
|
|
5592
|
+
},
|
|
5593
|
+
ctx.cwd
|
|
5594
|
+
);
|
|
5595
|
+
if (!created.ok) {
|
|
5596
|
+
process.stderr.write(`[goal-tick] finalizeGoal: gh pr create failed: ${created.error}
|
|
5597
|
+
`);
|
|
5598
|
+
} else {
|
|
5599
|
+
goal.goalPrUrl = created.value;
|
|
5600
|
+
}
|
|
5601
|
+
}
|
|
5602
|
+
finishState(goal);
|
|
5603
|
+
};
|
|
5604
|
+
function finishState(goal) {
|
|
5605
|
+
goal.state = "done";
|
|
5606
|
+
goal.completedAt = nowIso();
|
|
5607
|
+
}
|
|
5608
|
+
|
|
4866
5609
|
// src/scripts/finishFlow.ts
|
|
4867
|
-
import { execFileSync as
|
|
5610
|
+
import { execFileSync as execFileSync15 } from "child_process";
|
|
4868
5611
|
var API_TIMEOUT_MS6 = 3e4;
|
|
4869
5612
|
var STATUS_ICON = {
|
|
4870
5613
|
"review-passed": "\u2705",
|
|
@@ -4898,7 +5641,7 @@ var finishFlow = async (ctx, _profile, _agentResult, args) => {
|
|
|
4898
5641
|
**PR:** ${state.core.prUrl}` : "";
|
|
4899
5642
|
const body = `${icon} kody flow \`${flowName}\` finished \u2014 \`${reason}\`${prSuffix}`;
|
|
4900
5643
|
try {
|
|
4901
|
-
|
|
5644
|
+
execFileSync15("gh", ["issue", "comment", String(issueNumber), "--body", body], {
|
|
4902
5645
|
timeout: API_TIMEOUT_MS6,
|
|
4903
5646
|
cwd: ctx.cwd,
|
|
4904
5647
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -4912,7 +5655,7 @@ var finishFlow = async (ctx, _profile, _agentResult, args) => {
|
|
|
4912
5655
|
};
|
|
4913
5656
|
|
|
4914
5657
|
// src/branch.ts
|
|
4915
|
-
import { execFileSync as
|
|
5658
|
+
import { execFileSync as execFileSync16 } from "child_process";
|
|
4916
5659
|
var UncommittedChangesError = class extends Error {
|
|
4917
5660
|
constructor(branch) {
|
|
4918
5661
|
super(`Uncommitted changes on branch '${branch}' \u2014 refusing to run to protect work in progress`);
|
|
@@ -4922,7 +5665,7 @@ var UncommittedChangesError = class extends Error {
|
|
|
4922
5665
|
branch;
|
|
4923
5666
|
};
|
|
4924
5667
|
function git2(args, cwd) {
|
|
4925
|
-
return
|
|
5668
|
+
return execFileSync16("git", args, {
|
|
4926
5669
|
encoding: "utf-8",
|
|
4927
5670
|
timeout: 3e4,
|
|
4928
5671
|
cwd,
|
|
@@ -4947,7 +5690,7 @@ function checkoutPrBranch(prNumber, cwd) {
|
|
|
4947
5690
|
SKIP_HOOKS: "1",
|
|
4948
5691
|
GH_TOKEN: process.env.GH_PAT?.trim() || process.env.GH_TOKEN || ""
|
|
4949
5692
|
};
|
|
4950
|
-
|
|
5693
|
+
execFileSync16("gh", ["pr", "checkout", String(prNumber)], {
|
|
4951
5694
|
cwd,
|
|
4952
5695
|
env,
|
|
4953
5696
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -5061,8 +5804,8 @@ function ensureFeatureBranch(issueNumber, title, defaultBranch, cwd, baseBranch)
|
|
|
5061
5804
|
}
|
|
5062
5805
|
|
|
5063
5806
|
// src/gha.ts
|
|
5064
|
-
import { execFileSync as
|
|
5065
|
-
import * as
|
|
5807
|
+
import { execFileSync as execFileSync17 } from "child_process";
|
|
5808
|
+
import * as fs21 from "fs";
|
|
5066
5809
|
function getRunUrl() {
|
|
5067
5810
|
const server = process.env.GITHUB_SERVER_URL;
|
|
5068
5811
|
const repo = process.env.GITHUB_REPOSITORY;
|
|
@@ -5073,10 +5816,10 @@ function getRunUrl() {
|
|
|
5073
5816
|
function reactToTriggerComment(cwd) {
|
|
5074
5817
|
if (process.env.GITHUB_EVENT_NAME !== "issue_comment") return;
|
|
5075
5818
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
5076
|
-
if (!eventPath || !
|
|
5819
|
+
if (!eventPath || !fs21.existsSync(eventPath)) return;
|
|
5077
5820
|
let event = null;
|
|
5078
5821
|
try {
|
|
5079
|
-
event = JSON.parse(
|
|
5822
|
+
event = JSON.parse(fs21.readFileSync(eventPath, "utf-8"));
|
|
5080
5823
|
} catch {
|
|
5081
5824
|
return;
|
|
5082
5825
|
}
|
|
@@ -5104,7 +5847,7 @@ function reactToTriggerComment(cwd) {
|
|
|
5104
5847
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
5105
5848
|
if (attempt > 0) sleepMs(attempt === 1 ? 500 : 1500);
|
|
5106
5849
|
try {
|
|
5107
|
-
|
|
5850
|
+
execFileSync17("gh", args, opts);
|
|
5108
5851
|
return;
|
|
5109
5852
|
} catch (err) {
|
|
5110
5853
|
lastErr = err;
|
|
@@ -5117,13 +5860,13 @@ function reactToTriggerComment(cwd) {
|
|
|
5117
5860
|
}
|
|
5118
5861
|
function sleepMs(ms) {
|
|
5119
5862
|
try {
|
|
5120
|
-
|
|
5863
|
+
execFileSync17("sleep", [(ms / 1e3).toString()], { stdio: "ignore", timeout: ms + 1e3 });
|
|
5121
5864
|
} catch {
|
|
5122
5865
|
}
|
|
5123
5866
|
}
|
|
5124
5867
|
|
|
5125
5868
|
// src/workflow.ts
|
|
5126
|
-
import { execFileSync as
|
|
5869
|
+
import { execFileSync as execFileSync18 } from "child_process";
|
|
5127
5870
|
var GH_TIMEOUT_MS = 3e4;
|
|
5128
5871
|
function ghToken3() {
|
|
5129
5872
|
return process.env.GH_PAT?.trim() || process.env.GH_TOKEN;
|
|
@@ -5131,7 +5874,7 @@ function ghToken3() {
|
|
|
5131
5874
|
function gh3(args, cwd) {
|
|
5132
5875
|
const token = ghToken3();
|
|
5133
5876
|
const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
|
|
5134
|
-
return
|
|
5877
|
+
return execFileSync18("gh", args, {
|
|
5135
5878
|
encoding: "utf-8",
|
|
5136
5879
|
timeout: GH_TIMEOUT_MS,
|
|
5137
5880
|
cwd,
|
|
@@ -5314,24 +6057,63 @@ function tryPostPr2(prNumber, body, cwd) {
|
|
|
5314
6057
|
}
|
|
5315
6058
|
}
|
|
5316
6059
|
|
|
6060
|
+
// src/scripts/handleAbandonedGoal.ts
|
|
6061
|
+
var handleAbandonedGoal = async (ctx) => {
|
|
6062
|
+
const goal = ctx.data.goal;
|
|
6063
|
+
if (!goal || goal.state !== "abandoned") return;
|
|
6064
|
+
process.stdout.write(`[goal-tick] ${goal.id} is abandoned \u2014 running cleanup
|
|
6065
|
+
`);
|
|
6066
|
+
const issues = listGoalIssues(goal.id, goal.goalIssueNumber, ctx.cwd);
|
|
6067
|
+
if (!issues.ok) {
|
|
6068
|
+
process.stderr.write(`[goal-tick] handleAbandonedGoal: list failed: ${issues.error}
|
|
6069
|
+
`);
|
|
6070
|
+
} else {
|
|
6071
|
+
for (const i of issues.value ?? []) {
|
|
6072
|
+
if (i.state !== "OPEN") continue;
|
|
6073
|
+
const r = closeIssue(
|
|
6074
|
+
i.number,
|
|
6075
|
+
{
|
|
6076
|
+
comment: "_Goal abandoned \u2014 closing this task without dispatch._",
|
|
6077
|
+
reason: "not planned"
|
|
6078
|
+
},
|
|
6079
|
+
ctx.cwd
|
|
6080
|
+
);
|
|
6081
|
+
if (!r.ok) {
|
|
6082
|
+
process.stderr.write(`[goal-tick] handleAbandonedGoal: failed to close #${i.number}: ${r.error}
|
|
6083
|
+
`);
|
|
6084
|
+
}
|
|
6085
|
+
}
|
|
6086
|
+
}
|
|
6087
|
+
const goalPrs = listPrsByHead(goal.goalBranch, "open", ctx.cwd);
|
|
6088
|
+
if (goalPrs.ok && goalPrs.value && goalPrs.value.length > 0) {
|
|
6089
|
+
const pr = goalPrs.value[0];
|
|
6090
|
+
const r = closePr(pr.number, "_Goal abandoned by operator \u2014 closing without merge._", ctx.cwd);
|
|
6091
|
+
if (!r.ok) {
|
|
6092
|
+
process.stderr.write(`[goal-tick] handleAbandonedGoal: failed to close goal PR #${pr.number}: ${r.error}
|
|
6093
|
+
`);
|
|
6094
|
+
}
|
|
6095
|
+
}
|
|
6096
|
+
goal.state = "closed";
|
|
6097
|
+
};
|
|
6098
|
+
|
|
5317
6099
|
// src/scripts/initFlow.ts
|
|
5318
|
-
import { execFileSync as
|
|
5319
|
-
import * as
|
|
5320
|
-
import * as
|
|
6100
|
+
import { execFileSync as execFileSync19 } from "child_process";
|
|
6101
|
+
import * as fs23 from "fs";
|
|
6102
|
+
import * as path22 from "path";
|
|
5321
6103
|
|
|
5322
6104
|
// src/scripts/loadQaGuide.ts
|
|
5323
|
-
import * as
|
|
5324
|
-
import * as
|
|
6105
|
+
import * as fs22 from "fs";
|
|
6106
|
+
import * as path21 from "path";
|
|
5325
6107
|
var QA_GUIDE_REL_PATH = ".kody/qa-guide.md";
|
|
5326
6108
|
var loadQaGuide = async (ctx) => {
|
|
5327
|
-
const full =
|
|
5328
|
-
if (!
|
|
6109
|
+
const full = path21.join(ctx.cwd, QA_GUIDE_REL_PATH);
|
|
6110
|
+
if (!fs22.existsSync(full)) {
|
|
5329
6111
|
ctx.data.qaGuide = "";
|
|
5330
6112
|
ctx.data.qaGuidePath = "";
|
|
5331
6113
|
return;
|
|
5332
6114
|
}
|
|
5333
6115
|
try {
|
|
5334
|
-
ctx.data.qaGuide =
|
|
6116
|
+
ctx.data.qaGuide = fs22.readFileSync(full, "utf-8");
|
|
5335
6117
|
ctx.data.qaGuidePath = QA_GUIDE_REL_PATH;
|
|
5336
6118
|
} catch {
|
|
5337
6119
|
ctx.data.qaGuide = "";
|
|
@@ -5341,9 +6123,9 @@ var loadQaGuide = async (ctx) => {
|
|
|
5341
6123
|
|
|
5342
6124
|
// src/scripts/initFlow.ts
|
|
5343
6125
|
function detectPackageManager(cwd) {
|
|
5344
|
-
if (
|
|
5345
|
-
if (
|
|
5346
|
-
if (
|
|
6126
|
+
if (fs23.existsSync(path22.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
6127
|
+
if (fs23.existsSync(path22.join(cwd, "yarn.lock"))) return "yarn";
|
|
6128
|
+
if (fs23.existsSync(path22.join(cwd, "bun.lockb"))) return "bun";
|
|
5347
6129
|
return "npm";
|
|
5348
6130
|
}
|
|
5349
6131
|
function qualityCommandsFor(pm) {
|
|
@@ -5356,7 +6138,7 @@ function qualityCommandsFor(pm) {
|
|
|
5356
6138
|
function detectOwnerRepo(cwd) {
|
|
5357
6139
|
let url;
|
|
5358
6140
|
try {
|
|
5359
|
-
url =
|
|
6141
|
+
url = execFileSync19("git", ["remote", "get-url", "origin"], {
|
|
5360
6142
|
cwd,
|
|
5361
6143
|
encoding: "utf-8",
|
|
5362
6144
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -5441,7 +6223,7 @@ jobs:
|
|
|
5441
6223
|
`;
|
|
5442
6224
|
function defaultBranchFromGit(cwd) {
|
|
5443
6225
|
try {
|
|
5444
|
-
const ref =
|
|
6226
|
+
const ref = execFileSync19("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
|
|
5445
6227
|
cwd,
|
|
5446
6228
|
encoding: "utf-8",
|
|
5447
6229
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -5449,7 +6231,7 @@ function defaultBranchFromGit(cwd) {
|
|
|
5449
6231
|
return ref.replace("refs/remotes/origin/", "");
|
|
5450
6232
|
} catch {
|
|
5451
6233
|
try {
|
|
5452
|
-
return
|
|
6234
|
+
return execFileSync19("git", ["branch", "--show-current"], {
|
|
5453
6235
|
cwd,
|
|
5454
6236
|
encoding: "utf-8",
|
|
5455
6237
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -5465,48 +6247,48 @@ function performInit(cwd, force) {
|
|
|
5465
6247
|
const pm = detectPackageManager(cwd);
|
|
5466
6248
|
const ownerRepo = detectOwnerRepo(cwd);
|
|
5467
6249
|
const defaultBranch = defaultBranchFromGit(cwd);
|
|
5468
|
-
const configPath =
|
|
5469
|
-
if (
|
|
6250
|
+
const configPath = path22.join(cwd, "kody.config.json");
|
|
6251
|
+
if (fs23.existsSync(configPath) && !force) {
|
|
5470
6252
|
skipped.push("kody.config.json");
|
|
5471
6253
|
} else {
|
|
5472
6254
|
const cfg = makeConfig(pm, ownerRepo, defaultBranch);
|
|
5473
|
-
|
|
6255
|
+
fs23.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
|
|
5474
6256
|
`);
|
|
5475
6257
|
wrote.push("kody.config.json");
|
|
5476
6258
|
}
|
|
5477
|
-
const workflowDir =
|
|
5478
|
-
const workflowPath =
|
|
5479
|
-
if (
|
|
6259
|
+
const workflowDir = path22.join(cwd, ".github", "workflows");
|
|
6260
|
+
const workflowPath = path22.join(workflowDir, "kody.yml");
|
|
6261
|
+
if (fs23.existsSync(workflowPath) && !force) {
|
|
5480
6262
|
skipped.push(".github/workflows/kody.yml");
|
|
5481
6263
|
} else {
|
|
5482
|
-
|
|
5483
|
-
|
|
6264
|
+
fs23.mkdirSync(workflowDir, { recursive: true });
|
|
6265
|
+
fs23.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
|
|
5484
6266
|
wrote.push(".github/workflows/kody.yml");
|
|
5485
6267
|
}
|
|
5486
|
-
const hasUi =
|
|
6268
|
+
const hasUi = fs23.existsSync(path22.join(cwd, "src/app")) || fs23.existsSync(path22.join(cwd, "app")) || fs23.existsSync(path22.join(cwd, "pages"));
|
|
5487
6269
|
if (hasUi) {
|
|
5488
|
-
const qaGuidePath =
|
|
5489
|
-
if (
|
|
6270
|
+
const qaGuidePath = path22.join(cwd, QA_GUIDE_REL_PATH);
|
|
6271
|
+
if (fs23.existsSync(qaGuidePath) && !force) {
|
|
5490
6272
|
skipped.push(QA_GUIDE_REL_PATH);
|
|
5491
6273
|
} else {
|
|
5492
|
-
|
|
6274
|
+
fs23.mkdirSync(path22.dirname(qaGuidePath), { recursive: true });
|
|
5493
6275
|
const discovery = runQaDiscovery(cwd);
|
|
5494
|
-
|
|
6276
|
+
fs23.writeFileSync(qaGuidePath, generateQaGuideTemplate(discovery));
|
|
5495
6277
|
wrote.push(QA_GUIDE_REL_PATH);
|
|
5496
6278
|
}
|
|
5497
6279
|
}
|
|
5498
6280
|
const builtinJobs = listBuiltinJobs();
|
|
5499
6281
|
if (builtinJobs.length > 0) {
|
|
5500
|
-
const jobsDir =
|
|
5501
|
-
|
|
6282
|
+
const jobsDir = path22.join(cwd, ".kody", "jobs");
|
|
6283
|
+
fs23.mkdirSync(jobsDir, { recursive: true });
|
|
5502
6284
|
for (const job of builtinJobs) {
|
|
5503
|
-
const rel =
|
|
5504
|
-
const target =
|
|
5505
|
-
if (
|
|
6285
|
+
const rel = path22.join(".kody", "jobs", `${job.slug}.md`);
|
|
6286
|
+
const target = path22.join(cwd, rel);
|
|
6287
|
+
if (fs23.existsSync(target) && !force) {
|
|
5506
6288
|
skipped.push(rel);
|
|
5507
6289
|
continue;
|
|
5508
6290
|
}
|
|
5509
|
-
|
|
6291
|
+
fs23.writeFileSync(target, fs23.readFileSync(job.filePath, "utf-8"));
|
|
5510
6292
|
wrote.push(rel);
|
|
5511
6293
|
}
|
|
5512
6294
|
}
|
|
@@ -5518,12 +6300,12 @@ function performInit(cwd, force) {
|
|
|
5518
6300
|
continue;
|
|
5519
6301
|
}
|
|
5520
6302
|
if (profile.kind !== "scheduled" || !profile.schedule) continue;
|
|
5521
|
-
const target =
|
|
5522
|
-
if (
|
|
6303
|
+
const target = path22.join(workflowDir, `kody-${exe.name}.yml`);
|
|
6304
|
+
if (fs23.existsSync(target) && !force) {
|
|
5523
6305
|
skipped.push(`.github/workflows/kody-${exe.name}.yml`);
|
|
5524
6306
|
continue;
|
|
5525
6307
|
}
|
|
5526
|
-
|
|
6308
|
+
fs23.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
|
|
5527
6309
|
wrote.push(`.github/workflows/kody-${exe.name}.yml`);
|
|
5528
6310
|
}
|
|
5529
6311
|
let labels;
|
|
@@ -5611,6 +6393,48 @@ var loadCoverageRules = async (ctx) => {
|
|
|
5611
6393
|
ctx.data.coverageRules = ctx.config.testRequirements ?? [];
|
|
5612
6394
|
};
|
|
5613
6395
|
|
|
6396
|
+
// src/scripts/loadGoalState.ts
|
|
6397
|
+
var loadGoalState = async (ctx) => {
|
|
6398
|
+
const goalId = ctx.args.goal;
|
|
6399
|
+
if (typeof goalId !== "string" || goalId.length === 0) {
|
|
6400
|
+
ctx.skipAgent = true;
|
|
6401
|
+
ctx.output.exitCode = 1;
|
|
6402
|
+
ctx.output.reason = "missing --goal";
|
|
6403
|
+
return;
|
|
6404
|
+
}
|
|
6405
|
+
if (goalId.includes("/") || goalId.includes("..")) {
|
|
6406
|
+
ctx.skipAgent = true;
|
|
6407
|
+
ctx.output.exitCode = 1;
|
|
6408
|
+
ctx.output.reason = "invalid goal id (no slashes or '..' allowed)";
|
|
6409
|
+
return;
|
|
6410
|
+
}
|
|
6411
|
+
try {
|
|
6412
|
+
const state = readGoalState(ctx.cwd, goalId);
|
|
6413
|
+
ctx.data.goal = {
|
|
6414
|
+
id: goalId,
|
|
6415
|
+
state: state.state,
|
|
6416
|
+
goalIssueNumber: state.goalIssueNumber,
|
|
6417
|
+
lastDispatchedIssue: state.lastDispatchedIssue,
|
|
6418
|
+
goalPrUrl: state.goalPrUrl,
|
|
6419
|
+
// Cache the full parsed object so saveGoalState can preserve `extra`.
|
|
6420
|
+
raw: state,
|
|
6421
|
+
// `phase` is populated by deriveGoalPhase later in the chain. Initialize
|
|
6422
|
+
// to undefined so runWhen on `data.goal.phase` can match correctly.
|
|
6423
|
+
phase: void 0,
|
|
6424
|
+
// Populated by ensureGoalBranch / configured by config.git.defaultBranch.
|
|
6425
|
+
defaultBranch: ctx.config.git.defaultBranch,
|
|
6426
|
+
// Convenience derivations.
|
|
6427
|
+
goalBranch: `goal-${goalId}`
|
|
6428
|
+
};
|
|
6429
|
+
} catch (err) {
|
|
6430
|
+
process.stdout.write(`[goal-tick] ${err instanceof Error ? err.message : String(err)}
|
|
6431
|
+
`);
|
|
6432
|
+
ctx.skipAgent = true;
|
|
6433
|
+
ctx.output.exitCode = 0;
|
|
6434
|
+
ctx.output.reason = "no goal state to tick";
|
|
6435
|
+
}
|
|
6436
|
+
};
|
|
6437
|
+
|
|
5614
6438
|
// src/scripts/loadIssueContext.ts
|
|
5615
6439
|
var DEFAULT_COMMENT_LIMIT = 12;
|
|
5616
6440
|
var DEFAULT_COMMENT_MAX_BYTES = 16e3;
|
|
@@ -5661,8 +6485,8 @@ var loadIssueStateComment = async (ctx, _profile, args) => {
|
|
|
5661
6485
|
};
|
|
5662
6486
|
|
|
5663
6487
|
// src/scripts/loadJobFromFile.ts
|
|
5664
|
-
import * as
|
|
5665
|
-
import * as
|
|
6488
|
+
import * as fs24 from "fs";
|
|
6489
|
+
import * as path23 from "path";
|
|
5666
6490
|
var loadJobFromFile = async (ctx, _profile, args) => {
|
|
5667
6491
|
const jobsDir = String(args?.jobsDir ?? ".kody/jobs");
|
|
5668
6492
|
const slugArg = String(args?.slugArg ?? "job");
|
|
@@ -5670,11 +6494,11 @@ var loadJobFromFile = async (ctx, _profile, args) => {
|
|
|
5670
6494
|
if (!slug) {
|
|
5671
6495
|
throw new Error(`loadJobFromFile: ctx.args.${slugArg} must be a non-empty slug`);
|
|
5672
6496
|
}
|
|
5673
|
-
const absPath =
|
|
5674
|
-
if (!
|
|
6497
|
+
const absPath = path23.join(ctx.cwd, jobsDir, `${slug}.md`);
|
|
6498
|
+
if (!fs24.existsSync(absPath)) {
|
|
5675
6499
|
throw new Error(`loadJobFromFile: job file not found: ${absPath}`);
|
|
5676
6500
|
}
|
|
5677
|
-
const raw =
|
|
6501
|
+
const raw = fs24.readFileSync(absPath, "utf-8");
|
|
5678
6502
|
const { title, body } = parseJobFile(raw, slug);
|
|
5679
6503
|
const backend = resolveBackend({ config: ctx.config, cwd: ctx.cwd, jobsDir });
|
|
5680
6504
|
const loaded = await backend.load(slug);
|
|
@@ -5706,16 +6530,16 @@ function humanizeSlug(slug) {
|
|
|
5706
6530
|
}
|
|
5707
6531
|
|
|
5708
6532
|
// src/scripts/loadMemoryContext.ts
|
|
5709
|
-
import * as
|
|
5710
|
-
import * as
|
|
6533
|
+
import * as fs25 from "fs";
|
|
6534
|
+
import * as path24 from "path";
|
|
5711
6535
|
var MEMORY_DIR_RELATIVE = ".kody/memory";
|
|
5712
6536
|
var MAX_PAGES = 8;
|
|
5713
6537
|
var PER_PAGE_MAX_BYTES = 4e3;
|
|
5714
6538
|
var TOTAL_MAX_BYTES = 24e3;
|
|
5715
6539
|
var TRUNCATED_SUFFIX = "\n\n\u2026 (truncated)";
|
|
5716
6540
|
var loadMemoryContext = async (ctx) => {
|
|
5717
|
-
const memoryAbs =
|
|
5718
|
-
if (!
|
|
6541
|
+
const memoryAbs = path24.join(ctx.cwd, MEMORY_DIR_RELATIVE);
|
|
6542
|
+
if (!fs25.existsSync(memoryAbs)) {
|
|
5719
6543
|
ctx.data.memoryContext = "";
|
|
5720
6544
|
return;
|
|
5721
6545
|
}
|
|
@@ -5740,21 +6564,21 @@ function collectPages(memoryAbs) {
|
|
|
5740
6564
|
walkMd(memoryAbs, (file) => {
|
|
5741
6565
|
let stat;
|
|
5742
6566
|
try {
|
|
5743
|
-
stat =
|
|
6567
|
+
stat = fs25.statSync(file);
|
|
5744
6568
|
} catch {
|
|
5745
6569
|
return;
|
|
5746
6570
|
}
|
|
5747
6571
|
let raw;
|
|
5748
6572
|
try {
|
|
5749
|
-
raw =
|
|
6573
|
+
raw = fs25.readFileSync(file, "utf-8");
|
|
5750
6574
|
} catch {
|
|
5751
6575
|
return;
|
|
5752
6576
|
}
|
|
5753
6577
|
const fm = raw.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
5754
|
-
const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ??
|
|
6578
|
+
const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path24.basename(file, ".md");
|
|
5755
6579
|
const updated = fm?.[1]?.match(/^updated:\s*([0-9T:.+\-Z]+)/m)?.[1]?.trim() ?? "";
|
|
5756
6580
|
out.push({
|
|
5757
|
-
relPath:
|
|
6581
|
+
relPath: path24.relative(memoryAbs, file),
|
|
5758
6582
|
title,
|
|
5759
6583
|
updated,
|
|
5760
6584
|
content: raw.length > PER_PAGE_MAX_BYTES ? raw.slice(0, PER_PAGE_MAX_BYTES) + TRUNCATED_SUFFIX : raw,
|
|
@@ -5822,16 +6646,16 @@ function walkMd(root, visit) {
|
|
|
5822
6646
|
const dir = stack.pop();
|
|
5823
6647
|
let names;
|
|
5824
6648
|
try {
|
|
5825
|
-
names =
|
|
6649
|
+
names = fs25.readdirSync(dir);
|
|
5826
6650
|
} catch {
|
|
5827
6651
|
continue;
|
|
5828
6652
|
}
|
|
5829
6653
|
for (const name of names) {
|
|
5830
6654
|
if (name.startsWith(".")) continue;
|
|
5831
|
-
const full =
|
|
6655
|
+
const full = path24.join(dir, name);
|
|
5832
6656
|
let stat;
|
|
5833
6657
|
try {
|
|
5834
|
-
stat =
|
|
6658
|
+
stat = fs25.statSync(full);
|
|
5835
6659
|
} catch {
|
|
5836
6660
|
continue;
|
|
5837
6661
|
}
|
|
@@ -5954,8 +6778,32 @@ var markFlowSuccess = async (ctx) => {
|
|
|
5954
6778
|
}
|
|
5955
6779
|
};
|
|
5956
6780
|
|
|
6781
|
+
// src/scripts/mergeReadyTaskPRs.ts
|
|
6782
|
+
var mergeReadyTaskPRs = async (ctx) => {
|
|
6783
|
+
const goal = ctx.data.goal;
|
|
6784
|
+
if (!goal) return;
|
|
6785
|
+
const open = listPrsByBase(goal.goalBranch, "open", ctx.cwd);
|
|
6786
|
+
if (!open.ok) {
|
|
6787
|
+
process.stderr.write(`[goal-tick] mergeReadyTaskPRs: list failed: ${open.error}
|
|
6788
|
+
`);
|
|
6789
|
+
return;
|
|
6790
|
+
}
|
|
6791
|
+
for (const pr of open.value ?? []) {
|
|
6792
|
+
if (pr.isDraft) continue;
|
|
6793
|
+
if (pr.mergeable !== "MERGEABLE") continue;
|
|
6794
|
+
if (pr.mergeStateStatus !== "CLEAN") continue;
|
|
6795
|
+
process.stdout.write(`[goal-tick] merging PR #${pr.number} into ${goal.goalBranch}
|
|
6796
|
+
`);
|
|
6797
|
+
const r = mergePrSquash(pr.number, ctx.cwd);
|
|
6798
|
+
if (!r.ok) {
|
|
6799
|
+
process.stderr.write(`[goal-tick] failed to merge PR #${pr.number}: ${r.error} (continuing)
|
|
6800
|
+
`);
|
|
6801
|
+
}
|
|
6802
|
+
}
|
|
6803
|
+
};
|
|
6804
|
+
|
|
5957
6805
|
// src/scripts/mergeReleasePr.ts
|
|
5958
|
-
import { execFileSync as
|
|
6806
|
+
import { execFileSync as execFileSync20 } from "child_process";
|
|
5959
6807
|
var API_TIMEOUT_MS7 = 6e4;
|
|
5960
6808
|
var mergeReleasePr = async (ctx) => {
|
|
5961
6809
|
const state = ctx.data.taskState;
|
|
@@ -5974,7 +6822,7 @@ var mergeReleasePr = async (ctx) => {
|
|
|
5974
6822
|
process.stderr.write(`[kody mergeReleasePr] merging PR #${prNumber} (${prUrl})
|
|
5975
6823
|
`);
|
|
5976
6824
|
try {
|
|
5977
|
-
const out =
|
|
6825
|
+
const out = execFileSync20("gh", ["pr", "merge", String(prNumber), "--merge"], {
|
|
5978
6826
|
timeout: API_TIMEOUT_MS7,
|
|
5979
6827
|
cwd: ctx.cwd,
|
|
5980
6828
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -6092,7 +6940,7 @@ function buildIssueTitle(scope, verdict) {
|
|
|
6092
6940
|
const verdictTag = verdict === "UNKNOWN" ? "REPORT" : verdict;
|
|
6093
6941
|
return `QA [${verdictTag}]: ${focus} \u2014 ${date}`.slice(0, 240);
|
|
6094
6942
|
}
|
|
6095
|
-
function
|
|
6943
|
+
function ensureLabel3(cwd) {
|
|
6096
6944
|
try {
|
|
6097
6945
|
gh(["label", "create", QA_LABEL, "--color", "8b5cf6", "--description", "kody: QA report", "--force"], { cwd });
|
|
6098
6946
|
return true;
|
|
@@ -6152,7 +7000,7 @@ QA_REPORT_POSTED=https://github.com/${ctx.config.github.owner}/${ctx.config.gith
|
|
|
6152
7000
|
}
|
|
6153
7001
|
const scope = ctx.args.scope;
|
|
6154
7002
|
const title = buildIssueTitle(scope, verdict);
|
|
6155
|
-
const hasLabel =
|
|
7003
|
+
const hasLabel = ensureLabel3(ctx.cwd);
|
|
6156
7004
|
let created;
|
|
6157
7005
|
try {
|
|
6158
7006
|
created = createQaIssue(title, reportBody, hasLabel, ctx.cwd);
|
|
@@ -6417,6 +7265,22 @@ var persistFlowState = async (ctx) => {
|
|
|
6417
7265
|
}
|
|
6418
7266
|
};
|
|
6419
7267
|
|
|
7268
|
+
// src/scripts/prOutcome.ts
|
|
7269
|
+
function readPrOutcome(data) {
|
|
7270
|
+
const raw = data.prResult;
|
|
7271
|
+
if (!raw || typeof raw !== "object") return null;
|
|
7272
|
+
const r = raw;
|
|
7273
|
+
switch (r.kind) {
|
|
7274
|
+
case "created":
|
|
7275
|
+
case "updated":
|
|
7276
|
+
case "skipped":
|
|
7277
|
+
case "crashed":
|
|
7278
|
+
return raw;
|
|
7279
|
+
default:
|
|
7280
|
+
return null;
|
|
7281
|
+
}
|
|
7282
|
+
}
|
|
7283
|
+
|
|
6420
7284
|
// src/scripts/postIssueComment.ts
|
|
6421
7285
|
var FAILED_LABEL_SPEC = {
|
|
6422
7286
|
label: "kody:failed",
|
|
@@ -6430,8 +7294,7 @@ var postIssueComment2 = async (ctx) => {
|
|
|
6430
7294
|
if (!targetType || !targetNumber) return;
|
|
6431
7295
|
const commitResult = ctx.data.commitResult;
|
|
6432
7296
|
const hasCommits = Boolean(ctx.data.hasCommitsAhead);
|
|
6433
|
-
const
|
|
6434
|
-
const prAction = ctx.data.prResult?.action;
|
|
7297
|
+
const prResult = readPrOutcome(ctx.data);
|
|
6435
7298
|
if (!commitResult?.committed && !hasCommits) {
|
|
6436
7299
|
const specific = computeFailureReason2(ctx);
|
|
6437
7300
|
const reason = specific.length > 0 ? specific : "no changes to commit";
|
|
@@ -6449,18 +7312,17 @@ var postIssueComment2 = async (ctx) => {
|
|
|
6449
7312
|
}
|
|
6450
7313
|
const failureReason = computeFailureReason2(ctx);
|
|
6451
7314
|
const isFailure = failureReason.length > 0;
|
|
6452
|
-
const justPushedToExistingPr = prAction === "updated" && commitResult?.committed === true;
|
|
6453
|
-
const successMsg = justPushedToExistingPr ? `\u2705 kody pushed to ${prUrl}` : prAction === "updated" ? `\u2139\uFE0F kody made no changes \u2014 PR: ${prUrl}` : `\u2705 kody PR opened: ${prUrl}`;
|
|
6454
7315
|
const branch = ctx.data.branch;
|
|
6455
|
-
const
|
|
6456
|
-
|
|
6457
|
-
|
|
7316
|
+
const msg = renderMessage({
|
|
7317
|
+
prResult,
|
|
7318
|
+
isFailure,
|
|
7319
|
+
failureReason,
|
|
7320
|
+
justPushedToExistingPr: prResult?.kind === "updated" && commitResult?.committed === true,
|
|
6458
7321
|
branch,
|
|
6459
7322
|
branchPushed: commitResult?.committed === true,
|
|
6460
7323
|
githubOwner: ctx.config.github?.owner,
|
|
6461
7324
|
githubRepo: ctx.config.github?.repo
|
|
6462
7325
|
});
|
|
6463
|
-
const msg = isFailure ? `\u26A0\uFE0F kody FAILED: ${truncate2(failureReason, 1500)}${failurePrSuffix}` : successMsg;
|
|
6464
7326
|
postWith(targetType, targetNumber, msg, ctx.cwd);
|
|
6465
7327
|
let exitCode = 0;
|
|
6466
7328
|
const agentDone = Boolean(ctx.data.agentDone);
|
|
@@ -6484,12 +7346,29 @@ function markRunFailed(ctx) {
|
|
|
6484
7346
|
}
|
|
6485
7347
|
}
|
|
6486
7348
|
function computeFailureSuffix(input) {
|
|
6487
|
-
if (input.
|
|
6488
|
-
|
|
6489
|
-
}
|
|
7349
|
+
if (input.prResult?.kind === "created") return ` \u2014 draft PR: ${input.prResult.url}`;
|
|
7350
|
+
if (input.prResult?.kind === "updated") return ` \u2014 PR: ${input.prResult.url}`;
|
|
6490
7351
|
if (!input.branchPushed || !input.branch || !input.githubOwner || !input.githubRepo) return "";
|
|
6491
7352
|
return ` \u2014 branch: https://github.com/${input.githubOwner}/${input.githubRepo}/tree/${input.branch}`;
|
|
6492
7353
|
}
|
|
7354
|
+
function renderMessage(input) {
|
|
7355
|
+
const suffix = computeFailureSuffix(input);
|
|
7356
|
+
if (input.isFailure) {
|
|
7357
|
+
return `\u26A0\uFE0F kody FAILED: ${truncate2(input.failureReason, 1500)}${suffix}`;
|
|
7358
|
+
}
|
|
7359
|
+
switch (input.prResult?.kind) {
|
|
7360
|
+
case "created":
|
|
7361
|
+
return `\u2705 kody PR opened: ${input.prResult.url}`;
|
|
7362
|
+
case "updated":
|
|
7363
|
+
return input.justPushedToExistingPr ? `\u2705 kody pushed to ${input.prResult.url}` : `\u2139\uFE0F kody made no changes \u2014 PR: ${input.prResult.url}`;
|
|
7364
|
+
case "skipped":
|
|
7365
|
+
return `\u26A0\uFE0F kody finished but did not open a PR \u2014 ${input.prResult.reason}${suffix}`;
|
|
7366
|
+
case "crashed":
|
|
7367
|
+
return `\u26A0\uFE0F kody PR step crashed: ${truncate2(input.prResult.reason, 1500)}${suffix}`;
|
|
7368
|
+
case void 0:
|
|
7369
|
+
return `\u26A0\uFE0F kody finished but PR step did not run${suffix}`;
|
|
7370
|
+
}
|
|
7371
|
+
}
|
|
6493
7372
|
function computeFailureReason2(ctx) {
|
|
6494
7373
|
const misses = ctx.data.coverageMisses ?? [];
|
|
6495
7374
|
if (misses.length > 0) return `missing tests: ${misses.map((m) => m.expectedTest).join(", ")}`;
|
|
@@ -6557,7 +7436,7 @@ ${body}`;
|
|
|
6557
7436
|
}
|
|
6558
7437
|
|
|
6559
7438
|
// src/scripts/recordClassification.ts
|
|
6560
|
-
import { execFileSync as
|
|
7439
|
+
import { execFileSync as execFileSync21 } from "child_process";
|
|
6561
7440
|
var API_TIMEOUT_MS8 = 3e4;
|
|
6562
7441
|
var VALID_CLASSES3 = /* @__PURE__ */ new Set(["feature", "bug", "spec", "chore"]);
|
|
6563
7442
|
var recordClassification = async (ctx) => {
|
|
@@ -6605,7 +7484,7 @@ function parseClassification(prSummary) {
|
|
|
6605
7484
|
}
|
|
6606
7485
|
function tryAuditComment(issueNumber, body, cwd) {
|
|
6607
7486
|
try {
|
|
6608
|
-
|
|
7487
|
+
execFileSync21("gh", ["issue", "comment", String(issueNumber), "--body", body], {
|
|
6609
7488
|
cwd,
|
|
6610
7489
|
timeout: API_TIMEOUT_MS8,
|
|
6611
7490
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -6644,14 +7523,14 @@ var requireFeedbackActions = async (ctx, profile) => {
|
|
|
6644
7523
|
const items = countActionItems(actions);
|
|
6645
7524
|
ctx.data.feedbackAgentItemCount = items;
|
|
6646
7525
|
if (items < MIN_ITEMS) {
|
|
6647
|
-
|
|
7526
|
+
fail2(
|
|
6648
7527
|
ctx,
|
|
6649
7528
|
profile,
|
|
6650
7529
|
actions.length === 0 ? "agent omitted required FEEDBACK_ACTIONS block \u2014 cannot verify that review feedback was addressed" : "agent FEEDBACK_ACTIONS block listed no items \u2014 cannot verify that review feedback was addressed"
|
|
6651
7530
|
);
|
|
6652
7531
|
}
|
|
6653
7532
|
};
|
|
6654
|
-
function
|
|
7533
|
+
function fail2(ctx, profile, reason) {
|
|
6655
7534
|
ctx.data.agentDone = false;
|
|
6656
7535
|
ctx.data.agentFailureReason = reason;
|
|
6657
7536
|
const modeSeg = profile.name.replace(/-/g, "_").toUpperCase();
|
|
@@ -6728,7 +7607,7 @@ var resolveArtifacts = async (ctx, profile) => {
|
|
|
6728
7607
|
};
|
|
6729
7608
|
|
|
6730
7609
|
// src/scripts/resolveFlow.ts
|
|
6731
|
-
import { execFileSync as
|
|
7610
|
+
import { execFileSync as execFileSync22 } from "child_process";
|
|
6732
7611
|
var CONFLICT_DIFF_MAX_BYTES = 4e4;
|
|
6733
7612
|
var resolveFlow = async (ctx) => {
|
|
6734
7613
|
const prNumber = ctx.args.pr;
|
|
@@ -6798,7 +7677,7 @@ function buildPreferBlock(prefer, baseBranch) {
|
|
|
6798
7677
|
}
|
|
6799
7678
|
function getConflictedFiles(cwd) {
|
|
6800
7679
|
try {
|
|
6801
|
-
const out =
|
|
7680
|
+
const out = execFileSync22("git", ["diff", "--name-only", "--diff-filter=U"], {
|
|
6802
7681
|
encoding: "utf-8",
|
|
6803
7682
|
cwd,
|
|
6804
7683
|
env: { ...process.env, HUSKY: "0" }
|
|
@@ -6813,7 +7692,7 @@ function getConflictMarkersPreview(files, cwd, maxBytes = CONFLICT_DIFF_MAX_BYTE
|
|
|
6813
7692
|
let total = 0;
|
|
6814
7693
|
for (const f of files) {
|
|
6815
7694
|
try {
|
|
6816
|
-
const content =
|
|
7695
|
+
const content = execFileSync22("cat", [f], { encoding: "utf-8", cwd }).toString();
|
|
6817
7696
|
const snippet = `### ${f}
|
|
6818
7697
|
|
|
6819
7698
|
\`\`\`
|
|
@@ -6914,10 +7793,10 @@ var resolvePreviewUrl = async (ctx) => {
|
|
|
6914
7793
|
};
|
|
6915
7794
|
|
|
6916
7795
|
// src/scripts/resolveQaUrl.ts
|
|
6917
|
-
import { execFileSync as
|
|
7796
|
+
import { execFileSync as execFileSync23 } from "child_process";
|
|
6918
7797
|
function ghQuery(args, cwd) {
|
|
6919
7798
|
try {
|
|
6920
|
-
const out =
|
|
7799
|
+
const out = execFileSync23("gh", args, {
|
|
6921
7800
|
cwd,
|
|
6922
7801
|
stdio: ["ignore", "pipe", "pipe"],
|
|
6923
7802
|
encoding: "utf-8",
|
|
@@ -6987,7 +7866,7 @@ var resolveQaUrl = async (ctx) => {
|
|
|
6987
7866
|
};
|
|
6988
7867
|
|
|
6989
7868
|
// src/scripts/revertFlow.ts
|
|
6990
|
-
import { execFileSync as
|
|
7869
|
+
import { execFileSync as execFileSync24 } from "child_process";
|
|
6991
7870
|
var SHA_RE = /^[0-9a-f]{4,40}$/i;
|
|
6992
7871
|
var revertFlow = async (ctx) => {
|
|
6993
7872
|
const prNumber = ctx.args.pr;
|
|
@@ -7069,7 +7948,7 @@ function buildPrSummary(resolved) {
|
|
|
7069
7948
|
return resolved.map((r) => `- Reverted \`${r.full.slice(0, 7)}\`${r.subject ? ` \u2014 ${r.subject}` : ""}`).join("\n");
|
|
7070
7949
|
}
|
|
7071
7950
|
function git3(args, cwd) {
|
|
7072
|
-
return
|
|
7951
|
+
return execFileSync24("git", args, {
|
|
7073
7952
|
encoding: "utf-8",
|
|
7074
7953
|
timeout: 3e4,
|
|
7075
7954
|
cwd,
|
|
@@ -7079,7 +7958,7 @@ function git3(args, cwd) {
|
|
|
7079
7958
|
}
|
|
7080
7959
|
function isAncestorOfHead(sha, cwd) {
|
|
7081
7960
|
try {
|
|
7082
|
-
|
|
7961
|
+
execFileSync24("git", ["merge-base", "--is-ancestor", sha, "HEAD"], {
|
|
7083
7962
|
cwd,
|
|
7084
7963
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
|
|
7085
7964
|
stdio: ["ignore", "ignore", "ignore"]
|
|
@@ -7175,17 +8054,17 @@ function resolveBaseOverride(value) {
|
|
|
7175
8054
|
}
|
|
7176
8055
|
function resolveBaseFromLabels(labels) {
|
|
7177
8056
|
if (!labels.includes("goal-runner:dispatched")) return null;
|
|
7178
|
-
const
|
|
7179
|
-
if (!
|
|
7180
|
-
const goalId =
|
|
8057
|
+
const goalLabel2 = labels.find((l) => l.startsWith("goal:"));
|
|
8058
|
+
if (!goalLabel2) return null;
|
|
8059
|
+
const goalId = goalLabel2.slice("goal:".length);
|
|
7181
8060
|
if (!/^[a-z0-9-]+$/.test(goalId)) return null;
|
|
7182
8061
|
return `goal-${goalId}`;
|
|
7183
8062
|
}
|
|
7184
8063
|
|
|
7185
8064
|
// src/scripts/runTickScript.ts
|
|
7186
8065
|
import { spawnSync } from "child_process";
|
|
7187
|
-
import * as
|
|
7188
|
-
import * as
|
|
8066
|
+
import * as fs26 from "fs";
|
|
8067
|
+
import * as path25 from "path";
|
|
7189
8068
|
var runTickScript = async (ctx, _profile, args) => {
|
|
7190
8069
|
ctx.skipAgent = true;
|
|
7191
8070
|
const jobsDir = String(args?.jobsDir ?? ".kody/jobs");
|
|
@@ -7197,13 +8076,13 @@ var runTickScript = async (ctx, _profile, args) => {
|
|
|
7197
8076
|
ctx.output.reason = `runTickScript: ctx.args.${slugArg} must be a non-empty slug`;
|
|
7198
8077
|
return;
|
|
7199
8078
|
}
|
|
7200
|
-
const jobPath =
|
|
7201
|
-
if (!
|
|
8079
|
+
const jobPath = path25.join(ctx.cwd, jobsDir, `${slug}.md`);
|
|
8080
|
+
if (!fs26.existsSync(jobPath)) {
|
|
7202
8081
|
ctx.output.exitCode = 99;
|
|
7203
8082
|
ctx.output.reason = `runTickScript: job file not found: ${jobPath}`;
|
|
7204
8083
|
return;
|
|
7205
8084
|
}
|
|
7206
|
-
const raw =
|
|
8085
|
+
const raw = fs26.readFileSync(jobPath, "utf-8");
|
|
7207
8086
|
const { frontmatter } = splitFrontmatter(raw);
|
|
7208
8087
|
const tickScript = frontmatter.tickScript;
|
|
7209
8088
|
if (!tickScript) {
|
|
@@ -7211,8 +8090,8 @@ var runTickScript = async (ctx, _profile, args) => {
|
|
|
7211
8090
|
ctx.output.reason = `runTickScript: job ${slug} has no \`tickScript:\` frontmatter \u2014 route via job-tick instead`;
|
|
7212
8091
|
return;
|
|
7213
8092
|
}
|
|
7214
|
-
const scriptPath =
|
|
7215
|
-
if (!
|
|
8093
|
+
const scriptPath = path25.isAbsolute(tickScript) ? tickScript : path25.join(ctx.cwd, tickScript);
|
|
8094
|
+
if (!fs26.existsSync(scriptPath)) {
|
|
7216
8095
|
ctx.output.exitCode = 99;
|
|
7217
8096
|
ctx.output.reason = `runTickScript: tickScript not found: ${scriptPath}`;
|
|
7218
8097
|
return;
|
|
@@ -7313,6 +8192,26 @@ function buildChildEnv(parent, force) {
|
|
|
7313
8192
|
return out;
|
|
7314
8193
|
}
|
|
7315
8194
|
|
|
8195
|
+
// src/scripts/saveGoalState.ts
|
|
8196
|
+
var saveGoalState = async (ctx) => {
|
|
8197
|
+
const goal = ctx.data.goal;
|
|
8198
|
+
if (!goal) {
|
|
8199
|
+
ctx.skipAgent = true;
|
|
8200
|
+
return;
|
|
8201
|
+
}
|
|
8202
|
+
const updated = {
|
|
8203
|
+
...goal.raw ?? { state: goal.state, extra: {} },
|
|
8204
|
+
state: goal.state,
|
|
8205
|
+
goalIssueNumber: goal.goalIssueNumber,
|
|
8206
|
+
lastDispatchedIssue: goal.lastDispatchedIssue,
|
|
8207
|
+
goalPrUrl: goal.goalPrUrl,
|
|
8208
|
+
completedAt: goal.completedAt ?? goal.raw?.completedAt,
|
|
8209
|
+
updatedAt: nowIso()
|
|
8210
|
+
};
|
|
8211
|
+
writeGoalState(ctx.cwd, goal.id, updated);
|
|
8212
|
+
ctx.skipAgent = true;
|
|
8213
|
+
};
|
|
8214
|
+
|
|
7316
8215
|
// src/scripts/saveTaskState.ts
|
|
7317
8216
|
var saveTaskState = async (ctx, profile) => {
|
|
7318
8217
|
const target = ctx.data.commentTargetType;
|
|
@@ -7388,11 +8287,11 @@ var skipAgent = async (ctx) => {
|
|
|
7388
8287
|
};
|
|
7389
8288
|
|
|
7390
8289
|
// src/scripts/stageMergeConflicts.ts
|
|
7391
|
-
import { execFileSync as
|
|
8290
|
+
import { execFileSync as execFileSync25 } from "child_process";
|
|
7392
8291
|
var stageMergeConflicts = async (ctx) => {
|
|
7393
8292
|
if (ctx.data.agentDone === false) return;
|
|
7394
8293
|
try {
|
|
7395
|
-
|
|
8294
|
+
execFileSync25("git", ["add", "-A"], {
|
|
7396
8295
|
cwd: ctx.cwd,
|
|
7397
8296
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
|
|
7398
8297
|
stdio: "pipe"
|
|
@@ -7402,7 +8301,7 @@ var stageMergeConflicts = async (ctx) => {
|
|
|
7402
8301
|
};
|
|
7403
8302
|
|
|
7404
8303
|
// src/scripts/startFlow.ts
|
|
7405
|
-
import { execFileSync as
|
|
8304
|
+
import { execFileSync as execFileSync26 } from "child_process";
|
|
7406
8305
|
var API_TIMEOUT_MS9 = 3e4;
|
|
7407
8306
|
var startFlow = async (ctx, profile, _agentResult, args) => {
|
|
7408
8307
|
const entry = args?.entry;
|
|
@@ -7436,7 +8335,7 @@ function postKodyComment(target, issueNumber, state, next, cwd) {
|
|
|
7436
8335
|
const sub = target === "pr" && state?.core.prUrl ? "pr" : "issue";
|
|
7437
8336
|
const body = `@kody ${next}`;
|
|
7438
8337
|
try {
|
|
7439
|
-
|
|
8338
|
+
execFileSync26("gh", [sub, "comment", String(targetNumber), "--body", body], {
|
|
7440
8339
|
timeout: API_TIMEOUT_MS9,
|
|
7441
8340
|
cwd,
|
|
7442
8341
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -7450,7 +8349,7 @@ function postKodyComment(target, issueNumber, state, next, cwd) {
|
|
|
7450
8349
|
}
|
|
7451
8350
|
|
|
7452
8351
|
// src/scripts/syncFlow.ts
|
|
7453
|
-
import { execFileSync as
|
|
8352
|
+
import { execFileSync as execFileSync27 } from "child_process";
|
|
7454
8353
|
var syncFlow = async (ctx, _profile, args) => {
|
|
7455
8354
|
const announceOnSuccess = Boolean(args?.announceOnSuccess);
|
|
7456
8355
|
const prNumber = ctx.args.pr;
|
|
@@ -7522,7 +8421,7 @@ function bail2(ctx, prNumber, reason) {
|
|
|
7522
8421
|
}
|
|
7523
8422
|
function revParseHead(cwd) {
|
|
7524
8423
|
try {
|
|
7525
|
-
return
|
|
8424
|
+
return execFileSync27("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).toString().trim();
|
|
7526
8425
|
} catch {
|
|
7527
8426
|
return "";
|
|
7528
8427
|
}
|
|
@@ -7530,9 +8429,9 @@ function revParseHead(cwd) {
|
|
|
7530
8429
|
function pushBranch(branch, cwd) {
|
|
7531
8430
|
const env = { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" };
|
|
7532
8431
|
try {
|
|
7533
|
-
|
|
8432
|
+
execFileSync27("git", ["push", "-u", "origin", branch], { cwd, env, stdio: ["ignore", "pipe", "pipe"] });
|
|
7534
8433
|
} catch {
|
|
7535
|
-
|
|
8434
|
+
execFileSync27("git", ["push", "--force-with-lease", "-u", "origin", branch], {
|
|
7536
8435
|
cwd,
|
|
7537
8436
|
env,
|
|
7538
8437
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -7759,7 +8658,7 @@ function downgrade2(ctx, reason) {
|
|
|
7759
8658
|
}
|
|
7760
8659
|
|
|
7761
8660
|
// src/scripts/waitForCi.ts
|
|
7762
|
-
import { execFileSync as
|
|
8661
|
+
import { execFileSync as execFileSync28 } from "child_process";
|
|
7763
8662
|
var API_TIMEOUT_MS10 = 3e4;
|
|
7764
8663
|
var waitForCi = async (ctx, _profile, _agentResult, args) => {
|
|
7765
8664
|
const timeoutMinutes = numArg(args, "timeoutMinutes", 30);
|
|
@@ -7837,7 +8736,7 @@ var waitForCi = async (ctx, _profile, _agentResult, args) => {
|
|
|
7837
8736
|
};
|
|
7838
8737
|
function fetchChecks(prNumber, cwd) {
|
|
7839
8738
|
try {
|
|
7840
|
-
const raw =
|
|
8739
|
+
const raw = execFileSync28("gh", ["pr", "checks", String(prNumber), "--json", "bucket,state,name,workflow,link"], {
|
|
7841
8740
|
encoding: "utf-8",
|
|
7842
8741
|
timeout: API_TIMEOUT_MS10,
|
|
7843
8742
|
cwd,
|
|
@@ -8095,7 +8994,7 @@ var writeJobStateFile = async (ctx, _profile, _agentResult, args) => {
|
|
|
8095
8994
|
};
|
|
8096
8995
|
|
|
8097
8996
|
// src/scripts/writeRunSummary.ts
|
|
8098
|
-
import * as
|
|
8997
|
+
import * as fs27 from "fs";
|
|
8099
8998
|
var writeRunSummary = async (ctx, profile) => {
|
|
8100
8999
|
const summaryPath = process.env.GITHUB_STEP_SUMMARY;
|
|
8101
9000
|
if (!summaryPath) return;
|
|
@@ -8117,7 +9016,7 @@ var writeRunSummary = async (ctx, profile) => {
|
|
|
8117
9016
|
if (reason) lines.push(`- **Reason:** ${reason}`);
|
|
8118
9017
|
lines.push("");
|
|
8119
9018
|
try {
|
|
8120
|
-
|
|
9019
|
+
fs27.appendFileSync(summaryPath, `${lines.join("\n")}
|
|
8121
9020
|
`);
|
|
8122
9021
|
} catch {
|
|
8123
9022
|
}
|
|
@@ -8156,7 +9055,19 @@ var preflightScripts = {
|
|
|
8156
9055
|
warmupMcp,
|
|
8157
9056
|
dispatchJobTicks,
|
|
8158
9057
|
dispatchJobFileTicks,
|
|
8159
|
-
runTickScript
|
|
9058
|
+
runTickScript,
|
|
9059
|
+
loadGoalState,
|
|
9060
|
+
handleAbandonedGoal,
|
|
9061
|
+
ensureLifecycleLabels,
|
|
9062
|
+
ensureUmbrellaIssue,
|
|
9063
|
+
ensureGoalPr,
|
|
9064
|
+
mergeReadyTaskPRs,
|
|
9065
|
+
closeMergedTaskIssues,
|
|
9066
|
+
deriveGoalPhase,
|
|
9067
|
+
ensureGoalBranch,
|
|
9068
|
+
dispatchNextTask,
|
|
9069
|
+
finalizeGoal,
|
|
9070
|
+
saveGoalState
|
|
8160
9071
|
};
|
|
8161
9072
|
var postflightScripts = {
|
|
8162
9073
|
parseAgentResult: parseAgentResult2,
|
|
@@ -8195,7 +9106,8 @@ var postflightScripts = {
|
|
|
8195
9106
|
recordOutcome,
|
|
8196
9107
|
mergeReleasePr,
|
|
8197
9108
|
waitForCi,
|
|
8198
|
-
markFlowSuccess
|
|
9109
|
+
markFlowSuccess,
|
|
9110
|
+
commitGoalState
|
|
8199
9111
|
};
|
|
8200
9112
|
var allScriptNames = /* @__PURE__ */ new Set([
|
|
8201
9113
|
...Object.keys(preflightScripts),
|
|
@@ -8203,7 +9115,7 @@ var allScriptNames = /* @__PURE__ */ new Set([
|
|
|
8203
9115
|
]);
|
|
8204
9116
|
|
|
8205
9117
|
// src/tools.ts
|
|
8206
|
-
import { execFileSync as
|
|
9118
|
+
import { execFileSync as execFileSync29 } from "child_process";
|
|
8207
9119
|
function verifyCliTools(tools, cwd) {
|
|
8208
9120
|
const out = [];
|
|
8209
9121
|
for (const t of tools) out.push(verifyOne(t, cwd));
|
|
@@ -8236,7 +9148,7 @@ function verifyOne(tool, cwd) {
|
|
|
8236
9148
|
}
|
|
8237
9149
|
function runShell(cmd, cwd, timeoutMs = 3e4) {
|
|
8238
9150
|
try {
|
|
8239
|
-
|
|
9151
|
+
execFileSync29("sh", ["-c", cmd], { cwd, stdio: "pipe", timeout: timeoutMs });
|
|
8240
9152
|
return true;
|
|
8241
9153
|
} catch {
|
|
8242
9154
|
return false;
|
|
@@ -8305,9 +9217,9 @@ async function runExecutable(profileName, input) {
|
|
|
8305
9217
|
data: {},
|
|
8306
9218
|
output: { exitCode: 0 }
|
|
8307
9219
|
};
|
|
8308
|
-
const ndjsonDir =
|
|
9220
|
+
const ndjsonDir = path26.join(input.cwd, ".kody");
|
|
8309
9221
|
const invokeAgent = async (prompt) => {
|
|
8310
|
-
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) =>
|
|
9222
|
+
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path26.isAbsolute(p) ? p : path26.resolve(profile.dir, p)).filter((p) => p.length > 0);
|
|
8311
9223
|
const syntheticPath = ctx.data.syntheticPluginPath;
|
|
8312
9224
|
const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
|
|
8313
9225
|
return runAgent({
|
|
@@ -8416,17 +9328,17 @@ function clearStampedLifecycleLabels(profile, ctx) {
|
|
|
8416
9328
|
function resolveProfilePath(profileName) {
|
|
8417
9329
|
const found = resolveExecutable(profileName);
|
|
8418
9330
|
if (found) return found;
|
|
8419
|
-
const here =
|
|
9331
|
+
const here = path26.dirname(new URL(import.meta.url).pathname);
|
|
8420
9332
|
const candidates = [
|
|
8421
|
-
|
|
9333
|
+
path26.join(here, "executables", profileName, "profile.json"),
|
|
8422
9334
|
// same-dir sibling (dev)
|
|
8423
|
-
|
|
9335
|
+
path26.join(here, "..", "executables", profileName, "profile.json"),
|
|
8424
9336
|
// up one (prod: dist/bin → dist/executables)
|
|
8425
|
-
|
|
9337
|
+
path26.join(here, "..", "src", "executables", profileName, "profile.json")
|
|
8426
9338
|
// fallback
|
|
8427
9339
|
];
|
|
8428
9340
|
for (const c of candidates) {
|
|
8429
|
-
if (
|
|
9341
|
+
if (fs28.existsSync(c)) return c;
|
|
8430
9342
|
}
|
|
8431
9343
|
return candidates[0];
|
|
8432
9344
|
}
|
|
@@ -8530,8 +9442,8 @@ function resolveShellTimeoutMs(entry) {
|
|
|
8530
9442
|
var SIGKILL_GRACE_MS = 5e3;
|
|
8531
9443
|
async function runShellEntry(entry, ctx, profile) {
|
|
8532
9444
|
const shellName = entry.shell;
|
|
8533
|
-
const shellPath =
|
|
8534
|
-
if (!
|
|
9445
|
+
const shellPath = path26.join(profile.dir, shellName);
|
|
9446
|
+
if (!fs28.existsSync(shellPath)) {
|
|
8535
9447
|
ctx.skipAgent = true;
|
|
8536
9448
|
ctx.output.exitCode = 99;
|
|
8537
9449
|
ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
|
|
@@ -8792,7 +9704,7 @@ async function runContainerLoop(profile, ctx, input) {
|
|
|
8792
9704
|
}
|
|
8793
9705
|
function resetWorkingTree(cwd) {
|
|
8794
9706
|
try {
|
|
8795
|
-
|
|
9707
|
+
execFileSync30("git", ["reset", "--hard", "HEAD"], {
|
|
8796
9708
|
cwd,
|
|
8797
9709
|
stdio: ["ignore", "pipe", "pipe"],
|
|
8798
9710
|
timeout: 3e4
|
|
@@ -8944,14 +9856,14 @@ function resolveAuthToken(env = process.env) {
|
|
|
8944
9856
|
return token;
|
|
8945
9857
|
}
|
|
8946
9858
|
function detectPackageManager2(cwd) {
|
|
8947
|
-
if (
|
|
8948
|
-
if (
|
|
8949
|
-
if (
|
|
9859
|
+
if (fs29.existsSync(path27.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
9860
|
+
if (fs29.existsSync(path27.join(cwd, "yarn.lock"))) return "yarn";
|
|
9861
|
+
if (fs29.existsSync(path27.join(cwd, "bun.lockb"))) return "bun";
|
|
8950
9862
|
return "npm";
|
|
8951
9863
|
}
|
|
8952
9864
|
function shellOut(cmd, args, cwd, stream = true) {
|
|
8953
9865
|
try {
|
|
8954
|
-
|
|
9866
|
+
execFileSync31(cmd, args, {
|
|
8955
9867
|
cwd,
|
|
8956
9868
|
stdio: stream ? "inherit" : "pipe",
|
|
8957
9869
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", CI: process.env.CI ?? "1" }
|
|
@@ -8964,7 +9876,7 @@ function shellOut(cmd, args, cwd, stream = true) {
|
|
|
8964
9876
|
}
|
|
8965
9877
|
function isOnPath(bin) {
|
|
8966
9878
|
try {
|
|
8967
|
-
|
|
9879
|
+
execFileSync31("which", [bin], { stdio: "pipe" });
|
|
8968
9880
|
return true;
|
|
8969
9881
|
} catch {
|
|
8970
9882
|
return false;
|
|
@@ -9005,7 +9917,7 @@ function installLitellmIfNeeded(cwd) {
|
|
|
9005
9917
|
} catch {
|
|
9006
9918
|
}
|
|
9007
9919
|
try {
|
|
9008
|
-
|
|
9920
|
+
execFileSync31("python3", ["-c", "import litellm"], { stdio: "pipe" });
|
|
9009
9921
|
process.stdout.write("\u2192 kody: litellm already installed\n");
|
|
9010
9922
|
return 0;
|
|
9011
9923
|
} catch {
|
|
@@ -9015,16 +9927,16 @@ function installLitellmIfNeeded(cwd) {
|
|
|
9015
9927
|
}
|
|
9016
9928
|
function configureGitIdentity(cwd) {
|
|
9017
9929
|
try {
|
|
9018
|
-
const name =
|
|
9930
|
+
const name = execFileSync31("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
|
|
9019
9931
|
if (name) return;
|
|
9020
9932
|
} catch {
|
|
9021
9933
|
}
|
|
9022
9934
|
try {
|
|
9023
|
-
|
|
9935
|
+
execFileSync31("git", ["config", "user.name", "github-actions[bot]"], { cwd, stdio: "pipe" });
|
|
9024
9936
|
} catch {
|
|
9025
9937
|
}
|
|
9026
9938
|
try {
|
|
9027
|
-
|
|
9939
|
+
execFileSync31("git", ["config", "user.email", "41898282+github-actions[bot]@users.noreply.github.com"], {
|
|
9028
9940
|
cwd,
|
|
9029
9941
|
stdio: "pipe"
|
|
9030
9942
|
});
|
|
@@ -9033,11 +9945,11 @@ function configureGitIdentity(cwd) {
|
|
|
9033
9945
|
}
|
|
9034
9946
|
function postFailureTail(issueNumber, cwd, reason) {
|
|
9035
9947
|
if (!issueNumber) return;
|
|
9036
|
-
const logPath =
|
|
9948
|
+
const logPath = path27.join(cwd, ".kody", "last-run.jsonl");
|
|
9037
9949
|
let tail = "";
|
|
9038
9950
|
try {
|
|
9039
|
-
if (
|
|
9040
|
-
const content =
|
|
9951
|
+
if (fs29.existsSync(logPath)) {
|
|
9952
|
+
const content = fs29.readFileSync(logPath, "utf-8");
|
|
9041
9953
|
tail = content.slice(-3e3);
|
|
9042
9954
|
}
|
|
9043
9955
|
} catch {
|
|
@@ -9062,7 +9974,7 @@ async function runCi(argv) {
|
|
|
9062
9974
|
return 0;
|
|
9063
9975
|
}
|
|
9064
9976
|
const args = parseCiArgs(argv);
|
|
9065
|
-
const cwd = args.cwd ?
|
|
9977
|
+
const cwd = args.cwd ? path27.resolve(args.cwd) : process.cwd();
|
|
9066
9978
|
let earlyConfig;
|
|
9067
9979
|
try {
|
|
9068
9980
|
earlyConfig = loadConfig(cwd);
|
|
@@ -9072,9 +9984,9 @@ async function runCi(argv) {
|
|
|
9072
9984
|
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
9073
9985
|
const dispatchEventPath = process.env.GITHUB_EVENT_PATH;
|
|
9074
9986
|
let manualWorkflowDispatch = false;
|
|
9075
|
-
if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath &&
|
|
9987
|
+
if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs29.existsSync(dispatchEventPath)) {
|
|
9076
9988
|
try {
|
|
9077
|
-
const evt = JSON.parse(
|
|
9989
|
+
const evt = JSON.parse(fs29.readFileSync(dispatchEventPath, "utf-8"));
|
|
9078
9990
|
const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
|
|
9079
9991
|
const sessionInput = String(evt?.inputs?.sessionId ?? "");
|
|
9080
9992
|
manualWorkflowDispatch = !sessionInput && !(Number.isFinite(issueInput) && issueInput > 0);
|
|
@@ -9289,15 +10201,15 @@ function parseChatArgs(argv, env = process.env) {
|
|
|
9289
10201
|
return result;
|
|
9290
10202
|
}
|
|
9291
10203
|
function commitChatFiles(cwd, sessionId, verbose) {
|
|
9292
|
-
const sessionFile =
|
|
9293
|
-
const eventsFile =
|
|
9294
|
-
const paths = [sessionFile, eventsFile].filter((p) =>
|
|
10204
|
+
const sessionFile = path28.relative(cwd, sessionFilePath(cwd, sessionId));
|
|
10205
|
+
const eventsFile = path28.relative(cwd, eventsFilePath(cwd, sessionId));
|
|
10206
|
+
const paths = [sessionFile, eventsFile].filter((p) => fs30.existsSync(path28.join(cwd, p)));
|
|
9295
10207
|
if (paths.length === 0) return;
|
|
9296
10208
|
const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
|
|
9297
10209
|
try {
|
|
9298
|
-
|
|
9299
|
-
|
|
9300
|
-
|
|
10210
|
+
execFileSync32("git", ["add", "-f", ...paths], opts);
|
|
10211
|
+
execFileSync32("git", ["commit", "--quiet", "-m", `chat: reply for ${sessionId}`], opts);
|
|
10212
|
+
execFileSync32("git", ["push", "--quiet", "origin", "HEAD"], opts);
|
|
9301
10213
|
} catch (err) {
|
|
9302
10214
|
const msg = err instanceof Error ? err.message : String(err);
|
|
9303
10215
|
process.stderr.write(`[kody:chat] commit/push skipped: ${msg}
|
|
@@ -9329,7 +10241,7 @@ async function runChat(argv) {
|
|
|
9329
10241
|
${CHAT_HELP}`);
|
|
9330
10242
|
return 64;
|
|
9331
10243
|
}
|
|
9332
|
-
const cwd = args.cwd ?
|
|
10244
|
+
const cwd = args.cwd ? path28.resolve(args.cwd) : process.cwd();
|
|
9333
10245
|
const sessionId = args.sessionId;
|
|
9334
10246
|
const unpackedSecrets = unpackAllSecrets();
|
|
9335
10247
|
if (unpackedSecrets > 0) {
|
|
@@ -9381,7 +10293,7 @@ ${CHAT_HELP}`);
|
|
|
9381
10293
|
const sink = buildSink(cwd, sessionId, args.dashboardUrl);
|
|
9382
10294
|
const meta = readMeta(sessionFile);
|
|
9383
10295
|
process.stdout.write(
|
|
9384
|
-
`\u2192 kody:chat: session file=${sessionFile} exists=${
|
|
10296
|
+
`\u2192 kody:chat: session file=${sessionFile} exists=${fs30.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
|
|
9385
10297
|
`
|
|
9386
10298
|
);
|
|
9387
10299
|
try {
|