crewforth 3.4.0 → 3.5.0-rc.2

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 (182) hide show
  1. package/CHANGELOG.md +273 -0
  2. package/README.md +3 -2
  3. package/VERSION +1 -1
  4. package/adopt.sh +40 -5
  5. package/bin/cli.js +138 -2
  6. package/kit/AGENT_TEMPLATE.md +15 -0
  7. package/kit/CLAUDE.md +50 -18
  8. package/kit/agents/crew-backend-expert.md +4 -3
  9. package/kit/agents/crew-commit-agent.md +2 -0
  10. package/kit/agents/crew-database-expert.md +2 -1
  11. package/kit/agents/crew-devops-expert.md +1 -0
  12. package/kit/agents/crew-docs-agent.md +3 -1
  13. package/kit/agents/crew-frontend-expert.md +2 -0
  14. package/kit/agents/crew-performance-expert.md +7 -2
  15. package/kit/agents/crew-planner.md +15 -3
  16. package/kit/agents/crew-privacy-agent.md +2 -0
  17. package/kit/agents/crew-review-agent.md +8 -1
  18. package/kit/agents/crew-security-expert.md +4 -3
  19. package/kit/agents/crew-test-expert.md +6 -2
  20. package/kit/eval/doctor.sh +201 -1
  21. package/kit/eval/gate-report.sh +28 -0
  22. package/kit/eval/golden-holdout.txt +2 -0
  23. package/kit/eval/golden-routing.txt +17 -0
  24. package/kit/eval/lib/crew-map/contracts.mjs +77 -0
  25. package/kit/eval/lib/crew-map/crew-map.sh +2 -1
  26. package/kit/eval/lib/crew-map/detect.mjs +2 -1
  27. package/kit/eval/lib/crew-map/narrative.mjs +14 -3
  28. package/kit/eval/lib/crew-map/project.mjs +45 -6
  29. package/kit/eval/lib/crew-map/scan.mjs +153 -14
  30. package/kit/eval/lib/findings/findings.schema.json +11 -1
  31. package/kit/eval/lib/findings/findings.sh +24 -1
  32. package/kit/eval/lib/hook-inject.sh +159 -0
  33. package/kit/eval/lib/lockfiles.sh +71 -0
  34. package/kit/eval/lib/payload-check.sh +29 -0
  35. package/kit/eval/lib/preview.sh +3 -3
  36. package/kit/eval/lib/review-bench/bench.mjs +14 -2
  37. package/kit/eval/lib/review-prepass/prepass.mjs +32 -3
  38. package/kit/eval/lib/settings-json.awk +18 -0
  39. package/kit/eval/lib/statusline.sh +22 -5
  40. package/kit/eval/lib/todo.awk +167 -0
  41. package/kit/eval/lib/todo.sh +120 -0
  42. package/kit/eval/lib/untrusted-text.awk +38 -0
  43. package/kit/eval/scan-skill.sh +67 -20
  44. package/kit/eval/smoke-test.sh +1759 -41
  45. package/kit/eval/utilization.sh +77 -3
  46. package/kit/hooks/agent-outcome.sh +9 -1
  47. package/kit/hooks/board-sync.sh +49 -3
  48. package/kit/hooks/board.sh +8 -8
  49. package/kit/hooks/branch-fresh.sh +101 -0
  50. package/kit/hooks/floor-blocklist.txt +19 -1
  51. package/kit/hooks/guard-bash.sh +92 -8
  52. package/kit/hooks/guard-question.sh +2 -1
  53. package/kit/hooks/loop-guard.sh +445 -0
  54. package/kit/hooks/map-stale.sh +437 -0
  55. package/kit/hooks/pre-commit +48 -7
  56. package/kit/hooks/precompact-snapshot.sh +102 -6
  57. package/kit/hooks/prompt-approval.sh +50 -14
  58. package/kit/hooks/route-hint.sh +36 -7
  59. package/kit/hooks/secret-blocklist.txt +55 -1
  60. package/kit/hooks/session-guard.sh +45 -15
  61. package/kit/hooks/session-rehydrate.sh +34 -11
  62. package/kit/hooks/session-stats.sh +169 -8
  63. package/kit/hooks/session-update-check.sh +5 -1
  64. package/kit/hooks/skill-trust.sh +23 -14
  65. package/kit/hooks/turn-check.sh +190 -0
  66. package/kit/owned-blobs.tsv +4 -4
  67. package/kit/settings.json +64 -0
  68. package/kit/skills/a11y/SKILL.md +50 -3
  69. package/kit/skills/a11y/references/recipes.md +41 -0
  70. package/kit/skills/adr/SKILL.md +14 -2
  71. package/kit/skills/api-design/SKILL.md +6 -1
  72. package/kit/skills/api-design/references/idempotency.md +39 -0
  73. package/kit/skills/brainstorm/SKILL.md +4 -2
  74. package/kit/skills/brainstorm/references/asking.md +47 -0
  75. package/kit/skills/ci-pipeline/SKILL.md +11 -1
  76. package/kit/skills/ci-pipeline/references/agent-job.md +33 -0
  77. package/kit/skills/commit-message/SKILL.md +9 -3
  78. package/kit/skills/confidence-check/SKILL.md +9 -7
  79. package/kit/skills/confidence-check/references/source-check.md +37 -0
  80. package/kit/skills/crew-approve/SKILL.md +3 -0
  81. package/kit/skills/crew-audit/SKILL.md +63 -0
  82. package/kit/skills/crew-board/SKILL.md +2 -2
  83. package/kit/skills/crew-code-review/SKILL.md +27 -4
  84. package/kit/skills/crew-code-review/references/panel-mode.md +9 -9
  85. package/kit/skills/crew-code-review/references/simplify.md +28 -0
  86. package/kit/skills/crew-doctor/SKILL.md +3 -2
  87. package/kit/skills/crew-gates/SKILL.md +3 -0
  88. package/kit/skills/crew-gates/references/enforcers.md +30 -0
  89. package/kit/skills/crew-interview/SKILL.md +42 -0
  90. package/kit/skills/crew-map/SKILL.md +26 -6
  91. package/kit/skills/crew-plan/SKILL.md +10 -2
  92. package/kit/skills/crew-pr/SKILL.md +57 -0
  93. package/kit/skills/crew-pr/references/checks.md +35 -0
  94. package/kit/skills/crew-pr/references/threads.md +23 -0
  95. package/kit/skills/crew-review/SKILL.md +34 -1
  96. package/kit/skills/crew-ship/SKILL.md +5 -2
  97. package/kit/skills/crew-skill/SKILL.md +26 -1
  98. package/kit/skills/crew-todo/SKILL.md +31 -0
  99. package/kit/skills/crew-update/SKILL.md +4 -1
  100. package/kit/skills/db-migration/SKILL.md +15 -0
  101. package/kit/skills/db-migration/references/expand-contract.md +52 -0
  102. package/kit/skills/dependency-audit/SKILL.md +34 -1
  103. package/kit/skills/dependency-audit/scripts/record.sh +37 -0
  104. package/kit/skills/dependency-upgrade/SKILL.md +29 -1
  105. package/kit/skills/deploy/SKILL.md +18 -0
  106. package/kit/skills/deploy/references/topology-a.md +7 -0
  107. package/kit/skills/deploy/references/topology-b.md +2 -2
  108. package/kit/skills/docs-writer/SKILL.md +37 -2
  109. package/kit/skills/eval-grader/SKILL.md +9 -0
  110. package/kit/skills/eval-grader/references/method.md +26 -0
  111. package/kit/skills/frontend/SKILL.md +5 -2
  112. package/kit/skills/frontend/references/from-design.md +29 -0
  113. package/kit/skills/frontend/references/page-metadata.md +17 -0
  114. package/kit/skills/frontend/references/verify-contract.md +18 -1
  115. package/kit/skills/frontend/references/worst-data.md +35 -0
  116. package/kit/skills/frontend-design/SKILL.md +20 -4
  117. package/kit/skills/frontend-design/references/design-review.md +21 -0
  118. package/kit/skills/frontend-design/references/detection.md +44 -0
  119. package/kit/skills/frontend-design/references/motion.md +54 -0
  120. package/kit/skills/frontend-design/references/ui-copy.md +35 -0
  121. package/kit/skills/frontend-rn-expo/SKILL.md +3 -0
  122. package/kit/skills/handoff/SKILL.md +28 -3
  123. package/kit/skills/handoff/references/cold-reader.md +13 -6
  124. package/kit/skills/i18n-integrity/SKILL.md +3 -0
  125. package/kit/skills/iterate/SKILL.md +7 -2
  126. package/kit/skills/mcp-builder/SKILL.md +21 -4
  127. package/kit/skills/mcp-builder/references/building.md +74 -13
  128. package/kit/skills/mcp-builder/references/cli-for-agents.md +26 -0
  129. package/kit/skills/observability/SKILL.md +13 -1
  130. package/kit/skills/performance/SKILL.md +25 -1
  131. package/kit/skills/performance/references/bottlenecks.md +11 -0
  132. package/kit/skills/performance/references/web-vitals.md +31 -0
  133. package/kit/skills/red-team/SKILL.md +25 -3
  134. package/kit/skills/reflect/SKILL.md +7 -2
  135. package/kit/skills/release/SKILL.md +13 -0
  136. package/kit/skills/security-scan/SKILL.md +6 -2
  137. package/kit/skills/security-scan/references/ai-agents.md +16 -0
  138. package/kit/skills/security-scan/references/fronts.md +59 -2
  139. package/kit/skills/security-scan/references/reporting.md +13 -0
  140. package/kit/skills/security-scan/references/verify.md +9 -0
  141. package/kit/skills/sonarqube-check/SKILL.md +32 -2
  142. package/kit/skills/sonarqube-check/references/ratchet.md +32 -0
  143. package/kit/skills/spec-planning/SKILL.md +32 -8
  144. package/kit/skills/systematic-debugging/SKILL.md +15 -7
  145. package/kit/skills/systematic-debugging/references/techniques.md +5 -0
  146. package/kit/skills/teamboard/SKILL.md +3 -3
  147. package/kit/skills/testing/SKILL.md +18 -3
  148. package/kit/skills/testing/references/browser-headless.md +21 -0
  149. package/kit/skills/token-budget/SKILL.md +26 -5
  150. package/kit/skills/worktree/SKILL.md +13 -5
  151. package/kit/studio/server/lib/session.js +23 -2
  152. package/kit/studio/server/lib/worktree-changes.js +64 -0
  153. package/kit/studio/web/agent-spend.js +32 -0
  154. package/kit/studio/web/app.js +245 -236
  155. package/kit/studio/web/approvals.js +14 -13
  156. package/kit/studio/web/ask.js +10 -9
  157. package/kit/studio/web/canvas.js +56 -54
  158. package/kit/studio/web/chat.js +41 -41
  159. package/kit/studio/web/convo.js +33 -16
  160. package/kit/studio/web/dock.js +20 -19
  161. package/kit/studio/web/graph-plan.js +62 -22
  162. package/kit/studio/web/i18n-tr.js +760 -0
  163. package/kit/studio/web/i18n.js +79 -0
  164. package/kit/studio/web/index.html +1 -0
  165. package/kit/studio/web/inspect.js +27 -24
  166. package/kit/studio/web/list-plan.js +16 -15
  167. package/kit/studio/web/list.js +6 -5
  168. package/kit/studio/web/liveness.js +11 -9
  169. package/kit/studio/web/map-plan.js +86 -36
  170. package/kit/studio/web/map.js +85 -72
  171. package/kit/studio/web/models-plan.js +47 -42
  172. package/kit/studio/web/models.js +21 -20
  173. package/kit/studio/web/names.js +13 -10
  174. package/kit/studio/web/nav.js +34 -33
  175. package/kit/studio/web/newsession.js +25 -24
  176. package/kit/studio/web/style.css +2 -0
  177. package/kit/studio/web/theme.js +5 -2
  178. package/kit/studio/web/timeline-plan.js +19 -18
  179. package/kit/studio/web/timeline.js +23 -22
  180. package/kit/studio/web/usage.js +36 -32
  181. package/package.json +5 -4
  182. package/start.sh +11 -2
@@ -14,8 +14,16 @@
14
14
  // 2. Nothing here reads the zoom. Positions are a function of the nodes and the viewer's choices, never of the
15
15
  // view, so zooming cannot move a card.
16
16
  // 3. Colour is not decided here at all. A box carries its status; the stylesheet turns status into colour.
17
+ //
18
+ // TWO SIDES
19
+ //
20
+ // In a window with room (`opts.sides`) the session stands in the middle: the agents that build — backend, frontend,
21
+ // database, test, devops, performance — grow to its left, mirrored, and everything else grows to its right. One
22
+ // column of a dozen agents beside an empty half of the pane is then two columns of six. A side is decided by an
23
+ // agent's type, never by how many are on either side, so an agent that arrives does not move one already drawn.
17
24
 
18
25
  import { typeOf, roleName, shownType } from './names.js';
26
+ import { t } from './i18n.js';
19
27
 
20
28
  /* ------------------------------------------------------------- measures --- */
21
29
 
@@ -80,6 +88,15 @@ export const stateOf = (status) => STATE[status] ?? 'unknown';
80
88
 
81
89
  const SESSION = 'session';
82
90
 
91
+ /** The agents that build the product. Under `sides` they stand to the left of the session; the rest to its right. */
92
+ export const BUILDERS = new Set([
93
+ 'crew-backend-expert', 'crew-frontend-expert', 'crew-database-expert',
94
+ 'crew-test-expert', 'crew-devops-expert', 'crew-performance-expert',
95
+ ]);
96
+
97
+ /** -1 for the left of the session, 1 for its right. A workflow run, an unknown type and every other agent: right. */
98
+ export const sideOf = (type) => (BUILDERS.has(type) ? -1 : 1);
99
+
83
100
  /* --------------------------------------------------------------- groups --- */
84
101
 
85
102
  const byArrival = (seq) => (a, b) => (seq.get(a.id) ?? 0) - (seq.get(b.id) ?? 0);
@@ -214,7 +231,7 @@ function itemsOf(nodes, { group, density, seq, stack, aside }) {
214
231
  if (n.kind === 'workflow' && group !== 'none') {
215
232
  const members = kids.get(n.id) ?? [];
216
233
  const g = groupItem(n.id, 'run', members, anchor, via, depth, seq, {
217
- label: n.workflowId ?? 'workflow run', node: n,
234
+ label: n.workflowId ?? t('workflow run'), node: n,
218
235
  });
219
236
  g.seq = seq.get(n.id) ?? g.seq;
220
237
  items.push(g);
@@ -295,6 +312,8 @@ export const sessionHeight = (n) => SIZE.session.h + (n?.backgroundNow?.length ?
295
312
  * the reference window's when it is not given
296
313
  * @param opts.aside true when an open type group puts its agents in the next column instead of growing
297
314
  * downward; a narrow window leaves it off
315
+ * @param opts.sides true to put the agents that build to the left of the session and the rest to its right
316
+ * (see TWO SIDES above); a narrow window leaves it off and the tree reads left to right
298
317
  * @returns { items, edges, width, height, drawn, density, sessionY }
299
318
  */
300
319
  export function plan(nodes, opts = {}) {
@@ -333,6 +352,17 @@ export function plan(nodes, opts = {}) {
333
352
  };
334
353
 
335
354
  const G = SIZE.group;
355
+ const sides = Boolean(opts.sides);
356
+
357
+ /** The side an item grows on: 0 for the session, and what the top of its branch is for everything under it. */
358
+ const sideOfItem = (it) => {
359
+ if (!sides || it.kind === 'session') return 0;
360
+ let top = it;
361
+ for (let guard = 0; top.parent && top.parent.kind !== 'session' && guard < 64; guard += 1) top = top.parent;
362
+ if (top.kind === 'agent') return sideOf(typeOf(top.node));
363
+ if (top.kind === 'group' && top.type === 'type') return sideOf(top.real);
364
+ return 1;
365
+ };
336
366
 
337
367
  /** Size and place everything drawn, at one density. Returns the box the picture needs. */
338
368
  const lay = (drawnItems, compact) => {
@@ -372,13 +402,18 @@ export function plan(nodes, opts = {}) {
372
402
  }));
373
403
  }
374
404
 
375
- // Columns: one per depth, as wide as the widest thing in it.
376
- const depths = [...new Set(drawnItems.map((it) => it.depth))].sort((a, b) => a - b);
405
+ // Columns: one per depth, as wide as the widest thing in it. Under `sides` a column is a depth on one side,
406
+ // keyed by the depth with the side's sign, so the left side's columns come first, outermost first.
407
+ for (const it of drawnItems) { it.side = sideOfItem(it); it.col = it.side < 0 ? -it.depth : it.depth; }
408
+ const cols = [...new Set(drawnItems.map((it) => it.col))].sort((a, b) => a - b);
377
409
  const colX = new Map();
410
+ const colW = new Map();
378
411
  let x = SIZE.padX;
379
- for (const d of depths) {
380
- colX.set(d, x);
381
- x += Math.max(...drawnItems.filter((it) => it.depth === d).map((it) => it.w)) + SIZE.colGap;
412
+ for (const k of cols) {
413
+ const w = Math.max(...drawnItems.filter((it) => it.col === k).map((it) => it.w));
414
+ colX.set(k, x);
415
+ colW.set(k, w);
416
+ x += w + SIZE.colGap;
382
417
  }
383
418
  const width = x - SIZE.colGap + SIZE.padX;
384
419
 
@@ -392,12 +427,14 @@ export function plan(nodes, opts = {}) {
392
427
  return Math.max(SIZE.padY, cy - it.h / 2);
393
428
  };
394
429
  let height = 0;
395
- for (const d of depths) {
396
- if (d === 0) continue;
397
- const col = drawnItems.filter((it) => it.depth === d).sort((a, b) => a.seq - b.seq || String(a.id).localeCompare(String(b.id)));
430
+ // Nearest the session first, so a parent has its place before its children ask for it.
431
+ for (const k of cols.slice().sort((a, b) => Math.abs(a) - Math.abs(b) || a - b)) {
432
+ if (k === 0) continue;
433
+ const col = drawnItems.filter((it) => it.col === k).sort((a, b) => a.seq - b.seq || String(a.id).localeCompare(String(b.id)));
398
434
  let cursor = SIZE.padY;
399
435
  for (const it of col) {
400
- it.x = colX.get(d);
436
+ // On the left a column's boxes line up on their right edge, the one their wire comes into.
437
+ it.x = it.side < 0 ? colX.get(k) + colW.get(k) - it.w : colX.get(k);
401
438
  it.y = Math.max(cursor, anchorY(it));
402
439
  cursor = it.y + it.h + SIZE.rowGap;
403
440
  }
@@ -449,15 +486,17 @@ export function plan(nodes, opts = {}) {
449
486
  if (p) { it.x = p.x; it.y = p.y; it.pinned = true; }
450
487
  }
451
488
 
452
- // Wires: out of the right edge of what spawned a thing, into its left edge.
489
+ // Wires: out of the right edge of what spawned a thing, into its left edge. On the left side, mirrored: out of
490
+ // the left edge, into the right one, and `dir` says so to whoever draws the curve.
453
491
  const edges = [];
454
492
  for (const it of drawnItems) {
455
493
  const p = it.parent;
456
494
  if (!p) continue;
457
495
  const cell = cellOf(it);
496
+ const left = it.side < 0;
458
497
  const from = cell
459
- ? { x: p.x + cell.x + cell.w, y: p.y + cell.y + cell.h / 2 }
460
- : { x: p.x + p.w, y: p.y + p.h / 2 };
498
+ ? { x: p.x + cell.x + (left ? 0 : cell.w), y: p.y + cell.y + cell.h / 2 }
499
+ : { x: p.x + (left ? 0 : p.w), y: p.y + p.h / 2 };
461
500
  const status = it.kind === 'group' ? it.status : (it.node.status ?? 'unknown');
462
501
  // A group's wire says whether the group is still working. What went wrong inside it is said by its bar, its
463
502
  // count and the member itself; a red wire into twelve agents because one failed would say the wrong thing
@@ -468,7 +507,8 @@ export function plan(nodes, opts = {}) {
468
507
  source: it.via ?? p.id,
469
508
  target: it.id,
470
509
  from,
471
- to: { x: it.x, y: it.y + it.h / 2 },
510
+ to: { x: it.x + (left ? it.w : 0), y: it.y + it.h / 2 },
511
+ dir: left ? -1 : 1,
472
512
  status,
473
513
  state,
474
514
  });
@@ -476,14 +516,14 @@ export function plan(nodes, opts = {}) {
476
516
 
477
517
  return {
478
518
  items: drawnItems, edges, width: Math.max(width, SIZE.padX * 2), height: Math.max(height, SIZE.padY * 2),
479
- drawn, density, group, stacked, aside: Boolean(aside), sessionY: sessionY ?? null,
519
+ drawn, density, group, stacked, aside: Boolean(aside), sides, sessionY: sessionY ?? null,
480
520
  };
481
521
  }
482
522
 
483
523
  /* ---------------------------------------------------------------- order --- */
484
524
 
485
525
  // What the wire from an agent to a later wave is called, and what it is: a reading of the order of events.
486
- export const INFERRED_NOTE = 'Inferred from the order of events: this wave was called after that agent reported. It is not a call from one agent to another; the session calls every agent.';
526
+ export const INFERRED_NOTE = t('Inferred from the order of events: this wave was called after that agent reported. It is not a call from one agent to another; the session calls every agent.');
487
527
 
488
528
  /**
489
529
  * The same agents by when the session called them.
@@ -568,8 +608,8 @@ function planOrder(nodes, asked, opts, tallest = AUTO.tallest) {
568
608
  waves[i].members.forEach((m, k) => wire(at.get(r.id), at.get(m.id), {
569
609
  kind: 'inferred', state: 'inferred',
570
610
  // Said once for a reporter, on the wire to the wave's first agent; the others carry it on hover.
571
- label: k === 0 ? `after ${shownType(r)} reported` : null,
572
- title: `after ${shownType(r)} reported\n${INFERRED_NOTE}`,
611
+ label: k === 0 ? t('after {type} reported', { type: shownType(r) }) : null,
612
+ title: `${t('after {type} reported', { type: shownType(r) })}\n${INFERRED_NOTE}`,
573
613
  }));
574
614
  }
575
615
  for (const m of waves[i].members) at.get(m.id).after = reporters.map((r) => shownType(r));
@@ -577,7 +617,7 @@ function planOrder(nodes, asked, opts, tallest = AUTO.tallest) {
577
617
 
578
618
  return {
579
619
  items, edges, width: Math.max(box.width, SIZE.padX * 2), height: Math.max(height, SIZE.padY * 2),
580
- drawn: agents.length, density: compact ? 'compact' : 'comfortable', group: 'order', stacked: false, aside: false,
620
+ drawn: agents.length, density: compact ? 'compact' : 'comfortable', group: 'order', stacked: false, aside: false, sides: false,
581
621
  waves: waves.length, sessionY,
582
622
  };
583
623
  }
@@ -659,14 +699,14 @@ export function attention(nodes, waiting = new Set()) {
659
699
  const needs = agents.filter((n) => waiting.has(n.id)).sort((a, b) => when(b) - when(a));
660
700
  const failed = agents.filter((n) => !waiting.has(n.id) && stateOf(n.status) === 'failed').sort((a, b) => when(b) - when(a));
661
701
  return [
662
- ...needs.map((n) => ({ id: n.id, tone: 'waiting', name: shownType(n), real: typeOf(n), says: 'waiting for you' })),
702
+ ...needs.map((n) => ({ id: n.id, tone: 'waiting', name: shownType(n), real: typeOf(n), says: t('waiting for you') })),
663
703
  ...failed.map((n) => ({
664
704
  id: n.id,
665
705
  tone: 'fail',
666
706
  name: shownType(n),
667
707
  real: typeOf(n),
668
- says: [n.status, n.errors ? `${n.errors} ${n.errors === 1 ? 'error' : 'errors'}` : null,
669
- n.workflow ? `in ${n.workflow}` : null].filter(Boolean).join(' · '),
708
+ says: [({ failed: t('failed'), killed: t('killed'), stopped: t('stopped') })[n.status] ?? n.status, n.errors ? (n.errors === 1 ? t('{n} error', { n: 1 }) : t('{n} errors', { n: n.errors })) : null,
709
+ n.workflow ? t('in {name}', { name: n.workflow }) : null].filter(Boolean).join(' · '),
670
710
  })),
671
711
  ];
672
712
  }