@kody-ade/kody-engine 0.4.498 → 0.4.500
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 +1361 -1340
- package/package.json +1 -1
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.500",
|
|
19
19
|
description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative implementation profiles.",
|
|
20
20
|
license: "MIT",
|
|
21
21
|
type: "module",
|
|
@@ -4117,521 +4117,916 @@ var init_task_artifacts = __esm({
|
|
|
4117
4117
|
}
|
|
4118
4118
|
});
|
|
4119
4119
|
|
|
4120
|
-
// src/
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
const
|
|
4125
|
-
const
|
|
4126
|
-
const
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
if (process.env.GITHUB_EVENT_NAME !== "issue_comment") return;
|
|
4132
|
-
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
4133
|
-
if (!eventPath || !fs18.existsSync(eventPath)) return;
|
|
4134
|
-
let event = null;
|
|
4135
|
-
try {
|
|
4136
|
-
event = JSON.parse(fs18.readFileSync(eventPath, "utf-8"));
|
|
4137
|
-
} catch {
|
|
4138
|
-
return;
|
|
4139
|
-
}
|
|
4140
|
-
const commentId = event?.comment?.id;
|
|
4141
|
-
const repo = process.env.GITHUB_REPOSITORY;
|
|
4142
|
-
if (!commentId || !repo) return;
|
|
4143
|
-
const token = process.env.KODY_TOKEN?.trim() || process.env.GH_TOKEN || process.env.GITHUB_TOKEN;
|
|
4144
|
-
const args = [
|
|
4145
|
-
"api",
|
|
4146
|
-
"-X",
|
|
4147
|
-
"POST",
|
|
4148
|
-
"-H",
|
|
4149
|
-
"Accept: application/vnd.github+json",
|
|
4150
|
-
`/repos/${repo}/issues/comments/${commentId}/reactions`,
|
|
4151
|
-
"-f",
|
|
4152
|
-
"content=eyes"
|
|
4153
|
-
];
|
|
4154
|
-
const opts = {
|
|
4155
|
-
cwd,
|
|
4156
|
-
env: { ...process.env, GH_TOKEN: token ?? process.env.GH_TOKEN ?? "" },
|
|
4157
|
-
stdio: "pipe",
|
|
4158
|
-
timeout: 15e3
|
|
4159
|
-
};
|
|
4160
|
-
let lastErr = null;
|
|
4161
|
-
for (let attempt = 0; attempt < 3; attempt++) {
|
|
4162
|
-
if (attempt > 0) sleepMs(attempt === 1 ? 500 : 1500);
|
|
4163
|
-
try {
|
|
4164
|
-
execFileSync2("gh", args, opts);
|
|
4165
|
-
return;
|
|
4166
|
-
} catch (err) {
|
|
4167
|
-
lastErr = err;
|
|
4168
|
-
}
|
|
4169
|
-
}
|
|
4170
|
-
process.stderr.write(
|
|
4171
|
-
`[kody] \u{1F440} reaction failed after 3 attempts on comment ${commentId}: ${lastErr instanceof Error ? lastErr.message : String(lastErr)}
|
|
4172
|
-
`
|
|
4173
|
-
);
|
|
4174
|
-
}
|
|
4175
|
-
function sleepMs(ms) {
|
|
4176
|
-
try {
|
|
4177
|
-
execFileSync2("sleep", [(ms / 1e3).toString()], { stdio: "ignore", timeout: ms + 1e3 });
|
|
4178
|
-
} catch {
|
|
4179
|
-
}
|
|
4180
|
-
}
|
|
4181
|
-
var init_gha = __esm({
|
|
4182
|
-
"src/gha.ts"() {
|
|
4183
|
-
"use strict";
|
|
4184
|
-
}
|
|
4185
|
-
});
|
|
4186
|
-
|
|
4187
|
-
// src/agencyBoundaryEval.ts
|
|
4188
|
-
function evaluateAgencyBoundaries(input) {
|
|
4189
|
-
const findings = [];
|
|
4190
|
-
const results = input.results ?? [];
|
|
4191
|
-
findings.push(evaluateObserveBoundary(input.capabilityKind, results));
|
|
4192
|
-
findings.push(evaluateVerifyBoundary(input.capabilityKind, results));
|
|
4193
|
-
findings.push(evaluateGoalOwnershipBoundary(results));
|
|
4194
|
-
return {
|
|
4195
|
-
version: 1,
|
|
4196
|
-
status: findings.some((finding) => finding.status === "fail") ? "fail" : "pass",
|
|
4197
|
-
...input.capability ? { capability: input.capability } : {},
|
|
4198
|
-
...input.capabilityKind ? { capabilityKind: input.capabilityKind } : {},
|
|
4199
|
-
findings
|
|
4200
|
-
};
|
|
4201
|
-
}
|
|
4202
|
-
function evaluateObserveBoundary(capabilityKind, results) {
|
|
4203
|
-
if (capabilityKind !== "observe") {
|
|
4204
|
-
return pass("observe-does-not-act", "capability is not observe", { capabilityKind });
|
|
4120
|
+
// src/workflowValidation.ts
|
|
4121
|
+
function validateWorkflow(value, options = {}) {
|
|
4122
|
+
const issues = [];
|
|
4123
|
+
const workflow = asRecord(value);
|
|
4124
|
+
const rawSteps = Array.isArray(value) ? value : Array.isArray(workflow?.steps) ? workflow.steps : [];
|
|
4125
|
+
const maxSteps = options.maxSteps ?? 100;
|
|
4126
|
+
const maxTransitions = options.maxTransitionsPerStep ?? 20;
|
|
4127
|
+
const maxLoopIterations = options.maxLoopIterations ?? 100;
|
|
4128
|
+
if (rawSteps.length === 0) {
|
|
4129
|
+
issue(issues, "steps_required", "steps", "workflow must contain at least one step");
|
|
4130
|
+
return issues;
|
|
4205
4131
|
}
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
return pass("observe-does-not-act", "observe capability reported facts without action output", {
|
|
4209
|
-
resultCount: results.length
|
|
4210
|
-
});
|
|
4132
|
+
if (rawSteps.length > maxSteps) {
|
|
4133
|
+
issue(issues, "too_many_steps", "steps", `workflow has ${rawSteps.length} steps; maximum is ${maxSteps}`);
|
|
4211
4134
|
}
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4135
|
+
const graphMode = workflow?.startAt !== void 0 || rawSteps.some((entry) => {
|
|
4136
|
+
const step = asRecord(entry);
|
|
4137
|
+
return Boolean(step && (step.id !== void 0 || step.next !== void 0));
|
|
4215
4138
|
});
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
}
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4139
|
+
const steps = rawSteps.map(
|
|
4140
|
+
(entry) => typeof entry === "string" ? { capability: entry } : asRecord(entry)
|
|
4141
|
+
);
|
|
4142
|
+
const ids = [];
|
|
4143
|
+
steps.forEach((step, index) => {
|
|
4144
|
+
const base = `steps[${index}]`;
|
|
4145
|
+
if (!step) {
|
|
4146
|
+
issue(issues, "invalid_step", base, "workflow step must be a capability name or an object");
|
|
4147
|
+
return;
|
|
4148
|
+
}
|
|
4149
|
+
for (const field of Object.keys(step)) {
|
|
4150
|
+
if (!SUPPORTED_STEP_FIELDS.has(field)) {
|
|
4151
|
+
issue(issues, "unsupported_step_field", `${base}.${field}`, `workflow step field ${field} is not supported`);
|
|
4152
|
+
}
|
|
4153
|
+
}
|
|
4154
|
+
const capability = text(step.capability ?? step.action);
|
|
4155
|
+
if (!capability || !SAFE_NAME.test(capability)) {
|
|
4156
|
+
issue(issues, "invalid_capability", `${base}.capability`, "workflow step must name a valid capability");
|
|
4157
|
+
} else if (options.knownCapabilities && !options.knownCapabilities.has(capability)) {
|
|
4158
|
+
issue(
|
|
4159
|
+
issues,
|
|
4160
|
+
"unknown_capability",
|
|
4161
|
+
`${base}.capability`,
|
|
4162
|
+
`workflow step references unknown capability ${capability}`
|
|
4163
|
+
);
|
|
4164
|
+
}
|
|
4165
|
+
if (graphMode) {
|
|
4166
|
+
const id = text(step.id);
|
|
4167
|
+
if (!id || !SAFE_STEP_ID.test(id)) {
|
|
4168
|
+
issue(issues, "invalid_step_id", `${base}.id`, "graph workflow steps must each have a valid id");
|
|
4169
|
+
} else {
|
|
4170
|
+
ids.push(id);
|
|
4171
|
+
}
|
|
4172
|
+
}
|
|
4173
|
+
validateDataMatch(step.runWhen, `${base}.runWhen`, issues);
|
|
4174
|
+
if (step.delivery !== void 0 && step.delivery !== "pull-request") {
|
|
4175
|
+
issue(issues, "invalid_delivery", `${base}.delivery`, "workflow step delivery must be pull-request");
|
|
4176
|
+
}
|
|
4177
|
+
if (step.input !== void 0 && !isJsonValue(step.input)) {
|
|
4178
|
+
issue(issues, "invalid_input", `${base}.input`, "workflow step input must be one JSON value");
|
|
4179
|
+
}
|
|
4230
4180
|
});
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
resultCount: results.length
|
|
4237
|
-
});
|
|
4238
|
-
}
|
|
4239
|
-
return fail("capability-does-not-own-goal-progress", "capability output names a goal target", {
|
|
4240
|
-
resultCount: results.length,
|
|
4241
|
-
targetBearingResults: targetBearing.map(resultSummary)
|
|
4181
|
+
if (!graphMode) return issues;
|
|
4182
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4183
|
+
ids.forEach((id, index) => {
|
|
4184
|
+
if (seen.has(id)) issue(issues, "duplicate_step_id", `steps[${index}].id`, `workflow step id ${id} is duplicated`);
|
|
4185
|
+
seen.add(id);
|
|
4242
4186
|
});
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
return Object.keys(result.facts).some((key) => ACTION_FACT_KEYS.has(key));
|
|
4247
|
-
}
|
|
4248
|
-
function resultSummary(result) {
|
|
4249
|
-
return {
|
|
4250
|
-
status: result.status,
|
|
4251
|
-
summary: result.summary,
|
|
4252
|
-
target: result.target,
|
|
4253
|
-
actionFactKeys: Object.keys(result.facts).filter((key) => ACTION_FACT_KEYS.has(key))
|
|
4254
|
-
};
|
|
4255
|
-
}
|
|
4256
|
-
function pass(rule, message, evidence) {
|
|
4257
|
-
return { rule, status: "pass", message, evidence };
|
|
4258
|
-
}
|
|
4259
|
-
function fail(rule, message, evidence) {
|
|
4260
|
-
return { rule, status: "fail", message, evidence };
|
|
4261
|
-
}
|
|
4262
|
-
var ACTION_FACT_KEYS;
|
|
4263
|
-
var init_agencyBoundaryEval = __esm({
|
|
4264
|
-
"src/agencyBoundaryEval.ts"() {
|
|
4265
|
-
"use strict";
|
|
4266
|
-
ACTION_FACT_KEYS = /* @__PURE__ */ new Set(["changedResources", "createdResources", "actionResult"]);
|
|
4267
|
-
}
|
|
4268
|
-
});
|
|
4269
|
-
|
|
4270
|
-
// src/agency/capability-contract-validation.ts
|
|
4271
|
-
import Ajv from "ajv";
|
|
4272
|
-
function validateCapabilityContractValue(boundary, schema, value) {
|
|
4273
|
-
const validate = validator.compile(schema);
|
|
4274
|
-
if (!validate(value)) {
|
|
4275
|
-
throw new CapabilityContractValidationError(boundary, validate.errors ?? []);
|
|
4276
|
-
}
|
|
4277
|
-
}
|
|
4278
|
-
function capabilityContractInput(inputs, args, capabilityId, contractProperties = []) {
|
|
4279
|
-
const isGenericRunnerInput = inputs.some((input) => input.name === "input") && Object.hasOwn(args, "input");
|
|
4280
|
-
if (!isGenericRunnerInput) {
|
|
4281
|
-
const isParameterlessGenericRunner = (inputs.some((input) => input.name === "capability") || args.capability === capabilityId || !contractProperties.includes("capability")) && Object.hasOwn(args, "capability");
|
|
4282
|
-
if (!isParameterlessGenericRunner) return args;
|
|
4283
|
-
const { capability: _routingCapability, ...businessArgs } = args;
|
|
4284
|
-
return businessArgs;
|
|
4285
|
-
}
|
|
4286
|
-
const value = args.input;
|
|
4287
|
-
if (typeof value !== "string") return value;
|
|
4288
|
-
try {
|
|
4289
|
-
return JSON.parse(value);
|
|
4290
|
-
} catch {
|
|
4291
|
-
return value;
|
|
4187
|
+
const startAt = text(workflow?.startAt) ?? text(steps[0]?.id);
|
|
4188
|
+
if (!startAt || !seen.has(startAt)) {
|
|
4189
|
+
issue(issues, "missing_start_step", "startAt", `workflow startAt references missing step ${startAt ?? "<none>"}`);
|
|
4292
4190
|
}
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4191
|
+
const adjacency = /* @__PURE__ */ new Map();
|
|
4192
|
+
const explicitEndSources = /* @__PURE__ */ new Set();
|
|
4193
|
+
steps.forEach((step, index) => {
|
|
4194
|
+
if (!step) return;
|
|
4195
|
+
const id = text(step.id);
|
|
4196
|
+
if (!id) return;
|
|
4197
|
+
const sourceCapability = text(step.capability ?? step.action);
|
|
4198
|
+
const transitions = transitionList(step.next);
|
|
4199
|
+
adjacency.set(id, []);
|
|
4200
|
+
if (transitions.length > maxTransitions) {
|
|
4201
|
+
issue(
|
|
4202
|
+
issues,
|
|
4203
|
+
"too_many_transitions",
|
|
4204
|
+
`steps[${index}].next`,
|
|
4205
|
+
`workflow step ${id} has ${transitions.length} connections; maximum is ${maxTransitions}`
|
|
4206
|
+
);
|
|
4207
|
+
}
|
|
4208
|
+
const defaults = transitions.filter((transition) => asRecord(transition)?.default === true);
|
|
4209
|
+
const conditionals = transitions.filter((transition) => asRecord(transition)?.when !== void 0);
|
|
4210
|
+
const unconditional = transitions.filter((transition) => {
|
|
4211
|
+
const raw = asRecord(transition);
|
|
4212
|
+
return typeof transition === "string" || Boolean(raw && raw.when === void 0 && raw.default !== true && raw.maxIterations === void 0);
|
|
4302
4213
|
});
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4214
|
+
if (defaults.length > 1) {
|
|
4215
|
+
issue(
|
|
4216
|
+
issues,
|
|
4217
|
+
"multiple_default_transitions",
|
|
4218
|
+
`steps[${index}].next`,
|
|
4219
|
+
`workflow step ${id} has more than one default connection`
|
|
4220
|
+
);
|
|
4221
|
+
}
|
|
4222
|
+
if (conditionals.length > 0 && defaults.length !== 1) {
|
|
4223
|
+
issue(
|
|
4224
|
+
issues,
|
|
4225
|
+
"missing_default_transition",
|
|
4226
|
+
`steps[${index}].next`,
|
|
4227
|
+
`workflow step ${id} has conditions and needs one default connection`
|
|
4228
|
+
);
|
|
4229
|
+
}
|
|
4230
|
+
if (unconditional.length > 1 || unconditional.length > 0 && transitions.length > 1) {
|
|
4231
|
+
issue(
|
|
4232
|
+
issues,
|
|
4233
|
+
"ambiguous_transition",
|
|
4234
|
+
`steps[${index}].next`,
|
|
4235
|
+
`workflow step ${id} mixes an unconditional connection with other connections`
|
|
4236
|
+
);
|
|
4237
|
+
}
|
|
4238
|
+
transitions.forEach((transition, transitionIndex) => {
|
|
4239
|
+
const raw = typeof transition === "string" ? { to: transition } : asRecord(transition);
|
|
4240
|
+
const base = `steps[${index}].next[${transitionIndex}]`;
|
|
4241
|
+
if (!raw) {
|
|
4242
|
+
issue(issues, "invalid_transition", base, "workflow connection must be a step id or an object");
|
|
4243
|
+
return;
|
|
4244
|
+
}
|
|
4245
|
+
for (const field of Object.keys(raw)) {
|
|
4246
|
+
if (!SUPPORTED_TRANSITION_FIELDS.has(field)) {
|
|
4247
|
+
issue(
|
|
4248
|
+
issues,
|
|
4249
|
+
"unsupported_transition_field",
|
|
4250
|
+
`${base}.${field}`,
|
|
4251
|
+
`workflow connection field ${field} is not supported`
|
|
4252
|
+
);
|
|
4253
|
+
}
|
|
4254
|
+
}
|
|
4255
|
+
const target = text(raw.to);
|
|
4256
|
+
if (!target || target !== "$end" && !SAFE_STEP_ID.test(target)) {
|
|
4257
|
+
issue(issues, "invalid_transition_target", `${base}.to`, "workflow connection must name a valid target step");
|
|
4258
|
+
return;
|
|
4259
|
+
}
|
|
4260
|
+
if (raw.default === true && raw.when !== void 0) {
|
|
4261
|
+
issue(issues, "conflicting_transition", base, "workflow connection cannot be both conditional and default");
|
|
4262
|
+
}
|
|
4263
|
+
if (raw.when !== void 0) {
|
|
4264
|
+
const outputPaths = options.capabilityOutputs?.get(sourceCapability ?? "");
|
|
4265
|
+
validateDataMatch(raw.when, `${base}.when`, issues, outputPaths);
|
|
4266
|
+
}
|
|
4267
|
+
if (target === "$end") {
|
|
4268
|
+
explicitEndSources.add(id);
|
|
4269
|
+
return;
|
|
4270
|
+
}
|
|
4271
|
+
if (!seen.has(target)) {
|
|
4272
|
+
issue(
|
|
4273
|
+
issues,
|
|
4274
|
+
"missing_transition_target",
|
|
4275
|
+
`${base}.to`,
|
|
4276
|
+
`workflow step ${id} connects to missing step ${target}`
|
|
4312
4277
|
);
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
this.name = "CapabilityContractValidationError";
|
|
4278
|
+
} else {
|
|
4279
|
+
adjacency.get(id)?.push(target);
|
|
4316
4280
|
}
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4281
|
+
const targetIndex = ids.indexOf(target ?? "");
|
|
4282
|
+
const iterations = raw.maxIterations;
|
|
4283
|
+
if (targetIndex >= 0 && targetIndex <= index) {
|
|
4284
|
+
if (!Number.isInteger(iterations) || Number(iterations) < 1) {
|
|
4285
|
+
issue(
|
|
4286
|
+
issues,
|
|
4287
|
+
"unbounded_loop",
|
|
4288
|
+
`${base}.maxIterations`,
|
|
4289
|
+
`workflow loop ${id}->${target} must set maxIterations`
|
|
4290
|
+
);
|
|
4291
|
+
} else if (Number(iterations) > maxLoopIterations) {
|
|
4292
|
+
issue(
|
|
4293
|
+
issues,
|
|
4294
|
+
"loop_limit_too_high",
|
|
4295
|
+
`${base}.maxIterations`,
|
|
4296
|
+
`workflow loop ${id}->${target} exceeds maximum ${maxLoopIterations}`
|
|
4297
|
+
);
|
|
4298
|
+
}
|
|
4299
|
+
} else if (iterations !== void 0 && (!Number.isInteger(iterations) || Number(iterations) < 1)) {
|
|
4300
|
+
issue(issues, "invalid_loop_limit", `${base}.maxIterations`, "maxIterations must be a positive integer");
|
|
4301
|
+
}
|
|
4302
|
+
});
|
|
4303
|
+
});
|
|
4304
|
+
if (startAt && seen.has(startAt)) {
|
|
4305
|
+
const reachable = /* @__PURE__ */ new Set();
|
|
4306
|
+
const pending = [startAt];
|
|
4307
|
+
while (pending.length > 0) {
|
|
4308
|
+
const id = pending.pop();
|
|
4309
|
+
if (reachable.has(id)) continue;
|
|
4310
|
+
reachable.add(id);
|
|
4311
|
+
pending.push(...adjacency.get(id) ?? []);
|
|
4312
|
+
}
|
|
4313
|
+
ids.forEach((id, index) => {
|
|
4314
|
+
if (!reachable.has(id)) issue(issues, "unreachable_step", `steps[${index}]`, `workflow step ${id} is unreachable`);
|
|
4315
|
+
});
|
|
4316
|
+
if (![...reachable].some((id) => (adjacency.get(id) ?? []).length === 0 || explicitEndSources.has(id))) {
|
|
4317
|
+
issue(issues, "missing_terminal_step", "steps", "workflow has no reachable final step");
|
|
4318
|
+
}
|
|
4320
4319
|
}
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4320
|
+
return issues;
|
|
4321
|
+
}
|
|
4322
|
+
function formatWorkflowValidationIssues(issues) {
|
|
4323
|
+
return issues.map((entry) => `${entry.path}: ${entry.message}`);
|
|
4324
|
+
}
|
|
4325
|
+
function validateDataMatch(value, path53, issues, capabilityOutputs) {
|
|
4326
|
+
if (value === void 0) return;
|
|
4327
|
+
const match = asRecord(value);
|
|
4328
|
+
if (!match || Object.keys(match).length === 0) {
|
|
4329
|
+
issue(issues, "invalid_condition", path53, "workflow condition must contain at least one match");
|
|
4330
|
+
return;
|
|
4331
|
+
}
|
|
4332
|
+
for (const [field, expected] of Object.entries(match)) {
|
|
4333
|
+
if (!SAFE_DATA_PATH.test(field)) {
|
|
4334
|
+
issue(
|
|
4335
|
+
issues,
|
|
4336
|
+
"invalid_data_path",
|
|
4337
|
+
`${path53}.${field}`,
|
|
4338
|
+
`workflow condition must read from facts, evidence, artifacts, result, workflow, or lastOutcome`
|
|
4339
|
+
);
|
|
4340
|
+
}
|
|
4341
|
+
if (capabilityOutputs && field.startsWith("result.") && !capabilityOutputs.has(field)) {
|
|
4342
|
+
issue(
|
|
4343
|
+
issues,
|
|
4344
|
+
"undeclared_result_path",
|
|
4345
|
+
`${path53}.${field}`,
|
|
4346
|
+
`workflow condition reads ${field}, but the source capability does not declare it`
|
|
4347
|
+
);
|
|
4348
|
+
}
|
|
4349
|
+
if (!isComparable(expected)) {
|
|
4350
|
+
issue(issues, "invalid_condition_value", `${path53}.${field}`, "workflow condition value must be a JSON scalar");
|
|
4333
4351
|
}
|
|
4334
4352
|
}
|
|
4335
|
-
return reports;
|
|
4336
4353
|
}
|
|
4337
|
-
function
|
|
4338
|
-
if (
|
|
4339
|
-
|
|
4340
|
-
const target = parseCapabilityReportTarget(obj.target);
|
|
4341
|
-
if (!target) return null;
|
|
4342
|
-
const evidence = parseCapabilityReportEvidence(obj.evidence);
|
|
4343
|
-
const facts = parseFacts(obj.facts);
|
|
4344
|
-
if (!evidence && !facts) return null;
|
|
4345
|
-
return {
|
|
4346
|
-
target,
|
|
4347
|
-
...evidence ? { evidence } : {},
|
|
4348
|
-
...facts ? { facts } : {}
|
|
4349
|
-
};
|
|
4354
|
+
function transitionList(value) {
|
|
4355
|
+
if (value === void 0) return [];
|
|
4356
|
+
return Array.isArray(value) ? value : [value];
|
|
4350
4357
|
}
|
|
4351
|
-
function
|
|
4352
|
-
|
|
4353
|
-
const target = raw;
|
|
4354
|
-
if (target.type !== "goal" && target.type !== "task" && target.type !== "capability") return null;
|
|
4355
|
-
if (typeof target.id !== "string" || target.id.trim().length === 0) return null;
|
|
4356
|
-
return { type: target.type, id: target.id.trim() };
|
|
4358
|
+
function asRecord(value) {
|
|
4359
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
4357
4360
|
}
|
|
4358
|
-
function
|
|
4359
|
-
|
|
4360
|
-
const out = {};
|
|
4361
|
-
for (const [key, value] of Object.entries(raw)) {
|
|
4362
|
-
if (typeof key !== "string" || key.length === 0 || typeof value !== "boolean") return null;
|
|
4363
|
-
out[key] = value;
|
|
4364
|
-
}
|
|
4365
|
-
return out;
|
|
4361
|
+
function text(value) {
|
|
4362
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
4366
4363
|
}
|
|
4367
|
-
function
|
|
4368
|
-
if (
|
|
4369
|
-
|
|
4370
|
-
for (const [key, value] of Object.entries(raw)) {
|
|
4371
|
-
if (typeof key !== "string" || key.length === 0) return null;
|
|
4372
|
-
if (CONTROL_FACT_KEYS.has(key)) continue;
|
|
4373
|
-
out[key] = value;
|
|
4374
|
-
}
|
|
4375
|
-
return out;
|
|
4364
|
+
function isComparable(value) {
|
|
4365
|
+
if (value === null || ["string", "number", "boolean"].includes(typeof value)) return true;
|
|
4366
|
+
return Array.isArray(value) && value.length > 0 && value.every((item) => isComparable(item) && !Array.isArray(item));
|
|
4376
4367
|
}
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4368
|
+
function isJsonValue(value) {
|
|
4369
|
+
if (value === null || ["string", "number", "boolean"].includes(typeof value)) return true;
|
|
4370
|
+
if (Array.isArray(value)) return value.every(isJsonValue);
|
|
4371
|
+
if (!value || typeof value !== "object") return false;
|
|
4372
|
+
return Object.values(value).every(isJsonValue);
|
|
4373
|
+
}
|
|
4374
|
+
function issue(issues, code, path53, message) {
|
|
4375
|
+
issues.push({ code, path: path53, message });
|
|
4376
|
+
}
|
|
4377
|
+
var SAFE_NAME, SAFE_STEP_ID, SAFE_DATA_PATH, SUPPORTED_STEP_FIELDS, SUPPORTED_TRANSITION_FIELDS;
|
|
4378
|
+
var init_workflowValidation = __esm({
|
|
4379
|
+
"src/workflowValidation.ts"() {
|
|
4380
4380
|
"use strict";
|
|
4381
|
-
|
|
4382
|
-
|
|
4381
|
+
SAFE_NAME = /^[a-z][a-z0-9-]*$/;
|
|
4382
|
+
SAFE_STEP_ID = /^[A-Za-z][A-Za-z0-9_-]*$/;
|
|
4383
|
+
SAFE_DATA_PATH = /^(facts|evidence|artifacts|result|workflow|lastOutcome)(?:\.[A-Za-z_][A-Za-z0-9_-]*)+$/;
|
|
4384
|
+
SUPPORTED_STEP_FIELDS = /* @__PURE__ */ new Set([
|
|
4385
|
+
"id",
|
|
4386
|
+
"capability",
|
|
4387
|
+
"input",
|
|
4388
|
+
"action",
|
|
4389
|
+
"evidence",
|
|
4390
|
+
"target",
|
|
4391
|
+
"delivery",
|
|
4392
|
+
"targetFact",
|
|
4393
|
+
"reason",
|
|
4394
|
+
"next",
|
|
4395
|
+
"runWhen",
|
|
4396
|
+
"continueOn",
|
|
4397
|
+
"saveReport",
|
|
4398
|
+
"report"
|
|
4399
|
+
]);
|
|
4400
|
+
SUPPORTED_TRANSITION_FIELDS = /* @__PURE__ */ new Set(["to", "when", "default", "maxIterations"]);
|
|
4383
4401
|
}
|
|
4384
4402
|
});
|
|
4385
4403
|
|
|
4386
|
-
// src/
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
const progress = parseGoalEvidenceProgress(value);
|
|
4392
|
-
if (progress) out[evidence] = progress;
|
|
4393
|
-
}
|
|
4394
|
-
return out;
|
|
4395
|
-
}
|
|
4396
|
-
function mergeGoalEvidenceProgress(state, evidence, update) {
|
|
4397
|
-
const prior = state[evidence];
|
|
4398
|
-
const next = {
|
|
4399
|
-
resultClass: update.resultClass,
|
|
4400
|
-
attempts: update.attempts ?? prior?.attempts ?? 0,
|
|
4401
|
-
...prior?.reason ? { reason: prior.reason } : {},
|
|
4402
|
-
...prior?.nextAction ? { nextAction: prior.nextAction } : {},
|
|
4403
|
-
...prior?.nextRetryAt ? { nextRetryAt: prior.nextRetryAt } : {},
|
|
4404
|
-
...prior?.issue ? { issue: prior.issue } : {},
|
|
4405
|
-
...prior?.updatedAt ? { updatedAt: prior.updatedAt } : {},
|
|
4406
|
-
...definedProgressFields(update)
|
|
4407
|
-
};
|
|
4408
|
-
return {
|
|
4409
|
-
...state,
|
|
4410
|
-
[evidence]: next
|
|
4411
|
-
};
|
|
4404
|
+
// src/workflowDefinitions.ts
|
|
4405
|
+
import * as fs16 from "fs";
|
|
4406
|
+
import * as path17 from "path";
|
|
4407
|
+
function isWorkflowDefinitionId(value) {
|
|
4408
|
+
return WORKFLOW_ID_PATTERN.test(value);
|
|
4412
4409
|
}
|
|
4413
|
-
function
|
|
4414
|
-
|
|
4410
|
+
function workflowDefinitionPath(id) {
|
|
4411
|
+
if (!isWorkflowDefinitionId(id)) {
|
|
4412
|
+
throw new Error(`Invalid workflow id "${id}"`);
|
|
4413
|
+
}
|
|
4414
|
+
return `workflows/${id}/workflow.json`;
|
|
4415
4415
|
}
|
|
4416
|
-
function
|
|
4416
|
+
function normalizeWorkflowDefinition(value) {
|
|
4417
4417
|
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
4418
4418
|
const raw = value;
|
|
4419
|
-
|
|
4420
|
-
const
|
|
4419
|
+
const name = typeof raw.name === "string" ? raw.name.trim() : "";
|
|
4420
|
+
const requestedAgent = typeof raw.agent === "string" ? raw.agent.trim() : "";
|
|
4421
|
+
const agent = /^[a-z][a-z0-9-]*$/.test(requestedAgent) ? requestedAgent : "kody";
|
|
4422
|
+
if (Array.isArray(raw.steps)) {
|
|
4423
|
+
if (validateWorkflow({ steps: raw.steps, ...raw.startAt !== void 0 ? { startAt: raw.startAt } : {} }).length > 0) {
|
|
4424
|
+
return null;
|
|
4425
|
+
}
|
|
4426
|
+
}
|
|
4427
|
+
const workflow = parseCapabilityWorkflow({
|
|
4428
|
+
steps: raw.steps,
|
|
4429
|
+
startAt: raw.startAt
|
|
4430
|
+
});
|
|
4431
|
+
const steps = workflow?.steps;
|
|
4432
|
+
const capabilities = steps ? steps.map((step) => step.capability) : normalizeWorkflowCapabilities(raw.capabilities);
|
|
4433
|
+
if (!name || capabilities.length === 0) return null;
|
|
4421
4434
|
return {
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
...
|
|
4426
|
-
...
|
|
4427
|
-
...
|
|
4428
|
-
...
|
|
4435
|
+
name,
|
|
4436
|
+
agent,
|
|
4437
|
+
capabilities,
|
|
4438
|
+
...raw.runWithoutApproval === true ? { runWithoutApproval: true } : {},
|
|
4439
|
+
...steps ? { steps } : {},
|
|
4440
|
+
...workflow?.startAt ? { startAt: workflow.startAt } : {},
|
|
4441
|
+
...typeof raw.createdAt === "string" ? { createdAt: raw.createdAt } : {},
|
|
4442
|
+
...typeof raw.updatedAt === "string" ? { updatedAt: raw.updatedAt } : {}
|
|
4429
4443
|
};
|
|
4430
4444
|
}
|
|
4431
|
-
function
|
|
4432
|
-
const
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4445
|
+
function readWorkflowDefinition(_config, cwd, id) {
|
|
4446
|
+
const root = cwd ?? process.cwd();
|
|
4447
|
+
const relativePath = workflowDefinitionPath(id);
|
|
4448
|
+
const candidates = [
|
|
4449
|
+
path17.join(root, ".kody-engine", "runtime", relativePath),
|
|
4450
|
+
path17.join(definitionsRoot(root), relativePath)
|
|
4451
|
+
];
|
|
4452
|
+
for (const filePath of candidates) {
|
|
4453
|
+
if (!fs16.existsSync(filePath)) continue;
|
|
4454
|
+
const workflow = parseWorkflowDefinition(fs16.readFileSync(filePath, "utf8"));
|
|
4455
|
+
if (workflow) return workflow;
|
|
4456
|
+
}
|
|
4457
|
+
return null;
|
|
4439
4458
|
}
|
|
4440
|
-
function
|
|
4441
|
-
return
|
|
4459
|
+
function workflowDefinitionToCapabilityFolder(id, workflow, source = workflowDefinitionPath(id)) {
|
|
4460
|
+
return {
|
|
4461
|
+
slug: id,
|
|
4462
|
+
dir: path17.dirname(source),
|
|
4463
|
+
profilePath: source,
|
|
4464
|
+
bodyPath: source,
|
|
4465
|
+
title: workflow.name,
|
|
4466
|
+
body: "",
|
|
4467
|
+
rawBody: "",
|
|
4468
|
+
rawProfile: { name: id, workflow },
|
|
4469
|
+
config: {
|
|
4470
|
+
action: id,
|
|
4471
|
+
workflow: workflowDefinitionToConfig(workflow),
|
|
4472
|
+
describe: workflow.name,
|
|
4473
|
+
agent: workflow.agent
|
|
4474
|
+
}
|
|
4475
|
+
};
|
|
4442
4476
|
}
|
|
4443
|
-
function
|
|
4444
|
-
if (
|
|
4445
|
-
|
|
4477
|
+
function normalizeWorkflowCapabilities(value) {
|
|
4478
|
+
if (!Array.isArray(value)) return [];
|
|
4479
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4480
|
+
const capabilities = [];
|
|
4481
|
+
for (const item of value) {
|
|
4482
|
+
if (typeof item !== "string") continue;
|
|
4483
|
+
const slug = item.trim();
|
|
4484
|
+
if (!CAPABILITY_ID_PATTERN.test(slug) || seen.has(slug)) continue;
|
|
4485
|
+
seen.add(slug);
|
|
4486
|
+
capabilities.push(slug);
|
|
4487
|
+
}
|
|
4488
|
+
return capabilities;
|
|
4446
4489
|
}
|
|
4447
|
-
|
|
4448
|
-
|
|
4490
|
+
function workflowDefinitionToConfig(workflow) {
|
|
4491
|
+
return {
|
|
4492
|
+
steps: workflow.steps ?? workflow.capabilities.map((capability) => ({ capability })),
|
|
4493
|
+
...workflow.startAt ? { startAt: workflow.startAt } : {}
|
|
4494
|
+
};
|
|
4495
|
+
}
|
|
4496
|
+
function parseWorkflowDefinition(content) {
|
|
4497
|
+
try {
|
|
4498
|
+
return normalizeWorkflowDefinition(JSON.parse(content));
|
|
4499
|
+
} catch {
|
|
4500
|
+
return null;
|
|
4501
|
+
}
|
|
4502
|
+
}
|
|
4503
|
+
var WORKFLOW_ID_PATTERN, CAPABILITY_ID_PATTERN;
|
|
4504
|
+
var init_workflowDefinitions = __esm({
|
|
4505
|
+
"src/workflowDefinitions.ts"() {
|
|
4449
4506
|
"use strict";
|
|
4507
|
+
init_capabilityFolders();
|
|
4508
|
+
init_definition_paths();
|
|
4509
|
+
init_workflowValidation();
|
|
4510
|
+
WORKFLOW_ID_PATTERN = /^[a-z0-9][a-z0-9_-]{0,79}$/;
|
|
4511
|
+
CAPABILITY_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,79}$/;
|
|
4450
4512
|
}
|
|
4451
4513
|
});
|
|
4452
4514
|
|
|
4453
|
-
// src/
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4515
|
+
// src/gha.ts
|
|
4516
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
4517
|
+
import * as fs19 from "fs";
|
|
4518
|
+
function getRunUrl() {
|
|
4519
|
+
const server = process.env.GITHUB_SERVER_URL;
|
|
4520
|
+
const repo = process.env.GITHUB_REPOSITORY;
|
|
4521
|
+
const runId = process.env.GITHUB_RUN_ID;
|
|
4522
|
+
if (!server || !repo || !runId) return "";
|
|
4523
|
+
return `${server}/${repo}/actions/runs/${runId}`;
|
|
4524
|
+
}
|
|
4525
|
+
function reactToTriggerComment(cwd) {
|
|
4526
|
+
if (process.env.GITHUB_EVENT_NAME !== "issue_comment") return;
|
|
4527
|
+
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
4528
|
+
if (!eventPath || !fs19.existsSync(eventPath)) return;
|
|
4529
|
+
let event = null;
|
|
4530
|
+
try {
|
|
4531
|
+
event = JSON.parse(fs19.readFileSync(eventPath, "utf-8"));
|
|
4532
|
+
} catch {
|
|
4533
|
+
return;
|
|
4534
|
+
}
|
|
4535
|
+
const commentId = event?.comment?.id;
|
|
4536
|
+
const repo = process.env.GITHUB_REPOSITORY;
|
|
4537
|
+
if (!commentId || !repo) return;
|
|
4538
|
+
const token = process.env.KODY_TOKEN?.trim() || process.env.GH_TOKEN || process.env.GITHUB_TOKEN;
|
|
4539
|
+
const args = [
|
|
4540
|
+
"api",
|
|
4541
|
+
"-X",
|
|
4542
|
+
"POST",
|
|
4543
|
+
"-H",
|
|
4544
|
+
"Accept: application/vnd.github+json",
|
|
4545
|
+
`/repos/${repo}/issues/comments/${commentId}/reactions`,
|
|
4546
|
+
"-f",
|
|
4547
|
+
"content=eyes"
|
|
4548
|
+
];
|
|
4549
|
+
const opts = {
|
|
4550
|
+
cwd,
|
|
4551
|
+
env: { ...process.env, GH_TOKEN: token ?? process.env.GH_TOKEN ?? "" },
|
|
4552
|
+
stdio: "pipe",
|
|
4553
|
+
timeout: 15e3
|
|
4554
|
+
};
|
|
4555
|
+
let lastErr = null;
|
|
4556
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
4557
|
+
if (attempt > 0) sleepMs(attempt === 1 ? 500 : 1500);
|
|
4459
4558
|
try {
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
} catch {
|
|
4559
|
+
execFileSync2("gh", args, opts);
|
|
4560
|
+
return;
|
|
4561
|
+
} catch (err) {
|
|
4562
|
+
lastErr = err;
|
|
4463
4563
|
}
|
|
4464
4564
|
}
|
|
4465
|
-
|
|
4565
|
+
process.stderr.write(
|
|
4566
|
+
`[kody] \u{1F440} reaction failed after 3 attempts on comment ${commentId}: ${lastErr instanceof Error ? lastErr.message : String(lastErr)}
|
|
4567
|
+
`
|
|
4568
|
+
);
|
|
4466
4569
|
}
|
|
4467
|
-
function
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
const
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4570
|
+
function sleepMs(ms) {
|
|
4571
|
+
try {
|
|
4572
|
+
execFileSync2("sleep", [(ms / 1e3).toString()], { stdio: "ignore", timeout: ms + 1e3 });
|
|
4573
|
+
} catch {
|
|
4574
|
+
}
|
|
4575
|
+
}
|
|
4576
|
+
var init_gha = __esm({
|
|
4577
|
+
"src/gha.ts"() {
|
|
4578
|
+
"use strict";
|
|
4579
|
+
}
|
|
4580
|
+
});
|
|
4581
|
+
|
|
4582
|
+
// src/agencyBoundaryEval.ts
|
|
4583
|
+
function evaluateAgencyBoundaries(input) {
|
|
4584
|
+
const findings = [];
|
|
4585
|
+
const results = input.results ?? [];
|
|
4586
|
+
findings.push(evaluateObserveBoundary(input.capabilityKind, results));
|
|
4587
|
+
findings.push(evaluateVerifyBoundary(input.capabilityKind, results));
|
|
4588
|
+
findings.push(evaluateGoalOwnershipBoundary(results));
|
|
4486
4589
|
return {
|
|
4487
4590
|
version: 1,
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
...
|
|
4491
|
-
|
|
4492
|
-
...evidence ? { evidence } : {},
|
|
4493
|
-
facts,
|
|
4494
|
-
artifacts,
|
|
4495
|
-
missingEvidence,
|
|
4496
|
-
blockers
|
|
4591
|
+
status: findings.some((finding) => finding.status === "fail") ? "fail" : "pass",
|
|
4592
|
+
...input.capability ? { capability: input.capability } : {},
|
|
4593
|
+
...input.capabilityKind ? { capabilityKind: input.capabilityKind } : {},
|
|
4594
|
+
findings
|
|
4497
4595
|
};
|
|
4498
4596
|
}
|
|
4499
|
-
function
|
|
4500
|
-
|
|
4501
|
-
}
|
|
4502
|
-
function parseFacts2(raw) {
|
|
4503
|
-
if (raw === void 0) return {};
|
|
4504
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
4505
|
-
const facts = {};
|
|
4506
|
-
for (const [key, value] of Object.entries(raw)) {
|
|
4507
|
-
if (!key.trim()) return null;
|
|
4508
|
-
if (CONTROL_FACT_KEYS2.has(key)) continue;
|
|
4509
|
-
facts[key] = value;
|
|
4597
|
+
function evaluateObserveBoundary(capabilityKind, results) {
|
|
4598
|
+
if (capabilityKind !== "observe") {
|
|
4599
|
+
return pass("observe-does-not-act", "capability is not observe", { capabilityKind });
|
|
4510
4600
|
}
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
for (const item of raw) {
|
|
4517
|
-
if (typeof item !== "string") return null;
|
|
4518
|
-
out.push(item);
|
|
4601
|
+
const actionResults = results.filter(resultLooksLikeAction);
|
|
4602
|
+
if (actionResults.length === 0) {
|
|
4603
|
+
return pass("observe-does-not-act", "observe capability reported facts without action output", {
|
|
4604
|
+
resultCount: results.length
|
|
4605
|
+
});
|
|
4519
4606
|
}
|
|
4520
|
-
return
|
|
4607
|
+
return fail("observe-does-not-act", "observe capability returned action-shaped output", {
|
|
4608
|
+
resultCount: results.length,
|
|
4609
|
+
actionResults: actionResults.map(resultSummary)
|
|
4610
|
+
});
|
|
4521
4611
|
}
|
|
4522
|
-
function
|
|
4523
|
-
if (
|
|
4524
|
-
|
|
4612
|
+
function evaluateVerifyBoundary(capabilityKind, results) {
|
|
4613
|
+
if (capabilityKind !== "verify") {
|
|
4614
|
+
return pass("verify-does-not-fix", "capability is not verify", { capabilityKind });
|
|
4615
|
+
}
|
|
4616
|
+
const actionResults = results.filter(resultLooksLikeAction);
|
|
4617
|
+
if (actionResults.length === 0) {
|
|
4618
|
+
return pass("verify-does-not-fix", "verify capability returned verdict evidence without action output", {
|
|
4619
|
+
resultCount: results.length
|
|
4620
|
+
});
|
|
4621
|
+
}
|
|
4622
|
+
return fail("verify-does-not-fix", "verify capability returned fix/change output", {
|
|
4623
|
+
resultCount: results.length,
|
|
4624
|
+
actionResults: actionResults.map(resultSummary)
|
|
4625
|
+
});
|
|
4525
4626
|
}
|
|
4526
|
-
function
|
|
4527
|
-
|
|
4528
|
-
if (
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
if (!item || typeof item !== "object" || Array.isArray(item)) return null;
|
|
4532
|
-
const rawArtifact = item;
|
|
4533
|
-
const label = typeof rawArtifact.label === "string" ? rawArtifact.label.trim() : "";
|
|
4534
|
-
const url = typeof rawArtifact.url === "string" ? rawArtifact.url.trim() : "";
|
|
4535
|
-
const artifactPath = typeof rawArtifact.path === "string" ? rawArtifact.path.trim() : "";
|
|
4536
|
-
if (!label || !url && !artifactPath) return null;
|
|
4537
|
-
artifacts.push({
|
|
4538
|
-
label,
|
|
4539
|
-
...url ? { url } : {},
|
|
4540
|
-
...artifactPath ? { path: artifactPath } : {}
|
|
4627
|
+
function evaluateGoalOwnershipBoundary(results) {
|
|
4628
|
+
const targetBearing = results.filter((result) => result.target?.type === "goal");
|
|
4629
|
+
if (targetBearing.length === 0) {
|
|
4630
|
+
return pass("capability-does-not-own-goal-progress", "capability output is parent-neutral", {
|
|
4631
|
+
resultCount: results.length
|
|
4541
4632
|
});
|
|
4542
4633
|
}
|
|
4543
|
-
return
|
|
4634
|
+
return fail("capability-does-not-own-goal-progress", "capability output names a goal target", {
|
|
4635
|
+
resultCount: results.length,
|
|
4636
|
+
targetBearingResults: targetBearing.map(resultSummary)
|
|
4637
|
+
});
|
|
4544
4638
|
}
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4639
|
+
function resultLooksLikeAction(result) {
|
|
4640
|
+
if (result.status === "changed") return true;
|
|
4641
|
+
return Object.keys(result.facts).some((key) => ACTION_FACT_KEYS.has(key));
|
|
4642
|
+
}
|
|
4643
|
+
function resultSummary(result) {
|
|
4644
|
+
return {
|
|
4645
|
+
status: result.status,
|
|
4646
|
+
summary: result.summary,
|
|
4647
|
+
target: result.target,
|
|
4648
|
+
actionFactKeys: Object.keys(result.facts).filter((key) => ACTION_FACT_KEYS.has(key))
|
|
4649
|
+
};
|
|
4650
|
+
}
|
|
4651
|
+
function pass(rule, message, evidence) {
|
|
4652
|
+
return { rule, status: "pass", message, evidence };
|
|
4653
|
+
}
|
|
4654
|
+
function fail(rule, message, evidence) {
|
|
4655
|
+
return { rule, status: "fail", message, evidence };
|
|
4656
|
+
}
|
|
4657
|
+
var ACTION_FACT_KEYS;
|
|
4658
|
+
var init_agencyBoundaryEval = __esm({
|
|
4659
|
+
"src/agencyBoundaryEval.ts"() {
|
|
4548
4660
|
"use strict";
|
|
4549
|
-
|
|
4550
|
-
init_evidenceState();
|
|
4551
|
-
RESULT_LINE = /^KODY_(?:CAPABILITY|CAPABILITY)_RESULT=(.+)$/gm;
|
|
4552
|
-
STATUSES = /* @__PURE__ */ new Set(["pass", "fail", "blocked", "changed", "noop"]);
|
|
4553
|
-
CONTROL_FACT_KEYS2 = /* @__PURE__ */ new Set(["blockers", "destination", "capabilities", "route", "stage", "state"]);
|
|
4661
|
+
ACTION_FACT_KEYS = /* @__PURE__ */ new Set(["changedResources", "createdResources", "actionResult"]);
|
|
4554
4662
|
}
|
|
4555
4663
|
});
|
|
4556
4664
|
|
|
4557
|
-
// src/
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4665
|
+
// src/agency/capability-contract-validation.ts
|
|
4666
|
+
import Ajv from "ajv";
|
|
4667
|
+
function validateCapabilityContractValue(boundary, schema, value) {
|
|
4668
|
+
const validate = validator.compile(schema);
|
|
4669
|
+
if (!validate(value)) {
|
|
4670
|
+
throw new CapabilityContractValidationError(boundary, validate.errors ?? []);
|
|
4671
|
+
}
|
|
4672
|
+
}
|
|
4673
|
+
function capabilityContractInput(inputs, args, capabilityId, contractProperties = []) {
|
|
4674
|
+
const isGenericRunnerInput = inputs.some((input) => input.name === "input") && Object.hasOwn(args, "input");
|
|
4675
|
+
if (!isGenericRunnerInput) {
|
|
4676
|
+
const isParameterlessGenericRunner = (inputs.some((input) => input.name === "capability") || args.capability === capabilityId || !contractProperties.includes("capability")) && Object.hasOwn(args, "capability");
|
|
4677
|
+
if (!isParameterlessGenericRunner) return args;
|
|
4678
|
+
const { capability: _routingCapability, ...businessArgs } = args;
|
|
4679
|
+
return businessArgs;
|
|
4680
|
+
}
|
|
4681
|
+
const value = args.input;
|
|
4682
|
+
if (typeof value !== "string") return value;
|
|
4683
|
+
try {
|
|
4684
|
+
return JSON.parse(value);
|
|
4685
|
+
} catch {
|
|
4686
|
+
return value;
|
|
4687
|
+
}
|
|
4688
|
+
}
|
|
4689
|
+
var validator, CapabilityContractValidationError;
|
|
4690
|
+
var init_capability_contract_validation = __esm({
|
|
4691
|
+
"src/agency/capability-contract-validation.ts"() {
|
|
4561
4692
|
"use strict";
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4693
|
+
validator = new Ajv({
|
|
4694
|
+
allErrors: true,
|
|
4695
|
+
strict: true,
|
|
4696
|
+
validateFormats: false
|
|
4697
|
+
});
|
|
4698
|
+
CapabilityContractValidationError = class extends Error {
|
|
4699
|
+
constructor(boundary, errors) {
|
|
4700
|
+
const details = errors.map((error) => {
|
|
4701
|
+
const location = error.instancePath || "$";
|
|
4702
|
+
const property = error.keyword === "additionalProperties" && typeof error.params.additionalProperty === "string" ? ` (${error.params.additionalProperty})` : "";
|
|
4703
|
+
return `${location}: ${error.message ?? error.keyword}${property}`;
|
|
4704
|
+
}).join("; ");
|
|
4705
|
+
super(
|
|
4706
|
+
`Capability ${boundary} does not match its declared contract: ${details}`
|
|
4707
|
+
);
|
|
4708
|
+
this.boundary = boundary;
|
|
4709
|
+
this.errors = errors;
|
|
4710
|
+
this.name = "CapabilityContractValidationError";
|
|
4568
4711
|
}
|
|
4569
|
-
|
|
4712
|
+
boundary;
|
|
4713
|
+
errors;
|
|
4570
4714
|
};
|
|
4571
4715
|
}
|
|
4572
4716
|
});
|
|
4573
4717
|
|
|
4574
|
-
// src/
|
|
4575
|
-
function
|
|
4576
|
-
const
|
|
4577
|
-
const
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
description: cfg.label.description
|
|
4585
|
-
}
|
|
4586
|
-
});
|
|
4587
|
-
const contextBundle = buildContextBundle(cfg.context, cfg.contextExtras);
|
|
4588
|
-
const afterPreflight = cfg.context === "minimal" && cfg.contextExtras.length === 0 ? [{ script: "composePrompt" }] : [...contextBundle, { script: "composePrompt" }];
|
|
4589
|
-
profile.scripts.preflight = [...before, ...profile.scripts.preflight, ...afterPreflight];
|
|
4590
|
-
const beforePostflight = [{ script: "parseAgentResult" }];
|
|
4591
|
-
const verifyChain = cfg.verify ? [{ script: "verifyWithRetry" }, { script: "checkCoverageWithRetry" }, { script: "abortUnfinishedGitOps" }] : [];
|
|
4592
|
-
const tail = [
|
|
4593
|
-
...verifyChain,
|
|
4594
|
-
{ script: "commitAndPush" },
|
|
4595
|
-
{ script: "requireDeliveryArtifacts" },
|
|
4596
|
-
{ script: "ensurePr" },
|
|
4597
|
-
{ script: "postIssueComment" },
|
|
4598
|
-
{ script: "writeAgentRunSummary" },
|
|
4599
|
-
{ script: "saveTaskState" }
|
|
4600
|
-
];
|
|
4601
|
-
if (cfg.mirrorState) tail.push({ script: "mirrorStateToPr" });
|
|
4602
|
-
if (cfg.advance) tail.push({ script: "advanceFlow" });
|
|
4603
|
-
if (cfg.finalize) tail.push({ script: "finalizeTerminal" });
|
|
4604
|
-
profile.scripts.postflight = [...beforePostflight, ...profile.scripts.postflight, ...tail];
|
|
4605
|
-
}
|
|
4606
|
-
function buildContextBundle(context, extras) {
|
|
4607
|
-
const base = CONTEXT_BUNDLES[context] ?? [];
|
|
4608
|
-
if (base.length === 0 && extras.length === 0) return [];
|
|
4609
|
-
const out = [];
|
|
4610
|
-
let extrasInserted = false;
|
|
4611
|
-
for (const name of base) {
|
|
4612
|
-
out.push({ script: name });
|
|
4613
|
-
if (name === "loadTaskState" && extras.length > 0) {
|
|
4614
|
-
for (const e of extras) out.push({ script: e });
|
|
4615
|
-
extrasInserted = true;
|
|
4718
|
+
// src/capabilityReport.ts
|
|
4719
|
+
function parseCapabilityReportsFromText(text2) {
|
|
4720
|
+
const reports = [];
|
|
4721
|
+
for (const match of text2.matchAll(REPORT_LINE)) {
|
|
4722
|
+
const raw = match[1]?.trim();
|
|
4723
|
+
if (!raw) continue;
|
|
4724
|
+
try {
|
|
4725
|
+
const parsed = parseCapabilityReport(JSON.parse(raw));
|
|
4726
|
+
if (parsed) reports.push(parsed);
|
|
4727
|
+
} catch {
|
|
4616
4728
|
}
|
|
4617
4729
|
}
|
|
4618
|
-
|
|
4619
|
-
|
|
4730
|
+
return reports;
|
|
4731
|
+
}
|
|
4732
|
+
function parseCapabilityReport(raw) {
|
|
4733
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
4734
|
+
const obj = raw;
|
|
4735
|
+
const target = parseCapabilityReportTarget(obj.target);
|
|
4736
|
+
if (!target) return null;
|
|
4737
|
+
const evidence = parseCapabilityReportEvidence(obj.evidence);
|
|
4738
|
+
const facts = parseFacts(obj.facts);
|
|
4739
|
+
if (!evidence && !facts) return null;
|
|
4740
|
+
return {
|
|
4741
|
+
target,
|
|
4742
|
+
...evidence ? { evidence } : {},
|
|
4743
|
+
...facts ? { facts } : {}
|
|
4744
|
+
};
|
|
4745
|
+
}
|
|
4746
|
+
function parseCapabilityReportTarget(raw) {
|
|
4747
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
4748
|
+
const target = raw;
|
|
4749
|
+
if (target.type !== "goal" && target.type !== "task" && target.type !== "capability") return null;
|
|
4750
|
+
if (typeof target.id !== "string" || target.id.trim().length === 0) return null;
|
|
4751
|
+
return { type: target.type, id: target.id.trim() };
|
|
4752
|
+
}
|
|
4753
|
+
function parseCapabilityReportEvidence(raw) {
|
|
4754
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
4755
|
+
const out = {};
|
|
4756
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
4757
|
+
if (typeof key !== "string" || key.length === 0 || typeof value !== "boolean") return null;
|
|
4758
|
+
out[key] = value;
|
|
4620
4759
|
}
|
|
4621
4760
|
return out;
|
|
4622
4761
|
}
|
|
4623
|
-
function
|
|
4624
|
-
if (!raw)
|
|
4625
|
-
|
|
4762
|
+
function parseFacts(raw) {
|
|
4763
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
4764
|
+
const out = {};
|
|
4765
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
4766
|
+
if (typeof key !== "string" || key.length === 0) return null;
|
|
4767
|
+
if (CONTROL_FACT_KEYS.has(key)) continue;
|
|
4768
|
+
out[key] = value;
|
|
4626
4769
|
}
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4770
|
+
return out;
|
|
4771
|
+
}
|
|
4772
|
+
var REPORT_LINE, CONTROL_FACT_KEYS;
|
|
4773
|
+
var init_capabilityReport = __esm({
|
|
4774
|
+
"src/capabilityReport.ts"() {
|
|
4775
|
+
"use strict";
|
|
4776
|
+
REPORT_LINE = /^KODY_(?:CAPABILITY|CAPABILITY)_REPORT=(.+)$/gm;
|
|
4777
|
+
CONTROL_FACT_KEYS = /* @__PURE__ */ new Set(["blockers", "destination", "capabilities", "route", "stage", "state"]);
|
|
4630
4778
|
}
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4779
|
+
});
|
|
4780
|
+
|
|
4781
|
+
// src/goal/evidenceState.ts
|
|
4782
|
+
function parseGoalEvidenceState(raw) {
|
|
4783
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
|
|
4784
|
+
const out = {};
|
|
4785
|
+
for (const [evidence, value] of Object.entries(raw)) {
|
|
4786
|
+
const progress = parseGoalEvidenceProgress(value);
|
|
4787
|
+
if (progress) out[evidence] = progress;
|
|
4788
|
+
}
|
|
4789
|
+
return out;
|
|
4790
|
+
}
|
|
4791
|
+
function mergeGoalEvidenceProgress(state, evidence, update) {
|
|
4792
|
+
const prior = state[evidence];
|
|
4793
|
+
const next = {
|
|
4794
|
+
resultClass: update.resultClass,
|
|
4795
|
+
attempts: update.attempts ?? prior?.attempts ?? 0,
|
|
4796
|
+
...prior?.reason ? { reason: prior.reason } : {},
|
|
4797
|
+
...prior?.nextAction ? { nextAction: prior.nextAction } : {},
|
|
4798
|
+
...prior?.nextRetryAt ? { nextRetryAt: prior.nextRetryAt } : {},
|
|
4799
|
+
...prior?.issue ? { issue: prior.issue } : {},
|
|
4800
|
+
...prior?.updatedAt ? { updatedAt: prior.updatedAt } : {},
|
|
4801
|
+
...definedProgressFields(update)
|
|
4802
|
+
};
|
|
4803
|
+
return {
|
|
4804
|
+
...state,
|
|
4805
|
+
[evidence]: next
|
|
4806
|
+
};
|
|
4807
|
+
}
|
|
4808
|
+
function isGoalEvidenceResultClass(value) {
|
|
4809
|
+
return value === "succeeded" || value === "pending" || value === "retryable" || value === "needsFix" || value === "fatal";
|
|
4810
|
+
}
|
|
4811
|
+
function parseGoalEvidenceProgress(value) {
|
|
4812
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
4813
|
+
const raw = value;
|
|
4814
|
+
if (!isGoalEvidenceResultClass(raw.resultClass)) return null;
|
|
4815
|
+
const attempts = typeof raw.attempts === "number" && raw.attempts >= 0 ? Math.floor(raw.attempts) : 0;
|
|
4816
|
+
return {
|
|
4817
|
+
resultClass: raw.resultClass,
|
|
4818
|
+
attempts,
|
|
4819
|
+
...stringField2(raw.reason) ? { reason: stringField2(raw.reason) } : {},
|
|
4820
|
+
...stringField2(raw.nextAction) ? { nextAction: stringField2(raw.nextAction) } : {},
|
|
4821
|
+
...stringField2(raw.nextRetryAt) ? { nextRetryAt: stringField2(raw.nextRetryAt) } : {},
|
|
4822
|
+
...positiveInteger(raw.issue) ? { issue: positiveInteger(raw.issue) } : {},
|
|
4823
|
+
...stringField2(raw.updatedAt) ? { updatedAt: stringField2(raw.updatedAt) } : {}
|
|
4824
|
+
};
|
|
4825
|
+
}
|
|
4826
|
+
function definedProgressFields(update) {
|
|
4827
|
+
const out = {};
|
|
4828
|
+
if (update.reason !== void 0) out.reason = update.reason;
|
|
4829
|
+
if (update.nextAction !== void 0) out.nextAction = update.nextAction;
|
|
4830
|
+
if (update.nextRetryAt !== void 0) out.nextRetryAt = update.nextRetryAt;
|
|
4831
|
+
if (update.issue !== void 0) out.issue = update.issue;
|
|
4832
|
+
if (update.updatedAt !== void 0) out.updatedAt = update.updatedAt;
|
|
4833
|
+
return out;
|
|
4834
|
+
}
|
|
4835
|
+
function stringField2(value) {
|
|
4836
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
4837
|
+
}
|
|
4838
|
+
function positiveInteger(value) {
|
|
4839
|
+
if (typeof value === "number" && Number.isInteger(value) && value > 0) return value;
|
|
4840
|
+
return void 0;
|
|
4841
|
+
}
|
|
4842
|
+
var init_evidenceState = __esm({
|
|
4843
|
+
"src/goal/evidenceState.ts"() {
|
|
4844
|
+
"use strict";
|
|
4845
|
+
}
|
|
4846
|
+
});
|
|
4847
|
+
|
|
4848
|
+
// src/capabilityResult.ts
|
|
4849
|
+
function parseCapabilityResultsFromText(text2) {
|
|
4850
|
+
const results = [];
|
|
4851
|
+
for (const match of text2.matchAll(RESULT_LINE)) {
|
|
4852
|
+
const raw = match[1]?.trim();
|
|
4853
|
+
if (!raw) continue;
|
|
4854
|
+
try {
|
|
4855
|
+
const parsed = parseCapabilityResult(JSON.parse(raw));
|
|
4856
|
+
if (parsed) results.push(parsed);
|
|
4857
|
+
} catch {
|
|
4858
|
+
}
|
|
4859
|
+
}
|
|
4860
|
+
return results;
|
|
4861
|
+
}
|
|
4862
|
+
function parseCapabilityResult(raw) {
|
|
4863
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
4864
|
+
const obj = raw;
|
|
4865
|
+
if (obj.version !== 1) return null;
|
|
4866
|
+
if (!isCapabilityResultStatus(obj.status)) return null;
|
|
4867
|
+
const summary = typeof obj.summary === "string" ? obj.summary.trim() : "";
|
|
4868
|
+
if (!summary) return null;
|
|
4869
|
+
const target = obj.target === void 0 ? void 0 : parseCapabilityReportTarget(obj.target);
|
|
4870
|
+
if (obj.target !== void 0 && !target) return null;
|
|
4871
|
+
const evidence = obj.evidence === void 0 ? void 0 : parseCapabilityReportEvidence(obj.evidence);
|
|
4872
|
+
if (obj.evidence !== void 0 && !evidence) return null;
|
|
4873
|
+
const facts = parseFacts2(obj.facts);
|
|
4874
|
+
if (!facts) return null;
|
|
4875
|
+
const artifacts = parseArtifacts(obj.artifacts);
|
|
4876
|
+
if (!artifacts) return null;
|
|
4877
|
+
const missingEvidence = parseOptionalStringArray(obj.missingEvidence);
|
|
4878
|
+
if (!missingEvidence) return null;
|
|
4879
|
+
const blockers = parseOptionalStringArray(obj.blockers);
|
|
4880
|
+
if (!blockers) return null;
|
|
4881
|
+
return {
|
|
4882
|
+
version: 1,
|
|
4883
|
+
...target ? { target } : {},
|
|
4884
|
+
status: obj.status,
|
|
4885
|
+
...isGoalEvidenceResultClass(obj.resultClass) ? { resultClass: obj.resultClass } : {},
|
|
4886
|
+
summary,
|
|
4887
|
+
...evidence ? { evidence } : {},
|
|
4888
|
+
facts,
|
|
4889
|
+
artifacts,
|
|
4890
|
+
missingEvidence,
|
|
4891
|
+
blockers
|
|
4892
|
+
};
|
|
4893
|
+
}
|
|
4894
|
+
function isCapabilityResultStatus(value) {
|
|
4895
|
+
return typeof value === "string" && STATUSES.has(value);
|
|
4896
|
+
}
|
|
4897
|
+
function parseFacts2(raw) {
|
|
4898
|
+
if (raw === void 0) return {};
|
|
4899
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
4900
|
+
const facts = {};
|
|
4901
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
4902
|
+
if (!key.trim()) return null;
|
|
4903
|
+
if (CONTROL_FACT_KEYS2.has(key)) continue;
|
|
4904
|
+
facts[key] = value;
|
|
4905
|
+
}
|
|
4906
|
+
return facts;
|
|
4907
|
+
}
|
|
4908
|
+
function parseStringArray(raw) {
|
|
4909
|
+
if (!Array.isArray(raw)) return null;
|
|
4910
|
+
const out = [];
|
|
4911
|
+
for (const item of raw) {
|
|
4912
|
+
if (typeof item !== "string") return null;
|
|
4913
|
+
out.push(item);
|
|
4914
|
+
}
|
|
4915
|
+
return out;
|
|
4916
|
+
}
|
|
4917
|
+
function parseOptionalStringArray(raw) {
|
|
4918
|
+
if (raw === void 0) return [];
|
|
4919
|
+
return parseStringArray(raw);
|
|
4920
|
+
}
|
|
4921
|
+
function parseArtifacts(raw) {
|
|
4922
|
+
if (raw === void 0) return [];
|
|
4923
|
+
if (!Array.isArray(raw)) return null;
|
|
4924
|
+
const artifacts = [];
|
|
4925
|
+
for (const item of raw) {
|
|
4926
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) return null;
|
|
4927
|
+
const rawArtifact = item;
|
|
4928
|
+
const label = typeof rawArtifact.label === "string" ? rawArtifact.label.trim() : "";
|
|
4929
|
+
const url = typeof rawArtifact.url === "string" ? rawArtifact.url.trim() : "";
|
|
4930
|
+
const artifactPath = typeof rawArtifact.path === "string" ? rawArtifact.path.trim() : "";
|
|
4931
|
+
if (!label || !url && !artifactPath) return null;
|
|
4932
|
+
artifacts.push({
|
|
4933
|
+
label,
|
|
4934
|
+
...url ? { url } : {},
|
|
4935
|
+
...artifactPath ? { path: artifactPath } : {}
|
|
4936
|
+
});
|
|
4937
|
+
}
|
|
4938
|
+
return artifacts;
|
|
4939
|
+
}
|
|
4940
|
+
var RESULT_LINE, STATUSES, CONTROL_FACT_KEYS2;
|
|
4941
|
+
var init_capabilityResult = __esm({
|
|
4942
|
+
"src/capabilityResult.ts"() {
|
|
4943
|
+
"use strict";
|
|
4944
|
+
init_capabilityReport();
|
|
4945
|
+
init_evidenceState();
|
|
4946
|
+
RESULT_LINE = /^KODY_(?:CAPABILITY|CAPABILITY)_RESULT=(.+)$/gm;
|
|
4947
|
+
STATUSES = /* @__PURE__ */ new Set(["pass", "fail", "blocked", "changed", "noop"]);
|
|
4948
|
+
CONTROL_FACT_KEYS2 = /* @__PURE__ */ new Set(["blockers", "destination", "capabilities", "route", "stage", "state"]);
|
|
4949
|
+
}
|
|
4950
|
+
});
|
|
4951
|
+
|
|
4952
|
+
// src/profile-error.ts
|
|
4953
|
+
var ProfileError;
|
|
4954
|
+
var init_profile_error = __esm({
|
|
4955
|
+
"src/profile-error.ts"() {
|
|
4956
|
+
"use strict";
|
|
4957
|
+
ProfileError = class extends Error {
|
|
4958
|
+
constructor(profilePath, message) {
|
|
4959
|
+
super(`Invalid profile at ${profilePath}:
|
|
4960
|
+
${message}`);
|
|
4961
|
+
this.profilePath = profilePath;
|
|
4962
|
+
this.name = "ProfileError";
|
|
4963
|
+
}
|
|
4964
|
+
profilePath;
|
|
4965
|
+
};
|
|
4966
|
+
}
|
|
4967
|
+
});
|
|
4968
|
+
|
|
4969
|
+
// src/lifecycles/prBranch.ts
|
|
4970
|
+
function prBranchLifecycle(profile, profilePath) {
|
|
4971
|
+
const cfg = validateConfig(profile.lifecycleConfig, profilePath);
|
|
4972
|
+
const before = [];
|
|
4973
|
+
if (cfg.sync) before.push({ script: "syncFlow" });
|
|
4974
|
+
before.push({
|
|
4975
|
+
script: "setLifecycleLabel",
|
|
4976
|
+
with: {
|
|
4977
|
+
label: cfg.label.name,
|
|
4978
|
+
color: cfg.label.color,
|
|
4979
|
+
description: cfg.label.description
|
|
4980
|
+
}
|
|
4981
|
+
});
|
|
4982
|
+
const contextBundle = buildContextBundle(cfg.context, cfg.contextExtras);
|
|
4983
|
+
const afterPreflight = cfg.context === "minimal" && cfg.contextExtras.length === 0 ? [{ script: "composePrompt" }] : [...contextBundle, { script: "composePrompt" }];
|
|
4984
|
+
profile.scripts.preflight = [...before, ...profile.scripts.preflight, ...afterPreflight];
|
|
4985
|
+
const beforePostflight = [{ script: "parseAgentResult" }];
|
|
4986
|
+
const verifyChain = cfg.verify ? [{ script: "verifyWithRetry" }, { script: "checkCoverageWithRetry" }, { script: "abortUnfinishedGitOps" }] : [];
|
|
4987
|
+
const tail = [
|
|
4988
|
+
...verifyChain,
|
|
4989
|
+
{ script: "commitAndPush" },
|
|
4990
|
+
{ script: "requireDeliveryArtifacts" },
|
|
4991
|
+
{ script: "ensurePr" },
|
|
4992
|
+
{ script: "postIssueComment" },
|
|
4993
|
+
{ script: "writeAgentRunSummary" },
|
|
4994
|
+
{ script: "saveTaskState" }
|
|
4995
|
+
];
|
|
4996
|
+
if (cfg.mirrorState) tail.push({ script: "mirrorStateToPr" });
|
|
4997
|
+
if (cfg.advance) tail.push({ script: "advanceFlow" });
|
|
4998
|
+
if (cfg.finalize) tail.push({ script: "finalizeTerminal" });
|
|
4999
|
+
profile.scripts.postflight = [...beforePostflight, ...profile.scripts.postflight, ...tail];
|
|
5000
|
+
}
|
|
5001
|
+
function buildContextBundle(context, extras) {
|
|
5002
|
+
const base = CONTEXT_BUNDLES[context] ?? [];
|
|
5003
|
+
if (base.length === 0 && extras.length === 0) return [];
|
|
5004
|
+
const out = [];
|
|
5005
|
+
let extrasInserted = false;
|
|
5006
|
+
for (const name of base) {
|
|
5007
|
+
out.push({ script: name });
|
|
5008
|
+
if (name === "loadTaskState" && extras.length > 0) {
|
|
5009
|
+
for (const e of extras) out.push({ script: e });
|
|
5010
|
+
extrasInserted = true;
|
|
5011
|
+
}
|
|
5012
|
+
}
|
|
5013
|
+
if (!extrasInserted && extras.length > 0) {
|
|
5014
|
+
out.unshift(...extras.map((e) => ({ script: e })));
|
|
5015
|
+
}
|
|
5016
|
+
return out;
|
|
5017
|
+
}
|
|
5018
|
+
function validateConfig(raw, profilePath) {
|
|
5019
|
+
if (!raw) {
|
|
5020
|
+
throw new ProfileError(profilePath, `lifecycle "pr-branch" requires "lifecycleConfig" with a "label" object`);
|
|
5021
|
+
}
|
|
5022
|
+
const label = raw.label;
|
|
5023
|
+
if (!label || typeof label !== "object" || Array.isArray(label)) {
|
|
5024
|
+
throw new ProfileError(profilePath, `lifecycle "pr-branch": lifecycleConfig.label must be an object`);
|
|
5025
|
+
}
|
|
5026
|
+
const lbl = label;
|
|
5027
|
+
for (const k of ["name", "color", "description"]) {
|
|
5028
|
+
if (typeof lbl[k] !== "string" || lbl[k].length === 0) {
|
|
5029
|
+
throw new ProfileError(
|
|
4635
5030
|
profilePath,
|
|
4636
5031
|
`lifecycle "pr-branch": lifecycleConfig.label.${k} must be a non-empty string`
|
|
4637
5032
|
);
|
|
@@ -4715,31 +5110,31 @@ var init_lifecycles = __esm({
|
|
|
4715
5110
|
});
|
|
4716
5111
|
|
|
4717
5112
|
// src/scripts/buildSyntheticPlugin.ts
|
|
4718
|
-
import * as
|
|
5113
|
+
import * as fs20 from "fs";
|
|
4719
5114
|
import * as os3 from "os";
|
|
4720
|
-
import * as
|
|
5115
|
+
import * as path19 from "path";
|
|
4721
5116
|
function getPluginsCatalogRoot() {
|
|
4722
|
-
const here =
|
|
5117
|
+
const here = path19.dirname(new URL(import.meta.url).pathname);
|
|
4723
5118
|
const candidates = [
|
|
4724
|
-
|
|
5119
|
+
path19.join(here, "..", "plugins"),
|
|
4725
5120
|
// dev: src/scripts → src/plugins
|
|
4726
|
-
|
|
5121
|
+
path19.join(here, "..", "..", "plugins"),
|
|
4727
5122
|
// built: dist/scripts → dist/plugins
|
|
4728
|
-
|
|
5123
|
+
path19.join(here, "..", "..", "src", "plugins")
|
|
4729
5124
|
// fallback
|
|
4730
5125
|
];
|
|
4731
5126
|
for (const c of candidates) {
|
|
4732
|
-
if (
|
|
5127
|
+
if (fs20.existsSync(c) && fs20.statSync(c).isDirectory()) return c;
|
|
4733
5128
|
}
|
|
4734
5129
|
return candidates[0];
|
|
4735
5130
|
}
|
|
4736
5131
|
function copyDir(src, dst) {
|
|
4737
|
-
|
|
4738
|
-
for (const ent of
|
|
4739
|
-
const s =
|
|
4740
|
-
const d =
|
|
5132
|
+
fs20.mkdirSync(dst, { recursive: true });
|
|
5133
|
+
for (const ent of fs20.readdirSync(src, { withFileTypes: true })) {
|
|
5134
|
+
const s = path19.join(src, ent.name);
|
|
5135
|
+
const d = path19.join(dst, ent.name);
|
|
4741
5136
|
if (ent.isDirectory()) copyDir(s, d);
|
|
4742
|
-
else if (ent.isFile())
|
|
5137
|
+
else if (ent.isFile()) fs20.copyFileSync(s, d);
|
|
4743
5138
|
}
|
|
4744
5139
|
}
|
|
4745
5140
|
var buildSyntheticPlugin;
|
|
@@ -4752,47 +5147,47 @@ var init_buildSyntheticPlugin = __esm({
|
|
|
4752
5147
|
if (!needsSynthetic) return;
|
|
4753
5148
|
const catalog = getPluginsCatalogRoot();
|
|
4754
5149
|
const runId = `${profile.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
4755
|
-
const root =
|
|
4756
|
-
|
|
5150
|
+
const root = path19.join(os3.tmpdir(), `kody-synth-${runId}`);
|
|
5151
|
+
fs20.mkdirSync(path19.join(root, ".claude-plugin"), { recursive: true });
|
|
4757
5152
|
const resolvePart = (bucket, entry) => {
|
|
4758
|
-
const local =
|
|
4759
|
-
if (
|
|
4760
|
-
const shared =
|
|
4761
|
-
if (
|
|
4762
|
-
const central =
|
|
4763
|
-
if (
|
|
5153
|
+
const local = path19.join(profile.dir, bucket, entry);
|
|
5154
|
+
if (fs20.existsSync(local)) return local;
|
|
5155
|
+
const shared = path19.resolve(profile.dir, "..", "..", "shared", bucket, entry);
|
|
5156
|
+
if (fs20.existsSync(shared)) return shared;
|
|
5157
|
+
const central = path19.join(catalog, bucket, entry);
|
|
5158
|
+
if (fs20.existsSync(central)) return central;
|
|
4764
5159
|
throw new Error(
|
|
4765
|
-
`buildSyntheticPlugin: ${bucket} entry '${entry}' not found in implementation dir (${profile.dir}/${bucket}/), Store shared assets (${
|
|
5160
|
+
`buildSyntheticPlugin: ${bucket} entry '${entry}' not found in implementation dir (${profile.dir}/${bucket}/), Store shared assets (${path19.dirname(shared)}/), or catalog (${catalog}/${bucket}/)`
|
|
4766
5161
|
);
|
|
4767
5162
|
};
|
|
4768
5163
|
if (cc.skills.length > 0) {
|
|
4769
|
-
const dst =
|
|
4770
|
-
|
|
5164
|
+
const dst = path19.join(root, "skills");
|
|
5165
|
+
fs20.mkdirSync(dst, { recursive: true });
|
|
4771
5166
|
for (const name of cc.skills) {
|
|
4772
|
-
copyDir(resolvePart("skills", name),
|
|
5167
|
+
copyDir(resolvePart("skills", name), path19.join(dst, name));
|
|
4773
5168
|
}
|
|
4774
5169
|
}
|
|
4775
5170
|
if (cc.commands.length > 0) {
|
|
4776
|
-
const dst =
|
|
4777
|
-
|
|
5171
|
+
const dst = path19.join(root, "commands");
|
|
5172
|
+
fs20.mkdirSync(dst, { recursive: true });
|
|
4778
5173
|
for (const name of cc.commands) {
|
|
4779
|
-
|
|
5174
|
+
fs20.copyFileSync(resolvePart("commands", `${name}.md`), path19.join(dst, `${name}.md`));
|
|
4780
5175
|
}
|
|
4781
5176
|
}
|
|
4782
5177
|
if (cc.hooks.length > 0) {
|
|
4783
|
-
const dst =
|
|
4784
|
-
|
|
5178
|
+
const dst = path19.join(root, "hooks");
|
|
5179
|
+
fs20.mkdirSync(dst, { recursive: true });
|
|
4785
5180
|
const merged = { hooks: {} };
|
|
4786
5181
|
for (const name of cc.hooks) {
|
|
4787
5182
|
const src = resolvePart("hooks", `${name}.json`);
|
|
4788
|
-
const parsed = JSON.parse(
|
|
5183
|
+
const parsed = JSON.parse(fs20.readFileSync(src, "utf-8"));
|
|
4789
5184
|
for (const [event, entries] of Object.entries(parsed.hooks ?? {})) {
|
|
4790
5185
|
if (!Array.isArray(entries)) continue;
|
|
4791
5186
|
if (!merged.hooks[event]) merged.hooks[event] = [];
|
|
4792
5187
|
merged.hooks[event].push(...entries);
|
|
4793
5188
|
}
|
|
4794
5189
|
}
|
|
4795
|
-
|
|
5190
|
+
fs20.writeFileSync(path19.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
|
|
4796
5191
|
`);
|
|
4797
5192
|
}
|
|
4798
5193
|
const manifest = {
|
|
@@ -4802,7 +5197,7 @@ var init_buildSyntheticPlugin = __esm({
|
|
|
4802
5197
|
};
|
|
4803
5198
|
if (cc.skills.length > 0) manifest.skills = ["./skills/"];
|
|
4804
5199
|
if (cc.commands.length > 0) manifest.commands = ["./commands/"];
|
|
4805
|
-
|
|
5200
|
+
fs20.writeFileSync(path19.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
|
|
4806
5201
|
`);
|
|
4807
5202
|
ctx.data.syntheticPluginPath = root;
|
|
4808
5203
|
};
|
|
@@ -4810,8 +5205,8 @@ var init_buildSyntheticPlugin = __esm({
|
|
|
4810
5205
|
});
|
|
4811
5206
|
|
|
4812
5207
|
// src/subagents.ts
|
|
4813
|
-
import * as
|
|
4814
|
-
import * as
|
|
5208
|
+
import * as fs21 from "fs";
|
|
5209
|
+
import * as path20 from "path";
|
|
4815
5210
|
function splitFrontmatter(raw) {
|
|
4816
5211
|
const match = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/.exec(raw);
|
|
4817
5212
|
if (!match) return { fm: {}, body: raw.trim() };
|
|
@@ -4824,12 +5219,12 @@ function splitFrontmatter(raw) {
|
|
|
4824
5219
|
return { fm, body: (match[2] ?? "").trim() };
|
|
4825
5220
|
}
|
|
4826
5221
|
function resolveAgentFile2(profileDir, name) {
|
|
4827
|
-
const local =
|
|
4828
|
-
if (
|
|
4829
|
-
const shared =
|
|
4830
|
-
if (
|
|
4831
|
-
const central =
|
|
4832
|
-
if (
|
|
5222
|
+
const local = path20.join(profileDir, "agents", `${name}.md`);
|
|
5223
|
+
if (fs21.existsSync(local)) return local;
|
|
5224
|
+
const shared = path20.resolve(profileDir, "..", "..", "shared", "agents", `${name}.md`);
|
|
5225
|
+
if (fs21.existsSync(shared)) return shared;
|
|
5226
|
+
const central = path20.join(getPluginsCatalogRoot(), "agents", `${name}.md`);
|
|
5227
|
+
if (fs21.existsSync(central)) return central;
|
|
4833
5228
|
throw new Error(
|
|
4834
5229
|
`loadSubagents: agent '${name}' not found in ${profileDir}/agents/, hydrated shared assets, or engine catalog`
|
|
4835
5230
|
);
|
|
@@ -4840,7 +5235,7 @@ function captureSubagentTemplates(profile) {
|
|
|
4840
5235
|
const out = {};
|
|
4841
5236
|
for (const name of names) {
|
|
4842
5237
|
try {
|
|
4843
|
-
out[name] =
|
|
5238
|
+
out[name] = fs21.readFileSync(resolveAgentFile2(profile.dir, name), "utf-8");
|
|
4844
5239
|
} catch {
|
|
4845
5240
|
}
|
|
4846
5241
|
}
|
|
@@ -4851,7 +5246,7 @@ function loadSubagents(profile) {
|
|
|
4851
5246
|
if (!names || names.length === 0) return void 0;
|
|
4852
5247
|
const agents = {};
|
|
4853
5248
|
for (const name of names) {
|
|
4854
|
-
const raw = profile.subagentTemplates?.[name] ??
|
|
5249
|
+
const raw = profile.subagentTemplates?.[name] ?? fs21.readFileSync(resolveAgentFile2(profile.dir, name), "utf-8");
|
|
4855
5250
|
const { fm, body } = splitFrontmatter(raw);
|
|
4856
5251
|
if (!body) throw new Error(`loadSubagents: agent '${name}' has an empty prompt body`);
|
|
4857
5252
|
const def = {
|
|
@@ -4876,15 +5271,15 @@ var init_subagents = __esm({
|
|
|
4876
5271
|
|
|
4877
5272
|
// src/profile.ts
|
|
4878
5273
|
import { createHash as createHash3 } from "crypto";
|
|
4879
|
-
import * as
|
|
4880
|
-
import * as
|
|
5274
|
+
import * as fs22 from "fs";
|
|
5275
|
+
import * as path21 from "path";
|
|
4881
5276
|
function loadProfile(profilePath) {
|
|
4882
|
-
if (!
|
|
5277
|
+
if (!fs22.existsSync(profilePath)) {
|
|
4883
5278
|
throw new ProfileError(profilePath, "file not found");
|
|
4884
5279
|
}
|
|
4885
5280
|
let raw;
|
|
4886
5281
|
try {
|
|
4887
|
-
raw = JSON.parse(
|
|
5282
|
+
raw = JSON.parse(fs22.readFileSync(profilePath, "utf-8"));
|
|
4888
5283
|
} catch (err) {
|
|
4889
5284
|
throw new ProfileError(profilePath, `invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
4890
5285
|
}
|
|
@@ -4896,7 +5291,7 @@ function loadProfile(profilePath) {
|
|
|
4896
5291
|
const unknownKeys = Object.keys(r).filter((k) => !KNOWN_PROFILE_KEYS.has(k));
|
|
4897
5292
|
if (unknownKeys.length > 0) {
|
|
4898
5293
|
process.stderr.write(
|
|
4899
|
-
`[kody profile] ${
|
|
5294
|
+
`[kody profile] ${path21.basename(path21.dirname(profilePath))}: unknown top-level keys ignored: ${unknownKeys.join(", ")}
|
|
4900
5295
|
`
|
|
4901
5296
|
);
|
|
4902
5297
|
}
|
|
@@ -4906,7 +5301,7 @@ function loadProfile(profilePath) {
|
|
|
4906
5301
|
if (!refPath) {
|
|
4907
5302
|
throw new ProfileError(profilePath, `capability references unknown implementation '${execRef}'`);
|
|
4908
5303
|
}
|
|
4909
|
-
if (
|
|
5304
|
+
if (path21.resolve(refPath) === path21.resolve(profilePath)) {
|
|
4910
5305
|
} else {
|
|
4911
5306
|
const base = loadProfile(refPath);
|
|
4912
5307
|
return {
|
|
@@ -5004,8 +5399,8 @@ function loadProfile(profilePath) {
|
|
|
5004
5399
|
// Phase 5 in-process handoff opt-in. Default false; containers
|
|
5005
5400
|
// flip to true after end-to-end verification.
|
|
5006
5401
|
preloadContext: r.preloadContext === true,
|
|
5007
|
-
dir:
|
|
5008
|
-
promptTemplates: readPromptTemplates(
|
|
5402
|
+
dir: path21.dirname(profilePath),
|
|
5403
|
+
promptTemplates: readPromptTemplates(path21.dirname(profilePath))
|
|
5009
5404
|
};
|
|
5010
5405
|
if (lifecycle) {
|
|
5011
5406
|
applyLifecycle(profile, profilePath);
|
|
@@ -5040,19 +5435,19 @@ function loadProfile(profilePath) {
|
|
|
5040
5435
|
return profile;
|
|
5041
5436
|
}
|
|
5042
5437
|
function compileRuntimeDocument(runtimePath, document) {
|
|
5043
|
-
if (
|
|
5438
|
+
if (path21.basename(runtimePath) !== "runtime.json") return document;
|
|
5044
5439
|
if (document.adapter !== "kody-engine-profile") {
|
|
5045
5440
|
throw new ProfileError(runtimePath, "unsupported runtime adapter document");
|
|
5046
5441
|
}
|
|
5047
|
-
const implementationDir =
|
|
5048
|
-
const implementation = readJsonObject(
|
|
5049
|
-
const definitionsRoot2 =
|
|
5442
|
+
const implementationDir = path21.dirname(runtimePath);
|
|
5443
|
+
const implementation = readJsonObject(path21.join(implementationDir, "definition.json"), "Implementation definition");
|
|
5444
|
+
const definitionsRoot2 = path21.dirname(path21.dirname(implementationDir));
|
|
5050
5445
|
const capabilityId = implementation.capabilityRef && typeof implementation.capabilityRef === "object" && !Array.isArray(implementation.capabilityRef) ? implementation.capabilityRef.id : void 0;
|
|
5051
5446
|
if (typeof capabilityId !== "string" || !capabilityId) {
|
|
5052
5447
|
throw new ProfileError(runtimePath, "Implementation capabilityRef is invalid");
|
|
5053
5448
|
}
|
|
5054
5449
|
const capability = readJsonObject(
|
|
5055
|
-
|
|
5450
|
+
path21.join(definitionsRoot2, "capabilities", capabilityId, "definition.json"),
|
|
5056
5451
|
"Capability definition"
|
|
5057
5452
|
);
|
|
5058
5453
|
const {
|
|
@@ -5091,7 +5486,7 @@ function canonical(value) {
|
|
|
5091
5486
|
}
|
|
5092
5487
|
function readJsonObject(filePath, label) {
|
|
5093
5488
|
try {
|
|
5094
|
-
const value = JSON.parse(
|
|
5489
|
+
const value = JSON.parse(fs22.readFileSync(filePath, "utf-8"));
|
|
5095
5490
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
5096
5491
|
throw new Error("must be an object");
|
|
5097
5492
|
}
|
|
@@ -5109,17 +5504,17 @@ function readPromptTemplates(dir) {
|
|
|
5109
5504
|
const out = {};
|
|
5110
5505
|
const read = (p) => {
|
|
5111
5506
|
try {
|
|
5112
|
-
out[p] =
|
|
5507
|
+
out[p] = fs22.readFileSync(p, "utf-8");
|
|
5113
5508
|
} catch {
|
|
5114
5509
|
}
|
|
5115
5510
|
};
|
|
5116
|
-
read(
|
|
5117
|
-
read(
|
|
5118
|
-
read(
|
|
5511
|
+
read(path21.join(dir, "prompt.md"));
|
|
5512
|
+
read(path21.join(dir, "capability.md"));
|
|
5513
|
+
read(path21.join(dir, "capability.md"));
|
|
5119
5514
|
try {
|
|
5120
|
-
const promptsDir =
|
|
5121
|
-
for (const ent of
|
|
5122
|
-
if (ent.endsWith(".md")) read(
|
|
5515
|
+
const promptsDir = path21.join(dir, "prompts");
|
|
5516
|
+
for (const ent of fs22.readdirSync(promptsDir)) {
|
|
5517
|
+
if (ent.endsWith(".md")) read(path21.join(promptsDir, ent));
|
|
5123
5518
|
}
|
|
5124
5519
|
} catch {
|
|
5125
5520
|
}
|
|
@@ -5894,16 +6289,16 @@ var init_state = __esm({
|
|
|
5894
6289
|
});
|
|
5895
6290
|
|
|
5896
6291
|
// src/prompt.ts
|
|
5897
|
-
import * as
|
|
5898
|
-
import * as
|
|
6292
|
+
import * as fs23 from "fs";
|
|
6293
|
+
import * as path22 from "path";
|
|
5899
6294
|
function loadProjectConventions(projectDir) {
|
|
5900
6295
|
const out = [];
|
|
5901
6296
|
for (const rel of CONVENTION_FILES) {
|
|
5902
|
-
const abs =
|
|
5903
|
-
if (!
|
|
6297
|
+
const abs = path22.join(projectDir, rel);
|
|
6298
|
+
if (!fs23.existsSync(abs)) continue;
|
|
5904
6299
|
let content;
|
|
5905
6300
|
try {
|
|
5906
|
-
content =
|
|
6301
|
+
content = fs23.readFileSync(abs, "utf-8");
|
|
5907
6302
|
} catch {
|
|
5908
6303
|
continue;
|
|
5909
6304
|
}
|
|
@@ -6138,8 +6533,8 @@ var loadMemoryContext_exports = {};
|
|
|
6138
6533
|
__export(loadMemoryContext_exports, {
|
|
6139
6534
|
loadMemoryContext: () => loadMemoryContext
|
|
6140
6535
|
});
|
|
6141
|
-
import * as
|
|
6142
|
-
import * as
|
|
6536
|
+
import * as fs24 from "fs";
|
|
6537
|
+
import * as path23 from "path";
|
|
6143
6538
|
function formatBlockFromBackend(docs) {
|
|
6144
6539
|
const pages = docs.flatMap((record2) => {
|
|
6145
6540
|
if (!record2.doc || typeof record2.doc !== "object") return [];
|
|
@@ -6162,21 +6557,21 @@ function collectPages(memoryAbs) {
|
|
|
6162
6557
|
walkMd(memoryAbs, (file) => {
|
|
6163
6558
|
let stat;
|
|
6164
6559
|
try {
|
|
6165
|
-
stat =
|
|
6560
|
+
stat = fs24.statSync(file);
|
|
6166
6561
|
} catch {
|
|
6167
6562
|
return;
|
|
6168
6563
|
}
|
|
6169
6564
|
let raw;
|
|
6170
6565
|
try {
|
|
6171
|
-
raw =
|
|
6566
|
+
raw = fs24.readFileSync(file, "utf-8");
|
|
6172
6567
|
} catch {
|
|
6173
6568
|
return;
|
|
6174
6569
|
}
|
|
6175
6570
|
const fm = raw.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
6176
|
-
const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ??
|
|
6571
|
+
const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path23.basename(file, ".md");
|
|
6177
6572
|
const updated = fm?.[1]?.match(/^updated:\s*([0-9T:.+\-Z]+)/m)?.[1]?.trim() ?? "";
|
|
6178
6573
|
out.push({
|
|
6179
|
-
relPath:
|
|
6574
|
+
relPath: path23.relative(memoryAbs, file),
|
|
6180
6575
|
title,
|
|
6181
6576
|
updated,
|
|
6182
6577
|
content: raw.length > PER_PAGE_MAX_BYTES ? raw.slice(0, PER_PAGE_MAX_BYTES) + TRUNCATED_SUFFIX2 : raw,
|
|
@@ -6244,16 +6639,16 @@ function walkMd(root, visit) {
|
|
|
6244
6639
|
const dir = stack.pop();
|
|
6245
6640
|
let names;
|
|
6246
6641
|
try {
|
|
6247
|
-
names =
|
|
6642
|
+
names = fs24.readdirSync(dir);
|
|
6248
6643
|
} catch {
|
|
6249
6644
|
continue;
|
|
6250
6645
|
}
|
|
6251
6646
|
for (const name of names) {
|
|
6252
6647
|
if (name.startsWith(".")) continue;
|
|
6253
|
-
const full =
|
|
6648
|
+
const full = path23.join(dir, name);
|
|
6254
6649
|
let stat;
|
|
6255
6650
|
try {
|
|
6256
|
-
stat =
|
|
6651
|
+
stat = fs24.statSync(full);
|
|
6257
6652
|
} catch {
|
|
6258
6653
|
continue;
|
|
6259
6654
|
}
|
|
@@ -6288,8 +6683,8 @@ var init_loadMemoryContext = __esm({
|
|
|
6288
6683
|
}
|
|
6289
6684
|
return;
|
|
6290
6685
|
}
|
|
6291
|
-
const memoryAbs =
|
|
6292
|
-
if (!
|
|
6686
|
+
const memoryAbs = path23.join(ctx.cwd, MEMORY_DIR_RELATIVE);
|
|
6687
|
+
if (!fs24.existsSync(memoryAbs)) {
|
|
6293
6688
|
ctx.data.memoryContext = "";
|
|
6294
6689
|
return;
|
|
6295
6690
|
}
|
|
@@ -6333,11 +6728,11 @@ var init_loadCoverageRules = __esm({
|
|
|
6333
6728
|
|
|
6334
6729
|
// src/container.ts
|
|
6335
6730
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
6336
|
-
import * as
|
|
6731
|
+
import * as fs25 from "fs";
|
|
6337
6732
|
function getProfileInputsForChild(profileName, _cwd) {
|
|
6338
6733
|
try {
|
|
6339
6734
|
const profilePath = resolveProfilePath(profileName);
|
|
6340
|
-
if (!
|
|
6735
|
+
if (!fs25.existsSync(profilePath)) return null;
|
|
6341
6736
|
return loadProfile(profilePath).inputs;
|
|
6342
6737
|
} catch {
|
|
6343
6738
|
return null;
|
|
@@ -6801,10 +7196,10 @@ var init_lifecycleLabels = __esm({
|
|
|
6801
7196
|
|
|
6802
7197
|
// src/litellm.ts
|
|
6803
7198
|
import { execFileSync as execFileSync4, spawn as spawn4 } from "child_process";
|
|
6804
|
-
import * as
|
|
7199
|
+
import * as fs26 from "fs";
|
|
6805
7200
|
import * as net from "net";
|
|
6806
7201
|
import * as os4 from "os";
|
|
6807
|
-
import * as
|
|
7202
|
+
import * as path24 from "path";
|
|
6808
7203
|
async function checkLitellmHealth(url) {
|
|
6809
7204
|
try {
|
|
6810
7205
|
const response = await fetch(`${url}/health`, { signal: AbortSignal.timeout(3e3) });
|
|
@@ -6874,7 +7269,7 @@ function locateLitellmScript() {
|
|
|
6874
7269
|
}
|
|
6875
7270
|
function resolveLitellmCommand() {
|
|
6876
7271
|
const imageScript = "/opt/venv/bin/litellm";
|
|
6877
|
-
if (
|
|
7272
|
+
if (fs26.existsSync(imageScript)) return imageScript;
|
|
6878
7273
|
try {
|
|
6879
7274
|
execFileSync4("which", ["litellm"], { timeout: 3e3, stdio: "pipe" });
|
|
6880
7275
|
return "litellm";
|
|
@@ -6914,13 +7309,13 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
|
|
|
6914
7309
|
const spawnProxy = () => {
|
|
6915
7310
|
const portMatch = activeUrl.match(/:(\d+)/);
|
|
6916
7311
|
const port = portMatch ? portMatch[1] : "4000";
|
|
6917
|
-
const configPath =
|
|
6918
|
-
|
|
7312
|
+
const configPath = path24.join(os4.tmpdir(), `kody-local-litellm-${Date.now()}.yaml`);
|
|
7313
|
+
fs26.writeFileSync(configPath, generateLitellmConfigYaml(model));
|
|
6919
7314
|
const args = ["--config", configPath, "--port", port];
|
|
6920
|
-
const nextLogPath =
|
|
6921
|
-
const outFd =
|
|
7315
|
+
const nextLogPath = path24.join(os4.tmpdir(), `kody-local-litellm-${Date.now()}.log`);
|
|
7316
|
+
const outFd = fs26.openSync(nextLogPath, "w");
|
|
6922
7317
|
child = spawn4(cmd, args, { stdio: ["ignore", outFd, outFd], detached: true, env: childEnv });
|
|
6923
|
-
|
|
7318
|
+
fs26.closeSync(outFd);
|
|
6924
7319
|
logPath = nextLogPath;
|
|
6925
7320
|
};
|
|
6926
7321
|
const waitForHealth = async () => {
|
|
@@ -6934,7 +7329,7 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
|
|
|
6934
7329
|
const readLogTail = () => {
|
|
6935
7330
|
if (!logPath) return "";
|
|
6936
7331
|
try {
|
|
6937
|
-
return
|
|
7332
|
+
return fs26.readFileSync(logPath, "utf-8").slice(-2e3);
|
|
6938
7333
|
} catch {
|
|
6939
7334
|
return "";
|
|
6940
7335
|
}
|
|
@@ -7017,10 +7412,10 @@ function canListen(port, host) {
|
|
|
7017
7412
|
});
|
|
7018
7413
|
}
|
|
7019
7414
|
function readDotenvApiKeys(projectDir) {
|
|
7020
|
-
const dotenvPath =
|
|
7021
|
-
if (!
|
|
7415
|
+
const dotenvPath = path24.join(projectDir, ".env");
|
|
7416
|
+
if (!fs26.existsSync(dotenvPath)) return {};
|
|
7022
7417
|
const result = {};
|
|
7023
|
-
for (const rawLine of
|
|
7418
|
+
for (const rawLine of fs26.readFileSync(dotenvPath, "utf-8").split("\n")) {
|
|
7024
7419
|
const line = rawLine.trim();
|
|
7025
7420
|
if (!line || line.startsWith("#")) continue;
|
|
7026
7421
|
const match = line.match(/^([A-Z_][A-Z0-9_]*_API_KEY)=(.*)$/);
|
|
@@ -7672,8 +8067,8 @@ var init_pushWithRetry = __esm({
|
|
|
7672
8067
|
|
|
7673
8068
|
// src/commit.ts
|
|
7674
8069
|
import { execFileSync as execFileSync6 } from "child_process";
|
|
7675
|
-
import * as
|
|
7676
|
-
import * as
|
|
8070
|
+
import * as fs27 from "fs";
|
|
8071
|
+
import * as path25 from "path";
|
|
7677
8072
|
function isGitHubYamlPath(filePath) {
|
|
7678
8073
|
const normalized = filePath.replace(/^\.\/+/, "");
|
|
7679
8074
|
return normalized.startsWith(".github/") && /\.ya?ml$/i.test(normalized);
|
|
@@ -7715,18 +8110,18 @@ function ensureGitIdentity(cwd) {
|
|
|
7715
8110
|
}
|
|
7716
8111
|
function abortUnfinishedGitOps(cwd) {
|
|
7717
8112
|
const aborted = [];
|
|
7718
|
-
const gitDir =
|
|
7719
|
-
if (!
|
|
7720
|
-
if (
|
|
8113
|
+
const gitDir = path25.join(cwd ?? process.cwd(), ".git");
|
|
8114
|
+
if (!fs27.existsSync(gitDir)) return aborted;
|
|
8115
|
+
if (fs27.existsSync(path25.join(gitDir, "MERGE_HEAD"))) {
|
|
7721
8116
|
if (tryGit(["merge", "--abort"], cwd)) aborted.push("merge");
|
|
7722
8117
|
}
|
|
7723
|
-
if (
|
|
8118
|
+
if (fs27.existsSync(path25.join(gitDir, "CHERRY_PICK_HEAD"))) {
|
|
7724
8119
|
if (tryGit(["cherry-pick", "--abort"], cwd)) aborted.push("cherry-pick");
|
|
7725
8120
|
}
|
|
7726
|
-
if (
|
|
8121
|
+
if (fs27.existsSync(path25.join(gitDir, "REVERT_HEAD"))) {
|
|
7727
8122
|
if (tryGit(["revert", "--abort"], cwd)) aborted.push("revert");
|
|
7728
8123
|
}
|
|
7729
|
-
if (
|
|
8124
|
+
if (fs27.existsSync(path25.join(gitDir, "rebase-merge")) || fs27.existsSync(path25.join(gitDir, "rebase-apply"))) {
|
|
7730
8125
|
if (tryGit(["rebase", "--abort"], cwd)) aborted.push("rebase");
|
|
7731
8126
|
}
|
|
7732
8127
|
try {
|
|
@@ -7783,7 +8178,7 @@ function normalizeCommitMessage(raw) {
|
|
|
7783
8178
|
function commitAndPush(branch, agentMessage, cwd) {
|
|
7784
8179
|
const allChanged = listChangedFiles(cwd);
|
|
7785
8180
|
const allowedFiles = allChanged.filter((f) => !isForbiddenPath(f));
|
|
7786
|
-
const mergeHeadExists =
|
|
8181
|
+
const mergeHeadExists = fs27.existsSync(path25.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
|
|
7787
8182
|
if (allowedFiles.length === 0 && !mergeHeadExists) {
|
|
7788
8183
|
return { committed: false, pushed: false, sha: "", message: "" };
|
|
7789
8184
|
}
|
|
@@ -8273,7 +8668,7 @@ function asStringArray(value) {
|
|
|
8273
8668
|
if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) return null;
|
|
8274
8669
|
return [...value];
|
|
8275
8670
|
}
|
|
8276
|
-
function
|
|
8671
|
+
function asRecord2(value) {
|
|
8277
8672
|
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
8278
8673
|
return { ...value };
|
|
8279
8674
|
}
|
|
@@ -8286,7 +8681,7 @@ function asRoute(value) {
|
|
|
8286
8681
|
if (typeof raw.evidence !== "string" || typeof raw.stage !== "string" || typeof raw.capability !== "string") {
|
|
8287
8682
|
return null;
|
|
8288
8683
|
}
|
|
8289
|
-
const args = raw.args === void 0 ? void 0 :
|
|
8684
|
+
const args = raw.args === void 0 ? void 0 : asRecord2(raw.args);
|
|
8290
8685
|
if (raw.args !== void 0 && !args) return null;
|
|
8291
8686
|
route.push({
|
|
8292
8687
|
evidence: raw.evidence,
|
|
@@ -8305,7 +8700,7 @@ function asRoutePolicy(value) {
|
|
|
8305
8700
|
if (typeof value === "string") {
|
|
8306
8701
|
return isRoutePolicyAction(value) ? { action: value } : void 0;
|
|
8307
8702
|
}
|
|
8308
|
-
const raw =
|
|
8703
|
+
const raw = asRecord2(value);
|
|
8309
8704
|
if (!raw || !isRoutePolicyAction(raw.action)) return void 0;
|
|
8310
8705
|
const maxAttempts = typeof raw.maxAttempts === "number" && Number.isInteger(raw.maxAttempts) && raw.maxAttempts > 0 ? raw.maxAttempts : void 0;
|
|
8311
8706
|
const retryAfterSeconds = typeof raw.retryAfterSeconds === "number" && raw.retryAfterSeconds >= 0 ? Math.floor(raw.retryAfterSeconds) : void 0;
|
|
@@ -8319,24 +8714,24 @@ function isRoutePolicyAction(value) {
|
|
|
8319
8714
|
return value === "wait" || value === "retry" || value === "block" || value === "issue";
|
|
8320
8715
|
}
|
|
8321
8716
|
function asPreferredRunTime(value) {
|
|
8322
|
-
const raw =
|
|
8717
|
+
const raw = asRecord2(value);
|
|
8323
8718
|
if (!raw) return void 0;
|
|
8324
8719
|
if (typeof raw.time !== "string" || !/^([01]\d|2[0-3]):[0-5]\d$/.test(raw.time)) return void 0;
|
|
8325
8720
|
if (typeof raw.timezone !== "string" || raw.timezone.trim().length === 0) return void 0;
|
|
8326
8721
|
return { time: raw.time, timezone: raw.timezone };
|
|
8327
8722
|
}
|
|
8328
8723
|
function asLoopTarget(value) {
|
|
8329
|
-
const raw =
|
|
8724
|
+
const raw = asRecord2(value);
|
|
8330
8725
|
if (!raw) return void 0;
|
|
8331
8726
|
if (raw.type !== "goal" && raw.type !== "capability" && raw.type !== "workflow") return void 0;
|
|
8332
8727
|
if (typeof raw.id !== "string" || raw.id.trim().length === 0) return void 0;
|
|
8333
8728
|
return { type: raw.type, id: raw.id };
|
|
8334
8729
|
}
|
|
8335
8730
|
function asWorkflowRef(value) {
|
|
8336
|
-
const raw =
|
|
8731
|
+
const raw = asRecord2(value);
|
|
8337
8732
|
if (!raw) return void 0;
|
|
8338
8733
|
if (typeof raw.id !== "string" || raw.id.trim().length === 0) return void 0;
|
|
8339
|
-
const args = raw.args === void 0 ? void 0 :
|
|
8734
|
+
const args = raw.args === void 0 ? void 0 : asRecord2(raw.args);
|
|
8340
8735
|
if (raw.args !== void 0 && !args) return void 0;
|
|
8341
8736
|
return {
|
|
8342
8737
|
id: raw.id.trim(),
|
|
@@ -8347,11 +8742,11 @@ function asWorkflowRef(value) {
|
|
|
8347
8742
|
}
|
|
8348
8743
|
function managedGoalFromState(state) {
|
|
8349
8744
|
const extra = state.extra;
|
|
8350
|
-
const destination =
|
|
8745
|
+
const destination = asRecord2(extra.destination);
|
|
8351
8746
|
const evidence = asStringArray(destination?.evidence);
|
|
8352
8747
|
const capabilities = asStringArray(extra.capabilities);
|
|
8353
8748
|
const route = asRoute(extra.route);
|
|
8354
|
-
const facts =
|
|
8749
|
+
const facts = asRecord2(extra.facts);
|
|
8355
8750
|
const blockers = asStringArray(extra.blockers);
|
|
8356
8751
|
if (typeof extra.type !== "string" || !destination || typeof destination.outcome !== "string" || !evidence || !capabilities || !route || !facts || !blockers) {
|
|
8357
8752
|
return null;
|
|
@@ -8423,7 +8818,7 @@ var init_state2 = __esm({
|
|
|
8423
8818
|
});
|
|
8424
8819
|
|
|
8425
8820
|
// src/goal/runLog.ts
|
|
8426
|
-
import * as
|
|
8821
|
+
import * as fs28 from "fs";
|
|
8427
8822
|
function stageGoalRunLogEvent(data, goalId, event, at = nowIso()) {
|
|
8428
8823
|
const logs = goalRunLogs(data);
|
|
8429
8824
|
const existing = logs[goalId];
|
|
@@ -8765,8 +9160,8 @@ function readGithubEvent() {
|
|
|
8765
9160
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
8766
9161
|
if (!eventPath) return null;
|
|
8767
9162
|
try {
|
|
8768
|
-
if (!
|
|
8769
|
-
const parsed = JSON.parse(
|
|
9163
|
+
if (!fs28.existsSync(eventPath)) return null;
|
|
9164
|
+
const parsed = JSON.parse(fs28.readFileSync(eventPath, "utf-8"));
|
|
8770
9165
|
return recordValue3(parsed);
|
|
8771
9166
|
} catch {
|
|
8772
9167
|
return null;
|
|
@@ -8796,782 +9191,387 @@ function recordValue3(value) {
|
|
|
8796
9191
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
8797
9192
|
}
|
|
8798
9193
|
function stringArrayValue(value) {
|
|
8799
|
-
return Array.isArray(value) && value.every((item) => typeof item === "string") ? [...value] : null;
|
|
8800
|
-
}
|
|
8801
|
-
function numberValue(value) {
|
|
8802
|
-
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
8803
|
-
}
|
|
8804
|
-
function pickRecord(input, keys) {
|
|
8805
|
-
const out = {};
|
|
8806
|
-
for (const key of keys) {
|
|
8807
|
-
if (input[key] !== void 0 && input[key] !== "") out[key] = input[key];
|
|
8808
|
-
}
|
|
8809
|
-
return out;
|
|
8810
|
-
}
|
|
8811
|
-
function pruneUndefined2(input) {
|
|
8812
|
-
for (const key of Object.keys(input)) {
|
|
8813
|
-
if (input[key] === void 0) delete input[key];
|
|
8814
|
-
}
|
|
8815
|
-
return input;
|
|
8816
|
-
}
|
|
8817
|
-
function truncateString(value, max) {
|
|
8818
|
-
if (!value) return void 0;
|
|
8819
|
-
return value.length > max ? `${value.slice(0, max)}...` : value;
|
|
8820
|
-
}
|
|
8821
|
-
function stringValue2(value) {
|
|
8822
|
-
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
8823
|
-
}
|
|
8824
|
-
function safePathSegment(value) {
|
|
8825
|
-
const safe = value.trim().replace(/[^A-Za-z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
8826
|
-
return safe || "unknown";
|
|
8827
|
-
}
|
|
8828
|
-
var LOGS_KEY, LOG_RUN_KEY, LOG_STARTED_KEY;
|
|
8829
|
-
var init_runLog = __esm({
|
|
8830
|
-
"src/goal/runLog.ts"() {
|
|
8831
|
-
"use strict";
|
|
8832
|
-
init_runIndex();
|
|
8833
|
-
init_state_backend();
|
|
8834
|
-
init_state2();
|
|
8835
|
-
LOGS_KEY = "__goalRunLogs";
|
|
8836
|
-
LOG_RUN_KEY = "__goalRunLogRunId";
|
|
8837
|
-
LOG_STARTED_KEY = "__goalRunLogStartedAt";
|
|
8838
|
-
}
|
|
8839
|
-
});
|
|
8840
|
-
|
|
8841
|
-
// src/goal/stateStore.ts
|
|
8842
|
-
function backendTenant(config) {
|
|
8843
|
-
const owner = config.github?.owner?.trim() || process.env.GITHUB_REPOSITORY?.split("/")[0]?.trim();
|
|
8844
|
-
const repo = config.github?.repo?.trim() || process.env.GITHUB_REPOSITORY?.split("/")[1]?.trim();
|
|
8845
|
-
return owner && repo ? `${owner}/${repo}` : null;
|
|
8846
|
-
}
|
|
8847
|
-
function decodeGoal(doc) {
|
|
8848
|
-
if (!doc?.state || typeof doc.state !== "object" || Array.isArray(doc.state)) return null;
|
|
8849
|
-
const state = doc.state;
|
|
8850
|
-
if (typeof state.state !== "string" || !state.extra || typeof state.extra !== "object") return null;
|
|
8851
|
-
return state;
|
|
8852
|
-
}
|
|
8853
|
-
async function fetchGoalStateAsync(config, goalId, _cwd) {
|
|
8854
|
-
const tenantId2 = backendTenant(config);
|
|
8855
|
-
if (!tenantId2) throw new Error("Repository identity is required for goal state");
|
|
8856
|
-
return decodeGoal(await createStateBackendFromEnv().getGoal(tenantId2, goalId));
|
|
8857
|
-
}
|
|
8858
|
-
async function putGoalStateAsync(config, goalId, state, _message = `chore(goals): update ${goalId}`, _cwd) {
|
|
8859
|
-
const tenantId2 = backendTenant(config);
|
|
8860
|
-
if (!tenantId2) throw new Error("Repository identity is required for goal state");
|
|
8861
|
-
const backend = createStateBackendFromEnv();
|
|
8862
|
-
const previous = await backend.getGoal(tenantId2, goalId);
|
|
8863
|
-
await backend.saveGoal(tenantId2, goalId, state, state.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString(), previous?.updatedAt);
|
|
8864
|
-
}
|
|
8865
|
-
async function listGoalStateIdsAsync(config, _cwd) {
|
|
8866
|
-
const tenantId2 = backendTenant(config);
|
|
8867
|
-
if (!tenantId2) throw new Error("Repository identity is required for goal state");
|
|
8868
|
-
const docs = await createStateBackendFromEnv().listGoals(tenantId2);
|
|
8869
|
-
return docs.map((doc) => doc.goalId).filter(Boolean).sort();
|
|
8870
|
-
}
|
|
8871
|
-
var init_stateStore = __esm({
|
|
8872
|
-
"src/goal/stateStore.ts"() {
|
|
8873
|
-
"use strict";
|
|
8874
|
-
init_state_backend();
|
|
8875
|
-
}
|
|
8876
|
-
});
|
|
8877
|
-
|
|
8878
|
-
// src/goal/targetLoopResolution.ts
|
|
8879
|
-
import * as fs28 from "fs";
|
|
8880
|
-
import * as path25 from "path";
|
|
8881
|
-
async function resolveActiveGoalLoopTarget(config, cwd, loopGoalId, loopGoal) {
|
|
8882
|
-
const targetId = loopGoal.loopTarget?.id.trim() ?? "";
|
|
8883
|
-
assertSafeGoalId(targetId, "loop target");
|
|
8884
|
-
const activeInstance = await findActiveTargetInstance(config, cwd, loopGoalId, targetId);
|
|
8885
|
-
if (activeInstance) {
|
|
8886
|
-
return {
|
|
8887
|
-
targetId: activeInstance.id,
|
|
8888
|
-
templateId: targetId,
|
|
8889
|
-
reason: "active target instance"
|
|
8890
|
-
};
|
|
8891
|
-
}
|
|
8892
|
-
const directTarget = await fetchGoalStateAsync(config, targetId, cwd);
|
|
8893
|
-
if (directTarget?.state === "active") {
|
|
8894
|
-
return { targetId, templateId: targetId, reason: "active target goal" };
|
|
8895
|
-
}
|
|
8896
|
-
return null;
|
|
8897
|
-
}
|
|
8898
|
-
async function resolveGoalLoopTarget(config, cwd, loopGoalId, loopGoal, now) {
|
|
8899
|
-
const targetId = loopGoal.loopTarget?.id.trim() ?? "";
|
|
8900
|
-
assertSafeGoalId(targetId, "loop target");
|
|
8901
|
-
const activeTarget = await resolveActiveGoalLoopTarget(config, cwd, loopGoalId, loopGoal);
|
|
8902
|
-
if (activeTarget) return activeTarget;
|
|
8903
|
-
const directTarget = await fetchGoalStateAsync(config, targetId, cwd);
|
|
8904
|
-
const template = loadGoalTemplate(cwd, targetId);
|
|
8905
|
-
if (!template) {
|
|
8906
|
-
if (directTarget) {
|
|
8907
|
-
throw new Error(`goal target ${targetId} is ${directTarget.state}; no active instance or template found`);
|
|
8908
|
-
}
|
|
8909
|
-
return { targetId, templateId: targetId, reason: "literal target; no target state or template found" };
|
|
8910
|
-
}
|
|
8911
|
-
const instanceId = await chooseTargetInstanceId(config, cwd, targetId, loopGoal.preferredRunTime?.timezone, now);
|
|
8912
|
-
const instance = buildGoalTargetInstance(template, targetId, now);
|
|
8913
|
-
await putGoalStateAsync(config, instanceId, instance, `chore(goals): create ${instanceId}`, cwd);
|
|
8914
|
-
return {
|
|
8915
|
-
targetId: instanceId,
|
|
8916
|
-
templateId: targetId,
|
|
8917
|
-
reason: "created target instance from template",
|
|
8918
|
-
created: true
|
|
8919
|
-
};
|
|
8920
|
-
}
|
|
8921
|
-
function goalLoopNow() {
|
|
8922
|
-
const value = process.env.KODY_GOAL_LOOP_NOW?.trim();
|
|
8923
|
-
if (value) {
|
|
8924
|
-
const date = new Date(value);
|
|
8925
|
-
if (!Number.isNaN(date.getTime())) return date;
|
|
8926
|
-
}
|
|
8927
|
-
return /* @__PURE__ */ new Date();
|
|
8928
|
-
}
|
|
8929
|
-
async function findActiveTargetInstance(config, cwd, loopGoalId, targetId) {
|
|
8930
|
-
const candidates = [];
|
|
8931
|
-
for (const entryId of await listGoalStateIdsAsync(config, cwd)) {
|
|
8932
|
-
const id = entryId.trim();
|
|
8933
|
-
if (!id || id === loopGoalId || id === targetId || !id.startsWith(`${targetId}-`)) continue;
|
|
8934
|
-
assertSafeGoalId(id, "goal instance");
|
|
8935
|
-
const state = await fetchGoalStateAsync(config, id, cwd);
|
|
8936
|
-
if (!state || state.state !== "active") continue;
|
|
8937
|
-
if (!isTargetInstanceState(id, state, targetId)) continue;
|
|
8938
|
-
candidates.push({ id, state });
|
|
8939
|
-
}
|
|
8940
|
-
candidates.sort(compareGoalInstanceAge);
|
|
8941
|
-
return candidates[0] ?? null;
|
|
8942
|
-
}
|
|
8943
|
-
function isTargetInstanceState(id, state, targetId) {
|
|
8944
|
-
if (id.startsWith(`${targetId}-`)) return true;
|
|
8945
|
-
return ["template", "sourceTemplate", "templateId", "type"].some((key) => state.extra[key] === targetId);
|
|
8946
|
-
}
|
|
8947
|
-
function compareGoalInstanceAge(a, b) {
|
|
8948
|
-
const byTime = goalInstanceTime(a.state) - goalInstanceTime(b.state);
|
|
8949
|
-
return byTime === 0 ? a.id.localeCompare(b.id) : byTime;
|
|
8950
|
-
}
|
|
8951
|
-
function goalInstanceTime(state) {
|
|
8952
|
-
const value = state.createdAt ?? state.startedAt ?? state.updatedAt;
|
|
8953
|
-
if (!value) return 0;
|
|
8954
|
-
const parsed = Date.parse(value);
|
|
8955
|
-
return Number.isNaN(parsed) ? 0 : parsed;
|
|
8956
|
-
}
|
|
8957
|
-
function loadGoalTemplate(cwd, targetId) {
|
|
8958
|
-
return readJsonObject2(path25.join(cwd, ".kody-engine", "definitions", "goals", targetId, "state.json"));
|
|
8959
|
-
}
|
|
8960
|
-
function readJsonObject2(filePath) {
|
|
8961
|
-
if (!fs28.existsSync(filePath)) return null;
|
|
8962
|
-
const parsed = JSON.parse(fs28.readFileSync(filePath, "utf8"));
|
|
8963
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
8964
|
-
throw new Error(`goal template ${filePath} must be a JSON object`);
|
|
8965
|
-
}
|
|
8966
|
-
return parsed;
|
|
8967
|
-
}
|
|
8968
|
-
async function chooseTargetInstanceId(config, cwd, targetId, timezone, now) {
|
|
8969
|
-
const base = `${targetId}-${zonedDate(now, timezone ?? "UTC")}`;
|
|
8970
|
-
for (let index = 1; index <= 20; index += 1) {
|
|
8971
|
-
const id = index === 1 ? base : `${base}-${index}`;
|
|
8972
|
-
assertSafeGoalId(id, "goal instance");
|
|
8973
|
-
const existing = await fetchGoalStateAsync(config, id, cwd);
|
|
8974
|
-
if (!existing || existing.state === "active") return id;
|
|
8975
|
-
}
|
|
8976
|
-
throw new Error(`could not allocate goal target instance id for ${targetId}`);
|
|
8977
|
-
}
|
|
8978
|
-
function buildGoalTargetInstance(template, targetId, now) {
|
|
8979
|
-
const extra = { ...template };
|
|
8980
|
-
for (const key of ["state", "createdAt", "updatedAt", "startedAt"]) {
|
|
8981
|
-
delete extra[key];
|
|
8982
|
-
}
|
|
8983
|
-
extra.kind = "instance";
|
|
8984
|
-
extra.template = targetId;
|
|
8985
|
-
extra.sourceTemplate = targetId;
|
|
8986
|
-
extra.templateId = targetId;
|
|
8987
|
-
if (!isPlainObject3(extra.facts)) extra.facts = {};
|
|
8988
|
-
if (!Array.isArray(extra.blockers)) extra.blockers = [];
|
|
8989
|
-
const at = isoNoMs(now);
|
|
8990
|
-
return {
|
|
8991
|
-
state: "active",
|
|
8992
|
-
createdAt: at,
|
|
8993
|
-
updatedAt: at,
|
|
8994
|
-
extra
|
|
8995
|
-
};
|
|
8996
|
-
}
|
|
8997
|
-
function isPlainObject3(value) {
|
|
8998
|
-
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
8999
|
-
}
|
|
9000
|
-
function assertSafeGoalId(value, label) {
|
|
9001
|
-
if (!/^[A-Za-z0-9_.-]+$/.test(value)) {
|
|
9002
|
-
throw new Error(`${label} id must contain only letters, numbers, dot, underscore, or dash: ${value}`);
|
|
9003
|
-
}
|
|
9004
|
-
}
|
|
9005
|
-
function zonedDate(date, timezone) {
|
|
9006
|
-
try {
|
|
9007
|
-
const parts = new Intl.DateTimeFormat("en-CA", {
|
|
9008
|
-
timeZone: timezone,
|
|
9009
|
-
year: "numeric",
|
|
9010
|
-
month: "2-digit",
|
|
9011
|
-
day: "2-digit"
|
|
9012
|
-
}).formatToParts(date);
|
|
9013
|
-
const get = (type) => parts.find((part) => part.type === type)?.value;
|
|
9014
|
-
const year = get("year");
|
|
9015
|
-
const month = get("month");
|
|
9016
|
-
const day = get("day");
|
|
9017
|
-
if (year && month && day) return `${year}-${month}-${day}`;
|
|
9018
|
-
} catch {
|
|
9019
|
-
}
|
|
9020
|
-
return date.toISOString().slice(0, 10);
|
|
9021
|
-
}
|
|
9022
|
-
function isoNoMs(date) {
|
|
9023
|
-
return date.toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
9024
|
-
}
|
|
9025
|
-
var init_targetLoopResolution = __esm({
|
|
9026
|
-
"src/goal/targetLoopResolution.ts"() {
|
|
9027
|
-
"use strict";
|
|
9028
|
-
init_stateStore();
|
|
9029
|
-
}
|
|
9030
|
-
});
|
|
9031
|
-
|
|
9032
|
-
// src/goal/typeDefinitions.ts
|
|
9033
|
-
function cloneRoute(route) {
|
|
9034
|
-
return route.map((step) => ({
|
|
9035
|
-
stage: step.stage,
|
|
9036
|
-
evidence: step.evidence,
|
|
9037
|
-
capability: step.capability,
|
|
9038
|
-
...step.implementation ? { implementation: step.implementation } : {},
|
|
9039
|
-
...step.args ? { args: structuredClone(step.args) } : {}
|
|
9040
|
-
}));
|
|
9041
|
-
}
|
|
9042
|
-
function stringArray(value) {
|
|
9043
|
-
return Array.isArray(value) && value.every((item) => typeof item === "string") ? value : null;
|
|
9044
|
-
}
|
|
9045
|
-
function routeArray(value) {
|
|
9046
|
-
if (!Array.isArray(value)) return null;
|
|
9047
|
-
const route = [];
|
|
9048
|
-
for (const item of value) {
|
|
9049
|
-
if (!item || typeof item !== "object" || Array.isArray(item)) return null;
|
|
9050
|
-
const raw = item;
|
|
9051
|
-
if (typeof raw.stage !== "string" || typeof raw.evidence !== "string" || typeof raw.capability !== "string")
|
|
9052
|
-
return null;
|
|
9053
|
-
route.push({
|
|
9054
|
-
stage: raw.stage,
|
|
9055
|
-
evidence: raw.evidence,
|
|
9056
|
-
capability: raw.capability,
|
|
9057
|
-
implementation: typeof raw.implementation === "string" ? raw.implementation : void 0,
|
|
9058
|
-
args: raw.args && typeof raw.args === "object" && !Array.isArray(raw.args) ? { ...raw.args } : void 0
|
|
9059
|
-
});
|
|
9060
|
-
}
|
|
9061
|
-
return route;
|
|
9062
|
-
}
|
|
9063
|
-
function managedGoalTypeDefinition(type) {
|
|
9064
|
-
return Object.hasOwn(GOAL_TYPE_DEFINITIONS, type) ? GOAL_TYPE_DEFINITIONS[type] : null;
|
|
9065
|
-
}
|
|
9066
|
-
function expandManagedGoalState(state) {
|
|
9067
|
-
const type = typeof state.extra.type === "string" ? state.extra.type : "";
|
|
9068
|
-
const definition = managedGoalTypeDefinition(type);
|
|
9069
|
-
if (!definition) return state;
|
|
9070
|
-
const destination = state.extra.destination && typeof state.extra.destination === "object" && !Array.isArray(state.extra.destination) ? { ...state.extra.destination } : {};
|
|
9071
|
-
const outcome = typeof destination.outcome === "string" ? destination.outcome : "";
|
|
9072
|
-
const evidence = stringArray(destination.evidence);
|
|
9073
|
-
const capabilities = stringArray(state.extra.capabilities);
|
|
9074
|
-
const route = routeArray(state.extra.route);
|
|
9075
|
-
const facts = state.extra.facts && typeof state.extra.facts === "object" && !Array.isArray(state.extra.facts) ? { ...state.extra.facts } : {};
|
|
9076
|
-
const blockers = stringArray(state.extra.blockers);
|
|
9077
|
-
return {
|
|
9078
|
-
...state,
|
|
9079
|
-
extra: {
|
|
9080
|
-
...state.extra,
|
|
9081
|
-
type: definition.type,
|
|
9082
|
-
destination: {
|
|
9083
|
-
...destination,
|
|
9084
|
-
outcome,
|
|
9085
|
-
evidence: evidence && evidence.length > 0 ? evidence : [...definition.evidence]
|
|
9086
|
-
},
|
|
9087
|
-
capabilities: capabilities && capabilities.length > 0 ? capabilities : [...definition.capabilities],
|
|
9088
|
-
route: route && route.length > 0 ? route : cloneRoute(definition.route),
|
|
9089
|
-
facts,
|
|
9090
|
-
blockers: blockers ?? []
|
|
9091
|
-
}
|
|
9092
|
-
};
|
|
9093
|
-
}
|
|
9094
|
-
var GOAL_TYPE_DEFINITIONS;
|
|
9095
|
-
var init_typeDefinitions = __esm({
|
|
9096
|
-
"src/goal/typeDefinitions.ts"() {
|
|
9097
|
-
"use strict";
|
|
9098
|
-
GOAL_TYPE_DEFINITIONS = {
|
|
9099
|
-
improve: {
|
|
9100
|
-
type: "improve",
|
|
9101
|
-
evidence: ["planReady", "changeImplemented", "changeVerified"],
|
|
9102
|
-
capabilities: ["plan", "fix", "review"],
|
|
9103
|
-
route: [
|
|
9104
|
-
{ stage: "plan", evidence: "planReady", capability: "plan", implementation: "plan" },
|
|
9105
|
-
{
|
|
9106
|
-
stage: "implement",
|
|
9107
|
-
evidence: "changeImplemented",
|
|
9108
|
-
capability: "fix",
|
|
9109
|
-
implementation: "fix"
|
|
9110
|
-
},
|
|
9111
|
-
{
|
|
9112
|
-
stage: "review",
|
|
9113
|
-
evidence: "changeVerified",
|
|
9114
|
-
capability: "review",
|
|
9115
|
-
implementation: "review"
|
|
9116
|
-
}
|
|
9117
|
-
]
|
|
9118
|
-
},
|
|
9119
|
-
maintain: {
|
|
9120
|
-
type: "maintain",
|
|
9121
|
-
evidence: [],
|
|
9122
|
-
capabilities: [
|
|
9123
|
-
"cleanup",
|
|
9124
|
-
"code-health",
|
|
9125
|
-
"docs-health",
|
|
9126
|
-
"documentation-maintenance",
|
|
9127
|
-
"memory-compaction",
|
|
9128
|
-
"repo-graph",
|
|
9129
|
-
"skills-research"
|
|
9130
|
-
],
|
|
9131
|
-
route: []
|
|
9132
|
-
},
|
|
9133
|
-
monitor: {
|
|
9134
|
-
type: "monitor",
|
|
9135
|
-
evidence: [],
|
|
9136
|
-
capabilities: ["health-check", "pr-health-triage", "qa-sweep"],
|
|
9137
|
-
route: []
|
|
9138
|
-
},
|
|
9139
|
-
release: {
|
|
9140
|
-
type: "release",
|
|
9141
|
-
evidence: ["releasePrExists", "mainMerged", "productionDeployed"],
|
|
9142
|
-
capabilities: ["release", "release-merge", "vercel-production-deploy"],
|
|
9143
|
-
route: [
|
|
9144
|
-
{
|
|
9145
|
-
stage: "release",
|
|
9146
|
-
evidence: "releasePrExists",
|
|
9147
|
-
capability: "release",
|
|
9148
|
-
implementation: "release-prepare",
|
|
9149
|
-
args: { issue: { fact: "issue" }, goal: { fact: "goalId" } }
|
|
9150
|
-
},
|
|
9151
|
-
{
|
|
9152
|
-
stage: "merge",
|
|
9153
|
-
evidence: "mainMerged",
|
|
9154
|
-
capability: "release-merge",
|
|
9155
|
-
implementation: "release-merge",
|
|
9156
|
-
args: { pr: { fact: "releasePr" }, issue: { fact: "issue" }, goal: { fact: "goalId" } }
|
|
9157
|
-
},
|
|
9158
|
-
{
|
|
9159
|
-
stage: "publish",
|
|
9160
|
-
evidence: "productionDeployed",
|
|
9161
|
-
capability: "vercel-production-deploy",
|
|
9162
|
-
implementation: "vercel-production-deploy"
|
|
9163
|
-
}
|
|
9164
|
-
]
|
|
9165
|
-
},
|
|
9166
|
-
checklist: {
|
|
9167
|
-
type: "checklist",
|
|
9168
|
-
evidence: ["checklistComplete"],
|
|
9169
|
-
capabilities: ["task-verifier"],
|
|
9170
|
-
route: [
|
|
9171
|
-
{
|
|
9172
|
-
stage: "verify",
|
|
9173
|
-
evidence: "checklistComplete",
|
|
9174
|
-
capability: "task-verifier",
|
|
9175
|
-
implementation: "task-verifier"
|
|
9176
|
-
}
|
|
9177
|
-
]
|
|
9178
|
-
}
|
|
9179
|
-
};
|
|
9180
|
-
}
|
|
9181
|
-
});
|
|
9182
|
-
|
|
9183
|
-
// src/workflowValidation.ts
|
|
9184
|
-
function validateWorkflow(value, options = {}) {
|
|
9185
|
-
const issues = [];
|
|
9186
|
-
const workflow = asRecord2(value);
|
|
9187
|
-
const rawSteps = Array.isArray(value) ? value : Array.isArray(workflow?.steps) ? workflow.steps : [];
|
|
9188
|
-
const maxSteps = options.maxSteps ?? 100;
|
|
9189
|
-
const maxTransitions = options.maxTransitionsPerStep ?? 20;
|
|
9190
|
-
const maxLoopIterations = options.maxLoopIterations ?? 100;
|
|
9191
|
-
if (rawSteps.length === 0) {
|
|
9192
|
-
issue(issues, "steps_required", "steps", "workflow must contain at least one step");
|
|
9193
|
-
return issues;
|
|
9194
|
-
}
|
|
9195
|
-
if (rawSteps.length > maxSteps) {
|
|
9196
|
-
issue(issues, "too_many_steps", "steps", `workflow has ${rawSteps.length} steps; maximum is ${maxSteps}`);
|
|
9197
|
-
}
|
|
9198
|
-
const graphMode = workflow?.startAt !== void 0 || rawSteps.some((entry) => {
|
|
9199
|
-
const step = asRecord2(entry);
|
|
9200
|
-
return Boolean(step && (step.id !== void 0 || step.next !== void 0));
|
|
9201
|
-
});
|
|
9202
|
-
const steps = rawSteps.map(
|
|
9203
|
-
(entry) => typeof entry === "string" ? { capability: entry } : asRecord2(entry)
|
|
9204
|
-
);
|
|
9205
|
-
const ids = [];
|
|
9206
|
-
steps.forEach((step, index) => {
|
|
9207
|
-
const base = `steps[${index}]`;
|
|
9208
|
-
if (!step) {
|
|
9209
|
-
issue(issues, "invalid_step", base, "workflow step must be a capability name or an object");
|
|
9210
|
-
return;
|
|
9211
|
-
}
|
|
9212
|
-
for (const field of Object.keys(step)) {
|
|
9213
|
-
if (!SUPPORTED_STEP_FIELDS.has(field)) {
|
|
9214
|
-
issue(issues, "unsupported_step_field", `${base}.${field}`, `workflow step field ${field} is not supported`);
|
|
9215
|
-
}
|
|
9216
|
-
}
|
|
9217
|
-
const capability = text(step.capability ?? step.action);
|
|
9218
|
-
if (!capability || !SAFE_NAME.test(capability)) {
|
|
9219
|
-
issue(issues, "invalid_capability", `${base}.capability`, "workflow step must name a valid capability");
|
|
9220
|
-
} else if (options.knownCapabilities && !options.knownCapabilities.has(capability)) {
|
|
9221
|
-
issue(
|
|
9222
|
-
issues,
|
|
9223
|
-
"unknown_capability",
|
|
9224
|
-
`${base}.capability`,
|
|
9225
|
-
`workflow step references unknown capability ${capability}`
|
|
9226
|
-
);
|
|
9227
|
-
}
|
|
9228
|
-
if (graphMode) {
|
|
9229
|
-
const id = text(step.id);
|
|
9230
|
-
if (!id || !SAFE_STEP_ID.test(id)) {
|
|
9231
|
-
issue(issues, "invalid_step_id", `${base}.id`, "graph workflow steps must each have a valid id");
|
|
9232
|
-
} else {
|
|
9233
|
-
ids.push(id);
|
|
9234
|
-
}
|
|
9235
|
-
}
|
|
9236
|
-
validateDataMatch(step.runWhen, `${base}.runWhen`, issues);
|
|
9237
|
-
if (step.delivery !== void 0 && step.delivery !== "pull-request") {
|
|
9238
|
-
issue(issues, "invalid_delivery", `${base}.delivery`, "workflow step delivery must be pull-request");
|
|
9239
|
-
}
|
|
9240
|
-
if (step.input !== void 0 && !isJsonValue(step.input)) {
|
|
9241
|
-
issue(issues, "invalid_input", `${base}.input`, "workflow step input must be one JSON value");
|
|
9242
|
-
}
|
|
9243
|
-
});
|
|
9244
|
-
if (!graphMode) return issues;
|
|
9245
|
-
const seen = /* @__PURE__ */ new Set();
|
|
9246
|
-
ids.forEach((id, index) => {
|
|
9247
|
-
if (seen.has(id)) issue(issues, "duplicate_step_id", `steps[${index}].id`, `workflow step id ${id} is duplicated`);
|
|
9248
|
-
seen.add(id);
|
|
9249
|
-
});
|
|
9250
|
-
const startAt = text(workflow?.startAt) ?? text(steps[0]?.id);
|
|
9251
|
-
if (!startAt || !seen.has(startAt)) {
|
|
9252
|
-
issue(issues, "missing_start_step", "startAt", `workflow startAt references missing step ${startAt ?? "<none>"}`);
|
|
9253
|
-
}
|
|
9254
|
-
const adjacency = /* @__PURE__ */ new Map();
|
|
9255
|
-
const explicitEndSources = /* @__PURE__ */ new Set();
|
|
9256
|
-
steps.forEach((step, index) => {
|
|
9257
|
-
if (!step) return;
|
|
9258
|
-
const id = text(step.id);
|
|
9259
|
-
if (!id) return;
|
|
9260
|
-
const sourceCapability = text(step.capability ?? step.action);
|
|
9261
|
-
const transitions = transitionList(step.next);
|
|
9262
|
-
adjacency.set(id, []);
|
|
9263
|
-
if (transitions.length > maxTransitions) {
|
|
9264
|
-
issue(
|
|
9265
|
-
issues,
|
|
9266
|
-
"too_many_transitions",
|
|
9267
|
-
`steps[${index}].next`,
|
|
9268
|
-
`workflow step ${id} has ${transitions.length} connections; maximum is ${maxTransitions}`
|
|
9269
|
-
);
|
|
9270
|
-
}
|
|
9271
|
-
const defaults = transitions.filter((transition) => asRecord2(transition)?.default === true);
|
|
9272
|
-
const conditionals = transitions.filter((transition) => asRecord2(transition)?.when !== void 0);
|
|
9273
|
-
const unconditional = transitions.filter((transition) => {
|
|
9274
|
-
const raw = asRecord2(transition);
|
|
9275
|
-
return typeof transition === "string" || Boolean(raw && raw.when === void 0 && raw.default !== true && raw.maxIterations === void 0);
|
|
9276
|
-
});
|
|
9277
|
-
if (defaults.length > 1) {
|
|
9278
|
-
issue(
|
|
9279
|
-
issues,
|
|
9280
|
-
"multiple_default_transitions",
|
|
9281
|
-
`steps[${index}].next`,
|
|
9282
|
-
`workflow step ${id} has more than one default connection`
|
|
9283
|
-
);
|
|
9284
|
-
}
|
|
9285
|
-
if (conditionals.length > 0 && defaults.length !== 1) {
|
|
9286
|
-
issue(
|
|
9287
|
-
issues,
|
|
9288
|
-
"missing_default_transition",
|
|
9289
|
-
`steps[${index}].next`,
|
|
9290
|
-
`workflow step ${id} has conditions and needs one default connection`
|
|
9291
|
-
);
|
|
9292
|
-
}
|
|
9293
|
-
if (unconditional.length > 1 || unconditional.length > 0 && transitions.length > 1) {
|
|
9294
|
-
issue(
|
|
9295
|
-
issues,
|
|
9296
|
-
"ambiguous_transition",
|
|
9297
|
-
`steps[${index}].next`,
|
|
9298
|
-
`workflow step ${id} mixes an unconditional connection with other connections`
|
|
9299
|
-
);
|
|
9300
|
-
}
|
|
9301
|
-
transitions.forEach((transition, transitionIndex) => {
|
|
9302
|
-
const raw = typeof transition === "string" ? { to: transition } : asRecord2(transition);
|
|
9303
|
-
const base = `steps[${index}].next[${transitionIndex}]`;
|
|
9304
|
-
if (!raw) {
|
|
9305
|
-
issue(issues, "invalid_transition", base, "workflow connection must be a step id or an object");
|
|
9306
|
-
return;
|
|
9307
|
-
}
|
|
9308
|
-
for (const field of Object.keys(raw)) {
|
|
9309
|
-
if (!SUPPORTED_TRANSITION_FIELDS.has(field)) {
|
|
9310
|
-
issue(
|
|
9311
|
-
issues,
|
|
9312
|
-
"unsupported_transition_field",
|
|
9313
|
-
`${base}.${field}`,
|
|
9314
|
-
`workflow connection field ${field} is not supported`
|
|
9315
|
-
);
|
|
9316
|
-
}
|
|
9317
|
-
}
|
|
9318
|
-
const target = text(raw.to);
|
|
9319
|
-
if (!target || target !== "$end" && !SAFE_STEP_ID.test(target)) {
|
|
9320
|
-
issue(issues, "invalid_transition_target", `${base}.to`, "workflow connection must name a valid target step");
|
|
9321
|
-
return;
|
|
9322
|
-
}
|
|
9323
|
-
if (raw.default === true && raw.when !== void 0) {
|
|
9324
|
-
issue(issues, "conflicting_transition", base, "workflow connection cannot be both conditional and default");
|
|
9325
|
-
}
|
|
9326
|
-
if (raw.when !== void 0) {
|
|
9327
|
-
const outputPaths = options.capabilityOutputs?.get(sourceCapability ?? "");
|
|
9328
|
-
validateDataMatch(raw.when, `${base}.when`, issues, outputPaths);
|
|
9329
|
-
}
|
|
9330
|
-
if (target === "$end") {
|
|
9331
|
-
explicitEndSources.add(id);
|
|
9332
|
-
return;
|
|
9333
|
-
}
|
|
9334
|
-
if (!seen.has(target)) {
|
|
9335
|
-
issue(
|
|
9336
|
-
issues,
|
|
9337
|
-
"missing_transition_target",
|
|
9338
|
-
`${base}.to`,
|
|
9339
|
-
`workflow step ${id} connects to missing step ${target}`
|
|
9340
|
-
);
|
|
9341
|
-
} else {
|
|
9342
|
-
adjacency.get(id)?.push(target);
|
|
9343
|
-
}
|
|
9344
|
-
const targetIndex = ids.indexOf(target ?? "");
|
|
9345
|
-
const iterations = raw.maxIterations;
|
|
9346
|
-
if (targetIndex >= 0 && targetIndex <= index) {
|
|
9347
|
-
if (!Number.isInteger(iterations) || Number(iterations) < 1) {
|
|
9348
|
-
issue(
|
|
9349
|
-
issues,
|
|
9350
|
-
"unbounded_loop",
|
|
9351
|
-
`${base}.maxIterations`,
|
|
9352
|
-
`workflow loop ${id}->${target} must set maxIterations`
|
|
9353
|
-
);
|
|
9354
|
-
} else if (Number(iterations) > maxLoopIterations) {
|
|
9355
|
-
issue(
|
|
9356
|
-
issues,
|
|
9357
|
-
"loop_limit_too_high",
|
|
9358
|
-
`${base}.maxIterations`,
|
|
9359
|
-
`workflow loop ${id}->${target} exceeds maximum ${maxLoopIterations}`
|
|
9360
|
-
);
|
|
9361
|
-
}
|
|
9362
|
-
} else if (iterations !== void 0 && (!Number.isInteger(iterations) || Number(iterations) < 1)) {
|
|
9363
|
-
issue(issues, "invalid_loop_limit", `${base}.maxIterations`, "maxIterations must be a positive integer");
|
|
9364
|
-
}
|
|
9365
|
-
});
|
|
9366
|
-
});
|
|
9367
|
-
if (startAt && seen.has(startAt)) {
|
|
9368
|
-
const reachable = /* @__PURE__ */ new Set();
|
|
9369
|
-
const pending = [startAt];
|
|
9370
|
-
while (pending.length > 0) {
|
|
9371
|
-
const id = pending.pop();
|
|
9372
|
-
if (reachable.has(id)) continue;
|
|
9373
|
-
reachable.add(id);
|
|
9374
|
-
pending.push(...adjacency.get(id) ?? []);
|
|
9375
|
-
}
|
|
9376
|
-
ids.forEach((id, index) => {
|
|
9377
|
-
if (!reachable.has(id)) issue(issues, "unreachable_step", `steps[${index}]`, `workflow step ${id} is unreachable`);
|
|
9378
|
-
});
|
|
9379
|
-
if (![...reachable].some((id) => (adjacency.get(id) ?? []).length === 0 || explicitEndSources.has(id))) {
|
|
9380
|
-
issue(issues, "missing_terminal_step", "steps", "workflow has no reachable final step");
|
|
9381
|
-
}
|
|
9382
|
-
}
|
|
9383
|
-
return issues;
|
|
9194
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string") ? [...value] : null;
|
|
9384
9195
|
}
|
|
9385
|
-
function
|
|
9386
|
-
return
|
|
9196
|
+
function numberValue(value) {
|
|
9197
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
9387
9198
|
}
|
|
9388
|
-
function
|
|
9389
|
-
|
|
9390
|
-
const
|
|
9391
|
-
|
|
9392
|
-
issue(issues, "invalid_condition", path53, "workflow condition must contain at least one match");
|
|
9393
|
-
return;
|
|
9199
|
+
function pickRecord(input, keys) {
|
|
9200
|
+
const out = {};
|
|
9201
|
+
for (const key of keys) {
|
|
9202
|
+
if (input[key] !== void 0 && input[key] !== "") out[key] = input[key];
|
|
9394
9203
|
}
|
|
9395
|
-
|
|
9396
|
-
|
|
9397
|
-
|
|
9398
|
-
|
|
9399
|
-
|
|
9400
|
-
`${path53}.${field}`,
|
|
9401
|
-
`workflow condition must read from facts, evidence, artifacts, result, workflow, or lastOutcome`
|
|
9402
|
-
);
|
|
9403
|
-
}
|
|
9404
|
-
if (capabilityOutputs && field.startsWith("result.") && !capabilityOutputs.has(field)) {
|
|
9405
|
-
issue(
|
|
9406
|
-
issues,
|
|
9407
|
-
"undeclared_result_path",
|
|
9408
|
-
`${path53}.${field}`,
|
|
9409
|
-
`workflow condition reads ${field}, but the source capability does not declare it`
|
|
9410
|
-
);
|
|
9411
|
-
}
|
|
9412
|
-
if (!isComparable(expected)) {
|
|
9413
|
-
issue(issues, "invalid_condition_value", `${path53}.${field}`, "workflow condition value must be a JSON scalar");
|
|
9414
|
-
}
|
|
9204
|
+
return out;
|
|
9205
|
+
}
|
|
9206
|
+
function pruneUndefined2(input) {
|
|
9207
|
+
for (const key of Object.keys(input)) {
|
|
9208
|
+
if (input[key] === void 0) delete input[key];
|
|
9415
9209
|
}
|
|
9210
|
+
return input;
|
|
9416
9211
|
}
|
|
9417
|
-
function
|
|
9418
|
-
if (value
|
|
9419
|
-
return
|
|
9212
|
+
function truncateString(value, max) {
|
|
9213
|
+
if (!value) return void 0;
|
|
9214
|
+
return value.length > max ? `${value.slice(0, max)}...` : value;
|
|
9420
9215
|
}
|
|
9421
|
-
function
|
|
9422
|
-
return
|
|
9216
|
+
function stringValue2(value) {
|
|
9217
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
9423
9218
|
}
|
|
9424
|
-
function
|
|
9425
|
-
|
|
9219
|
+
function safePathSegment(value) {
|
|
9220
|
+
const safe = value.trim().replace(/[^A-Za-z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
9221
|
+
return safe || "unknown";
|
|
9426
9222
|
}
|
|
9427
|
-
|
|
9428
|
-
|
|
9429
|
-
|
|
9223
|
+
var LOGS_KEY, LOG_RUN_KEY, LOG_STARTED_KEY;
|
|
9224
|
+
var init_runLog = __esm({
|
|
9225
|
+
"src/goal/runLog.ts"() {
|
|
9226
|
+
"use strict";
|
|
9227
|
+
init_runIndex();
|
|
9228
|
+
init_state_backend();
|
|
9229
|
+
init_state2();
|
|
9230
|
+
LOGS_KEY = "__goalRunLogs";
|
|
9231
|
+
LOG_RUN_KEY = "__goalRunLogRunId";
|
|
9232
|
+
LOG_STARTED_KEY = "__goalRunLogStartedAt";
|
|
9233
|
+
}
|
|
9234
|
+
});
|
|
9235
|
+
|
|
9236
|
+
// src/goal/stateStore.ts
|
|
9237
|
+
function backendTenant(config) {
|
|
9238
|
+
const owner = config.github?.owner?.trim() || process.env.GITHUB_REPOSITORY?.split("/")[0]?.trim();
|
|
9239
|
+
const repo = config.github?.repo?.trim() || process.env.GITHUB_REPOSITORY?.split("/")[1]?.trim();
|
|
9240
|
+
return owner && repo ? `${owner}/${repo}` : null;
|
|
9430
9241
|
}
|
|
9431
|
-
function
|
|
9432
|
-
if (
|
|
9433
|
-
|
|
9434
|
-
if (!
|
|
9435
|
-
return
|
|
9242
|
+
function decodeGoal(doc) {
|
|
9243
|
+
if (!doc?.state || typeof doc.state !== "object" || Array.isArray(doc.state)) return null;
|
|
9244
|
+
const state = doc.state;
|
|
9245
|
+
if (typeof state.state !== "string" || !state.extra || typeof state.extra !== "object") return null;
|
|
9246
|
+
return state;
|
|
9436
9247
|
}
|
|
9437
|
-
function
|
|
9438
|
-
|
|
9248
|
+
async function fetchGoalStateAsync(config, goalId, _cwd) {
|
|
9249
|
+
const tenantId2 = backendTenant(config);
|
|
9250
|
+
if (!tenantId2) throw new Error("Repository identity is required for goal state");
|
|
9251
|
+
return decodeGoal(await createStateBackendFromEnv().getGoal(tenantId2, goalId));
|
|
9439
9252
|
}
|
|
9440
|
-
|
|
9441
|
-
|
|
9442
|
-
"
|
|
9253
|
+
async function putGoalStateAsync(config, goalId, state, _message = `chore(goals): update ${goalId}`, _cwd) {
|
|
9254
|
+
const tenantId2 = backendTenant(config);
|
|
9255
|
+
if (!tenantId2) throw new Error("Repository identity is required for goal state");
|
|
9256
|
+
const backend = createStateBackendFromEnv();
|
|
9257
|
+
const previous = await backend.getGoal(tenantId2, goalId);
|
|
9258
|
+
await backend.saveGoal(tenantId2, goalId, state, state.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString(), previous?.updatedAt);
|
|
9259
|
+
}
|
|
9260
|
+
async function listGoalStateIdsAsync(config, _cwd) {
|
|
9261
|
+
const tenantId2 = backendTenant(config);
|
|
9262
|
+
if (!tenantId2) throw new Error("Repository identity is required for goal state");
|
|
9263
|
+
const docs = await createStateBackendFromEnv().listGoals(tenantId2);
|
|
9264
|
+
return docs.map((doc) => doc.goalId).filter(Boolean).sort();
|
|
9265
|
+
}
|
|
9266
|
+
var init_stateStore = __esm({
|
|
9267
|
+
"src/goal/stateStore.ts"() {
|
|
9443
9268
|
"use strict";
|
|
9444
|
-
|
|
9445
|
-
SAFE_STEP_ID = /^[A-Za-z][A-Za-z0-9_-]*$/;
|
|
9446
|
-
SAFE_DATA_PATH = /^(facts|evidence|artifacts|result|workflow|lastOutcome)(?:\.[A-Za-z_][A-Za-z0-9_-]*)+$/;
|
|
9447
|
-
SUPPORTED_STEP_FIELDS = /* @__PURE__ */ new Set([
|
|
9448
|
-
"id",
|
|
9449
|
-
"capability",
|
|
9450
|
-
"input",
|
|
9451
|
-
"action",
|
|
9452
|
-
"evidence",
|
|
9453
|
-
"target",
|
|
9454
|
-
"delivery",
|
|
9455
|
-
"targetFact",
|
|
9456
|
-
"reason",
|
|
9457
|
-
"next",
|
|
9458
|
-
"runWhen",
|
|
9459
|
-
"continueOn",
|
|
9460
|
-
"saveReport",
|
|
9461
|
-
"report"
|
|
9462
|
-
]);
|
|
9463
|
-
SUPPORTED_TRANSITION_FIELDS = /* @__PURE__ */ new Set(["to", "when", "default", "maxIterations"]);
|
|
9269
|
+
init_state_backend();
|
|
9464
9270
|
}
|
|
9465
9271
|
});
|
|
9466
9272
|
|
|
9467
|
-
// src/
|
|
9273
|
+
// src/goal/targetLoopResolution.ts
|
|
9468
9274
|
import * as fs29 from "fs";
|
|
9469
9275
|
import * as path26 from "path";
|
|
9470
|
-
function
|
|
9471
|
-
|
|
9276
|
+
async function resolveActiveGoalLoopTarget(config, cwd, loopGoalId, loopGoal) {
|
|
9277
|
+
const targetId = loopGoal.loopTarget?.id.trim() ?? "";
|
|
9278
|
+
assertSafeGoalId(targetId, "loop target");
|
|
9279
|
+
const activeInstance = await findActiveTargetInstance(config, cwd, loopGoalId, targetId);
|
|
9280
|
+
if (activeInstance) {
|
|
9281
|
+
return {
|
|
9282
|
+
targetId: activeInstance.id,
|
|
9283
|
+
templateId: targetId,
|
|
9284
|
+
reason: "active target instance"
|
|
9285
|
+
};
|
|
9286
|
+
}
|
|
9287
|
+
const directTarget = await fetchGoalStateAsync(config, targetId, cwd);
|
|
9288
|
+
if (directTarget?.state === "active") {
|
|
9289
|
+
return { targetId, templateId: targetId, reason: "active target goal" };
|
|
9290
|
+
}
|
|
9291
|
+
return null;
|
|
9472
9292
|
}
|
|
9473
|
-
function
|
|
9474
|
-
|
|
9475
|
-
|
|
9293
|
+
async function resolveGoalLoopTarget(config, cwd, loopGoalId, loopGoal, now) {
|
|
9294
|
+
const targetId = loopGoal.loopTarget?.id.trim() ?? "";
|
|
9295
|
+
assertSafeGoalId(targetId, "loop target");
|
|
9296
|
+
const activeTarget = await resolveActiveGoalLoopTarget(config, cwd, loopGoalId, loopGoal);
|
|
9297
|
+
if (activeTarget) return activeTarget;
|
|
9298
|
+
const directTarget = await fetchGoalStateAsync(config, targetId, cwd);
|
|
9299
|
+
const template = loadGoalTemplate(cwd, targetId);
|
|
9300
|
+
if (!template) {
|
|
9301
|
+
if (directTarget) {
|
|
9302
|
+
throw new Error(`goal target ${targetId} is ${directTarget.state}; no active instance or template found`);
|
|
9303
|
+
}
|
|
9304
|
+
return { targetId, templateId: targetId, reason: "literal target; no target state or template found" };
|
|
9305
|
+
}
|
|
9306
|
+
const instanceId = await chooseTargetInstanceId(config, cwd, targetId, loopGoal.preferredRunTime?.timezone, now);
|
|
9307
|
+
const instance = buildGoalTargetInstance(template, targetId, now);
|
|
9308
|
+
await putGoalStateAsync(config, instanceId, instance, `chore(goals): create ${instanceId}`, cwd);
|
|
9309
|
+
return {
|
|
9310
|
+
targetId: instanceId,
|
|
9311
|
+
templateId: targetId,
|
|
9312
|
+
reason: "created target instance from template",
|
|
9313
|
+
created: true
|
|
9314
|
+
};
|
|
9315
|
+
}
|
|
9316
|
+
function goalLoopNow() {
|
|
9317
|
+
const value = process.env.KODY_GOAL_LOOP_NOW?.trim();
|
|
9318
|
+
if (value) {
|
|
9319
|
+
const date = new Date(value);
|
|
9320
|
+
if (!Number.isNaN(date.getTime())) return date;
|
|
9321
|
+
}
|
|
9322
|
+
return /* @__PURE__ */ new Date();
|
|
9323
|
+
}
|
|
9324
|
+
async function findActiveTargetInstance(config, cwd, loopGoalId, targetId) {
|
|
9325
|
+
const candidates = [];
|
|
9326
|
+
for (const entryId of await listGoalStateIdsAsync(config, cwd)) {
|
|
9327
|
+
const id = entryId.trim();
|
|
9328
|
+
if (!id || id === loopGoalId || id === targetId || !id.startsWith(`${targetId}-`)) continue;
|
|
9329
|
+
assertSafeGoalId(id, "goal instance");
|
|
9330
|
+
const state = await fetchGoalStateAsync(config, id, cwd);
|
|
9331
|
+
if (!state || state.state !== "active") continue;
|
|
9332
|
+
if (!isTargetInstanceState(id, state, targetId)) continue;
|
|
9333
|
+
candidates.push({ id, state });
|
|
9334
|
+
}
|
|
9335
|
+
candidates.sort(compareGoalInstanceAge);
|
|
9336
|
+
return candidates[0] ?? null;
|
|
9337
|
+
}
|
|
9338
|
+
function isTargetInstanceState(id, state, targetId) {
|
|
9339
|
+
if (id.startsWith(`${targetId}-`)) return true;
|
|
9340
|
+
return ["template", "sourceTemplate", "templateId", "type"].some((key) => state.extra[key] === targetId);
|
|
9341
|
+
}
|
|
9342
|
+
function compareGoalInstanceAge(a, b) {
|
|
9343
|
+
const byTime = goalInstanceTime(a.state) - goalInstanceTime(b.state);
|
|
9344
|
+
return byTime === 0 ? a.id.localeCompare(b.id) : byTime;
|
|
9345
|
+
}
|
|
9346
|
+
function goalInstanceTime(state) {
|
|
9347
|
+
const value = state.createdAt ?? state.startedAt ?? state.updatedAt;
|
|
9348
|
+
if (!value) return 0;
|
|
9349
|
+
const parsed = Date.parse(value);
|
|
9350
|
+
return Number.isNaN(parsed) ? 0 : parsed;
|
|
9351
|
+
}
|
|
9352
|
+
function loadGoalTemplate(cwd, targetId) {
|
|
9353
|
+
return readJsonObject2(path26.join(cwd, ".kody-engine", "definitions", "goals", targetId, "state.json"));
|
|
9354
|
+
}
|
|
9355
|
+
function readJsonObject2(filePath) {
|
|
9356
|
+
if (!fs29.existsSync(filePath)) return null;
|
|
9357
|
+
const parsed = JSON.parse(fs29.readFileSync(filePath, "utf8"));
|
|
9358
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
9359
|
+
throw new Error(`goal template ${filePath} must be a JSON object`);
|
|
9360
|
+
}
|
|
9361
|
+
return parsed;
|
|
9362
|
+
}
|
|
9363
|
+
async function chooseTargetInstanceId(config, cwd, targetId, timezone, now) {
|
|
9364
|
+
const base = `${targetId}-${zonedDate(now, timezone ?? "UTC")}`;
|
|
9365
|
+
for (let index = 1; index <= 20; index += 1) {
|
|
9366
|
+
const id = index === 1 ? base : `${base}-${index}`;
|
|
9367
|
+
assertSafeGoalId(id, "goal instance");
|
|
9368
|
+
const existing = await fetchGoalStateAsync(config, id, cwd);
|
|
9369
|
+
if (!existing || existing.state === "active") return id;
|
|
9370
|
+
}
|
|
9371
|
+
throw new Error(`could not allocate goal target instance id for ${targetId}`);
|
|
9372
|
+
}
|
|
9373
|
+
function buildGoalTargetInstance(template, targetId, now) {
|
|
9374
|
+
const extra = { ...template };
|
|
9375
|
+
for (const key of ["state", "createdAt", "updatedAt", "startedAt"]) {
|
|
9376
|
+
delete extra[key];
|
|
9377
|
+
}
|
|
9378
|
+
extra.kind = "instance";
|
|
9379
|
+
extra.template = targetId;
|
|
9380
|
+
extra.sourceTemplate = targetId;
|
|
9381
|
+
extra.templateId = targetId;
|
|
9382
|
+
if (!isPlainObject3(extra.facts)) extra.facts = {};
|
|
9383
|
+
if (!Array.isArray(extra.blockers)) extra.blockers = [];
|
|
9384
|
+
const at = isoNoMs(now);
|
|
9385
|
+
return {
|
|
9386
|
+
state: "active",
|
|
9387
|
+
createdAt: at,
|
|
9388
|
+
updatedAt: at,
|
|
9389
|
+
extra
|
|
9390
|
+
};
|
|
9391
|
+
}
|
|
9392
|
+
function isPlainObject3(value) {
|
|
9393
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
9394
|
+
}
|
|
9395
|
+
function assertSafeGoalId(value, label) {
|
|
9396
|
+
if (!/^[A-Za-z0-9_.-]+$/.test(value)) {
|
|
9397
|
+
throw new Error(`${label} id must contain only letters, numbers, dot, underscore, or dash: ${value}`);
|
|
9476
9398
|
}
|
|
9477
|
-
return `workflows/${id}/workflow.json`;
|
|
9478
9399
|
}
|
|
9479
|
-
function
|
|
9480
|
-
|
|
9481
|
-
|
|
9482
|
-
|
|
9483
|
-
|
|
9484
|
-
|
|
9485
|
-
|
|
9486
|
-
|
|
9487
|
-
|
|
9488
|
-
|
|
9400
|
+
function zonedDate(date, timezone) {
|
|
9401
|
+
try {
|
|
9402
|
+
const parts = new Intl.DateTimeFormat("en-CA", {
|
|
9403
|
+
timeZone: timezone,
|
|
9404
|
+
year: "numeric",
|
|
9405
|
+
month: "2-digit",
|
|
9406
|
+
day: "2-digit"
|
|
9407
|
+
}).formatToParts(date);
|
|
9408
|
+
const get = (type) => parts.find((part) => part.type === type)?.value;
|
|
9409
|
+
const year = get("year");
|
|
9410
|
+
const month = get("month");
|
|
9411
|
+
const day = get("day");
|
|
9412
|
+
if (year && month && day) return `${year}-${month}-${day}`;
|
|
9413
|
+
} catch {
|
|
9489
9414
|
}
|
|
9490
|
-
|
|
9491
|
-
steps: raw.steps,
|
|
9492
|
-
startAt: raw.startAt
|
|
9493
|
-
});
|
|
9494
|
-
const steps = workflow?.steps;
|
|
9495
|
-
const capabilities = steps ? steps.map((step) => step.capability) : normalizeWorkflowCapabilities(raw.capabilities);
|
|
9496
|
-
if (!name || capabilities.length === 0) return null;
|
|
9497
|
-
return {
|
|
9498
|
-
name,
|
|
9499
|
-
agent,
|
|
9500
|
-
capabilities,
|
|
9501
|
-
...raw.runWithoutApproval === true ? { runWithoutApproval: true } : {},
|
|
9502
|
-
...steps ? { steps } : {},
|
|
9503
|
-
...workflow?.startAt ? { startAt: workflow.startAt } : {},
|
|
9504
|
-
...typeof raw.createdAt === "string" ? { createdAt: raw.createdAt } : {},
|
|
9505
|
-
...typeof raw.updatedAt === "string" ? { updatedAt: raw.updatedAt } : {}
|
|
9506
|
-
};
|
|
9415
|
+
return date.toISOString().slice(0, 10);
|
|
9507
9416
|
}
|
|
9508
|
-
function
|
|
9509
|
-
|
|
9510
|
-
|
|
9511
|
-
|
|
9512
|
-
|
|
9513
|
-
|
|
9514
|
-
|
|
9515
|
-
for (const filePath of candidates) {
|
|
9516
|
-
if (!fs29.existsSync(filePath)) continue;
|
|
9517
|
-
const workflow = parseWorkflowDefinition(fs29.readFileSync(filePath, "utf8"));
|
|
9518
|
-
if (workflow) return workflow;
|
|
9417
|
+
function isoNoMs(date) {
|
|
9418
|
+
return date.toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
9419
|
+
}
|
|
9420
|
+
var init_targetLoopResolution = __esm({
|
|
9421
|
+
"src/goal/targetLoopResolution.ts"() {
|
|
9422
|
+
"use strict";
|
|
9423
|
+
init_stateStore();
|
|
9519
9424
|
}
|
|
9520
|
-
|
|
9425
|
+
});
|
|
9426
|
+
|
|
9427
|
+
// src/goal/typeDefinitions.ts
|
|
9428
|
+
function cloneRoute(route) {
|
|
9429
|
+
return route.map((step) => ({
|
|
9430
|
+
stage: step.stage,
|
|
9431
|
+
evidence: step.evidence,
|
|
9432
|
+
capability: step.capability,
|
|
9433
|
+
...step.implementation ? { implementation: step.implementation } : {},
|
|
9434
|
+
...step.args ? { args: structuredClone(step.args) } : {}
|
|
9435
|
+
}));
|
|
9521
9436
|
}
|
|
9522
|
-
function
|
|
9523
|
-
return
|
|
9524
|
-
slug: id,
|
|
9525
|
-
dir: path26.dirname(source),
|
|
9526
|
-
profilePath: source,
|
|
9527
|
-
bodyPath: source,
|
|
9528
|
-
title: workflow.name,
|
|
9529
|
-
body: "",
|
|
9530
|
-
rawBody: "",
|
|
9531
|
-
rawProfile: { name: id, workflow },
|
|
9532
|
-
config: {
|
|
9533
|
-
action: id,
|
|
9534
|
-
workflow: workflowDefinitionToConfig(workflow),
|
|
9535
|
-
describe: workflow.name,
|
|
9536
|
-
agent: workflow.agent
|
|
9537
|
-
}
|
|
9538
|
-
};
|
|
9437
|
+
function stringArray(value) {
|
|
9438
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string") ? value : null;
|
|
9539
9439
|
}
|
|
9540
|
-
function
|
|
9541
|
-
if (!Array.isArray(value)) return
|
|
9542
|
-
const
|
|
9543
|
-
const capabilities = [];
|
|
9440
|
+
function routeArray(value) {
|
|
9441
|
+
if (!Array.isArray(value)) return null;
|
|
9442
|
+
const route = [];
|
|
9544
9443
|
for (const item of value) {
|
|
9545
|
-
if (typeof item !== "
|
|
9546
|
-
const
|
|
9547
|
-
if (
|
|
9548
|
-
|
|
9549
|
-
|
|
9444
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) return null;
|
|
9445
|
+
const raw = item;
|
|
9446
|
+
if (typeof raw.stage !== "string" || typeof raw.evidence !== "string" || typeof raw.capability !== "string")
|
|
9447
|
+
return null;
|
|
9448
|
+
route.push({
|
|
9449
|
+
stage: raw.stage,
|
|
9450
|
+
evidence: raw.evidence,
|
|
9451
|
+
capability: raw.capability,
|
|
9452
|
+
implementation: typeof raw.implementation === "string" ? raw.implementation : void 0,
|
|
9453
|
+
args: raw.args && typeof raw.args === "object" && !Array.isArray(raw.args) ? { ...raw.args } : void 0
|
|
9454
|
+
});
|
|
9550
9455
|
}
|
|
9551
|
-
return
|
|
9456
|
+
return route;
|
|
9552
9457
|
}
|
|
9553
|
-
function
|
|
9458
|
+
function managedGoalTypeDefinition(type) {
|
|
9459
|
+
return Object.hasOwn(GOAL_TYPE_DEFINITIONS, type) ? GOAL_TYPE_DEFINITIONS[type] : null;
|
|
9460
|
+
}
|
|
9461
|
+
function expandManagedGoalState(state) {
|
|
9462
|
+
const type = typeof state.extra.type === "string" ? state.extra.type : "";
|
|
9463
|
+
const definition = managedGoalTypeDefinition(type);
|
|
9464
|
+
if (!definition) return state;
|
|
9465
|
+
const destination = state.extra.destination && typeof state.extra.destination === "object" && !Array.isArray(state.extra.destination) ? { ...state.extra.destination } : {};
|
|
9466
|
+
const outcome = typeof destination.outcome === "string" ? destination.outcome : "";
|
|
9467
|
+
const evidence = stringArray(destination.evidence);
|
|
9468
|
+
const capabilities = stringArray(state.extra.capabilities);
|
|
9469
|
+
const route = routeArray(state.extra.route);
|
|
9470
|
+
const facts = state.extra.facts && typeof state.extra.facts === "object" && !Array.isArray(state.extra.facts) ? { ...state.extra.facts } : {};
|
|
9471
|
+
const blockers = stringArray(state.extra.blockers);
|
|
9554
9472
|
return {
|
|
9555
|
-
|
|
9556
|
-
|
|
9473
|
+
...state,
|
|
9474
|
+
extra: {
|
|
9475
|
+
...state.extra,
|
|
9476
|
+
type: definition.type,
|
|
9477
|
+
destination: {
|
|
9478
|
+
...destination,
|
|
9479
|
+
outcome,
|
|
9480
|
+
evidence: evidence && evidence.length > 0 ? evidence : [...definition.evidence]
|
|
9481
|
+
},
|
|
9482
|
+
capabilities: capabilities && capabilities.length > 0 ? capabilities : [...definition.capabilities],
|
|
9483
|
+
route: route && route.length > 0 ? route : cloneRoute(definition.route),
|
|
9484
|
+
facts,
|
|
9485
|
+
blockers: blockers ?? []
|
|
9486
|
+
}
|
|
9557
9487
|
};
|
|
9558
9488
|
}
|
|
9559
|
-
|
|
9560
|
-
|
|
9561
|
-
|
|
9562
|
-
} catch {
|
|
9563
|
-
return null;
|
|
9564
|
-
}
|
|
9565
|
-
}
|
|
9566
|
-
var WORKFLOW_ID_PATTERN, CAPABILITY_ID_PATTERN;
|
|
9567
|
-
var init_workflowDefinitions = __esm({
|
|
9568
|
-
"src/workflowDefinitions.ts"() {
|
|
9489
|
+
var GOAL_TYPE_DEFINITIONS;
|
|
9490
|
+
var init_typeDefinitions = __esm({
|
|
9491
|
+
"src/goal/typeDefinitions.ts"() {
|
|
9569
9492
|
"use strict";
|
|
9570
|
-
|
|
9571
|
-
|
|
9572
|
-
|
|
9573
|
-
|
|
9574
|
-
|
|
9493
|
+
GOAL_TYPE_DEFINITIONS = {
|
|
9494
|
+
improve: {
|
|
9495
|
+
type: "improve",
|
|
9496
|
+
evidence: ["planReady", "changeImplemented", "changeVerified"],
|
|
9497
|
+
capabilities: ["plan", "fix", "review"],
|
|
9498
|
+
route: [
|
|
9499
|
+
{ stage: "plan", evidence: "planReady", capability: "plan", implementation: "plan" },
|
|
9500
|
+
{
|
|
9501
|
+
stage: "implement",
|
|
9502
|
+
evidence: "changeImplemented",
|
|
9503
|
+
capability: "fix",
|
|
9504
|
+
implementation: "fix"
|
|
9505
|
+
},
|
|
9506
|
+
{
|
|
9507
|
+
stage: "review",
|
|
9508
|
+
evidence: "changeVerified",
|
|
9509
|
+
capability: "review",
|
|
9510
|
+
implementation: "review"
|
|
9511
|
+
}
|
|
9512
|
+
]
|
|
9513
|
+
},
|
|
9514
|
+
maintain: {
|
|
9515
|
+
type: "maintain",
|
|
9516
|
+
evidence: [],
|
|
9517
|
+
capabilities: [
|
|
9518
|
+
"cleanup",
|
|
9519
|
+
"code-health",
|
|
9520
|
+
"docs-health",
|
|
9521
|
+
"documentation-maintenance",
|
|
9522
|
+
"memory-compaction",
|
|
9523
|
+
"repo-graph",
|
|
9524
|
+
"skills-research"
|
|
9525
|
+
],
|
|
9526
|
+
route: []
|
|
9527
|
+
},
|
|
9528
|
+
monitor: {
|
|
9529
|
+
type: "monitor",
|
|
9530
|
+
evidence: [],
|
|
9531
|
+
capabilities: ["health-check", "pr-health-triage", "qa-sweep"],
|
|
9532
|
+
route: []
|
|
9533
|
+
},
|
|
9534
|
+
release: {
|
|
9535
|
+
type: "release",
|
|
9536
|
+
evidence: ["releasePrExists", "mainMerged", "productionDeployed"],
|
|
9537
|
+
capabilities: ["release", "release-merge", "vercel-production-deploy"],
|
|
9538
|
+
route: [
|
|
9539
|
+
{
|
|
9540
|
+
stage: "release",
|
|
9541
|
+
evidence: "releasePrExists",
|
|
9542
|
+
capability: "release",
|
|
9543
|
+
implementation: "release-prepare",
|
|
9544
|
+
args: { issue: { fact: "issue" }, goal: { fact: "goalId" } }
|
|
9545
|
+
},
|
|
9546
|
+
{
|
|
9547
|
+
stage: "merge",
|
|
9548
|
+
evidence: "mainMerged",
|
|
9549
|
+
capability: "release-merge",
|
|
9550
|
+
implementation: "release-merge",
|
|
9551
|
+
args: { pr: { fact: "releasePr" }, issue: { fact: "issue" }, goal: { fact: "goalId" } }
|
|
9552
|
+
},
|
|
9553
|
+
{
|
|
9554
|
+
stage: "publish",
|
|
9555
|
+
evidence: "productionDeployed",
|
|
9556
|
+
capability: "vercel-production-deploy",
|
|
9557
|
+
implementation: "vercel-production-deploy"
|
|
9558
|
+
}
|
|
9559
|
+
]
|
|
9560
|
+
},
|
|
9561
|
+
checklist: {
|
|
9562
|
+
type: "checklist",
|
|
9563
|
+
evidence: ["checklistComplete"],
|
|
9564
|
+
capabilities: ["task-verifier"],
|
|
9565
|
+
route: [
|
|
9566
|
+
{
|
|
9567
|
+
stage: "verify",
|
|
9568
|
+
evidence: "checklistComplete",
|
|
9569
|
+
capability: "task-verifier",
|
|
9570
|
+
implementation: "task-verifier"
|
|
9571
|
+
}
|
|
9572
|
+
]
|
|
9573
|
+
}
|
|
9574
|
+
};
|
|
9575
9575
|
}
|
|
9576
9576
|
});
|
|
9577
9577
|
|
|
@@ -22249,7 +22249,11 @@ async function runCapabilityImplementationStep(valid, profileName, capabilityIde
|
|
|
22249
22249
|
const shouldApplyResolvedCapabilityArgs = valid.implementation === void 0 && resolvedCapability && profileName === resolvedCapability.implementation;
|
|
22250
22250
|
input.cliArgs = shouldApplyResolvedCapabilityArgs ? { ...resolvedCapability.cliArgs, ...input.cliArgs } : input.cliArgs;
|
|
22251
22251
|
if (simpleCapabilityRuntime && profileName === simpleCapabilityRuntime.implementation && capabilityIdentity) {
|
|
22252
|
-
const
|
|
22252
|
+
const businessArgs = { ...valid.cliArgs };
|
|
22253
|
+
if (businessArgs.capability === capabilityIdentity) {
|
|
22254
|
+
delete businessArgs.capability;
|
|
22255
|
+
}
|
|
22256
|
+
const capabilityInput = Object.keys(businessArgs).length > 0 ? genericInputFromArgs(businessArgs) : void 0;
|
|
22253
22257
|
input.cliArgs = simpleCapabilityRuntimeArgs(simpleCapabilityRuntime, capabilityIdentity, capabilityInput);
|
|
22254
22258
|
}
|
|
22255
22259
|
const run = base.chain === false ? runImplementation : runImplementationChain;
|
|
@@ -23980,9 +23984,10 @@ init_config();
|
|
|
23980
23984
|
|
|
23981
23985
|
// src/definition-hydration.ts
|
|
23982
23986
|
init_state_backend();
|
|
23987
|
+
init_workflowDefinitions();
|
|
23983
23988
|
import { createHash as createHash2 } from "crypto";
|
|
23984
|
-
import * as
|
|
23985
|
-
import * as
|
|
23989
|
+
import * as fs17 from "fs";
|
|
23990
|
+
import * as path18 from "path";
|
|
23986
23991
|
var SLUG_RE = /^[a-z0-9][a-z0-9_-]{0,127}$/;
|
|
23987
23992
|
var REPOSITORY_OWNED_NAMESPACES = ["loops"];
|
|
23988
23993
|
function assertSafeDefinitionPath(filePath) {
|
|
@@ -24014,9 +24019,9 @@ function verifyDefinition(definition) {
|
|
|
24014
24019
|
}
|
|
24015
24020
|
function writeBundle(root, bundle) {
|
|
24016
24021
|
for (const [filePath, contents] of Object.entries(bundle.files)) {
|
|
24017
|
-
const target =
|
|
24018
|
-
|
|
24019
|
-
|
|
24022
|
+
const target = path18.join(root, filePath);
|
|
24023
|
+
fs17.mkdirSync(path18.dirname(target), { recursive: true });
|
|
24024
|
+
fs17.writeFileSync(target, contents, "utf8");
|
|
24020
24025
|
}
|
|
24021
24026
|
}
|
|
24022
24027
|
function writeDefinition(root, kind, definition) {
|
|
@@ -24024,46 +24029,59 @@ function writeDefinition(root, kind, definition) {
|
|
|
24024
24029
|
if (kind === "agent") {
|
|
24025
24030
|
const raw = bundle.files["agent.md"];
|
|
24026
24031
|
if (typeof raw !== "string") throw new Error(`agent definition ${definition.slug} is missing agent.md`);
|
|
24027
|
-
|
|
24032
|
+
fs17.writeFileSync(path18.join(root, "agents", `${definition.slug}.md`), raw, "utf8");
|
|
24028
24033
|
return;
|
|
24029
24034
|
}
|
|
24030
24035
|
if (kind === "goal") {
|
|
24031
|
-
writeBundle(
|
|
24036
|
+
writeBundle(path18.join(root, "goals", definition.slug), bundle);
|
|
24032
24037
|
return;
|
|
24033
24038
|
}
|
|
24034
24039
|
if (kind === "implementation") {
|
|
24035
|
-
writeBundle(
|
|
24040
|
+
writeBundle(path18.join(root, "implementations", definition.slug), bundle);
|
|
24036
24041
|
return;
|
|
24037
24042
|
}
|
|
24038
24043
|
if (kind === "asset") {
|
|
24039
|
-
writeBundle(
|
|
24044
|
+
writeBundle(path18.join(root, "shared"), bundle);
|
|
24040
24045
|
return;
|
|
24041
24046
|
}
|
|
24042
|
-
writeBundle(
|
|
24047
|
+
writeBundle(path18.join(root, "capabilities", definition.slug), bundle);
|
|
24048
|
+
}
|
|
24049
|
+
function writeWorkflow(root, document) {
|
|
24050
|
+
const workflow = normalizeWorkflowDefinition(document.definition);
|
|
24051
|
+
if (!workflow) throw new Error(`invalid workflow definition: ${document.workflowId}`);
|
|
24052
|
+
const contents = `${JSON.stringify(workflow, null, 2)}
|
|
24053
|
+
`;
|
|
24054
|
+
const bundle = { schemaVersion: 1, files: { "workflow.json": contents } };
|
|
24055
|
+
const target = path18.join(root, workflowDefinitionPath(document.workflowId));
|
|
24056
|
+
fs17.mkdirSync(path18.dirname(target), { recursive: true });
|
|
24057
|
+
fs17.writeFileSync(target, contents, "utf8");
|
|
24058
|
+
return definitionVersion(bundle);
|
|
24043
24059
|
}
|
|
24044
24060
|
function preserveRepositoryDefinitions(root, staging) {
|
|
24045
24061
|
for (const namespace of REPOSITORY_OWNED_NAMESPACES) {
|
|
24046
|
-
const source =
|
|
24047
|
-
if (!
|
|
24048
|
-
|
|
24062
|
+
const source = path18.join(root, namespace);
|
|
24063
|
+
if (!fs17.existsSync(source)) continue;
|
|
24064
|
+
fs17.cpSync(source, path18.join(staging, namespace), { recursive: true });
|
|
24049
24065
|
}
|
|
24050
24066
|
}
|
|
24051
24067
|
async function hydrateDefinitions(options) {
|
|
24052
|
-
const root =
|
|
24068
|
+
const root = path18.join(options.cwd, ".kody-engine", "definitions");
|
|
24053
24069
|
const staging = `${root}.tmp-${process.pid}-${Date.now()}`;
|
|
24054
|
-
|
|
24055
|
-
|
|
24056
|
-
|
|
24057
|
-
|
|
24058
|
-
|
|
24059
|
-
|
|
24070
|
+
fs17.rmSync(staging, { recursive: true, force: true });
|
|
24071
|
+
fs17.mkdirSync(path18.join(staging, "agents"), { recursive: true });
|
|
24072
|
+
fs17.mkdirSync(path18.join(staging, "capabilities"), { recursive: true });
|
|
24073
|
+
fs17.mkdirSync(path18.join(staging, "goals"), { recursive: true });
|
|
24074
|
+
fs17.mkdirSync(path18.join(staging, "implementations"), { recursive: true });
|
|
24075
|
+
fs17.mkdirSync(path18.join(staging, "shared"), { recursive: true });
|
|
24076
|
+
fs17.mkdirSync(path18.join(staging, "workflows"), { recursive: true });
|
|
24060
24077
|
try {
|
|
24061
|
-
const [capabilities, agents, goals, implementations, assets] = await Promise.all([
|
|
24078
|
+
const [capabilities, agents, goals, implementations, assets, workflows] = await Promise.all([
|
|
24062
24079
|
options.backend.listDefinitions(options.tenantId, "capability"),
|
|
24063
24080
|
options.backend.listDefinitions(options.tenantId, "agent"),
|
|
24064
24081
|
options.backend.listDefinitions(options.tenantId, "goal"),
|
|
24065
24082
|
options.backend.listDefinitions(options.tenantId, "implementation"),
|
|
24066
|
-
options.backend.listDefinitions(options.tenantId, "asset")
|
|
24083
|
+
options.backend.listDefinitions(options.tenantId, "asset"),
|
|
24084
|
+
options.backend.listWorkflows?.(options.tenantId) ?? Promise.resolve([])
|
|
24067
24085
|
]);
|
|
24068
24086
|
const versions = {};
|
|
24069
24087
|
for (const definition of capabilities) {
|
|
@@ -24086,6 +24104,9 @@ async function hydrateDefinitions(options) {
|
|
|
24086
24104
|
writeDefinition(staging, "asset", definition);
|
|
24087
24105
|
versions[`asset:${definition.slug}`] = definition.version;
|
|
24088
24106
|
}
|
|
24107
|
+
for (const workflow of workflows) {
|
|
24108
|
+
versions[`workflow:${workflow.workflowId}`] = writeWorkflow(staging, workflow);
|
|
24109
|
+
}
|
|
24089
24110
|
preserveRepositoryDefinitions(root, staging);
|
|
24090
24111
|
const manifest = {
|
|
24091
24112
|
schemaVersion: 1,
|
|
@@ -24093,13 +24114,13 @@ async function hydrateDefinitions(options) {
|
|
|
24093
24114
|
hydratedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
24094
24115
|
versions: Object.fromEntries(Object.entries(versions).sort(([left], [right]) => left.localeCompare(right)))
|
|
24095
24116
|
};
|
|
24096
|
-
|
|
24117
|
+
fs17.writeFileSync(path18.join(staging, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
|
|
24097
24118
|
`, "utf8");
|
|
24098
|
-
|
|
24099
|
-
|
|
24119
|
+
fs17.rmSync(root, { recursive: true, force: true });
|
|
24120
|
+
fs17.renameSync(staging, root);
|
|
24100
24121
|
return { root, tenantId: options.tenantId, versions: manifest.versions };
|
|
24101
24122
|
} catch (error) {
|
|
24102
|
-
|
|
24123
|
+
fs17.rmSync(staging, { recursive: true, force: true });
|
|
24103
24124
|
throw error;
|
|
24104
24125
|
}
|
|
24105
24126
|
}
|
|
@@ -24251,7 +24272,7 @@ init_definition_paths();
|
|
|
24251
24272
|
|
|
24252
24273
|
// src/dispatch.ts
|
|
24253
24274
|
init_config();
|
|
24254
|
-
import * as
|
|
24275
|
+
import * as fs18 from "fs";
|
|
24255
24276
|
|
|
24256
24277
|
// src/cron-match.ts
|
|
24257
24278
|
var FIELD_BOUNDS = [
|
|
@@ -24358,10 +24379,10 @@ function autoDispatch(opts) {
|
|
|
24358
24379
|
}
|
|
24359
24380
|
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
24360
24381
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
24361
|
-
if (!eventName || !eventPath || !
|
|
24382
|
+
if (!eventName || !eventPath || !fs18.existsSync(eventPath)) return null;
|
|
24362
24383
|
let event = {};
|
|
24363
24384
|
try {
|
|
24364
|
-
event = JSON.parse(
|
|
24385
|
+
event = JSON.parse(fs18.readFileSync(eventPath, "utf-8"));
|
|
24365
24386
|
} catch {
|
|
24366
24387
|
return null;
|
|
24367
24388
|
}
|
|
@@ -24485,7 +24506,7 @@ function autoDispatchTyped(opts) {
|
|
|
24485
24506
|
if (legacy) return { kind: "route", ...legacy };
|
|
24486
24507
|
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
24487
24508
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
24488
|
-
if (!eventName || !eventPath || !
|
|
24509
|
+
if (!eventName || !eventPath || !fs18.existsSync(eventPath)) {
|
|
24489
24510
|
return { kind: "silent", reason: "no GHA event context" };
|
|
24490
24511
|
}
|
|
24491
24512
|
if (eventName !== "issue_comment") {
|
|
@@ -24493,7 +24514,7 @@ function autoDispatchTyped(opts) {
|
|
|
24493
24514
|
}
|
|
24494
24515
|
let event = {};
|
|
24495
24516
|
try {
|
|
24496
|
-
event = JSON.parse(
|
|
24517
|
+
event = JSON.parse(fs18.readFileSync(eventPath, "utf-8"));
|
|
24497
24518
|
} catch {
|
|
24498
24519
|
return { kind: "silent", reason: "GHA event payload unreadable" };
|
|
24499
24520
|
}
|
|
@@ -24547,7 +24568,7 @@ function dispatchScheduledWatches(opts) {
|
|
|
24547
24568
|
for (const exe of listRuntimeProfilesForCwd(opts?.cwd ?? process.cwd())) {
|
|
24548
24569
|
let raw;
|
|
24549
24570
|
try {
|
|
24550
|
-
raw =
|
|
24571
|
+
raw = fs18.readFileSync(exe.profilePath, "utf-8");
|
|
24551
24572
|
} catch {
|
|
24552
24573
|
continue;
|
|
24553
24574
|
}
|