DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

Locael123 /

Locael123/dsh-code-impact

Verified

Post-edit impact checks for DeepSeek Harness agents — a symbol index and reference graph that tell you what an edit touched, without re-scanning the repo.

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@32b054a3

dsh-code-impact

Post-edit impact checks for DeepSeek Harness — a symbol index, a reference graph, and an inheritance model that answer "what did this edit touch?" instead of "grep the repo and re-run everything".

DSH plugin engine languages tests

中文文档


Why

Every small edit — a default value, a rename, a deleted method — triggers the same ceremony: search the whole repo, open a pile of files, run the full type-check and the full test suite. The problem isn't wanting full coverage. The problem is doing it by scanning.

An index inverts that:

  • the whole repo's relationships are already in memory, so a "full check" is one query, not a scan;
  • after an edit, only the changed file is re-parsed, and the reference edges tell you exactly which sites must be revisited;
  • answers never say "probably fine" — they give file:line, a confidence, and an explicitly listed unverified part.

What you get

Two tools, registered into every agent session — plus a browsable page of the same index (Settings → Code Impact).

code_query

op Answers
find where is this symbol (addNode, Graph.addNode, ts:src/x.ts#Graph.addNode)
outline declaration skeleton of one file, with line ranges, exports, heritage clauses
refs who mentions this symbol — each site resolved individually, so a same-named local doesn't count
hierarchy extends / implements / subtypes / members (own, inherited, overridden)
search substring match over names and signatures
status index size, root, cache location, engine mode, parse failures
find "parseTimeout" — 1 match

ts:src/config.ts#parseTimeout
  variable  src/config.ts:1-1  exported  refs:3
  parseTimeout = 30 ;
refs ts:src/config.ts#parseTimeout — 3 sites (exported, refs:3)
  variable src/config.ts:1  parseTimeout = 30 ;
  confidence: exact 3 · resolved 0 · heuristic 0 · unresolved mentions 0

src/policy.ts:4  value  in RetryPolicy
    timeout = parseTimeout;
src/service.ts:5  value  in makeService
    const policy = buildPolicy({ timeout: parseTimeout });

code_verify

Call it after editing instead of re-scanning. It re-parses only the files that changed on disk (and discovers new ones), then reports a symbol-level diff:

verify — verdict: internal
  1 file changed (0 deleted) · +0 -0 symbols · 1 body-only

body-only (signature unchanged, callers unaffected):
  ts:src/config.ts#parseTimeout  refs:6
verify — verdict: breaking
  1 file changed (0 deleted) · +1 -1 symbols · 1 contract

symbols that no longer exist:
  ts:src/config.ts#parseTimeout

references that no longer resolve:
  dangling src/policy.ts:4  parseTimeout

verdict has four levels: clean (nothing changed), internal (implementation only), contract (signature/export changed, callers may need edits), breaking (some reference no longer resolves).

A reference is only called dangling when it used to resolve to the symbol that went away. A mention that never resolved in the first place — filepath.Base, a package that is not installed, a name the heuristic merely guessed at — is not reported, because a false alarm costs more than a missed one.

The rule that makes it useful in practice: const timeout = 30 → = 60 is a value change, reported as body-only, never as a contract change. Containers (class / interface / struct) are not listed themselves — their members already express the change, so one memo edit doesn't drag the whole class into the report.

code_verify is incremental: it reports changes since the index last synced. Ask twice without editing in between and the second answer is clean. That's the intended loop — one question after each edit, and the answer is about what you just did.

The index page

The same index is browsable: Settings → Code Impact. Three views over the workspace, drawn from the index rather than from a fresh scan:

view what it answers
Architecture a treemap of directories weighted by symbol count, coloured by language — where the code actually lives
Imports the file-level import graph as a chord diagram; hover a file to isolate its edges
Inheritance the type hierarchy by depth, implements drawn dashed
Sessions which sessions used the plugin, what they asked, what came back

Clicking a file lists its symbols with kind, signature and line; clicking a type shows its supertypes, subtypes and the file that declares it. The header carries the honesty chips — files / symbols / names / languages, when the index was built, and a warning when files were skipped — plus a Refresh button that re-checks the disk before redrawing.

The Sessions tab is the plugin's own audit trail: every code_query / code_verify call is recorded per session (id, working directory, call counts, and the last 40 calls with their target and one-line outcome). It is written to $DSH_HOME/cache/code-index/usage.json, so it survives a restart and covers sessions this process never saw. It records the plugin's own calls only — it is not an authority on what the agent did — and Clear empties it without touching the index.

Open full page puts the same map in a browser tab (/code-impact/app) with more room and no settings chrome behind it; the two views share the same routes and the same index.

The page draws one indexed workspace at a time, and the folder box under the header is how a second one gets in: type an absolute path (D:\work\my-project), press Enter, and the page builds an index for it and switches to it. The same workspace stays in the dropdown afterwards. This is the only way a workspace appears without the agent using a tool in it first — a session whose cwd is the project also indexes it, implicitly, the first time code_query runs.

It is a client half of the bundle (client.js, React, theme tokens only — no iframe, no inline HTML), talking to the host through read routes, one build route, and the usage routes. The standalone page (web/app.html + web/app.js, plain DOM and SVG) is served from the same host:

GET  /code-impact/api/roots
GET  /code-impact/api/overview?root=<abs path>&refresh=1
GET  /code-impact/api/file?root=<abs path>&path=<repo-relative path>
POST /code-impact/api/index          {"root": "<absolute path>"}
GET  /code-impact/api/usage?limit=100
POST /code-impact/api/usage/clear    {}
GET  /code-impact/app                the full-page view
GET  /code-impact/app.js

Reads are allow-listed: the page can only read a directory that is already indexed, or the session's working directory. POST /index is the one route that does work on request — it builds a cache for the exact directory you named and nothing else, refuses relative paths, missing directories and plain files with a 400 carrying the reason, and never writes inside your repository.

Install

dsh-code-impact is a DSH host bundle: zero dependencies, plain ESM, one patch row.

# from GitHub (or any published tarball / registry package)
dsh plugin --profile desktop add github:Locael123/dsh-code-impact
dsh web    # restart: the host half of a plugin is imported once, at boot
# from a local checkout
plugin_manager install_bundle <absolute path>/dsh-code-impact

Manual install — only if the package is already resolvable from the profile's node_modules (plugin_manager does that for you; a bare cordis.yml row alone will not):

- id: dsh-code-impact
  name: dsh-code-impact
  config:
    maxFiles: 20000

The bundle ships its own cordis.patch.yml, so the plugin manager path is preferred — it wires the patch row, links the directory, and keeps files[] correct.

config default meaning
maxFiles 20000 cap on files per build; truncation is reported in status
root session working directory override the indexed root

Cache: $DSH_HOME/cache/code-index/<hash(root)>/ — content-addressed facts/ plus a manifest.json. Editing one file rewrites one fact. It is a cache: delete it any time, it rebuilds; it is never written inside the indexed repo.

Precision contract

Every answer carries its own bounds instead of a confident guess:

  • confidence: exact | resolved | heuristic | ambiguous — exact resolves to a concrete declaration; heuristic is unique-name / nearest-path inference;
  • total / truncated — truncation is stated, not hidden;
  • unverified: — what this particular answer does not cover (cross-language edges, dynamic references, type-level compatibility).

A file that fails to parse never disappears silently: code_query op=status reports warning: N file(s) could not be parsed and are NOT indexed, and code_verify states it in unverified:. If the index is under-counting, you get told.

When in doubt it returns candidates rather than "not found".

Scope & limits

  • Languages: TypeScript / JavaScript / Go (.ts .tsx .mts .cts .js .jsx .mjs .cjs .go).
  • Module resolution: relative paths with extension and /index probing, tsconfig/jsconfig baseUrl + paths, go.mod module prefix with same-package directories.
  • Skipped: node_modules .git dist build out vendor target __pycache__ .venv and friends, files over 512 KiB, binaries.
  • Deliberately approximate: TS implements and Go interface satisfaction are structural / implicit, so they yield a candidate set marked heuristic. This is the only place the tool knowingly guesses.
  • Not modelled: cross-language edges (a Go interface to its implementors, a JSON schema to its consumers), dynamic dispatch, generated code.

How it works

  • Facts, not graphs. Each file stores only what this file says: imports, exports, decls (with heritage and members), refs (name + position + role + lexical scope). There is no target ID on disk. Name → definition is resolved at query time, which is why editing one file invalidates exactly one file.
  • Stable SymbolId: <lang>:<path>#<qualname>, no line numbers. A rename or move changes the ID, and that change is itself the signal.
  • Freshness: a query stats the files in its answer before returning (mtime + size), so a stale hit is impossible for anything it reports. code_verify drives incremental re-indexing; external edits are caught by the same stat check; a structural change bumps a manifest version and rebuilds.
  • No native modules, no LSP, no subprocess. A hand-written lexer plus a scope-aware resolver per language keeps cold start in the hundreds of milliseconds and the whole plugin dependency-free.

Measured on a 42-file mixed TS/Go workspace: cold build 276 ms, 883 symbols; queries 1–4 ms; warm start from manifest 3 ms for a 5-file fixture.

Development

node test/selftest.mjs               # index + tools, 36 assertions
node test/addroot.mjs               # the page's "index a folder" path, including its refusals
node test/usage.mjs                 # the usage ledger: per-session records, persistence, clearing
node test/webcheck.mjs <repo>        # the JSON routes the page calls
node test/clientsmoke.mjs            # client half registers its settings section

The self-test copies test/fixture — a deliberately small TS + Go project with a tsconfig path alias, a class hierarchy, and a two-package Go module — into a temp directory, builds a real index, and runs 36 assertions: body-only edits are not reported as contract changes, a renamed export surfaces its dangling references, a renamed directory does not, new files are picked up, deleted files are reported, Go cross-package references resolve exact, warm start reloads from the manifest, and a file that mentions constructor still parses (that one was a real bug: a plain object used as a delimiter stack hit Object.prototype.constructor).

License

MIT

—/ 5

No ratings yet

Verified DSH bundle

Commit 32b054a3d883

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout