crewforth 3.0.0-rc.3 → 3.0.1-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 (41) hide show
  1. package/CHANGELOG.md +94 -1
  2. package/VERSION +1 -1
  3. package/adopt.sh +186 -19
  4. package/kit/CLAUDE.md +5 -4
  5. package/kit/README.md +1 -1
  6. package/kit/agents/crew-frontend-expert.md +6 -6
  7. package/kit/agents/crew-session-manager.md +1 -1
  8. package/kit/eval/doctor.sh +72 -18
  9. package/kit/eval/golden-routing.txt +15 -0
  10. package/kit/eval/preflight.sh +2 -2
  11. package/kit/eval/smoke-test.sh +625 -29
  12. package/kit/hooks/board-sync.sh +1 -1
  13. package/kit/hooks/board.sh +4 -3
  14. package/kit/hooks/guard-bash.sh +195 -21
  15. package/kit/hooks/guard-commit-scan.sh +2 -2
  16. package/kit/hooks/guard-powershell.sh +328 -0
  17. package/kit/hooks/guard-write.sh +28 -3
  18. package/kit/hooks/route-hint.sh +5 -1
  19. package/kit/hooks/session-update-check.sh +2 -2
  20. package/kit/hooks/skill-trust.sh +22 -11
  21. package/kit/legacy-blobs.tsv +208 -0
  22. package/kit/settings.json +10 -0
  23. package/kit/skills/automode-policy/SKILL.md +1 -1
  24. package/kit/skills/automode-policy/scripts/check.sh +2 -2
  25. package/kit/skills/crew-board/SKILL.md +1 -1
  26. package/kit/skills/crew-doctor/SKILL.md +3 -3
  27. package/kit/skills/crew-gates/SKILL.md +2 -1
  28. package/kit/skills/crew-handoff/SKILL.md +1 -1
  29. package/kit/skills/crew-skill/SKILL.md +2 -2
  30. package/kit/skills/crew-studio/SKILL.md +3 -2
  31. package/kit/skills/crew-update/SKILL.md +3 -3
  32. package/kit/skills/frontend/SKILL.md +26 -2
  33. package/kit/skills/frontend-rn-expo/SKILL.md +3 -0
  34. package/kit/skills/handoff/SKILL.md +2 -2
  35. package/kit/skills/reflect/SKILL.md +2 -2
  36. package/kit/skills/teamboard/SKILL.md +1 -1
  37. package/kit/skills/token-budget/SKILL.md +2 -2
  38. package/kit/skills/trace-scan/SKILL.md +4 -3
  39. package/kit/studio/README.md +1 -1
  40. package/package.json +1 -1
  41. package/start.sh +15 -8
package/CHANGELOG.md CHANGED
@@ -5,7 +5,100 @@ Crewforth was named Claude Starter Kit until 3.0.0.
5
5
  Notable changes to this project are recorded here. Format follows [Keep a Changelog](https://keepachangelog.com/en/),
6
6
  versioning follows [SemVer](https://semver.org/).
7
7
 
8
- ## [Unreleased] — 3.0.0
8
+ ## [Unreleased] — 3.0.1
9
+
10
+ ### Security
11
+
12
+ - A chained command could write `core.hooksPath` through the gate: `git config --get core.hooksPath && git config
13
+ core.hooksPath <path>` passed, because a `--get` anywhere in the line exempted all of it. Fixed.
14
+ - The same shape let a harmless command carry a gated one in three more rules — infrastructure teardown
15
+ (`terraform --help && terraform destroy -auto-approve`), `.env` reads (`cat .env.example; cat .env`) and private
16
+ keys / credentials (`cat ~/.ssh/id_rsa.pub; cat ~/.ssh/id_rsa`). An exemption now covers only the command it is in.
17
+ - `(cat .env)`, `$(cat .env)` and `` `cat .env` `` read the file without being stopped, and
18
+ `cat .env.example $(cat .env)` hid the read behind the template in the same line. Fixed.
19
+ - `core.hooksPath` could be redirected past the gate by spellings git accepts: a lower-case key
20
+ (`core.hookspath`), `git -C <dir> config …`, a backslash-newline between `config` and the key, and removing or
21
+ renaming the whole `[core]` section. All blocked now.
22
+
23
+ - In the plugin edition, a shell command could delete, overwrite or rename the gate scripts and their wiring
24
+ (`rm <plugin>/hooks/guard-bash.sh` passed, while the same command on `.claude/hooks/` was refused), and the Write
25
+ tool guarded only the gate scripts it knew by name. Everything under the plugin's `hooks/` and `.claude-plugin/` —
26
+ in this version and in every other version of the plugin in Claude Code's cache, however the path is spelled — is
27
+ now guarded like `.claude/hooks/`. Reading them stays free.
28
+ - The gates read `eval/lib/crew-env.sh` on every call, but the file itself was not guarded: overwritten, it switched
29
+ every rule off. It is now guarded in both editions.
30
+
31
+ ### Fixed
32
+
33
+ - Reading `core.hooksPath` without a value (`git config core.hooksPath`, with or without `--local` / `--global` /
34
+ `--system`) was refused as tampering. It passes now; every write form stays blocked.
35
+ - Updating a project that carried `cqrs-aop-module` but was recorded as `stack=generic` left the skill untrusted,
36
+ and its first session asked whether to trust the pattern skill Crewforth itself had shipped. The update now
37
+ vouches for the skill when every file in it is byte-for-byte a copy Crewforth shipped (2.12–2.13, or
38
+ `devarch-module` renamed in place), whatever the recorded stack. An edited copy is named instead, and the next
39
+ session asks — an edited skill is the user's work.
40
+ - When recording that trust failed, the update said nothing. It now says so, with the reason.
41
+ - Updating a pre-1.1 install (discipline inline in `CLAUDE.md`) on the current branch stopped with
42
+ `TS: unbound variable` when it replaced the inline block. Fixed.
43
+ - In the plugin edition, skills and agents told the model to run `bash .claude/…`, a path a plugin install does not
44
+ have: `/crew-handoff`, the fill reading, `reflect`, `handoff`, the board and the auto-mode policy scripts all
45
+ exited 127. They now name the plugin's own directory, quoted. `/crew-gates` and the skill-usage report say they are
46
+ file-install tools; `trace-scan` names the plugin's pattern list; the panel's Node lookup is quoted.
47
+ - `/crew-doctor`'s shell-gate check could say "watch both Bash and PowerShell" for an install whose Bash guard
48
+ watched only Bash, stayed silent when the guard was not wired at all, and printed its ❌ after "healthy ✅". It now
49
+ reads the guard's own entry with the JSON reader, and its result counts in the verdict.
50
+ - The board printed a path a plugin install cannot run when it was started from its own folder. Fixed.
51
+ - `bash start.sh --help` answered "kit/ not found" and exited 1 when `kit/` was not beside it. Help now needs nothing
52
+ but the script.
53
+ - A site deploy could report success while GitHub Pages kept serving the previous version (at the 3.0.0 launch the
54
+ site showed the release candidate's changelog for half an hour). The site workflow now reads what Pages serves after
55
+ each deploy and fails if it is still the old build.
56
+ - The unvetted-component notice offered one command, `--trust`, that accepted every foreign component at once —
57
+ including ones the user never looked at — and told the model to ask before anything else, so a user's urgent first
58
+ message waited behind it. Each component now carries its own `--trust-one` command, and the model answers the
59
+ user first and asks at the end of that first reply; until then the components are not used.
60
+ - When a subagent handed its report back, the routing hint read the report as a new request and suggested
61
+ delegating the work that had just been done — "Use the crew-database-expert subagent" right after that agent
62
+ finished. A hand-back now gets no hint.
63
+ - A message that only named the frontend in passing ("if there is work for acme_ui, pass it over — the frontend
64
+ team owns that") was routed to the `frontend` skill: the bare word "frontend" and a project called `<x>_ui` each
65
+ matched. The skill now triggers on phrases that carry the intent ("in the frontend", "the UI", "UI component").
66
+
67
+ ### Added
68
+
69
+ - When the model sends one of Crewforth's own scripts through the PowerShell tool — where `bash` can be WSL's and
70
+ the script fails — the call is stopped and the model is told to use the Bash tool. "Crewforth's own" is every `.sh`
71
+ it installs, except a skill of your own. Every other PowerShell command passes as before, including searches and
72
+ messages that merely mention such a command.
73
+ - An update moves aside what an older version installed and Crewforth no longer ships — the v1 `-cck` agents, the
74
+ plain v1 commands (`/plan`, `/review`, …), `vps-deploy`, `code-review` — when every file is byte-for-byte a copy
75
+ Crewforth shipped. They go to `.claude/.legacy-backup/<time>/` and the update prints the one line that puts them
76
+ back. A component with an edit, an extra file or a symlink stays where it is and is named. Nothing is deleted, and
77
+ it works without an install manifest. A moved skill is no longer raised as unvetted at the next session start.
78
+ - `/crew-doctor` reports when the install is missing what its manifest lists, or has no manifest next to `VERSION`
79
+ — the last update did not finish, or `.claude/` was copied from another project — and suggests
80
+ `npx crewforth update --here`. It changes nothing. A component removed on purpose looks the same on disk, so the
81
+ line says to ignore it in that case.
82
+
83
+ ### Changed
84
+
85
+ - The discipline now says where a project's own `CLAUDE.md` stands: it wins on conflict, and in §4 it can only
86
+ tighten, never loosen — the one exception is the §4.1 trace allowlist chosen at adoption. The § numbers in the
87
+ discipline refer to the discipline itself. The updater's summary, handover and decision record say the same.
88
+ - Every place that tells the model to run one of Crewforth's scripts now says to use the Bash tool, not PowerShell.
89
+ - The client side is stack-neutral too. The install summary, help and install page named the scope "backend, web and
90
+ mobile (RN/Expo)"; they now say the stack is read from the project, and React Native/Expo is one optional layer. The
91
+ first client task resolves the stack the way the backend does — the request, `Client:` in `## Stack`, the repo's
92
+ manifests (`package.json`, `pubspec.yaml`, `Package.swift`, `build.gradle`, a MAUI `*.csproj`) — and in an empty repo
93
+ asks once, recommending nothing, then records it. `frontend-rn-expo` applies only to an RN/Expo project; Flutter,
94
+ SwiftUI and Compose requests go to `crew-frontend-expert`.
95
+ - `/crew-doctor` and the doctor said skills past the listing budget "stop being picked" / "stop matching requests".
96
+ They are picked less often on their own, not never; the warning now says so, in English and Turkish.
97
+ - The Bash guard no longer starts a process per rule on the read-only path: `git status` went from 14 processes per
98
+ call to 1 (macOS: ~35 ms to ~10 ms per call). On a Windows machine where one process was measured at 1.3–3.9 s,
99
+ the old path cost 37–66 s per call.
100
+
101
+ ## [3.0.0] — 2026-09-28
9
102
 
10
103
  ### BREAKING — Claude Starter Kit is now Crewforth
11
104
 
package/VERSION CHANGED
@@ -1 +1 @@
1
- 3.0.0
1
+ 3.0.1
package/adopt.sh CHANGED
@@ -125,6 +125,9 @@ _mt() { # $1 = English text (the key); further args fill %s; result in _M
125
125
  "stack=%s %s") s='stack=%s %s' ;;
126
126
  "stack=%s · via %s") s='stack=%s · kuran: %s' ;;
127
127
  "stack=dotnet — 3.0 records generic; the pattern skill stays as a project skill") s="stack=dotnet — 3.0 generic kaydeder; desen skill'i proje skill'i olarak kalır" ;;
128
+ '⚠️ cqrs-aop-module could not be recorded as trusted (skill-trust.sh exit %s: %s) — the next session will ask about it') s="⚠️ cqrs-aop-module güvenilir olarak kaydedilemedi (skill-trust.sh çıkış kodu %s: %s) — bir sonraki oturum onu soracak" ;;
129
+ "cqrs-aop-module could not be checked against the copies Crewforth shipped (git or the shipped list is missing) — not vouched for; the next session will ask whether to trust it.") s="cqrs-aop-module, Crewforth'un gönderdiği kopyalarla karşılaştırılamadı (git ya da gönderilen liste yok) — güvenilir sayılmadı; bir sonraki oturum güvenip güvenmeyeceğinizi soracak." ;;
130
+ "cqrs-aop-module is 2.x's .NET pattern, changed since Crewforth shipped it — not vouched for; the next session will ask whether to trust it.") s="cqrs-aop-module 2.x'in .NET deseni; Crewforth'un gönderdiği halinden değiştirilmiş — güvenilir sayılmadı; bir sonraki oturum güvenip güvenmeyeceğinizi soracak." ;;
128
131
  "cqrs-aop-module is now a project skill (Crewforth no longer ships it); backend-expert applies it as your project's pattern.") s="cqrs-aop-module artık bir proje skill'i (Crewforth onu artık taşımıyor); backend-expert onu projenizin deseni olarak uygular." ;;
129
132
  "CSK_CORRECT_STACK has no effect since 3.0 — there is one backend shape; the stack lives in CLAUDE.md ## Stack.") s="CSK_CORRECT_STACK 3.0'dan beri etkisiz — tek bir backend biçimi var; yığın CLAUDE.md ## Stack bölümünde durur." ;;
130
133
  "§4.2: DevArchitecture stays armed in the trace blocklist (it was armed before this update)") s="§4.2: DevArchitecture iz engel listesinde devrede kalıyor (güncellemeden önce de devredeydi)" ;;
@@ -134,7 +137,7 @@ _mt() { # $1 = English text (the key); further args fill %s; result in _M
134
137
  "no/untracked") s='hayır / izlenmiyor' ;;
135
138
  ".claude/CLAUDE.md in git") s=".claude/CLAUDE.md git'te mi" ;;
136
139
  "supply-chain scan flagged existing project skills/agents (advisory — review before trusting them):") s='tedarik zinciri taraması projedeki bazı skill/ajanları işaretledi (yalnız uyarı — güvenmeden önce inceleyin):' ;;
137
- "full report after install: %s (heuristic; a security skill can score low by design)") s="kurulumdan sonra tam rapor: %s (sezgisel; güvenlik skill'leri doğası gereği düşük puan alabilir)" ;;
140
+ "full report after install: %s (Bash tool, not PowerShell; heuristic — a security skill can score low by design)") s="kurulumdan sonra tam rapor: %s (PowerShell değil, Bash aracıyla; sezgisel — güvenlik skill'leri doğası gereği düşük puan alabilir)" ;;
138
141
  "supply-chain scan") s='tedarik zinciri taraması' ;;
139
142
  "existing project skills/agents look clean (no red flags)") s='projedeki skill/ajanlar temiz görünüyor (şüpheli bir şey yok)' ;;
140
143
  "[3] 7 handover decisions — SMART SUGGESTION") s='[3] Devralma için 7 karar — AKILLI ÖNERİ' ;;
@@ -148,7 +151,7 @@ _mt() { # $1 = English text (the key); further args fill %s; result in _M
148
151
  "no custom agents found in the project") s='projede özel ajan yok' ;;
149
152
  "2 Precedence") s='2 Öncelik' ;;
150
153
  "project wins (fixed)") s='proje önde (sabit)' ;;
151
- "on conflict the project's rules always win; Crewforth fills gaps (not overridable)") s='çakışmada her zaman projenin kuralı geçerli; Crewforth yalnız boşlukları doldurur (değiştirilemez)' ;;
154
+ "on conflict the project's rules win; in DISCIPLINE.md §4 they can only tighten, never loosen (the one exception: the §4.1 trace allowlist you choose at adoption); Crewforth fills gaps (not overridable)") s="çakışmada projenin kuralı geçerli; DISCIPLINE.md §4'te proje yalnız sıkılaştırabilir, gevşetemez (tek istisna: kurulumda sizin seçtiğiniz §4.1 iz izin listesi); Crewforth boşlukları doldurur (değiştirilemez)" ;;
152
155
  "3 Trace gate") s='3 İz kapısı' ;;
153
156
  "loosen (.trace-allowlist)") s='gevşet (.trace-allowlist)' ;;
154
157
  "co-author/sign-off present in git log — may be a convention") s='git geçmişinde co-author/sign-off var — ekibin alışkanlığı olabilir' ;;
@@ -227,6 +230,15 @@ _mt() { # $1 = English text (the key); further args fill %s; result in _M
227
230
  "The name is the invocation: a leftover COMMAND still lists in the / picker (/review twice), and a") s='Burada adın kendisi çağrıdır: artakalan bir KOMUT / menüsünde hâlâ görünür (/review iki kez), artakalan' ;;
228
231
  "leftover SKILL still matches prompts, so it competes with whatever replaced it.") s='bir SKILL de istemlerle eşleşmeye devam eder ve yerine gelenle yarışır.' ;;
229
232
  "Nothing is deleted for you — one of these may be a file you customised. To drop them all:") s='Hiçbiri sizin yerinize silinmez — aralarında özelleştirdiğiniz bir dosya olabilir. Hepsini kaldırmak için:' ;;
233
+ 'older Crewforth components could not be checked against what Crewforth shipped (git or the shipped list is missing) — left in place:%s') s="eski Crewforth bileşenleri Crewforth'un dağıttıklarıyla karşılaştırılamadı (git ya da dağıtım listesi yok) — yerinde bırakıldı:%s" ;;
234
+ 'moved aside — each one exactly as an older Crewforth shipped it, and no longer shipped:%s') s="kenara alındı — her biri eski bir Crewforth'un dağıttığıyla birebir aynı ve artık dağıtılmıyor:%s" ;;
235
+ 'to put them back: cp -R %s/. .claude/') s='geri koymak için: cp -R %s/. .claude/' ;;
236
+ 'left in place — installed by an older Crewforth, no longer shipped, and changed since (yours now?):%s') s="yerinde bırakıldı — eski bir Crewforth kurmuştu, artık dağıtılmıyor ve o günden beri değişmiş (artık sizin mi?):%s" ;;
237
+ 'Each still answers its name (a command lists in the / picker, a skill matches prompts). To drop them:') s='Her biri adıyla çağrılmaya devam eder (komut / menüsünde görünür, skill istemlerle eşleşir). Kaldırmak için:' ;;
238
+ '%s (an unchanged copy Crewforth shipped) moved aside; your %s was not touched') s="%s (Crewforth'un dağıttığı, değişmemiş kopya) yedeğe taşındı; sizin %s dosyanıza dokunulmadı" ;;
239
+ '%s (a second, unchanged copy Crewforth shipped) moved aside; %s already answers that command') s="%s (Crewforth'un dağıttığı ikinci, değişmemiş kopya) yedeğe taşındı; o komutu zaten %s karşılıyor" ;;
240
+ 'older Crewforth components in a symlinked (shared?) directory are left in place — moving them out would remove them from every project that uses it:%s') s='sembolik bağlantılı (paylaşılan?) bir dizindeki eski Crewforth bileşenleri yerinde bırakıldı — taşımak onları bu dizini kullanan her projeden kaldırırdı:%s' ;;
241
+ '(an unchanged copy put back is moved aside again by the next update — to keep one, change it)') s='(geri konan değişmemiş bir kopya sonraki güncellemede yine kenara alınır — birini tutmak için onu değiştirin)' ;;
230
242
  "Coexist summary") s='Kurulum özeti' ;;
231
243
  "%s · %s skipped") s='%s · %s atlandı' ;;
232
244
  "+%s added") s='+%s eklendi' ;;
@@ -301,7 +313,7 @@ _mt() { # $1 = English text (the key); further args fill %s; result in _M
301
313
  "staged") s='stage edilen' ;;
302
314
  "see it: open the Source Control / Changes panel (every added + changed file is listed) · or: %s") s='görmek için: Source Control / Changes panelini açın (eklenen ve değişen her dosya orada) · ya da: %s' ;;
303
315
  "panel: %s opens it from this project (or: %s)") s='panel: bu projede %s ile açılır (ya da: %s)' ;;
304
- "panel: needs Node 18+, absent here — Crewforth can fetch one: %s") s='panel: Node 18+ gerekiyor ve bu makinede yok — Crewforth indirebilir: %s' ;;
316
+ "panel: needs Node 18+, absent here — Crewforth can fetch one (Bash tool, not PowerShell): %s") s='panel: Node 18+ gerekiyor ve bu makinede yok — Crewforth indirebilir (PowerShell değil, Bash aracıyla): %s' ;;
305
317
  "(it asks first, verifies the checksum, and touches nothing outside %s)") s='(önce sorar, sağlama toplamını doğrular ve %s dışında hiçbir şeye dokunmaz)' ;;
306
318
  "If Claude Code is running in this project, run /clear (or quit and relaunch it) — a new session loads the") s='Claude Code bu projede açıksa /clear çalıştırın (ya da kapatıp yeniden açın) — yeni oturum güncel' ;;
307
319
  "updated CLAUDE.md and discipline; a session opened before this run keeps the old rules until then.") s='CLAUDE.md ve disiplini yükler; bu çalıştırmadan önce açılmış oturum o zamana kadar eski kuralları uygular.' ;;
@@ -631,7 +643,7 @@ if { [ "$N_PAGENTS" != 0 ] || [ "$N_PSKILLS" != 0 ]; } && [ -f "$SRC/eval/scan-s
631
643
  if printf '%s' "$SCANOUT" | grep -qE 'DANGER|REVIEW'; then
632
644
  warnm 'supply-chain scan flagged existing project skills/agents (advisory — review before trusting them):'
633
645
  printf '%s\n' "$SCANOUT" | grep -E 'DANGER|REVIEW' | sed 's/^/ /'
634
- _mt 'full report after install: %s (heuristic; a security skill can score low by design)' 'bash .claude/eval/scan-skill.sh .claude'
646
+ _mt 'full report after install: %s (Bash tool, not PowerShell; heuristic — a security skill can score low by design)' 'bash .claude/eval/scan-skill.sh .claude'
635
647
  sub " $_M"
636
648
  else
637
649
  rowm 'supply-chain scan' 'existing project skills/agents look clean (no red flags)'
@@ -655,7 +667,7 @@ elif [ "$N_PAGENTS" != 0 ]; then
655
667
  else
656
668
  propm '1 Role clash' 'none' 'no custom agents found in the project'
657
669
  fi
658
- propm '2 Precedence' 'project wins (fixed)' "on conflict the project's rules always win; Crewforth fills gaps (not overridable)"
670
+ propm '2 Precedence' 'project wins (fixed)' "on conflict the project's rules win; in DISCIPLINE.md §4 they can only tighten, never loosen (the one exception: the §4.1 trace allowlist you choose at adoption); Crewforth fills gaps (not overridable)"
659
671
  if [ "$COAUTHOR" = 1 ]; then
660
672
  propm '3 Trace gate' 'loosen (.trace-allowlist)' 'co-author/sign-off present in git log — may be a convention'
661
673
  else
@@ -872,8 +884,8 @@ if [ "$KIT_PRESENT" = 1 ] && [ -d .claude/skills/devarch-module ]; then
872
884
  say '⚠️ both devarch-module and cqrs-aop-module are present — nothing moved; remove the old one when ready'
873
885
  fi
874
886
  fi
875
- [ "$LEGACY_DOTNET" = 1 ] && [ -d .claude/skills/cqrs-aop-module ] \
876
- && say "cqrs-aop-module is now a project skill (Crewforth no longer ships it); backend-expert applies it as your project's pattern."
887
+ # Whether the updater vouches for cqrs-aop-module is decided AFTER the manifest is written — see "THE PATTERN SKILL'S
888
+ # TRUST" below; the notice is printed there with the verdict.
877
889
  # THE 3.0 NAME MIGRATION: <x>-csk -> crew-<x>, for the KIT'S OWN components only (agents, commands, the code-review
878
890
  # skill), and only on a project the kit was installed on. Same principle as the devarch-module rename above: move,
879
891
  # never delete. The names come from the payload, so a project file that merely ends in -csk (my-helper-csk.md) is
@@ -893,7 +905,6 @@ if [ "$KIT_PRESENT" = 1 ]; then
893
905
  else mv "$old" "$new" 2>/dev/null && { LEGACY_MOVED=$((LEGACY_MOVED+1)); say '3.0 rename: %s → %s' "${old#.claude/}" "${new#.claude/}"; }
894
906
  fi
895
907
  done
896
- [ -n "$LEGACY_BOTH" ] && warnm '3.0 rename: both the old and the new name exist for:%s — nothing moved; keep one' "$LEGACY_BOTH"
897
908
  fi
898
909
  # THE 3.0 COMMANDS -> SKILLS MOVE. Claude Code merged custom commands into skills (`.claude/commands/` is "the
899
910
  # older format"), and a skill and a command of the same name are the same `/name` — the skill wins. So the kit's
@@ -928,8 +939,6 @@ for kf in $CMD_SKILLS; do
928
939
  fi
929
940
  done
930
941
  done
931
- [ -n "$CMD_BOTH" ] && warnm '3.0 commands are skills: a skill of that name already exists for:%s — nothing moved; keep one' "$CMD_BOTH"
932
- [ -n "$CMD_DUP" ] && warnm '3.0 commands are skills: an older copy of an already-moved command is left in place:%s — remove it' "$CMD_DUP"
933
942
  [ -n "$CMD_LINK" ] && warnm '3.0 commands are skills: symlinked command file(s) left as they are:%s — the skill of that name now answers /name' "$CMD_LINK"
934
943
  [ "$CMD_DIR_SHARED" = 1 ] && [ "$KIT_PRESENT" = 1 ] && warnm '3.0 commands are skills: .claude/commands is a symlink (shared?) — nothing was moved out of it; the skills of those names now answer /name'
935
944
  [ -n "$CMD_MINE" ] && warnm 'your own command(s) keep their name — the Crewforth skill of the same name was not installed:%s' "$CMD_MINE"
@@ -981,6 +990,130 @@ if [ "$KIT_PRESENT" = 1 ] && [ -f "$_AMS" ] && grep -qE '"CSK (Uncommitted Work
981
990
  fi
982
991
  rm -f "$_AMS.crew-new"
983
992
  fi
993
+ # THE LEGACY SWEEP. Components Crewforth shipped in 1.x-2.x and no longer ships (kit/legacy-blobs.tsv, generated from the
994
+ # release tags) stay on disk after an update, and for every kind the NAME IS THE INVOCATION: a leftover command lists
995
+ # in the / picker, a leftover skill competes for prompts, and the next session's trust gate asks about each skill as
996
+ # if the user had brought it in. One that is byte-for-byte a copy Crewforth shipped is MOVED ASIDE to
997
+ # .claude/.legacy-backup/<time>/ with a one-line restore; anything else — an edit, an extra file, a symlink — is the
998
+ # user's work and stays where it is, named. Nothing is deleted. The rule is "every file is a blob shipped for ITS path",
999
+ # not "all from one release": a field install carried vps-deploy with SKILL.md from 2.0-2.6 and references from 1.4.
1000
+ # cqrs-aop-module / devarch-module are excluded: 3.0 keeps the pattern skill as the project's own (its trust is below).
1001
+ # Runs after the rename and commands->skills moves, so what reaches it is what they left: a v1 -cck agent, a plain
1002
+ # v1 command, a -csk copy next to its crew- name. Works without an install manifest — the list is the kit's, not the
1003
+ # project's.
1004
+ #
1005
+ # _untouched_copies <component>...: prints each component (skills/<n>, agents/<n>.md, commands/<n>.md) that is an
1006
+ # untouched shipped copy. rc 2 = cannot tell (no git, no list). Same hardening as the
1007
+ # pattern skill's check, which uses it: a symlink or special file anywhere refuses the component (`find -type f` does
1008
+ # not list one, so a link would be "checked" by checking nothing); zero files is not a match; CR is stripped only when
1009
+ # every CR ends a line; paths reach awk as data, never through -v. Batched: one find, one git, one awk for all of
1010
+ # them — per-file hashing was 5 processes a file, which is seconds on Git Bash.
1011
+ _untouched_copies(){
1012
+ local lst="$SRC/legacy-blobs.tsv" NL=$'\n' TB=$'\t' CR=$'\r' c p bad fo f h hl cr crl rows="" i=0
1013
+ local live=() fl=() hs=()
1014
+ [ -f "$lst" ] && command -v git >/dev/null 2>&1 || return 2
1015
+ for c in "$@"; do [ -e ".claude/$c" ] || [ -L ".claude/$c" ] && live+=(".claude/$c"); done # a link is kept, for find to refuse
1016
+ [ "${#live[@]}" -gt 0 ] || return 0
1017
+ # A newline, tab or CR in a name would split or bend a line below (git's line reader drops a trailing CR), so it
1018
+ # disqualifies its component like a symlink does.
1019
+ bad="$(find "${live[@]}" \( ! -type f ! -type d \) -o -name "*$NL*" -o -name "*$TB*" -o -name "*$CR*" 2>/dev/null)"
1020
+ for p in "${live[@]}"; do
1021
+ case "$NL$bad$NL" in *"$NL$p$NL"*|*"$NL$p/"*) continue ;; esac
1022
+ # find's status is read: a subdirectory it cannot enter hides files, and those would travel with the component.
1023
+ fo="$(find "$p" -type f 2>/dev/null)" || continue
1024
+ while IFS= read -r f; do [ -n "$f" ] && fl+=("$f"); done <<< "$fo"
1025
+ done
1026
+ [ "${#fl[@]}" -gt 0 ] || return 0
1027
+ while IFS= read -r h; do hs+=("$h"); done < <(printf '%s\n' "${fl[@]}" | git hash-object --no-filters --stdin-paths 2>/dev/null)
1028
+ # One file git cannot read stops the batch. Then each file is hashed on its own, and one that fails is simply not a
1029
+ # match — it refuses its own component, not every component.
1030
+ if [ "${#hs[@]}" != "${#fl[@]}" ]; then
1031
+ hs=(); for f in "${fl[@]}"; do h="$(git hash-object --no-filters -- "$f" 2>/dev/null)"; hs+=("${h:--}"); done
1032
+ fi
1033
+ # Only a file that holds a CR pays for the CR-stripped hash (none of the shipped blobs has one).
1034
+ local crf; crf="$NL$(grep -l $'\r' -- "${fl[@]}" 2>/dev/null)$NL"
1035
+ while [ "$i" -lt "${#fl[@]}" ]; do
1036
+ f="${fl[$i]}"; h="${hs[$i]}"; hl="$h"
1037
+ case "$crf" in *"$NL$f$NL"*)
1038
+ cr="$(tr -dc '\r' < "$f" 2>/dev/null | wc -c | tr -d ' ')"; crl="$(grep -c $'\r$' "$f" 2>/dev/null)"
1039
+ [ "$cr" = "${crl:-x}" ] && hl="$(tr -d '\r' < "$f" 2>/dev/null | git hash-object --no-filters --stdin 2>/dev/null)" ;;
1040
+ esac
1041
+ rows="$rows$f$TB$h$TB${hl:-$h}$NL"; i=$((i+1))
1042
+ done
1043
+ printf '%s' "$rows" | awk -F'\t' 'FNR == NR { if ($0 !~ /^#/) ok[$2 SUBSEP $3] = 1; next }
1044
+ { c = substr($1, 9); split(c, a, "/"); if (a[1] == "skills") c = a[1] "/" a[2]
1045
+ n[c]++; if (!((($1 SUBSEP $2) in ok) || (($1 SUBSEP $3) in ok))) bad[c] = 1 }
1046
+ END { for (c in n) if (!(c in bad)) print c }' "$lst" - | LC_ALL=C sort
1047
+ }
1048
+ LEG_MOVED=""; LEG_KEPT=""; LEG_BK=""; _LNL=$'\n'
1049
+ if [ "$KIT_PRESENT" = 1 ] && [ -f "$SRC/legacy-blobs.tsv" ]; then
1050
+ _lon=""; _lsh=""
1051
+ for c in $(awk -F'\t' '!/^#/ && $1 != "skills/cqrs-aop-module" && $1 != "skills/devarch-module" && !s[$1]++ { print $1 }' "$SRC/legacy-blobs.tsv"); do
1052
+ [ -e ".claude/$c" ] || [ -L ".claude/$c" ] || continue
1053
+ # A symlinked .claude/ or .claude/<kind>/ is likely shared by several projects: moving a file OUT of it would take
1054
+ # it from every one of them (the same reason the commands->skills move leaves a linked commands/ alone).
1055
+ if [ -L .claude ] || [ -L ".claude/${c%%/*}" ]; then _lsh="$_lsh $c"; continue; fi
1056
+ # A 2.x command or skill beside a skills/crew-<x>/ of the user's that has no SKILL.md: the kit's skill is not
1057
+ # installed there, so this one is the only thing answering the name — it stays, and "keep one" below says why.
1058
+ case "$c" in commands/*-csk.md) _b="${c#commands/}"; _b="${_b%-csk.md}" ;; skills/*-csk) _b="${c#skills/}"; _b="${_b%-csk}" ;; *) _b="" ;; esac
1059
+ [ -n "$_b" ] && [ -d ".claude/skills/crew-$_b" ] && [ ! -f ".claude/skills/crew-$_b/SKILL.md" ] && continue
1060
+ _lon="$_lon $c"
1061
+ done
1062
+ [ -n "$_lsh" ] && warnm 'older Crewforth components in a symlinked (shared?) directory are left in place — moving them out would remove them from every project that uses it:%s' "$_lsh"
1063
+ if [ -n "$_lon" ]; then
1064
+ _lun="$(_untouched_copies $_lon)"; _lrc=$?
1065
+ if [ "$_lrc" = 2 ]; then
1066
+ LEG_KEPT="$_lon"
1067
+ warnm 'older Crewforth components could not be checked against what Crewforth shipped (git or the shipped list is missing) — left in place:%s' "$_lon"
1068
+ else
1069
+ LEG_BK=".claude/.legacy-backup/$(date +%Y%m%d-%H%M%S)"; _b="$LEG_BK"; i=2
1070
+ while [ -e "$_b" ]; do _b="$LEG_BK-$i"; i=$((i+1)); done; LEG_BK="$_b"
1071
+ for c in $_lon; do
1072
+ case "$_LNL$_lun$_LNL" in *"$_LNL$c$_LNL"*)
1073
+ if mkdir -p "$LEG_BK/${c%/*}" 2>/dev/null && mv ".claude/$c" "$LEG_BK/$c" 2>/dev/null; then
1074
+ LEG_MOVED="$LEG_MOVED $c"
1075
+ # A v1 -cck agent was counted as the project's own (KIT_OLD knows the 2.x names); moved aside, it is neither.
1076
+ case "$c" in agents/*) _la="${c#agents/}"; case "$KIT_OLD" in *" ${_la%.md} "*) ;; *) N_PAGENTS=$((N_PAGENTS-1)) ;; esac ;; esac
1077
+ continue
1078
+ fi ;;
1079
+ esac
1080
+ LEG_KEPT="$LEG_KEPT $c"
1081
+ done
1082
+ if [ -n "$LEG_MOVED" ]; then
1083
+ # A team that shares .claude/ should not commit the backup by accident; the restore reads the files, not git.
1084
+ [ -f .claude/.legacy-backup/.gitignore ] || printf '*\n' > .claude/.legacy-backup/.gitignore 2>/dev/null
1085
+ say 'moved aside — each one exactly as an older Crewforth shipped it, and no longer shipped:%s' "$LEG_MOVED"
1086
+ say 'to put them back: cp -R %s/. .claude/' "$LEG_BK"
1087
+ say '(an unchanged copy put back is moved aside again by the next update — to keep one, change it)'
1088
+ else rmdir "$LEG_BK" .claude/.legacy-backup 2>/dev/null || true
1089
+ fi
1090
+ if [ -n "$LEG_KEPT" ]; then
1091
+ warnm 'left in place — installed by an older Crewforth, no longer shipped, and changed since (yours now?):%s' "$LEG_KEPT"
1092
+ _mt 'Each still answers its name (a command lists in the / picker, a skill matches prompts). To drop them:'; printf ' %s\n' "$_M"
1093
+ printf ' rm -r'; for e in $LEG_KEPT; do printf ' .claude/%s' "$e"; done; echo
1094
+ fi
1095
+ fi
1096
+ fi
1097
+ fi
1098
+ [ -n "$LEG_MOVED" ] && [ "$CMD_DIR_SHARED" = 0 ] && { rmdir .claude/commands 2>/dev/null || true; } # emptied by the sweep
1099
+ # The "keep one" notices wait for the sweep: a -csk copy it moved aside is no longer one of two. When it moved one,
1100
+ # the line names BOTH files, so the user reads from one line what went, why, and that their own file was not touched.
1101
+ _lb=""; for o in $LEGACY_BOTH; do
1102
+ if [ -e "$o" ]; then _lb="$_lb $o"; continue; fi
1103
+ _lo="${o#.claude/}"; _lx=""; case "$_lo" in *.md) _lx=.md ;; esac; _ln="${_lo%/*}/crew-$(_b="${_lo##*/}"; _b="${_b%.md}"; printf '%s' "${_b%-csk}")$_lx"
1104
+ say '%s (an unchanged copy Crewforth shipped) moved aside; your %s was not touched' "$_lo" "$_ln"
1105
+ done
1106
+ [ -n "$_lb" ] && warnm '3.0 rename: both the old and the new name exist for:%s — nothing moved; keep one' "$_lb"
1107
+ _lb=""; for o in $CMD_BOTH; do
1108
+ if [ -e ".claude/$o" ]; then _lb="$_lb $o"; continue; fi
1109
+ _b="${o##*/}"; _b="${_b%.md}"; say '%s (an unchanged copy Crewforth shipped) moved aside; your %s was not touched' "$o" "skills/crew-${_b%-csk}/"
1110
+ done
1111
+ [ -n "$_lb" ] && warnm '3.0 commands are skills: a skill of that name already exists for:%s — nothing moved; keep one' "$_lb"
1112
+ _lb=""; for o in $CMD_DUP; do
1113
+ if [ -e ".claude/$o" ]; then _lb="$_lb $o"; continue; fi
1114
+ _b="${o##*/}"; _b="${_b%.md}"; _b="${_b#crew-}"; say '%s (a second, unchanged copy Crewforth shipped) moved aside; %s already answers that command' "$o" "skills/crew-${_b%-csk}/"
1115
+ done
1116
+ [ -n "$_lb" ] && warnm '3.0 commands are skills: an older copy of an already-moved command is left in place:%s — remove it' "$_lb"
984
1117
  # #1 keepmine: your overlapping agents own those roles, so the kit's matching crew- agents are NOT installed.
985
1118
  [ "$COLLIDE_MODE" = keepmine ] && for b in $COLLIDE; do EXCL_A="$EXCL_A crew-$b.md"; done
986
1119
  # kit-owned trees: FORCE-refresh on a re-adopt (KIT_PRESENT) so kit updates land; never-overwrite on a fresh adopt
@@ -1011,11 +1144,6 @@ done
1011
1144
  # a plain -x match read it as disarmed and the refresh switched §4.2 off without a word (measured in review).
1012
1145
  VENDOR_ARMED=0; grep -qxE $'DevArchitecture\r?' .claude/hooks/trace-blocklist.txt 2>/dev/null && VENDOR_ARMED=1
1013
1146
  copy_noclobber "$SRC/hooks" .claude/hooks "$KIT_PRESENT"; H_ADD=$ret_add; H_SKIP=$ret_skip
1014
- # The 3.0 migration made cqrs-aop-module a project skill; it is the one Crewforth shipped until now, so the updater
1015
- # vouches for it rather than having the next session open by asking whether to trust it. Only that component, and
1016
- # only after the refreshed hook (which knows --trust-one) is in place.
1017
- [ "$LEGACY_DOTNET" = 1 ] && [ -f .claude/skills/cqrs-aop-module/SKILL.md ] \
1018
- && bash .claude/hooks/skill-trust.sh --trust-one skills/cqrs-aop-module >/dev/null 2>&1 || true
1019
1147
  copy_noclobber "$SRC/eval" .claude/eval "$KIT_PRESENT"; E_ADD=$ret_add; E_SKIP=$ret_skip
1020
1148
  # The Studio panel. This is the line that answers "I updated and `/crew-studio` says the panel
1021
1149
  # is missing": with KIT_PRESENT=1 it is a force-refresh, so a project that already has the kit
@@ -1199,6 +1327,7 @@ if [ -f .claude/kit-manifest.txt ]; then
1199
1327
  [ -e "$SRC/$entry" ] && continue
1200
1328
  # The 3.0 migration keeps the former .NET pattern skill as the project's own; this sweep's advice is `rm -r`.
1201
1329
  [ "$entry" = skills/cqrs-aop-module ] && continue
1330
+ case " $LEG_KEPT " in *" $entry "*) continue ;; esac # the legacy sweep above has already named it
1202
1331
  [ -e ".claude/$entry" ] && STALE="$STALE $entry"
1203
1332
  done < .claude/kit-manifest.txt
1204
1333
  if [ -n "$STALE" ]; then
@@ -1219,6 +1348,44 @@ fi
1219
1348
  for f in "$SRC"/agents/*.md; do [ -e "$f" ] && echo "agents/$(basename "$f")"; done
1220
1349
  } > .claude/kit-manifest.txt 2>/dev/null || true
1221
1350
 
1351
+ # THE PATTERN SKILL'S TRUST. 3.0 keeps cqrs-aop-module as the project's own; the updater vouches for it only when it is
1352
+ # PROVABLY the copy Crewforth shipped — every file in it hashes (raw, or with CR removed) to a blob id listed for its
1353
+ # path in legacy-blobs.tsv (cqrs-aop-module 2.12-2.13, and devarch-module 1.0.0-2.11.0 renamed in place), and nothing
1354
+ # else is in the directory. An edited copy is the user's work, and a skill is executable instruction: it is named,
1355
+ # not vouched for, and the next session asks. The old test was the recorded stack (`stack=dotnet`), which measured
1356
+ # the wrong thing: a generic-recorded install carrying the skill got nothing and its first session flagged it
1357
+ # (field report, reproduced in e2e). Asked only while the skill is still unvetted — the trust gate's own answer,
1358
+ # read after the manifest exists — so an update does not repeat itself once it is settled.
1359
+ # Returns 0 shipped · 1 not a shipped copy · 2 cannot tell (no git, no list). The check is the legacy sweep's
1360
+ # _untouched_copies (hardened in review: a symlink anywhere is refused — a link to arbitrary text used to be "checked"
1361
+ # by checking nothing, and --trust-one then recorded the target's digest; zero files is not a match; CR is stripped
1362
+ # only for a CRLF copy; paths never reach awk through -v, which would turn SKIL\114.md into SKILL.md).
1363
+ _cqrs_shipped(){
1364
+ local u rc
1365
+ u="$(_untouched_copies skills/cqrs-aop-module)"; rc=$?
1366
+ [ "$rc" = 2 ] && return 2
1367
+ [ "$u" = skills/cqrs-aop-module ] && [ -f .claude/skills/cqrs-aop-module/SKILL.md ] && [ ! -L .claude/skills/cqrs-aop-module/SKILL.md ]
1368
+ }
1369
+ # The gate's answer is read from its output, never through `| grep -q`: grep closing early SIGPIPEs the hook and
1370
+ # pipefail turns the whole test false — that is how the first version of this block never ran (e2e, C1).
1371
+ _ptrust=""; [ "$KIT_PRESENT" = 1 ] && [ -f .claude/skills/cqrs-aop-module/SKILL.md ] \
1372
+ && _ptrust="$(CLAUDE_PROJECT_DIR="$PWD" bash .claude/hooks/skill-trust.sh </dev/null 2>/dev/null || true)"
1373
+ if grep -qE '(^|[[:space:]])skills/cqrs-aop-module([[:space:]]|$)' <<< "$_ptrust"; then # exact name: not skills/cqrs-aop-module-x
1374
+ _cqrs_shipped; _cqs=$?
1375
+ if [ "$_cqs" = 2 ]; then
1376
+ say "cqrs-aop-module could not be checked against the copies Crewforth shipped (git or the shipped list is missing) — not vouched for; the next session will ask whether to trust it."
1377
+ elif [ "$_cqs" = 0 ]; then
1378
+ _tout="$(bash .claude/hooks/skill-trust.sh --trust-one skills/cqrs-aop-module 2>&1)"; _trc=$?
1379
+ if [ "$_trc" = 0 ]; then
1380
+ say "cqrs-aop-module is now a project skill (Crewforth no longer ships it); backend-expert applies it as your project's pattern."
1381
+ else
1382
+ say '⚠️ cqrs-aop-module could not be recorded as trusted (skill-trust.sh exit %s: %s) — the next session will ask about it' "$_trc" "${_tout:-no output}"
1383
+ fi
1384
+ else
1385
+ say "cqrs-aop-module is 2.x's .NET pattern, changed since Crewforth shipped it — not vouched for; the next session will ask whether to trust it."
1386
+ fi
1387
+ fi
1388
+
1222
1389
  # stack= is always generic since 3.0; the key is kept for older updaters. Rewritten WITHOUT the pre-2.0
1223
1390
  # 'profile=' key: dropping it is what retires the migration notice, so a second refresh stays quiet — and
1224
1391
  # writing generic over a former 'dotnet' retires the 3.0 notice the same way.
@@ -1297,7 +1464,7 @@ if [ -f CLAUDE.md ]; then
1297
1464
  _mt 'the inline block is lines 1-%s; your project section starts at line %s' "$((BND-1))" "$BND"
1298
1465
  printf ' %s%s%s\n' "$D" "$_M" "$R"
1299
1466
  if ask_yes ' Replace that inline block with the single @import line? (a backup is written; this branch is reviewable)'; then
1300
- BK=".claude/CLAUDE.md.pre-kit-$TS"
1467
+ BK=".claude/CLAUDE.md.pre-kit-${TS:-$(date +%Y%m%d-%H%M%S)}" # TS is set only when a handover branch was opened
1301
1468
  cp CLAUDE.md "$BK"
1302
1469
  { printf '<!-- Crewforth discipline · on conflict the project rules BELOW win -->\n%s\n\n' "$IMPORT_LINE"
1303
1470
  tail -n +"$BND" CLAUDE.md; } > CLAUDE.md.kit-tmp && mv CLAUDE.md.kit-tmp CLAUDE.md
@@ -1582,7 +1749,7 @@ cat > docs/HANDOVER.md <<HAND
1582
1749
  | # | Decision | Value |
1583
1750
  |---|---|---|
1584
1751
  | 1 | Role clash | $D1 |
1585
- | 2 | Precedence | $D2 (axis-by-axis) |
1752
+ | 2 | Precedence | $D2 (axis-by-axis; DISCIPLINE.md §4 tighten-only, except the §4.1 trace allowlist of decision 3) |
1586
1753
  | 3 | Trace gate | $D3 |
1587
1754
  | 4 | Share/hide | $D4 |
1588
1755
  | 5 | Git hook | $D5 |
@@ -1623,7 +1790,7 @@ Goal: don't break the project, don't lose decisions already made, and don't leav
1623
1790
  ## Decision
1624
1791
  - Crewforth's agents were installed under the crew- namespace; the project's agents are preserved side by side, untouched.
1625
1792
  - Crewforth's discipline is active via .claude/DISCIPLINE.md + @import; the project CLAUDE.md is untouched.
1626
- - On rule conflicts the PROJECT wins (axis-by-axis).
1793
+ - On rule conflicts the PROJECT wins (axis-by-axis); in DISCIPLINE.md §4 it can only tighten, never loosen — the one exception is the §4.1 trace allowlist chosen at adoption (decision 3).
1627
1794
  - Git gates: $HOOKDESC.
1628
1795
  - Every change is on a reviewable git branch; rollback = git.
1629
1796
 
@@ -1713,7 +1880,7 @@ subm 'see it: open the Source Control / Changes panel (every added + changed f
1713
1880
  if bash "$SRC/eval/preflight.sh" --has node 2>/dev/null; then
1714
1881
  subm 'panel: %s opens it from this project (or: %s)' /crew-studio 'node .claude/studio/server/index.js --open'
1715
1882
  else
1716
- subm 'panel: needs Node 18+, absent here — Crewforth can fetch one: %s' 'bash .claude/studio/ensure-node.sh --plan'
1883
+ subm 'panel: needs Node 18+, absent here — Crewforth can fetch one (Bash tool, not PowerShell): %s' 'bash .claude/studio/ensure-node.sh --plan'
1717
1884
  _mt '(it asks first, verifies the checksum, and touches nothing outside %s)' '~/.claude/studio-runtime'; sub " $_M"
1718
1885
  fi
1719
1886
  sub "$ACCEPT_LINE"
package/kit/CLAUDE.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  The discipline, identical in every project. The installer writes it to `.claude/DISCIPLINE.md`; your `./CLAUDE.md`
4
4
  imports it with one `@.claude/DISCIPLINE.md` line. **Crewforth-owned**: updates overwrite it, so put your own rules in
5
- `./CLAUDE.md`, where they win on conflict.
5
+ `./CLAUDE.md`, where they win on conflict; in §4 they can only tighten, never loosen (§4.1 names the one exception).
6
6
 
7
7
  ## Four working principles
8
8
  1. Think, then write. State assumptions explicitly; if unsure, **STOP and ask**.
@@ -13,6 +13,7 @@ imports it with one `@.claude/DISCIPLINE.md` line. **Crewforth-owned**: updates
13
13
  **When two rules collide** (they do, and improvising an order is how the wrong one wins): §4 prohibitions and
14
14
  safety first, then the user's explicit in-session instruction, then scope-as-asked, then quality, then speed. A
15
15
  lower one never overrides a higher one. Say which you applied and why — a silent trade-off is an unreviewable one.
16
+ A project `CLAUDE.md` can tighten §4, never loosen it (§4.1 names the one exception). The § numbers in this file refer only to this file.
16
17
  **A skill's output format is not on this ladder**: it shapes what you write, not whether the work continues.
17
18
  "Final reply = the report" ends that skill's step, not the task.
18
19
 
@@ -118,7 +119,7 @@ Last line of a reply, after the next step: `🔋 Session: [low/medium/high fill]
118
119
 
119
120
  **Never guess the fill.** You cannot run `/context`; the `UserPromptSubmit` hook injects the measured line
120
121
  `🔋 Session %NN.N → level` every turn (`input + cache_read + cache_creation` = the `/context` figure). Use it. Exact
121
- reading: `bash .claude/hooks/context-usage.sh --verbose`. Never invent a number. **No line → run that command
122
+ reading: `bash .claude/hooks/context-usage.sh --verbose` (Bash tool, not PowerShell). Never invent a number. **No line → run that command
122
123
  once; if it also fails, say so ONCE and drop the 🔋 line for the rest of the session.** Repeating "could not
123
124
  measure" every turn is noise that reads as a fault.
124
125
 
@@ -203,7 +204,7 @@ Runtime: <e.g. Node 22 · Go 1.23 · Python 3.12 · .NET 10 · Java 21>
203
204
  Web framework: <e.g. Fastify · chi · FastAPI · ASP.NET Core · Spring Boot>
204
205
  Database: <e.g. PostgreSQL 17 (+ Redis cache) · SQLite · MongoDB> · Migrations: <tool>
205
206
  Architecture pattern: <layered · clean/hexagonal · vertical slice · CQRS>
206
- Client: <e.g. web React/Next · mobile React Native/Expo · desktop — depending on the project>
207
+ Client: <e.g. web · native iOS/Android · Flutter · React Native · .NET MAUI · KMP · desktop — the user chooses; empty = ask, recommend none>
207
208
 
208
209
  ## Project skills
209
210
  Domain-specific "how"s live under `.claude/skills/` (e.g. payment-contract, notification-rules).
@@ -225,4 +226,4 @@ read this section and follow it verbatim; with nothing declared they fall back t
225
226
  Behavior · four principles · DoD · Prohibitions (§4) · session management · sources live in
226
227
  `.claude/DISCIPLINE.md`, pulled in by the `@.claude/DISCIPLINE.md` line at the top of this file. That file is
227
228
  Crewforth-owned: an update overwrites it, so put **nothing** of your own there — this file is where your rules go, and
228
- on conflict the rules here win. No dependency on Home (`~/.claude`) — everything stays inside the repo (handover §3).
229
+ on conflict the rules here win; in DISCIPLINE.md §4 they can only tighten, never loosen (its §4.1 names the one exception). No dependency on Home (`~/.claude`) — everything stays inside the repo (handover §3).
package/kit/README.md CHANGED
@@ -37,7 +37,7 @@ summarizes what lives under `.claude/` and how it works.
37
37
  - **settings.json** — permissions and the hook chain (PreToolUse · UserPromptSubmit · Stop · SessionStart).
38
38
  - **`DISCIPLINE.md`** — behavior, four principles, workflow, definition of done, token discipline, and prohibitions.
39
39
  Crewforth-owned: an update **overwrites** it, so keep nothing of your own here. Your `./CLAUDE.md` pulls it in with a
40
- single `@.claude/DISCIPLINE.md` line and holds your project rules, which win on conflict.
40
+ single `@.claude/DISCIPLINE.md` line and holds your project rules, which win on conflict; in DISCIPLINE.md §4 they can only tighten, never loosen (its §4.1 names the one exception).
41
41
  - **`kit.conf`** — which installer ran and at what version. `stack=` is always `generic` since 3.0 (kept for older
42
42
  updaters); the project's stack itself lives in the `## Stack` section of `./CLAUDE.md`.
43
43
  - **`kit-manifest.txt`** — the component names Crewforth ships, one per line. It is what separates Crewforth-owned from
@@ -2,8 +2,8 @@
2
2
  name: crew-frontend-expert
3
3
  color: purple
4
4
  description: |
5
- Stack-agnostic frontend expert — web (React/Next/Vue/Svelte/Angular), mobile (React Native/Flutter), desktop.
6
- The "how" lives in the `frontend` skill (mobile: `frontend-rn-expo`). **Use proactively — owns everything the user sees or
5
+ Stack-agnostic frontend expert — web, mobile (native, Flutter, React Native, MAUI, KMP), desktop.
6
+ The "how" lives in the `frontend` skill (an RN/Expo project adds `frontend-rn-expo`). **Use proactively — owns everything the user sees or
7
7
  interacts with:** screens, components, navigation, client state, i18n, accessibility, responsive, native
8
8
  bridges, and all visual work — design systems, token/theme layers, typography, dark mode, "it doesn't look
9
9
  premium". Any request about it is yours whatever its size, wording or language.
@@ -16,11 +16,11 @@ metadata:
16
16
  # Frontend Expert (stack-agnostic)
17
17
 
18
18
  <!-- routing-eval reads the next line; why it sits in the body: AGENT_TEMPLATE.md -->
19
- Trigger phrases: "screen size", "component", "page", "navigation", "client routing", "react router", "UI polish", "responsive", "i18n interface", "state management", "visual design", "design system", "design token", "dark mode", "look premium"
19
+ Trigger phrases: "screen size", "component", "page", "navigation", "client routing", "react router", "UI polish", "responsive", "i18n interface", "state management", "visual design", "design system", "design token", "dark mode", "look premium", "flutter", "swiftui", "jetpack compose", "maui", "kotlin multiplatform", "mobile screen"
20
20
 
21
- The role is general; the "how" varies per project. First detect the project's frontend stack
22
- (package.json / repo structure / CLAUDE.md), then follow that project's conventions —
23
- do not impose your own preferences.
21
+ The role is general; the "how" varies per project. Resolve the client stack first, in the order the `frontend`
22
+ skill gives (Client stack: the request → `## Stack` `Client:` → the repo's manifests → ask once and record it). No
23
+ stack is a default and none is suggested; then follow that project's conventions, not your own preferences.
24
24
 
25
25
  ## Expertise stance (senior product engineer)
26
26
  - **Design states up front**: loading / empty / error / offline — not just "populated".
@@ -38,7 +38,7 @@ Appends a single line to the very END of the response:
38
38
 
39
39
  **Actual fill is MEASURED, guessing is FORBIDDEN.** The `UserPromptSubmit` hook runs `context-usage.sh` each turn,
40
40
  automatically injecting the real `🔋 Session: %.. (token) → level` line into the context — use that value. If you want an
41
- exact/fresh reading, run it by hand: `bash .claude/hooks/context-usage.sh`
41
+ exact/fresh reading, run it by hand with the Bash tool, not PowerShell: `bash .claude/hooks/context-usage.sh`
42
42
  (the `input + cache_read + cache_creation` of the last main-context turn in the transcript = the `/context` count).
43
43
  If there's no injected line (hook off / transcript unreachable) **don't invent a %** — run `bash .claude/hooks/context-usage.sh --verbose` once; if that also fails, say so once, drop the 🔋 line for the rest of the session, and only report a topic change.
44
44