@kody-ade/kody-engine 0.4.37 → 0.4.39
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 +421 -774
- package/dist/executables/goal-tick/profile.json +2 -30
- package/package.json +1 -1
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.39",
|
|
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,7 +51,7 @@ var package_default = {
|
|
|
51
51
|
};
|
|
52
52
|
|
|
53
53
|
// src/chat-cli.ts
|
|
54
|
-
import { execFileSync as
|
|
54
|
+
import { execFileSync as execFileSync31 } from "child_process";
|
|
55
55
|
import * as fs30 from "fs";
|
|
56
56
|
import * as path28 from "path";
|
|
57
57
|
|
|
@@ -912,7 +912,7 @@ async function emit2(sink, type, sessionId, suffix, payload) {
|
|
|
912
912
|
}
|
|
913
913
|
|
|
914
914
|
// src/kody-cli.ts
|
|
915
|
-
import { execFileSync as
|
|
915
|
+
import { execFileSync as execFileSync30 } from "child_process";
|
|
916
916
|
import * as fs29 from "fs";
|
|
917
917
|
import * as path27 from "path";
|
|
918
918
|
|
|
@@ -1492,7 +1492,7 @@ function postPrReviewComment(prNumber, body, cwd) {
|
|
|
1492
1492
|
}
|
|
1493
1493
|
|
|
1494
1494
|
// src/executor.ts
|
|
1495
|
-
import { execFileSync as
|
|
1495
|
+
import { execFileSync as execFileSync29, spawn as spawn5 } from "child_process";
|
|
1496
1496
|
import * as fs28 from "fs";
|
|
1497
1497
|
import * as path26 from "path";
|
|
1498
1498
|
|
|
@@ -2801,301 +2801,6 @@ function defaultLabelMap() {
|
|
|
2801
2801
|
};
|
|
2802
2802
|
}
|
|
2803
2803
|
|
|
2804
|
-
// src/goal/operations.ts
|
|
2805
|
-
import { execFileSync as execFileSync9 } from "child_process";
|
|
2806
|
-
|
|
2807
|
-
// src/goal/labels.ts
|
|
2808
|
-
function goalLabel(goalId) {
|
|
2809
|
-
return `goal:${goalId}`;
|
|
2810
|
-
}
|
|
2811
|
-
var DISPATCHED_LABEL = "goal-runner:dispatched";
|
|
2812
|
-
var FAILED_LABEL = "goal-runner:failed";
|
|
2813
|
-
var UMBRELLA_BUILDING_LABEL = "kody:building";
|
|
2814
|
-
var TICK_LABELS = [
|
|
2815
|
-
{
|
|
2816
|
-
name: DISPATCHED_LABEL,
|
|
2817
|
-
color: "ededed",
|
|
2818
|
-
description: "kody goal-runner: already dispatched this tick"
|
|
2819
|
-
},
|
|
2820
|
-
{
|
|
2821
|
-
name: FAILED_LABEL,
|
|
2822
|
-
color: "b60205",
|
|
2823
|
-
description: "kody goal-runner: task failed; needs human attention"
|
|
2824
|
-
}
|
|
2825
|
-
];
|
|
2826
|
-
|
|
2827
|
-
// src/goal/operations.ts
|
|
2828
|
-
function fail(err) {
|
|
2829
|
-
if (err instanceof Error) {
|
|
2830
|
-
const lines = err.message.split("\n").filter(Boolean);
|
|
2831
|
-
return { ok: false, error: lines[0] ?? err.message };
|
|
2832
|
-
}
|
|
2833
|
-
return { ok: false, error: String(err) };
|
|
2834
|
-
}
|
|
2835
|
-
function listGoalIssues(goalId, excludeIssueNumber, cwd) {
|
|
2836
|
-
try {
|
|
2837
|
-
const out = gh(
|
|
2838
|
-
[
|
|
2839
|
-
"api",
|
|
2840
|
-
`repos/{owner}/{repo}/issues?labels=${goalLabel(goalId)}&state=all&per_page=100`,
|
|
2841
|
-
"--jq",
|
|
2842
|
-
"[.[] | select(.pull_request == null) | {number, state: (.state | ascii_upcase), labels: [.labels[].name]}]"
|
|
2843
|
-
],
|
|
2844
|
-
{ cwd }
|
|
2845
|
-
);
|
|
2846
|
-
const arr = JSON.parse(out);
|
|
2847
|
-
const filtered = excludeIssueNumber !== void 0 ? arr.filter((i) => i.number !== excludeIssueNumber) : arr;
|
|
2848
|
-
return { ok: true, value: filtered };
|
|
2849
|
-
} catch (err) {
|
|
2850
|
-
return fail(err);
|
|
2851
|
-
}
|
|
2852
|
-
}
|
|
2853
|
-
function ensureLabel(name, color, description, cwd) {
|
|
2854
|
-
try {
|
|
2855
|
-
gh(["label", "create", name, "--color", color, "--description", description, "--force"], { cwd });
|
|
2856
|
-
return { ok: true };
|
|
2857
|
-
} catch (err) {
|
|
2858
|
-
return fail(err);
|
|
2859
|
-
}
|
|
2860
|
-
}
|
|
2861
|
-
function addLabel2(issueNumber, label, cwd) {
|
|
2862
|
-
try {
|
|
2863
|
-
gh(["issue", "edit", String(issueNumber), "--add-label", label], { cwd });
|
|
2864
|
-
return { ok: true };
|
|
2865
|
-
} catch (err) {
|
|
2866
|
-
return fail(err);
|
|
2867
|
-
}
|
|
2868
|
-
}
|
|
2869
|
-
function commentOnIssue(issueNumber, body, cwd) {
|
|
2870
|
-
try {
|
|
2871
|
-
gh(["issue", "comment", String(issueNumber), "--body", body], { cwd });
|
|
2872
|
-
return { ok: true };
|
|
2873
|
-
} catch (err) {
|
|
2874
|
-
return fail(err);
|
|
2875
|
-
}
|
|
2876
|
-
}
|
|
2877
|
-
function closeIssue(issueNumber, options, cwd) {
|
|
2878
|
-
try {
|
|
2879
|
-
if (options.comment) {
|
|
2880
|
-
gh(["issue", "comment", String(issueNumber), "--body", options.comment], { cwd });
|
|
2881
|
-
}
|
|
2882
|
-
const args = ["issue", "close", String(issueNumber)];
|
|
2883
|
-
if (options.reason) args.push("--reason", options.reason);
|
|
2884
|
-
gh(args, { cwd });
|
|
2885
|
-
return { ok: true };
|
|
2886
|
-
} catch (err) {
|
|
2887
|
-
return fail(err);
|
|
2888
|
-
}
|
|
2889
|
-
}
|
|
2890
|
-
function getIssueState(issueNumber, cwd) {
|
|
2891
|
-
try {
|
|
2892
|
-
const out = gh(["issue", "view", String(issueNumber), "--json", "state", "--jq", ".state"], {
|
|
2893
|
-
cwd
|
|
2894
|
-
});
|
|
2895
|
-
const norm = out.trim().toUpperCase();
|
|
2896
|
-
if (norm !== "OPEN" && norm !== "CLOSED") {
|
|
2897
|
-
return { ok: false, error: `unexpected state: ${out}` };
|
|
2898
|
-
}
|
|
2899
|
-
return { ok: true, value: norm };
|
|
2900
|
-
} catch (err) {
|
|
2901
|
-
return fail(err);
|
|
2902
|
-
}
|
|
2903
|
-
}
|
|
2904
|
-
function findUmbrellaByTitle(goalId, title, cwd) {
|
|
2905
|
-
try {
|
|
2906
|
-
const out = gh(
|
|
2907
|
-
[
|
|
2908
|
-
"api",
|
|
2909
|
-
`repos/{owner}/{repo}/issues?labels=${goalLabel(goalId)}&state=all&per_page=100`,
|
|
2910
|
-
"--jq",
|
|
2911
|
-
`[.[] | select(.pull_request == null) | select(.title == "${title.replace(/"/g, '\\"')}")] | (map(select(.state == "open")) + map(select(.state != "open")))[0].number // empty`
|
|
2912
|
-
],
|
|
2913
|
-
{ cwd }
|
|
2914
|
-
);
|
|
2915
|
-
const trimmed = out.trim();
|
|
2916
|
-
if (!trimmed) return { ok: true, value: null };
|
|
2917
|
-
const n = Number.parseInt(trimmed, 10);
|
|
2918
|
-
if (!Number.isFinite(n)) return { ok: true, value: null };
|
|
2919
|
-
return { ok: true, value: n };
|
|
2920
|
-
} catch (err) {
|
|
2921
|
-
return fail(err);
|
|
2922
|
-
}
|
|
2923
|
-
}
|
|
2924
|
-
function createIssue(args, cwd) {
|
|
2925
|
-
try {
|
|
2926
|
-
const cliArgs = ["issue", "create", "--title", args.title, "--body", args.body];
|
|
2927
|
-
for (const l of args.labels) cliArgs.push("--label", l);
|
|
2928
|
-
const url = gh(cliArgs, { cwd });
|
|
2929
|
-
const match = url.match(/\/issues\/(\d+)/);
|
|
2930
|
-
if (!match?.[1]) return { ok: false, error: `couldn't parse issue number from URL: ${url}` };
|
|
2931
|
-
return { ok: true, value: Number.parseInt(match[1], 10) };
|
|
2932
|
-
} catch (err) {
|
|
2933
|
-
return fail(err);
|
|
2934
|
-
}
|
|
2935
|
-
}
|
|
2936
|
-
function listPrsByBase(base, state, cwd) {
|
|
2937
|
-
try {
|
|
2938
|
-
const out = gh(
|
|
2939
|
-
[
|
|
2940
|
-
"pr",
|
|
2941
|
-
"list",
|
|
2942
|
-
"--base",
|
|
2943
|
-
base,
|
|
2944
|
-
"--state",
|
|
2945
|
-
state,
|
|
2946
|
-
"--limit",
|
|
2947
|
-
"50",
|
|
2948
|
-
"--json",
|
|
2949
|
-
"number,isDraft,mergeable,mergeStateStatus,url,headRefName,body"
|
|
2950
|
-
],
|
|
2951
|
-
{ cwd }
|
|
2952
|
-
);
|
|
2953
|
-
return { ok: true, value: JSON.parse(out) };
|
|
2954
|
-
} catch (err) {
|
|
2955
|
-
return fail(err);
|
|
2956
|
-
}
|
|
2957
|
-
}
|
|
2958
|
-
function listPrsByHead(head, state, cwd) {
|
|
2959
|
-
try {
|
|
2960
|
-
const out = gh(
|
|
2961
|
-
[
|
|
2962
|
-
"pr",
|
|
2963
|
-
"list",
|
|
2964
|
-
"--head",
|
|
2965
|
-
head,
|
|
2966
|
-
"--state",
|
|
2967
|
-
state,
|
|
2968
|
-
"--json",
|
|
2969
|
-
"number,isDraft,mergeable,mergeStateStatus,url,headRefName,body"
|
|
2970
|
-
],
|
|
2971
|
-
{ cwd }
|
|
2972
|
-
);
|
|
2973
|
-
return { ok: true, value: JSON.parse(out) };
|
|
2974
|
-
} catch (err) {
|
|
2975
|
-
return fail(err);
|
|
2976
|
-
}
|
|
2977
|
-
}
|
|
2978
|
-
function mergePrSquash(prNumber, cwd) {
|
|
2979
|
-
try {
|
|
2980
|
-
gh(["pr", "merge", String(prNumber), "--squash", "--delete-branch"], { cwd });
|
|
2981
|
-
return { ok: true };
|
|
2982
|
-
} catch (err) {
|
|
2983
|
-
return fail(err);
|
|
2984
|
-
}
|
|
2985
|
-
}
|
|
2986
|
-
function closePr(prNumber, comment, cwd) {
|
|
2987
|
-
try {
|
|
2988
|
-
gh(["pr", "close", String(prNumber), "--comment", comment], { cwd });
|
|
2989
|
-
return { ok: true };
|
|
2990
|
-
} catch (err) {
|
|
2991
|
-
return fail(err);
|
|
2992
|
-
}
|
|
2993
|
-
}
|
|
2994
|
-
function createPr(args, cwd) {
|
|
2995
|
-
try {
|
|
2996
|
-
const cli = ["pr", "create", "--head", args.head, "--base", args.base, "--title", args.title, "--body", args.body];
|
|
2997
|
-
if (args.draft) cli.push("--draft");
|
|
2998
|
-
const url = gh(cli, { cwd });
|
|
2999
|
-
if (!url.includes("/pull/")) return { ok: false, error: `gh pr create returned unexpected output: ${url}` };
|
|
3000
|
-
return { ok: true, value: url.trim() };
|
|
3001
|
-
} catch (err) {
|
|
3002
|
-
return fail(err);
|
|
3003
|
-
}
|
|
3004
|
-
}
|
|
3005
|
-
function editPrBody(prNumber, body, cwd) {
|
|
3006
|
-
try {
|
|
3007
|
-
gh(["pr", "edit", String(prNumber), "--body", body], { cwd });
|
|
3008
|
-
return { ok: true };
|
|
3009
|
-
} catch (err) {
|
|
3010
|
-
return fail(err);
|
|
3011
|
-
}
|
|
3012
|
-
}
|
|
3013
|
-
function markPrReady(prNumber, cwd) {
|
|
3014
|
-
try {
|
|
3015
|
-
gh(["pr", "ready", String(prNumber)], { cwd });
|
|
3016
|
-
return { ok: true };
|
|
3017
|
-
} catch (err) {
|
|
3018
|
-
return fail(err);
|
|
3019
|
-
}
|
|
3020
|
-
}
|
|
3021
|
-
function ghTokenEnv() {
|
|
3022
|
-
const token = process.env.GH_PAT?.trim() || process.env.GH_TOKEN;
|
|
3023
|
-
return token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
|
|
3024
|
-
}
|
|
3025
|
-
function remoteBranchExists(ref, cwd) {
|
|
3026
|
-
try {
|
|
3027
|
-
execFileSync9("git", ["rev-parse", "--verify", "--quiet", `refs/remotes/origin/${ref}`], {
|
|
3028
|
-
cwd,
|
|
3029
|
-
stdio: "pipe",
|
|
3030
|
-
env: ghTokenEnv()
|
|
3031
|
-
});
|
|
3032
|
-
return true;
|
|
3033
|
-
} catch {
|
|
3034
|
-
return false;
|
|
3035
|
-
}
|
|
3036
|
-
}
|
|
3037
|
-
function fetchOrigin(cwd) {
|
|
3038
|
-
try {
|
|
3039
|
-
execFileSync9("git", ["fetch", "origin", "--quiet"], { cwd, stdio: "pipe", env: ghTokenEnv() });
|
|
3040
|
-
} catch {
|
|
3041
|
-
}
|
|
3042
|
-
}
|
|
3043
|
-
function createBranchFrom(branch, base, cwd) {
|
|
3044
|
-
try {
|
|
3045
|
-
execFileSync9("git", ["push", "origin", `refs/remotes/origin/${base}:refs/heads/${branch}`, "--quiet"], {
|
|
3046
|
-
cwd,
|
|
3047
|
-
stdio: "pipe",
|
|
3048
|
-
env: ghTokenEnv()
|
|
3049
|
-
});
|
|
3050
|
-
return { ok: true };
|
|
3051
|
-
} catch (err) {
|
|
3052
|
-
return fail(err);
|
|
3053
|
-
}
|
|
3054
|
-
}
|
|
3055
|
-
function inferLinkedIssue(pr) {
|
|
3056
|
-
const body = pr.body ?? "";
|
|
3057
|
-
const m = body.match(/\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s+#(\d+)\b/i);
|
|
3058
|
-
if (m?.[1]) return Number.parseInt(m[1], 10);
|
|
3059
|
-
const ref = pr.headRefName ?? "";
|
|
3060
|
-
const bm = ref.match(/^(\d+)-/);
|
|
3061
|
-
if (bm?.[1]) return Number.parseInt(bm[1], 10);
|
|
3062
|
-
return void 0;
|
|
3063
|
-
}
|
|
3064
|
-
|
|
3065
|
-
// src/scripts/closeMergedTaskIssues.ts
|
|
3066
|
-
var closeMergedTaskIssues = async (ctx) => {
|
|
3067
|
-
const goal = ctx.data.goal;
|
|
3068
|
-
if (!goal) return;
|
|
3069
|
-
const merged = listPrsByBase(goal.goalBranch, "merged", ctx.cwd);
|
|
3070
|
-
if (!merged.ok) {
|
|
3071
|
-
process.stderr.write(`[goal-tick] closeMergedTaskIssues: list failed: ${merged.error}
|
|
3072
|
-
`);
|
|
3073
|
-
return;
|
|
3074
|
-
}
|
|
3075
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3076
|
-
for (const pr of merged.value ?? []) {
|
|
3077
|
-
const linked = inferLinkedIssue(pr);
|
|
3078
|
-
if (linked === void 0 || seen.has(linked)) continue;
|
|
3079
|
-
seen.add(linked);
|
|
3080
|
-
const stateRes = getIssueState(linked, ctx.cwd);
|
|
3081
|
-
if (!stateRes.ok || stateRes.value !== "OPEN") continue;
|
|
3082
|
-
process.stdout.write(`[goal-tick] closing #${linked} (PR merged into ${goal.goalBranch})
|
|
3083
|
-
`);
|
|
3084
|
-
const r = closeIssue(
|
|
3085
|
-
linked,
|
|
3086
|
-
{
|
|
3087
|
-
comment: `_Closed by goal-tick: PR for this task merged into \`${goal.goalBranch}\`._`,
|
|
3088
|
-
reason: "completed"
|
|
3089
|
-
},
|
|
3090
|
-
ctx.cwd
|
|
3091
|
-
);
|
|
3092
|
-
if (!r.ok) {
|
|
3093
|
-
process.stderr.write(`[goal-tick] failed to close #${linked}: ${r.error} (continuing)
|
|
3094
|
-
`);
|
|
3095
|
-
}
|
|
3096
|
-
}
|
|
3097
|
-
};
|
|
3098
|
-
|
|
3099
2804
|
// src/scripts/commitAndPush.ts
|
|
3100
2805
|
var DEFAULT_COMMIT_MESSAGE = "chore: kody changes";
|
|
3101
2806
|
var commitAndPush2 = async (ctx) => {
|
|
@@ -3140,14 +2845,14 @@ var commitAndPush2 = async (ctx) => {
|
|
|
3140
2845
|
};
|
|
3141
2846
|
|
|
3142
2847
|
// src/scripts/commitGoalState.ts
|
|
3143
|
-
import { execFileSync as
|
|
2848
|
+
import { execFileSync as execFileSync9 } from "child_process";
|
|
3144
2849
|
import * as path12 from "path";
|
|
3145
2850
|
var commitGoalState = async (ctx) => {
|
|
3146
2851
|
const goal = ctx.data.goal;
|
|
3147
2852
|
if (!goal) return;
|
|
3148
2853
|
const stateRel = path12.posix.join(".kody", "goals", goal.id, "state.json");
|
|
3149
2854
|
try {
|
|
3150
|
-
|
|
2855
|
+
execFileSync9("git", ["add", stateRel], { cwd: ctx.cwd, stdio: "pipe" });
|
|
3151
2856
|
} catch (err) {
|
|
3152
2857
|
process.stderr.write(
|
|
3153
2858
|
`[goal-tick] commitGoalState: git add failed: ${err instanceof Error ? err.message : String(err)}
|
|
@@ -3156,13 +2861,13 @@ var commitGoalState = async (ctx) => {
|
|
|
3156
2861
|
return;
|
|
3157
2862
|
}
|
|
3158
2863
|
try {
|
|
3159
|
-
|
|
2864
|
+
execFileSync9("git", ["diff", "--cached", "--quiet"], { cwd: ctx.cwd, stdio: "pipe" });
|
|
3160
2865
|
return;
|
|
3161
2866
|
} catch {
|
|
3162
2867
|
}
|
|
3163
2868
|
const msg = describeCommitMessage(goal);
|
|
3164
2869
|
try {
|
|
3165
|
-
|
|
2870
|
+
execFileSync9("git", ["commit", "-m", msg, "--quiet"], { cwd: ctx.cwd, stdio: "pipe" });
|
|
3166
2871
|
} catch (err) {
|
|
3167
2872
|
process.stderr.write(
|
|
3168
2873
|
`[goal-tick] commitGoalState: git commit failed: ${err instanceof Error ? err.message : String(err)}
|
|
@@ -3171,7 +2876,7 @@ var commitGoalState = async (ctx) => {
|
|
|
3171
2876
|
return;
|
|
3172
2877
|
}
|
|
3173
2878
|
try {
|
|
3174
|
-
|
|
2879
|
+
execFileSync9("git", ["push", "--quiet"], { cwd: ctx.cwd, stdio: "pipe" });
|
|
3175
2880
|
} catch {
|
|
3176
2881
|
process.stderr.write("[goal-tick] commitGoalState: push failed (will retry next tick)\n");
|
|
3177
2882
|
}
|
|
@@ -3185,9 +2890,6 @@ function describeCommitMessage(goal) {
|
|
|
3185
2890
|
if (goal.phase === "in-flight") {
|
|
3186
2891
|
return `chore(goals): tick ${goal.id} (waiting for in-flight task)`;
|
|
3187
2892
|
}
|
|
3188
|
-
if (goal.phase === "blocked-by-failure") {
|
|
3189
|
-
return `chore(goals): tick ${goal.id} (blocked by failed task)`;
|
|
3190
|
-
}
|
|
3191
2893
|
return `chore(goals): tick ${goal.id} (idle)`;
|
|
3192
2894
|
}
|
|
3193
2895
|
|
|
@@ -3290,7 +2992,7 @@ function formatToolsUsage(profile) {
|
|
|
3290
2992
|
}
|
|
3291
2993
|
|
|
3292
2994
|
// src/scripts/createQaGoal.ts
|
|
3293
|
-
import { execFileSync as
|
|
2995
|
+
import { execFileSync as execFileSync10 } from "child_process";
|
|
3294
2996
|
import * as fs14 from "fs";
|
|
3295
2997
|
import * as path14 from "path";
|
|
3296
2998
|
|
|
@@ -3474,7 +3176,7 @@ ${json}
|
|
|
3474
3176
|
${MANIFEST_END}
|
|
3475
3177
|
`;
|
|
3476
3178
|
}
|
|
3477
|
-
function
|
|
3179
|
+
function ensureLabel(name, color, description, cwd) {
|
|
3478
3180
|
try {
|
|
3479
3181
|
gh(["label", "create", name, "--color", color, "--description", description, "--force"], { cwd });
|
|
3480
3182
|
} catch {
|
|
@@ -3494,7 +3196,7 @@ function ensureSeverityLabels(findings, cwd) {
|
|
|
3494
3196
|
for (const f of findings) {
|
|
3495
3197
|
if (seen.has(f.severity)) continue;
|
|
3496
3198
|
seen.add(f.severity);
|
|
3497
|
-
|
|
3199
|
+
ensureLabel(severityLabel(f.severity), SEVERITY_COLORS[f.severity], `kody QA finding severity ${f.severity}`, cwd);
|
|
3498
3200
|
}
|
|
3499
3201
|
}
|
|
3500
3202
|
function buildIssueBody(f, goalId, parentManifestNumber) {
|
|
@@ -3528,7 +3230,7 @@ function buildIssueBody(f, goalId, parentManifestNumber) {
|
|
|
3528
3230
|
return lines.join("\n");
|
|
3529
3231
|
}
|
|
3530
3232
|
function createOrUpdateManifestIssue(number, manifest, cwd) {
|
|
3531
|
-
|
|
3233
|
+
ensureLabel(MANIFEST_LABEL, "8b5cf6", "kody: goals manifest", cwd);
|
|
3532
3234
|
const body = serializeManifestBody(manifest);
|
|
3533
3235
|
if (number !== null) {
|
|
3534
3236
|
gh(["issue", "edit", String(number), "--body-file", "-"], { input: body, cwd });
|
|
@@ -3561,7 +3263,7 @@ function writeStateFile(cwd, goalId, lastDispatchedIssue) {
|
|
|
3561
3263
|
function gitTry(args, cwd) {
|
|
3562
3264
|
const env = { ...process.env, SKIP_HOOKS: "1", HUSKY: "0" };
|
|
3563
3265
|
try {
|
|
3564
|
-
|
|
3266
|
+
execFileSync10("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"], env });
|
|
3565
3267
|
return { ok: true, stderr: "" };
|
|
3566
3268
|
} catch (err) {
|
|
3567
3269
|
const e = err;
|
|
@@ -3643,8 +3345,8 @@ ${tail}
|
|
|
3643
3345
|
}
|
|
3644
3346
|
function createTaskIssue(finding, goalId, manifestNumber, cwd) {
|
|
3645
3347
|
const labels = [`goal:${goalId}`, severityLabel(finding.severity), FINDING_LABEL];
|
|
3646
|
-
|
|
3647
|
-
|
|
3348
|
+
ensureLabel(`goal:${goalId}`, "1d76db", `goal: ${goalId}`, cwd);
|
|
3349
|
+
ensureLabel(FINDING_LABEL, "ededed", "kody: QA finding", cwd);
|
|
3648
3350
|
const title = `[${finding.severity}] ${finding.title}`.slice(0, 240);
|
|
3649
3351
|
const body = buildIssueBody(finding, goalId, manifestNumber);
|
|
3650
3352
|
const args = ["issue", "create", "--title", title, "--body-file", "-"];
|
|
@@ -3703,7 +3405,7 @@ QA_REPORT_POSTED=https://github.com/${ctx.config.github.owner}/${ctx.config.gith
|
|
|
3703
3405
|
ctx.output.exitCode = verdict === "FAIL" ? 1 : 0;
|
|
3704
3406
|
return;
|
|
3705
3407
|
}
|
|
3706
|
-
|
|
3408
|
+
ensureLabel(FINDING_LABEL, "ededed", "kody: QA finding", ctx.cwd);
|
|
3707
3409
|
const scope2 = ctx.args.scope;
|
|
3708
3410
|
const title = `QA [${verdict}]: ${scope2?.trim() || "smoke"} \u2014 ${todayIso()}`.slice(0, 240);
|
|
3709
3411
|
let url = "";
|
|
@@ -3833,51 +3535,209 @@ QA_GOAL_TARGETED=(no manifest issue) (id: ${goalId}, verdict: ${verdict})
|
|
|
3833
3535
|
ctx.output.exitCode = verdict === "FAIL" ? 1 : 0;
|
|
3834
3536
|
};
|
|
3835
3537
|
|
|
3538
|
+
// src/goal/labels.ts
|
|
3539
|
+
function goalLabel(goalId) {
|
|
3540
|
+
return `goal:${goalId}`;
|
|
3541
|
+
}
|
|
3542
|
+
|
|
3543
|
+
// src/goal/operations.ts
|
|
3544
|
+
function fail(err) {
|
|
3545
|
+
if (err instanceof Error) {
|
|
3546
|
+
const lines = err.message.split("\n").filter(Boolean);
|
|
3547
|
+
return { ok: false, error: lines[0] ?? err.message };
|
|
3548
|
+
}
|
|
3549
|
+
return { ok: false, error: String(err) };
|
|
3550
|
+
}
|
|
3551
|
+
function listGoalIssues(goalId, cwd) {
|
|
3552
|
+
try {
|
|
3553
|
+
const out = gh(
|
|
3554
|
+
[
|
|
3555
|
+
"api",
|
|
3556
|
+
`repos/{owner}/{repo}/issues?labels=${goalLabel(goalId)}&state=all&per_page=100`,
|
|
3557
|
+
"--jq",
|
|
3558
|
+
"[.[] | select(.pull_request == null) | {number, state: (.state | ascii_upcase)}]"
|
|
3559
|
+
],
|
|
3560
|
+
{ cwd }
|
|
3561
|
+
);
|
|
3562
|
+
return { ok: true, value: JSON.parse(out) };
|
|
3563
|
+
} catch (err) {
|
|
3564
|
+
return fail(err);
|
|
3565
|
+
}
|
|
3566
|
+
}
|
|
3567
|
+
function listOpenPrs(cwd) {
|
|
3568
|
+
try {
|
|
3569
|
+
const out = gh(
|
|
3570
|
+
[
|
|
3571
|
+
"pr",
|
|
3572
|
+
"list",
|
|
3573
|
+
"--state",
|
|
3574
|
+
"open",
|
|
3575
|
+
"--limit",
|
|
3576
|
+
"200",
|
|
3577
|
+
"--json",
|
|
3578
|
+
"number,url,isDraft,headRefName,baseRefName,body"
|
|
3579
|
+
],
|
|
3580
|
+
{ cwd }
|
|
3581
|
+
);
|
|
3582
|
+
return { ok: true, value: JSON.parse(out) };
|
|
3583
|
+
} catch (err) {
|
|
3584
|
+
return fail(err);
|
|
3585
|
+
}
|
|
3586
|
+
}
|
|
3587
|
+
function pairIssuesWithPrs(issues, openPrs) {
|
|
3588
|
+
const prByIssue = /* @__PURE__ */ new Map();
|
|
3589
|
+
for (const pr of openPrs) claimPrForIssue(pr, prByIssue);
|
|
3590
|
+
return issues.map((i) => {
|
|
3591
|
+
const pr = prByIssue.get(i.number);
|
|
3592
|
+
let prState = "absent";
|
|
3593
|
+
if (pr) prState = pr.isDraft ? "draft" : "ready";
|
|
3594
|
+
return { number: i.number, state: i.state, prState };
|
|
3595
|
+
});
|
|
3596
|
+
}
|
|
3597
|
+
function claimPrForIssue(pr, prByIssue) {
|
|
3598
|
+
for (const issueNum of extractClosesIssues(pr.body)) {
|
|
3599
|
+
if (!prByIssue.has(issueNum)) {
|
|
3600
|
+
prByIssue.set(issueNum, pr);
|
|
3601
|
+
return;
|
|
3602
|
+
}
|
|
3603
|
+
}
|
|
3604
|
+
const headMatch = pr.headRefName.match(/^(\d+)-/);
|
|
3605
|
+
if (headMatch) {
|
|
3606
|
+
const n = Number.parseInt(headMatch[1], 10);
|
|
3607
|
+
if (Number.isFinite(n) && !prByIssue.has(n)) prByIssue.set(n, pr);
|
|
3608
|
+
}
|
|
3609
|
+
}
|
|
3610
|
+
function extractClosesIssues(body) {
|
|
3611
|
+
const out = [];
|
|
3612
|
+
const re = /\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s+#(\d+)\b/gi;
|
|
3613
|
+
for (let m = re.exec(body); m !== null; m = re.exec(body)) {
|
|
3614
|
+
const n = Number.parseInt(m[1], 10);
|
|
3615
|
+
if (Number.isFinite(n)) out.push(n);
|
|
3616
|
+
}
|
|
3617
|
+
return out;
|
|
3618
|
+
}
|
|
3619
|
+
function pickLeafPr(prs) {
|
|
3620
|
+
if (prs.length === 0) return void 0;
|
|
3621
|
+
const bases = new Set(prs.map((p) => p.baseRefName));
|
|
3622
|
+
const leaves = prs.filter((p) => !bases.has(p.headRefName));
|
|
3623
|
+
return leaves.sort((a, b) => b.number - a.number)[0];
|
|
3624
|
+
}
|
|
3625
|
+
function commentOnIssue(issueNumber, body, cwd) {
|
|
3626
|
+
try {
|
|
3627
|
+
gh(["issue", "comment", String(issueNumber), "--body", body], { cwd });
|
|
3628
|
+
return { ok: true };
|
|
3629
|
+
} catch (err) {
|
|
3630
|
+
return fail(err);
|
|
3631
|
+
}
|
|
3632
|
+
}
|
|
3633
|
+
function closeIssue(issueNumber, options, cwd) {
|
|
3634
|
+
try {
|
|
3635
|
+
if (options.comment) {
|
|
3636
|
+
gh(["issue", "comment", String(issueNumber), "--body", options.comment], { cwd });
|
|
3637
|
+
}
|
|
3638
|
+
const args = ["issue", "close", String(issueNumber)];
|
|
3639
|
+
if (options.reason) args.push("--reason", options.reason);
|
|
3640
|
+
gh(args, { cwd });
|
|
3641
|
+
return { ok: true };
|
|
3642
|
+
} catch (err) {
|
|
3643
|
+
return fail(err);
|
|
3644
|
+
}
|
|
3645
|
+
}
|
|
3646
|
+
function closePr(prNumber, comment, cwd) {
|
|
3647
|
+
try {
|
|
3648
|
+
gh(["pr", "close", String(prNumber), "--comment", comment], { cwd });
|
|
3649
|
+
return { ok: true };
|
|
3650
|
+
} catch (err) {
|
|
3651
|
+
return fail(err);
|
|
3652
|
+
}
|
|
3653
|
+
}
|
|
3654
|
+
function mergePrSquash(prNumber, cwd) {
|
|
3655
|
+
try {
|
|
3656
|
+
gh(["pr", "merge", String(prNumber), "--squash", "--delete-branch"], { cwd });
|
|
3657
|
+
return { ok: true };
|
|
3658
|
+
} catch (err) {
|
|
3659
|
+
return fail(err);
|
|
3660
|
+
}
|
|
3661
|
+
}
|
|
3662
|
+
function markPrReady(prNumber, cwd) {
|
|
3663
|
+
try {
|
|
3664
|
+
gh(["pr", "ready", String(prNumber)], { cwd });
|
|
3665
|
+
return { ok: true };
|
|
3666
|
+
} catch (err) {
|
|
3667
|
+
return fail(err);
|
|
3668
|
+
}
|
|
3669
|
+
}
|
|
3670
|
+
|
|
3836
3671
|
// src/goal/phase.ts
|
|
3837
3672
|
function derivePhase(snap) {
|
|
3838
|
-
if (snap.lifecycleState
|
|
3673
|
+
if (!snap.lifecycleState) return "missing";
|
|
3839
3674
|
if (snap.lifecycleState === "abandoned") return "abandoned";
|
|
3840
3675
|
if (snap.lifecycleState === "closed" || snap.lifecycleState === "done") return "terminal";
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
if (
|
|
3844
|
-
const
|
|
3845
|
-
if (
|
|
3846
|
-
const
|
|
3847
|
-
if (inFlight) return "in-flight";
|
|
3848
|
-
const dispatchable = snap.childTasks.some((t) => t.state === "OPEN" && !t.labels.includes(DISPATCHED_LABEL));
|
|
3676
|
+
const hasInFlight = snap.childTasks.some((t) => t.state === "OPEN" && t.prState === "draft");
|
|
3677
|
+
if (hasInFlight) return "in-flight";
|
|
3678
|
+
if (snap.childTasks.length === 0) return "idle";
|
|
3679
|
+
const allDone = snap.childTasks.every((t) => t.state === "CLOSED" || t.prState === "ready");
|
|
3680
|
+
if (allDone) return "all-done";
|
|
3681
|
+
const dispatchable = snap.childTasks.some((t) => t.state === "OPEN" && t.prState === "absent");
|
|
3849
3682
|
if (dispatchable) return "ready-to-dispatch";
|
|
3850
3683
|
return "idle";
|
|
3851
3684
|
}
|
|
3852
3685
|
function pickNextDispatchable(snap) {
|
|
3853
|
-
|
|
3854
|
-
return candidates[0];
|
|
3686
|
+
return snap.childTasks.filter((t) => t.state === "OPEN" && t.prState === "absent").sort((a, b) => a.number - b.number)[0];
|
|
3855
3687
|
}
|
|
3856
3688
|
|
|
3857
3689
|
// src/scripts/deriveGoalPhase.ts
|
|
3858
3690
|
var deriveGoalPhase = async (ctx) => {
|
|
3859
3691
|
const goal = ctx.data.goal;
|
|
3860
3692
|
if (!goal) return;
|
|
3861
|
-
const issues = listGoalIssues(goal.id,
|
|
3693
|
+
const issues = listGoalIssues(goal.id, ctx.cwd);
|
|
3862
3694
|
if (!issues.ok) {
|
|
3863
|
-
process.stderr.write(`[goal-tick] deriveGoalPhase: list failed: ${issues.error}
|
|
3695
|
+
process.stderr.write(`[goal-tick] deriveGoalPhase: list issues failed: ${issues.error}
|
|
3864
3696
|
`);
|
|
3865
3697
|
goal.childTasks = [];
|
|
3698
|
+
goal.openTaskPrs = [];
|
|
3699
|
+
goal.phase = "idle";
|
|
3700
|
+
return;
|
|
3701
|
+
}
|
|
3702
|
+
const rawIssues = issues.value ?? [];
|
|
3703
|
+
const allPrs = listOpenPrs(ctx.cwd);
|
|
3704
|
+
if (!allPrs.ok) {
|
|
3705
|
+
process.stderr.write(`[goal-tick] deriveGoalPhase: list PRs failed: ${allPrs.error}
|
|
3706
|
+
`);
|
|
3707
|
+
goal.childTasks = rawIssues.map((i) => ({ ...i, prState: "absent" }));
|
|
3708
|
+
goal.openTaskPrs = [];
|
|
3866
3709
|
goal.phase = "idle";
|
|
3867
3710
|
return;
|
|
3868
3711
|
}
|
|
3869
|
-
const
|
|
3870
|
-
goal.
|
|
3712
|
+
const taskPrs = filterGoalTaskPrs(allPrs.value ?? [], rawIssues.map((i) => i.number));
|
|
3713
|
+
goal.openTaskPrs = taskPrs;
|
|
3714
|
+
goal.leafPr = pickLeafPr(taskPrs);
|
|
3715
|
+
goal.childTasks = pairIssuesWithPrs(rawIssues, taskPrs);
|
|
3871
3716
|
goal.phase = derivePhase({
|
|
3872
3717
|
lifecycleState: goal.state,
|
|
3873
|
-
childTasks
|
|
3718
|
+
childTasks: goal.childTasks
|
|
3874
3719
|
});
|
|
3875
|
-
process.stdout.write(
|
|
3876
|
-
`)
|
|
3720
|
+
process.stdout.write(
|
|
3721
|
+
`[goal-tick] phase=${goal.phase} goal=${goal.id} tasks=${rawIssues.length} stack=${taskPrs.length}` + (goal.leafPr ? ` leaf=#${goal.leafPr.number}` : "") + "\n"
|
|
3722
|
+
);
|
|
3877
3723
|
};
|
|
3724
|
+
function filterGoalTaskPrs(prs, taskIssueNumbers) {
|
|
3725
|
+
const taskSet = new Set(taskIssueNumbers);
|
|
3726
|
+
return prs.filter((pr) => {
|
|
3727
|
+
for (const n of extractClosesIssues(pr.body)) {
|
|
3728
|
+
if (taskSet.has(n)) return true;
|
|
3729
|
+
}
|
|
3730
|
+
const headMatch = pr.headRefName.match(/^(\d+)-/);
|
|
3731
|
+
if (headMatch) {
|
|
3732
|
+
const n = Number.parseInt(headMatch[1], 10);
|
|
3733
|
+
if (Number.isFinite(n) && taskSet.has(n)) return true;
|
|
3734
|
+
}
|
|
3735
|
+
return false;
|
|
3736
|
+
});
|
|
3737
|
+
}
|
|
3878
3738
|
|
|
3879
3739
|
// src/scripts/diagMcp.ts
|
|
3880
|
-
import { execFileSync as
|
|
3740
|
+
import { execFileSync as execFileSync11 } from "child_process";
|
|
3881
3741
|
import * as fs15 from "fs";
|
|
3882
3742
|
import * as os3 from "os";
|
|
3883
3743
|
import * as path15 from "path";
|
|
@@ -3897,7 +3757,7 @@ var diagMcp = async (_ctx) => {
|
|
|
3897
3757
|
process.stderr.write(`[kody diag] chromium present: ${hasChromium ? "yes" : "no"}
|
|
3898
3758
|
`);
|
|
3899
3759
|
try {
|
|
3900
|
-
const v =
|
|
3760
|
+
const v = execFileSync11("npx", ["-y", "--package=@playwright/mcp@latest", "--", "playwright-mcp", "--version"], {
|
|
3901
3761
|
stdio: "pipe",
|
|
3902
3762
|
timeout: 6e4,
|
|
3903
3763
|
encoding: "utf8"
|
|
@@ -4399,7 +4259,7 @@ var discoverQaContext = async (ctx) => {
|
|
|
4399
4259
|
};
|
|
4400
4260
|
|
|
4401
4261
|
// src/scripts/dispatch.ts
|
|
4402
|
-
import { execFileSync as
|
|
4262
|
+
import { execFileSync as execFileSync12 } from "child_process";
|
|
4403
4263
|
var API_TIMEOUT_MS4 = 3e4;
|
|
4404
4264
|
var dispatch = async (ctx, _profile, _agentResult, args) => {
|
|
4405
4265
|
const next = args?.next;
|
|
@@ -4435,7 +4295,7 @@ var dispatch = async (ctx, _profile, _agentResult, args) => {
|
|
|
4435
4295
|
const sub = usePr ? "pr" : "issue";
|
|
4436
4296
|
const body = `@kody ${next}`;
|
|
4437
4297
|
try {
|
|
4438
|
-
|
|
4298
|
+
execFileSync12("gh", [sub, "comment", String(targetNumber), "--body", body], {
|
|
4439
4299
|
timeout: API_TIMEOUT_MS4,
|
|
4440
4300
|
cwd: ctx.cwd,
|
|
4441
4301
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -4455,7 +4315,7 @@ function parsePr(url) {
|
|
|
4455
4315
|
}
|
|
4456
4316
|
|
|
4457
4317
|
// src/scripts/dispatchClassified.ts
|
|
4458
|
-
import { execFileSync as
|
|
4318
|
+
import { execFileSync as execFileSync13 } from "child_process";
|
|
4459
4319
|
var API_TIMEOUT_MS5 = 3e4;
|
|
4460
4320
|
var VALID_CLASSES2 = /* @__PURE__ */ new Set(["feature", "bug", "spec", "chore"]);
|
|
4461
4321
|
var dispatchClassified = async (ctx) => {
|
|
@@ -4464,7 +4324,7 @@ var dispatchClassified = async (ctx) => {
|
|
|
4464
4324
|
const classification = ctx.data.classification;
|
|
4465
4325
|
if (!classification || !VALID_CLASSES2.has(classification)) return;
|
|
4466
4326
|
try {
|
|
4467
|
-
|
|
4327
|
+
execFileSync13("gh", ["issue", "comment", String(issueNumber), "--body", `@kody ${classification}`], {
|
|
4468
4328
|
cwd: ctx.cwd,
|
|
4469
4329
|
timeout: API_TIMEOUT_MS5,
|
|
4470
4330
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -5095,129 +4955,25 @@ function listIssuesByLabel(label, cwd) {
|
|
|
5095
4955
|
// src/scripts/dispatchNextTask.ts
|
|
5096
4956
|
var dispatchNextTask = async (ctx) => {
|
|
5097
4957
|
const goal = ctx.data.goal;
|
|
5098
|
-
if (!goal?.childTasks) return;
|
|
5099
|
-
const next = pickNextDispatchable({
|
|
5100
|
-
lifecycleState: goal.state,
|
|
5101
|
-
childTasks: goal.childTasks
|
|
5102
|
-
});
|
|
5103
|
-
if (!next) {
|
|
5104
|
-
process.stdout.write("[goal-tick] no
|
|
5105
|
-
return;
|
|
5106
|
-
}
|
|
5107
|
-
process.stdout.write(`[goal-tick] dispatching @kody on task #${next.number} (--base ${goal.goalBranch})
|
|
5108
|
-
`);
|
|
5109
|
-
const comment = commentOnIssue(next.number, `@kody --base ${goal.goalBranch}`, ctx.cwd);
|
|
5110
|
-
if (!comment.ok) {
|
|
5111
|
-
process.stderr.write(`[goal-tick] dispatchNextTask: comment failed on #${next.number}: ${comment.error}
|
|
5112
|
-
`);
|
|
5113
|
-
return;
|
|
5114
|
-
}
|
|
5115
|
-
const label = addLabel2(next.number, DISPATCHED_LABEL, ctx.cwd);
|
|
5116
|
-
if (!label.ok) {
|
|
5117
|
-
process.stderr.write(
|
|
5118
|
-
`[goal-tick] dispatchNextTask: add-label failed on #${next.number}: ${label.error} (continuing \u2014 comment already posted)
|
|
5119
|
-
`
|
|
5120
|
-
);
|
|
5121
|
-
}
|
|
5122
|
-
goal.lastDispatchedIssue = next.number;
|
|
5123
|
-
};
|
|
5124
|
-
|
|
5125
|
-
// src/scripts/ensureGoalBranch.ts
|
|
5126
|
-
var ensureGoalBranch = async (ctx) => {
|
|
5127
|
-
const goal = ctx.data.goal;
|
|
5128
|
-
if (!goal) return;
|
|
5129
|
-
fetchOrigin(ctx.cwd);
|
|
5130
|
-
if (remoteBranchExists(goal.goalBranch, ctx.cwd)) {
|
|
5131
|
-
process.stdout.write(`[goal-tick] origin/${goal.goalBranch} already exists \u2014 leaving as-is
|
|
5132
|
-
`);
|
|
5133
|
-
return;
|
|
5134
|
-
}
|
|
5135
|
-
if (!remoteBranchExists(goal.defaultBranch, ctx.cwd)) {
|
|
5136
|
-
process.stderr.write(`[goal-tick] cannot create goal branch: origin/${goal.defaultBranch} missing
|
|
5137
|
-
`);
|
|
5138
|
-
return;
|
|
5139
|
-
}
|
|
5140
|
-
process.stdout.write(`[goal-tick] creating origin/${goal.goalBranch} from origin/${goal.defaultBranch}
|
|
5141
|
-
`);
|
|
5142
|
-
const r = createBranchFrom(goal.goalBranch, goal.defaultBranch, ctx.cwd);
|
|
5143
|
-
if (!r.ok) {
|
|
5144
|
-
process.stderr.write(
|
|
5145
|
-
`[goal-tick] push of ${goal.goalBranch} failed: ${r.error} \u2014 task dispatch will fall back to defaultBranch
|
|
5146
|
-
`
|
|
5147
|
-
);
|
|
5148
|
-
}
|
|
5149
|
-
};
|
|
5150
|
-
|
|
5151
|
-
// src/scripts/ensureGoalPr.ts
|
|
5152
|
-
var ensureGoalPr = async (ctx) => {
|
|
5153
|
-
const goal = ctx.data.goal;
|
|
5154
|
-
if (!goal) return;
|
|
5155
|
-
if (goal.goalPrUrl) return;
|
|
5156
|
-
if (!remoteBranchExists(goal.goalBranch, ctx.cwd)) return;
|
|
5157
|
-
const existing = listPrsByHead(goal.goalBranch, "open", ctx.cwd);
|
|
5158
|
-
if (existing.ok && existing.value && existing.value.length > 0) {
|
|
5159
|
-
goal.goalPrUrl = existing.value[0].url;
|
|
5160
|
-
return;
|
|
5161
|
-
}
|
|
5162
|
-
const title = `goal: ${goal.id}`;
|
|
5163
|
-
const body = goal.goalIssueNumber ? `Tracking integration PR for goal **${goal.id}**.
|
|
5164
|
-
|
|
5165
|
-
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.
|
|
5166
|
-
|
|
5167
|
-
Closes #${goal.goalIssueNumber}
|
|
5168
|
-
` : `Tracking integration PR for goal **${goal.id}**.
|
|
5169
|
-
|
|
5170
|
-
Child task PRs merge into \`${goal.goalBranch}\`. Held in **draft** until every task is complete.
|
|
5171
|
-
`;
|
|
5172
|
-
const created = createPr(
|
|
5173
|
-
{
|
|
5174
|
-
head: goal.goalBranch,
|
|
5175
|
-
base: goal.defaultBranch,
|
|
5176
|
-
title,
|
|
5177
|
-
body,
|
|
5178
|
-
draft: true
|
|
5179
|
-
},
|
|
5180
|
-
ctx.cwd
|
|
5181
|
-
);
|
|
5182
|
-
if (!created.ok) {
|
|
5183
|
-
process.stderr.write(
|
|
5184
|
-
`[goal-tick] ensureGoalPr: gh pr create failed: ${created.error} (continuing without goal PR)
|
|
5185
|
-
`
|
|
5186
|
-
);
|
|
5187
|
-
return;
|
|
5188
|
-
}
|
|
5189
|
-
process.stdout.write(`[goal-tick] opened draft goal PR ${created.value} for ${goal.id}
|
|
5190
|
-
`);
|
|
5191
|
-
goal.goalPrUrl = created.value;
|
|
5192
|
-
};
|
|
5193
|
-
|
|
5194
|
-
// src/scripts/ensureLifecycleLabels.ts
|
|
5195
|
-
var ensureLifecycleLabels = async (ctx) => {
|
|
5196
|
-
const goal = ctx.data.goal;
|
|
5197
|
-
if (!goal) return;
|
|
5198
|
-
for (const spec of TICK_LABELS) {
|
|
5199
|
-
const r2 = ensureLabel(spec.name, spec.color, spec.description, ctx.cwd);
|
|
5200
|
-
if (!r2.ok) {
|
|
5201
|
-
process.stderr.write(`[goal-tick] ensureLifecycleLabels: ${spec.name}: ${r2.error}
|
|
5202
|
-
`);
|
|
5203
|
-
}
|
|
4958
|
+
if (!goal?.childTasks) return;
|
|
4959
|
+
const next = pickNextDispatchable({
|
|
4960
|
+
lifecycleState: goal.state,
|
|
4961
|
+
childTasks: goal.childTasks
|
|
4962
|
+
});
|
|
4963
|
+
if (!next) {
|
|
4964
|
+
process.stdout.write("[goal-tick] no dispatchable task \u2014 idle\n");
|
|
4965
|
+
return;
|
|
5204
4966
|
}
|
|
5205
|
-
const
|
|
5206
|
-
|
|
5207
|
-
if (!r.ok) {
|
|
5208
|
-
process.stderr.write(`[goal-tick] ensureLifecycleLabels: ${goalLbl}: ${r.error}
|
|
4967
|
+
const base = goal.leafPr?.headRefName ?? goal.defaultBranch;
|
|
4968
|
+
process.stdout.write(`[goal-tick] dispatching @kody on #${next.number} (--base ${base})
|
|
5209
4969
|
`);
|
|
5210
|
-
}
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
"1d76db",
|
|
5214
|
-
"kody: in-flight (work being assembled on a branch)",
|
|
5215
|
-
ctx.cwd
|
|
5216
|
-
);
|
|
5217
|
-
if (!u.ok) {
|
|
5218
|
-
process.stderr.write(`[goal-tick] ensureLifecycleLabels: ${UMBRELLA_BUILDING_LABEL}: ${u.error}
|
|
4970
|
+
const comment = commentOnIssue(next.number, `@kody --base ${base}`, ctx.cwd);
|
|
4971
|
+
if (!comment.ok) {
|
|
4972
|
+
process.stderr.write(`[goal-tick] dispatchNextTask: comment failed on #${next.number}: ${comment.error}
|
|
5219
4973
|
`);
|
|
4974
|
+
return;
|
|
5220
4975
|
}
|
|
4976
|
+
goal.lastDispatchedIssue = next.number;
|
|
5221
4977
|
};
|
|
5222
4978
|
|
|
5223
4979
|
// src/pr.ts
|
|
@@ -5487,203 +5243,41 @@ function collectExpectedTests(raw) {
|
|
|
5487
5243
|
return out;
|
|
5488
5244
|
}
|
|
5489
5245
|
|
|
5490
|
-
// src/scripts/ensureUmbrellaIssue.ts
|
|
5491
|
-
var ensureUmbrellaIssue = async (ctx) => {
|
|
5492
|
-
const goal = ctx.data.goal;
|
|
5493
|
-
if (!goal) return;
|
|
5494
|
-
if (goal.goalIssueNumber !== void 0) return;
|
|
5495
|
-
const title = `goal: ${goal.id}`;
|
|
5496
|
-
const body = `Umbrella issue for goal **${goal.id}**.
|
|
5497
|
-
|
|
5498
|
-
Closed automatically when the goal PR (\`${goal.goalBranch}\` \u2192 \`${goal.defaultBranch}\`) merges.
|
|
5499
|
-
`;
|
|
5500
|
-
const existing = findUmbrellaByTitle(goal.id, title, ctx.cwd);
|
|
5501
|
-
if (existing.ok && existing.value !== null && existing.value !== void 0) {
|
|
5502
|
-
process.stdout.write(`[goal-tick] adopted existing umbrella issue #${existing.value} for ${goal.id}
|
|
5503
|
-
`);
|
|
5504
|
-
goal.goalIssueNumber = existing.value;
|
|
5505
|
-
return;
|
|
5506
|
-
}
|
|
5507
|
-
const created = createIssue(
|
|
5508
|
-
{
|
|
5509
|
-
title,
|
|
5510
|
-
body,
|
|
5511
|
-
labels: [goalLabel(goal.id), UMBRELLA_BUILDING_LABEL]
|
|
5512
|
-
},
|
|
5513
|
-
ctx.cwd
|
|
5514
|
-
);
|
|
5515
|
-
if (!created.ok) {
|
|
5516
|
-
process.stderr.write(
|
|
5517
|
-
`[goal-tick] ensureUmbrellaIssue: gh issue create failed: ${created.error} \u2014 continuing without umbrella issue
|
|
5518
|
-
`
|
|
5519
|
-
);
|
|
5520
|
-
return;
|
|
5521
|
-
}
|
|
5522
|
-
process.stdout.write(`[goal-tick] opened umbrella issue #${created.value} for ${goal.id}
|
|
5523
|
-
`);
|
|
5524
|
-
goal.goalIssueNumber = created.value;
|
|
5525
|
-
};
|
|
5526
|
-
|
|
5527
|
-
// src/goal/state.ts
|
|
5528
|
-
import * as fs20 from "fs";
|
|
5529
|
-
import * as path20 from "path";
|
|
5530
|
-
var VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "done"]);
|
|
5531
|
-
var GoalStateError = class extends Error {
|
|
5532
|
-
constructor(path29, message) {
|
|
5533
|
-
super(`Invalid goal state at ${path29}:
|
|
5534
|
-
${message}`);
|
|
5535
|
-
this.path = path29;
|
|
5536
|
-
this.name = "GoalStateError";
|
|
5537
|
-
}
|
|
5538
|
-
path;
|
|
5539
|
-
};
|
|
5540
|
-
function parseGoalState(filePath, raw) {
|
|
5541
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
5542
|
-
throw new GoalStateError(filePath, "must be a JSON object");
|
|
5543
|
-
}
|
|
5544
|
-
const r = raw;
|
|
5545
|
-
const stateValue = r.state;
|
|
5546
|
-
if (typeof stateValue !== "string" || !VALID_STATES.has(stateValue)) {
|
|
5547
|
-
throw new GoalStateError(
|
|
5548
|
-
filePath,
|
|
5549
|
-
`"state" is required and must be one of: ${[...VALID_STATES].join(" | ")} (got ${JSON.stringify(stateValue)})`
|
|
5550
|
-
);
|
|
5551
|
-
}
|
|
5552
|
-
const parsed = {
|
|
5553
|
-
state: stateValue,
|
|
5554
|
-
extra: {}
|
|
5555
|
-
};
|
|
5556
|
-
if (typeof r.goalIssueNumber === "number" && Number.isFinite(r.goalIssueNumber)) {
|
|
5557
|
-
parsed.goalIssueNumber = r.goalIssueNumber;
|
|
5558
|
-
}
|
|
5559
|
-
if (typeof r.lastDispatchedIssue === "number" && Number.isFinite(r.lastDispatchedIssue)) {
|
|
5560
|
-
parsed.lastDispatchedIssue = r.lastDispatchedIssue;
|
|
5561
|
-
}
|
|
5562
|
-
if (typeof r.goalPrUrl === "string" && r.goalPrUrl.length > 0) {
|
|
5563
|
-
parsed.goalPrUrl = r.goalPrUrl;
|
|
5564
|
-
}
|
|
5565
|
-
for (const ts of ["updatedAt", "createdAt", "startedAt", "completedAt"]) {
|
|
5566
|
-
const v = r[ts];
|
|
5567
|
-
if (typeof v === "string" && v.length > 0) parsed[ts] = v;
|
|
5568
|
-
}
|
|
5569
|
-
const known = /* @__PURE__ */ new Set([
|
|
5570
|
-
"state",
|
|
5571
|
-
"goalIssueNumber",
|
|
5572
|
-
"lastDispatchedIssue",
|
|
5573
|
-
"goalPrUrl",
|
|
5574
|
-
"updatedAt",
|
|
5575
|
-
"createdAt",
|
|
5576
|
-
"startedAt",
|
|
5577
|
-
"completedAt"
|
|
5578
|
-
]);
|
|
5579
|
-
for (const [k, v] of Object.entries(r)) {
|
|
5580
|
-
if (!known.has(k)) parsed.extra[k] = v;
|
|
5581
|
-
}
|
|
5582
|
-
return parsed;
|
|
5583
|
-
}
|
|
5584
|
-
function serializeGoalState(s) {
|
|
5585
|
-
const obj = { ...s.extra, state: s.state };
|
|
5586
|
-
if (s.goalIssueNumber !== void 0) obj.goalIssueNumber = s.goalIssueNumber;
|
|
5587
|
-
if (s.lastDispatchedIssue !== void 0) obj.lastDispatchedIssue = s.lastDispatchedIssue;
|
|
5588
|
-
if (s.goalPrUrl !== void 0) obj.goalPrUrl = s.goalPrUrl;
|
|
5589
|
-
if (s.createdAt !== void 0) obj.createdAt = s.createdAt;
|
|
5590
|
-
if (s.startedAt !== void 0) obj.startedAt = s.startedAt;
|
|
5591
|
-
if (s.completedAt !== void 0) obj.completedAt = s.completedAt;
|
|
5592
|
-
if (s.updatedAt !== void 0) obj.updatedAt = s.updatedAt;
|
|
5593
|
-
return `${JSON.stringify(obj, null, 2)}
|
|
5594
|
-
`;
|
|
5595
|
-
}
|
|
5596
|
-
function goalStatePath(cwd, goalId) {
|
|
5597
|
-
return path20.join(cwd, ".kody", "goals", goalId, "state.json");
|
|
5598
|
-
}
|
|
5599
|
-
function readGoalState(cwd, goalId) {
|
|
5600
|
-
const file = goalStatePath(cwd, goalId);
|
|
5601
|
-
if (!fs20.existsSync(file)) {
|
|
5602
|
-
throw new GoalStateError(file, "file not found");
|
|
5603
|
-
}
|
|
5604
|
-
let raw;
|
|
5605
|
-
try {
|
|
5606
|
-
raw = JSON.parse(fs20.readFileSync(file, "utf-8"));
|
|
5607
|
-
} catch (err) {
|
|
5608
|
-
throw new GoalStateError(file, `invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
5609
|
-
}
|
|
5610
|
-
return parseGoalState(file, raw);
|
|
5611
|
-
}
|
|
5612
|
-
function writeGoalState(cwd, goalId, state) {
|
|
5613
|
-
const file = goalStatePath(cwd, goalId);
|
|
5614
|
-
fs20.mkdirSync(path20.dirname(file), { recursive: true });
|
|
5615
|
-
fs20.writeFileSync(file, serializeGoalState(state), "utf-8");
|
|
5616
|
-
}
|
|
5617
|
-
function nowIso() {
|
|
5618
|
-
return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
5619
|
-
}
|
|
5620
|
-
|
|
5621
5246
|
// src/scripts/finalizeGoal.ts
|
|
5622
5247
|
var finalizeGoal = async (ctx) => {
|
|
5623
5248
|
const goal = ctx.data.goal;
|
|
5624
5249
|
if (!goal) return;
|
|
5625
|
-
process.stdout.write(`[goal-tick] all task(s)
|
|
5626
|
-
`);
|
|
5627
|
-
if (!remoteBranchExists(goal.goalBranch, ctx.cwd)) {
|
|
5628
|
-
process.stderr.write(`[goal-tick] goal branch ${goal.goalBranch} not found on origin \u2014 skipping final PR
|
|
5629
|
-
`);
|
|
5630
|
-
finishState(goal);
|
|
5631
|
-
return;
|
|
5632
|
-
}
|
|
5633
|
-
const title = `goal: ${goal.id}`;
|
|
5634
|
-
const closesLine = goal.goalIssueNumber ? `
|
|
5635
|
-
|
|
5636
|
-
Closes #${goal.goalIssueNumber}
|
|
5637
|
-
` : "\n";
|
|
5638
|
-
const body = `Final integration PR for goal **${goal.id}**.
|
|
5639
|
-
|
|
5640
|
-
All task issues are closed and merged into \`${goal.goalBranch}\`. Ready for review.${closesLine}`;
|
|
5641
|
-
const existing = listPrsByHead(goal.goalBranch, "open", ctx.cwd);
|
|
5642
|
-
if (existing.ok && existing.value && existing.value.length > 0) {
|
|
5643
|
-
const pr = existing.value[0];
|
|
5644
|
-
goal.goalPrUrl = pr.url;
|
|
5645
|
-
const edit = editPrBody(pr.number, body, ctx.cwd);
|
|
5646
|
-
if (!edit.ok) {
|
|
5647
|
-
process.stderr.write(`[goal-tick] finalizeGoal: editPrBody failed: ${edit.error}
|
|
5250
|
+
process.stdout.write(`[goal-tick] all task(s) done \u2014 finalising goal ${goal.id}
|
|
5648
5251
|
`);
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5252
|
+
const leaf = goal.leafPr;
|
|
5253
|
+
if (leaf) {
|
|
5254
|
+
if (leaf.isDraft) {
|
|
5255
|
+
process.stdout.write(`[goal-tick] promoting draft leaf PR #${leaf.number} \u2192 ready
|
|
5652
5256
|
`);
|
|
5653
|
-
const ready = markPrReady(
|
|
5257
|
+
const ready = markPrReady(leaf.number, ctx.cwd);
|
|
5654
5258
|
if (!ready.ok) {
|
|
5655
5259
|
process.stderr.write(`[goal-tick] finalizeGoal: markPrReady failed: ${ready.error}
|
|
5656
5260
|
`);
|
|
5261
|
+
return;
|
|
5657
5262
|
}
|
|
5658
5263
|
}
|
|
5659
|
-
|
|
5660
|
-
const created = createPr(
|
|
5661
|
-
{
|
|
5662
|
-
head: goal.goalBranch,
|
|
5663
|
-
base: goal.defaultBranch,
|
|
5664
|
-
title,
|
|
5665
|
-
body,
|
|
5666
|
-
// ready-for-review (not draft) since we're finalizing.
|
|
5667
|
-
draft: false
|
|
5668
|
-
},
|
|
5669
|
-
ctx.cwd
|
|
5670
|
-
);
|
|
5671
|
-
if (!created.ok) {
|
|
5672
|
-
process.stderr.write(`[goal-tick] finalizeGoal: gh pr create failed: ${created.error}
|
|
5264
|
+
process.stdout.write(`[goal-tick] squash-merging leaf PR #${leaf.number}
|
|
5673
5265
|
`);
|
|
5674
|
-
|
|
5675
|
-
|
|
5266
|
+
const merged = mergePrSquash(leaf.number, ctx.cwd);
|
|
5267
|
+
if (!merged.ok) {
|
|
5268
|
+
process.stderr.write(`[goal-tick] finalizeGoal: mergePrSquash failed: ${merged.error}
|
|
5269
|
+
`);
|
|
5270
|
+
return;
|
|
5676
5271
|
}
|
|
5272
|
+
} else {
|
|
5273
|
+
process.stdout.write(`[goal-tick] no leaf PR \u2014 marking goal done without merge
|
|
5274
|
+
`);
|
|
5677
5275
|
}
|
|
5678
|
-
finishState(goal);
|
|
5679
|
-
};
|
|
5680
|
-
function finishState(goal) {
|
|
5681
5276
|
goal.state = "done";
|
|
5682
|
-
|
|
5683
|
-
}
|
|
5277
|
+
};
|
|
5684
5278
|
|
|
5685
5279
|
// src/scripts/finishFlow.ts
|
|
5686
|
-
import { execFileSync as
|
|
5280
|
+
import { execFileSync as execFileSync14 } from "child_process";
|
|
5687
5281
|
var API_TIMEOUT_MS6 = 3e4;
|
|
5688
5282
|
var STATUS_ICON = {
|
|
5689
5283
|
"review-passed": "\u2705",
|
|
@@ -5717,7 +5311,7 @@ var finishFlow = async (ctx, _profile, _agentResult, args) => {
|
|
|
5717
5311
|
**PR:** ${state.core.prUrl}` : "";
|
|
5718
5312
|
const body = `${icon} kody flow \`${flowName}\` finished \u2014 \`${reason}\`${prSuffix}`;
|
|
5719
5313
|
try {
|
|
5720
|
-
|
|
5314
|
+
execFileSync14("gh", ["issue", "comment", String(issueNumber), "--body", body], {
|
|
5721
5315
|
timeout: API_TIMEOUT_MS6,
|
|
5722
5316
|
cwd: ctx.cwd,
|
|
5723
5317
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -5731,7 +5325,7 @@ var finishFlow = async (ctx, _profile, _agentResult, args) => {
|
|
|
5731
5325
|
};
|
|
5732
5326
|
|
|
5733
5327
|
// src/branch.ts
|
|
5734
|
-
import { execFileSync as
|
|
5328
|
+
import { execFileSync as execFileSync15 } from "child_process";
|
|
5735
5329
|
var UncommittedChangesError = class extends Error {
|
|
5736
5330
|
constructor(branch) {
|
|
5737
5331
|
super(`Uncommitted changes on branch '${branch}' \u2014 refusing to run to protect work in progress`);
|
|
@@ -5741,7 +5335,7 @@ var UncommittedChangesError = class extends Error {
|
|
|
5741
5335
|
branch;
|
|
5742
5336
|
};
|
|
5743
5337
|
function git2(args, cwd) {
|
|
5744
|
-
return
|
|
5338
|
+
return execFileSync15("git", args, {
|
|
5745
5339
|
encoding: "utf-8",
|
|
5746
5340
|
timeout: 3e4,
|
|
5747
5341
|
cwd,
|
|
@@ -5766,7 +5360,15 @@ function checkoutPrBranch(prNumber, cwd) {
|
|
|
5766
5360
|
SKIP_HOOKS: "1",
|
|
5767
5361
|
GH_TOKEN: process.env.GH_PAT?.trim() || process.env.GH_TOKEN || ""
|
|
5768
5362
|
};
|
|
5769
|
-
|
|
5363
|
+
try {
|
|
5364
|
+
execFileSync15("git", ["reset", "--hard", "HEAD"], { cwd, env, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
|
|
5365
|
+
} catch {
|
|
5366
|
+
}
|
|
5367
|
+
try {
|
|
5368
|
+
execFileSync15("git", ["clean", "-fd"], { cwd, env, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
|
|
5369
|
+
} catch {
|
|
5370
|
+
}
|
|
5371
|
+
execFileSync15("gh", ["pr", "checkout", String(prNumber)], {
|
|
5770
5372
|
cwd,
|
|
5771
5373
|
env,
|
|
5772
5374
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -5880,8 +5482,8 @@ function ensureFeatureBranch(issueNumber, title, defaultBranch, cwd, baseBranch)
|
|
|
5880
5482
|
}
|
|
5881
5483
|
|
|
5882
5484
|
// src/gha.ts
|
|
5883
|
-
import { execFileSync as
|
|
5884
|
-
import * as
|
|
5485
|
+
import { execFileSync as execFileSync16 } from "child_process";
|
|
5486
|
+
import * as fs20 from "fs";
|
|
5885
5487
|
function getRunUrl() {
|
|
5886
5488
|
const server = process.env.GITHUB_SERVER_URL;
|
|
5887
5489
|
const repo = process.env.GITHUB_REPOSITORY;
|
|
@@ -5892,10 +5494,10 @@ function getRunUrl() {
|
|
|
5892
5494
|
function reactToTriggerComment(cwd) {
|
|
5893
5495
|
if (process.env.GITHUB_EVENT_NAME !== "issue_comment") return;
|
|
5894
5496
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
5895
|
-
if (!eventPath || !
|
|
5497
|
+
if (!eventPath || !fs20.existsSync(eventPath)) return;
|
|
5896
5498
|
let event = null;
|
|
5897
5499
|
try {
|
|
5898
|
-
event = JSON.parse(
|
|
5500
|
+
event = JSON.parse(fs20.readFileSync(eventPath, "utf-8"));
|
|
5899
5501
|
} catch {
|
|
5900
5502
|
return;
|
|
5901
5503
|
}
|
|
@@ -5923,7 +5525,7 @@ function reactToTriggerComment(cwd) {
|
|
|
5923
5525
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
5924
5526
|
if (attempt > 0) sleepMs(attempt === 1 ? 500 : 1500);
|
|
5925
5527
|
try {
|
|
5926
|
-
|
|
5528
|
+
execFileSync16("gh", args, opts);
|
|
5927
5529
|
return;
|
|
5928
5530
|
} catch (err) {
|
|
5929
5531
|
lastErr = err;
|
|
@@ -5936,13 +5538,13 @@ function reactToTriggerComment(cwd) {
|
|
|
5936
5538
|
}
|
|
5937
5539
|
function sleepMs(ms) {
|
|
5938
5540
|
try {
|
|
5939
|
-
|
|
5541
|
+
execFileSync16("sleep", [(ms / 1e3).toString()], { stdio: "ignore", timeout: ms + 1e3 });
|
|
5940
5542
|
} catch {
|
|
5941
5543
|
}
|
|
5942
5544
|
}
|
|
5943
5545
|
|
|
5944
5546
|
// src/workflow.ts
|
|
5945
|
-
import { execFileSync as
|
|
5547
|
+
import { execFileSync as execFileSync17 } from "child_process";
|
|
5946
5548
|
var GH_TIMEOUT_MS = 3e4;
|
|
5947
5549
|
function ghToken3() {
|
|
5948
5550
|
return process.env.GH_PAT?.trim() || process.env.GH_TOKEN;
|
|
@@ -5950,7 +5552,7 @@ function ghToken3() {
|
|
|
5950
5552
|
function gh3(args, cwd) {
|
|
5951
5553
|
const token = ghToken3();
|
|
5952
5554
|
const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
|
|
5953
|
-
return
|
|
5555
|
+
return execFileSync17("gh", args, {
|
|
5954
5556
|
encoding: "utf-8",
|
|
5955
5557
|
timeout: GH_TIMEOUT_MS,
|
|
5956
5558
|
cwd,
|
|
@@ -6139,13 +5741,15 @@ var handleAbandonedGoal = async (ctx) => {
|
|
|
6139
5741
|
if (!goal || goal.state !== "abandoned") return;
|
|
6140
5742
|
process.stdout.write(`[goal-tick] ${goal.id} is abandoned \u2014 running cleanup
|
|
6141
5743
|
`);
|
|
6142
|
-
const issues = listGoalIssues(goal.id,
|
|
5744
|
+
const issues = listGoalIssues(goal.id, ctx.cwd);
|
|
6143
5745
|
if (!issues.ok) {
|
|
6144
|
-
process.stderr.write(`[goal-tick] handleAbandonedGoal: list failed: ${issues.error}
|
|
5746
|
+
process.stderr.write(`[goal-tick] handleAbandonedGoal: list issues failed: ${issues.error}
|
|
6145
5747
|
`);
|
|
6146
5748
|
} else {
|
|
5749
|
+
const issueNumbers = /* @__PURE__ */ new Set();
|
|
6147
5750
|
for (const i of issues.value ?? []) {
|
|
6148
5751
|
if (i.state !== "OPEN") continue;
|
|
5752
|
+
issueNumbers.add(i.number);
|
|
6149
5753
|
const r = closeIssue(
|
|
6150
5754
|
i.number,
|
|
6151
5755
|
{
|
|
@@ -6159,37 +5763,41 @@ var handleAbandonedGoal = async (ctx) => {
|
|
|
6159
5763
|
`);
|
|
6160
5764
|
}
|
|
6161
5765
|
}
|
|
6162
|
-
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
|
|
6166
|
-
|
|
6167
|
-
|
|
6168
|
-
|
|
5766
|
+
const prs = listOpenPrs(ctx.cwd);
|
|
5767
|
+
if (prs.ok) {
|
|
5768
|
+
for (const pr of prs.value ?? []) {
|
|
5769
|
+
const headMatch = pr.headRefName.match(/^(\d+)-/);
|
|
5770
|
+
const headIssue = headMatch ? Number.parseInt(headMatch[1], 10) : NaN;
|
|
5771
|
+
if (!Number.isFinite(headIssue) || !issueNumbers.has(headIssue)) continue;
|
|
5772
|
+
const r = closePr(pr.number, "_Goal abandoned \u2014 closing stacked PR._", ctx.cwd);
|
|
5773
|
+
if (!r.ok) {
|
|
5774
|
+
process.stderr.write(`[goal-tick] handleAbandonedGoal: failed to close PR #${pr.number}: ${r.error}
|
|
6169
5775
|
`);
|
|
5776
|
+
}
|
|
5777
|
+
}
|
|
6170
5778
|
}
|
|
6171
5779
|
}
|
|
6172
5780
|
goal.state = "closed";
|
|
6173
5781
|
};
|
|
6174
5782
|
|
|
6175
5783
|
// src/scripts/initFlow.ts
|
|
6176
|
-
import { execFileSync as
|
|
6177
|
-
import * as fs23 from "fs";
|
|
6178
|
-
import * as path22 from "path";
|
|
6179
|
-
|
|
6180
|
-
// src/scripts/loadQaGuide.ts
|
|
5784
|
+
import { execFileSync as execFileSync18 } from "child_process";
|
|
6181
5785
|
import * as fs22 from "fs";
|
|
6182
5786
|
import * as path21 from "path";
|
|
5787
|
+
|
|
5788
|
+
// src/scripts/loadQaGuide.ts
|
|
5789
|
+
import * as fs21 from "fs";
|
|
5790
|
+
import * as path20 from "path";
|
|
6183
5791
|
var QA_GUIDE_REL_PATH = ".kody/qa-guide.md";
|
|
6184
5792
|
var loadQaGuide = async (ctx) => {
|
|
6185
|
-
const full =
|
|
6186
|
-
if (!
|
|
5793
|
+
const full = path20.join(ctx.cwd, QA_GUIDE_REL_PATH);
|
|
5794
|
+
if (!fs21.existsSync(full)) {
|
|
6187
5795
|
ctx.data.qaGuide = "";
|
|
6188
5796
|
ctx.data.qaGuidePath = "";
|
|
6189
5797
|
return;
|
|
6190
5798
|
}
|
|
6191
5799
|
try {
|
|
6192
|
-
ctx.data.qaGuide =
|
|
5800
|
+
ctx.data.qaGuide = fs21.readFileSync(full, "utf-8");
|
|
6193
5801
|
ctx.data.qaGuidePath = QA_GUIDE_REL_PATH;
|
|
6194
5802
|
} catch {
|
|
6195
5803
|
ctx.data.qaGuide = "";
|
|
@@ -6199,9 +5807,9 @@ var loadQaGuide = async (ctx) => {
|
|
|
6199
5807
|
|
|
6200
5808
|
// src/scripts/initFlow.ts
|
|
6201
5809
|
function detectPackageManager(cwd) {
|
|
6202
|
-
if (
|
|
6203
|
-
if (
|
|
6204
|
-
if (
|
|
5810
|
+
if (fs22.existsSync(path21.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
5811
|
+
if (fs22.existsSync(path21.join(cwd, "yarn.lock"))) return "yarn";
|
|
5812
|
+
if (fs22.existsSync(path21.join(cwd, "bun.lockb"))) return "bun";
|
|
6205
5813
|
return "npm";
|
|
6206
5814
|
}
|
|
6207
5815
|
function qualityCommandsFor(pm) {
|
|
@@ -6214,7 +5822,7 @@ function qualityCommandsFor(pm) {
|
|
|
6214
5822
|
function detectOwnerRepo(cwd) {
|
|
6215
5823
|
let url;
|
|
6216
5824
|
try {
|
|
6217
|
-
url =
|
|
5825
|
+
url = execFileSync18("git", ["remote", "get-url", "origin"], {
|
|
6218
5826
|
cwd,
|
|
6219
5827
|
encoding: "utf-8",
|
|
6220
5828
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -6299,7 +5907,7 @@ jobs:
|
|
|
6299
5907
|
`;
|
|
6300
5908
|
function defaultBranchFromGit(cwd) {
|
|
6301
5909
|
try {
|
|
6302
|
-
const ref =
|
|
5910
|
+
const ref = execFileSync18("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
|
|
6303
5911
|
cwd,
|
|
6304
5912
|
encoding: "utf-8",
|
|
6305
5913
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -6307,7 +5915,7 @@ function defaultBranchFromGit(cwd) {
|
|
|
6307
5915
|
return ref.replace("refs/remotes/origin/", "");
|
|
6308
5916
|
} catch {
|
|
6309
5917
|
try {
|
|
6310
|
-
return
|
|
5918
|
+
return execFileSync18("git", ["branch", "--show-current"], {
|
|
6311
5919
|
cwd,
|
|
6312
5920
|
encoding: "utf-8",
|
|
6313
5921
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -6323,48 +5931,48 @@ function performInit(cwd, force) {
|
|
|
6323
5931
|
const pm = detectPackageManager(cwd);
|
|
6324
5932
|
const ownerRepo = detectOwnerRepo(cwd);
|
|
6325
5933
|
const defaultBranch = defaultBranchFromGit(cwd);
|
|
6326
|
-
const configPath =
|
|
6327
|
-
if (
|
|
5934
|
+
const configPath = path21.join(cwd, "kody.config.json");
|
|
5935
|
+
if (fs22.existsSync(configPath) && !force) {
|
|
6328
5936
|
skipped.push("kody.config.json");
|
|
6329
5937
|
} else {
|
|
6330
5938
|
const cfg = makeConfig(pm, ownerRepo, defaultBranch);
|
|
6331
|
-
|
|
5939
|
+
fs22.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
|
|
6332
5940
|
`);
|
|
6333
5941
|
wrote.push("kody.config.json");
|
|
6334
5942
|
}
|
|
6335
|
-
const workflowDir =
|
|
6336
|
-
const workflowPath =
|
|
6337
|
-
if (
|
|
5943
|
+
const workflowDir = path21.join(cwd, ".github", "workflows");
|
|
5944
|
+
const workflowPath = path21.join(workflowDir, "kody.yml");
|
|
5945
|
+
if (fs22.existsSync(workflowPath) && !force) {
|
|
6338
5946
|
skipped.push(".github/workflows/kody.yml");
|
|
6339
5947
|
} else {
|
|
6340
|
-
|
|
6341
|
-
|
|
5948
|
+
fs22.mkdirSync(workflowDir, { recursive: true });
|
|
5949
|
+
fs22.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
|
|
6342
5950
|
wrote.push(".github/workflows/kody.yml");
|
|
6343
5951
|
}
|
|
6344
|
-
const hasUi =
|
|
5952
|
+
const hasUi = fs22.existsSync(path21.join(cwd, "src/app")) || fs22.existsSync(path21.join(cwd, "app")) || fs22.existsSync(path21.join(cwd, "pages"));
|
|
6345
5953
|
if (hasUi) {
|
|
6346
|
-
const qaGuidePath =
|
|
6347
|
-
if (
|
|
5954
|
+
const qaGuidePath = path21.join(cwd, QA_GUIDE_REL_PATH);
|
|
5955
|
+
if (fs22.existsSync(qaGuidePath) && !force) {
|
|
6348
5956
|
skipped.push(QA_GUIDE_REL_PATH);
|
|
6349
5957
|
} else {
|
|
6350
|
-
|
|
5958
|
+
fs22.mkdirSync(path21.dirname(qaGuidePath), { recursive: true });
|
|
6351
5959
|
const discovery = runQaDiscovery(cwd);
|
|
6352
|
-
|
|
5960
|
+
fs22.writeFileSync(qaGuidePath, generateQaGuideTemplate(discovery));
|
|
6353
5961
|
wrote.push(QA_GUIDE_REL_PATH);
|
|
6354
5962
|
}
|
|
6355
5963
|
}
|
|
6356
5964
|
const builtinJobs = listBuiltinJobs();
|
|
6357
5965
|
if (builtinJobs.length > 0) {
|
|
6358
|
-
const jobsDir =
|
|
6359
|
-
|
|
5966
|
+
const jobsDir = path21.join(cwd, ".kody", "jobs");
|
|
5967
|
+
fs22.mkdirSync(jobsDir, { recursive: true });
|
|
6360
5968
|
for (const job of builtinJobs) {
|
|
6361
|
-
const rel =
|
|
6362
|
-
const target =
|
|
6363
|
-
if (
|
|
5969
|
+
const rel = path21.join(".kody", "jobs", `${job.slug}.md`);
|
|
5970
|
+
const target = path21.join(cwd, rel);
|
|
5971
|
+
if (fs22.existsSync(target) && !force) {
|
|
6364
5972
|
skipped.push(rel);
|
|
6365
5973
|
continue;
|
|
6366
5974
|
}
|
|
6367
|
-
|
|
5975
|
+
fs22.writeFileSync(target, fs22.readFileSync(job.filePath, "utf-8"));
|
|
6368
5976
|
wrote.push(rel);
|
|
6369
5977
|
}
|
|
6370
5978
|
}
|
|
@@ -6376,12 +5984,12 @@ function performInit(cwd, force) {
|
|
|
6376
5984
|
continue;
|
|
6377
5985
|
}
|
|
6378
5986
|
if (profile.kind !== "scheduled" || !profile.schedule) continue;
|
|
6379
|
-
const target =
|
|
6380
|
-
if (
|
|
5987
|
+
const target = path21.join(workflowDir, `kody-${exe.name}.yml`);
|
|
5988
|
+
if (fs22.existsSync(target) && !force) {
|
|
6381
5989
|
skipped.push(`.github/workflows/kody-${exe.name}.yml`);
|
|
6382
5990
|
continue;
|
|
6383
5991
|
}
|
|
6384
|
-
|
|
5992
|
+
fs22.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
|
|
6385
5993
|
wrote.push(`.github/workflows/kody-${exe.name}.yml`);
|
|
6386
5994
|
}
|
|
6387
5995
|
let labels;
|
|
@@ -6469,6 +6077,82 @@ var loadCoverageRules = async (ctx) => {
|
|
|
6469
6077
|
ctx.data.coverageRules = ctx.config.testRequirements ?? [];
|
|
6470
6078
|
};
|
|
6471
6079
|
|
|
6080
|
+
// src/goal/state.ts
|
|
6081
|
+
import * as fs23 from "fs";
|
|
6082
|
+
import * as path22 from "path";
|
|
6083
|
+
var VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "done"]);
|
|
6084
|
+
var GoalStateError = class extends Error {
|
|
6085
|
+
constructor(path29, message) {
|
|
6086
|
+
super(`Invalid goal state at ${path29}:
|
|
6087
|
+
${message}`);
|
|
6088
|
+
this.path = path29;
|
|
6089
|
+
this.name = "GoalStateError";
|
|
6090
|
+
}
|
|
6091
|
+
path;
|
|
6092
|
+
};
|
|
6093
|
+
function parseGoalState(filePath, raw) {
|
|
6094
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
6095
|
+
throw new GoalStateError(filePath, "must be a JSON object");
|
|
6096
|
+
}
|
|
6097
|
+
const r = raw;
|
|
6098
|
+
const stateValue = r.state;
|
|
6099
|
+
if (typeof stateValue !== "string" || !VALID_STATES.has(stateValue)) {
|
|
6100
|
+
throw new GoalStateError(
|
|
6101
|
+
filePath,
|
|
6102
|
+
`"state" is required and must be one of: ${[...VALID_STATES].join(" | ")} (got ${JSON.stringify(stateValue)})`
|
|
6103
|
+
);
|
|
6104
|
+
}
|
|
6105
|
+
const parsed = {
|
|
6106
|
+
state: stateValue,
|
|
6107
|
+
extra: {}
|
|
6108
|
+
};
|
|
6109
|
+
if (typeof r.lastDispatchedIssue === "number" && Number.isFinite(r.lastDispatchedIssue)) {
|
|
6110
|
+
parsed.lastDispatchedIssue = r.lastDispatchedIssue;
|
|
6111
|
+
}
|
|
6112
|
+
for (const ts of ["updatedAt", "createdAt", "startedAt"]) {
|
|
6113
|
+
const v = r[ts];
|
|
6114
|
+
if (typeof v === "string" && v.length > 0) parsed[ts] = v;
|
|
6115
|
+
}
|
|
6116
|
+
const known = /* @__PURE__ */ new Set(["state", "lastDispatchedIssue", "updatedAt", "createdAt", "startedAt"]);
|
|
6117
|
+
for (const [k, v] of Object.entries(r)) {
|
|
6118
|
+
if (!known.has(k)) parsed.extra[k] = v;
|
|
6119
|
+
}
|
|
6120
|
+
return parsed;
|
|
6121
|
+
}
|
|
6122
|
+
function serializeGoalState(s) {
|
|
6123
|
+
const obj = { ...s.extra, state: s.state };
|
|
6124
|
+
if (s.lastDispatchedIssue !== void 0) obj.lastDispatchedIssue = s.lastDispatchedIssue;
|
|
6125
|
+
if (s.createdAt !== void 0) obj.createdAt = s.createdAt;
|
|
6126
|
+
if (s.startedAt !== void 0) obj.startedAt = s.startedAt;
|
|
6127
|
+
if (s.updatedAt !== void 0) obj.updatedAt = s.updatedAt;
|
|
6128
|
+
return `${JSON.stringify(obj, null, 2)}
|
|
6129
|
+
`;
|
|
6130
|
+
}
|
|
6131
|
+
function goalStatePath(cwd, goalId) {
|
|
6132
|
+
return path22.join(cwd, ".kody", "goals", goalId, "state.json");
|
|
6133
|
+
}
|
|
6134
|
+
function readGoalState(cwd, goalId) {
|
|
6135
|
+
const file = goalStatePath(cwd, goalId);
|
|
6136
|
+
if (!fs23.existsSync(file)) {
|
|
6137
|
+
throw new GoalStateError(file, "file not found");
|
|
6138
|
+
}
|
|
6139
|
+
let raw;
|
|
6140
|
+
try {
|
|
6141
|
+
raw = JSON.parse(fs23.readFileSync(file, "utf-8"));
|
|
6142
|
+
} catch (err) {
|
|
6143
|
+
throw new GoalStateError(file, `invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
6144
|
+
}
|
|
6145
|
+
return parseGoalState(file, raw);
|
|
6146
|
+
}
|
|
6147
|
+
function writeGoalState(cwd, goalId, state) {
|
|
6148
|
+
const file = goalStatePath(cwd, goalId);
|
|
6149
|
+
fs23.mkdirSync(path22.dirname(file), { recursive: true });
|
|
6150
|
+
fs23.writeFileSync(file, serializeGoalState(state), "utf-8");
|
|
6151
|
+
}
|
|
6152
|
+
function nowIso() {
|
|
6153
|
+
return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
6154
|
+
}
|
|
6155
|
+
|
|
6472
6156
|
// src/scripts/loadGoalState.ts
|
|
6473
6157
|
var loadGoalState = async (ctx) => {
|
|
6474
6158
|
const goalId = ctx.args.goal;
|
|
@@ -6489,18 +6173,14 @@ var loadGoalState = async (ctx) => {
|
|
|
6489
6173
|
ctx.data.goal = {
|
|
6490
6174
|
id: goalId,
|
|
6491
6175
|
state: state.state,
|
|
6492
|
-
goalIssueNumber: state.goalIssueNumber,
|
|
6493
6176
|
lastDispatchedIssue: state.lastDispatchedIssue,
|
|
6494
|
-
goalPrUrl: state.goalPrUrl,
|
|
6495
6177
|
// Cache the full parsed object so saveGoalState can preserve `extra`.
|
|
6496
6178
|
raw: state,
|
|
6497
|
-
// `phase`
|
|
6498
|
-
//
|
|
6179
|
+
// `phase`, `childTasks`, `openTaskPrs`, `leafPr` are populated by
|
|
6180
|
+
// deriveGoalPhase later in the chain. Initialize to undefined so
|
|
6181
|
+
// runWhen on `data.goal.phase` can match correctly.
|
|
6499
6182
|
phase: void 0,
|
|
6500
|
-
|
|
6501
|
-
defaultBranch: ctx.config.git.defaultBranch,
|
|
6502
|
-
// Convenience derivations.
|
|
6503
|
-
goalBranch: `goal-${goalId}`
|
|
6183
|
+
defaultBranch: ctx.config.git.defaultBranch
|
|
6504
6184
|
};
|
|
6505
6185
|
} catch (err) {
|
|
6506
6186
|
process.stdout.write(`[goal-tick] ${err instanceof Error ? err.message : String(err)}
|
|
@@ -6854,32 +6534,8 @@ var markFlowSuccess = async (ctx) => {
|
|
|
6854
6534
|
}
|
|
6855
6535
|
};
|
|
6856
6536
|
|
|
6857
|
-
// src/scripts/mergeReadyTaskPRs.ts
|
|
6858
|
-
var mergeReadyTaskPRs = async (ctx) => {
|
|
6859
|
-
const goal = ctx.data.goal;
|
|
6860
|
-
if (!goal) return;
|
|
6861
|
-
const open = listPrsByBase(goal.goalBranch, "open", ctx.cwd);
|
|
6862
|
-
if (!open.ok) {
|
|
6863
|
-
process.stderr.write(`[goal-tick] mergeReadyTaskPRs: list failed: ${open.error}
|
|
6864
|
-
`);
|
|
6865
|
-
return;
|
|
6866
|
-
}
|
|
6867
|
-
for (const pr of open.value ?? []) {
|
|
6868
|
-
if (pr.isDraft) continue;
|
|
6869
|
-
if (pr.mergeable !== "MERGEABLE") continue;
|
|
6870
|
-
if (pr.mergeStateStatus !== "CLEAN") continue;
|
|
6871
|
-
process.stdout.write(`[goal-tick] merging PR #${pr.number} into ${goal.goalBranch}
|
|
6872
|
-
`);
|
|
6873
|
-
const r = mergePrSquash(pr.number, ctx.cwd);
|
|
6874
|
-
if (!r.ok) {
|
|
6875
|
-
process.stderr.write(`[goal-tick] failed to merge PR #${pr.number}: ${r.error} (continuing)
|
|
6876
|
-
`);
|
|
6877
|
-
}
|
|
6878
|
-
}
|
|
6879
|
-
};
|
|
6880
|
-
|
|
6881
6537
|
// src/scripts/mergeReleasePr.ts
|
|
6882
|
-
import { execFileSync as
|
|
6538
|
+
import { execFileSync as execFileSync19 } from "child_process";
|
|
6883
6539
|
var API_TIMEOUT_MS7 = 6e4;
|
|
6884
6540
|
var mergeReleasePr = async (ctx) => {
|
|
6885
6541
|
const state = ctx.data.taskState;
|
|
@@ -6898,7 +6554,7 @@ var mergeReleasePr = async (ctx) => {
|
|
|
6898
6554
|
process.stderr.write(`[kody mergeReleasePr] merging PR #${prNumber} (${prUrl})
|
|
6899
6555
|
`);
|
|
6900
6556
|
try {
|
|
6901
|
-
const out =
|
|
6557
|
+
const out = execFileSync19("gh", ["pr", "merge", String(prNumber), "--merge"], {
|
|
6902
6558
|
timeout: API_TIMEOUT_MS7,
|
|
6903
6559
|
cwd: ctx.cwd,
|
|
6904
6560
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -7016,7 +6672,7 @@ function buildIssueTitle(scope, verdict) {
|
|
|
7016
6672
|
const verdictTag = verdict === "UNKNOWN" ? "REPORT" : verdict;
|
|
7017
6673
|
return `QA [${verdictTag}]: ${focus} \u2014 ${date}`.slice(0, 240);
|
|
7018
6674
|
}
|
|
7019
|
-
function
|
|
6675
|
+
function ensureLabel2(cwd) {
|
|
7020
6676
|
try {
|
|
7021
6677
|
gh(["label", "create", QA_LABEL, "--color", "8b5cf6", "--description", "kody: QA report", "--force"], { cwd });
|
|
7022
6678
|
return true;
|
|
@@ -7076,7 +6732,7 @@ QA_REPORT_POSTED=https://github.com/${ctx.config.github.owner}/${ctx.config.gith
|
|
|
7076
6732
|
}
|
|
7077
6733
|
const scope = ctx.args.scope;
|
|
7078
6734
|
const title = buildIssueTitle(scope, verdict);
|
|
7079
|
-
const hasLabel =
|
|
6735
|
+
const hasLabel = ensureLabel2(ctx.cwd);
|
|
7080
6736
|
let created;
|
|
7081
6737
|
try {
|
|
7082
6738
|
created = createQaIssue(title, reportBody, hasLabel, ctx.cwd);
|
|
@@ -7512,7 +7168,7 @@ ${body}`;
|
|
|
7512
7168
|
}
|
|
7513
7169
|
|
|
7514
7170
|
// src/scripts/recordClassification.ts
|
|
7515
|
-
import { execFileSync as
|
|
7171
|
+
import { execFileSync as execFileSync20 } from "child_process";
|
|
7516
7172
|
var API_TIMEOUT_MS8 = 3e4;
|
|
7517
7173
|
var VALID_CLASSES3 = /* @__PURE__ */ new Set(["feature", "bug", "spec", "chore"]);
|
|
7518
7174
|
var recordClassification = async (ctx) => {
|
|
@@ -7560,7 +7216,7 @@ function parseClassification(prSummary) {
|
|
|
7560
7216
|
}
|
|
7561
7217
|
function tryAuditComment(issueNumber, body, cwd) {
|
|
7562
7218
|
try {
|
|
7563
|
-
|
|
7219
|
+
execFileSync20("gh", ["issue", "comment", String(issueNumber), "--body", body], {
|
|
7564
7220
|
cwd,
|
|
7565
7221
|
timeout: API_TIMEOUT_MS8,
|
|
7566
7222
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -7683,7 +7339,7 @@ var resolveArtifacts = async (ctx, profile) => {
|
|
|
7683
7339
|
};
|
|
7684
7340
|
|
|
7685
7341
|
// src/scripts/resolveFlow.ts
|
|
7686
|
-
import { execFileSync as
|
|
7342
|
+
import { execFileSync as execFileSync21 } from "child_process";
|
|
7687
7343
|
var CONFLICT_DIFF_MAX_BYTES = 4e4;
|
|
7688
7344
|
var resolveFlow = async (ctx) => {
|
|
7689
7345
|
const prNumber = ctx.args.pr;
|
|
@@ -7776,7 +7432,7 @@ function buildPreferBlock(prefer, baseBranch) {
|
|
|
7776
7432
|
}
|
|
7777
7433
|
function getConflictedFiles(cwd) {
|
|
7778
7434
|
try {
|
|
7779
|
-
const out =
|
|
7435
|
+
const out = execFileSync21("git", ["diff", "--name-only", "--diff-filter=U"], {
|
|
7780
7436
|
encoding: "utf-8",
|
|
7781
7437
|
cwd,
|
|
7782
7438
|
env: { ...process.env, HUSKY: "0" }
|
|
@@ -7791,7 +7447,7 @@ function getConflictMarkersPreview(files, cwd, maxBytes = CONFLICT_DIFF_MAX_BYTE
|
|
|
7791
7447
|
let total = 0;
|
|
7792
7448
|
for (const f of files) {
|
|
7793
7449
|
try {
|
|
7794
|
-
const content =
|
|
7450
|
+
const content = execFileSync21("cat", [f], { encoding: "utf-8", cwd }).toString();
|
|
7795
7451
|
const snippet = `### ${f}
|
|
7796
7452
|
|
|
7797
7453
|
\`\`\`
|
|
@@ -7815,12 +7471,12 @@ function tryPostPr3(prNumber, body, cwd) {
|
|
|
7815
7471
|
function pushEmptyCommit(branch, cwd) {
|
|
7816
7472
|
const env = { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" };
|
|
7817
7473
|
try {
|
|
7818
|
-
|
|
7474
|
+
execFileSync21(
|
|
7819
7475
|
"git",
|
|
7820
7476
|
["commit", "--allow-empty", "-m", "chore: kody resolve refresh \u2014 empty commit to recompute mergeable status"],
|
|
7821
7477
|
{ cwd, env, stdio: ["ignore", "pipe", "pipe"] }
|
|
7822
7478
|
);
|
|
7823
|
-
|
|
7479
|
+
execFileSync21("git", ["push", "-u", "origin", branch], {
|
|
7824
7480
|
cwd,
|
|
7825
7481
|
env,
|
|
7826
7482
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -7910,10 +7566,10 @@ var resolvePreviewUrl = async (ctx) => {
|
|
|
7910
7566
|
};
|
|
7911
7567
|
|
|
7912
7568
|
// src/scripts/resolveQaUrl.ts
|
|
7913
|
-
import { execFileSync as
|
|
7569
|
+
import { execFileSync as execFileSync22 } from "child_process";
|
|
7914
7570
|
function ghQuery(args, cwd) {
|
|
7915
7571
|
try {
|
|
7916
|
-
const out =
|
|
7572
|
+
const out = execFileSync22("gh", args, {
|
|
7917
7573
|
cwd,
|
|
7918
7574
|
stdio: ["ignore", "pipe", "pipe"],
|
|
7919
7575
|
encoding: "utf-8",
|
|
@@ -7983,7 +7639,7 @@ var resolveQaUrl = async (ctx) => {
|
|
|
7983
7639
|
};
|
|
7984
7640
|
|
|
7985
7641
|
// src/scripts/revertFlow.ts
|
|
7986
|
-
import { execFileSync as
|
|
7642
|
+
import { execFileSync as execFileSync23 } from "child_process";
|
|
7987
7643
|
var SHA_RE = /^[0-9a-f]{4,40}$/i;
|
|
7988
7644
|
var revertFlow = async (ctx) => {
|
|
7989
7645
|
const prNumber = ctx.args.pr;
|
|
@@ -8065,7 +7721,7 @@ function buildPrSummary(resolved) {
|
|
|
8065
7721
|
return resolved.map((r) => `- Reverted \`${r.full.slice(0, 7)}\`${r.subject ? ` \u2014 ${r.subject}` : ""}`).join("\n");
|
|
8066
7722
|
}
|
|
8067
7723
|
function git3(args, cwd) {
|
|
8068
|
-
return
|
|
7724
|
+
return execFileSync23("git", args, {
|
|
8069
7725
|
encoding: "utf-8",
|
|
8070
7726
|
timeout: 3e4,
|
|
8071
7727
|
cwd,
|
|
@@ -8075,7 +7731,7 @@ function git3(args, cwd) {
|
|
|
8075
7731
|
}
|
|
8076
7732
|
function isAncestorOfHead(sha, cwd) {
|
|
8077
7733
|
try {
|
|
8078
|
-
|
|
7734
|
+
execFileSync23("git", ["merge-base", "--is-ancestor", sha, "HEAD"], {
|
|
8079
7735
|
cwd,
|
|
8080
7736
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
|
|
8081
7737
|
stdio: ["ignore", "ignore", "ignore"]
|
|
@@ -8319,10 +7975,7 @@ var saveGoalState = async (ctx) => {
|
|
|
8319
7975
|
const updated = {
|
|
8320
7976
|
...goal.raw ?? { state: goal.state, extra: {} },
|
|
8321
7977
|
state: goal.state,
|
|
8322
|
-
goalIssueNumber: goal.goalIssueNumber,
|
|
8323
7978
|
lastDispatchedIssue: goal.lastDispatchedIssue,
|
|
8324
|
-
goalPrUrl: goal.goalPrUrl,
|
|
8325
|
-
completedAt: goal.completedAt ?? goal.raw?.completedAt,
|
|
8326
7979
|
updatedAt: nowIso()
|
|
8327
7980
|
};
|
|
8328
7981
|
writeGoalState(ctx.cwd, goal.id, updated);
|
|
@@ -8404,11 +8057,11 @@ var skipAgent = async (ctx) => {
|
|
|
8404
8057
|
};
|
|
8405
8058
|
|
|
8406
8059
|
// src/scripts/stageMergeConflicts.ts
|
|
8407
|
-
import { execFileSync as
|
|
8060
|
+
import { execFileSync as execFileSync24 } from "child_process";
|
|
8408
8061
|
var stageMergeConflicts = async (ctx) => {
|
|
8409
8062
|
if (ctx.data.agentDone === false) return;
|
|
8410
8063
|
try {
|
|
8411
|
-
|
|
8064
|
+
execFileSync24("git", ["add", "-A"], {
|
|
8412
8065
|
cwd: ctx.cwd,
|
|
8413
8066
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
|
|
8414
8067
|
stdio: "pipe"
|
|
@@ -8418,7 +8071,7 @@ var stageMergeConflicts = async (ctx) => {
|
|
|
8418
8071
|
};
|
|
8419
8072
|
|
|
8420
8073
|
// src/scripts/startFlow.ts
|
|
8421
|
-
import { execFileSync as
|
|
8074
|
+
import { execFileSync as execFileSync25 } from "child_process";
|
|
8422
8075
|
var API_TIMEOUT_MS9 = 3e4;
|
|
8423
8076
|
var startFlow = async (ctx, profile, _agentResult, args) => {
|
|
8424
8077
|
const entry = args?.entry;
|
|
@@ -8452,7 +8105,7 @@ function postKodyComment(target, issueNumber, state, next, cwd) {
|
|
|
8452
8105
|
const sub = target === "pr" && state?.core.prUrl ? "pr" : "issue";
|
|
8453
8106
|
const body = `@kody ${next}`;
|
|
8454
8107
|
try {
|
|
8455
|
-
|
|
8108
|
+
execFileSync25("gh", [sub, "comment", String(targetNumber), "--body", body], {
|
|
8456
8109
|
timeout: API_TIMEOUT_MS9,
|
|
8457
8110
|
cwd,
|
|
8458
8111
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -8466,7 +8119,7 @@ function postKodyComment(target, issueNumber, state, next, cwd) {
|
|
|
8466
8119
|
}
|
|
8467
8120
|
|
|
8468
8121
|
// src/scripts/syncFlow.ts
|
|
8469
|
-
import { execFileSync as
|
|
8122
|
+
import { execFileSync as execFileSync26 } from "child_process";
|
|
8470
8123
|
var syncFlow = async (ctx, _profile, args) => {
|
|
8471
8124
|
const announceOnSuccess = Boolean(args?.announceOnSuccess);
|
|
8472
8125
|
const prNumber = ctx.args.pr;
|
|
@@ -8538,7 +8191,7 @@ function bail2(ctx, prNumber, reason) {
|
|
|
8538
8191
|
}
|
|
8539
8192
|
function revParseHead(cwd) {
|
|
8540
8193
|
try {
|
|
8541
|
-
return
|
|
8194
|
+
return execFileSync26("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).toString().trim();
|
|
8542
8195
|
} catch {
|
|
8543
8196
|
return "";
|
|
8544
8197
|
}
|
|
@@ -8546,9 +8199,9 @@ function revParseHead(cwd) {
|
|
|
8546
8199
|
function pushBranch(branch, cwd) {
|
|
8547
8200
|
const env = { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" };
|
|
8548
8201
|
try {
|
|
8549
|
-
|
|
8202
|
+
execFileSync26("git", ["push", "-u", "origin", branch], { cwd, env, stdio: ["ignore", "pipe", "pipe"] });
|
|
8550
8203
|
} catch {
|
|
8551
|
-
|
|
8204
|
+
execFileSync26("git", ["push", "--force-with-lease", "-u", "origin", branch], {
|
|
8552
8205
|
cwd,
|
|
8553
8206
|
env,
|
|
8554
8207
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -8812,7 +8465,7 @@ function downgrade2(ctx, reason) {
|
|
|
8812
8465
|
}
|
|
8813
8466
|
|
|
8814
8467
|
// src/scripts/waitForCi.ts
|
|
8815
|
-
import { execFileSync as
|
|
8468
|
+
import { execFileSync as execFileSync27 } from "child_process";
|
|
8816
8469
|
var API_TIMEOUT_MS10 = 3e4;
|
|
8817
8470
|
var waitForCi = async (ctx, _profile, _agentResult, args) => {
|
|
8818
8471
|
const timeoutMinutes = numArg(args, "timeoutMinutes", 30);
|
|
@@ -8890,7 +8543,7 @@ var waitForCi = async (ctx, _profile, _agentResult, args) => {
|
|
|
8890
8543
|
};
|
|
8891
8544
|
function fetchChecks(prNumber, cwd) {
|
|
8892
8545
|
try {
|
|
8893
|
-
const raw =
|
|
8546
|
+
const raw = execFileSync27("gh", ["pr", "checks", String(prNumber), "--json", "bucket,state,name,workflow,link"], {
|
|
8894
8547
|
encoding: "utf-8",
|
|
8895
8548
|
timeout: API_TIMEOUT_MS10,
|
|
8896
8549
|
cwd,
|
|
@@ -9212,13 +8865,7 @@ var preflightScripts = {
|
|
|
9212
8865
|
runTickScript,
|
|
9213
8866
|
loadGoalState,
|
|
9214
8867
|
handleAbandonedGoal,
|
|
9215
|
-
ensureLifecycleLabels,
|
|
9216
|
-
ensureUmbrellaIssue,
|
|
9217
|
-
ensureGoalPr,
|
|
9218
|
-
mergeReadyTaskPRs,
|
|
9219
|
-
closeMergedTaskIssues,
|
|
9220
8868
|
deriveGoalPhase,
|
|
9221
|
-
ensureGoalBranch,
|
|
9222
8869
|
dispatchNextTask,
|
|
9223
8870
|
finalizeGoal,
|
|
9224
8871
|
saveGoalState
|
|
@@ -9269,7 +8916,7 @@ var allScriptNames = /* @__PURE__ */ new Set([
|
|
|
9269
8916
|
]);
|
|
9270
8917
|
|
|
9271
8918
|
// src/tools.ts
|
|
9272
|
-
import { execFileSync as
|
|
8919
|
+
import { execFileSync as execFileSync28 } from "child_process";
|
|
9273
8920
|
function verifyCliTools(tools, cwd) {
|
|
9274
8921
|
const out = [];
|
|
9275
8922
|
for (const t of tools) out.push(verifyOne(t, cwd));
|
|
@@ -9302,7 +8949,7 @@ function verifyOne(tool, cwd) {
|
|
|
9302
8949
|
}
|
|
9303
8950
|
function runShell(cmd, cwd, timeoutMs = 3e4) {
|
|
9304
8951
|
try {
|
|
9305
|
-
|
|
8952
|
+
execFileSync28("sh", ["-c", cmd], { cwd, stdio: "pipe", timeout: timeoutMs });
|
|
9306
8953
|
return true;
|
|
9307
8954
|
} catch {
|
|
9308
8955
|
return false;
|
|
@@ -9858,7 +9505,7 @@ async function runContainerLoop(profile, ctx, input) {
|
|
|
9858
9505
|
}
|
|
9859
9506
|
function resetWorkingTree(cwd) {
|
|
9860
9507
|
try {
|
|
9861
|
-
|
|
9508
|
+
execFileSync29("git", ["reset", "--hard", "HEAD"], {
|
|
9862
9509
|
cwd,
|
|
9863
9510
|
stdio: ["ignore", "pipe", "pipe"],
|
|
9864
9511
|
timeout: 3e4
|
|
@@ -10017,7 +9664,7 @@ function detectPackageManager2(cwd) {
|
|
|
10017
9664
|
}
|
|
10018
9665
|
function shellOut(cmd, args, cwd, stream = true) {
|
|
10019
9666
|
try {
|
|
10020
|
-
|
|
9667
|
+
execFileSync30(cmd, args, {
|
|
10021
9668
|
cwd,
|
|
10022
9669
|
stdio: stream ? "inherit" : "pipe",
|
|
10023
9670
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", CI: process.env.CI ?? "1" }
|
|
@@ -10030,7 +9677,7 @@ function shellOut(cmd, args, cwd, stream = true) {
|
|
|
10030
9677
|
}
|
|
10031
9678
|
function isOnPath(bin) {
|
|
10032
9679
|
try {
|
|
10033
|
-
|
|
9680
|
+
execFileSync30("which", [bin], { stdio: "pipe" });
|
|
10034
9681
|
return true;
|
|
10035
9682
|
} catch {
|
|
10036
9683
|
return false;
|
|
@@ -10071,7 +9718,7 @@ function installLitellmIfNeeded(cwd) {
|
|
|
10071
9718
|
} catch {
|
|
10072
9719
|
}
|
|
10073
9720
|
try {
|
|
10074
|
-
|
|
9721
|
+
execFileSync30("python3", ["-c", "import litellm"], { stdio: "pipe" });
|
|
10075
9722
|
process.stdout.write("\u2192 kody: litellm already installed\n");
|
|
10076
9723
|
return 0;
|
|
10077
9724
|
} catch {
|
|
@@ -10081,16 +9728,16 @@ function installLitellmIfNeeded(cwd) {
|
|
|
10081
9728
|
}
|
|
10082
9729
|
function configureGitIdentity(cwd) {
|
|
10083
9730
|
try {
|
|
10084
|
-
const name =
|
|
9731
|
+
const name = execFileSync30("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
|
|
10085
9732
|
if (name) return;
|
|
10086
9733
|
} catch {
|
|
10087
9734
|
}
|
|
10088
9735
|
try {
|
|
10089
|
-
|
|
9736
|
+
execFileSync30("git", ["config", "user.name", "github-actions[bot]"], { cwd, stdio: "pipe" });
|
|
10090
9737
|
} catch {
|
|
10091
9738
|
}
|
|
10092
9739
|
try {
|
|
10093
|
-
|
|
9740
|
+
execFileSync30("git", ["config", "user.email", "41898282+github-actions[bot]@users.noreply.github.com"], {
|
|
10094
9741
|
cwd,
|
|
10095
9742
|
stdio: "pipe"
|
|
10096
9743
|
});
|
|
@@ -10391,9 +10038,9 @@ function commitChatFiles(cwd, sessionId, verbose) {
|
|
|
10391
10038
|
if (paths.length === 0) return;
|
|
10392
10039
|
const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
|
|
10393
10040
|
try {
|
|
10394
|
-
|
|
10395
|
-
|
|
10396
|
-
|
|
10041
|
+
execFileSync31("git", ["add", "-f", ...paths], opts);
|
|
10042
|
+
execFileSync31("git", ["commit", "--quiet", "-m", `chat: reply for ${sessionId}`], opts);
|
|
10043
|
+
execFileSync31("git", ["push", "--quiet", "origin", "HEAD"], opts);
|
|
10397
10044
|
} catch (err) {
|
|
10398
10045
|
const msg = err instanceof Error ? err.message : String(err);
|
|
10399
10046
|
process.stderr.write(`[kody:chat] commit/push skipped: ${msg}
|