scientify 1.12.0 → 1.12.2
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.
- package/README.md +3 -1
- package/README.zh.md +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -5
- package/dist/index.js.map +1 -1
- package/dist/src/cli/research.d.ts +1 -1
- package/dist/src/cli/research.d.ts.map +1 -1
- package/dist/src/cli/research.js +123 -227
- package/dist/src/cli/research.js.map +1 -1
- package/dist/src/commands/metabolism-status.d.ts +2 -2
- package/dist/src/commands/metabolism-status.d.ts.map +1 -1
- package/dist/src/commands/metabolism-status.js +75 -72
- package/dist/src/commands/metabolism-status.js.map +1 -1
- package/dist/src/commands.d.ts.map +1 -1
- package/dist/src/commands.js +55 -0
- package/dist/src/commands.js.map +1 -1
- package/dist/src/hooks/research-mode.d.ts +1 -1
- package/dist/src/hooks/research-mode.d.ts.map +1 -1
- package/dist/src/hooks/research-mode.js +67 -24
- package/dist/src/hooks/research-mode.js.map +1 -1
- package/dist/src/hooks/scientify-signature.d.ts.map +1 -1
- package/dist/src/hooks/scientify-signature.js +5 -2
- package/dist/src/hooks/scientify-signature.js.map +1 -1
- package/dist/src/knowledge-state/render.d.ts +9 -1
- package/dist/src/knowledge-state/render.d.ts.map +1 -1
- package/dist/src/knowledge-state/render.js +132 -33
- package/dist/src/knowledge-state/render.js.map +1 -1
- package/dist/src/knowledge-state/store.d.ts.map +1 -1
- package/dist/src/knowledge-state/store.js +545 -38
- package/dist/src/knowledge-state/store.js.map +1 -1
- package/dist/src/knowledge-state/types.d.ts +31 -0
- package/dist/src/knowledge-state/types.d.ts.map +1 -1
- package/dist/src/literature/subscription-state.d.ts +2 -0
- package/dist/src/literature/subscription-state.d.ts.map +1 -1
- package/dist/src/literature/subscription-state.js +586 -7
- package/dist/src/literature/subscription-state.js.map +1 -1
- package/dist/src/research-subscriptions/constants.d.ts +1 -1
- package/dist/src/research-subscriptions/constants.js +1 -1
- package/dist/src/research-subscriptions/parse.d.ts.map +1 -1
- package/dist/src/research-subscriptions/parse.js +10 -0
- package/dist/src/research-subscriptions/parse.js.map +1 -1
- package/dist/src/research-subscriptions/prompt.d.ts +1 -1
- package/dist/src/research-subscriptions/prompt.d.ts.map +1 -1
- package/dist/src/research-subscriptions/prompt.js +144 -195
- package/dist/src/research-subscriptions/prompt.js.map +1 -1
- package/dist/src/research-subscriptions/types.d.ts +1 -0
- package/dist/src/research-subscriptions/types.d.ts.map +1 -1
- package/dist/src/templates/bootstrap.d.ts.map +1 -1
- package/dist/src/templates/bootstrap.js +19 -32
- package/dist/src/templates/bootstrap.js.map +1 -1
- package/dist/src/tools/scientify-cron.d.ts +6 -2
- package/dist/src/tools/scientify-cron.d.ts.map +1 -1
- package/dist/src/tools/scientify-cron.js +396 -12
- package/dist/src/tools/scientify-cron.js.map +1 -1
- package/dist/src/tools/scientify-literature-state.d.ts +12 -0
- package/dist/src/tools/scientify-literature-state.d.ts.map +1 -1
- package/dist/src/tools/scientify-literature-state.js +159 -45
- package/dist/src/tools/scientify-literature-state.js.map +1 -1
- package/openclaw.plugin.json +2 -4
- package/package.json +1 -1
- package/skills/research-subscription/SKILL.md +23 -0
|
@@ -2,18 +2,36 @@ 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, renderTopicUpdateMarkdown, slugifyTopic, } from "./render.js";
|
|
5
|
+
import { dayKeyFromTimestamp, renderDailyChangesMarkdown, renderExplorationLogMarkdown, renderHypothesisMarkdown, renderIngestLogMarkdown, renderKnowledgeIndexMarkdown, renderPaperNoteHeaderMarkdown, renderPaperNoteRunMarkdown, renderReflectionLogMarkdown, 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;
|
|
12
13
|
const MAX_RECENT_PAPERS = 50;
|
|
13
14
|
const MAX_PAPER_NOTES = 800;
|
|
15
|
+
const MAX_HYPOTHESIS_REJECTION_REASONS = 24;
|
|
14
16
|
const MIN_CORE_FULLTEXT_COVERAGE = 0.8;
|
|
15
17
|
const MIN_EVIDENCE_BINDING_RATE = 0.9;
|
|
16
18
|
const MAX_CITATION_ERROR_RATE = 0.02;
|
|
19
|
+
const MIN_FULLTEXT_PROFILE_COMPLETENESS = 0.55;
|
|
20
|
+
const MIN_HYPOTHESIS_EVIDENCE = 2;
|
|
21
|
+
const MIN_HYPOTHESIS_DEPENDENCY_STEPS = 2;
|
|
22
|
+
const MIN_HYPOTHESIS_STATEMENT_CHARS = 48;
|
|
23
|
+
const PLACEHOLDER_TEXT_RE = /^(?:n\/a|na|none|not provided|not available|unknown|tbd|todo|null|nil|未提供|暂无|未知|无)$/iu;
|
|
24
|
+
function defaultRunProfile() {
|
|
25
|
+
return "strict";
|
|
26
|
+
}
|
|
27
|
+
function defaultTriggerState(nowMs = Date.now()) {
|
|
28
|
+
return {
|
|
29
|
+
consecutiveNewReviseDays: 0,
|
|
30
|
+
bridgeCount7d: 0,
|
|
31
|
+
unreadCoreBacklog: 0,
|
|
32
|
+
lastUpdatedAtMs: nowMs,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
17
35
|
function defaultQualityGateState() {
|
|
18
36
|
return {
|
|
19
37
|
passed: false,
|
|
@@ -23,9 +41,26 @@ function defaultQualityGateState() {
|
|
|
23
41
|
reasons: ["quality gate not evaluated"],
|
|
24
42
|
};
|
|
25
43
|
}
|
|
44
|
+
function defaultHypothesisGateState() {
|
|
45
|
+
return {
|
|
46
|
+
accepted: 0,
|
|
47
|
+
rejected: 0,
|
|
48
|
+
rejectionReasons: [],
|
|
49
|
+
};
|
|
50
|
+
}
|
|
26
51
|
function normalizeText(raw) {
|
|
27
52
|
return raw.trim().replace(/\s+/g, " ");
|
|
28
53
|
}
|
|
54
|
+
function cleanOptionalText(raw) {
|
|
55
|
+
if (typeof raw !== "string")
|
|
56
|
+
return undefined;
|
|
57
|
+
const normalized = normalizeText(raw);
|
|
58
|
+
if (!normalized)
|
|
59
|
+
return undefined;
|
|
60
|
+
if (PLACEHOLDER_TEXT_RE.test(normalized))
|
|
61
|
+
return undefined;
|
|
62
|
+
return normalized;
|
|
63
|
+
}
|
|
29
64
|
function sanitizeId(raw) {
|
|
30
65
|
return normalizeText(raw)
|
|
31
66
|
.toLowerCase()
|
|
@@ -106,6 +141,9 @@ async function loadState(projectPath) {
|
|
|
106
141
|
topic: normalizeText(rawStream.topic ?? "topic"),
|
|
107
142
|
topicKey,
|
|
108
143
|
projectId: sanitizeId(rawStream.projectId ?? "auto-topic-global-000000") || "auto-topic-global-000000",
|
|
144
|
+
lastRunProfile: rawStream.lastRunProfile === "strict" || rawStream.lastRunProfile === "fast"
|
|
145
|
+
? rawStream.lastRunProfile
|
|
146
|
+
: defaultRunProfile(),
|
|
109
147
|
totalRuns: typeof rawStream.totalRuns === "number" ? Math.max(0, Math.floor(rawStream.totalRuns)) : 0,
|
|
110
148
|
totalHypotheses: typeof rawStream.totalHypotheses === "number" ? Math.max(0, Math.floor(rawStream.totalHypotheses)) : 0,
|
|
111
149
|
knowledgeTopics: Array.isArray(rawStream.knowledgeTopics)
|
|
@@ -114,6 +152,26 @@ async function loadState(projectPath) {
|
|
|
114
152
|
paperNotes: Array.isArray(rawStream.paperNotes)
|
|
115
153
|
? rawStream.paperNotes.filter((item) => typeof item === "string").map((item) => normalizeText(item))
|
|
116
154
|
: [],
|
|
155
|
+
triggerState: rawStream.triggerState && typeof rawStream.triggerState === "object" && !Array.isArray(rawStream.triggerState)
|
|
156
|
+
? {
|
|
157
|
+
consecutiveNewReviseDays: typeof rawStream.triggerState.consecutiveNewReviseDays === "number" &&
|
|
158
|
+
Number.isFinite(rawStream.triggerState.consecutiveNewReviseDays)
|
|
159
|
+
? Math.max(0, Math.floor(rawStream.triggerState.consecutiveNewReviseDays))
|
|
160
|
+
: 0,
|
|
161
|
+
bridgeCount7d: typeof rawStream.triggerState.bridgeCount7d === "number" &&
|
|
162
|
+
Number.isFinite(rawStream.triggerState.bridgeCount7d)
|
|
163
|
+
? Math.max(0, Math.floor(rawStream.triggerState.bridgeCount7d))
|
|
164
|
+
: 0,
|
|
165
|
+
unreadCoreBacklog: typeof rawStream.triggerState.unreadCoreBacklog === "number" &&
|
|
166
|
+
Number.isFinite(rawStream.triggerState.unreadCoreBacklog)
|
|
167
|
+
? Math.max(0, Math.floor(rawStream.triggerState.unreadCoreBacklog))
|
|
168
|
+
: 0,
|
|
169
|
+
lastUpdatedAtMs: typeof rawStream.triggerState.lastUpdatedAtMs === "number" &&
|
|
170
|
+
Number.isFinite(rawStream.triggerState.lastUpdatedAtMs)
|
|
171
|
+
? Math.floor(rawStream.triggerState.lastUpdatedAtMs)
|
|
172
|
+
: Date.now(),
|
|
173
|
+
}
|
|
174
|
+
: defaultTriggerState(),
|
|
117
175
|
recentFullTextReadCount: typeof rawStream.recentFullTextReadCount === "number"
|
|
118
176
|
? Math.max(0, Math.floor(rawStream.recentFullTextReadCount))
|
|
119
177
|
: 0,
|
|
@@ -178,6 +236,39 @@ async function loadState(projectPath) {
|
|
|
178
236
|
.map(normalizeTrace)
|
|
179
237
|
.filter((item) => Boolean(item))
|
|
180
238
|
: [],
|
|
239
|
+
lastReflectionTasks: Array.isArray(rawStream.lastReflectionTasks)
|
|
240
|
+
? rawStream.lastReflectionTasks
|
|
241
|
+
.filter((item) => !!item && typeof item === "object")
|
|
242
|
+
.map((item) => ({
|
|
243
|
+
id: sanitizeId(item.id ?? "task"),
|
|
244
|
+
trigger: ["BRIDGE", "TREND", "CONTRADICTION", "UNREAD_CORE"].includes(item.trigger)
|
|
245
|
+
? item.trigger
|
|
246
|
+
: "TREND",
|
|
247
|
+
reason: normalizeText(item.reason ?? ""),
|
|
248
|
+
query: normalizeText(item.query ?? ""),
|
|
249
|
+
priority: ["high", "medium", "low"].includes(item.priority) ? item.priority : "medium",
|
|
250
|
+
status: (item.status === "executed" ? "executed" : "planned"),
|
|
251
|
+
}))
|
|
252
|
+
.filter((item) => item.reason.length > 0 && item.query.length > 0)
|
|
253
|
+
: [],
|
|
254
|
+
lastHypothesisGate: rawStream.lastHypothesisGate &&
|
|
255
|
+
typeof rawStream.lastHypothesisGate === "object" &&
|
|
256
|
+
!Array.isArray(rawStream.lastHypothesisGate)
|
|
257
|
+
? {
|
|
258
|
+
accepted: typeof rawStream.lastHypothesisGate.accepted === "number"
|
|
259
|
+
? Math.max(0, Math.floor(rawStream.lastHypothesisGate.accepted))
|
|
260
|
+
: 0,
|
|
261
|
+
rejected: typeof rawStream.lastHypothesisGate.rejected === "number"
|
|
262
|
+
? Math.max(0, Math.floor(rawStream.lastHypothesisGate.rejected))
|
|
263
|
+
: 0,
|
|
264
|
+
rejectionReasons: Array.isArray(rawStream.lastHypothesisGate.rejectionReasons)
|
|
265
|
+
? rawStream.lastHypothesisGate.rejectionReasons
|
|
266
|
+
.filter((item) => typeof item === "string")
|
|
267
|
+
.map((item) => normalizeText(item))
|
|
268
|
+
.filter((item) => item.length > 0)
|
|
269
|
+
: [],
|
|
270
|
+
}
|
|
271
|
+
: defaultHypothesisGateState(),
|
|
181
272
|
};
|
|
182
273
|
}
|
|
183
274
|
return {
|
|
@@ -208,8 +299,8 @@ function normalizeStringArray(raw) {
|
|
|
208
299
|
return undefined;
|
|
209
300
|
const values = raw
|
|
210
301
|
.filter((item) => typeof item === "string")
|
|
211
|
-
.map((item) =>
|
|
212
|
-
.filter((item) => item
|
|
302
|
+
.map((item) => cleanOptionalText(item))
|
|
303
|
+
.filter((item) => Boolean(item));
|
|
213
304
|
return values.length > 0 ? values : undefined;
|
|
214
305
|
}
|
|
215
306
|
function normalizeEvidenceAnchors(raw) {
|
|
@@ -218,14 +309,14 @@ function normalizeEvidenceAnchors(raw) {
|
|
|
218
309
|
const anchors = raw
|
|
219
310
|
.filter((item) => !!item && typeof item === "object")
|
|
220
311
|
.map((item) => {
|
|
221
|
-
const claim =
|
|
312
|
+
const claim = cleanOptionalText(item.claim);
|
|
222
313
|
if (!claim)
|
|
223
314
|
return undefined;
|
|
224
315
|
return {
|
|
225
|
-
...(item.section ? { section:
|
|
226
|
-
...(item.locator ? { locator:
|
|
316
|
+
...(cleanOptionalText(item.section) ? { section: cleanOptionalText(item.section) } : {}),
|
|
317
|
+
...(cleanOptionalText(item.locator) ? { locator: cleanOptionalText(item.locator) } : {}),
|
|
227
318
|
claim,
|
|
228
|
-
...(item.quote ? { quote:
|
|
319
|
+
...(cleanOptionalText(item.quote) ? { quote: cleanOptionalText(item.quote) } : {}),
|
|
229
320
|
};
|
|
230
321
|
})
|
|
231
322
|
.filter((item) => Boolean(item));
|
|
@@ -243,7 +334,7 @@ function toPaperNoteSlug(paper) {
|
|
|
243
334
|
}
|
|
244
335
|
function normalizePaper(input) {
|
|
245
336
|
const evidenceIds = Array.isArray(input.evidenceIds)
|
|
246
|
-
? input.evidenceIds.map((id) =>
|
|
337
|
+
? input.evidenceIds.map((id) => cleanOptionalText(id)).filter((id) => Boolean(id))
|
|
247
338
|
: undefined;
|
|
248
339
|
const keyEvidenceSpans = normalizeStringArray(input.keyEvidenceSpans);
|
|
249
340
|
const subdomains = normalizeStringArray(input.subdomains);
|
|
@@ -264,31 +355,33 @@ function normalizePaper(input) {
|
|
|
264
355
|
: readStatus
|
|
265
356
|
? false
|
|
266
357
|
: undefined;
|
|
267
|
-
const unreadReason =
|
|
358
|
+
const unreadReason = cleanOptionalText(input.unreadReason);
|
|
268
359
|
return {
|
|
269
|
-
...(input.id ? { id:
|
|
270
|
-
...(input.title ? { title:
|
|
271
|
-
...(input.url ? { url:
|
|
272
|
-
...(input.source ? { source:
|
|
273
|
-
...(input.publishedAt ? { publishedAt:
|
|
360
|
+
...(cleanOptionalText(input.id) ? { id: cleanOptionalText(input.id) } : {}),
|
|
361
|
+
...(cleanOptionalText(input.title) ? { title: cleanOptionalText(input.title) } : {}),
|
|
362
|
+
...(cleanOptionalText(input.url) ? { url: cleanOptionalText(input.url) } : {}),
|
|
363
|
+
...(cleanOptionalText(input.source) ? { source: cleanOptionalText(input.source) } : {}),
|
|
364
|
+
...(cleanOptionalText(input.publishedAt) ? { publishedAt: cleanOptionalText(input.publishedAt) } : {}),
|
|
274
365
|
...(typeof input.score === "number" && Number.isFinite(input.score)
|
|
275
366
|
? { score: Number(input.score.toFixed(2)) }
|
|
276
367
|
: {}),
|
|
277
|
-
...(input.reason ? { reason:
|
|
278
|
-
...(input.summary ? { summary:
|
|
368
|
+
...(cleanOptionalText(input.reason) ? { reason: cleanOptionalText(input.reason) } : {}),
|
|
369
|
+
...(cleanOptionalText(input.summary) ? { summary: cleanOptionalText(input.summary) } : {}),
|
|
279
370
|
...(evidenceIds && evidenceIds.length > 0 ? { evidenceIds } : {}),
|
|
280
371
|
...(typeof fullTextRead === "boolean" ? { fullTextRead } : {}),
|
|
281
372
|
...(readStatus ? { readStatus } : {}),
|
|
282
|
-
...(input.fullTextSource ? { fullTextSource:
|
|
283
|
-
...(input.fullTextRef ? { fullTextRef:
|
|
373
|
+
...(cleanOptionalText(input.fullTextSource) ? { fullTextSource: cleanOptionalText(input.fullTextSource) } : {}),
|
|
374
|
+
...(cleanOptionalText(input.fullTextRef) ? { fullTextRef: cleanOptionalText(input.fullTextRef) } : {}),
|
|
284
375
|
...(unreadReason ? { unreadReason } : {}),
|
|
285
376
|
...(keyEvidenceSpans && keyEvidenceSpans.length > 0 ? { keyEvidenceSpans } : {}),
|
|
286
|
-
...(input.domain ? { domain:
|
|
377
|
+
...(cleanOptionalText(input.domain) ? { domain: cleanOptionalText(input.domain) } : {}),
|
|
287
378
|
...(subdomains ? { subdomains } : {}),
|
|
288
379
|
...(crossDomainLinks ? { crossDomainLinks } : {}),
|
|
289
|
-
...(input.researchGoal ? { researchGoal:
|
|
290
|
-
...(input.approach ? { approach:
|
|
291
|
-
...(
|
|
380
|
+
...(cleanOptionalText(input.researchGoal) ? { researchGoal: cleanOptionalText(input.researchGoal) } : {}),
|
|
381
|
+
...(cleanOptionalText(input.approach) ? { approach: cleanOptionalText(input.approach) } : {}),
|
|
382
|
+
...(cleanOptionalText(input.methodologyDesign)
|
|
383
|
+
? { methodologyDesign: cleanOptionalText(input.methodologyDesign) }
|
|
384
|
+
: {}),
|
|
292
385
|
...(keyContributions ? { keyContributions } : {}),
|
|
293
386
|
...(practicalInsights ? { practicalInsights } : {}),
|
|
294
387
|
...(mustUnderstandPoints ? { mustUnderstandPoints } : {}),
|
|
@@ -401,6 +494,32 @@ function hasStructuredProfile(paper) {
|
|
|
401
494
|
(paper.limitations && paper.limitations.length > 0) ||
|
|
402
495
|
(paper.evidenceAnchors && paper.evidenceAnchors.length > 0));
|
|
403
496
|
}
|
|
497
|
+
function countStructuredProfileFields(paper) {
|
|
498
|
+
let count = 0;
|
|
499
|
+
if (paper.domain && paper.domain.trim())
|
|
500
|
+
count += 1;
|
|
501
|
+
if (paper.subdomains && paper.subdomains.length > 0)
|
|
502
|
+
count += 1;
|
|
503
|
+
if (paper.crossDomainLinks && paper.crossDomainLinks.length > 0)
|
|
504
|
+
count += 1;
|
|
505
|
+
if (paper.researchGoal && paper.researchGoal.trim())
|
|
506
|
+
count += 1;
|
|
507
|
+
if (paper.approach && paper.approach.trim())
|
|
508
|
+
count += 1;
|
|
509
|
+
if (paper.methodologyDesign && paper.methodologyDesign.trim())
|
|
510
|
+
count += 1;
|
|
511
|
+
if (paper.keyContributions && paper.keyContributions.length > 0)
|
|
512
|
+
count += 1;
|
|
513
|
+
if (paper.practicalInsights && paper.practicalInsights.length > 0)
|
|
514
|
+
count += 1;
|
|
515
|
+
if (paper.mustUnderstandPoints && paper.mustUnderstandPoints.length > 0)
|
|
516
|
+
count += 1;
|
|
517
|
+
if (paper.limitations && paper.limitations.length > 0)
|
|
518
|
+
count += 1;
|
|
519
|
+
if (paper.evidenceAnchors && paper.evidenceAnchors.length > 0)
|
|
520
|
+
count += 1;
|
|
521
|
+
return count;
|
|
522
|
+
}
|
|
404
523
|
function isFullTextRead(paper) {
|
|
405
524
|
return paper.fullTextRead === true || paper.readStatus === "fulltext";
|
|
406
525
|
}
|
|
@@ -447,6 +566,13 @@ function applyQualityGates(args) {
|
|
|
447
566
|
const fullTextCoreCount = corePapers.filter((paper) => isFullTextRead(paper)).length;
|
|
448
567
|
const fullTextCoverage = coreCount > 0 ? fullTextCoreCount / coreCount : 0;
|
|
449
568
|
const fullTextCoveragePct = Number((fullTextCoverage * 100).toFixed(2));
|
|
569
|
+
const fullTextCorePapers = corePapers.filter((paper) => isFullTextRead(paper));
|
|
570
|
+
const structuredFieldTotal = 11;
|
|
571
|
+
const avgFullTextProfileCompleteness = fullTextCorePapers.length > 0
|
|
572
|
+
? fullTextCorePapers.reduce((sum, paper) => sum + countStructuredProfileFields(paper) / structuredFieldTotal, 0) /
|
|
573
|
+
fullTextCorePapers.length
|
|
574
|
+
: 0;
|
|
575
|
+
const avgFullTextProfileCompletenessPct = Number((avgFullTextProfileCompleteness * 100).toFixed(2));
|
|
450
576
|
const unreadCorePaperIds = dedupeText(corePapers
|
|
451
577
|
.filter((paper) => !isFullTextRead(paper))
|
|
452
578
|
.map((paper) => paper.id?.trim() || paper.url?.trim() || paper.title?.trim() || "unknown-paper")).slice(0, 50);
|
|
@@ -522,15 +648,43 @@ function applyQualityGates(args) {
|
|
|
522
648
|
}
|
|
523
649
|
}
|
|
524
650
|
const reasons = [];
|
|
651
|
+
if (typeof args.requiredCorePapers === "number" &&
|
|
652
|
+
Number.isFinite(args.requiredCorePapers) &&
|
|
653
|
+
args.requiredCorePapers > 0 &&
|
|
654
|
+
coreCount < args.requiredCorePapers) {
|
|
655
|
+
reasons.push(`core_paper_count_below_required(${coreCount} < ${Math.floor(args.requiredCorePapers)})`);
|
|
656
|
+
}
|
|
525
657
|
if (fullTextCoverage < MIN_CORE_FULLTEXT_COVERAGE) {
|
|
526
658
|
reasons.push(`core_fulltext_coverage_below_threshold(${fullTextCoveragePct}% < ${Number((MIN_CORE_FULLTEXT_COVERAGE * 100).toFixed(0))}%)`);
|
|
527
659
|
}
|
|
660
|
+
if (fullTextCorePapers.length > 0 && avgFullTextProfileCompleteness < MIN_FULLTEXT_PROFILE_COMPLETENESS) {
|
|
661
|
+
reasons.push(`fulltext_profile_completeness_below_threshold(${avgFullTextProfileCompletenessPct}% < ${Number((MIN_FULLTEXT_PROFILE_COMPLETENESS * 100).toFixed(0))}%)`);
|
|
662
|
+
}
|
|
663
|
+
if (typeof args.requiredFullTextCoveragePct === "number" &&
|
|
664
|
+
Number.isFinite(args.requiredFullTextCoveragePct) &&
|
|
665
|
+
args.requiredFullTextCoveragePct > 0 &&
|
|
666
|
+
fullTextCoveragePct < args.requiredFullTextCoveragePct) {
|
|
667
|
+
reasons.push(`core_fulltext_coverage_below_required(${fullTextCoveragePct}% < ${Number(args.requiredFullTextCoveragePct.toFixed(2))}%)`);
|
|
668
|
+
}
|
|
528
669
|
if (evidenceBindingRate < MIN_EVIDENCE_BINDING_RATE) {
|
|
529
670
|
reasons.push(`evidence_binding_rate_below_threshold(${evidenceBindingRatePct}% < ${Number((MIN_EVIDENCE_BINDING_RATE * 100).toFixed(0))}%)`);
|
|
530
671
|
}
|
|
531
672
|
if (citationErrorRate >= MAX_CITATION_ERROR_RATE) {
|
|
532
673
|
reasons.push(`citation_error_rate_above_threshold(${citationErrorRatePct}% >= ${Number((MAX_CITATION_ERROR_RATE * 100).toFixed(0))}%)`);
|
|
533
674
|
}
|
|
675
|
+
const bridgeChangeCount = args.knowledgeChanges.filter((item) => item.type === "BRIDGE").length;
|
|
676
|
+
const reviseCount = args.knowledgeChanges.filter((item) => item.type === "REVISE").length;
|
|
677
|
+
const confirmCount = args.knowledgeChanges.filter((item) => item.type === "CONFIRM").length;
|
|
678
|
+
const executedReflectionCount = args.reflectionTasks.filter((task) => task.status === "executed").length;
|
|
679
|
+
if (bridgeChangeCount > 0 && executedReflectionCount === 0) {
|
|
680
|
+
reasons.push(`reflection_missing_for_bridge(bridge_count=${bridgeChangeCount})`);
|
|
681
|
+
}
|
|
682
|
+
if (reviseCount > 0 && confirmCount > 0 && executedReflectionCount === 0) {
|
|
683
|
+
reasons.push(`reflection_missing_for_conflict(revise_count=${reviseCount},confirm_count=${confirmCount})`);
|
|
684
|
+
}
|
|
685
|
+
if (args.hypothesisGate.rejected > 0 && args.hypothesisGate.accepted === 0 && args.hypotheses.length > 0) {
|
|
686
|
+
reasons.push(`hypothesis_gate_rejected_all(${args.hypothesisGate.rejected})`);
|
|
687
|
+
}
|
|
534
688
|
if (downgradedHighConfidenceCount > 0) {
|
|
535
689
|
reasons.push(`high_confidence_downgraded(${downgradedHighConfidenceCount})`);
|
|
536
690
|
}
|
|
@@ -642,10 +796,12 @@ function toSummary(stream) {
|
|
|
642
796
|
return {
|
|
643
797
|
projectId: stream.projectId,
|
|
644
798
|
streamKey: stream.topicKey,
|
|
799
|
+
runProfile: stream.lastRunProfile,
|
|
645
800
|
totalRuns: stream.totalRuns,
|
|
646
801
|
totalHypotheses: stream.totalHypotheses,
|
|
647
802
|
knowledgeTopicsCount: stream.knowledgeTopics.length,
|
|
648
803
|
paperNotesCount: stream.paperNotes.length,
|
|
804
|
+
triggerState: stream.triggerState,
|
|
649
805
|
recentFullTextReadCount: stream.recentFullTextReadCount,
|
|
650
806
|
recentNotFullTextReadCount: stream.recentNotFullTextReadCount,
|
|
651
807
|
qualityGate: stream.lastQualityGate,
|
|
@@ -656,6 +812,8 @@ function toSummary(stream) {
|
|
|
656
812
|
recentHypotheses: stream.recentHypotheses,
|
|
657
813
|
recentChangeStats: stream.recentChangeStats,
|
|
658
814
|
lastExplorationTrace: stream.lastExplorationTrace,
|
|
815
|
+
lastReflectionTasks: stream.lastReflectionTasks,
|
|
816
|
+
hypothesisGate: stream.lastHypothesisGate,
|
|
659
817
|
};
|
|
660
818
|
}
|
|
661
819
|
function countChangeStats(day, runId, changes) {
|
|
@@ -682,6 +840,277 @@ function countChangeStats(day, runId, changes) {
|
|
|
682
840
|
bridgeCount,
|
|
683
841
|
};
|
|
684
842
|
}
|
|
843
|
+
function tokenizeForQuery(raw) {
|
|
844
|
+
return normalizeText(raw)
|
|
845
|
+
.toLowerCase()
|
|
846
|
+
.replace(/[^a-z0-9\u4e00-\u9fff\s_-]+/g, " ")
|
|
847
|
+
.split(/\s+/)
|
|
848
|
+
.map((item) => item.trim())
|
|
849
|
+
.filter((item) => item.length >= 3);
|
|
850
|
+
}
|
|
851
|
+
function uniqueText(values) {
|
|
852
|
+
return [...new Set(values.map((item) => normalizeText(item)).filter((item) => item.length > 0))];
|
|
853
|
+
}
|
|
854
|
+
function buildReflectionQuery(topic, statement, fallbackHint) {
|
|
855
|
+
const topicTokens = tokenizeForQuery(topic).slice(0, 4);
|
|
856
|
+
const stmtTokens = tokenizeForQuery(statement).slice(0, 6);
|
|
857
|
+
const merged = uniqueText([...topicTokens, ...stmtTokens]);
|
|
858
|
+
if (merged.length === 0)
|
|
859
|
+
return `${topic} ${fallbackHint}`.trim();
|
|
860
|
+
return merged.join(" ");
|
|
861
|
+
}
|
|
862
|
+
function queryMatchesTrace(query, trace) {
|
|
863
|
+
const tokens = tokenizeForQuery(query).slice(0, 4);
|
|
864
|
+
if (tokens.length === 0)
|
|
865
|
+
return false;
|
|
866
|
+
return trace.some((step) => {
|
|
867
|
+
const hay = normalizeText(step.query).toLowerCase();
|
|
868
|
+
let hit = 0;
|
|
869
|
+
for (const token of tokens) {
|
|
870
|
+
if (hay.includes(token))
|
|
871
|
+
hit += 1;
|
|
872
|
+
if (hit >= Math.min(2, tokens.length))
|
|
873
|
+
return true;
|
|
874
|
+
}
|
|
875
|
+
return false;
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
function deriveReflectionTasks(args) {
|
|
879
|
+
const tasks = [];
|
|
880
|
+
const bridge = args.changes.filter((item) => item.type === "BRIDGE");
|
|
881
|
+
const revise = args.changes.filter((item) => item.type === "REVISE");
|
|
882
|
+
const confirm = args.changes.filter((item) => item.type === "CONFIRM");
|
|
883
|
+
const newly = args.changes.filter((item) => item.type === "NEW");
|
|
884
|
+
for (const [idx, change] of bridge.slice(0, 3).entries()) {
|
|
885
|
+
const query = buildReflectionQuery(args.topic, change.statement, "cross-domain mechanism");
|
|
886
|
+
tasks.push({
|
|
887
|
+
id: sanitizeId(`bridge-${idx + 1}-${query}`),
|
|
888
|
+
trigger: "BRIDGE",
|
|
889
|
+
reason: `Bridge signal requires cross-domain follow-up: ${change.statement}`,
|
|
890
|
+
query,
|
|
891
|
+
priority: "high",
|
|
892
|
+
status: queryMatchesTrace(query, args.trace) ? "executed" : "planned",
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
if (newly.length >= 3) {
|
|
896
|
+
const query = buildReflectionQuery(args.topic, newly.map((item) => item.statement).join(" "), "trend synthesis");
|
|
897
|
+
tasks.push({
|
|
898
|
+
id: sanitizeId(`trend-${query}`),
|
|
899
|
+
trigger: "TREND",
|
|
900
|
+
reason: `New findings accumulated (${newly.length}); run trend synthesis and gap scan.`,
|
|
901
|
+
query,
|
|
902
|
+
priority: "medium",
|
|
903
|
+
status: queryMatchesTrace(query, args.trace) ? "executed" : "planned",
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
if (revise.length > 0 && confirm.length > 0) {
|
|
907
|
+
const query = buildReflectionQuery(args.topic, `${revise[0]?.statement ?? ""} ${confirm[0]?.statement ?? ""}`, "contradiction resolution");
|
|
908
|
+
tasks.push({
|
|
909
|
+
id: sanitizeId(`contradiction-${query}`),
|
|
910
|
+
trigger: "CONTRADICTION",
|
|
911
|
+
reason: `Revise and confirm signals co-exist; verify contradiction boundaries.`,
|
|
912
|
+
query,
|
|
913
|
+
priority: "high",
|
|
914
|
+
status: queryMatchesTrace(query, args.trace) ? "executed" : "planned",
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
const unreadCore = args.corePapers.filter((paper) => !isFullTextRead(paper));
|
|
918
|
+
if (unreadCore.length > 0) {
|
|
919
|
+
const topId = unreadCore[0]?.id ?? unreadCore[0]?.title ?? "core-paper";
|
|
920
|
+
const query = buildReflectionQuery(args.topic, String(topId), "full text retrieval");
|
|
921
|
+
tasks.push({
|
|
922
|
+
id: sanitizeId(`unread-core-${query}`),
|
|
923
|
+
trigger: "UNREAD_CORE",
|
|
924
|
+
reason: `${unreadCore.length} core paper(s) were not fully read; prioritize retrieval and verification.`,
|
|
925
|
+
query,
|
|
926
|
+
priority: "medium",
|
|
927
|
+
status: queryMatchesTrace(query, args.trace) ? "executed" : "planned",
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
const dedup = new Map();
|
|
931
|
+
for (const task of tasks) {
|
|
932
|
+
const key = normalizeText(task.query).toLowerCase();
|
|
933
|
+
if (!key)
|
|
934
|
+
continue;
|
|
935
|
+
const existing = dedup.get(key);
|
|
936
|
+
if (!existing) {
|
|
937
|
+
dedup.set(key, task);
|
|
938
|
+
continue;
|
|
939
|
+
}
|
|
940
|
+
// Keep higher priority / executed status when duplicates collide.
|
|
941
|
+
const priorityRank = { high: 3, medium: 2, low: 1 };
|
|
942
|
+
const pick = (existing.status !== "executed" && task.status === "executed") ||
|
|
943
|
+
priorityRank[task.priority] > priorityRank[existing.priority]
|
|
944
|
+
? task
|
|
945
|
+
: existing;
|
|
946
|
+
dedup.set(key, pick);
|
|
947
|
+
}
|
|
948
|
+
return [...dedup.values()].slice(0, MAX_LAST_REFLECTION_TASKS);
|
|
949
|
+
}
|
|
950
|
+
function sanitizeKnowledgeChanges(args) {
|
|
951
|
+
if (args.changes.length === 0) {
|
|
952
|
+
return {
|
|
953
|
+
changes: [],
|
|
954
|
+
droppedBridgeCount: 0,
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
const paperLookup = buildPaperLookup(args.allRunPapers);
|
|
958
|
+
const next = [];
|
|
959
|
+
let droppedBridgeCount = 0;
|
|
960
|
+
for (const change of args.changes) {
|
|
961
|
+
if (change.type !== "BRIDGE") {
|
|
962
|
+
next.push(change);
|
|
963
|
+
continue;
|
|
964
|
+
}
|
|
965
|
+
const evidenceIds = (change.evidenceIds ?? []).map((id) => normalizedCitationToken(id)).filter((id) => id.length > 0);
|
|
966
|
+
if (evidenceIds.length === 0) {
|
|
967
|
+
droppedBridgeCount += 1;
|
|
968
|
+
continue;
|
|
969
|
+
}
|
|
970
|
+
let hasResolvedEvidence = false;
|
|
971
|
+
let hasFullTextEvidence = false;
|
|
972
|
+
for (const evidenceId of evidenceIds) {
|
|
973
|
+
const paper = paperLookup.get(evidenceId);
|
|
974
|
+
if (!paper)
|
|
975
|
+
continue;
|
|
976
|
+
hasResolvedEvidence = true;
|
|
977
|
+
if (isFullTextRead(paper))
|
|
978
|
+
hasFullTextEvidence = true;
|
|
979
|
+
}
|
|
980
|
+
// Guard against speculative bridge signals with no grounded full-text evidence.
|
|
981
|
+
if (!hasResolvedEvidence || !hasFullTextEvidence) {
|
|
982
|
+
droppedBridgeCount += 1;
|
|
983
|
+
continue;
|
|
984
|
+
}
|
|
985
|
+
next.push(change);
|
|
986
|
+
}
|
|
987
|
+
return {
|
|
988
|
+
changes: next,
|
|
989
|
+
droppedBridgeCount,
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
function applyHypothesisGate(args) {
|
|
993
|
+
const acceptedHypotheses = [];
|
|
994
|
+
const rejectionReasonSet = new Set();
|
|
995
|
+
const paperLookup = buildPaperLookup(args.allRunPapers);
|
|
996
|
+
const changeCounts = {
|
|
997
|
+
NEW: args.knowledgeChanges.filter((item) => item.type === "NEW").length,
|
|
998
|
+
CONFIRM: args.knowledgeChanges.filter((item) => item.type === "CONFIRM").length,
|
|
999
|
+
REVISE: args.knowledgeChanges.filter((item) => item.type === "REVISE").length,
|
|
1000
|
+
BRIDGE: args.knowledgeChanges.filter((item) => item.type === "BRIDGE").length,
|
|
1001
|
+
};
|
|
1002
|
+
for (const hypothesis of args.hypotheses) {
|
|
1003
|
+
const reasons = [];
|
|
1004
|
+
const statementLen = normalizeText(hypothesis.statement).length;
|
|
1005
|
+
if (statementLen < MIN_HYPOTHESIS_STATEMENT_CHARS) {
|
|
1006
|
+
reasons.push(`statement_too_short(${statementLen}<${MIN_HYPOTHESIS_STATEMENT_CHARS})`);
|
|
1007
|
+
}
|
|
1008
|
+
const evidenceIds = uniqueText((hypothesis.evidenceIds ?? []).map((id) => normalizedCitationToken(id)));
|
|
1009
|
+
if (evidenceIds.length < MIN_HYPOTHESIS_EVIDENCE) {
|
|
1010
|
+
reasons.push(`insufficient_evidence_ids(${evidenceIds.length}<${MIN_HYPOTHESIS_EVIDENCE})`);
|
|
1011
|
+
}
|
|
1012
|
+
let resolvedEvidence = 0;
|
|
1013
|
+
let fullTextSupported = 0;
|
|
1014
|
+
for (const evidenceId of evidenceIds) {
|
|
1015
|
+
const paper = paperLookup.get(evidenceId);
|
|
1016
|
+
if (!paper)
|
|
1017
|
+
continue;
|
|
1018
|
+
resolvedEvidence += 1;
|
|
1019
|
+
if (isFullTextRead(paper))
|
|
1020
|
+
fullTextSupported += 1;
|
|
1021
|
+
}
|
|
1022
|
+
if (resolvedEvidence < evidenceIds.length) {
|
|
1023
|
+
reasons.push(`unresolved_evidence_ids(${evidenceIds.length - resolvedEvidence})`);
|
|
1024
|
+
}
|
|
1025
|
+
if (fullTextSupported === 0 && args.runProfile === "strict") {
|
|
1026
|
+
reasons.push("no_fulltext_backed_evidence");
|
|
1027
|
+
}
|
|
1028
|
+
if (resolvedEvidence === 0) {
|
|
1029
|
+
reasons.push("no_resolved_evidence");
|
|
1030
|
+
}
|
|
1031
|
+
const dependencyPathLength = hypothesis.dependencyPath?.length ?? 0;
|
|
1032
|
+
if (dependencyPathLength < MIN_HYPOTHESIS_DEPENDENCY_STEPS) {
|
|
1033
|
+
reasons.push(`dependency_path_too_short(${dependencyPathLength}<${MIN_HYPOTHESIS_DEPENDENCY_STEPS})`);
|
|
1034
|
+
}
|
|
1035
|
+
const hasScore = typeof hypothesis.novelty === "number" &&
|
|
1036
|
+
typeof hypothesis.feasibility === "number" &&
|
|
1037
|
+
typeof hypothesis.impact === "number";
|
|
1038
|
+
if (!hasScore) {
|
|
1039
|
+
reasons.push("missing_self_assessment_scores");
|
|
1040
|
+
}
|
|
1041
|
+
if (hypothesis.trigger === "BRIDGE" && changeCounts.BRIDGE === 0) {
|
|
1042
|
+
reasons.push("trigger_bridge_without_bridge_change");
|
|
1043
|
+
}
|
|
1044
|
+
if (hypothesis.trigger === "TREND" && changeCounts.NEW < 2) {
|
|
1045
|
+
reasons.push("trigger_trend_without_new_accumulation");
|
|
1046
|
+
}
|
|
1047
|
+
if (hypothesis.trigger === "CONTRADICTION" && !(changeCounts.REVISE > 0 && changeCounts.CONFIRM > 0)) {
|
|
1048
|
+
reasons.push("trigger_contradiction_without_revise_confirm_pair");
|
|
1049
|
+
}
|
|
1050
|
+
if (hypothesis.trigger === "GAP" && changeCounts.NEW + changeCounts.REVISE < 2) {
|
|
1051
|
+
reasons.push("trigger_gap_without_gap_signal");
|
|
1052
|
+
}
|
|
1053
|
+
if (reasons.length > 0) {
|
|
1054
|
+
for (const reason of reasons)
|
|
1055
|
+
rejectionReasonSet.add(reason);
|
|
1056
|
+
continue;
|
|
1057
|
+
}
|
|
1058
|
+
acceptedHypotheses.push(hypothesis);
|
|
1059
|
+
}
|
|
1060
|
+
return {
|
|
1061
|
+
acceptedHypotheses,
|
|
1062
|
+
gate: {
|
|
1063
|
+
accepted: acceptedHypotheses.length,
|
|
1064
|
+
rejected: Math.max(0, args.hypotheses.length - acceptedHypotheses.length),
|
|
1065
|
+
rejectionReasons: [...rejectionReasonSet].slice(0, MAX_HYPOTHESIS_REJECTION_REASONS),
|
|
1066
|
+
},
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
function toDayStartMs(day) {
|
|
1070
|
+
const ts = Date.parse(`${day}T00:00:00.000Z`);
|
|
1071
|
+
return Number.isFinite(ts) ? ts : undefined;
|
|
1072
|
+
}
|
|
1073
|
+
function deriveTriggerState(args) {
|
|
1074
|
+
const sorted = [...args.recentChangeStats].sort((a, b) => b.day.localeCompare(a.day));
|
|
1075
|
+
// consecutive days from latest day with NEW/REVISE > 0
|
|
1076
|
+
let consecutiveNewReviseDays = 0;
|
|
1077
|
+
let expectedDayMs;
|
|
1078
|
+
for (const item of sorted) {
|
|
1079
|
+
const dayMs = toDayStartMs(item.day);
|
|
1080
|
+
if (dayMs === undefined)
|
|
1081
|
+
continue;
|
|
1082
|
+
const hasSignal = item.newCount > 0 || item.reviseCount > 0;
|
|
1083
|
+
if (!hasSignal)
|
|
1084
|
+
break;
|
|
1085
|
+
if (expectedDayMs === undefined) {
|
|
1086
|
+
consecutiveNewReviseDays = 1;
|
|
1087
|
+
expectedDayMs = dayMs - 24 * 60 * 60 * 1000;
|
|
1088
|
+
continue;
|
|
1089
|
+
}
|
|
1090
|
+
if (dayMs === expectedDayMs) {
|
|
1091
|
+
consecutiveNewReviseDays += 1;
|
|
1092
|
+
expectedDayMs = dayMs - 24 * 60 * 60 * 1000;
|
|
1093
|
+
continue;
|
|
1094
|
+
}
|
|
1095
|
+
break;
|
|
1096
|
+
}
|
|
1097
|
+
const sevenDaysAgo = args.nowMs - 7 * 24 * 60 * 60 * 1000;
|
|
1098
|
+
let bridgeCount7d = 0;
|
|
1099
|
+
for (const item of sorted) {
|
|
1100
|
+
const dayMs = toDayStartMs(item.day);
|
|
1101
|
+
if (dayMs === undefined)
|
|
1102
|
+
continue;
|
|
1103
|
+
if (dayMs < sevenDaysAgo)
|
|
1104
|
+
continue;
|
|
1105
|
+
bridgeCount7d += Math.max(0, Math.floor(item.bridgeCount));
|
|
1106
|
+
}
|
|
1107
|
+
return {
|
|
1108
|
+
consecutiveNewReviseDays,
|
|
1109
|
+
bridgeCount7d,
|
|
1110
|
+
unreadCoreBacklog: Math.max(0, Math.floor(args.unreadCoreBacklog)),
|
|
1111
|
+
lastUpdatedAtMs: args.nowMs,
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
685
1114
|
export async function commitKnowledgeRun(input) {
|
|
686
1115
|
const project = await resolveProjectContext({
|
|
687
1116
|
projectId: input.projectId,
|
|
@@ -706,7 +1135,7 @@ export async function commitKnowledgeRun(input) {
|
|
|
706
1135
|
const explorationTrace = (knowledgeState.explorationTrace ?? [])
|
|
707
1136
|
.map(normalizeTrace)
|
|
708
1137
|
.filter((item) => Boolean(item));
|
|
709
|
-
const
|
|
1138
|
+
const submittedKnowledgeChanges = (knowledgeState.knowledgeChanges ?? [])
|
|
710
1139
|
.map(normalizeChange)
|
|
711
1140
|
.filter((item) => Boolean(item));
|
|
712
1141
|
const knowledgeUpdates = (knowledgeState.knowledgeUpdates ?? [])
|
|
@@ -733,10 +1162,12 @@ export async function commitKnowledgeRun(input) {
|
|
|
733
1162
|
topic: normalizeText(input.topic),
|
|
734
1163
|
topicKey: streamKey,
|
|
735
1164
|
projectId: project.projectId,
|
|
1165
|
+
lastRunProfile: defaultRunProfile(),
|
|
736
1166
|
totalRuns: 0,
|
|
737
1167
|
totalHypotheses: 0,
|
|
738
1168
|
knowledgeTopics: [],
|
|
739
1169
|
paperNotes: [],
|
|
1170
|
+
triggerState: defaultTriggerState(),
|
|
740
1171
|
recentFullTextReadCount: 0,
|
|
741
1172
|
recentNotFullTextReadCount: 0,
|
|
742
1173
|
lastQualityGate: defaultQualityGateState(),
|
|
@@ -747,14 +1178,16 @@ export async function commitKnowledgeRun(input) {
|
|
|
747
1178
|
recentHypotheses: [],
|
|
748
1179
|
recentChangeStats: [],
|
|
749
1180
|
lastExplorationTrace: [],
|
|
1181
|
+
lastReflectionTasks: [],
|
|
1182
|
+
lastHypothesisGate: defaultHypothesisGateState(),
|
|
750
1183
|
};
|
|
751
1184
|
const paperIds = mergePapers(corePapers, explorationPapers)
|
|
752
1185
|
.map((paper) => paper.id || paper.url || paper.title || "")
|
|
753
1186
|
.map((value) => normalizeText(value))
|
|
754
1187
|
.filter((value) => value.length > 0);
|
|
755
|
-
const
|
|
756
|
-
|
|
757
|
-
|
|
1188
|
+
const explicitRunId = input.runId?.trim() ? sanitizeId(input.runId) : undefined;
|
|
1189
|
+
let runId = explicitRunId ??
|
|
1190
|
+
buildRunFingerprint({
|
|
758
1191
|
scope: stream.scope,
|
|
759
1192
|
topic: stream.topic,
|
|
760
1193
|
status: input.status,
|
|
@@ -762,15 +1195,30 @@ export async function commitKnowledgeRun(input) {
|
|
|
762
1195
|
paperIds,
|
|
763
1196
|
note: input.note,
|
|
764
1197
|
});
|
|
1198
|
+
const inferredRunProfile = input.knowledgeState?.runLog?.runProfile === "strict" || input.knowledgeState?.runLog?.runProfile === "fast"
|
|
1199
|
+
? input.knowledgeState.runLog.runProfile
|
|
1200
|
+
: input.knowledgeState?.runLog?.requiredCorePapers !== undefined ||
|
|
1201
|
+
input.knowledgeState?.runLog?.requiredFullTextCoveragePct !== undefined
|
|
1202
|
+
? "strict"
|
|
1203
|
+
: defaultRunProfile();
|
|
765
1204
|
if (stream.recentRunIds.includes(runId)) {
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
streamKey
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
1205
|
+
// Preserve idempotency for fingerprint-based runs, but avoid silently dropping
|
|
1206
|
+
// valid new cron cycles that accidentally reuse an explicit run_id.
|
|
1207
|
+
if (!explicitRunId) {
|
|
1208
|
+
root.streams[streamKey] = stream;
|
|
1209
|
+
return {
|
|
1210
|
+
projectId: project.projectId,
|
|
1211
|
+
streamKey,
|
|
1212
|
+
summary: toSummary(stream),
|
|
1213
|
+
runId,
|
|
1214
|
+
createdProject: project.created,
|
|
1215
|
+
};
|
|
1216
|
+
}
|
|
1217
|
+
const collisionTag = createHash("sha1")
|
|
1218
|
+
.update(`${nowMs}\n${paperIds.join("|")}\n${input.status ?? ""}\n${input.note ?? ""}`)
|
|
1219
|
+
.digest("hex")
|
|
1220
|
+
.slice(0, 8);
|
|
1221
|
+
runId = `${runId}-r${collisionTag}`;
|
|
774
1222
|
}
|
|
775
1223
|
const rootPath = getKnowledgeStateRoot(project.projectPath);
|
|
776
1224
|
const logDir = path.join(rootPath, "logs");
|
|
@@ -785,15 +1233,48 @@ export async function commitKnowledgeRun(input) {
|
|
|
785
1233
|
trace: explorationTrace,
|
|
786
1234
|
papers: explorationPapers,
|
|
787
1235
|
}));
|
|
788
|
-
await appendMarkdown(path.join(dailyDir, `day-${dayKey}.md`), renderDailyChangesMarkdown({ now: nowIso, runId, topic: stream.topic, changes: knowledgeChanges }));
|
|
789
1236
|
const mergedRunPapers = mergePapers(corePapers, explorationPapers);
|
|
1237
|
+
const changeSanitization = sanitizeKnowledgeChanges({
|
|
1238
|
+
changes: submittedKnowledgeChanges,
|
|
1239
|
+
allRunPapers: mergedRunPapers,
|
|
1240
|
+
});
|
|
1241
|
+
const knowledgeChanges = changeSanitization.changes;
|
|
1242
|
+
await appendMarkdown(path.join(dailyDir, `day-${dayKey}.md`), renderDailyChangesMarkdown({ now: nowIso, runId, topic: stream.topic, changes: knowledgeChanges }));
|
|
1243
|
+
const reflectionTasks = deriveReflectionTasks({
|
|
1244
|
+
topic: stream.topic,
|
|
1245
|
+
changes: knowledgeChanges,
|
|
1246
|
+
trace: explorationTrace,
|
|
1247
|
+
corePapers,
|
|
1248
|
+
});
|
|
1249
|
+
await appendMarkdown(path.join(logDir, `day-${dayKey}-reflection.md`), renderReflectionLogMarkdown({
|
|
1250
|
+
now: nowIso,
|
|
1251
|
+
runId,
|
|
1252
|
+
tasks: reflectionTasks,
|
|
1253
|
+
}));
|
|
1254
|
+
const submittedHypotheses = hypotheses;
|
|
1255
|
+
const hypothesisEval = applyHypothesisGate({
|
|
1256
|
+
hypotheses: submittedHypotheses,
|
|
1257
|
+
allRunPapers: mergedRunPapers,
|
|
1258
|
+
knowledgeChanges,
|
|
1259
|
+
runProfile: inferredRunProfile,
|
|
1260
|
+
});
|
|
1261
|
+
const acceptedHypotheses = hypothesisEval.acceptedHypotheses;
|
|
790
1262
|
const qualityEval = applyQualityGates({
|
|
791
1263
|
corePapers,
|
|
792
1264
|
allRunPapers: mergedRunPapers,
|
|
1265
|
+
explorationTrace,
|
|
1266
|
+
reflectionTasks,
|
|
793
1267
|
knowledgeChanges,
|
|
794
1268
|
knowledgeUpdates,
|
|
795
|
-
hypotheses,
|
|
1269
|
+
hypotheses: acceptedHypotheses,
|
|
1270
|
+
hypothesisGate: hypothesisEval.gate,
|
|
1271
|
+
requiredCorePapers: input.knowledgeState?.runLog?.requiredCorePapers,
|
|
1272
|
+
requiredFullTextCoveragePct: input.knowledgeState?.runLog?.requiredFullTextCoveragePct,
|
|
796
1273
|
});
|
|
1274
|
+
if (changeSanitization.droppedBridgeCount > 0) {
|
|
1275
|
+
qualityEval.qualityGate.passed = false;
|
|
1276
|
+
qualityEval.qualityGate.reasons.push(`bridge_dropped_due_to_ungrounded_evidence(${changeSanitization.droppedBridgeCount})`);
|
|
1277
|
+
}
|
|
797
1278
|
const requestedStatus = normalizeText(input.status ?? "ok");
|
|
798
1279
|
const qualitySensitiveStatus = requestedStatus === "ok" || requestedStatus === "fallback_representative";
|
|
799
1280
|
const effectiveStatus = qualitySensitiveStatus && !qualityEval.qualityGate.passed ? "degraded_quality" : requestedStatus;
|
|
@@ -836,7 +1317,7 @@ export async function commitKnowledgeRun(input) {
|
|
|
836
1317
|
const recentHypothesisSummaries = [];
|
|
837
1318
|
let seq = stream.totalHypotheses;
|
|
838
1319
|
const dayToken = dayKey.replace(/-/g, "");
|
|
839
|
-
for (const hypothesis of
|
|
1320
|
+
for (const hypothesis of acceptedHypotheses) {
|
|
840
1321
|
seq += 1;
|
|
841
1322
|
const hypothesisId = hypothesis.id && hypothesis.id.length > 0 ? sanitizeId(hypothesis.id) : `hyp-${dayToken}-${String(seq).padStart(4, "0")}`;
|
|
842
1323
|
const file = `${hypothesisId}.md`;
|
|
@@ -854,6 +1335,7 @@ export async function commitKnowledgeRun(input) {
|
|
|
854
1335
|
await writeFile(path.join(knowledgeDir, "_index.md"), renderKnowledgeIndexMarkdown({
|
|
855
1336
|
now: nowIso,
|
|
856
1337
|
topic: stream.topic,
|
|
1338
|
+
runProfile: inferredRunProfile,
|
|
857
1339
|
topicFiles: stream.knowledgeTopics,
|
|
858
1340
|
paperNotesCount: stream.paperNotes.length,
|
|
859
1341
|
totalHypotheses: stream.totalHypotheses + recentHypothesisSummaries.length,
|
|
@@ -862,10 +1344,13 @@ export async function commitKnowledgeRun(input) {
|
|
|
862
1344
|
notFullTextReadCount: fullTextStats.notFullTextReadCount,
|
|
863
1345
|
qualityGate: qualityEval.qualityGate,
|
|
864
1346
|
unreadCorePaperIds: qualityEval.unreadCorePaperIds,
|
|
1347
|
+
reflectionTasks,
|
|
1348
|
+
hypothesisGate: hypothesisEval.gate,
|
|
865
1349
|
lastStatus: effectiveStatus,
|
|
866
1350
|
}), "utf-8");
|
|
867
1351
|
const changeStat = countChangeStats(dayKey, runId, knowledgeChanges);
|
|
868
1352
|
stream.projectId = project.projectId;
|
|
1353
|
+
stream.lastRunProfile = inferredRunProfile;
|
|
869
1354
|
stream.totalRuns += 1;
|
|
870
1355
|
stream.totalHypotheses += recentHypothesisSummaries.length;
|
|
871
1356
|
stream.lastRunAtMs = nowMs;
|
|
@@ -875,6 +1360,8 @@ export async function commitKnowledgeRun(input) {
|
|
|
875
1360
|
stream.lastQualityGate = qualityEval.qualityGate;
|
|
876
1361
|
stream.lastUnreadCorePaperIds = qualityEval.unreadCorePaperIds;
|
|
877
1362
|
stream.lastExplorationTrace = explorationTrace.slice(0, MAX_LAST_TRACE);
|
|
1363
|
+
stream.lastReflectionTasks = reflectionTasks.slice(0, MAX_LAST_REFLECTION_TASKS);
|
|
1364
|
+
stream.lastHypothesisGate = hypothesisEval.gate;
|
|
878
1365
|
stream.recentPapers = mergePapers(mergedRunPapers, stream.recentPapers).slice(0, MAX_RECENT_PAPERS);
|
|
879
1366
|
stream.recentRunIds = [runId, ...stream.recentRunIds.filter((id) => id !== runId)].slice(0, MAX_RECENT_RUN_IDS);
|
|
880
1367
|
stream.recentHypothesisIds = [
|
|
@@ -883,11 +1370,18 @@ export async function commitKnowledgeRun(input) {
|
|
|
883
1370
|
].slice(0, MAX_RECENT_HYPOTHESES);
|
|
884
1371
|
stream.recentHypotheses = [...recentHypothesisSummaries, ...stream.recentHypotheses].slice(0, MAX_RECENT_HYPOTHESES);
|
|
885
1372
|
stream.recentChangeStats = [changeStat, ...stream.recentChangeStats].slice(0, MAX_RECENT_CHANGE_STATS);
|
|
1373
|
+
stream.triggerState = deriveTriggerState({
|
|
1374
|
+
recentChangeStats: stream.recentChangeStats,
|
|
1375
|
+
unreadCoreBacklog: qualityEval.unreadCorePaperIds.length,
|
|
1376
|
+
nowMs,
|
|
1377
|
+
});
|
|
886
1378
|
root.streams[streamKey] = stream;
|
|
887
1379
|
await saveStateAtomic(project.projectPath, root);
|
|
888
1380
|
await appendFile(path.join(logDir, `day-${dayKey}-run-details.jsonl`), `${JSON.stringify({
|
|
889
1381
|
ts: nowMs,
|
|
1382
|
+
run_id: runId,
|
|
890
1383
|
runId,
|
|
1384
|
+
run_profile: inferredRunProfile,
|
|
891
1385
|
scope: stream.scope,
|
|
892
1386
|
topic: stream.topic,
|
|
893
1387
|
streamKey,
|
|
@@ -895,9 +1389,14 @@ export async function commitKnowledgeRun(input) {
|
|
|
895
1389
|
corePapers,
|
|
896
1390
|
explorationPapers,
|
|
897
1391
|
explorationTrace,
|
|
1392
|
+
reflectionTasks,
|
|
1393
|
+
submittedKnowledgeChanges,
|
|
898
1394
|
knowledgeChanges,
|
|
1395
|
+
droppedBridgeCount: changeSanitization.droppedBridgeCount,
|
|
899
1396
|
knowledgeUpdates,
|
|
900
|
-
hypotheses,
|
|
1397
|
+
hypotheses: acceptedHypotheses,
|
|
1398
|
+
submittedHypotheses,
|
|
1399
|
+
hypothesisGate: hypothesisEval.gate,
|
|
901
1400
|
paperNoteFiles: runPaperNoteFiles,
|
|
902
1401
|
quality: {
|
|
903
1402
|
fullTextReadCount: fullTextStats.fullTextReadCount,
|
|
@@ -913,7 +1412,9 @@ export async function commitKnowledgeRun(input) {
|
|
|
913
1412
|
})}\n`, "utf-8");
|
|
914
1413
|
await appendEvent(project.projectPath, {
|
|
915
1414
|
ts: nowMs,
|
|
1415
|
+
run_id: runId,
|
|
916
1416
|
runId,
|
|
1417
|
+
run_profile: inferredRunProfile,
|
|
917
1418
|
scope: stream.scope,
|
|
918
1419
|
topic: stream.topic,
|
|
919
1420
|
streamKey,
|
|
@@ -929,8 +1430,14 @@ export async function commitKnowledgeRun(input) {
|
|
|
929
1430
|
qualityGate: qualityEval.qualityGate,
|
|
930
1431
|
unreadCorePaperIds: qualityEval.unreadCorePaperIds,
|
|
931
1432
|
downgradedHighConfidenceCount: qualityEval.downgradedHighConfidenceCount,
|
|
1433
|
+
submittedChangeCount: submittedKnowledgeChanges.length,
|
|
932
1434
|
changeCount: knowledgeChanges.length,
|
|
1435
|
+
droppedBridgeCount: changeSanitization.droppedBridgeCount,
|
|
933
1436
|
hypothesisCount: recentHypothesisSummaries.length,
|
|
1437
|
+
submittedHypothesisCount: submittedHypotheses.length,
|
|
1438
|
+
hypothesisGate: hypothesisEval.gate,
|
|
1439
|
+
triggerState: stream.triggerState,
|
|
1440
|
+
reflectionTasks,
|
|
934
1441
|
corePapers,
|
|
935
1442
|
explorationPapers,
|
|
936
1443
|
note: input.note,
|