@mrsf/cli 0.1.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.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +346 -0
  3. package/dist/bin.d.ts +8 -0
  4. package/dist/bin.js +76 -0
  5. package/dist/commands/add.d.ts +6 -0
  6. package/dist/commands/add.js +82 -0
  7. package/dist/commands/init.d.ts +6 -0
  8. package/dist/commands/init.js +52 -0
  9. package/dist/commands/list.d.ts +6 -0
  10. package/dist/commands/list.js +95 -0
  11. package/dist/commands/reanchor.d.ts +6 -0
  12. package/dist/commands/reanchor.js +127 -0
  13. package/dist/commands/rename.d.ts +6 -0
  14. package/dist/commands/rename.js +53 -0
  15. package/dist/commands/resolve.d.ts +6 -0
  16. package/dist/commands/resolve.js +35 -0
  17. package/dist/commands/status.d.ts +6 -0
  18. package/dist/commands/status.js +156 -0
  19. package/dist/commands/validate.d.ts +6 -0
  20. package/dist/commands/validate.js +55 -0
  21. package/dist/index.d.ts +18 -0
  22. package/dist/index.js +25 -0
  23. package/dist/lib/comments.d.ts +52 -0
  24. package/dist/lib/comments.js +228 -0
  25. package/dist/lib/discovery.d.ts +40 -0
  26. package/dist/lib/discovery.js +125 -0
  27. package/dist/lib/fuzzy.d.ts +39 -0
  28. package/dist/lib/fuzzy.js +277 -0
  29. package/dist/lib/git.d.ts +68 -0
  30. package/dist/lib/git.js +246 -0
  31. package/dist/lib/parser.d.ts +19 -0
  32. package/dist/lib/parser.js +56 -0
  33. package/dist/lib/reanchor.d.ts +45 -0
  34. package/dist/lib/reanchor.js +389 -0
  35. package/dist/lib/resolve-files.d.ts +18 -0
  36. package/dist/lib/resolve-files.js +42 -0
  37. package/dist/lib/types.d.ts +151 -0
  38. package/dist/lib/types.js +5 -0
  39. package/dist/lib/validator.d.ts +13 -0
  40. package/dist/lib/validator.js +177 -0
  41. package/dist/lib/writer.d.ts +30 -0
  42. package/dist/lib/writer.js +62 -0
  43. package/package.json +70 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Wictor Wilén
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,346 @@
1
+ # MRSF CLI
2
+
3
+ Command-line tool and Node.js library for the **Markdown Review Sidecar Format** ([MRSF v1.0](../MRSF-v1.0.md)).
4
+
5
+ Validate sidecars, add and resolve review comments, re-anchor comments after document edits, and integrate MRSF into CI/CD pipelines — all from the terminal.
6
+
7
+ ```
8
+ ╔╦╗╦═╗╔═╗╔═╗
9
+ ║║║╠╦╝╚═╗╠╣
10
+ ╩ ╩╩╚═╚═╝╚
11
+ Markdown Review Sidecar Format
12
+ ```
13
+
14
+ ## Quick Start
15
+
16
+ ```bash
17
+ # Install globally
18
+ npm install -g @mrsf/cli
19
+
20
+ # …or run without installing
21
+ npx @mrsf/cli --help
22
+ ```
23
+
24
+ ### From source
25
+
26
+ ```bash
27
+ git clone https://github.com/wictorwilen/MRSF.git
28
+ cd MRSF/cli
29
+ npm install
30
+ npm run build
31
+ node dist/bin.js --help
32
+ ```
33
+
34
+ ## Commands
35
+
36
+ ### `mrsf validate [files...]`
37
+
38
+ Validate sidecar files against the JSON Schema and MRSF spec rules.
39
+
40
+ ```bash
41
+ # Validate a single sidecar
42
+ mrsf validate docs/architecture.md.review.yaml
43
+
44
+ # Validate all sidecars discovered in the workspace
45
+ mrsf validate
46
+
47
+ # Treat warnings as errors (useful for CI)
48
+ mrsf validate --strict
49
+ ```
50
+
51
+ | Option | Description |
52
+ |---|---|
53
+ | `-s, --strict` | Treat warnings as errors (exit non-zero) |
54
+
55
+ ---
56
+
57
+ ### `mrsf add <document>`
58
+
59
+ Add a review comment to a document's sidecar file. Creates the sidecar if it doesn't exist. Automatically populates `selected_text` from the document when a line is specified, and stamps the current git commit.
60
+
61
+ ```bash
62
+ # Add a line comment
63
+ mrsf add docs/api.md \
64
+ --author "alice" \
65
+ --text "Clarify the rate limit behavior" \
66
+ --line 42
67
+
68
+ # Add a column-span comment with type and severity
69
+ mrsf add docs/api.md \
70
+ --author "bob" \
71
+ --text "Typo: should be 'response'" \
72
+ --line 18 --start-column 10 --end-column 18 \
73
+ --type suggestion --severity low
74
+
75
+ # Reply to an existing comment
76
+ mrsf add docs/api.md \
77
+ --author "alice" \
78
+ --text "Good catch, fixed in abc123" \
79
+ --reply-to 3eeccbd3
80
+ ```
81
+
82
+ | Option | Description |
83
+ |---|---|
84
+ | `-a, --author <name>` | Comment author (required) |
85
+ | `-t, --text <text>` | Comment body (required) |
86
+ | `-l, --line <n>` | Line number (1-based) |
87
+ | `--end-line <n>` | End line (inclusive) |
88
+ | `--start-column <n>` | Start column (0-based) |
89
+ | `--end-column <n>` | End column (0-based) |
90
+ | `--type <type>` | Category: `suggestion`, `issue`, `question`, `accuracy`, `style`, `clarity` |
91
+ | `--severity <level>` | Importance: `low`, `medium`, `high` |
92
+ | `--reply-to <id>` | Reply to an existing comment by ID |
93
+ | `--selected-text <text>` | Override auto-detected selected text |
94
+
95
+ ---
96
+
97
+ ### `mrsf resolve <sidecar> <id>`
98
+
99
+ Resolve (or unresolve) a comment by ID.
100
+
101
+ ```bash
102
+ # Resolve a comment
103
+ mrsf resolve docs/api.md.review.yaml 1d3c72b0
104
+
105
+ # Unresolve it
106
+ mrsf resolve --undo docs/api.md.review.yaml 1d3c72b0
107
+
108
+ # Resolve and cascade to all direct replies
109
+ mrsf resolve --cascade docs/api.md.review.yaml 1d3c72b0
110
+ ```
111
+
112
+ | Option | Description |
113
+ |---|---|
114
+ | `--cascade` | Also resolve direct replies |
115
+ | `-u, --undo` | Unresolve instead of resolving |
116
+
117
+ ---
118
+
119
+ ### `mrsf list [files...]`
120
+
121
+ List and filter comments across one or more sidecar files.
122
+
123
+ ```bash
124
+ # List all open comments
125
+ mrsf list --open docs/api.md.review.yaml
126
+
127
+ # Filter by author and severity
128
+ mrsf list --author alice --severity high
129
+
130
+ # Summary view (counts, types, severities)
131
+ mrsf list --summary
132
+
133
+ # Machine-readable JSON output
134
+ mrsf list --json --open
135
+ ```
136
+
137
+ | Option | Description |
138
+ |---|---|
139
+ | `--open` | Show only open (unresolved) comments |
140
+ | `--resolved` | Show only resolved comments |
141
+ | `--orphaned` | Show only orphaned comments |
142
+ | `--author <name>` | Filter by author |
143
+ | `--type <type>` | Filter by comment type |
144
+ | `--severity <level>` | Filter by severity |
145
+ | `--summary` | Show aggregate summary instead of individual comments |
146
+ | `--json` | Output as JSON |
147
+
148
+ ---
149
+
150
+ ### `mrsf reanchor [files...]`
151
+
152
+ Re-anchor comments after the source document has been edited. Uses a multi-step resolution algorithm (§7.4):
153
+
154
+ 1. **Diff-based shift** — uses `git diff` to calculate line offsets
155
+ 2. **Exact text match** — searches for `selected_text` verbatim
156
+ 3. **Fuzzy match** — token-level LCS + Levenshtein similarity
157
+ 4. **Line/column fallback** — checks whether original position is still plausible
158
+ 5. **Orphan** — retains unresolvable comments and marks them for review
159
+
160
+ ```bash
161
+ # Dry run — see what would change without writing
162
+ mrsf reanchor --dry-run docs/api.md.review.yaml
163
+
164
+ # Re-anchor with a higher fuzzy threshold (stricter matching)
165
+ mrsf reanchor --threshold 0.8
166
+
167
+ # Only process sidecars for staged files (pre-commit hook)
168
+ mrsf reanchor --staged
169
+
170
+ # Disable git (pure text-based matching only)
171
+ mrsf reanchor --no-git
172
+
173
+ # Override the from-commit for all comments
174
+ mrsf reanchor --from abc1234
175
+
176
+ # Also update selected_text to match the current document text (opt-in)
177
+ mrsf reanchor --update-text
178
+ ```
179
+
180
+ | Option | Description | Default |
181
+ |---|---|---|
182
+ | `-n, --dry-run` | Report without modifying files | `false` |
183
+ | `-t, --threshold <n>` | Fuzzy match threshold (0.0–1.0) | `0.6` |
184
+ | `--staged` | Only process sidecars for git-staged documents | `false` |
185
+ | `--no-git` | Disable git integration entirely | git enabled |
186
+ | `--from <commit>` | Override from-commit for diff calculation | per-comment `commit` field |
187
+ | `--update-text` | Also replace `selected_text` with current document text | `false` |
188
+
189
+ > **Text preservation (§6.2):** By default, re-anchoring preserves the
190
+ > original `selected_text` and records the current document text in
191
+ > `anchored_text`. Use `--update-text` to opt in to overwriting
192
+ > `selected_text` directly.
193
+
194
+ ---
195
+
196
+ ### `mrsf status [files...]`
197
+
198
+ Assess anchor health for each comment.
199
+
200
+ ```bash
201
+ mrsf status docs/api.md.review.yaml
202
+ mrsf status --json
203
+ ```
204
+
205
+ Health states:
206
+
207
+ | State | Meaning |
208
+ |---|---|
209
+ | `fresh` | `selected_text` matches at the recorded position |
210
+ | `stale` | `selected_text` matches but at a different position, or commit differs |
211
+ | `orphaned` | `selected_text` not found in the document |
212
+ | `unknown` | No `selected_text` to verify |
213
+
214
+ | Option | Description |
215
+ |---|---|
216
+ | `--json` | Output as JSON |
217
+
218
+ ---
219
+
220
+ ### `mrsf init <document>`
221
+
222
+ Create an empty sidecar file for a Markdown document.
223
+
224
+ ```bash
225
+ mrsf init docs/new-feature.md
226
+ mrsf init --force docs/existing.md # overwrite
227
+ ```
228
+
229
+ | Option | Description |
230
+ |---|---|
231
+ | `-f, --force` | Overwrite an existing sidecar |
232
+
233
+ ---
234
+
235
+ ### `mrsf rename <old-document> <new-document>`
236
+
237
+ Update a sidecar after its source document has been renamed or moved.
238
+
239
+ ```bash
240
+ mrsf rename docs/old-name.md docs/new-name.md
241
+ ```
242
+
243
+ Updates the `document` field inside the sidecar, writes it to the new path, and removes the old sidecar file.
244
+
245
+ ## Global Options
246
+
247
+ These apply to all commands:
248
+
249
+ | Option | Description |
250
+ |---|---|
251
+ | `--cwd <dir>` | Override the working directory |
252
+ | `--config <path>` | Path to `.mrsf.yaml` configuration |
253
+ | `--no-color` | Disable coloured output |
254
+ | `-q, --quiet` | Suppress informational output |
255
+ | `-V, --version` | Print version |
256
+ | `-h, --help` | Show help |
257
+
258
+ ## CI/CD Integration
259
+
260
+ ### GitHub Actions
261
+
262
+ ```yaml
263
+ name: MRSF Review Lint
264
+ on: [pull_request]
265
+ jobs:
266
+ mrsf:
267
+ runs-on: ubuntu-latest
268
+ steps:
269
+ - uses: actions/checkout@v4
270
+ - uses: actions/setup-node@v4
271
+ with:
272
+ node-version: 20
273
+ - run: npx @mrsf/cli validate --strict
274
+ - run: npx @mrsf/cli reanchor --staged --dry-run
275
+ ```
276
+
277
+ ### Git Pre-commit Hook
278
+
279
+ ```bash
280
+ #!/usr/bin/env bash
281
+ # .git/hooks/pre-commit
282
+ npx @mrsf/cli reanchor --staged
283
+ npx @mrsf/cli validate --strict
284
+ ```
285
+
286
+ ### Git Post-merge Hook
287
+
288
+ ```bash
289
+ #!/usr/bin/env bash
290
+ # .git/hooks/post-merge
291
+ npx @mrsf/cli reanchor
292
+ ```
293
+
294
+ ## Library Usage
295
+
296
+ The CLI is built library-first. All functionality is available as importable functions:
297
+
298
+ ```typescript
299
+ import {
300
+ parseSidecar,
301
+ writeSidecar,
302
+ validate,
303
+ reanchorDocument,
304
+ addComment,
305
+ resolveComment,
306
+ filterComments,
307
+ summarize,
308
+ discoverSidecar,
309
+ discoverAllSidecars,
310
+ } from "mrsf";
311
+
312
+ // Validate a sidecar programmatically
313
+ const doc = await parseSidecar("docs/api.md.review.yaml");
314
+ const result = await validate(doc);
315
+ console.log(result.valid, result.errors, result.warnings);
316
+
317
+ // Add a comment
318
+ const updated = addComment(doc, lines, {
319
+ author: "ci-bot",
320
+ text: "Auto-generated review comment",
321
+ line: 10,
322
+ });
323
+ await writeSidecar("docs/api.md.review.yaml", updated);
324
+ ```
325
+
326
+ ## Configuration
327
+
328
+ Place a `.mrsf.yaml` at your repository root to configure sidecar discovery:
329
+
330
+ ```yaml
331
+ # Store all sidecars in a central directory instead of co-located
332
+ sidecar_root: .reviews
333
+ ```
334
+
335
+ With this config, the sidecar for `docs/architecture.md` is resolved as `.reviews/docs/architecture.md.review.yaml`.
336
+
337
+ See [§3.2 Alternate Sidecar Location](../MRSF-v1.0.md) in the spec for details.
338
+
339
+ ## Requirements
340
+
341
+ - Node.js 18 or later
342
+ - Git (optional — enables diff-based re-anchoring, commit stamping, rename detection)
343
+
344
+ ## License
345
+
346
+ MIT — see [LICENSE](../LICENSE).
package/dist/bin.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MRSF CLI — entry point.
4
+ *
5
+ * npx @mrsf/cli <command> [options]
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=bin.d.ts.map
package/dist/bin.js ADDED
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MRSF CLI — entry point.
4
+ *
5
+ * npx @mrsf/cli <command> [options]
6
+ */
7
+ import { createRequire } from "node:module";
8
+ import { Command } from "commander";
9
+ import chalk from "chalk";
10
+ import { registerValidate } from "./commands/validate.js";
11
+ const require = createRequire(import.meta.url);
12
+ const { version } = require("../package.json");
13
+ import { registerReanchor } from "./commands/reanchor.js";
14
+ import { registerAdd } from "./commands/add.js";
15
+ import { registerResolve } from "./commands/resolve.js";
16
+ import { registerList } from "./commands/list.js";
17
+ import { registerInit } from "./commands/init.js";
18
+ import { registerStatus } from "./commands/status.js";
19
+ import { registerRename } from "./commands/rename.js";
20
+ /* ── Banner ────────────────────────────────────────────────────────── */
21
+ /**
22
+ * Detect CI/CD or non-interactive environments where the banner should
23
+ * be suppressed. Checks common CI env vars and whether stdout is a TTY.
24
+ */
25
+ function isCI() {
26
+ const env = process.env;
27
+ return !!(env.CI ||
28
+ env.CONTINUOUS_INTEGRATION ||
29
+ env.BUILD_NUMBER ||
30
+ env.GITHUB_ACTIONS ||
31
+ env.GITLAB_CI ||
32
+ env.CIRCLECI ||
33
+ env.TRAVIS ||
34
+ env.JENKINS_URL ||
35
+ env.CODEBUILD_BUILD_ID ||
36
+ env.TF_BUILD ||
37
+ env.BUILDKITE ||
38
+ env.TEAMCITY_VERSION);
39
+ }
40
+ function showBanner() {
41
+ // Skip in CI, non-TTY, --quiet, or --no-color
42
+ if (isCI() || !process.stdout.isTTY)
43
+ return;
44
+ if (process.argv.includes("-q") || process.argv.includes("--quiet"))
45
+ return;
46
+ const repo = "https://github.com/wictorwilen/MRSF";
47
+ const banner = `
48
+ ${chalk.cyan.bold("╔╦╗")}${chalk.blue.bold("╦═╗")}${chalk.magenta.bold("╔═╗")}${chalk.yellow.bold("╔═╗")}
49
+ ${chalk.cyan.bold("║║║")}${chalk.blue.bold("╠╦╝")}${chalk.magenta.bold("╚═╗")}${chalk.yellow.bold("╠╣")}
50
+ ${chalk.cyan.bold("╩ ╩")}${chalk.blue.bold("╩╚═")}${chalk.magenta.bold("╚═╝")}${chalk.yellow.bold("╚")}
51
+ ${chalk.dim("Markdown Review Sidecar Format")} ${chalk.dim.italic(`v${version}`)}
52
+ ${chalk.dim.underline(repo)}
53
+ `;
54
+ process.stderr.write(banner + "\n");
55
+ }
56
+ showBanner();
57
+ /* ── Program ───────────────────────────────────────────────────────── */
58
+ const program = new Command();
59
+ program
60
+ .name("mrsf")
61
+ .description("Markdown Review Sidecar Format — CLI & toolkit")
62
+ .version(version)
63
+ .option("--cwd <dir>", "Working directory")
64
+ .option("--config <path>", "Path to .mrsf.yaml")
65
+ .option("--no-color", "Disable colour output")
66
+ .option("-q, --quiet", "Suppress non-essential output");
67
+ registerValidate(program);
68
+ registerReanchor(program);
69
+ registerAdd(program);
70
+ registerResolve(program);
71
+ registerList(program);
72
+ registerInit(program);
73
+ registerStatus(program);
74
+ registerRename(program);
75
+ program.parse();
76
+ //# sourceMappingURL=bin.js.map
@@ -0,0 +1,6 @@
1
+ /**
2
+ * mrsf add — Add a comment to a sidecar file.
3
+ */
4
+ import type { Command } from "commander";
5
+ export declare function registerAdd(program: Command): void;
6
+ //# sourceMappingURL=add.d.ts.map
@@ -0,0 +1,82 @@
1
+ /**
2
+ * mrsf add — Add a comment to a sidecar file.
3
+ */
4
+ import chalk from "chalk";
5
+ import path from "node:path";
6
+ import { discoverSidecar, findWorkspaceRoot } from "../lib/discovery.js";
7
+ import { parseSidecar, readDocumentLines } from "../lib/parser.js";
8
+ import { writeSidecar } from "../lib/writer.js";
9
+ import { addComment, populateSelectedText } from "../lib/comments.js";
10
+ import { findRepoRoot } from "../lib/git.js";
11
+ export function registerAdd(program) {
12
+ program
13
+ .command("add <document>")
14
+ .description("Add a review comment to a document's sidecar")
15
+ .requiredOption("-a, --author <name>", "Comment author")
16
+ .requiredOption("-t, --text <text>", "Comment text")
17
+ .option("-l, --line <n>", "Line number (1-based)")
18
+ .option("--end-line <n>", "End line (inclusive)")
19
+ .option("--start-column <n>", "Start column (0-based)")
20
+ .option("--end-column <n>", "End column (0-based)")
21
+ .option("--type <type>", "Comment type (e.g. suggestion, issue)")
22
+ .option("--severity <level>", "Severity: low | medium | high")
23
+ .option("--reply-to <id>", "Reply to an existing comment")
24
+ .option("--selected-text <text>", "Selected text to attach")
25
+ .action(async (document, opts) => {
26
+ const parentOpts = program.opts();
27
+ const cwd = parentOpts.cwd ?? process.cwd();
28
+ const quiet = parentOpts.quiet ?? false;
29
+ const docPath = path.resolve(cwd, document);
30
+ const root = await findWorkspaceRoot(cwd);
31
+ const sidecarPath = await discoverSidecar(docPath, { cwd: root ?? cwd });
32
+ // Load or create sidecar
33
+ let doc;
34
+ try {
35
+ doc = await parseSidecar(sidecarPath);
36
+ }
37
+ catch {
38
+ // Create new sidecar
39
+ doc = {
40
+ mrsf_version: "1.0",
41
+ document: path.basename(docPath),
42
+ comments: [],
43
+ };
44
+ }
45
+ const repoRoot = await findRepoRoot(cwd);
46
+ const comment = await addComment(doc, {
47
+ author: opts.author,
48
+ text: opts.text,
49
+ line: opts.line ? parseInt(opts.line, 10) : undefined,
50
+ end_line: opts.endLine ? parseInt(opts.endLine, 10) : undefined,
51
+ start_column: opts.startColumn
52
+ ? parseInt(opts.startColumn, 10)
53
+ : undefined,
54
+ end_column: opts.endColumn
55
+ ? parseInt(opts.endColumn, 10)
56
+ : undefined,
57
+ type: opts.type,
58
+ severity: opts.severity,
59
+ reply_to: opts.replyTo,
60
+ }, repoRoot ?? undefined);
61
+ // Auto-populate selected_text from the document if not provided
62
+ if (opts.selectedText) {
63
+ comment.selected_text = opts.selectedText;
64
+ }
65
+ else if (comment.line != null) {
66
+ try {
67
+ const lines = await readDocumentLines(docPath);
68
+ populateSelectedText(comment, lines);
69
+ }
70
+ catch {
71
+ // Document file may not exist yet, skip
72
+ }
73
+ }
74
+ await writeSidecar(sidecarPath, doc);
75
+ if (!quiet) {
76
+ console.log(chalk.green(`Added comment ${comment.id}`) +
77
+ (comment.line ? ` at line ${comment.line}` : "") +
78
+ ` → ${sidecarPath}`);
79
+ }
80
+ });
81
+ }
82
+ //# sourceMappingURL=add.js.map
@@ -0,0 +1,6 @@
1
+ /**
2
+ * mrsf init — Initialize a new sidecar file for a document.
3
+ */
4
+ import type { Command } from "commander";
5
+ export declare function registerInit(program: Command): void;
6
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1,52 @@
1
+ /**
2
+ * mrsf init — Initialize a new sidecar file for a document.
3
+ */
4
+ import chalk from "chalk";
5
+ import path from "node:path";
6
+ import fs from "node:fs/promises";
7
+ import { discoverSidecar, findWorkspaceRoot } from "../lib/discovery.js";
8
+ import { writeSidecar } from "../lib/writer.js";
9
+ export function registerInit(program) {
10
+ program
11
+ .command("init <document>")
12
+ .description("Create a new sidecar file for a Markdown document")
13
+ .option("-f, --force", "Overwrite existing sidecar")
14
+ .action(async (document, opts) => {
15
+ const parentOpts = program.opts();
16
+ const cwd = parentOpts.cwd ?? process.cwd();
17
+ const quiet = parentOpts.quiet ?? false;
18
+ const docPath = path.resolve(cwd, document);
19
+ // Verify the document exists
20
+ try {
21
+ await fs.access(docPath);
22
+ }
23
+ catch {
24
+ console.error(chalk.red(`Document not found: ${docPath}`));
25
+ process.exit(1);
26
+ }
27
+ const root = await findWorkspaceRoot(cwd);
28
+ const sidecarPath = await discoverSidecar(docPath, { cwd: root ?? cwd });
29
+ // Check if sidecar already exists
30
+ try {
31
+ await fs.access(sidecarPath);
32
+ if (!opts.force) {
33
+ console.error(chalk.yellow(`Sidecar already exists: ${sidecarPath}`));
34
+ console.error("Use --force to overwrite.");
35
+ process.exit(1);
36
+ }
37
+ }
38
+ catch {
39
+ // Good — doesn't exist yet
40
+ }
41
+ const doc = {
42
+ mrsf_version: "1.0",
43
+ document: path.basename(docPath),
44
+ comments: [],
45
+ };
46
+ await writeSidecar(sidecarPath, doc);
47
+ if (!quiet) {
48
+ console.log(chalk.green(`Created ${sidecarPath}`));
49
+ }
50
+ });
51
+ }
52
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1,6 @@
1
+ /**
2
+ * mrsf list — List comments in sidecar files.
3
+ */
4
+ import type { Command } from "commander";
5
+ export declare function registerList(program: Command): void;
6
+ //# sourceMappingURL=list.d.ts.map