sphica 0.6.28 → 0.6.30
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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +11 -5
- package/db/migrations/0010.check.sql +11 -0
- package/db/migrations/0010.sql +931 -0
- package/db/schema.sql +195 -54
- package/dist/capture.js +47 -4
- package/dist/cli.js +277 -4
- package/dist/deliver.js +187 -49
- package/dist/mcp-record.js +859 -308
- package/dist/mcp.js +277 -183
- package/hooks/hooks.json +13 -0
- package/package.json +1 -1
- package/skills/export/SKILL.md +3 -2
- package/skills/glean/SKILL.md +1 -1
- package/skills/review/reviewers/precedent.md +6 -1
- package/skills/rules/SKILL.md +5 -2
- package/skills/trace/SKILL.md +43 -7
package/dist/mcp.js
CHANGED
|
@@ -45649,6 +45649,29 @@ function parseObject(obj, jsonPath, options) {
|
|
|
45649
45649
|
return target;
|
|
45650
45650
|
}
|
|
45651
45651
|
|
|
45652
|
+
// server/src/authority.ts
|
|
45653
|
+
var AUTHORITY = {
|
|
45654
|
+
owner: "the owner's decision",
|
|
45655
|
+
agent: "decided by an AI",
|
|
45656
|
+
none: "adopted by no one"
|
|
45657
|
+
};
|
|
45658
|
+
var AI_DECIDED = "A record marked decided by an AI was decided by an AI in an earlier session, not by the owner (unmarked delivered decisions are the owner's): with a concrete reason you may depart from it, saying in your reply which record and why. It never relaxes the owner's rules, public contracts, or approval gates.";
|
|
45659
|
+
async function authorityOf(db, ids, asOf) {
|
|
45660
|
+
const out = new Map(ids.map((id) => [id, "none"]));
|
|
45661
|
+
if (!ids.length)
|
|
45662
|
+
return out;
|
|
45663
|
+
const at = asOf ?? "9999";
|
|
45664
|
+
const rows = await db.selectFrom("unit_adoption").select(["unit_id", "route"]).where("unit_id", "in", ids).where("added_at", "<=", at).where((eb) => eb.or([eb("retracted_at", "is", null), eb("retracted_at", ">", at)])).execute();
|
|
45665
|
+
for (const r of rows) {
|
|
45666
|
+
if (r.route === "agent") {
|
|
45667
|
+
if (out.get(r.unit_id) === "none")
|
|
45668
|
+
out.set(r.unit_id, "agent");
|
|
45669
|
+
} else
|
|
45670
|
+
out.set(r.unit_id, "owner");
|
|
45671
|
+
}
|
|
45672
|
+
return out;
|
|
45673
|
+
}
|
|
45674
|
+
|
|
45652
45675
|
// server/src/kysely-node-sqlite.ts
|
|
45653
45676
|
var plain2 = (row) => {
|
|
45654
45677
|
const out = {};
|
|
@@ -45711,7 +45734,7 @@ var HOST_MARKS = new Set([".orphaned_at", ".in_use"]);
|
|
|
45711
45734
|
|
|
45712
45735
|
// server/src/sqlite.ts
|
|
45713
45736
|
var SCHEMA_GENERATION = 2;
|
|
45714
|
-
var SCHEMA_REVISION =
|
|
45737
|
+
var SCHEMA_REVISION = 10;
|
|
45715
45738
|
var sphicaHome = () => process.env.SPHICA_HOME || path2.join(os.homedir(), ".sphica");
|
|
45716
45739
|
var dbFile = () => process.env.SPHICA_DB || path2.join(sphicaHome(), "sphica.db");
|
|
45717
45740
|
function requireRuntime() {
|
|
@@ -45831,7 +45854,8 @@ var EVIDENCE_ROLES = [
|
|
|
45831
45854
|
"rejects",
|
|
45832
45855
|
"explains",
|
|
45833
45856
|
"implements",
|
|
45834
|
-
"reconsiders"
|
|
45857
|
+
"reconsiders",
|
|
45858
|
+
"decides"
|
|
45835
45859
|
];
|
|
45836
45860
|
var WORK_STATUSES = ["active", "blocked", "paused", "done", "abandoned"];
|
|
45837
45861
|
var FIELD_TYPES = ["text", "enum", "integer", "date"];
|
|
@@ -45973,6 +45997,126 @@ function renamesSince(root, commit) {
|
|
|
45973
45997
|
}
|
|
45974
45998
|
}
|
|
45975
45999
|
|
|
46000
|
+
// server/src/rule-files.ts
|
|
46001
|
+
import fs4 from "node:fs";
|
|
46002
|
+
import path4 from "node:path";
|
|
46003
|
+
var RULE_LIMITS = { files: 200, bytes: 256 * 1024, depth: 8, entries: 5000 };
|
|
46004
|
+
var RULE_NAMES = new Set(["CLAUDE.md", "AGENTS.md", "AGENTS.override.md"]);
|
|
46005
|
+
var PATHSPECS = [...RULE_NAMES, ".claude/rules/**/*.md"].map((p) => `:(glob)**/${p}`);
|
|
46006
|
+
function isRuleFile(rel) {
|
|
46007
|
+
const parts = rel.split("/");
|
|
46008
|
+
const name = parts.at(-1) ?? "";
|
|
46009
|
+
if (RULE_NAMES.has(name))
|
|
46010
|
+
return true;
|
|
46011
|
+
const i = parts.findIndex((p, k) => p === ".claude" && parts[k + 1] === "rules");
|
|
46012
|
+
return i >= 0 && parts.length > i + 2 && name.endsWith(".md");
|
|
46013
|
+
}
|
|
46014
|
+
function ruleFiles(root) {
|
|
46015
|
+
const listed = gitList(root) ?? walk(root);
|
|
46016
|
+
const out = { files: [], skipped: 0, incomplete: listed.incomplete };
|
|
46017
|
+
const realRoot = fs4.realpathSync(root);
|
|
46018
|
+
for (const [i, rel] of [...new Set(listed.paths)].sort().entries()) {
|
|
46019
|
+
if (i >= RULE_LIMITS.files) {
|
|
46020
|
+
out.skipped++;
|
|
46021
|
+
continue;
|
|
46022
|
+
}
|
|
46023
|
+
const text = readBounded(root, realRoot, rel);
|
|
46024
|
+
if (text === null)
|
|
46025
|
+
continue;
|
|
46026
|
+
if (text === undefined)
|
|
46027
|
+
out.skipped++;
|
|
46028
|
+
else
|
|
46029
|
+
out.files.push({ path: rel, text });
|
|
46030
|
+
}
|
|
46031
|
+
return out;
|
|
46032
|
+
}
|
|
46033
|
+
function readBounded(root, realRoot, rel) {
|
|
46034
|
+
const abs = path4.join(root, rel);
|
|
46035
|
+
if (leaves(path4.relative(root, abs)))
|
|
46036
|
+
return;
|
|
46037
|
+
try {
|
|
46038
|
+
const st = fs4.lstatSync(abs, { throwIfNoEntry: false });
|
|
46039
|
+
if (!st)
|
|
46040
|
+
return null;
|
|
46041
|
+
if (!st.isFile() || st.size > RULE_LIMITS.bytes)
|
|
46042
|
+
return;
|
|
46043
|
+
if (leaves(path4.relative(realRoot, fs4.realpathSync(abs))))
|
|
46044
|
+
return;
|
|
46045
|
+
const buf = fs4.readFileSync(abs);
|
|
46046
|
+
return buf.length > RULE_LIMITS.bytes || buf.includes(0) ? undefined : buf.toString("utf8");
|
|
46047
|
+
} catch {
|
|
46048
|
+
return;
|
|
46049
|
+
}
|
|
46050
|
+
}
|
|
46051
|
+
function gitList(root) {
|
|
46052
|
+
try {
|
|
46053
|
+
if (cleanGit(root, ["rev-parse", "--is-inside-work-tree"]).toString("utf8").trim() !== "true")
|
|
46054
|
+
return null;
|
|
46055
|
+
} catch {
|
|
46056
|
+
return null;
|
|
46057
|
+
}
|
|
46058
|
+
try {
|
|
46059
|
+
const out = cleanGit(root, ["ls-files", "--cached", "--others", "--exclude-standard", "-z", "--", ...PATHSPECS], 16 * 1024 * 1024);
|
|
46060
|
+
return { paths: out.toString("utf8").split("\x00").filter(isRuleFile), incomplete: null };
|
|
46061
|
+
} catch {
|
|
46062
|
+
return { paths: [], incomplete: "git could not list the files" };
|
|
46063
|
+
}
|
|
46064
|
+
}
|
|
46065
|
+
function walk(root) {
|
|
46066
|
+
const paths = [];
|
|
46067
|
+
let entries = 0;
|
|
46068
|
+
let unread = 0;
|
|
46069
|
+
let tooDeep = false;
|
|
46070
|
+
let stopped = false;
|
|
46071
|
+
const visit2 = (rel, depth) => {
|
|
46072
|
+
if (stopped)
|
|
46073
|
+
return;
|
|
46074
|
+
let list;
|
|
46075
|
+
try {
|
|
46076
|
+
list = fs4.readdirSync(path4.join(root, rel), { withFileTypes: true });
|
|
46077
|
+
} catch {
|
|
46078
|
+
unread++;
|
|
46079
|
+
return;
|
|
46080
|
+
}
|
|
46081
|
+
for (const d of list) {
|
|
46082
|
+
if (++entries > RULE_LIMITS.entries) {
|
|
46083
|
+
stopped = true;
|
|
46084
|
+
return;
|
|
46085
|
+
}
|
|
46086
|
+
const child = rel ? `${rel}/${d.name}` : d.name;
|
|
46087
|
+
if ((d.isFile() || d.isSymbolicLink()) && isRuleFile(child))
|
|
46088
|
+
paths.push(child);
|
|
46089
|
+
else if (d.isDirectory() && d.name !== "node_modules" && (!d.name.startsWith(".") || d.name === ".claude")) {
|
|
46090
|
+
if (depth >= RULE_LIMITS.depth)
|
|
46091
|
+
tooDeep = true;
|
|
46092
|
+
else
|
|
46093
|
+
visit2(child, depth + 1);
|
|
46094
|
+
}
|
|
46095
|
+
}
|
|
46096
|
+
};
|
|
46097
|
+
visit2("", 1);
|
|
46098
|
+
const why = [
|
|
46099
|
+
...stopped ? [`stopped after ${RULE_LIMITS.entries} directory entries`] : [],
|
|
46100
|
+
...tooDeep ? [`did not look deeper than ${RULE_LIMITS.depth} directories`] : [],
|
|
46101
|
+
...unread ? [`could not read ${unread} director${unread === 1 ? "y" : "ies"}`] : []
|
|
46102
|
+
];
|
|
46103
|
+
return { paths, incomplete: why.length ? why.join("; ") : null };
|
|
46104
|
+
}
|
|
46105
|
+
var INSTRUCTION_DIRS = new Set([
|
|
46106
|
+
".claude",
|
|
46107
|
+
".agents",
|
|
46108
|
+
".codex",
|
|
46109
|
+
".cursor",
|
|
46110
|
+
"skills",
|
|
46111
|
+
".claude-plugin",
|
|
46112
|
+
".codex-plugin"
|
|
46113
|
+
]);
|
|
46114
|
+
var instructionFile = (relative) => {
|
|
46115
|
+
const parts = relative.toLowerCase().split(/[\\/]/);
|
|
46116
|
+
const names = [...RULE_NAMES, "SKILL.md"].map((n) => n.toLowerCase());
|
|
46117
|
+
return parts.some((p) => INSTRUCTION_DIRS.has(p)) || names.includes(parts.at(-1) ?? "") || parts.slice(-2).join("/") === ".github/copilot-instructions.md";
|
|
46118
|
+
};
|
|
46119
|
+
|
|
45976
46120
|
// server/src/record.ts
|
|
45977
46121
|
var KEY = /^[a-z0-9][a-z0-9._/-]{0,63}$/;
|
|
45978
46122
|
var FIELD_NAME = /^[a-z][a-z0-9_]{0,39}$/;
|
|
@@ -46062,8 +46206,8 @@ var strong = (matched, of) => matched * 2 > of;
|
|
|
46062
46206
|
var PATH_REFUSED = "path must be relative to the repository root, such as src/x.ts: absolute paths, .., backslashes, and empty parts are refused";
|
|
46063
46207
|
async function searchUnits(db, projectId, q) {
|
|
46064
46208
|
const wanted = queryTerms(q.question);
|
|
46065
|
-
const
|
|
46066
|
-
if (
|
|
46209
|
+
const path5 = q.path === undefined ? undefined : repoPath(q.path);
|
|
46210
|
+
if (path5 === null)
|
|
46067
46211
|
return { hits: [], weaker: 0, terms: wanted, stopped: false, read: 0, refused: PATH_REFUSED };
|
|
46068
46212
|
const match = ftsQuery(q.question);
|
|
46069
46213
|
if (!match)
|
|
@@ -46073,8 +46217,8 @@ async function searchUnits(db, projectId, q) {
|
|
|
46073
46217
|
query = query.where("u.kind", "in", q.kinds);
|
|
46074
46218
|
if (q.lifecycles?.length)
|
|
46075
46219
|
query = query.where("u.lifecycle", "in", q.lifecycles);
|
|
46076
|
-
if (
|
|
46077
|
-
query = query.where(({ exists, selectFrom }) => exists(selectFrom("unit_anchor as a").select("a.id").whereRef("a.unit_id", "=", "u.id").where("a.path", "=",
|
|
46220
|
+
if (path5)
|
|
46221
|
+
query = query.where(({ exists, selectFrom }) => exists(selectFrom("unit_anchor as a").select("a.id").whereRef("a.unit_id", "=", "u.id").where("a.path", "=", path5).where("a.retired_at", "is", null)));
|
|
46078
46222
|
const ranked = await query.select(["u.id", "f.rank"]).orderBy("f.rank").orderBy("u.id").limit(UNIT_SCAN_MAX + 1).execute();
|
|
46079
46223
|
const stopped = ranked.length > UNIT_SCAN_MAX;
|
|
46080
46224
|
let weaker = 0;
|
|
@@ -46115,8 +46259,10 @@ async function searchUnits(db, projectId, q) {
|
|
|
46115
46259
|
});
|
|
46116
46260
|
}
|
|
46117
46261
|
hits.sort((a, b) => (LIFE_ORDER[a.lifecycle] ?? 9) - (LIFE_ORDER[b.lifecycle] ?? 9) || b.matched.length - a.matched.length || a.rank - b.rank);
|
|
46262
|
+
const top = hits.slice(0, q.limit);
|
|
46263
|
+
const whose = await authorityOf(db, top.filter((h) => ["decision", "constraint"].includes(h.kind)).map((h) => h.id));
|
|
46118
46264
|
return {
|
|
46119
|
-
hits:
|
|
46265
|
+
hits: top.map(({ rank: _rank, ...h }) => ({ ...h, authority: whose.get(h.id) })),
|
|
46120
46266
|
weaker,
|
|
46121
46267
|
terms: wanted,
|
|
46122
46268
|
stopped,
|
|
@@ -46125,31 +46271,16 @@ async function searchUnits(db, projectId, q) {
|
|
|
46125
46271
|
}
|
|
46126
46272
|
async function liveSuccessors(db, id) {
|
|
46127
46273
|
const seen = new Set([id]);
|
|
46128
|
-
|
|
46129
|
-
|
|
46130
|
-
|
|
46131
|
-
|
|
46132
|
-
|
|
46133
|
-
|
|
46134
|
-
|
|
46135
|
-
|
|
46136
|
-
"n.stance",
|
|
46137
|
-
"n.lifecycle",
|
|
46138
|
-
"n.text",
|
|
46139
|
-
"n.why",
|
|
46140
|
-
"n.revisit_when"
|
|
46141
|
-
]).execute();
|
|
46142
|
-
frontier = [];
|
|
46143
|
-
for (const { to_unit, ...n } of next) {
|
|
46144
|
-
replaced.add(to_unit);
|
|
46145
|
-
if (seen.has(n.id))
|
|
46146
|
-
continue;
|
|
46147
|
-
seen.add(n.id);
|
|
46148
|
-
found.push(n);
|
|
46149
|
-
frontier.push(n.id);
|
|
46150
|
-
}
|
|
46274
|
+
let end;
|
|
46275
|
+
for (let at = id;; ) {
|
|
46276
|
+
const next = await db.selectFrom("unit_replacement as h").innerJoin("unit as n", "n.id", "h.from_unit").where("h.to_unit", "=", at).where("h.ended_at", "is", null).select(["n.id", "n.key", "n.kind", "n.stance", "n.lifecycle", "n.text", "n.why", "n.revisit_when"]).executeTakeFirst();
|
|
46277
|
+
if (!next || seen.has(next.id))
|
|
46278
|
+
break;
|
|
46279
|
+
seen.add(next.id);
|
|
46280
|
+
end = next;
|
|
46281
|
+
at = next.id;
|
|
46151
46282
|
}
|
|
46152
|
-
return
|
|
46283
|
+
return end ? [end] : [];
|
|
46153
46284
|
}
|
|
46154
46285
|
async function judgeUnits(db, rows, wanted, hits) {
|
|
46155
46286
|
const ids = rows.map((r) => r.id);
|
|
@@ -46260,6 +46391,21 @@ async function searchSources(db, projectId, question, limit, owner) {
|
|
|
46260
46391
|
stopped = true;
|
|
46261
46392
|
return { hits, weaker, terms: wanted, stopped, read };
|
|
46262
46393
|
}
|
|
46394
|
+
var hitText = (h) => [
|
|
46395
|
+
`## ${h.key} (u${h.id}): ${h.kind}${h.stance ? ` ${h.stance}` : ""}, ${h.lifecycle}${h.authority ? `, ${AUTHORITY[h.authority]}` : ""}`,
|
|
46396
|
+
head(h.text, 600),
|
|
46397
|
+
...h.why ? [`Why: ${head(h.why, 400)}`] : [],
|
|
46398
|
+
...h.revisit_when ? [`Revisit when: ${head(h.revisit_when, 200)}`] : [],
|
|
46399
|
+
...h.options.length ? [
|
|
46400
|
+
`Options: ${h.options.map((o) => `${o.text} (${o.outcome}${o.why ? `: ${head(o.why, 160)}` : ""})`).join(" / ")}`
|
|
46401
|
+
] : [],
|
|
46402
|
+
...h.anchors.length ? [`Code: ${h.anchors.map((a) => `${a.path}${a.symbol ? ` ${a.symbol}` : ""} (${a.role})`).join(", ")}`] : [],
|
|
46403
|
+
h.successorOf ? `Replaces ${h.successorOf}, which matched` : `Matched: ${h.matched.join(", ")}${h.aliasOnly ? " (search aliases only)" : ""}`
|
|
46404
|
+
].join(`
|
|
46405
|
+
`);
|
|
46406
|
+
var hitsText = (hits) => [...hits.map(hitText), ...hits.some((h) => h.authority === "agent") ? [AI_DECIDED] : []].join(`
|
|
46407
|
+
|
|
46408
|
+
`);
|
|
46263
46409
|
|
|
46264
46410
|
// server/src/asked.ts
|
|
46265
46411
|
var DECIDED_KINDS = new Set(["decision", "constraint"]);
|
|
@@ -46448,7 +46594,15 @@ async function describe3(db, u, root, asOf, renames) {
|
|
|
46448
46594
|
"s.text"
|
|
46449
46595
|
]).orderBy("a.id").execute(),
|
|
46450
46596
|
db.selectFrom("unit_anchor").select(["path", "symbol", "role", "commit_sha", "line_start", "retired_at"]).where("unit_id", "=", u.id).where("added_at", "<=", asOf ?? "9999").orderBy("id").execute(),
|
|
46451
|
-
db.selectFrom("unit_link as l").innerJoin("unit as a", "a.id", "l.from_unit").innerJoin("unit as b", "b.id", "l.to_unit").where((eb) => eb.or([eb("l.from_unit", "=", u.id), eb("l.to_unit", "=", u.id)])).where("l.added_at", "<=", asOf ?? "9999").select(
|
|
46597
|
+
db.selectFrom("unit_link as l").innerJoin("unit as a", "a.id", "l.from_unit").innerJoin("unit as b", "b.id", "l.to_unit").where((eb) => eb.or([eb("l.from_unit", "=", u.id), eb("l.to_unit", "=", u.id)])).where("l.added_at", "<=", asOf ?? "9999").select((eb) => [
|
|
46598
|
+
"l.kind",
|
|
46599
|
+
"l.resolved_at",
|
|
46600
|
+
"a.id as from_id",
|
|
46601
|
+
"b.id as to_id",
|
|
46602
|
+
"a.key as from_key",
|
|
46603
|
+
"b.key as to_key",
|
|
46604
|
+
eb.selectFrom("unit_state as t").whereRef("t.unit_id", "=", "a.id").where("t.at", "<=", asOf ?? "9999").select("t.to_state").orderBy("t.id", "desc").limit(1).as("from_lifecycle")
|
|
46605
|
+
]).execute(),
|
|
46452
46606
|
db.selectFrom("unit_state").select(["from_state", "to_state", "at", "reason"]).where("unit_id", "=", u.id).orderBy("id").execute(),
|
|
46453
46607
|
db.selectFrom("unit_field as f").innerJoin("field_def as d", "d.id", "f.field_def_id").innerJoin("source as s", "s.id", "f.source_id").where("f.unit_id", "=", u.id).where("f.added_at", "<=", asOf ?? "9999").select([
|
|
46454
46608
|
"d.name",
|
|
@@ -46482,10 +46636,13 @@ async function describe3(db, u, root, asOf, renames) {
|
|
|
46482
46636
|
for (const l of links)
|
|
46483
46637
|
l.resolved_at = later(l.resolved_at);
|
|
46484
46638
|
}
|
|
46639
|
+
const whose = ["decision", "constraint"].includes(u.kind) ? (await authorityOf(db, [u.id], asOf)).get(u.id) : undefined;
|
|
46485
46640
|
const out = [
|
|
46486
|
-
`${u.key} (u${u.id}, revision ${u.revision}): ${u.kind}${u.stance ? ` ${u.stance}` : ""}, ${lifecycle}${u.extraction === "quarantined" ? `, quarantined: ${u.extraction_reason}` : ""}${u.unsourced ? ", unsourced: no source was given, so it is never used as fact" : ""}`,
|
|
46641
|
+
`${u.key} (u${u.id}, revision ${u.revision}): ${u.kind}${u.stance ? ` ${u.stance}` : ""}, ${lifecycle}${whose ? `, ${AUTHORITY[whose]}` : ""}${u.extraction === "quarantined" ? `, quarantined: ${u.extraction_reason}` : ""}${u.unsourced ? ", unsourced: no source was given, so it is never used as fact" : ""}`,
|
|
46487
46642
|
u.text
|
|
46488
46643
|
];
|
|
46644
|
+
if (whose === "agent")
|
|
46645
|
+
out.push(AI_DECIDED);
|
|
46489
46646
|
if (u.why)
|
|
46490
46647
|
out.push(`Why: ${u.why}`);
|
|
46491
46648
|
if (u.scope_note)
|
|
@@ -46528,11 +46685,14 @@ async function describe3(db, u, root, asOf, renames) {
|
|
|
46528
46685
|
out.push(` - ${where} (${a.role}${a.commit_sha ? `, commit ${a.commit_sha.slice(0, 12)}` : ""}): ${c.state}${c.line ? ` at line ${c.line}` : ""}${c.state === "missing" ? ` — needs review: the code it points at is gone${movedTo(root, a, renames)}` : ""}`);
|
|
46529
46686
|
}
|
|
46530
46687
|
}
|
|
46531
|
-
|
|
46532
|
-
|
|
46533
|
-
|
|
46534
|
-
|
|
46535
|
-
|
|
46688
|
+
out.push(...await replacements(db, u.id, links, asOf));
|
|
46689
|
+
const conflicting = links.filter((l) => l.kind === "conflicts");
|
|
46690
|
+
const sides = await authorityOf(db, conflicting.map((l) => l.from_id === u.id ? l.to_id : l.from_id), asOf);
|
|
46691
|
+
const mine = (await authorityOf(db, [u.id], asOf)).get(u.id);
|
|
46692
|
+
for (const l of conflicting) {
|
|
46693
|
+
const other = l.from_id === u.id ? l.to_id : l.from_id;
|
|
46694
|
+
const held = mine !== "owner" || sides.get(other) === "owner";
|
|
46695
|
+
out.push(`Conflicts with ${l.from_id === u.id ? l.to_key : l.from_key}${l.resolved_at ? " (resolved)" : held ? " (unresolved: held back from automatic delivery until resolved, not withdrawn)" : " (unresolved: still delivered, since only the owner's words hold the owner's decision back)"}`);
|
|
46536
46696
|
}
|
|
46537
46697
|
const aliases = await db.selectFrom("unit_alias").select("terms").where("unit_id", "=", u.id).where("content_hash", "=", u.content_hash).where("added_at", "<=", asOf ?? "9999").orderBy("id", "desc").executeTakeFirst();
|
|
46538
46698
|
const terms2 = aliases ? JSON.parse(aliases.terms) : [];
|
|
@@ -46542,6 +46702,46 @@ async function describe3(db, u, root, asOf, renames) {
|
|
|
46542
46702
|
return out.join(`
|
|
46543
46703
|
`);
|
|
46544
46704
|
}
|
|
46705
|
+
async function replacements(db, id, links, asOf) {
|
|
46706
|
+
const intents = links.filter((l) => l.kind === "supersedes");
|
|
46707
|
+
if (!intents.length)
|
|
46708
|
+
return [];
|
|
46709
|
+
const own2 = intents.find((l) => l.from_id === id);
|
|
46710
|
+
const rows = await db.selectFrom("unit_replacement as h").innerJoin("unit as a", "a.id", "h.from_unit").where((eb) => eb.or([
|
|
46711
|
+
eb("h.from_unit", "=", id),
|
|
46712
|
+
eb("h.to_unit", "=", id),
|
|
46713
|
+
...own2 ? [eb("h.to_unit", "=", own2.to_id)] : []
|
|
46714
|
+
])).where("h.started_at", "<=", asOf ?? "9999").select(["h.from_unit", "h.to_unit", "h.started_at", "h.ended_at", "h.end_reason", "a.key as from_key"]).orderBy("h.id").execute();
|
|
46715
|
+
const gaps = await db.selectFrom("unit_replacement_gap").select(["from_unit", "to_unit"]).where((eb) => eb.or([eb("from_unit", "=", id), eb("to_unit", "=", id)])).execute();
|
|
46716
|
+
const gap = (from, to) => gaps.some((g) => g.from_unit === from && g.to_unit === to);
|
|
46717
|
+
const unrecorded = "its history before the update to revision 10 was not recorded";
|
|
46718
|
+
const open2 = (r) => r.ended_at === null || asOf !== undefined && r.ended_at > asOf;
|
|
46719
|
+
const period = (r) => `from ${r.started_at} to ${r.ended_at}: ${inline(r.end_reason ?? "")}`;
|
|
46720
|
+
const out = [];
|
|
46721
|
+
if (own2) {
|
|
46722
|
+
const mine = rows.filter((r) => r.from_unit === id && r.to_unit === own2.to_id);
|
|
46723
|
+
const now = mine.find(open2);
|
|
46724
|
+
const holder = rows.find((r) => r.to_unit === own2.to_id && r.from_unit !== id && open2(r));
|
|
46725
|
+
out.push(`Supersedes ${own2.to_key} (${now ? `in effect since ${now.started_at}` : holder ? `not in effect: ${holder.from_key} is in effect as its successor` : "not in effect"})`);
|
|
46726
|
+
for (const r of mine)
|
|
46727
|
+
if (!open2(r))
|
|
46728
|
+
out.push(`Replaced ${own2.to_key} ${period(r)}`);
|
|
46729
|
+
if (gap(id, own2.to_id))
|
|
46730
|
+
out.push(`Supersedes ${own2.to_key}: ${unrecorded}`);
|
|
46731
|
+
}
|
|
46732
|
+
const into = rows.filter((r) => r.to_unit === id);
|
|
46733
|
+
for (const r of into)
|
|
46734
|
+
out.push(open2(r) ? `Superseded by ${r.from_key} (since ${r.started_at})` : `Was superseded by ${r.from_key} ${period(r)}`);
|
|
46735
|
+
for (const l of intents) {
|
|
46736
|
+
if (l.to_id !== id)
|
|
46737
|
+
continue;
|
|
46738
|
+
if (gap(l.from_id, id))
|
|
46739
|
+
out.push(`Superseded by ${l.from_key} at some time: ${unrecorded}`);
|
|
46740
|
+
else if (!into.some((r) => r.from_unit === l.from_id))
|
|
46741
|
+
out.push(`Replacement proposed by ${l.from_key} (${l.from_lifecycle ?? "candidate"})`);
|
|
46742
|
+
}
|
|
46743
|
+
return out;
|
|
46744
|
+
}
|
|
46545
46745
|
function movedTo(root, a, renames) {
|
|
46546
46746
|
if (!root || !a.commit_sha || fileState(root, a.path) !== "gone")
|
|
46547
46747
|
return "";
|
|
@@ -46582,112 +46782,6 @@ function part(id, text2, from) {
|
|
|
46582
46782
|
return end < all.length ? [shown, `(${all.length - end} more bytes; read s${id}@${end} for the rest)`] : [shown];
|
|
46583
46783
|
}
|
|
46584
46784
|
|
|
46585
|
-
// server/src/rule-files.ts
|
|
46586
|
-
import fs4 from "node:fs";
|
|
46587
|
-
import path4 from "node:path";
|
|
46588
|
-
var RULE_LIMITS = { files: 200, bytes: 256 * 1024, depth: 8, entries: 5000 };
|
|
46589
|
-
var RULE_NAMES = new Set(["CLAUDE.md", "AGENTS.md", "AGENTS.override.md"]);
|
|
46590
|
-
var PATHSPECS = [...RULE_NAMES, ".claude/rules/**/*.md"].map((p) => `:(glob)**/${p}`);
|
|
46591
|
-
function isRuleFile(rel) {
|
|
46592
|
-
const parts = rel.split("/");
|
|
46593
|
-
const name = parts.at(-1) ?? "";
|
|
46594
|
-
if (RULE_NAMES.has(name))
|
|
46595
|
-
return true;
|
|
46596
|
-
const i = parts.findIndex((p, k) => p === ".claude" && parts[k + 1] === "rules");
|
|
46597
|
-
return i >= 0 && parts.length > i + 2 && name.endsWith(".md");
|
|
46598
|
-
}
|
|
46599
|
-
function ruleFiles(root) {
|
|
46600
|
-
const listed = gitList(root) ?? walk(root);
|
|
46601
|
-
const out = { files: [], skipped: 0, incomplete: listed.incomplete };
|
|
46602
|
-
const realRoot = fs4.realpathSync(root);
|
|
46603
|
-
for (const [i, rel] of [...new Set(listed.paths)].sort().entries()) {
|
|
46604
|
-
if (i >= RULE_LIMITS.files) {
|
|
46605
|
-
out.skipped++;
|
|
46606
|
-
continue;
|
|
46607
|
-
}
|
|
46608
|
-
const text2 = readBounded(root, realRoot, rel);
|
|
46609
|
-
if (text2 === null)
|
|
46610
|
-
continue;
|
|
46611
|
-
if (text2 === undefined)
|
|
46612
|
-
out.skipped++;
|
|
46613
|
-
else
|
|
46614
|
-
out.files.push({ path: rel, text: text2 });
|
|
46615
|
-
}
|
|
46616
|
-
return out;
|
|
46617
|
-
}
|
|
46618
|
-
function readBounded(root, realRoot, rel) {
|
|
46619
|
-
const abs = path4.join(root, rel);
|
|
46620
|
-
if (leaves(path4.relative(root, abs)))
|
|
46621
|
-
return;
|
|
46622
|
-
try {
|
|
46623
|
-
const st = fs4.lstatSync(abs, { throwIfNoEntry: false });
|
|
46624
|
-
if (!st)
|
|
46625
|
-
return null;
|
|
46626
|
-
if (!st.isFile() || st.size > RULE_LIMITS.bytes)
|
|
46627
|
-
return;
|
|
46628
|
-
if (leaves(path4.relative(realRoot, fs4.realpathSync(abs))))
|
|
46629
|
-
return;
|
|
46630
|
-
const buf = fs4.readFileSync(abs);
|
|
46631
|
-
return buf.length > RULE_LIMITS.bytes || buf.includes(0) ? undefined : buf.toString("utf8");
|
|
46632
|
-
} catch {
|
|
46633
|
-
return;
|
|
46634
|
-
}
|
|
46635
|
-
}
|
|
46636
|
-
function gitList(root) {
|
|
46637
|
-
try {
|
|
46638
|
-
if (cleanGit(root, ["rev-parse", "--is-inside-work-tree"]).toString("utf8").trim() !== "true")
|
|
46639
|
-
return null;
|
|
46640
|
-
} catch {
|
|
46641
|
-
return null;
|
|
46642
|
-
}
|
|
46643
|
-
try {
|
|
46644
|
-
const out = cleanGit(root, ["ls-files", "--cached", "--others", "--exclude-standard", "-z", "--", ...PATHSPECS], 16 * 1024 * 1024);
|
|
46645
|
-
return { paths: out.toString("utf8").split("\x00").filter(isRuleFile), incomplete: null };
|
|
46646
|
-
} catch {
|
|
46647
|
-
return { paths: [], incomplete: "git could not list the files" };
|
|
46648
|
-
}
|
|
46649
|
-
}
|
|
46650
|
-
function walk(root) {
|
|
46651
|
-
const paths = [];
|
|
46652
|
-
let entries = 0;
|
|
46653
|
-
let unread = 0;
|
|
46654
|
-
let tooDeep = false;
|
|
46655
|
-
let stopped = false;
|
|
46656
|
-
const visit2 = (rel, depth) => {
|
|
46657
|
-
if (stopped)
|
|
46658
|
-
return;
|
|
46659
|
-
let list;
|
|
46660
|
-
try {
|
|
46661
|
-
list = fs4.readdirSync(path4.join(root, rel), { withFileTypes: true });
|
|
46662
|
-
} catch {
|
|
46663
|
-
unread++;
|
|
46664
|
-
return;
|
|
46665
|
-
}
|
|
46666
|
-
for (const d of list) {
|
|
46667
|
-
if (++entries > RULE_LIMITS.entries) {
|
|
46668
|
-
stopped = true;
|
|
46669
|
-
return;
|
|
46670
|
-
}
|
|
46671
|
-
const child = rel ? `${rel}/${d.name}` : d.name;
|
|
46672
|
-
if ((d.isFile() || d.isSymbolicLink()) && isRuleFile(child))
|
|
46673
|
-
paths.push(child);
|
|
46674
|
-
else if (d.isDirectory() && d.name !== "node_modules" && (!d.name.startsWith(".") || d.name === ".claude")) {
|
|
46675
|
-
if (depth >= RULE_LIMITS.depth)
|
|
46676
|
-
tooDeep = true;
|
|
46677
|
-
else
|
|
46678
|
-
visit2(child, depth + 1);
|
|
46679
|
-
}
|
|
46680
|
-
}
|
|
46681
|
-
};
|
|
46682
|
-
visit2("", 1);
|
|
46683
|
-
const why = [
|
|
46684
|
-
...stopped ? [`stopped after ${RULE_LIMITS.entries} directory entries`] : [],
|
|
46685
|
-
...tooDeep ? [`did not look deeper than ${RULE_LIMITS.depth} directories`] : [],
|
|
46686
|
-
...unread ? [`could not read ${unread} director${unread === 1 ? "y" : "ies"}`] : []
|
|
46687
|
-
];
|
|
46688
|
-
return { paths, incomplete: why.length ? why.join("; ") : null };
|
|
46689
|
-
}
|
|
46690
|
-
|
|
46691
46785
|
// server/src/export.ts
|
|
46692
46786
|
var EXPORT_LIMITS = { records: 50, depth: 20, bytes: 60 * 1024 };
|
|
46693
46787
|
async function chosen(db, projectId, ref) {
|
|
@@ -46740,9 +46834,11 @@ async function lines(db, u) {
|
|
|
46740
46834
|
const kept = (t) => plain(t).split(`
|
|
46741
46835
|
`).join(`
|
|
46742
46836
|
`);
|
|
46837
|
+
const whose = ["decision", "constraint"].includes(u.kind) ? (await authorityOf(db, [u.id])).get(u.id) : undefined;
|
|
46743
46838
|
const out = [
|
|
46744
46839
|
`key: ${inline(u.key)} (u${u.id})`,
|
|
46745
46840
|
`kind: ${u.kind}${u.stance ? ` ${u.stance}` : ""}`,
|
|
46841
|
+
...whose ? [`authority: ${AUTHORITY[whose]}`] : [],
|
|
46746
46842
|
`text: ${kept(u.text)}`
|
|
46747
46843
|
];
|
|
46748
46844
|
if (u.why)
|
|
@@ -46784,7 +46880,7 @@ async function replaced(db, from) {
|
|
|
46784
46880
|
const out = [];
|
|
46785
46881
|
let frontier = [from];
|
|
46786
46882
|
for (let depth = 0;frontier.length; depth++) {
|
|
46787
|
-
const older = await db.selectFrom("
|
|
46883
|
+
const older = await db.selectFrom("unit_replacement as h").innerJoin("unit as u", "u.id", "h.to_unit").where("h.from_unit", "in", frontier.map((u) => u.id)).where("h.ended_at", "is", null).selectAll("u").select("h.from_unit").orderBy("h.from_unit").orderBy("u.id").execute();
|
|
46788
46884
|
if (!older.some((u) => !seen.has(u.id)))
|
|
46789
46885
|
break;
|
|
46790
46886
|
if (depth === EXPORT_LIMITS.depth)
|
|
@@ -46859,12 +46955,6 @@ ${problems.join(`
|
|
|
46859
46955
|
return tooBig;
|
|
46860
46956
|
return { document };
|
|
46861
46957
|
}
|
|
46862
|
-
var INSTRUCTION_DIRS = new Set([".claude", ".agents", ".codex", ".cursor"]);
|
|
46863
|
-
var instructionFile = (relative) => {
|
|
46864
|
-
const parts = relative.toLowerCase().split(/[\\/]/);
|
|
46865
|
-
const names = [...RULE_NAMES].map((n) => n.toLowerCase());
|
|
46866
|
-
return parts.some((p) => INSTRUCTION_DIRS.has(p)) || names.includes(parts.at(-1) ?? "") || parts.slice(-2).join("/") === ".github/copilot-instructions.md";
|
|
46867
|
-
};
|
|
46868
46958
|
function exportPath(root, given) {
|
|
46869
46959
|
if (inline(given) !== given || given.includes("\x00"))
|
|
46870
46960
|
return { error: "The path has characters that cannot be shown as typed; use plain characters." };
|
|
@@ -47017,12 +47107,13 @@ async function liveOverview(db, projectId, after) {
|
|
|
47017
47107
|
live.select(["id", "key", "kind", "stance", "text"]).where("id", ">", after ?? 0).orderBy("id").limit(OVERVIEW_LIMITS.records + 1).execute()
|
|
47018
47108
|
]);
|
|
47019
47109
|
const anchors = rows.length ? await db.selectFrom("unit_anchor").select(["unit_id", "path"]).where("unit_id", "in", rows.map((r) => r.id)).where("role", "=", "applies_to").where("retired_at", "is", null).orderBy("id").execute() : [];
|
|
47110
|
+
const whose = await authorityOf(db, rows.map((r) => r.id));
|
|
47020
47111
|
const shown = [];
|
|
47021
47112
|
for (const r of rows.slice(0, OVERVIEW_LIMITS.records)) {
|
|
47022
47113
|
const paths = [...new Set(anchors.filter((a) => a.unit_id === r.id).map((a) => a.path))];
|
|
47023
47114
|
const first = paths[0];
|
|
47024
47115
|
const dir = first === undefined ? null : path6.posix.dirname(first);
|
|
47025
|
-
const line = `- ${head(inline(r.key), OVERVIEW_LIMITS.key)} (u${r.id}, ${r.kind}${r.stance ? ` ${r.stance}` : ""}): ${head(inline(r.text), OVERVIEW_LIMITS.text)}${paths.length ? ` [${pathList(paths)}]` : ""}`;
|
|
47116
|
+
const line = `- ${head(inline(r.key), OVERVIEW_LIMITS.key)} (u${r.id}, ${r.kind}${r.stance ? ` ${r.stance}` : ""}${whose.get(r.id) === "agent" ? ", decided by an AI" : ""}): ${head(inline(r.text), OVERVIEW_LIMITS.text)}${paths.length ? ` [${pathList(paths)}]` : ""}`;
|
|
47026
47117
|
shown.push({
|
|
47027
47118
|
id: r.id,
|
|
47028
47119
|
group: dir === null ? PROJECT_WIDE : dir === "." ? "(repository root)" : `${dir}/`,
|
|
@@ -47043,7 +47134,8 @@ async function liveOverview(db, projectId, after) {
|
|
|
47043
47134
|
]),
|
|
47044
47135
|
`${shown.length} shown of ${n} active decisions and constraints${after === null ? "" : ` (ids after ${after})`}.`,
|
|
47045
47136
|
more ? `More follow: call overview again with after: ${last}. Pages are read at different times: a record that became active in between, with a lower id, is not on a later page.` : "That is the end of the list.",
|
|
47046
|
-
"Read a record by its key or u<id> before relying on it."
|
|
47137
|
+
"Read a record by its key or u<id> before relying on it.",
|
|
47138
|
+
...shown.some((x) => whose.get(x.id) === "agent") ? [AI_DECIDED] : []
|
|
47047
47139
|
].join(`
|
|
47048
47140
|
`);
|
|
47049
47141
|
}
|
|
@@ -47170,7 +47262,7 @@ async function successor(db, id) {
|
|
|
47170
47262
|
let end = null;
|
|
47171
47263
|
let at = id;
|
|
47172
47264
|
for (;; ) {
|
|
47173
|
-
const next = await db.selectFrom("
|
|
47265
|
+
const next = await db.selectFrom("unit_replacement as h").innerJoin("unit as u", "u.id", "h.from_unit").where("h.to_unit", "=", at).where("h.ended_at", "is", null).select(["u.id", "u.key", "u.lifecycle"]).executeTakeFirst();
|
|
47174
47266
|
if (!next)
|
|
47175
47267
|
break;
|
|
47176
47268
|
end = { key: next.key, lifecycle: next.lifecycle };
|
|
@@ -47414,6 +47506,16 @@ async function selectForReview(db, projectId2, files) {
|
|
|
47414
47506
|
}
|
|
47415
47507
|
return [...out.values()];
|
|
47416
47508
|
}
|
|
47509
|
+
var AI_DEPARTURE = "A record marked decided by an AI was decided by an AI in an earlier session, not by the owner: a change that departs from it is a violation only when the change gives no reason for departing.";
|
|
47510
|
+
async function selectedText(db, hits) {
|
|
47511
|
+
const whose = await authorityOf(db, hits.map((u) => u.id));
|
|
47512
|
+
const ai = hits.some((u) => whose.get(u.id) === "agent");
|
|
47513
|
+
return [
|
|
47514
|
+
...hits.map((u) => `- ${u.key} (${u.kind}${u.stance ? ` ${u.stance}` : ""}${whose.get(u.id) === "agent" ? ", decided by an AI" : ""}): ${head(inline(u.text), 300)} [${u.because}]`),
|
|
47515
|
+
...ai ? [AI_DEPARTURE] : []
|
|
47516
|
+
].join(`
|
|
47517
|
+
`);
|
|
47518
|
+
}
|
|
47417
47519
|
|
|
47418
47520
|
// server/src/review-findings.ts
|
|
47419
47521
|
var Finding = exports_external.object({
|
|
@@ -47464,21 +47566,28 @@ async function checkFindings(db, projectId2, files, raw) {
|
|
|
47464
47566
|
// server/src/trace.ts
|
|
47465
47567
|
var PENDING_DAYS = 14;
|
|
47466
47568
|
var pendingCutoff = (now) => new Date(now.getTime() - PENDING_DAYS * 86400000).toISOString();
|
|
47467
|
-
var
|
|
47468
|
-
|
|
47469
|
-
"
|
|
47470
|
-
|
|
47471
|
-
|
|
47472
|
-
|
|
47473
|
-
|
|
47474
|
-
|
|
47475
|
-
|
|
47476
|
-
|
|
47569
|
+
var untraced = (eb, auto) => {
|
|
47570
|
+
const q = eb.selectFrom("source as m").whereRef("m.session_id", "=", "s.id");
|
|
47571
|
+
return (auto ? q : q.where("m.author_kind", "=", "owner")).where(({ not, exists, selectFrom }) => not(exists(selectFrom("source_processing as p").whereRef("p.source_id", "=", "m.id").select(sql`1`.as("x")))));
|
|
47572
|
+
};
|
|
47573
|
+
var untracedSessions = (db, projectId2, o = {}) => {
|
|
47574
|
+
const auto = o.auto ?? false;
|
|
47575
|
+
const skip = o.skip;
|
|
47576
|
+
return db.selectFrom("session as s").where("s.project_id", "=", projectId2).$if(Boolean(skip), (q) => q.where((eb) => eb.not(eb.and([eb("s.host", "=", skip?.host ?? ""), eb("s.external_id", "=", skip?.session ?? "")])))).where((eb) => eb.exists(untraced(eb, auto).select(sql`1`.as("x")))).select((eb) => [
|
|
47577
|
+
"s.id",
|
|
47578
|
+
"s.host",
|
|
47579
|
+
"s.started_at",
|
|
47580
|
+
"s.branch",
|
|
47581
|
+
eb.selectFrom("source as o").whereRef("o.session_id", "=", "s.id").where("o.author_kind", "=", "owner").select((o2) => o2.fn.max("o.created_at").as("last")).as("last"),
|
|
47582
|
+
untraced(eb, auto).select((m) => m.fn.countAll().as("waiting")).as("waiting"),
|
|
47583
|
+
untraced(eb, auto).select((m) => m.fn.min("m.id").as("first")).as("first")
|
|
47584
|
+
]);
|
|
47585
|
+
};
|
|
47477
47586
|
|
|
47478
47587
|
// server/src/status.ts
|
|
47479
|
-
async function pendingCount(db, projectId2, now = new Date) {
|
|
47588
|
+
async function pendingCount(db, projectId2, now = new Date, o = {}) {
|
|
47480
47589
|
const cutoff = pendingCutoff(now);
|
|
47481
|
-
const r = await db.selectFrom(untracedSessions(db, projectId2).as("w")).select([
|
|
47590
|
+
const r = await db.selectFrom(untracedSessions(db, projectId2, o).as("w")).select([
|
|
47482
47591
|
sql`count(case when w.last >= ${cutoff} then 1 end)`.as("recent"),
|
|
47483
47592
|
sql`count(case when w.last < ${cutoff} then 1 end)`.as("older")
|
|
47484
47593
|
]).executeTakeFirst();
|
|
@@ -47544,18 +47653,6 @@ var among = (r) => r.stopped ? `among the first ${r.read} candidates by rank ` :
|
|
|
47544
47653
|
var stoppedAfter = (r) => r.stopped ? `
|
|
47545
47654
|
|
|
47546
47655
|
Stopped after ${r.read} candidates by rank; more may match.` : "";
|
|
47547
|
-
var hitText = (h) => [
|
|
47548
|
-
`## ${h.key} (u${h.id}): ${h.kind}${h.stance ? ` ${h.stance}` : ""}, ${h.lifecycle}`,
|
|
47549
|
-
head(h.text, 600),
|
|
47550
|
-
...h.why ? [`Why: ${head(h.why, 400)}`] : [],
|
|
47551
|
-
...h.revisit_when ? [`Revisit when: ${head(h.revisit_when, 200)}`] : [],
|
|
47552
|
-
...h.options.length ? [
|
|
47553
|
-
`Options: ${h.options.map((o) => `${o.text} (${o.outcome}${o.why ? `: ${head(o.why, 160)}` : ""})`).join(" / ")}`
|
|
47554
|
-
] : [],
|
|
47555
|
-
...h.anchors.length ? [`Code: ${h.anchors.map((a) => `${a.path}${a.symbol ? ` ${a.symbol}` : ""} (${a.role})`).join(", ")}`] : [],
|
|
47556
|
-
h.successorOf ? `Replaces ${h.successorOf}, which matched` : `Matched: ${h.matched.join(", ")}${h.aliasOnly ? " (search aliases only)" : ""}`
|
|
47557
|
-
].join(`
|
|
47558
|
-
`);
|
|
47559
47656
|
var server = new McpServer({ name: "sphica", version: VERSION ?? "unknown" }, {
|
|
47560
47657
|
capabilities: { experimental: { "codex/sandbox-state-meta": {} } },
|
|
47561
47658
|
instructions: [
|
|
@@ -47646,9 +47743,7 @@ Matched: ${h.matched.join(", ")}`).join(`
|
|
|
47646
47743
|
return text2(`Nothing was searched: ${r.refused}`, true);
|
|
47647
47744
|
if (!r.hits.length)
|
|
47648
47745
|
return text2(`No record ${among(r)}holds most of: ${r.terms.join(", ") || "(no searchable words)"}. ${r.weaker} weaker matches left out. ` + (r.stopped ? "Search with more specific words. " : "") + "Search again with other words or the other language, or search sources; status says whether sessions are still untraced.");
|
|
47649
|
-
return text2(framed(`${r.hits
|
|
47650
|
-
|
|
47651
|
-
`)}${stoppedAfter(r)}
|
|
47746
|
+
return text2(framed(`${hitsText(r.hits)}${stoppedAfter(r)}
|
|
47652
47747
|
|
|
47653
47748
|
Read a record by its key before relying on it.`));
|
|
47654
47749
|
} catch (e) {
|
|
@@ -47757,8 +47852,7 @@ server.registerTool("review_select", {
|
|
|
47757
47852
|
if (!hits.length)
|
|
47758
47853
|
return text2(`Decision lane: checked. No active record applies to the ${files.length} changed files.`);
|
|
47759
47854
|
return text2(`Decision lane: checked. ${hits.length} records apply; read each before judging it.
|
|
47760
|
-
${framed(
|
|
47761
|
-
`))}`);
|
|
47855
|
+
${framed(await selectedText(db, hits))}`);
|
|
47762
47856
|
} catch (e) {
|
|
47763
47857
|
return notChecked(e);
|
|
47764
47858
|
}
|