@kody-ade/kody-engine 0.4.234 → 0.4.236
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/kody.js
CHANGED
|
@@ -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.236",
|
|
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",
|
|
@@ -2385,21 +2385,6 @@ function getCompanyStoreExecutablesRoot() {
|
|
|
2385
2385
|
function getCompanyStoreDutiesRoot() {
|
|
2386
2386
|
return getCompanyStoreAssetRoot("duties");
|
|
2387
2387
|
}
|
|
2388
|
-
function getBuiltinJobsRoot() {
|
|
2389
|
-
const here = path8.dirname(new URL(import.meta.url).pathname);
|
|
2390
|
-
const candidates = [
|
|
2391
|
-
path8.join(here, "jobs"),
|
|
2392
|
-
// dev: src/
|
|
2393
|
-
path8.join(here, "..", "jobs"),
|
|
2394
|
-
// built: dist/bin → dist/jobs
|
|
2395
|
-
path8.join(here, "..", "src", "jobs")
|
|
2396
|
-
// fallback
|
|
2397
|
-
];
|
|
2398
|
-
for (const c of candidates) {
|
|
2399
|
-
if (fs8.existsSync(c) && fs8.statSync(c).isDirectory()) return c;
|
|
2400
|
-
}
|
|
2401
|
-
return candidates[0];
|
|
2402
|
-
}
|
|
2403
2388
|
function getBuiltinDutiesRoot() {
|
|
2404
2389
|
const here = path8.dirname(new URL(import.meta.url).pathname);
|
|
2405
2390
|
const candidates = [
|
|
@@ -2415,23 +2400,6 @@ function getBuiltinDutiesRoot() {
|
|
|
2415
2400
|
}
|
|
2416
2401
|
return candidates[0];
|
|
2417
2402
|
}
|
|
2418
|
-
function listBuiltinJobs(root = getBuiltinJobsRoot()) {
|
|
2419
|
-
if (!fs8.existsSync(root) || !fs8.statSync(root).isDirectory()) return [];
|
|
2420
|
-
const out = [];
|
|
2421
|
-
for (const ent of fs8.readdirSync(root, { withFileTypes: true })) {
|
|
2422
|
-
if (ent.name.startsWith("_") || ent.name.startsWith(".")) continue;
|
|
2423
|
-
const full = path8.join(root, ent.name);
|
|
2424
|
-
if (ent.isDirectory()) {
|
|
2425
|
-
const profilePath = path8.join(full, DUTY_PROFILE_FILE);
|
|
2426
|
-
const bodyPath = path8.join(full, DUTY_BODY_FILE);
|
|
2427
|
-
if (!fs8.existsSync(profilePath) || !fs8.statSync(profilePath).isFile()) continue;
|
|
2428
|
-
if (!fs8.existsSync(bodyPath) || !fs8.statSync(bodyPath).isFile()) continue;
|
|
2429
|
-
out.push({ slug: ent.name, dir: full, profilePath, bodyPath });
|
|
2430
|
-
}
|
|
2431
|
-
}
|
|
2432
|
-
out.sort((a, b) => a.slug.localeCompare(b.slug));
|
|
2433
|
-
return out;
|
|
2434
|
-
}
|
|
2435
2403
|
function getExecutableRoots() {
|
|
2436
2404
|
const storeRoot = getCompanyStoreExecutablesRoot();
|
|
2437
2405
|
return [getProjectExecutablesRoot(), ...storeRoot ? [storeRoot] : [], getExecutablesRoot()];
|
|
@@ -2480,9 +2448,12 @@ function listDutyActions(projectDutiesRoot = getProjectDutiesRoot()) {
|
|
|
2480
2448
|
out.push(action);
|
|
2481
2449
|
};
|
|
2482
2450
|
const roots = getDutyRoots(projectDutiesRoot);
|
|
2451
|
+
const executableRoots = getExecutableRoots();
|
|
2483
2452
|
for (const action of listFolderDutyActions(roots[0], "project-folder")) add(action);
|
|
2453
|
+
for (const action of listExecutableDutyActions(executableRoots[0], "project-executable")) add(action);
|
|
2484
2454
|
if (roots.length === 3) {
|
|
2485
2455
|
for (const action of listFolderDutyActions(roots[1], "company-store")) add(action);
|
|
2456
|
+
for (const action of listExecutableDutyActions(executableRoots[1], "company-store-executable")) add(action);
|
|
2486
2457
|
for (const action of listBuiltinDutyActions(roots[2])) add(action);
|
|
2487
2458
|
} else {
|
|
2488
2459
|
for (const action of listBuiltinDutyActions(roots[1])) add(action);
|
|
@@ -2527,6 +2498,34 @@ function executableDeclaresInput(executable, inputName) {
|
|
|
2527
2498
|
function isSafeName(name) {
|
|
2528
2499
|
return /^[a-z][a-z0-9-]*$/.test(name) && !name.includes("..");
|
|
2529
2500
|
}
|
|
2501
|
+
function listExecutableDutyActions(root, source) {
|
|
2502
|
+
if (!fs8.existsSync(root) || !fs8.statSync(root).isDirectory()) return [];
|
|
2503
|
+
const out = [];
|
|
2504
|
+
for (const ent of fs8.readdirSync(root, { withFileTypes: true })) {
|
|
2505
|
+
if (!ent.isDirectory() || !isSafeName(ent.name)) continue;
|
|
2506
|
+
const profilePath = path8.join(root, ent.name, DUTY_PROFILE_FILE);
|
|
2507
|
+
if (!fs8.existsSync(profilePath) || !fs8.statSync(profilePath).isFile()) continue;
|
|
2508
|
+
try {
|
|
2509
|
+
const raw = JSON.parse(fs8.readFileSync(profilePath, "utf-8"));
|
|
2510
|
+
const action = typeof raw.action === "string" && raw.action.trim() ? raw.action.trim() : "";
|
|
2511
|
+
if (!action) continue;
|
|
2512
|
+
if (!PUBLIC_EXECUTABLE_ACTION_ROLES.has(String(raw.role))) continue;
|
|
2513
|
+
if (!PUBLIC_EXECUTABLE_CAPABILITY_KINDS.has(String(raw.capabilityKind))) continue;
|
|
2514
|
+
if (!Array.isArray(raw.inputs)) continue;
|
|
2515
|
+
out.push({
|
|
2516
|
+
action,
|
|
2517
|
+
duty: ent.name,
|
|
2518
|
+
executable: ent.name,
|
|
2519
|
+
cliArgs: {},
|
|
2520
|
+
source,
|
|
2521
|
+
describe: typeof raw.describe === "string" ? raw.describe : void 0,
|
|
2522
|
+
profilePath
|
|
2523
|
+
});
|
|
2524
|
+
} catch {
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
return out.sort((a, b) => a.action.localeCompare(b.action));
|
|
2528
|
+
}
|
|
2530
2529
|
function listFolderDutyActions(root, source) {
|
|
2531
2530
|
if (!fs8.existsSync(root) || !fs8.statSync(root).isDirectory()) return [];
|
|
2532
2531
|
const out = [];
|
|
@@ -2607,11 +2606,14 @@ function parseGenericFlags(argv) {
|
|
|
2607
2606
|
if (positional.length > 0) args._ = positional;
|
|
2608
2607
|
return args;
|
|
2609
2608
|
}
|
|
2609
|
+
var PUBLIC_EXECUTABLE_ACTION_ROLES, PUBLIC_EXECUTABLE_CAPABILITY_KINDS;
|
|
2610
2610
|
var init_registry = __esm({
|
|
2611
2611
|
"src/registry.ts"() {
|
|
2612
2612
|
"use strict";
|
|
2613
2613
|
init_companyStore();
|
|
2614
2614
|
init_dutyFolders();
|
|
2615
|
+
PUBLIC_EXECUTABLE_ACTION_ROLES = /* @__PURE__ */ new Set(["primitive", "orchestrator", "container", "watch", "utility"]);
|
|
2616
|
+
PUBLIC_EXECUTABLE_CAPABILITY_KINDS = /* @__PURE__ */ new Set(["observe", "act", "verify"]);
|
|
2615
2617
|
}
|
|
2616
2618
|
});
|
|
2617
2619
|
|
|
@@ -4768,6 +4770,125 @@ var init_dutyReport = __esm({
|
|
|
4768
4770
|
}
|
|
4769
4771
|
});
|
|
4770
4772
|
|
|
4773
|
+
// src/dutyResult.ts
|
|
4774
|
+
function parseDutyResultsFromText(text) {
|
|
4775
|
+
const results = [];
|
|
4776
|
+
for (const match of text.matchAll(RESULT_LINE)) {
|
|
4777
|
+
const raw = match[1]?.trim();
|
|
4778
|
+
if (!raw) continue;
|
|
4779
|
+
try {
|
|
4780
|
+
const parsed = parseDutyResult(JSON.parse(raw));
|
|
4781
|
+
if (parsed) results.push(parsed);
|
|
4782
|
+
} catch {
|
|
4783
|
+
}
|
|
4784
|
+
}
|
|
4785
|
+
return results;
|
|
4786
|
+
}
|
|
4787
|
+
function parseDutyResult(raw) {
|
|
4788
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
4789
|
+
const obj = raw;
|
|
4790
|
+
if (obj.version !== 1) return null;
|
|
4791
|
+
if (!isDutyResultStatus(obj.status)) return null;
|
|
4792
|
+
const summary = typeof obj.summary === "string" ? obj.summary.trim() : "";
|
|
4793
|
+
if (!summary) return null;
|
|
4794
|
+
const facts = parseFacts2(obj.facts);
|
|
4795
|
+
if (!facts) return null;
|
|
4796
|
+
const artifacts = parseArtifacts(obj.artifacts);
|
|
4797
|
+
if (!artifacts) return null;
|
|
4798
|
+
return {
|
|
4799
|
+
version: 1,
|
|
4800
|
+
status: obj.status,
|
|
4801
|
+
summary,
|
|
4802
|
+
facts,
|
|
4803
|
+
artifacts
|
|
4804
|
+
};
|
|
4805
|
+
}
|
|
4806
|
+
function applyDutyResultToObjectiveState(state, result, evidenceOverride) {
|
|
4807
|
+
const priorFacts = parseFacts2(state.extra.facts) ?? {};
|
|
4808
|
+
const nextFacts = { ...priorFacts };
|
|
4809
|
+
for (const [key, value] of Object.entries(result.facts)) {
|
|
4810
|
+
if (CONTROL_FACT_KEYS2.has(key)) continue;
|
|
4811
|
+
nextFacts[key] = value;
|
|
4812
|
+
}
|
|
4813
|
+
const evidence = evidenceOverride || (typeof nextFacts.pendingEvidence === "string" ? nextFacts.pendingEvidence : "");
|
|
4814
|
+
if (evidence) {
|
|
4815
|
+
if (result.status === "pass") nextFacts[evidence] = true;
|
|
4816
|
+
if (result.status === "fail" || result.status === "blocked") nextFacts[evidence] = false;
|
|
4817
|
+
if ((result.status === "pass" || result.status === "fail" || result.status === "blocked") && nextFacts.pendingEvidence === evidence) {
|
|
4818
|
+
delete nextFacts.pendingEvidence;
|
|
4819
|
+
}
|
|
4820
|
+
}
|
|
4821
|
+
const blockers = parseStringArray2(state.extra.blockers) ?? [];
|
|
4822
|
+
if ((result.status === "fail" || result.status === "blocked") && !blockers.includes(result.summary)) {
|
|
4823
|
+
blockers.push(result.summary);
|
|
4824
|
+
}
|
|
4825
|
+
return {
|
|
4826
|
+
...state,
|
|
4827
|
+
extra: {
|
|
4828
|
+
...state.extra,
|
|
4829
|
+
facts: nextFacts,
|
|
4830
|
+
blockers,
|
|
4831
|
+
lastDutyResult: {
|
|
4832
|
+
status: result.status,
|
|
4833
|
+
summary: result.summary,
|
|
4834
|
+
facts: result.facts,
|
|
4835
|
+
artifacts: result.artifacts
|
|
4836
|
+
}
|
|
4837
|
+
}
|
|
4838
|
+
};
|
|
4839
|
+
}
|
|
4840
|
+
function isDutyResultStatus(value) {
|
|
4841
|
+
return typeof value === "string" && STATUSES.has(value);
|
|
4842
|
+
}
|
|
4843
|
+
function parseFacts2(raw) {
|
|
4844
|
+
if (raw === void 0) return {};
|
|
4845
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
4846
|
+
const facts = {};
|
|
4847
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
4848
|
+
if (!key.trim()) return null;
|
|
4849
|
+
if (CONTROL_FACT_KEYS2.has(key)) continue;
|
|
4850
|
+
facts[key] = value;
|
|
4851
|
+
}
|
|
4852
|
+
return facts;
|
|
4853
|
+
}
|
|
4854
|
+
function parseStringArray2(raw) {
|
|
4855
|
+
if (!Array.isArray(raw)) return null;
|
|
4856
|
+
const out = [];
|
|
4857
|
+
for (const item of raw) {
|
|
4858
|
+
if (typeof item !== "string") return null;
|
|
4859
|
+
out.push(item);
|
|
4860
|
+
}
|
|
4861
|
+
return out;
|
|
4862
|
+
}
|
|
4863
|
+
function parseArtifacts(raw) {
|
|
4864
|
+
if (raw === void 0) return [];
|
|
4865
|
+
if (!Array.isArray(raw)) return null;
|
|
4866
|
+
const artifacts = [];
|
|
4867
|
+
for (const item of raw) {
|
|
4868
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) return null;
|
|
4869
|
+
const rawArtifact = item;
|
|
4870
|
+
const label = typeof rawArtifact.label === "string" ? rawArtifact.label.trim() : "";
|
|
4871
|
+
const url = typeof rawArtifact.url === "string" ? rawArtifact.url.trim() : "";
|
|
4872
|
+
const artifactPath = typeof rawArtifact.path === "string" ? rawArtifact.path.trim() : "";
|
|
4873
|
+
if (!label || !url && !artifactPath) return null;
|
|
4874
|
+
artifacts.push({
|
|
4875
|
+
label,
|
|
4876
|
+
...url ? { url } : {},
|
|
4877
|
+
...artifactPath ? { path: artifactPath } : {}
|
|
4878
|
+
});
|
|
4879
|
+
}
|
|
4880
|
+
return artifacts;
|
|
4881
|
+
}
|
|
4882
|
+
var RESULT_LINE, STATUSES, CONTROL_FACT_KEYS2;
|
|
4883
|
+
var init_dutyResult = __esm({
|
|
4884
|
+
"src/dutyResult.ts"() {
|
|
4885
|
+
"use strict";
|
|
4886
|
+
RESULT_LINE = /^KODY_DUTY_RESULT=(.+)$/gm;
|
|
4887
|
+
STATUSES = /* @__PURE__ */ new Set(["pass", "fail", "blocked", "changed", "noop"]);
|
|
4888
|
+
CONTROL_FACT_KEYS2 = /* @__PURE__ */ new Set(["blockers", "destination", "duties", "route", "stage", "state"]);
|
|
4889
|
+
}
|
|
4890
|
+
});
|
|
4891
|
+
|
|
4771
4892
|
// src/lifecycleLabels.ts
|
|
4772
4893
|
function groupOf(label) {
|
|
4773
4894
|
const idx = label.indexOf(":");
|
|
@@ -6760,49 +6881,88 @@ function collectReports(raw, agentResult) {
|
|
|
6760
6881
|
const out = [];
|
|
6761
6882
|
if (Array.isArray(raw)) {
|
|
6762
6883
|
for (const item of raw) {
|
|
6763
|
-
|
|
6884
|
+
const parsed = parseDutyReport(item);
|
|
6885
|
+
if (parsed) out.push(parsed);
|
|
6764
6886
|
}
|
|
6765
6887
|
}
|
|
6766
6888
|
if (agentResult?.finalText) out.push(...parseDutyReportsFromText(agentResult.finalText));
|
|
6767
6889
|
return out;
|
|
6768
6890
|
}
|
|
6769
|
-
function
|
|
6770
|
-
|
|
6891
|
+
function collectResults(raw, agentResult) {
|
|
6892
|
+
const out = [];
|
|
6893
|
+
if (Array.isArray(raw)) {
|
|
6894
|
+
for (const item of raw) {
|
|
6895
|
+
const parsed = parseDutyResult(item);
|
|
6896
|
+
if (parsed) out.push(parsed);
|
|
6897
|
+
}
|
|
6898
|
+
}
|
|
6899
|
+
if (agentResult?.finalText) out.push(...parseDutyResultsFromText(agentResult.finalText));
|
|
6900
|
+
return out;
|
|
6771
6901
|
}
|
|
6772
|
-
function
|
|
6773
|
-
const
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
|
|
6777
|
-
|
|
6902
|
+
function groupGoalReports(reports) {
|
|
6903
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
6904
|
+
for (const report of reports) {
|
|
6905
|
+
if (report.target.type !== "goal") continue;
|
|
6906
|
+
const list = grouped.get(report.target.id) ?? [];
|
|
6907
|
+
list.push(report);
|
|
6908
|
+
grouped.set(report.target.id, list);
|
|
6909
|
+
}
|
|
6910
|
+
return grouped;
|
|
6911
|
+
}
|
|
6912
|
+
function describeMessage(goalId, reports, results) {
|
|
6913
|
+
const pieces = [];
|
|
6914
|
+
if (reports && reports.length > 0) pieces.push(`report=${reports.length}`);
|
|
6915
|
+
if (results.length > 0) pieces.push(`result=${results.map((result) => result.status).join(",")}`);
|
|
6916
|
+
return `Apply duty output to ${goalId}${pieces.length > 0 ? ` (${pieces.join("; ")})` : ""}`;
|
|
6778
6917
|
}
|
|
6779
6918
|
var applyDutyReports;
|
|
6780
6919
|
var init_applyDutyReports = __esm({
|
|
6781
6920
|
"src/scripts/applyDutyReports.ts"() {
|
|
6782
6921
|
"use strict";
|
|
6783
6922
|
init_dutyReport();
|
|
6923
|
+
init_dutyResult();
|
|
6784
6924
|
init_state2();
|
|
6785
6925
|
init_stateStore();
|
|
6786
6926
|
applyDutyReports = async (ctx, _profile, agentResult) => {
|
|
6787
6927
|
const reports = collectReports(ctx.data.dutyReports, agentResult);
|
|
6788
|
-
|
|
6928
|
+
const results = collectResults(ctx.data.dutyResults, agentResult);
|
|
6929
|
+
const resultGoalId = typeof ctx.args.goal === "string" && ctx.args.goal.length > 0 ? ctx.args.goal : null;
|
|
6930
|
+
if (reports.length === 0 && (results.length === 0 || !resultGoalId)) return;
|
|
6789
6931
|
const owner = ctx.config.github?.owner;
|
|
6790
6932
|
const repo = ctx.config.github?.repo;
|
|
6791
6933
|
if (!owner || !repo) {
|
|
6792
6934
|
process.stderr.write("[kody duty-report] missing github owner/repo; cannot apply reports\n");
|
|
6793
6935
|
return;
|
|
6794
6936
|
}
|
|
6795
|
-
const
|
|
6796
|
-
|
|
6797
|
-
|
|
6937
|
+
const reportsByGoal = groupGoalReports(reports);
|
|
6938
|
+
const goalIds = new Set(reportsByGoal.keys());
|
|
6939
|
+
if (results.length > 0 && resultGoalId) goalIds.add(resultGoalId);
|
|
6940
|
+
for (const goalId of goalIds) {
|
|
6941
|
+
const prior = fetchGoalState(owner, repo, goalId, ctx.cwd);
|
|
6798
6942
|
if (!prior) {
|
|
6799
|
-
process.stderr.write(`[kody duty-report] goal ${
|
|
6943
|
+
process.stderr.write(`[kody duty-report] goal ${goalId} missing on kody-state; report skipped
|
|
6800
6944
|
`);
|
|
6801
6945
|
continue;
|
|
6802
6946
|
}
|
|
6803
|
-
|
|
6947
|
+
let next = prior;
|
|
6948
|
+
for (const report of reportsByGoal.get(goalId) ?? []) {
|
|
6949
|
+
next = applyDutyReportToGoalState(next, report);
|
|
6950
|
+
}
|
|
6951
|
+
if (goalId === resultGoalId) {
|
|
6952
|
+
const evidence = typeof ctx.args.evidence === "string" && ctx.args.evidence.length > 0 ? ctx.args.evidence : void 0;
|
|
6953
|
+
for (const result of results) {
|
|
6954
|
+
next = applyDutyResultToObjectiveState(next, result, evidence);
|
|
6955
|
+
}
|
|
6956
|
+
}
|
|
6804
6957
|
if (serializeGoalState(next) === serializeGoalState(prior)) continue;
|
|
6805
|
-
putGoalState(
|
|
6958
|
+
putGoalState(
|
|
6959
|
+
owner,
|
|
6960
|
+
repo,
|
|
6961
|
+
goalId,
|
|
6962
|
+
{ ...next, updatedAt: nowIso() },
|
|
6963
|
+
describeMessage(goalId, reportsByGoal.get(goalId), results),
|
|
6964
|
+
ctx.cwd
|
|
6965
|
+
);
|
|
6806
6966
|
}
|
|
6807
6967
|
};
|
|
6808
6968
|
}
|
|
@@ -9922,26 +10082,6 @@ function performInit(cwd, force) {
|
|
|
9922
10082
|
fs31.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
|
|
9923
10083
|
wrote.push(".github/workflows/kody.yml");
|
|
9924
10084
|
}
|
|
9925
|
-
const builtinJobs = listBuiltinJobs();
|
|
9926
|
-
if (builtinJobs.length > 0) {
|
|
9927
|
-
const jobsDir = path30.join(cwd, ".kody", "duties");
|
|
9928
|
-
fs31.mkdirSync(jobsDir, { recursive: true });
|
|
9929
|
-
for (const job of builtinJobs) {
|
|
9930
|
-
const targetDir = path30.join(jobsDir, job.slug);
|
|
9931
|
-
const relProfile = path30.join(".kody", "duties", job.slug, "profile.json");
|
|
9932
|
-
const relBody = path30.join(".kody", "duties", job.slug, "duty.md");
|
|
9933
|
-
if (fs31.existsSync(targetDir) && fs31.existsSync(path30.join(targetDir, "profile.json")) && !force) {
|
|
9934
|
-
skipped.push(relProfile);
|
|
9935
|
-
skipped.push(relBody);
|
|
9936
|
-
continue;
|
|
9937
|
-
}
|
|
9938
|
-
fs31.mkdirSync(targetDir, { recursive: true });
|
|
9939
|
-
fs31.writeFileSync(path30.join(targetDir, "profile.json"), fs31.readFileSync(job.profilePath, "utf-8"));
|
|
9940
|
-
fs31.writeFileSync(path30.join(targetDir, "duty.md"), fs31.readFileSync(job.bodyPath, "utf-8"));
|
|
9941
|
-
wrote.push(relProfile);
|
|
9942
|
-
wrote.push(relBody);
|
|
9943
|
-
}
|
|
9944
|
-
}
|
|
9945
10085
|
const staffDir = path30.join(cwd, ".kody", "staff");
|
|
9946
10086
|
const staffPath = path30.join(staffDir, "kody.md");
|
|
9947
10087
|
if (fs31.existsSync(staffPath) && !force) {
|
|
@@ -10007,7 +10147,7 @@ jobs:
|
|
|
10007
10147
|
python-version: "3.12"
|
|
10008
10148
|
- env:
|
|
10009
10149
|
GH_TOKEN: \${{ secrets.KODY_TOKEN || github.token }}
|
|
10010
|
-
run: npx -y -p @kody-ade/kody-engine@latest kody-engine ${name}
|
|
10150
|
+
run: npx -y -p @kody-ade/kody-engine@latest kody-engine exec ${name}
|
|
10011
10151
|
`;
|
|
10012
10152
|
}
|
|
10013
10153
|
var WORKFLOW_TEMPLATE, DEFAULT_STAFF_PERSONA, initFlow;
|
|
@@ -14454,6 +14594,26 @@ function isMutatingPostflight(scriptName) {
|
|
|
14454
14594
|
function shouldBlockMutatingPostflight(scriptName, exitCode) {
|
|
14455
14595
|
return isMutatingPostflight(scriptName) && (exitCode ?? 0) !== 0;
|
|
14456
14596
|
}
|
|
14597
|
+
function collectShellSideChannels(ctx, stdout) {
|
|
14598
|
+
if (/^KODY_SKIP_AGENT=true\s*$/m.test(stdout)) {
|
|
14599
|
+
ctx.skipAgent = true;
|
|
14600
|
+
if (ctx.output.exitCode === void 0) ctx.output.exitCode = 0;
|
|
14601
|
+
}
|
|
14602
|
+
const prUrlMatch = stdout.match(/^KODY_PR_URL=(.+)$/m);
|
|
14603
|
+
if (prUrlMatch?.[1]) ctx.output.prUrl = prUrlMatch[1].trim();
|
|
14604
|
+
const reasonMatch = stdout.match(/^KODY_REASON=(.+)$/m);
|
|
14605
|
+
if (reasonMatch?.[1]) ctx.output.reason = reasonMatch[1].trim();
|
|
14606
|
+
const dutyReports = parseDutyReportsFromText(stdout);
|
|
14607
|
+
if (dutyReports.length > 0) {
|
|
14608
|
+
const prior = Array.isArray(ctx.data.dutyReports) ? ctx.data.dutyReports : [];
|
|
14609
|
+
ctx.data.dutyReports = [...prior, ...dutyReports];
|
|
14610
|
+
}
|
|
14611
|
+
const dutyResults = parseDutyResultsFromText(stdout);
|
|
14612
|
+
if (dutyResults.length > 0) {
|
|
14613
|
+
const prior = Array.isArray(ctx.data.dutyResults) ? ctx.data.dutyResults : [];
|
|
14614
|
+
ctx.data.dutyResults = [...prior, ...dutyResults];
|
|
14615
|
+
}
|
|
14616
|
+
}
|
|
14457
14617
|
function operatorRequestBlock(why) {
|
|
14458
14618
|
const text = why.trim();
|
|
14459
14619
|
if (!text) return null;
|
|
@@ -15158,19 +15318,7 @@ async function runShellEntry(entry, ctx, profile) {
|
|
|
15158
15318
|
ctx.output.reason = `shell '${shellName}' failed to spawn: ${result.spawnErr.message}`;
|
|
15159
15319
|
return;
|
|
15160
15320
|
}
|
|
15161
|
-
|
|
15162
|
-
ctx.skipAgent = true;
|
|
15163
|
-
if (ctx.output.exitCode === void 0) ctx.output.exitCode = 0;
|
|
15164
|
-
}
|
|
15165
|
-
const prUrlMatch = stdout.match(/^KODY_PR_URL=(.+)$/m);
|
|
15166
|
-
if (prUrlMatch?.[1]) ctx.output.prUrl = prUrlMatch[1].trim();
|
|
15167
|
-
const reasonMatch = stdout.match(/^KODY_REASON=(.+)$/m);
|
|
15168
|
-
if (reasonMatch?.[1]) ctx.output.reason = reasonMatch[1].trim();
|
|
15169
|
-
const dutyReports = parseDutyReportsFromText(stdout);
|
|
15170
|
-
if (dutyReports.length > 0) {
|
|
15171
|
-
const prior = Array.isArray(ctx.data.dutyReports) ? ctx.data.dutyReports : [];
|
|
15172
|
-
ctx.data.dutyReports = [...prior, ...dutyReports];
|
|
15173
|
-
}
|
|
15321
|
+
collectShellSideChannels(ctx, stdout);
|
|
15174
15322
|
if (timedOut) {
|
|
15175
15323
|
ctx.skipAgent = true;
|
|
15176
15324
|
const seconds = Math.round(timeoutMs / 1e3);
|
|
@@ -15221,6 +15369,7 @@ var init_executor = __esm({
|
|
|
15221
15369
|
init_container();
|
|
15222
15370
|
init_discipline();
|
|
15223
15371
|
init_dutyReport();
|
|
15372
|
+
init_dutyResult();
|
|
15224
15373
|
init_events();
|
|
15225
15374
|
init_lifecycleLabels();
|
|
15226
15375
|
init_litellm();
|
|
@@ -15291,7 +15440,8 @@ async function runJob(job, base) {
|
|
|
15291
15440
|
const resolvedDuty = action ? resolveDutyAction(action, projectDutiesRoot) : null;
|
|
15292
15441
|
const dutyIdentity = valid.duty ?? resolvedDuty?.duty;
|
|
15293
15442
|
const dutyContext = loadDutyContext(dutyIdentity, base.cwd);
|
|
15294
|
-
|
|
15443
|
+
const explicitExecutableOnly = valid.executable !== void 0 && (valid.action === void 0 || valid.action === valid.executable) && (valid.duty === void 0 || valid.duty === valid.executable);
|
|
15444
|
+
if (!resolvedDuty && !dutyContext && !explicitExecutableOnly) {
|
|
15295
15445
|
throw new InvalidJobError(`job duty not found: ${action ?? valid.duty ?? "<none>"}`);
|
|
15296
15446
|
}
|
|
15297
15447
|
const dutySelectedExecutable = resolvedDuty?.executable ?? dutyContext?.config.executable ?? dutyContext?.config.executables?.[0] ?? (dutyContext?.config.tickScript ? "duty-tick-scripted" : void 0);
|
|
@@ -16789,7 +16939,13 @@ async function runCi(argv) {
|
|
|
16789
16939
|
}
|
|
16790
16940
|
if (forceRunAction) {
|
|
16791
16941
|
const config = earlyConfig ?? loadConfig(cwd);
|
|
16792
|
-
const
|
|
16942
|
+
const manualGoalManager = forceRunAction === "goal-manager";
|
|
16943
|
+
const route = manualGoalManager ? {
|
|
16944
|
+
action: "goal-manager",
|
|
16945
|
+
duty: "goal-manager",
|
|
16946
|
+
executable: "goal-manager",
|
|
16947
|
+
cliArgs: forceRunCliArgs
|
|
16948
|
+
} : resolveDutyAction(forceRunAction);
|
|
16793
16949
|
if (!route) {
|
|
16794
16950
|
process.stderr.write(`[kody] manual one-shot action '${forceRunAction}' has no duty action
|
|
16795
16951
|
`);
|
|
@@ -19904,6 +20060,7 @@ Usage:
|
|
|
19904
20060
|
kody-engine release --issue <N> [--cwd <path>] [--verbose|--quiet]
|
|
19905
20061
|
kody-engine init [--cwd <path>] [--verbose|--quiet]
|
|
19906
20062
|
kody-engine <action> [--cwd <path>] [--verbose|--quiet]
|
|
20063
|
+
kody-engine exec <executable> [--cwd <path>] [--verbose|--quiet]
|
|
19907
20064
|
kody-engine ci [preflight flags \u2014 see: kody-engine ci --help]
|
|
19908
20065
|
kody-engine chat [chat flags \u2014 see: kody-engine chat --help]
|
|
19909
20066
|
kody-engine stats [--since 7d|--run <id>|--json|--cwd <path>]
|
|
@@ -19911,7 +20068,8 @@ Usage:
|
|
|
19911
20068
|
kody-engine version
|
|
19912
20069
|
|
|
19913
20070
|
Top-level work commands are duty actions. A duty owns the public action name
|
|
19914
|
-
and selects an implementation executable.
|
|
20071
|
+
and selects an implementation executable. Use exec only for internal executable
|
|
20072
|
+
profiles such as scheduled helpers.
|
|
19915
20073
|
|
|
19916
20074
|
Exit codes:
|
|
19917
20075
|
0 success (PR opened, verify passed \u2014 or resolve produced a merge commit)
|
|
@@ -19942,6 +20100,24 @@ function parseArgs(argv) {
|
|
|
19942
20100
|
if (cmd === "stats") {
|
|
19943
20101
|
return { ...result, command: "stats", statsArgv: argv.slice(1) };
|
|
19944
20102
|
}
|
|
20103
|
+
if (cmd === "exec") {
|
|
20104
|
+
const executableName = argv[1];
|
|
20105
|
+
if (!executableName || executableName.startsWith("-")) {
|
|
20106
|
+
result.errors.push("exec requires an executable name");
|
|
20107
|
+
return result;
|
|
20108
|
+
}
|
|
20109
|
+
if (!resolveExecutable(executableName)) {
|
|
20110
|
+
result.errors.push(`unknown executable: ${executableName}`);
|
|
20111
|
+
return result;
|
|
20112
|
+
}
|
|
20113
|
+
result.command = "__exec__";
|
|
20114
|
+
result.executableName = executableName;
|
|
20115
|
+
result.cliArgs = parseGenericFlags(argv.slice(2));
|
|
20116
|
+
if (typeof result.cliArgs.cwd === "string") result.cwd = result.cliArgs.cwd;
|
|
20117
|
+
if (result.cliArgs.verbose === true) result.verbose = true;
|
|
20118
|
+
if (result.cliArgs.quiet === true) result.quiet = true;
|
|
20119
|
+
return result;
|
|
20120
|
+
}
|
|
19945
20121
|
const SERVER_VERBS = /* @__PURE__ */ new Set(["serve", "pool-serve", "runner-serve", "brain-serve", "brain-proxy", "mcp-http-server"]);
|
|
19946
20122
|
if (SERVER_VERBS.has(cmd)) {
|
|
19947
20123
|
result.command = "server";
|
|
@@ -19962,7 +20138,7 @@ function parseArgs(argv) {
|
|
|
19962
20138
|
return result;
|
|
19963
20139
|
}
|
|
19964
20140
|
const discoveredActions = listDutyActions().map((e) => e.action);
|
|
19965
|
-
const available = ["ci", "chat", "stats", "help", "version", ...discoveredActions];
|
|
20141
|
+
const available = ["ci", "chat", "stats", "exec", "help", "version", ...discoveredActions];
|
|
19966
20142
|
result.errors.push(`unknown command: ${cmd} (available: ${available.join(", ")})`);
|
|
19967
20143
|
return result;
|
|
19968
20144
|
}
|
|
@@ -20094,7 +20270,44 @@ ${HELP_TEXT}`);
|
|
|
20094
20270
|
return 99;
|
|
20095
20271
|
}
|
|
20096
20272
|
}
|
|
20097
|
-
|
|
20273
|
+
if (args.command === "__exec__") {
|
|
20274
|
+
const executable = args.executableName;
|
|
20275
|
+
const cliArgs = args.cliArgs ?? {};
|
|
20276
|
+
const skipConfig = configlessCommands.has(executable);
|
|
20277
|
+
try {
|
|
20278
|
+
const result = await runJob(
|
|
20279
|
+
{
|
|
20280
|
+
action: executable,
|
|
20281
|
+
duty: executable,
|
|
20282
|
+
executable,
|
|
20283
|
+
cliArgs,
|
|
20284
|
+
target: numericTarget(cliArgs),
|
|
20285
|
+
flavor: "instant"
|
|
20286
|
+
},
|
|
20287
|
+
{
|
|
20288
|
+
cwd,
|
|
20289
|
+
skipConfig,
|
|
20290
|
+
verbose: args.verbose,
|
|
20291
|
+
quiet: args.quiet
|
|
20292
|
+
}
|
|
20293
|
+
);
|
|
20294
|
+
if (result.exitCode !== 0 && result.reason) {
|
|
20295
|
+
process.stderr.write(`error: ${result.reason}
|
|
20296
|
+
`);
|
|
20297
|
+
}
|
|
20298
|
+
return result.exitCode;
|
|
20299
|
+
} catch (err) {
|
|
20300
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
20301
|
+
process.stderr.write(`[kody] ${executable} crashed: ${msg}
|
|
20302
|
+
`);
|
|
20303
|
+
if (err instanceof Error && err.stack) process.stderr.write(`${err.stack}
|
|
20304
|
+
`);
|
|
20305
|
+
process.stdout.write(`PR_URL=FAILED: ${executable} crashed: ${msg}
|
|
20306
|
+
`);
|
|
20307
|
+
return 99;
|
|
20308
|
+
}
|
|
20309
|
+
}
|
|
20310
|
+
process.stderr.write("error: command did not resolve to a duty or executable\n");
|
|
20098
20311
|
return 64;
|
|
20099
20312
|
}
|
|
20100
20313
|
function numericTarget(cliArgs) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kody-ade/kody-engine",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.236",
|
|
4
4
|
"description": "kody — autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
{{dutyReference}}
|
|
2
|
-
|
|
3
|
-
You are **{{staffTitle}}** (staff `{{staffSlug}}`), running the **watch-stale-prs** duty — a weekly digest of open PRs that haven't been touched in a while. You do **not** touch code, do **not** commit, and do **not** edit files. You coordinate by inspecting GitHub state via `gh` and writing a single report file at `.kody/reports/{{dutySlug}}.md`.
|
|
4
|
-
|
|
5
|
-
## Who you are — staff persona (authoritative identity)
|
|
6
|
-
|
|
7
|
-
The duty assigns you, staff **`{{staffSlug}}`**, as its executor. This persona defines *who* runs the duty: your authority, doctrine, voice, and hard limits. Where the persona's restrictions are stricter than the duty body, **the persona wins** — a duty can never grant you authority your staff persona withholds.
|
|
8
|
-
|
|
9
|
-
{{workerPersona}}
|
|
10
|
-
|
|
11
|
-
## The duty
|
|
12
|
-
|
|
13
|
-
Slug **`{{dutySlug}}`** — *{{dutyTitle}}*, assigned to staff **`{{staffSlug}}`**, running on executable **`{{executableSlug}}`**. Cadence is enforced by the engine via the `every: 7d` profile field — this duty only fires once per 7 days regardless of how often `duty-scheduler` wakes. No prose cadence guard needed.
|
|
14
|
-
|
|
15
|
-
**Addressing the operator.** When the duty tells you to @-mention the operator, the exact handle(s) to use are: {{mentions}}. Copy that string **verbatim** — never invent, abbreviate, guess, or retype a GitHub username. If the line above is blank, the duty declared no operator; post without a mention.
|
|
16
|
-
|
|
17
|
-
### What "stale" means
|
|
18
|
-
|
|
19
|
-
Find every open PR untouched for **≥ 7 days** and write a report listing them, sorted by staleness (oldest first). When there are no stale PRs, write a short "all clear" report so operators know the check ran.
|
|
20
|
-
|
|
21
|
-
A PR is stale if:
|
|
22
|
-
|
|
23
|
-
- `state` is `OPEN`, AND
|
|
24
|
-
- `updatedAt` is more than 7 days before now.
|
|
25
|
-
|
|
26
|
-
Use `gh pr list --state open --limit 100 --json number,title,url,updatedAt,author` to enumerate. Filter and sort client-side; do not call `gh` once per PR.
|
|
27
|
-
|
|
28
|
-
### Report shape
|
|
29
|
-
|
|
30
|
-
Write to `.kody/reports/watch-stale-prs.md`. Overwrite each run.
|
|
31
|
-
|
|
32
|
-
When stale PRs exist:
|
|
33
|
-
|
|
34
|
-
```markdown
|
|
35
|
-
# Stale PRs — <ISO date>
|
|
36
|
-
|
|
37
|
-
🟡 <N> PR(s) untouched for > 7 days.
|
|
38
|
-
|
|
39
|
-
| # | Title | Author | Days stale | Updated |
|
|
40
|
-
|---|-------|--------|------------|---------|
|
|
41
|
-
| [#123](url) | <title> | @user | 14 | 2026-04-25 |
|
|
42
|
-
| ... | | | | |
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
When none:
|
|
46
|
-
|
|
47
|
-
```markdown
|
|
48
|
-
# Stale PRs — <ISO date>
|
|
49
|
-
|
|
50
|
-
🟢 No open PRs untouched for more than 7 days.
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
Truncate to the 50 oldest if the list is longer; append a final line
|
|
54
|
-
`> … and N more not shown`.
|
|
55
|
-
|
|
56
|
-
## Allowed Commands
|
|
57
|
-
|
|
58
|
-
- `gh pr list --state open --limit 100 --json number,title,url,updatedAt,author`
|
|
59
|
-
- `gh api -X GET /repos/{owner}/{repo}/contents/.kody/reports/watch-stale-prs.md`
|
|
60
|
-
— only to fetch the existing file's `sha` for an update.
|
|
61
|
-
- `gh api -X PUT /repos/{owner}/{repo}/contents/.kody/reports/watch-stale-prs.md`
|
|
62
|
-
— to write the report (base64-encoded `content`, `message`, and `sha`
|
|
63
|
-
when updating). This is the **only** permitted write path for this job.
|
|
64
|
-
|
|
65
|
-
## Restrictions
|
|
66
|
-
|
|
67
|
-
- Never edit, create, or delete any other file in the working tree.
|
|
68
|
-
- Never `git commit`, `git push`, or open a PR.
|
|
69
|
-
- Never post comments on PRs or issues; the report file is the only
|
|
70
|
-
output channel.
|
|
71
|
-
- Never call `gh` per-PR — one `pr list` is enough.
|
|
72
|
-
|
|
73
|
-
## State
|
|
74
|
-
|
|
75
|
-
`cursor`: always `"idle"` — this job has no phases; each fire is a
|
|
76
|
-
one-shot report write.
|
|
77
|
-
|
|
78
|
-
`data`:
|
|
79
|
-
|
|
80
|
-
- `lastStaleCount` (number) — how many stale PRs were in the most recent
|
|
81
|
-
report. Diagnostic only; the engine ignores it.
|
|
82
|
-
|
|
83
|
-
(Engine-managed fields like `lastFiredAt` live under `data` automatically;
|
|
84
|
-
do not write or rely on them from the prompt.)
|
|
85
|
-
|
|
86
|
-
`done`: always `false` — this job is evergreen.
|
|
87
|
-
|
|
88
|
-
## What to do on this tick
|
|
89
|
-
|
|
90
|
-
1. **Check `done`.** If the prior state has `done: true`, emit the same state back unchanged and exit without any action.
|
|
91
|
-
2. **Enumerate stale PRs** with `gh pr list`.
|
|
92
|
-
3. **Write the report** to `.kody/reports/{{dutySlug}}.md` via `gh api -X PUT`.
|
|
93
|
-
4. **Submit the new state** by calling the `submit_state` tool with `cursor: "idle"`, the `lastStaleCount` in `data`, and `done: false`.
|
|
94
|
-
|
|
95
|
-
The duty cadence (`every: 7d`) is enforced by the engine — do not re-arm it from the prompt.
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "watch-stale-prs",
|
|
3
|
-
"role": "primitive",
|
|
4
|
-
"describe": "Weekly digest of open PRs that haven't been touched in a while. Writes a markdown report at .kody/reports/watch-stale-prs.md (surfaced by the dashboard's /reports page).",
|
|
5
|
-
"kind": "oneshot",
|
|
6
|
-
"staff": "kody",
|
|
7
|
-
"every": "7d",
|
|
8
|
-
"inputs": [],
|
|
9
|
-
"claudeCode": {
|
|
10
|
-
"model": "inherit",
|
|
11
|
-
"permissionMode": "default",
|
|
12
|
-
"maxTurns": 100,
|
|
13
|
-
"maxThinkingTokens": null,
|
|
14
|
-
"systemPromptAppend": null,
|
|
15
|
-
"enableSubmitTool": true,
|
|
16
|
-
"tools": ["Bash", "Read", "mcp__kody-submit"],
|
|
17
|
-
"hooks": [],
|
|
18
|
-
"skills": [],
|
|
19
|
-
"commands": [],
|
|
20
|
-
"subagents": [],
|
|
21
|
-
"plugins": [],
|
|
22
|
-
"mcpServers": []
|
|
23
|
-
},
|
|
24
|
-
"cliTools": [
|
|
25
|
-
{
|
|
26
|
-
"name": "gh",
|
|
27
|
-
"install": {
|
|
28
|
-
"required": true,
|
|
29
|
-
"checkCommand": "command -v gh"
|
|
30
|
-
},
|
|
31
|
-
"verify": "gh auth status",
|
|
32
|
-
"usage": "Use `gh` for all GitHub actions: `gh pr list --state open --limit 100 --json number,title,url,updatedAt,author` to enumerate candidate PRs, `gh api -X GET /repos/{owner}/{repo}/contents/.kody/reports/watch-stale-prs.md` to fetch the existing file's `sha` for an update, `gh api -X PUT /repos/{owner}/{repo}/contents/.kody/reports/watch-stale-prs.md` to write the report (base64-encoded `content`, `message`, and `sha` when updating). NEVER edit files in the working tree.",
|
|
33
|
-
"allowedUses": ["pr", "api"]
|
|
34
|
-
}
|
|
35
|
-
],
|
|
36
|
-
"scripts": {
|
|
37
|
-
"preflight": [
|
|
38
|
-
{ "script": "loadDutyState" },
|
|
39
|
-
{ "script": "composePrompt" }
|
|
40
|
-
],
|
|
41
|
-
"postflight": [
|
|
42
|
-
{ "script": "parseJobStateFromAgentResult", "with": { "fenceLabel": "kody-job-next-state" } },
|
|
43
|
-
{ "script": "writeJobStateFile" }
|
|
44
|
-
]
|
|
45
|
-
}
|
|
46
|
-
}
|