@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.
- package/LICENSE +21 -0
- package/README.md +346 -0
- package/dist/bin.d.ts +8 -0
- package/dist/bin.js +76 -0
- package/dist/commands/add.d.ts +6 -0
- package/dist/commands/add.js +82 -0
- package/dist/commands/init.d.ts +6 -0
- package/dist/commands/init.js +52 -0
- package/dist/commands/list.d.ts +6 -0
- package/dist/commands/list.js +95 -0
- package/dist/commands/reanchor.d.ts +6 -0
- package/dist/commands/reanchor.js +127 -0
- package/dist/commands/rename.d.ts +6 -0
- package/dist/commands/rename.js +53 -0
- package/dist/commands/resolve.d.ts +6 -0
- package/dist/commands/resolve.js +35 -0
- package/dist/commands/status.d.ts +6 -0
- package/dist/commands/status.js +156 -0
- package/dist/commands/validate.d.ts +6 -0
- package/dist/commands/validate.js +55 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +25 -0
- package/dist/lib/comments.d.ts +52 -0
- package/dist/lib/comments.js +228 -0
- package/dist/lib/discovery.d.ts +40 -0
- package/dist/lib/discovery.js +125 -0
- package/dist/lib/fuzzy.d.ts +39 -0
- package/dist/lib/fuzzy.js +277 -0
- package/dist/lib/git.d.ts +68 -0
- package/dist/lib/git.js +246 -0
- package/dist/lib/parser.d.ts +19 -0
- package/dist/lib/parser.js +56 -0
- package/dist/lib/reanchor.d.ts +45 -0
- package/dist/lib/reanchor.js +389 -0
- package/dist/lib/resolve-files.d.ts +18 -0
- package/dist/lib/resolve-files.js +42 -0
- package/dist/lib/types.d.ts +151 -0
- package/dist/lib/types.js +5 -0
- package/dist/lib/validator.d.ts +13 -0
- package/dist/lib/validator.js +177 -0
- package/dist/lib/writer.d.ts +30 -0
- package/dist/lib/writer.js +62 -0
- 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
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,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,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
|