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 +13 -0
- package/dist/cli.js +558 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/resources.d.ts +2 -0
- package/dist/resources.js +131 -0
- package/dist/serve.d.ts +2 -0
- package/dist/serve.js +5 -0
- package/dist/server.d.ts +4 -0
- package/dist/server.js +32 -0
- package/dist/tools.d.ts +19 -0
- package/dist/tools.js +551 -0
- package/package.json +28 -4
- package/skills/stepwell/SKILL.md +117 -0
- package/README.md +0 -3
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
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -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";
|