simt-dev 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +191 -870
- package/README.md +188 -213
- package/bin/simt.mjs +19 -55
- package/docs/decisions/ADR-014-workspace-is-configuration.md +2 -2
- package/docs/decisions/ADR-025-a-baseline-is-a-region-in-a-file-the-repo-owns.md +1 -1
- package/docs/decisions/ADR-026-a-config-without-a-region-is-a-file-the-repo-owns.md +1 -1
- package/docs/decisions/ADR-027-the-one-gitattributes-line-that-is-neither-dangerous-nor-empty.md +1 -1
- package/docs/decisions/ADR-028-a-base-file-is-a-floor-so-the-disagreement-it-cannot-repair-becomes-a-check.md +1 -1
- package/docs/decisions/ADR-029-the-pins-a-repository-declares-are-its-own.md +1 -1
- package/docs/decisions/ADR-030-what-a-plan-does-not-ship-is-a-decision-with-a-record.md +1 -1
- package/docs/decisions/ADR-048-a-baseline-carries-only-keys-a-reader-honours.md +1 -1
- package/docs/decisions/ADR-082-the-tracker-override-is-a-manifest-key.md +1 -1
- package/docs/decisions/ADR-099-the-release-verifies-served-bytes-and-the-formula-mirrors-them.md +1 -1
- package/docs/decisions/ADR-117-the-darwin-release-builds-on-supported-macos-15-runners-and-a-re-run-is-idempotent.md +81 -0
- package/docs/decisions/ADR-118-the-bench-corpus-is-keyed-on-history-is-append-only-and-is-portable-as-a-sealed-document.md +91 -0
- package/docs/decisions/ADR-119-the-environment-variable-points-into-a-workspace-the-repo-declared.md +90 -0
- package/docs/decisions/ADR-120-a-pre-push-whose-tree-is-already-proven-green-skips-the-test-gate-on-that-evidence.md +96 -0
- package/docs/decisions/ADR-122-the-shims-move-into-the-state-directory-and-the-legacy-directory-removes-itself.md +94 -0
- package/docs/decisions/ADR-123-a-surplus-positional-is-not-refused-because-arity-is-not-in-the-source.md +57 -0
- package/docs/decisions/ADR-124-the-manifest-has-a-schema-in-the-tool-and-its-unknown-keys-stay-the-repositorys.md +64 -0
- package/docs/decisions/ADR-125-a-shared-context-belongs-to-a-read-and-a-writer-reads-directly.md +65 -0
- package/docs/decisions/ADR-126-the-mcp-surface-stays-a-reader-so-there-is-no-write-path-to-build.md +68 -0
- package/docs/decisions/ADR-127-the-mcp-tool-list-is-written-by-hand-and-roots-is-not-asked-for.md +65 -0
- package/docs/decisions/ADR-128-a-research-plan-is-a-plan-and-the-tracker-reads-it.md +42 -0
- package/ecosystems/bun.json +6 -0
- package/ecosystems/cargo.json +25 -0
- package/ecosystems/composer.json +6 -0
- package/ecosystems/dart.json +24 -0
- package/ecosystems/deno.json +21 -0
- package/ecosystems/devcontainers.json +6 -0
- package/ecosystems/docker-compose.json +6 -0
- package/ecosystems/docker.json +6 -0
- package/ecosystems/dotnet.json +25 -0
- package/ecosystems/elixir.json +23 -0
- package/ecosystems/gitsubmodule.json +6 -0
- package/ecosystems/go.json +25 -0
- package/ecosystems/gradle.json +13 -0
- package/ecosystems/maven.json +23 -0
- package/ecosystems/node.json +25 -0
- package/ecosystems/python.json +20 -0
- package/ecosystems/ruby.json +23 -0
- package/ecosystems/swift.json +25 -0
- package/ecosystems/terraform.json +6 -0
- package/ecosystems/uv.json +6 -0
- package/harnesses/antigravity.json +162 -0
- package/harnesses/claude.json +206 -0
- package/harnesses/codex.json +136 -0
- package/harnesses/copilot.json +88 -0
- package/harnesses/cursor.json +140 -0
- package/harnesses/deepseek.json +99 -0
- package/harnesses/gemini.json +122 -0
- package/harnesses/opencode.json +173 -0
- package/package.json +12 -2
- package/rules/core.md +7 -0
- package/rules/mdc/intake-questions.mdc +8 -0
- package/rules/mdc/slice-commit.mdc +57 -4
- package/src/advice/advice.mjs +3 -2
- package/src/agenthooks/prompt.mjs +5 -2
- package/src/agenthooks/render.mjs +86 -180
- package/src/agenthooks/shim.mjs +1 -1
- package/src/agents/render.mjs +15 -18
- package/src/api.mjs +31 -0
- package/src/argv.mjs +48 -3
- package/src/bench/ablate.mjs +3 -4
- package/src/bench/adapters.json +163 -105
- package/src/bench/adapters.mjs +466 -0
- package/src/bench/arms.mjs +333 -0
- package/src/bench/grade.mjs +33 -73
- package/src/bench/graders.mjs +42 -0
- package/src/bench/mine.mjs +121 -475
- package/src/bench/probe-ledger.mjs +149 -0
- package/src/bench/probes.catalog.mjs +918 -0
- package/src/bench/probes.mjs +79 -1064
- package/src/bench/render.mjs +111 -0
- package/src/bench/report.mjs +120 -962
- package/src/bench/run.mjs +185 -802
- package/src/bench/variants.mjs +4 -5
- package/src/bench/workspace.mjs +160 -0
- package/src/brief/assemble.mjs +48 -15
- package/src/ci/dependabot.mjs +25 -55
- package/src/ci/workflow.mjs +30 -7
- package/src/cli.mjs +45 -0
- package/src/commands/agent.mjs +4 -13
- package/src/commands/agenthook.mjs +70 -40
- package/src/commands/bench.mjs +191 -18
- package/src/commands/brief.mjs +5 -4
- package/src/commands/ci.mjs +13 -14
- package/src/commands/compare.mjs +42 -42
- package/src/commands/completions.mjs +2 -6
- package/src/commands/decide.mjs +4 -4
- package/src/commands/distill.mjs +4 -3
- package/src/commands/docs.mjs +88 -0
- package/src/commands/doctor.mjs +11 -7
- package/src/commands/evidence.mjs +6 -9
- package/src/commands/explain.mjs +0 -1
- package/src/commands/gate.mjs +26 -25
- package/src/commands/graph.mjs +2 -3
- package/src/commands/harness.mjs +6 -5
- package/src/commands/help.mjs +168 -19
- package/src/commands/hooks.mjs +119 -49
- package/src/commands/ignore.mjs +13 -8
- package/src/commands/impact.mjs +8 -15
- package/src/commands/index.mjs +318 -193
- package/src/commands/init-scan.mjs +90 -0
- package/src/commands/init.mjs +124 -105
- package/src/commands/lease.mjs +73 -25
- package/src/commands/lessons.mjs +10 -9
- package/src/commands/local.mjs +3 -3
- package/src/commands/map.mjs +24 -14
- package/src/commands/mcp.mjs +2 -18
- package/src/commands/pack.mjs +9 -6
- package/src/commands/plan.mjs +6 -5
- package/src/commands/plugin.mjs +2 -2
- package/src/commands/prbody.mjs +3 -3
- package/src/commands/repoindex.mjs +12 -11
- package/src/commands/review.mjs +2 -2
- package/src/commands/rules.mjs +37 -16
- package/src/commands/shared.mjs +14 -19
- package/src/commands/skills.mjs +33 -32
- package/src/commands/skillscompile.mjs +7 -8
- package/src/commands/skillsinstall.mjs +7 -6
- package/src/commands/slice.mjs +220 -74
- package/src/commands/spec.mjs +167 -0
- package/src/commands/status.mjs +27 -18
- package/src/commands/sync.mjs +27 -25
- package/src/commands/tests.mjs +18 -21
- package/src/commands/tokens.mjs +21 -26
- package/src/commands/undo.mjs +5 -15
- package/src/commands/upgrade.mjs +15 -7
- package/src/commands/why.mjs +15 -21
- package/src/compare/hooksstate.mjs +2 -6
- package/src/compare/learn.mjs +3 -1
- package/src/compare/row.mjs +0 -2
- package/src/context.mjs +64 -0
- package/src/decisions/index-doc.mjs +46 -0
- package/src/decisions/index.mjs +3 -2
- package/src/diff.mjs +1 -1
- package/src/distill/distill.mjs +14 -1
- package/src/distill/engine.mjs +5 -1
- package/src/docs/commands.mjs +65 -0
- package/src/ecosystem/records.mjs +219 -0
- package/src/explain.mjs +31 -0
- package/src/gates/index.mjs +385 -0
- package/src/graph/compile.mjs +8 -4
- package/src/graph/load.mjs +6 -2
- package/src/graph/spec.mjs +10 -8
- package/src/harness/records.mjs +143 -0
- package/src/hooks/capability.mjs +7 -5
- package/src/hooks/dispatcher.mjs +209 -60
- package/src/hooks/gates.mjs +10 -43
- package/src/hooks/resolve.mjs +4 -14
- package/src/hooks/shim.mjs +5 -2
- package/src/hooks/where.mjs +2 -7
- package/src/identity.mjs +31 -12
- package/src/index/coverage.mjs +58 -0
- package/src/index/extract.mjs +6 -16
- package/src/index/gate.mjs +1 -1
- package/src/index/govern.mjs +26 -9
- package/src/index/impact.mjs +4 -8
- package/src/index/map.mjs +14 -15
- package/src/index/open.mjs +173 -0
- package/src/index/persist.mjs +224 -57
- package/src/index/store.mjs +82 -39
- package/src/index/tests.mjs +2 -6
- package/src/journal.mjs +94 -20
- package/src/kernel/card.mjs +37 -63
- package/src/kernel/checks.mjs +4 -10
- package/src/lease.mjs +102 -7
- package/src/ledger/index.mjs +150 -14
- package/src/local/client.mjs +4 -6
- package/src/locate.mjs +33 -9
- package/src/manifest/schema.mjs +213 -0
- package/src/mcp/paginate.mjs +33 -0
- package/src/mcp/resources.mjs +7 -10
- package/src/mcp/schemas.mjs +512 -0
- package/src/mcp/server.mjs +119 -25
- package/src/mcp/tools-facts.mjs +326 -0
- package/src/mcp/tools-read.mjs +221 -0
- package/src/mcp/tools.mjs +4 -967
- package/src/measure/corpus.mjs +150 -0
- package/src/measure/metrics.mjs +236 -0
- package/src/measure/records.mjs +211 -0
- package/src/measure/session.mjs +3 -2
- package/src/measure/stats.mjs +538 -0
- package/src/measure/task-schema.mjs +125 -0
- package/src/measure/tasks.mjs +69 -0
- package/src/memory/decide.mjs +6 -15
- package/src/memory/lesson-text.mjs +61 -0
- package/src/memory/lessons.mjs +29 -82
- package/src/name.mjs +7 -0
- package/src/node-version.mjs +37 -0
- package/src/output/index.mjs +209 -10
- package/src/packs/manage.mjs +32 -12
- package/src/packs.mjs +20 -2
- package/src/plan/index.mjs +1 -1
- package/src/plugins.mjs +22 -16
- package/src/prbody.mjs +7 -11
- package/src/projection/loaders.mjs +7 -692
- package/src/projection/lock.mjs +11 -1
- package/src/projection/manifest.mjs +29 -468
- package/src/projection/mdc.mjs +7 -8
- package/src/projection/project.mjs +126 -221
- package/src/projection/region.mjs +561 -0
- package/src/projection/rules.mjs +20 -550
- package/src/projection/surfaces.mjs +43 -210
- package/src/projection/targets.mjs +5 -5
- package/src/repo-root.mjs +31 -0
- package/src/resolve/eval.mjs +2 -1
- package/src/resolve/fingerprint.mjs +19 -22
- package/src/resolve/resolve.mjs +1 -41
- package/src/review/critic.mjs +10 -13
- package/src/run.mjs +98 -0
- package/src/secrets.mjs +14 -22
- package/src/state/events.mjs +129 -0
- package/src/status-snapshot.mjs +233 -0
- package/src/status.mjs +2 -6
- package/src/sys/git.mjs +82 -0
- package/src/sys/proc.mjs +151 -0
- package/src/testing/parsers.mjs +265 -0
- package/src/testing/patches.mjs +23 -0
- package/src/testing/paths.mjs +49 -0
- package/src/testing/runners.mjs +186 -0
- package/src/testing/vault.mjs +68 -0
- package/src/text/frontmatter.mjs +49 -0
- package/src/text/tokens.mjs +52 -0
- package/src/toolwords.mjs +3 -0
- package/src/util/fs.mjs +20 -0
- package/src/util/sort.mjs +12 -0
- package/src/workspace.mjs +58 -11
- package/src/write/plan.mjs +82 -0
- package/templates/plan/tracker.json +1 -0
- package/docs/decisions/ADR-033-three-plan-rows-closed-by-decision-not-by-authoring.md +0 -82
- package/docs/decisions/ADR-102-the-two-queued-review-rows-are-closed-by-ruling.md +0 -49
- package/docs/decisions/README.md +0 -600
- package/src/bench/tasks.mjs +0 -36
package/src/index/persist.mjs
CHANGED
|
@@ -9,19 +9,27 @@ import { mkdir, readFile, rename, stat, writeFile } from 'node:fs/promises';
|
|
|
9
9
|
import { dirname, join } from 'node:path';
|
|
10
10
|
|
|
11
11
|
import { LOCAL_DIR } from '../state.mjs';
|
|
12
|
+
import { cmp } from '../util/sort.mjs';
|
|
12
13
|
|
|
13
14
|
export const SQLITE_FILE = join(LOCAL_DIR, 'index.sqlite');
|
|
14
15
|
export const JSONL_FILE = join(LOCAL_DIR, 'index.jsonl');
|
|
15
16
|
const SQLITE_FILE_LEGACY = '.simt/index.sqlite';
|
|
16
17
|
const JSONL_FILE_LEGACY = '.simt/index.jsonl';
|
|
17
|
-
export const SCHEMA_V = '
|
|
18
|
+
export const SCHEMA_V = '5';
|
|
18
19
|
|
|
19
20
|
// The FTS tables carry their tokenizer in their definition, so `if not exists`
|
|
20
21
|
// cannot migrate them: a v3 database would keep tokenizing new rows the v3 way
|
|
21
|
-
// (ASCII fold, kind and ref searchable)
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
// (ASCII fold, kind and ref searchable), and a v4 `fts` table cannot grow the
|
|
23
|
+
// node_id column the v5 layout needs. The preamble creates them only when they
|
|
24
|
+
// are missing (a cold build); the 3 -> 4 and 4 -> 5 migrations below drop and
|
|
25
|
+
// recreate them, which is the versioned replacement for the per-write wipe (Q44).
|
|
26
|
+
export const FTS_SCHEMA = 'create virtual table if not exists fts using fts5(body, node_id UNINDEXED, kind UNINDEXED, tokenize = "unicode61 remove_diacritics 2")';
|
|
27
|
+
export const FTS_TRI_SCHEMA = 'create virtual table if not exists fts_tri using fts5(kind UNINDEXED, ref UNINDEXED, body, tokenize = "trigram")';
|
|
28
|
+
|
|
29
|
+
// The v4 `fts` shape. The 3 -> 4 step recreates the table with THIS shape so a
|
|
30
|
+
// v3 database emerges looking exactly like a genuine v4 one - the 4 -> 5 step
|
|
31
|
+
// then copies its `ref` column, which the v5 shape no longer has.
|
|
32
|
+
const FTS_V4_SCHEMA = 'create virtual table fts using fts5(kind UNINDEXED, ref UNINDEXED, body, tokenize = "unicode61 remove_diacritics 2")';
|
|
25
33
|
|
|
26
34
|
// A database written by a *newer* simt is never opened for write, and never
|
|
27
35
|
// read blind (review 0003 Q44): the mismatched canonical file stays with its
|
|
@@ -53,11 +61,58 @@ const MIGRATIONS = [
|
|
|
53
61
|
to: 4,
|
|
54
62
|
apply: (db) => {
|
|
55
63
|
db.exec('drop table if exists fts');
|
|
56
|
-
db.exec(
|
|
64
|
+
db.exec(FTS_V4_SCHEMA);
|
|
57
65
|
db.exec('drop table if exists fts_tri');
|
|
58
66
|
db.exec(FTS_TRI_SCHEMA);
|
|
59
67
|
},
|
|
60
68
|
},
|
|
69
|
+
// v4 is per-kind tables (files/symbols/edges/embeddings); v5 is the entity
|
|
70
|
+
// layout (sources/nodes/edges/vectors) with the read shapes restored as SQL
|
|
71
|
+
// in the store. `edges` and `fts` are built under `_v5` names and swapped in
|
|
72
|
+
// after the copy, because the v4 tables of those names still hold data.
|
|
73
|
+
// fts_tri keeps its (kind, ref, body) shape - the CJK trigram path is
|
|
74
|
+
// untouched (Q43).
|
|
75
|
+
{
|
|
76
|
+
from: 4,
|
|
77
|
+
to: 5,
|
|
78
|
+
apply: (db) => {
|
|
79
|
+
db.exec(TABLES);
|
|
80
|
+
db.exec(EDGES_DDL.replaceAll('edges(', 'edges_v5('));
|
|
81
|
+
db.exec(FTS_SCHEMA.replaceAll('exists fts ', 'exists fts_v5 '));
|
|
82
|
+
db.exec(`insert into sources(kind, path, revision, lang, bytes, status, props)
|
|
83
|
+
select 'blob', path, blob, lang, bytes, 'indexed', json_object('pkg', coalesce(pkg, '')) from files`);
|
|
84
|
+
db.exec(`insert into nodes(key, kind, source_id)
|
|
85
|
+
select 'file:' || path, 'file', id from sources where kind = 'blob'`);
|
|
86
|
+
db.exec(`insert into nodes(key, kind, source_id, name, line, props)
|
|
87
|
+
select 'sym:' || s.path || '#' || s.name || '@' || coalesce(s.line, 0), 'symbol', src.id,
|
|
88
|
+
s.name, s.line, json_object('kind', coalesce(s.kind, 'symbol'), 'sig', coalesce(s.sig, ''), 'exported', coalesce(s.exported, 0))
|
|
89
|
+
from symbols s join sources src on src.kind = 'blob' and src.path = s.path`);
|
|
90
|
+
db.exec(`insert into edges_v5(src_id, kind, dst_key, dst_id, weight, owner_source_id, trust)
|
|
91
|
+
select sn.id, e.kind, e.dst, dn.id, e.weight, src.id, 'observed'
|
|
92
|
+
from edges e
|
|
93
|
+
join sources src on src.kind = 'blob' and src.path = e.src
|
|
94
|
+
join nodes sn on sn.source_id = src.id and sn.kind = 'file'
|
|
95
|
+
left join sources dst on dst.kind = 'blob' and dst.path = e.dst
|
|
96
|
+
left join nodes dn on dn.source_id = dst.id and dn.kind = 'file'`);
|
|
97
|
+
db.exec(`insert into vectors(node_id, model, dims, vec)
|
|
98
|
+
select n.id, em.model, em.dims, em.vec
|
|
99
|
+
from embeddings em
|
|
100
|
+
join sources s on s.kind = 'blob' and s.path = em.path
|
|
101
|
+
join nodes n on n.source_id = s.id and n.kind = 'file'`);
|
|
102
|
+
db.exec(`insert into fts_v5(body, node_id, kind)
|
|
103
|
+
select f.body, n.id, f.kind
|
|
104
|
+
from fts f
|
|
105
|
+
join sources s on s.kind = 'blob' and s.path = f.ref
|
|
106
|
+
join nodes n on n.source_id = s.id and n.kind = 'file'`);
|
|
107
|
+
db.exec('drop table files');
|
|
108
|
+
db.exec('drop table symbols');
|
|
109
|
+
db.exec('drop table embeddings');
|
|
110
|
+
db.exec('drop table edges');
|
|
111
|
+
db.exec('drop table fts');
|
|
112
|
+
db.exec('alter table edges_v5 rename to edges');
|
|
113
|
+
db.exec('alter table fts_v5 rename to fts');
|
|
114
|
+
},
|
|
115
|
+
},
|
|
61
116
|
];
|
|
62
117
|
|
|
63
118
|
/**
|
|
@@ -67,21 +122,89 @@ const MIGRATIONS = [
|
|
|
67
122
|
* each is idempotent on an already-migrated table set.
|
|
68
123
|
*/
|
|
69
124
|
function migrateDb(db) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
125
|
+
let at = Number(db.prepare('pragma user_version').get()?.user_version ?? 0);
|
|
126
|
+
// `user_version 0` is ambiguous: a cold build has no legacy tables at all,
|
|
127
|
+
// and a pre-stamp database still holds the v3 shape. The 4 -> 5 step copies
|
|
128
|
+
// FROM the v4 tables (`files`, `symbols`, ...), so a cold build must not run
|
|
129
|
+
// it - the 3 -> 4 step would leave its `fts` in the v4 shape behind.
|
|
130
|
+
const legacy =
|
|
131
|
+
at !== 0 ||
|
|
132
|
+
db.prepare(`select count(*) as n from sqlite_master where type = 'table' and name in ('files', 'symbols', 'embeddings')`).get().n > 0;
|
|
133
|
+
if (legacy) {
|
|
134
|
+
for (const step of MIGRATIONS) {
|
|
135
|
+
if (at === 0 || (at >= step.from && at < step.to)) {
|
|
136
|
+
step.apply(db);
|
|
137
|
+
at = step.to; // the next step sees the version this one left behind
|
|
138
|
+
}
|
|
139
|
+
}
|
|
73
140
|
}
|
|
74
141
|
db.exec(`pragma user_version = ${Number(SCHEMA_V)}`);
|
|
75
142
|
}
|
|
76
143
|
|
|
77
|
-
const
|
|
78
|
-
create table if not exists
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
144
|
+
const TABLES = `
|
|
145
|
+
create table if not exists sources(
|
|
146
|
+
id integer primary key,
|
|
147
|
+
kind text not null default 'blob',
|
|
148
|
+
path text not null,
|
|
149
|
+
revision text,
|
|
150
|
+
lang text,
|
|
151
|
+
bytes integer,
|
|
152
|
+
status text not null default 'indexed',
|
|
153
|
+
props text,
|
|
154
|
+
unique(kind, path)
|
|
155
|
+
);
|
|
156
|
+
create table if not exists nodes(
|
|
157
|
+
id integer primary key,
|
|
158
|
+
key text not null unique,
|
|
159
|
+
kind text,
|
|
160
|
+
source_id integer,
|
|
161
|
+
name text,
|
|
162
|
+
line integer,
|
|
163
|
+
span_start integer,
|
|
164
|
+
span_end integer,
|
|
165
|
+
digest text,
|
|
166
|
+
trust text not null default 'observed',
|
|
167
|
+
props text
|
|
168
|
+
);
|
|
169
|
+
create table if not exists vectors(
|
|
170
|
+
node_id integer primary key,
|
|
171
|
+
model text,
|
|
172
|
+
dims integer,
|
|
173
|
+
vec blob
|
|
174
|
+
);
|
|
82
175
|
create table if not exists meta(key text primary key, value text);
|
|
83
176
|
`;
|
|
84
177
|
|
|
178
|
+
// The v5 edges table. Named `edges_v5` in the 4 -> 5 migration, because the
|
|
179
|
+
// v4 database still holds an `edges` table of a different shape until the
|
|
180
|
+
// copy below is done.
|
|
181
|
+
const EDGES_DDL = `create table if not exists edges(
|
|
182
|
+
src_id integer,
|
|
183
|
+
kind text,
|
|
184
|
+
dst_key text,
|
|
185
|
+
dst_id integer,
|
|
186
|
+
weight real,
|
|
187
|
+
owner_source_id integer,
|
|
188
|
+
trust text,
|
|
189
|
+
props text,
|
|
190
|
+
primary key(src_id, kind, dst_key)
|
|
191
|
+
);`;
|
|
192
|
+
|
|
193
|
+
// Q76: the v4 layout had no index on the edge endpoints or the node owners -
|
|
194
|
+
// the dependent, impact and coverage walks each paid a full scan per file.
|
|
195
|
+
// Executed after `migrateDb`, not in the preamble: on a v3 database the v3
|
|
196
|
+
// `edges` table survives `if not exists` until the 4 -> 5 step swaps in the
|
|
197
|
+
// v5 shape, and `idx_edges_dst` needs the `dst_id` column that only the v5
|
|
198
|
+
// table has.
|
|
199
|
+
const INDEXES = `
|
|
200
|
+
create index if not exists idx_edges_dst on edges(dst_id, kind);
|
|
201
|
+
create index if not exists idx_edges_owner on edges(owner_source_id);
|
|
202
|
+
create index if not exists idx_nodes_source on nodes(source_id);
|
|
203
|
+
create index if not exists idx_nodes_kind_name on nodes(kind, name);
|
|
204
|
+
`;
|
|
205
|
+
|
|
206
|
+
const SCHEMA = `${TABLES}\n${EDGES_DDL}\n`;
|
|
207
|
+
|
|
85
208
|
export function encodeVectorBlob(vec) {
|
|
86
209
|
if (!Array.isArray(vec) && !(vec instanceof Float32Array)) return Buffer.alloc(0);
|
|
87
210
|
const f32 = vec instanceof Float32Array ? vec : Float32Array.from(vec, (v) => Number(v) || 0);
|
|
@@ -186,55 +309,95 @@ export async function writeSqlite(root, next, removed, symbols, edges, fts, embe
|
|
|
186
309
|
db.exec('pragma busy_timeout = 5000');
|
|
187
310
|
db.exec('pragma synchronous = normal');
|
|
188
311
|
db.exec(SCHEMA);
|
|
312
|
+
db.exec(FTS_SCHEMA);
|
|
313
|
+
db.exec(FTS_TRI_SCHEMA);
|
|
189
314
|
db.exec('begin immediate');
|
|
190
315
|
migrateDb(db);
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
//
|
|
195
|
-
//
|
|
196
|
-
//
|
|
197
|
-
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
316
|
+
// The `edges` table is v5-shaped now on every path: the preamble created
|
|
317
|
+
// it on a cold build, the migration swapped it in on a legacy one.
|
|
318
|
+
db.exec(INDEXES);
|
|
319
|
+
// One source row per file, keyed (kind, path): the revision is the git blob
|
|
320
|
+
// sha, so an unchanged blob keeps its row, and the file's node, vectors,
|
|
321
|
+
// edges and fts rows all hang off it. `props` carries what the v5 columns
|
|
322
|
+
// do not name - the package directory, which the files read shape serves.
|
|
323
|
+
const upsertSource = db.prepare(`insert into sources(kind, path, revision, lang, bytes, status, props)
|
|
324
|
+
values('blob', ?, ?, ?, ?, 'indexed', ?)
|
|
325
|
+
on conflict(kind, path) do update set revision = excluded.revision, lang = excluded.lang,
|
|
326
|
+
bytes = excluded.bytes, props = excluded.props`);
|
|
327
|
+
const sourceIdOf = db.prepare(`select id from sources where kind = 'blob' and path = ?`);
|
|
328
|
+
// `prepare` compiles only the FIRST statement in a string (Q76): a
|
|
329
|
+
// multi-statement delete prepared as one ran only its head, and the
|
|
330
|
+
// surviving `file:` node collided with the next insert on `nodes.key`.
|
|
331
|
+
const dropDependents = [
|
|
332
|
+
db.prepare('delete from vectors where node_id in (select id from nodes where source_id = ?)'),
|
|
333
|
+
db.prepare('delete from fts where node_id in (select id from nodes where source_id = ?)'),
|
|
334
|
+
db.prepare('delete from edges where owner_source_id = ?'),
|
|
335
|
+
db.prepare('delete from nodes where source_id = ?'),
|
|
336
|
+
];
|
|
337
|
+
const addFileNode = db.prepare(`insert into nodes(key, kind, source_id, trust) values('file:' || ?, 'file', ?, 'observed')`);
|
|
338
|
+
const addSymbol = db.prepare(`insert into nodes(key, kind, source_id, name, line, props)
|
|
339
|
+
values('sym:' || ? || '#' || ? || '@' || ?, 'symbol', ?, ?, ?, ?)`);
|
|
340
|
+
const addEdge = db.prepare(`insert into edges(src_id, kind, dst_key, dst_id, weight, owner_source_id, trust)
|
|
341
|
+
values (?, ?, ?, ?, ?, ?, 'observed')`);
|
|
342
|
+
const addFts = db.prepare('insert into fts(body, node_id, kind) values (?, ?, ?)');
|
|
206
343
|
const addTri = db.prepare('insert into fts_tri(kind, ref, body) values (?, ?, ?)');
|
|
207
|
-
const
|
|
208
|
-
on conflict(
|
|
344
|
+
const upsertVector = db.prepare(`insert into vectors(node_id, model, dims, vec) values (?, ?, ?, ?)
|
|
345
|
+
on conflict(node_id) do update set model = excluded.model, dims = excluded.dims, vec = excluded.vec`);
|
|
346
|
+
const dropSource = [
|
|
347
|
+
db.prepare(`delete from vectors where node_id in (select id from nodes where source_id in (select id from sources where kind = 'blob' and path = ?))`),
|
|
348
|
+
db.prepare(`delete from fts where node_id in (select id from nodes where source_id in (select id from sources where kind = 'blob' and path = ?))`),
|
|
349
|
+
db.prepare(`delete from edges where owner_source_id in (select id from sources where kind = 'blob' and path = ?)`),
|
|
350
|
+
db.prepare('delete from edges where dst_key = ?'),
|
|
351
|
+
db.prepare(`delete from nodes where source_id in (select id from sources where kind = 'blob' and path = ?)`),
|
|
352
|
+
db.prepare(`delete from sources where kind = 'blob' and path = ?`),
|
|
353
|
+
];
|
|
209
354
|
for (const row of next) {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
dropFts.run(row.path);
|
|
214
|
-
dropTri.run(row.path);
|
|
215
|
-
dropEmbed.run(row.path);
|
|
355
|
+
const sourceId = sourceIdOf.get(row.path)?.id;
|
|
356
|
+
if (sourceId !== undefined) for (const st of dropDependents) st.run(sourceId);
|
|
357
|
+
upsertSource.run(row.path, row.blob, row.lang, row.bytes, JSON.stringify({ pkg: row.pkg ?? '' }));
|
|
216
358
|
}
|
|
217
359
|
for (const path of removed) {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
360
|
+
for (const st of dropSource) st.run(path);
|
|
361
|
+
// fts_tri is still path-keyed: the trigram table keeps its v4 shape (Q43).
|
|
362
|
+
db.prepare('delete from fts_tri where ref = ?').run(path);
|
|
363
|
+
}
|
|
364
|
+
for (const row of next) {
|
|
365
|
+
addFileNode.run(row.path, sourceIdOf.get(row.path).id);
|
|
366
|
+
}
|
|
367
|
+
// The ids a changed file's rows hang off were just rebuilt; an unchanged
|
|
368
|
+
// file's rows keep their old ids. One query maps every path to both.
|
|
369
|
+
const ids = new Map();
|
|
370
|
+
for (const r of db.prepare(`select s.path, s.id as source_id, n.id as node_id
|
|
371
|
+
from sources s join nodes n on n.source_id = s.id and n.kind = 'file'
|
|
372
|
+
where s.kind = 'blob'`).all()) {
|
|
373
|
+
ids.set(r.path, { sourceId: r.source_id, nodeId: r.node_id });
|
|
374
|
+
}
|
|
375
|
+
for (const row of symbols) {
|
|
376
|
+
const entry = ids.get(row.path);
|
|
377
|
+
if (!entry) continue;
|
|
378
|
+
addSymbol.run(row.path, row.name, row.line, entry.sourceId, row.name, row.line,
|
|
379
|
+
JSON.stringify({ kind: row.kind ?? 'symbol', sig: row.sig ?? '', exported: row.exported ? 1 : 0 }));
|
|
380
|
+
}
|
|
381
|
+
for (const row of edges) {
|
|
382
|
+
const src = ids.get(row.src);
|
|
383
|
+
if (!src) continue;
|
|
384
|
+
const dst = ids.get(row.dst);
|
|
385
|
+
addEdge.run(src.nodeId, row.kind, row.dst, dst ? dst.nodeId : null, row.weight, src.sourceId);
|
|
225
386
|
}
|
|
226
|
-
for (const row of symbols) addSymbol.run(row.path, row.name, row.kind, row.line, row.sig, row.exported);
|
|
227
|
-
for (const row of edges) addEdge.run(row.src, row.dst, row.kind, row.weight);
|
|
228
387
|
for (const row of fts) {
|
|
229
|
-
|
|
388
|
+
const entry = ids.get(row.ref);
|
|
389
|
+
if (!entry) continue;
|
|
390
|
+
addFts.run(row.body, entry.nodeId, row.kind);
|
|
230
391
|
// The trigram table carries folded raw text for CJK-bearing sections
|
|
231
392
|
// only; an ASCII-only section is already reachable through `fts`.
|
|
232
393
|
if (row.raw) addTri.run(row.kind, row.ref, row.raw);
|
|
233
394
|
}
|
|
234
395
|
for (const row of embeddings) {
|
|
396
|
+
const entry = ids.get(row.path);
|
|
397
|
+
if (!entry) continue;
|
|
235
398
|
const vecBlob = Buffer.isBuffer(row.vec) || row.vec instanceof Uint8Array ? row.vec : encodeVectorBlob(row.vec);
|
|
236
399
|
const dims = Number.isInteger(row.dims) ? row.dims : Math.floor(vecBlob.byteLength / 4);
|
|
237
|
-
|
|
400
|
+
upsertVector.run(entry.nodeId, row.model ?? '', dims, vecBlob);
|
|
238
401
|
}
|
|
239
402
|
const upsertMeta = db.prepare(
|
|
240
403
|
'insert into meta(key, value) values (?, ?) on conflict(key) do update set value = excluded.value',
|
|
@@ -529,13 +692,20 @@ export async function readTreeDigest(root, backend) {
|
|
|
529
692
|
}
|
|
530
693
|
|
|
531
694
|
export async function countSqlite(root) {
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
const
|
|
695
|
+
// One open, three counts: the v4 layout paid a full open/close per metric,
|
|
696
|
+
// and a read that only wants the counts was buying three connections (Q76).
|
|
697
|
+
const { rows } = await readSqliteRows(
|
|
698
|
+
root,
|
|
699
|
+
`select
|
|
700
|
+
(select count(*) from nodes where kind = 'symbol') as symbols,
|
|
701
|
+
(select count(*) from edges) as edges,
|
|
702
|
+
(select count(*) from vectors) as embeddings`,
|
|
703
|
+
);
|
|
704
|
+
const row = rows[0] ?? {};
|
|
535
705
|
return {
|
|
536
|
-
symbols: Number(symbols
|
|
537
|
-
edges: Number(edges
|
|
538
|
-
embeddings: Number(embeddings
|
|
706
|
+
symbols: Number(row.symbols ?? 0),
|
|
707
|
+
edges: Number(row.edges ?? 0),
|
|
708
|
+
embeddings: Number(row.embeddings ?? 0),
|
|
539
709
|
};
|
|
540
710
|
}
|
|
541
711
|
|
|
@@ -551,6 +721,3 @@ function byEdge(a, b) {
|
|
|
551
721
|
return cmp(a.src, b.src) || cmp(a.dst, b.dst) || cmp(a.kind, b.kind);
|
|
552
722
|
}
|
|
553
723
|
|
|
554
|
-
function cmp(a, b) {
|
|
555
|
-
return a < b ? -1 : a > b ? 1 : 0;
|
|
556
|
-
}
|
package/src/index/store.mjs
CHANGED
|
@@ -8,10 +8,16 @@ import { execFile, spawn } from 'node:child_process';
|
|
|
8
8
|
import { stat } from 'node:fs/promises';
|
|
9
9
|
import { dirname, join } from 'node:path';
|
|
10
10
|
|
|
11
|
+
import { git as gitRaw } from '../sys/git.mjs';
|
|
12
|
+
|
|
11
13
|
import { readManifest } from '../projection/manifest.mjs';
|
|
12
14
|
import { readLocalConfig } from '../local/config.mjs';
|
|
13
15
|
import { localEmbed } from '../local/client.mjs';
|
|
14
16
|
import { extract, resolveImports } from './extract.mjs';
|
|
17
|
+
// Q70: the extension-to-language map is the records' `languages[]` (ADR-013). `canonical`
|
|
18
|
+
// names the extensions that RESOLVE to a language (JavaScript claims `.ts` for extraction
|
|
19
|
+
// but not for resolution), so a record's extraction and resolution cannot desynchronize.
|
|
20
|
+
import { languageResolver } from '../ecosystem/records.mjs';
|
|
15
21
|
import {
|
|
16
22
|
FTS_SCHEMA,
|
|
17
23
|
FTS_TRI_SCHEMA,
|
|
@@ -34,24 +40,32 @@ export { JSONL_FILE, SQLITE_FILE, decodeVectorBlob, encodeVectorBlob };
|
|
|
34
40
|
|
|
35
41
|
const MARKERS = ['package.json', 'pyproject.toml', 'go.mod', 'Cargo.toml', 'pom.xml'];
|
|
36
42
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
43
|
+
// A tracked blob larger than this is recorded with its size and counted in
|
|
44
|
+
// `skipped`, not streamed and tokenized: a 65 MiB file is indexed as a row, and
|
|
45
|
+
// tokenizing it would buy nothing (Q76). The cap sits under that file on purpose
|
|
46
|
+
// so the 65 MiB test exercises the skip, not the tokenize path.
|
|
47
|
+
const BLOB_CAP = 16 * 1024 * 1024;
|
|
48
|
+
|
|
49
|
+
const LANG = languageResolver();
|
|
42
50
|
|
|
43
51
|
/** `sqlite` when this process can open a database and create an FTS5 table. */
|
|
52
|
+
// Probed once per process: the check builds both FTS schemas in a memory
|
|
53
|
+
// database, and every read path used to pay for that (Q76).
|
|
54
|
+
let backendProbe;
|
|
44
55
|
async function probeBackend() {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
56
|
+
backendProbe ??= (async () => {
|
|
57
|
+
try {
|
|
58
|
+
const { DatabaseSync } = await import('node:sqlite');
|
|
59
|
+
const db = new DatabaseSync(':memory:');
|
|
60
|
+
db.exec(FTS_SCHEMA);
|
|
61
|
+
db.exec(FTS_TRI_SCHEMA);
|
|
62
|
+
db.close();
|
|
63
|
+
return 'sqlite';
|
|
64
|
+
} catch {
|
|
65
|
+
return 'jsonl';
|
|
66
|
+
}
|
|
67
|
+
})();
|
|
68
|
+
return backendProbe;
|
|
55
69
|
}
|
|
56
70
|
|
|
57
71
|
export function ftsKindOf(filePath, lang = '') {
|
|
@@ -224,7 +238,10 @@ export async function reindex(root, opts = {}) {
|
|
|
224
238
|
changed.push(entry);
|
|
225
239
|
}
|
|
226
240
|
const sizes = await blobSizes(root, changed.map((entry) => entry.blob));
|
|
227
|
-
|
|
241
|
+
// Over-cap blobs are recorded with their size and counted in `skipped`, not
|
|
242
|
+
// streamed and tokenized: their row is written, their text is never read.
|
|
243
|
+
const textBlobs = changed.map((entry) => entry.blob).filter((blob) => (sizes.get(blob) ?? 0) <= BLOB_CAP);
|
|
244
|
+
const texts = await blobTexts(root, textBlobs);
|
|
228
245
|
const adapter = await resolveAdapter(root, opts);
|
|
229
246
|
const paths = new Set(listed.map((entry) => entry.path));
|
|
230
247
|
const next = [];
|
|
@@ -383,7 +400,14 @@ function withReadError(rows, error) {
|
|
|
383
400
|
export async function readFiles(root, opts = {}) {
|
|
384
401
|
const backend = opts.backend ?? await probeBackend();
|
|
385
402
|
if (backend === 'sqlite') {
|
|
386
|
-
|
|
403
|
+
// The v5 layout keys files as `sources` rows; the git blob sha is `revision`
|
|
404
|
+
// and the package directory rides in `props`, which the v4 `files` table
|
|
405
|
+
// named `pkg`.
|
|
406
|
+
const { rows, error } = await readSqliteRows(
|
|
407
|
+
root,
|
|
408
|
+
`select s.path as path, s.revision as blob, s.lang as lang, s.bytes as bytes,
|
|
409
|
+
json_extract(s.props, '$.pkg') as pkg from sources s where s.kind = 'blob' order by s.path`,
|
|
410
|
+
);
|
|
387
411
|
return withReadError(rows, error);
|
|
388
412
|
}
|
|
389
413
|
const { rows, error } = await readJsonl(root, 'files');
|
|
@@ -394,7 +418,14 @@ export async function readFiles(root, opts = {}) {
|
|
|
394
418
|
export async function readSymbols(root, opts = {}) {
|
|
395
419
|
const backend = opts.backend ?? await probeBackend();
|
|
396
420
|
if (backend === 'sqlite') {
|
|
397
|
-
const { rows, error } = await readSqliteRows(
|
|
421
|
+
const { rows, error } = await readSqliteRows(
|
|
422
|
+
root,
|
|
423
|
+
`select s.path as path, n.name as name, json_extract(n.props, '$.kind') as kind,
|
|
424
|
+
n.line as line, json_extract(n.props, '$.sig') as sig,
|
|
425
|
+
json_extract(n.props, '$.exported') as exported
|
|
426
|
+
from nodes n join sources s on s.id = n.source_id and s.kind = 'blob'
|
|
427
|
+
where n.kind = 'symbol' order by s.path, n.line, n.name, n.kind`,
|
|
428
|
+
);
|
|
398
429
|
return withReadError(rows, error);
|
|
399
430
|
}
|
|
400
431
|
const { rows, error } = await readJsonl(root, 'symbols');
|
|
@@ -405,7 +436,14 @@ export async function readSymbols(root, opts = {}) {
|
|
|
405
436
|
export async function readEdges(root, opts = {}) {
|
|
406
437
|
const backend = opts.backend ?? await probeBackend();
|
|
407
438
|
if (backend === 'sqlite') {
|
|
408
|
-
|
|
439
|
+
// Paths come back from the node key (`file:<path>`) and the stable `dst_key`:
|
|
440
|
+
// node ids are regenerated when a file changes, so a path is the only key
|
|
441
|
+
// that survives a reindex of one endpoint.
|
|
442
|
+
const { rows, error } = await readSqliteRows(
|
|
443
|
+
root,
|
|
444
|
+
`select substr(ns.key, 6) as src, e.dst_key as dst, e.kind as kind, e.weight as weight
|
|
445
|
+
from edges e join nodes ns on ns.id = e.src_id order by src, dst, kind`,
|
|
446
|
+
);
|
|
409
447
|
return withReadError(rows, error);
|
|
410
448
|
}
|
|
411
449
|
const { rows, error } = await readJsonl(root, 'edges');
|
|
@@ -416,7 +454,11 @@ export async function readEdges(root, opts = {}) {
|
|
|
416
454
|
export async function readFts(root, opts = {}) {
|
|
417
455
|
const backend = opts.backend ?? await probeBackend();
|
|
418
456
|
if (backend === 'sqlite') {
|
|
419
|
-
const { rows, error } = await readSqliteRows(
|
|
457
|
+
const { rows, error } = await readSqliteRows(
|
|
458
|
+
root,
|
|
459
|
+
`select f.kind as kind, substr(n.key, 6) as ref, f.body as body
|
|
460
|
+
from fts f join nodes n on n.id = f.node_id order by ref, kind, f.rowid`,
|
|
461
|
+
);
|
|
420
462
|
return withReadError(rows, error);
|
|
421
463
|
}
|
|
422
464
|
const { rows, error } = await readJsonl(root, 'fts');
|
|
@@ -427,7 +469,12 @@ export async function readFts(root, opts = {}) {
|
|
|
427
469
|
export async function readEmbeddings(root, opts = {}) {
|
|
428
470
|
const backend = opts.backend ?? await probeBackend();
|
|
429
471
|
if (backend === 'sqlite') {
|
|
430
|
-
const { rows: raw, error } = await readSqliteRows(
|
|
472
|
+
const { rows: raw, error } = await readSqliteRows(
|
|
473
|
+
root,
|
|
474
|
+
`select substr(n.key, 6) as path, s.revision as blob, v.model as model, v.dims as dims, v.vec as vec
|
|
475
|
+
from vectors v join nodes n on n.id = v.node_id
|
|
476
|
+
join sources s on s.id = n.source_id and s.kind = 'blob' order by path`,
|
|
477
|
+
);
|
|
431
478
|
const rows = raw.map((row) => {
|
|
432
479
|
const vec = decodeVectorBlob(row.vec);
|
|
433
480
|
return {
|
|
@@ -500,11 +547,15 @@ export async function searchFtsBm25(root, task, opts = {}) {
|
|
|
500
547
|
async function ftsBranch(root, table, terms) {
|
|
501
548
|
if (terms.length === 0) return { rows: [], error: null };
|
|
502
549
|
const matchExpr = terms.map((t) => `"${t}"`).join(' OR ');
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
550
|
+
// `fts` is the v5 node-keyed table: the path comes back through the node key.
|
|
551
|
+
// `fts_tri` keeps its v4 (kind, ref, body) shape for the CJK trigram path (Q43),
|
|
552
|
+
// so it is queried the old way.
|
|
553
|
+
const sql = table === 'fts'
|
|
554
|
+
? `select f.kind as kind, substr(n.key, 6) as path, -bm25(f) as score
|
|
555
|
+
from fts f join nodes n on n.id = f.node_id
|
|
556
|
+
where f match ? order by score desc, path asc limit ?`
|
|
557
|
+
: `select kind, ref as path, -bm25(${table}) as score from ${table} where ${table} match ? order by score desc, path asc limit ?`;
|
|
558
|
+
const { rows, error } = await readSqliteRows(root, sql, [matchExpr, FTS_LIMIT]);
|
|
508
559
|
return {
|
|
509
560
|
rows: rows.map((row) => ({ kind: row.kind, path: row.path, score: Number(row.score) || 0 })),
|
|
510
561
|
error,
|
|
@@ -590,20 +641,10 @@ export async function packageOf(root, filePath, cache) {
|
|
|
590
641
|
}
|
|
591
642
|
}
|
|
592
643
|
|
|
593
|
-
|
|
594
|
-
return new Promise((resolve, reject) => {
|
|
595
|
-
const child = execFile('git', args, { cwd: root, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 }, (error, stdout, stderr) => {
|
|
596
|
-
if (error) reject(Object.assign(error, { stdout, stderr }));
|
|
597
|
-
else resolve(stdout);
|
|
598
|
-
});
|
|
599
|
-
if (input !== undefined) {
|
|
600
|
-
child.stdin.end(input);
|
|
601
|
-
}
|
|
602
|
-
});
|
|
603
|
-
}
|
|
644
|
+
|
|
604
645
|
|
|
605
646
|
async function lsFiles(root) {
|
|
606
|
-
const stdout = await
|
|
647
|
+
const stdout = await gitRaw(root, ['ls-files', '-s', '-z']);
|
|
607
648
|
const rows = [];
|
|
608
649
|
for (const rec of stdout.split('\0')) {
|
|
609
650
|
if (rec === '') continue;
|
|
@@ -738,7 +779,9 @@ async function blobSizes(root, blobs) {
|
|
|
738
779
|
const unique = [...new Set(blobs)];
|
|
739
780
|
const sizes = new Map();
|
|
740
781
|
if (unique.length === 0) return sizes;
|
|
741
|
-
const stdout = await
|
|
782
|
+
const stdout = await gitRaw(root, ['cat-file', '--batch-check=%(objectname) %(objectsize)'], {
|
|
783
|
+
input: `${unique.join('\n')}\n`,
|
|
784
|
+
});
|
|
742
785
|
for (const line of stdout.split('\n')) {
|
|
743
786
|
if (line === '') continue;
|
|
744
787
|
const sp = line.indexOf(' ');
|
package/src/index/tests.mjs
CHANGED
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
// the tree is not a guess. Co-change is a different kind: files that land in
|
|
7
7
|
// the same commit are not, by that fact, a test.
|
|
8
8
|
|
|
9
|
-
import { isTestPath } from '../
|
|
9
|
+
import { isTestPath } from '../testing/paths.mjs';
|
|
10
10
|
import { extract, resolveImports } from './extract.mjs';
|
|
11
|
+
import { cmp } from '../util/sort.mjs';
|
|
11
12
|
|
|
12
13
|
export { scriptSpecifiers } from './extract.mjs';
|
|
13
14
|
|
|
@@ -251,11 +252,6 @@ function byEdge(a, b) {
|
|
|
251
252
|
return cmp(a.kind, b.kind) || cmp(a.src, b.src) || cmp(a.dst, b.dst);
|
|
252
253
|
}
|
|
253
254
|
|
|
254
|
-
function cmp(a, b) {
|
|
255
|
-
if (a < b) return -1;
|
|
256
|
-
if (a > b) return 1;
|
|
257
|
-
return 0;
|
|
258
|
-
}
|
|
259
255
|
|
|
260
256
|
/** Imports in a file, resolved against the tracked tree. One path now: `extract`
|
|
261
257
|
* reads scripts with `scriptSpecifiers`, so gate, impact and the index agree on
|