@kody-ade/kody-engine 0.4.429 → 0.4.431

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/kody.js CHANGED
@@ -15,7 +15,7 @@ var init_package = __esm({
15
15
  "package.json"() {
16
16
  package_default = {
17
17
  name: "@kody-ade/kody-engine",
18
- version: "0.4.429",
18
+ version: "0.4.431",
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",
@@ -52,8 +52,9 @@ var init_package = __esm({
52
52
  dependencies: {
53
53
  "@actions/cache": "^6.0.0",
54
54
  "@anthropic-ai/claude-agent-sdk": "0.2.119",
55
- "@kody-ade/agency-domain": "0.5.0",
55
+ "@kody-ade/agency-domain": "0.5.1",
56
56
  "@modelcontextprotocol/sdk": "^1.29.0",
57
+ ajv: "^8.18.0",
57
58
  convex: "^1.17.0",
58
59
  zod: "^4.0.0"
59
60
  },
@@ -255,6 +256,7 @@ function loadConfig(projectDir = process.cwd()) {
255
256
  ...parsePerImplementationReasoningEffort(agent.perImplementationReasoningEffort),
256
257
  ...parseAgentReasoningEffort(agent.reasoningEffort)
257
258
  },
259
+ execution: parseExecutionConfig(raw.execution),
258
260
  issueContext: parseIssueContext(raw.issueContext),
259
261
  testRequirements: parseTestRequirements(raw.testRequirements),
260
262
  defaultImplementation: typeof raw.defaultImplementation === "string" && raw.defaultImplementation.length > 0 ? raw.defaultImplementation : "run",
@@ -267,6 +269,18 @@ function loadConfig(projectDir = process.cwd()) {
267
269
  access: parseAccessConfig(raw.access)
268
270
  };
269
271
  }
272
+ function parseExecutionConfig(value) {
273
+ const execution = recordValue(value);
274
+ const bindings = recordValue(execution?.capabilityBindings);
275
+ if (!bindings) return void 0;
276
+ const capabilityBindings = {};
277
+ for (const [capabilityId, implementationId] of Object.entries(bindings)) {
278
+ if (/^[a-z][a-z0-9-]*$/.test(capabilityId) && typeof implementationId === "string" && /^[a-z][a-z0-9-]*$/.test(implementationId)) {
279
+ capabilityBindings[capabilityId] = implementationId;
280
+ }
281
+ }
282
+ return Object.keys(capabilityBindings).length > 0 ? { capabilityBindings } : void 0;
283
+ }
270
284
  function parseAccessConfig(raw) {
271
285
  if (raw === void 0 || raw === null) {
272
286
  return { allowedAssociations: [...DEFAULT_ALLOWED_ASSOCIATIONS] };
@@ -1631,10 +1645,71 @@ var init_issue = __esm({
1631
1645
  }
1632
1646
  });
1633
1647
 
1648
+ // src/agency/implementation-resolution.ts
1649
+ function resolveCapabilityImplementation(input) {
1650
+ const compatible = input.implementations.filter(
1651
+ (implementation) => implementation.capabilityRef.id === input.capabilityId && implementation.compatibleCapabilityRevision === input.capabilityRevision
1652
+ );
1653
+ if (input.explicitOverride) {
1654
+ if (!input.authorizeOverride?.(input.explicitOverride)) {
1655
+ throw new ImplementationResolutionError(`Implementation override "${input.explicitOverride}" is not authorized`);
1656
+ }
1657
+ return selectNamed(input.explicitOverride, input, compatible, "override");
1658
+ }
1659
+ if (input.repositoryBinding) {
1660
+ return selectNamed(input.repositoryBinding, input, compatible, "repository binding");
1661
+ }
1662
+ if (compatible.length === 1) return compatible[0];
1663
+ if (compatible.length === 0) {
1664
+ throw new ImplementationResolutionError(
1665
+ `No compatible Implementation is available for Capability "${input.capabilityId}" at revision "${input.capabilityRevision}"`
1666
+ );
1667
+ }
1668
+ throw new ImplementationResolutionError(
1669
+ `Capability "${input.capabilityId}" has ${compatible.length} compatible Implementations; configure a repository binding`
1670
+ );
1671
+ }
1672
+ function selectNamed(id, input, compatible, source) {
1673
+ const known = input.implementations.find((implementation) => implementation.id === id);
1674
+ if (!known) {
1675
+ throw new ImplementationResolutionError(`Implementation ${source} "${id}" is not available`);
1676
+ }
1677
+ const selected = compatible.find((implementation) => implementation.id === id);
1678
+ if (!selected) {
1679
+ throw new ImplementationResolutionError(
1680
+ `Implementation ${source} "${id}" is not compatible with Capability "${input.capabilityId}" at revision "${input.capabilityRevision}"`
1681
+ );
1682
+ }
1683
+ return selected;
1684
+ }
1685
+ var ImplementationResolutionError;
1686
+ var init_implementation_resolution = __esm({
1687
+ "src/agency/implementation-resolution.ts"() {
1688
+ "use strict";
1689
+ ImplementationResolutionError = class extends Error {
1690
+ constructor(message) {
1691
+ super(message);
1692
+ this.name = "ImplementationResolutionError";
1693
+ }
1694
+ };
1695
+ }
1696
+ });
1697
+
1634
1698
  // src/capabilityFolders.ts
1635
1699
  import * as fs4 from "fs";
1636
1700
  import * as path5 from "path";
1637
1701
  function capabilityOutputConditionPaths(config) {
1702
+ if (config.outputSchema) {
1703
+ const properties = isPlainObject(config.outputSchema.properties) ? config.outputSchema.properties : void 0;
1704
+ const factContract = isPlainObject(properties?.facts) ? properties.facts : void 0;
1705
+ const facts = isPlainObject(factContract?.properties) ? factContract.properties : void 0;
1706
+ return /* @__PURE__ */ new Set([
1707
+ ...properties?.status ? ["result.status"] : [],
1708
+ ...properties?.summary ? ["result.summary"] : [],
1709
+ ...properties?.resultClass ? ["result.resultClass"] : [],
1710
+ ...Object.keys(facts ?? {}).map((fact) => `result.facts.${fact}`)
1711
+ ]);
1712
+ }
1638
1713
  const result = config.output?.result;
1639
1714
  if (!result) return /* @__PURE__ */ new Set();
1640
1715
  return /* @__PURE__ */ new Set([
@@ -1655,16 +1730,19 @@ function listCapabilityFolderSlugs(absDir) {
1655
1730
  return entries.filter((e) => e.isDirectory() && !e.name.startsWith("_") && !e.name.startsWith(".")).filter((e) => isCapabilityFolder(path5.join(absDir, e.name))).map((e) => e.name).sort();
1656
1731
  }
1657
1732
  function isCapabilityFolder(dir) {
1658
- return fs4.existsSync(path5.join(dir, CAPABILITY_PROFILE_FILE)) && fs4.existsSync(path5.join(dir, CAPABILITY_BODY_FILE));
1733
+ return (fs4.existsSync(path5.join(dir, CAPABILITY_DEFINITION_FILE)) || fs4.existsSync(path5.join(dir, CAPABILITY_PROFILE_FILE))) && fs4.existsSync(path5.join(dir, CAPABILITY_BODY_FILE));
1659
1734
  }
1660
1735
  function readCapabilityFolder(root, slug) {
1661
1736
  const dir = path5.join(root, slug);
1662
- const profilePath = path5.join(dir, CAPABILITY_PROFILE_FILE);
1737
+ const definitionPath = path5.join(dir, CAPABILITY_DEFINITION_FILE);
1738
+ const legacyProfilePath = path5.join(dir, CAPABILITY_PROFILE_FILE);
1739
+ const profilePath = fs4.existsSync(definitionPath) ? definitionPath : legacyProfilePath;
1663
1740
  const bodyPath = path5.join(dir, CAPABILITY_BODY_FILE);
1664
1741
  if (!fs4.existsSync(profilePath) || !fs4.statSync(profilePath).isFile()) return null;
1665
1742
  if (!fs4.existsSync(bodyPath) || !fs4.statSync(bodyPath).isFile()) return null;
1666
1743
  try {
1667
- const rawProfile = JSON.parse(fs4.readFileSync(profilePath, "utf-8"));
1744
+ const rawDefinition = JSON.parse(fs4.readFileSync(profilePath, "utf-8"));
1745
+ const rawProfile = rawDefinition;
1668
1746
  const rawBody = fs4.readFileSync(bodyPath, "utf-8");
1669
1747
  const { title, body } = parseCapabilityBody(rawBody, slug);
1670
1748
  return {
@@ -1700,11 +1778,12 @@ function parseCapabilityConfig(raw) {
1700
1778
  capabilityToolMode: parseCapabilityToolMode(raw.capabilityToolMode),
1701
1779
  implementations,
1702
1780
  role: stringField(raw.role),
1703
- describe: stringField(raw.describe),
1781
+ describe: stringField(raw.describe) ?? stringField(raw.purpose),
1704
1782
  stage: stringField(raw.stage),
1705
1783
  readsFrom: stringList(raw.readsFrom ?? raw.reads_from),
1706
1784
  writesTo: stringList(raw.writesTo ?? raw.writes_to),
1707
1785
  output: parseCapabilityOutput(raw.output),
1786
+ outputSchema: isPlainObject(raw.outputSchema) ? raw.outputSchema : void 0,
1708
1787
  workflow: parseCapabilityWorkflow(raw.workflow)
1709
1788
  };
1710
1789
  }
@@ -1873,11 +1952,12 @@ function isSafeSlug(value) {
1873
1952
  function isSafeStepId(value) {
1874
1953
  return /^[A-Za-z][A-Za-z0-9_-]*$/.test(value) && !value.includes("..");
1875
1954
  }
1876
- var CAPABILITY_PROFILE_FILE, CAPABILITY_BODY_FILE;
1955
+ var CAPABILITY_PROFILE_FILE, CAPABILITY_DEFINITION_FILE, CAPABILITY_BODY_FILE;
1877
1956
  var init_capabilityFolders = __esm({
1878
1957
  "src/capabilityFolders.ts"() {
1879
1958
  "use strict";
1880
1959
  CAPABILITY_PROFILE_FILE = "profile.json";
1960
+ CAPABILITY_DEFINITION_FILE = "definition.json";
1881
1961
  CAPABILITY_BODY_FILE = "capability.md";
1882
1962
  }
1883
1963
  });
@@ -1903,6 +1983,9 @@ function hasExplicitDefinitionsRoot(cwd = process.cwd(), env = process.env) {
1903
1983
  function capabilitiesRoot(cwd = process.cwd()) {
1904
1984
  return path6.join(definitionsRoot(cwd), "capabilities");
1905
1985
  }
1986
+ function implementationsRoot(cwd = process.cwd()) {
1987
+ return path6.join(definitionsRoot(cwd), "implementations");
1988
+ }
1906
1989
  function agentsRoot(cwd = process.cwd()) {
1907
1990
  return path6.join(definitionsRoot(cwd), "agents");
1908
1991
  }
@@ -1913,6 +1996,7 @@ var init_definition_paths = __esm({
1913
1996
  });
1914
1997
 
1915
1998
  // src/registry.ts
1999
+ import { createHash as createHash2 } from "crypto";
1916
2000
  import * as fs6 from "fs";
1917
2001
  import * as path7 from "path";
1918
2002
  function getImplementationsRoot() {
@@ -1952,7 +2036,7 @@ function getImplementationRoots() {
1952
2036
  return getImplementationRootsForCwd(process.cwd());
1953
2037
  }
1954
2038
  function getImplementationRootsForCwd(cwd) {
1955
- return [capabilitiesRoot(cwd), getImplementationsRoot()];
2039
+ return [implementationsRoot(cwd), getImplementationsRoot()];
1956
2040
  }
1957
2041
  function getCapabilityRoots(projectCapabilitiesRoot = getProjectCapabilitiesRoot()) {
1958
2042
  return [projectCapabilitiesRoot, getBuiltinCapabilitiesRoot()];
@@ -1968,7 +2052,7 @@ function listImplementations(roots = getImplementationRoots()) {
1968
2052
  for (const ent of entries) {
1969
2053
  if (!ent.isDirectory()) continue;
1970
2054
  if (seen.has(ent.name)) continue;
1971
- const profilePath = path7.join(root, ent.name, CAPABILITY_PROFILE_FILE);
2055
+ const profilePath = implementationRuntimePath(root, ent.name);
1972
2056
  if (fs6.existsSync(profilePath) && fs6.statSync(profilePath).isFile() && isImplementationProfile(profilePath, requireImplementationProfile)) {
1973
2057
  out.push({ name: ent.name, profilePath });
1974
2058
  seen.add(ent.name);
@@ -1985,7 +2069,7 @@ function resolveImplementationCandidates(name, roots = getImplementationRoots())
1985
2069
  const rootList = typeof roots === "string" ? [roots] : roots;
1986
2070
  const out = [];
1987
2071
  for (const root of rootList) {
1988
- const profilePath = path7.join(root, name, "profile.json");
2072
+ const profilePath = implementationRuntimePath(root, name);
1989
2073
  if (fs6.existsSync(profilePath) && fs6.statSync(profilePath).isFile() && isImplementationProfile(profilePath, isCapabilityRoot(root))) {
1990
2074
  out.push(profilePath);
1991
2075
  }
@@ -2023,9 +2107,43 @@ function resolveCapabilityFolder(slug, projectCapabilitiesRoot = getProjectCapab
2023
2107
  function getCapabilityActionInputs(action, projectCapabilitiesRoot = getProjectCapabilitiesRoot()) {
2024
2108
  const resolved = resolveCapabilityAction(action, projectCapabilitiesRoot);
2025
2109
  if (!resolved) return null;
2110
+ const capability = resolveCapabilityFolder(resolved.capability, projectCapabilitiesRoot);
2111
+ if (capability && path7.basename(capability.profilePath) === "definition.json") {
2112
+ const schema = capability.rawProfile.inputSchema;
2113
+ if (!schema || typeof schema !== "object" || Array.isArray(schema)) return [];
2114
+ const properties = schema.properties;
2115
+ if (!properties || typeof properties !== "object" || Array.isArray(properties)) return [];
2116
+ const required2 = new Set(
2117
+ Array.isArray(schema.required) ? schema.required.filter((value) => typeof value === "string") : []
2118
+ );
2119
+ return Object.entries(properties).map(([name, value]) => {
2120
+ const property = value && typeof value === "object" && !Array.isArray(value) ? value : {};
2121
+ const type = property.type === "integer" ? "int" : property.type === "boolean" ? "bool" : Array.isArray(property.enum) ? "enum" : "string";
2122
+ return {
2123
+ name,
2124
+ flag: `--${name}`,
2125
+ type,
2126
+ required: required2.has(name),
2127
+ ...type === "enum" && Array.isArray(property.enum) ? { values: property.enum.filter((item) => typeof item === "string") } : {},
2128
+ describe: typeof property.description === "string" ? property.description : name
2129
+ };
2130
+ });
2131
+ }
2026
2132
  return getProfileInputs(resolved.implementation);
2027
2133
  }
2028
2134
  function resolveCapabilityExecution(capability, cwd = process.cwd()) {
2135
+ if (path7.basename(capability.profilePath) === "definition.json") {
2136
+ const implementations = readExternalImplementations(cwd);
2137
+ const definition = JSON.parse(fs6.readFileSync(capability.profilePath, "utf-8"));
2138
+ const capabilityRevision = createHash2("sha256").update(canonical(definition)).digest("hex");
2139
+ const selected = resolveCapabilityImplementation({
2140
+ capabilityId: capability.slug,
2141
+ capabilityRevision,
2142
+ implementations,
2143
+ repositoryBinding: repositoryImplementationBinding(capability.slug, cwd)
2144
+ });
2145
+ return { implementation: selected.id, cliArgs: {} };
2146
+ }
2029
2147
  const firstWorkflowStep = capability.config.workflow?.steps[0];
2030
2148
  if (firstWorkflowStep) {
2031
2149
  const implementation2 = firstWorkflowStep.implementation ?? firstWorkflowStep.capability;
@@ -2035,11 +2153,42 @@ function resolveCapabilityExecution(capability, cwd = process.cwd()) {
2035
2153
  const cliArgs = implementationDeclaresInput(implementation, "capability", cwd) ? { capability: capability.slug } : {};
2036
2154
  return { implementation, cliArgs };
2037
2155
  }
2156
+ function repositoryImplementationBinding(capabilityId, cwd) {
2157
+ try {
2158
+ return loadConfig(cwd).execution?.capabilityBindings[capabilityId];
2159
+ } catch {
2160
+ return void 0;
2161
+ }
2162
+ }
2163
+ function readExternalImplementations(cwd) {
2164
+ const root = implementationsRoot(cwd);
2165
+ if (!fs6.existsSync(root)) return [];
2166
+ const definitions = [];
2167
+ for (const entry of fs6.readdirSync(root, { withFileTypes: true })) {
2168
+ if (!entry.isDirectory() || !isSafeName(entry.name)) continue;
2169
+ const definitionPath = path7.join(root, entry.name, "definition.json");
2170
+ if (!fs6.existsSync(definitionPath)) continue;
2171
+ try {
2172
+ const definition = JSON.parse(fs6.readFileSync(definitionPath, "utf-8"));
2173
+ definitions.push(definition);
2174
+ } catch {
2175
+ }
2176
+ }
2177
+ return definitions;
2178
+ }
2179
+ function canonical(value) {
2180
+ if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
2181
+ if (value && typeof value === "object") {
2182
+ return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${canonical(item)}`).join(",")}}`;
2183
+ }
2184
+ return JSON.stringify(value);
2185
+ }
2038
2186
  function implementationDeclaresInput(implementation, inputName, cwd = process.cwd()) {
2039
2187
  const profilePath = resolveImplementation(implementation, getImplementationRootsForCwd(cwd));
2040
2188
  if (!profilePath) return false;
2041
2189
  try {
2042
- const raw = JSON.parse(fs6.readFileSync(profilePath, "utf-8"));
2190
+ const document = JSON.parse(fs6.readFileSync(profilePath, "utf-8"));
2191
+ const raw = document.config ?? document;
2043
2192
  if (!Array.isArray(raw.inputs)) return false;
2044
2193
  return raw.inputs.some((entry) => {
2045
2194
  if (!entry || typeof entry !== "object" || Array.isArray(entry)) return false;
@@ -2059,6 +2208,10 @@ function isCapabilityRoot(root) {
2059
2208
  const knownRoots = [getProjectCapabilitiesRoot(), getBuiltinCapabilitiesRoot()];
2060
2209
  return knownRoots.some((candidate) => candidate && path7.normalize(candidate) === normalized);
2061
2210
  }
2211
+ function implementationRuntimePath(root, name) {
2212
+ const runtimePath = path7.join(root, name, "runtime.json");
2213
+ return fs6.existsSync(runtimePath) ? runtimePath : path7.join(root, name, CAPABILITY_PROFILE_FILE);
2214
+ }
2062
2215
  function isImplementationProfile(profilePath, requireImplementationProfile) {
2063
2216
  if (!requireImplementationProfile) return true;
2064
2217
  try {
@@ -2126,8 +2279,10 @@ function getProfileInputs(name, roots = getImplementationRoots()) {
2126
2279
  const profilePath = resolveImplementation(name, roots);
2127
2280
  if (!profilePath) return null;
2128
2281
  try {
2129
- const raw = JSON.parse(fs6.readFileSync(profilePath, "utf-8"));
2130
- if (!raw || typeof raw !== "object" || !Array.isArray(raw.inputs)) return [];
2282
+ const document = JSON.parse(fs6.readFileSync(profilePath, "utf-8"));
2283
+ if (!document || typeof document !== "object") return [];
2284
+ const raw = "config" in document && document.config && typeof document.config === "object" ? document.config : document;
2285
+ if (!Array.isArray(raw.inputs)) return [];
2131
2286
  return raw.inputs;
2132
2287
  } catch {
2133
2288
  return null;
@@ -2162,7 +2317,9 @@ var PUBLIC_IMPLEMENTATION_ROLES;
2162
2317
  var init_registry = __esm({
2163
2318
  "src/registry.ts"() {
2164
2319
  "use strict";
2320
+ init_implementation_resolution();
2165
2321
  init_capabilityFolders();
2322
+ init_config();
2166
2323
  init_definition_paths();
2167
2324
  PUBLIC_IMPLEMENTATION_ROLES = /* @__PURE__ */ new Set(["primitive", "orchestrator", "container", "watch", "utility"]);
2168
2325
  }
@@ -2455,9 +2612,10 @@ function createStateBackendFromEnv(env = process.env, client) {
2455
2612
  });
2456
2613
  return Array.isArray(result) ? result : [];
2457
2614
  },
2458
- async getAgencyState(tenantId2, definitionId2) {
2615
+ async getAgencyState(tenantId2, kind, definitionId2) {
2459
2616
  const result = await transport.query(anyApi.agencyModel.getState, {
2460
2617
  tenantId: requireTenant(tenantId2),
2618
+ kind,
2461
2619
  definitionId: requireNonEmpty(definitionId2, "definitionId")
2462
2620
  });
2463
2621
  return result ?? null;
@@ -4165,6 +4323,40 @@ var init_agencyBoundaryEval = __esm({
4165
4323
  }
4166
4324
  });
4167
4325
 
4326
+ // src/agency/capability-contract-validation.ts
4327
+ import Ajv from "ajv";
4328
+ function validateCapabilityContractValue(boundary, schema, value) {
4329
+ const validate = validator.compile(schema);
4330
+ if (!validate(value)) {
4331
+ throw new CapabilityContractValidationError(boundary, validate.errors ?? []);
4332
+ }
4333
+ }
4334
+ var validator, CapabilityContractValidationError;
4335
+ var init_capability_contract_validation = __esm({
4336
+ "src/agency/capability-contract-validation.ts"() {
4337
+ "use strict";
4338
+ validator = new Ajv({
4339
+ allErrors: true,
4340
+ strict: true,
4341
+ validateFormats: false
4342
+ });
4343
+ CapabilityContractValidationError = class extends Error {
4344
+ constructor(boundary, errors) {
4345
+ super(
4346
+ `Capability ${boundary} does not match its canonical contract: ${validator.errorsText([...errors], {
4347
+ separator: "; "
4348
+ })}`
4349
+ );
4350
+ this.boundary = boundary;
4351
+ this.errors = errors;
4352
+ this.name = "CapabilityContractValidationError";
4353
+ }
4354
+ boundary;
4355
+ errors;
4356
+ };
4357
+ }
4358
+ });
4359
+
4168
4360
  // src/capabilityReport.ts
4169
4361
  function parseCapabilityReportsFromText(text2) {
4170
4362
  const reports = [];
@@ -4714,6 +4906,7 @@ var init_subagents = __esm({
4714
4906
  });
4715
4907
 
4716
4908
  // src/profile.ts
4909
+ import { createHash as createHash3 } from "crypto";
4717
4910
  import * as fs20 from "fs";
4718
4911
  import * as path19 from "path";
4719
4912
  function loadProfile(profilePath) {
@@ -4729,7 +4922,8 @@ function loadProfile(profilePath) {
4729
4922
  if (!raw || typeof raw !== "object") {
4730
4923
  throw new ProfileError(profilePath, "profile must be a JSON object");
4731
4924
  }
4732
- const r = raw;
4925
+ const document = raw;
4926
+ const r = compileRuntimeDocument(profilePath, document);
4733
4927
  const unknownKeys = Object.keys(r).filter((k) => !KNOWN_PROFILE_KEYS.has(k));
4734
4928
  if (unknownKeys.length > 0) {
4735
4929
  process.stderr.write(
@@ -4876,6 +5070,67 @@ function loadProfile(profilePath) {
4876
5070
  profile.subagentTemplates = captureSubagentTemplates(profile);
4877
5071
  return profile;
4878
5072
  }
5073
+ function compileRuntimeDocument(runtimePath, document) {
5074
+ if (path19.basename(runtimePath) !== "runtime.json") return document;
5075
+ if (document.adapter !== "kody-engine-profile") {
5076
+ throw new ProfileError(runtimePath, "unsupported runtime adapter document");
5077
+ }
5078
+ const implementationDir = path19.dirname(runtimePath);
5079
+ const implementation = readJsonObject(path19.join(implementationDir, "definition.json"), "Implementation definition");
5080
+ const definitionsRoot2 = path19.dirname(path19.dirname(implementationDir));
5081
+ const capabilityId = implementation.capabilityRef && typeof implementation.capabilityRef === "object" && !Array.isArray(implementation.capabilityRef) ? implementation.capabilityRef.id : void 0;
5082
+ if (typeof capabilityId !== "string" || !capabilityId) {
5083
+ throw new ProfileError(runtimePath, "Implementation capabilityRef is invalid");
5084
+ }
5085
+ const capability = readJsonObject(
5086
+ path19.join(definitionsRoot2, "capabilities", capabilityId, "definition.json"),
5087
+ "Capability definition"
5088
+ );
5089
+ const {
5090
+ adapter: _adapter,
5091
+ inputBindings: _inputBindings,
5092
+ outputBindings: _outputBindings,
5093
+ requirements: _requirements,
5094
+ config: nestedConfig,
5095
+ ...inlineConfig
5096
+ } = document;
5097
+ const config = nestedConfig && typeof nestedConfig === "object" && !Array.isArray(nestedConfig) ? nestedConfig : inlineConfig;
5098
+ const agentRef = implementation.agentRef && typeof implementation.agentRef === "object" && !Array.isArray(implementation.agentRef) ? implementation.agentRef.id : void 0;
5099
+ return {
5100
+ ...config,
5101
+ name: implementation.id,
5102
+ action: capability.action,
5103
+ describe: capability.purpose,
5104
+ inputs: config.inputs ?? [],
5105
+ agent: agentRef,
5106
+ canonicalContract: {
5107
+ capabilityId,
5108
+ capabilityRevision: createHash3("sha256").update(canonical2(capability)).digest("hex"),
5109
+ implementationId: String(implementation.id),
5110
+ implementationRevision: createHash3("sha256").update(canonical2(implementation)).digest("hex"),
5111
+ inputSchema: capability.inputSchema,
5112
+ outputSchema: capability.outputSchema
5113
+ }
5114
+ };
5115
+ }
5116
+ function canonical2(value) {
5117
+ if (Array.isArray(value)) return `[${value.map(canonical2).join(",")}]`;
5118
+ if (value && typeof value === "object") {
5119
+ return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${canonical2(item)}`).join(",")}}`;
5120
+ }
5121
+ return JSON.stringify(value);
5122
+ }
5123
+ function readJsonObject(filePath, label) {
5124
+ try {
5125
+ const value = JSON.parse(fs20.readFileSync(filePath, "utf-8"));
5126
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
5127
+ throw new Error("must be an object");
5128
+ }
5129
+ return value;
5130
+ } catch (error) {
5131
+ throw new ProfileError(filePath, `${label} is invalid: ${error instanceof Error ? error.message : String(error)}`);
5132
+ }
5133
+ }
4879
5134
  function parseCapabilityToolMode2(profilePath, raw) {
4880
5135
  if (raw === void 0 || raw === null || raw === "") return void 0;
4881
5136
  if (raw === "lock" || raw === "append") return raw;
@@ -5255,6 +5510,7 @@ var init_profile = __esm({
5255
5510
  VALID_CONTAINER_CHILD_TARGETS = /* @__PURE__ */ new Set(["issue", "pr"]);
5256
5511
  VALID_PHASES = /* @__PURE__ */ new Set(["research", "planning", "implementing", "reviewing", "shipped", "failed", "idle"]);
5257
5512
  KNOWN_PROFILE_KEYS = /* @__PURE__ */ new Set([
5513
+ "canonicalContract",
5258
5514
  "name",
5259
5515
  "action",
5260
5516
  "implementation",
@@ -6898,6 +7154,9 @@ function runIndexRowFromJobContext(input) {
6898
7154
  capability: stringValue(input.data.jobCapability) ?? void 0,
6899
7155
  workflow: workflow ?? void 0,
6900
7156
  implementation: stringValue(input.data.selectedImplementation) ?? input.profileName,
7157
+ parentRunId: stringValue(input.data.parentRunId) ?? void 0,
7158
+ capabilityRevision: stringValue(input.data.capabilityRevision) ?? void 0,
7159
+ implementationRevision: stringValue(input.data.implementationRevision) ?? void 0,
6901
7160
  agent: stringValue(input.data.jobAgent) ?? input.profile.agent ?? void 0,
6902
7161
  model: stringValue(input.data.jobModel) ?? void 0,
6903
7162
  modelProvider: stringValue(input.data.jobModelProvider) ?? void 0,
@@ -6962,7 +7221,7 @@ function statusFromExitCode(exitCode) {
6962
7221
  return exitCode === 0 ? "success" : "failed";
6963
7222
  }
6964
7223
  function isRunSubjectType(value) {
6965
- return value === "goal" || value === "loop" || value === "workflow";
7224
+ return value === "goal" || value === "loop" || value === "workflow" || value === "capability";
6966
7225
  }
6967
7226
  function stagedRunIndexRows(data) {
6968
7227
  const value = data[STAGED_RUN_INDEX_ROWS_KEY];
@@ -8471,9 +8730,9 @@ function goalInstanceTime(state) {
8471
8730
  return Number.isNaN(parsed) ? 0 : parsed;
8472
8731
  }
8473
8732
  function loadGoalTemplate(cwd, targetId) {
8474
- return readJsonObject(path24.join(cwd, ".kody-engine", "definitions", "goals", targetId, "state.json"));
8733
+ return readJsonObject2(path24.join(cwd, ".kody-engine", "definitions", "goals", targetId, "state.json"));
8475
8734
  }
8476
- function readJsonObject(filePath) {
8735
+ function readJsonObject2(filePath) {
8477
8736
  if (!fs27.existsSync(filePath)) return null;
8478
8737
  const parsed = JSON.parse(fs27.readFileSync(filePath, "utf8"));
8479
8738
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
@@ -9503,6 +9762,7 @@ async function planGoalCapabilitySchedule(opts) {
9503
9762
  capability2,
9504
9763
  slug,
9505
9764
  backend,
9765
+ opts.cwd,
9506
9766
  opts.previousScheduleState?.capabilities[slug]
9507
9767
  );
9508
9768
  statuses[slug] = status;
@@ -9557,21 +9817,19 @@ async function planGoalCapabilitySchedule(opts) {
9557
9817
  }
9558
9818
  };
9559
9819
  }
9560
- async function describeCapabilitySchedule(capability, slug, backend, previous) {
9820
+ async function describeCapabilitySchedule(capability, slug, backend, cwd, previous) {
9561
9821
  if (!capability) return { slug, state: "blocked", reason: "capability folder missing" };
9562
- const { config } = capability;
9563
- if (config.disabled === true) {
9822
+ if (capability.config.disabled === true) {
9564
9823
  return { slug, title: capability.title, state: "disabled", reason: "disabled" };
9565
9824
  }
9566
- if (!config.agent || config.agent.trim().length === 0) {
9567
- return { slug, title: capability.title, state: "blocked", reason: "no agent assigned" };
9568
- }
9569
- if (config.implementations && config.implementations.length > 1) {
9825
+ try {
9826
+ resolveCapabilityExecution(capability, cwd);
9827
+ } catch (error) {
9570
9828
  return {
9571
9829
  slug,
9572
9830
  title: capability.title,
9573
9831
  state: "blocked",
9574
- reason: "multi-implementation capability needs task-jobs route"
9832
+ reason: error instanceof Error ? error.message : "Implementation unavailable"
9575
9833
  };
9576
9834
  }
9577
9835
  let lastFiredAt = validIso(previous?.lastFiredAt) ? previous?.lastFiredAt : void 0;
@@ -13028,11 +13286,13 @@ import {
13028
13286
  createWorkflowDefinition,
13029
13287
  relationshipIssues
13030
13288
  } from "@kody-ade/agency-domain";
13031
- function goalProgressFromOutputs(definition, outputs) {
13289
+ function goalProgressFromOutputs(definition, revision, outputs) {
13032
13290
  const required2 = definition.objective.requiredEvidence;
13033
13291
  if (required2.length === 0) return 1;
13034
13292
  const satisfied = new Set(
13035
- outputs.filter((output) => output.kind === "evidence" && output.value === true).map((output) => output.key)
13293
+ outputs.filter(
13294
+ (output) => output.kind === "evidence" && output.value === true && output.parentRef?.kind === "goal" && output.parentRef.id === definition.id && output.parentRef.revision === revision
13295
+ ).map((output) => output.key)
13036
13296
  );
13037
13297
  return required2.filter((key) => satisfied.has(key)).length / required2.length;
13038
13298
  }
@@ -13123,7 +13383,11 @@ var init_agencyModelRepository = __esm({
13123
13383
  definition: record2.definition,
13124
13384
  revision: record2.revision,
13125
13385
  state: parseState(
13126
- await this.backend.getAgencyState(this.tenantId, record2.definition.id),
13386
+ await this.backend.getAgencyState(
13387
+ this.tenantId,
13388
+ record2.kind,
13389
+ record2.definition.id
13390
+ ),
13127
13391
  record2.definition,
13128
13392
  record2.kind
13129
13393
  )
@@ -13169,7 +13433,11 @@ var init_agencyModelRepository = __esm({
13169
13433
  const state = createGoalState({
13170
13434
  definitionId: record2.definition.id,
13171
13435
  lifecycle: previous?.lifecycle ?? "draft",
13172
- progress: goalProgressFromOutputs(record2.definition, await this.listOutputs()),
13436
+ progress: goalProgressFromOutputs(
13437
+ record2.definition,
13438
+ record2.revision,
13439
+ await this.listOutputs()
13440
+ ),
13173
13441
  blockers: previous?.blockers ?? [],
13174
13442
  updatedAt
13175
13443
  });
@@ -13187,8 +13455,7 @@ function decideTrigger(input) {
13187
13455
  return { kind: "skip", reason: `loop is ${input.state.lifecycle}` };
13188
13456
  }
13189
13457
  const trigger = input.definition.trigger;
13190
- if (trigger.type === "manual") {
13191
- if (!input.manualRequestId?.trim()) return { kind: "skip", reason: "manual trigger was not requested" };
13458
+ if (input.manualRequestId?.trim()) {
13192
13459
  return {
13193
13460
  kind: "fire",
13194
13461
  reason: "manual trigger was requested",
@@ -13196,6 +13463,9 @@ function decideTrigger(input) {
13196
13463
  idempotencyKey: `${input.definition.id}:manual:${input.manualRequestId.trim()}`
13197
13464
  };
13198
13465
  }
13466
+ if (trigger.type === "manual") {
13467
+ return { kind: "skip", reason: "manual trigger was not requested" };
13468
+ }
13199
13469
  if (trigger.type !== "schedule") {
13200
13470
  return { kind: "skip", reason: `${trigger.type} trigger is not enabled yet` };
13201
13471
  }
@@ -13229,7 +13499,7 @@ var init_triggerDispatcher = __esm({
13229
13499
  });
13230
13500
 
13231
13501
  // src/goal/policyResolver.ts
13232
- import { createHash as createHash2 } from "crypto";
13502
+ import { createHash as createHash4 } from "crypto";
13233
13503
  function resolveDispatchPolicy(input) {
13234
13504
  const operation = input.catalog.operations.get(input.owner.definition.operationId);
13235
13505
  if (!operation) throw new Error(`Dispatch blocked: Operation "${input.owner.definition.operationId}" is unresolved`);
@@ -13247,7 +13517,7 @@ function resolveDispatchPolicy(input) {
13247
13517
  const snapshotValue = { policy, constraints };
13248
13518
  return {
13249
13519
  snapshot: {
13250
- hash: createHash2("sha256").update(stableJson(snapshotValue)).digest("hex"),
13520
+ hash: createHash4("sha256").update(stableJson(snapshotValue)).digest("hex"),
13251
13521
  ...snapshotValue
13252
13522
  },
13253
13523
  operation,
@@ -13331,7 +13601,15 @@ async function dispatchAgencyLoopsWith(input) {
13331
13601
  );
13332
13602
  const results = [];
13333
13603
  for (const record2 of loops) {
13334
- const decision = decideTrigger({ definition: record2.definition, state: record2.state, now: input.now });
13604
+ if (input.manualRequest && record2.definition.id !== input.manualRequest.loopId) {
13605
+ continue;
13606
+ }
13607
+ const decision = decideTrigger({
13608
+ definition: record2.definition,
13609
+ state: record2.state,
13610
+ now: input.now,
13611
+ ...input.manualRequest ? { manualRequestId: input.manualRequest.requestId } : {}
13612
+ });
13335
13613
  const now = input.now.toISOString();
13336
13614
  if (decision.kind === "skip") {
13337
13615
  const key = `${record2.definition.id}:skip:${now}`;
@@ -13493,14 +13771,25 @@ async function dispatchAgencyLoopsWith(input) {
13493
13771
  const goalRecord = record2.definition.targetRef.kind === "goal" ? records.find(
13494
13772
  (candidate) => "executionRef" in candidate.definition && candidate.definition.id === record2.definition.targetRef.id
13495
13773
  ) : void 0;
13496
- if (succeeded && goalRecord && finalRunId) {
13774
+ if (succeeded && finalRunId) {
13497
13775
  await appendCapabilityOutputs(
13498
13776
  repository,
13499
13777
  finalRunId,
13500
13778
  target.reference,
13779
+ goalRecord ? {
13780
+ kind: "goal",
13781
+ id: goalRecord.definition.id,
13782
+ revision: goalRecord.revision
13783
+ } : {
13784
+ kind: "loop",
13785
+ id: record2.definition.id,
13786
+ revision: record2.revision
13787
+ },
13501
13788
  finalCapabilityResults,
13502
13789
  finishedAt
13503
13790
  );
13791
+ }
13792
+ if (succeeded && goalRecord) {
13504
13793
  await repository.refreshGoalProgress(goalRecord, finishedAt);
13505
13794
  }
13506
13795
  await input.backend.finishAgencyDispatch(
@@ -13581,7 +13870,7 @@ async function runAttempt(run, job, timeoutSeconds) {
13581
13870
  if (timer) clearTimeout(timer);
13582
13871
  }
13583
13872
  }
13584
- async function appendCapabilityOutputs(repository, runId, producer, results, createdAt) {
13873
+ async function appendCapabilityOutputs(repository, runId, producer, parentRef, results, createdAt) {
13585
13874
  for (const result of results) {
13586
13875
  const outputs = [
13587
13876
  ...Object.entries(result.facts).map(([key, value]) => ({ kind: "fact", key, value })),
@@ -13601,6 +13890,7 @@ async function appendCapabilityOutputs(repository, runId, producer, results, cre
13601
13890
  ...output,
13602
13891
  runId,
13603
13892
  producer: { kind: producer.kind, id: producer.id },
13893
+ parentRef,
13604
13894
  contract: "capability-result/v1",
13605
13895
  createdAt
13606
13896
  });
@@ -13632,10 +13922,17 @@ var init_dispatchAgencyLoops = __esm({
13632
13922
  const tenantId2 = repositoryTenant(ctx.config);
13633
13923
  if (!tenantId2) throw new Error("Repository identity is required for Agency Loop dispatch");
13634
13924
  const backend = createStateBackendFromEnv();
13925
+ const requestedLoopId = typeof ctx.args.loop === "string" ? ctx.args.loop.trim() : "";
13635
13926
  const results = await dispatchAgencyLoopsWith({
13636
13927
  tenantId: tenantId2,
13637
13928
  backend,
13638
13929
  now: /* @__PURE__ */ new Date(),
13930
+ ...requestedLoopId ? {
13931
+ manualRequest: {
13932
+ loopId: requestedLoopId,
13933
+ requestId: process.env.GITHUB_RUN_ID?.trim() || `local-${randomUUID()}`
13934
+ }
13935
+ } : {},
13639
13936
  run: (job, abortController) => runJob(job, {
13640
13937
  cwd: ctx.cwd,
13641
13938
  config: ctx.config,
@@ -15491,9 +15788,9 @@ var init_kodyVariables = __esm({
15491
15788
  });
15492
15789
 
15493
15790
  // src/backendVault.ts
15494
- import { createDecipheriv, createHash as createHash3 } from "crypto";
15791
+ import { createDecipheriv, createHash as createHash5 } from "crypto";
15495
15792
  function cacheKey(owner, repo, masterKey) {
15496
- const keyHash = createHash3("sha256").update(masterKey).digest("hex").slice(0, 16);
15793
+ const keyHash = createHash5("sha256").update(masterKey).digest("hex").slice(0, 16);
15497
15794
  return `${owner}/${repo}:${keyHash}`.toLowerCase();
15498
15795
  }
15499
15796
  function decryptVault(payload, masterKey) {
@@ -16142,7 +16439,7 @@ var init_notifyTerminal = __esm({
16142
16439
  });
16143
16440
 
16144
16441
  // src/scripts/openAgencyModelReviewPr.ts
16145
- import { createHash as createHash4 } from "crypto";
16442
+ import { createHash as createHash6 } from "crypto";
16146
16443
  function parseAgencyModelProposal(raw) {
16147
16444
  const text2 = raw.trim();
16148
16445
  const jsonText = (text2.match(/^```(?:json)?\s*\n([\s\S]*?)\n```$/i)?.[1] ?? text2).trim();
@@ -16194,7 +16491,7 @@ function normalizeBundleFiles(bundle) {
16194
16491
  });
16195
16492
  }
16196
16493
  function buildProposalId(issueNumber, bundle, sourceLabel) {
16197
- const digest = createHash4("sha256").update(JSON.stringify({ issueNumber, sourceLabel, title: bundle.title, files: normalizeBundleFiles(bundle) })).digest("hex").slice(0, 16);
16494
+ const digest = createHash6("sha256").update(JSON.stringify({ issueNumber, sourceLabel, title: bundle.title, files: normalizeBundleFiles(bundle) })).digest("hex").slice(0, 16);
16198
16495
  return `issue-${issueNumber}-${digest}`;
16199
16496
  }
16200
16497
  function isDryRun(ctx) {
@@ -18266,9 +18563,9 @@ var init_runFlow = __esm({
18266
18563
  });
18267
18564
 
18268
18565
  // src/scripts/previewBuildHelpers.ts
18269
- import { createDecipheriv as createDecipheriv2, createHash as createHash5, hkdfSync as hkdfSync2 } from "crypto";
18566
+ import { createDecipheriv as createDecipheriv2, createHash as createHash7, hkdfSync as hkdfSync2 } from "crypto";
18270
18567
  function shortHash(s) {
18271
- return createHash5("sha256").update(s).digest("hex").slice(0, 6);
18568
+ return createHash7("sha256").update(s).digest("hex").slice(0, 6);
18272
18569
  }
18273
18570
  function previewAppName(repo, pr) {
18274
18571
  const [owner, name] = repo.split("/");
@@ -18301,7 +18598,7 @@ function formatPreviewComment(args) {
18301
18598
  ].join("\n");
18302
18599
  }
18303
18600
  function defaultImageTag(repo, ref) {
18304
- return createHash5("sha256").update(`${repo}@${ref}`).digest("hex").slice(0, 12);
18601
+ return createHash7("sha256").update(`${repo}@${ref}`).digest("hex").slice(0, 12);
18305
18602
  }
18306
18603
  var init_previewBuildHelpers = __esm({
18307
18604
  "src/scripts/previewBuildHelpers.ts"() {
@@ -20707,6 +21004,9 @@ async function runImplementation(profileName, input) {
20707
21004
  let args;
20708
21005
  try {
20709
21006
  args = validateInputs(profile.inputs, input.cliArgs);
21007
+ if (profile.canonicalContract) {
21008
+ validateCapabilityContractValue("input", profile.canonicalContract.inputSchema, args);
21009
+ }
20710
21010
  } catch (err) {
20711
21011
  return finishAndEnd({ exitCode: 64, reason: err instanceof Error ? err.message : String(err) });
20712
21012
  }
@@ -20767,6 +21067,12 @@ async function runImplementation(profileName, input) {
20767
21067
  ctx.data.jobModelProvider = model.provider;
20768
21068
  ctx.data.jobModelName = model.model;
20769
21069
  if (reasoningEffort) ctx.data.jobReasoningEffort = reasoningEffort;
21070
+ if (profile.canonicalContract) {
21071
+ ctx.data.jobCapability = profile.canonicalContract.capabilityId;
21072
+ ctx.data.selectedImplementation = profile.canonicalContract.implementationId;
21073
+ ctx.data.capabilityRevision = profile.canonicalContract.capabilityRevision;
21074
+ ctx.data.implementationRevision = profile.canonicalContract.implementationRevision;
21075
+ }
20770
21076
  const runIndexStartedAt = new Date(stageStartedAt).toISOString();
20771
21077
  if (!input.skipConfig) {
20772
21078
  await upsertRunIndexRowBestEffortAsync(
@@ -21076,6 +21382,25 @@ async function runImplementation(profileName, input) {
21076
21382
  outcome: postOutcome
21077
21383
  });
21078
21384
  }
21385
+ const capabilityResults = Array.isArray(ctx.data.capabilityResults) ? ctx.data.capabilityResults : void 0;
21386
+ if (profile.canonicalContract) {
21387
+ try {
21388
+ validateCapabilityContractValue(
21389
+ "output",
21390
+ profile.canonicalContract.outputSchema,
21391
+ capabilityResults?.at(-1) ?? {
21392
+ exitCode: ctx.output.exitCode ?? 0,
21393
+ reason: ctx.output.reason,
21394
+ prUrl: ctx.output.prUrl
21395
+ }
21396
+ );
21397
+ } catch (error) {
21398
+ return finishAndEnd({
21399
+ exitCode: 99,
21400
+ reason: error instanceof Error ? error.message : String(error)
21401
+ });
21402
+ }
21403
+ }
21079
21404
  return finishAndEnd({
21080
21405
  exitCode: ctx.output.exitCode ?? 0,
21081
21406
  prUrl: ctx.output.prUrl,
@@ -21084,7 +21409,7 @@ async function runImplementation(profileName, input) {
21084
21409
  nextJob: ctx.output.nextJob,
21085
21410
  afterNextJob: ctx.output.afterNextJob,
21086
21411
  taskState: ctx.data.taskState,
21087
- capabilityResults: Array.isArray(ctx.data.capabilityResults) ? ctx.data.capabilityResults : void 0
21412
+ capabilityResults
21088
21413
  });
21089
21414
  } catch (err) {
21090
21415
  const msg = err instanceof Error ? err.message : String(err);
@@ -21288,10 +21613,7 @@ function resolveProfilePath(profileName, cwd = process.cwd()) {
21288
21613
  return candidates[0];
21289
21614
  }
21290
21615
  function loadRunnableProfile(profileName, cwd) {
21291
- const candidates = resolveImplementationCandidates(
21292
- profileName,
21293
- getImplementationRootsForCwd(cwd)
21294
- );
21616
+ const candidates = resolveImplementationCandidates(profileName, getImplementationRootsForCwd(cwd));
21295
21617
  const skipped = [];
21296
21618
  for (const profilePath2 of candidates) {
21297
21619
  const profile2 = loadProfile(profilePath2);
@@ -21543,6 +21865,7 @@ var MUTATING_POSTFLIGHTS, SHELL_MARKER_RE, TASK_ARTIFACT_WRITE_TOOLS, MAX_CHAIN_
21543
21865
  var init_executor = __esm({
21544
21866
  "src/executor.ts"() {
21545
21867
  "use strict";
21868
+ init_capability_contract_validation();
21546
21869
  init_agent();
21547
21870
  init_agents();
21548
21871
  init_capabilityReport();
@@ -21746,7 +22069,52 @@ async function runJob(job, base) {
21746
22069
  ...valid.workflowState ?? persistedState ? { workflowState: valid.workflowState ?? persistedState ?? void 0 } : {}
21747
22070
  };
21748
22071
  const checkpoint = valid.workflowRunId && workflowIdentity && base.config ? (state) => writeWorkflowRunState(base.config, base.cwd, workflowIdentity, valid.workflowRunId, state) : void 0;
21749
- const result = await runCapabilityWorkflow(workflowJob, workflow, workflowCapability, base, checkpoint);
22072
+ const parentRunId = `workflow:${workflowIdentity}:${valid.workflowRunId ?? newJobId(valid.flavor)}`;
22073
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
22074
+ const parentRow = {
22075
+ version: 1,
22076
+ id: parentRunId,
22077
+ subjectType: "workflow",
22078
+ subjectId: workflowIdentity,
22079
+ subjectLabel: workflowCapability.title,
22080
+ status: "running",
22081
+ title: workflowCapability.title,
22082
+ startedAt,
22083
+ updatedAt: startedAt,
22084
+ workflow: workflowIdentity,
22085
+ kodyRunId: valid.workflowRunId,
22086
+ sourceType: "job"
22087
+ };
22088
+ const persistRun = Boolean(base.config && !base.skipConfig && hasStateBackendConfig());
22089
+ if (base.config && persistRun) {
22090
+ await upsertRunIndexRowBestEffortAsync(base.config, base.cwd, parentRow);
22091
+ }
22092
+ const workflowBase = {
22093
+ ...base,
22094
+ preloadedData: { ...base.preloadedData ?? {}, parentRunId }
22095
+ };
22096
+ let result;
22097
+ try {
22098
+ result = await runCapabilityWorkflow(workflowJob, workflow, workflowCapability, workflowBase, checkpoint);
22099
+ } catch (error) {
22100
+ if (base.config && persistRun) {
22101
+ await upsertRunIndexRowBestEffortAsync(base.config, base.cwd, {
22102
+ ...parentRow,
22103
+ status: "failed",
22104
+ summary: error instanceof Error ? error.message : String(error),
22105
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
22106
+ });
22107
+ }
22108
+ throw error;
22109
+ }
22110
+ if (base.config && persistRun) {
22111
+ await upsertRunIndexRowBestEffortAsync(base.config, base.cwd, {
22112
+ ...parentRow,
22113
+ status: result.exitCode === 0 ? "success" : "failed",
22114
+ summary: result.reason,
22115
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
22116
+ });
22117
+ }
21750
22118
  if (valid.workflowRunId && workflowIdentity && base.config && result.workflowState) {
21751
22119
  await writeWorkflowRunState(base.config, base.cwd, workflowIdentity, valid.workflowRunId, result.workflowState);
21752
22120
  }
@@ -21886,6 +22254,9 @@ async function runLinearCapabilityWorkflow(parent, workflow, capability, base) {
21886
22254
  ...base,
21887
22255
  preloadedData: {
21888
22256
  ...chainData,
22257
+ runSubjectType: "capability",
22258
+ runSubjectId: step.capability,
22259
+ runSubjectLabel: label,
21889
22260
  workflowStep: label,
21890
22261
  workflowStepIndex: index + 1,
21891
22262
  workflowStepReason: step.reason,
@@ -22030,6 +22401,9 @@ async function runGraphCapabilityWorkflow(parent, workflow, capability, base, ch
22030
22401
  ...base,
22031
22402
  preloadedData: {
22032
22403
  ...chainData,
22404
+ runSubjectType: "capability",
22405
+ runSubjectId: step.capability,
22406
+ runSubjectLabel: step.id,
22033
22407
  workflowStep: step.id,
22034
22408
  workflowStepIndex: index + 1,
22035
22409
  workflowStepReason: step.reason,
@@ -22283,7 +22657,7 @@ function hydratedCapabilitiesRoot(cwd) {
22283
22657
  return path46.join(cwd, ".kody-engine", "definitions", "capabilities");
22284
22658
  }
22285
22659
  function loadWorkflowContext(slug, base) {
22286
- if (!slug || !base.config || !isWorkflowDefinitionId(slug)) return null;
22660
+ if (!slug || !isWorkflowDefinitionId(slug)) return null;
22287
22661
  const workflow = readWorkflowDefinition(base.config, base.cwd, slug);
22288
22662
  return workflow ? workflowDefinitionToCapabilityFolder(slug, workflow) : null;
22289
22663
  }
@@ -22319,6 +22693,8 @@ var init_job = __esm({
22319
22693
  init_capabilityFolders();
22320
22694
  init_executor();
22321
22695
  init_registry();
22696
+ init_runIndex();
22697
+ init_state_backend();
22322
22698
  init_workflowDefinitions();
22323
22699
  init_workflowRunState();
22324
22700
  init_workflowValidation();
@@ -24494,8 +24870,16 @@ async function runCi(argv) {
24494
24870
  const noTarget = !sessionInput && !(Number.isFinite(issueInput) && issueInput > 0);
24495
24871
  if (noTarget && capabilityInput) {
24496
24872
  forceRunAction = capabilityInput;
24497
- if (capabilityInput === "goal-manager" && messageInput) {
24498
- forceRunCliArgs = { goal: messageInput };
24873
+ if (messageInput) {
24874
+ const route = resolveCapabilityAction(capabilityInput);
24875
+ const textInputs = route?.implementation ? (getProfileInputs(route.implementation) ?? []).filter(
24876
+ (input) => input.type === "string"
24877
+ ) : [];
24878
+ if (textInputs.length === 1) {
24879
+ forceRunCliArgs = { [textInputs[0].name]: messageInput };
24880
+ } else if (capabilityInput === "goal-manager") {
24881
+ forceRunCliArgs = { goal: messageInput };
24882
+ }
24499
24883
  }
24500
24884
  } else {
24501
24885
  manualWorkflowDispatch = noTarget;
@@ -26336,7 +26720,7 @@ init_definition_paths();
26336
26720
 
26337
26721
  // src/definition-hydration.ts
26338
26722
  init_state_backend();
26339
- import { createHash as createHash6 } from "crypto";
26723
+ import { createHash as createHash8 } from "crypto";
26340
26724
  import * as fs50 from "fs";
26341
26725
  import * as path51 from "path";
26342
26726
  var SLUG_RE2 = /^[a-z0-9][a-z0-9_-]{0,63}$/;
@@ -26357,7 +26741,7 @@ function normalizeDefinitionBundle(bundle) {
26357
26741
  return { schemaVersion: 1, files };
26358
26742
  }
26359
26743
  function definitionVersion(bundle) {
26360
- return `sha256:${createHash6("sha256").update(JSON.stringify(normalizeDefinitionBundle(bundle))).digest("hex")}`;
26744
+ return `sha256:${createHash8("sha256").update(JSON.stringify(normalizeDefinitionBundle(bundle))).digest("hex")}`;
26361
26745
  }
26362
26746
  function verifyDefinition(definition) {
26363
26747
  if (!SLUG_RE2.test(definition.slug)) throw new Error(`invalid definition slug: ${definition.slug}`);
@@ -0,0 +1,42 @@
1
+ {
2
+ "id": "run",
3
+ "action": "run",
4
+ "purpose": "Implement one GitHub issue end-to-end.",
5
+ "inputSchema": {
6
+ "type": "object",
7
+ "properties": {
8
+ "issue": {
9
+ "type": "integer",
10
+ "description": "GitHub issue number to implement."
11
+ },
12
+ "base": {
13
+ "type": "string",
14
+ "description": "Optional safe base branch override for manual branch targeting (see resolveBaseOverride in runFlow.ts)."
15
+ }
16
+ },
17
+ "required": [
18
+ "issue"
19
+ ],
20
+ "additionalProperties": false
21
+ },
22
+ "outputSchema": {
23
+ "type": "object",
24
+ "properties": {
25
+ "reason": {
26
+ "type": "string"
27
+ },
28
+ "summary": {
29
+ "type": "string"
30
+ },
31
+ "data": {
32
+ "type": "object",
33
+ "additionalProperties": true
34
+ }
35
+ },
36
+ "additionalProperties": true
37
+ },
38
+ "effects": [],
39
+ "permissions": [],
40
+ "success": "The requested issue is implemented and the canonical result is returned.",
41
+ "failure": "The issue cannot be implemented safely or no valid canonical result is returned."
42
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "id": "run",
3
+ "capabilityRef": {
4
+ "kind": "capability",
5
+ "id": "run"
6
+ },
7
+ "compatibleCapabilityRevision": "c50fdcd64daedbcde4428b2985a68a8a11ece518f00a0f5a0c0a2c9d38b5e9c1",
8
+ "type": "agent",
9
+ "agentRef": {
10
+ "kind": "agent",
11
+ "id": "kody"
12
+ }
13
+ }
@@ -1,7 +1,6 @@
1
1
  {
2
- "name": "run",
2
+ "adapter": "kody-engine-profile",
3
3
  "role": "primitive",
4
- "describe": "Implement a GitHub issue end-to-end: branch, code, commit, open PR.",
5
4
  "inputs": [
6
5
  {
7
6
  "name": "issue",
@@ -40,6 +40,14 @@ export interface AuthSpec {
40
40
  }
41
41
 
42
42
  export interface Profile {
43
+ canonicalContract?: {
44
+ capabilityId: string
45
+ capabilityRevision: string
46
+ implementationId: string
47
+ implementationRevision: string
48
+ inputSchema: Record<string, unknown>
49
+ outputSchema: Record<string, unknown>
50
+ }
43
51
  name: string
44
52
  /**
45
53
  * Public action name owned by a capability. A user may type `@kody <action>`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.429",
3
+ "version": "0.4.431",
4
4
  "description": "kody — autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative implementation profiles.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -12,33 +12,12 @@
12
12
  "templates",
13
13
  "kody.config.schema.json"
14
14
  ],
15
- "scripts": {
16
- "kody:run": "tsx bin/kody.ts",
17
- "serve": "tsx bin/kody.ts serve",
18
- "serve:vscode": "tsx bin/kody.ts serve vscode",
19
- "serve:claude": "tsx bin/kody.ts serve claude",
20
- "clean:dist": "node scripts/clean-dist.cjs",
21
- "build": "pnpm clean:dist && tsup && node scripts/copy-assets.cjs",
22
- "check:modularity": "tsx scripts/check-script-modularity.ts",
23
- "pretest": "pnpm check:modularity",
24
- "test": "vitest run tests/unit tests/int --coverage",
25
- "posttest": "tsx scripts/check-coverage-floor.ts",
26
- "test:smoke": "vitest run tests/smoke --no-coverage",
27
- "test:e2e": "vitest run tests/e2e --no-coverage",
28
- "test:all": "vitest run tests --no-coverage",
29
- "typecheck": "tsc --noEmit",
30
- "lint": "biome check",
31
- "lint:fix": "biome check --write",
32
- "format": "biome format --write",
33
- "verify:package": "node scripts/verify-package-tarball.cjs",
34
- "brain:publish": "docker buildx build --platform linux/amd64 -f runner/Dockerfile.brain -t ghcr.io/${KODY_BRAIN_GHCR_OWNER:-aharonyaircohen}/kody-brain:latest --push runner",
35
- "prepublishOnly": "pnpm typecheck && vitest run tests/unit tests/int --no-coverage && pnpm build && pnpm verify:package"
36
- },
37
15
  "dependencies": {
38
16
  "@actions/cache": "^6.0.0",
39
17
  "@anthropic-ai/claude-agent-sdk": "0.2.119",
40
- "@kody-ade/agency-domain": "0.5.0",
18
+ "@kody-ade/agency-domain": "0.5.1",
41
19
  "@modelcontextprotocol/sdk": "^1.29.0",
20
+ "ajv": "^8.18.0",
42
21
  "convex": "^1.17.0",
43
22
  "zod": "^4.0.0"
44
23
  },
@@ -59,5 +38,26 @@
59
38
  "url": "git+https://github.com/aharonyaircohen/kody-engine.git"
60
39
  },
61
40
  "homepage": "https://github.com/aharonyaircohen/kody-engine",
62
- "bugs": "https://github.com/aharonyaircohen/kody-engine/issues"
63
- }
41
+ "bugs": "https://github.com/aharonyaircohen/kody-engine/issues",
42
+ "scripts": {
43
+ "kody:run": "tsx bin/kody.ts",
44
+ "serve": "tsx bin/kody.ts serve",
45
+ "serve:vscode": "tsx bin/kody.ts serve vscode",
46
+ "serve:claude": "tsx bin/kody.ts serve claude",
47
+ "clean:dist": "node scripts/clean-dist.cjs",
48
+ "build": "pnpm clean:dist && tsup && node scripts/copy-assets.cjs",
49
+ "check:modularity": "tsx scripts/check-script-modularity.ts",
50
+ "pretest": "pnpm check:modularity",
51
+ "test": "vitest run tests/unit tests/int --coverage",
52
+ "posttest": "tsx scripts/check-coverage-floor.ts",
53
+ "test:smoke": "vitest run tests/smoke --no-coverage",
54
+ "test:e2e": "vitest run tests/e2e --no-coverage",
55
+ "test:all": "vitest run tests --no-coverage",
56
+ "typecheck": "tsc --noEmit",
57
+ "lint": "biome check",
58
+ "lint:fix": "biome check --write",
59
+ "format": "biome format --write",
60
+ "verify:package": "node scripts/verify-package-tarball.cjs",
61
+ "brain:publish": "docker buildx build --platform linux/amd64 -f runner/Dockerfile.brain -t ghcr.io/${KODY_BRAIN_GHCR_OWNER:-aharonyaircohen}/kody-brain:latest --push runner"
62
+ }
63
+ }
@@ -1,6 +0,0 @@
1
- {
2
- "name": "run",
3
- "action": "run",
4
- "implementation": "run",
5
- "describe": "Implement a GitHub issue end-to-end."
6
- }