@geohar/pi-cribsheet 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,188 @@
1
+ // Resolve the `sharedserver` binary, fetching it when none is usable.
2
+ //
3
+ // VENDORED FILE. This is copied byte-identical into the consuming OpenCode plugins
4
+ // (cribsheet, svg-mcp) by their scripts/sync-vendored.sh, so the drift check is a plain
5
+ // diff. There is no way to share it as a dependency without coupling their versions to
6
+ // sharedserver's — see plugins/claude/bin/sharedserver for the same reasoning on the
7
+ // shell side. Edit it HERE; consumers re-sync.
8
+ //
9
+ // Everything repo-specific arrives through ResolveConfig rather than being hardcoded,
10
+ // mirroring bin/sharedserver.conf.
11
+ import { spawnSync } from "node:child_process";
12
+ import { existsSync, mkdirSync, rmSync, statSync, writeFileSync } from "node:fs";
13
+ import { homedir } from "node:os";
14
+ import { join } from "node:path";
15
+ /** Oldest release these plugins are correct against: 0.5.0 added the PID-reuse guard,
16
+ * without which a recycled client PID can hold a server open indefinitely. */
17
+ const HARDCODED_FLOOR = "0.5.0";
18
+ const CANDIDATE_BINARIES = [
19
+ "sharedserver",
20
+ join(homedir(), ".cargo", "bin", "sharedserver"),
21
+ join(homedir(), ".local", "bin", "sharedserver"),
22
+ "/usr/local/bin/sharedserver",
23
+ "/opt/homebrew/bin/sharedserver",
24
+ ];
25
+ function parseVersion(text) {
26
+ const m = text.match(/(\d+)\.(\d+)\.(\d+)/);
27
+ return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : undefined;
28
+ }
29
+ function gte(a, b) {
30
+ for (let i = 0; i < 3; i++)
31
+ if (a[i] !== b[i])
32
+ return a[i] > b[i];
33
+ return true;
34
+ }
35
+ function versionOf(candidate, env) {
36
+ const r = spawnSync(candidate, ["--version"], { env });
37
+ if (r.error)
38
+ return undefined;
39
+ return parseVersion(`${r.stdout?.toString() ?? ""}${r.stderr?.toString() ?? ""}`);
40
+ }
41
+ /** Synchronous sleep — this path runs before anything can be awaited, so the lock wait
42
+ * cannot use timers. Atomics.wait blocks without spinning. */
43
+ function sleepSync(ms) {
44
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
45
+ }
46
+ /** First sharedserver found at ANY version, or undefined. */
47
+ function probeAny(override, env) {
48
+ const candidates = [override, env.SHAREDSERVER_BIN, ...CANDIDATE_BINARIES].filter((v) => typeof v === "string" && v.length > 0);
49
+ for (const candidate of candidates) {
50
+ if (candidate.includes("/")) {
51
+ if (existsSync(candidate))
52
+ return candidate;
53
+ continue;
54
+ }
55
+ // Presence is "spawn did not fail", NOT "exited 0" — a build that rejects some
56
+ // flag is still present.
57
+ if (!spawnSync(candidate, ["--version"], { stdio: "ignore", env }).error)
58
+ return candidate;
59
+ }
60
+ return undefined;
61
+ }
62
+ /** Download and run the cargo-dist installer, then re-probe. */
63
+ function installPinned(cfg, url, what, env, log, toast) {
64
+ // The SAME lock directory as the shell shim — cross-client coordination, so a
65
+ // Claude session and an OpenCode session starting together do not race.
66
+ const lockdir = join(env.TMPDIR || "/tmp", ".sharedserver-install.lock");
67
+ let haveLock = false;
68
+ try {
69
+ mkdirSync(lockdir);
70
+ haveLock = true;
71
+ }
72
+ catch {
73
+ const deadline = Date.now() + 20_000;
74
+ while (existsSync(lockdir) && Date.now() < deadline)
75
+ sleepSync(250);
76
+ const after = probeAny(undefined, env);
77
+ if (after)
78
+ return after;
79
+ try {
80
+ mkdirSync(lockdir);
81
+ haveLock = true;
82
+ }
83
+ catch {
84
+ log?.("warn", `${cfg.label}: another process is installing sharedserver and did not finish; remove the stale lock if this persists: ${lockdir}`);
85
+ return undefined;
86
+ }
87
+ }
88
+ try {
89
+ // Re-probe under the lock: the winner may have finished while we waited.
90
+ const already = probeAny(undefined, env);
91
+ const floor = parseVersion(cfg.minVersion ?? HARDCODED_FLOOR);
92
+ if (already) {
93
+ const v = versionOf(already, env);
94
+ if (v && floor && gte(v, floor))
95
+ return already;
96
+ }
97
+ // Download to a file, then run it. `curl … | sh` would report success on a 404,
98
+ // since a pipeline's status is sh's; spawnSync uses no shell, so this is exact.
99
+ const script = join(lockdir, "installer.sh");
100
+ const dl = spawnSync("curl", ["--proto", "=https", "--tlsv1.2", "-LsSf", url, "-o", script], { env });
101
+ if (dl.error || dl.status !== 0) {
102
+ const msg = `${cfg.label}: could not download the sharedserver installer (${what})`;
103
+ log?.("warn", msg);
104
+ toast?.("warning", msg);
105
+ return undefined;
106
+ }
107
+ if (!existsSync(script) || statSync(script).size === 0) {
108
+ log?.("warn", `${cfg.label}: the downloaded sharedserver installer was empty`);
109
+ return undefined;
110
+ }
111
+ // cargo-dist embeds the expected sha256 but looks up only `sha256sum` and SKIPS
112
+ // verification when missing — i.e. always, on macOS. Give it one (identical
113
+ // output) rather than execute an unverified binary.
114
+ let runEnv = env;
115
+ if (spawnSync("sh", ["-c", "command -v sha256sum"], { stdio: "ignore" }).status !== 0) {
116
+ if (spawnSync("sh", ["-c", "command -v shasum"], { stdio: "ignore" }).status !== 0) {
117
+ log?.("warn", `${cfg.label}: refusing to install sharedserver — no sha256sum or shasum to verify the download`);
118
+ return undefined;
119
+ }
120
+ writeFileSync(join(lockdir, "sha256sum"), '#!/bin/sh\nexec shasum -a 256 "$@"\n', { mode: 0o755 });
121
+ runEnv = { ...env, PATH: `${lockdir}:${env.PATH ?? ""}` };
122
+ }
123
+ const run = spawnSync("sh", [script], { env: runEnv, stdio: "ignore" });
124
+ if (run.error || run.status !== 0) {
125
+ log?.("warn", `${cfg.label}: the sharedserver installer ran but failed`);
126
+ return undefined;
127
+ }
128
+ return probeAny(undefined, env);
129
+ }
130
+ finally {
131
+ if (haveLock) {
132
+ try {
133
+ rmSync(lockdir, { recursive: true, force: true });
134
+ }
135
+ catch {
136
+ /* best effort — a leaked lock costs the next run its bounded wait */
137
+ }
138
+ }
139
+ }
140
+ }
141
+ /** Resolve the sharedserver binary, fetching a release when nothing usable is present.
142
+ *
143
+ * Behaviourally identical to plugins/claude/bin/sharedserver — same ladder, floor,
144
+ * lock and degrade-rather-than-die fallback, so a user running both clients gets the
145
+ * same answer to "which sharedserver am I on, and why". */
146
+ export function resolveSharedserver(cfg, override, env, log, toast) {
147
+ const label = cfg.label ?? "sharedserver";
148
+ const minVersion = cfg.minVersion ?? cfg.pkgVersion ?? HARDCODED_FLOOR;
149
+ const url = cfg.installerUrl ??
150
+ (cfg.pkgVersion
151
+ ? `https://github.com/georgeharker/sharedserver/releases/download/v${cfg.pkgVersion}/sharedserver-installer.sh`
152
+ : "https://github.com/georgeharker/sharedserver/releases/latest/download/sharedserver-installer.sh");
153
+ const what = cfg.installerUrl || !cfg.pkgVersion ? "the latest release" : `v${cfg.pkgVersion}`;
154
+ const resolved = { ...cfg, label, minVersion };
155
+ const floor = parseVersion(minVersion);
156
+ // An explicit binary / SHAREDSERVER_BIN is never second-guessed: the user named a
157
+ // specific one, so quietly downloading a different one is the wrong answer.
158
+ const explicit = override ?? env.SHAREDSERVER_BIN;
159
+ if (explicit) {
160
+ const present = explicit.includes("/")
161
+ ? existsSync(explicit)
162
+ : !spawnSync(explicit, ["--version"], { stdio: "ignore", env }).error;
163
+ if (present) {
164
+ const v = versionOf(explicit, env);
165
+ if (v && floor && !gte(v, floor)) {
166
+ const msg = `${label}: sharedserver at ${explicit} is ${v.join(".")}; this plugin expects >= ${minVersion}. Using it anyway because you set it explicitly.`;
167
+ log?.("warn", msg);
168
+ toast?.("warning", msg);
169
+ }
170
+ return explicit;
171
+ }
172
+ }
173
+ const found = probeAny(override, env);
174
+ if (found) {
175
+ const v = versionOf(found, env);
176
+ if (v && floor && gte(v, floor))
177
+ return found;
178
+ // Too old: fetch, but remember this one — if the download fails we would rather
179
+ // run the old binary than nothing.
180
+ const msg = `${label}: sharedserver at ${found} is ${v?.join(".") ?? "an unknown version"}; this plugin expects >= ${minVersion}. Fetching ${what}. To keep your own build, set SHAREDSERVER_BIN=${found}`;
181
+ log?.("warn", msg);
182
+ toast?.("warning", msg);
183
+ return installPinned(resolved, url, what, env, log, toast) ?? found;
184
+ }
185
+ log?.("info", `${label}: no usable sharedserver found; fetching ${what} (one time). This needs no Rust toolchain.`);
186
+ return installPinned(resolved, url, what, env, log, toast);
187
+ }
188
+ //# sourceMappingURL=sharedserver-resolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sharedserver-resolve.js","sourceRoot":"","sources":["../src/sharedserver-resolve.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,140 @@
1
+ # Memory (cribsheet)
2
+
3
+ <!-- Canonical reach-for-crib directive. Copy into your global
4
+ $CLAUDE_CONFIG_DIR/CLAUDE.md, or let a cribsheet plugin inject it each session
5
+ (the plugins symlink this file). It directs *approach*; the MCP tool
6
+ descriptions carry the mechanics. -->
7
+
8
+ Five rules. Each fires on a **cue you can catch yourself in the middle of**, not on
9
+ a topic you have to remember to think about. That's deliberate: you will not remember
10
+ to "consult memory" — but you can notice your own hand reaching for `grep`.
11
+
12
+ Rules 4 and 5 are the ones you will walk past most, because their cues don't feel like
13
+ actions: settling a design question and writing a todo list both feel like *thinking*,
14
+ not like *reaching for a tool*. Watch for them anyway.
15
+
16
+ ### 1. About to grep? That's the cue.
17
+
18
+ `grep` / `rg` / `Glob`, or opening a file to see what it does, is a **stop sign, not
19
+ an action**. At that exact moment:
20
+
21
+ | your next move was | do this instead |
22
+ |---|---|
23
+ | grep for a symbol — by name, or by hunch | `code_lookup "what it does, in your own words"` — answers by intent, across files |
24
+ | open the file to understand a symbol | `code_dossier <symbol>` — signature, description, callers, callees, references, in one call |
25
+ | chain greps to trace a caller | `code_xref` / `code_graph` |
26
+ | *(repo isn't indexed)* | `project_index` — one call, and the **expected first step**, not a dead end |
27
+
28
+ grep is the **fallback for when the index misses**. It is never the opening move.
29
+ There is no shortcut around indexing; the utility *is* the index.
30
+
31
+ **Catch yourself:** if you've read three files to answer one question, this rule
32
+ already fired and you walked past it. Stop where you are, `code_lookup`, redo it.
33
+
34
+ ### 2. About to answer from your own knowledge? Look it up first.
35
+
36
+ Cue: you're about to explain a project, a past decision, or a convention — or open a
37
+ codebase cold. `lookup` / `apropos` **before** you answer, not after. What's stored may
38
+ be more current than what you'd reconstruct, and *you can't tell which from the inside*:
39
+ a confident reconstruction feels exactly like a correct one.
40
+
41
+ ### 3. Just understood something? Store it before you move on.
42
+
43
+ Cue: the "ah, *that's* why" moment — a convention, a gotcha, a contract. The
44
+ moment passes; the note has to be written inside it. `lookup` first, then `append`/`edit`
45
+ over creating a near-duplicate.
46
+
47
+ **Not a decision, and not a task.** Those have their own verbs — rules 4 and 5. If what
48
+ you just understood is *why we chose this*, `note_store` is the wrong verb: it takes the
49
+ prose and silently drops the edges, which were the part worth keeping.
50
+
51
+ Writes need an explicit `project=` — they do **not** inherit your current project. Name
52
+ the project the fact is *about*: the repo you're in, or `default` (or that tool's own
53
+ project) for cross-cutting knowledge like a CLI or editor convention. Say where it landed.
54
+ To pin a hard-won insight about a symbol: `code_append <symbol> "…"`.
55
+
56
+ ### 4. Just settled a question — or about to explain why? Use the decision graph.
57
+
58
+ Cue: a design question stopped being open. "Chunks are keyed by X." "Writes must name
59
+ their project." It's the moment the arguing stops — and it feels like you merely
60
+ *finished thinking*, which is exactly why this rule gets walked past.
61
+
62
+ Decisions have their **own verbs**. Use those, not `note_*`: a note holds the prose,
63
+ but only the facet knows what a decision rests on, what rests on it, and what your
64
+ change just invalidated. That last part is the whole point.
65
+
66
+ | your next move was | do this instead |
67
+ |---|---|
68
+ | a question just got settled, and it has consequences | `design_add <title> <rationale>` with `--dep` on what it builds on |
69
+ | the new decision *replaces* an existing one | `design_supersede` — a second `design_add` leaves both standing and both findable |
70
+ | explain *why* it's built this way | `design_lookup "<query>"` **before** reconstructing it from the code — hits flag stale decisions |
71
+ | read / cite a decision | `design_read <ref>` — body **plus** deps, dependents, and whether either moved |
72
+ | change a decision, or code that implements one | `design_check` **first** — then `design_edit`/`design_append`, whose result lists what you just tainted |
73
+ | propose an architecture change | `design_tree` (or `--dependents`) — see what rests on it before you move it |
74
+
75
+ - **Adding is SILENT — so check what it lands next to.** `design_add` seeds its
76
+ `checked` state from its deps as they read right now, so a new decision is
77
+ born verified and nothing gains taint just because it appeared. The graph will
78
+ hold two decisions that flatly contradict each other and never say a word: it
79
+ tracks the edges you declared, not agreement. So `design_lookup` the area
80
+ first, and read the `similar` list the result hands back. Restates an existing
81
+ decision → `design_append`/`design_edit` **that one**. Replaces it →
82
+ `design_supersede`. Changes the ground under other work → `design_check`,
83
+ which is the only verb that answers "what did I just break".
84
+ - **A dep is a promise**: *if that changes, reconsider this.* A plain note can't
85
+ make that promise; the graph can. Every edge checks — there is no "just for
86
+ reference" edge that lets an origin change silently.
87
+ - **Each stale decision** comes back with the chain explaining *why*, what kind of
88
+ change did it, when, and the verb to run. After reconsidering, `design_reaffirm`
89
+ marks it current. Taint means *a dep moved*, not *you were wrong* — reaffirming
90
+ is the cheap, normal ending, so don't treat it as a crisis. When it genuinely no
91
+ longer holds, `design_supersede`.
92
+ - **A hit marked `tainted`** is a decision whose ground shifted and nobody
93
+ re-read. Don't quietly reason from it — `design_read` it first.
94
+
95
+ **Catch yourself:** if you've just written a paragraph explaining why something is
96
+ built the way it is, you either read that out of the graph or you owe it to the
97
+ graph. Same for "let's go with X" — that's a `design_add`, not a sentence.
98
+
99
+ ### 5. About to write a todo list — in a file, in chat, or in your head? That's the cue.
100
+
101
+ `plan_add` instead. An in-chat list dies with the chat and a list in your head dies
102
+ with the context window; a plan item outlives both. One call takes the whole batch,
103
+ and deps can point at earlier items in the same batch.
104
+
105
+ - **`plan_lookup` before you add — it may already be planned.** Another session,
106
+ or another agent, may have written the item you're about to write. An item
107
+ that duplicates a `done` one is louder than a duplicate: either the work
108
+ regressed or you've mis-scoped this, and both are worth knowing *before* a
109
+ second entry exists.
110
+ - **`--dep` the DECISION an item rests on, not just the items before it.** Deps
111
+ mix, and they block differently: a **plan** dep blocks until it's done; a
112
+ **design** dep blocks while it's *tainted*; a **note** dep never blocks. So an
113
+ item that declares the decision it implements drops out of `plan_next` the
114
+ moment that decision's ground moves — the work stops looking actionable
115
+ exactly when it stops being safe to do. That is the plan facet's version of
116
+ "check it didn't break", and it is the only one that fires without you asking.
117
+ - **`plan_status` as you go, not at the end** — `in-progress` is a *claim* other
118
+ agents can see, and marking one `done` tells you what it just unblocked.
119
+ - **`plan_next` is the "where was I" call** — run it when you pick work back up, and
120
+ any future session (yours or another agent's) gets exactly what's actionable now.
121
+
122
+ **Catch yourself:** if you've just typed "first I'll… then I'll…", or reached for a
123
+ scratch todo list, this rule already fired.
124
+
125
+ ### Importing a doc (either facet)
126
+
127
+ Asked to capture a DESIGN.md, a spec, or a plan file? `design_import` / `plan_import`
128
+ that doc, then follow the procedure it returns. They run no model and write nothing —
129
+ they hand you the doc's exact citable sections, what already draws on it, and the
130
+ steps. *You* read and judge. Each entry cites the **section** it came from
131
+ (`--source "DESIGN.md#10.3 Fusion"`, never the whole file), so a later edit to that
132
+ passage re-checks exactly what was drawn from it. Extracted decisions land
133
+ `proposed`: they gate work and taint nothing until a human confirms them with
134
+ `design_promote`.
135
+
136
+ <!-- Project selection for *reads*: code tools act on ONE current project — set it once
137
+ with `use_project <name>`, or it's inferred from `project_path` on your first code
138
+ call; reads then need no project args. To read a *different* project you must name it
139
+ (`project=` / `project_path=`). `project_path` means "which repo I mean" — it is not
140
+ your shell cwd. -->
@@ -0,0 +1,7 @@
1
+ {
2
+ "mcpServers": {
3
+ "cribsheet": {
4
+ "url": "http://127.0.0.1:7732/mcp"
5
+ }
6
+ }
7
+ }
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@geohar/pi-cribsheet",
3
+ "version": "0.0.0",
4
+ "description": "Pi extension: run the crib (cribsheet) memory + code-index MCP server via sharedserver, inject the reach-for-crib directive, and ship the /crib recall command. Pairs with pi-mcp-adapter.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "pi": {
9
+ "extensions": [
10
+ "./src/index.ts"
11
+ ]
12
+ },
13
+ "files": [
14
+ "src",
15
+ "dist",
16
+ "instructions.txt",
17
+ "mcp.json.example",
18
+ "README.md",
19
+ "LICENSE"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsc",
23
+ "clean": "rm -rf dist",
24
+ "typecheck": "tsc --noEmit",
25
+ "prepack": "node -e \"require('fs').copyFileSync('../../CLAUDE.md.example', './instructions.txt')\"",
26
+ "prepublishOnly": "npm run clean && npm run build"
27
+ },
28
+ "keywords": [
29
+ "pi",
30
+ "pi-extension",
31
+ "pi-mono",
32
+ "mcp",
33
+ "cribsheet",
34
+ "crib",
35
+ "memory",
36
+ "sharedserver",
37
+ "extension"
38
+ ],
39
+ "author": "George Harker <george@georgeharker.com>",
40
+ "license": "MIT",
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "git+https://github.com/georgeharker/cribsheet.git",
44
+ "directory": "plugins/pi"
45
+ },
46
+ "publishConfig": {
47
+ "access": "public"
48
+ },
49
+ "devDependencies": {
50
+ "@types/node": "^22.0.0",
51
+ "typescript": "^5.6.0"
52
+ },
53
+ "engines": {
54
+ "node": ">=18.0.0"
55
+ }
56
+ }