@kody-ade/kody-engine 0.4.321 → 0.4.322

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.
Files changed (2) hide show
  1. package/dist/bin/kody.js +401 -70
  2. package/package.json +1 -1
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.321",
18
+ version: "0.4.322",
19
19
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
20
20
  license: "MIT",
21
21
  type: "module",
@@ -3942,6 +3942,73 @@ var init_capabilityReport = __esm({
3942
3942
  }
3943
3943
  });
3944
3944
 
3945
+ // src/goal/evidenceState.ts
3946
+ function parseGoalEvidenceState(raw) {
3947
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
3948
+ const out = {};
3949
+ for (const [evidence, value] of Object.entries(raw)) {
3950
+ const progress = parseGoalEvidenceProgress(value);
3951
+ if (progress) out[evidence] = progress;
3952
+ }
3953
+ return out;
3954
+ }
3955
+ function mergeGoalEvidenceProgress(state, evidence, update) {
3956
+ const prior = state[evidence];
3957
+ const next = {
3958
+ resultClass: update.resultClass,
3959
+ attempts: update.attempts ?? prior?.attempts ?? 0,
3960
+ ...prior?.reason ? { reason: prior.reason } : {},
3961
+ ...prior?.nextAction ? { nextAction: prior.nextAction } : {},
3962
+ ...prior?.nextRetryAt ? { nextRetryAt: prior.nextRetryAt } : {},
3963
+ ...prior?.issue ? { issue: prior.issue } : {},
3964
+ ...prior?.updatedAt ? { updatedAt: prior.updatedAt } : {},
3965
+ ...definedProgressFields(update)
3966
+ };
3967
+ return {
3968
+ ...state,
3969
+ [evidence]: next
3970
+ };
3971
+ }
3972
+ function isGoalEvidenceResultClass(value) {
3973
+ return value === "succeeded" || value === "pending" || value === "retryable" || value === "needsFix" || value === "fatal";
3974
+ }
3975
+ function parseGoalEvidenceProgress(value) {
3976
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
3977
+ const raw = value;
3978
+ if (!isGoalEvidenceResultClass(raw.resultClass)) return null;
3979
+ const attempts = typeof raw.attempts === "number" && raw.attempts >= 0 ? Math.floor(raw.attempts) : 0;
3980
+ return {
3981
+ resultClass: raw.resultClass,
3982
+ attempts,
3983
+ ...stringField2(raw.reason) ? { reason: stringField2(raw.reason) } : {},
3984
+ ...stringField2(raw.nextAction) ? { nextAction: stringField2(raw.nextAction) } : {},
3985
+ ...stringField2(raw.nextRetryAt) ? { nextRetryAt: stringField2(raw.nextRetryAt) } : {},
3986
+ ...positiveInteger(raw.issue) ? { issue: positiveInteger(raw.issue) } : {},
3987
+ ...stringField2(raw.updatedAt) ? { updatedAt: stringField2(raw.updatedAt) } : {}
3988
+ };
3989
+ }
3990
+ function definedProgressFields(update) {
3991
+ const out = {};
3992
+ if (update.reason !== void 0) out.reason = update.reason;
3993
+ if (update.nextAction !== void 0) out.nextAction = update.nextAction;
3994
+ if (update.nextRetryAt !== void 0) out.nextRetryAt = update.nextRetryAt;
3995
+ if (update.issue !== void 0) out.issue = update.issue;
3996
+ if (update.updatedAt !== void 0) out.updatedAt = update.updatedAt;
3997
+ return out;
3998
+ }
3999
+ function stringField2(value) {
4000
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
4001
+ }
4002
+ function positiveInteger(value) {
4003
+ if (typeof value === "number" && Number.isInteger(value) && value > 0) return value;
4004
+ return void 0;
4005
+ }
4006
+ var init_evidenceState = __esm({
4007
+ "src/goal/evidenceState.ts"() {
4008
+ "use strict";
4009
+ }
4010
+ });
4011
+
3945
4012
  // src/capabilityResult.ts
3946
4013
  function parseCapabilityResultsFromText(text) {
3947
4014
  const results = [];
@@ -3979,6 +4046,7 @@ function parseCapabilityResult(raw) {
3979
4046
  version: 1,
3980
4047
  ...target ? { target } : {},
3981
4048
  status: obj.status,
4049
+ ...isGoalEvidenceResultClass(obj.resultClass) ? { resultClass: obj.resultClass } : {},
3982
4050
  summary,
3983
4051
  ...evidence ? { evidence } : {},
3984
4052
  facts,
@@ -4038,6 +4106,7 @@ var init_capabilityResult = __esm({
4038
4106
  "src/capabilityResult.ts"() {
4039
4107
  "use strict";
4040
4108
  init_capabilityReport();
4109
+ init_evidenceState();
4041
4110
  RESULT_LINE = /^KODY_(?:CAPABILITY|CAPABILITY)_RESULT=(.+)$/gm;
4042
4111
  STATUSES = /* @__PURE__ */ new Set(["pass", "fail", "blocked", "changed", "noop"]);
4043
4112
  CONTROL_FACT_KEYS2 = /* @__PURE__ */ new Set(["blockers", "destination", "capabilities", "route", "stage", "state"]);
@@ -6831,6 +6900,8 @@ function planManagedGoalTick(goal) {
6831
6900
  if (!missing) {
6832
6901
  goal.stage = "done";
6833
6902
  delete goal.facts.pendingEvidence;
6903
+ goal.reason = "destination evidence satisfied";
6904
+ goal.nextAction = "done";
6834
6905
  return { kind: "done" };
6835
6906
  }
6836
6907
  const step = goal.route.find((candidate) => candidate.evidence === missing);
@@ -6840,32 +6911,44 @@ function planManagedGoalTick(goal) {
6840
6911
  delete goal.facts.pendingEvidence;
6841
6912
  const total = typeof goal.facts.simpleAttachedTaskCount === "number" ? goal.facts.simpleAttachedTaskCount : 0;
6842
6913
  const open = typeof goal.facts.simpleOpenTaskCount === "number" ? goal.facts.simpleOpenTaskCount : 0;
6914
+ goal.reason = total === 0 ? "waiting for labelled tasks" : `waiting for ${open} open labelled task(s)`;
6915
+ goal.nextAction = "wait";
6843
6916
  return {
6844
6917
  kind: "wait",
6845
6918
  evidence: missing,
6846
6919
  stage: "waiting",
6847
- reason: total === 0 ? "waiting for labelled tasks" : `waiting for ${open} open labelled task(s)`
6920
+ reason: goal.reason
6848
6921
  };
6849
6922
  }
6850
6923
  const reason = `no route step for evidence: ${missing}`;
6851
6924
  goal.stage = "blocked";
6925
+ goal.reason = reason;
6926
+ goal.nextAction = "fix route";
6852
6927
  pushBlocker(goal, reason);
6853
6928
  return { kind: "blocked", evidence: missing, stage: "blocked", reason };
6854
6929
  }
6930
+ const progressDecision = decisionFromEvidenceProgress(goal, step, missing);
6931
+ if (progressDecision) return progressDecision;
6855
6932
  if (!goal.capabilities.includes(step.capability)) {
6856
6933
  const reason = `route capability ${step.capability} is not attached to this goal`;
6857
6934
  goal.stage = "blocked";
6935
+ goal.reason = reason;
6936
+ goal.nextAction = "attach capability";
6858
6937
  pushBlocker(goal, reason);
6859
6938
  return { kind: "blocked", evidence: missing, stage: step.stage, reason };
6860
6939
  }
6861
6940
  const resolved = resolveRouteArgs(goal, step);
6862
6941
  if (!resolved.ok) {
6863
6942
  goal.stage = "blocked";
6943
+ goal.reason = resolved.reason;
6944
+ goal.nextAction = "fix route args";
6864
6945
  pushBlocker(goal, resolved.reason);
6865
6946
  return { kind: "blocked", evidence: missing, stage: step.stage, reason: resolved.reason };
6866
6947
  }
6867
6948
  goal.stage = step.stage;
6868
6949
  goal.facts.pendingEvidence = missing;
6950
+ goal.reason = `dispatch ${step.capability} for ${missing}`;
6951
+ goal.nextAction = "dispatch";
6869
6952
  return {
6870
6953
  kind: "dispatch",
6871
6954
  evidence: missing,
@@ -6876,6 +6959,79 @@ function planManagedGoalTick(goal) {
6876
6959
  ...step.saveReport === true ? { saveReport: true } : {}
6877
6960
  };
6878
6961
  }
6962
+ function decisionFromEvidenceProgress(goal, step, evidence) {
6963
+ const progress = goal.evidenceState?.[evidence];
6964
+ if (!progress) return null;
6965
+ if (progress.resultClass === "pending") {
6966
+ const policy = step.onPending ?? { action: "wait" };
6967
+ if (policy.action === "retry") return null;
6968
+ if (policy.action === "block" || policy.action === "issue") {
6969
+ const reason2 = progress.reason ?? `waiting for ${evidence}`;
6970
+ goal.stage = "blocked";
6971
+ goal.reason = reason2;
6972
+ goal.nextAction = policy.action === "issue" ? "create issue" : "block";
6973
+ pushBlocker(goal, reason2);
6974
+ return { kind: "blocked", evidence, stage: step.stage, reason: reason2 };
6975
+ }
6976
+ const reason = progress.reason ?? `waiting for ${evidence}`;
6977
+ goal.stage = step.stage;
6978
+ goal.reason = reason;
6979
+ goal.nextAction = progress.nextAction ?? "wait";
6980
+ return { kind: "wait", evidence, stage: step.stage, reason };
6981
+ }
6982
+ if (progress.resultClass === "retryable") {
6983
+ const policy = step.onFailure ?? { action: "retry" };
6984
+ if (policy.action === "block" || policy.action === "issue") {
6985
+ const reason = progress.reason ?? `retryable failure for ${evidence}`;
6986
+ goal.stage = "blocked";
6987
+ goal.reason = reason;
6988
+ goal.nextAction = policy.action === "issue" ? "create issue" : "block";
6989
+ pushBlocker(goal, reason);
6990
+ return { kind: "blocked", evidence, stage: step.stage, reason };
6991
+ }
6992
+ if (policy.action === "wait") {
6993
+ const reason = progress.reason ?? `waiting to retry ${evidence}`;
6994
+ goal.stage = step.stage;
6995
+ goal.reason = reason;
6996
+ goal.nextAction = progress.nextRetryAt ? `retry after ${progress.nextRetryAt}` : "wait";
6997
+ return { kind: "wait", evidence, stage: step.stage, reason };
6998
+ }
6999
+ if (policy.maxAttempts !== void 0 && progress.attempts >= policy.maxAttempts) {
7000
+ const reason = `retry limit reached for ${evidence}`;
7001
+ goal.stage = "blocked";
7002
+ goal.reason = reason;
7003
+ goal.nextAction = "create issue";
7004
+ pushBlocker(goal, reason);
7005
+ return { kind: "blocked", evidence, stage: step.stage, reason };
7006
+ }
7007
+ if (progress.nextRetryAt && Date.parse(progress.nextRetryAt) > Date.now()) {
7008
+ const reason = progress.reason ?? `retry ${evidence} after ${progress.nextRetryAt}`;
7009
+ goal.stage = step.stage;
7010
+ goal.reason = reason;
7011
+ goal.nextAction = `retry after ${progress.nextRetryAt}`;
7012
+ return { kind: "wait", evidence, stage: step.stage, reason };
7013
+ }
7014
+ return null;
7015
+ }
7016
+ if (progress.resultClass === "needsFix" || progress.resultClass === "fatal") {
7017
+ const policy = step.onFailure ?? defaultFailurePolicy(progress.resultClass);
7018
+ if (policy.action === "retry") return null;
7019
+ const reason = progress.reason ?? `${resultClassLabel(progress.resultClass)} for ${evidence}`;
7020
+ goal.stage = "blocked";
7021
+ goal.reason = reason;
7022
+ goal.nextAction = progress.issue ? `fix issue #${progress.issue}` : policy.action === "issue" ? "create issue" : "block";
7023
+ pushBlocker(goal, reason);
7024
+ return { kind: "blocked", evidence, stage: step.stage, reason };
7025
+ }
7026
+ return null;
7027
+ }
7028
+ function defaultFailurePolicy(resultClass) {
7029
+ return resultClass === "needsFix" ? { action: "issue" } : { action: "block" };
7030
+ }
7031
+ function resultClassLabel(resultClass) {
7032
+ if (resultClass === "needsFix") return "needs fix";
7033
+ return resultClass;
7034
+ }
6879
7035
  function asStringArray(value) {
6880
7036
  if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) return null;
6881
7037
  return [...value];
@@ -6901,11 +7057,30 @@ function asRoute(value) {
6901
7057
  capability: raw.capability,
6902
7058
  executable: typeof raw.executable === "string" ? raw.executable : void 0,
6903
7059
  args: args ?? void 0,
6904
- saveReport: raw.saveReport === true
7060
+ saveReport: raw.saveReport === true,
7061
+ onPending: asRoutePolicy(raw.onPending),
7062
+ onFailure: asRoutePolicy(raw.onFailure)
6905
7063
  });
6906
7064
  }
6907
7065
  return route;
6908
7066
  }
7067
+ function asRoutePolicy(value) {
7068
+ if (typeof value === "string") {
7069
+ return isRoutePolicyAction(value) ? { action: value } : void 0;
7070
+ }
7071
+ const raw = asRecord(value);
7072
+ if (!raw || !isRoutePolicyAction(raw.action)) return void 0;
7073
+ const maxAttempts = typeof raw.maxAttempts === "number" && Number.isInteger(raw.maxAttempts) && raw.maxAttempts > 0 ? raw.maxAttempts : void 0;
7074
+ const retryAfterSeconds = typeof raw.retryAfterSeconds === "number" && raw.retryAfterSeconds >= 0 ? Math.floor(raw.retryAfterSeconds) : void 0;
7075
+ return {
7076
+ action: raw.action,
7077
+ ...maxAttempts !== void 0 ? { maxAttempts } : {},
7078
+ ...retryAfterSeconds !== void 0 ? { retryAfterSeconds } : {}
7079
+ };
7080
+ }
7081
+ function isRoutePolicyAction(value) {
7082
+ return value === "wait" || value === "retry" || value === "block" || value === "issue";
7083
+ }
6909
7084
  function asPreferredRunTime(value) {
6910
7085
  const raw = asRecord(value);
6911
7086
  if (!raw) return void 0;
@@ -6941,7 +7116,10 @@ function managedGoalFromState(state) {
6941
7116
  loopTarget: asLoopTarget(extra.loopTarget),
6942
7117
  stage: typeof extra.stage === "string" ? extra.stage : void 0,
6943
7118
  facts,
6944
- blockers
7119
+ blockers,
7120
+ evidenceState: parseGoalEvidenceState(extra.evidenceState),
7121
+ reason: typeof extra.reason === "string" ? extra.reason : void 0,
7122
+ nextAction: typeof extra.nextAction === "string" ? extra.nextAction : void 0
6945
7123
  };
6946
7124
  }
6947
7125
  function writeManagedGoalToState(state, goal) {
@@ -6955,7 +7133,10 @@ function writeManagedGoalToState(state, goal) {
6955
7133
  route: goal.route,
6956
7134
  stage: goal.stage,
6957
7135
  facts: goal.facts,
6958
- blockers: goal.blockers
7136
+ blockers: goal.blockers,
7137
+ evidenceState: goal.evidenceState ?? {},
7138
+ reason: goal.reason,
7139
+ nextAction: goal.nextAction
6959
7140
  }
6960
7141
  };
6961
7142
  }
@@ -6963,6 +7144,7 @@ var SIMPLE_GOAL_TYPE, SIMPLE_GOAL_EVIDENCE;
6963
7144
  var init_manager = __esm({
6964
7145
  "src/goal/manager.ts"() {
6965
7146
  "use strict";
7147
+ init_evidenceState();
6966
7148
  SIMPLE_GOAL_TYPE = "simple";
6967
7149
  SIMPLE_GOAL_EVIDENCE = "labelledTasksComplete";
6968
7150
  }
@@ -7072,7 +7254,8 @@ function goalRunLogSnapshot(goalId, goalState, goal) {
7072
7254
  preferredRunTime: goal.preferredRunTime,
7073
7255
  loopTarget: goal.loopTarget,
7074
7256
  blockers: [...goal.blockers],
7075
- facts: { ...goal.facts }
7257
+ facts: { ...goal.facts },
7258
+ evidenceState: { ...goal.evidenceState ?? {} }
7076
7259
  };
7077
7260
  }
7078
7261
  function goalRunLogChange(before, after) {
@@ -7096,6 +7279,11 @@ function goalRunLogChange(before, after) {
7096
7279
  const evidence = diffStringArrays(beforeSatisfied ?? [], afterSatisfied ?? []);
7097
7280
  if (evidence.added.length > 0 || evidence.removed.length > 0) change.satisfiedEvidence = evidence;
7098
7281
  }
7282
+ const beforeEvidenceState = recordValue2(before.evidenceState);
7283
+ const afterEvidenceState = recordValue2(after.evidenceState);
7284
+ if (beforeEvidenceState || afterEvidenceState) {
7285
+ change.evidenceState = diffRecordKeys(beforeEvidenceState ?? {}, afterEvidenceState ?? {});
7286
+ }
7099
7287
  return Object.keys(change).length > 0 ? change : void 0;
7100
7288
  }
7101
7289
  function goalRunLogs(data) {
@@ -7456,11 +7644,11 @@ function parseTodoGoalState(goalId, filePath, raw) {
7456
7644
  const data = parseJsonRecord(raw) ?? {};
7457
7645
  const items = normalizeItems(data.items);
7458
7646
  const destination = recordField(data.destination);
7459
- const evidence = stringArray(destination.evidence).length > 0 ? stringArray(destination.evidence) : stringArray(data.evidence).length > 0 ? stringArray(data.evidence) : items.map((item) => stringField2(recordField(item.meta).evidence) || item.id).filter(Boolean);
7647
+ const evidence = stringArray(destination.evidence).length > 0 ? stringArray(destination.evidence) : stringArray(data.evidence).length > 0 ? stringArray(data.evidence) : items.map((item) => stringField3(recordField(item.meta).evidence) || item.id).filter(Boolean);
7460
7648
  const facts = {
7461
7649
  ...recordField(data.facts),
7462
7650
  ...Object.fromEntries(
7463
- items.map((item) => [stringField2(recordField(item.meta).evidence) || item.id, item.completed])
7651
+ items.map((item) => [stringField3(recordField(item.meta).evidence) || item.id, item.completed])
7464
7652
  )
7465
7653
  };
7466
7654
  const route = Array.isArray(data.route) ? data.route : routeFromItems(items);
@@ -7472,10 +7660,10 @@ function parseTodoGoalState(goalId, filePath, raw) {
7472
7660
  type: data.type ?? "general",
7473
7661
  destination: {
7474
7662
  ...destination,
7475
- outcome: stringField2(data.description) || stringField2(destination.outcome),
7663
+ outcome: stringField3(data.description) || stringField3(destination.outcome),
7476
7664
  evidence
7477
7665
  },
7478
- capabilities: stringArray(data.capabilities).length > 0 ? stringArray(data.capabilities) : route.map((step) => stringField2(step.capability)).filter(Boolean),
7666
+ capabilities: stringArray(data.capabilities).length > 0 ? stringArray(data.capabilities) : route.map((step) => stringField3(step.capability)).filter(Boolean),
7479
7667
  route,
7480
7668
  facts,
7481
7669
  blockers: stringArray(data.blockers)
@@ -7487,16 +7675,25 @@ function isManagedTodoRaw(raw) {
7487
7675
  function serializeTodoGoalState(goalId, state, previousRaw) {
7488
7676
  const raw = JSON.parse(serializeGoalState(state));
7489
7677
  const destination = recordField(raw.destination);
7490
- const outcome = stringField2(raw.description) || stringField2(destination.outcome);
7678
+ const outcome = stringField3(raw.description) || stringField3(destination.outcome);
7491
7679
  const evidence = stringArray(destination.evidence).length > 0 ? stringArray(destination.evidence) : stringArray(raw.evidence);
7492
7680
  const route = Array.isArray(raw.route) ? raw.route : [];
7493
7681
  const facts = recordField(raw.facts);
7682
+ const evidenceState = recordField(raw.evidenceState);
7494
7683
  const now = (/* @__PURE__ */ new Date()).toISOString();
7495
- const createdAt = stringField2(raw.createdAt) || stringField2(raw.startedAt) || now;
7496
- const routeByEvidence = new Map(route.map((step) => [stringField2(step.evidence), step]));
7684
+ const createdAt = stringField3(raw.createdAt) || stringField3(raw.startedAt) || now;
7685
+ const routeByEvidence = new Map(route.map((step) => [stringField3(step.evidence), step]));
7497
7686
  const previousItems = new Map(parseItemsFromAnyRaw(previousRaw ?? "").map((item) => [item.id, item]));
7498
7687
  const items = evidence.length > 0 ? evidence.map(
7499
- (key) => itemFromEvidence(key, routeByEvidence.get(key), facts, createdAt, now, previousItems.get(key))
7688
+ (key) => itemFromEvidence(
7689
+ key,
7690
+ routeByEvidence.get(key),
7691
+ facts,
7692
+ evidenceState,
7693
+ createdAt,
7694
+ now,
7695
+ previousItems.get(key)
7696
+ )
7500
7697
  ) : stringArray(raw.capabilities).map(
7501
7698
  (capability) => itemFromCapability(capability, createdAt, previousItems.get(capability))
7502
7699
  );
@@ -7521,11 +7718,12 @@ function serializeTodoGoalState(goalId, state, previousRaw) {
7521
7718
  function isManagedTodoRecord(record2) {
7522
7719
  return record2.managed === true || record2.managed === "true" || record2.managedModel === "agentGoal" || record2.managedModel === "agentLoop";
7523
7720
  }
7524
- function itemFromEvidence(evidence, step, facts, createdAt, now, prior) {
7721
+ function itemFromEvidence(evidence, step, facts, evidenceState, createdAt, now, prior) {
7525
7722
  const completed = facts[evidence] === true;
7723
+ const progress = recordField(evidenceState[evidence]);
7526
7724
  return {
7527
7725
  id: evidence,
7528
- title: (prior?.title ?? stringField2(step?.stage)) || evidence,
7726
+ title: (prior?.title ?? stringField3(step?.stage)) || evidence,
7529
7727
  body: prior?.body ?? "",
7530
7728
  assignee: prior?.assignee ?? null,
7531
7729
  completed,
@@ -7534,11 +7732,19 @@ function itemFromEvidence(evidence, step, facts, createdAt, now, prior) {
7534
7732
  meta: {
7535
7733
  ...prior?.meta ?? {},
7536
7734
  evidence,
7735
+ ...stringField3(progress?.resultClass) ? { resultClass: stringField3(progress?.resultClass) } : {},
7736
+ ...typeof progress?.attempts === "number" ? { attempts: progress.attempts } : {},
7737
+ ...stringField3(progress?.reason) ? { reason: stringField3(progress?.reason) } : {},
7738
+ ...stringField3(progress?.nextAction) ? { nextAction: stringField3(progress?.nextAction) } : {},
7739
+ ...stringField3(progress?.nextRetryAt) ? { nextRetryAt: stringField3(progress?.nextRetryAt) } : {},
7740
+ ...typeof progress?.issue === "number" ? { issue: progress.issue } : {},
7537
7741
  ...step ? {
7538
- stage: stringField2(step.stage),
7539
- capability: stringField2(step.capability),
7742
+ stage: stringField3(step.stage),
7743
+ capability: stringField3(step.capability),
7540
7744
  ...step.args && typeof step.args === "object" ? { args: step.args } : {},
7541
- ...step.saveReport === true ? { saveReport: true } : {}
7745
+ ...step.saveReport === true ? { saveReport: true } : {},
7746
+ ...step.onPending && typeof step.onPending === "object" ? { onPending: step.onPending } : {},
7747
+ ...step.onFailure && typeof step.onFailure === "object" ? { onFailure: step.onFailure } : {}
7542
7748
  } : {}
7543
7749
  }
7544
7750
  };
@@ -7558,9 +7764,9 @@ function itemFromCapability(capability, createdAt, prior) {
7558
7764
  function routeFromItems(items) {
7559
7765
  return items.flatMap((item) => {
7560
7766
  const meta = recordField(item.meta);
7561
- const evidence = stringField2(meta.evidence) || item.id;
7562
- const stage = stringField2(meta.stage);
7563
- const capability = stringField2(meta.capability);
7767
+ const evidence = stringField3(meta.evidence) || item.id;
7768
+ const stage = stringField3(meta.stage);
7769
+ const capability = stringField3(meta.capability);
7564
7770
  if (!evidence || !stage || !capability) return [];
7565
7771
  return [
7566
7772
  {
@@ -7568,7 +7774,9 @@ function routeFromItems(items) {
7568
7774
  stage,
7569
7775
  capability,
7570
7776
  ...meta.args ? { args: meta.args } : {},
7571
- ...meta.saveReport === true ? { saveReport: true } : {}
7777
+ ...meta.saveReport === true ? { saveReport: true } : {},
7778
+ ...meta.onPending ? { onPending: meta.onPending } : {},
7779
+ ...meta.onFailure ? { onFailure: meta.onFailure } : {}
7572
7780
  }
7573
7781
  ];
7574
7782
  });
@@ -7591,7 +7799,7 @@ function parseJsonRecord(raw) {
7591
7799
  function recordField(value) {
7592
7800
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
7593
7801
  }
7594
- function stringField2(value) {
7802
+ function stringField3(value) {
7595
7803
  return typeof value === "string" ? value : "";
7596
7804
  }
7597
7805
  function stringArray(value) {
@@ -8431,7 +8639,9 @@ async function planGoalCapabilitySchedule(opts) {
8431
8639
  const backend = resolveBackend({ config: opts.config, cwd: opts.cwd, jobsDir });
8432
8640
  const statuses = {};
8433
8641
  const blockers = [];
8434
- for (const slug2 of opts.goal.capabilities) {
8642
+ const explicitCapabilityTarget = opts.goal.loopTarget?.type === "capability" ? opts.goal.loopTarget.id.trim() : "";
8643
+ const capabilitySlugs = explicitCapabilityTarget ? [explicitCapabilityTarget] : opts.goal.capabilities;
8644
+ for (const slug2 of capabilitySlugs) {
8435
8645
  const capability2 = resolveCapabilityFolder(slug2, jobsRoot);
8436
8646
  const status = await describeCapabilitySchedule(
8437
8647
  capability2,
@@ -8442,7 +8652,7 @@ async function planGoalCapabilitySchedule(opts) {
8442
8652
  statuses[slug2] = status;
8443
8653
  if (status.state === "blocked") blockers.push(`${slug2}: ${status.reason}`);
8444
8654
  }
8445
- const due = opts.goal.capabilities.map((slug2) => statuses[slug2]).filter((status) => status?.state === "due").sort(compareOldestLastFired)[0];
8655
+ const due = capabilitySlugs.map((slug2) => statuses[slug2]).filter((status) => status?.state === "due").sort(compareOldestLastFired)[0];
8446
8656
  if (!due) {
8447
8657
  const reason = blockers.length > 0 ? "no runnable capability; blocked capabilities need attention" : "no runnable capability";
8448
8658
  const kind = blockers.length > 0 ? "blocked" : "idle";
@@ -9241,16 +9451,16 @@ function normalizeCompanyIntent(path51, raw) {
9241
9451
  throw new Error(`${path51}: intent must be JSON object`);
9242
9452
  }
9243
9453
  const input = raw;
9244
- const id = stringField3(input.id);
9454
+ const id = stringField4(input.id);
9245
9455
  if (!id || !isCompanyIntentId(id)) throw new Error(`${path51}: invalid intent id`);
9246
- const createdAt = stringField3(input.createdAt) || nowIso();
9247
- const updatedAt = stringField3(input.updatedAt) || createdAt;
9248
- const description = stringField3(input.description);
9456
+ const createdAt = stringField4(input.createdAt) || nowIso();
9457
+ const updatedAt = stringField4(input.updatedAt) || createdAt;
9458
+ const description = stringField4(input.description);
9249
9459
  return {
9250
9460
  version: 1,
9251
9461
  id,
9252
9462
  status: oneOf2(input.status, ["active", "paused", "archived"], "active"),
9253
- for: stringField3(input.for),
9463
+ for: stringField4(input.for),
9254
9464
  ...description ? { description } : {},
9255
9465
  priority: numberField(input.priority, 100),
9256
9466
  posture: oneOf2(
@@ -9324,8 +9534,8 @@ function listCompanyPortfolio(config, cwd) {
9324
9534
  goals.push({
9325
9535
  id,
9326
9536
  state: state.state,
9327
- type: stringField3(state.extra.type) || void 0,
9328
- outcome: stringField3(destination?.outcome) || void 0,
9537
+ type: stringField4(state.extra.type) || void 0,
9538
+ outcome: stringField4(destination?.outcome) || void 0,
9329
9539
  capabilities: stringArray4(state.extra.capabilities),
9330
9540
  isLoop: state.extra.scheduleMode === "agentLoop" || state.extra.type === "agentLoop",
9331
9541
  updatedAt: state.updatedAt
@@ -9340,7 +9550,7 @@ function writeCompanyGoalState(config, cwd, id, state, message) {
9340
9550
  function assertIntentId(id) {
9341
9551
  if (!isCompanyIntentId(id)) throw new Error(`invalid intent/portfolio id: ${id}`);
9342
9552
  }
9343
- function stringField3(value) {
9553
+ function stringField4(value) {
9344
9554
  return typeof value === "string" ? value.trim() : "";
9345
9555
  }
9346
9556
  function numberField(value, fallback) {
@@ -9547,11 +9757,14 @@ function capabilityReportToEvidence(report) {
9547
9757
  if (report.target.type !== "goal") return null;
9548
9758
  const evidence = report.evidence ?? {};
9549
9759
  const values = Object.values(evidence);
9550
- const status = values.some((value) => value === false) ? "fail" : values.length > 0 || report.facts ? "changed" : "noop";
9760
+ const hasFalse = values.some((value) => value === false);
9761
+ const hasTrue = values.some((value) => value === true);
9762
+ const status = hasFalse ? "fail" : values.length > 0 || report.facts ? "changed" : "noop";
9551
9763
  return {
9552
9764
  version: 1,
9553
9765
  target: { type: "goal", id: report.target.id },
9554
9766
  status,
9767
+ resultClass: hasFalse ? "needsFix" : hasTrue ? "succeeded" : "pending",
9555
9768
  summary: "capability reported goal evidence",
9556
9769
  evidence,
9557
9770
  facts: report.facts ?? {},
@@ -9570,9 +9783,10 @@ function capabilityResultToEvidence(result, fallbackGoalId, explicitEvidence) {
9570
9783
  version: 1,
9571
9784
  target: { type: "goal", id: targetId },
9572
9785
  status: result.status,
9786
+ resultClass: result.resultClass ?? resultClassFromStatus(result.status),
9573
9787
  summary: result.summary,
9574
- evidence: result.evidence,
9575
- explicitEvidence: hasEvidenceValues ? void 0 : explicitEvidence,
9788
+ ...result.evidence ? { evidence: result.evidence } : {},
9789
+ ...hasEvidenceValues || !explicitEvidence ? {} : { explicitEvidence },
9576
9790
  facts: result.facts,
9577
9791
  artifacts: result.artifacts,
9578
9792
  missingEvidence: result.missingEvidence,
@@ -9604,6 +9818,7 @@ function mergeCapabilityEvidence(items) {
9604
9818
  function applyCapabilityEvidenceToGoalState(state, evidence) {
9605
9819
  const priorFacts = parseFacts3(state.extra.facts) ?? {};
9606
9820
  const nextFacts = { ...priorFacts };
9821
+ const changesProgressState = evidence.sources.includes("result") || Object.entries(evidence.facts).some(([key, value]) => !CONTROL_FACT_KEYS3.has(key) && priorFacts[key] !== value) || Object.entries(evidence.evidence ?? {}).some(([key, value]) => priorFacts[key] !== value);
9607
9822
  for (const [key, value] of Object.entries(evidence.facts)) {
9608
9823
  if (CONTROL_FACT_KEYS3.has(key)) continue;
9609
9824
  nextFacts[key] = value;
@@ -9614,24 +9829,42 @@ function applyCapabilityEvidenceToGoalState(state, evidence) {
9614
9829
  const pending = typeof nextFacts.pendingEvidence === "string" ? nextFacts.pendingEvidence : "";
9615
9830
  const hasEvidenceValues = Object.keys(evidence.evidence ?? {}).length > 0;
9616
9831
  const statusEvidence = evidence.explicitEvidence || (hasEvidenceValues ? "" : pending);
9832
+ const progressEvidence = statusEvidence || singleEvidenceKey(evidence.evidence);
9833
+ const resultClass = evidence.resultClass ?? resultClassFromStatus(evidence.status);
9617
9834
  const hasPendingEvidenceValue = pending ? Object.hasOwn(evidence.evidence ?? {}, pending) : false;
9618
- const terminalStatus = evidence.status === "pass" || evidence.status === "fail" || evidence.status === "blocked";
9835
+ const terminalResult = resultClass === "succeeded" || resultClass === "needsFix" || resultClass === "fatal";
9619
9836
  if (statusEvidence && !Object.hasOwn(evidence.evidence ?? {}, statusEvidence)) {
9620
- if (evidence.status === "pass") nextFacts[statusEvidence] = true;
9621
- if (evidence.status === "fail" || evidence.status === "blocked") nextFacts[statusEvidence] = false;
9837
+ if (resultClass === "succeeded") nextFacts[statusEvidence] = true;
9838
+ if (resultClass === "needsFix" || resultClass === "fatal") nextFacts[statusEvidence] = false;
9622
9839
  }
9623
- if (pending && (hasPendingEvidenceValue || statusEvidence === pending && terminalStatus)) {
9840
+ if (pending && (hasPendingEvidenceValue || statusEvidence === pending && terminalResult)) {
9624
9841
  delete nextFacts.pendingEvidence;
9625
9842
  }
9626
9843
  const blockers = parseStringArray3(state.extra.blockers) ?? [];
9627
- const evidenceBlockers = evidence.blockers.length > 0 || evidence.status !== "fail" && evidence.status !== "blocked" ? evidence.blockers : [evidence.summary];
9844
+ const evidenceBlockers = evidence.blockers.length > 0 || resultClass !== "needsFix" && resultClass !== "fatal" ? evidence.blockers : [evidence.summary];
9628
9845
  for (const blocker of evidenceBlockers) {
9629
9846
  if (!blockers.includes(blocker)) blockers.push(blocker);
9630
9847
  }
9848
+ const evidenceState = parseGoalEvidenceState(state.extra.evidenceState);
9849
+ const nextEvidenceState = progressEvidence && changesProgressState ? mergeGoalEvidenceProgress(evidenceState, progressEvidence, {
9850
+ resultClass,
9851
+ attempts: (evidenceState[progressEvidence]?.attempts ?? 0) + 1,
9852
+ reason: evidence.summary,
9853
+ nextAction: nextActionForResultClass(resultClass),
9854
+ nextRetryAt: nextRetryAtFor(state, progressEvidence, resultClass),
9855
+ updatedAt: nowIso()
9856
+ }) : evidenceState;
9631
9857
  const nextExtra = {
9632
9858
  ...state.extra,
9633
- facts: nextFacts
9859
+ facts: nextFacts,
9860
+ ...changesProgressState ? {
9861
+ reason: evidence.summary,
9862
+ nextAction: progressEvidence ? nextEvidenceState[progressEvidence]?.nextAction : void 0
9863
+ } : {}
9634
9864
  };
9865
+ if (changesProgressState || state.extra.evidenceState !== void 0) {
9866
+ nextExtra.evidenceState = nextEvidenceState;
9867
+ }
9635
9868
  if (blockers.length > 0 || Array.isArray(state.extra.blockers)) {
9636
9869
  nextExtra.blockers = blockers;
9637
9870
  }
@@ -9640,6 +9873,13 @@ function applyCapabilityEvidenceToGoalState(state, evidence) {
9640
9873
  extra: nextExtra
9641
9874
  };
9642
9875
  }
9876
+ function resultClassFromStatus(status) {
9877
+ if (status === "pass") return "succeeded";
9878
+ if (status === "fail") return "needsFix";
9879
+ if (status === "blocked") return "fatal";
9880
+ if (status === "noop") return "pending";
9881
+ return "pending";
9882
+ }
9643
9883
  function evidenceMatches(report, result, allReports) {
9644
9884
  if (report.target.id !== result.target.id) return false;
9645
9885
  if (result.explicitEvidence && Object.hasOwn(report.evidence ?? {}, result.explicitEvidence)) return true;
@@ -9652,6 +9892,7 @@ function evidenceMatches(report, result, allReports) {
9652
9892
  function mergeReportAndResult(report, result) {
9653
9893
  return {
9654
9894
  ...result,
9895
+ resultClass: result.resultClass ?? report.resultClass,
9655
9896
  evidence: mergeOptionalRecords(report.evidence, result.evidence),
9656
9897
  facts: { ...report.facts, ...result.facts },
9657
9898
  artifacts: uniqueArtifacts([...report.artifacts, ...result.artifacts]),
@@ -9660,6 +9901,31 @@ function mergeReportAndResult(report, result) {
9660
9901
  sources: uniqueSources([...report.sources, ...result.sources])
9661
9902
  };
9662
9903
  }
9904
+ function singleEvidenceKey(evidence) {
9905
+ const keys = Object.keys(evidence ?? {});
9906
+ return keys.length === 1 ? keys[0] : "";
9907
+ }
9908
+ function nextActionForResultClass(resultClass) {
9909
+ if (resultClass === "succeeded") return "continue";
9910
+ if (resultClass === "pending") return "wait";
9911
+ if (resultClass === "retryable") return "retry";
9912
+ if (resultClass === "needsFix") return "create issue";
9913
+ return "block";
9914
+ }
9915
+ function nextRetryAtFor(state, evidence, resultClass) {
9916
+ if (resultClass !== "retryable") return void 0;
9917
+ const delaySeconds = retryAfterSecondsFor(state.extra.route, evidence) ?? 300;
9918
+ return new Date(Date.now() + delaySeconds * 1e3).toISOString().replace(/\.\d{3}Z$/, "Z");
9919
+ }
9920
+ function retryAfterSecondsFor(route, evidence) {
9921
+ if (!Array.isArray(route)) return void 0;
9922
+ const step = route.find(
9923
+ (item) => !!item && typeof item === "object" && !Array.isArray(item) && item.evidence === evidence
9924
+ );
9925
+ const policy = step && recordField4(step.onFailure);
9926
+ const retryAfter = typeof policy?.retryAfterSeconds === "number" ? policy.retryAfterSeconds : void 0;
9927
+ return retryAfter !== void 0 && retryAfter >= 0 ? Math.floor(retryAfter) : void 0;
9928
+ }
9663
9929
  function mergeOptionalRecords(left, right) {
9664
9930
  const merged = { ...left ?? {}, ...right ?? {} };
9665
9931
  return Object.keys(merged).length > 0 ? merged : void 0;
@@ -9705,10 +9971,15 @@ function parseStringArray3(raw) {
9705
9971
  }
9706
9972
  return out;
9707
9973
  }
9974
+ function recordField4(value) {
9975
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
9976
+ }
9708
9977
  var CONTROL_FACT_KEYS3;
9709
9978
  var init_capabilityEvidence = __esm({
9710
9979
  "src/capabilityEvidence.ts"() {
9711
9980
  "use strict";
9981
+ init_evidenceState();
9982
+ init_state2();
9712
9983
  CONTROL_FACT_KEYS3 = /* @__PURE__ */ new Set(["blockers", "destination", "capabilities", "route", "stage", "state"]);
9713
9984
  }
9714
9985
  });
@@ -9745,6 +10016,7 @@ function capabilityEvidenceOutput(evidence) {
9745
10016
  kind: "capability-evidence",
9746
10017
  sources: evidence.sources,
9747
10018
  status: evidence.status,
10019
+ resultClass: evidence.resultClass,
9748
10020
  summary: evidence.summary,
9749
10021
  evidence: evidence.evidence ?? {},
9750
10022
  facts: evidence.facts,
@@ -9756,7 +10028,7 @@ function capabilityEvidenceOutput(evidence) {
9756
10028
  function goalReportBody(goalId, state, snapshot, latestEvent, evidenceItems) {
9757
10029
  const outputs = evidenceItems.map(capabilityEvidenceOutput);
9758
10030
  const latestOutput = outputs.at(-1);
9759
- const facts = recordField4(snapshot, "facts") ?? recordField4(state.extra, "facts") ?? {};
10031
+ const facts = recordField5(snapshot, "facts") ?? recordField5(state.extra, "facts") ?? {};
9760
10032
  const blockers = uniqueStrings2([
9761
10033
  ...stringArrayField(snapshot, "blockers"),
9762
10034
  ...stringArrayField(latestEvent, "blockers"),
@@ -9775,12 +10047,12 @@ function goalReportBody(goalId, state, snapshot, latestEvent, evidenceItems) {
9775
10047
  "",
9776
10048
  "## Status",
9777
10049
  `- State: ${state.state}`,
9778
- `- Stage: ${stringField4(snapshot, "stage") ?? stringField4(state.extra, "stage") ?? "unknown"}`,
10050
+ `- Stage: ${stringField5(snapshot, "stage") ?? stringField5(state.extra, "stage") ?? "unknown"}`,
9779
10051
  `- Next step: ${nextStepFromEvent(state, snapshot, latestOutput, latestEvent)}`,
9780
10052
  `- Updated: ${state.updatedAt ?? state.createdAt ?? state.startedAt ?? "unknown"}`,
9781
10053
  "",
9782
10054
  "## Decision",
9783
- `- Event: ${stringField4(latestEvent, "event") ?? "unknown"}`,
10055
+ `- Event: ${stringField5(latestEvent, "event") ?? "unknown"}`,
9784
10056
  `- Reason: ${decisionReason(state, latestEvent, latestOutput, missingEvidence, blockers)}`,
9785
10057
  `- Required evidence: ${listOrNone(stringArrayField(snapshot, "requiredEvidence"))}`,
9786
10058
  `- Satisfied evidence: ${listOrNone(stringArrayField(snapshot, "satisfiedEvidence"))}`,
@@ -9808,9 +10080,9 @@ function capabilityEvidenceMarkdown(outputs) {
9808
10080
  function decisionReason(state, latestEvent, latestOutput, missingEvidence, blockers) {
9809
10081
  if (state.state === "done") return "destination evidence satisfied";
9810
10082
  if (blockers.length > 0) return blockers[0] ?? "blocked";
9811
- const eventReason = stringField4(latestEvent, "reason") ?? stringField4(recordField4(latestEvent, "decision"), "reason");
10083
+ const eventReason = stringField5(latestEvent, "reason") ?? stringField5(recordField5(latestEvent, "decision"), "reason");
9812
10084
  if (eventReason) return eventReason;
9813
- const summary = stringField4(latestOutput, "summary");
10085
+ const summary = stringField5(latestOutput, "summary");
9814
10086
  if (summary) return summary;
9815
10087
  if (missingEvidence.length > 0) return `waiting for ${missingEvidence[0]}`;
9816
10088
  return "waiting for more evidence";
@@ -9818,33 +10090,33 @@ function decisionReason(state, latestEvent, latestOutput, missingEvidence, block
9818
10090
  function evidenceOutputMarkdown(index, output) {
9819
10091
  return [
9820
10092
  `### Output ${index}`,
9821
- `- Status: ${stringField4(output, "status") ?? "unknown"}`,
9822
- `- Summary: ${stringField4(output, "summary") ?? "no summary"}`,
10093
+ `- Status: ${stringField5(output, "status") ?? "unknown"}`,
10094
+ `- Summary: ${stringField5(output, "summary") ?? "no summary"}`,
9823
10095
  `- Sources: ${listOrNone(stringArrayField(output, "sources"))}`,
9824
- `- Evidence values: ${inlineJson(recordField4(output, "evidence") ?? {})}`,
10096
+ `- Evidence values: ${inlineJson(recordField5(output, "evidence") ?? {})}`,
9825
10097
  `- Missing evidence: ${listOrNone(stringArrayField(output, "missingEvidence"))}`,
9826
10098
  `- Blockers: ${listOrNone(stringArrayField(output, "blockers"))}`,
9827
10099
  ""
9828
10100
  ];
9829
10101
  }
9830
10102
  function dispatchContextMarkdown(latestEvent) {
9831
- const context = recordField4(latestEvent, "dispatchContext");
10103
+ const context = recordField5(latestEvent, "dispatchContext");
9832
10104
  if (!context) return ["- none"];
9833
- const githubActor = stringField4(context, "githubActor");
9834
- const githubActorRole = stringField4(context, "githubActorRole");
9835
- const target = dispatchTargetLabel(recordField4(context, "target"));
10105
+ const githubActor = stringField5(context, "githubActor");
10106
+ const githubActorRole = stringField5(context, "githubActorRole");
10107
+ const target = dispatchTargetLabel(recordField5(context, "target"));
9836
10108
  return [
9837
- `- Triggered by: ${stringField4(context, "triggeredBy") ?? "unknown"}`,
9838
- `- Mode: ${stringField4(context, "dispatchMode") ?? "unknown"}`,
10109
+ `- Triggered by: ${stringField5(context, "triggeredBy") ?? "unknown"}`,
10110
+ `- Mode: ${stringField5(context, "dispatchMode") ?? "unknown"}`,
9839
10111
  `- GitHub actor: ${githubActor ? `${githubActor}${githubActorRole ? ` (${githubActorRole})` : ""}` : "none"}`,
9840
- `- Decided by: ${stringField4(context, "decidedBy") ?? "unknown"}`,
9841
- `- Dispatched by: ${stringField4(context, "dispatchedBy") ?? "unknown"}`,
10112
+ `- Decided by: ${stringField5(context, "decidedBy") ?? "unknown"}`,
10113
+ `- Dispatched by: ${stringField5(context, "dispatchedBy") ?? "unknown"}`,
9842
10114
  `- Target: ${target ?? "none"}`
9843
10115
  ];
9844
10116
  }
9845
10117
  function dispatchTargetLabel(target) {
9846
- const type = stringField4(target, "type");
9847
- const id = stringField4(target, "id");
10118
+ const type = stringField5(target, "type");
10119
+ const id = stringField5(target, "id");
9848
10120
  if (type && id) return `${type} ${id}`;
9849
10121
  return id ?? type;
9850
10122
  }
@@ -9867,11 +10139,11 @@ function listOrNone(values) {
9867
10139
  function uniqueStrings2(values) {
9868
10140
  return [...new Set(values)].sort();
9869
10141
  }
9870
- function stringField4(record2, key) {
10142
+ function stringField5(record2, key) {
9871
10143
  const value = record2?.[key];
9872
10144
  return typeof value === "string" && value.trim() ? value : void 0;
9873
10145
  }
9874
- function recordField4(record2, key) {
10146
+ function recordField5(record2, key) {
9875
10147
  const value = record2?.[key];
9876
10148
  return value && typeof value === "object" && !Array.isArray(value) ? { ...value } : void 0;
9877
10149
  }
@@ -9904,7 +10176,7 @@ ${artifact.path ?? ""}`;
9904
10176
  }
9905
10177
  function nextStepFromEvent(state, goalAfter, capabilityOutput, latestEvent) {
9906
10178
  if (state.state === "done") return "done";
9907
- const decisionKind = stringField4(recordField4(latestEvent, "decision"), "kind") ?? stringField4(latestEvent, "status");
10179
+ const decisionKind = stringField5(recordField5(latestEvent, "decision"), "kind") ?? stringField5(latestEvent, "status");
9908
10180
  if (decisionKind === "done") return "done";
9909
10181
  if (decisionKind === "dispatch") return "dispatch";
9910
10182
  if (decisionKind === "blocked" || decisionKind === "reject-evidence") return "block";
@@ -9954,6 +10226,64 @@ var init_report = __esm({
9954
10226
  });
9955
10227
 
9956
10228
  // src/scripts/applyCapabilityReports.ts
10229
+ function ensureNeedsFixIssue(ctx, goalId, state, evidence, evidenceKey) {
10230
+ if (evidence.resultClass !== "needsFix" || !evidenceKey) return state;
10231
+ const evidenceState = parseGoalEvidenceState(state.extra.evidenceState);
10232
+ const progress = evidenceState[evidenceKey];
10233
+ if (progress?.issue) return state;
10234
+ const issue = findExistingNeedsFixIssue(goalId, evidenceKey, ctx.cwd) ?? createNeedsFixIssue(goalId, evidenceKey, evidence, ctx.cwd);
10235
+ const nextEvidenceState = mergeGoalEvidenceProgress(evidenceState, evidenceKey, {
10236
+ resultClass: "needsFix",
10237
+ attempts: progress?.attempts ?? 1,
10238
+ reason: evidence.summary,
10239
+ nextAction: `fix issue #${issue}`,
10240
+ issue,
10241
+ updatedAt: nowIso()
10242
+ });
10243
+ return {
10244
+ ...state,
10245
+ extra: {
10246
+ ...state.extra,
10247
+ evidenceState: nextEvidenceState,
10248
+ reason: evidence.summary,
10249
+ nextAction: `fix issue #${issue}`
10250
+ }
10251
+ };
10252
+ }
10253
+ function evidenceKeyFromOutput(evidence, beforeSnapshot) {
10254
+ if (evidence.explicitEvidence) return evidence.explicitEvidence;
10255
+ const keys = Object.keys(evidence.evidence ?? {});
10256
+ if (keys.length === 1) return keys[0];
10257
+ const pending = beforeSnapshot?.pendingEvidence;
10258
+ return typeof pending === "string" ? pending : "";
10259
+ }
10260
+ function needsFixIssueMarker(goalId, evidence) {
10261
+ return `<!-- kody-managed-goal-needs-fix: ${goalId}:${evidence} -->`;
10262
+ }
10263
+ function findExistingNeedsFixIssue(goalId, evidence, cwd) {
10264
+ const marker = needsFixIssueMarker(goalId, evidence);
10265
+ const raw = gh(["issue", "list", "--state", "all", "--limit", "100", "--json", "number,body"], { cwd });
10266
+ const issues = JSON.parse(raw);
10267
+ const match = issues.find((issue) => typeof issue.number === "number" && issue.body?.includes(marker));
10268
+ return match?.number ?? null;
10269
+ }
10270
+ function createNeedsFixIssue(goalId, evidence, result, cwd) {
10271
+ const title = `Goal ${goalId}: fix ${evidence}`.slice(0, 120);
10272
+ const body = [
10273
+ `Managed goal: \`${goalId}\``,
10274
+ `Evidence: \`${evidence}\``,
10275
+ "",
10276
+ `Reason: ${result.summary}`,
10277
+ "",
10278
+ result.blockers.length > 0 ? `Blockers: ${result.blockers.join(", ")}` : "Blockers: none",
10279
+ "",
10280
+ needsFixIssueMarker(goalId, evidence)
10281
+ ].join("\n");
10282
+ const out = gh(["issue", "create", "--title", title, "--body-file", "-"], { input: body, cwd });
10283
+ const match = out.match(/\/issues\/(\d+)(?:[/?#]|$)/);
10284
+ if (!match) throw new Error(`gh issue create returned unexpected output: ${out}`);
10285
+ return Number(match[1]);
10286
+ }
9957
10287
  function flushLogs(ctx) {
9958
10288
  try {
9959
10289
  flushGoalRunLogEvents(ctx.config, ctx.cwd, ctx.data);
@@ -10046,15 +10376,13 @@ function completeSatisfiedManagedGoal(state) {
10046
10376
  if (decision.kind !== "done") return state;
10047
10377
  return writeManagedGoalToState({ ...state, state: "done" }, managed);
10048
10378
  }
10049
- function shouldResumeManagedGoal(state) {
10379
+ function shouldResumeManagedGoal(goalId, state) {
10050
10380
  if (state.state !== "active") return false;
10051
10381
  const managed = managedGoalFromState(state);
10052
10382
  if (!managed) return false;
10053
10383
  if (managed.blockers.length > 0) return false;
10054
- const missing = managed.destination.evidence.find((evidence) => managed.facts[evidence] !== true);
10055
- if (!missing) return false;
10056
- const step = managed.route.find((candidate) => candidate.evidence === missing);
10057
- return Boolean(step && managed.capabilities.includes(step.capability));
10384
+ if (managed.facts.goalId === void 0) managed.facts.goalId = goalId;
10385
+ return planManagedGoalTick(managed).kind === "dispatch";
10058
10386
  }
10059
10387
  function snapshotFromState2(goalId, state) {
10060
10388
  const managed = managedGoalFromState(state);
@@ -10111,11 +10439,13 @@ var init_applyCapabilityReports = __esm({
10111
10439
  init_capabilityEvidence();
10112
10440
  init_capabilityReport();
10113
10441
  init_capabilityResult();
10442
+ init_evidenceState();
10114
10443
  init_manager();
10115
10444
  init_report();
10116
10445
  init_runLog();
10117
10446
  init_state2();
10118
10447
  init_stateStore();
10448
+ init_issue();
10119
10449
  applyCapabilityReports = async (ctx, _profile, agentResult) => {
10120
10450
  const reports = collectReports(ctx.data.capabilityReports, agentResult);
10121
10451
  const results = collectResults(ctx.data.capabilityResults ?? ctx.data.dutyResults, agentResult);
@@ -10153,6 +10483,7 @@ var init_applyCapabilityReports = __esm({
10153
10483
  for (const evidence of goalEvidence) {
10154
10484
  const beforeSnapshot = snapshotFromState2(goalId, next);
10155
10485
  next = applyCapabilityEvidenceToGoalState(next, evidence);
10486
+ next = ensureNeedsFixIssue(ctx, goalId, next, evidence, evidenceKeyFromOutput(evidence, beforeSnapshot));
10156
10487
  const afterSnapshot = snapshotFromState2(goalId, next);
10157
10488
  const change = goalRunLogChange(beforeSnapshot, afterSnapshot);
10158
10489
  const output = capabilityEvidenceOutput(evidence);
@@ -10203,7 +10534,7 @@ var init_applyCapabilityReports = __esm({
10203
10534
  putGoalState(ctx.config, goalId, nextForOutput, describeMessage(goalId, goalEvidence), ctx.cwd);
10204
10535
  }
10205
10536
  refreshReportOrFail(ctx, goalId, nextForOutput, goalEvidence);
10206
- if (changed && ctx.output.exitCode === 0 && !ctx.output.nextDispatch && shouldResumeManagedGoal(nextForOutput)) {
10537
+ if (changed && ctx.output.exitCode === 0 && !ctx.output.nextDispatch && shouldResumeManagedGoal(goalId, nextForOutput)) {
10207
10538
  ctx.output.nextDispatch = {
10208
10539
  action: "goal-manager",
10209
10540
  executable: "goal-manager",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.321",
3
+ "version": "0.4.322",
4
4
  "description": "kody — autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
5
5
  "license": "MIT",
6
6
  "type": "module",