scientify 1.12.1 → 1.13.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.
- package/README.md +6 -2
- 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.map +1 -1
- package/dist/src/hooks/research-mode.js +55 -37
- 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 +1 -0
- package/dist/src/knowledge-state/render.d.ts.map +1 -1
- package/dist/src/knowledge-state/render.js +101 -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 +308 -66
- package/dist/src/knowledge-state/store.js.map +1 -1
- package/dist/src/knowledge-state/types.d.ts +40 -0
- package/dist/src/knowledge-state/types.d.ts.map +1 -1
- package/dist/src/literature/subscription-state.d.ts.map +1 -1
- package/dist/src/literature/subscription-state.js +949 -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 +146 -221
- 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 +4 -2
- package/dist/src/tools/scientify-cron.d.ts.map +1 -1
- package/dist/src/tools/scientify-cron.js +369 -17
- package/dist/src/tools/scientify-cron.js.map +1 -1
- package/dist/src/tools/scientify-literature-state.d.ts +40 -0
- package/dist/src/tools/scientify-literature-state.d.ts.map +1 -1
- package/dist/src/tools/scientify-literature-state.js +215 -72
- 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 +11 -3
|
@@ -16,11 +16,30 @@ const MAX_HYPOTHESIS_REJECTION_REASONS = 24;
|
|
|
16
16
|
const MIN_CORE_FULLTEXT_COVERAGE = 0.8;
|
|
17
17
|
const MIN_EVIDENCE_BINDING_RATE = 0.9;
|
|
18
18
|
const MAX_CITATION_ERROR_RATE = 0.02;
|
|
19
|
+
const FATAL_CITATION_ERROR_RATE = 0.2;
|
|
20
|
+
const MIN_FULLTEXT_PROFILE_COMPLETENESS = 0.55;
|
|
19
21
|
const MIN_HYPOTHESIS_EVIDENCE = 2;
|
|
20
22
|
const MIN_HYPOTHESIS_DEPENDENCY_STEPS = 2;
|
|
21
23
|
const MIN_HYPOTHESIS_STATEMENT_CHARS = 48;
|
|
24
|
+
const PLACEHOLDER_TEXT_RE = /^(?:n\/a|na|none|not provided|not available|unknown|tbd|todo|null|nil|未提供|暂无|未知|无)$/iu;
|
|
25
|
+
function defaultRunProfile() {
|
|
26
|
+
return "strict";
|
|
27
|
+
}
|
|
28
|
+
function defaultTriggerState(nowMs = Date.now()) {
|
|
29
|
+
return {
|
|
30
|
+
consecutiveNewReviseDays: 0,
|
|
31
|
+
bridgeCount7d: 0,
|
|
32
|
+
unreadCoreBacklog: 0,
|
|
33
|
+
lastUpdatedAtMs: nowMs,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
22
36
|
function defaultQualityGateState() {
|
|
23
37
|
return {
|
|
38
|
+
mode: "soft",
|
|
39
|
+
severity: "warn",
|
|
40
|
+
warnings: ["quality gate not evaluated"],
|
|
41
|
+
fatalReasons: [],
|
|
42
|
+
blocking: false,
|
|
24
43
|
passed: false,
|
|
25
44
|
fullTextCoveragePct: 0,
|
|
26
45
|
evidenceBindingRatePct: 0,
|
|
@@ -38,6 +57,16 @@ function defaultHypothesisGateState() {
|
|
|
38
57
|
function normalizeText(raw) {
|
|
39
58
|
return raw.trim().replace(/\s+/g, " ");
|
|
40
59
|
}
|
|
60
|
+
function cleanOptionalText(raw) {
|
|
61
|
+
if (typeof raw !== "string")
|
|
62
|
+
return undefined;
|
|
63
|
+
const normalized = normalizeText(raw);
|
|
64
|
+
if (!normalized)
|
|
65
|
+
return undefined;
|
|
66
|
+
if (PLACEHOLDER_TEXT_RE.test(normalized))
|
|
67
|
+
return undefined;
|
|
68
|
+
return normalized;
|
|
69
|
+
}
|
|
41
70
|
function sanitizeId(raw) {
|
|
42
71
|
return normalizeText(raw)
|
|
43
72
|
.toLowerCase()
|
|
@@ -118,6 +147,9 @@ async function loadState(projectPath) {
|
|
|
118
147
|
topic: normalizeText(rawStream.topic ?? "topic"),
|
|
119
148
|
topicKey,
|
|
120
149
|
projectId: sanitizeId(rawStream.projectId ?? "auto-topic-global-000000") || "auto-topic-global-000000",
|
|
150
|
+
lastRunProfile: rawStream.lastRunProfile === "strict" || rawStream.lastRunProfile === "fast"
|
|
151
|
+
? rawStream.lastRunProfile
|
|
152
|
+
: defaultRunProfile(),
|
|
121
153
|
totalRuns: typeof rawStream.totalRuns === "number" ? Math.max(0, Math.floor(rawStream.totalRuns)) : 0,
|
|
122
154
|
totalHypotheses: typeof rawStream.totalHypotheses === "number" ? Math.max(0, Math.floor(rawStream.totalHypotheses)) : 0,
|
|
123
155
|
knowledgeTopics: Array.isArray(rawStream.knowledgeTopics)
|
|
@@ -126,6 +158,26 @@ async function loadState(projectPath) {
|
|
|
126
158
|
paperNotes: Array.isArray(rawStream.paperNotes)
|
|
127
159
|
? rawStream.paperNotes.filter((item) => typeof item === "string").map((item) => normalizeText(item))
|
|
128
160
|
: [],
|
|
161
|
+
triggerState: rawStream.triggerState && typeof rawStream.triggerState === "object" && !Array.isArray(rawStream.triggerState)
|
|
162
|
+
? {
|
|
163
|
+
consecutiveNewReviseDays: typeof rawStream.triggerState.consecutiveNewReviseDays === "number" &&
|
|
164
|
+
Number.isFinite(rawStream.triggerState.consecutiveNewReviseDays)
|
|
165
|
+
? Math.max(0, Math.floor(rawStream.triggerState.consecutiveNewReviseDays))
|
|
166
|
+
: 0,
|
|
167
|
+
bridgeCount7d: typeof rawStream.triggerState.bridgeCount7d === "number" &&
|
|
168
|
+
Number.isFinite(rawStream.triggerState.bridgeCount7d)
|
|
169
|
+
? Math.max(0, Math.floor(rawStream.triggerState.bridgeCount7d))
|
|
170
|
+
: 0,
|
|
171
|
+
unreadCoreBacklog: typeof rawStream.triggerState.unreadCoreBacklog === "number" &&
|
|
172
|
+
Number.isFinite(rawStream.triggerState.unreadCoreBacklog)
|
|
173
|
+
? Math.max(0, Math.floor(rawStream.triggerState.unreadCoreBacklog))
|
|
174
|
+
: 0,
|
|
175
|
+
lastUpdatedAtMs: typeof rawStream.triggerState.lastUpdatedAtMs === "number" &&
|
|
176
|
+
Number.isFinite(rawStream.triggerState.lastUpdatedAtMs)
|
|
177
|
+
? Math.floor(rawStream.triggerState.lastUpdatedAtMs)
|
|
178
|
+
: Date.now(),
|
|
179
|
+
}
|
|
180
|
+
: defaultTriggerState(),
|
|
129
181
|
recentFullTextReadCount: typeof rawStream.recentFullTextReadCount === "number"
|
|
130
182
|
? Math.max(0, Math.floor(rawStream.recentFullTextReadCount))
|
|
131
183
|
: 0,
|
|
@@ -135,27 +187,60 @@ async function loadState(projectPath) {
|
|
|
135
187
|
lastQualityGate: rawStream.lastQualityGate &&
|
|
136
188
|
typeof rawStream.lastQualityGate === "object" &&
|
|
137
189
|
!Array.isArray(rawStream.lastQualityGate)
|
|
138
|
-
? {
|
|
139
|
-
|
|
140
|
-
fullTextCoveragePct: typeof rawStream.lastQualityGate.fullTextCoveragePct === "number" &&
|
|
141
|
-
Number.isFinite(rawStream.lastQualityGate.fullTextCoveragePct)
|
|
142
|
-
? Number(rawStream.lastQualityGate.fullTextCoveragePct.toFixed(2))
|
|
143
|
-
: 0,
|
|
144
|
-
evidenceBindingRatePct: typeof rawStream.lastQualityGate.evidenceBindingRatePct === "number" &&
|
|
145
|
-
Number.isFinite(rawStream.lastQualityGate.evidenceBindingRatePct)
|
|
146
|
-
? Number(rawStream.lastQualityGate.evidenceBindingRatePct.toFixed(2))
|
|
147
|
-
: 0,
|
|
148
|
-
citationErrorRatePct: typeof rawStream.lastQualityGate.citationErrorRatePct === "number" &&
|
|
149
|
-
Number.isFinite(rawStream.lastQualityGate.citationErrorRatePct)
|
|
150
|
-
? Number(rawStream.lastQualityGate.citationErrorRatePct.toFixed(2))
|
|
151
|
-
: 0,
|
|
152
|
-
reasons: Array.isArray(rawStream.lastQualityGate.reasons)
|
|
190
|
+
? (() => {
|
|
191
|
+
const reasons = Array.isArray(rawStream.lastQualityGate.reasons)
|
|
153
192
|
? rawStream.lastQualityGate.reasons
|
|
154
193
|
.filter((item) => typeof item === "string")
|
|
155
194
|
.map((item) => normalizeText(item))
|
|
156
195
|
.filter((item) => item.length > 0)
|
|
157
|
-
: []
|
|
158
|
-
|
|
196
|
+
: [];
|
|
197
|
+
const warnings = Array.isArray(rawStream.lastQualityGate.warnings)
|
|
198
|
+
? rawStream.lastQualityGate.warnings
|
|
199
|
+
.filter((item) => typeof item === "string")
|
|
200
|
+
.map((item) => normalizeText(item))
|
|
201
|
+
.filter((item) => item.length > 0)
|
|
202
|
+
: reasons;
|
|
203
|
+
const fatalReasons = Array.isArray(rawStream.lastQualityGate.fatalReasons)
|
|
204
|
+
? rawStream.lastQualityGate.fatalReasons
|
|
205
|
+
.filter((item) => typeof item === "string")
|
|
206
|
+
.map((item) => normalizeText(item))
|
|
207
|
+
.filter((item) => item.length > 0)
|
|
208
|
+
: [];
|
|
209
|
+
const blocking = rawStream.lastQualityGate.blocking === true || fatalReasons.length > 0;
|
|
210
|
+
const severityRaw = typeof rawStream.lastQualityGate.severity === "string"
|
|
211
|
+
? rawStream.lastQualityGate.severity.toLowerCase()
|
|
212
|
+
: undefined;
|
|
213
|
+
const severity = severityRaw === "fatal" || blocking
|
|
214
|
+
? "fatal"
|
|
215
|
+
: severityRaw === "ok"
|
|
216
|
+
? "ok"
|
|
217
|
+
: warnings.length > 0
|
|
218
|
+
? "warn"
|
|
219
|
+
: "ok";
|
|
220
|
+
return {
|
|
221
|
+
mode: "soft",
|
|
222
|
+
severity,
|
|
223
|
+
warnings,
|
|
224
|
+
fatalReasons,
|
|
225
|
+
blocking,
|
|
226
|
+
passed: typeof rawStream.lastQualityGate.passed === "boolean"
|
|
227
|
+
? rawStream.lastQualityGate.passed
|
|
228
|
+
: fatalReasons.length === 0,
|
|
229
|
+
fullTextCoveragePct: typeof rawStream.lastQualityGate.fullTextCoveragePct === "number" &&
|
|
230
|
+
Number.isFinite(rawStream.lastQualityGate.fullTextCoveragePct)
|
|
231
|
+
? Number(rawStream.lastQualityGate.fullTextCoveragePct.toFixed(2))
|
|
232
|
+
: 0,
|
|
233
|
+
evidenceBindingRatePct: typeof rawStream.lastQualityGate.evidenceBindingRatePct === "number" &&
|
|
234
|
+
Number.isFinite(rawStream.lastQualityGate.evidenceBindingRatePct)
|
|
235
|
+
? Number(rawStream.lastQualityGate.evidenceBindingRatePct.toFixed(2))
|
|
236
|
+
: 0,
|
|
237
|
+
citationErrorRatePct: typeof rawStream.lastQualityGate.citationErrorRatePct === "number" &&
|
|
238
|
+
Number.isFinite(rawStream.lastQualityGate.citationErrorRatePct)
|
|
239
|
+
? Number(rawStream.lastQualityGate.citationErrorRatePct.toFixed(2))
|
|
240
|
+
: 0,
|
|
241
|
+
reasons: reasons.length > 0 ? reasons : [...warnings, ...fatalReasons],
|
|
242
|
+
};
|
|
243
|
+
})()
|
|
159
244
|
: defaultQualityGateState(),
|
|
160
245
|
lastUnreadCorePaperIds: Array.isArray(rawStream.lastUnreadCorePaperIds)
|
|
161
246
|
? rawStream.lastUnreadCorePaperIds
|
|
@@ -253,8 +338,8 @@ function normalizeStringArray(raw) {
|
|
|
253
338
|
return undefined;
|
|
254
339
|
const values = raw
|
|
255
340
|
.filter((item) => typeof item === "string")
|
|
256
|
-
.map((item) =>
|
|
257
|
-
.filter((item) => item
|
|
341
|
+
.map((item) => cleanOptionalText(item))
|
|
342
|
+
.filter((item) => Boolean(item));
|
|
258
343
|
return values.length > 0 ? values : undefined;
|
|
259
344
|
}
|
|
260
345
|
function normalizeEvidenceAnchors(raw) {
|
|
@@ -263,14 +348,14 @@ function normalizeEvidenceAnchors(raw) {
|
|
|
263
348
|
const anchors = raw
|
|
264
349
|
.filter((item) => !!item && typeof item === "object")
|
|
265
350
|
.map((item) => {
|
|
266
|
-
const claim =
|
|
351
|
+
const claim = cleanOptionalText(item.claim);
|
|
267
352
|
if (!claim)
|
|
268
353
|
return undefined;
|
|
269
354
|
return {
|
|
270
|
-
...(item.section ? { section:
|
|
271
|
-
...(item.locator ? { locator:
|
|
355
|
+
...(cleanOptionalText(item.section) ? { section: cleanOptionalText(item.section) } : {}),
|
|
356
|
+
...(cleanOptionalText(item.locator) ? { locator: cleanOptionalText(item.locator) } : {}),
|
|
272
357
|
claim,
|
|
273
|
-
...(item.quote ? { quote:
|
|
358
|
+
...(cleanOptionalText(item.quote) ? { quote: cleanOptionalText(item.quote) } : {}),
|
|
274
359
|
};
|
|
275
360
|
})
|
|
276
361
|
.filter((item) => Boolean(item));
|
|
@@ -288,7 +373,7 @@ function toPaperNoteSlug(paper) {
|
|
|
288
373
|
}
|
|
289
374
|
function normalizePaper(input) {
|
|
290
375
|
const evidenceIds = Array.isArray(input.evidenceIds)
|
|
291
|
-
? input.evidenceIds.map((id) =>
|
|
376
|
+
? input.evidenceIds.map((id) => cleanOptionalText(id)).filter((id) => Boolean(id))
|
|
292
377
|
: undefined;
|
|
293
378
|
const keyEvidenceSpans = normalizeStringArray(input.keyEvidenceSpans);
|
|
294
379
|
const subdomains = normalizeStringArray(input.subdomains);
|
|
@@ -309,31 +394,33 @@ function normalizePaper(input) {
|
|
|
309
394
|
: readStatus
|
|
310
395
|
? false
|
|
311
396
|
: undefined;
|
|
312
|
-
const unreadReason =
|
|
397
|
+
const unreadReason = cleanOptionalText(input.unreadReason);
|
|
313
398
|
return {
|
|
314
|
-
...(input.id ? { id:
|
|
315
|
-
...(input.title ? { title:
|
|
316
|
-
...(input.url ? { url:
|
|
317
|
-
...(input.source ? { source:
|
|
318
|
-
...(input.publishedAt ? { publishedAt:
|
|
399
|
+
...(cleanOptionalText(input.id) ? { id: cleanOptionalText(input.id) } : {}),
|
|
400
|
+
...(cleanOptionalText(input.title) ? { title: cleanOptionalText(input.title) } : {}),
|
|
401
|
+
...(cleanOptionalText(input.url) ? { url: cleanOptionalText(input.url) } : {}),
|
|
402
|
+
...(cleanOptionalText(input.source) ? { source: cleanOptionalText(input.source) } : {}),
|
|
403
|
+
...(cleanOptionalText(input.publishedAt) ? { publishedAt: cleanOptionalText(input.publishedAt) } : {}),
|
|
319
404
|
...(typeof input.score === "number" && Number.isFinite(input.score)
|
|
320
405
|
? { score: Number(input.score.toFixed(2)) }
|
|
321
406
|
: {}),
|
|
322
|
-
...(input.reason ? { reason:
|
|
323
|
-
...(input.summary ? { summary:
|
|
407
|
+
...(cleanOptionalText(input.reason) ? { reason: cleanOptionalText(input.reason) } : {}),
|
|
408
|
+
...(cleanOptionalText(input.summary) ? { summary: cleanOptionalText(input.summary) } : {}),
|
|
324
409
|
...(evidenceIds && evidenceIds.length > 0 ? { evidenceIds } : {}),
|
|
325
410
|
...(typeof fullTextRead === "boolean" ? { fullTextRead } : {}),
|
|
326
411
|
...(readStatus ? { readStatus } : {}),
|
|
327
|
-
...(input.fullTextSource ? { fullTextSource:
|
|
328
|
-
...(input.fullTextRef ? { fullTextRef:
|
|
412
|
+
...(cleanOptionalText(input.fullTextSource) ? { fullTextSource: cleanOptionalText(input.fullTextSource) } : {}),
|
|
413
|
+
...(cleanOptionalText(input.fullTextRef) ? { fullTextRef: cleanOptionalText(input.fullTextRef) } : {}),
|
|
329
414
|
...(unreadReason ? { unreadReason } : {}),
|
|
330
415
|
...(keyEvidenceSpans && keyEvidenceSpans.length > 0 ? { keyEvidenceSpans } : {}),
|
|
331
|
-
...(input.domain ? { domain:
|
|
416
|
+
...(cleanOptionalText(input.domain) ? { domain: cleanOptionalText(input.domain) } : {}),
|
|
332
417
|
...(subdomains ? { subdomains } : {}),
|
|
333
418
|
...(crossDomainLinks ? { crossDomainLinks } : {}),
|
|
334
|
-
...(input.researchGoal ? { researchGoal:
|
|
335
|
-
...(input.approach ? { approach:
|
|
336
|
-
...(
|
|
419
|
+
...(cleanOptionalText(input.researchGoal) ? { researchGoal: cleanOptionalText(input.researchGoal) } : {}),
|
|
420
|
+
...(cleanOptionalText(input.approach) ? { approach: cleanOptionalText(input.approach) } : {}),
|
|
421
|
+
...(cleanOptionalText(input.methodologyDesign)
|
|
422
|
+
? { methodologyDesign: cleanOptionalText(input.methodologyDesign) }
|
|
423
|
+
: {}),
|
|
337
424
|
...(keyContributions ? { keyContributions } : {}),
|
|
338
425
|
...(practicalInsights ? { practicalInsights } : {}),
|
|
339
426
|
...(mustUnderstandPoints ? { mustUnderstandPoints } : {}),
|
|
@@ -446,6 +533,32 @@ function hasStructuredProfile(paper) {
|
|
|
446
533
|
(paper.limitations && paper.limitations.length > 0) ||
|
|
447
534
|
(paper.evidenceAnchors && paper.evidenceAnchors.length > 0));
|
|
448
535
|
}
|
|
536
|
+
function countStructuredProfileFields(paper) {
|
|
537
|
+
let count = 0;
|
|
538
|
+
if (paper.domain && paper.domain.trim())
|
|
539
|
+
count += 1;
|
|
540
|
+
if (paper.subdomains && paper.subdomains.length > 0)
|
|
541
|
+
count += 1;
|
|
542
|
+
if (paper.crossDomainLinks && paper.crossDomainLinks.length > 0)
|
|
543
|
+
count += 1;
|
|
544
|
+
if (paper.researchGoal && paper.researchGoal.trim())
|
|
545
|
+
count += 1;
|
|
546
|
+
if (paper.approach && paper.approach.trim())
|
|
547
|
+
count += 1;
|
|
548
|
+
if (paper.methodologyDesign && paper.methodologyDesign.trim())
|
|
549
|
+
count += 1;
|
|
550
|
+
if (paper.keyContributions && paper.keyContributions.length > 0)
|
|
551
|
+
count += 1;
|
|
552
|
+
if (paper.practicalInsights && paper.practicalInsights.length > 0)
|
|
553
|
+
count += 1;
|
|
554
|
+
if (paper.mustUnderstandPoints && paper.mustUnderstandPoints.length > 0)
|
|
555
|
+
count += 1;
|
|
556
|
+
if (paper.limitations && paper.limitations.length > 0)
|
|
557
|
+
count += 1;
|
|
558
|
+
if (paper.evidenceAnchors && paper.evidenceAnchors.length > 0)
|
|
559
|
+
count += 1;
|
|
560
|
+
return count;
|
|
561
|
+
}
|
|
449
562
|
function isFullTextRead(paper) {
|
|
450
563
|
return paper.fullTextRead === true || paper.readStatus === "fulltext";
|
|
451
564
|
}
|
|
@@ -492,6 +605,13 @@ function applyQualityGates(args) {
|
|
|
492
605
|
const fullTextCoreCount = corePapers.filter((paper) => isFullTextRead(paper)).length;
|
|
493
606
|
const fullTextCoverage = coreCount > 0 ? fullTextCoreCount / coreCount : 0;
|
|
494
607
|
const fullTextCoveragePct = Number((fullTextCoverage * 100).toFixed(2));
|
|
608
|
+
const fullTextCorePapers = corePapers.filter((paper) => isFullTextRead(paper));
|
|
609
|
+
const structuredFieldTotal = 11;
|
|
610
|
+
const avgFullTextProfileCompleteness = fullTextCorePapers.length > 0
|
|
611
|
+
? fullTextCorePapers.reduce((sum, paper) => sum + countStructuredProfileFields(paper) / structuredFieldTotal, 0) /
|
|
612
|
+
fullTextCorePapers.length
|
|
613
|
+
: 0;
|
|
614
|
+
const avgFullTextProfileCompletenessPct = Number((avgFullTextProfileCompleteness * 100).toFixed(2));
|
|
495
615
|
const unreadCorePaperIds = dedupeText(corePapers
|
|
496
616
|
.filter((paper) => !isFullTextRead(paper))
|
|
497
617
|
.map((paper) => paper.id?.trim() || paper.url?.trim() || paper.title?.trim() || "unknown-paper")).slice(0, 50);
|
|
@@ -566,42 +686,68 @@ function applyQualityGates(args) {
|
|
|
566
686
|
downgradedHighConfidenceCount += 1;
|
|
567
687
|
}
|
|
568
688
|
}
|
|
569
|
-
const
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
689
|
+
const warnings = [];
|
|
690
|
+
const fatalReasons = [];
|
|
691
|
+
if (!args.hasAuditableArtifacts && !args.hasRunError) {
|
|
692
|
+
fatalReasons.push("no_auditable_artifacts_without_run_error");
|
|
693
|
+
}
|
|
694
|
+
if (typeof args.requiredCorePapers === "number" && Number.isFinite(args.requiredCorePapers) && args.requiredCorePapers > 0) {
|
|
695
|
+
const requiredCore = Math.floor(args.requiredCorePapers);
|
|
696
|
+
if (coreCount === 0) {
|
|
697
|
+
fatalReasons.push(`core_paper_count_below_required(${coreCount} < ${requiredCore})`);
|
|
698
|
+
}
|
|
699
|
+
else if (coreCount < requiredCore) {
|
|
700
|
+
warnings.push(`core_paper_count_below_required(${coreCount} < ${requiredCore})`);
|
|
701
|
+
}
|
|
575
702
|
}
|
|
576
703
|
if (fullTextCoverage < MIN_CORE_FULLTEXT_COVERAGE) {
|
|
577
|
-
|
|
704
|
+
warnings.push(`core_fulltext_coverage_below_threshold(${fullTextCoveragePct}% < ${Number((MIN_CORE_FULLTEXT_COVERAGE * 100).toFixed(0))}%)`);
|
|
705
|
+
}
|
|
706
|
+
if (fullTextCorePapers.length > 0 && avgFullTextProfileCompleteness < MIN_FULLTEXT_PROFILE_COMPLETENESS) {
|
|
707
|
+
warnings.push(`fulltext_profile_completeness_below_threshold(${avgFullTextProfileCompletenessPct}% < ${Number((MIN_FULLTEXT_PROFILE_COMPLETENESS * 100).toFixed(0))}%)`);
|
|
578
708
|
}
|
|
579
709
|
if (typeof args.requiredFullTextCoveragePct === "number" &&
|
|
580
710
|
Number.isFinite(args.requiredFullTextCoveragePct) &&
|
|
581
711
|
args.requiredFullTextCoveragePct > 0 &&
|
|
582
712
|
fullTextCoveragePct < args.requiredFullTextCoveragePct) {
|
|
583
|
-
|
|
713
|
+
warnings.push(`core_fulltext_coverage_below_required(${fullTextCoveragePct}% < ${Number(args.requiredFullTextCoveragePct.toFixed(2))}%)`);
|
|
584
714
|
}
|
|
585
715
|
if (evidenceBindingRate < MIN_EVIDENCE_BINDING_RATE) {
|
|
586
|
-
|
|
716
|
+
warnings.push(`evidence_binding_rate_below_threshold(${evidenceBindingRatePct}% < ${Number((MIN_EVIDENCE_BINDING_RATE * 100).toFixed(0))}%)`);
|
|
587
717
|
}
|
|
588
|
-
if (citationErrorRate >=
|
|
589
|
-
|
|
718
|
+
if (citationErrorRate >= FATAL_CITATION_ERROR_RATE) {
|
|
719
|
+
fatalReasons.push(`citation_error_rate_above_threshold(${citationErrorRatePct}% >= ${Number((FATAL_CITATION_ERROR_RATE * 100).toFixed(0))}%)`);
|
|
720
|
+
}
|
|
721
|
+
else if (citationErrorRate >= MAX_CITATION_ERROR_RATE) {
|
|
722
|
+
warnings.push(`citation_error_rate_above_warning_threshold(${citationErrorRatePct}% >= ${Number((MAX_CITATION_ERROR_RATE * 100).toFixed(0))}%)`);
|
|
590
723
|
}
|
|
591
724
|
const bridgeChangeCount = args.knowledgeChanges.filter((item) => item.type === "BRIDGE").length;
|
|
725
|
+
const reviseCount = args.knowledgeChanges.filter((item) => item.type === "REVISE").length;
|
|
726
|
+
const confirmCount = args.knowledgeChanges.filter((item) => item.type === "CONFIRM").length;
|
|
592
727
|
const executedReflectionCount = args.reflectionTasks.filter((task) => task.status === "executed").length;
|
|
593
728
|
if (bridgeChangeCount > 0 && executedReflectionCount === 0) {
|
|
594
|
-
|
|
729
|
+
warnings.push(`reflection_missing_for_bridge(bridge_count=${bridgeChangeCount})`);
|
|
730
|
+
}
|
|
731
|
+
if (reviseCount > 0 && confirmCount > 0 && executedReflectionCount === 0) {
|
|
732
|
+
warnings.push(`reflection_missing_for_conflict(revise_count=${reviseCount},confirm_count=${confirmCount})`);
|
|
595
733
|
}
|
|
596
734
|
if (args.hypothesisGate.rejected > 0 && args.hypothesisGate.accepted === 0 && args.hypotheses.length > 0) {
|
|
597
|
-
|
|
735
|
+
warnings.push(`hypothesis_gate_rejected_all(${args.hypothesisGate.rejected})`);
|
|
598
736
|
}
|
|
599
737
|
if (downgradedHighConfidenceCount > 0) {
|
|
600
|
-
|
|
738
|
+
warnings.push(`high_confidence_downgraded(${downgradedHighConfidenceCount})`);
|
|
601
739
|
}
|
|
740
|
+
const reasons = [...warnings, ...fatalReasons];
|
|
741
|
+
const blocking = fatalReasons.length > 0;
|
|
742
|
+
const severity = blocking ? "fatal" : warnings.length > 0 ? "warn" : "ok";
|
|
602
743
|
return {
|
|
603
744
|
qualityGate: {
|
|
604
|
-
|
|
745
|
+
mode: "soft",
|
|
746
|
+
severity,
|
|
747
|
+
warnings,
|
|
748
|
+
fatalReasons,
|
|
749
|
+
blocking,
|
|
750
|
+
passed: !blocking,
|
|
605
751
|
fullTextCoveragePct,
|
|
606
752
|
evidenceBindingRatePct,
|
|
607
753
|
citationErrorRatePct,
|
|
@@ -707,10 +853,12 @@ function toSummary(stream) {
|
|
|
707
853
|
return {
|
|
708
854
|
projectId: stream.projectId,
|
|
709
855
|
streamKey: stream.topicKey,
|
|
856
|
+
runProfile: stream.lastRunProfile,
|
|
710
857
|
totalRuns: stream.totalRuns,
|
|
711
858
|
totalHypotheses: stream.totalHypotheses,
|
|
712
859
|
knowledgeTopicsCount: stream.knowledgeTopics.length,
|
|
713
860
|
paperNotesCount: stream.paperNotes.length,
|
|
861
|
+
triggerState: stream.triggerState,
|
|
714
862
|
recentFullTextReadCount: stream.recentFullTextReadCount,
|
|
715
863
|
recentNotFullTextReadCount: stream.recentNotFullTextReadCount,
|
|
716
864
|
qualityGate: stream.lastQualityGate,
|
|
@@ -931,9 +1079,12 @@ function applyHypothesisGate(args) {
|
|
|
931
1079
|
if (resolvedEvidence < evidenceIds.length) {
|
|
932
1080
|
reasons.push(`unresolved_evidence_ids(${evidenceIds.length - resolvedEvidence})`);
|
|
933
1081
|
}
|
|
934
|
-
if (fullTextSupported === 0) {
|
|
1082
|
+
if (fullTextSupported === 0 && args.runProfile === "strict") {
|
|
935
1083
|
reasons.push("no_fulltext_backed_evidence");
|
|
936
1084
|
}
|
|
1085
|
+
if (resolvedEvidence === 0) {
|
|
1086
|
+
reasons.push("no_resolved_evidence");
|
|
1087
|
+
}
|
|
937
1088
|
const dependencyPathLength = hypothesis.dependencyPath?.length ?? 0;
|
|
938
1089
|
if (dependencyPathLength < MIN_HYPOTHESIS_DEPENDENCY_STEPS) {
|
|
939
1090
|
reasons.push(`dependency_path_too_short(${dependencyPathLength}<${MIN_HYPOTHESIS_DEPENDENCY_STEPS})`);
|
|
@@ -972,6 +1123,51 @@ function applyHypothesisGate(args) {
|
|
|
972
1123
|
},
|
|
973
1124
|
};
|
|
974
1125
|
}
|
|
1126
|
+
function toDayStartMs(day) {
|
|
1127
|
+
const ts = Date.parse(`${day}T00:00:00.000Z`);
|
|
1128
|
+
return Number.isFinite(ts) ? ts : undefined;
|
|
1129
|
+
}
|
|
1130
|
+
function deriveTriggerState(args) {
|
|
1131
|
+
const sorted = [...args.recentChangeStats].sort((a, b) => b.day.localeCompare(a.day));
|
|
1132
|
+
// consecutive days from latest day with NEW/REVISE > 0
|
|
1133
|
+
let consecutiveNewReviseDays = 0;
|
|
1134
|
+
let expectedDayMs;
|
|
1135
|
+
for (const item of sorted) {
|
|
1136
|
+
const dayMs = toDayStartMs(item.day);
|
|
1137
|
+
if (dayMs === undefined)
|
|
1138
|
+
continue;
|
|
1139
|
+
const hasSignal = item.newCount > 0 || item.reviseCount > 0;
|
|
1140
|
+
if (!hasSignal)
|
|
1141
|
+
break;
|
|
1142
|
+
if (expectedDayMs === undefined) {
|
|
1143
|
+
consecutiveNewReviseDays = 1;
|
|
1144
|
+
expectedDayMs = dayMs - 24 * 60 * 60 * 1000;
|
|
1145
|
+
continue;
|
|
1146
|
+
}
|
|
1147
|
+
if (dayMs === expectedDayMs) {
|
|
1148
|
+
consecutiveNewReviseDays += 1;
|
|
1149
|
+
expectedDayMs = dayMs - 24 * 60 * 60 * 1000;
|
|
1150
|
+
continue;
|
|
1151
|
+
}
|
|
1152
|
+
break;
|
|
1153
|
+
}
|
|
1154
|
+
const sevenDaysAgo = args.nowMs - 7 * 24 * 60 * 60 * 1000;
|
|
1155
|
+
let bridgeCount7d = 0;
|
|
1156
|
+
for (const item of sorted) {
|
|
1157
|
+
const dayMs = toDayStartMs(item.day);
|
|
1158
|
+
if (dayMs === undefined)
|
|
1159
|
+
continue;
|
|
1160
|
+
if (dayMs < sevenDaysAgo)
|
|
1161
|
+
continue;
|
|
1162
|
+
bridgeCount7d += Math.max(0, Math.floor(item.bridgeCount));
|
|
1163
|
+
}
|
|
1164
|
+
return {
|
|
1165
|
+
consecutiveNewReviseDays,
|
|
1166
|
+
bridgeCount7d,
|
|
1167
|
+
unreadCoreBacklog: Math.max(0, Math.floor(args.unreadCoreBacklog)),
|
|
1168
|
+
lastUpdatedAtMs: args.nowMs,
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
975
1171
|
export async function commitKnowledgeRun(input) {
|
|
976
1172
|
const project = await resolveProjectContext({
|
|
977
1173
|
projectId: input.projectId,
|
|
@@ -1023,10 +1219,12 @@ export async function commitKnowledgeRun(input) {
|
|
|
1023
1219
|
topic: normalizeText(input.topic),
|
|
1024
1220
|
topicKey: streamKey,
|
|
1025
1221
|
projectId: project.projectId,
|
|
1222
|
+
lastRunProfile: defaultRunProfile(),
|
|
1026
1223
|
totalRuns: 0,
|
|
1027
1224
|
totalHypotheses: 0,
|
|
1028
1225
|
knowledgeTopics: [],
|
|
1029
1226
|
paperNotes: [],
|
|
1227
|
+
triggerState: defaultTriggerState(),
|
|
1030
1228
|
recentFullTextReadCount: 0,
|
|
1031
1229
|
recentNotFullTextReadCount: 0,
|
|
1032
1230
|
lastQualityGate: defaultQualityGateState(),
|
|
@@ -1044,9 +1242,9 @@ export async function commitKnowledgeRun(input) {
|
|
|
1044
1242
|
.map((paper) => paper.id || paper.url || paper.title || "")
|
|
1045
1243
|
.map((value) => normalizeText(value))
|
|
1046
1244
|
.filter((value) => value.length > 0);
|
|
1047
|
-
const
|
|
1048
|
-
|
|
1049
|
-
|
|
1245
|
+
const explicitRunId = input.runId?.trim() ? sanitizeId(input.runId) : undefined;
|
|
1246
|
+
let runId = explicitRunId ??
|
|
1247
|
+
buildRunFingerprint({
|
|
1050
1248
|
scope: stream.scope,
|
|
1051
1249
|
topic: stream.topic,
|
|
1052
1250
|
status: input.status,
|
|
@@ -1054,15 +1252,30 @@ export async function commitKnowledgeRun(input) {
|
|
|
1054
1252
|
paperIds,
|
|
1055
1253
|
note: input.note,
|
|
1056
1254
|
});
|
|
1255
|
+
const inferredRunProfile = input.knowledgeState?.runLog?.runProfile === "strict" || input.knowledgeState?.runLog?.runProfile === "fast"
|
|
1256
|
+
? input.knowledgeState.runLog.runProfile
|
|
1257
|
+
: input.knowledgeState?.runLog?.requiredCorePapers !== undefined ||
|
|
1258
|
+
input.knowledgeState?.runLog?.requiredFullTextCoveragePct !== undefined
|
|
1259
|
+
? "strict"
|
|
1260
|
+
: defaultRunProfile();
|
|
1057
1261
|
if (stream.recentRunIds.includes(runId)) {
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
streamKey
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1262
|
+
// Preserve idempotency for fingerprint-based runs, but avoid silently dropping
|
|
1263
|
+
// valid new cron cycles that accidentally reuse an explicit run_id.
|
|
1264
|
+
if (!explicitRunId) {
|
|
1265
|
+
root.streams[streamKey] = stream;
|
|
1266
|
+
return {
|
|
1267
|
+
projectId: project.projectId,
|
|
1268
|
+
streamKey,
|
|
1269
|
+
summary: toSummary(stream),
|
|
1270
|
+
runId,
|
|
1271
|
+
createdProject: project.created,
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
const collisionTag = createHash("sha1")
|
|
1275
|
+
.update(`${nowMs}\n${paperIds.join("|")}\n${input.status ?? ""}\n${input.note ?? ""}`)
|
|
1276
|
+
.digest("hex")
|
|
1277
|
+
.slice(0, 8);
|
|
1278
|
+
runId = `${runId}-r${collisionTag}`;
|
|
1066
1279
|
}
|
|
1067
1280
|
const rootPath = getKnowledgeStateRoot(project.projectPath);
|
|
1068
1281
|
const logDir = path.join(rootPath, "logs");
|
|
@@ -1100,8 +1313,16 @@ export async function commitKnowledgeRun(input) {
|
|
|
1100
1313
|
hypotheses: submittedHypotheses,
|
|
1101
1314
|
allRunPapers: mergedRunPapers,
|
|
1102
1315
|
knowledgeChanges,
|
|
1316
|
+
runProfile: inferredRunProfile,
|
|
1103
1317
|
});
|
|
1104
1318
|
const acceptedHypotheses = hypothesisEval.acceptedHypotheses;
|
|
1319
|
+
const runArtifactCount = corePapers.length +
|
|
1320
|
+
explorationPapers.length +
|
|
1321
|
+
explorationTrace.length +
|
|
1322
|
+
knowledgeChanges.length +
|
|
1323
|
+
knowledgeUpdates.length +
|
|
1324
|
+
submittedHypotheses.length;
|
|
1325
|
+
const hasRunError = Boolean(input.knowledgeState?.runLog?.error && normalizeText(input.knowledgeState.runLog.error).length > 0);
|
|
1105
1326
|
const qualityEval = applyQualityGates({
|
|
1106
1327
|
corePapers,
|
|
1107
1328
|
allRunPapers: mergedRunPapers,
|
|
@@ -1113,10 +1334,19 @@ export async function commitKnowledgeRun(input) {
|
|
|
1113
1334
|
hypothesisGate: hypothesisEval.gate,
|
|
1114
1335
|
requiredCorePapers: input.knowledgeState?.runLog?.requiredCorePapers,
|
|
1115
1336
|
requiredFullTextCoveragePct: input.knowledgeState?.runLog?.requiredFullTextCoveragePct,
|
|
1337
|
+
hasAuditableArtifacts: runArtifactCount > 0,
|
|
1338
|
+
hasRunError,
|
|
1116
1339
|
});
|
|
1340
|
+
if (changeSanitization.droppedBridgeCount > 0) {
|
|
1341
|
+
qualityEval.qualityGate.warnings.push(`bridge_dropped_due_to_ungrounded_evidence(${changeSanitization.droppedBridgeCount})`);
|
|
1342
|
+
qualityEval.qualityGate.reasons.push(`bridge_dropped_due_to_ungrounded_evidence(${changeSanitization.droppedBridgeCount})`);
|
|
1343
|
+
if (qualityEval.qualityGate.severity === "ok") {
|
|
1344
|
+
qualityEval.qualityGate.severity = "warn";
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1117
1347
|
const requestedStatus = normalizeText(input.status ?? "ok");
|
|
1118
1348
|
const qualitySensitiveStatus = requestedStatus === "ok" || requestedStatus === "fallback_representative";
|
|
1119
|
-
const effectiveStatus = qualitySensitiveStatus &&
|
|
1349
|
+
const effectiveStatus = qualitySensitiveStatus && qualityEval.qualityGate.blocking ? "degraded_quality" : requestedStatus;
|
|
1120
1350
|
const topicToUpdates = new Map();
|
|
1121
1351
|
for (const update of knowledgeUpdates) {
|
|
1122
1352
|
const key = slugifyTopic(update.topic);
|
|
@@ -1174,6 +1404,7 @@ export async function commitKnowledgeRun(input) {
|
|
|
1174
1404
|
await writeFile(path.join(knowledgeDir, "_index.md"), renderKnowledgeIndexMarkdown({
|
|
1175
1405
|
now: nowIso,
|
|
1176
1406
|
topic: stream.topic,
|
|
1407
|
+
runProfile: inferredRunProfile,
|
|
1177
1408
|
topicFiles: stream.knowledgeTopics,
|
|
1178
1409
|
paperNotesCount: stream.paperNotes.length,
|
|
1179
1410
|
totalHypotheses: stream.totalHypotheses + recentHypothesisSummaries.length,
|
|
@@ -1188,6 +1419,7 @@ export async function commitKnowledgeRun(input) {
|
|
|
1188
1419
|
}), "utf-8");
|
|
1189
1420
|
const changeStat = countChangeStats(dayKey, runId, knowledgeChanges);
|
|
1190
1421
|
stream.projectId = project.projectId;
|
|
1422
|
+
stream.lastRunProfile = inferredRunProfile;
|
|
1191
1423
|
stream.totalRuns += 1;
|
|
1192
1424
|
stream.totalHypotheses += recentHypothesisSummaries.length;
|
|
1193
1425
|
stream.lastRunAtMs = nowMs;
|
|
@@ -1207,11 +1439,18 @@ export async function commitKnowledgeRun(input) {
|
|
|
1207
1439
|
].slice(0, MAX_RECENT_HYPOTHESES);
|
|
1208
1440
|
stream.recentHypotheses = [...recentHypothesisSummaries, ...stream.recentHypotheses].slice(0, MAX_RECENT_HYPOTHESES);
|
|
1209
1441
|
stream.recentChangeStats = [changeStat, ...stream.recentChangeStats].slice(0, MAX_RECENT_CHANGE_STATS);
|
|
1442
|
+
stream.triggerState = deriveTriggerState({
|
|
1443
|
+
recentChangeStats: stream.recentChangeStats,
|
|
1444
|
+
unreadCoreBacklog: qualityEval.unreadCorePaperIds.length,
|
|
1445
|
+
nowMs,
|
|
1446
|
+
});
|
|
1210
1447
|
root.streams[streamKey] = stream;
|
|
1211
1448
|
await saveStateAtomic(project.projectPath, root);
|
|
1212
1449
|
await appendFile(path.join(logDir, `day-${dayKey}-run-details.jsonl`), `${JSON.stringify({
|
|
1213
1450
|
ts: nowMs,
|
|
1451
|
+
run_id: runId,
|
|
1214
1452
|
runId,
|
|
1453
|
+
run_profile: inferredRunProfile,
|
|
1215
1454
|
scope: stream.scope,
|
|
1216
1455
|
topic: stream.topic,
|
|
1217
1456
|
streamKey,
|
|
@@ -1242,7 +1481,9 @@ export async function commitKnowledgeRun(input) {
|
|
|
1242
1481
|
})}\n`, "utf-8");
|
|
1243
1482
|
await appendEvent(project.projectPath, {
|
|
1244
1483
|
ts: nowMs,
|
|
1484
|
+
run_id: runId,
|
|
1245
1485
|
runId,
|
|
1486
|
+
run_profile: inferredRunProfile,
|
|
1246
1487
|
scope: stream.scope,
|
|
1247
1488
|
topic: stream.topic,
|
|
1248
1489
|
streamKey,
|
|
@@ -1264,6 +1505,7 @@ export async function commitKnowledgeRun(input) {
|
|
|
1264
1505
|
hypothesisCount: recentHypothesisSummaries.length,
|
|
1265
1506
|
submittedHypothesisCount: submittedHypotheses.length,
|
|
1266
1507
|
hypothesisGate: hypothesisEval.gate,
|
|
1508
|
+
triggerState: stream.triggerState,
|
|
1267
1509
|
reflectionTasks,
|
|
1268
1510
|
corePapers,
|
|
1269
1511
|
explorationPapers,
|