gyrus 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +273 -0
  3. package/package.json +78 -0
  4. package/src/commands/adr.ts +482 -0
  5. package/src/commands/doctor.ts +263 -0
  6. package/src/commands/init.ts +293 -0
  7. package/src/commands/knowledge.ts +446 -0
  8. package/src/commands/list.ts +51 -0
  9. package/src/commands/mcp.ts +94 -0
  10. package/src/commands/use.ts +65 -0
  11. package/src/config/index.ts +262 -0
  12. package/src/config/schema.ts +139 -0
  13. package/src/formatters/adr.ts +295 -0
  14. package/src/formatters/index.ts +44 -0
  15. package/src/formatters/knowledge.ts +282 -0
  16. package/src/formatters/workspace.ts +149 -0
  17. package/src/index.ts +153 -0
  18. package/src/operations/adr.ts +312 -0
  19. package/src/operations/index.ts +58 -0
  20. package/src/operations/knowledge.ts +324 -0
  21. package/src/operations/types.ts +199 -0
  22. package/src/operations/workspace.ts +108 -0
  23. package/src/server/mcp-stdio.ts +526 -0
  24. package/src/services/adr.ts +511 -0
  25. package/src/services/knowledge.ts +516 -0
  26. package/src/services/markdown.ts +155 -0
  27. package/src/services/workspace.ts +377 -0
  28. package/src/templates/knowledge/README.md +199 -0
  29. package/src/tools/adr-create.ts +99 -0
  30. package/src/tools/adr-list.ts +72 -0
  31. package/src/tools/adr-read.ts +54 -0
  32. package/src/tools/adr-search.ts +79 -0
  33. package/src/tools/adr-update.ts +95 -0
  34. package/src/tools/gyrus-list.ts +41 -0
  35. package/src/tools/gyrus-switch.ts +45 -0
  36. package/src/tools/index.ts +91 -0
  37. package/src/tools/knowledge-create.ts +75 -0
  38. package/src/tools/knowledge-list.ts +60 -0
  39. package/src/tools/knowledge-read.ts +62 -0
  40. package/src/tools/knowledge-search.ts +65 -0
  41. package/src/tools/knowledge-update.ts +76 -0
  42. package/src/types/index.ts +343 -0
  43. package/tsconfig.json +26 -0
@@ -0,0 +1,482 @@
1
+ /**
2
+ * ADR CLI Command
3
+ * Handles all ADR-related CLI operations
4
+ *
5
+ * Usage:
6
+ * gyrus adr list [--workspace <name>] [--status <status>] [--type <type>] [--tags <t1,t2>]
7
+ * gyrus adr read <name> [--workspace <name>]
8
+ * gyrus adr search <query> [--workspace <name>] [--status <status>] [--type <type>] [--tags <t1,t2>]
9
+ * gyrus adr create --title <title> --type <type> --working-folder <path> [options]
10
+ * gyrus adr update <name> [--title <title>] [--status <status>] [--content <content>]
11
+ *
12
+ * Aliases: gyrus a
13
+ */
14
+
15
+ import { readConfig } from '../config/index.ts';
16
+ import { WorkspaceManager } from '../services/workspace.ts';
17
+ import {
18
+ listAdrs,
19
+ readAdr,
20
+ searchAdrs,
21
+ createAdr,
22
+ updateAdr,
23
+ } from '../operations/adr.ts';
24
+ import {
25
+ formatAdrListCli,
26
+ formatAdrReadCli,
27
+ formatAdrSearchCli,
28
+ formatAdrCreateCli,
29
+ formatAdrUpdateCli,
30
+ } from '../formatters/adr.ts';
31
+ import type { AdrStatus, AdrType } from '../types/index.ts';
32
+
33
+ const HELP = `
34
+ Usage: gyrus adr <subcommand> [options]
35
+
36
+ Subcommands:
37
+ list List all ADRs
38
+ read <name> Read a specific ADR by name (partial match supported)
39
+ search <query> Search ADRs by text query
40
+ create Create a new ADR
41
+ update <name> Update an existing ADR
42
+
43
+ Global Options:
44
+ -w, --workspace <name> Target workspace (default: active workspace)
45
+ -h, --help Show this help message
46
+
47
+ List Options:
48
+ -s, --status <status> Filter by status (todo, in-progress, in-review, blocked, completed, deprecated)
49
+ --type <type> Filter by type (enhancement, debug, research)
50
+ -t, --tags <t1,t2,...> Filter by tags (comma-separated)
51
+
52
+ Search Options:
53
+ -s, --status <status> Filter by status
54
+ --type <type> Filter by type
55
+ -t, --tags <t1,t2,...> Filter by tags
56
+
57
+ Create Options:
58
+ --title <title> ADR title (required)
59
+ --type <type> ADR type: enhancement, debug, research (required)
60
+ --working-folder <path> Working folder path (required)
61
+ --description <desc> Brief description
62
+ -t, --tags <t1,t2,...> Tags (comma-separated)
63
+ -s, --status <status> Initial status (default: todo)
64
+ --content <content> Custom content (or use --file)
65
+ --file <path> Read content from file
66
+
67
+ Update Options:
68
+ --title <title> New title
69
+ --description <desc> New description
70
+ -s, --status <status> New status
71
+ --type <type> New type
72
+ -t, --tags <t1,t2,...> New tags (replaces existing)
73
+ --content <content> New content (or use --file)
74
+ --file <path> Read content from file
75
+ --working-folder <path> New working folder
76
+
77
+ Examples:
78
+ gyrus adr list
79
+ gyrus adr list --status in-progress
80
+ gyrus adr read brain-cli
81
+ gyrus adr search "authentication" --type enhancement
82
+ gyrus adr create --title "New Feature" --type enhancement --working-folder /path/to/project
83
+ gyrus adr update brain-cli --status completed
84
+ gyrus a list -w work
85
+
86
+ Aliases: a
87
+ `;
88
+
89
+ /**
90
+ * Parse command-line arguments into options object
91
+ */
92
+ function parseArgs(args: string[]): {
93
+ subcommand: string;
94
+ positional: string[];
95
+ options: Record<string, string | boolean>;
96
+ } {
97
+ const options: Record<string, string | boolean> = {};
98
+ const positional: string[] = [];
99
+ let subcommand = '';
100
+
101
+ let i = 0;
102
+
103
+ // First non-flag argument is the subcommand
104
+ while (i < args.length && !subcommand) {
105
+ if (!args[i].startsWith('-')) {
106
+ subcommand = args[i];
107
+ i++;
108
+ break;
109
+ }
110
+ i++;
111
+ }
112
+
113
+ // Parse remaining arguments
114
+ while (i < args.length) {
115
+ const arg = args[i];
116
+
117
+ if (arg === '-h' || arg === '--help') {
118
+ options.help = true;
119
+ i++;
120
+ } else if (arg === '-w' || arg === '--workspace') {
121
+ options.workspace = args[++i] || '';
122
+ i++;
123
+ } else if (arg === '-s' || arg === '--status') {
124
+ options.status = args[++i] || '';
125
+ i++;
126
+ } else if (arg === '--type') {
127
+ options.type = args[++i] || '';
128
+ i++;
129
+ } else if (arg === '-t' || arg === '--tags') {
130
+ options.tags = args[++i] || '';
131
+ i++;
132
+ } else if (arg === '--title') {
133
+ options.title = args[++i] || '';
134
+ i++;
135
+ } else if (arg === '--description') {
136
+ options.description = args[++i] || '';
137
+ i++;
138
+ } else if (arg === '--content') {
139
+ options.content = args[++i] || '';
140
+ i++;
141
+ } else if (arg === '--file') {
142
+ options.file = args[++i] || '';
143
+ i++;
144
+ } else if (arg === '--working-folder') {
145
+ options.workingFolder = args[++i] || '';
146
+ i++;
147
+ } else if (!arg.startsWith('-')) {
148
+ positional.push(arg);
149
+ i++;
150
+ } else {
151
+ // Unknown flag, skip
152
+ i++;
153
+ }
154
+ }
155
+
156
+ return { subcommand, positional, options };
157
+ }
158
+
159
+ /**
160
+ * Parse comma-separated string into array
161
+ */
162
+ function parseList(value: string | undefined): string[] | undefined {
163
+ if (!value || typeof value !== 'string') return undefined;
164
+ return value
165
+ .split(',')
166
+ .map((s) => s.trim())
167
+ .filter(Boolean);
168
+ }
169
+
170
+ /**
171
+ * Read content from file if --file option is provided
172
+ */
173
+ async function getContent(
174
+ options: Record<string, string | boolean>
175
+ ): Promise<string | undefined> {
176
+ if (options.file && typeof options.file === 'string') {
177
+ try {
178
+ const file = Bun.file(options.file);
179
+ return await file.text();
180
+ } catch (error) {
181
+ console.error(`Error reading file: ${options.file}`);
182
+ process.exit(1);
183
+ }
184
+ }
185
+ return typeof options.content === 'string' ? options.content : undefined;
186
+ }
187
+
188
+ /**
189
+ * Validate status value
190
+ */
191
+ function validateStatus(status: string | undefined): AdrStatus | undefined {
192
+ if (!status) return undefined;
193
+ const valid: AdrStatus[] = [
194
+ 'todo',
195
+ 'in-progress',
196
+ 'in-review',
197
+ 'blocked',
198
+ 'completed',
199
+ 'deprecated',
200
+ ];
201
+ if (!valid.includes(status as AdrStatus)) {
202
+ console.error(`Error: Invalid status "${status}"`);
203
+ console.error(`Valid statuses: ${valid.join(', ')}`);
204
+ process.exit(1);
205
+ }
206
+ return status as AdrStatus;
207
+ }
208
+
209
+ /**
210
+ * Validate type value
211
+ */
212
+ function validateType(type: string | undefined): AdrType | undefined {
213
+ if (!type) return undefined;
214
+ const valid: AdrType[] = ['enhancement', 'debug', 'research'];
215
+ if (!valid.includes(type as AdrType)) {
216
+ console.error(`Error: Invalid type "${type}"`);
217
+ console.error(`Valid types: ${valid.join(', ')}`);
218
+ process.exit(1);
219
+ }
220
+ return type as AdrType;
221
+ }
222
+
223
+ /**
224
+ * Handle the list subcommand
225
+ */
226
+ async function handleList(
227
+ manager: WorkspaceManager,
228
+ options: Record<string, string | boolean>
229
+ ): Promise<void> {
230
+ const result = await listAdrs(manager, {
231
+ workspace:
232
+ typeof options.workspace === 'string' ? options.workspace : undefined,
233
+ status: validateStatus(options.status as string),
234
+ type: validateType(options.type as string),
235
+ tags: parseList(options.tags as string),
236
+ });
237
+
238
+ if (!result.success) {
239
+ console.error(`Error: ${result.error}`);
240
+ process.exit(1);
241
+ }
242
+
243
+ console.log(formatAdrListCli(result.data!));
244
+ }
245
+
246
+ /**
247
+ * Handle the read subcommand
248
+ */
249
+ async function handleRead(
250
+ manager: WorkspaceManager,
251
+ positional: string[],
252
+ options: Record<string, string | boolean>
253
+ ): Promise<void> {
254
+ const name = positional[0];
255
+
256
+ if (!name) {
257
+ console.error('Error: Please provide an ADR name');
258
+ console.error('Usage: gyrus adr read <name>');
259
+ console.error('\nTip: You can use a partial name to search.');
260
+ process.exit(1);
261
+ }
262
+
263
+ const result = await readAdr(manager, {
264
+ workspace:
265
+ typeof options.workspace === 'string' ? options.workspace : undefined,
266
+ name,
267
+ });
268
+
269
+ if (!result.success) {
270
+ console.error(`Error: ${result.error}`);
271
+ process.exit(1);
272
+ }
273
+
274
+ console.log(formatAdrReadCli(result.data!));
275
+ }
276
+
277
+ /**
278
+ * Handle the search subcommand
279
+ */
280
+ async function handleSearch(
281
+ manager: WorkspaceManager,
282
+ positional: string[],
283
+ options: Record<string, string | boolean>
284
+ ): Promise<void> {
285
+ const query = positional[0];
286
+ const status = validateStatus(options.status as string);
287
+ const type = validateType(options.type as string);
288
+ const tags = parseList(options.tags as string);
289
+
290
+ if (!query && !status && !type && !tags?.length) {
291
+ console.error(
292
+ 'Error: Please provide a search query, status, type, or tags'
293
+ );
294
+ console.error(
295
+ 'Usage: gyrus adr search <query> [--status <status>] [--type <type>] [--tags <t1,t2>]'
296
+ );
297
+ process.exit(1);
298
+ }
299
+
300
+ const result = await searchAdrs(manager, {
301
+ workspace:
302
+ typeof options.workspace === 'string' ? options.workspace : undefined,
303
+ query,
304
+ status,
305
+ type,
306
+ tags,
307
+ });
308
+
309
+ if (!result.success) {
310
+ console.error(`Error: ${result.error}`);
311
+ process.exit(1);
312
+ }
313
+
314
+ console.log(formatAdrSearchCli(result.data!));
315
+ }
316
+
317
+ /**
318
+ * Handle the create subcommand
319
+ */
320
+ async function handleCreate(
321
+ manager: WorkspaceManager,
322
+ options: Record<string, string | boolean>
323
+ ): Promise<void> {
324
+ const title = typeof options.title === 'string' ? options.title : undefined;
325
+ const type = typeof options.type === 'string' ? options.type : undefined;
326
+ const workingFolder =
327
+ typeof options.workingFolder === 'string'
328
+ ? options.workingFolder
329
+ : undefined;
330
+ const content = await getContent(options);
331
+
332
+ if (!title || !type || !workingFolder) {
333
+ console.error('Error: Missing required options for create');
334
+ console.error('Required: --title, --type, --working-folder');
335
+ console.error('\nExample:');
336
+ console.error(
337
+ ' gyrus adr create --title "New Feature" --type enhancement --working-folder /path/to/project'
338
+ );
339
+ process.exit(1);
340
+ }
341
+
342
+ const validatedType = validateType(type);
343
+ if (!validatedType) {
344
+ process.exit(1);
345
+ }
346
+
347
+ const result = await createAdr(manager, {
348
+ workspace:
349
+ typeof options.workspace === 'string' ? options.workspace : undefined,
350
+ title,
351
+ type: validatedType,
352
+ workingFolder,
353
+ description:
354
+ typeof options.description === 'string' ? options.description : undefined,
355
+ tags: parseList(options.tags as string),
356
+ status: validateStatus(options.status as string),
357
+ content,
358
+ });
359
+
360
+ if (!result.success) {
361
+ console.error(`Error: ${result.error}`);
362
+ process.exit(1);
363
+ }
364
+
365
+ console.log(formatAdrCreateCli(result.data!));
366
+ }
367
+
368
+ /**
369
+ * Handle the update subcommand
370
+ */
371
+ async function handleUpdate(
372
+ manager: WorkspaceManager,
373
+ positional: string[],
374
+ options: Record<string, string | boolean>
375
+ ): Promise<void> {
376
+ const name = positional[0];
377
+
378
+ if (!name) {
379
+ console.error('Error: Please provide an ADR name to update');
380
+ console.error('Usage: gyrus adr update <name> [options]');
381
+ process.exit(1);
382
+ }
383
+
384
+ const content = await getContent(options);
385
+ const hasUpdates =
386
+ options.title ||
387
+ options.description ||
388
+ options.status ||
389
+ options.type ||
390
+ options.tags ||
391
+ content ||
392
+ options.workingFolder;
393
+
394
+ if (!hasUpdates) {
395
+ console.error('Error: No update options provided');
396
+ console.error(
397
+ 'Available options: --title, --description, --status, --type, --tags, --content, --file, --working-folder'
398
+ );
399
+ process.exit(1);
400
+ }
401
+
402
+ const result = await updateAdr(manager, {
403
+ workspace:
404
+ typeof options.workspace === 'string' ? options.workspace : undefined,
405
+ name,
406
+ title: typeof options.title === 'string' ? options.title : undefined,
407
+ description:
408
+ typeof options.description === 'string' ? options.description : undefined,
409
+ status: validateStatus(options.status as string),
410
+ type: validateType(options.type as string),
411
+ tags: parseList(options.tags as string),
412
+ content,
413
+ workingFolder:
414
+ typeof options.workingFolder === 'string'
415
+ ? options.workingFolder
416
+ : undefined,
417
+ });
418
+
419
+ if (!result.success) {
420
+ console.error(`Error: ${result.error}`);
421
+ process.exit(1);
422
+ }
423
+
424
+ console.log(formatAdrUpdateCli(result.data!));
425
+ }
426
+
427
+ /**
428
+ * Main ADR command handler
429
+ */
430
+ export async function adrCommand(args: string[]): Promise<void> {
431
+ const { subcommand, positional, options } = parseArgs(args);
432
+
433
+ // Show help if requested or no subcommand
434
+ if (options.help || !subcommand) {
435
+ console.log(HELP);
436
+ process.exit(0);
437
+ }
438
+
439
+ // Load config and create workspace manager
440
+ const config = readConfig();
441
+ if (!config) {
442
+ console.error('Error: No configuration found. Run `gyrus init` first.');
443
+ process.exit(1);
444
+ }
445
+
446
+ const manager = new WorkspaceManager(config);
447
+
448
+ // Route to subcommand handler
449
+ switch (subcommand) {
450
+ case 'list':
451
+ case 'ls':
452
+ await handleList(manager, options);
453
+ break;
454
+
455
+ case 'read':
456
+ case 'get':
457
+ case 'show':
458
+ await handleRead(manager, positional, options);
459
+ break;
460
+
461
+ case 'search':
462
+ case 'find':
463
+ await handleSearch(manager, positional, options);
464
+ break;
465
+
466
+ case 'create':
467
+ case 'new':
468
+ case 'add':
469
+ await handleCreate(manager, options);
470
+ break;
471
+
472
+ case 'update':
473
+ case 'edit':
474
+ await handleUpdate(manager, positional, options);
475
+ break;
476
+
477
+ default:
478
+ console.error(`Unknown subcommand: ${subcommand}`);
479
+ console.log('\nRun `gyrus adr --help` for usage information.');
480
+ process.exit(1);
481
+ }
482
+ }