@kody-ade/kody-engine 0.4.373 → 0.4.375

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.373",
18
+ version: "0.4.375",
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",
@@ -1064,8 +1064,8 @@ function formatAssistant(msg, _opts) {
1064
1064
  const lines = [];
1065
1065
  for (const block of content) {
1066
1066
  if (block.type === "text") {
1067
- const text = block.text.trim();
1068
- if (text) lines.push(text);
1067
+ const text2 = block.text.trim();
1068
+ if (text2) lines.push(text2);
1069
1069
  } else if (block.type === "tool_use") {
1070
1070
  const tu = block;
1071
1071
  lines.push(`\u2192 ${tu.name}${summarizeToolInput(tu.name, tu.input)}`);
@@ -1079,14 +1079,14 @@ function formatUserToolResult(msg, opts) {
1079
1079
  for (const block of content) {
1080
1080
  if (block.type === "tool_result") {
1081
1081
  const tr = block;
1082
- const text = stringifyToolContent(tr.content);
1083
- const lineCount = text.split("\n").length;
1084
- const sizeBytes = text.length;
1082
+ const text2 = stringifyToolContent(tr.content);
1083
+ const lineCount = text2.split("\n").length;
1084
+ const sizeBytes = text2.length;
1085
1085
  const flag = tr.is_error ? " ERROR" : "";
1086
1086
  const summary = ` \u21B3${flag} ${lineCount} lines, ${formatBytes(sizeBytes)}`;
1087
1087
  if (opts.verbose) {
1088
1088
  lines.push(`${summary}
1089
- ${truncate2(text, 4e3)}`);
1089
+ ${truncate2(text2, 4e3)}`);
1090
1090
  } else {
1091
1091
  lines.push(summary);
1092
1092
  }
@@ -1612,7 +1612,7 @@ function cmsHeaders(opts) {
1612
1612
  }
1613
1613
  };
1614
1614
  }
1615
- async function callDashboardCms(opts, path51, init = {}) {
1615
+ async function callDashboardCms(opts, path52, init = {}) {
1616
1616
  const baseUrl = dashboardBaseUrl(opts);
1617
1617
  if (!baseUrl) {
1618
1618
  return {
@@ -1624,7 +1624,7 @@ async function callDashboardCms(opts, path51, init = {}) {
1624
1624
  const headerResult = cmsHeaders(opts);
1625
1625
  if (!headerResult.ok) return headerResult;
1626
1626
  try {
1627
- const res = await fetch(`${baseUrl}${path51}`, {
1627
+ const res = await fetch(`${baseUrl}${path52}`, {
1628
1628
  ...init,
1629
1629
  headers: {
1630
1630
  ...headerResult.headers,
@@ -1696,8 +1696,8 @@ function documentArg(value) {
1696
1696
  function normalizeCmsDocumentIdInput(input) {
1697
1697
  const trimmed = stripWrappingQuotes(input.trim());
1698
1698
  const withoutQuery = trimmed.split(/[?#]/, 1)[0] ?? trimmed;
1699
- const path51 = parseDocumentPath(withoutQuery);
1700
- return path51 ?? parseDocumentIdSegment(withoutQuery) ?? withoutQuery;
1699
+ const path52 = parseDocumentPath(withoutQuery);
1700
+ return path52 ?? parseDocumentIdSegment(withoutQuery) ?? withoutQuery;
1701
1701
  }
1702
1702
  function stripWrappingQuotes(value) {
1703
1703
  let current = value;
@@ -1708,9 +1708,9 @@ function stripWrappingQuotes(value) {
1708
1708
  }
1709
1709
  }
1710
1710
  function parseDocumentPath(value) {
1711
- const path51 = value.startsWith("http://") || value.startsWith("https://") ? urlPathname(value) : value;
1712
- if (!path51?.includes("/content/entries/")) return null;
1713
- const parts = path51.split("/").filter(Boolean).map(decodePathPart);
1711
+ const path52 = value.startsWith("http://") || value.startsWith("https://") ? urlPathname(value) : value;
1712
+ if (!path52?.includes("/content/entries/")) return null;
1713
+ const parts = path52.split("/").filter(Boolean).map(decodePathPart);
1714
1714
  const entriesIndex = parts.findIndex((part, index) => part === "content" && parts[index + 1] === "entries");
1715
1715
  const idPart = parts[entriesIndex + 3];
1716
1716
  if (!idPart || idPart === "new") return null;
@@ -1970,7 +1970,12 @@ function stringList(value) {
1970
1970
  function parseCapabilityWorkflow(value) {
1971
1971
  const stepsRaw = Array.isArray(value) ? value : value && typeof value === "object" && Array.isArray(value.steps) ? value.steps : [];
1972
1972
  const steps = stepsRaw.map(parseWorkflowStep).filter((step) => step !== null);
1973
- return steps.length > 0 ? { steps } : void 0;
1973
+ if (steps.length === 0) return void 0;
1974
+ const startAt = value && typeof value === "object" && !Array.isArray(value) ? stringField(value.startAt) : void 0;
1975
+ return {
1976
+ steps,
1977
+ ...startAt && isSafeSlug(startAt) ? { startAt } : {}
1978
+ };
1974
1979
  }
1975
1980
  function parseWorkflowStep(value) {
1976
1981
  if (typeof value === "string") {
@@ -1982,6 +1987,7 @@ function parseWorkflowStep(value) {
1982
1987
  const capability = stringField(raw.capability ?? raw.action);
1983
1988
  if (!capability || !isSafeSlug(capability)) return null;
1984
1989
  const implementation = stringField(raw.implementation);
1990
+ const id = stringField(raw.id);
1985
1991
  const action = stringField(raw.action);
1986
1992
  const evidence = stringField(raw.evidence);
1987
1993
  const agent = stringField(raw.agent);
@@ -1989,9 +1995,12 @@ function parseWorkflowStep(value) {
1989
1995
  const target = stringField(raw.target);
1990
1996
  const targetFact = stringField(raw.targetFact ?? raw.target_fact);
1991
1997
  const cliArgs = raw.cliArgs;
1998
+ const inputs = parseWorkflowInputs(raw.inputs);
1999
+ const next = parseWorkflowTransitions(raw.next);
1992
2000
  const report = parseReportPublication(raw.report);
1993
2001
  return {
1994
2002
  capability,
2003
+ ...id && isSafeSlug(id) ? { id } : {},
1995
2004
  ...action && isSafeSlug(action) ? { action } : {},
1996
2005
  ...implementation && isSafeSlug(implementation) ? { implementation } : {},
1997
2006
  ...evidence ? { evidence } : {},
@@ -2000,12 +2009,45 @@ function parseWorkflowStep(value) {
2000
2009
  ...agent && isSafeSlug(agent) ? { agent } : {},
2001
2010
  ...reason ? { reason } : {},
2002
2011
  ...cliArgs && typeof cliArgs === "object" && !Array.isArray(cliArgs) ? { cliArgs } : {},
2012
+ ...inputs ? { inputs } : {},
2013
+ ...next ? { next } : {},
2003
2014
  ...isPlainObject(raw.runWhen) ? { runWhen: raw.runWhen } : {},
2004
2015
  ...stringList(raw.continueOn ?? raw.continue_on).length > 0 ? { continueOn: stringList(raw.continueOn ?? raw.continue_on) } : {},
2005
2016
  ...raw.saveReport === true ? { saveReport: true } : {},
2006
2017
  ...report ? { report } : {}
2007
2018
  };
2008
2019
  }
2020
+ function parseWorkflowInputs(value) {
2021
+ if (!isPlainObject(value)) return void 0;
2022
+ const inputs = {};
2023
+ for (const [name, raw] of Object.entries(value)) {
2024
+ if (!isSafeSlug(name) || !isPlainObject(raw)) continue;
2025
+ const from = stringField(raw.from);
2026
+ if (!from) continue;
2027
+ inputs[name] = { from };
2028
+ }
2029
+ return Object.keys(inputs).length > 0 ? inputs : void 0;
2030
+ }
2031
+ function parseWorkflowTransitions(value) {
2032
+ const rawTransitions = Array.isArray(value) ? value : value === void 0 ? [] : [value];
2033
+ const transitions = rawTransitions.map((raw) => {
2034
+ if (typeof raw === "string") {
2035
+ const to2 = raw.trim();
2036
+ return isSafeSlug(to2) ? { to: to2 } : null;
2037
+ }
2038
+ if (!isPlainObject(raw)) return null;
2039
+ const to = stringField(raw.to);
2040
+ if (!to || !isSafeSlug(to)) return null;
2041
+ const maxIterations = typeof raw.maxIterations === "number" && Number.isInteger(raw.maxIterations) && raw.maxIterations > 0 ? raw.maxIterations : void 0;
2042
+ return {
2043
+ to,
2044
+ ...isPlainObject(raw.when) ? { when: raw.when } : {},
2045
+ ...raw.default === true ? { default: true } : {},
2046
+ ...maxIterations ? { maxIterations } : {}
2047
+ };
2048
+ }).filter((transition) => transition !== null);
2049
+ return transitions.length > 0 ? transitions : void 0;
2050
+ }
2009
2051
  function parseReportPublication(value) {
2010
2052
  if (!isPlainObject(value)) return void 0;
2011
2053
  const type = stringField(value.type);
@@ -2317,8 +2359,8 @@ function resolveCapabilityFolder(slug, projectCapabilitiesRoot = getProjectCapab
2317
2359
  }
2318
2360
  return null;
2319
2361
  }
2320
- function getCapabilityActionInputs(action) {
2321
- const resolved = resolveCapabilityAction(action);
2362
+ function getCapabilityActionInputs(action, projectCapabilitiesRoot = getProjectCapabilitiesRoot()) {
2363
+ const resolved = resolveCapabilityAction(action, projectCapabilitiesRoot);
2322
2364
  if (!resolved) return null;
2323
2365
  return getProfileInputs(resolved.implementation);
2324
2366
  }
@@ -2637,20 +2679,20 @@ function readLedger(label) {
2637
2679
  const raw = gh(["issue", "list", "--state", "open", "--label", label, "--limit", "5", "--json", "number,body"]);
2638
2680
  const issues = JSON.parse(raw);
2639
2681
  if (issues.length === 0) return { found: false, payload: null };
2640
- const issue = issues.sort((a, b) => a.number - b.number)[0];
2641
- const body = issue?.body ?? "";
2682
+ const issue2 = issues.sort((a, b) => a.number - b.number)[0];
2683
+ const body = issue2?.body ?? "";
2642
2684
  const startIdx = body.indexOf(startTag);
2643
2685
  const endIdx = body.indexOf(endTag);
2644
2686
  if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx) {
2645
- return { found: true, issueNumber: issue?.number, payload: null };
2687
+ return { found: true, issueNumber: issue2?.number, payload: null };
2646
2688
  }
2647
2689
  const between = body.slice(startIdx + startTag.length, endIdx);
2648
2690
  const fenceMatch = between.match(/```json\s*([\s\S]*?)```/);
2649
- if (!fenceMatch) return { found: true, issueNumber: issue?.number, payload: null };
2691
+ if (!fenceMatch) return { found: true, issueNumber: issue2?.number, payload: null };
2650
2692
  try {
2651
- return { found: true, issueNumber: issue?.number, payload: JSON.parse(fenceMatch[1]) };
2693
+ return { found: true, issueNumber: issue2?.number, payload: JSON.parse(fenceMatch[1]) };
2652
2694
  } catch {
2653
- return { found: true, issueNumber: issue?.number, payload: null };
2695
+ return { found: true, issueNumber: issue2?.number, payload: null };
2654
2696
  }
2655
2697
  } catch (err) {
2656
2698
  return { found: false, payload: { error: err instanceof Error ? err.message : String(err) } };
@@ -2698,12 +2740,7 @@ function readCheckRuns(repoSlug, ref, ignoreNames) {
2698
2740
  let rawStatuses = "";
2699
2741
  try {
2700
2742
  rawStatuses = gh(
2701
- [
2702
- "api",
2703
- `repos/${repoSlug}/commits/${sha}/status`,
2704
- "--jq",
2705
- ".statuses[] | {context, state, target_url}"
2706
- ],
2743
+ ["api", `repos/${repoSlug}/commits/${sha}/status`, "--jq", ".statuses[] | {context, state, target_url}"],
2707
2744
  ghOptions
2708
2745
  );
2709
2746
  } catch {
@@ -2745,14 +2782,14 @@ ${marker}`
2745
2782
  return { error: err instanceof Error ? err.message : String(err) };
2746
2783
  }
2747
2784
  }
2748
- function ensureComment(repoSlug, issue, key, body) {
2785
+ function ensureComment(repoSlug, issue2, key, body) {
2749
2786
  const marker = commentMarker(key);
2750
2787
  try {
2751
- const raw = gh(["issue", "view", String(issue), "-R", repoSlug, "--json", "comments"]);
2788
+ const raw = gh(["issue", "view", String(issue2), "-R", repoSlug, "--json", "comments"]);
2752
2789
  const parsed = JSON.parse(raw);
2753
2790
  const already = (parsed.comments ?? []).some((c) => (c.body ?? "").includes(marker));
2754
2791
  if (already) return { posted: false };
2755
- gh(["issue", "comment", String(issue), "-R", repoSlug, "--body-file", "-"], { input: `${body}
2792
+ gh(["issue", "comment", String(issue2), "-R", repoSlug, "--body-file", "-"], { input: `${body}
2756
2793
 
2757
2794
  ${marker}` });
2758
2795
  return { posted: true };
@@ -2794,9 +2831,9 @@ function dispatchWorkflow(workflowFile, capability, issueNumber, repoSlug, ref)
2794
2831
  return { ok: false, error: err instanceof Error ? err.message : String(err) };
2795
2832
  }
2796
2833
  }
2797
- function startCapability(workflowFile, name, issue, repoSlug, ref) {
2834
+ function startCapability(workflowFile, name, issue2, repoSlug, ref) {
2798
2835
  const acceptsIssue = capabilityAcceptsIssue(name);
2799
- const forwardedIssue = acceptsIssue === false ? void 0 : issue;
2836
+ const forwardedIssue = acceptsIssue === false ? void 0 : issue2;
2800
2837
  return dispatchWorkflow(workflowFile, name, forwardedIssue, repoSlug, ref);
2801
2838
  }
2802
2839
  function capabilityAcceptsIssue(capability) {
@@ -2862,8 +2899,8 @@ function capabilityToolDefinitions(opts) {
2862
2899
  handler: async (args) => {
2863
2900
  const pr = Number(args.pr);
2864
2901
  const result = dispatchVerb(workflowFile, opts.repoSlug, verb, pr);
2865
- const text = result.ok ? `Dispatched \`${verb}\` on PR #${pr}. The repair runs in its own workflow_dispatch \u2014 wait for the next tick to see the new headSha.` : `Dispatch failed for \`${verb}\` on PR #${pr}: ${result.error}`;
2866
- return { content: [{ type: "text", text }] };
2902
+ const text2 = result.ok ? `Dispatched \`${verb}\` on PR #${pr}. The repair runs in its own workflow_dispatch \u2014 wait for the next tick to see the new headSha.` : `Dispatch failed for \`${verb}\` on PR #${pr}: ${result.error}`;
2903
+ return { content: [{ type: "text", text: text2 }] };
2867
2904
  }
2868
2905
  });
2869
2906
  const syncTool = makeDispatch(
@@ -2889,8 +2926,8 @@ function capabilityToolDefinitions(opts) {
2889
2926
  const pr = Number(args.pr);
2890
2927
  const body = String(args.body ?? "");
2891
2928
  const result = postRecommendation(opts.repoSlug, pr, opts.operatorMention, body, opts.capabilitySlug);
2892
- const text = result.ok ? result.posted ? `Recommendation posted on PR #${pr}.` : `Recommendation already exists on PR #${pr}; skipped.` : `Recommendation failed on PR #${pr}: ${result.error}`;
2893
- return { content: [{ type: "text", text }] };
2929
+ const text2 = result.ok ? result.posted ? `Recommendation posted on PR #${pr}.` : `Recommendation already exists on PR #${pr}; skipped.` : `Recommendation failed on PR #${pr}: ${result.error}`;
2930
+ return { content: [{ type: "text", text: text2 }] };
2894
2931
  }
2895
2932
  };
2896
2933
  const ledgerTool = {
@@ -2964,10 +3001,10 @@ function capabilityToolDefinitions(opts) {
2964
3001
  body: z3.string().min(1).describe("Comment body markdown.")
2965
3002
  },
2966
3003
  handler: async (args) => {
2967
- const issue = Number(args.issue);
3004
+ const issue2 = Number(args.issue);
2968
3005
  const key = String(args.key ?? "");
2969
3006
  const body = String(args.body ?? "");
2970
- const result = ensureComment(opts.repoSlug, issue, key, body);
3007
+ const result = ensureComment(opts.repoSlug, issue2, key, body);
2971
3008
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
2972
3009
  }
2973
3010
  };
@@ -2982,19 +3019,13 @@ function capabilityToolDefinitions(opts) {
2982
3019
  handler: async (args) => {
2983
3020
  const name = String(args.name ?? "");
2984
3021
  const rawIssue = args.issue ?? args.issueNumber;
2985
- const issue = rawIssue == null ? void 0 : Number(rawIssue);
2986
- if (issue !== void 0 && (!Number.isFinite(issue) || issue <= 0)) {
3022
+ const issue2 = rawIssue == null ? void 0 : Number(rawIssue);
3023
+ if (issue2 !== void 0 && (!Number.isFinite(issue2) || issue2 <= 0)) {
2987
3024
  return { content: [{ type: "text", text: "Start failed: `issue` must be a positive number when provided." }] };
2988
3025
  }
2989
- const result = startCapability(
2990
- workflowFile,
2991
- name,
2992
- issue,
2993
- opts.repoSlug,
2994
- opts.defaultBranch
2995
- );
2996
- const text = JSON.stringify(result);
2997
- return { content: [{ type: "text", text }] };
3026
+ const result = startCapability(workflowFile, name, issue2, opts.repoSlug, opts.defaultBranch);
3027
+ const text2 = JSON.stringify(result);
3028
+ return { content: [{ type: "text", text: text2 }] };
2998
3029
  }
2999
3030
  };
3000
3031
  const cmsTools = dashboardCmsToolDefinitions({
@@ -3209,8 +3240,8 @@ function classifySubtype(subtype) {
3209
3240
  if (lower.includes("error")) return "model_error";
3210
3241
  return "generic_failed";
3211
3242
  }
3212
- function isClaudeLoginRequiredText(text) {
3213
- const normalized = text.toLowerCase();
3243
+ function isClaudeLoginRequiredText(text2) {
3244
+ const normalized = text2.toLowerCase();
3214
3245
  return normalized.includes("not logged in") && normalized.includes("/login");
3215
3246
  }
3216
3247
  function resolveTurnTimeoutMs(opts) {
@@ -3549,9 +3580,9 @@ async function runAgent(opts) {
3549
3580
  outcome = "completed";
3550
3581
  outcomeKind = "ok";
3551
3582
  sawTerminalSuccess = true;
3552
- const text = (typeof m.result === "string" ? m.result : "").trim();
3553
- if (isClaudeLoginRequiredText(text)) sawLoginRequired = true;
3554
- if (text) resultTexts.push(text);
3583
+ const text2 = (typeof m.result === "string" ? m.result : "").trim();
3584
+ if (isClaudeLoginRequiredText(text2)) sawLoginRequired = true;
3585
+ if (text2) resultTexts.push(text2);
3555
3586
  } else {
3556
3587
  outcome = "failed";
3557
3588
  outcomeKind = classifySubtype(m.subtype);
@@ -3966,9 +3997,9 @@ var init_agencyBoundaryEval = __esm({
3966
3997
  });
3967
3998
 
3968
3999
  // src/capabilityReport.ts
3969
- function parseCapabilityReportsFromText(text) {
4000
+ function parseCapabilityReportsFromText(text2) {
3970
4001
  const reports = [];
3971
- for (const match of text.matchAll(REPORT_LINE)) {
4002
+ for (const match of text2.matchAll(REPORT_LINE)) {
3972
4003
  const raw = match[1]?.trim();
3973
4004
  if (!raw) continue;
3974
4005
  try {
@@ -4096,9 +4127,9 @@ var init_evidenceState = __esm({
4096
4127
  });
4097
4128
 
4098
4129
  // src/capabilityResult.ts
4099
- function parseCapabilityResultsFromText(text) {
4130
+ function parseCapabilityResultsFromText(text2) {
4100
4131
  const results = [];
4101
- for (const match of text.matchAll(RESULT_LINE)) {
4132
+ for (const match of text2.matchAll(RESULT_LINE)) {
4102
4133
  const raw = match[1]?.trim();
4103
4134
  if (!raw) continue;
4104
4135
  try {
@@ -5419,8 +5450,8 @@ function loadProjectConventions(projectDir) {
5419
5450
  return out;
5420
5451
  }
5421
5452
  function parseAgentResult(finalText) {
5422
- const text = (finalText || "").trim();
5423
- if (!text)
5453
+ const text2 = (finalText || "").trim();
5454
+ if (!text2)
5424
5455
  return {
5425
5456
  done: false,
5426
5457
  commitMessage: "",
@@ -5434,7 +5465,7 @@ function parseAgentResult(finalText) {
5434
5465
  const MARKDOWN_PREFIX = "[\\s>*_#`~\\-]*";
5435
5466
  const FAILED_RE = new RegExp(`(?:^|\\n)${MARKDOWN_PREFIX}FAILED${MARKDOWN_PREFIX}\\s*:\\s*(.+?)\\s*$`, "s");
5436
5467
  const DONE_RE = new RegExp(`(?:^|\\n)${MARKDOWN_PREFIX}DONE\\b`);
5437
- const scanText = stripFencedCodeBlocks(text);
5468
+ const scanText = stripFencedCodeBlocks(text2);
5438
5469
  const failedMatch = scanText.match(FAILED_RE);
5439
5470
  if (failedMatch) {
5440
5471
  return {
@@ -5449,32 +5480,32 @@ function parseAgentResult(finalText) {
5449
5480
  };
5450
5481
  }
5451
5482
  const hasDoneMarker = DONE_RE.test(scanText);
5452
- const hasCommitMsg = /^[\s>*_#`~-]*COMMIT_MSG\s*:/im.test(text);
5453
- const hasPrSummary = /^[\s>*_#`~-]*PR_SUMMARY\s*:/im.test(text);
5483
+ const hasCommitMsg = /^[\s>*_#`~-]*COMMIT_MSG\s*:/im.test(text2);
5484
+ const hasPrSummary = /^[\s>*_#`~-]*PR_SUMMARY\s*:/im.test(text2);
5454
5485
  const markerMissing = !hasDoneMarker && !hasCommitMsg && !hasPrSummary;
5455
- const commitMatch = text.match(/^[\s>*_#`~-]*COMMIT_MSG[\s>*_#`~-]*\s*:\s*(.+)$/im);
5486
+ const commitMatch = text2.match(/^[\s>*_#`~-]*COMMIT_MSG[\s>*_#`~-]*\s*:\s*(.+)$/im);
5456
5487
  const commitMessage = commitMatch ? stripMarkdownEmphasis(commitMatch[1]) : "";
5457
5488
  const feedbackActions = extractBlock(
5458
- text,
5489
+ text2,
5459
5490
  /(?:^|\n)[ \t]*FEEDBACK_ACTIONS\s*:[ \t]*\n/i,
5460
5491
  /(?:^|\n)[ \t]*(?:PLAN_DEVIATIONS|COMMIT_MSG|PR_SUMMARY|PRIOR_ART)\s*:/i
5461
5492
  );
5462
5493
  let planDeviations = extractBlock(
5463
- text,
5494
+ text2,
5464
5495
  /(?:^|\n)[ \t]*PLAN_DEVIATIONS\s*:[ \t]*\n/i,
5465
5496
  /(?:^|\n)[ \t]*(?:COMMIT_MSG|PR_SUMMARY|FEEDBACK_ACTIONS|PRIOR_ART)\s*:/i
5466
5497
  );
5467
5498
  if (!planDeviations) {
5468
- const inline = text.match(/(?:^|\n)[ \t]*PLAN_DEVIATIONS\s*:[ \t]*(.+?)[ \t]*(?:\n|$)/i);
5499
+ const inline = text2.match(/(?:^|\n)[ \t]*PLAN_DEVIATIONS\s*:[ \t]*(.+?)[ \t]*(?:\n|$)/i);
5469
5500
  if (inline) planDeviations = inline[1].trim();
5470
5501
  }
5471
5502
  let priorArt = "";
5472
- const priorArtInline = text.match(/(?:^|\n)[ \t]*PRIOR_ART\s*:[ \t]*(.+?)[ \t]*(?:\n|$)/i);
5503
+ const priorArtInline = text2.match(/(?:^|\n)[ \t]*PRIOR_ART\s*:[ \t]*(.+?)[ \t]*(?:\n|$)/i);
5473
5504
  if (priorArtInline) priorArt = priorArtInline[1].trim();
5474
- const summaryStart = text.search(/(^|\n)[ \t]*PR_SUMMARY\s*:[ \t]*\n/i);
5505
+ const summaryStart = text2.search(/(^|\n)[ \t]*PR_SUMMARY\s*:[ \t]*\n/i);
5475
5506
  let prSummary = "";
5476
5507
  if (summaryStart !== -1) {
5477
- const afterMarker = text.slice(summaryStart).replace(/^[\s\S]*?PR_SUMMARY\s*:[ \t]*\n/i, "");
5508
+ const afterMarker = text2.slice(summaryStart).replace(/^[\s\S]*?PR_SUMMARY\s*:[ \t]*\n/i, "");
5478
5509
  prSummary = afterMarker.replace(/\n\s*```\s*$/g, "").replace(/```\s*$/g, "").trim();
5479
5510
  }
5480
5511
  return {
@@ -5494,10 +5525,10 @@ function stripMarkdownEmphasis(s) {
5494
5525
  function stripFencedCodeBlocks(s) {
5495
5526
  return s.replace(/```[\s\S]*?```/g, "").replace(/~~~[\s\S]*?~~~/g, "");
5496
5527
  }
5497
- function extractBlock(text, startMarker, endMarker) {
5498
- const startIdx = text.search(startMarker);
5528
+ function extractBlock(text2, startMarker, endMarker) {
5529
+ const startIdx = text2.search(startMarker);
5499
5530
  if (startIdx === -1) return "";
5500
- const afterStart = text.slice(startIdx).replace(startMarker, "");
5531
+ const afterStart = text2.slice(startIdx).replace(startMarker, "");
5501
5532
  const endIdx = afterStart.search(endMarker);
5502
5533
  const body = endIdx === -1 ? afterStart : afterStart.slice(0, endIdx);
5503
5534
  return body.replace(/\n\s*```\s*$/g, "").trim();
@@ -5673,9 +5704,9 @@ function collectPages(memoryAbs) {
5673
5704
  }
5674
5705
  function extractQueryTerms(ctx) {
5675
5706
  const terms = [];
5676
- const issue = ctx.data.issue;
5707
+ const issue2 = ctx.data.issue;
5677
5708
  const pr = ctx.data.pr;
5678
- if (issue?.title) terms.push(...tokenize(issue.title));
5709
+ if (issue2?.title) terms.push(...tokenize(issue2.title));
5679
5710
  if (pr?.title) terms.push(...tokenize(pr.title));
5680
5711
  return Array.from(new Set(terms)).slice(0, 20);
5681
5712
  }
@@ -7689,10 +7720,10 @@ var init_state2 = __esm({
7689
7720
  "use strict";
7690
7721
  VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "done"]);
7691
7722
  GoalStateError = class extends Error {
7692
- constructor(path51, message) {
7693
- super(`Invalid goal state at ${path51}:
7723
+ constructor(path52, message) {
7724
+ super(`Invalid goal state at ${path52}:
7694
7725
  ${message}`);
7695
- this.path = path51;
7726
+ this.path = path52;
7696
7727
  this.name = "GoalStateError";
7697
7728
  }
7698
7729
  path;
@@ -7705,9 +7736,9 @@ import * as fs25 from "fs";
7705
7736
  function stageGoalRunLogEvent(data, goalId, event, at = nowIso()) {
7706
7737
  const logs = goalRunLogs(data);
7707
7738
  const existing = logs[goalId];
7708
- const path51 = existing?.path ?? goalRunLogPath(goalId, data);
7739
+ const path52 = existing?.path ?? goalRunLogPath(goalId, data);
7709
7740
  logs[goalId] = {
7710
- path: path51,
7741
+ path: path52,
7711
7742
  events: [...existing?.events ?? [], buildGoalRunLogEvent(data, goalId, event, at)]
7712
7743
  };
7713
7744
  }
@@ -8726,6 +8757,291 @@ var init_typeDefinitions = __esm({
8726
8757
  }
8727
8758
  });
8728
8759
 
8760
+ // src/workflowValidation.ts
8761
+ function validateWorkflow(value, options = {}) {
8762
+ const issues = [];
8763
+ const workflow = asRecord2(value);
8764
+ const rawSteps = Array.isArray(value) ? value : Array.isArray(workflow?.steps) ? workflow.steps : [];
8765
+ const maxSteps = options.maxSteps ?? 100;
8766
+ const maxTransitions = options.maxTransitionsPerStep ?? 20;
8767
+ const maxLoopIterations = options.maxLoopIterations ?? 100;
8768
+ if (rawSteps.length === 0) {
8769
+ issue(issues, "steps_required", "steps", "workflow must contain at least one step");
8770
+ return issues;
8771
+ }
8772
+ if (rawSteps.length > maxSteps) {
8773
+ issue(issues, "too_many_steps", "steps", `workflow has ${rawSteps.length} steps; maximum is ${maxSteps}`);
8774
+ }
8775
+ const graphMode = workflow?.startAt !== void 0 || rawSteps.some((entry) => {
8776
+ const step = asRecord2(entry);
8777
+ return Boolean(step && (step.id !== void 0 || step.next !== void 0 || step.inputs !== void 0));
8778
+ });
8779
+ const steps = rawSteps.map(
8780
+ (entry) => typeof entry === "string" ? { capability: entry } : asRecord2(entry)
8781
+ );
8782
+ const ids = [];
8783
+ steps.forEach((step, index) => {
8784
+ const base = `steps[${index}]`;
8785
+ if (!step) {
8786
+ issue(issues, "invalid_step", base, "workflow step must be a capability name or an object");
8787
+ return;
8788
+ }
8789
+ for (const field of Object.keys(step)) {
8790
+ if (!SUPPORTED_STEP_FIELDS.has(field)) {
8791
+ issue(issues, "unsupported_step_field", `${base}.${field}`, `workflow step field ${field} is not supported`);
8792
+ }
8793
+ }
8794
+ const capability = text(step.capability ?? step.action);
8795
+ if (!capability || !SAFE_NAME.test(capability)) {
8796
+ issue(issues, "invalid_capability", `${base}.capability`, "workflow step must name a valid capability");
8797
+ } else if (options.knownCapabilities && !options.knownCapabilities.has(capability)) {
8798
+ issue(
8799
+ issues,
8800
+ "unknown_capability",
8801
+ `${base}.capability`,
8802
+ `workflow step references unknown capability ${capability}`
8803
+ );
8804
+ }
8805
+ if (graphMode) {
8806
+ const id = text(step.id);
8807
+ if (!id || !SAFE_STEP_ID.test(id)) {
8808
+ issue(issues, "invalid_step_id", `${base}.id`, "graph workflow steps must each have a valid id");
8809
+ } else {
8810
+ ids.push(id);
8811
+ }
8812
+ }
8813
+ validateDataMatch(step.runWhen, `${base}.runWhen`, issues);
8814
+ const inputs = asRecord2(step.inputs);
8815
+ if (step.inputs !== void 0 && !inputs) {
8816
+ issue(issues, "invalid_inputs", `${base}.inputs`, "workflow step inputs must be an object");
8817
+ }
8818
+ if (inputs) {
8819
+ for (const [name, mapping] of Object.entries(inputs)) {
8820
+ const inputPath = `${base}.inputs.${name}`;
8821
+ if (!SAFE_NAME.test(name)) issue(issues, "invalid_input_name", inputPath, `invalid input name ${name}`);
8822
+ const from = text(asRecord2(mapping)?.from);
8823
+ if (!from || !SAFE_DATA_PATH.test(from)) {
8824
+ issue(
8825
+ issues,
8826
+ "invalid_data_path",
8827
+ `${inputPath}.from`,
8828
+ `workflow input ${name} must read from facts, evidence, artifacts, result, workflow, or lastOutcome`
8829
+ );
8830
+ }
8831
+ const declared = capability ? options.capabilityInputs?.get(capability) : void 0;
8832
+ if (declared && !declared.has(name)) {
8833
+ issue(
8834
+ issues,
8835
+ "unknown_capability_input",
8836
+ inputPath,
8837
+ `capability ${capability} does not declare input ${name}`
8838
+ );
8839
+ }
8840
+ }
8841
+ }
8842
+ });
8843
+ if (!graphMode) return issues;
8844
+ const seen = /* @__PURE__ */ new Set();
8845
+ ids.forEach((id, index) => {
8846
+ if (seen.has(id)) issue(issues, "duplicate_step_id", `steps[${index}].id`, `workflow step id ${id} is duplicated`);
8847
+ seen.add(id);
8848
+ });
8849
+ const startAt = text(workflow?.startAt) ?? text(steps[0]?.id);
8850
+ if (!startAt || !seen.has(startAt)) {
8851
+ issue(issues, "missing_start_step", "startAt", `workflow startAt references missing step ${startAt ?? "<none>"}`);
8852
+ }
8853
+ const adjacency = /* @__PURE__ */ new Map();
8854
+ steps.forEach((step, index) => {
8855
+ if (!step) return;
8856
+ const id = text(step.id);
8857
+ if (!id) return;
8858
+ const transitions = transitionList(step.next);
8859
+ adjacency.set(id, []);
8860
+ if (transitions.length > maxTransitions) {
8861
+ issue(
8862
+ issues,
8863
+ "too_many_transitions",
8864
+ `steps[${index}].next`,
8865
+ `workflow step ${id} has ${transitions.length} connections; maximum is ${maxTransitions}`
8866
+ );
8867
+ }
8868
+ const defaults = transitions.filter((transition) => asRecord2(transition)?.default === true);
8869
+ const conditionals = transitions.filter((transition) => asRecord2(transition)?.when !== void 0);
8870
+ const unconditional = transitions.filter((transition) => {
8871
+ const raw = asRecord2(transition);
8872
+ return typeof transition === "string" || Boolean(raw && raw.when === void 0 && raw.default !== true && raw.maxIterations === void 0);
8873
+ });
8874
+ if (defaults.length > 1) {
8875
+ issue(
8876
+ issues,
8877
+ "multiple_default_transitions",
8878
+ `steps[${index}].next`,
8879
+ `workflow step ${id} has more than one default connection`
8880
+ );
8881
+ }
8882
+ if (conditionals.length > 0 && defaults.length !== 1) {
8883
+ issue(
8884
+ issues,
8885
+ "missing_default_transition",
8886
+ `steps[${index}].next`,
8887
+ `workflow step ${id} has conditions and needs one default connection`
8888
+ );
8889
+ }
8890
+ if (unconditional.length > 1 || unconditional.length > 0 && transitions.length > 1) {
8891
+ issue(
8892
+ issues,
8893
+ "ambiguous_transition",
8894
+ `steps[${index}].next`,
8895
+ `workflow step ${id} mixes an unconditional connection with other connections`
8896
+ );
8897
+ }
8898
+ transitions.forEach((transition, transitionIndex) => {
8899
+ const raw = typeof transition === "string" ? { to: transition } : asRecord2(transition);
8900
+ const base = `steps[${index}].next[${transitionIndex}]`;
8901
+ if (!raw) {
8902
+ issue(issues, "invalid_transition", base, "workflow connection must be a step id or an object");
8903
+ return;
8904
+ }
8905
+ for (const field of Object.keys(raw)) {
8906
+ if (!SUPPORTED_TRANSITION_FIELDS.has(field)) {
8907
+ issue(
8908
+ issues,
8909
+ "unsupported_transition_field",
8910
+ `${base}.${field}`,
8911
+ `workflow connection field ${field} is not supported`
8912
+ );
8913
+ }
8914
+ }
8915
+ const target = text(raw.to);
8916
+ if (!target || !SAFE_NAME.test(target)) {
8917
+ issue(issues, "invalid_transition_target", `${base}.to`, "workflow connection must name a valid target step");
8918
+ return;
8919
+ }
8920
+ if (!seen.has(target)) {
8921
+ issue(
8922
+ issues,
8923
+ "missing_transition_target",
8924
+ `${base}.to`,
8925
+ `workflow step ${id} connects to missing step ${target}`
8926
+ );
8927
+ } else {
8928
+ adjacency.get(id)?.push(target);
8929
+ }
8930
+ if (raw.default === true && raw.when !== void 0) {
8931
+ issue(issues, "conflicting_transition", base, "workflow connection cannot be both conditional and default");
8932
+ }
8933
+ if (raw.when !== void 0) validateDataMatch(raw.when, `${base}.when`, issues);
8934
+ const targetIndex = ids.indexOf(target ?? "");
8935
+ const iterations = raw.maxIterations;
8936
+ if (targetIndex >= 0 && targetIndex <= index) {
8937
+ if (!Number.isInteger(iterations) || Number(iterations) < 1) {
8938
+ issue(
8939
+ issues,
8940
+ "unbounded_loop",
8941
+ `${base}.maxIterations`,
8942
+ `workflow loop ${id}->${target} must set maxIterations`
8943
+ );
8944
+ } else if (Number(iterations) > maxLoopIterations) {
8945
+ issue(
8946
+ issues,
8947
+ "loop_limit_too_high",
8948
+ `${base}.maxIterations`,
8949
+ `workflow loop ${id}->${target} exceeds maximum ${maxLoopIterations}`
8950
+ );
8951
+ }
8952
+ } else if (iterations !== void 0 && (!Number.isInteger(iterations) || Number(iterations) < 1)) {
8953
+ issue(issues, "invalid_loop_limit", `${base}.maxIterations`, "maxIterations must be a positive integer");
8954
+ }
8955
+ });
8956
+ });
8957
+ if (startAt && seen.has(startAt)) {
8958
+ const reachable = /* @__PURE__ */ new Set();
8959
+ const pending = [startAt];
8960
+ while (pending.length > 0) {
8961
+ const id = pending.pop();
8962
+ if (reachable.has(id)) continue;
8963
+ reachable.add(id);
8964
+ pending.push(...adjacency.get(id) ?? []);
8965
+ }
8966
+ ids.forEach((id, index) => {
8967
+ if (!reachable.has(id)) issue(issues, "unreachable_step", `steps[${index}]`, `workflow step ${id} is unreachable`);
8968
+ });
8969
+ if (![...reachable].some((id) => (adjacency.get(id) ?? []).length === 0)) {
8970
+ issue(issues, "missing_terminal_step", "steps", "workflow has no reachable final step");
8971
+ }
8972
+ }
8973
+ return issues;
8974
+ }
8975
+ function formatWorkflowValidationIssues(issues) {
8976
+ return issues.map((entry) => `${entry.path}: ${entry.message}`);
8977
+ }
8978
+ function validateDataMatch(value, path52, issues) {
8979
+ if (value === void 0) return;
8980
+ const match = asRecord2(value);
8981
+ if (!match || Object.keys(match).length === 0) {
8982
+ issue(issues, "invalid_condition", path52, "workflow condition must contain at least one match");
8983
+ return;
8984
+ }
8985
+ for (const [field, expected] of Object.entries(match)) {
8986
+ if (!SAFE_DATA_PATH.test(field)) {
8987
+ issue(
8988
+ issues,
8989
+ "invalid_data_path",
8990
+ `${path52}.${field}`,
8991
+ `workflow condition must read from facts, evidence, artifacts, result, workflow, or lastOutcome`
8992
+ );
8993
+ }
8994
+ if (!isComparable(expected)) {
8995
+ issue(issues, "invalid_condition_value", `${path52}.${field}`, "workflow condition value must be a JSON scalar");
8996
+ }
8997
+ }
8998
+ }
8999
+ function transitionList(value) {
9000
+ if (value === void 0) return [];
9001
+ return Array.isArray(value) ? value : [value];
9002
+ }
9003
+ function asRecord2(value) {
9004
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
9005
+ }
9006
+ function text(value) {
9007
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
9008
+ }
9009
+ function isComparable(value) {
9010
+ if (value === null || ["string", "number", "boolean"].includes(typeof value)) return true;
9011
+ return Array.isArray(value) && value.length > 0 && value.every((item) => isComparable(item) && !Array.isArray(item));
9012
+ }
9013
+ function issue(issues, code, path52, message) {
9014
+ issues.push({ code, path: path52, message });
9015
+ }
9016
+ var SAFE_NAME, SAFE_STEP_ID, SAFE_DATA_PATH, SUPPORTED_STEP_FIELDS, SUPPORTED_TRANSITION_FIELDS;
9017
+ var init_workflowValidation = __esm({
9018
+ "src/workflowValidation.ts"() {
9019
+ "use strict";
9020
+ SAFE_NAME = /^[a-z][a-z0-9-]*$/;
9021
+ SAFE_STEP_ID = /^[A-Za-z][A-Za-z0-9_-]*$/;
9022
+ SAFE_DATA_PATH = /^(facts|evidence|artifacts|result|workflow|lastOutcome)(?:\.[A-Za-z_][A-Za-z0-9_-]*)+$/;
9023
+ SUPPORTED_STEP_FIELDS = /* @__PURE__ */ new Set([
9024
+ "id",
9025
+ "capability",
9026
+ "action",
9027
+ "implementation",
9028
+ "evidence",
9029
+ "target",
9030
+ "targetFact",
9031
+ "reason",
9032
+ "agent",
9033
+ "cliArgs",
9034
+ "inputs",
9035
+ "next",
9036
+ "runWhen",
9037
+ "continueOn",
9038
+ "saveReport",
9039
+ "report"
9040
+ ]);
9041
+ SUPPORTED_TRANSITION_FIELDS = /* @__PURE__ */ new Set(["to", "when", "default", "maxIterations"]);
9042
+ }
9043
+ });
9044
+
8729
9045
  // src/workflowDefinitions.ts
8730
9046
  import * as fs28 from "fs";
8731
9047
  import * as path26 from "path";
@@ -8742,7 +9058,18 @@ function normalizeWorkflowDefinition(value) {
8742
9058
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
8743
9059
  const raw = value;
8744
9060
  const name = typeof raw.name === "string" ? raw.name.trim() : "";
8745
- const workflow = parseCapabilityWorkflow({ steps: raw.steps });
9061
+ const hasGraphConnections = Array.isArray(raw.steps) && raw.steps.some(
9062
+ (step) => step && typeof step === "object" && !Array.isArray(step) && (step.next !== void 0 || step.inputs !== void 0)
9063
+ );
9064
+ if (hasGraphConnections) {
9065
+ if (validateWorkflow({ steps: raw.steps, ...raw.startAt !== void 0 ? { startAt: raw.startAt } : {} }).length > 0) {
9066
+ return null;
9067
+ }
9068
+ }
9069
+ const workflow = parseCapabilityWorkflow({
9070
+ steps: raw.steps,
9071
+ startAt: raw.startAt
9072
+ });
8746
9073
  const steps = workflow?.steps;
8747
9074
  const capabilities = steps ? steps.map((step) => step.capability) : normalizeWorkflowCapabilities(raw.capabilities);
8748
9075
  if (!name || capabilities.length === 0) return null;
@@ -8752,6 +9079,7 @@ function normalizeWorkflowDefinition(value) {
8752
9079
  capabilities,
8753
9080
  ...raw.runWithoutApproval === true ? { runWithoutApproval: true } : {},
8754
9081
  ...steps ? { steps } : {},
9082
+ ...workflow?.startAt ? { startAt: workflow.startAt } : {},
8755
9083
  ...typeof raw.createdAt === "string" ? { createdAt: raw.createdAt } : {},
8756
9084
  ...typeof raw.updatedAt === "string" ? { updatedAt: raw.updatedAt } : {}
8757
9085
  };
@@ -8793,7 +9121,8 @@ function normalizeWorkflowCapabilities(value) {
8793
9121
  }
8794
9122
  function workflowDefinitionToConfig(workflow) {
8795
9123
  return {
8796
- steps: workflow.steps ?? workflow.capabilities.map((capability) => ({ capability }))
9124
+ steps: workflow.steps ?? workflow.capabilities.map((capability) => ({ capability })),
9125
+ ...workflow.startAt ? { startAt: workflow.startAt } : {}
8797
9126
  };
8798
9127
  }
8799
9128
  function readCompanyStoreWorkflowDefinition(id) {
@@ -8817,6 +9146,7 @@ var init_workflowDefinitions = __esm({
8817
9146
  init_capabilityFolders();
8818
9147
  init_companyStore();
8819
9148
  init_stateRepo();
9149
+ init_workflowValidation();
8820
9150
  WORKFLOW_ID_PATTERN = /^[a-z0-9][a-z0-9_-]{0,79}$/;
8821
9151
  CAPABILITY_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,79}$/;
8822
9152
  }
@@ -9639,7 +9969,7 @@ function readSimpleGoalTaskSummary(goalId, cwd) {
9639
9969
  );
9640
9970
  const issues = JSON.parse(raw);
9641
9971
  const total = issues.length;
9642
- const open = issues.filter((issue) => String(issue.state ?? "").toLowerCase() === "open").length;
9972
+ const open = issues.filter((issue2) => String(issue2.state ?? "").toLowerCase() === "open").length;
9643
9973
  return { total, open };
9644
9974
  }
9645
9975
  function previousDispatchWasTargetInstance(managed, previousScheduleState) {
@@ -9688,7 +10018,7 @@ function findExistingGoalIssue(goalId, cwd) {
9688
10018
  const marker = goalIssueMarker(goalId);
9689
10019
  const raw = gh(["issue", "list", "--state", "all", "--limit", "100", "--json", "number,body"], { cwd });
9690
10020
  const issues = JSON.parse(raw);
9691
- const match = issues.find((issue) => typeof issue.number === "number" && issue.body?.includes(marker));
10021
+ const match = issues.find((issue2) => typeof issue2.number === "number" && issue2.body?.includes(marker));
9692
10022
  return match?.number ?? null;
9693
10023
  }
9694
10024
  function createGoalIssue(goal, goalId, cwd) {
@@ -10537,13 +10867,13 @@ function ensureNeedsFixIssue(ctx, goalId, state, evidence, evidenceKey) {
10537
10867
  const evidenceState = parseGoalEvidenceState(state.extra.evidenceState);
10538
10868
  const progress = evidenceState[evidenceKey];
10539
10869
  if (progress?.issue) return state;
10540
- const issue = findExistingNeedsFixIssue(goalId, evidenceKey, ctx.cwd) ?? createNeedsFixIssue(goalId, evidenceKey, evidence, ctx.cwd);
10870
+ const issue2 = findExistingNeedsFixIssue(goalId, evidenceKey, ctx.cwd) ?? createNeedsFixIssue(goalId, evidenceKey, evidence, ctx.cwd);
10541
10871
  const nextEvidenceState = mergeGoalEvidenceProgress(evidenceState, evidenceKey, {
10542
10872
  resultClass: "needsFix",
10543
10873
  attempts: progress?.attempts ?? 1,
10544
10874
  reason: evidence.summary,
10545
- nextAction: `fix issue #${issue}`,
10546
- issue,
10875
+ nextAction: `fix issue #${issue2}`,
10876
+ issue: issue2,
10547
10877
  updatedAt: nowIso()
10548
10878
  });
10549
10879
  return {
@@ -10552,7 +10882,7 @@ function ensureNeedsFixIssue(ctx, goalId, state, evidence, evidenceKey) {
10552
10882
  ...state.extra,
10553
10883
  evidenceState: nextEvidenceState,
10554
10884
  reason: evidence.summary,
10555
- nextAction: `fix issue #${issue}`
10885
+ nextAction: `fix issue #${issue2}`
10556
10886
  }
10557
10887
  };
10558
10888
  }
@@ -10570,7 +10900,7 @@ function findExistingNeedsFixIssue(goalId, evidence, cwd) {
10570
10900
  const marker = needsFixIssueMarker(goalId, evidence);
10571
10901
  const raw = gh(["issue", "list", "--state", "all", "--limit", "100", "--json", "number,body"], { cwd });
10572
10902
  const issues = JSON.parse(raw);
10573
- const match = issues.find((issue) => typeof issue.number === "number" && issue.body?.includes(marker));
10903
+ const match = issues.find((issue2) => typeof issue2.number === "number" && issue2.body?.includes(marker));
10574
10904
  return match?.number ?? null;
10575
10905
  }
10576
10906
  function createNeedsFixIssue(goalId, evidence, result, cwd) {
@@ -11008,8 +11338,8 @@ var init_classifyByLabel = __esm({
11008
11338
  "use strict";
11009
11339
  VALID_CLASSES = /* @__PURE__ */ new Set(["feature", "bug", "spec", "chore"]);
11010
11340
  classifyByLabel = async (ctx) => {
11011
- const issue = ctx.data.issue;
11012
- const labels = issue?.labels;
11341
+ const issue2 = ctx.data.issue;
11342
+ const labels = issue2?.labels;
11013
11343
  if (!labels || labels.length === 0) return;
11014
11344
  const cfgMap = ctx.config.classify?.labelMap;
11015
11345
  const map = cfgMap ?? defaultLabelMap();
@@ -11535,19 +11865,19 @@ function buildGoalName(scope, verdict) {
11535
11865
  const verdictTag = verdict === "UNKNOWN" ? "REPORT" : verdict;
11536
11866
  return `QA: ${focus} \u2014 ${verdictTag} \u2014 ${todayIso()}`.slice(0, 240);
11537
11867
  }
11538
- function splitReport(text) {
11539
- const open = text.indexOf(REPORT_JSON_OPEN);
11868
+ function splitReport(text2) {
11869
+ const open = text2.indexOf(REPORT_JSON_OPEN);
11540
11870
  if (open < 0) {
11541
- const fallback = parseFallbackFindingsJson(text);
11871
+ const fallback = parseFallbackFindingsJson(text2);
11542
11872
  if (fallback) return fallback;
11543
- return { markdown: text.trim(), data: null, jsonError: "no JSON block marker" };
11873
+ return { markdown: text2.trim(), data: null, jsonError: "no JSON block marker" };
11544
11874
  }
11545
- const closeRel = text.slice(open + REPORT_JSON_OPEN.length).indexOf(REPORT_JSON_CLOSE);
11875
+ const closeRel = text2.slice(open + REPORT_JSON_OPEN.length).indexOf(REPORT_JSON_CLOSE);
11546
11876
  if (closeRel < 0) {
11547
- return { markdown: text.slice(0, open).trim(), data: null, jsonError: "JSON block not terminated" };
11877
+ return { markdown: text2.slice(0, open).trim(), data: null, jsonError: "JSON block not terminated" };
11548
11878
  }
11549
11879
  const closeAbs = open + REPORT_JSON_OPEN.length + closeRel;
11550
- const rawJson = text.slice(open + REPORT_JSON_OPEN.length, closeAbs).trim();
11880
+ const rawJson = text2.slice(open + REPORT_JSON_OPEN.length, closeAbs).trim();
11551
11881
  const fenced = rawJson.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/);
11552
11882
  const cleanJson = fenced ? fenced[1].trim() : rawJson;
11553
11883
  let parsed = null;
@@ -11562,11 +11892,11 @@ function splitReport(text) {
11562
11892
  } catch (err) {
11563
11893
  parseError = err instanceof Error ? err.message : String(err);
11564
11894
  }
11565
- const markdown = text.slice(0, open).trim();
11895
+ const markdown = text2.slice(0, open).trim();
11566
11896
  return { markdown, data: parsed, jsonError: parseError };
11567
11897
  }
11568
- function parseFallbackFindingsJson(text) {
11569
- const fences = [...text.matchAll(/```(?:json)?\s*([\s\S]*?)\s*```/gi)];
11898
+ function parseFallbackFindingsJson(text2) {
11899
+ const fences = [...text2.matchAll(/```(?:json)?\s*([\s\S]*?)\s*```/gi)];
11570
11900
  for (let i = fences.length - 1; i >= 0; i--) {
11571
11901
  const match = fences[i];
11572
11902
  const raw = match[1]?.trim();
@@ -11575,18 +11905,18 @@ function parseFallbackFindingsJson(text) {
11575
11905
  const parsed = JSON.parse(raw);
11576
11906
  if (!parsed || !Array.isArray(parsed.findings)) {
11577
11907
  return {
11578
- markdown: removeFence(text, match).trim(),
11908
+ markdown: removeFence(text2, match).trim(),
11579
11909
  data: null,
11580
11910
  jsonError: "fallback JSON missing 'findings' array"
11581
11911
  };
11582
11912
  }
11583
11913
  return {
11584
- markdown: removeFence(text, match).trim(),
11914
+ markdown: removeFence(text2, match).trim(),
11585
11915
  data: { findings: parsed.findings.map((f, idx) => normalizeFallbackFinding(f, idx)) }
11586
11916
  };
11587
11917
  } catch (err) {
11588
11918
  return {
11589
- markdown: removeFence(text, match).trim(),
11919
+ markdown: removeFence(text2, match).trim(),
11590
11920
  data: null,
11591
11921
  jsonError: err instanceof Error ? err.message : String(err)
11592
11922
  };
@@ -11594,10 +11924,10 @@ function parseFallbackFindingsJson(text) {
11594
11924
  }
11595
11925
  return null;
11596
11926
  }
11597
- function removeFence(text, match) {
11927
+ function removeFence(text2, match) {
11598
11928
  const start = match.index ?? -1;
11599
- if (start < 0) return text;
11600
- return `${text.slice(0, start)}${text.slice(start + match[0].length)}`;
11929
+ if (start < 0) return text2;
11930
+ return `${text2.slice(0, start)}${text2.slice(start + match[0].length)}`;
11601
11931
  }
11602
11932
  function normalizeFallbackFinding(raw, idx) {
11603
11933
  const finding = raw && typeof raw === "object" ? raw : {};
@@ -11654,9 +11984,9 @@ function loadManifest(cwd) {
11654
11984
  return { number: null, manifest: { version: 1, goals: [] } };
11655
11985
  }
11656
11986
  if (arr.length === 0) return { number: null, manifest: { version: 1, goals: [] } };
11657
- const issue = arr[0];
11658
- const manifest = parseManifestBody(issue.body);
11659
- return { number: issue.number, manifest };
11987
+ const issue2 = arr[0];
11988
+ const manifest = parseManifestBody(issue2.body);
11989
+ return { number: issue2.number, manifest };
11660
11990
  }
11661
11991
  function parseManifestBody(body) {
11662
11992
  if (!body) return { version: 1, goals: [] };
@@ -11882,8 +12212,8 @@ ${markdown}`, ctx.cwd);
11882
12212
  const failed = [];
11883
12213
  for (const f of findings) {
11884
12214
  try {
11885
- const issue = createTaskIssue(f, goalId, manifestIssueNumber, ctx.cwd);
11886
- opened.push({ ...issue, severity: f.severity });
12215
+ const issue2 = createTaskIssue(f, goalId, manifestIssueNumber, ctx.cwd);
12216
+ opened.push({ ...issue2, severity: f.severity });
11887
12217
  } catch (err) {
11888
12218
  const reason = err instanceof Error ? err.message : String(err);
11889
12219
  failed.push({ title: f.title, reason });
@@ -12025,8 +12355,8 @@ var init_deriveQaScopeFromIssue = __esm({
12025
12355
  if (!Number.isFinite(issueNumber) || issueNumber <= 0) return;
12026
12356
  let title = "";
12027
12357
  try {
12028
- const issue = getIssue(issueNumber, ctx.cwd);
12029
- title = (issue.title ?? "").trim();
12358
+ const issue2 = getIssue(issueNumber, ctx.cwd);
12359
+ title = (issue2.title ?? "").trim();
12030
12360
  } catch (err) {
12031
12361
  process.stderr.write(
12032
12362
  `[kody] deriveQaScopeFromIssue: could not read #${issueNumber}: ${err instanceof Error ? err.message : String(err)}
@@ -12631,28 +12961,28 @@ var init_dispatchCapabilityTicks = __esm({
12631
12961
  process.stdout.write(`[jobs] ticking ${issues.length} issue(s) via ${targetImplementation}
12632
12962
  `);
12633
12963
  const results = [];
12634
- for (const issue of issues) {
12635
- process.stdout.write(`[jobs] \u2192 tick #${issue.number}: ${issue.title}
12964
+ for (const issue2 of issues) {
12965
+ process.stdout.write(`[jobs] \u2192 tick #${issue2.number}: ${issue2.title}
12636
12966
  `);
12637
12967
  try {
12638
12968
  const out = await runJob(
12639
12969
  mintScheduledJob({
12640
12970
  capability: targetImplementation,
12641
12971
  implementation: targetImplementation,
12642
- cliArgs: { [issueArg]: issue.number }
12972
+ cliArgs: { [issueArg]: issue2.number }
12643
12973
  }),
12644
12974
  { cwd: ctx.cwd, config: ctx.config, verbose: ctx.verbose, quiet: ctx.quiet, chain: false }
12645
12975
  );
12646
- results.push({ issue: issue.number, exitCode: out.exitCode, reason: out.reason });
12976
+ results.push({ issue: issue2.number, exitCode: out.exitCode, reason: out.reason });
12647
12977
  if (out.exitCode !== 0) {
12648
- process.stderr.write(`[jobs] tick #${issue.number} failed (exit ${out.exitCode}): ${out.reason ?? ""}
12978
+ process.stderr.write(`[jobs] tick #${issue2.number} failed (exit ${out.exitCode}): ${out.reason ?? ""}
12649
12979
  `);
12650
12980
  }
12651
12981
  } catch (err) {
12652
12982
  const msg = err instanceof Error ? err.message : String(err);
12653
- process.stderr.write(`[jobs] tick #${issue.number} crashed: ${msg}
12983
+ process.stderr.write(`[jobs] tick #${issue2.number} crashed: ${msg}
12654
12984
  `);
12655
- results.push({ issue: issue.number, exitCode: 99, reason: msg });
12985
+ results.push({ issue: issue2.number, exitCode: 99, reason: msg });
12656
12986
  }
12657
12987
  }
12658
12988
  ctx.data.jobTickResults = results;
@@ -12921,7 +13251,14 @@ function updateExistingPr(existing, body, draft, cwd, preserveBody) {
12921
13251
  const promotedTitle = existing.title?.replace(/^\[WIP\]\s*/, "");
12922
13252
  if (promotedTitle && promotedTitle !== existing.title) {
12923
13253
  gh(
12924
- ["api", "--method", "PATCH", `repos/${owner}/${repo}/pulls/${existing.number}`, "-f", `title=${promotedTitle}`],
13254
+ [
13255
+ "api",
13256
+ "--method",
13257
+ "PATCH",
13258
+ `repos/${owner}/${repo}/pulls/${existing.number}`,
13259
+ "-f",
13260
+ `title=${promotedTitle}`
13261
+ ],
12925
13262
  { cwd, preferRepoToken: true }
12926
13263
  );
12927
13264
  }
@@ -13067,10 +13404,10 @@ var init_ensurePr = __esm({
13067
13404
  const failureReason = computeFailureReason(ctx);
13068
13405
  const isFailure = failureReason.length > 0;
13069
13406
  const changedFiles = ctx.data.changedFiles ?? [];
13070
- const issue = ctx.data.issue;
13407
+ const issue2 = ctx.data.issue;
13071
13408
  const pr = ctx.data.pr;
13072
13409
  const targetNumber = Number(ctx.data.commentTargetNumber ?? 0);
13073
- const title = issue?.title ?? pr?.title ?? `kody changes`;
13410
+ const title = issue2?.title ?? pr?.title ?? `kody changes`;
13074
13411
  const baseBranch = ctx.data.baseBranch;
13075
13412
  try {
13076
13413
  const result = ensurePr({
@@ -13121,12 +13458,12 @@ var init_failOnceTaskJob = __esm({
13121
13458
  init_jobIdentity();
13122
13459
  failOnceTaskJob = async (ctx, profile) => {
13123
13460
  ctx.skipAgent = true;
13124
- const issue = typeof ctx.args.issue === "number" ? ctx.args.issue : void 0;
13461
+ const issue2 = typeof ctx.args.issue === "number" ? ctx.args.issue : void 0;
13125
13462
  const fallbackJob = {
13126
13463
  capability: profile.action ?? profile.name,
13127
13464
  implementation: profile.name,
13128
13465
  flavor: "instant",
13129
- ...typeof issue === "number" ? { target: issue, cliArgs: { issue } } : { cliArgs: {} }
13466
+ ...typeof issue2 === "number" ? { target: issue2, cliArgs: { issue: issue2 } } : { cliArgs: {} }
13130
13467
  };
13131
13468
  const jobKey = typeof ctx.data.jobKey === "string" ? ctx.data.jobKey : stableJobKey(fallbackJob);
13132
13469
  const state = ctx.data.taskState;
@@ -14112,13 +14449,13 @@ function companyIntentPath(id) {
14112
14449
  assertIntentId(id);
14113
14450
  return `intents/${id}/intent.json`;
14114
14451
  }
14115
- function normalizeCompanyIntent(path51, raw) {
14452
+ function normalizeCompanyIntent(path52, raw) {
14116
14453
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
14117
- throw new Error(`${path51}: intent must be JSON object`);
14454
+ throw new Error(`${path52}: intent must be JSON object`);
14118
14455
  }
14119
14456
  const input = raw;
14120
14457
  const id = stringField5(input.id);
14121
- if (!id || !isCompanyIntentId(id)) throw new Error(`${path51}: invalid intent id`);
14458
+ if (!id || !isCompanyIntentId(id)) throw new Error(`${path52}: invalid intent id`);
14122
14459
  const createdAt = stringField5(input.createdAt) || nowIso();
14123
14460
  const updatedAt = stringField5(input.updatedAt) || createdAt;
14124
14461
  const description = stringField5(input.description);
@@ -14158,8 +14495,8 @@ function listCompanyIntents(config, cwd) {
14158
14495
  const records = [];
14159
14496
  for (const entry of entries) {
14160
14497
  if (entry.type !== "dir" || !entry.name || !isCompanyIntentId(entry.name)) continue;
14161
- const path51 = companyIntentPath(entry.name);
14162
- const file = readStateText(config, cwd, path51);
14498
+ const path52 = companyIntentPath(entry.name);
14499
+ const file = readStateText(config, cwd, path52);
14163
14500
  if (!file) continue;
14164
14501
  records.push({
14165
14502
  id: entry.name,
@@ -14368,14 +14705,14 @@ var init_loadIssueContext = __esm({
14368
14705
  if (!ctx.data.commentTargetNumber) ctx.data.commentTargetNumber = issueNumber;
14369
14706
  return;
14370
14707
  }
14371
- const issue = getIssue(issueNumber, ctx.cwd);
14708
+ const issue2 = getIssue(issueNumber, ctx.cwd);
14372
14709
  const cfgCtx = ctx.config.issueContext ?? {};
14373
14710
  const limit = cfgCtx.commentLimit ?? DEFAULT_COMMENT_LIMIT;
14374
14711
  const maxBytes = cfgCtx.commentMaxBytes ?? DEFAULT_COMMENT_MAX_BYTES;
14375
- const commentsFormatted = formatIssueComments(issue.comments, limit, maxBytes);
14376
- const labels = issue.labels ?? [];
14712
+ const commentsFormatted = formatIssueComments(issue2.comments, limit, maxBytes);
14713
+ const labels = issue2.labels ?? [];
14377
14714
  const labelsFormatted = labels.length === 0 ? "(no labels)" : labels.map((l) => `\`${l}\``).join(", ");
14378
- ctx.data.issue = { ...issue, commentsFormatted, labelsFormatted };
14715
+ ctx.data.issue = { ...issue2, commentsFormatted, labelsFormatted };
14379
14716
  ctx.data.commentTargetType = "issue";
14380
14717
  ctx.data.commentTargetNumber = issueNumber;
14381
14718
  };
@@ -14404,11 +14741,11 @@ var init_loadIssueStateComment = __esm({
14404
14741
  if (!owner || !repo) {
14405
14742
  throw new Error("loadIssueStateComment: ctx.config.github.owner/repo must be set");
14406
14743
  }
14407
- const issue = getIssue(issueNumber, ctx.cwd);
14744
+ const issue2 = getIssue(issueNumber, ctx.cwd);
14408
14745
  const loaded = findStateComment(owner, repo, issueNumber, marker, ctx.cwd);
14409
14746
  ctx.data.stateMarker = marker;
14410
- ctx.data.issueIntent = issue.body;
14411
- ctx.data.issueTitle = issue.title;
14747
+ ctx.data.issueIntent = issue2.body;
14748
+ ctx.data.issueTitle = issue2.title;
14412
14749
  ctx.data.issueNumber = String(issueNumber);
14413
14750
  ctx.data.issueStateComment = loaded;
14414
14751
  ctx.data.issueStateJson = loaded ? JSON.stringify(loaded.state, null, 2) : "null";
@@ -14538,15 +14875,15 @@ var init_loadLinkedFinding = __esm({
14538
14875
  if (!pr) return;
14539
14876
  const findingNumber = resolveFindingNumber(pr);
14540
14877
  if (!findingNumber) return;
14541
- let issue;
14878
+ let issue2;
14542
14879
  try {
14543
- issue = getIssue(findingNumber, ctx.cwd);
14880
+ issue2 = getIssue(findingNumber, ctx.cwd);
14544
14881
  } catch {
14545
14882
  return;
14546
14883
  }
14547
- ctx.data.linkedFinding = `Issue #${issue.number}: ${issue.title}
14884
+ ctx.data.linkedFinding = `Issue #${issue2.number}: ${issue2.title}
14548
14885
 
14549
- ${truncate(issue.body, FINDING_BODY_MAX_BYTES)}`;
14886
+ ${truncate(issue2.body, FINDING_BODY_MAX_BYTES)}`;
14550
14887
  };
14551
14888
  }
14552
14889
  });
@@ -14751,8 +15088,8 @@ async function writeGithubStateTextWithConfig(opts) {
14751
15088
  );
14752
15089
  }
14753
15090
  }
14754
- function jsonlLines(text) {
14755
- return text.split("\n").filter((line) => line.length > 0);
15091
+ function jsonlLines(text2) {
15092
+ return text2.split("\n").filter((line) => line.length > 0);
14756
15093
  }
14757
15094
  function renderJsonl(lines) {
14758
15095
  return lines.length > 0 ? `${lines.join("\n")}
@@ -15052,14 +15389,14 @@ var init_loadTaskContext = __esm({
15052
15389
  loadTaskContext = async (ctx) => {
15053
15390
  const runId = resolveRunId();
15054
15391
  const rawIssue = ctx.data.issue;
15055
- const issue = rawIssue ? {
15392
+ const issue2 = rawIssue ? {
15056
15393
  ...rawIssue,
15057
15394
  commentsFormatted: rawIssue.commentsFormatted ?? "",
15058
15395
  labelsFormatted: rawIssue.labelsFormatted ?? ""
15059
15396
  } : void 0;
15060
15397
  const taskContext = buildTaskContext({
15061
15398
  runId,
15062
- issue,
15399
+ issue: issue2,
15063
15400
  conventions: ctx.data.conventions,
15064
15401
  priorArt: typeof ctx.data.priorArt === "string" ? ctx.data.priorArt : "",
15065
15402
  memoryContext: typeof ctx.data.memoryContext === "string" ? ctx.data.memoryContext : "",
@@ -15414,6 +15751,12 @@ var init_notifyTerminal = __esm({
15414
15751
  });
15415
15752
 
15416
15753
  // src/scripts/openAgencyModelReviewPr.ts
15754
+ function isDryRun(ctx) {
15755
+ const arg = ctx.args.dry_run ?? ctx.args.dryRun;
15756
+ if (arg === true) return true;
15757
+ if (typeof arg === "string" && ["1", "true", "yes"].includes(arg.trim().toLowerCase())) return true;
15758
+ return ["1", "true", "yes"].includes((process.env.KODY_DRY_RUN ?? "").trim().toLowerCase());
15759
+ }
15417
15760
  function parseAgencyModelProposal(raw) {
15418
15761
  const jsonText = stripJsonFence(raw);
15419
15762
  let parsed;
@@ -15483,9 +15826,9 @@ function normalizeBundleFiles(ctx, bundle) {
15483
15826
  });
15484
15827
  }
15485
15828
  function stripJsonFence(raw) {
15486
- const text = raw.trim();
15487
- const fence = text.match(/^```(?:json)?\s*\n([\s\S]*?)\n```$/i);
15488
- return (fence ? fence[1] : text).trim();
15829
+ const text2 = raw.trim();
15830
+ const fence = text2.match(/^```(?:json)?\s*\n([\s\S]*?)\n```$/i);
15831
+ return (fence ? fence[1] : text2).trim();
15489
15832
  }
15490
15833
  function readIssueNumber(ctx) {
15491
15834
  const issueNumber = ctx.args.issue;
@@ -15495,9 +15838,9 @@ function readIssueNumber(ctx) {
15495
15838
  return issueNumber;
15496
15839
  }
15497
15840
  function readRequiredJsonString(value, field) {
15498
- const text = readJsonString(value, field).trim();
15499
- if (!text) throw new Error(`openAgencyModelReviewPr: ${field} must be a non-empty string`);
15500
- return text;
15841
+ const text2 = readJsonString(value, field).trim();
15842
+ if (!text2) throw new Error(`openAgencyModelReviewPr: ${field} must be a non-empty string`);
15843
+ return text2;
15501
15844
  }
15502
15845
  function readJsonString(value, field) {
15503
15846
  if (typeof value !== "string") throw new Error(`openAgencyModelReviewPr: ${field} must be a string`);
@@ -15577,6 +15920,16 @@ var init_openAgencyModelReviewPr = __esm({
15577
15920
  const stateRepo = parseStateRepo(ctx.config);
15578
15921
  const baseBranch = "main";
15579
15922
  const branch = buildStatePrBranchName(sourceLabel, issueNumber, bundle.title);
15923
+ if (isDryRun(ctx)) {
15924
+ ctx.data.agencyModelReviewPr = {
15925
+ dryRun: true,
15926
+ repo: `${stateRepo.owner}/${stateRepo.repo}`,
15927
+ branch,
15928
+ base: baseBranch,
15929
+ files: normalizedFiles.map((file) => file.targetPath)
15930
+ };
15931
+ return;
15932
+ }
15580
15933
  const baseRef = ghJson(
15581
15934
  ["api", `/repos/${stateRepo.owner}/${stateRepo.repo}/git/ref/heads/${baseBranch}`],
15582
15935
  ctx.cwd
@@ -15689,10 +16042,10 @@ function ensureLabel2(cwd) {
15689
16042
  return false;
15690
16043
  }
15691
16044
  }
15692
- function markIssueWithReportLabel(issue, cwd) {
16045
+ function markIssueWithReportLabel(issue2, cwd) {
15693
16046
  if (!ensureLabel2(cwd)) return;
15694
16047
  try {
15695
- gh(["issue", "edit", String(issue), "--add-label", QA_LABEL], { cwd });
16048
+ gh(["issue", "edit", String(issue2), "--add-label", QA_LABEL], { cwd });
15696
16049
  } catch {
15697
16050
  }
15698
16051
  }
@@ -15831,13 +16184,13 @@ function isPartialEnvelope(x) {
15831
16184
  function escapeRegex(s) {
15832
16185
  return s.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
15833
16186
  }
15834
- function extractFencedBlock(text, label) {
16187
+ function extractFencedBlock(text2, label) {
15835
16188
  const re = new RegExp(`\`\`\`${escapeRegex(label)}\\s*\\n([\\s\\S]*?)\\n\`\`\``, "m");
15836
- const m = re.exec(text);
16189
+ const m = re.exec(text2);
15837
16190
  return m ? m[1].trim() : null;
15838
16191
  }
15839
- function extractNextStateFromText(text, fenceLabel, prevRev) {
15840
- const inner = extractFencedBlock(text, fenceLabel);
16192
+ function extractNextStateFromText(text2, fenceLabel, prevRev) {
16193
+ const inner = extractFencedBlock(text2, fenceLabel);
15841
16194
  if (inner === null) {
15842
16195
  return { error: `missing \`${fenceLabel}\` fenced block` };
15843
16196
  }
@@ -15956,15 +16309,15 @@ var init_parseJobStateFromAgentResult = __esm({
15956
16309
  });
15957
16310
 
15958
16311
  // src/scripts/parseReproOutput.ts
15959
- function extractTestPath(text) {
15960
- const m = text.match(/^[\s>*_#`~-]*TEST_PATH[\s>*_#`~-]*\s*:\s*(.+?)\s*$/im);
16312
+ function extractTestPath(text2) {
16313
+ const m = text2.match(/^[\s>*_#`~-]*TEST_PATH[\s>*_#`~-]*\s*:\s*(.+?)\s*$/im);
15961
16314
  if (!m) return "";
15962
16315
  return stripMarkdownEmphasis2(m[1] ?? "");
15963
16316
  }
15964
- function extractFailureSignatureBlock(text) {
15965
- const startIdx = text.search(/(?:^|\n)[ \t]*FAILURE_SIGNATURE\s*:[ \t]*/i);
16317
+ function extractFailureSignatureBlock(text2) {
16318
+ const startIdx = text2.search(/(?:^|\n)[ \t]*FAILURE_SIGNATURE\s*:[ \t]*/i);
15966
16319
  if (startIdx === -1) return "";
15967
- const afterMarker = text.slice(startIdx).replace(/^[\s\S]*?FAILURE_SIGNATURE\s*:[ \t]*\n?/i, "");
16320
+ const afterMarker = text2.slice(startIdx).replace(/^[\s\S]*?FAILURE_SIGNATURE\s*:[ \t]*\n?/i, "");
15968
16321
  const stopRe = /(?:^|\n)[ \t]*(?:COMMIT_MSG|PR_SUMMARY|TEST_PATH)\s*:/i;
15969
16322
  const stopIdx = afterMarker.search(stopRe);
15970
16323
  let block = stopIdx === -1 ? afterMarker : afterMarker.slice(0, stopIdx);
@@ -15981,14 +16334,14 @@ function normalizeFailureSignatureBlock(block) {
15981
16334
  const jsonObject = extractFirstJsonObject(s);
15982
16335
  return jsonObject || s;
15983
16336
  }
15984
- function extractFirstJsonObject(text) {
15985
- const start = text.indexOf("{");
16337
+ function extractFirstJsonObject(text2) {
16338
+ const start = text2.indexOf("{");
15986
16339
  if (start === -1) return "";
15987
16340
  let depth = 0;
15988
16341
  let inString = false;
15989
16342
  let escaped = false;
15990
- for (let i = start; i < text.length; i++) {
15991
- const ch = text[i];
16343
+ for (let i = start; i < text2.length; i++) {
16344
+ const ch = text2[i];
15992
16345
  if (inString) {
15993
16346
  if (escaped) {
15994
16347
  escaped = false;
@@ -16005,7 +16358,7 @@ function extractFirstJsonObject(text) {
16005
16358
  depth++;
16006
16359
  } else if (ch === "}") {
16007
16360
  depth--;
16008
- if (depth === 0) return text.slice(start, i + 1).trim();
16361
+ if (depth === 0) return text2.slice(start, i + 1).trim();
16009
16362
  }
16010
16363
  }
16011
16364
  return "";
@@ -16031,9 +16384,9 @@ var init_parseReproOutput = __esm({
16031
16384
  "use strict";
16032
16385
  parseReproOutput = async (ctx, _profile, agentResult) => {
16033
16386
  if (!agentResult || ctx.data.agentDone === false) return;
16034
- const text = agentResult.finalText ?? "";
16035
- const testPath = extractTestPath(text);
16036
- const signatureRaw = extractFailureSignatureBlock(text);
16387
+ const text2 = agentResult.finalText ?? "";
16388
+ const testPath = extractTestPath(text2);
16389
+ const signatureRaw = extractFailureSignatureBlock(text2);
16037
16390
  if (!testPath) {
16038
16391
  downgrade(ctx, "reproduce missing TEST_PATH line in final message");
16039
16392
  return;
@@ -16212,8 +16565,8 @@ var init_planTaskJobs = __esm({
16212
16565
  ctx.output.reason = "planTaskJobs requires --issue";
16213
16566
  return;
16214
16567
  }
16215
- const issue = ctx.data.issue;
16216
- const specs = parseTaskJobSpecs(issue?.body ?? "");
16568
+ const issue2 = ctx.data.issue;
16569
+ const specs = parseTaskJobSpecs(issue2?.body ?? "");
16217
16570
  if (specs.length === 0) {
16218
16571
  ctx.skipAgent = true;
16219
16572
  ctx.output.exitCode = 64;
@@ -16543,8 +16896,8 @@ var init_promoteQaGoal = __esm({
16543
16896
  }
16544
16897
  let report;
16545
16898
  try {
16546
- const issue = getIssue(issueNum, ctx.cwd);
16547
- const reportComment = [...issue.comments].reverse().find((c) => c.body.includes(REPORT_JSON_OPEN2));
16899
+ const issue2 = getIssue(issueNum, ctx.cwd);
16900
+ const reportComment = [...issue2.comments].reverse().find((c) => c.body.includes(REPORT_JSON_OPEN2));
16548
16901
  if (!reportComment) {
16549
16902
  ctx.output.exitCode = 3;
16550
16903
  ctx.output.reason = `no QA report (${REPORT_JSON_OPEN2} \u2026) found on issue #${issueNum}`;
@@ -16611,9 +16964,9 @@ function latestResult(raw, agentResult) {
16611
16964
  function recordField6(value) {
16612
16965
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
16613
16966
  }
16614
- function resolveDotted(root, path51) {
16615
- if (!path51) return void 0;
16616
- return path51.split(".").reduce((value, key) => recordField6(value)?.[key], root);
16967
+ function resolveDotted(root, path52) {
16968
+ if (!path52) return void 0;
16969
+ return path52.split(".").reduce((value, key) => recordField6(value)?.[key], root);
16617
16970
  }
16618
16971
  function stringValue4(value) {
16619
16972
  return typeof value === "string" && value.trim() ? value.trim() : null;
@@ -16655,13 +17008,7 @@ var init_publishReport = __esm({
16655
17008
  ...publication.reviewArea ? { reviewArea: publication.reviewArea } : {}
16656
17009
  });
16657
17010
  const runId = generatedAt.replace(/\.\d{3}Z$/, "Z").replace(/:/g, "-");
16658
- writeStateText(
16659
- ctx.config,
16660
- ctx.cwd,
16661
- `reports/${slug}/runs/${runId}.md`,
16662
- markdown,
16663
- `chore(reports): add ${slug} run`
16664
- );
17011
+ writeStateText(ctx.config, ctx.cwd, `reports/${slug}/runs/${runId}.md`, markdown, `chore(reports): add ${slug} run`);
16665
17012
  };
16666
17013
  }
16667
17014
  });
@@ -17369,15 +17716,15 @@ var init_runFlow = __esm({
17369
17716
  init_issue();
17370
17717
  runFlow = async (ctx) => {
17371
17718
  const issueNumber = ctx.args.issue;
17372
- const issue = getIssue(issueNumber, ctx.cwd);
17719
+ const issue2 = getIssue(issueNumber, ctx.cwd);
17373
17720
  const cfgCtx = ctx.config.issueContext ?? {};
17374
17721
  const commentsFormatted = formatIssueComments(
17375
- issue.comments,
17722
+ issue2.comments,
17376
17723
  cfgCtx.commentLimit ?? DEFAULT_COMMENT_LIMIT,
17377
17724
  cfgCtx.commentMaxBytes ?? DEFAULT_COMMENT_MAX_BYTES
17378
17725
  );
17379
- ctx.data.issue = { ...issue, commentsFormatted };
17380
- if (issue.isPullRequest) {
17726
+ ctx.data.issue = { ...issue2, commentsFormatted };
17727
+ if (issue2.isPullRequest) {
17381
17728
  ctx.data.commentTargetType = "pr";
17382
17729
  ctx.data.commentTargetNumber = issueNumber;
17383
17730
  ctx.skipAgent = true;
@@ -17401,7 +17748,7 @@ var init_runFlow = __esm({
17401
17748
  }
17402
17749
  const branchInfo = ensureFeatureBranch(
17403
17750
  issueNumber,
17404
- issue.title,
17751
+ issue2.title,
17405
17752
  ctx.config.git.defaultBranch,
17406
17753
  ctx.cwd,
17407
17754
  base ?? void 0
@@ -17650,8 +17997,8 @@ async function flyCreateApp(name, orgSlug, token) {
17650
17997
  });
17651
17998
  if (res.status === 422) return;
17652
17999
  if (!res.ok) {
17653
- const text = await res.text().catch(() => "");
17654
- throw new Error(`createApp ${name}: ${res.status} ${text.slice(0, 200)}`);
18000
+ const text2 = await res.text().catch(() => "");
18001
+ throw new Error(`createApp ${name}: ${res.status} ${text2.slice(0, 200)}`);
17655
18002
  }
17656
18003
  }
17657
18004
  async function flyAllocateSharedIps(appName, token) {
@@ -17758,9 +18105,9 @@ async function flyCreatePreviewMachine(args, token) {
17758
18105
  const { id } = await res.json();
17759
18106
  return id;
17760
18107
  }
17761
- const text = await res.text().catch(() => "");
17762
- lastErr = new Error(`createPreviewMachine ${res.status}: ${text.slice(0, 300)}`);
17763
- if (!/MANIFEST_UNKNOWN|manifest unknown/i.test(text)) break;
18108
+ const text2 = await res.text().catch(() => "");
18109
+ lastErr = new Error(`createPreviewMachine ${res.status}: ${text2.slice(0, 300)}`);
18110
+ if (!/MANIFEST_UNKNOWN|manifest unknown/i.test(text2)) break;
17764
18111
  await new Promise((r) => setTimeout(r, 2e3 * (attempt + 1)));
17765
18112
  }
17766
18113
  throw lastErr ?? new Error("createPreviewMachine failed (unknown)");
@@ -18199,8 +18546,8 @@ var init_setCommentTarget = __esm({
18199
18546
 
18200
18547
  // src/scripts/setLifecycleLabel.ts
18201
18548
  function resolveTargetNumber(args) {
18202
- const issue = args.issue;
18203
- if (typeof issue === "number" && Number.isFinite(issue)) return issue;
18549
+ const issue2 = args.issue;
18550
+ if (typeof issue2 === "number" && Number.isFinite(issue2)) return issue2;
18204
18551
  const pr = args.pr;
18205
18552
  if (typeof pr === "number" && Number.isFinite(pr)) return pr;
18206
18553
  return void 0;
@@ -18415,9 +18762,10 @@ var init_syncFlow = __esm({
18415
18762
  });
18416
18763
 
18417
18764
  // src/scripts/validateAgencyModelProposal.ts
18418
- function validateModelBundle(bundle, expectedKind) {
18765
+ import * as path43 from "path";
18766
+ function validateModelBundle(bundle, expectedKind, options = {}) {
18419
18767
  const failures = [];
18420
- validateOneModel(bundle.model, bundle.files, "model", true, failures, expectedKind);
18768
+ validateOneModel(bundle.model, bundle.files, "model", true, failures, expectedKind, options);
18421
18769
  return failures;
18422
18770
  }
18423
18771
  function readExpectedModelKind(args) {
@@ -18427,7 +18775,7 @@ function readExpectedModelKind(args) {
18427
18775
  "validateAgencyModelProposal: with.modelKind must be intent, operation, agent, capability, goal, agentLoop, or workflow"
18428
18776
  );
18429
18777
  }
18430
- function validateOneModel(rawModel, files, label, strictSingleModel, failures, expectedKind) {
18778
+ function validateOneModel(rawModel, files, label, strictSingleModel, failures, expectedKind, options = {}) {
18431
18779
  if (!rawModel || typeof rawModel !== "object" || Array.isArray(rawModel)) {
18432
18780
  failures.push(`${label} must be an object`);
18433
18781
  return;
@@ -18445,11 +18793,11 @@ function validateOneModel(rawModel, files, label, strictSingleModel, failures, e
18445
18793
  for (const doc of REQUIRED_DOCS[kind]) {
18446
18794
  if (!docsUsed.includes(doc)) failures.push(`${label} docsUsed missing ${doc}`);
18447
18795
  }
18448
- validateFilesForKind(kind, slug, files, strictSingleModel, failures);
18796
+ validateFilesForKind(kind, slug, files, strictSingleModel, failures, options);
18449
18797
  validateModelShape(kind, model, files, slug, failures);
18450
18798
  }
18451
18799
  }
18452
- function validateFilesForKind(kind, slug, files, strictSingleModel, failures) {
18800
+ function validateFilesForKind(kind, slug, files, strictSingleModel, failures, options) {
18453
18801
  const paths = files.map((file) => normalizeBundlePath(file.path));
18454
18802
  if (paths.some((filePath) => filePath === "implementations" || filePath.startsWith("implementations/"))) {
18455
18803
  failures.push("files must not use obsolete implementation storage");
@@ -18496,6 +18844,7 @@ function validateFilesForKind(kind, slug, files, strictSingleModel, failures) {
18496
18844
  }
18497
18845
  if (kind === "workflow") {
18498
18846
  requirePath(paths, `capabilities/${slug}/profile.json`, "workflow capability profile", failures);
18847
+ requirePath(paths, `capabilities/${slug}/capability.md`, "workflow capability body", failures);
18499
18848
  const profile = parseJsonFile(files, `capabilities/${slug}/profile.json`, failures);
18500
18849
  if (profile) {
18501
18850
  if (profile.capabilityKind !== void 0) {
@@ -18505,6 +18854,30 @@ function validateFilesForKind(kind, slug, files, strictSingleModel, failures) {
18505
18854
  const hasTopLevelSteps = Array.isArray(profile.steps) && profile.steps.length > 0;
18506
18855
  if (!hasWorkflowObject && !hasTopLevelSteps) {
18507
18856
  failures.push("workflow profile must include workflow object or top-level steps");
18857
+ } else {
18858
+ const workflow = hasWorkflowObject ? profile.workflow : { steps: profile.steps, ...profile.startAt !== void 0 ? { startAt: profile.startAt } : {} };
18859
+ const known = options.capabilityRoot ? getCapabilityRoots(options.capabilityRoot).flatMap((root) => listCapabilityFolderSlugs(root)) : [];
18860
+ const uniqueKnown = [...new Set(known)];
18861
+ const capabilityInputs = /* @__PURE__ */ new Map();
18862
+ if (options.capabilityRoot) {
18863
+ for (const capability of uniqueKnown) {
18864
+ const inputs = getCapabilityActionInputs(capability, options.capabilityRoot);
18865
+ if (inputs) {
18866
+ capabilityInputs.set(
18867
+ capability,
18868
+ new Set(inputs.flatMap((input) => [input.name, input.flag.replace(/^--/, "")]))
18869
+ );
18870
+ }
18871
+ }
18872
+ }
18873
+ failures.push(
18874
+ ...formatWorkflowValidationIssues(
18875
+ validateWorkflow(workflow, {
18876
+ ...uniqueKnown.length > 0 ? { knownCapabilities: new Set(uniqueKnown) } : {},
18877
+ ...capabilityInputs.size > 0 ? { capabilityInputs } : {}
18878
+ })
18879
+ )
18880
+ );
18508
18881
  }
18509
18882
  }
18510
18883
  }
@@ -18692,6 +19065,9 @@ var REQUIRED_DOCS, validateAgencyModelProposal;
18692
19065
  var init_validateAgencyModelProposal = __esm({
18693
19066
  "src/scripts/validateAgencyModelProposal.ts"() {
18694
19067
  "use strict";
19068
+ init_capabilityFolders();
19069
+ init_registry();
19070
+ init_workflowValidation();
18695
19071
  init_openAgencyModelReviewPr();
18696
19072
  REQUIRED_DOCS = {
18697
19073
  intent: ["docs/intents.md", "docs/engine-company.md"],
@@ -18707,7 +19083,9 @@ var init_validateAgencyModelProposal = __esm({
18707
19083
  const raw = String(ctx.data.prSummary ?? "");
18708
19084
  const bundle = parseAgencyModelProposal(raw);
18709
19085
  const expectedKind = readExpectedModelKind(args);
18710
- const failures = validateModelBundle(bundle, expectedKind);
19086
+ const failures = validateModelBundle(bundle, expectedKind, {
19087
+ capabilityRoot: path43.join(ctx.cwd, ".kody", "capabilities")
19088
+ });
18711
19089
  if (failures.length > 0) {
18712
19090
  throw new Error(`validateAgencyModelProposal: ${failures.join("; ")}`);
18713
19091
  }
@@ -19266,9 +19644,9 @@ var init_writeAgentRunSummary = __esm({
19266
19644
  const summaryPath = process.env.GITHUB_STEP_SUMMARY;
19267
19645
  if (!summaryPath) return;
19268
19646
  const implementation = profile.name;
19269
- const issue = ctx.args.issue;
19647
+ const issue2 = ctx.args.issue;
19270
19648
  const pr = ctx.args.pr;
19271
- const target = issue ? `issue #${issue}` : pr ? `PR #${pr}` : "(unknown)";
19649
+ const target = issue2 ? `issue #${issue2}` : pr ? `PR #${pr}` : "(unknown)";
19272
19650
  const prUrl = ctx.output.prUrl;
19273
19651
  const exitCode = ctx.output.exitCode ?? 0;
19274
19652
  const reason = ctx.output.reason;
@@ -19608,38 +19986,38 @@ import { execFileSync as execFileSync24 } from "child_process";
19608
19986
  import * as crypto3 from "crypto";
19609
19987
  import * as fs45 from "fs";
19610
19988
  import * as os7 from "os";
19611
- import * as path43 from "path";
19989
+ import * as path44 from "path";
19612
19990
  function writeLocalFile(cwd, relativePath, content) {
19613
- const fullPath = path43.join(cwd, relativePath);
19614
- fs45.mkdirSync(path43.dirname(fullPath), { recursive: true });
19991
+ const fullPath = path44.join(cwd, relativePath);
19992
+ fs45.mkdirSync(path44.dirname(fullPath), { recursive: true });
19615
19993
  fs45.writeFileSync(fullPath, content);
19616
19994
  }
19617
19995
  function copyPath(source, target) {
19618
19996
  const st = fs45.lstatSync(source);
19619
19997
  fs45.rmSync(target, { recursive: true, force: true });
19620
19998
  if (st.isSymbolicLink()) return;
19621
- fs45.mkdirSync(path43.dirname(target), { recursive: true });
19999
+ fs45.mkdirSync(path44.dirname(target), { recursive: true });
19622
20000
  fs45.cpSync(source, target, { recursive: true, force: true });
19623
20001
  }
19624
20002
  function overlayDirectoryChildren(cwd, sourceDir, localDir) {
19625
20003
  if (!fs45.existsSync(sourceDir)) return;
19626
20004
  for (const entry of fs45.readdirSync(sourceDir, { withFileTypes: true })) {
19627
- const source = path43.join(sourceDir, entry.name);
19628
- const target = path43.join(cwd, localDir, entry.name);
20005
+ const source = path44.join(sourceDir, entry.name);
20006
+ const target = path44.join(cwd, localDir, entry.name);
19629
20007
  copyPath(source, target);
19630
20008
  }
19631
20009
  }
19632
20010
  function hydrateStateWorkspace(config, cwd) {
19633
20011
  if (process.env.VITEST && process.env[TEST_FETCH_ENV] !== "1") return;
19634
20012
  const parsed = parseStateRepo(config);
19635
- const hydrateKey = `${path43.resolve(cwd)}|${parsed.owner}/${parsed.repo}|${parsed.basePath}|${parsed.branch}`;
20013
+ const hydrateKey = `${path44.resolve(cwd)}|${parsed.owner}/${parsed.repo}|${parsed.basePath}|${parsed.branch}`;
19636
20014
  if (hydratedWorkspaces.has(hydrateKey)) return;
19637
20015
  const snapshotRoot = fetchStateSnapshot(parsed);
19638
20016
  for (const mapping of DIR_MAPPINGS) {
19639
- overlayDirectoryChildren(cwd, path43.join(snapshotRoot, mapping.stateDir), mapping.localDir);
20017
+ overlayDirectoryChildren(cwd, path44.join(snapshotRoot, mapping.stateDir), mapping.localDir);
19640
20018
  }
19641
20019
  for (const mapping of FILE_MAPPINGS) {
19642
- const source = path43.join(snapshotRoot, mapping.statePath);
20020
+ const source = path44.join(snapshotRoot, mapping.statePath);
19643
20021
  if (fs45.existsSync(source) && !fs45.lstatSync(source).isSymbolicLink() && fs45.statSync(source).isFile()) {
19644
20022
  writeLocalFile(cwd, mapping.localPath, fs45.readFileSync(source, "utf-8"));
19645
20023
  }
@@ -19647,11 +20025,11 @@ function hydrateStateWorkspace(config, cwd) {
19647
20025
  hydratedWorkspaces.add(hydrateKey);
19648
20026
  }
19649
20027
  function fetchStateSnapshot(parsed) {
19650
- const cacheDir = path43.join(cacheRoot2(), cacheKey3(parsed));
20028
+ const cacheDir = path44.join(cacheRoot2(), cacheKey3(parsed));
19651
20029
  const url = `https://github.com/${parsed.owner}/${parsed.repo}.git`;
19652
20030
  try {
19653
- fs45.mkdirSync(path43.dirname(cacheDir), { recursive: true });
19654
- if (!fs45.existsSync(path43.join(cacheDir, ".git"))) {
20031
+ fs45.mkdirSync(path44.dirname(cacheDir), { recursive: true });
20032
+ if (!fs45.existsSync(path44.join(cacheDir, ".git"))) {
19655
20033
  fs45.rmSync(cacheDir, { recursive: true, force: true });
19656
20034
  runGit3(["clone", "--no-checkout", "--filter=blob:none", url, cacheDir]);
19657
20035
  }
@@ -19667,10 +20045,10 @@ function fetchStateSnapshot(parsed) {
19667
20045
  `stateWorkspace: failed to fetch ${parsed.owner}/${parsed.repo}:${parsed.basePath}@${parsed.branch}: ${msg}`
19668
20046
  );
19669
20047
  }
19670
- return path43.join(cacheDir, parsed.basePath);
20048
+ return path44.join(cacheDir, parsed.basePath);
19671
20049
  }
19672
20050
  function cacheRoot2() {
19673
- return process.env[CACHE_ENV2]?.trim() || path43.join(os7.homedir(), ".cache", "kody", "state-repo");
20051
+ return process.env[CACHE_ENV2]?.trim() || path44.join(os7.homedir(), ".cache", "kody", "state-repo");
19674
20052
  }
19675
20053
  function cacheKey3(parsed) {
19676
20054
  return crypto3.createHash("sha256").update(`${parsed.owner}/${parsed.repo}#${parsed.branch}#${parsed.basePath}`).digest("hex").slice(0, 24);
@@ -19710,16 +20088,16 @@ var init_stateWorkspace = __esm({
19710
20088
  "use strict";
19711
20089
  init_stateRepo();
19712
20090
  DIR_MAPPINGS = [
19713
- { stateDir: "capabilities", localDir: path43.join(".kody", "capabilities") },
19714
- { stateDir: "agents", localDir: path43.join(".kody", "agents") },
19715
- { stateDir: "context", localDir: path43.join(".kody", "context") },
19716
- { stateDir: "memory", localDir: path43.join(".kody", "memory") }
20091
+ { stateDir: "capabilities", localDir: path44.join(".kody", "capabilities") },
20092
+ { stateDir: "agents", localDir: path44.join(".kody", "agents") },
20093
+ { stateDir: "context", localDir: path44.join(".kody", "context") },
20094
+ { stateDir: "memory", localDir: path44.join(".kody", "memory") }
19717
20095
  ];
19718
20096
  FILE_MAPPINGS = [
19719
- { statePath: "instructions.md", localPath: path43.join(".kody", "instructions.md") },
19720
- { statePath: "system-prompt.md", localPath: path43.join(".kody", "system-prompt.md") },
19721
- { statePath: "variables.json", localPath: path43.join(".kody", "variables.json") },
19722
- { statePath: "secrets.enc", localPath: path43.join(".kody", "secrets.enc") }
20097
+ { statePath: "instructions.md", localPath: path44.join(".kody", "instructions.md") },
20098
+ { statePath: "system-prompt.md", localPath: path44.join(".kody", "system-prompt.md") },
20099
+ { statePath: "variables.json", localPath: path44.join(".kody", "variables.json") },
20100
+ { statePath: "secrets.enc", localPath: path44.join(".kody", "secrets.enc") }
19723
20101
  ];
19724
20102
  CACHE_ENV2 = "KODY_STATE_REPO_CACHE";
19725
20103
  TEST_FETCH_ENV = "KODY_STATE_WORKSPACE_FETCH_FOR_TESTS";
@@ -19795,7 +20173,7 @@ var init_tools = __esm({
19795
20173
  import { spawn as spawn7 } from "child_process";
19796
20174
  import * as fs46 from "fs";
19797
20175
  import * as os8 from "os";
19798
- import * as path44 from "path";
20176
+ import * as path45 from "path";
19799
20177
  function isMutatingPostflight(scriptName) {
19800
20178
  return MUTATING_POSTFLIGHTS.has(scriptName ?? "");
19801
20179
  }
@@ -19823,9 +20201,9 @@ function collectShellSideChannels(ctx, stdout) {
19823
20201
  }
19824
20202
  }
19825
20203
  function operatorRequestBlock(why) {
19826
- const text = why.trim();
19827
- if (!text) return null;
19828
- const safe = text.replace(/-{3,}\s*END UNTRUSTED INPUT\s*-{3,}/gi, "[END UNTRUSTED INPUT]");
20204
+ const text2 = why.trim();
20205
+ if (!text2) return null;
20206
+ const safe = text2.replace(/-{3,}\s*END UNTRUSTED INPUT\s*-{3,}/gi, "[END UNTRUSTED INPUT]");
19829
20207
  return [
19830
20208
  "## The request that triggered this run",
19831
20209
  "",
@@ -20018,7 +20396,7 @@ async function runImplementation(profileName, input) {
20018
20396
  const jobWhyBlock = typeof ctx.data.jobWhy === "string" ? operatorRequestBlock(ctx.data.jobWhy) : null;
20019
20397
  const jobRefBlock = jobReferenceBlock(profileName, profile, ctx.data);
20020
20398
  const invokeAgent = async (prompt) => {
20021
- const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path44.isAbsolute(p) ? p : path44.resolve(profile.dir, p)).filter((p) => p.length > 0);
20399
+ const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path45.isAbsolute(p) ? p : path45.resolve(profile.dir, p)).filter((p) => p.length > 0);
20022
20400
  const syntheticPath = ctx.data.syntheticPluginPath;
20023
20401
  const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
20024
20402
  const agents = loadSubagents(profile);
@@ -20267,7 +20645,8 @@ async function runImplementation(profileName, input) {
20267
20645
  nextDispatch: ctx.output.nextDispatch,
20268
20646
  nextJob: ctx.output.nextJob,
20269
20647
  afterNextJob: ctx.output.afterNextJob,
20270
- taskState: ctx.data.taskState
20648
+ taskState: ctx.data.taskState,
20649
+ capabilityResults: Array.isArray(ctx.data.capabilityResults) ? ctx.data.capabilityResults : void 0
20271
20650
  });
20272
20651
  } catch (err) {
20273
20652
  const msg = err instanceof Error ? err.message : String(err);
@@ -20455,13 +20834,13 @@ function clearStampedLifecycleLabels(profile, ctx) {
20455
20834
  function resolveProfilePath(profileName) {
20456
20835
  const found = resolveImplementation(profileName);
20457
20836
  if (found) return found;
20458
- const here = path44.dirname(new URL(import.meta.url).pathname);
20837
+ const here = path45.dirname(new URL(import.meta.url).pathname);
20459
20838
  const candidates = [
20460
- path44.join(here, "implementations", profileName, "profile.json"),
20839
+ path45.join(here, "implementations", profileName, "profile.json"),
20461
20840
  // same-dir sibling (dev)
20462
- path44.join(here, "..", "implementations", profileName, "profile.json"),
20841
+ path45.join(here, "..", "implementations", profileName, "profile.json"),
20463
20842
  // up one (prod: dist/bin → dist/implementations)
20464
- path44.join(here, "..", "src", "implementations", profileName, "profile.json")
20843
+ path45.join(here, "..", "src", "implementations", profileName, "profile.json")
20465
20844
  // fallback
20466
20845
  ];
20467
20846
  for (const c of candidates) {
@@ -20580,7 +20959,7 @@ function resolveShellTimeoutMs(entry) {
20580
20959
  }
20581
20960
  async function runShellEntry(entry, ctx, profile) {
20582
20961
  const shellName = entry.shell;
20583
- const shellPath = path44.join(profile.dir, shellName);
20962
+ const shellPath = path45.join(profile.dir, shellName);
20584
20963
  if (!fs46.existsSync(shellPath)) {
20585
20964
  ctx.skipAgent = true;
20586
20965
  ctx.output.exitCode = 99;
@@ -20588,7 +20967,7 @@ async function runShellEntry(entry, ctx, profile) {
20588
20967
  return;
20589
20968
  }
20590
20969
  const positional = entry.with ? Object.values(entry.with).map((v) => String(v)) : [];
20591
- const outputFile = path44.join(
20970
+ const outputFile = path45.join(
20592
20971
  os8.tmpdir(),
20593
20972
  `kody-shell-output-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
20594
20973
  );
@@ -20756,6 +21135,68 @@ var init_executor = __esm({
20756
21135
  }
20757
21136
  });
20758
21137
 
21138
+ // src/workflowRunState.ts
21139
+ function workflowRunStatePath(workflowId, runId) {
21140
+ if (!SAFE_ID.test(workflowId)) throw new Error(`invalid workflow id ${workflowId}`);
21141
+ if (!SAFE_ID.test(runId)) throw new Error(`invalid workflow run id ${runId}`);
21142
+ return `workflows/${workflowId}/runs/${runId}.json`;
21143
+ }
21144
+ function parseWorkflowRunState(raw) {
21145
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
21146
+ const state = raw;
21147
+ if (state.status !== "running" && state.status !== "blocked" && state.status !== "failed" && state.status !== "done")
21148
+ return null;
21149
+ const completedStepIds = Array.isArray(state.completedStepIds) ? state.completedStepIds.filter((value) => typeof value === "string") : [];
21150
+ const transitionCounts = state.transitionCounts && typeof state.transitionCounts === "object" && !Array.isArray(state.transitionCounts) ? Object.fromEntries(
21151
+ Object.entries(state.transitionCounts).filter(
21152
+ (entry) => typeof entry[1] === "number" && Number.isInteger(entry[1]) && entry[1] >= 0
21153
+ )
21154
+ ) : {};
21155
+ const facts = state.facts && typeof state.facts === "object" && !Array.isArray(state.facts) ? state.facts : {};
21156
+ const evidenceEntries = state.evidence && typeof state.evidence === "object" && !Array.isArray(state.evidence) ? Object.entries(state.evidence).filter((entry) => typeof entry[1] === "boolean") : [];
21157
+ const artifacts = Array.isArray(state.artifacts) ? state.artifacts.filter(
21158
+ (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")
21159
+ ) : [];
21160
+ return {
21161
+ status: state.status,
21162
+ ...typeof state.currentStepId === "string" ? { currentStepId: state.currentStepId } : {},
21163
+ completedStepIds,
21164
+ transitionCounts,
21165
+ facts: { ...facts },
21166
+ evidence: Object.fromEntries(evidenceEntries),
21167
+ artifacts: artifacts.map((artifact) => ({ ...artifact })),
21168
+ ...typeof state.blocker === "string" ? { blocker: state.blocker } : {}
21169
+ };
21170
+ }
21171
+ function readWorkflowRunState(config, cwd, workflowId, runId) {
21172
+ const file = readStateText(config, cwd, workflowRunStatePath(workflowId, runId));
21173
+ if (!file) return null;
21174
+ try {
21175
+ return parseWorkflowRunState(JSON.parse(file.content));
21176
+ } catch {
21177
+ return null;
21178
+ }
21179
+ }
21180
+ function writeWorkflowRunState(config, cwd, workflowId, runId, state) {
21181
+ const path52 = workflowRunStatePath(workflowId, runId);
21182
+ upsertStateText(
21183
+ config,
21184
+ cwd,
21185
+ path52,
21186
+ `${JSON.stringify(state, null, 2)}
21187
+ `,
21188
+ `chore(workflows): update ${workflowId} run ${runId}`
21189
+ );
21190
+ }
21191
+ var SAFE_ID;
21192
+ var init_workflowRunState = __esm({
21193
+ "src/workflowRunState.ts"() {
21194
+ "use strict";
21195
+ init_stateRepo();
21196
+ SAFE_ID = /^[a-z0-9][a-z0-9_-]{0,79}$/;
21197
+ }
21198
+ });
21199
+
20759
21200
  // src/job.ts
20760
21201
  var job_exports = {};
20761
21202
  __export(job_exports, {
@@ -20768,7 +21209,7 @@ __export(job_exports, {
20768
21209
  stableJobKey: () => stableJobKey,
20769
21210
  validateJob: () => validateJob
20770
21211
  });
20771
- import * as path45 from "path";
21212
+ import * as path46 from "path";
20772
21213
  function newJobId(flavor) {
20773
21214
  localJobSeq += 1;
20774
21215
  const runId = process.env.GITHUB_RUN_ID;
@@ -20800,6 +21241,8 @@ function validateJob(input) {
20800
21241
  target: typeof j.target === "number" ? j.target : void 0,
20801
21242
  cliArgs: j.cliArgs ?? {},
20802
21243
  workflowFacts: j.workflowFacts && typeof j.workflowFacts === "object" && !Array.isArray(j.workflowFacts) ? j.workflowFacts : void 0,
21244
+ workflowState: parseWorkflowRunState(j.workflowState) ?? void 0,
21245
+ workflowRunId: typeof j.workflowRunId === "string" && j.workflowRunId.trim() ? j.workflowRunId.trim() : void 0,
20803
21246
  evidence: parseJobEvidence(j),
20804
21247
  flavor: j.flavor,
20805
21248
  force: j.force === true,
@@ -20834,7 +21277,7 @@ function parseJobEvidence(job) {
20834
21277
  async function runJob(job, base) {
20835
21278
  const valid = validateJob(job);
20836
21279
  const action = valid.action ?? valid.capability;
20837
- const projectCapabilitiesRoot = path45.join(base.cwd, ".kody", "capabilities");
21280
+ const projectCapabilitiesRoot = path46.join(base.cwd, ".kody", "capabilities");
20838
21281
  const resolvedCapability = !valid.workflow && action ? resolveCapabilityAction(action, projectCapabilitiesRoot) : null;
20839
21282
  const capabilityIdentity = valid.capability ?? resolvedCapability?.capability;
20840
21283
  const capabilityContext = valid.workflow ? null : loadCapabilityContext(capabilityIdentity, base.cwd);
@@ -20852,8 +21295,17 @@ async function runJob(job, base) {
20852
21295
  const profileName = explicitImplementation ?? capabilitySelectedImplementation;
20853
21296
  if (workflow && shouldRunCapabilityWorkflow(valid, workflow, workflowIdentity, capabilitySelectedImplementation, base)) {
20854
21297
  const workflowCapability = capabilityContext ?? workflowContext;
20855
- const workflowJob = workflowContext && !valid.why ? { ...valid, why: workflowContext.body } : valid;
20856
- return runCapabilityWorkflow(workflowJob, workflow, workflowCapability, base);
21298
+ const persistedState = valid.workflowRunId && workflowIdentity && base.config ? readWorkflowRunState(base.config, base.cwd, workflowIdentity, valid.workflowRunId) : null;
21299
+ const workflowJob = {
21300
+ ...workflowContext && !valid.why ? { ...valid, why: workflowContext.body } : valid,
21301
+ ...valid.workflowState ?? persistedState ? { workflowState: valid.workflowState ?? persistedState ?? void 0 } : {}
21302
+ };
21303
+ const checkpoint = valid.workflowRunId && workflowIdentity && base.config ? (state) => writeWorkflowRunState(base.config, base.cwd, workflowIdentity, valid.workflowRunId, state) : void 0;
21304
+ const result = await runCapabilityWorkflow(workflowJob, workflow, workflowCapability, base, checkpoint);
21305
+ if (valid.workflowRunId && workflowIdentity && base.config && result.workflowState) {
21306
+ writeWorkflowRunState(base.config, base.cwd, workflowIdentity, valid.workflowRunId, result.workflowState);
21307
+ }
21308
+ return result;
20857
21309
  }
20858
21310
  if (!profileName) {
20859
21311
  throw new InvalidJobError(`job capability resolves to no implementation: ${capabilityIdentity ?? action}`);
@@ -20933,7 +21385,22 @@ function shouldRunCapabilityWorkflow(job, workflow, capabilityIdentity, selected
20933
21385
  if (!requestedImplementation) return true;
20934
21386
  return requestedImplementation === selectedImplementation || requestedImplementation === capabilityIdentity || requestedImplementation === job.action;
20935
21387
  }
20936
- async function runCapabilityWorkflow(parent, workflow, capability, base) {
21388
+ async function runCapabilityWorkflow(parent, workflow, capability, base, checkpoint) {
21389
+ const invalid = workflowError(workflow, base);
21390
+ if (invalid) {
21391
+ if (isGraphWorkflow(workflow)) {
21392
+ const state = initialWorkflowState(parent, workflow);
21393
+ state.status = "blocked";
21394
+ state.blocker = invalid;
21395
+ checkpoint?.(state);
21396
+ return { exitCode: 64, reason: invalid, workflowState: state };
21397
+ }
21398
+ return { exitCode: 64, reason: invalid };
21399
+ }
21400
+ if (isGraphWorkflow(workflow)) return runGraphCapabilityWorkflow(parent, workflow, capability, base, checkpoint);
21401
+ return runLinearCapabilityWorkflow(parent, workflow, capability, base);
21402
+ }
21403
+ async function runLinearCapabilityWorkflow(parent, workflow, capability, base) {
20937
21404
  let chainData = {
20938
21405
  ...base.preloadedData ?? {},
20939
21406
  runSubjectType: "workflow",
@@ -20997,6 +21464,200 @@ async function runCapabilityWorkflow(parent, workflow, capability, base) {
20997
21464
  }
20998
21465
  return withWorkflowBoundaryEval(capability, result);
20999
21466
  }
21467
+ function isGraphWorkflow(workflow) {
21468
+ return workflow.startAt !== void 0 || workflow.steps.some((step) => step.id !== void 0 || step.next !== void 0 || step.inputs !== void 0);
21469
+ }
21470
+ function workflowError(workflow, base) {
21471
+ const projectCapabilitiesRoot = path46.join(base.cwd, ".kody", "capabilities");
21472
+ const knownCapabilities = /* @__PURE__ */ new Set();
21473
+ const capabilityInputs = /* @__PURE__ */ new Map();
21474
+ for (const step of workflow.steps) {
21475
+ const action = step.action ?? step.capability;
21476
+ const resolvedAction = resolveCapabilityAction(action, projectCapabilitiesRoot);
21477
+ const resolvedFolder = resolveCapabilityFolder(step.capability, projectCapabilitiesRoot);
21478
+ if (!resolvedAction && !resolvedFolder) continue;
21479
+ knownCapabilities.add(step.capability);
21480
+ const inputs = getCapabilityActionInputs(action, projectCapabilitiesRoot);
21481
+ if (inputs) {
21482
+ capabilityInputs.set(
21483
+ step.capability,
21484
+ new Set(inputs.flatMap((input) => [input.name, input.flag.replace(/^--/, "")]))
21485
+ );
21486
+ }
21487
+ }
21488
+ return formatWorkflowValidationIssues(validateWorkflow(workflow, { knownCapabilities, capabilityInputs }))[0] ?? null;
21489
+ }
21490
+ function initialWorkflowState(parent, workflow) {
21491
+ const prior = parent.workflowState;
21492
+ if (prior?.status === "done") {
21493
+ return {
21494
+ ...prior,
21495
+ status: "done",
21496
+ completedStepIds: [...prior.completedStepIds],
21497
+ transitionCounts: { ...prior.transitionCounts },
21498
+ facts: { ...prior.facts },
21499
+ evidence: { ...prior.evidence },
21500
+ artifacts: prior.artifacts.map((artifact) => ({ ...artifact }))
21501
+ };
21502
+ }
21503
+ const firstStepId = workflow.startAt ?? workflow.steps[0]?.id;
21504
+ const currentStepId = prior?.currentStepId ?? firstStepId;
21505
+ return {
21506
+ status: "running",
21507
+ ...currentStepId ? { currentStepId } : {},
21508
+ completedStepIds: [...prior?.completedStepIds ?? []],
21509
+ transitionCounts: { ...prior?.transitionCounts ?? {} },
21510
+ facts: { ...parent.workflowFacts ?? {}, ...prior?.facts ?? {} },
21511
+ evidence: { ...prior?.evidence ?? {} },
21512
+ artifacts: (prior?.artifacts ?? []).map((artifact) => ({ ...artifact }))
21513
+ };
21514
+ }
21515
+ function workflowChainData(parent, capability, base, state) {
21516
+ return {
21517
+ ...base.preloadedData ?? {},
21518
+ runSubjectType: "workflow",
21519
+ runSubjectId: capability.slug,
21520
+ runSubjectLabel: capability.title,
21521
+ runSubjectWorkflow: capability.slug,
21522
+ workflowCapability: capability.slug,
21523
+ workflowTitle: capability.title,
21524
+ workflowStepCount: capability.config.workflow?.steps.length ?? 0,
21525
+ workflowIssueNumber: workflowIssueNumber(parent),
21526
+ workflowFacts: state.facts,
21527
+ workflowEvidence: state.evidence,
21528
+ workflowArtifacts: state.artifacts,
21529
+ workflowStack: [
21530
+ ...Array.isArray(base.preloadedData?.workflowStack) ? base.preloadedData.workflowStack.filter((entry) => typeof entry === "string") : [],
21531
+ capability.slug
21532
+ ]
21533
+ };
21534
+ }
21535
+ async function runGraphCapabilityWorkflow(parent, workflow, capability, base, checkpoint) {
21536
+ const state = initialWorkflowState(parent, workflow);
21537
+ let chainData = workflowChainData(parent, capability, base, state);
21538
+ let result = { exitCode: 0 };
21539
+ let executedSteps = 0;
21540
+ const maxExecutedSteps = 1e3;
21541
+ while (state.currentStepId) {
21542
+ executedSteps += 1;
21543
+ if (executedSteps > maxExecutedSteps) {
21544
+ const reason = `workflow ${capability.slug} exceeded ${maxExecutedSteps} executed steps`;
21545
+ state.status = "blocked";
21546
+ state.blocker = reason;
21547
+ checkpoint?.(state);
21548
+ return { ...result, exitCode: 64, reason, workflowState: state };
21549
+ }
21550
+ const index = workflow.steps.findIndex((step2) => step2.id === state.currentStepId);
21551
+ const step = workflow.steps[index];
21552
+ if (!step) {
21553
+ const reason = `workflow ${capability.slug} current step ${state.currentStepId} is missing`;
21554
+ state.status = "blocked";
21555
+ state.blocker = reason;
21556
+ checkpoint?.(state);
21557
+ return { ...result, exitCode: 64, reason, workflowState: state };
21558
+ }
21559
+ const label = step.action ?? step.capability;
21560
+ checkpoint?.(state);
21561
+ let child;
21562
+ try {
21563
+ child = workflowStepToJob(step, parent, chainData);
21564
+ } catch (error) {
21565
+ const reason = error instanceof Error ? error.message : String(error);
21566
+ state.status = "blocked";
21567
+ state.blocker = reason;
21568
+ checkpoint?.(state);
21569
+ return { exitCode: 64, reason, workflowState: state };
21570
+ }
21571
+ process.stdout.write(
21572
+ `\u2192 kody: workflow ${capability.slug} step ${index + 1}/${workflow.steps.length} \u2192 ${label}
21573
+
21574
+ `
21575
+ );
21576
+ result = await runJob(child, {
21577
+ ...base,
21578
+ preloadedData: {
21579
+ ...chainData,
21580
+ workflowStep: step.id,
21581
+ workflowStepIndex: index + 1,
21582
+ workflowStepReason: step.reason,
21583
+ workflowContinueOn: step.continueOn ?? []
21584
+ }
21585
+ });
21586
+ mergeWorkflowResults(state, result.capabilityResults);
21587
+ const outcome = workflowOutcome(result);
21588
+ const prUrl = result.prUrl ?? result.taskState?.core.prUrl ?? (typeof chainData.workflowPrUrl === "string" ? chainData.workflowPrUrl : void 0);
21589
+ chainData = {
21590
+ ...workflowChainData(parent, capability, base, state),
21591
+ ...result.taskState ? { taskState: result.taskState } : {},
21592
+ ...outcome ? { workflowLastOutcome: outcome } : {},
21593
+ ...result.capabilityResults?.at(-1) ? { workflowLastResult: result.capabilityResults.at(-1) } : {},
21594
+ ...prUrl ? { workflowPrUrl: prUrl } : {},
21595
+ ...parsePrNumber5(prUrl) ? { workflowPrNumber: parsePrNumber5(prUrl) } : {}
21596
+ };
21597
+ if (!state.completedStepIds.includes(step.id)) state.completedStepIds.push(step.id);
21598
+ if (result.exitCode !== 0 && !canContinueWorkflow(step, outcome)) {
21599
+ state.status = "failed";
21600
+ state.blocker = result.reason ?? `workflow step ${step.id} failed`;
21601
+ checkpoint?.(state);
21602
+ return withWorkflowBoundaryEval(capability, { ...result, workflowState: state });
21603
+ }
21604
+ if (!step.next || step.next.length === 0) {
21605
+ state.status = "done";
21606
+ delete state.currentStepId;
21607
+ delete state.blocker;
21608
+ checkpoint?.(state);
21609
+ return withWorkflowBoundaryEval(capability, { ...result, workflowState: state });
21610
+ }
21611
+ const transition = selectWorkflowTransition(step, chainData, state.transitionCounts);
21612
+ if (!transition) {
21613
+ const reason = `workflow step ${step.id} has no available connection`;
21614
+ state.status = "blocked";
21615
+ state.blocker = reason;
21616
+ checkpoint?.(state);
21617
+ return { ...result, exitCode: 64, reason, workflowState: state };
21618
+ }
21619
+ if (transition.maxIterations !== void 0) {
21620
+ const key = `${step.id}->${transition.to}`;
21621
+ state.transitionCounts[key] = (state.transitionCounts[key] ?? 0) + 1;
21622
+ }
21623
+ state.currentStepId = transition.to;
21624
+ state.status = "running";
21625
+ delete state.blocker;
21626
+ checkpoint?.(state);
21627
+ }
21628
+ state.status = "done";
21629
+ checkpoint?.(state);
21630
+ return withWorkflowBoundaryEval(capability, { ...result, workflowState: state });
21631
+ }
21632
+ function mergeWorkflowResults(state, results) {
21633
+ for (const result of results ?? []) {
21634
+ Object.assign(state.facts, result.facts);
21635
+ Object.assign(state.evidence, result.evidence ?? {});
21636
+ for (const artifact of result.artifacts) {
21637
+ if (!state.artifacts.some(
21638
+ (existing) => existing.label === artifact.label && existing.url === artifact.url && existing.path === artifact.path
21639
+ )) {
21640
+ state.artifacts.push({ ...artifact });
21641
+ }
21642
+ }
21643
+ }
21644
+ }
21645
+ function selectWorkflowTransition(step, data, counts) {
21646
+ let fallback = null;
21647
+ for (const transition of step.next ?? []) {
21648
+ const key = `${step.id}->${transition.to}`;
21649
+ if (transition.maxIterations !== void 0 && (counts[key] ?? 0) >= transition.maxIterations) continue;
21650
+ if (transition.default === true) {
21651
+ fallback ??= transition;
21652
+ continue;
21653
+ }
21654
+ if (!transition.when || conditionMatches(transition.when, workflowConditionContext(data))) return transition;
21655
+ }
21656
+ return fallback;
21657
+ }
21658
+ function conditionMatches(condition, context) {
21659
+ return Object.entries(condition).every(([path52, expected]) => valueMatches(resolveDottedPath2(context, path52), expected));
21660
+ }
21000
21661
  function withWorkflowBoundaryEval(capability, result) {
21001
21662
  const capabilityKind = capability.config.capabilityKind;
21002
21663
  if (!capabilityKind) return result;
@@ -21017,8 +21678,18 @@ function withWorkflowBoundaryEval(capability, result) {
21017
21678
  }
21018
21679
  function workflowStepToJob(step, parent, chainData) {
21019
21680
  const action = step.action ?? step.capability;
21681
+ const mappedArgs = {};
21682
+ const conditionContext = workflowConditionContext(chainData);
21683
+ for (const [name, mapping] of Object.entries(step.inputs ?? {})) {
21684
+ const value = resolveDottedPath2(conditionContext, mapping.from);
21685
+ if (value === void 0) {
21686
+ throw new InvalidJobError(`workflow step ${step.id ?? action} needs missing input ${mapping.from}`);
21687
+ }
21688
+ mappedArgs[name] = value;
21689
+ }
21020
21690
  const rawArgs = {
21021
21691
  ...parent.cliArgs,
21692
+ ...mappedArgs,
21022
21693
  ...step.cliArgs ?? {}
21023
21694
  };
21024
21695
  const targetNumber = workflowStepTargetNumber(step, parent, chainData);
@@ -21052,9 +21723,7 @@ function workflowStepToJob(step, parent, chainData) {
21052
21723
  function shouldRunWorkflowStep(step, data) {
21053
21724
  if (!step.runWhen) return true;
21054
21725
  const context = workflowConditionContext(data);
21055
- return Object.entries(step.runWhen).every(
21056
- ([path51, expected]) => valueMatches(resolveDottedPath2(context, path51), expected)
21057
- );
21726
+ return conditionMatches(step.runWhen, context);
21058
21727
  }
21059
21728
  function canContinueWorkflow(step, outcome) {
21060
21729
  if (!outcome || !step.continueOn || step.continueOn.length === 0) return false;
@@ -21065,10 +21734,16 @@ function workflowOutcome(result) {
21065
21734
  }
21066
21735
  function workflowConditionContext(data) {
21067
21736
  const lastOutcome = data.workflowLastOutcome;
21737
+ const lastResult = data.workflowLastResult;
21068
21738
  return {
21069
21739
  ...data,
21740
+ facts: data.workflowFacts ?? {},
21741
+ evidence: data.workflowEvidence ?? {},
21742
+ artifacts: data.workflowArtifacts ?? [],
21743
+ result: lastResult,
21070
21744
  workflow: {
21071
21745
  lastOutcome,
21746
+ lastResult,
21072
21747
  issueNumber: data.workflowIssueNumber,
21073
21748
  prNumber: data.workflowPrNumber,
21074
21749
  prUrl: data.workflowPrUrl
@@ -21136,7 +21811,7 @@ function composeStepWhy(parentWhy, step) {
21136
21811
  }
21137
21812
  function loadCapabilityContext(slug, cwd) {
21138
21813
  if (!slug) return null;
21139
- return resolveCapabilityFolder(slug, path45.join(cwd, ".kody", "capabilities"));
21814
+ return resolveCapabilityFolder(slug, path46.join(cwd, ".kody", "capabilities"));
21140
21815
  }
21141
21816
  function loadWorkflowContext(slug, base) {
21142
21817
  if (!slug || !base.config || !isWorkflowDefinitionId(slug)) return null;
@@ -21175,6 +21850,8 @@ var init_job = __esm({
21175
21850
  init_executor();
21176
21851
  init_registry();
21177
21852
  init_workflowDefinitions();
21853
+ init_workflowRunState();
21854
+ init_workflowValidation();
21178
21855
  init_jobIdentity();
21179
21856
  init_jobIdentity();
21180
21857
  DEFAULT_INSTANT_AGENT = "kody";
@@ -21297,7 +21974,7 @@ function translateOpenAISseToBrain(opts) {
21297
21974
  // src/servers/brain-serve.ts
21298
21975
  import * as fs49 from "fs";
21299
21976
  import { createServer as createServer2 } from "http";
21300
- import * as path48 from "path";
21977
+ import * as path49 from "path";
21301
21978
 
21302
21979
  // src/chat/loop.ts
21303
21980
  init_agent();
@@ -21827,8 +22504,8 @@ async function runOpenAIChatTurn(args) {
21827
22504
  })
21828
22505
  });
21829
22506
  if (!response.ok) {
21830
- const text = await response.text().catch(() => "");
21831
- const error = `OpenAI-compatible model request failed ${response.status}${text ? `: ${text.slice(0, 500)}` : ""}`;
22507
+ const text2 = await response.text().catch(() => "");
22508
+ const error = `OpenAI-compatible model request failed ${response.status}${text2 ? `: ${text2.slice(0, 500)}` : ""}`;
21832
22509
  await emit(opts.sink, "chat.error", opts.sessionId, "error", { error });
21833
22510
  return { exitCode: 99, error };
21834
22511
  }
@@ -21866,8 +22543,8 @@ function extractOpenAIReply(payload) {
21866
22543
  return content.map((part) => {
21867
22544
  if (typeof part === "string") return part;
21868
22545
  if (part && typeof part === "object" && "text" in part) {
21869
- const text = part.text;
21870
- return typeof text === "string" ? text : "";
22546
+ const text2 = part.text;
22547
+ return typeof text2 === "string" ? text2 : "";
21871
22548
  }
21872
22549
  return "";
21873
22550
  }).join("");
@@ -21985,7 +22662,7 @@ init_config();
21985
22662
  // src/kody-cli.ts
21986
22663
  import { execFileSync as execFileSync26 } from "child_process";
21987
22664
  import * as fs47 from "fs";
21988
- import * as path46 from "path";
22665
+ import * as path47 from "path";
21989
22666
 
21990
22667
  // src/app-auth.ts
21991
22668
  import { createSign } from "crypto";
@@ -22256,7 +22933,7 @@ function autoDispatch(opts) {
22256
22933
  }
22257
22934
  if (eventName !== "issue_comment") return null;
22258
22935
  const comment = objectValue(event.comment);
22259
- const issue = objectValue(event.issue);
22936
+ const issue2 = objectValue(event.issue);
22260
22937
  const user = objectValue(comment?.user);
22261
22938
  const rawBody = String(comment?.body ?? "");
22262
22939
  const authorLogin = String(user?.login ?? "");
@@ -22265,8 +22942,8 @@ function autoDispatch(opts) {
22265
22942
  const isBotAuthor = authorLogin === "kody-bot" || authorType === "Bot";
22266
22943
  if (!associationAllowed(event, opts?.config)) return null;
22267
22944
  const body = rawBody;
22268
- const targetNum = Number(issue?.number ?? 0);
22269
- const isPr = !!issue?.pull_request;
22945
+ const targetNum = Number(issue2?.number ?? 0);
22946
+ const isPr = !!issue2?.pull_request;
22270
22947
  if (!targetNum) return null;
22271
22948
  const afterTag = extractAfterTag(body);
22272
22949
  const firstTokenRaw = extractSubcommand(afterTag);
@@ -22350,7 +23027,7 @@ function autoDispatchTyped(opts) {
22350
23027
  return { kind: "silent", reason: "GHA event payload unreadable" };
22351
23028
  }
22352
23029
  const comment = objectValue(event.comment);
22353
- const issue = objectValue(event.issue);
23030
+ const issue2 = objectValue(event.issue);
22354
23031
  const user = objectValue(comment?.user);
22355
23032
  const rawBody = String(comment?.body ?? "");
22356
23033
  const authorLogin = String(user?.login ?? "");
@@ -22358,8 +23035,8 @@ function autoDispatchTyped(opts) {
22358
23035
  if (!hasKodyMention(rawBody)) {
22359
23036
  return { kind: "silent", reason: "comment does not mention @kody" };
22360
23037
  }
22361
- const targetNum = Number(issue?.number ?? 0);
22362
- const isPr = !!issue?.pull_request;
23038
+ const targetNum = Number(issue2?.number ?? 0);
23039
+ const isPr = !!issue2?.pull_request;
22363
23040
  if (!targetNum) {
22364
23041
  return { kind: "silent", reason: "comment has no associated issue/PR number" };
22365
23042
  }
@@ -22703,7 +23380,8 @@ function routeRunRequest(request) {
22703
23380
  if (intent !== "run" && intent !== "tick") {
22704
23381
  return { kind: "error", error: `workflow target does not support intent '${intent}'` };
22705
23382
  }
22706
- return { kind: "action", action: target.id, cliArgs: {} };
23383
+ const workflowRunId = typeof request.input?.runId === "string" && /^[a-z0-9][a-z0-9_-]{0,79}$/.test(request.input.runId) ? request.input.runId : void 0;
23384
+ return { kind: "action", action: target.id, cliArgs: {}, ...workflowRunId ? { workflowRunId } : {} };
22707
23385
  }
22708
23386
  return { kind: "error", error: "unsupported run request target" };
22709
23387
  }
@@ -22782,9 +23460,9 @@ async function resolveAuthToken(env = process.env) {
22782
23460
  return void 0;
22783
23461
  }
22784
23462
  function detectPackageManager2(cwd) {
22785
- if (fs47.existsSync(path46.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
22786
- if (fs47.existsSync(path46.join(cwd, "yarn.lock"))) return "yarn";
22787
- if (fs47.existsSync(path46.join(cwd, "bun.lockb"))) return "bun";
23463
+ if (fs47.existsSync(path47.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
23464
+ if (fs47.existsSync(path47.join(cwd, "yarn.lock"))) return "yarn";
23465
+ if (fs47.existsSync(path47.join(cwd, "bun.lockb"))) return "bun";
22788
23466
  return "npm";
22789
23467
  }
22790
23468
  function shouldChainScheduledWatch(match) {
@@ -22907,7 +23585,7 @@ async function runCi(argv) {
22907
23585
  return 0;
22908
23586
  }
22909
23587
  const args = parseCiArgs(argv);
22910
- const cwd = args.cwd ? path46.resolve(args.cwd) : process.cwd();
23588
+ const cwd = args.cwd ? path47.resolve(args.cwd) : process.cwd();
22911
23589
  try {
22912
23590
  const n = unpackAllSecrets();
22913
23591
  if (n > 0) process.stdout.write(`\u2192 kody: unpacked ${n} secret(s) from ALL_SECRETS
@@ -22931,6 +23609,7 @@ async function runCi(argv) {
22931
23609
  let manualWorkflowDispatch = false;
22932
23610
  let forceRunAction = null;
22933
23611
  let forceRunCliArgs = {};
23612
+ let forceWorkflowRunId;
22934
23613
  let runRequestFanOut = false;
22935
23614
  let runRequestFanOutForce = false;
22936
23615
  const parsedRunRequest = readRunRequestFromEnv();
@@ -22955,6 +23634,7 @@ async function runCi(argv) {
22955
23634
  } else if (route.kind === "action") {
22956
23635
  forceRunAction = route.action;
22957
23636
  forceRunCliArgs = route.cliArgs;
23637
+ forceWorkflowRunId = route.workflowRunId;
22958
23638
  }
22959
23639
  }
22960
23640
  const envForceAction = (process.env.KODY_FORCE_ACTION ?? "").trim();
@@ -23052,6 +23732,7 @@ async function runCi(argv) {
23052
23732
  capability: route.capability,
23053
23733
  workflow: route.workflow,
23054
23734
  implementation: route.implementation,
23735
+ workflowRunId: forceWorkflowRunId,
23055
23736
  cliArgs: { ...route.cliArgs, ...forceRunCliArgs },
23056
23737
  flavor: "instant",
23057
23738
  force: true
@@ -23341,7 +24022,7 @@ init_repoWorkspace();
23341
24022
  // src/scripts/brainTurnLog.ts
23342
24023
  init_runtimePaths();
23343
24024
  import * as fs48 from "fs";
23344
- import * as path47 from "path";
24025
+ import * as path48 from "path";
23345
24026
  import posixPath4 from "path/posix";
23346
24027
  var live = /* @__PURE__ */ new Map();
23347
24028
  function brainEventsFilePath(dir, chatId) {
@@ -23391,7 +24072,7 @@ function beginTurn(dir, chatId) {
23391
24072
  };
23392
24073
  live.set(chatId, state);
23393
24074
  const p = brainEventsFilePath(dir, chatId);
23394
- fs48.mkdirSync(path47.dirname(p), { recursive: true });
24075
+ fs48.mkdirSync(path48.dirname(p), { recursive: true });
23395
24076
  return (event) => {
23396
24077
  state.seq += 1;
23397
24078
  const rec = { seq: state.seq, turn, ts: Date.now(), event };
@@ -23769,7 +24450,7 @@ async function handleChatTurn(req, res, chatId, opts) {
23769
24450
  );
23770
24451
  }
23771
24452
  }
23772
- fs49.mkdirSync(path48.dirname(sessionFile), { recursive: true });
24453
+ fs49.mkdirSync(path49.dirname(sessionFile), { recursive: true });
23773
24454
  appendTurn(sessionFile, {
23774
24455
  role: "user",
23775
24456
  content: message,
@@ -23844,7 +24525,7 @@ async function handleChatTurn(req, res, chatId, opts) {
23844
24525
  function buildServer(opts) {
23845
24526
  const runTurn = opts.runTurn ?? runChatTurn;
23846
24527
  const cloneRepo = opts.cloneRepo ?? defaultCloneRepo;
23847
- const reposRoot = opts.reposRoot ?? path48.join(path48.dirname(path48.resolve(opts.cwd)), "repos");
24528
+ const reposRoot = opts.reposRoot ?? path49.join(path49.dirname(path49.resolve(opts.cwd)), "repos");
23848
24529
  return createServer2(async (req, res) => {
23849
24530
  if (!req.method || !req.url) {
23850
24531
  sendJson(res, 400, { error: "bad request" });
@@ -24448,7 +25129,7 @@ async function loadConfigSafe() {
24448
25129
 
24449
25130
  // src/chat-cli.ts
24450
25131
  import * as fs51 from "fs";
24451
- import * as path50 from "path";
25132
+ import * as path51 from "path";
24452
25133
 
24453
25134
  // src/chat/inbox.ts
24454
25135
  import { execFileSync as execFileSync27 } from "child_process";
@@ -24521,9 +25202,9 @@ function currentBranch(cwd) {
24521
25202
  // src/chat/state-sync.ts
24522
25203
  init_stateRepo();
24523
25204
  import * as fs50 from "fs";
24524
- import * as path49 from "path";
24525
- function jsonlLines2(text) {
24526
- return text.split("\n").filter((line) => line.length > 0);
25205
+ import * as path50 from "path";
25206
+ function jsonlLines2(text2) {
25207
+ return text2.split("\n").filter((line) => line.length > 0);
24527
25208
  }
24528
25209
  function renderJsonl2(lines) {
24529
25210
  return lines.length > 0 ? `${lines.join("\n")}
@@ -24541,7 +25222,7 @@ function syncJsonlFileFromState(opts) {
24541
25222
  const local = fs50.existsSync(opts.localPath) ? fs50.readFileSync(opts.localPath, "utf-8") : "";
24542
25223
  const next = mergeJsonl2(local, remote.content);
24543
25224
  if (next === local) return;
24544
- fs50.mkdirSync(path49.dirname(opts.localPath), { recursive: true });
25225
+ fs50.mkdirSync(path50.dirname(opts.localPath), { recursive: true });
24545
25226
  fs50.writeFileSync(opts.localPath, next);
24546
25227
  }
24547
25228
  function persistJsonlFileToState(opts) {
@@ -24811,7 +25492,7 @@ async function runChat(argv) {
24811
25492
  ${CHAT_HELP}`);
24812
25493
  return 64;
24813
25494
  }
24814
- const cwd = args.cwd ? path50.resolve(args.cwd) : process.cwd();
25495
+ const cwd = args.cwd ? path51.resolve(args.cwd) : process.cwd();
24815
25496
  const sessionId = args.sessionId;
24816
25497
  const runRequest = readRunRequestFromEnv();
24817
25498
  if (runRequest && "request" in runRequest) {
@@ -24991,8 +25672,8 @@ var FlyClient = class {
24991
25672
  get fetch() {
24992
25673
  return this.opts.fetchImpl ?? fetch;
24993
25674
  }
24994
- async call(path51, init = {}) {
24995
- const res = await this.fetch(`${FLY_API_BASE}${path51}`, {
25675
+ async call(path52, init = {}) {
25676
+ const res = await this.fetch(`${FLY_API_BASE}${path52}`, {
24996
25677
  method: init.method ?? "GET",
24997
25678
  headers: {
24998
25679
  Authorization: `Bearer ${this.opts.token}`,
@@ -25002,8 +25683,8 @@ var FlyClient = class {
25002
25683
  });
25003
25684
  if (res.status === 404 && init.allow404) return null;
25004
25685
  if (!res.ok) {
25005
- const text = await res.text().catch(() => "");
25006
- throw new Error(`Fly API ${res.status} on ${path51}: ${text.slice(0, 200) || res.statusText}`);
25686
+ const text2 = await res.text().catch(() => "");
25687
+ throw new Error(`Fly API ${res.status} on ${path52}: ${text2.slice(0, 200) || res.statusText}`);
25007
25688
  }
25008
25689
  if (res.status === 204) return null;
25009
25690
  const raw = await res.text();
@@ -26350,11 +27031,11 @@ function envRunMode(env = process.env) {
26350
27031
  return { ...result, command: "ci", ciArgv: [] };
26351
27032
  }
26352
27033
  if (mode === "issue") {
26353
- const issue = (env.ISSUE_NUMBER ?? "").trim();
26354
- if (!issue) {
27034
+ const issue2 = (env.ISSUE_NUMBER ?? "").trim();
27035
+ if (!issue2) {
26355
27036
  return { ...result, errors: ["KODY_RUN_MODE=issue requires ISSUE_NUMBER"] };
26356
27037
  }
26357
- return { ...result, command: "ci", ciArgv: ["--issue", issue] };
27038
+ return { ...result, command: "ci", ciArgv: ["--issue", issue2] };
26358
27039
  }
26359
27040
  return { ...result, errors: [`unknown KODY_RUN_MODE: ${mode}`] };
26360
27041
  }