@zhuk/spego 0.8.0 → 0.10.1

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 (105) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +57 -2
  3. package/dist/cli/body-input.d.ts +12 -0
  4. package/dist/cli/body-input.js +27 -0
  5. package/dist/cli/body-input.js.map +1 -0
  6. package/dist/cli/commands/artifact.d.ts +8 -0
  7. package/dist/cli/commands/artifact.js +171 -0
  8. package/dist/cli/commands/artifact.js.map +1 -0
  9. package/dist/cli/commands/delivery.d.ts +7 -0
  10. package/dist/cli/commands/delivery.js +99 -0
  11. package/dist/cli/commands/delivery.js.map +1 -0
  12. package/dist/cli/commands/index-rebuild.d.ts +5 -0
  13. package/dist/cli/commands/index-rebuild.js +31 -0
  14. package/dist/cli/commands/index-rebuild.js.map +1 -0
  15. package/dist/cli/commands/meta.d.ts +6 -0
  16. package/dist/cli/commands/meta.js +60 -0
  17. package/dist/cli/commands/meta.js.map +1 -0
  18. package/dist/cli/commands/orchestrate.d.ts +6 -0
  19. package/dist/cli/commands/orchestrate.js +51 -0
  20. package/dist/cli/commands/orchestrate.js.map +1 -0
  21. package/dist/cli/commands/skills.d.ts +5 -0
  22. package/dist/cli/commands/skills.js +49 -0
  23. package/dist/cli/commands/skills.js.map +1 -0
  24. package/dist/cli/commands/view.d.ts +8 -0
  25. package/dist/cli/commands/view.js +59 -0
  26. package/dist/cli/commands/view.js.map +1 -0
  27. package/dist/cli/commands/workspace.d.ts +5 -0
  28. package/dist/cli/commands/workspace.js +61 -0
  29. package/dist/cli/commands/workspace.js.map +1 -0
  30. package/dist/cli/errors.d.ts +20 -0
  31. package/dist/cli/errors.js +55 -0
  32. package/dist/cli/errors.js.map +1 -0
  33. package/dist/cli/output.d.ts +12 -0
  34. package/dist/cli/output.js +28 -0
  35. package/dist/cli/output.js.map +1 -0
  36. package/dist/cli/render.d.ts +7 -0
  37. package/dist/cli/render.js +22 -0
  38. package/dist/cli/render.js.map +1 -1
  39. package/dist/cli/runtime.d.ts +26 -0
  40. package/dist/cli/runtime.js +48 -0
  41. package/dist/cli/runtime.js.map +1 -0
  42. package/dist/cli.d.ts +8 -0
  43. package/dist/cli.js +65 -582
  44. package/dist/cli.js.map +1 -1
  45. package/dist/commands/registry.js +12 -0
  46. package/dist/commands/registry.js.map +1 -1
  47. package/dist/errors.d.ts +1 -1
  48. package/dist/errors.js.map +1 -1
  49. package/dist/index.d.ts +2 -0
  50. package/dist/index.js +1 -0
  51. package/dist/index.js.map +1 -1
  52. package/dist/orchestration/agents.d.ts +15 -0
  53. package/dist/orchestration/agents.js +156 -0
  54. package/dist/orchestration/agents.js.map +1 -0
  55. package/dist/orchestration/artifacts.d.ts +8 -0
  56. package/dist/orchestration/artifacts.js +20 -0
  57. package/dist/orchestration/artifacts.js.map +1 -0
  58. package/dist/orchestration/client.d.ts +59 -0
  59. package/dist/orchestration/client.js +107 -0
  60. package/dist/orchestration/client.js.map +1 -0
  61. package/dist/orchestration/config.d.ts +14 -0
  62. package/dist/orchestration/config.js +45 -0
  63. package/dist/orchestration/config.js.map +1 -0
  64. package/dist/orchestration/emit.d.ts +7 -0
  65. package/dist/orchestration/emit.js +19 -0
  66. package/dist/orchestration/emit.js.map +1 -0
  67. package/dist/orchestration/engine.d.ts +23 -0
  68. package/dist/orchestration/engine.js +136 -0
  69. package/dist/orchestration/engine.js.map +1 -0
  70. package/dist/orchestration/file-gate.d.ts +19 -0
  71. package/dist/orchestration/file-gate.js +25 -0
  72. package/dist/orchestration/file-gate.js.map +1 -0
  73. package/dist/orchestration/index.d.ts +18 -0
  74. package/dist/orchestration/index.js +12 -0
  75. package/dist/orchestration/index.js.map +1 -0
  76. package/dist/orchestration/manager.d.ts +13 -0
  77. package/dist/orchestration/manager.js +26 -0
  78. package/dist/orchestration/manager.js.map +1 -0
  79. package/dist/orchestration/prompts.d.ts +10 -0
  80. package/dist/orchestration/prompts.js +71 -0
  81. package/dist/orchestration/prompts.js.map +1 -0
  82. package/dist/orchestration/scheduler.d.ts +18 -0
  83. package/dist/orchestration/scheduler.js +49 -0
  84. package/dist/orchestration/scheduler.js.map +1 -0
  85. package/dist/orchestration/schema.d.ts +12 -0
  86. package/dist/orchestration/schema.js +78 -0
  87. package/dist/orchestration/schema.js.map +1 -0
  88. package/dist/orchestration/tiers.d.ts +14 -0
  89. package/dist/orchestration/tiers.js +57 -0
  90. package/dist/orchestration/tiers.js.map +1 -0
  91. package/dist/orchestration/types.d.ts +74 -0
  92. package/dist/orchestration/types.js +8 -0
  93. package/dist/orchestration/types.js.map +1 -0
  94. package/dist/orchestration/verifier.d.ts +14 -0
  95. package/dist/orchestration/verifier.js +66 -0
  96. package/dist/orchestration/verifier.js.map +1 -0
  97. package/dist/orchestration/worker.d.ts +16 -0
  98. package/dist/orchestration/worker.js +80 -0
  99. package/dist/orchestration/worker.js.map +1 -0
  100. package/dist/workspace/config.d.ts +69 -2
  101. package/dist/workspace/config.js +20 -0
  102. package/dist/workspace/config.js.map +1 -1
  103. package/dist/workspace/init.js +2 -0
  104. package/dist/workspace/init.js.map +1 -1
  105. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -1,121 +1,25 @@
1
1
  #!/usr/bin/env node
2
- import { Command } from 'commander';
3
- import fs from 'node:fs/promises';
4
- import { initWorkspace, workspaceStatus } from './workspace/init.js';
5
- import { ArtifactEngine } from './artifacts/engine.js';
6
- import { viewArtifacts } from './export/view.js';
7
- import path from 'node:path';
8
- import { renderArtifactFile } from './artifacts/storage.js';
9
- import { SpegoError } from './errors.js';
10
- import { COMMAND_REGISTRY } from './commands/registry.js';
11
- import { WORKFLOW_REGISTRY } from './workflows/registry.js';
12
- import { generateAll } from './generator/index.js';
13
- import { resolveWorkspacePaths } from './workspace/paths.js';
14
- import { readConfig } from './workspace/config.js';
15
- import { resolveAdapter } from './delivery/index.js';
16
- import { renderBox, renderTable, renderHeader, renderDivider } from './cli/render.js';
17
- import pkg from '../package.json' with { type: 'json' };
18
- // ---------- output helpers ----------
19
- function emitJson(payload) {
20
- process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
21
- }
22
- function emitHuman(text) {
23
- process.stdout.write(text.endsWith('\n') ? text : `${text}\n`);
24
- }
25
- function output(json, payload, human) {
26
- if (json) {
27
- emitJson(payload);
28
- }
29
- else {
30
- emitHuman(human());
31
- }
32
- }
33
- function deprecate(json, message) {
34
- if (!json) {
35
- process.stderr.write(`deprecated: ${message}\n`);
36
- }
37
- }
38
- async function readBodyOption(opts) {
39
- if (opts.bodyFile)
40
- return fs.readFile(opts.bodyFile, 'utf8');
41
- return opts.body;
42
- }
43
- // ---------- per-domain renderers ----------
44
- function renderEpicRow(epic) {
45
- const progress = epic.taskCount != null ? `${epic.tasksDone ?? 0}/${epic.taskCount}` : '—';
46
- return [epic.externalId, epic.status, progress, epic.title];
47
- }
48
- function renderTaskRow(task) {
49
- return [task.status === 'done' ? '[x]' : '[ ]', task.title];
50
- }
51
- function renderArtifactHeader(fm) {
52
- return `📄 ${fm.type}/${fm.slug} rev ${fm.revision} (${fm.id})`;
53
- }
54
2
  /**
55
- * Insert `─` dividers between consecutive artifact sections in a markdown
56
- * bundle. We mark a section by `## ` at the start of a line; the first
57
- * occurrence is preserved as-is, every subsequent occurrence is prefixed
58
- * with `\n<divider>\n\n`.
3
+ * spego CLI orchestrator.
4
+ *
5
+ * `buildProgram` wires the global `--json` flag, the program-level help and
6
+ * version handling, and one registrar per command group. Every command body
7
+ * lives under `src/cli/commands/` and routes through the shared runtime helper
8
+ * in `src/cli/runtime.ts`. Error formatting is centralized in `src/cli/errors.ts`.
59
9
  */
60
- function intersperseBundleDividers(markdown) {
61
- const lines = markdown.split('\n');
62
- const out = [];
63
- let seenFirstSection = false;
64
- const divider = renderDivider();
65
- for (const line of lines) {
66
- if (line.startsWith('## ')) {
67
- if (seenFirstSection) {
68
- out.push('', divider, '');
69
- }
70
- seenFirstSection = true;
71
- }
72
- out.push(line);
73
- }
74
- return out.join('\n');
75
- }
76
- function renderCommandsHuman() {
77
- const order = ['workspace', 'artifact', 'view', 'planning'];
78
- const titles = {
79
- workspace: 'Workspace',
80
- artifact: 'Artifacts',
81
- view: 'View',
82
- planning: 'Planning',
83
- };
84
- const sections = order
85
- .map((cat) => {
86
- const items = COMMAND_REGISTRY.filter((c) => c.category === cat);
87
- if (items.length === 0)
88
- return '';
89
- const table = renderTable(['name', 'description'], items.map((c) => [c.name, c.description]));
90
- return `${titles[cat]}\n${table}`;
91
- })
92
- .filter((s) => s.length > 0);
93
- return [renderHeader('🛠️', 'Commands'), '', sections.join('\n\n')].join('\n');
94
- }
95
- function renderWorkflowsHuman() {
96
- const rows = WORKFLOW_REGISTRY.map((w) => [w.name, String(w.phases.length), w.description]);
97
- const table = renderTable(['name', 'phases', 'description'], rows, { maxWidth: 80 });
98
- return [renderHeader('🧭', 'Workflows'), '', table].join('\n');
99
- }
100
- // ---------- error rendering ----------
101
- function fail(err, json) {
102
- if (err instanceof SpegoError) {
103
- if (json) {
104
- process.stderr.write(`${JSON.stringify({ error: err.toJSON() }, null, 2)}\n`);
105
- }
106
- else {
107
- process.stderr.write(`⚠️ [${err.code}] ${err.message}\n`);
108
- }
109
- process.exit(2);
110
- }
111
- if (json) {
112
- process.stderr.write(`${JSON.stringify({ error: { code: 'INTERNAL', message: err.message } }, null, 2)}\n`);
113
- }
114
- else {
115
- process.stderr.write(`⚠️ [INTERNAL] ${err.message}\n`);
116
- }
117
- process.exit(1);
118
- }
10
+ import { Command } from 'commander';
11
+ import { realpathSync } from 'node:fs';
12
+ import { fileURLToPath } from 'node:url';
13
+ import pkg from '../package.json' with { type: 'json' };
14
+ import { fail, failValidation } from './cli/errors.js';
15
+ import { registerMeta } from './cli/commands/meta.js';
16
+ import { registerWorkspace } from './cli/commands/workspace.js';
17
+ import { registerArtifact } from './cli/commands/artifact.js';
18
+ import { registerView } from './cli/commands/view.js';
19
+ import { registerIndex } from './cli/commands/index-rebuild.js';
20
+ import { registerSkills } from './cli/commands/skills.js';
21
+ import { registerDelivery } from './cli/commands/delivery.js';
22
+ import { registerOrchestrate } from './cli/commands/orchestrate.js';
119
23
  export function buildProgram() {
120
24
  const program = new Command();
121
25
  program
@@ -132,474 +36,53 @@ export function buildProgram() {
132
36
  },
133
37
  });
134
38
  program.exitOverride((err) => {
135
- const json = program.opts().json;
136
- if (json) {
137
- process.stderr.write(`${JSON.stringify({ error: { code: 'VALIDATION_FAILED', message: err.message } }, null, 2)}\n`);
138
- }
139
- else {
140
- process.stderr.write(`⚠️ [VALIDATION_FAILED] ${err.message}\n`);
141
- }
142
- process.exit(2);
143
- });
144
- // ---------- meta: commands ----------
145
- program
146
- .command('commands')
147
- .description('List available spego commands with metadata')
148
- .action(() => {
149
- const json = program.opts().json;
150
- output(json, COMMAND_REGISTRY, renderCommandsHuman);
151
- });
152
- // ---------- meta: workflows ----------
153
- program
154
- .command('workflows')
155
- .description('List available workflow skills')
156
- .action(() => {
157
- const json = program.opts().json;
158
- const entries = WORKFLOW_REGISTRY.map((w) => ({
159
- name: w.name,
160
- description: w.description,
161
- personas: w.personas,
162
- phases: w.phases,
163
- inputs: w.inputs,
164
- outputs: w.outputs,
165
- }));
166
- output(json, entries, renderWorkflowsHuman);
167
- });
168
- // ---------- init ----------
169
- program
170
- .command('init')
171
- .description('Initialize a .spego/ workspace in the current project')
172
- .option('--agents <list>', 'comma-separated list of agent toolchains', 'claude')
173
- .option('--demo <bool>', 'seed demo artifacts (true|false)', 'false')
174
- .option('--force', 'overwrite config (does not delete artifacts)', false)
175
- .option('--cwd <dir>', 'project root (defaults to current directory)')
176
- .action(async (opts) => {
177
- const json = program.opts().json;
178
- try {
179
- const summary = await initWorkspace({
180
- projectRoot: opts.cwd,
181
- agents: String(opts.agents).split(',').map((s) => s.trim()).filter(Boolean),
182
- demo: opts.demo === 'true',
183
- force: opts.force,
184
- });
185
- output(json, summary, () => {
186
- const headerLabel = summary.alreadyInitialized
187
- ? 'Workspace already initialized'
188
- : 'Initialized spego workspace';
189
- const box = renderBox('Workspace', [
190
- ['root', summary.workspaceRoot],
191
- ['config', summary.configPath],
192
- ['artifacts', summary.artifactsRoot],
193
- ['index', summary.indexPath],
194
- ['agents', summary.config.agents.join(', ')],
195
- ]);
196
- return `${renderHeader('📦', headerLabel)}\n${box}`;
197
- });
198
- }
199
- catch (err) {
200
- fail(err, json);
201
- }
202
- });
203
- // ---------- status ----------
204
- program
205
- .command('status')
206
- .description('Report workspace status')
207
- .option('--cwd <dir>', 'project root')
208
- .action(async (opts) => {
209
- const json = program.opts().json;
210
- try {
211
- const status = await workspaceStatus(opts.cwd);
212
- output(json, status, () => {
213
- if (!status.initialized) {
214
- return `No spego workspace; run: spego init`;
215
- }
216
- const box = renderBox('Workspace status', [
217
- ['root', status.workspaceRoot ?? ''],
218
- ['agents', status.agents?.join(', ') ?? '(none)'],
219
- ['artifacts', status.artifactsRoot ?? ''],
220
- ['index', status.indexPath ?? ''],
221
- ]);
222
- return `${renderHeader('📦', 'Workspace ready')}\n${box}`;
223
- });
224
- }
225
- catch (err) {
226
- fail(err, json);
227
- }
228
- });
229
- // ---------- create ----------
230
- program
231
- .command('create')
232
- .description('Create an artifact')
233
- .requiredOption('--type <type>', 'artifact type, e.g. prd|api|architecture')
234
- .requiredOption('--title <title>', 'artifact title')
235
- .option('--slug <slug>', 'optional explicit slug')
236
- .option('--body <text>', 'inline markdown body')
237
- .option('--body-file <path>', 'read markdown body from a file (use - for stdin)')
238
- .option('--meta <json>', 'JSON object of metadata', '{}')
239
- .option('--cwd <dir>', 'project root')
240
- .action(async (opts) => {
241
- const json = program.opts().json;
242
- const engine = await ArtifactEngine.open({ projectRoot: opts.cwd }).catch((err) => fail(err, json));
243
- try {
244
- const body = opts.bodyFile === '-'
245
- ? await new Promise((resolve, reject) => {
246
- let buf = '';
247
- process.stdin.setEncoding('utf8');
248
- process.stdin.on('data', (chunk) => (buf += chunk));
249
- process.stdin.on('end', () => resolve(buf));
250
- process.stdin.on('error', reject);
251
- })
252
- : (await readBodyOption(opts)) ?? '';
253
- const meta = JSON.parse(opts.meta);
254
- const record = await engine.create({
255
- type: opts.type,
256
- title: opts.title,
257
- slug: opts.slug,
258
- body,
259
- meta,
260
- });
261
- output(json, {
262
- id: record.frontmatter.id,
263
- type: record.frontmatter.type,
264
- slug: record.frontmatter.slug,
265
- revision: record.frontmatter.revision,
266
- path: record.path,
267
- markdown: renderArtifactFile(record.frontmatter, record.body),
268
- }, () => `✨ Created ${record.frontmatter.type}/${record.frontmatter.slug} rev ${record.frontmatter.revision}\n path: ${record.path}`);
269
- }
270
- catch (err) {
271
- fail(err, json);
272
- }
273
- finally {
274
- engine.close();
275
- }
276
- });
277
- // ---------- read ----------
278
- program
279
- .command('read')
280
- .description('Read an artifact by id or type+slug')
281
- .option('--id <id>', 'artifact id')
282
- .option('--type <type>', 'artifact type (with --slug)')
283
- .option('--slug <slug>', 'artifact slug (with --type)')
284
- .option('--path <path>', 'read artifact by absolute file path')
285
- .option('--revision <n>', 'specific revision number')
286
- .option('--cwd <dir>', 'project root')
287
- .action(async (opts) => {
288
- const json = program.opts().json;
289
- const engine = await ArtifactEngine.open({ projectRoot: opts.cwd }).catch((err) => fail(err, json));
290
- try {
291
- let record;
292
- if (opts.id) {
293
- record =
294
- opts.revision !== undefined
295
- ? await engine.readRevision(opts.id, Number(opts.revision))
296
- : await engine.readById(opts.id);
297
- }
298
- else if (opts.type && opts.slug) {
299
- record = await engine.readByTypeSlug(opts.type, opts.slug);
300
- }
301
- else if (opts.path) {
302
- record = await engine.readByPath(path.resolve(opts.path));
303
- }
304
- else {
305
- throw new SpegoError('VALIDATION_FAILED', 'Provide --id, --type and --slug, or --path');
306
- }
307
- output(json, { frontmatter: record.frontmatter, body: record.body, path: record.path }, () => `${renderArtifactHeader(record.frontmatter)}\n${renderDivider()}\n${record.body}`);
308
- }
309
- catch (err) {
310
- fail(err, json);
311
- }
312
- finally {
313
- engine.close();
314
- }
315
- });
316
- // ---------- update ----------
317
- program
318
- .command('update')
319
- .description('Update an artifact (requires id)')
320
- .requiredOption('--id <id>', 'artifact id')
321
- .option('--title <title>', 'new title')
322
- .option('--body <text>', 'new markdown body')
323
- .option('--body-file <path>', 'read body from file')
324
- .option('--meta <json>', 'replace metadata (JSON object)')
325
- .option('--expected-revision <n>', 'expected current revision (optimistic concurrency)')
326
- .option('--cwd <dir>', 'project root')
327
- .action(async (opts) => {
328
- const json = program.opts().json;
329
- const engine = await ArtifactEngine.open({ projectRoot: opts.cwd }).catch((err) => fail(err, json));
330
- try {
331
- const body = await readBodyOption(opts);
332
- const patch = {};
333
- if (opts.title !== undefined)
334
- patch.title = opts.title;
335
- if (body !== undefined)
336
- patch.body = body;
337
- if (opts.meta !== undefined)
338
- patch.meta = JSON.parse(opts.meta);
339
- if (opts.expectedRevision !== undefined)
340
- patch.expectedRevision = Number(opts.expectedRevision);
341
- const record = await engine.update(opts.id, patch);
342
- output(json, { id: record.frontmatter.id, revision: record.frontmatter.revision, path: record.path }, () => `${renderArtifactHeader(record.frontmatter)}\n${renderDivider()}\n${record.body}`);
343
- }
344
- catch (err) {
345
- fail(err, json);
346
- }
347
- finally {
348
- engine.close();
349
- }
350
- });
351
- // ---------- delete ----------
352
- program
353
- .command('delete')
354
- .description('Soft-delete an artifact')
355
- .requiredOption('--id <id>', 'artifact id')
356
- .option('--cwd <dir>', 'project root')
357
- .action(async (opts) => {
358
- const json = program.opts().json;
359
- const engine = await ArtifactEngine.open({ projectRoot: opts.cwd }).catch((err) => fail(err, json));
360
- try {
361
- const record = await engine.softDelete(opts.id);
362
- output(json, {
363
- id: record.frontmatter.id,
364
- deletedAt: record.frontmatter.deletedAt,
365
- revision: record.frontmatter.revision,
366
- }, () => `🗑 Deleted ${record.frontmatter.id} at ${record.frontmatter.deletedAt}`);
367
- }
368
- catch (err) {
369
- fail(err, json);
370
- }
371
- finally {
372
- engine.close();
373
- }
374
- });
375
- // ---------- list ----------
376
- program
377
- .command('list')
378
- .description('List artifacts')
379
- .option('--type <type>', 'filter by type')
380
- .option('--include-deleted', 'include soft-deleted artifacts', false)
381
- .option('--cwd <dir>', 'project root')
382
- .action(async (opts) => {
383
- const json = program.opts().json;
384
- const engine = await ArtifactEngine.open({ projectRoot: opts.cwd }).catch((err) => fail(err, json));
385
- try {
386
- const items = engine.list({ type: opts.type, includeDeleted: opts.includeDeleted });
387
- output(json, items, () => {
388
- if (items.length === 0)
389
- return 'No artifacts.';
390
- const rows = items.map((a) => [
391
- `${a.type}/${a.slug}`,
392
- String(a.revision),
393
- a.deletedAt ? 'deleted' : 'active',
394
- a.title,
395
- a.id,
396
- ]);
397
- const table = renderTable(['type/slug', 'rev', 'status', 'title', 'id'], rows);
398
- return `${renderHeader('📦', 'Artifacts')}\n${table}`;
399
- });
400
- }
401
- catch (err) {
402
- fail(err, json);
403
- }
404
- finally {
405
- engine.close();
406
- }
407
- });
408
- // ---------- view ----------
409
- program
410
- .command('view')
411
- .description('Export artifact bundle as markdown (default) or JSON via --json')
412
- .option('--type <type>', 'filter by type')
413
- .option('--id <id>', 'limit to a single artifact')
414
- .option('--revision <n>', 'specific revision (requires --id)')
415
- .option('--include-deleted', 'include soft-deleted artifacts', false)
416
- .option('--format <fmt>', '[deprecated] markdown | json — use the global --json flag instead')
417
- .option('--cwd <dir>', 'project root')
418
- .action(async (opts) => {
419
- const globalJson = program.opts().json;
420
- // Deprecation: --format is still honored but warns in human mode.
421
- if (opts.format !== undefined) {
422
- deprecate(globalJson, "--format is deprecated; use the global --json flag");
423
- }
424
- const wantJson = globalJson || opts.format === 'json';
425
- const engine = await ArtifactEngine.open({ projectRoot: opts.cwd }).catch((err) => fail(err, wantJson));
426
- try {
427
- const view = await viewArtifacts(engine, {
428
- type: opts.type,
429
- id: opts.id,
430
- revision: opts.revision !== undefined ? Number(opts.revision) : undefined,
431
- includeDeleted: opts.includeDeleted,
432
- });
433
- if (wantJson) {
434
- emitJson(view.json);
435
- }
436
- else {
437
- const body = intersperseBundleDividers(view.markdown.content);
438
- process.stdout.write(`${renderHeader('📦', 'Artifact bundle')}\n${body}`);
439
- }
440
- }
441
- catch (err) {
442
- fail(err, wantJson);
443
- }
444
- finally {
445
- engine.close();
446
- }
447
- });
448
- // ---------- index rebuild ----------
449
- const indexCmd = program.command('index').description('Index maintenance');
450
- // ---------- skills ----------
451
- async function runSkillsCommand(json, cwd) {
452
- const projectRoot = path.resolve(cwd ?? process.cwd());
453
- const wsPaths = resolveWorkspacePaths(projectRoot);
454
- const config = await readConfig(wsPaths.configPath);
455
- const reports = await generateAll(projectRoot, config.agents, { emitWarnings: !json });
456
- output(json, reports, () => {
457
- const lines = [renderHeader('🛠️', 'Skill regeneration'), ''];
458
- for (const r of reports) {
459
- lines.push(`[${r.target}]`);
460
- for (const f of r.files) {
461
- lines.push(` ${f.action} ${f.path}`);
462
- }
463
- }
464
- return lines.join('\n');
39
+ if (err.code === 'commander.help' ||
40
+ err.code === 'commander.helpDisplayed' ||
41
+ err.code === 'commander.version') {
42
+ process.exit(err.exitCode ?? 0);
43
+ }
44
+ failValidation(err, program.opts().json);
45
+ });
46
+ registerMeta(program);
47
+ registerWorkspace(program);
48
+ registerArtifact(program);
49
+ registerView(program);
50
+ registerIndex(program);
51
+ registerSkills(program);
52
+ registerDelivery(program);
53
+ registerOrchestrate(program);
54
+ return program;
55
+ }
56
+ /**
57
+ * Detect whether this module is the process entry point. Without this guard,
58
+ * importing `buildProgram` from a test would execute `parseAsync` against the
59
+ * test runner's own argv and either error out or terminate the process.
60
+ */
61
+ function isEntryPoint() {
62
+ if (typeof process === 'undefined')
63
+ return false;
64
+ const argv1 = process.argv[1];
65
+ if (!argv1)
66
+ return false;
67
+ try {
68
+ return realpathSync(argv1) === fileURLToPath(import.meta.url);
69
+ }
70
+ catch {
71
+ return false;
72
+ }
73
+ }
74
+ if (isEntryPoint()) {
75
+ const program = buildProgram();
76
+ const args = process.argv;
77
+ if (args.length <= 2) {
78
+ program.parseAsync([...args, '--help']).catch((err) => {
79
+ fail(err, program.opts().json);
80
+ });
81
+ }
82
+ else {
83
+ program.parseAsync(args).catch((err) => {
84
+ fail(err, program.opts().json);
465
85
  });
466
86
  }
467
- program
468
- .command('skills')
469
- .description('Regenerate agent skill and command files')
470
- .option('--cwd <dir>', 'project root')
471
- .action(async (opts) => {
472
- const json = program.opts().json;
473
- try {
474
- await runSkillsCommand(json, opts.cwd);
475
- }
476
- catch (err) {
477
- fail(err, json);
478
- }
479
- });
480
- program
481
- .command('regenerate', { hidden: true })
482
- .description('Regenerate agent skill and command files')
483
- .option('--cwd <dir>', 'project root')
484
- .action(async (opts) => {
485
- const json = program.opts().json;
486
- deprecate(json, "'spego regenerate' is now 'spego skills'");
487
- try {
488
- await runSkillsCommand(json, opts.cwd);
489
- }
490
- catch (err) {
491
- fail(err, json);
492
- }
493
- });
494
- // ---------- epics ----------
495
- program
496
- .command('epics')
497
- .description('List epics or get a single epic')
498
- .argument('[action]', 'list (default) or get')
499
- .option('--change <name>', 'get a single epic by name')
500
- .option('--cwd <dir>', 'project root')
501
- .action(async (action, opts) => {
502
- const json = program.opts().json;
503
- try {
504
- if (action && !['list', 'get'].includes(action)) {
505
- throw new SpegoError('DELIVERY_READ_ONLY', `Cannot '${action}' delivery epics through spego. Use the OpenSpec CLI for delivery mutations.`, { attemptedAction: action, suggestedTool: 'openspec' });
506
- }
507
- const projectRoot = path.resolve(opts.cwd ?? process.cwd());
508
- const wsPaths = resolveWorkspacePaths(projectRoot);
509
- const config = await readConfig(wsPaths.configPath);
510
- const adapter = resolveAdapter(projectRoot, config);
511
- if (opts.change) {
512
- const epic = await adapter.getEpic(opts.change);
513
- output(json, epic, () => {
514
- const table = renderTable(['id', 'status', 'progress', 'title'], [renderEpicRow(epic)]);
515
- return `${renderHeader('📚', `Epic (${adapter.name})`)}\n${table}`;
516
- });
517
- }
518
- else {
519
- const epics = await adapter.listEpics();
520
- output(json, epics, () => {
521
- if (epics.length === 0)
522
- return 'No epics.';
523
- const table = renderTable(['id', 'status', 'progress', 'title'], epics.map(renderEpicRow));
524
- return `${renderHeader('📚', `Epics (${adapter.name})`)}\n${table}`;
525
- });
526
- }
527
- }
528
- catch (err) {
529
- fail(err, json);
530
- }
531
- });
532
- // ---------- tasks ----------
533
- program
534
- .command('tasks')
535
- .description('List tasks for a change or get a single task')
536
- .argument('[action]', 'list (default) or get')
537
- .requiredOption('--change <name>', 'the epic/change name')
538
- .option('--task <id>', 'get a single task by id')
539
- .option('--cwd <dir>', 'project root')
540
- .action(async (action, opts) => {
541
- const json = program.opts().json;
542
- try {
543
- if (action && !['list', 'get'].includes(action)) {
544
- throw new SpegoError('DELIVERY_READ_ONLY', `Cannot '${action}' delivery tasks through spego. Use the OpenSpec CLI for delivery mutations.`, { attemptedAction: action, suggestedTool: 'openspec' });
545
- }
546
- const projectRoot = path.resolve(opts.cwd ?? process.cwd());
547
- const wsPaths = resolveWorkspacePaths(projectRoot);
548
- const config = await readConfig(wsPaths.configPath);
549
- const adapter = resolveAdapter(projectRoot, config);
550
- if (opts.task) {
551
- const task = await adapter.getTask(opts.change, opts.task);
552
- output(json, task, () => {
553
- const table = renderTable(['done', 'title'], [renderTaskRow(task)]);
554
- return `${renderHeader('✅', `Task: ${opts.change} (${adapter.name})`)}\n${table}`;
555
- });
556
- }
557
- else {
558
- const tasks = await adapter.listTasks(opts.change);
559
- output(json, tasks, () => {
560
- if (tasks.length === 0)
561
- return `No tasks for ${opts.change}.`;
562
- const table = renderTable(['done', 'title'], tasks.map(renderTaskRow));
563
- return `${renderHeader('✅', `Tasks: ${opts.change} (${adapter.name})`)}\n${table}`;
564
- });
565
- }
566
- }
567
- catch (err) {
568
- fail(err, json);
569
- }
570
- });
571
- indexCmd
572
- .command('rebuild')
573
- .description('Rebuild the SQLite index from markdown files')
574
- .option('--cwd <dir>', 'project root')
575
- .action(async (opts) => {
576
- const json = program.opts().json;
577
- const engine = await ArtifactEngine.open({ projectRoot: opts.cwd }).catch((err) => fail(err, json));
578
- try {
579
- const report = await engine.rebuildIndex();
580
- output(json, report, () => {
581
- const summary = renderBox('Index rebuild', [
582
- ['scanned', String(report.scannedFiles)],
583
- ['indexed', String(report.indexedArtifacts)],
584
- ['invalid', String(report.invalidFiles.length)],
585
- ]);
586
- if (report.invalidFiles.length === 0)
587
- return summary;
588
- const table = renderTable(['path', 'error'], report.invalidFiles.map((i) => [i.path, i.error]), { maxWidth: 80 });
589
- return `${summary}\n\nInvalid files:\n${table}`;
590
- });
591
- }
592
- catch (err) {
593
- fail(err, json);
594
- }
595
- finally {
596
- engine.close();
597
- }
598
- });
599
- return program;
600
87
  }
601
- const program = buildProgram();
602
- program.parseAsync(process.argv).catch((err) => {
603
- fail(err, program.opts().json);
604
- });
605
88
  //# sourceMappingURL=cli.js.map