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

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 (74) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/package.json +7 -7
  3. package/src/autoresearch/command-resume.md +5 -8
  4. package/src/autoresearch/git.ts +41 -51
  5. package/src/autoresearch/helpers.ts +43 -359
  6. package/src/autoresearch/index.ts +281 -273
  7. package/src/autoresearch/prompt-setup.md +43 -0
  8. package/src/autoresearch/prompt.md +52 -193
  9. package/src/autoresearch/resume-message.md +2 -8
  10. package/src/autoresearch/state.ts +59 -166
  11. package/src/autoresearch/storage.ts +687 -0
  12. package/src/autoresearch/tools/init-experiment.ts +201 -290
  13. package/src/autoresearch/tools/log-experiment.ts +304 -517
  14. package/src/autoresearch/tools/run-experiment.ts +117 -296
  15. package/src/autoresearch/tools/update-notes.ts +116 -0
  16. package/src/autoresearch/types.ts +16 -66
  17. package/src/cli/list-models.ts +66 -0
  18. package/src/config/settings-schema.ts +1 -1
  19. package/src/config/settings.ts +20 -1
  20. package/src/cursor.ts +1 -1
  21. package/src/edit/index.ts +9 -31
  22. package/src/edit/line-hash.ts +70 -43
  23. package/src/edit/modes/hashline.lark +26 -0
  24. package/src/edit/modes/hashline.ts +898 -1099
  25. package/src/edit/modes/patch.ts +0 -7
  26. package/src/edit/modes/replace.ts +0 -4
  27. package/src/edit/renderer.ts +22 -20
  28. package/src/edit/streaming.ts +8 -28
  29. package/src/eval/eval.lark +24 -30
  30. package/src/eval/js/context-manager.ts +5 -162
  31. package/src/eval/js/prelude.txt +0 -12
  32. package/src/eval/parse.ts +129 -129
  33. package/src/eval/py/prelude.py +1 -219
  34. package/src/export/html/template.generated.ts +1 -1
  35. package/src/export/html/template.js +2 -2
  36. package/src/internal-urls/docs-index.generated.ts +2 -2
  37. package/src/main.ts +18 -3
  38. package/src/modes/components/session-observer-overlay.ts +5 -2
  39. package/src/modes/components/status-line/segments.ts +1 -1
  40. package/src/modes/components/status-line.ts +3 -5
  41. package/src/modes/components/tree-selector.ts +4 -5
  42. package/src/modes/components/welcome.ts +11 -1
  43. package/src/modes/controllers/command-controller.ts +2 -6
  44. package/src/modes/controllers/event-controller.ts +7 -5
  45. package/src/modes/controllers/extension-ui-controller.ts +3 -15
  46. package/src/modes/controllers/input-controller.ts +0 -1
  47. package/src/modes/controllers/selector-controller.ts +1 -1
  48. package/src/modes/interactive-mode.ts +5 -7
  49. package/src/prompts/system/system-prompt.md +14 -38
  50. package/src/prompts/tools/ast-edit.md +8 -8
  51. package/src/prompts/tools/ast-grep.md +10 -10
  52. package/src/prompts/tools/eval.md +13 -31
  53. package/src/prompts/tools/find.md +2 -1
  54. package/src/prompts/tools/hashline.md +66 -57
  55. package/src/prompts/tools/search.md +2 -2
  56. package/src/session/agent-session.ts +1 -1
  57. package/src/session/session-manager.ts +17 -13
  58. package/src/tools/ast-edit.ts +141 -44
  59. package/src/tools/ast-grep.ts +112 -36
  60. package/src/tools/eval.ts +2 -53
  61. package/src/tools/find.ts +16 -15
  62. package/src/tools/gh-renderer.ts +184 -59
  63. package/src/tools/path-utils.ts +36 -196
  64. package/src/tools/search.ts +56 -35
  65. package/src/utils/edit-mode.ts +2 -11
  66. package/src/utils/file-display-mode.ts +1 -1
  67. package/src/utils/git.ts +59 -24
  68. package/src/utils/session-color.ts +0 -12
  69. package/src/utils/title-generator.ts +22 -38
  70. package/src/autoresearch/apply-contract-to-state.ts +0 -24
  71. package/src/autoresearch/contract.ts +0 -288
  72. package/src/edit/modes/atom.lark +0 -29
  73. package/src/edit/modes/atom.ts +0 -1773
  74. package/src/prompts/tools/atom.md +0 -150
package/CHANGELOG.md CHANGED
@@ -2,6 +2,55 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [14.6.1] - 2026-05-02
6
+ ### Changed
7
+
8
+ - 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
9
+ - Changed non-run-watch result rendering to honor terminal width, truncate long lines, and show a `+N more lines` expansion hint when output exceeds the preview limit
10
+
11
+ ### Fixed
12
+
13
+ - 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
+
15
+ ## [14.6.0] - 2026-05-02
16
+ ### Breaking Changes
17
+
18
+ - 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.
19
+ - Removed the autoresearch edit guard. `write`/`edit`/`ast_edit` are no longer blocked based on scope. Scope/off-limits are now post-hoc accountability fields on `log_experiment`.
20
+ - Replaced rigid `init_experiment` contract validation with a simpler schema: `name`, `goal`, `primary_metric`, `metric_unit`, `direction`, `secondary_metrics`, `scope_paths`, `off_limits`, `constraints`, `max_iterations`, `new_segment`. Removed `from_autoresearch_md`, `abandon_unlogged_runs`, `force`, and `preferred_command` flags — the harness `./autoresearch.sh` is the canonical workload, edit it and bump segment when you need to change it.
21
+ - `run_experiment` no longer accepts a `command` parameter. The tool always runs `bash autoresearch.sh`. To change the workload, edit the harness and call `init_experiment new_segment: true`. Removed `force`, `checks_timeout_seconds`, and the legacy `autoresearch.checks.sh` auto-execution; run validation through the regular `bash` tool.
22
+ - Replaced `log_experiment` ASI requirements and `force`/`skip_restore` flags with `justification` (post-hoc explanation for scope deviations) and `flag_runs` (mark earlier runs suspect to exclude them from baseline math). ASI is now opaque metadata.
23
+ - `/autoresearch clear` now resets the worktree to the session's recorded baseline commit (when on an `autoresearch/*` branch or with `--reset-tree`), closes the active session, and deletes any leftover legacy autoresearch repo artifacts.
24
+ - `/autoresearch` now refuses on a dirty worktree with an explicit error instead of silently continuing on the current branch. Commit or stash before invoking — the session needs a clean baseline on a dedicated `autoresearch/*` branch.
25
+ - Split `/autoresearch` into a two-phase protocol. Phase 1 (no session) prompts the agent to build the benchmark harness as `./autoresearch.sh` (must exit 0 and print `METRIC <name>=<value>`). Calling `init_experiment` ends Phase 1: it requires `./autoresearch.sh` to exist, auto-commits any pending harness changes on an `autoresearch/*` branch, then records that commit as the baseline. Phase 2 is the existing iteration loop.
26
+ - Autoresearch sessions are now scoped to the git branch they were created on. Switching off the `autoresearch/*` branch hides the dashboard widget, detaches the experiment tools, and skips the autoresearch system prompt; switching back resumes seamlessly. `/autoresearch` on a fresh branch starts a fresh session instead of resurrecting a session bound to a different branch.
27
+ - `log_experiment discard` no longer rewinds prior `keep` commits. On an `autoresearch/*` branch it now resets the worktree to `HEAD` (and `git clean`s untracked) instead of `git reset --hard $baseline_commit`. Discard reverts only the current iteration's uncommitted edits; previously kept improvements stay on the branch. `/autoresearch clear` continues to reset to the recorded baseline commit when explicitly requested.
28
+ - Autoresearch SQLite storage is now created lazily on first `init_experiment`. Running `omp` in a project that never invokes `/autoresearch` no longer creates a per-folder DB.
29
+
30
+ - Changed `search`, `find`, `ast_grep`, and `ast_edit` to accept `paths: string[]` instead of comma- or whitespace-delimited path strings.
31
+
32
+ ### Added
33
+
34
+ - Added `update_notes` tool with `body` (replace) and `append_idea` (append a bullet under an `## Ideas` section). Notes are injected into the system prompt every iteration and replace the file-based `autoresearch.md` / `.program.md` / `.ideas.md` ecosystem.
35
+
36
+ ### Changed
37
+
38
+ - Updated `log_experiment` summary output to include the count of scope deviations detected for a run
39
+ - Used the active session context in autoresearch resume instructions instead of referencing deleted repo-side files
40
+ - Removed `PI_STRICT_EDIT_MODE`; model-specific edit mode fallbacks are no longer disableable by environment flag.
41
+
42
+ ### Fixed
43
+
44
+ - Atom edit auto-rebase warning now dedupes by `(originalLid, rebasedLine)` pair. Previously, `@Lid` followed by N `+TEXT` lines emitted N identical "Auto-rebased anchor" warnings (one per cloned cursor anchor); now emits exactly one per distinct rebase.
45
+ - Atom/hashline diff preview no longer renders deleted lines with a 2-space hash placeholder (`-20 |old`) that visually mimicked a Lid. Removed lines now use `--` as the placeholder (`-20--|old`), making them unambiguously non-Lid.
46
+ - Atom/hashline diff preview no longer folds size-mismatched `-`/`+` runs into a confusing mix of `*` (paired modification) lines plus surplus `-`/`+` lines. The `*` collapse now applies only to clean 1:1 line replacements (same number of dels and adds); range replaces with N→M (N≠M) render as plain unified-diff `-` then `+` runs.
47
+ - Atom edits now warn when `@Lid` lands on a brace-opening line and the inserted content is at sibling indent (≤ anchor indent) — a foot-gun where the agent meant `^<nextSibling>` but the inserts ended up as the first body element of the `{...}` block.
48
+ - Atom auto-fix warning for adjacent-duplicate cleanup is now formatted as `AUTO-FIX applied — verify the result. Removed ...` instead of the easier-to-miss `Auto-fixed: removed ...`, and explains that `{}/()/[]` balance was the trigger.
49
+ - Fixed multi-target `search`, `ast-grep`, and `ast-edit` path handling by running each resolved target separately under root-level path resolution
50
+ - Fixed pagination and match/replacement summaries for multi-target AST and text searches so totals and affected file counts include all targets
51
+ - Fixed returned file paths for multi-target `search` and `ast-grep` results by normalizing them to the original search scope
52
+ - 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
+
5
54
  ## [14.5.14] - 2026-05-01
6
55
  ### Changed
7
56
 
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.5.14",
4
+ "version": "14.6.1",
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.5.14",
50
- "@oh-my-pi/pi-agent-core": "14.5.14",
51
- "@oh-my-pi/pi-ai": "14.5.14",
52
- "@oh-my-pi/pi-natives": "14.5.14",
53
- "@oh-my-pi/pi-tui": "14.5.14",
54
- "@oh-my-pi/pi-utils": "14.5.14",
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",
55
55
  "@puppeteer/browsers": "^2.13.0",
56
56
  "@sinclair/typebox": "^0.34.49",
57
57
  "@xterm/headless": "^6.0.0",
@@ -1,6 +1,4 @@
1
- Resume autoresearch from the attached notes.
2
-
3
- @{{autoresearch_md_path}}
1
+ Resume autoresearch on the active session.
4
2
 
5
3
  {{branch_status_line}}
6
4
  {{#if has_resume_context}}
@@ -10,8 +8,7 @@ Additional context from the user:
10
8
  {{resume_context}}
11
9
  {{/if}}
12
10
 
13
- Use the notes as the source of truth for the current direction, scope, and constraints.
14
- - inspect recent git history for context
15
- - inspect `autoresearch.jsonl` if it exists
16
- - continue the most promising unfinished direction on the current protected branch
17
- - keep iterating until interrupted or until the configured iteration cap is reached
11
+ - Use the active session context above as the source of truth for goal, scope, constraints, and run history.
12
+ - Inspect recent git history for context.
13
+ - Continue the most promising unfinished direction.
14
+ - Keep iterating until interrupted or until the configured iteration cap is reached.
@@ -1,6 +1,6 @@
1
1
  import type { ExtensionAPI } from "../extensibility/extensions";
2
2
  import * as git from "../utils/git";
3
- import { isAutoresearchLocalStatePath, normalizeAutoresearchPath } from "./helpers";
3
+ import { normalizePathSpec } from "./helpers";
4
4
 
5
5
  const AUTORESEARCH_BRANCH_PREFIX = "autoresearch/";
6
6
  const BRANCH_NAME_MAX_LENGTH = 48;
@@ -11,9 +11,10 @@ export interface EnsureAutoresearchBranchFailure {
11
11
  }
12
12
 
13
13
  export interface EnsureAutoresearchBranchSuccess {
14
- branchName: string;
14
+ branchName: string | null;
15
15
  created: boolean;
16
16
  ok: true;
17
+ warning?: string;
17
18
  }
18
19
 
19
20
  export type EnsureAutoresearchBranchResult = EnsureAutoresearchBranchFailure | EnsureAutoresearchBranchSuccess;
@@ -23,6 +24,14 @@ export async function getCurrentAutoresearchBranch(_api: ExtensionAPI, workDir:
23
24
  return currentBranch.startsWith(AUTORESEARCH_BRANCH_PREFIX) ? currentBranch : null;
24
25
  }
25
26
 
27
+ /**
28
+ * Ensure the working tree is on an `autoresearch/*` branch when possible.
29
+ *
30
+ * If the worktree is dirty and we're not already on an autoresearch branch, this returns
31
+ * `{ ok: true, branchName: null, warning }` rather than failing. The caller surfaces the
32
+ * warning and continues on the current branch — `keep` will skip auto-commits and `discard`
33
+ * will revert only run-modified paths instead of resetting to baseline.
34
+ */
26
35
  export async function ensureAutoresearchBranch(
27
36
  api: ExtensionAPI,
28
37
  workDir: string,
@@ -31,57 +40,48 @@ export async function ensureAutoresearchBranch(
31
40
  const repoRoot = await git.repo.root(workDir);
32
41
  if (!repoRoot) {
33
42
  return {
34
- error: "Autoresearch requires a git repository so it can isolate experiments and revert failed runs safely.",
35
- ok: false,
43
+ ok: true,
44
+ branchName: null,
45
+ created: false,
46
+ warning:
47
+ "Not in a git repository — autoresearch will run without branch isolation, baseline reset, or auto-commits.",
36
48
  };
37
49
  }
38
50
 
39
51
  let dirtyPathsOutput: string;
40
52
  try {
41
- dirtyPathsOutput = await git.status(repoRoot, {
42
- porcelainV1: true,
43
- untrackedFiles: "all",
44
- z: true,
45
- });
53
+ dirtyPathsOutput = await git.status(repoRoot, { porcelainV1: true, untrackedFiles: "all", z: true });
46
54
  } catch (err) {
47
55
  return {
48
- error: `Unable to inspect git status before starting autoresearch: ${err instanceof Error ? err.message : String(err)}`,
49
56
  ok: false,
57
+ error: `Unable to inspect git status before starting autoresearch: ${err instanceof Error ? err.message : String(err)}`,
50
58
  };
51
59
  }
52
60
 
53
61
  const workDirPrefix = await readGitWorkDirPrefix(api, workDir);
54
- const unsafeDirtyPaths = collectUnsafeDirtyPaths(dirtyPathsOutput, workDirPrefix);
62
+ const dirtyPaths = collectRelativeDirtyPaths(dirtyPathsOutput, workDirPrefix);
55
63
  const currentBranch = await getCurrentAutoresearchBranch(api, workDir);
56
64
  if (currentBranch) {
57
- if (unsafeDirtyPaths.length > 0) {
58
- return buildUnsafeDirtyPathsFailure(unsafeDirtyPaths);
59
- }
65
+ return { ok: true, branchName: currentBranch, created: false };
66
+ }
67
+ if (dirtyPaths.length > 0) {
68
+ const preview = formatDirtyPaths(dirtyPaths);
60
69
  return {
61
- branchName: currentBranch,
62
- created: false,
63
- ok: true,
70
+ ok: false,
71
+ error: `Worktree is dirty (${preview}). Commit or stash these changes before starting autoresearch — a fresh autoresearch/* branch needs a clean baseline.`,
64
72
  };
65
73
  }
66
- if (unsafeDirtyPaths.length > 0) {
67
- return buildUnsafeDirtyPathsFailure(unsafeDirtyPaths);
68
- }
69
74
 
70
75
  const branchName = await allocateBranchName(api, workDir, goal);
71
76
  try {
72
77
  await git.branch.checkoutNew(workDir, branchName);
73
78
  } catch (err) {
74
79
  return {
75
- error: `Failed to create autoresearch branch ${branchName}: ${err instanceof Error ? err.message : String(err)}`,
76
80
  ok: false,
81
+ error: `Failed to create autoresearch branch ${branchName}: ${err instanceof Error ? err.message : String(err)}`,
77
82
  };
78
83
  }
79
-
80
- return {
81
- branchName,
82
- created: true,
83
- ok: true,
84
- };
84
+ return { ok: true, branchName, created: true };
85
85
  }
86
86
 
87
87
  export function parseWorkDirDirtyPaths(statusOutput: string, workDirPrefix: string): string[] {
@@ -96,7 +96,7 @@ export function parseWorkDirDirtyPaths(statusOutput: string, workDirPrefix: stri
96
96
 
97
97
  export function relativizeGitPathToWorkDir(repoRelativePath: string, workDirPrefix: string): string | null {
98
98
  const normalizedPath = normalizeStatusPath(repoRelativePath);
99
- const normalizedPrefix = normalizeAutoresearchPath(workDirPrefix);
99
+ const normalizedPrefix = normalizePathSpec(workDirPrefix);
100
100
  if (normalizedPrefix === "" || normalizedPrefix === ".") {
101
101
  return normalizedPath;
102
102
  }
@@ -106,7 +106,7 @@ export function relativizeGitPathToWorkDir(repoRelativePath: string, workDirPref
106
106
  if (!normalizedPath.startsWith(`${normalizedPrefix}/`)) {
107
107
  return null;
108
108
  }
109
- return normalizeAutoresearchPath(normalizedPath.slice(normalizedPrefix.length + 1));
109
+ return normalizePathSpec(normalizedPath.slice(normalizedPrefix.length + 1));
110
110
  }
111
111
 
112
112
  async function readGitWorkDirPrefix(api: ExtensionAPI, workDir: string): Promise<string> {
@@ -162,12 +162,12 @@ function parseDirtyPathsLines(statusOutput: string): string[] {
162
162
  return [...unsafePaths];
163
163
  }
164
164
 
165
- export function normalizeStatusPath(path: string): string {
166
- let normalized = path.trim();
165
+ export function normalizeStatusPath(rawPath: string): string {
166
+ let normalized = rawPath.trim();
167
167
  if (normalized.startsWith('"') && normalized.endsWith('"')) {
168
168
  normalized = normalized.slice(1, -1);
169
169
  }
170
- return normalizeAutoresearchPath(normalized);
170
+ return normalizePathSpec(normalized);
171
171
  }
172
172
 
173
173
  async function allocateBranchName(api: ExtensionAPI, workDir: string, goal: string | null): Promise<string> {
@@ -209,32 +209,23 @@ function addDirtyPath(paths: Set<string>, rawPath: string): void {
209
209
  paths.add(normalizedPath);
210
210
  }
211
211
 
212
- function buildUnsafeDirtyPathsFailure(unsafeDirtyPaths: string[]): EnsureAutoresearchBranchFailure {
213
- const preview = unsafeDirtyPaths.slice(0, 5).join(", ");
214
- const suffix = unsafeDirtyPaths.length > 5 ? ` (+${unsafeDirtyPaths.length - 5} more)` : "";
215
- return {
216
- error:
217
- "Autoresearch needs a clean git worktree before it can create or reuse an isolated branch. " +
218
- `Commit or stash these paths first: ${preview}${suffix}`,
219
- ok: false,
220
- };
221
- }
222
-
223
212
  function isRenameOrCopy(statusToken: string): boolean {
224
213
  const trimmed = statusToken.trim();
225
214
  return trimmed.startsWith("R") || trimmed.startsWith("C");
226
215
  }
227
216
 
228
- function collectUnsafeDirtyPaths(statusOutput: string, workDirPrefix: string): string[] {
229
- const unsafeDirtyPaths: string[] = [];
217
+ function collectRelativeDirtyPaths(statusOutput: string, workDirPrefix: string): string[] {
218
+ const dirtyPaths: string[] = [];
230
219
  for (const dirtyPath of parseDirtyPaths(statusOutput)) {
231
220
  const relativePath = relativizeGitPathToWorkDir(dirtyPath, workDirPrefix);
232
- if (relativePath && isAutoresearchLocalStatePath(relativePath)) {
233
- continue;
234
- }
235
- unsafeDirtyPaths.push(relativePath ?? normalizeStatusPath(dirtyPath));
221
+ dirtyPaths.push(relativePath ?? normalizeStatusPath(dirtyPath));
236
222
  }
237
- return unsafeDirtyPaths;
223
+ return dirtyPaths;
224
+ }
225
+
226
+ function formatDirtyPaths(paths: string[]): string {
227
+ const preview = paths.slice(0, 5).join(", ");
228
+ return paths.length > 5 ? `${preview} (+${paths.length - 5} more)` : preview;
238
229
  }
239
230
 
240
231
  export interface DirtyPathEntry {
@@ -318,7 +309,6 @@ export function computeRunModifiedPaths(
318
309
  const untracked: string[] = [];
319
310
  for (const entry of parseWorkDirDirtyPathsWithStatus(currentStatusOutput, workDirPrefix)) {
320
311
  if (preRunSet.has(entry.path)) continue;
321
- if (isAutoresearchLocalStatePath(entry.path)) continue;
322
312
  if (entry.untracked) {
323
313
  untracked.push(entry.path);
324
314
  } else {