scopecairn 0.14.1 → 0.16.1
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/README.md +121 -30
- package/dist/{agentsmd-GS2DHP65.js → agentsmd-WIZWH7QM.js} +5 -3
- package/dist/{chunk-DY2AAM62.js → chunk-ADAWTBOC.js} +2 -2
- package/dist/{chunk-IRYNTYZD.js → chunk-AK6TUDHG.js} +112 -18
- package/dist/{chunk-JQBYQ4EF.js → chunk-DADX4SRG.js} +13 -0
- package/dist/{chunk-GQFBQRDD.js → chunk-GERWAENA.js} +1 -1
- package/dist/{chunk-UVKHDHBU.js → chunk-M4PQW6PF.js} +51 -0
- package/dist/{chunk-NAQXQTMH.js → chunk-RTLKS5IS.js} +343 -29
- package/dist/{chunk-2MIHTOLI.js → chunk-RYQ5FO6C.js} +37 -6
- package/dist/{chunk-ZV4BSIQM.js → chunk-T3CVC4TV.js} +14 -1
- package/dist/{chunk-UU5WHX3D.js → chunk-WU5IORKP.js} +30 -0
- package/dist/{classify-V7GTELLP.js → classify-6YEOV3NX.js} +2 -2
- package/dist/cli.js +5164 -1372
- package/dist/{contextBuilder-A5NX7EQO.js → contextBuilder-BDRASC5V.js} +6 -5
- package/dist/{cycles-6KSI4OPZ.js → cycles-UYLIDTQJ.js} +3 -1
- package/dist/grammars/tree-sitter-bash.wasm +0 -0
- package/dist/grammars/tree-sitter-dart.wasm +0 -0
- package/dist/grammars/tree-sitter-kotlin.wasm +0 -0
- package/dist/{metrics-BLUHSM64.js → metrics-4PQMCQY6.js} +1 -1
- package/dist/{protected-37GK4NAJ.js → protected-PCNAIBWV.js} +1 -1
- package/dist/retrieve-JJ3EV63H.js +21 -0
- package/dist/{search-RFQREAFV.js → search-FYU5Q3LA.js} +2 -2
- package/package.json +1 -1
- package/dist/retrieve-GW6YA7BQ.js +0 -9
package/README.md
CHANGED
|
@@ -58,10 +58,11 @@ No MCP server, no embeddings, no network calls, no telemetry.
|
|
|
58
58
|
- **Multi-language** — TypeScript, JavaScript, Python, Java, Go, Rust,
|
|
59
59
|
PHP, C#, C/C++, Ruby, HTML, Vue SFC (plus config/schema files as
|
|
60
60
|
graph nodes).
|
|
61
|
-
- **Multi-agent setup** —
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
61
|
+
- **Multi-agent setup** — native rules & skills for Antigravity, Cursor,
|
|
62
|
+
Claude Code, Windsurf, GitHub Copilot, Kiro, OpenCode, and Qoder. ScopeCairn
|
|
63
|
+
merges safely into your project rules (Antigravity `AGENTS.md`, Copilot instructions)
|
|
64
|
+
via append-only managed blocks that keep your repo details 100% intact,
|
|
65
|
+
or writes native rules and skills.
|
|
65
66
|
- **`GRAPH.md` knowledge file** — `.scopecairn/GRAPH.md` is a budgeted
|
|
66
67
|
(±150 lines) frozen summary of the graph (modules, most-used symbols,
|
|
67
68
|
PageRank hotspots, circular dependencies, routes, models, protected).
|
|
@@ -76,7 +77,7 @@ No MCP server, no embeddings, no network calls, no telemetry.
|
|
|
76
77
|
click-for-details side panel (callers, callees, centrality scores).
|
|
77
78
|
Nodes sized by symbols/PageRank, colored by Louvain community.
|
|
78
79
|
Pan, zoom, hover inspect. Double-click to open, no server.
|
|
79
|
-
- **Decision log** — `scopecairn decide "
|
|
80
|
+
- **Decision log** — `scopecairn decide "use SQLite graph, no embeddings"` records an
|
|
80
81
|
architectural decision once; it is carried into every later
|
|
81
82
|
`context` call, so the agent stops re-litigating settled choices.
|
|
82
83
|
- **Definition of Done check** — `verify --dod` rates each DoD item and
|
|
@@ -129,38 +130,69 @@ it runs project setup itself.
|
|
|
129
130
|
|
|
130
131
|
```bash
|
|
131
132
|
cd your-repo
|
|
132
|
-
scopecairn init # index + Antigravity Skill + Workflow
|
|
133
|
+
scopecairn init # index + Antigravity Rule + AGENTS.md merge + Skill + Workflow
|
|
133
134
|
```
|
|
134
135
|
|
|
135
|
-
|
|
136
|
+
Per-agent installers (instead of `init --agents …`):
|
|
137
|
+
|
|
138
|
+
You can list supported agents anytime with `scopecairn agents list`. Install or remove integrations individually using native paths:
|
|
136
139
|
|
|
137
140
|
```bash
|
|
141
|
+
# Antigravity IDE (.agents/rules/ + AGENTS.md merge + skills/ + workflows/)
|
|
142
|
+
scopecairn antigravity install
|
|
143
|
+
|
|
144
|
+
# Claude Code (.claude/skills/ + rule mirroring in .claude/rules/)
|
|
138
145
|
scopecairn claude install
|
|
146
|
+
|
|
147
|
+
# Cursor (.cursor/rules/scopecairn.mdc with alwaysApply + knowledge skill)
|
|
148
|
+
scopecairn cursor install
|
|
149
|
+
|
|
150
|
+
# Windsurf (.windsurf/rules/scopecairn.md + knowledge skill)
|
|
151
|
+
scopecairn windsurf install
|
|
152
|
+
|
|
153
|
+
# GitHub Copilot (.github/copilot-instructions.md + knowledge skill)
|
|
154
|
+
scopecairn copilot install
|
|
155
|
+
|
|
156
|
+
# Kiro (.kiro/steering/scopecairn.md + knowledge skill)
|
|
157
|
+
scopecairn kiro install
|
|
158
|
+
|
|
159
|
+
# OpenCode (.opencode/commands/scopecairn.md + knowledge skill + permission snippet)
|
|
160
|
+
scopecairn opencode install
|
|
161
|
+
|
|
162
|
+
# Qoder (.qoder/rules/scopecairn.md + knowledge skill in .qoder/skills/)
|
|
163
|
+
scopecairn qoder install
|
|
164
|
+
|
|
165
|
+
# Batch installation during init
|
|
166
|
+
scopecairn init --agents all # install for all supported agents
|
|
167
|
+
scopecairn init --agents cursor,copilot # install for specific agents only
|
|
168
|
+
|
|
169
|
+
# Clean removal (preserves existing configs & custom instructions)
|
|
170
|
+
scopecairn <agent> uninstall
|
|
139
171
|
```
|
|
140
172
|
|
|
173
|
+
| Agent | Native Files Written / Merged | Execution Behavior |
|
|
174
|
+
| :--- | :--- | :--- |
|
|
175
|
+
| **Antigravity** | `.agents/rules/scopecairn.md`<br>`AGENTS.md` (smart merge)<br>`.agents/skills/scopecairn/SKILL.md`<br>`.agents/skills/scopecairn-knowledge/SKILL.md`<br>`.agents/workflows/scopecairn.md` | Auto-loaded into `<user_rules>` on every turn via `AGENTS.md` and rule files; skills available on demand. |
|
|
176
|
+
| **Cursor** | `.cursor/rules/scopecairn.mdc`<br>`.cursor/skills/scopecairn-knowledge/SKILL.md` | `alwaysApply: true` auto-injects protocol into every chat & agent session. |
|
|
177
|
+
| **Claude Code** | `.claude/skills/scopecairn/SKILL.md`<br>`.claude/rules/scopecairn.md`<br>`.claude/skills/scopecairn-knowledge/SKILL.md` | Standing rules loaded automatically; `allowed-tools: Bash` provides pre-approved terminal execution. |
|
|
178
|
+
| **Windsurf** | `.windsurf/rules/scopecairn.md`<br>`.windsurf/skills/scopecairn-knowledge/SKILL.md` | Cascade loads modern `.windsurf/rules/` automatically. |
|
|
179
|
+
| **GitHub Copilot** | `.github/copilot-instructions.md`<br>`.github/skills/scopecairn-knowledge/SKILL.md` | Safely merges managed block into repository custom instructions. |
|
|
180
|
+
| **Kiro** | `.kiro/steering/scopecairn.md`<br>`.kiro/skills/scopecairn-knowledge/SKILL.md` | Default `always` steering inclusion injects context into all prompts. |
|
|
181
|
+
| **OpenCode** | `.opencode/commands/scopecairn.md`<br>`.opencode/skills/scopecairn-knowledge/SKILL.md` | Registers `/scopecairn` slash command with `$ARGUMENTS` and prints permission guide. |
|
|
182
|
+
| **Qoder** | `.qoder/rules/scopecairn.md`<br>`.qoder/skills/scopecairn-knowledge/SKILL.md` | Rules without loading frontmatter are always active, so the protocol is injected into every Qoder IDE and CLI session; `AGENTS.md` is still honoured as a compatibility source. |
|
|
183
|
+
|
|
141
184
|
After setup, your agent calls ScopeCairn automatically on every coding task.
|
|
142
185
|
To query manually (replace `src/indexer.ts` or `buildContext` with files/symbols from your codebase):
|
|
143
186
|
|
|
144
187
|
```bash
|
|
145
188
|
scopecairn scan # index repository (--rebuild, --clean, --watch)
|
|
146
|
-
scopecairn context "
|
|
189
|
+
scopecairn context "add auth feature" # AI agent task context + scope
|
|
147
190
|
scopecairn impact src/indexer.ts # blast radius & affected tests (--run-tests)
|
|
148
191
|
scopecairn graph buildContext # inspect relations, path (--to), export (--export)
|
|
149
192
|
scopecairn verify --dod # pre-commit safety guard (--diff, --arch, --dod)
|
|
150
193
|
scopecairn status --usage # index statistics & health checks (--doctor, --html)
|
|
151
194
|
scopecairn read symbol indexFileSymbols # symbol-level source excerpt
|
|
152
|
-
scopecairn decide "
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
Per-agent installers (instead of `init --agents …`):
|
|
156
|
-
|
|
157
|
-
```bash
|
|
158
|
-
scopecairn antigravity install # Skill + Workflow + allowlist guide
|
|
159
|
-
scopecairn claude install # project skill .claude/skills (also: cursor, windsurf, copilot)
|
|
160
|
-
scopecairn kiro install # steering .kiro/steering + knowledge skill .kiro/skills/scopecairn-knowledge
|
|
161
|
-
scopecairn opencode install # /scopecairn slash command + permission snippet
|
|
162
|
-
scopecairn claude uninstall # clean removal, user content preserved
|
|
163
|
-
scopecairn agents list
|
|
195
|
+
scopecairn decide "graph in SQLite, no embeddings" # record architectural decision
|
|
164
196
|
```
|
|
165
197
|
|
|
166
198
|
## 6 Core Commands
|
|
@@ -173,18 +205,32 @@ ScopeCairn organizes its capabilities into **6 core commands** aligned with code
|
|
|
173
205
|
| `context "<task>"` | **AI Agent Context & Scope:** Main agent entry point with auto-refresh, task classification, relevant context, task scope, and Task Guard. Options: `--escalate`, `--mode NORMAL\|FAST\|SAFE\|AUDIT`, `--max-tokens N`, `--no-history`, `--with-diff`, `--json`. |
|
|
174
206
|
| `impact <target>` | **Blast Radius & Test Selection:** Change impact analysis across direct, indirect, tests, UI components, and API clients. Options: `--run-tests` (run affected tests directly), `--tests` (list affected tests only), `--repos [list]` (cross-repo blast radius). |
|
|
175
207
|
| `graph [symbol]` | **Knowledge Graph & Navigation:** Symbol relations, PageRank centrality, community clusters, and cycle status. Options: `--to <target>` (shortest multi-hop call path), `--export <fmt>` (`mermaid\|graphml\|dot\|json`). |
|
|
176
|
-
| `verify` | **Pre-Commit Safety
|
|
208
|
+
| `verify` | **Pre-Commit Safety & Testing Gate:** Working tree check against protected files, task scope creep, circular dependency regressions, and test gate. Options: `--diff` (structural symbol-level diff vs HEAD), `--arch` (architectural layer rules), `--dod` (Definition of Done: verified / warned / manual, never a claimed pass), `--test-gate` (detect weakened tests, enforce changed-line coverage default 80% for Vitest/Jest, optional `--mutation` via Stryker), `--strict`, `--task <t>`. |
|
|
177
209
|
| `status` | **Health & Diagnostics:** Index statistics and metadata. Options: `--doctor` (integration and adapter health check), `--usage` (local usage & compliance report), `--verbose`, `--html` (generate local HTML dashboard). |
|
|
178
210
|
|
|
179
211
|
### Helper & Setup Commands
|
|
180
212
|
|
|
181
213
|
| Command | Description |
|
|
182
214
|
| ------- | ----------- |
|
|
183
|
-
| `init [path]` | Initial indexing + Skill, Workflow, detected agent skills (`--agents …\|all`). |
|
|
215
|
+
| `init [path]` | Initial indexing + Skill, Workflow, detected agent skills (`--agents …\|all`, `--hooks` to install `.git/hooks/pre-commit` test & verification gate). |
|
|
216
|
+
| `reuse "<query>"` | Find existing similar functions or classes from the graph (name tokens, signature, doc comment, call-graph neighbors) before creating new ones. |
|
|
217
|
+
| `dead` | Report symbols and files with no incoming edges (`high`/`medium`/`low` confidence, excluding entrypoints, tests, framework routes, and Protected paths). |
|
|
218
|
+
| `session <save\|load>` | Atomically save or load working session state in `.scopecairn/session.md` (max 4,000 chars, rejects secrets, reports commits & files changed since last save). |
|
|
184
219
|
| `read symbol <name>` | Symbol-level source excerpt (not whole files). |
|
|
185
220
|
| `decide "…"` | Record an architectural decision into `.scopecairn/decisions.md` (no argument: list them). Read back by `context`. |
|
|
186
221
|
| `benchmark [--tune]` | Retrieval recall, irrelevant ratio, context reduction + weight tuning (dev-only, hidden from `--help`). |
|
|
187
|
-
| `<agent> install` | Agent installers: `antigravity`, `claude`, `cursor`, `windsurf`, `copilot`, `kiro`, `opencode`. |
|
|
222
|
+
| `<agent> install` | Agent installers: `antigravity`, `claude`, `cursor`, `windsurf`, `copilot`, `kiro`, `opencode`, `qoder`. |
|
|
223
|
+
|
|
224
|
+
### Testing Gate & Pre-Commit Hook Limitations (`init --hooks`)
|
|
225
|
+
|
|
226
|
+
Running `scopecairn init --hooks` installs a managed `.git/hooks/pre-commit` guard that executes `scopecairn verify --no-strict --test-gate`:
|
|
227
|
+
- **Weakened Test Detection:** Blocks diffs in test files that delete assertions (`expect`/`assert`), add `.skip`/`.todo`/`xit`, or loosen strict matchers (`toBe`/`toEqual` → `toBeDefined`/`toBeTruthy` or tautological `expect(true).toBe(true)`).
|
|
228
|
+
- **Changed-Line Coverage (Vitest & Jest):** Evaluates coverage on modified/added executable source lines against a configurable threshold (`coverage_threshold: 80` in `.scopecairn/config.yml`, or `--coverage-threshold <n>`).
|
|
229
|
+
- **Optional Mutation Testing (Stryker):** Supports `--mutation` (or `mutation_enabled: true` in `.scopecairn/config.yml`) to validate mutation score on changed files via Stryker (`--testRunner vitest|jest`). Disabled by default because mutation testing is slow.
|
|
230
|
+
- **Limitation (`--no-verify` bypass):** Because an AI agent or developer can bypass local git hooks using `git commit --no-verify`, always run the same check in your CI pipeline:
|
|
231
|
+
```bash
|
|
232
|
+
scopecairn verify --test-gate --base origin/main
|
|
233
|
+
```
|
|
188
234
|
|
|
189
235
|
> [!NOTE]
|
|
190
236
|
> **Backward Compatibility:** All previous standalone commands (`rebuild`, `clean`, `watch`, `doctor`, `dashboard`, `test-select`, `path`, `export`, `diff`, `arch-check`, `history`) are preserved as hidden aliases mapped directly into these 6 core commands — they still work but no longer appear in `--help`. `update` is deprecated (use `npm install -g scopecairn@latest`).
|
|
@@ -228,15 +274,60 @@ prompt → scopecairn context (refresh at start) → agent edits code
|
|
|
228
274
|
|
|
229
275
|
Measure retrieval quality anytime: `scopecairn benchmark [--tune]`.
|
|
230
276
|
|
|
277
|
+
## Ranking 5+2 sinyal
|
|
278
|
+
|
|
279
|
+
Retrieval (`src/retrieval/retrieve.ts`) menggabungkan 5 sinyal klasik + 2 sinyal graph:
|
|
280
|
+
|
|
281
|
+
| Sinyal | Bobot default (Fase 6) | Makna |
|
|
282
|
+
| ------ | ---------------------- | ----- |
|
|
283
|
+
| `wSeed` (seed) | 0.5 | Token-match FTS5 terhadap task + glossary. |
|
|
284
|
+
| `wProximity` | 0.25 | Dekat di graph dari seed (BFS `maxDepth`, `decayPerHop`). |
|
|
285
|
+
| `wCentrality` | 0.1 | `0.5*PageRank + 0.5*fan-in` (cache `node_metrics`/`fanin`). |
|
|
286
|
+
| `wRecency` | 0.1 | File sering berubah baru-baru ini (sinyal git). |
|
|
287
|
+
| `wCochange` | 0.1 | File yang sering berubah bersama file seed (riwayat). |
|
|
288
|
+
| `wPpr` | 0.0 | Personalized PageRank proxy (opsional, 0 = nonaktif). |
|
|
289
|
+
| `wPr` | 0.0 | PageRank global mandiri (opsional, 0 = nonaktif). |
|
|
290
|
+
|
|
291
|
+
+ `verified` (closed-loop): file yang terbukti berubah saat `verify` lulus mendapat bonus `+0.35` proporsional.
|
|
292
|
+
|
|
293
|
+
Bobot dapat dioverride di `.scopecairn/config.yml` (`w_seed:`, `w_proximity:`, …) atau via `retrieve(db, root, task, { weights: { ... } })` saat benchmark/tuning.
|
|
294
|
+
|
|
295
|
+
Contoh pakai:
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
# Breakdown skor per simbol (wSeed*seed, prox, cent, recency, cochange, verified)
|
|
299
|
+
scopecairn context "add login" --explain
|
|
300
|
+
|
|
301
|
+
# Format output ringkas untuk agent hemat token
|
|
302
|
+
scopecairn context "add login" --format brief # 1 baris/simbol
|
|
303
|
+
scopecairn context "add login" --format table # markdown table
|
|
304
|
+
scopecairn context "add login" --format tree # grup per folder
|
|
305
|
+
scopecairn context "add login" --format mermaid # graph TD antar simbol teratas
|
|
306
|
+
|
|
307
|
+
# PR risk agregat (protected/cycle/fan-in) vs base, + opsi SARIF untuk GitHub
|
|
308
|
+
scopecairn verify --pr-risk --base origin/main
|
|
309
|
+
scopecairn verify --pr-risk --sarif # SARIF 2.1.0 JSON
|
|
310
|
+
|
|
311
|
+
# Dependensi manifest dan kandidat unused (npm/python/go/rust)
|
|
312
|
+
scopecairn deps
|
|
313
|
+
scopecairn deps --json
|
|
314
|
+
|
|
315
|
+
# Deteksi LSP opsional saat scan (best-effort, tak wajib)
|
|
316
|
+
scopecairn scan --lsp # melaporkan tsserver/gopls/pyright di PATH
|
|
317
|
+
|
|
318
|
+
# Kalibrasi ulang bobot (grid search 36 kombinasi wSeed/wProximity/wCentrality)
|
|
319
|
+
scopecairn benchmark --tune
|
|
320
|
+
|
|
321
|
+
# CI gate (tulis .github/workflows/scopecairn.yml)
|
|
322
|
+
scopecairn init --ci
|
|
323
|
+
```
|
|
324
|
+
|
|
231
325
|
## History privacy
|
|
232
326
|
|
|
233
|
-
`scan`
|
|
234
|
-
`.scopecairn/scopecairn.db` (
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
Pesan commit bisa memuat info sensitif; karena tidak keluar dari mesin, risikonya
|
|
238
|
-
kecil. Repo tanpa git atau clone dangkal tetap didukung (riwayat parsial/absen,
|
|
239
|
-
dilaporkan `status --doctor`).
|
|
327
|
+
`scan` indexes local git commit messages into the `commits`/`commit_files` tables in
|
|
328
|
+
`.scopecairn/scopecairn.db` (skipping commits with >40 files, lockfiles, and generated files).
|
|
329
|
+
Commit contents are **never** included in `GRAPH.md`, `GRAPH.html`, or `graph --export` (which are often committed and shared) — history lives exclusively in your local database and in `context` output.
|
|
330
|
+
Commit messages may contain sensitive project information; because they never leave your local machine, risk is minimized. Repositories without git or shallow clones are fully supported (partial or absent history is reported by `status --doctor`).
|
|
240
331
|
|
|
241
332
|
## License
|
|
242
333
|
|
|
@@ -3,11 +3,13 @@ import {
|
|
|
3
3
|
PROJECT_RULES_MAX_CHARS,
|
|
4
4
|
demoteHeadings,
|
|
5
5
|
findAgentsMd,
|
|
6
|
-
loadProjectRules
|
|
7
|
-
|
|
6
|
+
loadProjectRules,
|
|
7
|
+
stripManagedBlock
|
|
8
|
+
} from "./chunk-T3CVC4TV.js";
|
|
8
9
|
export {
|
|
9
10
|
PROJECT_RULES_MAX_CHARS,
|
|
10
11
|
demoteHeadings,
|
|
11
12
|
findAgentsMd,
|
|
12
|
-
loadProjectRules
|
|
13
|
+
loadProjectRules,
|
|
14
|
+
stripManagedBlock
|
|
13
15
|
};
|
|
@@ -86,8 +86,8 @@ function computeBetweenness(db) {
|
|
|
86
86
|
sigma.set(s, 1);
|
|
87
87
|
dist.set(s, 0);
|
|
88
88
|
const queue = [s];
|
|
89
|
-
|
|
90
|
-
const v = queue
|
|
89
|
+
for (let qi = 0; qi < queue.length; qi++) {
|
|
90
|
+
const v = queue[qi];
|
|
91
91
|
stack.push(v);
|
|
92
92
|
for (const w of adj.get(v) ?? []) {
|
|
93
93
|
if ((dist.get(w) ?? -1) < 0) {
|
|
@@ -1,15 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
coChangeSet,
|
|
4
|
+
groupHistoryFiles
|
|
5
|
+
} from "./chunk-GERWAENA.js";
|
|
6
|
+
import {
|
|
7
|
+
logError
|
|
8
|
+
} from "./chunk-OPA4TMAT.js";
|
|
2
9
|
import {
|
|
3
10
|
demoteHeadings
|
|
4
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-T3CVC4TV.js";
|
|
12
|
+
import {
|
|
13
|
+
isTestPath,
|
|
14
|
+
selectHigh
|
|
15
|
+
} from "./chunk-RTLKS5IS.js";
|
|
5
16
|
import {
|
|
6
17
|
isProtected,
|
|
7
18
|
loadProtectedPatterns
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
19
|
+
} from "./chunk-DADX4SRG.js";
|
|
20
|
+
|
|
21
|
+
// src/retrieval/contextBuilder.ts
|
|
22
|
+
import fs from "fs";
|
|
23
|
+
import path from "path";
|
|
13
24
|
|
|
14
25
|
// src/scope/scope.ts
|
|
15
26
|
function computeScope(highFiles, mediumFiles, testFiles, patterns, historyOptional) {
|
|
@@ -110,8 +121,9 @@ Task sederhana. Gunakan pencarian langsung. Kandidat: ${cand}
|
|
|
110
121
|
};
|
|
111
122
|
}
|
|
112
123
|
const ranked = result.ranked;
|
|
113
|
-
const hi = ranked
|
|
114
|
-
const
|
|
124
|
+
const hi = selectHigh(ranked);
|
|
125
|
+
const hiSet = new Set(hi);
|
|
126
|
+
const med = ranked.filter((r) => r.score >= 0.15 && !hiSet.has(r)).slice(0, 10);
|
|
115
127
|
const highFiles = [...new Set(hi.map((r) => r.file))];
|
|
116
128
|
const medFiles = [...new Set(med.map((r) => r.file))].filter((f) => !highFiles.includes(f));
|
|
117
129
|
const traceSeed = highFiles.length > 0 ? highFiles : opts?.knowledge ? medFiles : [];
|
|
@@ -131,10 +143,11 @@ Task sederhana. Gunakan pencarian langsung. Kandidat: ${cand}
|
|
|
131
143
|
LIMIT 10`
|
|
132
144
|
).all(...traceSeed, ...traceSeed);
|
|
133
145
|
for (const r of rows) deps.push(`${r.a} \u2192 ${r.b}`);
|
|
134
|
-
} catch {
|
|
146
|
+
} catch (err) {
|
|
147
|
+
logError("contextBuilder:deps", err, opts?.repoRoot);
|
|
135
148
|
}
|
|
136
149
|
}
|
|
137
|
-
const isTestFileLike =
|
|
150
|
+
const isTestFileLike = isTestPath;
|
|
138
151
|
const testSet = /* @__PURE__ */ new Set();
|
|
139
152
|
if (highFiles.length > 0) {
|
|
140
153
|
const ph = highFiles.map(() => "?").join(",");
|
|
@@ -149,7 +162,8 @@ Task sederhana. Gunakan pencarian langsung. Kandidat: ${cand}
|
|
|
149
162
|
WHERE r.relationship_type = 'TESTS' AND f2.path IN (${ph})`
|
|
150
163
|
).all(...highFiles);
|
|
151
164
|
for (const r of rows) testSet.add(r.t);
|
|
152
|
-
} catch {
|
|
165
|
+
} catch (err) {
|
|
166
|
+
logError("contextBuilder:tests", err, opts?.repoRoot);
|
|
153
167
|
}
|
|
154
168
|
try {
|
|
155
169
|
const symRows = db.prepare(
|
|
@@ -190,14 +204,45 @@ Task sederhana. Gunakan pencarian langsung. Kandidat: ${cand}
|
|
|
190
204
|
}
|
|
191
205
|
}
|
|
192
206
|
}
|
|
193
|
-
} catch {
|
|
207
|
+
} catch (err) {
|
|
208
|
+
logError("contextBuilder:callers", err, opts?.repoRoot);
|
|
194
209
|
}
|
|
195
210
|
}
|
|
196
211
|
for (const r of ranked) {
|
|
197
212
|
if (isTestFileLike(r.file)) testSet.add(r.file);
|
|
198
213
|
}
|
|
199
214
|
const tests = [...testSet].slice(0, 10);
|
|
200
|
-
const
|
|
215
|
+
const callerStmt = (() => {
|
|
216
|
+
try {
|
|
217
|
+
return db.prepare(
|
|
218
|
+
`SELECT DISTINCT s.name AS name, f.path AS file, s.start_line AS startLine
|
|
219
|
+
FROM relationships r
|
|
220
|
+
JOIN symbols s ON s.id = r.source_id
|
|
221
|
+
JOIN files f ON f.id = s.file_id
|
|
222
|
+
WHERE r.target_id = ? AND r.relationship_type IN ('CALLS','IMPORTS') AND s.type != 'file' AND s.id != ?
|
|
223
|
+
LIMIT 3`
|
|
224
|
+
);
|
|
225
|
+
} catch {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
})();
|
|
229
|
+
const symLine = (r) => {
|
|
230
|
+
const loc = r.startLine && r.startLine > 0 ? `${r.file}:${r.startLine}-${r.endLine ?? r.startLine}` : r.file;
|
|
231
|
+
const sig = r.signature ? ` \`${r.signature.replace(/\s+/g, " ").trim().slice(0, 90)}\`` : "";
|
|
232
|
+
let callersLine = "";
|
|
233
|
+
if (callerStmt) {
|
|
234
|
+
try {
|
|
235
|
+
const cRows = callerStmt.all(r.id, r.id);
|
|
236
|
+
if (cRows.length > 0) {
|
|
237
|
+
const formatted = cRows.map((c) => `${c.name} (\`${c.file}${c.startLine && c.startLine > 0 ? `:${c.startLine}` : ""}\`)`).join(", ");
|
|
238
|
+
callersLine = `
|
|
239
|
+
\u21B3 called-by: ${formatted}`;
|
|
240
|
+
}
|
|
241
|
+
} catch {
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return `- ${r.name} (${r.type}) \u2014 \`${loc}\`${sig} [${r.score.toFixed(2)} ${r.reason}]${callersLine}`;
|
|
245
|
+
};
|
|
201
246
|
const hotspotLine = (h) => `- ${h.name} (${h.type}) \u2014 \`${h.file}\` [pr=${h.pagerank.toFixed(4)}]`;
|
|
202
247
|
let hotspots = [];
|
|
203
248
|
let cycleWarn = "";
|
|
@@ -230,13 +275,29 @@ Task sederhana. Gunakan pencarian langsung. Kandidat: ${cand}
|
|
|
230
275
|
`;
|
|
231
276
|
}
|
|
232
277
|
}
|
|
233
|
-
} catch {
|
|
278
|
+
} catch (err) {
|
|
279
|
+
logError("contextBuilder:metrics", err, opts?.repoRoot);
|
|
280
|
+
}
|
|
281
|
+
const verifiedAnalogs = result.verifiedAnalogs ?? [];
|
|
282
|
+
const verifiedFiles = [...new Set(verifiedAnalogs.flatMap((v) => v.files))];
|
|
283
|
+
for (const vf of verifiedFiles) {
|
|
284
|
+
if (!highFiles.includes(vf)) highFiles.push(vf);
|
|
234
285
|
}
|
|
235
286
|
const historyAnalogs = opts?.history?.analogs ?? [];
|
|
236
287
|
const cochange = coChangeSet(historyAnalogs);
|
|
237
288
|
const patterns = loadProtectedPatterns(opts?.repoRoot ?? ".");
|
|
238
289
|
const historyOptional = cochange.map((c) => c.path).filter((f) => !isProtected(f, patterns));
|
|
239
290
|
const scope = computeScope(highFiles, medFiles, tests, patterns, historyOptional);
|
|
291
|
+
let verifiedBlock = "";
|
|
292
|
+
if (verifiedAnalogs.length > 0) {
|
|
293
|
+
const lines = [`## Analogi task terverifikasi`, ``];
|
|
294
|
+
for (const v of verifiedAnalogs) {
|
|
295
|
+
lines.push(`- "${v.description}" (${v.verifiedAt.slice(0, 10)})`);
|
|
296
|
+
lines.push(` file: ${v.files.map((f) => `\`${f}\``).join(", ")}`);
|
|
297
|
+
}
|
|
298
|
+
lines.push(``);
|
|
299
|
+
verifiedBlock = lines.join("\n") + "\n";
|
|
300
|
+
}
|
|
240
301
|
let historyBlock = "";
|
|
241
302
|
if (historyAnalogs.length > 0) {
|
|
242
303
|
const lines = [`## Analogi riwayat`, ``];
|
|
@@ -305,7 +366,7 @@ ${kSyms.slice(0, 8).map(symLine).join("\n") || "-"}
|
|
|
305
366
|
## Related Tests
|
|
306
367
|
${tests.map((t) => `- ${t}`).join("\n") || "-"}
|
|
307
368
|
|
|
308
|
-
` + historyBlock + cycleWarn;
|
|
369
|
+
` + verifiedBlock + historyBlock + cycleWarn;
|
|
309
370
|
return {
|
|
310
371
|
markdown: mdKnowledge,
|
|
311
372
|
high: highFiles,
|
|
@@ -316,6 +377,39 @@ ${tests.map((t) => `- ${t}`).join("\n") || "-"}
|
|
|
316
377
|
scope
|
|
317
378
|
};
|
|
318
379
|
}
|
|
380
|
+
let excerptsBlock = "";
|
|
381
|
+
if (opts?.withBodies !== false && opts?.repoRoot) {
|
|
382
|
+
const excerptLines = [];
|
|
383
|
+
const maxSpan = opts?.withBodies === true ? 40 : 25;
|
|
384
|
+
const candidates = hi.filter(
|
|
385
|
+
(r) => r.type !== "file" && (opts?.withBodies === true || r.type !== "variable") && r.startLine && r.startLine > 0
|
|
386
|
+
).slice(0, 2);
|
|
387
|
+
for (const r of candidates) {
|
|
388
|
+
const start = r.startLine;
|
|
389
|
+
const rawEnd = r.endLine && r.endLine >= start ? r.endLine : start;
|
|
390
|
+
const span = rawEnd - start + 1;
|
|
391
|
+
if (opts?.withBodies !== true && (span <= 1 || span > maxSpan)) continue;
|
|
392
|
+
try {
|
|
393
|
+
const abs = path.join(opts.repoRoot, r.file);
|
|
394
|
+
const fileLines = fs.readFileSync(abs, "utf8").replace(/\r\n/g, "\n").split("\n");
|
|
395
|
+
const end = Math.min(fileLines.length, opts?.withBodies === true && rawEnd === start ? start + 24 : Math.min(rawEnd, start + maxSpan - 1));
|
|
396
|
+
const body = fileLines.slice(start - 1, end).join("\n").trimEnd();
|
|
397
|
+
if (body) {
|
|
398
|
+
excerptLines.push(`### ${r.name} (\`${r.file}:${start}-${end}\`)
|
|
399
|
+
\`\`\`
|
|
400
|
+
${body}
|
|
401
|
+
\`\`\``);
|
|
402
|
+
}
|
|
403
|
+
} catch {
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
if (excerptLines.length > 0) {
|
|
407
|
+
excerptsBlock = `## Key Excerpts
|
|
408
|
+
${excerptLines.join("\n\n")}
|
|
409
|
+
|
|
410
|
+
`;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
319
413
|
const md = `# Task Context
|
|
320
414
|
|
|
321
415
|
## Task
|
|
@@ -333,7 +427,7 @@ ${medFiles.map((f) => `- ${f}`).join("\n") || "-"}
|
|
|
333
427
|
## Key Symbols
|
|
334
428
|
${hi.slice(0, 8).map(symLine).join("\n") || "-"}
|
|
335
429
|
|
|
336
|
-
` + (hotspots.length > 0 ? `## Critical hotspots (PageRank)
|
|
430
|
+
` + excerptsBlock + (hotspots.length > 0 ? `## Critical hotspots (PageRank)
|
|
337
431
|
${hotspots.map(hotspotLine).join("\n")}
|
|
338
432
|
|
|
339
433
|
` : ``) + `## Dependencies
|
|
@@ -342,8 +436,8 @@ ${deps.map((d) => `- ${d}`).join("\n") || "-"}
|
|
|
342
436
|
## Related Tests
|
|
343
437
|
${tests.map((t) => `- ${t}`).join("\n") || "-"}
|
|
344
438
|
|
|
345
|
-
` + historyBlock + cycleWarn + scopeBlock(scope) + `
|
|
346
|
-
` + guardBlock(extraDoD.length > 0 ? extraDoD : void 0);
|
|
439
|
+
` + verifiedBlock + historyBlock + cycleWarn + scopeBlock(scope) + (opts?.withGuard ? `
|
|
440
|
+
` + guardBlock(extraDoD.length > 0 ? extraDoD : void 0) : ``);
|
|
347
441
|
return {
|
|
348
442
|
markdown: md,
|
|
349
443
|
high: highFiles,
|
|
@@ -55,6 +55,7 @@ function openDb(repoRoot) {
|
|
|
55
55
|
CREATE INDEX IF NOT EXISTS idx_symbols_file ON symbols(file_id);
|
|
56
56
|
CREATE INDEX IF NOT EXISTS idx_symbols_name ON symbols(name);
|
|
57
57
|
CREATE INDEX IF NOT EXISTS idx_symbols_type ON symbols(type);
|
|
58
|
+
CREATE INDEX IF NOT EXISTS idx_symbols_file_name_type ON symbols(file_id, name, type);
|
|
58
59
|
CREATE TABLE IF NOT EXISTS relationships (
|
|
59
60
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
60
61
|
source_id INTEGER NOT NULL REFERENCES symbols(id) ON DELETE CASCADE,
|
|
@@ -66,6 +67,7 @@ function openDb(repoRoot) {
|
|
|
66
67
|
CREATE INDEX IF NOT EXISTS idx_rel_source ON relationships(source_id);
|
|
67
68
|
CREATE INDEX IF NOT EXISTS idx_rel_target ON relationships(target_id);
|
|
68
69
|
CREATE INDEX IF NOT EXISTS idx_rel_type ON relationships(relationship_type);
|
|
70
|
+
CREATE INDEX IF NOT EXISTS idx_rel_source_type ON relationships(source_id, relationship_type);
|
|
69
71
|
CREATE TABLE IF NOT EXISTS meta (
|
|
70
72
|
key TEXT PRIMARY KEY,
|
|
71
73
|
value TEXT NOT NULL
|
|
@@ -164,6 +166,17 @@ function openDb(repoRoot) {
|
|
|
164
166
|
CREATE VIRTUAL TABLE IF NOT EXISTS commit_fts USING fts5(
|
|
165
167
|
sha UNINDEXED, subject, body, paths
|
|
166
168
|
);
|
|
169
|
+
-- Closed-loop learning: pasangan task -> file yang terbukti berubah saat verify lulus
|
|
170
|
+
CREATE TABLE IF NOT EXISTS verified_tasks (
|
|
171
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
172
|
+
task_id INTEGER REFERENCES tasks(id) ON DELETE SET NULL,
|
|
173
|
+
description TEXT NOT NULL UNIQUE,
|
|
174
|
+
files_json TEXT NOT NULL,
|
|
175
|
+
verified_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
176
|
+
);
|
|
177
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS verified_tasks_fts USING fts5(
|
|
178
|
+
task_id UNINDEXED, description, files
|
|
179
|
+
);
|
|
167
180
|
`);
|
|
168
181
|
db.prepare(
|
|
169
182
|
`INSERT INTO meta(key, value) VALUES ('schema_version', ?)
|
|
@@ -34,6 +34,10 @@ function stemVariants(t) {
|
|
|
34
34
|
const base = t.slice(0, -5);
|
|
35
35
|
out.add(base);
|
|
36
36
|
}
|
|
37
|
+
if ((t.endsWith("er") || t.endsWith("ed")) && t.length > 5) {
|
|
38
|
+
out.add(t.slice(0, -1));
|
|
39
|
+
out.add(t.slice(0, -2));
|
|
40
|
+
}
|
|
37
41
|
return [...out];
|
|
38
42
|
}
|
|
39
43
|
var EXPLICIT_STEM = /* @__PURE__ */ new Map([
|
|
@@ -48,6 +52,51 @@ var EXPLICIT_STEM = /* @__PURE__ */ new Map([
|
|
|
48
52
|
["extraction", "extract"],
|
|
49
53
|
["extracted", "extract"]
|
|
50
54
|
]);
|
|
55
|
+
var DOMAIN_SYNONYMS = /* @__PURE__ */ new Map([
|
|
56
|
+
// Kupon / diskon
|
|
57
|
+
["kupon", "voucher"],
|
|
58
|
+
["voucher", "kupon"],
|
|
59
|
+
["coupon", "voucher"],
|
|
60
|
+
["promo", "voucher"],
|
|
61
|
+
["diskon", "discount"],
|
|
62
|
+
["discount", "diskon"],
|
|
63
|
+
// Penagihan
|
|
64
|
+
["invoice", "billing"],
|
|
65
|
+
["billing", "invoice"],
|
|
66
|
+
["tagihan", "invoice"],
|
|
67
|
+
["pembayaran", "payment"],
|
|
68
|
+
["payment", "pembayaran"],
|
|
69
|
+
["pelunasan", "payment"],
|
|
70
|
+
// Pengguna / akun
|
|
71
|
+
["pengguna", "user"],
|
|
72
|
+
["akun", "account"],
|
|
73
|
+
["account", "akun"],
|
|
74
|
+
// Keranjang / pesanan
|
|
75
|
+
["keranjang", "cart"],
|
|
76
|
+
["cart", "keranjang"],
|
|
77
|
+
["troli", "cart"],
|
|
78
|
+
["pesanan", "order"],
|
|
79
|
+
["order", "pesanan"],
|
|
80
|
+
["checkout", "order"],
|
|
81
|
+
// Pengiriman / stok
|
|
82
|
+
["pengiriman", "shipping"],
|
|
83
|
+
["shipping", "pengiriman"],
|
|
84
|
+
["ongkir", "shipping"],
|
|
85
|
+
["stok", "stock"],
|
|
86
|
+
["stock", "stok"],
|
|
87
|
+
["inventaris", "stock"],
|
|
88
|
+
// Produk / harga
|
|
89
|
+
["harga", "price"],
|
|
90
|
+
["price", "harga"],
|
|
91
|
+
["produk", "product"],
|
|
92
|
+
["product", "produk"],
|
|
93
|
+
// Auth umum
|
|
94
|
+
["masuk", "login"],
|
|
95
|
+
["login", "masuk"],
|
|
96
|
+
["daftar", "signup"],
|
|
97
|
+
["logout", "signout"],
|
|
98
|
+
["signout", "logout"]
|
|
99
|
+
]);
|
|
51
100
|
function tokenizeIdentifier(raw) {
|
|
52
101
|
const parts = raw.replace(/[\\/]/g, " ").replace(/[.:\-_"']/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").toLowerCase().split(/[^a-z0-9]+/g).filter((t) => t.length >= 2 && !STOP.has(t));
|
|
53
102
|
const out = /* @__PURE__ */ new Set();
|
|
@@ -58,6 +107,8 @@ function tokenizeIdentifier(raw) {
|
|
|
58
107
|
}
|
|
59
108
|
const e = EXPLICIT_STEM.get(t);
|
|
60
109
|
if (e && !STOP.has(e)) out.add(e);
|
|
110
|
+
const d = DOMAIN_SYNONYMS.get(t);
|
|
111
|
+
if (d && !STOP.has(d)) out.add(d);
|
|
61
112
|
}
|
|
62
113
|
return [...out];
|
|
63
114
|
}
|