@kody-ade/kody-engine 0.4.258 → 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.258",
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",
@@ -8252,6 +8252,135 @@ function flushLogs(ctx) {
8252
8252
  );
8253
8253
  }
8254
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
+ }
8255
8384
  function collectReports(raw, agentResult) {
8256
8385
  const out = [];
8257
8386
  if (Array.isArray(raw)) {
@@ -8365,7 +8494,7 @@ function describeMessage(goalId, evidenceItems) {
8365
8494
  const pieces = evidenceItems.map((evidence) => `${evidence.sources.join("+")}:${evidence.status}`);
8366
8495
  return `Apply agentResponsibility output to ${goalId}${pieces.length > 0 ? ` (${pieces.join("; ")})` : ""}`;
8367
8496
  }
8368
- var applyAgentResponsibilityReports;
8497
+ var REPORT_SLUG_RE, applyAgentResponsibilityReports;
8369
8498
  var init_applyAgentResponsibilityReports = __esm({
8370
8499
  "src/scripts/applyAgentResponsibilityReports.ts"() {
8371
8500
  "use strict";
@@ -8376,6 +8505,8 @@ var init_applyAgentResponsibilityReports = __esm({
8376
8505
  init_runLog();
8377
8506
  init_state2();
8378
8507
  init_stateStore();
8508
+ init_stateRepo();
8509
+ REPORT_SLUG_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/;
8379
8510
  applyAgentResponsibilityReports = async (ctx, _profile, agentResult) => {
8380
8511
  const reports = collectReports(ctx.data.agentResponsibilityReports, agentResult);
8381
8512
  const results = collectResults(ctx.data.dutyResults, agentResult);
@@ -8455,11 +8586,20 @@ var init_applyAgentResponsibilityReports = __esm({
8455
8586
  change: goalRunLogChange(beforeCompletionSnapshot, afterCompletionSnapshot)
8456
8587
  });
8457
8588
  }
8458
- 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) {
8459
8599
  flushLogs(ctx);
8460
8600
  continue;
8461
8601
  }
8462
- putGoalState(ctx.config, goalId, { ...next, updatedAt: nowIso() }, describeMessage(goalId, goalEvidence), ctx.cwd);
8602
+ putGoalState(ctx.config, goalId, nextForOutput, describeMessage(goalId, goalEvidence), ctx.cwd);
8463
8603
  flushLogs(ctx);
8464
8604
  }
8465
8605
  };
@@ -12039,7 +12179,7 @@ var init_markFlowSuccess = __esm({
12039
12179
  });
12040
12180
 
12041
12181
  // src/goal/operations.ts
12042
- function fail(err) {
12182
+ function fail2(err) {
12043
12183
  if (err instanceof Error) {
12044
12184
  const lines = err.message.split("\n").filter(Boolean);
12045
12185
  return { ok: false, error: lines[0] ?? err.message };
@@ -12051,7 +12191,7 @@ function commentOnIssue(issueNumber, body, cwd) {
12051
12191
  gh(["issue", "comment", String(issueNumber), "--body", body], { cwd });
12052
12192
  return { ok: true };
12053
12193
  } catch (err) {
12054
- return fail(err);
12194
+ return fail2(err);
12055
12195
  }
12056
12196
  }
12057
12197
  function mergePrSquash(prNumber, cwd) {
@@ -12059,7 +12199,7 @@ function mergePrSquash(prNumber, cwd) {
12059
12199
  gh(["pr", "merge", String(prNumber), "--squash", "--delete-branch"], { cwd });
12060
12200
  return { ok: true };
12061
12201
  } catch (err) {
12062
- return fail(err);
12202
+ return fail2(err);
12063
12203
  }
12064
12204
  }
12065
12205
  var init_operations = __esm({
@@ -12590,21 +12730,21 @@ var init_openQaIssue = __esm({
12590
12730
  ctx.data.action = failedAction3(reason);
12591
12731
  return;
12592
12732
  }
12593
- const reportBody2 = agentResult.finalText.trim();
12594
- if (!reportBody2) {
12733
+ const reportBody = agentResult.finalText.trim();
12734
+ if (!reportBody) {
12595
12735
  process.stderr.write("qa-engineer: agent produced no report body\n");
12596
12736
  ctx.output.exitCode = 1;
12597
12737
  ctx.output.reason = "empty report body";
12598
12738
  ctx.data.action = failedAction3("empty report body");
12599
12739
  return;
12600
12740
  }
12601
- const verdict = detectVerdict(reportBody2);
12741
+ const verdict = detectVerdict(reportBody);
12602
12742
  ctx.data.qaVerdict = verdict;
12603
- ctx.data.qaReport = reportBody2;
12743
+ ctx.data.qaReport = reportBody;
12604
12744
  const existingIssue = ctx.args.issue;
12605
12745
  if (typeof existingIssue === "number" && Number.isFinite(existingIssue) && existingIssue > 0) {
12606
12746
  try {
12607
- postIssueComment(existingIssue, reportBody2, ctx.cwd);
12747
+ postIssueComment(existingIssue, reportBody, ctx.cwd);
12608
12748
  } catch (err) {
12609
12749
  const msg = err instanceof Error ? err.message : String(err);
12610
12750
  ctx.output.exitCode = 4;
@@ -12626,7 +12766,7 @@ QA_REPORT_POSTED=https://github.com/${ctx.config.github.owner}/${ctx.config.gith
12626
12766
  const hasLabel = ensureLabel2(ctx.cwd);
12627
12767
  let created;
12628
12768
  try {
12629
- created = createQaIssue(title, reportBody2, hasLabel, ctx.cwd);
12769
+ created = createQaIssue(title, reportBody, hasLabel, ctx.cwd);
12630
12770
  } catch (err) {
12631
12771
  const msg = err instanceof Error ? err.message : String(err);
12632
12772
  ctx.output.exitCode = 4;
@@ -15958,86 +16098,6 @@ var init_writeJobStateFile = __esm({
15958
16098
  }
15959
16099
  });
15960
16100
 
15961
- // src/scripts/writeResponsibilityReport.ts
15962
- function writeReport(ctx, profile, agentResult) {
15963
- const slug2 = responsibilitySlug(ctx);
15964
- if (!slug2) {
15965
- fail2(ctx, "writeResponsibilityReport: missing responsibility slug");
15966
- return;
15967
- }
15968
- if (!REPORT_SLUG_RE.test(slug2)) {
15969
- fail2(ctx, `writeResponsibilityReport: invalid responsibility slug "${slug2}"`);
15970
- return;
15971
- }
15972
- const body = reportBody(ctx, agentResult, profile);
15973
- if (!body.trim()) {
15974
- fail2(ctx, `writeResponsibilityReport: ${slug2} produced no report output`);
15975
- return;
15976
- }
15977
- const filePath = `reports/${slug2}.md`;
15978
- const current = readStateText(ctx.config, ctx.cwd, filePath);
15979
- if (current?.content === body) {
15980
- ctx.data.responsibilityReport = { slug: slug2, path: current.path, changed: false };
15981
- return;
15982
- }
15983
- upsertStateText(
15984
- ctx.config,
15985
- ctx.cwd,
15986
- filePath,
15987
- body,
15988
- `chore(reports): refresh ${slug2}`
15989
- );
15990
- ctx.data.responsibilityReport = { slug: slug2, path: filePath, changed: true };
15991
- }
15992
- function responsibilitySlug(ctx) {
15993
- const candidates = [
15994
- ctx.data.jobAgentResponsibility,
15995
- ctx.data.agentResponsibilitySlug,
15996
- ctx.data.jobSlug
15997
- ];
15998
- for (const candidate of candidates) {
15999
- if (typeof candidate === "string" && candidate.trim()) return candidate.trim();
16000
- }
16001
- return null;
16002
- }
16003
- function reportBody(ctx, agentResult, profile) {
16004
- const prSummary = ctx.data.prSummary;
16005
- if (typeof prSummary === "string" && prSummary.trim()) return ensureTrailingNewline(prSummary.trim());
16006
- if (agentResult?.finalText.trim()) return ensureTrailingNewline(agentResult.finalText.trim());
16007
- const reason = ctx.output.reason || ctx.data.agentFailureReason || agentResult?.error;
16008
- if (typeof reason === "string" && reason.trim()) {
16009
- return `# ${profile.name}
16010
-
16011
- FAILED: ${reason.trim()}
16012
- `;
16013
- }
16014
- return "";
16015
- }
16016
- function fail2(ctx, reason) {
16017
- ctx.output.reason = ctx.output.reason ? `${ctx.output.reason}; ${reason}` : reason;
16018
- if (ctx.output.exitCode === 0) ctx.output.exitCode = 99;
16019
- }
16020
- function ensureTrailingNewline(value) {
16021
- return value.endsWith("\n") ? value : `${value}
16022
- `;
16023
- }
16024
- var REPORT_SLUG_RE, writeResponsibilityReport;
16025
- var init_writeResponsibilityReport = __esm({
16026
- "src/scripts/writeResponsibilityReport.ts"() {
16027
- "use strict";
16028
- init_stateRepo();
16029
- REPORT_SLUG_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/;
16030
- writeResponsibilityReport = async (ctx, profile, agentResult) => {
16031
- if (ctx.data.jobSaveReport !== true) return;
16032
- try {
16033
- writeReport(ctx, profile, agentResult);
16034
- } catch (err) {
16035
- fail2(ctx, `writeResponsibilityReport: ${err instanceof Error ? err.message : String(err)}`);
16036
- }
16037
- };
16038
- }
16039
- });
16040
-
16041
16101
  // src/scripts/index.ts
16042
16102
  var preflightScripts, postflightScripts, allScriptNames;
16043
16103
  var init_scripts = __esm({
@@ -16139,7 +16199,6 @@ var init_scripts = __esm({
16139
16199
  init_writeAgentRunSummary();
16140
16200
  init_writeIssueStateComment();
16141
16201
  init_writeJobStateFile();
16142
- init_writeResponsibilityReport();
16143
16202
  preflightScripts = {
16144
16203
  runFlow,
16145
16204
  fixFlow,
@@ -16218,7 +16277,6 @@ var init_scripts = __esm({
16218
16277
  postReviewResult,
16219
16278
  persistArtifacts,
16220
16279
  writeAgentRunSummary,
16221
- writeResponsibilityReport,
16222
16280
  saveTaskState,
16223
16281
  mirrorStateToPr,
16224
16282
  startFlow,
@@ -16778,7 +16836,6 @@ async function runAgentAction(profileName, input) {
16778
16836
  outcome: postOutcome
16779
16837
  });
16780
16838
  }
16781
- await writeResponsibilityReport(ctx, profile, agentResult);
16782
16839
  return finishAndEnd({
16783
16840
  exitCode: ctx.output.exitCode ?? 0,
16784
16841
  prUrl: ctx.output.prUrl,
@@ -17176,9 +17233,8 @@ var init_executor = __esm({
17176
17233
  init_registry();
17177
17234
  init_runtimePaths();
17178
17235
  init_scripts();
17179
- init_writeResponsibilityReport();
17180
- init_subagents();
17181
17236
  init_stateWorkspace();
17237
+ init_subagents();
17182
17238
  init_task_artifacts();
17183
17239
  init_tools();
17184
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.258",
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",