@oh-my-pi/pi-coding-agent 14.6.1 → 14.6.3

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 (63) hide show
  1. package/CHANGELOG.md +82 -1
  2. package/README.md +21 -0
  3. package/package.json +23 -7
  4. package/src/cli/grievances-cli.ts +89 -4
  5. package/src/commands/grievances.ts +33 -7
  6. package/src/config/prompt-templates.ts +14 -7
  7. package/src/config/settings-schema.ts +595 -100
  8. package/src/config/settings.ts +46 -0
  9. package/src/discovery/helpers.ts +13 -6
  10. package/src/edit/index.ts +3 -3
  11. package/src/edit/line-hash.ts +73 -25
  12. package/src/edit/modes/hashline.lark +10 -3
  13. package/src/edit/modes/hashline.ts +104 -38
  14. package/src/edit/renderer.ts +3 -3
  15. package/src/hindsight/backend.ts +444 -0
  16. package/src/hindsight/bank.ts +131 -0
  17. package/src/hindsight/client.ts +445 -0
  18. package/src/hindsight/config.ts +165 -0
  19. package/src/hindsight/content.ts +205 -0
  20. package/src/hindsight/index.ts +6 -0
  21. package/src/hindsight/retain-queue.ts +166 -0
  22. package/src/hindsight/transcript.ts +71 -0
  23. package/src/main.ts +7 -10
  24. package/src/memories/index.ts +1 -1
  25. package/src/memory-backend/index.ts +4 -0
  26. package/src/memory-backend/local-backend.ts +30 -0
  27. package/src/memory-backend/off-backend.ts +16 -0
  28. package/src/memory-backend/resolve.ts +24 -0
  29. package/src/memory-backend/types.ts +69 -0
  30. package/src/modes/components/settings-defs.ts +50 -451
  31. package/src/modes/components/settings-selector.ts +4 -2
  32. package/src/modes/components/status-line/presets.ts +1 -1
  33. package/src/modes/components/status-line.ts +4 -1
  34. package/src/modes/controllers/command-controller.ts +6 -5
  35. package/src/modes/controllers/event-controller.ts +12 -0
  36. package/src/modes/controllers/mcp-command-controller.ts +23 -0
  37. package/src/modes/controllers/selector-controller.ts +10 -12
  38. package/src/modes/interactive-mode.ts +3 -2
  39. package/src/modes/theme/theme.ts +4 -0
  40. package/src/prompts/tools/github.md +3 -0
  41. package/src/prompts/tools/hashline.md +20 -16
  42. package/src/prompts/tools/read.md +10 -6
  43. package/src/prompts/tools/recall.md +5 -0
  44. package/src/prompts/tools/reflect.md +5 -0
  45. package/src/prompts/tools/retain.md +5 -0
  46. package/src/prompts/tools/search.md +1 -1
  47. package/src/sdk.ts +12 -9
  48. package/src/session/agent-session.ts +75 -3
  49. package/src/slash-commands/builtin-registry.ts +2 -12
  50. package/src/ssh/connection-manager.ts +1 -1
  51. package/src/tools/ast-edit.ts +14 -5
  52. package/src/tools/ast-grep.ts +12 -3
  53. package/src/tools/find.ts +47 -7
  54. package/src/tools/gh-renderer.ts +10 -1
  55. package/src/tools/gh.ts +233 -5
  56. package/src/tools/hindsight-recall.ts +70 -0
  57. package/src/tools/hindsight-reflect.ts +57 -0
  58. package/src/tools/hindsight-retain.ts +63 -0
  59. package/src/tools/index.ts +17 -0
  60. package/src/tools/output-meta.ts +1 -0
  61. package/src/tools/path-utils.ts +55 -0
  62. package/src/tools/read.ts +1 -1
  63. package/src/tools/search.ts +45 -8
package/CHANGELOG.md CHANGED
@@ -2,7 +2,70 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [14.6.3] - 2026-05-03
6
+
7
+ ### Breaking Changes
8
+
9
+ - Renamed hashline separator configuration from `PI_HASHLINE_SEP` to `PI_HL_SEP` and changed the default payload separator from `\\` to `>`
10
+
11
+ ### Added
12
+
13
+ - Added inline hashline edit syntax so `< ANCHOR${sep}TEXT` prepended text to an anchored line and `+ ANCHOR${sep}TEXT` appended text to it without requiring a multi-line payload block
14
+ - Added a `memory.backend` setting (off, local, hindsight) under a new Memory settings tab to control which memory subsystem is active
15
+ - Added Hindsight memory settings (`hindsight.*`) for API connection, bank identification, and recall/retain policy
16
+ - Added `retain`, `recall`, and `reflect` tools for direct long-term memory search, retention, and reflection when using the Hindsight backend
17
+ - Added `hindsight.scoping` setting (`global`, `per-project`, `per-project-tagged`) that controls whether memories are shared across projects, isolated per cwd, or tagged so global + project memories merge on recall (default: `per-project-tagged`)
18
+ - Added `search_code`, `search_commits`, and `search_repos` ops to the `github` tool so the search surface mirrors `gh search`'s subcommands
19
+
20
+ ### Changed
21
+
22
+ - Changed `hindsight` tool retains to enqueue memory writes and return `Memory queued.` immediately instead of waiting on a network request
23
+ - Changed `hindsight` tool memory writes to use automatic background batching (up to 16 items or 5 seconds) so tool calls do not block
24
+ - Changed failed `hindsight` queue flushes to be surfaced as UI warning notices rather than failing the foreground `hindsight` tool call
25
+ - Changed hashline read/search previews and diff output to keep `|` as the anchor-to-text separator while using the separate configured edit payload separator
26
+ - Mapped invalid `hindsight.scoping` settings back to the default `per-project-tagged` behavior with a warning
27
+ - Changed `/memory view`, `/memory clear`, and `/memory enqueue` to route through the selected memory backend instead of being hardcoded to local memories
28
+ - Changed compaction context assembly to include backend-provided recall context when available
29
+ - Updated multi-path `search`, `find`, `ast-edit`, and `ast-grep` calls to skip missing base paths, returning matches from remaining paths and reporting skipped paths in output
30
+ - Replaced `hindsight.dynamicBankId` (boolean) with the explicit `hindsight.scoping` enum; legacy values are migrated automatically (`dynamicBankId=true` → `scoping="per-project"`)
31
+ - Changed `search_repos` to run as a global repository search using query qualifiers without applying the `repo` filter
32
+
33
+ ### Deprecated
34
+
35
+ - Set explicit `hindsight.scoping` now takes precedence over legacy `hindsight.dynamicBankId` when migrating old settings
36
+
37
+ ### Removed
38
+
39
+ - Removed legacy `hindsight.dynamicBankId` and `hindsight.agentName` fields from the active settings model
40
+ - Removed `hindsight.agentName` and the `HINDSIGHT_AGENT_NAME` / `HINDSIGHT_CHANNEL_ID` / `HINDSIGHT_USER_ID` env vars; the legacy `agent::project::channel::user` bank tuple is gone. A user-set `agentName` is migrated onto `hindsight.bankId`.
41
+
42
+ ### Fixed
43
+
44
+ - Fixed pending `hindsight` queued memory writes to flush on agent end, clear, and enqueue operations so tool-invoked facts are not dropped when sessions transition
45
+ - Fixed retained memory writes from the `hindsight` tool to include session context and tags consistently in background batches
46
+ - Fixed inline hashline modify operations to fail fast when combined with a delete or replace on the same target line
47
+ - Fixed hashline parsing of payload blocks to handle a shared extra leading symbol prefix (such as markdown `>>`) on all payload lines by stripping it as an auto-correction instead of rejecting the edit
48
+ - Forwarded project scoping tags to `hindsight` retain, recall, and reflect operations so manual memory commands honor the active tagging mode
49
+ - Fixed legacy migrations by mapping existing `memories.enabled` values to `memory.backend` on load to preserve prior enable/disable behavior
50
+ - Fixed memory retention so recalled `<memories>` blocks and legacy `<hindsight_memories>` / `<relevant_memories>` blocks are stripped before storing transcripts and do not feed back as new memory
51
+ - Fixed `search_code` output to include each match path, repository, shortened SHA, and a one-line matching fragment
52
+ - Fixed `search_commits` output to show shortened SHAs with commit message first lines
53
+ - Fixed `search_repos` output formatting to return repository summaries including language, stars, forks, issues, visibility, and key status fields
54
+
55
+ ## [14.6.2] - 2026-05-03
56
+
57
+ ### Added
58
+
59
+ - Added `statusLine.sessionAccent` to disable session-name accent coloring for the editor border and status line gap ([#918](https://github.com/can1357/oh-my-pi/issues/918))
60
+
61
+ ### Fixed
62
+
63
+ - Disabled repeated OSC 11 background-color polling under WSL to avoid Windows terminal tab crashes while keeping initial and event-driven appearance detection ([#914](https://github.com/can1357/oh-my-pi/issues/914))
64
+
65
+ - Fixed SSH ControlMaster socket paths to use OpenSSH's connection hash (`%C`) so connections to the same host with different users, ports, or jump hosts do not share a master session.
66
+
5
67
  ## [14.6.1] - 2026-05-02
68
+
6
69
  ### Changed
7
70
 
8
71
  - Updated GitHub call headers to display operation-specific titles and contextual metadata such as repository, branch, issue/PR IDs, and search query snippets for supported operations
@@ -13,6 +76,7 @@
13
76
  - Fixed GitHub tool output fallbacks that previously always showed a GitHub Run Watch heading so they now show the actual operation and clear `no output`/`request failed` status messaging
14
77
 
15
78
  ## [14.6.0] - 2026-05-02
79
+
16
80
  ### Breaking Changes
17
81
 
18
82
  - Reworked autoresearch storage and protocol. State now lives in `~/.omp/autoresearch/<project>.db` (SQLite) and per-run logs in `~/.omp/autoresearch/<project>/runs/<id>/benchmark.log`. The repo-side artifacts `autoresearch.md`, `autoresearch.sh`, `autoresearch.checks.sh`, `autoresearch.program.md`, `autoresearch.ideas.md`, `autoresearch.jsonl`, `.autoresearch/`, and `autoresearch.config.json` are no longer read or written; they are deleted by `/autoresearch clear`. Any existing data is not migrated.
@@ -52,6 +116,7 @@
52
116
  - Fixed `log_experiment keep` silently dropping the iteration's diff on an autoresearch branch. The previous logic filtered out every path that was already dirty when `run_experiment` ran — but in the iteration cycle the agent's edits always land before `run_experiment`, so the entire iteration was filtered away and nothing was committed. On an autoresearch branch, `keep` now treats every currently-dirty path as the iteration's change and commits it.
53
117
 
54
118
  ## [14.5.14] - 2026-05-01
119
+
55
120
  ### Changed
56
121
 
57
122
  - Changed markdown conversion and archive tooling to defer loading heavy dependencies (Turndown, fflate, and browser agent content) until first use, reducing startup overhead for CLI startup and command initialization
@@ -93,6 +158,7 @@
93
158
  - Fixed eval startup messaging to report `eval` as unavailable when Python is unreachable and JavaScript backend is disabled
94
159
 
95
160
  ### Fixed
161
+
96
162
  - Stabilized MCP tool ordering so reconnects and refreshes no longer reorder the tools array sent to the model. Anthropic prompt caching is keyed on byte-identical tool definitions; previously, the order depended on connection sequence and a single MCP server reconnect could shuffle tools across servers and invalidate the tools cache breakpoint.
97
163
  - Skipped redundant system-prompt rebuilds in `AgentSession.refreshMCPTools` when the active tool set is unchanged. MCP transport flapping (e.g. routine 5-minute SSE reconnects) used to call `rebuildSystemPrompt` on every reconnect even though the resulting prompt was byte-identical, eating CPU and risking cache misses if the rebuild ever became non-deterministic. The applied-tool signature also covers `customWireName` so a wire-name flip with the rest of the tool metadata constant still forces a rebuild.
98
164
 
@@ -123,6 +189,7 @@
123
189
  - Fixed plan mode to auto-redirect `write` and `edit` calls targeting a bare `PLAN.md` (or any same-basename cwd-relative path) to the canonical `local://PLAN.md` plan artifact instead of rejecting them
124
190
 
125
191
  ## [14.5.11] - 2026-04-30
192
+
126
193
  ### Breaking Changes
127
194
 
128
195
  - `todo_write`: renamed `replace` op to `init` and reshaped its input to `list: [{phase: string, items: string[]}]`. Tasks no longer accept a `status` field; all start `pending` and the first auto-promotes to `in_progress`. The `append` op's `items` is now `string[]` (was `{id, label}[]`)
@@ -194,6 +261,7 @@
194
261
  - Fixed hash mismatch errors to include likely-shifted anchor hints when a unique matching line is found elsewhere in the file
195
262
 
196
263
  ## [14.5.8] - 2026-04-29
264
+
197
265
  ### Breaking Changes
198
266
 
199
267
  - Changed the task runner toggle from `just.enabled` to `runCommand.enabled`, so existing configurations using `just.enabled` must be migrated
@@ -219,12 +287,15 @@
219
287
  ### Fixed
220
288
 
221
289
  - Fixed hook editors to recognize Ctrl+Enter when terminals include NumLock or keypad Enter metadata.
290
+
222
291
  ## [14.5.6] - 2026-04-29
292
+
223
293
  ### Changed
224
294
 
225
295
  - Removed the atom edit mode's multi-anchor auto-rebase rejection so stale-but-uniquely-rebasable block edits apply with warnings instead of failing.
226
296
 
227
297
  ## [14.5.5] - 2026-04-29
298
+
228
299
  ### Breaking Changes
229
300
 
230
301
  - Rejected atom diffs with unrecognized operations (including lone '-' lines) by throwing parse errors instead of treating them as inserts
@@ -243,6 +314,7 @@
243
314
  - Fixed bracket-corruption caused by botched block rewrites by automatically removing a newly introduced duplicate adjacent line when removing it restores the original `{}`, `()`, and `[]` balance and by warning when automatic removal is unsafe
244
315
 
245
316
  ## [14.5.4] - 2026-04-28
317
+
246
318
  ### Breaking Changes
247
319
 
248
320
  - Changed the `atom` edit mode from JSON `{ path, edits }` calls to the compact file-oriented `input` patch language that was previously exposed as `atomd`; `atomd` is no longer a separate edit variant
@@ -296,6 +368,7 @@
296
368
  - Fixed `splice_block` for same-line `(` bodies so inline call sites like `int(port)` can be replaced correctly
297
369
 
298
370
  ## [14.5.2] - 2026-04-26
371
+
299
372
  ### Breaking Changes
300
373
 
301
374
  - Removed support for sed-style string expressions and required `sed` to be specified as an object with `pat` and `rep` (and optional `g`, `F`, `i` flags)
@@ -320,6 +393,7 @@
320
393
  - Removed the hand-rolled JSON unescape fallback in the streaming edit-arg renderer; partial fragments that fail `JSON.parse` are now surfaced raw rather than partially decoded with a non-spec-compliant unescaper that mishandled lone surrogates
321
394
 
322
395
  ## [14.4.3] - 2026-04-26
396
+
323
397
  ### Added
324
398
 
325
399
  - Added `irc` tool for agent-to-agent messaging with `list` and `send` operations, including optional broadcast to `all` and optional suppression of reply waits
@@ -339,6 +413,7 @@
339
413
  - Fixed `/btw` request replacement so issuing a new query cleanly aborts the previous active request
340
414
 
341
415
  ## [14.4.2] - 2026-04-26
416
+
342
417
  ### Breaking Changes
343
418
 
344
419
  - Changed `/todo append` from JSON payload input to `/todo append [<phase>] <task...>` with optional quoted tokens and automatic phase creation
@@ -376,6 +451,7 @@
376
451
  - Fixed `poll` wait duration parsing to fall back to `30s` when the provided value is an empty string
377
452
 
378
453
  ## [14.4.1] - 2026-04-26
454
+
379
455
  ### Breaking Changes
380
456
 
381
457
  - Replaced the legacy `gh_repo_view`, `gh_issue_view`, `gh_pr_view`, `gh_pr_diff`, `gh_pr_checkout`, `gh_pr_push`, `gh_run_watch`, `gh_search_issues`, and `gh_search_prs` tool names with only `github`, which requires updating existing callers that invoked the old `gh_*` tools
@@ -577,6 +653,7 @@
577
653
 
578
654
  - Fixed task calls in `schema-free` and `independent` modes to return clear mode-specific errors when disallowed `context` or `schema` inputs are provided
579
655
  - Fixed newly generated session IDs to use UUIDv7 for new, forked, and branched sessions while preserving resumed session IDs
656
+
580
657
  ## [14.1.1] - 2026-04-14
581
658
 
582
659
  ### Breaking Changes
@@ -618,7 +695,7 @@
618
695
  - Changed Vim page-scroll commands `C-f`, `C-b`, `C-u`, and `C-d` to move in viewport-height based increments instead of fixed constants
619
696
  - Changed `z` command behavior so `zt`, `zb`, and `z.` now align cursor movement to first non-blank in the line
620
697
  - Changed `:g`/`:v` global command handling to process matching lines safely by working in reverse order and preserving file structure
621
- - Changed vim tab breadcrumb rendering from ` → ` to `→` in the editor view
698
+ - Changed vim tab breadcrumb rendering from `→` to `→` in the editor view
622
699
  - Changed custom tool and task execution contexts to no longer expose a shared `searchDb` accessor, removing direct access to native grep/glob/fuzzyFind search backends from extension callbacks
623
700
  - Changed the `task` tool `schema` field to require JSON-encoded JTD schema text instead of a schema object, matching prompt guidance and task-subagent invocation
624
701
  - Changed chunk edit payloads to encode selectors as `path: "file:selector"` and updated chunk tool guidance and examples to match
@@ -652,6 +729,7 @@
652
729
  - Fixed retained Python kernel ownership so `AgentSession.dispose()` only shuts down kernels owned by that session, including warmup-created kernels
653
730
 
654
731
  ## [14.1.0] - 2026-04-11
732
+
655
733
  ### Added
656
734
 
657
735
  - Added richer tool rendering details in session export HTML, including metadata badges, argument formatting, and todo task tree styling for exported tool and workflow messages
@@ -693,6 +771,7 @@
693
771
  - Blocked destructive SQL execution in read-mode SQLite access by using read-only connections and rejecting bound-parameter raw SQL
694
772
 
695
773
  ## [14.0.5] - 2026-04-11
774
+
696
775
  ### Added
697
776
 
698
777
  - Added `designer` model role for UI/UX design tasks with Gemini 3.1 Pro as default model
@@ -739,6 +818,7 @@
739
818
  - Fixed MCP config docs and schema to use `~/.omp/agent/mcp.json` for user-scoped OMP-native MCP config while keeping project config at `<cwd>/.omp/mcp.json`
740
819
 
741
820
  ## [14.0.4] - 2026-04-10
821
+
742
822
  ### Added
743
823
 
744
824
  - Added `PI_CHUNK_AUTOINDENT` environment variable to control whether chunk read/edit tools normalize indentation to canonical tabs or preserve literal file whitespace
@@ -765,6 +845,7 @@
765
845
  - Fixed cached Ollama discovery rows so upgraded installs switch to the OpenAI Responses transport instead of staying on the old completions transport
766
846
 
767
847
  ## [14.0.2] - 2026-04-09
848
+
768
849
  ### Added
769
850
 
770
851
  - Added `/force` slash command to force the next agent turn to use a specific tool
package/README.md CHANGED
@@ -12,3 +12,24 @@ Package-specific references:
12
12
  - [MCP runtime lifecycle](../../docs/mcp-runtime-lifecycle.md)
13
13
  - [MCP server/tool authoring](../../docs/mcp-server-tool-authoring.md)
14
14
  - [DEVELOPMENT](./DEVELOPMENT.md)
15
+
16
+ ## Memory backends
17
+
18
+ The agent supports three mutually-exclusive memory backends, selected via the `memory.backend` setting (Settings → Memory tab, or `~/.omp/config.yml`):
19
+
20
+ - `off` (default) — no memory subsystem runs.
21
+ - `local` — existing rollout-summarisation pipeline; writes `memory_summary.md` and consolidated artifacts under the agent dir.
22
+ - `hindsight` — talks to a [Hindsight](https://hindsight.vectorize.io) server (Cloud or self-hosted Docker), retains transcripts every Nth user turn, recalls memories on the first turn of a session, and exposes `retain`, `recall`, and `reflect`.
23
+
24
+ ### Hindsight quickstart
25
+
26
+ 1. Run a Hindsight server (Cloud or `docker run -p 8888:8888 ghcr.io/vectorize-io/hindsight:latest`).
27
+ 2. Set `memory.backend = "hindsight"` and `hindsight.apiUrl = "http://localhost:8888"` (or your Cloud URL).
28
+ 3. Optional environment overrides (env wins over settings):
29
+ - `HINDSIGHT_API_URL`, `HINDSIGHT_API_TOKEN` — connection
30
+ - `HINDSIGHT_BANK_ID`, `HINDSIGHT_DYNAMIC_BANK_ID`, `HINDSIGHT_AGENT_NAME` — bank addressing
31
+ - `HINDSIGHT_AUTO_RECALL`, `HINDSIGHT_AUTO_RETAIN`, `HINDSIGHT_RETAIN_MODE` — lifecycle
32
+ - `HINDSIGHT_RECALL_BUDGET`, `HINDSIGHT_RECALL_MAX_TOKENS` — recall sizing
33
+ - `HINDSIGHT_BANK_MISSION`, `HINDSIGHT_DEBUG`
34
+
35
+ Switching backends mid-session is honoured on the next system-prompt rebuild and the next `/memory` slash command. Existing users with `memories.enabled = true|false` are migrated to `memory.backend = "local"|"off"` exactly once on first launch.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-coding-agent",
4
- "version": "14.6.1",
4
+ "version": "14.6.3",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://github.com/can1357/oh-my-pi",
7
7
  "author": "Can Boluk",
@@ -46,12 +46,12 @@
46
46
  "dependencies": {
47
47
  "@agentclientprotocol/sdk": "0.20.0",
48
48
  "@mozilla/readability": "^0.6.0",
49
- "@oh-my-pi/omp-stats": "14.6.1",
50
- "@oh-my-pi/pi-agent-core": "14.6.1",
51
- "@oh-my-pi/pi-ai": "14.6.1",
52
- "@oh-my-pi/pi-natives": "14.6.1",
53
- "@oh-my-pi/pi-tui": "14.6.1",
54
- "@oh-my-pi/pi-utils": "14.6.1",
49
+ "@oh-my-pi/omp-stats": "14.6.3",
50
+ "@oh-my-pi/pi-agent-core": "14.6.3",
51
+ "@oh-my-pi/pi-ai": "14.6.3",
52
+ "@oh-my-pi/pi-natives": "14.6.3",
53
+ "@oh-my-pi/pi-tui": "14.6.3",
54
+ "@oh-my-pi/pi-utils": "14.6.3",
55
55
  "@puppeteer/browsers": "^2.13.0",
56
56
  "@sinclair/typebox": "^0.34.49",
57
57
  "@xterm/headless": "^6.0.0",
@@ -371,6 +371,22 @@
371
371
  "types": "./src/memories/*.ts",
372
372
  "import": "./src/memories/*.ts"
373
373
  },
374
+ "./memory-backend": {
375
+ "types": "./src/memory-backend/index.ts",
376
+ "import": "./src/memory-backend/index.ts"
377
+ },
378
+ "./memory-backend/*": {
379
+ "types": "./src/memory-backend/*.ts",
380
+ "import": "./src/memory-backend/*.ts"
381
+ },
382
+ "./hindsight": {
383
+ "types": "./src/hindsight/index.ts",
384
+ "import": "./src/hindsight/index.ts"
385
+ },
386
+ "./hindsight/*": {
387
+ "types": "./src/hindsight/*.ts",
388
+ "import": "./src/hindsight/*.ts"
389
+ },
374
390
  "./modes": {
375
391
  "types": "./src/modes/index.ts",
376
392
  "import": "./src/modes/index.ts"
@@ -19,17 +19,31 @@ export interface ListGrievancesOptions {
19
19
  json: boolean;
20
20
  }
21
21
 
22
- function openDb(): Database | null {
22
+ export interface CleanGrievancesOptions {
23
+ /** Delete a single grievance by id. */
24
+ id?: number;
25
+ /** Delete every grievance recorded for this tool name. */
26
+ tool?: string;
27
+ /** Delete every grievance regardless of tool/id. */
28
+ all?: boolean;
29
+ /** Output the deletion count as JSON instead of a status message. */
30
+ json?: boolean;
31
+ }
32
+
33
+ function openDb(readonly: boolean): Database | null {
23
34
  try {
24
- const db = new Database(getAutoQaDbPath(), { readonly: true });
25
- return db;
35
+ // bun:sqlite rejects `{ readonly: false }` — it requires either readonly,
36
+ // readwrite, or create flags to be explicit. Use the default constructor
37
+ // (readwrite + create) for write mode and only pass `readonly: true` when
38
+ // listing.
39
+ return readonly ? new Database(getAutoQaDbPath(), { readonly: true }) : new Database(getAutoQaDbPath());
26
40
  } catch {
27
41
  return null;
28
42
  }
29
43
  }
30
44
 
31
45
  export async function listGrievances(options: ListGrievancesOptions): Promise<void> {
32
- const db = openDb();
46
+ const db = openDb(true);
33
47
  if (!db) {
34
48
  if (options.json) {
35
49
  console.log("[]");
@@ -76,3 +90,74 @@ export async function listGrievances(options: ListGrievancesOptions): Promise<vo
76
90
  db.close();
77
91
  }
78
92
  }
93
+
94
+ /**
95
+ * Delete grievances from the auto-QA database.
96
+ *
97
+ * Selectors are mutually exclusive in intent — exactly one of `id`, `tool`, or
98
+ * `all` is required. Multiple selectors are rejected to prevent ambiguous deletes
99
+ * (e.g. `--id 5 --all` would be a footgun). Returns silently when the database
100
+ * does not exist yet.
101
+ */
102
+ export async function cleanGrievances(options: CleanGrievancesOptions): Promise<void> {
103
+ const selectors = [options.id !== undefined, !!options.tool, !!options.all].filter(Boolean).length;
104
+ if (selectors === 0) {
105
+ console.error(chalk.red("Specify exactly one of --id, --tool, or --all."));
106
+ process.exitCode = 1;
107
+ return;
108
+ }
109
+ if (selectors > 1) {
110
+ console.error(chalk.red("--id, --tool, and --all are mutually exclusive."));
111
+ process.exitCode = 1;
112
+ return;
113
+ }
114
+
115
+ const db = openDb(false);
116
+ if (!db) {
117
+ if (options.json) {
118
+ console.log(JSON.stringify({ deleted: 0 }));
119
+ } else {
120
+ console.log(
121
+ chalk.dim("No grievances database found. Enable auto-QA with PI_AUTO_QA=1 or the dev.autoqa setting."),
122
+ );
123
+ }
124
+ return;
125
+ }
126
+
127
+ try {
128
+ let deleted = 0;
129
+ if (options.id !== undefined) {
130
+ const result = db.prepare("DELETE FROM grievances WHERE id = ?").run(options.id);
131
+ deleted = Number(result.changes);
132
+ } else if (options.tool) {
133
+ const result = db.prepare("DELETE FROM grievances WHERE tool = ?").run(options.tool);
134
+ deleted = Number(result.changes);
135
+ } else {
136
+ const result = db.prepare("DELETE FROM grievances").run();
137
+ deleted = Number(result.changes);
138
+ // Reset the autoincrement counter so a fresh slate starts at #1 again.
139
+ // `sqlite_sequence` only exists if AUTOINCREMENT was ever used; ignore failures.
140
+ try {
141
+ db.prepare("DELETE FROM sqlite_sequence WHERE name = 'grievances'").run();
142
+ } catch {
143
+ /* sequence table missing on a brand-new db — nothing to reset */
144
+ }
145
+ }
146
+
147
+ if (options.json) {
148
+ console.log(JSON.stringify({ deleted }));
149
+ return;
150
+ }
151
+
152
+ if (deleted === 0) {
153
+ console.log(chalk.dim("No matching grievances to delete."));
154
+ return;
155
+ }
156
+
157
+ const scope =
158
+ options.id !== undefined ? `#${options.id}` : options.tool ? `for ${options.tool}` : "(all entries)";
159
+ console.log(chalk.green(`Deleted ${deleted} grievance${deleted === 1 ? "" : "s"} ${scope}.`));
160
+ } finally {
161
+ db.close();
162
+ }
163
+ }
@@ -1,20 +1,46 @@
1
1
  /**
2
- * View recently reported tool issues from automated QA.
2
+ * View and clean recently reported tool issues from automated QA.
3
3
  */
4
- import { Command, Flags } from "@oh-my-pi/pi-utils/cli";
5
- import { listGrievances } from "../cli/grievances-cli";
4
+ import { Args, Command, Flags } from "@oh-my-pi/pi-utils/cli";
5
+ import { cleanGrievances, listGrievances } from "../cli/grievances-cli";
6
6
 
7
7
  export default class Grievances extends Command {
8
- static description = "View reported tool issues (auto-QA grievances)";
8
+ static description = "View or clean reported tool issues (auto-QA grievances)";
9
+
10
+ static args = {
11
+ // Positional action: "list" (default) or "clean". A positional arg keeps
12
+ // the historical `omp grievances` invocation working unchanged while
13
+ // reusing the same command surface for the new clean sub-action.
14
+ action: Args.string({
15
+ description: "list (default) or clean",
16
+ required: false,
17
+ options: ["list", "clean"],
18
+ default: "list",
19
+ }),
20
+ };
9
21
 
10
22
  static flags = {
11
- limit: Flags.integer({ char: "n", description: "Number of recent issues to show", default: 20 }),
12
- tool: Flags.string({ char: "t", description: "Filter by tool name" }),
23
+ limit: Flags.integer({ char: "n", description: "Number of recent issues to show (list)", default: 20 }),
24
+ tool: Flags.string({ char: "t", description: "Filter by tool name (list, clean)" }),
13
25
  json: Flags.boolean({ char: "j", description: "Output as JSON", default: false }),
26
+ id: Flags.integer({ description: "Delete a single grievance by id (clean)" }),
27
+ all: Flags.boolean({ description: "Delete every grievance (clean)", default: false }),
14
28
  };
15
29
 
30
+ static examples = [
31
+ "omp grievances",
32
+ "omp grievances list --tool find",
33
+ "omp grievances clean --id 209",
34
+ "omp grievances clean --tool find",
35
+ "omp grievances clean --all",
36
+ ];
37
+
16
38
  async run(): Promise<void> {
17
- const { flags } = await this.parse(Grievances);
39
+ const { args, flags } = await this.parse(Grievances);
40
+ if (args.action === "clean") {
41
+ await cleanGrievances({ id: flags.id, tool: flags.tool, all: flags.all, json: flags.json });
42
+ return;
43
+ }
18
44
  await listGrievances({ limit: flags.limit, tool: flags.tool, json: flags.json });
19
45
  }
20
46
  }
@@ -8,7 +8,7 @@ import {
8
8
  parseFrontmatter,
9
9
  prompt,
10
10
  } from "@oh-my-pi/pi-utils";
11
- import { computeLineHash, HASHLINE_CONTENT_SEPARATOR } from "../edit/line-hash";
11
+ import { computeLineHash, HL_BODY_SEP, HL_EDIT_SEP } from "../edit/line-hash";
12
12
  import { jtdToTypeScript } from "../tools/jtd-to-typescript";
13
13
  import { parseCommandArgs, substituteArgs } from "../utils/command-args";
14
14
 
@@ -53,10 +53,10 @@ interface HashlineHelperState {
53
53
  byLine: Map<number, HashlineHelperRef>;
54
54
  }
55
55
 
56
- const HASHLINE_HELPER_STATE = Symbol("hashlineHelperState");
56
+ const HL_HELPER_STATE = Symbol("hashlineHelperState");
57
57
 
58
58
  interface HashlineHelperStateHolder {
59
- [HASHLINE_HELPER_STATE]?: HashlineHelperState;
59
+ [HL_HELPER_STATE]?: HashlineHelperState;
60
60
  }
61
61
 
62
62
  function isHelperOptions(value: unknown): value is prompt.HelperOptions {
@@ -78,10 +78,10 @@ function getHashlineHelperState(context: unknown, options: prompt.HelperOptions
78
78
  }
79
79
 
80
80
  const holder = holderTarget as HashlineHelperStateHolder;
81
- if (!holder[HASHLINE_HELPER_STATE]) {
82
- holder[HASHLINE_HELPER_STATE] = { byLine: new Map() };
81
+ if (!holder[HL_HELPER_STATE]) {
82
+ holder[HL_HELPER_STATE] = { byLine: new Map() };
83
83
  }
84
- return holder[HASHLINE_HELPER_STATE];
84
+ return holder[HL_HELPER_STATE];
85
85
  }
86
86
 
87
87
  function isLineNumberArg(value: unknown): boolean {
@@ -156,9 +156,16 @@ prompt.registerHelper("hline", function (this: unknown, ...args: unknown[]): str
156
156
  const { num, ref, text } = formatHashlineRef(lineNum, content);
157
157
  const state = getHashlineHelperState(this, options);
158
158
  rememberHashlineRef(state, num, ref);
159
- return `${ref}${HASHLINE_CONTENT_SEPARATOR}${text}`;
159
+ return `${ref}${HL_BODY_SEP}${text}`;
160
160
  });
161
161
 
162
+ /**
163
+ * {{hsep}} — emit the configured hashline payload separator character.
164
+ * Stays in sync with {@link HL_EDIT_SEP} so edit prompt templates
165
+ * never have to hardcode the payload separator.
166
+ */
167
+ prompt.registerHelper("hsep", (): string => HL_EDIT_SEP);
168
+
162
169
  const INLINE_ARG_SHELL_PATTERN = /\$(?:ARGUMENTS|@(?:\[\d+(?::\d*)?\])?|\d+)/;
163
170
  const INLINE_ARG_TEMPLATE_PATTERN = /\{\{[\s\S]*?(?:\b(?:arguments|ARGUMENTS|args)\b|\barg\s+[^}]+)[\s\S]*?\}\}/;
164
171