@kontextmind/kxm 0.7.92 → 0.7.93

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 (91) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.kxm/workflows/default.yaml +1 -1
  3. package/CHANGELOG.md +204 -0
  4. package/README.md +3 -0
  5. package/docs/README.md +3 -0
  6. package/docs/agent-skills.md +123 -60
  7. package/docs/architecture.md +5 -2
  8. package/docs/cli-reference.md +3527 -0
  9. package/docs/config-reference.md +1943 -0
  10. package/docs/configuration.md +29 -3
  11. package/docs/continuous-improvement.md +122 -10
  12. package/docs/contracts/routing.md +95 -11
  13. package/docs/harness-routing.md +616 -0
  14. package/docs/kxm-handbook.md +106 -19
  15. package/docs/templates/README.md +1 -1
  16. package/docs/test-matrix.md +12 -6
  17. package/docs/troubleshooting.md +2 -2
  18. package/examples/project/.kxm/workflows/fix.yaml +1 -1
  19. package/examples/project/.kxm/workflows/improve.yaml +1 -1
  20. package/package.json +1 -1
  21. package/plugins/kxm/.claude-plugin/plugin.json +9 -10
  22. package/plugins/kxm/README.md +238 -56
  23. package/plugins/kxm/dist/claude-hook.js +10083 -0
  24. package/plugins/kxm/dist/cli.js +3068 -2446
  25. package/plugins/kxm/dist/client.js +64 -0
  26. package/plugins/kxm/dist/core.js +102 -9
  27. package/plugins/kxm/dist/extension.js +210 -68
  28. package/plugins/kxm/dist/mcp-server.js +217 -40
  29. package/plugins/kxm/dist/runtime-supervisor.js +1628 -157
  30. package/plugins/kxm/dist/runtime.js +1874 -298
  31. package/plugins/kxm/dist/server.js +416 -82
  32. package/plugins/kxm/package.json +1 -1
  33. package/plugins/kxm/skills/hints.json +1 -1
  34. package/plugins/kxm/skills/kxm/SKILL.md +48 -24
  35. package/plugins/kxm/skills/kxm/references/protocol.md +3 -3
  36. package/plugins/kxm/skills/kxm-context-memory/SKILL.md +61 -21
  37. package/plugins/kxm/skills/kxm-definitions/SKILL.md +9 -0
  38. package/plugins/kxm/skills/kxm-harness-auth/SKILL.md +82 -16
  39. package/plugins/kxm/skills/kxm-harvest/SKILL.md +1 -1
  40. package/plugins/kxm/skills/kxm-hub-ops/SKILL.md +55 -27
  41. package/plugins/kxm/skills/kxm-insights/SKILL.md +1 -1
  42. package/plugins/kxm/skills/kxm-mind/SKILL.md +2 -2
  43. package/plugins/kxm/skills/{kxm-setup → kxm-mind-setup}/SKILL.md +4 -4
  44. package/plugins/kxm/skills/kxm-peer/SKILL.md +68 -93
  45. package/plugins/kxm/skills/kxm-project-setup/SKILL.md +156 -23
  46. package/plugins/kxm/skills/kxm-projects/SKILL.md +1 -1
  47. package/plugins/kxm/skills/kxm-protocol/SKILL.md +1 -1
  48. package/plugins/kxm/skills/kxm-query/SKILL.md +1 -1
  49. package/plugins/kxm/skills/kxm-routing-improve/SKILL.md +74 -15
  50. package/plugins/kxm/skills/kxm-runs/SKILL.md +46 -17
  51. package/plugins/kxm/skills/kxm-session/SKILL.md +64 -36
  52. package/plugins/kxm/skills/kxm-skill-lifecycle/SKILL.md +44 -15
  53. package/plugins/kxm/skills/kxm-tasks/SKILL.md +16 -4
  54. package/plugins/kxm/skills/kxm-triage/SKILL.md +1 -1
  55. package/plugins/kxm/skills/kxm-work/SKILL.md +1 -1
  56. package/plugins/kxm/skills/kxm-workflow/SKILL.md +60 -19
  57. package/plugins/kxm/src/arbiter.ts +67 -22
  58. package/plugins/kxm/src/autocomplete.ts +1 -1
  59. package/plugins/kxm/src/claude-hook.ts +192 -0
  60. package/plugins/kxm/src/cli/project.ts +11 -5
  61. package/plugins/kxm/src/cli/system.ts +85 -13
  62. package/plugins/kxm/src/cli/types.ts +4 -1
  63. package/plugins/kxm/src/cli/workflows.ts +18 -16
  64. package/plugins/kxm/src/cli.ts +23 -13
  65. package/plugins/kxm/src/client.ts +15 -4
  66. package/plugins/kxm/src/commands.ts +19 -9
  67. package/plugins/kxm/src/config.ts +42 -7
  68. package/plugins/kxm/src/context-packet.ts +14 -2
  69. package/plugins/kxm/src/context.ts +16 -5
  70. package/plugins/kxm/src/dispatch-context.ts +286 -0
  71. package/plugins/kxm/src/engine-plan.ts +40 -0
  72. package/plugins/kxm/src/engine.ts +138 -6
  73. package/plugins/kxm/src/hub-env.ts +17 -1
  74. package/plugins/kxm/src/hub.ts +92 -29
  75. package/plugins/kxm/src/improve-sources.ts +228 -0
  76. package/plugins/kxm/src/improve.ts +325 -140
  77. package/plugins/kxm/src/local-snapshot.ts +101 -42
  78. package/plugins/kxm/src/mcp-server.ts +129 -30
  79. package/plugins/kxm/src/project-config.ts +25 -0
  80. package/plugins/kxm/src/protocol.ts +11 -0
  81. package/plugins/kxm/src/relevance.ts +138 -0
  82. package/plugins/kxm/src/retrospective.ts +16 -10
  83. package/plugins/kxm/src/runtime-service.ts +8 -1
  84. package/plugins/kxm/src/runtime-supervisor.ts +16 -2
  85. package/plugins/kxm/src/session-token-hint.ts +17 -0
  86. package/plugins/kxm/src/suggest.ts +7 -7
  87. package/plugins/kxm/src/workflow-manager.ts +80 -78
  88. package/plugins/kxm/src/workflow.ts +202 -12
  89. package/scripts/build-runtime.mjs +7 -1
  90. package/scripts/check-generated.mjs +1 -0
  91. package/scripts/emit-codex-artifacts.mjs +1 -1
@@ -14815,6 +14815,15 @@ function toAgentRecord(agent, staleAfterMs, now) {
14815
14815
  return { ...agent, ...agentPresenceView(agent, staleAfterMs, now) };
14816
14816
  }
14817
14817
  var MAX_SYNC_BATCH_EVENTS = 100;
14818
+ var IMPROVEMENT_AREAS = [
14819
+ "harness",
14820
+ "gates",
14821
+ "implementation",
14822
+ "workflow",
14823
+ "documentation",
14824
+ "security",
14825
+ "other"
14826
+ ];
14818
14827
  var ProtocolError = class extends Error {
14819
14828
  statusCode;
14820
14829
  code;
@@ -15626,6 +15635,151 @@ import { createHash as createHash3, randomUUID as randomUUID2, timingSafeEqual }
15626
15635
 
15627
15636
  // plugins/kxm/src/workflow.ts
15628
15637
  import { createHash as createHash2 } from "node:crypto";
15638
+
15639
+ // plugins/kxm/src/relevance.ts
15640
+ var RELEVANCE_STOPWORDS = Object.freeze(/* @__PURE__ */ new Set([
15641
+ "a",
15642
+ "an",
15643
+ "and",
15644
+ "are",
15645
+ "as",
15646
+ "at",
15647
+ "be",
15648
+ "been",
15649
+ "but",
15650
+ "by",
15651
+ "can",
15652
+ "could",
15653
+ "did",
15654
+ "do",
15655
+ "does",
15656
+ "for",
15657
+ "from",
15658
+ "had",
15659
+ "has",
15660
+ "have",
15661
+ "how",
15662
+ "if",
15663
+ "in",
15664
+ "into",
15665
+ "is",
15666
+ "it",
15667
+ "its",
15668
+ "of",
15669
+ "on",
15670
+ "or",
15671
+ "our",
15672
+ "should",
15673
+ "so",
15674
+ "than",
15675
+ "that",
15676
+ "the",
15677
+ "their",
15678
+ "them",
15679
+ "then",
15680
+ "there",
15681
+ "these",
15682
+ "they",
15683
+ "this",
15684
+ "those",
15685
+ "to",
15686
+ "was",
15687
+ "we",
15688
+ "were",
15689
+ "what",
15690
+ "when",
15691
+ "where",
15692
+ "which",
15693
+ "while",
15694
+ "who",
15695
+ "why",
15696
+ "will",
15697
+ "with",
15698
+ "would",
15699
+ "you",
15700
+ "your"
15701
+ ]));
15702
+ var RELEVANCE_K1 = 1.2;
15703
+ var RELEVANCE_B = 0.75;
15704
+ var MIN_TOKEN_CHARS = 2;
15705
+ var MAX_TOKEN_CHARS = 64;
15706
+ function foldPlural(token) {
15707
+ if (new RegExp("^\\p{N}+$", "u").test(token)) return token;
15708
+ if (token.length > 4 && token.endsWith("ies")) return `${token.slice(0, -3)}y`;
15709
+ if (token.length > 4 && token.endsWith("sses")) return token.slice(0, -2);
15710
+ if (token.length > 3 && token.endsWith("s") && !token.endsWith("ss") && !token.endsWith("us") && !token.endsWith("is")) {
15711
+ return token.slice(0, -1);
15712
+ }
15713
+ return token;
15714
+ }
15715
+ function relevanceTokens(text2) {
15716
+ const tokens = [];
15717
+ for (const raw of text2.normalize("NFKC").toLowerCase().split(/[^\p{L}\p{N}]+/u)) {
15718
+ if (raw.length < MIN_TOKEN_CHARS || raw.length > MAX_TOKEN_CHARS) continue;
15719
+ if (RELEVANCE_STOPWORDS.has(raw)) continue;
15720
+ tokens.push(foldPlural(raw));
15721
+ }
15722
+ return tokens;
15723
+ }
15724
+ function scoreRelevance(query, documents) {
15725
+ const scores = documents.map(() => 0);
15726
+ const terms = [...new Set(relevanceTokens(query))];
15727
+ if (terms.length === 0 || documents.length === 0) return scores;
15728
+ const indexed = documents.map((document) => {
15729
+ const tokens = relevanceTokens(document);
15730
+ const frequencies = /* @__PURE__ */ new Map();
15731
+ for (const token of tokens) frequencies.set(token, (frequencies.get(token) ?? 0) + 1);
15732
+ return { length: tokens.length, frequencies };
15733
+ });
15734
+ const count = indexed.length;
15735
+ let totalLength = 0;
15736
+ for (const document of indexed) totalLength += document.length;
15737
+ const averageLength = totalLength > 0 ? totalLength / count : 1;
15738
+ const inverseFrequency = /* @__PURE__ */ new Map();
15739
+ for (const term of terms) {
15740
+ let documentFrequency = 0;
15741
+ for (const document of indexed) {
15742
+ if (document.frequencies.has(term)) documentFrequency += 1;
15743
+ }
15744
+ inverseFrequency.set(term, Math.log(1 + (count - documentFrequency + 0.5) / (documentFrequency + 0.5)));
15745
+ }
15746
+ indexed.forEach((document, index) => {
15747
+ let score = 0;
15748
+ for (const term of terms) {
15749
+ const frequency = document.frequencies.get(term) ?? 0;
15750
+ if (frequency === 0) continue;
15751
+ const lengthNorm = 1 - RELEVANCE_B + RELEVANCE_B * (document.length / averageLength);
15752
+ score += inverseFrequency.get(term) * (frequency * (RELEVANCE_K1 + 1) / (frequency + RELEVANCE_K1 * lengthNorm));
15753
+ }
15754
+ scores[index] = score;
15755
+ });
15756
+ return scores;
15757
+ }
15758
+ function roundRelevance(score) {
15759
+ return Math.round(score * 1e3) / 1e3;
15760
+ }
15761
+ function contextItemRelevanceText(item) {
15762
+ return item.stateKey !== void 0 ? `${item.summary} ${item.stateKey}` : item.summary;
15763
+ }
15764
+ function compareCodeUnitIds(left, right) {
15765
+ return left < right ? -1 : left > right ? 1 : 0;
15766
+ }
15767
+ function rankRecall(query, items, limit) {
15768
+ const needle = query.toLowerCase();
15769
+ const scores = scoreRelevance(query, items.map(contextItemRelevanceText));
15770
+ const ranked = [];
15771
+ items.forEach((item, index) => {
15772
+ const score = scores[index] ?? 0;
15773
+ const phraseHit = needle === "" || item.summary.toLowerCase().includes(needle) || (item.stateKey ?? "").toLowerCase().includes(needle);
15774
+ if (phraseHit || score > 0) ranked.push({ item, score, phraseHit });
15775
+ });
15776
+ ranked.sort(
15777
+ (left, right) => (right.phraseHit ? 1 : 0) - (left.phraseHit ? 1 : 0) || right.score - left.score || compareCodeUnitIds(left.item.id, right.item.id)
15778
+ );
15779
+ return ranked.slice(0, limit).map(({ item, score }) => ({ item, relevance: roundRelevance(score) }));
15780
+ }
15781
+
15782
+ // plugins/kxm/src/workflow.ts
15629
15783
  var JOURNAL_CATEGORIES = [
15630
15784
  "plan",
15631
15785
  "decision",
@@ -15715,17 +15869,8 @@ function applyJournalPromotion(entry, decision, decidedAt) {
15715
15869
  return { ...entry, promotion: [...entry.promotion ?? [], record] };
15716
15870
  }
15717
15871
  function improvementReport(entries) {
15718
- const areas = [
15719
- "harness",
15720
- "gates",
15721
- "implementation",
15722
- "workflow",
15723
- "documentation",
15724
- "security",
15725
- "other"
15726
- ];
15727
15872
  const severityWeight = { error: 3, warning: 2, info: 1 };
15728
- return areas.map((area) => {
15873
+ return IMPROVEMENT_AREAS.map((area) => {
15729
15874
  const matching = entries.filter((entry) => entry.area === area);
15730
15875
  const priorities = [...matching].filter((entry) => entry.category === "error" || entry.category === "contradiction" || entry.category === "lesson" || entry.category === "skill-candidate").sort((left, right) => severityWeight[right.severity] - severityWeight[left.severity]).slice(0, 10);
15731
15876
  return {
@@ -15738,6 +15883,97 @@ function improvementReport(entries) {
15738
15883
  };
15739
15884
  }).filter((report) => report.total > 0);
15740
15885
  }
15886
+ var SECURITY_SIGNAL_CLASSES = ["invalid_auth", "invalid_identity", "signal_mismatch"];
15887
+ var SIGNAL_CATEGORIES = ["error", "contradiction", "lesson", "skill-candidate"];
15888
+ var SIGNAL_SEVERITY_WEIGHT = { error: 3, warning: 2, info: 1 };
15889
+ var SIGNAL_CLASS = /^[a-z0-9_]{1,64}$/;
15890
+ var MAX_SIGNAL_IDS = 16;
15891
+ var MAX_SIGNAL_SUMMARY_KEY_CHARS = 160;
15892
+ function normalizeSignalSummary(summary) {
15893
+ return summary.toLowerCase().replace(/\b[a-z]+_[0-9a-f]{8,}\b/g, "<id>").replace(/\b\d{4}-\d{2}-\d{2}t\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:z|[+-]\d{2}:?\d{2})?/g, "<ts>").replace(/\b[0-9a-f]{7,}\b/g, "<hex>").replace(/\d+/g, "#").replace(/\s+/g, " ").trim().slice(0, MAX_SIGNAL_SUMMARY_KEY_CHARS);
15894
+ }
15895
+ function round3(value) {
15896
+ return Math.round(value * 1e3) / 1e3;
15897
+ }
15898
+ function signalKeyOf(entry, runs) {
15899
+ const classRef = entry.evidence.find((ref) => ref.startsWith("class:"));
15900
+ const signalClass = classRef?.slice("class:".length);
15901
+ if (signalClass !== void 0 && SIGNAL_CLASS.test(signalClass) && signalClass !== "unknown") {
15902
+ return { key: `${entry.category}|class:${signalClass}`, basis: "class", signalClass };
15903
+ }
15904
+ const run = runs.get(entry.runId);
15905
+ if (entry.category === "error" && entry.stageId !== void 0 && run) {
15906
+ return { key: `error|stage:${run.definitionId}/${entry.stageId}`, basis: "stage" };
15907
+ }
15908
+ return {
15909
+ key: `${entry.category}|summary:${normalizeSignalSummary(redactSecrets(entry.summary))}`,
15910
+ basis: "summary"
15911
+ };
15912
+ }
15913
+ function runAttemptCost(run) {
15914
+ let attempts = 0;
15915
+ for (const stage of run.stages) attempts += stage.attempts;
15916
+ return Math.max(1, attempts + (run.transitions?.length ?? 0));
15917
+ }
15918
+ function rankImprovementSignals(entries, runs, limit = 20) {
15919
+ const resolvedContradictions = /* @__PURE__ */ new Set();
15920
+ for (const entry of entries) {
15921
+ if (entry.category !== "decision" && entry.category !== "lesson") continue;
15922
+ for (const related of entry.relatedEntryIds) resolvedContradictions.add(`${entry.runId}\0${related}`);
15923
+ }
15924
+ const groups = /* @__PURE__ */ new Map();
15925
+ for (const entry of entries) {
15926
+ if (!SIGNAL_CATEGORIES.includes(entry.category)) continue;
15927
+ if (entry.category === "contradiction" && resolvedContradictions.has(`${entry.runId}\0${entry.id}`)) continue;
15928
+ const promotionState = journalPromotionState(entry);
15929
+ if (promotionState !== void 0 && promotionState !== "proposed") continue;
15930
+ const { key, basis, signalClass } = signalKeyOf(entry, runs);
15931
+ const group = groups.get(key);
15932
+ if (group) group.entries.push(entry);
15933
+ else groups.set(key, { basis, category: entry.category, ...signalClass !== void 0 ? { signalClass } : {}, entries: [entry] });
15934
+ }
15935
+ const areaRank = (area) => {
15936
+ const index = IMPROVEMENT_AREAS.indexOf(area);
15937
+ return index === -1 ? IMPROVEMENT_AREAS.length : index;
15938
+ };
15939
+ const signals = [];
15940
+ for (const [key, group] of groups) {
15941
+ const runIds = [...new Set(group.entries.map((entry) => entry.runId))].sort(compareCodeUnitIds);
15942
+ const entryIds = group.entries.map((entry) => entry.id).sort(compareCodeUnitIds);
15943
+ let severity = "info";
15944
+ for (const entry of group.entries) {
15945
+ if ((SIGNAL_SEVERITY_WEIGHT[entry.severity] ?? 0) > SIGNAL_SEVERITY_WEIGHT[severity]) severity = entry.severity;
15946
+ }
15947
+ const severityWeight = SIGNAL_SEVERITY_WEIGHT[severity];
15948
+ const knownRuns = runIds.map((runId) => runs.get(runId)).filter((run) => run !== void 0);
15949
+ const workflowCost = knownRuns.length > 0 ? knownRuns.reduce((total, run) => total + runAttemptCost(run), 0) / knownRuns.length : null;
15950
+ const withEvidence = group.entries.filter((entry) => entry.evidence.length > 0).length;
15951
+ const confidence = 0.5 + 0.5 * (withEvidence / group.entries.length);
15952
+ const areaCounts = /* @__PURE__ */ new Map();
15953
+ for (const entry of group.entries) areaCounts.set(entry.area, (areaCounts.get(entry.area) ?? 0) + 1);
15954
+ const area = [...areaCounts].sort((left, right) => right[1] - left[1] || areaRank(left[0]) - areaRank(right[0]))[0][0];
15955
+ const latest = [...group.entries].sort((left, right) => compareCodeUnitIds(left.createdAt, right.createdAt) || compareCodeUnitIds(left.id, right.id)).at(-1);
15956
+ const security = area === "security" || group.signalClass !== void 0 && SECURITY_SIGNAL_CLASSES.includes(group.signalClass);
15957
+ signals.push({
15958
+ key,
15959
+ basis: group.basis,
15960
+ category: group.category,
15961
+ area,
15962
+ ...security ? { overrideTier: "security" } : {},
15963
+ frequency: runIds.length,
15964
+ runIds: runIds.slice(0, MAX_SIGNAL_IDS),
15965
+ entryIds: entryIds.slice(0, MAX_SIGNAL_IDS),
15966
+ severity,
15967
+ severityWeight,
15968
+ workflowCost,
15969
+ costBasis: workflowCost === null ? "unknown" : "run-attempts",
15970
+ confidence,
15971
+ priority: round3(runIds.length * severityWeight * (workflowCost ?? 1) * confidence),
15972
+ summary: redactSecrets(latest.summary)
15973
+ });
15974
+ }
15975
+ return signals.sort((left, right) => (right.overrideTier === "security" ? 1 : 0) - (left.overrideTier === "security" ? 1 : 0) || right.priority - left.priority || right.frequency - left.frequency || compareCodeUnitIds(left.key, right.key)).slice(0, limit);
15976
+ }
15741
15977
  function object(value, name) {
15742
15978
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${name} must be an object`);
15743
15979
  return value;
@@ -15825,6 +16061,19 @@ function workflowEvidenceStrings(evidence) {
15825
16061
  function activeWorkflowAttempt(stage) {
15826
16062
  return stage.attempts + 1;
15827
16063
  }
16064
+ function journalAttemptFor(stage) {
16065
+ switch (stage.status) {
16066
+ case "in_progress":
16067
+ case "waiting":
16068
+ return stage.attempts + 1;
16069
+ case "pending":
16070
+ return stage.attempts > 0 ? stage.attempts : void 0;
16071
+ case "passed":
16072
+ case "warning":
16073
+ case "failed":
16074
+ return Math.max(1, stage.attempts);
16075
+ }
16076
+ }
15828
16077
  function validIsoTimestamp(value) {
15829
16078
  if (!value) return void 0;
15830
16079
  const parsed = Date.parse(value);
@@ -16139,7 +16388,7 @@ function parseWorkflowDefinitions(raw, environment = process.env, onWarning) {
16139
16388
  throw new Error(`stage ${stageId} maxAttempts must be an integer between 1 and 20`);
16140
16389
  }
16141
16390
  const area = stage.area ? requireString(stage.area, "stage.area", { max: 24 }) : void 0;
16142
- if (area && !["harness", "gates", "implementation", "workflow", "documentation", "security", "other"].includes(area)) {
16391
+ if (area && !IMPROVEMENT_AREAS.includes(area)) {
16143
16392
  throw new Error(`stage ${stageId} area is invalid`);
16144
16393
  }
16145
16394
  const requiredEvidence = stringArray(stage.requiredEvidence ?? [], "stage.requiredEvidence").map((requirement, requirementIndex) => canonicalWorkflowEvidenceKey(
@@ -16977,7 +17226,7 @@ var AGENT_COMMANDS = [
16977
17226
  group: "workflow",
16978
17227
  verb: "run",
16979
17228
  label: "Get workflow run",
16980
- description: "Get a workflow's stages and journal of plans, decisions, contradictions, errors, and lessons.",
17229
+ description: "Get a workflow's stages and its learning journal (plans, decisions, contradictions, errors, lessons, and the other journal categories).",
16981
17230
  parameters: {
16982
17231
  type: "object",
16983
17232
  properties: {
@@ -17051,20 +17300,24 @@ var AGENT_COMMANDS = [
17051
17300
  group: "workflow",
17052
17301
  verb: "record",
17053
17302
  label: "Record workflow journal entry",
17054
- description: "Record a plan, decision, contradiction, error, or lesson for continuous improvement.",
17303
+ description: "Record a plan, decision, contradiction, error, lesson, observation, hypothesis, experiment, state-change, or skill-candidate for continuous improvement. Pass stageId to bind the entry to that stage: the hub derives the attempt, and area defaults to the stage's declared area. Lessons and skill-candidates require evidence.",
17055
17304
  parameters: {
17056
17305
  type: "object",
17057
17306
  properties: {
17058
17307
  runId: { type: "string", description: "Active durable workflow run ID" },
17059
17308
  category: {
17060
17309
  type: "string",
17061
- enum: ["plan", "decision", "contradiction", "error", "lesson"],
17310
+ enum: [...JOURNAL_CATEGORIES],
17062
17311
  description: "Category of journal entry"
17063
17312
  },
17064
17313
  area: {
17065
17314
  type: "string",
17066
- enum: ["harness", "gates", "implementation", "workflow", "documentation", "security", "other"],
17067
- description: "System area"
17315
+ enum: [...IMPROVEMENT_AREAS],
17316
+ description: "System area; required unless stageId names a stage that declares an area"
17317
+ },
17318
+ stageId: {
17319
+ type: "string",
17320
+ description: "Stage the entry belongs to; the hub binds the attempt from the stage's state"
17068
17321
  },
17069
17322
  severity: {
17070
17323
  type: "string",
@@ -17087,13 +17340,14 @@ var AGENT_COMMANDS = [
17087
17340
  description: "Related previous journal entry IDs"
17088
17341
  }
17089
17342
  },
17090
- required: ["runId", "category", "area", "summary"],
17343
+ required: ["runId", "category", "summary"],
17091
17344
  additionalProperties: false
17092
17345
  },
17093
17346
  async execute(client, args) {
17094
17347
  return await client.recordWorkflowEntry(requiredString(args.runId, "runId"), {
17095
17348
  category: requiredString(args.category, "category"),
17096
- area: requiredString(args.area, "area"),
17349
+ ...optionalString2(args.area) ? { area: optionalString2(args.area) } : {},
17350
+ ...optionalString2(args.stageId) ? { stageId: optionalString2(args.stageId) } : {},
17097
17351
  ...optionalString2(args.severity) ? { severity: optionalString2(args.severity) } : {},
17098
17352
  summary: requiredString(args.summary, "summary"),
17099
17353
  ...optionalString2(args.details) ? { details: optionalString2(args.details) } : {},
@@ -17170,7 +17424,7 @@ var AGENT_COMMANDS = [
17170
17424
  group: "workflow",
17171
17425
  verb: "improve-report",
17172
17426
  label: "Summarize improvement report",
17173
- description: "Summarize workflow errors, contradictions, and lessons by improvement area.",
17427
+ description: "Summarize workflow errors, contradictions, lessons, and skill candidates by improvement area, plus ranked cross-run signals: duplicates merged across runs and scored by frequency x severity x run-attempt cost x evidence confidence, security first, with redacted text.",
17174
17428
  parameters: {
17175
17429
  type: "object",
17176
17430
  properties: {},
@@ -17224,7 +17478,7 @@ var AGENT_COMMANDS = [
17224
17478
  group: "context",
17225
17479
  verb: "recall",
17226
17480
  label: "Recall context metadata",
17227
- description: "Search durable context records for a project by query; returns bounded metadata only.",
17481
+ description: "Search durable context records for a project by query. Ranks exact-phrase matches first, then token relevance, then id; returns bounded metadata with a numeric relevance per item, never summaries.",
17228
17482
  parameters: {
17229
17483
  type: "object",
17230
17484
  properties: {
@@ -17547,7 +17801,14 @@ function parseContextRequest(value) {
17547
17801
  return request;
17548
17802
  }
17549
17803
  function validateContextPacketContents(request, packet) {
17550
- const items = [...packet.currentState, ...packet.knowledge, ...packet.episodes, ...packet.skills, ...packet.contradictions];
17804
+ const items = [
17805
+ ...packet.currentState,
17806
+ ...packet.knowledge,
17807
+ ...packet.evidence,
17808
+ ...packet.episodes,
17809
+ ...packet.skills,
17810
+ ...packet.contradictions
17811
+ ];
17551
17812
  if (items.length > MAX_CONTEXT_ITEMS) {
17552
17813
  throw new ProtocolError(400, `context packet exceeds ${MAX_CONTEXT_ITEMS} items`, "context_limits_exceeded");
17553
17814
  }
@@ -17569,12 +17830,12 @@ function validateContextPacketContents(request, packet) {
17569
17830
  }
17570
17831
  }
17571
17832
  }
17833
+ function contextItemCharacters(item) {
17834
+ return item.summary.length + item.id.length + item.kind.length + (item.provenance.sourceRef?.length ?? 0);
17835
+ }
17572
17836
  function estimateContextTokens(items) {
17573
17837
  let characters = 0;
17574
- for (const item of items) {
17575
- characters += item.summary.length + item.id.length + item.kind.length;
17576
- if (item.provenance.sourceRef) characters += item.provenance.sourceRef.length;
17577
- }
17838
+ for (const item of items) characters += contextItemCharacters(item);
17578
17839
  return Math.ceil(characters / 4);
17579
17840
  }
17580
17841
  function authorityRank(authority) {
@@ -17619,7 +17880,7 @@ var ROLE_POLICIES = [
17619
17880
  {
17620
17881
  role: "repro",
17621
17882
  label: "Reproduction specialist",
17622
- kinds: ["episode", "knowledge"],
17883
+ kinds: ["episode", "knowledge", "evidence"],
17623
17884
  journalCategories: ["error", "lesson", "observation", "contradiction"],
17624
17885
  budgetTokens: 8e3
17625
17886
  },
@@ -17640,7 +17901,7 @@ var ROLE_POLICIES = [
17640
17901
  {
17641
17902
  role: "implementer",
17642
17903
  label: "Implementer",
17643
- kinds: ["knowledge", "state", "skill", "episode"],
17904
+ kinds: ["knowledge", "state", "skill", "episode", "evidence"],
17644
17905
  journalCategories: ["plan", "decision", "lesson", "state-change"],
17645
17906
  budgetTokens: 16e3
17646
17907
  },
@@ -17712,29 +17973,40 @@ function arbitrate(requestInput, pool, options = {}) {
17712
17973
  const rank = kindRank.get(item.kind);
17713
17974
  return rank === void 0 ? requestedKinds.length : rank;
17714
17975
  };
17715
- const ordered = [...candidates].sort(
17716
- (left, right) => (contradictions.has(right.id) ? 1 : 0) - (contradictions.has(left.id) ? 1 : 0) || (left.project === request.project ? 0 : 1) - (right.project === request.project ? 0 : 1) || kindPreference(left) - kindPreference(right) || CONFIDENCE_RANK[right.confidence] - CONFIDENCE_RANK[left.confidence] || AUTHORITY_WEIGHT[right.authority] - AUTHORITY_WEIGHT[left.authority] || left.id.localeCompare(right.id)
17976
+ const allowedKinds = new Set(requestedKinds);
17977
+ const inertProposal = (item) => item.kind === "state" && item.status !== void 0 && item.status !== "current" || item.kind === "skill" && item.status === "proposed";
17978
+ const eligible = candidates.filter((item) => contradictions.has(item.id) || allowedKinds.has(item.kind) && !inertProposal(item));
17979
+ const scoreList = scoreRelevance(request.task, eligible.map(contextItemRelevanceText));
17980
+ const scores = /* @__PURE__ */ new Map();
17981
+ eligible.forEach((item, index) => scores.set(item, scoreList[index] ?? 0));
17982
+ const scoreOf = (item) => scores.get(item) ?? 0;
17983
+ const when = (item) => {
17984
+ const parsed = Date.parse(item.observedAt ?? item.validFrom ?? "");
17985
+ return Number.isFinite(parsed) ? parsed : 0;
17986
+ };
17987
+ const ordered = [...eligible].sort(
17988
+ (left, right) => (contradictions.has(right.id) ? 1 : 0) - (contradictions.has(left.id) ? 1 : 0) || (left.project === request.project ? 0 : 1) - (right.project === request.project ? 0 : 1) || (scoreOf(right) > 0 ? 1 : 0) - (scoreOf(left) > 0 ? 1 : 0) || kindPreference(left) - kindPreference(right) || scoreOf(right) - scoreOf(left) || CONFIDENCE_RANK[right.confidence] - CONFIDENCE_RANK[left.confidence] || AUTHORITY_WEIGHT[right.authority] - AUTHORITY_WEIGHT[left.authority] || when(right) - when(left) || compareCodeUnitIds(left.id, right.id)
17717
17989
  );
17718
- const kindAllowed = (item) => (request.includeKinds ?? policy.kinds).includes(item.kind) || contradictions.has(item.id);
17719
17990
  const selected = [];
17720
17991
  const unresolvedGaps = [];
17992
+ let characters = 0;
17993
+ let deferredForBudget = 0;
17721
17994
  for (const item of ordered) {
17722
17995
  if (selected.length >= MAX_CONTEXT_ITEMS) {
17723
17996
  unresolvedGaps.push("context item limit reached; refine the task or kinds");
17724
17997
  break;
17725
17998
  }
17726
- if (!kindAllowed(item)) continue;
17727
- const nextTokens = estimateContextTokens([...selected, item]);
17728
- if (nextTokens > budget) {
17729
- if (selected.length === 0) {
17730
- unresolvedGaps.push(`budget of ${budget} tokens cannot fit any selected context`);
17731
- break;
17732
- }
17733
- unresolvedGaps.push(`budget of ${budget} tokens reached; ${ordered.length - selected.length} candidates deferred`);
17734
- break;
17999
+ const itemCharacters = contextItemCharacters(item);
18000
+ if (Math.ceil((characters + itemCharacters) / 4) > budget) {
18001
+ deferredForBudget += 1;
18002
+ continue;
17735
18003
  }
18004
+ characters += itemCharacters;
17736
18005
  selected.push(item);
17737
18006
  }
18007
+ if (deferredForBudget > 0) {
18008
+ unresolvedGaps.push(selected.length === 0 ? `budget of ${budget} tokens cannot fit any selected context` : `budget of ${budget} tokens reached; ${deferredForBudget} candidates deferred`);
18009
+ }
17738
18010
  if (candidates.length === 0) {
17739
18011
  unresolvedGaps.push("no context records exist for this project yet");
17740
18012
  }
@@ -17743,6 +18015,7 @@ function arbitrate(requestInput, pool, options = {}) {
17743
18015
  workingState: options.workingState ?? {},
17744
18016
  currentState: bySection("state").filter((item) => item.status === "current" || item.status === void 0),
17745
18017
  knowledge: bySection("knowledge"),
18018
+ evidence: bySection("evidence"),
17746
18019
  episodes: bySection("episode"),
17747
18020
  skills: bySection("skill").filter((item) => item.status !== "proposed"),
17748
18021
  contradictions: selected.filter((item) => contradictions.has(item.id)),
@@ -17767,7 +18040,12 @@ function arbitrate(requestInput, pool, options = {}) {
17767
18040
  budgetTokens: budget,
17768
18041
  candidateCount: candidates.length,
17769
18042
  excludedSuperseded,
17770
- unresolvedGaps
18043
+ unresolvedGaps,
18044
+ relevance: {
18045
+ taskTokens: new Set(relevanceTokens(request.task)).size,
18046
+ matchedCandidates: scoreList.filter((score) => score > 0).length,
18047
+ selected: selected.map((item) => roundRelevance(scoreOf(item)))
18048
+ }
17771
18049
  }
17772
18050
  };
17773
18051
  }
@@ -17784,10 +18062,9 @@ function journalEntryToContextItem(entry, project) {
17784
18062
  },
17785
18063
  authority: entry.category === "decision" || entry.category === "plan" ? "evidence" : "evidence",
17786
18064
  confidence: entry.severity === "error" ? "probable" : "probable",
17787
- ...entry.stageId !== void 0 ? { observedAt: entry.createdAt } : {},
18065
+ observedAt: entry.createdAt,
17788
18066
  evidenceRefs: entry.evidence.filter((ref) => ref.length > 0 && ref.length <= 200).slice(0, 16)
17789
18067
  };
17790
- if (entry.stageId !== void 0) item.observedAt = entry.createdAt;
17791
18068
  if (kind === "skill") item.status = "proposed";
17792
18069
  return parseContextItem(item);
17793
18070
  }
@@ -18907,19 +19184,25 @@ function buildRetrospective(run, journal, exportedAt = (/* @__PURE__ */ new Date
18907
19184
  const byCategory = {};
18908
19185
  const byArea = {};
18909
19186
  const byClass = {};
19187
+ const byErrorClass = {};
18910
19188
  for (const entry of entries) {
18911
19189
  increment(byCategory, entry.category);
18912
19190
  increment(byArea, entry.area);
18913
19191
  increment(byClass, classFromEvidence(entry.evidence));
19192
+ if (entry.category === "error") increment(byErrorClass, classFromEvidence(entry.evidence));
18914
19193
  }
18915
- const recurringErrorClasses = Object.entries(byClass).map(([errorClass, count]) => ({ class: errorClass, count })).sort((left, right) => right.count - left.count || left.class.localeCompare(right.class));
19194
+ const recurringErrorClasses = Object.entries(byErrorClass).map(([errorClass, count]) => ({ class: errorClass, count })).sort((left, right) => right.count - left.count || left.class.localeCompare(right.class));
18916
19195
  const resolvedContradictions = new Set(entries.filter((entry) => entry.category === "decision" || entry.category === "lesson").flatMap((entry) => entry.relatedEntryIds));
18917
19196
  const openContradictions = entries.filter((entry) => entry.category === "contradiction" && !resolvedContradictions.has(entry.id)).map((entry) => ({ id: entry.id, summary: entry.summary, area: entry.area }));
18918
19197
  const decisions = entries.filter((entry) => entry.category === "decision").map((entry) => ({ id: entry.id, summary: entry.summary, area: entry.area }));
18919
- const proposedImprovements = entries.filter((entry) => entry.category === "lesson" || entry.category === "error").slice(0, 12).map((entry) => ({
18920
- area: entry.area,
18921
- summary: entry.summary,
18922
- successMeasure: "reduce recurrence of this class in the next comparable run",
19198
+ const proposedImprovements = rankImprovementSignals(
19199
+ journal.filter((entry) => entry.runId === run.id && (entry.category === "error" || entry.category === "lesson")),
19200
+ /* @__PURE__ */ new Map([[run.id, run]]),
19201
+ 12
19202
+ ).map((signal) => ({
19203
+ area: signal.area,
19204
+ summary: signal.summary,
19205
+ successMeasure: redactSecrets(`no recurrence of ${signal.key} in the next ${run.definitionId} run`),
18923
19206
  status: "proposed"
18924
19207
  }));
18925
19208
  const evidenceAudit = buildEvidenceAudit(run);
@@ -20825,7 +21108,7 @@ data: ${JSON.stringify({ type: "ops", project, topic, at: nowIso() })}
20825
21108
  "Execute these stages in order:",
20826
21109
  stageList,
20827
21110
  "",
20828
- "At every stage, record material plans, decisions, contradictions, errors, and lessons with kxm_workflow_record.",
21111
+ `At every stage, record material knowledge with kxm_workflow_record in one of these categories: ${JOURNAL_CATEGORIES.join(", ")}. Pass the stageId the entry belongs to; the hub binds the attempt and, when you omit area, uses the stage's declared area.`,
20829
21112
  "Keep repository-local configuration in .kxm/config, logs in .kxm/logs, and durable workflow artifacts in .kxm/assets; never commit runtime logs, state, or secrets.",
20830
21113
  "Complete each stage with kxm_workflow_checkpoint. Supply evidence as an object whose keys exactly match the stage's required evidence keys. Unrelated keys never satisfy a requirement. A warning or failure must be corrected and checkpointed again until it passes or the attempt limit is reached.",
20831
21114
  "For a peer-evidence requirement, send or fan out with workflowContext containing this run ID, the exact stage ID, requirement key, and current 1-based attempt. At checkpoint, cite only the returned message IDs under evidenceRefs; the hub derives producer and reply provenance.",
@@ -20853,7 +21136,7 @@ data: ${JSON.stringify({ type: "ops", project, topic, at: nowIso() })}
20853
21136
  `Evidence: ${workflowEvidenceStrings(evidence).join(", ") || "none supplied"}`,
20854
21137
  "",
20855
21138
  nextInstruction,
20856
- "Review the run with kxm_workflow_get and keep recording material plans, decisions, contradictions, errors, and lessons.",
21139
+ "Review the run with kxm_workflow_get and keep recording material learning with kxm_workflow_record (any of its ten categories; pass stageId for stage-bound entries).",
20857
21140
  "Do not claim the workflow is complete until the checkpoint response reports completed=true."
20858
21141
  ].join("\n"), "workflow resume prompt", { max: MAX_CONTENT_CHARS });
20859
21142
  const seq = store.nextAgentSequence(run.targetAgentId);
@@ -20907,7 +21190,8 @@ data: ${JSON.stringify({ type: "ops", project, topic, at: nowIso() })}
20907
21190
  summary: `External workflow signal timed out: ${waiting.signalKey}`,
20908
21191
  evidence: [`wait-created:${waiting.createdAt}`, `wait-expired:${waiting.expiresAt}`],
20909
21192
  relatedEntryIds: [],
20910
- createdAt: timestamp
21193
+ createdAt: timestamp,
21194
+ ...stage ? { stageId: stage.id, attempt: stage.attempts + 1 } : {}
20911
21195
  };
20912
21196
  const definition = webhookWorkflows2.get(transition.definitionId);
20913
21197
  const ttlMs = parseBoundedInteger(
@@ -20986,6 +21270,7 @@ data: ${JSON.stringify({ type: "ops", project, topic, at: nowIso() })}
20986
21270
  (candidate) => candidate.messageId === message.id && candidate.status === "running"
20987
21271
  );
20988
21272
  if (run) {
21273
+ const expiredStage = run.stages.find((candidate) => candidate.id === run.currentStage);
20989
21274
  run.status = "failed";
20990
21275
  delete run.currentStage;
20991
21276
  run.updatedAt = nowIso();
@@ -21001,7 +21286,8 @@ data: ${JSON.stringify({ type: "ops", project, topic, at: nowIso() })}
21001
21286
  summary: "Workflow coordinator prompt expired before completion",
21002
21287
  evidence: [`message:${message.id}`],
21003
21288
  relatedEntryIds: [],
21004
- createdAt: run.updatedAt
21289
+ createdAt: run.updatedAt,
21290
+ ...expiredStage ? { stageId: expiredStage.id, attempt: expiredStage.attempts + 1 } : {}
21005
21291
  };
21006
21292
  store.saveJournalEntry(entry);
21007
21293
  counters.journalEntries += 1;
@@ -21237,7 +21523,9 @@ data: ${JSON.stringify({ type: "ops", project, topic, at: nowIso() })}
21237
21523
  summary: `${stage.label}: ${summary}`,
21238
21524
  evidence: workflowEvidenceStrings(evidence),
21239
21525
  relatedEntryIds: [],
21240
- createdAt: receivedAt
21526
+ createdAt: receivedAt,
21527
+ stageId: stage.id,
21528
+ attempt: stage.attempts
21241
21529
  };
21242
21530
  } else if (result.degraded) {
21243
21531
  const stage = transition.stages.find((candidate) => candidate.id === result.stageId);
@@ -21254,7 +21542,9 @@ data: ${JSON.stringify({ type: "ops", project, topic, at: nowIso() })}
21254
21542
  ...(stage.degradedRequirements ?? []).map((requirement) => `requirement:${requirement}`)
21255
21543
  ],
21256
21544
  relatedEntryIds: [],
21257
- createdAt: receivedAt
21545
+ createdAt: receivedAt,
21546
+ stageId: stage.id,
21547
+ attempt: stage.attempts
21258
21548
  };
21259
21549
  }
21260
21550
  if (transition.status === "running") {
@@ -21522,7 +21812,9 @@ data: ${JSON.stringify({ type: "ops", project, topic: "agents", at: nowIso() })}
21522
21812
  `approved-min:${result.approval.approvedMinProducers}`
21523
21813
  ],
21524
21814
  relatedEntryIds: [],
21525
- createdAt: timestamp
21815
+ createdAt: timestamp,
21816
+ stageId,
21817
+ attempt: result.approval.attempt
21526
21818
  };
21527
21819
  store.saveWorkflowTransition(transition, void 0, entry);
21528
21820
  publishOps(transition.project, "workflows");
@@ -21555,9 +21847,16 @@ data: ${JSON.stringify({ type: "ops", project, topic: "agents", at: nowIso() })}
21555
21847
  ...skillLifecycle ? { skillLifecycle } : {}
21556
21848
  });
21557
21849
  counters.contextRequests += 1;
21850
+ const assembledRequest = outcome.audit.request;
21558
21851
  logger({
21559
21852
  event: "context_packet_assembled",
21560
- ...outcome.audit.request,
21853
+ project: assembledRequest.project,
21854
+ role: assembledRequest.role,
21855
+ ...assembledRequest.workflowRunId !== void 0 ? { workflowRunId: assembledRequest.workflowRunId } : {},
21856
+ ...assembledRequest.stageId !== void 0 ? { stageId: assembledRequest.stageId } : {},
21857
+ taskChars: assembledRequest.task.length,
21858
+ taskTokens: outcome.audit.relevance.taskTokens,
21859
+ matchedCandidates: outcome.audit.relevance.matchedCandidates,
21561
21860
  selectedIds: outcome.audit.selectedIds,
21562
21861
  provenanceSummary: outcome.audit.provenanceSummary,
21563
21862
  estimatedTokens: outcome.audit.estimatedTokens,
@@ -21572,14 +21871,25 @@ data: ${JSON.stringify({ type: "ops", project, topic: "agents", at: nowIso() })}
21572
21871
  if (method === "POST" && contextRecallMatch) {
21573
21872
  const body = await readJson(request);
21574
21873
  const { project: callerProject, caller: callerId } = contextCallerProject(request, body.project);
21575
- const query = requireString(body.query ?? "", "query", { max: 500, allowEmpty: true }).toLowerCase();
21874
+ const query = requireString(body.query ?? "", "query", { max: 500, allowEmpty: true });
21576
21875
  const kinds = Array.isArray(body.kinds) ? body.kinds.filter((kind) => typeof kind === "string") : void 0;
21577
21876
  const limit = parseBoundedInteger(body.limit, "limit", 25, 1, 100);
21578
21877
  const { pool } = projectContextPool2(callerProject);
21579
- const recalled = pool.filter((item) => item.status !== "superseded" && item.status !== "rejected").filter((item) => kinds === void 0 || kinds.includes(item.kind)).filter((item) => query === "" || item.summary.toLowerCase().includes(query) || (item.stateKey ?? "").toLowerCase().includes(query)).sort((left, right) => left.id.localeCompare(right.id)).slice(0, limit);
21878
+ const live = pool.filter((item) => item.status !== "superseded" && item.status !== "rejected").filter((item) => kinds === void 0 || kinds.includes(item.kind));
21879
+ const recalled = rankRecall(query, live, limit);
21580
21880
  counters.contextRequests += 1;
21581
- logger({ event: "context_recall", project: callerProject, query, limit, results: recalled.length });
21582
- json(response, 200, { items: recalled.map(contextItemAuditMetadata), unresolvedGaps: recalled.length === 0 ? ["no matching context records"] : [] });
21881
+ logger({
21882
+ event: "context_recall",
21883
+ project: callerProject,
21884
+ queryChars: query.length,
21885
+ queryTokens: new Set(relevanceTokens(query)).size,
21886
+ limit,
21887
+ results: recalled.length
21888
+ });
21889
+ json(response, 200, {
21890
+ items: recalled.map(({ item, relevance }) => ({ ...contextItemAuditMetadata(item), relevance })),
21891
+ unresolvedGaps: recalled.length === 0 ? ["no matching context records"] : []
21892
+ });
21583
21893
  return;
21584
21894
  }
21585
21895
  const contextStateMatch = url.pathname.match(/^\/v1\/context\/state$/);
@@ -21715,7 +22025,11 @@ data: ${JSON.stringify({ type: "ops", project, topic: "agents", at: nowIso() })}
21715
22025
  nowIso()
21716
22026
  );
21717
22027
  store.saveJournalEntry(updated);
21718
- publishOps(entry.runId, "workflows");
22028
+ const promotedRun = workflowRuns.get(entry.runId);
22029
+ if (promotedRun) {
22030
+ publishOps(promotedRun.project, "workflows");
22031
+ exportTerminalRetrospective(promotedRun);
22032
+ }
21719
22033
  logger({
21720
22034
  event: "journal_promotion_recorded",
21721
22035
  journalEntryId: entry.id,
@@ -21841,6 +22155,7 @@ data: ${JSON.stringify({ type: "ops", project, topic: "agents", at: nowIso() })}
21841
22155
  typeof body.outcome === "string" && body.outcome.trim() ? requireString(body.outcome, "outcome", { max: 64 }) : void 0
21842
22156
  );
21843
22157
  const checkpointStage = transition.stages.find((candidate) => candidate.id === stageId);
22158
+ const checkpointAttempt = result.transition?.attempt ?? checkpointStage.attempts;
21844
22159
  if (result.transition) {
21845
22160
  const transitionEntry = {
21846
22161
  id: newId("journal"),
@@ -21852,7 +22167,9 @@ data: ${JSON.stringify({ type: "ops", project, topic: "agents", at: nowIso() })}
21852
22167
  summary: `typed transition ${result.transition.fromStage} -> ${result.transition.toStage} (${result.transition.outcome})`,
21853
22168
  evidence: result.transition.evidenceKeys.map((key) => `requirement:${key}`),
21854
22169
  relatedEntryIds: [],
21855
- createdAt: timestamp
22170
+ createdAt: timestamp,
22171
+ stageId,
22172
+ attempt: checkpointAttempt
21856
22173
  };
21857
22174
  store.saveWorkflowTransition(transition, void 0, transitionEntry);
21858
22175
  counters.journalEntries += 1;
@@ -21868,7 +22185,9 @@ data: ${JSON.stringify({ type: "ops", project, topic: "agents", at: nowIso() })}
21868
22185
  summary: `transition budget exhausted at ${stageId} (outcome ${body.outcome ?? status}); run failed safely`,
21869
22186
  evidence: [`class:transition_budget_exhausted`, `stage:${stageId}`],
21870
22187
  relatedEntryIds: [],
21871
- createdAt: timestamp
22188
+ createdAt: timestamp,
22189
+ stageId,
22190
+ attempt: checkpointAttempt
21872
22191
  };
21873
22192
  store.saveWorkflowTransition(transition, void 0, exhaustEntry);
21874
22193
  counters.journalEntries += 1;
@@ -21885,7 +22204,9 @@ data: ${JSON.stringify({ type: "ops", project, topic: "agents", at: nowIso() })}
21885
22204
  summary: `${checkpointStage.label}: ${summary}`,
21886
22205
  evidence: workflowEvidenceStrings(evidence),
21887
22206
  relatedEntryIds: [],
21888
- createdAt: transition.updatedAt
22207
+ createdAt: transition.updatedAt,
22208
+ stageId,
22209
+ attempt: checkpointAttempt
21889
22210
  };
21890
22211
  } else if (result.degraded) {
21891
22212
  entry = {
@@ -21901,7 +22222,9 @@ data: ${JSON.stringify({ type: "ops", project, topic: "agents", at: nowIso() })}
21901
22222
  ...(checkpointStage.degradedRequirements ?? []).map((requirement) => `requirement:${requirement}`)
21902
22223
  ],
21903
22224
  relatedEntryIds: [],
21904
- createdAt: transition.updatedAt
22225
+ createdAt: transition.updatedAt,
22226
+ stageId,
22227
+ attempt: checkpointAttempt
21905
22228
  };
21906
22229
  }
21907
22230
  store.saveWorkflowTransition(transition, void 0, entry);
@@ -21942,10 +22265,23 @@ data: ${JSON.stringify({ type: "ops", project, topic: "agents", at: nowIso() })}
21942
22265
  );
21943
22266
  }
21944
22267
  const category = parseJournalCategory(body.category);
21945
- const area = requireString(body.area, "area", { max: 24 });
21946
- if (!["harness", "gates", "implementation", "workflow", "documentation", "security", "other"].includes(area)) {
21947
- throw new ProtocolError(400, "invalid improvement area", "invalid_improvement_area");
22268
+ let stage;
22269
+ if (body.stageId !== void 0 && body.stageId !== null) {
22270
+ const requestedStageId = requireString(body.stageId, "stageId", { max: 128 });
22271
+ stage = run.stages.find((candidate) => candidate.id === requestedStageId);
22272
+ if (!stage) {
22273
+ throw new ProtocolError(400, `stageId ${requestedStageId} is not part of this workflow run`, "invalid_journal_relation");
22274
+ }
21948
22275
  }
22276
+ const area = body.area == null ? stage?.area : requireString(body.area, "area", { max: 24 });
22277
+ if (area === void 0 || !IMPROVEMENT_AREAS.includes(area)) {
22278
+ throw new ProtocolError(
22279
+ 400,
22280
+ "area is required unless stageId names a stage that declares an area",
22281
+ "invalid_improvement_area"
22282
+ );
22283
+ }
22284
+ const attempt = stage ? journalAttemptFor(stage) : void 0;
21949
22285
  const severity = requireString(body.severity ?? "info", "severity", { max: 16 });
21950
22286
  if (severity !== "info" && severity !== "warning" && severity !== "error") {
21951
22287
  throw new ProtocolError(400, "severity must be info, warning, or error", "invalid_journal_severity");
@@ -21966,16 +22302,6 @@ data: ${JSON.stringify({ type: "ops", project, topic: "agents", at: nowIso() })}
21966
22302
  "journal_evidence_required"
21967
22303
  );
21968
22304
  }
21969
- let stageId;
21970
- let attempt;
21971
- if (body.stageId !== void 0 && body.stageId !== null) {
21972
- stageId = requireString(body.stageId, "stageId", { max: 128 });
21973
- const stage = run.stages.find((candidate) => candidate.id === stageId);
21974
- if (!stage) {
21975
- throw new ProtocolError(400, `stageId ${stageId} is not part of this workflow run`, "invalid_journal_relation");
21976
- }
21977
- attempt = stage.attempts + 1;
21978
- }
21979
22305
  const entry = {
21980
22306
  id: newId("journal"),
21981
22307
  runId: run.id,
@@ -21988,11 +22314,12 @@ data: ${JSON.stringify({ type: "ops", project, topic: "agents", at: nowIso() })}
21988
22314
  evidence,
21989
22315
  relatedEntryIds,
21990
22316
  createdAt: nowIso(),
21991
- ...stageId !== void 0 ? { stageId } : {},
22317
+ ...stage !== void 0 ? { stageId: stage.id } : {},
21992
22318
  ...attempt !== void 0 ? { attempt } : {}
21993
22319
  };
21994
22320
  store.saveJournalEntry(entry);
21995
22321
  counters.journalEntries += 1;
22322
+ exportTerminalRetrospective(run);
21996
22323
  logger({
21997
22324
  event: "workflow_journal_recorded",
21998
22325
  workflowRunId: run.id,
@@ -22007,11 +22334,16 @@ data: ${JSON.stringify({ type: "ops", project, topic: "agents", at: nowIso() })}
22007
22334
  if (method === "GET" && url.pathname === "/v1/improvements") {
22008
22335
  const agent = requireAgent(request);
22009
22336
  requireProjectAuth(request, agent.project);
22010
- const visibleRuns = new Set(
22011
- [...workflowRuns.values()].filter((run) => run.project === agent.project).map((run) => run.id)
22337
+ const projectRuns = new Map(
22338
+ [...workflowRuns.values()].filter((run) => run.project === agent.project).map((run) => [run.id, run])
22012
22339
  );
22340
+ const visibleRuns = new Set(projectRuns.keys());
22013
22341
  const entries = [...journal.values()].filter((entry) => visibleRuns.has(entry.runId));
22014
- json(response, 200, { reports: improvementReport(entries), entries: entries.length });
22342
+ json(response, 200, {
22343
+ reports: improvementReport(entries),
22344
+ signals: rankImprovementSignals(entries, projectRuns),
22345
+ entries: entries.length
22346
+ });
22015
22347
  return;
22016
22348
  }
22017
22349
  if (method === "POST" && url.pathname === "/v1/agents/register") {
@@ -22429,6 +22761,7 @@ data: ${JSON.stringify({ agent: publicAgent(current, staleAfterMs) })}
22429
22761
  publishOps(message.project, "messages");
22430
22762
  const workflowRun = [...workflowRuns.values()].find((run) => run.messageId === message.id);
22431
22763
  if (workflowRun?.status === "running") {
22764
+ const settledStage = workflowRun.stages.find((candidate) => candidate.id === workflowRun.currentStage);
22432
22765
  workflowRun.status = "failed";
22433
22766
  delete workflowRun.currentStage;
22434
22767
  workflowRun.updatedAt = message.repliedAt;
@@ -22444,7 +22777,8 @@ data: ${JSON.stringify({ agent: publicAgent(current, staleAfterMs) })}
22444
22777
  summary: "Coordinator settled before all required workflow checkpoints passed",
22445
22778
  evidence: [`message:${message.id}`],
22446
22779
  relatedEntryIds: [],
22447
- createdAt: message.repliedAt
22780
+ createdAt: message.repliedAt,
22781
+ ...settledStage ? { stageId: settledStage.id, attempt: settledStage.attempts + 1 } : {}
22448
22782
  };
22449
22783
  store.saveJournalEntry(entry);
22450
22784
  counters.journalEntries += 1;