@alan512/experienceengine 0.1.2 → 0.1.3

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 (215) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.mcp.json +4 -8
  3. package/LICENSE +21 -0
  4. package/README.md +158 -47
  5. package/README.zh-CN.md +178 -57
  6. package/dist/adapters/codex/instruction-template.d.ts +3 -0
  7. package/dist/adapters/codex/instruction-template.js +12 -0
  8. package/dist/adapters/codex/instruction-template.js.map +1 -0
  9. package/dist/adapters/codex/mcp-server.d.ts +38 -41
  10. package/dist/adapters/codex/mcp-server.js +107 -338
  11. package/dist/adapters/codex/mcp-server.js.map +1 -1
  12. package/dist/analyzer/llm-learning-gate.js +39 -0
  13. package/dist/analyzer/llm-learning-gate.js.map +1 -1
  14. package/dist/cli/commands/claude-hook.js +12 -3
  15. package/dist/cli/commands/claude-hook.js.map +1 -1
  16. package/dist/cli/commands/config.js +88 -2
  17. package/dist/cli/commands/config.js.map +1 -1
  18. package/dist/cli/commands/doctor.d.ts +4 -28
  19. package/dist/cli/commands/doctor.js +136 -68
  20. package/dist/cli/commands/doctor.js.map +1 -1
  21. package/dist/cli/commands/feedback.js +4 -0
  22. package/dist/cli/commands/feedback.js.map +1 -1
  23. package/dist/cli/commands/init.d.ts +28 -0
  24. package/dist/cli/commands/init.js +419 -0
  25. package/dist/cli/commands/init.js.map +1 -0
  26. package/dist/cli/commands/inspect.js +203 -37
  27. package/dist/cli/commands/inspect.js.map +1 -1
  28. package/dist/cli/commands/install.js +9 -0
  29. package/dist/cli/commands/install.js.map +1 -1
  30. package/dist/cli/commands/maintenance.js +1 -1
  31. package/dist/cli/commands/maintenance.js.map +1 -1
  32. package/dist/cli/commands/mcp-server.js +4 -0
  33. package/dist/cli/commands/mcp-server.js.map +1 -1
  34. package/dist/cli/commands/status.js +57 -7
  35. package/dist/cli/commands/status.js.map +1 -1
  36. package/dist/cli/dispatch.js +22 -7
  37. package/dist/cli/dispatch.js.map +1 -1
  38. package/dist/cli/state-model.d.ts +14 -0
  39. package/dist/cli/state-model.js +23 -0
  40. package/dist/cli/state-model.js.map +1 -0
  41. package/dist/config/config-schema.d.ts +32 -0
  42. package/dist/config/config-schema.js +26 -0
  43. package/dist/config/config-schema.js.map +1 -1
  44. package/dist/config/default-config.js +3 -0
  45. package/dist/config/default-config.js.map +1 -1
  46. package/dist/config/load-config.js +19 -1
  47. package/dist/config/load-config.js.map +1 -1
  48. package/dist/config/path-resolver.d.ts +0 -1
  49. package/dist/config/path-resolver.js +0 -2
  50. package/dist/config/path-resolver.js.map +1 -1
  51. package/dist/config/runtime-env.d.ts +8 -0
  52. package/dist/config/runtime-env.js +14 -0
  53. package/dist/config/runtime-env.js.map +1 -0
  54. package/dist/config/secrets-store.d.ts +15 -0
  55. package/dist/config/secrets-store.js +56 -0
  56. package/dist/config/secrets-store.js.map +1 -0
  57. package/dist/config/settings-store.d.ts +10 -0
  58. package/dist/config/settings-store.js +44 -0
  59. package/dist/config/settings-store.js.map +1 -1
  60. package/dist/controller/candidate-retriever.d.ts +35 -2
  61. package/dist/controller/candidate-retriever.js +189 -10
  62. package/dist/controller/candidate-retriever.js.map +1 -1
  63. package/dist/controller/injection-renderer.js +52 -1
  64. package/dist/controller/injection-renderer.js.map +1 -1
  65. package/dist/controller/injection-scorecard.d.ts +14 -2
  66. package/dist/controller/injection-scorecard.js +18 -1
  67. package/dist/controller/injection-scorecard.js.map +1 -1
  68. package/dist/controller/intervention-controller.d.ts +15 -2
  69. package/dist/controller/intervention-controller.js +173 -11
  70. package/dist/controller/intervention-controller.js.map +1 -1
  71. package/dist/controller/lexical-retriever.d.ts +14 -0
  72. package/dist/controller/lexical-retriever.js +117 -0
  73. package/dist/controller/lexical-retriever.js.map +1 -0
  74. package/dist/controller/model-reranker.d.ts +20 -0
  75. package/dist/controller/model-reranker.js +187 -0
  76. package/dist/controller/model-reranker.js.map +1 -0
  77. package/dist/controller/node-ranker.js +1 -0
  78. package/dist/controller/node-ranker.js.map +1 -1
  79. package/dist/controller/query-rewrite.d.ts +8 -0
  80. package/dist/controller/query-rewrite.js +69 -0
  81. package/dist/controller/query-rewrite.js.map +1 -0
  82. package/dist/controller/trigger-evaluator.d.ts +23 -2
  83. package/dist/controller/trigger-evaluator.js +57 -3
  84. package/dist/controller/trigger-evaluator.js.map +1 -1
  85. package/dist/distillation/experience-family.d.ts +4 -0
  86. package/dist/distillation/experience-family.js +14 -0
  87. package/dist/distillation/experience-family.js.map +1 -0
  88. package/dist/distillation/host-llm.d.ts +1 -0
  89. package/dist/distillation/host-llm.js +5 -1
  90. package/dist/distillation/host-llm.js.map +1 -1
  91. package/dist/distillation/llm-distiller.js +4 -0
  92. package/dist/distillation/llm-distiller.js.map +1 -1
  93. package/dist/distillation/merge-decider.js +4 -0
  94. package/dist/distillation/merge-decider.js.map +1 -1
  95. package/dist/distillation/prompt-contract.d.ts +1 -1
  96. package/dist/distillation/prompt-contract.js +3 -0
  97. package/dist/distillation/prompt-contract.js.map +1 -1
  98. package/dist/distillation/queue-worker.js +57 -7
  99. package/dist/distillation/queue-worker.js.map +1 -1
  100. package/dist/feedback/state-transition.js +9 -0
  101. package/dist/feedback/state-transition.js.map +1 -1
  102. package/dist/input/input-adapter.js +2 -1
  103. package/dist/input/input-adapter.js.map +1 -1
  104. package/dist/input/outcome-resolver.js +5 -5
  105. package/dist/input/outcome-resolver.js.map +1 -1
  106. package/dist/input/tasktype-resolver.js +2 -0
  107. package/dist/input/tasktype-resolver.js.map +1 -1
  108. package/dist/input/tool-event-significance.d.ts +5 -0
  109. package/dist/input/tool-event-significance.js +7 -0
  110. package/dist/input/tool-event-significance.js.map +1 -0
  111. package/dist/install/claude-code-doctor.d.ts +7 -2
  112. package/dist/install/claude-code-doctor.js +38 -9
  113. package/dist/install/claude-code-doctor.js.map +1 -1
  114. package/dist/install/claude-marketplace-state.d.ts +14 -0
  115. package/dist/install/claude-marketplace-state.js +47 -0
  116. package/dist/install/claude-marketplace-state.js.map +1 -0
  117. package/dist/install/codex-installer.d.ts +18 -0
  118. package/dist/install/codex-installer.js +91 -1
  119. package/dist/install/codex-installer.js.map +1 -1
  120. package/dist/install/openclaw-installer.d.ts +7 -0
  121. package/dist/install/openclaw-installer.js +16 -0
  122. package/dist/install/openclaw-installer.js.map +1 -1
  123. package/dist/install/public-install.d.ts +14 -4
  124. package/dist/install/public-install.js +20 -7
  125. package/dist/install/public-install.js.map +1 -1
  126. package/dist/interaction/repo-summary.d.ts +3 -17
  127. package/dist/interaction/repo-summary.js +10 -27
  128. package/dist/interaction/repo-summary.js.map +1 -1
  129. package/dist/interaction/service.d.ts +44 -95
  130. package/dist/interaction/service.js +333 -248
  131. package/dist/interaction/service.js.map +1 -1
  132. package/dist/maintenance/scope-merge.d.ts +0 -1
  133. package/dist/maintenance/scope-merge.js +0 -20
  134. package/dist/maintenance/scope-merge.js.map +1 -1
  135. package/dist/plugin/openclaw-plugin.d.ts +23 -0
  136. package/dist/plugin/openclaw-plugin.js +86 -6
  137. package/dist/plugin/openclaw-plugin.js.map +1 -1
  138. package/dist/plugin/openclaw-routine-interaction.d.ts +6 -0
  139. package/dist/plugin/openclaw-routine-interaction.js +296 -0
  140. package/dist/plugin/openclaw-routine-interaction.js.map +1 -0
  141. package/dist/runtime/service.d.ts +0 -1
  142. package/dist/runtime/service.js +20 -29
  143. package/dist/runtime/service.js.map +1 -1
  144. package/dist/store/sqlite/db.js +9 -0
  145. package/dist/store/sqlite/db.js.map +1 -1
  146. package/dist/store/sqlite/repositories/candidate-repo.js +8 -2
  147. package/dist/store/sqlite/repositories/candidate-repo.js.map +1 -1
  148. package/dist/store/sqlite/repositories/injection-repo.d.ts +1 -0
  149. package/dist/store/sqlite/repositories/injection-repo.js +11 -0
  150. package/dist/store/sqlite/repositories/injection-repo.js.map +1 -1
  151. package/dist/store/sqlite/repositories/input-record-repo.d.ts +2 -0
  152. package/dist/store/sqlite/repositories/input-record-repo.js +22 -0
  153. package/dist/store/sqlite/repositories/input-record-repo.js.map +1 -1
  154. package/dist/store/sqlite/repositories/node-repo.js +17 -2
  155. package/dist/store/sqlite/repositories/node-repo.js.map +1 -1
  156. package/dist/store/sqlite/repositories/task-run-repo.d.ts +2 -0
  157. package/dist/store/sqlite/repositories/task-run-repo.js +18 -2
  158. package/dist/store/sqlite/repositories/task-run-repo.js.map +1 -1
  159. package/dist/store/sqlite/schema.sql +9 -49
  160. package/dist/store/vector/api-embedding-provider.d.ts +2 -0
  161. package/dist/store/vector/api-embedding-provider.js +23 -19
  162. package/dist/store/vector/api-embedding-provider.js.map +1 -1
  163. package/dist/store/vector/embeddings.d.ts +1 -1
  164. package/dist/store/vector/embeddings.js +4 -1
  165. package/dist/store/vector/embeddings.js.map +1 -1
  166. package/dist/types/domain.d.ts +30 -46
  167. package/dist/types/plugin.d.ts +2 -1
  168. package/docs/releases/v0.1.2.md +3 -3
  169. package/docs/releases/v0.1.3.md +94 -0
  170. package/docs/user-guide.md +226 -123
  171. package/openclaw.plugin.json +1 -1
  172. package/package.json +3 -2
  173. package/plugins/claude-code-experienceengine/.claude-plugin/plugin.json +1 -1
  174. package/plugins/claude-code-experienceengine/.mcp.json +4 -3
  175. package/plugins/claude-code-experienceengine/scripts/claude-hook.sh +30 -1
  176. package/plugins/claude-code-experienceengine/scripts/install-deps.sh +41 -6
  177. package/dist/cli/commands/pack.d.ts +0 -1
  178. package/dist/cli/commands/pack.js +0 -321
  179. package/dist/cli/commands/pack.js.map +0 -1
  180. package/dist/compiler/agents-renderer.d.ts +0 -4
  181. package/dist/compiler/agents-renderer.js +0 -105
  182. package/dist/compiler/agents-renderer.js.map +0 -1
  183. package/dist/compiler/claude-renderer.d.ts +0 -2
  184. package/dist/compiler/claude-renderer.js +0 -40
  185. package/dist/compiler/claude-renderer.js.map +0 -1
  186. package/dist/compiler/codex-renderer.d.ts +0 -2
  187. package/dist/compiler/codex-renderer.js +0 -40
  188. package/dist/compiler/codex-renderer.js.map +0 -1
  189. package/dist/compiler/compiler.d.ts +0 -4
  190. package/dist/compiler/compiler.js +0 -87
  191. package/dist/compiler/compiler.js.map +0 -1
  192. package/dist/compiler/deployer.d.ts +0 -21
  193. package/dist/compiler/deployer.js +0 -64
  194. package/dist/compiler/deployer.js.map +0 -1
  195. package/dist/compiler/github-renderer.d.ts +0 -2
  196. package/dist/compiler/github-renderer.js +0 -63
  197. package/dist/compiler/github-renderer.js.map +0 -1
  198. package/dist/compiler/types.d.ts +0 -45
  199. package/dist/compiler/types.js +0 -2
  200. package/dist/compiler/types.js.map +0 -1
  201. package/dist/interaction/pack-actions-service.d.ts +0 -59
  202. package/dist/interaction/pack-actions-service.js +0 -172
  203. package/dist/interaction/pack-actions-service.js.map +0 -1
  204. package/dist/packs/fs-registry.d.ts +0 -27
  205. package/dist/packs/fs-registry.js +0 -216
  206. package/dist/packs/fs-registry.js.map +0 -1
  207. package/dist/packs/index-sync.d.ts +0 -9
  208. package/dist/packs/index-sync.js +0 -54
  209. package/dist/packs/index-sync.js.map +0 -1
  210. package/dist/packs/types.d.ts +0 -55
  211. package/dist/packs/types.js +0 -2
  212. package/dist/packs/types.js.map +0 -1
  213. package/dist/store/sqlite/repositories/pack-repo.d.ts +0 -16
  214. package/dist/store/sqlite/repositories/pack-repo.js +0 -192
  215. package/dist/store/sqlite/repositories/pack-repo.js.map +0 -1
@@ -1,6 +1,3 @@
1
- import { compilePack } from "../compiler/compiler.js";
2
- import { deployCompiledPack } from "../compiler/deployer.js";
3
- import { ExperiencePackRegistry } from "../packs/fs-registry.js";
4
1
  import { buildInjectionScorecard } from "../controller/injection-scorecard.js";
5
2
  import { buildBenchmarkSummary } from "../evaluation/benchmark-summary.js";
6
3
  import { resolveScope } from "../input/scope-resolver.js";
@@ -14,11 +11,39 @@ import { OutcomeRecordRepository } from "../store/sqlite/repositories/outcome-re
14
11
  import { ReviewEventRepository } from "../store/sqlite/repositories/review-event-repo.js";
15
12
  import { ScopeRepository } from "../store/sqlite/repositories/scope-repo.js";
16
13
  import { TaskRunRepository } from "../store/sqlite/repositories/task-run-repo.js";
17
- import { ExperiencePackRepository } from "../store/sqlite/repositories/pack-repo.js";
18
14
  import { transitionState, transitionValidationState } from "../feedback/state-transition.js";
19
15
  import { nowIso } from "../utils/clock.js";
20
16
  import { createId } from "../utils/ids.js";
21
17
  import { buildRepoSummary } from "./repo-summary.js";
18
+ export const deriveStructuredSilenceReason = (input) => {
19
+ const { inspection, readiness } = input;
20
+ const learningReason = inspection.learningReason?.toLowerCase() ?? "";
21
+ const hasEarlyRepoEvidence = readiness.rawRecords < 3 && readiness.taskRuns < 3 && readiness.candidates === 0 && readiness.nodes === 0;
22
+ if (inspection.learningStatus === "not_applicable") {
23
+ return "non_applicable_turn";
24
+ }
25
+ if (inspection.learningStatus === "rejected" && learningReason.includes("expression-layer refinement")) {
26
+ return "non_applicable_turn";
27
+ }
28
+ if (inspection.scorecard?.decisionReason === "ambiguous_same_family_candidate"
29
+ || inspection.scorecard?.decisionReason === "promising_candidate_quality"
30
+ || inspection.intervention === "shadow"
31
+ || inspection.intervention === "holdout") {
32
+ return "withheld_low_confidence";
33
+ }
34
+ if (hasEarlyRepoEvidence) {
35
+ return "warming_up";
36
+ }
37
+ if (inspection.learningStatus === "captured"
38
+ || learningReason.includes("llm gate failed")
39
+ || learningReason.includes("rule fallback rejected candidate")) {
40
+ return "unknown";
41
+ }
42
+ if (inspection.intervention === "skip" && (readiness.rawRecords > 0 || readiness.taskRuns > 0 || readiness.candidates > 0 || readiness.nodes > 0)) {
43
+ return "no_strong_match";
44
+ }
45
+ return "unknown";
46
+ };
22
47
  const toReviewEvent = (nodeId, eventType, source, taskRunId) => ({
23
48
  id: createId("review"),
24
49
  node_id: nodeId,
@@ -27,6 +52,65 @@ const toReviewEvent = (nodeId, eventType, source, taskRunId) => ({
27
52
  source,
28
53
  created_at: nowIso()
29
54
  });
55
+ const deriveNodeRisk = (node) => {
56
+ if (node.state === "candidate") {
57
+ return "high";
58
+ }
59
+ if (node.state === "priority_candidate") {
60
+ return "medium";
61
+ }
62
+ if (node.state === "cooling" || node.harmed_count > 0 || node.node_type === "warning") {
63
+ return node.harmed_count > node.helped_count ? "high" : "medium";
64
+ }
65
+ return "low";
66
+ };
67
+ const deriveQualityBand = (node) => {
68
+ if (node.state === "retired" || node.harmed_count > node.helped_count || deriveNodeRisk(node) === "high") {
69
+ return "risky";
70
+ }
71
+ if (node.state === "active" &&
72
+ node.validation_state === "validated_by_reuse" &&
73
+ node.harmed_count === 0) {
74
+ return "strong";
75
+ }
76
+ return "building";
77
+ };
78
+ const buildQualityDrivers = (node) => {
79
+ const drivers = [];
80
+ if (node.validation_state === "validated_by_reuse") {
81
+ drivers.push("This node has already been validated by successful reuse.");
82
+ }
83
+ if (node.helped_count > node.harmed_count) {
84
+ drivers.push("Helpful outcomes still outweigh harmful ones for this node.");
85
+ }
86
+ else if (node.harmed_count > node.helped_count) {
87
+ drivers.push("Harmful outcomes currently outweigh helpful ones for this node.");
88
+ }
89
+ if (node.state === "candidate" || node.state === "priority_candidate") {
90
+ drivers.push("This node is still early in its lifecycle and needs more runtime evidence.");
91
+ }
92
+ else if (node.state === "cooling") {
93
+ drivers.push("This node is in cooling state because recent runtime evidence weakened confidence.");
94
+ }
95
+ return drivers.slice(0, 3);
96
+ };
97
+ const deriveConfidence = (node) => {
98
+ if (node.validation_state === "validated_by_reuse" && node.state === "active" && node.harmed_count === 0) {
99
+ return "high";
100
+ }
101
+ if (node.state === "candidate" || node.harmed_count > node.helped_count) {
102
+ return "low";
103
+ }
104
+ return "medium";
105
+ };
106
+ const formatTaskFamily = (taskType) => taskType === "general" ? "general tasks" : `${taskType} tasks`;
107
+ const buildApplicabilityProfile = (node) => ({
108
+ bestFit: `${formatTaskFamily(node.task_type)} in this repo scope`,
109
+ scopeValidity: node.applicability_notes ?? "Use within the same repo scope unless fresh evidence says otherwise.",
110
+ confidence: deriveConfidence(node),
111
+ risk: deriveNodeRisk(node),
112
+ avoidWhen: node.stop_condition ?? node.escalation_condition ?? node.avoid_steps?.[0]
113
+ });
30
114
  const toNodeSummary = (node) => ({
31
115
  id: node.id,
32
116
  type: node.node_type,
@@ -36,13 +120,21 @@ const toNodeSummary = (node) => ({
36
120
  distillationMode: node.distillation_mode_used,
37
121
  distillationSource: node.distillation_source,
38
122
  redistilledFrom: node.redistilled_from,
123
+ promotionSignal: node.promotion_signal,
124
+ promotionReason: node.promotion_reason,
125
+ mergeDecision: node.merge_decision,
126
+ mergeReason: node.merge_reason,
127
+ priorityPromotionApplied: node.priority_promotion_applied,
39
128
  triggerPattern: node.trigger_pattern,
40
129
  evidenceSummary: node.evidence_summary,
41
130
  originRecordIds: node.origin_record_ids,
42
131
  helped: node.helped_count,
43
132
  harmed: node.harmed_count,
44
133
  lastUsedAt: node.last_used_at,
45
- hint: node.compact_hint
134
+ hint: node.compact_hint,
135
+ qualityBand: deriveQualityBand(node),
136
+ qualityDrivers: buildQualityDrivers(node),
137
+ applicabilityProfile: buildApplicabilityProfile(node)
46
138
  });
47
139
  const toNodeDetail = (node) => ({
48
140
  ...toNodeSummary(node),
@@ -73,7 +165,6 @@ const applyNodeFeedback = (node, feedback) => {
73
165
  state: node.state === "retired" ? "retired" : transitionState(next)
74
166
  };
75
167
  };
76
- const REPO_SUMMARY_TARGETS = ["codex", "agents", "claude", "github"];
77
168
  const summarizeAutomaticFeedback = (events) => {
78
169
  const automatic = events.filter((event) => event.source === "automatic");
79
170
  if (automatic.some((event) => event.event_type === "mark_harmed")) {
@@ -149,36 +240,73 @@ const buildLatestTimeline = (input) => {
149
240
  }
150
241
  return entries.sort((left, right) => left.createdAt.localeCompare(right.createdAt));
151
242
  };
152
- const toScopePackActivation = (pack, activation) => {
153
- if (!pack) {
243
+ const compareIsoDesc = (left, right) => {
244
+ if (!left && !right) {
245
+ return 0;
246
+ }
247
+ if (!left) {
248
+ return 1;
249
+ }
250
+ if (!right) {
251
+ return -1;
252
+ }
253
+ return right.localeCompare(left);
254
+ };
255
+ const buildDecisionExplanation = (input) => {
256
+ const scorecard = input.scorecard;
257
+ if (!scorecard) {
154
258
  return undefined;
155
259
  }
156
- return {
157
- scopeId: activation.scope_id,
158
- packId: activation.pack_id,
159
- status: pack.status,
160
- currentVersion: pack.current_version,
161
- pinnedVersion: activation.pinned_version,
162
- enabled: activation.enabled,
163
- updatedAt: activation.updated_at
164
- };
260
+ if (scorecard.mode === "inject_conservative") {
261
+ if (scorecard.decisionReason === "ambiguous_same_family_candidate") {
262
+ return "ExperienceEngine found a promising same-family match and chose conservative injection instead of skipping.";
263
+ }
264
+ if (scorecard.decisionReason === "promising_candidate_quality") {
265
+ return "ExperienceEngine found a credible candidate, but kept the injection conservative until it has stronger runtime proof.";
266
+ }
267
+ return "ExperienceEngine chose conservative injection because the best match still needs more runtime evidence.";
268
+ }
269
+ if (scorecard.decisionReason === "mature_validated_candidate") {
270
+ return "A mature validated candidate cleared the fast path, so ExperienceEngine injected it normally.";
271
+ }
272
+ if (scorecard.decisionReason === "candidate_quality_positive") {
273
+ return "Candidate quality was strong enough to justify intervention for this task.";
274
+ }
275
+ if (scorecard.mode === "inject") {
276
+ return "ExperienceEngine injected the best available reusable guidance for this task.";
277
+ }
278
+ if (input.intervention === "shadow") {
279
+ return "ExperienceEngine found a usable match, but delivery was suppressed because this run was in shadow mode.";
280
+ }
281
+ if (input.intervention === "holdout") {
282
+ return "ExperienceEngine found a usable match, but delivery was withheld for evaluation.";
283
+ }
284
+ return undefined;
165
285
  };
166
- const toCompiledArtifactWithPack = (packId, artifact) => ({
167
- ...artifact,
168
- packId
169
- });
170
- const summarizePackCompiler = (registry, packIds, versionByPackId) => {
171
- const compiledArtifacts = packIds.flatMap((packId) => registry.listCompiledArtifacts(packId).map((artifact) => toCompiledArtifactWithPack(packId, artifact)));
172
- const stalePublishedPacks = packIds.filter((packId) => {
173
- const currentVersion = versionByPackId.get(packId);
174
- return currentVersion ? registry.getCompileStatus(packId, currentVersion).stale : false;
175
- }).length;
176
- return {
177
- publishedPacks: packIds.length,
178
- compiledTargets: compiledArtifacts.length,
179
- stalePublishedPacks,
180
- latestCompiledArtifact: compiledArtifacts[0]
181
- };
286
+ const buildTrustSummary = (input) => {
287
+ const scorecard = input.scorecard;
288
+ const primaryNode = input.injectedNodes[0];
289
+ if (!scorecard || !primaryNode) {
290
+ return undefined;
291
+ }
292
+ return `${scorecard.riskLevel}-risk ${primaryNode.state} guidance with ${primaryNode.helped} helped and ${primaryNode.harmed} harmed signal(s).`;
293
+ };
294
+ const buildRetrievalNotes = (scorecard) => {
295
+ if (!scorecard) {
296
+ return [];
297
+ }
298
+ const notes = [];
299
+ if (scorecard.queryRewriteApplied) {
300
+ notes.push("Query rewrite preserved retrieval intent for this task.");
301
+ }
302
+ const rerankSource = scorecard.topCandidates?.[0]?.rerankSource;
303
+ if (rerankSource === "model") {
304
+ notes.push("Model reranking participated in the final ordering.");
305
+ }
306
+ if (scorecard.fastPathApplied) {
307
+ notes.push("A strong candidate fast path was used.");
308
+ }
309
+ return notes;
182
310
  };
183
311
  export class ExperienceInteractionService {
184
312
  inputRepo;
@@ -190,10 +318,7 @@ export class ExperienceInteractionService {
190
318
  outcomeRepo;
191
319
  reviewEventRepo;
192
320
  scopeRepo;
193
- packRepo;
194
- packRegistry;
195
- packsDir;
196
- constructor(config, options = {}) {
321
+ constructor(config) {
197
322
  const db = openDatabase(config);
198
323
  bootstrapDatabase(db);
199
324
  this.inputRepo = new InputRecordRepository(db);
@@ -205,11 +330,6 @@ export class ExperienceInteractionService {
205
330
  this.outcomeRepo = new OutcomeRecordRepository(db);
206
331
  this.reviewEventRepo = new ReviewEventRepository(db);
207
332
  this.scopeRepo = new ScopeRepository(db);
208
- this.packRepo = new ExperiencePackRepository(db);
209
- this.packsDir = options.packsDir ?? `${config.dataDir}/packs`;
210
- this.packRegistry = new ExperiencePackRegistry({
211
- packsDir: this.packsDir
212
- });
213
333
  }
214
334
  inspectRecord(record) {
215
335
  if (!record) {
@@ -248,22 +368,13 @@ export class ExperienceInteractionService {
248
368
  : "inject";
249
369
  const outcomeRecord = taskRun?.id ? this.outcomeRepo.listByTaskRunId(taskRun.id)[0] : undefined;
250
370
  const latestAutomaticFeedback = reviewEvents.find((event) => event.source === "automatic");
251
- const scopePackStatus = this.inspectScopePackStatusByScopeId(record.scope_id);
252
- const matchedPackIds = new Set();
253
- for (const activation of scopePackStatus.activations.filter((entry) => entry.enabled)) {
254
- const version = activation.pinnedVersion ?? activation.currentVersion;
255
- for (const membership of this.packRepo.listMemberships(activation.packId, version)) {
256
- if (selectedNodeIds.includes(membership.node_id)) {
257
- matchedPackIds.add(activation.packId);
258
- }
259
- }
260
- }
261
371
  const autoFeedbackReason = inferAutoFeedbackReason({
262
372
  explicitReason: injectionEvent?.attribution_reason,
263
373
  autoFeedback,
264
374
  intervention,
265
375
  outcome: record.outcome_signal
266
376
  });
377
+ const decisionExplanation = buildDecisionExplanation({ intervention, scorecard });
267
378
  return {
268
379
  sessionId: record.session_id,
269
380
  scopeId: record.scope_id,
@@ -278,6 +389,9 @@ export class ExperienceInteractionService {
278
389
  hints: injectedNodes.map((node) => node.compact_hint),
279
390
  evidence: record.evidence,
280
391
  scorecard,
392
+ decisionExplanation,
393
+ trustSummary: buildTrustSummary({ scorecard, injectedNodes: injectedNodes.map(toNodeSummary) }),
394
+ retrievalNotes: buildRetrievalNotes(scorecard),
281
395
  timeline: buildLatestTimeline({
282
396
  record,
283
397
  taskRunCreatedAt: taskRun?.created_at,
@@ -290,14 +404,110 @@ export class ExperienceInteractionService {
290
404
  autoFeedback,
291
405
  autoFeedbackCreatedAt: latestAutomaticFeedback?.created_at
292
406
  }),
293
- activePacks: scopePackStatus.activations.filter((activation) => activation.enabled),
294
- matchedPacks: scopePackStatus.activations.filter((activation) => matchedPackIds.has(activation.packId)),
407
+ learningStatus: taskRun?.learning_status,
408
+ learningReason: taskRun?.learning_reason,
295
409
  summary: record.task_summary,
296
410
  createdAt: record.created_at
297
411
  };
298
412
  }
299
- inspectLast() {
300
- return this.inspectRecord(this.inputRepo.getLatest());
413
+ inspectInjectionEvent(event) {
414
+ if (!event) {
415
+ return undefined;
416
+ }
417
+ const taskRun = event.session_id ? this.taskRunRepo.getLatestBySessionId(event.session_id) : undefined;
418
+ const latestRecord = event.session_id ? this.inputRepo.getLatestBySessionId(event.session_id) : undefined;
419
+ if (latestRecord) {
420
+ return this.inspectRecord(latestRecord);
421
+ }
422
+ const injectedNodes = this.nodeRepo.listByIds(event.injected_node_ids);
423
+ const reviewEvents = taskRun?.id ? this.reviewEventRepo.listByTaskRunId(taskRun.id) : [];
424
+ const autoFeedback = summarizeAutomaticFeedback(reviewEvents);
425
+ const latestAutomaticFeedback = reviewEvents.find((reviewEvent) => reviewEvent.source === "automatic");
426
+ const intervention = !event.delivered
427
+ ? event.delivery_mode === "holdout"
428
+ ? "holdout"
429
+ : "shadow"
430
+ : "inject";
431
+ const outcomeRecord = taskRun?.id ? this.outcomeRepo.listByTaskRunId(taskRun.id)[0] : undefined;
432
+ const outcome = outcomeRecord?.outcome_signal ??
433
+ (taskRun?.final_status === "success" ? "success" : taskRun?.final_status === "failure" ? "failure" : "unknown");
434
+ const summary = event.task_summary ?? taskRun?.task_summary ?? "Latest injection event";
435
+ const decisionExplanation = buildDecisionExplanation({ intervention, scorecard: event.scorecard });
436
+ return {
437
+ sessionId: event.session_id,
438
+ scopeId: event.scope_id,
439
+ taskType: event.task_type,
440
+ intervention,
441
+ deliveryMode: event.delivery_mode,
442
+ delivered: event.delivered,
443
+ autoFeedback,
444
+ autoFeedbackReason: inferAutoFeedbackReason({
445
+ explicitReason: event.attribution_reason,
446
+ autoFeedback,
447
+ intervention,
448
+ outcome
449
+ }),
450
+ outcome,
451
+ injectedNodes: injectedNodes.map(toNodeSummary),
452
+ hints: injectedNodes.map((node) => node.compact_hint),
453
+ evidence: [],
454
+ scorecard: event.scorecard,
455
+ decisionExplanation,
456
+ trustSummary: buildTrustSummary({ scorecard: event.scorecard, injectedNodes: injectedNodes.map(toNodeSummary) }),
457
+ retrievalNotes: buildRetrievalNotes(event.scorecard),
458
+ timeline: buildLatestTimeline({
459
+ record: {
460
+ record_id: `injection:${event.injection_id}`,
461
+ scope_id: event.scope_id,
462
+ session_id: event.session_id,
463
+ task_type: event.task_type,
464
+ task_summary: summary,
465
+ outcome_signal: outcome,
466
+ context_summary: taskRun?.context_summary,
467
+ evidence: [],
468
+ injected_node_ids: event.injected_node_ids,
469
+ created_at: event.created_at
470
+ },
471
+ taskRunCreatedAt: taskRun?.created_at,
472
+ outcomeCreatedAt: outcomeRecord?.created_at,
473
+ outcomeSummary: outcomeRecord?.summary,
474
+ injectionCreatedAt: event.created_at,
475
+ intervention,
476
+ delivered: event.delivered,
477
+ injectedCount: injectedNodes.length,
478
+ autoFeedback,
479
+ autoFeedbackCreatedAt: latestAutomaticFeedback?.created_at
480
+ }),
481
+ learningStatus: taskRun?.learning_status,
482
+ learningReason: taskRun?.learning_reason,
483
+ summary,
484
+ createdAt: event.created_at
485
+ };
486
+ }
487
+ inspectLast(cwd = process.cwd()) {
488
+ const scope = resolveScope(cwd);
489
+ const latestRecordInScope = this.inputRepo.getLatestByScope(scope.scope_id);
490
+ const latestInjectionInScope = this.injectionRepo.getLatestByScope(scope.scope_id);
491
+ const latestScopedInspection = compareIsoDesc(latestInjectionInScope?.created_at, latestRecordInScope?.created_at) < 0
492
+ ? this.inspectInjectionEvent(latestInjectionInScope)
493
+ : this.inspectRecord(latestRecordInScope);
494
+ if (latestScopedInspection) {
495
+ return latestScopedInspection;
496
+ }
497
+ const latestRecord = this.inputRepo.getLatest();
498
+ const latestInjection = this.injectionRepo.getLatest();
499
+ return compareIsoDesc(latestInjection?.created_at, latestRecord?.created_at) < 0
500
+ ? this.inspectInjectionEvent(latestInjection)
501
+ : this.inspectRecord(latestRecord);
502
+ }
503
+ inspectLatestInjected(cwd = process.cwd()) {
504
+ const scope = resolveScope(cwd);
505
+ const latestScopedInjected = this.inputRepo.getLatestInjectedByScope(scope.scope_id);
506
+ if (latestScopedInjected) {
507
+ return this.inspectRecord(latestScopedInjected);
508
+ }
509
+ const latestInjected = this.inputRepo.getLatestInjected();
510
+ return latestInjected ? this.inspectRecord(latestInjected) : undefined;
301
511
  }
302
512
  inspectRecent(options = {}) {
303
513
  return this.inputRepo.listRecent(options).map((record) => ({
@@ -319,149 +529,6 @@ export class ExperienceInteractionService {
319
529
  const node = this.nodeRepo.getById(nodeId);
320
530
  return node ? toNodeDetail(node) : undefined;
321
531
  }
322
- listPacks() {
323
- return this.packRepo.listPacks().map((pack) => ({
324
- packId: pack.pack_id,
325
- name: pack.name,
326
- description: pack.description,
327
- owner: pack.owner,
328
- status: pack.status,
329
- currentVersion: pack.current_version,
330
- createdAt: pack.created_at,
331
- updatedAt: pack.updated_at,
332
- publishedAt: pack.published_at,
333
- rolledBackAt: pack.rolled_back_at,
334
- scopeHints: pack.scope_hints,
335
- taskFamilies: pack.task_families,
336
- hostCompatibility: pack.host_compatibility
337
- }));
338
- }
339
- inspectPack(packId) {
340
- const pack = this.packRepo.getPack(packId);
341
- if (!pack) {
342
- return undefined;
343
- }
344
- const manifest = this.packRegistry.readVersionManifest(packId, pack.current_version);
345
- const nodes = this.packRegistry.readVersionNodes(packId, pack.current_version);
346
- return {
347
- packId: pack.pack_id,
348
- name: pack.name,
349
- description: pack.description,
350
- owner: pack.owner,
351
- status: pack.status,
352
- currentVersion: pack.current_version,
353
- createdAt: pack.created_at,
354
- updatedAt: pack.updated_at,
355
- publishedAt: pack.published_at,
356
- rolledBackAt: pack.rolled_back_at,
357
- scopeHints: pack.scope_hints,
358
- taskFamilies: pack.task_families,
359
- hostCompatibility: pack.host_compatibility,
360
- manifest,
361
- nodeIds: nodes.map((node) => node.id),
362
- compiledArtifacts: this.packRegistry.listCompiledArtifacts(packId),
363
- compileStatus: this.packRegistry.getCompileStatus(packId, pack.current_version),
364
- activations: this.packRepo.listActivationsByPack(packId).map((activation) => ({
365
- scopeId: activation.scope_id,
366
- enabled: activation.enabled,
367
- pinnedVersion: activation.pinned_version,
368
- updatedAt: activation.updated_at
369
- }))
370
- };
371
- }
372
- enablePack(args) {
373
- const scopeId = resolveScope(args.cwd).scope_id;
374
- const pack = this.packRepo.getPack(args.packId);
375
- if (!pack) {
376
- throw new Error(`Unknown experience pack: ${args.packId}`);
377
- }
378
- const existing = this.packRepo.listActivations(scopeId).find((activation) => activation.pack_id === args.packId);
379
- const updatedAt = nowIso();
380
- this.packRepo.upsertActivation({
381
- scope_id: scopeId,
382
- pack_id: args.packId,
383
- enabled: true,
384
- pinned_version: pack.current_version,
385
- created_at: existing?.created_at ?? updatedAt,
386
- updated_at: updatedAt
387
- });
388
- return {
389
- scopeId,
390
- packId: args.packId,
391
- enabled: true,
392
- pinnedVersion: pack.current_version,
393
- updatedAt
394
- };
395
- }
396
- disablePack(args) {
397
- const scopeId = resolveScope(args.cwd).scope_id;
398
- const existing = this.packRepo.listActivations(scopeId).find((activation) => activation.pack_id === args.packId);
399
- const updatedAt = nowIso();
400
- this.packRepo.upsertActivation({
401
- scope_id: scopeId,
402
- pack_id: args.packId,
403
- enabled: false,
404
- pinned_version: existing?.pinned_version,
405
- created_at: existing?.created_at ?? updatedAt,
406
- updated_at: updatedAt
407
- });
408
- return {
409
- scopeId,
410
- packId: args.packId,
411
- enabled: false,
412
- pinnedVersion: existing?.pinned_version,
413
- updatedAt
414
- };
415
- }
416
- compilePack(args) {
417
- return compilePack({
418
- packsDir: this.packsDir,
419
- packId: args.packId,
420
- version: args.version,
421
- target: args.target
422
- });
423
- }
424
- inspectPackDeploymentStatus(args) {
425
- return deployCompiledPack({
426
- packsDir: this.packsDir,
427
- packId: args.packId,
428
- version: args.version,
429
- target: args.target,
430
- repoPath: args.repoPath,
431
- statusOnly: true
432
- });
433
- }
434
- deployPackPreview(args) {
435
- return deployCompiledPack({
436
- packsDir: this.packsDir,
437
- packId: args.packId,
438
- version: args.version,
439
- target: args.target,
440
- repoPath: args.repoPath,
441
- dryRun: true
442
- });
443
- }
444
- inspectScopePackStatus(cwd = process.cwd()) {
445
- return this.inspectScopePackStatusByScopeId(resolveScope(cwd).scope_id);
446
- }
447
- inspectScopePackStatusByScopeId(scopeId) {
448
- const activations = this.packRepo
449
- .listActivations(scopeId)
450
- .map((activation) => toScopePackActivation(this.packRepo.getPack(activation.pack_id), activation))
451
- .filter((activation) => Boolean(activation));
452
- const publishedPackIds = Array.from(new Set(activations
453
- .filter((activation) => activation.status === "published" || activation.status === "rolled_back")
454
- .map((activation) => activation.packId)));
455
- const compiler = summarizePackCompiler(this.packRegistry, publishedPackIds, new Map(activations
456
- .filter((activation) => activation.status === "published" || activation.status === "rolled_back")
457
- .map((activation) => [activation.packId, activation.currentVersion])));
458
- return {
459
- scopeId,
460
- enabledCount: activations.filter((activation) => activation.enabled).length,
461
- activations,
462
- compiler
463
- };
464
- }
465
532
  listNodesByState(state) {
466
533
  return this.nodeRepo.listByState(state).map(toNodeSummary);
467
534
  }
@@ -474,7 +541,7 @@ export class ExperienceInteractionService {
474
541
  buildLearningSummary(scopeId) {
475
542
  const candidateStates = ["pending", "distilled", "failed", "discarded"];
476
543
  const jobStates = ["pending", "processing", "succeeded", "failed", "discarded"];
477
- const nodeStates = ["candidate", "active", "cooling", "retired"];
544
+ const nodeStates = ["candidate", "priority_candidate", "active", "cooling", "retired"];
478
545
  const nodeSources = ["explicit_provider", "rule", "disabled"];
479
546
  const attributionReasons = [
480
547
  "success_outcome",
@@ -492,10 +559,6 @@ export class ExperienceInteractionService {
492
559
  : candidateStates.flatMap((state) => this.candidateRepo.listByLifecycleState(state));
493
560
  const candidateIds = new Set(candidates.map((candidate) => candidate.id));
494
561
  const jobs = jobStates.flatMap((state) => this.jobRepo.listByStatus(state)).filter((job) => candidateIds.has(job.candidate_id));
495
- const publishedPacks = this.packRepo
496
- .listPacks()
497
- .filter((pack) => pack.status === "published" || pack.status === "rolled_back");
498
- const compiler = summarizePackCompiler(this.packRegistry, publishedPacks.map((pack) => pack.pack_id), new Map(publishedPacks.map((pack) => [pack.pack_id, pack.current_version])));
499
562
  const effectiveness = {
500
563
  decisions: scopeId ? this.injectionRepo.countByScope(scopeId) : this.injectionRepo.count(),
501
564
  live: scopeId ? this.injectionRepo.countByScopeAndDeliveryMode(scopeId, "live") : this.injectionRepo.countByDeliveryMode("live"),
@@ -530,7 +593,6 @@ export class ExperienceInteractionService {
530
593
  outcomes: scopeId ? this.outcomeRepo.countByScope(scopeId) : this.outcomeRepo.count(),
531
594
  reviews: scopeId ? this.reviewEventRepo.countByNodeScope(scopeId) : this.reviewEventRepo.count()
532
595
  },
533
- compiler,
534
596
  latestRecordCreatedAt: latestRecord?.created_at
535
597
  };
536
598
  }
@@ -539,11 +601,6 @@ export class ExperienceInteractionService {
539
601
  const latestRecord = this.inputRepo.getLatestByScope(scope.scope_id);
540
602
  const latest = latestRecord ? this.inspectRecord(latestRecord) : undefined;
541
603
  const learning = this.buildLearningSummary(scope.scope_id);
542
- const scopePackStatus = this.inspectScopePackStatusByScopeId(scope.scope_id);
543
- const activePacks = scopePackStatus.activations.filter((activation) => activation.enabled);
544
- const matchedPackIds = new Set(latest?.matchedPacks.map((pack) => pack.packId) ?? []);
545
- const matchedPacks = scopePackStatus.activations.filter((activation) => matchedPackIds.has(activation.packId));
546
- const deployments = this.inspectRepoDeploymentStatuses(scopePackStatus.activations, cwd);
547
604
  return buildRepoSummary({
548
605
  scope: {
549
606
  scopeId: scope.scope_id,
@@ -551,14 +608,12 @@ export class ExperienceInteractionService {
551
608
  rootPath: scope.root_path
552
609
  },
553
610
  latest: latest && latest.scopeId === scope.scope_id ? latest : undefined,
554
- learning,
555
- activePacks,
556
- matchedPacks,
557
- deployments
611
+ learning
558
612
  });
559
613
  }
560
- inspectFirstValueReadiness() {
561
- const summary = this.inspectLearningSummary();
614
+ inspectFirstValueReadiness(cwd = process.cwd()) {
615
+ const scope = resolveScope(cwd);
616
+ const summary = this.buildLearningSummary(scope.scope_id);
562
617
  const rawRecords = summary.runtime.records;
563
618
  const taskRuns = summary.runtime.taskRuns;
564
619
  const candidates = summary.candidates.pending;
@@ -582,8 +637,68 @@ export class ExperienceInteractionService {
582
637
  nextStep
583
638
  };
584
639
  }
585
- feedbackLast(feedback) {
586
- const record = this.inputRepo.getLatestInjected();
640
+ inspectDecisionHealth(cwd = process.cwd(), limit = 10) {
641
+ const scope = resolveScope(cwd);
642
+ const recentRecords = this.inputRepo.listRecentByScope(scope.scope_id, limit);
643
+ let recentInjects = 0;
644
+ let recentConservativeInjects = 0;
645
+ let recentSkips = 0;
646
+ let recentFastPathActivations = 0;
647
+ let recentRerankParticipations = 0;
648
+ let recentQueryRewriteUsages = 0;
649
+ const scopedNodes = this.nodeRepo.listByScope(scope.scope_id);
650
+ const recentNodes = scopedNodes.slice(0, limit);
651
+ const currentPriorityCandidates = scopedNodes.filter((node) => node.state === "priority_candidate").length;
652
+ const recentConvergedUpdates = recentNodes.filter((node) => node.merge_decision === "UPDATE").length;
653
+ const recentPriorityPromotions = recentNodes.filter((node) => node.priority_promotion_applied).length;
654
+ let lastDecisionMode;
655
+ for (const record of recentRecords) {
656
+ const injectionEvent = record.session_id
657
+ ? this.injectionRepo.getLatestBySessionId(record.session_id)
658
+ : undefined;
659
+ const decisionMode = injectionEvent?.mode ?? "skip";
660
+ if (!lastDecisionMode) {
661
+ lastDecisionMode = decisionMode;
662
+ }
663
+ if (decisionMode === "inject") {
664
+ recentInjects += 1;
665
+ }
666
+ else if (decisionMode === "inject_conservative") {
667
+ recentConservativeInjects += 1;
668
+ }
669
+ else {
670
+ recentSkips += 1;
671
+ }
672
+ if (injectionEvent?.scorecard?.fastPathApplied) {
673
+ recentFastPathActivations += 1;
674
+ }
675
+ if (injectionEvent?.scorecard?.topCandidates?.some((candidate) => typeof candidate.rerankScore === "number")) {
676
+ recentRerankParticipations += 1;
677
+ }
678
+ if (injectionEvent?.scorecard?.queryRewriteApplied) {
679
+ recentQueryRewriteUsages += 1;
680
+ }
681
+ }
682
+ return {
683
+ scopeId: scope.scope_id,
684
+ recentDecisions: recentRecords.length,
685
+ recentInjects,
686
+ recentConservativeInjects,
687
+ recentSkips,
688
+ recentFastPathActivations,
689
+ recentRerankParticipations,
690
+ recentQueryRewriteUsages,
691
+ currentPriorityCandidates,
692
+ recentConvergedUpdates,
693
+ recentPriorityPromotions,
694
+ lastDecisionMode
695
+ };
696
+ }
697
+ feedbackLast(feedback, cwd) {
698
+ const scope = cwd ? resolveScope(cwd) : undefined;
699
+ const record = scope
700
+ ? this.inputRepo.getLatestInjectedByScope(scope.scope_id) ?? this.inputRepo.getLatestInjected()
701
+ : this.inputRepo.getLatestInjected();
587
702
  if (!record) {
588
703
  return {
589
704
  status: "not_found",
@@ -672,35 +787,5 @@ export class ExperienceInteractionService {
672
787
  state: updated.state
673
788
  };
674
789
  }
675
- inspectRepoDeploymentStatuses(activations, repoPath) {
676
- const published = activations.find((activation) => activation.enabled && (activation.status === "published" || activation.status === "rolled_back"));
677
- if (!published) {
678
- return REPO_SUMMARY_TARGETS.map((target) => ({
679
- target,
680
- status: "missing"
681
- }));
682
- }
683
- return REPO_SUMMARY_TARGETS.map((target) => {
684
- try {
685
- const result = this.inspectPackDeploymentStatus({
686
- packId: published.packId,
687
- version: published.pinnedVersion ?? published.currentVersion,
688
- target,
689
- repoPath
690
- });
691
- return {
692
- target,
693
- status: result.deploymentStatus,
694
- destination: result.destinationPath
695
- };
696
- }
697
- catch {
698
- return {
699
- target,
700
- status: "missing"
701
- };
702
- }
703
- });
704
- }
705
790
  }
706
791
  //# sourceMappingURL=service.js.map