@orion-agents/orion-code 0.3.22 → 0.3.25
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 +81 -3
- package/README.md +9 -5
- package/README.zh-CN.md +7 -4
- package/dist/harness/failure-attribution.d.ts +23 -0
- package/dist/harness/failure-attribution.js +103 -0
- package/dist/harness/verified-repair.d.ts +71 -0
- package/dist/harness/verified-repair.js +212 -0
- package/dist/product/paths.d.ts +20 -1
- package/dist/product/paths.js +293 -10
- package/dist/runtime/model-coordinator.d.ts +1 -0
- package/dist/runtime/model-coordinator.js +9 -2
- package/dist/runtime/release-receipts.d.ts +3 -3
- package/dist/runtime/release-receipts.js +29 -0
- package/dist/runtime/session-composer-control.js +5 -1
- package/dist/services/config-dir.d.ts +3 -2
- package/dist/services/config-dir.js +6 -29
- package/dist/services/repo-intelligence/index.d.ts +11 -0
- package/dist/services/repo-intelligence/index.js +20 -0
- package/dist/services/repo-intelligence/recall.d.ts +44 -0
- package/dist/services/repo-intelligence/recall.js +115 -0
- package/dist/services/repo-intelligence/symbol-index.d.ts +52 -0
- package/dist/services/repo-intelligence/symbol-index.js +258 -0
- package/dist/services/session-storage.d.ts +18 -2
- package/dist/services/session-storage.js +196 -60
- package/dist/services/workspace-registry.d.ts +9 -0
- package/dist/services/workspace-registry.js +13 -2
- package/dist/web/git-history-service.js +3 -0
- package/dist/web/git-read-model-service.d.ts +9 -0
- package/dist/web/git-read-model-service.js +35 -0
- package/dist/web/git-version-service.d.ts +8 -0
- package/dist/web/git-version-service.js +50 -26
- package/dist/web/protocol.d.ts +13 -0
- package/dist/web/workbench-controller.js +47 -2
- package/dist/web-client/assets/{FilesPanel-CKeXPofZ.js → FilesPanel-CdoCa8u0.js} +1 -1
- package/dist/web-client/assets/{GitPanel-DollPxv0.js → GitPanel-CRr_iIDz.js} +1 -1
- package/dist/web-client/assets/{ReviewPanel-CNADacIn.js → ReviewPanel-C1Cu2IRV.js} +1 -1
- package/dist/web-client/assets/{TerminalPanel-Cyns4uJt.js → TerminalPanel-BniCjatZ.js} +1 -1
- package/dist/web-client/assets/index-kQqA3li6.js +20 -0
- package/dist/web-client/assets/{useAutoNotice-BLADeFFj.js → useAutoNotice-DPXFWIwK.js} +1 -1
- package/dist/web-client/index.html +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/dist/services/auto-fix/autoFixConfig.d.ts +0 -30
- package/dist/services/auto-fix/autoFixConfig.js +0 -78
- package/dist/services/auto-fix/autoFixHook.d.ts +0 -27
- package/dist/services/auto-fix/autoFixHook.js +0 -99
- package/dist/services/auto-fix/autoFixRunner.d.ts +0 -60
- package/dist/services/auto-fix/autoFixRunner.js +0 -207
- package/dist/services/auto-fix/index.d.ts +0 -7
- package/dist/services/auto-fix/index.js +0 -17
- package/dist/web-client/assets/index-BhGhORAX.js +0 -20
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.recallFiles = recallFiles;
|
|
4
|
+
exports.selectWithinBudget = selectWithinBudget;
|
|
5
|
+
const basenameOf = (path) => path.split('/').pop() ?? path;
|
|
6
|
+
const testPairOf = (path) => {
|
|
7
|
+
const normalized = path.split('\\').join('/');
|
|
8
|
+
const testMatch = /^(?:tests?\/)(.+)\.(?:test|spec)\.[cm]?[jt]sx?$/.exec(normalized);
|
|
9
|
+
if (testMatch) {
|
|
10
|
+
const base = testMatch[1];
|
|
11
|
+
for (const candidate of [`src/${base}.ts`, `src/${base}.tsx`, `src/${base}.js`]) {
|
|
12
|
+
return candidate;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
const srcMatch = /^src\/(.+)\.[cm]?[jt]sx?$/.exec(normalized);
|
|
16
|
+
if (srcMatch) {
|
|
17
|
+
const base = srcMatch[1];
|
|
18
|
+
return `tests/${base}.test.ts`;
|
|
19
|
+
}
|
|
20
|
+
return undefined;
|
|
21
|
+
};
|
|
22
|
+
function recallFiles(index, options) {
|
|
23
|
+
const limit = options.limit ?? 20;
|
|
24
|
+
const snapshot = index.getSnapshot();
|
|
25
|
+
const intentLower = options.intent.toLowerCase();
|
|
26
|
+
const activeSet = new Set(options.activeFiles.map(path => path.split('\\').join('/')));
|
|
27
|
+
const changedSet = new Set((options.changedFiles ?? []).map(path => path.split('\\').join('/')));
|
|
28
|
+
// Symbols explicitly named in the intent (quoted or camel-cased tokens).
|
|
29
|
+
const intentSymbols = new Set();
|
|
30
|
+
for (const match of options.intent.matchAll(/[A-Za-z_$][\w$]{2,}/g)) {
|
|
31
|
+
intentSymbols.add(match[0]);
|
|
32
|
+
}
|
|
33
|
+
const scores = new Map();
|
|
34
|
+
const bump = (path, points, reason) => {
|
|
35
|
+
const normalized = path.split('\\').join('/');
|
|
36
|
+
const entry = scores.get(normalized) ?? { score: 0, reasons: new Set() };
|
|
37
|
+
entry.score += points;
|
|
38
|
+
entry.reasons.add(reason);
|
|
39
|
+
scores.set(normalized, entry);
|
|
40
|
+
};
|
|
41
|
+
for (const [path, record] of snapshot) {
|
|
42
|
+
const base = basenameOf(path);
|
|
43
|
+
// Direct mention: the intent names the file path or its basename.
|
|
44
|
+
if (intentLower.includes(path.toLowerCase()) || intentLower.includes(base.toLowerCase())) {
|
|
45
|
+
bump(path, 50, 'mentioned in the task intent');
|
|
46
|
+
}
|
|
47
|
+
// Symbol match: an intent token matches a symbol defined in this file.
|
|
48
|
+
for (const symbol of record.symbols) {
|
|
49
|
+
if (symbol.name.length >= 4 && intentSymbols.has(symbol.name)) {
|
|
50
|
+
bump(path, 30, `defines symbol ${symbol.name}`);
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Import adjacency to active files.
|
|
55
|
+
for (const active of activeSet) {
|
|
56
|
+
if (record.imports.includes(active)) {
|
|
57
|
+
bump(path, 20, `imports the active file ${active}`);
|
|
58
|
+
}
|
|
59
|
+
if (snapshot.get(active)?.imports.includes(path)) {
|
|
60
|
+
bump(path, 20, `imported by the active file ${active}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Test pairing.
|
|
64
|
+
const pair = testPairOf(path);
|
|
65
|
+
if (pair && (activeSet.has(pair) || changedSet.has(pair))) {
|
|
66
|
+
bump(path, 15, `paired with ${pair}`);
|
|
67
|
+
}
|
|
68
|
+
// Recent change.
|
|
69
|
+
if (changedSet.has(path)) {
|
|
70
|
+
bump(path, 10, 'in the current change-set');
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return [...scores.entries()]
|
|
74
|
+
.map(([path, entry]) => ({
|
|
75
|
+
path,
|
|
76
|
+
score: entry.score,
|
|
77
|
+
reasons: [...entry.reasons].sort(),
|
|
78
|
+
}))
|
|
79
|
+
.sort((left, right) => right.score - left.score || left.path.localeCompare(right.path))
|
|
80
|
+
.slice(0, limit);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Budget-aware selection: rank candidates, deduplicate by directory concentration
|
|
84
|
+
* (at most `maxPerDirectory` per immediate directory), and always keep explicitly
|
|
85
|
+
* active files (the user touched them — they are never dropped for budget).
|
|
86
|
+
*/
|
|
87
|
+
function selectWithinBudget(candidates, options) {
|
|
88
|
+
const maxFiles = options.maxFiles ?? 12;
|
|
89
|
+
const maxPerDirectory = options.maxPerDirectory ?? 4;
|
|
90
|
+
const activeSet = new Set(options.activeFiles.map(path => path.split('\\').join('/')));
|
|
91
|
+
const perDirectory = new Map();
|
|
92
|
+
const selected = [];
|
|
93
|
+
for (const candidate of candidates) {
|
|
94
|
+
const normalized = candidate.path.split('\\').join('/');
|
|
95
|
+
if (selected.length >= maxFiles)
|
|
96
|
+
break;
|
|
97
|
+
const directory = normalized.split('/').slice(0, -1).join('/');
|
|
98
|
+
const isProtected = activeSet.has(normalized);
|
|
99
|
+
const count = perDirectory.get(directory) ?? 0;
|
|
100
|
+
if (!isProtected && count >= maxPerDirectory)
|
|
101
|
+
continue;
|
|
102
|
+
selected.push(candidate);
|
|
103
|
+
perDirectory.set(directory, count + 1);
|
|
104
|
+
}
|
|
105
|
+
// Active files that scoring missed are still kept (user-touched is sacred).
|
|
106
|
+
for (const active of activeSet) {
|
|
107
|
+
if (selected.length >= maxFiles)
|
|
108
|
+
break;
|
|
109
|
+
if (!selected.some(candidate => candidate.path === active)) {
|
|
110
|
+
selected.push({ path: active, score: 0, reasons: ['active file (user-pinned)'] });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return selected;
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=recall.js.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export interface RepoSymbolEntryV1 {
|
|
2
|
+
readonly name: string;
|
|
3
|
+
readonly kind: 'function' | 'class' | 'interface' | 'type' | 'enum' | 'const' | 'let' | 'var' | 'method';
|
|
4
|
+
readonly file: string;
|
|
5
|
+
readonly line: number;
|
|
6
|
+
readonly exported: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface RepoFileRecordV1 {
|
|
9
|
+
/** Workspace-relative POSIX-style path. */
|
|
10
|
+
readonly path: string;
|
|
11
|
+
readonly mtimeMs: number;
|
|
12
|
+
readonly size: number;
|
|
13
|
+
readonly symbols: readonly RepoSymbolEntryV1[];
|
|
14
|
+
/** Workspace-relative paths this file imports (relative specifiers only). */
|
|
15
|
+
readonly imports: readonly string[];
|
|
16
|
+
readonly exportedNames: readonly string[];
|
|
17
|
+
}
|
|
18
|
+
export interface RepoIndexStatsV1 {
|
|
19
|
+
readonly files: number;
|
|
20
|
+
readonly parsed: number;
|
|
21
|
+
readonly skipped: number;
|
|
22
|
+
readonly symbols: number;
|
|
23
|
+
readonly durationMs: number;
|
|
24
|
+
readonly incremental: boolean;
|
|
25
|
+
}
|
|
26
|
+
export interface RepoSymbolIndexOptionsV1 {
|
|
27
|
+
readonly include?: readonly string[];
|
|
28
|
+
readonly ignore?: readonly string[];
|
|
29
|
+
readonly maxFiles?: number;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The workspace symbol index. Construction does not parse anything; call
|
|
33
|
+
* `build()` (full) or `update()` (incremental).
|
|
34
|
+
*/
|
|
35
|
+
export declare class RepoSymbolIndexV1 {
|
|
36
|
+
private readonly root;
|
|
37
|
+
private readonly ignore;
|
|
38
|
+
private readonly maxFiles;
|
|
39
|
+
private readonly files;
|
|
40
|
+
private readonly digests;
|
|
41
|
+
constructor(root: string, options?: RepoSymbolIndexOptionsV1);
|
|
42
|
+
get workspaceRoot(): string;
|
|
43
|
+
/** Full build: parse every source file under the root. */
|
|
44
|
+
build(): RepoIndexStatsV1;
|
|
45
|
+
/** Incremental update: re-parse only files whose digest changed or that are new. */
|
|
46
|
+
update(): RepoIndexStatsV1;
|
|
47
|
+
getSnapshot(): ReadonlyMap<string, RepoFileRecordV1>;
|
|
48
|
+
findSymbol(name: string): readonly RepoSymbolEntryV1[];
|
|
49
|
+
importersOf(relativePath: string): readonly string[];
|
|
50
|
+
invalidate(relativePath: string): void;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=symbol-index.d.ts.map
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.RepoSymbolIndexV1 = void 0;
|
|
7
|
+
/**
|
|
8
|
+
* v0.3.23 T23-B1/B2 — lightweight repository symbol index.
|
|
9
|
+
*
|
|
10
|
+
* Parses TypeScript/JavaScript sources with the compiler API (already a
|
|
11
|
+
* dependency) into a per-file record: symbols (functions/classes/interfaces/
|
|
12
|
+
* types/enums), their exportedness, and resolved relative import edges.
|
|
13
|
+
*
|
|
14
|
+
* Maintenance is incremental: each file keeps a mtime+size digest, and
|
|
15
|
+
* `update()` re-parses only the stale subset. The index never reads file
|
|
16
|
+
* CONTENT beyond parsing — it follows the workspace root and skips the usual
|
|
17
|
+
* high-noise directories (node_modules, dist, .git, hidden, build outputs).
|
|
18
|
+
*/
|
|
19
|
+
const fs_1 = require("fs");
|
|
20
|
+
const path_1 = require("path");
|
|
21
|
+
const typescript_1 = __importDefault(require("typescript"));
|
|
22
|
+
const DEFAULT_IGNORE = [
|
|
23
|
+
'node_modules',
|
|
24
|
+
'dist',
|
|
25
|
+
'build',
|
|
26
|
+
'out',
|
|
27
|
+
'.git',
|
|
28
|
+
'coverage',
|
|
29
|
+
'.cache',
|
|
30
|
+
'target',
|
|
31
|
+
'vendor',
|
|
32
|
+
'__pycache__',
|
|
33
|
+
];
|
|
34
|
+
const SOURCE_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs']);
|
|
35
|
+
function isSourceFile(path) {
|
|
36
|
+
return SOURCE_EXTENSIONS.has(path.slice(path.lastIndexOf('.')));
|
|
37
|
+
}
|
|
38
|
+
/** Walk the workspace root collecting source files (bounded). */
|
|
39
|
+
function walkSources(root, ignore, maxFiles) {
|
|
40
|
+
const paths = [];
|
|
41
|
+
let skipped = 0;
|
|
42
|
+
const visit = (directory) => {
|
|
43
|
+
if (paths.length >= maxFiles)
|
|
44
|
+
return;
|
|
45
|
+
let entries;
|
|
46
|
+
try {
|
|
47
|
+
entries = (0, fs_1.readdirSync)(directory);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
for (const entry of entries) {
|
|
53
|
+
if (paths.length >= maxFiles)
|
|
54
|
+
return;
|
|
55
|
+
if (entry.startsWith('.') || ignore.has(entry))
|
|
56
|
+
continue;
|
|
57
|
+
const absolute = (0, path_1.join)(directory, entry);
|
|
58
|
+
let stats;
|
|
59
|
+
try {
|
|
60
|
+
stats = (0, fs_1.statSync)(absolute);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (stats.isDirectory())
|
|
66
|
+
visit(absolute);
|
|
67
|
+
else if (stats.isFile() && isSourceFile(entry)) {
|
|
68
|
+
if (paths.length < maxFiles)
|
|
69
|
+
paths.push(absolute);
|
|
70
|
+
else
|
|
71
|
+
skipped += 1;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
visit(root);
|
|
76
|
+
return { paths, skipped };
|
|
77
|
+
}
|
|
78
|
+
function fileDigest(stats) {
|
|
79
|
+
return `${Math.round(stats.mtimeMs)}:${stats.size}`;
|
|
80
|
+
}
|
|
81
|
+
/** Extract symbols + import edges from one source file. */
|
|
82
|
+
function parseSource(root, absolutePath) {
|
|
83
|
+
const relativePath = (0, path_1.relative)(root, absolutePath).split('\\').join('/');
|
|
84
|
+
const source = typescript_1.default.createSourceFile(absolutePath, (0, fs_1.readFileSync)(absolutePath, 'utf8'), typescript_1.default.ScriptTarget.Latest, true);
|
|
85
|
+
const symbols = [];
|
|
86
|
+
const imports = [];
|
|
87
|
+
const exportedNames = [];
|
|
88
|
+
const lineOf = (node) => source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1;
|
|
89
|
+
const push = (node, name, kind, exported) => {
|
|
90
|
+
if (!name)
|
|
91
|
+
return;
|
|
92
|
+
symbols.push({ name, kind, file: relativePath, line: lineOf(node), exported });
|
|
93
|
+
if (exported)
|
|
94
|
+
exportedNames.push(name);
|
|
95
|
+
};
|
|
96
|
+
const visit = (node) => {
|
|
97
|
+
if (typescript_1.default.isImportDeclaration(node) && typescript_1.default.isStringLiteral(node.moduleSpecifier)) {
|
|
98
|
+
const specifier = node.moduleSpecifier.text;
|
|
99
|
+
if (specifier.startsWith('.')) {
|
|
100
|
+
const resolvedBase = (0, path_1.resolve)((0, path_1.dirname)(absolutePath), specifier);
|
|
101
|
+
for (const candidate of [
|
|
102
|
+
resolvedBase,
|
|
103
|
+
`${resolvedBase}.ts`,
|
|
104
|
+
`${resolvedBase}.tsx`,
|
|
105
|
+
`${resolvedBase}.js`,
|
|
106
|
+
`${resolvedBase}.jsx`,
|
|
107
|
+
(0, path_1.join)(resolvedBase, 'index.ts'),
|
|
108
|
+
(0, path_1.join)(resolvedBase, 'index.js'),
|
|
109
|
+
]) {
|
|
110
|
+
if ((0, fs_1.existsSync)(candidate) && !(0, fs_1.statSync)(candidate).isDirectory()) {
|
|
111
|
+
imports.push((0, path_1.relative)(root, candidate).split('\\').join('/'));
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (typescript_1.default.isFunctionDeclaration(node) && node.name)
|
|
118
|
+
push(node, node.name.text, 'function', node.modifiers?.some(m => m.kind === typescript_1.default.SyntaxKind.ExportKeyword) === true);
|
|
119
|
+
else if (typescript_1.default.isClassDeclaration(node) && node.name)
|
|
120
|
+
push(node, node.name.text, 'class', node.modifiers?.some(m => m.kind === typescript_1.default.SyntaxKind.ExportKeyword) === true);
|
|
121
|
+
else if (typescript_1.default.isInterfaceDeclaration(node) && node.name)
|
|
122
|
+
push(node, node.name.text, 'interface', node.modifiers?.some(m => m.kind === typescript_1.default.SyntaxKind.ExportKeyword) === true);
|
|
123
|
+
else if (typescript_1.default.isTypeAliasDeclaration(node) && node.name)
|
|
124
|
+
push(node, node.name.text, 'type', node.modifiers?.some(m => m.kind === typescript_1.default.SyntaxKind.ExportKeyword) === true);
|
|
125
|
+
else if (typescript_1.default.isEnumDeclaration(node) && node.name)
|
|
126
|
+
push(node, node.name.text, 'enum', node.modifiers?.some(m => m.kind === typescript_1.default.SyntaxKind.ExportKeyword) === true);
|
|
127
|
+
else if (typescript_1.default.isVariableStatement(node)) {
|
|
128
|
+
const exported = node.modifiers?.some(m => m.kind === typescript_1.default.SyntaxKind.ExportKeyword) === true;
|
|
129
|
+
for (const declaration of node.declarationList.declarations) {
|
|
130
|
+
if (typescript_1.default.isIdentifier(declaration.name)) {
|
|
131
|
+
const kind = declaration.name.getText(source).length > 0 &&
|
|
132
|
+
node.declarationList.flags & typescript_1.default.NodeFlags.Const
|
|
133
|
+
? 'const'
|
|
134
|
+
: node.declarationList.flags & typescript_1.default.NodeFlags.Let
|
|
135
|
+
? 'let'
|
|
136
|
+
: 'var';
|
|
137
|
+
push(node, declaration.name.text, kind, exported);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
else if ((typescript_1.default.isMethodDeclaration(node) || typescript_1.default.isPropertyDeclaration(node)) &&
|
|
142
|
+
typescript_1.default.isIdentifier(node.name)) {
|
|
143
|
+
push(node, node.name.text, 'method', false);
|
|
144
|
+
}
|
|
145
|
+
typescript_1.default.forEachChild(node, visit);
|
|
146
|
+
};
|
|
147
|
+
typescript_1.default.forEachChild(source, visit);
|
|
148
|
+
return { symbols, imports, exportedNames };
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* The workspace symbol index. Construction does not parse anything; call
|
|
152
|
+
* `build()` (full) or `update()` (incremental).
|
|
153
|
+
*/
|
|
154
|
+
class RepoSymbolIndexV1 {
|
|
155
|
+
constructor(root, options = {}) {
|
|
156
|
+
this.files = new Map();
|
|
157
|
+
this.digests = new Map();
|
|
158
|
+
this.root = (0, path_1.resolve)(root);
|
|
159
|
+
this.ignore = new Set([...DEFAULT_IGNORE, ...(options.ignore ?? [])]);
|
|
160
|
+
this.maxFiles = options.maxFiles ?? 5000;
|
|
161
|
+
}
|
|
162
|
+
get workspaceRoot() {
|
|
163
|
+
return this.root;
|
|
164
|
+
}
|
|
165
|
+
/** Full build: parse every source file under the root. */
|
|
166
|
+
build() {
|
|
167
|
+
this.files.clear();
|
|
168
|
+
this.digests.clear();
|
|
169
|
+
return this.update();
|
|
170
|
+
}
|
|
171
|
+
/** Incremental update: re-parse only files whose digest changed or that are new. */
|
|
172
|
+
update() {
|
|
173
|
+
const startedAt = Date.now();
|
|
174
|
+
const { paths, skipped } = walkSources(this.root, this.ignore, this.maxFiles);
|
|
175
|
+
const seen = new Set();
|
|
176
|
+
let parsed = 0;
|
|
177
|
+
let symbolCount = 0;
|
|
178
|
+
for (const absolute of paths) {
|
|
179
|
+
const relativePath = (0, path_1.relative)(this.root, absolute).split('\\').join('/');
|
|
180
|
+
seen.add(relativePath);
|
|
181
|
+
let stats;
|
|
182
|
+
try {
|
|
183
|
+
stats = (0, fs_1.statSync)(absolute);
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
const digest = fileDigest(stats);
|
|
189
|
+
const existing = this.digests.get(relativePath);
|
|
190
|
+
if (existing === digest && this.files.has(relativePath)) {
|
|
191
|
+
symbolCount += this.files.get(relativePath)?.symbols.length ?? 0;
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
try {
|
|
195
|
+
const parsedFile = parseSource(this.root, absolute);
|
|
196
|
+
const record = {
|
|
197
|
+
path: relativePath,
|
|
198
|
+
mtimeMs: stats.mtimeMs,
|
|
199
|
+
size: stats.size,
|
|
200
|
+
symbols: parsedFile.symbols,
|
|
201
|
+
imports: parsedFile.imports,
|
|
202
|
+
exportedNames: parsedFile.exportedNames,
|
|
203
|
+
};
|
|
204
|
+
this.files.set(relativePath, record);
|
|
205
|
+
this.digests.set(relativePath, digest);
|
|
206
|
+
parsed += 1;
|
|
207
|
+
symbolCount += record.symbols.length;
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
// Unparsable source: skip it; a later update will retry.
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// Drop records for files that no longer exist.
|
|
214
|
+
for (const path of [...this.files.keys()]) {
|
|
215
|
+
if (!seen.has(path)) {
|
|
216
|
+
this.files.delete(path);
|
|
217
|
+
this.digests.delete(path);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
files: this.files.size,
|
|
222
|
+
parsed,
|
|
223
|
+
skipped,
|
|
224
|
+
symbols: symbolCount,
|
|
225
|
+
durationMs: Math.max(0, Date.now() - startedAt),
|
|
226
|
+
incremental: this.files.size > 0 && parsed < this.files.size,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
getSnapshot() {
|
|
230
|
+
return new Map(this.files);
|
|
231
|
+
}
|
|
232
|
+
findSymbol(name) {
|
|
233
|
+
const results = [];
|
|
234
|
+
for (const record of this.files.values()) {
|
|
235
|
+
for (const symbol of record.symbols) {
|
|
236
|
+
if (symbol.name === name)
|
|
237
|
+
results.push(symbol);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return results;
|
|
241
|
+
}
|
|
242
|
+
importersOf(relativePath) {
|
|
243
|
+
const normalized = relativePath.split('\\').join('/');
|
|
244
|
+
const results = [];
|
|
245
|
+
for (const record of this.files.values()) {
|
|
246
|
+
if (record.imports.includes(normalized))
|
|
247
|
+
results.push(record.path);
|
|
248
|
+
}
|
|
249
|
+
return results;
|
|
250
|
+
}
|
|
251
|
+
invalidate(relativePath) {
|
|
252
|
+
const normalized = relativePath.split('\\').join('/');
|
|
253
|
+
this.files.delete(normalized);
|
|
254
|
+
this.digests.delete(normalized);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
exports.RepoSymbolIndexV1 = RepoSymbolIndexV1;
|
|
258
|
+
//# sourceMappingURL=symbol-index.js.map
|
|
@@ -412,6 +412,8 @@ export declare function getProjectKey(projectPath: string): string;
|
|
|
412
412
|
export declare function isSessionMetaFile(file: string): boolean;
|
|
413
413
|
/** Shared exclusion list for session sidecar files. */
|
|
414
414
|
export declare const SESSION_SIDECAR_SUFFIXES: string[];
|
|
415
|
+
/** Warm/rebuild the global read model without synchronous lock retry or scan. */
|
|
416
|
+
export declare function prepareSessionCatalogAsync(waitMs?: number): Promise<void>;
|
|
415
417
|
/** Capture the append-only transcript identity before preparing a compact candidate. */
|
|
416
418
|
export declare function prepareSessionCompactSourceReceipt(sessionId: string): CompactPrepareSourceReceipt;
|
|
417
419
|
/**
|
|
@@ -422,8 +424,22 @@ export declare function prepareSessionCompactSourceReceipt(sessionId: string): C
|
|
|
422
424
|
export declare function advanceSessionCompactSourceReceipt(receipt: CompactPrepareSourceReceipt, appendedMessages: readonly SessionMessage[]): CompactPrepareSourceReceipt;
|
|
423
425
|
/**
|
|
424
426
|
* 创建新会话
|
|
425
|
-
|
|
426
|
-
|
|
427
|
+
* @param options Optional pre-probed Git metadata. Supplying this object skips synchronous Git.
|
|
428
|
+
*/
|
|
429
|
+
export declare function createSession(projectPath: string, model: string, options?: {
|
|
430
|
+
readonly gitBranch?: string;
|
|
431
|
+
}): SessionMeta;
|
|
432
|
+
/**
|
|
433
|
+
* Web creation waits for cross-process metadata/catalog locks asynchronously.
|
|
434
|
+
* Both locks are acquired before any Session bytes are written. The original
|
|
435
|
+
* synchronous lock owner/recovery and atomic publication remain authoritative.
|
|
436
|
+
*/
|
|
437
|
+
export declare function createSessionAsync(projectPath: string, model: string, options?: {
|
|
438
|
+
readonly gitBranch?: string;
|
|
439
|
+
readonly name?: string;
|
|
440
|
+
readonly waitMs?: number;
|
|
441
|
+
readonly beforeCommit?: () => void;
|
|
442
|
+
}): Promise<SessionMeta>;
|
|
427
443
|
/**
|
|
428
444
|
* 保存会话元数据
|
|
429
445
|
*/
|