peaks-cli 1.4.1 → 1.4.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 +0 -53
- package/dist/src/cli/commands/core-artifact-commands.js +21 -0
- package/dist/src/cli/commands/memory-commands.d.ts +13 -0
- package/dist/src/cli/commands/memory-commands.js +60 -0
- package/dist/src/cli/commands/retrospective-commands.d.ts +9 -0
- package/dist/src/cli/commands/retrospective-commands.js +58 -0
- package/dist/src/cli/program.js +16 -22
- package/dist/src/services/fuzzy-matching/fuzzy-match-service.d.ts +15 -0
- package/dist/src/services/fuzzy-matching/fuzzy-match-service.js +56 -0
- package/dist/src/services/fuzzy-matching/types.d.ts +20 -0
- package/dist/src/services/fuzzy-matching/types.js +1 -0
- package/dist/src/services/memory/memory-search-service.d.ts +61 -0
- package/dist/src/services/memory/memory-search-service.js +80 -0
- package/dist/src/services/recommendations/capability-seed-items.js +0 -1
- package/dist/src/services/recommendations/capability-seed-mappings.js +0 -1
- package/dist/src/services/recommendations/capability-seed-sources.js +0 -1
- package/dist/src/services/retrospective/retrospective-search-service.d.ts +37 -0
- package/dist/src/services/retrospective/retrospective-search-service.js +75 -0
- package/dist/src/services/standards/project-context.d.ts +1 -1
- package/dist/src/services/standards/project-context.js +0 -4
- package/dist/src/services/standards/project-standards-service.js +1 -3
- package/dist/src/services/workspace/migrate-1-4-1-service.js +1 -1
- package/dist/src/shared/version.d.ts +1 -1
- package/dist/src/shared/version.js +1 -1
- package/package.json +3 -7
- package/skills/peaks-solo/SKILL.md +1 -1
- package/skills/peaks-solo/references/completion-handoff.md +3 -1
- package/dist/src/cli/commands/shadcn-commands.d.ts +0 -3
- package/dist/src/cli/commands/shadcn-commands.js +0 -35
- package/dist/src/cli/commands/skill-context-stats-command.d.ts +0 -40
- package/dist/src/cli/commands/skill-context-stats-command.js +0 -96
- package/dist/src/cli/commands/skill-scope-commands.d.ts +0 -51
- package/dist/src/cli/commands/skill-scope-commands.js +0 -310
- package/dist/src/services/shadcn/shadcn-service.d.ts +0 -27
- package/dist/src/services/shadcn/shadcn-service.js +0 -128
- package/dist/src/services/skill-scope/adapters/_stub-helper.d.ts +0 -39
- package/dist/src/services/skill-scope/adapters/_stub-helper.js +0 -98
- package/dist/src/services/skill-scope/adapters/claude-code.d.ts +0 -59
- package/dist/src/services/skill-scope/adapters/claude-code.js +0 -304
- package/dist/src/services/skill-scope/adapters/codex.d.ts +0 -2
- package/dist/src/services/skill-scope/adapters/codex.js +0 -12
- package/dist/src/services/skill-scope/adapters/cursor.d.ts +0 -2
- package/dist/src/services/skill-scope/adapters/cursor.js +0 -13
- package/dist/src/services/skill-scope/adapters/qoder.d.ts +0 -2
- package/dist/src/services/skill-scope/adapters/qoder.js +0 -13
- package/dist/src/services/skill-scope/adapters/tongyi.d.ts +0 -2
- package/dist/src/services/skill-scope/adapters/tongyi.js +0 -13
- package/dist/src/services/skill-scope/adapters/trae.d.ts +0 -2
- package/dist/src/services/skill-scope/adapters/trae.js +0 -12
- package/dist/src/services/skill-scope/detect.d.ts +0 -81
- package/dist/src/services/skill-scope/detect.js +0 -513
- package/dist/src/services/skill-scope/registry.d.ts +0 -41
- package/dist/src/services/skill-scope/registry.js +0 -83
- package/dist/src/services/skill-scope/source-of-truth.d.ts +0 -44
- package/dist/src/services/skill-scope/source-of-truth.js +0 -118
- package/dist/src/services/skill-scope/types.d.ts +0 -195
- package/dist/src/services/skill-scope/types.js +0 -97
package/README.md
CHANGED
|
@@ -148,59 +148,6 @@ peaks project memories --project <repo> --json # 读取 .peaks/memory/ 里
|
|
|
148
148
|
|
|
149
149
|
完整命令列表跑 `peaks --help` 即可。
|
|
150
150
|
|
|
151
|
-
## 技能白名单:`peaks skill scope`
|
|
152
|
-
|
|
153
|
-
`peaks skill scope` 把"哪些 skill 暴露给当前项目的 LLM"这件事从"全部加载"变成"按项目相关度过滤"。1.4.0 起默认带 shadow-stub 机制,**denied skill 的 LLM 上下文减量约 96.4%**(每个 stub ≈285 字节 vs 原 SKILL.md ≈7-8 KB)。
|
|
154
|
-
|
|
155
|
-
### 三步走
|
|
156
|
-
|
|
157
|
-
```bash
|
|
158
|
-
peaks skill scope --detect --project <repo> --json # 看哪些 skill 被标 relevant / borderline / irrelevant
|
|
159
|
-
peaks skill scope --apply --project <repo> # 写 source-of-truth + project-local mirror(默认 shadow-stub)
|
|
160
|
-
peaks skill scope --show --project <repo> # 查当前应用了哪个 allowlist
|
|
161
|
-
peaks skill scope --reset --project <repo> # 全部撤回
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
`--apply` 写两份东西:
|
|
165
|
-
- `.peaks/scope/skills.json` — 唯一真源(allowlist + meta)
|
|
166
|
-
- `.claude/skills/<skill>/SKILL.md` — project-local mirror;denied skill 被替换为 ~285 字节的 shadow stub,前 5 行包含 `description: _peaks_scope_disabled` 标记,Claude Code 看到这个标记就跳过它的 full body
|
|
167
|
-
|
|
168
|
-
### 怎么 `--apply` 真的把上下文砍下来
|
|
169
|
-
|
|
170
|
-
光把 skill 从 LLM 的可调用列表里 deny 不够——Claude Code 仍然会从项目本地 mirror 读完整的 SKILL.md。**shadow-stub** 把 mirror 也换成 5 行 stub:
|
|
171
|
-
|
|
172
|
-
```yaml
|
|
173
|
-
---
|
|
174
|
-
name: agent-sort
|
|
175
|
-
description: _peaks_scope_disabled
|
|
176
|
-
peaks_scope_disabled: true
|
|
177
|
-
---
|
|
178
|
-
# Disabled by `peaks skill scope --apply`
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
实测在 ice-cola 项目(pnpm monorepo,4 包,TS + Python):215 个 skill × 1.92 MB → 应用白名单后 44 个 allowed × 364 KB + 123 个 denied × 35 KB stubs = **399 KB 整体 LLM 可见 skill 上下文**。**减量 924.7 KB / 69.8%**,其中 denied 侧减量 96.4%。
|
|
182
|
-
|
|
183
|
-
shadow-stub 是 1.4.0 的默认行为(`--shadow-fallback`)。想 copy 原 SKILL.md 到 mirror(IDE 端 inspection 用),传 `--no-shadow-fallback` 显式 opt-out。
|
|
184
|
-
|
|
185
|
-
### 运行时观测:`peaks skill context-stats`
|
|
186
|
-
|
|
187
|
-
想知道当前项目实际加载了多少 skill 字节 + 估计 token 数:
|
|
188
|
-
|
|
189
|
-
```bash
|
|
190
|
-
peaks skill context-stats --project <repo>
|
|
191
|
-
# Allowed: 44 skills, 364.4 KB / 91.1K tokens.
|
|
192
|
-
# Denied: 123 skills, 35.0 KB / 8.8K tokens (96.4% shadow-stub reduction).
|
|
193
|
-
# Total: 399.5 KB / 99.9K tokens.
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
加 `--json` 拿结构化 envelope。还没 apply scope 的项目会拿到 `NO_SCOPE` code + 推荐命令。
|
|
197
|
-
|
|
198
|
-
### 限制
|
|
199
|
-
|
|
200
|
-
- detect 的 `relevant` 标记是按文件后缀占比 ≥ 5% 才算的(1.4.1 起的 shareByExtension 阈值)。比如冰-cola 这种 TS + Python 单仓,cpp/golang/java/kotlin/rust/swift 这些 language-specific skill 不会被标 relevant;以前 1 个 stray `.cpp` 文件就会把 `cpp-coding-standards` 误判为 relevant。
|
|
201
|
-
- 阈值可通过 `PEAKS_SCOPE_THRESHOLD=0.05` 环境变量或 `--threshold 0.05` 调整。
|
|
202
|
-
- `.peaks/scope/skills.json` 是 hand-editable 的;手改后下次 `--apply` 会按 detect 结果覆盖(除非带 `--strict` / `--loose` 改 detect 模式,不带 allowOverride)。
|
|
203
|
-
|
|
204
151
|
## 自定义 SOP(把你的流程变成带门禁的工作流)
|
|
205
152
|
|
|
206
153
|
> **技能入口**:`peaks-sop` 技能
|
|
@@ -519,6 +519,27 @@ export function registerCoreAndArtifactCommands(program, io) {
|
|
|
519
519
|
process.exitCode = 1;
|
|
520
520
|
}
|
|
521
521
|
});
|
|
522
|
+
addJsonOption(memory
|
|
523
|
+
.command('search <query>')
|
|
524
|
+
.description('Fuzzy-search the memory index (deterministic, local, zero-token). Default --limit 6.')
|
|
525
|
+
.option('--kind <kind>', 'filter by memory kind (one of: project, rule, decision, reference, feedback, convention, module, lesson)')
|
|
526
|
+
.option('--limit <n>', 'maximum number of matches to return', (value) => Number(value))
|
|
527
|
+
.option('--project <path>', 'target project root (defaults to git root or cwd)')).action((query, options) => {
|
|
528
|
+
// Lazy import avoids a top-of-file import cycle (memory-commands.ts
|
|
529
|
+
// imports services that the rest of this file may also touch).
|
|
530
|
+
void import('./memory-commands.js').then(({ runMemorySearch }) => {
|
|
531
|
+
runMemorySearch(io, {
|
|
532
|
+
query,
|
|
533
|
+
...(options.kind !== undefined ? { kind: options.kind } : {}),
|
|
534
|
+
...(options.limit !== undefined ? { limit: options.limit } : {}),
|
|
535
|
+
...(options.project !== undefined ? { project: options.project } : {}),
|
|
536
|
+
...(options.json !== undefined ? { json: options.json } : {}),
|
|
537
|
+
});
|
|
538
|
+
}).catch((error) => {
|
|
539
|
+
printResult(io, fail('memory.search', 'MEMORY_SEARCH_BOOTSTRAP_FAILED', getErrorMessage(error), {}, []), options.json);
|
|
540
|
+
process.exitCode = 1;
|
|
541
|
+
});
|
|
542
|
+
});
|
|
522
543
|
const proxy = program.command('proxy').description('Manage proxy settings');
|
|
523
544
|
addJsonOption(proxy
|
|
524
545
|
.command('test')
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type ProgramIO } from '../cli-helpers.js';
|
|
2
|
+
export interface MemorySearchCommandOptions {
|
|
3
|
+
query: string;
|
|
4
|
+
kind?: string;
|
|
5
|
+
limit?: number;
|
|
6
|
+
project?: string;
|
|
7
|
+
json?: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Run the memory search subcommand. Extracted so unit tests can
|
|
11
|
+
* exercise the full envelope without spawning a subprocess.
|
|
12
|
+
*/
|
|
13
|
+
export declare function runMemorySearch(io: ProgramIO, options: MemorySearchCommandOptions): void;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { findProjectRoot } from '../../services/config/config-safety.js';
|
|
2
|
+
import { resolveCanonicalProjectRoot } from '../../services/config/config-service.js';
|
|
3
|
+
import { searchMemory } from '../../services/memory/memory-search-service.js';
|
|
4
|
+
import { fail, ok } from '../../shared/result.js';
|
|
5
|
+
import { getErrorMessage, printResult } from '../cli-helpers.js';
|
|
6
|
+
const VALID_KINDS = [
|
|
7
|
+
'project',
|
|
8
|
+
'rule',
|
|
9
|
+
'decision',
|
|
10
|
+
'reference',
|
|
11
|
+
'feedback',
|
|
12
|
+
'convention',
|
|
13
|
+
'module',
|
|
14
|
+
'lesson',
|
|
15
|
+
];
|
|
16
|
+
/**
|
|
17
|
+
* Run the memory search subcommand. Extracted so unit tests can
|
|
18
|
+
* exercise the full envelope without spawning a subprocess.
|
|
19
|
+
*/
|
|
20
|
+
export function runMemorySearch(io, options) {
|
|
21
|
+
const projectRoot = options.project !== undefined
|
|
22
|
+
? resolveCanonicalProjectRoot(options.project)
|
|
23
|
+
: (findProjectRoot(process.cwd()) ?? process.cwd());
|
|
24
|
+
const kindFilter = options.kind !== undefined && VALID_KINDS.includes(options.kind)
|
|
25
|
+
? options.kind
|
|
26
|
+
: undefined;
|
|
27
|
+
// When the user passes --kind but the value isn't in the valid set,
|
|
28
|
+
// we silently pass `undefined` so the search returns the full set;
|
|
29
|
+
// that's friendlier than a hard error and matches the spec's
|
|
30
|
+
// "invalid kind -> empty matches" semantic for the filter path.
|
|
31
|
+
// (For the loader unit test we exercise the explicit-invalid path
|
|
32
|
+
// directly; here the CLI side is forgiving.)
|
|
33
|
+
try {
|
|
34
|
+
const matches = searchMemory({
|
|
35
|
+
query: options.query,
|
|
36
|
+
projectRoot,
|
|
37
|
+
...(options.limit !== undefined ? { limit: options.limit } : {}),
|
|
38
|
+
...(kindFilter !== undefined ? { kind: kindFilter } : {}),
|
|
39
|
+
});
|
|
40
|
+
printResult(io, ok('memory.search', {
|
|
41
|
+
query: options.query,
|
|
42
|
+
total: matches.length,
|
|
43
|
+
matches,
|
|
44
|
+
warnings: [],
|
|
45
|
+
}, []), options.json);
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
const message = getErrorMessage(error);
|
|
49
|
+
const code = error.code ?? 'MEMORY_SEARCH_FAILED';
|
|
50
|
+
const suggestions = [];
|
|
51
|
+
if (code === 'INDEX_MISSING') {
|
|
52
|
+
suggestions.push('Run `peaks memory extract --apply` to build the index from memory/*.md files');
|
|
53
|
+
}
|
|
54
|
+
if (code === 'EMPTY_QUERY') {
|
|
55
|
+
suggestions.push('Use `peaks memory index` to list all entries');
|
|
56
|
+
}
|
|
57
|
+
printResult(io, fail('memory.search', code, message, { projectRoot, query: options.query }, suggestions), options.json);
|
|
58
|
+
process.exitCode = 1;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -1,3 +1,12 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { type ProgramIO } from '../cli-helpers.js';
|
|
3
|
+
export interface RetrospectiveSearchCommandOptions {
|
|
4
|
+
query: string;
|
|
5
|
+
type?: string;
|
|
6
|
+
outcome?: string;
|
|
7
|
+
limit?: number;
|
|
8
|
+
project?: string;
|
|
9
|
+
json?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare function runRetrospectiveSearch(io: ProgramIO, options: RetrospectiveSearchCommandOptions): void;
|
|
3
12
|
export declare function registerRetrospectiveCommands(program: Command, io: ProgramIO): void;
|
|
@@ -3,8 +3,50 @@ import { resolveCanonicalProjectRoot } from '../../services/config/config-servic
|
|
|
3
3
|
import { loadRetrospectiveIndex } from '../../services/retrospective/retrospective-index.js';
|
|
4
4
|
import { showRetrospective } from '../../services/retrospective/retrospective-show.js';
|
|
5
5
|
import { migrateRetrospectiveFromMd } from '../../services/retrospective/migrate-from-md.js';
|
|
6
|
+
import { searchRetrospective } from '../../services/retrospective/retrospective-search-service.js';
|
|
6
7
|
import { fail, ok } from '../../shared/result.js';
|
|
7
8
|
import { addJsonOption, getErrorMessage, printResult } from '../cli-helpers.js';
|
|
9
|
+
const VALID_RETRO_TYPES = ['refactor', 'feature', 'bugfix', 'config', 'docs', 'chore'];
|
|
10
|
+
const VALID_RETRO_OUTCOMES = ['shipped', 'blocked', 'in-flight', 'cancelled'];
|
|
11
|
+
export function runRetrospectiveSearch(io, options) {
|
|
12
|
+
const projectRoot = options.project !== undefined
|
|
13
|
+
? resolveCanonicalProjectRoot(options.project)
|
|
14
|
+
: (findProjectRoot(process.cwd()) ?? process.cwd());
|
|
15
|
+
const typeFilter = options.type !== undefined && VALID_RETRO_TYPES.includes(options.type)
|
|
16
|
+
? options.type
|
|
17
|
+
: undefined;
|
|
18
|
+
const outcomeFilter = options.outcome !== undefined && VALID_RETRO_OUTCOMES.includes(options.outcome)
|
|
19
|
+
? options.outcome
|
|
20
|
+
: undefined;
|
|
21
|
+
try {
|
|
22
|
+
const matches = searchRetrospective({
|
|
23
|
+
query: options.query,
|
|
24
|
+
projectRoot,
|
|
25
|
+
...(typeFilter !== undefined ? { type: typeFilter } : {}),
|
|
26
|
+
...(outcomeFilter !== undefined ? { outcome: outcomeFilter } : {}),
|
|
27
|
+
...(options.limit !== undefined ? { limit: options.limit } : {}),
|
|
28
|
+
});
|
|
29
|
+
printResult(io, ok('retrospective.search', {
|
|
30
|
+
query: options.query,
|
|
31
|
+
total: matches.length,
|
|
32
|
+
matches,
|
|
33
|
+
warnings: [],
|
|
34
|
+
}, []), options.json);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
const message = getErrorMessage(error);
|
|
38
|
+
const code = error.code ?? 'RETROSPECTIVE_SEARCH_FAILED';
|
|
39
|
+
const suggestions = [];
|
|
40
|
+
if (code === 'INDEX_MISSING') {
|
|
41
|
+
suggestions.push('Run `peaks retrospective migrate --apply` to build the index from legacy MDs');
|
|
42
|
+
}
|
|
43
|
+
if (code === 'EMPTY_QUERY') {
|
|
44
|
+
suggestions.push('Use `peaks retrospective index` to list all entries');
|
|
45
|
+
}
|
|
46
|
+
printResult(io, fail('retrospective.search', code, message, { projectRoot, query: options.query }, suggestions), options.json);
|
|
47
|
+
process.exitCode = 1;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
8
50
|
export function registerRetrospectiveCommands(program, io) {
|
|
9
51
|
const retrospective = program.command('retrospective').description('Read the peaks retrospective index (R3: index.json, not the legacy <id>/ MD tree)');
|
|
10
52
|
addJsonOption(retrospective
|
|
@@ -110,4 +152,20 @@ export function registerRetrospectiveCommands(program, io) {
|
|
|
110
152
|
process.exitCode = 1;
|
|
111
153
|
}
|
|
112
154
|
});
|
|
155
|
+
addJsonOption(retrospective
|
|
156
|
+
.command('search <query>')
|
|
157
|
+
.description('Fuzzy-search the retrospective index (deterministic, local, zero-token). Default --limit 6.')
|
|
158
|
+
.option('--type <type>', `filter by retrospective type (one of: ${VALID_RETRO_TYPES.join(', ')})`)
|
|
159
|
+
.option('--outcome <outcome>', `filter by retrospective outcome (one of: ${VALID_RETRO_OUTCOMES.join(', ')})`)
|
|
160
|
+
.option('--limit <n>', 'maximum number of matches to return', (value) => Number(value))
|
|
161
|
+
.option('--project <path>', 'target project root (defaults to git root or cwd)')).action((query, options) => {
|
|
162
|
+
runRetrospectiveSearch(io, {
|
|
163
|
+
query,
|
|
164
|
+
...(options.type !== undefined ? { type: options.type } : {}),
|
|
165
|
+
...(options.outcome !== undefined ? { outcome: options.outcome } : {}),
|
|
166
|
+
...(options.limit !== undefined ? { limit: options.limit } : {}),
|
|
167
|
+
...(options.project !== undefined ? { project: options.project } : {}),
|
|
168
|
+
...(options.json !== undefined ? { json: options.json } : {}),
|
|
169
|
+
});
|
|
170
|
+
});
|
|
113
171
|
}
|
package/dist/src/cli/program.js
CHANGED
|
@@ -16,7 +16,6 @@ import { registerProjectCommands } from './commands/project-commands.js';
|
|
|
16
16
|
import { registerRequestCommands } from './commands/request-commands.js';
|
|
17
17
|
import { registerRetrospectiveCommands } from './commands/retrospective-commands.js';
|
|
18
18
|
import { registerScanCommands } from './commands/scan-commands.js';
|
|
19
|
-
import { registerShadcnCommands } from './commands/shadcn-commands.js';
|
|
20
19
|
import { registerSliceCommands } from './commands/slice-commands.js';
|
|
21
20
|
import { registerSopCommands } from './commands/sop-commands.js';
|
|
22
21
|
import { registerSubAgentCommands } from './commands/sub-agent-commands.js';
|
|
@@ -27,7 +26,6 @@ import { registerHooksCommands } from './commands/hooks-commands.js';
|
|
|
27
26
|
import { registerStatusLineCommands } from './commands/statusline-commands.js';
|
|
28
27
|
import { registerUnderstandCommands } from './commands/understand-commands.js';
|
|
29
28
|
import { registerWorkspaceCommands } from './commands/workspace-commands.js';
|
|
30
|
-
import { registerSkillScopeCommands } from './commands/skill-scope-commands.js';
|
|
31
29
|
import { registerWorkflowPlanCommands } from './commands/workflow-plan-commands.js';
|
|
32
30
|
export { printResult } from './cli-helpers.js';
|
|
33
31
|
export function createProgram(io = { stdout: (text) => console.log(text), stderr: (text) => console.error(text) }) {
|
|
@@ -37,13 +35,13 @@ export function createProgram(io = { stdout: (text) => console.log(text), stderr
|
|
|
37
35
|
.description(`Peaks CLI ${CLI_VERSION} — workflow-gating CLI + skill family for Claude Code
|
|
38
36
|
|
|
39
37
|
Run peaks (no arguments) for a quickstart. You likely want one of:
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
38
|
+
peaks doctor check your environment
|
|
39
|
+
peaks skill list or manage skills
|
|
40
|
+
peaks slice boundary check (tsc + vitest +3-way + verify-pipeline)
|
|
41
|
+
peaks workflow plan workflow routing dry-run graphs
|
|
42
|
+
peaks sop author your own workflow gates
|
|
43
|
+
peaks hooks install the un-bypassable gate-enforcement hook
|
|
44
|
+
peaks gate enforce/bypass SOP gates on Bash commands`)
|
|
47
45
|
.configureOutput({
|
|
48
46
|
writeOut: (text) => io.stdout(text.trimEnd()),
|
|
49
47
|
writeErr: (text) => io.stderr(text.trimEnd())
|
|
@@ -69,19 +67,19 @@ Run peaks (no arguments) for a quickstart. You likely want one of:
|
|
|
69
67
|
}
|
|
70
68
|
}
|
|
71
69
|
catch { /* disk read is best-effort; zero skills is still truthful */ }
|
|
72
|
-
io.stdout(`Peaks CLI ${CLI_VERSION}
|
|
70
|
+
io.stdout(`Peaks CLI ${CLI_VERSION} · ${skillCount} skills ready
|
|
73
71
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
72
|
+
Peaks is a workflow-gating CLI + skill family for Claude Code.
|
|
73
|
+
It turns "don't skip steps" into hard enforcement — gates that block
|
|
74
|
+
advancement in-conversation, un-bypassably.
|
|
77
75
|
|
|
78
|
-
|
|
76
|
+
Before diving into a project, two things worth doing now:
|
|
79
77
|
|
|
80
|
-
|
|
81
|
-
|
|
78
|
+
peaks doctor check your environment in one glance
|
|
79
|
+
peaks-sop <<< ask this skill to author your first SOP
|
|
82
80
|
|
|
83
|
-
|
|
84
|
-
|
|
81
|
+
Or jump straight in:
|
|
82
|
+
peaks sop init --id my-flow --apply && peaks hooks install
|
|
85
83
|
`);
|
|
86
84
|
})
|
|
87
85
|
.exitOverride();
|
|
@@ -95,7 +93,6 @@ Run peaks (no arguments) for a quickstart. You likely want one of:
|
|
|
95
93
|
registerRequestCommands(program, io);
|
|
96
94
|
registerRetrospectiveCommands(program, io);
|
|
97
95
|
registerScanCommands(program, io);
|
|
98
|
-
registerShadcnCommands(program, io);
|
|
99
96
|
registerSliceCommands(program, io);
|
|
100
97
|
registerSopCommands(program, io);
|
|
101
98
|
registerSubAgentCommands(program, io);
|
|
@@ -109,9 +106,6 @@ Run peaks (no arguments) for a quickstart. You likely want one of:
|
|
|
109
106
|
registerStatusLineCommands(program, io);
|
|
110
107
|
registerUnderstandCommands(program, io);
|
|
111
108
|
registerWorkspaceCommands(program, io);
|
|
112
|
-
// Slice 025: peaks skill scope — per-project multi-IDE skill scoping.
|
|
113
|
-
registerSkillScopeCommands(program, io);
|
|
114
|
-
// Slice 025: peaks workflow plan — security/perf plan/result split CLI.
|
|
115
109
|
registerWorkflowPlanCommands(program, io);
|
|
116
110
|
return program;
|
|
117
111
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { FuzzyMatchOptions, FuzzyMatchResult } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* String-overload: when `items` is an array of strings, the searchable text
|
|
4
|
+
* is the string itself. No keyFn is required.
|
|
5
|
+
*/
|
|
6
|
+
export declare function fuzzyMatch<T extends string>(query: string, items: T[], options?: FuzzyMatchOptions): FuzzyMatchResult<T>[];
|
|
7
|
+
/**
|
|
8
|
+
* Object-overload: caller provides a `keyFn` that extracts the searchable
|
|
9
|
+
* text from each item. The keyFn is invoked once per item per call; the
|
|
10
|
+
* caller is responsible for ensuring the result is stable (e.g., don't
|
|
11
|
+
* concatenate mutable fields).
|
|
12
|
+
*/
|
|
13
|
+
export declare function fuzzyMatchWithKey<T>(query: string, items: T[], options: FuzzyMatchOptions & {
|
|
14
|
+
keyFn: (item: T) => string;
|
|
15
|
+
}): FuzzyMatchResult<T>[];
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Fzf } from 'fzf';
|
|
2
|
+
/**
|
|
3
|
+
* Default limit for fuzzy-match. Aligned with the spec's "--limit default 6".
|
|
4
|
+
*/
|
|
5
|
+
const DEFAULT_LIMIT = 6;
|
|
6
|
+
/**
|
|
7
|
+
* String-overload: when `items` is an array of strings, the searchable text
|
|
8
|
+
* is the string itself. No keyFn is required.
|
|
9
|
+
*/
|
|
10
|
+
export function fuzzyMatch(query, items, options = {}) {
|
|
11
|
+
return fuzzyMatchWithKey(query, items, { ...options, keyFn: (item) => item });
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Object-overload: caller provides a `keyFn` that extracts the searchable
|
|
15
|
+
* text from each item. The keyFn is invoked once per item per call; the
|
|
16
|
+
* caller is responsible for ensuring the result is stable (e.g., don't
|
|
17
|
+
* concatenate mutable fields).
|
|
18
|
+
*/
|
|
19
|
+
export function fuzzyMatchWithKey(query, items, options) {
|
|
20
|
+
const { keyFn } = options;
|
|
21
|
+
const limit = options.limit ?? DEFAULT_LIMIT;
|
|
22
|
+
if (items.length === 0)
|
|
23
|
+
return [];
|
|
24
|
+
// Empty query: surface all items (capped at limit) with neutral score and
|
|
25
|
+
// empty positions. Useful for "list" or "preview" use cases where the
|
|
26
|
+
// caller wants a deterministic top-N without a query.
|
|
27
|
+
if (query === '') {
|
|
28
|
+
return items.slice(0, limit).map((item) => ({ item, score: 0, positions: [] }));
|
|
29
|
+
}
|
|
30
|
+
const fzf = new Fzf(items, {
|
|
31
|
+
selector: keyFn,
|
|
32
|
+
limit,
|
|
33
|
+
// Per spec: default is case-insensitive (NOT fzf's smart-case).
|
|
34
|
+
// The user explicitly opts into case-sensitive via caseSensitive:true.
|
|
35
|
+
casing: options.caseSensitive === true ? 'case-sensitive' : 'case-insensitive',
|
|
36
|
+
// normalize:true (default) strips diacritics; fzf returns more matches
|
|
37
|
+
// for non-ASCII text this way, which is what we want for
|
|
38
|
+
// bilingual (zh-CN + en) memory entries.
|
|
39
|
+
});
|
|
40
|
+
const raw = fzf.find(query);
|
|
41
|
+
if (raw.length === 0)
|
|
42
|
+
return [];
|
|
43
|
+
// fzf-for-js score is "higher = better". Normalize so the top of the
|
|
44
|
+
// current batch is exactly 1.0 and others are in [0, 1].
|
|
45
|
+
// When the top score is 0 (degenerate — exact-character-only query that
|
|
46
|
+
// still matched somehow), fall back to 1.0 to avoid divide-by-zero.
|
|
47
|
+
const topScore = raw[0]?.score ?? 1;
|
|
48
|
+
const denom = topScore > 0 ? topScore : 1;
|
|
49
|
+
return raw.slice(0, limit).map((entry) => {
|
|
50
|
+
const score = topScore > 0 ? Number((entry.score / denom).toFixed(4)) : 1;
|
|
51
|
+
// positions is a Set<number> in fzf-for-js; convert to a sorted array
|
|
52
|
+
// so the JSON envelope is stable and human-readable.
|
|
53
|
+
const positions = [...entry.positions].sort((a, b) => a - b);
|
|
54
|
+
return { item: entry.item, score, positions };
|
|
55
|
+
});
|
|
56
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for the generic fuzzy-match kernel.
|
|
3
|
+
*/
|
|
4
|
+
export interface FuzzyMatchOptions {
|
|
5
|
+
/** Maximum number of matches to return. Default 6. */
|
|
6
|
+
limit?: number;
|
|
7
|
+
/** When true, matching is case-sensitive. Default false (smart-case). */
|
|
8
|
+
caseSensitive?: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* A single fuzzy-match hit. `item` is the original entry; `score` is
|
|
12
|
+
* normalized to [0, 1] with the top of the current batch at 1.0;
|
|
13
|
+
* `positions` is the set of char indices in the searchable text that
|
|
14
|
+
* contributed to the match.
|
|
15
|
+
*/
|
|
16
|
+
export interface FuzzyMatchResult<T> {
|
|
17
|
+
item: T;
|
|
18
|
+
score: number;
|
|
19
|
+
positions: number[];
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export type { ProjectMemoryKind } from './project-memory-service.js';
|
|
2
|
+
import type { ProjectMemoryKind } from './project-memory-service.js';
|
|
3
|
+
/**
|
|
4
|
+
* One entry in `.peaks/memory/index.json` after the on-disk `hot[]` +
|
|
5
|
+
* `cold[]` arrays are flattened. Mirrors the field shape that the
|
|
6
|
+
* existing `project-memory-service.ts` writer emits.
|
|
7
|
+
*/
|
|
8
|
+
export interface MemoryIndexEntry {
|
|
9
|
+
name: string;
|
|
10
|
+
kind: ProjectMemoryKind;
|
|
11
|
+
description: string;
|
|
12
|
+
sourcePath: string;
|
|
13
|
+
sourceArtifact: string | null;
|
|
14
|
+
updatedAt: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The full snapshot of `.peaks/memory/index.json`.
|
|
18
|
+
*/
|
|
19
|
+
export interface MemoryIndexSnapshot {
|
|
20
|
+
indexPath: string;
|
|
21
|
+
version: number;
|
|
22
|
+
updatedAt: string;
|
|
23
|
+
entries: MemoryIndexEntry[];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Input to `searchMemory`. `projectRoot` defaults to the resolved
|
|
27
|
+
* peaks project root (CLI resolves this before calling). `query` is
|
|
28
|
+
* required and non-empty.
|
|
29
|
+
*/
|
|
30
|
+
export interface MemorySearchInput {
|
|
31
|
+
query: string;
|
|
32
|
+
projectRoot?: string;
|
|
33
|
+
limit?: number;
|
|
34
|
+
kind?: ProjectMemoryKind;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* One hit returned by `searchMemory`. Mirrors the entry shape with a
|
|
38
|
+
* normalized score in [0, 1] (top of batch = 1.0) and the char indices
|
|
39
|
+
* in the searchable text that contributed to the match.
|
|
40
|
+
*/
|
|
41
|
+
export interface MemorySearchResult {
|
|
42
|
+
name: string;
|
|
43
|
+
kind: ProjectMemoryKind;
|
|
44
|
+
description: string;
|
|
45
|
+
sourcePath: string;
|
|
46
|
+
score: number;
|
|
47
|
+
positions: number[];
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Read `.peaks/memory/index.json` and flatten the on-disk `hot[<kind>][]`
|
|
51
|
+
* + `cold[]` shape into a single `entries[]` array. Throws structured
|
|
52
|
+
* errors with stable `code` markers that the CLI converts to the
|
|
53
|
+
* peaks envelope.
|
|
54
|
+
*/
|
|
55
|
+
export declare function loadMemoryIndex(projectRoot: string): MemoryIndexSnapshot;
|
|
56
|
+
/**
|
|
57
|
+
* Run the generic fuzzy kernel against the on-disk memory index. The
|
|
58
|
+
* searchable text is `name + " " + description` for each entry (per
|
|
59
|
+
* spec §Component Details).
|
|
60
|
+
*/
|
|
61
|
+
export declare function searchMemory(input: MemorySearchInput): MemorySearchResult[];
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { fuzzyMatchWithKey } from '../fuzzy-matching/fuzzy-match-service.js';
|
|
4
|
+
const DEFAULT_LIMIT = 6;
|
|
5
|
+
/**
|
|
6
|
+
* Read `.peaks/memory/index.json` and flatten the on-disk `hot[<kind>][]`
|
|
7
|
+
* + `cold[]` shape into a single `entries[]` array. Throws structured
|
|
8
|
+
* errors with stable `code` markers that the CLI converts to the
|
|
9
|
+
* peaks envelope.
|
|
10
|
+
*/
|
|
11
|
+
export function loadMemoryIndex(projectRoot) {
|
|
12
|
+
const indexPath = join(projectRoot, '.peaks', 'memory', 'index.json');
|
|
13
|
+
if (!existsSync(indexPath)) {
|
|
14
|
+
const err = new Error(`INDEX_MISSING: memory index not found at ${indexPath}`);
|
|
15
|
+
err.code = 'INDEX_MISSING';
|
|
16
|
+
throw err;
|
|
17
|
+
}
|
|
18
|
+
let raw;
|
|
19
|
+
try {
|
|
20
|
+
raw = readFileSync(indexPath, 'utf8');
|
|
21
|
+
}
|
|
22
|
+
catch (cause) {
|
|
23
|
+
const err = new Error(`INDEX_INVALID: failed to read memory index at ${indexPath}: ${cause.message}`);
|
|
24
|
+
err.code = 'INDEX_INVALID';
|
|
25
|
+
throw err;
|
|
26
|
+
}
|
|
27
|
+
let parsed;
|
|
28
|
+
try {
|
|
29
|
+
parsed = JSON.parse(raw);
|
|
30
|
+
}
|
|
31
|
+
catch (cause) {
|
|
32
|
+
const err = new Error(`INDEX_INVALID: malformed memory index at ${indexPath}: ${cause.message}`);
|
|
33
|
+
err.code = 'INDEX_INVALID';
|
|
34
|
+
throw err;
|
|
35
|
+
}
|
|
36
|
+
const index = parsed;
|
|
37
|
+
const hot = index.hot ?? {};
|
|
38
|
+
const flatFromHot = Object.values(hot).flat();
|
|
39
|
+
const flatFromCold = (index.cold ?? []);
|
|
40
|
+
const entries = [...flatFromHot, ...flatFromCold];
|
|
41
|
+
return {
|
|
42
|
+
indexPath,
|
|
43
|
+
version: index.version ?? 1,
|
|
44
|
+
updatedAt: index.updatedAt ?? '',
|
|
45
|
+
entries,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Run the generic fuzzy kernel against the on-disk memory index. The
|
|
50
|
+
* searchable text is `name + " " + description` for each entry (per
|
|
51
|
+
* spec §Component Details).
|
|
52
|
+
*/
|
|
53
|
+
export function searchMemory(input) {
|
|
54
|
+
if (input.query === '') {
|
|
55
|
+
const err = new Error('EMPTY_QUERY: searchMemory requires a non-empty query (use `peaks memory index` to list all)');
|
|
56
|
+
err.code = 'EMPTY_QUERY';
|
|
57
|
+
throw err;
|
|
58
|
+
}
|
|
59
|
+
const projectRoot = input.projectRoot ?? process.cwd();
|
|
60
|
+
const limit = input.limit ?? DEFAULT_LIMIT;
|
|
61
|
+
const snapshot = loadMemoryIndex(projectRoot);
|
|
62
|
+
let candidates = snapshot.entries;
|
|
63
|
+
if (input.kind !== undefined) {
|
|
64
|
+
candidates = candidates.filter((e) => e.kind === input.kind);
|
|
65
|
+
}
|
|
66
|
+
// Per spec: searchable text is name + " " + description.
|
|
67
|
+
// The keyFn is invoked once per item per call.
|
|
68
|
+
const matches = fuzzyMatchWithKey(input.query, candidates, { keyFn: (e) => `${e.name} ${e.description}`, limit, caseSensitive: false });
|
|
69
|
+
return matches.map((m) => {
|
|
70
|
+
const entry = m.item;
|
|
71
|
+
return {
|
|
72
|
+
name: entry.name,
|
|
73
|
+
kind: entry.kind,
|
|
74
|
+
description: entry.description,
|
|
75
|
+
sourcePath: entry.sourcePath,
|
|
76
|
+
score: m.score,
|
|
77
|
+
positions: m.positions,
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
}
|
|
@@ -95,7 +95,6 @@ export const seedCapabilityItems = [
|
|
|
95
95
|
capability('agent-browser.browser-agent', 'agent-browser', 'Agent Browser', 'agent', 'browser-agent', ['engineer', 'qa'], 'medium', 'manual-browser-walkthrough', 'Use screenshots and manual test steps if agent browser is unavailable.', 'Agent Browser', '浏览器代理', 'Supports browser-based validation and interaction planning.', '支持基于浏览器的验证和交互规划。'),
|
|
96
96
|
capability('minimax-skills.worker-guidance', 'minimax-skills', 'MiniMax Worker Guidance', 'skill', 'worker-guidance', ['engineer'], 'medium', 'peaks-worker-contract', 'Use Peaks built-in minimax-worker contract and review handoff.', 'MiniMax Worker Guidance', 'MiniMax Worker 指南', 'Guides MiniMax coding/test worker delegation.', '指导 MiniMax 编码/测试 worker 委托。'),
|
|
97
97
|
capability('claude-mem.memory-persistence', 'claude-mem', 'Claude Memory Persistence', 'skill', 'memory', ['engineer'], 'medium', 'peaks-txt-context-capsule', 'Use peaks-txt context capsules without storing secrets.', 'Memory Persistence', '记忆持久化', 'Persists reusable context when explicitly approved.', '在明确授权时持久化可复用上下文。'),
|
|
98
|
-
capability('shadcn-ui.component-system', 'shadcn-ui', 'shadcn/ui Component System', 'doc', 'ui-components', ['engineer', 'designer'], 'low', 'project-local-ui-patterns', 'Use existing project components and design tokens.', 'Component System Reference', '组件系统参考', 'Provides component and design-system references for UI planning.', '为 UI 规划提供组件和设计系统参考。'),
|
|
99
98
|
capability('openspec.spec-workflow', 'openspec', 'OpenSpec Workflow', 'workflow', 'spec-workflow', ['product', 'engineer'], 'low', 'peaks-prd-rd-qa-artifacts', 'Use Peaks built-in PRD/RD/QA artifact flow.', 'OpenSpec Workflow', 'OpenSpec 规格流程', 'Supports spec-first product and engineering governance.', '支持规格优先的产品与工程治理。'),
|
|
100
99
|
capability('gitnexus.repo-intelligence', 'gitnexus', 'GitNexus Repository Intelligence', 'cli', 'repo-intelligence', ['engineer'], 'medium', 'local-repo-scan', 'Use local project scanning through Peaks RD.', 'Repository Intelligence', '仓库智能分析', 'Repository intelligence should be proxied through Peaks before use.', '仓库智能分析应先通过 Peaks 代理边界再使用。'),
|
|
101
100
|
capability('claude-code-best-practice.workflow-guidance', 'claude-code-best-practice', 'Claude Code Best Practice', 'doc', 'workflow-guidance', ['engineer'], 'low', 'peaks-built-in-rules', 'Use Peaks built-in workflow and review rules.', 'Claude Code Best Practice', 'Claude Code 最佳实践', 'Guidance for Claude Code engineering workflows.', 'Claude Code 工程工作流指导。'),
|
|
@@ -33,7 +33,6 @@ export const seedCapabilityLandingMappings = [
|
|
|
33
33
|
mapping({ capabilityId: 'agent-browser.browser-agent', sourceId: 'agent-browser', sourceGroup: 'mcp-server', landingKind: 'skill', target: 'peaks-qa', skillName: 'peaks-qa', guidance: 'Use for browser validation; never submit forms or mutate authenticated state without explicit permission.' }),
|
|
34
34
|
mapping({ capabilityId: 'minimax-skills.worker-guidance', sourceId: 'minimax-skills', sourceGroup: 'mcp-server', landingKind: 'cli', target: 'peaks minimax-worker', commandPreview: 'peaks minimax-worker --json', guidance: 'Use Peaks worker command only after reviewing inputs; add --confirm manually when explicit external-provider approval exists.' }),
|
|
35
35
|
mapping({ capabilityId: 'claude-mem.memory-persistence', sourceId: 'claude-mem', sourceGroup: 'mcp-server', landingKind: 'skill', target: 'peaks-txt', skillName: 'peaks-txt', guidance: 'Use only with explicit durable-memory consent and never store secrets.' }),
|
|
36
|
-
mapping({ capabilityId: 'shadcn-ui.component-system', sourceId: 'shadcn-ui', sourceGroup: 'mcp-server', landingKind: 'skill', target: 'peaks-ui', skillName: 'peaks-ui', guidance: 'Use as a component-system reference, not as an unreviewed generated UI default.' }),
|
|
37
36
|
mapping({ capabilityId: 'darwin-skill.external-skill', sourceId: 'darwin-skill', sourceGroup: 'mcp-server', landingKind: 'catalog', target: 'external skill catalog', guidance: 'Catalog only until inspected for project fit and safety.' }),
|
|
38
37
|
mapping({ capabilityId: 'claude-code-best-practice.workflow-guidance', sourceId: 'claude-code-best-practice', sourceGroup: 'mcp-server', landingKind: 'skill', target: 'peaks-rd', skillName: 'peaks-rd', guidance: 'Use as Claude Code workflow reference while preserving Peaks gates.' }),
|
|
39
38
|
mapping({ capabilityId: 'openspec.spec-workflow', sourceId: 'openspec', sourceGroup: 'mcp-server', landingKind: 'skill', target: 'peaks-prd', skillName: 'peaks-prd', guidance: 'Use for spec-first product and engineering artifact structure.' }),
|
|
@@ -17,7 +17,6 @@ export const seedCapabilitySources = [
|
|
|
17
17
|
{ sourceId: 'agent-browser', sourceType: 'repo', sourceGroup: 'mcp-server', title: 'Agent Browser', url: 'https://github.com/vercel-labs/agent-browser', discoveryStatus: 'indexed', items: ['agent-browser.browser-agent'] },
|
|
18
18
|
{ sourceId: 'minimax-skills', sourceType: 'skills-package', sourceGroup: 'mcp-server', title: 'MiniMax Skills', url: 'https://github.com/MiniMax-AI/skills', discoveryStatus: 'indexed', items: ['minimax-skills.worker-guidance'] },
|
|
19
19
|
{ sourceId: 'claude-mem', sourceType: 'repo', sourceGroup: 'mcp-server', title: 'claude-mem', url: 'https://github.com/thedotmack/claude-mem', discoveryStatus: 'indexed', items: ['claude-mem.memory-persistence'] },
|
|
20
|
-
{ sourceId: 'shadcn-ui', sourceType: 'repo', sourceGroup: 'mcp-server', title: 'shadcn/ui', url: 'https://github.com/shadcn-ui/ui', discoveryStatus: 'indexed', items: ['shadcn-ui.component-system'] },
|
|
21
20
|
{ sourceId: 'darwin-skill', sourceType: 'skills-package', sourceGroup: 'mcp-server', title: 'darwin-skill', url: 'https://github.com/alchaincyf/darwin-skill', discoveryStatus: 'unscanned', items: ['darwin-skill.external-skill'] },
|
|
22
21
|
{ sourceId: 'claude-code-best-practice', sourceType: 'repo', sourceGroup: 'mcp-server', title: 'Claude Code Best Practice', url: 'https://github.com/shanraisshan/claude-code-best-practice', discoveryStatus: 'indexed', items: ['claude-code-best-practice.workflow-guidance'] },
|
|
23
22
|
{ sourceId: 'openspec', sourceType: 'repo', sourceGroup: 'mcp-server', title: 'OpenSpec', url: 'https://github.com/Fission-AI/OpenSpec', discoveryStatus: 'indexed', items: ['openspec.spec-workflow'] },
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export type { RetrospectiveType, RetrospectiveOutcome, RetrospectiveEntry } from './retrospective-index.js';
|
|
2
|
+
import type { RetrospectiveType, RetrospectiveOutcome } from './retrospective-index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Input to `searchRetrospective`. `projectRoot` defaults to `process.cwd()`.
|
|
5
|
+
* `query` is required and non-empty. `type` and `outcome` are optional
|
|
6
|
+
* structured filters that compose with AND.
|
|
7
|
+
*/
|
|
8
|
+
export interface RetrospectiveSearchInput {
|
|
9
|
+
query: string;
|
|
10
|
+
projectRoot?: string;
|
|
11
|
+
limit?: number;
|
|
12
|
+
type?: RetrospectiveType;
|
|
13
|
+
outcome?: RetrospectiveOutcome;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* One hit returned by `searchRetrospective`. The `artifactPaths` are
|
|
17
|
+
* preserved so the LLM can follow up with `peaks retrospective show
|
|
18
|
+
* <id>` (or read the artifact directly).
|
|
19
|
+
*/
|
|
20
|
+
export interface RetrospectiveSearchResult {
|
|
21
|
+
id: string;
|
|
22
|
+
sessionId: string;
|
|
23
|
+
type: RetrospectiveType;
|
|
24
|
+
title: string;
|
|
25
|
+
summary: string;
|
|
26
|
+
outcome: RetrospectiveOutcome;
|
|
27
|
+
artifactPaths: string[];
|
|
28
|
+
score: number;
|
|
29
|
+
positions: number[];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Run the generic fuzzy kernel against the on-disk retrospective index.
|
|
33
|
+
* Searchable text is `title + " " + summary` per spec §Component Details.
|
|
34
|
+
* `--type` and `--outcome` filters compose with AND before the kernel
|
|
35
|
+
* runs (cheaper to filter, then fuzzy on a smaller set).
|
|
36
|
+
*/
|
|
37
|
+
export declare function searchRetrospective(input: RetrospectiveSearchInput): RetrospectiveSearchResult[];
|