crewforth 3.3.0 → 3.5.0-rc.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 (154) hide show
  1. package/CHANGELOG.md +271 -0
  2. package/README.md +7 -2
  3. package/VERSION +1 -1
  4. package/adopt.sh +40 -5
  5. package/bin/cli.js +193 -2
  6. package/kit/AGENT_TEMPLATE.md +15 -0
  7. package/kit/CLAUDE.md +49 -17
  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/untrusted-text.awk +38 -0
  40. package/kit/eval/scan-skill.sh +67 -20
  41. package/kit/eval/smoke-test.sh +1792 -52
  42. package/kit/eval/utilization.sh +77 -3
  43. package/kit/hooks/agent-outcome.sh +9 -1
  44. package/kit/hooks/board-sync.sh +49 -3
  45. package/kit/hooks/board.sh +8 -8
  46. package/kit/hooks/branch-fresh.sh +101 -0
  47. package/kit/hooks/floor-blocklist.txt +19 -1
  48. package/kit/hooks/guard-bash.sh +92 -8
  49. package/kit/hooks/guard-question.sh +2 -1
  50. package/kit/hooks/loop-guard.sh +445 -0
  51. package/kit/hooks/map-stale.sh +437 -0
  52. package/kit/hooks/pre-commit +48 -7
  53. package/kit/hooks/precompact-snapshot.sh +102 -6
  54. package/kit/hooks/prompt-approval.sh +45 -9
  55. package/kit/hooks/route-hint.sh +36 -7
  56. package/kit/hooks/secret-blocklist.txt +55 -1
  57. package/kit/hooks/session-guard.sh +85 -28
  58. package/kit/hooks/session-rehydrate.sh +34 -11
  59. package/kit/hooks/session-stats.sh +169 -8
  60. package/kit/hooks/session-update-check.sh +5 -1
  61. package/kit/hooks/skill-trust.sh +23 -14
  62. package/kit/hooks/turn-check.sh +190 -0
  63. package/kit/hooks/worktree-provision.sh +91 -0
  64. package/kit/owned-blobs.tsv +4 -2
  65. package/kit/settings.json +86 -0
  66. package/kit/skills/a11y/SKILL.md +50 -3
  67. package/kit/skills/a11y/references/recipes.md +41 -0
  68. package/kit/skills/adr/SKILL.md +14 -2
  69. package/kit/skills/api-design/SKILL.md +6 -1
  70. package/kit/skills/api-design/references/idempotency.md +39 -0
  71. package/kit/skills/brainstorm/SKILL.md +4 -2
  72. package/kit/skills/brainstorm/references/asking.md +47 -0
  73. package/kit/skills/ci-pipeline/SKILL.md +11 -1
  74. package/kit/skills/ci-pipeline/references/agent-job.md +33 -0
  75. package/kit/skills/commit-message/SKILL.md +9 -3
  76. package/kit/skills/confidence-check/SKILL.md +9 -7
  77. package/kit/skills/confidence-check/references/source-check.md +37 -0
  78. package/kit/skills/crew-approve/SKILL.md +3 -0
  79. package/kit/skills/crew-audit/SKILL.md +63 -0
  80. package/kit/skills/crew-board/SKILL.md +2 -2
  81. package/kit/skills/crew-code-review/SKILL.md +27 -4
  82. package/kit/skills/crew-code-review/references/panel-mode.md +9 -9
  83. package/kit/skills/crew-code-review/references/simplify.md +28 -0
  84. package/kit/skills/crew-doctor/SKILL.md +3 -2
  85. package/kit/skills/crew-gates/SKILL.md +3 -0
  86. package/kit/skills/crew-gates/references/enforcers.md +30 -0
  87. package/kit/skills/crew-interview/SKILL.md +42 -0
  88. package/kit/skills/crew-map/SKILL.md +26 -6
  89. package/kit/skills/crew-plan/SKILL.md +9 -2
  90. package/kit/skills/crew-pr/SKILL.md +57 -0
  91. package/kit/skills/crew-pr/references/checks.md +35 -0
  92. package/kit/skills/crew-pr/references/threads.md +23 -0
  93. package/kit/skills/crew-review/SKILL.md +34 -1
  94. package/kit/skills/crew-ship/SKILL.md +5 -2
  95. package/kit/skills/crew-skill/SKILL.md +26 -1
  96. package/kit/skills/crew-update/SKILL.md +4 -1
  97. package/kit/skills/db-migration/SKILL.md +15 -0
  98. package/kit/skills/db-migration/references/expand-contract.md +52 -0
  99. package/kit/skills/dependency-audit/SKILL.md +34 -1
  100. package/kit/skills/dependency-audit/scripts/record.sh +37 -0
  101. package/kit/skills/dependency-upgrade/SKILL.md +29 -1
  102. package/kit/skills/deploy/SKILL.md +18 -0
  103. package/kit/skills/deploy/references/topology-a.md +7 -0
  104. package/kit/skills/deploy/references/topology-b.md +2 -2
  105. package/kit/skills/docs-writer/SKILL.md +37 -2
  106. package/kit/skills/eval-grader/SKILL.md +9 -0
  107. package/kit/skills/eval-grader/references/method.md +26 -0
  108. package/kit/skills/frontend/SKILL.md +5 -2
  109. package/kit/skills/frontend/references/from-design.md +29 -0
  110. package/kit/skills/frontend/references/page-metadata.md +17 -0
  111. package/kit/skills/frontend/references/verify-contract.md +18 -1
  112. package/kit/skills/frontend/references/worst-data.md +35 -0
  113. package/kit/skills/frontend-design/SKILL.md +20 -4
  114. package/kit/skills/frontend-design/references/design-review.md +21 -0
  115. package/kit/skills/frontend-design/references/detection.md +44 -0
  116. package/kit/skills/frontend-design/references/motion.md +54 -0
  117. package/kit/skills/frontend-design/references/ui-copy.md +35 -0
  118. package/kit/skills/frontend-rn-expo/SKILL.md +3 -0
  119. package/kit/skills/handoff/SKILL.md +28 -3
  120. package/kit/skills/handoff/references/cold-reader.md +13 -6
  121. package/kit/skills/i18n-integrity/SKILL.md +3 -0
  122. package/kit/skills/iterate/SKILL.md +7 -2
  123. package/kit/skills/mcp-builder/SKILL.md +21 -4
  124. package/kit/skills/mcp-builder/references/building.md +74 -13
  125. package/kit/skills/mcp-builder/references/cli-for-agents.md +26 -0
  126. package/kit/skills/observability/SKILL.md +13 -1
  127. package/kit/skills/performance/SKILL.md +25 -1
  128. package/kit/skills/performance/references/bottlenecks.md +11 -0
  129. package/kit/skills/performance/references/web-vitals.md +31 -0
  130. package/kit/skills/red-team/SKILL.md +25 -3
  131. package/kit/skills/reflect/SKILL.md +7 -2
  132. package/kit/skills/release/SKILL.md +13 -0
  133. package/kit/skills/security-scan/SKILL.md +6 -2
  134. package/kit/skills/security-scan/references/ai-agents.md +16 -0
  135. package/kit/skills/security-scan/references/fronts.md +59 -2
  136. package/kit/skills/security-scan/references/reporting.md +13 -0
  137. package/kit/skills/security-scan/references/verify.md +9 -0
  138. package/kit/skills/sonarqube-check/SKILL.md +32 -2
  139. package/kit/skills/sonarqube-check/references/ratchet.md +32 -0
  140. package/kit/skills/spec-planning/SKILL.md +25 -8
  141. package/kit/skills/systematic-debugging/SKILL.md +15 -7
  142. package/kit/skills/systematic-debugging/references/techniques.md +5 -0
  143. package/kit/skills/teamboard/SKILL.md +3 -3
  144. package/kit/skills/testing/SKILL.md +18 -3
  145. package/kit/skills/testing/references/browser-headless.md +21 -0
  146. package/kit/skills/token-budget/SKILL.md +26 -5
  147. package/kit/skills/worktree/SKILL.md +13 -5
  148. package/kit/studio/server/lib/session.js +23 -2
  149. package/kit/studio/server/lib/worktree-changes.js +64 -0
  150. package/kit/studio/web/convo.js +14 -0
  151. package/kit/studio/web/map-plan.js +54 -8
  152. package/kit/studio/web/map.js +15 -3
  153. package/package.json +5 -4
  154. package/start.sh +11 -2
@@ -315,6 +315,14 @@ export function inspectOf(node, doc, edges, findings) {
315
315
  other: node.structure?.otherFiles ?? 0,
316
316
  tests: node.structure?.testFiles ?? 0,
317
317
  } : null,
318
+ // Where its tests are (its own test files and a test project named for it), how often it changed in the commits
319
+ // the scan read, and the WHY/HACK/FIXME lines in its code: what someone about to change it asks first.
320
+ testFiles: Array.isArray(node.tests?.files) ? node.tests.files.filter((f) => typeof f === 'string') : [],
321
+ testTotal: Number.isInteger(node.tests?.total) ? node.tests.total : 0,
322
+ changed: Number.isInteger(node.commits) && Number.isInteger(doc?.source?.commitsRead) && doc.source.commitsRead > 0
323
+ ? `Changed in ${node.commits} of the last ${doc.source.commitsRead} ${doc.source.commitsRead === 1 ? 'commit' : 'commits'}` : null,
324
+ notes: (node.markers?.evidence ?? []).filter((e) => e && typeof e.file === 'string').map((e) => ({ at: `${e.file}:${e.line}`, text: String(e.text ?? '') })),
325
+ notesTotal: node.markers?.evidenceTotal ?? (node.markers?.evidence ?? []).length,
318
326
  connectsTo: out,
319
327
  connections: { out, in: into },
320
328
  byKind: Object.entries(byKind).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)),
@@ -385,23 +393,61 @@ export function layoutOf(nodes, { width = 960, chips = {}, rowKey = null } = {})
385
393
  return { bands, pos, width: Math.max(width, right + GAP_X), height: y };
386
394
  }
387
395
 
388
- /** A line between two cards: out of the bottom of the upper one into the top of the lower, or side to side. */
389
- export function pathOf(a, b) {
396
+ /**
397
+ * A line between two cards: out of the bottom of the upper one into the top of the lower, or side to side.
398
+ * `pa` and `pb` say where along its side the line leaves `a` and enters `b`, as a fraction of that side (½ = the
399
+ * middle); `routesOf` spreads them so lines that share a side do not share a pixel.
400
+ */
401
+ export function pathOf(a, b, pa = 0.5, pb = 0.5) {
390
402
  if (!a || !b) return null;
391
403
  const down = b.y >= a.y + a.h;
392
404
  const up = a.y >= b.y + b.h;
393
405
  if (down || up) {
394
- const [s, t] = down ? [a, b] : [b, a];
395
- const x1 = s.x + s.w / 2; const y1 = s.y + s.h; const x2 = t.x + t.w / 2; const y2 = t.y;
406
+ const [s, t, fs, ft] = down ? [a, b, pa, pb] : [b, a, pb, pa];
407
+ const x1 = s.x + s.w * fs; const y1 = s.y + s.h; const x2 = t.x + t.w * ft; const y2 = t.y;
396
408
  const m = (y1 + y2) / 2;
397
409
  return { d: `M${x1} ${y1} C${x1} ${m} ${x2} ${m} ${x2} ${y2}`, mid: [(x1 + x2) / 2, m] };
398
410
  }
399
- const [s, t] = a.x <= b.x ? [a, b] : [b, a];
400
- const x1 = s.x + s.w; const y1 = s.y + s.h / 2; const x2 = t.x; const y2 = t.y + t.h / 2;
401
- return { d: `M${x1} ${y1} L${x2} ${y2}`, mid: [(x1 + x2) / 2, (y1 + y2) / 2] };
411
+ const [s, t, fs, ft] = a.x <= b.x ? [a, b, pa, pb] : [b, a, pb, pa];
412
+ const x1 = s.x + s.w; const y1 = s.y + s.h * fs; const x2 = t.x; const y2 = t.y + t.h * ft;
413
+ const mx = (x1 + x2) / 2;
414
+ // Level ends are one straight line; ends at two heights turn square halfway, so no line runs on a slant.
415
+ if (y1 === y2) return { d: `M${x1} ${y1} L${x2} ${y2}`, mid: [mx, y1] };
416
+ return { d: `M${x1} ${y1} H${mx} V${y2} H${x2}`, mid: [mx, (y1 + y2) / 2] };
417
+ }
418
+
419
+ /**
420
+ * Every edge's line, with the lines that leave or enter one side of one card spread along it: the k-th of N at
421
+ * k/(N+1), ranked by where the other end sits, so neighbours do not cross. Measured on the crew-map-test fixtures and
422
+ * Crewforth's own map before this: 48 line ends shared a pixel with another across 46 edges, so a card with three
423
+ * dependents showed one line. A side with one line keeps the middle, as before.
424
+ */
425
+ export function routesOf(edges, pos) {
426
+ const sides = new Map();
427
+ const add = (key, i, c) => { if (!sides.has(key)) sides.set(key, []); sides.get(key).push({ i, c }); };
428
+ const geo = edges.map((e, i) => {
429
+ const a = pos[e.from]; const b = pos[e.to];
430
+ if (!a || !b) return null;
431
+ const vertical = b.y >= a.y + a.h || a.y >= b.y + b.h;
432
+ if (vertical) {
433
+ const aBelow = a.y >= b.y + b.h;
434
+ const ka = `${e.from}:${aBelow ? 'top' : 'bottom'}`; const kb = `${e.to}:${aBelow ? 'bottom' : 'top'}`;
435
+ add(ka, i, b.x + b.w / 2); add(kb, i, a.x + a.w / 2);
436
+ return { a, b, ka, kb };
437
+ }
438
+ const aLeft = a.x <= b.x;
439
+ const ka = `${e.from}:${aLeft ? 'right' : 'left'}`; const kb = `${e.to}:${aLeft ? 'left' : 'right'}`;
440
+ add(ka, i, b.y + b.h / 2); add(kb, i, a.y + a.h / 2);
441
+ return { a, b, ka, kb };
442
+ });
443
+ const at = new Map();
444
+ for (const [key, list] of sides) {
445
+ list.sort((p, q) => p.c - q.c || p.i - q.i);
446
+ list.forEach((p, k) => at.set(`${key}|${p.i}`, (k + 1) / (list.length + 1)));
447
+ }
448
+ return geo.map((g, i) => (g ? pathOf(g.a, g.b, at.get(`${g.ka}|${i}`), at.get(`${g.kb}|${i}`)) : null));
402
449
  }
403
450
 
404
- /** The endpoint chips of a module, as the Endpoints layer lists them: what it provides, then what it calls. */
405
451
  export function chipsOf(node, doc) {
406
452
  const p = (doc.provides?.endpoints ?? []).filter((e) => e.module === node.id).map((e) => `${e.method} ${e.path}`);
407
453
  const c = (doc.consumes?.endpoints ?? []).filter((e) => e.module === node.id).map((e) => `→ ${e.method} ${e.path}`);
@@ -10,7 +10,7 @@
10
10
  // nothing; what leaves the page is copied text: a finding as a task, or a file:line.
11
11
  import {
12
12
  nodesOf, edgesOf, edgeKey, litEdges, findingsOf, findingCounts, cardSub, summaryOf, sourceOf, inspectOf, taskOf,
13
- emptyNote, layoutOf, pathOf, chipsOf, inferredOnly, groupsOf, foldView, placed, BANDS, cardOf, tourOf, searchOf, resourceWord,
13
+ emptyNote, layoutOf, routesOf, chipsOf, inferredOnly, groupsOf, foldView, placed, BANDS, cardOf, tourOf, searchOf, resourceWord,
14
14
  } from './map-plan.js';
15
15
  import { Viewport, draggable, swallowClick } from './viewport.js';
16
16
 
@@ -231,8 +231,9 @@ export class MapView {
231
231
  }
232
232
  const lines = svg('svg', { class: 'mp-lines', width: lay.width, height: lay.height, 'aria-hidden': 'true' });
233
233
  const labels = [];
234
- for (const e of edges) {
235
- const p = pathOf(lay.pos[e.from], lay.pos[e.to]);
234
+ const routes = routesOf(edges, lay.pos);
235
+ for (const [ei, e] of edges.entries()) {
236
+ const p = routes[ei];
236
237
  if (!p) continue;
237
238
  const path = svg('path', { d: p.d, class: 'mp-line' });
238
239
  path.dataset.solid = String(e.solid);
@@ -495,6 +496,17 @@ export class MapView {
495
496
  if (v.inside.other) sec.append(mk('p', 'sub', `${v.inside.other} more files in other folders`));
496
497
  if (v.inside.tests) sec.append(mk('p', 'sub', `${v.inside.tests} test ${v.inside.tests === 1 ? 'file' : 'files'}`));
497
498
  }
499
+ if (v.changed) sec.append(mk('p', 'sub', v.changed));
500
+ if (v.testFiles.length) {
501
+ sec.append(mk('span', 'lbl', `Tests · ${v.testTotal}`));
502
+ for (const f of v.testFiles) { const r = mk('div', 'mp-conn mp-inside'); r.append(mk('span', 'mp-file', f)); sec.append(r); }
503
+ if (v.testTotal > v.testFiles.length) sec.append(mk('p', 'sub', `${v.testTotal - v.testFiles.length} more in the map file`));
504
+ }
505
+ if (v.notes.length) {
506
+ sec.append(mk('span', 'lbl', `Notes in the code · ${v.notesTotal}`));
507
+ for (const x of v.notes) { const r = mk('div', 'mp-conn mp-inside'); r.append(this.#cite(x.at), mk('span', 'mp-ev', x.text)); sec.append(r); }
508
+ if (v.notesTotal > v.notes.length) sec.append(mk('p', 'sub', `${v.notesTotal - v.notes.length} more in the map file`));
509
+ }
498
510
  sec.append(mk('span', 'lbl', 'Connects to'));
499
511
  if (!v.connectsTo.length) sec.append(mk('p', 'sub', 'Nothing on this map.'));
500
512
  for (const c of v.connectsTo) sec.append(this.#conn(c));
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "crewforth",
3
- "version": "3.3.0",
4
- "description": "Crewforth \u2014 your engineering crew for Claude Code: agents, skills and tool-level gates, in any stack.",
3
+ "version": "3.5.0-rc.1",
4
+ "description": "Crewforth — your engineering crew for Claude Code: agents, skills and tool-level gates, in any stack.",
5
5
  "bin": {
6
6
  "crewforth": "bin/cli.js"
7
7
  },
@@ -15,7 +15,8 @@
15
15
  "kit/",
16
16
  "VERSION",
17
17
  "LICENSE",
18
- "README.md", "CHANGELOG.md"
18
+ "README.md",
19
+ "CHANGELOG.md"
19
20
  ],
20
21
  "engines": {
21
22
  "node": ">=20"
@@ -42,7 +43,7 @@
42
43
  "code-review",
43
44
  "git-hooks"
44
45
  ],
45
- "author": "Bar\u0131\u015f Yerlikaya",
46
+ "author": "Barış Yerlikaya",
46
47
  "homepage": "https://crewforth.com/",
47
48
  "repository": {
48
49
  "type": "git",
package/start.sh CHANGED
@@ -33,6 +33,15 @@ if [ "$_HELP_ONLY" = 0 ] && [ ! -d "$SRC" ]; then
33
33
  echo "start.sh and kit/ must be in the SAME directory (both come together when you unzip)."
34
34
  exit 1
35
35
  fi
36
+ # A payload that lost files (a truncated archive, a folder dropped in a copy) is refused before the first write, so
37
+ # a broken download says so instead of installing gates that fail on every call. English, like the check above: it
38
+ # answers before the language is chosen. eval/lib/payload-check.sh says what it reads.
39
+ if [ "$_HELP_ONLY" = 0 ] && [ "${CREW_PAYLOAD_UNCHECKED:-0}" != 1 ] && [ -f "$SRC/eval/lib/payload-check.sh" ]; then
40
+ _pc="$(bash "$SRC/eval/lib/payload-check.sh" "$SRC" 2>&1)" || {
41
+ echo "ERROR: the kit/ payload is incomplete — nothing installed:"; printf '%s\n' "$_pc" | sed 's/^/ /'
42
+ echo "Download it again (npx crewforth@latest, or a fresh archive). CREW_PAYLOAD_UNCHECKED=1 installs it anyway."
43
+ exit 1; }
44
+ fi
36
45
 
37
46
  # Refuse to run inside a checkout of the kit's own source repository. This script ends by deleting
38
47
  # kit/ and itself, which is correct when the kit has been unpacked into a project and is being
@@ -569,7 +578,7 @@ count_installed() { # $1=glob -> count to install
569
578
  }
570
579
  N_AG="$(count_installed "$SRC/agents/*.md")"
571
580
  # Skills and commands the way the README and crewforth.com count them, so the install does not print a third number:
572
- # a skill whose metadata says `kind: command` is a command, one marked `experimental: true` is neither (it is
581
+ # a skill whose metadata says `kind: command` is a command, one marked `experimental: "true"` is neither (it is
573
582
  # installed and works when typed, but nothing advertises it). Read from the frontmatter with builtins only.
574
583
  count_showcase() { # $1 = a skills directory -> N_SKL N_CMD
575
584
  N_SKL=0; N_CMD=0; local f l k e d
@@ -577,7 +586,7 @@ count_showcase() { # $1 = a skills directory -> N_SKL N_CMD
577
586
  [ -f "$f" ] || continue; k=0; e=0; d=0
578
587
  while IFS= read -r l || [ -n "$l" ]; do
579
588
  l="${l%$'\r'}"
580
- case "$l" in ---) d=$((d+1)); [ "$d" -ge 2 ] && break ;; " kind: command") k=1 ;; " experimental: true") e=1 ;; esac
589
+ case "$l" in ---) d=$((d+1)); [ "$d" -ge 2 ] && break ;; " kind: command") k=1 ;; " experimental: true"|" experimental: \"true\"") e=1 ;; esac
581
590
  done < "$f"
582
591
  [ "$e" = 1 ] && continue
583
592
  if [ "$k" = 1 ]; then N_CMD=$((N_CMD+1)); else N_SKL=$((N_SKL+1)); fi