all-hands-cli 0.1.4 → 0.1.6

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.
@@ -7,6 +7,7 @@
7
7
  * - ah specs list - List all specs grouped by domain_name
8
8
  * - ah specs complete <name> - Mark spec completed, move spec out of roadmap
9
9
  * - ah specs create <path> - Create spec: validate, assign branch, commit and push
10
+ * - ah specs graph - Render dependency graph with availability markers
10
11
  */
11
12
 
12
13
  import { Command } from 'commander';
@@ -349,6 +350,195 @@ export function register(program: Command): void {
349
350
  }
350
351
  });
351
352
 
353
+ // ah specs graph
354
+ specs
355
+ .command('graph')
356
+ .description('Render dependency graph showing spec relationships and availability')
357
+ .option('--json', 'Output as JSON')
358
+ .option('--roadmap', 'Only show roadmap/in-progress specs')
359
+ .action((options: { json?: boolean; roadmap?: boolean }) => {
360
+ const allSpecs = loadAllSpecGroups().flatMap((g) => g.specs);
361
+
362
+ if (allSpecs.length === 0) {
363
+ if (options.json) {
364
+ console.log(JSON.stringify({ success: true, count: 0, available: [], tree: [], summary: { completed: 0, in_progress: 0, roadmap: 0, available: 0 } }, null, 2));
365
+ } else {
366
+ console.log('No specs found.');
367
+ }
368
+ return;
369
+ }
370
+
371
+ // Index all specs by id for lookups
372
+ const specById = new Map<string, typeof allSpecs[0]>();
373
+ for (const spec of allSpecs) {
374
+ specById.set(spec.id, spec);
375
+ }
376
+
377
+ // Compute availability: roadmap + all deps completed (against full unfiltered set)
378
+ // Dangling deps (unknown spec ids) are treated as satisfied
379
+ function isAvailable(spec: typeof allSpecs[0]): boolean {
380
+ if (spec.status !== 'roadmap') return false;
381
+ return spec.dependencies.every((depId) => {
382
+ const dep = specById.get(depId);
383
+ return !dep || dep.status === 'completed';
384
+ });
385
+ }
386
+
387
+ const availableIds = allSpecs.filter(isAvailable).map((s) => s.id).sort();
388
+
389
+ // Build display set (filtered or full)
390
+ let displaySpecs = allSpecs;
391
+ if (options.roadmap) {
392
+ displaySpecs = allSpecs.filter((s) => s.status !== 'completed');
393
+ if (displaySpecs.length === 0) {
394
+ if (options.json) {
395
+ console.log(JSON.stringify({ success: true, count: 0, available: availableIds, tree: [], summary: { completed: allSpecs.filter((s) => s.status === 'completed').length, in_progress: 0, roadmap: 0, available: availableIds.length } }, null, 2));
396
+ } else {
397
+ console.log('No roadmap specs found.');
398
+ }
399
+ return;
400
+ }
401
+ }
402
+
403
+ const displayIds = new Set(displaySpecs.map((s) => s.id));
404
+
405
+ // Build parent→children edges and find roots in one pass
406
+ const childrenOf = new Map<string, string[]>();
407
+ const roots: string[] = [];
408
+ for (const spec of displaySpecs) {
409
+ const visibleDeps = spec.dependencies.filter((depId) => depId !== spec.id && displayIds.has(depId));
410
+ if (visibleDeps.length === 0) {
411
+ roots.push(spec.id);
412
+ }
413
+ for (const depId of visibleDeps) {
414
+ if (!childrenOf.has(depId)) childrenOf.set(depId, []);
415
+ childrenOf.get(depId)!.push(spec.id);
416
+ }
417
+ }
418
+
419
+ // Detect orphaned cycles: specs not reachable from roots
420
+ const reachable = new Set<string>();
421
+ function markReachable(id: string): void {
422
+ if (reachable.has(id)) return;
423
+ reachable.add(id);
424
+ for (const childId of childrenOf.get(id) || []) {
425
+ markReachable(childId);
426
+ }
427
+ }
428
+ for (const rootId of roots) {
429
+ markReachable(rootId);
430
+ }
431
+ for (const spec of displaySpecs) {
432
+ if (!reachable.has(spec.id)) {
433
+ roots.push(spec.id);
434
+ markReachable(spec.id);
435
+ }
436
+ }
437
+
438
+ // Sort roots alphabetically
439
+ roots.sort();
440
+
441
+ // Summary counts
442
+ const summary = {
443
+ completed: displaySpecs.filter((s) => s.status === 'completed').length,
444
+ in_progress: displaySpecs.filter((s) => s.status === 'in_progress').length,
445
+ roadmap: displaySpecs.filter((s) => s.status === 'roadmap').length,
446
+ available: availableIds.length,
447
+ };
448
+
449
+ // JSON output
450
+ if (options.json) {
451
+ interface TreeNode {
452
+ id: string;
453
+ domain_name: string;
454
+ status: string;
455
+ available: boolean;
456
+ children: TreeNode[];
457
+ }
458
+
459
+ function buildJsonTree(id: string, path: Set<string>): TreeNode | null {
460
+ const spec = specById.get(id);
461
+ if (!spec) return null;
462
+ const node: TreeNode = {
463
+ id: spec.id,
464
+ domain_name: spec.domain_name,
465
+ status: spec.status,
466
+ available: availableIds.includes(spec.id),
467
+ children: [],
468
+ };
469
+ if (path.has(id)) return node; // cycle: return node without children
470
+ path.add(id);
471
+ const children = (childrenOf.get(id) || []).slice().sort();
472
+ for (const childId of children) {
473
+ const child = buildJsonTree(childId, path);
474
+ if (child) node.children.push(child);
475
+ }
476
+ path.delete(id);
477
+ return node;
478
+ }
479
+
480
+ const tree: TreeNode[] = [];
481
+ for (const rootId of roots) {
482
+ const node = buildJsonTree(rootId, new Set());
483
+ if (node) tree.push(node);
484
+ }
485
+
486
+ console.log(JSON.stringify({
487
+ success: true,
488
+ count: displaySpecs.length,
489
+ available: availableIds,
490
+ tree,
491
+ summary,
492
+ }, null, 2));
493
+ return;
494
+ }
495
+
496
+ // Human-readable tree output
497
+ const lines: string[] = [];
498
+ lines.push(`Dependency Tree (${displaySpecs.length} specs):\n`);
499
+
500
+ function statusIcon(status: string): string {
501
+ if (status === 'completed') return '[x]';
502
+ if (status === 'in_progress') return '[>]';
503
+ return '[ ]';
504
+ }
505
+
506
+ function renderNode(id: string, prefix: string, isLast: boolean, isRoot: boolean, pathSet: Set<string>): void {
507
+ const spec = specById.get(id);
508
+ if (!spec) return;
509
+
510
+ const connector = isRoot ? '' : isLast ? '└── ' : '├── ';
511
+ const icon = statusIcon(spec.status);
512
+ const avail = availableIds.includes(spec.id) ? ' ★' : '';
513
+
514
+ if (pathSet.has(id)) {
515
+ lines.push(`${prefix}${connector}${icon} ${spec.id} (${spec.domain_name}) [cycle]`);
516
+ return;
517
+ }
518
+
519
+ lines.push(`${prefix}${connector}${icon} ${spec.id} (${spec.domain_name})${avail}`);
520
+
521
+ const children = (childrenOf.get(id) || []).slice().sort();
522
+ if (children.length === 0) return;
523
+
524
+ pathSet.add(id);
525
+ const childPrefix = isRoot ? prefix : prefix + (isLast ? ' ' : '│ ');
526
+ for (let i = 0; i < children.length; i++) {
527
+ renderNode(children[i], childPrefix, i === children.length - 1, false, pathSet);
528
+ }
529
+ pathSet.delete(id);
530
+ }
531
+
532
+ for (const rootId of roots) {
533
+ renderNode(rootId, '', true, true, new Set());
534
+ }
535
+
536
+ lines.push('');
537
+ lines.push('Legend: [x] completed [>] in_progress [ ] roadmap ★ available');
538
+
539
+ console.log(lines.join('\n'));
540
+ });
541
+
352
542
  // ah specs create <path>
353
543
  specs
354
544
  .command('create <path>')
@@ -42,10 +42,19 @@ Per **Frontier Models are Capable** and **Context is Precious**:
42
42
  - **`--help` as prerequisite**: Suites MUST instruct agents to pull `<tool> --help` before any exploration — command vocabulary shapes exploration quality. The suite MUST NOT replicate full command docs.
43
43
  - **Inline command examples**: Weave brief examples into use-case motivations as calibration anchors — not exhaustive catalogs, not separated command reference sections.
44
44
  - **Motivation framing**: Frame around harness value: reducing human-in-loop supervision, verifying code quality, confirming implementation matches expectations.
45
- - **Exploration categories**: Describe with enough command specificity to orient, not prescriptive sequences that constrain.
45
+ - **Exploration categories**: Describe with enough command specificity to orient. For untested territory, prefer motivations over prescriptive sequences the agent extrapolates better from goals than rigid steps. For patterns verified through testing, state them authoritatively (see below).
46
46
 
47
47
  Formula: **motivations backed by inline command examples + `--help` as prerequisite and progressive disclosure**. Commands woven into use cases give direction; `--help` reveals depth.
48
48
 
49
+ ### Proven vs Untested Guidance
50
+
51
+ Validation suites should be grounded in hands-on testing against the actual repo, not theoretical instructions. The level of authority in how guidance is written depends on whether it has been verified:
52
+
53
+ - **Proven patterns** (verified via the Tool Validation Phase): State authoritatively within use-case motivations — the pattern is established fact, not a suggestion. These override generic tool documentation when they conflict. Example: "`xctrace` requires `--device '<UDID>'` for simulator" is a hard requirement discovered through testing, stated directly alongside the motivation (why: `xctrace` can't find simulator processes without it). The motivation formula still applies — proven patterns are *authoritative examples within motivations*, not raw command catalogs.
54
+ - **Untested edge cases** (not yet exercised in this repo): Define the **motivation** (what the agent should achieve and why) and reference **analogous solved examples** from proven patterns. Do NOT write prescriptive step-by-step instructions for scenarios that haven't been verified — unverified prescriptions can mislead the agent into rigid sequences that don't match reality. Instead, trust that a frontier model given clear motivation and a reference example of how a similar problem was solved will extrapolate the correct approach through stochastic exploration.
55
+
56
+ **Why this matters**: Frontier models produce emergent, adaptive behavior when given goals and reference points. Unverified prescriptive instructions constrain this emergence and risk encoding incorrect assumptions. Motivation + examples activate the model's reasoning about the problem space; rigid untested instructions bypass it. The Tool Validation Phase exists to convert untested guidance into proven patterns over time — the crystallization lifecycle in action.
57
+
49
58
  ### Evidence Capture
50
59
 
51
60
  Per **Quality Engineering**, two audiences require different artifacts:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "all-hands-cli",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Agentic harness for model-first software development",
5
5
  "type": "module",
6
6
  "bin": {