@mrsf/cli 0.4.1 → 0.4.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/dist/browser.d.ts +15 -2
- package/dist/browser.js +15 -2
- package/dist/commands/init.js +7 -5
- package/dist/index.d.ts +61 -10
- package/dist/index.js +60 -9
- package/dist/lib/comments.d.ts +2 -1
- package/dist/lib/comments.js +14 -0
- package/dist/lib/fuzzy.js +152 -15
- package/dist/lib/parser.js +1 -1
- package/dist/lib/types.d.ts +5 -0
- package/package.json +1 -1
package/dist/browser.d.ts
CHANGED
|
@@ -1,4 +1,17 @@
|
|
|
1
|
+
import * as fuzzy from "./lib/fuzzy.js";
|
|
2
|
+
import * as reanchorCore from "./lib/reanchor-core.js";
|
|
1
3
|
export type { Comment, DiffHunk, FuzzyCandidate, MrsfDocument, ReanchorResult, ReanchorStatus, } from "./lib/types.js";
|
|
2
|
-
export
|
|
3
|
-
export
|
|
4
|
+
export declare const combinedScore: typeof fuzzy.combinedScore;
|
|
5
|
+
export declare const exactMatch: typeof fuzzy.exactMatch;
|
|
6
|
+
export declare const fuzzySearch: typeof fuzzy.fuzzySearch;
|
|
7
|
+
export declare const levenshteinScore: typeof fuzzy.levenshteinScore;
|
|
8
|
+
export declare const normalizedMatch: typeof fuzzy.normalizedMatch;
|
|
9
|
+
export declare const tokenLcsScore: typeof fuzzy.tokenLcsScore;
|
|
10
|
+
export declare const applyReanchorResults: typeof reanchorCore.applyReanchorResults;
|
|
11
|
+
export declare const DEFAULT_THRESHOLD = 0.6;
|
|
12
|
+
export declare const HIGH_THRESHOLD = 0.8;
|
|
13
|
+
export declare const reanchorComment: typeof reanchorCore.reanchorComment;
|
|
14
|
+
export declare const reanchorDocumentLines: typeof reanchorCore.reanchorDocumentLines;
|
|
15
|
+
export declare const reanchorDocumentText: typeof reanchorCore.reanchorDocumentText;
|
|
16
|
+
export declare const toReanchorLines: typeof reanchorCore.toReanchorLines;
|
|
4
17
|
//# sourceMappingURL=browser.d.ts.map
|
package/dist/browser.js
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import * as fuzzy from "./lib/fuzzy.js";
|
|
2
|
+
import * as reanchorCore from "./lib/reanchor-core.js";
|
|
3
|
+
export const combinedScore = fuzzy.combinedScore;
|
|
4
|
+
export const exactMatch = fuzzy.exactMatch;
|
|
5
|
+
export const fuzzySearch = fuzzy.fuzzySearch;
|
|
6
|
+
export const levenshteinScore = fuzzy.levenshteinScore;
|
|
7
|
+
export const normalizedMatch = fuzzy.normalizedMatch;
|
|
8
|
+
export const tokenLcsScore = fuzzy.tokenLcsScore;
|
|
9
|
+
export const applyReanchorResults = reanchorCore.applyReanchorResults;
|
|
10
|
+
export const DEFAULT_THRESHOLD = reanchorCore.DEFAULT_THRESHOLD;
|
|
11
|
+
export const HIGH_THRESHOLD = reanchorCore.HIGH_THRESHOLD;
|
|
12
|
+
export const reanchorComment = reanchorCore.reanchorComment;
|
|
13
|
+
export const reanchorDocumentLines = reanchorCore.reanchorDocumentLines;
|
|
14
|
+
export const reanchorDocumentText = reanchorCore.reanchorDocumentText;
|
|
15
|
+
export const toReanchorLines = reanchorCore.toReanchorLines;
|
|
3
16
|
//# sourceMappingURL=browser.js.map
|
package/dist/commands/init.js
CHANGED
|
@@ -28,17 +28,19 @@ export function registerInit(program) {
|
|
|
28
28
|
const root = await findWorkspaceRoot(cwd);
|
|
29
29
|
const sidecarPath = await discoverSidecar(docPath, { cwd: root ?? cwd });
|
|
30
30
|
// Check if sidecar already exists
|
|
31
|
+
let sidecarExists = false;
|
|
31
32
|
try {
|
|
32
33
|
await fs.access(sidecarPath);
|
|
33
|
-
|
|
34
|
-
console.error(chalk.yellow(`Sidecar already exists: ${sidecarPath}`));
|
|
35
|
-
console.error("Use --force to overwrite.");
|
|
36
|
-
process.exit(1);
|
|
37
|
-
}
|
|
34
|
+
sidecarExists = true;
|
|
38
35
|
}
|
|
39
36
|
catch {
|
|
40
37
|
// Good — doesn't exist yet
|
|
41
38
|
}
|
|
39
|
+
if (sidecarExists && !opts.force) {
|
|
40
|
+
console.error(chalk.yellow(`Sidecar already exists: ${sidecarPath}`));
|
|
41
|
+
console.error("Use --force to overwrite.");
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
42
44
|
const doc = {
|
|
43
45
|
mrsf_version: "1.0",
|
|
44
46
|
document: path.basename(docPath),
|
package/dist/index.d.ts
CHANGED
|
@@ -4,16 +4,67 @@
|
|
|
4
4
|
* Usage:
|
|
5
5
|
* import { validate, reanchorFile, addComment, ... } from "mrsf";
|
|
6
6
|
*/
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
import * as discovery from "./lib/discovery.js";
|
|
8
|
+
import * as resolveFiles from "./lib/resolve-files.js";
|
|
9
|
+
import * as parser from "./lib/parser.js";
|
|
10
|
+
import * as writer from "./lib/writer.js";
|
|
11
|
+
import * as validator from "./lib/validator.js";
|
|
12
|
+
import * as fuzzy from "./lib/fuzzy.js";
|
|
13
|
+
import * as git from "./lib/git.js";
|
|
14
|
+
import * as reanchor from "./lib/reanchor.js";
|
|
15
|
+
import * as comments from "./lib/comments.js";
|
|
16
|
+
export type { MrsfDocument, Comment, CommentExtensions, CommentExtensionValue, MrsfConfig, DiagnosticSeverity, ValidationDiagnostic, ValidationResult, ReanchorStatus, ReanchorResult, FuzzyCandidate, DiffHunk, AddCommentOptions, EditCommentOptions, CommentFilter, AnchorHealth, StatusResult, BaseOptions, ReanchorOptions, ValidateOptions, } from "./lib/types.js";
|
|
17
|
+
export declare const findWorkspaceRoot: typeof discovery.findWorkspaceRoot;
|
|
18
|
+
export declare const loadConfig: typeof discovery.loadConfig;
|
|
19
|
+
export declare const discoverSidecar: typeof discovery.discoverSidecar;
|
|
20
|
+
export declare const sidecarToDocument: typeof discovery.sidecarToDocument;
|
|
21
|
+
export declare const discoverAllSidecars: typeof discovery.discoverAllSidecars;
|
|
22
|
+
export declare const resolveSidecarPaths: typeof resolveFiles.resolveSidecarPaths;
|
|
23
|
+
export declare const parseSidecar: typeof parser.parseSidecar;
|
|
24
|
+
export declare const parseSidecarContent: typeof parser.parseSidecarContent;
|
|
25
|
+
export declare const parseSidecarLenient: typeof parser.parseSidecarLenient;
|
|
26
|
+
export declare const parseSidecarContentLenient: typeof parser.parseSidecarContentLenient;
|
|
27
|
+
export declare const readDocumentLines: typeof parser.readDocumentLines;
|
|
11
28
|
export type { LenientParseResult } from "./lib/parser.js";
|
|
12
|
-
export
|
|
13
|
-
export
|
|
14
|
-
export
|
|
15
|
-
export
|
|
16
|
-
export
|
|
17
|
-
export
|
|
29
|
+
export declare const computeHash: typeof writer.computeHash;
|
|
30
|
+
export declare const syncHash: typeof writer.syncHash;
|
|
31
|
+
export declare const toYaml: typeof writer.toYaml;
|
|
32
|
+
export declare const toJson: typeof writer.toJson;
|
|
33
|
+
export declare const writeSidecar: typeof writer.writeSidecar;
|
|
34
|
+
export declare const validate: typeof validator.validate;
|
|
35
|
+
export declare const validateFile: typeof validator.validateFile;
|
|
36
|
+
export declare const exactMatch: typeof fuzzy.exactMatch;
|
|
37
|
+
export declare const normalizedMatch: typeof fuzzy.normalizedMatch;
|
|
38
|
+
export declare const fuzzySearch: typeof fuzzy.fuzzySearch;
|
|
39
|
+
export declare const combinedScore: typeof fuzzy.combinedScore;
|
|
40
|
+
export declare const isGitAvailable: typeof git.isGitAvailable;
|
|
41
|
+
export declare const findRepoRoot: typeof git.findRepoRoot;
|
|
42
|
+
export declare const getCurrentCommit: typeof git.getCurrentCommit;
|
|
43
|
+
export declare const isStale: typeof git.isStale;
|
|
44
|
+
export declare const getDiff: typeof git.getDiff;
|
|
45
|
+
export declare const getLineShift: typeof git.getLineShift;
|
|
46
|
+
export declare const getFileAtCommit: typeof git.getFileAtCommit;
|
|
47
|
+
export declare const getStagedFiles: typeof git.getStagedFiles;
|
|
48
|
+
export declare const detectRenames: typeof git.detectRenames;
|
|
49
|
+
export declare const parseDiffHunks: typeof git.parseDiffHunks;
|
|
50
|
+
export declare const DEFAULT_THRESHOLD = 0.6;
|
|
51
|
+
export declare const HIGH_THRESHOLD = 0.8;
|
|
52
|
+
export declare const reanchorComment: typeof reanchor.reanchorComment;
|
|
53
|
+
export declare const reanchorDocumentLines: typeof reanchor.reanchorDocumentLines;
|
|
54
|
+
export declare const reanchorDocumentText: typeof reanchor.reanchorDocumentText;
|
|
55
|
+
export declare const toReanchorLines: typeof reanchor.toReanchorLines;
|
|
56
|
+
export declare const reanchorDocument: typeof reanchor.reanchorDocument;
|
|
57
|
+
export declare const applyReanchorResults: typeof reanchor.applyReanchorResults;
|
|
58
|
+
export declare const reanchorFile: typeof reanchor.reanchorFile;
|
|
59
|
+
export declare const addComment: typeof comments.addComment;
|
|
60
|
+
export declare const editComment: typeof comments.editComment;
|
|
61
|
+
export declare const normalizeCommentExtensions: typeof comments.normalizeCommentExtensions;
|
|
62
|
+
export declare const populateSelectedText: typeof comments.populateSelectedText;
|
|
63
|
+
export declare const resolveComment: typeof comments.resolveComment;
|
|
64
|
+
export declare const unresolveComment: typeof comments.unresolveComment;
|
|
65
|
+
export declare const removeComment: typeof comments.removeComment;
|
|
66
|
+
export declare const filterComments: typeof comments.filterComments;
|
|
67
|
+
export declare const getThreads: typeof comments.getThreads;
|
|
68
|
+
export declare const summarize: typeof comments.summarize;
|
|
18
69
|
export type { CommentSummary, RemoveCommentOptions } from "./lib/comments.js";
|
|
19
70
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -4,22 +4,73 @@
|
|
|
4
4
|
* Usage:
|
|
5
5
|
* import { validate, reanchorFile, addComment, ... } from "mrsf";
|
|
6
6
|
*/
|
|
7
|
+
import * as discovery from "./lib/discovery.js";
|
|
8
|
+
import * as resolveFiles from "./lib/resolve-files.js";
|
|
9
|
+
import * as parser from "./lib/parser.js";
|
|
10
|
+
import * as writer from "./lib/writer.js";
|
|
11
|
+
import * as validator from "./lib/validator.js";
|
|
12
|
+
import * as fuzzy from "./lib/fuzzy.js";
|
|
13
|
+
import * as git from "./lib/git.js";
|
|
14
|
+
import * as reanchor from "./lib/reanchor.js";
|
|
15
|
+
import * as comments from "./lib/comments.js";
|
|
7
16
|
// Discovery
|
|
8
|
-
export
|
|
17
|
+
export const findWorkspaceRoot = discovery.findWorkspaceRoot;
|
|
18
|
+
export const loadConfig = discovery.loadConfig;
|
|
19
|
+
export const discoverSidecar = discovery.discoverSidecar;
|
|
20
|
+
export const sidecarToDocument = discovery.sidecarToDocument;
|
|
21
|
+
export const discoverAllSidecars = discovery.discoverAllSidecars;
|
|
9
22
|
// File resolution
|
|
10
|
-
export
|
|
23
|
+
export const resolveSidecarPaths = resolveFiles.resolveSidecarPaths;
|
|
11
24
|
// Parsing
|
|
12
|
-
export
|
|
25
|
+
export const parseSidecar = parser.parseSidecar;
|
|
26
|
+
export const parseSidecarContent = parser.parseSidecarContent;
|
|
27
|
+
export const parseSidecarLenient = parser.parseSidecarLenient;
|
|
28
|
+
export const parseSidecarContentLenient = parser.parseSidecarContentLenient;
|
|
29
|
+
export const readDocumentLines = parser.readDocumentLines;
|
|
13
30
|
// Writing
|
|
14
|
-
export
|
|
31
|
+
export const computeHash = writer.computeHash;
|
|
32
|
+
export const syncHash = writer.syncHash;
|
|
33
|
+
export const toYaml = writer.toYaml;
|
|
34
|
+
export const toJson = writer.toJson;
|
|
35
|
+
export const writeSidecar = writer.writeSidecar;
|
|
15
36
|
// Validation
|
|
16
|
-
export
|
|
37
|
+
export const validate = validator.validate;
|
|
38
|
+
export const validateFile = validator.validateFile;
|
|
17
39
|
// Fuzzy matching
|
|
18
|
-
export
|
|
40
|
+
export const exactMatch = fuzzy.exactMatch;
|
|
41
|
+
export const normalizedMatch = fuzzy.normalizedMatch;
|
|
42
|
+
export const fuzzySearch = fuzzy.fuzzySearch;
|
|
43
|
+
export const combinedScore = fuzzy.combinedScore;
|
|
19
44
|
// Git
|
|
20
|
-
export
|
|
45
|
+
export const isGitAvailable = git.isGitAvailable;
|
|
46
|
+
export const findRepoRoot = git.findRepoRoot;
|
|
47
|
+
export const getCurrentCommit = git.getCurrentCommit;
|
|
48
|
+
export const isStale = git.isStale;
|
|
49
|
+
export const getDiff = git.getDiff;
|
|
50
|
+
export const getLineShift = git.getLineShift;
|
|
51
|
+
export const getFileAtCommit = git.getFileAtCommit;
|
|
52
|
+
export const getStagedFiles = git.getStagedFiles;
|
|
53
|
+
export const detectRenames = git.detectRenames;
|
|
54
|
+
export const parseDiffHunks = git.parseDiffHunks;
|
|
21
55
|
// Re-anchoring
|
|
22
|
-
export
|
|
56
|
+
export const DEFAULT_THRESHOLD = reanchor.DEFAULT_THRESHOLD;
|
|
57
|
+
export const HIGH_THRESHOLD = reanchor.HIGH_THRESHOLD;
|
|
58
|
+
export const reanchorComment = reanchor.reanchorComment;
|
|
59
|
+
export const reanchorDocumentLines = reanchor.reanchorDocumentLines;
|
|
60
|
+
export const reanchorDocumentText = reanchor.reanchorDocumentText;
|
|
61
|
+
export const toReanchorLines = reanchor.toReanchorLines;
|
|
62
|
+
export const reanchorDocument = reanchor.reanchorDocument;
|
|
63
|
+
export const applyReanchorResults = reanchor.applyReanchorResults;
|
|
64
|
+
export const reanchorFile = reanchor.reanchorFile;
|
|
23
65
|
// Comments
|
|
24
|
-
export
|
|
66
|
+
export const addComment = comments.addComment;
|
|
67
|
+
export const editComment = comments.editComment;
|
|
68
|
+
export const normalizeCommentExtensions = comments.normalizeCommentExtensions;
|
|
69
|
+
export const populateSelectedText = comments.populateSelectedText;
|
|
70
|
+
export const resolveComment = comments.resolveComment;
|
|
71
|
+
export const unresolveComment = comments.unresolveComment;
|
|
72
|
+
export const removeComment = comments.removeComment;
|
|
73
|
+
export const filterComments = comments.filterComments;
|
|
74
|
+
export const getThreads = comments.getThreads;
|
|
75
|
+
export const summarize = comments.summarize;
|
|
25
76
|
//# sourceMappingURL=index.js.map
|
package/dist/lib/comments.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MRSF Comment Operations — CRUD for sidecar comments.
|
|
3
3
|
*/
|
|
4
|
-
import type { AddCommentOptions, Comment, CommentFilter, CommentExtensions, MrsfDocument } from "./types.js";
|
|
4
|
+
import type { AddCommentOptions, Comment, CommentFilter, CommentExtensions, EditCommentOptions, MrsfDocument } from "./types.js";
|
|
5
5
|
export declare function normalizeCommentExtensions(extensions?: Record<string, unknown>): CommentExtensions;
|
|
6
6
|
/**
|
|
7
7
|
* Add a new comment to a document. Mutates doc.comments in place.
|
|
@@ -13,6 +13,7 @@ export declare function addComment(doc: MrsfDocument, opts: AddCommentOptions, r
|
|
|
13
13
|
* Should be called after addComment if the caller provides line info but not selected_text.
|
|
14
14
|
*/
|
|
15
15
|
export declare function populateSelectedText(comment: Comment, documentLines: string[]): void;
|
|
16
|
+
export declare function editComment(doc: MrsfDocument, commentId: string, opts: EditCommentOptions): Comment;
|
|
16
17
|
/**
|
|
17
18
|
* Resolve a comment by id. Per §9, resolving a parent does NOT
|
|
18
19
|
* automatically resolve its replies.
|
package/dist/lib/comments.js
CHANGED
|
@@ -129,6 +129,20 @@ export function populateSelectedText(comment, documentLines) {
|
|
|
129
129
|
comment.selected_text_hash = computeHash(comment.selected_text);
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
|
+
export function editComment(doc, commentId, opts) {
|
|
133
|
+
const comment = doc.comments.find((entry) => entry.id === commentId);
|
|
134
|
+
if (!comment) {
|
|
135
|
+
throw new Error(`Unknown comment '${commentId}'.`);
|
|
136
|
+
}
|
|
137
|
+
if (opts.actor && comment.author !== opts.actor) {
|
|
138
|
+
throw new Error("Only the comment author can edit this comment.");
|
|
139
|
+
}
|
|
140
|
+
if (opts.text.trim().length === 0) {
|
|
141
|
+
throw new Error("Comment text cannot be empty.");
|
|
142
|
+
}
|
|
143
|
+
comment.text = opts.text;
|
|
144
|
+
return comment;
|
|
145
|
+
}
|
|
132
146
|
// ---------------------------------------------------------------------------
|
|
133
147
|
// Resolve / Unresolve
|
|
134
148
|
// ---------------------------------------------------------------------------
|
package/dist/lib/fuzzy.js
CHANGED
|
@@ -110,6 +110,37 @@ export function normalizedMatch(lines, needle) {
|
|
|
110
110
|
function tokenize(text) {
|
|
111
111
|
return text.split(/\s+/).filter((t) => t.length > 0);
|
|
112
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* Build a frequency map for a token array. Used for fast multiset
|
|
115
|
+
* intersection during the fuzzySearch prefilter.
|
|
116
|
+
*/
|
|
117
|
+
function buildTokenFreq(tokens) {
|
|
118
|
+
const freq = new Map();
|
|
119
|
+
for (const tok of tokens) {
|
|
120
|
+
freq.set(tok, (freq.get(tok) ?? 0) + 1);
|
|
121
|
+
}
|
|
122
|
+
return freq;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Multiset intersection size: sum over needle tokens of
|
|
126
|
+
* min(needleFreq[token], windowFreq[token]). This is an upper bound on
|
|
127
|
+
* the longest common subsequence length, so it lets us cheaply skip
|
|
128
|
+
* windows that cannot possibly reach the score threshold.
|
|
129
|
+
*/
|
|
130
|
+
function multisetIntersection(needleFreq, windowFreq) {
|
|
131
|
+
let common = 0;
|
|
132
|
+
// Iterate the smaller map for speed.
|
|
133
|
+
const [smaller, larger] = needleFreq.size <= windowFreq.size
|
|
134
|
+
? [needleFreq, windowFreq]
|
|
135
|
+
: [windowFreq, needleFreq];
|
|
136
|
+
for (const [tok, cnt] of smaller) {
|
|
137
|
+
const other = larger.get(tok);
|
|
138
|
+
if (other !== undefined) {
|
|
139
|
+
common += cnt < other ? cnt : other;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return common;
|
|
143
|
+
}
|
|
113
144
|
/**
|
|
114
145
|
* Longest Common Subsequence length of two token arrays.
|
|
115
146
|
*/
|
|
@@ -199,38 +230,144 @@ export function fuzzySearch(lines, needle, threshold = 0.6, hintLine) {
|
|
|
199
230
|
const needleLines = needle.split("\n");
|
|
200
231
|
const needleLineCount = needleLines.length;
|
|
201
232
|
const candidates = [];
|
|
233
|
+
// Pre-tokenize: do this once per call instead of per window. Per-line
|
|
234
|
+
// tokenization gives identical results to tokenize(lines.join("\n")) since
|
|
235
|
+
// tokenize splits on /\s+/ and filters empties.
|
|
236
|
+
const lineTokens = new Array(lines.length);
|
|
237
|
+
const lineTokenLengths = new Array(lines.length);
|
|
238
|
+
for (let i = 0; i < lines.length; i++) {
|
|
239
|
+
const toks = lines[i] ? tokenize(lines[i]) : [];
|
|
240
|
+
lineTokens[i] = toks;
|
|
241
|
+
lineTokenLengths[i] = toks.length;
|
|
242
|
+
}
|
|
243
|
+
const needleTokens = tokenize(needle);
|
|
244
|
+
const needleTokenCount = needleTokens.length;
|
|
245
|
+
const needleFreq = buildTokenFreq(needleTokens);
|
|
246
|
+
// The combined score is 0.6 * tScore + 0.4 * lScore. Since lScore <= 1.0,
|
|
247
|
+
// a window can only reach `threshold` if tScore >= (threshold - 0.4) / 0.6.
|
|
248
|
+
// Use this as a hard prefilter before doing the expensive LCS / Levenshtein.
|
|
249
|
+
const minTScore = Math.max(0, (threshold - 0.4) / 0.6);
|
|
202
250
|
// Window sizes: ±30% of original line count, minimum 1
|
|
203
251
|
const minWindow = Math.max(1, Math.floor(needleLineCount * 0.7));
|
|
204
252
|
const maxWindow = Math.min(lines.length - 1, Math.ceil(needleLineCount * 1.3) + 1);
|
|
253
|
+
// Track total token count per window using a rolling sum so we don't
|
|
254
|
+
// re-walk the line-token arrays for every (winSize, startLine) pair.
|
|
205
255
|
for (let winSize = minWindow; winSize <= maxWindow; winSize++) {
|
|
256
|
+
if (winSize < 1)
|
|
257
|
+
continue;
|
|
258
|
+
if (winSize > lines.length - 1)
|
|
259
|
+
continue;
|
|
260
|
+
// Initial window token sum for startLine = 1.
|
|
261
|
+
let runningTokenCount = 0;
|
|
262
|
+
for (let i = 1; i < 1 + winSize && i < lines.length; i++) {
|
|
263
|
+
runningTokenCount += lineTokenLengths[i];
|
|
264
|
+
}
|
|
206
265
|
for (let startLine = 1; startLine + winSize - 1 < lines.length; startLine++) {
|
|
207
|
-
const
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
if (
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
266
|
+
const endLineExclusive = startLine + winSize;
|
|
267
|
+
const M = runningTokenCount;
|
|
268
|
+
// Cheap skip: empty windows or empty needle can't fuzzy-match.
|
|
269
|
+
if (needleTokenCount > 0 && M > 0) {
|
|
270
|
+
// Build a frequency map for the window tokens. Cheaper than LCS:
|
|
271
|
+
// O(M) instead of O(M * needleTokenCount). We allocate a fresh map
|
|
272
|
+
// per window; tried a rolling map and the bookkeeping was a wash
|
|
273
|
+
// with realistic windowSize but if profiling demands it can be
|
|
274
|
+
// converted.
|
|
275
|
+
const windowFreq = new Map();
|
|
276
|
+
for (let li = startLine; li < endLineExclusive; li++) {
|
|
277
|
+
const arr = lineTokens[li];
|
|
278
|
+
for (let ti = 0; ti < arr.length; ti++) {
|
|
279
|
+
const tok = arr[ti];
|
|
280
|
+
windowFreq.set(tok, (windowFreq.get(tok) ?? 0) + 1);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
// Multiset intersection is an upper bound on LCS length. If we
|
|
284
|
+
// can't reach minTScore even with a perfect LCS, skip the window.
|
|
285
|
+
const common = multisetIntersection(needleFreq, windowFreq);
|
|
286
|
+
const tScoreUpperBound = common / Math.max(needleTokenCount, M);
|
|
287
|
+
if (tScoreUpperBound >= minTScore) {
|
|
288
|
+
// Build the flat window token array only when we actually need
|
|
289
|
+
// to compute LCS.
|
|
290
|
+
const windowTokens = new Array(M);
|
|
291
|
+
let wi = 0;
|
|
292
|
+
for (let li = startLine; li < endLineExclusive; li++) {
|
|
293
|
+
const arr = lineTokens[li];
|
|
294
|
+
for (let ti = 0; ti < arr.length; ti++) {
|
|
295
|
+
windowTokens[wi++] = arr[ti];
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
const lcs = lcsLength(needleTokens, windowTokens);
|
|
299
|
+
const tScore = lcs / Math.max(needleTokenCount, M);
|
|
300
|
+
if (tScore >= minTScore) {
|
|
301
|
+
// Defer expensive string join + Levenshtein until we know the
|
|
302
|
+
// tScore could plausibly reach threshold.
|
|
303
|
+
const windowLines = lines.slice(startLine, endLineExclusive);
|
|
304
|
+
const windowText = windowLines.join("\n");
|
|
305
|
+
let lScore;
|
|
306
|
+
if (needle.length < 500 && windowText.length < 500) {
|
|
307
|
+
lScore = levenshteinScore(needle, windowText);
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
lScore = tScore; // fall back to token score only
|
|
311
|
+
}
|
|
312
|
+
const score = tScore * 0.6 + lScore * 0.4;
|
|
313
|
+
if (score >= threshold) {
|
|
314
|
+
candidates.push({
|
|
315
|
+
text: windowText,
|
|
316
|
+
line: startLine,
|
|
317
|
+
endLine: startLine + winSize - 1,
|
|
318
|
+
startColumn: 0,
|
|
319
|
+
endColumn: windowLines[windowLines.length - 1].length,
|
|
320
|
+
score,
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
// Slide window by one: drop startLine token count, add endLine token count.
|
|
327
|
+
const nextStart = startLine + 1;
|
|
328
|
+
const nextEndExclusive = nextStart + winSize;
|
|
329
|
+
if (nextEndExclusive <= lines.length) {
|
|
330
|
+
runningTokenCount += lineTokenLengths[endLineExclusive] - lineTokenLengths[startLine];
|
|
219
331
|
}
|
|
220
332
|
}
|
|
221
333
|
}
|
|
222
|
-
// For single-line needles, also try substring matching within each line
|
|
334
|
+
// For single-line needles, also try substring matching within each line.
|
|
335
|
+
// Prefilter each line by token overlap before running the inner sliding
|
|
336
|
+
// window — long lines without enough needle-token coverage are skipped
|
|
337
|
+
// wholesale, since no substring of such a line can exceed its parent
|
|
338
|
+
// line's token-overlap with the needle.
|
|
223
339
|
if (needleLineCount === 1 && needle.length < 200) {
|
|
340
|
+
// The minimum number of needle tokens that must appear in a line for any
|
|
341
|
+
// substring of that line to plausibly reach minTScore. tScore =
|
|
342
|
+
// lcs / max(needleN, subN), so lcs >= minTScore * needleN is required,
|
|
343
|
+
// and lcs <= number of common tokens.
|
|
344
|
+
const requiredLineCommon = Math.max(0, Math.ceil(needleTokenCount * minTScore));
|
|
224
345
|
for (let lineNum = 1; lineNum < lines.length; lineNum++) {
|
|
225
346
|
const line = lines[lineNum];
|
|
226
347
|
if (!line)
|
|
227
348
|
continue;
|
|
228
|
-
|
|
349
|
+
if (needleTokenCount > 0 && requiredLineCommon > 0) {
|
|
350
|
+
const lineFreq = buildTokenFreq(lineTokens[lineNum]);
|
|
351
|
+
const lineCommon = multisetIntersection(needleFreq, lineFreq);
|
|
352
|
+
if (lineCommon < requiredLineCommon)
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
// Sliding window within line. Step by 1 by default for accuracy, but
|
|
356
|
+
// for very long lines and long needles, we coarsen the column step to
|
|
357
|
+
// avoid quadratic-in-line-length behaviour. The natural `len = winLen`
|
|
358
|
+
// is always swept densely so we don't miss the most likely match.
|
|
229
359
|
const winLen = needle.length;
|
|
230
360
|
const minWinLen = Math.max(3, Math.floor(winLen * 0.7));
|
|
231
361
|
const maxWinLen = Math.min(line.length, Math.ceil(winLen * 1.3));
|
|
232
362
|
for (let len = minWinLen; len <= maxWinLen; len++) {
|
|
233
|
-
|
|
363
|
+
// Coarsen the column step proportionally to needle length once the
|
|
364
|
+
// sub-window is no longer the natural needle length. This trades a
|
|
365
|
+
// tiny bit of recall for an order-of-magnitude speedup on long
|
|
366
|
+
// lines with many candidate offsets.
|
|
367
|
+
const colStep = len === winLen
|
|
368
|
+
? 1
|
|
369
|
+
: Math.max(1, Math.floor(winLen / 16));
|
|
370
|
+
for (let col = 0; col + len <= line.length; col += colStep) {
|
|
234
371
|
const sub = line.substring(col, col + len);
|
|
235
372
|
const score = combinedScore(needle, sub);
|
|
236
373
|
if (score >= threshold) {
|
package/dist/lib/parser.js
CHANGED
|
@@ -175,7 +175,7 @@ function salvageYaml(content) {
|
|
|
175
175
|
*/
|
|
176
176
|
export async function readDocumentLines(filePath) {
|
|
177
177
|
const content = await readFile(path.resolve(filePath), "utf-8");
|
|
178
|
-
const lines = content.split("\n");
|
|
178
|
+
const lines = content.replace(/\r\n?/g, "\n").split("\n");
|
|
179
179
|
// Prepend empty element so lines[1] = first line (1-based)
|
|
180
180
|
return ["", ...lines];
|
|
181
181
|
}
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -113,6 +113,11 @@ export interface AddCommentOptions {
|
|
|
113
113
|
/** Tool-specific extension fields keyed by x_*. */
|
|
114
114
|
extensions?: CommentExtensions;
|
|
115
115
|
}
|
|
116
|
+
export interface EditCommentOptions {
|
|
117
|
+
text: string;
|
|
118
|
+
/** Optional actor identity for author-based edit permission checks. */
|
|
119
|
+
actor?: string;
|
|
120
|
+
}
|
|
116
121
|
export interface CommentFilter {
|
|
117
122
|
open?: boolean;
|
|
118
123
|
resolved?: boolean;
|