mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-10-26 10:45:22 +00:00
从官方 Git 仓库导入 chandra447/pi-hermes-memory 在 tag v0.9.10 (df624056a2e8c68bc89ba0a81e1c120856ec9665,2026-10-02)的源码快照。 本次仅导入源码,尚未接入组合包:package.json、加载顺序、依赖与权限策略 均未改动。 - 原样导入 src/、tests/、docs/、scripts/、README、CHANGELOG、PLAN、 AGENTS 与 LICENSE;该快照共 156 个上游文件。 - 剔除嵌套 .git、上游 .github 工作流、.ralph/、.claude/、node_modules、 覆盖率与构建产物。 - 新增中文 UPSTREAM.md,记录来源,以及启用前必须完成的待决事项: 在 pi-compaction 之前注册加载顺序、better-sqlite3 版本与根 allowScripts 对齐、strip-ansi 解析确认。 - 说明选择 tag 而非 main 的原因:导入时上游 main 已领先至 24fcd8ad26fc27badb8d7dbaf8034645de07b893(2026-10-11),选 tag 是为 对应一个已发布、可复现的版本。 尚未接线。启用前还需反转 tests/extension-load.test.ts 中"不得加载 Hermes"的断言,并同步 pi-tool-search 分组、权限策略与文档。 验证:快照 156 个上游文件全部导入,目录内仅多出 UPSTREAM.md 一份; 已确认与上游 main 内容存在差异,证明取的是 tag 而非 main。
217 lines
11 KiB
Markdown
217 lines
11 KiB
Markdown
# Test Plan — v0.2.0: Skills + Smart Curation
|
|
|
|
> This document defines the test strategy for v0.2.0. Each section maps to an epic in the implementation plan.
|
|
|
|
## Current State
|
|
|
|
- **119 existing tests** — all passing after `add()` async migration
|
|
- **Zero new tests** yet for v0.2 features
|
|
- **Type check**: `npm run check` passes with zero errors
|
|
|
|
---
|
|
|
|
## Epic 2: Auto-Consolidation
|
|
|
|
### Unit Tests: `tests/handlers/auto-consolidate.test.ts`
|
|
|
|
| Test | What | Expected |
|
|
|---|---|---|
|
|
| `triggerConsolidation builds correct prompt` | Verify prompt includes current entries + CONSOLIDATION_PROMPT | Prompt contains entry text and consolidation instructions |
|
|
| `triggerConsolidation returns consolidated on success` | Mock `pi.exec()` to return code 0 | `{ consolidated: true }` |
|
|
| `triggerConsolidation returns error on failure` | Mock `pi.exec()` to return code 1 | `{ consolidated: false, error: "..." }` |
|
|
| `triggerConsolidation returns error on exception` | Mock `pi.exec()` to throw | `{ consolidated: false, error: "Consolidation failed..." }` |
|
|
| `/memory-consolidate consolidates both targets` | Mock handler, verify both "memory" and "user" get consolidated | UI notification contains both results |
|
|
| `/memory-consolidate skips empty targets` | Store has empty user entries | Report shows "(empty, nothing to consolidate)" for that target |
|
|
|
|
### Integration Tests: `tests/store/memory-store.test.ts` (extend existing)
|
|
|
|
| Test | What | Expected |
|
|
|---|---|---|
|
|
| `add() triggers consolidation when over limit` | Config `autoConsolidate: true`, mock consolidator returns success, entry exceeds limit | `add()` succeeds after consolidation + reload |
|
|
| `add() retries once after consolidation` | Verify consolidator called once, then `loadFromDisk()` called, then add succeeds | Entry appears in entries |
|
|
| `add() falls through to error if consolidation fails` | Mock consolidator returns `{ consolidated: false }` | Error about exceeding limit |
|
|
| `add() skips consolidation when disabled` | Config `autoConsolidate: false` | Error about exceeding limit (no consolidator call) |
|
|
| `add() skips consolidation when no consolidator set` | `setConsolidator()` not called | Error about exceeding limit |
|
|
| `add() consolidates for user target too` | Same test but target is "user" | Works identically |
|
|
|
|
---
|
|
|
|
## Epic 3: Correction Detection
|
|
|
|
### Unit Tests: `tests/handlers/correction-detector.test.ts`
|
|
|
|
#### Pattern Matching: `isCorrection()`
|
|
|
|
**Strong patterns (always trigger):**
|
|
| Input | Expected |
|
|
|---|---|
|
|
| `"don't do that"` | `true` |
|
|
| `"not like that"` | `true` |
|
|
| `"I said use yarn"` | `true` |
|
|
| `"I told you already"` | `true` |
|
|
| `"we already discussed this"` | `true` |
|
|
| `"please don't commit yet"` | `true` |
|
|
| `"that's not what I asked for"` | `true` |
|
|
|
|
**Weak patterns (need directive clause):**
|
|
| Input | Expected | Why |
|
|
|---|---|---|
|
|
| `"no, use yarn instead"` | `true` | "use" is directive |
|
|
| `"wrong, the file is in src/"` | `true` | "the" is directive |
|
|
| `"actually, don't use that"` | `true` | "don't" is directive |
|
|
| `"stop, fix the test first"` | `true` | "fix" is directive |
|
|
| `"no worries, I'll handle it"` | `false` | Negative pattern suppresses |
|
|
| `"no problem"` | `false` | Negative pattern suppresses |
|
|
| `"no thanks"` | `false` | Negative pattern suppresses |
|
|
| `"no need to change that"` | `false` | Negative pattern suppresses |
|
|
| `"actually, that looks great"` | `false` | Negative pattern suppresses |
|
|
| `"actually, perfect"` | `false` | Negative pattern suppresses |
|
|
| `"stop there"` | `false` | Negative pattern suppresses |
|
|
|
|
**Non-corrections (should NOT trigger):**
|
|
| Input | Expected |
|
|
|---|---|
|
|
| `"yes, do that"` | `false` |
|
|
| `"looks good"` | `false` |
|
|
| `"can you also check the tests?"` | `false` |
|
|
| `""` | `false` |
|
|
| `"thanks"` | `false` |
|
|
|
|
#### Handler Behavior
|
|
|
|
| Test | What | Expected |
|
|
|---|---|---|
|
|
| `triggers pi.exec on correction` | Emit user message "no, don't use npm", then turn_end | `pi.exec()` called with CORRECTION_SAVE_PROMPT |
|
|
| `does not trigger on normal message` | Emit user message "looks good", then turn_end | `pi.exec()` NOT called |
|
|
| `rate limits: 1 per 3 turns` | Emit correction at turn 1, correction at turn 2 | Only first correction triggers save |
|
|
| `rate limit resets after 3 turns` | Emit correction at turn 1, then normal turns 2-4, then correction at turn 5 | Both corrections trigger save |
|
|
| `does not trigger when in progress` | Emit correction, then another correction before first completes | Only first triggers |
|
|
| `disabled via config` | Config `correctionDetection: false` | No handler registered |
|
|
| `includes recent context (last 6 exchanges)` | Verify prompt content | Prompt includes recent messages + current memory |
|
|
|
|
---
|
|
|
|
## Epic 4: Tool-Call-Aware Nudge
|
|
|
|
### Unit Tests: `tests/handlers/background-review.test.ts` (extend existing)
|
|
|
|
| Test | What | Expected |
|
|
|---|---|---|
|
|
| `triggers on turn count threshold` | `turnsSinceReview >= 10`, `toolCallsSinceReview < 15` | Review triggers |
|
|
| `triggers on tool call count threshold` | `turnsSinceReview < 10`, `toolCallsSinceReview >= 15` | Review triggers |
|
|
| `triggers when both thresholds met` | Both thresholds exceeded | Review triggers |
|
|
| `does not trigger when neither threshold met` | Both below threshold | No review |
|
|
| `resets both counters after review` | After review completes | Both counters = 0 |
|
|
| `counts toolCall blocks from branch` | Branch has 3 assistant messages with 2 toolCall blocks each | `toolCallsSinceReview = 6` |
|
|
| `ignores text blocks in content` | Branch has text blocks only | `toolCallsSinceReview = 0` |
|
|
| `falls back gracefully if branch access fails` | `sessionManager.getBranch()` throws | No crash, continues with turn-based only |
|
|
|
|
---
|
|
|
|
## Epic 1: Skill Tool + Procedural Memory
|
|
|
|
### Unit Tests: `tests/store/skill-store.test.ts`
|
|
|
|
#### CRUD Operations
|
|
|
|
| Test | What | Expected |
|
|
|---|---|---|
|
|
| `create() writes SKILL.md with correct frontmatter` | Create skill with name, description, body | File exists with `---\nname: ...\n---\n` format |
|
|
| `create() slugifies name correctly` | Name: `"Debug TypeScript Errors!"` | File: `debug-typescript-errors.md` |
|
|
| `create() returns error for duplicate name` | Create same skill twice | Second returns error about existing skill |
|
|
| `create() returns error for empty name` | `name: ""` | Error: "Skill name is required." |
|
|
| `create() returns error for empty description` | Valid name, empty description | Error: "Skill description is required." |
|
|
| `create() returns error for empty body` | Valid name/desc, empty body | Error: "Skill body is required." |
|
|
| `create() scans content for security` | Body contains injection pattern | Error: "Blocked: content matches threat pattern" |
|
|
| `loadIndex() returns all skills` | Create 3 skills, call loadIndex | Returns 3 SkillIndex entries |
|
|
| `loadIndex() returns empty array when no skills` | Empty skills dir | Returns `[]` |
|
|
| `loadSkill() returns full document` | Load specific .md file | Returns SkillDocument with all fields |
|
|
| `loadSkill() returns null for missing file` | Nonexistent file | Returns `null` |
|
|
| `loadSkill() returns null for missing frontmatter` | File without `---` frontmatter | Returns `null` |
|
|
| `patch() replaces existing section` | Skill has `## Procedure`, patch it | New content replaces old section |
|
|
| `patch() appends new section` | Skill has no `## Pitfalls`, patch it | Section appended |
|
|
| `patch() increments version` | Patch a skill | Version goes from 1 → 2 |
|
|
| `patch() scans content` | New content has injection pattern | Blocked |
|
|
| `edit() replaces description and body` | Edit with new desc + body | Both updated |
|
|
| `edit() replaces only description` | Edit with new desc, empty body | Only description changes |
|
|
| `edit() increments version` | Edit a skill | Version incremented |
|
|
| `delete() removes file` | Delete a skill | File gone, loadIndex returns one fewer |
|
|
| `delete() returns error for missing file` | Delete nonexistent | Error: "not found" |
|
|
|
|
#### Frontmatter Parsing
|
|
|
|
| Test | What | Expected |
|
|
|---|---|---|
|
|
| `parses standard frontmatter` | `---\nname: foo\ndescription: bar\n---\nbody` | `{ name: "foo", description: "bar", body: "body" }` |
|
|
| `handles value with colons` | `description: uses this: that` | Description = `"uses this: that"` |
|
|
| `handles empty body` | Frontmatter only, no body after `---` | body = `""` |
|
|
| `handles no frontmatter` | Plain markdown without `---` | Returns `{ meta: {}, body: raw }` |
|
|
|
|
#### Progressive Disclosure
|
|
|
|
| Test | What | Expected |
|
|
|---|---|---|
|
|
| `formatIndexForSystemPrompt() returns formatted index` | 2 skills | String with skill names + descriptions |
|
|
| `formatIndexForSystemPrompt() returns empty when no skills` | No skills | Returns `""` |
|
|
| `index does not include body content` | Skills have long bodies | Index only shows name + description |
|
|
|
|
#### Atomic Writes
|
|
|
|
| Test | What | Expected |
|
|
|---|---|---|
|
|
| `create() uses atomic write (file exists after create)` | Create skill, read file | File exists with correct content |
|
|
| `file content is correct after create + patch` | Create then patch | File on disk reflects patch |
|
|
|
|
### Unit Tests: `tests/tools/skill-tool.test.ts`
|
|
|
|
| Test | What | Expected |
|
|
|---|---|---|
|
|
| `registers tool with name 'skill'` | Check tool registration | Tool name = "skill" |
|
|
| `create requires name, description, content` | Missing each param | Error for each missing param |
|
|
| `view without file_name lists all skills` | No file_name param | Returns skill index |
|
|
| `view with file_name returns full document` | Valid file_name | Returns SkillDocument |
|
|
| `view with invalid file_name returns error` | Nonexistent file | Error: "not found" |
|
|
| `patch requires file_name, section, content` | Missing params | Error for each |
|
|
| `edit requires file_name` | No file_name | Error |
|
|
| `delete requires file_name` | No file_name | Error |
|
|
| `unknown action returns error` | `action: "foo"` | Error: "Unknown action" |
|
|
|
|
### Unit Tests: `tests/handlers/skill-auto-trigger.test.ts`
|
|
|
|
| Test | What | Expected |
|
|
|---|---|---|
|
|
| `triggers at 8+ tool calls with 2+ types` | Branch has 8 toolCall blocks with 3 distinct tool names | `pi.exec()` called |
|
|
| `does not trigger below 8 tool calls` | Branch has 7 toolCall blocks | Not triggered |
|
|
| `does not trigger with only 1 tool type` | 10 toolCall blocks, all same tool | Not triggered |
|
|
| `only triggers once per session` | Two turn_end events both meeting threshold | Only first triggers |
|
|
| `handles branch access failure gracefully` | `getBranch()` throws | No crash |
|
|
|
|
---
|
|
|
|
## Epic 5: Documentation & Release
|
|
|
|
### Manual Verification
|
|
|
|
| Check | Command | Expected |
|
|
|---|---|---|
|
|
| Type check passes | `npm run check` | Zero errors |
|
|
| All tests pass | `npm test` | 119+ tests, 0 failures |
|
|
| README updated | Manual review | Mentions skill tool, auto-consolidation, correction detection |
|
|
| ROADMAP updated | Manual review | v0.2 marked complete |
|
|
| Version bumped | `cat package.json \| grep version` | `"version": "0.2.0"` |
|
|
| Git tagged | `git tag -l "v0.2*"` | `v0.2.0` exists |
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
| Area | New Tests | Existing Tests Modified |
|
|
|---|---|---|
|
|
| Auto-Consolidation | 6 + 6 | `memory-store.test.ts` (6 tests for async+consolidator) |
|
|
| Correction Detection | ~20 (patterns + handler) | — |
|
|
| Tool-Call Nudge | 8 | `background-review.test.ts` (extend) |
|
|
| Skill Store | ~25 | — |
|
|
| Skill Tool | ~10 | — |
|
|
| Skill Auto-Trigger | 5 | — |
|
|
| **Total** | **~80 new tests** | **~14 modified** |
|