@kody-ade/kody-engine 0.4.224 → 0.4.225
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/README.md +2 -2
- package/dist/bin/kody.js +779 -831
- package/dist/executables/run/profile.json +1 -1
- package/dist/executables/types.ts +3 -1
- package/package.json +1 -1
- package/dist/duties/duty-scheduler/duty.md +0 -3
- package/dist/duties/duty-scheduler/profile.json +0 -6
- package/dist/duties/duty-tick/duty.md +0 -3
- package/dist/duties/duty-tick/profile.json +0 -6
- package/dist/duties/duty-tick-scripted/duty.md +0 -3
- package/dist/duties/duty-tick-scripted/profile.json +0 -6
- package/dist/duties/fix/duty.md +0 -10
- package/dist/duties/fix/profile.json +0 -6
- package/dist/duties/fix-ci/duty.md +0 -10
- package/dist/duties/fix-ci/profile.json +0 -6
- package/dist/duties/goal-scheduler/duty.md +0 -3
- package/dist/duties/goal-scheduler/profile.json +0 -6
- package/dist/duties/goal-tick/duty.md +0 -3
- package/dist/duties/goal-tick/profile.json +0 -6
- package/dist/duties/init/duty.md +0 -10
- package/dist/duties/init/profile.json +0 -6
- package/dist/duties/job-live-verify/duty.md +0 -3
- package/dist/duties/job-live-verify/profile.json +0 -6
- package/dist/duties/merge/duty.md +0 -10
- package/dist/duties/merge/profile.json +0 -6
- package/dist/duties/npm-publish/duty.md +0 -17
- package/dist/duties/npm-publish/profile.json +0 -6
- package/dist/duties/plan-verify/duty.md +0 -3
- package/dist/duties/plan-verify/profile.json +0 -6
- package/dist/duties/preview-build/duty.md +0 -10
- package/dist/duties/preview-build/profile.json +0 -6
- package/dist/duties/probe-skill/duty.md +0 -3
- package/dist/duties/probe-skill/profile.json +0 -6
- package/dist/duties/qa-goal/duty.md +0 -3
- package/dist/duties/qa-goal/profile.json +0 -6
- package/dist/duties/release/duty.md +0 -10
- package/dist/duties/release/profile.json +0 -6
- package/dist/duties/release-deploy/duty.md +0 -10
- package/dist/duties/release-deploy/profile.json +0 -6
- package/dist/duties/release-prepare/duty.md +0 -10
- package/dist/duties/release-prepare/profile.json +0 -6
- package/dist/duties/release-publish/duty.md +0 -10
- package/dist/duties/release-publish/profile.json +0 -6
- package/dist/duties/resolve/duty.md +0 -10
- package/dist/duties/resolve/profile.json +0 -6
- package/dist/duties/revert/duty.md +0 -10
- package/dist/duties/revert/profile.json +0 -6
- package/dist/duties/sync/duty.md +0 -10
- package/dist/duties/sync/profile.json +0 -6
- package/dist/duties/task-job-fail-once/duty.md +0 -3
- package/dist/duties/task-job-fail-once/profile.json +0 -6
- package/dist/duties/task-job-pass-a/duty.md +0 -3
- package/dist/duties/task-job-pass-a/profile.json +0 -6
- package/dist/duties/task-job-pass-b/duty.md +0 -3
- package/dist/duties/task-job-pass-b/profile.json +0 -6
- package/dist/duties/task-jobs/duty.md +0 -3
- package/dist/duties/task-jobs/profile.json +0 -6
- package/dist/duties/worker-ask/duty.md +0 -10
- package/dist/duties/worker-ask/profile.json +0 -6
- package/dist/executables/duty-scheduler/profile.json +0 -57
- package/dist/executables/duty-tick/profile.json +0 -78
- package/dist/executables/duty-tick/prompt.md +0 -77
- package/dist/executables/duty-tick/prompts/locked.md +0 -50
- package/dist/executables/duty-tick-scripted/profile.json +0 -72
- package/dist/executables/fix/profile.json +0 -91
- package/dist/executables/fix/prompt.md +0 -90
- package/dist/executables/fix-ci/profile.json +0 -71
- package/dist/executables/fix-ci/prompt.md +0 -78
- package/dist/executables/goal-scheduler/profile.json +0 -53
- package/dist/executables/goal-scheduler/scheduler.sh +0 -82
- package/dist/executables/goal-tick/profile.json +0 -69
- package/dist/executables/init/profile.json +0 -36
- package/dist/executables/job-live-verify/profile.json +0 -36
- package/dist/executables/job-live-verify/prompt.md +0 -42
- package/dist/executables/merge/profile.json +0 -54
- package/dist/executables/npm-publish/profile.json +0 -73
- package/dist/executables/npm-publish/publish.sh +0 -93
- package/dist/executables/plan-verify/profile.json +0 -38
- package/dist/executables/plan-verify/prompt.md +0 -72
- package/dist/executables/preview-build/profile.json +0 -61
- package/dist/executables/preview-health/profile.json +0 -48
- package/dist/executables/preview-health/tick.sh +0 -319
- package/dist/executables/probe-skill/profile.json +0 -35
- package/dist/executables/probe-skill/prompt.md +0 -31
- package/dist/executables/probe-skill/skills/probe-skill-marker/SKILL.md +0 -18
- package/dist/executables/qa-goal/profile.json +0 -65
- package/dist/executables/release/deploy.sh +0 -157
- package/dist/executables/release/prepare.sh +0 -353
- package/dist/executables/release/profile.json +0 -95
- package/dist/executables/release/publish.sh +0 -88
- package/dist/executables/release/release.sh +0 -230
- package/dist/executables/release/wait.sh +0 -88
- package/dist/executables/release-deploy/deploy.sh +0 -222
- package/dist/executables/release-deploy/profile.json +0 -53
- package/dist/executables/release-prepare/prepare.sh +0 -368
- package/dist/executables/release-prepare/profile.json +0 -70
- package/dist/executables/release-publish/profile.json +0 -53
- package/dist/executables/release-publish/publish.sh +0 -137
- package/dist/executables/resolve/apply-prefer.sh +0 -54
- package/dist/executables/resolve/profile.json +0 -107
- package/dist/executables/resolve/prompt.md +0 -51
- package/dist/executables/revert/profile.json +0 -82
- package/dist/executables/revert/revert.sh +0 -33
- package/dist/executables/sync/profile.json +0 -48
- package/dist/executables/task-job-fail-once/profile.json +0 -44
- package/dist/executables/task-job-fail-once/prompt.md +0 -1
- package/dist/executables/task-job-pass-a/profile.json +0 -43
- package/dist/executables/task-job-pass-a/prompt.md +0 -1
- package/dist/executables/task-job-pass-b/profile.json +0 -43
- package/dist/executables/task-job-pass-b/prompt.md +0 -1
- package/dist/executables/task-jobs/profile.json +0 -41
- package/dist/executables/task-jobs/prompt.md +0 -1
- package/dist/executables/worker-ask/profile.json +0 -70
- package/dist/executables/worker-ask/prompt.md +0 -60
package/dist/bin/kody.js
CHANGED
|
@@ -15,7 +15,7 @@ var init_package = __esm({
|
|
|
15
15
|
"package.json"() {
|
|
16
16
|
package_default = {
|
|
17
17
|
name: "@kody-ade/kody-engine",
|
|
18
|
-
version: "0.4.
|
|
18
|
+
version: "0.4.225",
|
|
19
19
|
description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
|
|
20
20
|
license: "MIT",
|
|
21
21
|
type: "module",
|
|
@@ -205,6 +205,7 @@ function loadConfig(projectDir = process.cwd()) {
|
|
|
205
205
|
agent: {
|
|
206
206
|
model: String(agent.model),
|
|
207
207
|
...parsePerExecutable(agent.perExecutable),
|
|
208
|
+
...parsePerExecutableReasoningEffort(agent.perExecutableReasoningEffort),
|
|
208
209
|
...parseAgentReasoningEffort(agent.reasoningEffort)
|
|
209
210
|
},
|
|
210
211
|
issueContext: parseIssueContext(raw.issueContext),
|
|
@@ -216,6 +217,7 @@ function loadConfig(projectDir = process.cwd()) {
|
|
|
216
217
|
classify: parseClassifyConfig(raw.classify),
|
|
217
218
|
release: parseReleaseConfig(raw.release),
|
|
218
219
|
jobs: parseJobsConfig(raw.jobs),
|
|
220
|
+
company: parseCompanyConfig(raw.company),
|
|
219
221
|
access: parseAccessConfig(raw.access)
|
|
220
222
|
};
|
|
221
223
|
}
|
|
@@ -262,6 +264,28 @@ function parseJobsConfig(raw) {
|
|
|
262
264
|
}
|
|
263
265
|
return Object.keys(out).length > 0 ? out : void 0;
|
|
264
266
|
}
|
|
267
|
+
function parseCompanyConfig(raw) {
|
|
268
|
+
if (!raw || typeof raw !== "object") return void 0;
|
|
269
|
+
const r = raw;
|
|
270
|
+
const out = {};
|
|
271
|
+
if (r.activeDuties !== void 0) out.activeDuties = parseSlugArray(r.activeDuties, "company.activeDuties");
|
|
272
|
+
if (r.activeGoals !== void 0) out.activeGoals = parseSlugArray(r.activeGoals, "company.activeGoals");
|
|
273
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
274
|
+
}
|
|
275
|
+
function parseSlugArray(raw, field) {
|
|
276
|
+
if (!Array.isArray(raw)) throw new Error(`kody.config.json: ${field} must be an array of strings`);
|
|
277
|
+
const out = [];
|
|
278
|
+
for (const value of raw) {
|
|
279
|
+
if (typeof value !== "string") throw new Error(`kody.config.json: ${field} entries must be strings`);
|
|
280
|
+
const slug = value.trim();
|
|
281
|
+
if (!slug) continue;
|
|
282
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(slug)) {
|
|
283
|
+
throw new Error(`kody.config.json: ${field} contains invalid slug "${value}"`);
|
|
284
|
+
}
|
|
285
|
+
out.push(slug);
|
|
286
|
+
}
|
|
287
|
+
return [...new Set(out)];
|
|
288
|
+
}
|
|
265
289
|
function recordValue(raw) {
|
|
266
290
|
return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : void 0;
|
|
267
291
|
}
|
|
@@ -282,6 +306,15 @@ function parsePerExecutable(raw) {
|
|
|
282
306
|
}
|
|
283
307
|
return Object.keys(out).length > 0 ? { perExecutable: out } : {};
|
|
284
308
|
}
|
|
309
|
+
function parsePerExecutableReasoningEffort(raw) {
|
|
310
|
+
if (!raw || typeof raw !== "object") return {};
|
|
311
|
+
const out = {};
|
|
312
|
+
for (const [k, v] of Object.entries(raw)) {
|
|
313
|
+
const effort = typeof v === "string" ? parseReasoningEffort(v) : null;
|
|
314
|
+
if (effort) out[k] = effort;
|
|
315
|
+
}
|
|
316
|
+
return Object.keys(out).length > 0 ? { perExecutableReasoningEffort: out } : {};
|
|
317
|
+
}
|
|
285
318
|
function parseAgentReasoningEffort(raw) {
|
|
286
319
|
if (typeof raw !== "string") return {};
|
|
287
320
|
return { reasoningEffort: parseReasoningEffort(raw) ?? void 0 };
|
|
@@ -562,7 +595,7 @@ var init_events = __esm({
|
|
|
562
595
|
// src/verify.ts
|
|
563
596
|
import { spawn } from "child_process";
|
|
564
597
|
function runCommand(command, cwd) {
|
|
565
|
-
return new Promise((
|
|
598
|
+
return new Promise((resolve7) => {
|
|
566
599
|
const start = Date.now();
|
|
567
600
|
const child = spawn(command, {
|
|
568
601
|
cwd,
|
|
@@ -591,11 +624,11 @@ function runCommand(command, cwd) {
|
|
|
591
624
|
child.on("exit", (code) => {
|
|
592
625
|
clearTimeout(timer);
|
|
593
626
|
const tail = Buffer.concat(buffers).toString("utf-8").slice(-TAIL_CHARS);
|
|
594
|
-
|
|
627
|
+
resolve7({ exitCode: code ?? -1, durationMs: Date.now() - start, tail });
|
|
595
628
|
});
|
|
596
629
|
child.on("error", (err) => {
|
|
597
630
|
clearTimeout(timer);
|
|
598
|
-
|
|
631
|
+
resolve7({ exitCode: -1, durationMs: Date.now() - start, tail: err.message });
|
|
599
632
|
});
|
|
600
633
|
});
|
|
601
634
|
}
|
|
@@ -1450,7 +1483,7 @@ var init_dutyMcp = __esm({
|
|
|
1450
1483
|
TRUST_STATE_BRANCH = "kody-state";
|
|
1451
1484
|
THREAD_BODY_MAX = 4e3;
|
|
1452
1485
|
CHECK_FAIL_CONCLUSIONS = /* @__PURE__ */ new Set(["FAILURE", "TIMED_OUT", "STARTUP_FAILURE", "ACTION_REQUIRED"]);
|
|
1453
|
-
DEFAULT_IGNORE_CHECKS = ["run", "kody", "duty-tick", "
|
|
1486
|
+
DEFAULT_IGNORE_CHECKS = ["run", "kody", "duty-tick", "worker-ask", "chat"];
|
|
1454
1487
|
trackMarker = (key) => `<!-- kody-track:${key} -->`;
|
|
1455
1488
|
commentMarker = (key) => `<!-- kody-track-comment:${key} -->`;
|
|
1456
1489
|
GATE_EXEMPT_DUTIES = /* @__PURE__ */ new Set(["qa-engineer", "ui-review"]);
|
|
@@ -1513,7 +1546,7 @@ var init_repoWorkspace = __esm({
|
|
|
1513
1546
|
defaultCloneRepo = (repo, token, dir) => {
|
|
1514
1547
|
fs4.mkdirSync(path4.dirname(dir), { recursive: true });
|
|
1515
1548
|
const authUrl = token ? `https://x-access-token:${token}@github.com/${repo}.git` : `https://github.com/${repo}.git`;
|
|
1516
|
-
return new Promise((
|
|
1549
|
+
return new Promise((resolve7, reject) => {
|
|
1517
1550
|
const child = spawn2("git", ["clone", "--depth=1", authUrl, dir], {
|
|
1518
1551
|
stdio: "inherit"
|
|
1519
1552
|
});
|
|
@@ -1529,7 +1562,7 @@ var init_repoWorkspace = __esm({
|
|
|
1529
1562
|
spawnSync("git", ["-C", dir, "config", "user.email", email]);
|
|
1530
1563
|
} catch {
|
|
1531
1564
|
}
|
|
1532
|
-
|
|
1565
|
+
resolve7();
|
|
1533
1566
|
});
|
|
1534
1567
|
child.on("error", reject);
|
|
1535
1568
|
});
|
|
@@ -1814,10 +1847,10 @@ async function runAgent(opts) {
|
|
|
1814
1847
|
let timer;
|
|
1815
1848
|
let next;
|
|
1816
1849
|
if (turnTimeoutMs > 0) {
|
|
1817
|
-
const timeoutPromise = new Promise((
|
|
1850
|
+
const timeoutPromise = new Promise((resolve7) => {
|
|
1818
1851
|
timer = setTimeout(() => {
|
|
1819
1852
|
timedOut = true;
|
|
1820
|
-
|
|
1853
|
+
resolve7({ done: true, value: void 0 });
|
|
1821
1854
|
}, turnTimeoutMs);
|
|
1822
1855
|
});
|
|
1823
1856
|
next = await Promise.race([nextPromise, timeoutPromise]);
|
|
@@ -2055,6 +2088,8 @@ function resolveCompanyStore() {
|
|
|
2055
2088
|
return { repo, ref };
|
|
2056
2089
|
}
|
|
2057
2090
|
function fetchCompanyStore(repo, ref) {
|
|
2091
|
+
const localRoot = localStoreRoot(repo);
|
|
2092
|
+
if (localRoot) return localRoot;
|
|
2058
2093
|
const url = repoToGitUrl(repo);
|
|
2059
2094
|
const cacheDir = path6.join(cacheRoot(), cacheKey(repo, ref));
|
|
2060
2095
|
try {
|
|
@@ -2075,6 +2110,12 @@ function fetchCompanyStore(repo, ref) {
|
|
|
2075
2110
|
return null;
|
|
2076
2111
|
}
|
|
2077
2112
|
}
|
|
2113
|
+
function localStoreRoot(repo) {
|
|
2114
|
+
if (!path6.isAbsolute(repo) && !repo.startsWith(".")) return null;
|
|
2115
|
+
const root = path6.resolve(repo);
|
|
2116
|
+
if (!fs6.existsSync(path6.join(root, ".kody"))) return null;
|
|
2117
|
+
return root;
|
|
2118
|
+
}
|
|
2078
2119
|
function repoToGitUrl(repo) {
|
|
2079
2120
|
if (/^(https?:|ssh:|git@|file:)/.test(repo)) return repo;
|
|
2080
2121
|
if (isGitHubShorthand(repo)) {
|
|
@@ -3205,6 +3246,7 @@ function parseClaudeCode(p, raw) {
|
|
|
3205
3246
|
permissionMode,
|
|
3206
3247
|
maxTurns: typeof r.maxTurns === "number" ? r.maxTurns : null,
|
|
3207
3248
|
maxThinkingTokens: typeof r.maxThinkingTokens === "number" ? r.maxThinkingTokens : null,
|
|
3249
|
+
reasoningEffort: typeof r.reasoningEffort === "string" ? parseReasoningEffort(r.reasoningEffort) : null,
|
|
3208
3250
|
maxTurnTimeoutSec: typeof r.maxTurnTimeoutSec === "number" ? r.maxTurnTimeoutSec : null,
|
|
3209
3251
|
systemPromptAppend: typeof r.systemPromptAppend === "string" ? r.systemPromptAppend : null,
|
|
3210
3252
|
cacheable: r.cacheable === true,
|
|
@@ -3388,6 +3430,7 @@ var VALID_INPUT_TYPES, VALID_PERMISSION_MODES, VALID_ROLES, VALID_CONTAINER_CHIL
|
|
|
3388
3430
|
var init_profile = __esm({
|
|
3389
3431
|
"src/profile.ts"() {
|
|
3390
3432
|
"use strict";
|
|
3433
|
+
init_config();
|
|
3391
3434
|
init_dutyMcp();
|
|
3392
3435
|
init_lifecycles();
|
|
3393
3436
|
init_profile_error();
|
|
@@ -4554,6 +4597,91 @@ here \u2014 never substitute it for the truth.`;
|
|
|
4554
4597
|
}
|
|
4555
4598
|
});
|
|
4556
4599
|
|
|
4600
|
+
// src/dutyReport.ts
|
|
4601
|
+
function parseDutyReportsFromText(text) {
|
|
4602
|
+
const reports = [];
|
|
4603
|
+
for (const match of text.matchAll(REPORT_LINE)) {
|
|
4604
|
+
const raw = match[1]?.trim();
|
|
4605
|
+
if (!raw) continue;
|
|
4606
|
+
try {
|
|
4607
|
+
const parsed = parseDutyReport(JSON.parse(raw));
|
|
4608
|
+
if (parsed) reports.push(parsed);
|
|
4609
|
+
} catch {
|
|
4610
|
+
}
|
|
4611
|
+
}
|
|
4612
|
+
return reports;
|
|
4613
|
+
}
|
|
4614
|
+
function parseDutyReport(raw) {
|
|
4615
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
4616
|
+
const obj = raw;
|
|
4617
|
+
const target = parseTarget(obj.target);
|
|
4618
|
+
if (!target) return null;
|
|
4619
|
+
const evidence = parseBooleanRecord(obj.evidence);
|
|
4620
|
+
const facts = parseFacts(obj.facts);
|
|
4621
|
+
if (!evidence && !facts) return null;
|
|
4622
|
+
return {
|
|
4623
|
+
target,
|
|
4624
|
+
...evidence ? { evidence } : {},
|
|
4625
|
+
...facts ? { facts } : {}
|
|
4626
|
+
};
|
|
4627
|
+
}
|
|
4628
|
+
function applyDutyReportToGoalState(state, report) {
|
|
4629
|
+
const priorFacts = parseFacts(state.extra.facts) ?? {};
|
|
4630
|
+
const nextFacts = { ...priorFacts };
|
|
4631
|
+
for (const [key, value] of Object.entries(report.facts ?? {})) {
|
|
4632
|
+
if (CONTROL_FACT_KEYS.has(key)) continue;
|
|
4633
|
+
nextFacts[key] = value;
|
|
4634
|
+
}
|
|
4635
|
+
for (const [key, value] of Object.entries(report.evidence ?? {})) {
|
|
4636
|
+
nextFacts[key] = value;
|
|
4637
|
+
}
|
|
4638
|
+
const pending = nextFacts.pendingEvidence;
|
|
4639
|
+
if (typeof pending === "string" && Object.hasOwn(report.evidence ?? {}, pending)) {
|
|
4640
|
+
delete nextFacts.pendingEvidence;
|
|
4641
|
+
}
|
|
4642
|
+
return {
|
|
4643
|
+
...state,
|
|
4644
|
+
extra: {
|
|
4645
|
+
...state.extra,
|
|
4646
|
+
facts: nextFacts
|
|
4647
|
+
}
|
|
4648
|
+
};
|
|
4649
|
+
}
|
|
4650
|
+
function parseTarget(raw) {
|
|
4651
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
4652
|
+
const target = raw;
|
|
4653
|
+
if (target.type !== "goal" && target.type !== "task" && target.type !== "duty") return null;
|
|
4654
|
+
if (typeof target.id !== "string" || target.id.trim().length === 0) return null;
|
|
4655
|
+
return { type: target.type, id: target.id.trim() };
|
|
4656
|
+
}
|
|
4657
|
+
function parseBooleanRecord(raw) {
|
|
4658
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
4659
|
+
const out = {};
|
|
4660
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
4661
|
+
if (typeof key !== "string" || key.length === 0 || typeof value !== "boolean") return null;
|
|
4662
|
+
out[key] = value;
|
|
4663
|
+
}
|
|
4664
|
+
return out;
|
|
4665
|
+
}
|
|
4666
|
+
function parseFacts(raw) {
|
|
4667
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
4668
|
+
const out = {};
|
|
4669
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
4670
|
+
if (typeof key !== "string" || key.length === 0) return null;
|
|
4671
|
+
if (CONTROL_FACT_KEYS.has(key)) continue;
|
|
4672
|
+
out[key] = value;
|
|
4673
|
+
}
|
|
4674
|
+
return out;
|
|
4675
|
+
}
|
|
4676
|
+
var REPORT_LINE, CONTROL_FACT_KEYS;
|
|
4677
|
+
var init_dutyReport = __esm({
|
|
4678
|
+
"src/dutyReport.ts"() {
|
|
4679
|
+
"use strict";
|
|
4680
|
+
REPORT_LINE = /^KODY_DUTY_REPORT=(.+)$/gm;
|
|
4681
|
+
CONTROL_FACT_KEYS = /* @__PURE__ */ new Set(["blockers", "destination", "duties", "route", "stage", "state"]);
|
|
4682
|
+
}
|
|
4683
|
+
});
|
|
4684
|
+
|
|
4557
4685
|
// src/lifecycleLabels.ts
|
|
4558
4686
|
function groupOf(label) {
|
|
4559
4687
|
const idx = label.indexOf(":");
|
|
@@ -5301,6 +5429,291 @@ var init_advanceFlow = __esm({
|
|
|
5301
5429
|
}
|
|
5302
5430
|
});
|
|
5303
5431
|
|
|
5432
|
+
// src/goal/manager.ts
|
|
5433
|
+
function evidenceSatisfied(goal, evidence) {
|
|
5434
|
+
return goal.facts[evidence] === true;
|
|
5435
|
+
}
|
|
5436
|
+
function firstMissingEvidence(goal) {
|
|
5437
|
+
return goal.destination.evidence.find((evidence) => !evidenceSatisfied(goal, evidence));
|
|
5438
|
+
}
|
|
5439
|
+
function pushBlocker(goal, reason) {
|
|
5440
|
+
if (!goal.blockers.includes(reason)) goal.blockers.push(reason);
|
|
5441
|
+
}
|
|
5442
|
+
function isSimpleGoal(goal) {
|
|
5443
|
+
return goal.type === SIMPLE_GOAL_TYPE;
|
|
5444
|
+
}
|
|
5445
|
+
function applySimpleGoalTaskSummary(goal, summary) {
|
|
5446
|
+
goal.facts.simpleAttachedTaskCount = summary.total;
|
|
5447
|
+
goal.facts.simpleOpenTaskCount = summary.open;
|
|
5448
|
+
goal.facts[SIMPLE_GOAL_EVIDENCE] = summary.total > 0 && summary.open === 0;
|
|
5449
|
+
}
|
|
5450
|
+
function isFactReference(value) {
|
|
5451
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
5452
|
+
const record = value;
|
|
5453
|
+
return Object.keys(record).length === 1 && typeof record.fact === "string" && record.fact.length > 0;
|
|
5454
|
+
}
|
|
5455
|
+
function isCliArgValue(value) {
|
|
5456
|
+
return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
5457
|
+
}
|
|
5458
|
+
function resolveRouteArgs(goal, step) {
|
|
5459
|
+
const cliArgs = {};
|
|
5460
|
+
for (const [name, value] of Object.entries(step.args ?? {})) {
|
|
5461
|
+
if (!isFactReference(value)) {
|
|
5462
|
+
cliArgs[name] = value;
|
|
5463
|
+
continue;
|
|
5464
|
+
}
|
|
5465
|
+
const factValue = goal.facts[value.fact];
|
|
5466
|
+
if (factValue === void 0 || factValue === null) {
|
|
5467
|
+
return { ok: false, reason: `route arg ${name} needs missing fact ${value.fact}` };
|
|
5468
|
+
}
|
|
5469
|
+
if (!isCliArgValue(factValue)) {
|
|
5470
|
+
return { ok: false, reason: `route arg ${name} needs scalar fact ${value.fact}` };
|
|
5471
|
+
}
|
|
5472
|
+
cliArgs[name] = factValue;
|
|
5473
|
+
}
|
|
5474
|
+
return { ok: true, cliArgs };
|
|
5475
|
+
}
|
|
5476
|
+
function planManagedGoalTick(goal) {
|
|
5477
|
+
goal.blockers = [];
|
|
5478
|
+
const missing = firstMissingEvidence(goal);
|
|
5479
|
+
if (!missing) {
|
|
5480
|
+
goal.stage = "done";
|
|
5481
|
+
delete goal.facts.pendingEvidence;
|
|
5482
|
+
return { kind: "done" };
|
|
5483
|
+
}
|
|
5484
|
+
const pending = goal.facts.pendingEvidence;
|
|
5485
|
+
if (pending === missing) {
|
|
5486
|
+
const stage = typeof goal.stage === "string" ? goal.stage : "waiting";
|
|
5487
|
+
return { kind: "wait", evidence: missing, stage, reason: `waiting for evidence: ${missing}` };
|
|
5488
|
+
}
|
|
5489
|
+
const step = goal.route.find((candidate) => candidate.evidence === missing);
|
|
5490
|
+
if (!step) {
|
|
5491
|
+
if (isSimpleGoal(goal) && missing === SIMPLE_GOAL_EVIDENCE) {
|
|
5492
|
+
goal.stage = "waiting";
|
|
5493
|
+
delete goal.facts.pendingEvidence;
|
|
5494
|
+
const total = typeof goal.facts.simpleAttachedTaskCount === "number" ? goal.facts.simpleAttachedTaskCount : 0;
|
|
5495
|
+
const open = typeof goal.facts.simpleOpenTaskCount === "number" ? goal.facts.simpleOpenTaskCount : 0;
|
|
5496
|
+
return {
|
|
5497
|
+
kind: "wait",
|
|
5498
|
+
evidence: missing,
|
|
5499
|
+
stage: "waiting",
|
|
5500
|
+
reason: total === 0 ? "waiting for labelled tasks" : `waiting for ${open} open labelled task(s)`
|
|
5501
|
+
};
|
|
5502
|
+
}
|
|
5503
|
+
const reason = `no route step for evidence: ${missing}`;
|
|
5504
|
+
goal.stage = "blocked";
|
|
5505
|
+
pushBlocker(goal, reason);
|
|
5506
|
+
return { kind: "blocked", evidence: missing, stage: "blocked", reason };
|
|
5507
|
+
}
|
|
5508
|
+
if (!goal.duties.includes(step.duty)) {
|
|
5509
|
+
const reason = `route duty ${step.duty} is not attached to this goal`;
|
|
5510
|
+
goal.stage = "blocked";
|
|
5511
|
+
pushBlocker(goal, reason);
|
|
5512
|
+
return { kind: "blocked", evidence: missing, stage: step.stage, reason };
|
|
5513
|
+
}
|
|
5514
|
+
const resolved = resolveRouteArgs(goal, step);
|
|
5515
|
+
if (!resolved.ok) {
|
|
5516
|
+
goal.stage = "blocked";
|
|
5517
|
+
pushBlocker(goal, resolved.reason);
|
|
5518
|
+
return { kind: "blocked", evidence: missing, stage: step.stage, reason: resolved.reason };
|
|
5519
|
+
}
|
|
5520
|
+
goal.stage = step.stage;
|
|
5521
|
+
goal.facts.pendingEvidence = missing;
|
|
5522
|
+
return {
|
|
5523
|
+
kind: "dispatch",
|
|
5524
|
+
evidence: missing,
|
|
5525
|
+
stage: step.stage,
|
|
5526
|
+
duty: step.duty,
|
|
5527
|
+
executable: step.executable,
|
|
5528
|
+
cliArgs: resolved.cliArgs
|
|
5529
|
+
};
|
|
5530
|
+
}
|
|
5531
|
+
function asStringArray(value) {
|
|
5532
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) return null;
|
|
5533
|
+
return [...value];
|
|
5534
|
+
}
|
|
5535
|
+
function asRecord(value) {
|
|
5536
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
5537
|
+
return { ...value };
|
|
5538
|
+
}
|
|
5539
|
+
function asRoute(value) {
|
|
5540
|
+
if (!Array.isArray(value)) return null;
|
|
5541
|
+
const route = [];
|
|
5542
|
+
for (const item of value) {
|
|
5543
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) return null;
|
|
5544
|
+
const raw = item;
|
|
5545
|
+
if (typeof raw.evidence !== "string" || typeof raw.stage !== "string" || typeof raw.duty !== "string") {
|
|
5546
|
+
return null;
|
|
5547
|
+
}
|
|
5548
|
+
const args = raw.args === void 0 ? void 0 : asRecord(raw.args);
|
|
5549
|
+
if (raw.args !== void 0 && !args) return null;
|
|
5550
|
+
route.push({
|
|
5551
|
+
evidence: raw.evidence,
|
|
5552
|
+
stage: raw.stage,
|
|
5553
|
+
duty: raw.duty,
|
|
5554
|
+
executable: typeof raw.executable === "string" ? raw.executable : void 0,
|
|
5555
|
+
args: args ?? void 0
|
|
5556
|
+
});
|
|
5557
|
+
}
|
|
5558
|
+
return route;
|
|
5559
|
+
}
|
|
5560
|
+
function managedGoalFromState(state) {
|
|
5561
|
+
const extra = state.extra;
|
|
5562
|
+
const destination = asRecord(extra.destination);
|
|
5563
|
+
const evidence = asStringArray(destination?.evidence);
|
|
5564
|
+
const duties = asStringArray(extra.duties);
|
|
5565
|
+
const route = asRoute(extra.route);
|
|
5566
|
+
const facts = asRecord(extra.facts);
|
|
5567
|
+
const blockers = asStringArray(extra.blockers);
|
|
5568
|
+
if (typeof extra.type !== "string" || !destination || typeof destination.outcome !== "string" || !evidence || !duties || !route || !facts || !blockers) {
|
|
5569
|
+
return null;
|
|
5570
|
+
}
|
|
5571
|
+
return {
|
|
5572
|
+
type: extra.type,
|
|
5573
|
+
destination: { outcome: destination.outcome, evidence },
|
|
5574
|
+
duties,
|
|
5575
|
+
route,
|
|
5576
|
+
stage: typeof extra.stage === "string" ? extra.stage : void 0,
|
|
5577
|
+
facts,
|
|
5578
|
+
blockers
|
|
5579
|
+
};
|
|
5580
|
+
}
|
|
5581
|
+
function writeManagedGoalToState(state, goal) {
|
|
5582
|
+
return {
|
|
5583
|
+
...state,
|
|
5584
|
+
extra: {
|
|
5585
|
+
...state.extra,
|
|
5586
|
+
type: goal.type,
|
|
5587
|
+
destination: goal.destination,
|
|
5588
|
+
duties: goal.duties,
|
|
5589
|
+
route: goal.route,
|
|
5590
|
+
stage: goal.stage,
|
|
5591
|
+
facts: goal.facts,
|
|
5592
|
+
blockers: goal.blockers
|
|
5593
|
+
}
|
|
5594
|
+
};
|
|
5595
|
+
}
|
|
5596
|
+
var SIMPLE_GOAL_TYPE, SIMPLE_GOAL_EVIDENCE;
|
|
5597
|
+
var init_manager = __esm({
|
|
5598
|
+
"src/goal/manager.ts"() {
|
|
5599
|
+
"use strict";
|
|
5600
|
+
SIMPLE_GOAL_TYPE = "simple";
|
|
5601
|
+
SIMPLE_GOAL_EVIDENCE = "labelledTasksComplete";
|
|
5602
|
+
}
|
|
5603
|
+
});
|
|
5604
|
+
|
|
5605
|
+
// src/goal/state.ts
|
|
5606
|
+
import * as fs24 from "fs";
|
|
5607
|
+
import * as path21 from "path";
|
|
5608
|
+
function parseGoalState(filePath, raw) {
|
|
5609
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
5610
|
+
throw new GoalStateError(filePath, "must be JSON object");
|
|
5611
|
+
}
|
|
5612
|
+
const r = raw;
|
|
5613
|
+
const stateValue = r.state;
|
|
5614
|
+
if (typeof stateValue !== "string" || !VALID_STATES.has(stateValue)) {
|
|
5615
|
+
throw new GoalStateError(
|
|
5616
|
+
filePath,
|
|
5617
|
+
`"state" is required and must be one of: ${[...VALID_STATES].join(" | ")} (got ${JSON.stringify(stateValue)})`
|
|
5618
|
+
);
|
|
5619
|
+
}
|
|
5620
|
+
const parsed = {
|
|
5621
|
+
state: stateValue,
|
|
5622
|
+
extra: {}
|
|
5623
|
+
};
|
|
5624
|
+
for (const ts of ["updatedAt", "createdAt", "startedAt"]) {
|
|
5625
|
+
const v = r[ts];
|
|
5626
|
+
if (typeof v === "string" && v.length > 0) parsed[ts] = v;
|
|
5627
|
+
}
|
|
5628
|
+
const known = /* @__PURE__ */ new Set(["state", "updatedAt", "createdAt", "startedAt"]);
|
|
5629
|
+
for (const [k, v] of Object.entries(r)) {
|
|
5630
|
+
if (!known.has(k)) parsed.extra[k] = v;
|
|
5631
|
+
}
|
|
5632
|
+
return parsed;
|
|
5633
|
+
}
|
|
5634
|
+
function serializeGoalState(s) {
|
|
5635
|
+
const obj = { ...s.extra, state: s.state };
|
|
5636
|
+
if (s.createdAt !== void 0) obj.createdAt = s.createdAt;
|
|
5637
|
+
if (s.startedAt !== void 0) obj.startedAt = s.startedAt;
|
|
5638
|
+
if (s.updatedAt !== void 0) obj.updatedAt = s.updatedAt;
|
|
5639
|
+
return `${JSON.stringify(obj, null, 2)}
|
|
5640
|
+
`;
|
|
5641
|
+
}
|
|
5642
|
+
function nowIso() {
|
|
5643
|
+
return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
5644
|
+
}
|
|
5645
|
+
var VALID_STATES, GoalStateError;
|
|
5646
|
+
var init_state2 = __esm({
|
|
5647
|
+
"src/goal/state.ts"() {
|
|
5648
|
+
"use strict";
|
|
5649
|
+
VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "done"]);
|
|
5650
|
+
GoalStateError = class extends Error {
|
|
5651
|
+
constructor(path45, message) {
|
|
5652
|
+
super(`Invalid goal state at ${path45}:
|
|
5653
|
+
${message}`);
|
|
5654
|
+
this.path = path45;
|
|
5655
|
+
this.name = "GoalStateError";
|
|
5656
|
+
}
|
|
5657
|
+
path;
|
|
5658
|
+
};
|
|
5659
|
+
}
|
|
5660
|
+
});
|
|
5661
|
+
|
|
5662
|
+
// src/scripts/advanceManagedGoal.ts
|
|
5663
|
+
function readSimpleGoalTaskSummary(goalId, cwd) {
|
|
5664
|
+
const raw = gh(
|
|
5665
|
+
["issue", "list", "--state", "all", "--label", `goal:${goalId}`, "--limit", "1000", "--json", "number,state"],
|
|
5666
|
+
{ cwd }
|
|
5667
|
+
);
|
|
5668
|
+
const issues = JSON.parse(raw);
|
|
5669
|
+
const total = issues.length;
|
|
5670
|
+
const open = issues.filter((issue) => String(issue.state ?? "").toLowerCase() === "open").length;
|
|
5671
|
+
return { total, open };
|
|
5672
|
+
}
|
|
5673
|
+
var advanceManagedGoal;
|
|
5674
|
+
var init_advanceManagedGoal = __esm({
|
|
5675
|
+
"src/scripts/advanceManagedGoal.ts"() {
|
|
5676
|
+
"use strict";
|
|
5677
|
+
init_manager();
|
|
5678
|
+
init_state2();
|
|
5679
|
+
init_issue();
|
|
5680
|
+
advanceManagedGoal = async (ctx) => {
|
|
5681
|
+
ctx.skipAgent = true;
|
|
5682
|
+
const goal = ctx.data.goal;
|
|
5683
|
+
if (!goal?.raw) {
|
|
5684
|
+
ctx.output.exitCode = 1;
|
|
5685
|
+
ctx.output.reason = "advanceManagedGoal requires loadGoalState first";
|
|
5686
|
+
return;
|
|
5687
|
+
}
|
|
5688
|
+
ctx.data.goalOriginalStateText = serializeGoalState(goal.raw);
|
|
5689
|
+
const managed = managedGoalFromState(goal.raw);
|
|
5690
|
+
if (!managed) {
|
|
5691
|
+
ctx.output.reason = "goal has no managed-goal contract; nothing to advance";
|
|
5692
|
+
return;
|
|
5693
|
+
}
|
|
5694
|
+
if (isSimpleGoal(managed)) {
|
|
5695
|
+
applySimpleGoalTaskSummary(managed, readSimpleGoalTaskSummary(goal.id, ctx.cwd));
|
|
5696
|
+
}
|
|
5697
|
+
const decision = planManagedGoalTick(managed);
|
|
5698
|
+
ctx.data.managedGoalDecision = decision;
|
|
5699
|
+
if (decision.kind === "done") {
|
|
5700
|
+
goal.state = "done";
|
|
5701
|
+
}
|
|
5702
|
+
goal.raw = writeManagedGoalToState({ ...goal.raw, state: goal.state }, managed);
|
|
5703
|
+
if (decision.kind === "blocked" || decision.kind === "wait" || decision.kind === "idle" || decision.kind === "done") {
|
|
5704
|
+
ctx.output.reason = decision.kind === "done" ? "managed goal complete" : decision.reason;
|
|
5705
|
+
return;
|
|
5706
|
+
}
|
|
5707
|
+
ctx.output.nextDispatch = {
|
|
5708
|
+
duty: decision.duty,
|
|
5709
|
+
executable: decision.executable,
|
|
5710
|
+
cliArgs: decision.cliArgs
|
|
5711
|
+
};
|
|
5712
|
+
ctx.output.reason = `dispatch ${decision.duty} for ${decision.evidence}`;
|
|
5713
|
+
};
|
|
5714
|
+
}
|
|
5715
|
+
});
|
|
5716
|
+
|
|
5304
5717
|
// src/stateBranch.ts
|
|
5305
5718
|
function is404(err) {
|
|
5306
5719
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -5418,6 +5831,118 @@ var init_appendCompanyActivity = __esm({
|
|
|
5418
5831
|
}
|
|
5419
5832
|
});
|
|
5420
5833
|
|
|
5834
|
+
// src/goal/stateStore.ts
|
|
5835
|
+
function statePath(goalId) {
|
|
5836
|
+
return `.kody/goals/instances/${goalId}/state.json`;
|
|
5837
|
+
}
|
|
5838
|
+
function is4042(err) {
|
|
5839
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5840
|
+
return /HTTP 404/i.test(msg) || /Not Found/i.test(msg);
|
|
5841
|
+
}
|
|
5842
|
+
function fetchGoalState(owner, repo, goalId, cwd) {
|
|
5843
|
+
const filePath = statePath(goalId);
|
|
5844
|
+
let raw;
|
|
5845
|
+
try {
|
|
5846
|
+
raw = gh(["api", `/repos/${owner}/${repo}/contents/${filePath}?ref=${STATE_BRANCH}`], { cwd });
|
|
5847
|
+
} catch (err) {
|
|
5848
|
+
if (is4042(err)) return null;
|
|
5849
|
+
throw err;
|
|
5850
|
+
}
|
|
5851
|
+
const o = JSON.parse(raw);
|
|
5852
|
+
if (!o.content) return null;
|
|
5853
|
+
const decoded = Buffer.from(o.content, "base64").toString("utf-8");
|
|
5854
|
+
return parseGoalState(filePath, JSON.parse(decoded));
|
|
5855
|
+
}
|
|
5856
|
+
function putGoalState(owner, repo, goalId, state, message, cwd) {
|
|
5857
|
+
ensureStateBranch(owner, repo, cwd);
|
|
5858
|
+
const filePath = statePath(goalId);
|
|
5859
|
+
const content = Buffer.from(serializeGoalState(state), "utf-8").toString("base64");
|
|
5860
|
+
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
5861
|
+
let sha;
|
|
5862
|
+
try {
|
|
5863
|
+
const cur = gh(["api", `/repos/${owner}/${repo}/contents/${filePath}?ref=${STATE_BRANCH}`], { cwd });
|
|
5864
|
+
const o = JSON.parse(cur);
|
|
5865
|
+
if (o.sha) sha = o.sha;
|
|
5866
|
+
} catch (err) {
|
|
5867
|
+
if (!is4042(err)) throw err;
|
|
5868
|
+
}
|
|
5869
|
+
const payload = { message, content, branch: STATE_BRANCH };
|
|
5870
|
+
if (sha) payload.sha = sha;
|
|
5871
|
+
try {
|
|
5872
|
+
gh(["api", "--method", "PUT", `/repos/${owner}/${repo}/contents/${filePath}`, "--input", "-"], {
|
|
5873
|
+
cwd,
|
|
5874
|
+
input: JSON.stringify(payload)
|
|
5875
|
+
});
|
|
5876
|
+
return;
|
|
5877
|
+
} catch (err) {
|
|
5878
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5879
|
+
const conflict = /HTTP 409/i.test(msg) || /HTTP 422/i.test(msg) || /does not match|but expected/i.test(msg);
|
|
5880
|
+
if (!conflict || attempt === 3) throw err;
|
|
5881
|
+
}
|
|
5882
|
+
}
|
|
5883
|
+
}
|
|
5884
|
+
var init_stateStore = __esm({
|
|
5885
|
+
"src/goal/stateStore.ts"() {
|
|
5886
|
+
"use strict";
|
|
5887
|
+
init_issue();
|
|
5888
|
+
init_stateBranch();
|
|
5889
|
+
init_state2();
|
|
5890
|
+
}
|
|
5891
|
+
});
|
|
5892
|
+
|
|
5893
|
+
// src/scripts/applyDutyReports.ts
|
|
5894
|
+
function collectReports(raw, agentResult) {
|
|
5895
|
+
const out = [];
|
|
5896
|
+
if (Array.isArray(raw)) {
|
|
5897
|
+
for (const item of raw) {
|
|
5898
|
+
if (isDutyReport(item)) out.push(item);
|
|
5899
|
+
}
|
|
5900
|
+
}
|
|
5901
|
+
if (agentResult?.finalText) out.push(...parseDutyReportsFromText(agentResult.finalText));
|
|
5902
|
+
return out;
|
|
5903
|
+
}
|
|
5904
|
+
function isDutyReport(raw) {
|
|
5905
|
+
return !!raw && typeof raw === "object" && !Array.isArray(raw) && !!raw.target && (raw.target.type === "goal" || raw.target.type === "task" || raw.target.type === "duty") && typeof raw.target.id === "string";
|
|
5906
|
+
}
|
|
5907
|
+
function describeMessage(report) {
|
|
5908
|
+
const keys = [
|
|
5909
|
+
...Object.keys(report.evidence ?? {}).map((key) => `evidence:${key}`),
|
|
5910
|
+
...Object.keys(report.facts ?? {}).map((key) => `fact:${key}`)
|
|
5911
|
+
];
|
|
5912
|
+
return `chore(goals): apply duty report for ${report.target.id}${keys.length ? ` (${keys.join(", ")})` : ""}`;
|
|
5913
|
+
}
|
|
5914
|
+
var applyDutyReports;
|
|
5915
|
+
var init_applyDutyReports = __esm({
|
|
5916
|
+
"src/scripts/applyDutyReports.ts"() {
|
|
5917
|
+
"use strict";
|
|
5918
|
+
init_dutyReport();
|
|
5919
|
+
init_state2();
|
|
5920
|
+
init_stateStore();
|
|
5921
|
+
applyDutyReports = async (ctx, _profile, agentResult) => {
|
|
5922
|
+
const reports = collectReports(ctx.data.dutyReports, agentResult);
|
|
5923
|
+
if (reports.length === 0) return;
|
|
5924
|
+
const owner = ctx.config.github?.owner;
|
|
5925
|
+
const repo = ctx.config.github?.repo;
|
|
5926
|
+
if (!owner || !repo) {
|
|
5927
|
+
process.stderr.write("[kody duty-report] missing github owner/repo; cannot apply reports\n");
|
|
5928
|
+
return;
|
|
5929
|
+
}
|
|
5930
|
+
const goalReports = reports.filter((report) => report.target.type === "goal");
|
|
5931
|
+
for (const report of goalReports) {
|
|
5932
|
+
const prior = fetchGoalState(owner, repo, report.target.id, ctx.cwd);
|
|
5933
|
+
if (!prior) {
|
|
5934
|
+
process.stderr.write(`[kody duty-report] goal ${report.target.id} missing on kody-state; report skipped
|
|
5935
|
+
`);
|
|
5936
|
+
continue;
|
|
5937
|
+
}
|
|
5938
|
+
const next = applyDutyReportToGoalState(prior, report);
|
|
5939
|
+
if (serializeGoalState(next) === serializeGoalState(prior)) continue;
|
|
5940
|
+
putGoalState(owner, repo, report.target.id, { ...next, updatedAt: nowIso() }, describeMessage(report), ctx.cwd);
|
|
5941
|
+
}
|
|
5942
|
+
};
|
|
5943
|
+
}
|
|
5944
|
+
});
|
|
5945
|
+
|
|
5421
5946
|
// src/coverage.ts
|
|
5422
5947
|
import { execFileSync as execFileSync10 } from "child_process";
|
|
5423
5948
|
function patternToRegex(pattern) {
|
|
@@ -5584,11 +6109,11 @@ var init_classifyByLabel = __esm({
|
|
|
5584
6109
|
});
|
|
5585
6110
|
|
|
5586
6111
|
// src/scripts/commitAndPush.ts
|
|
5587
|
-
import * as
|
|
5588
|
-
import * as
|
|
6112
|
+
import * as fs25 from "fs";
|
|
6113
|
+
import * as path22 from "path";
|
|
5589
6114
|
function sentinelPathForStage(cwd, profileName) {
|
|
5590
6115
|
const runId = resolveRunId();
|
|
5591
|
-
return
|
|
6116
|
+
return path22.join(cwd, ".kody", "runs", runId, `commit-${profileName}.lock`);
|
|
5592
6117
|
}
|
|
5593
6118
|
var DEFAULT_COMMIT_MESSAGE, commitAndPush2;
|
|
5594
6119
|
var init_commitAndPush = __esm({
|
|
@@ -5605,9 +6130,9 @@ var init_commitAndPush = __esm({
|
|
|
5605
6130
|
}
|
|
5606
6131
|
const idempotencyEnabled = process.env.KODY_COMMIT_IDEMPOTENCY !== "0";
|
|
5607
6132
|
const sentinel = idempotencyEnabled ? sentinelPathForStage(ctx.cwd, profile.name) : null;
|
|
5608
|
-
if (sentinel &&
|
|
6133
|
+
if (sentinel && fs25.existsSync(sentinel)) {
|
|
5609
6134
|
try {
|
|
5610
|
-
const replay = JSON.parse(
|
|
6135
|
+
const replay = JSON.parse(fs25.readFileSync(sentinel, "utf-8"));
|
|
5611
6136
|
ctx.data.commitResult = replay.commitResult ?? { committed: false, pushed: false };
|
|
5612
6137
|
if (Array.isArray(replay.changedFiles)) ctx.data.changedFiles = replay.changedFiles;
|
|
5613
6138
|
if (typeof replay.hasCommitsAhead === "boolean") ctx.data.hasCommitsAhead = replay.hasCommitsAhead;
|
|
@@ -5660,8 +6185,8 @@ var init_commitAndPush = __esm({
|
|
|
5660
6185
|
const result = ctx.data.commitResult;
|
|
5661
6186
|
if (sentinel && result?.committed) {
|
|
5662
6187
|
try {
|
|
5663
|
-
|
|
5664
|
-
|
|
6188
|
+
fs25.mkdirSync(path22.dirname(sentinel), { recursive: true });
|
|
6189
|
+
fs25.writeFileSync(
|
|
5665
6190
|
sentinel,
|
|
5666
6191
|
JSON.stringify(
|
|
5667
6192
|
{
|
|
@@ -5682,148 +6207,17 @@ var init_commitAndPush = __esm({
|
|
|
5682
6207
|
}
|
|
5683
6208
|
});
|
|
5684
6209
|
|
|
5685
|
-
// src/goal/state.ts
|
|
5686
|
-
import * as fs25 from "fs";
|
|
5687
|
-
import * as path22 from "path";
|
|
5688
|
-
function parseGoalState(filePath, raw) {
|
|
5689
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
5690
|
-
throw new GoalStateError(filePath, "must be a JSON object");
|
|
5691
|
-
}
|
|
5692
|
-
const r = raw;
|
|
5693
|
-
const stateValue = r.state;
|
|
5694
|
-
if (typeof stateValue !== "string" || !VALID_STATES.has(stateValue)) {
|
|
5695
|
-
throw new GoalStateError(
|
|
5696
|
-
filePath,
|
|
5697
|
-
`"state" is required and must be one of: ${[...VALID_STATES].join(" | ")} (got ${JSON.stringify(stateValue)})`
|
|
5698
|
-
);
|
|
5699
|
-
}
|
|
5700
|
-
const parsed = {
|
|
5701
|
-
state: stateValue,
|
|
5702
|
-
extra: {}
|
|
5703
|
-
};
|
|
5704
|
-
if (typeof r.mergeApproved === "boolean") {
|
|
5705
|
-
parsed.mergeApproved = r.mergeApproved;
|
|
5706
|
-
}
|
|
5707
|
-
if (typeof r.lastDispatchedIssue === "number" && Number.isFinite(r.lastDispatchedIssue)) {
|
|
5708
|
-
parsed.lastDispatchedIssue = r.lastDispatchedIssue;
|
|
5709
|
-
}
|
|
5710
|
-
for (const ts of ["updatedAt", "createdAt", "startedAt"]) {
|
|
5711
|
-
const v = r[ts];
|
|
5712
|
-
if (typeof v === "string" && v.length > 0) parsed[ts] = v;
|
|
5713
|
-
}
|
|
5714
|
-
const known = /* @__PURE__ */ new Set(["state", "mergeApproved", "lastDispatchedIssue", "updatedAt", "createdAt", "startedAt"]);
|
|
5715
|
-
for (const [k, v] of Object.entries(r)) {
|
|
5716
|
-
if (!known.has(k)) parsed.extra[k] = v;
|
|
5717
|
-
}
|
|
5718
|
-
return parsed;
|
|
5719
|
-
}
|
|
5720
|
-
function serializeGoalState(s) {
|
|
5721
|
-
const obj = { ...s.extra, state: s.state };
|
|
5722
|
-
if (s.mergeApproved !== void 0) obj.mergeApproved = s.mergeApproved;
|
|
5723
|
-
if (s.lastDispatchedIssue !== void 0) obj.lastDispatchedIssue = s.lastDispatchedIssue;
|
|
5724
|
-
if (s.createdAt !== void 0) obj.createdAt = s.createdAt;
|
|
5725
|
-
if (s.startedAt !== void 0) obj.startedAt = s.startedAt;
|
|
5726
|
-
if (s.updatedAt !== void 0) obj.updatedAt = s.updatedAt;
|
|
5727
|
-
return `${JSON.stringify(obj, null, 2)}
|
|
5728
|
-
`;
|
|
5729
|
-
}
|
|
5730
|
-
function nowIso() {
|
|
5731
|
-
return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
5732
|
-
}
|
|
5733
|
-
var VALID_STATES, GoalStateError;
|
|
5734
|
-
var init_state2 = __esm({
|
|
5735
|
-
"src/goal/state.ts"() {
|
|
5736
|
-
"use strict";
|
|
5737
|
-
VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "awaiting-merge", "done"]);
|
|
5738
|
-
GoalStateError = class extends Error {
|
|
5739
|
-
constructor(path45, message) {
|
|
5740
|
-
super(`Invalid goal state at ${path45}:
|
|
5741
|
-
${message}`);
|
|
5742
|
-
this.path = path45;
|
|
5743
|
-
this.name = "GoalStateError";
|
|
5744
|
-
}
|
|
5745
|
-
path;
|
|
5746
|
-
};
|
|
5747
|
-
}
|
|
5748
|
-
});
|
|
5749
|
-
|
|
5750
|
-
// src/goal/stateStore.ts
|
|
5751
|
-
function statePath(goalId) {
|
|
5752
|
-
return `.kody/goals/${goalId}/state.json`;
|
|
5753
|
-
}
|
|
5754
|
-
function is4042(err) {
|
|
5755
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
5756
|
-
return /HTTP 404/i.test(msg) || /Not Found/i.test(msg);
|
|
5757
|
-
}
|
|
5758
|
-
function fetchGoalState(owner, repo, goalId, cwd) {
|
|
5759
|
-
const filePath = statePath(goalId);
|
|
5760
|
-
let raw;
|
|
5761
|
-
try {
|
|
5762
|
-
raw = gh(["api", `/repos/${owner}/${repo}/contents/${filePath}?ref=${STATE_BRANCH}`], { cwd });
|
|
5763
|
-
} catch (err) {
|
|
5764
|
-
if (is4042(err)) return null;
|
|
5765
|
-
throw err;
|
|
5766
|
-
}
|
|
5767
|
-
const o = JSON.parse(raw);
|
|
5768
|
-
if (!o.content) return null;
|
|
5769
|
-
const decoded = Buffer.from(o.content, "base64").toString("utf-8");
|
|
5770
|
-
return parseGoalState(filePath, JSON.parse(decoded));
|
|
5771
|
-
}
|
|
5772
|
-
function putGoalState(owner, repo, goalId, state, message, cwd) {
|
|
5773
|
-
ensureStateBranch(owner, repo, cwd);
|
|
5774
|
-
const filePath = statePath(goalId);
|
|
5775
|
-
const content = Buffer.from(serializeGoalState(state), "utf-8").toString("base64");
|
|
5776
|
-
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
5777
|
-
let sha;
|
|
5778
|
-
try {
|
|
5779
|
-
const cur = gh(["api", `/repos/${owner}/${repo}/contents/${filePath}?ref=${STATE_BRANCH}`], { cwd });
|
|
5780
|
-
const o = JSON.parse(cur);
|
|
5781
|
-
if (o.sha) sha = o.sha;
|
|
5782
|
-
} catch (err) {
|
|
5783
|
-
if (!is4042(err)) throw err;
|
|
5784
|
-
}
|
|
5785
|
-
const payload = { message, content, branch: STATE_BRANCH };
|
|
5786
|
-
if (sha) payload.sha = sha;
|
|
5787
|
-
try {
|
|
5788
|
-
gh(["api", "--method", "PUT", `/repos/${owner}/${repo}/contents/${filePath}`, "--input", "-"], {
|
|
5789
|
-
cwd,
|
|
5790
|
-
input: JSON.stringify(payload)
|
|
5791
|
-
});
|
|
5792
|
-
return;
|
|
5793
|
-
} catch (err) {
|
|
5794
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
5795
|
-
const conflict = /HTTP 409/i.test(msg) || /HTTP 422/i.test(msg) || /does not match|but expected/i.test(msg);
|
|
5796
|
-
if (!conflict || attempt === 3) throw err;
|
|
5797
|
-
}
|
|
5798
|
-
}
|
|
5799
|
-
}
|
|
5800
|
-
var init_stateStore = __esm({
|
|
5801
|
-
"src/goal/stateStore.ts"() {
|
|
5802
|
-
"use strict";
|
|
5803
|
-
init_issue();
|
|
5804
|
-
init_stateBranch();
|
|
5805
|
-
init_state2();
|
|
5806
|
-
}
|
|
5807
|
-
});
|
|
5808
|
-
|
|
5809
6210
|
// src/scripts/commitGoalState.ts
|
|
5810
6211
|
function describeCommitMessage(goal) {
|
|
5811
|
-
if (goal.state === "
|
|
5812
|
-
|
|
5813
|
-
if (goal.state === "done") return `chore(goals): mark ${goal.id} done`;
|
|
5814
|
-
if (goal.lastDispatchedIssue !== void 0) {
|
|
5815
|
-
return `chore(goals): dispatched #${goal.lastDispatchedIssue} for ${goal.id}`;
|
|
5816
|
-
}
|
|
5817
|
-
if (goal.phase === "in-flight") {
|
|
5818
|
-
return `chore(goals): tick ${goal.id} (waiting for in-flight task)`;
|
|
5819
|
-
}
|
|
5820
|
-
return `chore(goals): tick ${goal.id} (idle)`;
|
|
6212
|
+
if (goal.state === "done") return `chore(goals): complete ${goal.id}`;
|
|
6213
|
+
return `chore(goals): update ${goal.id}`;
|
|
5821
6214
|
}
|
|
5822
|
-
var
|
|
6215
|
+
var STATE_BRANCH_LABEL, commitGoalState;
|
|
5823
6216
|
var init_commitGoalState = __esm({
|
|
5824
6217
|
"src/scripts/commitGoalState.ts"() {
|
|
5825
6218
|
"use strict";
|
|
5826
6219
|
init_stateStore();
|
|
6220
|
+
STATE_BRANCH_LABEL = "kody-state";
|
|
5827
6221
|
commitGoalState = async (ctx) => {
|
|
5828
6222
|
const goal = ctx.data.goal;
|
|
5829
6223
|
if (!goal) return;
|
|
@@ -5833,7 +6227,7 @@ var init_commitGoalState = __esm({
|
|
|
5833
6227
|
const owner = ctx.config.github?.owner;
|
|
5834
6228
|
const repo = ctx.config.github?.repo;
|
|
5835
6229
|
if (!owner || !repo) {
|
|
5836
|
-
process.stderr.write(`[goal-
|
|
6230
|
+
process.stderr.write(`[goal-manager] commitGoalState: missing github owner/repo; cannot persist ${goal.id}
|
|
5837
6231
|
`);
|
|
5838
6232
|
return;
|
|
5839
6233
|
}
|
|
@@ -5841,12 +6235,11 @@ var init_commitGoalState = __esm({
|
|
|
5841
6235
|
putGoalState(owner, repo, goal.id, updated, describeCommitMessage(goal), ctx.cwd);
|
|
5842
6236
|
} catch (err) {
|
|
5843
6237
|
process.stderr.write(
|
|
5844
|
-
`[goal-
|
|
6238
|
+
`[goal-manager] commitGoalState: persist to ${STATE_BRANCH_LABEL} failed (${err instanceof Error ? err.message : String(err)}); will retry next tick
|
|
5845
6239
|
`
|
|
5846
6240
|
);
|
|
5847
6241
|
}
|
|
5848
6242
|
};
|
|
5849
|
-
STATE_BRANCH_LABEL = "kody-state";
|
|
5850
6243
|
}
|
|
5851
6244
|
});
|
|
5852
6245
|
|
|
@@ -6301,6 +6694,25 @@ function createTaskIssue(finding, goalId, manifestNumber, cwd) {
|
|
|
6301
6694
|
if (!m) throw new Error(`gh issue create returned unexpected output: ${out}`);
|
|
6302
6695
|
return { number: Number(m[1]), url };
|
|
6303
6696
|
}
|
|
6697
|
+
function buildQaManagedGoal(goalId, verdict, opened, failed) {
|
|
6698
|
+
return {
|
|
6699
|
+
type: "qa",
|
|
6700
|
+
destination: {
|
|
6701
|
+
outcome: `QA findings filed for ${goalId}`,
|
|
6702
|
+
evidence: ["qaFindingsFiled"]
|
|
6703
|
+
},
|
|
6704
|
+
duties: ["qa-goal"],
|
|
6705
|
+
route: [],
|
|
6706
|
+
stage: "filed",
|
|
6707
|
+
facts: {
|
|
6708
|
+
qaFindingsFiled: true,
|
|
6709
|
+
verdict,
|
|
6710
|
+
findingsOpened: opened,
|
|
6711
|
+
findingsFailed: failed
|
|
6712
|
+
},
|
|
6713
|
+
blockers: failed > 0 ? [`${failed} QA finding issue(s) failed to open`] : []
|
|
6714
|
+
};
|
|
6715
|
+
}
|
|
6304
6716
|
async function promoteReportToGoal(ctx, finalText, scopeArg, explicitGoalArg) {
|
|
6305
6717
|
const { markdown, data, jsonError } = splitReport(finalText);
|
|
6306
6718
|
const verdict = detectVerdict(markdown);
|
|
@@ -6404,7 +6816,13 @@ ${markdown}`, ctx.cwd);
|
|
|
6404
6816
|
}
|
|
6405
6817
|
}
|
|
6406
6818
|
const now = nowIso();
|
|
6407
|
-
const
|
|
6819
|
+
const managedGoal = buildQaManagedGoal(goalId, verdict, opened.length, failed.length);
|
|
6820
|
+
const goalState = {
|
|
6821
|
+
state: "active",
|
|
6822
|
+
startedAt: now,
|
|
6823
|
+
updatedAt: now,
|
|
6824
|
+
extra: { version: 1, ...managedGoal }
|
|
6825
|
+
};
|
|
6408
6826
|
try {
|
|
6409
6827
|
putGoalState(
|
|
6410
6828
|
ctx.config.github.owner,
|
|
@@ -6415,377 +6833,104 @@ ${markdown}`, ctx.cwd);
|
|
|
6415
6833
|
ctx.cwd
|
|
6416
6834
|
);
|
|
6417
6835
|
} catch (err) {
|
|
6418
|
-
process.stderr.write(
|
|
6419
|
-
`[createQaGoal] failed to persist goal state to kody-state: ${err instanceof Error ? err.message : String(err)}
|
|
6420
|
-
[createQaGoal] goal-scheduler will not see ${goalId} until this succeeds.
|
|
6421
|
-
`
|
|
6422
|
-
);
|
|
6423
|
-
}
|
|
6424
|
-
const repoUrl = `https://github.com/${ctx.config.github.owner}/${ctx.config.github.repo}`;
|
|
6425
|
-
if (manifestIssueNumber !== null) {
|
|
6426
|
-
const verb = manifestUpdated ? manifestCreated ? "OPENED" : "UPDATED" : "TARGETED";
|
|
6427
|
-
process.stdout.write(
|
|
6428
|
-
`
|
|
6429
|
-
QA_GOAL_${verb}=${repoUrl}/issues/${manifestIssueNumber} (id: ${goalId}, verdict: ${verdict})
|
|
6430
|
-
`
|
|
6431
|
-
);
|
|
6432
|
-
} else {
|
|
6433
|
-
process.stdout.write(`
|
|
6434
|
-
QA_GOAL_TARGETED=(no manifest issue) (id: ${goalId}, verdict: ${verdict})
|
|
6435
|
-
`);
|
|
6436
|
-
}
|
|
6437
|
-
for (const o of opened) {
|
|
6438
|
-
process.stdout.write(`QA_FINDING_OPENED=${o.url} (severity: ${o.severity})
|
|
6439
|
-
`);
|
|
6440
|
-
}
|
|
6441
|
-
if (failed.length > 0) {
|
|
6442
|
-
process.stdout.write(`QA_FINDINGS_FAILED=${failed.length} (see stderr above)
|
|
6443
|
-
`);
|
|
6444
|
-
}
|
|
6445
|
-
ctx.data.action = qaAction(verdict, {
|
|
6446
|
-
goalId,
|
|
6447
|
-
manifestIssue: manifestIssueNumber ?? void 0,
|
|
6448
|
-
findingsOpened: opened.length,
|
|
6449
|
-
findingsFailed: failed.length,
|
|
6450
|
-
mode: explicitGoal ? "goal-attach" : manifestCreated ? "goal-create" : "goal-append"
|
|
6451
|
-
});
|
|
6452
|
-
ctx.output.exitCode = verdict === "FAIL" ? 1 : 0;
|
|
6453
|
-
}
|
|
6454
|
-
var MANIFEST_LABEL, MANIFEST_TITLE, MANIFEST_START, MANIFEST_END, FINDING_LABEL, REPORT_JSON_OPEN, REPORT_JSON_CLOSE, SEVERITY_COLORS, createQaGoal;
|
|
6455
|
-
var init_createQaGoal = __esm({
|
|
6456
|
-
"src/scripts/createQaGoal.ts"() {
|
|
6457
|
-
"use strict";
|
|
6458
|
-
init_state2();
|
|
6459
|
-
init_stateStore();
|
|
6460
|
-
init_issue();
|
|
6461
|
-
init_postReviewResult();
|
|
6462
|
-
MANIFEST_LABEL = "kody:goals-manifest";
|
|
6463
|
-
MANIFEST_TITLE = "Kody Goals Manifest";
|
|
6464
|
-
MANIFEST_START = "<!-- kody-goals-start -->";
|
|
6465
|
-
MANIFEST_END = "<!-- kody-goals-end -->";
|
|
6466
|
-
FINDING_LABEL = "kody:qa-finding";
|
|
6467
|
-
REPORT_JSON_OPEN = "<!-- KODY_QA_REPORT_JSON";
|
|
6468
|
-
REPORT_JSON_CLOSE = "-->";
|
|
6469
|
-
SEVERITY_COLORS = {
|
|
6470
|
-
P0: "b60205",
|
|
6471
|
-
P1: "d93f0b",
|
|
6472
|
-
P2: "fbca04",
|
|
6473
|
-
P3: "0e8a16"
|
|
6474
|
-
};
|
|
6475
|
-
createQaGoal = async (ctx, _profile, agentResult) => {
|
|
6476
|
-
if (!agentResult || agentResult.outcome !== "completed") {
|
|
6477
|
-
const reason = agentResult?.error ?? "agent did not complete";
|
|
6478
|
-
process.stderr.write(`[createQaGoal] ${reason}
|
|
6479
|
-
`);
|
|
6480
|
-
ctx.output.exitCode = 1;
|
|
6481
|
-
ctx.output.reason = reason;
|
|
6482
|
-
ctx.data.action = failedAction2(reason);
|
|
6483
|
-
return;
|
|
6484
|
-
}
|
|
6485
|
-
const finalText = agentResult.finalText.trim();
|
|
6486
|
-
if (!finalText) {
|
|
6487
|
-
process.stderr.write("[createQaGoal] agent produced no report body\n");
|
|
6488
|
-
ctx.output.exitCode = 1;
|
|
6489
|
-
ctx.output.reason = "empty report body";
|
|
6490
|
-
ctx.data.action = failedAction2("empty report body");
|
|
6491
|
-
return;
|
|
6492
|
-
}
|
|
6493
|
-
const { markdown } = splitReport(finalText);
|
|
6494
|
-
const verdict = detectVerdict(markdown);
|
|
6495
|
-
const existingIssue = ctx.args.issue;
|
|
6496
|
-
if (typeof existingIssue === "number" && existingIssue > 0) {
|
|
6497
|
-
try {
|
|
6498
|
-
postIssueComment(existingIssue, finalText, ctx.cwd);
|
|
6499
|
-
} catch (err) {
|
|
6500
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
6501
|
-
ctx.output.exitCode = 4;
|
|
6502
|
-
ctx.output.reason = `failed to comment on issue #${existingIssue}: ${msg}`;
|
|
6503
|
-
ctx.data.action = failedAction2(ctx.output.reason);
|
|
6504
|
-
return;
|
|
6505
|
-
}
|
|
6506
|
-
process.stdout.write(
|
|
6507
|
-
`
|
|
6508
|
-
QA_REPORT_POSTED=https://github.com/${ctx.config.github.owner}/${ctx.config.github.repo}/issues/${existingIssue} (verdict: ${verdict})
|
|
6509
|
-
`
|
|
6510
|
-
);
|
|
6511
|
-
ctx.data.action = qaAction(verdict, { issueNumber: existingIssue, mode: "comment" });
|
|
6512
|
-
ctx.output.exitCode = verdict === "FAIL" ? 1 : 0;
|
|
6513
|
-
return;
|
|
6514
|
-
}
|
|
6515
|
-
await promoteReportToGoal(ctx, finalText, ctx.args.scope, ctx.args.goal);
|
|
6516
|
-
};
|
|
6517
|
-
}
|
|
6518
|
-
});
|
|
6519
|
-
|
|
6520
|
-
// src/goal/labels.ts
|
|
6521
|
-
function goalLabel(goalId) {
|
|
6522
|
-
return `goal:${goalId}`;
|
|
6523
|
-
}
|
|
6524
|
-
var QA_GATE_LABEL;
|
|
6525
|
-
var init_labels = __esm({
|
|
6526
|
-
"src/goal/labels.ts"() {
|
|
6527
|
-
"use strict";
|
|
6528
|
-
QA_GATE_LABEL = "kody:qa-gate";
|
|
6529
|
-
}
|
|
6530
|
-
});
|
|
6531
|
-
|
|
6532
|
-
// src/goal/operations.ts
|
|
6533
|
-
function fail(err) {
|
|
6534
|
-
if (err instanceof Error) {
|
|
6535
|
-
const lines = err.message.split("\n").filter(Boolean);
|
|
6536
|
-
return { ok: false, error: lines[0] ?? err.message };
|
|
6537
|
-
}
|
|
6538
|
-
return { ok: false, error: String(err) };
|
|
6539
|
-
}
|
|
6540
|
-
function listGoalIssues(goalId, cwd) {
|
|
6541
|
-
try {
|
|
6542
|
-
const out = gh(
|
|
6543
|
-
[
|
|
6544
|
-
"api",
|
|
6545
|
-
`repos/{owner}/{repo}/issues?labels=${goalLabel(goalId)}&state=all&per_page=100`,
|
|
6546
|
-
"--jq",
|
|
6547
|
-
`[.[] | select(.pull_request == null) | {number, state: (.state | ascii_upcase), isQaGate: ([.labels[].name] | any(. == "${QA_GATE_LABEL}"))}]`
|
|
6548
|
-
],
|
|
6549
|
-
{ cwd }
|
|
6550
|
-
);
|
|
6551
|
-
return { ok: true, value: JSON.parse(out) };
|
|
6552
|
-
} catch (err) {
|
|
6553
|
-
return fail(err);
|
|
6554
|
-
}
|
|
6555
|
-
}
|
|
6556
|
-
function listOpenPrs(cwd) {
|
|
6557
|
-
try {
|
|
6558
|
-
const out = gh(
|
|
6559
|
-
["pr", "list", "--state", "open", "--limit", "200", "--json", "number,url,isDraft,headRefName,baseRefName,body"],
|
|
6560
|
-
{ cwd }
|
|
6561
|
-
);
|
|
6562
|
-
return { ok: true, value: JSON.parse(out) };
|
|
6563
|
-
} catch (err) {
|
|
6564
|
-
return fail(err);
|
|
6565
|
-
}
|
|
6566
|
-
}
|
|
6567
|
-
function pairIssuesWithPrs(issues, openPrs) {
|
|
6568
|
-
const prByIssue = /* @__PURE__ */ new Map();
|
|
6569
|
-
for (const pr of openPrs) claimPrForIssue(pr, prByIssue);
|
|
6570
|
-
return issues.map((i) => {
|
|
6571
|
-
const pr = prByIssue.get(i.number);
|
|
6572
|
-
let prState = "absent";
|
|
6573
|
-
if (pr) prState = pr.isDraft ? "draft" : "ready";
|
|
6574
|
-
return { number: i.number, state: i.state, prState, isQaGate: i.isQaGate };
|
|
6575
|
-
});
|
|
6576
|
-
}
|
|
6577
|
-
function claimPrForIssue(pr, prByIssue) {
|
|
6578
|
-
for (const issueNum of extractClosesIssues(pr.body)) {
|
|
6579
|
-
if (!prByIssue.has(issueNum)) {
|
|
6580
|
-
prByIssue.set(issueNum, pr);
|
|
6581
|
-
return;
|
|
6582
|
-
}
|
|
6583
|
-
}
|
|
6584
|
-
const headMatch = pr.headRefName.match(/^(\d+)-/);
|
|
6585
|
-
if (headMatch) {
|
|
6586
|
-
const n = Number.parseInt(headMatch[1], 10);
|
|
6587
|
-
if (Number.isFinite(n) && !prByIssue.has(n)) prByIssue.set(n, pr);
|
|
6588
|
-
}
|
|
6589
|
-
}
|
|
6590
|
-
function extractClosesIssues(body) {
|
|
6591
|
-
const out = [];
|
|
6592
|
-
const re = /\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s+#(\d+)\b/gi;
|
|
6593
|
-
for (let m = re.exec(body); m !== null; m = re.exec(body)) {
|
|
6594
|
-
const n = Number.parseInt(m[1], 10);
|
|
6595
|
-
if (Number.isFinite(n)) out.push(n);
|
|
6596
|
-
}
|
|
6597
|
-
return out;
|
|
6598
|
-
}
|
|
6599
|
-
function pickLeafPr(prs) {
|
|
6600
|
-
if (prs.length === 0) return void 0;
|
|
6601
|
-
const bases = new Set(prs.map((p) => p.baseRefName));
|
|
6602
|
-
const leaves = prs.filter((p) => !bases.has(p.headRefName));
|
|
6603
|
-
return leaves.sort((a, b) => b.number - a.number)[0];
|
|
6604
|
-
}
|
|
6605
|
-
function commentOnIssue(issueNumber, body, cwd) {
|
|
6606
|
-
try {
|
|
6607
|
-
gh(["issue", "comment", String(issueNumber), "--body", body], { cwd });
|
|
6608
|
-
return { ok: true };
|
|
6609
|
-
} catch (err) {
|
|
6610
|
-
return fail(err);
|
|
6611
|
-
}
|
|
6612
|
-
}
|
|
6613
|
-
function dispatchTaskRun(issueNumber, base, cwd) {
|
|
6614
|
-
try {
|
|
6615
|
-
gh(
|
|
6616
|
-
[
|
|
6617
|
-
"workflow",
|
|
6618
|
-
"run",
|
|
6619
|
-
GOAL_TASK_WORKFLOW,
|
|
6620
|
-
"-f",
|
|
6621
|
-
`issue_number=${issueNumber}`,
|
|
6622
|
-
"-f",
|
|
6623
|
-
"duty=classify",
|
|
6624
|
-
"-f",
|
|
6625
|
-
`base=${base}`
|
|
6626
|
-
],
|
|
6627
|
-
{ cwd }
|
|
6628
|
-
);
|
|
6629
|
-
return { ok: true };
|
|
6630
|
-
} catch (err) {
|
|
6631
|
-
return fail(err);
|
|
6632
|
-
}
|
|
6633
|
-
}
|
|
6634
|
-
function closeIssue(issueNumber, options, cwd) {
|
|
6635
|
-
try {
|
|
6636
|
-
if (options.comment) {
|
|
6637
|
-
gh(["issue", "comment", String(issueNumber), "--body", options.comment], { cwd });
|
|
6638
|
-
}
|
|
6639
|
-
const args = ["issue", "close", String(issueNumber)];
|
|
6640
|
-
if (options.reason) args.push("--reason", options.reason);
|
|
6641
|
-
gh(args, { cwd });
|
|
6642
|
-
return { ok: true };
|
|
6643
|
-
} catch (err) {
|
|
6644
|
-
return fail(err);
|
|
6645
|
-
}
|
|
6646
|
-
}
|
|
6647
|
-
function closePr(prNumber, comment, cwd) {
|
|
6648
|
-
try {
|
|
6649
|
-
gh(["pr", "close", String(prNumber), "--comment", comment], { cwd });
|
|
6650
|
-
return { ok: true };
|
|
6651
|
-
} catch (err) {
|
|
6652
|
-
return fail(err);
|
|
6653
|
-
}
|
|
6654
|
-
}
|
|
6655
|
-
function mergePrSquash(prNumber, cwd) {
|
|
6656
|
-
try {
|
|
6657
|
-
gh(["pr", "merge", String(prNumber), "--squash", "--delete-branch"], { cwd });
|
|
6658
|
-
return { ok: true };
|
|
6659
|
-
} catch (err) {
|
|
6660
|
-
return fail(err);
|
|
6661
|
-
}
|
|
6662
|
-
}
|
|
6663
|
-
function editPrBase(prNumber, baseBranch, cwd) {
|
|
6664
|
-
try {
|
|
6665
|
-
gh(["api", "--method", "PATCH", `repos/{owner}/{repo}/pulls/${prNumber}`, "-f", `base=${baseBranch}`], { cwd });
|
|
6666
|
-
return { ok: true };
|
|
6667
|
-
} catch (err) {
|
|
6668
|
-
return fail(err);
|
|
6669
|
-
}
|
|
6670
|
-
}
|
|
6671
|
-
function branchContains(leafHead, candidateHead, cwd) {
|
|
6672
|
-
if (leafHead === candidateHead) return { ok: true, value: true };
|
|
6673
|
-
try {
|
|
6674
|
-
const out = gh(
|
|
6675
|
-
[
|
|
6676
|
-
"api",
|
|
6677
|
-
`repos/{owner}/{repo}/compare/${encodeURIComponent(leafHead)}...${encodeURIComponent(candidateHead)}`,
|
|
6678
|
-
"--jq",
|
|
6679
|
-
".ahead_by"
|
|
6680
|
-
],
|
|
6681
|
-
{ cwd }
|
|
6682
|
-
);
|
|
6683
|
-
return { ok: true, value: Number.parseInt(out.trim(), 10) === 0 };
|
|
6684
|
-
} catch (err) {
|
|
6685
|
-
return fail(err);
|
|
6836
|
+
process.stderr.write(
|
|
6837
|
+
`[createQaGoal] failed to persist goal state to kody-state: ${err instanceof Error ? err.message : String(err)}
|
|
6838
|
+
[createQaGoal] goal-scheduler will not see ${goalId} until this succeeds.
|
|
6839
|
+
`
|
|
6840
|
+
);
|
|
6686
6841
|
}
|
|
6687
|
-
}
|
|
6688
|
-
|
|
6689
|
-
|
|
6690
|
-
|
|
6691
|
-
|
|
6692
|
-
|
|
6693
|
-
|
|
6842
|
+
const repoUrl = `https://github.com/${ctx.config.github.owner}/${ctx.config.github.repo}`;
|
|
6843
|
+
if (manifestIssueNumber !== null) {
|
|
6844
|
+
const verb = manifestUpdated ? manifestCreated ? "OPENED" : "UPDATED" : "TARGETED";
|
|
6845
|
+
process.stdout.write(
|
|
6846
|
+
`
|
|
6847
|
+
QA_GOAL_${verb}=${repoUrl}/issues/${manifestIssueNumber} (id: ${goalId}, verdict: ${verdict})
|
|
6848
|
+
`
|
|
6849
|
+
);
|
|
6850
|
+
} else {
|
|
6851
|
+
process.stdout.write(`
|
|
6852
|
+
QA_GOAL_TARGETED=(no manifest issue) (id: ${goalId}, verdict: ${verdict})
|
|
6853
|
+
`);
|
|
6694
6854
|
}
|
|
6695
|
-
|
|
6696
|
-
|
|
6697
|
-
|
|
6698
|
-
"src/goal/operations.ts"() {
|
|
6699
|
-
"use strict";
|
|
6700
|
-
init_issue();
|
|
6701
|
-
init_labels();
|
|
6702
|
-
GOAL_TASK_WORKFLOW = "kody.yml";
|
|
6855
|
+
for (const o of opened) {
|
|
6856
|
+
process.stdout.write(`QA_FINDING_OPENED=${o.url} (severity: ${o.severity})
|
|
6857
|
+
`);
|
|
6703
6858
|
}
|
|
6704
|
-
|
|
6705
|
-
|
|
6706
|
-
|
|
6707
|
-
function derivePhase(snap) {
|
|
6708
|
-
if (!snap.lifecycleState) return "missing";
|
|
6709
|
-
if (snap.lifecycleState === "abandoned") return "abandoned";
|
|
6710
|
-
if (snap.lifecycleState === "closed" || snap.lifecycleState === "done") return "terminal";
|
|
6711
|
-
if (snap.lifecycleState === "awaiting-merge") return "awaiting-merge";
|
|
6712
|
-
const tasks = snap.childTasks.filter((t) => !t.isQaGate);
|
|
6713
|
-
const qaGateOpen = snap.childTasks.some((t) => t.isQaGate && t.state === "OPEN");
|
|
6714
|
-
const hasInFlight = tasks.some((t) => t.state === "OPEN" && t.prState === "draft");
|
|
6715
|
-
if (hasInFlight) return "in-flight";
|
|
6716
|
-
if (tasks.length === 0) return "idle";
|
|
6717
|
-
const dispatchable = tasks.some((t) => t.state === "OPEN" && t.prState === "absent");
|
|
6718
|
-
if (dispatchable) return "ready-to-dispatch";
|
|
6719
|
-
const allDone = tasks.every((t) => t.state === "CLOSED" || t.prState === "ready");
|
|
6720
|
-
if (allDone) return qaGateOpen ? "idle" : "all-done";
|
|
6721
|
-
return "idle";
|
|
6722
|
-
}
|
|
6723
|
-
function pickNextDispatchable(snap) {
|
|
6724
|
-
return snap.childTasks.filter((t) => !t.isQaGate && t.state === "OPEN" && t.prState === "absent").sort((a, b) => a.number - b.number)[0];
|
|
6725
|
-
}
|
|
6726
|
-
var init_phase = __esm({
|
|
6727
|
-
"src/goal/phase.ts"() {
|
|
6728
|
-
"use strict";
|
|
6859
|
+
if (failed.length > 0) {
|
|
6860
|
+
process.stdout.write(`QA_FINDINGS_FAILED=${failed.length} (see stderr above)
|
|
6861
|
+
`);
|
|
6729
6862
|
}
|
|
6730
|
-
|
|
6731
|
-
|
|
6732
|
-
|
|
6733
|
-
|
|
6734
|
-
|
|
6735
|
-
|
|
6736
|
-
for (const n of extractClosesIssues(pr.body)) {
|
|
6737
|
-
if (taskSet.has(n)) return true;
|
|
6738
|
-
}
|
|
6739
|
-
const headMatch = pr.headRefName.match(/^(\d+)-/);
|
|
6740
|
-
if (headMatch) {
|
|
6741
|
-
const n = Number.parseInt(headMatch[1], 10);
|
|
6742
|
-
if (Number.isFinite(n) && taskSet.has(n)) return true;
|
|
6743
|
-
}
|
|
6744
|
-
return false;
|
|
6863
|
+
ctx.data.action = qaAction(verdict, {
|
|
6864
|
+
goalId,
|
|
6865
|
+
manifestIssue: manifestIssueNumber ?? void 0,
|
|
6866
|
+
findingsOpened: opened.length,
|
|
6867
|
+
findingsFailed: failed.length,
|
|
6868
|
+
mode: explicitGoal ? "goal-attach" : manifestCreated ? "goal-create" : "goal-append"
|
|
6745
6869
|
});
|
|
6870
|
+
ctx.output.exitCode = verdict === "FAIL" ? 1 : 0;
|
|
6746
6871
|
}
|
|
6747
|
-
var
|
|
6748
|
-
var
|
|
6749
|
-
"src/scripts/
|
|
6872
|
+
var MANIFEST_LABEL, MANIFEST_TITLE, MANIFEST_START, MANIFEST_END, FINDING_LABEL, REPORT_JSON_OPEN, REPORT_JSON_CLOSE, SEVERITY_COLORS, createQaGoal;
|
|
6873
|
+
var init_createQaGoal = __esm({
|
|
6874
|
+
"src/scripts/createQaGoal.ts"() {
|
|
6750
6875
|
"use strict";
|
|
6751
|
-
|
|
6752
|
-
|
|
6753
|
-
|
|
6754
|
-
|
|
6755
|
-
|
|
6756
|
-
|
|
6757
|
-
|
|
6758
|
-
|
|
6876
|
+
init_state2();
|
|
6877
|
+
init_stateStore();
|
|
6878
|
+
init_issue();
|
|
6879
|
+
init_postReviewResult();
|
|
6880
|
+
MANIFEST_LABEL = "kody:goals-manifest";
|
|
6881
|
+
MANIFEST_TITLE = "Kody Goals Manifest";
|
|
6882
|
+
MANIFEST_START = "<!-- kody-goals-start -->";
|
|
6883
|
+
MANIFEST_END = "<!-- kody-goals-end -->";
|
|
6884
|
+
FINDING_LABEL = "kody:qa-finding";
|
|
6885
|
+
REPORT_JSON_OPEN = "<!-- KODY_QA_REPORT_JSON";
|
|
6886
|
+
REPORT_JSON_CLOSE = "-->";
|
|
6887
|
+
SEVERITY_COLORS = {
|
|
6888
|
+
P0: "b60205",
|
|
6889
|
+
P1: "d93f0b",
|
|
6890
|
+
P2: "fbca04",
|
|
6891
|
+
P3: "0e8a16"
|
|
6892
|
+
};
|
|
6893
|
+
createQaGoal = async (ctx, _profile, agentResult) => {
|
|
6894
|
+
if (!agentResult || agentResult.outcome !== "completed") {
|
|
6895
|
+
const reason = agentResult?.error ?? "agent did not complete";
|
|
6896
|
+
process.stderr.write(`[createQaGoal] ${reason}
|
|
6759
6897
|
`);
|
|
6760
|
-
|
|
6761
|
-
|
|
6762
|
-
|
|
6898
|
+
ctx.output.exitCode = 1;
|
|
6899
|
+
ctx.output.reason = reason;
|
|
6900
|
+
ctx.data.action = failedAction2(reason);
|
|
6763
6901
|
return;
|
|
6764
6902
|
}
|
|
6765
|
-
const
|
|
6766
|
-
|
|
6767
|
-
|
|
6768
|
-
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
goal.openTaskPrs = [];
|
|
6772
|
-
goal.phase = "idle";
|
|
6903
|
+
const finalText = agentResult.finalText.trim();
|
|
6904
|
+
if (!finalText) {
|
|
6905
|
+
process.stderr.write("[createQaGoal] agent produced no report body\n");
|
|
6906
|
+
ctx.output.exitCode = 1;
|
|
6907
|
+
ctx.output.reason = "empty report body";
|
|
6908
|
+
ctx.data.action = failedAction2("empty report body");
|
|
6773
6909
|
return;
|
|
6774
6910
|
}
|
|
6775
|
-
const
|
|
6776
|
-
|
|
6777
|
-
|
|
6778
|
-
)
|
|
6779
|
-
|
|
6780
|
-
|
|
6781
|
-
|
|
6782
|
-
|
|
6783
|
-
|
|
6784
|
-
|
|
6785
|
-
|
|
6786
|
-
|
|
6787
|
-
|
|
6788
|
-
|
|
6911
|
+
const { markdown } = splitReport(finalText);
|
|
6912
|
+
const verdict = detectVerdict(markdown);
|
|
6913
|
+
const existingIssue = ctx.args.issue;
|
|
6914
|
+
if (typeof existingIssue === "number" && existingIssue > 0) {
|
|
6915
|
+
try {
|
|
6916
|
+
postIssueComment(existingIssue, finalText, ctx.cwd);
|
|
6917
|
+
} catch (err) {
|
|
6918
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
6919
|
+
ctx.output.exitCode = 4;
|
|
6920
|
+
ctx.output.reason = `failed to comment on issue #${existingIssue}: ${msg}`;
|
|
6921
|
+
ctx.data.action = failedAction2(ctx.output.reason);
|
|
6922
|
+
return;
|
|
6923
|
+
}
|
|
6924
|
+
process.stdout.write(
|
|
6925
|
+
`
|
|
6926
|
+
QA_REPORT_POSTED=https://github.com/${ctx.config.github.owner}/${ctx.config.github.repo}/issues/${existingIssue} (verdict: ${verdict})
|
|
6927
|
+
`
|
|
6928
|
+
);
|
|
6929
|
+
ctx.data.action = qaAction(verdict, { issueNumber: existingIssue, mode: "comment" });
|
|
6930
|
+
ctx.output.exitCode = verdict === "FAIL" ? 1 : 0;
|
|
6931
|
+
return;
|
|
6932
|
+
}
|
|
6933
|
+
await promoteReportToGoal(ctx, finalText, ctx.args.scope, ctx.args.goal);
|
|
6789
6934
|
};
|
|
6790
6935
|
}
|
|
6791
6936
|
});
|
|
@@ -8003,12 +8148,18 @@ function parseDutyFilter(raw) {
|
|
|
8003
8148
|
function filterSlugs(slugs, onlyDuty) {
|
|
8004
8149
|
return onlyDuty ? slugs.filter((slug) => slug === onlyDuty) : slugs;
|
|
8005
8150
|
}
|
|
8006
|
-
function
|
|
8151
|
+
function listActivatedDutySlugs(projectRoot, storeRoot, activeStoreDuties) {
|
|
8007
8152
|
const seen = /* @__PURE__ */ new Set();
|
|
8008
8153
|
const out = [];
|
|
8009
|
-
for (const
|
|
8010
|
-
|
|
8011
|
-
|
|
8154
|
+
for (const slug of listDutyFolderSlugs(projectRoot)) {
|
|
8155
|
+
if (seen.has(slug)) continue;
|
|
8156
|
+
seen.add(slug);
|
|
8157
|
+
out.push(slug);
|
|
8158
|
+
}
|
|
8159
|
+
const active = new Set(activeStoreDuties ?? []);
|
|
8160
|
+
if (storeRoot && active.size > 0) {
|
|
8161
|
+
for (const slug of listDutyFolderSlugs(storeRoot)) {
|
|
8162
|
+
if (!active.has(slug) || seen.has(slug)) continue;
|
|
8012
8163
|
seen.add(slug);
|
|
8013
8164
|
out.push(slug);
|
|
8014
8165
|
}
|
|
@@ -8086,8 +8237,10 @@ var init_dispatchDutyFileTicks = __esm({
|
|
|
8086
8237
|
const onlyDuty = parseDutyFilter(ctx.args.duty);
|
|
8087
8238
|
const jobsPath = path28.join(ctx.cwd, jobsDir);
|
|
8088
8239
|
const storeJobsPath = getCompanyStoreDutiesRoot();
|
|
8089
|
-
const
|
|
8090
|
-
|
|
8240
|
+
const slugs = filterSlugs(
|
|
8241
|
+
listActivatedDutySlugs(jobsPath, storeJobsPath, ctx.config.company?.activeDuties),
|
|
8242
|
+
onlyDuty
|
|
8243
|
+
);
|
|
8091
8244
|
ctx.data.jobSlugCount = slugs.length;
|
|
8092
8245
|
if (slugs.length === 0) {
|
|
8093
8246
|
const filter = onlyDuty ? ` matching ${onlyDuty}` : "";
|
|
@@ -8278,38 +8431,6 @@ var init_dispatchDutyTicks = __esm({
|
|
|
8278
8431
|
}
|
|
8279
8432
|
});
|
|
8280
8433
|
|
|
8281
|
-
// src/scripts/dispatchNextTask.ts
|
|
8282
|
-
var dispatchNextTask;
|
|
8283
|
-
var init_dispatchNextTask = __esm({
|
|
8284
|
-
"src/scripts/dispatchNextTask.ts"() {
|
|
8285
|
-
"use strict";
|
|
8286
|
-
init_operations();
|
|
8287
|
-
init_phase();
|
|
8288
|
-
dispatchNextTask = async (ctx) => {
|
|
8289
|
-
const goal = ctx.data.goal;
|
|
8290
|
-
if (!goal?.childTasks) return;
|
|
8291
|
-
const next = pickNextDispatchable({
|
|
8292
|
-
lifecycleState: goal.state,
|
|
8293
|
-
childTasks: goal.childTasks
|
|
8294
|
-
});
|
|
8295
|
-
if (!next) {
|
|
8296
|
-
process.stdout.write("[goal-tick] no dispatchable task \u2014 idle\n");
|
|
8297
|
-
return;
|
|
8298
|
-
}
|
|
8299
|
-
const base = goal.leafPr?.headRefName ?? goal.defaultBranch;
|
|
8300
|
-
process.stdout.write(`[goal-tick] dispatching #${next.number} via workflow_dispatch (classify, --base ${base})
|
|
8301
|
-
`);
|
|
8302
|
-
const res = dispatchTaskRun(next.number, base, ctx.cwd);
|
|
8303
|
-
if (!res.ok) {
|
|
8304
|
-
process.stderr.write(`[goal-tick] dispatchNextTask: workflow_dispatch on #${next.number} failed: ${res.error}
|
|
8305
|
-
`);
|
|
8306
|
-
return;
|
|
8307
|
-
}
|
|
8308
|
-
goal.lastDispatchedIssue = next.number;
|
|
8309
|
-
};
|
|
8310
|
-
}
|
|
8311
|
-
});
|
|
8312
|
-
|
|
8313
8434
|
// src/scripts/dispatchNextTaskJob.ts
|
|
8314
8435
|
function taskJobToJob(job, issueArg) {
|
|
8315
8436
|
const target = typeof job.target === "number" ? job.target : typeof issueArg === "number" ? issueArg : void 0;
|
|
@@ -8687,133 +8808,6 @@ var init_failOnceTaskJob = __esm({
|
|
|
8687
8808
|
}
|
|
8688
8809
|
});
|
|
8689
8810
|
|
|
8690
|
-
// src/scripts/finalizeGoal.ts
|
|
8691
|
-
function prIssueNumbers(pr) {
|
|
8692
|
-
const nums = new Set(extractClosesIssues(pr.body));
|
|
8693
|
-
const headMatch = pr.headRefName.match(/^(\d+)-/);
|
|
8694
|
-
if (headMatch) {
|
|
8695
|
-
const n = Number.parseInt(headMatch[1], 10);
|
|
8696
|
-
if (Number.isFinite(n)) nums.add(n);
|
|
8697
|
-
}
|
|
8698
|
-
return [...nums];
|
|
8699
|
-
}
|
|
8700
|
-
var finalizeGoal;
|
|
8701
|
-
var init_finalizeGoal = __esm({
|
|
8702
|
-
"src/scripts/finalizeGoal.ts"() {
|
|
8703
|
-
"use strict";
|
|
8704
|
-
init_operations();
|
|
8705
|
-
finalizeGoal = async (ctx) => {
|
|
8706
|
-
const goal = ctx.data.goal;
|
|
8707
|
-
if (!goal) return;
|
|
8708
|
-
process.stdout.write(`[goal-tick] all task(s) done \u2014 preparing deliverable PR for goal ${goal.id}
|
|
8709
|
-
`);
|
|
8710
|
-
const leaf = goal.leafPr;
|
|
8711
|
-
if (!leaf) {
|
|
8712
|
-
process.stdout.write(`[goal-tick] no leaf PR \u2014 marking goal done without a deliverable PR
|
|
8713
|
-
`);
|
|
8714
|
-
goal.state = "done";
|
|
8715
|
-
return;
|
|
8716
|
-
}
|
|
8717
|
-
if (leaf.baseRefName !== goal.defaultBranch) {
|
|
8718
|
-
process.stdout.write(
|
|
8719
|
-
`[goal-tick] retargeting leaf PR #${leaf.number} base ${leaf.baseRefName} \u2192 ${goal.defaultBranch}
|
|
8720
|
-
`
|
|
8721
|
-
);
|
|
8722
|
-
const retarget = editPrBase(leaf.number, goal.defaultBranch, ctx.cwd);
|
|
8723
|
-
if (!retarget.ok) {
|
|
8724
|
-
process.stderr.write(`[goal-tick] finalizeGoal: editPrBase #${leaf.number} failed: ${retarget.error}
|
|
8725
|
-
`);
|
|
8726
|
-
return;
|
|
8727
|
-
}
|
|
8728
|
-
}
|
|
8729
|
-
if (leaf.isDraft) {
|
|
8730
|
-
process.stdout.write(`[goal-tick] promoting draft leaf PR #${leaf.number} \u2192 ready-for-review
|
|
8731
|
-
`);
|
|
8732
|
-
const ready = markPrReady(leaf.number, ctx.cwd);
|
|
8733
|
-
if (!ready.ok) {
|
|
8734
|
-
process.stderr.write(`[goal-tick] finalizeGoal: markPrReady #${leaf.number} failed: ${ready.error}
|
|
8735
|
-
`);
|
|
8736
|
-
return;
|
|
8737
|
-
}
|
|
8738
|
-
}
|
|
8739
|
-
process.stdout.write(
|
|
8740
|
-
`[goal-tick] leaf PR #${leaf.number} is the deliverable (cumulative goal diff vs ${goal.defaultBranch}) \u2014 left open for human merge
|
|
8741
|
-
`
|
|
8742
|
-
);
|
|
8743
|
-
const uncarriedIssues = /* @__PURE__ */ new Set();
|
|
8744
|
-
const others = (goal.openTaskPrs ?? []).filter((p) => p.number !== leaf.number);
|
|
8745
|
-
for (const pr of others) {
|
|
8746
|
-
const contained = branchContains(leaf.headRefName, pr.headRefName, ctx.cwd);
|
|
8747
|
-
if (!contained.ok || contained.value !== true) {
|
|
8748
|
-
const why = contained.ok ? `commits on \`${pr.headRefName}\` are NOT reachable from the deliverable leaf \`${leaf.headRefName}\`` : `could not verify containment (${contained.error})`;
|
|
8749
|
-
process.stderr.write(
|
|
8750
|
-
`[goal-tick] finalizeGoal: NOT closing PR #${pr.number} \u2014 ${why}; leaving it open (broken stack)
|
|
8751
|
-
`
|
|
8752
|
-
);
|
|
8753
|
-
for (const n of prIssueNumbers(pr)) uncarriedIssues.add(n);
|
|
8754
|
-
commentOnIssue(
|
|
8755
|
-
pr.number,
|
|
8756
|
-
`\u26A0\uFE0F _Stacked-PR finalize: this PR's commits are **not** carried by the goal's deliverable PR #${leaf.number} (the stack chain was broken). Leaving this PR open so its work isn't lost \u2014 review and land it manually._`,
|
|
8757
|
-
ctx.cwd
|
|
8758
|
-
);
|
|
8759
|
-
continue;
|
|
8760
|
-
}
|
|
8761
|
-
process.stdout.write(`[goal-tick] closing intermediate stacked PR #${pr.number} (carried by deliverable leaf)
|
|
8762
|
-
`);
|
|
8763
|
-
const closed = closePr(
|
|
8764
|
-
pr.number,
|
|
8765
|
-
`_Stacked-PR finalize: this PR's content is carried by the goal's single deliverable PR #${leaf.number} (open against \`${goal.defaultBranch}\`, awaiting human merge)._`,
|
|
8766
|
-
ctx.cwd
|
|
8767
|
-
);
|
|
8768
|
-
if (!closed.ok) {
|
|
8769
|
-
process.stderr.write(`[goal-tick] finalizeGoal: closePr #${pr.number} failed: ${closed.error}
|
|
8770
|
-
`);
|
|
8771
|
-
}
|
|
8772
|
-
}
|
|
8773
|
-
const leafIssues = new Set(prIssueNumbers(leaf));
|
|
8774
|
-
const openIssues = (goal.childTasks ?? []).filter((t) => t.state === "OPEN");
|
|
8775
|
-
for (const t of openIssues) {
|
|
8776
|
-
if (uncarriedIssues.has(t.number)) {
|
|
8777
|
-
process.stderr.write(
|
|
8778
|
-
`[goal-tick] finalizeGoal: NOT closing task issue #${t.number} \u2014 its PR's work is not carried by the deliverable (broken stack)
|
|
8779
|
-
`
|
|
8780
|
-
);
|
|
8781
|
-
continue;
|
|
8782
|
-
}
|
|
8783
|
-
if (leafIssues.has(t.number)) {
|
|
8784
|
-
process.stdout.write(
|
|
8785
|
-
`[goal-tick] leaving leaf task issue #${t.number} OPEN as the review anchor for deliverable PR #${leaf.number}
|
|
8786
|
-
`
|
|
8787
|
-
);
|
|
8788
|
-
commentOnIssue(
|
|
8789
|
-
t.number,
|
|
8790
|
-
`_Goal \`${goal.id}\` finalized \u2014 this task's PR #${leaf.number} (open against \`${goal.defaultBranch}\`) is the goal's single deliverable and carries every task's changes. This issue stays open as the review anchor; merge PR #${leaf.number} to ship._`,
|
|
8791
|
-
ctx.cwd
|
|
8792
|
-
);
|
|
8793
|
-
continue;
|
|
8794
|
-
}
|
|
8795
|
-
process.stdout.write(
|
|
8796
|
-
`[goal-tick] closing task issue #${t.number} (goal finalized \u2014 carried by PR #${leaf.number})
|
|
8797
|
-
`
|
|
8798
|
-
);
|
|
8799
|
-
const closed = closeIssue(
|
|
8800
|
-
t.number,
|
|
8801
|
-
{
|
|
8802
|
-
comment: `_Goal \`${goal.id}\` finalized \u2014 its single deliverable PR #${leaf.number} (open against \`${goal.defaultBranch}\`) carries this task's changes. Merge that PR to ship._`,
|
|
8803
|
-
reason: "completed"
|
|
8804
|
-
},
|
|
8805
|
-
ctx.cwd
|
|
8806
|
-
);
|
|
8807
|
-
if (!closed.ok) {
|
|
8808
|
-
process.stderr.write(`[goal-tick] finalizeGoal: closeIssue #${t.number} failed: ${closed.error}
|
|
8809
|
-
`);
|
|
8810
|
-
}
|
|
8811
|
-
}
|
|
8812
|
-
goal.state = "done";
|
|
8813
|
-
};
|
|
8814
|
-
}
|
|
8815
|
-
});
|
|
8816
|
-
|
|
8817
8811
|
// src/scripts/finalizeTerminal.ts
|
|
8818
8812
|
var DONE, FAILED, finalizeTerminal;
|
|
8819
8813
|
var init_finalizeTerminal = __esm({
|
|
@@ -9363,58 +9357,6 @@ var init_fixFlow = __esm({
|
|
|
9363
9357
|
}
|
|
9364
9358
|
});
|
|
9365
9359
|
|
|
9366
|
-
// src/scripts/handleAbandonedGoal.ts
|
|
9367
|
-
var handleAbandonedGoal;
|
|
9368
|
-
var init_handleAbandonedGoal = __esm({
|
|
9369
|
-
"src/scripts/handleAbandonedGoal.ts"() {
|
|
9370
|
-
"use strict";
|
|
9371
|
-
init_operations();
|
|
9372
|
-
handleAbandonedGoal = async (ctx) => {
|
|
9373
|
-
const goal = ctx.data.goal;
|
|
9374
|
-
if (!goal || goal.state !== "abandoned") return;
|
|
9375
|
-
process.stdout.write(`[goal-tick] ${goal.id} is abandoned \u2014 running cleanup
|
|
9376
|
-
`);
|
|
9377
|
-
const issues = listGoalIssues(goal.id, ctx.cwd);
|
|
9378
|
-
if (!issues.ok) {
|
|
9379
|
-
process.stderr.write(`[goal-tick] handleAbandonedGoal: list issues failed: ${issues.error}
|
|
9380
|
-
`);
|
|
9381
|
-
} else {
|
|
9382
|
-
const issueNumbers = /* @__PURE__ */ new Set();
|
|
9383
|
-
for (const i of issues.value ?? []) {
|
|
9384
|
-
if (i.state !== "OPEN") continue;
|
|
9385
|
-
issueNumbers.add(i.number);
|
|
9386
|
-
const r = closeIssue(
|
|
9387
|
-
i.number,
|
|
9388
|
-
{
|
|
9389
|
-
comment: "_Goal abandoned \u2014 closing this task without dispatch._",
|
|
9390
|
-
reason: "not planned"
|
|
9391
|
-
},
|
|
9392
|
-
ctx.cwd
|
|
9393
|
-
);
|
|
9394
|
-
if (!r.ok) {
|
|
9395
|
-
process.stderr.write(`[goal-tick] handleAbandonedGoal: failed to close #${i.number}: ${r.error}
|
|
9396
|
-
`);
|
|
9397
|
-
}
|
|
9398
|
-
}
|
|
9399
|
-
const prs = listOpenPrs(ctx.cwd);
|
|
9400
|
-
if (prs.ok) {
|
|
9401
|
-
for (const pr of prs.value ?? []) {
|
|
9402
|
-
const headMatch = pr.headRefName.match(/^(\d+)-/);
|
|
9403
|
-
const headIssue = headMatch ? Number.parseInt(headMatch[1], 10) : NaN;
|
|
9404
|
-
if (!Number.isFinite(headIssue) || !issueNumbers.has(headIssue)) continue;
|
|
9405
|
-
const r = closePr(pr.number, "_Goal abandoned \u2014 closing stacked PR._", ctx.cwd);
|
|
9406
|
-
if (!r.ok) {
|
|
9407
|
-
process.stderr.write(`[goal-tick] handleAbandonedGoal: failed to close PR #${pr.number}: ${r.error}
|
|
9408
|
-
`);
|
|
9409
|
-
}
|
|
9410
|
-
}
|
|
9411
|
-
}
|
|
9412
|
-
}
|
|
9413
|
-
goal.state = "closed";
|
|
9414
|
-
};
|
|
9415
|
-
}
|
|
9416
|
-
});
|
|
9417
|
-
|
|
9418
9360
|
// src/scripts/initFlow.ts
|
|
9419
9361
|
import { execFileSync as execFileSync17 } from "child_process";
|
|
9420
9362
|
import * as fs31 from "fs";
|
|
@@ -9788,7 +9730,7 @@ var init_loadGoalState = __esm({
|
|
|
9788
9730
|
try {
|
|
9789
9731
|
const state = fetchGoalState(owner, repo, goalId, ctx.cwd);
|
|
9790
9732
|
if (!state) {
|
|
9791
|
-
process.stdout.write(`[goal-
|
|
9733
|
+
process.stdout.write(`[goal-manager] no goal state for ${goalId} on ${owner}/${repo}; nothing to tick
|
|
9792
9734
|
`);
|
|
9793
9735
|
ctx.skipAgent = true;
|
|
9794
9736
|
ctx.output.exitCode = 0;
|
|
@@ -9798,17 +9740,11 @@ var init_loadGoalState = __esm({
|
|
|
9798
9740
|
ctx.data.goal = {
|
|
9799
9741
|
id: goalId,
|
|
9800
9742
|
state: state.state,
|
|
9801
|
-
lastDispatchedIssue: state.lastDispatchedIssue,
|
|
9802
|
-
// Cache the full parsed object so saveGoalState can preserve `extra`.
|
|
9803
9743
|
raw: state,
|
|
9804
|
-
// `phase`, `childTasks`, `openTaskPrs`, `leafPr` are populated by
|
|
9805
|
-
// deriveGoalPhase later in the chain. Initialize to undefined so
|
|
9806
|
-
// runWhen on `data.goal.phase` can match correctly.
|
|
9807
|
-
phase: void 0,
|
|
9808
9744
|
defaultBranch: ctx.config.git.defaultBranch
|
|
9809
9745
|
};
|
|
9810
9746
|
} catch (err) {
|
|
9811
|
-
process.stdout.write(`[goal-
|
|
9747
|
+
process.stdout.write(`[goal-manager] ${err instanceof Error ? err.message : String(err)}
|
|
9812
9748
|
`);
|
|
9813
9749
|
ctx.skipAgent = true;
|
|
9814
9750
|
ctx.output.exitCode = 0;
|
|
@@ -9933,23 +9869,7 @@ var init_staff = __esm({
|
|
|
9933
9869
|
"use strict";
|
|
9934
9870
|
init_companyStore();
|
|
9935
9871
|
DEFAULT_STAFF_DIR = ".kody/staff";
|
|
9936
|
-
BUILTIN_PERSONAS = {
|
|
9937
|
-
kody: [
|
|
9938
|
-
"You are **kody**, the repository's autonomous engineer.",
|
|
9939
|
-
"",
|
|
9940
|
-
"- You work the way this repo already works: follow its conventions, its",
|
|
9941
|
-
" existing patterns, and its tests. Read before you write.",
|
|
9942
|
-
"- You ship small, correct, reviewable changes. You don't refactor beyond the",
|
|
9943
|
-
" task, and you don't add scope nobody asked for.",
|
|
9944
|
-
"- You are honest about outcomes: if something failed, didn't run, or you're",
|
|
9945
|
-
" unsure, you say so plainly rather than papering over it.",
|
|
9946
|
-
"- The request that triggered you IS your authorization to do the work:",
|
|
9947
|
-
" opening a PR, pushing commits, and commenting are exactly what you're for \u2014",
|
|
9948
|
-
" do them without waiting for extra approval.",
|
|
9949
|
-
"- You still don't weaken security, delete work you didn't create, or take",
|
|
9950
|
-
" destructive actions beyond the task you were given."
|
|
9951
|
-
].join("\n")
|
|
9952
|
-
};
|
|
9872
|
+
BUILTIN_PERSONAS = {};
|
|
9953
9873
|
}
|
|
9954
9874
|
});
|
|
9955
9875
|
|
|
@@ -10402,6 +10322,37 @@ var init_markFlowSuccess = __esm({
|
|
|
10402
10322
|
}
|
|
10403
10323
|
});
|
|
10404
10324
|
|
|
10325
|
+
// src/goal/operations.ts
|
|
10326
|
+
function fail(err) {
|
|
10327
|
+
if (err instanceof Error) {
|
|
10328
|
+
const lines = err.message.split("\n").filter(Boolean);
|
|
10329
|
+
return { ok: false, error: lines[0] ?? err.message };
|
|
10330
|
+
}
|
|
10331
|
+
return { ok: false, error: String(err) };
|
|
10332
|
+
}
|
|
10333
|
+
function commentOnIssue(issueNumber, body, cwd) {
|
|
10334
|
+
try {
|
|
10335
|
+
gh(["issue", "comment", String(issueNumber), "--body", body], { cwd });
|
|
10336
|
+
return { ok: true };
|
|
10337
|
+
} catch (err) {
|
|
10338
|
+
return fail(err);
|
|
10339
|
+
}
|
|
10340
|
+
}
|
|
10341
|
+
function mergePrSquash(prNumber, cwd) {
|
|
10342
|
+
try {
|
|
10343
|
+
gh(["pr", "merge", String(prNumber), "--squash", "--delete-branch"], { cwd });
|
|
10344
|
+
return { ok: true };
|
|
10345
|
+
} catch (err) {
|
|
10346
|
+
return fail(err);
|
|
10347
|
+
}
|
|
10348
|
+
}
|
|
10349
|
+
var init_operations = __esm({
|
|
10350
|
+
"src/goal/operations.ts"() {
|
|
10351
|
+
"use strict";
|
|
10352
|
+
init_issue();
|
|
10353
|
+
}
|
|
10354
|
+
});
|
|
10355
|
+
|
|
10405
10356
|
// src/scripts/mergeFlow.ts
|
|
10406
10357
|
function readPr(prNumber, cwd) {
|
|
10407
10358
|
const out = gh(["pr", "view", String(prNumber), "--json", "state,isDraft,mergeable,mergeStateStatus,title,url"], {
|
|
@@ -12159,7 +12110,7 @@ var init_previewBuildHelpers = __esm({
|
|
|
12159
12110
|
// src/scripts/previewBuildRun.ts
|
|
12160
12111
|
import { spawn as spawn4 } from "child_process";
|
|
12161
12112
|
async function runCmd(cmd, args, opts = {}) {
|
|
12162
|
-
await new Promise((
|
|
12113
|
+
await new Promise((resolve7, reject) => {
|
|
12163
12114
|
const child = spawn4(cmd, args, {
|
|
12164
12115
|
cwd: opts.cwd,
|
|
12165
12116
|
env: { ...process.env, ...opts.env ?? {} },
|
|
@@ -12171,7 +12122,7 @@ async function runCmd(cmd, args, opts = {}) {
|
|
|
12171
12122
|
}
|
|
12172
12123
|
child.on("error", reject);
|
|
12173
12124
|
child.on("close", (code) => {
|
|
12174
|
-
if (code === 0)
|
|
12125
|
+
if (code === 0) resolve7();
|
|
12175
12126
|
else reject(new Error(`${cmd} ${args.join(" ")} exited ${code}`));
|
|
12176
12127
|
});
|
|
12177
12128
|
});
|
|
@@ -12811,29 +12762,21 @@ var init_runTickScript = __esm({
|
|
|
12811
12762
|
}
|
|
12812
12763
|
});
|
|
12813
12764
|
|
|
12814
|
-
// src/scripts/
|
|
12815
|
-
var
|
|
12816
|
-
var
|
|
12817
|
-
"src/scripts/
|
|
12765
|
+
// src/scripts/saveManagedGoalState.ts
|
|
12766
|
+
var saveManagedGoalState;
|
|
12767
|
+
var init_saveManagedGoalState = __esm({
|
|
12768
|
+
"src/scripts/saveManagedGoalState.ts"() {
|
|
12818
12769
|
"use strict";
|
|
12819
12770
|
init_state2();
|
|
12820
|
-
|
|
12771
|
+
saveManagedGoalState = async (ctx) => {
|
|
12772
|
+
ctx.skipAgent = true;
|
|
12821
12773
|
const goal = ctx.data.goal;
|
|
12822
|
-
if (!goal)
|
|
12823
|
-
|
|
12824
|
-
|
|
12825
|
-
}
|
|
12826
|
-
const prev = goal.raw;
|
|
12827
|
-
const changed = !prev || prev.state !== goal.state || prev.lastDispatchedIssue !== goal.lastDispatchedIssue;
|
|
12828
|
-
const updated = {
|
|
12829
|
-
...prev ?? { state: goal.state, extra: {} },
|
|
12830
|
-
state: goal.state,
|
|
12831
|
-
lastDispatchedIssue: goal.lastDispatchedIssue,
|
|
12832
|
-
updatedAt: changed ? nowIso() : prev?.updatedAt
|
|
12833
|
-
};
|
|
12774
|
+
if (!goal?.raw) return;
|
|
12775
|
+
const original = typeof ctx.data.goalOriginalStateText === "string" ? ctx.data.goalOriginalStateText : "";
|
|
12776
|
+
const changedBeforeTimestamp = original !== serializeGoalState(goal.raw);
|
|
12777
|
+
const updated = changedBeforeTimestamp ? { ...goal.raw, updatedAt: nowIso() } : goal.raw;
|
|
12834
12778
|
ctx.data.goalPersistState = updated;
|
|
12835
|
-
ctx.data.goalPersistChanged =
|
|
12836
|
-
ctx.skipAgent = true;
|
|
12779
|
+
ctx.data.goalPersistChanged = original !== serializeGoalState(updated);
|
|
12837
12780
|
};
|
|
12838
12781
|
}
|
|
12839
12782
|
});
|
|
@@ -13125,7 +13068,7 @@ function stripAnsi2(s) {
|
|
|
13125
13068
|
return s.replace(ANSI_RE2, "");
|
|
13126
13069
|
}
|
|
13127
13070
|
function runCommand2(command, cwd) {
|
|
13128
|
-
return new Promise((
|
|
13071
|
+
return new Promise((resolve7) => {
|
|
13129
13072
|
const child = spawn5(command, {
|
|
13130
13073
|
cwd,
|
|
13131
13074
|
shell: true,
|
|
@@ -13152,11 +13095,11 @@ function runCommand2(command, cwd) {
|
|
|
13152
13095
|
}, TEST_TIMEOUT_MS);
|
|
13153
13096
|
child.on("exit", (code) => {
|
|
13154
13097
|
clearTimeout(timer);
|
|
13155
|
-
|
|
13098
|
+
resolve7({ exitCode: code ?? -1, output: Buffer.concat(buffers).toString("utf-8") });
|
|
13156
13099
|
});
|
|
13157
13100
|
child.on("error", (err) => {
|
|
13158
13101
|
clearTimeout(timer);
|
|
13159
|
-
|
|
13102
|
+
resolve7({ exitCode: -1, output: err.message });
|
|
13160
13103
|
});
|
|
13161
13104
|
});
|
|
13162
13105
|
}
|
|
@@ -13561,21 +13504,21 @@ function lineStream(stream) {
|
|
|
13561
13504
|
tryDeliver();
|
|
13562
13505
|
});
|
|
13563
13506
|
return {
|
|
13564
|
-
next: (timeoutMs) => new Promise((
|
|
13507
|
+
next: (timeoutMs) => new Promise((resolve7) => {
|
|
13565
13508
|
if (queue.length > 0) {
|
|
13566
|
-
|
|
13509
|
+
resolve7(queue.shift());
|
|
13567
13510
|
return;
|
|
13568
13511
|
}
|
|
13569
13512
|
if (ended) {
|
|
13570
|
-
|
|
13513
|
+
resolve7(null);
|
|
13571
13514
|
return;
|
|
13572
13515
|
}
|
|
13573
|
-
waiter =
|
|
13516
|
+
waiter = resolve7;
|
|
13574
13517
|
const t = setTimeout(
|
|
13575
13518
|
() => {
|
|
13576
|
-
if (waiter ===
|
|
13519
|
+
if (waiter === resolve7) {
|
|
13577
13520
|
waiter = null;
|
|
13578
|
-
|
|
13521
|
+
resolve7(null);
|
|
13579
13522
|
}
|
|
13580
13523
|
},
|
|
13581
13524
|
Math.max(0, timeoutMs)
|
|
@@ -13756,7 +13699,9 @@ var init_scripts = __esm({
|
|
|
13756
13699
|
"use strict";
|
|
13757
13700
|
init_abortUnfinishedGitOps();
|
|
13758
13701
|
init_advanceFlow();
|
|
13702
|
+
init_advanceManagedGoal();
|
|
13759
13703
|
init_appendCompanyActivity();
|
|
13704
|
+
init_applyDutyReports();
|
|
13760
13705
|
init_buildSyntheticPlugin();
|
|
13761
13706
|
init_checkCoverageWithRetry();
|
|
13762
13707
|
init_classifyByLabel();
|
|
@@ -13764,7 +13709,6 @@ var init_scripts = __esm({
|
|
|
13764
13709
|
init_commitGoalState();
|
|
13765
13710
|
init_composePrompt();
|
|
13766
13711
|
init_createQaGoal();
|
|
13767
|
-
init_deriveGoalPhase();
|
|
13768
13712
|
init_deriveQaScopeFromIssue();
|
|
13769
13713
|
init_diagMcp();
|
|
13770
13714
|
init_discoverQaContext();
|
|
@@ -13772,16 +13716,13 @@ var init_scripts = __esm({
|
|
|
13772
13716
|
init_dispatchClassified();
|
|
13773
13717
|
init_dispatchDutyFileTicks();
|
|
13774
13718
|
init_dispatchDutyTicks();
|
|
13775
|
-
init_dispatchNextTask();
|
|
13776
13719
|
init_dispatchNextTaskJob();
|
|
13777
13720
|
init_ensurePr();
|
|
13778
13721
|
init_failOnceTaskJob();
|
|
13779
|
-
init_finalizeGoal();
|
|
13780
13722
|
init_finalizeTerminal();
|
|
13781
13723
|
init_finishFlow();
|
|
13782
13724
|
init_fixCiFlow();
|
|
13783
13725
|
init_fixFlow();
|
|
13784
|
-
init_handleAbandonedGoal();
|
|
13785
13726
|
init_initFlow();
|
|
13786
13727
|
init_loadConventions();
|
|
13787
13728
|
init_loadCoverageRules();
|
|
@@ -13830,7 +13771,7 @@ var init_scripts = __esm({
|
|
|
13830
13771
|
init_runPreviewBuild();
|
|
13831
13772
|
init_runScheduledExecutableTick();
|
|
13832
13773
|
init_runTickScript();
|
|
13833
|
-
|
|
13774
|
+
init_saveManagedGoalState();
|
|
13834
13775
|
init_saveTaskState();
|
|
13835
13776
|
init_setCommentTarget();
|
|
13836
13777
|
init_setLifecycleLabel();
|
|
@@ -13890,12 +13831,9 @@ var init_scripts = __esm({
|
|
|
13890
13831
|
runScheduledExecutableTick,
|
|
13891
13832
|
runTickScript,
|
|
13892
13833
|
runPreviewBuild,
|
|
13834
|
+
advanceManagedGoal,
|
|
13893
13835
|
loadGoalState,
|
|
13894
|
-
|
|
13895
|
-
deriveGoalPhase,
|
|
13896
|
-
dispatchNextTask,
|
|
13897
|
-
finalizeGoal,
|
|
13898
|
-
saveGoalState
|
|
13836
|
+
saveManagedGoalState
|
|
13899
13837
|
};
|
|
13900
13838
|
postflightScripts = {
|
|
13901
13839
|
parseAgentResult: parseAgentResult2,
|
|
@@ -13930,6 +13868,7 @@ var init_scripts = __esm({
|
|
|
13930
13868
|
finalizeTerminal,
|
|
13931
13869
|
advanceFlow,
|
|
13932
13870
|
persistFlowState,
|
|
13871
|
+
applyDutyReports,
|
|
13933
13872
|
recordClassification,
|
|
13934
13873
|
dispatchClassified,
|
|
13935
13874
|
notifyTerminal,
|
|
@@ -14120,6 +14059,8 @@ async function runExecutable(profileName, input) {
|
|
|
14120
14059
|
}
|
|
14121
14060
|
const perExecutableModel = config.agent.perExecutable?.[profileName];
|
|
14122
14061
|
const modelSpec = perExecutableModel ? perExecutableModel : profile.claudeCode.model === "inherit" ? config.agent.model : profile.claudeCode.model;
|
|
14062
|
+
const profileHasThinkingTokens = typeof profile.claudeCode.maxThinkingTokens === "number" && profile.claudeCode.maxThinkingTokens > 0;
|
|
14063
|
+
const reasoningEffort = config.agent.perExecutableReasoningEffort?.[profileName] ?? profile.claudeCode.reasoningEffort ?? (profileHasThinkingTokens ? void 0 : config.agent.reasoningEffort);
|
|
14123
14064
|
let model;
|
|
14124
14065
|
try {
|
|
14125
14066
|
model = parseProviderModel(modelSpec);
|
|
@@ -14195,6 +14136,7 @@ async function runExecutable(profileName, input) {
|
|
|
14195
14136
|
pluginPaths: pluginPaths.length > 0 ? pluginPaths : void 0,
|
|
14196
14137
|
agents,
|
|
14197
14138
|
maxTurns: profile.claudeCode.maxTurns,
|
|
14139
|
+
reasoningEffort,
|
|
14198
14140
|
maxThinkingTokens: profile.claudeCode.maxThinkingTokens,
|
|
14199
14141
|
maxTurnTimeoutMs: typeof profile.claudeCode.maxTurnTimeoutSec === "number" ? Math.floor(profile.claudeCode.maxTurnTimeoutSec * 1e3) : void 0,
|
|
14200
14142
|
// DISCIPLINE leads so the stable, role-agnostic block sits at the front
|
|
@@ -14689,14 +14631,14 @@ async function runShellEntry(entry, ctx, profile) {
|
|
|
14689
14631
|
let killTimer;
|
|
14690
14632
|
let escalateTimer;
|
|
14691
14633
|
const result = await new Promise(
|
|
14692
|
-
(
|
|
14634
|
+
(resolve7) => {
|
|
14693
14635
|
let settled = false;
|
|
14694
14636
|
const settle = (code, signal, spawnErr) => {
|
|
14695
14637
|
if (settled) return;
|
|
14696
14638
|
settled = true;
|
|
14697
14639
|
if (killTimer) clearTimeout(killTimer);
|
|
14698
14640
|
if (escalateTimer) clearTimeout(escalateTimer);
|
|
14699
|
-
|
|
14641
|
+
resolve7({ code, signal, spawnErr });
|
|
14700
14642
|
};
|
|
14701
14643
|
child.on("error", (err) => settle(null, null, err));
|
|
14702
14644
|
child.on("close", (code, signal) => settle(code, signal));
|
|
@@ -14732,6 +14674,11 @@ async function runShellEntry(entry, ctx, profile) {
|
|
|
14732
14674
|
if (prUrlMatch?.[1]) ctx.output.prUrl = prUrlMatch[1].trim();
|
|
14733
14675
|
const reasonMatch = stdout.match(/^KODY_REASON=(.+)$/m);
|
|
14734
14676
|
if (reasonMatch?.[1]) ctx.output.reason = reasonMatch[1].trim();
|
|
14677
|
+
const dutyReports = parseDutyReportsFromText(stdout);
|
|
14678
|
+
if (dutyReports.length > 0) {
|
|
14679
|
+
const prior = Array.isArray(ctx.data.dutyReports) ? ctx.data.dutyReports : [];
|
|
14680
|
+
ctx.data.dutyReports = [...prior, ...dutyReports];
|
|
14681
|
+
}
|
|
14735
14682
|
if (timedOut) {
|
|
14736
14683
|
ctx.skipAgent = true;
|
|
14737
14684
|
const seconds = Math.round(timeoutMs / 1e3);
|
|
@@ -14781,6 +14728,7 @@ var init_executor = __esm({
|
|
|
14781
14728
|
init_config();
|
|
14782
14729
|
init_container();
|
|
14783
14730
|
init_discipline();
|
|
14731
|
+
init_dutyReport();
|
|
14784
14732
|
init_events();
|
|
14785
14733
|
init_lifecycleLabels();
|
|
14786
14734
|
init_litellm();
|
|
@@ -14791,7 +14739,7 @@ var init_executor = __esm({
|
|
|
14791
14739
|
init_subagents();
|
|
14792
14740
|
init_task_artifacts();
|
|
14793
14741
|
init_tools();
|
|
14794
|
-
MUTATING_POSTFLIGHTS = /* @__PURE__ */ new Set(["commitAndPush", "ensurePr"]);
|
|
14742
|
+
MUTATING_POSTFLIGHTS = /* @__PURE__ */ new Set(["commitAndPush", "ensurePr", "applyDutyReports"]);
|
|
14795
14743
|
MAX_CHAIN_HOPS = 60;
|
|
14796
14744
|
DEFAULT_SHELL_TIMEOUT_MS = 3e5;
|
|
14797
14745
|
SIGKILL_GRACE_MS = 5e3;
|
|
@@ -16811,17 +16759,17 @@ function authOk(req, expected) {
|
|
|
16811
16759
|
return false;
|
|
16812
16760
|
}
|
|
16813
16761
|
function readJsonBody(req) {
|
|
16814
|
-
return new Promise((
|
|
16762
|
+
return new Promise((resolve7, reject) => {
|
|
16815
16763
|
const chunks = [];
|
|
16816
16764
|
req.on("data", (c) => chunks.push(c));
|
|
16817
16765
|
req.on("end", () => {
|
|
16818
16766
|
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
16819
16767
|
if (!raw.trim()) {
|
|
16820
|
-
|
|
16768
|
+
resolve7({});
|
|
16821
16769
|
return;
|
|
16822
16770
|
}
|
|
16823
16771
|
try {
|
|
16824
|
-
|
|
16772
|
+
resolve7(JSON.parse(raw));
|
|
16825
16773
|
} catch (err) {
|
|
16826
16774
|
reject(err instanceof Error ? err : new Error(String(err)));
|
|
16827
16775
|
}
|
|
@@ -17070,11 +17018,11 @@ async function brainServe(opts) {
|
|
|
17070
17018
|
model,
|
|
17071
17019
|
litellmUrl
|
|
17072
17020
|
});
|
|
17073
|
-
await new Promise((
|
|
17021
|
+
await new Promise((resolve7) => {
|
|
17074
17022
|
server.listen(port, "0.0.0.0", () => {
|
|
17075
17023
|
process.stdout.write(`[brain-serve] listening on 0.0.0.0:${port} (cwd=${opts.cwd})
|
|
17076
17024
|
`);
|
|
17077
|
-
|
|
17025
|
+
resolve7();
|
|
17078
17026
|
});
|
|
17079
17027
|
});
|
|
17080
17028
|
const shutdown = (signal) => {
|
|
@@ -17324,14 +17272,14 @@ async function startBrainProxy(opts) {
|
|
|
17324
17272
|
const { httpServer, handler } = buildBrainProxy(opts);
|
|
17325
17273
|
const port = opts.port ?? 0;
|
|
17326
17274
|
const host = opts.host ?? "127.0.0.1";
|
|
17327
|
-
await new Promise((
|
|
17275
|
+
await new Promise((resolve7) => httpServer.listen(port, host, () => resolve7()));
|
|
17328
17276
|
const addr = httpServer.address();
|
|
17329
17277
|
return {
|
|
17330
17278
|
httpServer,
|
|
17331
17279
|
port: addr.port,
|
|
17332
17280
|
url: `http://${host}:${addr.port}`,
|
|
17333
|
-
stop: () => new Promise((
|
|
17334
|
-
httpServer.close(() =>
|
|
17281
|
+
stop: () => new Promise((resolve7) => {
|
|
17282
|
+
httpServer.close(() => resolve7());
|
|
17335
17283
|
}),
|
|
17336
17284
|
handler
|
|
17337
17285
|
};
|
|
@@ -17481,23 +17429,23 @@ function buildMcpHttpServer(opts) {
|
|
|
17481
17429
|
httpServer,
|
|
17482
17430
|
routes,
|
|
17483
17431
|
port,
|
|
17484
|
-
stop: () => new Promise((
|
|
17432
|
+
stop: () => new Promise((resolve7) => {
|
|
17485
17433
|
let pending = transports.size;
|
|
17486
17434
|
if (pending === 0) {
|
|
17487
|
-
httpServer.close(() =>
|
|
17435
|
+
httpServer.close(() => resolve7());
|
|
17488
17436
|
return;
|
|
17489
17437
|
}
|
|
17490
17438
|
for (const transport of transports.values()) {
|
|
17491
17439
|
void transport.close().finally(() => {
|
|
17492
17440
|
pending--;
|
|
17493
|
-
if (pending === 0) httpServer.close(() =>
|
|
17441
|
+
if (pending === 0) httpServer.close(() => resolve7());
|
|
17494
17442
|
});
|
|
17495
17443
|
}
|
|
17496
17444
|
})
|
|
17497
17445
|
};
|
|
17498
17446
|
}
|
|
17499
17447
|
function listenMcpHttpServer(server, host = "127.0.0.1") {
|
|
17500
|
-
return new Promise((
|
|
17448
|
+
return new Promise((resolve7, reject) => {
|
|
17501
17449
|
server.httpServer.once("error", reject);
|
|
17502
17450
|
server.httpServer.listen(server.port, host, () => {
|
|
17503
17451
|
server.httpServer.off("error", reject);
|
|
@@ -17505,7 +17453,7 @@ function listenMcpHttpServer(server, host = "127.0.0.1") {
|
|
|
17505
17453
|
if (addr && typeof addr === "object") {
|
|
17506
17454
|
server.port = addr.port;
|
|
17507
17455
|
}
|
|
17508
|
-
|
|
17456
|
+
resolve7();
|
|
17509
17457
|
});
|
|
17510
17458
|
});
|
|
17511
17459
|
}
|
|
@@ -17644,7 +17592,7 @@ async function waitForNextUserMessage(opts) {
|
|
|
17644
17592
|
}
|
|
17645
17593
|
}
|
|
17646
17594
|
function sleep2(ms) {
|
|
17647
|
-
return new Promise((
|
|
17595
|
+
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
17648
17596
|
}
|
|
17649
17597
|
function currentBranch(cwd) {
|
|
17650
17598
|
try {
|
|
@@ -18759,14 +18707,14 @@ function sendJson2(res, status, body) {
|
|
|
18759
18707
|
res.end(JSON.stringify(body));
|
|
18760
18708
|
}
|
|
18761
18709
|
function readJsonBody2(req) {
|
|
18762
|
-
return new Promise((
|
|
18710
|
+
return new Promise((resolve7, reject) => {
|
|
18763
18711
|
const chunks = [];
|
|
18764
18712
|
req.on("data", (c) => chunks.push(c));
|
|
18765
18713
|
req.on("end", () => {
|
|
18766
18714
|
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
18767
|
-
if (!raw.trim()) return
|
|
18715
|
+
if (!raw.trim()) return resolve7({});
|
|
18768
18716
|
try {
|
|
18769
|
-
|
|
18717
|
+
resolve7(JSON.parse(raw));
|
|
18770
18718
|
} catch (err) {
|
|
18771
18719
|
reject(err instanceof Error ? err : new Error(String(err)));
|
|
18772
18720
|
}
|
|
@@ -18892,10 +18840,10 @@ async function poolServe() {
|
|
|
18892
18840
|
}
|
|
18893
18841
|
});
|
|
18894
18842
|
const apiHost = process.env.POOL_API_HOST ?? "::";
|
|
18895
|
-
await new Promise((
|
|
18843
|
+
await new Promise((resolve7) => {
|
|
18896
18844
|
server.listen(apiPort, apiHost, () => {
|
|
18897
18845
|
log(`listening on ${apiHost}:${apiPort} (min=${min}, app=${app}, region=${region})`);
|
|
18898
|
-
|
|
18846
|
+
resolve7();
|
|
18899
18847
|
});
|
|
18900
18848
|
});
|
|
18901
18849
|
const shutdown = (signal) => {
|
|
@@ -18934,17 +18882,17 @@ function authOk2(req, expected) {
|
|
|
18934
18882
|
return false;
|
|
18935
18883
|
}
|
|
18936
18884
|
function readJsonBody3(req) {
|
|
18937
|
-
return new Promise((
|
|
18885
|
+
return new Promise((resolve7, reject) => {
|
|
18938
18886
|
const chunks = [];
|
|
18939
18887
|
req.on("data", (c) => chunks.push(c));
|
|
18940
18888
|
req.on("end", () => {
|
|
18941
18889
|
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
18942
18890
|
if (!raw.trim()) {
|
|
18943
|
-
|
|
18891
|
+
resolve7({});
|
|
18944
18892
|
return;
|
|
18945
18893
|
}
|
|
18946
18894
|
try {
|
|
18947
|
-
|
|
18895
|
+
resolve7(JSON.parse(raw));
|
|
18948
18896
|
} catch (err) {
|
|
18949
18897
|
reject(err instanceof Error ? err : new Error(String(err)));
|
|
18950
18898
|
}
|
|
@@ -19023,13 +18971,13 @@ async function defaultRunJob(job) {
|
|
|
19023
18971
|
...interactive && job.idleExitMs ? { KODY_IDLE_EXIT_MS: String(job.idleExitMs) } : {},
|
|
19024
18972
|
...interactive && job.hardCapMs ? { KODY_HARD_CAP_MS: String(job.hardCapMs) } : {}
|
|
19025
18973
|
};
|
|
19026
|
-
const run = (cmd, args, cwd) => new Promise((
|
|
18974
|
+
const run = (cmd, args, cwd) => new Promise((resolve7) => {
|
|
19027
18975
|
const child = spawn8(cmd, args, { stdio: "inherit", env: childEnv, cwd });
|
|
19028
|
-
child.on("exit", (code) =>
|
|
18976
|
+
child.on("exit", (code) => resolve7(code ?? 0));
|
|
19029
18977
|
child.on("error", (err) => {
|
|
19030
18978
|
process.stderr.write(`[runner-serve] ${cmd} failed: ${err.message}
|
|
19031
18979
|
`);
|
|
19032
|
-
|
|
18980
|
+
resolve7(1);
|
|
19033
18981
|
});
|
|
19034
18982
|
});
|
|
19035
18983
|
process.stdout.write(`[runner-serve] job ${job.jobId}: cloning ${job.repo}@${branch}
|
|
@@ -19106,11 +19054,11 @@ async function runnerServe() {
|
|
|
19106
19054
|
const port = Number(process.env.PORT ?? DEFAULT_PORT2);
|
|
19107
19055
|
const server = buildServer2({ apiKey });
|
|
19108
19056
|
const host = process.env.RUNNER_HOST ?? "::";
|
|
19109
|
-
await new Promise((
|
|
19057
|
+
await new Promise((resolve7) => {
|
|
19110
19058
|
server.listen(port, host, () => {
|
|
19111
19059
|
process.stdout.write(`[runner-serve] listening on ${host}:${port} (idle, awaiting job)
|
|
19112
19060
|
`);
|
|
19113
|
-
|
|
19061
|
+
resolve7();
|
|
19114
19062
|
});
|
|
19115
19063
|
});
|
|
19116
19064
|
const shutdown = (signal) => {
|
|
@@ -19129,7 +19077,7 @@ async function runnerServe() {
|
|
|
19129
19077
|
init_config();
|
|
19130
19078
|
init_litellm();
|
|
19131
19079
|
import { spawn as spawn9 } from "child_process";
|
|
19132
|
-
function
|
|
19080
|
+
function parseTarget2(positional) {
|
|
19133
19081
|
if (!Array.isArray(positional) || positional.length === 0) return "none";
|
|
19134
19082
|
const first = String(positional[0]).toLowerCase();
|
|
19135
19083
|
if (first === "vscode" || first === "code") return "vscode";
|
|
@@ -19144,7 +19092,7 @@ function buildProxyEnv(url) {
|
|
|
19144
19092
|
};
|
|
19145
19093
|
}
|
|
19146
19094
|
async function serve(opts) {
|
|
19147
|
-
const target =
|
|
19095
|
+
const target = parseTarget2(opts.args);
|
|
19148
19096
|
const model = parseProviderModel(opts.config.agent.model);
|
|
19149
19097
|
const usesProxy = needsLitellmProxy(model);
|
|
19150
19098
|
let handle = null;
|
|
@@ -19179,14 +19127,14 @@ async function serve(opts) {
|
|
|
19179
19127
|
`);
|
|
19180
19128
|
const args = ["--dangerously-skip-permissions", "--model", model.model];
|
|
19181
19129
|
const child = spawn9("claude", args, { stdio: "inherit", env: editorEnv, cwd: opts.cwd });
|
|
19182
|
-
const exitCode = await new Promise((
|
|
19183
|
-
child.on("exit", (code) =>
|
|
19130
|
+
const exitCode = await new Promise((resolve7) => {
|
|
19131
|
+
child.on("exit", (code) => resolve7(code ?? 0));
|
|
19184
19132
|
child.on("error", (err) => {
|
|
19185
19133
|
process.stderr.write(`[kody serve] failed to launch Claude Code: ${err.message}
|
|
19186
19134
|
`);
|
|
19187
19135
|
process.stderr.write(` Install: https://docs.anthropic.com/claude/docs/claude-code
|
|
19188
19136
|
`);
|
|
19189
|
-
|
|
19137
|
+
resolve7(1);
|
|
19190
19138
|
});
|
|
19191
19139
|
});
|
|
19192
19140
|
killProxy();
|