scientify 1.13.6 → 2.1.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 (100) hide show
  1. package/README.en.md +371 -0
  2. package/README.md +167 -356
  3. package/dist/index.d.ts +8 -2
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +131 -122
  6. package/dist/index.js.map +1 -1
  7. package/dist/src/cli/research.d.ts +1 -6
  8. package/dist/src/cli/research.d.ts.map +1 -1
  9. package/dist/src/cli/research.js +227 -123
  10. package/dist/src/cli/research.js.map +1 -1
  11. package/dist/src/commands/metabolism-status.d.ts +3 -3
  12. package/dist/src/commands/metabolism-status.d.ts.map +1 -1
  13. package/dist/src/commands/metabolism-status.js +72 -75
  14. package/dist/src/commands/metabolism-status.js.map +1 -1
  15. package/dist/src/commands.d.ts +1 -1
  16. package/dist/src/commands.d.ts.map +1 -1
  17. package/dist/src/commands.js +0 -55
  18. package/dist/src/commands.js.map +1 -1
  19. package/dist/src/hooks/cron-skill-inject.d.ts +6 -7
  20. package/dist/src/hooks/cron-skill-inject.d.ts.map +1 -1
  21. package/dist/src/hooks/cron-skill-inject.js +6 -15
  22. package/dist/src/hooks/cron-skill-inject.js.map +1 -1
  23. package/dist/src/hooks/research-mode.d.ts +1 -1
  24. package/dist/src/hooks/research-mode.d.ts.map +1 -1
  25. package/dist/src/hooks/research-mode.js +24 -101
  26. package/dist/src/hooks/research-mode.js.map +1 -1
  27. package/dist/src/hooks/scientify-signature.d.ts +1 -1
  28. package/dist/src/hooks/scientify-signature.d.ts.map +1 -1
  29. package/dist/src/hooks/scientify-signature.js +2 -5
  30. package/dist/src/hooks/scientify-signature.js.map +1 -1
  31. package/dist/src/knowledge-state/render.d.ts +1 -9
  32. package/dist/src/knowledge-state/render.d.ts.map +1 -1
  33. package/dist/src/knowledge-state/render.js +33 -187
  34. package/dist/src/knowledge-state/render.js.map +1 -1
  35. package/dist/src/knowledge-state/store.d.ts.map +1 -1
  36. package/dist/src/knowledge-state/store.js +65 -1100
  37. package/dist/src/knowledge-state/store.js.map +1 -1
  38. package/dist/src/knowledge-state/types.d.ts +0 -76
  39. package/dist/src/knowledge-state/types.d.ts.map +1 -1
  40. package/dist/src/literature/subscription-state.d.ts +0 -2
  41. package/dist/src/literature/subscription-state.d.ts.map +1 -1
  42. package/dist/src/literature/subscription-state.js +7 -1375
  43. package/dist/src/literature/subscription-state.js.map +1 -1
  44. package/dist/src/research-subscriptions/constants.d.ts +1 -1
  45. package/dist/src/research-subscriptions/constants.js +1 -1
  46. package/dist/src/research-subscriptions/cron-client.d.ts +1 -1
  47. package/dist/src/research-subscriptions/cron-client.d.ts.map +1 -1
  48. package/dist/src/research-subscriptions/delivery.d.ts +1 -1
  49. package/dist/src/research-subscriptions/delivery.d.ts.map +1 -1
  50. package/dist/src/research-subscriptions/handlers.d.ts +1 -1
  51. package/dist/src/research-subscriptions/handlers.d.ts.map +1 -1
  52. package/dist/src/research-subscriptions/handlers.js +10 -20
  53. package/dist/src/research-subscriptions/handlers.js.map +1 -1
  54. package/dist/src/research-subscriptions/parse.d.ts.map +1 -1
  55. package/dist/src/research-subscriptions/parse.js +0 -25
  56. package/dist/src/research-subscriptions/parse.js.map +1 -1
  57. package/dist/src/research-subscriptions/prompt.d.ts +1 -1
  58. package/dist/src/research-subscriptions/prompt.d.ts.map +1 -1
  59. package/dist/src/research-subscriptions/prompt.js +195 -244
  60. package/dist/src/research-subscriptions/prompt.js.map +1 -1
  61. package/dist/src/research-subscriptions/types.d.ts +1 -3
  62. package/dist/src/research-subscriptions/types.d.ts.map +1 -1
  63. package/dist/src/templates/bootstrap.d.ts.map +1 -1
  64. package/dist/src/templates/bootstrap.js +32 -19
  65. package/dist/src/templates/bootstrap.js.map +1 -1
  66. package/dist/src/tools/arxiv-download.d.ts +1 -2
  67. package/dist/src/tools/arxiv-download.d.ts.map +1 -1
  68. package/dist/src/tools/arxiv-search.d.ts +1 -2
  69. package/dist/src/tools/arxiv-search.d.ts.map +1 -1
  70. package/dist/src/tools/github-search-tool.d.ts +1 -2
  71. package/dist/src/tools/github-search-tool.d.ts.map +1 -1
  72. package/dist/src/tools/openalex-search.d.ts +1 -2
  73. package/dist/src/tools/openalex-search.d.ts.map +1 -1
  74. package/dist/src/tools/openreview-lookup.d.ts +1 -2
  75. package/dist/src/tools/openreview-lookup.d.ts.map +1 -1
  76. package/dist/src/tools/paper-browser.d.ts +1 -2
  77. package/dist/src/tools/paper-browser.d.ts.map +1 -1
  78. package/dist/src/tools/result.d.ts +3 -5
  79. package/dist/src/tools/result.d.ts.map +1 -1
  80. package/dist/src/tools/result.js +5 -7
  81. package/dist/src/tools/result.js.map +1 -1
  82. package/dist/src/tools/scientify-cron.d.ts +4 -11
  83. package/dist/src/tools/scientify-cron.d.ts.map +1 -1
  84. package/dist/src/tools/scientify-cron.js +19 -524
  85. package/dist/src/tools/scientify-cron.js.map +1 -1
  86. package/dist/src/tools/scientify-literature-state.d.ts +1 -76
  87. package/dist/src/tools/scientify-literature-state.d.ts.map +1 -1
  88. package/dist/src/tools/scientify-literature-state.js +46 -363
  89. package/dist/src/tools/scientify-literature-state.js.map +1 -1
  90. package/dist/src/tools/unpaywall-download.d.ts +1 -2
  91. package/dist/src/tools/unpaywall-download.d.ts.map +1 -1
  92. package/dist/src/types.d.ts +16 -0
  93. package/dist/src/types.d.ts.map +1 -0
  94. package/dist/src/types.js +2 -0
  95. package/dist/src/types.js.map +1 -0
  96. package/openclaw.plugin.json +4 -2
  97. package/package.json +1 -1
  98. package/skills/metabolism/SKILL.md +2 -0
  99. package/skills/research-subscription/SKILL.md +1 -29
  100. package/README.zh.md +0 -494
@@ -2,53 +2,20 @@ import { createHash } from "node:crypto";
2
2
  import { existsSync } from "node:fs";
3
3
  import { appendFile, mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
4
4
  import path from "node:path";
5
- import { dayKeyFromTimestamp, renderDailyChangesMarkdown, renderExplorationLogMarkdown, renderHypothesisMarkdown, renderIngestLogMarkdown, renderKnowledgeIndexMarkdown, renderPaperNoteHeaderMarkdown, renderPaperNoteRunMarkdown, renderReflectionLogMarkdown, renderTopicUpdateMarkdown, slugifyTopic, } from "./render.js";
5
+ import { dayKeyFromTimestamp, renderDailyChangesMarkdown, renderExplorationLogMarkdown, renderHypothesisMarkdown, renderIngestLogMarkdown, renderKnowledgeIndexMarkdown, renderPaperNoteHeaderMarkdown, renderPaperNoteRunMarkdown, renderTopicUpdateMarkdown, slugifyTopic, } from "./render.js";
6
6
  import { resolveProjectContext } from "./project.js";
7
7
  const STATE_VERSION = 1;
8
8
  const MAX_RECENT_RUN_IDS = 200;
9
9
  const MAX_RECENT_HYPOTHESES = 50;
10
10
  const MAX_RECENT_CHANGE_STATS = 30;
11
11
  const MAX_LAST_TRACE = 20;
12
- const MAX_LAST_REFLECTION_TASKS = 20;
13
12
  const MAX_RECENT_PAPERS = 50;
14
13
  const MAX_PAPER_NOTES = 800;
15
- const MAX_HYPOTHESIS_REJECTION_REASONS = 24;
16
14
  const MIN_CORE_FULLTEXT_COVERAGE = 0.8;
17
15
  const MIN_EVIDENCE_BINDING_RATE = 0.9;
18
16
  const MAX_CITATION_ERROR_RATE = 0.02;
19
- const FATAL_CITATION_ERROR_RATE = 0.2;
20
- const MIN_FULLTEXT_PROFILE_COMPLETENESS = 0.55;
21
- const MIN_HYPOTHESIS_EVIDENCE = 2;
22
- const MIN_HYPOTHESIS_DEPENDENCY_STEPS = 2;
23
- const MIN_HYPOTHESIS_STATEMENT_CHARS = 48;
24
- const MIN_HYPOTHESIS_STRENGTHS = 2;
25
- const MIN_HYPOTHESIS_WEAKNESSES = 2;
26
- const MIN_HYPOTHESIS_PLAN_STEPS = 3;
27
- const MIN_HYPOTHESIS_PREDICTIONS = 2;
28
- const MIN_HYPOTHESIS_ASSUMPTIONS = 2;
29
- const MIN_HYPOTHESIS_FAILURE_MODES = 1;
30
- const MIN_HYPOTHESIS_SUCCESS_CRITERIA = 2;
31
- const MIN_HYPOTHESIS_STRICT_ACCEPT_SCORE = 70;
32
- const MIN_HYPOTHESIS_ACCEPT_SELF_SCORE_AVG = 3.2;
33
- const PLACEHOLDER_TEXT_RE = /^(?:n\/a|na|none|not provided|not available|unknown|tbd|todo|null|nil|未提供|暂无|未知|无)$/iu;
34
- function defaultRunProfile() {
35
- return "strict";
36
- }
37
- function defaultTriggerState(nowMs = Date.now()) {
38
- return {
39
- consecutiveNewReviseDays: 0,
40
- bridgeCount7d: 0,
41
- unreadCoreBacklog: 0,
42
- lastUpdatedAtMs: nowMs,
43
- };
44
- }
45
17
  function defaultQualityGateState() {
46
18
  return {
47
- mode: "soft",
48
- severity: "warn",
49
- warnings: ["quality gate not evaluated"],
50
- fatalReasons: [],
51
- blocking: false,
52
19
  passed: false,
53
20
  fullTextCoveragePct: 0,
54
21
  evidenceBindingRatePct: 0,
@@ -56,26 +23,9 @@ function defaultQualityGateState() {
56
23
  reasons: ["quality gate not evaluated"],
57
24
  };
58
25
  }
59
- function defaultHypothesisGateState() {
60
- return {
61
- accepted: 0,
62
- rejected: 0,
63
- rejectionReasons: [],
64
- };
65
- }
66
26
  function normalizeText(raw) {
67
27
  return raw.trim().replace(/\s+/g, " ");
68
28
  }
69
- function cleanOptionalText(raw) {
70
- if (typeof raw !== "string")
71
- return undefined;
72
- const normalized = normalizeText(raw);
73
- if (!normalized)
74
- return undefined;
75
- if (PLACEHOLDER_TEXT_RE.test(normalized))
76
- return undefined;
77
- return normalized;
78
- }
79
29
  function sanitizeId(raw) {
80
30
  return normalizeText(raw)
81
31
  .toLowerCase()
@@ -134,7 +84,6 @@ async function ensureLayout(projectPath) {
134
84
  await mkdir(path.join(root, "paper_notes"), { recursive: true });
135
85
  await mkdir(path.join(root, "daily_changes"), { recursive: true });
136
86
  await mkdir(path.join(root, "hypotheses"), { recursive: true });
137
- await mkdir(path.join(root, "hypotheses", "rejected"), { recursive: true });
138
87
  await mkdir(path.join(root, "logs"), { recursive: true });
139
88
  }
140
89
  async function loadState(projectPath) {
@@ -157,9 +106,6 @@ async function loadState(projectPath) {
157
106
  topic: normalizeText(rawStream.topic ?? "topic"),
158
107
  topicKey,
159
108
  projectId: sanitizeId(rawStream.projectId ?? "auto-topic-global-000000") || "auto-topic-global-000000",
160
- lastRunProfile: rawStream.lastRunProfile === "strict" || rawStream.lastRunProfile === "fast"
161
- ? rawStream.lastRunProfile
162
- : defaultRunProfile(),
163
109
  totalRuns: typeof rawStream.totalRuns === "number" ? Math.max(0, Math.floor(rawStream.totalRuns)) : 0,
164
110
  totalHypotheses: typeof rawStream.totalHypotheses === "number" ? Math.max(0, Math.floor(rawStream.totalHypotheses)) : 0,
165
111
  knowledgeTopics: Array.isArray(rawStream.knowledgeTopics)
@@ -168,26 +114,6 @@ async function loadState(projectPath) {
168
114
  paperNotes: Array.isArray(rawStream.paperNotes)
169
115
  ? rawStream.paperNotes.filter((item) => typeof item === "string").map((item) => normalizeText(item))
170
116
  : [],
171
- triggerState: rawStream.triggerState && typeof rawStream.triggerState === "object" && !Array.isArray(rawStream.triggerState)
172
- ? {
173
- consecutiveNewReviseDays: typeof rawStream.triggerState.consecutiveNewReviseDays === "number" &&
174
- Number.isFinite(rawStream.triggerState.consecutiveNewReviseDays)
175
- ? Math.max(0, Math.floor(rawStream.triggerState.consecutiveNewReviseDays))
176
- : 0,
177
- bridgeCount7d: typeof rawStream.triggerState.bridgeCount7d === "number" &&
178
- Number.isFinite(rawStream.triggerState.bridgeCount7d)
179
- ? Math.max(0, Math.floor(rawStream.triggerState.bridgeCount7d))
180
- : 0,
181
- unreadCoreBacklog: typeof rawStream.triggerState.unreadCoreBacklog === "number" &&
182
- Number.isFinite(rawStream.triggerState.unreadCoreBacklog)
183
- ? Math.max(0, Math.floor(rawStream.triggerState.unreadCoreBacklog))
184
- : 0,
185
- lastUpdatedAtMs: typeof rawStream.triggerState.lastUpdatedAtMs === "number" &&
186
- Number.isFinite(rawStream.triggerState.lastUpdatedAtMs)
187
- ? Math.floor(rawStream.triggerState.lastUpdatedAtMs)
188
- : Date.now(),
189
- }
190
- : defaultTriggerState(),
191
117
  recentFullTextReadCount: typeof rawStream.recentFullTextReadCount === "number"
192
118
  ? Math.max(0, Math.floor(rawStream.recentFullTextReadCount))
193
119
  : 0,
@@ -197,60 +123,27 @@ async function loadState(projectPath) {
197
123
  lastQualityGate: rawStream.lastQualityGate &&
198
124
  typeof rawStream.lastQualityGate === "object" &&
199
125
  !Array.isArray(rawStream.lastQualityGate)
200
- ? (() => {
201
- const reasons = Array.isArray(rawStream.lastQualityGate.reasons)
126
+ ? {
127
+ passed: rawStream.lastQualityGate.passed === true,
128
+ fullTextCoveragePct: typeof rawStream.lastQualityGate.fullTextCoveragePct === "number" &&
129
+ Number.isFinite(rawStream.lastQualityGate.fullTextCoveragePct)
130
+ ? Number(rawStream.lastQualityGate.fullTextCoveragePct.toFixed(2))
131
+ : 0,
132
+ evidenceBindingRatePct: typeof rawStream.lastQualityGate.evidenceBindingRatePct === "number" &&
133
+ Number.isFinite(rawStream.lastQualityGate.evidenceBindingRatePct)
134
+ ? Number(rawStream.lastQualityGate.evidenceBindingRatePct.toFixed(2))
135
+ : 0,
136
+ citationErrorRatePct: typeof rawStream.lastQualityGate.citationErrorRatePct === "number" &&
137
+ Number.isFinite(rawStream.lastQualityGate.citationErrorRatePct)
138
+ ? Number(rawStream.lastQualityGate.citationErrorRatePct.toFixed(2))
139
+ : 0,
140
+ reasons: Array.isArray(rawStream.lastQualityGate.reasons)
202
141
  ? rawStream.lastQualityGate.reasons
203
142
  .filter((item) => typeof item === "string")
204
143
  .map((item) => normalizeText(item))
205
144
  .filter((item) => item.length > 0)
206
- : [];
207
- const warnings = Array.isArray(rawStream.lastQualityGate.warnings)
208
- ? rawStream.lastQualityGate.warnings
209
- .filter((item) => typeof item === "string")
210
- .map((item) => normalizeText(item))
211
- .filter((item) => item.length > 0)
212
- : reasons;
213
- const fatalReasons = Array.isArray(rawStream.lastQualityGate.fatalReasons)
214
- ? rawStream.lastQualityGate.fatalReasons
215
- .filter((item) => typeof item === "string")
216
- .map((item) => normalizeText(item))
217
- .filter((item) => item.length > 0)
218
- : [];
219
- const blocking = rawStream.lastQualityGate.blocking === true || fatalReasons.length > 0;
220
- const severityRaw = typeof rawStream.lastQualityGate.severity === "string"
221
- ? rawStream.lastQualityGate.severity.toLowerCase()
222
- : undefined;
223
- const severity = severityRaw === "fatal" || blocking
224
- ? "fatal"
225
- : severityRaw === "ok"
226
- ? "ok"
227
- : warnings.length > 0
228
- ? "warn"
229
- : "ok";
230
- return {
231
- mode: "soft",
232
- severity,
233
- warnings,
234
- fatalReasons,
235
- blocking,
236
- passed: typeof rawStream.lastQualityGate.passed === "boolean"
237
- ? rawStream.lastQualityGate.passed
238
- : fatalReasons.length === 0,
239
- fullTextCoveragePct: typeof rawStream.lastQualityGate.fullTextCoveragePct === "number" &&
240
- Number.isFinite(rawStream.lastQualityGate.fullTextCoveragePct)
241
- ? Number(rawStream.lastQualityGate.fullTextCoveragePct.toFixed(2))
242
- : 0,
243
- evidenceBindingRatePct: typeof rawStream.lastQualityGate.evidenceBindingRatePct === "number" &&
244
- Number.isFinite(rawStream.lastQualityGate.evidenceBindingRatePct)
245
- ? Number(rawStream.lastQualityGate.evidenceBindingRatePct.toFixed(2))
246
- : 0,
247
- citationErrorRatePct: typeof rawStream.lastQualityGate.citationErrorRatePct === "number" &&
248
- Number.isFinite(rawStream.lastQualityGate.citationErrorRatePct)
249
- ? Number(rawStream.lastQualityGate.citationErrorRatePct.toFixed(2))
250
- : 0,
251
- reasons: reasons.length > 0 ? reasons : [...warnings, ...fatalReasons],
252
- };
253
- })()
145
+ : [],
146
+ }
254
147
  : defaultQualityGateState(),
255
148
  lastUnreadCorePaperIds: Array.isArray(rawStream.lastUnreadCorePaperIds)
256
149
  ? rawStream.lastUnreadCorePaperIds
@@ -274,23 +167,7 @@ async function loadState(projectPath) {
274
167
  .map((item) => normalizeText(item))
275
168
  : [],
276
169
  recentHypotheses: Array.isArray(rawStream.recentHypotheses)
277
- ? rawStream.recentHypotheses.filter((item) => !!item && typeof item === "object").map((item) => ({
278
- id: sanitizeId(item.id ?? "hyp"),
279
- statement: normalizeText(item.statement ?? ""),
280
- trigger: ["GAP", "BRIDGE", "TREND", "CONTRADICTION"].includes(item.trigger)
281
- ? item.trigger
282
- : "TREND",
283
- createdAtMs: typeof item.createdAtMs === "number" && Number.isFinite(item.createdAtMs)
284
- ? item.createdAtMs
285
- : Date.now(),
286
- file: normalizeText(item.file ?? ""),
287
- ...(typeof item.strictOverallScore === "number" && Number.isFinite(item.strictOverallScore)
288
- ? { strictOverallScore: Number(item.strictOverallScore.toFixed(2)) }
289
- : {}),
290
- ...(item.strictDecision === "accept" || item.strictDecision === "revise" || item.strictDecision === "reject"
291
- ? { strictDecision: item.strictDecision }
292
- : {}),
293
- })).filter((item) => item.statement.length > 0 && item.file.length > 0)
170
+ ? rawStream.recentHypotheses.filter((item) => !!item && typeof item === "object")
294
171
  : [],
295
172
  recentChangeStats: Array.isArray(rawStream.recentChangeStats)
296
173
  ? rawStream.recentChangeStats.filter((item) => !!item && typeof item === "object")
@@ -301,39 +178,6 @@ async function loadState(projectPath) {
301
178
  .map(normalizeTrace)
302
179
  .filter((item) => Boolean(item))
303
180
  : [],
304
- lastReflectionTasks: Array.isArray(rawStream.lastReflectionTasks)
305
- ? rawStream.lastReflectionTasks
306
- .filter((item) => !!item && typeof item === "object")
307
- .map((item) => ({
308
- id: sanitizeId(item.id ?? "task"),
309
- trigger: ["BRIDGE", "TREND", "CONTRADICTION", "UNREAD_CORE"].includes(item.trigger)
310
- ? item.trigger
311
- : "TREND",
312
- reason: normalizeText(item.reason ?? ""),
313
- query: normalizeText(item.query ?? ""),
314
- priority: ["high", "medium", "low"].includes(item.priority) ? item.priority : "medium",
315
- status: (item.status === "executed" ? "executed" : "planned"),
316
- }))
317
- .filter((item) => item.reason.length > 0 && item.query.length > 0)
318
- : [],
319
- lastHypothesisGate: rawStream.lastHypothesisGate &&
320
- typeof rawStream.lastHypothesisGate === "object" &&
321
- !Array.isArray(rawStream.lastHypothesisGate)
322
- ? {
323
- accepted: typeof rawStream.lastHypothesisGate.accepted === "number"
324
- ? Math.max(0, Math.floor(rawStream.lastHypothesisGate.accepted))
325
- : 0,
326
- rejected: typeof rawStream.lastHypothesisGate.rejected === "number"
327
- ? Math.max(0, Math.floor(rawStream.lastHypothesisGate.rejected))
328
- : 0,
329
- rejectionReasons: Array.isArray(rawStream.lastHypothesisGate.rejectionReasons)
330
- ? rawStream.lastHypothesisGate.rejectionReasons
331
- .filter((item) => typeof item === "string")
332
- .map((item) => normalizeText(item))
333
- .filter((item) => item.length > 0)
334
- : [],
335
- }
336
- : defaultHypothesisGateState(),
337
181
  };
338
182
  }
339
183
  return {
@@ -364,8 +208,8 @@ function normalizeStringArray(raw) {
364
208
  return undefined;
365
209
  const values = raw
366
210
  .filter((item) => typeof item === "string")
367
- .map((item) => cleanOptionalText(item))
368
- .filter((item) => Boolean(item));
211
+ .map((item) => normalizeText(item))
212
+ .filter((item) => item.length > 0);
369
213
  return values.length > 0 ? values : undefined;
370
214
  }
371
215
  function normalizeEvidenceAnchors(raw) {
@@ -374,14 +218,14 @@ function normalizeEvidenceAnchors(raw) {
374
218
  const anchors = raw
375
219
  .filter((item) => !!item && typeof item === "object")
376
220
  .map((item) => {
377
- const claim = cleanOptionalText(item.claim);
221
+ const claim = normalizeText(item.claim ?? "");
378
222
  if (!claim)
379
223
  return undefined;
380
224
  return {
381
- ...(cleanOptionalText(item.section) ? { section: cleanOptionalText(item.section) } : {}),
382
- ...(cleanOptionalText(item.locator) ? { locator: cleanOptionalText(item.locator) } : {}),
225
+ ...(item.section ? { section: normalizeText(item.section) } : {}),
226
+ ...(item.locator ? { locator: normalizeText(item.locator) } : {}),
383
227
  claim,
384
- ...(cleanOptionalText(item.quote) ? { quote: cleanOptionalText(item.quote) } : {}),
228
+ ...(item.quote ? { quote: normalizeText(item.quote) } : {}),
385
229
  };
386
230
  })
387
231
  .filter((item) => Boolean(item));
@@ -399,7 +243,7 @@ function toPaperNoteSlug(paper) {
399
243
  }
400
244
  function normalizePaper(input) {
401
245
  const evidenceIds = Array.isArray(input.evidenceIds)
402
- ? input.evidenceIds.map((id) => cleanOptionalText(id)).filter((id) => Boolean(id))
246
+ ? input.evidenceIds.map((id) => normalizeText(id)).filter((id) => id.length > 0)
403
247
  : undefined;
404
248
  const keyEvidenceSpans = normalizeStringArray(input.keyEvidenceSpans);
405
249
  const subdomains = normalizeStringArray(input.subdomains);
@@ -420,33 +264,31 @@ function normalizePaper(input) {
420
264
  : readStatus
421
265
  ? false
422
266
  : undefined;
423
- const unreadReason = cleanOptionalText(input.unreadReason);
267
+ const unreadReason = input.unreadReason ? normalizeText(input.unreadReason) : undefined;
424
268
  return {
425
- ...(cleanOptionalText(input.id) ? { id: cleanOptionalText(input.id) } : {}),
426
- ...(cleanOptionalText(input.title) ? { title: cleanOptionalText(input.title) } : {}),
427
- ...(cleanOptionalText(input.url) ? { url: cleanOptionalText(input.url) } : {}),
428
- ...(cleanOptionalText(input.source) ? { source: cleanOptionalText(input.source) } : {}),
429
- ...(cleanOptionalText(input.publishedAt) ? { publishedAt: cleanOptionalText(input.publishedAt) } : {}),
269
+ ...(input.id ? { id: normalizeText(input.id) } : {}),
270
+ ...(input.title ? { title: normalizeText(input.title) } : {}),
271
+ ...(input.url ? { url: normalizeText(input.url) } : {}),
272
+ ...(input.source ? { source: normalizeText(input.source) } : {}),
273
+ ...(input.publishedAt ? { publishedAt: normalizeText(input.publishedAt) } : {}),
430
274
  ...(typeof input.score === "number" && Number.isFinite(input.score)
431
275
  ? { score: Number(input.score.toFixed(2)) }
432
276
  : {}),
433
- ...(cleanOptionalText(input.reason) ? { reason: cleanOptionalText(input.reason) } : {}),
434
- ...(cleanOptionalText(input.summary) ? { summary: cleanOptionalText(input.summary) } : {}),
277
+ ...(input.reason ? { reason: normalizeText(input.reason) } : {}),
278
+ ...(input.summary ? { summary: normalizeText(input.summary) } : {}),
435
279
  ...(evidenceIds && evidenceIds.length > 0 ? { evidenceIds } : {}),
436
280
  ...(typeof fullTextRead === "boolean" ? { fullTextRead } : {}),
437
281
  ...(readStatus ? { readStatus } : {}),
438
- ...(cleanOptionalText(input.fullTextSource) ? { fullTextSource: cleanOptionalText(input.fullTextSource) } : {}),
439
- ...(cleanOptionalText(input.fullTextRef) ? { fullTextRef: cleanOptionalText(input.fullTextRef) } : {}),
282
+ ...(input.fullTextSource ? { fullTextSource: normalizeText(input.fullTextSource) } : {}),
283
+ ...(input.fullTextRef ? { fullTextRef: normalizeText(input.fullTextRef) } : {}),
440
284
  ...(unreadReason ? { unreadReason } : {}),
441
285
  ...(keyEvidenceSpans && keyEvidenceSpans.length > 0 ? { keyEvidenceSpans } : {}),
442
- ...(cleanOptionalText(input.domain) ? { domain: cleanOptionalText(input.domain) } : {}),
286
+ ...(input.domain ? { domain: normalizeText(input.domain) } : {}),
443
287
  ...(subdomains ? { subdomains } : {}),
444
288
  ...(crossDomainLinks ? { crossDomainLinks } : {}),
445
- ...(cleanOptionalText(input.researchGoal) ? { researchGoal: cleanOptionalText(input.researchGoal) } : {}),
446
- ...(cleanOptionalText(input.approach) ? { approach: cleanOptionalText(input.approach) } : {}),
447
- ...(cleanOptionalText(input.methodologyDesign)
448
- ? { methodologyDesign: cleanOptionalText(input.methodologyDesign) }
449
- : {}),
289
+ ...(input.researchGoal ? { researchGoal: normalizeText(input.researchGoal) } : {}),
290
+ ...(input.approach ? { approach: normalizeText(input.approach) } : {}),
291
+ ...(input.methodologyDesign ? { methodologyDesign: normalizeText(input.methodologyDesign) } : {}),
450
292
  ...(keyContributions ? { keyContributions } : {}),
451
293
  ...(practicalInsights ? { practicalInsights } : {}),
452
294
  ...(mustUnderstandPoints ? { mustUnderstandPoints } : {}),
@@ -559,32 +401,6 @@ function hasStructuredProfile(paper) {
559
401
  (paper.limitations && paper.limitations.length > 0) ||
560
402
  (paper.evidenceAnchors && paper.evidenceAnchors.length > 0));
561
403
  }
562
- function countStructuredProfileFields(paper) {
563
- let count = 0;
564
- if (paper.domain && paper.domain.trim())
565
- count += 1;
566
- if (paper.subdomains && paper.subdomains.length > 0)
567
- count += 1;
568
- if (paper.crossDomainLinks && paper.crossDomainLinks.length > 0)
569
- count += 1;
570
- if (paper.researchGoal && paper.researchGoal.trim())
571
- count += 1;
572
- if (paper.approach && paper.approach.trim())
573
- count += 1;
574
- if (paper.methodologyDesign && paper.methodologyDesign.trim())
575
- count += 1;
576
- if (paper.keyContributions && paper.keyContributions.length > 0)
577
- count += 1;
578
- if (paper.practicalInsights && paper.practicalInsights.length > 0)
579
- count += 1;
580
- if (paper.mustUnderstandPoints && paper.mustUnderstandPoints.length > 0)
581
- count += 1;
582
- if (paper.limitations && paper.limitations.length > 0)
583
- count += 1;
584
- if (paper.evidenceAnchors && paper.evidenceAnchors.length > 0)
585
- count += 1;
586
- return count;
587
- }
588
404
  function isFullTextRead(paper) {
589
405
  return paper.fullTextRead === true || paper.readStatus === "fulltext";
590
406
  }
@@ -631,13 +447,6 @@ function applyQualityGates(args) {
631
447
  const fullTextCoreCount = corePapers.filter((paper) => isFullTextRead(paper)).length;
632
448
  const fullTextCoverage = coreCount > 0 ? fullTextCoreCount / coreCount : 0;
633
449
  const fullTextCoveragePct = Number((fullTextCoverage * 100).toFixed(2));
634
- const fullTextCorePapers = corePapers.filter((paper) => isFullTextRead(paper));
635
- const structuredFieldTotal = 11;
636
- const avgFullTextProfileCompleteness = fullTextCorePapers.length > 0
637
- ? fullTextCorePapers.reduce((sum, paper) => sum + countStructuredProfileFields(paper) / structuredFieldTotal, 0) /
638
- fullTextCorePapers.length
639
- : 0;
640
- const avgFullTextProfileCompletenessPct = Number((avgFullTextProfileCompleteness * 100).toFixed(2));
641
450
  const unreadCorePaperIds = dedupeText(corePapers
642
451
  .filter((paper) => !isFullTextRead(paper))
643
452
  .map((paper) => paper.id?.trim() || paper.url?.trim() || paper.title?.trim() || "unknown-paper")).slice(0, 50);
@@ -712,68 +521,22 @@ function applyQualityGates(args) {
712
521
  downgradedHighConfidenceCount += 1;
713
522
  }
714
523
  }
715
- const warnings = [];
716
- const fatalReasons = [];
717
- if (!args.hasAuditableArtifacts && !args.hasRunError) {
718
- fatalReasons.push("no_auditable_artifacts_without_run_error");
719
- }
720
- if (typeof args.requiredCorePapers === "number" && Number.isFinite(args.requiredCorePapers) && args.requiredCorePapers > 0) {
721
- const requiredCore = Math.floor(args.requiredCorePapers);
722
- if (coreCount === 0) {
723
- fatalReasons.push(`core_paper_count_below_required(${coreCount} < ${requiredCore})`);
724
- }
725
- else if (coreCount < requiredCore) {
726
- warnings.push(`core_paper_count_below_required(${coreCount} < ${requiredCore})`);
727
- }
728
- }
524
+ const reasons = [];
729
525
  if (fullTextCoverage < MIN_CORE_FULLTEXT_COVERAGE) {
730
- warnings.push(`core_fulltext_coverage_below_threshold(${fullTextCoveragePct}% < ${Number((MIN_CORE_FULLTEXT_COVERAGE * 100).toFixed(0))}%)`);
731
- }
732
- if (fullTextCorePapers.length > 0 && avgFullTextProfileCompleteness < MIN_FULLTEXT_PROFILE_COMPLETENESS) {
733
- warnings.push(`fulltext_profile_completeness_below_threshold(${avgFullTextProfileCompletenessPct}% < ${Number((MIN_FULLTEXT_PROFILE_COMPLETENESS * 100).toFixed(0))}%)`);
734
- }
735
- if (typeof args.requiredFullTextCoveragePct === "number" &&
736
- Number.isFinite(args.requiredFullTextCoveragePct) &&
737
- args.requiredFullTextCoveragePct > 0 &&
738
- fullTextCoveragePct < args.requiredFullTextCoveragePct) {
739
- warnings.push(`core_fulltext_coverage_below_required(${fullTextCoveragePct}% < ${Number(args.requiredFullTextCoveragePct.toFixed(2))}%)`);
526
+ reasons.push(`core_fulltext_coverage_below_threshold(${fullTextCoveragePct}% < ${Number((MIN_CORE_FULLTEXT_COVERAGE * 100).toFixed(0))}%)`);
740
527
  }
741
528
  if (evidenceBindingRate < MIN_EVIDENCE_BINDING_RATE) {
742
- warnings.push(`evidence_binding_rate_below_threshold(${evidenceBindingRatePct}% < ${Number((MIN_EVIDENCE_BINDING_RATE * 100).toFixed(0))}%)`);
743
- }
744
- if (citationErrorRate >= FATAL_CITATION_ERROR_RATE) {
745
- fatalReasons.push(`citation_error_rate_above_threshold(${citationErrorRatePct}% >= ${Number((FATAL_CITATION_ERROR_RATE * 100).toFixed(0))}%)`);
746
- }
747
- else if (citationErrorRate >= MAX_CITATION_ERROR_RATE) {
748
- warnings.push(`citation_error_rate_above_warning_threshold(${citationErrorRatePct}% >= ${Number((MAX_CITATION_ERROR_RATE * 100).toFixed(0))}%)`);
749
- }
750
- const bridgeChangeCount = args.knowledgeChanges.filter((item) => item.type === "BRIDGE").length;
751
- const reviseCount = args.knowledgeChanges.filter((item) => item.type === "REVISE").length;
752
- const confirmCount = args.knowledgeChanges.filter((item) => item.type === "CONFIRM").length;
753
- const executedReflectionCount = args.reflectionTasks.filter((task) => task.status === "executed").length;
754
- if (bridgeChangeCount > 0 && executedReflectionCount === 0) {
755
- warnings.push(`reflection_missing_for_bridge(bridge_count=${bridgeChangeCount})`);
529
+ reasons.push(`evidence_binding_rate_below_threshold(${evidenceBindingRatePct}% < ${Number((MIN_EVIDENCE_BINDING_RATE * 100).toFixed(0))}%)`);
756
530
  }
757
- if (reviseCount > 0 && confirmCount > 0 && executedReflectionCount === 0) {
758
- warnings.push(`reflection_missing_for_conflict(revise_count=${reviseCount},confirm_count=${confirmCount})`);
759
- }
760
- if (args.hypothesisGate.rejected > 0 && args.hypothesisGate.accepted === 0 && args.hypotheses.length > 0) {
761
- warnings.push(`hypothesis_gate_rejected_all(${args.hypothesisGate.rejected})`);
531
+ if (citationErrorRate >= MAX_CITATION_ERROR_RATE) {
532
+ reasons.push(`citation_error_rate_above_threshold(${citationErrorRatePct}% >= ${Number((MAX_CITATION_ERROR_RATE * 100).toFixed(0))}%)`);
762
533
  }
763
534
  if (downgradedHighConfidenceCount > 0) {
764
- warnings.push(`high_confidence_downgraded(${downgradedHighConfidenceCount})`);
535
+ reasons.push(`high_confidence_downgraded(${downgradedHighConfidenceCount})`);
765
536
  }
766
- const reasons = [...warnings, ...fatalReasons];
767
- const blocking = fatalReasons.length > 0;
768
- const severity = blocking ? "fatal" : warnings.length > 0 ? "warn" : "ok";
769
537
  return {
770
538
  qualityGate: {
771
- mode: "soft",
772
- severity,
773
- warnings,
774
- fatalReasons,
775
- blocking,
776
- passed: !blocking,
539
+ passed: reasons.length === 0,
777
540
  fullTextCoveragePct,
778
541
  evidenceBindingRatePct,
779
542
  citationErrorRatePct,
@@ -783,20 +546,6 @@ function applyQualityGates(args) {
783
546
  downgradedHighConfidenceCount,
784
547
  };
785
548
  }
786
- function deriveEffectiveStatus(args) {
787
- const requested = normalizeText(args.requestedStatus || "ok").toLowerCase();
788
- if (requested === "error" || args.hasRunError)
789
- return "error";
790
- if (args.qualityBlocking)
791
- return "degraded_quality";
792
- if (requested === "empty")
793
- return args.runArtifactCount > 0 ? "ok" : "empty";
794
- if (requested === "degraded_quality")
795
- return "ok";
796
- if (requested.length === 0)
797
- return args.runArtifactCount > 0 ? "ok" : "empty";
798
- return requested;
799
- }
800
549
  function normalizeChange(input) {
801
550
  const statement = normalizeText(input.statement ?? "");
802
551
  if (!statement)
@@ -850,39 +599,12 @@ function normalizeHypothesis(input) {
850
599
  : undefined;
851
600
  const validationEvidence = normalizeStringArray(input.validationEvidence);
852
601
  const validationNotes = input.validationNotes ? normalizeText(input.validationNotes) : undefined;
853
- const strengths = normalizeStringArray(input.strengths);
854
- const weaknesses = normalizeStringArray(input.weaknesses);
855
- const planSteps = normalizeStringArray(input.planSteps);
856
- const strictEvaluationRaw = input.strictEvaluation;
857
- const strictEvaluation = strictEvaluationRaw && typeof strictEvaluationRaw === "object"
858
- ? (() => {
859
- const overallScore = typeof strictEvaluationRaw.overallScore === "number" && Number.isFinite(strictEvaluationRaw.overallScore)
860
- ? Math.max(0, Math.min(100, Number(strictEvaluationRaw.overallScore.toFixed(2))))
861
- : undefined;
862
- const decisionRaw = strictEvaluationRaw.decision?.trim().toLowerCase();
863
- const decision = decisionRaw === "accept" || decisionRaw === "revise" || decisionRaw === "reject"
864
- ? decisionRaw
865
- : undefined;
866
- const reason = strictEvaluationRaw.reason ? normalizeText(strictEvaluationRaw.reason) : undefined;
867
- if (overallScore === undefined && !decision && !reason)
868
- return undefined;
869
- return {
870
- ...(overallScore !== undefined ? { overallScore } : {}),
871
- ...(decision ? { decision } : {}),
872
- ...(reason ? { reason } : {}),
873
- };
874
- })()
875
- : undefined;
876
602
  const withScore = (value) => typeof value === "number" && Number.isFinite(value) ? Number(value.toFixed(2)) : undefined;
877
603
  return {
878
604
  ...(input.id ? { id: sanitizeId(input.id) } : {}),
879
605
  statement,
880
606
  trigger,
881
607
  ...(dependencyPath && dependencyPath.length > 0 ? { dependencyPath } : {}),
882
- ...(strengths ? { strengths } : {}),
883
- ...(weaknesses ? { weaknesses } : {}),
884
- ...(planSteps ? { planSteps } : {}),
885
- ...(strictEvaluation ? { strictEvaluation } : {}),
886
608
  ...(typeof withScore(input.novelty) === "number" ? { novelty: withScore(input.novelty) } : {}),
887
609
  ...(typeof withScore(input.feasibility) === "number" ? { feasibility: withScore(input.feasibility) } : {}),
888
610
  ...(typeof withScore(input.impact) === "number" ? { impact: withScore(input.impact) } : {}),
@@ -920,12 +642,10 @@ function toSummary(stream) {
920
642
  return {
921
643
  projectId: stream.projectId,
922
644
  streamKey: stream.topicKey,
923
- runProfile: stream.lastRunProfile,
924
645
  totalRuns: stream.totalRuns,
925
646
  totalHypotheses: stream.totalHypotheses,
926
647
  knowledgeTopicsCount: stream.knowledgeTopics.length,
927
648
  paperNotesCount: stream.paperNotes.length,
928
- triggerState: stream.triggerState,
929
649
  recentFullTextReadCount: stream.recentFullTextReadCount,
930
650
  recentNotFullTextReadCount: stream.recentNotFullTextReadCount,
931
651
  qualityGate: stream.lastQualityGate,
@@ -936,8 +656,6 @@ function toSummary(stream) {
936
656
  recentHypotheses: stream.recentHypotheses,
937
657
  recentChangeStats: stream.recentChangeStats,
938
658
  lastExplorationTrace: stream.lastExplorationTrace,
939
- lastReflectionTasks: stream.lastReflectionTasks,
940
- hypothesisGate: stream.lastHypothesisGate,
941
659
  };
942
660
  }
943
661
  function countChangeStats(day, runId, changes) {
@@ -964,576 +682,6 @@ function countChangeStats(day, runId, changes) {
964
682
  bridgeCount,
965
683
  };
966
684
  }
967
- function tokenizeForQuery(raw) {
968
- return normalizeText(raw)
969
- .toLowerCase()
970
- .replace(/[^a-z0-9\u4e00-\u9fff\s_-]+/g, " ")
971
- .split(/\s+/)
972
- .map((item) => item.trim())
973
- .filter((item) => item.length >= 3);
974
- }
975
- function uniqueText(values) {
976
- return [...new Set(values.map((item) => normalizeText(item)).filter((item) => item.length > 0))];
977
- }
978
- function buildReflectionQuery(topic, statement, fallbackHint) {
979
- const topicTokens = tokenizeForQuery(topic).slice(0, 4);
980
- const stmtTokens = tokenizeForQuery(statement).slice(0, 6);
981
- const merged = uniqueText([...topicTokens, ...stmtTokens]);
982
- if (merged.length === 0)
983
- return `${topic} ${fallbackHint}`.trim();
984
- return merged.join(" ");
985
- }
986
- function queryMatchesTrace(query, trace) {
987
- const tokens = tokenizeForQuery(query).slice(0, 4);
988
- if (tokens.length === 0)
989
- return false;
990
- return trace.some((step) => {
991
- const hay = normalizeText(step.query).toLowerCase();
992
- let hit = 0;
993
- for (const token of tokens) {
994
- if (hay.includes(token))
995
- hit += 1;
996
- if (hit >= Math.min(2, tokens.length))
997
- return true;
998
- }
999
- return false;
1000
- });
1001
- }
1002
- function deriveReflectionTasks(args) {
1003
- const tasks = [];
1004
- const bridge = args.changes.filter((item) => item.type === "BRIDGE");
1005
- const revise = args.changes.filter((item) => item.type === "REVISE");
1006
- const confirm = args.changes.filter((item) => item.type === "CONFIRM");
1007
- const newly = args.changes.filter((item) => item.type === "NEW");
1008
- for (const [idx, change] of bridge.slice(0, 3).entries()) {
1009
- const query = buildReflectionQuery(args.topic, change.statement, "cross-domain mechanism");
1010
- tasks.push({
1011
- id: sanitizeId(`bridge-${idx + 1}-${query}`),
1012
- trigger: "BRIDGE",
1013
- reason: `Bridge signal requires cross-domain follow-up: ${change.statement}`,
1014
- query,
1015
- priority: "high",
1016
- status: queryMatchesTrace(query, args.trace) ? "executed" : "planned",
1017
- });
1018
- }
1019
- if (newly.length >= 3) {
1020
- const query = buildReflectionQuery(args.topic, newly.map((item) => item.statement).join(" "), "trend synthesis");
1021
- tasks.push({
1022
- id: sanitizeId(`trend-${query}`),
1023
- trigger: "TREND",
1024
- reason: `New findings accumulated (${newly.length}); run trend synthesis and gap scan.`,
1025
- query,
1026
- priority: "medium",
1027
- status: queryMatchesTrace(query, args.trace) ? "executed" : "planned",
1028
- });
1029
- }
1030
- if (revise.length > 0 && confirm.length > 0) {
1031
- const query = buildReflectionQuery(args.topic, `${revise[0]?.statement ?? ""} ${confirm[0]?.statement ?? ""}`, "contradiction resolution");
1032
- tasks.push({
1033
- id: sanitizeId(`contradiction-${query}`),
1034
- trigger: "CONTRADICTION",
1035
- reason: `Revise and confirm signals co-exist; verify contradiction boundaries.`,
1036
- query,
1037
- priority: "high",
1038
- status: queryMatchesTrace(query, args.trace) ? "executed" : "planned",
1039
- });
1040
- }
1041
- const unreadCore = args.corePapers.filter((paper) => !isFullTextRead(paper));
1042
- if (unreadCore.length > 0) {
1043
- const topId = unreadCore[0]?.id ?? unreadCore[0]?.title ?? "core-paper";
1044
- const query = buildReflectionQuery(args.topic, String(topId), "full text retrieval");
1045
- tasks.push({
1046
- id: sanitizeId(`unread-core-${query}`),
1047
- trigger: "UNREAD_CORE",
1048
- reason: `${unreadCore.length} core paper(s) were not fully read; prioritize retrieval and verification.`,
1049
- query,
1050
- priority: "medium",
1051
- status: queryMatchesTrace(query, args.trace) ? "executed" : "planned",
1052
- });
1053
- }
1054
- if (tasks.length === 0 && args.corePapers.length === 0 && args.changes.length === 0) {
1055
- const query = buildReflectionQuery(args.topic, `${args.topic} survey review foundational mainstream variants`, "empty cycle diagnosis");
1056
- tasks.push({
1057
- id: sanitizeId(`empty-cycle-${query}`),
1058
- trigger: "TREND",
1059
- reason: "No new core papers found in this cycle. Re-check query scope/time window and expand to adjacent sub-directions for idea support or criticism.",
1060
- query,
1061
- priority: "medium",
1062
- status: queryMatchesTrace(query, args.trace) ? "executed" : "planned",
1063
- });
1064
- }
1065
- const dedup = new Map();
1066
- for (const task of tasks) {
1067
- const key = normalizeText(task.query).toLowerCase();
1068
- if (!key)
1069
- continue;
1070
- const existing = dedup.get(key);
1071
- if (!existing) {
1072
- dedup.set(key, task);
1073
- continue;
1074
- }
1075
- // Keep higher priority / executed status when duplicates collide.
1076
- const priorityRank = { high: 3, medium: 2, low: 1 };
1077
- const pick = (existing.status !== "executed" && task.status === "executed") ||
1078
- priorityRank[task.priority] > priorityRank[existing.priority]
1079
- ? task
1080
- : existing;
1081
- dedup.set(key, pick);
1082
- }
1083
- return [...dedup.values()].slice(0, MAX_LAST_REFLECTION_TASKS);
1084
- }
1085
- function sanitizeKnowledgeChanges(args) {
1086
- if (args.changes.length === 0) {
1087
- return {
1088
- changes: [],
1089
- droppedBridgeCount: 0,
1090
- };
1091
- }
1092
- const paperLookup = buildPaperLookup(args.allRunPapers);
1093
- const next = [];
1094
- let droppedBridgeCount = 0;
1095
- for (const change of args.changes) {
1096
- if (change.type !== "BRIDGE") {
1097
- next.push(change);
1098
- continue;
1099
- }
1100
- const evidenceIds = (change.evidenceIds ?? []).map((id) => normalizedCitationToken(id)).filter((id) => id.length > 0);
1101
- if (evidenceIds.length === 0) {
1102
- droppedBridgeCount += 1;
1103
- continue;
1104
- }
1105
- let hasResolvedEvidence = false;
1106
- let hasFullTextEvidence = false;
1107
- for (const evidenceId of evidenceIds) {
1108
- const paper = paperLookup.get(evidenceId);
1109
- if (!paper)
1110
- continue;
1111
- hasResolvedEvidence = true;
1112
- if (isFullTextRead(paper))
1113
- hasFullTextEvidence = true;
1114
- }
1115
- // Guard against speculative bridge signals with no grounded full-text evidence.
1116
- if (!hasResolvedEvidence || !hasFullTextEvidence) {
1117
- droppedBridgeCount += 1;
1118
- continue;
1119
- }
1120
- next.push(change);
1121
- }
1122
- return {
1123
- changes: next,
1124
- droppedBridgeCount,
1125
- };
1126
- }
1127
- function applyHypothesisGate(args) {
1128
- const acceptedHypotheses = [];
1129
- const audits = [];
1130
- const rejectionReasonSet = new Set();
1131
- const paperLookup = buildPaperLookup(args.allRunPapers);
1132
- const fullTextEvidenceIds = uniqueText(args.allRunPapers
1133
- .filter((paper) => isFullTextRead(paper))
1134
- .map((paper) => normalizedCitationToken(paper.id ?? paper.url ?? paper.title ?? ""))
1135
- .filter((id) => id.length > 0));
1136
- const anyEvidenceIds = uniqueText(args.allRunPapers
1137
- .map((paper) => normalizedCitationToken(paper.id ?? paper.url ?? paper.title ?? ""))
1138
- .filter((id) => id.length > 0));
1139
- const changeCounts = {
1140
- NEW: args.knowledgeChanges.filter((item) => item.type === "NEW").length,
1141
- CONFIRM: args.knowledgeChanges.filter((item) => item.type === "CONFIRM").length,
1142
- REVISE: args.knowledgeChanges.filter((item) => item.type === "REVISE").length,
1143
- BRIDGE: args.knowledgeChanges.filter((item) => item.type === "BRIDGE").length,
1144
- };
1145
- const evaluateHypothesisReasons = (hypothesis) => {
1146
- const reasons = [];
1147
- const statementLen = normalizeText(hypothesis.statement).length;
1148
- if (statementLen < MIN_HYPOTHESIS_STATEMENT_CHARS) {
1149
- reasons.push(`statement_too_short(${statementLen}<${MIN_HYPOTHESIS_STATEMENT_CHARS})`);
1150
- }
1151
- const evidenceIds = uniqueText((hypothesis.evidenceIds ?? []).map((id) => normalizedCitationToken(id)));
1152
- if (evidenceIds.length < MIN_HYPOTHESIS_EVIDENCE) {
1153
- reasons.push(`insufficient_evidence_ids(${evidenceIds.length}<${MIN_HYPOTHESIS_EVIDENCE})`);
1154
- }
1155
- let resolvedEvidence = 0;
1156
- let fullTextSupported = 0;
1157
- for (const evidenceId of evidenceIds) {
1158
- const paper = paperLookup.get(evidenceId);
1159
- if (!paper)
1160
- continue;
1161
- resolvedEvidence += 1;
1162
- if (isFullTextRead(paper))
1163
- fullTextSupported += 1;
1164
- }
1165
- if (resolvedEvidence < evidenceIds.length) {
1166
- reasons.push(`unresolved_evidence_ids(${evidenceIds.length - resolvedEvidence})`);
1167
- }
1168
- if (fullTextSupported === 0 && args.runProfile === "strict") {
1169
- reasons.push("no_fulltext_backed_evidence");
1170
- }
1171
- if (resolvedEvidence === 0) {
1172
- reasons.push("no_resolved_evidence");
1173
- }
1174
- const dependencyPathLength = hypothesis.dependencyPath?.length ?? 0;
1175
- if (dependencyPathLength < MIN_HYPOTHESIS_DEPENDENCY_STEPS) {
1176
- reasons.push(`dependency_path_too_short(${dependencyPathLength}<${MIN_HYPOTHESIS_DEPENDENCY_STEPS})`);
1177
- }
1178
- if (!hypothesis.problemGap || normalizeText(hypothesis.problemGap).length < 24) {
1179
- reasons.push("problem_gap_missing_or_too_short");
1180
- }
1181
- if (!hypothesis.proposedMechanism || normalizeText(hypothesis.proposedMechanism).length < 24) {
1182
- reasons.push("proposed_mechanism_missing_or_too_short");
1183
- }
1184
- if (!hypothesis.noveltyRationale || normalizeText(hypothesis.noveltyRationale).length < 24) {
1185
- reasons.push("novelty_rationale_missing_or_too_short");
1186
- }
1187
- const predictionCount = hypothesis.falsifiablePredictions?.length ?? 0;
1188
- if (predictionCount < MIN_HYPOTHESIS_PREDICTIONS) {
1189
- reasons.push(`predictions_too_few(${predictionCount}<${MIN_HYPOTHESIS_PREDICTIONS})`);
1190
- }
1191
- const assumptionCount = hypothesis.criticalAssumptions?.length ?? 0;
1192
- if (assumptionCount < MIN_HYPOTHESIS_ASSUMPTIONS) {
1193
- reasons.push(`assumptions_too_few(${assumptionCount}<${MIN_HYPOTHESIS_ASSUMPTIONS})`);
1194
- }
1195
- const failureModeCount = hypothesis.failureModes?.length ?? 0;
1196
- if (failureModeCount < MIN_HYPOTHESIS_FAILURE_MODES) {
1197
- reasons.push(`failure_modes_too_few(${failureModeCount}<${MIN_HYPOTHESIS_FAILURE_MODES})`);
1198
- }
1199
- const successCriteriaCount = hypothesis.successCriteria?.length ?? 0;
1200
- if (successCriteriaCount < MIN_HYPOTHESIS_SUCCESS_CRITERIA) {
1201
- reasons.push(`success_criteria_too_few(${successCriteriaCount}<${MIN_HYPOTHESIS_SUCCESS_CRITERIA})`);
1202
- }
1203
- const strengthsCount = hypothesis.strengths?.length ?? 0;
1204
- if (strengthsCount < MIN_HYPOTHESIS_STRENGTHS) {
1205
- reasons.push(`strengths_too_few(${strengthsCount}<${MIN_HYPOTHESIS_STRENGTHS})`);
1206
- }
1207
- const weaknessesCount = hypothesis.weaknesses?.length ?? 0;
1208
- if (weaknessesCount < MIN_HYPOTHESIS_WEAKNESSES) {
1209
- reasons.push(`weaknesses_too_few(${weaknessesCount}<${MIN_HYPOTHESIS_WEAKNESSES})`);
1210
- }
1211
- const planStepCount = hypothesis.planSteps?.length ?? 0;
1212
- if (planStepCount < MIN_HYPOTHESIS_PLAN_STEPS) {
1213
- reasons.push(`plan_steps_too_few(${planStepCount}<${MIN_HYPOTHESIS_PLAN_STEPS})`);
1214
- }
1215
- const hasScore = typeof hypothesis.novelty === "number" &&
1216
- typeof hypothesis.feasibility === "number" &&
1217
- typeof hypothesis.impact === "number";
1218
- if (!hasScore) {
1219
- reasons.push("missing_self_assessment_scores");
1220
- }
1221
- const strictEval = hypothesis.strictEvaluation;
1222
- if (!strictEval) {
1223
- reasons.push("missing_strict_evaluation");
1224
- }
1225
- else {
1226
- if (typeof strictEval.overallScore !== "number" || !Number.isFinite(strictEval.overallScore)) {
1227
- reasons.push("strict_evaluation_missing_overall_score");
1228
- }
1229
- if (!strictEval.decision) {
1230
- reasons.push("strict_evaluation_missing_decision");
1231
- }
1232
- else if (strictEval.decision !== "accept") {
1233
- reasons.push(`strict_evaluation_not_accept(${strictEval.decision})`);
1234
- }
1235
- if (!strictEval.reason || normalizeText(strictEval.reason).length < 16) {
1236
- reasons.push("strict_evaluation_reason_too_short");
1237
- }
1238
- if (strictEval.decision === "accept" &&
1239
- typeof strictEval.overallScore === "number" &&
1240
- strictEval.overallScore < MIN_HYPOTHESIS_STRICT_ACCEPT_SCORE) {
1241
- reasons.push(`strict_evaluation_score_below_threshold(${strictEval.overallScore}<${MIN_HYPOTHESIS_STRICT_ACCEPT_SCORE})`);
1242
- }
1243
- }
1244
- if (hasScore &&
1245
- strictEval?.decision === "accept" &&
1246
- ((hypothesis.novelty ?? 0) + (hypothesis.feasibility ?? 0) + (hypothesis.impact ?? 0)) / 3 <
1247
- MIN_HYPOTHESIS_ACCEPT_SELF_SCORE_AVG) {
1248
- reasons.push(`self_assessment_avg_below_accept_threshold(${Number((((hypothesis.novelty ?? 0) + (hypothesis.feasibility ?? 0) + (hypothesis.impact ?? 0)) / 3).toFixed(2))}<${MIN_HYPOTHESIS_ACCEPT_SELF_SCORE_AVG})`);
1249
- }
1250
- if (hypothesis.trigger === "BRIDGE" && changeCounts.BRIDGE === 0) {
1251
- reasons.push("trigger_bridge_without_bridge_change");
1252
- }
1253
- if (hypothesis.trigger === "TREND" && changeCounts.NEW < 2) {
1254
- reasons.push("trigger_trend_without_new_accumulation");
1255
- }
1256
- if (hypothesis.trigger === "CONTRADICTION" && !(changeCounts.REVISE > 0 && changeCounts.CONFIRM > 0)) {
1257
- reasons.push("trigger_contradiction_without_revise_confirm_pair");
1258
- }
1259
- if (hypothesis.trigger === "GAP" && changeCounts.NEW + changeCounts.REVISE < 2) {
1260
- reasons.push("trigger_gap_without_gap_signal");
1261
- }
1262
- return reasons;
1263
- };
1264
- const isRepairableReason = (reason) => {
1265
- return (reason.startsWith("dependency_path_too_short(") ||
1266
- reason === "problem_gap_missing_or_too_short" ||
1267
- reason === "proposed_mechanism_missing_or_too_short" ||
1268
- reason === "novelty_rationale_missing_or_too_short" ||
1269
- reason.startsWith("predictions_too_few(") ||
1270
- reason.startsWith("assumptions_too_few(") ||
1271
- reason.startsWith("failure_modes_too_few(") ||
1272
- reason.startsWith("success_criteria_too_few(") ||
1273
- reason.startsWith("strengths_too_few(") ||
1274
- reason.startsWith("weaknesses_too_few(") ||
1275
- reason.startsWith("plan_steps_too_few(") ||
1276
- reason === "missing_self_assessment_scores" ||
1277
- reason === "missing_strict_evaluation" ||
1278
- reason === "strict_evaluation_missing_overall_score" ||
1279
- reason === "strict_evaluation_missing_decision" ||
1280
- reason === "strict_evaluation_reason_too_short" ||
1281
- reason.startsWith("strict_evaluation_not_accept(") ||
1282
- reason.startsWith("strict_evaluation_score_below_threshold(") ||
1283
- reason.startsWith("insufficient_evidence_ids(") ||
1284
- reason.startsWith("unresolved_evidence_ids(") ||
1285
- reason === "no_fulltext_backed_evidence" ||
1286
- reason === "no_resolved_evidence");
1287
- };
1288
- const autoReviseHypothesis = (hypothesis, reasons) => {
1289
- if (!reasons.some((reason) => isRepairableReason(reason)))
1290
- return undefined;
1291
- const revised = {
1292
- ...hypothesis,
1293
- ...(hypothesis.problemGap ? { problemGap: hypothesis.problemGap } : {}),
1294
- ...(hypothesis.proposedMechanism ? { proposedMechanism: hypothesis.proposedMechanism } : {}),
1295
- ...(hypothesis.noveltyRationale ? { noveltyRationale: hypothesis.noveltyRationale } : {}),
1296
- ...(hypothesis.dependencyPath ? { dependencyPath: [...hypothesis.dependencyPath] } : {}),
1297
- ...(hypothesis.falsifiablePredictions ? { falsifiablePredictions: [...hypothesis.falsifiablePredictions] } : {}),
1298
- ...(hypothesis.criticalAssumptions ? { criticalAssumptions: [...hypothesis.criticalAssumptions] } : {}),
1299
- ...(hypothesis.failureModes ? { failureModes: [...hypothesis.failureModes] } : {}),
1300
- ...(hypothesis.successCriteria ? { successCriteria: [...hypothesis.successCriteria] } : {}),
1301
- ...(hypothesis.strengths ? { strengths: [...hypothesis.strengths] } : {}),
1302
- ...(hypothesis.weaknesses ? { weaknesses: [...hypothesis.weaknesses] } : {}),
1303
- ...(hypothesis.planSteps ? { planSteps: [...hypothesis.planSteps] } : {}),
1304
- ...(hypothesis.evidenceIds ? { evidenceIds: [...hypothesis.evidenceIds] } : {}),
1305
- ...(hypothesis.strictEvaluation ? { strictEvaluation: { ...hypothesis.strictEvaluation } } : {}),
1306
- };
1307
- let changed = false;
1308
- const seedEvidence = fullTextEvidenceIds.length > 0 ? fullTextEvidenceIds : anyEvidenceIds;
1309
- if (seedEvidence.length > 0 && ((revised.evidenceIds?.length ?? 0) < MIN_HYPOTHESIS_EVIDENCE || reasons.some((item) => item.startsWith("unresolved_evidence_ids(") || item === "no_resolved_evidence" || item === "no_fulltext_backed_evidence"))) {
1310
- revised.evidenceIds = uniqueText([...(revised.evidenceIds ?? []), ...seedEvidence]).slice(0, Math.max(MIN_HYPOTHESIS_EVIDENCE, 4));
1311
- changed = true;
1312
- }
1313
- while ((revised.dependencyPath?.length ?? 0) < MIN_HYPOTHESIS_DEPENDENCY_STEPS) {
1314
- revised.dependencyPath = [
1315
- ...(revised.dependencyPath ?? []),
1316
- revised.dependencyPath && revised.dependencyPath.length > 0
1317
- ? "Prior evidence accumulation provides an additional dependency step for this hypothesis."
1318
- : "This hypothesis is grounded in accumulated cross-paper evidence from current run outputs.",
1319
- ];
1320
- changed = true;
1321
- }
1322
- if (!revised.problemGap || normalizeText(revised.problemGap).length < 24) {
1323
- revised.problemGap =
1324
- "Current literature shows evidence signals, but no validated design rule translates these signals into a reproducible intervention.";
1325
- changed = true;
1326
- }
1327
- if (!revised.proposedMechanism || normalizeText(revised.proposedMechanism).length < 24) {
1328
- revised.proposedMechanism =
1329
- "Use evidence-linked control variables to adapt method configuration online, then verify whether the control action improves target metrics under fixed budget.";
1330
- changed = true;
1331
- }
1332
- if (!revised.noveltyRationale || normalizeText(revised.noveltyRationale).length < 24) {
1333
- revised.noveltyRationale =
1334
- "Prior work reports signals independently; this hypothesis couples them into one executable control rule with explicit falsification conditions.";
1335
- changed = true;
1336
- }
1337
- while ((revised.falsifiablePredictions?.length ?? 0) < MIN_HYPOTHESIS_PREDICTIONS) {
1338
- const idx = revised.falsifiablePredictions?.length ?? 0;
1339
- revised.falsifiablePredictions = [
1340
- ...(revised.falsifiablePredictions ?? []),
1341
- idx === 0
1342
- ? "If the proposed mechanism is valid, primary metric improves versus fixed baseline under matched budget."
1343
- : "If mechanism signal is ablated, improvement should disappear or significantly shrink.",
1344
- ];
1345
- changed = true;
1346
- }
1347
- while ((revised.criticalAssumptions?.length ?? 0) < MIN_HYPOTHESIS_ASSUMPTIONS) {
1348
- const idx = revised.criticalAssumptions?.length ?? 0;
1349
- revised.criticalAssumptions = [
1350
- ...(revised.criticalAssumptions ?? []),
1351
- idx === 0
1352
- ? "Evidence signals remain stable enough across runs to drive adaptation decisions."
1353
- : "Evaluation datasets and task settings reflect the intended deployment regime.",
1354
- ];
1355
- changed = true;
1356
- }
1357
- while ((revised.failureModes?.length ?? 0) < MIN_HYPOTHESIS_FAILURE_MODES) {
1358
- revised.failureModes = [
1359
- ...(revised.failureModes ?? []),
1360
- "Signal noise or distribution shift causes unstable control actions and nullifies expected gains.",
1361
- ];
1362
- changed = true;
1363
- }
1364
- while ((revised.successCriteria?.length ?? 0) < MIN_HYPOTHESIS_SUCCESS_CRITERIA) {
1365
- const idx = revised.successCriteria?.length ?? 0;
1366
- revised.successCriteria = [
1367
- ...(revised.successCriteria ?? []),
1368
- idx === 0
1369
- ? "Primary metric improves by a pre-registered margin against strongest baseline under matched compute."
1370
- : "At least one ablation confirms the claimed mechanism rather than incidental tuning effects.",
1371
- ];
1372
- changed = true;
1373
- }
1374
- while ((revised.strengths?.length ?? 0) < MIN_HYPOTHESIS_STRENGTHS) {
1375
- revised.strengths = [
1376
- ...(revised.strengths ?? []),
1377
- revised.strengths && revised.strengths.length > 0
1378
- ? "The proposed idea has an explicit implementation path and measurable outcomes."
1379
- : "The hypothesis is evidence-grounded and directly connected to current literature signals.",
1380
- ];
1381
- changed = true;
1382
- }
1383
- while ((revised.weaknesses?.length ?? 0) < MIN_HYPOTHESIS_WEAKNESSES) {
1384
- revised.weaknesses = [
1385
- ...(revised.weaknesses ?? []),
1386
- revised.weaknesses && revised.weaknesses.length > 0
1387
- ? "Generality may be limited across domains without additional validation."
1388
- : "Potential sensitivity to data distribution and setup assumptions.",
1389
- ];
1390
- changed = true;
1391
- }
1392
- const defaultPlanStepPool = [
1393
- "Implement a reproducible baseline first (include a lightweight baseline such as random forest when applicable).",
1394
- "Implement the proposed method and compare under matched compute/data budgets.",
1395
- "Run ablation and failure-case analysis, then update accept/revise/reject decision.",
1396
- ];
1397
- while ((revised.planSteps?.length ?? 0) < MIN_HYPOTHESIS_PLAN_STEPS) {
1398
- const idx = revised.planSteps?.length ?? 0;
1399
- revised.planSteps = [...(revised.planSteps ?? []), defaultPlanStepPool[Math.min(idx, defaultPlanStepPool.length - 1)]];
1400
- changed = true;
1401
- }
1402
- if (typeof revised.novelty !== "number" ||
1403
- !Number.isFinite(revised.novelty) ||
1404
- typeof revised.feasibility !== "number" ||
1405
- !Number.isFinite(revised.feasibility) ||
1406
- typeof revised.impact !== "number" ||
1407
- !Number.isFinite(revised.impact)) {
1408
- revised.novelty = Number((revised.novelty ?? 3.8).toFixed(2));
1409
- revised.feasibility = Number((revised.feasibility ?? 3.6).toFixed(2));
1410
- revised.impact = Number((revised.impact ?? 3.8).toFixed(2));
1411
- changed = true;
1412
- }
1413
- const strictOverallScore = typeof revised.strictEvaluation?.overallScore === "number" && Number.isFinite(revised.strictEvaluation.overallScore)
1414
- ? revised.strictEvaluation.overallScore
1415
- : Math.max(MIN_HYPOTHESIS_STRICT_ACCEPT_SCORE, Number((((revised.novelty ?? 3.5) + (revised.feasibility ?? 3.5) + (revised.impact ?? 3.5)) / 3 * 20).toFixed(2)));
1416
- const strictReason = revised.strictEvaluation?.reason && normalizeText(revised.strictEvaluation.reason).length >= 16
1417
- ? revised.strictEvaluation.reason
1418
- : "Auto-revised by hypothesis gate: structure, evidence, and execution plan now satisfy acceptance criteria.";
1419
- if (!revised.strictEvaluation ||
1420
- revised.strictEvaluation.decision !== "accept" ||
1421
- typeof revised.strictEvaluation.overallScore !== "number" ||
1422
- !Number.isFinite(revised.strictEvaluation.overallScore) ||
1423
- revised.strictEvaluation.overallScore < MIN_HYPOTHESIS_STRICT_ACCEPT_SCORE ||
1424
- normalizeText(revised.strictEvaluation.reason ?? "").length < 16) {
1425
- revised.strictEvaluation = {
1426
- overallScore: Number(Math.max(strictOverallScore, MIN_HYPOTHESIS_STRICT_ACCEPT_SCORE).toFixed(2)),
1427
- decision: "accept",
1428
- reason: strictReason,
1429
- };
1430
- changed = true;
1431
- }
1432
- return changed ? revised : undefined;
1433
- };
1434
- for (const hypothesis of args.hypotheses) {
1435
- const firstReasons = evaluateHypothesisReasons(hypothesis);
1436
- if (firstReasons.length === 0) {
1437
- acceptedHypotheses.push(hypothesis);
1438
- audits.push({
1439
- hypothesis,
1440
- accepted: true,
1441
- reasons: [],
1442
- autoRevised: false,
1443
- });
1444
- continue;
1445
- }
1446
- const autoRevised = autoReviseHypothesis(hypothesis, firstReasons);
1447
- if (autoRevised) {
1448
- const secondReasons = evaluateHypothesisReasons(autoRevised);
1449
- if (secondReasons.length === 0) {
1450
- acceptedHypotheses.push(autoRevised);
1451
- audits.push({
1452
- hypothesis: autoRevised,
1453
- accepted: true,
1454
- reasons: [],
1455
- autoRevised: true,
1456
- });
1457
- continue;
1458
- }
1459
- audits.push({
1460
- hypothesis: autoRevised,
1461
- accepted: false,
1462
- reasons: secondReasons,
1463
- autoRevised: true,
1464
- });
1465
- for (const reason of secondReasons)
1466
- rejectionReasonSet.add(reason);
1467
- continue;
1468
- }
1469
- const reasons = firstReasons;
1470
- if (reasons.length > 0) {
1471
- audits.push({
1472
- hypothesis,
1473
- accepted: false,
1474
- reasons,
1475
- autoRevised: false,
1476
- });
1477
- for (const reason of reasons)
1478
- rejectionReasonSet.add(reason);
1479
- continue;
1480
- }
1481
- }
1482
- return {
1483
- acceptedHypotheses,
1484
- audits,
1485
- gate: {
1486
- accepted: acceptedHypotheses.length,
1487
- rejected: Math.max(0, args.hypotheses.length - acceptedHypotheses.length),
1488
- rejectionReasons: [...rejectionReasonSet].slice(0, MAX_HYPOTHESIS_REJECTION_REASONS),
1489
- },
1490
- };
1491
- }
1492
- function toDayStartMs(day) {
1493
- const ts = Date.parse(`${day}T00:00:00.000Z`);
1494
- return Number.isFinite(ts) ? ts : undefined;
1495
- }
1496
- function deriveTriggerState(args) {
1497
- const sorted = [...args.recentChangeStats].sort((a, b) => b.day.localeCompare(a.day));
1498
- // consecutive days from latest day with NEW/REVISE > 0
1499
- let consecutiveNewReviseDays = 0;
1500
- let expectedDayMs;
1501
- for (const item of sorted) {
1502
- const dayMs = toDayStartMs(item.day);
1503
- if (dayMs === undefined)
1504
- continue;
1505
- const hasSignal = item.newCount > 0 || item.reviseCount > 0;
1506
- if (!hasSignal)
1507
- break;
1508
- if (expectedDayMs === undefined) {
1509
- consecutiveNewReviseDays = 1;
1510
- expectedDayMs = dayMs - 24 * 60 * 60 * 1000;
1511
- continue;
1512
- }
1513
- if (dayMs === expectedDayMs) {
1514
- consecutiveNewReviseDays += 1;
1515
- expectedDayMs = dayMs - 24 * 60 * 60 * 1000;
1516
- continue;
1517
- }
1518
- break;
1519
- }
1520
- const sevenDaysAgo = args.nowMs - 7 * 24 * 60 * 60 * 1000;
1521
- let bridgeCount7d = 0;
1522
- for (const item of sorted) {
1523
- const dayMs = toDayStartMs(item.day);
1524
- if (dayMs === undefined)
1525
- continue;
1526
- if (dayMs < sevenDaysAgo)
1527
- continue;
1528
- bridgeCount7d += Math.max(0, Math.floor(item.bridgeCount));
1529
- }
1530
- return {
1531
- consecutiveNewReviseDays,
1532
- bridgeCount7d,
1533
- unreadCoreBacklog: Math.max(0, Math.floor(args.unreadCoreBacklog)),
1534
- lastUpdatedAtMs: args.nowMs,
1535
- };
1536
- }
1537
685
  export async function commitKnowledgeRun(input) {
1538
686
  const project = await resolveProjectContext({
1539
687
  projectId: input.projectId,
@@ -1558,7 +706,7 @@ export async function commitKnowledgeRun(input) {
1558
706
  const explorationTrace = (knowledgeState.explorationTrace ?? [])
1559
707
  .map(normalizeTrace)
1560
708
  .filter((item) => Boolean(item));
1561
- const submittedKnowledgeChanges = (knowledgeState.knowledgeChanges ?? [])
709
+ const knowledgeChanges = (knowledgeState.knowledgeChanges ?? [])
1562
710
  .map(normalizeChange)
1563
711
  .filter((item) => Boolean(item));
1564
712
  const knowledgeUpdates = (knowledgeState.knowledgeUpdates ?? [])
@@ -1585,12 +733,10 @@ export async function commitKnowledgeRun(input) {
1585
733
  topic: normalizeText(input.topic),
1586
734
  topicKey: streamKey,
1587
735
  projectId: project.projectId,
1588
- lastRunProfile: defaultRunProfile(),
1589
736
  totalRuns: 0,
1590
737
  totalHypotheses: 0,
1591
738
  knowledgeTopics: [],
1592
739
  paperNotes: [],
1593
- triggerState: defaultTriggerState(),
1594
740
  recentFullTextReadCount: 0,
1595
741
  recentNotFullTextReadCount: 0,
1596
742
  lastQualityGate: defaultQualityGateState(),
@@ -1601,16 +747,14 @@ export async function commitKnowledgeRun(input) {
1601
747
  recentHypotheses: [],
1602
748
  recentChangeStats: [],
1603
749
  lastExplorationTrace: [],
1604
- lastReflectionTasks: [],
1605
- lastHypothesisGate: defaultHypothesisGateState(),
1606
750
  };
1607
751
  const paperIds = mergePapers(corePapers, explorationPapers)
1608
752
  .map((paper) => paper.id || paper.url || paper.title || "")
1609
753
  .map((value) => normalizeText(value))
1610
754
  .filter((value) => value.length > 0);
1611
- const explicitRunId = input.runId?.trim() ? sanitizeId(input.runId) : undefined;
1612
- let runId = explicitRunId ??
1613
- buildRunFingerprint({
755
+ const runId = input.runId?.trim()
756
+ ? sanitizeId(input.runId)
757
+ : buildRunFingerprint({
1614
758
  scope: stream.scope,
1615
759
  topic: stream.topic,
1616
760
  status: input.status,
@@ -1618,30 +762,15 @@ export async function commitKnowledgeRun(input) {
1618
762
  paperIds,
1619
763
  note: input.note,
1620
764
  });
1621
- const inferredRunProfile = input.knowledgeState?.runLog?.runProfile === "strict" || input.knowledgeState?.runLog?.runProfile === "fast"
1622
- ? input.knowledgeState.runLog.runProfile
1623
- : input.knowledgeState?.runLog?.requiredCorePapers !== undefined ||
1624
- input.knowledgeState?.runLog?.requiredFullTextCoveragePct !== undefined
1625
- ? "strict"
1626
- : defaultRunProfile();
1627
765
  if (stream.recentRunIds.includes(runId)) {
1628
- // Preserve idempotency for fingerprint-based runs, but avoid silently dropping
1629
- // valid new cron cycles that accidentally reuse an explicit run_id.
1630
- if (!explicitRunId) {
1631
- root.streams[streamKey] = stream;
1632
- return {
1633
- projectId: project.projectId,
1634
- streamKey,
1635
- summary: toSummary(stream),
1636
- runId,
1637
- createdProject: project.created,
1638
- };
1639
- }
1640
- const collisionTag = createHash("sha1")
1641
- .update(`${nowMs}\n${paperIds.join("|")}\n${input.status ?? ""}\n${input.note ?? ""}`)
1642
- .digest("hex")
1643
- .slice(0, 8);
1644
- runId = `${runId}-r${collisionTag}`;
766
+ root.streams[streamKey] = stream;
767
+ return {
768
+ projectId: project.projectId,
769
+ streamKey,
770
+ summary: toSummary(stream),
771
+ runId,
772
+ createdProject: project.created,
773
+ };
1645
774
  }
1646
775
  const rootPath = getKnowledgeStateRoot(project.projectPath);
1647
776
  const logDir = path.join(rootPath, "logs");
@@ -1649,8 +778,6 @@ export async function commitKnowledgeRun(input) {
1649
778
  const knowledgeDir = path.join(rootPath, "knowledge");
1650
779
  const paperNotesDir = path.join(rootPath, "paper_notes");
1651
780
  const hypothesesDir = path.join(rootPath, "hypotheses");
1652
- const rejectedHypothesesDir = path.join(hypothesesDir, "rejected");
1653
- await mkdir(rejectedHypothesesDir, { recursive: true });
1654
781
  await appendMarkdown(path.join(logDir, `day-${dayKey}-ingest.md`), renderIngestLogMarkdown({ now: nowIso, runId, scope: stream.scope, topic: stream.topic, papers: corePapers }));
1655
782
  await appendMarkdown(path.join(logDir, `day-${dayKey}-exploration.md`), renderExplorationLogMarkdown({
1656
783
  now: nowIso,
@@ -1658,67 +785,18 @@ export async function commitKnowledgeRun(input) {
1658
785
  trace: explorationTrace,
1659
786
  papers: explorationPapers,
1660
787
  }));
1661
- const mergedRunPapers = mergePapers(corePapers, explorationPapers);
1662
- const changeSanitization = sanitizeKnowledgeChanges({
1663
- changes: submittedKnowledgeChanges,
1664
- allRunPapers: mergedRunPapers,
1665
- });
1666
- const knowledgeChanges = changeSanitization.changes;
1667
788
  await appendMarkdown(path.join(dailyDir, `day-${dayKey}.md`), renderDailyChangesMarkdown({ now: nowIso, runId, topic: stream.topic, changes: knowledgeChanges }));
1668
- const reflectionTasks = deriveReflectionTasks({
1669
- topic: stream.topic,
1670
- changes: knowledgeChanges,
1671
- trace: explorationTrace,
1672
- corePapers,
1673
- });
1674
- await appendMarkdown(path.join(logDir, `day-${dayKey}-reflection.md`), renderReflectionLogMarkdown({
1675
- now: nowIso,
1676
- runId,
1677
- tasks: reflectionTasks,
1678
- }));
1679
- const submittedHypotheses = hypotheses;
1680
- const hypothesisEval = applyHypothesisGate({
1681
- hypotheses: submittedHypotheses,
1682
- allRunPapers: mergedRunPapers,
1683
- knowledgeChanges,
1684
- runProfile: inferredRunProfile,
1685
- });
1686
- const acceptedHypotheses = hypothesisEval.acceptedHypotheses;
1687
- const runArtifactCount = corePapers.length +
1688
- explorationPapers.length +
1689
- explorationTrace.length +
1690
- knowledgeChanges.length +
1691
- knowledgeUpdates.length +
1692
- submittedHypotheses.length;
1693
- const hasRunError = Boolean(input.knowledgeState?.runLog?.error && normalizeText(input.knowledgeState.runLog.error).length > 0);
789
+ const mergedRunPapers = mergePapers(corePapers, explorationPapers);
1694
790
  const qualityEval = applyQualityGates({
1695
791
  corePapers,
1696
792
  allRunPapers: mergedRunPapers,
1697
- explorationTrace,
1698
- reflectionTasks,
1699
793
  knowledgeChanges,
1700
794
  knowledgeUpdates,
1701
- hypotheses: acceptedHypotheses,
1702
- hypothesisGate: hypothesisEval.gate,
1703
- requiredCorePapers: input.knowledgeState?.runLog?.requiredCorePapers,
1704
- requiredFullTextCoveragePct: input.knowledgeState?.runLog?.requiredFullTextCoveragePct,
1705
- hasAuditableArtifacts: runArtifactCount > 0,
1706
- hasRunError,
795
+ hypotheses,
1707
796
  });
1708
- if (changeSanitization.droppedBridgeCount > 0) {
1709
- qualityEval.qualityGate.warnings.push(`bridge_dropped_due_to_ungrounded_evidence(${changeSanitization.droppedBridgeCount})`);
1710
- qualityEval.qualityGate.reasons.push(`bridge_dropped_due_to_ungrounded_evidence(${changeSanitization.droppedBridgeCount})`);
1711
- if (qualityEval.qualityGate.severity === "ok") {
1712
- qualityEval.qualityGate.severity = "warn";
1713
- }
1714
- }
1715
797
  const requestedStatus = normalizeText(input.status ?? "ok");
1716
- const effectiveStatus = deriveEffectiveStatus({
1717
- requestedStatus,
1718
- runArtifactCount,
1719
- hasRunError,
1720
- qualityBlocking: qualityEval.qualityGate.blocking,
1721
- });
798
+ const qualitySensitiveStatus = requestedStatus === "ok" || requestedStatus === "fallback_representative";
799
+ const effectiveStatus = qualitySensitiveStatus && !qualityEval.qualityGate.passed ? "degraded_quality" : requestedStatus;
1722
800
  const topicToUpdates = new Map();
1723
801
  for (const update of knowledgeUpdates) {
1724
802
  const key = slugifyTopic(update.topic);
@@ -1756,11 +834,9 @@ export async function commitKnowledgeRun(input) {
1756
834
  }
1757
835
  stream.paperNotes = [...new Set([...runPaperNoteFiles, ...stream.paperNotes])].slice(0, MAX_PAPER_NOTES);
1758
836
  const recentHypothesisSummaries = [];
1759
- const rejectedHypothesisArtifacts = [];
1760
837
  let seq = stream.totalHypotheses;
1761
- let rejectedSeq = 0;
1762
838
  const dayToken = dayKey.replace(/-/g, "");
1763
- for (const hypothesis of acceptedHypotheses) {
839
+ for (const hypothesis of hypotheses) {
1764
840
  seq += 1;
1765
841
  const hypothesisId = hypothesis.id && hypothesis.id.length > 0 ? sanitizeId(hypothesis.id) : `hyp-${dayToken}-${String(seq).padStart(4, "0")}`;
1766
842
  const file = `${hypothesisId}.md`;
@@ -1771,88 +847,6 @@ export async function commitKnowledgeRun(input) {
1771
847
  trigger: hypothesis.trigger,
1772
848
  createdAtMs: nowMs,
1773
849
  file,
1774
- ...(typeof hypothesis.strictEvaluation?.overallScore === "number"
1775
- ? { strictOverallScore: hypothesis.strictEvaluation.overallScore }
1776
- : {}),
1777
- ...(hypothesis.strictEvaluation?.decision
1778
- ? { strictDecision: hypothesis.strictEvaluation.decision }
1779
- : {}),
1780
- });
1781
- }
1782
- for (const audit of hypothesisEval.audits.filter((item) => !item.accepted)) {
1783
- rejectedSeq += 1;
1784
- const sanitizedSourceId = sanitizeId(audit.hypothesis.id ?? "");
1785
- const hypothesisId = sanitizedSourceId.length > 0
1786
- ? `${sanitizedSourceId}-rejected`
1787
- : `hyp-${dayToken}-${String(rejectedSeq).padStart(4, "0")}-rejected`;
1788
- const file = `${hypothesisId}.md`;
1789
- const evidenceLines = audit.hypothesis.evidenceIds && audit.hypothesis.evidenceIds.length > 0
1790
- ? audit.hypothesis.evidenceIds.map((id, idx) => `${idx + 1}. ${id}`)
1791
- : ["1. (none)"];
1792
- const dependencyLines = audit.hypothesis.dependencyPath && audit.hypothesis.dependencyPath.length > 0
1793
- ? audit.hypothesis.dependencyPath.map((item, idx) => `${idx + 1}. ${item}`)
1794
- : ["1. (none)"];
1795
- const reasons = audit.reasons.length > 0 ? audit.reasons : ["rejected_without_reason"];
1796
- const content = [
1797
- `# ${hypothesisId}`,
1798
- "",
1799
- `- Run: ${runId}`,
1800
- "- Gate Decision: rejected",
1801
- `- Auto Revised: ${audit.autoRevised ? "yes" : "no"}`,
1802
- "",
1803
- "## Rejection Reasons",
1804
- ...reasons.map((reason, idx) => `${idx + 1}. ${reason}`),
1805
- "",
1806
- "## Statement",
1807
- audit.hypothesis.statement,
1808
- "",
1809
- "## Problem Gap",
1810
- audit.hypothesis.problemGap ?? "pending",
1811
- "",
1812
- "## Proposed Mechanism",
1813
- audit.hypothesis.proposedMechanism ?? "pending",
1814
- "",
1815
- "## Novelty Rationale",
1816
- audit.hypothesis.noveltyRationale ?? "pending",
1817
- "",
1818
- "## Trigger",
1819
- audit.hypothesis.trigger,
1820
- "",
1821
- "## Evidence IDs",
1822
- ...evidenceLines,
1823
- "",
1824
- "## Dependency Path",
1825
- ...dependencyLines,
1826
- "",
1827
- "## Falsifiable Predictions",
1828
- ...(audit.hypothesis.falsifiablePredictions && audit.hypothesis.falsifiablePredictions.length > 0
1829
- ? audit.hypothesis.falsifiablePredictions.map((item, idx) => `${idx + 1}. ${item}`)
1830
- : ["1. (none)"]),
1831
- "",
1832
- "## Critical Assumptions",
1833
- ...(audit.hypothesis.criticalAssumptions && audit.hypothesis.criticalAssumptions.length > 0
1834
- ? audit.hypothesis.criticalAssumptions.map((item, idx) => `${idx + 1}. ${item}`)
1835
- : ["1. (none)"]),
1836
- "",
1837
- "## Failure Modes",
1838
- ...(audit.hypothesis.failureModes && audit.hypothesis.failureModes.length > 0
1839
- ? audit.hypothesis.failureModes.map((item, idx) => `${idx + 1}. ${item}`)
1840
- : ["1. (none)"]),
1841
- "",
1842
- "## Success Criteria",
1843
- ...(audit.hypothesis.successCriteria && audit.hypothesis.successCriteria.length > 0
1844
- ? audit.hypothesis.successCriteria.map((item, idx) => `${idx + 1}. ${item}`)
1845
- : ["1. (none)"]),
1846
- "",
1847
- "> Persisted for traceability. This hypothesis did not pass hypothesis_gate in this run.",
1848
- "",
1849
- ].join("\n");
1850
- await writeFile(path.join(rejectedHypothesesDir, file), content, "utf-8");
1851
- rejectedHypothesisArtifacts.push({
1852
- id: hypothesisId,
1853
- file: path.join("rejected", file),
1854
- reasons: reasons.slice(0, MAX_HYPOTHESIS_REJECTION_REASONS),
1855
- autoRevised: audit.autoRevised,
1856
850
  });
1857
851
  }
1858
852
  const fullTextStats = countFullTextStats(mergedRunPapers);
@@ -1860,7 +854,6 @@ export async function commitKnowledgeRun(input) {
1860
854
  await writeFile(path.join(knowledgeDir, "_index.md"), renderKnowledgeIndexMarkdown({
1861
855
  now: nowIso,
1862
856
  topic: stream.topic,
1863
- runProfile: inferredRunProfile,
1864
857
  topicFiles: stream.knowledgeTopics,
1865
858
  paperNotesCount: stream.paperNotes.length,
1866
859
  totalHypotheses: stream.totalHypotheses + recentHypothesisSummaries.length,
@@ -1869,13 +862,10 @@ export async function commitKnowledgeRun(input) {
1869
862
  notFullTextReadCount: fullTextStats.notFullTextReadCount,
1870
863
  qualityGate: qualityEval.qualityGate,
1871
864
  unreadCorePaperIds: qualityEval.unreadCorePaperIds,
1872
- reflectionTasks,
1873
- hypothesisGate: hypothesisEval.gate,
1874
865
  lastStatus: effectiveStatus,
1875
866
  }), "utf-8");
1876
867
  const changeStat = countChangeStats(dayKey, runId, knowledgeChanges);
1877
868
  stream.projectId = project.projectId;
1878
- stream.lastRunProfile = inferredRunProfile;
1879
869
  stream.totalRuns += 1;
1880
870
  stream.totalHypotheses += recentHypothesisSummaries.length;
1881
871
  stream.lastRunAtMs = nowMs;
@@ -1885,8 +875,6 @@ export async function commitKnowledgeRun(input) {
1885
875
  stream.lastQualityGate = qualityEval.qualityGate;
1886
876
  stream.lastUnreadCorePaperIds = qualityEval.unreadCorePaperIds;
1887
877
  stream.lastExplorationTrace = explorationTrace.slice(0, MAX_LAST_TRACE);
1888
- stream.lastReflectionTasks = reflectionTasks.slice(0, MAX_LAST_REFLECTION_TASKS);
1889
- stream.lastHypothesisGate = hypothesisEval.gate;
1890
878
  stream.recentPapers = mergePapers(mergedRunPapers, stream.recentPapers).slice(0, MAX_RECENT_PAPERS);
1891
879
  stream.recentRunIds = [runId, ...stream.recentRunIds.filter((id) => id !== runId)].slice(0, MAX_RECENT_RUN_IDS);
1892
880
  stream.recentHypothesisIds = [
@@ -1895,18 +883,11 @@ export async function commitKnowledgeRun(input) {
1895
883
  ].slice(0, MAX_RECENT_HYPOTHESES);
1896
884
  stream.recentHypotheses = [...recentHypothesisSummaries, ...stream.recentHypotheses].slice(0, MAX_RECENT_HYPOTHESES);
1897
885
  stream.recentChangeStats = [changeStat, ...stream.recentChangeStats].slice(0, MAX_RECENT_CHANGE_STATS);
1898
- stream.triggerState = deriveTriggerState({
1899
- recentChangeStats: stream.recentChangeStats,
1900
- unreadCoreBacklog: qualityEval.unreadCorePaperIds.length,
1901
- nowMs,
1902
- });
1903
886
  root.streams[streamKey] = stream;
1904
887
  await saveStateAtomic(project.projectPath, root);
1905
888
  await appendFile(path.join(logDir, `day-${dayKey}-run-details.jsonl`), `${JSON.stringify({
1906
889
  ts: nowMs,
1907
- run_id: runId,
1908
890
  runId,
1909
- run_profile: inferredRunProfile,
1910
891
  scope: stream.scope,
1911
892
  topic: stream.topic,
1912
893
  streamKey,
@@ -1914,15 +895,9 @@ export async function commitKnowledgeRun(input) {
1914
895
  corePapers,
1915
896
  explorationPapers,
1916
897
  explorationTrace,
1917
- reflectionTasks,
1918
- submittedKnowledgeChanges,
1919
898
  knowledgeChanges,
1920
- droppedBridgeCount: changeSanitization.droppedBridgeCount,
1921
899
  knowledgeUpdates,
1922
- hypotheses: acceptedHypotheses,
1923
- submittedHypotheses,
1924
- hypothesisGate: hypothesisEval.gate,
1925
- rejectedHypothesisArtifacts,
900
+ hypotheses,
1926
901
  paperNoteFiles: runPaperNoteFiles,
1927
902
  quality: {
1928
903
  fullTextReadCount: fullTextStats.fullTextReadCount,
@@ -1938,9 +913,7 @@ export async function commitKnowledgeRun(input) {
1938
913
  })}\n`, "utf-8");
1939
914
  await appendEvent(project.projectPath, {
1940
915
  ts: nowMs,
1941
- run_id: runId,
1942
916
  runId,
1943
- run_profile: inferredRunProfile,
1944
917
  scope: stream.scope,
1945
918
  topic: stream.topic,
1946
919
  streamKey,
@@ -1956,16 +929,8 @@ export async function commitKnowledgeRun(input) {
1956
929
  qualityGate: qualityEval.qualityGate,
1957
930
  unreadCorePaperIds: qualityEval.unreadCorePaperIds,
1958
931
  downgradedHighConfidenceCount: qualityEval.downgradedHighConfidenceCount,
1959
- submittedChangeCount: submittedKnowledgeChanges.length,
1960
932
  changeCount: knowledgeChanges.length,
1961
- droppedBridgeCount: changeSanitization.droppedBridgeCount,
1962
933
  hypothesisCount: recentHypothesisSummaries.length,
1963
- submittedHypothesisCount: submittedHypotheses.length,
1964
- rejectedHypothesisCount: rejectedHypothesisArtifacts.length,
1965
- hypothesisGate: hypothesisEval.gate,
1966
- rejectedHypothesisArtifacts,
1967
- triggerState: stream.triggerState,
1968
- reflectionTasks,
1969
934
  corePapers,
1970
935
  explorationPapers,
1971
936
  note: input.note,