@evomap/evolver-core 2.0.0-beta.1 → 2.0.0-beta.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 (110) hide show
  1. package/assets/gep/genes.jsonl +5 -0
  2. package/dist/algo/candidateAssembly.js +23 -14
  3. package/dist/algo/capabilityCandidates.d.ts +2 -0
  4. package/dist/algo/capabilityCandidates.js +5 -0
  5. package/dist/algo/conversationSniffer.d.ts +18 -0
  6. package/dist/algo/conversationSniffer.js +132 -0
  7. package/dist/algo/cycleEngine.d.ts +11 -0
  8. package/dist/algo/cycleEngine.js +14 -6
  9. package/dist/algo/cycleFailureClassifier.d.ts +1 -1
  10. package/dist/algo/cycleFailureClassifier.js +13 -5
  11. package/dist/algo/geneIntake.d.ts +14 -4
  12. package/dist/algo/geneIntake.js +37 -9
  13. package/dist/algo/geneSelection.d.ts +18 -1
  14. package/dist/algo/geneSelection.js +42 -18
  15. package/dist/algo/index.d.ts +2 -0
  16. package/dist/algo/index.js +2 -0
  17. package/dist/algo/memoryGraph.d.ts +62 -0
  18. package/dist/algo/memoryGraph.js +86 -0
  19. package/dist/algo/orchestrator.d.ts +3 -0
  20. package/dist/algo/orchestrator.js +14 -2
  21. package/dist/assetstore/assetSidecarRecords.d.ts +23 -0
  22. package/dist/assetstore/assetSidecarRecords.js +142 -0
  23. package/dist/assetstore/assetSidecarRecovery.d.ts +48 -0
  24. package/dist/assetstore/assetSidecarRecovery.js +288 -0
  25. package/dist/assetstore/assetStoreHealth.d.ts +75 -0
  26. package/dist/assetstore/assetStoreHealth.js +277 -0
  27. package/dist/assetstore/assetStoreLayout.d.ts +2 -0
  28. package/dist/assetstore/assetStoreLayout.js +6 -0
  29. package/dist/assetstore/assetStoreStorage.d.ts +42 -0
  30. package/dist/assetstore/assetStoreStorage.js +318 -0
  31. package/dist/assetstore/assetSyncLedger.d.ts +32 -0
  32. package/dist/assetstore/assetSyncLedger.js +66 -0
  33. package/dist/assetstore/index.d.ts +5 -1
  34. package/dist/assetstore/index.js +5 -1
  35. package/dist/assetstore/localJsonl.d.ts +10 -2
  36. package/dist/assetstore/localJsonl.js +93 -37
  37. package/dist/assetstore/pendingSignals.js +3 -1
  38. package/dist/assetstore/provenance.d.ts +20 -2
  39. package/dist/assetstore/provenance.js +92 -56
  40. package/dist/assetstore/provider.d.ts +2 -0
  41. package/dist/assetstore/reviewFilter.js +5 -2
  42. package/dist/assetstore/reviewLedger.d.ts +14 -2
  43. package/dist/assetstore/reviewLedger.js +78 -43
  44. package/dist/assetstore/seedGenes.d.ts +3 -0
  45. package/dist/assetstore/seedGenes.js +134 -0
  46. package/dist/benchmark/antiGeneBenchmark.d.ts +2 -0
  47. package/dist/benchmark/antiGeneBenchmark.js +11 -1
  48. package/dist/benchmark/antiGeneImpact.d.ts +16 -0
  49. package/dist/benchmark/antiGeneImpact.js +34 -0
  50. package/dist/benchmark/antiGeneRollout.d.ts +2 -0
  51. package/dist/benchmark/antiGeneRollout.js +13 -1
  52. package/dist/benchmark/index.d.ts +2 -1
  53. package/dist/benchmark/index.js +2 -1
  54. package/dist/benchmark/triggerShift.d.ts +62 -0
  55. package/dist/benchmark/triggerShift.js +106 -0
  56. package/dist/events/eventArchive.d.ts +65 -0
  57. package/dist/events/eventArchive.js +343 -0
  58. package/dist/events/eventStore.js +2 -12
  59. package/dist/events/ingest.d.ts +1 -1
  60. package/dist/events/ingest.js +2 -0
  61. package/dist/events/paths.d.ts +1 -1
  62. package/dist/events/paths.js +2 -2
  63. package/dist/events/public.d.ts +3 -1
  64. package/dist/events/public.js +2 -1
  65. package/dist/events/retention.d.ts +24 -1
  66. package/dist/events/retention.js +133 -37
  67. package/dist/exec/autoExec.d.ts +6 -1
  68. package/dist/exec/autoExec.js +34 -0
  69. package/dist/exec/autonomousCycle.d.ts +2 -0
  70. package/dist/exec/autonomousCycle.js +5 -0
  71. package/dist/exec/claudeBridge.d.ts +7 -2
  72. package/dist/exec/claudeBridge.js +115 -14
  73. package/dist/exec/prompt.js +9 -0
  74. package/dist/exec/runnerRegistry.d.ts +64 -18
  75. package/dist/exec/runnerRegistry.js +341 -38
  76. package/dist/exec/selfPrObfuscation.d.ts +2 -1
  77. package/dist/exec/selfPrObfuscation.js +19 -6
  78. package/dist/hub/agentDirectory.d.ts +90 -0
  79. package/dist/hub/agentDirectory.js +104 -0
  80. package/dist/hub/bindings.js +23 -3
  81. package/dist/hub/capability.d.ts +14 -2
  82. package/dist/hub/fake.d.ts +4 -2
  83. package/dist/hub/fake.js +3 -2
  84. package/dist/hub/index.d.ts +1 -0
  85. package/dist/hub/index.js +1 -0
  86. package/dist/mailbox/catalog.js +3 -0
  87. package/dist/mailbox/ipcServer.js +2 -2
  88. package/dist/mailbox/store.d.ts +14 -0
  89. package/dist/mailbox/store.js +44 -2
  90. package/dist/material/consumer.js +9 -6
  91. package/dist/material/index.d.ts +1 -0
  92. package/dist/material/index.js +1 -0
  93. package/dist/material/materialArchive.d.ts +81 -0
  94. package/dist/material/materialArchive.js +466 -0
  95. package/dist/material/materialStore.d.ts +1 -0
  96. package/dist/material/materialStore.js +6 -13
  97. package/dist/ops/savingsCore.js +1 -2
  98. package/dist/ops/selfUpdate.d.ts +10 -1
  99. package/dist/ops/selfUpdate.js +64 -15
  100. package/dist/schema/common.d.ts +1 -1
  101. package/dist/schema/common.js +1 -1
  102. package/dist/schema/material.d.ts +5 -5
  103. package/dist/trace/trajectoryExport.js +2 -2
  104. package/dist/util/fileLock.d.ts +19 -2
  105. package/dist/util/fileLock.js +166 -31
  106. package/dist/wire/geneHints.d.ts +42 -1
  107. package/dist/wire/geneHints.js +52 -1
  108. package/dist/wire/index.d.ts +14 -2
  109. package/dist/wire/index.js +1 -1
  110. package/package.json +6 -1
@@ -8,8 +8,12 @@
8
8
  //
9
9
  // Default (no record) = approved/eligible: the only writer of quarantine records is `--distill`; cycle-self-
10
10
  // produced and v1-migrated genes are never quarantined, so the explore→prove loop is untouched for them.
11
- import { appendFileSync, existsSync, readFileSync, mkdirSync, statSync } from 'node:fs';
12
11
  import { join, dirname } from 'node:path';
12
+ import { appendUtf8Durable, assertAssetStoreDirectory, ensureAssetStoreDirectory, readUtf8Regular, regularFileFingerprint, withAssetStoreLock, } from './assetStoreStorage.js';
13
+ import { assertTrustSidecarHealthy, parseReviewRecord, parseSidecarJsonl, } from './assetSidecarRecords.js';
14
+ function immutableRecord(record) {
15
+ return Object.freeze({ ...record });
16
+ }
13
17
  /**
14
18
  * Append-only JSONL sidecar at <baseDir>/review.jsonl. Default for an asset with NO record = approved (eligible):
15
19
  * only auto-distilled drafts are quarantined here; everything else is eligible by default.
@@ -22,11 +26,14 @@ import { join, dirname } from 'node:path';
22
26
  export class ReviewLedger {
23
27
  now;
24
28
  path;
29
+ lockPath;
25
30
  index = new Map();
26
- sig = ''; // `${mtimeMs}:${size}` of review.jsonl at the last index build; '' = never loaded / file absent
31
+ fileState = null;
27
32
  constructor(baseDir, now = Date.now) {
28
33
  this.now = now;
34
+ ensureAssetStoreDirectory(baseDir);
29
35
  this.path = join(baseDir, 'review.jsonl');
36
+ this.lockPath = join(baseDir, '.assetstore.lock');
30
37
  }
31
38
  static isHuman(s) { return s === 'approved' || s === 'rejected'; }
32
39
  /** Which record wins for an asset_id: a human decision beats a quarantine; otherwise the later one wins. */
@@ -37,43 +44,50 @@ export class ReviewLedger {
37
44
  return r; // human decision always wins (and later human beats earlier)
38
45
  return ReviewLedger.isHuman(existing.state) ? existing : r; // a quarantine replaces only a prior quarantine
39
46
  }
40
- // Reload-aware (not load-once): the resident `autoexec` daemon holds ONE ReviewLedger, yet an operator runs
41
- // `evolver review --approve` in a SEPARATE process. Re-read review.jsonl whenever its mtime+size changed, so a
42
- // live approval lifts a quarantined gene without restarting the daemon. The file is small and queried at most
43
- // once per cycle, so a statSync per query is cheap. (mtime alone can collide within one ms → pair it with size.)
44
- load() {
45
- if (!existsSync(this.path)) {
46
- this.index.clear();
47
- this.sig = '';
48
- return;
49
- }
50
- const st = statSync(this.path);
51
- const sig = `${st.mtimeMs}:${st.size}`;
52
- if (sig === this.sig)
53
- return; // unchanged since last read → cached index is current
54
- this.index.clear();
55
- for (const line of readFileSync(this.path, 'utf8').split('\n')) {
56
- if (!line.trim())
57
- continue;
58
- try {
59
- const r = JSON.parse(line);
60
- if (r.assetId)
61
- this.index.set(r.assetId, ReviewLedger.keep(this.index.get(r.assetId), r));
47
+ rebuildIndex(state) {
48
+ const next = new Map();
49
+ const raw = state === 'missing' ? null : readUtf8Regular(this.path);
50
+ if (raw !== null) {
51
+ const parsed = parseSidecarJsonl(raw, parseReviewRecord);
52
+ assertTrustSidecarHealthy('review', parsed);
53
+ for (const record of parsed.records) {
54
+ const frozen = immutableRecord(record);
55
+ next.set(record.assetId, ReviewLedger.keep(next.get(record.assetId), frozen));
62
56
  }
63
- catch { /* skip corrupt line */ }
64
57
  }
65
- this.sig = sig;
58
+ this.index.clear();
59
+ for (const [assetId, record] of next)
60
+ this.index.set(assetId, record);
61
+ this.fileState = state;
62
+ }
63
+ refreshUnderLock() {
64
+ const state = regularFileFingerprint(this.path);
65
+ if (state !== this.fileState)
66
+ this.rebuildIndex(state);
67
+ }
68
+ withFreshRead(read) {
69
+ assertAssetStoreDirectory(dirname(this.path));
70
+ return withAssetStoreLock(this.lockPath, () => {
71
+ this.refreshUnderLock();
72
+ return read(this.index);
73
+ });
74
+ }
75
+ appendUnderLock(full) {
76
+ appendUtf8Durable(this.path, `${JSON.stringify(full)}\n`);
77
+ const frozen = immutableRecord(full);
78
+ this.index.set(full.assetId, ReviewLedger.keep(this.index.get(full.assetId), frozen));
79
+ this.fileState = regularFileFingerprint(this.path);
80
+ return this.index.get(full.assetId);
66
81
  }
67
82
  /** Record a review-state for an asset_id (append-only; the JSONL history is the audit trail). */
68
83
  mark(rec) {
69
- this.load();
70
84
  const full = { ...rec, at: rec.at ?? new Date(this.now()).toISOString() };
71
- mkdirSync(dirname(this.path), { recursive: true });
72
- appendFileSync(this.path, `${JSON.stringify(full)}\n`);
73
- this.index.set(full.assetId, ReviewLedger.keep(this.index.get(full.assetId), full));
74
- const st = statSync(this.path); // single stat: two separate calls could straddle a concurrent same-ms append
75
- this.sig = `${st.mtimeMs}:${st.size}`; // our own write is already indexed; don't force a re-read
76
- return full;
85
+ assertAssetStoreDirectory(dirname(this.path));
86
+ return withAssetStoreLock(this.lockPath, () => {
87
+ this.refreshUnderLock();
88
+ this.appendUnderLock(full);
89
+ return immutableRecord(full);
90
+ });
77
91
  }
78
92
  /** Quarantine an auto-distilled draft: its strategy must not enter a real run until reviewed. */
79
93
  quarantine(assetId, reason = 'auto-distilled — review before use') {
@@ -87,9 +101,19 @@ export class ReviewLedger {
87
101
  * approval is inert. Returns the surviving record (the existing decision, or the new quarantine).
88
102
  */
89
103
  quarantineIfAbsent(assetId, reason = 'auto-distilled — review before use') {
90
- this.load();
91
- const existing = this.index.get(assetId);
92
- return existing ?? this.quarantine(assetId, reason);
104
+ assertAssetStoreDirectory(dirname(this.path));
105
+ return withAssetStoreLock(this.lockPath, () => {
106
+ this.refreshUnderLock();
107
+ const existing = this.index.get(assetId);
108
+ if (existing)
109
+ return existing;
110
+ return this.appendUnderLock({
111
+ assetId,
112
+ state: 'quarantined',
113
+ reason,
114
+ at: new Date(this.now()).toISOString(),
115
+ });
116
+ });
93
117
  }
94
118
  /** Explicit, audited approval (who/why) — flips a draft to eligible. */
95
119
  approve(assetId, by, reason) {
@@ -100,8 +124,7 @@ export class ReviewLedger {
100
124
  return this.mark({ assetId, state: 'rejected', by, reason });
101
125
  }
102
126
  get(assetId) {
103
- this.load();
104
- return this.index.get(assetId) ?? null;
127
+ return this.withFreshRead((index) => index.get(assetId) ?? null);
105
128
  }
106
129
  /**
107
130
  * Every recorded review decision (resolved, reload-aware). The authoritative source of which assets are
@@ -109,13 +132,25 @@ export class ReviewLedger {
109
132
  * asset list, so a draft awaiting approval is never missed behind a store-list cutoff.
110
133
  */
111
134
  records() {
112
- this.load();
113
- return [...this.index.values()];
135
+ return [...this.snapshot().values()];
136
+ }
137
+ /** One linearizable review snapshot for bounded batch readers. */
138
+ snapshot() {
139
+ return this.withFreshRead((index) => new Map(index));
114
140
  }
115
141
  /** No record → approved (default eligible); a record → approved only when its state is 'approved'. */
116
142
  isApproved(assetId) {
117
- this.load();
118
- const r = this.index.get(assetId);
119
- return r ? r.state === 'approved' : true;
143
+ return this.withFreshRead((index) => {
144
+ const r = index.get(assetId);
145
+ return r ? r.state === 'approved' : true;
146
+ });
147
+ }
148
+ /**
149
+ * True only when a human approval record exists. Safety-sensitive consumers such as AntiGene warning
150
+ * injection must fail closed when review state is absent; unlike cycle/migrate-authored Genes, an unreviewed
151
+ * negative-memory asset must never inherit the ledger's backward-compatible "no record = eligible" default.
152
+ */
153
+ isExplicitlyApproved(assetId) {
154
+ return this.withFreshRead((index) => index.get(assetId)?.state === 'approved');
120
155
  }
121
156
  }
@@ -0,0 +1,3 @@
1
+ import { type Gene } from '../wire/index.js';
2
+ export declare const BUNDLED_HARNESS_SEED_GENES: readonly Gene[];
3
+ export declare function bundledHarnessSeedGenesJsonl(): string;
@@ -0,0 +1,134 @@
1
+ import { computeAssetId, SCHEMA_VERSION } from '../wire/index.js';
2
+ const HARNESS_SEED_GENE_DRAFTS = [
3
+ {
4
+ id: 'gene_seed_publish_feishu_doc',
5
+ category: 'innovate',
6
+ signals_match: [
7
+ 'publish_markdown_to_feishu',
8
+ 'create_feishu_doc',
9
+ 'export_report_to_feishu',
10
+ 'publish_results_to_lark_doc',
11
+ 'feishu_doc',
12
+ 'lark_doc',
13
+ ],
14
+ preconditions: [
15
+ 'lark-cli is installed and available on PATH.',
16
+ 'lark-cli auth status reports a ready user or bot identity.',
17
+ ],
18
+ strategy: [
19
+ 'Run lark-cli doctor and require an ok result before publishing.',
20
+ 'Use the Docs v2 API and avoid deprecated Docs v1 flags.',
21
+ 'Write long Markdown content to a temporary file and pass it as an @file input.',
22
+ 'Create or update the document with the intended user or bot identity, then return the document URL.',
23
+ 'Never overwrite local lark-cli credential files while preparing the document.',
24
+ ],
25
+ constraints: { max_files: 2, forbidden_paths: ['.git', 'node_modules', '~/.lark-cli/config.json'] },
26
+ validation: ['node --version'],
27
+ summary: 'Publish Markdown content as a Feishu/Lark document through the official CLI, using file-backed content and Docs v2.',
28
+ },
29
+ {
30
+ id: 'gene_seed_conventional_git_commit',
31
+ category: 'optimize',
32
+ signals_match: [
33
+ 'git_commit',
34
+ 'create_commit',
35
+ 'commit_changes',
36
+ 'conventional_commit',
37
+ 'stage_and_commit',
38
+ 'write_commit_message',
39
+ ],
40
+ preconditions: ['A git repository has staged or unstaged changes that should become one logical commit.'],
41
+ strategy: [
42
+ 'Inspect git status plus the staged or unstaged diff before choosing a commit shape.',
43
+ 'Pick a Conventional Commits type and optional scope from the actual changed files.',
44
+ 'Stage only the intended logical files and never stage credentials or unrelated changes.',
45
+ 'Write a present-tense imperative subject under 72 characters, with body or footer when needed.',
46
+ 'Do not amend, force-push, hard-reset, or bypass hooks without explicit operator approval.',
47
+ ],
48
+ constraints: { max_files: 50, forbidden_paths: ['.git', 'node_modules'] },
49
+ validation: ['node --version'],
50
+ summary: 'Create a focused Conventional Commits-style git commit from the real diff while avoiding secrets and unrelated changes.',
51
+ },
52
+ {
53
+ id: 'gene_seed_poll_bugbot_review',
54
+ category: 'optimize',
55
+ signals_match: [
56
+ 'poll_bugbot',
57
+ 'bugbot_review',
58
+ 'wait_for_ci_review',
59
+ 'pr_review_gate',
60
+ 'cursor_bugbot',
61
+ 'pr_opened',
62
+ ],
63
+ preconditions: ['An open GitHub PR exists and Cursor Bugbot is expected to review it.'],
64
+ strategy: [
65
+ 'Poll the Cursor Bugbot check by name until it reaches a terminal conclusion or the operator timeout is reached.',
66
+ 'Treat SUCCESS as mergeable only when required checks are green and there are no unresolved Bugbot comments.',
67
+ 'Treat NEUTRAL as not passed; fetch and summarize Cursor Bugbot inline comments for the operator.',
68
+ 'On FAILURE or ACTION_REQUIRED, surface the findings and do not merge automatically.',
69
+ 'Only squash-merge when the operator explicitly asked for merge and the review gate is clean.',
70
+ ],
71
+ constraints: { max_files: 1, forbidden_paths: ['.git', 'node_modules'] },
72
+ validation: ['node --version'],
73
+ summary: 'Wait for Cursor Bugbot on a GitHub PR and gate merge decisions on the actual check conclusion and comments.',
74
+ },
75
+ {
76
+ id: 'gene_seed_gateway_timeout_recovery',
77
+ category: 'repair',
78
+ signals_match: [
79
+ 'gateway_timeout',
80
+ 'upstream_timeout',
81
+ 'http_524',
82
+ 'http_504',
83
+ 'request_timed_out',
84
+ 'retry_on_timeout',
85
+ ],
86
+ preconditions: ['A tool call, fetch, subagent request, or long command returned a gateway-class timeout.'],
87
+ strategy: [
88
+ 'Treat gateway timeout as transient or size-driven until one bounded recovery attempt proves otherwise.',
89
+ 'Retry the exact same operation once, then stop retrying the monolithic call.',
90
+ 'If the retry also times out, split the work by file, directory, endpoint, record, section, or time window.',
91
+ 'Run independent slices in parallel when safe and merge the results.',
92
+ 'Apply the same decomposition recursively only to the slice that still times out.',
93
+ ],
94
+ constraints: { max_files: 1, forbidden_paths: ['.git', 'node_modules'] },
95
+ validation: ['node --version'],
96
+ summary: 'Recover from gateway or upstream timeouts by retrying once, then decomposing the work instead of looping the same large call.',
97
+ },
98
+ {
99
+ id: 'gene_seed_github_webhook_listener',
100
+ category: 'innovate',
101
+ signals_match: [
102
+ 'github_webhook_listener',
103
+ 'bugbot_webhook',
104
+ 'passive_pr_notifications',
105
+ 'notify_when_bugbot_finishes',
106
+ 'webhook_tunnel',
107
+ 'github_pr_inbox',
108
+ ],
109
+ preconditions: ['A developer machine can run a local listener and an outbound tunnel for GitHub webhook delivery.'],
110
+ strategy: [
111
+ 'Use an HMAC-validated local listener for GitHub webhook payloads and deduplicate delivery ids.',
112
+ 'Expose the listener through an outbound-only tunnel rather than an inbound public port.',
113
+ 'Write vetted notifications into a local inbox with restrictive permissions; never execute webhook payloads.',
114
+ 'On session start, re-fetch PR state through GitHub before surfacing any inbox item.',
115
+ 'Rotate webhook secrets periodically and immediately after suspected exposure.',
116
+ ],
117
+ constraints: { max_files: 20, forbidden_paths: ['.git', 'node_modules'] },
118
+ validation: ['node --version'],
119
+ summary: 'Deploy a local HMAC-validated GitHub webhook listener that turns PR and Bugbot events into safe operator notifications.',
120
+ },
121
+ ];
122
+ function materializeSeedGene(draft) {
123
+ const gene = {
124
+ type: 'Gene',
125
+ schema_version: SCHEMA_VERSION,
126
+ ...draft,
127
+ asset_id: '',
128
+ };
129
+ return { ...gene, asset_id: computeAssetId(gene) ?? '' };
130
+ }
131
+ export const BUNDLED_HARNESS_SEED_GENES = Object.freeze(HARNESS_SEED_GENE_DRAFTS.map((gene) => Object.freeze(materializeSeedGene(gene))));
132
+ export function bundledHarnessSeedGenesJsonl() {
133
+ return `${BUNDLED_HARNESS_SEED_GENES.map((gene) => JSON.stringify(gene)).join('\n')}\n`;
134
+ }
@@ -2,6 +2,7 @@ import type { AssetStoreProvider } from '../assetstore/provider.js';
2
2
  import { ReviewLedger } from '../assetstore/reviewLedger.js';
3
3
  import type { ReportEvent } from '../events/reports.js';
4
4
  import type { GepCategory } from '../wire/index.js';
5
+ import { type AntiGeneOverblockSummary } from './antiGeneImpact.js';
5
6
  export type AntiGeneBenchmarkArm = 'baseline' | 'antiGene';
6
7
  export type AntiGeneBenchmarkVerdict = 'anti_gene_better' | 'no_clear_improvement' | 'anti_gene_worse' | 'insufficient_samples';
7
8
  export interface AntiGeneBenchmarkOutcomeSpec {
@@ -58,6 +59,7 @@ export interface AntiGeneBenchmarkReport {
58
59
  antiGene: AntiGeneBenchmarkArmMetrics;
59
60
  missingExpectedWarnings: number;
60
61
  overblocked: number;
62
+ overblockedAntiGenes: AntiGeneOverblockSummary[];
61
63
  failureDelta: number;
62
64
  verdict: AntiGeneBenchmarkVerdict;
63
65
  taskResults: AntiGeneBenchmarkTaskResult[];
@@ -8,6 +8,7 @@ import { LineTooLargeError } from '../events/eventStore.js';
8
8
  import { CycleEngine } from '../algo/cycleEngine.js';
9
9
  import { runEvolutionCycle } from '../algo/orchestrator.js';
10
10
  import { makeGeneSelectionPoint } from '../algo/geneSelection.js';
11
+ import { parseOverblockedAntiGenes, summarizeOverblockedAntiGenes } from './antiGeneImpact.js';
11
12
  const ALL_ASSETS_LIMIT = Number.MAX_SAFE_INTEGER;
12
13
  const ASSET_KINDS = ['Gene', 'Capsule', 'EvolutionEvent', 'AntiGene'];
13
14
  function buildProblem(task, now) {
@@ -148,6 +149,7 @@ function buildReport(suite, taskResults, minSamples, minFailureDelta) {
148
149
  antiGene,
149
150
  missingExpectedWarnings: taskResults.reduce((sum, r) => sum + r.antiGene.missingExpectedWarnings.length, 0),
150
151
  overblocked: antiGene.overblocked,
152
+ overblockedAntiGenes: summarizeOverblockedAntiGenes(taskResults),
151
153
  failureDelta: baseline.failureRate - antiGene.failureRate,
152
154
  taskResults,
153
155
  };
@@ -225,13 +227,21 @@ function isReportPayload(value) {
225
227
  && typeof value.tasks === 'number'
226
228
  && typeof value.verdict === 'string';
227
229
  }
230
+ function normalizeReportPayload(value) {
231
+ const parsed = parseOverblockedAntiGenes(value.overblockedAntiGenes);
232
+ const truncated = typeof value.taskResultsTruncated === 'number' && value.taskResultsTruncated > 0;
233
+ return {
234
+ ...value,
235
+ overblockedAntiGenes: parsed ?? (truncated ? [] : summarizeOverblockedAntiGenes(value.taskResults ?? [])),
236
+ };
237
+ }
228
238
  export function antiGeneBenchmarkReportsFromEvents(events) {
229
239
  const out = [];
230
240
  for (const event of events) {
231
241
  if (event.type !== 'anti_gene.benchmark_result')
232
242
  continue;
233
243
  if (isReportPayload(event.payload))
234
- out.push(event.payload);
244
+ out.push(normalizeReportPayload(event.payload));
235
245
  }
236
246
  return out;
237
247
  }
@@ -0,0 +1,16 @@
1
+ export interface AntiGeneOverblockSummary {
2
+ antiGeneId: string;
3
+ count: number;
4
+ taskIds: string[];
5
+ }
6
+ interface AntiGeneImpactRunResult {
7
+ overblocked: boolean;
8
+ observedAntiWarnings: readonly string[];
9
+ }
10
+ interface AntiGeneImpactTaskResult {
11
+ taskId: string;
12
+ antiGene: AntiGeneImpactRunResult;
13
+ }
14
+ export declare function summarizeOverblockedAntiGenes(taskResults: readonly AntiGeneImpactTaskResult[]): AntiGeneOverblockSummary[];
15
+ export declare function parseOverblockedAntiGenes(value: unknown): AntiGeneOverblockSummary[] | null;
16
+ export {};
@@ -0,0 +1,34 @@
1
+ export function summarizeOverblockedAntiGenes(taskResults) {
2
+ const byId = new Map();
3
+ for (const task of taskResults) {
4
+ if (!task.antiGene.overblocked)
5
+ continue;
6
+ for (const antiGeneId of new Set(task.antiGene.observedAntiWarnings)) {
7
+ const row = byId.get(antiGeneId) ?? { count: 0, taskIds: new Set() };
8
+ row.count += 1;
9
+ row.taskIds.add(task.taskId);
10
+ byId.set(antiGeneId, row);
11
+ }
12
+ }
13
+ return [...byId.entries()]
14
+ .map(([antiGeneId, row]) => ({ antiGeneId, count: row.count, taskIds: [...row.taskIds].sort() }))
15
+ .sort((a, b) => b.count - a.count || a.antiGeneId.localeCompare(b.antiGeneId));
16
+ }
17
+ export function parseOverblockedAntiGenes(value) {
18
+ if (!Array.isArray(value))
19
+ return null;
20
+ const out = [];
21
+ for (const item of value) {
22
+ if (!item || typeof item !== 'object' || Array.isArray(item))
23
+ return null;
24
+ const row = item;
25
+ if (typeof row['antiGeneId'] !== 'string')
26
+ return null;
27
+ if (typeof row['count'] !== 'number' || !Number.isFinite(row['count']))
28
+ return null;
29
+ if (!Array.isArray(row['taskIds']) || !row['taskIds'].every((taskId) => typeof taskId === 'string'))
30
+ return null;
31
+ out.push({ antiGeneId: row['antiGeneId'], count: row['count'], taskIds: [...row['taskIds']] });
32
+ }
33
+ return out;
34
+ }
@@ -3,6 +3,7 @@ import { ReviewLedger } from '../assetstore/reviewLedger.js';
3
3
  import type { ReportEvent } from '../events/reports.js';
4
4
  import { type CycleInput } from '../algo/cycleEngine.js';
5
5
  import type { GepCategory } from '../wire/index.js';
6
+ import { type AntiGeneOverblockSummary } from './antiGeneImpact.js';
6
7
  export type AntiGeneRolloutArm = 'baseline' | 'antiGene';
7
8
  export type AntiGeneRolloutVerdict = 'anti_gene_better' | 'no_clear_improvement' | 'anti_gene_worse' | 'insufficient_samples';
8
9
  export interface AntiGeneRolloutTask {
@@ -50,6 +51,7 @@ export interface AntiGeneRolloutReport {
50
51
  antiGene: AntiGeneRolloutArmMetrics;
51
52
  missingExpectedWarnings: number;
52
53
  overblocked: number;
54
+ overblockedAntiGenes: AntiGeneOverblockSummary[];
53
55
  failureDelta: number;
54
56
  verdict: AntiGeneRolloutVerdict;
55
57
  taskResults: AntiGeneRolloutTaskResult[];
@@ -8,6 +8,7 @@ import { LineTooLargeError } from '../events/eventStore.js';
8
8
  import { CycleEngine } from '../algo/cycleEngine.js';
9
9
  import { runEvolutionCycle } from '../algo/orchestrator.js';
10
10
  import { makeGeneSelectionPoint } from '../algo/geneSelection.js';
11
+ import { parseOverblockedAntiGenes, summarizeOverblockedAntiGenes } from './antiGeneImpact.js';
11
12
  const ALL_ASSETS_LIMIT = Number.MAX_SAFE_INTEGER;
12
13
  const ASSET_KINDS = ['Gene', 'Capsule', 'EvolutionEvent', 'AntiGene'];
13
14
  function buildProblem(task, now) {
@@ -163,6 +164,7 @@ function buildReport(suite, rawTaskResults, minSamples, minFailureDelta) {
163
164
  antiGene,
164
165
  missingExpectedWarnings: taskResults.reduce((sum, r) => sum + r.antiGene.missingExpectedWarnings.length, 0),
165
166
  overblocked: antiGene.overblocked,
167
+ overblockedAntiGenes: summarizeOverblockedAntiGenes(taskResults),
166
168
  failureDelta: baseline.failureRate - antiGene.failureRate,
167
169
  taskResults,
168
170
  };
@@ -240,13 +242,23 @@ function isReportPayload(value) {
240
242
  && typeof value.tasks === 'number'
241
243
  && typeof value.verdict === 'string';
242
244
  }
245
+ function normalizeReportPayload(value) {
246
+ const parsed = parseOverblockedAntiGenes(value.overblockedAntiGenes);
247
+ const taskResults = normalizeTaskResults(value.taskResults ?? []);
248
+ const truncated = typeof value.taskResultsTruncated === 'number' && value.taskResultsTruncated > 0;
249
+ return {
250
+ ...value,
251
+ taskResults,
252
+ overblockedAntiGenes: parsed ?? (truncated ? [] : summarizeOverblockedAntiGenes(taskResults)),
253
+ };
254
+ }
243
255
  export function antiGeneRolloutReportsFromEvents(events) {
244
256
  const out = [];
245
257
  for (const event of events) {
246
258
  if (event.type !== 'anti_gene.rollout_result')
247
259
  continue;
248
260
  if (isReportPayload(event.payload))
249
- out.push(event.payload);
261
+ out.push(normalizeReportPayload(event.payload));
250
262
  }
251
263
  return out;
252
264
  }
@@ -2,4 +2,5 @@ export * from './benchmark.js';
2
2
  export * from './thesis.js';
3
3
  export * from './evolutionThesisSolver.js';
4
4
  export * from './antiGeneBenchmark.js';
5
- export * from './antiGeneRollout.js';
5
+ export * from './antiGeneRollout.js';
6
+ export * from './triggerShift.js';
@@ -2,4 +2,5 @@ export * from './benchmark.js';
2
2
  export * from './thesis.js';
3
3
  export * from './evolutionThesisSolver.js';
4
4
  export * from './antiGeneBenchmark.js';
5
- export * from './antiGeneRollout.js';
5
+ export * from './antiGeneRollout.js';
6
+ export * from './triggerShift.js';
@@ -0,0 +1,62 @@
1
+ export declare const TRIGGER_SHIFT_METHOD_VERSION = "trigger-shift-v1";
2
+ /** @experimental Shift dimension for a semantically paired replay task. */
3
+ export type TriggerShiftAxis = 'wrapper_trigger' | 'temporal_context' | 'instruction_phrasing';
4
+ /** @experimental One replay prompt variant in a trigger-shift pair. */
5
+ export interface TriggerShiftTask {
6
+ id: string;
7
+ prompt: string;
8
+ wrapperTrigger?: string;
9
+ temporalContext?: string;
10
+ instructionPhrasing?: string;
11
+ }
12
+ /** @experimental Same objective, paired across one trigger/context shift axis. */
13
+ export interface TriggerShiftPair {
14
+ id: string;
15
+ objectiveId: string;
16
+ axis: TriggerShiftAxis;
17
+ expectedDecision: string;
18
+ train: TriggerShiftTask;
19
+ shifted: TriggerShiftTask;
20
+ }
21
+ /** @experimental Policy output normalized for replay scoring. */
22
+ export interface TriggerShiftDecision {
23
+ label: string;
24
+ confidence?: number;
25
+ }
26
+ /** @experimental Replay-only policy seam; callers own any model/tool execution. */
27
+ export interface TriggerShiftPolicy {
28
+ id: string;
29
+ predict(task: TriggerShiftTask): TriggerShiftDecision;
30
+ }
31
+ /** @experimental One paired replay result with train/shifted rewards and gap. */
32
+ export interface TriggerShiftPairResult {
33
+ pairId: string;
34
+ objectiveId: string;
35
+ axis: TriggerShiftAxis;
36
+ trainTaskId: string;
37
+ shiftedTaskId: string;
38
+ expectedDecision: string;
39
+ trainDecision: string;
40
+ shiftedDecision: string;
41
+ trainReward: number;
42
+ shiftedReward: number;
43
+ gap: number;
44
+ }
45
+ /** @experimental Aggregate replay report; diagnostic only, not a selector input. */
46
+ export interface TriggerShiftReport {
47
+ methodVersion: string;
48
+ policyId: string;
49
+ pairs: number;
50
+ meanTrainReward: number;
51
+ meanShiftedReward: number;
52
+ meanGap: number;
53
+ maxGap: number;
54
+ rows: TriggerShiftPairResult[];
55
+ }
56
+ /**
57
+ * @experimental Offline trigger-shift replay evaluator. It returns inert report
58
+ * rows only: no trigger fires, no store writes, and no live selection updates.
59
+ */
60
+ export declare function evaluateTriggerShift(policy: TriggerShiftPolicy, pairs: readonly TriggerShiftPair[]): TriggerShiftReport;
61
+ /** @experimental Tiny public calibration/demo suite; not a live threshold. */
62
+ export declare function smallTriggerShiftSuite(): TriggerShiftPair[];