Skip to content

Commit 88705bd

Browse files
committed
feat: implement semantic search functionality and enhance model management in CLI
1 parent c50e6e2 commit 88705bd

10 files changed

Lines changed: 136 additions & 30 deletions

File tree

README.en.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,37 @@ marchen status <name> [--json] # View artifact status and workflow su
7878
marchen instructions <name> <artifact> # Get artifact creation instructions (JSON)
7979
marchen archive <name> [--summary <text>] # Archive change and write to changelog
8080
marchen update # Update skill/command files to latest version
81+
marchen search <query> [--rebuild] # Search archived change history
8182
```
8283

84+
## Semantic Search
85+
86+
MarchenSpec includes local-model-based semantic search for retrieving relevant design decisions and change records from your archive history.
87+
88+
```bash
89+
marchen search "user auth" # Semantic search across archives
90+
marchen search "refactor" -n 10 # Specify result count
91+
marchen search "auth" --min-score 0.5 # Set minimum score threshold
92+
marchen search "auth" --rebuild # Rebuild index before searching
93+
```
94+
95+
Required models (~2GB) are downloaded automatically on first use. If models are not installed, it falls back to BM25 keyword search.
96+
97+
Semantic search requires SQLite:
98+
99+
```bash
100+
# macOS
101+
brew install sqlite
102+
103+
# Debian/Ubuntu
104+
sudo apt install sqlite3 libsqlite3-dev
105+
106+
# RHEL/Fedora
107+
sudo dnf install sqlite sqlite-devel
108+
```
109+
110+
The explore and apply skills also leverage search to automatically retrieve relevant history as context during workflows.
111+
83112
## Updating
84113

85114
After upgrading marchen-spec, run update to sync skill files:

README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,37 @@ marchen status <name> [--json] # 查看 artifact 状态和工作流
7878
marchen instructions <name> <artifact> # 获取 artifact 创建指令(JSON)
7979
marchen archive <name> [--summary <text>] # 归档变更并写入 changelog
8080
marchen update # 更新 skill/command 文件到最新版本
81+
marchen search <query> [--rebuild] # 搜索归档变更历史
8182
```
8283

84+
## 语义搜索
85+
86+
MarchenSpec 内置基于本地模型的语义搜索,可以从归档历史中检索相关的设计决策和变更记录。
87+
88+
```bash
89+
marchen search "用户认证" # 语义搜索归档历史
90+
marchen search "重构" -n 10 # 指定结果数量
91+
marchen search "认证" --min-score 0.5 # 设置最低分数阈值
92+
marchen search "认证" --rebuild # 重建索引后搜索
93+
```
94+
95+
首次使用时会自动下载所需模型(约 2GB)。如果模型未安装,自动降级为 BM25 关键词搜索。
96+
97+
语义搜索依赖 SQLite:
98+
99+
```bash
100+
# macOS
101+
brew install sqlite
102+
103+
# Debian/Ubuntu
104+
sudo apt install sqlite3 libsqlite3-dev
105+
106+
# RHEL/Fedora
107+
sudo dnf install sqlite sqlite-devel
108+
```
109+
110+
explore 和 apply skill 也会利用搜索能力,在工作流中自动检索相关历史作为上下文。
111+
83112
## 更新
84113

85114
升级 marchen-spec 后,运行 update 同步 skill 文件:

apps/cli/src/commands/init.ts

Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { ModelDownloadProgress } from '@marchen-spec/core'
22
import type { Command } from 'commander'
33
import * as p from '@clack/prompts'
44
import { AGENT_PROVIDERS } from '@marchen-spec/config'
5-
import { ModelManager } from '@marchen-spec/core'
5+
import { ModelManager, SearchManager } from '@marchen-spec/core'
66
import { createContext } from '../utils/context.js'
77

88
/** 模型类型显示名称 */
@@ -99,31 +99,41 @@ export function registerInitCommand(program: Command): void {
9999
.join(', ')
100100
p.log.success(`已为 ${names} 生成 skills 文件`)
101101

102-
// 询问是否下载搜索模型
103-
const modelManager = new ModelManager()
104-
const hasModels = await modelManager.hasLocalModels()
102+
// 检测搜索环境
103+
const search = new SearchManager(workspace)
104+
const qmdAvailable = await search.isAvailable()
105105

106-
if (hasModels) {
107-
p.log.info('语义搜索已就绪')
106+
if (!qmdAvailable) {
107+
p.log.warn(
108+
'语义搜索不可用(缺少 SQLite 依赖),使用基础关键词搜索',
109+
)
110+
p.log.info('安装 SQLite 后可通过 marchen search 启用语义搜索')
108111
} else {
109-
const downloadModels = await p.confirm({
110-
message: '是否启用语义搜索?(需要下载约 2GB 模型)',
111-
initialValue: false,
112-
})
112+
const modelManager = new ModelManager()
113+
const hasModels = await modelManager.hasLocalModels()
113114

114-
if (!p.isCancel(downloadModels) && downloadModels) {
115-
const spinner = p.spinner()
116-
spinner.start('下载搜索模型...')
117-
await modelManager.ensureModels({
118-
onProgress: (prog) => {
119-
spinner.message(formatModelProgress(prog))
120-
},
121-
})
122-
spinner.stop('语义搜索已启用')
115+
if (hasModels) {
116+
p.log.info('语义搜索已就绪')
123117
} else {
124-
p.log.info(
125-
'使用基础关键词搜索,后续可通过 marchen search --rebuild 启用语义搜索',
126-
)
118+
const downloadModels = await p.confirm({
119+
message: '是否启用语义搜索?(需要下载约 2GB 模型)',
120+
initialValue: false,
121+
})
122+
123+
if (!p.isCancel(downloadModels) && downloadModels) {
124+
const spinner = p.spinner()
125+
spinner.start('下载搜索模型...')
126+
await modelManager.ensureModels({
127+
onProgress: (prog) => {
128+
spinner.message(formatModelProgress(prog))
129+
},
130+
})
131+
spinner.stop('语义搜索已启用')
132+
} else {
133+
p.log.info(
134+
'使用基础关键词搜索,后续可通过 marchen search --rebuild 启用语义搜索',
135+
)
136+
}
127137
}
128138
}
129139

apps/cli/src/commands/search.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ export function registerSearchCommand(program: Command): void {
7979
onModelProgress: (prog) => {
8080
spinner?.message(formatModelProgress(prog))
8181
},
82+
downloadIfMissing: false,
8283
})
8384
spinner?.stop('搜索引擎就绪')
8485

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
name: fix-search-model-download
2+
schema: lite
3+
createdAt: '2026-04-29T09:06:58.929Z'
4+
status: archived
5+
archivedAt: '2026-04-29T09:16:44.742Z'
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
## 背景
2+
3+
`marchen search` 命令始终向 `SearchManager.prepare()` 传递 `onModelProgress` 回调,导致即使用户没有安装本地模型也会触发模型下载。`prepare()` 的设计意图是"有回调 = 要下载",但 CLI 只是想展示进度,不一定要下载。
4+
5+
方案:给 `PrepareOptions` 增加 `downloadIfMissing` 选项(默认 false),解耦"展示进度"和"触发下载"两个语义。CLI search 命令默认不下载,只有显式场景才触发下载。
6+
7+
## 1. core 层:扩展 PrepareOptions
8+
9+
- [x] 1.1 `PrepareOptions` 增加 `downloadIfMissing?: boolean` 字段
10+
- [x] 1.2 重写 `prepare()` 分支逻辑:downloadIfMissing=true 时下载,否则仅本地有模型才加载
11+
12+
## 2. CLI 层:适配调用方
13+
14+
- [x] 2.1 search 命令传 `downloadIfMissing: false`
15+
- [x] 2.2 确认其他调用点(如 archive 中的 indexChange)无需改动

marchen/changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,4 @@
3232
- 2026-04-24: [integrate-qmd-search](./archive/2026-04-24-integrate-qmd-search/) — 集成 qmd 语义搜索,新增 SearchManager 和 marchen search 命令,archive 时自动索引
3333
- 2026-04-26: [add-qmd-model-source](./archive/2026-04-26-add-qmd-model-source/) — 支持从自定义CDN下载qmd模型,新增ModelManager管理模型生命周期,SearchManager分离prepare阶段并展示下载进度
3434
- 2026-04-26: [integrate-search-skills](./archive/2026-04-26-integrate-search-skills/) — 搜索自动降级BM25关键词模式,init增加模型下载选项,explore和apply模板集成语义搜索
35+
- 2026-04-29: [fix-search-model-download](./archive/2026-04-29-fix-search-model-download/) — search命令增加downloadIfMissing选项,无本地模型时不触发下载,自动降级BM25搜索
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
name: fix-init-search-check
2+
schema: lite
3+
createdAt: '2026-04-29T09:38:25.753Z'
4+
status: open
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
## 背景
2+
3+
`marchen init` 的模型下载询问没有先检测 qmd SDK 是否可用。如果用户没装 SQLite 依赖,qmd import 会失败,但 init 仍然会询问是否下载 2GB 模型——下了也用不了。
4+
5+
方案:在询问模型下载前,先用 `SearchManager.isAvailable()` 检测 qmd 能否加载。不可用时直接提示缺少依赖,跳过模型下载询问。
6+
7+
## 1. init 命令增加 qmd 可用性检测
8+
9+
- [x] 1.1 在模型下载逻辑前,用 `SearchManager.isAvailable()` 检测 qmd SDK
10+
- [x] 1.2 qmd 不可用时提示缺少依赖并跳过模型下载询问,可用时保持现有流程

packages/core/src/search-manager.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ export interface SearchOptions {
2727
export interface PrepareOptions {
2828
/** 模型下载进度回调 */
2929
readonly onModelProgress?: (progress: ModelDownloadProgress) => void
30+
/** 本地无模型时是否触发下载,默认 false(降级为 BM25) */
31+
readonly downloadIfMissing?: boolean
3032
}
3133

3234
/**
@@ -67,14 +69,14 @@ export class SearchManager {
6769
const { ModelManager } = await import('./model-manager.js')
6870
const modelManager = new ModelManager()
6971

70-
if (options?.onModelProgress) {
71-
const paths = await modelManager.ensureModels({
72-
onProgress: options.onModelProgress,
73-
})
74-
modelManager.applyEnv(paths)
75-
this.modelsReady = true
76-
} else if (await modelManager.hasLocalModels()) {
77-
const paths = await modelManager.ensureModels()
72+
const shouldLoad =
73+
options?.downloadIfMissing || (await modelManager.hasLocalModels())
74+
75+
if (shouldLoad) {
76+
const ensureOpts = options?.onModelProgress
77+
? { onProgress: options.onModelProgress }
78+
: undefined
79+
const paths = await modelManager.ensureModels(ensureOpts)
7880
modelManager.applyEnv(paths)
7981
this.modelsReady = true
8082
}

0 commit comments

Comments
 (0)