@titan-design/code-graph 0.8.0 → 0.9.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.
package/README.md CHANGED
@@ -148,7 +148,7 @@ always recomputed over the whole assembled graph, so a heavily-reused run and a
148
148
 
149
149
  ## Store layout
150
150
 
151
- `openCodeGraph` opens the database with the kit's pragmas and runs three migrations.
151
+ `openCodeGraph` opens the database with the kit's pragmas and runs four migrations.
152
152
 
153
153
  It refuses a database stamped past `SCHEMA_VERSION` with `SchemaTooNewError`: a code graph
154
154
  database belongs to one build, so a higher version means a newer build already moved the
@@ -163,7 +163,8 @@ every consumer reads directly: `node.language`, `node.role`, `snapshot.commit_ha
163
163
  Domain tables in `schema.ts`: `edge` (snapshot-scoped, keyed
164
164
  `(snapshot_id, src_id, dst_id, kind)` — the kit's own edge table is bi-temporal, which is the
165
165
  wrong time model for a population re-indexed all at once), `metric`, `id_alias`, and
166
- `file_fingerprint`.
166
+ `file_fingerprint`. Migration 4 adds `finding` and `verdict`, both keyed
167
+ `(snapshot_id, key)` on a `findingKey` and pruned with their snapshot.
167
168
 
168
169
  The symbol layer is hidden by default. `listNodes` drops `symbol` nodes and `listEdges` drops
169
170
  `references` and `calls` edges unless you ask for them (`includeReferences` covers both), so a caller reasoning about module structure sees
package/dist/index.d.ts CHANGED
@@ -175,8 +175,14 @@ declare const KIT: {
175
175
  * `file_fingerprint` is the reuse basis the incremental indexer diffs against.
176
176
  */
177
177
  declare const DOMAIN_DDL = "\n CREATE TABLE IF NOT EXISTS edge (\n snapshot_id INTEGER NOT NULL,\n src_id TEXT NOT NULL,\n dst_id TEXT NOT NULL,\n kind TEXT NOT NULL,\n attrs TEXT,\n PRIMARY KEY (snapshot_id, src_id, dst_id, kind)\n );\n CREATE INDEX IF NOT EXISTS idx_edge_dst ON edge(snapshot_id, dst_id);\n CREATE INDEX IF NOT EXISTS idx_edge_kind ON edge(snapshot_id, kind);\n\n CREATE TABLE IF NOT EXISTS metric (\n snapshot_id INTEGER NOT NULL,\n node_id TEXT NOT NULL,\n name TEXT NOT NULL,\n value REAL,\n unit TEXT,\n PRIMARY KEY (snapshot_id, node_id, name)\n );\n CREATE INDEX IF NOT EXISTS idx_metric_name ON metric(snapshot_id, name);\n\n CREATE TABLE IF NOT EXISTS id_alias (\n snapshot_id INTEGER NOT NULL,\n old_id TEXT NOT NULL,\n new_id TEXT NOT NULL,\n reason TEXT NOT NULL,\n PRIMARY KEY (snapshot_id, old_id, new_id)\n );\n\n CREATE TABLE IF NOT EXISTS file_fingerprint (\n snapshot_id INTEGER NOT NULL,\n file_id TEXT NOT NULL,\n content_hash TEXT NOT NULL,\n structural_hash TEXT,\n PRIMARY KEY (snapshot_id, file_id)\n );\n";
178
+ /**
179
+ * Stored audit findings and model verdicts. `key` is a `findingKey`, not `Finding.id`,
180
+ * because an external tool's id embeds its line number and can repeat within one audit.
181
+ * The whole `Finding` and a verdict's citations live in `attrs`.
182
+ */
183
+ declare const FINDING_DDL = "\n CREATE TABLE IF NOT EXISTS finding (\n snapshot_id INTEGER NOT NULL,\n key TEXT NOT NULL,\n finding_id TEXT NOT NULL,\n tool TEXT NOT NULL,\n signal TEXT NOT NULL,\n path TEXT NOT NULL,\n line_start INTEGER,\n line_end INTEGER,\n severity TEXT NOT NULL,\n excerpt_hash TEXT,\n attrs TEXT NOT NULL,\n PRIMARY KEY (snapshot_id, key)\n );\n CREATE INDEX IF NOT EXISTS idx_finding_path ON finding(snapshot_id, path);\n\n CREATE TABLE IF NOT EXISTS verdict (\n snapshot_id INTEGER NOT NULL,\n key TEXT NOT NULL,\n verdict TEXT NOT NULL CHECK (verdict IN ('confirmed', 'justified', 'unclear')),\n rationale TEXT NOT NULL,\n excerpt_hash TEXT NOT NULL,\n model TEXT,\n run_id TEXT,\n cost_usd REAL,\n provenance TEXT,\n control_run TEXT,\n carried_from INTEGER,\n attrs TEXT NOT NULL,\n PRIMARY KEY (snapshot_id, key)\n );\n";
178
184
  /** Every table keyed by `snapshot_id`. No DDL above declares a foreign key, so dropping a snapshot walks this list. */
179
- declare const SNAPSHOT_SCOPED_TABLES: readonly ["node", "edge", "metric", "id_alias", "file_fingerprint"];
185
+ declare const SNAPSHOT_SCOPED_TABLES: readonly ["node", "edge", "metric", "id_alias", "file_fingerprint", "finding", "verdict"];
180
186
  declare const MIGRATIONS: Migration[];
181
187
  /** The top of this package's schema. A code graph database is never shared, so this is the top of one. */
182
188
  declare const SCHEMA_VERSION: number;
@@ -1299,6 +1305,67 @@ interface ExternalDiagnostic {
1299
1305
  declare function toFindings(result: CheckResult): Finding[];
1300
1306
  declare function externalToFinding(input: ExternalDiagnostic): Finding;
1301
1307
 
1308
+ type VerdictLabel = "confirmed" | "justified" | "unclear";
1309
+ interface VerdictCitation {
1310
+ path: string;
1311
+ lineStart: number;
1312
+ lineEnd: number;
1313
+ quote: string;
1314
+ }
1315
+ /** A finding as stored against a snapshot, addressed by its `findingKey`. */
1316
+ interface StoredFinding {
1317
+ key: string;
1318
+ finding: Finding;
1319
+ /** Hash of the excerpt a reader would be shown for this finding; carry-forward compares it. */
1320
+ excerptHash?: string;
1321
+ }
1322
+ /** A model's judgement of one stored finding, after its citations were verified. */
1323
+ interface StoredVerdict {
1324
+ key: string;
1325
+ verdict: VerdictLabel;
1326
+ rationale: string;
1327
+ citations: VerdictCitation[];
1328
+ /** Hash of the excerpt the verdict judged. */
1329
+ excerptHash: string;
1330
+ model?: string;
1331
+ costUsd?: number;
1332
+ runId?: string;
1333
+ provenance?: string;
1334
+ controlRun?: "ok" | "provisional";
1335
+ dropped?: string[];
1336
+ /** The snapshot this verdict was carried from, when carry-forward copied it. */
1337
+ carriedFrom?: number;
1338
+ }
1339
+ interface FindingKeyInput {
1340
+ finding: Finding;
1341
+ /** The innermost symbol node id containing the finding; the finding's path when absent. */
1342
+ anchor?: string;
1343
+ /** The source text of the flagged lines. */
1344
+ flaggedText: string;
1345
+ excerptHash?: string;
1346
+ }
1347
+ /** Whitespace-insensitive, so reindenting or reflowing the flagged lines keeps the key. */
1348
+ declare function normalizeFlaggedText(text: string): string;
1349
+ declare function hashText(text: string): string;
1350
+ /**
1351
+ * A finding's identity across snapshots: no line number, so an inserted line above it
1352
+ * keeps the key, and a collision ordinal separates same-rule findings on the same text.
1353
+ */
1354
+ declare function findingKey(input: FindingKeyInput, ordinal?: number): string;
1355
+ /** Key a whole audit's findings, numbering collisions in source order so the result ignores input order. */
1356
+ declare function keyFindings(inputs: readonly FindingKeyInput[]): StoredFinding[];
1357
+ /** Insert or replace findings by key within one snapshot. */
1358
+ declare function saveFindings(store: CodeGraphStore, snapshotId: number, findings: readonly StoredFinding[]): void;
1359
+ declare function listFindings(store: CodeGraphStore, snapshotId: number): StoredFinding[];
1360
+ /** Insert or replace verdicts by key within one snapshot. */
1361
+ declare function saveVerdicts(store: CodeGraphStore, snapshotId: number, verdicts: readonly StoredVerdict[]): void;
1362
+ declare function listVerdicts(store: CodeGraphStore, snapshotId: number): StoredVerdict[];
1363
+ /**
1364
+ * Copy each verdict of `from` onto the finding in `to` with the same key whose excerpt hash
1365
+ * equals the one the verdict judged. A verdict `to` already holds is kept. Returns the count copied.
1366
+ */
1367
+ declare function carryForwardVerdicts(store: CodeGraphStore, from: number, to: number): number;
1368
+
1302
1369
  /** Glob when the pattern has a `*` (`**` crosses directories, `*` stays in a segment); otherwise a case-sensitive substring. */
1303
1370
  declare function patternToRegex(pattern: string): RegExp;
1304
1371
  declare function compilePatterns(patterns: readonly string[] | undefined): RegExp[];
@@ -1619,4 +1686,4 @@ declare function getConventionMap(store: CodeGraphStore, snapshotId: number, mod
1619
1686
  */
1620
1687
  declare function findConventions(store: CodeGraphStore, snapshotId: number, query: string, embedder: Embedder, model: string, opts?: FindConventionsOptions): Promise<ConventionQueryResult>;
1621
1688
 
1622
- export { ALIAS_BASE_ATTR, ALL_ROLES, type AliasChain, type AliasChainInput, type AliasChainOptions, type AliasLoader, type AliasResolution, COMMUNITY_SUMMARY_NAMESPACE, COVERAGE_METRIC_NAME, type CachedEmbedResult, type CheckDiff, type CheckResult, type CheckRule, type CheckRulesFile, type CheckSnapshotOptions, type CheckSnapshotResult, type CheckViolation, CodeGraphStore, type ConventionArea, type ConventionCorpus, type ConventionCoverage, type ConventionMap, type ConventionMatch, type ConventionOptions, type ConventionQueryResult, type ConventionSymbol, DEAD_CODE_METRIC_NAMES, DEFAULT_CHURN_WINDOWS, DEFAULT_OUTLIER_MIN_SAMPLE, DOMAIN_DDL, type DeepAst, type DeepAstInput, type DiffCheckResultsOptions, type DiffSnapshotsOptions, EXCEPTION_METRIC_NAMES, type EdgeKind, type EmbedAttempt, type EmbedCoverage, type EmbedSnapshotResult, type EmbeddableSymbol, type ExternalDiagnostic, type FileFingerprint, type FindConventionsOptions, type FindSimilarOptions, type Finding, type ForbidImportRule, GROWTH_RISK_METRIC_NAMES, type GraphDiff, type GraphDiffSummary, type GraphEdge, type GraphFragment, type GraphMetric, type GraphNode, type HistoryMetricsOptions, INDEX_VERSION, type IdAlias, type IdAliasReason, type IndexOptions, type IndexResult, type IstanbulCoverage, KIT, LanguageExtractor, type LanguageExtractorOptions, type LayeredDepsRule, type LineSpan, type Lineage, type LineageSnapshot, type LineageStep, type LinkMethod, type LinkTestsOptions, type LoadedHistory, METRIC_CATALOGUE, MIGRATIONS, type MemberInfo, type MetricAbsence, type MetricAggregate, type MetricDelta, type MetricDescriptor, type MetricDirection, type MetricMaxRule, type MetricMinRule, type MetricOutlierRule, type MetricProductMaxRule, type MetricRollup, type MetricSource, type MetricUnit, type NoInternalOnlyBarrelsRule, type NodeKind, type NodeRename, type NodeRole, type PackageFlag, type PackageLayer, type PackageStats, type PageRankOptions, type PageRankResult, type PageRankRow, type PairCoupling, type PairFlag, type ParamInfo, type PartitionQualityInput, type PartitionQualityResult, type PriorSnapshotOptions, type PruneOptions, type PrunePlan, type PruneResult, PythonGraphExtractor, type ReadFile, type ReferenceEdgeLite, type RelevanceOptions, type ResolveAliasOptions, type ReuseBasis, type RuleStore, type RunChecksOptions, SCHEMA_VERSION, SNAPSHOT_SCOPED_TABLES, SOURCE_METRIC_NAMES, SYMBOL_EMBEDDING_NAMESPACE, SYMBOL_ID_SEP, SYMBOL_METRIC_NAMES, type Severity, type SimilarCandidate, type SimilarResult, type SnapshotInsert, type SnapshotRow, type SnapshotSpec, type SourceLanguage, type SummarizeConventionsResult, type Summarizer, type SymbolConsumers, type SymbolCouplingOptions, type SymbolCouplingPair, type SymbolSpan, type TestCoverageOwnershipOptions, type TestSourceLink, type TopMetricRow, TsMorphGraphExtractor, type TsMorphGraphExtractorOptions, type UnchangedViolation, type ValidateRulesOptions, aggregateMetrics, aliasChain, annotateRoles, attributeCoverage, buildAliases, buildConventionAreas, buildEmbedText, buildFileModuleNodes, buildIndexerMetrics, buildLineage, canonicalEdgeKind, canonicalMetricName, canonicalRole, checkSnapshot, classifyRole, collectDeclaredNames, collectDeclaredSpans, compilePatterns, computeDeadCodeMetrics, computeDeepAst, computeGrowthRiskMetrics, computeMetrics, computePageRank, computePartitionQuality, computeRecencyWindows, computeRelevance, computeRoleHints, computeSourceMetrics, computeSymbolConsumers, computeSymbolCoupling, computeTestCoverageOwnership, createAliasChain, defaultTargetCount, describeMetric, describeMetrics, detectCommunities, detectGitHead, detectGitToplevel, detectRenames, diffCheckResults, diffSnapshots, edgeWeight, embedSnapshot, embedTextsCached, externalId, externalToFinding, fileId, findConventions, findSimilarCapability, getConventionMap, getEdgeWeight, groupTestsBySource, hashContent, hashEmbedText, indexPaths, invertBuckets, isGeneratedByHeuristic, isGeneratedFile, isInsideGitRepo, lineagePath, linkTestsToSources, listEdges, listEdgesTouching, listEmbeddableSymbols, listMetrics, listMetricsForNode, listNodes, loadCheckRules, loadGeneratedPatterns, loadHistoryMetrics, loadLineage, matchesAny, moduleId, openCodeGraph, packageId, parentModuleId, parseSymbolId, patternToRegex, percentileOf, planPrune, priorSnapshotForRef, pruneDanglingReferences, readSourceFiles, rebasedViolationKey, resolveAlias, resolveBarrelEdges, resolveChurnWindows, resolveGitRef, resolveSnapshot, runChecks, runPrune, snapshotPageRank, snapshotReferenceEdges, snapshotRelevance, snapshotSymbolConsumers, snapshotSymbolCoupling, snapshotViolations, structuralSignature, summarizeConventions, symbolId, testCoverageCountMetrics, toFindings, tryEmbedSnapshot, validateRules, violationKey, walkSourceFiles, windowSuffix };
1689
+ export { ALIAS_BASE_ATTR, ALL_ROLES, type AliasChain, type AliasChainInput, type AliasChainOptions, type AliasLoader, type AliasResolution, COMMUNITY_SUMMARY_NAMESPACE, COVERAGE_METRIC_NAME, type CachedEmbedResult, type CheckDiff, type CheckResult, type CheckRule, type CheckRulesFile, type CheckSnapshotOptions, type CheckSnapshotResult, type CheckViolation, CodeGraphStore, type ConventionArea, type ConventionCorpus, type ConventionCoverage, type ConventionMap, type ConventionMatch, type ConventionOptions, type ConventionQueryResult, type ConventionSymbol, DEAD_CODE_METRIC_NAMES, DEFAULT_CHURN_WINDOWS, DEFAULT_OUTLIER_MIN_SAMPLE, DOMAIN_DDL, type DeepAst, type DeepAstInput, type DiffCheckResultsOptions, type DiffSnapshotsOptions, EXCEPTION_METRIC_NAMES, type EdgeKind, type EmbedAttempt, type EmbedCoverage, type EmbedSnapshotResult, type EmbeddableSymbol, type ExternalDiagnostic, FINDING_DDL, type FileFingerprint, type FindConventionsOptions, type FindSimilarOptions, type Finding, type FindingKeyInput, type ForbidImportRule, GROWTH_RISK_METRIC_NAMES, type GraphDiff, type GraphDiffSummary, type GraphEdge, type GraphFragment, type GraphMetric, type GraphNode, type HistoryMetricsOptions, INDEX_VERSION, type IdAlias, type IdAliasReason, type IndexOptions, type IndexResult, type IstanbulCoverage, KIT, LanguageExtractor, type LanguageExtractorOptions, type LayeredDepsRule, type LineSpan, type Lineage, type LineageSnapshot, type LineageStep, type LinkMethod, type LinkTestsOptions, type LoadedHistory, METRIC_CATALOGUE, MIGRATIONS, type MemberInfo, type MetricAbsence, type MetricAggregate, type MetricDelta, type MetricDescriptor, type MetricDirection, type MetricMaxRule, type MetricMinRule, type MetricOutlierRule, type MetricProductMaxRule, type MetricRollup, type MetricSource, type MetricUnit, type NoInternalOnlyBarrelsRule, type NodeKind, type NodeRename, type NodeRole, type PackageFlag, type PackageLayer, type PackageStats, type PageRankOptions, type PageRankResult, type PageRankRow, type PairCoupling, type PairFlag, type ParamInfo, type PartitionQualityInput, type PartitionQualityResult, type PriorSnapshotOptions, type PruneOptions, type PrunePlan, type PruneResult, PythonGraphExtractor, type ReadFile, type ReferenceEdgeLite, type RelevanceOptions, type ResolveAliasOptions, type ReuseBasis, type RuleStore, type RunChecksOptions, SCHEMA_VERSION, SNAPSHOT_SCOPED_TABLES, SOURCE_METRIC_NAMES, SYMBOL_EMBEDDING_NAMESPACE, SYMBOL_ID_SEP, SYMBOL_METRIC_NAMES, type Severity, type SimilarCandidate, type SimilarResult, type SnapshotInsert, type SnapshotRow, type SnapshotSpec, type SourceLanguage, type StoredFinding, type StoredVerdict, type SummarizeConventionsResult, type Summarizer, type SymbolConsumers, type SymbolCouplingOptions, type SymbolCouplingPair, type SymbolSpan, type TestCoverageOwnershipOptions, type TestSourceLink, type TopMetricRow, TsMorphGraphExtractor, type TsMorphGraphExtractorOptions, type UnchangedViolation, type ValidateRulesOptions, type VerdictCitation, type VerdictLabel, aggregateMetrics, aliasChain, annotateRoles, attributeCoverage, buildAliases, buildConventionAreas, buildEmbedText, buildFileModuleNodes, buildIndexerMetrics, buildLineage, canonicalEdgeKind, canonicalMetricName, canonicalRole, carryForwardVerdicts, checkSnapshot, classifyRole, collectDeclaredNames, collectDeclaredSpans, compilePatterns, computeDeadCodeMetrics, computeDeepAst, computeGrowthRiskMetrics, computeMetrics, computePageRank, computePartitionQuality, computeRecencyWindows, computeRelevance, computeRoleHints, computeSourceMetrics, computeSymbolConsumers, computeSymbolCoupling, computeTestCoverageOwnership, createAliasChain, defaultTargetCount, describeMetric, describeMetrics, detectCommunities, detectGitHead, detectGitToplevel, detectRenames, diffCheckResults, diffSnapshots, edgeWeight, embedSnapshot, embedTextsCached, externalId, externalToFinding, fileId, findConventions, findSimilarCapability, findingKey, getConventionMap, getEdgeWeight, groupTestsBySource, hashContent, hashEmbedText, hashText, indexPaths, invertBuckets, isGeneratedByHeuristic, isGeneratedFile, isInsideGitRepo, keyFindings, lineagePath, linkTestsToSources, listEdges, listEdgesTouching, listEmbeddableSymbols, listFindings, listMetrics, listMetricsForNode, listNodes, listVerdicts, loadCheckRules, loadGeneratedPatterns, loadHistoryMetrics, loadLineage, matchesAny, moduleId, normalizeFlaggedText, openCodeGraph, packageId, parentModuleId, parseSymbolId, patternToRegex, percentileOf, planPrune, priorSnapshotForRef, pruneDanglingReferences, readSourceFiles, rebasedViolationKey, resolveAlias, resolveBarrelEdges, resolveChurnWindows, resolveGitRef, resolveSnapshot, runChecks, runPrune, saveFindings, saveVerdicts, snapshotPageRank, snapshotReferenceEdges, snapshotRelevance, snapshotSymbolConsumers, snapshotSymbolCoupling, snapshotViolations, structuralSignature, summarizeConventions, symbolId, testCoverageCountMetrics, toFindings, tryEmbedSnapshot, validateRules, violationKey, walkSourceFiles, windowSuffix };
package/dist/index.js CHANGED
@@ -62,11 +62,53 @@ var DOMAIN_DDL = `
62
62
  PRIMARY KEY (snapshot_id, file_id)
63
63
  );
64
64
  `;
65
- var SNAPSHOT_SCOPED_TABLES = ["node", "edge", "metric", "id_alias", "file_fingerprint"];
65
+ var FINDING_DDL = `
66
+ CREATE TABLE IF NOT EXISTS finding (
67
+ snapshot_id INTEGER NOT NULL,
68
+ key TEXT NOT NULL,
69
+ finding_id TEXT NOT NULL,
70
+ tool TEXT NOT NULL,
71
+ signal TEXT NOT NULL,
72
+ path TEXT NOT NULL,
73
+ line_start INTEGER,
74
+ line_end INTEGER,
75
+ severity TEXT NOT NULL,
76
+ excerpt_hash TEXT,
77
+ attrs TEXT NOT NULL,
78
+ PRIMARY KEY (snapshot_id, key)
79
+ );
80
+ CREATE INDEX IF NOT EXISTS idx_finding_path ON finding(snapshot_id, path);
81
+
82
+ CREATE TABLE IF NOT EXISTS verdict (
83
+ snapshot_id INTEGER NOT NULL,
84
+ key TEXT NOT NULL,
85
+ verdict TEXT NOT NULL CHECK (verdict IN ('confirmed', 'justified', 'unclear')),
86
+ rationale TEXT NOT NULL,
87
+ excerpt_hash TEXT NOT NULL,
88
+ model TEXT,
89
+ run_id TEXT,
90
+ cost_usd REAL,
91
+ provenance TEXT,
92
+ control_run TEXT,
93
+ carried_from INTEGER,
94
+ attrs TEXT NOT NULL,
95
+ PRIMARY KEY (snapshot_id, key)
96
+ );
97
+ `;
98
+ var SNAPSHOT_SCOPED_TABLES = [
99
+ "node",
100
+ "edge",
101
+ "metric",
102
+ "id_alias",
103
+ "file_fingerprint",
104
+ "finding",
105
+ "verdict"
106
+ ];
66
107
  var MIGRATIONS = [
67
108
  kitMigration(1, { snapshot: KIT.snapshot, entitySnap: KIT.entitySnap, cacheBlob: KIT.cacheBlob }),
68
109
  { version: 2, name: "code graph columns", up: (db) => db.exec(KIT_EXTENSIONS) },
69
- { version: 3, name: "code graph tables", up: (db) => db.exec(DOMAIN_DDL) }
110
+ { version: 3, name: "code graph tables", up: (db) => db.exec(DOMAIN_DDL) },
111
+ { version: 4, name: "findings and verdicts", up: (db) => db.exec(FINDING_DDL) }
70
112
  ];
71
113
  var SCHEMA_VERSION = Math.max(...MIGRATIONS.map((m) => m.version));
72
114
 
@@ -5605,6 +5647,127 @@ function withoutUndefined(finding) {
5605
5647
  return Object.fromEntries(Object.entries(finding).filter(([, v]) => v !== void 0));
5606
5648
  }
5607
5649
 
5650
+ // src/check/finding-store.ts
5651
+ import { createHash as createHash2 } from "crypto";
5652
+ function normalizeFlaggedText(text) {
5653
+ return text.split(/\r?\n/).map((line) => line.trim().replace(/\s+/g, " ")).filter((line) => line.length > 0).join("\n");
5654
+ }
5655
+ function hashText(text) {
5656
+ return createHash2("sha256").update(text, "utf8").digest("hex").slice(0, 16);
5657
+ }
5658
+ function findingKey(input, ordinal = 0) {
5659
+ const { finding } = input;
5660
+ const anchor = input.anchor ?? finding.path;
5661
+ const textHash = hashText(normalizeFlaggedText(input.flaggedText));
5662
+ return `${finding.tool}:${finding.signal}:${anchor}:${textHash}#${ordinal}`;
5663
+ }
5664
+ function keyFindings(inputs) {
5665
+ const ordered = [...inputs].sort(compareBySource);
5666
+ const seen = /* @__PURE__ */ new Map();
5667
+ return ordered.map((input) => {
5668
+ const base = findingKey(input);
5669
+ const ordinal = seen.get(base) ?? 0;
5670
+ seen.set(base, ordinal + 1);
5671
+ const stored = { key: findingKey(input, ordinal), finding: input.finding };
5672
+ if (input.excerptHash !== void 0) stored.excerptHash = input.excerptHash;
5673
+ return stored;
5674
+ });
5675
+ }
5676
+ function compareBySource(a, b) {
5677
+ const fa = a.finding;
5678
+ const fb = b.finding;
5679
+ return fa.path.localeCompare(fb.path) || (fa.lineStart ?? 0) - (fb.lineStart ?? 0) || (fa.lineEnd ?? 0) - (fb.lineEnd ?? 0) || (fa.evidence ?? "").localeCompare(fb.evidence ?? "") || fa.id.localeCompare(fb.id);
5680
+ }
5681
+ function saveFindings(store, snapshotId, findings) {
5682
+ const insert = store.db.prepare(
5683
+ `INSERT OR REPLACE INTO finding
5684
+ (snapshot_id, key, finding_id, tool, signal, path, line_start, line_end, severity, excerpt_hash, attrs)
5685
+ VALUES (@snapshotId, @key, @findingId, @tool, @signal, @path, @lineStart, @lineEnd, @severity, @excerptHash, @attrs)`
5686
+ );
5687
+ store.db.transaction(() => {
5688
+ for (const { key, finding, excerptHash } of findings) {
5689
+ insert.run({
5690
+ snapshotId,
5691
+ key,
5692
+ findingId: finding.id,
5693
+ tool: finding.tool,
5694
+ signal: finding.signal,
5695
+ path: finding.path,
5696
+ lineStart: finding.lineStart ?? null,
5697
+ lineEnd: finding.lineEnd ?? null,
5698
+ severity: finding.severity,
5699
+ excerptHash: excerptHash ?? null,
5700
+ attrs: JSON.stringify(finding)
5701
+ });
5702
+ }
5703
+ })();
5704
+ }
5705
+ function listFindings(store, snapshotId) {
5706
+ const rows = store.db.prepare("SELECT key, excerpt_hash, attrs FROM finding WHERE snapshot_id = ? ORDER BY key").all(snapshotId);
5707
+ return rows.map((row) => {
5708
+ const stored = { key: row.key, finding: JSON.parse(row.attrs) };
5709
+ if (row.excerpt_hash !== null) stored.excerptHash = row.excerpt_hash;
5710
+ return stored;
5711
+ });
5712
+ }
5713
+ function saveVerdicts(store, snapshotId, verdicts) {
5714
+ const insert = store.db.prepare(
5715
+ `INSERT OR REPLACE INTO verdict
5716
+ (snapshot_id, key, verdict, rationale, excerpt_hash, model, run_id, cost_usd, provenance, control_run,
5717
+ carried_from, attrs)
5718
+ VALUES (@snapshotId, @key, @verdict, @rationale, @excerptHash, @model, @runId, @costUsd, @provenance,
5719
+ @controlRun, @carriedFrom, @attrs)`
5720
+ );
5721
+ store.db.transaction(() => {
5722
+ for (const v of verdicts) insert.run(verdictParams(snapshotId, v));
5723
+ })();
5724
+ }
5725
+ function verdictParams(snapshotId, v) {
5726
+ return {
5727
+ snapshotId,
5728
+ key: v.key,
5729
+ verdict: v.verdict,
5730
+ rationale: v.rationale,
5731
+ excerptHash: v.excerptHash,
5732
+ model: v.model ?? null,
5733
+ runId: v.runId ?? null,
5734
+ costUsd: v.costUsd ?? null,
5735
+ provenance: v.provenance ?? null,
5736
+ controlRun: v.controlRun ?? null,
5737
+ carriedFrom: v.carriedFrom ?? null,
5738
+ attrs: JSON.stringify({ citations: v.citations, dropped: v.dropped ?? [] })
5739
+ };
5740
+ }
5741
+ function listVerdicts(store, snapshotId) {
5742
+ const rows = store.db.prepare("SELECT * FROM verdict WHERE snapshot_id = ? ORDER BY key").all(snapshotId);
5743
+ return rows.map(rowToVerdict);
5744
+ }
5745
+ function rowToVerdict(row) {
5746
+ const attrs = JSON.parse(row.attrs);
5747
+ const verdict = {
5748
+ key: row.key,
5749
+ verdict: row.verdict,
5750
+ rationale: row.rationale,
5751
+ citations: attrs.citations,
5752
+ excerptHash: row.excerpt_hash
5753
+ };
5754
+ if (row.model !== null) verdict.model = row.model;
5755
+ if (row.cost_usd !== null) verdict.costUsd = row.cost_usd;
5756
+ if (row.run_id !== null) verdict.runId = row.run_id;
5757
+ if (row.provenance !== null) verdict.provenance = row.provenance;
5758
+ if (row.control_run !== null) verdict.controlRun = row.control_run;
5759
+ if (attrs.dropped.length > 0) verdict.dropped = attrs.dropped;
5760
+ if (row.carried_from !== null) verdict.carriedFrom = row.carried_from;
5761
+ return verdict;
5762
+ }
5763
+ function carryForwardVerdicts(store, from, to) {
5764
+ const targets = new Map(listFindings(store, to).map((f) => [f.key, f.excerptHash]));
5765
+ const existing = new Set(listVerdicts(store, to).map((v) => v.key));
5766
+ const carried = listVerdicts(store, from).filter((v) => !existing.has(v.key) && targets.get(v.key) === v.excerptHash).map((v) => ({ ...v, carriedFrom: v.carriedFrom ?? from }));
5767
+ saveVerdicts(store, to, carried);
5768
+ return carried.length;
5769
+ }
5770
+
5608
5771
  // src/check/validate.ts
5609
5772
  function validateRules(input, options = {}) {
5610
5773
  if (!input || typeof input !== "object") {
@@ -6489,6 +6652,7 @@ export {
6489
6652
  DEFAULT_OUTLIER_MIN_SAMPLE,
6490
6653
  DOMAIN_DDL,
6491
6654
  EXCEPTION_METRIC_NAMES,
6655
+ FINDING_DDL,
6492
6656
  GROWTH_RISK_METRIC_NAMES,
6493
6657
  INDEX_VERSION,
6494
6658
  KIT,
@@ -6516,6 +6680,7 @@ export {
6516
6680
  canonicalEdgeKind,
6517
6681
  canonicalMetricName,
6518
6682
  canonicalRole,
6683
+ carryForwardVerdicts,
6519
6684
  checkSnapshot,
6520
6685
  classifyRole,
6521
6686
  collectDeclaredNames,
@@ -6552,6 +6717,7 @@ export {
6552
6717
  fileId,
6553
6718
  findConventions,
6554
6719
  findSimilarCapability,
6720
+ findingKey,
6555
6721
  getConventionMap,
6556
6722
  getEdgeWeight,
6557
6723
  getLanguageFromPath3 as getLanguageFromPath,
@@ -6559,25 +6725,30 @@ export {
6559
6725
  groupTestsBySource,
6560
6726
  hashContent,
6561
6727
  hashEmbedText,
6728
+ hashText,
6562
6729
  indexPaths,
6563
6730
  invertBuckets,
6564
6731
  isGeneratedByHeuristic,
6565
6732
  isGeneratedFile,
6566
6733
  isInsideGitRepo,
6734
+ keyFindings,
6567
6735
  lineagePath,
6568
6736
  linkTestsToSources,
6569
6737
  listEdges,
6570
6738
  listEdgesTouching,
6571
6739
  listEmbeddableSymbols,
6740
+ listFindings,
6572
6741
  listMetrics,
6573
6742
  listMetricsForNode,
6574
6743
  listNodes,
6744
+ listVerdicts,
6575
6745
  loadCheckRules,
6576
6746
  loadGeneratedPatterns,
6577
6747
  loadHistoryMetrics,
6578
6748
  loadLineage,
6579
6749
  matchesAny,
6580
6750
  moduleId,
6751
+ normalizeFlaggedText,
6581
6752
  openCodeGraph,
6582
6753
  packageId,
6583
6754
  parentModuleId,
@@ -6597,6 +6768,8 @@ export {
6597
6768
  resolveSnapshot,
6598
6769
  runChecks,
6599
6770
  runPrune,
6771
+ saveFindings,
6772
+ saveVerdicts,
6600
6773
  shouldIncludeFile2 as shouldIncludeFile,
6601
6774
  snapshotPageRank,
6602
6775
  snapshotReferenceEdges,