@mrsf/cli 0.4.3 → 0.6.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.
@@ -1,15 +1,11 @@
1
1
  /**
2
2
  * MRSF Validator — JSON Schema + cross-field validation per §10.
3
3
  */
4
- import AjvModule from "ajv";
5
- import addFormatsModule from "ajv-formats";
6
- // ESM interop: handle both default and namespace imports
7
- const Ajv = AjvModule.default ?? AjvModule;
8
- const addFormats = addFormatsModule.default ?? addFormatsModule;
9
4
  import { readFile } from "node:fs/promises";
10
5
  import path from "node:path";
11
6
  import { fileURLToPath } from "node:url";
12
7
  import { computeHash } from "./writer.js";
8
+ import { validateCrossFields, validateSchema } from "./validate-core.js";
13
9
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
14
10
  let _schemaCache = null;
15
11
  async function loadSchema() {
@@ -44,114 +40,9 @@ export async function validate(doc, options = {}) {
44
40
  const warnings = [];
45
41
  // ── JSON Schema validation ──
46
42
  const rawSchema = await loadSchema();
47
- // Ajv 8 doesn't natively support 2020-12; strip $schema to use draft-07 mode
48
- const { $schema, ...schema } = rawSchema;
49
- const ajv = new Ajv({ allErrors: true, strict: false });
50
- addFormats(ajv);
51
- const ajvValidate = ajv.compile(schema);
52
- const schemaValid = ajvValidate(doc);
53
- if (!schemaValid && ajvValidate.errors) {
54
- for (const err of ajvValidate.errors) {
55
- errors.push({
56
- severity: "error",
57
- message: `${err.instancePath || "/"}: ${err.message ?? "schema error"}`,
58
- path: err.instancePath || "/",
59
- });
60
- }
61
- }
43
+ validateSchema(doc, rawSchema, errors);
62
44
  // ── Cross-field validation (§10) ──
63
- if (Array.isArray(doc.comments)) {
64
- const ids = new Set();
65
- for (let i = 0; i < doc.comments.length; i++) {
66
- const c = doc.comments[i];
67
- const prefix = `/comments/${i}`;
68
- // Unique id check
69
- if (c.id) {
70
- if (ids.has(c.id)) {
71
- errors.push({
72
- severity: "error",
73
- message: `Duplicate comment id "${c.id}"`,
74
- path: `${prefix}/id`,
75
- commentId: c.id,
76
- });
77
- }
78
- ids.add(c.id);
79
- }
80
- // end_line ≥ line
81
- if (c.line != null && c.end_line != null && c.end_line < c.line) {
82
- errors.push({
83
- severity: "error",
84
- message: `end_line (${c.end_line}) must be ≥ line (${c.line})`,
85
- path: `${prefix}/end_line`,
86
- commentId: c.id,
87
- });
88
- }
89
- // end_column ≥ start_column when same line
90
- if (c.start_column != null &&
91
- c.end_column != null &&
92
- (c.line == null || c.end_line == null || c.line === c.end_line) &&
93
- c.end_column < c.start_column) {
94
- errors.push({
95
- severity: "error",
96
- message: `end_column (${c.end_column}) must be ≥ start_column (${c.start_column}) on the same line`,
97
- path: `${prefix}/end_column`,
98
- commentId: c.id,
99
- });
100
- }
101
- // selected_text length
102
- if (c.selected_text && c.selected_text.length > 4096) {
103
- errors.push({
104
- severity: "error",
105
- message: `selected_text exceeds 4096 characters (${c.selected_text.length})`,
106
- path: `${prefix}/selected_text`,
107
- commentId: c.id,
108
- });
109
- }
110
- // text length
111
- if (c.text && c.text.length > 16384) {
112
- warnings.push({
113
- severity: "warning",
114
- message: `text exceeds recommended 16384 characters (${c.text.length})`,
115
- path: `${prefix}/text`,
116
- commentId: c.id,
117
- });
118
- }
119
- // selected_text_hash consistency
120
- if (c.selected_text && c.selected_text_hash) {
121
- const expected = computeHash(c.selected_text);
122
- if (c.selected_text_hash !== expected) {
123
- warnings.push({
124
- severity: "warning",
125
- message: `selected_text_hash mismatch (expected ${expected.slice(0, 12)}…, got ${c.selected_text_hash.slice(0, 12)}…)`,
126
- path: `${prefix}/selected_text_hash`,
127
- commentId: c.id,
128
- });
129
- }
130
- }
131
- // reply_to resolution
132
- if (c.reply_to && !ids.has(c.reply_to)) {
133
- // Check forward references too
134
- const allIds = doc.comments.map((x) => x.id);
135
- if (!allIds.includes(c.reply_to)) {
136
- warnings.push({
137
- severity: "warning",
138
- message: `reply_to "${c.reply_to}" does not resolve to any comment id in this file`,
139
- path: `${prefix}/reply_to`,
140
- commentId: c.id,
141
- });
142
- }
143
- }
144
- // Missing selected_text warning
145
- if (c.line != null && !c.selected_text) {
146
- warnings.push({
147
- severity: "warning",
148
- message: `Comment has line anchors but no selected_text — anchoring will be fragile across edits`,
149
- path: `${prefix}/selected_text`,
150
- commentId: c.id,
151
- });
152
- }
153
- }
154
- }
45
+ validateCrossFields(doc, errors, warnings, computeHash);
155
46
  const valid = errors.length === 0 && (!options.strict || warnings.length === 0);
156
47
  return { valid, errors, warnings };
157
48
  }
@@ -170,6 +61,7 @@ export async function validateFile(filePath, options = {}) {
170
61
  errors: [
171
62
  {
172
63
  severity: "error",
64
+ code: "parse-error",
173
65
  message: `Failed to parse: ${e.message}`,
174
66
  },
175
67
  ],
@@ -21,14 +21,7 @@ export declare function computeHash(text: string): string;
21
21
  * If selected_text is absent, removes the hash.
22
22
  */
23
23
  export declare function syncHash(comment: Comment): Comment;
24
- /**
25
- * Serialize an MrsfDocument to YAML (for new files / non-round-trip use).
26
- */
27
- export declare function toYaml(doc: MrsfDocument): string;
28
- /**
29
- * Serialize an MrsfDocument to JSON.
30
- */
31
- export declare function toJson(doc: MrsfDocument): string;
24
+ export { toYaml, toJson } from "./serialize.js";
32
25
  /**
33
26
  * Write an MrsfDocument to disk.
34
27
  *
@@ -14,7 +14,8 @@ 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
16
  import path from "node:path";
17
- import { Document, Parser, CST, parse as yamlParse } from "yaml";
17
+ import { Parser, CST, parse as yamlParse } from "yaml";
18
+ import { toYaml, toJson } from "./serialize.js";
18
19
  /* ------------------------------------------------------------------ */
19
20
  /* Per-file write serialization */
20
21
  /* ------------------------------------------------------------------ */
@@ -327,19 +328,9 @@ function deepEqual(a, b) {
327
328
  /* ------------------------------------------------------------------ */
328
329
  /* Public API */
329
330
  /* ------------------------------------------------------------------ */
330
- /**
331
- * Serialize an MrsfDocument to YAML (for new files / non-round-trip use).
332
- */
333
- export function toYaml(doc) {
334
- const yamlDoc = new Document(doc);
335
- return yamlDoc.toString({ lineWidth: 0 });
336
- }
337
- /**
338
- * Serialize an MrsfDocument to JSON.
339
- */
340
- export function toJson(doc) {
341
- return JSON.stringify(doc, null, 2) + "\n";
342
- }
331
+ // `toYaml` / `toJson` now live in the Node-free serialize module; re-exported
332
+ // here so existing `@mrsf/cli` import paths keep working.
333
+ export { toYaml, toJson } from "./serialize.js";
343
334
  /**
344
335
  * Write an MrsfDocument to disk.
345
336
  *
package/package.json CHANGED
@@ -1,18 +1,21 @@
1
1
  {
2
2
  "name": "@mrsf/cli",
3
- "version": "0.4.3",
3
+ "version": "0.6.0",
4
4
  "description": "CLI and library for the Markdown Review Sidecar Format (MRSF)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
8
8
  "exports": {
9
9
  ".": {
10
+ "types": "./dist/index.d.ts",
10
11
  "import": "./dist/index.js",
11
- "types": "./dist/index.d.ts"
12
+ "require": "./dist/index.cjs"
12
13
  },
13
14
  "./browser": {
15
+ "types": "./dist/browser.d.ts",
16
+ "browser": "./dist/browser.js",
14
17
  "import": "./dist/browser.js",
15
- "types": "./dist/browser.d.ts"
18
+ "require": "./dist/browser.cjs"
16
19
  }
17
20
  },
18
21
  "bin": {
@@ -22,7 +25,7 @@
22
25
  "node": ">=18"
23
26
  },
24
27
  "scripts": {
25
- "build": "tsc",
28
+ "build": "tsc && node esbuild.config.mjs",
26
29
  "dev": "tsc --watch",
27
30
  "test": "vitest run",
28
31
  "test:watch": "vitest",
@@ -57,6 +60,8 @@
57
60
  "files": [
58
61
  "dist/**/*.js",
59
62
  "dist/**/*.d.ts",
63
+ "dist/**/*.cjs",
64
+ "dist/**/*.cjs.map",
60
65
  "mrsf.schema.json",
61
66
  "README.md",
62
67
  "LICENSE"
@@ -78,6 +83,9 @@
78
83
  "@types/uuid": "^10.0.0",
79
84
  "@vitest/coverage-v8": "^4.0.18",
80
85
  "typescript": "^5.8.2",
81
- "vitest": "^4.0.18"
82
- }
86
+ "vitest": "^4.0.18",
87
+ "esbuild": "^0.27.3"
88
+ },
89
+ "module": "./dist/index.js",
90
+ "sideEffects": false
83
91
  }