snipara-companion 3.6.1 → 3.6.2
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/CHANGELOG.md +13 -0
- package/dist/index.d.ts +58 -1
- package/dist/index.js +469 -8
- package/docs/CODE_GRAPH.md +74 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
Release notes for `snipara-companion`, newest first.
|
|
4
4
|
|
|
5
|
+
## New In 3.6.2
|
|
6
|
+
|
|
7
|
+
- Adds `snipara.semantic.v1` to local code overlays and impact results with
|
|
8
|
+
evidence-backed public APIs, implicit contracts, architecture roles, and
|
|
9
|
+
critical-versus-incidental dependency classification.
|
|
10
|
+
- Marks explicit TypeScript and Go exports during extraction and bumps the
|
|
11
|
+
local extractor version to `3` while preserving the overlay v2 envelope.
|
|
12
|
+
- Adds semantic risk points to local impact, preserving every contributing
|
|
13
|
+
assertion and rule as inspectable evidence.
|
|
14
|
+
- Merges local and hosted semantic assertions with explicit provenance in
|
|
15
|
+
hybrid mode. Historical regression-path associations stay shadow-only and
|
|
16
|
+
cannot silently change the risk score.
|
|
17
|
+
|
|
5
18
|
## New In 3.6.1
|
|
6
19
|
|
|
7
20
|
- Enforces `--max-nodes` as a strict total traversal budget, including seed
|
package/dist/index.d.ts
CHANGED
|
@@ -1808,6 +1808,61 @@ interface LocalCodeOverlaySymbol {
|
|
|
1808
1808
|
endLine?: number;
|
|
1809
1809
|
qualifiedName: string;
|
|
1810
1810
|
localKey: string;
|
|
1811
|
+
exported?: boolean;
|
|
1812
|
+
defaultExport?: boolean;
|
|
1813
|
+
}
|
|
1814
|
+
type LocalSemanticPredicate = "public_api" | "implicit_contract" | "architecture_role" | "dependency_criticality";
|
|
1815
|
+
interface LocalSemanticEvidence {
|
|
1816
|
+
kind: string;
|
|
1817
|
+
detail: string;
|
|
1818
|
+
filePath?: string;
|
|
1819
|
+
line?: number;
|
|
1820
|
+
edgeKind?: LocalCodeOverlayEdge["kind"];
|
|
1821
|
+
}
|
|
1822
|
+
interface LocalSemanticAssertion {
|
|
1823
|
+
id: string;
|
|
1824
|
+
subject: string;
|
|
1825
|
+
predicate: LocalSemanticPredicate;
|
|
1826
|
+
value: string | boolean;
|
|
1827
|
+
confidence: number;
|
|
1828
|
+
source: "typescript-compiler-api" | "language-parser" | "architecture-pattern-rules" | "criticality-rules" | "graph-criticality-rules";
|
|
1829
|
+
extractorVersion: 1;
|
|
1830
|
+
evidence: LocalSemanticEvidence[];
|
|
1831
|
+
}
|
|
1832
|
+
interface LocalSemanticModel {
|
|
1833
|
+
version: "snipara.semantic.v1";
|
|
1834
|
+
extractorVersion: 1;
|
|
1835
|
+
source: "deterministic-graph-inference";
|
|
1836
|
+
scope: "repository" | "impact";
|
|
1837
|
+
assertions: LocalSemanticAssertion[];
|
|
1838
|
+
publicContracts: LocalSemanticAssertion[];
|
|
1839
|
+
architectureRoles: LocalSemanticAssertion[];
|
|
1840
|
+
dependencyCriticality: LocalSemanticAssertion[];
|
|
1841
|
+
truncation: {
|
|
1842
|
+
truncated: boolean;
|
|
1843
|
+
totalAssertionCount: number;
|
|
1844
|
+
returnedAssertionCount: number;
|
|
1845
|
+
assertionLimit: number;
|
|
1846
|
+
};
|
|
1847
|
+
historicalRegression: {
|
|
1848
|
+
mode: "shadow";
|
|
1849
|
+
sampleThreshold: 3;
|
|
1850
|
+
failureEventCount: 0;
|
|
1851
|
+
associations: [];
|
|
1852
|
+
riskContributionEnabled: false;
|
|
1853
|
+
suggestedRiskDelta: 0;
|
|
1854
|
+
caveat: string;
|
|
1855
|
+
};
|
|
1856
|
+
summary: {
|
|
1857
|
+
assertionCount: number;
|
|
1858
|
+
publicContractCount: number;
|
|
1859
|
+
architectureRoleCount: number;
|
|
1860
|
+
criticalDependencyCount: number;
|
|
1861
|
+
criticalEdgeCount: number;
|
|
1862
|
+
incidentalDependencyCount: number;
|
|
1863
|
+
historicalAssociationCount: 0;
|
|
1864
|
+
};
|
|
1865
|
+
caveats: string[];
|
|
1811
1866
|
}
|
|
1812
1867
|
interface LocalCodeOverlayImportBinding {
|
|
1813
1868
|
localName: string;
|
|
@@ -1854,7 +1909,7 @@ interface LocalCodeOverlayExcludedFile {
|
|
|
1854
1909
|
}
|
|
1855
1910
|
interface LocalCodeOverlayManifest {
|
|
1856
1911
|
version: "snipara.local_code_overlay.v2";
|
|
1857
|
-
extractorVersion: 2;
|
|
1912
|
+
extractorVersion: 2 | 3;
|
|
1858
1913
|
generatedAt: string;
|
|
1859
1914
|
indexedAt: string;
|
|
1860
1915
|
repoRoot: string;
|
|
@@ -1874,6 +1929,7 @@ interface LocalCodeOverlayManifest {
|
|
|
1874
1929
|
imports: LocalCodeOverlayImport[];
|
|
1875
1930
|
references: LocalCodeOverlayReference[];
|
|
1876
1931
|
edges: LocalCodeOverlayEdge[];
|
|
1932
|
+
semantic?: LocalSemanticModel;
|
|
1877
1933
|
incremental: {
|
|
1878
1934
|
reusedFiles: number;
|
|
1879
1935
|
parsedFiles: number;
|
|
@@ -2010,6 +2066,7 @@ interface LocalCodeOverlaySummary {
|
|
|
2010
2066
|
imports: number;
|
|
2011
2067
|
references: number;
|
|
2012
2068
|
edges: number;
|
|
2069
|
+
semanticAssertions: number;
|
|
2013
2070
|
excluded: number;
|
|
2014
2071
|
};
|
|
2015
2072
|
incremental: LocalCodeOverlayManifest["incremental"];
|
package/dist/index.js
CHANGED
|
@@ -3440,6 +3440,17 @@ function typeScriptLine(sourceFile, node) {
|
|
|
3440
3440
|
function typeScriptEndLine(sourceFile, node) {
|
|
3441
3441
|
return sourceFile.getLineAndCharacterOfPosition(node.getEnd()).line + 1;
|
|
3442
3442
|
}
|
|
3443
|
+
function typeScriptExportStatus(node, name, namedExports) {
|
|
3444
|
+
let declaration = node;
|
|
3445
|
+
if (import_typescript.default.isVariableDeclaration(node) && import_typescript.default.isVariableDeclarationList(node.parent)) {
|
|
3446
|
+
const statement = node.parent.parent;
|
|
3447
|
+
if (import_typescript.default.isVariableStatement(statement)) declaration = statement;
|
|
3448
|
+
}
|
|
3449
|
+
const modifiers = import_typescript.default.canHaveModifiers(declaration) ? import_typescript.default.getModifiers(declaration) : void 0;
|
|
3450
|
+
const exported = namedExports.has(name) || modifiers?.some((modifier) => modifier.kind === import_typescript.default.SyntaxKind.ExportKeyword);
|
|
3451
|
+
const defaultExport = modifiers?.some((modifier) => modifier.kind === import_typescript.default.SyntaxKind.DefaultKeyword) ?? false;
|
|
3452
|
+
return { exported: Boolean(exported || defaultExport), defaultExport };
|
|
3453
|
+
}
|
|
3443
3454
|
function extractTypeScript(content, filePath) {
|
|
3444
3455
|
const symbols = [];
|
|
3445
3456
|
const imports = [];
|
|
@@ -3452,9 +3463,18 @@ function extractTypeScript(content, filePath) {
|
|
|
3452
3463
|
true,
|
|
3453
3464
|
typeScriptScriptKind(filePath)
|
|
3454
3465
|
);
|
|
3466
|
+
const namedExports = /* @__PURE__ */ new Set();
|
|
3467
|
+
for (const statement of sourceFile.statements) {
|
|
3468
|
+
if (import_typescript.default.isExportDeclaration(statement) && !statement.moduleSpecifier && statement.exportClause && import_typescript.default.isNamedExports(statement.exportClause)) {
|
|
3469
|
+
for (const element of statement.exportClause.elements) {
|
|
3470
|
+
namedExports.add(element.propertyName?.text ?? element.name.text);
|
|
3471
|
+
}
|
|
3472
|
+
}
|
|
3473
|
+
}
|
|
3455
3474
|
const addSymbol = (node, name, kind, ownerQualifiedName, ownerKey) => {
|
|
3456
3475
|
const qualifiedName = ownerQualifiedName ? `${ownerQualifiedName}.${name}` : name;
|
|
3457
3476
|
const line = typeScriptLine(sourceFile, node);
|
|
3477
|
+
const exportStatus = ownerQualifiedName ? { exported: false, defaultExport: false } : typeScriptExportStatus(node, name, namedExports);
|
|
3458
3478
|
const symbol = {
|
|
3459
3479
|
name,
|
|
3460
3480
|
kind,
|
|
@@ -3462,7 +3482,9 @@ function extractTypeScript(content, filePath) {
|
|
|
3462
3482
|
line,
|
|
3463
3483
|
endLine: typeScriptEndLine(sourceFile, node),
|
|
3464
3484
|
qualifiedName,
|
|
3465
|
-
localKey: buildSymbolKey(filePath, qualifiedName, kind)
|
|
3485
|
+
localKey: buildSymbolKey(filePath, qualifiedName, kind),
|
|
3486
|
+
exported: exportStatus.exported,
|
|
3487
|
+
defaultExport: exportStatus.defaultExport
|
|
3466
3488
|
};
|
|
3467
3489
|
symbols.push(symbol);
|
|
3468
3490
|
if (ownerKey) {
|
|
@@ -3604,7 +3626,9 @@ function extractPython(content, filePath) {
|
|
|
3604
3626
|
filePath,
|
|
3605
3627
|
line,
|
|
3606
3628
|
qualifiedName,
|
|
3607
|
-
localKey: buildSymbolKey(filePath, qualifiedName, kind)
|
|
3629
|
+
localKey: buildSymbolKey(filePath, qualifiedName, kind),
|
|
3630
|
+
exported: /^[A-Z]/.test(name),
|
|
3631
|
+
defaultExport: false
|
|
3608
3632
|
});
|
|
3609
3633
|
}
|
|
3610
3634
|
for (const match of content.matchAll(importPattern)) {
|
|
@@ -3771,7 +3795,7 @@ function buildLocalCodeOverlay(options = {}) {
|
|
|
3771
3795
|
}
|
|
3772
3796
|
const contentHash2 = sha2563(indexedContent);
|
|
3773
3797
|
const previousFile = previousFiles.get(filePath);
|
|
3774
|
-
const canReuse = previous?.version === "snipara.local_code_overlay.v2" && previous.extractorVersion ===
|
|
3798
|
+
const canReuse = previous?.version === "snipara.local_code_overlay.v2" && previous.extractorVersion === 3 && previousFile?.sha256 === contentHash2 && previousFile.language === language;
|
|
3775
3799
|
const extracted = canReuse ? {
|
|
3776
3800
|
symbols: previous.symbols.filter((item) => item.filePath === filePath),
|
|
3777
3801
|
imports: previous.imports.filter((item) => item.filePath === filePath),
|
|
@@ -3798,6 +3822,7 @@ function buildLocalCodeOverlay(options = {}) {
|
|
|
3798
3822
|
extractedEdges.push(...extracted.edges);
|
|
3799
3823
|
}
|
|
3800
3824
|
const edges = resolveLocalGraphEdges(files, symbols, imports, references2, extractedEdges);
|
|
3825
|
+
const semantic = buildLocalSemanticModel({ symbols, edges });
|
|
3801
3826
|
for (const file of files) {
|
|
3802
3827
|
file.edgeCount = edges.filter((edge) => edge.filePath === file.path).length;
|
|
3803
3828
|
}
|
|
@@ -3833,7 +3858,7 @@ function buildLocalCodeOverlay(options = {}) {
|
|
|
3833
3858
|
) : null;
|
|
3834
3859
|
return {
|
|
3835
3860
|
version: "snipara.local_code_overlay.v2",
|
|
3836
|
-
extractorVersion:
|
|
3861
|
+
extractorVersion: 3,
|
|
3837
3862
|
generatedAt: now,
|
|
3838
3863
|
indexedAt: now,
|
|
3839
3864
|
repoRoot,
|
|
@@ -3859,6 +3884,7 @@ function buildLocalCodeOverlay(options = {}) {
|
|
|
3859
3884
|
imports,
|
|
3860
3885
|
references: references2,
|
|
3861
3886
|
edges,
|
|
3887
|
+
semantic,
|
|
3862
3888
|
incremental: {
|
|
3863
3889
|
reusedFiles,
|
|
3864
3890
|
parsedFiles,
|
|
@@ -3895,6 +3921,7 @@ function summarizeLocalCodeOverlay(manifest) {
|
|
|
3895
3921
|
imports: manifest.imports.length,
|
|
3896
3922
|
references: manifest.references.length,
|
|
3897
3923
|
edges: manifest.edges.length,
|
|
3924
|
+
semanticAssertions: manifest.semantic?.assertions.length ?? 0,
|
|
3898
3925
|
excluded: manifest.excluded.total
|
|
3899
3926
|
},
|
|
3900
3927
|
incremental: manifest.incremental,
|
|
@@ -4281,6 +4308,366 @@ function uniqueTraversalFiles(nodes, excluded) {
|
|
|
4281
4308
|
)
|
|
4282
4309
|
].sort();
|
|
4283
4310
|
}
|
|
4311
|
+
var LOCAL_SEMANTIC_TEST_PATH = /(^|\/)(tests?|__tests__|fixtures?)(\/|$)|\.(test|spec)\./i;
|
|
4312
|
+
var LOCAL_SEMANTIC_SENSITIVE_PATH = /(^|\/)(auth|middleware|billing|payments?|stripe|webhooks?|prisma|schema|migrations?|deploy)(\/|\.|$)/i;
|
|
4313
|
+
var LOCAL_SEMANTIC_CONTRACT_PATH = /(^|\/)(schemas?|contracts?|protocols?|interfaces?|config|tool_defs)(\/|\.|$)|route\.(ts|tsx|py)$/i;
|
|
4314
|
+
function localSemanticEvidence(kind, detail, metadata = {}) {
|
|
4315
|
+
return { kind, detail, ...metadata };
|
|
4316
|
+
}
|
|
4317
|
+
function localSemanticAssertion(subject, predicate, value, confidence, source2, evidence) {
|
|
4318
|
+
const identity = JSON.stringify([subject, predicate, value, source2]);
|
|
4319
|
+
return {
|
|
4320
|
+
id: `semantic-${sha2563(identity).slice(0, 16)}`,
|
|
4321
|
+
subject,
|
|
4322
|
+
predicate,
|
|
4323
|
+
value,
|
|
4324
|
+
confidence: Math.round(Math.max(0, Math.min(1, confidence)) * 1e3) / 1e3,
|
|
4325
|
+
source: source2,
|
|
4326
|
+
extractorVersion: 1,
|
|
4327
|
+
evidence
|
|
4328
|
+
};
|
|
4329
|
+
}
|
|
4330
|
+
function localArchitectureRoles(symbol) {
|
|
4331
|
+
const haystack = `${symbol.filePath} ${symbol.name}`;
|
|
4332
|
+
const candidates = [
|
|
4333
|
+
{ role: "repository", pattern: /repository|(^|[_.])repo($|[_.])/i, confidence: 0.9 },
|
|
4334
|
+
{ role: "adapter", pattern: /adapter/i, confidence: 0.9 },
|
|
4335
|
+
{ role: "facade", pattern: /facade/i, confidence: 0.9 },
|
|
4336
|
+
{ role: "controller", pattern: /controller/i, confidence: 0.85 },
|
|
4337
|
+
{ role: "service", pattern: /service/i, confidence: 0.78 },
|
|
4338
|
+
{ role: "worker", pattern: /worker|background[_-]?job|indexer/i, confidence: 0.82 },
|
|
4339
|
+
{ role: "factory", pattern: /factory|builder/i, confidence: 0.76 }
|
|
4340
|
+
];
|
|
4341
|
+
const roles = [];
|
|
4342
|
+
if (/\/app\/api\//i.test(symbol.filePath) || /\/route\.(ts|tsx|py)$/i.test(symbol.filePath)) {
|
|
4343
|
+
roles.push(
|
|
4344
|
+
localSemanticAssertion(
|
|
4345
|
+
symbol.localKey,
|
|
4346
|
+
"architecture_role",
|
|
4347
|
+
"route_handler",
|
|
4348
|
+
0.94,
|
|
4349
|
+
"architecture-pattern-rules",
|
|
4350
|
+
[
|
|
4351
|
+
localSemanticEvidence("route_path", "Route path identifies an API boundary.", {
|
|
4352
|
+
filePath: symbol.filePath,
|
|
4353
|
+
line: symbol.line
|
|
4354
|
+
})
|
|
4355
|
+
]
|
|
4356
|
+
)
|
|
4357
|
+
);
|
|
4358
|
+
}
|
|
4359
|
+
if (LOCAL_SEMANTIC_TEST_PATH.test(symbol.filePath)) {
|
|
4360
|
+
roles.push(
|
|
4361
|
+
localSemanticAssertion(
|
|
4362
|
+
symbol.localKey,
|
|
4363
|
+
"architecture_role",
|
|
4364
|
+
"test",
|
|
4365
|
+
0.98,
|
|
4366
|
+
"architecture-pattern-rules",
|
|
4367
|
+
[localSemanticEvidence("test_path", "File is under a test surface.")]
|
|
4368
|
+
)
|
|
4369
|
+
);
|
|
4370
|
+
}
|
|
4371
|
+
for (const candidate of candidates) {
|
|
4372
|
+
if (!candidate.pattern.test(haystack)) continue;
|
|
4373
|
+
roles.push(
|
|
4374
|
+
localSemanticAssertion(
|
|
4375
|
+
symbol.localKey,
|
|
4376
|
+
"architecture_role",
|
|
4377
|
+
candidate.role,
|
|
4378
|
+
candidate.confidence,
|
|
4379
|
+
"architecture-pattern-rules",
|
|
4380
|
+
[
|
|
4381
|
+
localSemanticEvidence(
|
|
4382
|
+
"name_or_path_pattern",
|
|
4383
|
+
`Name or path matches the ${candidate.role} pattern.`,
|
|
4384
|
+
{ filePath: symbol.filePath, line: symbol.line }
|
|
4385
|
+
)
|
|
4386
|
+
]
|
|
4387
|
+
)
|
|
4388
|
+
);
|
|
4389
|
+
}
|
|
4390
|
+
return roles;
|
|
4391
|
+
}
|
|
4392
|
+
function localSymbolSemanticAssertions(symbol) {
|
|
4393
|
+
const assertions = [];
|
|
4394
|
+
const routeSurface = /\/app\/api\//i.test(symbol.filePath) || /\/route\.(ts|tsx|py)$/i.test(symbol.filePath);
|
|
4395
|
+
const toolSurface = /(^|\/)(mcp|tool_defs)(\/|\.|$)/i.test(symbol.filePath);
|
|
4396
|
+
const publicEvidence = [];
|
|
4397
|
+
if (symbol.exported) {
|
|
4398
|
+
publicEvidence.push(
|
|
4399
|
+
localSemanticEvidence(
|
|
4400
|
+
"explicit_export",
|
|
4401
|
+
"TypeScript/Go declaration is explicitly exported.",
|
|
4402
|
+
{
|
|
4403
|
+
filePath: symbol.filePath,
|
|
4404
|
+
line: symbol.line
|
|
4405
|
+
}
|
|
4406
|
+
)
|
|
4407
|
+
);
|
|
4408
|
+
}
|
|
4409
|
+
if (routeSurface) {
|
|
4410
|
+
publicEvidence.push(
|
|
4411
|
+
localSemanticEvidence("route_path", "Symbol is declared in a route module.", {
|
|
4412
|
+
filePath: symbol.filePath,
|
|
4413
|
+
line: symbol.line
|
|
4414
|
+
})
|
|
4415
|
+
);
|
|
4416
|
+
}
|
|
4417
|
+
if (toolSurface) {
|
|
4418
|
+
publicEvidence.push(
|
|
4419
|
+
localSemanticEvidence(
|
|
4420
|
+
"tool_surface",
|
|
4421
|
+
"Symbol is declared in an MCP/tool-definition surface.",
|
|
4422
|
+
{
|
|
4423
|
+
filePath: symbol.filePath,
|
|
4424
|
+
line: symbol.line
|
|
4425
|
+
}
|
|
4426
|
+
)
|
|
4427
|
+
);
|
|
4428
|
+
}
|
|
4429
|
+
if (publicEvidence.length > 0) {
|
|
4430
|
+
assertions.push(
|
|
4431
|
+
localSemanticAssertion(
|
|
4432
|
+
symbol.localKey,
|
|
4433
|
+
"public_api",
|
|
4434
|
+
true,
|
|
4435
|
+
Math.min(0.98, 0.82 + publicEvidence.length * 0.05),
|
|
4436
|
+
symbol.exported ? "typescript-compiler-api" : "architecture-pattern-rules",
|
|
4437
|
+
publicEvidence
|
|
4438
|
+
)
|
|
4439
|
+
);
|
|
4440
|
+
}
|
|
4441
|
+
let contractKind = null;
|
|
4442
|
+
const contractEvidence = [];
|
|
4443
|
+
if (symbol.exported && ["interface", "type"].includes(symbol.kind)) {
|
|
4444
|
+
contractKind = "exported_type_contract";
|
|
4445
|
+
contractEvidence.push(
|
|
4446
|
+
localSemanticEvidence("symbol_kind", `Exported ${symbol.kind} defines a structural contract.`)
|
|
4447
|
+
);
|
|
4448
|
+
} else if (LOCAL_SEMANTIC_CONTRACT_PATH.test(symbol.filePath)) {
|
|
4449
|
+
contractKind = "repository_boundary_contract";
|
|
4450
|
+
contractEvidence.push(
|
|
4451
|
+
localSemanticEvidence(
|
|
4452
|
+
"contract_path",
|
|
4453
|
+
"File path denotes a schema, contract, config, route, or tool boundary."
|
|
4454
|
+
)
|
|
4455
|
+
);
|
|
4456
|
+
} else if (symbol.exported) {
|
|
4457
|
+
contractKind = "public_surface_contract";
|
|
4458
|
+
contractEvidence.push(...publicEvidence.slice(0, 1));
|
|
4459
|
+
}
|
|
4460
|
+
if (contractKind) {
|
|
4461
|
+
assertions.push(
|
|
4462
|
+
localSemanticAssertion(
|
|
4463
|
+
symbol.localKey,
|
|
4464
|
+
"implicit_contract",
|
|
4465
|
+
contractKind,
|
|
4466
|
+
["interface", "type"].includes(symbol.kind) ? 0.92 : 0.78,
|
|
4467
|
+
symbol.exported ? "typescript-compiler-api" : "architecture-pattern-rules",
|
|
4468
|
+
contractEvidence
|
|
4469
|
+
)
|
|
4470
|
+
);
|
|
4471
|
+
}
|
|
4472
|
+
assertions.push(...localArchitectureRoles(symbol));
|
|
4473
|
+
let criticality = "ordinary";
|
|
4474
|
+
let criticalityConfidence = 0.58;
|
|
4475
|
+
let criticalityEvidence = [
|
|
4476
|
+
localSemanticEvidence("default", "No critical or incidental evidence matched.")
|
|
4477
|
+
];
|
|
4478
|
+
if (LOCAL_SEMANTIC_TEST_PATH.test(symbol.filePath)) {
|
|
4479
|
+
criticality = "incidental";
|
|
4480
|
+
criticalityConfidence = 0.95;
|
|
4481
|
+
criticalityEvidence = [localSemanticEvidence("test_path", "Test-only dependency.")];
|
|
4482
|
+
} else if (LOCAL_SEMANTIC_SENSITIVE_PATH.test(symbol.filePath) || routeSurface || toolSurface) {
|
|
4483
|
+
criticality = "critical";
|
|
4484
|
+
criticalityConfidence = 0.92;
|
|
4485
|
+
criticalityEvidence = [
|
|
4486
|
+
localSemanticEvidence(
|
|
4487
|
+
"sensitive_surface",
|
|
4488
|
+
"Path identifies an auth, billing, schema, webhook, deploy, route, or MCP surface."
|
|
4489
|
+
)
|
|
4490
|
+
];
|
|
4491
|
+
} else if (publicEvidence.length > 0) {
|
|
4492
|
+
criticality = "important";
|
|
4493
|
+
criticalityConfidence = 0.85;
|
|
4494
|
+
criticalityEvidence = [
|
|
4495
|
+
localSemanticEvidence(
|
|
4496
|
+
"exported_surface",
|
|
4497
|
+
"Explicit export defines a module contract but is not by itself a critical runtime surface."
|
|
4498
|
+
)
|
|
4499
|
+
];
|
|
4500
|
+
} else if (contractKind) {
|
|
4501
|
+
criticality = "important";
|
|
4502
|
+
criticalityConfidence = 0.82;
|
|
4503
|
+
criticalityEvidence = [
|
|
4504
|
+
localSemanticEvidence("contract_surface", "Dependency defines a repository contract.")
|
|
4505
|
+
];
|
|
4506
|
+
}
|
|
4507
|
+
assertions.push(
|
|
4508
|
+
localSemanticAssertion(
|
|
4509
|
+
symbol.localKey,
|
|
4510
|
+
"dependency_criticality",
|
|
4511
|
+
criticality,
|
|
4512
|
+
criticalityConfidence,
|
|
4513
|
+
"criticality-rules",
|
|
4514
|
+
criticalityEvidence
|
|
4515
|
+
)
|
|
4516
|
+
);
|
|
4517
|
+
return assertions;
|
|
4518
|
+
}
|
|
4519
|
+
function buildLocalSemanticModel(manifest, options = {}) {
|
|
4520
|
+
const symbols = options.symbolKeys ? manifest.symbols.filter((symbol) => options.symbolKeys?.has(symbol.localKey)) : manifest.symbols;
|
|
4521
|
+
const assertions = symbols.flatMap(localSymbolSemanticAssertions);
|
|
4522
|
+
const symbolByKey = new Map(manifest.symbols.map((symbol) => [symbol.localKey, symbol]));
|
|
4523
|
+
const criticalityBySymbol = new Map(
|
|
4524
|
+
assertions.filter((assertion) => assertion.predicate === "dependency_criticality").map((assertion) => [assertion.subject, String(assertion.value)])
|
|
4525
|
+
);
|
|
4526
|
+
const criticalityRank = {
|
|
4527
|
+
incidental: 0,
|
|
4528
|
+
ordinary: 1,
|
|
4529
|
+
important: 2,
|
|
4530
|
+
critical: 3
|
|
4531
|
+
};
|
|
4532
|
+
const criticalityByFile = /* @__PURE__ */ new Map();
|
|
4533
|
+
for (const symbol of manifest.symbols) {
|
|
4534
|
+
const value = criticalityBySymbol.get(symbol.localKey);
|
|
4535
|
+
if (!value) continue;
|
|
4536
|
+
const current = criticalityByFile.get(symbol.filePath);
|
|
4537
|
+
if (!current || criticalityRank[value] > criticalityRank[current]) {
|
|
4538
|
+
criticalityByFile.set(symbol.filePath, value);
|
|
4539
|
+
}
|
|
4540
|
+
}
|
|
4541
|
+
for (const edge of options.edges ?? []) {
|
|
4542
|
+
const fromSymbol = symbolByKey.get(edge.from);
|
|
4543
|
+
const toSymbol = symbolByKey.get(edge.to);
|
|
4544
|
+
const fromFile = fromSymbol?.filePath ?? edge.from.replace(/^local-file::/, "");
|
|
4545
|
+
const toFile = toSymbol?.filePath ?? edge.to.replace(/^local-file::/, "");
|
|
4546
|
+
const fromCriticality = criticalityBySymbol.get(edge.from) ?? criticalityByFile.get(fromFile);
|
|
4547
|
+
const toCriticality = criticalityBySymbol.get(edge.to) ?? criticalityByFile.get(toFile);
|
|
4548
|
+
const edgeSubject = `edge::${edge.from}::${edge.kind}::${edge.to}`;
|
|
4549
|
+
const evidence = [
|
|
4550
|
+
localSemanticEvidence("graph_edge", `${edge.kind} from ${fromFile} to ${toFile}.`, {
|
|
4551
|
+
filePath: edge.filePath,
|
|
4552
|
+
line: edge.line,
|
|
4553
|
+
edgeKind: edge.kind
|
|
4554
|
+
})
|
|
4555
|
+
];
|
|
4556
|
+
let value = "ordinary";
|
|
4557
|
+
let confidence = 0.62;
|
|
4558
|
+
if (LOCAL_SEMANTIC_TEST_PATH.test(fromFile) || LOCAL_SEMANTIC_TEST_PATH.test(toFile)) {
|
|
4559
|
+
value = "incidental";
|
|
4560
|
+
confidence = 0.92;
|
|
4561
|
+
evidence.push(localSemanticEvidence("test_path", "At least one edge endpoint is test-only."));
|
|
4562
|
+
} else if (fromCriticality === "critical" || toCriticality === "critical") {
|
|
4563
|
+
value = "critical";
|
|
4564
|
+
confidence = 0.9;
|
|
4565
|
+
evidence.push(
|
|
4566
|
+
localSemanticEvidence("critical_endpoint", "At least one dependency endpoint is critical.")
|
|
4567
|
+
);
|
|
4568
|
+
} else if (edge.kind === "CONTAINS" || edge.confidence < 0.65) {
|
|
4569
|
+
value = "incidental";
|
|
4570
|
+
confidence = 0.78;
|
|
4571
|
+
evidence.push(
|
|
4572
|
+
localSemanticEvidence(
|
|
4573
|
+
"weak_or_structural",
|
|
4574
|
+
"Containment or low-confidence evidence is incidental to change propagation."
|
|
4575
|
+
)
|
|
4576
|
+
);
|
|
4577
|
+
} else if (fromCriticality === "important" || toCriticality === "important") {
|
|
4578
|
+
value = "important";
|
|
4579
|
+
confidence = 0.82;
|
|
4580
|
+
evidence.push(
|
|
4581
|
+
localSemanticEvidence(
|
|
4582
|
+
"contract_endpoint",
|
|
4583
|
+
"At least one dependency endpoint is a contract."
|
|
4584
|
+
)
|
|
4585
|
+
);
|
|
4586
|
+
}
|
|
4587
|
+
assertions.push(
|
|
4588
|
+
localSemanticAssertion(
|
|
4589
|
+
edgeSubject,
|
|
4590
|
+
"dependency_criticality",
|
|
4591
|
+
value,
|
|
4592
|
+
confidence,
|
|
4593
|
+
"graph-criticality-rules",
|
|
4594
|
+
evidence
|
|
4595
|
+
)
|
|
4596
|
+
);
|
|
4597
|
+
}
|
|
4598
|
+
const allAssertions = [
|
|
4599
|
+
...new Map(assertions.map((assertion) => [assertion.id, assertion])).values()
|
|
4600
|
+
].sort(
|
|
4601
|
+
(left, right) => `${left.subject}:${left.predicate}:${String(left.value)}`.localeCompare(
|
|
4602
|
+
`${right.subject}:${right.predicate}:${String(right.value)}`
|
|
4603
|
+
)
|
|
4604
|
+
);
|
|
4605
|
+
const assertionLimit = (options.scope ?? "repository") === "impact" ? 2e3 : 5e3;
|
|
4606
|
+
const returnedAssertions = allAssertions.slice(0, assertionLimit);
|
|
4607
|
+
const allPublicContracts = allAssertions.filter(
|
|
4608
|
+
(assertion) => assertion.predicate === "public_api" || assertion.predicate === "implicit_contract"
|
|
4609
|
+
);
|
|
4610
|
+
const allArchitectureRoles = allAssertions.filter(
|
|
4611
|
+
(assertion) => assertion.predicate === "architecture_role"
|
|
4612
|
+
);
|
|
4613
|
+
const allDependencyCriticality = allAssertions.filter(
|
|
4614
|
+
(assertion) => assertion.predicate === "dependency_criticality"
|
|
4615
|
+
);
|
|
4616
|
+
const publicContracts = returnedAssertions.filter(
|
|
4617
|
+
(assertion) => assertion.predicate === "public_api" || assertion.predicate === "implicit_contract"
|
|
4618
|
+
);
|
|
4619
|
+
const architectureRoles = returnedAssertions.filter(
|
|
4620
|
+
(assertion) => assertion.predicate === "architecture_role"
|
|
4621
|
+
);
|
|
4622
|
+
const dependencyCriticality = returnedAssertions.filter(
|
|
4623
|
+
(assertion) => assertion.predicate === "dependency_criticality"
|
|
4624
|
+
);
|
|
4625
|
+
return {
|
|
4626
|
+
version: "snipara.semantic.v1",
|
|
4627
|
+
extractorVersion: 1,
|
|
4628
|
+
source: "deterministic-graph-inference",
|
|
4629
|
+
scope: options.scope ?? "repository",
|
|
4630
|
+
assertions: returnedAssertions,
|
|
4631
|
+
publicContracts,
|
|
4632
|
+
architectureRoles,
|
|
4633
|
+
dependencyCriticality,
|
|
4634
|
+
truncation: {
|
|
4635
|
+
truncated: allAssertions.length > assertionLimit,
|
|
4636
|
+
totalAssertionCount: allAssertions.length,
|
|
4637
|
+
returnedAssertionCount: returnedAssertions.length,
|
|
4638
|
+
assertionLimit
|
|
4639
|
+
},
|
|
4640
|
+
historicalRegression: {
|
|
4641
|
+
mode: "shadow",
|
|
4642
|
+
sampleThreshold: 3,
|
|
4643
|
+
failureEventCount: 0,
|
|
4644
|
+
associations: [],
|
|
4645
|
+
riskContributionEnabled: false,
|
|
4646
|
+
suggestedRiskDelta: 0,
|
|
4647
|
+
caveat: "The local overlay has no durable regression outcome stream. Historical path learning stays shadow-only until hosted evidence is available."
|
|
4648
|
+
},
|
|
4649
|
+
summary: {
|
|
4650
|
+
assertionCount: allAssertions.length,
|
|
4651
|
+
publicContractCount: allPublicContracts.length,
|
|
4652
|
+
architectureRoleCount: allArchitectureRoles.length,
|
|
4653
|
+
criticalDependencyCount: allDependencyCriticality.filter(
|
|
4654
|
+
(assertion) => assertion.value === "critical"
|
|
4655
|
+
).length,
|
|
4656
|
+
criticalEdgeCount: allDependencyCriticality.filter(
|
|
4657
|
+
(assertion) => assertion.value === "critical" && assertion.subject.startsWith("edge::")
|
|
4658
|
+
).length,
|
|
4659
|
+
incidentalDependencyCount: allDependencyCriticality.filter(
|
|
4660
|
+
(assertion) => assertion.value === "incidental"
|
|
4661
|
+
).length,
|
|
4662
|
+
historicalAssociationCount: 0
|
|
4663
|
+
},
|
|
4664
|
+
caveats: [
|
|
4665
|
+
"Semantic assertions are deterministic inferences, not declarations of author intent.",
|
|
4666
|
+
"Name-only architecture patterns use lower confidence than explicit export or path evidence.",
|
|
4667
|
+
"Historical regression learning requires hosted outcome evidence and remains shadow-only."
|
|
4668
|
+
]
|
|
4669
|
+
};
|
|
4670
|
+
}
|
|
4284
4671
|
function loadLocalRiskConfig(repoRoot) {
|
|
4285
4672
|
const defaults = {
|
|
4286
4673
|
depthDecay: 0.65,
|
|
@@ -4305,7 +4692,7 @@ function loadLocalRiskConfig(repoRoot) {
|
|
|
4305
4692
|
return defaults;
|
|
4306
4693
|
}
|
|
4307
4694
|
}
|
|
4308
|
-
function buildLocalRisk(manifest, changedFiles, traversal, depth) {
|
|
4695
|
+
function buildLocalRisk(manifest, changedFiles, traversal, depth, semantic) {
|
|
4309
4696
|
const config = loadLocalRiskConfig(manifest.repoRoot);
|
|
4310
4697
|
const allFiles = [
|
|
4311
4698
|
...changedFiles,
|
|
@@ -4344,13 +4731,27 @@ function buildLocalRisk(manifest, changedFiles, traversal, depth) {
|
|
|
4344
4731
|
(reference) => changedFiles.includes(reference.filePath) && !resolvedReferenceKeys.has(`${reference.from}:${reference.line}`)
|
|
4345
4732
|
).length;
|
|
4346
4733
|
const uncertaintyPenalty = Math.min(15, unresolvedReferences * 2 + (traversal.truncated ? 8 : 0));
|
|
4347
|
-
const
|
|
4734
|
+
const criticalSemanticDependencies = semantic.summary.criticalEdgeCount;
|
|
4735
|
+
const changedSymbolKeys = new Set(
|
|
4736
|
+
manifest.symbols.filter((symbol) => changedFiles.includes(symbol.filePath)).map((symbol) => symbol.localKey)
|
|
4737
|
+
);
|
|
4738
|
+
const changedContracts = manifest.symbols.filter((symbol) => changedSymbolKeys.has(symbol.localKey)).flatMap(localSymbolSemanticAssertions).filter(
|
|
4739
|
+
(assertion) => assertion.predicate === "public_api" || assertion.predicate === "implicit_contract"
|
|
4740
|
+
).length;
|
|
4741
|
+
const semanticRiskPoints = Math.min(20, criticalSemanticDependencies * 3) + Math.min(10, changedContracts * 2);
|
|
4742
|
+
const rawScore = criticality * (directFiles.size * 8 + propagation * 4) + uncertaintyPenalty + semanticRiskPoints;
|
|
4348
4743
|
const score = Math.max(0, Math.min(100, Math.round(rawScore)));
|
|
4349
4744
|
const reasons = [
|
|
4350
4745
|
`${directFiles.size} direct dependent file${directFiles.size === 1 ? "" : "s"}`,
|
|
4351
4746
|
`${transitiveFiles.size} transitive dependent file${transitiveFiles.size === 1 ? "" : "s"}`,
|
|
4352
4747
|
...criticalMatches.length ? [`critical-surface multiplier ${criticality.toFixed(2)}`] : [],
|
|
4353
4748
|
...unresolvedReferences ? [`${unresolvedReferences} unresolved reference${unresolvedReferences === 1 ? "" : "s"}`] : [],
|
|
4749
|
+
...criticalSemanticDependencies ? [
|
|
4750
|
+
`${criticalSemanticDependencies} critical semantic dependenc${criticalSemanticDependencies === 1 ? "y" : "ies"}`
|
|
4751
|
+
] : [],
|
|
4752
|
+
...changedContracts ? [
|
|
4753
|
+
`${changedContracts} public or implicit contract assertion${changedContracts === 1 ? "" : "s"}`
|
|
4754
|
+
] : [],
|
|
4354
4755
|
...traversal.truncated ? ["traversal truncated by max-nodes"] : []
|
|
4355
4756
|
];
|
|
4356
4757
|
return {
|
|
@@ -4361,9 +4762,12 @@ function buildLocalRisk(manifest, changedFiles, traversal, depth) {
|
|
|
4361
4762
|
transitiveDependentFiles: transitiveFiles.size,
|
|
4362
4763
|
criticality,
|
|
4363
4764
|
uncertaintyPenalty,
|
|
4765
|
+
semanticRiskPoints,
|
|
4766
|
+
criticalSemanticDependencies,
|
|
4767
|
+
changedContracts,
|
|
4364
4768
|
unresolvedReferences,
|
|
4365
4769
|
reasons,
|
|
4366
|
-
formula: "criticality * (directImpact + decayedEdgePropagation) + uncertaintyPenalty"
|
|
4770
|
+
formula: "criticality * (directImpact + decayedEdgePropagation) + uncertaintyPenalty + semanticRiskPoints"
|
|
4367
4771
|
};
|
|
4368
4772
|
}
|
|
4369
4773
|
function missingTargetDetail(manifest, filePath) {
|
|
@@ -4679,6 +5083,42 @@ function hybridRisk(local, hosted) {
|
|
|
4679
5083
|
)
|
|
4680
5084
|
};
|
|
4681
5085
|
}
|
|
5086
|
+
function hybridSemantic(local, hosted) {
|
|
5087
|
+
const localSemantic = asUnknownRecord(local.semantic);
|
|
5088
|
+
const hostedSemantic = asUnknownRecord(hosted.semantic);
|
|
5089
|
+
if (Object.keys(localSemantic).length === 0 && Object.keys(hostedSemantic).length === 0) {
|
|
5090
|
+
return void 0;
|
|
5091
|
+
}
|
|
5092
|
+
const assertions = mergeResultArrays(
|
|
5093
|
+
[{ assertions: hostedSemantic.assertions }, { assertions: localSemantic.assertions }],
|
|
5094
|
+
["assertions"]
|
|
5095
|
+
);
|
|
5096
|
+
return {
|
|
5097
|
+
version: "snipara.semantic.hybrid.v1",
|
|
5098
|
+
mode: "hybrid",
|
|
5099
|
+
provenance: {
|
|
5100
|
+
canonicalBase: Object.keys(hostedSemantic).length > 0 ? "hosted_graph" : "unavailable",
|
|
5101
|
+
checkoutDelta: Object.keys(localSemantic).length > 0 ? "local_overlay" : "unavailable",
|
|
5102
|
+
crossSourceAssertionSynthesis: false
|
|
5103
|
+
},
|
|
5104
|
+
assertions,
|
|
5105
|
+
summary: {
|
|
5106
|
+
assertionCount: assertions.length,
|
|
5107
|
+
publicContractCount: assertions.filter(
|
|
5108
|
+
(item) => asUnknownRecord(item).predicate === "public_api" || asUnknownRecord(item).predicate === "implicit_contract"
|
|
5109
|
+
).length,
|
|
5110
|
+
architectureRoleCount: assertions.filter(
|
|
5111
|
+
(item) => asUnknownRecord(item).predicate === "architecture_role"
|
|
5112
|
+
).length,
|
|
5113
|
+
criticalDependencyCount: assertions.filter(
|
|
5114
|
+
(item) => asUnknownRecord(item).predicate === "dependency_criticality" && asUnknownRecord(item).value === "critical"
|
|
5115
|
+
).length
|
|
5116
|
+
},
|
|
5117
|
+
historicalRegression: hostedSemantic.historical_regression ?? hostedSemantic.historicalRegression ?? localSemantic.historicalRegression ?? null,
|
|
5118
|
+
local: Object.keys(localSemantic).length > 0 ? localSemantic : null,
|
|
5119
|
+
hosted: Object.keys(hostedSemantic).length > 0 ? hostedSemantic : null
|
|
5120
|
+
};
|
|
5121
|
+
}
|
|
4682
5122
|
function mergeHybridCodeResults(verb, localResult, hostedResult, hostedError) {
|
|
4683
5123
|
const local = asUnknownRecord(localResult);
|
|
4684
5124
|
const hosted = asUnknownRecord(hostedResult);
|
|
@@ -4698,6 +5138,7 @@ function mergeHybridCodeResults(verb, localResult, hostedResult, hostedError) {
|
|
|
4698
5138
|
collectStringValues(local, /* @__PURE__ */ new Set(["changed_files", "changedFiles"]), changedFiles);
|
|
4699
5139
|
collectStringValues(hosted, /* @__PURE__ */ new Set(["changed_files", "changedFiles"]), changedFiles);
|
|
4700
5140
|
const risk = hybridRisk(local, hosted);
|
|
5141
|
+
const semantic = hybridSemantic(local, hosted);
|
|
4701
5142
|
const recommendedTests = mergeResultArrays(
|
|
4702
5143
|
[hosted, local],
|
|
4703
5144
|
["recommended_tests", "recommendedTests", "related_tests", "relatedTests", "tests"]
|
|
@@ -4725,6 +5166,7 @@ function mergeHybridCodeResults(verb, localResult, hostedResult, hostedError) {
|
|
|
4725
5166
|
changed_files: [...changedFiles].sort(),
|
|
4726
5167
|
impactedFiles: [...affectedFiles].sort(),
|
|
4727
5168
|
...risk ? { risk } : {},
|
|
5169
|
+
...semantic ? { semantic } : {},
|
|
4728
5170
|
recommended_tests: recommendedTests,
|
|
4729
5171
|
recommended_actions: recommendedActions,
|
|
4730
5172
|
coverage_gaps: coverageGaps,
|
|
@@ -5342,6 +5784,24 @@ function buildLocalImpactResult(options) {
|
|
|
5342
5784
|
edgeKinds: options.edgeKinds?.length ? options.edgeKinds : ["IMPORTS", "CALLS", "REFERENCES"],
|
|
5343
5785
|
maxNodes
|
|
5344
5786
|
});
|
|
5787
|
+
const traversalEdges = [
|
|
5788
|
+
...new Map(
|
|
5789
|
+
traversal.nodes.flatMap((node) => node.edges).map((edge) => [`${edge.from}:${edge.kind}:${edge.to}:${edge.line}`, edge])
|
|
5790
|
+
).values()
|
|
5791
|
+
];
|
|
5792
|
+
const semanticSymbolKeys = /* @__PURE__ */ new Set([...seedKeys, ...traversal.nodes.map((node) => node.key)]);
|
|
5793
|
+
const semanticFiles = /* @__PURE__ */ new Set([
|
|
5794
|
+
...changedFiles,
|
|
5795
|
+
...traversal.nodes.flatMap((node) => node.filePath ? [node.filePath] : [])
|
|
5796
|
+
]);
|
|
5797
|
+
for (const candidate of manifest.symbols) {
|
|
5798
|
+
if (semanticFiles.has(candidate.filePath)) semanticSymbolKeys.add(candidate.localKey);
|
|
5799
|
+
}
|
|
5800
|
+
const semantic = buildLocalSemanticModel(manifest, {
|
|
5801
|
+
scope: "impact",
|
|
5802
|
+
symbolKeys: semanticSymbolKeys,
|
|
5803
|
+
edges: traversalEdges
|
|
5804
|
+
});
|
|
5345
5805
|
const transitiveFiles = uniqueTraversalFiles(traversal.nodes, selectedFiles);
|
|
5346
5806
|
const impactedFiles = [.../* @__PURE__ */ new Set([...directImpactedFiles, ...transitiveFiles])].sort();
|
|
5347
5807
|
const relevantWarningFiles = /* @__PURE__ */ new Set([
|
|
@@ -5382,7 +5842,8 @@ function buildLocalImpactResult(options) {
|
|
|
5382
5842
|
visitedCount: traversal.visitedCount,
|
|
5383
5843
|
maxNodes
|
|
5384
5844
|
},
|
|
5385
|
-
|
|
5845
|
+
semantic,
|
|
5846
|
+
risk: buildLocalRisk(manifest, changedFiles, traversal, depth, semantic)
|
|
5386
5847
|
};
|
|
5387
5848
|
}
|
|
5388
5849
|
async function codeLocalImportsCommand(options) {
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Semantic Code Graph
|
|
2
|
+
|
|
3
|
+
`snipara-companion code impact` builds a structural graph from the current
|
|
4
|
+
checkout and adds a deterministic semantic model above it. The semantic layer
|
|
5
|
+
does not replace `CALLS`, `REFERENCES`, `IMPORTS`, or `CONTAINS` edges. It
|
|
6
|
+
classifies what those nodes and edges mean for a change.
|
|
7
|
+
|
|
8
|
+
## Semantic model v1
|
|
9
|
+
|
|
10
|
+
Local manifests and impact results expose `snipara.semantic.v1`. Each assertion
|
|
11
|
+
contains:
|
|
12
|
+
|
|
13
|
+
- `subject`: a stable local symbol key or edge identity;
|
|
14
|
+
- `predicate`: `public_api`, `implicit_contract`, `architecture_role`, or
|
|
15
|
+
`dependency_criticality`;
|
|
16
|
+
- `value`: the inferred classification;
|
|
17
|
+
- `confidence`: a bounded score from `0` to `1`;
|
|
18
|
+
- `source` and `extractorVersion`;
|
|
19
|
+
- `evidence`: the export modifier, symbol kind, file/path pattern, or graph edge
|
|
20
|
+
that produced the assertion.
|
|
21
|
+
|
|
22
|
+
The extractor recognizes explicit TypeScript and Go exports, route and MCP
|
|
23
|
+
surfaces, exported type contracts, and common Repository, Adapter, Facade,
|
|
24
|
+
Controller, Service, Worker, Factory, schema, route-handler, and test roles.
|
|
25
|
+
Name-only patterns have lower confidence than explicit export, route, or graph
|
|
26
|
+
evidence.
|
|
27
|
+
|
|
28
|
+
## Dependency criticality
|
|
29
|
+
|
|
30
|
+
Criticality is evaluated for symbols and for the edges traversed by impact:
|
|
31
|
+
|
|
32
|
+
- `critical`: runtime-exposed route/MCP surfaces and sensitive auth, billing,
|
|
33
|
+
schema, webhook, or deploy paths;
|
|
34
|
+
- `important`: explicit module exports and inferred contract boundaries;
|
|
35
|
+
- `ordinary`: normal application dependencies without stronger evidence;
|
|
36
|
+
- `incidental`: test-only, containment, or weak-confidence dependencies.
|
|
37
|
+
|
|
38
|
+
Local risk scoring adds bounded `semanticRiskPoints` for critical traversed
|
|
39
|
+
dependencies and contracts in the changed files. The output lists the exact
|
|
40
|
+
assertions and the formula; callers never receive a hidden semantic multiplier.
|
|
41
|
+
Large impact traversals cap returned assertions at 2,000 (repository manifests
|
|
42
|
+
at 5,000) and report total/returned counts in `semantic.truncation`; risk uses
|
|
43
|
+
the complete pre-truncation counts.
|
|
44
|
+
|
|
45
|
+
## Hybrid provenance
|
|
46
|
+
|
|
47
|
+
Hybrid queries preserve hosted canonical assertions and local checkout-delta
|
|
48
|
+
assertions under `snipara.semantic.hybrid.v1`. Snipara unions the evidence but
|
|
49
|
+
does not manufacture cross-source edges or assertions.
|
|
50
|
+
|
|
51
|
+
## Historical regression paths
|
|
52
|
+
|
|
53
|
+
Hosted impact can associate sanitized warning/error execution traces with
|
|
54
|
+
graph paths by their tracked files. Version 1 reports sample count, confidence,
|
|
55
|
+
event IDs, and a suggested risk delta. This surface is intentionally
|
|
56
|
+
`mode: shadow` and `riskContributionEnabled: false`: temporal association is
|
|
57
|
+
not causal proof, and it cannot affect risk until separately calibrated and
|
|
58
|
+
promoted.
|
|
59
|
+
|
|
60
|
+
The local overlay has no durable outcome stream, so it returns the same shadow
|
|
61
|
+
contract with zero samples and directs callers to hosted evidence when needed.
|
|
62
|
+
|
|
63
|
+
## Example
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npx -y snipara-companion code impact \
|
|
67
|
+
--source local \
|
|
68
|
+
--changed-files src/auth/session-repository.ts \
|
|
69
|
+
--depth 4 \
|
|
70
|
+
--json
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Inspect `result.semantic.assertions`, `result.semantic.summary`, and
|
|
74
|
+
`result.risk.semanticRiskPoints` in the response.
|