@kody-ade/kody-engine 0.4.547 → 0.4.549

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.547",
18
+ version: "0.4.549",
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",
@@ -2093,11 +2093,13 @@ function parseWorkflowStep(value) {
2093
2093
  const delivery = stringField(raw.delivery);
2094
2094
  const targetFact = stringField(raw.targetFact ?? raw.target_fact);
2095
2095
  const hasInput = Object.hasOwn(raw, "input");
2096
+ const inputs = parseWorkflowInputBindings(raw.inputs);
2096
2097
  const next = parseWorkflowTransitions(raw.next);
2097
2098
  const report = parseReportPublication(raw.report);
2098
2099
  return {
2099
2100
  capability,
2100
2101
  ...hasInput ? { input: raw.input } : {},
2102
+ ...inputs ? { inputs } : {},
2101
2103
  ...id && isSafeStepId(id) ? { id } : {},
2102
2104
  ...action && isSafeSlug(action) ? { action } : {},
2103
2105
  ...evidence ? { evidence } : {},
@@ -2112,6 +2114,15 @@ function parseWorkflowStep(value) {
2112
2114
  ...report ? { report } : {}
2113
2115
  };
2114
2116
  }
2117
+ function parseWorkflowInputBindings(value) {
2118
+ if (!isPlainObject(value)) return void 0;
2119
+ const entries = Object.entries(value).flatMap(([name, binding]) => {
2120
+ if (!/^[A-Za-z_][A-Za-z0-9_-]*$/.test(name) || !isPlainObject(binding)) return [];
2121
+ const from = stringField(binding.from);
2122
+ return from ? [[name, { from }]] : [];
2123
+ });
2124
+ return entries.length > 0 ? Object.fromEntries(entries) : void 0;
2125
+ }
2115
2126
  function parseWorkflowTransitions(value) {
2116
2127
  const rawTransitions = Array.isArray(value) ? value : value === void 0 ? [] : [value];
2117
2128
  const transitions = rawTransitions.map((raw) => {
@@ -4448,6 +4459,15 @@ function validateWorkflow(value, options = {}) {
4448
4459
  if (step.input !== void 0 && !isJsonValue(step.input)) {
4449
4460
  issue(issues, "invalid_input", `${base}.input`, "workflow step input must be one JSON value");
4450
4461
  }
4462
+ if (step.input !== void 0 && step.inputs !== void 0) {
4463
+ issue(issues, "conflicting_inputs", base, "workflow step cannot declare both input and inputs");
4464
+ }
4465
+ validateInputBindings(
4466
+ step.inputs,
4467
+ `${base}.inputs`,
4468
+ issues,
4469
+ capability ? options.capabilityInputs?.get(capability) : void 0
4470
+ );
4451
4471
  });
4452
4472
  if (!graphMode) return issues;
4453
4473
  const seen = /* @__PURE__ */ new Set();
@@ -4461,11 +4481,24 @@ function validateWorkflow(value, options = {}) {
4461
4481
  }
4462
4482
  const adjacency = /* @__PURE__ */ new Map();
4463
4483
  const explicitEndSources = /* @__PURE__ */ new Set();
4484
+ const capabilitiesByStep = /* @__PURE__ */ new Map();
4485
+ steps.forEach((step) => {
4486
+ const id = text(step?.id);
4487
+ const capability = text(step?.capability ?? step?.action);
4488
+ if (id && capability) capabilitiesByStep.set(id, capability);
4489
+ });
4464
4490
  steps.forEach((step, index) => {
4465
4491
  if (!step) return;
4466
4492
  const id = text(step.id);
4467
4493
  if (!id) return;
4468
4494
  const sourceCapability = text(step.capability ?? step.action);
4495
+ validateInputBindingSources(
4496
+ step.inputs,
4497
+ `steps[${index}].inputs`,
4498
+ issues,
4499
+ capabilitiesByStep,
4500
+ options.capabilityOutputs
4501
+ );
4469
4502
  const transitions = transitionList(step.next);
4470
4503
  adjacency.set(id, []);
4471
4504
  if (transitions.length > maxTransitions) {
@@ -4581,6 +4614,63 @@ function validateWorkflow(value, options = {}) {
4581
4614
  }
4582
4615
  return issues;
4583
4616
  }
4617
+ function validateInputBindings(value, path53, issues, declaredInputs) {
4618
+ if (value === void 0) return;
4619
+ const bindings = asRecord(value);
4620
+ if (!bindings || Object.keys(bindings).length === 0) {
4621
+ issue(issues, "invalid_inputs", path53, "workflow step inputs must contain at least one named mapping");
4622
+ return;
4623
+ }
4624
+ for (const [name, value2] of Object.entries(bindings)) {
4625
+ const bindingPath = `${path53}.${name}`;
4626
+ if (!/^[A-Za-z_][A-Za-z0-9_-]*$/.test(name)) {
4627
+ issue(issues, "invalid_input_name", bindingPath, `workflow input name ${name} is invalid`);
4628
+ }
4629
+ if (declaredInputs && !declaredInputs.has(name)) {
4630
+ issue(issues, "undeclared_input", bindingPath, `target capability does not declare input ${name}`);
4631
+ }
4632
+ const binding = asRecord(value2);
4633
+ const from = text(binding?.from);
4634
+ if (!binding || Object.keys(binding).some((field) => field !== "from") || !from || !SAFE_INPUT_SOURCE.test(from)) {
4635
+ issue(
4636
+ issues,
4637
+ "invalid_input_source",
4638
+ `${bindingPath}.from`,
4639
+ "workflow input mapping must read from workflow input/state or a prior step result"
4640
+ );
4641
+ }
4642
+ }
4643
+ }
4644
+ function validateInputBindingSources(value, path53, issues, capabilitiesByStep, capabilityOutputs) {
4645
+ const bindings = asRecord(value);
4646
+ if (!bindings) return;
4647
+ for (const [name, rawBinding] of Object.entries(bindings)) {
4648
+ const from = text(asRecord(rawBinding)?.from);
4649
+ if (!from?.startsWith("steps.")) continue;
4650
+ const parts = from.split(".");
4651
+ const sourceStep = parts[1];
4652
+ const sourceCapability = sourceStep ? capabilitiesByStep.get(sourceStep) : void 0;
4653
+ if (!sourceStep || !sourceCapability) {
4654
+ issue(
4655
+ issues,
4656
+ "missing_input_step",
4657
+ `${path53}.${name}.from`,
4658
+ `workflow input mapping references missing step ${sourceStep ?? "<none>"}`
4659
+ );
4660
+ continue;
4661
+ }
4662
+ const outputPath = parts.slice(2).join(".");
4663
+ const declaredOutputs = capabilityOutputs?.get(sourceCapability);
4664
+ if (declaredOutputs && !declaredOutputs.has(outputPath)) {
4665
+ issue(
4666
+ issues,
4667
+ "undeclared_step_output",
4668
+ `${path53}.${name}.from`,
4669
+ `workflow input mapping reads ${outputPath}, but step ${sourceStep} does not declare it`
4670
+ );
4671
+ }
4672
+ }
4673
+ }
4584
4674
  function formatWorkflowValidationIssues(issues) {
4585
4675
  return issues.map((entry) => `${entry.path}: ${entry.message}`);
4586
4676
  }
@@ -4636,17 +4726,19 @@ function isJsonValue(value) {
4636
4726
  function issue(issues, code, path53, message) {
4637
4727
  issues.push({ code, path: path53, message });
4638
4728
  }
4639
- var SAFE_NAME, SAFE_STEP_ID, SAFE_DATA_PATH, SUPPORTED_STEP_FIELDS, SUPPORTED_TRANSITION_FIELDS;
4729
+ var SAFE_NAME, SAFE_STEP_ID, SAFE_DATA_PATH, SAFE_INPUT_SOURCE, SUPPORTED_STEP_FIELDS, SUPPORTED_TRANSITION_FIELDS;
4640
4730
  var init_workflowValidation = __esm({
4641
4731
  "src/workflowValidation.ts"() {
4642
4732
  "use strict";
4643
4733
  SAFE_NAME = /^[a-z][a-z0-9-]*$/;
4644
4734
  SAFE_STEP_ID = /^[A-Za-z][A-Za-z0-9_-]*$/;
4645
4735
  SAFE_DATA_PATH = /^(facts|evidence|artifacts|result|workflow|lastOutcome)(?:\.[A-Za-z_][A-Za-z0-9_-]*)+$/;
4736
+ SAFE_INPUT_SOURCE = /^(?:workflow\.(?:input|facts|evidence)(?:\.[A-Za-z_][A-Za-z0-9_-]*)+|steps\.[A-Za-z][A-Za-z0-9_-]*\.result(?:\.[A-Za-z_][A-Za-z0-9_-]*)+)$/;
4646
4737
  SUPPORTED_STEP_FIELDS = /* @__PURE__ */ new Set([
4647
4738
  "id",
4648
4739
  "capability",
4649
4740
  "input",
4741
+ "inputs",
4650
4742
  "action",
4651
4743
  "evidence",
4652
4744
  "target",
@@ -16954,6 +17046,10 @@ function parseOutput(text2) {
16954
17046
  if (labelledOutput.found) return labelledOutput.value;
16955
17047
  const plainOutput = parseSingleJsonCandidate(fences.filter((match) => !match[1]).map((match) => match[2]));
16956
17048
  if (plainOutput.found) return plainOutput.value;
17049
+ const finalStatusOutput = parseSingleJsonCandidate(
17050
+ [...text2.matchAll(/<final_status>\s*([\s\S]*?)\s*<\/final_status>/gi)].map((match) => match[1])
17051
+ );
17052
+ if (finalStatusOutput.found) return finalStatusOutput.value;
16957
17053
  const legacyText = text2.trim();
16958
17054
  return legacyText ? { summary: legacyText, output: legacyText } : void 0;
16959
17055
  }
@@ -17743,53 +17839,6 @@ var init_prepareBrowserAuth = __esm({
17743
17839
  }
17744
17840
  });
17745
17841
 
17746
- // src/scripts/prepareSimpleCapabilityRuntime.ts
17747
- function requirementsFrom(ctx) {
17748
- const raw = ctx.data.capabilityRequirements;
17749
- return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
17750
- }
17751
- function configureBrowser(profile) {
17752
- if (!profile.claudeCode.tools.includes("mcp__playwright")) {
17753
- profile.claudeCode.tools = [...profile.claudeCode.tools, "mcp__playwright"];
17754
- }
17755
- if (!profile.claudeCode.mcpServers.some(({ name }) => name === PLAYWRIGHT_SERVER.name)) {
17756
- profile.claudeCode.mcpServers = [...profile.claudeCode.mcpServers, PLAYWRIGHT_SERVER];
17757
- }
17758
- }
17759
- function appendPrompt(ctx, section) {
17760
- const prompt = typeof ctx.data.prompt === "string" ? ctx.data.prompt.trim() : "";
17761
- ctx.data.prompt = [prompt, section.trim()].filter(Boolean).join("\n\n");
17762
- }
17763
- var PLAYWRIGHT_SERVER, prepareSimpleCapabilityRuntime;
17764
- var init_prepareSimpleCapabilityRuntime = __esm({
17765
- "src/scripts/prepareSimpleCapabilityRuntime.ts"() {
17766
- "use strict";
17767
- init_loadQaContext();
17768
- PLAYWRIGHT_SERVER = {
17769
- name: "playwright",
17770
- command: "npx",
17771
- args: ["-y", "--package=@playwright/mcp@latest", "--", "playwright-mcp", "--headless"]
17772
- };
17773
- prepareSimpleCapabilityRuntime = async (ctx, profile) => {
17774
- const requirements = requirementsFrom(ctx);
17775
- if (!requirements.browser) return;
17776
- configureBrowser(profile);
17777
- if (!requirements.qaCredentials) return;
17778
- await loadQaContext(ctx, profile);
17779
- appendPrompt(
17780
- ctx,
17781
- [
17782
- "## QA authentication",
17783
- "",
17784
- String(ctx.data.qaAuthBlock ?? ""),
17785
- "",
17786
- "If the changed surface requires authentication and the credentials are missing or the login is rejected, return a blocked result with a safe explanation. Do not include usernames, passwords, tokens, or other credential values in the result."
17787
- ].join("\n")
17788
- );
17789
- };
17790
- }
17791
- });
17792
-
17793
17842
  // src/capabilityDelivery.ts
17794
17843
  function capabilityDeliveryTarget(input) {
17795
17844
  if (!input || typeof input !== "object" || Array.isArray(input)) return null;
@@ -17901,6 +17950,53 @@ var init_prepareCapabilityDelivery = __esm({
17901
17950
  }
17902
17951
  });
17903
17952
 
17953
+ // src/scripts/prepareSimpleCapabilityRuntime.ts
17954
+ function requirementsFrom(ctx) {
17955
+ const raw = ctx.data.capabilityRequirements;
17956
+ return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
17957
+ }
17958
+ function configureBrowser(profile) {
17959
+ if (!profile.claudeCode.tools.includes("mcp__playwright")) {
17960
+ profile.claudeCode.tools = [...profile.claudeCode.tools, "mcp__playwright"];
17961
+ }
17962
+ if (!profile.claudeCode.mcpServers.some(({ name }) => name === PLAYWRIGHT_SERVER.name)) {
17963
+ profile.claudeCode.mcpServers = [...profile.claudeCode.mcpServers, PLAYWRIGHT_SERVER];
17964
+ }
17965
+ }
17966
+ function appendPrompt(ctx, section) {
17967
+ const prompt = typeof ctx.data.prompt === "string" ? ctx.data.prompt.trim() : "";
17968
+ ctx.data.prompt = [prompt, section.trim()].filter(Boolean).join("\n\n");
17969
+ }
17970
+ var PLAYWRIGHT_SERVER, prepareSimpleCapabilityRuntime;
17971
+ var init_prepareSimpleCapabilityRuntime = __esm({
17972
+ "src/scripts/prepareSimpleCapabilityRuntime.ts"() {
17973
+ "use strict";
17974
+ init_loadQaContext();
17975
+ PLAYWRIGHT_SERVER = {
17976
+ name: "playwright",
17977
+ command: "npx",
17978
+ args: ["-y", "--package=@playwright/mcp@latest", "--", "playwright-mcp", "--headless"]
17979
+ };
17980
+ prepareSimpleCapabilityRuntime = async (ctx, profile) => {
17981
+ const requirements = requirementsFrom(ctx);
17982
+ if (!requirements.browser) return;
17983
+ configureBrowser(profile);
17984
+ if (!requirements.qaCredentials) return;
17985
+ await loadQaContext(ctx, profile);
17986
+ appendPrompt(
17987
+ ctx,
17988
+ [
17989
+ "## QA authentication",
17990
+ "",
17991
+ String(ctx.data.qaAuthBlock ?? ""),
17992
+ "",
17993
+ "If the changed surface requires authentication and the credentials are missing or the login is rejected, return a blocked result with a safe explanation. Do not include usernames, passwords, tokens, or other credential values in the result."
17994
+ ].join("\n")
17995
+ );
17996
+ };
17997
+ }
17998
+ });
17999
+
17904
18000
  // src/scripts/promoteQaGoal.ts
17905
18001
  var REPORT_JSON_OPEN2, promoteQaGoal;
17906
18002
  var init_promoteQaGoal = __esm({
@@ -18463,45 +18559,6 @@ var init_requirePlanDeviations = __esm({
18463
18559
  }
18464
18560
  });
18465
18561
 
18466
- // src/scripts/retryMissingDeliveryChange.ts
18467
- function claimsChange(output) {
18468
- if (!output || typeof output !== "object" || Array.isArray(output)) return false;
18469
- const status = output.status;
18470
- return status === "fixed" || status === "changed";
18471
- }
18472
- var retryMissingDeliveryChange;
18473
- var init_retryMissingDeliveryChange = __esm({
18474
- "src/scripts/retryMissingDeliveryChange.ts"() {
18475
- "use strict";
18476
- init_commit();
18477
- init_parseAgentResult();
18478
- init_parseSimpleCapabilityOutput();
18479
- retryMissingDeliveryChange = async (ctx, profile) => {
18480
- if (ctx.data.jobDelivery !== "pull-request") return;
18481
- if (!claimsChange(ctx.data.capabilityOutput)) return;
18482
- if (listChangedFiles(ctx.cwd).some((file) => !isForbiddenPath(file))) return;
18483
- const invoker = ctx.data.__invokeAgent;
18484
- const prompt = ctx.data.prompt;
18485
- if (!invoker || !prompt) return;
18486
- process.stderr.write("[kody] capability claimed a pull-request fix without a file change; retrying once\n");
18487
- const retry = await invoker(
18488
- [
18489
- prompt,
18490
- "",
18491
- "# Missing delivery change (retry)",
18492
- "",
18493
- "Your previous result claimed the pull request was fixed, but no repository file changed.",
18494
- "Inspect the supplied input and failure evidence, make the actual repair, and verify it.",
18495
- "If no safe change is possible, return a blocked result instead of claiming fixed.",
18496
- "This is the only retry."
18497
- ].join("\n")
18498
- );
18499
- await parseAgentResult2(ctx, profile, retry);
18500
- await parseSimpleCapabilityOutput(ctx, profile, retry);
18501
- };
18502
- }
18503
- });
18504
-
18505
18562
  // src/scripts/resolveArtifacts.ts
18506
18563
  var resolveArtifacts;
18507
18564
  var init_resolveArtifacts = __esm({
@@ -21118,8 +21175,8 @@ var init_scripts = __esm({
21118
21175
  init_postResearchComment();
21119
21176
  init_postReviewResult();
21120
21177
  init_prepareBrowserAuth();
21121
- init_prepareSimpleCapabilityRuntime();
21122
21178
  init_prepareCapabilityDelivery();
21179
+ init_prepareSimpleCapabilityRuntime();
21123
21180
  init_promoteQaGoal();
21124
21181
  init_publishReport();
21125
21182
  init_recordClassification();
@@ -21127,7 +21184,6 @@ var init_scripts = __esm({
21127
21184
  init_requireDeliveryArtifacts();
21128
21185
  init_requireFeedbackActions();
21129
21186
  init_requirePlanDeviations();
21130
- init_retryMissingDeliveryChange();
21131
21187
  init_resolveArtifacts();
21132
21188
  init_resolveFlow();
21133
21189
  init_resolvePreviewUrl();
@@ -21226,7 +21282,6 @@ var init_scripts = __esm({
21226
21282
  requireFeedbackActions,
21227
21283
  requireDeliveryArtifacts,
21228
21284
  requirePlanDeviations,
21229
- retryMissingDeliveryChange,
21230
21285
  verify,
21231
21286
  verifyWithRetry,
21232
21287
  verifyReproFails,
@@ -22499,6 +22554,8 @@ function parseWorkflowRunState(raw) {
22499
22554
  )
22500
22555
  ) : {};
22501
22556
  const facts = state.facts && typeof state.facts === "object" && !Array.isArray(state.facts) ? state.facts : {};
22557
+ const input = state.input && typeof state.input === "object" && !Array.isArray(state.input) ? state.input : void 0;
22558
+ const steps = parseWorkflowSteps(state.steps);
22502
22559
  const evidenceEntries = state.evidence && typeof state.evidence === "object" && !Array.isArray(state.evidence) ? Object.entries(state.evidence).filter((entry) => typeof entry[1] === "boolean") : [];
22503
22560
  const artifacts = Array.isArray(state.artifacts) ? state.artifacts.filter(
22504
22561
  (artifact) => !!artifact && typeof artifact === "object" && typeof artifact.label === "string" && (artifact.url === void 0 || typeof artifact.url === "string") && (artifact.path === void 0 || typeof artifact.path === "string")
@@ -22508,12 +22565,35 @@ function parseWorkflowRunState(raw) {
22508
22565
  ...typeof state.currentStepId === "string" ? { currentStepId: state.currentStepId } : {},
22509
22566
  completedStepIds,
22510
22567
  transitionCounts,
22568
+ ...input ? { input: { ...input } } : {},
22569
+ ...typeof state.definitionHash === "string" && state.definitionHash.trim() ? { definitionHash: state.definitionHash.trim() } : {},
22570
+ ...steps ? { steps } : {},
22511
22571
  facts: { ...facts },
22512
22572
  evidence: Object.fromEntries(evidenceEntries),
22513
22573
  artifacts: artifacts.map((artifact) => ({ ...artifact })),
22514
22574
  ...typeof state.blocker === "string" ? { blocker: state.blocker } : {}
22515
22575
  };
22516
22576
  }
22577
+ function parseWorkflowSteps(value) {
22578
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
22579
+ const steps = {};
22580
+ for (const [stepId, raw] of Object.entries(value)) {
22581
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) continue;
22582
+ const step = raw;
22583
+ if (step.status !== "running" && step.status !== "completed" && step.status !== "blocked" && step.status !== "failed") {
22584
+ continue;
22585
+ }
22586
+ steps[stepId] = {
22587
+ status: step.status,
22588
+ ...typeof step.capability === "string" ? { capability: step.capability } : {},
22589
+ ...Object.hasOwn(step, "input") ? { input: step.input } : {},
22590
+ ...Object.hasOwn(step, "output") ? { output: step.output } : {},
22591
+ ...typeof step.startedAt === "string" ? { startedAt: step.startedAt } : {},
22592
+ ...typeof step.completedAt === "string" ? { completedAt: step.completedAt } : {}
22593
+ };
22594
+ }
22595
+ return Object.keys(steps).length > 0 ? steps : void 0;
22596
+ }
22517
22597
  async function readWorkflowRunState(config, _cwd, workflowId, runId) {
22518
22598
  const tenantId2 = runtimeTenant(config);
22519
22599
  const row = await createStateBackendFromEnv().getWorkflowRun(tenantId2, workflowId, runId);
@@ -22556,6 +22636,7 @@ __export(job_exports, {
22556
22636
  stableJobKey: () => stableJobKey,
22557
22637
  validateJob: () => validateJob
22558
22638
  });
22639
+ import { createHash as createHash8 } from "crypto";
22559
22640
  function newJobId(flavor) {
22560
22641
  localJobSeq += 1;
22561
22642
  const runId = process.env.GITHUB_RUN_ID;
@@ -22844,6 +22925,8 @@ async function runLinearCapabilityWorkflow(parent, workflow, capability, base, c
22844
22925
  workflowStepCount: workflow.steps.length,
22845
22926
  workflowIssueNumber: workflowIssueNumber(parent),
22846
22927
  workflowContext: workflowInputContext(parent.cliArgs),
22928
+ workflowInput: state.input ?? {},
22929
+ workflowStepResults: workflowStepResults(state.steps),
22847
22930
  workflowFacts: parent.workflowFacts ?? {},
22848
22931
  workflowStack: [
22849
22932
  ...Array.isArray(base.preloadedData?.workflowStack) ? base.preloadedData.workflowStack.filter((entry) => typeof entry === "string") : [],
@@ -22864,6 +22947,8 @@ async function runLinearCapabilityWorkflow(parent, workflow, capability, base, c
22864
22947
  continue;
22865
22948
  }
22866
22949
  const child = workflowStepToJob(step, parent, chainData, base.cwd);
22950
+ beginWorkflowStep(state, step, child);
22951
+ await checkpoint?.(state);
22867
22952
  process.stdout.write(
22868
22953
  `\u2192 kody: workflow ${capability.slug} step ${index + 1}/${workflow.steps.length} \u2192 ${label}
22869
22954
 
@@ -22883,6 +22968,7 @@ async function runLinearCapabilityWorkflow(parent, workflow, capability, base, c
22883
22968
  workflowContinueOn: step.continueOn ?? []
22884
22969
  }
22885
22970
  });
22971
+ finishWorkflowStep(state, step, result);
22886
22972
  mergeWorkflowResults(state, result.capabilityResults);
22887
22973
  if (result.capabilityOutput && typeof result.capabilityOutput === "object" && !Array.isArray(result.capabilityOutput)) {
22888
22974
  Object.assign(state.facts, result.capabilityOutput);
@@ -22891,6 +22977,7 @@ async function runLinearCapabilityWorkflow(parent, workflow, capability, base, c
22891
22977
  const prUrl = result.prUrl ?? result.taskState?.core.prUrl ?? (typeof chainData.workflowPrUrl === "string" ? chainData.workflowPrUrl : void 0);
22892
22978
  chainData = {
22893
22979
  ...chainData,
22980
+ workflowStepResults: workflowStepResults(state.steps),
22894
22981
  ...result.taskState ? { taskState: result.taskState } : {},
22895
22982
  ...outcome ? { workflowLastOutcome: outcome } : {},
22896
22983
  ...result.capabilityOutput !== void 0 ? {
@@ -22922,6 +23009,7 @@ function isGraphWorkflow(workflow) {
22922
23009
  function workflowError(workflow, base) {
22923
23010
  const projectCapabilitiesRoot = hydratedCapabilitiesRoot(base.cwd);
22924
23011
  const knownCapabilities = /* @__PURE__ */ new Set();
23012
+ const capabilityInputs = /* @__PURE__ */ new Map();
22925
23013
  const capabilityOutputs = /* @__PURE__ */ new Map();
22926
23014
  for (const step of workflow.steps) {
22927
23015
  const action = step.action ?? step.capability;
@@ -22929,10 +23017,14 @@ function workflowError(workflow, base) {
22929
23017
  const resolvedFolder = resolveCapabilityFolder(step.capability, projectCapabilitiesRoot);
22930
23018
  if (!resolvedAction && !resolvedFolder) continue;
22931
23019
  knownCapabilities.add(step.capability);
23020
+ const inputNames = resolvedFolder ? capabilityInputNames(resolvedFolder) : /* @__PURE__ */ new Set();
23021
+ if (inputNames.size > 0) capabilityInputs.set(step.capability, inputNames);
22932
23022
  const outputPaths = resolvedFolder ? capabilityOutputConditionPaths(resolvedFolder.config) : /* @__PURE__ */ new Set();
22933
23023
  if (outputPaths.size > 0) capabilityOutputs.set(step.capability, outputPaths);
22934
23024
  }
22935
- return formatWorkflowValidationIssues(validateWorkflow(workflow, { knownCapabilities, capabilityOutputs }))[0] ?? null;
23025
+ return formatWorkflowValidationIssues(
23026
+ validateWorkflow(workflow, { knownCapabilities, capabilityInputs, capabilityOutputs })
23027
+ )[0] ?? null;
22936
23028
  }
22937
23029
  function initialWorkflowState(parent, workflow) {
22938
23030
  const prior = parent.workflowState;
@@ -22942,6 +23034,8 @@ function initialWorkflowState(parent, workflow) {
22942
23034
  status: "done",
22943
23035
  completedStepIds: [...prior.completedStepIds],
22944
23036
  transitionCounts: { ...prior.transitionCounts },
23037
+ ...prior.input ? { input: { ...prior.input } } : {},
23038
+ ...prior.steps ? { steps: cloneWorkflowSteps(prior.steps) } : {},
22945
23039
  facts: { ...prior.facts },
22946
23040
  evidence: { ...prior.evidence },
22947
23041
  artifacts: prior.artifacts.map((artifact) => ({ ...artifact }))
@@ -22951,9 +23045,12 @@ function initialWorkflowState(parent, workflow) {
22951
23045
  const currentStepId = prior?.currentStepId ?? firstStepId;
22952
23046
  return {
22953
23047
  status: "running",
23048
+ input: { ...prior?.input ?? workflowInputContext(parent.cliArgs) },
23049
+ definitionHash: prior?.definitionHash ?? workflowDefinitionHash(workflow),
22954
23050
  ...currentStepId ? { currentStepId } : {},
22955
23051
  completedStepIds: [...prior?.completedStepIds ?? []],
22956
23052
  transitionCounts: { ...prior?.transitionCounts ?? {} },
23053
+ steps: cloneWorkflowSteps(prior?.steps ?? {}),
22957
23054
  facts: {
22958
23055
  ...workflowInputContext(parent.cliArgs),
22959
23056
  ...parent.workflowFacts ?? {},
@@ -22975,6 +23072,8 @@ function workflowChainData(parent, capability, base, state) {
22975
23072
  workflowStepCount: capability.config.workflow?.steps.length ?? 0,
22976
23073
  workflowIssueNumber: workflowIssueNumber(parent),
22977
23074
  workflowContext: base.preloadedData?.workflowContext ?? workflowInputContext(parent.cliArgs),
23075
+ workflowInput: state.input ?? {},
23076
+ workflowStepResults: workflowStepResults(state.steps),
22978
23077
  workflowFacts: state.facts,
22979
23078
  workflowEvidence: state.evidence,
22980
23079
  workflowArtifacts: state.artifacts,
@@ -23017,9 +23116,18 @@ async function runGraphCapabilityWorkflow(parent, workflow, capability, base, ch
23017
23116
  const reason = error instanceof Error ? error.message : String(error);
23018
23117
  state.status = "blocked";
23019
23118
  state.blocker = reason;
23119
+ state.steps = state.steps ?? {};
23120
+ state.steps[step.id] = {
23121
+ capability: step.capability,
23122
+ status: "blocked",
23123
+ output: { status: "blocked", summary: reason },
23124
+ completedAt: (/* @__PURE__ */ new Date()).toISOString()
23125
+ };
23020
23126
  await checkpoint?.(state);
23021
23127
  return { exitCode: 64, reason, workflowState: state };
23022
23128
  }
23129
+ beginWorkflowStep(state, step, child);
23130
+ await checkpoint?.(state);
23023
23131
  process.stdout.write(
23024
23132
  `\u2192 kody: workflow ${capability.slug} step ${index + 1}/${workflow.steps.length} \u2192 ${label}
23025
23133
 
@@ -23044,6 +23152,7 @@ async function runGraphCapabilityWorkflow(parent, workflow, capability, base, ch
23044
23152
  workflowContinueOn: step.continueOn ?? []
23045
23153
  }
23046
23154
  });
23155
+ finishWorkflowStep(state, step, result);
23047
23156
  mergeWorkflowResults(state, result.capabilityResults);
23048
23157
  if (result.capabilityOutput && typeof result.capabilityOutput === "object" && !Array.isArray(result.capabilityOutput)) {
23049
23158
  Object.assign(state.facts, result.capabilityOutput);
@@ -23201,9 +23310,8 @@ function withWorkflowBoundaryEval(capability, result) {
23201
23310
  function workflowStepToJob(step, parent, chainData, cwd) {
23202
23311
  const action = step.action ?? step.capability;
23203
23312
  const targetNumber = workflowStepTargetNumber(step, parent, chainData);
23204
- const rawArgs = {
23205
- ...parent.cliArgs
23206
- };
23313
+ const mappedInputs = resolveWorkflowStepInputs(step, chainData);
23314
+ const rawArgs = mappedInputs ? { ...mappedInputs } : { ...parent.cliArgs };
23207
23315
  if (step.target === "pr") {
23208
23316
  if (typeof targetNumber !== "number") {
23209
23317
  throw new InvalidJobError(`workflow step ${action} needs a PR target but no prior PR URL is available`);
@@ -23213,7 +23321,7 @@ function workflowStepToJob(step, parent, chainData, cwd) {
23213
23321
  rawArgs.issue = targetNumber;
23214
23322
  }
23215
23323
  const genericInput = capabilityStepInput(
23216
- step.input ?? chainData.workflowContext ?? chainData.workflowLastOutput ?? genericInputFromArgs(rawArgs),
23324
+ step.input ?? mappedInputs ?? chainData.workflowInput ?? genericInputFromArgs(rawArgs),
23217
23325
  step.target,
23218
23326
  targetNumber
23219
23327
  );
@@ -23236,6 +23344,74 @@ function workflowStepToJob(step, parent, chainData, cwd) {
23236
23344
  ...parent.resultTarget ? { resultTarget: parent.resultTarget } : {}
23237
23345
  };
23238
23346
  }
23347
+ function resolveWorkflowStepInputs(step, chainData) {
23348
+ if (!step.inputs) return void 0;
23349
+ const source = {
23350
+ workflow: {
23351
+ input: chainData.workflowInput ?? {},
23352
+ facts: chainData.workflowFacts ?? {},
23353
+ evidence: chainData.workflowEvidence ?? {}
23354
+ },
23355
+ steps: chainData.workflowStepResults ?? {}
23356
+ };
23357
+ const input = {};
23358
+ for (const [name, binding] of Object.entries(step.inputs)) {
23359
+ const value = resolveDottedPath2(source, binding.from);
23360
+ if (value === void 0) {
23361
+ throw new InvalidJobError(`workflow step ${step.id ?? step.capability} needs missing input ${binding.from}`);
23362
+ }
23363
+ input[name] = value;
23364
+ }
23365
+ return input;
23366
+ }
23367
+ function capabilityInputNames(folder) {
23368
+ const properties = folder.config.inputSchema?.properties;
23369
+ if (!properties || typeof properties !== "object" || Array.isArray(properties)) return /* @__PURE__ */ new Set();
23370
+ return new Set(Object.keys(properties));
23371
+ }
23372
+ function workflowDefinitionHash(workflow) {
23373
+ return createHash8("sha256").update(stableJson(workflow)).digest("hex");
23374
+ }
23375
+ function stableJson(value) {
23376
+ if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
23377
+ if (value && typeof value === "object") {
23378
+ return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableJson(item)}`).join(",")}}`;
23379
+ }
23380
+ return JSON.stringify(value);
23381
+ }
23382
+ function cloneWorkflowSteps(steps) {
23383
+ return Object.fromEntries(Object.entries(steps).map(([id, step]) => [id, { ...step }]));
23384
+ }
23385
+ function workflowStepResults(steps) {
23386
+ return Object.fromEntries(
23387
+ Object.entries(steps ?? {}).map(([id, step]) => [id, Object.hasOwn(step, "output") ? { result: step.output } : {}])
23388
+ );
23389
+ }
23390
+ function workflowStepAuditInput(job) {
23391
+ if (Object.hasOwn(job.cliArgs, "input")) return genericInputFromArgs(job.cliArgs);
23392
+ return { ...job.cliArgs };
23393
+ }
23394
+ function beginWorkflowStep(state, step, job) {
23395
+ const id = step.id ?? step.action ?? step.capability;
23396
+ state.steps = state.steps ?? {};
23397
+ state.steps[id] = {
23398
+ capability: step.capability,
23399
+ status: "running",
23400
+ input: workflowStepAuditInput(job),
23401
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
23402
+ };
23403
+ }
23404
+ function finishWorkflowStep(state, step, result) {
23405
+ const id = step.id ?? step.action ?? step.capability;
23406
+ const prior = state.steps?.[id];
23407
+ state.steps = state.steps ?? {};
23408
+ state.steps[id] = {
23409
+ ...prior ?? { capability: step.capability, status: "running" },
23410
+ status: result.exitCode === 0 ? "completed" : result.capabilityResults?.at(-1)?.status === "blocked" ? "blocked" : "failed",
23411
+ ...result.capabilityOutput !== void 0 ? { output: result.capabilityOutput } : {},
23412
+ completedAt: (/* @__PURE__ */ new Date()).toISOString()
23413
+ };
23414
+ }
23239
23415
  function usesGenericCapabilityInput(action, cwd) {
23240
23416
  const inputs = getCapabilityActionInputs(action, hydratedCapabilitiesRoot(cwd));
23241
23417
  return Boolean(inputs?.length === 1 && inputs[0]?.name === "input" && inputs[0]?.flag === "--input");
@@ -23418,10 +23594,10 @@ var init_job = __esm({
23418
23594
  init_config();
23419
23595
  init_definition_paths();
23420
23596
  init_executor();
23597
+ init_kody_api_client();
23421
23598
  init_registry();
23422
23599
  init_runIndex();
23423
23600
  init_publishReport();
23424
- init_kody_api_client();
23425
23601
  init_simpleCapabilityRuntime();
23426
23602
  init_state_backend();
23427
23603
  init_workflowDefinitions();
@@ -595,9 +595,25 @@ export interface Job {
595
595
 
596
596
  export interface WorkflowRunState {
597
597
  status: "running" | "blocked" | "failed" | "done"
598
+ /** Immutable input supplied when this workflow run started. */
599
+ input?: Record<string, unknown>
600
+ /** Hash of the workflow definition used by this run. */
601
+ definitionHash?: string
598
602
  currentStepId?: string
599
603
  completedStepIds: string[]
600
604
  transitionCounts: Record<string, number>
605
+ /** Exact per-step handoffs for audit, resume, and debugging. */
606
+ steps?: Record<
607
+ string,
608
+ {
609
+ capability?: string
610
+ status: "running" | "completed" | "blocked" | "failed"
611
+ input?: unknown
612
+ output?: unknown
613
+ startedAt?: string
614
+ completedAt?: string
615
+ }
616
+ >
601
617
  facts: Record<string, unknown>
602
618
  evidence: Record<string, boolean>
603
619
  artifacts: Array<{ label: string; url?: string; path?: string }>
@@ -81,8 +81,7 @@
81
81
  }
82
82
  ],
83
83
  "postflight": [
84
- { "script": "parseSimpleCapabilityOutput" },
85
- { "script": "retryMissingDeliveryChange" }
84
+ { "script": "parseSimpleCapabilityOutput" }
86
85
  ]
87
86
  },
88
87
  "inputArtifacts": [],
File without changes
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.547",
4
- "description": "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative implementation profiles.",
3
+ "version": "0.4.549",
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",
7
7
  "bin": {
@@ -12,29 +12,6 @@
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:runtime-services": "node --test \"tests/runtime-services/*.test.mjs\"",
29
- "test:all": "vitest run tests --no-coverage",
30
- "typecheck": "tsc --noEmit",
31
- "lint": "biome check",
32
- "lint:fix": "biome check --write",
33
- "format": "biome format --write",
34
- "verify:package": "node scripts/verify-package-tarball.cjs",
35
- "brain:publish": "docker buildx build --platform linux/amd64 -f runner/Dockerfile.brain --build-arg KODY_ENGINE_REF=$(git rev-parse HEAD) -t ghcr.io/${KODY_BRAIN_GHCR_OWNER:-aharonyaircohen}/kody-brain:latest --push runner",
36
- "prepublishOnly": "pnpm typecheck && pnpm test:runtime-services && pnpm build && pnpm verify:package"
37
- },
38
15
  "dependencies": {
39
16
  "@actions/cache": "^6.0.0",
40
17
  "@anthropic-ai/claude-agent-sdk": "0.2.119",
@@ -61,5 +38,27 @@
61
38
  "url": "git+https://github.com/aharonyaircohen/kody-engine.git"
62
39
  },
63
40
  "homepage": "https://github.com/aharonyaircohen/kody-engine",
64
- "bugs": "https://github.com/aharonyaircohen/kody-engine/issues"
65
- }
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:runtime-services": "node --test \"tests/runtime-services/*.test.mjs\"",
56
+ "test:all": "vitest run tests --no-coverage",
57
+ "typecheck": "tsc --noEmit",
58
+ "lint": "biome check",
59
+ "lint:fix": "biome check --write",
60
+ "format": "biome format --write",
61
+ "verify:package": "node scripts/verify-package-tarball.cjs",
62
+ "brain:publish": "docker buildx build --platform linux/amd64 -f runner/Dockerfile.brain --build-arg KODY_ENGINE_REF=$(git rev-parse HEAD) -t ghcr.io/${KODY_BRAIN_GHCR_OWNER:-aharonyaircohen}/kody-brain:latest --push runner"
63
+ }
64
+ }