@thincoder/core 0.9.5 → 0.10.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/CHANGELOG.md +49 -0
- package/README.md +1 -1
- package/abort-provenance.mjs +10 -10
- package/advisor/citations.mjs +5 -5
- package/advisor/compaction.mjs +16 -15
- package/advisor/loop.mjs +12 -12
- package/advisor/messages.mjs +6 -6
- package/advisor/project-context.mjs +5 -5
- package/advisor/repos.mjs +3 -3
- package/advisor/run.mjs +22 -12
- package/advisor.mjs +4 -4
- package/agent/assemble.mjs +111 -0
- package/agent/chat-call.mjs +68 -0
- package/agent/child-marks.mjs +2 -2
- package/agent/completion.mjs +9 -2
- package/agent/continue-decision.mjs +25 -0
- package/agent/dispatch-gates.mjs +140 -0
- package/agent/dispatch-run.mjs +167 -0
- package/agent/dispatch.mjs +54 -296
- package/agent/family-tools.mjs +5 -5
- package/agent/helpers.mjs +53 -3
- package/agent/live-beat.mjs +49 -0
- package/agent/post-turn.mjs +4 -8
- package/agent/record-results.mjs +23 -20
- package/agent/relay-prefix.mjs +2 -2
- package/agent/run-stages.mjs +15 -11
- package/agent/run-start.mjs +111 -0
- package/agent/setup-reminders.mjs +9 -9
- package/agent/setup.mjs +32 -4
- package/agent/spawn-child.mjs +9 -9
- package/agent/suspension.mjs +89 -29
- package/agent/timers.mjs +113 -0
- package/agent/turn-loop.mjs +244 -0
- package/agent/write-gate.mjs +103 -6
- package/agent-tools/advisor-async.mjs +9 -9
- package/agent-tools/advisor-settle.mjs +12 -12
- package/agent-tools/advisor.mjs +36 -41
- package/agent-tools/async-discard.mjs +44 -1
- package/agent-tools/async-settle.mjs +21 -18
- package/agent-tools/audit-block.mjs +7 -7
- package/agent-tools/bash-async.mjs +281 -0
- package/agent-tools/batch-lifecycle.mjs +57 -18
- package/agent-tools/batch-paths.mjs +52 -8
- package/agent-tools/batch-skeleton.mjs +19 -1
- package/agent-tools/batch.mjs +12 -65
- package/agent-tools/checkpoint.mjs +121 -0
- package/agent-tools/child-permission.mjs +5 -3
- package/agent-tools/consult.mjs +56 -56
- package/agent-tools/context.mjs +4 -20
- package/agent-tools/design-token.mjs +3 -3
- package/agent-tools/digest-budget.mjs +1 -1
- package/agent-tools/eng.mjs +5 -5
- package/agent-tools/escalate-async.mjs +16 -11
- package/agent-tools/goal.mjs +3 -10
- package/agent-tools/panel-blocks.mjs +2 -2
- package/agent-tools/parent-channel.mjs +3 -13
- package/agent-tools/plan.mjs +3 -4
- package/agent-tools/read-history.mjs +12 -28
- package/agent-tools/recent-changes.mjs +4 -5
- package/agent-tools/review-facts.mjs +1 -1
- package/agent-tools/settings.mjs +47 -20
- package/agent-tools/skill.mjs +3 -5
- package/agent-tools/spawn-gates.mjs +3 -3
- package/agent-tools/subagent-actions-query.mjs +270 -0
- package/agent-tools/subagent-actions.mjs +21 -265
- package/agent-tools/subagent-async.mjs +25 -20
- package/agent-tools/subagent-panel.mjs +33 -4
- package/agent-tools/subagent-run.mjs +36 -21
- package/agent-tools/subagent-scheduler.mjs +28 -27
- package/agent-tools/subagent-spawn.mjs +45 -14
- package/agent-tools/subagent.mjs +33 -53
- package/agent-tools/task.mjs +3 -9
- package/agent-tools/timer.mjs +12 -12
- package/agent-tools/verify.mjs +14 -13
- package/agent-tools.mjs +0 -1
- package/agent.mjs +18 -355
- package/attachments.mjs +125 -0
- package/auto-think.mjs +1 -1
- package/config-presets.mjs +11 -6
- package/config-watch.mjs +83 -0
- package/config.mjs +29 -12
- package/context-degrade.mjs +81 -0
- package/context-echo.mjs +88 -0
- package/context-push.mjs +51 -0
- package/context.mjs +8 -187
- package/conventions.mjs +110 -161
- package/declaration.mjs +194 -0
- package/embedding.mjs +3 -1
- package/env-ref.mjs +81 -0
- package/explore-distill.mjs +2 -3
- package/file-links.mjs +53 -0
- package/file-refs.mjs +103 -0
- package/generate-title.mjs +28 -4
- package/history-window.mjs +17 -2
- package/i18n.mjs +2 -0
- package/index-discover.mjs +7 -7
- package/ledger-cmd.mjs +34 -126
- package/ledger-db.mjs +10 -2
- package/ledger-tools.mjs +188 -0
- package/ledger.mjs +9 -4
- package/log.mjs +12 -7
- package/manifest-discovery.mjs +131 -0
- package/manifest-schema.mjs +166 -0
- package/manifest.mjs +18 -217
- package/mcp.mjs +4 -1
- package/memory/code-search.mjs +149 -0
- package/memory/code-sync.mjs +89 -230
- package/memory/core.mjs +47 -32
- package/memory/docs.mjs +63 -262
- package/memory/file-list.mjs +89 -0
- package/memory/file-walk.mjs +7 -4
- package/memory/memory-tool.mjs +229 -0
- package/memory/schema.mjs +18 -3
- package/memory/sweep.mjs +289 -0
- package/memory/sync-tail.mjs +81 -0
- package/memory-status.mjs +69 -0
- package/memory.mjs +3 -0
- package/model-ref.mjs +96 -3
- package/model-specs.mjs +16 -7
- package/notify-policy.mjs +51 -0
- package/package.json +1 -1
- package/peer-claims.mjs +2 -2
- package/peer-domains.mjs +7 -11
- package/peer-instances.mjs +9 -10
- package/process-probe-exec.mjs +187 -0
- package/process-probe.mjs +27 -175
- package/prompt-overlays.mjs +11 -10
- package/prompts/advisor-design.md +2 -2
- package/prompts/advisor-round2.md +1 -1
- package/prompts/advisor-round3.md +1 -1
- package/prompts/common.md +16 -8
- package/prompts/discipline-engineering.md +57 -16
- package/prompts/discipline-normal.md +6 -3
- package/prompts/persona-coder.md +0 -1
- package/prompts/persona-eng-coder.md +1 -5
- package/prompts/persona-eng-designer.md +2 -3
- package/prompts/persona-engineering.md +32 -11
- package/prompts/persona-explore.md +0 -1
- package/prompts/persona-normal.md +1 -1
- package/prompts/persona-plan.md +3 -4
- package/prompts/subagent-base.md +14 -0
- package/provider/core.mjs +11 -48
- package/provider/list-models.mjs +3 -0
- package/provider/responses-request.mjs +237 -0
- package/provider/responses.mjs +6 -228
- package/provider-flows.mjs +248 -0
- package/queued.mjs +90 -0
- package/rules.mjs +209 -2
- package/session-guard.mjs +27 -7
- package/session-lifecycle.mjs +108 -27
- package/session-rename.mjs +5 -1
- package/session-slot-claims.mjs +230 -0
- package/session-slot-scan.mjs +299 -0
- package/session-slot-verify.mjs +304 -0
- package/session-slot-write.mjs +74 -8
- package/session-slots-manifest.mjs +163 -241
- package/session-slots.mjs +99 -71
- package/session.mjs +30 -10
- package/shell-candidates.mjs +78 -0
- package/text-budget.mjs +2 -2
- package/think-off.mjs +49 -0
- package/token-ttl.mjs +15 -4
- package/tool-args.mjs +73 -0
- package/tool-docs/advisor.md +9 -0
- package/tool-docs/batch.md +1 -0
- package/tool-docs/code_search.md +1 -0
- package/tool-docs/consult_start.md +5 -0
- package/tool-docs/consult_stop.md +3 -0
- package/tool-docs/context.md +7 -0
- package/tool-docs/doc_search.md +1 -0
- package/tool-docs/edit.md +2 -13
- package/tool-docs/eng.md +1 -0
- package/tool-docs/execute.md +1 -9
- package/tool-docs/git.md +25 -51
- package/tool-docs/goal.md +6 -0
- package/tool-docs/ledger_add.md +1 -0
- package/tool-docs/ledger_close.md +1 -0
- package/tool-docs/ledger_count.md +1 -0
- package/tool-docs/ledger_query.md +1 -0
- package/tool-docs/ledger_update.md +1 -0
- package/tool-docs/main_history.md +1 -0
- package/tool-docs/memory.md +10 -0
- package/tool-docs/notify_parent.md +9 -0
- package/tool-docs/peer_instances.md +1 -0
- package/tool-docs/plan.md +5 -0
- package/tool-docs/process.md +7 -3
- package/tool-docs/read_history.md +7 -0
- package/tool-docs/recent_changes.md +1 -0
- package/tool-docs/repo_outline.md +1 -0
- package/tool-docs/settings.md +6 -0
- package/tool-docs/skill.md +5 -0
- package/tool-docs/subagent.md +31 -0
- package/tool-docs/task.md +7 -0
- package/tool-docs/timer.md +6 -0
- package/tool-docs/verify.md +1 -0
- package/tool-docs/wait_for.md +2 -1
- package/tools/bash.mjs +28 -4
- package/tools/edit-batch.mjs +4 -4
- package/tools/edit-diff.mjs +4 -23
- package/tools/exec-run.mjs +102 -4
- package/tools/execute.mjs +5 -2
- package/tools/file.mjs +4 -9
- package/tools/git-run.mjs +1 -1
- package/tools/git.mjs +0 -24
- package/tools/index.mjs +2 -1
- package/tools/linter.mjs +2 -1
- package/tools/node-child.mjs +21 -0
- package/tools/ops.mjs +50 -13
- package/tools/patch.mjs +4 -2
- package/tools/repomap.mjs +20 -9
- package/tools/shared.mjs +8 -6
- package/tools/web.mjs +5 -2
- package/tools/write-path.mjs +1 -1
- package/traces/trace-store.mjs +17 -14
- package/vision-reader.mjs +76 -0
- package/agent-tools/batch-segment.mjs +0 -18
package/manifest.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* manifest.mjs — M1
|
|
2
|
+
* manifest.mjs — M1 项目状态档(项目级状态账——工程模式是其**读侧消费者**之一)。
|
|
3
3
|
* 权威设计 = docs/core/design/MANIFEST.md §2
|
|
4
4
|
* (机制代码在核,操作对象 = 被开发项目 cwd 根的 PROJECT-MANIFEST.json 数据档)。
|
|
5
5
|
*
|
|
@@ -8,7 +8,11 @@
|
|
|
8
8
|
* 读 + validateManifest(obj);缺键 → 产 missingKeys(非拒)→ 补默认值 →
|
|
9
9
|
* 再校验通过;整档缺失 → { ok:false, reason:'missing' }(不静默 fallback)。
|
|
10
10
|
* - validateManifest(obj) → { ok, errors, missingKeys }:枚举 / version 数值 /
|
|
11
|
-
* docRoot 子键值形态(串 | 数组,F7
|
|
11
|
+
* docRoot 子键值形态(串 | 数组,F7)+ checkConfig.lineCounts 元素层形态——**纯函数、零 fs**;不落盘。
|
|
12
|
+
* - 三族声明键(`codePaths` / `index.{codeExtensions,docExtensions,publicRepos}` / `advisor.{docMap,standardsDoc}`——
|
|
13
|
+
* KD-M1-31 / M1-32):缺键补默认;形态错 = 档非法(fail-closed,与 docRoot 子键同款);
|
|
14
|
+
* 读向 = `declaration.mjs`(2026-10-01 自 `conventions.mjs` 迁出——经其转口可达)经 `readManifest` /
|
|
15
|
+
* `manifestFilePath` 投影(单向——KD-M1-33);`index.publicRepos` 绝对根集 = `declaredPublicRoots`(§6.15)。
|
|
12
16
|
* - isValidDocRootValue(value) / docRootPaths(value, cwd)(F7 判据单源 KD-M1-8):值形态谓词
|
|
13
17
|
* + 值 → 绝对路径数组(展开 / trim + `\` 归一 / 基数 = 项目根 / 去重保序)。
|
|
14
18
|
* - requireManifest(cwd) → 装配钩子入口 = readManifest(cwd)。
|
|
@@ -18,10 +22,11 @@
|
|
|
18
22
|
* - owningProject(target) → 归属形单点(沿祖先链取**最近带档目录**——KD-M1-30)。
|
|
19
23
|
* - projectView(target) → { state, root, path, manifest?, candidates?, errors?, matched }:**按用点解析**
|
|
20
24
|
* 的读侧单点(归属 ∨ 发现兜底 / 五态 / 非抛错 / **零写**——KD-M1-24)。
|
|
21
|
-
* - resolveProjectRoot(cwd)
|
|
25
|
+
* - resolveProjectRoot(cwd):项目根(归属 ∨ 发现——KD-M1-30)= `projectRootView(cwd).root`(薄委托);
|
|
26
|
+
* projectRootView(cwd) → { state: ok|ambiguous|none, root, candidates }:歧义直读形(判定单点——§2.2)。
|
|
22
27
|
* - resolveEngineeringManifest(cwd, { writer, init }) → 入口决策树(**非抛错**——KD-M1-20):
|
|
23
28
|
* 两端入口钩子与翻转面(拒翻)共用同一张树(判据单源;§2.8 F1)——失败码五枚
|
|
24
|
-
* `missing` / `invalid` / `no-project` / `ambiguous` / `init-failed`(KD-M1-28
|
|
29
|
+
* `missing` / `invalid` / `no-project` / `ambiguous` / `init-failed`(KD-M1-28)。名中 `Engineering` = 历史命名(沿用不改名);消费面 = 两端入口钩子 + 翻转面——**非**「模式拥有本模块」。
|
|
25
30
|
* - manifestFilePath(cwd) → 数据档绝对路径(档路径单源 KD-M1-18——读 / 写 / mtime 门控三处同源)。
|
|
26
31
|
* - initManifest(cwd, { writer = 'subagent' } = {}) → 经写门写 DEFAULT_MANIFEST,缺省拒。
|
|
27
32
|
* - writeManifest(cwd, manifest, { writer = 'subagent' } = {}) → 落盘前先校验(ok:false 拒
|
|
@@ -30,114 +35,14 @@
|
|
|
30
35
|
* N1 零依赖:仅 node:fs / node:path + 标准库 JSON.parse——无第三方解析器、无 node:sqlite。
|
|
31
36
|
* N3 可迁移:本模块不硬编码任何本仓路径(docRoot / checkConfig 由被开发项目声明)。
|
|
32
37
|
*/
|
|
33
|
-
import {
|
|
34
|
-
import {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
export
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
export
|
|
41
|
-
export function _resetProjectRootForTest() { _projectRootOverride = null }
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* 一层扫描内核(**模块私有**——两条梯表共用;KD-M1-23 / 设计 §2.2「单源结构」):枚举锚的
|
|
45
|
-
* **直接子目录**一层(不递归)+ 按名排序 + 谓词过滤 ⇒ 命中表(绝对路径)。
|
|
46
|
-
* 全档唯一 `readdirSync` 落点(结构机判 T54);锚不可读 ⇒ `[]`(不抛——调用方按「零命中」处置)。
|
|
47
|
-
* @param {string} anchor 锚绝对路径
|
|
48
|
-
* @param {(dir:string)=>boolean} isHit 子目录谓词
|
|
49
|
-
* @returns {string[]} 命中子目录绝对路径(按名排序)
|
|
50
|
-
*/
|
|
51
|
-
function scanChildren(anchor, isHit) {
|
|
52
|
-
try {
|
|
53
|
-
return readdirSync(anchor, { withFileTypes: true })
|
|
54
|
-
.filter((e) => e.isDirectory())
|
|
55
|
-
.map((e) => e.name)
|
|
56
|
-
.sort()
|
|
57
|
-
.map((e) => join(anchor, e))
|
|
58
|
-
.filter(isHit)
|
|
59
|
-
} catch { return [] }
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* **项目发现**(项目梯五级——KD-M1-23 / 设计 `docs/core/design/MANIFEST.md` §2.2;**git 非前提**,
|
|
64
|
-
* 2026-09-21 用户裁定):① 锚自身带 `MANIFEST_REL` ⇒ 锚;② 锚含 `.git` ⇒ 锚(缺档 = 建档机会);
|
|
65
|
-
* ③ 直接子目录中**带档**者优先(非空即只看此级:恰一 ⇒ `unique` / ≥2 ⇒ `ambiguous`);
|
|
66
|
-
* ④ 零带档才看含 `.git` 的**裸仓**(同判);⑤ 均无 ⇒ `none`(⇒ 建档流,落点 = 会话锚)。
|
|
67
|
-
* `candidates` 一律**按名排序**(`self` / `none` ⇒ `[]`);`matched` ∈ `manifest` / `git` / `null`
|
|
68
|
-
* 记命中(或歧义)出自哪一级。**纯 fs**(只判存在性——不解析档内容)· **不抛** · **不向上** · **不递归**。
|
|
69
|
-
* **覆盖位**(`_setProjectRootForTest` 在场)⇒ **头部短路**(测试注入面——先于真判据,同 `discoverRepos`)。
|
|
70
|
-
* @param {string} [cwd] 会话锚(缺省 → 进程 cwd)
|
|
71
|
-
* @returns {{kind:'self'|'unique'|'none'|'ambiguous', root:string|null, candidates:string[], matched:'manifest'|'git'|null}}
|
|
72
|
-
*/
|
|
73
|
-
export function discoverProjects(cwd) {
|
|
74
|
-
if (_projectRootOverride) return { kind: "self", root: resolve(_projectRootOverride), candidates: [], matched: null }
|
|
75
|
-
const anchor = resolve(cwd ?? ".")
|
|
76
|
-
if (existsSync(join(anchor, MANIFEST_REL))) return { kind: "self", root: anchor, candidates: [], matched: "manifest" }
|
|
77
|
-
if (existsSync(join(anchor, ".git"))) return { kind: "self", root: anchor, candidates: [], matched: "git" }
|
|
78
|
-
const withManifest = scanChildren(anchor, (d) => existsSync(join(d, MANIFEST_REL)))
|
|
79
|
-
if (withManifest.length > 1) return { kind: "ambiguous", root: null, candidates: withManifest, matched: "manifest" }
|
|
80
|
-
if (withManifest.length === 1) return { kind: "unique", root: withManifest[0], candidates: [...withManifest], matched: "manifest" }
|
|
81
|
-
const bareRepos = scanChildren(anchor, (d) => existsSync(join(d, ".git")))
|
|
82
|
-
if (bareRepos.length > 1) return { kind: "ambiguous", root: null, candidates: bareRepos, matched: "git" }
|
|
83
|
-
if (bareRepos.length === 1) return { kind: "unique", root: bareRepos[0], candidates: [...bareRepos], matched: "git" }
|
|
84
|
-
return { kind: "none", root: null, candidates: [], matched: null }
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* **仓发现**(仓梯——KD-M1-23 / `docs/core/design/TOOLS.md` §6.13;**单源** KD-M1-22——`git` 工具
|
|
89
|
-
* 经此符号接线,禁第二份实现):① 锚含 `.git` ⇒ 锚;② 直接子目录中 `.git` **∧** 带档者恰一 ⇒ 命中
|
|
90
|
-
* (≥2 ⇒ 歧义);③ **零个此类时才看**含 `.git` 的裸仓(恰一 ⇒ 命中 / ≥2 ⇒ 歧义);④ 均无 ⇒ `none`。
|
|
91
|
-
* 与 `discoverProjects` = **同一 walk 内核**(`scanChildren`)的两种梯表——差异只在谓词与级序。
|
|
92
|
-
* `candidates` 按名排序;`matched` 记档位;纯 fs / 不抛 / 不递归 / 不向上;覆盖位 ⇒ 头部短路。
|
|
93
|
-
* @param {string} [cwd] 会话锚(缺省 → 进程 cwd)
|
|
94
|
-
* @returns {{kind:'self'|'unique'|'none'|'ambiguous', root:string|null, candidates:string[], matched:'manifest'|'git'|null}}
|
|
95
|
-
*/
|
|
96
|
-
export function discoverRepos(cwd) {
|
|
97
|
-
if (_projectRootOverride) return { kind: "self", root: resolve(_projectRootOverride), candidates: [], matched: null }
|
|
98
|
-
const anchor = resolve(cwd ?? ".")
|
|
99
|
-
if (existsSync(join(anchor, ".git"))) return { kind: "self", root: anchor, candidates: [], matched: "git" }
|
|
100
|
-
const scoped = scanChildren(anchor, (d) => existsSync(join(d, ".git")) && existsSync(join(d, MANIFEST_REL)))
|
|
101
|
-
if (scoped.length > 1) return { kind: "ambiguous", root: null, candidates: scoped, matched: "manifest" }
|
|
102
|
-
if (scoped.length === 1) return { kind: "unique", root: scoped[0], candidates: [...scoped], matched: "manifest" }
|
|
103
|
-
const bare = scanChildren(anchor, (d) => existsSync(join(d, ".git")))
|
|
104
|
-
if (bare.length > 1) return { kind: "ambiguous", root: null, candidates: bare, matched: "git" }
|
|
105
|
-
if (bare.length === 1) return { kind: "unique", root: bare[0], candidates: [...bare], matched: "git" }
|
|
106
|
-
return { kind: "none", root: null, candidates: [], matched: null }
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* **归属形单点**(KD-M1-30 / 设计 §2.9 A——2026-09-21 用户裁定「最近者优先」):自 `target`
|
|
111
|
-
* (目录含自身;文件路径自其父目录起)沿**祖先链**逐级上溯至盘根,取**最近**带 `MANIFEST_REL`
|
|
112
|
-
* 的目录;无 ⇒ `null`(⇒ 调用方走发现兜底)。**嵌套合法**:子内归子、根其余归根。
|
|
113
|
-
* **纯 fs**(只判存在性——不解析档内容 / 不问模式)· **不跨兄弟** · **无全局优先级**。
|
|
114
|
-
* **覆盖位**(`_setProjectRootForTest` 在场)⇒ **头部短路**:直接返回覆盖值(不查档存在性——
|
|
115
|
-
* 保「覆盖即覆盖值」语义,回归守卫 = `test/manifest.test.mjs` T42 覆盖断言)。
|
|
116
|
-
* @param {string} [target] 目标路径(目录 / 文件)
|
|
117
|
-
* @returns {string|null} 最近带档祖先目录绝对路径 / null
|
|
118
|
-
*/
|
|
119
|
-
export function owningProject(target) {
|
|
120
|
-
if (_projectRootOverride) return resolve(_projectRootOverride)
|
|
121
|
-
let dir = resolve(target ?? ".")
|
|
122
|
-
for (;;) {
|
|
123
|
-
if (existsSync(join(dir, MANIFEST_REL))) return dir
|
|
124
|
-
const parent = dirname(dir)
|
|
125
|
-
if (parent === dir) return null // 盘根 → 祖先链无档
|
|
126
|
-
dir = parent
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* 项目根解析(**归属 ∨ 发现**——KD-M1-30 / M1-24):`owningProject(cwd) ?? discoverProjects(cwd).root`。
|
|
132
|
-
* 带档路径与批前**逐字同**;变更面两条(设计 §2.2):① 项目树内路径(祖先带档)⇒ **该项目根**
|
|
133
|
-
* (批前回落 `resolve(cwd)`——错层建档面,本批修);② 裸仓恰一 ⇒ 该仓根(零档降级 = 建档机会)。
|
|
134
|
-
* `none` / `ambiguous` ⇒ `null`。调用方(`manifestFilePath` / `docRootBase` / `ledger-db.mjs` /
|
|
135
|
-
* `ledger-cmd.mjs`)**零改**(行为随语义变更——设计 §2.5 键面条 / 错层条)。
|
|
136
|
-
* @returns {string|null} 项目根绝对路径 / null
|
|
137
|
-
*/
|
|
138
|
-
export function resolveProjectRoot(cwd) {
|
|
139
|
-
return owningProject(cwd) ?? discoverProjects(cwd).root
|
|
140
|
-
}
|
|
38
|
+
import { readFileSync, writeFileSync } from "node:fs"
|
|
39
|
+
import { join, resolve } from "node:path"
|
|
40
|
+
import { DEFAULT_MANIFEST, isValidDocRootValue, validateManifest, fillDefaults } from "./manifest-schema.mjs"
|
|
41
|
+
import { MANIFEST_REL, discoverProjects, owningProject, resolveProjectRoot } from "./manifest-discovery.mjs"
|
|
42
|
+
// 同名 re-export(2026-09-29 structure-split-2 · 台账 #620——缝 = 同名再出口):发现 ∕ 归属族与校验 ∕ 默认值族出档
|
|
43
|
+
// `manifest-discovery.mjs` ∕ `manifest-schema.mjs`;全迁移导出经本档转口——消费者 import 面零改。
|
|
44
|
+
export { MANIFEST_REL, discoverProjects, discoverRepos, owningProject, projectRootView, resolveProjectRoot, _setProjectRootForTest, _resetProjectRootForTest } from "./manifest-discovery.mjs"
|
|
45
|
+
export { DEFAULT_MANIFEST, MANIFEST_SCHEMA, isValidDocRootValue, validateManifest } from "./manifest-schema.mjs"
|
|
141
46
|
|
|
142
47
|
/**
|
|
143
48
|
* **按用点解析**的读侧单点(KD-M1-24 / M1-30——**非抛错 / 零写 / 无缓存**):两段合成——
|
|
@@ -188,19 +93,6 @@ export function docRootBase(cwd) {
|
|
|
188
93
|
return resolveProjectRoot(cwd) ?? resolve(cwd ?? ".")
|
|
189
94
|
}
|
|
190
95
|
|
|
191
|
-
/**
|
|
192
|
-
* `docRoot` 子键**值形态判据**(F7 / KD-M1-6 / KD-M1-8——判据单源):非空字符串(`trim` 后
|
|
193
|
-
* 非空),或非空数组且元素皆非空字符串(**同款口径**:元素 `trim` 后非空);其余(空串 /
|
|
194
|
-
* 空白串 / 空数组 / 数组含非串 / 空串 / 空白串元素 / 非串非数组)为非法。
|
|
195
|
-
* @param {unknown} value docRoot 某子键的值
|
|
196
|
-
* @returns {boolean}
|
|
197
|
-
*/
|
|
198
|
-
export function isValidDocRootValue(value) {
|
|
199
|
-
if (typeof value === "string") return value.trim() !== ""
|
|
200
|
-
if (!Array.isArray(value) || value.length === 0) return false
|
|
201
|
-
return value.every((p) => typeof p === "string" && p.trim() !== "")
|
|
202
|
-
}
|
|
203
|
-
|
|
204
96
|
/**
|
|
205
97
|
* `docRoot` 子键值 → **绝对路径数组**(F7 / §2.7 解析管线——全在本模块一处):展开(串 /
|
|
206
98
|
* 数组统一成列表)→ 逐元素 `trim` + `\` 归一 → `resolve(docRootBase(cwd), p)`(基数 =
|
|
@@ -234,97 +126,6 @@ export function manifestFilePath(cwd) {
|
|
|
234
126
|
return join(writeRoot(cwd), MANIFEST_REL)
|
|
235
127
|
}
|
|
236
128
|
|
|
237
|
-
/** 默认五键(架构 §2.3 E1 逐字)——整档初始化的写源与缺键 fallback 的补源。 */
|
|
238
|
-
export const DEFAULT_MANIFEST = Object.freeze({
|
|
239
|
-
version: 1,
|
|
240
|
-
phase: "initial-dev",
|
|
241
|
-
docRoot: Object.freeze({
|
|
242
|
-
requirements: "docs/requirements",
|
|
243
|
-
specs: "docs/requirements/specs",
|
|
244
|
-
design: "docs/design",
|
|
245
|
-
modules: "docs/design/modules",
|
|
246
|
-
batches: "docs/batches",
|
|
247
|
-
}),
|
|
248
|
-
promptsLanding: "thincoder-core/prompts",
|
|
249
|
-
checkConfig: Object.freeze({
|
|
250
|
-
scanDirs: Object.freeze(["docs"]),
|
|
251
|
-
lineWidth: 300,
|
|
252
|
-
anchors: Object.freeze({ domain: "docs", exclude: Object.freeze(["_archive", "batches"]) }),
|
|
253
|
-
exemptions: Object.freeze([]),
|
|
254
|
-
}),
|
|
255
|
-
})
|
|
256
|
-
|
|
257
|
-
/** 校验判据(模块设计 §2.2)——枚举 / 键存在(fallback 用);判据单源(KD-M1-4)。 */
|
|
258
|
-
export const MANIFEST_SCHEMA = Object.freeze({
|
|
259
|
-
$anchor: "docs/core/design/MANIFEST.md",
|
|
260
|
-
enum: Object.freeze({
|
|
261
|
-
phase: Object.freeze(["initial-dev", "production"]),
|
|
262
|
-
}),
|
|
263
|
-
keys: Object.freeze(Object.keys(DEFAULT_MANIFEST)),
|
|
264
|
-
nestedKeys: Object.freeze({
|
|
265
|
-
docRoot: Object.freeze(Object.keys(DEFAULT_MANIFEST.docRoot)),
|
|
266
|
-
checkConfig: Object.freeze(Object.keys(DEFAULT_MANIFEST.checkConfig)),
|
|
267
|
-
}),
|
|
268
|
-
})
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* 形状校验(模块设计 §2.2)——枚举 / version 数值恒做;**纯函数、零 fs**(原 `{ cwd }`
|
|
272
|
-
* 指针腿已随字段整链裁撤——KD-M1-5 墓志);不落盘。键存在性入 missingKeys(非拒):
|
|
273
|
-
* 缺键 = 便利 fallback(补默认值),不是错(与整档缺失两分——KD-M1-2)。
|
|
274
|
-
* @param {object} obj 待校验 manifest 对象
|
|
275
|
-
* @returns {{ok:boolean, errors:string[], missingKeys:string[]}}
|
|
276
|
-
*/
|
|
277
|
-
export function validateManifest(obj) {
|
|
278
|
-
const errors = []
|
|
279
|
-
const missingKeys = []
|
|
280
|
-
if (obj === null || typeof obj !== "object" || Array.isArray(obj)) {
|
|
281
|
-
return { ok: false, errors: ["manifest 顶层必须是 JSON 对象"], missingKeys }
|
|
282
|
-
}
|
|
283
|
-
for (const key of MANIFEST_SCHEMA.keys) {
|
|
284
|
-
if (!(key in obj)) missingKeys.push(key)
|
|
285
|
-
}
|
|
286
|
-
for (const [nested, subkeys] of Object.entries(MANIFEST_SCHEMA.nestedKeys)) {
|
|
287
|
-
const value = obj[nested]
|
|
288
|
-
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
289
|
-
missingKeys.push(nested) // 整键缺失/非对象 → 与整键缺同语义(整键补默认)
|
|
290
|
-
continue
|
|
291
|
-
}
|
|
292
|
-
for (const sub of subkeys) {
|
|
293
|
-
if (!(sub in value)) { missingKeys.push(`${nested}.${sub}`); continue } // 子键路径(AC-3 / T3b)
|
|
294
|
-
// docRoot 子键值形态(F7 / KD-M1-7):串 | 非空串数组;非法 → 拒(不静默跳过)。
|
|
295
|
-
if (nested === "docRoot" && !isValidDocRootValue(value[sub])) {
|
|
296
|
-
errors.push(`docRoot.${sub} 值形态非法:${JSON.stringify(value[sub])}(须为非空字符串或非空字符串数组——KD-M1-6 / KD-M1-7)`)
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
if (obj.phase !== undefined && !MANIFEST_SCHEMA.enum.phase.includes(obj.phase)) {
|
|
301
|
-
errors.push(`phase 取值非法:"${obj.phase}"(允许:${MANIFEST_SCHEMA.enum.phase.join(" | ")})`)
|
|
302
|
-
}
|
|
303
|
-
if (obj.version !== undefined && (typeof obj.version !== "number" || !Number.isFinite(obj.version))) {
|
|
304
|
-
errors.push(`version 非法:${JSON.stringify(obj.version)}(须为数值)`)
|
|
305
|
-
}
|
|
306
|
-
return { ok: errors.length === 0, errors, missingKeys }
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
/** 缺键补默认值(module 设计 §2.2 管线):顶层缺键补默认、docRoot/checkConfig 子键补默认
|
|
310
|
-
* (与整键缺同语义);不覆写既有值。深拷贝默认源(structuredClone)——冻结常量永不外泄引用。 */
|
|
311
|
-
function fillDefaults(obj) {
|
|
312
|
-
const out = structuredClone(DEFAULT_MANIFEST)
|
|
313
|
-
for (const key of MANIFEST_SCHEMA.keys) {
|
|
314
|
-
if (!(key in obj)) continue
|
|
315
|
-
if (key === "docRoot" || key === "checkConfig") {
|
|
316
|
-
const src = obj[key]
|
|
317
|
-
if (src === null || typeof src !== "object" || Array.isArray(src)) continue // 整键非对象 → 保持默认
|
|
318
|
-
for (const sub of Object.keys(out[key])) {
|
|
319
|
-
if (sub in src) out[key][sub] = src[sub]
|
|
320
|
-
}
|
|
321
|
-
} else {
|
|
322
|
-
out[key] = obj[key]
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
return out
|
|
326
|
-
}
|
|
327
|
-
|
|
328
129
|
/**
|
|
329
130
|
* 读 cwd 根数据档(模块设计 §2.2):读 + validateManifest(obj) → 缺键产
|
|
330
131
|
* missingKeys(非拒)→ 补默认值 → 再校验通过。整档缺失 → { ok:false, reason:'missing' }
|
|
@@ -440,7 +241,7 @@ export function writeManifest(cwd, manifest, { writer = "subagent" } = {}) {
|
|
|
440
241
|
}
|
|
441
242
|
|
|
442
243
|
/**
|
|
443
|
-
* 初始化 =
|
|
244
|
+
* 初始化 = 写默认八键档(模块设计 §2.2):只提供机制,不包交互问答(问答归壳面)。
|
|
444
245
|
* 落盘走同一写门(内部 writeManifest(cwd, DEFAULT_MANIFEST, { writer }))——与 AC-M1-5
|
|
445
246
|
* 同一闸,无第二条写路径(KD-M1-4 / 评审 #12)。
|
|
446
247
|
* @returns {object} 写入的默认 manifest
|
package/mcp.mjs
CHANGED
|
@@ -6,6 +6,7 @@ import { INIT_TIMEOUT_MS, withTimeout, sanitizeToolName } from "./mcp/helpers.mj
|
|
|
6
6
|
import { stdioTransport } from "./mcp/transport-stdio.mjs"
|
|
7
7
|
import { httpTransport } from "./mcp/transport-http.mjs"
|
|
8
8
|
import { wsTransport } from "./mcp/transport-ws.mjs"
|
|
9
|
+
import { resolveMcpServerSecrets } from "./env-ref.mjs"
|
|
9
10
|
|
|
10
11
|
/** F6/D-5:config.token → `Authorization: Bearer <token>` 合成(仅当 headers 未显式给
|
|
11
12
|
* Authorization——显式优先,向后兼容)。合成发生在传给 transport 前,不写回 config。 */
|
|
@@ -52,7 +53,9 @@ export const _mcpHooks = {
|
|
|
52
53
|
|
|
53
54
|
/** 按 config 创建并完成握手的 transport(findTransportConfig 与 vscode 对齐)。 */
|
|
54
55
|
async function createConnectedTransport(rawConfig, serverName) {
|
|
55
|
-
|
|
56
|
+
// #57(provider-config-family 批):值位 `${env:VAR}` 消费侧解析(CONFIG.md §6.3)——传输建连单点
|
|
57
|
+
// (connect ∕ probe ∕ 重连同享);指纹 ∕ 漂移比对仍按存储原文(connectMcpServer 的 configFingerprint 零改)。
|
|
58
|
+
const config = withBearerToken(resolveMcpServerSecrets(rawConfig)) // F6/D-5:token 合成(不写回原 config)
|
|
56
59
|
let transport
|
|
57
60
|
if (config.wsUrl) {
|
|
58
61
|
transport = wsTransport(config.wsUrl, config.headers ?? {})
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* memory/code-search.mjs — 代码检索面(自 `memory/code-sync.mjs` 迁出 · 2026-10-01 core 拆分批 #755 ∥ #786)。
|
|
3
|
+
* 内容 = `CODE_EMBED_BATCH` ∥ `codeSearch`(FTS5(BM25) + 向量余弦,RRF 合并;无 embedder ⇒ 纯 FTS,
|
|
4
|
+
* ftsQuery 空且有 embedder ⇒ 纯向量)∥ `ensureCodeEmbeddings`(并发锁 + `_runEnsureCodeEmbeddings`)
|
|
5
|
+
* ∥ `codeSearchTool`——迁出块逐字;原档 `memory/code-sync.mjs` 经 `export { … } from` 转口保名
|
|
6
|
+
* (消费面 / 批内件 import 面零改)。
|
|
7
|
+
*/
|
|
8
|
+
import { embed, cosine, toBlob, fromBlob } from "../embedding.mjs"
|
|
9
|
+
import { scanVectors, createTopK } from "./scan.mjs"
|
|
10
|
+
import { normalizeOrigin } from "./origin.mjs"
|
|
11
|
+
import { declaredPublicRoots } from "../conventions.mjs"
|
|
12
|
+
import { buildFtsQuery, ensureEmbeddings, EMBED_TEXT_MAX_LEN } from "./core.mjs"
|
|
13
|
+
import { safeSliceUTF16 } from "../text-budget.mjs"
|
|
14
|
+
import { DESC } from "../tools/shared.mjs" // #15 描述外置:文本单点 = tool-docs/code_search.md
|
|
15
|
+
|
|
16
|
+
const CODE_EMBED_BATCH = 64
|
|
17
|
+
|
|
18
|
+
/** 读面 origin 集(§6.15):项目 origin ∪ 声明公共仓 origin(归一 ∥ 去重 ∥ 升序——BINARY 序 = PK 序;趟序 ∕ 到达序无关);无项目 ⇒ 空集。 */
|
|
19
|
+
export function searchOrigins(memory) {
|
|
20
|
+
const project = normalizeOrigin(memory.codeOrigin)
|
|
21
|
+
if (!project) return []
|
|
22
|
+
const set = new Set([project, ...declaredPublicRoots(memory.codeOrigin).map((p) => normalizeOrigin(p))])
|
|
23
|
+
return [...set].sort((a, b) => Buffer.compare(Buffer.from(a, "utf8"), Buffer.from(b, "utf8")))
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Code search: FTS5(BM25) + optional vector cosine, RRF merged.
|
|
28
|
+
* Falls back to pure FTS when no embedder; falls back to pure vector when ftsQuery is empty and embedder is present.
|
|
29
|
+
*/
|
|
30
|
+
export async function codeSearch(memory, query, { limit = 5 } = {}) {
|
|
31
|
+
const ftsQuery = buildFtsQuery(query)
|
|
32
|
+
if (!ftsQuery && !memory.embedder) return []
|
|
33
|
+
|
|
34
|
+
// §6.15 读面 origin 集(项目 ∪ 声明公共仓;单 origin ⇒ 逐字零行为——快径 SQL 形零改)
|
|
35
|
+
const origins = searchOrigins(memory)
|
|
36
|
+
const ftsOriginFilter = origins.length === 1 ? `AND c.origin = ?` : origins.length > 1 ? `AND c.origin IN (${origins.map(() => "?").join(", ")})` : ""
|
|
37
|
+
const originParams = origins
|
|
38
|
+
|
|
39
|
+
const ftsList = ftsQuery ? memory.db.prepare(`
|
|
40
|
+
SELECT c.rowid, c.path, c.language, c.symbol_name, c.content, c.line_start, c.line_end, bm25(code_chunks_fts) AS rank
|
|
41
|
+
FROM code_chunks_fts JOIN code_chunks c ON c.rowid = code_chunks_fts.rowid
|
|
42
|
+
WHERE code_chunks_fts MATCH ? ${ftsOriginFilter}
|
|
43
|
+
ORDER BY rank LIMIT ?
|
|
44
|
+
`).all(ftsQuery, ...originParams, Math.max(limit * 4, 20)) : []
|
|
45
|
+
|
|
46
|
+
if (!memory.embedder) return ftsList.slice(0, limit)
|
|
47
|
+
|
|
48
|
+
let ensured
|
|
49
|
+
try { ensured = await ensureEmbeddings(memory) } catch (e) {
|
|
50
|
+
console.error(`[code] embedding ensure failed, falling back to FTS-only: ${e.message}`)
|
|
51
|
+
return ftsList.slice(0, limit)
|
|
52
|
+
}
|
|
53
|
+
if (ensured?.code) { // §6.14 B7 读面零写:模型键失配 ⇒ 本面向量通道降级 FTS-only(一行可见;失效执行归维护口)
|
|
54
|
+
console.warn("[code] embedding model changed — vector channel degraded to FTS-only (rebuild runs at maintenance: sync tail / reindex)")
|
|
55
|
+
return ftsList.slice(0, limit)
|
|
56
|
+
}
|
|
57
|
+
let qvec
|
|
58
|
+
try { [qvec] = await embed(memory.embedder, [query]) } catch (e) {
|
|
59
|
+
console.error(`[code] query embedding failed, falling back to FTS-only: ${e.message}`)
|
|
60
|
+
return ftsList.slice(0, limit)
|
|
61
|
+
}
|
|
62
|
+
// TUI-OOM-ROOTCAUSE(MEMORY.md §10.3):分块扫描 + 有界 top-K(原全表 .all()——峰值 = 块 + K)
|
|
63
|
+
// TUI 假死批(§6.10 修法 A1/A2):游标 = PK 去等值过滤前缀列(有 origin 过滤 ⇒ 2 元组;
|
|
64
|
+
// 无过滤 ⇒ 全 PK);scanVectors = async(让出)。SELECT 须携键列(游标值源)。
|
|
65
|
+
const cursorKey = origins.length > 0 ? ["path", "line_start"] : ["origin", "path", "line_start"]
|
|
66
|
+
const keyCols = cursorKey.join(", ")
|
|
67
|
+
const top = createTopK(Math.max(limit * 4, 20))
|
|
68
|
+
// §6.15 多 origin ⇒ 逐 origin 分趟(每趟单 origin 等值 + 游标键去等值前缀列——与单 origin 快径同形)
|
|
69
|
+
const vecSql = `SELECT rowid, ${keyCols}, embedding FROM code_chunks WHERE embedding IS NOT NULL ${origins.length ? "AND origin = ?" : ""}`
|
|
70
|
+
for (const origin of origins.length ? origins : [null]) {
|
|
71
|
+
await scanVectors(memory.db, vecSql, origin ? [origin] : [], {
|
|
72
|
+
cursorKey,
|
|
73
|
+
onRow: (r) => top.push({ id: r.rowid, rowid: r.rowid, score: cosine(qvec, fromBlob(r.embedding)) }),
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
const vecList = top.list().map((c) => ({ rowid: c.id, score: c.score }))
|
|
77
|
+
|
|
78
|
+
const K = 60
|
|
79
|
+
const scores = new Map()
|
|
80
|
+
ftsList.forEach((r, i) => scores.set(r.rowid, (scores.get(r.rowid) ?? 0) + 1 / (K + i + 1)))
|
|
81
|
+
vecList.forEach((r, i) => scores.set(r.rowid, (scores.get(r.rowid) ?? 0) + 1 / (K + i + 1)))
|
|
82
|
+
|
|
83
|
+
const fetchChunk = memory.db.prepare(`
|
|
84
|
+
SELECT path, language, symbol_name, content, line_start, line_end FROM code_chunks WHERE rowid = ?
|
|
85
|
+
`)
|
|
86
|
+
const sorted = [...scores.entries()]
|
|
87
|
+
.sort((a, b) => b[1] - a[1])
|
|
88
|
+
.slice(0, limit)
|
|
89
|
+
return sorted
|
|
90
|
+
.map(([rowid, score]) => {
|
|
91
|
+
const chunk = fetchChunk.get(rowid)
|
|
92
|
+
if (!chunk) return null
|
|
93
|
+
chunk._score = Math.round(score * 100) / 100
|
|
94
|
+
return chunk
|
|
95
|
+
})
|
|
96
|
+
.filter(Boolean)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Lazily backfill missing vectors for code_chunks. Guarded against concurrent calls. */
|
|
100
|
+
let _codeEmbedLock = null
|
|
101
|
+
export function ensureCodeEmbeddings(memory) {
|
|
102
|
+
if (_codeEmbedLock) return _codeEmbedLock
|
|
103
|
+
_codeEmbedLock = _runEnsureCodeEmbeddings(memory).finally(() => { _codeEmbedLock = null })
|
|
104
|
+
return _codeEmbedLock
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function _runEnsureCodeEmbeddings(memory) {
|
|
108
|
+
if (!memory.embedder) return
|
|
109
|
+
const modelKey = memory.embedder.model
|
|
110
|
+
const stored = memory.db.prepare(`SELECT value FROM meta WHERE key = 'code_embedding_model'`).get()?.value
|
|
111
|
+
if (stored !== modelKey) {
|
|
112
|
+
// §6.14 B7 读面零写:只判定(返回降级信号——调用方 FTS-only + 一行可见);失效**执行**
|
|
113
|
+
// (清向量 + 落键)归维护口 `invalidateStaleEmbeddings`(同步尾 ∕ /reindex ∕ 桌面 ∕ VSC)。
|
|
114
|
+
return { mismatch: true }
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const pending = memory.db.prepare(`SELECT rowid, path, symbol_name, content FROM code_chunks WHERE embedding IS NULL LIMIT ${CODE_EMBED_BATCH}`).all()
|
|
118
|
+
if (pending.length === 0) return
|
|
119
|
+
|
|
120
|
+
const texts = pending.map((r) => `${r.path}${r.symbol_name ? " :: " + r.symbol_name : ""}\n${safeSliceUTF16(r.content, EMBED_TEXT_MAX_LEN)}`)
|
|
121
|
+
const vecs = await embed(memory.embedder, texts)
|
|
122
|
+
|
|
123
|
+
const update = memory.db.prepare(`UPDATE code_chunks SET embedding = ? WHERE rowid = ?`)
|
|
124
|
+
pending.forEach((r, i) => update.run(toBlob(vecs[i]), r.rowid))
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Generate the code_search tool (read-only). */
|
|
128
|
+
export function codeSearchTool(memory) {
|
|
129
|
+
return {
|
|
130
|
+
name: "code_search",
|
|
131
|
+
description: DESC("code_search"), // #15 外置:文本单点 = tool-docs/code_search.md
|
|
132
|
+
parameters: {
|
|
133
|
+
type: "object",
|
|
134
|
+
properties: {
|
|
135
|
+
query: { type: "string", description: "Natural language or code snippet to search for" },
|
|
136
|
+
limit: { type: "number", description: "Max results (default 5)" },
|
|
137
|
+
},
|
|
138
|
+
required: ["query"],
|
|
139
|
+
},
|
|
140
|
+
readonly: true,
|
|
141
|
+
async execute(args) {
|
|
142
|
+
const results = await codeSearch(memory, args.query, { limit: args.limit ?? 5 })
|
|
143
|
+
if (results.length === 0) return "(no matching code)"
|
|
144
|
+
return results.map((r) =>
|
|
145
|
+
`${r.path}${r.symbol_name ? ` :: ${r.symbol_name}` : ""} (L${r.line_start}-L${r.line_end}, relevance ${r._score?.toFixed(2) ?? "?"}):\n${r.content.slice(0, 2000)}`
|
|
146
|
+
).join("\n\n---\n\n")
|
|
147
|
+
},
|
|
148
|
+
}
|
|
149
|
+
}
|