stepwell 0.0.0-stage → 0.2.4

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/cli.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ export interface CliIo {
3
+ stdout: NodeJS.WritableStream;
4
+ stderr: NodeJS.WritableStream;
5
+ }
6
+ /**
7
+ * Version des JSON-Output-Kontrakts aller `--json`-Ausgaben (M6/9.6). Regel:
8
+ * Breaking-Änderung an Feldern je Command ⇒ diese Zahl hochzählen (Decision 16).
9
+ */
10
+ export declare const JSON_SCHEMA_VERSION = 2;
11
+ export declare class UsageError extends Error {
12
+ }
13
+ export declare function runCli(argv: string[], io: CliIo, defaultRoot?: string): Promise<number>;
package/dist/cli.js ADDED
@@ -0,0 +1,558 @@
1
+ #!/usr/bin/env node
2
+ import { applyArchivePlan, applyProgressPlan, docsRepair, docsStatus, docsValidate, planArchiveItem, planProgressUpdate, priorityAliasHelp, readBacklog, readProgress, resolvePriority, resolveStatus, statusAliasHelp, } from "stepwell-core";
3
+ import { commitDocs, docCommitMessage, pullTrunk, pushTrunk, syncPreview, workstreamReport, } from "stepwell-git";
4
+ /**
5
+ * Version des JSON-Output-Kontrakts aller `--json`-Ausgaben (M6/9.6). Regel:
6
+ * Breaking-Änderung an Feldern je Command ⇒ diese Zahl hochzählen (Decision 16).
7
+ */
8
+ export const JSON_SCHEMA_VERSION = 2;
9
+ function jsonOut(payload) {
10
+ return `${JSON.stringify({ schema: JSON_SCHEMA_VERSION, ...payload }, null, 2)}\n`;
11
+ }
12
+ /** One-line commit outcome for human-readable output (H5/26.3). */
13
+ function commitLine(outcome) {
14
+ if (outcome.ok === true) {
15
+ const committed = outcome.committed === true;
16
+ return committed
17
+ ? `Committed ${outcome.files?.length ?? 0} doc file(s): ${outcome.sha ?? "?"}`
18
+ : "Nothing to commit (docs unchanged)";
19
+ }
20
+ const error = outcome.error;
21
+ return `Commit failed (${error?.code}): ${error?.message ?? "unknown cause"}`;
22
+ }
23
+ // --- sync text renderers (H5/26.5) ----------------------------------------
24
+ function syncErrorText(error) {
25
+ const files = error.files !== undefined && error.files.length > 0 ? ` (files: ${error.files.join(", ")})` : "";
26
+ return `sync failed (${error.code}): ${error.message}${files}\n`;
27
+ }
28
+ function syncStatusText(report) {
29
+ if (!report.ok)
30
+ return syncErrorText(report.error);
31
+ const lines = [`Trunk: ${report.trunk}`, `Current branch: ${report.current}`];
32
+ if (report.worktrees.length === 0) {
33
+ lines.push("Workstreams: none (single-tree mode)");
34
+ lines.push("Set up a workstream: git worktree add ../<project>-<branch> -b <branch>");
35
+ }
36
+ else {
37
+ lines.push("Workstreams:");
38
+ for (const ws of report.worktrees) {
39
+ const state = ws.branch === "" ? "(detached)" : `${ws.ahead} ahead / ${ws.behind} behind`;
40
+ const live = ws.lastSubject === "" ? "" : ` — last: "${ws.lastSubject}"`;
41
+ const overlap = ws.overlap.length > 0 ? ` — overlap: ${ws.overlap.join(", ")}` : "";
42
+ const dirty = ws.dirty ? " [dirty]" : "";
43
+ lines.push(` ${ws.path} (${ws.branch}) ${state}${dirty}${live}${overlap}`);
44
+ // H10/30.4: file-based collision prognosis from open-item Location bullets
45
+ for (const p of ws.predictedOverlap) {
46
+ lines.push(` predicted overlap: ${p.path} (open items: ${p.openItems.join(", ")})`);
47
+ }
48
+ }
49
+ }
50
+ return `${lines.join("\n")}\n`;
51
+ }
52
+ function syncPreviewText(preview) {
53
+ if (preview.direction === "pull") {
54
+ const p = preview;
55
+ return [
56
+ `pull preview (dry-run): trunk "${p.trunk}" → branch "${p.branch}"`,
57
+ ` behind: ${p.behind} commit(s), files: ${p.files.length === 0 ? "(none)" : p.files.join(", ")}`,
58
+ p.overlap.length > 0 ? ` overlap (likely conflicts): ${p.overlap.join(", ")}` : " overlap: none",
59
+ ].join("\n") + "\n";
60
+ }
61
+ const p = preview;
62
+ return [
63
+ `push preview (dry-run): branch "${p.branch}" → trunk "${p.trunk}"`,
64
+ ` ahead: ${p.ahead} commit(s), trunk would move to ${p.target}`,
65
+ ].join("\n") + "\n";
66
+ }
67
+ function syncApplyText(direction, outcome) {
68
+ if (direction === "pull") {
69
+ return `pulled trunk into branch: fast-forward=${outcome.fastForward === true}, files changed: ${outcome.mergedFiles ?? 0}\n`;
70
+ }
71
+ return "pushed branch to trunk: trunk fast-forwarded to the branch head\n";
72
+ }
73
+ const USAGE = `usage: stepwell <command> [options]
74
+
75
+ commands:
76
+ status Project aggregate (open items, running phases, ✅ ratio)
77
+ backlog List items of BACKLOG.md
78
+ progress List progress table rows
79
+ validate Check consistency (exit 1 on findings)
80
+ repair Mechanically repair validate findings over the responsible
81
+ planners (dry-run; --apply to write; exit 1 while findings remain)
82
+ archive Move completed item into archive (dry-run; --apply to write)
83
+ progress-update Update step status incl. phase completion (dry-run; --apply to write)
84
+ sync <direction> Git workstream sync (H5): status (read-only report) | pull | push.
85
+ pull/push are pre-flight previews by default; --apply executes.
86
+
87
+ options:
88
+ --root <dir> Project root (defaults to the current directory)
89
+ --priority <list> Comma list, icons or aliases: 🔴=red/critical/p1, 🟠=orange/high/p2,
90
+ 🟡=yellow/medium/p3, 🟢=green/low/p4, 🔵=blue/test/p5, unknown
91
+ --open <bool> Checkbox filter (true/false, backlog only)
92
+ --section <title> Exact section title (backlog only)
93
+ --status <icon> Status filter (progress) or new icon (progress-update):
94
+ ⬜=open, 🔄=running/wip, ✅=done, ⛔=blocked, unknown (filter only)
95
+ --id <id> Item id (archive only)
96
+ --note <text> Done/verification note (archive, progress-update)
97
+ --title <text> New phase title (progress-update) — renames the block heading
98
+ --checkpoint <sha> Commit SHA of the phase, 7–40 hex (progress-update) — lands in
99
+ the verification line of the archive block
100
+ --locale <de|en> Language for generated text (archive, progress-update; default: auto-detect)
101
+ --apply Write the changes (archive, progress-update; default: dry-run preview)
102
+ --commit After a successful apply, commit ONLY the four doc files (archive,
103
+ progress-update; ignored for dry-runs). Refused when the index has
104
+ pre-staged changes (COMMIT_BLOCKED); a failed commit keeps the edits
105
+ applied (COMMIT_FAILED) and the command exits 1.
106
+ --commit-message Commit subject with --commit; default generated from the operation
107
+ --json Raw payloads instead of human-readable text
108
+ `;
109
+ function parseArgs(argv) {
110
+ const options = {
111
+ root: undefined,
112
+ json: false,
113
+ priority: [],
114
+ open: undefined,
115
+ section: undefined,
116
+ status: undefined,
117
+ id: undefined,
118
+ note: undefined,
119
+ title: undefined,
120
+ checkpoint: undefined,
121
+ phase: undefined,
122
+ step: undefined,
123
+ locale: undefined,
124
+ apply: false,
125
+ commit: false,
126
+ commitMessage: undefined,
127
+ positionals: [],
128
+ };
129
+ let command;
130
+ for (let i = 0; i < argv.length; i++) {
131
+ const arg = argv[i];
132
+ const next = () => argv[++i];
133
+ switch (arg) {
134
+ case "--root":
135
+ options.root = next();
136
+ break;
137
+ case "--json":
138
+ options.json = true;
139
+ break;
140
+ case "--priority":
141
+ options.priority = (next() ?? "").split(",").map((p) => p.trim()).filter((p) => p !== "");
142
+ break;
143
+ case "--open": {
144
+ const value = next();
145
+ options.open = value === "false" ? false : value === "true" ? true : true;
146
+ if (value !== "false" && value !== "true")
147
+ i--;
148
+ break;
149
+ }
150
+ case "--section":
151
+ options.section = next();
152
+ break;
153
+ case "--status":
154
+ options.status = next();
155
+ break;
156
+ case "--id":
157
+ options.id = next();
158
+ break;
159
+ case "--note":
160
+ options.note = next();
161
+ break;
162
+ case "--title":
163
+ options.title = next();
164
+ break;
165
+ case "--checkpoint":
166
+ options.checkpoint = next();
167
+ break;
168
+ case "--apply":
169
+ options.apply = true;
170
+ break;
171
+ case "--commit":
172
+ options.commit = true;
173
+ break;
174
+ case "--commit-message":
175
+ options.commitMessage = next();
176
+ break;
177
+ case "--phase":
178
+ options.phase = next();
179
+ break;
180
+ case "--step":
181
+ options.step = next();
182
+ break;
183
+ case "--locale":
184
+ options.locale = next();
185
+ break;
186
+ default:
187
+ if (command === undefined && !arg.startsWith("-")) {
188
+ command = arg;
189
+ }
190
+ else if (!arg.startsWith("-")) {
191
+ options.positionals.push(arg);
192
+ }
193
+ else {
194
+ throw new UsageError(`unknown argument: ${arg}`);
195
+ }
196
+ }
197
+ }
198
+ return { command, options };
199
+ }
200
+ export class UsageError extends Error {
201
+ }
202
+ function requireRoot(options, defaultRoot) {
203
+ const root = options.root === undefined || options.root === "" ? defaultRoot : options.root;
204
+ if (root === undefined || root === "") {
205
+ throw new UsageError("missing required option: --root <dir> (or run inside a project directory)");
206
+ }
207
+ return root;
208
+ }
209
+ function basename(path) {
210
+ return path.split(/[\\/]/u).pop() ?? path;
211
+ }
212
+ function requireLocale(options) {
213
+ if (options.locale === undefined || options.locale === "")
214
+ return undefined;
215
+ if (options.locale !== "de" && options.locale !== "en") {
216
+ throw new UsageError("invalid --locale: allowed values are de and en");
217
+ }
218
+ return options.locale;
219
+ }
220
+ function requirePriorities(options) {
221
+ return options.priority.map((token) => {
222
+ const resolved = resolvePriority(token);
223
+ if (resolved === undefined) {
224
+ throw new UsageError(`invalid --priority value: "${token}" — allowed: ${priorityAliasHelp()}`);
225
+ }
226
+ return resolved;
227
+ });
228
+ }
229
+ function requireStatusFilter(options) {
230
+ const resolved = resolveStatus(options.status);
231
+ if (resolved === undefined) {
232
+ throw new UsageError(`invalid --status value: "${options.status}" — allowed: ${statusAliasHelp()}`);
233
+ }
234
+ return resolved;
235
+ }
236
+ function requireStatusIcon(options) {
237
+ const resolved = resolveStatus(options.status);
238
+ if (resolved === undefined || resolved === "unknown") {
239
+ throw new UsageError(`invalid --status value: "${options.status}" — allowed: ${statusAliasHelp().replace(", unknown", "")}`);
240
+ }
241
+ return resolved;
242
+ }
243
+ function statusText(s) {
244
+ const prio = s.openByPriority;
245
+ const prioLine = `🔴 ${prio["🔴"]} · 🟠 ${prio["🟠"]} · 🟡 ${prio["🟡"]} · 🟢 ${prio["🟢"]} · 🔵 ${prio["🔵"]} · unknown ${prio.unknown}`;
246
+ const steps = s.runningSteps.length > 0
247
+ ? s.runningSteps.map((r) => `${r.step} ${r.name} (${r.status})`).join(", ")
248
+ : "(none)";
249
+ const phases = s.runningPhases.length > 0 ? s.runningPhases.join(", ") : "(none)";
250
+ const nextStep = s.nextStep !== undefined ? `${s.nextStep.step} ${s.nextStep.name} (⬜)` : "(none)";
251
+ const nextPriority = s.nextPriority !== undefined
252
+ ? `${s.nextPriority} (${s.openByPriority[s.nextPriority]} open)`
253
+ : "(none)";
254
+ const quote = `✅ ratio: ${s.doneQuote.done}/${s.doneQuote.total} (${s.doneQuote.percent}%)`;
255
+ // L24/32.7: growth is measurable — one compact line over all doc files
256
+ const docFiles = s.docFiles.length > 0
257
+ ? `Doc files: ${s.docFiles.length} files, ${s.docFiles.reduce((n, f) => n + f.lines, 0)} lines, ${s.docFiles.reduce((n, f) => n + f.bytes, 0)} bytes`
258
+ : "Doc files: (none)";
259
+ return [
260
+ `Open items: ${s.openTotal} (${prioLine})`,
261
+ `Running steps: ${steps}`,
262
+ `Running phases: ${phases}`,
263
+ `Next step: ${nextStep}`,
264
+ `Next priority: ${nextPriority}`,
265
+ quote,
266
+ docFiles,
267
+ `Warnings: ${s.warnings.length}`,
268
+ "",
269
+ ].join("\n");
270
+ }
271
+ function backlogText(items) {
272
+ const lines = items.map((i) => `${i.id} ${i.priority} [${i.open ? " " : "x"}] ${i.section} — ${i.title}`);
273
+ return [...lines, "", `Items: ${items.length}`, ""].join("\n");
274
+ }
275
+ function progressText(rows) {
276
+ const lines = rows.map((r) => `${r.step} ${r.status} ${r.name}`);
277
+ return [...lines, "", `Rows: ${rows.length}`, ""].join("\n");
278
+ }
279
+ function validateText(result) {
280
+ const lines = [];
281
+ if (result.ok) {
282
+ lines.push("OK — no consistency findings.");
283
+ }
284
+ else {
285
+ lines.push(`Findings: ${result.findings.length}`);
286
+ for (const f of result.findings) {
287
+ const where = f.line === undefined ? basename(f.file) : `${basename(f.file)}:${f.line}`;
288
+ lines.push(`${f.code} ${where} — ${f.message}`);
289
+ }
290
+ }
291
+ if (result.acknowledged.length > 0) {
292
+ lines.push(`Acknowledged (accepted findings): ${result.acknowledged.length}`);
293
+ for (const f of result.acknowledged) {
294
+ const where = f.line === undefined ? basename(f.file) : `${basename(f.file)}:${f.line}`;
295
+ lines.push(` ${f.code} ${where}`);
296
+ }
297
+ }
298
+ lines.push(`Warnings (parse): ${result.warnings.length}`);
299
+ lines.push("");
300
+ return lines.join("\n");
301
+ }
302
+ function planText(plan) {
303
+ const lines = [
304
+ plan.dryRun ? "Dry-run — nothing was written (--apply to apply)." : "Apply — changes written:",
305
+ "",
306
+ ];
307
+ for (const change of plan.changes) {
308
+ lines.push(`## ${basename(change.file)} — ${change.description}`);
309
+ lines.push(change.diff);
310
+ lines.push("");
311
+ }
312
+ return lines.join("\n");
313
+ }
314
+ function repairText(result) {
315
+ const lines = [
316
+ result.dryRun ? "Dry-run — nothing was written (--apply to apply)." : "Apply — repairs executed:",
317
+ "",
318
+ ];
319
+ const list = (label, actions) => {
320
+ lines.push(`${label}: ${actions.length}`);
321
+ for (const a of actions)
322
+ lines.push(` ${a.code}${a.target !== "" ? ` — ${a.target}` : ""}`);
323
+ };
324
+ list(result.dryRun ? "Planned" : "Applied", result.dryRun ? result.fix.planned : result.fix.applied);
325
+ list("Skipped", result.fix.skipped);
326
+ for (const s of result.fix.skipped)
327
+ lines.push(` reason: ${s.reason}`);
328
+ lines.push("");
329
+ lines.push(result.verification.ok ? "OK — no findings remain." : `Findings remaining: ${result.verification.messages.length}`);
330
+ for (const m of result.verification.messages)
331
+ lines.push(` ${m}`);
332
+ lines.push("");
333
+ if (!result.dryRun) {
334
+ for (const change of result.changes) {
335
+ lines.push(`## ${basename(change.file)} — ${change.description}`);
336
+ lines.push(change.diff);
337
+ lines.push("");
338
+ }
339
+ }
340
+ return lines.join("\n");
341
+ }
342
+ function applyResultText(result) {
343
+ const lines = [
344
+ result.verification.ok ? "OK — archival verified." : "ERROR — verification failed:",
345
+ ...result.verification.messages.map((m) => ` ${m}`),
346
+ "",
347
+ ];
348
+ return lines.join("\n");
349
+ }
350
+ export async function runCli(argv, io, defaultRoot) {
351
+ let command;
352
+ let options;
353
+ try {
354
+ const parsed = parseArgs(argv);
355
+ command = parsed.command;
356
+ options = parsed.options;
357
+ }
358
+ catch (err) {
359
+ if (err instanceof UsageError) {
360
+ io.stderr.write(`${err.message}\n\n${USAGE}`);
361
+ return 2;
362
+ }
363
+ throw err;
364
+ }
365
+ if (command === undefined || !["status", "backlog", "progress", "validate", "repair", "archive", "progress-update", "sync"].includes(command)) {
366
+ io.stderr.write(USAGE);
367
+ return 2;
368
+ }
369
+ try {
370
+ switch (command) {
371
+ case "status": {
372
+ const s = docsStatus(requireRoot(options, defaultRoot));
373
+ io.stdout.write(options.json ? jsonOut(s) : statusText(s));
374
+ return 0;
375
+ }
376
+ case "backlog": {
377
+ const root = requireRoot(options, defaultRoot);
378
+ const parsed = readBacklog(root);
379
+ let items = parsed.value.items;
380
+ const priorities = requirePriorities(options);
381
+ if (priorities.length > 0) {
382
+ const wanted = new Set(priorities);
383
+ items = items.filter((i) => wanted.has(i.priority));
384
+ }
385
+ if (options.open !== undefined) {
386
+ items = items.filter((i) => i.open === options.open);
387
+ }
388
+ if (options.section !== undefined) {
389
+ items = items.filter((i) => i.section === options.section);
390
+ }
391
+ if (options.json) {
392
+ io.stdout.write(jsonOut({ count: items.length, items: items.map(({ raw: _raw, ...rest }) => rest), warnings: parsed.warnings }));
393
+ }
394
+ else {
395
+ io.stdout.write(backlogText(items));
396
+ }
397
+ return 0;
398
+ }
399
+ case "progress": {
400
+ const root = requireRoot(options, defaultRoot);
401
+ const parsed = readProgress(root);
402
+ let rows = parsed.value.rows;
403
+ if (options.status !== undefined && options.status !== "") {
404
+ const wanted = requireStatusFilter(options);
405
+ rows = rows.filter((r) => r.status === wanted);
406
+ }
407
+ io.stdout.write(options.json ? jsonOut({ count: rows.length, rows, warnings: parsed.warnings }) : progressText(rows));
408
+ return 0;
409
+ }
410
+ case "validate": {
411
+ const result = docsValidate(requireRoot(options, defaultRoot));
412
+ io.stdout.write(options.json ? jsonOut(result) : validateText(result));
413
+ return result.ok ? 0 : 1;
414
+ }
415
+ case "repair": {
416
+ const result = docsRepair(requireRoot(options, defaultRoot), { dryRun: !options.apply });
417
+ io.stdout.write(options.json ? jsonOut(result) : repairText(result));
418
+ return result.dryRun ? 0 : result.verification.ok ? 0 : 1;
419
+ }
420
+ case "archive": {
421
+ const root = requireRoot(options, defaultRoot);
422
+ if (options.id === undefined || options.id === "") {
423
+ throw new UsageError("missing required option: --id <itemId>");
424
+ }
425
+ const locale = requireLocale(options);
426
+ const plan = planArchiveItem(root, options.id, {
427
+ dryRun: !options.apply,
428
+ ...(options.note !== undefined ? { note: options.note } : {}),
429
+ ...(locale !== undefined ? { locale } : {}),
430
+ });
431
+ if (plan.dryRun) {
432
+ io.stdout.write(options.json ? jsonOut(plan) : planText(plan));
433
+ return 0;
434
+ }
435
+ const result = applyArchivePlan(plan);
436
+ let commitOutcome;
437
+ if (options.commit) {
438
+ const outcome = commitDocs(root, {
439
+ message: options.commitMessage ?? docCommitMessage({ kind: "archive", id: options.id }),
440
+ });
441
+ commitOutcome = outcome;
442
+ result.commit = commitOutcome;
443
+ }
444
+ if (options.json) {
445
+ io.stdout.write(jsonOut(result));
446
+ }
447
+ else {
448
+ io.stdout.write(applyResultText(result));
449
+ if (commitOutcome !== undefined)
450
+ io.stdout.write(`${commitLine(commitOutcome)}\n`);
451
+ }
452
+ return result.verification.ok && (commitOutcome?.ok !== false) ? 0 : 1;
453
+ }
454
+ case "progress-update": {
455
+ const root = requireRoot(options, defaultRoot);
456
+ if (options.phase === undefined || options.phase === "") {
457
+ throw new UsageError("missing required option: --phase <phase>");
458
+ }
459
+ if (options.step === undefined || options.step === "") {
460
+ throw new UsageError("missing required option: --step <nr>");
461
+ }
462
+ if (options.status === undefined || options.status === "") {
463
+ throw new UsageError("missing required option: --status <icon|alias>");
464
+ }
465
+ const statusIcon = requireStatusIcon(options);
466
+ const plan = planProgressUpdate(root, options.phase, options.step, statusIcon, {
467
+ dryRun: !options.apply,
468
+ ...(options.title !== undefined && options.title !== "" ? { title: options.title } : {}),
469
+ ...(options.note !== undefined ? { note: options.note } : {}),
470
+ ...(options.checkpoint !== undefined && options.checkpoint !== ""
471
+ ? { checkpoint: options.checkpoint }
472
+ : {}),
473
+ ...(requireLocale(options) !== undefined ? { locale: requireLocale(options) } : {}),
474
+ });
475
+ if (plan.dryRun) {
476
+ io.stdout.write(options.json ? jsonOut(plan) : planText(plan));
477
+ return 0;
478
+ }
479
+ const result = applyProgressPlan(plan);
480
+ let commitOutcome;
481
+ if (options.commit) {
482
+ const outcome = commitDocs(root, {
483
+ message: options.commitMessage ??
484
+ docCommitMessage({ kind: "progress", steps: [options.step], status: statusIcon }),
485
+ });
486
+ commitOutcome = outcome;
487
+ result.commit = commitOutcome;
488
+ }
489
+ if (options.json) {
490
+ io.stdout.write(jsonOut(result));
491
+ }
492
+ else {
493
+ io.stdout.write(applyResultText(result));
494
+ if (commitOutcome !== undefined)
495
+ io.stdout.write(`${commitLine(commitOutcome)}\n`);
496
+ }
497
+ return result.verification.ok && (commitOutcome?.ok !== false) ? 0 : 1;
498
+ }
499
+ case "sync": {
500
+ const root = requireRoot(options, defaultRoot);
501
+ const direction = options.positionals[0];
502
+ if (direction === undefined || options.positionals.length > 1) {
503
+ throw new UsageError("usage: stepwell sync <status|pull|push> (exactly one direction)");
504
+ }
505
+ if (direction !== "status" && direction !== "pull" && direction !== "push") {
506
+ throw new UsageError(`unknown sync direction: ${direction}`);
507
+ }
508
+ if (direction === "status") {
509
+ const report = workstreamReport(root);
510
+ if (options.json) {
511
+ io.stdout.write(jsonOut(report));
512
+ }
513
+ else {
514
+ io.stdout.write(syncStatusText(report));
515
+ }
516
+ return report.ok ? 0 : 1;
517
+ }
518
+ const dryRun = !options.apply;
519
+ const preview = syncPreview(root, direction);
520
+ if (dryRun || !preview.ok) {
521
+ if (options.json) {
522
+ io.stdout.write(jsonOut({ direction, dryRun, ...preview }));
523
+ }
524
+ else {
525
+ io.stdout.write(preview.ok ? syncPreviewText(preview) : syncErrorText(preview.error));
526
+ }
527
+ return preview.ok ? 0 : 1;
528
+ }
529
+ const outcome = direction === "pull" ? pullTrunk(root) : pushTrunk(root);
530
+ if (options.json) {
531
+ io.stdout.write(jsonOut({ direction, dryRun, ...outcome }));
532
+ }
533
+ else {
534
+ io.stdout.write(outcome.ok ? syncApplyText(direction, outcome) : syncErrorText(outcome.error));
535
+ }
536
+ return outcome.ok ? 0 : 1;
537
+ }
538
+ default:
539
+ io.stderr.write(USAGE);
540
+ return 2;
541
+ }
542
+ }
543
+ catch (err) {
544
+ if (err instanceof UsageError) {
545
+ io.stderr.write(`${err.message}\n\n${USAGE}`);
546
+ return 2;
547
+ }
548
+ const message = err instanceof Error ? err.message : String(err);
549
+ io.stderr.write(`error: ${message}\n`);
550
+ return 1;
551
+ }
552
+ }
553
+ // Direktausführung aus src (cli.ts, native TS) wie aus dist (cli.js, H1/10.5) —
554
+ // die Heuristik muss beide Endungen erkennen, sonst startet das installierte Bin nicht.
555
+ const isDirectRun = process.argv[1] !== undefined && /cli\.(ts|js)$/u.test(process.argv[1]);
556
+ if (isDirectRun) {
557
+ process.exitCode = await runCli(process.argv.slice(2), { stdout: process.stdout, stderr: process.stderr }, process.cwd());
558
+ }
@@ -0,0 +1 @@
1
+ export { createDocsServer, SERVER_NAME, SERVER_VERSION } from "./server.ts";
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export { createDocsServer, SERVER_NAME, SERVER_VERSION } from "./server.js";
@@ -0,0 +1,2 @@
1
+ import { type McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerDocsResources(server: McpServer): void;