@gi4nks/ph 1.7.0 → 1.8.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.
@@ -7,7 +7,7 @@
7
7
  - **Source**: reverse-engineered from code (files listed in §8)
8
8
 
9
9
  ## 1. Purpose
10
- Find prompts by full-text (FTS5 over prompt+response), by metadata filters
10
+ Find prompts by full-text (FTS5 over prompt+response+derived analysis metadata), by metadata filters
11
11
  (tool/project/language/role/tag/starred/quality/relevance/date range), or by
12
12
  semantic similarity (sqlite-vec, SPEC-008). Backs `ph search`, the TUI text
13
13
  filter, the MCP `search_prompts*` tools and the HTTP `/api/prompts/search`.
@@ -30,8 +30,9 @@ filter, the MCP `search_prompts*` tools and the HTTP `/api/prompts/search`.
30
30
  1. Build identical filters for two paths: FTS (JOIN with alias `p.`) and scan
31
31
  (direct `prompts`). Filters use `json_extract` on metadata for
32
32
  project/language/role/tag/starred/quality/relevance (db/index.ts:252-297).
33
- 2. With a text query → FTS path: `prompts_fts JOIN prompts p` via MATCH, filters
34
- prefixed `p.` (db/index.ts:300-320).
33
+ 2. With a text query → FTS path: `prompts_fts` plus the derived-analysis FTS index are matched and deduplicated by interaction, filters
34
+ prefixed `p.` and ordered by FTS5 BM25 relevance, then recency for ties
35
+ (db/index.ts:300-320).
35
36
  3. Without text → scan path: `SELECT * FROM prompts WHERE … ORDER BY timestamp
36
37
  DESC LIMIT ?` (db/index.ts:322-330).
37
38
  4. Date range: `since`/`until` become timestamp comparisons.
@@ -43,21 +44,37 @@ that project before the final result limit. Requires embeddings (SPEC-008).
43
44
  Because sqlite-vec applies `k` before ordinary metadata predicates, scoped
44
45
  queries currently examine all stored vectors before filtering and limiting.
45
46
 
47
+ The CLI cosine-similarity path uses the legacy embedding table. When scoped, it
48
+ loads only vectors joined to prompts whose valid metadata names the selected
49
+ project. It does not load all vectors and issue one `getById` query per vector.
50
+
46
51
  ### 4.3 CLI (src/commands/search.ts)
47
- `ph search` prints via `printResults` (src/display/print.ts:33); `-i` opens the
48
- TUI (SPEC-001); `--archive` queries `prompts_archive` (SPEC-009).
52
+ `ph search` defaults to five results and scopes to the detected current project.
53
+ `--all` searches every project; `--project` selects another project. If no
54
+ project is detected, it labels and searches all saved work. `-i` is a text-only
55
+ compatibility flag (SPEC-001); `--archive` queries `prompts_archive` (SPEC-009).
49
56
 
50
57
  ## 5. Invariants & Business rules
51
58
  - FTS path is used ONLY when a text query exists; otherwise full scan + filters.
52
59
  - `tag` filter is a JSON-array LIKE `%"tag"%` — substring match on the serialized
53
60
  array, not a JSON array membership check (db/index.ts:276-282).
54
- - `limit` defaults to 50 in the CLI; results ordered timestamp DESC.
61
+ - CLI `limit` defaults to five and must be between 1 and 1000.
62
+ - Raw prompts/responses and derived analysis remain in separate FTS5 indexes.
63
+ Text search ranks each source list by BM25, then combines their ordinal ranks
64
+ with Reciprocal Rank Fusion (`1 / (60 + rank)`). Results order by fused score,
65
+ then timestamp DESC and ID DESC for deterministic ties. Raw-only query order
66
+ retains its BM25 ordering; an interaction found in both sources receives
67
+ evidence from both ranked lists.
68
+ - The user-facing search defaults to the current detected project; `--all`
69
+ explicitly broadens the search. The archive path remains global.
55
70
  - Project-scoped semantic search returns the nearest matching prompts within
56
71
  that project, even when closer prompts from other projects exist.
72
+ - Project-scoped CLI semantic search loads its vectors with one project join;
73
+ malformed metadata is ignored without aborting the search.
57
74
 
58
75
  ## 6. UI / UX surface
59
76
  `ph search` output: `#id tool date [tags] [exit:N]` + truncated prompt
60
- (printResults); `--full` shows the complete text.
77
+ (printResults); `--full` shows complete prompt and response and prints a `ph show <id>` hint. Search text also matches derived summary, insights, and technical decisions; a separate FTS index preserves raw prompt/response BM25 ranking.
61
78
 
62
79
  ## 7. Acceptance criteria (Given/When/Then)
63
80
  - **G1**: Given prompts with text "goroutines" and "react", When `search({query:
@@ -77,6 +94,16 @@ TUI (SPEC-001); `--archive` queries `prompts_archive` (SPEC-009).
77
94
  - **G6**: Given an unrelated project has the nearest vector, When
78
95
  `searchSemantic(query, 1, "go-play")` runs, Then it returns the nearest
79
96
  vector belonging to `go-play` (src/db/index.ts, project scope before limit).
97
+ - **G7**: Given the CLI runs inside a detected project, When `ph search <query>`
98
+ runs, Then it returns at most five matches from that project by default.
99
+ - **G8**: Given `ph search --all <query>`, When the command runs, Then results
100
+ may include every project and the output labels the global scope.
101
+ - **G9**: Given matching prompts with different FTS5 scores, When the text
102
+ search runs, Then matches from raw text and analysis are fused by RRF and
103
+ tied results resolve by timestamp and ID (SPEC-019).
104
+ - **G10**: Given embeddings across projects, When CLI semantic search targets
105
+ one project, Then the database returns only that project's vectors in one
106
+ joined query and the command does not call `getById` for each vector.
80
107
 
81
108
  ## 8. Key implementation map
82
109
  | Concern | File(s) |
@@ -84,6 +111,7 @@ TUI (SPEC-001); `--archive` queries `prompts_archive` (SPEC-009).
84
111
  | Dual-path search | src/db/index.ts:243-330 |
85
112
  | Semantic KNN | src/db/index.ts (`searchSemantic`) |
86
113
  | CLI output | src/commands/search.ts, src/display/print.ts:33-60 |
114
+ | Search scope and defaults | src/commands/search.ts |
87
115
  | SearchOptions | src/types.ts:54-68 |
88
116
 
89
117
  ## 9. Open questions / discrepancies
@@ -91,10 +119,17 @@ TUI (SPEC-001); `--archive` queries `prompts_archive` (SPEC-009).
91
119
  `auth` (substring). JSON array membership would be stricter.
92
120
  - FTS path columns need the `p.` prefix; a new filter added to only one path
93
121
  silently breaks the other (dual maintenance).
94
- - Project-scoped semantic search currently evaluates the full vector set
95
- before metadata filtering because sqlite-vec's KNN limit precedes the join
96
- predicate. A project-aware vector index or auxiliary metadata column may
97
- reduce this cost if vector counts make it material.
122
+ - Project-scoped semantic search on the sqlite-vec KNN path still evaluates the
123
+ full vector set before metadata filtering because sqlite-vec's KNN limit
124
+ precedes the join predicate. The CLI cosine path is project-filtered at load
125
+ time. A project-aware vector index may reduce KNN cost if vector counts make
126
+ it material.
127
+ - Semantic search has two implementations: CLI `--semantic` loads the legacy
128
+ BLOB table and computes cosine similarity in TypeScript; task context, HTTP,
129
+ and MCP use `sqlite-vec`. Embeddings do not record their producing model, and
130
+ `ph embed-all` fills missing rows without rebuilding vectors after a model
131
+ change. Consolidate the search contract and add explicit vector profile and
132
+ rebuild behavior (ISSUE-026).
98
133
 
99
134
  ## 10. Related
100
135
  - SPEC-002 (primitives), SPEC-008 (embeddings), SPEC-009 (archive),
@@ -8,8 +8,9 @@
8
8
 
9
9
  ## 1. Purpose
10
10
  Turns raw prompts into structured knowledge: role/tags/relevance/quality plus
11
- summary/key_insights/technical_decisions via an LLM. Results feed metadata
12
- (SPEC-002), memories and project summaries (SPEC-007).
11
+ summary/key_insights/technical_decisions via an LLM. Results enrich interaction
12
+ metadata (SPEC-002) and derived full-text search. They do not create active
13
+ memories or project summaries; explicit approval promotes durable knowledge.
13
14
 
14
15
  ## 2. Scope
15
16
  - **In scope**: ANALYSIS_PROMPT, parseAnalysisResponse, analyzePrompt, mergeMetadata,
@@ -41,8 +42,8 @@ contract; input truncated to 2000 chars (:49). No markdown fences allowed.
41
42
  metadata unless `force=true` (:136-183).
42
43
 
43
44
  ### 4.4 Batch (src/analyzer/index.ts:205)
44
- `analyzeAll(db, provider, opts)` iterates unanalyzed prompts, updates metadata,
45
- writes memories (SPEC-007). CLI `ph analyze` with `--limit/--force/--prune/
45
+ `analyzeAll(db, provider, opts)` iterates unanalyzed prompts and updates only
46
+ per-interaction metadata, including lifecycle outcome fields. CLI `ph analyze` with `--limit/--force/--prune/
46
47
  --dry-run`.
47
48
 
48
49
  ### 4.5 Background (src/background/analyzer.ts:7)
@@ -60,8 +61,9 @@ deletes low-score ones (SPEC-009 cross-ref).
60
61
  - Input truncated to 2000 chars before the LLM.
61
62
 
62
63
  ## 6. UI / UX surface
63
- `ph analyze` progress output; TUI shows `●` (analyzed) vs `○` (pending) in
64
- ListEntry (SPEC-012).
64
+ `ph analyze` progress output; `ph show` displays full analysis and state;
65
+ `ph status` lists bounded recent background/remote lifecycle outcomes without
66
+ printing prompt or response content.
65
67
 
66
68
  ## 7. Acceptance criteria (Given/When/Then)
67
69
  - **G1**: Given an LLM response with fenced JSON, When `parseAnalysisResponse`
@@ -7,11 +7,10 @@
7
7
  - **Source**: reverse-engineered from code (files listed in §8)
8
8
 
9
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).
10
+ Project-level knowledge explicitly promoted from reviewed proposals or MCP
11
+ decisions: an append-only `memories` table plus one merged, deduplicated
12
+ `project_summaries` row per project. Current automatic analysis enriches prompt
13
+ metadata only and does not write to these active stores.
15
14
 
16
15
  ## 2. Scope
17
16
  - **In scope**: memories CRUD, upsertProjectMemory (append-only),
@@ -19,7 +18,7 @@ knowledge base" core of the evolution plan (docs/superpowers/specs/
19
18
  tracking.
20
19
  - **Out of scope**: LLM analysis itself (SPEC-006), MCP exposure (SPEC-011),
21
20
  context output (SPEC-014).
22
- - **Entry points**: analysis pipeline, `ph memory-migrate`, `ph context`,
21
+ - **Entry points**: `ph close`, MCP `save_decision`, `ph memory-migrate`, `ph context`,
23
22
  `ph timeline`, MCP tools, `save_decision`.
24
23
 
25
24
  ## 3. Data Model
@@ -33,13 +32,13 @@ knowledge base" core of the evolution plan (docs/superpowers/specs/
33
32
 
34
33
  ## 4. Flows
35
34
 
36
- ### 4.1 Append-only memory (src/db/index.ts:535-640)
35
+ ### 4.1 Append-only memory (src/db/memory-repository.ts)
37
36
  `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;
37
+ technical_decisions, git_context_snapshot})` inserts a new row for an explicitly
38
+ approved or saved decision; automatic per-prompt analysis does not call it. `searchMemories(project, limit)` returns latest N;
40
39
  `getAllMemoriesByProject` (774) ascending for timelines.
41
40
 
42
- ### 4.2 Curated summary (src/db/index.ts:643-699)
41
+ ### 4.2 Curated summary (src/db/memory-repository.ts)
43
42
  `upsertProjectSummary(params)` upserts ONE row per project: merges new insights/
44
43
  decisions into existing arrays, deduping by content; updates prompt_count,
45
44
  first/last_analyzed. `getProjectSummary(project)` (690).
@@ -52,7 +51,8 @@ first/last_analyzed. `getProjectSummary(project)` (690).
52
51
  `recordMemoryAccess(id)` bumps access_count + last_accessed (used by MCP reads).
53
52
 
54
53
  ## 5. Invariants & Business rules
55
- - memories = append-only timeline; project_summaries = 1 row per project.
54
+ - memories = append-only timeline of promoted knowledge; project_summaries = 1 row per project.
55
+ - Automatic analysis writes source interaction metadata only; explicit promotion owns active knowledge.
56
56
  - Summary merge dedups by insight/decision CONTENT (not id).
57
57
  - `save_decision` writes to summaries too (MCP, SPEC-011).
58
58
  - Access stats are write-on-read (recordMemoryAccess on MCP/timeline reads).
@@ -63,15 +63,15 @@ PreviewPane memory tab (SPEC-012); `ph context` markdown output; `ph timeline`.
63
63
  ## 7. Acceptance criteria (Given/When/Then)
64
64
  - **G1**: Given two analyses for the same project, When `upsertProjectMemory`
65
65
  runs twice, Then `memories` contains 2 rows (append-only)
66
- (src/db/index.ts:612-628).
66
+ (src/db/memory-repository.ts).
67
67
  - **G2**: Given two summaries with the same insight, When `upsertProjectSummary`
68
68
  runs twice, Then the insight appears once in key_insights and there is 1 row
69
- (src/db/index.ts:643-690).
69
+ (src/db/memory-repository.ts).
70
70
  - **G3**: Given `upsertProjectSummary` with prompt_count 3, When the row is
71
71
  read, Then prompt_count is 3 and first/last_analyzed are set
72
- (src/db/index.ts:643-690).
72
+ (src/db/memory-repository.ts).
73
73
  - **G4**: Given `recordMemoryAccess(id)`, When run, Then access_count
74
- increments and last_accessed updates (src/db/index.ts:701-706).
74
+ increments and last_accessed updates (src/db/memory-repository.ts).
75
75
  - **G5**: Given `ph memory-migrate` on a DB with memories but no summaries,
76
76
  When run, Then project_summaries rows are backfilled
77
77
  (src/commands/memory-migrate.ts).
@@ -79,9 +79,9 @@ PreviewPane memory tab (SPEC-012); `ph context` markdown output; `ph timeline`.
79
79
  ## 8. Key implementation map
80
80
  | Concern | File(s) |
81
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 |
82
+ | Memories CRUD + upsert | src/db/memory-repository.ts |
83
+ | Summary upsert/merge + read | src/db/memory-repository.ts |
84
+ | Access tracking | src/db/memory-repository.ts |
85
85
  | Migrate command | src/commands/memory-migrate.ts |
86
86
  | Evolution design | docs/superpowers/specs/2026-06-12-ph-evolution-design.md |
87
87
 
@@ -92,5 +92,5 @@ PreviewPane memory tab (SPEC-012); `ph context` markdown output; `ph timeline`.
92
92
  but their use beyond storage is undocumented.
93
93
 
94
94
  ## 10. Related
95
- - SPEC-006 (analysis feeds memories), SPEC-011 (MCP tools), SPEC-014 (context/
95
+ - SPEC-006 (interaction analysis metadata), SPEC-011 (MCP tools), SPEC-014 (context/
96
96
  timeline). No tests (SPEC-ISSUES-007) — G1/G2/G3 are prime vitest targets.
@@ -82,8 +82,14 @@ unreachable.
82
82
  ## 9. Open questions / discrepancies
83
83
  - 768 is hard-coded in the vec0 DDL; a model with a different dim fails at
84
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.
85
+ - Project-scoped `searchSemantic` requests all vectors as the KNN candidate set
86
+ and applies the project predicate in SQL afterward; large databases may pay
87
+ full-index KNN cost first (`src/db/semantic-index.ts:17-35`).
88
+ - CLI semantic search uses the legacy BLOB vectors and a TypeScript cosine
89
+ ranking path, while context, HTTP, and MCP use `sqlite-vec`. Vector rows do
90
+ not retain their producing model, and `ph embed-all` fills missing vectors
91
+ rather than rebuilding after a model change. The same query can therefore
92
+ return different results or compare incompatible vector spaces (ISSUE-026).
87
93
 
88
94
  ## 10. Related
89
95
  - SPEC-005 (semantic search), SPEC-014 (cluster/cohesion use embeddings),
@@ -75,8 +75,8 @@ TUI header shows `N prompts (M archived)` (Header.tsx archiveCount prop);
75
75
  | Concern | File(s) |
76
76
  |---|---|
77
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 |
78
+ | Archive move (transaction) | src/db/archive-repository.ts |
79
+ | Archive search + purge + stats | src/db/archive-repository.ts |
80
80
  | Config keys | src/config/index.ts:18-21 |
81
81
  | TUI archive count | src/ui/Header.tsx, BrowseApp.tsx:793 |
82
82
 
@@ -1,106 +1,80 @@
1
- # SPEC-010: Remote Sync (HTTP Server + Push/Pull)
1
+ # SPEC-010 — Remote sync and HTTP server
2
2
 
3
3
  - **ID**: SPEC-010
4
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.
5
+ - **Status**: Implemented; lossless protocol defined by SPEC-019
6
+ - **Date**: 2026-10-24
7
+ - **Source**: `src/server/`, `src/commands/remote.ts`, `src/sync/`
8
+
9
+ ## Purpose and scope
10
+
11
+ Share local PH history across trusted machines while each local SQLite database
12
+ remains usable offline. This spec covers server access, authentication, stable
13
+ pagination, deduplication, cursor persistence, and the status command.
14
+
15
+ ## Server contract
16
+
17
+ - The default listener is `127.0.0.1`; a non-loopback host must be explicit.
18
+ - The server refuses to start without a non-empty `remote-api-key`.
19
+ - `/health` is public and returns `{ "status": "ok" }`. All data routes require
20
+ `Authorization: Bearer <remote-api-key>` and compare credentials safely.
21
+ - Request bodies are limited to 32 MiB. Oversized and malformed requests return
22
+ 413 and 400 respectively. Internal errors are returned without exception
23
+ details.
24
+ - `/api/sync/push` accepts up to 500 entries. It reports imported, duplicate,
25
+ and rejected records separately; the client advances only after complete
26
+ acknowledgement of the page.
27
+ - `/api/sync/pull` accepts `afterId` and `limit` (1–500), and returns entries in
28
+ ascending database row-ID order with `hasMore` and `nextCursor`.
29
+
30
+ ## Client contract
31
+
32
+ - Push selects local rows after a cursor scoped to the normalized target and
33
+ persistent source database UUID. New imports receive higher IDs even when
34
+ their timestamps are old, so the next page sees them. Active wrapper captures
35
+ are not sent until completion; exited-owner markers are reclaimed. Byte-limited
36
+ HTTP subpages may split one 500-row outer page, and the outer cursor advances
37
+ only after every subpage is acknowledged.
38
+ - Pull reads remote row-ID pages, inserts or deduplicates every entry, then
39
+ persists the cursor. Replaying a page is safe through `sync_hash`.
40
+ - Switching the normalized server target or local database starts from row
41
+ zero. Legacy timestamp or database-unscoped cursors are ignored and trigger an
42
+ idempotent full scan. A server without cursor response fields produces an
43
+ actionable upgrade error.
44
+ - `PH_REMOTE_URL` overrides `remoteUrl`. Credentials are never included in the
45
+ stored target identity.
46
+ - `ph remote status` reports counts after the current target's push cursor and
47
+ the last successful push/pull times.
48
+
49
+ ## Open reassessment finding
50
+
51
+ - **ISSUE-025**: cursor identity currently includes the normalized URL and local
52
+ database UUID, but not the remote database generation. Replacing the remote
53
+ SQLite file behind the same URL can make a high stored cursor skip the new
54
+ database's lower row IDs. Config holds only one cursor per direction, so
55
+ switching local databases and switching back triggers an idempotent full
56
+ replay. Last-success timestamps are global config fields rather than
57
+ target-scoped values. Add a remote identity handshake and persist state per
58
+ complete source/target identity before treating cursors as durable across
59
+ server restores.
60
+
61
+ ## Acceptance criteria
62
+
63
+ - **G1**: Server startup requires an API key and defaults to loopback; health
64
+ discloses no database path; data endpoints reject missing/incorrect keys.
65
+ - **G2**: Equal timestamps, old-timestamp late imports, and multiple pages do
66
+ not skip rows.
67
+ - **G3**: A failed record prevents that page's cursor from advancing; retrying
68
+ the page does not duplicate acknowledged records.
69
+ - **G4**: Pull pages are ordered and cursor-based; legacy timestamp configuration
70
+ replays from the beginning; an old server fails clearly.
71
+ - **G5**: Malformed and oversized bodies return bounded client errors and do not
72
+ expose internal exceptions.
73
+ - **G6**: `PH_REMOTE_URL` takes precedence and target changes restart sync from
74
+ row zero.
75
+ - **G7**: Changing local database files restarts push and pull at row zero;
76
+ byte-limited subpages all remain within the server request cap; live records
77
+ are not bulk-pushed before response completion.
78
+
79
+ Focused server and protocol tests live under `src/server/__tests__/` and
80
+ `src/sync/`. SPEC-019 defines cross-cutting capture and ranking requirements.
@@ -2,7 +2,7 @@
2
2
 
3
3
  - **ID**: SPEC-011
4
4
  - **Cluster**: MCP
5
- - **Status**: Implemented (baseline)
5
+ - **Status**: Implemented; adapter decomposition in SPEC-017
6
6
  - **Date**: 2026-08-08
7
7
  - **Source**: reverse-engineered from code (files listed in §8)
8
8
 
@@ -74,9 +74,12 @@ MCP config.
74
74
  ## 8. Key implementation map
75
75
  | Concern | File(s) |
76
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 |
77
+ | Server composition + transport | src/mcp/server.ts |
78
+ | Tool definitions | src/mcp/tools/definitions.ts |
79
+ | Prompt and project handlers | src/mcp/tools/prompts.ts, src/mcp/tools/projects.ts |
80
+ | Timeline handler | src/mcp/tools/timeline.ts |
81
+ | Response formatting | src/mcp/tools/format.ts |
82
+ | Memory/summary backends | src/db/memory-repository.ts |
80
83
 
81
84
  ## 9. Open questions / discrepancies
82
85
  - Protocol smoke coverage uses the official SDK client with paired in-memory
@@ -1,98 +1,32 @@
1
- # SPEC-012: TUI Browser
1
+ # SPEC-012: Interactive History Browser (Retired)
2
2
 
3
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.
4
+ - **Cluster**: Retired UI
5
+ - **Status**: Retired on 2026-10-22
6
+ - **Implementation**: Removed from `src/ui/`; Ink/React runtime dependencies removed
7
+ - **Replacement**: `ph search`, `ph last`, and `ph context` print terminal text
8
+
9
+ ## Decision
10
+
11
+ PH no longer ships the interactive history browser. The former browser combined
12
+ history filtering, details, metadata editing, starring, deletion, reruns,
13
+ provider selection, settings, and context launch in a keyboard-driven Ink/React
14
+ application. This breadth made PH feel like a second terminal application and
15
+ kept the user from the smaller jobs the product needs to do well.
16
+
17
+ The default command now prints a short guide. `ph search` is the primary way to
18
+ find past interactions; `ph last` and the legacy `ph browse` command print recent
19
+ interactions. `ph search -i` is accepted for compatibility and prints normal
20
+ search results. No search or memory data is changed by this UI retirement.
21
+
22
+ ## Historical Context
23
+
24
+ The retired implementation supported list/detail panes, filters, settings,
25
+ star/edit/delete, rerun, context-aware chat, keyboard navigation, and an
26
+ alternate-screen terminal mode. Those interactions were removed with the UI
27
+ components and their PTY/filter tests. Equivalent history and knowledge remain
28
+ available through the CLI, MCP, and existing storage APIs where applicable.
29
+
30
+ The old keybindings, source locations, and interaction criteria are deliberately
31
+ not maintained as current requirements. Use `SPEC-001` for the current CLI
32
+ contract.