@gi4nks/ph 1.4.0 → 1.6.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 (34) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +147 -58
  3. package/dist/cli.js +2292 -1021
  4. package/dist/cli.js.map +1 -1
  5. package/docs/evals/context-retrieval-golden.json +26 -0
  6. package/docs/evals/context-retrieval.md +27 -0
  7. package/docs/hooks.md +13 -7
  8. package/docs/ph-analysis.html +145 -0
  9. package/docs/ph-manual.md +352 -231
  10. package/docs/specs/SPEC-001-architecture-cli.md +82 -0
  11. package/docs/specs/SPEC-002-database-layer.md +105 -0
  12. package/docs/specs/SPEC-003-capture-modes.md +110 -0
  13. package/docs/specs/SPEC-004-filter-metadata.md +82 -0
  14. package/docs/specs/SPEC-005-search.md +102 -0
  15. package/docs/specs/SPEC-006-analysis-pipeline.md +98 -0
  16. package/docs/specs/SPEC-007-memory-summaries.md +96 -0
  17. package/docs/specs/SPEC-008-embeddings.md +90 -0
  18. package/docs/specs/SPEC-009-retention-archive.md +91 -0
  19. package/docs/specs/SPEC-010-remote-sync.md +106 -0
  20. package/docs/specs/SPEC-011-mcp-server.md +94 -0
  21. package/docs/specs/SPEC-012-tui-browser.md +98 -0
  22. package/docs/specs/SPEC-013-importers.md +109 -0
  23. package/docs/specs/SPEC-014-analytics-commands.md +109 -0
  24. package/docs/specs/SPEC-015-config-env.md +85 -0
  25. package/docs/specs/SPEC-INDEX.md +35 -0
  26. package/docs/specs/SPEC-ISSUES.md +122 -0
  27. package/docs/specs/_TEMPLATE.md +65 -0
  28. package/docs/superpowers/plans/2026-06-12-ph-evolution-phase1.md +466 -0
  29. package/docs/superpowers/plans/2026-06-12-ph-evolution-phase2.md +351 -0
  30. package/docs/superpowers/specs/2026-06-12-ph-evolution-design.md +173 -0
  31. package/hooks/codex/README.md +54 -0
  32. package/hooks/codex/ph-hook.sh +14 -0
  33. package/hooks/opencode/ph-plugin.ts +60 -57
  34. package/package.json +13 -12
@@ -14,11 +14,13 @@ const sessions = new Map<string, {
14
14
  }>();
15
15
 
16
16
  function textFromParts(parts: { type?: string; text?: string }[]): string {
17
- return parts
18
- .filter(p => p.type === 'text')
19
- .map(p => p.text || '')
20
- .join('')
21
- .trim();
17
+ try {
18
+ return parts
19
+ .filter(p => p.type === 'text')
20
+ .map(p => p.text || '')
21
+ .join('')
22
+ .trim();
23
+ } catch { return ''; }
22
24
  }
23
25
 
24
26
  async function callPhLog(
@@ -27,75 +29,76 @@ async function callPhLog(
27
29
  response: string,
28
30
  workdir: string,
29
31
  ): Promise<void> {
30
- if (!prompt.text || !response) return;
32
+ try {
33
+ if (!prompt.text || !response) return;
31
34
 
32
- const payload: Record<string, unknown> = {
33
- tool: 'opencode',
34
- prompt: prompt.text,
35
- response,
36
- workdir,
37
- };
35
+ const payload: Record<string, unknown> = {
36
+ tool: 'opencode',
37
+ prompt: prompt.text,
38
+ response,
39
+ workdir,
40
+ };
38
41
 
39
- const meta: Record<string, unknown> = { source: 'plugin' };
40
- if (prompt.agent) meta.agent = prompt.agent;
41
- if (prompt.model) meta.model = prompt.model;
42
- payload.metadata = JSON.stringify(meta);
42
+ const meta: Record<string, unknown> = { source: 'plugin' };
43
+ if (prompt.agent) meta.agent = prompt.agent;
44
+ if (prompt.model) meta.model = prompt.model;
45
+ payload.metadata = JSON.stringify(meta);
43
46
 
44
- const json = JSON.stringify(payload);
45
- const _ = $`echo ${json} | ph log > /dev/null 2>&1`;
46
- _.then(() => {}).catch(() => {});
47
+ const json = JSON.stringify(payload);
48
+ $`echo ${json} | ph log > /dev/null 2>&1`.catch(() => {});
49
+ } catch { /* silent */ }
47
50
  }
48
51
 
49
52
  export const PhOpenCodePlugin: Plugin = async ({ directory, $ }) => {
50
53
  return {
51
54
  "chat.message": async (input, output) => {
52
- const { sessionID, agent, model } = input;
53
- const { message, parts } = output;
54
- const role = (message as Record<string, unknown>).role;
55
+ try {
56
+ const { sessionID, agent, model } = input;
57
+ const { message, parts } = output;
58
+ const role = (message as Record<string, unknown>).role;
55
59
 
56
- if (role === 'user') {
57
- const existing = sessions.get(sessionID);
58
- const prompt = existing?.prompt;
59
- const response = existing?.response;
60
+ if (role === 'user') {
61
+ const existing = sessions.get(sessionID);
62
+ const prompt = existing?.prompt;
63
+ const response = existing?.response;
60
64
 
61
- // Log previous turn if we have both parts
62
- if (prompt && response) {
63
- await callPhLog($, prompt, response, directory);
64
- }
65
+ if (prompt && response) {
66
+ await callPhLog($, prompt, response, directory);
67
+ }
65
68
 
66
- // Store new prompt, reset response
67
- const text = textFromParts(parts);
68
- if (text) {
69
- sessions.set(sessionID, {
70
- prompt: {
71
- text,
72
- timestamp: new Date().toISOString(),
73
- agent,
74
- model: model ? `${model.providerID}/${model.modelID}` : undefined,
75
- },
76
- response: '',
77
- });
78
- }
79
- } else if (role === 'assistant') {
80
- const existing = sessions.get(sessionID);
81
- if (!existing?.prompt) return;
69
+ const text = textFromParts(parts);
70
+ if (text) {
71
+ sessions.set(sessionID, {
72
+ prompt: {
73
+ text,
74
+ timestamp: new Date().toISOString(),
75
+ agent,
76
+ model: model ? `${model.providerID}/${model.modelID}` : undefined,
77
+ },
78
+ response: '',
79
+ });
80
+ }
81
+ } else if (role === 'assistant') {
82
+ const existing = sessions.get(sessionID);
83
+ if (!existing?.prompt) return;
82
84
 
83
- // Snapshot whatever text is available (non-streaming or initial frame)
84
- const text = textFromParts(parts);
85
- if (text) {
86
- sessions.set(sessionID, { ...existing, response: text });
85
+ const text = textFromParts(parts);
86
+ if (text) {
87
+ sessions.set(sessionID, { ...existing, response: text });
88
+ }
87
89
  }
88
- }
90
+ } catch { /* silent */ }
89
91
  },
90
92
 
91
93
  "experimental.text.complete": async (input, output) => {
92
- const session = sessions.get(input.sessionID);
93
- if (!session?.prompt) return;
94
+ try {
95
+ const session = sessions.get(input.sessionID);
96
+ if (!session?.prompt) return;
94
97
 
95
- // Replace with complete text (handles streaming: this fires after streaming ends)
96
- if (output.text) {
97
- sessions.set(input.sessionID, { ...session, response: output.text });
98
- }
98
+ if (output.text) {
99
+ sessions.set(input.sessionID, { ...session, response: output.text });
100
+ }
101
+ } catch { /* silent */ }
99
102
  },
100
103
  };
101
104
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gi4nks/ph",
3
- "version": "1.4.0",
3
+ "version": "1.6.0",
4
4
  "description": "Transparent CLI wrapper that captures and indexes AI prompts",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -51,36 +51,37 @@
51
51
  "dev": "tsx src/cli.ts",
52
52
  "start": "node dist/cli.js",
53
53
  "test": "vitest run",
54
- "lint": "eslint src"
54
+ "lint": "eslint src",
55
+ "eval:retrieval": "tsx scripts/eval-context-retrieval.ts"
55
56
  },
56
57
  "dependencies": {
57
58
  "@gi4nks/ink": "^1.5.0",
58
59
  "@lydell/node-pty": "^1.2.0-beta.3",
59
60
  "@modelcontextprotocol/sdk": "^1.29.0",
60
61
  "better-sqlite3": "^12.8.0",
61
- "ink": "^6.8.0",
62
- "react": "^19.2.4",
62
+ "ink": "^7.0.5",
63
+ "react": "^19.2.7",
63
64
  "sqlite-vec": "^0.1.9",
64
65
  "zod": "^4.3.6"
65
66
  },
66
67
  "devDependencies": {
68
+ "@eslint/js": "^9.39.5",
67
69
  "@semantic-release/changelog": "^6.0.3",
68
70
  "@semantic-release/git": "^10.0.1",
69
71
  "@semantic-release/github": "^12.0.8",
70
72
  "@semantic-release/npm": "^13.1.5",
71
73
  "@types/better-sqlite3": "^7.6.13",
72
- "@types/node": "^25.5.0",
73
- "@types/react": "^19.2.14",
74
- "@typescript-eslint/eslint-plugin": "^8.57.2",
75
- "@typescript-eslint/parser": "^8.57.2",
76
- "eslint": "^9.39.4",
74
+ "@types/node": "^25.9.3",
75
+ "@types/react": "^19.2.7",
76
+ "@typescript-eslint/eslint-plugin": "^8.58.2",
77
+ "@typescript-eslint/parser": "^8.58.2",
78
+ "eslint": "^9.39.5",
77
79
  "eslint-plugin-react": "^7.37.5",
78
80
  "eslint-plugin-react-hooks": "^7.0.1",
79
81
  "semantic-release": "^25.0.3",
80
- "standard-version": "^9.5.0",
81
82
  "tsup": "^8.5.1",
82
83
  "tsx": "^4.21.0",
83
- "typescript": "^5.9.3",
84
- "vitest": "^4.1.0"
84
+ "typescript": "^6.0.3",
85
+ "vitest": "^4.1.8"
85
86
  }
86
87
  }