@intentic/iq 1.309.0 → 1.311.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/README.md +27 -142
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -1,153 +1,38 @@
|
|
|
1
1
|
# iq
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
The `iq` CLI, agent-native workspace search that answers a symbol, path, regex or plain question with ranked `path:line` anchors in one call.
|
|
4
|
+
|
|
5
|
+
```mermaid
|
|
6
|
+
flowchart LR
|
|
7
|
+
plugin["plugin/<br/>skill + hooks"] -. "teaches" .-> agent["Agent or shell"]
|
|
8
|
+
agent -- "iq '…' · def · refs · outline" --> iq(["iq"])
|
|
9
|
+
iq -- "createEngine" --> engine["iq-engine<br/>index + ranking"]
|
|
10
|
+
iq -- "sessions" --> recall["iq-recall<br/>past transcripts"]
|
|
11
|
+
iq -- "capsule + code<br/>within --budget" --> agent
|
|
9
12
|
```
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
**Requirements**
|
|
18
|
-
|
|
19
|
-
- **Node ≥ 24** (uses `node:sqlite`).
|
|
20
|
-
- **ripgrep** on `PATH`: the lexical engine shells into `rg`. Install via your package manager (`apt install ripgrep`, `brew install ripgrep`, …) or point `IQ_RG_PATH` at a binary.
|
|
21
|
-
- **Semantic search is optional.** Natural-language queries use a local embedding + cross-encoder model. Set `IQ_MODEL_DIR` to a directory holding them (fetch once with the bundled `node_modules/@intentic/iq-engine/scripts/fetch-model.mjs <dir>`). Without it, they degrade to keyword-expanded lexical search: everything else works unchanged.
|
|
22
|
-
|
|
23
|
-
The index self-manages: it builds on first query and revalidates against disk on every run. Nothing to set up.
|
|
24
|
-
|
|
25
|
-
## Quickstart: pick the verb by what you already know
|
|
26
|
-
|
|
27
|
-
- **You know which symbol you want to read** → `iq read X`, or `iq read path::X` / `iq read path::Class::method` when the name is not unique. One call returns the body, where `def` then `context path:line` took two and needed a line number in between. Open the file itself when you want the whole file: a `Read` with no offset hands back up to 2000 lines whether you wanted them or not.
|
|
28
|
-
- **You know the file** (a stack trace or failing test names it) → `iq outline <path>` for its shape, then `iq read <path>::<symbol>`; `iq context path:line` / `iq who path:line` when a line is all you have.
|
|
29
|
-
- **You know the exact identifier** → `iq def X` / `iq refs X`: one call replaces a grep-then-filter chain.
|
|
30
|
-
- **You don't know where it lives**, or your words may not match the code's vocabulary → bare `iq "…"`, phrased as a question. Natural language has no verb of its own. This is where `iq` decisively beats grep.
|
|
31
|
-
- **You have error-message text** → `iq find 'literal text'`, then `iq context` on the hit.
|
|
32
|
-
- **You know nothing yet**: a repo you've never opened → `iq map --budget 4000` for the shape, `iq hotspots` for where the risk sits. These answer "what is this and where do I start", which no amount of searching does.
|
|
33
|
-
|
|
34
|
-
| I want… | Run |
|
|
35
|
-
|---|---|
|
|
36
|
-
| the repo's shape | `iq map --budget 4000` |
|
|
37
|
-
| where risk concentrates | `iq hotspots --in _apps` |
|
|
38
|
-
| what my change reaches | `iq impact` |
|
|
39
|
-
| text/regex match | `iq find 'createServer\(' --lang ts` |
|
|
40
|
-
| a file by name | `iq files wkignore` (`--exact` for globs) |
|
|
41
|
-
| where X is defined | `iq def createIgnoreScope` |
|
|
42
|
-
| who uses X | `iq refs createIgnoreScope --kind call` |
|
|
43
|
-
| symbols by pattern | `iq sym 'Workspace*Schema' --kind type` |
|
|
44
|
-
| structural AST pattern | `iq ast 'await $FN($$$)' --lang ts` |
|
|
45
|
-
| a natural-language answer | `iq "how does the daemon expose tools?"` |
|
|
46
|
-
| a file's skeleton | `iq outline src/workspace/workspace-ignore.ts` |
|
|
47
|
-
| a symbol's body | `iq read createIgnoreScope`, `iq read src/app.ts::Server::start` |
|
|
48
|
-
| code around a hit | `iq context src/workspace/workspace-tree.ts:48` |
|
|
49
|
-
| recent changes | `iq recent --since 2d` |
|
|
50
|
-
| history of a string | `iq log "MAX_TOTAL_MATCHES" --path src/workspace` |
|
|
51
|
-
| blame a line | `iq who src/file.ts:15` |
|
|
52
|
-
| several queries, one spawn | `iq multi "how is auth refreshed" "def refreshToken"` |
|
|
53
|
-
|
|
54
|
-
Regex is **rust syntax**: alternation is `a|b` (not `a\|b`); for literal text use `--literal`.
|
|
55
|
-
|
|
56
|
-
## Output contract
|
|
57
|
-
|
|
58
|
-
- **Answer first.** Every response opens with a capsule that precedes the code: `answer:` names the top anchor, its enclosing symbol and whether the top result is `confident` or `ambiguous`; `candidates:` names the ranked `path:line` anchors that did not fit; `more:` gives the exact `--after <cursor>` command. A reader that keeps only the first few lines keeps everything actionable.
|
|
59
|
-
- **Token budget is first-class.** Output fits `--budget` (default 1500 tokens); the tool decides how to spend it and truncates whole groups from the tail.
|
|
60
|
-
- **Every hit is a `path:line` anchor** into the live file; hits show their enclosing symbol (`⟨in createWidget (fn)⟩`), and natural-language answers deliver the top hits' full enclosing bodies plus `related:` definition anchors for follow-up.
|
|
61
|
-
- **Scope** with `--in <dir>`, `--repo <name>`, `--lang ts,py`, `--glob`/`--not-glob`, `--only tests|src|docs|config`; `--ignored` includes gitignored files (the security floor (secrets, `.git`) never lifts).
|
|
62
|
-
- **Exit codes** follow grep convention: `0` hits, `1` none, `2` usage error.
|
|
63
|
-
- **Machine output**: `--json` (one result document) or `--ndjson` (one line per group).
|
|
64
|
-
- **Zero hits are never a dead end**: an identifier, path or pattern that matches nothing exactly is re-run semantically (the header says so), and a genuine zero carries a `hint:` diagnosing the likely cause.
|
|
65
|
-
|
|
66
|
-
## Configuration (environment)
|
|
67
|
-
|
|
68
|
-
| Var | Effect |
|
|
69
|
-
|---|---|
|
|
70
|
-
| `WORKSPACE_ROOT` | Directory to search (default: current directory). |
|
|
71
|
-
| `IQ_MODEL_DIR` | Embedding/reranker model directory; unset → natural-language queries degrade to lexical. |
|
|
72
|
-
| `IQ_RG_PATH` | Override the ripgrep binary resolved from `PATH`. |
|
|
73
|
-
| `INTENTIC_OUTPUT` | Default output mode when no flag is given: `text` (default), `json`, `ndjson`. |
|
|
74
|
-
| `IQ_FEATURES` | Retrieval-stage toggles (see below); the `--features` flag overrides it. |
|
|
75
|
-
| `IQ_DEBUG` | Keep the full JS stack on a thrown error instead of the one-line message. |
|
|
76
|
-
|
|
77
|
-
## How it works
|
|
78
|
-
|
|
79
|
-
A bare query is classified (identifier / regex / path / natural-language) and routed through a fused pipeline:
|
|
80
|
-
|
|
81
|
-
**ripgrep** (exact) + **FTS5 BM25** (sparse relevance) + **RM3 pseudo-relevance feedback** (query expansion) + **dense embeddings** (semantic) → **reciprocal-rank fusion** with **defboost** / **pathboost** / **recency** multipliers (one toggle each; pathboost matches path *words*, so `indexer.ts` answers "index" while `_textwrap.py` does not answer "wrap") and a **source-first** class prior (implementation over its tests and docs, natural-language answers only) → **cross-encoder rerank** (blended into the fused order via RRF: it *votes*, it doesn't veto) → **enclosing-symbol context** + **graph** neighbor anchors + **pack** (the top groups arrive as the actual code slice, not just a pointer, so the reader usually needs no follow-up open).
|
|
82
|
-
|
|
83
|
-
All twelve stages are independently toggleable for benchmarking and deployment tuning, via `--features` or `IQ_FEATURES` (allow-list `bm25` = only BM25; default-minus `-rerank,-prf` = all except those):
|
|
84
|
-
|
|
85
|
-
`bm25`, `semantic`, `rerank`, `prf`, `confidence`, `symctx`, `graph`, `defboost`, `pathboost`, `recency`, `srcfirst`, `pack`.
|
|
14
|
+
- A bare query picks its own strategy: a path, an identifier, a regex or natural language, falling back to semantic search when nothing matches exactly. There is no separate verb for questions.
|
|
15
|
+
- Every answer opens with a capsule: `answer:` names the top anchor, its enclosing symbol and whether it is confident or ambiguous; `candidates:` and `more:` follow. Output fits `--budget` tokens, capsule included.
|
|
16
|
+
- Exit codes follow grep (0 hits, 1 none, 2 error). Common grep flags get a one-line redirect instead of a usage dump.
|
|
17
|
+
- The index lives in `.intentic/local/cache/iq` and maintains itself; `iq index rebuild` is for a stale index only.
|
|
18
|
+
- `plugin/` is a Claude Code plugin: the `iq` skill, a SessionStart nudge that ingests transcripts, and a prompt hook that suggests matching past sessions. The sandbox image bakes it and loads it for every agent.
|
|
86
19
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
### Orientation verbs
|
|
90
|
-
|
|
91
|
-
`map` ranks files by **PageRank over the import graph** (an edge A→B means A imports B) then prints each file's exported signatures until `--budget` runs out. Specifiers are extracted at index time and resolved at query time against the indexed file set: relative paths directly (including TypeScript's `./x.js` → `x.ts` rule), bare specifiers via each workspace `package.json`'s `name`, so cross-package imports don't fragment a monorepo into disconnected islands. Unresolved specifiers (node_modules, stdlib) are simply not edges. Generated files contribute no definitions; a re-export shim would otherwise look like the most depended-upon file in the repo.
|
|
92
|
-
|
|
93
|
-
An earlier version built edges from exported-symbol name matches instead. It doesn't work at any weighting: text can't tell a reference from a coincidence, so modules exporting ordinary words (`App`, `Host`, `Repo`) top the list.
|
|
94
|
-
|
|
95
|
-
`hotspots` multiplies **git churn** (commits touching a file) by **complexity** (branch points, counted at index time from the same ast-grep parse that extracts symbols, with a lexical fallback for languages that have no grammar). Neither half is interesting alone: a churning config file is trivial, a gnarly file nobody edits costs nobody anything. Data and markup score zero, keyword scans there read content, not code paths.
|
|
96
|
-
|
|
97
|
-
Complexity is reported as a **raw branch count**, not a composite score: counts are comparable to the file in front of you, and you can recount them by hand.
|
|
98
|
-
|
|
99
|
-
`impact` answers "what does this change reach?" over the same import graph. With no argument it reads your uncommitted changes; given paths, it answers about those. Each result carries its distance and whether it is a test, and the header names what the walk could **not** answer: seeds the index has never seen, how many reachable files the cap dropped, and (the useful half) which changed files no test reaches at all.
|
|
100
|
-
|
|
101
|
-
It walks exactly **one hop, in both directions**, and that is a measured choice rather than a cautious one. Against 762 co-change cases mined from this repo's own history (`iq-bench impact`), one hop each way beat the no-graph "same folder" baseline 444 wins to 168 and beat one hop of importers alone 318 to 159, both at p < 0.001. Depth made it worse, not better: two hops of importers *lost* to one hop, and two hops in both directions reach hundreds of files per seed at 0.06 precision. Coverage depth is deliberately separate and deliberately timid (a test that imports the file directly counts, nothing further) because the co-change benchmark measured which files change together, which is not the same question as which tests exercise what, and borrowing its answer would be borrowing evidence never collected.
|
|
102
|
-
|
|
103
|
-
## Benchmarks
|
|
104
|
-
|
|
105
|
-
`iq` is measured by a two-tier harness in [`_search/iq-bench`](../../_search/iq-bench): tier 1 scores retrieval quality against golden query→anchor datasets with no LLM (free, deterministic); tier 2 runs real coding-agent CLIs on real tasks, paired with and without `iq`, graded automatically (answer must name the ground-truth `path:line`, or the repo's tests must pass). All numbers below are reproducible by re-running the committed harness and datasets.
|
|
106
|
-
|
|
107
|
-
### Tier 1, retrieval quality (90 golden queries, 3 repos: this workspace + `hono` + `click`)
|
|
108
|
-
|
|
109
|
-
The full pipeline is the best configuration on aggregate (recall@5 **0.81**, recall@10 **0.88**, nDCG@10 **0.65**). Ablations, paired per query vs the full pipeline (exact sign test):
|
|
110
|
-
|
|
111
|
-
| Configuration | vs full | Δ recall@10 | Verdict |
|
|
112
|
-
|---|---|---:|---|
|
|
113
|
-
| drop **semantic** | loses 24 / wins 3 (p<0.001) | −0.04 | significant: dense retrieval earns its place |
|
|
114
|
-
| **lexical only** (grep-grade) | loses 27 / wins 3 (p<0.001) | −0.09 | significantly worse on natural-language queries |
|
|
115
|
-
| **bm25 only** | loses 29 / wins 7 (p<0.001) | −0.07 | significantly worse |
|
|
116
|
-
| drop **rerank** | loses 5 / wins 4 (p=1.0) | ±0.00 | neutral on ranking (helps agents downstream) |
|
|
117
|
-
| drop **pack** | loses 0 / wins 3 (p=0.25) | +0.02 | ranking-neutral (marginally trims recall); payoff is agent-side (below) |
|
|
118
|
-
|
|
119
|
-
The gap over grep-grade retrieval concentrates exactly on the synonym-gap queries where an agent's phrasing differs from the code's vocabulary: the case `iq` exists for.
|
|
120
|
-
|
|
121
|
-
### Tier 2: does it help a real agent? (Claude Sonnet, 8 tasks, `iq` vs plain grep/find)
|
|
122
|
-
|
|
123
|
-
Both arms solved every task, so at this difficulty `iq` doesn't change *whether* the problem is solved: it changes the *cost* of getting there, and the gap widens with search difficulty:
|
|
124
|
-
|
|
125
|
-
- **Aggregate: iq $2.19 vs grep $3.29 across 8 tasks (−33% cost).**
|
|
126
|
-
- **The search-hardest task** (trace an off-by-2 bug through a URL parser): grep baseline burned **21 turns / $1.27 / 306 s**; with `iq`, **10 turns / $0.33 / 50 s**, **−74% cost, −52% turns, −84% wall.** The baseline thrashed through ad-hoc probes and repeated reads; `iq` located the mechanism directly.
|
|
127
|
-
|
|
128
|
-
On trivial single-file lookups `iq` is roughly break-even (when the answer is one obvious grep away, the index fetch is overhead it can't earn back), the value is concentrated where retrieval is genuinely hard. Full methodology, per-task tables, paired statistics, and transcripts: [`_search/iq-bench`](../../_search/iq-bench).
|
|
129
|
-
|
|
130
|
-
## Using it with a coding agent
|
|
131
|
-
|
|
132
|
-
`iq` ships as a **Claude Code plugin** ([`plugin/`](plugin)) that teaches an agent to prefer `iq` over grep/find/Glob by default: a bundled skill plus a SessionStart nudge. The plugin installs the *teaching*, not the binary, so install both:
|
|
20
|
+
## Usage
|
|
133
21
|
|
|
134
22
|
```sh
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
23
|
+
iq "where do we enforce the secrets floor?" # anything; intent is auto-detected
|
|
24
|
+
iq def createIgnoreScope # where a symbol is defined
|
|
25
|
+
iq refs createIgnoreScope --kind call # who calls it
|
|
26
|
+
iq outline src/app.ts # a file's shape without reading it
|
|
27
|
+
iq read src/app.ts::Server::start # one symbol's body
|
|
28
|
+
iq impact # what the uncommitted change reaches, and its tests
|
|
29
|
+
iq sessions files "auth refresh" # files past sessions touched for a topic
|
|
138
30
|
```
|
|
139
31
|
|
|
140
|
-
The plugin's skill lives at [`plugin/skills/iq/SKILL.md`](plugin/skills/iq/SKILL.md) and doubles as a `CLAUDE.md` / `AGENTS.md` note if you'd rather paste it in directly. The Agent SDK loads the same plugin via `plugins: [{ type: "local", path: "…/_search/iq/plugin" }]`. The intentic sandbox bakes this exact plugin into its image, so the sandbox agent and an external user get identical behavior.
|
|
141
|
-
|
|
142
|
-
## License
|
|
143
|
-
|
|
144
|
-
MIT
|
|
145
|
-
|
|
146
32
|
## Key files
|
|
147
33
|
|
|
148
|
-
- [src/
|
|
149
|
-
- [src/
|
|
150
|
-
- [src/
|
|
151
|
-
- [src/
|
|
152
|
-
|
|
153
|
-
clamp), which `fileq` and `webq` keep the same way.
|
|
34
|
+
- [src/app.ts](src/app.ts) — the verb table and the `--help` text agents read.
|
|
35
|
+
- [src/lib/run.ts](src/lib/run.ts) — the executor every search verb shares: engine, path resolution, output mode, exit code.
|
|
36
|
+
- [src/commands/q.command.ts](src/commands/q.command.ts) — the default verb behind a bare `iq "…"`.
|
|
37
|
+
- [src/commands/sessions/sessions.routes.ts](src/commands/sessions/sessions.routes.ts) — session recall verbs over `iq-recall`.
|
|
38
|
+
- [plugin/skills/iq/SKILL.md](plugin/skills/iq/SKILL.md) — which verb to reach for, as agents are taught it.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentic/iq",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.311.0",
|
|
4
4
|
"description": "iq, agent-native workspace search CLI: one intent-first entry point over lexical, structural, semantic, and git engines",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -33,11 +33,11 @@
|
|
|
33
33
|
}
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@intentic/agent-cli": "1.
|
|
37
|
-
"@intentic/base": "1.
|
|
38
|
-
"@intentic/constants": "1.
|
|
39
|
-
"@intentic/iq-engine": "1.
|
|
40
|
-
"@intentic/iq-recall": "1.
|
|
36
|
+
"@intentic/agent-cli": "1.311.0",
|
|
37
|
+
"@intentic/base": "1.311.0",
|
|
38
|
+
"@intentic/constants": "1.311.0",
|
|
39
|
+
"@intentic/iq-engine": "1.311.0",
|
|
40
|
+
"@intentic/iq-recall": "1.311.0",
|
|
41
41
|
"@puristic/env": "1.6.1",
|
|
42
42
|
"@stricli/core": "1.3.0",
|
|
43
43
|
"tslib": "2.8.1",
|