@kody-ade/kody-engine 0.4.546 → 0.4.548

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.546",
18
+ version: "0.4.548",
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",
@@ -17743,53 +17835,6 @@ var init_prepareBrowserAuth = __esm({
17743
17835
  }
17744
17836
  });
17745
17837
 
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
17838
  // src/capabilityDelivery.ts
17794
17839
  function capabilityDeliveryTarget(input) {
17795
17840
  if (!input || typeof input !== "object" || Array.isArray(input)) return null;
@@ -17901,6 +17946,53 @@ var init_prepareCapabilityDelivery = __esm({
17901
17946
  }
17902
17947
  });
17903
17948
 
17949
+ // src/scripts/prepareSimpleCapabilityRuntime.ts
17950
+ function requirementsFrom(ctx) {
17951
+ const raw = ctx.data.capabilityRequirements;
17952
+ return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
17953
+ }
17954
+ function configureBrowser(profile) {
17955
+ if (!profile.claudeCode.tools.includes("mcp__playwright")) {
17956
+ profile.claudeCode.tools = [...profile.claudeCode.tools, "mcp__playwright"];
17957
+ }
17958
+ if (!profile.claudeCode.mcpServers.some(({ name }) => name === PLAYWRIGHT_SERVER.name)) {
17959
+ profile.claudeCode.mcpServers = [...profile.claudeCode.mcpServers, PLAYWRIGHT_SERVER];
17960
+ }
17961
+ }
17962
+ function appendPrompt(ctx, section) {
17963
+ const prompt = typeof ctx.data.prompt === "string" ? ctx.data.prompt.trim() : "";
17964
+ ctx.data.prompt = [prompt, section.trim()].filter(Boolean).join("\n\n");
17965
+ }
17966
+ var PLAYWRIGHT_SERVER, prepareSimpleCapabilityRuntime;
17967
+ var init_prepareSimpleCapabilityRuntime = __esm({
17968
+ "src/scripts/prepareSimpleCapabilityRuntime.ts"() {
17969
+ "use strict";
17970
+ init_loadQaContext();
17971
+ PLAYWRIGHT_SERVER = {
17972
+ name: "playwright",
17973
+ command: "npx",
17974
+ args: ["-y", "--package=@playwright/mcp@latest", "--", "playwright-mcp", "--headless"]
17975
+ };
17976
+ prepareSimpleCapabilityRuntime = async (ctx, profile) => {
17977
+ const requirements = requirementsFrom(ctx);
17978
+ if (!requirements.browser) return;
17979
+ configureBrowser(profile);
17980
+ if (!requirements.qaCredentials) return;
17981
+ await loadQaContext(ctx, profile);
17982
+ appendPrompt(
17983
+ ctx,
17984
+ [
17985
+ "## QA authentication",
17986
+ "",
17987
+ String(ctx.data.qaAuthBlock ?? ""),
17988
+ "",
17989
+ "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."
17990
+ ].join("\n")
17991
+ );
17992
+ };
17993
+ }
17994
+ });
17995
+
17904
17996
  // src/scripts/promoteQaGoal.ts
17905
17997
  var REPORT_JSON_OPEN2, promoteQaGoal;
17906
17998
  var init_promoteQaGoal = __esm({
@@ -18463,45 +18555,6 @@ var init_requirePlanDeviations = __esm({
18463
18555
  }
18464
18556
  });
18465
18557
 
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
18558
  // src/scripts/resolveArtifacts.ts
18506
18559
  var resolveArtifacts;
18507
18560
  var init_resolveArtifacts = __esm({
@@ -21118,8 +21171,8 @@ var init_scripts = __esm({
21118
21171
  init_postResearchComment();
21119
21172
  init_postReviewResult();
21120
21173
  init_prepareBrowserAuth();
21121
- init_prepareSimpleCapabilityRuntime();
21122
21174
  init_prepareCapabilityDelivery();
21175
+ init_prepareSimpleCapabilityRuntime();
21123
21176
  init_promoteQaGoal();
21124
21177
  init_publishReport();
21125
21178
  init_recordClassification();
@@ -21127,7 +21180,6 @@ var init_scripts = __esm({
21127
21180
  init_requireDeliveryArtifacts();
21128
21181
  init_requireFeedbackActions();
21129
21182
  init_requirePlanDeviations();
21130
- init_retryMissingDeliveryChange();
21131
21183
  init_resolveArtifacts();
21132
21184
  init_resolveFlow();
21133
21185
  init_resolvePreviewUrl();
@@ -21226,7 +21278,6 @@ var init_scripts = __esm({
21226
21278
  requireFeedbackActions,
21227
21279
  requireDeliveryArtifacts,
21228
21280
  requirePlanDeviations,
21229
- retryMissingDeliveryChange,
21230
21281
  verify,
21231
21282
  verifyWithRetry,
21232
21283
  verifyReproFails,
@@ -22499,6 +22550,8 @@ function parseWorkflowRunState(raw) {
22499
22550
  )
22500
22551
  ) : {};
22501
22552
  const facts = state.facts && typeof state.facts === "object" && !Array.isArray(state.facts) ? state.facts : {};
22553
+ const input = state.input && typeof state.input === "object" && !Array.isArray(state.input) ? state.input : void 0;
22554
+ const steps = parseWorkflowSteps(state.steps);
22502
22555
  const evidenceEntries = state.evidence && typeof state.evidence === "object" && !Array.isArray(state.evidence) ? Object.entries(state.evidence).filter((entry) => typeof entry[1] === "boolean") : [];
22503
22556
  const artifacts = Array.isArray(state.artifacts) ? state.artifacts.filter(
22504
22557
  (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 +22561,35 @@ function parseWorkflowRunState(raw) {
22508
22561
  ...typeof state.currentStepId === "string" ? { currentStepId: state.currentStepId } : {},
22509
22562
  completedStepIds,
22510
22563
  transitionCounts,
22564
+ ...input ? { input: { ...input } } : {},
22565
+ ...typeof state.definitionHash === "string" && state.definitionHash.trim() ? { definitionHash: state.definitionHash.trim() } : {},
22566
+ ...steps ? { steps } : {},
22511
22567
  facts: { ...facts },
22512
22568
  evidence: Object.fromEntries(evidenceEntries),
22513
22569
  artifacts: artifacts.map((artifact) => ({ ...artifact })),
22514
22570
  ...typeof state.blocker === "string" ? { blocker: state.blocker } : {}
22515
22571
  };
22516
22572
  }
22573
+ function parseWorkflowSteps(value) {
22574
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
22575
+ const steps = {};
22576
+ for (const [stepId, raw] of Object.entries(value)) {
22577
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) continue;
22578
+ const step = raw;
22579
+ if (step.status !== "running" && step.status !== "completed" && step.status !== "blocked" && step.status !== "failed") {
22580
+ continue;
22581
+ }
22582
+ steps[stepId] = {
22583
+ status: step.status,
22584
+ ...typeof step.capability === "string" ? { capability: step.capability } : {},
22585
+ ...Object.hasOwn(step, "input") ? { input: step.input } : {},
22586
+ ...Object.hasOwn(step, "output") ? { output: step.output } : {},
22587
+ ...typeof step.startedAt === "string" ? { startedAt: step.startedAt } : {},
22588
+ ...typeof step.completedAt === "string" ? { completedAt: step.completedAt } : {}
22589
+ };
22590
+ }
22591
+ return Object.keys(steps).length > 0 ? steps : void 0;
22592
+ }
22517
22593
  async function readWorkflowRunState(config, _cwd, workflowId, runId) {
22518
22594
  const tenantId2 = runtimeTenant(config);
22519
22595
  const row = await createStateBackendFromEnv().getWorkflowRun(tenantId2, workflowId, runId);
@@ -22556,6 +22632,7 @@ __export(job_exports, {
22556
22632
  stableJobKey: () => stableJobKey,
22557
22633
  validateJob: () => validateJob
22558
22634
  });
22635
+ import { createHash as createHash8 } from "crypto";
22559
22636
  function newJobId(flavor) {
22560
22637
  localJobSeq += 1;
22561
22638
  const runId = process.env.GITHUB_RUN_ID;
@@ -22844,6 +22921,8 @@ async function runLinearCapabilityWorkflow(parent, workflow, capability, base, c
22844
22921
  workflowStepCount: workflow.steps.length,
22845
22922
  workflowIssueNumber: workflowIssueNumber(parent),
22846
22923
  workflowContext: workflowInputContext(parent.cliArgs),
22924
+ workflowInput: state.input ?? {},
22925
+ workflowStepResults: workflowStepResults(state.steps),
22847
22926
  workflowFacts: parent.workflowFacts ?? {},
22848
22927
  workflowStack: [
22849
22928
  ...Array.isArray(base.preloadedData?.workflowStack) ? base.preloadedData.workflowStack.filter((entry) => typeof entry === "string") : [],
@@ -22864,6 +22943,8 @@ async function runLinearCapabilityWorkflow(parent, workflow, capability, base, c
22864
22943
  continue;
22865
22944
  }
22866
22945
  const child = workflowStepToJob(step, parent, chainData, base.cwd);
22946
+ beginWorkflowStep(state, step, child);
22947
+ await checkpoint?.(state);
22867
22948
  process.stdout.write(
22868
22949
  `\u2192 kody: workflow ${capability.slug} step ${index + 1}/${workflow.steps.length} \u2192 ${label}
22869
22950
 
@@ -22883,6 +22964,7 @@ async function runLinearCapabilityWorkflow(parent, workflow, capability, base, c
22883
22964
  workflowContinueOn: step.continueOn ?? []
22884
22965
  }
22885
22966
  });
22967
+ finishWorkflowStep(state, step, result);
22886
22968
  mergeWorkflowResults(state, result.capabilityResults);
22887
22969
  if (result.capabilityOutput && typeof result.capabilityOutput === "object" && !Array.isArray(result.capabilityOutput)) {
22888
22970
  Object.assign(state.facts, result.capabilityOutput);
@@ -22891,6 +22973,7 @@ async function runLinearCapabilityWorkflow(parent, workflow, capability, base, c
22891
22973
  const prUrl = result.prUrl ?? result.taskState?.core.prUrl ?? (typeof chainData.workflowPrUrl === "string" ? chainData.workflowPrUrl : void 0);
22892
22974
  chainData = {
22893
22975
  ...chainData,
22976
+ workflowStepResults: workflowStepResults(state.steps),
22894
22977
  ...result.taskState ? { taskState: result.taskState } : {},
22895
22978
  ...outcome ? { workflowLastOutcome: outcome } : {},
22896
22979
  ...result.capabilityOutput !== void 0 ? {
@@ -22922,6 +23005,7 @@ function isGraphWorkflow(workflow) {
22922
23005
  function workflowError(workflow, base) {
22923
23006
  const projectCapabilitiesRoot = hydratedCapabilitiesRoot(base.cwd);
22924
23007
  const knownCapabilities = /* @__PURE__ */ new Set();
23008
+ const capabilityInputs = /* @__PURE__ */ new Map();
22925
23009
  const capabilityOutputs = /* @__PURE__ */ new Map();
22926
23010
  for (const step of workflow.steps) {
22927
23011
  const action = step.action ?? step.capability;
@@ -22929,10 +23013,14 @@ function workflowError(workflow, base) {
22929
23013
  const resolvedFolder = resolveCapabilityFolder(step.capability, projectCapabilitiesRoot);
22930
23014
  if (!resolvedAction && !resolvedFolder) continue;
22931
23015
  knownCapabilities.add(step.capability);
23016
+ const inputNames = resolvedFolder ? capabilityInputNames(resolvedFolder) : /* @__PURE__ */ new Set();
23017
+ if (inputNames.size > 0) capabilityInputs.set(step.capability, inputNames);
22932
23018
  const outputPaths = resolvedFolder ? capabilityOutputConditionPaths(resolvedFolder.config) : /* @__PURE__ */ new Set();
22933
23019
  if (outputPaths.size > 0) capabilityOutputs.set(step.capability, outputPaths);
22934
23020
  }
22935
- return formatWorkflowValidationIssues(validateWorkflow(workflow, { knownCapabilities, capabilityOutputs }))[0] ?? null;
23021
+ return formatWorkflowValidationIssues(
23022
+ validateWorkflow(workflow, { knownCapabilities, capabilityInputs, capabilityOutputs })
23023
+ )[0] ?? null;
22936
23024
  }
22937
23025
  function initialWorkflowState(parent, workflow) {
22938
23026
  const prior = parent.workflowState;
@@ -22942,6 +23030,8 @@ function initialWorkflowState(parent, workflow) {
22942
23030
  status: "done",
22943
23031
  completedStepIds: [...prior.completedStepIds],
22944
23032
  transitionCounts: { ...prior.transitionCounts },
23033
+ ...prior.input ? { input: { ...prior.input } } : {},
23034
+ ...prior.steps ? { steps: cloneWorkflowSteps(prior.steps) } : {},
22945
23035
  facts: { ...prior.facts },
22946
23036
  evidence: { ...prior.evidence },
22947
23037
  artifacts: prior.artifacts.map((artifact) => ({ ...artifact }))
@@ -22951,9 +23041,12 @@ function initialWorkflowState(parent, workflow) {
22951
23041
  const currentStepId = prior?.currentStepId ?? firstStepId;
22952
23042
  return {
22953
23043
  status: "running",
23044
+ input: { ...prior?.input ?? workflowInputContext(parent.cliArgs) },
23045
+ definitionHash: prior?.definitionHash ?? workflowDefinitionHash(workflow),
22954
23046
  ...currentStepId ? { currentStepId } : {},
22955
23047
  completedStepIds: [...prior?.completedStepIds ?? []],
22956
23048
  transitionCounts: { ...prior?.transitionCounts ?? {} },
23049
+ steps: cloneWorkflowSteps(prior?.steps ?? {}),
22957
23050
  facts: {
22958
23051
  ...workflowInputContext(parent.cliArgs),
22959
23052
  ...parent.workflowFacts ?? {},
@@ -22975,6 +23068,8 @@ function workflowChainData(parent, capability, base, state) {
22975
23068
  workflowStepCount: capability.config.workflow?.steps.length ?? 0,
22976
23069
  workflowIssueNumber: workflowIssueNumber(parent),
22977
23070
  workflowContext: base.preloadedData?.workflowContext ?? workflowInputContext(parent.cliArgs),
23071
+ workflowInput: state.input ?? {},
23072
+ workflowStepResults: workflowStepResults(state.steps),
22978
23073
  workflowFacts: state.facts,
22979
23074
  workflowEvidence: state.evidence,
22980
23075
  workflowArtifacts: state.artifacts,
@@ -23017,9 +23112,18 @@ async function runGraphCapabilityWorkflow(parent, workflow, capability, base, ch
23017
23112
  const reason = error instanceof Error ? error.message : String(error);
23018
23113
  state.status = "blocked";
23019
23114
  state.blocker = reason;
23115
+ state.steps = state.steps ?? {};
23116
+ state.steps[step.id] = {
23117
+ capability: step.capability,
23118
+ status: "blocked",
23119
+ output: { status: "blocked", summary: reason },
23120
+ completedAt: (/* @__PURE__ */ new Date()).toISOString()
23121
+ };
23020
23122
  await checkpoint?.(state);
23021
23123
  return { exitCode: 64, reason, workflowState: state };
23022
23124
  }
23125
+ beginWorkflowStep(state, step, child);
23126
+ await checkpoint?.(state);
23023
23127
  process.stdout.write(
23024
23128
  `\u2192 kody: workflow ${capability.slug} step ${index + 1}/${workflow.steps.length} \u2192 ${label}
23025
23129
 
@@ -23044,6 +23148,7 @@ async function runGraphCapabilityWorkflow(parent, workflow, capability, base, ch
23044
23148
  workflowContinueOn: step.continueOn ?? []
23045
23149
  }
23046
23150
  });
23151
+ finishWorkflowStep(state, step, result);
23047
23152
  mergeWorkflowResults(state, result.capabilityResults);
23048
23153
  if (result.capabilityOutput && typeof result.capabilityOutput === "object" && !Array.isArray(result.capabilityOutput)) {
23049
23154
  Object.assign(state.facts, result.capabilityOutput);
@@ -23201,9 +23306,8 @@ function withWorkflowBoundaryEval(capability, result) {
23201
23306
  function workflowStepToJob(step, parent, chainData, cwd) {
23202
23307
  const action = step.action ?? step.capability;
23203
23308
  const targetNumber = workflowStepTargetNumber(step, parent, chainData);
23204
- const rawArgs = {
23205
- ...parent.cliArgs
23206
- };
23309
+ const mappedInputs = resolveWorkflowStepInputs(step, chainData);
23310
+ const rawArgs = mappedInputs ? { ...mappedInputs } : { ...parent.cliArgs };
23207
23311
  if (step.target === "pr") {
23208
23312
  if (typeof targetNumber !== "number") {
23209
23313
  throw new InvalidJobError(`workflow step ${action} needs a PR target but no prior PR URL is available`);
@@ -23213,7 +23317,7 @@ function workflowStepToJob(step, parent, chainData, cwd) {
23213
23317
  rawArgs.issue = targetNumber;
23214
23318
  }
23215
23319
  const genericInput = capabilityStepInput(
23216
- step.input ?? chainData.workflowContext ?? chainData.workflowLastOutput ?? genericInputFromArgs(rawArgs),
23320
+ step.input ?? mappedInputs ?? chainData.workflowInput ?? genericInputFromArgs(rawArgs),
23217
23321
  step.target,
23218
23322
  targetNumber
23219
23323
  );
@@ -23236,6 +23340,74 @@ function workflowStepToJob(step, parent, chainData, cwd) {
23236
23340
  ...parent.resultTarget ? { resultTarget: parent.resultTarget } : {}
23237
23341
  };
23238
23342
  }
23343
+ function resolveWorkflowStepInputs(step, chainData) {
23344
+ if (!step.inputs) return void 0;
23345
+ const source = {
23346
+ workflow: {
23347
+ input: chainData.workflowInput ?? {},
23348
+ facts: chainData.workflowFacts ?? {},
23349
+ evidence: chainData.workflowEvidence ?? {}
23350
+ },
23351
+ steps: chainData.workflowStepResults ?? {}
23352
+ };
23353
+ const input = {};
23354
+ for (const [name, binding] of Object.entries(step.inputs)) {
23355
+ const value = resolveDottedPath2(source, binding.from);
23356
+ if (value === void 0) {
23357
+ throw new InvalidJobError(`workflow step ${step.id ?? step.capability} needs missing input ${binding.from}`);
23358
+ }
23359
+ input[name] = value;
23360
+ }
23361
+ return input;
23362
+ }
23363
+ function capabilityInputNames(folder) {
23364
+ const properties = folder.config.inputSchema?.properties;
23365
+ if (!properties || typeof properties !== "object" || Array.isArray(properties)) return /* @__PURE__ */ new Set();
23366
+ return new Set(Object.keys(properties));
23367
+ }
23368
+ function workflowDefinitionHash(workflow) {
23369
+ return createHash8("sha256").update(stableJson(workflow)).digest("hex");
23370
+ }
23371
+ function stableJson(value) {
23372
+ if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
23373
+ if (value && typeof value === "object") {
23374
+ return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableJson(item)}`).join(",")}}`;
23375
+ }
23376
+ return JSON.stringify(value);
23377
+ }
23378
+ function cloneWorkflowSteps(steps) {
23379
+ return Object.fromEntries(Object.entries(steps).map(([id, step]) => [id, { ...step }]));
23380
+ }
23381
+ function workflowStepResults(steps) {
23382
+ return Object.fromEntries(
23383
+ Object.entries(steps ?? {}).map(([id, step]) => [id, Object.hasOwn(step, "output") ? { result: step.output } : {}])
23384
+ );
23385
+ }
23386
+ function workflowStepAuditInput(job) {
23387
+ if (Object.hasOwn(job.cliArgs, "input")) return genericInputFromArgs(job.cliArgs);
23388
+ return { ...job.cliArgs };
23389
+ }
23390
+ function beginWorkflowStep(state, step, job) {
23391
+ const id = step.id ?? step.action ?? step.capability;
23392
+ state.steps = state.steps ?? {};
23393
+ state.steps[id] = {
23394
+ capability: step.capability,
23395
+ status: "running",
23396
+ input: workflowStepAuditInput(job),
23397
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
23398
+ };
23399
+ }
23400
+ function finishWorkflowStep(state, step, result) {
23401
+ const id = step.id ?? step.action ?? step.capability;
23402
+ const prior = state.steps?.[id];
23403
+ state.steps = state.steps ?? {};
23404
+ state.steps[id] = {
23405
+ ...prior ?? { capability: step.capability, status: "running" },
23406
+ status: result.exitCode === 0 ? "completed" : result.capabilityResults?.at(-1)?.status === "blocked" ? "blocked" : "failed",
23407
+ ...result.capabilityOutput !== void 0 ? { output: result.capabilityOutput } : {},
23408
+ completedAt: (/* @__PURE__ */ new Date()).toISOString()
23409
+ };
23410
+ }
23239
23411
  function usesGenericCapabilityInput(action, cwd) {
23240
23412
  const inputs = getCapabilityActionInputs(action, hydratedCapabilitiesRoot(cwd));
23241
23413
  return Boolean(inputs?.length === 1 && inputs[0]?.name === "input" && inputs[0]?.flag === "--input");
@@ -23418,10 +23590,10 @@ var init_job = __esm({
23418
23590
  init_config();
23419
23591
  init_definition_paths();
23420
23592
  init_executor();
23593
+ init_kody_api_client();
23421
23594
  init_registry();
23422
23595
  init_runIndex();
23423
23596
  init_publishReport();
23424
- init_kody_api_client();
23425
23597
  init_simpleCapabilityRuntime();
23426
23598
  init_state_backend();
23427
23599
  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": [],
@@ -2,8 +2,10 @@
2
2
 
3
3
  ## Delivery
4
4
 
5
- The delivery wrapper owns git commits, pushes, and pull requests. Do not run
6
- git or GitHub write commands.
5
+ The delivery wrapper has already checked out the requested target before you
6
+ start. Inspect and edit the current working tree. Do not fetch, checkout, sync,
7
+ merge, commit, push, or run any other git or GitHub write command; the wrapper
8
+ owns those operations.
7
9
 
8
10
  After completing the capability work, finish with exactly this structure:
9
11
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.546",
3
+ "version": "0.4.548",
4
4
  "description": "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative implementation profiles.",
5
5
  "license": "MIT",
6
6
  "type": "module",