omp-vcc 0.1.2 → 0.1.5

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/scripts/smoke.ts CHANGED
@@ -55,6 +55,10 @@ try {
55
55
  "vcc_recall registered",
56
56
  tools.some((t) => t.name === "vcc_recall"),
57
57
  );
58
+ check(
59
+ "vcc_stats registered",
60
+ tools.some((t) => t.name === "vcc_stats"),
61
+ );
58
62
  check(
59
63
  "omp-vcc command registered",
60
64
  commands.some((c) => c.name === "omp-vcc"),
@@ -67,7 +71,23 @@ try {
67
71
  "pi-vcc alias registered",
68
72
  commands.some((c) => c.name === "pi-vcc"),
69
73
  );
70
- } catch (e) {
74
+ check(
75
+ "vcc-stats command registered",
76
+ commands.some((c) => c.name === "vcc-stats"),
77
+ );
78
+ check(
79
+ "no omp-vcc-stats duplicate",
80
+ !commands.some((c) => c.name === "omp-vcc-stats"),
81
+ );
82
+ check(
83
+ "pi-vcc-recall alias registered",
84
+ commands.some((c) => c.name === "pi-vcc-recall"),
85
+ );
86
+ check(
87
+ "no file slash command duplication (only extension commands)",
88
+ commands.filter((c) => c.name === "omp-vcc").length === 1,
89
+ );
90
+ } catch (e) {
71
91
  check("extension loads", false, String(e));
72
92
  }
73
93
 
@@ -1,73 +1,302 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * postuninstall hook: reset ownership marker if this plugin owned global state.
4
- * Generic postuninstall reset for ownership marker
5
- * Marker: ~/.config/@zhulinchng/omp-vcc/.ownership.json with { state: "owned", previous: boolean }
3
+ * postuninstall hook: reset ownership marker and clean stale plugin entries.
4
+ *
5
+ * 1. Marker: ~/.config/@zhulinchng/omp-vcc/.ownership.json with { state: "owned" }
6
+ * When the plugin set `startup.quiet: true` in the global config, restore.
7
+ * No-op when marker missing or not owned.
8
+ *
9
+ * 2. Stale plugin entries: historic `package.json:name` renames left
10
+ * `@zhu/omp-vcc` → `@zhulinchng/omp-vcc` → `omp-vcc`. `omp plugin link .`
11
+ * under an old name creates a symlink+lock entry that survives the rename
12
+ * (host `PluginManager.link` did not clean same-realpath stale keys, and
13
+ * `PluginManager.uninstall` for linked plugins left the symlink behind).
14
+ * This script removes those stale symlinks+lock entries without touching
15
+ * correctly installed `omp-vcc` (or npm `dependencies` entries which are
16
+ * real directories, not symlinks).
17
+ * Safe to run repeatedly; also runs on extension activation (see
18
+ * extensions/vcc-core/core/migrate-stale.ts).
6
19
  */
7
- import { readFileSync, rmSync, writeFileSync } from "node:fs";
20
+ import { readFileSync, rmSync, writeFileSync, existsSync, lstatSync, readdirSync, realpathSync } from "node:fs";
21
+ import { readFile, lstat, readdir, realpath, rm, writeFile } from "node:fs/promises";
8
22
  import { homedir } from "node:os";
9
- import { join } from "node:path";
23
+ import { join, resolve, dirname } from "node:path";
10
24
  import { pathToFileURL } from "node:url";
11
25
 
26
+ const HISTORIC_NAMES = ["@zhu/omp-vcc", "@zhulinchng/omp-vcc"];
27
+ const CURRENT_NAME = "omp-vcc";
28
+
12
29
  export function resetOwnedQuiet(home) {
13
- const markerPath = join(
14
- home,
15
- ".config",
16
- "@zhulinchng/omp-vcc",
17
- ".ownership.json",
18
- );
19
- const configPath = join(home, ".omp", "agent", "config.yml");
30
+ const baseHome = home || homedir();
31
+ const markerPath = join(baseHome, ".config", "@zhulinchng/omp-vcc", ".ownership.json");
32
+ const configCandidates = [
33
+ join(baseHome, ".omp", "agent", "config.yml"),
34
+ join(baseHome, ".omp", "config.yml"),
35
+ ];
36
+
20
37
  let marker;
21
38
  try {
22
39
  marker = JSON.parse(readFileSync(markerPath, "utf8"));
23
40
  } catch {
24
41
  return "no-marker";
25
42
  }
43
+
26
44
  const clearMarker = () => {
27
45
  try {
28
46
  rmSync(markerPath, { force: true });
29
47
  } catch {}
30
48
  };
49
+
31
50
  if (marker?.state !== "owned") {
32
51
  clearMarker();
33
52
  return "not-owned";
34
53
  }
54
+
35
55
  let content;
36
- try {
37
- content = readFileSync(configPath, "utf8");
38
- } catch {
56
+ let configPath;
57
+ for (const p of configCandidates) {
58
+ try {
59
+ content = readFileSync(p, "utf8");
60
+ configPath = p;
61
+ break;
62
+ } catch {}
63
+ }
64
+ if (!configPath || content === undefined) {
39
65
  clearMarker();
40
66
  return "config-missing";
41
67
  }
68
+
42
69
  const lines = content.split("\n");
43
70
  let inStartup = false;
44
71
  let changed = false;
45
72
  for (let i = 0; i < lines.length; i++) {
46
73
  const line = lines[i] ?? "";
47
74
  if (/^startup:\s*$/.test(line)) inStartup = true;
48
- else if (inStartup && /^[^ \t]/.test(line) && line.trim() !== "")
49
- inStartup = false;
75
+ else if (inStartup && /^[^ \t]/.test(line) && line.trim() !== "") inStartup = false;
50
76
  if (inStartup && /quiet:\s*true/.test(line)) {
51
77
  lines[i] = line.replace("true", "false");
52
78
  changed = true;
53
79
  break;
54
80
  }
55
81
  }
82
+
56
83
  if (changed) {
57
84
  try {
58
85
  writeFileSync(configPath, lines.join("\n"), "utf8");
59
86
  } catch {
87
+ clearMarker();
60
88
  return "write-failed";
61
89
  }
62
90
  }
91
+
63
92
  clearMarker();
64
93
  return changed ? "restored" : "already-default";
65
94
  }
66
95
 
67
- if (
96
+ // --- stale plugin cleanup (in-plugin, no host changes) ---
97
+
98
+ function isSymlinkSync(p) {
99
+ try {
100
+ return lstatSync(p).isSymbolicLink();
101
+ } catch {
102
+ return false;
103
+ }
104
+ }
105
+
106
+ function readJsonSync(p, fallback = null) {
107
+ try {
108
+ return JSON.parse(readFileSync(p, "utf8"));
109
+ } catch {
110
+ return fallback;
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Remove stale `omp-vcc` plugin entries left by historic package.json renames
116
+ * and by `omp plugin uninstall` for linked plugins (which left the symlink
117
+ * behind on old hosts).
118
+ *
119
+ * - Only touches lockfile-only entries (not in `package.json:dependencies`)
120
+ * where `package.json:name` mismatches the lock key, or where two keys
121
+ * point at the same realpath.
122
+ * - Only removes symlinks, never real directories (npm installs).
123
+ * - Returns a summary string for logging.
124
+ */
125
+ export function cleanupStalePluginEntries(home) {
126
+ const baseHome = home || homedir();
127
+ const pluginsDir = join(baseHome, ".omp", "plugins");
128
+ const lockPath = join(pluginsDir, "omp-plugins.lock.json");
129
+ const pkgPath = join(pluginsDir, "package.json");
130
+ const nm = join(pluginsDir, "node_modules");
131
+
132
+ const lock = readJsonSync(lockPath, null);
133
+ if (!lock || typeof lock !== "object") return "no-lock";
134
+
135
+ const pkg = readJsonSync(pkgPath, { dependencies: {} });
136
+ const deps = (pkg && typeof pkg.dependencies === "object" && pkg.dependencies) || {};
137
+
138
+ const pluginKeys = lock.plugins && typeof lock.plugins === "object" ? Object.keys(lock.plugins) : [];
139
+ const ompKeys = pluginKeys.filter((k) => k.includes("omp-vcc"));
140
+
141
+ // If no omp-vcc keys at all, still check for orphaned symlinks at historic
142
+ // paths that may remain after `omp plugin uninstall` (old host left symlink).
143
+ const candidates = new Set([...ompKeys, ...HISTORIC_NAMES, CURRENT_NAME]);
144
+
145
+ let removedLocks = [];
146
+ let removedLinks = [];
147
+
148
+ // Helper to get package.json:name for a node_modules entry, without throwing
149
+ const getPkgName = (name) => {
150
+ try {
151
+ const jp = readJsonSync(join(nm, name, "package.json"), null);
152
+ return jp && typeof jp.name === "string" ? jp.name : null;
153
+ } catch {
154
+ return null;
155
+ }
156
+ };
157
+
158
+ // Helper to get realpath for dedup, null if not exists or not symlink
159
+ const getReal = (name) => {
160
+ const p = join(nm, name);
161
+ if (!isSymlinkSync(p)) return null;
162
+ try {
163
+ return realpathSync(p);
164
+ } catch {
165
+ return null;
166
+ }
167
+ };
168
+
169
+ // Map realpath -> list of lock keys that resolve to it (for dedup)
170
+ const realToKeys = new Map();
171
+ for (const k of ompKeys) {
172
+ const rp = getReal(k);
173
+ if (!rp) continue;
174
+ const list = realToKeys.get(rp) || [];
175
+ list.push(k);
176
+ realToKeys.set(rp, list);
177
+ }
178
+
179
+ // 1. lock-only mismatch: lock key !== pkg name -> stale (e.g. @zhu/omp-vcc -> omp-vcc)
180
+ for (const k of [...ompKeys]) {
181
+ if (k in deps) continue; // npm-declared, keep even if name mismatches (dual publish)
182
+ const pkgName = getPkgName(k);
183
+ if (pkgName && pkgName !== k) {
184
+ // Historic rename leftover: lock key is old name, package is new name
185
+ const isHistoric = HISTORIC_NAMES.includes(k) || k !== CURRENT_NAME;
186
+ if (isHistoric) {
187
+ // Remove symlink if it exists
188
+ const p = join(nm, k);
189
+ if (isSymlinkSync(p)) {
190
+ try {
191
+ rmSync(p, { force: true });
192
+ removedLinks.push(k);
193
+ // try to remove empty scope dir e.g. .../node_modules/@zhu
194
+ if (k.startsWith("@")) {
195
+ const scopeDir = join(nm, k.split("/")[0]);
196
+ try {
197
+ if (readdirSync(scopeDir).length === 0) rmSync(scopeDir, { force: true });
198
+ } catch {}
199
+ }
200
+ } catch {}
201
+ }
202
+ // Remove lock entry
203
+ if (lock.plugins[k]) {
204
+ delete lock.plugins[k];
205
+ removedLocks.push(k);
206
+ }
207
+ if (lock.settings && lock.settings[k]) delete lock.settings[k];
208
+ }
209
+ }
210
+ }
211
+
212
+ // 2. duplicate realpath: two lock keys point at same directory -> keep the
213
+ // one where lock key === pkg name, remove the other(s)
214
+ for (const [, keys] of realToKeys) {
215
+ if (keys.length <= 1) continue;
216
+ // Determine keeper: prefer CURRENT_NAME or key that matches pkg name
217
+ let keeper = null;
218
+ for (const k of keys) {
219
+ const pkgName = getPkgName(k);
220
+ if (pkgName === k) {
221
+ keeper = k;
222
+ break;
223
+ }
224
+ }
225
+ if (!keeper) keeper = keys.includes(CURRENT_NAME) ? CURRENT_NAME : keys[0];
226
+ for (const k of keys) {
227
+ if (k === keeper) continue;
228
+ // Only remove if lock-only (not deps) and symlink
229
+ if (k in deps) continue;
230
+ const p = join(nm, k);
231
+ if (isSymlinkSync(p)) {
232
+ try {
233
+ rmSync(p, { force: true });
234
+ if (!removedLinks.includes(k)) removedLinks.push(k);
235
+ if (k.startsWith("@")) {
236
+ const scopeDir = join(nm, k.split("/")[0]);
237
+ try {
238
+ if (readdirSync(scopeDir).length === 0) rmSync(scopeDir, { force: true });
239
+ } catch {}
240
+ }
241
+ } catch {}
242
+ }
243
+ if (lock.plugins[k]) {
244
+ delete lock.plugins[k];
245
+ if (!removedLocks.includes(k)) removedLocks.push(k);
246
+ }
247
+ if (lock.settings && lock.settings[k]) delete lock.settings[k];
248
+ }
249
+ }
250
+
251
+ // 3. orphaned symlinks at candidate paths that have no lock entry (old
252
+ // host left symlink after uninstall). Remove them if they are symlinks
253
+ // and their package name is omp-vcc (so we don't delete unrelated).
254
+ for (const cand of candidates) {
255
+ const p = join(nm, cand);
256
+ if (!isSymlinkSync(p)) continue;
257
+ const hasLock = !!lock.plugins[cand];
258
+ if (hasLock) continue; // already handled
259
+ const pkgName = getPkgName(cand);
260
+ // Only remove orphaned omp-vcc symlinks, not other plugins
261
+ if (pkgName === CURRENT_NAME || pkgName === "@zhu/omp-vcc" || pkgName === "@zhulinchng/omp-vcc") {
262
+ try {
263
+ rmSync(p, { force: true });
264
+ if (!removedLinks.includes(cand)) removedLinks.push(cand);
265
+ if (cand.startsWith("@")) {
266
+ const scopeDir = join(nm, cand.split("/")[0]);
267
+ try {
268
+ if (readdirSync(scopeDir).length === 0) rmSync(scopeDir, { force: true });
269
+ } catch {}
270
+ }
271
+ } catch {}
272
+ }
273
+ }
274
+
275
+ if (removedLocks.length > 0 || removedLinks.length > 0) {
276
+ try {
277
+ writeFileSync(lockPath, JSON.stringify(lock, null, 2));
278
+ } catch {}
279
+ return `cleaned locks:${removedLocks.join(",") || "none"} links:${removedLinks.join(",") || "none"}`;
280
+ }
281
+ return "no-stale";
282
+ }
283
+
284
+ const isDirectRun =
68
285
  process.argv[1] &&
69
- import.meta.url === pathToFileURL(process.argv[1]).href
70
- ) {
71
- const result = resetOwnedQuiet(homedir());
72
- console.log(`uninstall-reset: ${result}`);
286
+ (() => {
287
+ try {
288
+ return import.meta.url === pathToFileURL(resolve(process.argv[1])).href;
289
+ } catch {
290
+ return false;
291
+ }
292
+ })();
293
+
294
+ if (isDirectRun) {
295
+ try {
296
+ const r1 = resetOwnedQuiet(homedir());
297
+ const r2 = cleanupStalePluginEntries(homedir());
298
+ console.log(`uninstall-reset: ${r1} ${r2}`);
299
+ } catch (err) {
300
+ console.log(`uninstall-reset: error ${err?.message ?? err}`);
301
+ }
73
302
  }
@@ -1,35 +1,116 @@
1
+ ---
2
+ name: omp-vcc
3
+ description: VCC-inspired algorithmic compaction for oh-my-pi — lossless V_ui summary + ranked brief + V_adapt recall via vcc_recall. Use after auto-compaction (toast 90k→22k), when context grows 50+ turns, or before /omp-vcc keep:N boundaries.
4
+ ---
5
+
1
6
  # omp-vcc Skill — VCC-Inspired Algorithmic Compaction
2
7
 
3
- > Lossless, transcript-preserving structured summaries — no LLM calls. Based on `sting8k/pi-vcc` (TypeScript) and `lllyasviel/VCC` (View-oriented Conversation Compiler, arXiv 2603.29678).
8
+ > Lossless, deterministic summarization — no LLM. `V_full` = full transcript, `V_ui` = structured summary + ranked brief, `V_adapt(b, ρ)` = structure-preserving recall. Use `V_ui → V_adapt(query) V_full[s:e]`: scan summary, query, drill to verbatim lines.
9
+
10
+ ## When to Use
11
+
12
+ - **After auto compaction** — read `V_ui` first, then `vcc_recall` for anything missing before asking the user to repeat. Toast `omp-vcc: 90.0k→22.0k (76% saved) · kept 1/5 turns` + divider `── 📷 compacted ──` means you just got a `V_ui`.
13
+ - **Context is growing** (50+ turns, heavy tool output) — prefer small keep + recall over a huge tail. Recall is cheap and preserves turn/header/block.
14
+ - **Before risky work** — create a clean boundary: `/omp-vcc keep:2 <focus>` (e.g. `/omp-vcc keep:2 fix auth`). The focus text is sent as the next user message after compaction.
15
+
16
+ ## What You Get (V_ui)
17
+
18
+ Compacted summary replaces the old transcript; `V_ui` + kept tail is what you see next:
19
+
20
+ ```sh
21
+ [Session Goal]
22
+ - …
23
+
24
+ [Files And Changes]
25
+ - …
26
+
27
+ [Commits]
28
+ - …
29
+
30
+ [Outstanding Context]
31
+ - …
32
+
33
+ [User Preferences]
34
+ - …
35
+
36
+ ---
37
+ * Read "src/pets.py" (file.txt:18-20) ← ranked brief, one line per block
38
+ * Edit src/auth.ts { old: "…" (#12:auth.ts:10-40) }
39
+
40
+ Use `vcc_recall` to search for prior work … Do not redo work already completed.
41
+ ```
42
+
43
+ - **5 sections** are extraction-only (no hallucination): Session Goal, Files And Changes, Commits, Outstanding Context, User Preferences.
44
+ - **Ranked brief**: TF-IDF-ranked tool summaries, capped at **120 lines** (`BRIEF_MAX_LINES=120`), token-budgeted **1100 → 2000 tokens** (`RANKED_BRIEF_BUDGET_TOKENS` floor, `CEILING` 2000, ~15 tok/block). `---` separates sections from brief; earlier lines beyond 120 are dropped tail-first.
45
+ - **Every line keeps a pointer** `(#N)` or `(path:s-e)` so `V_ui → V_full[s:e]` is structural. Trust the summary's pointers; drill for verbatim.
46
+
47
+ ## Commands & Tools
48
+
49
+ | Task | How | Notes |
50
+ |---|---|---|
51
+ | **Check savings** | `/vcc-stats` · `vcc_stats({history:true})` | Last + history table (50-capped, no `omp-vcc-stats` alias). `/omp-vcc` toast is single line only; detailed savings via `/vcc-stats`. Use to confirm headroom before long edits. |
52
+ | **Recall search** | `/vcc-recall <query> [scope:all] [page:2]` · alias `/pi-vcc-recall` · tool `vcc_recall({query, scope, mode, page, expand})` | 5 hits/page, up to 50 total. See cookbook below. |
53
+ | **Stats tool** | `vcc_stats({history?: boolean})` | Same as `/vcc-stats`. `history:true` = full 50-row table. |
54
+
55
+ `vcc_recall` params (all optional): `query?: string`, `page?: number` (1-indexed), `scope?: "lineage" | "all"` (default `lineage` = active branch), `mode?: "hybrid" | "touched"` (default `hybrid`), `expand?: number[]` (valid indices only).
56
+
57
+ No config needed for normal use. Auto `threshold`/`overflow` compaction is already `V_ui` (deterministic, no model call, ~30–470 ms benchmark) when `overrideDefaultCompaction:true` (default). Don't fight it — just use the summary.
58
+
59
+ ## Recall Cookbook — V_adapt
60
+
61
+ **How search works**: regex first; if invalid or no hits → TF-IDF keyword OR (rare terms weighted, stopwords removed). Each hit preserves turn/header/block, role tags, and `(#N)`. ±2 lines around the match are shown.
4
62
 
5
- ## Philosophy
63
+ ```sh
64
+ # basic keyword (TF-IDF OR)
65
+ vcc_recall({query:"redis cache"})
66
+ /vcc-recall redis cache
6
67
 
7
- - **Agent trace as structured document** (`user`, `assistant`, `thinking`, `tool_call/result`, `subagent`, compaction boundaries, harness directives). Like VCC's `V_full` identity view defines a line-number coordinate system; `V_ui` gives a one-line tool summary (`* Read "src/pets.py" (file.txt:18-20)`); `V_adapt(b, ρ)` projects via relevance predicate `ρ` (regex / BM25 / embedding / LLM) preserving turn headers, role tags, and `(f:s-e)` pointers.
8
- - **Fast, deterministic, lossless** — pure extraction, not LLM summarization. 30–470 ms, 35–99% context reduction, pointer invariant `V_ui → V_full[s:e]` holds structurally (SSA-like).
9
- - **Progressive disclosure** — brief transcript → ranked recall via `vcc_recall` (`ρ = regex→OR`) → drill-down `#N:path` resolves `(session.jsonl:s-e)` into the full view.
68
+ # regex (contains | * + ? {} () [] \ ^ $ .)
69
+ vcc_recall({query:"hook|inject"})
70
+ /vcc-recall hook|inject
10
71
 
11
- ## Pipeline
72
+ # scope: include abandoned branches (default is active branch only)
73
+ vcc_recall({query:"auth", scope:"all"})
74
+ /vcc-recall auth scope:all
12
75
 
13
- Calibrated `charsPerToken` from `preparation.tokensBefore` (heuristic fallback 4) → Smart keep-tail (5 k → 25 k) → Build own cut (`firstKeptEntryId`, orphan recovery) → Token-budget tail rescue (`no_anchor` / `oversized_tail` ×2.5) → Normalize (IR lex: escaped JSON→`|` block, `digits→` strip, `<system-reminder>` filter, `TodoWrite`/`ToolSearch`/ANSI strip, `same message.id` merge, `queue-operation` discard, base64 image extract) → Filter noise → Build 5 sections (Session Goal, Files And Changes, Commits, Outstanding Context, User Preferences) → Ranked brief transcript (TF-IDF, `RANKED_BRIEF_BUDGET_TOKENS=1100` ceiling 2000, ~15 tok/block) → Format bracketed sections + separator `---` → Bounded merge (sticky dedup, volatile replace, transcript roll, 120-line cap).
76
+ # pagination (5/page, up to 50)
77
+ vcc_recall({query:"auth", page:2})
78
+ /vcc-recall auth page:2 scope:all
14
79
 
15
- ## Usage
80
+ # file index — what was touched, not text search
81
+ vcc_recall({query:"", mode:"touched"})
82
+ /vcc-recall touched mode:touched
16
83
 
17
- - **Auto**: threshold/overflow compaction intercepts `session_before_compact` when `overrideDefaultCompaction=true` (default). No LLM summary; token-budgeted.
18
- - **Manual**: `/omp-vcc [keep:N] [focus]` — e.g. `/omp-vcc keep:2 fix auth` keeps last 2 user turns. Also `/pi-vcc` alias.
19
- - **Recall**: `vcc_recall({query:"redis cache", scope:"all", page:1})` or `/vcc-recall hook|inject scope:all page:2`. Regex first, then TF-IDF OR fallback. `mode:'touched'` lists files, `#12:src/auth.ts` drills. 5 per page.
84
+ # drill to verbatim lines resolves (#N) or (path:s-e)
85
+ vcc_recall({query:"#12:src/auth.ts"})
86
+ /vcc-recall #12:src/auth.ts
87
+ vcc_recall({query:"#18"}) # whole turn 18
88
+ vcc_recall({query:"#18:src/auth.ts:40-80"}) # slice (offset/limit via drill-down)
20
89
 
21
- ## Configuration
90
+ # expand multiple turns by index (from a prior recall's #N)
91
+ vcc_recall({query:"", expand:[12,18,25]})
92
+ ```
22
93
 
23
- File `~/.omp/omp-vcc/config.json` (XDG-aware via `$OMP_DIR`/`$PI_CODING_AGENT_DIR`/`$OMP_VCC_CONFIG_PATH`, migrates legacy `~/.pi/agent/pi-vcc-config.json`). Manifest `omp.settings` also exposed: `vccEnabled`, `overrideDefaultCompaction`, `smartKeepTail`, `continueAfterThresholdCompact`, `debug` (`/tmp/omp-vcc-debug.json`).
94
+ **Pick the right query:**
24
95
 
25
- Optional native strategy: add `vcc` to `COMPACTION_METHOD_CHOICES` in `oh-my-pi` (see `docs/configuration.md#native-strategy`).
96
+ - Find an edit/file: `mode:touched` then drill, or `query:"src/auth.ts"` then `#N:path`.
97
+ - Find a decision: `query:"why did we choose|decision|ADR"` (regex).
98
+ - Find an error/tool output: keyword of the error message — full tool output is searchable.
99
+ - Nothing found in `lineage` but you know it existed: retry `scope:all` (abandoned `/clear` branches are excluded by default).
26
100
 
27
- ## Verification
101
+ **If you get:**
28
102
 
29
- `bunx tsc --noEmit` `bun test` `bun run smoke` `omp plugin link` → `/omp-vcc keep:1` shows `[Session Goal]` toast `omp-vcc: kept 1/5 turns, ~2.1k tok`.
103
+ - `0 matches` try keywords (no regex chars) or `scope:all`; check spelling of path.
104
+ - `truncated — showing 50 of 120 matches, refine…` — narrow regex/keywords.
105
+ - `Page 3 is outside 1-2 (7 matches)…` — use `page` in range or refine.
106
+ - `Cannot expand indices outside active lineage: 42. Use scope:'all'` — add `scope:"all"` or pick index from the same lineage.
107
+ - `#N` outside active lineage → same: retry with `scope:"all"` or use a `lineage` hit.
30
108
 
31
- ## Related
109
+ ## Agent Playbook
32
110
 
33
- - VCC paper `arxiv:2603.29678` three views, AppWorld evaluation (+1.1–4.2 pts, ½–⅔ tokens)
34
- - `sting8k/pi-vcc` `@0.7.0` ported core `extensions/vcc-core/*` verbatim, imports adapted to `@oh-my-pi/*`
35
- - `lllyasviel/VCC` `VCC.py` adaptive `SEP`, `match_lines`, transposed modalities
111
+ 1. **After any compaction, re-orient from V_ui.** Read Session Goal Files Outstanding → brief. Don't re-ask the user for what's already in the summary.
112
+ 2. **Small keep + recall beats large keep.** `/omp-vcc keep:1` + `vcc_recall({query:"auth"})` keeps the focused tail small and lets you pull exact history on demand. Only `keep:3+` when you need verbatim recent context immediately after compaction.
113
+ 3. **Recall before synthesis.** For any question about prior work (file changed, test added, decision made), call `vcc_recall` proportionally to context size: small session → 1 recall with broad keywords; long/complex session → 2–3 targeted recalls (keywords then drill).
114
+ 4. **Create boundaries intentionally.** Before a multi-file refactor or hand-off doc: `/omp-vcc keep:2 continue auth refactor` — next turn starts from a fresh, citable `V_ui`. Verify with `/vcc-stats` (`kept 2/18 turns, 76% saved`) before continuing.
115
+ 5. **Don't stall after threshold.** Auto threshold/overflow compaction auto-continues via invisible follow-up (you'll just see the summary and your next turn proceeds). If you issued `/omp-vcc keep:2 <focus>`, that focus text arrives as the next user message — treat it as the goal.
116
+ 6. **Use pointers, don't re-derive.** When you quote prior work, cite `(#N)` or `(file:s-e)` from the brief; drill `#N:path` for verbatim to paste, not guessed content.
@@ -1,19 +0,0 @@
1
- ---
2
- description: Compact conversation with omp-vcc structured summary (keep:N + optional focus)
3
- ---
4
-
5
- # /omp-vcc
6
-
7
- Algorithmic VCC compaction — fast lossless no-LLM. Registered by `@zhulinchng/omp-vcc` extension.
8
-
9
- Usage:
10
-
11
- - `/omp-vcc` — compact with default keep:1 (smart-keep may boost to keep:N up to 25k tokens)
12
- - `/omp-vcc keep:2` — keep last 2 user turns, summarize the rest
13
- - `/omp-vcc keep:0` — compact all (no tail, sentinel firstKeptEntryId="")
14
- - `/omp-vcc some prompt text` — compact with additional prompt/focus for the summary
15
- - `/omp-vcc keep:2 some prompt text` — both keep and prompt
16
-
17
- This command is handled by the `omp-vcc` extension (`extensions/main.ts`). It delegates to the VCC pipeline (calibrate → smart-keep → budget-cut → normalize → filter-noise → build-sections → brief transcript → format → merge) and writes a structured summary with `[Session Goal]` / `[Files And Changes]` / `[Brief transcript]` sections.
18
-
19
- Arguments: `$ARGUMENTS`
@@ -1,21 +0,0 @@
1
- ---
2
- description: Recall earlier parts of this session via ranked search (V_adapt)
3
- ---
4
-
5
- # /vcc-recall
6
-
7
- Search compacted history — implements VCC `V_adapt` with rho predicate (regex or BM25-like ranked OR).
8
-
9
- Registered by `@zhulinchng/omp-vcc` extension.
10
-
11
- Usage:
12
-
13
- - `/vcc-recall` — show 25 most recent entries
14
- - `/vcc-recall auth token` — keyword search (OR-ranked, TF-IDF)
15
- - `/vcc-recall "hook|inject" scope:all` — regex search across all branches
16
- - `/vcc-recall cache page:2` — paginated results (5 per page)
17
- - `vcc_recall` tool: `{"query":"redis cache","scope":"all","page":1}` — same engine, also supports `mode:'touched'` for file index and `expand:[12,34]` or `#12:path` drill-down
18
-
19
- The tool preserves VCC invariants: role tags, line range pointers `(#N)`, and progressive disclosure `V_ui → V_adapt → V_full[s:e]`.
20
-
21
- Arguments: `$ARGUMENTS`