@kody-ade/kody-engine 0.4.499 → 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.
Files changed (2) hide show
  1. package/dist/bin/kody.js +1357 -1340
  2. 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.499",
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/gha.ts
4121
- import { execFileSync as execFileSync2 } from "child_process";
4122
- import * as fs18 from "fs";
4123
- function getRunUrl() {
4124
- const server = process.env.GITHUB_SERVER_URL;
4125
- const repo = process.env.GITHUB_REPOSITORY;
4126
- const runId = process.env.GITHUB_RUN_ID;
4127
- if (!server || !repo || !runId) return "";
4128
- return `${server}/${repo}/actions/runs/${runId}`;
4129
- }
4130
- function reactToTriggerComment(cwd) {
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
- const actionResults = results.filter(resultLooksLikeAction);
4207
- if (actionResults.length === 0) {
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
- return fail("observe-does-not-act", "observe capability returned action-shaped output", {
4213
- resultCount: results.length,
4214
- actionResults: actionResults.map(resultSummary)
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
- function evaluateVerifyBoundary(capabilityKind, results) {
4218
- if (capabilityKind !== "verify") {
4219
- return pass("verify-does-not-fix", "capability is not verify", { capabilityKind });
4220
- }
4221
- const actionResults = results.filter(resultLooksLikeAction);
4222
- if (actionResults.length === 0) {
4223
- return pass("verify-does-not-fix", "verify capability returned verdict evidence without action output", {
4224
- resultCount: results.length
4225
- });
4226
- }
4227
- return fail("verify-does-not-fix", "verify capability returned fix/change output", {
4228
- resultCount: results.length,
4229
- actionResults: actionResults.map(resultSummary)
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
- function evaluateGoalOwnershipBoundary(results) {
4233
- const targetBearing = results.filter((result) => result.target?.type === "goal");
4234
- if (targetBearing.length === 0) {
4235
- return pass("capability-does-not-own-goal-progress", "capability output is parent-neutral", {
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
- function resultLooksLikeAction(result) {
4245
- if (result.status === "changed") return true;
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
- var validator, CapabilityContractValidationError;
4295
- var init_capability_contract_validation = __esm({
4296
- "src/agency/capability-contract-validation.ts"() {
4297
- "use strict";
4298
- validator = new Ajv({
4299
- allErrors: true,
4300
- strict: true,
4301
- validateFormats: false
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
- CapabilityContractValidationError = class extends Error {
4304
- constructor(boundary, errors) {
4305
- const details = errors.map((error) => {
4306
- const location = error.instancePath || "$";
4307
- const property = error.keyword === "additionalProperties" && typeof error.params.additionalProperty === "string" ? ` (${error.params.additionalProperty})` : "";
4308
- return `${location}: ${error.message ?? error.keyword}${property}`;
4309
- }).join("; ");
4310
- super(
4311
- `Capability ${boundary} does not match its declared contract: ${details}`
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
- this.boundary = boundary;
4314
- this.errors = errors;
4315
- this.name = "CapabilityContractValidationError";
4278
+ } else {
4279
+ adjacency.get(id)?.push(target);
4316
4280
  }
4317
- boundary;
4318
- errors;
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
- // src/capabilityReport.ts
4324
- function parseCapabilityReportsFromText(text2) {
4325
- const reports = [];
4326
- for (const match of text2.matchAll(REPORT_LINE)) {
4327
- const raw = match[1]?.trim();
4328
- if (!raw) continue;
4329
- try {
4330
- const parsed = parseCapabilityReport(JSON.parse(raw));
4331
- if (parsed) reports.push(parsed);
4332
- } catch {
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 parseCapabilityReport(raw) {
4338
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
4339
- const obj = raw;
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 parseCapabilityReportTarget(raw) {
4352
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
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 parseCapabilityReportEvidence(raw) {
4359
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
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 parseFacts(raw) {
4368
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
4369
- const out = {};
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
- var REPORT_LINE, CONTROL_FACT_KEYS;
4378
- var init_capabilityReport = __esm({
4379
- "src/capabilityReport.ts"() {
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
- REPORT_LINE = /^KODY_(?:CAPABILITY|CAPABILITY)_REPORT=(.+)$/gm;
4382
- CONTROL_FACT_KEYS = /* @__PURE__ */ new Set(["blockers", "destination", "capabilities", "route", "stage", "state"]);
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/goal/evidenceState.ts
4387
- function parseGoalEvidenceState(raw) {
4388
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
4389
- const out = {};
4390
- for (const [evidence, value] of Object.entries(raw)) {
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 isGoalEvidenceResultClass(value) {
4414
- return value === "succeeded" || value === "pending" || value === "retryable" || value === "needsFix" || value === "fatal";
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 parseGoalEvidenceProgress(value) {
4416
+ function normalizeWorkflowDefinition(value) {
4417
4417
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
4418
4418
  const raw = value;
4419
- if (!isGoalEvidenceResultClass(raw.resultClass)) return null;
4420
- const attempts = typeof raw.attempts === "number" && raw.attempts >= 0 ? Math.floor(raw.attempts) : 0;
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
- resultClass: raw.resultClass,
4423
- attempts,
4424
- ...stringField2(raw.reason) ? { reason: stringField2(raw.reason) } : {},
4425
- ...stringField2(raw.nextAction) ? { nextAction: stringField2(raw.nextAction) } : {},
4426
- ...stringField2(raw.nextRetryAt) ? { nextRetryAt: stringField2(raw.nextRetryAt) } : {},
4427
- ...positiveInteger(raw.issue) ? { issue: positiveInteger(raw.issue) } : {},
4428
- ...stringField2(raw.updatedAt) ? { updatedAt: stringField2(raw.updatedAt) } : {}
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 definedProgressFields(update) {
4432
- const out = {};
4433
- if (update.reason !== void 0) out.reason = update.reason;
4434
- if (update.nextAction !== void 0) out.nextAction = update.nextAction;
4435
- if (update.nextRetryAt !== void 0) out.nextRetryAt = update.nextRetryAt;
4436
- if (update.issue !== void 0) out.issue = update.issue;
4437
- if (update.updatedAt !== void 0) out.updatedAt = update.updatedAt;
4438
- return out;
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 stringField2(value) {
4441
- return typeof value === "string" && value.trim() ? value.trim() : void 0;
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 positiveInteger(value) {
4444
- if (typeof value === "number" && Number.isInteger(value) && value > 0) return value;
4445
- return void 0;
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
- var init_evidenceState = __esm({
4448
- "src/goal/evidenceState.ts"() {
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/capabilityResult.ts
4454
- function parseCapabilityResultsFromText(text2) {
4455
- const results = [];
4456
- for (const match of text2.matchAll(RESULT_LINE)) {
4457
- const raw = match[1]?.trim();
4458
- if (!raw) continue;
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
- const parsed = parseCapabilityResult(JSON.parse(raw));
4461
- if (parsed) results.push(parsed);
4462
- } catch {
4559
+ execFileSync2("gh", args, opts);
4560
+ return;
4561
+ } catch (err) {
4562
+ lastErr = err;
4463
4563
  }
4464
4564
  }
4465
- return results;
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 parseCapabilityResult(raw) {
4468
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
4469
- const obj = raw;
4470
- if (obj.version !== 1) return null;
4471
- if (!isCapabilityResultStatus(obj.status)) return null;
4472
- const summary = typeof obj.summary === "string" ? obj.summary.trim() : "";
4473
- if (!summary) return null;
4474
- const target = obj.target === void 0 ? void 0 : parseCapabilityReportTarget(obj.target);
4475
- if (obj.target !== void 0 && !target) return null;
4476
- const evidence = obj.evidence === void 0 ? void 0 : parseCapabilityReportEvidence(obj.evidence);
4477
- if (obj.evidence !== void 0 && !evidence) return null;
4478
- const facts = parseFacts2(obj.facts);
4479
- if (!facts) return null;
4480
- const artifacts = parseArtifacts(obj.artifacts);
4481
- if (!artifacts) return null;
4482
- const missingEvidence = parseOptionalStringArray(obj.missingEvidence);
4483
- if (!missingEvidence) return null;
4484
- const blockers = parseOptionalStringArray(obj.blockers);
4485
- if (!blockers) return null;
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
- ...target ? { target } : {},
4489
- status: obj.status,
4490
- ...isGoalEvidenceResultClass(obj.resultClass) ? { resultClass: obj.resultClass } : {},
4491
- summary,
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 isCapabilityResultStatus(value) {
4500
- return typeof value === "string" && STATUSES.has(value);
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
- return facts;
4512
- }
4513
- function parseStringArray(raw) {
4514
- if (!Array.isArray(raw)) return null;
4515
- const out = [];
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 out;
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 parseOptionalStringArray(raw) {
4523
- if (raw === void 0) return [];
4524
- return parseStringArray(raw);
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 parseArtifacts(raw) {
4527
- if (raw === void 0) return [];
4528
- if (!Array.isArray(raw)) return null;
4529
- const artifacts = [];
4530
- for (const item of raw) {
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 artifacts;
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
- var RESULT_LINE, STATUSES, CONTROL_FACT_KEYS2;
4546
- var init_capabilityResult = __esm({
4547
- "src/capabilityResult.ts"() {
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
- init_capabilityReport();
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/profile-error.ts
4558
- var ProfileError;
4559
- var init_profile_error = __esm({
4560
- "src/profile-error.ts"() {
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
- ProfileError = class extends Error {
4563
- constructor(profilePath, message) {
4564
- super(`Invalid profile at ${profilePath}:
4565
- ${message}`);
4566
- this.profilePath = profilePath;
4567
- this.name = "ProfileError";
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
- profilePath;
4712
+ boundary;
4713
+ errors;
4570
4714
  };
4571
4715
  }
4572
4716
  });
4573
4717
 
4574
- // src/lifecycles/prBranch.ts
4575
- function prBranchLifecycle(profile, profilePath) {
4576
- const cfg = validateConfig(profile.lifecycleConfig, profilePath);
4577
- const before = [];
4578
- if (cfg.sync) before.push({ script: "syncFlow" });
4579
- before.push({
4580
- script: "setLifecycleLabel",
4581
- with: {
4582
- label: cfg.label.name,
4583
- color: cfg.label.color,
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
- if (!extrasInserted && extras.length > 0) {
4619
- out.unshift(...extras.map((e) => ({ script: e })));
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 validateConfig(raw, profilePath) {
4624
- if (!raw) {
4625
- throw new ProfileError(profilePath, `lifecycle "pr-branch" requires "lifecycleConfig" with a "label" object`);
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
- const label = raw.label;
4628
- if (!label || typeof label !== "object" || Array.isArray(label)) {
4629
- throw new ProfileError(profilePath, `lifecycle "pr-branch": lifecycleConfig.label must be an object`);
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
- const lbl = label;
4632
- for (const k of ["name", "color", "description"]) {
4633
- if (typeof lbl[k] !== "string" || lbl[k].length === 0) {
4634
- throw new ProfileError(
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 fs19 from "fs";
5113
+ import * as fs20 from "fs";
4719
5114
  import * as os3 from "os";
4720
- import * as path18 from "path";
5115
+ import * as path19 from "path";
4721
5116
  function getPluginsCatalogRoot() {
4722
- const here = path18.dirname(new URL(import.meta.url).pathname);
5117
+ const here = path19.dirname(new URL(import.meta.url).pathname);
4723
5118
  const candidates = [
4724
- path18.join(here, "..", "plugins"),
5119
+ path19.join(here, "..", "plugins"),
4725
5120
  // dev: src/scripts → src/plugins
4726
- path18.join(here, "..", "..", "plugins"),
5121
+ path19.join(here, "..", "..", "plugins"),
4727
5122
  // built: dist/scripts → dist/plugins
4728
- path18.join(here, "..", "..", "src", "plugins")
5123
+ path19.join(here, "..", "..", "src", "plugins")
4729
5124
  // fallback
4730
5125
  ];
4731
5126
  for (const c of candidates) {
4732
- if (fs19.existsSync(c) && fs19.statSync(c).isDirectory()) return c;
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
- fs19.mkdirSync(dst, { recursive: true });
4738
- for (const ent of fs19.readdirSync(src, { withFileTypes: true })) {
4739
- const s = path18.join(src, ent.name);
4740
- const d = path18.join(dst, ent.name);
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()) fs19.copyFileSync(s, d);
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 = path18.join(os3.tmpdir(), `kody-synth-${runId}`);
4756
- fs19.mkdirSync(path18.join(root, ".claude-plugin"), { recursive: true });
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 = path18.join(profile.dir, bucket, entry);
4759
- if (fs19.existsSync(local)) return local;
4760
- const shared = path18.resolve(profile.dir, "..", "..", "shared", bucket, entry);
4761
- if (fs19.existsSync(shared)) return shared;
4762
- const central = path18.join(catalog, bucket, entry);
4763
- if (fs19.existsSync(central)) return central;
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 (${path18.dirname(shared)}/), or catalog (${catalog}/${bucket}/)`
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 = path18.join(root, "skills");
4770
- fs19.mkdirSync(dst, { recursive: true });
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), path18.join(dst, name));
5167
+ copyDir(resolvePart("skills", name), path19.join(dst, name));
4773
5168
  }
4774
5169
  }
4775
5170
  if (cc.commands.length > 0) {
4776
- const dst = path18.join(root, "commands");
4777
- fs19.mkdirSync(dst, { recursive: true });
5171
+ const dst = path19.join(root, "commands");
5172
+ fs20.mkdirSync(dst, { recursive: true });
4778
5173
  for (const name of cc.commands) {
4779
- fs19.copyFileSync(resolvePart("commands", `${name}.md`), path18.join(dst, `${name}.md`));
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 = path18.join(root, "hooks");
4784
- fs19.mkdirSync(dst, { recursive: true });
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(fs19.readFileSync(src, "utf-8"));
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
- fs19.writeFileSync(path18.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
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
- fs19.writeFileSync(path18.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
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 fs20 from "fs";
4814
- import * as path19 from "path";
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 = path19.join(profileDir, "agents", `${name}.md`);
4828
- if (fs20.existsSync(local)) return local;
4829
- const shared = path19.resolve(profileDir, "..", "..", "shared", "agents", `${name}.md`);
4830
- if (fs20.existsSync(shared)) return shared;
4831
- const central = path19.join(getPluginsCatalogRoot(), "agents", `${name}.md`);
4832
- if (fs20.existsSync(central)) return central;
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] = fs20.readFileSync(resolveAgentFile2(profile.dir, name), "utf-8");
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] ?? fs20.readFileSync(resolveAgentFile2(profile.dir, name), "utf-8");
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 fs21 from "fs";
4880
- import * as path20 from "path";
5274
+ import * as fs22 from "fs";
5275
+ import * as path21 from "path";
4881
5276
  function loadProfile(profilePath) {
4882
- if (!fs21.existsSync(profilePath)) {
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(fs21.readFileSync(profilePath, "utf-8"));
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] ${path20.basename(path20.dirname(profilePath))}: unknown top-level keys ignored: ${unknownKeys.join(", ")}
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 (path20.resolve(refPath) === path20.resolve(profilePath)) {
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: path20.dirname(profilePath),
5008
- promptTemplates: readPromptTemplates(path20.dirname(profilePath))
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 (path20.basename(runtimePath) !== "runtime.json") return document;
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 = path20.dirname(runtimePath);
5048
- const implementation = readJsonObject(path20.join(implementationDir, "definition.json"), "Implementation definition");
5049
- const definitionsRoot2 = path20.dirname(path20.dirname(implementationDir));
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
- path20.join(definitionsRoot2, "capabilities", capabilityId, "definition.json"),
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(fs21.readFileSync(filePath, "utf-8"));
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] = fs21.readFileSync(p, "utf-8");
5507
+ out[p] = fs22.readFileSync(p, "utf-8");
5113
5508
  } catch {
5114
5509
  }
5115
5510
  };
5116
- read(path20.join(dir, "prompt.md"));
5117
- read(path20.join(dir, "capability.md"));
5118
- read(path20.join(dir, "capability.md"));
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 = path20.join(dir, "prompts");
5121
- for (const ent of fs21.readdirSync(promptsDir)) {
5122
- if (ent.endsWith(".md")) read(path20.join(promptsDir, ent));
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 fs22 from "fs";
5898
- import * as path21 from "path";
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 = path21.join(projectDir, rel);
5903
- if (!fs22.existsSync(abs)) continue;
6297
+ const abs = path22.join(projectDir, rel);
6298
+ if (!fs23.existsSync(abs)) continue;
5904
6299
  let content;
5905
6300
  try {
5906
- content = fs22.readFileSync(abs, "utf-8");
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 fs23 from "fs";
6142
- import * as path22 from "path";
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 = fs23.statSync(file);
6560
+ stat = fs24.statSync(file);
6166
6561
  } catch {
6167
6562
  return;
6168
6563
  }
6169
6564
  let raw;
6170
6565
  try {
6171
- raw = fs23.readFileSync(file, "utf-8");
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() ?? path22.basename(file, ".md");
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: path22.relative(memoryAbs, file),
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 = fs23.readdirSync(dir);
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 = path22.join(dir, name);
6648
+ const full = path23.join(dir, name);
6254
6649
  let stat;
6255
6650
  try {
6256
- stat = fs23.statSync(full);
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 = path22.join(ctx.cwd, MEMORY_DIR_RELATIVE);
6292
- if (!fs23.existsSync(memoryAbs)) {
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 fs24 from "fs";
6731
+ import * as fs25 from "fs";
6337
6732
  function getProfileInputsForChild(profileName, _cwd) {
6338
6733
  try {
6339
6734
  const profilePath = resolveProfilePath(profileName);
6340
- if (!fs24.existsSync(profilePath)) return null;
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 fs25 from "fs";
7199
+ import * as fs26 from "fs";
6805
7200
  import * as net from "net";
6806
7201
  import * as os4 from "os";
6807
- import * as path23 from "path";
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 (fs25.existsSync(imageScript)) return imageScript;
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 = path23.join(os4.tmpdir(), `kody-local-litellm-${Date.now()}.yaml`);
6918
- fs25.writeFileSync(configPath, generateLitellmConfigYaml(model));
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 = path23.join(os4.tmpdir(), `kody-local-litellm-${Date.now()}.log`);
6921
- const outFd = fs25.openSync(nextLogPath, "w");
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
- fs25.closeSync(outFd);
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 fs25.readFileSync(logPath, "utf-8").slice(-2e3);
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 = path23.join(projectDir, ".env");
7021
- if (!fs25.existsSync(dotenvPath)) return {};
7415
+ const dotenvPath = path24.join(projectDir, ".env");
7416
+ if (!fs26.existsSync(dotenvPath)) return {};
7022
7417
  const result = {};
7023
- for (const rawLine of fs25.readFileSync(dotenvPath, "utf-8").split("\n")) {
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 fs26 from "fs";
7676
- import * as path24 from "path";
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 = path24.join(cwd ?? process.cwd(), ".git");
7719
- if (!fs26.existsSync(gitDir)) return aborted;
7720
- if (fs26.existsSync(path24.join(gitDir, "MERGE_HEAD"))) {
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 (fs26.existsSync(path24.join(gitDir, "CHERRY_PICK_HEAD"))) {
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 (fs26.existsSync(path24.join(gitDir, "REVERT_HEAD"))) {
8121
+ if (fs27.existsSync(path25.join(gitDir, "REVERT_HEAD"))) {
7727
8122
  if (tryGit(["revert", "--abort"], cwd)) aborted.push("revert");
7728
8123
  }
7729
- if (fs26.existsSync(path24.join(gitDir, "rebase-merge")) || fs26.existsSync(path24.join(gitDir, "rebase-apply"))) {
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 = fs26.existsSync(path24.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
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 asRecord(value) {
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 : asRecord(raw.args);
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 = asRecord(value);
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 = asRecord(value);
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 = asRecord(value);
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 = asRecord(value);
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 : asRecord(raw.args);
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 = asRecord(extra.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 = asRecord(extra.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 fs27 from "fs";
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 (!fs27.existsSync(eventPath)) return null;
8769
- const parsed = JSON.parse(fs27.readFileSync(eventPath, "utf-8"));
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;
@@ -8795,783 +9190,388 @@ function diffStringArrays(before, after) {
8795
9190
  function recordValue3(value) {
8796
9191
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
8797
9192
  }
8798
- 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;
9193
+ function stringArrayValue(value) {
9194
+ return Array.isArray(value) && value.every((item) => typeof item === "string") ? [...value] : null;
9384
9195
  }
9385
- function formatWorkflowValidationIssues(issues) {
9386
- return issues.map((entry) => `${entry.path}: ${entry.message}`);
9196
+ function numberValue(value) {
9197
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
9387
9198
  }
9388
- function validateDataMatch(value, path53, issues, capabilityOutputs) {
9389
- if (value === void 0) return;
9390
- const match = asRecord2(value);
9391
- if (!match || Object.keys(match).length === 0) {
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
- for (const [field, expected] of Object.entries(match)) {
9396
- if (!SAFE_DATA_PATH.test(field)) {
9397
- issue(
9398
- issues,
9399
- "invalid_data_path",
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 transitionList(value) {
9418
- if (value === void 0) return [];
9419
- return Array.isArray(value) ? value : [value];
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 asRecord2(value) {
9422
- return value && typeof value === "object" && !Array.isArray(value) ? value : null;
9216
+ function stringValue2(value) {
9217
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
9423
9218
  }
9424
- function text(value) {
9425
- return typeof value === "string" && value.trim() ? value.trim() : void 0;
9219
+ function safePathSegment(value) {
9220
+ const safe = value.trim().replace(/[^A-Za-z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "");
9221
+ return safe || "unknown";
9426
9222
  }
9427
- function isComparable(value) {
9428
- if (value === null || ["string", "number", "boolean"].includes(typeof value)) return true;
9429
- return Array.isArray(value) && value.length > 0 && value.every((item) => isComparable(item) && !Array.isArray(item));
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 isJsonValue(value) {
9432
- if (value === null || ["string", "number", "boolean"].includes(typeof value)) return true;
9433
- if (Array.isArray(value)) return value.every(isJsonValue);
9434
- if (!value || typeof value !== "object") return false;
9435
- return Object.values(value).every(isJsonValue);
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 issue(issues, code, path53, message) {
9438
- issues.push({ code, path: path53, message });
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
- var SAFE_NAME, SAFE_STEP_ID, SAFE_DATA_PATH, SUPPORTED_STEP_FIELDS, SUPPORTED_TRANSITION_FIELDS;
9441
- var init_workflowValidation = __esm({
9442
- "src/workflowValidation.ts"() {
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
- SAFE_NAME = /^[a-z][a-z0-9-]*$/;
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/workflowDefinitions.ts
9273
+ // src/goal/targetLoopResolution.ts
9468
9274
  import * as fs29 from "fs";
9469
9275
  import * as path26 from "path";
9470
- function isWorkflowDefinitionId(value) {
9471
- return WORKFLOW_ID_PATTERN.test(value);
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 workflowDefinitionPath(id) {
9474
- if (!isWorkflowDefinitionId(id)) {
9475
- throw new Error(`Invalid workflow id "${id}"`);
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 normalizeWorkflowDefinition(value) {
9480
- if (!value || typeof value !== "object" || Array.isArray(value)) return null;
9481
- const raw = value;
9482
- const name = typeof raw.name === "string" ? raw.name.trim() : "";
9483
- const requestedAgent = typeof raw.agent === "string" ? raw.agent.trim() : "";
9484
- const agent = /^[a-z][a-z0-9-]*$/.test(requestedAgent) ? requestedAgent : "kody";
9485
- if (Array.isArray(raw.steps)) {
9486
- if (validateWorkflow({ steps: raw.steps, ...raw.startAt !== void 0 ? { startAt: raw.startAt } : {} }).length > 0) {
9487
- return null;
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
- const workflow = parseCapabilityWorkflow({
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 readWorkflowDefinition(_config, cwd, id) {
9509
- const root = cwd ?? process.cwd();
9510
- const relativePath = workflowDefinitionPath(id);
9511
- const candidates = [
9512
- path26.join(root, ".kody-engine", "runtime", relativePath),
9513
- path26.join(definitionsRoot(root), relativePath)
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
- return null;
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 workflowDefinitionToCapabilityFolder(id, workflow, source = workflowDefinitionPath(id)) {
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 normalizeWorkflowCapabilities(value) {
9541
- if (!Array.isArray(value)) return [];
9542
- const seen = /* @__PURE__ */ new Set();
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 !== "string") continue;
9546
- const slug = item.trim();
9547
- if (!CAPABILITY_ID_PATTERN.test(slug) || seen.has(slug)) continue;
9548
- seen.add(slug);
9549
- capabilities.push(slug);
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 capabilities;
9456
+ return route;
9552
9457
  }
9553
- function workflowDefinitionToConfig(workflow) {
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
- steps: workflow.steps ?? workflow.capabilities.map((capability) => ({ capability })),
9556
- ...workflow.startAt ? { startAt: workflow.startAt } : {}
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
- function parseWorkflowDefinition(content) {
9560
- try {
9561
- return normalizeWorkflowDefinition(JSON.parse(content));
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
- init_capabilityFolders();
9571
- init_definition_paths();
9572
- init_workflowValidation();
9573
- WORKFLOW_ID_PATTERN = /^[a-z0-9][a-z0-9_-]{0,79}$/;
9574
- CAPABILITY_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,79}$/;
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
 
@@ -23984,9 +23984,10 @@ init_config();
23984
23984
 
23985
23985
  // src/definition-hydration.ts
23986
23986
  init_state_backend();
23987
+ init_workflowDefinitions();
23987
23988
  import { createHash as createHash2 } from "crypto";
23988
- import * as fs16 from "fs";
23989
- import * as path17 from "path";
23989
+ import * as fs17 from "fs";
23990
+ import * as path18 from "path";
23990
23991
  var SLUG_RE = /^[a-z0-9][a-z0-9_-]{0,127}$/;
23991
23992
  var REPOSITORY_OWNED_NAMESPACES = ["loops"];
23992
23993
  function assertSafeDefinitionPath(filePath) {
@@ -24018,9 +24019,9 @@ function verifyDefinition(definition) {
24018
24019
  }
24019
24020
  function writeBundle(root, bundle) {
24020
24021
  for (const [filePath, contents] of Object.entries(bundle.files)) {
24021
- const target = path17.join(root, filePath);
24022
- fs16.mkdirSync(path17.dirname(target), { recursive: true });
24023
- fs16.writeFileSync(target, contents, "utf8");
24022
+ const target = path18.join(root, filePath);
24023
+ fs17.mkdirSync(path18.dirname(target), { recursive: true });
24024
+ fs17.writeFileSync(target, contents, "utf8");
24024
24025
  }
24025
24026
  }
24026
24027
  function writeDefinition(root, kind, definition) {
@@ -24028,46 +24029,59 @@ function writeDefinition(root, kind, definition) {
24028
24029
  if (kind === "agent") {
24029
24030
  const raw = bundle.files["agent.md"];
24030
24031
  if (typeof raw !== "string") throw new Error(`agent definition ${definition.slug} is missing agent.md`);
24031
- fs16.writeFileSync(path17.join(root, "agents", `${definition.slug}.md`), raw, "utf8");
24032
+ fs17.writeFileSync(path18.join(root, "agents", `${definition.slug}.md`), raw, "utf8");
24032
24033
  return;
24033
24034
  }
24034
24035
  if (kind === "goal") {
24035
- writeBundle(path17.join(root, "goals", definition.slug), bundle);
24036
+ writeBundle(path18.join(root, "goals", definition.slug), bundle);
24036
24037
  return;
24037
24038
  }
24038
24039
  if (kind === "implementation") {
24039
- writeBundle(path17.join(root, "implementations", definition.slug), bundle);
24040
+ writeBundle(path18.join(root, "implementations", definition.slug), bundle);
24040
24041
  return;
24041
24042
  }
24042
24043
  if (kind === "asset") {
24043
- writeBundle(path17.join(root, "shared"), bundle);
24044
+ writeBundle(path18.join(root, "shared"), bundle);
24044
24045
  return;
24045
24046
  }
24046
- writeBundle(path17.join(root, "capabilities", definition.slug), bundle);
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);
24047
24059
  }
24048
24060
  function preserveRepositoryDefinitions(root, staging) {
24049
24061
  for (const namespace of REPOSITORY_OWNED_NAMESPACES) {
24050
- const source = path17.join(root, namespace);
24051
- if (!fs16.existsSync(source)) continue;
24052
- fs16.cpSync(source, path17.join(staging, namespace), { recursive: true });
24062
+ const source = path18.join(root, namespace);
24063
+ if (!fs17.existsSync(source)) continue;
24064
+ fs17.cpSync(source, path18.join(staging, namespace), { recursive: true });
24053
24065
  }
24054
24066
  }
24055
24067
  async function hydrateDefinitions(options) {
24056
- const root = path17.join(options.cwd, ".kody-engine", "definitions");
24068
+ const root = path18.join(options.cwd, ".kody-engine", "definitions");
24057
24069
  const staging = `${root}.tmp-${process.pid}-${Date.now()}`;
24058
- fs16.rmSync(staging, { recursive: true, force: true });
24059
- fs16.mkdirSync(path17.join(staging, "agents"), { recursive: true });
24060
- fs16.mkdirSync(path17.join(staging, "capabilities"), { recursive: true });
24061
- fs16.mkdirSync(path17.join(staging, "goals"), { recursive: true });
24062
- fs16.mkdirSync(path17.join(staging, "implementations"), { recursive: true });
24063
- fs16.mkdirSync(path17.join(staging, "shared"), { recursive: true });
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 });
24064
24077
  try {
24065
- const [capabilities, agents, goals, implementations, assets] = await Promise.all([
24078
+ const [capabilities, agents, goals, implementations, assets, workflows] = await Promise.all([
24066
24079
  options.backend.listDefinitions(options.tenantId, "capability"),
24067
24080
  options.backend.listDefinitions(options.tenantId, "agent"),
24068
24081
  options.backend.listDefinitions(options.tenantId, "goal"),
24069
24082
  options.backend.listDefinitions(options.tenantId, "implementation"),
24070
- options.backend.listDefinitions(options.tenantId, "asset")
24083
+ options.backend.listDefinitions(options.tenantId, "asset"),
24084
+ options.backend.listWorkflows?.(options.tenantId) ?? Promise.resolve([])
24071
24085
  ]);
24072
24086
  const versions = {};
24073
24087
  for (const definition of capabilities) {
@@ -24090,6 +24104,9 @@ async function hydrateDefinitions(options) {
24090
24104
  writeDefinition(staging, "asset", definition);
24091
24105
  versions[`asset:${definition.slug}`] = definition.version;
24092
24106
  }
24107
+ for (const workflow of workflows) {
24108
+ versions[`workflow:${workflow.workflowId}`] = writeWorkflow(staging, workflow);
24109
+ }
24093
24110
  preserveRepositoryDefinitions(root, staging);
24094
24111
  const manifest = {
24095
24112
  schemaVersion: 1,
@@ -24097,13 +24114,13 @@ async function hydrateDefinitions(options) {
24097
24114
  hydratedAt: (/* @__PURE__ */ new Date()).toISOString(),
24098
24115
  versions: Object.fromEntries(Object.entries(versions).sort(([left], [right]) => left.localeCompare(right)))
24099
24116
  };
24100
- fs16.writeFileSync(path17.join(staging, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
24117
+ fs17.writeFileSync(path18.join(staging, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
24101
24118
  `, "utf8");
24102
- fs16.rmSync(root, { recursive: true, force: true });
24103
- fs16.renameSync(staging, root);
24119
+ fs17.rmSync(root, { recursive: true, force: true });
24120
+ fs17.renameSync(staging, root);
24104
24121
  return { root, tenantId: options.tenantId, versions: manifest.versions };
24105
24122
  } catch (error) {
24106
- fs16.rmSync(staging, { recursive: true, force: true });
24123
+ fs17.rmSync(staging, { recursive: true, force: true });
24107
24124
  throw error;
24108
24125
  }
24109
24126
  }
@@ -24255,7 +24272,7 @@ init_definition_paths();
24255
24272
 
24256
24273
  // src/dispatch.ts
24257
24274
  init_config();
24258
- import * as fs17 from "fs";
24275
+ import * as fs18 from "fs";
24259
24276
 
24260
24277
  // src/cron-match.ts
24261
24278
  var FIELD_BOUNDS = [
@@ -24362,10 +24379,10 @@ function autoDispatch(opts) {
24362
24379
  }
24363
24380
  const eventName = process.env.GITHUB_EVENT_NAME;
24364
24381
  const eventPath = process.env.GITHUB_EVENT_PATH;
24365
- if (!eventName || !eventPath || !fs17.existsSync(eventPath)) return null;
24382
+ if (!eventName || !eventPath || !fs18.existsSync(eventPath)) return null;
24366
24383
  let event = {};
24367
24384
  try {
24368
- event = JSON.parse(fs17.readFileSync(eventPath, "utf-8"));
24385
+ event = JSON.parse(fs18.readFileSync(eventPath, "utf-8"));
24369
24386
  } catch {
24370
24387
  return null;
24371
24388
  }
@@ -24489,7 +24506,7 @@ function autoDispatchTyped(opts) {
24489
24506
  if (legacy) return { kind: "route", ...legacy };
24490
24507
  const eventName = process.env.GITHUB_EVENT_NAME;
24491
24508
  const eventPath = process.env.GITHUB_EVENT_PATH;
24492
- if (!eventName || !eventPath || !fs17.existsSync(eventPath)) {
24509
+ if (!eventName || !eventPath || !fs18.existsSync(eventPath)) {
24493
24510
  return { kind: "silent", reason: "no GHA event context" };
24494
24511
  }
24495
24512
  if (eventName !== "issue_comment") {
@@ -24497,7 +24514,7 @@ function autoDispatchTyped(opts) {
24497
24514
  }
24498
24515
  let event = {};
24499
24516
  try {
24500
- event = JSON.parse(fs17.readFileSync(eventPath, "utf-8"));
24517
+ event = JSON.parse(fs18.readFileSync(eventPath, "utf-8"));
24501
24518
  } catch {
24502
24519
  return { kind: "silent", reason: "GHA event payload unreadable" };
24503
24520
  }
@@ -24551,7 +24568,7 @@ function dispatchScheduledWatches(opts) {
24551
24568
  for (const exe of listRuntimeProfilesForCwd(opts?.cwd ?? process.cwd())) {
24552
24569
  let raw;
24553
24570
  try {
24554
- raw = fs17.readFileSync(exe.profilePath, "utf-8");
24571
+ raw = fs18.readFileSync(exe.profilePath, "utf-8");
24555
24572
  } catch {
24556
24573
  continue;
24557
24574
  }