@neurcode-ai/cli 0.16.4 → 0.16.6
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/.telemetry-bundle/dist/index.js +0 -0
- package/LICENSE +201 -0
- package/dist/api-client.d.ts +75 -0
- package/dist/api-client.d.ts.map +1 -1
- package/dist/api-client.js +43 -0
- package/dist/api-client.js.map +1 -1
- package/dist/commands/brain.d.ts.map +1 -1
- package/dist/commands/brain.js +151 -0
- package/dist/commands/brain.js.map +1 -1
- package/dist/commands/cursor.d.ts.map +1 -1
- package/dist/commands/cursor.js +72 -0
- package/dist/commands/cursor.js.map +1 -1
- package/dist/commands/eval.d.ts +19 -0
- package/dist/commands/eval.d.ts.map +1 -0
- package/dist/commands/eval.js +246 -0
- package/dist/commands/eval.js.map +1 -0
- package/dist/commands/onboard.d.ts +29 -0
- package/dist/commands/onboard.d.ts.map +1 -0
- package/dist/commands/onboard.js +247 -0
- package/dist/commands/onboard.js.map +1 -0
- package/dist/commands/runtime-doctor.d.ts.map +1 -1
- package/dist/commands/runtime-doctor.js +80 -9
- package/dist/commands/runtime-doctor.js.map +1 -1
- package/dist/commands/runtime-sync.d.ts.map +1 -1
- package/dist/commands/runtime-sync.js +22 -0
- package/dist/commands/runtime-sync.js.map +1 -1
- package/dist/commands/runtime.d.ts +18 -0
- package/dist/commands/runtime.d.ts.map +1 -1
- package/dist/commands/runtime.js +321 -1
- package/dist/commands/runtime.js.map +1 -1
- package/dist/commands/session-hook.d.ts +10 -2
- package/dist/commands/session-hook.d.ts.map +1 -1
- package/dist/commands/session-hook.js +533 -122
- package/dist/commands/session-hook.js.map +1 -1
- package/dist/commands/session.d.ts +34 -0
- package/dist/commands/session.d.ts.map +1 -1
- package/dist/commands/session.js +243 -2
- package/dist/commands/session.js.map +1 -1
- package/dist/index.js +84 -0
- package/dist/index.js.map +1 -1
- package/dist/runtime-build.json +5 -5
- package/dist/utils/agent-guard-supervisor.d.ts.map +1 -1
- package/dist/utils/agent-guard-supervisor.js +0 -1
- package/dist/utils/agent-guard-supervisor.js.map +1 -1
- package/dist/utils/cursor-gate.d.ts +1 -0
- package/dist/utils/cursor-gate.d.ts.map +1 -1
- package/dist/utils/cursor-gate.js +34 -7
- package/dist/utils/cursor-gate.js.map +1 -1
- package/dist/utils/guided-eval.d.ts +251 -0
- package/dist/utils/guided-eval.d.ts.map +1 -0
- package/dist/utils/guided-eval.js +880 -0
- package/dist/utils/guided-eval.js.map +1 -0
- package/dist/utils/local-repo-brain.d.ts +158 -0
- package/dist/utils/local-repo-brain.d.ts.map +1 -0
- package/dist/utils/local-repo-brain.js +854 -0
- package/dist/utils/local-repo-brain.js.map +1 -0
- package/dist/utils/runtime-live.d.ts +25 -0
- package/dist/utils/runtime-live.d.ts.map +1 -1
- package/dist/utils/runtime-live.js +103 -4
- package/dist/utils/runtime-live.js.map +1 -1
- package/dist/utils/runtime-outbox.d.ts +2 -1
- package/dist/utils/runtime-outbox.d.ts.map +1 -1
- package/dist/utils/runtime-outbox.js +21 -16
- package/dist/utils/runtime-outbox.js.map +1 -1
- package/dist/utils/session-allowlist-rules.d.ts +12 -0
- package/dist/utils/session-allowlist-rules.d.ts.map +1 -1
- package/dist/utils/session-allowlist-rules.js +61 -1
- package/dist/utils/session-allowlist-rules.js.map +1 -1
- package/dist/utils/structural-understanding.d.ts +61 -1
- package/dist/utils/structural-understanding.d.ts.map +1 -1
- package/dist/utils/structural-understanding.js +534 -1
- package/dist/utils/structural-understanding.js.map +1 -1
- package/dist/utils/v0-governance.d.ts.map +1 -1
- package/dist/utils/v0-governance.js +10 -0
- package/dist/utils/v0-governance.js.map +1 -1
- package/package.json +7 -8
|
@@ -48,6 +48,10 @@ exports.STRUCTURAL_UNDERSTANDING_SCHEMA_VERSION = 'neurcode.structural-understan
|
|
|
48
48
|
exports.CONSEQUENCE_UNDERSTANDING_SCHEMA_VERSION = 'neurcode.consequence-understanding.v1';
|
|
49
49
|
const TS_LIKE = /\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/i;
|
|
50
50
|
const SKIP_DIR = /^(node_modules|\.git|dist|build|out|coverage|\.next|vendor)$/i;
|
|
51
|
+
const REUSE_MIN_TOKEN_COUNT = 12;
|
|
52
|
+
const REUSE_TOKEN_SHINGLE_SIZE = 5;
|
|
53
|
+
const REUSE_TOKEN_OVERLAP_THRESHOLD = 0.86;
|
|
54
|
+
const REUSE_MAX_FINDINGS = 20;
|
|
51
55
|
const EFFECT_REGISTRY = [
|
|
52
56
|
{
|
|
53
57
|
category: 'filesystem-write',
|
|
@@ -197,6 +201,27 @@ function emptyConsequenceUnderstanding(generatedAt, reason = null) {
|
|
|
197
201
|
};
|
|
198
202
|
return { ...core, artifactHash: hash(consequenceHashInput(core)) };
|
|
199
203
|
}
|
|
204
|
+
function emptyRepoSymbolIndexSummary() {
|
|
205
|
+
return {
|
|
206
|
+
schemaVersion: 'neurcode.repo-symbol-index.v1',
|
|
207
|
+
language: 'typescript/javascript',
|
|
208
|
+
modelUsed: false,
|
|
209
|
+
sourceUploaded: false,
|
|
210
|
+
sourceStored: false,
|
|
211
|
+
indexedFileCount: 0,
|
|
212
|
+
indexedSymbolCount: 0,
|
|
213
|
+
exportedSymbolCount: 0,
|
|
214
|
+
localFunctionCount: 0,
|
|
215
|
+
methodCount: 0,
|
|
216
|
+
classCount: 0,
|
|
217
|
+
changedCandidateCount: 0,
|
|
218
|
+
unsupportedLanguages: ['python'],
|
|
219
|
+
indexHash: hash('empty-repo-symbol-index'),
|
|
220
|
+
fingerprintAlgorithm: 'typescript-scanner-normalized-token-shingles-v1',
|
|
221
|
+
signatureAlgorithm: 'name-insensitive-kind-arity-param-return-shape-v1',
|
|
222
|
+
provenance: 'repo-symbol-index',
|
|
223
|
+
};
|
|
224
|
+
}
|
|
200
225
|
function none(projectRoot, diffFiles, reason, generatedAt, suppressedArtifacts = []) {
|
|
201
226
|
const core = {
|
|
202
227
|
schemaVersion: exports.STRUCTURAL_UNDERSTANDING_SCHEMA_VERSION,
|
|
@@ -222,6 +247,8 @@ function none(projectRoot, diffFiles, reason, generatedAt, suppressedArtifacts =
|
|
|
222
247
|
testReferences: [],
|
|
223
248
|
digest: emptyDigest(generatedAt),
|
|
224
249
|
consequenceUnderstanding: emptyConsequenceUnderstanding(generatedAt, reason),
|
|
250
|
+
repoSymbolIndex: emptyRepoSymbolIndexSummary(),
|
|
251
|
+
reuseFindings: [],
|
|
225
252
|
planAlignment: null,
|
|
226
253
|
boundaryImpact: [],
|
|
227
254
|
limitations: commonLimitations(),
|
|
@@ -236,7 +263,7 @@ function privacyBlock() {
|
|
|
236
263
|
diffStored: false,
|
|
237
264
|
modelUsed: false,
|
|
238
265
|
factsOnly: true,
|
|
239
|
-
outputContains: ['repo-relative paths', 'symbol names', 'line numbers', 'import targets', 'owner tokens', 'hashes'],
|
|
266
|
+
outputContains: ['repo-relative paths', 'symbol names', 'line numbers', 'import targets', 'owner tokens', 'hashes', 'signature hashes', 'token fingerprints'],
|
|
240
267
|
outputOmits: ['source text', 'diff hunks', 'patch content', 'shell command bodies', 'model judgments'],
|
|
241
268
|
};
|
|
242
269
|
}
|
|
@@ -260,6 +287,8 @@ function provenanceMap() {
|
|
|
260
287
|
consequenceUnderstanding: 'typescript-checker',
|
|
261
288
|
planAlignment: 'session-plan',
|
|
262
289
|
boundaryImpact: 'codeowners-profile',
|
|
290
|
+
repoSymbolIndex: 'repo-symbol-index',
|
|
291
|
+
reuseFindings: 'repo-symbol-index',
|
|
263
292
|
};
|
|
264
293
|
}
|
|
265
294
|
function changedFileFacts(diffFiles) {
|
|
@@ -926,6 +955,501 @@ function collectDeclarations(sourceFile, checker, projectRoot) {
|
|
|
926
955
|
ts.forEachChild(sourceFile, visit);
|
|
927
956
|
return out;
|
|
928
957
|
}
|
|
958
|
+
function isFunctionLikeVariableDeclaration(node) {
|
|
959
|
+
return ts.isVariableDeclaration(node) &&
|
|
960
|
+
Boolean(node.initializer && (ts.isArrowFunction(node.initializer) || ts.isFunctionExpression(node.initializer)));
|
|
961
|
+
}
|
|
962
|
+
function functionLikeVariableInitializer(node) {
|
|
963
|
+
if (!ts.isVariableDeclaration(node))
|
|
964
|
+
return null;
|
|
965
|
+
const initializer = node.initializer;
|
|
966
|
+
return initializer && (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer))
|
|
967
|
+
? initializer
|
|
968
|
+
: null;
|
|
969
|
+
}
|
|
970
|
+
function reusableKind(node) {
|
|
971
|
+
if (ts.isClassDeclaration(node))
|
|
972
|
+
return 'class';
|
|
973
|
+
if (ts.isMethodDeclaration(node))
|
|
974
|
+
return 'method';
|
|
975
|
+
if (ts.isFunctionDeclaration(node) || isFunctionLikeVariableDeclaration(node))
|
|
976
|
+
return 'function';
|
|
977
|
+
return null;
|
|
978
|
+
}
|
|
979
|
+
function enclosingClassDeclaration(node) {
|
|
980
|
+
let current = node.parent;
|
|
981
|
+
while (current) {
|
|
982
|
+
if (ts.isClassDeclaration(current))
|
|
983
|
+
return current;
|
|
984
|
+
current = current.parent;
|
|
985
|
+
}
|
|
986
|
+
return null;
|
|
987
|
+
}
|
|
988
|
+
function isTopLevelReusableDeclaration(node) {
|
|
989
|
+
if (ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node)) {
|
|
990
|
+
return ts.isSourceFile(node.parent);
|
|
991
|
+
}
|
|
992
|
+
if (ts.isVariableDeclaration(node)) {
|
|
993
|
+
return Boolean(node.parent &&
|
|
994
|
+
ts.isVariableDeclarationList(node.parent) &&
|
|
995
|
+
node.parent.parent &&
|
|
996
|
+
ts.isVariableStatement(node.parent.parent) &&
|
|
997
|
+
ts.isSourceFile(node.parent.parent.parent));
|
|
998
|
+
}
|
|
999
|
+
return false;
|
|
1000
|
+
}
|
|
1001
|
+
function isExportedReusableDeclaration(node) {
|
|
1002
|
+
if (isExportedDeclaration(node))
|
|
1003
|
+
return true;
|
|
1004
|
+
if (ts.isMethodDeclaration(node)) {
|
|
1005
|
+
const parentClass = enclosingClassDeclaration(node);
|
|
1006
|
+
return Boolean(parentClass && isExportedDeclaration(parentClass));
|
|
1007
|
+
}
|
|
1008
|
+
return false;
|
|
1009
|
+
}
|
|
1010
|
+
function callableParameters(node) {
|
|
1011
|
+
if (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node))
|
|
1012
|
+
return Array.from(node.parameters);
|
|
1013
|
+
const initializer = functionLikeVariableInitializer(node);
|
|
1014
|
+
if (initializer)
|
|
1015
|
+
return Array.from(initializer.parameters);
|
|
1016
|
+
return [];
|
|
1017
|
+
}
|
|
1018
|
+
function callableReturnTypeNode(node) {
|
|
1019
|
+
if (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node))
|
|
1020
|
+
return node.type ?? null;
|
|
1021
|
+
const initializer = functionLikeVariableInitializer(node);
|
|
1022
|
+
if (initializer)
|
|
1023
|
+
return initializer.type ?? null;
|
|
1024
|
+
return null;
|
|
1025
|
+
}
|
|
1026
|
+
function propertyNameText(name) {
|
|
1027
|
+
if (!name)
|
|
1028
|
+
return null;
|
|
1029
|
+
if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name))
|
|
1030
|
+
return name.text;
|
|
1031
|
+
if (ts.isPrivateIdentifier(name))
|
|
1032
|
+
return name.text;
|
|
1033
|
+
return null;
|
|
1034
|
+
}
|
|
1035
|
+
function typeShape(node) {
|
|
1036
|
+
if (!node)
|
|
1037
|
+
return 'implicit';
|
|
1038
|
+
switch (node.kind) {
|
|
1039
|
+
case ts.SyntaxKind.StringKeyword: return 'string';
|
|
1040
|
+
case ts.SyntaxKind.NumberKeyword: return 'number';
|
|
1041
|
+
case ts.SyntaxKind.BooleanKeyword: return 'boolean';
|
|
1042
|
+
case ts.SyntaxKind.VoidKeyword: return 'void';
|
|
1043
|
+
case ts.SyntaxKind.NullKeyword: return 'null';
|
|
1044
|
+
case ts.SyntaxKind.UndefinedKeyword: return 'undefined';
|
|
1045
|
+
case ts.SyntaxKind.AnyKeyword: return 'any';
|
|
1046
|
+
case ts.SyntaxKind.UnknownKeyword: return 'unknown';
|
|
1047
|
+
case ts.SyntaxKind.NeverKeyword: return 'never';
|
|
1048
|
+
case ts.SyntaxKind.ObjectKeyword: return 'object';
|
|
1049
|
+
case ts.SyntaxKind.ArrayType: return 'array';
|
|
1050
|
+
case ts.SyntaxKind.TupleType: return 'tuple';
|
|
1051
|
+
case ts.SyntaxKind.TypeLiteral: return 'object-literal';
|
|
1052
|
+
case ts.SyntaxKind.UnionType: return 'union';
|
|
1053
|
+
case ts.SyntaxKind.IntersectionType: return 'intersection';
|
|
1054
|
+
case ts.SyntaxKind.FunctionType: return 'function';
|
|
1055
|
+
case ts.SyntaxKind.LiteralType: return 'literal';
|
|
1056
|
+
case ts.SyntaxKind.ParenthesizedType: return ts.isParenthesizedTypeNode(node) ? typeShape(node.type) : 'parenthesized';
|
|
1057
|
+
case ts.SyntaxKind.TypePredicate: return 'predicate';
|
|
1058
|
+
default:
|
|
1059
|
+
if (ts.isTypeReferenceNode(node)) {
|
|
1060
|
+
const name = node.typeName.getText(node.getSourceFile()).toLowerCase();
|
|
1061
|
+
if (name === 'promise')
|
|
1062
|
+
return 'promise';
|
|
1063
|
+
if (name === 'array' || name === 'readonlyarray')
|
|
1064
|
+
return 'array';
|
|
1065
|
+
if (name === 'record' || name === 'map' || name === 'set' || name === 'weakmap' || name === 'weakset')
|
|
1066
|
+
return 'collection';
|
|
1067
|
+
return 'reference';
|
|
1068
|
+
}
|
|
1069
|
+
return 'other';
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
function parameterShape(param) {
|
|
1073
|
+
const rest = param.dotDotDotToken ? 'rest' : 'value';
|
|
1074
|
+
const optional = param.questionToken || param.initializer ? 'optional' : 'required';
|
|
1075
|
+
return `${rest}:${optional}:${typeShape(param.type)}`;
|
|
1076
|
+
}
|
|
1077
|
+
function classMemberShapes(node) {
|
|
1078
|
+
const shapes = [];
|
|
1079
|
+
for (const member of node.members) {
|
|
1080
|
+
const name = propertyNameText(member.name) ?? 'anonymous';
|
|
1081
|
+
if (ts.isConstructorDeclaration(member)) {
|
|
1082
|
+
shapes.push(`constructor:${member.parameters.length}`);
|
|
1083
|
+
}
|
|
1084
|
+
else if (ts.isMethodDeclaration(member)) {
|
|
1085
|
+
shapes.push(`method:${name}:${member.parameters.length}:${typeShape(member.type)}`);
|
|
1086
|
+
}
|
|
1087
|
+
else if (ts.isPropertyDeclaration(member)) {
|
|
1088
|
+
shapes.push(`property:${name}:${typeShape(member.type)}`);
|
|
1089
|
+
}
|
|
1090
|
+
else if (ts.isGetAccessor(member)) {
|
|
1091
|
+
shapes.push(`get:${name}:${typeShape(member.type)}`);
|
|
1092
|
+
}
|
|
1093
|
+
else if (ts.isSetAccessor(member)) {
|
|
1094
|
+
shapes.push(`set:${name}:${member.parameters.length}`);
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
return uniqueSorted(shapes);
|
|
1098
|
+
}
|
|
1099
|
+
function signatureShapeForDeclaration(node, kind) {
|
|
1100
|
+
if (kind === 'class' && ts.isClassDeclaration(node)) {
|
|
1101
|
+
const members = classMemberShapes(node);
|
|
1102
|
+
const constructor = node.members.find(ts.isConstructorDeclaration);
|
|
1103
|
+
const paramCount = constructor ? constructor.parameters.length : 0;
|
|
1104
|
+
return {
|
|
1105
|
+
shape: `class|ctor=${paramCount}|members=${members.join(',')}`,
|
|
1106
|
+
paramCount,
|
|
1107
|
+
memberCount: members.length,
|
|
1108
|
+
returnShape: null,
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1111
|
+
const params = callableParameters(node);
|
|
1112
|
+
const returnShape = typeShape(callableReturnTypeNode(node) ?? undefined);
|
|
1113
|
+
return {
|
|
1114
|
+
shape: `${kind}|params=${params.map(parameterShape).join(',')}|return=${returnShape}`,
|
|
1115
|
+
paramCount: params.length,
|
|
1116
|
+
memberCount: 0,
|
|
1117
|
+
returnShape,
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
function normalizedTokensForNode(node) {
|
|
1121
|
+
const sourceFile = node.getSourceFile();
|
|
1122
|
+
const scanner = ts.createScanner(ts.ScriptTarget.Latest, true, ts.LanguageVariant.Standard, node.getText(sourceFile));
|
|
1123
|
+
const tokens = [];
|
|
1124
|
+
let token = scanner.scan();
|
|
1125
|
+
while (token !== ts.SyntaxKind.EndOfFileToken) {
|
|
1126
|
+
if (token === ts.SyntaxKind.Identifier || token === ts.SyntaxKind.PrivateIdentifier) {
|
|
1127
|
+
tokens.push('ID');
|
|
1128
|
+
}
|
|
1129
|
+
else if (token === ts.SyntaxKind.StringLiteral ||
|
|
1130
|
+
token === ts.SyntaxKind.NumericLiteral ||
|
|
1131
|
+
token === ts.SyntaxKind.BigIntLiteral ||
|
|
1132
|
+
token === ts.SyntaxKind.RegularExpressionLiteral ||
|
|
1133
|
+
token === ts.SyntaxKind.FirstTemplateToken ||
|
|
1134
|
+
token === ts.SyntaxKind.TemplateHead ||
|
|
1135
|
+
token === ts.SyntaxKind.TemplateMiddle ||
|
|
1136
|
+
token === ts.SyntaxKind.TemplateTail) {
|
|
1137
|
+
tokens.push('LIT');
|
|
1138
|
+
}
|
|
1139
|
+
else {
|
|
1140
|
+
tokens.push(ts.tokenToString(token) ?? ts.SyntaxKind[token] ?? String(token));
|
|
1141
|
+
}
|
|
1142
|
+
token = scanner.scan();
|
|
1143
|
+
}
|
|
1144
|
+
return tokens;
|
|
1145
|
+
}
|
|
1146
|
+
function tokenShingles(tokens) {
|
|
1147
|
+
const out = new Set();
|
|
1148
|
+
if (tokens.length < REUSE_TOKEN_SHINGLE_SIZE)
|
|
1149
|
+
return out;
|
|
1150
|
+
for (let index = 0; index <= tokens.length - REUSE_TOKEN_SHINGLE_SIZE; index += 1) {
|
|
1151
|
+
out.add(hash(tokens.slice(index, index + REUSE_TOKEN_SHINGLE_SIZE).join(' '), 16));
|
|
1152
|
+
}
|
|
1153
|
+
return out;
|
|
1154
|
+
}
|
|
1155
|
+
function shingleOverlap(left, right) {
|
|
1156
|
+
if (left.size === 0 || right.size === 0)
|
|
1157
|
+
return 0;
|
|
1158
|
+
let intersection = 0;
|
|
1159
|
+
for (const item of left) {
|
|
1160
|
+
if (right.has(item))
|
|
1161
|
+
intersection += 1;
|
|
1162
|
+
}
|
|
1163
|
+
const union = left.size + right.size - intersection;
|
|
1164
|
+
return union > 0 ? intersection / union : 0;
|
|
1165
|
+
}
|
|
1166
|
+
function symbolRef(entry) {
|
|
1167
|
+
return {
|
|
1168
|
+
file: entry.file,
|
|
1169
|
+
name: entry.name,
|
|
1170
|
+
kind: entry.kind,
|
|
1171
|
+
exported: entry.exported,
|
|
1172
|
+
local: entry.local,
|
|
1173
|
+
signatureHash: entry.signatureHash,
|
|
1174
|
+
tokenFingerprintHash: entry.tokenFingerprintHash,
|
|
1175
|
+
tokenShingleSetHash: entry.tokenShingleSetHash,
|
|
1176
|
+
paramCount: entry.paramCount,
|
|
1177
|
+
memberCount: entry.memberCount,
|
|
1178
|
+
returnShape: entry.returnShape,
|
|
1179
|
+
provenance: 'repo-symbol-index',
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
function reuseEntryFromDeclaration(decl, action) {
|
|
1183
|
+
const kind = reusableKind(decl.node);
|
|
1184
|
+
if (!kind)
|
|
1185
|
+
return null;
|
|
1186
|
+
const topLevel = isTopLevelReusableDeclaration(decl.node);
|
|
1187
|
+
const exported = isExportedReusableDeclaration(decl.node);
|
|
1188
|
+
const local = !exported;
|
|
1189
|
+
if (!exported && kind === 'method')
|
|
1190
|
+
return null;
|
|
1191
|
+
if (!exported && !topLevel && kind !== 'function')
|
|
1192
|
+
return null;
|
|
1193
|
+
const signature = signatureShapeForDeclaration(decl.node, kind);
|
|
1194
|
+
const normalizedTokens = normalizedTokensForNode(decl.node);
|
|
1195
|
+
const shingles = tokenShingles(normalizedTokens);
|
|
1196
|
+
const tokenFingerprintHash = normalizedTokens.length >= REUSE_MIN_TOKEN_COUNT
|
|
1197
|
+
? hash(normalizedTokens.join(' '), 24)
|
|
1198
|
+
: null;
|
|
1199
|
+
const tokenShingleSetHash = shingles.size > 0 ? hash([...shingles].sort(), 24) : null;
|
|
1200
|
+
return {
|
|
1201
|
+
file: decl.file,
|
|
1202
|
+
name: decl.name,
|
|
1203
|
+
kind,
|
|
1204
|
+
exported,
|
|
1205
|
+
local,
|
|
1206
|
+
signatureHash: hash(signature.shape, 24),
|
|
1207
|
+
tokenFingerprintHash,
|
|
1208
|
+
tokenShingleSetHash,
|
|
1209
|
+
paramCount: signature.paramCount,
|
|
1210
|
+
memberCount: signature.memberCount,
|
|
1211
|
+
returnShape: signature.returnShape,
|
|
1212
|
+
provenance: 'repo-symbol-index',
|
|
1213
|
+
action,
|
|
1214
|
+
topLevel,
|
|
1215
|
+
signatureShape: signature.shape,
|
|
1216
|
+
normalizedTokens,
|
|
1217
|
+
tokenShingles: shingles,
|
|
1218
|
+
declaration: decl.node,
|
|
1219
|
+
};
|
|
1220
|
+
}
|
|
1221
|
+
function highInformationSignature(entry) {
|
|
1222
|
+
if (entry.kind === 'class')
|
|
1223
|
+
return entry.memberCount >= 2 || entry.paramCount >= 2;
|
|
1224
|
+
if (entry.paramCount >= 2)
|
|
1225
|
+
return true;
|
|
1226
|
+
if (entry.returnShape && !['implicit', 'void', 'any', 'unknown'].includes(entry.returnShape)) {
|
|
1227
|
+
return entry.paramCount >= 1;
|
|
1228
|
+
}
|
|
1229
|
+
return false;
|
|
1230
|
+
}
|
|
1231
|
+
function confidenceRank(confidence) {
|
|
1232
|
+
return confidence === 'high' ? 0 : 1;
|
|
1233
|
+
}
|
|
1234
|
+
function matchRank(matchType) {
|
|
1235
|
+
switch (matchType) {
|
|
1236
|
+
case 'signature_token_fingerprint_match': return 0;
|
|
1237
|
+
case 'token_fingerprint_match': return 1;
|
|
1238
|
+
case 'exported_name_collision': return 2;
|
|
1239
|
+
case 'normalized_signature_match': return 3;
|
|
1240
|
+
default: return 9;
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
function mergeReuseFinding(current, next) {
|
|
1244
|
+
if (!current)
|
|
1245
|
+
return next;
|
|
1246
|
+
const reasonCodes = uniqueSorted([...current.reasonCodes, ...next.reasonCodes]);
|
|
1247
|
+
const tokenOverlap = Math.max(current.evidence.tokenOverlap ?? 0, next.evidence.tokenOverlap ?? 0);
|
|
1248
|
+
const signature = current.evidence.signatureHash ?? next.evidence.signatureHash;
|
|
1249
|
+
const tokenFingerprint = current.evidence.tokenFingerprintHash ?? next.evidence.tokenFingerprintHash;
|
|
1250
|
+
const tokenShingle = current.evidence.tokenShingleSetHash ?? next.evidence.tokenShingleSetHash;
|
|
1251
|
+
const matchType = reasonCodes.includes('same_normalized_signature') &&
|
|
1252
|
+
(reasonCodes.includes('same_normalized_token_fingerprint') || reasonCodes.includes('high_token_shingle_overlap'))
|
|
1253
|
+
? 'signature_token_fingerprint_match'
|
|
1254
|
+
: reasonCodes.includes('same_normalized_token_fingerprint') || reasonCodes.includes('high_token_shingle_overlap')
|
|
1255
|
+
? 'token_fingerprint_match'
|
|
1256
|
+
: reasonCodes.includes('exported_symbol_name_collision')
|
|
1257
|
+
? 'exported_name_collision'
|
|
1258
|
+
: 'normalized_signature_match';
|
|
1259
|
+
const confidence = matchType === 'signature_token_fingerprint_match' ||
|
|
1260
|
+
reasonCodes.includes('same_normalized_token_fingerprint') ||
|
|
1261
|
+
reasonCodes.includes('exported_symbol_name_collision')
|
|
1262
|
+
? 'high'
|
|
1263
|
+
: 'medium';
|
|
1264
|
+
return {
|
|
1265
|
+
...current,
|
|
1266
|
+
matchType,
|
|
1267
|
+
confidence,
|
|
1268
|
+
reasonCodes,
|
|
1269
|
+
evidence: {
|
|
1270
|
+
...current.evidence,
|
|
1271
|
+
signatureHash: signature,
|
|
1272
|
+
tokenFingerprintHash: tokenFingerprint,
|
|
1273
|
+
tokenShingleSetHash: tokenShingle,
|
|
1274
|
+
tokenOverlap: tokenOverlap > 0 ? Number(tokenOverlap.toFixed(3)) : null,
|
|
1275
|
+
},
|
|
1276
|
+
message: reuseFindingMessage(current.changed, current.existing, matchType),
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
function reuseFindingMessage(changed, existing, matchType) {
|
|
1280
|
+
const subject = changed.kind === 'class' ? 'class' : changed.kind === 'method' ? 'method' : 'function';
|
|
1281
|
+
const match = matchType === 'signature_token_fingerprint_match'
|
|
1282
|
+
? 'signature+token fingerprint'
|
|
1283
|
+
: matchType.replace(/_/g, ' ');
|
|
1284
|
+
return `Potential reuse issue: changed ${subject} ${changed.name} resembles existing ${existing.file}#${existing.name}; match type: ${match}; action: review existing helper before merging.`;
|
|
1285
|
+
}
|
|
1286
|
+
function reuseFindingForPair(changed, existing) {
|
|
1287
|
+
if (changed.file === existing.file)
|
|
1288
|
+
return null;
|
|
1289
|
+
const reasonCodes = [
|
|
1290
|
+
changed.action === 'add' ? 'changed_symbol_added' : 'changed_symbol_modified',
|
|
1291
|
+
'existing_symbol_elsewhere',
|
|
1292
|
+
'typescript_javascript_only',
|
|
1293
|
+
];
|
|
1294
|
+
let signatureHash = null;
|
|
1295
|
+
let tokenFingerprintHash = null;
|
|
1296
|
+
let tokenShingleSetHash = null;
|
|
1297
|
+
let tokenOverlap = null;
|
|
1298
|
+
if (changed.exported &&
|
|
1299
|
+
existing.exported &&
|
|
1300
|
+
changed.topLevel &&
|
|
1301
|
+
existing.topLevel &&
|
|
1302
|
+
changed.kind === existing.kind &&
|
|
1303
|
+
changed.name === existing.name) {
|
|
1304
|
+
reasonCodes.push('exported_symbol_name_collision');
|
|
1305
|
+
}
|
|
1306
|
+
if (changed.kind === existing.kind &&
|
|
1307
|
+
changed.signatureHash === existing.signatureHash &&
|
|
1308
|
+
highInformationSignature(changed)) {
|
|
1309
|
+
reasonCodes.push('same_normalized_signature');
|
|
1310
|
+
signatureHash = changed.signatureHash;
|
|
1311
|
+
}
|
|
1312
|
+
if (changed.tokenFingerprintHash &&
|
|
1313
|
+
existing.tokenFingerprintHash &&
|
|
1314
|
+
changed.tokenFingerprintHash === existing.tokenFingerprintHash) {
|
|
1315
|
+
reasonCodes.push('same_normalized_token_fingerprint');
|
|
1316
|
+
tokenFingerprintHash = changed.tokenFingerprintHash;
|
|
1317
|
+
tokenShingleSetHash = changed.tokenShingleSetHash;
|
|
1318
|
+
tokenOverlap = 1;
|
|
1319
|
+
}
|
|
1320
|
+
else if (changed.normalizedTokens.length >= REUSE_MIN_TOKEN_COUNT &&
|
|
1321
|
+
existing.normalizedTokens.length >= REUSE_MIN_TOKEN_COUNT) {
|
|
1322
|
+
const overlap = shingleOverlap(changed.tokenShingles, existing.tokenShingles);
|
|
1323
|
+
if (overlap >= REUSE_TOKEN_OVERLAP_THRESHOLD) {
|
|
1324
|
+
reasonCodes.push('high_token_shingle_overlap');
|
|
1325
|
+
tokenShingleSetHash = changed.tokenShingleSetHash;
|
|
1326
|
+
tokenOverlap = Number(overlap.toFixed(3));
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
if (!reasonCodes.includes('exported_symbol_name_collision') &&
|
|
1330
|
+
!reasonCodes.includes('same_normalized_signature') &&
|
|
1331
|
+
!reasonCodes.includes('same_normalized_token_fingerprint') &&
|
|
1332
|
+
!reasonCodes.includes('high_token_shingle_overlap')) {
|
|
1333
|
+
return null;
|
|
1334
|
+
}
|
|
1335
|
+
const matchType = reasonCodes.includes('same_normalized_signature') &&
|
|
1336
|
+
(reasonCodes.includes('same_normalized_token_fingerprint') || reasonCodes.includes('high_token_shingle_overlap'))
|
|
1337
|
+
? 'signature_token_fingerprint_match'
|
|
1338
|
+
: reasonCodes.includes('same_normalized_token_fingerprint') || reasonCodes.includes('high_token_shingle_overlap')
|
|
1339
|
+
? 'token_fingerprint_match'
|
|
1340
|
+
: reasonCodes.includes('exported_symbol_name_collision')
|
|
1341
|
+
? 'exported_name_collision'
|
|
1342
|
+
: 'normalized_signature_match';
|
|
1343
|
+
const confidence = matchType === 'signature_token_fingerprint_match' ||
|
|
1344
|
+
reasonCodes.includes('same_normalized_token_fingerprint') ||
|
|
1345
|
+
reasonCodes.includes('exported_symbol_name_collision')
|
|
1346
|
+
? 'high'
|
|
1347
|
+
: 'medium';
|
|
1348
|
+
const changedRef = symbolRef(changed);
|
|
1349
|
+
const existingRef = symbolRef(existing);
|
|
1350
|
+
return {
|
|
1351
|
+
schemaVersion: 'neurcode.reuse-finding.v1',
|
|
1352
|
+
severity: 'warn',
|
|
1353
|
+
advisory: true,
|
|
1354
|
+
hardBlock: false,
|
|
1355
|
+
changed: changedRef,
|
|
1356
|
+
existing: existingRef,
|
|
1357
|
+
matchType,
|
|
1358
|
+
confidence,
|
|
1359
|
+
reasonCodes: uniqueSorted(reasonCodes),
|
|
1360
|
+
evidence: {
|
|
1361
|
+
signatureHash,
|
|
1362
|
+
tokenFingerprintHash,
|
|
1363
|
+
tokenShingleSetHash,
|
|
1364
|
+
tokenOverlap,
|
|
1365
|
+
changedNormalizedTokenCount: changed.normalizedTokens.length,
|
|
1366
|
+
existingNormalizedTokenCount: existing.normalizedTokens.length,
|
|
1367
|
+
},
|
|
1368
|
+
action: 'review_existing_helper_before_merging',
|
|
1369
|
+
message: reuseFindingMessage(changedRef, existingRef, matchType),
|
|
1370
|
+
provenance: 'repo-symbol-index',
|
|
1371
|
+
};
|
|
1372
|
+
}
|
|
1373
|
+
function buildReuseDetection(input) {
|
|
1374
|
+
const entries = [];
|
|
1375
|
+
const changedActionByKey = new Map(input.targets.map((target) => [
|
|
1376
|
+
symbolKey(target.file, target.kind, target.name),
|
|
1377
|
+
target.action,
|
|
1378
|
+
]));
|
|
1379
|
+
const changedCandidates = [];
|
|
1380
|
+
for (const declarations of input.declarationsByFile.values()) {
|
|
1381
|
+
for (const decl of declarations) {
|
|
1382
|
+
const key = symbolKey(decl.file, decl.kind, decl.name);
|
|
1383
|
+
const changedAction = changedActionByKey.get(key);
|
|
1384
|
+
if (changedAction === 'delete')
|
|
1385
|
+
continue;
|
|
1386
|
+
const entry = reuseEntryFromDeclaration(decl, changedAction ?? 'existing');
|
|
1387
|
+
if (!entry)
|
|
1388
|
+
continue;
|
|
1389
|
+
entries.push(entry);
|
|
1390
|
+
if (changedActionByKey.has(key))
|
|
1391
|
+
changedCandidates.push(entry);
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
const byPair = new Map();
|
|
1395
|
+
for (const changed of changedCandidates) {
|
|
1396
|
+
for (const existing of entries) {
|
|
1397
|
+
if (existing === changed)
|
|
1398
|
+
continue;
|
|
1399
|
+
const finding = reuseFindingForPair(changed, existing);
|
|
1400
|
+
if (!finding)
|
|
1401
|
+
continue;
|
|
1402
|
+
const key = `${finding.changed.file}\0${finding.changed.name}\0${finding.existing.file}\0${finding.existing.name}`;
|
|
1403
|
+
byPair.set(key, mergeReuseFinding(byPair.get(key), finding));
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
const sourceFileCount = input.program.getSourceFiles()
|
|
1407
|
+
.filter((file) => !file.isDeclarationFile && !file.fileName.includes('node_modules'))
|
|
1408
|
+
.length;
|
|
1409
|
+
const indexHash = hash(entries
|
|
1410
|
+
.map((entry) => ({
|
|
1411
|
+
file: entry.file,
|
|
1412
|
+
name: entry.name,
|
|
1413
|
+
kind: entry.kind,
|
|
1414
|
+
exported: entry.exported,
|
|
1415
|
+
signatureHash: entry.signatureHash,
|
|
1416
|
+
tokenFingerprintHash: entry.tokenFingerprintHash,
|
|
1417
|
+
tokenShingleSetHash: entry.tokenShingleSetHash,
|
|
1418
|
+
}))
|
|
1419
|
+
.sort((a, b) => a.file.localeCompare(b.file) ||
|
|
1420
|
+
a.name.localeCompare(b.name) ||
|
|
1421
|
+
a.kind.localeCompare(b.kind)));
|
|
1422
|
+
const reuseFindings = [...byPair.values()]
|
|
1423
|
+
.sort((a, b) => confidenceRank(a.confidence) - confidenceRank(b.confidence) ||
|
|
1424
|
+
matchRank(a.matchType) - matchRank(b.matchType) ||
|
|
1425
|
+
a.changed.file.localeCompare(b.changed.file) ||
|
|
1426
|
+
a.changed.name.localeCompare(b.changed.name) ||
|
|
1427
|
+
a.existing.file.localeCompare(b.existing.file) ||
|
|
1428
|
+
a.existing.name.localeCompare(b.existing.name))
|
|
1429
|
+
.slice(0, REUSE_MAX_FINDINGS);
|
|
1430
|
+
return {
|
|
1431
|
+
repoSymbolIndex: {
|
|
1432
|
+
schemaVersion: 'neurcode.repo-symbol-index.v1',
|
|
1433
|
+
language: 'typescript/javascript',
|
|
1434
|
+
modelUsed: false,
|
|
1435
|
+
sourceUploaded: false,
|
|
1436
|
+
sourceStored: false,
|
|
1437
|
+
indexedFileCount: sourceFileCount,
|
|
1438
|
+
indexedSymbolCount: entries.length,
|
|
1439
|
+
exportedSymbolCount: entries.filter((entry) => entry.exported).length,
|
|
1440
|
+
localFunctionCount: entries.filter((entry) => entry.local && entry.kind === 'function').length,
|
|
1441
|
+
methodCount: entries.filter((entry) => entry.kind === 'method').length,
|
|
1442
|
+
classCount: entries.filter((entry) => entry.kind === 'class').length,
|
|
1443
|
+
changedCandidateCount: changedCandidates.length,
|
|
1444
|
+
unsupportedLanguages: ['python'],
|
|
1445
|
+
indexHash,
|
|
1446
|
+
fingerprintAlgorithm: 'typescript-scanner-normalized-token-shingles-v1',
|
|
1447
|
+
signatureAlgorithm: 'name-insensitive-kind-arity-param-return-shape-v1',
|
|
1448
|
+
provenance: 'repo-symbol-index',
|
|
1449
|
+
},
|
|
1450
|
+
reuseFindings,
|
|
1451
|
+
};
|
|
1452
|
+
}
|
|
929
1453
|
function smallestContaining(declarations, line) {
|
|
930
1454
|
return declarations
|
|
931
1455
|
.filter((decl) => line >= decl.startLine && line <= decl.endLine)
|
|
@@ -2243,6 +2767,11 @@ function buildStructuralUnderstanding(projectRoot, diffFiles, options = {}) {
|
|
|
2243
2767
|
suppressedArtifacts,
|
|
2244
2768
|
profile,
|
|
2245
2769
|
});
|
|
2770
|
+
const reuseDetection = buildReuseDetection({
|
|
2771
|
+
program,
|
|
2772
|
+
declarationsByFile,
|
|
2773
|
+
targets: [...targets.values()],
|
|
2774
|
+
});
|
|
2246
2775
|
const core = {
|
|
2247
2776
|
schemaVersion: exports.STRUCTURAL_UNDERSTANDING_SCHEMA_VERSION,
|
|
2248
2777
|
generatedAt,
|
|
@@ -2273,10 +2802,14 @@ function buildStructuralUnderstanding(projectRoot, diffFiles, options = {}) {
|
|
|
2273
2802
|
testReferences,
|
|
2274
2803
|
digest,
|
|
2275
2804
|
consequenceUnderstanding,
|
|
2805
|
+
repoSymbolIndex: reuseDetection.repoSymbolIndex,
|
|
2806
|
+
reuseFindings: reuseDetection.reuseFindings,
|
|
2276
2807
|
planAlignment,
|
|
2277
2808
|
boundaryImpact,
|
|
2278
2809
|
limitations: [
|
|
2279
2810
|
...commonLimitations(),
|
|
2811
|
+
'Reuse governance is deterministic and TypeScript/JavaScript-first; Python and other languages are not enforced in V1.',
|
|
2812
|
+
'Reuse findings are advisory WARN signals, not hard blocks; token fingerprints and normalized signatures can miss semantic duplicates or produce review noise.',
|
|
2280
2813
|
...(changedPackageNames.length
|
|
2281
2814
|
? [
|
|
2282
2815
|
`Cross-package references resolved for direct importers of [${changedPackageNames.join(', ')}] across ${consumerPackages.length} consumer package(s). Transitive re-export chains and consumers that do not import the changed package directly may be under-counted.`,
|