any-doctor 0.0.7 → 0.0.9

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.
Files changed (62) hide show
  1. package/CONTEXT.md +96 -12
  2. package/README.md +45 -29
  3. package/bin/analysis-host.d.ts +5 -3
  4. package/bin/analysis-host.js +10 -6
  5. package/bin/analysis.d.ts +9 -1
  6. package/bin/analysis.js +188 -17
  7. package/bin/certify.js +56 -139
  8. package/bin/cli.js +15 -8
  9. package/bin/cohort.d.ts +4 -3
  10. package/bin/cohort.js +4 -4
  11. package/bin/contract.d.ts +99 -8
  12. package/bin/contract.js +67 -25
  13. package/bin/dashboard.d.ts +3 -55
  14. package/bin/dashboard.js +12 -209
  15. package/bin/diff.d.ts +30 -5
  16. package/bin/diff.js +170 -40
  17. package/bin/discover.js +16 -3
  18. package/bin/doctor-tree.d.ts +52 -0
  19. package/bin/doctor-tree.js +113 -0
  20. package/bin/engine.d.ts +2 -0
  21. package/bin/engine.js +100 -1
  22. package/bin/identity.d.ts +63 -0
  23. package/bin/identity.js +456 -0
  24. package/bin/palette.d.ts +1 -0
  25. package/bin/palette.js +1 -1
  26. package/bin/prompts.d.ts +4 -0
  27. package/bin/prompts.js +103 -0
  28. package/bin/report.d.ts +9 -19
  29. package/bin/report.js +49 -13
  30. package/bin/runner.js +12 -1
  31. package/bin/score.d.ts +2 -0
  32. package/bin/score.js +9 -2
  33. package/bin/sdk.d.ts +0 -1
  34. package/bin/sdk.js +24 -25
  35. package/bin/search-host.d.ts +0 -2
  36. package/bin/search-host.js +1 -32
  37. package/bin/summary.d.ts +1 -3
  38. package/bin/summary.js +9 -6
  39. package/docs/HANDOFF.md +102 -64
  40. package/docs/decisions.md +149 -1
  41. package/docs/doctor-reliability.md +82 -0
  42. package/docs/evidence/repair-after-0.0.7/convex-verify.txt +103 -0
  43. package/docs/evidence/repair-after-0.0.7/sift-convex-packed.json +583 -0
  44. package/docs/evidence/repair-after-0.0.7/sift-convex-published.json +1023 -0
  45. package/docs/evidence/repair-after-0.0.7/sift-slop-packed.json +956 -0
  46. package/docs/evidence/repair-after-0.0.7/sift-slop-published.json +951 -0
  47. package/docs/evidence/repair-after-0.0.7/tests.txt +307 -0
  48. package/docs/example-catalog.md +7 -1
  49. package/docs/features.md +62 -67
  50. package/docs/intents.md +6 -0
  51. package/docs/plans/analysis-improvements.md +234 -0
  52. package/docs/plans/finding-lifecycle/design.md +170 -0
  53. package/docs/plans/finding-lifecycle/milestones.md +120 -0
  54. package/docs/plans/finding-lifecycle/proposal.md +104 -0
  55. package/docs/repair-audit-007.md +78 -0
  56. package/docs/vision.md +101 -83
  57. package/doctors/convex-doctor.fixtures.mjs +472 -34
  58. package/doctors/convex-doctor.mjs +137 -219
  59. package/fixtures/sensitivity/discard-with-unrelated-combiner/convex/notes.ts +9 -0
  60. package/fixtures/sensitivity/discard-with-unrelated-combiner/expect.json +13 -0
  61. package/package.json +2 -1
  62. package/skill/any-doctor.skill.md +64 -32
package/CONTEXT.md CHANGED
@@ -3,6 +3,14 @@
3
3
  Canonical vocabulary for any-doctor. Glossary only — no implementation.
4
4
  When a term here conflicts with language elsewhere, this file wins.
5
5
 
6
+ Current product intent lives in [docs/vision.md](docs/vision.md). For work on
7
+ persistent decisions, history, identity, or team convergence, read the
8
+ [lifecycle design](docs/plans/finding-lifecycle/design.md); the first identity
9
+ delivery (lifecycle M1, slices A1+A2) is planned in
10
+ [analysis improvements](docs/plans/analysis-improvements.md). Their proposed records
11
+ are not implemented interfaces; the glossary below describes current behavior
12
+ unless a term is explicitly marked planned.
13
+
6
14
  ## Doctor program
7
15
 
8
16
  The artifact an LLM writes: a JavaScript module that inspects a target
@@ -57,6 +65,33 @@ deriving twice from one RunOutcome yields one Summary; rendering
57
65
  derivation. The facts a gate needs (`--fail-on` severity counts,
58
66
  baseline-diffable shapes) live here as data, not inside rendering.
59
67
 
68
+ ## Dashboard
69
+
70
+ The interactive review surface over a RunOutcome: one module
71
+ (src/dashboard.ts) that owns layout, the frame renderer, the TUI loop, and
72
+ the per-finding read state — composing the Doctor tree as its view-model
73
+ and the Task prompts it copies. Selection, expansion, keymap, and the
74
+ clipboard notice live here; the tree's shape and the prompt copy do not.
75
+
76
+ ## Doctor tree
77
+
78
+ The dashboard's view-model: DoctorGroup → checks → SiteFinding, computed
79
+ once from the Summary's per-doctor check buckets (src/doctor-tree.ts —
80
+ the tree joins and orders, it never re-groups) and ordered for triage —
81
+ worst severity first, then finding count, then name. Every consumer (the
82
+ list rows, the detail pane, the task prompts) flattens or reads the one
83
+ tree without rebuilding it. A SiteFinding's readKey
84
+ (`checkKey@file:line`) is the within-run identity the read state keys on.
85
+
86
+ ## Task prompt
87
+
88
+ Text the dashboard copies to the clipboard as one unit of agent work: one
89
+ finding (fixPrompt), every finding of one check (checkFixPrompt), or a
90
+ doctor's whole batch (doctorFixPrompt) — pure functions of Doctor-tree
91
+ types plus the verify command, no terminal required. The lifecycle plan
92
+ (M2) reworks this family toward investigation-first framing and
93
+ authorized decision paths.
94
+
60
95
  ## Gate
61
96
 
62
97
  A run's exit policy — one module (src/gate.ts), one law. Findings are
@@ -69,13 +104,15 @@ infrastructure failure is not a finding and must never paint a run
69
104
  green. Diff mode (`--base <ref>`) judges only what a change ADDED: the
70
105
  same cohort scans the merge base of the ref and HEAD (a stateless
71
106
  baseline — nothing committed, nothing stale), the two deduped finding
72
- sets compare through the verify gate's own rule-aware multiset, and the
107
+ sets compare through the identity layer (movement-aware a finding
108
+ that moved with its code is Continuing; `compareFindings` remains the
109
+ fixture gate's exact multiset, never the diff's), and the
73
110
  bar counts added findings only; a change is not blamed for the debt it
74
111
  was born into. A partial base never gates: any base-scan crash aborts
75
112
  the run loudly (exit 1, no report, no JSON), because a baseline
76
113
  missing findings would dress pre-existing debt up as "added" — and a
77
114
  crashed HEAD doctor skips the diff for the same reason: its findings
78
- are absent, and absence must never read as "resolved". Machine
115
+ are absent, and absence must never read as "no longer detected". Machine
79
116
  output rides `--format json` — one schema-tagged object on stdout,
80
117
  diagnostics on stderr.
81
118
 
@@ -93,12 +130,21 @@ runs. There is no override in any mode.
93
130
 
94
131
  ## Finding
95
132
 
96
- One emitted finding: a location (file, line) plus optional per-finding
133
+ One emitted finding: an observed condition, represented by a location (file, line, optional zero-based column) plus optional per-finding
97
134
  message or severity override. "Issue" and "instance" are retired
98
135
  synonyms — Finding is the term in code, copy, and prompts. The
99
136
  doctor-level truth (id, description, default severity, blind spots)
100
137
  lives in the program's meta, not in individual findings.
101
138
 
139
+ A finding may establish a defect, flag a project convention, or identify a
140
+ contextual review candidate. Its existence alone does not establish that a code
141
+ change is appropriate. Current locations are not durable lifecycle identities,
142
+ but a finding may carry an optional `evidence` range (`endLine`, exclusive
143
+ `endColumn`) covering its whole expression: the host validates it against the
144
+ scanned source and the identity layer digests the covered span, so edits on
145
+ continuation lines still break identity. Findings without a range match on
146
+ their flagged line alone — line-scoped, surfaced as such.
147
+
102
148
  ## Check
103
149
 
104
150
  One rule within a doctor program. A finding names its check via `rule`;
@@ -107,7 +153,7 @@ and, when the check uses the identity engine at full power, its
107
153
  declaration of that need (`needs`), which is what renders "narrowed"
108
154
  when the engine is absent; the doctor's meta supplies the defaults when
109
155
  a finding names no check. A check id is a short kebab-case noun phrase
110
- over [a-z0-9-], unique within its doctor, naming the defect
156
+ over [a-z0-9-], unique within its doctor, naming the detected concern
111
157
  (fetch-calls-without-abortsignal, filter-table-scan). One doctor
112
158
  program, many checks.
113
159
 
@@ -167,9 +213,12 @@ scans doctors once carried privately (the audit truncation bugs lived in
167
213
  those approximations). The engine is optional (oxc-parser +
168
214
  eslint-scope behind the Engine seam's second adapter): checks declare
169
215
  the analysis they need on their CheckMeta (`needs` — vocabulary:
170
- "bindings", "spans"), narrow without it,
216
+ "bindings", "spans", "calls"), narrow without it,
171
217
  and the report renders "narrowed" — a degraded run is visible, never
172
- silent. References answer by position, so analysis queries compose with
218
+ silent. `ctx.analysis.calls(file)` provides immediate call use, receiver identity,
219
+ inline callback registration, and linked call ranges. These are syntax facts;
220
+ stored, passed, or returned is not a promise-settlement verdict. Parse/adapter
221
+ errors fail the doctor run explicitly. References answer by position, so analysis queries compose with
173
222
  rule queries: shapes from one engine, identities from the other.
174
223
 
175
224
  ## Engine
@@ -197,7 +246,7 @@ count as the doctors scanned it (default extensions); findings naming
197
246
  files outside that count can push the raw value negative, so the result
198
247
  is floored into 0–100 — and floored, never rounded, so any finding costs
199
248
  at least one point. Findings duplicated across doctors at the same
200
- file:line are deduplicated before scoring — the first-sorted copy wins
249
+ file:line:column are deduplicated before scoring — the first-sorted copy wins
201
250
  (groups sort by the first finding carrying an explicit severity
202
251
  override, else the doctor's declared default; equal-severity groups
203
252
  fall back to input order) and a hidden duplicate's severity does not
@@ -227,9 +276,10 @@ known input.
227
276
  ## Fixture
228
277
 
229
278
  One seed plus the findings expected from running a doctor program against
230
- it. Expected findings match exactly on (rule, file, line), duplicates
279
+ it. Expected findings match on (rule, file, line, optional column), duplicates
231
280
  counted: a missing expected finding is a recall failure; an unexpected
232
- finding is a precision failure. The rule in an expectation is part of the
281
+ finding is a precision failure within these labeled cases, not a population
282
+ accuracy measurement. The rule in an expectation is part of the
233
283
  match — a wrong-check finding at the right line fails the gate. A fixture
234
284
  also declares its analysis mode (D20 Stage 2): "on" (default) pins the
235
285
  full-power path and skips with a named notice where the engine is not
@@ -248,8 +298,8 @@ workflow's second pass; a doctor ships only after surviving its attack.
248
298
  Running a doctor program against its fixtures and diffing the findings.
249
299
  The trust gate: a doctor program is not considered working until verify
250
300
  passes. The policies that compose the gate — the claim contract, the
251
- per-fixture diff, the shared innocent corpus, the duplicate-location
252
- probe, the shared sensitivity corpus — live in one module, the
301
+ per-fixture diff, the shared innocent corpus, per-check location
302
+ coverage, the shared sensitivity corpus — live in one module, the
253
303
  Certification harness (`src/certify.ts`), behind one interface:
254
304
  `certify(mod, fixtures) -> result rows`. The doctor loader calls it once;
255
305
  prevention tiers land there, not in loader choreography.
@@ -277,7 +327,8 @@ the consuming repo), user-global (`~/.any-doctor/doctors/`, available
277
327
  in every repo), or bundled (the first-party pack inside the package,
278
328
  read-only — a starting point, not a dependency). Repo-local wins slug
279
329
  collisions, then user-global, then bundled. Scanning a target repo
280
- never writes to any scope.
330
+ currently does not persist state in any scope. Planned CLI-owned state is
331
+ separate from doctor discovery and does not grant doctors write capabilities.
281
332
 
282
333
  ## Skill
283
334
 
@@ -287,3 +338,36 @@ marker; `generate` refreshes a copy it planted (the marker is the
287
338
  boundary) and never touches a copy without one. The generation prompt
288
339
  embeds the skill verbatim. Any Doctor equips agents with the skill;
289
340
  it never launches, deploys, or speaks for an agent.
341
+
342
+ ## Location coverage
343
+
344
+ A certification result for one check's reporting unit. Occurrence-level
345
+ warning/error checks need a passing, context-preserving fixture with two
346
+ distinct locations of that check in one file. File/project checks need a
347
+ positive witness. Unspecified legacy units and unavailable analysis are
348
+ reported as not exercised, not counted as passing. Fixture expectations
349
+ establish tested coverage, not general correctness or independence of labels.
350
+
351
+ ## Lifecycle vocabulary (planned, except where marked landed)
352
+
353
+ These terms describe the accepted direction, not current fields on Finding or
354
+ DoctorCtx. The [design](docs/plans/finding-lifecycle/design.md) owns their data
355
+ and applicability rules.
356
+
357
+ - **Finding identity** *(landed in the diff path, D30)*: continuity of one
358
+ occurrence across comparable scans, distinct from its current source
359
+ coordinates. Host-derived today — check key, file, normalized flagged-line
360
+ digest, indentation-relative column, and innermost enclosing function span —
361
+ computed per comparison from one post-scan read, never persisted.
362
+ - **Observation:** evidence that a finding was detected in a particular scan.
363
+ - **Decision:** a reasoned accepted/not-applicable disposition with local or project
364
+ scope; it changes review state, not the raw observation.
365
+ - **Continuing** *(landed in the diff path)*: a head occurrence matched to a
366
+ compatible base occurrence by identity — movement is not addition. Matches
367
+ resting on content alone are flagged contextFallback; identical copies
368
+ matched by cardinality are flagged ambiguous.
369
+ - **No longer detected** *(landed in the diff path)*: absence established by
370
+ compatible, completed coverage.
371
+ - **Claimed fix:** a recorded explanation of remediation, separate from rescan evidence.
372
+ - **Reassessment:** a decision requires review because identity or applicability is
373
+ changed, conflicting, or uncertain.
package/README.md CHANGED
@@ -1,17 +1,20 @@
1
1
  # any-doctor
2
2
 
3
- > Your agent writes the analyzer. Fixtures prove it. CI reruns it forever.
3
+ > Your agent writes the doctor. Inspect its findings. Remember what matters.
4
4
 
5
- Your LLM writes code fast and roughly. **Doctors** are small deterministic
6
- programs that catch what it keeps getting wrong — and any-doctor ships with
7
- five of them covering the disciplines LLMs fumble most.
5
+ Any Doctor helps you and your agent find recurring problems in AI-written code
6
+ beyond ordinary lint configurations. **Doctors** are small deterministic analysis
7
+ programs that surface concrete concerns and codebase-specific conventions.
8
8
 
9
9
  When none of them covers the convention you keep explaining in code review,
10
- your agent writes a new doctor for it against a typed `ctx` API, gated by
11
- fixtures, rerun forever in CI with zero inference. **A skill without a doctor
12
- is a suggestion.**
10
+ your own agent writes a doctor for it against a typed `ctx` API, with positive
11
+ examples and valid lookalikes. Run it after coding, inspect the evidence, and
12
+ rescan after changes. Saved doctors run without model inference.
13
13
 
14
- Everything runs locally. No account, no API key, no telemetry, no network.
14
+ Analysis runs locally without an account, API key, or telemetry. Package/tool
15
+ installation can require downloads; the doctor runtime has no network access.
16
+ Remembered decisions and finding history are [planned](docs/plans/finding-lifecycle/proposal.md),
17
+ not available yet.
15
18
 
16
19
  [![npm version](https://img.shields.io/npm/v/any-doctor.svg)](https://www.npmjs.com/package/any-doctor)
17
20
  [![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](#)
@@ -45,11 +48,12 @@ Non-terminals and CI never see a prompt — output is stable and pipeable
45
48
  | **openrouter-doctor** | OpenRouter discipline: stream errors surfaced, keep-alives skipped, cancellations that stop billing | 5 |
46
49
  | **async-doctor** | Async and concurrency: dropped promise results, uncleared timers, fetch hygiene | 3 |
47
50
 
48
- Every check ships with fixtures proving both directions — it fires on the
49
- shapes it claims (recall) and stays silent on innocent lookalikes
50
- (precision). 144 fixtures across the pack, run by `verify` as an exact
51
- multiset on `rule:file:line`. slop-doctor's checks were born from 1,368
52
- Bugbot findings across 182 reviewed PRs every check carries its evidence.
51
+ Checks ship positive and innocent-lookalike fixtures. `verify` compares an
52
+ exact multiset of rule/file/line and optional column, then runs shared innocent
53
+ and sensitivity corpora. Convex checks also declare per-check location coverage;
54
+ legacy undeclared coverage is reported as not exercised. Passing fixtures show
55
+ agreement on those cases, not a general accuracy guarantee. See
56
+ [the reliability protocol](docs/doctor-reliability.md).
53
57
 
54
58
  ## Write your own
55
59
 
@@ -64,7 +68,7 @@ writes two files:
64
68
  - `doctors/your-doctor.mjs` — the analyzer: `meta` (checks, blind spots,
65
69
  severity) + `doctor(ctx)`. One self-contained file; `ctx` is its entire
66
70
  world (files, structural search via ast-grep, binding analysis, findings).
67
- - `doctors/your-doctor.fixtures.mjs` — the proof: seed codebases plus the
71
+ - `doctors/your-doctor.fixtures.mjs` — regression evidence: seed codebases plus the
68
72
  findings the doctor must produce, and the lookalikes it must ignore.
69
73
 
70
74
  Then the loop agents love:
@@ -76,11 +80,11 @@ npx any-doctor@latest verify doctors/your-doctor.mjs
76
80
  # ✔ accepts an awaited result
77
81
  ```
78
82
 
79
- `missing` fails recall, `unexpected` fails precision, the exit code stays
80
- non-zero until every fixture passes and the skill teaches the sharp edges
83
+ `missing` and `unexpected` identify failures against the labeled fixture cases;
84
+ the exit code stays non-zero until every fixture passes. The skill teaches the sharp edges
81
85
  (scope-analysis semantics, fixture-seed worlds, the adversarial
82
- counter-fixture pass) so the loop converges fast. No install needed;
83
- `verify` sandboxes everything.
86
+ counter-fixture pass). Passing these cases does not measure general accuracy.
87
+ No project install needed; `verify` sandboxes the doctor runs.
84
88
 
85
89
  ## CI: the gate
86
90
 
@@ -100,17 +104,22 @@ npx any-doctor@latest run --all --fail-on warning --base origin/main
100
104
 
101
105
  ## Why trust a finding
102
106
 
103
- - **Deterministic.** The same doctor on the same commit produces the same
104
- report no model runs at scan time, ever.
105
- - **Fixtures gate everything.** A doctor that hasn't passed `verify` doesn't
106
- exist; discovery lists it as broken, not as a tool.
107
+ - **Repeatable analysis.** Hold source contents, doctor implementation, engine
108
+ versions, configuration, and capabilities fixed when comparing runs. No model
109
+ runs at scan time.
110
+ - **Executable evidence.** Run `verify` to test declared cases and shared
111
+ counterexamples. Discovery is not proof that a doctor passed verification;
112
+ independent real-code evaluation is still needed.
113
+ - **Specific claims.** A finding can establish a defect, flag a project convention,
114
+ or identify a review candidate. Its evidence should support that interpretation;
115
+ a suggested change can still require judgment.
107
116
  - **Blind spots are data.** Every doctor declares what it cannot see, and
108
117
  the report renders those declarations beside the findings.
109
118
  - **Doctors are confined.** The runtime capability gate refuses to execute
110
119
  a doctor that imports, writes, spawns, or touches the network — a
111
120
  malicious doctor is refused before it runs, with no override.
112
- - **The score is the share of clean files**, per doctor and overall
113
- health and work are reported together, never conflated.
121
+ - **The score summarizes files without reported findings**, per doctor and overall.
122
+ It is not a probability of correctness or proof that unexamined code is safe.
114
123
 
115
124
  ## Docs
116
125
 
@@ -118,14 +127,21 @@ npx any-doctor@latest run --all --fail-on warning --base origin/main
118
127
  |---|---|
119
128
  | [skill/any-doctor.skill.md](skill/any-doctor.skill.md) | The authoring contract — what your agent reads to write doctors |
120
129
  | [CONTEXT.md](CONTEXT.md) | Domain glossary — canonical terms |
121
- | [docs/decisions.md](docs/decisions.md) | Decision log (D1–D20). Read first; don't relitigate |
122
- | [docs/vision.md](docs/vision.md) | The product idea and lifecycle novelty |
130
+ | [docs/vision.md](docs/vision.md) | Current goals and product direction |
131
+ | [docs/features.md](docs/features.md) | Available features versus planned work |
132
+ | [docs/plans/analysis-improvements.md](docs/plans/analysis-improvements.md) | Next slice: source evidence, identity, and reliable Git-base comparisons |
133
+ | [docs/plans/finding-lifecycle/proposal.md](docs/plans/finding-lifecycle/proposal.md) | Planned decisions, history, and team workflows |
134
+ | [docs/plans/finding-lifecycle/design.md](docs/plans/finding-lifecycle/design.md) | State ownership, SQLite, Git convergence, and open choices |
135
+ | [docs/plans/finding-lifecycle/milestones.md](docs/plans/finding-lifecycle/milestones.md) | Implementation slices and acceptance evidence |
136
+ | [docs/HANDOFF.md](docs/HANDOFF.md) | Current handoff and next bounded task |
137
+ | [docs/decisions.md](docs/decisions.md) | Historical choices and explicit supersessions |
123
138
  | [docs/kill-test.md](docs/kill-test.md) + [docs/RESULTS.md](docs/RESULTS.md) | The validation experiment and its numbers |
124
139
  | [docs/REPAIR-LOG.md](docs/REPAIR-LOG.md) | Generation-bug categories — feeds the authoring skill |
125
140
  | [docs/example-catalog.md](docs/example-catalog.md) | Rule intents across the JS ecosystem |
126
141
 
127
142
  ## Status
128
143
 
129
- Pre-1.0 and moving fast — the decision log is the honest history. Next on
130
- the ladder: the registry (`any-doctor add <slug>`), adoption (`init`), and
131
- a growing pack. MIT.
144
+ Pre-1.0. The next direction is reliable finding identity, remembered decisions,
145
+ team sharing, and bounded local history. Scanning stays available through npx
146
+ without mandatory initialization. See the [feature map](docs/features.md) for
147
+ current availability. MIT.
@@ -1,5 +1,5 @@
1
- import { AnalysisFile, AnalysisSpans, Mode } from "./contract.js";
2
- import { analysisStatus, analyzeBindings, analyzeSpans } from "./analysis.js";
1
+ import { AnalysisFile, AnalysisSpans, AnalysisCalls, Mode } from "./contract.js";
2
+ import { analysisStatus, analyzeBindings, analyzeSpans, analyzeCalls } from "./analysis.js";
3
3
  type Analyzer = typeof analyzeBindings;
4
4
  type SpansAnalyzer = typeof analyzeSpans;
5
5
  type Status = typeof analysisStatus;
@@ -16,8 +16,10 @@ export type AnalysisResponse = {
16
16
  file: AnalysisFile;
17
17
  } | {
18
18
  file: AnalysisSpans;
19
+ } | {
20
+ file: AnalysisCalls;
19
21
  } | {
20
22
  error: string;
21
23
  };
22
- export declare function handleAnalysisRequest(req: AnalysisRequestBody, mode: Mode, analyzer?: Analyzer, status?: Status, spansAnalyzer?: SpansAnalyzer): AnalysisResponse;
24
+ export declare function handleAnalysisRequest(req: AnalysisRequestBody, mode: Mode, analyzer?: Analyzer, status?: Status, spansAnalyzer?: SpansAnalyzer, callsAnalyzer?: typeof analyzeCalls): AnalysisResponse;
23
25
  export {};
@@ -1,11 +1,12 @@
1
1
  import * as fs from "fs";
2
2
  import * as path from "path";
3
- import { analysisStatus, analyzeBindings, analyzeSpans } from "./analysis.js";
4
- import { searchBase, withinBase } from "./search-host.js";
3
+ import { searchBase, withinBase, withinDir } from "./contract.js";
4
+ import { analysisStatus, analyzeBindings, analyzeSpans, analyzeCalls } from "./analysis.js";
5
5
  // One cache per host process. The host lives in the runner process, so
6
6
  // the lifetime is the any-doctor invocation; across a cohort's doctors
7
7
  // the same unchanged file answers from memory.
8
8
  const modelCache = new Map();
9
+ const callsCache = new Map();
9
10
  const spansCache = new Map();
10
11
  // Test seam: the model cache is keyed by mtime+size for the process
11
12
  // lifetime; tests bust it between cases. Invisible to slop-doctor's
@@ -13,8 +14,9 @@ const spansCache = new Map();
13
14
  export function clearAnalysisCache() {
14
15
  modelCache.clear();
15
16
  spansCache.clear();
17
+ callsCache.clear();
16
18
  }
17
- export function handleAnalysisRequest(req, mode, analyzer = analyzeBindings, status = analysisStatus, spansAnalyzer = analyzeSpans) {
19
+ export function handleAnalysisRequest(req, mode, analyzer = analyzeBindings, status = analysisStatus, spansAnalyzer = analyzeSpans, callsAnalyzer = analyzeCalls) {
18
20
  const base = searchBase(mode);
19
21
  const root = typeof req.root === "string" ? path.resolve(req.root) : "";
20
22
  if (base === "" || !withinBase(root, base)) {
@@ -24,20 +26,22 @@ export function handleAnalysisRequest(req, mode, analyzer = analyzeBindings, sta
24
26
  const s = status();
25
27
  return s.available ? { available: true } : { available: false, reason: s.reason };
26
28
  }
27
- if (req.kind === "bindings" || req.kind === "spans") {
29
+ if (req.kind === "bindings" || req.kind === "spans" || req.kind === "calls") {
28
30
  if (typeof req.file !== "string" || req.file === "") {
29
31
  return { error: `ctx.analysis.${req.kind} needs a "file" path` };
30
32
  }
31
33
  const abs = path.resolve(root, req.file);
32
- if (!withinBase(abs, root)) {
34
+ if (!withinDir(abs, root)) {
33
35
  return { error: `ctx.analysis failed: file is outside the search root: ${req.file}` };
34
36
  }
35
37
  if (req.kind === "bindings") {
36
38
  return cachedModel(abs, root, modelCache, analyzer, req.file);
37
39
  }
40
+ if (req.kind === "calls")
41
+ return cachedModel(abs, root, callsCache, callsAnalyzer, req.file);
38
42
  return cachedModel(abs, root, spansCache, spansAnalyzer, req.file);
39
43
  }
40
- return { error: `unknown analysis kind ${JSON.stringify(req.kind)} — known kinds: available, bindings, spans` };
44
+ return { error: `unknown analysis kind ${JSON.stringify(req.kind)} — known kinds: available, bindings, spans, calls` };
41
45
  }
42
46
  // The shared per-file model lifecycle: stat (cache hit on mtime+size),
43
47
  // read, compute, cache. Bindings and spans are the same policy over two
package/bin/analysis.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { AnalysisFile, AnalysisSpans, BindingInfo, BindingRef, SpanInfo } from "./contract.js";
1
+ import { AnalysisFile, AnalysisSpans, AnalysisCalls, BindingInfo, BindingRef, SpanInfo } from "./contract.js";
2
2
  export type { AnalysisFile, AnalysisSpans, BindingInfo, BindingRef, SpanInfo };
3
3
  export interface AnalysisStatus {
4
4
  available: true;
@@ -15,6 +15,13 @@ export type AnalysisResult = {
15
15
  ok: false;
16
16
  error: string;
17
17
  };
18
+ export type CallsResult = {
19
+ ok: true;
20
+ file: AnalysisCalls;
21
+ } | {
22
+ ok: false;
23
+ error: string;
24
+ };
18
25
  export type SpansResult = {
19
26
  ok: true;
20
27
  file: AnalysisSpans;
@@ -24,3 +31,4 @@ export type SpansResult = {
24
31
  };
25
32
  export declare function analyzeSpans(file: string, source: string): SpansResult;
26
33
  export declare function analyzeBindings(file: string, source: string): AnalysisResult;
34
+ export declare function analyzeCalls(file: string, source: string): CallsResult;