@mrsf/cli 0.2.0 → 0.2.1
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/commands/reanchor.js +7 -1
- package/dist/lib/reanchor.d.ts +2 -0
- package/dist/lib/reanchor.js +47 -5
- package/dist/lib/types.d.ts +2 -0
- package/package.json +1 -1
|
@@ -8,7 +8,7 @@ import { readDocumentLines } from "../lib/parser.js";
|
|
|
8
8
|
import { sidecarToDocument } from "../lib/discovery.js";
|
|
9
9
|
import { resolveSidecarPaths } from "../lib/resolve-files.js";
|
|
10
10
|
import { writeSidecar } from "../lib/writer.js";
|
|
11
|
-
import { findRepoRoot, getStagedFiles } from "../lib/git.js";
|
|
11
|
+
import { findRepoRoot, getStagedFiles, getCurrentCommit } from "../lib/git.js";
|
|
12
12
|
import { reanchorDocument, applyReanchorResults, } from "../lib/reanchor.js";
|
|
13
13
|
export function registerReanchor(program) {
|
|
14
14
|
program
|
|
@@ -20,6 +20,7 @@ export function registerReanchor(program) {
|
|
|
20
20
|
.option("--no-git", "Disable git integration")
|
|
21
21
|
.option("--from <commit>", "Override from-commit for all comments")
|
|
22
22
|
.option("--update-text", "Also update selected_text to match current document text")
|
|
23
|
+
.option("-f, --force", "Firmly anchor high-confidence results (update commit to HEAD, clear audit fields)")
|
|
23
24
|
.action(async (files, opts) => {
|
|
24
25
|
const parentOpts = program.opts();
|
|
25
26
|
const cwd = parentOpts.cwd ?? process.cwd();
|
|
@@ -70,6 +71,7 @@ export function registerReanchor(program) {
|
|
|
70
71
|
const docPath = sidecarToDocument(sp);
|
|
71
72
|
const lines = await readDocumentLines(docPath);
|
|
72
73
|
const repoRoot = !noGit ? await findRepoRoot(cwd) : null;
|
|
74
|
+
const headCommit = repoRoot ? await getCurrentCommit(repoRoot) : undefined;
|
|
73
75
|
const results = await reanchorDocument(doc, lines, {
|
|
74
76
|
threshold,
|
|
75
77
|
noGit,
|
|
@@ -82,6 +84,8 @@ export function registerReanchor(program) {
|
|
|
82
84
|
if (!opts.dryRun) {
|
|
83
85
|
const changed = applyReanchorResults(doc, results, {
|
|
84
86
|
updateText: opts.updateText,
|
|
87
|
+
force: opts.force,
|
|
88
|
+
headCommit: headCommit ?? undefined,
|
|
85
89
|
});
|
|
86
90
|
totalChanged += changed;
|
|
87
91
|
if (changed > 0) {
|
|
@@ -93,6 +97,8 @@ export function registerReanchor(program) {
|
|
|
93
97
|
const clone = structuredClone(doc);
|
|
94
98
|
totalChanged += applyReanchorResults(clone, results, {
|
|
95
99
|
updateText: opts.updateText,
|
|
100
|
+
force: opts.force,
|
|
101
|
+
headCommit: headCommit ?? undefined,
|
|
96
102
|
});
|
|
97
103
|
}
|
|
98
104
|
if (!quiet) {
|
package/dist/lib/reanchor.d.ts
CHANGED
|
@@ -33,6 +33,8 @@ export declare function reanchorDocument(doc: MrsfDocument, documentLines: strin
|
|
|
33
33
|
*/
|
|
34
34
|
export declare function applyReanchorResults(doc: MrsfDocument, results: ReanchorResult[], opts?: {
|
|
35
35
|
updateText?: boolean;
|
|
36
|
+
force?: boolean;
|
|
37
|
+
headCommit?: string;
|
|
36
38
|
}): number;
|
|
37
39
|
/**
|
|
38
40
|
* High-level re-anchor for a single sidecar file path.
|
package/dist/lib/reanchor.js
CHANGED
|
@@ -45,8 +45,24 @@ export function reanchorComment(comment, documentLines, opts = {}) {
|
|
|
45
45
|
// -----------------------------------------------------------------------
|
|
46
46
|
// Step 0: Diff-based shift
|
|
47
47
|
// -----------------------------------------------------------------------
|
|
48
|
-
if (
|
|
48
|
+
if (comment.line != null && opts.diffHunks?.length) {
|
|
49
49
|
const { shift, modified } = getLineShift(opts.diffHunks, comment.line);
|
|
50
|
+
// For line-only comments (no selected_text), apply the diff shift directly
|
|
51
|
+
if (!selectedText) {
|
|
52
|
+
const shiftedLine = comment.line + shift;
|
|
53
|
+
const lineSpan = comment.end_line != null ? comment.end_line - comment.line : 0;
|
|
54
|
+
const shiftedEndLine = comment.end_line != null ? shiftedLine + lineSpan : undefined;
|
|
55
|
+
return {
|
|
56
|
+
commentId,
|
|
57
|
+
status: shift === 0 ? "anchored" : "shifted",
|
|
58
|
+
score: 1.0,
|
|
59
|
+
newLine: shiftedLine,
|
|
60
|
+
newEndLine: shiftedEndLine,
|
|
61
|
+
reason: shift === 0
|
|
62
|
+
? "Line-only comment unchanged (diff confirms position)."
|
|
63
|
+
: `Line-only comment shifted by ${shift > 0 ? "+" : ""}${shift} line(s) via diff.`,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
50
66
|
if (!modified) {
|
|
51
67
|
const shiftedLine = comment.line + shift;
|
|
52
68
|
const lineSpan = comment.end_line != null ? comment.end_line - comment.line : 0;
|
|
@@ -167,13 +183,18 @@ export function reanchorComment(comment, documentLines, opts = {}) {
|
|
|
167
183
|
}
|
|
168
184
|
}
|
|
169
185
|
// Pure line fallback
|
|
186
|
+
// If there's no selected_text, the comment is purely line-anchored.
|
|
187
|
+
// A commit change alone doesn't make it ambiguous — we accept it as-is.
|
|
188
|
+
const isLineOnly = !selectedText;
|
|
170
189
|
return {
|
|
171
190
|
commentId,
|
|
172
|
-
status: opts.commitIsStale ? "ambiguous" : "anchored",
|
|
173
|
-
score: opts.commitIsStale ? 0.5 : 0.8,
|
|
191
|
+
status: isLineOnly ? "anchored" : (opts.commitIsStale ? "ambiguous" : "anchored"),
|
|
192
|
+
score: isLineOnly ? 1.0 : (opts.commitIsStale ? 0.5 : 0.8),
|
|
174
193
|
newLine: comment.line,
|
|
175
194
|
newEndLine: comment.end_line,
|
|
176
|
-
reason:
|
|
195
|
+
reason: isLineOnly
|
|
196
|
+
? "Line-only comment (no selected_text to verify)."
|
|
197
|
+
: `Line/column fallback${qualifier}.`,
|
|
177
198
|
};
|
|
178
199
|
}
|
|
179
200
|
}
|
|
@@ -320,6 +341,22 @@ export function applyReanchorResults(doc, results, opts = {}) {
|
|
|
320
341
|
comment.x_reanchor_status = result.status;
|
|
321
342
|
comment.x_reanchor_score = result.score;
|
|
322
343
|
}
|
|
344
|
+
// --force: firmly anchor high-confidence results
|
|
345
|
+
// Update commit to HEAD and clear audit fields so the comment
|
|
346
|
+
// won't be re-evaluated on the next reanchor run.
|
|
347
|
+
if (opts.force &&
|
|
348
|
+
opts.headCommit &&
|
|
349
|
+
(result.status === "anchored" || result.status === "shifted") &&
|
|
350
|
+
result.score >= HIGH_THRESHOLD) {
|
|
351
|
+
comment.commit = opts.headCommit;
|
|
352
|
+
delete comment.x_reanchor_status;
|
|
353
|
+
delete comment.x_reanchor_score;
|
|
354
|
+
// Clear stale anchored_text since anchor is now confirmed
|
|
355
|
+
if (comment.anchored_text && comment.anchored_text === comment.selected_text) {
|
|
356
|
+
delete comment.anchored_text;
|
|
357
|
+
}
|
|
358
|
+
isChanged = true;
|
|
359
|
+
}
|
|
323
360
|
if (isChanged)
|
|
324
361
|
changed++;
|
|
325
362
|
}
|
|
@@ -333,6 +370,7 @@ export async function reanchorFile(sidecarPath, opts = {}) {
|
|
|
333
370
|
const docPath = sidecarToDocument(sidecarPath);
|
|
334
371
|
const documentLines = await readDocumentLines(docPath);
|
|
335
372
|
const repoRoot = !opts.noGit ? await findRepoRoot(opts.cwd) : null;
|
|
373
|
+
const headCommit = repoRoot ? await getCurrentCommit(repoRoot) : undefined;
|
|
336
374
|
const results = await reanchorDocument(doc, documentLines, {
|
|
337
375
|
...opts,
|
|
338
376
|
documentPath: docPath,
|
|
@@ -341,7 +379,11 @@ export async function reanchorFile(sidecarPath, opts = {}) {
|
|
|
341
379
|
let changed = 0;
|
|
342
380
|
let written = false;
|
|
343
381
|
if (!opts.dryRun) {
|
|
344
|
-
changed = applyReanchorResults(doc, results, {
|
|
382
|
+
changed = applyReanchorResults(doc, results, {
|
|
383
|
+
updateText: opts.updateText,
|
|
384
|
+
force: opts.force,
|
|
385
|
+
headCommit: headCommit ?? undefined,
|
|
386
|
+
});
|
|
345
387
|
if (changed > 0 || opts.autoUpdate) {
|
|
346
388
|
await writeSidecar(sidecarPath, doc);
|
|
347
389
|
written = true;
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -143,6 +143,8 @@ export interface ReanchorOptions extends BaseOptions {
|
|
|
143
143
|
fromCommit?: string;
|
|
144
144
|
/** Update selected_text to match anchored_text (opt-in per §6.2). */
|
|
145
145
|
updateText?: boolean;
|
|
146
|
+
/** Force-anchor: update commit to HEAD and clear audit fields for high-confidence results. */
|
|
147
|
+
force?: boolean;
|
|
146
148
|
}
|
|
147
149
|
export interface ValidateOptions extends BaseOptions {
|
|
148
150
|
/** Treat warnings as errors. */
|