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/README.md +67 -59
- package/extensions/main.ts +20 -8
- package/extensions/vcc-core/core/migrate-stale.ts +162 -0
- package/extensions/vcc-core/core/settings.ts +11 -0
- package/extensions/vcc-core/details.ts +10 -1
- package/extensions/vcc-core/hook.ts +336 -30
- package/package.json +14 -10
- package/scripts/e2e.ts +131 -0
- package/scripts/smoke.ts +21 -1
- package/scripts/uninstall-reset.js +251 -22
- package/skills/omp-vcc/SKILL.md +101 -20
- package/commands/omp-vcc.md +0 -19
- package/commands/vcc-recall.md +0 -21
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
|
-
|
|
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
|
|
4
|
-
*
|
|
5
|
-
* Marker: ~/.config/@zhulinchng/omp-vcc/.ownership.json with { state: "owned"
|
|
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
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
"
|
|
17
|
-
".
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
}
|
package/skills/omp-vcc/SKILL.md
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
63
|
+
```sh
|
|
64
|
+
# basic keyword (TF-IDF OR)
|
|
65
|
+
vcc_recall({query:"redis cache"})
|
|
66
|
+
/vcc-recall redis cache
|
|
6
67
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
-
|
|
68
|
+
# regex (contains | * + ? {} () [] \ ^ $ .)
|
|
69
|
+
vcc_recall({query:"hook|inject"})
|
|
70
|
+
/vcc-recall hook|inject
|
|
10
71
|
|
|
11
|
-
|
|
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
|
-
|
|
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
|
-
|
|
80
|
+
# file index — what was touched, not text search
|
|
81
|
+
vcc_recall({query:"", mode:"touched"})
|
|
82
|
+
/vcc-recall touched mode:touched
|
|
16
83
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
90
|
+
# expand multiple turns by index (from a prior recall's #N)
|
|
91
|
+
vcc_recall({query:"", expand:[12,18,25]})
|
|
92
|
+
```
|
|
22
93
|
|
|
23
|
-
|
|
94
|
+
**Pick the right query:**
|
|
24
95
|
|
|
25
|
-
|
|
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
|
-
|
|
101
|
+
**If you get:**
|
|
28
102
|
|
|
29
|
-
|
|
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
|
-
##
|
|
109
|
+
## Agent Playbook
|
|
32
110
|
|
|
33
|
-
-
|
|
34
|
-
|
|
35
|
-
|
|
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.
|
package/commands/omp-vcc.md
DELETED
|
@@ -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`
|
package/commands/vcc-recall.md
DELETED
|
@@ -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`
|