@mrsf/cli 0.7.1 → 0.8.0

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.
@@ -9,7 +9,8 @@ export declare function normalizeCommentExtensions(extensions?: Record<string, u
9
9
  */
10
10
  export declare function addComment(doc: MrsfDocument, opts: AddCommentOptions, repoRoot?: string): Promise<Comment>;
11
11
  /**
12
- * Populate selected_text from document lines (reads the file content region).
12
+ * Populate selected_text from a zero-based array of document lines.
13
+ * Remove the unused first element from readDocumentLines before calling.
13
14
  * Should be called after addComment if the caller provides line info but not selected_text.
14
15
  */
15
16
  export declare function populateSelectedText(comment: Comment, documentLines: string[]): void;
@@ -91,7 +91,8 @@ export async function addComment(doc, opts, repoRoot) {
91
91
  return comment;
92
92
  }
93
93
  /**
94
- * Populate selected_text from document lines (reads the file content region).
94
+ * Populate selected_text from a zero-based array of document lines.
95
+ * Remove the unused first element from readDocumentLines before calling.
95
96
  * Should be called after addComment if the caller provides line info but not selected_text.
96
97
  */
97
98
  export function populateSelectedText(comment, documentLines) {
@@ -44,7 +44,7 @@ export async function reanchorDocument(doc, documentLines, opts = {}) {
44
44
  if (!opts.noGit && (await isGitAvailable())) {
45
45
  const repoRoot = opts.repoRoot ?? (await findRepoRoot(opts.cwd));
46
46
  if (repoRoot && opts.documentPath) {
47
- const relPath = path.relative(repoRoot, opts.documentPath);
47
+ const relPath = path.relative(repoRoot, opts.documentPath).split(path.sep).join("/");
48
48
  const head = await getCurrentCommit(repoRoot);
49
49
  // Use a shared fromCommit for all comments, or per-comment
50
50
  const globalFrom = opts.fromCommit;
@@ -18,7 +18,9 @@ export interface Comment {
18
18
  resolved: boolean;
19
19
  line?: number;
20
20
  end_line?: number;
21
+ /** Inclusive, zero-based UTF-16 code-unit offset within line. */
21
22
  start_column?: number;
23
+ /** Exclusive, zero-based UTF-16 code-unit offset within end_line or line. */
22
24
  end_column?: number;
23
25
  selected_text?: string;
24
26
  selected_text_hash?: string;
@@ -13,6 +13,7 @@
13
13
  import { readFile, writeFile, rename, unlink } from "node:fs/promises";
14
14
  import { existsSync } from "node:fs";
15
15
  import { createHash, randomBytes } from "node:crypto";
16
+ import { setTimeout as delay } from "node:timers/promises";
16
17
  import path from "node:path";
17
18
  import { Parser, CST, parse as yamlParse } from "yaml";
18
19
  import { toYaml, toJson } from "./serialize.js";
@@ -55,7 +56,20 @@ async function atomicWriteFile(filePath, content) {
55
56
  const tmp = filePath + "." + randomBytes(6).toString("hex") + ".tmp";
56
57
  try {
57
58
  await writeFile(tmp, content, "utf-8");
58
- await rename(tmp, filePath);
59
+ for (let attempt = 0;; attempt++) {
60
+ try {
61
+ await rename(tmp, filePath);
62
+ break;
63
+ }
64
+ catch (error) {
65
+ // Windows readers and antivirus scanners can briefly lock the destination.
66
+ if (attempt >= 5 || !(error instanceof Error) || !("code" in error)
67
+ || !["EPERM", "EACCES", "EBUSY"].some((code) => code === error.code)) {
68
+ throw error;
69
+ }
70
+ await delay(50 * (attempt + 1));
71
+ }
72
+ }
59
73
  }
60
74
  catch (err) {
61
75
  // Clean up temp file on failure
package/mrsf.schema.json CHANGED
@@ -1,113 +1,113 @@
1
-
2
- {
3
- "$schema": "https://json-schema.org/draft/2020-12/schema",
4
- "$id": "https://github.com/wictorwilen/MRSF/raw/main/mrsf.schema.json",
5
- "title": "Markdown Review Sidecar Format (MRSF) v1.0",
6
- "description": "Schema for MRSF review sidecar files. See MRSF-v1.0.md for the full specification.",
7
- "type": "object",
8
- "required": ["mrsf_version", "document", "comments"],
9
- "additionalProperties": true,
10
- "properties": {
11
- "mrsf_version": {
12
- "type": "string",
13
- "pattern": "^1\\.\\d+$",
14
- "description": "MRSF format version. MUST be a supported major.minor version (e.g., 1.0)."
15
- },
16
- "document": {
17
- "type": "string",
18
- "description": "Relative path to the Markdown document being reviewed."
19
- },
20
- "comments": {
21
- "type": "array",
22
- "items": {
23
- "type": "object",
24
- "required": ["id", "author", "timestamp", "text", "resolved"],
25
- "additionalProperties": true,
26
- "properties": {
27
- "id": {
28
- "type": "string",
29
- "description": "Globally unique, opaque, collision-resistant identifier for the comment."
30
- },
31
- "author": {
32
- "type": "string",
33
- "description": "Creator of the comment. SHOULD follow the convention 'Display Name (identifier)'."
34
- },
35
- "timestamp": {
36
- "type": "string",
37
- "format": "date-time",
38
- "description": "ISO 8601 / RFC 3339 timestamp of comment creation; SHOULD include timezone offset."
39
- },
40
- "text": {
41
- "type": "string",
42
- "maxLength": 16384,
43
- "description": "The content of the review comment. MUST be plain text."
44
- },
45
- "resolved": {
46
- "type": "boolean",
47
- "description": "Whether the comment has been resolved."
48
- },
49
- "commit": {
50
- "type": "string",
51
- "description": "Git commit hash associated with the comment. SHOULD be the full (long) SHA."
52
- },
53
- "type": {
54
- "type": "string",
55
- "description": "Categorization of the comment. Recommended values listed in examples.",
56
- "examples": [
57
- "suggestion",
58
- "issue",
59
- "question",
60
- "accuracy",
61
- "style",
62
- "clarity"
63
- ]
64
- },
65
- "severity": {
66
- "type": "string",
67
- "description": "Importance level of the comment.",
68
- "enum": ["low", "medium", "high"]
69
- },
70
- "reply_to": {
71
- "type": "string",
72
- "description": "ID of another comment in the same file that this comment replies to."
73
- },
74
- "line": {
75
- "type": "integer",
76
- "minimum": 1,
77
- "description": "Starting line number (1-based) in the target document."
78
- },
79
- "end_line": {
80
- "type": "integer",
81
- "minimum": 1,
82
- "description": "Ending line number (inclusive, 1-based). MUST be >= line."
83
- },
84
- "start_column": {
85
- "type": "integer",
86
- "minimum": 0,
87
- "description": "Starting column index (0-based) within the starting line."
88
- },
89
- "end_column": {
90
- "type": "integer",
91
- "minimum": 0,
92
- "description": "Ending column index. MUST be >= start_column when on the same line."
93
- },
94
- "selected_text": {
95
- "type": "string",
96
- "maxLength": 4096,
97
- "description": "Exact text selected by the reviewer. SHOULD NOT be modified by re-anchoring tools. SHOULD NOT exceed 4096 characters."
98
- },
99
- "anchored_text": {
100
- "type": "string",
101
- "maxLength": 4096,
102
- "description": "Text currently found at the resolved anchor position. Populated by re-anchoring tools when the document text differs from selected_text. SHOULD be omitted when identical to selected_text."
103
- },
104
- "selected_text_hash": {
105
- "type": "string",
106
- "pattern": "^[a-f0-9]{64}$",
107
- "description": "Hex-encoded SHA-256 hash of selected_text. Immutable after creation; used for fast exact-match detection during re-anchoring, integrity verification, and staleness checks. SHOULD NOT be modified by re-anchoring tools unless selected_text is also replaced (opt-in behaviour), in which case it MUST be recomputed."
108
- }
109
- }
110
- }
111
- }
112
- }
113
- }
1
+
2
+ {
3
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
4
+ "$id": "https://github.com/wictorwilen/MRSF/raw/main/mrsf.schema.json",
5
+ "title": "Markdown Review Sidecar Format (MRSF) v1.0",
6
+ "description": "Schema for MRSF review sidecar files. See MRSF-v1.0.md for the full specification.",
7
+ "type": "object",
8
+ "required": ["mrsf_version", "document", "comments"],
9
+ "additionalProperties": true,
10
+ "properties": {
11
+ "mrsf_version": {
12
+ "type": "string",
13
+ "pattern": "^1\\.\\d+$",
14
+ "description": "MRSF format version. MUST be a supported major.minor version (e.g., 1.0)."
15
+ },
16
+ "document": {
17
+ "type": "string",
18
+ "description": "Relative path to the Markdown document being reviewed."
19
+ },
20
+ "comments": {
21
+ "type": "array",
22
+ "items": {
23
+ "type": "object",
24
+ "required": ["id", "author", "timestamp", "text", "resolved"],
25
+ "additionalProperties": true,
26
+ "properties": {
27
+ "id": {
28
+ "type": "string",
29
+ "description": "Globally unique, opaque, collision-resistant identifier for the comment."
30
+ },
31
+ "author": {
32
+ "type": "string",
33
+ "description": "Creator of the comment. SHOULD follow the convention 'Display Name (identifier)'."
34
+ },
35
+ "timestamp": {
36
+ "type": "string",
37
+ "format": "date-time",
38
+ "description": "ISO 8601 / RFC 3339 timestamp of comment creation; SHOULD include timezone offset."
39
+ },
40
+ "text": {
41
+ "type": "string",
42
+ "maxLength": 16384,
43
+ "description": "The content of the review comment. MUST be plain text."
44
+ },
45
+ "resolved": {
46
+ "type": "boolean",
47
+ "description": "Whether the comment has been resolved."
48
+ },
49
+ "commit": {
50
+ "type": "string",
51
+ "description": "Git commit hash associated with the comment. SHOULD be the full (long) SHA."
52
+ },
53
+ "type": {
54
+ "type": "string",
55
+ "description": "Categorization of the comment. Recommended values listed in examples.",
56
+ "examples": [
57
+ "suggestion",
58
+ "issue",
59
+ "question",
60
+ "accuracy",
61
+ "style",
62
+ "clarity"
63
+ ]
64
+ },
65
+ "severity": {
66
+ "type": "string",
67
+ "description": "Importance level of the comment.",
68
+ "enum": ["low", "medium", "high"]
69
+ },
70
+ "reply_to": {
71
+ "type": "string",
72
+ "description": "ID of another comment in the same file that this comment replies to."
73
+ },
74
+ "line": {
75
+ "type": "integer",
76
+ "minimum": 1,
77
+ "description": "Starting line number (1-based) in the target document."
78
+ },
79
+ "end_line": {
80
+ "type": "integer",
81
+ "minimum": 1,
82
+ "description": "Ending line number (inclusive, 1-based). MUST be >= line."
83
+ },
84
+ "start_column": {
85
+ "type": "integer",
86
+ "minimum": 0,
87
+ "description": "Inclusive starting UTF-16 code-unit offset (0-based) within the starting line."
88
+ },
89
+ "end_column": {
90
+ "type": "integer",
91
+ "minimum": 0,
92
+ "description": "Exclusive ending UTF-16 code-unit offset (0-based) within the ending line. MUST be >= start_column when on the same line."
93
+ },
94
+ "selected_text": {
95
+ "type": "string",
96
+ "maxLength": 4096,
97
+ "description": "Exact text selected by the reviewer. SHOULD NOT be modified by re-anchoring tools. SHOULD NOT exceed 4096 characters."
98
+ },
99
+ "anchored_text": {
100
+ "type": "string",
101
+ "maxLength": 4096,
102
+ "description": "Text currently found at the resolved anchor position. Populated by re-anchoring tools when the document text differs from selected_text. SHOULD be omitted when identical to selected_text."
103
+ },
104
+ "selected_text_hash": {
105
+ "type": "string",
106
+ "pattern": "^[a-f0-9]{64}$",
107
+ "description": "Hex-encoded SHA-256 hash of selected_text. Immutable after creation; used for fast exact-match detection during re-anchoring, integrity verification, and staleness checks. SHOULD NOT be modified by re-anchoring tools unless selected_text is also replaced (opt-in behaviour), in which case it MUST be recomputed."
108
+ }
109
+ }
110
+ }
111
+ }
112
+ }
113
+ }
package/package.json CHANGED
@@ -1,95 +1,95 @@
1
- {
2
- "name": "@mrsf/cli",
3
- "version": "0.7.1",
4
- "description": "CLI and library for the Markdown Review Sidecar Format (MRSF)",
5
- "type": "module",
6
- "main": "./dist/index.js",
7
- "types": "./dist/index.d.ts",
8
- "exports": {
9
- ".": {
10
- "types": "./dist/index.d.ts",
11
- "import": "./dist/index.js",
12
- "require": "./dist/index.cjs"
13
- },
14
- "./browser": {
15
- "types": "./dist/browser.d.ts",
16
- "browser": "./dist/browser.js",
17
- "import": "./dist/browser.js",
18
- "require": "./dist/browser.cjs"
19
- }
20
- },
21
- "bin": {
22
- "mrsf": "./dist/bin.js"
23
- },
24
- "engines": {
25
- "node": ">=18"
26
- },
27
- "scripts": {
28
- "build": "tsc && node esbuild.config.mjs",
29
- "dev": "tsc --watch",
30
- "test": "vitest run",
31
- "test:watch": "vitest",
32
- "lint": "tsc --noEmit",
33
- "eval:reanchor": "tsc && node dist/evaluation/run-reanchor-eval.js",
34
- "eval:reanchor:baseline": "tsc && node dist/evaluation/run-reanchor-eval.js --baseline ../evaluation/reanchor/baseline.json",
35
- "eval:reanchor:generate": "tsc && node dist/evaluation/run-reanchor-generator.js",
36
- "eval:reanchor:profile": "tsc && node dist/evaluation/run-reanchor-profile.js",
37
- "prepublishOnly": "cp ../LICENSE . && npm run build && npm test",
38
- "postpublish": "rm -f LICENSE"
39
- },
40
- "keywords": [
41
- "markdown",
42
- "review",
43
- "sidecar",
44
- "mrsf",
45
- "sidemark",
46
- "anchoring",
47
- "comments",
48
- "cli",
49
- "yaml",
50
- "validation",
51
- "reanchor"
52
- ],
53
- "license": "MIT",
54
- "author": "Wictor Wilén (https://github.com/wictorwilen)",
55
- "homepage": "https://github.com/wictorwilen/MRSF/tree/main/cli#readme",
56
- "bugs": {
57
- "url": "https://github.com/wictorwilen/MRSF/issues"
58
- },
59
- "repository": {
60
- "type": "git",
61
- "url": "https://github.com/wictorwilen/MRSF.git",
62
- "directory": "cli"
63
- },
64
- "files": [
65
- "dist/**/*.js",
66
- "dist/**/*.d.ts",
67
- "dist/**/*.cjs",
68
- "dist/**/*.cjs.map",
69
- "mrsf.schema.json",
70
- "README.md",
71
- "LICENSE"
72
- ],
73
- "dependencies": {
74
- "ajv": "^8.17.1",
75
- "ajv-formats": "^3.0.1",
76
- "chalk": "^5.4.1",
77
- "chokidar": "^4.0.3",
78
- "commander": "^13.1.0",
79
- "fastest-levenshtein": "^1.0.16",
80
- "js-yaml": "^4.1.0",
81
- "uuid": "^11.1.0",
82
- "yaml": "^2.8.2"
83
- },
84
- "devDependencies": {
85
- "@types/js-yaml": "^4.0.9",
86
- "@types/node": "^22.13.10",
87
- "@types/uuid": "^10.0.0",
88
- "@vitest/coverage-v8": "^4.0.18",
89
- "typescript": "^5.8.2",
90
- "vitest": "^4.0.18",
91
- "esbuild": "^0.27.3"
92
- },
93
- "module": "./dist/index.js",
94
- "sideEffects": false
95
- }
1
+ {
2
+ "name": "@mrsf/cli",
3
+ "version": "0.8.0",
4
+ "description": "CLI and library for the Markdown Review Sidecar Format (MRSF)",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "require": "./dist/index.cjs"
13
+ },
14
+ "./browser": {
15
+ "types": "./dist/browser.d.ts",
16
+ "browser": "./dist/browser.js",
17
+ "import": "./dist/browser.js",
18
+ "require": "./dist/browser.cjs"
19
+ }
20
+ },
21
+ "bin": {
22
+ "mrsf": "./dist/bin.js"
23
+ },
24
+ "engines": {
25
+ "node": ">=18"
26
+ },
27
+ "scripts": {
28
+ "build": "tsc && node esbuild.config.mjs",
29
+ "dev": "tsc --watch",
30
+ "test": "vitest run",
31
+ "test:watch": "vitest",
32
+ "lint": "tsc --noEmit",
33
+ "eval:reanchor": "tsc && node dist/evaluation/run-reanchor-eval.js",
34
+ "eval:reanchor:baseline": "tsc && node dist/evaluation/run-reanchor-eval.js --baseline ../evaluation/reanchor/baseline.json",
35
+ "eval:reanchor:generate": "tsc && node dist/evaluation/run-reanchor-generator.js",
36
+ "eval:reanchor:profile": "tsc && node dist/evaluation/run-reanchor-profile.js",
37
+ "prepublishOnly": "cp ../LICENSE . && npm run build && npm test",
38
+ "postpublish": "rm -f LICENSE"
39
+ },
40
+ "keywords": [
41
+ "markdown",
42
+ "review",
43
+ "sidecar",
44
+ "mrsf",
45
+ "sidemark",
46
+ "anchoring",
47
+ "comments",
48
+ "cli",
49
+ "yaml",
50
+ "validation",
51
+ "reanchor"
52
+ ],
53
+ "license": "MIT",
54
+ "author": "Wictor Wilén (https://github.com/wictorwilen)",
55
+ "homepage": "https://github.com/wictorwilen/MRSF/tree/main/cli#readme",
56
+ "bugs": {
57
+ "url": "https://github.com/wictorwilen/MRSF/issues"
58
+ },
59
+ "repository": {
60
+ "type": "git",
61
+ "url": "https://github.com/wictorwilen/MRSF.git",
62
+ "directory": "cli"
63
+ },
64
+ "files": [
65
+ "dist/**/*.js",
66
+ "dist/**/*.d.ts",
67
+ "dist/**/*.cjs",
68
+ "dist/**/*.cjs.map",
69
+ "mrsf.schema.json",
70
+ "README.md",
71
+ "LICENSE"
72
+ ],
73
+ "dependencies": {
74
+ "ajv": "^8.17.1",
75
+ "ajv-formats": "^3.0.1",
76
+ "chalk": "^5.4.1",
77
+ "chokidar": "^4.0.3",
78
+ "commander": "^13.1.0",
79
+ "fastest-levenshtein": "^1.0.16",
80
+ "js-yaml": "^4.1.0",
81
+ "uuid": "^11.1.0",
82
+ "yaml": "^2.8.2"
83
+ },
84
+ "devDependencies": {
85
+ "@types/js-yaml": "^4.0.9",
86
+ "@types/node": "^22.13.10",
87
+ "@types/uuid": "^10.0.0",
88
+ "@vitest/coverage-v8": "^4.0.18",
89
+ "typescript": "^5.8.2",
90
+ "vitest": "^4.0.18",
91
+ "esbuild": "^0.27.3"
92
+ },
93
+ "module": "./dist/index.js",
94
+ "sideEffects": false
95
+ }