scip-query 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/CHANGELOG.md +10 -1
  2. package/README.md +15 -1
  3. package/dist/{chunk-U3VMUDXJ.js → chunk-4SCMYKD6.js} +1 -1
  4. package/dist/chunk-YBGC7HTY.js +3 -0
  5. package/dist/cli.js +255 -248
  6. package/dist/{config-types-BrHl3Bge.d.ts → config-types-aCgyLrvE.d.ts} +2 -0
  7. package/dist/{db-_Bdx0E1W.d.ts → db-DwCdT3rv.d.ts} +1 -1
  8. package/dist/{health-BEZ1Rt0S.d.ts → health-nJUidasZ.d.ts} +1 -1
  9. package/dist/index.d.ts +3 -3
  10. package/dist/queries/affected.d.ts +2 -2
  11. package/dist/queries/bottlenecks.d.ts +2 -2
  12. package/dist/queries/by-kind.d.ts +2 -2
  13. package/dist/queries/call-graph.d.ts +2 -2
  14. package/dist/queries/change-surface.d.ts +2 -2
  15. package/dist/queries/cleanup-plan.d.ts +2 -2
  16. package/dist/queries/co-change.d.ts +2 -2
  17. package/dist/queries/code.d.ts +2 -2
  18. package/dist/queries/complexity-hotspots.d.ts +2 -2
  19. package/dist/queries/complexity.d.ts +2 -2
  20. package/dist/queries/convergence.d.ts +2 -2
  21. package/dist/queries/coupling.d.ts +2 -2
  22. package/dist/queries/cycles.d.ts +2 -2
  23. package/dist/queries/dataflow.d.ts +2 -2
  24. package/dist/queries/dead.d.ts +2 -2
  25. package/dist/queries/decorative-checkers.d.ts +2 -2
  26. package/dist/queries/deep-chains.d.ts +2 -2
  27. package/dist/queries/deps.d.ts +2 -2
  28. package/dist/queries/diff-gate.d.ts +2 -2
  29. package/dist/queries/diff-gate.js +1 -1
  30. package/dist/queries/diff-impact.d.ts +2 -2
  31. package/dist/queries/doc-drift.d.ts +2 -2
  32. package/dist/queries/drift.d.ts +2 -2
  33. package/dist/queries/duplicate-bodies.d.ts +2 -2
  34. package/dist/queries/extract-candidates.d.ts +2 -2
  35. package/dist/queries/fan.d.ts +2 -2
  36. package/dist/queries/files.d.ts +2 -2
  37. package/dist/queries/health.d.ts +3 -3
  38. package/dist/queries/health.js +1 -1
  39. package/dist/queries/hierarchy.d.ts +2 -2
  40. package/dist/queries/hotspots.d.ts +2 -2
  41. package/dist/queries/imports.d.ts +2 -2
  42. package/dist/queries/incomplete-migration.d.ts +2 -2
  43. package/dist/queries/index.d.ts +3 -3
  44. package/dist/queries/index.js +1 -1
  45. package/dist/queries/isolated.d.ts +2 -2
  46. package/dist/queries/locality-candidates.d.ts +2 -2
  47. package/dist/queries/members.d.ts +2 -2
  48. package/dist/queries/methods.d.ts +2 -2
  49. package/dist/queries/not-implemented.d.ts +2 -2
  50. package/dist/queries/outline.d.ts +2 -2
  51. package/dist/queries/passthrough-candidates.d.ts +2 -2
  52. package/dist/queries/plan-context.d.ts +2 -2
  53. package/dist/queries/react-component-duplicates.d.ts +2 -2
  54. package/dist/queries/react-hook-candidates.d.ts +2 -2
  55. package/dist/queries/react-large-component-pressure.d.ts +2 -2
  56. package/dist/queries/recent-duplicates.d.ts +2 -2
  57. package/dist/queries/redundant-reexports.d.ts +2 -2
  58. package/dist/queries/refs.d.ts +2 -2
  59. package/dist/queries/self-audit.d.ts +2 -2
  60. package/dist/queries/similar-chains.d.ts +2 -2
  61. package/dist/queries/similar-files.d.ts +2 -2
  62. package/dist/queries/similar-signatures.d.ts +2 -2
  63. package/dist/queries/similar.d.ts +2 -2
  64. package/dist/queries/slice.d.ts +2 -2
  65. package/dist/queries/stale-abstractions.d.ts +2 -2
  66. package/dist/queries/stats.d.ts +2 -2
  67. package/dist/queries/surface.d.ts +2 -2
  68. package/dist/queries/symbols.d.ts +2 -2
  69. package/dist/queries/system.d.ts +2 -2
  70. package/dist/queries/test-quality.d.ts +2 -2
  71. package/dist/queries/trace.d.ts +2 -2
  72. package/dist/queries/twin-ab.d.ts +2 -2
  73. package/dist/queries/twin-drift.d.ts +2 -2
  74. package/dist/queries/unused-imports.d.ts +2 -2
  75. package/dist/queries/unused-params.d.ts +2 -2
  76. package/dist/queries/vue-component-duplicates.d.ts +2 -2
  77. package/dist/queries/vue-composable-candidates.d.ts +2 -2
  78. package/dist/queries/vue-large-view-pressure.d.ts +2 -2
  79. package/dist/queries/wrapper-candidates.d.ts +2 -2
  80. package/dist/reindex.d.ts +3 -3
  81. package/dist/runtime.d.ts +3 -3
  82. package/dist/runtime.js +2 -2
  83. package/dist/{scip-cli-C7cg4ZHR.d.ts → scip-cli-DeAbuJ2b.d.ts} +1 -1
  84. package/docs/COMMAND_REFERENCE.md +2 -1
  85. package/package.json +1 -1
  86. package/skills/_shared/SKILL.md +3 -1
  87. package/skills/scip-calibrate/SKILL.md +7 -3
  88. package/skills/scip-verify/SKILL.md +6 -1
  89. package/dist/chunk-XC2PJOVM.js +0 -3
@@ -1,6 +1,6 @@
1
- import { S as ScipDatabase } from '../db-_Bdx0E1W.js';
1
+ import { S as ScipDatabase } from '../db-DwCdT3rv.js';
2
2
  import 'better-sqlite3';
3
- import '../config-types-BrHl3Bge.js';
3
+ import '../config-types-aCgyLrvE.js';
4
4
 
5
5
  type AuditQuestion = 'references' | 'callees';
6
6
  type AuditOracleKind = 'semantic' | 'source';
@@ -1,6 +1,6 @@
1
- import { S as ScipDatabase } from '../db-_Bdx0E1W.js';
1
+ import { S as ScipDatabase } from '../db-DwCdT3rv.js';
2
2
  import 'better-sqlite3';
3
- import '../config-types-BrHl3Bge.js';
3
+ import '../config-types-aCgyLrvE.js';
4
4
 
5
5
  interface SimilarChainResult {
6
6
  chainA: string[];
@@ -1,6 +1,6 @@
1
- import { S as ScipDatabase } from '../db-_Bdx0E1W.js';
1
+ import { S as ScipDatabase } from '../db-DwCdT3rv.js';
2
2
  import 'better-sqlite3';
3
- import '../config-types-BrHl3Bge.js';
3
+ import '../config-types-aCgyLrvE.js';
4
4
 
5
5
  interface SimilarFileResult {
6
6
  fileA: string;
@@ -1,6 +1,6 @@
1
- import { S as ScipDatabase } from '../db-_Bdx0E1W.js';
1
+ import { S as ScipDatabase } from '../db-DwCdT3rv.js';
2
2
  import 'better-sqlite3';
3
- import '../config-types-BrHl3Bge.js';
3
+ import '../config-types-aCgyLrvE.js';
4
4
 
5
5
  type SignatureLocBand = '<=5' | '6-20' | '>20';
6
6
  type SimilarSignatureFunction = {
@@ -1,6 +1,6 @@
1
- import { S as ScipDatabase } from '../db-_Bdx0E1W.js';
1
+ import { S as ScipDatabase } from '../db-DwCdT3rv.js';
2
2
  import 'better-sqlite3';
3
- import '../config-types-BrHl3Bge.js';
3
+ import '../config-types-aCgyLrvE.js';
4
4
 
5
5
  interface SimilarSymbolResult {
6
6
  symbolA: string;
@@ -1,6 +1,6 @@
1
- import { S as ScipDatabase } from '../db-_Bdx0E1W.js';
1
+ import { S as ScipDatabase } from '../db-DwCdT3rv.js';
2
2
  import 'better-sqlite3';
3
- import '../config-types-BrHl3Bge.js';
3
+ import '../config-types-aCgyLrvE.js';
4
4
 
5
5
  interface SliceResult {
6
6
  symbol: string;
@@ -1,6 +1,6 @@
1
- import { S as ScipDatabase } from '../db-_Bdx0E1W.js';
1
+ import { S as ScipDatabase } from '../db-DwCdT3rv.js';
2
2
  import 'better-sqlite3';
3
- import '../config-types-BrHl3Bge.js';
3
+ import '../config-types-aCgyLrvE.js';
4
4
 
5
5
  type StaleAbstractionActionTier = 'direct' | 'signal';
6
6
  type StalenessKind = 'unused-abstraction' | 'misplaced-single-consumer-type' | 'single-consumer-abstraction' | 'one-to-one-class-encapsulation';
@@ -1,6 +1,6 @@
1
- import { S as ScipDatabase } from '../db-_Bdx0E1W.js';
1
+ import { S as ScipDatabase } from '../db-DwCdT3rv.js';
2
2
  import 'better-sqlite3';
3
- import '../config-types-BrHl3Bge.js';
3
+ import '../config-types-aCgyLrvE.js';
4
4
 
5
5
  interface StatsResult {
6
6
  documents: number;
@@ -1,6 +1,6 @@
1
- import { S as ScipDatabase } from '../db-_Bdx0E1W.js';
1
+ import { S as ScipDatabase } from '../db-DwCdT3rv.js';
2
2
  import 'better-sqlite3';
3
- import '../config-types-BrHl3Bge.js';
3
+ import '../config-types-aCgyLrvE.js';
4
4
 
5
5
  interface SurfaceResult {
6
6
  consumer: string;
@@ -1,6 +1,6 @@
1
- import { S as ScipDatabase } from '../db-_Bdx0E1W.js';
1
+ import { S as ScipDatabase } from '../db-DwCdT3rv.js';
2
2
  import 'better-sqlite3';
3
- import '../config-types-BrHl3Bge.js';
3
+ import '../config-types-aCgyLrvE.js';
4
4
 
5
5
  interface SymbolResult {
6
6
  startLine: number;
@@ -1,7 +1,7 @@
1
- import { S as ScipDatabase } from '../db-_Bdx0E1W.js';
1
+ import { S as ScipDatabase } from '../db-DwCdT3rv.js';
2
2
  import { SymbolResult } from './symbols.js';
3
3
  import 'better-sqlite3';
4
- import '../config-types-BrHl3Bge.js';
4
+ import '../config-types-aCgyLrvE.js';
5
5
 
6
6
  interface SystemResult {
7
7
  files: string[];
@@ -1,6 +1,6 @@
1
- import { S as ScipDatabase } from '../db-_Bdx0E1W.js';
1
+ import { S as ScipDatabase } from '../db-DwCdT3rv.js';
2
2
  import 'better-sqlite3';
3
- import '../config-types-BrHl3Bge.js';
3
+ import '../config-types-aCgyLrvE.js';
4
4
 
5
5
  /**
6
6
  * test-quality (D3): the biggest uncovered surface — test files are not
@@ -1,6 +1,6 @@
1
- import { S as ScipDatabase } from '../db-_Bdx0E1W.js';
1
+ import { S as ScipDatabase } from '../db-DwCdT3rv.js';
2
2
  import 'better-sqlite3';
3
- import '../config-types-BrHl3Bge.js';
3
+ import '../config-types-aCgyLrvE.js';
4
4
 
5
5
  interface TraceResult {
6
6
  definitions: Array<{
@@ -1,6 +1,6 @@
1
- import { S as ScipDatabase } from '../db-_Bdx0E1W.js';
1
+ import { S as ScipDatabase } from '../db-DwCdT3rv.js';
2
2
  import 'better-sqlite3';
3
- import '../config-types-BrHl3Bge.js';
3
+ import '../config-types-aCgyLrvE.js';
4
4
 
5
5
  /**
6
6
  * twin-ab (Q4): mechanical support for scip-integrity-audit drill 5
@@ -1,6 +1,6 @@
1
- import { S as ScipDatabase } from '../db-_Bdx0E1W.js';
1
+ import { S as ScipDatabase } from '../db-DwCdT3rv.js';
2
2
  import 'better-sqlite3';
3
- import '../config-types-BrHl3Bge.js';
3
+ import '../config-types-aCgyLrvE.js';
4
4
 
5
5
  /**
6
6
  * Twin-drift: same leaf name (or a near-name variant like escapeRegex vs
@@ -1,4 +1,4 @@
1
- import '../db-_Bdx0E1W.js';
1
+ import '../db-DwCdT3rv.js';
2
2
  export { ImportResult, UnusedImportResult, importedBy, imports, unusedImports } from './imports.js';
3
3
  import 'better-sqlite3';
4
- import '../config-types-BrHl3Bge.js';
4
+ import '../config-types-aCgyLrvE.js';
@@ -1,6 +1,6 @@
1
- import { S as ScipDatabase } from '../db-_Bdx0E1W.js';
1
+ import { S as ScipDatabase } from '../db-DwCdT3rv.js';
2
2
  import 'better-sqlite3';
3
- import '../config-types-BrHl3Bge.js';
3
+ import '../config-types-aCgyLrvE.js';
4
4
 
5
5
  interface UnusedParamsFinding {
6
6
  symbol: string;
@@ -1,7 +1,7 @@
1
- import { S as ScipDatabase } from '../db-_Bdx0E1W.js';
1
+ import { S as ScipDatabase } from '../db-DwCdT3rv.js';
2
2
  import { F as FrontendBehaviorEvidenceClass, a as FrontendBehaviorActionTier } from '../frontend-behavior-evidence-EfM4_9bc.js';
3
3
  import 'better-sqlite3';
4
- import '../config-types-BrHl3Bge.js';
4
+ import '../config-types-aCgyLrvE.js';
5
5
 
6
6
  interface VueComponentDuplicateResult {
7
7
  fileA: string;
@@ -1,7 +1,7 @@
1
- import { S as ScipDatabase } from '../db-_Bdx0E1W.js';
1
+ import { S as ScipDatabase } from '../db-DwCdT3rv.js';
2
2
  import { a as FrontendBehaviorActionTier, F as FrontendBehaviorEvidenceClass } from '../frontend-behavior-evidence-EfM4_9bc.js';
3
3
  import 'better-sqlite3';
4
- import '../config-types-BrHl3Bge.js';
4
+ import '../config-types-aCgyLrvE.js';
5
5
 
6
6
  type VueComposableEvidenceClass = FrontendBehaviorEvidenceClass;
7
7
  type VueComposableActionTier = FrontendBehaviorActionTier;
@@ -1,6 +1,6 @@
1
- import { S as ScipDatabase } from '../db-_Bdx0E1W.js';
1
+ import { S as ScipDatabase } from '../db-DwCdT3rv.js';
2
2
  import 'better-sqlite3';
3
- import '../config-types-BrHl3Bge.js';
3
+ import '../config-types-aCgyLrvE.js';
4
4
 
5
5
  type VueLargeViewPressureAxis = 'template' | 'script' | 'style' | 'external-script' | 'custom-block' | 'total';
6
6
  type VueLargeViewContextKind = 'component' | 'route-page';
@@ -1,6 +1,6 @@
1
- import { S as ScipDatabase } from '../db-_Bdx0E1W.js';
1
+ import { S as ScipDatabase } from '../db-DwCdT3rv.js';
2
2
  import 'better-sqlite3';
3
- import '../config-types-BrHl3Bge.js';
3
+ import '../config-types-aCgyLrvE.js';
4
4
 
5
5
  type WrapperActionTier = 'direct' | 'signal';
6
6
  interface WrapperCandidate {
package/dist/reindex.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { a as SupportedLanguage, I as IndexerConfig, T as TypeScriptProjectMode } from './config-types-BrHl3Bge.js';
2
- import { R as RefreshTrigger, L as LastRefreshMetadata } from './scip-cli-C7cg4ZHR.js';
3
- export { t as tryInstallScipCli } from './scip-cli-C7cg4ZHR.js';
1
+ import { a as SupportedLanguage, I as IndexerConfig, T as TypeScriptProjectMode } from './config-types-aCgyLrvE.js';
2
+ import { R as RefreshTrigger, L as LastRefreshMetadata } from './scip-cli-DeAbuJ2b.js';
3
+ export { t as tryInstallScipCli } from './scip-cli-DeAbuJ2b.js';
4
4
  import { Index } from '@c4312/scip';
5
5
 
6
6
  /**
package/dist/runtime.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { P as ProjectConfig, a as SupportedLanguage } from './config-types-BrHl3Bge.js';
2
- import { W as WatcherStatus } from './scip-cli-C7cg4ZHR.js';
3
- export { g as getScipVersion, i as isScipInstalled, p as printScipInstallInstructions } from './scip-cli-C7cg4ZHR.js';
1
+ import { P as ProjectConfig, a as SupportedLanguage } from './config-types-aCgyLrvE.js';
2
+ import { W as WatcherStatus } from './scip-cli-DeAbuJ2b.js';
3
+ export { g as getScipVersion, i as isScipInstalled, p as printScipInstallInstructions } from './scip-cli-DeAbuJ2b.js';
4
4
 
5
5
  /**
6
6
  * Load project config from .scipquery.json in the project root.
package/dist/runtime.js CHANGED
@@ -1,3 +1,3 @@
1
- import{c as $,d as M,e as A}from"./chunk-WIBFXSYB.js";import{a as k}from"./chunk-YZXV3CU3.js";import{readFileSync as O,writeFileSync as D,existsSync as P,mkdirSync as N}from"fs";import{isAbsolute as de,join as u,relative as he,resolve as R}from"path";import{createHash as G}from"crypto";import{homedir as F}from"os";var f=".scipquery.json",W={enabled:!1,debounceMs:3e4,cooldownMs:6e4,gitPollMs:2e3,autoRefresh:!0,ignore:[]},U=["typescript","javascript","java","scala","kotlin","rust","python","ruby","go","cpp","c","csharp","vb","dart","php","clojure"];var me=new Set(U);function y(r){let e=u(r,f);if(!P(e))return{};try{let t=O(e,"utf-8");return JSON.parse(t)}catch(t){let i=t instanceof Error?t.message:String(t);throw t instanceof SyntaxError?new Error(`invalid ${f} at ${e}: ${i}`,{cause:t}):new Error(`unable to read ${f} at ${e}: ${i}`,{cause:t})}}function E(r){return{...W,...r.watch}}function L(r,e){let t=process.env.SCIP_QUERY_CACHE_DIR;if(t)return m(t);if(e?.dbPath)return m(R(r,e.dbPath));let s=process.env.XDG_CACHE_HOME||u(F(),".cache"),o=G("sha256").update(R(r)).digest("hex").slice(0,12),n=u(s,"scip-query","projects",o);return m(n)}function S(r,e){let t=L(r,e);return{cacheDir:t,dbPath:u(t,"index.db"),indexPath:u(t,"index.scip"),metaPath:u(t,"meta.json")}}function q(r,e){let t=u(r,f);return P(t)||D(t,JSON.stringify({languages:e,watch:{enabled:!1,debounceMs:3e4,cooldownMs:6e4,gitPollMs:2e3,autoRefresh:!0}},null,2)+`
2
- `),t}function m(r){return N(r,{recursive:!0}),r}import{execFileSync as Y,fork as K}from"child_process";import{statSync as H,watch as X}from"fs";import{join as B,relative as V}from"path";import z from"ignore";var C=class{projectRoot;config;watchConfig;languages;pnpmWorkspaces;typescriptProjectMode;typescriptProjects;clojureConfigPath;indexerConcurrency;onStatus;onReindexComplete;onError;status={state:"idle"};debounceTimer=null;cooldownTimer=null;dirty=!1;changedFiles=0;pendingTrigger=null;reindexInFlight=!1;lastReindexEnd=0;fsWatchers=[];gitPollTimer=null;lastGitState=null;gitignoreFilter;extraIgnore;stopped=!1;constructor(e){this.projectRoot=e.projectRoot,this.config=e.config,this.watchConfig=E(e.config),this.languages=e.languages,this.pnpmWorkspaces=e.config.indexer?.typescript?.pnpmWorkspaces??!1,this.typescriptProjectMode=e.config.indexer?.typescript?.projectMode,this.typescriptProjects=e.config.indexer?.typescript?.projects,this.clojureConfigPath=e.config.indexer?.clojure?.configPath,this.indexerConcurrency=e.config.indexerConcurrency,this.onStatus=e.onStatus??(()=>{}),this.onReindexComplete=e.onReindexComplete??(()=>{}),this.onError=e.onError??(t=>console.error(t.message)),this.gitignoreFilter=k(e.projectRoot),this.extraIgnore=z(),this.watchConfig.ignore.length>0&&this.extraIgnore.add(this.watchConfig.ignore)}start(){this.stopped=!1,this.setStatus({state:"idle"}),this.startGitStatePolling();try{let e=X(this.projectRoot,{recursive:!0},(t,i)=>{i&&!this.stopped&&this.handleFileChange(i)});this.fsWatchers.push(e)}catch{this.onError(new Error("Failed to start file watcher. On Linux, you may need to increase inotify limits: sysctl -w fs.inotify.max_user_watches=524288"))}}stop(){this.stopped=!0;for(let e of this.fsWatchers)e.close();this.fsWatchers=[],this.clearDebounceTimer(),this.clearCooldownTimer(),this.clearGitPollTimer(),this.setStatus({state:"idle"})}handleFileChange(e){let t=V(this.projectRoot,B(this.projectRoot,e));t===".git"||t.startsWith(".git/")||this.gitignoreFilter.isIgnored(t)||this.extraIgnore.ignores(t)||e.endsWith("index.db")||e.endsWith("index.scip")||e.endsWith("index.db.tmp")||this.scheduleReindex({kind:"watch-source",detail:t})}scheduleReindex(e){if(this.pendingTrigger=J(this.pendingTrigger,e),this.changedFiles++,this.reindexInFlight){this.dirty=!0,this.setStatus({state:"indexing",startedAt:this.status.startedAt});return}if(this.status.state==="cooldown"){this.dirty=!0,this.setStatus({state:"cooldown",until:this.status.until,dirty:!0});return}this.clearDebounceTimer();let t=Date.now()+this.watchConfig.debounceMs;this.setStatus({state:"waiting",changedFiles:this.changedFiles,reindexAt:t}),this.debounceTimer=setTimeout(()=>{this.debounceTimer=null,this.triggerReindex()},this.watchConfig.debounceMs)}triggerReindex(){if(this.reindexInFlight||this.stopped)return;let e=Date.now()-this.lastReindexEnd;if(this.lastReindexEnd>0&&e<this.watchConfig.cooldownMs){let s=this.watchConfig.cooldownMs-e;this.dirty=!0;let o=Date.now()+s;this.setStatus({state:"cooldown",until:o,dirty:!0}),this.cooldownTimer=setTimeout(()=>{this.cooldownTimer=null,this.dirty&&!this.stopped&&(this.dirty=!1,this.triggerReindex())},s);return}this.reindexInFlight=!0,this.dirty=!1,this.changedFiles=0;let t=this.pendingTrigger??{kind:"watch-source"};this.pendingTrigger=null;let i=Date.now();this.setStatus({state:"indexing",startedAt:i}),this.runReindex(t).then(s=>{if(this.reindexInFlight=!1,this.lastReindexEnd=Date.now(),this.onReindexComplete(s),this.dirty&&!this.stopped){let o=Date.now()+this.watchConfig.cooldownMs;this.setStatus({state:"cooldown",until:o,dirty:!0}),this.cooldownTimer=setTimeout(()=>{this.cooldownTimer=null,this.dirty&&!this.stopped?(this.dirty=!1,this.triggerReindex()):this.setStatus({state:"idle"})},this.watchConfig.cooldownMs)}else this.setStatus({state:"idle"})}).catch(s=>{this.reindexInFlight=!1,this.lastReindexEnd=Date.now(),this.onError(s instanceof Error?s:new Error(String(s))),this.setStatus({state:"idle"})})}runReindex(e){return new Promise((t,i)=>{let s=Date.now(),o=y(this.projectRoot),n=Object.keys(o).length>0?o:this.config,c=S(this.projectRoot,n),a=n.indexer?.typescript,l=n.indexer?.clojure,d=K(new URL("./reindex-worker.js",import.meta.url).pathname,[],{detached:!0,env:{...process.env,SCIP_REINDEX_PROJECT_ROOT:this.projectRoot,SCIP_REINDEX_OUTPUT_SCIP:c.indexPath,SCIP_REINDEX_OUTPUT_DB:c.dbPath,SCIP_REINDEX_LANGUAGES:(n.languages??this.languages)?.join(",")??"",SCIP_REINDEX_INDEXER_CONCURRENCY:String(n.indexerConcurrency??this.indexerConcurrency??""),SCIP_REINDEX_PNPM_WORKSPACES:a?.pnpmWorkspaces??this.pnpmWorkspaces?"1":"",SCIP_REINDEX_TYPESCRIPT_CONFIG:JSON.stringify({projectMode:a?.projectMode??this.typescriptProjectMode,projects:a?.projects??this.typescriptProjects??[]}),SCIP_REINDEX_CLOJURE_CONFIG_PATH:l?.configPath??this.clojureConfigPath??"",SCIP_REINDEX_TRIGGER_KIND:e.kind,SCIP_REINDEX_TRIGGER_DETAIL:e.detail??""},stdio:"pipe"});d.on("exit",p=>{p===0?t(Date.now()-s):i(new Error(`Reindex worker exited with code ${p}`))}),d.on("error",i)})}setStatus(e){this.status=e,this.onStatus(e)}clearDebounceTimer(){this.debounceTimer&&(clearTimeout(this.debounceTimer),this.debounceTimer=null)}clearCooldownTimer(){this.cooldownTimer&&(clearTimeout(this.cooldownTimer),this.cooldownTimer=null)}startGitStatePolling(){this.lastGitState=this.readGitState(),this.lastGitState&&(this.gitPollTimer=setInterval(()=>this.pollGitState(),this.watchConfig.gitPollMs),this.gitPollTimer.unref?.())}pollGitState(){let e=this.lastGitState,t=this.readGitState();if(!e||!t||this.stopped){this.lastGitState=t;return}this.lastGitState=t;let i=e.head!==t.head,s=e.indexPath!==t.indexPath||e.indexMtimeMs!==t.indexMtimeMs||e.indexSize!==t.indexSize;i&&s?this.scheduleReindex({kind:"watch-git-state",detail:"HEAD and index changed"}):i?this.scheduleReindex({kind:"watch-git-head",detail:"HEAD changed"}):s&&this.scheduleReindex({kind:"watch-git-index",detail:t.indexPath??"index changed"})}readGitState(){let e=j(this.projectRoot,["rev-parse","--git-path","index"]);if(!e)return null;let t={head:j(this.projectRoot,["rev-parse","--verify","HEAD"]),indexPath:e};try{let i=H(e);t.indexMtimeMs=i.mtimeMs,t.indexSize=i.size}catch{t.indexMtimeMs=void 0,t.indexSize=void 0}return t}clearGitPollTimer(){this.gitPollTimer&&(clearInterval(this.gitPollTimer),this.gitPollTimer=null)}};function j(r,e){try{return Y("git",["-C",r,...e],{encoding:"utf-8",stdio:["ignore","pipe","ignore"]}).trim()}catch{return}}function J(r,e){return r?r.kind===e.kind?r.detail===e.detail?r:{kind:r.kind,detail:"multiple changes"}:{kind:"watch-git-state",detail:`${r.kind}, ${e.kind}`}:e}import{existsSync as v,mkdirSync as Q,symlinkSync as Z,readlinkSync as b,unlinkSync as T,readdirSync as ee}from"fs";import{join as h,dirname as w,resolve as g,relative as te,isAbsolute as re}from"path";import{homedir as x,platform as ie}from"os";import{fileURLToPath as se}from"url";var ne=ie()==="win32",_=["_shared","scip-query","scip-setup","scip-calibrate","scip-cleanup-audit","scip-cleanup-improve","scip-integrity-audit","scip-twin-drift","scip-claim-audit","scip-probe-reachability","scip-hyper-optimization","scip-api-impact","scip-concrete-plan","scip-conductor","scip-debug","scip-explore","scip-triage-issue","scip-diagram","scip-doc-reconcile","scip-directory-architecture","scip-maintainability","scip-react-maintainability","scip-vue-maintainability","scip-verify","scip-language-playbook","scip-tla-model-system"];function oe(r={}){let e=r.quiet?()=>{}:console.log,t=se(import.meta.url),i=g(w(t),"..","skills"),s=[h(x(),".claude","skills"),h(x(),".codex","skills"),h(x(),".agents","skills")],o={installed:[],skipped:[],alreadyLinked:[],pruned:[]};for(let n of s){let c=w(n);if(!v(c))continue;Q(n,{recursive:!0});let a=ce(n);ae(n,i,a,o,e);for(let l of _){let d=h(i,l),p=h(n,l);if(!v(d)){o.skipped.push(`${a}/${l}`);continue}if(v(p)){try{let I=b(p);if(g(I)===g(d)){o.alreadyLinked.push(`${a}/${l}`),e(` ok: ${l} \u2192 ${a} (already linked)`);continue}}catch{o.skipped.push(`${a}/${l}`),e(` skip: ${l} \u2192 ${a} (exists, not a symlink)`);continue}T(p)}Z(d,p,ne?"junction":"dir"),o.installed.push(`${a}/${l}`),e(` done: ${l} \u2192 ${a}`)}}return o}function ae(r,e,t,i,s){let o=new Set(_);for(let n of ee(r)){if(o.has(n))continue;let c=h(r,n),a;try{a=g(w(c),b(c))}catch{continue}le(a,e)&&(T(c),i.pruned.push(`${t}/${n}`),s(` prune: ${n} \u2192 ${t} (removed stale scip-query skill link)`))}}function le(r,e){let t=te(g(e),g(r));return t===""||!t.startsWith("..")&&!re(t)}function ce(r){return r.includes(".codex")?"Codex":r.includes(".agents")?"Agents":"Claude"}export{C as Watcher,M as getScipVersion,q as initProjectConfig,oe as installSkills,$ as isScipInstalled,y as loadProjectConfig,A as printScipInstallInstructions,S as resolveIndexStoragePaths};
1
+ import{c as $,d as M,e as A}from"./chunk-WIBFXSYB.js";import{a as R}from"./chunk-YZXV3CU3.js";import{readFileSync as O,writeFileSync as D,existsSync as P,mkdirSync as N}from"fs";import{isAbsolute as ue,join as d,relative as he,resolve as k}from"path";import{createHash as G}from"crypto";import{homedir as F}from"os";var f=".scipquery.json",W={enabled:!1,debounceMs:3e4,cooldownMs:6e4,gitPollMs:2e3,autoRefresh:!0,ignore:[]},U=["typescript","javascript","java","scala","kotlin","rust","python","ruby","go","cpp","c","csharp","vb","dart","php","clojure"];var me=new Set(U);function y(r){let e=d(r,f);if(!P(e))return{};try{let t=O(e,"utf-8");return JSON.parse(t)}catch(t){let i=t instanceof Error?t.message:String(t);throw t instanceof SyntaxError?new Error(`invalid ${f} at ${e}: ${i}`,{cause:t}):new Error(`unable to read ${f} at ${e}: ${i}`,{cause:t})}}function E(r){return{...W,...r.watch}}function L(r,e){let t=process.env.SCIP_QUERY_CACHE_DIR;if(t)return m(t);if(e?.dbPath)return m(k(r,e.dbPath));let s=process.env.XDG_CACHE_HOME||d(F(),".cache"),o=G("sha256").update(k(r)).digest("hex").slice(0,12),n=d(s,"scip-query","projects",o);return m(n)}function S(r,e){let t=L(r,e);return{cacheDir:t,dbPath:d(t,"index.db"),indexPath:d(t,"index.scip"),metaPath:d(t,"meta.json")}}function q(r,e){let t=d(r,f);return P(t)||D(t,JSON.stringify({languages:e,watch:{enabled:!1,debounceMs:3e4,cooldownMs:6e4,gitPollMs:2e3,autoRefresh:!0}},null,2)+`
2
+ `),t}function m(r){return N(r,{recursive:!0}),r}import{execFileSync as Y,fork as K}from"child_process";import{statSync as H,watch as X}from"fs";import{join as B,relative as V}from"path";import z from"ignore";var v=class{projectRoot;config;watchConfig;languages;pnpmWorkspaces;typescriptProjectMode;typescriptProjects;clojureConfigPath;indexerConcurrency;onStatus;onReindexComplete;onError;status={state:"idle"};debounceTimer=null;cooldownTimer=null;dirty=!1;changedFiles=0;pendingTrigger=null;reindexInFlight=!1;lastReindexEnd=0;fsWatchers=[];gitPollTimer=null;lastGitState=null;gitignoreFilter;extraIgnore;stopped=!1;constructor(e){this.projectRoot=e.projectRoot,this.config=e.config,this.watchConfig=E(e.config),this.languages=e.languages,this.pnpmWorkspaces=e.config.indexer?.typescript?.pnpmWorkspaces??!1,this.typescriptProjectMode=e.config.indexer?.typescript?.projectMode,this.typescriptProjects=e.config.indexer?.typescript?.projects,this.clojureConfigPath=e.config.indexer?.clojure?.configPath,this.indexerConcurrency=e.config.indexerConcurrency,this.onStatus=e.onStatus??(()=>{}),this.onReindexComplete=e.onReindexComplete??(()=>{}),this.onError=e.onError??(t=>console.error(t.message)),this.gitignoreFilter=R(e.projectRoot),this.extraIgnore=z(),this.watchConfig.ignore.length>0&&this.extraIgnore.add(this.watchConfig.ignore)}start(){this.stopped=!1,this.setStatus({state:"idle"}),this.startGitStatePolling();try{let e=X(this.projectRoot,{recursive:!0},(t,i)=>{i&&!this.stopped&&this.handleFileChange(i)});this.fsWatchers.push(e)}catch{this.onError(new Error("Failed to start file watcher. On Linux, you may need to increase inotify limits: sysctl -w fs.inotify.max_user_watches=524288"))}}stop(){this.stopped=!0;for(let e of this.fsWatchers)e.close();this.fsWatchers=[],this.clearDebounceTimer(),this.clearCooldownTimer(),this.clearGitPollTimer(),this.setStatus({state:"idle"})}handleFileChange(e){let t=V(this.projectRoot,B(this.projectRoot,e));t===".git"||t.startsWith(".git/")||this.gitignoreFilter.isIgnored(t)||this.extraIgnore.ignores(t)||e.endsWith("index.db")||e.endsWith("index.scip")||e.endsWith("index.db.tmp")||this.scheduleReindex({kind:"watch-source",detail:t})}scheduleReindex(e){if(this.pendingTrigger=J(this.pendingTrigger,e),this.changedFiles++,this.reindexInFlight){this.dirty=!0,this.setStatus({state:"indexing",startedAt:this.status.startedAt});return}if(this.status.state==="cooldown"){this.dirty=!0,this.setStatus({state:"cooldown",until:this.status.until,dirty:!0});return}this.clearDebounceTimer();let t=Date.now()+this.watchConfig.debounceMs;this.setStatus({state:"waiting",changedFiles:this.changedFiles,reindexAt:t}),this.debounceTimer=setTimeout(()=>{this.debounceTimer=null,this.triggerReindex()},this.watchConfig.debounceMs)}triggerReindex(){if(this.reindexInFlight||this.stopped)return;let e=Date.now()-this.lastReindexEnd;if(this.lastReindexEnd>0&&e<this.watchConfig.cooldownMs){let s=this.watchConfig.cooldownMs-e;this.dirty=!0;let o=Date.now()+s;this.setStatus({state:"cooldown",until:o,dirty:!0}),this.cooldownTimer=setTimeout(()=>{this.cooldownTimer=null,this.dirty&&!this.stopped&&(this.dirty=!1,this.triggerReindex())},s);return}this.reindexInFlight=!0,this.dirty=!1,this.changedFiles=0;let t=this.pendingTrigger??{kind:"watch-source"};this.pendingTrigger=null;let i=Date.now();this.setStatus({state:"indexing",startedAt:i}),this.runReindex(t).then(s=>{if(this.reindexInFlight=!1,this.lastReindexEnd=Date.now(),this.onReindexComplete(s),this.dirty&&!this.stopped){let o=Date.now()+this.watchConfig.cooldownMs;this.setStatus({state:"cooldown",until:o,dirty:!0}),this.cooldownTimer=setTimeout(()=>{this.cooldownTimer=null,this.dirty&&!this.stopped?(this.dirty=!1,this.triggerReindex()):this.setStatus({state:"idle"})},this.watchConfig.cooldownMs)}else this.setStatus({state:"idle"})}).catch(s=>{this.reindexInFlight=!1,this.lastReindexEnd=Date.now(),this.onError(s instanceof Error?s:new Error(String(s))),this.setStatus({state:"idle"})})}runReindex(e){return new Promise((t,i)=>{let s=Date.now(),o=y(this.projectRoot),n=Object.keys(o).length>0?o:this.config,c=S(this.projectRoot,n),a=n.indexer?.typescript,l=n.indexer?.clojure,u=K(new URL("./reindex-worker.js",import.meta.url).pathname,[],{detached:!0,env:{...process.env,SCIP_REINDEX_PROJECT_ROOT:this.projectRoot,SCIP_REINDEX_OUTPUT_SCIP:c.indexPath,SCIP_REINDEX_OUTPUT_DB:c.dbPath,SCIP_REINDEX_LANGUAGES:(n.languages??this.languages)?.join(",")??"",SCIP_REINDEX_INDEXER_CONCURRENCY:String(n.indexerConcurrency??this.indexerConcurrency??""),SCIP_REINDEX_PNPM_WORKSPACES:a?.pnpmWorkspaces??this.pnpmWorkspaces?"1":"",SCIP_REINDEX_TYPESCRIPT_CONFIG:JSON.stringify({projectMode:a?.projectMode??this.typescriptProjectMode,projects:a?.projects??this.typescriptProjects??[]}),SCIP_REINDEX_CLOJURE_CONFIG_PATH:l?.configPath??this.clojureConfigPath??"",SCIP_REINDEX_TRIGGER_KIND:e.kind,SCIP_REINDEX_TRIGGER_DETAIL:e.detail??""},stdio:"pipe"});u.on("exit",p=>{p===0?t(Date.now()-s):i(new Error(`Reindex worker exited with code ${p}`))}),u.on("error",i)})}setStatus(e){this.status=e,this.onStatus(e)}clearDebounceTimer(){this.debounceTimer&&(clearTimeout(this.debounceTimer),this.debounceTimer=null)}clearCooldownTimer(){this.cooldownTimer&&(clearTimeout(this.cooldownTimer),this.cooldownTimer=null)}startGitStatePolling(){this.lastGitState=this.readGitState(),this.lastGitState&&(this.gitPollTimer=setInterval(()=>this.pollGitState(),this.watchConfig.gitPollMs),this.gitPollTimer.unref?.())}pollGitState(){let e=this.lastGitState,t=this.readGitState();if(!e||!t||this.stopped){this.lastGitState=t;return}this.lastGitState=t;let i=e.head!==t.head,s=e.indexPath!==t.indexPath||e.indexMtimeMs!==t.indexMtimeMs||e.indexSize!==t.indexSize;i&&s?this.scheduleReindex({kind:"watch-git-state",detail:"HEAD and index changed"}):i?this.scheduleReindex({kind:"watch-git-head",detail:"HEAD changed"}):s&&this.scheduleReindex({kind:"watch-git-index",detail:t.indexPath??"index changed"})}readGitState(){let e=j(this.projectRoot,["rev-parse","--git-path","index"]);if(!e)return null;let t={head:j(this.projectRoot,["rev-parse","--verify","HEAD"]),indexPath:e};try{let i=H(e);t.indexMtimeMs=i.mtimeMs,t.indexSize=i.size}catch{t.indexMtimeMs=void 0,t.indexSize=void 0}return t}clearGitPollTimer(){this.gitPollTimer&&(clearInterval(this.gitPollTimer),this.gitPollTimer=null)}};function j(r,e){try{return Y("git",["-C",r,...e],{encoding:"utf-8",stdio:["ignore","pipe","ignore"]}).trim()}catch{return}}function J(r,e){return r?r.kind===e.kind?r.detail===e.detail?r:{kind:r.kind,detail:"multiple changes"}:{kind:"watch-git-state",detail:`${r.kind}, ${e.kind}`}:e}import{existsSync as C,mkdirSync as Q,symlinkSync as Z,readlinkSync as b,unlinkSync as T,readdirSync as ee}from"fs";import{join as h,dirname as w,resolve as g,relative as te,isAbsolute as re}from"path";import{homedir as x,platform as ie}from"os";import{fileURLToPath as se}from"url";var ne=ie()==="win32",_=["_shared","scip-query","scip-setup","scip-calibrate","scip-cleanup-audit","scip-cleanup-improve","scip-integrity-audit","scip-twin-drift","scip-claim-audit","scip-probe-reachability","scip-hyper-optimization","scip-api-impact","scip-concrete-plan","scip-conductor","scip-debug","scip-explore","scip-triage-issue","scip-diagram","scip-doc-reconcile","scip-directory-architecture","scip-maintainability","scip-react-maintainability","scip-vue-maintainability","scip-verify","scip-language-playbook","scip-tla-model-system"];function oe(r={}){let e=r.quiet?()=>{}:console.log,t=se(import.meta.url),i=g(w(t),"..","skills"),s=[h(x(),".claude","skills"),h(x(),".codex","skills"),h(x(),".agents","skills")],o={installed:[],skipped:[],alreadyLinked:[],pruned:[]};for(let n of s){let c=w(n);if(!C(c))continue;Q(n,{recursive:!0});let a=ce(n);ae(n,i,a,o,e);for(let l of _){let u=h(i,l),p=h(n,l);if(!C(u)){o.skipped.push(`${a}/${l}`);continue}if(C(p)){try{let I=b(p);if(g(I)===g(u)){o.alreadyLinked.push(`${a}/${l}`),e(` ok: ${l} \u2192 ${a} (already linked)`);continue}}catch{o.skipped.push(`${a}/${l}`),e(` skip: ${l} \u2192 ${a} (exists, not a symlink)`);continue}T(p)}Z(u,p,ne?"junction":"dir"),o.installed.push(`${a}/${l}`),e(` done: ${l} \u2192 ${a}`)}}return o}function ae(r,e,t,i,s){let o=new Set(_);for(let n of ee(r)){if(o.has(n))continue;let c=h(r,n),a;try{a=g(w(c),b(c))}catch{continue}le(a,e)&&(T(c),i.pruned.push(`${t}/${n}`),s(` prune: ${n} \u2192 ${t} (removed stale scip-query skill link)`))}}function le(r,e){let t=te(g(e),g(r));return t===""||!t.startsWith("..")&&!re(t)}function ce(r){return r.includes(".codex")?"Codex":r.includes(".agents")?"Agents":"Claude"}export{v as Watcher,M as getScipVersion,q as initProjectConfig,oe as installSkills,$ as isScipInstalled,y as loadProjectConfig,A as printScipInstallInstructions,S as resolveIndexStoragePaths};
3
3
  //# sourceMappingURL=runtime.js.map
@@ -1,4 +1,4 @@
1
- import { a as SupportedLanguage } from './config-types-BrHl3Bge.js';
1
+ import { a as SupportedLanguage } from './config-types-aCgyLrvE.js';
2
2
 
3
3
  type WatcherStatus = {
4
4
  state: 'idle';
@@ -135,7 +135,8 @@ This syntax summary is generated from the CLI command descriptors. Keep workflow
135
135
  | `capability-matrix` | Deprecated alias for capabilities --matrix | `--json` |
136
136
  | `init` | Create a .scipquery.json config file for this project | - |
137
137
  | `config-validate` | Validate .scipquery.json, including structured suppressions and declared coupling groups | `--json` |
138
- | `suppress <id>` | Record an accepted finding in .scipquery.json with a required reason | `--reason <text>`<br>`--check <check>`<br>`--file <path>`<br>`--expires-at <iso>`<br>`--json` |
138
+ | `suppress <id>` | Record an accepted finding as a file under .scipquery/suppressions/ with a required reason | `--reason <text>`<br>`--check <check>`<br>`--file <path>`<br>`--expires-at <iso>`<br>`--json` |
139
+ | `effectiveness` | Per-check effectiveness from the committed outcome ledger: findings caught, fixed by code changes, suppressed, precision, median time-to-fix | `--since <window>`<br>`--check <check>`<br>`--json` |
139
140
  | `doctor` | Diagnose config, index freshness, dependency readiness, and project capabilities | `--json` |
140
141
  | `setup` | Bootstrap this project: install agent skills, refresh the index, verify capabilities, and report health | `--git-hook`<br>`--no-hooks`<br>`--dossier-dir <path>`<br>`--json` |
141
142
  | `setup-agent` | Seed agent guidance for this project: AGENTS.md/CLAUDE.md block pointing agents at the scip-query skills and diff gate, plus an optional git pre-commit backstop | `--git-hook` |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scip-query",
3
- "version": "0.14.0",
3
+ "version": "0.15.0",
4
4
  "description": "Evidence and verification for AI coding agents: map code, reuse concepts, finish migrations, and gate diffs.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -181,7 +181,8 @@ scip-query capabilities # Report which evidence and verification capabilities ar
181
181
  scip-query capability-matrix # Deprecated alias for capabilities --matrix
182
182
  scip-query init # Create a .scipquery.json config file for this project
183
183
  scip-query config-validate # Validate .scipquery.json, including structured suppressions and declared coupling groups
184
- scip-query suppress <id> # Record an accepted finding in .scipquery.json with a required reason
184
+ scip-query suppress <id> # Record an accepted finding as a file under .scipquery/suppressions/ with a required reason
185
+ scip-query effectiveness # Per-check effectiveness from the committed outcome ledger: findings caught, fixed by code changes, suppressed, precision, median time-to-fix
185
186
  scip-query doctor # Diagnose config, index freshness, dependency readiness, and project capabilities
186
187
  scip-query setup # Bootstrap this project: install agent skills, refresh the index, verify capabilities, and report health
187
188
  scip-query setup-agent # Seed agent guidance for this project: AGENTS.md/CLAUDE.md block pointing agents at the scip-query skills and diff gate, plus an optional git pre-commit backstop
@@ -205,6 +206,7 @@ measured precision, not by volume:
205
206
  - **Advisory gate findings** (marked `(advisory)`) never block; they are context, not obligations.
206
207
  - **False-dead archetypes (2026-07-02 remediation, docs/plans/2026-07-02-followups.md items 1-3)**: `import type`-only consumers (incl. tsconfig `paths`-aliased specifiers) and pnpm/npm/yarn workspace cross-package consumers (incl. unbuilt `dist/` exports maps) are now resolved by the shared reference-counting layer that `dead`/`isolated`/`new-dead`/`stale-abstractions`/`production-callables` all consume; Vue `<script setup>` composable consumers were already correctly resolved (verified live, no code change needed). One narrow residual gap remains: a symbol with an **ambiguous leaf name** (a same-named definition exists elsewhere in the project) reached only through a **re-exporting barrel file** in a workspace package can still misattribute — `new-dead` labels that specific shape `unconfirmed (cross-package ambiguous-name resolution gap)` with `evidence: "heuristic"` and lowered confidence instead of asserting `dead`; treat those as "verify manually," not as a fact. Everything else in this class is a normal graph-fact `dead` claim again — confirm with `refs` when in doubt, same as any other finding.
207
208
  - **Ledger nudges**: when `diff-gate --hook` reports "this check is rarely acted on in this repo", either tune that check's config, suppress the standing findings with reasons, or consciously accept the noise — do not let unresolved findings accumulate as wallpaper.
209
+ - **Effectiveness ledger**: hook-mode gate runs append caught/resolved/suppressed transitions to `.scipquery/ledger/events.jsonl` (committed, conflict-free via a scoped `merge=union` gitattribute — commit it with your changes). Query it with `scip-query effectiveness [--since 30d] [--check <check>] [--json]`: per check it reports findings caught, fixed by code changes, suppressed, still open, `moved` (rename noise), precision (fixed ÷ concluded), and median days-to-fix. A finding that disappears without a suppression counts as fixed; a suppression counts against the detector's precision. Use it to decide which checks to trust, tune, or suppress in a given repo.
208
210
 
209
211
  ## Diff Gate Checks
210
212
 
@@ -8,6 +8,7 @@ commands:
8
8
  - scip-query dead --json
9
9
  - scip-query similar <symbol>
10
10
  - scip-query config-validate
11
+ - scip-query effectiveness --since 30d --json
11
12
  ---
12
13
 
13
14
  # scip-calibrate
@@ -116,9 +117,12 @@ filed upstream.
116
117
 
117
118
  The finding-outcome ledger keeps calibrating for you: once the repo uses
118
119
  diff-gate, per-detector precision is recomputed from real outcomes
119
- (`detectorPrecision` in `health --json`). Re-run this protocol only after
120
- major detector upgrades or when the ledger's precision numbers drift from
121
- what you measured. Write the calibration report to
120
+ (`detectorPrecision` in `health --json`), and the committed event ledger
121
+ makes those outcomes durable and queryable `scip-query effectiveness
122
+ --since 30d --json` reports per-check caught/fixed/suppressed counts,
123
+ precision, and median days-to-fix from `.scipquery/ledger/events.jsonl`.
124
+ Re-run this protocol only after major detector upgrades or when the
125
+ ledger's precision numbers drift from what you measured. Write the calibration report to
122
126
  `docs/validation/YYYY-MM-DD-external-calibration-<repo>.md` in the shape of
123
127
  the exemplars this skill was distilled from — battery table, retro-gate
124
128
  walls, per-detector verdicts, noise archetypes, caps and deviations.
@@ -82,9 +82,14 @@ If it reports findings, fix them or record the acceptance reason. If a suppressi
82
82
 
83
83
  ```bash
84
84
  scip-query suppress <id> --reason "<specific reason>"
85
- scip-query config-validate
86
85
  ```
87
86
 
87
+ `suppress` writes one file per suppression under `.scipquery/suppressions/` —
88
+ commit that file with your change. Suppressing is a real decision, not a
89
+ bypass: every suppression counts against that detector's precision in
90
+ `scip-query effectiveness`, while fixing the finding counts for it. Prefer the
91
+ fix whenever the finding is real.
92
+
88
93
  This step is complete only when `diff-gate` passes or every finding has a durable explanation.
89
94
 
90
95
  ### 5. Check health, docs, and config when relevant
@@ -1,3 +0,0 @@
1
- import{a as be}from"./chunk-M7EEHYSU.js";import{a as ye}from"./chunk-N2OTJWC2.js";import{a as ne,c as oe,d as re,e as w}from"./chunk-ZEO4CXQR.js";import{a as pe}from"./chunk-27XEGMCT.js";import{a as ge}from"./chunk-6JUAGXR4.js";import{a as me}from"./chunk-C2QSK7E7.js";import{a as de}from"./chunk-6KMTPNZL.js";import{a as fe}from"./chunk-JJUX2OAP.js";import{a as ce}from"./chunk-X3DTIEMU.js";import{a as le}from"./chunk-2XRXEIRK.js";import{b as ie}from"./chunk-HZZQKNVE.js";import{a as ee,b as te}from"./chunk-3AMWF6V5.js";import{a as Y,b as J,c as Z,g as j,h as B}from"./chunk-KYB7FPXT.js";import{a as Q,b as D,e as X}from"./chunk-Q7PWDXL5.js";import{b as ae}from"./chunk-QVGFVRKD.js";import{a as N,c as W,d as q,e as V}from"./chunk-IUSSGHJO.js";import{a as U}from"./chunk-7COVQPQQ.js";import{a as he,e as ue}from"./chunk-KSJ544IV.js";import{a as se}from"./chunk-LY7NMOBW.js";import{a as O}from"./chunk-IMQFJ53K.js";import{f as R,g as T}from"./chunk-BZXE3HO4.js";import{i as H}from"./chunk-GULVO6NF.js";import{e as F,f as z,g as I}from"./chunk-X5C43YVI.js";import{a as _}from"./chunk-JZBGTSAB.js";import{a as L}from"./chunk-ISLWJ4PY.js";import{l as P}from"./chunk-WS3Z6W3M.js";import{createHash as We}from"crypto";import{existsSync as K}from"fs";function Ce(e){let[t,i]=G(e,":"),o=t||"unknown",n=(a={})=>{let s=a.actionTier??Ie(o);return{sourceAnalyzer:o,actionTier:s,rootCauseKey:a.rootCauseKey??(i||e),label:a.label??`${o} finding`,relatedFiles:a.relatedFiles??[],why:a.why??[],remediation:a.remediation??Ne(s,o),file:a.file}};if(!i)return n({rootCauseKey:e,why:["Baseline identity did not include analyzer payload."]});if(["dead","isolated","extract","wrapper","passthrough","stale"].includes(o)){let[a,s]=G(i,":");return n({file:a,relatedFiles:a?[a]:[],rootCauseKey:s?`${a}:${s}`:i,label:`${o} finding`,why:s?[`Baseline subject: ${s}.`]:[]})}if(o==="cycle"){let a=i.split(">").filter(Boolean);return n({relatedFiles:a,rootCauseKey:i,label:"cycle finding",why:a.length>0?[`Cycle path: ${a.join(" > ")}.`]:[]})}if(o==="similar"){let a=i.split("|").filter(Boolean);return n({relatedFiles:[],rootCauseKey:i,label:"similarity finding",why:a.length>0?[`Similar symbols: ${a.join(" | ")}.`]:[]})}if(o==="drift"){let[a,s]=G(i,":"),[l,c]=G(s,":");return n({actionTier:a==="unused-import"?"direct":"signal",file:l,relatedFiles:[...new Set([l,c].filter(Boolean))],rootCauseKey:i,label:`${a||"drift"} finding`,why:[...a?[`Drift kind: ${a}.`]:[],...c?[`Related dependency: ${c}.`]:[]]})}return n({why:["Baseline analyzer prefix is not recognized by this diff-gate version."]})}function Ie(e){switch(e){case"dead":case"isolated":case"cycle":case"passthrough":return"direct";case"similar":case"extract":case"wrapper":case"stale":return"signal";default:return"signal"}}function Ne(e,t){return e==="direct"?`Fix the new ${t} baseline finding, or knowingly accept it via health --write-baseline.`:`Review the new ${t} baseline signal; fix it if it reflects real debt, or knowingly accept it via health --write-baseline.`}function G(e,t){let i=e.indexOf(t);return i<0?[e,""]:[e.slice(0,i),e.slice(i+t.length)]}import{existsSync as je,readFileSync as Be,writeFileSync as Me}from"fs";import{isAbsolute as Ke,join as Le}from"path";var Pe=25e3,_e=2500,ze=2500;function He(e){let t=me(e);return t.symbols>=Pe||t.documents>=_e?ze:void 0}var Oe=".scipquery-baseline.json";function x(e,t){return t&&Ke(t)?t:Le(e.config.projectRoot,t??Oe)}function $e(e,t={}){let{scope:i}=t,o=He(e),n=[],a=ae(e,{scope:i,...w.dead,scanLimit:o});for(let s of a.symbols)R(e,s.relativePath)||T(e,s.symbol,s.relativePath)||s.kind==="dead-code"&&n.push(`dead:${s.relativePath}:${s.shortName}`);for(let s of fe(e,{scope:i,...w.isolated,scanLimit:o}))R(e,s.relativePath)||T(e,s.symbol,s.relativePath)||n.push(`isolated:${s.relativePath}:${s.shortName}`);for(let s of se(e,{scope:i}))s.kind==="real"&&n.push(`cycle:${Ue(s.path)}`);for(let s of ue(e,{scope:i,...w.similar,scanLimit:o}))n.push(`similar:${[s.symbolA,s.symbolB].sort().join("|")}`);for(let s of ee(e,{scope:i,...w.duplicateBodies,scanLimit:o}))n.push(`duplicate-bodies:${s.hash}:${s.functions.map(l=>l.symbol).sort().join("|")}`);for(let s of le(e,{scope:i,...w.extract,scanLimit:o}))n.push(`extract:${s.relativePath}:${s.shortName}`);for(let s of pe(e,{scope:i,...w.wrappers,scanLimit:o}))n.push(`wrapper:${s.file}:${s.shortName}`);for(let s of de(e,{scope:i,...w.passthroughs,scanLimit:o}))n.push(`passthrough:${s.file}:${s.shortName}`);for(let s of ge(e,{scope:i,...w.stale,scanLimit:o}))n.push(`stale:${s.file}:${s.shortName}`);for(let s of ce(e,{scope:i,...w.drift,includePatternDeviations:!1,limit:Number.POSITIVE_INFINITY}).results)n.push(`drift:${s.kind}:${s.file}:${s.dep}`);return[...new Set(n)].sort()}function Ue(e){if(e.length===0)return"";let t=0;for(let i=1;i<e.length;i++)e[i]<e[t]&&(t=i);return[...e.slice(t),...e.slice(0,t)].join(">")}function Kt(e,t={}){let i=$e(e,{scope:t.scope}),o=x(e,t.path);return Me(o,JSON.stringify({version:1,findings:i},null,2)+`
2
- `),{path:o,findingCount:i.length}}function Se(e,t={}){let i=x(e,t.path);if(!je(i))throw new Error(`No baseline found at ${i}. Create one with: scip-query health --write-baseline`);let o=JSON.parse(Be(i,"utf-8")),n=new Set(o.findings??[]),a=$e(e,{scope:t.scope}),s=new Set(a);return{baselinePath:i,baselineCount:n.size,current:a,newFindings:a.filter(l=>!n.has(l)),fixedFindings:[...n].filter(l=>!s.has(l))}}var ci=["echo","incomplete-migration","co-change-partner","twin-partner","coverage-contract","doc-reference","unused-params","new-dead","baseline"];function li(e,t={}){let i=t.base??"HEAD",{minTogether:o=6,minConfidence:n=.6,maxEchoChecks:a=Number.POSITIVE_INFINITY,maxHelpers:s=Number.POSITIVE_INFINITY,minSimilarity:l=.8,includeBaseline:c=!1,scanLimit:r}=t,d=t.semantic!==!1,u=new Set(t.skip??[]),h=Z(e,{base:i}),p=J(e,{base:i,plan:h}),m=p.changedFiles,b=new Set(m),S=new Set(h.changedFileLines),f=new Set([...b,...S]),C=j(e.config.projectRoot,i,B(h,m)),$=Ct(e.config.projectRoot,i,h,C),g={base:i,changedFiles:m,changedSymbols:p.changedSymbols.length,checksRun:[],skipped:[],suppressed:[],findings:[],attributionNotes:p.attributionNotes,rootCauseGroups:[],note:p.summary.note};if(m.length===0)return g;let v=(A,Ae)=>{if(u.has(A)){g.skipped.push({check:A,reason:"skipped via --skip"});return}let Ge=g.findings.length,xe=g.skipped.length,Ee=g.checksRun.length;P(`diff-gate.check.${A}`,Ae,()=>({check:A,changedFiles:m.length,changedSymbols:p.changedSymbols.length,findingsAdded:g.findings.length-Ge,skippedAdded:g.skipped.length-xe,checksRunAdded:g.checksRun.length-Ee}))};return v("echo",()=>Je(e,p.changedSymbols,b,$,a,l,r,d,g)),v("incomplete-migration",()=>it(e,i,h,s,r,d,C,g)),v("co-change-partner",()=>nt(e,f,o,n,g)),v("twin-partner",()=>ot(e,p.changedSymbols,b,r,g)),v("coverage-contract",()=>rt(e,f,g)),v("doc-reference",()=>ft(e,b,S,h.changedRanges,h.renamedFiles,g)),v("unused-params",()=>gt(e,m,g)),v("new-dead",()=>mt(e,p.changedSymbols,$,g)),c&&v("baseline",()=>St(e,g)),Ve(g,e.config.suppressions??[]),g.rootCauseGroups=qe(g.findings),g}function fi(e){return e.checksRun.length===0&&e.note===Y}function di(e){return e.filter(t=>!t.advisory)}function qe(e){let t=new Map;for(let i of e){let o=i.groupKey??`${i.check}:${i.id}`,n=t.get(o);n||(n={groupKey:o,check:i.check,severity:i.severity,actionTier:i.actionTier,sourceAnalyzer:i.sourceAnalyzer,rootCauseKey:i.rootCauseKey,count:0,findingIds:[],files:new Set,relatedFiles:new Set,message:i.message,remediation:i.remediation},t.set(o,n)),n.count+=1,n.findingIds.push(i.id),n.severity=Re(n.severity,i.severity),n.actionTier=Te(n.actionTier,i.actionTier),i.file&&n.files.add(i.file);for(let a of i.relatedFiles??[])n.relatedFiles.add(a)}return[...t.values()].map(i=>({...i,files:[...i.files].sort(),relatedFiles:[...i.relatedFiles].sort()})).sort((i,o)=>o.count-i.count||E(o.severity)-E(i.severity)||i.groupKey.localeCompare(o.groupKey))}function Re(e,t){return E(t)>E(e)?t:e}function E(e){return e==="error"?2:e==="warning"?1:0}function Te(e,t){return e?t&&we(t)>we(e)?t:e:t}function we(e){return e==="direct"?2:e==="signal"?1:0}function Ve(e,t){if(t.length===0||e.findings.length===0)return;let i=[];for(let o of e.findings){let n=t.find(a=>Ye(a,o));n?e.suppressed.push({finding:o,suppression:n}):i.push(o)}e.findings=i}function Ye(e,t){return!e.reason||e.reason.trim()===""||e.expiresAt&&Date.parse(e.expiresAt)<=Date.now()||e.id&&e.id!==t.id&&!t.legacySuppressionIds?.includes(e.id)||e.check&&e.check!==t.check||e.file&&e.file!==t.file?!1:!!(e.id||e.check)}function k(e,t){e.findings.push({...t,suppressionHint:`scip-query: ignore ${t.check} ${t.id} -- <reason>`})}function Je(e,t,i,o,n,a,s,l,c){c.checksRun.push("echo");for(let r of t.slice(0,n)){if(!I(r.symbol)||o(r))continue;let d=te(e,r.symbol,{maxLoc:15,scanLimit:s}).filter(f=>!i.has(f.file)).slice(0,5).map(f=>({otherFile:f.file,otherShort:f.shortName,otherSymbol:f.symbol,similarity:1,similarityBasis:"exact-body",sharedEvidence:[`exact normalized body (${f.loc} LOC)`]}));if(d.length>0){let f=y("echo",r.symbol,r.file),C=y("echo",r.symbol,r.file,...d.map(g=>`${g.otherSymbol}|${g.otherFile}`).sort()),$=d[0];k(c,{id:f,legacySuppressionIds:C===f?void 0:[C],groupKey:f,actionTier:"direct",check:"echo",severity:"warning",evidence:"heuristic",confidence:1,file:r.file,symbol:r.symbol,relatedFiles:[...new Set(d.map(g=>g.otherFile))].sort(),sourceAnalyzer:"duplicate-bodies",rootCauseKey:`${r.symbol}:${$.otherSymbol}`,message:ke(r,d),why:De(r,d),remediation:Fe("direct",r.shortName,$.otherShort)});continue}let u=he(e,r.symbol,{minSimilarity:a,limit:5,scanLimit:s,semantic:l,sourceCandidateMode:"target-pruned"}),h=[];for(let f of u){let C=f.fileA===r.file?f.fileB:f.fileA,$=f.fileA===r.file?f.shortNameB:f.shortNameA,g=f.fileA===r.file?f.symbolB:f.symbolA;i.has(C)||h.push({otherFile:C,otherShort:$,otherSymbol:g,similarity:f.similarity,similarityBasis:f.similarityBasis??"callees",sharedEvidence:f.sharedCallees})}if(h.length===0)continue;let p=h[0],m=y("echo",r.symbol,r.file,...h.map(f=>`${f.otherSymbol}|${f.otherFile}`).sort()),b=y("echo",r.symbol,r.file),S=Ze(r,h);k(c,{id:b,legacySuppressionIds:m===b?void 0:[m],groupKey:b,actionTier:S,check:"echo",severity:"warning",evidence:"heuristic",confidence:p.similarity,file:r.file,symbol:r.symbol,relatedFiles:[...new Set(h.map(f=>f.otherFile))].sort(),message:ke(r,h),why:De(r,h),remediation:Fe(S,r.shortName,p.otherShort)})}t.length>n&&c.skipped.push({check:"echo",reason:`echo check capped at ${n} of ${t.length} changed symbols`})}function Ze(e,t){return t.some(o=>Qe(e.symbol,o))?"direct":"signal"}function Qe(e,t){if(t.similarityBasis==="exact-body")return!0;if(t.similarityBasis!=="source-tokens"||t.similarity<.98||t.sharedEvidence.length>8)return!1;let i=F(e),o=F(t.otherSymbol);return!i||!o?!1:i===o?!0:tt(t.sharedEvidence)?!1:Xe(i,o)}function Xe(e,t){let i=ve(e),o=ve(t);if(i.length===0||o.length===0)return!1;let n=new Set(o),a=i.filter(s=>n.has(s)).length;return a>=2&&a/Math.min(i.length,o.length)>=.5}function ve(e){return e.replace(/([a-z0-9])([A-Z])/g,"$1 $2").split(/[^A-Za-z0-9]+/).map(t=>t.toLowerCase()).filter(t=>t.length>=2)}var et=new Set(["bytes","crypto","hex","num","random","secret","to","token"]);function tt(e){return e.length>=3&&e.every(t=>et.has(t.toLowerCase()))}function ke(e,t){let i=t[0],o=Math.round(i.similarity*100);return t.length===1?`${e.shortName} (${e.file}) is ${o}% similar to established ${i.otherShort} (${i.otherFile})`:`${e.shortName} (${e.file}) resembles ${t.length} established symbol(s); strongest is ${o}% similar to ${i.otherShort} (${i.otherFile})`}function De(e,t){let i=[`${e.shortName} was changed in this diff.`];for(let o of t.slice(0,3))i.push(`${o.otherShort} is outside this diff and scored ${Math.round(o.similarity*100)}% similar by ${o.similarityBasis} evidence.`),i.push(`Shared evidence: ${o.sharedEvidence.slice(0,5).join(", ")||"(none listed)"}.`);return t.length>3&&i.push(`${t.length-3} additional similar match(es) grouped into this finding.`),i}function Fe(e,t,i){return e==="direct"?`Extract or reuse the shared behavior with ${i}, or keep the duplicate with a suppression reason.`:`Review whether ${t} is intentionally parallel to the established implementation(s); extract shared behavior only if the product semantics match.`}function it(e,t,i,o,n,a,s,l){let c=ye(e,{base:t,maxHelpers:o,diffPlan:i,scanLimit:n,semantic:a,baseContentAt:s});if(!c.available){l.skipped.push({check:"incomplete-migration",reason:"no git history"});return}l.checksRun.push("incomplete-migration"),c.note&&l.skipped.push({check:"incomplete-migration",reason:c.note});for(let r of c.findings){let d=r.leftovers.map(f=>`${f.shortName} (${f.file}, ${Math.round(f.containment*100)}% helper / ${Math.round(f.siteCoverage*100)}% site, ${f.migrationScope})`).join(", "),u=r.leftovers.map(f=>`${f.shortName}: ${f.migrationScope} (${f.migrationScopeReasons[0]})`).join("; "),h=[...r.migratedFiles,...r.leftovers.map(f=>f.file)],p=r.leftovers.length===0?void 0:Math.max(...r.leftovers.map(f=>Math.min(f.containment,f.siteCoverage))),m=r.leftovers.some(f=>f.migrationScope==="possible-subtype"),b=y("incomplete-migration",r.helperSymbol,r.helperFile,h.join("|")),S=y("incomplete-migration",r.helperSymbol,r.helperFile);k(l,{id:S,legacySuppressionIds:b===S?void 0:[b],check:"incomplete-migration",severity:"warning",evidence:"heuristic",confidence:p,file:r.helperFile,symbol:r.helperSymbol,relatedFiles:h,message:`new helper ${r.helperShortName} (${r.helperFile}) is wired into ${r.migratedFiles.length} file(s), but ${r.leftovers.length} similar un-migrated site(s) remain: ${d}`,why:[`${r.helperShortName} is new in this diff and already referenced by ${r.migratedFiles.join(", ")}.`,`Helper shape: ${r.helperShape}; ${r.specificHelperCalleeCount}/${r.helperCalleeCount} helper callees are specific enough to score.`,`Unchanged site(s) still contain the helper's callee pattern: ${d}.`,`Migration scope hints: ${u}.`],remediation:m?`Migrate same-scope sites to ${r.helperShortName}; review possible subtype/variant sites before applying the helper.`:`Migrate the remaining sites to ${r.helperShortName}, or confirm they are intentionally different.`})}}function nt(e,t,i,o,n){let a=U(e).directionalCoChangePairsForFiles(t,{minTogether:i,minConfidence:0,maxFilesPerCommit:20});if(!a){n.skipped.push({check:"co-change-partner",reason:"no git history"});return}n.checksRun.push("co-change-partner");let s=new Set,l;for(let c of a){let r=t.has(c.fileA),d=t.has(c.fileB);if(r===d)continue;let u=r?c.fileA:c.fileB,h=r?c.fileB:c.fileA,p=r?c.changesA:c.changesB,m=p>0?c.together/p:0;if(m<o||N(h)||N(u)||!K(`${e.config.projectRoot}/${h}`))continue;let b=`${u}|${h}`;if(s.has(b))continue;s.add(b);let S=y("co-change-partner",u,h,String(c.together)),f=y("co-change-partner",u,h),C=W(u,h),$=q({fileA:u,fileB:h,together:c.together,confidence:m,commitScope:c.commitScope,recency:c.recency,subjectContext:c.subjectContext},C,(l??=V(e))(u,h)),g=[u,h].sort().join("|");n.findings.push({id:f,legacySuppressionIds:S===f?void 0:[S],check:"co-change-partner",severity:"warning",evidence:"change-graph",actionTier:"signal",groupKey:`co-change-partner:${g}`,confidence:m,file:u,relatedFiles:[h],sourceAnalyzer:"co-change",rootCauseKey:g,partnerClass:C.partnerClass,partnerClassReasons:C.reasons,commitScope:c.commitScope,recency:c.recency,focusedTogether:c.focusedTogether,broadTogether:c.broadTogether,broadCommitRatio:c.broadCommitRatio,lastTogetherAt:c.lastTogetherAt,recentTogether:c.recentTogether,subjectContext:c.subjectContext,...$?{declaredCouplingSuggestion:$}:{},message:`${u} changed, but ${h} did not \u2014 they change together ${c.together}x (${Math.round(m*100)}% of the time)`,why:[`${u} is in this diff and ${h} is not.`,`Git history shows ${c.together} co-change(s), which is ${Math.round(m*100)}% of changes to the edited side.`,`History context: ${st(c)}.`,`Subject context: ${at(c.subjectContext)}.`,...ct(c),`Partner class: ${C.partnerClass} (${C.reasons.join("; ")}).`],remediation:$?`Update ${h} alongside this change, declare the coupling "${$.name}", or confirm the coupling no longer holds.`:`Update ${h} alongside this change, or confirm the coupling no longer holds.`})}}function ot(e,t,i,o,n){n.checksRun.push("twin-partner");let a=ie(e,{scanLimit:o}).filter(c=>c.relationship==="divergent"||c.relationship==="identical");if(a.length===0)return;let s=new Map;for(let c of a)for(let r of c.members)s.set(r.symbol,c);let l=new Set;for(let c of t){if(!I(c.symbol))continue;let r=s.get(c.symbol);if(!r)continue;let d=r.members.filter(m=>m.symbol!==c.symbol&&!i.has(m.file));if(d.length===0)continue;let u=[c.symbol,...d.map(m=>m.symbol)].sort().join("|");if(l.has(u))continue;l.add(u);let h=d[0],p=y("twin-partner",c.symbol,c.file,h.symbol);k(n,{id:p,groupKey:`twin-partner:${u}`,check:"twin-partner",severity:"warning",evidence:"heuristic",actionTier:"signal",advisory:!0,confidence:r.relationship==="identical"?1:Math.max(0,1-r.maxDivergence),file:c.file,symbol:c.symbol,relatedFiles:d.map(m=>m.file).sort(),sourceAnalyzer:"twin-drift",rootCauseKey:u,message:`${c.shortName} (${c.file}) changed but its same-name twin ${h.shortName} (${h.file}) did not \u2014 verify the change shouldn't apply to both, or consolidate them.`,why:[`${c.symbol} is in this diff.`,`${h.symbol} shares its (near-)name and is classified '${r.relationship}' with it, but ${h.file} is not in this diff.`,r.relationship==="divergent"?`The two bodies already diverge (~${Math.round(r.maxDivergence*100)}% token difference) \u2014 this diff may widen or narrow that drift.`:"The two bodies were byte-identical before this diff.","Advisory: twin-partner never fails the gate by itself \u2014 see the twin-partner entry in the diff-gate checks table."],remediation:`Update ${h.file} alongside this change, consolidate ${c.shortName}/${h.shortName} into one helper, or confirm the twin should diverge on purpose.`})}}function rt(e,t,i){i.checksRun.push("coverage-contract");let o=e.config.coverageContracts??[];for(let n of o){if(!oe(n,t))continue;let a=ne(e,n);if(a.status==="ok")continue;let s=y("coverage-contract",n.name,n.file);k(i,{id:s,groupKey:`coverage-contract:${n.name}`,check:"coverage-contract",severity:a.status==="unavailable"?"warning":"error",evidence:"graph-fact",actionTier:"direct",file:n.file,sourceAnalyzer:"coverage-contracts",rootCauseKey:n.name,message:re(a),why:[`Contract "${n.name}" declares that ${n.file} (${n.keys.type}) must track ${n.mustEqual.type}.`,a.status==="unavailable"?`The declared side could not be evaluated: ${a.unavailableReason}.`:`Missing: ${a.missing.join(", ")||"none"}. Extra: ${a.extra.join(", ")||"none"}.`,"Either side of this contract changed in this diff."],remediation:a.status==="unavailable"?"Investigate why the declared key set could not be parsed (see the reason above) \u2014 do not treat this as a pass.":`Update ${n.file} so its ${n.keys.type==="object-literal-keys"||n.keys.type==="string-array"?n.keys.identifier:n.keys.marker} matches ${n.mustEqual.type}, or fix the ground-truth source if the declared set is right.`})}}function st(e){return`${e.commitScope}/${e.recency}; ${e.focusedTogether} focused, ${e.broadTogether} broad-sweep (${Math.round(e.broadCommitRatio*100)}% broad), ${e.recentTogether} recent, last ${lt(e.lastTogetherAt)}`}function at(e){let t=e.subjectLabels.length>0?e.subjectLabels.slice(0,5).join(", "):"none inferred",i=e.issueRefs.length>0?e.issueRefs.slice(0,5).join(", "):"none",o=e.sampleSubjects.length>0?e.sampleSubjects.slice(0,3).map(n=>`"${n}"`).join("; "):"none";return`labels ${t}; refs ${i}; samples ${o}; external issue/PR labels ${e.externalIssueLabelStatus}`}function ct(e){let t=[];return e.commitScope==="broad-sweep"&&t.push("Broad-sweep history is weaker than focused co-edits; confirm this is a real coordination contract."),e.recency==="stale"&&t.push("The pair has no co-change in the recent history window; confirm the coupling still holds."),t}function lt(e){return e<=0?"unknown":new Date(e*1e3).toISOString().slice(0,10)}var M=3;function ft(e,t,i,o,n,a){a.checksRun.push("doc-reference");let s=D.referenceTargets(e,t,o);if(s.size===0)return;let l=new Set(n.map(r=>r.from)),c=[];for(let r of X(e,s))i.has(r.doc)||Q(e,r.doc)||c.push(dt(e,r,l));for(let r of ht(c))k(a,r)}function dt(e,t,i){let o=t.citedClaims.length>0?t.citedClaims:D.citationContexts(e,t.doc,t.cited),n=D.classifyCitation(o),a=D.hasLineAnchoredCitation(t.citations),s=t.cited.some(u=>i.has(u)||!K(`${e.config.projectRoot}/${u}`)),l=!(a||s),c=t.cited[0]??"",r=y("doc-reference",t.doc,t.cited.join("|")),d=y("doc-reference",t.doc,c);return{id:d,legacySuppressionIds:r===d?void 0:[r],check:"doc-reference",severity:"warning",evidence:"change-graph",actionTier:n.actionTier,groupKey:`doc-reference:${c}`,rootCauseKey:c,confidence:1,file:t.doc,relatedFiles:t.cited,citationKind:n.citationKind,citationKindReasons:n.reasons,citedClaims:o,...l?{advisory:!0}:{},message:`${t.doc} cites ${t.cited.join(", ")} as ${D.citationKindLabel(n.citationKind)} \u2014 changed in this diff, doc untouched`,why:[`${t.cited.join(", ")} changed in this diff.`,`${t.doc} cites the changed file(s) but was not updated in the same diff.`,...n.reasons.map(u=>`Citation kind evidence: ${u}`),l?"Advisory: bare file-mention citation (no line anchor, cited file still present under the same path) \u2014 21.2 calibration found this citation shape low-precision; verify at your leisure.":a?"Blocking: citation includes a line anchor that may now point at the wrong lines.":"Blocking: the cited file was deleted or renamed in this diff \u2014 the citation almost certainly needs an update."],remediation:D.citationRemediation(n.citationKind,t.doc)}}function ht(e){let t=new Map;for(let o of e){let n=o.rootCauseKey??"",a=t.get(n);a?a.push(o):t.set(n,[o])}let i=[];for(let[o,n]of t)n.length<=M?i.push(...n):i.push(ut(o,n));return i}function ut(e,t){let i=[...t].sort((d,u)=>(d.file??"").localeCompare(u.file??"")),o=i.slice(0,M).map(d=>({doc:d.file??"",citedClaims:d.citedClaims??[]})),n=i.length-o.length,a=i.every(d=>d.advisory===!0),s=i.reduce((d,u)=>Re(d,u.severity),"info"),l=i.reduce((d,u)=>Te(d,u.actionTier),void 0),c=i.map(d=>d.file??"").filter(d=>d.length>0),r=[...new Set(i.flatMap(d=>[d.id,...d.legacySuppressionIds??[]]))];return{id:y("doc-reference","hub-cluster",e),memberFindingIds:r,check:"doc-reference",severity:s,evidence:"change-graph",actionTier:l,groupKey:`doc-reference:${e}`,rootCauseKey:e,confidence:1,file:o[0]?.doc,relatedFiles:[e],citationCount:i.length,citationExemplars:o,suppressedCount:n,...a?{advisory:!0}:{},message:`${e} is cited by ${i.length} doc(s) changed in this diff, doc(s) untouched \u2014 showing ${o.length} example(s), ${n} more damped`,why:[`${e} changed in this diff.`,`${i.length} doc(s) cite it but were not updated in the same diff: ${c.join(", ")}.`,...o.map(d=>`Example \u2014 ${d.doc}: ${d.citedClaims[0]??"(no captured citation text)"}`),`${n} additional doc(s) also cite this file, past the per-hub-file cascade cap (${M}) \u2014 not silently dropped: counted in citationCount/suppressedCount and individually addressable via memberFindingIds.`,a?"Advisory: every citation in this cluster is a bare file-mention (no line anchor, cited file still present).":"Blocking: at least one citation in this cluster is line-anchored or cites a deleted/renamed file."],remediation:`Review the ${o.length} example doc(s) above; ${i.length} total doc(s) cite ${e} the same way \u2014 see citationExemplars and memberFindingIds in --json for the complete list.`}}function gt(e,t,i){i.checksRun.push("unused-params");for(let o of be(e,{files:t,limit:50})){let n=y("unused-params",o.symbol,o.file,o.unusedTrailing.join("|")),a=y("unused-params",o.symbol,o.file);k(i,{id:a,legacySuppressionIds:n===a?void 0:[n],check:"unused-params",severity:"warning",evidence:"heuristic",confidence:.85,file:o.file,startLine:o.startLine,endLine:o.endLine,symbol:o.symbol,message:`${o.shortName} (${o.file}) has trailing unused parameter(s): ${o.unusedTrailing.join(", ")}`,why:[`${o.shortName} is in a changed file.`,`The trailing parameter(s) ${o.unusedTrailing.join(", ")} are not referenced in the function body.`],remediation:"Drop the unused trailing parameters and their call-site arguments."})}}function mt(e,t,i,o){o.checksRun.push("new-dead");let n=new O(e),a=L(e.config.projectRoot),s=a.length>1;for(let l of t){if(i(l)||l.fanIn>0||n.fileKind(l.file)==="test"||R(e,l.file)||T(e,l.symbol,l.file)||bt(l.symbol))continue;let c=y("new-dead",l.symbol,l.file),r=s&&pt(a,l.file)&&yt(e,l.symbol);k(o,{id:c,check:"new-dead",severity:"warning",evidence:r?"heuristic":"graph-fact",confidence:r?.4:.9,file:l.file,symbol:l.symbol,message:r?`${l.shortName} (${l.file}) unconfirmed (cross-package ambiguous-name resolution gap): zero indexed consumers found, but this name is ambiguous project-wide and lives in a workspace package \u2014 verify manually before treating as dead`:`${l.shortName} (${l.file}) was changed but has zero indexed consumers`,why:r?[`${l.shortName} is a changed production symbol in a workspace package.`,"The index, semantic, and source-fallback layers all report zero consumers for this symbol.","Its leaf name is ambiguous project-wide (a same-named definition exists elsewhere), and cross-package consumers reached only through a re-exporting barrel are a known unresolvable shape for this tool \u2014 see docs/plans/2026-07-02-followups.md item 2."]:[`${l.shortName} is a changed production symbol.`,"The index reports zero consumers for this symbol.","The symbol is not in a detected entry surface or configured live root."],remediation:r?"Verify with a direct code search before deleting; this tool cannot confidently resolve this symbol.":"Wire it up, or remove it before it becomes permanent dead code."})}}function pt(e,t){return e.some(i=>t===i.relativeDir||t.startsWith(`${i.relativeDir}/`))}function yt(e,t){let i=F(t);return i?(H(e).get(i)?.length??0)>1:!1}function bt(e){if(z(e)!=="type")return!1;let t=F(e);return t.startsWith("_Assert")||t.startsWith("Assert")}function Ct(e,t,i,o){let n=new Map(i.renamedFiles.map(s=>[s.to,s.from])),a=o??j(e,t,B(i));return s=>{let l=n.get(s.file)??s.file,c=F(s.symbol);if(!c)return!1;let r=a(l);return r!==null&&$t(r,c)}}function $t(e,t){return new RegExp(`\\b${_(t)}\\b`).test(e)}function St(e,t){if(!K(x(e))){t.skipped.push({check:"baseline",reason:"no .scipquery-baseline.json \u2014 run health --write-baseline to enable"});return}t.checksRun.push("baseline");let i=Se(e);for(let o of i.newFindings){let n=Ce(o),a=y("baseline",o);k(t,{id:a,groupKey:`baseline:${n.sourceAnalyzer}:${n.rootCauseKey}`,check:"baseline",severity:"error",evidence:"baseline",actionTier:n.actionTier,confidence:1,file:n.file,relatedFiles:n.relatedFiles.length>0?n.relatedFiles:void 0,sourceAnalyzer:n.sourceAnalyzer,rootCauseKey:n.rootCauseKey,message:`new ${n.label} vs committed baseline: ${o}`,why:["A committed health baseline exists.","The current health result contains a finding not present in that baseline.",`Underlying analyzer: ${n.sourceAnalyzer}.`,`Inherited action tier: ${n.actionTier}.`,`Root cause key: ${n.rootCauseKey}.`,...n.why],remediation:n.remediation})}}function y(e,...t){return`SQ${We("sha256").update([e,...t].join("\0")).digest("hex").slice(0,12).toUpperCase()}`}export{$e as a,Kt as b,Se as c,ci as d,li as e,fi as f,di as g,Ct as h};
3
- //# sourceMappingURL=chunk-XC2PJOVM.js.map