opencode-swarm 7.159.3 → 7.160.0

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/dist/background/delegation-health.d.ts +1 -1
  2. package/dist/cli/{coder-settlement-50p7dsya.js → coder-settlement-zn552sp9.js} +11 -10
  3. package/dist/cli/{config-doctor-a7drgs33.js → config-doctor-ddtdz2g0.js} +3 -3
  4. package/dist/cli/{core-madqgdsc.js → core-embasn2j.js} +2 -2
  5. package/dist/cli/{curation-policy-ray8h55z.js → curation-policy-agr6ga1s.js} +6 -6
  6. package/dist/cli/{curator-3sz7refy.js → curator-dgp6dxxn.js} +32 -31
  7. package/dist/cli/curator-llm-factory-7cwvmc46.js +67 -0
  8. package/dist/cli/{evidence-summary-service-cwyjkdsa.js → evidence-summary-service-sbnyann4.js} +14 -13
  9. package/dist/cli/{gate-evidence-95k5ap1z.js → gate-evidence-5v67a1dz.js} +7 -7
  10. package/dist/cli/guardrail-explain-2gwarnca.js +68 -0
  11. package/dist/cli/{guardrail-log-nerqk9qb.js → guardrail-log-zbnbff1y.js} +9 -6
  12. package/dist/cli/{guardrail-reset-chbc0mep.js → guardrail-reset-3cnz54hx.js} +32 -31
  13. package/dist/cli/{hive-promoter-4rtxhw7n.js → hive-promoter-7cr7t2kg.js} +32 -31
  14. package/dist/cli/{index-ebxmfr4j.js → index-1f4n43yz.js} +2 -2
  15. package/dist/cli/{index-zswy6xgb.js → index-3055dgzp.js} +7 -7
  16. package/dist/cli/{index-2jzmg2km.js → index-3yh737hc.js} +6 -6
  17. package/dist/cli/{index-0zerbq1v.js → index-4mm22fzj.js} +23 -14
  18. package/dist/cli/{index-5hfqc3h3.js → index-5m7qjj29.js} +24 -7
  19. package/dist/cli/{index-nvz4mp5w.js → index-7qv869f0.js} +6 -6
  20. package/dist/cli/{index-gk6gpe7r.js → index-85qjc7b2.js} +2 -2
  21. package/dist/cli/{index-xqjhbfbx.js → index-8bjv01t8.js} +4 -4
  22. package/dist/cli/{index-t3wk7q51.js → index-962jgzdq.js} +1 -1
  23. package/dist/cli/{index-nhhek8yw.js → index-9fspg1xw.js} +1 -1
  24. package/dist/cli/{index-wwpe4am7.js → index-9k3zc2t8.js} +7 -7
  25. package/dist/cli/{index-y2d381ce.js → index-9q6w7kke.js} +1 -1
  26. package/dist/cli/{index-av3gnekk.js → index-b5adxpy6.js} +1 -1
  27. package/dist/cli/{index-8fyebh3w.js → index-cn233hsy.js} +1 -1
  28. package/dist/cli/{index-qxpfmryw.js → index-d9s5hb0m.js} +4 -4
  29. package/dist/cli/{index-prknc0xn.js → index-dke4wzkn.js} +108 -44
  30. package/dist/cli/index-e75bzfgd.js +785 -0
  31. package/dist/cli/{index-mqfs5f8a.js → index-fzkx24q5.js} +2 -2
  32. package/dist/cli/{index-m9m3emns.js → index-gt8cxfbp.js} +2 -2
  33. package/dist/cli/{index-yt75r1er.js → index-h3re5c78.js} +1 -1
  34. package/dist/cli/{index-sn6mtxxc.js → index-jmths6jj.js} +34 -33
  35. package/dist/cli/{index-p3ym3y8b.js → index-kkfgygvn.js} +4 -4
  36. package/dist/cli/{index-9qw4sbg1.js → index-p39xem8b.js} +4 -4
  37. package/dist/cli/{index-6jp5yxye.js → index-py6bqwnb.js} +2 -2
  38. package/dist/cli/{index-515nwvdp.js → index-q6gjwy7m.js} +19 -10
  39. package/dist/cli/{index-v3ybemb1.js → index-rhc8jve7.js} +2 -2
  40. package/dist/cli/{index-txwx9p21.js → index-rnyt2z5r.js} +345 -97
  41. package/dist/cli/{index-ve0xj03m.js → index-rq7k03rk.js} +4 -4
  42. package/dist/cli/{index-2fq4a3vk.js → index-s7w428r3.js} +1 -1
  43. package/dist/cli/{index-h96xp4t0.js → index-sy5aeegg.js} +11 -2
  44. package/dist/cli/{index-fnxmg8sz.js → index-va1edd2n.js} +5 -5
  45. package/dist/cli/{index-rq4jcw64.js → index-w699ekk9.js} +4 -4
  46. package/dist/cli/{index-8zw60wya.js → index-wzx6atbv.js} +13 -4
  47. package/dist/cli/{index-xs32znyz.js → index-xs15netk.js} +1 -1
  48. package/dist/cli/{index-40t6s98s.js → index-yrar1ask.js} +3 -3
  49. package/dist/cli/{index-bxpqq9th.js → index-zjvf7edj.js} +3 -3
  50. package/dist/cli/index.js +32 -31
  51. package/dist/cli/{knowledge-escalator-f80n84ch.js → knowledge-escalator-zksm9833.js} +13 -12
  52. package/dist/cli/{knowledge-events-1d7v2krw.js → knowledge-events-ayta750w.js} +11 -10
  53. package/dist/cli/{knowledge-link-vm4caewt.js → knowledge-link-7c7fjnzc.js} +5 -5
  54. package/dist/cli/{knowledge-store-66yxeet7.js → knowledge-store-q5axge8b.js} +6 -6
  55. package/dist/cli/{knowledge-validator-p0xcx7jx.js → knowledge-validator-3rrbva2p.js} +8 -8
  56. package/dist/cli/{model-preflight-w85ecyxh.js → model-preflight-x9szw1v4.js} +1 -1
  57. package/dist/cli/{pending-delegations-vad1z71d.js → pending-delegations-kc7tzxg9.js} +5 -3
  58. package/dist/cli/{pr-review-reentry-authorization-kn3nny7h.js → pr-review-reentry-authorization-vag5gh50.js} +32 -31
  59. package/dist/cli/{pr-subscriptions-482gzvt6.js → pr-subscriptions-wbdw9a82.js} +5 -4
  60. package/dist/cli/{runner-sn7a6zd0.js → runner-p9wg6z34.js} +6 -6
  61. package/dist/cli/{scan-cursor-2v7995nb.js → scan-cursor-4fjwtt1k.js} +7 -7
  62. package/dist/cli/{schema-dy8fqycb.js → schema-d0nbzj5k.js} +2 -2
  63. package/dist/cli/{scope-persistence-baty62vx.js → scope-persistence-bckcbc8h.js} +10 -9
  64. package/dist/cli/{skill-generator-nwrvtqbz.js → skill-generator-kebhevet.js} +15 -14
  65. package/dist/cli/{telemetry-3rh94wv3.js → telemetry-gpr9s3sm.js} +3 -1
  66. package/dist/cli/{worktree-collision-ownership-cy24p5mr.js → worktree-collision-ownership-h8qswnrp.js} +6 -5
  67. package/dist/cli/{worktree-isolation-8jbx0s8d.js → worktree-isolation-q0kbmadz.js} +32 -31
  68. package/dist/config/context-window.d.ts +11 -4
  69. package/dist/config/evidence-schema.d.ts +6 -6
  70. package/dist/context-map/telemetry.d.ts +1 -1
  71. package/dist/health/learning-health.d.ts +356 -0
  72. package/dist/hooks/context-budget.d.ts +10 -1
  73. package/dist/hooks/curator.d.ts +2 -0
  74. package/dist/hooks/final-context-accounting.d.ts +2 -0
  75. package/dist/hooks/model-limits.d.ts +56 -25
  76. package/dist/hooks/skill-usage-pending.d.ts +1 -1
  77. package/dist/index.js +588 -587
  78. package/dist/observability/catalog.d.ts +2 -2
  79. package/dist/observability/ids.d.ts +17 -0
  80. package/dist/prm/trajectory-store.d.ts +1 -1
  81. package/dist/services/diagnose-service.d.ts +7 -0
  82. package/dist/services/status-service.d.ts +18 -0
  83. package/dist/telemetry.d.ts +52 -1
  84. package/dist/tools/context-status.d.ts +15 -1
  85. package/dist/tools/repo-graph/ontology.d.ts +6 -0
  86. package/dist/tools/repo-graph/pack-query.d.ts +36 -0
  87. package/dist/tools/repo-graph/types.d.ts +198 -1
  88. package/dist/tools/repo-graph.d.ts +5 -3
  89. package/package.json +1 -1
  90. package/dist/cli/curator-llm-factory-pyp5kmra.js +0 -66
  91. package/dist/cli/guardrail-explain-a13kbmrt.js +0 -67
@@ -0,0 +1,785 @@
1
+ // @bun
2
+ import {
3
+ atomicWriteSwarmFile
4
+ } from "./index-p39xem8b.js";
5
+ import {
6
+ pseudonymousRef,
7
+ resolveLineageSalt,
8
+ telemetry
9
+ } from "./index-dke4wzkn.js";
10
+
11
+ // src/health/learning-health.ts
12
+ import { promises as fs } from "fs";
13
+ import path from "path";
14
+ var HEALTH_SOURCES_TABLE = {
15
+ context_budget: {
16
+ producer: "src/hooks/context-budget.ts:199",
17
+ readers: [
18
+ "src/services/status-service.ts",
19
+ "src/services/diagnose-service.ts"
20
+ ],
21
+ alarms: ["headroom_dead_streak"]
22
+ },
23
+ model_limits: {
24
+ producer: "src/hooks/model-limits.ts:333",
25
+ readers: [
26
+ "src/tools/context-status.ts",
27
+ "src/services/status-service.ts",
28
+ "src/services/diagnose-service.ts"
29
+ ],
30
+ alarms: ["model_limit_fallback"]
31
+ },
32
+ knowledge_receipts: {
33
+ producer: "src/hooks/knowledge-receipt-ledger.ts:2219",
34
+ readers: [
35
+ "src/services/status-service.ts",
36
+ "src/services/diagnose-service.ts"
37
+ ],
38
+ alarms: ["retrieval_outcome_liveness"]
39
+ },
40
+ curator_compliance: {
41
+ producer: "src/hooks/curator.ts:1906",
42
+ readers: [
43
+ "src/services/status-service.ts",
44
+ "src/services/diagnose-service.ts"
45
+ ],
46
+ alarms: ["role_participation"]
47
+ },
48
+ promotion_evidence: {
49
+ producer: "src/hooks/promotion-evidence-store.ts:94",
50
+ readers: [
51
+ "src/services/status-service.ts",
52
+ "src/services/diagnose-service.ts"
53
+ ],
54
+ alarms: ["promoted_fixture_share"]
55
+ },
56
+ close_archive: {
57
+ producer: "src/commands/close.ts:754",
58
+ readers: [
59
+ "src/services/status-service.ts",
60
+ "src/services/diagnose-service.ts"
61
+ ],
62
+ alarms: ["archive_activity_mismatch"]
63
+ },
64
+ delegation_ledger: {
65
+ producer: "src/background/delegation-health.ts:581",
66
+ readers: [
67
+ "src/services/status-service.ts",
68
+ "src/services/diagnose-service.ts"
69
+ ],
70
+ alarms: ["recovery_ledger_pressure"]
71
+ },
72
+ store_health_events: {
73
+ producer: "src/events/core-events.ts:1616 (direct observeStoreHealth; one per audited store)",
74
+ readers: [
75
+ "src/services/status-service.ts",
76
+ "src/services/diagnose-service.ts"
77
+ ],
78
+ alarms: ["compaction_drop_coverage"]
79
+ }
80
+ };
81
+ var HEALTH_SOURCES = Object.freeze(HEALTH_SOURCES_TABLE);
82
+ var LEARNING_HEALTH_ALARM_CONFIG = Object.freeze({
83
+ headroom_dead_streak: Object.freeze({
84
+ windowMs: 600000,
85
+ raiseFacts: 3,
86
+ cooldownMs: 1800000,
87
+ severity: "warning"
88
+ }),
89
+ model_limit_fallback: Object.freeze({
90
+ windowMs: 1800000,
91
+ cooldownMs: 3600000,
92
+ severity: "warning"
93
+ }),
94
+ retrieval_outcome_liveness: Object.freeze({
95
+ windowMs: 1800000,
96
+ cooldownMs: 900000,
97
+ severity: "warning",
98
+ gap2StalenessMs: 600000
99
+ }),
100
+ role_participation: Object.freeze({
101
+ windowMs: 86400000,
102
+ raiseFacts: 2,
103
+ cooldownMs: 43200000,
104
+ severity: "warning"
105
+ }),
106
+ promoted_fixture_share: Object.freeze({
107
+ windowMs: 604800000,
108
+ raiseShare: 0.5,
109
+ clearShare: 0.3,
110
+ minEvidence: 4,
111
+ cooldownMs: 86400000,
112
+ severity: "warning"
113
+ }),
114
+ archive_activity_mismatch: Object.freeze({
115
+ windowMs: 0,
116
+ cooldownMs: 86400000,
117
+ severity: "warning"
118
+ }),
119
+ recovery_ledger_pressure: Object.freeze({
120
+ windowMs: 900000,
121
+ raisePct: 0.8,
122
+ clearPct: 0.7,
123
+ cooldownMs: 1800000,
124
+ severity: "warning"
125
+ }),
126
+ compaction_drop_coverage: Object.freeze({
127
+ windowMs: 3600000,
128
+ corruptRaise: 1,
129
+ droppedRaise: 100,
130
+ cooldownMs: 3600000,
131
+ severity: "warning"
132
+ })
133
+ });
134
+ var FIELD_RECEIPT_SOURCES = new Set([
135
+ "delegate",
136
+ "reviewer",
137
+ "architect",
138
+ "architect_marker",
139
+ "test_engineer"
140
+ ]);
141
+ var NON_FIELD_RECEIPT_SOURCES = new Set([
142
+ "phase_override",
143
+ "manual",
144
+ "migration",
145
+ "application_gate_staleness_release",
146
+ "application_gate_denial_limit_release",
147
+ "application_gate_session_reset_release",
148
+ "unknown"
149
+ ]);
150
+ var MAX_HEALTH_SCOPES = 64;
151
+ var MAX_FACTS_PER_SCOPE = 64;
152
+ var MAX_LIVENESS_GAPS = 64;
153
+ var MAX_TRANSITION_RING = 100;
154
+ var PERSIST_DEBOUNCE_MS = 5000;
155
+ var CLOCK_SKEW_TOLERANCE_MS = 300000;
156
+ var ARTIFACT_FILENAME = "learning-health.json";
157
+ var ARTIFACT_SCHEMA_VERSION = 1;
158
+ var STORE_HEALTH_KINDS = new Set([
159
+ "context_telemetry_health",
160
+ "skill_usage_health",
161
+ "core_events_health",
162
+ "shell_audit_health",
163
+ "trajectory_health",
164
+ "pr_subscription_health"
165
+ ]);
166
+ var STORE_LABEL_BY_KIND = Object.freeze({
167
+ context_telemetry_health: "context_telemetry",
168
+ skill_usage_health: "skill_usage",
169
+ core_events_health: "core_events",
170
+ shell_audit_health: "shell_audit",
171
+ trajectory_health: "trajectory",
172
+ pr_subscription_health: "pr_subscription"
173
+ });
174
+ var alarmScopes = new Map;
175
+ var livenessGaps = new Map;
176
+ var transitionRing = [];
177
+ var lastPersistAtByDirectory = new Map;
178
+ var recentPersistDirectories = [];
179
+ var MAX_TRACKED_DIRECTORIES = 16;
180
+ function rememberPersistDirectory(directory) {
181
+ if (recentPersistDirectories.includes(directory)) {
182
+ const idx = recentPersistDirectories.indexOf(directory);
183
+ recentPersistDirectories.splice(idx, 1);
184
+ }
185
+ recentPersistDirectories.push(directory);
186
+ while (recentPersistDirectories.length > MAX_TRACKED_DIRECTORIES) {
187
+ const evicted = recentPersistDirectories.shift();
188
+ if (evicted !== undefined)
189
+ lastPersistAtByDirectory.delete(evicted);
190
+ }
191
+ }
192
+ var _internals = {
193
+ now: () => Date.now(),
194
+ emitTelemetry: (payload) => {
195
+ telemetry.learningHealthAlarm(payload);
196
+ },
197
+ writeArtifact: (directory, contents) => atomicWriteSwarmFile(path.join(directory, ".swarm", ARTIFACT_FILENAME), contents),
198
+ readArtifact: async (directory) => {
199
+ try {
200
+ return await fs.readFile(path.join(directory, ".swarm", ARTIFACT_FILENAME), "utf-8");
201
+ } catch {
202
+ return null;
203
+ }
204
+ }
205
+ };
206
+ var projectRefCache = new Map;
207
+ function projectRef(directory) {
208
+ const cached = projectRefCache.get(directory);
209
+ if (cached !== undefined)
210
+ return cached;
211
+ const ref = pseudonymousRef(directory, resolveLineageSalt());
212
+ if (!projectRefCache.has(directory)) {
213
+ while (projectRefCache.size >= MAX_TRACKED_DIRECTORIES) {
214
+ const oldest = projectRefCache.keys().next().value;
215
+ if (oldest === undefined)
216
+ break;
217
+ projectRefCache.delete(oldest);
218
+ }
219
+ }
220
+ projectRefCache.set(directory, ref);
221
+ return ref;
222
+ }
223
+ function getScopeMap(alarm) {
224
+ let map = alarmScopes.get(alarm);
225
+ if (!map) {
226
+ map = new Map;
227
+ alarmScopes.set(alarm, map);
228
+ }
229
+ return map;
230
+ }
231
+ function ensureScope(alarm, scopeKey, now) {
232
+ const map = getScopeMap(alarm);
233
+ let scope = map.get(scopeKey);
234
+ if (scope)
235
+ return scope;
236
+ while (map.size >= MAX_HEALTH_SCOPES) {
237
+ const oldest = map.keys().next().value;
238
+ if (oldest === undefined)
239
+ break;
240
+ if (oldest === scopeKey)
241
+ break;
242
+ map.delete(oldest);
243
+ }
244
+ scope = {
245
+ status: "idle",
246
+ severity: LEARNING_HEALTH_ALARM_CONFIG[alarm].severity,
247
+ windowStartMs: now,
248
+ factCount: 0,
249
+ lastFactAtMs: 0,
250
+ raisedAtMs: 0,
251
+ lastEmitAtMs: 0,
252
+ transitionCount: 0,
253
+ facts: []
254
+ };
255
+ map.set(scopeKey, scope);
256
+ return scope;
257
+ }
258
+ function appendFact(scope, kind, atMs, windowMs) {
259
+ const duplicate = scope.facts.some((f) => f.kind === kind && f.atMs === atMs);
260
+ if (!duplicate) {
261
+ scope.facts.push({ atMs, kind });
262
+ while (scope.facts.length > MAX_FACTS_PER_SCOPE)
263
+ scope.facts.shift();
264
+ }
265
+ const skewAdjusted = Math.min(atMs, _internals.now() + CLOCK_SKEW_TOLERANCE_MS);
266
+ scope.lastFactAtMs = Math.max(scope.lastFactAtMs, skewAdjusted);
267
+ if (skewAdjusted - scope.windowStartMs > windowMs) {
268
+ const edge = skewAdjusted - windowMs;
269
+ scope.facts = scope.facts.filter((f) => f.atMs >= edge);
270
+ scope.windowStartMs = edge;
271
+ }
272
+ scope.factCount = scope.facts.length;
273
+ }
274
+ function countFacts(scope, kind) {
275
+ let n = 0;
276
+ for (const fact of scope.facts)
277
+ if (fact.kind === kind)
278
+ n += 1;
279
+ return n;
280
+ }
281
+ var DETAIL_PAYLOAD_KEYS = Object.freeze({
282
+ sessionRef: "session_ref",
283
+ limitSource: "limit_source",
284
+ denominatorFallback: "denominator_fallback",
285
+ pressurePct: "pressure_pct",
286
+ sharePct: "share_pct",
287
+ fieldCount: "field_count",
288
+ nonFieldCount: "non_field_count",
289
+ gapType: "gap_type"
290
+ });
291
+ function emitTransition(alarm, scopeClass, scopeKey, transition, severity, detail, scope, now) {
292
+ scope.lastEmitAtMs = now;
293
+ scope.transitionCount += 1;
294
+ const payload = {
295
+ alarm,
296
+ transition,
297
+ severity,
298
+ scope_class: scopeClass,
299
+ window_ms: LEARNING_HEALTH_ALARM_CONFIG[alarm].windowMs,
300
+ coverage_facts: scope.factCount,
301
+ raise_facts: scope.raisedAtMs > 0 ? countRaiseFacts(alarm, scope) : 0,
302
+ age_ms: scope.raisedAtMs > 0 ? Math.max(0, now - scope.raisedAtMs) : 0
303
+ };
304
+ if (scopeClass === "session") {
305
+ payload.session_ref = scopeKey.split("/").pop();
306
+ }
307
+ for (const [key, value] of Object.entries(detail)) {
308
+ if (value === undefined)
309
+ continue;
310
+ const payloadKey = DETAIL_PAYLOAD_KEYS[key] ?? key;
311
+ payload[payloadKey] = value;
312
+ }
313
+ transitionRing.push({
314
+ alarm,
315
+ transition,
316
+ severity,
317
+ atMs: now,
318
+ coverageFacts: scope.factCount
319
+ });
320
+ while (transitionRing.length > MAX_TRANSITION_RING)
321
+ transitionRing.shift();
322
+ _internals.emitTelemetry(payload);
323
+ }
324
+ function countRaiseFacts(alarm, scope) {
325
+ switch (alarm) {
326
+ case "headroom_dead_streak":
327
+ return countFacts(scope, "dead");
328
+ case "role_participation":
329
+ return countFacts(scope, "gap");
330
+ case "promoted_fixture_share":
331
+ return countFacts(scope, "non_field");
332
+ case "compaction_drop_coverage":
333
+ return countFacts(scope, "corrupt") + countFacts(scope, "dropped");
334
+ default:
335
+ return scope.factCount;
336
+ }
337
+ }
338
+ function applyAlarmState(alarm, scopeClass, scopeKey, desired, severity, detail, directory, now) {
339
+ if (directory)
340
+ rememberPersistDirectory(directory);
341
+ const scope = ensureScope(alarm, scopeKey, now);
342
+ const config = LEARNING_HEALTH_ALARM_CONFIG[alarm];
343
+ if (desired === "active") {
344
+ scope.severity = severity;
345
+ if (scope.status === "idle") {
346
+ scope.status = "active";
347
+ scope.raisedAtMs = now;
348
+ emitTransition(alarm, scopeClass, scopeKey, "raised", severity, detail, scope, now);
349
+ schedulePersist(directory, now, true);
350
+ } else if (now - scope.lastEmitAtMs >= config.cooldownMs) {
351
+ emitTransition(alarm, scopeClass, scopeKey, "sustained", severity, detail, scope, now);
352
+ schedulePersist(directory, now, true);
353
+ }
354
+ return;
355
+ }
356
+ if (scope.status === "active") {
357
+ scope.status = "idle";
358
+ scope.severity = LEARNING_HEALTH_ALARM_CONFIG[alarm].severity;
359
+ emitTransition(alarm, scopeClass, scopeKey, "recovered", severity, detail, scope, now);
360
+ schedulePersist(directory, now, true);
361
+ }
362
+ }
363
+ function schedulePersist(directory, now, force) {
364
+ const target = directory ?? recentPersistDirectories.at(-1);
365
+ if (!target)
366
+ return;
367
+ const last = lastPersistAtByDirectory.get(target) ?? 0;
368
+ if (!force && now - last < PERSIST_DEBOUNCE_MS)
369
+ return;
370
+ lastPersistAtByDirectory.set(target, now);
371
+ persistLearningHealth(target).catch(() => {
372
+ return;
373
+ });
374
+ }
375
+ async function persistLearningHealth(directory) {
376
+ const artifact = {
377
+ schemaVersion: ARTIFACT_SCHEMA_VERSION,
378
+ updatedAtMs: _internals.now(),
379
+ alarms: {},
380
+ transitions: [...transitionRing]
381
+ };
382
+ for (const [alarm, scopes] of alarmScopes) {
383
+ if (scopes.size === 0)
384
+ continue;
385
+ const serialized = {};
386
+ for (const [key, scope] of scopes) {
387
+ serialized[key] = {
388
+ status: scope.status,
389
+ severity: scope.severity,
390
+ windowStartMs: scope.windowStartMs,
391
+ factCount: scope.factCount,
392
+ lastFactAtMs: scope.lastFactAtMs,
393
+ raisedAtMs: scope.raisedAtMs,
394
+ transitionCount: scope.transitionCount
395
+ };
396
+ }
397
+ artifact.alarms[alarm] = { scopes: serialized };
398
+ }
399
+ try {
400
+ await _internals.writeArtifact(directory, `${JSON.stringify(artifact)}
401
+ `);
402
+ } catch {}
403
+ }
404
+ async function rehydrateFromArtifact(directory) {
405
+ const raw = await _internals.readArtifact(directory);
406
+ if (!raw)
407
+ return;
408
+ let parsed;
409
+ try {
410
+ parsed = JSON.parse(raw);
411
+ } catch {
412
+ return;
413
+ }
414
+ if (parsed.schemaVersion !== ARTIFACT_SCHEMA_VERSION)
415
+ return;
416
+ const now = _internals.now();
417
+ if (transitionRing.length === 0 && Array.isArray(parsed.transitions)) {
418
+ for (const tr of parsed.transitions) {
419
+ if (!tr || typeof tr.alarm !== "string" || !Object.hasOwn(LEARNING_HEALTH_ALARM_CONFIG, tr.alarm) || typeof tr.atMs !== "number" || !Number.isFinite(tr.atMs)) {
420
+ continue;
421
+ }
422
+ transitionRing.push({
423
+ alarm: tr.alarm,
424
+ transition: tr.transition === "raised" || tr.transition === "sustained" || tr.transition === "recovered" ? tr.transition : "raised",
425
+ severity: tr.severity === "critical" ? "critical" : "warning",
426
+ atMs: tr.atMs,
427
+ coverageFacts: typeof tr.coverageFacts === "number" && Number.isFinite(tr.coverageFacts) ? tr.coverageFacts : 0
428
+ });
429
+ }
430
+ while (transitionRing.length > MAX_TRANSITION_RING)
431
+ transitionRing.shift();
432
+ }
433
+ for (const [alarm, section] of Object.entries(parsed.alarms ?? {})) {
434
+ if (!Object.hasOwn(LEARNING_HEALTH_ALARM_CONFIG, alarm))
435
+ continue;
436
+ if (!section?.scopes)
437
+ continue;
438
+ const map = getScopeMap(alarm);
439
+ for (const [key, persisted] of Object.entries(section.scopes)) {
440
+ if (map.has(key))
441
+ continue;
442
+ if (!isAdoptableScopeKey(key))
443
+ continue;
444
+ if (!persisted || persisted.status !== "idle" && persisted.status !== "active" || persisted.severity !== "warning" && persisted.severity !== "critical" || !isFiniteCounter(persisted.windowStartMs) || !isFiniteCounter(persisted.factCount) || !isFiniteCounter(persisted.lastFactAtMs) || !isFiniteCounter(persisted.raisedAtMs) || !isFiniteCounter(persisted.transitionCount)) {
445
+ continue;
446
+ }
447
+ map.set(key, {
448
+ status: persisted.status,
449
+ severity: persisted.severity,
450
+ windowStartMs: persisted.windowStartMs,
451
+ factCount: persisted.factCount,
452
+ lastFactAtMs: persisted.lastFactAtMs,
453
+ raisedAtMs: persisted.raisedAtMs,
454
+ lastEmitAtMs: now,
455
+ transitionCount: persisted.transitionCount,
456
+ facts: []
457
+ });
458
+ }
459
+ }
460
+ }
461
+ function isAdoptableScopeKey(key) {
462
+ if (typeof key !== "string" || key.length === 0 || key.length > 200) {
463
+ return false;
464
+ }
465
+ return /^[0-9a-zA-Z:_.-/]+$/.test(key);
466
+ }
467
+ function isFiniteCounter(value) {
468
+ return typeof value === "number" && Number.isFinite(value) && value >= 0;
469
+ }
470
+ function livenessMap(directory) {
471
+ let map = livenessGaps.get(directory);
472
+ if (!map) {
473
+ if (livenessGaps.size >= MAX_TRACKED_DIRECTORIES) {
474
+ let victim;
475
+ let victimSize = Number.POSITIVE_INFINITY;
476
+ for (const [dir, entry] of livenessGaps) {
477
+ if (entry.size < victimSize) {
478
+ victim = dir;
479
+ victimSize = entry.size;
480
+ }
481
+ }
482
+ if (victim !== undefined)
483
+ livenessGaps.delete(victim);
484
+ }
485
+ map = new Map;
486
+ livenessGaps.set(directory, map);
487
+ }
488
+ return map;
489
+ }
490
+ function evaluateLiveness(directory, now) {
491
+ const config = LEARNING_HEALTH_ALARM_CONFIG.retrieval_outcome_liveness;
492
+ const gaps = livenessMap(directory);
493
+ const ageOutAt = config.windowMs * 4;
494
+ for (const [key, gap] of gaps) {
495
+ if (now - gap.openedAtMs > ageOutAt)
496
+ gaps.delete(key);
497
+ }
498
+ const scopeKey = `${projectRef(directory)}/liveness`;
499
+ const _scope = ensureScope("retrieval_outcome_liveness", scopeKey, now);
500
+ let stalled = 0;
501
+ let oldestAgeMs = 0;
502
+ let stalledType;
503
+ for (const gap of gaps.values()) {
504
+ if (gap.eligibleAtMs > now)
505
+ continue;
506
+ const age = now - gap.openedAtMs;
507
+ if (age > config.windowMs) {
508
+ stalled += 1;
509
+ if (age > oldestAgeMs) {
510
+ oldestAgeMs = age;
511
+ stalledType = gap.gapType;
512
+ }
513
+ }
514
+ }
515
+ if (stalled > 0) {
516
+ applyAlarmState("retrieval_outcome_liveness", "project", scopeKey, "active", config.severity, { reason: "gap_open", gapType: stalledType }, directory, now);
517
+ } else if (stalled === 0 && _scope.status === "active") {
518
+ applyAlarmState("retrieval_outcome_liveness", "project", scopeKey, "idle", config.severity, {}, directory, now);
519
+ }
520
+ }
521
+ function observeReceiptTransition(input) {
522
+ try {
523
+ ensureLearningHealth();
524
+ const now = input.atMs ?? _internals.now();
525
+ const config = LEARNING_HEALTH_ALARM_CONFIG.retrieval_outcome_liveness;
526
+ const gaps = livenessMap(input.directory);
527
+ const gapKey1 = `m:${input.traceId}`;
528
+ const gapKey2 = `a:${input.traceId}`;
529
+ switch (input.kind) {
530
+ case "membership_committed": {
531
+ const existing = gaps.get(gapKey1);
532
+ if (!existing) {
533
+ while (gaps.size >= MAX_LIVENESS_GAPS) {
534
+ const oldest = gaps.keys().next().value;
535
+ if (oldest === undefined)
536
+ break;
537
+ gaps.delete(oldest);
538
+ }
539
+ gaps.set(gapKey1, {
540
+ gapType: "membership_to_terminal",
541
+ openedAtMs: now,
542
+ eligibleAtMs: now
543
+ });
544
+ }
545
+ break;
546
+ }
547
+ case "legacy_imported":
548
+ break;
549
+ case "terminal_committed": {
550
+ gaps.delete(gapKey1);
551
+ if (input.receiptOutcome === "applied") {
552
+ const committedAt = input.membershipCommittedAtMs ?? now;
553
+ const existing = gaps.get(gapKey2);
554
+ if (!existing) {
555
+ while (gaps.size >= MAX_LIVENESS_GAPS) {
556
+ const oldest = gaps.keys().next().value;
557
+ if (oldest === undefined)
558
+ break;
559
+ gaps.delete(oldest);
560
+ }
561
+ gaps.set(gapKey2, {
562
+ gapType: "terminal_to_application",
563
+ openedAtMs: now,
564
+ eligibleAtMs: Math.max(now, committedAt + config.gap2StalenessMs)
565
+ });
566
+ }
567
+ } else {
568
+ gaps.delete(gapKey2);
569
+ }
570
+ break;
571
+ }
572
+ case "application_marker_committed": {
573
+ if (input.receiptSource === "architect_marker") {
574
+ gaps.delete(gapKey2);
575
+ }
576
+ break;
577
+ }
578
+ case "gate_release_committed":
579
+ case "phase_closed":
580
+ case "authorized_transition_committed": {
581
+ gaps.delete(gapKey1);
582
+ gaps.delete(gapKey2);
583
+ break;
584
+ }
585
+ default:
586
+ break;
587
+ }
588
+ evaluateLiveness(input.directory, now);
589
+ } catch {}
590
+ }
591
+ var PARTICIPATION_GAP_TYPES = new Set([
592
+ "missing_reviewer",
593
+ "missing_retro",
594
+ "missing_sme"
595
+ ]);
596
+ var EXPECTED_ROLES_BY_GAP_TYPE = Object.freeze({
597
+ missing_reviewer: Object.freeze(["reviewer"]),
598
+ missing_sme: Object.freeze(["sme"]),
599
+ missing_retro: Object.freeze(["reviewer", "architect"])
600
+ });
601
+ function normalizeParticipationAgent(agent) {
602
+ const stripped = agent.includes("_") ? agent.split("_").pop() ?? agent : agent;
603
+ return stripped.trim().toLowerCase();
604
+ }
605
+ function observeCuratorCompliance(input) {
606
+ try {
607
+ ensureLearningHealth();
608
+ const now = _internals.now();
609
+ const scopeKey = `${projectRef(input.directory)}/participation`;
610
+ const scope = ensureScope("role_participation", scopeKey, now);
611
+ const config = LEARNING_HEALTH_ALARM_CONFIG.role_participation;
612
+ for (const type of input.gapTypes) {
613
+ if (!PARTICIPATION_GAP_TYPES.has(type))
614
+ continue;
615
+ appendFact(scope, `gap:${type}`, now, config.windowMs);
616
+ }
617
+ for (const agent of input.agentsUsed) {
618
+ appendFact(scope, `participated:${normalizeParticipationAgent(agent)}`, now, config.windowMs);
619
+ }
620
+ const gapFacts = scope.facts.filter((f) => f.kind.startsWith("gap:"));
621
+ let lastGapAtMs = 0;
622
+ const openTypes = new Set;
623
+ for (const fact of scope.facts) {
624
+ if (fact.kind.startsWith("gap:")) {
625
+ lastGapAtMs = Math.max(lastGapAtMs, fact.atMs);
626
+ openTypes.add(fact.kind.slice("gap:".length));
627
+ }
628
+ }
629
+ const matchingLateParticipation = scope.facts.some((fact) => {
630
+ if (!fact.kind.startsWith("participated:"))
631
+ return false;
632
+ if (fact.atMs < lastGapAtMs)
633
+ return false;
634
+ const agent = fact.kind.slice("participated:".length);
635
+ for (const type of openTypes) {
636
+ const expected = EXPECTED_ROLES_BY_GAP_TYPE[type];
637
+ if (expected?.includes(agent))
638
+ return true;
639
+ }
640
+ return false;
641
+ });
642
+ if (gapFacts.length >= config.raiseFacts && !matchingLateParticipation) {
643
+ applyAlarmState("role_participation", "project", scopeKey, "active", config.severity, { reason: "eligible_role_gap", phase: input.phase }, input.directory, now);
644
+ } else if (matchingLateParticipation) {
645
+ applyAlarmState("role_participation", "project", scopeKey, "idle", config.severity, {}, input.directory, now);
646
+ }
647
+ } catch {}
648
+ }
649
+ function observeCloseArchive(input) {
650
+ try {
651
+ ensureLearningHealth();
652
+ const now = _internals.now();
653
+ const scopeKey = `${projectRef(input.directory)}/archive`;
654
+ const scope = ensureScope("archive_activity_mismatch", scopeKey, now);
655
+ const config = LEARNING_HEALTH_ALARM_CONFIG.archive_activity_mismatch;
656
+ const mismatch = (!input.archiveValid || input.archiveEmpty) && input.activityPredictsContent;
657
+ appendFact(scope, mismatch ? "mismatch" : "ok", now, config.windowMs || 1);
658
+ const detail = {
659
+ reason: mismatch ? input.archiveEmpty ? "archive_empty_with_activity" : "archive_invalid_with_activity" : undefined
660
+ };
661
+ if (mismatch) {
662
+ applyAlarmState("archive_activity_mismatch", "project", scopeKey, "active", config.severity, detail, input.directory, now);
663
+ } else {
664
+ applyAlarmState("archive_activity_mismatch", "project", scopeKey, "idle", config.severity, {}, input.directory, now);
665
+ }
666
+ } catch {}
667
+ }
668
+ function observeDelegationLedgerPressure(input) {
669
+ try {
670
+ ensureLearningHealth();
671
+ const now = _internals.now();
672
+ const scopeKey = `${projectRef(input.directory)}/ledger`;
673
+ const scope = ensureScope("recovery_ledger_pressure", scopeKey, now);
674
+ const config = LEARNING_HEALTH_ALARM_CONFIG.recovery_ledger_pressure;
675
+ const badBand = input.band === "compact-overdue" || input.band === "fail-closed";
676
+ const high = badBand || input.pressurePct >= config.raisePct;
677
+ const low = !badBand && input.pressurePct < config.clearPct && !input.uncertain;
678
+ appendFact(scope, high ? "high" : "ok", now, config.windowMs);
679
+ const detail = {
680
+ pressurePct: Math.round(input.pressurePct * 100),
681
+ band: input.band,
682
+ reason: input.uncertain ? "recovery_observation_uncertain" : undefined
683
+ };
684
+ if (high) {
685
+ applyAlarmState("recovery_ledger_pressure", "project", scopeKey, "active", config.severity, detail, input.directory, now);
686
+ } else if (low) {
687
+ applyAlarmState("recovery_ledger_pressure", "project", scopeKey, "idle", config.severity, detail, input.directory, now);
688
+ }
689
+ } catch {}
690
+ }
691
+ function observeStoreHealth(input) {
692
+ try {
693
+ observeStoreHealthEvent(input.kind, input.payload, input.directory);
694
+ } catch {}
695
+ }
696
+ function observeStoreHealthEvent(event, data, directory) {
697
+ ensureLearningHealth();
698
+ if (!STORE_HEALTH_KINDS.has(event))
699
+ return;
700
+ const store = STORE_LABEL_BY_KIND[event];
701
+ if (!store)
702
+ return;
703
+ const corrupt = Math.max(toCount(data.corrupt_count), toCount(data.corrupt));
704
+ const dropped = Math.max(toCount(data.dropped_count), toCount(data.dropped), toCount(data.dropped_audit_count));
705
+ const retained = toCount(data.retained_count);
706
+ const accepted = Math.max(toCount(data.accepted_count), toCount(data.accepted));
707
+ const now = _internals.now();
708
+ const scopeKey = directory ? `${projectRef(directory)}/store/${store}` : `store/${store}`;
709
+ const scope = ensureScope("compaction_drop_coverage", scopeKey, now);
710
+ const config = LEARNING_HEALTH_ALARM_CONFIG.compaction_drop_coverage;
711
+ if (corrupt > 0) {
712
+ appendFact(scope, "corrupt", now, config.windowMs);
713
+ } else if (dropped >= config.droppedRaise) {
714
+ appendFact(scope, "dropped", now, config.windowMs);
715
+ } else {
716
+ appendFact(scope, "clean", now, config.windowMs);
717
+ }
718
+ const corruptFacts = countFacts(scope, "corrupt");
719
+ const droppedFacts = countFacts(scope, "dropped");
720
+ const cleanFacts = countFacts(scope, "clean");
721
+ const detail = {
722
+ store,
723
+ dropped,
724
+ corrupt,
725
+ retained,
726
+ accepted
727
+ };
728
+ if (corruptFacts >= config.corruptRaise || droppedFacts > 0) {
729
+ applyAlarmState("compaction_drop_coverage", "project", scopeKey, "active", droppedFacts > 0 ? "critical" : "warning", detail, directory, now);
730
+ } else if (cleanFacts > 0 && corruptFacts === 0 && droppedFacts === 0) {
731
+ applyAlarmState("compaction_drop_coverage", "project", scopeKey, "idle", "warning", detail, directory, now);
732
+ }
733
+ }
734
+ function toCount(value) {
735
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
736
+ }
737
+ function ensureLearningHealth() {}
738
+ async function readLearningHealth(directory) {
739
+ ensureLearningHealth();
740
+ try {
741
+ await rehydrateFromArtifact(directory);
742
+ const now = _internals.now();
743
+ evaluateLiveness(directory, now);
744
+ const scopeOwner = projectRef(directory);
745
+ const active = [];
746
+ for (const [alarm, scopes] of alarmScopes) {
747
+ for (const [key, scope] of scopes) {
748
+ if (scope.status !== "active")
749
+ continue;
750
+ const owned = key.startsWith(`${scopeOwner}/`) || key.startsWith("u/") || key.startsWith("store/");
751
+ if (!owned)
752
+ continue;
753
+ active.push({
754
+ alarm,
755
+ severity: scope.severity,
756
+ scopeClass: scopeClassFor(alarm),
757
+ scopeRef: key.split("/").pop() ?? key,
758
+ ageMs: scope.raisedAtMs > 0 ? Math.max(0, now - scope.raisedAtMs) : 0,
759
+ coverageFacts: scope.factCount,
760
+ transitionCount: scope.transitionCount
761
+ });
762
+ }
763
+ }
764
+ schedulePersist(directory, now, false);
765
+ return {
766
+ activeAlarms: active,
767
+ totalTransitions: transitionRing.length,
768
+ updatedAtMs: now
769
+ };
770
+ } catch {
771
+ return { activeAlarms: [], totalTransitions: 0, updatedAtMs: 0 };
772
+ }
773
+ }
774
+ function scopeClassFor(alarm) {
775
+ switch (alarm) {
776
+ case "headroom_dead_streak":
777
+ return "session";
778
+ case "model_limit_fallback":
779
+ return "identity";
780
+ default:
781
+ return "project";
782
+ }
783
+ }
784
+
785
+ export { observeReceiptTransition, observeCuratorCompliance, observeCloseArchive, observeDelegationLedgerPressure, observeStoreHealth, readLearningHealth };