@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,82 @@
|
|
|
1
|
+
# SPEC-001: Architecture & CLI Dispatch
|
|
2
|
+
|
|
3
|
+
- **ID**: SPEC-001
|
|
4
|
+
- **Cluster**: Core
|
|
5
|
+
- **Status**: Implemented (baseline)
|
|
6
|
+
- **Date**: 2026-08-08
|
|
7
|
+
- **Source**: reverse-engineered from code (files listed in §8)
|
|
8
|
+
|
|
9
|
+
## 1. Purpose
|
|
10
|
+
`ph` is a transparent observability layer for AI CLI tools: every prompt+response
|
|
11
|
+
is captured to a local SQLite DB (SPEC-002), analyzed (SPEC-006), and made
|
|
12
|
+
browsable via TUI (SPEC-012), CLI commands, MCP (SPEC-011) and HTTP (SPEC-010).
|
|
13
|
+
This spec covers the entry point and the command dispatch architecture.
|
|
14
|
+
|
|
15
|
+
## 2. Scope
|
|
16
|
+
- **In scope**: cli.ts main(), command resolution, wrapper-mode fallback, config
|
|
17
|
+
loading order, TUI bootstrap.
|
|
18
|
+
- **Out of scope**: individual commands (their own specs), the TUI internals.
|
|
19
|
+
- **Entry points**: `ph` (no args → TUI), `ph <command>`, `ph <tool> "prompt"` (wrapper).
|
|
20
|
+
|
|
21
|
+
## 3. Data Model
|
|
22
|
+
- `argv` split into `command` (first non-flag arg) + `cmdArgs` + `preArgs`
|
|
23
|
+
(ph-specific flags before the command, e.g. `--ph-tag`).
|
|
24
|
+
- `PhConfig` (src/config/index.ts:5) loaded before dispatch; `geminiApiKey`
|
|
25
|
+
is copied to `process.env.GEMINI_API_KEY` (cli.ts:153-155).
|
|
26
|
+
- DB path resolution: `PH_DB` env > `cfg.dbPath` > `defaultPath()`
|
|
27
|
+
(cli.ts:157).
|
|
28
|
+
|
|
29
|
+
## 4. Flows
|
|
30
|
+
|
|
31
|
+
### 4.1 Dispatch (src/cli.ts:148-441)
|
|
32
|
+
1. Load config; set env; resolve dbPath.
|
|
33
|
+
2. No args + TTY → render BrowseApp in alt-screen, wait for exit, optional
|
|
34
|
+
rerun (cli.ts:159-186).
|
|
35
|
+
3. Find first non-flag arg; flags `--ph-tag`/`--ph-role` consume their value
|
|
36
|
+
(cli.ts:190-203).
|
|
37
|
+
4. Switch on the command name: ~25 cases (search, context, last, sessions,
|
|
38
|
+
stats, cluster, analyze*, cleanup*, star, export, import, analyze, mcp,
|
|
39
|
+
server, remote, log, embed-all, vacuum, config, _bg-analyze, browse, chat,
|
|
40
|
+
timeline, ollama-models, help).
|
|
41
|
+
5. Unknown command → wrapper mode: `cmdWrap(dbPath, command, [...preArgs,
|
|
42
|
+
...cmdArgs], cfg)` (cli.ts:436-439).
|
|
43
|
+
|
|
44
|
+
### 4.2 Wrapper mode (SPEC-003)
|
|
45
|
+
Unknown first arg = a real binary (`claude`, `gemini`, …): run it and capture.
|
|
46
|
+
|
|
47
|
+
## 5. Invariants & Business rules
|
|
48
|
+
- ESM only, `.js` import extensions (package.json `"type": "module"`).
|
|
49
|
+
- Manual arg parsing (`parseFlags`, src/commands/_utils.ts:1) — no commander.
|
|
50
|
+
- Every command handler owns its `PhDB` instance and closes it
|
|
51
|
+
(`new PhDB(...)` … `db.close()` per case).
|
|
52
|
+
- `main()` catches everything: `ph: <message>` on stderr, exit 1 (cli.ts:443-446).
|
|
53
|
+
|
|
54
|
+
## 6. UI / UX surface
|
|
55
|
+
USAGE text is a single ~200-line template literal (cli.ts:38-140); `ph --help`,
|
|
56
|
+
`-h`, `help` and non-TTY no-arg all print it.
|
|
57
|
+
|
|
58
|
+
## 7. Acceptance criteria (Given/When/Then)
|
|
59
|
+
- **G1**: Given `ph search foo`, When main() runs, Then the `search` case opens a
|
|
60
|
+
PhDB, calls `cmdSearch` and closes the DB (cli.ts:250-253).
|
|
61
|
+
- **G2**: Given `ph claude "hello"` (unknown command), When main() runs, Then
|
|
62
|
+
`cmdWrap` receives tool=`claude` and args `["hello"]` (cli.ts:436-439).
|
|
63
|
+
- **G3**: Given `ph` with no args on a non-TTY stdin, When main() runs, Then the
|
|
64
|
+
USAGE text is printed and the process exits 0 (cli.ts:183-186).
|
|
65
|
+
- **G4**: Given `--ph-tag auth claude "x"`, When parsing, Then `auth` is consumed
|
|
66
|
+
as the tag value and `claude` is the command (cli.ts:192-194).
|
|
67
|
+
|
|
68
|
+
## 8. Key implementation map
|
|
69
|
+
| Concern | File(s) |
|
|
70
|
+
|---|---|
|
|
71
|
+
| Entry + dispatch + USAGE | src/cli.ts:38-441 |
|
|
72
|
+
| Config load/save | src/config/index.ts:26-40 |
|
|
73
|
+
| Flag parsing | src/commands/_utils.ts:1-30 |
|
|
74
|
+
| Shared TUI bootstrap | src/cli.ts (`openBrowser`, `runRerun`) |
|
|
75
|
+
|
|
76
|
+
## 9. Open questions / discrepancies
|
|
77
|
+
- `--ph-debug` is listed in USAGE but only consumed by wrapper paths (inline/pty
|
|
78
|
+
debug logs); no validation if used with non-wrapper commands.
|
|
79
|
+
|
|
80
|
+
## 10. Related
|
|
81
|
+
- All other specs (commands dispatch into each), SPEC-012 (BrowseApp),
|
|
82
|
+
SPEC-003 (wrapper mode). CLI dispatch tests remain part of SPEC-ISSUES-007.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# SPEC-002: Database Layer (PhDB)
|
|
2
|
+
|
|
3
|
+
- **ID**: SPEC-002
|
|
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
|
+
All persistence for ph: prompts (raw history), FTS5 full-text index, sqlite-vec
|
|
11
|
+
embeddings, memories, project summaries and the archive — behind a single
|
|
12
|
+
`PhDB` class (src/db/index.ts, ~800 lines, ~50 public methods). WAL mode, best
|
|
13
|
+
effort migrations, zero ORM.
|
|
14
|
+
|
|
15
|
+
## 2. Scope
|
|
16
|
+
- **In scope**: schema, migrations, insert/update/delete/search primitives,
|
|
17
|
+
memories + summaries + archive APIs, stats.
|
|
18
|
+
- **Out of scope**: FTS query semantics (SPEC-005), semantic search (SPEC-008),
|
|
19
|
+
retention rules (SPEC-009), sync dedup (SPEC-010).
|
|
20
|
+
- **Entry points**: `new PhDB(dbPath)`, `defaultPath()` (src/db/index.ts).
|
|
21
|
+
|
|
22
|
+
## 3. Data Model
|
|
23
|
+
- `prompts` (src/db/index.ts:56-66): id, timestamp, tool, prompt, args, workdir,
|
|
24
|
+
hostname, exit_code, metadata (JSON). NOTE: `response` column is added by ALTER
|
|
25
|
+
(SPEC-ISSUES-001).
|
|
26
|
+
- `prompts_fts` FTS5 external-content (src/db/index.ts:68-73) with AFTER INSERT /
|
|
27
|
+
AFTER DELETE triggers (75-82).
|
|
28
|
+
- `embeddings` (legacy BLOB, 87-91) + `vec_embeddings` vec0 float[768] (100-104).
|
|
29
|
+
- `memories` (106-121) append-only per project; `project_summaries` (PhDB.PROJECT_SUMMARIES_SCHEMA, :123);
|
|
30
|
+
`prompts_archive` (PhDB.ARCHIVE_SCHEMA, :124).
|
|
31
|
+
- `PromptEntry` / `PromptMetadata` / `MemoryEntry` / `ProjectSummary` types
|
|
32
|
+
(src/types.ts:1-52).
|
|
33
|
+
|
|
34
|
+
## 4. Flows
|
|
35
|
+
|
|
36
|
+
### 4.1 Open + migrate (src/db/index.ts:47-196)
|
|
37
|
+
1. `new Database(dbPath)`; `PRAGMA journal_mode = WAL`; `sqliteVec.load(db)`.
|
|
38
|
+
2. CREATE TABLE IF NOT EXISTS for all tables + triggers + indexes.
|
|
39
|
+
3. Migrations: `$schema_version` backfill (127-136), legacy metadata column
|
|
40
|
+
(139-141), `response` column (143-146), BLOB→vec0 embedding migration
|
|
41
|
+
(148-180).
|
|
42
|
+
|
|
43
|
+
### 4.2 Insert (src/db/index.ts:198-218)
|
|
44
|
+
`insert(entry)` defaults `response ?? ''` and `metadata || '{}'`, INSERTs, FTS
|
|
45
|
+
trigger keeps the index in sync. Returns lastInsertRowid.
|
|
46
|
+
|
|
47
|
+
### 4.3 Read paths
|
|
48
|
+
- `search(opts)` (238) — see SPEC-005.
|
|
49
|
+
- `getById` (232), `getPromptsSince` (393), `getAllPrompts` (387),
|
|
50
|
+
`getPromptCount`/`getPromptCountSince` (399-410), `getStats` (410+).
|
|
51
|
+
- `getPromptBySyncHash` (403-407) — used by sync dedup (SPEC-010).
|
|
52
|
+
- Memories: `insertMemory` (535), `upsertProjectMemory` (612),
|
|
53
|
+
`upsertProjectSummary` (643), `getProjectSummary` (690) — SPEC-007.
|
|
54
|
+
- Archive: `archivePrompts` (464), `searchArchive` (496), `purgeArchive` (506),
|
|
55
|
+
`getArchiveStats` (511) — SPEC-009.
|
|
56
|
+
|
|
57
|
+
## 5. Invariants & Business rules
|
|
58
|
+
- WAL mode always on; sqlite-vec loaded on every open.
|
|
59
|
+
- FTS index maintained by triggers (never manually rebuilt except migration).
|
|
60
|
+
- `metadata` is always valid JSON (`'{}'` fallback).
|
|
61
|
+
- All counts/read queries are typed casts on prepared statements — no ORM.
|
|
62
|
+
|
|
63
|
+
## 6. UI / UX surface
|
|
64
|
+
None — library layer consumed by commands, TUI, MCP and HTTP server.
|
|
65
|
+
|
|
66
|
+
## 7. Acceptance criteria (Given/When/Then)
|
|
67
|
+
- **G1**: Given a fresh temp DB, When `insert()` runs with a prompt, Then the
|
|
68
|
+
row is retrievable via `getById` and count is 1 (src/db/index.ts:198-232).
|
|
69
|
+
- **G2**: Given an insert without `response`, When read back, Then `response`
|
|
70
|
+
defaults to `''` (src/db/index.ts:199-201).
|
|
71
|
+
- **G3**: Given two prompts with the same `tool|prompt|response`, When
|
|
72
|
+
`getPromptBySyncHash` runs, Then only the first match is returned
|
|
73
|
+
(src/db/index.ts:403-407).
|
|
74
|
+
- **G4**: Given a delete, When `getById` runs, Then the entry is gone and the
|
|
75
|
+
FTS trigger removed it (src/db/index.ts:79-82, 427).
|
|
76
|
+
- **G5**: Given `getStats()`, When run, Then total/totalMemories/byTool are
|
|
77
|
+
consistent with the tables (src/db/index.ts:410+).
|
|
78
|
+
- **G6**: Given a fresh DB, When opened, Then `vec_embeddings` exists as a vec0
|
|
79
|
+
table with float[768] (src/db/index.ts:95-104).
|
|
80
|
+
|
|
81
|
+
## 8. Key implementation map
|
|
82
|
+
| Concern | File(s) |
|
|
83
|
+
|---|---|
|
|
84
|
+
| Schema + migrations | src/db/index.ts:47-196 |
|
|
85
|
+
| CRUD + search + sync primitives | src/db/index.ts:198-531 |
|
|
86
|
+
| Memories + summaries | src/db/index.ts:535-720 |
|
|
87
|
+
| Archive | src/db/index.ts:464-518 |
|
|
88
|
+
| Semantic vector index | src/db/semantic-index.ts |
|
|
89
|
+
| Types | src/types.ts:1-52 |
|
|
90
|
+
|
|
91
|
+
## 9. Open questions / discrepancies
|
|
92
|
+
- **Architecture decision (2026-10-09)**: keep `PhDB` as the single SQLite adapter
|
|
93
|
+
for now. Context consumers depend on the focused `ProjectContextStore` interface
|
|
94
|
+
(`src/context/index.ts`). Vector encoding, compatibility storage, and KNN
|
|
95
|
+
ranking now live in `src/db/semantic-index.ts`, with `PhDB` retaining its public
|
|
96
|
+
methods as the adapter. Scope behavior has an integration test. Keep unrelated
|
|
97
|
+
CRUD/memory/archive methods together until a second adapter or concrete
|
|
98
|
+
independent migration/testing seam justifies further division.
|
|
99
|
+
- `response` column via ALTER (SPEC-ISSUES-001).
|
|
100
|
+
- `memories.prompt_ids` possibly unused after summaries (SPEC-ISSUES-014).
|
|
101
|
+
- `delete` (427) and `deleteById` (435) coexist — duplicate deletion paths.
|
|
102
|
+
|
|
103
|
+
## 10. Related
|
|
104
|
+
- SPEC-005 (search), SPEC-007 (memory), SPEC-009 (retention), SPEC-010 (sync).
|
|
105
|
+
Public behavior is covered by Vitest integration tests (SPEC-ISSUES-007).
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# SPEC-003: Capture Modes (PTY / Inline / Hooks / Log)
|
|
2
|
+
|
|
3
|
+
- **ID**: SPEC-003
|
|
4
|
+
- **Cluster**: Capture
|
|
5
|
+
- **Status**: Implemented (baseline)
|
|
6
|
+
- **Date**: 2026-08-08
|
|
7
|
+
- **Source**: reverse-engineered from code (files listed in §8)
|
|
8
|
+
|
|
9
|
+
## 1. Purpose
|
|
10
|
+
The three ways a prompt+response reaches the DB: transparent PTY wrapping of an
|
|
11
|
+
interactive tool, inline (non-interactive) capture, and direct logging via hooks
|
|
12
|
+
or `ph log`. Plus the OpenCode plugin for real-time capture.
|
|
13
|
+
|
|
14
|
+
## 2. Scope
|
|
15
|
+
- **In scope**: runPTY (ESC/OSC state machine), runInline, resolveRealBinary,
|
|
16
|
+
ph log command, hooks, opencode plugin.
|
|
17
|
+
- **Out of scope**: what happens after capture (analysis SPEC-006, background
|
|
18
|
+
push SPEC-010).
|
|
19
|
+
- **Entry points**: `ph <tool> …` (wrapper), `ph log`, hook scripts, opencode plugin.
|
|
20
|
+
|
|
21
|
+
## 3. Data Model
|
|
22
|
+
- `PromptCallback(prompt, timestamp) => id` and `ResponseCallback(id, response)`
|
|
23
|
+
(src/pty/wrapper.ts:4-5).
|
|
24
|
+
- Entry shape: `Omit<PromptEntry,'id'|'response'> & {response?: string}`
|
|
25
|
+
(PhDB.insert, SPEC-002).
|
|
26
|
+
- Live captures and imports use `createCaptureRecord` (src/capture/index.ts)
|
|
27
|
+
to normalize timestamps, host, project/language/title metadata, and source
|
|
28
|
+
fields before persistence.
|
|
29
|
+
|
|
30
|
+
## 4. Flows
|
|
31
|
+
|
|
32
|
+
### 4.1 PTY wrapper (src/pty/wrapper.ts:23-169)
|
|
33
|
+
1. `pty.spawn(binary, args, {name:'xterm-256color', cols, rows, cwd, env})`.
|
|
34
|
+
2. Forward PTY output to stdout; buffer it as the "response".
|
|
35
|
+
3. stdin in raw mode; a byte-level ESC/CSI/OSC state machine (wrapper.ts:74-145)
|
|
36
|
+
detects Enter (prompt capture), backspace, Ctrl-C/D, and ignores escape
|
|
37
|
+
sequences. On Enter: finalize previous response, call `onPrompt`.
|
|
38
|
+
4. On PTY exit: finalize last response, restore stdin, resolve with exitCode.
|
|
39
|
+
|
|
40
|
+
### 4.2 Inline (src/runner/inline.ts:37)
|
|
41
|
+
`runInline` captures args → `extractPrompt(args)` (:8), runs the binary
|
|
42
|
+
non-interactively (spawnSync), records exit code, then `db.insert`.
|
|
43
|
+
|
|
44
|
+
### 4.3 Direct log (src/commands/log.ts:9-70)
|
|
45
|
+
`ph log --tool <t> --prompt <p> [--response <r>]` builds the entry (project
|
|
46
|
+
detection, language detection, topic extraction), inserts, then optionally
|
|
47
|
+
spawns background analysis (SPEC-006) and a fire-and-forget remote push
|
|
48
|
+
(SPEC-010).
|
|
49
|
+
|
|
50
|
+
### 4.4 Hooks + plugin
|
|
51
|
+
- `hooks/claude/ph-hook.sh`, `hooks/gemini/ph-hook.sh` — shell scripts invoked
|
|
52
|
+
post-session; they pipe JSON to `ph log`.
|
|
53
|
+
- `hooks/codex/ph-hook.sh` — Codex `Stop` hook reads `transcript_path` from
|
|
54
|
+
stdin and imports the active rollout with `ph import codex --file`.
|
|
55
|
+
- `hooks/opencode/ph-plugin.ts` — real-time capture via OpenCode hooks
|
|
56
|
+
(`chat.message` + `experimental.text.complete`), pairs prompts with streamed
|
|
57
|
+
responses, calls `ph log` in background; `hooks/opencode/install.sh`.
|
|
58
|
+
|
|
59
|
+
## 5. Invariants & Business rules
|
|
60
|
+
- PTY capture is byte-exact for prompts: ANSI sequences never leak into the
|
|
61
|
+
prompt text (ESC state machine).
|
|
62
|
+
- `--ph-debug` writes a session log to `~/.ph_debug.log` via `debugLog`.
|
|
63
|
+
- Capture never blocks the wrapped tool (background processes for analysis/push).
|
|
64
|
+
- Binary resolution: `resolveRealBinary(tool)` maps known tool names
|
|
65
|
+
(src/runner/inline.ts:15).
|
|
66
|
+
- All supported capture adapters produce the same normalized PromptEntry shape;
|
|
67
|
+
source-specific metadata remains attached to that record.
|
|
68
|
+
|
|
69
|
+
## 6. UI / UX surface
|
|
70
|
+
Transparent: the wrapped tool's own output is forwarded verbatim; ph itself
|
|
71
|
+
prints only capture feedback.
|
|
72
|
+
|
|
73
|
+
## 7. Acceptance criteria (Given/When/Then)
|
|
74
|
+
- **G1**: Given a PTY session where the user types `hello` and presses Enter,
|
|
75
|
+
When the ESC state machine processes the bytes, Then `onPrompt` fires with
|
|
76
|
+
`"hello"` (src/pty/wrapper.ts:87-98).
|
|
77
|
+
- **G2**: Given ANSI/OSC sequences (e.g. `\x1b]0;title\x07`), When the state
|
|
78
|
+
machine runs, Then the sequences are consumed and never appended to
|
|
79
|
+
`lineBuffer` (src/pty/wrapper.ts:114-143).
|
|
80
|
+
- **G3**: Given `ph log --tool claude --prompt "x"` without `--response`, When
|
|
81
|
+
cmdLog runs, Then the entry is inserted with `response: ''` and project
|
|
82
|
+
metadata (src/commands/log.ts, SPEC-002 G2).
|
|
83
|
+
- **G4**: Given `extractPrompt(["explain","goroutines"])`, When run, Then the
|
|
84
|
+
joined prompt text is returned (src/runner/inline.ts:8-14).
|
|
85
|
+
- **G5**: Given a capture with a project marker and adapter metadata, When
|
|
86
|
+
`createCaptureRecord` builds it, Then it includes detected project/language,
|
|
87
|
+
source metadata, and common fields (src/capture/index.ts).
|
|
88
|
+
|
|
89
|
+
## 8. Key implementation map
|
|
90
|
+
| Concern | File(s) |
|
|
91
|
+
|---|---|
|
|
92
|
+
| PTY + ESC state machine | src/pty/wrapper.ts:23-169 |
|
|
93
|
+
| Inline runner + binary resolution | src/runner/inline.ts:8-60 |
|
|
94
|
+
| Direct log | src/commands/log.ts:9-85 |
|
|
95
|
+
| Normalized capture record | src/capture/index.ts |
|
|
96
|
+
| Hooks | hooks/claude/ph-hook.sh, hooks/gemini/ph-hook.sh, hooks/codex/ph-hook.sh |
|
|
97
|
+
| OpenCode plugin | hooks/opencode/ph-plugin.ts, install.sh |
|
|
98
|
+
|
|
99
|
+
## 9. Open questions / discrepancies
|
|
100
|
+
- `cleanLine` (wrapper.ts:7-10) trims whitespace — prompts containing leading/
|
|
101
|
+
trailing spaces or multi-line pastes are collapsed; paste detection (bracketed
|
|
102
|
+
paste) is not implemented.
|
|
103
|
+
- The PTY path is not exercised by any test (requires a PTY); the state machine
|
|
104
|
+
logic is pure enough to unit-test with byte arrays.
|
|
105
|
+
|
|
106
|
+
## 10. Related
|
|
107
|
+
- SPEC-001 (wrapper-mode dispatch), SPEC-002 (insert), SPEC-006 (background
|
|
108
|
+
analysis), SPEC-010 (background push). The normalized capture record is
|
|
109
|
+
covered by `src/capture/__tests__/record.test.ts`; PTY/integration coverage
|
|
110
|
+
remains open (SPEC-ISSUES-007).
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# SPEC-004: Filter Pipeline & Metadata
|
|
2
|
+
|
|
3
|
+
- **ID**: SPEC-004
|
|
4
|
+
- **Cluster**: Core
|
|
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 noise out of the history: `FilterPipeline` rejects too-short, binary,
|
|
11
|
+
trivial and duplicate prompts before they reach the DB, and enriches entries
|
|
12
|
+
with project/language/topic metadata at capture time.
|
|
13
|
+
|
|
14
|
+
## 2. Scope
|
|
15
|
+
- **In scope**: FilterPipeline checks, FilterResult, metadata enrichment
|
|
16
|
+
(detectProject, detectLanguage, extractTopic), filter config knobs.
|
|
17
|
+
- **Out of scope**: LLM analysis (SPEC-006), retention (SPEC-009).
|
|
18
|
+
- **Entry points**: FilterPipeline used by `ph log` / importers; metadata
|
|
19
|
+
helpers used across commands.
|
|
20
|
+
|
|
21
|
+
## 3. Data Model
|
|
22
|
+
- `FilterReason` union + `FilterResult` (src/filter/index.ts:4-11): `{ pass,
|
|
23
|
+
reason? }`.
|
|
24
|
+
- `FilterOptions` (src/filter/index.ts:17): minLength, minRelevance.
|
|
25
|
+
- Metadata JSON shape `PromptMetadata` (src/types.ts:14-27).
|
|
26
|
+
|
|
27
|
+
## 4. Flows
|
|
28
|
+
|
|
29
|
+
### 4.1 Filtering (src/filter/index.ts:34)
|
|
30
|
+
`FilterPipeline.check(text)` runs 4 checks (AGENTS.md + filter/index.ts):
|
|
31
|
+
1. too short (< `filterMinLength`, default 15)
|
|
32
|
+
2. non-printable characters
|
|
33
|
+
3. trivial pattern (e.g. single words, "ok", "yes")
|
|
34
|
+
4. exact duplicate (against recent history)
|
|
35
|
+
Optional relevance check when `minRelevance > 0`.
|
|
36
|
+
|
|
37
|
+
### 4.2 Enrichment
|
|
38
|
+
- `detectProject(startDir)` walks up looking for `.git`/`go.mod`/`package.json`/
|
|
39
|
+
`Cargo.toml` (src/runner/project.ts:25).
|
|
40
|
+
- `detectLanguage(rootDir)` (src/runner/project.ts:40).
|
|
41
|
+
- `extractTopic(prompt)` (src/utils/extractTopic.ts:5) — title-like extraction.
|
|
42
|
+
- Assembled into `metadata` JSON before insert (e.g. src/commands/log.ts:50-58).
|
|
43
|
+
|
|
44
|
+
## 5. Invariants & Business rules
|
|
45
|
+
- Filtered prompts never reach the DB (importers and log both call the pipeline).
|
|
46
|
+
- Metadata merge later (SPEC-006) preserves manual fields unless `force`.
|
|
47
|
+
- `filterMinRelevance` default 3 — but relevance only exists after analysis, so
|
|
48
|
+
the check only bites on re-analysis paths.
|
|
49
|
+
|
|
50
|
+
## 6. UI / UX surface
|
|
51
|
+
Filter counts/reasons are logged at capture time; configurable via
|
|
52
|
+
`ph config set filter-min-length <n>` etc.
|
|
53
|
+
|
|
54
|
+
## 7. Acceptance criteria (Given/When/Then)
|
|
55
|
+
- **G1**: Given a 5-char prompt with `minLength: 15`, When `FilterPipeline.check`
|
|
56
|
+
runs, Then `{ pass: false, reason: 'too short' }` (src/filter/index.ts:34+).
|
|
57
|
+
- **G2**: Given a prompt with control bytes, When `check` runs, Then it is
|
|
58
|
+
rejected as non-printable (src/filter/index.ts).
|
|
59
|
+
- **G3**: Given a prompt equal to an existing recent entry, When `check` runs,
|
|
60
|
+
Then it is rejected as duplicate (src/filter/index.ts).
|
|
61
|
+
- **G4**: Given a dir containing `package.json`, When `detectProject` runs, Then
|
|
62
|
+
the project name is derived from the package (src/runner/project.ts:25-39).
|
|
63
|
+
- **G5**: Given `extractTopic("fix the login bug")`, When run, Then a short
|
|
64
|
+
topic string is returned (src/utils/extractTopic.ts:5).
|
|
65
|
+
|
|
66
|
+
## 8. Key implementation map
|
|
67
|
+
| Concern | File(s) |
|
|
68
|
+
|---|---|
|
|
69
|
+
| FilterPipeline + reasons | src/filter/index.ts:4-80 |
|
|
70
|
+
| Project/language detection | src/runner/project.ts:25-60 |
|
|
71
|
+
| Topic extraction | src/utils/extractTopic.ts:5 |
|
|
72
|
+
| Capture-time assembly | src/commands/log.ts:50-58 |
|
|
73
|
+
|
|
74
|
+
## 9. Open questions / discrepancies
|
|
75
|
+
- The exact trivial-pattern list lives inline in filter/index.ts; verify the
|
|
76
|
+
regexes when wiring GWT tests.
|
|
77
|
+
- Duplicate check scope (recent history window vs all) is implementation detail
|
|
78
|
+
in filter/index.ts — confirm before writing the G3 test.
|
|
79
|
+
|
|
80
|
+
## 10. Related
|
|
81
|
+
- SPEC-002 (insert), SPEC-003 (capture), SPEC-013 (importers reuse the filter),
|
|
82
|
+
SPEC-015 (config keys). No tests exist (SPEC-ISSUES-007).
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# SPEC-005: Search (FTS + Filters + Semantic)
|
|
2
|
+
|
|
3
|
+
- **ID**: SPEC-005
|
|
4
|
+
- **Cluster**: Search
|
|
5
|
+
- **Status**: Implemented (baseline)
|
|
6
|
+
- **Date**: 2026-08-08
|
|
7
|
+
- **Source**: reverse-engineered from code (files listed in §8)
|
|
8
|
+
|
|
9
|
+
## 1. Purpose
|
|
10
|
+
Find prompts by full-text (FTS5 over prompt+response), by metadata filters
|
|
11
|
+
(tool/project/language/role/tag/starred/quality/relevance/date range), or by
|
|
12
|
+
semantic similarity (sqlite-vec, SPEC-008). Backs `ph search`, the TUI text
|
|
13
|
+
filter, the MCP `search_prompts*` tools and the HTTP `/api/prompts/search`.
|
|
14
|
+
|
|
15
|
+
## 2. Scope
|
|
16
|
+
- **In scope**: `PhDB.search(opts)` dual-path (FTS vs scan), filter building,
|
|
17
|
+
semantic search, `ph search` command output.
|
|
18
|
+
- **Out of scope**: embeddings generation (SPEC-008), archive search (SPEC-009),
|
|
19
|
+
TUI filter panel (SPEC-012).
|
|
20
|
+
- **Entry points**: `ph search [--semantic] [--archive] [flags]`, TUI `/`, MCP tools, HTTP endpoints.
|
|
21
|
+
|
|
22
|
+
## 3. Data Model
|
|
23
|
+
- `SearchOptions` (src/types.ts:54-68): query, tool, project, language, role,
|
|
24
|
+
tag, starred, minQuality, minRelevance, since, until, limit, semantic.
|
|
25
|
+
- Results: `PromptEntry[]`.
|
|
26
|
+
|
|
27
|
+
## 4. Flows
|
|
28
|
+
|
|
29
|
+
### 4.1 Dual-path query (src/db/index.ts:243-330)
|
|
30
|
+
1. Build identical filters for two paths: FTS (JOIN with alias `p.`) and scan
|
|
31
|
+
(direct `prompts`). Filters use `json_extract` on metadata for
|
|
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).
|
|
35
|
+
3. Without text → scan path: `SELECT * FROM prompts WHERE … ORDER BY timestamp
|
|
36
|
+
DESC LIMIT ?` (db/index.ts:322-330).
|
|
37
|
+
4. Date range: `since`/`until` become timestamp comparisons.
|
|
38
|
+
|
|
39
|
+
### 4.2 Semantic search (src/db/index.ts)
|
|
40
|
+
`searchSemantic(queryVector, limit, project?)` — vec0 KNN on `vec_embeddings`
|
|
41
|
+
joined back to prompts. When `project` is supplied, matching is restricted to
|
|
42
|
+
that project before the final result limit. Requires embeddings (SPEC-008).
|
|
43
|
+
Because sqlite-vec applies `k` before ordinary metadata predicates, scoped
|
|
44
|
+
queries currently examine all stored vectors before filtering and limiting.
|
|
45
|
+
|
|
46
|
+
### 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).
|
|
49
|
+
|
|
50
|
+
## 5. Invariants & Business rules
|
|
51
|
+
- FTS path is used ONLY when a text query exists; otherwise full scan + filters.
|
|
52
|
+
- `tag` filter is a JSON-array LIKE `%"tag"%` — substring match on the serialized
|
|
53
|
+
array, not a JSON array membership check (db/index.ts:276-282).
|
|
54
|
+
- `limit` defaults to 50 in the CLI; results ordered timestamp DESC.
|
|
55
|
+
- Project-scoped semantic search returns the nearest matching prompts within
|
|
56
|
+
that project, even when closer prompts from other projects exist.
|
|
57
|
+
|
|
58
|
+
## 6. UI / UX surface
|
|
59
|
+
`ph search` output: `#id tool date [tags] [exit:N]` + truncated prompt
|
|
60
|
+
(printResults); `--full` shows the complete text.
|
|
61
|
+
|
|
62
|
+
## 7. Acceptance criteria (Given/When/Then)
|
|
63
|
+
- **G1**: Given prompts with text "goroutines" and "react", When `search({query:
|
|
64
|
+
"goroutines"})` runs, Then only the goroutines entry is returned via the FTS
|
|
65
|
+
path (src/db/index.ts:300-320).
|
|
66
|
+
- **G2**: Given prompts from tools claude/gemini, When `search({tool: "gemini"})`
|
|
67
|
+
runs (no query), Then only gemini entries are returned via the scan path
|
|
68
|
+
(src/db/index.ts:322-330).
|
|
69
|
+
- **G3**: Given entries with metadata `project: "ph"`, When
|
|
70
|
+
`search({project: "ph"})` runs, Then only those entries match via
|
|
71
|
+
`json_extract` (src/db/index.ts:258-263).
|
|
72
|
+
- **G4**: Given a tag `auth` stored in `tags: ["auth","jwt"]`, When
|
|
73
|
+
`search({tag: "auth"})` runs, Then the entry matches via the LIKE filter
|
|
74
|
+
(src/db/index.ts:276-282).
|
|
75
|
+
- **G5**: Given `since: 2026-01-01`, When `search` runs, Then entries older than
|
|
76
|
+
the date are excluded (src/db/index.ts:since handling).
|
|
77
|
+
- **G6**: Given an unrelated project has the nearest vector, When
|
|
78
|
+
`searchSemantic(query, 1, "go-play")` runs, Then it returns the nearest
|
|
79
|
+
vector belonging to `go-play` (src/db/index.ts, project scope before limit).
|
|
80
|
+
|
|
81
|
+
## 8. Key implementation map
|
|
82
|
+
| Concern | File(s) |
|
|
83
|
+
|---|---|
|
|
84
|
+
| Dual-path search | src/db/index.ts:243-330 |
|
|
85
|
+
| Semantic KNN | src/db/index.ts (`searchSemantic`) |
|
|
86
|
+
| CLI output | src/commands/search.ts, src/display/print.ts:33-60 |
|
|
87
|
+
| SearchOptions | src/types.ts:54-68 |
|
|
88
|
+
|
|
89
|
+
## 9. Open questions / discrepancies
|
|
90
|
+
- `tag` filter uses LIKE on the JSON string — matches `"auth2"` when filtering
|
|
91
|
+
`auth` (substring). JSON array membership would be stricter.
|
|
92
|
+
- FTS path columns need the `p.` prefix; a new filter added to only one path
|
|
93
|
+
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.
|
|
98
|
+
|
|
99
|
+
## 10. Related
|
|
100
|
+
- SPEC-002 (primitives), SPEC-008 (embeddings), SPEC-009 (archive),
|
|
101
|
+
SPEC-011 (MCP), SPEC-010 (HTTP). Vitest coverage: `src/db/__tests__/phdb.test.ts`
|
|
102
|
+
covers G1-G6, including project-scoped nearest-neighbor behavior.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# SPEC-006: Analysis Pipeline (LLM)
|
|
2
|
+
|
|
3
|
+
- **ID**: SPEC-006
|
|
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
|
+
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).
|
|
13
|
+
|
|
14
|
+
## 2. Scope
|
|
15
|
+
- **In scope**: ANALYSIS_PROMPT, parseAnalysisResponse, analyzePrompt, mergeMetadata,
|
|
16
|
+
analyzeAll, background analysis spawn, reusability scoring.
|
|
17
|
+
- **Out of scope**: provider details (src/ai/*), memory persistence (SPEC-007).
|
|
18
|
+
- **Entry points**: `ph analyze`, `ph analyze-reusability`, `_bg-analyze`,
|
|
19
|
+
auto-analysis after `ph log` when `backgroundAnalysis` is enabled.
|
|
20
|
+
|
|
21
|
+
## 3. Data Model
|
|
22
|
+
- `AnalysisResult` (src/analyzer/index.ts:8-18): project, language, role, tags,
|
|
23
|
+
relevance (0-10), quality (0-10), summary, key_insights, technical_decisions.
|
|
24
|
+
- `PromptMetadata` (src/types.ts:14-27) — analysis merged into it.
|
|
25
|
+
- `LLMProvider` interface (src/ai/provider.ts:5-12); factory `getProvider(cfg)`
|
|
26
|
+
(:14) returns Ollama or Gemini.
|
|
27
|
+
|
|
28
|
+
## 4. Flows
|
|
29
|
+
|
|
30
|
+
### 4.1 Prompt (src/analyzer/index.ts:20-50)
|
|
31
|
+
`ANALYSIS_PROMPT(text)` demands a single JSON object with the exact field
|
|
32
|
+
contract; input truncated to 2000 chars (:49). No markdown fences allowed.
|
|
33
|
+
|
|
34
|
+
### 4.2 Parsing (src/analyzer/index.ts:56-120)
|
|
35
|
+
`parseAnalysisResponse` strips fences, extracts the first `{…}` block
|
|
36
|
+
(indexOf/lastIndexOf), JSON.parses; on failure returns `{}` (never throws).
|
|
37
|
+
|
|
38
|
+
### 4.3 Analyze + merge (src/analyzer/index.ts:123-183)
|
|
39
|
+
`analyzePrompt(text, provider)` → `parseAnalysisResponse(await provider.analyze…)`.
|
|
40
|
+
`mergeMetadata(existing, analysis, force?)` merges fields, preserving manual
|
|
41
|
+
metadata unless `force=true` (:136-183).
|
|
42
|
+
|
|
43
|
+
### 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/
|
|
46
|
+
--dry-run`.
|
|
47
|
+
|
|
48
|
+
### 4.5 Background (src/background/analyzer.ts:7)
|
|
49
|
+
`spawnBackgroundAnalysis(id, dbPath)` spawns a detached `_bg-analyze` child —
|
|
50
|
+
never blocks the capture path. Enabled via `ph config set background-analysis true`.
|
|
51
|
+
|
|
52
|
+
### 4.6 Reusability (src/analyzer/reusability.ts)
|
|
53
|
+
`ph analyze-reusability` scores prompts for reuse; `cleanup-reusability`
|
|
54
|
+
deletes low-score ones (SPEC-009 cross-ref).
|
|
55
|
+
|
|
56
|
+
## 5. Invariants & Business rules
|
|
57
|
+
- Parsing NEVER throws — worst case is an empty AnalysisResult.
|
|
58
|
+
- Analysis is additive: manual fields survive unless `force`.
|
|
59
|
+
- Background analysis is detached: capture latency is never affected.
|
|
60
|
+
- Input truncated to 2000 chars before the LLM.
|
|
61
|
+
|
|
62
|
+
## 6. UI / UX surface
|
|
63
|
+
`ph analyze` progress output; TUI shows `●` (analyzed) vs `○` (pending) in
|
|
64
|
+
ListEntry (SPEC-012).
|
|
65
|
+
|
|
66
|
+
## 7. Acceptance criteria (Given/When/Then)
|
|
67
|
+
- **G1**: Given an LLM response with fenced JSON, When `parseAnalysisResponse`
|
|
68
|
+
runs, Then the JSON is extracted and parsed (src/analyzer/index.ts:56-65).
|
|
69
|
+
- **G2**: Given a non-JSON response, When `parseAnalysisResponse` runs, Then
|
|
70
|
+
`{}` is returned without throwing (src/analyzer/index.ts:61-70).
|
|
71
|
+
- **G3**: Given existing metadata with a manual role and analysis with a
|
|
72
|
+
different role, When `mergeMetadata(existing, analysis)` runs without force,
|
|
73
|
+
Then the manual role is preserved (src/analyzer/index.ts:136-183).
|
|
74
|
+
- **G4**: Given `force=true`, When `mergeMetadata` runs, Then the analysis
|
|
75
|
+
values win (src/analyzer/index.ts:136-183).
|
|
76
|
+
- **G5**: Given `backgroundAnalysis: true`, When `ph log` runs, Then
|
|
77
|
+
`spawnBackgroundAnalysis` is invoked with the new prompt id and dbPath
|
|
78
|
+
(src/commands/log.ts:61-63, src/background/analyzer.ts:7).
|
|
79
|
+
|
|
80
|
+
## 8. Key implementation map
|
|
81
|
+
| Concern | File(s) |
|
|
82
|
+
|---|---|
|
|
83
|
+
| Prompt + parse + merge + batch | src/analyzer/index.ts:20-275 |
|
|
84
|
+
| Providers + factory | src/ai/provider.ts:5-20, ollama.ts:8, gemini.ts:36 |
|
|
85
|
+
| Background spawn | src/background/analyzer.ts:7 |
|
|
86
|
+
| Reusability | src/analyzer/reusability.ts |
|
|
87
|
+
|
|
88
|
+
## 9. Open questions / discrepancies
|
|
89
|
+
- `parseAnalysisResponse` uses first/last brace — nested braces or multiple JSON
|
|
90
|
+
blocks break extraction (same fragility class as lens's old regex plans).
|
|
91
|
+
- `_bg-analyze` passes only (id, dbPath): the child reloads config; a config
|
|
92
|
+
change between capture and analysis silently changes the model used.
|
|
93
|
+
- Reusability scoring lives in analyzer/reusability.ts — verify the formula when
|
|
94
|
+
wiring tests.
|
|
95
|
+
|
|
96
|
+
## 10. Related
|
|
97
|
+
- SPEC-002 (metadata update), SPEC-007 (memories), SPEC-015 (config),
|
|
98
|
+
SPEC-004 (filter runs before analysis). No tests (SPEC-ISSUES-007).
|