@kody-ade/kody-engine 0.4.257 → 0.4.259

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.
@@ -469,23 +469,23 @@ export interface Context {
469
469
  * `@kody <next>` comment, which is silently ignored when Kody comments as
470
470
  * a GitHub App (bot author), stalling the pipeline at classify.
471
471
  */
472
- nextDispatch?: {
473
- action?: string
474
- agentResponsibility?: string
475
- agentAction?: string
476
- cliArgs: Record<string, unknown>
477
- saveReport?: boolean
478
- }
472
+ nextDispatch?: {
473
+ action?: string
474
+ agentResponsibility?: string
475
+ agentAction?: string
476
+ cliArgs: Record<string, unknown>
477
+ saveReport?: boolean
478
+ }
479
479
  /** In-process hand-off to a full Job, preserving job identity in task state. */
480
480
  nextJob?: Job
481
481
  /** Where to return after nextJob succeeds. Used by task-jobs to keep draining pending work. */
482
- afterNextJob?: {
483
- action?: string
484
- agentResponsibility?: string
485
- agentAction?: string
486
- cliArgs: Record<string, unknown>
487
- saveReport?: boolean
488
- }
482
+ afterNextJob?: {
483
+ action?: string
484
+ agentResponsibility?: string
485
+ agentAction?: string
486
+ cliArgs: Record<string, unknown>
487
+ saveReport?: boolean
488
+ }
489
489
  }
490
490
  /**
491
491
  * If a preflight script sets this to true, the executor skips the agent
@@ -552,6 +552,6 @@ export interface Job {
552
552
  flavor: JobFlavor
553
553
  /** Manual force-run (bypass cadence) for a scheduled job. */
554
554
  force?: boolean
555
- /** Save this responsibility run output as reports/<responsibility>.md. */
555
+ /** Ask the owning goal/loop to refresh reports/<goal-or-loop>.md after it applies evidence. */
556
556
  saveReport?: boolean
557
557
  }
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.257",
18
+ version: "0.4.259",
19
19
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative agentAction profiles.",
20
20
  license: "MIT",
21
21
  type: "module",
@@ -8090,15 +8090,17 @@ function agentResponsibilityReportToEvidence(report) {
8090
8090
  };
8091
8091
  }
8092
8092
  function agentResponsibilityResultToEvidence(result, fallbackGoalId, explicitEvidence) {
8093
- const targetId = result.target?.type === "goal" ? result.target.id : fallbackGoalId;
8093
+ if (result.target && result.target.type !== "goal") return null;
8094
+ const targetId = result.target?.id ?? fallbackGoalId;
8094
8095
  if (!targetId) return null;
8096
+ const hasEvidenceValues = Object.keys(result.evidence ?? {}).length > 0;
8095
8097
  return {
8096
8098
  version: 1,
8097
8099
  target: { type: "goal", id: targetId },
8098
8100
  status: result.status,
8099
8101
  summary: result.summary,
8100
8102
  evidence: result.evidence,
8101
- explicitEvidence,
8103
+ explicitEvidence: hasEvidenceValues ? void 0 : explicitEvidence,
8102
8104
  facts: result.facts,
8103
8105
  artifacts: result.artifacts,
8104
8106
  missingEvidence: result.missingEvidence,
@@ -8138,7 +8140,8 @@ function applyAgentResponsibilityEvidenceToGoalState(state, evidence) {
8138
8140
  nextFacts[key] = value;
8139
8141
  }
8140
8142
  const pending = typeof nextFacts.pendingEvidence === "string" ? nextFacts.pendingEvidence : "";
8141
- const statusEvidence = evidence.explicitEvidence || pending;
8143
+ const hasEvidenceValues = Object.keys(evidence.evidence ?? {}).length > 0;
8144
+ const statusEvidence = evidence.explicitEvidence || (hasEvidenceValues ? "" : pending);
8142
8145
  const hasPendingEvidenceValue = pending ? Object.hasOwn(evidence.evidence ?? {}, pending) : false;
8143
8146
  const terminalStatus = evidence.status === "pass" || evidence.status === "fail" || evidence.status === "blocked";
8144
8147
  if (statusEvidence && !Object.hasOwn(evidence.evidence ?? {}, statusEvidence)) {
@@ -8249,6 +8252,135 @@ function flushLogs(ctx) {
8249
8252
  );
8250
8253
  }
8251
8254
  }
8255
+ function writeGoalDashboardReport(ctx, goalId, state, snapshot, evidenceItems) {
8256
+ if (ctx.data.jobSaveReport !== true) return;
8257
+ if (!REPORT_SLUG_RE.test(goalId)) {
8258
+ fail(ctx, `goal report: invalid goal id "${goalId}"`);
8259
+ return;
8260
+ }
8261
+ const filePath = `reports/${goalId}.md`;
8262
+ const body = goalReportBody(goalId, state, snapshot, evidenceItems);
8263
+ try {
8264
+ const current = readStateText(ctx.config, ctx.cwd, filePath);
8265
+ if (current?.content === body) {
8266
+ recordGoalReport(ctx.data, { slug: goalId, path: current.path, changed: false });
8267
+ return;
8268
+ }
8269
+ upsertStateText(ctx.config, ctx.cwd, filePath, body, `chore(reports): refresh ${goalId}`);
8270
+ recordGoalReport(ctx.data, { slug: goalId, path: filePath, changed: true });
8271
+ } catch (err) {
8272
+ fail(ctx, `goal report: ${err instanceof Error ? err.message : String(err)}`);
8273
+ }
8274
+ }
8275
+ function recordGoalReport(data, report) {
8276
+ const prior = Array.isArray(data.goalReports) ? data.goalReports : [];
8277
+ data.goalReports = [...prior, report];
8278
+ }
8279
+ function goalReportBody(goalId, state, snapshot, evidenceItems) {
8280
+ const outputs = evidenceItems.map(responsibilityEvidenceOutput);
8281
+ const latestOutput = outputs.at(-1);
8282
+ const nextStep = state.state === "done" ? "done" : latestOutput ? nextStepFromEvidence(snapshot, latestOutput) : "wait";
8283
+ const facts = recordField2(snapshot, "facts") ?? recordField2(state.extra, "facts") ?? {};
8284
+ const blockers = uniqueStrings2([
8285
+ ...stringArrayField(snapshot, "blockers"),
8286
+ ...evidenceItems.flatMap((item) => item.blockers)
8287
+ ]);
8288
+ const missingEvidence = uniqueStrings2([
8289
+ ...stringArrayField(snapshot, "missingEvidence"),
8290
+ ...evidenceItems.flatMap((item) => item.missingEvidence)
8291
+ ]);
8292
+ const artifacts = uniqueArtifacts2(evidenceItems.flatMap((item) => item.artifacts));
8293
+ return [
8294
+ `# ${goalId}`,
8295
+ "",
8296
+ "## Status",
8297
+ `- State: ${state.state}`,
8298
+ `- Stage: ${stringField3(snapshot, "stage") ?? stringField3(state.extra, "stage") ?? "unknown"}`,
8299
+ `- Next step: ${nextStep}`,
8300
+ `- Updated: ${state.updatedAt ?? state.createdAt ?? state.startedAt ?? "unknown"}`,
8301
+ "",
8302
+ "## Decision",
8303
+ `- Reason: ${decisionReason(state, latestOutput, missingEvidence, blockers)}`,
8304
+ `- Required evidence: ${listOrNone(stringArrayField(snapshot, "requiredEvidence"))}`,
8305
+ `- Satisfied evidence: ${listOrNone(stringArrayField(snapshot, "satisfiedEvidence"))}`,
8306
+ `- Missing evidence: ${listOrNone(missingEvidence)}`,
8307
+ `- Blockers: ${listOrNone(blockers)}`,
8308
+ "",
8309
+ "## Responsibility Evidence",
8310
+ ...outputs.flatMap((output, index) => evidenceOutputMarkdown(index + 1, output)),
8311
+ "",
8312
+ "## Facts",
8313
+ fencedJson(facts),
8314
+ "",
8315
+ "## Artifacts",
8316
+ ...artifactMarkdown(artifacts),
8317
+ ""
8318
+ ].join("\n");
8319
+ }
8320
+ function decisionReason(state, latestOutput, missingEvidence, blockers) {
8321
+ if (state.state === "done") return "destination evidence satisfied";
8322
+ if (blockers.length > 0) return blockers[0] ?? "blocked";
8323
+ const summary = stringField3(latestOutput, "summary");
8324
+ if (summary) return summary;
8325
+ if (missingEvidence.length > 0) return `waiting for ${missingEvidence[0]}`;
8326
+ return "waiting for more evidence";
8327
+ }
8328
+ function evidenceOutputMarkdown(index, output) {
8329
+ return [
8330
+ `### Output ${index}`,
8331
+ `- Status: ${stringField3(output, "status") ?? "unknown"}`,
8332
+ `- Summary: ${stringField3(output, "summary") ?? "no summary"}`,
8333
+ `- Sources: ${listOrNone(stringArrayField(output, "sources"))}`,
8334
+ `- Evidence values: ${inlineJson(recordField2(output, "evidence") ?? {})}`,
8335
+ `- Missing evidence: ${listOrNone(stringArrayField(output, "missingEvidence"))}`,
8336
+ `- Blockers: ${listOrNone(stringArrayField(output, "blockers"))}`,
8337
+ ""
8338
+ ];
8339
+ }
8340
+ function artifactMarkdown(artifacts) {
8341
+ if (artifacts.length === 0) return ["- none"];
8342
+ return artifacts.map((artifact) => {
8343
+ if (artifact.url) return `- [${artifact.label}](${artifact.url})`;
8344
+ return `- ${artifact.label}: ${artifact.path}`;
8345
+ });
8346
+ }
8347
+ function fencedJson(value) {
8348
+ return ["```json", JSON.stringify(value, null, 2), "```"].join("\n");
8349
+ }
8350
+ function inlineJson(value) {
8351
+ return JSON.stringify(value);
8352
+ }
8353
+ function listOrNone(values) {
8354
+ return values.length > 0 ? values.join(", ") : "none";
8355
+ }
8356
+ function uniqueStrings2(values) {
8357
+ return [...new Set(values)].sort();
8358
+ }
8359
+ function stringField3(record2, key) {
8360
+ const value = record2?.[key];
8361
+ return typeof value === "string" && value.trim() ? value : void 0;
8362
+ }
8363
+ function recordField2(record2, key) {
8364
+ const value = record2?.[key];
8365
+ return value && typeof value === "object" && !Array.isArray(value) ? { ...value } : void 0;
8366
+ }
8367
+ function uniqueArtifacts2(artifacts) {
8368
+ const seen = /* @__PURE__ */ new Set();
8369
+ const out = [];
8370
+ for (const artifact of artifacts) {
8371
+ const key = `${artifact.label}
8372
+ ${artifact.url ?? ""}
8373
+ ${artifact.path ?? ""}`;
8374
+ if (seen.has(key)) continue;
8375
+ seen.add(key);
8376
+ out.push(artifact);
8377
+ }
8378
+ return out;
8379
+ }
8380
+ function fail(ctx, reason) {
8381
+ ctx.output.reason = ctx.output.reason ? `${ctx.output.reason}; ${reason}` : reason;
8382
+ if (ctx.output.exitCode === 0) ctx.output.exitCode = 99;
8383
+ }
8252
8384
  function collectReports(raw, agentResult) {
8253
8385
  const out = [];
8254
8386
  if (Array.isArray(raw)) {
@@ -8362,7 +8494,7 @@ function describeMessage(goalId, evidenceItems) {
8362
8494
  const pieces = evidenceItems.map((evidence) => `${evidence.sources.join("+")}:${evidence.status}`);
8363
8495
  return `Apply agentResponsibility output to ${goalId}${pieces.length > 0 ? ` (${pieces.join("; ")})` : ""}`;
8364
8496
  }
8365
- var applyAgentResponsibilityReports;
8497
+ var REPORT_SLUG_RE, applyAgentResponsibilityReports;
8366
8498
  var init_applyAgentResponsibilityReports = __esm({
8367
8499
  "src/scripts/applyAgentResponsibilityReports.ts"() {
8368
8500
  "use strict";
@@ -8373,6 +8505,8 @@ var init_applyAgentResponsibilityReports = __esm({
8373
8505
  init_runLog();
8374
8506
  init_state2();
8375
8507
  init_stateStore();
8508
+ init_stateRepo();
8509
+ REPORT_SLUG_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/;
8376
8510
  applyAgentResponsibilityReports = async (ctx, _profile, agentResult) => {
8377
8511
  const reports = collectReports(ctx.data.agentResponsibilityReports, agentResult);
8378
8512
  const results = collectResults(ctx.data.dutyResults, agentResult);
@@ -8452,11 +8586,20 @@ var init_applyAgentResponsibilityReports = __esm({
8452
8586
  change: goalRunLogChange(beforeCompletionSnapshot, afterCompletionSnapshot)
8453
8587
  });
8454
8588
  }
8455
- if (serializeGoalState(next) === serializeGoalState(prior)) {
8589
+ const changed = serializeGoalState(next) !== serializeGoalState(prior);
8590
+ const nextForOutput = changed ? { ...next, updatedAt: nowIso() } : next;
8591
+ writeGoalDashboardReport(
8592
+ ctx,
8593
+ goalId,
8594
+ nextForOutput,
8595
+ afterCompletionSnapshot ?? beforeCompletionSnapshot,
8596
+ goalEvidence
8597
+ );
8598
+ if (!changed) {
8456
8599
  flushLogs(ctx);
8457
8600
  continue;
8458
8601
  }
8459
- putGoalState(ctx.config, goalId, { ...next, updatedAt: nowIso() }, describeMessage(goalId, goalEvidence), ctx.cwd);
8602
+ putGoalState(ctx.config, goalId, nextForOutput, describeMessage(goalId, goalEvidence), ctx.cwd);
8460
8603
  flushLogs(ctx);
8461
8604
  }
8462
8605
  };
@@ -12036,7 +12179,7 @@ var init_markFlowSuccess = __esm({
12036
12179
  });
12037
12180
 
12038
12181
  // src/goal/operations.ts
12039
- function fail(err) {
12182
+ function fail2(err) {
12040
12183
  if (err instanceof Error) {
12041
12184
  const lines = err.message.split("\n").filter(Boolean);
12042
12185
  return { ok: false, error: lines[0] ?? err.message };
@@ -12048,7 +12191,7 @@ function commentOnIssue(issueNumber, body, cwd) {
12048
12191
  gh(["issue", "comment", String(issueNumber), "--body", body], { cwd });
12049
12192
  return { ok: true };
12050
12193
  } catch (err) {
12051
- return fail(err);
12194
+ return fail2(err);
12052
12195
  }
12053
12196
  }
12054
12197
  function mergePrSquash(prNumber, cwd) {
@@ -12056,7 +12199,7 @@ function mergePrSquash(prNumber, cwd) {
12056
12199
  gh(["pr", "merge", String(prNumber), "--squash", "--delete-branch"], { cwd });
12057
12200
  return { ok: true };
12058
12201
  } catch (err) {
12059
- return fail(err);
12202
+ return fail2(err);
12060
12203
  }
12061
12204
  }
12062
12205
  var init_operations = __esm({
@@ -12587,21 +12730,21 @@ var init_openQaIssue = __esm({
12587
12730
  ctx.data.action = failedAction3(reason);
12588
12731
  return;
12589
12732
  }
12590
- const reportBody2 = agentResult.finalText.trim();
12591
- if (!reportBody2) {
12733
+ const reportBody = agentResult.finalText.trim();
12734
+ if (!reportBody) {
12592
12735
  process.stderr.write("qa-engineer: agent produced no report body\n");
12593
12736
  ctx.output.exitCode = 1;
12594
12737
  ctx.output.reason = "empty report body";
12595
12738
  ctx.data.action = failedAction3("empty report body");
12596
12739
  return;
12597
12740
  }
12598
- const verdict = detectVerdict(reportBody2);
12741
+ const verdict = detectVerdict(reportBody);
12599
12742
  ctx.data.qaVerdict = verdict;
12600
- ctx.data.qaReport = reportBody2;
12743
+ ctx.data.qaReport = reportBody;
12601
12744
  const existingIssue = ctx.args.issue;
12602
12745
  if (typeof existingIssue === "number" && Number.isFinite(existingIssue) && existingIssue > 0) {
12603
12746
  try {
12604
- postIssueComment(existingIssue, reportBody2, ctx.cwd);
12747
+ postIssueComment(existingIssue, reportBody, ctx.cwd);
12605
12748
  } catch (err) {
12606
12749
  const msg = err instanceof Error ? err.message : String(err);
12607
12750
  ctx.output.exitCode = 4;
@@ -12623,7 +12766,7 @@ QA_REPORT_POSTED=https://github.com/${ctx.config.github.owner}/${ctx.config.gith
12623
12766
  const hasLabel = ensureLabel2(ctx.cwd);
12624
12767
  let created;
12625
12768
  try {
12626
- created = createQaIssue(title, reportBody2, hasLabel, ctx.cwd);
12769
+ created = createQaIssue(title, reportBody, hasLabel, ctx.cwd);
12627
12770
  } catch (err) {
12628
12771
  const msg = err instanceof Error ? err.message : String(err);
12629
12772
  ctx.output.exitCode = 4;
@@ -15955,86 +16098,6 @@ var init_writeJobStateFile = __esm({
15955
16098
  }
15956
16099
  });
15957
16100
 
15958
- // src/scripts/writeResponsibilityReport.ts
15959
- function writeReport(ctx, profile, agentResult) {
15960
- const slug2 = responsibilitySlug(ctx);
15961
- if (!slug2) {
15962
- fail2(ctx, "writeResponsibilityReport: missing responsibility slug");
15963
- return;
15964
- }
15965
- if (!REPORT_SLUG_RE.test(slug2)) {
15966
- fail2(ctx, `writeResponsibilityReport: invalid responsibility slug "${slug2}"`);
15967
- return;
15968
- }
15969
- const body = reportBody(ctx, agentResult, profile);
15970
- if (!body.trim()) {
15971
- fail2(ctx, `writeResponsibilityReport: ${slug2} produced no report output`);
15972
- return;
15973
- }
15974
- const filePath = `reports/${slug2}.md`;
15975
- const current = readStateText(ctx.config, ctx.cwd, filePath);
15976
- if (current?.content === body) {
15977
- ctx.data.responsibilityReport = { slug: slug2, path: current.path, changed: false };
15978
- return;
15979
- }
15980
- upsertStateText(
15981
- ctx.config,
15982
- ctx.cwd,
15983
- filePath,
15984
- body,
15985
- `chore(reports): refresh ${slug2}`
15986
- );
15987
- ctx.data.responsibilityReport = { slug: slug2, path: filePath, changed: true };
15988
- }
15989
- function responsibilitySlug(ctx) {
15990
- const candidates = [
15991
- ctx.data.jobAgentResponsibility,
15992
- ctx.data.agentResponsibilitySlug,
15993
- ctx.data.jobSlug
15994
- ];
15995
- for (const candidate of candidates) {
15996
- if (typeof candidate === "string" && candidate.trim()) return candidate.trim();
15997
- }
15998
- return null;
15999
- }
16000
- function reportBody(ctx, agentResult, profile) {
16001
- const prSummary = ctx.data.prSummary;
16002
- if (typeof prSummary === "string" && prSummary.trim()) return ensureTrailingNewline(prSummary.trim());
16003
- if (agentResult?.finalText.trim()) return ensureTrailingNewline(agentResult.finalText.trim());
16004
- const reason = ctx.output.reason || ctx.data.agentFailureReason || agentResult?.error;
16005
- if (typeof reason === "string" && reason.trim()) {
16006
- return `# ${profile.name}
16007
-
16008
- FAILED: ${reason.trim()}
16009
- `;
16010
- }
16011
- return "";
16012
- }
16013
- function fail2(ctx, reason) {
16014
- ctx.output.reason = ctx.output.reason ? `${ctx.output.reason}; ${reason}` : reason;
16015
- if (ctx.output.exitCode === 0) ctx.output.exitCode = 99;
16016
- }
16017
- function ensureTrailingNewline(value) {
16018
- return value.endsWith("\n") ? value : `${value}
16019
- `;
16020
- }
16021
- var REPORT_SLUG_RE, writeResponsibilityReport;
16022
- var init_writeResponsibilityReport = __esm({
16023
- "src/scripts/writeResponsibilityReport.ts"() {
16024
- "use strict";
16025
- init_stateRepo();
16026
- REPORT_SLUG_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/;
16027
- writeResponsibilityReport = async (ctx, profile, agentResult) => {
16028
- if (ctx.data.jobSaveReport !== true) return;
16029
- try {
16030
- writeReport(ctx, profile, agentResult);
16031
- } catch (err) {
16032
- fail2(ctx, `writeResponsibilityReport: ${err instanceof Error ? err.message : String(err)}`);
16033
- }
16034
- };
16035
- }
16036
- });
16037
-
16038
16101
  // src/scripts/index.ts
16039
16102
  var preflightScripts, postflightScripts, allScriptNames;
16040
16103
  var init_scripts = __esm({
@@ -16136,7 +16199,6 @@ var init_scripts = __esm({
16136
16199
  init_writeAgentRunSummary();
16137
16200
  init_writeIssueStateComment();
16138
16201
  init_writeJobStateFile();
16139
- init_writeResponsibilityReport();
16140
16202
  preflightScripts = {
16141
16203
  runFlow,
16142
16204
  fixFlow,
@@ -16215,7 +16277,6 @@ var init_scripts = __esm({
16215
16277
  postReviewResult,
16216
16278
  persistArtifacts,
16217
16279
  writeAgentRunSummary,
16218
- writeResponsibilityReport,
16219
16280
  saveTaskState,
16220
16281
  mirrorStateToPr,
16221
16282
  startFlow,
@@ -16775,7 +16836,6 @@ async function runAgentAction(profileName, input) {
16775
16836
  outcome: postOutcome
16776
16837
  });
16777
16838
  }
16778
- await writeResponsibilityReport(ctx, profile, agentResult);
16779
16839
  return finishAndEnd({
16780
16840
  exitCode: ctx.output.exitCode ?? 0,
16781
16841
  prUrl: ctx.output.prUrl,
@@ -17173,9 +17233,8 @@ var init_executor = __esm({
17173
17233
  init_registry();
17174
17234
  init_runtimePaths();
17175
17235
  init_scripts();
17176
- init_writeResponsibilityReport();
17177
- init_subagents();
17178
17236
  init_stateWorkspace();
17237
+ init_subagents();
17179
17238
  init_task_artifacts();
17180
17239
  init_tools();
17181
17240
  MUTATING_POSTFLIGHTS = /* @__PURE__ */ new Set([
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.257",
3
+ "version": "0.4.259",
4
4
  "description": "kody — autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative agentAction profiles.",
5
5
  "license": "MIT",
6
6
  "type": "module",