sphica 0.5.0 → 0.5.2

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://anthropic.com/claude-code/plugin.schema.json",
3
3
  "name": "sphica",
4
- "version": "0.5.0",
4
+ "version": "0.5.2",
5
5
  "description": "Records Claude Code and Codex sessions on your machine and keeps past implementation and decisions, with their sources, for your agent to find.",
6
6
  "author": {
7
7
  "name": "iroha924",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sphica",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Records Claude Code and Codex sessions on your machine and keeps past implementation and decisions, with their sources, for your agent to find.",
5
5
  "author": {
6
6
  "name": "iroha924",
package/README.md CHANGED
@@ -11,17 +11,17 @@ English | [日本語](https://github.com/iroha924/sphica/blob/main/README.ja.md)
11
11
 
12
12
  **Local memory of past implementation and decisions for Claude Code and Codex.**
13
13
  Sphica records your coding sessions, and keeps what was decided, rejected, deferred, and built, each record quoting the words it came from.
14
- Your agent finds those records when it searches, and sees the relevant ones on its own before it edits a file they apply to.
14
+ Your agent finds those records when it searches, and sees the relevant ones on its own when it reads or edits a file they apply to (in Codex, before a shell command that names the file and before `apply_patch`).
15
15
  The database is a single SQLite file on your machine.
16
16
 
17
17
  ## Features
18
18
 
19
19
  - **Automatic recording.** Sphica keeps your prompts, the agent's final reply for each turn, and the paths of the files a turn changed (by the edit tools, or seen in `git status` at the turn's end).
20
20
  - **Records with their sources.** `/sphica:trace` turns a session into records: decisions with the options rejected and why, constraints, implementations, findings, dead ends, and open questions. Every record quotes the exact words it came from, and a decision counts as adopted only when you said so.
21
- - **Pull requests too.** `/sphica:harvest <number>` keeps a GitHub pull request (body, comments, reviews, review comments, commits, and the issues it closes) and records what it decided. A reviewer's suggestion stays a proposal unless the owner or a maintainer adopted it; a merge alone adopts nothing.
21
+ - **Pull requests too.** `/sphica:harvest <number>` keeps a GitHub pull request (body, comments, reviews, review comments, commits, and up to five issues the body says it closes) and records what it decided. A reviewer's suggestion stays a proposal unless the owner or a maintainer adopted it; a merge alone adopts nothing.
22
22
  - **Evidence found later.** `/sphica:glean` adds evidence and corrections to existing records. It asks you for the source (an issue URL, the file and line, meeting notes) before saving; a claim without one is kept only as unsourced and never used as fact.
23
- - **Shown when it matters (Claude Code).** At session start, the current work; before an edit, the active decisions anchored to that file; when your prompt names a recorded option or code symbol, that record.
24
- - **Search in Japanese and English.** Records carry search words in both languages, so a question in one finds a record written in the other.
23
+ - **Shown when it matters.** At session start, the current work; before the agent reads or edits a file, or runs a shell command that names it, the decisions tied to that file; when your prompt names a recorded option or code symbol, that record. Works in both Claude Code and Codex.
24
+ - **Search in Japanese and English.** Records are made with search words in both languages, so a question in either language is more likely to find them.
25
25
  - **Reviews check past decisions.** `/sphica:review` runs a reviewer per focus (correctness, security, written conventions, and past decisions by default; redundancy with `full`), and checks the diff against the records it touches.
26
26
 
27
27
  Records are never rewritten: a correction is a new record that supersedes the old one, and the history stays.
@@ -60,7 +60,7 @@ codex plugin marketplace add iroha924/sphica --ref main
60
60
  codex plugin add sphica@sphica
61
61
  ```
62
62
 
63
- In Codex, open `/hooks` and mark Sphica's hooks as trusted. Nothing is recorded until you do. If a plugin update changes the hooks, trust them again.
63
+ In Codex, open `/hooks` and mark Sphica's hooks as trusted. Automatic recording does not start until you do. If a plugin update changes the hooks, trust them again.
64
64
 
65
65
  **3. Set up in your repository**
66
66
 
@@ -81,7 +81,7 @@ sphica doctor
81
81
 
82
82
  ## Quick start
83
83
 
84
- Sphica records sessions only in repositories you register (projects); run `sphica init` in each one.
84
+ Only sessions in repositories you register (projects) go into Sphica's database; run `sphica init` in each one.
85
85
 
86
86
  Work as usual. At the end of a session with something worth keeping, run `/sphica:trace` (`$sphica:trace` in Codex).
87
87
  `/sphica:trace pending` lists earlier sessions not traced yet. To keep what a pull request decided, run `/sphica:harvest 123`.
@@ -99,21 +99,21 @@ Without being asked, Sphica adds a few past records to what the agent sees, each
99
99
 
100
100
  - At session start: the current work and project-wide constraints.
101
101
  - On a prompt that names a recorded code symbol, file path, or option.
102
- - Before the agent reads or edits a file a decision applies to. A read shows each record once per session.
103
- - Before a review. When you run your own review command (any name containing `review`, or a name listed in the `SPHICA_REVIEW_COMMANDS`
104
- environment variable, comma-separated), it gets the decisions your local change touches. `/sphica:review` checks them itself. Claude Code only.
102
+ - Before the agent reads or edits a file a decision applies to, and before a shell command that names such a file (naming it is not proof the command reads it). A read shows each record once per session.
103
+ - Before a review (Claude Code only). When you run your own review command (any name containing `review`, or a name listed in the `SPHICA_REVIEW_COMMANDS`
104
+ environment variable, comma-separated), it gets the decisions your local change touches. `/sphica:review` checks them itself.
105
105
 
106
- In Codex the same happens at session start, on a prompt, before an `apply_patch` edit, and before a shell command that names such a file.
107
- Edits made through shell commands are not covered, and there is no review hook: run `$sphica:review`.
106
+ In Codex the same happens at session start, on a prompt, before an `apply_patch` edit, and before a shell command that names such a file (Codex reads files through shell commands, so this covers reads).
107
+ There is no review hook in Codex: run `$sphica:review`.
108
108
 
109
- The agent searches with Sphica's `search` and opens full records with `read`. `status` tells it how much of the history has been traced, so an empty search is not mistaken for "never decided".
109
+ The agent searches with Sphica's `search` and opens full records with `read`. `status` tells it how much of the history has been traced, so an empty search is less likely to be mistaken for "never decided".
110
110
 
111
111
  ## What gets recorded and where it goes
112
112
 
113
- - **Where.** The database is `~/.sphica/sphica.db`. Records wait in a local queue, `~/.sphica/spool`, until they are written to it. Each machine has its own database; nothing is shared between machines.
114
- - **What.** Your prompts, the agent's final reply for each turn, and the paths of changed files. Background-task notifications and messages from other agents are skipped when Sphica recognizes their format. Replies in the middle of a turn, and files created and deleted within one turn, are not seen.
113
+ - **Where.** The database is `~/.sphica/sphica.db`. Captured sessions wait in a local queue, `~/.sphica/spool`, until they are written to it. Each machine has its own database; nothing is shared between machines.
114
+ - **What.** Your prompts, the agent's final reply for each turn, and the paths of changed files. Background-task notifications and messages from other agents are skipped when Sphica recognizes their format. Replies in the middle of a turn are not kept, nor are files created and deleted within one turn without the edit tools.
115
115
  - **What was shown.** Each automatic delivery is logged by which records it showed, not their text.
116
- - **Unregistered repositories.** Sessions in a repository you have not registered stay in the queue and are written after you register it. Held records are dropped after 30 days, and when more than 1,000 are waiting the oldest go first.
116
+ - **Unregistered repositories.** Sessions in a repository you have not registered stay in the queue and are written after you register it. Held sessions are dropped after 30 days, and when more than 1,000 are waiting the oldest go first.
117
117
  - **Secrets.** Only secrets with a recognizable shape are masked:
118
118
  - keys with known prefixes
119
119
  - `KEY=…` and `"password": …` assignments
@@ -123,20 +123,16 @@ The agent searches with Sphica's `search` and opens full records with `read`. `s
123
123
 
124
124
  **Anything else is stored as typed, so do not paste secrets into a session.**
125
125
  - **Network.** Sphica has no account, no hosted service, and no telemetry, and makes no network connections itself. `/sphica:harvest` and `/sphica:glean` run `gh api` with your credentials to read pull requests and issues, and `sphica doctor` runs `npm` and `claude` to check installed versions.
126
- - **Text written by others.** Pull request and issue text may come from anyone. It is kept as a source and passed to the agent as data, never as instructions, and only the owner's or a maintainer's words can adopt a decision.
126
+ - **Text written by others.** Pull request and issue text may come from anyone. It is kept as a source and passed to the agent as data, never as instructions, and only your words, or those of the repository's owner or a maintainer, can adopt a decision.
127
127
 
128
- ## Limits in 0.5.0
128
+ ## What Sphica can't do yet
129
129
 
130
- - Structured records exist only for what you traced, harvested, or gleaned. Everything else is searchable only as captured text (`search` with `sources: true`).
131
- - In Codex, a shell command that names a file gets its decisions even when the command does not read it, and edits made through shell commands get none.
132
- - In Codex, `$sphica:trace`, `$sphica:harvest`, and `$sphica:glean` write only when Codex tells Sphica which directory the session is in. Codex 0.157.1 does, through an experimental MCP capability; if a later Codex stops, they stop with a message and write nothing.
133
- - Showing a record does not make the agent follow it. In our evaluation Codex received and found an earlier decision against a request, and still carried out the request as asked.
134
- - A code location in a record is checked against your working tree when it is read ("located", "moved", "missing"). A located symbol does not prove the decision still holds.
135
-
136
- ## Upgrading from 0.4
137
-
138
- 0.5.0 keeps records in a new format. A 0.4 database is refused and left unchanged; its records are not carried over.
139
- Move `~/.sphica/sphica.db` aside (keep it if you want the old data), then run `sphica init` again in each repository.
130
+ - Only what you traced, harvested, or gleaned becomes a structured record. The rest of a conversation is searchable as captured text (`search` with `sources: true`).
131
+ - For shell commands, Sphica only sees whether a command names a file. It may show decisions for a file the command never reads, and a shell command that edits a file is not treated as an edit (in Codex, a patch passed to `apply_patch` through the shell is treated as an edit).
132
+ - Commands run through Claude Code's PowerShell tool (Windows without Git Bash) do not get decisions yet.
133
+ - In Codex, `$sphica:trace`, `$sphica:harvest`, and `$sphica:glean` can write only when Codex tells Sphica which directory the session is in. Current Codex does. When it does not, they write nothing and tell you why.
134
+ - Showing a record does not make the agent follow it.
135
+ - A code location in a record is checked against your working tree when it is read. Finding the code name (a function name, say) the record points to does not mean the decision still holds.
140
136
 
141
137
  ## Updating
142
138
 
@@ -200,11 +196,11 @@ Everything else runs inside Claude Code and Codex, through the `/sphica:*` comma
200
196
 
201
197
  Report vulnerabilities privately as described in [SECURITY.md](https://github.com/iroha924/sphica/blob/main/SECURITY.md).
202
198
 
203
- Since 0.37.1, each release is built by GitHub Actions from a tag on the head of a pull request whose CI has passed, and staged on npm.
199
+ Each release is built by GitHub Actions from a tag on the head of a pull request whose CI has passed, and staged on npm.
204
200
  The maintainer checks its SHA-512 checksum and provenance, then approves publication with two-factor authentication.
205
- For these versions, the [npm page](https://www.npmjs.com/package/sphica#provenance) links to the workflow and the commit each one was built from.
201
+ The [npm page](https://www.npmjs.com/package/sphica#provenance) links to the workflow and the commit each release was built from.
206
202
 
207
- Dependabot opens pull requests to update the GitHub Actions used in CI. It does not cover the npm dependencies bundled into the package, because Dependabot cannot read the Bun lockfile format (v2) this repository uses.
203
+ Dependabot opens weekly pull requests to update the GitHub Actions used in CI, and Renovate opens monthly ones for the npm dependencies bundled into the package.
208
204
 
209
205
  ## Contributing
210
206
 
package/dist/capture.js CHANGED
@@ -3,7 +3,6 @@
3
3
  // server/src/capture.ts
4
4
  import { spawn } from "node:child_process";
5
5
  import fs4 from "node:fs";
6
- import os3 from "node:os";
7
6
  import path5 from "node:path";
8
7
 
9
8
  // server/node_modules/kysely/dist/util/object-utils.js
@@ -10815,7 +10814,8 @@ import path from "node:path";
10815
10814
  import { constants as C, DatabaseSync } from "node:sqlite";
10816
10815
  var SCHEMA_GENERATION = 2;
10817
10816
  var SCHEMA_REVISION = 1;
10818
- var dbFile = () => process.env.SPHICA_DB || path.join(os.homedir(), ".sphica", "sphica.db");
10817
+ var sphicaHome = () => process.env.SPHICA_HOME || path.join(os.homedir(), ".sphica");
10818
+ var dbFile = () => process.env.SPHICA_DB || path.join(sphicaHome(), "sphica.db");
10819
10819
  function requireRuntime() {
10820
10820
  const proto = DatabaseSync.prototype;
10821
10821
  if (typeof proto.setAuthorizer !== "function" || typeof proto.enableDefensive !== "function")
@@ -11287,7 +11287,6 @@ var plain2 = (s) => visible(stripVTControlCharacters(s.replace(STRING_SEQUENCE,
11287
11287
  // server/src/project.ts
11288
11288
  import { execFileSync } from "node:child_process";
11289
11289
  import fs2 from "node:fs";
11290
- import os2 from "node:os";
11291
11290
  import path3 from "node:path";
11292
11291
 
11293
11292
  // server/src/anchors.ts
@@ -11296,7 +11295,7 @@ var MAX_BYTES = 2 * 1024 * 1024;
11296
11295
  var leaves = (rel) => rel === ".." || rel.startsWith(`..${path2.sep}`) || path2.isAbsolute(rel);
11297
11296
 
11298
11297
  // server/src/project.ts
11299
- var localFile = () => path3.join(os2.homedir(), ".sphica", "projects.json");
11298
+ var localFile = () => path3.join(sphicaHome(), "projects.json");
11300
11299
  var LOCAL_KEY = /^[a-z0-9][a-z0-9._-]*$/;
11301
11300
  function normalizeRemote(url) {
11302
11301
  const raw = String(url ?? "").trim();
@@ -11439,13 +11438,13 @@ function changed(root, before, after) {
11439
11438
  }
11440
11439
 
11441
11440
  // server/src/capture.ts
11442
- var spoolDir = () => path5.join(os3.homedir(), ".sphica", "spool");
11443
- var stateFile = () => path5.join(os3.homedir(), ".sphica", "capture.json");
11441
+ var spoolDir = () => path5.join(sphicaHome(), "spool");
11442
+ var stateFile = () => path5.join(sphicaHome(), "capture.json");
11444
11443
  var rejectedDir = () => path5.join(spoolDir(), "rejected");
11445
11444
  var unregisteredDir = () => path5.join(spoolDir(), "unregistered");
11446
11445
  var HOLD_DAYS = 30;
11447
11446
  var HOLD_MAX = 1000;
11448
- var baselineDir = () => path5.join(os3.homedir(), ".sphica", "worktree");
11447
+ var baselineDir = () => path5.join(sphicaHome(), "worktree");
11449
11448
  function current(raw) {
11450
11449
  const r = raw;
11451
11450
  if (r.v === 2)
package/dist/cli.js CHANGED
@@ -93,7 +93,6 @@ var require_src = __commonJS(function(exports, module) {
93
93
 
94
94
  // server/src/cli.ts
95
95
  import fs8 from "node:fs";
96
- import os5 from "node:os";
97
96
  import path9 from "node:path";
98
97
 
99
98
  // server/node_modules/@clack/core/dist/index.mjs
@@ -14181,7 +14180,8 @@ import path2 from "node:path";
14181
14180
  import { constants as C, DatabaseSync } from "node:sqlite";
14182
14181
  var SCHEMA_GENERATION = 2;
14183
14182
  var SCHEMA_REVISION = 1;
14184
- var dbFile = () => process.env.SPHICA_DB || path2.join(os.homedir(), ".sphica", "sphica.db");
14183
+ var sphicaHome = () => process.env.SPHICA_HOME || path2.join(os.homedir(), ".sphica");
14184
+ var dbFile = () => process.env.SPHICA_DB || path2.join(sphicaHome(), "sphica.db");
14185
14185
  function requireRuntime() {
14186
14186
  const proto = DatabaseSync.prototype;
14187
14187
  if (typeof proto.setAuthorizer !== "function" || typeof proto.enableDefensive !== "function")
@@ -14522,7 +14522,6 @@ var leaves = (rel) => rel === ".." || rel.startsWith(`..${path4.sep}`) || path4.
14522
14522
  // server/src/capture.ts
14523
14523
  import { spawn } from "node:child_process";
14524
14524
  import fs6 from "node:fs";
14525
- import os3 from "node:os";
14526
14525
  import path7 from "node:path";
14527
14526
 
14528
14527
  // server/src/knowledge.ts
@@ -14533,7 +14532,7 @@ import { execFileSync } from "node:child_process";
14533
14532
  import fs4 from "node:fs";
14534
14533
  import os2 from "node:os";
14535
14534
  import path5 from "node:path";
14536
- var localFile = () => path5.join(os2.homedir(), ".sphica", "projects.json");
14535
+ var localFile = () => path5.join(sphicaHome(), "projects.json");
14537
14536
  var LOCAL_KEY = /^[a-z0-9][a-z0-9._-]*$/;
14538
14537
  function normalizeRemote(url) {
14539
14538
  const raw = String(url ?? "").trim();
@@ -14726,13 +14725,13 @@ function changed(root, before, after) {
14726
14725
  }
14727
14726
 
14728
14727
  // server/src/capture.ts
14729
- var spoolDir = () => path7.join(os3.homedir(), ".sphica", "spool");
14730
- var stateFile = () => path7.join(os3.homedir(), ".sphica", "capture.json");
14728
+ var spoolDir = () => path7.join(sphicaHome(), "spool");
14729
+ var stateFile = () => path7.join(sphicaHome(), "capture.json");
14731
14730
  var rejectedDir = () => path7.join(spoolDir(), "rejected");
14732
14731
  var unregisteredDir = () => path7.join(spoolDir(), "unregistered");
14733
14732
  var HOLD_DAYS = 30;
14734
14733
  var HOLD_MAX = 1000;
14735
- var baselineDir = () => path7.join(os3.homedir(), ".sphica", "worktree");
14734
+ var baselineDir = () => path7.join(sphicaHome(), "worktree");
14736
14735
  function current(raw) {
14737
14736
  const r2 = raw;
14738
14737
  if (r2.v === 2)
@@ -15252,7 +15251,7 @@ async function withDb(role, fn) {
15252
15251
  // server/src/plugin.ts
15253
15252
  import { execFileSync as execFileSync3 } from "node:child_process";
15254
15253
  import fs7 from "node:fs";
15255
- import os4 from "node:os";
15254
+ import os3 from "node:os";
15256
15255
  import path8 from "node:path";
15257
15256
  import { fileURLToPath as fileURLToPath2 } from "node:url";
15258
15257
  var MANIFEST = path8.join(".claude-plugin", "plugin.json");
@@ -15381,7 +15380,7 @@ function observe(cwdRoot) {
15381
15380
  } catch {
15382
15381
  claude = "unknown";
15383
15382
  }
15384
- let codexHome = process.env.CODEX_HOME ?? path8.join(os4.homedir(), ".codex");
15383
+ let codexHome = process.env.CODEX_HOME ?? path8.join(os3.homedir(), ".codex");
15385
15384
  try {
15386
15385
  codexHome = fs7.realpathSync(codexHome);
15387
15386
  } catch {}
@@ -15460,7 +15459,7 @@ function report(s, now = new Date) {
15460
15459
  const lines = [];
15461
15460
  const issues = [];
15462
15461
  const todo = new Set;
15463
- const home = os4.homedir();
15462
+ const home = os3.homedir();
15464
15463
  const short = (p) => p.startsWith(`${home}/`) ? `~${p.slice(home.length)}` : p;
15465
15464
  const say2 = (m, label, text) => {
15466
15465
  if (m === "warn" || m === "fail")
@@ -15738,7 +15737,7 @@ async function init(flags) {
15738
15737
  });
15739
15738
  }
15740
15739
  async function uninstall(yes) {
15741
- const home = path9.join(os5.homedir(), ".sphica");
15740
+ const home = sphicaHome();
15742
15741
  const inHome = (p) => {
15743
15742
  const rel = path9.relative(home, path9.resolve(p));
15744
15743
  return rel === "" || !leaves(rel);
@@ -15748,6 +15747,8 @@ async function uninstall(yes) {
15748
15747
  ...process.env.SPHICA_DB && !inHome(process.env.SPHICA_DB) ? [process.env.SPHICA_DB] : []
15749
15748
  ];
15750
15749
  await boxed("sphica uninstall", async () => {
15750
+ if (process.env.SPHICA_HOME)
15751
+ throw new Error("SPHICA_HOME is set; uninstall deletes only ~/.sphica. Unset SPHICA_HOME and run it again");
15751
15752
  if (!fs8.existsSync(home))
15752
15753
  console.log(indent(`${mark("none")} ${home} does not exist`));
15753
15754
  else {
package/dist/deliver.js CHANGED
@@ -16,7 +16,7 @@ var __export = (target, all) => {
16
16
 
17
17
  // server/src/deliver.ts
18
18
  import fs6 from "node:fs";
19
- import os4 from "node:os";
19
+ import os2 from "node:os";
20
20
  import path7 from "node:path";
21
21
 
22
22
  // server/src/anchors.ts
@@ -27,7 +27,6 @@ var leaves = (rel) => rel === ".." || rel.startsWith(`..${path.sep}`) || path.is
27
27
  // server/src/capture.ts
28
28
  import { spawn } from "node:child_process";
29
29
  import fs4 from "node:fs";
30
- import os3 from "node:os";
31
30
  import path5 from "node:path";
32
31
 
33
32
  // server/node_modules/kysely/dist/util/object-utils.js
@@ -10839,7 +10838,8 @@ import path2 from "node:path";
10839
10838
  import { constants as C, DatabaseSync } from "node:sqlite";
10840
10839
  var SCHEMA_GENERATION = 2;
10841
10840
  var SCHEMA_REVISION = 1;
10842
- var dbFile = () => process.env.SPHICA_DB || path2.join(os.homedir(), ".sphica", "sphica.db");
10841
+ var sphicaHome = () => process.env.SPHICA_HOME || path2.join(os.homedir(), ".sphica");
10842
+ var dbFile = () => process.env.SPHICA_DB || path2.join(sphicaHome(), "sphica.db");
10843
10843
  function requireRuntime() {
10844
10844
  const proto = DatabaseSync.prototype;
10845
10845
  if (typeof proto.setAuthorizer !== "function" || typeof proto.enableDefensive !== "function")
@@ -11336,9 +11336,8 @@ var inline = (s) => plain2(s).replace(/[\n\t]+/g, " ");
11336
11336
  // server/src/project.ts
11337
11337
  import { execFileSync } from "node:child_process";
11338
11338
  import fs2 from "node:fs";
11339
- import os2 from "node:os";
11340
11339
  import path3 from "node:path";
11341
- var localFile = () => path3.join(os2.homedir(), ".sphica", "projects.json");
11340
+ var localFile = () => path3.join(sphicaHome(), "projects.json");
11342
11341
  var LOCAL_KEY = /^[a-z0-9][a-z0-9._-]*$/;
11343
11342
  function normalizeRemote(url) {
11344
11343
  const raw = String(url ?? "").trim();
@@ -11485,13 +11484,13 @@ function changed(root, before, after) {
11485
11484
  }
11486
11485
 
11487
11486
  // server/src/capture.ts
11488
- var spoolDir = () => path5.join(os3.homedir(), ".sphica", "spool");
11489
- var stateFile = () => path5.join(os3.homedir(), ".sphica", "capture.json");
11487
+ var spoolDir = () => path5.join(sphicaHome(), "spool");
11488
+ var stateFile = () => path5.join(sphicaHome(), "capture.json");
11490
11489
  var rejectedDir = () => path5.join(spoolDir(), "rejected");
11491
11490
  var unregisteredDir = () => path5.join(spoolDir(), "unregistered");
11492
11491
  var HOLD_DAYS = 30;
11493
11492
  var HOLD_MAX = 1000;
11494
- var baselineDir = () => path5.join(os3.homedir(), ".sphica", "worktree");
11493
+ var baselineDir = () => path5.join(sphicaHome(), "worktree");
11495
11494
  function current(raw) {
11496
11495
  const r = raw;
11497
11496
  if (r.v === 2)
@@ -31841,12 +31840,12 @@ function toldBefore(session, key) {
31841
31840
  function markOnce(kind, key) {
31842
31841
  const user = (() => {
31843
31842
  try {
31844
- return os4.userInfo().username;
31843
+ return os2.userInfo().username;
31845
31844
  } catch {
31846
31845
  return String(process.getuid?.() ?? "user");
31847
31846
  }
31848
31847
  })();
31849
- const dir = path7.join(os4.tmpdir(), `sphica-${sha256(user).toString("hex").slice(0, 12)}`, kind);
31848
+ const dir = path7.join(os2.tmpdir(), `sphica-${sha256(user).toString("hex").slice(0, 12)}`, kind);
31850
31849
  try {
31851
31850
  fs6.mkdirSync(dir, { recursive: true, mode: 448 });
31852
31851
  fs6.writeFileSync(path7.join(dir, sha256(key).toString("hex").slice(0, 24)), "", { flag: "wx" });
@@ -31883,7 +31882,7 @@ async function log(file2, projectId2, host, external2, event, plan, outcome, bra
31883
31882
  async function deliver(input2, host = "claude-code", file2 = dbFile()) {
31884
31883
  const name = input2.hook_event_name;
31885
31884
  const call = reviewCall(input2);
31886
- const event = call ? "review" : name === "SessionStart" ? "session_start" : name === "UserPromptSubmit" ? "prompt" : name === "PreToolUse" ? input2.tool_name === "Read" || host === "codex" && input2.tool_name === "Bash" && !shellPatch(input2) ? "pre_read" : "pre_edit" : null;
31885
+ const event = call ? "review" : name === "SessionStart" ? "session_start" : name === "UserPromptSubmit" ? "prompt" : name === "PreToolUse" ? input2.tool_name === "Read" || input2.tool_name === "Bash" && !(host === "codex" && shellPatch(input2)) ? "pre_read" : "pre_edit" : null;
31887
31886
  if (!event || !input2.session_id)
31888
31887
  return "";
31889
31888
  if (event === "prompt" && !isOwnerTurn(input2))
@@ -31892,7 +31891,7 @@ async function deliver(input2, host = "claude-code", file2 = dbFile()) {
31892
31891
  return "";
31893
31892
  const ti = input2.tool_input ?? {};
31894
31893
  const patch = host === "codex" && input2.tool_name === "apply_patch" && typeof ti.command === "string" ? ti.command : host === "codex" ? shellPatch(input2) : null;
31895
- const shell = host === "codex" && input2.tool_name === "Bash" && !patch && typeof ti.command === "string" ? ti.command : null;
31894
+ const shell = input2.tool_name === "Bash" && !patch && typeof ti.command === "string" ? ti.command : null;
31896
31895
  const targets = patch ? patchPaths2(patch) : [ti.file_path, ti.notebook_path].filter((p) => typeof p === "string").slice(0, 1);
31897
31896
  const onPath = event === "pre_edit" || event === "pre_read";
31898
31897
  if (onPath && !shell && (!(event === "pre_read" || patch || EDIT_TOOLS.has(input2.tool_name ?? "")) || !targets.length))
@@ -45531,7 +45531,8 @@ import path from "node:path";
45531
45531
  import { constants as C, DatabaseSync } from "node:sqlite";
45532
45532
  var SCHEMA_GENERATION = 2;
45533
45533
  var SCHEMA_REVISION = 1;
45534
- var dbFile = () => process.env.SPHICA_DB || path.join(os.homedir(), ".sphica", "sphica.db");
45534
+ var sphicaHome = () => process.env.SPHICA_HOME || path.join(os.homedir(), ".sphica");
45535
+ var dbFile = () => process.env.SPHICA_DB || path.join(sphicaHome(), "sphica.db");
45535
45536
  function requireRuntime() {
45536
45537
  const proto = DatabaseSync.prototype;
45537
45538
  if (typeof proto.setAuthorizer !== "function" || typeof proto.enableDefensive !== "function")
@@ -45987,7 +45988,6 @@ import crypto2 from "node:crypto";
45987
45988
  // server/src/capture.ts
45988
45989
  import { spawn } from "node:child_process";
45989
45990
  import fs5 from "node:fs";
45990
- import os3 from "node:os";
45991
45991
  import path5 from "node:path";
45992
45992
 
45993
45993
  // server/src/knowledge.ts
@@ -46023,7 +46023,6 @@ var inline = (s) => plain2(s).replace(/[\n\t]+/g, " ");
46023
46023
  // server/src/project.ts
46024
46024
  import { execFileSync } from "node:child_process";
46025
46025
  import fs3 from "node:fs";
46026
- import os2 from "node:os";
46027
46026
  import path3 from "node:path";
46028
46027
  import { fileURLToPath } from "node:url";
46029
46028
 
@@ -46062,7 +46061,7 @@ function locate(root, rel, symbol2) {
46062
46061
  }
46063
46062
 
46064
46063
  // server/src/project.ts
46065
- var localFile = () => path3.join(os2.homedir(), ".sphica", "projects.json");
46064
+ var localFile = () => path3.join(sphicaHome(), "projects.json");
46066
46065
  var LOCAL_KEY = /^[a-z0-9][a-z0-9._-]*$/;
46067
46066
  function normalizeRemote(url2) {
46068
46067
  const raw = String(url2 ?? "").trim();
@@ -46229,13 +46228,13 @@ function changed(root, before, after) {
46229
46228
  }
46230
46229
 
46231
46230
  // server/src/capture.ts
46232
- var spoolDir = () => path5.join(os3.homedir(), ".sphica", "spool");
46233
- var stateFile = () => path5.join(os3.homedir(), ".sphica", "capture.json");
46231
+ var spoolDir = () => path5.join(sphicaHome(), "spool");
46232
+ var stateFile = () => path5.join(sphicaHome(), "capture.json");
46234
46233
  var rejectedDir = () => path5.join(spoolDir(), "rejected");
46235
46234
  var unregisteredDir = () => path5.join(spoolDir(), "unregistered");
46236
46235
  var HOLD_DAYS = 30;
46237
46236
  var HOLD_MAX = 1000;
46238
- var baselineDir = () => path5.join(os3.homedir(), ".sphica", "worktree");
46237
+ var baselineDir = () => path5.join(sphicaHome(), "worktree");
46239
46238
  function current(raw) {
46240
46239
  const r = raw;
46241
46240
  if (r.v === 2)
package/dist/mcp.js CHANGED
@@ -45528,7 +45528,8 @@ import path from "node:path";
45528
45528
  import { constants as C, DatabaseSync } from "node:sqlite";
45529
45529
  var SCHEMA_GENERATION = 2;
45530
45530
  var SCHEMA_REVISION = 1;
45531
- var dbFile = () => process.env.SPHICA_DB || path.join(os.homedir(), ".sphica", "sphica.db");
45531
+ var sphicaHome = () => process.env.SPHICA_HOME || path.join(os.homedir(), ".sphica");
45532
+ var dbFile = () => process.env.SPHICA_DB || path.join(sphicaHome(), "sphica.db");
45532
45533
  function requireRuntime() {
45533
45534
  const proto = DatabaseSync.prototype;
45534
45535
  if (typeof proto.setAuthorizer !== "function" || typeof proto.enableDefensive !== "function")
@@ -45794,7 +45795,6 @@ var HOST_MARKS = new Set([".orphaned_at", ".in_use"]);
45794
45795
  // server/src/project.ts
45795
45796
  import { execFileSync } from "node:child_process";
45796
45797
  import fs4 from "node:fs";
45797
- import os2 from "node:os";
45798
45798
  import path4 from "node:path";
45799
45799
 
45800
45800
  // server/src/anchors.ts
@@ -45844,7 +45844,7 @@ function checkAnchor(root, a) {
45844
45844
  }
45845
45845
 
45846
45846
  // server/src/project.ts
45847
- var localFile = () => path4.join(os2.homedir(), ".sphica", "projects.json");
45847
+ var localFile = () => path4.join(sphicaHome(), "projects.json");
45848
45848
  var LOCAL_KEY = /^[a-z0-9][a-z0-9._-]*$/;
45849
45849
  function normalizeRemote(url2) {
45850
45850
  const raw = String(url2 ?? "").trim();
package/hooks/hooks.json CHANGED
@@ -65,7 +65,7 @@
65
65
  ],
66
66
  "PreToolUse": [
67
67
  {
68
- "matcher": "Edit|Write|MultiEdit|NotebookEdit|Read|Skill",
68
+ "matcher": "Edit|Write|MultiEdit|NotebookEdit|Read|Skill|Bash",
69
69
  "hooks": [
70
70
  {
71
71
  "type": "command",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sphica",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Records Claude Code and Codex sessions on your machine and keeps past implementation and decisions, with their sources, for your agent to find.",
5
5
  "license": "MIT",
6
6
  "type": "module",