snipara-companion 3.6.2 → 3.6.3
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 +11 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +195 -15
- package/docs/CODE_GRAPH.md +37 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
Release notes for `snipara-companion`, newest first.
|
|
4
4
|
|
|
5
|
+
## New In 3.6.3
|
|
6
|
+
|
|
7
|
+
- Labels every semantic `confidence` value as an uncalibrated
|
|
8
|
+
`heuristic_prior`, preserving the numeric compatibility field without
|
|
9
|
+
presenting it as a measured probability.
|
|
10
|
+
- Loads bounded, literal project naming terms from
|
|
11
|
+
`.snipara/semantic-rules.json`; teams can extend or replace English defaults
|
|
12
|
+
without enabling arbitrary regular expressions.
|
|
13
|
+
- Keeps historical regression associations strictly shadow-only and marks
|
|
14
|
+
their association strength as uncalibrated.
|
|
15
|
+
|
|
5
16
|
## New In 3.6.2
|
|
6
17
|
|
|
7
18
|
- Adds `snipara.semantic.v1` to local code overlays and impact results with
|
package/dist/index.d.ts
CHANGED
|
@@ -1825,6 +1825,8 @@ interface LocalSemanticAssertion {
|
|
|
1825
1825
|
predicate: LocalSemanticPredicate;
|
|
1826
1826
|
value: string | boolean;
|
|
1827
1827
|
confidence: number;
|
|
1828
|
+
scoreKind: "heuristic_prior";
|
|
1829
|
+
calibrated: false;
|
|
1828
1830
|
source: "typescript-compiler-api" | "language-parser" | "architecture-pattern-rules" | "criticality-rules" | "graph-criticality-rules";
|
|
1829
1831
|
extractorVersion: 1;
|
|
1830
1832
|
evidence: LocalSemanticEvidence[];
|
|
@@ -1833,6 +1835,29 @@ interface LocalSemanticModel {
|
|
|
1833
1835
|
version: "snipara.semantic.v1";
|
|
1834
1836
|
extractorVersion: 1;
|
|
1835
1837
|
source: "deterministic-graph-inference";
|
|
1838
|
+
modelType: "rule-based-heuristic";
|
|
1839
|
+
scoreContract: {
|
|
1840
|
+
kind: "heuristic_prior";
|
|
1841
|
+
calibrated: false;
|
|
1842
|
+
probability: false;
|
|
1843
|
+
comparableAcrossProjects: false;
|
|
1844
|
+
basis: "hand-tuned-v1";
|
|
1845
|
+
rulesetVersion: 1;
|
|
1846
|
+
interpretation: string;
|
|
1847
|
+
};
|
|
1848
|
+
ruleConfig: {
|
|
1849
|
+
version: "snipara.semantic.rules.v1";
|
|
1850
|
+
source: "defaults" | "project-file";
|
|
1851
|
+
path: ".snipara/semantic-rules.json";
|
|
1852
|
+
replaceDefaults: boolean;
|
|
1853
|
+
sensitivePathTermCount: number;
|
|
1854
|
+
contractPathTermCount: number;
|
|
1855
|
+
testPathTermCount: number;
|
|
1856
|
+
architectureRoleTermCount: number;
|
|
1857
|
+
configuredRoles: string[];
|
|
1858
|
+
configHash: string;
|
|
1859
|
+
warnings: string[];
|
|
1860
|
+
};
|
|
1836
1861
|
scope: "repository" | "impact";
|
|
1837
1862
|
assertions: LocalSemanticAssertion[];
|
|
1838
1863
|
publicContracts: LocalSemanticAssertion[];
|
package/dist/index.js
CHANGED
|
@@ -3822,7 +3822,10 @@ function buildLocalCodeOverlay(options = {}) {
|
|
|
3822
3822
|
extractedEdges.push(...extracted.edges);
|
|
3823
3823
|
}
|
|
3824
3824
|
const edges = resolveLocalGraphEdges(files, symbols, imports, references2, extractedEdges);
|
|
3825
|
-
const semantic = buildLocalSemanticModel(
|
|
3825
|
+
const semantic = buildLocalSemanticModel(
|
|
3826
|
+
{ symbols, edges },
|
|
3827
|
+
{ semanticRules: loadLocalSemanticRuleConfig(repoRoot) }
|
|
3828
|
+
);
|
|
3826
3829
|
for (const file of files) {
|
|
3827
3830
|
file.edgeCount = edges.filter((edge) => edge.filePath === file.path).length;
|
|
3828
3831
|
}
|
|
@@ -4308,9 +4311,125 @@ function uniqueTraversalFiles(nodes, excluded) {
|
|
|
4308
4311
|
)
|
|
4309
4312
|
].sort();
|
|
4310
4313
|
}
|
|
4314
|
+
var LOCAL_SEMANTIC_RULE_CONFIG_PATH = import_path.default.join(".snipara", "semantic-rules.json");
|
|
4315
|
+
var LOCAL_SEMANTIC_RULE_CONFIG_VERSION = "snipara.semantic.rules.v1";
|
|
4316
|
+
var LOCAL_SEMANTIC_MAX_TERMS = 64;
|
|
4317
|
+
var LOCAL_SEMANTIC_MAX_TERM_LENGTH = 80;
|
|
4318
|
+
var LOCAL_SEMANTIC_MAX_ROLES = 24;
|
|
4311
4319
|
var LOCAL_SEMANTIC_TEST_PATH = /(^|\/)(tests?|__tests__|fixtures?)(\/|$)|\.(test|spec)\./i;
|
|
4312
4320
|
var LOCAL_SEMANTIC_SENSITIVE_PATH = /(^|\/)(auth|middleware|billing|payments?|stripe|webhooks?|prisma|schema|migrations?|deploy)(\/|\.|$)/i;
|
|
4313
4321
|
var LOCAL_SEMANTIC_CONTRACT_PATH = /(^|\/)(schemas?|contracts?|protocols?|interfaces?|config|tool_defs)(\/|\.|$)|route\.(ts|tsx|py)$/i;
|
|
4322
|
+
function localSemanticTerms(raw, label, warnings) {
|
|
4323
|
+
if (raw === void 0 || raw === null) return [];
|
|
4324
|
+
if (!Array.isArray(raw)) {
|
|
4325
|
+
warnings.push(`${label} terms must be an array.`);
|
|
4326
|
+
return [];
|
|
4327
|
+
}
|
|
4328
|
+
const terms = [];
|
|
4329
|
+
for (const value of raw.slice(0, LOCAL_SEMANTIC_MAX_TERMS)) {
|
|
4330
|
+
const term = String(value).trim().toLowerCase();
|
|
4331
|
+
if (!term) continue;
|
|
4332
|
+
if (term.length > LOCAL_SEMANTIC_MAX_TERM_LENGTH) {
|
|
4333
|
+
warnings.push(
|
|
4334
|
+
`Ignored a ${label} term longer than ${LOCAL_SEMANTIC_MAX_TERM_LENGTH} characters.`
|
|
4335
|
+
);
|
|
4336
|
+
continue;
|
|
4337
|
+
}
|
|
4338
|
+
terms.push(term);
|
|
4339
|
+
}
|
|
4340
|
+
if (raw.length > LOCAL_SEMANTIC_MAX_TERMS) {
|
|
4341
|
+
warnings.push(`Limited ${label} terms to ${LOCAL_SEMANTIC_MAX_TERMS} entries.`);
|
|
4342
|
+
}
|
|
4343
|
+
return [...new Set(terms)];
|
|
4344
|
+
}
|
|
4345
|
+
function loadLocalSemanticRuleConfig(repoRoot) {
|
|
4346
|
+
const defaults = {
|
|
4347
|
+
replaceDefaults: false,
|
|
4348
|
+
sensitivePathTerms: [],
|
|
4349
|
+
contractPathTerms: [],
|
|
4350
|
+
testPathTerms: [],
|
|
4351
|
+
architectureRoleTerms: {},
|
|
4352
|
+
source: "defaults",
|
|
4353
|
+
warnings: []
|
|
4354
|
+
};
|
|
4355
|
+
const configPath = import_path.default.join(repoRoot, LOCAL_SEMANTIC_RULE_CONFIG_PATH);
|
|
4356
|
+
if (!import_fs.default.existsSync(configPath)) return defaults;
|
|
4357
|
+
try {
|
|
4358
|
+
const parsed = JSON.parse(import_fs.default.readFileSync(configPath, "utf8"));
|
|
4359
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
4360
|
+
return {
|
|
4361
|
+
...defaults,
|
|
4362
|
+
source: "project-file",
|
|
4363
|
+
warnings: ["Semantic rule config must be a JSON object; defaults were retained."]
|
|
4364
|
+
};
|
|
4365
|
+
}
|
|
4366
|
+
const raw = parsed;
|
|
4367
|
+
const warnings = [];
|
|
4368
|
+
const rawRoles = raw.architectureRoleTerms ?? raw.architecture_role_terms ?? {};
|
|
4369
|
+
const architectureRoleTerms = {};
|
|
4370
|
+
if (rawRoles && typeof rawRoles === "object" && !Array.isArray(rawRoles)) {
|
|
4371
|
+
for (const [rawRole, rawTerms] of Object.entries(rawRoles).slice(
|
|
4372
|
+
0,
|
|
4373
|
+
LOCAL_SEMANTIC_MAX_ROLES
|
|
4374
|
+
)) {
|
|
4375
|
+
const role = rawRole.trim().toLowerCase().replace(/[^a-z0-9_]+/g, "_").replace(/^_+|_+$/g, "");
|
|
4376
|
+
if (!role) {
|
|
4377
|
+
warnings.push("Ignored an empty or unsupported architecture role name.");
|
|
4378
|
+
continue;
|
|
4379
|
+
}
|
|
4380
|
+
architectureRoleTerms[role] = localSemanticTerms(
|
|
4381
|
+
rawTerms,
|
|
4382
|
+
`architecture role ${role}`,
|
|
4383
|
+
warnings
|
|
4384
|
+
);
|
|
4385
|
+
}
|
|
4386
|
+
} else if (rawRoles) {
|
|
4387
|
+
warnings.push("architectureRoleTerms must be an object of role-to-term arrays.");
|
|
4388
|
+
}
|
|
4389
|
+
return {
|
|
4390
|
+
replaceDefaults: Boolean(raw.replaceDefaults ?? raw.replace_defaults ?? false),
|
|
4391
|
+
sensitivePathTerms: localSemanticTerms(
|
|
4392
|
+
raw.sensitivePathTerms ?? raw.sensitive_path_terms,
|
|
4393
|
+
"sensitive path",
|
|
4394
|
+
warnings
|
|
4395
|
+
),
|
|
4396
|
+
contractPathTerms: localSemanticTerms(
|
|
4397
|
+
raw.contractPathTerms ?? raw.contract_path_terms,
|
|
4398
|
+
"contract path",
|
|
4399
|
+
warnings
|
|
4400
|
+
),
|
|
4401
|
+
testPathTerms: localSemanticTerms(
|
|
4402
|
+
raw.testPathTerms ?? raw.test_path_terms,
|
|
4403
|
+
"test path",
|
|
4404
|
+
warnings
|
|
4405
|
+
),
|
|
4406
|
+
architectureRoleTerms,
|
|
4407
|
+
source: "project-file",
|
|
4408
|
+
warnings
|
|
4409
|
+
};
|
|
4410
|
+
} catch (error) {
|
|
4411
|
+
return {
|
|
4412
|
+
...defaults,
|
|
4413
|
+
source: "project-file",
|
|
4414
|
+
warnings: [
|
|
4415
|
+
`Could not parse ${LOCAL_SEMANTIC_RULE_CONFIG_PATH}: ${error instanceof Error ? error.message : "invalid JSON"}`
|
|
4416
|
+
]
|
|
4417
|
+
};
|
|
4418
|
+
}
|
|
4419
|
+
}
|
|
4420
|
+
function localSemanticMatchesTerms(value, terms) {
|
|
4421
|
+
const normalized = value.replace(/\\/g, "/").toLowerCase();
|
|
4422
|
+
return terms.some((term) => normalized.includes(term));
|
|
4423
|
+
}
|
|
4424
|
+
function localSemanticIsTestPath(filePath, rules) {
|
|
4425
|
+
return !rules.replaceDefaults && LOCAL_SEMANTIC_TEST_PATH.test(filePath) || localSemanticMatchesTerms(filePath, rules.testPathTerms);
|
|
4426
|
+
}
|
|
4427
|
+
function localSemanticIsSensitivePath(filePath, rules) {
|
|
4428
|
+
return !rules.replaceDefaults && LOCAL_SEMANTIC_SENSITIVE_PATH.test(filePath) || localSemanticMatchesTerms(filePath, rules.sensitivePathTerms);
|
|
4429
|
+
}
|
|
4430
|
+
function localSemanticIsContractPath(filePath, rules) {
|
|
4431
|
+
return !rules.replaceDefaults && LOCAL_SEMANTIC_CONTRACT_PATH.test(filePath) || localSemanticMatchesTerms(filePath, rules.contractPathTerms);
|
|
4432
|
+
}
|
|
4314
4433
|
function localSemanticEvidence(kind, detail, metadata = {}) {
|
|
4315
4434
|
return { kind, detail, ...metadata };
|
|
4316
4435
|
}
|
|
@@ -4322,14 +4441,16 @@ function localSemanticAssertion(subject, predicate, value, confidence, source2,
|
|
|
4322
4441
|
predicate,
|
|
4323
4442
|
value,
|
|
4324
4443
|
confidence: Math.round(Math.max(0, Math.min(1, confidence)) * 1e3) / 1e3,
|
|
4444
|
+
scoreKind: "heuristic_prior",
|
|
4445
|
+
calibrated: false,
|
|
4325
4446
|
source: source2,
|
|
4326
4447
|
extractorVersion: 1,
|
|
4327
4448
|
evidence
|
|
4328
4449
|
};
|
|
4329
4450
|
}
|
|
4330
|
-
function localArchitectureRoles(symbol) {
|
|
4451
|
+
function localArchitectureRoles(symbol, rules) {
|
|
4331
4452
|
const haystack = `${symbol.filePath} ${symbol.name}`;
|
|
4332
|
-
const
|
|
4453
|
+
const defaultCandidates = [
|
|
4333
4454
|
{ role: "repository", pattern: /repository|(^|[_.])repo($|[_.])/i, confidence: 0.9 },
|
|
4334
4455
|
{ role: "adapter", pattern: /adapter/i, confidence: 0.9 },
|
|
4335
4456
|
{ role: "facade", pattern: /facade/i, confidence: 0.9 },
|
|
@@ -4356,7 +4477,7 @@ function localArchitectureRoles(symbol) {
|
|
|
4356
4477
|
)
|
|
4357
4478
|
);
|
|
4358
4479
|
}
|
|
4359
|
-
if (
|
|
4480
|
+
if (localSemanticIsTestPath(symbol.filePath, rules)) {
|
|
4360
4481
|
roles.push(
|
|
4361
4482
|
localSemanticAssertion(
|
|
4362
4483
|
symbol.localKey,
|
|
@@ -4368,7 +4489,7 @@ function localArchitectureRoles(symbol) {
|
|
|
4368
4489
|
)
|
|
4369
4490
|
);
|
|
4370
4491
|
}
|
|
4371
|
-
for (const candidate of
|
|
4492
|
+
for (const candidate of rules.replaceDefaults ? [] : defaultCandidates) {
|
|
4372
4493
|
if (!candidate.pattern.test(haystack)) continue;
|
|
4373
4494
|
roles.push(
|
|
4374
4495
|
localSemanticAssertion(
|
|
@@ -4387,9 +4508,29 @@ function localArchitectureRoles(symbol) {
|
|
|
4387
4508
|
)
|
|
4388
4509
|
);
|
|
4389
4510
|
}
|
|
4511
|
+
const existingRoles = new Set(roles.map((assertion) => String(assertion.value)));
|
|
4512
|
+
for (const [role, terms] of Object.entries(rules.architectureRoleTerms)) {
|
|
4513
|
+
if (existingRoles.has(role) || !localSemanticMatchesTerms(haystack, terms)) continue;
|
|
4514
|
+
roles.push(
|
|
4515
|
+
localSemanticAssertion(
|
|
4516
|
+
symbol.localKey,
|
|
4517
|
+
"architecture_role",
|
|
4518
|
+
role,
|
|
4519
|
+
0.7,
|
|
4520
|
+
"architecture-pattern-rules",
|
|
4521
|
+
[
|
|
4522
|
+
localSemanticEvidence(
|
|
4523
|
+
"project_role_term",
|
|
4524
|
+
`Name or path matches a configured ${role} project term.`,
|
|
4525
|
+
{ filePath: symbol.filePath, line: symbol.line }
|
|
4526
|
+
)
|
|
4527
|
+
]
|
|
4528
|
+
)
|
|
4529
|
+
);
|
|
4530
|
+
}
|
|
4390
4531
|
return roles;
|
|
4391
4532
|
}
|
|
4392
|
-
function localSymbolSemanticAssertions(symbol) {
|
|
4533
|
+
function localSymbolSemanticAssertions(symbol, rules) {
|
|
4393
4534
|
const assertions = [];
|
|
4394
4535
|
const routeSurface = /\/app\/api\//i.test(symbol.filePath) || /\/route\.(ts|tsx|py)$/i.test(symbol.filePath);
|
|
4395
4536
|
const toolSurface = /(^|\/)(mcp|tool_defs)(\/|\.|$)/i.test(symbol.filePath);
|
|
@@ -4445,7 +4586,7 @@ function localSymbolSemanticAssertions(symbol) {
|
|
|
4445
4586
|
contractEvidence.push(
|
|
4446
4587
|
localSemanticEvidence("symbol_kind", `Exported ${symbol.kind} defines a structural contract.`)
|
|
4447
4588
|
);
|
|
4448
|
-
} else if (
|
|
4589
|
+
} else if (localSemanticIsContractPath(symbol.filePath, rules)) {
|
|
4449
4590
|
contractKind = "repository_boundary_contract";
|
|
4450
4591
|
contractEvidence.push(
|
|
4451
4592
|
localSemanticEvidence(
|
|
@@ -4469,17 +4610,17 @@ function localSymbolSemanticAssertions(symbol) {
|
|
|
4469
4610
|
)
|
|
4470
4611
|
);
|
|
4471
4612
|
}
|
|
4472
|
-
assertions.push(...localArchitectureRoles(symbol));
|
|
4613
|
+
assertions.push(...localArchitectureRoles(symbol, rules));
|
|
4473
4614
|
let criticality = "ordinary";
|
|
4474
4615
|
let criticalityConfidence = 0.58;
|
|
4475
4616
|
let criticalityEvidence = [
|
|
4476
4617
|
localSemanticEvidence("default", "No critical or incidental evidence matched.")
|
|
4477
4618
|
];
|
|
4478
|
-
if (
|
|
4619
|
+
if (localSemanticIsTestPath(symbol.filePath, rules)) {
|
|
4479
4620
|
criticality = "incidental";
|
|
4480
4621
|
criticalityConfidence = 0.95;
|
|
4481
4622
|
criticalityEvidence = [localSemanticEvidence("test_path", "Test-only dependency.")];
|
|
4482
|
-
} else if (
|
|
4623
|
+
} else if (localSemanticIsSensitivePath(symbol.filePath, rules) || routeSurface || toolSurface) {
|
|
4483
4624
|
criticality = "critical";
|
|
4484
4625
|
criticalityConfidence = 0.92;
|
|
4485
4626
|
criticalityEvidence = [
|
|
@@ -4517,8 +4658,9 @@ function localSymbolSemanticAssertions(symbol) {
|
|
|
4517
4658
|
return assertions;
|
|
4518
4659
|
}
|
|
4519
4660
|
function buildLocalSemanticModel(manifest, options = {}) {
|
|
4661
|
+
const rules = options.semanticRules ?? loadLocalSemanticRuleConfig(process.cwd());
|
|
4520
4662
|
const symbols = options.symbolKeys ? manifest.symbols.filter((symbol) => options.symbolKeys?.has(symbol.localKey)) : manifest.symbols;
|
|
4521
|
-
const assertions = symbols.flatMap(localSymbolSemanticAssertions);
|
|
4663
|
+
const assertions = symbols.flatMap((symbol) => localSymbolSemanticAssertions(symbol, rules));
|
|
4522
4664
|
const symbolByKey = new Map(manifest.symbols.map((symbol) => [symbol.localKey, symbol]));
|
|
4523
4665
|
const criticalityBySymbol = new Map(
|
|
4524
4666
|
assertions.filter((assertion) => assertion.predicate === "dependency_criticality").map((assertion) => [assertion.subject, String(assertion.value)])
|
|
@@ -4555,7 +4697,7 @@ function buildLocalSemanticModel(manifest, options = {}) {
|
|
|
4555
4697
|
];
|
|
4556
4698
|
let value = "ordinary";
|
|
4557
4699
|
let confidence = 0.62;
|
|
4558
|
-
if (
|
|
4700
|
+
if (localSemanticIsTestPath(fromFile, rules) || localSemanticIsTestPath(toFile, rules)) {
|
|
4559
4701
|
value = "incidental";
|
|
4560
4702
|
confidence = 0.92;
|
|
4561
4703
|
evidence.push(localSemanticEvidence("test_path", "At least one edge endpoint is test-only."));
|
|
@@ -4626,6 +4768,40 @@ function buildLocalSemanticModel(manifest, options = {}) {
|
|
|
4626
4768
|
version: "snipara.semantic.v1",
|
|
4627
4769
|
extractorVersion: 1,
|
|
4628
4770
|
source: "deterministic-graph-inference",
|
|
4771
|
+
modelType: "rule-based-heuristic",
|
|
4772
|
+
scoreContract: {
|
|
4773
|
+
kind: "heuristic_prior",
|
|
4774
|
+
calibrated: false,
|
|
4775
|
+
probability: false,
|
|
4776
|
+
comparableAcrossProjects: false,
|
|
4777
|
+
basis: "hand-tuned-v1",
|
|
4778
|
+
rulesetVersion: 1,
|
|
4779
|
+
interpretation: "Relative rule strength for ranking and explanation; not an observed probability."
|
|
4780
|
+
},
|
|
4781
|
+
ruleConfig: {
|
|
4782
|
+
version: LOCAL_SEMANTIC_RULE_CONFIG_VERSION,
|
|
4783
|
+
source: rules.source,
|
|
4784
|
+
path: ".snipara/semantic-rules.json",
|
|
4785
|
+
replaceDefaults: rules.replaceDefaults,
|
|
4786
|
+
sensitivePathTermCount: rules.sensitivePathTerms.length,
|
|
4787
|
+
contractPathTermCount: rules.contractPathTerms.length,
|
|
4788
|
+
testPathTermCount: rules.testPathTerms.length,
|
|
4789
|
+
architectureRoleTermCount: Object.values(rules.architectureRoleTerms).reduce(
|
|
4790
|
+
(total, terms) => total + terms.length,
|
|
4791
|
+
0
|
|
4792
|
+
),
|
|
4793
|
+
configuredRoles: Object.keys(rules.architectureRoleTerms).sort(),
|
|
4794
|
+
configHash: sha2563(
|
|
4795
|
+
JSON.stringify({
|
|
4796
|
+
replaceDefaults: rules.replaceDefaults,
|
|
4797
|
+
sensitivePathTerms: rules.sensitivePathTerms,
|
|
4798
|
+
contractPathTerms: rules.contractPathTerms,
|
|
4799
|
+
testPathTerms: rules.testPathTerms,
|
|
4800
|
+
architectureRoleTerms: rules.architectureRoleTerms
|
|
4801
|
+
})
|
|
4802
|
+
).slice(0, 16),
|
|
4803
|
+
warnings: rules.warnings
|
|
4804
|
+
},
|
|
4629
4805
|
scope: options.scope ?? "repository",
|
|
4630
4806
|
assertions: returnedAssertions,
|
|
4631
4807
|
publicContracts,
|
|
@@ -4663,7 +4839,9 @@ function buildLocalSemanticModel(manifest, options = {}) {
|
|
|
4663
4839
|
},
|
|
4664
4840
|
caveats: [
|
|
4665
4841
|
"Semantic assertions are deterministic inferences, not declarations of author intent.",
|
|
4666
|
-
"
|
|
4842
|
+
"Confidence is an uncalibrated rule-based prior, not a probability or measured accuracy.",
|
|
4843
|
+
"Priors are not comparable across projects with different semantic rule configuration.",
|
|
4844
|
+
"Name-only architecture patterns use a lower prior than explicit export or path evidence.",
|
|
4667
4845
|
"Historical regression learning requires hosted outcome evidence and remains shadow-only."
|
|
4668
4846
|
]
|
|
4669
4847
|
};
|
|
@@ -4735,7 +4913,8 @@ function buildLocalRisk(manifest, changedFiles, traversal, depth, semantic) {
|
|
|
4735
4913
|
const changedSymbolKeys = new Set(
|
|
4736
4914
|
manifest.symbols.filter((symbol) => changedFiles.includes(symbol.filePath)).map((symbol) => symbol.localKey)
|
|
4737
4915
|
);
|
|
4738
|
-
const
|
|
4916
|
+
const semanticRules = loadLocalSemanticRuleConfig(manifest.repoRoot);
|
|
4917
|
+
const changedContracts = manifest.symbols.filter((symbol) => changedSymbolKeys.has(symbol.localKey)).flatMap((symbol) => localSymbolSemanticAssertions(symbol, semanticRules)).filter(
|
|
4739
4918
|
(assertion) => assertion.predicate === "public_api" || assertion.predicate === "implicit_contract"
|
|
4740
4919
|
).length;
|
|
4741
4920
|
const semanticRiskPoints = Math.min(20, criticalSemanticDependencies * 3) + Math.min(10, changedContracts * 2);
|
|
@@ -5800,7 +5979,8 @@ function buildLocalImpactResult(options) {
|
|
|
5800
5979
|
const semantic = buildLocalSemanticModel(manifest, {
|
|
5801
5980
|
scope: "impact",
|
|
5802
5981
|
symbolKeys: semanticSymbolKeys,
|
|
5803
|
-
edges: traversalEdges
|
|
5982
|
+
edges: traversalEdges,
|
|
5983
|
+
semanticRules: loadLocalSemanticRuleConfig(manifest.repoRoot)
|
|
5804
5984
|
});
|
|
5805
5985
|
const transitiveFiles = uniqueTraversalFiles(traversal.nodes, selectedFiles);
|
|
5806
5986
|
const impactedFiles = [.../* @__PURE__ */ new Set([...directImpactedFiles, ...transitiveFiles])].sort();
|
package/docs/CODE_GRAPH.md
CHANGED
|
@@ -14,7 +14,9 @@ contains:
|
|
|
14
14
|
- `predicate`: `public_api`, `implicit_contract`, `architecture_role`, or
|
|
15
15
|
`dependency_criticality`;
|
|
16
16
|
- `value`: the inferred classification;
|
|
17
|
-
- `confidence`: a bounded
|
|
17
|
+
- `confidence`: a backward-compatible bounded rule-strength prior from `0` to
|
|
18
|
+
`1`, explicitly qualified by `scoreKind: "heuristic_prior"` and
|
|
19
|
+
`calibrated: false`;
|
|
18
20
|
- `source` and `extractorVersion`;
|
|
19
21
|
- `evidence`: the export modifier, symbol kind, file/path pattern, or graph edge
|
|
20
22
|
that produced the assertion.
|
|
@@ -22,8 +24,37 @@ contains:
|
|
|
22
24
|
The extractor recognizes explicit TypeScript and Go exports, route and MCP
|
|
23
25
|
surfaces, exported type contracts, and common Repository, Adapter, Facade,
|
|
24
26
|
Controller, Service, Worker, Factory, schema, route-handler, and test roles.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
`confidence` is not a probability, measured accuracy, or cross-project metric.
|
|
28
|
+
It is a deterministic prior used for relative ranking and explanation inside
|
|
29
|
+
one rule configuration. `semantic.scoreContract.basis` is
|
|
30
|
+
`hand-tuned-v1`; the values must be calibrated against measured outcomes before
|
|
31
|
+
that status can change. Name-only patterns have a lower prior than explicit
|
|
32
|
+
export, route, or graph evidence.
|
|
33
|
+
|
|
34
|
+
### Project naming rules
|
|
35
|
+
|
|
36
|
+
Projects can extend or replace built-in English naming conventions with
|
|
37
|
+
`.snipara/semantic-rules.json`:
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"replaceDefaults": false,
|
|
42
|
+
"sensitivePathTerms": ["securite", "facturation"],
|
|
43
|
+
"contractPathTerms": ["contrats"],
|
|
44
|
+
"testPathTerms": ["essais"],
|
|
45
|
+
"architectureRoleTerms": {
|
|
46
|
+
"repository": ["depot"],
|
|
47
|
+
"service": ["metier"]
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Terms are case-insensitive literal strings, not regular expressions. Each group
|
|
53
|
+
is limited to 64 terms of at most 80 characters and role names are normalized.
|
|
54
|
+
Invalid config is reported in `semantic.ruleConfig.warnings`; it never aborts
|
|
55
|
+
overlay construction. Set `replaceDefaults: true` only when the project wants
|
|
56
|
+
configured naming terms to replace built-in naming conventions. Explicit
|
|
57
|
+
exports, route surfaces, and graph evidence continue to apply.
|
|
27
58
|
|
|
28
59
|
## Dependency criticality
|
|
29
60
|
|
|
@@ -51,8 +82,9 @@ does not manufacture cross-source edges or assertions.
|
|
|
51
82
|
## Historical regression paths
|
|
52
83
|
|
|
53
84
|
Hosted impact can associate sanitized warning/error execution traces with
|
|
54
|
-
graph paths by their tracked files. Version 1 reports sample count,
|
|
55
|
-
event IDs, and a suggested risk
|
|
85
|
+
graph paths by their tracked files. Version 1 reports sample count, an
|
|
86
|
+
uncalibrated shadow association strength, event IDs, and a suggested risk
|
|
87
|
+
delta. This surface is intentionally
|
|
56
88
|
`mode: shadow` and `riskContributionEnabled: false`: temporal association is
|
|
57
89
|
not causal proof, and it cannot affect risk until separately calibrated and
|
|
58
90
|
promoted.
|