sphica 0.6.35 → 0.6.36

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.6.35",
4
+ "version": "0.6.36",
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.6.35",
3
+ "version": "0.6.36",
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
@@ -74,7 +74,7 @@ In Codex, open `/hooks` and mark Sphica's hooks as trusted. Automatic recording
74
74
  **3. Set up in your repository**
75
75
 
76
76
  ```bash
77
- cd ~/Projects/your-repo
77
+ cd path/to/your-repo
78
78
  sphica init
79
79
  ```
80
80
 
@@ -0,0 +1,26 @@
1
+ -- Revision 10 → 11 of generation 2: a harvest run keeps the sources it may cite, chosen when it begins (harvest_run_source). A running
2
+ -- harvest has none, so it is removed and its session gets "Begin again"; nothing cites a running run (a save also finishes it).
3
+ -- Every statement that stays is the same as in the schema at revision 11.
4
+
5
+ create temp table sphica_migration_note (rule text, item text, action text);
6
+
7
+ create table harvest_run_source (
8
+ run_id integer not null references extraction_run (id) on delete cascade,
9
+ source_id integer not null references source (id) on delete cascade,
10
+ primary key (run_id, source_id)
11
+ ) strict;
12
+ create index harvest_run_source_source on harvest_run_source (source_id);
13
+
14
+ create trigger harvest_run_source_run before insert on harvest_run_source begin
15
+ select raise(abort, 'only a harvest run keeps sources, from its own project')
16
+ where (select origin from extraction_run where id = new.run_id) is not 'harvest'
17
+ or (select project_id from source where id = new.source_id) is not (select project_id from extraction_run where id = new.run_id);
18
+ end;
19
+
20
+ insert into sphica_migration_note
21
+ select 'a harvest run still running, begun before its sources were kept', 'run ' || id || ' ' || target || ' begun ' || started_at,
22
+ 'removed; begin the harvest again'
23
+ from extraction_run where origin = 'harvest' and status = 'running' order by id;
24
+ delete from extraction_run where origin = 'harvest' and status = 'running';
25
+
26
+ pragma user_version = 11;
package/db/schema.sql CHANGED
@@ -6,7 +6,7 @@
6
6
  -- captured sources session, source, artifact_link, edit_observation: what was said or written, never rewritten
7
7
  -- (the owner can forget chosen sources: forget_batch and source_forgotten keep what was removed, without its text)
8
8
  -- extracted units unit and its option, evidence, adoption, link, state, anchor, alias tables: what was decided or implemented
9
- -- processing extraction_run, source_processing: what has been looked at and saved, so gaps are counted
9
+ -- processing extraction_run, source_processing, harvest_run_source: what has been looked at and saved, so gaps are counted
10
10
  -- work and delivery work, delivery, delivery_unit: the current work status and what the hooks injected
11
11
  -- Every table is STRICT and every primary key is not null. Times are ISO 8601 UTC (`Date#toISOString()`); `strftime(...) is column` rejects others.
12
12
  -- Paths (a source's, an edit observation's, an anchor's) are repository-relative with forward slashes, in one form, so one place
@@ -293,6 +293,15 @@ create table source_processing (
293
293
  ) strict;
294
294
  create index source_processing_run on source_processing (run_id);
295
295
 
296
+ -- The sources a harvest run may show and cite, chosen when it begins, so another harvest of the same pull request cannot change them.
297
+ -- Ids only; a source the owner forgets leaves with its row
298
+ create table harvest_run_source (
299
+ run_id integer not null references extraction_run (id) on delete cascade,
300
+ source_id integer not null references source (id) on delete cascade,
301
+ primary key (run_id, source_id)
302
+ ) strict;
303
+ create index harvest_run_source_source on harvest_run_source (source_id);
304
+
296
305
  -- An extracted unit. Its text is never rewritten: corrections are successors, withdrawals, retractions, and anchor replacements.
297
306
  -- extraction: supported (every evidence span was found in retained text) or quarantined (with reason).
298
307
  -- lifecycle changes only through unit_state (its trigger sets this column). revision rises with every change to the unit's relations,
@@ -981,6 +990,11 @@ create trigger source_processing_project before insert on source_processing begi
981
990
  select raise(abort, 'source and run belong to different projects')
982
991
  where (select project_id from source where id = new.source_id) is not (select project_id from extraction_run where id = new.run_id);
983
992
  end;
993
+ create trigger harvest_run_source_run before insert on harvest_run_source begin
994
+ select raise(abort, 'only a harvest run keeps sources, from its own project')
995
+ where (select origin from extraction_run where id = new.run_id) is not 'harvest'
996
+ or (select project_id from source where id = new.source_id) is not (select project_id from extraction_run where id = new.run_id);
997
+ end;
984
998
 
985
999
  -- Every change to a unit's relations raises its revision (stale drafts are refused against it)
986
1000
  create trigger unit_rev_evidence_i after insert on unit_evidence begin update unit set revision = revision + 1 where id = new.unit_id; end;
@@ -1211,4 +1225,4 @@ create trigger capture_delivery_prune_insert instead of insert on capture_delive
1211
1225
  order by d.at, d.id limit 200);
1212
1226
  end;
1213
1227
 
1214
- pragma user_version = 10;
1228
+ pragma user_version = 11;
package/dist/capture.js CHANGED
@@ -11396,7 +11396,7 @@ var HOST_MARKS = new Set([".orphaned_at", ".in_use"]);
11396
11396
 
11397
11397
  // server/src/sqlite.ts
11398
11398
  var SCHEMA_GENERATION = 2;
11399
- var SCHEMA_REVISION = 10;
11399
+ var SCHEMA_REVISION = 11;
11400
11400
  var sphicaHome = () => process.env.SPHICA_HOME || path2.join(os.homedir(), ".sphica");
11401
11401
  var dbFile = () => process.env.SPHICA_DB || path2.join(sphicaHome(), "sphica.db");
11402
11402
  function requireRuntime() {
@@ -11597,6 +11597,7 @@ var INGEST_INSERTS = new Set([
11597
11597
  "ingest_source",
11598
11598
  "extraction_run",
11599
11599
  "source_processing",
11600
+ "harvest_run_source",
11600
11601
  "unit",
11601
11602
  "unit_option",
11602
11603
  "unit_evidence",
@@ -11692,6 +11693,7 @@ var FORGET_WRITES = {
11692
11693
  "unit_evidence",
11693
11694
  "unit_adoption",
11694
11695
  "source_processing",
11696
+ "harvest_run_source",
11695
11697
  "source_fts",
11696
11698
  "field_def",
11697
11699
  "unit_field",
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 fs10 from "node:fs";
96
- import os4 from "node:os";
97
96
  import path10 from "node:path";
98
97
 
99
98
  // server/node_modules/@clack/core/dist/index.mjs
@@ -4912,7 +4911,7 @@ ${indent}${caution(note)}` : ""}`);
4912
4911
 
4913
4912
  // server/src/sqlite.ts
4914
4913
  var SCHEMA_GENERATION = 2;
4915
- var SCHEMA_REVISION = 10;
4914
+ var SCHEMA_REVISION = 11;
4916
4915
  var sphicaHome = () => process.env.SPHICA_HOME || path3.join(os2.homedir(), ".sphica");
4917
4916
  var dbFile = () => process.env.SPHICA_DB || path3.join(sphicaHome(), "sphica.db");
4918
4917
  function requireRuntime() {
@@ -15957,6 +15956,7 @@ var INGEST_INSERTS = new Set([
15957
15956
  "ingest_source",
15958
15957
  "extraction_run",
15959
15958
  "source_processing",
15959
+ "harvest_run_source",
15960
15960
  "unit",
15961
15961
  "unit_option",
15962
15962
  "unit_evidence",
@@ -16052,6 +16052,7 @@ var FORGET_WRITES = {
16052
16052
  "unit_evidence",
16053
16053
  "unit_adoption",
16054
16054
  "source_processing",
16055
+ "harvest_run_source",
16055
16056
  "source_fts",
16056
16057
  "field_def",
16057
16058
  "unit_field",
@@ -16120,7 +16121,6 @@ var sessionId = (projectId, host, externalId) => uuidFrom(String(projectId), hos
16120
16121
  // server/src/project.ts
16121
16122
  import { execFileSync as execFileSync2 } from "node:child_process";
16122
16123
  import fs6 from "node:fs";
16123
- import os3 from "node:os";
16124
16124
  import path6 from "node:path";
16125
16125
 
16126
16126
  // server/src/anchors.ts
@@ -16348,48 +16348,6 @@ function nameLocal(dir, name) {
16348
16348
  });
16349
16349
  return { key: `local:${name}`, root, name };
16350
16350
  }
16351
- function underHome(p, homes, api = path6) {
16352
- for (const h2 of homes) {
16353
- const rel = api.relative(h2, p);
16354
- if (rel === "")
16355
- return "~";
16356
- if (rel !== ".." && !rel.startsWith(`..${api.sep}`) && !api.isAbsolute(rel))
16357
- return `~${api.sep}${rel}`;
16358
- }
16359
- return p;
16360
- }
16361
- var projectsDir = () => path6.join(os3.homedir(), "Projects");
16362
- function localRoots(roots = [projectsDir()]) {
16363
- const seen = new Map;
16364
- const add = (p) => {
16365
- if (p)
16366
- seen.set(p.key, [...new Set([...seen.get(p.key) ?? [], p.root])]);
16367
- };
16368
- for (const r2 of roots) {
16369
- let entries = [];
16370
- try {
16371
- entries = fs6.readdirSync(r2, { withFileTypes: true });
16372
- } catch {
16373
- continue;
16374
- }
16375
- for (const e of entries)
16376
- if (e.isDirectory() && !e.name.startsWith("."))
16377
- add(identify(path6.join(r2, e.name)));
16378
- }
16379
- for (const [root, name] of Object.entries(localMap())) {
16380
- if (LOCAL_KEY.test(name) && fs6.existsSync(root))
16381
- add({ key: `local:${name}`, root, name });
16382
- }
16383
- const found = new Map;
16384
- const ambiguous = new Map;
16385
- for (const [key, dirs] of seen) {
16386
- if (dirs.length === 1 && dirs[0])
16387
- found.set(key, dirs[0]);
16388
- else
16389
- ambiguous.set(key, dirs);
16390
- }
16391
- return { found, ambiguous };
16392
- }
16393
16351
  function relativeTo(root, file, cwd = root) {
16394
16352
  const abs = path6.resolve(cwd, file);
16395
16353
  const rel = path6.relative(root, abs);
@@ -18769,14 +18727,7 @@ async function doctor(cwd) {
18769
18727
  if (twice.length)
18770
18728
  say2("warn", "Records", `${plural(twice.length, "set")} of live records hold the same words. Read them with Sphica's read and withdraw the extra ones with /sphica:glean`);
18771
18729
  await adoptionStops(db, q.waiting, say2);
18772
- const { found, ambiguous } = localRoots();
18773
- const homes = [os4.homedir()];
18774
- try {
18775
- homes.push(fs10.realpathSync.native(os4.homedir()));
18776
- } catch {}
18777
- const tilde = (p) => underHome(p, homes);
18778
18730
  const rows = await db.selectFrom("project as p").select((eb) => [
18779
- "p.key",
18780
18731
  "p.name",
18781
18732
  eb.selectFrom("unit as u").select((u3) => u3.fn.countAll().as("n")).whereRef("u.project_id", "=", "p.id").as("records"),
18782
18733
  eb.selectFrom("extraction_run as r").select((r2) => r2.fn.max("r.finished_at").as("at")).whereRef("r.project_id", "=", "p.id").where("r.status", "=", "saved").where("r.origin", "<>", "migration").as("extracted")
@@ -18784,11 +18735,8 @@ async function doctor(cwd) {
18784
18735
  if (rows.length)
18785
18736
  console.log(section("Projects", true));
18786
18737
  const column = Math.max(...rows.map((x) => width(inline(x.name)))) + 2;
18787
- for (const x of rows) {
18788
- const copies = ambiguous.get(x.key);
18789
- const where = copies ? ` (${copies.length} copies: ${copies.map((c) => inline(tilde(c))).sort().join(", ")})` : found.get(x.key) ? "" : ` (not found in ${inline(tilde(projectsDir()))} or the named projects)`;
18790
- console.log(indent(` ${mark("none")} ${pad(inline(x.name), column)}${plural(Number(x.records ?? 0), "record")}${x.extracted ? ` / last extraction ${new Date(x.extracted).toLocaleString("sv-SE")}` : ""}${where}`));
18791
- }
18738
+ for (const x of rows)
18739
+ console.log(indent(` ${mark("none")} ${pad(inline(x.name), column)}${plural(Number(x.records ?? 0), "record")}${x.extracted ? ` / last extraction ${new Date(x.extracted).toLocaleString("sv-SE")}` : ""}`));
18792
18740
  });
18793
18741
  } catch (e) {
18794
18742
  say2("fail", "DB", `cannot read: ${plain(reason(e))}`);
package/dist/deliver.js CHANGED
@@ -11427,7 +11427,7 @@ var HOST_MARKS = new Set([".orphaned_at", ".in_use"]);
11427
11427
 
11428
11428
  // server/src/sqlite.ts
11429
11429
  var SCHEMA_GENERATION = 2;
11430
- var SCHEMA_REVISION = 10;
11430
+ var SCHEMA_REVISION = 11;
11431
11431
  var sphicaHome = () => process.env.SPHICA_HOME || path3.join(os.homedir(), ".sphica");
11432
11432
  var dbFile = () => process.env.SPHICA_DB || path3.join(sphicaHome(), "sphica.db");
11433
11433
  function requireRuntime() {
@@ -11652,6 +11652,7 @@ var INGEST_INSERTS = new Set([
11652
11652
  "ingest_source",
11653
11653
  "extraction_run",
11654
11654
  "source_processing",
11655
+ "harvest_run_source",
11655
11656
  "unit",
11656
11657
  "unit_option",
11657
11658
  "unit_evidence",
@@ -11747,6 +11748,7 @@ var FORGET_WRITES = {
11747
11748
  "unit_evidence",
11748
11749
  "unit_adoption",
11749
11750
  "source_processing",
11751
+ "harvest_run_source",
11750
11752
  "source_fts",
11751
11753
  "field_def",
11752
11754
  "unit_field",
@@ -46183,7 +46183,7 @@ var HOST_MARKS = new Set([".orphaned_at", ".in_use"]);
46183
46183
 
46184
46184
  // server/src/sqlite.ts
46185
46185
  var SCHEMA_GENERATION = 2;
46186
- var SCHEMA_REVISION = 10;
46186
+ var SCHEMA_REVISION = 11;
46187
46187
  var sphicaHome = () => process.env.SPHICA_HOME || path2.join(os.homedir(), ".sphica");
46188
46188
  var dbFile = () => process.env.SPHICA_DB || path2.join(sphicaHome(), "sphica.db");
46189
46189
  function requireRuntime() {
@@ -46384,6 +46384,7 @@ var INGEST_INSERTS = new Set([
46384
46384
  "ingest_source",
46385
46385
  "extraction_run",
46386
46386
  "source_processing",
46387
+ "harvest_run_source",
46387
46388
  "unit",
46388
46389
  "unit_option",
46389
46390
  "unit_evidence",
@@ -46479,6 +46480,7 @@ var FORGET_WRITES = {
46479
46480
  "unit_evidence",
46480
46481
  "unit_adoption",
46481
46482
  "source_processing",
46483
+ "harvest_run_source",
46482
46484
  "source_fts",
46483
46485
  "field_def",
46484
46486
  "unit_field",
@@ -47787,6 +47789,7 @@ var item = (v) => ({
47787
47789
  commit: null,
47788
47790
  ...v
47789
47791
  });
47792
+ var said = (body) => body?.trim() ? body : "";
47790
47793
  var sha = (s) => s && /^[0-9a-f]{40}$/.test(s) ? s : null;
47791
47794
  var cleanPath = (p) => p && !p.startsWith("/") && !p.includes("\\") && !/\p{Cc}/u.test(p) && !p.split("/").some((x) => x === "" || x === "." || x === "..") ? p : null;
47792
47795
  async function readPull(get, number5, repo = null) {
@@ -47807,52 +47810,49 @@ async function readPull(get, number5, repo = null) {
47807
47810
  association: p.author_association ?? null,
47808
47811
  url: p.html_url,
47809
47812
  createdAt: p.created_at,
47810
- text: p.body?.trim() ? p.body : ""
47813
+ text: said(p.body)
47811
47814
  }));
47812
47815
  for (const c of comments)
47813
- if (c.body?.trim())
47814
- items.push(item({
47815
- kind: "pr_comment",
47816
- artifact,
47817
- externalId: `comment:${c.id}`,
47818
- author: c.user ?? null,
47819
- association: c.author_association ?? null,
47820
- url: c.html_url,
47821
- createdAt: c.created_at,
47822
- text: c.body
47823
- }));
47816
+ items.push(item({
47817
+ kind: "pr_comment",
47818
+ artifact,
47819
+ externalId: `comment:${c.id}`,
47820
+ author: c.user ?? null,
47821
+ association: c.author_association ?? null,
47822
+ url: c.html_url,
47823
+ createdAt: c.created_at,
47824
+ text: said(c.body)
47825
+ }));
47824
47826
  for (const r of reviews)
47825
- if (r.body?.trim())
47826
- items.push(item({
47827
- kind: "review",
47828
- artifact,
47829
- externalId: `review:${r.id}`,
47830
- author: r.user ?? null,
47831
- association: r.author_association ?? null,
47832
- url: r.html_url,
47833
- createdAt: r.submitted_at ?? p.created_at,
47834
- text: r.body
47835
- }));
47836
- for (const c of reviewComments)
47837
- if (c.body?.trim()) {
47838
- const end = c.line ?? null;
47839
- const start = c.start_line && c.start_line <= (end ?? 0) && (c.start_side ?? c.side) === c.side ? c.start_line : end;
47840
- items.push(item({
47841
- kind: "review_comment",
47842
- artifact,
47843
- externalId: `review_comment:${c.id}`,
47844
- author: c.user ?? null,
47845
- association: c.author_association ?? null,
47846
- parent: c.in_reply_to_id ? `review_comment:${c.in_reply_to_id}` : null,
47847
- url: c.html_url,
47848
- createdAt: c.created_at,
47849
- text: c.body,
47850
- path: cleanPath(c.path),
47851
- lines: cleanPath(c.path) && end && start ? [start, end] : null,
47852
- hunk: c.diff_hunk ?? null,
47853
- commit: sha(c.commit_id)
47854
- }));
47855
- }
47827
+ items.push(item({
47828
+ kind: "review",
47829
+ artifact,
47830
+ externalId: `review:${r.id}`,
47831
+ author: r.user ?? null,
47832
+ association: r.author_association ?? null,
47833
+ url: r.html_url,
47834
+ createdAt: r.submitted_at ?? p.created_at,
47835
+ text: said(r.body)
47836
+ }));
47837
+ for (const c of reviewComments) {
47838
+ const end = c.line ?? null;
47839
+ const start = c.start_line && c.start_line <= (end ?? 0) && (c.start_side ?? c.side) === c.side ? c.start_line : end;
47840
+ items.push(item({
47841
+ kind: "review_comment",
47842
+ artifact,
47843
+ externalId: `review_comment:${c.id}`,
47844
+ author: c.user ?? null,
47845
+ association: c.author_association ?? null,
47846
+ parent: c.in_reply_to_id ? `review_comment:${c.in_reply_to_id}` : null,
47847
+ url: c.html_url,
47848
+ createdAt: c.created_at,
47849
+ text: said(c.body),
47850
+ path: cleanPath(c.path),
47851
+ lines: cleanPath(c.path) && end && start ? [start, end] : null,
47852
+ hunk: c.diff_hunk ?? null,
47853
+ commit: sha(c.commit_id)
47854
+ }));
47855
+ }
47856
47856
  for (const c of commits)
47857
47857
  items.push(item({
47858
47858
  kind: "commit_message",
@@ -47885,29 +47885,27 @@ async function readIssue(get, n) {
47885
47885
  const issue2 = await get(`issues/${n}`);
47886
47886
  if (issue2.pull_request)
47887
47887
  return items;
47888
- if (issue2.body?.trim())
47888
+ items.push(item({
47889
+ kind: "issue_body",
47890
+ artifact: `issue:${n}`,
47891
+ externalId: `issue:${n}`,
47892
+ author: issue2.user ?? null,
47893
+ association: issue2.author_association ?? null,
47894
+ url: issue2.html_url,
47895
+ createdAt: issue2.created_at,
47896
+ text: said(issue2.body)
47897
+ }));
47898
+ for (const c of await get(`issues/${n}/comments?per_page=100`, true))
47889
47899
  items.push(item({
47890
- kind: "issue_body",
47900
+ kind: "issue_comment",
47891
47901
  artifact: `issue:${n}`,
47892
- externalId: `issue:${n}`,
47893
- author: issue2.user ?? null,
47894
- association: issue2.author_association ?? null,
47895
- url: issue2.html_url,
47896
- createdAt: issue2.created_at,
47897
- text: issue2.body
47902
+ externalId: `comment:${c.id}`,
47903
+ author: c.user ?? null,
47904
+ association: c.author_association ?? null,
47905
+ url: c.html_url,
47906
+ createdAt: c.created_at,
47907
+ text: said(c.body)
47898
47908
  }));
47899
- for (const c of await get(`issues/${n}/comments?per_page=100`, true))
47900
- if (c.body?.trim())
47901
- items.push(item({
47902
- kind: "issue_comment",
47903
- artifact: `issue:${n}`,
47904
- externalId: `comment:${c.id}`,
47905
- author: c.user ?? null,
47906
- association: c.author_association ?? null,
47907
- url: c.html_url,
47908
- createdAt: c.created_at,
47909
- text: c.body
47910
- }));
47911
47909
  return items;
47912
47910
  }
47913
47911
  function githubTarget(repo, url2) {
@@ -47996,12 +47994,16 @@ async function linkIssues(db, projectId2, number5, closes) {
47996
47994
  kind: "closes"
47997
47995
  }).onConflict((oc) => oc.doNothing()).execute();
47998
47996
  }
47999
- async function pullSources(db, projectId2, number5) {
47997
+ async function pullSourceIds(db, projectId2, number5) {
48000
47998
  const artifacts = [
48001
47999
  `pr:${number5}`,
48002
48000
  ...(await db.selectFrom("artifact_link").select("to_artifact").where("project_id", "=", projectId2).where("from_artifact", "=", `pr:${number5}`).execute()).map((l) => l.to_artifact)
48003
48001
  ];
48004
- return db.selectFrom("source as s").where("s.project_id", "=", projectId2).where("s.artifact", "in", artifacts).where(({ not, exists, selectFrom }) => not(exists(selectFrom("source as n").select("n.id").whereRef("n.project_id", "=", "s.project_id").whereRef("n.kind", "=", "s.kind").whereRef("n.external_id", "=", "s.external_id").whereRef("n.revision", ">", "s.revision")))).where(({ not, exists, selectFrom }) => not(exists(selectFrom("source_forgotten as f").select("f.source_id").whereRef("f.project_id", "=", "s.project_id").whereRef("f.kind", "=", "s.kind").whereRef("f.external_id", "=", "s.external_id").whereRef("f.revision", ">", "s.revision")))).select([
48002
+ const rows = await db.selectFrom("source as s").where("s.project_id", "=", projectId2).where("s.artifact", "in", artifacts).where(({ not, exists, selectFrom }) => not(exists(selectFrom("source as n").select("n.id").whereRef("n.project_id", "=", "s.project_id").whereRef("n.kind", "=", "s.kind").whereRef("n.external_id", "=", "s.external_id").whereRef("n.revision", ">", "s.revision")))).where(({ not, exists, selectFrom }) => not(exists(selectFrom("source_forgotten as f").select("f.source_id").whereRef("f.project_id", "=", "s.project_id").whereRef("f.kind", "=", "s.kind").whereRef("f.external_id", "=", "s.external_id").whereRef("f.revision", ">", "s.revision")))).select("s.id").execute();
48003
+ return rows.map((r) => r.id);
48004
+ }
48005
+ async function runSources(db, runId) {
48006
+ return db.selectFrom("harvest_run_source as h").innerJoin("source as s", "s.id", "h.source_id").where("h.run_id", "=", runId).where(({ not, exists, selectFrom }) => not(exists(selectFrom("source_forgotten as f").select("f.source_id").whereRef("f.project_id", "=", "s.project_id").whereRef("f.kind", "=", "s.kind").whereRef("f.external_id", "=", "s.external_id").whereRef("f.revision", ">", "s.revision")))).select([
48005
48007
  "s.id",
48006
48008
  "s.kind",
48007
48009
  "s.artifact",
@@ -48010,7 +48012,6 @@ async function pullSources(db, projectId2, number5) {
48010
48012
  "s.author_login",
48011
48013
  "s.author_association",
48012
48014
  "s.created_at",
48013
- "s.captured_at",
48014
48015
  "s.path",
48015
48016
  "s.line_start",
48016
48017
  "s.text"
@@ -49495,8 +49496,8 @@ async function checkGlean(db, target, raw, facts = prepareGlean(target.root, raw
49495
49496
  if (op.op === "replace_anchor") {
49496
49497
  if (!repoPath(op.to.path))
49497
49498
  errors3.push(`${what}: the path is not inside the repository`);
49498
- const said = await span(op.source, op.quote, what);
49499
- if (said && said.s.author_kind !== "owner")
49499
+ const said2 = await span(op.source, op.quote, what);
49500
+ if (said2 && said2.s.author_kind !== "owner")
49500
49501
  errors3.push(`${what}: only the owner's words can move an anchor`);
49501
49502
  const from = repoPath(op.from.path);
49502
49503
  if (!from)
@@ -49974,7 +49975,7 @@ async function beginHarvest(db, projectId2, number5, get, beginCall) {
49974
49975
  const kept = (await storeItems(trx, projectId2, pull.items)).filter((id) => id !== null).length;
49975
49976
  await linkIssues(trx, projectId2, number5, pull.closes);
49976
49977
  const run = newRunId();
49977
- await openRun(trx, {
49978
+ const runId = await openRun(trx, {
49978
49979
  projectId: projectId2,
49979
49980
  origin: "harvest",
49980
49981
  target: `pr:${number5}`,
@@ -49982,6 +49983,8 @@ async function beginHarvest(db, projectId2, number5, get, beginCall) {
49982
49983
  draftId: run,
49983
49984
  beginCall
49984
49985
  });
49986
+ for (const sourceId of await pullSourceIds(trx, projectId2, number5))
49987
+ await trx.insertInto("harvest_run_source").values({ run_id: runId, source_id: sourceId }).execute();
49985
49988
  return { run, sources: kept };
49986
49989
  });
49987
49990
  }
@@ -50036,7 +50039,7 @@ async function agentRun(db, run, call) {
50036
50039
  async function scopeOf(db, run, root) {
50037
50040
  if (run.origin === "harvest") {
50038
50041
  const number5 = Number(run.target.slice("pr:".length));
50039
- const sources2 = (await pullSources(db, run.project_id, number5)).filter((x) => x.captured_at <= run.started_at);
50042
+ const sources2 = await runSources(db, run.id);
50040
50043
  return {
50041
50044
  target: {
50042
50045
  projectId: run.project_id,
package/dist/mcp.js CHANGED
@@ -46004,7 +46004,7 @@ var HOST_MARKS = new Set([".orphaned_at", ".in_use"]);
46004
46004
 
46005
46005
  // server/src/sqlite.ts
46006
46006
  var SCHEMA_GENERATION = 2;
46007
- var SCHEMA_REVISION = 10;
46007
+ var SCHEMA_REVISION = 11;
46008
46008
  var sphicaHome = () => process.env.SPHICA_HOME || path2.join(os.homedir(), ".sphica");
46009
46009
  var dbFile = () => process.env.SPHICA_DB || path2.join(sphicaHome(), "sphica.db");
46010
46010
  function requireRuntime() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sphica",
3
- "version": "0.6.35",
3
+ "version": "0.6.36",
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",