@mrsf/cli 0.3.0 → 0.3.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/bin.js CHANGED
@@ -49,7 +49,7 @@ function showBanner() {
49
49
  ${chalk.cyan.bold("╔╦╗")}${chalk.blue.bold("╦═╗")}${chalk.magenta.bold("╔═╗")}${chalk.yellow.bold("╔═╗")}
50
50
  ${chalk.cyan.bold("║║║")}${chalk.blue.bold("╠╦╝")}${chalk.magenta.bold("╚═╗")}${chalk.yellow.bold("╠╣")}
51
51
  ${chalk.cyan.bold("╩ ╩")}${chalk.blue.bold("╩╚═")}${chalk.magenta.bold("╚═╝")}${chalk.yellow.bold("╚")}
52
- ${chalk.dim("Markdown Review Sidecar Format")} ${chalk.dim.italic(`v${version}`)}
52
+ ${chalk.dim("Markdown Review Sidecar Format")} ${chalk.dim.italic(`v${version} (Node.js)`)}
53
53
  ${chalk.dim.underline(repo)}
54
54
  `;
55
55
  process.stderr.write(banner + "\n");
@@ -59,8 +59,8 @@ showBanner();
59
59
  const program = new Command();
60
60
  program
61
61
  .name("mrsf")
62
- .description("Markdown Review Sidecar Format — CLI & toolkit")
63
- .version(version)
62
+ .description("Markdown Review Sidecar Format — CLI & toolkit (Node.js)")
63
+ .version(`${version} (Node.js)`)
64
64
  .option("--cwd <dir>", "Working directory")
65
65
  .option("--config <path>", "Path to .mrsf.yaml")
66
66
  .option("--no-color", "Disable colour output")
@@ -42,11 +42,11 @@ export function registerValidate(program) {
42
42
  console.log(`${icon} ${sp}`);
43
43
  }
44
44
  for (const e of result.errors) {
45
- console.log(chalk.red(` error: ${e.message}${e.path ? ` (${e.path})` : ""}`));
45
+ console.log(chalk.red(` ERROR: ${e.message}${e.path ? ` (${e.path})` : ""}`));
46
46
  }
47
47
  for (const w of result.warnings) {
48
48
  const fn = opts.strict ? chalk.red : chalk.yellow;
49
- console.log(fn(` ${opts.strict ? "error" : "warn"}: ${w.message}${w.path ? ` (${w.path})` : ""}`));
49
+ console.log(fn(` ${opts.strict ? "ERROR" : "WARN"}: ${w.message}${w.path ? ` (${w.path})` : ""}`));
50
50
  }
51
51
  if (verbose) {
52
52
  console.log(chalk.dim(` ${result.errors.length} error(s), ${result.warnings.length} warning(s)`));
@@ -66,11 +66,11 @@ function printValidateResults(label, result, strict) {
66
66
  const icon = effectiveValid ? chalk.green("✓") : chalk.red("✗");
67
67
  console.log(` ${chalk.cyan("validate")} ${icon} ${label}`);
68
68
  for (const e of result.errors) {
69
- console.log(chalk.red(` error: ${e.message}${e.path ? ` (${e.path})` : ""}`));
69
+ console.log(chalk.red(` ERROR: ${e.message}${e.path ? ` (${e.path})` : ""}`));
70
70
  }
71
71
  for (const w of result.warnings) {
72
72
  const fn = strict ? chalk.red : chalk.yellow;
73
- const tag = strict ? "error" : "warn";
73
+ const tag = strict ? "ERROR" : "WARN";
74
74
  console.log(fn(` ${tag}: ${w.message}${w.path ? ` (${w.path})` : ""}`));
75
75
  }
76
76
  }
@@ -38,7 +38,7 @@ export async function loadConfig(workspaceRoot, configPath) {
38
38
  if (!existsSync(cfgPath))
39
39
  return null;
40
40
  const raw = await readFile(cfgPath, "utf-8");
41
- const parsed = yaml.load(raw);
41
+ const parsed = yaml.load(raw, { schema: yaml.JSON_SCHEMA });
42
42
  if (!parsed || typeof parsed !== "object")
43
43
  return null;
44
44
  const config = {};
@@ -32,7 +32,7 @@ export function parseSidecarContent(content, filenameHint) {
32
32
  }
33
33
  else {
34
34
  try {
35
- parsed = yaml.load(trimmed);
35
+ parsed = yaml.load(trimmed, { schema: yaml.JSON_SCHEMA });
36
36
  }
37
37
  catch (e) {
38
38
  throw new Error(`Failed to parse YAML: ${e.message}`);
@@ -74,7 +74,7 @@ export function parseSidecarContentLenient(content, filenameHint) {
74
74
  // First, try a normal parse
75
75
  let parsed;
76
76
  try {
77
- parsed = isJson ? JSON.parse(trimmed) : yaml.load(trimmed);
77
+ parsed = isJson ? JSON.parse(trimmed) : yaml.load(trimmed, { schema: yaml.JSON_SCHEMA });
78
78
  }
79
79
  catch (e) {
80
80
  // Total parse failure — try to salvage what we can from YAML
@@ -143,7 +143,7 @@ function salvageYaml(content) {
143
143
  continue;
144
144
  // Wrap in a minimal YAML array context and try to parse
145
145
  try {
146
- const parsed = yaml.load(trimmed);
146
+ const parsed = yaml.load(trimmed, { schema: yaml.JSON_SCHEMA });
147
147
  if (Array.isArray(parsed) && parsed.length > 0) {
148
148
  const c = parsed[0];
149
149
  if (c && typeof c === "object" && typeof c.id === "string") {
@@ -16,8 +16,11 @@ async function loadSchema() {
16
16
  if (_schemaCache)
17
17
  return _schemaCache;
18
18
  // The schema lives at the repo root, two levels up from dist/lib/
19
+ // When installed as a package, it's at the package root (cli/)
19
20
  const candidates = [
20
- path.resolve(__dirname, "../../../mrsf.schema.json"), // from dist/lib/
21
+ path.resolve(__dirname, "mrsf.schema.json"), // same dir (esbuild bundle, e.g. MCP server)
22
+ path.resolve(__dirname, "../../mrsf.schema.json"), // from dist/lib/ → cli/ (installed package)
23
+ path.resolve(__dirname, "../../../mrsf.schema.json"), // from dist/lib/ → repo root (dev)
21
24
  path.resolve(__dirname, "../../../../mrsf.schema.json"), // fallback
22
25
  path.resolve(process.cwd(), "mrsf.schema.json"), // cwd fallback
23
26
  ];
@@ -107,6 +107,8 @@ const COMMENT_KEY_ORDER = [
107
107
  * double-quoted via JSON.stringify, which is always valid YAML.
108
108
  */
109
109
  function valueToSource(v) {
110
+ if (v instanceof Date)
111
+ return JSON.stringify(v.toISOString());
110
112
  if (typeof v === "number" || typeof v === "boolean")
111
113
  return String(v);
112
114
  if (typeof v === "string") {
@@ -0,0 +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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrsf/cli",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "CLI and library for the Markdown Review Sidecar Format (MRSF)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -31,8 +31,13 @@
31
31
  "review",
32
32
  "sidecar",
33
33
  "mrsf",
34
+ "sidemark",
34
35
  "anchoring",
35
- "comments"
36
+ "comments",
37
+ "cli",
38
+ "yaml",
39
+ "validation",
40
+ "reanchor"
36
41
  ],
37
42
  "license": "MIT",
38
43
  "author": "Wictor Wilén (https://github.com/wictorwilen)",
@@ -48,6 +53,7 @@
48
53
  "files": [
49
54
  "dist/**/*.js",
50
55
  "dist/**/*.d.ts",
56
+ "mrsf.schema.json",
51
57
  "README.md",
52
58
  "LICENSE"
53
59
  ],
@@ -66,6 +72,7 @@
66
72
  "@types/js-yaml": "^4.0.9",
67
73
  "@types/node": "^22.13.10",
68
74
  "@types/uuid": "^10.0.0",
75
+ "@vitest/coverage-v8": "^3.2.4",
69
76
  "typescript": "^5.8.2",
70
77
  "vitest": "^3.0.9"
71
78
  }