Change-impact analysis for TypeScriptAnalyse d’impact des changements pour TypeScript
A diff goes in.
A verdict comes out.Un diff entre.
Un verdict sort.
semctx reads the Git diff of a TypeScript repository, finds the code it touches and checks the rules you declared. It answers with one verdict: PASS, WARN or BLOCK. The analysis runs on your machine, with no network call and no LLM.
semctx lit le diff Git d’un dépôt TypeScript, retrouve le code touché et vérifie les règles que vous avez déclarées. Il répond par un seul verdict : PASS, WARN ou BLOCK. L’analyse tourne sur votre machine, sans appel réseau ni LLM.
Try it in your repositoryEssayez-le dans votre dépôt
bunx semctx@0.4.5 setup
# edit some code# modifiez du code
bunx semctx@0.4.5 verify diff
Needs Bun 1.4.0 or newer and a Git repository with at least one commit. Run setup on a clean tree, then edit and verify without re-indexing in between. For a branch, CI or a coding agent, see three ways to start.
Il faut Bun 1.4.0 ou plus récent et un dépôt Git avec au moins un commit. Lancez setup sur un arbre propre, puis modifiez et vérifiez sans réindexer entre les deux. Pour une branche, la CI ou un agent de code, voir trois façons de commencer.
Diagram: the changed files src/cart.ts, src/greeting.ts and src/pricing.ts run as lines into one signal. The added line discountCents in the exported interface CartTotal lights its route through a CartTotal node, and the signal shows WARN, its middle lamp.
Schéma : les fichiers modifiés src/cart.ts, src/greeting.ts et src/pricing.ts forment des voies qui rejoignent un signal. La ligne ajoutée discountCents dans l’interface exportée CartTotal allume sa voie à travers un nœud CartTotal, et le signal affiche WARN, son feu du milieu.
Verdict: WARN range : working tree changed files : 3 impacted nodes: 7 Impacted contracts Exported interface "CartTotal" is a public, compiler-enforced contract. [statically_verified] Recommended tests none Unknowns ? Could not confirm index binding (STALE: ANALYSIS_INPUT_MISMATCH, WORKING_DIFF_MISMATCH); impact is reported without a freshness proof. Findings [WARN ] contract_changed_without_test: exported contract changed without a covering test: CartTotalstderrWARN non-blocking warnings present
semctx verify diff on the repository of the first-use demo, run as above: setup on a clean commit, three edits, then verify. One edit adds a required field to the exported interface CartTotal. No test imports it, so the verdict is WARN. The line under Unknowns is expected for uncommitted edits: the index describes your last commit, not your edits.
Sortie réelle de semctx verify diff sur le dépôt de la démo de premier usage, lancée comme ci-dessus : setup sur un commit propre, trois modifications, puis vérification. L’une ajoute un champ obligatoire à l’interface exportée CartTotal. Aucun test ne l’importe, d’où le verdict WARN. La ligne sous Unknowns est normale pour des modifications non commitées : l’index décrit votre dernier commit, pas vos modifications.
01 · What it touchesCe qu’il touche
It maps your diff to the code it touches.Il relie votre diff au code qu’il touche.
semctx keeps a local index of your TypeScript. For every changed line it finds the declaration the line belongs to and the contracts and rules attached to it. For every changed file it lists the test files that import any of its symbols.
semctx tient un index local de votre TypeScript. Pour chaque ligne modifiée, il retrouve la déclaration qui la contient et les contrats et règles qui s’y rattachent. Pour chaque fichier modifié, il liste les fichiers de test qui importent l’un de ses symboles.
-
SymbolsSymboles
The functions, classes, interfaces, types and enums your changed lines land in, plus the modules that hold them.
Les fonctions, classes, interfaces, types et enums où tombent vos lignes modifiées, plus les modules qui les contiennent.
impacted nodes: 7
-
Exported contractsContrats exportésWARN
Exported interfaces and type aliases that other code compiles against. In this diff,
CartTotalchanged and nothing tests it.Les interfaces et alias de type exportés sur lesquels compile le reste du code. Dans ce diff,
CartTotala changé et rien ne le teste.Exported interface "CartTotal" is a public, compiler-enforced contract. [statically_verified]
-
@invariant rulesRègles @invariant
The rules you write in a JSDoc marker, like the one below. This diff touches none; section 02 shows this one at work.
Les règles que vous écrivez dans un marqueur JSDoc, comme celui ci-dessous. Ce diff n’en touche aucune ; la section 02 montre celle-ci à l’œuvre.
@invariant greeting-non-empty
-
Linked test filesFichiers de test liés
Test files that import any symbol from a changed file. semctx lists them so you know what to run. It never runs them itself.
Les fichiers de test qui importent un symbole d’un fichier modifié. semctx les liste pour que vous sachiez quoi lancer. Il ne les exécute jamais lui-même.
Recommended tests none
02 · Your rulesVos règles
You decide what must not break.Vous décidez de ce qui ne doit pas casser.
Write the rule as a JSDoc marker right above the code, commit it and run semctx index. From then on, if that code changes and no test imports it, the verdict is BLOCK and, by default, the exit code is 3. semctx never guesses what is critical.
Écrivez la règle sous forme de marqueur JSDoc, juste au-dessus du code, commitez-la et lancez semctx index. Dès lors, si ce code change et qu’aucun test ne l’importe, le verdict est BLOCK et, par défaut, le code de sortie vaut 3. semctx ne devine jamais ce qui est critique.
1/**5 * @capability greeting6 * @invariant greeting-non-empty: a greeting must never be an empty string7 */8export function greet(name: string): string {9 return `Hello, ${name}!`;10}
@@ -9 +9 @@ export function greet(name: string): string {- return `Hello, ${name}!`;+ return name.length > 0 ? `Hello, ${name}!` : "";
Verdict: BLOCKlines omittedlignes omisesImpacted invariants ! a greeting must never be an empty string [inferred]lines omittedlignes omisesFindings [BLOCK] invariant_touched_without_test: invariant-constrained code changed without a covering test: greet
examples/consumer-typescript-repo, committed on a branch. To clear the BLOCK, add a test file that imports greet from src/index.ts, commit, run semctx index, then verify again.
Extrait ; les points marquent les lignes omises. Observation historique : ce sont les lignes que semctx 0.4.2 affiche pour cette modification de examples/consumer-typescript-repo, commitée sur une branche. Pour lever le BLOCK, ajoutez un fichier de test qui importe greet depuis src/index.ts, commitez, lancez semctx index, puis vérifiez à nouveau.
- Markers work above a function, a constant holding an arrow function, a class, an interface, a type alias or an enum. On a method, a property or a plain constant they are ignored, so to guard a method, mark its class.Les marqueurs fonctionnent au-dessus d’une fonction, d’une constante qui contient une fonction fléchée, d’une classe, d’une interface, d’un alias de type ou d’un enum. Sur une méthode, une propriété ou une simple constante, ils sont ignorés : pour protéger une méthode, marquez sa classe.
- In the default TypeScript-only flow, a test covers a symbol through a direct import under its declared name. Aliased, namespace, type-only and barrel imports do not count in that flow.Dans le parcours TypeScript seul par défaut, un test couvre un symbole par un import direct qui conserve son nom déclaré. Les imports renommés, par espace de noms, de type seul ou via un barrel ne comptent pas dans ce parcours.
- The opt-in config v2
qualified-static-v1profile also follows canonical renamed imports, modeled namespace members and re-exports. Unknown or unsupported targets do not establish coverage. See the qualified profile and admission contract. These links are static associations; semctx does not run the tests.Le profil optionnelqualified-static-v1, en config v2, suit aussi les imports renommés, les membres d’espaces de noms modélisés et les réexports. Une cible inconnue ou non prise en charge ne prouve pas la couverture. Voir le profil qualifié et le contrat d’admission, en anglais. Ces liens restent statiques ; semctx n’exécute pas les tests. @tag securityon a function, class, interface, type alias or enum (methods and plain constants are ignored), or@tag criticalon an exported interface or type, also turns a missing test into BLOCK.@tag securitysur une fonction, une classe, une interface, un alias de type ou un enum (les méthodes et les simples constantes sont ignorées), ou@tag criticalsur une interface ou un type exporté, transforme aussi un test manquant en BLOCK.
03 · Three verdictsTrois verdicts
Three verdicts, one exit code to gate on.Trois verdicts, un code de sortie pour trancher.
The verdict is the most severe finding in the report. A hook, a CI job or a coding agent can stop on the exit code alone; to tell PASS from WARN, read the verdict line or --format json. The lamps keep their order: BLOCK on top, WARN in the middle, PASS at the bottom.
Le verdict reprend le constat le plus grave du rapport. Un hook, une CI ou un agent de code peut s’arrêter sur le seul code de sortie ; pour distinguer PASS de WARN, lisez la ligne du verdict ou --format json. Les feux gardent leur ordre : BLOCK en haut, WARN au milieu, PASS en bas.
-
BLOCKexit 3
- Code under an
@invariantchanged and no test covers it.Du code sous un@invarianta changé et aucun test ne le couvre.invariant_touched_ without_ test - An exported interface or type tagged
@tag criticalor@tag securitychanged and no test covers it.Une interface ou un type exporté marqué@tag criticalou@tag securitya changé et aucun test ne le couvre.critical_contract_ changed_ without_ test - A
@tag securitysymbol changed and no test covers it.Un symbole@tag securitya changé et aucun test ne le couvre.security_surface_ without_ verification - Built in: the index does not describe the code under review (see section 06).Intégré : l’index ne décrit pas le code examiné (voir la section 06).
index_binding_ stale
- Code under an
-
WARNexit 0
- Another exported interface or type alias changed and no test covers it.Une autre interface ou un autre alias de type exporté a changé et aucun test ne le couvre.
contract_changed_ without_ test - The change touches a document marked contradicted or deprecated.Le changement touche un document marqué contredit ou obsolète.
contradiction_unresolved
- Another exported interface or type alias changed and no test covers it.Une autre interface ou un autre alias de type exporté a changé et aucun test ne le couvre.
-
PASSexit 0
No rule fired. A PASS covers only what the rules check; it does not prove the code is correct.
Aucune règle ne s’est déclenchée. Un PASS ne couvre que ce que vérifient les règles ; il ne prouve pas que le code est correct.
| blockdefaultdéfaut | warn | none | |
|---|---|---|---|
| BLOCK | 3 | 3 | 0 |
| WARN | 0 | 3 | 0 |
| PASS | 0 | 0 | 0 |
--fail-on warn also fails on WARN, --fail-on none never fails. An error, such as a repository with no commit yet, exits 1.
--fail-on warn échoue aussi sur WARN, --fail-on none n’échoue jamais. Une erreur, comme un dépôt encore sans commit, sort en 1.
04 · Where it runsOù il tourne
The same verdict in a terminal, a pull request or an agent.Le même verdict dans un terminal, une pull request ou un agent.
-
CLI
semctx verify diffHuman text, a versioned JSON report (
--format json) or GitHub annotations (--format github). CLI referenceTexte lisible, rapport JSON versionné (--format json) ou annotations GitHub (--format github). Référence CLI -
GitHub Actions
uses: hoklims/semctx/packages/github-action@v0.4.5::warning title=semctx%3A contract_changed_without_test,file=src/cart.ts,line=1::exported contract changed without a covering test: CartTotalIndexes the checked-out commit, checks the pull request range, annotates each finding at the declaration it concerns and fails the job on BLOCK. No secrets, no PR comments. GuideIndexe le commit extrait, vérifie la plage de la pull request, annote chaque constat sur la déclaration concernée et fait échouer le job sur BLOCK. Sans secret, sans commentaire de PR. Guide
-
Claude Code
bunx semctx@0.4.5 installsemctx guarded mode: no verification on record.A plugin with MCP tools and skills. Optional guarded mode:
git commitandgit pushwait until a non-BLOCK verdict is recorded for that exact content. Guarded modeUn plugin avec outils MCP et skills. Mode protégé en option :git commitetgit pushattendent qu’un verdict autre que BLOCK soit enregistré pour ce contenu exact. Mode protégé -
MCP
semctx_verify_changeOther MCP clients can run the server from a source checkout. An agent then checks its own diff before it says it is done. SetupLes autres clients MCP peuvent lancer le serveur depuis une copie du dépôt. Un agent vérifie alors son propre diff avant de dire qu’il a fini. Configuration
05 · Run itLancez-le
Three ways to start.Trois façons de commencer.
The CLI and the plugins need Bun 1.4.0 or newer, and the plugin hooks also need Node. The GitHub Action installs Bun itself. Every path needs a Git repository with at least one commit.
La CLI et les plugins demandent Bun 1.4.0 ou plus récent, et les hooks des plugins demandent aussi Node. L’action GitHub installe Bun elle-même. Dans tous les cas, il faut un dépôt Git avec au moins un commit.
# start from a clean, committed tree# partez d’un arbre propre, déjà commité bunx semctx@0.4.5 setup git switch -c try-semctx # edit your code, then commit# modifiez le code, puis commitez git add -A && git commit -m "my change" bunx semctx@0.4.5 index bunx semctx@0.4.5 verify diff --base main
- This checks the whole branch against its merge base, new files and new tests included.
- Replace
mainwith your default branch. It must exist locally: semctx never fetches. - Run
indexagain after every commit, pull or branch switch (section 06). - The first
setupoften ends withOK ready (analysis partial). On a small repository that is normal.
- Cela vérifie toute la branche depuis sa merge base, nouveaux fichiers et nouveaux tests compris.
- Remplacez
mainpar votre branche par défaut. Elle doit exister en local : semctx ne fait jamais de fetch. - Relancez
indexaprès chaque commit, pull ou changement de branche (section 06). - Le premier
setupse termine souvent parOK ready (analysis partial). Sur un petit dépôt, c’est normal.
# preview, change nothing# aperçu, rien n’est modifié bunx semctx@0.4.5 install --dry-run bunx semctx@0.4.5 install
Claude Code or Codex must already be installed. The command installs or updates the semctx plugin for each one it finds, then runs setup in this repository. With neither, it stops without changing anything and exits 1.
Claude Code ou Codex doit déjà être installé. La commande installe ou met à jour le plugin semctx pour chaque hôte trouvé, puis lance setup dans ce dépôt. Sans aucun des deux, elle s’arrête sans rien modifier et sort en 1.
Running it again is harmless on a clean, committed tree; it runs setup, which re-indexes, so add --skip-setup when you have uncommitted edits. Afterwards, open a new Codex task or run /reload-plugins in Claude Code.
La relancer est sans risque sur une copie propre et commitée ; comme elle lance setup, qui réindexe, ajoutez --skip-setup si vous avez des modifications non commitées. Ensuite, ouvrez une nouvelle tâche Codex ou lancez /reload-plugins dans Claude Code.
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: hoklims/semctx/packages/github-action@v0.4.5
with:
base: ${{ github.event.pull_request.base.sha }}
head: ${{ github.sha }}
fail-on: block
fetch-depth: 0 keeps the merge base local, since semctx never fetches. BLOCK fails the job; WARN fails it only with fail-on: warn. Pin a release tag. Full workflow
fetch-depth: 0 garde la merge base en local, car semctx ne fait jamais de fetch. BLOCK fait échouer le job ; WARN seulement avec fail-on: warn. Épinglez un tag de version. Workflow complet
06 · Keep the index currentGardez l’index à jour
Re-index after every commit.Réindexez après chaque commit.
semctx reads your diff through a local index of your code, so the index has to describe the commit you start from. Three situations cover it.
semctx lit votre diff à travers un index local de votre code : l’index doit donc décrire le commit dont vous partez. Trois situations couvrent l’essentiel.
-
Uncommitted editsModifications non commitées
semctx setup# edit, no re-index# modifiez, sans réindexersemctx verify diff? Could not confirm index binding (STALE: ANALYSIS_INPUT_MISMATCH, WORKING_DIFF_MISMATCH); impact is reported without a freshness proof.The verdict stands, with this note under Unknowns. Do not re-index in between: the index would hold your edited line numbers while the diff counts from your last commit, and a finding can disappear.Le verdict tient, avec cette note sous Unknowns. Ne réindexez pas entre les deux : l’index prendrait vos numéros de ligne modifiés alors que le diff compte depuis votre dernier commit, et un constat peut disparaître.
-
After a commit, pull or branch switchAprès un commit, un pull ou un changement de branche
semctx index[BLOCK] index_binding_staleRun it before the next check. Until you do,
verify diffreturns BLOCK withindex_binding_stale, even for a harmless change.Lancez-le avant la vérification suivante. Tant que vous ne le faites pas,verify diffrend BLOCK avecindex_binding_stale, même pour un changement anodin. -
A branch or a pull requestUne branche ou une pull request
git commitsemctx indexsemctx verify diff --base mainThe most complete check: new files and new tests count, and no freshness note appears. The GitHub Action runs this flow for you.La vérification la plus complète : les nouveaux fichiers et les nouveaux tests comptent, et aucune note de fraîcheur n’apparaît. L’action GitHub suit ce chemin pour vous.
With setup --polyglot (config v2), use the branch flow for files the config selects: an uncommitted check after editing one returns BLOCK. Commit the change, run semctx index, then verify with --base; never re-index uncommitted edits.
Avec setup --polyglot (config v2), passez par la branche pour les fichiers que la config sélectionne : une vérification non commitée après la modification de l’un d’eux rend BLOCK. Commitez la modification, lancez semctx index, puis vérifiez avec --base ; ne réindexez jamais des modifications non commitées.
07 · LimitsLimites
What it can’t see.Ce qu’il ne voit pas.
- It reads your code. It never builds it or runs your tests, so treat the report as a list of what to re-check.Il lit votre code. Il ne le compile jamais et ne lance pas vos tests : traitez le rapport comme une liste de choses à revérifier.
- Without a marker, a behaviour change inside a function gives no finding. In the demo, an off-by-one in a discount helper goes unreported.Sans marqueur, un changement de comportement dans une fonction ne donne aucun constat. Dans la démo, une erreur d’un dans un calcul de remise passe inaperçue.
- It sees what
git diffsees. Untracked files are ignored, and uncommitted checks skip symbols declared in brand-new files. Commit them and use--baseto include them.Il voit ce que voitgit diff. Les fichiers non suivis sont ignorés, et les vérifications non commitées sautent les symboles déclarés dans des fichiers tout nouveaux. Commitez-les et utilisez--basepour les inclure. - Calls it cannot resolve statically are left out, and runtime behaviour such as races is out of reach.Les appels qu’il ne peut pas résoudre statiquement sont laissés de côté, et le comportement à l’exécution, comme les situations de concurrence, reste hors de portée.
- It analyses TypeScript, except declaration files (
.d.ts), which yield no symbols. Markdown and SQL files only count as documents and migrations, limited Python support needssetup --polyglot, and other languages are skipped unless config v2 selects them, in which case changing such a file blocks.Il analyse le TypeScript, sauf les fichiers de déclaration (.d.ts), qui ne produisent aucun symbole. Les fichiers Markdown et SQL ne comptent que comme documents et migrations, la prise en charge limitée de Python demandesetup --polyglot, et les autres langages sont ignorés, sauf si la config v2 les sélectionne : modifier un tel fichier bloque alors.
semctx says the same on its first run, on stderr:
semctx le dit aussi à sa première exécution, sur stderr :
WARN setup succeeded, but incomplete analysis cannot justify negative conclusions; run 'semctx index-health --json' for the exact gates and reasons.
In plain words: an empty report does not prove that nothing was affected.
Autrement dit : un rapport vide ne prouve pas que rien n’a été touché.
Try itEssayez-le
bunx semctx@0.4.5 setup
Then edit, and run bunx semctx@0.4.5 verify diff. Other paths: three ways to start.
Puis modifiez, et lancez bunx semctx@0.4.5 verify diff. Autres chemins : trois façons de commencer.