@gi4nks/ph 1.5.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -2
- package/LICENSE +1 -1
- package/README.md +155 -145
- package/dist/cli.js +2279 -1052
- package/dist/cli.js.map +1 -1
- package/docs/evals/context-retrieval-golden.json +26 -0
- package/docs/evals/context-retrieval.md +27 -0
- package/docs/hooks.md +13 -7
- package/docs/ph-analysis.html +145 -0
- package/docs/ph-manual.md +375 -373
- package/docs/specs/SPEC-001-architecture-cli.md +82 -0
- package/docs/specs/SPEC-002-database-layer.md +105 -0
- package/docs/specs/SPEC-003-capture-modes.md +110 -0
- package/docs/specs/SPEC-004-filter-metadata.md +82 -0
- package/docs/specs/SPEC-005-search.md +102 -0
- package/docs/specs/SPEC-006-analysis-pipeline.md +98 -0
- package/docs/specs/SPEC-007-memory-summaries.md +96 -0
- package/docs/specs/SPEC-008-embeddings.md +90 -0
- package/docs/specs/SPEC-009-retention-archive.md +91 -0
- package/docs/specs/SPEC-010-remote-sync.md +106 -0
- package/docs/specs/SPEC-011-mcp-server.md +94 -0
- package/docs/specs/SPEC-012-tui-browser.md +98 -0
- package/docs/specs/SPEC-013-importers.md +109 -0
- package/docs/specs/SPEC-014-analytics-commands.md +109 -0
- package/docs/specs/SPEC-015-config-env.md +85 -0
- package/docs/specs/SPEC-INDEX.md +35 -0
- package/docs/specs/SPEC-ISSUES.md +122 -0
- package/docs/specs/_TEMPLATE.md +65 -0
- package/docs/superpowers/plans/2026-06-12-ph-evolution-phase1.md +466 -0
- package/docs/superpowers/plans/2026-06-12-ph-evolution-phase2.md +351 -0
- package/docs/superpowers/specs/2026-06-12-ph-evolution-design.md +173 -0
- package/hooks/claude/README.md +1 -1
- package/hooks/codex/README.md +54 -0
- package/hooks/codex/ph-hook.sh +14 -0
- package/hooks/gemini/README.md +1 -1
- package/hooks/opencode/ph-plugin.ts +60 -57
- package/package.json +18 -12
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# SPEC-007: Memory & Project Summaries
|
|
2
|
+
|
|
3
|
+
- **ID**: SPEC-007
|
|
4
|
+
- **Cluster**: Memory
|
|
5
|
+
- **Status**: Implemented (baseline)
|
|
6
|
+
- **Date**: 2026-08-08
|
|
7
|
+
- **Source**: reverse-engineered from code (files listed in §8)
|
|
8
|
+
|
|
9
|
+
## 1. Purpose
|
|
10
|
+
Project-level knowledge derived from analysis: an append-only `memories` table
|
|
11
|
+
(full timeline per project) PLUS a single curated `project_summaries` row per
|
|
12
|
+
project (merged, deduped by insight/decision content). This is the "curated
|
|
13
|
+
knowledge base" core of the evolution plan (docs/superpowers/specs/
|
|
14
|
+
2026-06-12-ph-evolution-design.md).
|
|
15
|
+
|
|
16
|
+
## 2. Scope
|
|
17
|
+
- **In scope**: memories CRUD, upsertProjectMemory (append-only),
|
|
18
|
+
upsertProjectSummary (merge/dedup), getProjectSummary, memory-migrate, access
|
|
19
|
+
tracking.
|
|
20
|
+
- **Out of scope**: LLM analysis itself (SPEC-006), MCP exposure (SPEC-011),
|
|
21
|
+
context output (SPEC-014).
|
|
22
|
+
- **Entry points**: analysis pipeline, `ph memory-migrate`, `ph context`,
|
|
23
|
+
`ph timeline`, MCP tools, `save_decision`.
|
|
24
|
+
|
|
25
|
+
## 3. Data Model
|
|
26
|
+
- `memories` (src/db/index.ts:106-121): id, project, prompt_ids, summary,
|
|
27
|
+
key_insights, technical_decisions, git_context_snapshot, created_at,
|
|
28
|
+
updated_at, access_count, last_accessed.
|
|
29
|
+
- `project_summaries` (PhDB.PROJECT_SUMMARIES_SCHEMA, :123): project (PK),
|
|
30
|
+
summary, key_insights, technical_decisions, prompt_count, first_analyzed,
|
|
31
|
+
last_analyzed, git_context_snapshot.
|
|
32
|
+
- `MemoryEntry` / `ProjectSummary` (src/types.ts:29-52).
|
|
33
|
+
|
|
34
|
+
## 4. Flows
|
|
35
|
+
|
|
36
|
+
### 4.1 Append-only memory (src/db/index.ts:535-640)
|
|
37
|
+
`upsertProjectMemory({project, prompt_id, summary, key_insights,
|
|
38
|
+
technical_decisions, git_context_snapshot})` INSERTS a new row per analysis —
|
|
39
|
+
full timeline preserved. `searchMemories(project, limit)` returns latest N;
|
|
40
|
+
`getAllMemoriesByProject` (774) ascending for timelines.
|
|
41
|
+
|
|
42
|
+
### 4.2 Curated summary (src/db/index.ts:643-699)
|
|
43
|
+
`upsertProjectSummary(params)` upserts ONE row per project: merges new insights/
|
|
44
|
+
decisions into existing arrays, deduping by content; updates prompt_count,
|
|
45
|
+
first/last_analyzed. `getProjectSummary(project)` (690).
|
|
46
|
+
|
|
47
|
+
### 4.3 Migrate (src/commands/memory-migrate.ts)
|
|
48
|
+
`ph memory-migrate` backfills `project_summaries` from existing memories
|
|
49
|
+
(83 projects migrated per AGENTS.md).
|
|
50
|
+
|
|
51
|
+
### 4.4 Access tracking (src/db/index.ts:701)
|
|
52
|
+
`recordMemoryAccess(id)` bumps access_count + last_accessed (used by MCP reads).
|
|
53
|
+
|
|
54
|
+
## 5. Invariants & Business rules
|
|
55
|
+
- memories = append-only timeline; project_summaries = 1 row per project.
|
|
56
|
+
- Summary merge dedups by insight/decision CONTENT (not id).
|
|
57
|
+
- `save_decision` writes to summaries too (MCP, SPEC-011).
|
|
58
|
+
- Access stats are write-on-read (recordMemoryAccess on MCP/timeline reads).
|
|
59
|
+
|
|
60
|
+
## 6. UI / UX surface
|
|
61
|
+
PreviewPane memory tab (SPEC-012); `ph context` markdown output; `ph timeline`.
|
|
62
|
+
|
|
63
|
+
## 7. Acceptance criteria (Given/When/Then)
|
|
64
|
+
- **G1**: Given two analyses for the same project, When `upsertProjectMemory`
|
|
65
|
+
runs twice, Then `memories` contains 2 rows (append-only)
|
|
66
|
+
(src/db/index.ts:612-628).
|
|
67
|
+
- **G2**: Given two summaries with the same insight, When `upsertProjectSummary`
|
|
68
|
+
runs twice, Then the insight appears once in key_insights and there is 1 row
|
|
69
|
+
(src/db/index.ts:643-690).
|
|
70
|
+
- **G3**: Given `upsertProjectSummary` with prompt_count 3, When the row is
|
|
71
|
+
read, Then prompt_count is 3 and first/last_analyzed are set
|
|
72
|
+
(src/db/index.ts:643-690).
|
|
73
|
+
- **G4**: Given `recordMemoryAccess(id)`, When run, Then access_count
|
|
74
|
+
increments and last_accessed updates (src/db/index.ts:701-706).
|
|
75
|
+
- **G5**: Given `ph memory-migrate` on a DB with memories but no summaries,
|
|
76
|
+
When run, Then project_summaries rows are backfilled
|
|
77
|
+
(src/commands/memory-migrate.ts).
|
|
78
|
+
|
|
79
|
+
## 8. Key implementation map
|
|
80
|
+
| Concern | File(s) |
|
|
81
|
+
|---|---|
|
|
82
|
+
| Memories CRUD + upsert | src/db/index.ts:535-640 |
|
|
83
|
+
| Summary upsert/merge + read | src/db/index.ts:643-699 |
|
|
84
|
+
| Access tracking | src/db/index.ts:701-706 |
|
|
85
|
+
| Migrate command | src/commands/memory-migrate.ts |
|
|
86
|
+
| Evolution design | docs/superpowers/specs/2026-06-12-ph-evolution-design.md |
|
|
87
|
+
|
|
88
|
+
## 9. Open questions / discrepancies
|
|
89
|
+
- `memories.prompt_ids` may be load-bearing only for the timeline join; summaries
|
|
90
|
+
use prompt_count (SPEC-ISSUES-014).
|
|
91
|
+
- Git context snapshots are captured by the analysis pipeline (git-context.ts)
|
|
92
|
+
but their use beyond storage is undocumented.
|
|
93
|
+
|
|
94
|
+
## 10. Related
|
|
95
|
+
- SPEC-006 (analysis feeds memories), SPEC-011 (MCP tools), SPEC-014 (context/
|
|
96
|
+
timeline). No tests (SPEC-ISSUES-007) — G1/G2/G3 are prime vitest targets.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# SPEC-008: Embeddings (sqlite-vec)
|
|
2
|
+
|
|
3
|
+
- **ID**: SPEC-008
|
|
4
|
+
- **Cluster**: AI
|
|
5
|
+
- **Status**: Implemented (baseline)
|
|
6
|
+
- **Date**: 2026-08-08
|
|
7
|
+
- **Source**: reverse-engineered from code (files listed in §8)
|
|
8
|
+
|
|
9
|
+
## 1. Purpose
|
|
10
|
+
Semantic search capability: prompt text → vectors (Ollama embed API) → stored
|
|
11
|
+
in a sqlite-vec `vec0` virtual table with 768-dim; `searchSemantic` runs KNN
|
|
12
|
+
against a query vector. Backs `ph search --semantic`, MCP
|
|
13
|
+
`search_prompts_semantic`, HTTP `/api/prompts/semantic`, and cluster/cohesion.
|
|
14
|
+
|
|
15
|
+
## 2. Scope
|
|
16
|
+
- **In scope**: getEmbeddings (batching), saveEmbedding/getAllEmbeddings,
|
|
17
|
+
searchSemantic, embed-all command, vec0 migration from legacy BLOB.
|
|
18
|
+
- **Out of scope**: the search UX (SPEC-005), clustering (SPEC-014).
|
|
19
|
+
- **Entry points**: `ph embed-all`, `ph search --semantic`, MCP, HTTP, background analysis.
|
|
20
|
+
|
|
21
|
+
## 3. Data Model
|
|
22
|
+
- `vec_embeddings` vec0 table: `embedding float[768]` (src/db/index.ts:100-104).
|
|
23
|
+
- Legacy `embeddings (prompt_id, vector BLOB)` (87-91) — migrated on open.
|
|
24
|
+
- Embedding dimension fixed at 768 (matches `nomic-embed-text-v2-moe` default,
|
|
25
|
+
src/config/index.ts:14).
|
|
26
|
+
|
|
27
|
+
## 4. Flows
|
|
28
|
+
|
|
29
|
+
### 4.1 Generation (src/embedding/index.ts:5-34)
|
|
30
|
+
`getEmbeddings(texts, ollamaUrl, model, batchSize=20)` POSTs batches to
|
|
31
|
+
`{ollamaUrl}/api/embed`; throws on non-OK; returns `Float32Array[]`.
|
|
32
|
+
|
|
33
|
+
### 4.2 Persistence
|
|
34
|
+
`saveEmbedding(id, vector)` (src/db/index.ts:331) writes to vec_embeddings
|
|
35
|
+
(rowid = prompt id); `getAllEmbeddings()` (361) for cluster/cohesion;
|
|
36
|
+
`getPromptsWithoutEmbeddings()` (377) for embed-all.
|
|
37
|
+
|
|
38
|
+
### 4.3 Search (src/db/index.ts:345-360)
|
|
39
|
+
`searchSemantic(queryVector, limit)` — vec0 KNN: `SELECT … FROM vec_embeddings
|
|
40
|
+
WHERE embedding MATCH ? AND k = ?` joined to prompts.
|
|
41
|
+
|
|
42
|
+
### 4.4 Migration (src/db/index.ts:148-180)
|
|
43
|
+
If legacy `embeddings` has rows and `vec_embeddings` is empty: copy via
|
|
44
|
+
`vec_f32(?)`; also drops a non-vec0 table named vec_embeddings (95-98).
|
|
45
|
+
|
|
46
|
+
### 4.5 CLI (src/commands/embed-all.ts)
|
|
47
|
+
`ph embed-all` batches unembedded prompts, generates vectors, saves.
|
|
48
|
+
|
|
49
|
+
## 5. Invariants & Business rules
|
|
50
|
+
- Dimension is always 768; a mismatched model breaks KNN at runtime.
|
|
51
|
+
- vec_embeddings rowid == prompts.id (identity mapping).
|
|
52
|
+
- Migration runs on every open but is a no-op once done.
|
|
53
|
+
- Embedding failures throw — callers (embed-all, semantic search) surface the
|
|
54
|
+
error instead of silently degrading.
|
|
55
|
+
|
|
56
|
+
## 6. UI / UX surface
|
|
57
|
+
`ph search --semantic` requires embeddings; CLI warns/errors when the model is
|
|
58
|
+
unreachable.
|
|
59
|
+
|
|
60
|
+
## 7. Acceptance criteria (Given/When/Then)
|
|
61
|
+
- **G1**: Given 50 texts with batchSize 20, When `getEmbeddings` runs, Then the
|
|
62
|
+
Ollama API is called 3 times and 50 vectors are returned
|
|
63
|
+
(src/embedding/index.ts:13-31).
|
|
64
|
+
- **G2**: Given a prompt with a saved embedding, When `searchSemantic` runs with
|
|
65
|
+
the same vector, Then the prompt is the top result (src/db/index.ts:345-360).
|
|
66
|
+
- **G3**: Given legacy `embeddings` rows and an empty `vec_embeddings`, When the
|
|
67
|
+
DB opens, Then the vectors are copied into vec_embeddings
|
|
68
|
+
(src/db/index.ts:148-180).
|
|
69
|
+
- **G4**: Given `saveEmbedding(id, vec)`, When `getAllEmbeddings` runs, Then the
|
|
70
|
+
map contains id → vec (src/db/index.ts:331-375).
|
|
71
|
+
- **G5**: Given a non-OK Ollama response, When `getEmbeddings` runs, Then it
|
|
72
|
+
throws with the HTTP status (src/embedding/index.ts:22-25).
|
|
73
|
+
|
|
74
|
+
## 8. Key implementation map
|
|
75
|
+
| Concern | File(s) |
|
|
76
|
+
|---|---|
|
|
77
|
+
| Embed API client | src/embedding/index.ts:5-34 |
|
|
78
|
+
| vec0 persistence + KNN | src/db/index.ts:331-360 |
|
|
79
|
+
| Migration | src/db/index.ts:95-104, 148-180 |
|
|
80
|
+
| embed-all | src/commands/embed-all.ts |
|
|
81
|
+
|
|
82
|
+
## 9. Open questions / discrepancies
|
|
83
|
+
- 768 is hard-coded in the vec0 DDL; a model with a different dim fails at
|
|
84
|
+
insert time with a vec0 error.
|
|
85
|
+
- `searchSemantic` runs KNN before filtering by project — the server filters in
|
|
86
|
+
JS afterwards (src/server/index.ts:81-89); large DBs pay KNN cost first.
|
|
87
|
+
|
|
88
|
+
## 10. Related
|
|
89
|
+
- SPEC-005 (semantic search), SPEC-014 (cluster/cohesion use embeddings),
|
|
90
|
+
SPEC-010 (HTTP semantic endpoint). No tests (SPEC-ISSUES-007).
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# SPEC-009: Retention & Archive
|
|
2
|
+
|
|
3
|
+
- **ID**: SPEC-009
|
|
4
|
+
- **Cluster**: DB
|
|
5
|
+
- **Status**: Implemented (baseline)
|
|
6
|
+
- **Date**: 2026-08-08
|
|
7
|
+
- **Source**: reverse-engineered from code (files listed in §8)
|
|
8
|
+
|
|
9
|
+
## 1. Purpose
|
|
10
|
+
Keeps the DB "shrinking, not growing" (evolution plan): `ph cleanup --retention`
|
|
11
|
+
archives old/unqualified prompts to `prompts_archive` (removing them from the
|
|
12
|
+
live tables) and auto-purges archived entries past 2× retentionDays.
|
|
13
|
+
|
|
14
|
+
## 2. Scope
|
|
15
|
+
- **In scope**: retention policy (age, starred, analyzed, relevance), archive
|
|
16
|
+
move (transactional copy+delete incl. embeddings cleanup), archive search,
|
|
17
|
+
purge, archive stats.
|
|
18
|
+
- **Out of scope**: simple `ph cleanup` (length/score deletion), reusability
|
|
19
|
+
cleanup (SPEC-006 cross-ref).
|
|
20
|
+
- **Entry points**: `ph cleanup --retention [--dry-run]`, `ph search --archive`,
|
|
21
|
+
TUI archive count.
|
|
22
|
+
|
|
23
|
+
## 3. Data Model
|
|
24
|
+
- `prompts_archive` (PhDB.ARCHIVE_SCHEMA, src/db/index.ts:124): full row copy +
|
|
25
|
+
`archived_at`, `original_id`.
|
|
26
|
+
- Config keys (src/config/index.ts:18-21): retentionDays (90), retentionMinStarred
|
|
27
|
+
(true), retentionMinAnalyzed (true), retentionMinRelevance (3).
|
|
28
|
+
- Archive stats: total/oldest/newest (src/db/index.ts:511).
|
|
29
|
+
|
|
30
|
+
## 4. Flows
|
|
31
|
+
|
|
32
|
+
### 4.1 Selection (src/commands/cleanup.ts:88-133)
|
|
33
|
+
1. cutoff = now − retentionDays.
|
|
34
|
+
2. Priority 1: relevance < threshold AND older than cutoff (cleanup.ts:104-105).
|
|
35
|
+
3. Priority 2: older than cutoff AND not starred AND not analyzed (cleanup.ts:105+).
|
|
36
|
+
4. `--dry-run` lists candidates; otherwise confirm/`--force` then archive.
|
|
37
|
+
|
|
38
|
+
### 4.2 Archive move (src/db/index.ts:464-493)
|
|
39
|
+
`archivePrompts(ids)` runs a TRANSACTION: copy row → prompts_archive (with
|
|
40
|
+
archived_at/original_id), DELETE from prompts (FTS trigger + embeddings cleanup
|
|
41
|
+
deleteByIds at :439 keep the indexes consistent). Returns moved count.
|
|
42
|
+
|
|
43
|
+
### 4.3 Purge (src/db/index.ts:506, cleanup.ts:157-162)
|
|
44
|
+
After archiving, entries in prompts_archive older than 2× retentionDays are
|
|
45
|
+
permanently purged.
|
|
46
|
+
|
|
47
|
+
### 4.4 Archive search (src/db/index.ts:496)
|
|
48
|
+
`searchArchive({since, until, limit})` over prompts_archive; CLI `ph search
|
|
49
|
+
--archive`.
|
|
50
|
+
|
|
51
|
+
## 5. Invariants & Business rules
|
|
52
|
+
- Archive move is transactional — no partial copies.
|
|
53
|
+
- Starred and analyzed prompts are never archived by default (config flags).
|
|
54
|
+
- Embeddings of archived prompts are deleted (deleteByIds handles vec cleanup).
|
|
55
|
+
- Purge only touches prompts_archive, never live prompts.
|
|
56
|
+
|
|
57
|
+
## 6. UI / UX surface
|
|
58
|
+
TUI header shows `N prompts (M archived)` (Header.tsx archiveCount prop);
|
|
59
|
+
`ph cleanup --retention` prints policy + per-entry reasons.
|
|
60
|
+
|
|
61
|
+
## 7. Acceptance criteria (Given/When/Then)
|
|
62
|
+
- **G1**: Given an old, unstarred, unanalyzed prompt, When retention selection
|
|
63
|
+
runs, Then it is a candidate for archiving (src/commands/cleanup.ts:105+).
|
|
64
|
+
- **G2**: Given `archivePrompts([id])` on an existing prompt, When run, Then the
|
|
65
|
+
row moves to prompts_archive with archived_at+original_id and disappears from
|
|
66
|
+
prompts (src/db/index.ts:464-493).
|
|
67
|
+
- **G3**: Given an archived entry older than 2× retentionDays, When
|
|
68
|
+
`purgeArchive` runs, Then it is deleted (src/db/index.ts:506-510).
|
|
69
|
+
- **G4**: Given `searchArchive({since})`, When run, Then only archived rows in
|
|
70
|
+
range are returned (src/db/index.ts:496-505).
|
|
71
|
+
- **G5**: Given `getArchiveStats()`, When run, Then total/oldest/newest reflect
|
|
72
|
+
prompts_archive (src/db/index.ts:511-518).
|
|
73
|
+
|
|
74
|
+
## 8. Key implementation map
|
|
75
|
+
| Concern | File(s) |
|
|
76
|
+
|---|---|
|
|
77
|
+
| Retention policy + selection | src/commands/cleanup.ts:88-133 |
|
|
78
|
+
| Archive move (transaction) | src/db/index.ts:464-493 |
|
|
79
|
+
| Archive search + purge + stats | src/db/index.ts:496-518 |
|
|
80
|
+
| Config keys | src/config/index.ts:18-21 |
|
|
81
|
+
| TUI archive count | src/ui/Header.tsx, BrowseApp.tsx:793 |
|
|
82
|
+
|
|
83
|
+
## 9. Open questions / discrepancies
|
|
84
|
+
- "Analyzed" is defined as `meta.summary || meta.role` (cleanup.ts) — verify the
|
|
85
|
+
exact predicate when wiring tests.
|
|
86
|
+
- `deleteByIds` (439) does the vec cleanup; `archivePrompts` relies on it —
|
|
87
|
+
archive + vec consistency is only as good as deleteByIds.
|
|
88
|
+
|
|
89
|
+
## 10. Related
|
|
90
|
+
- SPEC-002 (primitives), SPEC-006 (reusability cleanup), SPEC-012 (TUI count).
|
|
91
|
+
No tests (SPEC-ISSUES-007) — G2/G3 are prime vitest targets.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# SPEC-010: Remote Sync (HTTP Server + Push/Pull)
|
|
2
|
+
|
|
3
|
+
- **ID**: SPEC-010
|
|
4
|
+
- **Cluster**: Sync
|
|
5
|
+
- **Status**: Implemented (baseline)
|
|
6
|
+
- **Date**: 2026-08-08
|
|
7
|
+
- **Source**: reverse-engineered from code (files listed in §8)
|
|
8
|
+
|
|
9
|
+
## 1. Purpose
|
|
10
|
+
Cross-laptop prompt history sharing: a zero-dependency HTTP server (`ph server`)
|
|
11
|
+
plus `ph remote push|pull|status` with dedup via `sync_hash`
|
|
12
|
+
(sha256(`tool|prompt|response`)) and fire-and-forget background push after
|
|
13
|
+
`ph log`. Offline-first: the local SQLite DB stays the source of truth.
|
|
14
|
+
|
|
15
|
+
## 2. Scope
|
|
16
|
+
- **In scope**: HTTP endpoints, push/pull/status, dedup, background push,
|
|
17
|
+
config/env precedence, CORS.
|
|
18
|
+
- **Out of scope**: authentication enforcement (see §9), conflict resolution
|
|
19
|
+
beyond timestamp+hash, server persistence (uses the same PhDB).
|
|
20
|
+
- **Entry points**: `ph server [--port] [--host]`, `ph remote push|pull|status`,
|
|
21
|
+
`PH_REMOTE_URL` env, `remoteUrl`/`remoteApiKey`/`remoteLastPush`/`remoteLastPull` config.
|
|
22
|
+
|
|
23
|
+
## 3. Data Model
|
|
24
|
+
- `sync_hash`: sha256 hex of `${tool}|${prompt}|${response}`, stored in
|
|
25
|
+
metadata JSON under `$.sync_hash` (src/server/index.ts:104-108).
|
|
26
|
+
- Config: `remoteUrl`, `remoteApiKey`, `remoteLastPush`, `remoteLastPull`
|
|
27
|
+
(src/config/index.ts:15-18).
|
|
28
|
+
- Env: `PH_REMOTE_URL` (precedence over config, src/commands/remote.ts:6-8).
|
|
29
|
+
|
|
30
|
+
## 4. Flows
|
|
31
|
+
|
|
32
|
+
### 4.1 Server (src/server/index.ts:9-164)
|
|
33
|
+
- CORS headers on every response (:15-17); OPTIONS → 204.
|
|
34
|
+
- `/health` → `{ status: 'ok', dbPath }` (:46-48).
|
|
35
|
+
- `POST /api/prompts/search|by-id|semantic` — search delegation (SPEC-005/008).
|
|
36
|
+
- `POST /api/sync/push` (:94-127): per prompt compute hash → skip if
|
|
37
|
+
`getPromptBySyncHash` finds it → else insert with sync_hash in metadata.
|
|
38
|
+
- `POST /api/sync/pull` (:129-139): prompts since timestamp (or all), capped 10000.
|
|
39
|
+
- `POST /api/memories/search|summary` (:141-154); `GET /api/stats` (:156-161).
|
|
40
|
+
|
|
41
|
+
### 4.2 Push (src/commands/remote.ts:37-80)
|
|
42
|
+
`push` sends prompts since `remoteLastPush` (≤5000) to `/api/sync/push`;
|
|
43
|
+
on success updates `cfg.remoteLastPush` and saves.
|
|
44
|
+
|
|
45
|
+
### 4.3 Pull (src/commands/remote.ts:82-146)
|
|
46
|
+
`pull` fetches since `remoteLastPull`; dedups locally via `getPromptBySyncHash`;
|
|
47
|
+
advances `remoteLastPull` to the max timestamp.
|
|
48
|
+
|
|
49
|
+
### 4.4 Background push (src/commands/log.ts:65-69)
|
|
50
|
+
After each `ph log`, if a remote is configured, `pushToRemote(...).catch(() => {})`
|
|
51
|
+
— fire-and-forget, never blocks or crashes.
|
|
52
|
+
|
|
53
|
+
### 4.5 Status (src/commands/remote.ts:148+)
|
|
54
|
+
Shows remote URL, local total, last push/pull, pending count
|
|
55
|
+
(`getPromptCountSince(remoteLastPush)`).
|
|
56
|
+
|
|
57
|
+
## 5. Invariants & Business rules
|
|
58
|
+
- sync_hash is the dedup key on BOTH sides (server skip + client skip).
|
|
59
|
+
- `PH_REMOTE_URL` env wins over config (remote.ts:6-8).
|
|
60
|
+
- Background push swallows errors (`.catch(() => {})`).
|
|
61
|
+
- The server is read/write open — no auth check (SPEC-ISSUES-013).
|
|
62
|
+
|
|
63
|
+
## 6. UI / UX surface
|
|
64
|
+
CLI progress lines (`Pushing N prompts…`, `Imported: X, Skipped: Y`).
|
|
65
|
+
|
|
66
|
+
## 7. Acceptance criteria (Given/When/Then)
|
|
67
|
+
- **G1**: Given the same `tool|prompt|response`, When `sha256` runs, Then the
|
|
68
|
+
same hash is produced both in push and server paths
|
|
69
|
+
(src/server/index.ts:104, remote.ts:116).
|
|
70
|
+
- **G2**: Given an existing sync_hash in metadata, When `/api/sync/push` receives
|
|
71
|
+
the entry, Then it is skipped (imported stays 0, skipped 1)
|
|
72
|
+
(src/server/index.ts:104-106).
|
|
73
|
+
- **G3**: Given `remoteLastPush` set, When `push` runs, Then only prompts newer
|
|
74
|
+
than the timestamp are sent (src/commands/remote.ts:39-43).
|
|
75
|
+
- **G4**: Given `PH_REMOTE_URL` env and a config remoteUrl, When `remoteUrl()`
|
|
76
|
+
runs, Then the env value wins (src/commands/remote.ts:6-8).
|
|
77
|
+
- **G5**: Given `remoteLastPush` absent, When `status` runs, Then pending equals
|
|
78
|
+
the local total (src/commands/remote.ts:151-156).
|
|
79
|
+
- **G6**: Given `POST /api/stats`, When the server handles it, Then
|
|
80
|
+
total/totalMemories/byTool are returned (src/server/index.ts:156-161).
|
|
81
|
+
|
|
82
|
+
## 8. Key implementation map
|
|
83
|
+
| Concern | File(s) |
|
|
84
|
+
|---|---|
|
|
85
|
+
| HTTP server + endpoints | src/server/index.ts:9-164 |
|
|
86
|
+
| Push/pull/status | src/commands/remote.ts:37-160 |
|
|
87
|
+
| Background push | src/commands/log.ts:65-85 |
|
|
88
|
+
| Config/env | src/config/index.ts:15-18, remote.ts:6-8 |
|
|
89
|
+
| Dedup primitive | src/db/index.ts:403-407 |
|
|
90
|
+
|
|
91
|
+
## 9. Open questions / discrepancies
|
|
92
|
+
- ~~sync_hash excludes args/workdir~~ Fixed 2026-08-08: shared `syncHash`
|
|
93
|
+
helper (src/utils/syncHash.ts) — args included only when present, backward
|
|
94
|
+
compatible with the legacy `tool|prompt|response` hash.
|
|
95
|
+
- ~~The server NEVER verifies `remoteApiKey`~~ Fixed 2026-08-08: auth gate in
|
|
96
|
+
`createRequestHandler` (Bearer + timingSafeEqual) on every endpoint except
|
|
97
|
+
/health when `remoteApiKey` is configured.
|
|
98
|
+
- `remoteLastPush` uses the CLIENT clock; clock skew between laptops breaks
|
|
99
|
+
incremental sync.
|
|
100
|
+
- No pagination beyond the 10000/5000 caps; a laptop with >5000 unsynced
|
|
101
|
+
prompts needs repeated pushes.
|
|
102
|
+
|
|
103
|
+
## 10. Related
|
|
104
|
+
- SPEC-002 (dedup/insert primitives), SPEC-005/008 (search endpoints),
|
|
105
|
+
SPEC-015 (config). GWT tests target the deterministic parts (hash, dedup,
|
|
106
|
+
since-window) — no network needed.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# SPEC-011: MCP Server
|
|
2
|
+
|
|
3
|
+
- **ID**: SPEC-011
|
|
4
|
+
- **Cluster**: MCP
|
|
5
|
+
- **Status**: Implemented (baseline)
|
|
6
|
+
- **Date**: 2026-08-08
|
|
7
|
+
- **Source**: reverse-engineered from code (files listed in §8)
|
|
8
|
+
|
|
9
|
+
## 1. Purpose
|
|
10
|
+
Agent-facing interface to ph's knowledge: an MCP stdio server
|
|
11
|
+
(`ph mcp`) exposing prompt history search, project memories/summaries, timelines
|
|
12
|
+
and decision recording. Auto-discovered by MCP clients (Claude Code, OpenCode,
|
|
13
|
+
etc.) — the primary consumer per the evolution plan.
|
|
14
|
+
|
|
15
|
+
## 2. Scope
|
|
16
|
+
- **In scope**: 11 registered tools, list/call handlers, stdio transport.
|
|
17
|
+
- **Out of scope**: the memory/analysis internals (SPEC-006/007), HTTP server
|
|
18
|
+
(SPEC-010).
|
|
19
|
+
- **Entry points**: `ph mcp` (stdio); tools auto-listed via ListToolsRequest.
|
|
20
|
+
|
|
21
|
+
## 3. Data Model
|
|
22
|
+
- MCP `tools` registration (src/mcp/server.ts:30-187):
|
|
23
|
+
- `list_prompts` (:34)
|
|
24
|
+
- `save_decision` (:47) — writes to project_summaries
|
|
25
|
+
- `get_project_diff` (:69)
|
|
26
|
+
- `search_project_memory` (:82) — keyword + semantic with summary fallback
|
|
27
|
+
- `get_project_context` (:95)
|
|
28
|
+
- `get_project_summary` (:107)
|
|
29
|
+
- `search_prompts` (:118) — FTS
|
|
30
|
+
- `get_prompt` (:135) — by id
|
|
31
|
+
- `search_prompts_semantic` (:146)
|
|
32
|
+
- `get_project_timeline` (:159)
|
|
33
|
+
- `check_project_knowledge` (:173) — "found" vs "new work"
|
|
34
|
+
- Transport: `McpServer` stdio (src/mcp/server.ts:20).
|
|
35
|
+
|
|
36
|
+
## 4. Flows
|
|
37
|
+
|
|
38
|
+
### 4.1 Tool invocation
|
|
39
|
+
1. Client lists tools → `ListToolsRequestSchema` handler returns the 11 tools
|
|
40
|
+
(src/mcp/server.ts:30-187).
|
|
41
|
+
2. Client calls a tool → `CallToolRequestSchema` handler (src/mcp/server.ts:189)
|
|
42
|
+
dispatches by name, opens a PhDB per call (or a shared one), returns
|
|
43
|
+
`{ content: [{ type: 'text', text }] }`.
|
|
44
|
+
3. `check_project_knowledge` (173) is the pre-implementation gate: agents call
|
|
45
|
+
it before coding a feature; it searches memories + semantic prompt history and
|
|
46
|
+
recommends "found" (with context) or "new work".
|
|
47
|
+
|
|
48
|
+
## 5. Invariants & Business rules
|
|
49
|
+
- All tools are read-only EXCEPT `save_decision` (writes summaries).
|
|
50
|
+
- `search_project_memory` falls back to `project_summaries` when semantic search
|
|
51
|
+
returns nothing (AGENTS.md, Phase 3).
|
|
52
|
+
- Every tool result is a text content block (JSON strings for structured data).
|
|
53
|
+
- The server is stdio-only (no network transport).
|
|
54
|
+
|
|
55
|
+
## 6. UI / UX surface
|
|
56
|
+
None (protocol). Consumed by agents: `ph mcp` must be registered in the client's
|
|
57
|
+
MCP config.
|
|
58
|
+
|
|
59
|
+
## 7. Acceptance criteria (Given/When/Then)
|
|
60
|
+
- **G1**: Given a client ListTools request, When the server responds, Then the
|
|
61
|
+
tool list contains `search_prompts`, `get_prompt`, `search_project_memory`,
|
|
62
|
+
`get_project_context`, `get_project_summary`, `check_project_knowledge` and
|
|
63
|
+
`save_decision` (src/mcp/server.ts:30-187).
|
|
64
|
+
- **G2**: Given `get_prompt` with an existing id, When called, Then the prompt
|
|
65
|
+
text is returned (src/mcp/server.ts:135-145).
|
|
66
|
+
- **G3**: Given `search_prompts` with a query, When called, Then FTS results are
|
|
67
|
+
returned as text content (src/mcp/server.ts:118-134).
|
|
68
|
+
- **G4**: Given `save_decision` with project/decision, When called, Then the
|
|
69
|
+
decision is written to project_summaries (src/mcp/server.ts:47-68,
|
|
70
|
+
SPEC-007).
|
|
71
|
+
- **G5**: Given a project with no knowledge, When `check_project_knowledge` is
|
|
72
|
+
called, Then the response recommends "new work" (src/mcp/server.ts:173-187).
|
|
73
|
+
|
|
74
|
+
## 8. Key implementation map
|
|
75
|
+
| Concern | File(s) |
|
|
76
|
+
|---|---|
|
|
77
|
+
| Server + transport | src/mcp/server.ts:1-30 |
|
|
78
|
+
| Tool registry + handlers | src/mcp/server.ts:30-260 |
|
|
79
|
+
| Memory/summary backends | src/db/index.ts:535-699 |
|
|
80
|
+
|
|
81
|
+
## 9. Open questions / discrepancies
|
|
82
|
+
- Protocol smoke coverage uses the official SDK client with paired in-memory
|
|
83
|
+
transports and a real temporary `PhDB`; semantic tools still need a separately
|
|
84
|
+
controlled embedding-provider integration test.
|
|
85
|
+
- A new PhDB is opened per call — connection churn on high-frequency agent use
|
|
86
|
+
(verify in the handler; a shared instance + close-on-exit would be cheaper).
|
|
87
|
+
- No tool input validation with zod (raw JSON.parse of arguments) — malformed
|
|
88
|
+
args from a client surface as handler errors.
|
|
89
|
+
- `get_project_diff` reads git state — behavior beyond the name needs
|
|
90
|
+
verification against git-context.ts.
|
|
91
|
+
|
|
92
|
+
## 10. Related
|
|
93
|
+
- SPEC-005/007/008 (backends), SPEC-006 (analysis feeding knowledge),
|
|
94
|
+
evolution plan (MCP as primary interface). No tests (SPEC-ISSUES-007).
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# SPEC-012: TUI Browser
|
|
2
|
+
|
|
3
|
+
- **ID**: SPEC-012
|
|
4
|
+
- **Cluster**: TUI
|
|
5
|
+
- **Status**: Implemented (baseline)
|
|
6
|
+
- **Date**: 2026-08-08
|
|
7
|
+
- **Source**: reverse-engineered from code (files listed in §8)
|
|
8
|
+
|
|
9
|
+
## 1. Purpose
|
|
10
|
+
Interactive exploration of the prompt history: list + detail panes, search,
|
|
11
|
+
filter panel, star/edit/rerun/delete, memory tab, settings. Ink + React.
|
|
12
|
+
|
|
13
|
+
## 2. Scope
|
|
14
|
+
- **In scope**: BrowseApp state machine, ListEntry/PreviewPane/Header/Footer/
|
|
15
|
+
SearchBar components, keybindings, filter panel, filter policy, themes.
|
|
16
|
+
- **Out of scope**: non-TUI output (src/display/print.ts), command handlers.
|
|
17
|
+
- **Entry points**: `ph` (no args), `ph browse`, `ph search -i`.
|
|
18
|
+
|
|
19
|
+
## 3. Data Model
|
|
20
|
+
- `Props` (src/ui/BrowseApp.tsx:561-565): db, initialTextFilter, initialFilters,
|
|
21
|
+
onRerun.
|
|
22
|
+
- State: allEntries, refreshKey, archiveCount, textFilter, activeFilters,
|
|
23
|
+
showFilterPanel, showSettings, pane/tab state (BrowseApp.tsx:568 onward).
|
|
24
|
+
- `ActiveFilters` — tool/project/language/role/tag/starred/minQuality/
|
|
25
|
+
minRelevance (populated from `search -i` flags, cli.ts:223-232).
|
|
26
|
+
|
|
27
|
+
## 4. Flows
|
|
28
|
+
|
|
29
|
+
### 4.1 Browse loop
|
|
30
|
+
1. `db.search({limit:1000})` seeds the list (BrowseApp.tsx:791).
|
|
31
|
+
2. `/` activates SearchBar → text filter → re-search; `f` opens FilterPanel
|
|
32
|
+
(flat list with counts, Enter toggle, letter jump).
|
|
33
|
+
3. `Tab` switches pane in wide mode; `1/2/3` switch prompt/response/memory tabs.
|
|
34
|
+
4. `y` copy, `s` star, `e` edit metadata, `x` delete (DB + refresh),
|
|
35
|
+
`r` rerun (onRerun → cli.ts spawns the tool), `C` chat mode (launch tool with
|
|
36
|
+
project context), `o` settings.
|
|
37
|
+
5. ListEntry renders role-colored bar, analysis indicator ●/○, star, Q/R badges,
|
|
38
|
+
summary line (ListEntry.tsx); Header shows counts + archive count
|
|
39
|
+
(Header.tsx:archiveCount); session separators `╌╌ date ╌╌`.
|
|
40
|
+
|
|
41
|
+
### 4.2 Filter panel
|
|
42
|
+
Flat scrollable list of all filter options with `[N]` counts; Enter toggles a
|
|
43
|
+
filter; letter jump to category. Pure filtering and option/count generation
|
|
44
|
+
live in `src/ui/filtering.ts`; `FilterPanel.tsx` owns keyboard interaction and
|
|
45
|
+
rendering.
|
|
46
|
+
|
|
47
|
+
## 5. Invariants & Business rules
|
|
48
|
+
- All mutations go through PhDB and refresh via `refreshKey`.
|
|
49
|
+
- The TUI is launched inside alt-screen (`\x1b[?1049h` … `l`) by cli.ts
|
|
50
|
+
(SPEC-001) — cleanup in `finally`.
|
|
51
|
+
- Rerun is synchronous spawnSync after the TUI exits (cli.ts:176-181).
|
|
52
|
+
|
|
53
|
+
## 6. UI / UX surface
|
|
54
|
+
Keybindings (AGENTS.md table): ↑↓/PgUp/PgDn, Tab, 1/2/3, y/s/e/r/x, /, o, f, c,
|
|
55
|
+
q/ESC. Themes in src/ui/themes.ts.
|
|
56
|
+
|
|
57
|
+
## 7. Acceptance criteria (Given/When/Then)
|
|
58
|
+
- **G1**: Given a DB with entries, When BrowseApp mounts, Then the list shows the
|
|
59
|
+
entries and Header shows the count (BrowseApp.tsx:791, Header.tsx).
|
|
60
|
+
- **G2**: Given `/` typed and a query, When the search applies, Then the list is
|
|
61
|
+
filtered via db.search (BrowseApp.tsx SearchBar wiring).
|
|
62
|
+
- **G3**: Given `x` on a selected entry, When pressed, Then the entry is deleted
|
|
63
|
+
via db.delete and the list refreshes (BrowseApp.tsx delete handler).
|
|
64
|
+
- **G4**: Given `s` on an entry, When pressed, Then `starred` toggles in
|
|
65
|
+
metadata (BrowseApp.tsx star handler).
|
|
66
|
+
- **G5**: Given an entry with a project, When `C` is pressed, Then chat mode
|
|
67
|
+
launches with project context injected (BrowseApp chat handler, SPEC-014).
|
|
68
|
+
- **G6**: Given entries and active filters, When `applyFilters` runs, Then only
|
|
69
|
+
matching metadata and text entries remain (`src/ui/filtering.ts`).
|
|
70
|
+
- **G7**: Given entries with filter metadata, When filter options are built,
|
|
71
|
+
Then category counts and active state reflect the full entry set
|
|
72
|
+
(`src/ui/filtering.ts`).
|
|
73
|
+
|
|
74
|
+
## 8. Key implementation map
|
|
75
|
+
| Concern | File(s) |
|
|
76
|
+
|---|---|
|
|
77
|
+
| Main app + settings | src/ui/BrowseApp.tsx |
|
|
78
|
+
| Filter interaction | src/ui/FilterPanel.tsx |
|
|
79
|
+
| Filter policy and option counts | src/ui/filtering.ts |
|
|
80
|
+
| Entry list / detail | src/ui/ListEntry.tsx, PreviewPane.tsx |
|
|
81
|
+
| Header / footer / search | src/ui/Header.tsx, Footer.tsx, SearchBar.tsx |
|
|
82
|
+
| Themes | src/ui/themes.ts |
|
|
83
|
+
| Alt-screen bootstrap (×3) | src/cli.ts:159-186, 214-248, 373-396 |
|
|
84
|
+
|
|
85
|
+
## 9. Open questions / discrepancies
|
|
86
|
+
- BrowseApp remains a large module; filter policy now has a tested seam, while
|
|
87
|
+
detail/edit/rerun/settings interactions remain coupled and need direct UI
|
|
88
|
+
coverage.
|
|
89
|
+
- `refreshKey` increments trigger full re-search — no pagination past the 1000
|
|
90
|
+
seed; huge histories truncate silently.
|
|
91
|
+
- React lint issues in BrowseApp/PreviewPane were reported pre-upgrade
|
|
92
|
+
(AGENTS.md "Known Issues") — status after ink 7 upgrade unverified (lint runs
|
|
93
|
+
clean now, but the rule set may have changed).
|
|
94
|
+
|
|
95
|
+
## 10. Related
|
|
96
|
+
- SPEC-001 (bootstrap), SPEC-005 (search backend), SPEC-002 (mutations),
|
|
97
|
+
SPEC-014 (chat/context). Filter policy/count tests are in
|
|
98
|
+
`src/ui/__tests__/filtering.test.ts`; full Ink interaction tests remain open.
|