@kody-ade/kody-engine 0.4.373 → 0.4.374

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.374",
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,290 @@ 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_NAME.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_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_DATA_PATH = /^(facts|evidence|artifacts|result|workflow|lastOutcome)(?:\.[A-Za-z_][A-Za-z0-9_-]*)+$/;
9022
+ SUPPORTED_STEP_FIELDS = /* @__PURE__ */ new Set([
9023
+ "id",
9024
+ "capability",
9025
+ "action",
9026
+ "implementation",
9027
+ "evidence",
9028
+ "target",
9029
+ "targetFact",
9030
+ "reason",
9031
+ "agent",
9032
+ "cliArgs",
9033
+ "inputs",
9034
+ "next",
9035
+ "runWhen",
9036
+ "continueOn",
9037
+ "saveReport",
9038
+ "report"
9039
+ ]);
9040
+ SUPPORTED_TRANSITION_FIELDS = /* @__PURE__ */ new Set(["to", "when", "default", "maxIterations"]);
9041
+ }
9042
+ });
9043
+
8729
9044
  // src/workflowDefinitions.ts
8730
9045
  import * as fs28 from "fs";
8731
9046
  import * as path26 from "path";
@@ -8742,7 +9057,18 @@ function normalizeWorkflowDefinition(value) {
8742
9057
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
8743
9058
  const raw = value;
8744
9059
  const name = typeof raw.name === "string" ? raw.name.trim() : "";
8745
- const workflow = parseCapabilityWorkflow({ steps: raw.steps });
9060
+ const hasGraphConnections = Array.isArray(raw.steps) && raw.steps.some(
9061
+ (step) => step && typeof step === "object" && !Array.isArray(step) && (step.next !== void 0 || step.inputs !== void 0)
9062
+ );
9063
+ if (hasGraphConnections) {
9064
+ if (validateWorkflow({ steps: raw.steps, ...raw.startAt !== void 0 ? { startAt: raw.startAt } : {} }).length > 0) {
9065
+ return null;
9066
+ }
9067
+ }
9068
+ const workflow = parseCapabilityWorkflow({
9069
+ steps: raw.steps,
9070
+ startAt: raw.startAt
9071
+ });
8746
9072
  const steps = workflow?.steps;
8747
9073
  const capabilities = steps ? steps.map((step) => step.capability) : normalizeWorkflowCapabilities(raw.capabilities);
8748
9074
  if (!name || capabilities.length === 0) return null;
@@ -8752,6 +9078,7 @@ function normalizeWorkflowDefinition(value) {
8752
9078
  capabilities,
8753
9079
  ...raw.runWithoutApproval === true ? { runWithoutApproval: true } : {},
8754
9080
  ...steps ? { steps } : {},
9081
+ ...workflow?.startAt ? { startAt: workflow.startAt } : {},
8755
9082
  ...typeof raw.createdAt === "string" ? { createdAt: raw.createdAt } : {},
8756
9083
  ...typeof raw.updatedAt === "string" ? { updatedAt: raw.updatedAt } : {}
8757
9084
  };
@@ -8793,7 +9120,8 @@ function normalizeWorkflowCapabilities(value) {
8793
9120
  }
8794
9121
  function workflowDefinitionToConfig(workflow) {
8795
9122
  return {
8796
- steps: workflow.steps ?? workflow.capabilities.map((capability) => ({ capability }))
9123
+ steps: workflow.steps ?? workflow.capabilities.map((capability) => ({ capability })),
9124
+ ...workflow.startAt ? { startAt: workflow.startAt } : {}
8797
9125
  };
8798
9126
  }
8799
9127
  function readCompanyStoreWorkflowDefinition(id) {
@@ -8817,6 +9145,7 @@ var init_workflowDefinitions = __esm({
8817
9145
  init_capabilityFolders();
8818
9146
  init_companyStore();
8819
9147
  init_stateRepo();
9148
+ init_workflowValidation();
8820
9149
  WORKFLOW_ID_PATTERN = /^[a-z0-9][a-z0-9_-]{0,79}$/;
8821
9150
  CAPABILITY_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,79}$/;
8822
9151
  }
@@ -9639,7 +9968,7 @@ function readSimpleGoalTaskSummary(goalId, cwd) {
9639
9968
  );
9640
9969
  const issues = JSON.parse(raw);
9641
9970
  const total = issues.length;
9642
- const open = issues.filter((issue) => String(issue.state ?? "").toLowerCase() === "open").length;
9971
+ const open = issues.filter((issue2) => String(issue2.state ?? "").toLowerCase() === "open").length;
9643
9972
  return { total, open };
9644
9973
  }
9645
9974
  function previousDispatchWasTargetInstance(managed, previousScheduleState) {
@@ -9688,7 +10017,7 @@ function findExistingGoalIssue(goalId, cwd) {
9688
10017
  const marker = goalIssueMarker(goalId);
9689
10018
  const raw = gh(["issue", "list", "--state", "all", "--limit", "100", "--json", "number,body"], { cwd });
9690
10019
  const issues = JSON.parse(raw);
9691
- const match = issues.find((issue) => typeof issue.number === "number" && issue.body?.includes(marker));
10020
+ const match = issues.find((issue2) => typeof issue2.number === "number" && issue2.body?.includes(marker));
9692
10021
  return match?.number ?? null;
9693
10022
  }
9694
10023
  function createGoalIssue(goal, goalId, cwd) {
@@ -10537,13 +10866,13 @@ function ensureNeedsFixIssue(ctx, goalId, state, evidence, evidenceKey) {
10537
10866
  const evidenceState = parseGoalEvidenceState(state.extra.evidenceState);
10538
10867
  const progress = evidenceState[evidenceKey];
10539
10868
  if (progress?.issue) return state;
10540
- const issue = findExistingNeedsFixIssue(goalId, evidenceKey, ctx.cwd) ?? createNeedsFixIssue(goalId, evidenceKey, evidence, ctx.cwd);
10869
+ const issue2 = findExistingNeedsFixIssue(goalId, evidenceKey, ctx.cwd) ?? createNeedsFixIssue(goalId, evidenceKey, evidence, ctx.cwd);
10541
10870
  const nextEvidenceState = mergeGoalEvidenceProgress(evidenceState, evidenceKey, {
10542
10871
  resultClass: "needsFix",
10543
10872
  attempts: progress?.attempts ?? 1,
10544
10873
  reason: evidence.summary,
10545
- nextAction: `fix issue #${issue}`,
10546
- issue,
10874
+ nextAction: `fix issue #${issue2}`,
10875
+ issue: issue2,
10547
10876
  updatedAt: nowIso()
10548
10877
  });
10549
10878
  return {
@@ -10552,7 +10881,7 @@ function ensureNeedsFixIssue(ctx, goalId, state, evidence, evidenceKey) {
10552
10881
  ...state.extra,
10553
10882
  evidenceState: nextEvidenceState,
10554
10883
  reason: evidence.summary,
10555
- nextAction: `fix issue #${issue}`
10884
+ nextAction: `fix issue #${issue2}`
10556
10885
  }
10557
10886
  };
10558
10887
  }
@@ -10570,7 +10899,7 @@ function findExistingNeedsFixIssue(goalId, evidence, cwd) {
10570
10899
  const marker = needsFixIssueMarker(goalId, evidence);
10571
10900
  const raw = gh(["issue", "list", "--state", "all", "--limit", "100", "--json", "number,body"], { cwd });
10572
10901
  const issues = JSON.parse(raw);
10573
- const match = issues.find((issue) => typeof issue.number === "number" && issue.body?.includes(marker));
10902
+ const match = issues.find((issue2) => typeof issue2.number === "number" && issue2.body?.includes(marker));
10574
10903
  return match?.number ?? null;
10575
10904
  }
10576
10905
  function createNeedsFixIssue(goalId, evidence, result, cwd) {
@@ -11008,8 +11337,8 @@ var init_classifyByLabel = __esm({
11008
11337
  "use strict";
11009
11338
  VALID_CLASSES = /* @__PURE__ */ new Set(["feature", "bug", "spec", "chore"]);
11010
11339
  classifyByLabel = async (ctx) => {
11011
- const issue = ctx.data.issue;
11012
- const labels = issue?.labels;
11340
+ const issue2 = ctx.data.issue;
11341
+ const labels = issue2?.labels;
11013
11342
  if (!labels || labels.length === 0) return;
11014
11343
  const cfgMap = ctx.config.classify?.labelMap;
11015
11344
  const map = cfgMap ?? defaultLabelMap();
@@ -11535,19 +11864,19 @@ function buildGoalName(scope, verdict) {
11535
11864
  const verdictTag = verdict === "UNKNOWN" ? "REPORT" : verdict;
11536
11865
  return `QA: ${focus} \u2014 ${verdictTag} \u2014 ${todayIso()}`.slice(0, 240);
11537
11866
  }
11538
- function splitReport(text) {
11539
- const open = text.indexOf(REPORT_JSON_OPEN);
11867
+ function splitReport(text2) {
11868
+ const open = text2.indexOf(REPORT_JSON_OPEN);
11540
11869
  if (open < 0) {
11541
- const fallback = parseFallbackFindingsJson(text);
11870
+ const fallback = parseFallbackFindingsJson(text2);
11542
11871
  if (fallback) return fallback;
11543
- return { markdown: text.trim(), data: null, jsonError: "no JSON block marker" };
11872
+ return { markdown: text2.trim(), data: null, jsonError: "no JSON block marker" };
11544
11873
  }
11545
- const closeRel = text.slice(open + REPORT_JSON_OPEN.length).indexOf(REPORT_JSON_CLOSE);
11874
+ const closeRel = text2.slice(open + REPORT_JSON_OPEN.length).indexOf(REPORT_JSON_CLOSE);
11546
11875
  if (closeRel < 0) {
11547
- return { markdown: text.slice(0, open).trim(), data: null, jsonError: "JSON block not terminated" };
11876
+ return { markdown: text2.slice(0, open).trim(), data: null, jsonError: "JSON block not terminated" };
11548
11877
  }
11549
11878
  const closeAbs = open + REPORT_JSON_OPEN.length + closeRel;
11550
- const rawJson = text.slice(open + REPORT_JSON_OPEN.length, closeAbs).trim();
11879
+ const rawJson = text2.slice(open + REPORT_JSON_OPEN.length, closeAbs).trim();
11551
11880
  const fenced = rawJson.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/);
11552
11881
  const cleanJson = fenced ? fenced[1].trim() : rawJson;
11553
11882
  let parsed = null;
@@ -11562,11 +11891,11 @@ function splitReport(text) {
11562
11891
  } catch (err) {
11563
11892
  parseError = err instanceof Error ? err.message : String(err);
11564
11893
  }
11565
- const markdown = text.slice(0, open).trim();
11894
+ const markdown = text2.slice(0, open).trim();
11566
11895
  return { markdown, data: parsed, jsonError: parseError };
11567
11896
  }
11568
- function parseFallbackFindingsJson(text) {
11569
- const fences = [...text.matchAll(/```(?:json)?\s*([\s\S]*?)\s*```/gi)];
11897
+ function parseFallbackFindingsJson(text2) {
11898
+ const fences = [...text2.matchAll(/```(?:json)?\s*([\s\S]*?)\s*```/gi)];
11570
11899
  for (let i = fences.length - 1; i >= 0; i--) {
11571
11900
  const match = fences[i];
11572
11901
  const raw = match[1]?.trim();
@@ -11575,18 +11904,18 @@ function parseFallbackFindingsJson(text) {
11575
11904
  const parsed = JSON.parse(raw);
11576
11905
  if (!parsed || !Array.isArray(parsed.findings)) {
11577
11906
  return {
11578
- markdown: removeFence(text, match).trim(),
11907
+ markdown: removeFence(text2, match).trim(),
11579
11908
  data: null,
11580
11909
  jsonError: "fallback JSON missing 'findings' array"
11581
11910
  };
11582
11911
  }
11583
11912
  return {
11584
- markdown: removeFence(text, match).trim(),
11913
+ markdown: removeFence(text2, match).trim(),
11585
11914
  data: { findings: parsed.findings.map((f, idx) => normalizeFallbackFinding(f, idx)) }
11586
11915
  };
11587
11916
  } catch (err) {
11588
11917
  return {
11589
- markdown: removeFence(text, match).trim(),
11918
+ markdown: removeFence(text2, match).trim(),
11590
11919
  data: null,
11591
11920
  jsonError: err instanceof Error ? err.message : String(err)
11592
11921
  };
@@ -11594,10 +11923,10 @@ function parseFallbackFindingsJson(text) {
11594
11923
  }
11595
11924
  return null;
11596
11925
  }
11597
- function removeFence(text, match) {
11926
+ function removeFence(text2, match) {
11598
11927
  const start = match.index ?? -1;
11599
- if (start < 0) return text;
11600
- return `${text.slice(0, start)}${text.slice(start + match[0].length)}`;
11928
+ if (start < 0) return text2;
11929
+ return `${text2.slice(0, start)}${text2.slice(start + match[0].length)}`;
11601
11930
  }
11602
11931
  function normalizeFallbackFinding(raw, idx) {
11603
11932
  const finding = raw && typeof raw === "object" ? raw : {};
@@ -11654,9 +11983,9 @@ function loadManifest(cwd) {
11654
11983
  return { number: null, manifest: { version: 1, goals: [] } };
11655
11984
  }
11656
11985
  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 };
11986
+ const issue2 = arr[0];
11987
+ const manifest = parseManifestBody(issue2.body);
11988
+ return { number: issue2.number, manifest };
11660
11989
  }
11661
11990
  function parseManifestBody(body) {
11662
11991
  if (!body) return { version: 1, goals: [] };
@@ -11882,8 +12211,8 @@ ${markdown}`, ctx.cwd);
11882
12211
  const failed = [];
11883
12212
  for (const f of findings) {
11884
12213
  try {
11885
- const issue = createTaskIssue(f, goalId, manifestIssueNumber, ctx.cwd);
11886
- opened.push({ ...issue, severity: f.severity });
12214
+ const issue2 = createTaskIssue(f, goalId, manifestIssueNumber, ctx.cwd);
12215
+ opened.push({ ...issue2, severity: f.severity });
11887
12216
  } catch (err) {
11888
12217
  const reason = err instanceof Error ? err.message : String(err);
11889
12218
  failed.push({ title: f.title, reason });
@@ -12025,8 +12354,8 @@ var init_deriveQaScopeFromIssue = __esm({
12025
12354
  if (!Number.isFinite(issueNumber) || issueNumber <= 0) return;
12026
12355
  let title = "";
12027
12356
  try {
12028
- const issue = getIssue(issueNumber, ctx.cwd);
12029
- title = (issue.title ?? "").trim();
12357
+ const issue2 = getIssue(issueNumber, ctx.cwd);
12358
+ title = (issue2.title ?? "").trim();
12030
12359
  } catch (err) {
12031
12360
  process.stderr.write(
12032
12361
  `[kody] deriveQaScopeFromIssue: could not read #${issueNumber}: ${err instanceof Error ? err.message : String(err)}
@@ -12631,28 +12960,28 @@ var init_dispatchCapabilityTicks = __esm({
12631
12960
  process.stdout.write(`[jobs] ticking ${issues.length} issue(s) via ${targetImplementation}
12632
12961
  `);
12633
12962
  const results = [];
12634
- for (const issue of issues) {
12635
- process.stdout.write(`[jobs] \u2192 tick #${issue.number}: ${issue.title}
12963
+ for (const issue2 of issues) {
12964
+ process.stdout.write(`[jobs] \u2192 tick #${issue2.number}: ${issue2.title}
12636
12965
  `);
12637
12966
  try {
12638
12967
  const out = await runJob(
12639
12968
  mintScheduledJob({
12640
12969
  capability: targetImplementation,
12641
12970
  implementation: targetImplementation,
12642
- cliArgs: { [issueArg]: issue.number }
12971
+ cliArgs: { [issueArg]: issue2.number }
12643
12972
  }),
12644
12973
  { cwd: ctx.cwd, config: ctx.config, verbose: ctx.verbose, quiet: ctx.quiet, chain: false }
12645
12974
  );
12646
- results.push({ issue: issue.number, exitCode: out.exitCode, reason: out.reason });
12975
+ results.push({ issue: issue2.number, exitCode: out.exitCode, reason: out.reason });
12647
12976
  if (out.exitCode !== 0) {
12648
- process.stderr.write(`[jobs] tick #${issue.number} failed (exit ${out.exitCode}): ${out.reason ?? ""}
12977
+ process.stderr.write(`[jobs] tick #${issue2.number} failed (exit ${out.exitCode}): ${out.reason ?? ""}
12649
12978
  `);
12650
12979
  }
12651
12980
  } catch (err) {
12652
12981
  const msg = err instanceof Error ? err.message : String(err);
12653
- process.stderr.write(`[jobs] tick #${issue.number} crashed: ${msg}
12982
+ process.stderr.write(`[jobs] tick #${issue2.number} crashed: ${msg}
12654
12983
  `);
12655
- results.push({ issue: issue.number, exitCode: 99, reason: msg });
12984
+ results.push({ issue: issue2.number, exitCode: 99, reason: msg });
12656
12985
  }
12657
12986
  }
12658
12987
  ctx.data.jobTickResults = results;
@@ -12921,7 +13250,14 @@ function updateExistingPr(existing, body, draft, cwd, preserveBody) {
12921
13250
  const promotedTitle = existing.title?.replace(/^\[WIP\]\s*/, "");
12922
13251
  if (promotedTitle && promotedTitle !== existing.title) {
12923
13252
  gh(
12924
- ["api", "--method", "PATCH", `repos/${owner}/${repo}/pulls/${existing.number}`, "-f", `title=${promotedTitle}`],
13253
+ [
13254
+ "api",
13255
+ "--method",
13256
+ "PATCH",
13257
+ `repos/${owner}/${repo}/pulls/${existing.number}`,
13258
+ "-f",
13259
+ `title=${promotedTitle}`
13260
+ ],
12925
13261
  { cwd, preferRepoToken: true }
12926
13262
  );
12927
13263
  }
@@ -13067,10 +13403,10 @@ var init_ensurePr = __esm({
13067
13403
  const failureReason = computeFailureReason(ctx);
13068
13404
  const isFailure = failureReason.length > 0;
13069
13405
  const changedFiles = ctx.data.changedFiles ?? [];
13070
- const issue = ctx.data.issue;
13406
+ const issue2 = ctx.data.issue;
13071
13407
  const pr = ctx.data.pr;
13072
13408
  const targetNumber = Number(ctx.data.commentTargetNumber ?? 0);
13073
- const title = issue?.title ?? pr?.title ?? `kody changes`;
13409
+ const title = issue2?.title ?? pr?.title ?? `kody changes`;
13074
13410
  const baseBranch = ctx.data.baseBranch;
13075
13411
  try {
13076
13412
  const result = ensurePr({
@@ -13121,12 +13457,12 @@ var init_failOnceTaskJob = __esm({
13121
13457
  init_jobIdentity();
13122
13458
  failOnceTaskJob = async (ctx, profile) => {
13123
13459
  ctx.skipAgent = true;
13124
- const issue = typeof ctx.args.issue === "number" ? ctx.args.issue : void 0;
13460
+ const issue2 = typeof ctx.args.issue === "number" ? ctx.args.issue : void 0;
13125
13461
  const fallbackJob = {
13126
13462
  capability: profile.action ?? profile.name,
13127
13463
  implementation: profile.name,
13128
13464
  flavor: "instant",
13129
- ...typeof issue === "number" ? { target: issue, cliArgs: { issue } } : { cliArgs: {} }
13465
+ ...typeof issue2 === "number" ? { target: issue2, cliArgs: { issue: issue2 } } : { cliArgs: {} }
13130
13466
  };
13131
13467
  const jobKey = typeof ctx.data.jobKey === "string" ? ctx.data.jobKey : stableJobKey(fallbackJob);
13132
13468
  const state = ctx.data.taskState;
@@ -14112,13 +14448,13 @@ function companyIntentPath(id) {
14112
14448
  assertIntentId(id);
14113
14449
  return `intents/${id}/intent.json`;
14114
14450
  }
14115
- function normalizeCompanyIntent(path51, raw) {
14451
+ function normalizeCompanyIntent(path52, raw) {
14116
14452
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
14117
- throw new Error(`${path51}: intent must be JSON object`);
14453
+ throw new Error(`${path52}: intent must be JSON object`);
14118
14454
  }
14119
14455
  const input = raw;
14120
14456
  const id = stringField5(input.id);
14121
- if (!id || !isCompanyIntentId(id)) throw new Error(`${path51}: invalid intent id`);
14457
+ if (!id || !isCompanyIntentId(id)) throw new Error(`${path52}: invalid intent id`);
14122
14458
  const createdAt = stringField5(input.createdAt) || nowIso();
14123
14459
  const updatedAt = stringField5(input.updatedAt) || createdAt;
14124
14460
  const description = stringField5(input.description);
@@ -14158,8 +14494,8 @@ function listCompanyIntents(config, cwd) {
14158
14494
  const records = [];
14159
14495
  for (const entry of entries) {
14160
14496
  if (entry.type !== "dir" || !entry.name || !isCompanyIntentId(entry.name)) continue;
14161
- const path51 = companyIntentPath(entry.name);
14162
- const file = readStateText(config, cwd, path51);
14497
+ const path52 = companyIntentPath(entry.name);
14498
+ const file = readStateText(config, cwd, path52);
14163
14499
  if (!file) continue;
14164
14500
  records.push({
14165
14501
  id: entry.name,
@@ -14368,14 +14704,14 @@ var init_loadIssueContext = __esm({
14368
14704
  if (!ctx.data.commentTargetNumber) ctx.data.commentTargetNumber = issueNumber;
14369
14705
  return;
14370
14706
  }
14371
- const issue = getIssue(issueNumber, ctx.cwd);
14707
+ const issue2 = getIssue(issueNumber, ctx.cwd);
14372
14708
  const cfgCtx = ctx.config.issueContext ?? {};
14373
14709
  const limit = cfgCtx.commentLimit ?? DEFAULT_COMMENT_LIMIT;
14374
14710
  const maxBytes = cfgCtx.commentMaxBytes ?? DEFAULT_COMMENT_MAX_BYTES;
14375
- const commentsFormatted = formatIssueComments(issue.comments, limit, maxBytes);
14376
- const labels = issue.labels ?? [];
14711
+ const commentsFormatted = formatIssueComments(issue2.comments, limit, maxBytes);
14712
+ const labels = issue2.labels ?? [];
14377
14713
  const labelsFormatted = labels.length === 0 ? "(no labels)" : labels.map((l) => `\`${l}\``).join(", ");
14378
- ctx.data.issue = { ...issue, commentsFormatted, labelsFormatted };
14714
+ ctx.data.issue = { ...issue2, commentsFormatted, labelsFormatted };
14379
14715
  ctx.data.commentTargetType = "issue";
14380
14716
  ctx.data.commentTargetNumber = issueNumber;
14381
14717
  };
@@ -14404,11 +14740,11 @@ var init_loadIssueStateComment = __esm({
14404
14740
  if (!owner || !repo) {
14405
14741
  throw new Error("loadIssueStateComment: ctx.config.github.owner/repo must be set");
14406
14742
  }
14407
- const issue = getIssue(issueNumber, ctx.cwd);
14743
+ const issue2 = getIssue(issueNumber, ctx.cwd);
14408
14744
  const loaded = findStateComment(owner, repo, issueNumber, marker, ctx.cwd);
14409
14745
  ctx.data.stateMarker = marker;
14410
- ctx.data.issueIntent = issue.body;
14411
- ctx.data.issueTitle = issue.title;
14746
+ ctx.data.issueIntent = issue2.body;
14747
+ ctx.data.issueTitle = issue2.title;
14412
14748
  ctx.data.issueNumber = String(issueNumber);
14413
14749
  ctx.data.issueStateComment = loaded;
14414
14750
  ctx.data.issueStateJson = loaded ? JSON.stringify(loaded.state, null, 2) : "null";
@@ -14538,15 +14874,15 @@ var init_loadLinkedFinding = __esm({
14538
14874
  if (!pr) return;
14539
14875
  const findingNumber = resolveFindingNumber(pr);
14540
14876
  if (!findingNumber) return;
14541
- let issue;
14877
+ let issue2;
14542
14878
  try {
14543
- issue = getIssue(findingNumber, ctx.cwd);
14879
+ issue2 = getIssue(findingNumber, ctx.cwd);
14544
14880
  } catch {
14545
14881
  return;
14546
14882
  }
14547
- ctx.data.linkedFinding = `Issue #${issue.number}: ${issue.title}
14883
+ ctx.data.linkedFinding = `Issue #${issue2.number}: ${issue2.title}
14548
14884
 
14549
- ${truncate(issue.body, FINDING_BODY_MAX_BYTES)}`;
14885
+ ${truncate(issue2.body, FINDING_BODY_MAX_BYTES)}`;
14550
14886
  };
14551
14887
  }
14552
14888
  });
@@ -14751,8 +15087,8 @@ async function writeGithubStateTextWithConfig(opts) {
14751
15087
  );
14752
15088
  }
14753
15089
  }
14754
- function jsonlLines(text) {
14755
- return text.split("\n").filter((line) => line.length > 0);
15090
+ function jsonlLines(text2) {
15091
+ return text2.split("\n").filter((line) => line.length > 0);
14756
15092
  }
14757
15093
  function renderJsonl(lines) {
14758
15094
  return lines.length > 0 ? `${lines.join("\n")}
@@ -15052,14 +15388,14 @@ var init_loadTaskContext = __esm({
15052
15388
  loadTaskContext = async (ctx) => {
15053
15389
  const runId = resolveRunId();
15054
15390
  const rawIssue = ctx.data.issue;
15055
- const issue = rawIssue ? {
15391
+ const issue2 = rawIssue ? {
15056
15392
  ...rawIssue,
15057
15393
  commentsFormatted: rawIssue.commentsFormatted ?? "",
15058
15394
  labelsFormatted: rawIssue.labelsFormatted ?? ""
15059
15395
  } : void 0;
15060
15396
  const taskContext = buildTaskContext({
15061
15397
  runId,
15062
- issue,
15398
+ issue: issue2,
15063
15399
  conventions: ctx.data.conventions,
15064
15400
  priorArt: typeof ctx.data.priorArt === "string" ? ctx.data.priorArt : "",
15065
15401
  memoryContext: typeof ctx.data.memoryContext === "string" ? ctx.data.memoryContext : "",
@@ -15414,6 +15750,12 @@ var init_notifyTerminal = __esm({
15414
15750
  });
15415
15751
 
15416
15752
  // src/scripts/openAgencyModelReviewPr.ts
15753
+ function isDryRun(ctx) {
15754
+ const arg = ctx.args.dry_run ?? ctx.args.dryRun;
15755
+ if (arg === true) return true;
15756
+ if (typeof arg === "string" && ["1", "true", "yes"].includes(arg.trim().toLowerCase())) return true;
15757
+ return ["1", "true", "yes"].includes((process.env.KODY_DRY_RUN ?? "").trim().toLowerCase());
15758
+ }
15417
15759
  function parseAgencyModelProposal(raw) {
15418
15760
  const jsonText = stripJsonFence(raw);
15419
15761
  let parsed;
@@ -15483,9 +15825,9 @@ function normalizeBundleFiles(ctx, bundle) {
15483
15825
  });
15484
15826
  }
15485
15827
  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();
15828
+ const text2 = raw.trim();
15829
+ const fence = text2.match(/^```(?:json)?\s*\n([\s\S]*?)\n```$/i);
15830
+ return (fence ? fence[1] : text2).trim();
15489
15831
  }
15490
15832
  function readIssueNumber(ctx) {
15491
15833
  const issueNumber = ctx.args.issue;
@@ -15495,9 +15837,9 @@ function readIssueNumber(ctx) {
15495
15837
  return issueNumber;
15496
15838
  }
15497
15839
  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;
15840
+ const text2 = readJsonString(value, field).trim();
15841
+ if (!text2) throw new Error(`openAgencyModelReviewPr: ${field} must be a non-empty string`);
15842
+ return text2;
15501
15843
  }
15502
15844
  function readJsonString(value, field) {
15503
15845
  if (typeof value !== "string") throw new Error(`openAgencyModelReviewPr: ${field} must be a string`);
@@ -15577,6 +15919,16 @@ var init_openAgencyModelReviewPr = __esm({
15577
15919
  const stateRepo = parseStateRepo(ctx.config);
15578
15920
  const baseBranch = "main";
15579
15921
  const branch = buildStatePrBranchName(sourceLabel, issueNumber, bundle.title);
15922
+ if (isDryRun(ctx)) {
15923
+ ctx.data.agencyModelReviewPr = {
15924
+ dryRun: true,
15925
+ repo: `${stateRepo.owner}/${stateRepo.repo}`,
15926
+ branch,
15927
+ base: baseBranch,
15928
+ files: normalizedFiles.map((file) => file.targetPath)
15929
+ };
15930
+ return;
15931
+ }
15580
15932
  const baseRef = ghJson(
15581
15933
  ["api", `/repos/${stateRepo.owner}/${stateRepo.repo}/git/ref/heads/${baseBranch}`],
15582
15934
  ctx.cwd
@@ -15689,10 +16041,10 @@ function ensureLabel2(cwd) {
15689
16041
  return false;
15690
16042
  }
15691
16043
  }
15692
- function markIssueWithReportLabel(issue, cwd) {
16044
+ function markIssueWithReportLabel(issue2, cwd) {
15693
16045
  if (!ensureLabel2(cwd)) return;
15694
16046
  try {
15695
- gh(["issue", "edit", String(issue), "--add-label", QA_LABEL], { cwd });
16047
+ gh(["issue", "edit", String(issue2), "--add-label", QA_LABEL], { cwd });
15696
16048
  } catch {
15697
16049
  }
15698
16050
  }
@@ -15831,13 +16183,13 @@ function isPartialEnvelope(x) {
15831
16183
  function escapeRegex(s) {
15832
16184
  return s.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
15833
16185
  }
15834
- function extractFencedBlock(text, label) {
16186
+ function extractFencedBlock(text2, label) {
15835
16187
  const re = new RegExp(`\`\`\`${escapeRegex(label)}\\s*\\n([\\s\\S]*?)\\n\`\`\``, "m");
15836
- const m = re.exec(text);
16188
+ const m = re.exec(text2);
15837
16189
  return m ? m[1].trim() : null;
15838
16190
  }
15839
- function extractNextStateFromText(text, fenceLabel, prevRev) {
15840
- const inner = extractFencedBlock(text, fenceLabel);
16191
+ function extractNextStateFromText(text2, fenceLabel, prevRev) {
16192
+ const inner = extractFencedBlock(text2, fenceLabel);
15841
16193
  if (inner === null) {
15842
16194
  return { error: `missing \`${fenceLabel}\` fenced block` };
15843
16195
  }
@@ -15956,15 +16308,15 @@ var init_parseJobStateFromAgentResult = __esm({
15956
16308
  });
15957
16309
 
15958
16310
  // src/scripts/parseReproOutput.ts
15959
- function extractTestPath(text) {
15960
- const m = text.match(/^[\s>*_#`~-]*TEST_PATH[\s>*_#`~-]*\s*:\s*(.+?)\s*$/im);
16311
+ function extractTestPath(text2) {
16312
+ const m = text2.match(/^[\s>*_#`~-]*TEST_PATH[\s>*_#`~-]*\s*:\s*(.+?)\s*$/im);
15961
16313
  if (!m) return "";
15962
16314
  return stripMarkdownEmphasis2(m[1] ?? "");
15963
16315
  }
15964
- function extractFailureSignatureBlock(text) {
15965
- const startIdx = text.search(/(?:^|\n)[ \t]*FAILURE_SIGNATURE\s*:[ \t]*/i);
16316
+ function extractFailureSignatureBlock(text2) {
16317
+ const startIdx = text2.search(/(?:^|\n)[ \t]*FAILURE_SIGNATURE\s*:[ \t]*/i);
15966
16318
  if (startIdx === -1) return "";
15967
- const afterMarker = text.slice(startIdx).replace(/^[\s\S]*?FAILURE_SIGNATURE\s*:[ \t]*\n?/i, "");
16319
+ const afterMarker = text2.slice(startIdx).replace(/^[\s\S]*?FAILURE_SIGNATURE\s*:[ \t]*\n?/i, "");
15968
16320
  const stopRe = /(?:^|\n)[ \t]*(?:COMMIT_MSG|PR_SUMMARY|TEST_PATH)\s*:/i;
15969
16321
  const stopIdx = afterMarker.search(stopRe);
15970
16322
  let block = stopIdx === -1 ? afterMarker : afterMarker.slice(0, stopIdx);
@@ -15981,14 +16333,14 @@ function normalizeFailureSignatureBlock(block) {
15981
16333
  const jsonObject = extractFirstJsonObject(s);
15982
16334
  return jsonObject || s;
15983
16335
  }
15984
- function extractFirstJsonObject(text) {
15985
- const start = text.indexOf("{");
16336
+ function extractFirstJsonObject(text2) {
16337
+ const start = text2.indexOf("{");
15986
16338
  if (start === -1) return "";
15987
16339
  let depth = 0;
15988
16340
  let inString = false;
15989
16341
  let escaped = false;
15990
- for (let i = start; i < text.length; i++) {
15991
- const ch = text[i];
16342
+ for (let i = start; i < text2.length; i++) {
16343
+ const ch = text2[i];
15992
16344
  if (inString) {
15993
16345
  if (escaped) {
15994
16346
  escaped = false;
@@ -16005,7 +16357,7 @@ function extractFirstJsonObject(text) {
16005
16357
  depth++;
16006
16358
  } else if (ch === "}") {
16007
16359
  depth--;
16008
- if (depth === 0) return text.slice(start, i + 1).trim();
16360
+ if (depth === 0) return text2.slice(start, i + 1).trim();
16009
16361
  }
16010
16362
  }
16011
16363
  return "";
@@ -16031,9 +16383,9 @@ var init_parseReproOutput = __esm({
16031
16383
  "use strict";
16032
16384
  parseReproOutput = async (ctx, _profile, agentResult) => {
16033
16385
  if (!agentResult || ctx.data.agentDone === false) return;
16034
- const text = agentResult.finalText ?? "";
16035
- const testPath = extractTestPath(text);
16036
- const signatureRaw = extractFailureSignatureBlock(text);
16386
+ const text2 = agentResult.finalText ?? "";
16387
+ const testPath = extractTestPath(text2);
16388
+ const signatureRaw = extractFailureSignatureBlock(text2);
16037
16389
  if (!testPath) {
16038
16390
  downgrade(ctx, "reproduce missing TEST_PATH line in final message");
16039
16391
  return;
@@ -16212,8 +16564,8 @@ var init_planTaskJobs = __esm({
16212
16564
  ctx.output.reason = "planTaskJobs requires --issue";
16213
16565
  return;
16214
16566
  }
16215
- const issue = ctx.data.issue;
16216
- const specs = parseTaskJobSpecs(issue?.body ?? "");
16567
+ const issue2 = ctx.data.issue;
16568
+ const specs = parseTaskJobSpecs(issue2?.body ?? "");
16217
16569
  if (specs.length === 0) {
16218
16570
  ctx.skipAgent = true;
16219
16571
  ctx.output.exitCode = 64;
@@ -16543,8 +16895,8 @@ var init_promoteQaGoal = __esm({
16543
16895
  }
16544
16896
  let report;
16545
16897
  try {
16546
- const issue = getIssue(issueNum, ctx.cwd);
16547
- const reportComment = [...issue.comments].reverse().find((c) => c.body.includes(REPORT_JSON_OPEN2));
16898
+ const issue2 = getIssue(issueNum, ctx.cwd);
16899
+ const reportComment = [...issue2.comments].reverse().find((c) => c.body.includes(REPORT_JSON_OPEN2));
16548
16900
  if (!reportComment) {
16549
16901
  ctx.output.exitCode = 3;
16550
16902
  ctx.output.reason = `no QA report (${REPORT_JSON_OPEN2} \u2026) found on issue #${issueNum}`;
@@ -16611,9 +16963,9 @@ function latestResult(raw, agentResult) {
16611
16963
  function recordField6(value) {
16612
16964
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
16613
16965
  }
16614
- function resolveDotted(root, path51) {
16615
- if (!path51) return void 0;
16616
- return path51.split(".").reduce((value, key) => recordField6(value)?.[key], root);
16966
+ function resolveDotted(root, path52) {
16967
+ if (!path52) return void 0;
16968
+ return path52.split(".").reduce((value, key) => recordField6(value)?.[key], root);
16617
16969
  }
16618
16970
  function stringValue4(value) {
16619
16971
  return typeof value === "string" && value.trim() ? value.trim() : null;
@@ -16655,13 +17007,7 @@ var init_publishReport = __esm({
16655
17007
  ...publication.reviewArea ? { reviewArea: publication.reviewArea } : {}
16656
17008
  });
16657
17009
  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
- );
17010
+ writeStateText(ctx.config, ctx.cwd, `reports/${slug}/runs/${runId}.md`, markdown, `chore(reports): add ${slug} run`);
16665
17011
  };
16666
17012
  }
16667
17013
  });
@@ -17369,15 +17715,15 @@ var init_runFlow = __esm({
17369
17715
  init_issue();
17370
17716
  runFlow = async (ctx) => {
17371
17717
  const issueNumber = ctx.args.issue;
17372
- const issue = getIssue(issueNumber, ctx.cwd);
17718
+ const issue2 = getIssue(issueNumber, ctx.cwd);
17373
17719
  const cfgCtx = ctx.config.issueContext ?? {};
17374
17720
  const commentsFormatted = formatIssueComments(
17375
- issue.comments,
17721
+ issue2.comments,
17376
17722
  cfgCtx.commentLimit ?? DEFAULT_COMMENT_LIMIT,
17377
17723
  cfgCtx.commentMaxBytes ?? DEFAULT_COMMENT_MAX_BYTES
17378
17724
  );
17379
- ctx.data.issue = { ...issue, commentsFormatted };
17380
- if (issue.isPullRequest) {
17725
+ ctx.data.issue = { ...issue2, commentsFormatted };
17726
+ if (issue2.isPullRequest) {
17381
17727
  ctx.data.commentTargetType = "pr";
17382
17728
  ctx.data.commentTargetNumber = issueNumber;
17383
17729
  ctx.skipAgent = true;
@@ -17401,7 +17747,7 @@ var init_runFlow = __esm({
17401
17747
  }
17402
17748
  const branchInfo = ensureFeatureBranch(
17403
17749
  issueNumber,
17404
- issue.title,
17750
+ issue2.title,
17405
17751
  ctx.config.git.defaultBranch,
17406
17752
  ctx.cwd,
17407
17753
  base ?? void 0
@@ -17650,8 +17996,8 @@ async function flyCreateApp(name, orgSlug, token) {
17650
17996
  });
17651
17997
  if (res.status === 422) return;
17652
17998
  if (!res.ok) {
17653
- const text = await res.text().catch(() => "");
17654
- throw new Error(`createApp ${name}: ${res.status} ${text.slice(0, 200)}`);
17999
+ const text2 = await res.text().catch(() => "");
18000
+ throw new Error(`createApp ${name}: ${res.status} ${text2.slice(0, 200)}`);
17655
18001
  }
17656
18002
  }
17657
18003
  async function flyAllocateSharedIps(appName, token) {
@@ -17758,9 +18104,9 @@ async function flyCreatePreviewMachine(args, token) {
17758
18104
  const { id } = await res.json();
17759
18105
  return id;
17760
18106
  }
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;
18107
+ const text2 = await res.text().catch(() => "");
18108
+ lastErr = new Error(`createPreviewMachine ${res.status}: ${text2.slice(0, 300)}`);
18109
+ if (!/MANIFEST_UNKNOWN|manifest unknown/i.test(text2)) break;
17764
18110
  await new Promise((r) => setTimeout(r, 2e3 * (attempt + 1)));
17765
18111
  }
17766
18112
  throw lastErr ?? new Error("createPreviewMachine failed (unknown)");
@@ -18199,8 +18545,8 @@ var init_setCommentTarget = __esm({
18199
18545
 
18200
18546
  // src/scripts/setLifecycleLabel.ts
18201
18547
  function resolveTargetNumber(args) {
18202
- const issue = args.issue;
18203
- if (typeof issue === "number" && Number.isFinite(issue)) return issue;
18548
+ const issue2 = args.issue;
18549
+ if (typeof issue2 === "number" && Number.isFinite(issue2)) return issue2;
18204
18550
  const pr = args.pr;
18205
18551
  if (typeof pr === "number" && Number.isFinite(pr)) return pr;
18206
18552
  return void 0;
@@ -18415,9 +18761,10 @@ var init_syncFlow = __esm({
18415
18761
  });
18416
18762
 
18417
18763
  // src/scripts/validateAgencyModelProposal.ts
18418
- function validateModelBundle(bundle, expectedKind) {
18764
+ import * as path43 from "path";
18765
+ function validateModelBundle(bundle, expectedKind, options = {}) {
18419
18766
  const failures = [];
18420
- validateOneModel(bundle.model, bundle.files, "model", true, failures, expectedKind);
18767
+ validateOneModel(bundle.model, bundle.files, "model", true, failures, expectedKind, options);
18421
18768
  return failures;
18422
18769
  }
18423
18770
  function readExpectedModelKind(args) {
@@ -18427,7 +18774,7 @@ function readExpectedModelKind(args) {
18427
18774
  "validateAgencyModelProposal: with.modelKind must be intent, operation, agent, capability, goal, agentLoop, or workflow"
18428
18775
  );
18429
18776
  }
18430
- function validateOneModel(rawModel, files, label, strictSingleModel, failures, expectedKind) {
18777
+ function validateOneModel(rawModel, files, label, strictSingleModel, failures, expectedKind, options = {}) {
18431
18778
  if (!rawModel || typeof rawModel !== "object" || Array.isArray(rawModel)) {
18432
18779
  failures.push(`${label} must be an object`);
18433
18780
  return;
@@ -18445,11 +18792,11 @@ function validateOneModel(rawModel, files, label, strictSingleModel, failures, e
18445
18792
  for (const doc of REQUIRED_DOCS[kind]) {
18446
18793
  if (!docsUsed.includes(doc)) failures.push(`${label} docsUsed missing ${doc}`);
18447
18794
  }
18448
- validateFilesForKind(kind, slug, files, strictSingleModel, failures);
18795
+ validateFilesForKind(kind, slug, files, strictSingleModel, failures, options);
18449
18796
  validateModelShape(kind, model, files, slug, failures);
18450
18797
  }
18451
18798
  }
18452
- function validateFilesForKind(kind, slug, files, strictSingleModel, failures) {
18799
+ function validateFilesForKind(kind, slug, files, strictSingleModel, failures, options) {
18453
18800
  const paths = files.map((file) => normalizeBundlePath(file.path));
18454
18801
  if (paths.some((filePath) => filePath === "implementations" || filePath.startsWith("implementations/"))) {
18455
18802
  failures.push("files must not use obsolete implementation storage");
@@ -18496,6 +18843,7 @@ function validateFilesForKind(kind, slug, files, strictSingleModel, failures) {
18496
18843
  }
18497
18844
  if (kind === "workflow") {
18498
18845
  requirePath(paths, `capabilities/${slug}/profile.json`, "workflow capability profile", failures);
18846
+ requirePath(paths, `capabilities/${slug}/capability.md`, "workflow capability body", failures);
18499
18847
  const profile = parseJsonFile(files, `capabilities/${slug}/profile.json`, failures);
18500
18848
  if (profile) {
18501
18849
  if (profile.capabilityKind !== void 0) {
@@ -18505,6 +18853,30 @@ function validateFilesForKind(kind, slug, files, strictSingleModel, failures) {
18505
18853
  const hasTopLevelSteps = Array.isArray(profile.steps) && profile.steps.length > 0;
18506
18854
  if (!hasWorkflowObject && !hasTopLevelSteps) {
18507
18855
  failures.push("workflow profile must include workflow object or top-level steps");
18856
+ } else {
18857
+ const workflow = hasWorkflowObject ? profile.workflow : { steps: profile.steps, ...profile.startAt !== void 0 ? { startAt: profile.startAt } : {} };
18858
+ const known = options.capabilityRoot ? getCapabilityRoots(options.capabilityRoot).flatMap((root) => listCapabilityFolderSlugs(root)) : [];
18859
+ const uniqueKnown = [...new Set(known)];
18860
+ const capabilityInputs = /* @__PURE__ */ new Map();
18861
+ if (options.capabilityRoot) {
18862
+ for (const capability of uniqueKnown) {
18863
+ const inputs = getCapabilityActionInputs(capability, options.capabilityRoot);
18864
+ if (inputs) {
18865
+ capabilityInputs.set(
18866
+ capability,
18867
+ new Set(inputs.flatMap((input) => [input.name, input.flag.replace(/^--/, "")]))
18868
+ );
18869
+ }
18870
+ }
18871
+ }
18872
+ failures.push(
18873
+ ...formatWorkflowValidationIssues(
18874
+ validateWorkflow(workflow, {
18875
+ ...uniqueKnown.length > 0 ? { knownCapabilities: new Set(uniqueKnown) } : {},
18876
+ ...capabilityInputs.size > 0 ? { capabilityInputs } : {}
18877
+ })
18878
+ )
18879
+ );
18508
18880
  }
18509
18881
  }
18510
18882
  }
@@ -18692,6 +19064,9 @@ var REQUIRED_DOCS, validateAgencyModelProposal;
18692
19064
  var init_validateAgencyModelProposal = __esm({
18693
19065
  "src/scripts/validateAgencyModelProposal.ts"() {
18694
19066
  "use strict";
19067
+ init_capabilityFolders();
19068
+ init_registry();
19069
+ init_workflowValidation();
18695
19070
  init_openAgencyModelReviewPr();
18696
19071
  REQUIRED_DOCS = {
18697
19072
  intent: ["docs/intents.md", "docs/engine-company.md"],
@@ -18707,7 +19082,9 @@ var init_validateAgencyModelProposal = __esm({
18707
19082
  const raw = String(ctx.data.prSummary ?? "");
18708
19083
  const bundle = parseAgencyModelProposal(raw);
18709
19084
  const expectedKind = readExpectedModelKind(args);
18710
- const failures = validateModelBundle(bundle, expectedKind);
19085
+ const failures = validateModelBundle(bundle, expectedKind, {
19086
+ capabilityRoot: path43.join(ctx.cwd, ".kody", "capabilities")
19087
+ });
18711
19088
  if (failures.length > 0) {
18712
19089
  throw new Error(`validateAgencyModelProposal: ${failures.join("; ")}`);
18713
19090
  }
@@ -19266,9 +19643,9 @@ var init_writeAgentRunSummary = __esm({
19266
19643
  const summaryPath = process.env.GITHUB_STEP_SUMMARY;
19267
19644
  if (!summaryPath) return;
19268
19645
  const implementation = profile.name;
19269
- const issue = ctx.args.issue;
19646
+ const issue2 = ctx.args.issue;
19270
19647
  const pr = ctx.args.pr;
19271
- const target = issue ? `issue #${issue}` : pr ? `PR #${pr}` : "(unknown)";
19648
+ const target = issue2 ? `issue #${issue2}` : pr ? `PR #${pr}` : "(unknown)";
19272
19649
  const prUrl = ctx.output.prUrl;
19273
19650
  const exitCode = ctx.output.exitCode ?? 0;
19274
19651
  const reason = ctx.output.reason;
@@ -19608,38 +19985,38 @@ import { execFileSync as execFileSync24 } from "child_process";
19608
19985
  import * as crypto3 from "crypto";
19609
19986
  import * as fs45 from "fs";
19610
19987
  import * as os7 from "os";
19611
- import * as path43 from "path";
19988
+ import * as path44 from "path";
19612
19989
  function writeLocalFile(cwd, relativePath, content) {
19613
- const fullPath = path43.join(cwd, relativePath);
19614
- fs45.mkdirSync(path43.dirname(fullPath), { recursive: true });
19990
+ const fullPath = path44.join(cwd, relativePath);
19991
+ fs45.mkdirSync(path44.dirname(fullPath), { recursive: true });
19615
19992
  fs45.writeFileSync(fullPath, content);
19616
19993
  }
19617
19994
  function copyPath(source, target) {
19618
19995
  const st = fs45.lstatSync(source);
19619
19996
  fs45.rmSync(target, { recursive: true, force: true });
19620
19997
  if (st.isSymbolicLink()) return;
19621
- fs45.mkdirSync(path43.dirname(target), { recursive: true });
19998
+ fs45.mkdirSync(path44.dirname(target), { recursive: true });
19622
19999
  fs45.cpSync(source, target, { recursive: true, force: true });
19623
20000
  }
19624
20001
  function overlayDirectoryChildren(cwd, sourceDir, localDir) {
19625
20002
  if (!fs45.existsSync(sourceDir)) return;
19626
20003
  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);
20004
+ const source = path44.join(sourceDir, entry.name);
20005
+ const target = path44.join(cwd, localDir, entry.name);
19629
20006
  copyPath(source, target);
19630
20007
  }
19631
20008
  }
19632
20009
  function hydrateStateWorkspace(config, cwd) {
19633
20010
  if (process.env.VITEST && process.env[TEST_FETCH_ENV] !== "1") return;
19634
20011
  const parsed = parseStateRepo(config);
19635
- const hydrateKey = `${path43.resolve(cwd)}|${parsed.owner}/${parsed.repo}|${parsed.basePath}|${parsed.branch}`;
20012
+ const hydrateKey = `${path44.resolve(cwd)}|${parsed.owner}/${parsed.repo}|${parsed.basePath}|${parsed.branch}`;
19636
20013
  if (hydratedWorkspaces.has(hydrateKey)) return;
19637
20014
  const snapshotRoot = fetchStateSnapshot(parsed);
19638
20015
  for (const mapping of DIR_MAPPINGS) {
19639
- overlayDirectoryChildren(cwd, path43.join(snapshotRoot, mapping.stateDir), mapping.localDir);
20016
+ overlayDirectoryChildren(cwd, path44.join(snapshotRoot, mapping.stateDir), mapping.localDir);
19640
20017
  }
19641
20018
  for (const mapping of FILE_MAPPINGS) {
19642
- const source = path43.join(snapshotRoot, mapping.statePath);
20019
+ const source = path44.join(snapshotRoot, mapping.statePath);
19643
20020
  if (fs45.existsSync(source) && !fs45.lstatSync(source).isSymbolicLink() && fs45.statSync(source).isFile()) {
19644
20021
  writeLocalFile(cwd, mapping.localPath, fs45.readFileSync(source, "utf-8"));
19645
20022
  }
@@ -19647,11 +20024,11 @@ function hydrateStateWorkspace(config, cwd) {
19647
20024
  hydratedWorkspaces.add(hydrateKey);
19648
20025
  }
19649
20026
  function fetchStateSnapshot(parsed) {
19650
- const cacheDir = path43.join(cacheRoot2(), cacheKey3(parsed));
20027
+ const cacheDir = path44.join(cacheRoot2(), cacheKey3(parsed));
19651
20028
  const url = `https://github.com/${parsed.owner}/${parsed.repo}.git`;
19652
20029
  try {
19653
- fs45.mkdirSync(path43.dirname(cacheDir), { recursive: true });
19654
- if (!fs45.existsSync(path43.join(cacheDir, ".git"))) {
20030
+ fs45.mkdirSync(path44.dirname(cacheDir), { recursive: true });
20031
+ if (!fs45.existsSync(path44.join(cacheDir, ".git"))) {
19655
20032
  fs45.rmSync(cacheDir, { recursive: true, force: true });
19656
20033
  runGit3(["clone", "--no-checkout", "--filter=blob:none", url, cacheDir]);
19657
20034
  }
@@ -19667,10 +20044,10 @@ function fetchStateSnapshot(parsed) {
19667
20044
  `stateWorkspace: failed to fetch ${parsed.owner}/${parsed.repo}:${parsed.basePath}@${parsed.branch}: ${msg}`
19668
20045
  );
19669
20046
  }
19670
- return path43.join(cacheDir, parsed.basePath);
20047
+ return path44.join(cacheDir, parsed.basePath);
19671
20048
  }
19672
20049
  function cacheRoot2() {
19673
- return process.env[CACHE_ENV2]?.trim() || path43.join(os7.homedir(), ".cache", "kody", "state-repo");
20050
+ return process.env[CACHE_ENV2]?.trim() || path44.join(os7.homedir(), ".cache", "kody", "state-repo");
19674
20051
  }
19675
20052
  function cacheKey3(parsed) {
19676
20053
  return crypto3.createHash("sha256").update(`${parsed.owner}/${parsed.repo}#${parsed.branch}#${parsed.basePath}`).digest("hex").slice(0, 24);
@@ -19710,16 +20087,16 @@ var init_stateWorkspace = __esm({
19710
20087
  "use strict";
19711
20088
  init_stateRepo();
19712
20089
  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") }
20090
+ { stateDir: "capabilities", localDir: path44.join(".kody", "capabilities") },
20091
+ { stateDir: "agents", localDir: path44.join(".kody", "agents") },
20092
+ { stateDir: "context", localDir: path44.join(".kody", "context") },
20093
+ { stateDir: "memory", localDir: path44.join(".kody", "memory") }
19717
20094
  ];
19718
20095
  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") }
20096
+ { statePath: "instructions.md", localPath: path44.join(".kody", "instructions.md") },
20097
+ { statePath: "system-prompt.md", localPath: path44.join(".kody", "system-prompt.md") },
20098
+ { statePath: "variables.json", localPath: path44.join(".kody", "variables.json") },
20099
+ { statePath: "secrets.enc", localPath: path44.join(".kody", "secrets.enc") }
19723
20100
  ];
19724
20101
  CACHE_ENV2 = "KODY_STATE_REPO_CACHE";
19725
20102
  TEST_FETCH_ENV = "KODY_STATE_WORKSPACE_FETCH_FOR_TESTS";
@@ -19795,7 +20172,7 @@ var init_tools = __esm({
19795
20172
  import { spawn as spawn7 } from "child_process";
19796
20173
  import * as fs46 from "fs";
19797
20174
  import * as os8 from "os";
19798
- import * as path44 from "path";
20175
+ import * as path45 from "path";
19799
20176
  function isMutatingPostflight(scriptName) {
19800
20177
  return MUTATING_POSTFLIGHTS.has(scriptName ?? "");
19801
20178
  }
@@ -19823,9 +20200,9 @@ function collectShellSideChannels(ctx, stdout) {
19823
20200
  }
19824
20201
  }
19825
20202
  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]");
20203
+ const text2 = why.trim();
20204
+ if (!text2) return null;
20205
+ const safe = text2.replace(/-{3,}\s*END UNTRUSTED INPUT\s*-{3,}/gi, "[END UNTRUSTED INPUT]");
19829
20206
  return [
19830
20207
  "## The request that triggered this run",
19831
20208
  "",
@@ -20018,7 +20395,7 @@ async function runImplementation(profileName, input) {
20018
20395
  const jobWhyBlock = typeof ctx.data.jobWhy === "string" ? operatorRequestBlock(ctx.data.jobWhy) : null;
20019
20396
  const jobRefBlock = jobReferenceBlock(profileName, profile, ctx.data);
20020
20397
  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);
20398
+ const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path45.isAbsolute(p) ? p : path45.resolve(profile.dir, p)).filter((p) => p.length > 0);
20022
20399
  const syntheticPath = ctx.data.syntheticPluginPath;
20023
20400
  const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
20024
20401
  const agents = loadSubagents(profile);
@@ -20267,7 +20644,8 @@ async function runImplementation(profileName, input) {
20267
20644
  nextDispatch: ctx.output.nextDispatch,
20268
20645
  nextJob: ctx.output.nextJob,
20269
20646
  afterNextJob: ctx.output.afterNextJob,
20270
- taskState: ctx.data.taskState
20647
+ taskState: ctx.data.taskState,
20648
+ capabilityResults: Array.isArray(ctx.data.capabilityResults) ? ctx.data.capabilityResults : void 0
20271
20649
  });
20272
20650
  } catch (err) {
20273
20651
  const msg = err instanceof Error ? err.message : String(err);
@@ -20455,13 +20833,13 @@ function clearStampedLifecycleLabels(profile, ctx) {
20455
20833
  function resolveProfilePath(profileName) {
20456
20834
  const found = resolveImplementation(profileName);
20457
20835
  if (found) return found;
20458
- const here = path44.dirname(new URL(import.meta.url).pathname);
20836
+ const here = path45.dirname(new URL(import.meta.url).pathname);
20459
20837
  const candidates = [
20460
- path44.join(here, "implementations", profileName, "profile.json"),
20838
+ path45.join(here, "implementations", profileName, "profile.json"),
20461
20839
  // same-dir sibling (dev)
20462
- path44.join(here, "..", "implementations", profileName, "profile.json"),
20840
+ path45.join(here, "..", "implementations", profileName, "profile.json"),
20463
20841
  // up one (prod: dist/bin → dist/implementations)
20464
- path44.join(here, "..", "src", "implementations", profileName, "profile.json")
20842
+ path45.join(here, "..", "src", "implementations", profileName, "profile.json")
20465
20843
  // fallback
20466
20844
  ];
20467
20845
  for (const c of candidates) {
@@ -20580,7 +20958,7 @@ function resolveShellTimeoutMs(entry) {
20580
20958
  }
20581
20959
  async function runShellEntry(entry, ctx, profile) {
20582
20960
  const shellName = entry.shell;
20583
- const shellPath = path44.join(profile.dir, shellName);
20961
+ const shellPath = path45.join(profile.dir, shellName);
20584
20962
  if (!fs46.existsSync(shellPath)) {
20585
20963
  ctx.skipAgent = true;
20586
20964
  ctx.output.exitCode = 99;
@@ -20588,7 +20966,7 @@ async function runShellEntry(entry, ctx, profile) {
20588
20966
  return;
20589
20967
  }
20590
20968
  const positional = entry.with ? Object.values(entry.with).map((v) => String(v)) : [];
20591
- const outputFile = path44.join(
20969
+ const outputFile = path45.join(
20592
20970
  os8.tmpdir(),
20593
20971
  `kody-shell-output-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
20594
20972
  );
@@ -20756,6 +21134,68 @@ var init_executor = __esm({
20756
21134
  }
20757
21135
  });
20758
21136
 
21137
+ // src/workflowRunState.ts
21138
+ function workflowRunStatePath(workflowId, runId) {
21139
+ if (!SAFE_ID.test(workflowId)) throw new Error(`invalid workflow id ${workflowId}`);
21140
+ if (!SAFE_ID.test(runId)) throw new Error(`invalid workflow run id ${runId}`);
21141
+ return `workflows/${workflowId}/runs/${runId}.json`;
21142
+ }
21143
+ function parseWorkflowRunState(raw) {
21144
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
21145
+ const state = raw;
21146
+ if (state.status !== "running" && state.status !== "blocked" && state.status !== "failed" && state.status !== "done")
21147
+ return null;
21148
+ const completedStepIds = Array.isArray(state.completedStepIds) ? state.completedStepIds.filter((value) => typeof value === "string") : [];
21149
+ const transitionCounts = state.transitionCounts && typeof state.transitionCounts === "object" && !Array.isArray(state.transitionCounts) ? Object.fromEntries(
21150
+ Object.entries(state.transitionCounts).filter(
21151
+ (entry) => typeof entry[1] === "number" && Number.isInteger(entry[1]) && entry[1] >= 0
21152
+ )
21153
+ ) : {};
21154
+ const facts = state.facts && typeof state.facts === "object" && !Array.isArray(state.facts) ? state.facts : {};
21155
+ const evidenceEntries = state.evidence && typeof state.evidence === "object" && !Array.isArray(state.evidence) ? Object.entries(state.evidence).filter((entry) => typeof entry[1] === "boolean") : [];
21156
+ const artifacts = Array.isArray(state.artifacts) ? state.artifacts.filter(
21157
+ (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")
21158
+ ) : [];
21159
+ return {
21160
+ status: state.status,
21161
+ ...typeof state.currentStepId === "string" ? { currentStepId: state.currentStepId } : {},
21162
+ completedStepIds,
21163
+ transitionCounts,
21164
+ facts: { ...facts },
21165
+ evidence: Object.fromEntries(evidenceEntries),
21166
+ artifacts: artifacts.map((artifact) => ({ ...artifact })),
21167
+ ...typeof state.blocker === "string" ? { blocker: state.blocker } : {}
21168
+ };
21169
+ }
21170
+ function readWorkflowRunState(config, cwd, workflowId, runId) {
21171
+ const file = readStateText(config, cwd, workflowRunStatePath(workflowId, runId));
21172
+ if (!file) return null;
21173
+ try {
21174
+ return parseWorkflowRunState(JSON.parse(file.content));
21175
+ } catch {
21176
+ return null;
21177
+ }
21178
+ }
21179
+ function writeWorkflowRunState(config, cwd, workflowId, runId, state) {
21180
+ const path52 = workflowRunStatePath(workflowId, runId);
21181
+ upsertStateText(
21182
+ config,
21183
+ cwd,
21184
+ path52,
21185
+ `${JSON.stringify(state, null, 2)}
21186
+ `,
21187
+ `chore(workflows): update ${workflowId} run ${runId}`
21188
+ );
21189
+ }
21190
+ var SAFE_ID;
21191
+ var init_workflowRunState = __esm({
21192
+ "src/workflowRunState.ts"() {
21193
+ "use strict";
21194
+ init_stateRepo();
21195
+ SAFE_ID = /^[a-z0-9][a-z0-9_-]{0,79}$/;
21196
+ }
21197
+ });
21198
+
20759
21199
  // src/job.ts
20760
21200
  var job_exports = {};
20761
21201
  __export(job_exports, {
@@ -20768,7 +21208,7 @@ __export(job_exports, {
20768
21208
  stableJobKey: () => stableJobKey,
20769
21209
  validateJob: () => validateJob
20770
21210
  });
20771
- import * as path45 from "path";
21211
+ import * as path46 from "path";
20772
21212
  function newJobId(flavor) {
20773
21213
  localJobSeq += 1;
20774
21214
  const runId = process.env.GITHUB_RUN_ID;
@@ -20800,6 +21240,8 @@ function validateJob(input) {
20800
21240
  target: typeof j.target === "number" ? j.target : void 0,
20801
21241
  cliArgs: j.cliArgs ?? {},
20802
21242
  workflowFacts: j.workflowFacts && typeof j.workflowFacts === "object" && !Array.isArray(j.workflowFacts) ? j.workflowFacts : void 0,
21243
+ workflowState: parseWorkflowRunState(j.workflowState) ?? void 0,
21244
+ workflowRunId: typeof j.workflowRunId === "string" && j.workflowRunId.trim() ? j.workflowRunId.trim() : void 0,
20803
21245
  evidence: parseJobEvidence(j),
20804
21246
  flavor: j.flavor,
20805
21247
  force: j.force === true,
@@ -20834,7 +21276,7 @@ function parseJobEvidence(job) {
20834
21276
  async function runJob(job, base) {
20835
21277
  const valid = validateJob(job);
20836
21278
  const action = valid.action ?? valid.capability;
20837
- const projectCapabilitiesRoot = path45.join(base.cwd, ".kody", "capabilities");
21279
+ const projectCapabilitiesRoot = path46.join(base.cwd, ".kody", "capabilities");
20838
21280
  const resolvedCapability = !valid.workflow && action ? resolveCapabilityAction(action, projectCapabilitiesRoot) : null;
20839
21281
  const capabilityIdentity = valid.capability ?? resolvedCapability?.capability;
20840
21282
  const capabilityContext = valid.workflow ? null : loadCapabilityContext(capabilityIdentity, base.cwd);
@@ -20852,8 +21294,17 @@ async function runJob(job, base) {
20852
21294
  const profileName = explicitImplementation ?? capabilitySelectedImplementation;
20853
21295
  if (workflow && shouldRunCapabilityWorkflow(valid, workflow, workflowIdentity, capabilitySelectedImplementation, base)) {
20854
21296
  const workflowCapability = capabilityContext ?? workflowContext;
20855
- const workflowJob = workflowContext && !valid.why ? { ...valid, why: workflowContext.body } : valid;
20856
- return runCapabilityWorkflow(workflowJob, workflow, workflowCapability, base);
21297
+ const persistedState = valid.workflowRunId && workflowIdentity && base.config ? readWorkflowRunState(base.config, base.cwd, workflowIdentity, valid.workflowRunId) : null;
21298
+ const workflowJob = {
21299
+ ...workflowContext && !valid.why ? { ...valid, why: workflowContext.body } : valid,
21300
+ ...valid.workflowState ?? persistedState ? { workflowState: valid.workflowState ?? persistedState ?? void 0 } : {}
21301
+ };
21302
+ const checkpoint = valid.workflowRunId && workflowIdentity && base.config ? (state) => writeWorkflowRunState(base.config, base.cwd, workflowIdentity, valid.workflowRunId, state) : void 0;
21303
+ const result = await runCapabilityWorkflow(workflowJob, workflow, workflowCapability, base, checkpoint);
21304
+ if (valid.workflowRunId && workflowIdentity && base.config && result.workflowState) {
21305
+ writeWorkflowRunState(base.config, base.cwd, workflowIdentity, valid.workflowRunId, result.workflowState);
21306
+ }
21307
+ return result;
20857
21308
  }
20858
21309
  if (!profileName) {
20859
21310
  throw new InvalidJobError(`job capability resolves to no implementation: ${capabilityIdentity ?? action}`);
@@ -20933,7 +21384,22 @@ function shouldRunCapabilityWorkflow(job, workflow, capabilityIdentity, selected
20933
21384
  if (!requestedImplementation) return true;
20934
21385
  return requestedImplementation === selectedImplementation || requestedImplementation === capabilityIdentity || requestedImplementation === job.action;
20935
21386
  }
20936
- async function runCapabilityWorkflow(parent, workflow, capability, base) {
21387
+ async function runCapabilityWorkflow(parent, workflow, capability, base, checkpoint) {
21388
+ const invalid = workflowError(workflow, base);
21389
+ if (invalid) {
21390
+ if (isGraphWorkflow(workflow)) {
21391
+ const state = initialWorkflowState(parent, workflow);
21392
+ state.status = "blocked";
21393
+ state.blocker = invalid;
21394
+ checkpoint?.(state);
21395
+ return { exitCode: 64, reason: invalid, workflowState: state };
21396
+ }
21397
+ return { exitCode: 64, reason: invalid };
21398
+ }
21399
+ if (isGraphWorkflow(workflow)) return runGraphCapabilityWorkflow(parent, workflow, capability, base, checkpoint);
21400
+ return runLinearCapabilityWorkflow(parent, workflow, capability, base);
21401
+ }
21402
+ async function runLinearCapabilityWorkflow(parent, workflow, capability, base) {
20937
21403
  let chainData = {
20938
21404
  ...base.preloadedData ?? {},
20939
21405
  runSubjectType: "workflow",
@@ -20997,6 +21463,200 @@ async function runCapabilityWorkflow(parent, workflow, capability, base) {
20997
21463
  }
20998
21464
  return withWorkflowBoundaryEval(capability, result);
20999
21465
  }
21466
+ function isGraphWorkflow(workflow) {
21467
+ return workflow.startAt !== void 0 || workflow.steps.some((step) => step.id !== void 0 || step.next !== void 0 || step.inputs !== void 0);
21468
+ }
21469
+ function workflowError(workflow, base) {
21470
+ const projectCapabilitiesRoot = path46.join(base.cwd, ".kody", "capabilities");
21471
+ const knownCapabilities = /* @__PURE__ */ new Set();
21472
+ const capabilityInputs = /* @__PURE__ */ new Map();
21473
+ for (const step of workflow.steps) {
21474
+ const action = step.action ?? step.capability;
21475
+ const resolvedAction = resolveCapabilityAction(action, projectCapabilitiesRoot);
21476
+ const resolvedFolder = resolveCapabilityFolder(step.capability, projectCapabilitiesRoot);
21477
+ if (!resolvedAction && !resolvedFolder) continue;
21478
+ knownCapabilities.add(step.capability);
21479
+ const inputs = getCapabilityActionInputs(action, projectCapabilitiesRoot);
21480
+ if (inputs) {
21481
+ capabilityInputs.set(
21482
+ step.capability,
21483
+ new Set(inputs.flatMap((input) => [input.name, input.flag.replace(/^--/, "")]))
21484
+ );
21485
+ }
21486
+ }
21487
+ return formatWorkflowValidationIssues(validateWorkflow(workflow, { knownCapabilities, capabilityInputs }))[0] ?? null;
21488
+ }
21489
+ function initialWorkflowState(parent, workflow) {
21490
+ const prior = parent.workflowState;
21491
+ if (prior?.status === "done") {
21492
+ return {
21493
+ ...prior,
21494
+ status: "done",
21495
+ completedStepIds: [...prior.completedStepIds],
21496
+ transitionCounts: { ...prior.transitionCounts },
21497
+ facts: { ...prior.facts },
21498
+ evidence: { ...prior.evidence },
21499
+ artifacts: prior.artifacts.map((artifact) => ({ ...artifact }))
21500
+ };
21501
+ }
21502
+ const firstStepId = workflow.startAt ?? workflow.steps[0]?.id;
21503
+ const currentStepId = prior?.currentStepId ?? firstStepId;
21504
+ return {
21505
+ status: "running",
21506
+ ...currentStepId ? { currentStepId } : {},
21507
+ completedStepIds: [...prior?.completedStepIds ?? []],
21508
+ transitionCounts: { ...prior?.transitionCounts ?? {} },
21509
+ facts: { ...parent.workflowFacts ?? {}, ...prior?.facts ?? {} },
21510
+ evidence: { ...prior?.evidence ?? {} },
21511
+ artifacts: (prior?.artifacts ?? []).map((artifact) => ({ ...artifact }))
21512
+ };
21513
+ }
21514
+ function workflowChainData(parent, capability, base, state) {
21515
+ return {
21516
+ ...base.preloadedData ?? {},
21517
+ runSubjectType: "workflow",
21518
+ runSubjectId: capability.slug,
21519
+ runSubjectLabel: capability.title,
21520
+ runSubjectWorkflow: capability.slug,
21521
+ workflowCapability: capability.slug,
21522
+ workflowTitle: capability.title,
21523
+ workflowStepCount: capability.config.workflow?.steps.length ?? 0,
21524
+ workflowIssueNumber: workflowIssueNumber(parent),
21525
+ workflowFacts: state.facts,
21526
+ workflowEvidence: state.evidence,
21527
+ workflowArtifacts: state.artifacts,
21528
+ workflowStack: [
21529
+ ...Array.isArray(base.preloadedData?.workflowStack) ? base.preloadedData.workflowStack.filter((entry) => typeof entry === "string") : [],
21530
+ capability.slug
21531
+ ]
21532
+ };
21533
+ }
21534
+ async function runGraphCapabilityWorkflow(parent, workflow, capability, base, checkpoint) {
21535
+ const state = initialWorkflowState(parent, workflow);
21536
+ let chainData = workflowChainData(parent, capability, base, state);
21537
+ let result = { exitCode: 0 };
21538
+ let executedSteps = 0;
21539
+ const maxExecutedSteps = 1e3;
21540
+ while (state.currentStepId) {
21541
+ executedSteps += 1;
21542
+ if (executedSteps > maxExecutedSteps) {
21543
+ const reason = `workflow ${capability.slug} exceeded ${maxExecutedSteps} executed steps`;
21544
+ state.status = "blocked";
21545
+ state.blocker = reason;
21546
+ checkpoint?.(state);
21547
+ return { ...result, exitCode: 64, reason, workflowState: state };
21548
+ }
21549
+ const index = workflow.steps.findIndex((step2) => step2.id === state.currentStepId);
21550
+ const step = workflow.steps[index];
21551
+ if (!step) {
21552
+ const reason = `workflow ${capability.slug} current step ${state.currentStepId} is missing`;
21553
+ state.status = "blocked";
21554
+ state.blocker = reason;
21555
+ checkpoint?.(state);
21556
+ return { ...result, exitCode: 64, reason, workflowState: state };
21557
+ }
21558
+ const label = step.action ?? step.capability;
21559
+ checkpoint?.(state);
21560
+ let child;
21561
+ try {
21562
+ child = workflowStepToJob(step, parent, chainData);
21563
+ } catch (error) {
21564
+ const reason = error instanceof Error ? error.message : String(error);
21565
+ state.status = "blocked";
21566
+ state.blocker = reason;
21567
+ checkpoint?.(state);
21568
+ return { exitCode: 64, reason, workflowState: state };
21569
+ }
21570
+ process.stdout.write(
21571
+ `\u2192 kody: workflow ${capability.slug} step ${index + 1}/${workflow.steps.length} \u2192 ${label}
21572
+
21573
+ `
21574
+ );
21575
+ result = await runJob(child, {
21576
+ ...base,
21577
+ preloadedData: {
21578
+ ...chainData,
21579
+ workflowStep: step.id,
21580
+ workflowStepIndex: index + 1,
21581
+ workflowStepReason: step.reason,
21582
+ workflowContinueOn: step.continueOn ?? []
21583
+ }
21584
+ });
21585
+ mergeWorkflowResults(state, result.capabilityResults);
21586
+ const outcome = workflowOutcome(result);
21587
+ const prUrl = result.prUrl ?? result.taskState?.core.prUrl ?? (typeof chainData.workflowPrUrl === "string" ? chainData.workflowPrUrl : void 0);
21588
+ chainData = {
21589
+ ...workflowChainData(parent, capability, base, state),
21590
+ ...result.taskState ? { taskState: result.taskState } : {},
21591
+ ...outcome ? { workflowLastOutcome: outcome } : {},
21592
+ ...result.capabilityResults?.at(-1) ? { workflowLastResult: result.capabilityResults.at(-1) } : {},
21593
+ ...prUrl ? { workflowPrUrl: prUrl } : {},
21594
+ ...parsePrNumber5(prUrl) ? { workflowPrNumber: parsePrNumber5(prUrl) } : {}
21595
+ };
21596
+ if (!state.completedStepIds.includes(step.id)) state.completedStepIds.push(step.id);
21597
+ if (result.exitCode !== 0 && !canContinueWorkflow(step, outcome)) {
21598
+ state.status = "failed";
21599
+ state.blocker = result.reason ?? `workflow step ${step.id} failed`;
21600
+ checkpoint?.(state);
21601
+ return withWorkflowBoundaryEval(capability, { ...result, workflowState: state });
21602
+ }
21603
+ if (!step.next || step.next.length === 0) {
21604
+ state.status = "done";
21605
+ delete state.currentStepId;
21606
+ delete state.blocker;
21607
+ checkpoint?.(state);
21608
+ return withWorkflowBoundaryEval(capability, { ...result, workflowState: state });
21609
+ }
21610
+ const transition = selectWorkflowTransition(step, chainData, state.transitionCounts);
21611
+ if (!transition) {
21612
+ const reason = `workflow step ${step.id} has no available connection`;
21613
+ state.status = "blocked";
21614
+ state.blocker = reason;
21615
+ checkpoint?.(state);
21616
+ return { ...result, exitCode: 64, reason, workflowState: state };
21617
+ }
21618
+ if (transition.maxIterations !== void 0) {
21619
+ const key = `${step.id}->${transition.to}`;
21620
+ state.transitionCounts[key] = (state.transitionCounts[key] ?? 0) + 1;
21621
+ }
21622
+ state.currentStepId = transition.to;
21623
+ state.status = "running";
21624
+ delete state.blocker;
21625
+ checkpoint?.(state);
21626
+ }
21627
+ state.status = "done";
21628
+ checkpoint?.(state);
21629
+ return withWorkflowBoundaryEval(capability, { ...result, workflowState: state });
21630
+ }
21631
+ function mergeWorkflowResults(state, results) {
21632
+ for (const result of results ?? []) {
21633
+ Object.assign(state.facts, result.facts);
21634
+ Object.assign(state.evidence, result.evidence ?? {});
21635
+ for (const artifact of result.artifacts) {
21636
+ if (!state.artifacts.some(
21637
+ (existing) => existing.label === artifact.label && existing.url === artifact.url && existing.path === artifact.path
21638
+ )) {
21639
+ state.artifacts.push({ ...artifact });
21640
+ }
21641
+ }
21642
+ }
21643
+ }
21644
+ function selectWorkflowTransition(step, data, counts) {
21645
+ let fallback = null;
21646
+ for (const transition of step.next ?? []) {
21647
+ const key = `${step.id}->${transition.to}`;
21648
+ if (transition.maxIterations !== void 0 && (counts[key] ?? 0) >= transition.maxIterations) continue;
21649
+ if (transition.default === true) {
21650
+ fallback ??= transition;
21651
+ continue;
21652
+ }
21653
+ if (!transition.when || conditionMatches(transition.when, workflowConditionContext(data))) return transition;
21654
+ }
21655
+ return fallback;
21656
+ }
21657
+ function conditionMatches(condition, context) {
21658
+ return Object.entries(condition).every(([path52, expected]) => valueMatches(resolveDottedPath2(context, path52), expected));
21659
+ }
21000
21660
  function withWorkflowBoundaryEval(capability, result) {
21001
21661
  const capabilityKind = capability.config.capabilityKind;
21002
21662
  if (!capabilityKind) return result;
@@ -21017,8 +21677,18 @@ function withWorkflowBoundaryEval(capability, result) {
21017
21677
  }
21018
21678
  function workflowStepToJob(step, parent, chainData) {
21019
21679
  const action = step.action ?? step.capability;
21680
+ const mappedArgs = {};
21681
+ const conditionContext = workflowConditionContext(chainData);
21682
+ for (const [name, mapping] of Object.entries(step.inputs ?? {})) {
21683
+ const value = resolveDottedPath2(conditionContext, mapping.from);
21684
+ if (value === void 0) {
21685
+ throw new InvalidJobError(`workflow step ${step.id ?? action} needs missing input ${mapping.from}`);
21686
+ }
21687
+ mappedArgs[name] = value;
21688
+ }
21020
21689
  const rawArgs = {
21021
21690
  ...parent.cliArgs,
21691
+ ...mappedArgs,
21022
21692
  ...step.cliArgs ?? {}
21023
21693
  };
21024
21694
  const targetNumber = workflowStepTargetNumber(step, parent, chainData);
@@ -21052,9 +21722,7 @@ function workflowStepToJob(step, parent, chainData) {
21052
21722
  function shouldRunWorkflowStep(step, data) {
21053
21723
  if (!step.runWhen) return true;
21054
21724
  const context = workflowConditionContext(data);
21055
- return Object.entries(step.runWhen).every(
21056
- ([path51, expected]) => valueMatches(resolveDottedPath2(context, path51), expected)
21057
- );
21725
+ return conditionMatches(step.runWhen, context);
21058
21726
  }
21059
21727
  function canContinueWorkflow(step, outcome) {
21060
21728
  if (!outcome || !step.continueOn || step.continueOn.length === 0) return false;
@@ -21065,10 +21733,16 @@ function workflowOutcome(result) {
21065
21733
  }
21066
21734
  function workflowConditionContext(data) {
21067
21735
  const lastOutcome = data.workflowLastOutcome;
21736
+ const lastResult = data.workflowLastResult;
21068
21737
  return {
21069
21738
  ...data,
21739
+ facts: data.workflowFacts ?? {},
21740
+ evidence: data.workflowEvidence ?? {},
21741
+ artifacts: data.workflowArtifacts ?? [],
21742
+ result: lastResult,
21070
21743
  workflow: {
21071
21744
  lastOutcome,
21745
+ lastResult,
21072
21746
  issueNumber: data.workflowIssueNumber,
21073
21747
  prNumber: data.workflowPrNumber,
21074
21748
  prUrl: data.workflowPrUrl
@@ -21136,7 +21810,7 @@ function composeStepWhy(parentWhy, step) {
21136
21810
  }
21137
21811
  function loadCapabilityContext(slug, cwd) {
21138
21812
  if (!slug) return null;
21139
- return resolveCapabilityFolder(slug, path45.join(cwd, ".kody", "capabilities"));
21813
+ return resolveCapabilityFolder(slug, path46.join(cwd, ".kody", "capabilities"));
21140
21814
  }
21141
21815
  function loadWorkflowContext(slug, base) {
21142
21816
  if (!slug || !base.config || !isWorkflowDefinitionId(slug)) return null;
@@ -21175,6 +21849,8 @@ var init_job = __esm({
21175
21849
  init_executor();
21176
21850
  init_registry();
21177
21851
  init_workflowDefinitions();
21852
+ init_workflowRunState();
21853
+ init_workflowValidation();
21178
21854
  init_jobIdentity();
21179
21855
  init_jobIdentity();
21180
21856
  DEFAULT_INSTANT_AGENT = "kody";
@@ -21297,7 +21973,7 @@ function translateOpenAISseToBrain(opts) {
21297
21973
  // src/servers/brain-serve.ts
21298
21974
  import * as fs49 from "fs";
21299
21975
  import { createServer as createServer2 } from "http";
21300
- import * as path48 from "path";
21976
+ import * as path49 from "path";
21301
21977
 
21302
21978
  // src/chat/loop.ts
21303
21979
  init_agent();
@@ -21827,8 +22503,8 @@ async function runOpenAIChatTurn(args) {
21827
22503
  })
21828
22504
  });
21829
22505
  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)}` : ""}`;
22506
+ const text2 = await response.text().catch(() => "");
22507
+ const error = `OpenAI-compatible model request failed ${response.status}${text2 ? `: ${text2.slice(0, 500)}` : ""}`;
21832
22508
  await emit(opts.sink, "chat.error", opts.sessionId, "error", { error });
21833
22509
  return { exitCode: 99, error };
21834
22510
  }
@@ -21866,8 +22542,8 @@ function extractOpenAIReply(payload) {
21866
22542
  return content.map((part) => {
21867
22543
  if (typeof part === "string") return part;
21868
22544
  if (part && typeof part === "object" && "text" in part) {
21869
- const text = part.text;
21870
- return typeof text === "string" ? text : "";
22545
+ const text2 = part.text;
22546
+ return typeof text2 === "string" ? text2 : "";
21871
22547
  }
21872
22548
  return "";
21873
22549
  }).join("");
@@ -21985,7 +22661,7 @@ init_config();
21985
22661
  // src/kody-cli.ts
21986
22662
  import { execFileSync as execFileSync26 } from "child_process";
21987
22663
  import * as fs47 from "fs";
21988
- import * as path46 from "path";
22664
+ import * as path47 from "path";
21989
22665
 
21990
22666
  // src/app-auth.ts
21991
22667
  import { createSign } from "crypto";
@@ -22256,7 +22932,7 @@ function autoDispatch(opts) {
22256
22932
  }
22257
22933
  if (eventName !== "issue_comment") return null;
22258
22934
  const comment = objectValue(event.comment);
22259
- const issue = objectValue(event.issue);
22935
+ const issue2 = objectValue(event.issue);
22260
22936
  const user = objectValue(comment?.user);
22261
22937
  const rawBody = String(comment?.body ?? "");
22262
22938
  const authorLogin = String(user?.login ?? "");
@@ -22265,8 +22941,8 @@ function autoDispatch(opts) {
22265
22941
  const isBotAuthor = authorLogin === "kody-bot" || authorType === "Bot";
22266
22942
  if (!associationAllowed(event, opts?.config)) return null;
22267
22943
  const body = rawBody;
22268
- const targetNum = Number(issue?.number ?? 0);
22269
- const isPr = !!issue?.pull_request;
22944
+ const targetNum = Number(issue2?.number ?? 0);
22945
+ const isPr = !!issue2?.pull_request;
22270
22946
  if (!targetNum) return null;
22271
22947
  const afterTag = extractAfterTag(body);
22272
22948
  const firstTokenRaw = extractSubcommand(afterTag);
@@ -22350,7 +23026,7 @@ function autoDispatchTyped(opts) {
22350
23026
  return { kind: "silent", reason: "GHA event payload unreadable" };
22351
23027
  }
22352
23028
  const comment = objectValue(event.comment);
22353
- const issue = objectValue(event.issue);
23029
+ const issue2 = objectValue(event.issue);
22354
23030
  const user = objectValue(comment?.user);
22355
23031
  const rawBody = String(comment?.body ?? "");
22356
23032
  const authorLogin = String(user?.login ?? "");
@@ -22358,8 +23034,8 @@ function autoDispatchTyped(opts) {
22358
23034
  if (!hasKodyMention(rawBody)) {
22359
23035
  return { kind: "silent", reason: "comment does not mention @kody" };
22360
23036
  }
22361
- const targetNum = Number(issue?.number ?? 0);
22362
- const isPr = !!issue?.pull_request;
23037
+ const targetNum = Number(issue2?.number ?? 0);
23038
+ const isPr = !!issue2?.pull_request;
22363
23039
  if (!targetNum) {
22364
23040
  return { kind: "silent", reason: "comment has no associated issue/PR number" };
22365
23041
  }
@@ -22703,7 +23379,8 @@ function routeRunRequest(request) {
22703
23379
  if (intent !== "run" && intent !== "tick") {
22704
23380
  return { kind: "error", error: `workflow target does not support intent '${intent}'` };
22705
23381
  }
22706
- return { kind: "action", action: target.id, cliArgs: {} };
23382
+ const workflowRunId = typeof request.input?.runId === "string" && /^[a-z0-9][a-z0-9_-]{0,79}$/.test(request.input.runId) ? request.input.runId : void 0;
23383
+ return { kind: "action", action: target.id, cliArgs: {}, ...workflowRunId ? { workflowRunId } : {} };
22707
23384
  }
22708
23385
  return { kind: "error", error: "unsupported run request target" };
22709
23386
  }
@@ -22782,9 +23459,9 @@ async function resolveAuthToken(env = process.env) {
22782
23459
  return void 0;
22783
23460
  }
22784
23461
  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";
23462
+ if (fs47.existsSync(path47.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
23463
+ if (fs47.existsSync(path47.join(cwd, "yarn.lock"))) return "yarn";
23464
+ if (fs47.existsSync(path47.join(cwd, "bun.lockb"))) return "bun";
22788
23465
  return "npm";
22789
23466
  }
22790
23467
  function shouldChainScheduledWatch(match) {
@@ -22907,7 +23584,7 @@ async function runCi(argv) {
22907
23584
  return 0;
22908
23585
  }
22909
23586
  const args = parseCiArgs(argv);
22910
- const cwd = args.cwd ? path46.resolve(args.cwd) : process.cwd();
23587
+ const cwd = args.cwd ? path47.resolve(args.cwd) : process.cwd();
22911
23588
  try {
22912
23589
  const n = unpackAllSecrets();
22913
23590
  if (n > 0) process.stdout.write(`\u2192 kody: unpacked ${n} secret(s) from ALL_SECRETS
@@ -22931,6 +23608,7 @@ async function runCi(argv) {
22931
23608
  let manualWorkflowDispatch = false;
22932
23609
  let forceRunAction = null;
22933
23610
  let forceRunCliArgs = {};
23611
+ let forceWorkflowRunId;
22934
23612
  let runRequestFanOut = false;
22935
23613
  let runRequestFanOutForce = false;
22936
23614
  const parsedRunRequest = readRunRequestFromEnv();
@@ -22955,6 +23633,7 @@ async function runCi(argv) {
22955
23633
  } else if (route.kind === "action") {
22956
23634
  forceRunAction = route.action;
22957
23635
  forceRunCliArgs = route.cliArgs;
23636
+ forceWorkflowRunId = route.workflowRunId;
22958
23637
  }
22959
23638
  }
22960
23639
  const envForceAction = (process.env.KODY_FORCE_ACTION ?? "").trim();
@@ -23052,6 +23731,7 @@ async function runCi(argv) {
23052
23731
  capability: route.capability,
23053
23732
  workflow: route.workflow,
23054
23733
  implementation: route.implementation,
23734
+ workflowRunId: forceWorkflowRunId,
23055
23735
  cliArgs: { ...route.cliArgs, ...forceRunCliArgs },
23056
23736
  flavor: "instant",
23057
23737
  force: true
@@ -23341,7 +24021,7 @@ init_repoWorkspace();
23341
24021
  // src/scripts/brainTurnLog.ts
23342
24022
  init_runtimePaths();
23343
24023
  import * as fs48 from "fs";
23344
- import * as path47 from "path";
24024
+ import * as path48 from "path";
23345
24025
  import posixPath4 from "path/posix";
23346
24026
  var live = /* @__PURE__ */ new Map();
23347
24027
  function brainEventsFilePath(dir, chatId) {
@@ -23391,7 +24071,7 @@ function beginTurn(dir, chatId) {
23391
24071
  };
23392
24072
  live.set(chatId, state);
23393
24073
  const p = brainEventsFilePath(dir, chatId);
23394
- fs48.mkdirSync(path47.dirname(p), { recursive: true });
24074
+ fs48.mkdirSync(path48.dirname(p), { recursive: true });
23395
24075
  return (event) => {
23396
24076
  state.seq += 1;
23397
24077
  const rec = { seq: state.seq, turn, ts: Date.now(), event };
@@ -23769,7 +24449,7 @@ async function handleChatTurn(req, res, chatId, opts) {
23769
24449
  );
23770
24450
  }
23771
24451
  }
23772
- fs49.mkdirSync(path48.dirname(sessionFile), { recursive: true });
24452
+ fs49.mkdirSync(path49.dirname(sessionFile), { recursive: true });
23773
24453
  appendTurn(sessionFile, {
23774
24454
  role: "user",
23775
24455
  content: message,
@@ -23844,7 +24524,7 @@ async function handleChatTurn(req, res, chatId, opts) {
23844
24524
  function buildServer(opts) {
23845
24525
  const runTurn = opts.runTurn ?? runChatTurn;
23846
24526
  const cloneRepo = opts.cloneRepo ?? defaultCloneRepo;
23847
- const reposRoot = opts.reposRoot ?? path48.join(path48.dirname(path48.resolve(opts.cwd)), "repos");
24527
+ const reposRoot = opts.reposRoot ?? path49.join(path49.dirname(path49.resolve(opts.cwd)), "repos");
23848
24528
  return createServer2(async (req, res) => {
23849
24529
  if (!req.method || !req.url) {
23850
24530
  sendJson(res, 400, { error: "bad request" });
@@ -24448,7 +25128,7 @@ async function loadConfigSafe() {
24448
25128
 
24449
25129
  // src/chat-cli.ts
24450
25130
  import * as fs51 from "fs";
24451
- import * as path50 from "path";
25131
+ import * as path51 from "path";
24452
25132
 
24453
25133
  // src/chat/inbox.ts
24454
25134
  import { execFileSync as execFileSync27 } from "child_process";
@@ -24521,9 +25201,9 @@ function currentBranch(cwd) {
24521
25201
  // src/chat/state-sync.ts
24522
25202
  init_stateRepo();
24523
25203
  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);
25204
+ import * as path50 from "path";
25205
+ function jsonlLines2(text2) {
25206
+ return text2.split("\n").filter((line) => line.length > 0);
24527
25207
  }
24528
25208
  function renderJsonl2(lines) {
24529
25209
  return lines.length > 0 ? `${lines.join("\n")}
@@ -24541,7 +25221,7 @@ function syncJsonlFileFromState(opts) {
24541
25221
  const local = fs50.existsSync(opts.localPath) ? fs50.readFileSync(opts.localPath, "utf-8") : "";
24542
25222
  const next = mergeJsonl2(local, remote.content);
24543
25223
  if (next === local) return;
24544
- fs50.mkdirSync(path49.dirname(opts.localPath), { recursive: true });
25224
+ fs50.mkdirSync(path50.dirname(opts.localPath), { recursive: true });
24545
25225
  fs50.writeFileSync(opts.localPath, next);
24546
25226
  }
24547
25227
  function persistJsonlFileToState(opts) {
@@ -24811,7 +25491,7 @@ async function runChat(argv) {
24811
25491
  ${CHAT_HELP}`);
24812
25492
  return 64;
24813
25493
  }
24814
- const cwd = args.cwd ? path50.resolve(args.cwd) : process.cwd();
25494
+ const cwd = args.cwd ? path51.resolve(args.cwd) : process.cwd();
24815
25495
  const sessionId = args.sessionId;
24816
25496
  const runRequest = readRunRequestFromEnv();
24817
25497
  if (runRequest && "request" in runRequest) {
@@ -24991,8 +25671,8 @@ var FlyClient = class {
24991
25671
  get fetch() {
24992
25672
  return this.opts.fetchImpl ?? fetch;
24993
25673
  }
24994
- async call(path51, init = {}) {
24995
- const res = await this.fetch(`${FLY_API_BASE}${path51}`, {
25674
+ async call(path52, init = {}) {
25675
+ const res = await this.fetch(`${FLY_API_BASE}${path52}`, {
24996
25676
  method: init.method ?? "GET",
24997
25677
  headers: {
24998
25678
  Authorization: `Bearer ${this.opts.token}`,
@@ -25002,8 +25682,8 @@ var FlyClient = class {
25002
25682
  });
25003
25683
  if (res.status === 404 && init.allow404) return null;
25004
25684
  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}`);
25685
+ const text2 = await res.text().catch(() => "");
25686
+ throw new Error(`Fly API ${res.status} on ${path52}: ${text2.slice(0, 200) || res.statusText}`);
25007
25687
  }
25008
25688
  if (res.status === 204) return null;
25009
25689
  const raw = await res.text();
@@ -26350,11 +27030,11 @@ function envRunMode(env = process.env) {
26350
27030
  return { ...result, command: "ci", ciArgv: [] };
26351
27031
  }
26352
27032
  if (mode === "issue") {
26353
- const issue = (env.ISSUE_NUMBER ?? "").trim();
26354
- if (!issue) {
27033
+ const issue2 = (env.ISSUE_NUMBER ?? "").trim();
27034
+ if (!issue2) {
26355
27035
  return { ...result, errors: ["KODY_RUN_MODE=issue requires ISSUE_NUMBER"] };
26356
27036
  }
26357
- return { ...result, command: "ci", ciArgv: ["--issue", issue] };
27037
+ return { ...result, command: "ci", ciArgv: ["--issue", issue2] };
26358
27038
  }
26359
27039
  return { ...result, errors: [`unknown KODY_RUN_MODE: ${mode}`] };
26360
27040
  }