crewforth 3.1.1 → 3.2.0-rc.10

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 (110) hide show
  1. package/CHANGELOG.md +323 -0
  2. package/README.md +6 -3
  3. package/VERSION +1 -1
  4. package/adopt.sh +66 -6
  5. package/bin/cli.js +3 -2
  6. package/kit/AGENT_TEMPLATE.md +2 -1
  7. package/kit/CLAUDE.md +15 -14
  8. package/kit/agents/crew-commit-agent.md +11 -4
  9. package/kit/agents/crew-docs-agent.md +71 -0
  10. package/kit/agents/crew-planner.md +2 -0
  11. package/kit/agents/crew-refuter.md +77 -0
  12. package/kit/agents/crew-review-agent.md +2 -0
  13. package/kit/agents/crew-security-expert.md +3 -1
  14. package/kit/agents/crew-session-manager.md +11 -9
  15. package/kit/eval/doctor.sh +128 -6
  16. package/kit/eval/gate-report.sh +2 -0
  17. package/kit/eval/golden-routing.txt +9 -0
  18. package/kit/eval/lib/crew-map/crew-map.sh +26 -0
  19. package/kit/eval/lib/crew-map/detect.mjs +238 -0
  20. package/kit/eval/lib/crew-map/narrative.mjs +132 -0
  21. package/kit/eval/lib/crew-map/parse.mjs +305 -0
  22. package/kit/eval/lib/crew-map/project.mjs +164 -0
  23. package/kit/eval/lib/crew-map/rules/names.json +38 -0
  24. package/kit/eval/lib/crew-map/rules/packages.json +142 -0
  25. package/kit/eval/lib/crew-map/rules/python-stdlib.json +197 -0
  26. package/kit/eval/lib/crew-map/rules/tech.json +35 -0
  27. package/kit/eval/lib/crew-map/scan.mjs +266 -0
  28. package/kit/eval/lib/crew-map/stacks.mjs +547 -0
  29. package/kit/eval/lib/crew-map/surface.mjs +535 -0
  30. package/kit/eval/lib/findings/findings.mjs +410 -0
  31. package/kit/eval/lib/findings/findings.schema.json +122 -0
  32. package/kit/eval/lib/findings/findings.sh +29 -0
  33. package/kit/eval/lib/gate-log.sh +52 -0
  34. package/kit/eval/lib/json-string.awk +27 -0
  35. package/kit/eval/lib/preset.sh +109 -0
  36. package/kit/eval/lib/presets/default.json +7 -0
  37. package/kit/eval/lib/presets/solo.json +7 -0
  38. package/kit/eval/lib/presets/strict.json +11 -0
  39. package/kit/eval/lib/preview.sh +150 -0
  40. package/kit/eval/lib/review-bench/bench.mjs +273 -0
  41. package/kit/eval/lib/review-prepass/prepass.mjs +203 -0
  42. package/kit/eval/lib/review-prepass/prepass.sh +23 -0
  43. package/kit/eval/lib/settings-default.sh +36 -0
  44. package/kit/eval/lib/settings-json.awk +19 -4
  45. package/kit/eval/lib/star.sh +23 -7
  46. package/kit/eval/lib/statusline.settings.json +6 -0
  47. package/kit/eval/lib/statusline.sh +100 -0
  48. package/kit/eval/lib/verify-cmd.sh +17 -0
  49. package/kit/eval/lib/version.sh +41 -0
  50. package/kit/eval/review-bench.sh +26 -0
  51. package/kit/eval/smoke-test.sh +878 -53
  52. package/kit/hooks/agent-outcome.sh +44 -12
  53. package/kit/hooks/board-sync.sh +2 -23
  54. package/kit/hooks/guard-agent-model.sh +177 -24
  55. package/kit/hooks/guard-bash.sh +295 -204
  56. package/kit/hooks/guard-commit-scan.sh +10 -2
  57. package/kit/hooks/guard-question.sh +73 -0
  58. package/kit/hooks/guard-write.sh +24 -7
  59. package/kit/hooks/precompact-snapshot.sh +75 -0
  60. package/kit/hooks/prompt-approval.sh +389 -103
  61. package/kit/hooks/refuter-ledger.sh +80 -0
  62. package/kit/hooks/session-rehydrate.sh +25 -2
  63. package/kit/hooks/session-update-check.sh +9 -8
  64. package/kit/hooks/trace-blocklist.txt +9 -0
  65. package/kit/owned-blobs.tsv +4 -4
  66. package/kit/settings.json +51 -0
  67. package/kit/skills/crew-approve/SKILL.md +7 -0
  68. package/kit/skills/crew-code-review/SKILL.md +16 -0
  69. package/kit/skills/crew-doctor/SKILL.md +5 -1
  70. package/kit/skills/crew-handoff/SKILL.md +3 -2
  71. package/kit/skills/crew-map/SKILL.md +55 -0
  72. package/kit/skills/crew-review/SKILL.md +4 -1
  73. package/kit/skills/crew-ship/SKILL.md +3 -1
  74. package/kit/skills/crew-update/SKILL.md +1 -1
  75. package/kit/skills/incident-runbook/SKILL.md +2 -2
  76. package/kit/skills/security-scan/references/verify.md +6 -3
  77. package/kit/studio/README.md +25 -2
  78. package/kit/studio/server/index.js +187 -21
  79. package/kit/studio/server/lib/code-stamp.js +35 -0
  80. package/kit/studio/server/lib/crew-map.js +113 -0
  81. package/kit/studio/server/lib/graph.js +9 -1
  82. package/kit/studio/server/lib/kit-telemetry.js +30 -5
  83. package/kit/studio/server/lib/lan.js +87 -0
  84. package/kit/studio/server/lib/model-outcomes.js +130 -15
  85. package/kit/studio/server/lib/owned-stream.js +56 -0
  86. package/kit/studio/server/lib/pricing.js +3 -0
  87. package/kit/studio/server/lib/projects.js +11 -4
  88. package/kit/studio/server/lib/redact.js +60 -0
  89. package/kit/studio/server/lib/remote.js +185 -0
  90. package/kit/studio/server/lib/session.js +1 -1
  91. package/kit/studio/server/lib/terminal.js +51 -13
  92. package/kit/studio/server/lib/transcript.js +32 -0
  93. package/kit/studio/server/lib/usage.js +74 -14
  94. package/kit/studio/web/app.js +287 -17
  95. package/kit/studio/web/canvas.js +58 -122
  96. package/kit/studio/web/chat.js +114 -24
  97. package/kit/studio/web/graph-plan.js +13 -6
  98. package/kit/studio/web/index.html +10 -5
  99. package/kit/studio/web/liveness.js +14 -0
  100. package/kit/studio/web/map-plan.js +410 -0
  101. package/kit/studio/web/map.js +596 -0
  102. package/kit/studio/web/models-plan.js +61 -7
  103. package/kit/studio/web/models.js +19 -3
  104. package/kit/studio/web/qr.js +200 -0
  105. package/kit/studio/web/style.css +165 -0
  106. package/kit/studio/web/tokens.css +6 -0
  107. package/kit/studio/web/usage.js +12 -0
  108. package/kit/studio/web/viewport.js +160 -0
  109. package/package.json +6 -5
  110. package/start.sh +24 -1
@@ -0,0 +1,160 @@
1
+ // A pane that pans and zooms one layer, and cards that can be dragged on it.
2
+ //
3
+ // The graph (canvas.js) and the project map (map.js) both draw cards on a layer the viewer moves around. This is
4
+ // that part, once: a drag on the empty pane pans, the wheel zooms about the pointer (or pans, when it is mostly
5
+ // sideways), Fit puts the whole picture in the pane, and a card dragged more than a few pixels is placed by hand.
6
+ // Where things go and what a card says stay with the view that owns them.
7
+ import { ZOOM, fitZoom } from './graph-plan.js';
8
+
9
+ const clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
10
+
11
+ export class Viewport {
12
+ /**
13
+ * @param pane the element the pointer works on; its size is the pane's
14
+ * @param layer the element moved and scaled: one transform, so a zoom restyles nothing
15
+ * @param skip (target) => true when a press on it is not a pan (a card, a control on the pane)
16
+ * @param onApply called after every change of the view
17
+ */
18
+ constructor(pane, layer, { skip = () => false, onApply = () => {} } = {}) {
19
+ this.pane = pane;
20
+ this.layer = layer;
21
+ this.skip = skip;
22
+ this.onApply = onApply;
23
+ this.view = { x: 0, y: 0, k: 1 };
24
+ this.touched = false; // the viewer moved the view themselves
25
+ this.#wire();
26
+ }
27
+
28
+ #wire() {
29
+ let panning = null;
30
+ this.pane.addEventListener('pointerdown', (e) => {
31
+ if (e.button !== 0) return; // right/middle must not pan
32
+ if (this.skip(e.target)) return;
33
+ panning = { px: e.clientX, py: e.clientY, x: this.view.x, y: this.view.y };
34
+ this.pane.setPointerCapture(e.pointerId);
35
+ this.pane.classList.add('cv-panning');
36
+ });
37
+ this.pane.addEventListener('pointermove', (e) => {
38
+ if (!panning) return;
39
+ this.view.x = panning.x + (e.clientX - panning.px);
40
+ this.view.y = panning.y + (e.clientY - panning.py);
41
+ this.touched = true;
42
+ this.apply();
43
+ });
44
+ const endPan = () => { panning = null; this.pane.classList.remove('cv-panning'); };
45
+ this.pane.addEventListener('pointerup', endPan);
46
+ this.pane.addEventListener('pointercancel', endPan);
47
+
48
+ this.pane.addEventListener('wheel', (e) => {
49
+ e.preventDefault();
50
+ const r = this.pane.getBoundingClientRect();
51
+ const mx = e.clientX - r.left;
52
+ const my = e.clientY - r.top;
53
+ // deltaMode 1 is lines and 2 is pages; Firefox reports ~3 where Chrome reports ~100, which made zoom
54
+ // imperceptible and pan crawl.
55
+ const unit = e.deltaMode === 1 ? 16 : e.deltaMode === 2 ? r.height : 1;
56
+ const dY = e.deltaY * unit;
57
+ const dX = e.deltaX * unit;
58
+ if (e.ctrlKey || e.metaKey || Math.abs(dY) > Math.abs(dX)) {
59
+ this.zoomAbout(this.view.k * Math.exp(-dY * 0.0016), mx, my);
60
+ } else {
61
+ this.view.x -= dX;
62
+ this.view.y -= dY;
63
+ }
64
+ this.touched = true;
65
+ this.apply();
66
+ }, { passive: false });
67
+ }
68
+
69
+ /** Zoom to `k`, keeping the world point under (mx, my) where it is. */
70
+ zoomAbout(k, mx, my) {
71
+ const next = clamp(k, ZOOM.min, ZOOM.max);
72
+ this.view.x = mx - (mx - this.view.x) * (next / this.view.k);
73
+ this.view.y = my - (my - this.view.y) * (next / this.view.k);
74
+ this.view.k = next;
75
+ }
76
+
77
+ /** Step the zoom from a toolbar: about the middle of the pane. */
78
+ zoomBy(factor) { this.zoomTo(this.view.k * factor); }
79
+
80
+ zoomTo(k) {
81
+ const r = this.pane.getBoundingClientRect();
82
+ this.zoomAbout(k, r.width / 2, r.height / 2);
83
+ this.touched = true;
84
+ this.apply();
85
+ }
86
+
87
+ /** Turn `view` into pixels: the single place that does. */
88
+ apply() {
89
+ const { x, y, k } = this.view;
90
+ this.layer.style.transform = `translate(${x}px, ${y}px) scale(${k})`;
91
+ this.onApply();
92
+ }
93
+
94
+ /** The whole picture (width × height, in the layer's own pixels) in the pane, never above 100%. */
95
+ /** Fit the picture to the pane; `floor` is a zoom it does not go under (the picture then starts at its top). */
96
+ fit(width, height, floor = 0) {
97
+ const r = this.pane.getBoundingClientRect();
98
+ const k = Math.max(fitZoom(width, height, r.width, r.height), Math.min(floor, ZOOM.fitMax));
99
+ this.view.k = k;
100
+ this.view.x = Math.max(ZOOM.pad, (r.width - width * k) / 2);
101
+ this.view.y = Math.max(ZOOM.pad, (r.height - height * k) / 2);
102
+ this.touched = false;
103
+ this.apply();
104
+ }
105
+
106
+ /** Bring a point of the layer to the middle of the pane, at least at `minK`. */
107
+ centerOn(cx, cy, minK = 1) {
108
+ const r = this.pane.getBoundingClientRect();
109
+ this.view.k = clamp(Math.max(this.view.k, minK), ZOOM.min, ZOOM.max);
110
+ this.view.x = r.width / 2 - cx * this.view.k;
111
+ this.view.y = r.height / 2 - cy * this.view.k;
112
+ this.touched = true;
113
+ this.apply();
114
+ }
115
+ }
116
+
117
+ /**
118
+ * A card that can be placed by hand. A press that moves more than 3 px (at the pane's zoom) is a drag, and the
119
+ * click that ends it is swallowed: without that, every reposition also toggled the selection.
120
+ * @param scale () => the pane's zoom
121
+ * @param at () => the card's position { x, y } when the drag starts
122
+ * @param move (x, y) => the card is now here
123
+ * @param end () => the drag is over (persist the place)
124
+ * @param ignore (target) => true for a part of the card that answers the press itself
125
+ */
126
+ export function draggable(el, { scale, at, move, end = () => {}, ignore = () => false }) {
127
+ let drag = null;
128
+ el.addEventListener('pointerdown', (e) => {
129
+ if (e.button !== 0) return;
130
+ e.stopPropagation(); // do not pan the pane too
131
+ if (ignore(e.target)) return;
132
+ const p = at() ?? { x: 0, y: 0 };
133
+ drag = { px: e.clientX, py: e.clientY, x: p.x, y: p.y, moved: false };
134
+ el.setPointerCapture(e.pointerId);
135
+ });
136
+ el.addEventListener('pointermove', (e) => {
137
+ if (!drag) return;
138
+ const dx = (e.clientX - drag.px) / scale();
139
+ const dy = (e.clientY - drag.py) / scale();
140
+ if (!drag.moved && Math.abs(dx) <= 3 && Math.abs(dy) <= 3) return;
141
+ drag.moved = true;
142
+ el.classList.add('cv-dragging');
143
+ move(Math.round(drag.x + dx), Math.round(drag.y + dy));
144
+ });
145
+ const stop = () => {
146
+ if (!drag) return;
147
+ if (drag.moved) { end(); el.dataset.suppressClick = '1'; }
148
+ drag = null;
149
+ el.classList.remove('cv-dragging');
150
+ };
151
+ el.addEventListener('pointerup', stop);
152
+ el.addEventListener('pointercancel', stop);
153
+ }
154
+
155
+ /** Did a drag just end on this element? Answers once, so the click that ends a drag does nothing. */
156
+ export function swallowClick(el) {
157
+ if (el.dataset.suppressClick !== '1') return false;
158
+ el.dataset.suppressClick = '0';
159
+ return true;
160
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "crewforth",
3
- "version": "3.1.1",
4
- "description": "Crewforth \u2014 your engineering crew for Claude Code: agents, skills and tool-level gates, in any stack.",
3
+ "version": "3.2.0-rc.10",
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,10 +43,10 @@
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",
49
- "url": "git+https://github.com/Crewforth/crewforth.git"
50
+ "url": "git+https://github.com/crewforth/crewforth.git"
50
51
  }
51
52
  }
package/start.sh CHANGED
@@ -96,6 +96,8 @@ _mt() { # $1 = English text (the key); further args fill %s; result in _M
96
96
  if [ "$CREW_LANG" = tr ]; then
97
97
  case "$s" in
98
98
  "Crewforth · setup wizard") s='Crewforth · kurulum' ;;
99
+ "Status line: Crewforth's (the gate's last word, a standing approval, crew agents, fill, cost).") s="Durum satırı: Crewforth'unki (kapının son sözü, geçerli onay, crew ajanları, doluluk, maliyet)." ;;
100
+ "Status line: yours is kept; Crewforth's is one command away (see /crew-doctor).") s="Durum satırı: seninki korundu; Crewforth'unki tek komutla açılır (/crew-doctor'a bak)." ;;
99
101
  "stack-agnostic") s='her yığınla çalışır' ;;
100
102
  "[1/2] Who is this install for?") s='[1/2] Kurulumu kim kullanacak?' ;;
101
103
  "Decides whether your teammates get Crewforth's configuration — and what goes into .gitignore.") s="Crewforth ayarlarının ekiple paylaşılıp paylaşılmayacağını ve .gitignore'a nelerin ekleneceğini belirler." ;;
@@ -189,6 +191,8 @@ Crewforth her zaman eksiksiz kurulur: tüm ajanlar ve skill'ler — backend, web
189
191
  (CLAUDE.md ## Stack ya da depo). React Native/Expo, hazır isteğe bağlı katmanlardan yalnız biridir.
190
192
  --backend | --frontend | --mobile | --fullstack hâlâ kabul edilir ama etkisi yok (eski komutlar bozulmasın diye)
191
193
  --private | --shared kurulum yalnızca sizin mi, yoksa ekiple paylaşılıp commit'lenecek mi? (varsayılan: private)
194
+ --preset <ad|dosya> bir kurum preset'i uygula: kritik yollar, model tabanları, izinli verify komutları
195
+ (gelenler: default, strict, solo; ya da kendi .json dosyanız)
192
196
  --lang tr|en kurulum dili (verilmezse sihirbaz sorar)
193
197
  --yes, -y tüm sorulara evet de (gözetimsiz kurulum)
194
198
  --version, -v Crewforth sürümünü yazdırıp çık
@@ -207,6 +211,8 @@ Every install ships everything: all agents, all skills — backend, web and mobi
207
211
  (CLAUDE.md ## Stack, or the repo). React Native/Expo is only one of the optional ready-made layers.
208
212
  --backend | --frontend | --mobile | --fullstack accepted, no effect (kept so older commands still run)
209
213
  --private | --shared is the install yours alone, or committed for the team? (default: private)
214
+ --preset <name|file> apply an organisation preset: critical paths, model floors, allowed verify commands
215
+ (shipped: default, strict, solo; or a .json file of your own)
210
216
  --lang tr|en installer language (asked interactively when not given)
211
217
  --yes, -y answer every question with yes (unattended install)
212
218
  --version, -v print the Crewforth version and exit
@@ -430,9 +436,13 @@ case "$CREW_LANG" in tr|en) ;; *) CREW_LANG=en ;; esac
430
436
  # choice made by --lang or the menu above stayed in this shell, and the preflight block printed English.
431
437
  export CREW_LANG
432
438
 
433
- STACK="generic"; DOTNET_FLAG=0; LEGACY_FLAGS=""; ASSUME_YES=0; VISIBILITY=""
439
+ STACK="generic"; DOTNET_FLAG=0; LEGACY_FLAGS=""; ASSUME_YES=0; VISIBILITY=""; PRESET=""; _preset_take=0
434
440
  for a in "$@"; do
441
+ # `--preset NAME`: the next word is its value, whatever it looks like (`--preset strict`, `--preset ./org.json`).
442
+ if [ "$_preset_take" = 1 ]; then _preset_take=0; PRESET="$a"; continue; fi
435
443
  case "$a" in
444
+ --preset=*) PRESET="${a#--preset=}" ;;
445
+ --preset) _preset_take=1 ;;
436
446
  --lang) ;; # value consumed in the language pass above
437
447
  --lang=*) ;;
438
448
  tr|en) ;; # the value of a separated --lang
@@ -446,6 +456,9 @@ for a in "$@"; do
446
456
  *) printf '%s\n' "$(m 'Unknown parameter: %s' "$a")"; echo; usage; exit 1 ;;
447
457
  esac
448
458
  done
459
+ [ "$_preset_take" = 1 ] && { printf '%s\n' "$(m 'Unknown parameter: %s' "--preset (no value)")"; echo; usage; exit 1; }
460
+ # A preset is checked before anything is installed: one that is not valid stops here, with nothing written.
461
+ if [ -n "$PRESET" ]; then bash "$SRC/eval/lib/preset.sh" --check "$PRESET" || exit 1; fi
449
462
 
450
463
  # ===================== COLOR / STYLE HELPERS =====================
451
464
  # Color is emitted ONLY on an interactive TTY + TERM!=dumb + NO_COLOR empty.
@@ -638,6 +651,14 @@ count_showcase .claude/skills
638
651
  # studio's gate hook is invoked as `bash <path>` today, so the bit is not load-bearing
639
652
  # yet; set it anyway, because a future direct exec would fail silently.
640
653
  chmod +x .claude/hooks/*.sh .claude/hooks/pre-commit .claude/hooks/commit-msg .claude/eval/*.sh .claude/studio/server/hooks/*.sh 2>/dev/null || true
654
+ # The status line, only where none is set: a user's own would lose to the project's setting, so it is never
655
+ # replaced. eval/lib/statusline.sh --install decides and writes; this only says what it did.
656
+ if [ -f .claude/eval/lib/statusline.sh ]; then
657
+ case "$(bash .claude/eval/lib/statusline.sh --install 2>/dev/null)" in
658
+ installed) _mt "Status line: Crewforth's (the gate's last word, a standing approval, crew agents, fill, cost)."; echo " ${_M}" ;;
659
+ kept) _mt "Status line: yours is kept; Crewforth's is one command away (see /crew-doctor)."; echo " ${_M}" ;;
660
+ esac
661
+ fi
641
662
 
642
663
  # §4.2: a vendor line that was armed before this run stays armed (the copy above resets it to a comment); nothing
643
664
  # else arms it. Rationale, and the measured failure of "arm it whenever the pattern skill exists", in adopt.sh.
@@ -679,6 +700,8 @@ cp "$SRC/README.md" .claude/ 2>/dev/null || true
679
700
  # as if somebody had chosen it. --lang, CREW_LANG and the menu are choices, and are.
680
701
  [ "${_LANG_GUESSED:-0}" = 1 ] || echo "lang=${CREW_LANG:-en}"
681
702
  } > .claude/kit.conf
703
+ # The organisation's preset, now that .claude/ is in place (checked above): a block at the top of crew-model-rules.
704
+ if [ -n "$PRESET" ]; then bash .claude/eval/lib/preset.sh --apply "$PRESET" || exit 1; fi
682
705
 
683
706
  # Discipline (kit-owned, refreshed on every update) vs project section (yours, written once), joined by @import.
684
707
  kit_require_sentinel "$SRC/CLAUDE.md"