@gi4nks/ph 1.6.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.
Files changed (33) hide show
  1. package/CHANGELOG.md +22 -1
  2. package/LICENSE +1 -1
  3. package/README.md +79 -19
  4. package/dist/cli.js +3593 -3677
  5. package/dist/cli.js.map +1 -1
  6. package/docs/evals/context-retrieval-golden.json +17 -1
  7. package/docs/ph-architecture-review.html +179 -0
  8. package/docs/ph-manual.md +129 -73
  9. package/docs/ph-operational-memory-analysis.html +104 -0
  10. package/docs/plans/2026-10-10-operational-memory.md +192 -0
  11. package/docs/specs/SPEC-001-architecture-cli.md +100 -72
  12. package/docs/specs/SPEC-002-database-layer.md +22 -3
  13. package/docs/specs/SPEC-003-capture-modes.md +22 -9
  14. package/docs/specs/SPEC-005-search.md +46 -11
  15. package/docs/specs/SPEC-006-analysis-pipeline.md +8 -6
  16. package/docs/specs/SPEC-007-memory-summaries.md +19 -19
  17. package/docs/specs/SPEC-008-embeddings.md +8 -2
  18. package/docs/specs/SPEC-009-retention-archive.md +2 -2
  19. package/docs/specs/SPEC-010-remote-sync.md +77 -103
  20. package/docs/specs/SPEC-011-mcp-server.md +7 -4
  21. package/docs/specs/SPEC-012-tui-browser.md +30 -96
  22. package/docs/specs/SPEC-015-config-env.md +4 -0
  23. package/docs/specs/SPEC-016-operational-memory.md +48 -0
  24. package/docs/specs/SPEC-017-architecture-consolidation.md +63 -0
  25. package/docs/specs/SPEC-018-interaction-inspection-and-recall-integrity.md +164 -0
  26. package/docs/specs/SPEC-019-network-capture-and-ranking-integrity.md +36 -0
  27. package/docs/specs/SPEC-INDEX.md +12 -7
  28. package/docs/specs/SPEC-ISSUES.md +104 -18
  29. package/hooks/claude/README.md +1 -1
  30. package/hooks/codex/README.md +1 -1
  31. package/hooks/codex/ph-hook.sh +2 -1
  32. package/hooks/gemini/README.md +1 -1
  33. package/package.json +6 -7
@@ -19,6 +19,7 @@ get/set`, and environment overrides (`PH_DB`, `PH_REMOTE_URL`, `GEMINI_API_KEY`)
19
19
  - `PhConfig` (src/config/index.ts:5-22): geminiApiKey, dbPath, analyzeProvider,
20
20
  ollamaUrl, ollamaModel, ollamaEmbedModel, filterMinLength, filterMinRelevance,
21
21
  backgroundAnalysis, remoteUrl, remoteApiKey, remoteLastPush, remoteLastPull,
22
+ remotePushCursor {target, databaseId, id}, remotePullCursor {target, databaseId, id},
22
23
  retentionDays, retentionMinStarred, retentionMinAnalyzed, retentionMinRelevance.
23
24
  - Env: `PH_DB` (dbPath, cli.ts:157), `PH_REMOTE_URL` (remote.ts:6-8),
24
25
  `GEMINI_API_KEY` (cli.ts:153-155).
@@ -75,6 +76,9 @@ file.
75
76
  - `save()` writes the whole object — concurrent `ph` processes (e.g. a hook
76
77
  logging while the user sets config) can clobber each other's writes (no
77
78
  locking).
79
+ - `remoteLastPush` and `remoteLastPull` are global timestamps rather than
80
+ target-scoped values, so status can display another remote's last success.
81
+ Cursor identity also omits the remote database generation (SPEC-ISSUES-025).
78
82
  - Config comments in AGENTS.md describe defaults that live in code
79
83
  (load-time defaults in cli.ts/commands) — the doc is authoritative for intent,
80
84
  code for truth.
@@ -0,0 +1,48 @@
1
+ # SPEC-016 — Operational memory workflow
2
+
3
+ **Status:** Implemented (2026-10-10)
4
+
5
+ ## Purpose
6
+
7
+ Support a deliberate `prepare → work → close → promote` loop. Task retrieval is shared by CLI context, chat, and MCP knowledge checks. General context without a task remains non-semantic. Captured work only becomes curated knowledge after explicit user approval, and promoted memories keep verifiable prompt sources.
8
+
9
+ ## Behavior
10
+
11
+ ### `ph prepare`
12
+
13
+ - Accepts a task description and a project inferred from CWD or specified with `--project`.
14
+ - Uses shared `getProjectContext` retrieval with the task as a semantic query.
15
+ - If embeddings fail, labels the failure and falls back to recent project context.
16
+ - Renders source IDs and bounds the retrieved content using `--budget`; source references are preserved even if they exceed that target.
17
+ - Does not generate text or write to the database.
18
+
19
+ ### `ph close`
20
+
21
+ - Requires exactly one explicit selection: `--since <duration>` or inclusive `--from <id> --to <id>`; selects only prompts tagged with the chosen project.
22
+ - Prints source prompt/response excerpts before generating proposals. `--dry-run` performs no generation and no database writes.
23
+ - Uses the configured analysis provider to propose zero or more knowledge entries. Every proposal must cite one or more selected prompt IDs.
24
+ - Requires an interactive TTY to approve or edit proposals. Proposals are staged until the user completes the review; quitting or interrupting before completion writes nothing.
25
+ - Checks for an interactive TTY before calling the analysis provider.
26
+ - On completed review, approved proposals append memories with cited `prompt_ids` and update `project_summaries` in one SQLite transaction. A failed write rolls back the whole approved batch.
27
+
28
+ ### MCP
29
+
30
+ - `check_project_knowledge` uses the shared retrieval and formatter; embedding failure is labeled and falls back to recent context.
31
+ - `save_decision` accepts optional `promptIds`, validates existence and project ownership before writing, and stores them on the memory.
32
+
33
+ ## GWT Acceptance
34
+
35
+ 1. **Given** a task in a project with semantic embeddings, **when** `ph prepare` runs, **then** it returns scoped evidence and source IDs without changing prompt, memory, or summary data.
36
+ 2. **Given** unavailable Ollama embeddings, **when** `ph prepare` runs, **then** it labels the fallback and renders recent context for the selected project.
37
+ 3. **Given** selected prompt history, **when** `ph close --dry-run` runs, **then** it displays the selected excerpts without calling a model or writing memory/summary data.
38
+ 4. **Given** generated proposals, **when** a cited ID is not in the selected prompt set, **then** the response is rejected before review or persistence.
39
+ 5. **Given** a user approves proposals and finishes review, **when** review commits, **then** each approved memory stores the proposal's cited prompt IDs and updates the matching project summary.
40
+ 6. **Given** `save_decision` receives a missing or other-project prompt ID, **when** called through MCP, **then** it returns an error and creates no memory.
41
+ 7. **Given** MCP or CLI task retrieval for a project, **when** embeddings fail, **then** shared recent-context fallback is used and the degradation is visible.
42
+ 8. **Given** a batch contains an invalid memory write, **when** promotion fails, **then** none of the batch's memories or summary updates persist.
43
+ 9. **Given** `ph close` runs without an interactive TTY, **when** it is not a dry run, **then** it exits before calling the analysis provider.
44
+
45
+ ## Verification
46
+
47
+ - Deterministic golden retrieval contract spans at least 12 judged queries across five projects; fixed-vector ranking test reports Recall@2/MRR without generation calls.
48
+ - Full local verification on 2026-10-10: 93 Vitest tests, ESLint, TypeScript, and tsup build passed.
@@ -0,0 +1,63 @@
1
+ # SPEC-017 — Internal architecture consolidation
2
+
3
+ - **ID**: SPEC-017
4
+ - **Cluster**: Core, DB, MCP
5
+ - **Status**: Implemented
6
+ - **Date**: 2026-10-22
7
+
8
+ ## Purpose
9
+
10
+ Reduce internal coupling and constrain product-surface growth without changing
11
+ the public behavior of existing PH commands, MCP tools, or the SQLite schema.
12
+
13
+ **Spec impact: none.** This is an internal-only change. Existing CLI syntax,
14
+ MCP tool names and payloads, database schema, persisted data, and retrieval
15
+ results remain compatible.
16
+
17
+ ## Decisions
18
+
19
+ 1. Do not add, remove, rename, or deprecate user-facing commands in this work.
20
+ Clarify the existing jobs of `context`, `prepare`, agent-facing MCP, and
21
+ `chat`; hold new feature proposals until the operational workflow is used on
22
+ three real tasks across different projects.
23
+ 2. Keep `src/mcp/server.ts` as the protocol composition root. Move tool
24
+ declarations, domain handlers, and response formatting into cohesive
25
+ modules. It remains responsible for MCP SDK registration and error/content
26
+ envelope translation.
27
+ 3. Keep `PhDB` as the public facade and keep its constructor, method names, and
28
+ return values stable. Keep prompt CRUD, FTS, schema, and migrations in the
29
+ prompt core. Move memory/summary operations and archive operations behind
30
+ internal repositories sharing the same SQLite connection. Semantic vectors
31
+ remain in the existing `SemanticIndex`. Preserve the single connection and
32
+ transaction boundary.
33
+ 4. Reuse shared project-context and memory-promotion operations from CLI and
34
+ MCP. Adapters must not own business validation or multi-write consistency.
35
+
36
+ ## Acceptance
37
+
38
+ - Existing commands and all registered MCP tools retain their current names,
39
+ schemas, behavior, and text response contracts.
40
+ - Existing databases open without migration or data rewrite.
41
+ - `PhDB` remains source-compatible for current callers.
42
+ - MCP server composition contains no per-tool business logic or response
43
+ formatting.
44
+ - Memory/summary and archive SQL operations are owned by cohesive internal
45
+ persistence modules; prompt CRUD and FTS remain together in the prompt core.
46
+ Transaction behavior stays on the shared connection.
47
+ - New user-facing capabilities are not introduced in this change.
48
+ - Existing test suite, lint, TypeScript check, and build pass.
49
+
50
+ ## Verification
51
+
52
+ - `npm test`: 17 files, 94 tests passed.
53
+ - `npm run lint`: passed.
54
+ - `npx tsc --noEmit`: passed.
55
+ - `npm run build`: passed.
56
+
57
+ ## Non-goals
58
+
59
+ - Removing or renaming existing commands or MCP tools.
60
+ - Reworking retrieval ranking, metadata storage, or the SQLite schema.
61
+ - Splitting files solely to reduce line counts.
62
+ - Adding agent feedback, automatic memory promotion, or project-state
63
+ fingerprinting.
@@ -0,0 +1,164 @@
1
+ # SPEC-018 — Interaction inspection and recall integrity
2
+
3
+ - **ID**: SPEC-018
4
+ - **Cluster**: Core, Capture, Search, Memory, MCP
5
+ - **Status**: Implemented
6
+ - **Date**: 2026-10-24
7
+ - **Source**: Implemented interaction retrieval in `src/commands/search.ts`, `src/commands/show.ts`, and `src/interaction/detail.ts`, with behavior covered in `src/commands/__tests__/`.
8
+
9
+ ## Purpose
10
+
11
+ Make the stored unit of PH useful and verifiable: a person can find one
12
+ interaction, inspect its complete prompt, response, capture metadata, and
13
+ derived analysis, and tell whether capture enrichment or configured remote
14
+ delivery succeeded. Keep the daily workflow small while making retrieval and
15
+ durable project knowledge consistent across adapters.
16
+
17
+ ## Scope
18
+
19
+ - Add one CLI detail operation and make export formats preserve the complete
20
+ interaction record.
21
+ - Make full-text search cover analyzed summary/insight text as well as the raw
22
+ prompt and response.
23
+ - Make task-aware context retrieval share one project-scoped policy across CLI,
24
+ chat, and MCP knowledge checks.
25
+ - Keep per-interaction analysis in prompt metadata. Only explicit `close` and
26
+ MCP `save_decision` promotion write active project memories and summaries.
27
+ - Persist bounded lifecycle outcomes for optional background analysis and
28
+ configured per-capture remote push; expose recent capture state through an
29
+ advanced CLI diagnostic.
30
+ - Keep everyday help focused on setup, search, detail, and optional context.
31
+ Existing maintenance and integration commands stay available through
32
+ `ph help --all`.
33
+
34
+ ## Decisions
35
+
36
+ 1. `ph show <id>` is the canonical single-interaction view. It prints complete
37
+ stored prompt and response, source/capture fields, all supported analysis
38
+ metadata, and an explicit analysis state. Missing response and missing
39
+ analysis are rendered as such, not silently omitted. `--json` emits the
40
+ complete record and decoded metadata. `--archive` resolves an archived row
41
+ using the ID printed by archived search.
42
+ 2. `ph search` remains a compact discovery list and prints an actionable
43
+ `ph show <id>` hint. `--full` means full prompt and response. Text queries
44
+ also match analyzed summary, key insights, and technical decisions. Search
45
+ ranking and current project/global scope rules remain stable.
46
+ 3. `ph export <id>` formats all preserve prompt and response. Markdown and JSON
47
+ also preserve complete capture and analysis metadata; plain text includes
48
+ clear Prompt and Response headings. `--archive` exports an archived row.
49
+ 4. `analyzeAll` and background analysis update only per-interaction metadata;
50
+ they do not write `memories` or `project_summaries`. Existing saved memories
51
+ are retained. Only `promoteProjectKnowledge`, reached through CLI `close`
52
+ and MCP `save_decision`, creates active curated project knowledge.
53
+ 5. `retrieveTaskContext` owns task embedding, project scope, shared sources, and
54
+ explicit recent-context fallback. CLI task `context`, `chat`, and MCP
55
+ `check_project_knowledge` use this policy. `chat` embeds the user's actual
56
+ task prompt. General project-context retrieval without a task remains a
57
+ separate non-semantic operation.
58
+ 6. Capture records use backward-compatible optional metadata fields for
59
+ analysis and remote-push lifecycle states. States are bounded enums and
60
+ failure text is a safe category, never raw provider output or credentials.
61
+ `ph status` shows the latest captures and their available state; it does not
62
+ claim that a configured hook delivered an interaction that is absent.
63
+ 7. SPEC-018 supersedes SPEC-017's “no new user-facing capability” acceptance
64
+ only for `ph show`, complete `ph export`/`--full`, and advanced `ph status`.
65
+ It does not remove or rename existing commands, MCP tools, or configuration.
66
+ The only new table column is rebuildable search text derived from existing
67
+ metadata; the migration must preserve all prompt/response records and be
68
+ idempotent.
69
+
70
+ ## Data contract
71
+
72
+ `PromptMetadata` may contain:
73
+
74
+ - `analysis_status`: `pending`, `complete`, or `failed` when analysis was
75
+ requested; absent on legacy records is inferred as `complete` when analysis
76
+ fields exist and otherwise `not requested`.
77
+ - `analysis_error`: one safe category (`worker_launch_failed`,
78
+ `provider_unavailable`, `analysis_failed`, or `analysis_persistence_failed`).
79
+ - `remote_sync_status`: `pending`, `complete`, or `failed` for configured
80
+ per-capture pushes; absent on old records is `unknown` when a remote is
81
+ configured and `not configured` otherwise.
82
+ - `remote_sync_error`: `push_failed` only; raw response bodies are not stored.
83
+ - `technical_decisions`: per-interaction analyzed decisions, alongside
84
+ `summary`, `key_insights`, `quality`, and `relevance`.
85
+
86
+ The FTS index receives a derived text field built from analysis summary, key
87
+ insights, and technical decisions. The existing prompt, response, and tool
88
+ fields remain indexed/scoped as before. Rebuilding FTS from existing rows must
89
+ be safe and idempotent.
90
+
91
+ ## Acceptance criteria
92
+
93
+ - **G1 — Full detail**: Given a stored interaction with prompt, response,
94
+ metadata, and analysis fields, when `ph show <id>` runs, then all are printed
95
+ without truncation. Given missing response or analysis, then the output names
96
+ the missing state and returns successfully.
97
+ - **G1a — Archived detail**: Given an archived search result ID, when
98
+ `ph show <id> --archive` or `ph export <id> --archive` runs, then the original
99
+ prompt, response, and metadata are shown without changing the archive.
100
+ - **G2 — Complete export**: Given a stored interaction, when each supported
101
+ export format runs, then prompt and response are present; JSON includes the
102
+ full entry and decoded metadata. Unknown IDs and formats fail with a useful
103
+ message.
104
+ - **G3 — Search to detail**: Given a matching result, when `ph search` prints
105
+ it, then the ID and `ph show <id>` instruction are visible. `--full` prints
106
+ complete prompt and response. Search still honors current-project default,
107
+ `--all`, explicit project, filters, and the requested result limit.
108
+ - **G4 — Search derived analysis**: Given only a summary, insight, or technical
109
+ decision contains a query term, when search runs, then the source interaction
110
+ is returned. Existing prompt/response matches and BM25 ordering continue to
111
+ work. Updating metadata updates indexed analysis text; rebuilding old DBs
112
+ indexes existing metadata without losing prompt history.
113
+ - **G5 — One active knowledge writer**: Given manual or background analysis,
114
+ when it succeeds, then the interaction metadata is updated and no active
115
+ memory or project summary is created. Given `close` approval or MCP
116
+ `save_decision`, then the existing provenance validation and transactional
117
+ promotion write the curated memory and summary. Existing memories remain
118
+ readable.
119
+ - **G6 — Shared task retrieval**: Given a task prompt in a project, when CLI
120
+ `context`, `chat`, or MCP `check_project_knowledge` retrieves context, then
121
+ they use the same task embedding prefix, project scope, sources, and labeled
122
+ fallback behavior. `chat` includes the user's task in the semantic query.
123
+ Retrieval remains read-only.
124
+ - **G7 — Lifecycle status**: Given background analysis is requested, when a
125
+ capture is stored, then its state becomes pending and transitions to complete
126
+ or a safe failed category. Spawn failure, missing provider, empty analysis,
127
+ provider error, and persistence error are observable. Given configured remote
128
+ push, then its state transitions from pending to complete/failed. State writes
129
+ do not overwrite unrelated metadata.
130
+ - **G8 — Diagnostic**: Given captured records exist, when `ph status` runs, then
131
+ it shows a bounded recent list with timestamp, tool, project, response
132
+ presence, analysis state, and remote state. It does not print prompt or
133
+ response content. `status` is listed in advanced help, not the short default
134
+ guide.
135
+ - **G9 — Small product surface**: The short guide documents setup, search,
136
+ inspecting a result with `show`, and optional task context. Existing advanced
137
+ commands remain available behind `ph help --all`.
138
+ - **G10 — Compatibility**: Existing DB files open without destructive rewrite;
139
+ existing IDs, prompt/response text, config, commands, and MCP tool names stay
140
+ intact. Analysis-derived FTS data is rebuilt transactionally or through an
141
+ idempotent migration.
142
+
143
+ ## Implementation map
144
+
145
+ | Concern | Files |
146
+ |---|---|
147
+ | Detail command, compact search, export/help | `src/cli.ts`, `src/commands/{show,search,export,status}.ts`, `src/display/print.ts` |
148
+ | Record and search index | `src/types.ts`, `src/db/index.ts`, `src/db/__tests__/phdb.test.ts` |
149
+ | Analysis lifecycle and trust | `src/analyzer/index.ts`, `src/commands/{analyze,background-analyze,log}.ts`, `src/background/analyzer.ts` |
150
+ | Task retrieval | `src/knowledge/task-context.ts`, `src/commands/{context,chat}.ts`, `src/mcp/tools/projects.ts` |
151
+ | MCP detail projection | `src/mcp/tools/prompts.ts`, `src/mcp/tools/format.ts` |
152
+ | Contracts and user docs | `docs/specs/SPEC-001-architecture-cli.md`, `SPEC-003-capture-modes.md`, `SPEC-005-search.md`, `SPEC-016-operational-memory.md`, `docs/ph-manual.md`, `README.md` |
153
+
154
+ ## Verification
155
+
156
+ Focused GWT tests cover detail/export fidelity, FTS metadata indexing and
157
+ migration, memory ownership, retrieval parity/fallback, lifecycle transitions,
158
+ and status output. Completion requires `npm test`, `npm run lint`,
159
+ `npx tsc --noEmit`, and `npm run build`.
160
+
161
+ ## Related
162
+
163
+ SPEC-001, SPEC-002, SPEC-003, SPEC-005, SPEC-006, SPEC-007, SPEC-011,
164
+ SPEC-016, and SPEC-017.
@@ -0,0 +1,36 @@
1
+ # SPEC-019 — Network, capture, and retrieval integrity
2
+
3
+ - **ID**: SPEC-019
4
+ - **Cluster**: Capture, Sync, Search, CLI
5
+ - **Status**: Implemented
6
+ - **Date**: 2026-10-24
7
+ - **Source**: Implemented network, sync, capture, and ranking contracts in `src/server/`, `src/sync/`, `src/capture/lifecycle.ts`, and related tests.
8
+
9
+ ## Purpose
10
+
11
+ Close the remaining correctness and exposure gaps at PH's network and live-capture seams. The everyday local workflow stays simple; optional HTTP and sync paths must fail safely and preserve every acknowledged interaction.
12
+
13
+ ## Scope and decisions
14
+
15
+ 1. **HTTP exposure**: `ph server` listens on loopback by default and requires a configured API key before opening the listener. Every endpoint except `/health` requires that key. Health never returns the database path. Request bodies have a documented byte limit; malformed/oversized requests return bounded client errors without leaking internal exception text. A non-loopback deployment still requires explicit `--host` and the same authentication; cross-machine deployments require a trusted network or TLS termination.
16
+ 2. **Push pagination**: local prompts are selected by monotonically increasing local row ID, not timestamp. The remote push cursor is scoped to both the normalized target URL and the persistent source database ID. Pages are split into HTTP bodies no larger than the server byte limit; the outer cursor advances only after every subpage and record is acknowledged or deduplicated. A failed subpage leaves the outer page retryable. Retrying is safe because `sync_hash` is idempotent.
17
+ 3. **Pull pagination**: the server returns prompts in ascending remote row-ID order, a continuation cursor, and `hasMore`. The client persists an opaque target-and-local-database-scoped ID cursor only after a page is fully inserted or deduplicated. Switching either target or local database starts at row zero. Missing cursor fields are a compatibility error, not permission to silently fall back to a capped timestamp pull. Legacy timestamp cursors trigger a full idempotent scan from row zero.
18
+ 4. **Capture completion**: hooks/direct `ph log`, inline wrapper, and interactive PTY wrapper share lifecycle initialization and configured analysis/remote-push effects. Invalid remote URLs mark the optional push failed without blocking local persistence or tool execution. Wrapper records identify a live capture owner; bulk sync stops before a live record so it cannot publish a partial response or advance past it. Markers from exited processes are cleared and the locally captured partial record remains eligible. Inline wrapper stdout is forwarded unchanged and persisted as the response; exit code is preserved. Live pushes have a five-second timeout. Empty responses are still captured and lifecycle work is dispatched.
19
+ 5. **Search ranking**: raw prompt/response FTS and derived-metadata FTS retain separate indexes. Results use Reciprocal Rank Fusion (RRF, fixed `k=60`) across the two ranked lists, then timestamp descending and ID descending for deterministic ties. The raw-only relative BM25 order remains unchanged. Project-scoped CLI semantic search loads project embeddings through one metadata join instead of issuing one prompt query per embedding.
20
+ 6. **Everyday help and docs**: short help lists optional `ph context "task"`; SPEC-010, SPEC-003, SPEC-005, SPEC-018, SPEC-ISSUES, README, manual, and AGENTS describe the implemented security, cursor, lifecycle, and ranking contracts.
21
+
22
+ ## Acceptance criteria
23
+
24
+ - **G1 — Fail-closed HTTP**: Without `remoteApiKey`, `ph server` fails before listening. Default host is loopback. With a key, health returns status only; all data endpoints require the Bearer key. Invalid JSON returns 400, an oversized body returns 413, and internal errors do not expose exception details.
25
+ - **G2 — Lossless push**: Given more than one row-ID page, same-timestamp rows, late-imported lower-timestamp rows, bodies that exceed the byte limit, and a server response with per-record errors, every local row remains eligible until acknowledged. Live captures are held until completion; completed subpages checkpoint only after all subrequests are acknowledged. A database switch resets its cursor to zero. Retries do not duplicate acknowledged records.
26
+ - **G3 — Lossless pull**: Given over 10,000 remote rows, pagination imports all rows. Equal timestamps do not skip records. Legacy timestamp-only or database-unscoped config starts an idempotent full scan. A target or local database change starts at row zero. A server without continuation support fails clearly.
27
+ - **G4 — Shared live capture effects**: Given configured analysis and remote sync, direct log, inline wrapper, and PTY wrapper mark pending and dispatch the same effects. A malformed optional remote URL does not block local persistence or launching the wrapped CLI. A concurrent bulk push stops before active captures and resumes after completion; abandoned owner markers are reclaimed. Inline stdout is both displayed and stored as response. Exit status is preserved. Empty output is explicitly stored and still gets lifecycle completion.
28
+ - **G5 — Search ranking**: Raw-only queries preserve BM25 order; analysis-only queries find the correct source record; mixed-source queries follow RRF with deterministic tie breaks. Updating analysis metadata keeps the derived index fresh.
29
+ - **G6 — Product help**: Default help documents setup, search, show, and optional task context; status and maintenance remain advanced.
30
+ - **G7 — Migration and compatibility**: Existing prompt/response rows and sync dedup hashes remain intact. Legacy sync config replays idempotently instead of advancing a lossy timestamp cursor. HTTP/API clients receive an actionable upgrade error when pagination support is missing.
31
+ - **G8 — Bounded startup work**: Given a database whose search-text migration is complete, When another `PhDB` instance opens, Then it does not scan and parse all rows with empty derived metadata again. The database identity remains stable across reopen and differs across database files.
32
+ - **G9 — Scoped semantic loading**: Given embeddings across multiple projects, When CLI semantic search selects one project, Then only that project's vectors are loaded by a joined query and no per-vector `getById` lookup is issued.
33
+
34
+ ## Verification
35
+
36
+ Verification completed: `npm test`, `npm run lint`, `npx tsc --noEmit`, and `npm run build` pass. Focused tests cover server startup/auth/body bounds, pull pagination over 10,000 records, partial push retry and late old-timestamp records, database-scoped push/pull cursors, active-capture deferral and stale-marker recovery, byte-sized push subpages, invalid-URL local capture and real inline child execution, legacy metadata backfill with blank rows and no repeated scan, project-scoped embedding loads, inline response/exit forwarding, and mixed-index ranking. HTML link validation and `git diff --check` pass.
@@ -7,27 +7,32 @@ semantic-release + OIDC provenance).
7
7
 
8
8
  Cluster map: Core (CLI/architecture) · Capture (wrapper/hooks/log) · DB (PhDB)
9
9
  · Search (FTS/semantic) · AI (analysis/embeddings) · Memory (memories/summaries)
10
- · Sync (server/remote) · MCP · TUI · Import · Analytics · Config.
10
+ · Sync (server/remote) · MCP · Import · Analytics · Config. The history TUI was
11
+ retired on 2026-10-22; SPEC-012 is retained as a decision record.
11
12
 
12
13
  ## Registry
13
14
 
14
15
  | ID | Feature | Cluster | Status | Tests |
15
16
  |----|---------|---------|--------|-------|
16
- | SPEC-001 | Architecture & CLI dispatch | Core | **Implemented (baseline)** | — |
17
- | SPEC-002 | Database layer (PhDB) | DB | **Implemented (baseline)** | — |
18
- | SPEC-003 | Capture modes (pty/inline/hooks/log) | Capture | **Implemented (baseline)** | — |
17
+ | SPEC-001 | Architecture & CLI dispatch | Core | **Implemented (plain-text daily workflow)** | `src/commands/__tests__/operational-memory.test.ts` |
18
+ | SPEC-002 | Database layer (PhDB) | DB | **Implemented; stable DB identity and one-time metadata migration (SPEC-019)** | `src/db/__tests__/phdb.test.ts` |
19
+ | SPEC-003 | Capture modes (pty/inline/hooks/log) | Capture | **Implemented; shared live lifecycle (SPEC-019)** | `src/runner/inline.test.ts`, `src/capture/lifecycle.ts` |
19
20
  | SPEC-004 | Filter pipeline & metadata | Core | **Implemented (baseline)** | — |
20
- | SPEC-005 | Search (FTS + filters + semantic) | Search | **Implemented (baseline)** | — |
21
+ | SPEC-005 | Search (FTS + filters + semantic) | Search | **Implemented; cross-index RRF and scoped CLI vector loading (SPEC-019)** | `src/db/__tests__/phdb.test.ts`, `src/commands/__tests__/search.test.ts` |
21
22
  | SPEC-006 | Analysis pipeline (LLM) | AI | **Implemented (baseline)** | — |
22
23
  | SPEC-007 | Memory & project summaries | Memory | **Implemented (baseline)** | — |
23
24
  | SPEC-008 | Embeddings (sqlite-vec) | AI | **Implemented (baseline)** | — |
24
25
  | SPEC-009 | Retention & archive | DB | **Implemented (baseline)** | — |
25
- | SPEC-010 | Remote sync (server + push/pull) | Sync | **Implemented (baseline)** | — |
26
+ | SPEC-010 | Remote sync (server + push/pull) | Sync | **Implemented; database-scoped cursors, active-capture barrier, byte-bounded pages (SPEC-019)** | `src/server/__tests__/server.test.ts`, `src/commands/__tests__/remote.test.ts`, `src/sync/__tests__/client.test.ts` |
26
27
  | SPEC-011 | MCP server | MCP | **Implemented (baseline)** | — |
27
- | SPEC-012 | TUI browser | TUI | **Implemented (baseline)** | — |
28
+ | SPEC-012 | Interactive history browser | Retired UI | **Retired 2026-10-22** | — |
28
29
  | SPEC-013 | Importers (claude/gemini/opencode/codex) | Import | **Implemented** | import fixtures |
29
30
  | SPEC-014 | Analytics commands (sessions/stats/cluster/timeline) | Analytics | **Implemented (baseline)** | — |
30
31
  | SPEC-015 | Config & env overrides | Config | **Implemented (baseline)** | — |
32
+ | SPEC-016 | Operational memory workflow (context/close/MCP provenance) | Memory, CLI, MCP | **Implemented** | `src/commands/__tests__/operational-memory.test.ts`, `src/mcp/__tests__/server.test.ts` |
33
+ | SPEC-017 | Internal architecture consolidation (CLI surface gate, MCP modules, DB repositories) | Core, DB, MCP | **Implemented** | `src/knowledge/`, `src/mcp/tools/`, `src/db/{memory,archive}-repository.ts` |
34
+ | SPEC-018 | Interaction inspection and recall integrity | Core, Capture, Search, Memory, MCP | **Implemented** | `src/commands/__tests__/`, `src/db/__tests__/phdb.test.ts`, `src/context/__tests__/context.test.ts`, `src/mcp/__tests__/server.test.ts` |
35
+ | SPEC-019 | Network, capture, and retrieval integrity | Capture, Sync, Search, CLI | **Implemented; follow-up edge cases closed** | `src/server/__tests__/server.test.ts`, `src/capture/__tests__/lifecycle.test.ts`, `src/commands/__tests__/remote.test.ts`, `src/sync/__tests__/client.test.ts`, `src/commands/__tests__/search.test.ts`, `src/db/__tests__/phdb.test.ts` |
31
36
 
32
37
  ## Cross-cutting findings (SPEC-ISSUES.md)
33
38
 
@@ -37,12 +37,10 @@ Status: Open → assigned SPEC / fixed.
37
37
  never in package.json → lint was completely broken). Fixed (downgrade).
38
38
  - **ISSUE-007 — ZERO test files (FIXED 2026-10-09)** — baseline had no test
39
39
  files. Tests now cover database, search, memory, archive, sync, config,
40
- analysis, sessions, context, capture, TUI filter transitions, importer formats,
41
- PTY response cleanup, HTTP endpoints, and MCP tool calls over an in-memory
42
- protocol transport. The Ink browser also has a PTY smoke test that opens its
43
- filter panel and exits cleanly; a nested PTY test verifies wrapped prompt and
44
- ANSI-clean response persistence. Broader screen flows remain covered by
45
- component/module tests rather than end-to-end navigation.
40
+ analysis, sessions, context, capture, importer formats, PTY response cleanup,
41
+ HTTP endpoints, and MCP tool calls over an in-memory protocol transport. The
42
+ retired Ink browser coverage is historical; current product tests target the
43
+ plain-text CLI, capture adapters, and command modules.
46
44
  - **ISSUE-016 — NON_PRINTABLE pattern matched plain words (FIXED 2026-08-08)** —
47
45
  the filter's non-printable regex `^[\x00-\x1f\x7f\x1b\[\]()#;\d;A-Za-z]*$`
48
46
  matched ANY all-alphanumeric string (e.g. "yes") because of `[A-Za-z\d]` in
@@ -73,23 +71,21 @@ Status: Open → assigned SPEC / fixed.
73
71
  - **ISSUE-010 — cli.ts TUI bootstrap duplicated 3× (FIXED 2026-08-08)** —
74
72
  extracted `openBrowser(db, props)` and `runRerun()` in `src/cli.ts`; default,
75
73
  `search -i`, and `browse` now share the alt-screen cleanup path. Fixed.
76
- - **ISSUE-011 — BrowseApp remains a large module** — detail, edit, rerun,
77
- provider selection, settings, and app state remain in `src/ui/BrowseApp.tsx`.
78
- Filter policy/count generation now lives in `src/ui/filtering.ts` and keyboard
79
- rendering in `src/ui/FilterPanel.tsx`; filter transitions are directly tested
80
- and the keyboard path to open/close the rendered filter panel has a PTY smoke
81
- test. Detail/edit/rerun and other screen interactions remain in BrowseApp.
74
+ - **ISSUE-011 — BrowseApp remains a large module (RETIRED 2026-10-22)** — the
75
+ history TUI and its Ink/React screens were removed when PH moved to the
76
+ plain-text daily workflow. This finding no longer applies to the product.
82
77
  - **ISSUE-012 — sync_hash excludes args/workdir (FIXED 2026-08-08)** — dedup
83
78
  hash was `sha256(tool|prompt|response)` duplicated inline in server
84
79
  (src/server/index.ts:104) and remote (remote.ts:116) — drift risk. Now a
85
80
  shared `syncHash` helper (src/utils/syncHash.ts) that includes `args` ONLY
86
81
  when present — backward compatible: entries without args keep the legacy
87
82
  hash (no mass re-push), entries with different args no longer collapse. Fixed.
88
- - **ISSUE-013 — server auth is declared but never enforced (FIXED 2026-08-08)** —
83
+ - **ISSUE-013 — server auth was conditional and fail-open (FIXED 2026-10-24)** —
89
84
  `remoteApiKey` was sent by the client but the HTTP server never checked it.
90
85
  Now `createRequestHandler` (exported for tests) gates every endpoint except
91
- `/health` with `Authorization: Bearer <key>` (timingSafeEqual) when
92
- `remoteApiKey` is configured (src/server/index.ts:30-60). Fixed.
86
+ `/health` with `Authorization: Bearer <key>` (timingSafeEqual). Since
87
+ SPEC-019, startup refuses to run without a key; default bind is loopback and
88
+ `/health` returns status only (src/server/index.ts). Fixed.
93
89
  - **ISSUE-014 — memories prompt_ids unused after summaries** — `prompt_ids` in
94
90
  `memories` (src/db/index.ts:109) is written but the summary merge path
95
91
  (project_summaries) tracks prompt_count instead; the timeline joins via
@@ -98,6 +94,87 @@ Status: Open → assigned SPEC / fixed.
98
94
  duplicates formatting logic that the TUI ListEntry also renders; two code paths
99
95
  for entry display can drift (e.g. badges, truncation rules).
100
96
 
97
+ ## Follow-up findings (2026-10-24)
98
+
99
+ - **ISSUE-019 — sync cursors were shared across local databases (FIXED 2026-10-24)** —
100
+ global configuration stored push/pull cursors keyed only by remote target, so
101
+ switching `PH_DB` or `db-path` could skip records. Each SQLite file now has a
102
+ persistent UUID included in both cursor comparisons; older cursor shapes replay
103
+ idempotently from row zero (`src/db/index.ts:69-79,129`,
104
+ `src/commands/remote.ts:44-49,118-126`). Regression coverage:
105
+ `src/commands/__tests__/remote.test.ts` switches local database files and
106
+ verifies both push and pull restart at row zero.
107
+ - **ISSUE-020 — invalid optional remote URL interrupted local capture (FIXED 2026-10-24)** —
108
+ `syncTarget()` could throw before the local row was inserted or the wrapped
109
+ CLI started. Remote URLs are validated when configured; environment overrides
110
+ are handled as a failed remote effect while local capture continues
111
+ (`src/sync/protocol.ts:25-40`, `src/capture/lifecycle.ts:24-40,63-68`,
112
+ `src/commands/config.ts:174-175`). Regression coverage:
113
+ `src/config/__tests__/config.test.ts`, `src/capture/__tests__/lifecycle.test.ts`,
114
+ and `src/runner/inline.test.ts` verify validation, local persistence, and that
115
+ the wrapped child still runs and its response is saved.
116
+ - **ISSUE-021 — bulk sync could publish a live partial response (FIXED 2026-10-24)** —
117
+ wrappers persist prompts before their response is complete. Captures now carry
118
+ a local owner PID; bulk push stops before a live capture and clears markers
119
+ left by exited owners, preventing the partial and final versions from being
120
+ imported as separate interactions (`src/commands/wrap.ts:53-63`,
121
+ `src/runner/inline.ts:50-58`, `src/capture/lifecycle.ts:24-40,49-55`,
122
+ `src/commands/remote.ts:68-104,205-212`). Regression coverage:
123
+ `src/commands/__tests__/remote.test.ts` verifies a live partial response is
124
+ withheld and only the completed response is pushed.
125
+ - **ISSUE-022 — fixed record-count pages could exceed the request-body cap (FIXED 2026-10-24)** —
126
+ a 500-row request could exceed the server's 32 MiB limit and repeat unchanged.
127
+ The client now splits outer pages by serialized byte size before sending and
128
+ aggregates acknowledgements; an individually oversized record gets an
129
+ explicit ID and size error (`src/sync/client.ts:9-29,32-50`,
130
+ `src/sync/protocol.ts:4-5`). Regression coverage in
131
+ `src/sync/__tests__/client.test.ts` verifies byte limits, record order,
132
+ acknowledgement aggregation, and a clear error for a single oversized row.
133
+ - **ISSUE-023 — search metadata backfill scanned the prompt table on every open (FIXED 2026-10-24)** —
134
+ rows with legitimately empty derived text were selected and parsed on each
135
+ command. The backfill now runs once under an immediate transaction and records
136
+ its completion in `ph_meta` (`src/db/index.ts:185-205`).
137
+ `src/db/__tests__/phdb.test.ts` upgrades a legacy database with analyzed and
138
+ blank rows, verifies searchable derived text, and ensures a later open does
139
+ not query the blank rows again.
140
+ - **ISSUE-024 — project semantic search issued one query per vector (FIXED 2026-10-24)** —
141
+ CLI search loaded all legacy embeddings and called `getById` for each vector
142
+ to filter the current project. `SemanticIndex.loadProject()` now filters via
143
+ one join, and the CLI loads only scoped vectors (`src/db/semantic-index.ts:43-50`,
144
+ `src/db/index.ts:518-524`, `src/commands/search.ts:107-110`). Regression
145
+ coverage in `src/db/__tests__/phdb.test.ts` checks project membership through
146
+ the joined query; `src/commands/__tests__/search.test.ts` verifies the CLI
147
+ chooses the scoped loader and never loads all vectors for a project search.
148
+ - **ISSUE-025 — remote database replacement is invisible to sync cursors (OPEN 2026-10-24)** —
149
+ Push/pull cursors are keyed by normalized URL and local database UUID, but
150
+ `/api/sync/pull` returns only row IDs and continuation state. If the remote
151
+ SQLite database is replaced behind the same URL, a stored high cursor treats
152
+ its new low IDs as already read; push likewise does not replay old local rows.
153
+ Config also stores only one push and one pull cursor, so switching A→B→A
154
+ replays A from row zero (idempotently, but with a full network scan).
155
+ The global `remoteLastPush` and `remoteLastPull` fields can also report another
156
+ target's time (`src/commands/remote.ts:111-119, 127-170, 183-199`,
157
+ `src/server/index.ts:161-170`, `src/config/index.ts:23-26`). Add remote
158
+ database identity to the protocol and scope all checkpoint/status state to it.
159
+ - **ISSUE-026 — semantic retrieval paths can disagree and vectors lack provenance (OPEN 2026-10-24)** —
160
+ The CLI loads the legacy BLOB table, computes cosine similarity, and applies
161
+ a fixed `0.3` cutoff, while context, HTTP, and MCP use `sqlite-vec`. The same
162
+ query can return a different set and order by consumer. Stored vectors do not
163
+ identify the configured embedding model; changing `ollama-embed-model` changes
164
+ query vectors, and `ph embed-all` leaves existing vectors untouched
165
+ (`src/commands/search.ts:95-120`, `src/db/semantic-index.ts:8-50`,
166
+ `src/commands/embed-all.ts:6-32`, `src/config/index.ts:20`). Unify the search
167
+ contract and make vector profile changes explicit.
168
+ - **ISSUE-027 — database migration work scales with history (OPEN 2026-10-24)** —
169
+ Every `PhDB` constructor calls `migrate()`. Routine opens still select prompts
170
+ missing `$schema_version` by parsing the JSON field and retry legacy column
171
+ migrations. On the first open after the derived-search migration, the marker
172
+ prevents repeats, but every blank `search_metadata` row is loaded and updated
173
+ individually inside one immediate transaction. Large databases can pay O(n)
174
+ startup work and hold a write lock during that first backfill
175
+ (`src/db/index.ts:72-80, 164-205, 209-217`). Version and bound the migration
176
+ steps, and avoid rewriting rows with no searchable analysis text.
177
+
101
178
  ## Issue-to-spec map
102
179
 
103
180
  | Issue | Spec (fix target) | Status |
@@ -108,15 +185,24 @@ Status: Open → assigned SPEC / fixed.
108
185
  | 004 | SPEC-003 | Fixed 2026-08-08 |
109
186
  | 005 | SPEC-002 | Fixed 2026-08-08 |
110
187
  | 006 | — (tooling) | Fixed 2026-08-08 (eslint 9 + @eslint/js) |
111
- | 007 | SPEC-002/003/005/009/010/011/012/013/014 | Fixed 2026-10-09 (core terminal and TUI paths covered; other screen flows remain module-tested) |
188
+ | 007 | SPEC-002/003/005/009/010/011/012/013/014 | Fixed 2026-10-09; test scope updated after TUI retirement |
112
189
  | 008 | — (docs) | Open |
113
190
  | 009 | — (chore) | Open |
114
191
  | 010 | SPEC-001 | Fixed 2026-08-08 |
115
- | 011 | SPEC-012 | Open |
192
+ | 011 | SPEC-012 | Retired 2026-10-22 |
116
193
  | 012 | SPEC-010 | Fixed 2026-08-08 (shared syncHash, args-aware, backward compatible) |
117
- | 013 | SPEC-010 | Fixed 2026-08-08 (auth gate on createRequestHandler) |
194
+ | 013 | SPEC-010/019 | Fixed 2026-10-24 (mandatory key, loopback default, health redaction) |
118
195
  | 014 | SPEC-007 | Open |
119
196
  | 015 | SPEC-012 | Open |
120
197
  | 016 | SPEC-004 | Fixed 2026-08-08 |
121
198
  | 017 | SPEC-004 | Fixed 2026-08-08 |
122
199
  | 018 | SPEC-005/014 | Fixed 2026-10-09 |
200
+ | 019 | SPEC-010/019 | Fixed 2026-10-24 (persistent local database UUID scopes push/pull cursors) |
201
+ | 020 | SPEC-003/019 | Fixed 2026-10-24 (invalid optional remote URL cannot block local capture) |
202
+ | 021 | SPEC-003/010/019 | Fixed 2026-10-24 (active and abandoned capture markers handled before bulk push) |
203
+ | 022 | SPEC-010/019 | Fixed 2026-10-24 (push subpages bounded by serialized request bytes) |
204
+ | 023 | SPEC-002/019 | Fixed 2026-10-24 (one-time transactional search metadata backfill) |
205
+ | 024 | SPEC-005/019 | Fixed 2026-10-24 (project embedding load uses one metadata join) |
206
+ | 025 | SPEC-010/019 | Open (remote database replacement is not detected; timestamps are global) |
207
+ | 026 | SPEC-005/008 | Open (semantic search engines diverge; vectors lack model profile) |
208
+ | 027 | SPEC-002 | Open (routine database initialization repeats history scans) |
@@ -25,7 +25,7 @@ Add (or update) the `hooks` section in your `~/.claude/settings.json` file:
25
25
  "hooks": [
26
26
  {
27
27
  "type": "command",
28
- "command": "/Users/gianluca/.claude/ph-hook.sh",
28
+ "command": "$HOME/.claude/ph-hook.sh",
29
29
  "async": true
30
30
  }
31
31
  ]
@@ -1,6 +1,6 @@
1
1
  # Codex CLI hook for ph
2
2
 
3
- The hook imports the active Codex rollout after each completed turn. Codex supplies the transcript path to the `Stop` hook; `ph` pairs user messages with the assistant response and deduplicates turns already captured.
3
+ The hook imports the active Codex rollout after each completed turn. Codex supplies the transcript path to the `Stop` hook; `ph` pairs user messages with the assistant response and deduplicates turns already captured. The hook supplies the active CLI working directory to the importer so captures are associated with the project where Codex was launched, even when the transcript records a synthetic directory.
4
4
 
5
5
  ## Install
6
6
 
@@ -4,11 +4,12 @@ set -u
4
4
 
5
5
  INPUT=$(cat)
6
6
  TRANSCRIPT_PATH=$(printf '%s' "$INPUT" | node -e 'let s="";process.stdin.on("data",d=>s+=d);process.stdin.on("end",()=>{try{process.stdout.write(JSON.parse(s).transcript_path||"")}catch{}})' 2>/dev/null)
7
+ WORKDIR="$PWD"
7
8
  if [ -z "$TRANSCRIPT_PATH" ] || [ ! -f "$TRANSCRIPT_PATH" ]; then
8
9
  printf '{}\n'
9
10
  exit 0
10
11
  fi
11
12
 
12
- ph import codex --file "$TRANSCRIPT_PATH" >/dev/null 2>&1 || true
13
+ ph import codex --file "$TRANSCRIPT_PATH" --workdir "$WORKDIR" >/dev/null 2>&1 || true
13
14
  printf '{}\n'
14
15
  exit 0
@@ -24,7 +24,7 @@ Add (or update) the `hooks` section in your `~/.gemini/settings.json` file:
24
24
  "hooks": [
25
25
  {
26
26
  "type": "command",
27
- "command": "/Users/gianluca/.gemini/ph-hook.sh"
27
+ "command": "$HOME/.gemini/ph-hook.sh"
28
28
  }
29
29
  ]
30
30
  }
package/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "@gi4nks/ph",
3
- "version": "1.6.0",
3
+ "version": "1.8.0",
4
4
  "description": "Transparent CLI wrapper that captures and indexes AI prompts",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
+ "engines": {
8
+ "node": ">=22"
9
+ },
7
10
  "bin": {
8
11
  "ph": "dist/cli.js"
9
12
  },
@@ -55,12 +58,11 @@
55
58
  "eval:retrieval": "tsx scripts/eval-context-retrieval.ts"
56
59
  },
57
60
  "dependencies": {
58
- "@gi4nks/ink": "^1.5.0",
61
+ "@gi4nks/wise": "^2.1.0",
59
62
  "@lydell/node-pty": "^1.2.0-beta.3",
60
63
  "@modelcontextprotocol/sdk": "^1.29.0",
64
+ "ai": "^7.0.136",
61
65
  "better-sqlite3": "^12.8.0",
62
- "ink": "^7.0.5",
63
- "react": "^19.2.7",
64
66
  "sqlite-vec": "^0.1.9",
65
67
  "zod": "^4.3.6"
66
68
  },
@@ -72,12 +74,9 @@
72
74
  "@semantic-release/npm": "^13.1.5",
73
75
  "@types/better-sqlite3": "^7.6.13",
74
76
  "@types/node": "^25.9.3",
75
- "@types/react": "^19.2.7",
76
77
  "@typescript-eslint/eslint-plugin": "^8.58.2",
77
78
  "@typescript-eslint/parser": "^8.58.2",
78
79
  "eslint": "^9.39.5",
79
- "eslint-plugin-react": "^7.37.5",
80
- "eslint-plugin-react-hooks": "^7.0.1",
81
80
  "semantic-release": "^25.0.3",
82
81
  "tsup": "^8.5.1",
83
82
  "tsx": "^4.21.0",