@kody-ade/kody-engine 0.4.224 → 0.4.226
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 +825 -832
- 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.226",
|
|
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,72 @@ 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 = parseGoalActivations(r.activeGoals);
|
|
273
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
274
|
+
}
|
|
275
|
+
function parseGoalActivations(raw) {
|
|
276
|
+
if (!Array.isArray(raw)) throw new Error(`kody.config.json: company.activeGoals must be an array`);
|
|
277
|
+
const out = [];
|
|
278
|
+
const seen = /* @__PURE__ */ new Set();
|
|
279
|
+
for (const value of raw) {
|
|
280
|
+
if (typeof value === "string") {
|
|
281
|
+
const slug = parseSlug(value, "company.activeGoals");
|
|
282
|
+
if (!slug) continue;
|
|
283
|
+
if (!seen.has(slug)) {
|
|
284
|
+
seen.add(slug);
|
|
285
|
+
out.push(slug);
|
|
286
|
+
}
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
290
|
+
throw new Error(`kody.config.json: company.activeGoals entries must be strings or goal schedule objects`);
|
|
291
|
+
}
|
|
292
|
+
const r = value;
|
|
293
|
+
const template = typeof r.template === "string" ? parseSlug(r.template, "company.activeGoals.template") : "";
|
|
294
|
+
if (!template) throw new Error(`kody.config.json: company.activeGoals object requires template`);
|
|
295
|
+
const entry = { template };
|
|
296
|
+
if (r.every !== void 0) {
|
|
297
|
+
if (typeof r.every !== "string" || !/^[1-9][0-9]*[mhdw]$/.test(r.every.trim())) {
|
|
298
|
+
throw new Error(`kody.config.json: company.activeGoals every must look like "1d", "1w", "15m", or "2h"`);
|
|
299
|
+
}
|
|
300
|
+
entry.every = r.every.trim();
|
|
301
|
+
}
|
|
302
|
+
if (r.idPrefix !== void 0) {
|
|
303
|
+
if (typeof r.idPrefix !== "string") throw new Error(`kody.config.json: company.activeGoals idPrefix must be a string`);
|
|
304
|
+
const idPrefix = parseSlug(r.idPrefix, "company.activeGoals.idPrefix");
|
|
305
|
+
if (idPrefix) entry.idPrefix = idPrefix;
|
|
306
|
+
}
|
|
307
|
+
const facts = recordValue(r.facts);
|
|
308
|
+
if (r.facts !== void 0 && !facts) throw new Error(`kody.config.json: company.activeGoals facts must be an object`);
|
|
309
|
+
if (facts) entry.facts = facts;
|
|
310
|
+
out.push(entry);
|
|
311
|
+
}
|
|
312
|
+
return out;
|
|
313
|
+
}
|
|
314
|
+
function parseSlugArray(raw, field) {
|
|
315
|
+
if (!Array.isArray(raw)) throw new Error(`kody.config.json: ${field} must be an array of strings`);
|
|
316
|
+
const out = [];
|
|
317
|
+
for (const value of raw) {
|
|
318
|
+
const slug = parseSlug(value, field);
|
|
319
|
+
if (!slug) continue;
|
|
320
|
+
out.push(slug);
|
|
321
|
+
}
|
|
322
|
+
return [...new Set(out)];
|
|
323
|
+
}
|
|
324
|
+
function parseSlug(value, field) {
|
|
325
|
+
if (typeof value !== "string") throw new Error(`kody.config.json: ${field} entries must be strings`);
|
|
326
|
+
const slug = value.trim();
|
|
327
|
+
if (!slug) return "";
|
|
328
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(slug)) {
|
|
329
|
+
throw new Error(`kody.config.json: ${field} contains invalid slug "${value}"`);
|
|
330
|
+
}
|
|
331
|
+
return slug;
|
|
332
|
+
}
|
|
265
333
|
function recordValue(raw) {
|
|
266
334
|
return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : void 0;
|
|
267
335
|
}
|
|
@@ -282,6 +350,15 @@ function parsePerExecutable(raw) {
|
|
|
282
350
|
}
|
|
283
351
|
return Object.keys(out).length > 0 ? { perExecutable: out } : {};
|
|
284
352
|
}
|
|
353
|
+
function parsePerExecutableReasoningEffort(raw) {
|
|
354
|
+
if (!raw || typeof raw !== "object") return {};
|
|
355
|
+
const out = {};
|
|
356
|
+
for (const [k, v] of Object.entries(raw)) {
|
|
357
|
+
const effort = typeof v === "string" ? parseReasoningEffort(v) : null;
|
|
358
|
+
if (effort) out[k] = effort;
|
|
359
|
+
}
|
|
360
|
+
return Object.keys(out).length > 0 ? { perExecutableReasoningEffort: out } : {};
|
|
361
|
+
}
|
|
285
362
|
function parseAgentReasoningEffort(raw) {
|
|
286
363
|
if (typeof raw !== "string") return {};
|
|
287
364
|
return { reasoningEffort: parseReasoningEffort(raw) ?? void 0 };
|
|
@@ -562,7 +639,7 @@ var init_events = __esm({
|
|
|
562
639
|
// src/verify.ts
|
|
563
640
|
import { spawn } from "child_process";
|
|
564
641
|
function runCommand(command, cwd) {
|
|
565
|
-
return new Promise((
|
|
642
|
+
return new Promise((resolve7) => {
|
|
566
643
|
const start = Date.now();
|
|
567
644
|
const child = spawn(command, {
|
|
568
645
|
cwd,
|
|
@@ -591,11 +668,11 @@ function runCommand(command, cwd) {
|
|
|
591
668
|
child.on("exit", (code) => {
|
|
592
669
|
clearTimeout(timer);
|
|
593
670
|
const tail = Buffer.concat(buffers).toString("utf-8").slice(-TAIL_CHARS);
|
|
594
|
-
|
|
671
|
+
resolve7({ exitCode: code ?? -1, durationMs: Date.now() - start, tail });
|
|
595
672
|
});
|
|
596
673
|
child.on("error", (err) => {
|
|
597
674
|
clearTimeout(timer);
|
|
598
|
-
|
|
675
|
+
resolve7({ exitCode: -1, durationMs: Date.now() - start, tail: err.message });
|
|
599
676
|
});
|
|
600
677
|
});
|
|
601
678
|
}
|
|
@@ -1450,7 +1527,7 @@ var init_dutyMcp = __esm({
|
|
|
1450
1527
|
TRUST_STATE_BRANCH = "kody-state";
|
|
1451
1528
|
THREAD_BODY_MAX = 4e3;
|
|
1452
1529
|
CHECK_FAIL_CONCLUSIONS = /* @__PURE__ */ new Set(["FAILURE", "TIMED_OUT", "STARTUP_FAILURE", "ACTION_REQUIRED"]);
|
|
1453
|
-
DEFAULT_IGNORE_CHECKS = ["run", "kody", "duty-tick", "
|
|
1530
|
+
DEFAULT_IGNORE_CHECKS = ["run", "kody", "duty-tick", "worker-ask", "chat"];
|
|
1454
1531
|
trackMarker = (key) => `<!-- kody-track:${key} -->`;
|
|
1455
1532
|
commentMarker = (key) => `<!-- kody-track-comment:${key} -->`;
|
|
1456
1533
|
GATE_EXEMPT_DUTIES = /* @__PURE__ */ new Set(["qa-engineer", "ui-review"]);
|
|
@@ -1513,7 +1590,7 @@ var init_repoWorkspace = __esm({
|
|
|
1513
1590
|
defaultCloneRepo = (repo, token, dir) => {
|
|
1514
1591
|
fs4.mkdirSync(path4.dirname(dir), { recursive: true });
|
|
1515
1592
|
const authUrl = token ? `https://x-access-token:${token}@github.com/${repo}.git` : `https://github.com/${repo}.git`;
|
|
1516
|
-
return new Promise((
|
|
1593
|
+
return new Promise((resolve7, reject) => {
|
|
1517
1594
|
const child = spawn2("git", ["clone", "--depth=1", authUrl, dir], {
|
|
1518
1595
|
stdio: "inherit"
|
|
1519
1596
|
});
|
|
@@ -1529,7 +1606,7 @@ var init_repoWorkspace = __esm({
|
|
|
1529
1606
|
spawnSync("git", ["-C", dir, "config", "user.email", email]);
|
|
1530
1607
|
} catch {
|
|
1531
1608
|
}
|
|
1532
|
-
|
|
1609
|
+
resolve7();
|
|
1533
1610
|
});
|
|
1534
1611
|
child.on("error", reject);
|
|
1535
1612
|
});
|
|
@@ -1814,10 +1891,10 @@ async function runAgent(opts) {
|
|
|
1814
1891
|
let timer;
|
|
1815
1892
|
let next;
|
|
1816
1893
|
if (turnTimeoutMs > 0) {
|
|
1817
|
-
const timeoutPromise = new Promise((
|
|
1894
|
+
const timeoutPromise = new Promise((resolve7) => {
|
|
1818
1895
|
timer = setTimeout(() => {
|
|
1819
1896
|
timedOut = true;
|
|
1820
|
-
|
|
1897
|
+
resolve7({ done: true, value: void 0 });
|
|
1821
1898
|
}, turnTimeoutMs);
|
|
1822
1899
|
});
|
|
1823
1900
|
next = await Promise.race([nextPromise, timeoutPromise]);
|
|
@@ -2055,6 +2132,8 @@ function resolveCompanyStore() {
|
|
|
2055
2132
|
return { repo, ref };
|
|
2056
2133
|
}
|
|
2057
2134
|
function fetchCompanyStore(repo, ref) {
|
|
2135
|
+
const localRoot = localStoreRoot(repo);
|
|
2136
|
+
if (localRoot) return localRoot;
|
|
2058
2137
|
const url = repoToGitUrl(repo);
|
|
2059
2138
|
const cacheDir = path6.join(cacheRoot(), cacheKey(repo, ref));
|
|
2060
2139
|
try {
|
|
@@ -2075,6 +2154,12 @@ function fetchCompanyStore(repo, ref) {
|
|
|
2075
2154
|
return null;
|
|
2076
2155
|
}
|
|
2077
2156
|
}
|
|
2157
|
+
function localStoreRoot(repo) {
|
|
2158
|
+
if (!path6.isAbsolute(repo) && !repo.startsWith(".")) return null;
|
|
2159
|
+
const root = path6.resolve(repo);
|
|
2160
|
+
if (!fs6.existsSync(path6.join(root, ".kody"))) return null;
|
|
2161
|
+
return root;
|
|
2162
|
+
}
|
|
2078
2163
|
function repoToGitUrl(repo) {
|
|
2079
2164
|
if (/^(https?:|ssh:|git@|file:)/.test(repo)) return repo;
|
|
2080
2165
|
if (isGitHubShorthand(repo)) {
|
|
@@ -3205,6 +3290,7 @@ function parseClaudeCode(p, raw) {
|
|
|
3205
3290
|
permissionMode,
|
|
3206
3291
|
maxTurns: typeof r.maxTurns === "number" ? r.maxTurns : null,
|
|
3207
3292
|
maxThinkingTokens: typeof r.maxThinkingTokens === "number" ? r.maxThinkingTokens : null,
|
|
3293
|
+
reasoningEffort: typeof r.reasoningEffort === "string" ? parseReasoningEffort(r.reasoningEffort) : null,
|
|
3208
3294
|
maxTurnTimeoutSec: typeof r.maxTurnTimeoutSec === "number" ? r.maxTurnTimeoutSec : null,
|
|
3209
3295
|
systemPromptAppend: typeof r.systemPromptAppend === "string" ? r.systemPromptAppend : null,
|
|
3210
3296
|
cacheable: r.cacheable === true,
|
|
@@ -3388,6 +3474,7 @@ var VALID_INPUT_TYPES, VALID_PERMISSION_MODES, VALID_ROLES, VALID_CONTAINER_CHIL
|
|
|
3388
3474
|
var init_profile = __esm({
|
|
3389
3475
|
"src/profile.ts"() {
|
|
3390
3476
|
"use strict";
|
|
3477
|
+
init_config();
|
|
3391
3478
|
init_dutyMcp();
|
|
3392
3479
|
init_lifecycles();
|
|
3393
3480
|
init_profile_error();
|
|
@@ -4554,6 +4641,91 @@ here \u2014 never substitute it for the truth.`;
|
|
|
4554
4641
|
}
|
|
4555
4642
|
});
|
|
4556
4643
|
|
|
4644
|
+
// src/dutyReport.ts
|
|
4645
|
+
function parseDutyReportsFromText(text) {
|
|
4646
|
+
const reports = [];
|
|
4647
|
+
for (const match of text.matchAll(REPORT_LINE)) {
|
|
4648
|
+
const raw = match[1]?.trim();
|
|
4649
|
+
if (!raw) continue;
|
|
4650
|
+
try {
|
|
4651
|
+
const parsed = parseDutyReport(JSON.parse(raw));
|
|
4652
|
+
if (parsed) reports.push(parsed);
|
|
4653
|
+
} catch {
|
|
4654
|
+
}
|
|
4655
|
+
}
|
|
4656
|
+
return reports;
|
|
4657
|
+
}
|
|
4658
|
+
function parseDutyReport(raw) {
|
|
4659
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
4660
|
+
const obj = raw;
|
|
4661
|
+
const target = parseTarget(obj.target);
|
|
4662
|
+
if (!target) return null;
|
|
4663
|
+
const evidence = parseBooleanRecord(obj.evidence);
|
|
4664
|
+
const facts = parseFacts(obj.facts);
|
|
4665
|
+
if (!evidence && !facts) return null;
|
|
4666
|
+
return {
|
|
4667
|
+
target,
|
|
4668
|
+
...evidence ? { evidence } : {},
|
|
4669
|
+
...facts ? { facts } : {}
|
|
4670
|
+
};
|
|
4671
|
+
}
|
|
4672
|
+
function applyDutyReportToGoalState(state, report) {
|
|
4673
|
+
const priorFacts = parseFacts(state.extra.facts) ?? {};
|
|
4674
|
+
const nextFacts = { ...priorFacts };
|
|
4675
|
+
for (const [key, value] of Object.entries(report.facts ?? {})) {
|
|
4676
|
+
if (CONTROL_FACT_KEYS.has(key)) continue;
|
|
4677
|
+
nextFacts[key] = value;
|
|
4678
|
+
}
|
|
4679
|
+
for (const [key, value] of Object.entries(report.evidence ?? {})) {
|
|
4680
|
+
nextFacts[key] = value;
|
|
4681
|
+
}
|
|
4682
|
+
const pending = nextFacts.pendingEvidence;
|
|
4683
|
+
if (typeof pending === "string" && Object.hasOwn(report.evidence ?? {}, pending)) {
|
|
4684
|
+
delete nextFacts.pendingEvidence;
|
|
4685
|
+
}
|
|
4686
|
+
return {
|
|
4687
|
+
...state,
|
|
4688
|
+
extra: {
|
|
4689
|
+
...state.extra,
|
|
4690
|
+
facts: nextFacts
|
|
4691
|
+
}
|
|
4692
|
+
};
|
|
4693
|
+
}
|
|
4694
|
+
function parseTarget(raw) {
|
|
4695
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
4696
|
+
const target = raw;
|
|
4697
|
+
if (target.type !== "goal" && target.type !== "task" && target.type !== "duty") return null;
|
|
4698
|
+
if (typeof target.id !== "string" || target.id.trim().length === 0) return null;
|
|
4699
|
+
return { type: target.type, id: target.id.trim() };
|
|
4700
|
+
}
|
|
4701
|
+
function parseBooleanRecord(raw) {
|
|
4702
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
4703
|
+
const out = {};
|
|
4704
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
4705
|
+
if (typeof key !== "string" || key.length === 0 || typeof value !== "boolean") return null;
|
|
4706
|
+
out[key] = value;
|
|
4707
|
+
}
|
|
4708
|
+
return out;
|
|
4709
|
+
}
|
|
4710
|
+
function parseFacts(raw) {
|
|
4711
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
4712
|
+
const out = {};
|
|
4713
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
4714
|
+
if (typeof key !== "string" || key.length === 0) return null;
|
|
4715
|
+
if (CONTROL_FACT_KEYS.has(key)) continue;
|
|
4716
|
+
out[key] = value;
|
|
4717
|
+
}
|
|
4718
|
+
return out;
|
|
4719
|
+
}
|
|
4720
|
+
var REPORT_LINE, CONTROL_FACT_KEYS;
|
|
4721
|
+
var init_dutyReport = __esm({
|
|
4722
|
+
"src/dutyReport.ts"() {
|
|
4723
|
+
"use strict";
|
|
4724
|
+
REPORT_LINE = /^KODY_DUTY_REPORT=(.+)$/gm;
|
|
4725
|
+
CONTROL_FACT_KEYS = /* @__PURE__ */ new Set(["blockers", "destination", "duties", "route", "stage", "state"]);
|
|
4726
|
+
}
|
|
4727
|
+
});
|
|
4728
|
+
|
|
4557
4729
|
// src/lifecycleLabels.ts
|
|
4558
4730
|
function groupOf(label) {
|
|
4559
4731
|
const idx = label.indexOf(":");
|
|
@@ -5301,6 +5473,291 @@ var init_advanceFlow = __esm({
|
|
|
5301
5473
|
}
|
|
5302
5474
|
});
|
|
5303
5475
|
|
|
5476
|
+
// src/goal/manager.ts
|
|
5477
|
+
function evidenceSatisfied(goal, evidence) {
|
|
5478
|
+
return goal.facts[evidence] === true;
|
|
5479
|
+
}
|
|
5480
|
+
function firstMissingEvidence(goal) {
|
|
5481
|
+
return goal.destination.evidence.find((evidence) => !evidenceSatisfied(goal, evidence));
|
|
5482
|
+
}
|
|
5483
|
+
function pushBlocker(goal, reason) {
|
|
5484
|
+
if (!goal.blockers.includes(reason)) goal.blockers.push(reason);
|
|
5485
|
+
}
|
|
5486
|
+
function isSimpleGoal(goal) {
|
|
5487
|
+
return goal.type === SIMPLE_GOAL_TYPE;
|
|
5488
|
+
}
|
|
5489
|
+
function applySimpleGoalTaskSummary(goal, summary) {
|
|
5490
|
+
goal.facts.simpleAttachedTaskCount = summary.total;
|
|
5491
|
+
goal.facts.simpleOpenTaskCount = summary.open;
|
|
5492
|
+
goal.facts[SIMPLE_GOAL_EVIDENCE] = summary.total > 0 && summary.open === 0;
|
|
5493
|
+
}
|
|
5494
|
+
function isFactReference(value) {
|
|
5495
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
5496
|
+
const record = value;
|
|
5497
|
+
return Object.keys(record).length === 1 && typeof record.fact === "string" && record.fact.length > 0;
|
|
5498
|
+
}
|
|
5499
|
+
function isCliArgValue(value) {
|
|
5500
|
+
return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
5501
|
+
}
|
|
5502
|
+
function resolveRouteArgs(goal, step) {
|
|
5503
|
+
const cliArgs = {};
|
|
5504
|
+
for (const [name, value] of Object.entries(step.args ?? {})) {
|
|
5505
|
+
if (!isFactReference(value)) {
|
|
5506
|
+
cliArgs[name] = value;
|
|
5507
|
+
continue;
|
|
5508
|
+
}
|
|
5509
|
+
const factValue = goal.facts[value.fact];
|
|
5510
|
+
if (factValue === void 0 || factValue === null) {
|
|
5511
|
+
return { ok: false, reason: `route arg ${name} needs missing fact ${value.fact}` };
|
|
5512
|
+
}
|
|
5513
|
+
if (!isCliArgValue(factValue)) {
|
|
5514
|
+
return { ok: false, reason: `route arg ${name} needs scalar fact ${value.fact}` };
|
|
5515
|
+
}
|
|
5516
|
+
cliArgs[name] = factValue;
|
|
5517
|
+
}
|
|
5518
|
+
return { ok: true, cliArgs };
|
|
5519
|
+
}
|
|
5520
|
+
function planManagedGoalTick(goal) {
|
|
5521
|
+
goal.blockers = [];
|
|
5522
|
+
const missing = firstMissingEvidence(goal);
|
|
5523
|
+
if (!missing) {
|
|
5524
|
+
goal.stage = "done";
|
|
5525
|
+
delete goal.facts.pendingEvidence;
|
|
5526
|
+
return { kind: "done" };
|
|
5527
|
+
}
|
|
5528
|
+
const pending = goal.facts.pendingEvidence;
|
|
5529
|
+
if (pending === missing) {
|
|
5530
|
+
const stage = typeof goal.stage === "string" ? goal.stage : "waiting";
|
|
5531
|
+
return { kind: "wait", evidence: missing, stage, reason: `waiting for evidence: ${missing}` };
|
|
5532
|
+
}
|
|
5533
|
+
const step = goal.route.find((candidate) => candidate.evidence === missing);
|
|
5534
|
+
if (!step) {
|
|
5535
|
+
if (isSimpleGoal(goal) && missing === SIMPLE_GOAL_EVIDENCE) {
|
|
5536
|
+
goal.stage = "waiting";
|
|
5537
|
+
delete goal.facts.pendingEvidence;
|
|
5538
|
+
const total = typeof goal.facts.simpleAttachedTaskCount === "number" ? goal.facts.simpleAttachedTaskCount : 0;
|
|
5539
|
+
const open = typeof goal.facts.simpleOpenTaskCount === "number" ? goal.facts.simpleOpenTaskCount : 0;
|
|
5540
|
+
return {
|
|
5541
|
+
kind: "wait",
|
|
5542
|
+
evidence: missing,
|
|
5543
|
+
stage: "waiting",
|
|
5544
|
+
reason: total === 0 ? "waiting for labelled tasks" : `waiting for ${open} open labelled task(s)`
|
|
5545
|
+
};
|
|
5546
|
+
}
|
|
5547
|
+
const reason = `no route step for evidence: ${missing}`;
|
|
5548
|
+
goal.stage = "blocked";
|
|
5549
|
+
pushBlocker(goal, reason);
|
|
5550
|
+
return { kind: "blocked", evidence: missing, stage: "blocked", reason };
|
|
5551
|
+
}
|
|
5552
|
+
if (!goal.duties.includes(step.duty)) {
|
|
5553
|
+
const reason = `route duty ${step.duty} is not attached to this goal`;
|
|
5554
|
+
goal.stage = "blocked";
|
|
5555
|
+
pushBlocker(goal, reason);
|
|
5556
|
+
return { kind: "blocked", evidence: missing, stage: step.stage, reason };
|
|
5557
|
+
}
|
|
5558
|
+
const resolved = resolveRouteArgs(goal, step);
|
|
5559
|
+
if (!resolved.ok) {
|
|
5560
|
+
goal.stage = "blocked";
|
|
5561
|
+
pushBlocker(goal, resolved.reason);
|
|
5562
|
+
return { kind: "blocked", evidence: missing, stage: step.stage, reason: resolved.reason };
|
|
5563
|
+
}
|
|
5564
|
+
goal.stage = step.stage;
|
|
5565
|
+
goal.facts.pendingEvidence = missing;
|
|
5566
|
+
return {
|
|
5567
|
+
kind: "dispatch",
|
|
5568
|
+
evidence: missing,
|
|
5569
|
+
stage: step.stage,
|
|
5570
|
+
duty: step.duty,
|
|
5571
|
+
executable: step.executable,
|
|
5572
|
+
cliArgs: resolved.cliArgs
|
|
5573
|
+
};
|
|
5574
|
+
}
|
|
5575
|
+
function asStringArray(value) {
|
|
5576
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) return null;
|
|
5577
|
+
return [...value];
|
|
5578
|
+
}
|
|
5579
|
+
function asRecord(value) {
|
|
5580
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
5581
|
+
return { ...value };
|
|
5582
|
+
}
|
|
5583
|
+
function asRoute(value) {
|
|
5584
|
+
if (!Array.isArray(value)) return null;
|
|
5585
|
+
const route = [];
|
|
5586
|
+
for (const item of value) {
|
|
5587
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) return null;
|
|
5588
|
+
const raw = item;
|
|
5589
|
+
if (typeof raw.evidence !== "string" || typeof raw.stage !== "string" || typeof raw.duty !== "string") {
|
|
5590
|
+
return null;
|
|
5591
|
+
}
|
|
5592
|
+
const args = raw.args === void 0 ? void 0 : asRecord(raw.args);
|
|
5593
|
+
if (raw.args !== void 0 && !args) return null;
|
|
5594
|
+
route.push({
|
|
5595
|
+
evidence: raw.evidence,
|
|
5596
|
+
stage: raw.stage,
|
|
5597
|
+
duty: raw.duty,
|
|
5598
|
+
executable: typeof raw.executable === "string" ? raw.executable : void 0,
|
|
5599
|
+
args: args ?? void 0
|
|
5600
|
+
});
|
|
5601
|
+
}
|
|
5602
|
+
return route;
|
|
5603
|
+
}
|
|
5604
|
+
function managedGoalFromState(state) {
|
|
5605
|
+
const extra = state.extra;
|
|
5606
|
+
const destination = asRecord(extra.destination);
|
|
5607
|
+
const evidence = asStringArray(destination?.evidence);
|
|
5608
|
+
const duties = asStringArray(extra.duties);
|
|
5609
|
+
const route = asRoute(extra.route);
|
|
5610
|
+
const facts = asRecord(extra.facts);
|
|
5611
|
+
const blockers = asStringArray(extra.blockers);
|
|
5612
|
+
if (typeof extra.type !== "string" || !destination || typeof destination.outcome !== "string" || !evidence || !duties || !route || !facts || !blockers) {
|
|
5613
|
+
return null;
|
|
5614
|
+
}
|
|
5615
|
+
return {
|
|
5616
|
+
type: extra.type,
|
|
5617
|
+
destination: { outcome: destination.outcome, evidence },
|
|
5618
|
+
duties,
|
|
5619
|
+
route,
|
|
5620
|
+
stage: typeof extra.stage === "string" ? extra.stage : void 0,
|
|
5621
|
+
facts,
|
|
5622
|
+
blockers
|
|
5623
|
+
};
|
|
5624
|
+
}
|
|
5625
|
+
function writeManagedGoalToState(state, goal) {
|
|
5626
|
+
return {
|
|
5627
|
+
...state,
|
|
5628
|
+
extra: {
|
|
5629
|
+
...state.extra,
|
|
5630
|
+
type: goal.type,
|
|
5631
|
+
destination: goal.destination,
|
|
5632
|
+
duties: goal.duties,
|
|
5633
|
+
route: goal.route,
|
|
5634
|
+
stage: goal.stage,
|
|
5635
|
+
facts: goal.facts,
|
|
5636
|
+
blockers: goal.blockers
|
|
5637
|
+
}
|
|
5638
|
+
};
|
|
5639
|
+
}
|
|
5640
|
+
var SIMPLE_GOAL_TYPE, SIMPLE_GOAL_EVIDENCE;
|
|
5641
|
+
var init_manager = __esm({
|
|
5642
|
+
"src/goal/manager.ts"() {
|
|
5643
|
+
"use strict";
|
|
5644
|
+
SIMPLE_GOAL_TYPE = "simple";
|
|
5645
|
+
SIMPLE_GOAL_EVIDENCE = "labelledTasksComplete";
|
|
5646
|
+
}
|
|
5647
|
+
});
|
|
5648
|
+
|
|
5649
|
+
// src/goal/state.ts
|
|
5650
|
+
import * as fs24 from "fs";
|
|
5651
|
+
import * as path21 from "path";
|
|
5652
|
+
function parseGoalState(filePath, raw) {
|
|
5653
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
5654
|
+
throw new GoalStateError(filePath, "must be JSON object");
|
|
5655
|
+
}
|
|
5656
|
+
const r = raw;
|
|
5657
|
+
const stateValue = r.state;
|
|
5658
|
+
if (typeof stateValue !== "string" || !VALID_STATES.has(stateValue)) {
|
|
5659
|
+
throw new GoalStateError(
|
|
5660
|
+
filePath,
|
|
5661
|
+
`"state" is required and must be one of: ${[...VALID_STATES].join(" | ")} (got ${JSON.stringify(stateValue)})`
|
|
5662
|
+
);
|
|
5663
|
+
}
|
|
5664
|
+
const parsed = {
|
|
5665
|
+
state: stateValue,
|
|
5666
|
+
extra: {}
|
|
5667
|
+
};
|
|
5668
|
+
for (const ts of ["updatedAt", "createdAt", "startedAt"]) {
|
|
5669
|
+
const v = r[ts];
|
|
5670
|
+
if (typeof v === "string" && v.length > 0) parsed[ts] = v;
|
|
5671
|
+
}
|
|
5672
|
+
const known = /* @__PURE__ */ new Set(["state", "updatedAt", "createdAt", "startedAt"]);
|
|
5673
|
+
for (const [k, v] of Object.entries(r)) {
|
|
5674
|
+
if (!known.has(k)) parsed.extra[k] = v;
|
|
5675
|
+
}
|
|
5676
|
+
return parsed;
|
|
5677
|
+
}
|
|
5678
|
+
function serializeGoalState(s) {
|
|
5679
|
+
const obj = { ...s.extra, state: s.state };
|
|
5680
|
+
if (s.createdAt !== void 0) obj.createdAt = s.createdAt;
|
|
5681
|
+
if (s.startedAt !== void 0) obj.startedAt = s.startedAt;
|
|
5682
|
+
if (s.updatedAt !== void 0) obj.updatedAt = s.updatedAt;
|
|
5683
|
+
return `${JSON.stringify(obj, null, 2)}
|
|
5684
|
+
`;
|
|
5685
|
+
}
|
|
5686
|
+
function nowIso() {
|
|
5687
|
+
return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
5688
|
+
}
|
|
5689
|
+
var VALID_STATES, GoalStateError;
|
|
5690
|
+
var init_state2 = __esm({
|
|
5691
|
+
"src/goal/state.ts"() {
|
|
5692
|
+
"use strict";
|
|
5693
|
+
VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "done"]);
|
|
5694
|
+
GoalStateError = class extends Error {
|
|
5695
|
+
constructor(path45, message) {
|
|
5696
|
+
super(`Invalid goal state at ${path45}:
|
|
5697
|
+
${message}`);
|
|
5698
|
+
this.path = path45;
|
|
5699
|
+
this.name = "GoalStateError";
|
|
5700
|
+
}
|
|
5701
|
+
path;
|
|
5702
|
+
};
|
|
5703
|
+
}
|
|
5704
|
+
});
|
|
5705
|
+
|
|
5706
|
+
// src/scripts/advanceManagedGoal.ts
|
|
5707
|
+
function readSimpleGoalTaskSummary(goalId, cwd) {
|
|
5708
|
+
const raw = gh(
|
|
5709
|
+
["issue", "list", "--state", "all", "--label", `goal:${goalId}`, "--limit", "1000", "--json", "number,state"],
|
|
5710
|
+
{ cwd }
|
|
5711
|
+
);
|
|
5712
|
+
const issues = JSON.parse(raw);
|
|
5713
|
+
const total = issues.length;
|
|
5714
|
+
const open = issues.filter((issue) => String(issue.state ?? "").toLowerCase() === "open").length;
|
|
5715
|
+
return { total, open };
|
|
5716
|
+
}
|
|
5717
|
+
var advanceManagedGoal;
|
|
5718
|
+
var init_advanceManagedGoal = __esm({
|
|
5719
|
+
"src/scripts/advanceManagedGoal.ts"() {
|
|
5720
|
+
"use strict";
|
|
5721
|
+
init_manager();
|
|
5722
|
+
init_state2();
|
|
5723
|
+
init_issue();
|
|
5724
|
+
advanceManagedGoal = async (ctx) => {
|
|
5725
|
+
ctx.skipAgent = true;
|
|
5726
|
+
const goal = ctx.data.goal;
|
|
5727
|
+
if (!goal?.raw) {
|
|
5728
|
+
ctx.output.exitCode = 1;
|
|
5729
|
+
ctx.output.reason = "advanceManagedGoal requires loadGoalState first";
|
|
5730
|
+
return;
|
|
5731
|
+
}
|
|
5732
|
+
ctx.data.goalOriginalStateText = serializeGoalState(goal.raw);
|
|
5733
|
+
const managed = managedGoalFromState(goal.raw);
|
|
5734
|
+
if (!managed) {
|
|
5735
|
+
ctx.output.reason = "goal has no managed-goal contract; nothing to advance";
|
|
5736
|
+
return;
|
|
5737
|
+
}
|
|
5738
|
+
if (isSimpleGoal(managed)) {
|
|
5739
|
+
applySimpleGoalTaskSummary(managed, readSimpleGoalTaskSummary(goal.id, ctx.cwd));
|
|
5740
|
+
}
|
|
5741
|
+
const decision = planManagedGoalTick(managed);
|
|
5742
|
+
ctx.data.managedGoalDecision = decision;
|
|
5743
|
+
if (decision.kind === "done") {
|
|
5744
|
+
goal.state = "done";
|
|
5745
|
+
}
|
|
5746
|
+
goal.raw = writeManagedGoalToState({ ...goal.raw, state: goal.state }, managed);
|
|
5747
|
+
if (decision.kind === "blocked" || decision.kind === "wait" || decision.kind === "idle" || decision.kind === "done") {
|
|
5748
|
+
ctx.output.reason = decision.kind === "done" ? "managed goal complete" : decision.reason;
|
|
5749
|
+
return;
|
|
5750
|
+
}
|
|
5751
|
+
ctx.output.nextDispatch = {
|
|
5752
|
+
duty: decision.duty,
|
|
5753
|
+
executable: decision.executable,
|
|
5754
|
+
cliArgs: decision.cliArgs
|
|
5755
|
+
};
|
|
5756
|
+
ctx.output.reason = `dispatch ${decision.duty} for ${decision.evidence}`;
|
|
5757
|
+
};
|
|
5758
|
+
}
|
|
5759
|
+
});
|
|
5760
|
+
|
|
5304
5761
|
// src/stateBranch.ts
|
|
5305
5762
|
function is404(err) {
|
|
5306
5763
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -5418,6 +5875,118 @@ var init_appendCompanyActivity = __esm({
|
|
|
5418
5875
|
}
|
|
5419
5876
|
});
|
|
5420
5877
|
|
|
5878
|
+
// src/goal/stateStore.ts
|
|
5879
|
+
function statePath(goalId) {
|
|
5880
|
+
return `.kody/goals/instances/${goalId}/state.json`;
|
|
5881
|
+
}
|
|
5882
|
+
function is4042(err) {
|
|
5883
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5884
|
+
return /HTTP 404/i.test(msg) || /Not Found/i.test(msg);
|
|
5885
|
+
}
|
|
5886
|
+
function fetchGoalState(owner, repo, goalId, cwd) {
|
|
5887
|
+
const filePath = statePath(goalId);
|
|
5888
|
+
let raw;
|
|
5889
|
+
try {
|
|
5890
|
+
raw = gh(["api", `/repos/${owner}/${repo}/contents/${filePath}?ref=${STATE_BRANCH}`], { cwd });
|
|
5891
|
+
} catch (err) {
|
|
5892
|
+
if (is4042(err)) return null;
|
|
5893
|
+
throw err;
|
|
5894
|
+
}
|
|
5895
|
+
const o = JSON.parse(raw);
|
|
5896
|
+
if (!o.content) return null;
|
|
5897
|
+
const decoded = Buffer.from(o.content, "base64").toString("utf-8");
|
|
5898
|
+
return parseGoalState(filePath, JSON.parse(decoded));
|
|
5899
|
+
}
|
|
5900
|
+
function putGoalState(owner, repo, goalId, state, message, cwd) {
|
|
5901
|
+
ensureStateBranch(owner, repo, cwd);
|
|
5902
|
+
const filePath = statePath(goalId);
|
|
5903
|
+
const content = Buffer.from(serializeGoalState(state), "utf-8").toString("base64");
|
|
5904
|
+
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
5905
|
+
let sha;
|
|
5906
|
+
try {
|
|
5907
|
+
const cur = gh(["api", `/repos/${owner}/${repo}/contents/${filePath}?ref=${STATE_BRANCH}`], { cwd });
|
|
5908
|
+
const o = JSON.parse(cur);
|
|
5909
|
+
if (o.sha) sha = o.sha;
|
|
5910
|
+
} catch (err) {
|
|
5911
|
+
if (!is4042(err)) throw err;
|
|
5912
|
+
}
|
|
5913
|
+
const payload = { message, content, branch: STATE_BRANCH };
|
|
5914
|
+
if (sha) payload.sha = sha;
|
|
5915
|
+
try {
|
|
5916
|
+
gh(["api", "--method", "PUT", `/repos/${owner}/${repo}/contents/${filePath}`, "--input", "-"], {
|
|
5917
|
+
cwd,
|
|
5918
|
+
input: JSON.stringify(payload)
|
|
5919
|
+
});
|
|
5920
|
+
return;
|
|
5921
|
+
} catch (err) {
|
|
5922
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5923
|
+
const conflict = /HTTP 409/i.test(msg) || /HTTP 422/i.test(msg) || /does not match|but expected/i.test(msg);
|
|
5924
|
+
if (!conflict || attempt === 3) throw err;
|
|
5925
|
+
}
|
|
5926
|
+
}
|
|
5927
|
+
}
|
|
5928
|
+
var init_stateStore = __esm({
|
|
5929
|
+
"src/goal/stateStore.ts"() {
|
|
5930
|
+
"use strict";
|
|
5931
|
+
init_issue();
|
|
5932
|
+
init_stateBranch();
|
|
5933
|
+
init_state2();
|
|
5934
|
+
}
|
|
5935
|
+
});
|
|
5936
|
+
|
|
5937
|
+
// src/scripts/applyDutyReports.ts
|
|
5938
|
+
function collectReports(raw, agentResult) {
|
|
5939
|
+
const out = [];
|
|
5940
|
+
if (Array.isArray(raw)) {
|
|
5941
|
+
for (const item of raw) {
|
|
5942
|
+
if (isDutyReport(item)) out.push(item);
|
|
5943
|
+
}
|
|
5944
|
+
}
|
|
5945
|
+
if (agentResult?.finalText) out.push(...parseDutyReportsFromText(agentResult.finalText));
|
|
5946
|
+
return out;
|
|
5947
|
+
}
|
|
5948
|
+
function isDutyReport(raw) {
|
|
5949
|
+
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";
|
|
5950
|
+
}
|
|
5951
|
+
function describeMessage(report) {
|
|
5952
|
+
const keys = [
|
|
5953
|
+
...Object.keys(report.evidence ?? {}).map((key) => `evidence:${key}`),
|
|
5954
|
+
...Object.keys(report.facts ?? {}).map((key) => `fact:${key}`)
|
|
5955
|
+
];
|
|
5956
|
+
return `chore(goals): apply duty report for ${report.target.id}${keys.length ? ` (${keys.join(", ")})` : ""}`;
|
|
5957
|
+
}
|
|
5958
|
+
var applyDutyReports;
|
|
5959
|
+
var init_applyDutyReports = __esm({
|
|
5960
|
+
"src/scripts/applyDutyReports.ts"() {
|
|
5961
|
+
"use strict";
|
|
5962
|
+
init_dutyReport();
|
|
5963
|
+
init_state2();
|
|
5964
|
+
init_stateStore();
|
|
5965
|
+
applyDutyReports = async (ctx, _profile, agentResult) => {
|
|
5966
|
+
const reports = collectReports(ctx.data.dutyReports, agentResult);
|
|
5967
|
+
if (reports.length === 0) return;
|
|
5968
|
+
const owner = ctx.config.github?.owner;
|
|
5969
|
+
const repo = ctx.config.github?.repo;
|
|
5970
|
+
if (!owner || !repo) {
|
|
5971
|
+
process.stderr.write("[kody duty-report] missing github owner/repo; cannot apply reports\n");
|
|
5972
|
+
return;
|
|
5973
|
+
}
|
|
5974
|
+
const goalReports = reports.filter((report) => report.target.type === "goal");
|
|
5975
|
+
for (const report of goalReports) {
|
|
5976
|
+
const prior = fetchGoalState(owner, repo, report.target.id, ctx.cwd);
|
|
5977
|
+
if (!prior) {
|
|
5978
|
+
process.stderr.write(`[kody duty-report] goal ${report.target.id} missing on kody-state; report skipped
|
|
5979
|
+
`);
|
|
5980
|
+
continue;
|
|
5981
|
+
}
|
|
5982
|
+
const next = applyDutyReportToGoalState(prior, report);
|
|
5983
|
+
if (serializeGoalState(next) === serializeGoalState(prior)) continue;
|
|
5984
|
+
putGoalState(owner, repo, report.target.id, { ...next, updatedAt: nowIso() }, describeMessage(report), ctx.cwd);
|
|
5985
|
+
}
|
|
5986
|
+
};
|
|
5987
|
+
}
|
|
5988
|
+
});
|
|
5989
|
+
|
|
5421
5990
|
// src/coverage.ts
|
|
5422
5991
|
import { execFileSync as execFileSync10 } from "child_process";
|
|
5423
5992
|
function patternToRegex(pattern) {
|
|
@@ -5584,11 +6153,11 @@ var init_classifyByLabel = __esm({
|
|
|
5584
6153
|
});
|
|
5585
6154
|
|
|
5586
6155
|
// src/scripts/commitAndPush.ts
|
|
5587
|
-
import * as
|
|
5588
|
-
import * as
|
|
6156
|
+
import * as fs25 from "fs";
|
|
6157
|
+
import * as path22 from "path";
|
|
5589
6158
|
function sentinelPathForStage(cwd, profileName) {
|
|
5590
6159
|
const runId = resolveRunId();
|
|
5591
|
-
return
|
|
6160
|
+
return path22.join(cwd, ".kody", "runs", runId, `commit-${profileName}.lock`);
|
|
5592
6161
|
}
|
|
5593
6162
|
var DEFAULT_COMMIT_MESSAGE, commitAndPush2;
|
|
5594
6163
|
var init_commitAndPush = __esm({
|
|
@@ -5605,9 +6174,9 @@ var init_commitAndPush = __esm({
|
|
|
5605
6174
|
}
|
|
5606
6175
|
const idempotencyEnabled = process.env.KODY_COMMIT_IDEMPOTENCY !== "0";
|
|
5607
6176
|
const sentinel = idempotencyEnabled ? sentinelPathForStage(ctx.cwd, profile.name) : null;
|
|
5608
|
-
if (sentinel &&
|
|
6177
|
+
if (sentinel && fs25.existsSync(sentinel)) {
|
|
5609
6178
|
try {
|
|
5610
|
-
const replay = JSON.parse(
|
|
6179
|
+
const replay = JSON.parse(fs25.readFileSync(sentinel, "utf-8"));
|
|
5611
6180
|
ctx.data.commitResult = replay.commitResult ?? { committed: false, pushed: false };
|
|
5612
6181
|
if (Array.isArray(replay.changedFiles)) ctx.data.changedFiles = replay.changedFiles;
|
|
5613
6182
|
if (typeof replay.hasCommitsAhead === "boolean") ctx.data.hasCommitsAhead = replay.hasCommitsAhead;
|
|
@@ -5660,8 +6229,8 @@ var init_commitAndPush = __esm({
|
|
|
5660
6229
|
const result = ctx.data.commitResult;
|
|
5661
6230
|
if (sentinel && result?.committed) {
|
|
5662
6231
|
try {
|
|
5663
|
-
|
|
5664
|
-
|
|
6232
|
+
fs25.mkdirSync(path22.dirname(sentinel), { recursive: true });
|
|
6233
|
+
fs25.writeFileSync(
|
|
5665
6234
|
sentinel,
|
|
5666
6235
|
JSON.stringify(
|
|
5667
6236
|
{
|
|
@@ -5682,148 +6251,17 @@ var init_commitAndPush = __esm({
|
|
|
5682
6251
|
}
|
|
5683
6252
|
});
|
|
5684
6253
|
|
|
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
6254
|
// src/scripts/commitGoalState.ts
|
|
5810
6255
|
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)`;
|
|
6256
|
+
if (goal.state === "done") return `chore(goals): complete ${goal.id}`;
|
|
6257
|
+
return `chore(goals): update ${goal.id}`;
|
|
5821
6258
|
}
|
|
5822
|
-
var
|
|
6259
|
+
var STATE_BRANCH_LABEL, commitGoalState;
|
|
5823
6260
|
var init_commitGoalState = __esm({
|
|
5824
6261
|
"src/scripts/commitGoalState.ts"() {
|
|
5825
6262
|
"use strict";
|
|
5826
6263
|
init_stateStore();
|
|
6264
|
+
STATE_BRANCH_LABEL = "kody-state";
|
|
5827
6265
|
commitGoalState = async (ctx) => {
|
|
5828
6266
|
const goal = ctx.data.goal;
|
|
5829
6267
|
if (!goal) return;
|
|
@@ -5833,7 +6271,7 @@ var init_commitGoalState = __esm({
|
|
|
5833
6271
|
const owner = ctx.config.github?.owner;
|
|
5834
6272
|
const repo = ctx.config.github?.repo;
|
|
5835
6273
|
if (!owner || !repo) {
|
|
5836
|
-
process.stderr.write(`[goal-
|
|
6274
|
+
process.stderr.write(`[goal-manager] commitGoalState: missing github owner/repo; cannot persist ${goal.id}
|
|
5837
6275
|
`);
|
|
5838
6276
|
return;
|
|
5839
6277
|
}
|
|
@@ -5841,12 +6279,11 @@ var init_commitGoalState = __esm({
|
|
|
5841
6279
|
putGoalState(owner, repo, goal.id, updated, describeCommitMessage(goal), ctx.cwd);
|
|
5842
6280
|
} catch (err) {
|
|
5843
6281
|
process.stderr.write(
|
|
5844
|
-
`[goal-
|
|
6282
|
+
`[goal-manager] commitGoalState: persist to ${STATE_BRANCH_LABEL} failed (${err instanceof Error ? err.message : String(err)}); will retry next tick
|
|
5845
6283
|
`
|
|
5846
6284
|
);
|
|
5847
6285
|
}
|
|
5848
6286
|
};
|
|
5849
|
-
STATE_BRANCH_LABEL = "kody-state";
|
|
5850
6287
|
}
|
|
5851
6288
|
});
|
|
5852
6289
|
|
|
@@ -6301,6 +6738,25 @@ function createTaskIssue(finding, goalId, manifestNumber, cwd) {
|
|
|
6301
6738
|
if (!m) throw new Error(`gh issue create returned unexpected output: ${out}`);
|
|
6302
6739
|
return { number: Number(m[1]), url };
|
|
6303
6740
|
}
|
|
6741
|
+
function buildQaManagedGoal(goalId, verdict, opened, failed) {
|
|
6742
|
+
return {
|
|
6743
|
+
type: "qa",
|
|
6744
|
+
destination: {
|
|
6745
|
+
outcome: `QA findings filed for ${goalId}`,
|
|
6746
|
+
evidence: ["qaFindingsFiled"]
|
|
6747
|
+
},
|
|
6748
|
+
duties: ["qa-goal"],
|
|
6749
|
+
route: [],
|
|
6750
|
+
stage: "filed",
|
|
6751
|
+
facts: {
|
|
6752
|
+
qaFindingsFiled: true,
|
|
6753
|
+
verdict,
|
|
6754
|
+
findingsOpened: opened,
|
|
6755
|
+
findingsFailed: failed
|
|
6756
|
+
},
|
|
6757
|
+
blockers: failed > 0 ? [`${failed} QA finding issue(s) failed to open`] : []
|
|
6758
|
+
};
|
|
6759
|
+
}
|
|
6304
6760
|
async function promoteReportToGoal(ctx, finalText, scopeArg, explicitGoalArg) {
|
|
6305
6761
|
const { markdown, data, jsonError } = splitReport(finalText);
|
|
6306
6762
|
const verdict = detectVerdict(markdown);
|
|
@@ -6404,7 +6860,13 @@ ${markdown}`, ctx.cwd);
|
|
|
6404
6860
|
}
|
|
6405
6861
|
}
|
|
6406
6862
|
const now = nowIso();
|
|
6407
|
-
const
|
|
6863
|
+
const managedGoal = buildQaManagedGoal(goalId, verdict, opened.length, failed.length);
|
|
6864
|
+
const goalState = {
|
|
6865
|
+
state: "active",
|
|
6866
|
+
startedAt: now,
|
|
6867
|
+
updatedAt: now,
|
|
6868
|
+
extra: { version: 1, ...managedGoal }
|
|
6869
|
+
};
|
|
6408
6870
|
try {
|
|
6409
6871
|
putGoalState(
|
|
6410
6872
|
ctx.config.github.owner,
|
|
@@ -6415,377 +6877,104 @@ ${markdown}`, ctx.cwd);
|
|
|
6415
6877
|
ctx.cwd
|
|
6416
6878
|
);
|
|
6417
6879
|
} 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);
|
|
6880
|
+
process.stderr.write(
|
|
6881
|
+
`[createQaGoal] failed to persist goal state to kody-state: ${err instanceof Error ? err.message : String(err)}
|
|
6882
|
+
[createQaGoal] goal-scheduler will not see ${goalId} until this succeeds.
|
|
6883
|
+
`
|
|
6884
|
+
);
|
|
6686
6885
|
}
|
|
6687
|
-
}
|
|
6688
|
-
|
|
6689
|
-
|
|
6690
|
-
|
|
6691
|
-
|
|
6692
|
-
|
|
6693
|
-
|
|
6886
|
+
const repoUrl = `https://github.com/${ctx.config.github.owner}/${ctx.config.github.repo}`;
|
|
6887
|
+
if (manifestIssueNumber !== null) {
|
|
6888
|
+
const verb = manifestUpdated ? manifestCreated ? "OPENED" : "UPDATED" : "TARGETED";
|
|
6889
|
+
process.stdout.write(
|
|
6890
|
+
`
|
|
6891
|
+
QA_GOAL_${verb}=${repoUrl}/issues/${manifestIssueNumber} (id: ${goalId}, verdict: ${verdict})
|
|
6892
|
+
`
|
|
6893
|
+
);
|
|
6894
|
+
} else {
|
|
6895
|
+
process.stdout.write(`
|
|
6896
|
+
QA_GOAL_TARGETED=(no manifest issue) (id: ${goalId}, verdict: ${verdict})
|
|
6897
|
+
`);
|
|
6694
6898
|
}
|
|
6695
|
-
|
|
6696
|
-
|
|
6697
|
-
|
|
6698
|
-
"src/goal/operations.ts"() {
|
|
6699
|
-
"use strict";
|
|
6700
|
-
init_issue();
|
|
6701
|
-
init_labels();
|
|
6702
|
-
GOAL_TASK_WORKFLOW = "kody.yml";
|
|
6899
|
+
for (const o of opened) {
|
|
6900
|
+
process.stdout.write(`QA_FINDING_OPENED=${o.url} (severity: ${o.severity})
|
|
6901
|
+
`);
|
|
6703
6902
|
}
|
|
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";
|
|
6903
|
+
if (failed.length > 0) {
|
|
6904
|
+
process.stdout.write(`QA_FINDINGS_FAILED=${failed.length} (see stderr above)
|
|
6905
|
+
`);
|
|
6729
6906
|
}
|
|
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;
|
|
6907
|
+
ctx.data.action = qaAction(verdict, {
|
|
6908
|
+
goalId,
|
|
6909
|
+
manifestIssue: manifestIssueNumber ?? void 0,
|
|
6910
|
+
findingsOpened: opened.length,
|
|
6911
|
+
findingsFailed: failed.length,
|
|
6912
|
+
mode: explicitGoal ? "goal-attach" : manifestCreated ? "goal-create" : "goal-append"
|
|
6745
6913
|
});
|
|
6914
|
+
ctx.output.exitCode = verdict === "FAIL" ? 1 : 0;
|
|
6746
6915
|
}
|
|
6747
|
-
var
|
|
6748
|
-
var
|
|
6749
|
-
"src/scripts/
|
|
6916
|
+
var MANIFEST_LABEL, MANIFEST_TITLE, MANIFEST_START, MANIFEST_END, FINDING_LABEL, REPORT_JSON_OPEN, REPORT_JSON_CLOSE, SEVERITY_COLORS, createQaGoal;
|
|
6917
|
+
var init_createQaGoal = __esm({
|
|
6918
|
+
"src/scripts/createQaGoal.ts"() {
|
|
6750
6919
|
"use strict";
|
|
6751
|
-
|
|
6752
|
-
|
|
6753
|
-
|
|
6754
|
-
|
|
6755
|
-
|
|
6756
|
-
|
|
6757
|
-
|
|
6758
|
-
|
|
6920
|
+
init_state2();
|
|
6921
|
+
init_stateStore();
|
|
6922
|
+
init_issue();
|
|
6923
|
+
init_postReviewResult();
|
|
6924
|
+
MANIFEST_LABEL = "kody:goals-manifest";
|
|
6925
|
+
MANIFEST_TITLE = "Kody Goals Manifest";
|
|
6926
|
+
MANIFEST_START = "<!-- kody-goals-start -->";
|
|
6927
|
+
MANIFEST_END = "<!-- kody-goals-end -->";
|
|
6928
|
+
FINDING_LABEL = "kody:qa-finding";
|
|
6929
|
+
REPORT_JSON_OPEN = "<!-- KODY_QA_REPORT_JSON";
|
|
6930
|
+
REPORT_JSON_CLOSE = "-->";
|
|
6931
|
+
SEVERITY_COLORS = {
|
|
6932
|
+
P0: "b60205",
|
|
6933
|
+
P1: "d93f0b",
|
|
6934
|
+
P2: "fbca04",
|
|
6935
|
+
P3: "0e8a16"
|
|
6936
|
+
};
|
|
6937
|
+
createQaGoal = async (ctx, _profile, agentResult) => {
|
|
6938
|
+
if (!agentResult || agentResult.outcome !== "completed") {
|
|
6939
|
+
const reason = agentResult?.error ?? "agent did not complete";
|
|
6940
|
+
process.stderr.write(`[createQaGoal] ${reason}
|
|
6759
6941
|
`);
|
|
6760
|
-
|
|
6761
|
-
|
|
6762
|
-
|
|
6942
|
+
ctx.output.exitCode = 1;
|
|
6943
|
+
ctx.output.reason = reason;
|
|
6944
|
+
ctx.data.action = failedAction2(reason);
|
|
6763
6945
|
return;
|
|
6764
6946
|
}
|
|
6765
|
-
const
|
|
6766
|
-
|
|
6767
|
-
|
|
6768
|
-
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
goal.openTaskPrs = [];
|
|
6772
|
-
goal.phase = "idle";
|
|
6947
|
+
const finalText = agentResult.finalText.trim();
|
|
6948
|
+
if (!finalText) {
|
|
6949
|
+
process.stderr.write("[createQaGoal] agent produced no report body\n");
|
|
6950
|
+
ctx.output.exitCode = 1;
|
|
6951
|
+
ctx.output.reason = "empty report body";
|
|
6952
|
+
ctx.data.action = failedAction2("empty report body");
|
|
6773
6953
|
return;
|
|
6774
6954
|
}
|
|
6775
|
-
const
|
|
6776
|
-
|
|
6777
|
-
|
|
6778
|
-
)
|
|
6779
|
-
|
|
6780
|
-
|
|
6781
|
-
|
|
6782
|
-
|
|
6783
|
-
|
|
6784
|
-
|
|
6785
|
-
|
|
6786
|
-
|
|
6787
|
-
|
|
6788
|
-
|
|
6955
|
+
const { markdown } = splitReport(finalText);
|
|
6956
|
+
const verdict = detectVerdict(markdown);
|
|
6957
|
+
const existingIssue = ctx.args.issue;
|
|
6958
|
+
if (typeof existingIssue === "number" && existingIssue > 0) {
|
|
6959
|
+
try {
|
|
6960
|
+
postIssueComment(existingIssue, finalText, ctx.cwd);
|
|
6961
|
+
} catch (err) {
|
|
6962
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
6963
|
+
ctx.output.exitCode = 4;
|
|
6964
|
+
ctx.output.reason = `failed to comment on issue #${existingIssue}: ${msg}`;
|
|
6965
|
+
ctx.data.action = failedAction2(ctx.output.reason);
|
|
6966
|
+
return;
|
|
6967
|
+
}
|
|
6968
|
+
process.stdout.write(
|
|
6969
|
+
`
|
|
6970
|
+
QA_REPORT_POSTED=https://github.com/${ctx.config.github.owner}/${ctx.config.github.repo}/issues/${existingIssue} (verdict: ${verdict})
|
|
6971
|
+
`
|
|
6972
|
+
);
|
|
6973
|
+
ctx.data.action = qaAction(verdict, { issueNumber: existingIssue, mode: "comment" });
|
|
6974
|
+
ctx.output.exitCode = verdict === "FAIL" ? 1 : 0;
|
|
6975
|
+
return;
|
|
6976
|
+
}
|
|
6977
|
+
await promoteReportToGoal(ctx, finalText, ctx.args.scope, ctx.args.goal);
|
|
6789
6978
|
};
|
|
6790
6979
|
}
|
|
6791
6980
|
});
|
|
@@ -8003,12 +8192,18 @@ function parseDutyFilter(raw) {
|
|
|
8003
8192
|
function filterSlugs(slugs, onlyDuty) {
|
|
8004
8193
|
return onlyDuty ? slugs.filter((slug) => slug === onlyDuty) : slugs;
|
|
8005
8194
|
}
|
|
8006
|
-
function
|
|
8195
|
+
function listActivatedDutySlugs(projectRoot, storeRoot, activeStoreDuties) {
|
|
8007
8196
|
const seen = /* @__PURE__ */ new Set();
|
|
8008
8197
|
const out = [];
|
|
8009
|
-
for (const
|
|
8010
|
-
|
|
8011
|
-
|
|
8198
|
+
for (const slug of listDutyFolderSlugs(projectRoot)) {
|
|
8199
|
+
if (seen.has(slug)) continue;
|
|
8200
|
+
seen.add(slug);
|
|
8201
|
+
out.push(slug);
|
|
8202
|
+
}
|
|
8203
|
+
const active = new Set(activeStoreDuties ?? []);
|
|
8204
|
+
if (storeRoot && active.size > 0) {
|
|
8205
|
+
for (const slug of listDutyFolderSlugs(storeRoot)) {
|
|
8206
|
+
if (!active.has(slug) || seen.has(slug)) continue;
|
|
8012
8207
|
seen.add(slug);
|
|
8013
8208
|
out.push(slug);
|
|
8014
8209
|
}
|
|
@@ -8086,8 +8281,10 @@ var init_dispatchDutyFileTicks = __esm({
|
|
|
8086
8281
|
const onlyDuty = parseDutyFilter(ctx.args.duty);
|
|
8087
8282
|
const jobsPath = path28.join(ctx.cwd, jobsDir);
|
|
8088
8283
|
const storeJobsPath = getCompanyStoreDutiesRoot();
|
|
8089
|
-
const
|
|
8090
|
-
|
|
8284
|
+
const slugs = filterSlugs(
|
|
8285
|
+
listActivatedDutySlugs(jobsPath, storeJobsPath, ctx.config.company?.activeDuties),
|
|
8286
|
+
onlyDuty
|
|
8287
|
+
);
|
|
8091
8288
|
ctx.data.jobSlugCount = slugs.length;
|
|
8092
8289
|
if (slugs.length === 0) {
|
|
8093
8290
|
const filter = onlyDuty ? ` matching ${onlyDuty}` : "";
|
|
@@ -8278,38 +8475,6 @@ var init_dispatchDutyTicks = __esm({
|
|
|
8278
8475
|
}
|
|
8279
8476
|
});
|
|
8280
8477
|
|
|
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
8478
|
// src/scripts/dispatchNextTaskJob.ts
|
|
8314
8479
|
function taskJobToJob(job, issueArg) {
|
|
8315
8480
|
const target = typeof job.target === "number" ? job.target : typeof issueArg === "number" ? issueArg : void 0;
|
|
@@ -8687,133 +8852,6 @@ var init_failOnceTaskJob = __esm({
|
|
|
8687
8852
|
}
|
|
8688
8853
|
});
|
|
8689
8854
|
|
|
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
8855
|
// src/scripts/finalizeTerminal.ts
|
|
8818
8856
|
var DONE, FAILED, finalizeTerminal;
|
|
8819
8857
|
var init_finalizeTerminal = __esm({
|
|
@@ -9363,58 +9401,6 @@ var init_fixFlow = __esm({
|
|
|
9363
9401
|
}
|
|
9364
9402
|
});
|
|
9365
9403
|
|
|
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
9404
|
// src/scripts/initFlow.ts
|
|
9419
9405
|
import { execFileSync as execFileSync17 } from "child_process";
|
|
9420
9406
|
import * as fs31 from "fs";
|
|
@@ -9788,7 +9774,7 @@ var init_loadGoalState = __esm({
|
|
|
9788
9774
|
try {
|
|
9789
9775
|
const state = fetchGoalState(owner, repo, goalId, ctx.cwd);
|
|
9790
9776
|
if (!state) {
|
|
9791
|
-
process.stdout.write(`[goal-
|
|
9777
|
+
process.stdout.write(`[goal-manager] no goal state for ${goalId} on ${owner}/${repo}; nothing to tick
|
|
9792
9778
|
`);
|
|
9793
9779
|
ctx.skipAgent = true;
|
|
9794
9780
|
ctx.output.exitCode = 0;
|
|
@@ -9798,17 +9784,11 @@ var init_loadGoalState = __esm({
|
|
|
9798
9784
|
ctx.data.goal = {
|
|
9799
9785
|
id: goalId,
|
|
9800
9786
|
state: state.state,
|
|
9801
|
-
lastDispatchedIssue: state.lastDispatchedIssue,
|
|
9802
|
-
// Cache the full parsed object so saveGoalState can preserve `extra`.
|
|
9803
9787
|
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
9788
|
defaultBranch: ctx.config.git.defaultBranch
|
|
9809
9789
|
};
|
|
9810
9790
|
} catch (err) {
|
|
9811
|
-
process.stdout.write(`[goal-
|
|
9791
|
+
process.stdout.write(`[goal-manager] ${err instanceof Error ? err.message : String(err)}
|
|
9812
9792
|
`);
|
|
9813
9793
|
ctx.skipAgent = true;
|
|
9814
9794
|
ctx.output.exitCode = 0;
|
|
@@ -9933,23 +9913,7 @@ var init_staff = __esm({
|
|
|
9933
9913
|
"use strict";
|
|
9934
9914
|
init_companyStore();
|
|
9935
9915
|
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
|
-
};
|
|
9916
|
+
BUILTIN_PERSONAS = {};
|
|
9953
9917
|
}
|
|
9954
9918
|
});
|
|
9955
9919
|
|
|
@@ -10402,6 +10366,37 @@ var init_markFlowSuccess = __esm({
|
|
|
10402
10366
|
}
|
|
10403
10367
|
});
|
|
10404
10368
|
|
|
10369
|
+
// src/goal/operations.ts
|
|
10370
|
+
function fail(err) {
|
|
10371
|
+
if (err instanceof Error) {
|
|
10372
|
+
const lines = err.message.split("\n").filter(Boolean);
|
|
10373
|
+
return { ok: false, error: lines[0] ?? err.message };
|
|
10374
|
+
}
|
|
10375
|
+
return { ok: false, error: String(err) };
|
|
10376
|
+
}
|
|
10377
|
+
function commentOnIssue(issueNumber, body, cwd) {
|
|
10378
|
+
try {
|
|
10379
|
+
gh(["issue", "comment", String(issueNumber), "--body", body], { cwd });
|
|
10380
|
+
return { ok: true };
|
|
10381
|
+
} catch (err) {
|
|
10382
|
+
return fail(err);
|
|
10383
|
+
}
|
|
10384
|
+
}
|
|
10385
|
+
function mergePrSquash(prNumber, cwd) {
|
|
10386
|
+
try {
|
|
10387
|
+
gh(["pr", "merge", String(prNumber), "--squash", "--delete-branch"], { cwd });
|
|
10388
|
+
return { ok: true };
|
|
10389
|
+
} catch (err) {
|
|
10390
|
+
return fail(err);
|
|
10391
|
+
}
|
|
10392
|
+
}
|
|
10393
|
+
var init_operations = __esm({
|
|
10394
|
+
"src/goal/operations.ts"() {
|
|
10395
|
+
"use strict";
|
|
10396
|
+
init_issue();
|
|
10397
|
+
}
|
|
10398
|
+
});
|
|
10399
|
+
|
|
10405
10400
|
// src/scripts/mergeFlow.ts
|
|
10406
10401
|
function readPr(prNumber, cwd) {
|
|
10407
10402
|
const out = gh(["pr", "view", String(prNumber), "--json", "state,isDraft,mergeable,mergeStateStatus,title,url"], {
|
|
@@ -12159,7 +12154,7 @@ var init_previewBuildHelpers = __esm({
|
|
|
12159
12154
|
// src/scripts/previewBuildRun.ts
|
|
12160
12155
|
import { spawn as spawn4 } from "child_process";
|
|
12161
12156
|
async function runCmd(cmd, args, opts = {}) {
|
|
12162
|
-
await new Promise((
|
|
12157
|
+
await new Promise((resolve7, reject) => {
|
|
12163
12158
|
const child = spawn4(cmd, args, {
|
|
12164
12159
|
cwd: opts.cwd,
|
|
12165
12160
|
env: { ...process.env, ...opts.env ?? {} },
|
|
@@ -12171,7 +12166,7 @@ async function runCmd(cmd, args, opts = {}) {
|
|
|
12171
12166
|
}
|
|
12172
12167
|
child.on("error", reject);
|
|
12173
12168
|
child.on("close", (code) => {
|
|
12174
|
-
if (code === 0)
|
|
12169
|
+
if (code === 0) resolve7();
|
|
12175
12170
|
else reject(new Error(`${cmd} ${args.join(" ")} exited ${code}`));
|
|
12176
12171
|
});
|
|
12177
12172
|
});
|
|
@@ -12811,29 +12806,21 @@ var init_runTickScript = __esm({
|
|
|
12811
12806
|
}
|
|
12812
12807
|
});
|
|
12813
12808
|
|
|
12814
|
-
// src/scripts/
|
|
12815
|
-
var
|
|
12816
|
-
var
|
|
12817
|
-
"src/scripts/
|
|
12809
|
+
// src/scripts/saveManagedGoalState.ts
|
|
12810
|
+
var saveManagedGoalState;
|
|
12811
|
+
var init_saveManagedGoalState = __esm({
|
|
12812
|
+
"src/scripts/saveManagedGoalState.ts"() {
|
|
12818
12813
|
"use strict";
|
|
12819
12814
|
init_state2();
|
|
12820
|
-
|
|
12815
|
+
saveManagedGoalState = async (ctx) => {
|
|
12816
|
+
ctx.skipAgent = true;
|
|
12821
12817
|
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
|
-
};
|
|
12818
|
+
if (!goal?.raw) return;
|
|
12819
|
+
const original = typeof ctx.data.goalOriginalStateText === "string" ? ctx.data.goalOriginalStateText : "";
|
|
12820
|
+
const changedBeforeTimestamp = original !== serializeGoalState(goal.raw);
|
|
12821
|
+
const updated = changedBeforeTimestamp ? { ...goal.raw, updatedAt: nowIso() } : goal.raw;
|
|
12834
12822
|
ctx.data.goalPersistState = updated;
|
|
12835
|
-
ctx.data.goalPersistChanged =
|
|
12836
|
-
ctx.skipAgent = true;
|
|
12823
|
+
ctx.data.goalPersistChanged = original !== serializeGoalState(updated);
|
|
12837
12824
|
};
|
|
12838
12825
|
}
|
|
12839
12826
|
});
|
|
@@ -13125,7 +13112,7 @@ function stripAnsi2(s) {
|
|
|
13125
13112
|
return s.replace(ANSI_RE2, "");
|
|
13126
13113
|
}
|
|
13127
13114
|
function runCommand2(command, cwd) {
|
|
13128
|
-
return new Promise((
|
|
13115
|
+
return new Promise((resolve7) => {
|
|
13129
13116
|
const child = spawn5(command, {
|
|
13130
13117
|
cwd,
|
|
13131
13118
|
shell: true,
|
|
@@ -13152,11 +13139,11 @@ function runCommand2(command, cwd) {
|
|
|
13152
13139
|
}, TEST_TIMEOUT_MS);
|
|
13153
13140
|
child.on("exit", (code) => {
|
|
13154
13141
|
clearTimeout(timer);
|
|
13155
|
-
|
|
13142
|
+
resolve7({ exitCode: code ?? -1, output: Buffer.concat(buffers).toString("utf-8") });
|
|
13156
13143
|
});
|
|
13157
13144
|
child.on("error", (err) => {
|
|
13158
13145
|
clearTimeout(timer);
|
|
13159
|
-
|
|
13146
|
+
resolve7({ exitCode: -1, output: err.message });
|
|
13160
13147
|
});
|
|
13161
13148
|
});
|
|
13162
13149
|
}
|
|
@@ -13561,21 +13548,21 @@ function lineStream(stream) {
|
|
|
13561
13548
|
tryDeliver();
|
|
13562
13549
|
});
|
|
13563
13550
|
return {
|
|
13564
|
-
next: (timeoutMs) => new Promise((
|
|
13551
|
+
next: (timeoutMs) => new Promise((resolve7) => {
|
|
13565
13552
|
if (queue.length > 0) {
|
|
13566
|
-
|
|
13553
|
+
resolve7(queue.shift());
|
|
13567
13554
|
return;
|
|
13568
13555
|
}
|
|
13569
13556
|
if (ended) {
|
|
13570
|
-
|
|
13557
|
+
resolve7(null);
|
|
13571
13558
|
return;
|
|
13572
13559
|
}
|
|
13573
|
-
waiter =
|
|
13560
|
+
waiter = resolve7;
|
|
13574
13561
|
const t = setTimeout(
|
|
13575
13562
|
() => {
|
|
13576
|
-
if (waiter ===
|
|
13563
|
+
if (waiter === resolve7) {
|
|
13577
13564
|
waiter = null;
|
|
13578
|
-
|
|
13565
|
+
resolve7(null);
|
|
13579
13566
|
}
|
|
13580
13567
|
},
|
|
13581
13568
|
Math.max(0, timeoutMs)
|
|
@@ -13756,7 +13743,9 @@ var init_scripts = __esm({
|
|
|
13756
13743
|
"use strict";
|
|
13757
13744
|
init_abortUnfinishedGitOps();
|
|
13758
13745
|
init_advanceFlow();
|
|
13746
|
+
init_advanceManagedGoal();
|
|
13759
13747
|
init_appendCompanyActivity();
|
|
13748
|
+
init_applyDutyReports();
|
|
13760
13749
|
init_buildSyntheticPlugin();
|
|
13761
13750
|
init_checkCoverageWithRetry();
|
|
13762
13751
|
init_classifyByLabel();
|
|
@@ -13764,7 +13753,6 @@ var init_scripts = __esm({
|
|
|
13764
13753
|
init_commitGoalState();
|
|
13765
13754
|
init_composePrompt();
|
|
13766
13755
|
init_createQaGoal();
|
|
13767
|
-
init_deriveGoalPhase();
|
|
13768
13756
|
init_deriveQaScopeFromIssue();
|
|
13769
13757
|
init_diagMcp();
|
|
13770
13758
|
init_discoverQaContext();
|
|
@@ -13772,16 +13760,13 @@ var init_scripts = __esm({
|
|
|
13772
13760
|
init_dispatchClassified();
|
|
13773
13761
|
init_dispatchDutyFileTicks();
|
|
13774
13762
|
init_dispatchDutyTicks();
|
|
13775
|
-
init_dispatchNextTask();
|
|
13776
13763
|
init_dispatchNextTaskJob();
|
|
13777
13764
|
init_ensurePr();
|
|
13778
13765
|
init_failOnceTaskJob();
|
|
13779
|
-
init_finalizeGoal();
|
|
13780
13766
|
init_finalizeTerminal();
|
|
13781
13767
|
init_finishFlow();
|
|
13782
13768
|
init_fixCiFlow();
|
|
13783
13769
|
init_fixFlow();
|
|
13784
|
-
init_handleAbandonedGoal();
|
|
13785
13770
|
init_initFlow();
|
|
13786
13771
|
init_loadConventions();
|
|
13787
13772
|
init_loadCoverageRules();
|
|
@@ -13830,7 +13815,7 @@ var init_scripts = __esm({
|
|
|
13830
13815
|
init_runPreviewBuild();
|
|
13831
13816
|
init_runScheduledExecutableTick();
|
|
13832
13817
|
init_runTickScript();
|
|
13833
|
-
|
|
13818
|
+
init_saveManagedGoalState();
|
|
13834
13819
|
init_saveTaskState();
|
|
13835
13820
|
init_setCommentTarget();
|
|
13836
13821
|
init_setLifecycleLabel();
|
|
@@ -13890,12 +13875,9 @@ var init_scripts = __esm({
|
|
|
13890
13875
|
runScheduledExecutableTick,
|
|
13891
13876
|
runTickScript,
|
|
13892
13877
|
runPreviewBuild,
|
|
13878
|
+
advanceManagedGoal,
|
|
13893
13879
|
loadGoalState,
|
|
13894
|
-
|
|
13895
|
-
deriveGoalPhase,
|
|
13896
|
-
dispatchNextTask,
|
|
13897
|
-
finalizeGoal,
|
|
13898
|
-
saveGoalState
|
|
13880
|
+
saveManagedGoalState
|
|
13899
13881
|
};
|
|
13900
13882
|
postflightScripts = {
|
|
13901
13883
|
parseAgentResult: parseAgentResult2,
|
|
@@ -13930,6 +13912,7 @@ var init_scripts = __esm({
|
|
|
13930
13912
|
finalizeTerminal,
|
|
13931
13913
|
advanceFlow,
|
|
13932
13914
|
persistFlowState,
|
|
13915
|
+
applyDutyReports,
|
|
13933
13916
|
recordClassification,
|
|
13934
13917
|
dispatchClassified,
|
|
13935
13918
|
notifyTerminal,
|
|
@@ -14120,6 +14103,8 @@ async function runExecutable(profileName, input) {
|
|
|
14120
14103
|
}
|
|
14121
14104
|
const perExecutableModel = config.agent.perExecutable?.[profileName];
|
|
14122
14105
|
const modelSpec = perExecutableModel ? perExecutableModel : profile.claudeCode.model === "inherit" ? config.agent.model : profile.claudeCode.model;
|
|
14106
|
+
const profileHasThinkingTokens = typeof profile.claudeCode.maxThinkingTokens === "number" && profile.claudeCode.maxThinkingTokens > 0;
|
|
14107
|
+
const reasoningEffort = config.agent.perExecutableReasoningEffort?.[profileName] ?? profile.claudeCode.reasoningEffort ?? (profileHasThinkingTokens ? void 0 : config.agent.reasoningEffort);
|
|
14123
14108
|
let model;
|
|
14124
14109
|
try {
|
|
14125
14110
|
model = parseProviderModel(modelSpec);
|
|
@@ -14195,6 +14180,7 @@ async function runExecutable(profileName, input) {
|
|
|
14195
14180
|
pluginPaths: pluginPaths.length > 0 ? pluginPaths : void 0,
|
|
14196
14181
|
agents,
|
|
14197
14182
|
maxTurns: profile.claudeCode.maxTurns,
|
|
14183
|
+
reasoningEffort,
|
|
14198
14184
|
maxThinkingTokens: profile.claudeCode.maxThinkingTokens,
|
|
14199
14185
|
maxTurnTimeoutMs: typeof profile.claudeCode.maxTurnTimeoutSec === "number" ? Math.floor(profile.claudeCode.maxTurnTimeoutSec * 1e3) : void 0,
|
|
14200
14186
|
// DISCIPLINE leads so the stable, role-agnostic block sits at the front
|
|
@@ -14689,14 +14675,14 @@ async function runShellEntry(entry, ctx, profile) {
|
|
|
14689
14675
|
let killTimer;
|
|
14690
14676
|
let escalateTimer;
|
|
14691
14677
|
const result = await new Promise(
|
|
14692
|
-
(
|
|
14678
|
+
(resolve7) => {
|
|
14693
14679
|
let settled = false;
|
|
14694
14680
|
const settle = (code, signal, spawnErr) => {
|
|
14695
14681
|
if (settled) return;
|
|
14696
14682
|
settled = true;
|
|
14697
14683
|
if (killTimer) clearTimeout(killTimer);
|
|
14698
14684
|
if (escalateTimer) clearTimeout(escalateTimer);
|
|
14699
|
-
|
|
14685
|
+
resolve7({ code, signal, spawnErr });
|
|
14700
14686
|
};
|
|
14701
14687
|
child.on("error", (err) => settle(null, null, err));
|
|
14702
14688
|
child.on("close", (code, signal) => settle(code, signal));
|
|
@@ -14732,6 +14718,11 @@ async function runShellEntry(entry, ctx, profile) {
|
|
|
14732
14718
|
if (prUrlMatch?.[1]) ctx.output.prUrl = prUrlMatch[1].trim();
|
|
14733
14719
|
const reasonMatch = stdout.match(/^KODY_REASON=(.+)$/m);
|
|
14734
14720
|
if (reasonMatch?.[1]) ctx.output.reason = reasonMatch[1].trim();
|
|
14721
|
+
const dutyReports = parseDutyReportsFromText(stdout);
|
|
14722
|
+
if (dutyReports.length > 0) {
|
|
14723
|
+
const prior = Array.isArray(ctx.data.dutyReports) ? ctx.data.dutyReports : [];
|
|
14724
|
+
ctx.data.dutyReports = [...prior, ...dutyReports];
|
|
14725
|
+
}
|
|
14735
14726
|
if (timedOut) {
|
|
14736
14727
|
ctx.skipAgent = true;
|
|
14737
14728
|
const seconds = Math.round(timeoutMs / 1e3);
|
|
@@ -14781,6 +14772,7 @@ var init_executor = __esm({
|
|
|
14781
14772
|
init_config();
|
|
14782
14773
|
init_container();
|
|
14783
14774
|
init_discipline();
|
|
14775
|
+
init_dutyReport();
|
|
14784
14776
|
init_events();
|
|
14785
14777
|
init_lifecycleLabels();
|
|
14786
14778
|
init_litellm();
|
|
@@ -14791,7 +14783,7 @@ var init_executor = __esm({
|
|
|
14791
14783
|
init_subagents();
|
|
14792
14784
|
init_task_artifacts();
|
|
14793
14785
|
init_tools();
|
|
14794
|
-
MUTATING_POSTFLIGHTS = /* @__PURE__ */ new Set(["commitAndPush", "ensurePr"]);
|
|
14786
|
+
MUTATING_POSTFLIGHTS = /* @__PURE__ */ new Set(["commitAndPush", "ensurePr", "applyDutyReports"]);
|
|
14795
14787
|
MAX_CHAIN_HOPS = 60;
|
|
14796
14788
|
DEFAULT_SHELL_TIMEOUT_MS = 3e5;
|
|
14797
14789
|
SIGKILL_GRACE_MS = 5e3;
|
|
@@ -14897,7 +14889,8 @@ async function runJob(job, base) {
|
|
|
14897
14889
|
quiet: base.quiet,
|
|
14898
14890
|
preloadedData: Object.keys(preloadedData).length > 0 ? preloadedData : void 0
|
|
14899
14891
|
};
|
|
14900
|
-
|
|
14892
|
+
const shouldApplyResolvedDutyArgs = valid.executable === void 0 && resolvedDuty && profileName === resolvedDuty.executable;
|
|
14893
|
+
input.cliArgs = shouldApplyResolvedDutyArgs ? { ...resolvedDuty.cliArgs, ...input.cliArgs } : input.cliArgs;
|
|
14901
14894
|
const run = base.chain === false ? runExecutable : runExecutableChain;
|
|
14902
14895
|
return run(profileName, input);
|
|
14903
14896
|
}
|
|
@@ -16811,17 +16804,17 @@ function authOk(req, expected) {
|
|
|
16811
16804
|
return false;
|
|
16812
16805
|
}
|
|
16813
16806
|
function readJsonBody(req) {
|
|
16814
|
-
return new Promise((
|
|
16807
|
+
return new Promise((resolve7, reject) => {
|
|
16815
16808
|
const chunks = [];
|
|
16816
16809
|
req.on("data", (c) => chunks.push(c));
|
|
16817
16810
|
req.on("end", () => {
|
|
16818
16811
|
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
16819
16812
|
if (!raw.trim()) {
|
|
16820
|
-
|
|
16813
|
+
resolve7({});
|
|
16821
16814
|
return;
|
|
16822
16815
|
}
|
|
16823
16816
|
try {
|
|
16824
|
-
|
|
16817
|
+
resolve7(JSON.parse(raw));
|
|
16825
16818
|
} catch (err) {
|
|
16826
16819
|
reject(err instanceof Error ? err : new Error(String(err)));
|
|
16827
16820
|
}
|
|
@@ -17070,11 +17063,11 @@ async function brainServe(opts) {
|
|
|
17070
17063
|
model,
|
|
17071
17064
|
litellmUrl
|
|
17072
17065
|
});
|
|
17073
|
-
await new Promise((
|
|
17066
|
+
await new Promise((resolve7) => {
|
|
17074
17067
|
server.listen(port, "0.0.0.0", () => {
|
|
17075
17068
|
process.stdout.write(`[brain-serve] listening on 0.0.0.0:${port} (cwd=${opts.cwd})
|
|
17076
17069
|
`);
|
|
17077
|
-
|
|
17070
|
+
resolve7();
|
|
17078
17071
|
});
|
|
17079
17072
|
});
|
|
17080
17073
|
const shutdown = (signal) => {
|
|
@@ -17324,14 +17317,14 @@ async function startBrainProxy(opts) {
|
|
|
17324
17317
|
const { httpServer, handler } = buildBrainProxy(opts);
|
|
17325
17318
|
const port = opts.port ?? 0;
|
|
17326
17319
|
const host = opts.host ?? "127.0.0.1";
|
|
17327
|
-
await new Promise((
|
|
17320
|
+
await new Promise((resolve7) => httpServer.listen(port, host, () => resolve7()));
|
|
17328
17321
|
const addr = httpServer.address();
|
|
17329
17322
|
return {
|
|
17330
17323
|
httpServer,
|
|
17331
17324
|
port: addr.port,
|
|
17332
17325
|
url: `http://${host}:${addr.port}`,
|
|
17333
|
-
stop: () => new Promise((
|
|
17334
|
-
httpServer.close(() =>
|
|
17326
|
+
stop: () => new Promise((resolve7) => {
|
|
17327
|
+
httpServer.close(() => resolve7());
|
|
17335
17328
|
}),
|
|
17336
17329
|
handler
|
|
17337
17330
|
};
|
|
@@ -17481,23 +17474,23 @@ function buildMcpHttpServer(opts) {
|
|
|
17481
17474
|
httpServer,
|
|
17482
17475
|
routes,
|
|
17483
17476
|
port,
|
|
17484
|
-
stop: () => new Promise((
|
|
17477
|
+
stop: () => new Promise((resolve7) => {
|
|
17485
17478
|
let pending = transports.size;
|
|
17486
17479
|
if (pending === 0) {
|
|
17487
|
-
httpServer.close(() =>
|
|
17480
|
+
httpServer.close(() => resolve7());
|
|
17488
17481
|
return;
|
|
17489
17482
|
}
|
|
17490
17483
|
for (const transport of transports.values()) {
|
|
17491
17484
|
void transport.close().finally(() => {
|
|
17492
17485
|
pending--;
|
|
17493
|
-
if (pending === 0) httpServer.close(() =>
|
|
17486
|
+
if (pending === 0) httpServer.close(() => resolve7());
|
|
17494
17487
|
});
|
|
17495
17488
|
}
|
|
17496
17489
|
})
|
|
17497
17490
|
};
|
|
17498
17491
|
}
|
|
17499
17492
|
function listenMcpHttpServer(server, host = "127.0.0.1") {
|
|
17500
|
-
return new Promise((
|
|
17493
|
+
return new Promise((resolve7, reject) => {
|
|
17501
17494
|
server.httpServer.once("error", reject);
|
|
17502
17495
|
server.httpServer.listen(server.port, host, () => {
|
|
17503
17496
|
server.httpServer.off("error", reject);
|
|
@@ -17505,7 +17498,7 @@ function listenMcpHttpServer(server, host = "127.0.0.1") {
|
|
|
17505
17498
|
if (addr && typeof addr === "object") {
|
|
17506
17499
|
server.port = addr.port;
|
|
17507
17500
|
}
|
|
17508
|
-
|
|
17501
|
+
resolve7();
|
|
17509
17502
|
});
|
|
17510
17503
|
});
|
|
17511
17504
|
}
|
|
@@ -17644,7 +17637,7 @@ async function waitForNextUserMessage(opts) {
|
|
|
17644
17637
|
}
|
|
17645
17638
|
}
|
|
17646
17639
|
function sleep2(ms) {
|
|
17647
|
-
return new Promise((
|
|
17640
|
+
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
17648
17641
|
}
|
|
17649
17642
|
function currentBranch(cwd) {
|
|
17650
17643
|
try {
|
|
@@ -18759,14 +18752,14 @@ function sendJson2(res, status, body) {
|
|
|
18759
18752
|
res.end(JSON.stringify(body));
|
|
18760
18753
|
}
|
|
18761
18754
|
function readJsonBody2(req) {
|
|
18762
|
-
return new Promise((
|
|
18755
|
+
return new Promise((resolve7, reject) => {
|
|
18763
18756
|
const chunks = [];
|
|
18764
18757
|
req.on("data", (c) => chunks.push(c));
|
|
18765
18758
|
req.on("end", () => {
|
|
18766
18759
|
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
18767
|
-
if (!raw.trim()) return
|
|
18760
|
+
if (!raw.trim()) return resolve7({});
|
|
18768
18761
|
try {
|
|
18769
|
-
|
|
18762
|
+
resolve7(JSON.parse(raw));
|
|
18770
18763
|
} catch (err) {
|
|
18771
18764
|
reject(err instanceof Error ? err : new Error(String(err)));
|
|
18772
18765
|
}
|
|
@@ -18892,10 +18885,10 @@ async function poolServe() {
|
|
|
18892
18885
|
}
|
|
18893
18886
|
});
|
|
18894
18887
|
const apiHost = process.env.POOL_API_HOST ?? "::";
|
|
18895
|
-
await new Promise((
|
|
18888
|
+
await new Promise((resolve7) => {
|
|
18896
18889
|
server.listen(apiPort, apiHost, () => {
|
|
18897
18890
|
log(`listening on ${apiHost}:${apiPort} (min=${min}, app=${app}, region=${region})`);
|
|
18898
|
-
|
|
18891
|
+
resolve7();
|
|
18899
18892
|
});
|
|
18900
18893
|
});
|
|
18901
18894
|
const shutdown = (signal) => {
|
|
@@ -18934,17 +18927,17 @@ function authOk2(req, expected) {
|
|
|
18934
18927
|
return false;
|
|
18935
18928
|
}
|
|
18936
18929
|
function readJsonBody3(req) {
|
|
18937
|
-
return new Promise((
|
|
18930
|
+
return new Promise((resolve7, reject) => {
|
|
18938
18931
|
const chunks = [];
|
|
18939
18932
|
req.on("data", (c) => chunks.push(c));
|
|
18940
18933
|
req.on("end", () => {
|
|
18941
18934
|
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
18942
18935
|
if (!raw.trim()) {
|
|
18943
|
-
|
|
18936
|
+
resolve7({});
|
|
18944
18937
|
return;
|
|
18945
18938
|
}
|
|
18946
18939
|
try {
|
|
18947
|
-
|
|
18940
|
+
resolve7(JSON.parse(raw));
|
|
18948
18941
|
} catch (err) {
|
|
18949
18942
|
reject(err instanceof Error ? err : new Error(String(err)));
|
|
18950
18943
|
}
|
|
@@ -19023,13 +19016,13 @@ async function defaultRunJob(job) {
|
|
|
19023
19016
|
...interactive && job.idleExitMs ? { KODY_IDLE_EXIT_MS: String(job.idleExitMs) } : {},
|
|
19024
19017
|
...interactive && job.hardCapMs ? { KODY_HARD_CAP_MS: String(job.hardCapMs) } : {}
|
|
19025
19018
|
};
|
|
19026
|
-
const run = (cmd, args, cwd) => new Promise((
|
|
19019
|
+
const run = (cmd, args, cwd) => new Promise((resolve7) => {
|
|
19027
19020
|
const child = spawn8(cmd, args, { stdio: "inherit", env: childEnv, cwd });
|
|
19028
|
-
child.on("exit", (code) =>
|
|
19021
|
+
child.on("exit", (code) => resolve7(code ?? 0));
|
|
19029
19022
|
child.on("error", (err) => {
|
|
19030
19023
|
process.stderr.write(`[runner-serve] ${cmd} failed: ${err.message}
|
|
19031
19024
|
`);
|
|
19032
|
-
|
|
19025
|
+
resolve7(1);
|
|
19033
19026
|
});
|
|
19034
19027
|
});
|
|
19035
19028
|
process.stdout.write(`[runner-serve] job ${job.jobId}: cloning ${job.repo}@${branch}
|
|
@@ -19106,11 +19099,11 @@ async function runnerServe() {
|
|
|
19106
19099
|
const port = Number(process.env.PORT ?? DEFAULT_PORT2);
|
|
19107
19100
|
const server = buildServer2({ apiKey });
|
|
19108
19101
|
const host = process.env.RUNNER_HOST ?? "::";
|
|
19109
|
-
await new Promise((
|
|
19102
|
+
await new Promise((resolve7) => {
|
|
19110
19103
|
server.listen(port, host, () => {
|
|
19111
19104
|
process.stdout.write(`[runner-serve] listening on ${host}:${port} (idle, awaiting job)
|
|
19112
19105
|
`);
|
|
19113
|
-
|
|
19106
|
+
resolve7();
|
|
19114
19107
|
});
|
|
19115
19108
|
});
|
|
19116
19109
|
const shutdown = (signal) => {
|
|
@@ -19129,7 +19122,7 @@ async function runnerServe() {
|
|
|
19129
19122
|
init_config();
|
|
19130
19123
|
init_litellm();
|
|
19131
19124
|
import { spawn as spawn9 } from "child_process";
|
|
19132
|
-
function
|
|
19125
|
+
function parseTarget2(positional) {
|
|
19133
19126
|
if (!Array.isArray(positional) || positional.length === 0) return "none";
|
|
19134
19127
|
const first = String(positional[0]).toLowerCase();
|
|
19135
19128
|
if (first === "vscode" || first === "code") return "vscode";
|
|
@@ -19144,7 +19137,7 @@ function buildProxyEnv(url) {
|
|
|
19144
19137
|
};
|
|
19145
19138
|
}
|
|
19146
19139
|
async function serve(opts) {
|
|
19147
|
-
const target =
|
|
19140
|
+
const target = parseTarget2(opts.args);
|
|
19148
19141
|
const model = parseProviderModel(opts.config.agent.model);
|
|
19149
19142
|
const usesProxy = needsLitellmProxy(model);
|
|
19150
19143
|
let handle = null;
|
|
@@ -19179,14 +19172,14 @@ async function serve(opts) {
|
|
|
19179
19172
|
`);
|
|
19180
19173
|
const args = ["--dangerously-skip-permissions", "--model", model.model];
|
|
19181
19174
|
const child = spawn9("claude", args, { stdio: "inherit", env: editorEnv, cwd: opts.cwd });
|
|
19182
|
-
const exitCode = await new Promise((
|
|
19183
|
-
child.on("exit", (code) =>
|
|
19175
|
+
const exitCode = await new Promise((resolve7) => {
|
|
19176
|
+
child.on("exit", (code) => resolve7(code ?? 0));
|
|
19184
19177
|
child.on("error", (err) => {
|
|
19185
19178
|
process.stderr.write(`[kody serve] failed to launch Claude Code: ${err.message}
|
|
19186
19179
|
`);
|
|
19187
19180
|
process.stderr.write(` Install: https://docs.anthropic.com/claude/docs/claude-code
|
|
19188
19181
|
`);
|
|
19189
|
-
|
|
19182
|
+
resolve7(1);
|
|
19190
19183
|
});
|
|
19191
19184
|
});
|
|
19192
19185
|
killProxy();
|