← README · Configuration · Commands
Start with the quickstart. This guide retains operational details, model-assisted workflows, and implementation contracts. PLAN.md remains the architecture and roadmap authority.
- Repository reconnaissance
- TypeScript checker enrichment
- Watcher lifecycle
- Semantic scouting
- Semantic retrieval
- Call-site queries
- Search anchors and expansion
- Confidence tiers
- Scoped dependency indexing
- Storage
- Gateway execution and indexing failures
- Building release packages
jscout scout repository <root> --max-calls N|all is the explicit G13 pass between
the neutral structural index and optional expensive work. It classifies exact
workspace packages, unowned directory areas, and configured TypeScript/JavaScript
projects as runtime, tooling, documentation, test, generated, mixed,
or unknown. The model receives manifests/configuration, aggregate file kinds,
and bounded representative outlines/imports/exports/entities. Ambiguous
documentation/unknown path labels remain hidden. Whole-scope counts expose
only the high-precision artifact surfaces handwritten, test, fixture,
and generated, so the scout can recognize material outside the representative
sample without treating a directory name as semantic truth.
Run the deterministic inspection first:
jscout scout repository /path/to/repo --max-calls all --max-subjects all --warn-subjects 512 --dry-run
jscout scout repository /path/to/repo --max-calls all --max-subjects all --warn-subjects 512
jscout embed /path/to/repo --product # with embedding.provider configured
jscout enrich /path/to/repoThe dry run prints every initial subject, the exact evidence pack and request
size, exact model-policy reuse status, downstream decision, possible children,
and depth/subject/context bounds. It starts the checker inventory and the local
LLM gateway to resolve the same endpoint/model fingerprint execution will use,
but makes no provider generation calls. reusable: true items have
would_call: false and do not consume the reported calls_planned budget.
mixed package/area results subdivide deterministically into immediate child
directories plus a direct-file residual. One command shares --max-calls,
--max-subjects (default all; both limits accept all), --max-depth
(default 3), and
--context-bytes across the entire recursive plan. Reaching a bound leaves the
unresolved subject neutral; it never invents a narrower role.
--warn-subjects N (default 512) reports when the initial or subdivided subject
count exceeds N without truncating it.
Whole-scope artifact counts guide classification but do not constrain the
answer: unknown/possible is always legal, and co-located tests, fixtures, or
generated output do not by themselves make a runtime package mixed.
mixed means that the evidence supports multiple semantic purposes worth
bounded subdivision. Deterministic per-file roles protect auxiliary artifacts
whether or not subdivision is useful.
When a later scout gives a parent scope a definite role, that parent controls
the current projection and suppresses policy from descendants created by an
older mixed result. The descendant rows remain immutable history and can
reactivate if the parent later returns to mixed.
Classifications are immutable durable policy metadata, not graph facts. Their
freshness covers ordered subject membership, manifests/configs, and the bounded
representative evidence actually shown to the model; the global structural
snapshot is audit metadata only. An unrelated reindex does not stale them, an
evidence or membership change in the subject restores neutral fallback, and a
return to an identical branch fingerprint reuses the prior run without another
model call. Only fresh likely classifications affect defaults:
- deterministic file role and scouted scope role remain separate. The derived
effective role protects
test,fixture, andgeneratedfrom a coarse runtime override; runtime may rescue ambiguousdocumentation/unknown, while auxiliary scope roles may demote otherwise production files; - search retains every hit but penalizes auxiliary scopes unless an explicit
--file-rolefilter is supplied; - workflow/card automatic planning excludes auxiliary scopes and permits a fresh runtime decision to override an ambiguous deterministic path role;
embed --productembeds fresh runtime scopes plus unclassified deterministic production/unknown files, while excluding fresh auxiliary scopes;- checker project scheduling follows fresh project purpose but retains an auxiliary project when it is a file's sole owner.
possible, mixed, unknown, stale, and missing classifications are neutral.
Each classification stores the exact cited evidence objects, including the
bounded content shown to the model, so historical citations remain auditable
after the source or deterministic pack changes.
Duplicate valid citations are removed in model order and valid citations after
the first eight are truncated without another model call. Empty or unknown
citations still fail validation, and the scouting report records normalization
counts.
If policy reconciliation cannot read or validate its optional inputs during
index, jscout warns, clears the disposable policy projection, and keeps the
new L1 snapshot available with neutral defaults.
Scout prompt/evidence upgrades can intentionally invalidate earlier
classifications. When historical scope classifications exist but none match
current evidence, repository_overview reports no_current_classifications
and points to jscout scout repository instead of silently omitting the layer.
Diagnostic search JSON retains deterministic file_role and adds the derived
repository_role only when an active reconnaissance policy exists; compact
output presents the effective role without adding a second metadata field.
jscout overview and MCP repository_overview attach a bounded, explicitly
untrusted reconnaissance section whenever current classifications exist. It
prioritizes mixed/unknown/conflicting scopes and includes role counts, effective
file counts, scope identity, confidence, policy, conflicts, a one-line reason,
and citation count. Full explanations, citation IDs, and bounded evidence
excerpts are opt-in: pass the exact returned subject to
--reconnaissance-subject SUBJECT --reconnaissance-detail (MCP:
reconnaissance_subject plus reconnaissance_detail=true). Use
--reconnaissance-limit or MCP reconnaissance_limit to cap the compact
inventory; set the limit to 0 to omit reconnaissance from the response.
jscout enrich <root> resolves every eligible indexed member-call occurrence
by default. The semantic question remains occurrence-specific — which
declaration owns this statically named property on this exact receiver? — but
the protocol sends bounded batches instead of one process round trip per call.
index stays deterministic and Node-free; jscout watch --enrich explicitly
opts into the same machinery after relevant changes. Enrichment never requests
diagnostics and does not replace deterministic name-matched member hubs.
Eligibility defaults to repository/workspace production and unknown-role calls
that still reach property candidates. Tests, fixtures, generated files,
documentation, and exact calls already explained by a direct deterministic
certain/likely edge are excluded. Repeat --file, --package, --member,
or --role to narrow that set. --all broadens other deterministic answers
and includes every synthetic inferred project for audit, but receiver
value-flow answers are never repeated through the checker. By default, a
package with a strict majority of unowned production/unknown-role source is
JS-first and its unowned default-role roots are included. In a TS-first
package, an orphan is
included only when a non-type import path reaches it from a main, exports,
bin, or script target. Tests, fixtures, generated files, and documentation
remain excluded from orphan scopes unless --all is used. Inferred roots
are grouped by nearest package and compatible compiler family, then
deterministically subdivided at a 150-root cap; imported dependencies can
therefore share one TypeScript Program without recreating the former
one-Program-per-file cost. Sharing a Program can expose ambient declarations
loaded through one root to its siblings. In the pinned grouping-only ai-pipe
parity run, 587 occurrences moved from unknown to @types/node declarations
while all 1,412 mapped repository fact payloads remained unchanged; on that
corpus grouping was fact-neutral, not coverage-neutral.
Node ESM scopes use NodeNext module and resolution
semantics. Node CommonJS scopes use the same paired NodeNext mode, which applies
CommonJS semantics from .cjs/.cts or type: commonjs while retaining modern
package exports/imports resolution. JSX scopes retain ESNext plus Bundler
resolution. Those effective options are part of each inferred scope's
configuration fingerprint, so a semantics change
cannot reuse facts produced under the previous family options. Skipped files
remain first-class in chunks, symbols, structural edges, FTS, embeddings, and
retrieval.
--max-occurrences N is the only occurrence-count cap and deliberately creates
partial coverage. Ordering is deterministic and spread across packages and
files within each priority tier; configured projects execute before inferred
projects within the same dirty/clean tier. The package-policy gate runs before
the operator cap, so skipped files cannot consume a capped selection.
--dry-run reports discovered, eligible, selected, omitted, project, and
configuration counts after one full-inventory configuration-only ownership
snapshot and does not construct a TypeScript Program. Its coverage fields
distinguish eligible files and occurrences without configured owners from
occurrences actually skipped by the default package policy.
--full bypasses exact-batch reuse and recomputes every selected project. It is
the manual equivalent of the watcher's periodic carry-free checker drift flush.
The default plan excludes calls whose member name has indexed namesakes only
outside effective-runtime files; --all bypasses that necessary anchorability
gate. Builtin-looking receivers (console, JSON, path, fs, …) are only
scheduled after ordinary receivers within the same structural tier and are
reported in occurrences_deprioritized_builtin_receiver. They are never
excluded: file-local scope and import spelling cannot account for project-wide
ambient declarations, lexical import shadows, or tsconfig path aliases. An
uncapped run therefore remains complete. A plan with no eligible occurrences
is a successful no-op and does not launch the checker sidecar. The sidecar
labels every returned declaration's provenance (repo, types, lib,
vendored, outside); non-repository
declarations skip the anchoring lookup but still count as unmapped, so
confidences are unchanged and the report attributes refusals by provenance in
unmapped_declaration_contexts.
When every returned declaration maps, a closed set of one to three targets is
published at likely; four or more targets, or any unmapped declaration, keeps
the set at possible. Every projected checker edge records candidateCount
for the targets that survive current path and target-fingerprint validation.
Configured projects start with a deterministic purpose classification. Explicit
lint configurations such as tsconfig.eslint.json are removed from a file's
ownership set when a non-tooling project still owns that file; they remain as
fallback owners for otherwise-unowned files. This avoids re-querying an
aggregate lint program without silently dropping its unique coverage.
Fresh likely repository reconnaissance can override that bootstrap purpose;
membership/config fingerprint drift restores the deterministic fallback.
--dry-run reports selected, excluded, and fallback occurrence counts per
affected project. jscout checker doctor prints each project's purpose and the
evidence used to classify it. Generic noEmit configurations are not treated
as tooling without an independent lint signal.
The sidecar prefers the repository's installed typescript; otherwise it uses
the pinned bundled fallback. jscout checker doctor <root> reports that choice,
every discovered owning tsconfig, and configuration-read problems. Query
paths, indexed BLAKE3 hashes, and exact call/receiver/property byte spans are
verified before checker work. A reverse ownership index assigns planned files
once. A file owned by multiple projects is queried in all of them. Conflicting
declarations remain separate possible candidates;
one mapped declaration becomes an occurrence-specific likely edge with
checker provenance. any, error, and unknown receiver types publish no edge.
If another owning project returns unknown, that incomplete answer does not
demote an otherwise clean, agreeing resolution. The edge's detail_json
reports the incomplete owner under unknownProjects, and the enrichment report
lists aggregate unknown_projects. Ambiguity from a resolved answer — multiple
targets or a declaration jscout cannot map — still makes every survivor
possible.
Rust schedules one selected project at a time, configured projects first. Its disposable Node worker constructs one TypeScript Program, resolves batches of at most 128 calls (the protocol accepts at most 512 and enforces 1 MiB request/response frames), rehashes the exact project input manifest without rebuilding the Program, and then exits so its heap is reclaimed before the next project starts.
Results are committed to SQLite staging after every successful batch. The run
key includes the code digest, deterministic plan, TypeScript identity,
and checker protocol. A killed Rust process leaves those rows non-public;
rerunning the same command resumes the missing occurrence/project pairs. A
controlled project failure activates only completed projects, marks the failed
owner in coverage, and forces affected targeted edges to possible; that same
batch remains the resume target. After every project completes, Rust rechecks
the code digest, distinct checker inputs, and mapped target
fingerprints, publishes the complete canonical batch, and drops the superseded
batch. A code-publication race remains staged and publishes nothing.
Each request has a hard deadline (--timeout, default 300 seconds); timeout
kills only the current project worker. Progress names the project and reports
staged occurrences plus Node RSS and heap usage. Worker crashes return the
actual Node error/stack in the command error as well as stderr.
Every checker batch is bound to exactly one code digest. Projection accepts it
only when source_snapshot matches. Manual jscout index preserves or rebinds
an active publication only when the code digest, checker contracts, canonical
checker inputs, module resolution, and on-disk inputs all still validate;
otherwise it clears the checker plane.
tsconfig files are checker invalidation boundaries, not code-digest inputs.
Editing or deleting one can therefore leave the reported code digest unchanged.
In watch mode the boundary event still starts a
generation, and the checker planner's configuration-chain and membership
fingerprints determine which projects must run again; code-digest equality alone
does not imply that checker configuration was unchanged.
jscout watch --enrich performs the cycle: reindex first, then run the same
project-batched, resumable checker pass for the resulting snapshot. Across a
changed snapshot it first considers each fully completed project in the newest
reusable superseded staging source, then falls back project-by-project to the
active publication. An empty newer destination left by a crash cannot displace
that source. Pending, partial, failed, or coverage-incomplete staging rows are
never carried. Configuration chain, membership, checker identity, protocol,
external inputs, and exact occurrence/target fingerprints are all revalidated.
If any owner of a multi-project occurrence cannot carry, all owners are
re-queried. Dirty projects and occurrences run first; fully carried projects
construct no TypeScript Program. Validated copies and predecessor-staging
retirement commit atomically, and neither old source is projected for the new
snapshot.
Source dirty affinity is a watcher backlog rather than generation-local state. It accumulates code paths across supersession, cancellation, retries, and terminal partial publication, and clears only after a non-superseded checker publication succeeds. Documentation changes remain refresh-only. Enrichment reports distinguish exact-batch reuse, durable staging resume, staging resets, unique carried occurrences, project-occurrence carry, and carry sourced from superseded staging versus the active publication.
External checker inputs remain watched for carried projects. An independent
daily-scale deadline schedules enrich --full semantics inside the watcher;
this is separate from the default ten-minute structural reconciliation. If a
source event supersedes that generation, the carry-free requirement follows
the successor instead of being dropped. Plain watch never starts Node and
never projects an old-snapshot batch.
Watch maintains structural state and, when explicitly enabled, checker facts
plus code and semantic vector indexes. It does not generate semantic content:
repository scouting, cards, workflows, summaries, and concepts remain explicit
jscout scout operations. With both optional planes enabled, the phase order is
refresh -> embed(code) -> enrich -> embed(semantic); without --enrich, the
semantic tail follows code embedding immediately. The tail absorbs artifacts
written by prior manual scout or agent-annotation operations and repairs their
semantic vector index from the durable cache. The complete manual enrichment
sequence remains:
jscout index .
jscout enrich .
jscout scout repository . --max-calls all
jscout embed . --product --semanticjscout watch subscribes before its startup pass and begins with the same full
disposable-snapshot refresh as jscout index. A bounded batch containing only
indexed JavaScript/TypeScript source paths then uses incremental extraction: it
walks and hashes the complete shared code-and-document inventory, but parses
and replaces only changed or missing files. Admitted Markdown/MDX changes use
the same incremental refresh without entering checker dirty affinity. Periodic
reconciliation also runs this complete-inventory incremental path, so it still
repairs missed create/delete/ignore transitions without rebuilding unchanged
rows. Startup, more than 256 changed source paths, Git/submodule controls,
source-inventory ignore files,
package/workspace manifests, lockfiles, tsconfig/jsconfig or declaration
inputs, selected dependency/checker inputs, pathless events, and backend errors
use full refresh. Non-boundary directory and uncertain missing-path events use
the same complete-inventory incremental path, because their paths are not the
correctness inventory. Full scope is sticky within a coalesced generation.
Both refresh modes rerun dependency ownership, module resolution, plane-digest calculation, vector occurrence rematerialization, and structural projection as needed. Either mode retains a checker publication only after exact code-digest and input validation. Watch may additionally keep the active publication plus the newest reusable superseded staging source hidden as inputs to the following validated carry pass. A deterministic extraction rejection or non-retryable read failure is reported and excluded; an old row for that path is not served as current. The refresh still succeeds over the indexable corpus. The classified workspace map is built first. Workspace globs are expanded against the filesystem, so a declared package keeps first-party identity even when it contains only excluded build output or gitignored source. Indexed sources are an alias-target preference, not a membership gate; when no indexed source mapping exists, a classified manifest-entry lookup preserves the declared alias. First-party extraction, current-import dependency discovery, and every selected-dependency source read then complete in one rollbackable transaction before the old snapshot publication is invalidated. A retryable acquisition failure therefore leaves the previous snapshot queryable until a complete replacement can commit.
Compatibility note: repositories indexed by the brief source-derived workspace-discovery implementation get a one-time resolution-identity change when source-less members return to the workspace map. In watch, the resulting membership-fingerprint change forces the affected checker projects to run once. The default two-second trailing quiet period coalesces edits; an event received during any phase advances the desired generation and cannot be consumed by the phase already running.
Each phase opens and closes its own database connection with a finite SQLite
busy timeout. Fatal refresh, embedding, and checker errors retry indefinitely
without waiting for another edit, with an exponential delay capped at 30
seconds. Recognized transient read failures such as descriptor exhaustion,
interrupted/network I/O, or stale handles are phase errors: the transaction
rolls back and watch retries instead of publishing a reduced corpus. A path
that disappears or changes between file and directory after inventory is
ordinary checkout churn, not evidence of an atomic-snapshot violation; its old
row is removed and later events or reconciliation converge on the next state.
Checker enrichment may publish a partial batch when some projects fail. A
transient project failure uses the phase retry loop; a partial batch containing
only deterministic project failures completes the generation with
status=partial and those projects are attempted again after the next source
generation or periodic reconciliation. Published partial batches remain
immutable; retryable rows are cloned into inactive staging before another
checker is launched. A checker worker or whole sidecar
process crash/exit is project-terminal: successful project staging is retained
and the crashed project follows that generation/reconciliation recovery path
instead of an uncapped immediate crash loop. Recognized launch, request,
transport, and resource failures remain immediately retryable.
Repository traversal applies the same classifier at subtree granularity:
retryable I/O aborts the phase, while a permanently inaccessible subtree is
reported and excluded without losing accessible siblings. Attached
.gitignore/.ignore errors are surfaced rather than discarded. Selected-
dependency traversal is a phase error because that explicitly requested
package inventory is planned as one bounded unit.
Non-retryable file reads and deterministic extraction failures are rejected
inputs. They do not degrade a refresh or trigger whole-repository retries, and
their path, stage, and error remain visible in every manual index report.
The watcher prints the full details once per distinct rejection set, reports
when the set clears, and retains rejected=N in every refresh summary. The
default ten-minute reconciliation pass naturally attempts those paths again
while also repairing missed notifications. Its interval is measured from
completion of the previous generation, avoiding back-to-back refreshes when a
cycle itself is slow; a nonzero interval must be greater than the debounce
period.
Set --reconcile-seconds 0 only when giving up that bounded recovery is
acceptable; it does not disable phase-error retries.
An external dependency/checker path that cannot be registered with the native filesystem watcher is marked as degraded coverage immediately. Registration is attempted again on later target reconciliation; it does not have a separate retry loop.
Database/WAL/SHM writes are excluded by exact path. For long-running watch,
prefer an external --database path (or ensure the selected database family is
gitignored) so broad staging commands cannot add it. Git HEAD,
.gitmodules, selected dependency roots and locators, and external inputs from
the latest checker batch are watched as additional invalidation boundaries.
Branch switches therefore rebuild the complete current file set. Extraction,
resolution, snapshot calculation, and graph publication commit in one SQLite
transaction. Concurrent WAL readers keep seeing the last committed publication
while the replacement is built; a failed refresh rolls back without exposing a
half-built index. A repository still needs one successful initial index before
read-only queries can start.
jscout scout repository, workflows, cards, summaries, and concepts make
schema-constrained model calls through the bundled pi-ai gateway. Workflow and
concept runs additionally require exhaustive candidate classification.
Generative calls default to
openai-codex:gpt-5.6-terra, which uses the ChatGPT-plan OAuth path; --model
is the explicit per-call override and llm.model is the repository default.
The old JSCOUT_LLM_MODEL remains a warned compatibility fallback. Request
policy is explicit per command: --timeout (default 300 s per request),
--max-calls (a hard
command-level budget), --context-bytes (default 240 000 serialized evidence
bytes, also checked against the selected model's context window),
--reasoning, --service-tier (rejected where the provider API does not
support tiers), and --rebuild (supersede a completed identical run instead
of reusing it). See Configuration for the complete
environment surface.
Without --seed, scouting derives bounded seeds from routes, GraphQL
operations, runtime handlers/producers, lifecycle/job/DI boundaries, and
exported package/application entry files. Export seeding is deliberately
limited to manifest-resolved entries and conventional index, main,
server, app, or entry filenames. Routes/GraphQL/handlers rank ahead of
producers and dispatchers, which rank ahead of DI injection sites. Equal
deterministic candidate fingerprints are collapsed before any call. Automatic
mode requires an explicit --max-calls; completed matching runs are reused
before that budget is spent, and one over-budget boundary is reported and
skipped without blocking smaller boundaries. --dry-run prints the exact
resolved seeds, candidate fingerprints, candidate counts, evidence file
counts, and evidence bytes without starting Node or contacting a model.
jscout scout cards writes one evidence-backed card per selected symbol.
Without --anchor, subjects are the union of exported production symbols,
runtime boundary endpoints, and participants of current published workflows,
deduped by anchor and capped at 1024. Selection is allocated round-robin across
current reconnaissance scopes (or deterministic top-level structural scopes)
before repeating a scope. The plan reports discovered, selected, and omitted
subjects per scope so the cap cannot be mistaken for repository coverage.
--anchor selects subjects explicitly — each resolves uniquely to a symbol,
like a workflow seed, and each becomes its own run. Repeatable --file and
--subject selectors target symbols only inside an exact indexed file or one
current repository_overview reconnaissance subject; they never widen when
the selected surface has no eligible symbol. Automatic and file/subject modes
require --max-calls; anchor-only mode defaults it to the number of anchors.
Evidence is the subject's declaring file plus its deterministic depth-1 edges,
which the prompt forbids restating: a card carries purpose, architectural role,
domain terms, side effects, invariants, and failure modes, never signatures or
call lists. Every individual claim cites its own line ranges in that file and
is published as its own support at likely confidence; an optional field the
model cannot support is omitted rather than guessed, and a claim without exact
evidence fails the run instead of downgrading it. --dry-run prints the
selected subjects, evidence bytes, per-item request bytes, and budget decisions
without starting Node or contacting a model.
jscout scout summaries writes one summary per scope, strictly bottom-up over
already-validated artifacts and never over raw source. Levels are file
(children: current cards and workflows), module (children: the file summaries
of one workspace package), and repository (children: module summaries plus
file summaries no package owns); omitting --level runs all three in that
order under a single --max-calls budget, so a module summary is planned from
the file summaries the same invocation just published. Every claim must cite
the enumerated child references it rests on, and each citation is published as
a summarizes relation pinned to that child's artifact fingerprint. Every
planned child also becomes a whole-summary input dependency, whether cited or
not — uncited prose never validates, and a scope with no current children is
not a summary subject at all. Publication rechecks inside the transaction that
the scope still has exactly the planned child set and that every child remains
current with its pinned fingerprint, so a child added, removed, or replaced
mid-flight refuses the write rather than publishing immediately stale prose.
Freshness then propagates upward: a missing, superseded, or changed child
stales its parent, and a current-but-not-fresh child degrades it, even when the
parent's own text never changed. --scope KEY selects scopes explicitly and
requires --level, since scope keys are level-specific. --dry-run prints the
per-level plans, child counts, and request bytes without starting Node or
contacting a model.
jscout scout concepts writes one concept per exact normalized vocabulary
group. Discovery considers only current fingerprinted workflows whose /name
claim has exact supports and current fingerprinted cards whose string-valued
/domain_terms/N claim has exact supports; other body fields and unsupported
prose are excluded. The versioned normalizer applies Unicode NFKC, Unicode
lowercase, and trimmed/collapsed whitespace while preserving punctuation, so
case and compatibility variants group together but invoice-id and
invoice id do not. The model supplies a repository-specific definition and
cites deterministic child references; it cannot choose the concept identity,
invent aliases, or add children. Published aliases exhaustively reproduce the
observed NFKC/whitespace-normalized display spellings, and the concept copies
none of their spans onto newly generated prose. Instead, claim-level
related_to relations pin each child fingerprint and preserve the drill-down
through that child's exact source supports.
Run concept scouting after the intended workflow/card sweep. The expected vocabulary group is recomputed on every semantic read: publishing another card or workflow with the same normalized term intentionally stales the existing concept until it is refreshed against the settled child set. In mixed refresh runs, jscout enforces this order automatically. All concept planning, including direct and dry-run commands, refuses reuse or model spend while one of the group's children is still non-fresh.
Without --term, all bounded groups are planned and --max-calls is required.
Repeatable --term TEXT selects an existing group through the same normalizer
and defaults the call budget to the number of supplied terms. Oversized groups
are skipped in automatic mode and fail explicitly selected runs rather than
being truncated. Publication atomically rechecks the snapshot, child
fingerprints, exact child set, and concept lineage. Near-duplicate matching and
many-lineage merging are deliberately not implemented: punctuation, fuzzy
similarity, stemming, and embedding proximity never cause an implicit merge.
--dry-run prints the normalized groups, exact aliases, child/support counts,
input bytes, skips, and budget decisions without starting Node or contacting a
model.
Generated workflows record their resolved seeds, traversal limits, service
tier, model, and reasoning policy in the run ledger; cards record their
subject anchor, summaries their level and scope key, and concepts their
normalized vocabulary group the same way. After
indexing exposes source or structural-context drift, jscout scout refresh --max-calls N selects current stale/degraded generated workflows, cards, and
summaries, and concepts and publishes immutable successors. A summary needs no
rule of its own here: child drift already makes it non-fresh, so it selects
naturally and is replanned against the children that are current now; a concept
is replanned from the currently supported exact vocabulary group. Index and
watch never make model calls. Runs created before replay configuration was
stored remain visible but are reported as non-refreshable; jscout does not
guess their original boundary. A stale target whose recorded seed or scope no
longer resolves is reported and skipped without blocking other refreshes.
jscout memory and the structural-profile MCP semantic_memory tool query
persistent memory through a vector-plus-lexical ranking plane separate from
code-chunk ranking. Semantic vectors are content-addressed by the bounded
artifact description and exact support anchors; create or refresh them after
workflow/card/summary/concept generation with
jscout embed <root> --semantic-only after configuring an embedding provider.
Without a
materialized semantic index, retrieval reports vector: degraded and falls
back to lexical matching. --no-vector (MCP: vector=false) requests lexical
matching explicitly.
Memory discovery filters by artifact type, computed freshness, direct relation,
or historical artifact id. Broad calls return compact handles with bounded
support summaries and a copy-safe view=body exact-artifact follow-up. Exact
artifact reads default to a type-aware compact projection: identity, trust,
freshness, a description/primary claim, and defining workflow participants.
view=body returns the complete body plus one compact evidence locator by
default. view=full returns diagnostic provenance, relations, complete selected
supports, hashes, and concept tags. Supplying an exact
anchor, indexed file, or current reconnaissance subject creates a hard evidence
scope: direct anchor support ranks before file support and scope-member support,
and unsupported lexical/vector analogies are not used as filler. An empty
localized result reports no_supported_memory. Bodies, relations, concept
tags, and source evidence are returned only for an exact artifact-id drill-down.
Current artifacts are the default; an exact historical id reports
current: false and its superseded_by successor. The default complete response
budget remains 24 KB.
semantic_search attaches compact, evidence-connected memory previews only
when requested with --memory (MCP: include_memory=true):
artifact identity, a short purpose/overview/description/claim, freshness,
and one evidence locator. An artifact is eligible only when
its support is in a returned hit or enclosing file, within the bounded
likely/certain memory graph path, or directly related to an artifact connected
that way. Text/vector similarity ranks candidates inside those tiers; it cannot
promote an unrelated generic card over direct evidence. memory_depth and
memory_nodes (CLI: --memory-depth, --memory-nodes) are explicit,
widenable bounds. attachment.status: "no_connected_memory" means the broad
semantic candidate pool had no evidence connection to the returned code; use
semantic_memory for unconstrained discovery. Full bodies, relations, and
evidence always belong to semantic_memory.
Annotations preview their required claim field. Normal compact transport omits
successful retrieval stages, candidate-pool sizes, scores, and successful
attachment traversal statistics. Degraded stages, no_connected_memory,
truncation, actionable omission counts, and the follow-up tool remain visible.
The complete diagnostics remain available with debug=true and in telemetry;
rank, lexical-score, and vector-cosine values are diagnostic signals, not
calibrated probabilities.
Compact code hits carry copy-safe symbol anchors without per-hit follow-up
objects. Pair one anchor with the response's top-level snapshot when calling
definition, who_uses, or neighborhood; pass a file hit's path to
file_outline. Exact definition/who_uses anchor mode is mutually exclusive
with their human-authored fuzzy symbol mode and preserves same-named methods
instead of round-tripping through a lossy path:name shorthand.
When an interactive annotate call starts with a healthy semantic vector
index, jscout embeds the new document and incrementally synchronizes that
profile after committing the artifact. A failed refresh does not roll back or
duplicate the durable annotation; the response reports the degraded vector
plane and its repair action. Batch scouts still embed once after publication.
When the selected results include current, fresh concepts, the response also
contains deterministic concept_tags: deduplicated file-level associations
and chunk-level associations for each indexed chunk whose line range overlaps
an exact support reached through the concept's claim-level child relations.
These are derived R2 localization hints, not stored claims and not additional
model output. --concept-tag-limit N (MCP:
concept_tag_limit, default 40, maximum 200) bounds them independently; the
complete response-byte budget removes whole concept tags before source,
relations, or semantic artifacts and reports the omitted count.
Add --source (MCP: include_source=true) to follow the artifact's pinned
outgoing claim-citation relations to leaf supports. Empty-path whole-input
dependencies remain visible in the relation section but are never presented as
claim evidence. Every returned path identifies the intermediate artifact,
relation, and JSON claim pointer; distinct claims that cite the same child stay
distinct. --source-depth bounds relation traversal and the response reports
depth/path truncation and skipped cycles explicitly.
Source resolution uses the indexed file identity—including virtual dependency
paths—then reads disk and compares the current bytes with the indexed hash. A
disk change that has not been indexed returns source_status: "index-stale".
After re-indexing changed source, an older support returns
source_status: "source-stale". Neither case returns a misleading excerpt; an
unavailable file is also explicit. Structural-context drift is reported
separately as support freshness and does not hide hash-verified source bytes.
jscout overview and MCP repository_overview return the deterministic corpus
inventory from one pinned SQLite read snapshot. Generated memory is absent by
default. --semantic/include_semantic=true adds a separately labelled,
untrusted overlay containing only current artifacts whose computed freshness is
fresh; cards require explicit type selection. Whole-response byte budgets
drop overlay artifacts and detailed reconnaissance before deterministic
inventory and report every omission. Generated prose never changes search
scores.
jscout calls answers "where is this option passed to this method?" with
exact AST matching instead of line-based text joins:
jscout calls /path/to/repo insert --arg merge=replace --receiver wave.card --jsonCandidate files come from the index (member-call names plus full-text
argument tokens); matches re-parse those files, so every hit reports the
complete call span — a multiline call owns every line inside it — the
static receiver chain (dbs.wave.card), the matched argument position, and
the innermost enclosing declaration anchor. All --arg KEY[=VALUE] filters
must match top-level literal properties of the same object-literal argument;
--arg-position pins which argument that is. Candidate files are
hash-verified against disk, and drift fails the query instead of answering
from a stale index. The calls matcher itself does not resolve receiver
identity. Use graph queries or enrichment to inspect occurrence-specific
receiver-value-flow/checker edges and property-hub candidates; the matcher
never silently guesses an implementation. The same query is exposed as the
calls MCP tool.
Search returns a code snapshot, retrieval-stage status, and ranked hits.
retrieval.vector is active, disabled, or degraded; degraded means the
requested vector stage failed and the returned ranking is lexical-only. This
status is present in compact CLI/MCP and full diagnostic JSON, so an agent does
not have to infer vector availability from stderr. Every hit includes a
file_role. Structurally eligible JavaScript/TypeScript hits also include a
file_anchor and one or more code-snapshot-scoped anchors projected from the
chunk's overlapping declarations, falling back to the file anchor. Rust lexical
hits omit structural anchors and graph follow-ups. Roles are deterministic:
production, test, fixture, generated, documentation, or unknown.
Use repeatable --file-role flags to restrict primary hits. Chunks remain
retrieval units; they do not become graph identity.
Code search spans JavaScript, TypeScript, and Rust by default. Use repeatable
--format javascript, --format typescript, or --format rust flags to scope
primary retrieval before candidate limits; the MCP semantic_search surface
uses the equivalent plural formats array. The formats share one lexical FTS
corpus, so the filter isolates candidates but not BM25 corpus statistics. Rust
is lexical-only until its named-chunk phase; a Rust-only search does not invoke
the embedding provider for code retrieval, although vector-enabled attached
semantic memory still uses the shared provider. Rust parsing follows the
package edition declared by the nearest visible Cargo.toml, including
workspace-inherited editions.
An explicit package.workspace pointer is authoritative; ancestor workspace
discovery is used only when that key is absent. Malformed or missing explicit
targets are reported and recover to the default. Packages without an edition
and standalone files use Rust 2015. Non-UTF-8 Rust and
deterministic extraction failures are reported and rejected per file without
blocking accessible repository siblings.
Structural expansion is off by default and does not alter search scores. Add
--expand to attach a separately labelled context pack:
jscout search /path/to/repo "checkout inventory" --json --expand \
--response-bytes 24000 \
--expand-mode paths --expand-depth 1 --expand-seeds 3 --expand-paths 8 \
--expand-nodes 40 --expand-edges 120 --expand-bytes 24000Compact expansion defaults to a ranked path forest rooted at the selected hit
anchors. It keeps the shared prefixes needed to reach cross-file symbols,
runtime hubs, handlers, state transitions, and effects instead of returning
every incident edge in the induced neighborhood. --expand-paths bounds the
number of ranked continuation endpoints. Depth one is the same compact one-hop
caller/callee projection. Use --expand-mode neighborhood only when the full
diagnostic neighborhood is actually needed. Both modes report omitted
path/node/edge counts and retain the existing independently widenable limits.
Under identical limits, diagnostic neighborhood reserves the selected compact
path forest before filling the remaining budget with ranked fan-out, so it is a
strict superset of the path projection rather than a competing bounded sample.
Expansion defaults to production and unknown file-backed nodes while
retaining structural hubs. Use repeatable --expand-file-role flags to opt
tests, fixtures, generated files, or documentation back in. Explicitly
included non-production nodes receive deterministic ranking penalties before
the traversal and global node/byte budgets are consumed.
--json and the MCP tools use compact, minified agent transport. It retains
source locations, symbols, snippets, anchors, graph direction, confidence,
provenance, and checker receiver types while omitting occurrence IDs, raw
diagnostic metadata, empty fields, and repeated defaults. Search
--debug-json, neighborhood --debug-json, and MCP debug: true retain the
full diagnostic representation.
Ranked code-search snippets are query-aware source excerpts: at most eight
source lines and 1 KiB (1,024 UTF-8 bytes), with ellipses when byte clipping is
needed.
Exact-tier hits prioritize their matched identifiers; other hits select a
window of literal query matches using the index's FTS tokenizer. A hit with
no literal source match keeps a bounded header excerpt. at still locates
the whole retrieval chunk; optional snippet_line gives the first excerpt
line when it differs from the chunk start. A leading ellipsis can indicate
a mid-line start. The source-byte cap does not replace the complete JSON
response budget, and snippet_truncated continues to indicate additional
response-budget truncation. Use the unchanged anchor with definition for
complete source. Exhaustive search continues to return match lines, not
snippets.
CLI --debug-json is not outer-response-budgeted when --response-bytes is
omitted, so inspecting diagnostics cannot silently remove graph nodes or edges.
Pass --response-bytes explicitly to test diagnostic truncation. Compact CLI
and MCP responses retain their configured complete-response budgets.
Code search defaults to a 30,000-byte complete JSON response ceiling, covering
hits, attached context, metadata, and JSON overhead. Override it in
[search].response_bytes, with CLI --response-bytes, or per MCP
semantic_search call with response_bytes; explicit limits still win.
Documentation search and other tools keep their existing response budgets.
For exact drill-down, construct arguments from a hit's anchor and the
response's top-level snapshot. Preserve any explicit origins and formats
from the originating search request for definition and who_uses;
neighborhood does not accept a format filter. Ambiguous chunks expose
anchors for the caller to choose from, and file-only hits retain their at
locator. Supplying the snapshot makes stale anchors re-resolve by
path/scope/name or fail closed instead of silently binding to a same-named
declaration.
An explicit --response-bytes caps whichever complete JSON representation was requested:
hits, expansion, budget metadata, and serialization overhead. The result
reports its actual rendered_bytes, original unbudgeted_bytes, and omitted
content. Search semantic-memory previews share a global eight-support cap.
When the budget binds, optional memory is shed first, then low-ranked graph
relations and their unused nodes, then lower-ranked code hits; the top code hit
is never silently removed. Expansion admits an edge together with both endpoint
nodes so a node-only context pack cannot consume the relation budget. The
expansion node, edge, and payload limits are subordinate budgets shared across
all search-hit seeds. --expand-min-confidence defaults to likely;
use possible only when explicit unresolved candidates are useful.
Evidence-connected semantic memory is opt-in for CLI and structural-profile
search; use --memory when a preview connected to localized code would help,
then use --memory-limit and
--memory-depth/--memory-nodes to widen its reported structural join bounds.
Every artifact carries evidence supports and a computed fresh, degraded,
or stale label. The complete response-byte limit includes semantic artifacts.
jscout index reparses the current checkout instead of carrying cheap
structural rows across snapshots. It preserves content-hash embedding cache
rows, semantic memory, and immutable repository-reconnaissance history, then
rematerializes current vector occurrences and exact fresh reconnaissance policy
from those durable planes. Checker enrichment is code-digest-bound: manual
index and watch may reuse an exact batch or validate and rebind unchanged facts
into the newly published code digest; invalid checker state is cleared. Run
jscout enrich when no validated occurrence-specific checker edges remain.
jscout watch coordinates full convergence and bounded incremental source
refreshes with optional embedding/checker operations, debounce, retries, and
periodic complete-inventory incremental reconciliation. watch --embed updates the default corpus;
watch --embed --product applies the same fresh reconnaissance policy and
neutral production fallback as jscout embed --product, so it does not widen a
product-only vector cache. Each embedding phase reports missing documents,
newly embedded documents, durable-cache reuse, and current vector occurrences;
a fully cached pass therefore reports reuse rather than embedded=0/0.
watch --embed also repairs and tops up semantic-artifact vectors after the
checker phase. Manual jscout index always remains a full disposable-snapshot
rebuild.
The structural nearest file-form .git indirection, worktree HEAD, and
.gitmodules controls remain watched even when documentation freshness is
disabled. Worktree-index, reference/log, shallow, reftable, and Git conversion
controls are provenance-specific and are subscribed only while freshness
indexing is enabled.
The startup line records the jscout version, executable-byte fingerprint,
loaded non-secret runtime-config fingerprint, whether a config file was loaded,
the documentation-indexing-only reload boundary, checker-policy fingerprint
derived from the actual watcher enrichment selection, and a separate non-secret
fingerprint of the effective watch invocation after CLI overrides alongside the
effective watch flags. These identities make logs from different binaries or
policies comparable without exposing credentials. If the running executable
cannot be read, jscout warns and records binary_fingerprint=unavailable;
diagnostic identity never prevents watch or MCP startup.
Retrieval-only CLI commands and MCP sessions open an existing published index
read-only: they do not create .jscout.db or migrate an old schema. The MCP
server opens a writer lazily only when its annotate tool is selected.
The old per-version migration ladder has been removed. Writer commands accept
the v16+ durable format by preserving embedding/semantic-memory/reconnaissance tables and
recreating all disposable snapshot tables once. Older durable formats are
rejected; preserve such a file before creating a fresh current database.
- certain — resolved through binding analysis + Node module resolution (incl. package.json
exports, tsconfigpaths, barrel/star re-exports, CommonJSrequirewith literals, dynamicimport('...')literals). - likely — bounded occurrence-specific receiver candidate sets from lexical value flow or complete mapped TypeScript-checker declaration sets. Small candidate sets remain explicit and record
candidateCount. - possible — name-matched member calls (
x.getUser()): candidates listed, never silently dropped. This is the honest checker-less answer for calls through type annotations.
When an otherwise-certain reference resolves to multiple same-named root
symbols, the traversal projection emits every candidate at possible
confidence and includes ambiguity details instead of dropping the edge.
Event wiring (emit('x') ↔ on('x')) is surfaced by the events tool/command.
Dependency internals are opt-in and package-scoped. jscout never walks all of
node_modules:
jscout index /path/to/repo --deps zod,lodashThe effective selector list is authoritative: explicit --deps overrides
index.dependencies for index, or the independent watch.dependencies for
watch. Omitting the flag uses that command's configured list; --no-deps
explicitly disables dependencies. Keep both TOML lists consistent, or repeat
the same explicit --deps values for both commands. Dependencies disappear when
the effective list is empty. Discovery starts from real indexed importers and Node resolution,
so distinct installed versions become distinct package instances. An unused
root installation may also be selected by its exact package name. Package
roots and resolved modules are canonicalized before identity is assigned:
pnpm store links deduplicate to one physical instance, while declared
workspace links remain first-party code.
Each package is independently bounded to 10,000 files, 100 MiB total source,
and 2 MiB per file. Hidden directories, nested node_modules, minified names,
and strongly bundled long-line artifacts are skipped; a package entry point is
retained even when it looks bundled so the boundary does not disappear. When
the manifest exposes a source field or source export condition, that source
tree is preferred. Otherwise the active exports/module/main runtime tree
is indexed. Source-map reconstruction and Yarn Plug'n'Play zip archives are not
supported yet; PnP is rejected explicitly instead of being treated as a
missing package.
Indexed dependency files remain invisible to normal retrieval. All read
surfaces default to repository,workspace; include dependency explicitly:
# Dependency internals only
jscout search /path/to/repo "parse object schema" --origin dependency
# Cross the first-party/package boundary in one expansion
jscout search /path/to/repo "validate credentials" --expand --json \
--origin repository,workspace,dependency
# Create vectors for dependency chunks (content-hash dedup still applies)
jscout embed /path/to/repo --origin dependencyThe same origins allowlist is available on MCP search, definition,
who-uses, file-outline, events, and neighborhood calls. Search filtering is
applied before BM25/vector candidate ranking. Without dependency visibility,
the structural graph still exposes a versioned package-instance boundary hub;
indexed modules sit behind that hub and enter traversal only when their origin
is allowed.
Canonical runtime-entity nodes are also file-less boundary hubs. Their source
occurrences and symbol endpoints retain file origin, so an entity identity can
join first- and third-party evidence without making dependency-backed endpoint
code visible unless dependency is included in the caller's origin filter.
Everything lives in one SQLite file, .jscout.db: code and Markdown chunks,
separate code/docs FTS5 corpora, symbols, import/export tables, classified
references, event/member-call sites, provenance-keyed embedding caches,
dimension-specific sqlite-vec indexes, and a disposable
graph_nodes/resolved_edges traversal projection. The projection is rebuilt
after indexing so barrel changes can reroute references in otherwise unchanged
files without leaving stale graph edges behind. Runtime module links use
import/imports_package; requests found only in type bindings use the
documentary imports_types/imports_package_types kinds. File roles live on canonical
file rows and are refreshed even when source hashes are unchanged. Files also
carry repository, workspace, or dependency origin plus optional package
instance/path identity. Package instances record canonical root, name, version,
locator, manifest hash, and completeness status.
Repository Markdown uses the shared files/chunks tables and durable
content-addressed embedding cache, while its documentation digest is independent
of the code digest. docs_fts and dimension-specific docs vector tables keep
its ranking statistics and candidates isolated from code search.
Agent-authored and generated workflow, card, summary, concept, and
annotation records live in separate semantic_artifacts/
semantic_supports tables; they never become structural edges. Workflow
participants are explicitly defining (the minimal stable
cross-file skeleton) or supporting (internal helpers and leaf operations), so
evidence does not flatten every related function into an equal boundary.
Supports store source and direct structural-context fingerprints.
Re-indexing preserves memory and makes source/context drift visible instead of
silently deleting or serving the record as current.
Workflow writes use a direct request: participants is top-level and every
participant carries its own anchor, role, scope, evidence file/span, and
confidence. jscout constructs the stored body and support pointers. Generic
body/supports input is reserved for annotation records. Writers retain
every distinct stable cross-file production stage as a participant; internal
or leaf stages are supporting, not compressed into another participant's
prose role.
See authentication before running model-backed commands, and the configuration reference for all settings.
Scouting model calls are serialized by default. Set
llm.max_concurrency = N to allow at most N independent subjects to wait on
the provider concurrently. jscout launches one local gateway worker per slot,
claims every run in the ledger before dispatch, and then validates and
publishes results in deterministic plan order. Summary and refresh dependency
levels remain barriers: only independent subjects inside the same level
overlap. --max-calls remains the total command budget and is not multiplied
by concurrency. Values must be positive and are not artificially capped; the
operator is responsible for provider rate limits and local process overhead.
Repository reconnaissance overlaps only the current frontier; children of a
mixed scope enter the next wave, in parent plan order.
The gateway owns one visible retry layer: at most two retries with 500 ms then 1,000 ms backoff, only for classified connection, timeout, rate-limit, overload, or capacity failures. Every attempt keeps the exact provider, model, service tier, and billing path. Auth, schema, context-window, quota, credit, and billing failures are terminal; there is no hidden provider/model/tier fallback. The command timeout includes retries and backoff. The first Ctrl-C sends cancellation to every active gateway request; a second Ctrl-C, or an interrupt when no request is active, forces exit status 130.
Normal gateway errors use stable controlled messages. Provider exception text, prompts, tool arguments, and credential values are not written to stderr or the run ledger; common key/token forms are redacted again at the protocol boundary. Do not enable third-party HTTP debug logging in a shell that contains secrets.
Indexing continues past non-retryable file reads, permanent subtree/boundary
failures, and deterministic extraction errors. The final summary reports both
removed=N and rejected=N, followed by every rejected path, its stage
(walk, ignore, workspace-manifest,
workspace-walk, workspace-alias, workspace-canonicalize, rust-edition,
read, or extract), and the underlying error on stderr. watch prints those details
once per distinct rejection set, emits one recovery line when they clear, and
keeps the count in every refresh summary. A recognized transient read error
fails the phase instead, so a reduced corpus is not published; watch retries
indefinitely with an exponential delay capped at 30 seconds.
Build a distributable archive containing the Rust binary, installed Node sidecars, and optional Python inference service sources:
scripts/package-release.sh # host target
scripts/package-release.sh TARGET_TRIPLEThe archive is written under target/release-packages/. Extract it anywhere,
put its directory on PATH, and keep the adjacent gateway/, checker/, and inference/
directories with the binary. Indexing and retrieval do not start Node.
jscout enrich starts the checker for typed member-call resolution;
jscout scout repository starts both the checker inventory and the pi-ai
gateway because configured projects are explicit reconnaissance subjects.
Build the npm publish tree instead — a @jscout/cli wrapper plus one
per-platform binary package, staged under target/npm/:
node scripts/npm-package.mjs # host platform + wrapper
node scripts/npm-package.mjs --target TRIPLEThe wrapper vendors no node_modules: it declares the sidecar dependencies
and lets the installer resolve them. Because the binary lands in a separate
platform package, current_exe() sidecar discovery cannot reach the bundled
gateway/ and checker/, so npm/cli/bin/jscout.mjs supplies their paths
through private bundled-discovery transport before exec. Repository config and
explicit legacy overrides remain authoritative. Publishing is
driven by .github/workflows/release-npm.yml on a vX.Y.Z tag; the tag must
match the Cargo.toml version.
That workflow authenticates by OIDC trusted publishing, which cannot perform a
package's first publish: the trusted publisher is configured per package at
npmjs.com/package/<name>/access, which requires the package to already exist
(npm/cli#8544). Any newly named
package — a new platform target as much as the initial release — is therefore
published once from a workstation:
node scripts/npm-bootstrap-publish.mjs --run-id RUN --dry-run
node scripts/npm-bootstrap-publish.mjs --run-id RUNIt takes the binaries from a workflow run rather than rebuilding them, and refuses to publish unless every platform package the wrapper declares is present at the Cargo.toml version with a binary whose actual architecture matches its target. npm prompts for the one-time password, so no token is created. Provenance begins with the first release published by the workflow.