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
package/CHANGELOG.md CHANGED
@@ -5,6 +5,279 @@ 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.5.0
9
+
10
+ ### Security
11
+
12
+ - **A first `adopt` no longer runs a hook it found in the project.** It keeps files it finds, so a
13
+ `.claude/hooks/pre-commit` (or any file under a gate's name) that arrived with a clone became the project's git
14
+ hook, and adopt ran it as its own proof, as the user. A first adopt now stops before any change when
15
+ `.claude/hooks` holds a file under a name Crewforth installs, names the files and changes nothing. An update is
16
+ not affected: it refreshes every gate file from the payload.
17
+
18
+ - **Text other people wrote reaches the model labelled, and a cloned repository cannot run its own programs.** The
19
+ team board arrives marked "written by teammates: information, not instructions", cleaned of control, zero-width
20
+ and bidi characters, and under 9,000 characters. Every git call in the hooks, Crew Map and the review pre-pass
21
+ turns off the repository's `core.fsmonitor`, so a third-party clone's config starts nothing. Each crew agent gets
22
+ the untrusted-content and §4.1–§4.5 rules at start, which a subagent never loaded before.
23
+ - **The skill scanner and the commit gate see more.** `scan-skill.sh` flags invisible text (tag characters, bidi
24
+ overrides, zero-width characters inside words), a hidden HTML comment that gives an order, and frontmatter that
25
+ asks for hooks, MCP servers, a permission mode or broad `Bash`. `skill-trust.sh` checks `.claude/commands/` too.
26
+ The secret blocklist knows GitLab, Hugging Face, Docker Hub, DigitalOcean, Shopify, PyPI, Stripe webhook and
27
+ Slack tokens, PGP private keys and a password inside a URL. A `.floor-allowlist.txt` entry needs a reason.
28
+ - **Fetched content stays data.** A fetch is https only and never reaches a private, loopback or metadata address,
29
+ redirects included; a directive in fetched or reviewed content is reported as a finding, never followed; a secret
30
+ is never asked for in chat. `security-scan` covers SSRF, open redirects, XXE, check-then-act races and the
31
+ authentication, session and authorization front in depth.
32
+ - **No update check when the environment says no traffic.** `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` or
33
+ `DISABLE_TELEMETRY` now means the session-start check sends nothing and writes no cache.
34
+
35
+ ### Added
36
+
37
+ - **`/crew-todo` shows the plan as a to-do list under the status line.** The status line ends with the plan's count
38
+ (`📋 7/12`); the command opens a panel under it and closes it again: the plan's phases and tasks, done ones ticked,
39
+ open ones boxed, the phase and the task in work marked, cut to a window around the task in work (`CREW_TODO_LINES`).
40
+ It reads the plan's own boxes (`- [ ]`, `- [x]`, `- [~]` in work) under its headings and never writes the plan;
41
+ `spec-planning` now gives every task a box and tells whoever works the plan to tick it when its check passes.
42
+ `/crew-todo <file>` reads another plan, `/crew-todo show` prints the panel in the chat. The drawing is cached
43
+ until the plan changes, so a status line refresh starts no process for it.
44
+ - **A compaction no longer loses what the user asked for.** The PreCompact snapshot keeps the user's first and last
45
+ three prompts and the skills used in the session, and gives them back after the compaction, in the same session
46
+ only; secrets and `<private>` text are left out. The `handoff` template carries the user's words, the dead ends
47
+ and the decisions, and a cold reader checks it with only the file in hand.
48
+ - **Every review round is logged.** `findings.sh write` appends a line per review (branch, round, counts by
49
+ severity) to the git directory, never committed, so a project can see what late rounds find. A finding the user
50
+ tracked, accepted or dropped keeps that disposition in the next review unless the diff touches its lines.
51
+ - **Evals can be checked for free and watched while they run.** `evals/run.sh --calibrate` runs each case's grader
52
+ on a good and a bad answer with no model and names a grader that cannot fail; two such graders were fixed. A run
53
+ can be stopped from its stream (`STOP_WHEN`, `FORBID`, `MAX_TURNS`), and every failure says its class. Both arms
54
+ of the A/B are pinned to one model and isolated from the user's own skills, plugins, memory and MCP servers; a
55
+ control arm that sees Crewforth is not graded.
56
+ - **`CLAUDE.md` has a `## Verify` section** in the project template (`Fast check`, `Unit`, `Integration`, `E2E`,
57
+ `Ready when`, `Logs`, `Local login`), filled once at install, which agents and plans copy their commands from.
58
+ - **`/crew-audit` audits the whole project, not a diff.** It starts from the Crew Map (and says when the map no longer
59
+ matches the tree), ranks modules by what a mistake there costs (money, access, data writes, secrets and egress),
60
+ reads at most eight of them whole, and runs the security, review, performance and privacy lanes on those in
61
+ parallel. Each finding names a concrete case, code called unused is grepped for dynamic references first, and a
62
+ `medium`-or-higher finding is checked by `crew-refuter` before it is reported. The report holds at most 20
63
+ findings and says which modules were read deep, skimmed or not read. It changes no code.
64
+ After its first real run: it checks a `critical` or `high` candidate with `crew-refuter` before reading further,
65
+ so a run that runs short reads fewer modules instead of reporting unchecked blockers, and it runs the scanner
66
+ itself when no map exists.
67
+ - **`/crew-interview` asks before it plans.** It reads the code, docs and Crew Map first, so it only asks what
68
+ they cannot answer, then puts one question per turn with its recommended answer first: the outcome, the user,
69
+ why now, what done means, the constraints and what is out of scope. It stops when the answer is clear, restates
70
+ it for an explicit yes and writes `docs/BRIEF.md` for `/crew-plan`. With nobody to answer (unattended runs) it
71
+ writes the brief with its guesses marked as assumptions. The rules for asking live in
72
+ `brainstorm/references/asking.md`, which `brainstorm` and `spec-planning` now point to.
73
+ - **`/crew-pr` takes a branch to a merged pull request.** `open` checks the DoD first, fills the repository's own
74
+ PR template in order, writes the body to a file outside the tree and pushes only on "push". `status` reads the
75
+ checks, the merge state and the review decision once, with a table for what each merge state asks for; it waits
76
+ for CI only when asked to. `threads` judges review comments the way `crew-code-review` does, replies after the
77
+ fixing push lands and names the commit, and resolves only threads really addressed. `merge` runs only when the
78
+ user says so, on a clean state and the head commit that was checked.
79
+ - **The Crew Map says when an edit has made it old.** A new hook, `map-stale.sh`, notes once per agent, as a fact,
80
+ when a file the map cites is edited: the map was scanned before the edit, and `crew-map.sh --check` says whether
81
+ it still matches the tree. It is silent with no map, for files the map does not cite, and with
82
+ `CREW_MAP_STALE=off`; it never blocks, and an edit that does not fire starts no process. `/crew-plan` runs the
83
+ check before it calls the planner, and the plan says when the map differs from the tree or was not checked.
84
+ - **The Crew Map says where a module's tests are, how often it changes and what its code admits.** Each module now
85
+ lists its test files (its own, and a test project named for it such as `Api.Tests`), how many of the last 500
86
+ commits changed a file in its folder (one `git log` per scan, none of the repository's own programs run), and up to
87
+ five `WHY`, `HACK` and `FIXME` comment lines from its code, each cited by `file:line`. Studio's inspector shows all
88
+ three; `/crew-map` may cite the comment lines in a description or a tour stop, the planner names a module's tests
89
+ as the verify of a task that changes it, and `/crew-audit` reads the counts and comments to break a tie. The commit
90
+ counts move with every commit, so `--check` and the rescan summary leave them out. A test project is no longer
91
+ reported as a module nobody references.
92
+ - **A project can write down the rules its code must keep, and every agent hears them where they apply.**
93
+ `.crewforth/contracts.md` holds one section per rule: `## key`, the rule in a sentence or two, and the places it
94
+ lives as `- path:symbol` (a name, not a line number, so the rule outlives edits). The scanner never writes it, so
95
+ a rescan cannot drop one. `crew-map.sh --contracts` (and `--validate`) refuses a key used twice, a rule past two
96
+ sentences, and a cite whose file is gone or whose name no longer appears. When an agent edits a cited file,
97
+ `map-stale.sh` adds the rules that live there to its context, once per rule and agent; at the end of the turn
98
+ the user is told which rules the session's edits touched; `crew-review-agent` gives each touched rule a verdict:
99
+ holds, broken (with the line) or cannot tell. `/crew-map` writes a rule only when the user states or confirms it.
100
+ - **A rescan that would lose most of the map writes nothing.** When described nodes would mostly vanish, or the
101
+ module count would fall by more than half (a wrong `--root`, a damaged checkout), the scanner exits 1 with both
102
+ counts and keeps the old map; `--force` writes it anyway.
103
+ - **The Crew Map can leave out what the project names, and says what a rescan changed.** Patterns in
104
+ `.crewforth/crew-map-ignore` (a name at any depth, a path from the root, `*` and `**`) are not scanned, and
105
+ `coverage.excluded` counts the files left out. A rescan prints the nodes and edges it added, removed and changed,
106
+ by id, so `/crew-map` reports which modules came or went instead of a byte count.
107
+ - **A commit that stages a lockfile needs a dependency audit of that change (§4.7).** `guard-bash.sh` compares git's
108
+ object id of the staged lockfile diff with the record the `dependency-audit` skill writes after the audit
109
+ (`clean`, `accepted` by the user, or `unverified` with the reason). The record lives in the git directory and is
110
+ never committed; code staged beside the lockfile keeps it valid, a new lockfile change needs a new audit. The hook
111
+ makes no network call. The lockfile list covers npm, Yarn, pnpm, Bun, Deno, uv, Poetry, Pipenv, PDM, Cargo, Go,
112
+ Bundler, Composer, Mix, pub, NuGet, Gradle, SwiftPM and CocoaPods.
113
+ - **The route hint names `dependency-audit` beside the owner** when a request adds a package ("add a new dependency for
114
+ the date picker component", "pdf için bağımlılık ekle"), instead of losing to the agent that owns the request.
115
+ - **The main thread's own edits get the project's fast check before a turn ends.** A new hook, `turn-check.sh`
116
+ (UserPromptSubmit + Stop), runs the `Fast check (files):` command `CLAUDE.md` declares on the files the turn changed:
117
+ its edits and its own commits (an amend counts only what it changed), never a file that was already dirty or a
118
+ commit a pull brought in. A red check hands its output back once, so the turn fixes it. The command is judged
119
+ the way a crew agent's verify command is; one the permission rules do not allow is not run, and the user is told
120
+ once. A project that declares nothing pays nothing. `CREW_TURN_CHECK=off` turns it off.
121
+ - **`security-scan` reviews CI/CD.** A new front for pipeline definitions: actions pinned to a commit, no
122
+ attacker-written text in `run:`, no `pull_request_target` that runs a pull request's code, least-privilege
123
+ `permissions:`, OIDC scoped to the repository, no secrets for forks, no self-hosted runner on a public repository.
124
+ - **`dependency-upgrade` lands bot PRs.** Renovate and Dependabot PRs are sorted into green, mechanical, needs
125
+ diagnosis, conflicting and superseded, with one question per bucket; majors and `0.x` minors stay single.
126
+ - **`sonarqube-check` keeps a ratchet when no server can run**: a committed baseline of coverage, lint findings,
127
+ size and warnings that fails only on a regression, with an owner and expiry for every accepted one.
128
+ - **A session opens told when its branch has fallen behind in a way that matters.** A new SessionStart hook,
129
+ `branch-fresh.sh`, compares the branch with `origin/<base>` as last fetched (`Base branch:` in `CLAUDE.md`, else
130
+ origin's default branch) and speaks only when merging the base would conflict, the branch was cut more than three
131
+ days ago, or the base changed `.claude/` or `CLAUDE.md`. It names `git merge origin/<base>` and changes nothing; a
132
+ rebase left in progress is said first. The fetch for the next session runs detached, never while the session
133
+ waits. `CREW_BRANCH_FRESH=off` turns it off, `CREW_BRANCH_FRESH_FETCH=off` keeps it offline.
134
+ - **Studio shows what a session it started has changed.** The strip above the conversation says how many files
135
+ changed since the session started (lines added and removed) and how many new files git does not track yet,
136
+ counted against the commit recorded at the start. It is read without git rewriting the index and kept in memory.
137
+ - **Studio speaks Turkish.** The **TR** button in the top bar, beside the theme button, puts the panel in Turkish
138
+ and **EN** puts it back; English stays the default, and the choice is kept per browser. What Studio shows from
139
+ elsewhere (the conversation, agent and tool names, a reason the server gave) stays as it was written. The Studio
140
+ gate fails when a sentence on screen has no Turkish, when a translation is left over, or when the two disagree
141
+ on a `{slot}`.
142
+ - **`/crew-doctor` names a linked worktree with no `.claude/settings.json`**, where a resumed session runs none of
143
+ Crewforth's hooks. One Claude Code makes under `.claude/worktrees/` is not named: it still runs this checkout's.
144
+ - **`npx crewforth remove` takes Crewforth out of a project.** By default it lists what it would change and changes
145
+ nothing; `--apply` does it. It drops the `settings.json` hook entries that run `.claude/hooks/` (yours stay), its
146
+ status line, the blanket `Bash` allow and the skill-listing budget key, and sets `core.hooksPath` back (unset, or the
147
+ hook chain the shim ran). Its files move to `.claude/.crewforth-removed-<time>/`; nothing is deleted, and your own
148
+ agents and skills stay. `CLAUDE.md` is not touched: the command names the `@.claude/DISCIPLINE.md` line to delete.
149
+ - **`/crew-doctor` reads two more ways a setup goes quiet.** A line in `CLAUDE.md` that reads as loosening a §4 rule
150
+ ("no need to ask before committing", "skip the review") is named with its line number, unless a "never" or "do
151
+ not" precedes it.
152
+ A `CREW_` variable set in the environment that no Crewforth file reads (a typo) is named, and so is a switch that
153
+ takes a whole number set to something else.
154
+
155
+ - **Studio's summary box says what each kind of agent spent.** Open **Agents** and, as **Cost** does for models, it
156
+ lists one line per agent type (`Backend`, `Test ×2`, …) with its new tokens and estimated cost, the most spent
157
+ first; a type that ran more than once shows how many times. Without an agent to break down, the row still opens on
158
+ the token kinds.
159
+
160
+ ### Changed
161
+
162
+ - **`dependency-audit` asks which registry answers an internal name** (scope mappings, `--index-url` against
163
+ `--extra-index-url`, NuGet source mapping, `GOPRIVATE`, image digests), names a registry config committed in the
164
+ repository, and checks each new package against the popular name one edit away.
165
+ - **`dependency-upgrade` reports a failed lookup as `could not check`**, never as up to date, and installs a new
166
+ package with its scripts off first.
167
+ - **A test run read through a pipe is not evidence.** `npm test | tail` exits with tail's status; the DoD, the
168
+ reviewer and `token-budget` now ask for `set -o pipefail` or a redirect, and the reviewer does not cite a piped run
169
+ without it.
170
+ - **The reviewer has Crewforth's own simplify passes** for when `/simplify` is shadowed or absent: behaviour does not
171
+ change, nothing unexplained is deleted, the pass stays inside the diff.
172
+ - **`crew-test-expert` reports exactly `pass`, `fail`, `unavailable` or `blocked`** and stops once the acceptance
173
+ tests pass; **`crew-commit-agent`** adds a `Not tested:` line under its proposal.
174
+ - **The `worktree` skill looks before it removes.** Before `git worktree remove` it lists uncommitted files and
175
+ commits that exist nowhere else and asks; `--force` and `branch -D` only on the user's word. It refuses to reuse an
176
+ existing branch name silently, never deletes a branch it adopted, tells a submodule from a worktree, runs the
177
+ tests once in a new worktree before the first edit, and has rules for services the worktrees share.
178
+ - **Studio's Map spreads lines that share a side of a card**, so a module with three dependents shows three lines
179
+ instead of one. Measured first on the crew-map test fixtures and Crewforth's own map: 48 line ends shared a pixel
180
+ across 46 edges; now none do. Sideways lines between ends at two heights turn square instead of running slanted.
181
+ - **Studio's graph uses both sides of a wide window.** The session stands in the middle: the agents that build
182
+ (backend, frontend, database, test, devops, performance) grow to its left and every other agent to its right, so
183
+ one tall column beside an empty half of the pane becomes two short ones. The side follows the agent's type, never
184
+ the count, so an agent that arrives moves nothing already drawn. A narrow window keeps the left-to-right tree, and
185
+ **Order** keeps its waves.
186
+ - **Frontend work checks more of what users hit.** `a11y` separates criteria from conventions (a failed A/AA
187
+ criterion is a finding, AAA or a 44 px target a recommendation), adds a skip link, flashing, an error summary and
188
+ positive `tabindex`, a table of patterns to grep before finishing, and recipes for focus, forms and which
189
+ announcement channel to use. `frontend-design` plans in two passes against the generic default, measures contrast
190
+ on the rendered pair, lists the UI blockers that are always high, and gains references for motion (durations,
191
+ easing, interruption, gestures), UI copy (buttons, errors, confirm or undo, empty states) and greppable layout,
192
+ type, colour and motion patterns. `frontend` gains worst-realistic-data probes, building from a design file, and
193
+ page metadata; `performance` gains Core Web Vitals; `testing` gains a headless path for the verify contract when
194
+ no browser tool is attached; `i18n-integrity` flags sentences assembled around a variable and hand-written plurals.
195
+ The README diagrams carry a title and a description under ids of their own.
196
+ - **A review ends.** A critical or high fix re-runs every lane, a lesser fix only the lane that raised it; a low
197
+ finding never starts a round; a round with nothing above low is the last, and after three rounds the user is
198
+ asked. The lanes follow what the diff touches: a docs-only diff gets the reviewer alone. Each lane reports
199
+ `ran`, `ran-partial`, `failed`, `timed-out` or `not-run`, and "clean" needs every launched lane to have run.
200
+ - **An approval is bound to what it approves, not to a 30-minute clock.** An answer given late is recorded while
201
+ HEAD, the branch and the staged tree are what they were when the question was asked; one given after the staged
202
+ content changed is refused (it used to be recorded). With nothing staged the commit question is not asked.
203
+ - **Short replies keep their meaning.** "Short never costs meaning" is in the communication rules: warnings,
204
+ numbers and conditions are never cut. Every report says what was not tested and labels each figure measured,
205
+ derived or inferred; a refused action is not retried another way.
206
+ - **Skill descriptions say when, not how.** Six descriptions that summarised their method now say when to reach for
207
+ the skill (the skill listing fell from 9,116 to 8,826 characters), and each model-picked skill has a measured
208
+ routing floor. A SKILL.md has a size cap; a long reference opens with its contents.
209
+ - **Smaller fixes to the rules:** a bypass the user accepted in writing is not a deferral; `db-migration` covers
210
+ expand/contract across deploys; the accessibility baseline is WCAG 2.2 AA; motion uses `ease-out`, never
211
+ `ease-in`; the planner returns the plan and the main thread writes it.
212
+ - **Plans say what they rest on, and stop when the code moves under them.** `docs/PLAN.md` now records the commit it
213
+ was written at, a size per task (S/M/L, never a date), the one test level that proves each criterion, the
214
+ constraints copied word for word from the request, `Interfaces:` between tasks that feed each other, a checkpoint
215
+ every two or three tasks, and up to five inputs no test covers, each assigned to a task. A task may quote the code
216
+ it changes; when the file no longer matches, the task stops and reports. A plan with unchecked work for something
217
+ else is not overwritten. The planner copies `verify:` from `## Verify` and the Crew Map's `tests` instead of
218
+ guessing, and treats the map's `notMeasured` areas as unknown. `confidence-check` gains a seventh check, "What must
219
+ it reach?" (every caller, test and export a change breaks), and looks for existing code in a fixed order before
220
+ writing new; `iterate` builds one slice at a time and lists what it noticed but did not touch.
221
+ - **Domain skills carry what their field learned the hard way.** `api-design` gains an idempotency reference (a key
222
+ from the user's intent, claimed atomically, a replay that answers what the first call answered, `422` for the same
223
+ key with another payload) and says observable behaviour is the contract. `ci-pipeline` gains rules for an
224
+ unattended agent job (pinned, few tools, an isolated copy, an allowlist for what comes back). `commit-message`
225
+ requires a body for breaking changes, security fixes, migrations and reverts, covers every staged file and never
226
+ invents a reason. `deploy` adds an advance / hold / roll back table for staged rollouts and a before/after
227
+ snapshot of the public contract; `release` checks there is something to release before it creates anything, and
228
+ reads the version back from the registry and installs it from an empty folder after publishing. `mcp-builder`
229
+ follows the v2 SDKs (checked 2026-11-07), Streamable HTTP, behaviour hints, paging and service-prefixed tool names,
230
+ and adds a reference for command-line tools an agent calls. `observability` starts from the questions on-call will
231
+ ask and keeps metric labels low-cardinality; `performance` keeps a change only when it beats the run-to-run noise
232
+ and checks an optimised function against the original first. Every crew agent that stops short now opens its report
233
+ with `stopped:` and why.
234
+ - **A broken payload installs nothing.** `start.sh` and `adopt.sh` check the payload before their first write: a hook
235
+ that `settings.json` wires but the payload does not carry, a skill without its `SKILL.md` or a missing gate file
236
+ stops the install with the list and "nothing installed".
237
+ - **Docs are grounded, and decisions continue the project's own records.** `docs-writer` reads each fact from where
238
+ it lives (behaviour from the code and its test, flags from `--help`, config from its loader, API shapes from the
239
+ schema, labels from the i18n source, versions from the tags) and deletes what it cannot check instead of flagging
240
+ it; a business or legal statement goes to its owner as a question. A doc is scored 0-2 on five points before it
241
+ ships, and a new "Diagrams" section caps a diagram at 9 boxes and 12 arrows and treats every box as a claim. `adr`
242
+ first looks for an existing decisions folder, `.adr-dir` or numbering and continues it; `docs/adr/` is only the
243
+ fallback. `/crew-doctor` now also names missing paths in a nested `CLAUDE.md` and in `AGENTS.md`, and the suite
244
+ checks that every `§N.M` cited in agents, skills, hooks and scripts is a heading of the discipline.
245
+ - **The quality gate reads one line, `## Stack` → `Quality analysis:`** (`sonarqube-local`, `sonarqube-server` or
246
+ `declined`), in place of six "(if SonarQube is in use)" clauses that a project with no server read as false every
247
+ time. `declined` is reported as "no analysis ran — unverified". The doctor's readiness section says when the line
248
+ is missing or empty.
249
+ - **`sonarqube-check` produces coverage before the scan** (the report and the scanner parameter per stack), writes
250
+ "not measured" instead of 0% when no report was passed, and reports the overall ratings when the new-code period is
251
+ empty, where every `new_*` condition reads `OK` with nothing to judge.
252
+
253
+ ### Fixed
254
+
255
+ - **A session no longer tells the user that a command is not installed when it only cannot see it.** The commands
256
+ marked user-only (`/crew-interview`, `/crew-approve`, `/crew-pr`…) are left out of the model's skill list, so a
257
+ session asked about `/crew-interview` answered that no such command existed, while the files were in
258
+ `.claude/skills`. The discipline now says so in one sentence and points at `ls .claude/skills`.
259
+ - **An approval refused in a background session now says so.** Claude Code reports a session started with
260
+ `claude --bg`, or from the agent view, as unattended (`CLAUDE_CODE_SESSION_ATTENDED=0`) even when the user attaches
261
+ and types into it, so `/crew-approve` was refused with "started with -p, or by a program" and no way on. Measured on
262
+ 2.1.319: a background session and `claude -p` both report 0, and a prompt started by a program in the background is
263
+ recorded as a human one, so the gate cannot tell them apart and stays closed. The refusal now names the background
264
+ case and the two ways on: a regular session, or the commit in the user's own terminal.
265
+ - **`adopt` refuses a `settings.json` whose hook entries Claude Code would reject.** Claude Code ignores the whole
266
+ file when one hook entry has the wrong shape, so merging Crewforth's hooks into it installed gates that never ran.
267
+ adopt now names the entries and stops the merge, and the merged result is checked the same way before it is
268
+ written.
269
+ - **The route hint ignores what the request declines or quotes.** "don't run the security review", text in backticks
270
+ or quotes, and a Turkish clause that ends in "gerek yok" no longer pick an agent.
271
+ - **A session that resumes after compaction says when its state was not restored**, instead of starting as if
272
+ nothing had been there. The two session-start hooks stop waiting for input after two seconds
273
+ (`CREW_STDIN_TIMEOUT`), so a client that never closes the input does not hold the session.
274
+ - **The pre-compaction snapshot no longer times out on a long pasted prompt.** On Git Bash an 8 MB prompt line took
275
+ 24 s against PreCompact's 30 s limit; it now takes under a second.
276
+ - **The context-fill warning is no longer silenced by another plugin.** `session-guard.sh` warned nothing on a turn
277
+ where any other Stop hook had blocked.
278
+ - **Crew Map and the review pre-pass report what they could not read** (an unreadable or binary file, untracked
279
+ files in the pre-pass) instead of leaving it out without a word.
280
+
8
281
  ## [3.4.0] — 2026-10-30
9
282
 
10
283
  ### Added
package/README.md CHANGED
@@ -1,12 +1,13 @@
1
1
  <p align="center"><img src="https://raw.githubusercontent.com/Crewforth/crewforth/main/assets/logo.svg" alt="Crewforth" width="420"></p>
2
2
 
3
- **Crewforth is your engineering crew for Claude Code.** It adds subagents, skills, commands and hooks to Claude Code: 14 specialist agents that each own a domain, 40 skills that hold the method, 14 commands you start with `/crew-…`, and hooks that enforce the rules that matter.
3
+ **Crewforth is your engineering crew for Claude Code.** It adds subagents, skills, commands and hooks to Claude Code: 14 specialist agents that each own a domain, 40 skills that hold the method, 17 commands you start with `/crew-…`, and hooks that enforce the rules that matter.
4
4
 
5
5
  ```bash
6
6
  npx crewforth init # set up a new project
7
7
  npx crewforth adopt # hand it over onto an existing repository, on a branch
8
8
  npx crewforth add <agent|skill> # copy one agent or skill into ./.claude
9
9
  npx crewforth studio # open the Studio panel; installs nothing
10
+ npx crewforth remove # show what removing it would take out (--apply does it)
10
11
  ```
11
12
 
12
13
  Then open Claude Code and run `/crew-doctor` to confirm the setup. Requires bash and git; on Windows, use Git Bash (the gates run only when Claude Code finds it; when it does not, the tools that write or run commands are stopped and `/crew-doctor` names the path to set).
@@ -31,7 +32,7 @@ Then open Claude Code and run `/crew-doctor` to confirm the setup. Requires bash
31
32
 
32
33
  - **14 specialist agents**, from `crew-planner` to `crew-commit-agent`, each owning one domain.
33
34
  - **40 skills** holding the method: testing, migrations, API contracts, observability, accessibility, deployment.
34
- - **14 commands**, each started with its `/crew-…` name: `/crew-plan`, `/crew-review`, `/crew-ship`, `/crew-handoff`, `/crew-brainstorm`, `/crew-update`, `/crew-doctor`, `/crew-gates`, `/crew-skill`, `/crew-studio`, `/crew-approve`, `/crew-loosen`, `/crew-allow-verify`, `/crew-map`.
35
+ - **18 commands**, each started with its `/crew-…` name: `/crew-plan`, `/crew-review`, `/crew-ship`, `/crew-handoff`, `/crew-brainstorm`, `/crew-update`, `/crew-doctor`, `/crew-gates`, `/crew-skill`, `/crew-studio`, `/crew-approve`, `/crew-loosen`, `/crew-allow-verify`, `/crew-map`, `/crew-audit`, `/crew-interview`, `/crew-pr`, `/crew-todo`.
35
36
  - In `auto` mode a commit or a push waits for you: type `/crew-approve` and what was just refused is approved, tied to what is staged.
36
37
  - A stopped destructive command says what it would have touched; a review starts from a deterministic pre-pass, and `crew-refuter` tries to refute each finding that blocks.
37
38
  - **Organisation presets.** `npx crewforth init --preset strict` (or `solo`, or your own JSON file) carries critical paths, model floors and verify commands to every project.
package/VERSION CHANGED
@@ -1 +1 @@
1
- 3.4.0
1
+ 3.5.0-rc.2
package/adopt.sh CHANGED
@@ -64,6 +64,15 @@ unset _hl _hh _ht
64
64
 
65
65
  SRC="$HERE/kit"
66
66
  [ -d "$SRC" ] || { echo "ERROR: kit/ not found (must be in the same directory as adopt.sh)."; exit 1; }
67
+ # A payload that lost files (a truncated archive, a folder dropped in a copy) is refused before the first write, so
68
+ # a broken download says so instead of installing gates that fail on every call. English, like the check above: it
69
+ # answers before the language is chosen. eval/lib/payload-check.sh says what it reads.
70
+ if [ "${CREW_PAYLOAD_UNCHECKED:-0}" != 1 ] && [ -f "$SRC/eval/lib/payload-check.sh" ]; then
71
+ _pc="$(bash "$SRC/eval/lib/payload-check.sh" "$SRC" 2>&1)" || {
72
+ echo "ERROR: the kit/ payload is incomplete — nothing installed:"; printf '%s\n' "$_pc" | sed 's/^/ /'
73
+ echo "Download it again (npx crewforth@latest, or a fresh archive). CREW_PAYLOAD_UNCHECKED=1 installs it anyway."
74
+ exit 1; }
75
+ fi
67
76
 
68
77
  # --- flags (B5 — where a refresh lands) --- --here: the current branch · --new-branch: a fresh review branch.
69
78
  # Left empty, Stage 2 picks a smart default (first adopt -> new; update + untracked .claude -> here; update +
@@ -323,6 +332,7 @@ _mt() { # $1 = English text (the key); further args fill %s; result in _M
323
332
  "settings.json: %s") s='settings.json: %s' ;;
324
333
  "merge failed -> project setting PRESERVED (not overwritten)") s='birleştirme başarısız -> proje ayarı KORUNDU (üzerine yazılmadı)' ;;
325
334
  "existing file is INVALID JSON -> merge ABORT (no silent overwrite). Fix it by hand first.") s='mevcut dosya GEÇERSİZ JSON -> birleştirme İPTAL (üzerine sessizce yazılmaz). Önce elle düzeltin.' ;;
335
+ "existing file has a hook entry Claude Code rejects (it then ignores the whole file) -> merge ABORT. Fix the entries below by hand first.") s="mevcut dosyada Claude Code'un reddettiği bir hook girdisi var (o zaman dosyanın tamamını yok sayar) -> birleştirme İPTAL. Önce aşağıdaki girdileri elle düzeltin." ;;
326
336
  "settings.json: retired §4.4 ask rule(s) REMOVED (%s) — guard-bash.sh now asks for these itself; an ask rule would override its CLAUDE_GIT_OK allow. Re-add one only if your project wants that trade.") s='settings.json: emekliye ayrılan §4.4 ask kuralları KALDIRILDI (%s) — bunları artık guard-bash.sh kendisi soruyor; bir ask kuralı onun CLAUDE_GIT_OK iznini ezerdi. Bu bedeli bilerek istiyorsanız geri ekleyin.' ;;
327
337
  "Stage 4 — arm the git gates (SHIM via husky) + PROOF") s='Aşama 4 — git kapılarını devreye al (husky üzerinden SHIM) + KANIT' ;;
328
338
  "core.hooksPath -> .claude/hooks (no existing hook chain)") s='core.hooksPath -> .claude/hooks (başka hook zinciri yok)' ;;
@@ -359,6 +369,8 @@ _mt() { # $1 = English text (the key); further args fill %s; result in _M
359
369
  "%s written") s='%s yazıldı' ;;
360
370
  "%s written (persistent handover decision)") s='%s yazıldı (devralmanın kalıcı karar kaydı)' ;;
361
371
  "%s already exists — untouched (never-overwrite)") s='%s zaten var — dokunulmadı (üzerine asla yazılmaz)' ;;
372
+ "STOP: .claude/hooks already holds files under names Crewforth installs as its gates:%s") s="DUR: .claude/hooks içinde Crewforth'un kapı olarak kurduğu adlarla dosyalar zaten var:%s" ;;
373
+ "Crewforth did not write them. A first adopt would keep them, make them your git hooks and run them during the install. Nothing was changed. Look at them, move them out of .claude/hooks, then run adopt again.") s="Bunları Crewforth yazmadı. İlk kurulum onları olduğu gibi bırakır, git hook'larınız yapar ve kurulum sırasında çalıştırırdı. Hiçbir şey değişmedi. Dosyalara bakın, .claude/hooks dışına taşıyın, sonra adopt'u yeniden çalıştırın." ;;
362
374
  "+ .gitattributes: eol pins so the shared hooks stay LF on a Windows checkout") s="+ .gitattributes: paylaşılan hook'lar Windows checkout'unda LF kalsın diye eol sabitlendi" ;;
363
375
  "Review in your editor — nothing committed yet") s='Editörünüzde inceleyin — henüz hiçbir şey commit edilmedi' ;;
364
376
  "staged") s='stage edilen' ;;
@@ -706,6 +718,22 @@ KIT_PRESENT=0; KIT_VER=""
706
718
  # not make a never-installed project look installed — that turns on the force-refresh and overwrites its files.
707
719
  if [ "$KIT_PRESENT" = 0 ]; then for n in $KIT_OLD; do [ -f ".claude/agents/$n.md" ] && { KIT_PRESENT=1; break; }; done; fi
708
720
  [ -f .claude/VERSION ] && KIT_VER="$(head -1 .claude/VERSION 2>/dev/null)"
721
+ # A FRESH adopt keeps a file it finds (never-overwrite), and then makes .claude/hooks the git hooks directory, runs
722
+ # pre-commit there as its proof, runs guard-bash.sh and skill-trust.sh, and wires the rest into settings.json. So a
723
+ # file already sitting in .claude/hooks under a name Crewforth ships would be run as Crewforth, by the user, during
724
+ # the install and on every commit after it. Git never runs hooks that arrive with a clone; a cloned repository that
725
+ # carries .claude/hooks/pre-commit would make this installer do exactly that. Nothing has changed yet at this
726
+ # point, so the run stops here and names the files. An update (KIT_PRESENT=1) refreshes every one of them from
727
+ # the payload instead, so it is not affected.
728
+ if [ "$KIT_PRESENT" = 0 ] && [ -d .claude/hooks ]; then
729
+ _planted=""
730
+ for _hf in "$SRC"/hooks/*; do [ -f "$_hf" ] || continue; _hn="${_hf##*/}"; [ -e ".claude/hooks/$_hn" ] || [ -L ".claude/hooks/$_hn" ] && _planted="$_planted $_hn"; done
731
+ if [ -n "$_planted" ]; then
732
+ _mt 'STOP: .claude/hooks already holds files under names Crewforth installs as its gates:%s' "$_planted"; printf '%s\n' "$_M"
733
+ _mt 'Crewforth did not write them. A first adopt would keep them, make them your git hooks and run them during the install. Nothing was changed. Look at them, move them out of .claude/hooks, then run adopt again.'; printf '%s\n' "$_M"
734
+ exit 1
735
+ fi
736
+ fi
709
737
  # Backend pattern of the existing install. Since 3.0 there is ONE shape (generic) and this only answers a
710
738
  # migration question: was this a pre-3.0 .NET install? If so its pattern skill stays, as the project's own.
711
739
  # LEGACY_PROFILE is a pre-2.0 leftover of the same kind: it selects nothing, it only drives a notice.
@@ -1243,7 +1271,7 @@ done
1243
1271
  # kit-owned trees: FORCE-refresh on a re-adopt (KIT_PRESENT) so kit updates land; never-overwrite on a fresh adopt
1244
1272
  copy_noclobber "$SRC/agents" .claude/agents "$KIT_PRESENT" "$EXCL_A"; A_ADD=$ret_add; A_SKIP=$ret_skip
1245
1273
  # The summary counts skills and commands the way the README does — one per skill, a `kind: command` skill as a
1246
- # command, an `experimental: true` one as neither. copy_noclobber counts FILES (a skill with references/ is several),
1274
+ # command, an `experimental: "true"` one as neither. copy_noclobber counts FILES (a skill with references/ is several),
1247
1275
  # which is how an update from 2.13.0 once reported "skills +75" for 39 skills. A skill counts as added when the
1248
1276
  # copy put it there or refreshed it; one that was excluded, or that a fresh adopt found already present and left
1249
1277
  # alone, counts as skipped. The "already present" answer is taken before the copy, with builtins only.
@@ -1254,7 +1282,7 @@ for kd in "$SRC"/skills/*/; do
1254
1282
  [ -f "$kd/SKILL.md" ] || continue; kn="${kd%/}"; kn="${kn##*/}"; _k=0; _e=0; _d=0
1255
1283
  while IFS= read -r _l || [ -n "$_l" ]; do
1256
1284
  _l="${_l%$'\r'}"
1257
- case "$_l" in ---) _d=$((_d+1)); [ "$_d" -ge 2 ] && break ;; " kind: command") _k=1 ;; " experimental: true") _e=1 ;; esac
1285
+ case "$_l" in ---) _d=$((_d+1)); [ "$_d" -ge 2 ] && break ;; " kind: command") _k=1 ;; " experimental: true"|" experimental: \"true\"") _e=1 ;; esac
1258
1286
  done < "$kd/SKILL.md"
1259
1287
  [ "$_e" = 1 ] && continue
1260
1288
  _got=1
@@ -1704,10 +1732,16 @@ SET_FRESH=0
1704
1732
  if [ ! -f "$PSET" ]; then
1705
1733
  SET_FRESH=1
1706
1734
  [ -f "$KSET" ] && { cp "$KSET" "$PSET"; say "settings.json: was missing in the project -> Crewforth's was installed"; }
1735
+ # The hook entries Claude Code would reject (a hook without "type", a "command" that is not a string, a matcher that
1736
+ # is not a string): it ignores the WHOLE settings.json then, so every gate is off and nothing says so. A project file
1737
+ # like that is not merged into (the merge would carry the entry along); the problems are named and the file is left.
1738
+ elif SET_HC="$(awk -v op=hookcheck -f "$SET_AWK" "$PSET" 2>/dev/null)"; [ "$?" = 1 ]; then
1739
+ SET_NOTE="existing file has a hook entry Claude Code rejects (it then ignores the whole file) -> merge ABORT. Fix the entries below by hand first."
1740
+ _mt "$SET_NOTE"; warnm 'settings.json: %s' "$_M"; printf '%s\n' "$SET_HC" | sed 's/^/ /'
1707
1741
  else
1708
1742
  awk -v op=merge -v retired='Bash(git add:*)|Bash(git commit:*)|Bash(git push:*)|Bash(git checkout -b:*)' -f "$SET_AWK" "$KSET" "$PSET" > "$PSET.tmp" 2>/dev/null
1709
1743
  case "$?" in
1710
- 0) if [ -s "$PSET.tmp" ] && awk -v op=validate -f "$SET_AWK" "$PSET.tmp" 2>/dev/null; then
1744
+ 0) if [ -s "$PSET.tmp" ] && awk -v op=validate -f "$SET_AWK" "$PSET.tmp" 2>/dev/null && awk -v op=hookcheck -f "$SET_AWK" "$PSET.tmp" >/dev/null 2>&1; then
1711
1745
  # Written THROUGH the existing file, not swapped in with mv: a symlinked settings.json (a team's shared
1712
1746
  # file) keeps its link and gets the merge, and a 0600 mode survives.
1713
1747
  cat "$PSET.tmp" > "$PSET"; say 'settings.json: hook-aware MERGE (Crewforth hooks refreshed - %s)' "$KEPT"
@@ -1729,8 +1763,9 @@ elif [ "$SET_FRESH" = 1 ]; then HAND_SET="Crewforth's settings.json installed (t
1729
1763
  else HAND_SET="hook-aware merge (Crewforth hooks REFRESHED to current — new events + timeouts land; your own custom hooks/permissions PRESERVED${RET_GONE:+, except the retired §4.4 ask rule(s) REMOVED: $RET_GONE — guard-bash.sh asks for these itself})"; fi
1730
1764
 
1731
1765
  # The status line, only where none is set (a user's own would lose to the project's setting, so it is never
1732
- # replaced). eval/lib/statusline.sh --install decides and writes; this only says what it did.
1733
- if [ -f .claude/eval/lib/statusline.sh ]; then
1766
+ # replaced). eval/lib/statusline.sh --install decides and writes; this only says what it did. Not after a refused
1767
+ # merge: that file was left as it was on purpose, and the status line is a write to it.
1768
+ if [ -z "$SET_NOTE" ] && [ -f .claude/eval/lib/statusline.sh ]; then
1734
1769
  case "$(bash .claude/eval/lib/statusline.sh --install 2>/dev/null)" in
1735
1770
  installed) say "Status line: Crewforth's (the gate's last word, a standing approval, crew agents, fill, cost)." ;;
1736
1771
  kept) say "Status line: yours is kept; Crewforth's is one command away (see /crew-doctor)." ;;
package/bin/cli.js CHANGED
@@ -36,6 +36,10 @@ Usage:
36
36
  Copy one agent or skill (and the skills an agent uses) into ./.claude — not the full install.
37
37
  --list shows the catalogue; --force replaces a file that differs; --no-deps adds the agent alone.
38
38
 
39
+ npx crewforth remove [--apply]
40
+ Take a file install back out: Crewforth's hook entries, git hook path and files (moved to
41
+ .claude/.crewforth-removed-<time>/, not deleted). A dry run unless --apply. CLAUDE.md is never touched.
42
+
39
43
  npx crewforth --version
40
44
  Print the Crewforth version and exit.
41
45
 
@@ -87,8 +91,8 @@ function catalogue(pkgDir) {
87
91
  const isCommand = (d) => /\n {2}kind: command\r?\n/.test(fs.readFileSync(path.join(skillsDir, d, 'SKILL.md'), 'utf8').split(/\r?\n---\r?\n/)[0] + '\n'); // CRLF too
88
92
  const commands = all.filter(isCommand);
89
93
  const skills = all.filter((d) => !commands.includes(d));
90
- // `metadata: experimental: true` installs and answers when named, but is not listed or counted until it is done.
91
- const experimental = all.filter((d) => /\n {2}experimental: true\r?\n/.test(fs.readFileSync(path.join(skillsDir, d, 'SKILL.md'), 'utf8').split(/\r?\n---\r?\n/)[0] + '\n'));
94
+ // `metadata: experimental: "true"` installs and answers when named, but is not listed or counted until it is done.
95
+ const experimental = all.filter((d) => /\n {2}experimental: "?true"?\r?\n/.test(fs.readFileSync(path.join(skillsDir, d, 'SKILL.md'), 'utf8').split(/\r?\n---\r?\n/)[0] + '\n'));
92
96
  return { root, agents, skills, commands, experimental };
93
97
  }
94
98
 
@@ -373,6 +377,132 @@ function addCommand(pkgDir, args, opts = {}) {
373
377
  }
374
378
  // ============================== /add ================================
375
379
 
380
+ // ================================ remove ================================
381
+ // `remove` takes a file install (start.sh / adopt.sh) back out of a project. A DRY RUN unless --apply: it prints
382
+ // what it would do and changes nothing. With --apply:
383
+ // settings.json every hook entry whose command or args name .claude/hooks/ goes (the merge treats exactly those
384
+ // as Crewforth's), and so does the status line when it is Crewforth's. The blanket "Bash" allow
385
+ // goes too: it is safe only while guard-bash.sh judges every command, and that hook is leaving.
386
+ // Every other key, permission and hook of the project stays. Not valid JSON: refused, untouched.
387
+ // git core.hooksPath is unset only when it points at .claude/hooks; when it points at the shim, it is
388
+ // set back to the chain the shim ran (husky, a hooks folder), or unset for .git/hooks.
389
+ // files what the install manifest and the payload name (Crewforth's agents and skills, hooks/, eval/,
390
+ // studio/, git-shim/ and its own records) MOVES to .claude/.crewforth-removed-<time>/, never deleted.
391
+ // A skill or agent of the project's own, not in the manifest, stays where it is.
392
+ // never touched ./CLAUDE.md (its @.claude/DISCIPLINE.md line is named for the user to delete), .gitignore.
393
+ // A second run finds nothing and says so. A plugin install is removed with /plugin uninstall instead.
394
+ const OWN_FLAT = ['DISCIPLINE.md', 'AGENT_TEMPLATE.md', 'README.md', 'VERSION', 'kit.conf', 'kit-manifest.txt', 'crewforth-added.json'];
395
+ const OWN_DIRS = ['hooks', 'eval', 'studio', 'git-shim', 'state', '.state'];
396
+ function removePlan(projectRoot) {
397
+ const cl = path.join(projectRoot, '.claude');
398
+ const plan = { settings: null, settingsWhy: [], git: null, move: [], notes: [] };
399
+ // settings.json
400
+ const sp = path.join(cl, 'settings.json');
401
+ if (fs.existsSync(sp)) {
402
+ let doc = null;
403
+ try { doc = JSON.parse(fs.readFileSync(sp, 'utf8')); } catch (_) { plan.notes.push('.claude/settings.json is not valid JSON: left as it is; remove its .claude/hooks/ entries by hand'); }
404
+ if (doc && typeof doc === 'object' && !Array.isArray(doc)) {
405
+ const ours = (h) => h && typeof h === 'object' && [h.command, ...(Array.isArray(h.args) ? h.args : [])].some((x) => typeof x === 'string' && /\.claude[\\/]hooks[\\/]/.test(x));
406
+ let n = 0;
407
+ if (doc.hooks && typeof doc.hooks === 'object') {
408
+ for (const ev of Object.keys(doc.hooks)) {
409
+ const list = Array.isArray(doc.hooks[ev]) ? doc.hooks[ev] : [];
410
+ const kept = [];
411
+ for (const e of list) {
412
+ const hs = e && Array.isArray(e.hooks) ? e.hooks : [];
413
+ const left = hs.filter((h) => !ours(h)); n += hs.length - left.length;
414
+ if (left.length) kept.push({ ...e, hooks: left }); else if (!hs.length) kept.push(e);
415
+ }
416
+ if (kept.length) doc.hooks[ev] = kept; else delete doc.hooks[ev];
417
+ }
418
+ if (!Object.keys(doc.hooks).length) delete doc.hooks;
419
+ }
420
+ if (n) plan.settingsWhy.push(`${n} hook command(s) that run .claude/hooks/`);
421
+ if (doc.statusLine && typeof doc.statusLine.command === 'string' && /\.claude[\\/]eval[\\/]lib[\\/]statusline\.sh/.test(doc.statusLine.command)) {
422
+ delete doc.statusLine; plan.settingsWhy.push("Crewforth's status line");
423
+ }
424
+ const allow = doc.permissions && Array.isArray(doc.permissions.allow) ? doc.permissions.allow : null;
425
+ if (allow && allow.includes('Bash') && n) {
426
+ doc.permissions.allow = allow.filter((x) => x !== 'Bash');
427
+ plan.settingsWhy.push('the blanket "Bash" allow (safe only while guard-bash.sh judges every command)');
428
+ }
429
+ if (n && doc.skillListingBudgetFraction === 0.04) { delete doc.skillListingBudgetFraction; plan.settingsWhy.push('the skill listing budget Crewforth set (0.04)'); }
430
+ if (plan.settingsWhy.length) plan.settings = doc;
431
+ }
432
+ }
433
+ // git
434
+ const gitEnv = { ...process.env }; delete gitEnv.GIT_DIR; delete gitEnv.GIT_WORK_TREE;
435
+ const git = (args) => spawnSync('git', args, { cwd: projectRoot, env: gitEnv, encoding: 'utf8' });
436
+ const hp = git(['config', '--get', 'core.hooksPath']);
437
+ const cur = hp.status === 0 ? String(hp.stdout).trim().replace(/\\/g, '/').replace(/\/+$/, '') : '';
438
+ if (/(^|\/)\.claude\/hooks$/.test(cur)) plan.git = { from: cur, to: null };
439
+ else if (/(^|\/)\.claude\/git-shim$/.test(cur)) {
440
+ let to = null;
441
+ const chain = git(['config', '--get', 'crewforth.hookschain']);
442
+ if (chain.status === 0 && String(chain.stdout).trim()) to = String(chain.stdout).trim();
443
+ else {
444
+ try { const m = /^P="\$ROOT\/(.+)\/\$H"$/m.exec(fs.readFileSync(path.join(cl, 'git-shim', 'pre-commit'), 'utf8')); if (m) to = m[1]; } catch (_) { /* no shim file */ }
445
+ }
446
+ if (to === '.git/hooks') to = null;
447
+ plan.git = { from: cur, to };
448
+ }
449
+ // files
450
+ const manifest = path.join(cl, 'kit-manifest.txt');
451
+ let named = [];
452
+ try { named = fs.readFileSync(manifest, 'utf8').split(/\r?\n/).map((l) => l.trim()).filter((l) => /^(agents|skills)\/[A-Za-z0-9._-]+$/.test(l)); } catch (_) { /* none */ }
453
+ for (const rel of [...named, ...OWN_DIRS, ...OWN_FLAT]) {
454
+ const p = path.join(cl, ...rel.split('/'));
455
+ try { fs.lstatSync(p); plan.move.push(rel); } catch (_) { /* absent */ }
456
+ }
457
+ let crewLeft = false; try { crewLeft = fs.readdirSync(path.join(cl, 'agents')).some((f) => /^crew-.*\.md$/.test(f)); } catch (_) { /* none */ }
458
+ if (!named.length && crewLeft) plan.notes.push('no .claude/kit-manifest.txt: Crewforth agents and skills cannot be told from the project\'s own, so they stay; move them by hand');
459
+ try { if (/^@\.claude\/DISCIPLINE\.md\s*$/m.test(fs.readFileSync(path.join(projectRoot, 'CLAUDE.md'), 'utf8'))) plan.notes.push('CLAUDE.md is not touched: delete its @.claude/DISCIPLINE.md line yourself'); } catch (_) { /* none */ }
460
+ plan.git && (plan.gitRun = git);
461
+ return plan;
462
+ }
463
+ function removeCommand(projectRoot, args, log = console.log) {
464
+ const unknown = args.filter((a) => a !== '--apply');
465
+ if (unknown.length) { log(`remove: unknown argument ${unknown[0]}\nUsage: npx crewforth remove [--apply]`); return 2; }
466
+ const apply = args.includes('--apply');
467
+ if (!fs.existsSync(path.join(projectRoot, '.claude'))) { log('remove: no .claude/ here — nothing to remove. A plugin install is removed with /plugin uninstall.'); return 0; }
468
+ const plan = removePlan(projectRoot);
469
+ if (!plan.settings && !plan.git && !plan.move.length) {
470
+ log('remove: nothing of Crewforth is left here (no hook entries, no git hook path, no files).');
471
+ for (const n of plan.notes) log(` · ${n}`);
472
+ return 0;
473
+ }
474
+ log(apply ? 'remove: taking Crewforth out of this project' : 'remove: DRY RUN — nothing is changed; run with --apply to do this');
475
+ if (plan.settings) log(` settings.json: drop ${plan.settingsWhy.join(', ')}; everything else stays`);
476
+ if (plan.git) log(` git: core.hooksPath ${plan.git.from} -> ${plan.git.to ? plan.git.to : '(unset)'}`);
477
+ const dest = `.claude/.crewforth-removed-${new Date().toISOString().replace(/[-:]/g, '').replace(/\..*$/, '')}`;
478
+ if (plan.move.length) {
479
+ const nA = plan.move.filter((x) => x.startsWith('agents/')).length; const nS = plan.move.filter((x) => x.startsWith('skills/')).length;
480
+ const rest = plan.move.filter((x) => !/^(agents|skills)\//.test(x));
481
+ log(` move to ${dest}/: ${[nA && `${nA} agent(s)`, nS && `${nS} skill(s)`, ...rest].filter(Boolean).join(', ')}`);
482
+ }
483
+ for (const n of plan.notes) log(` · ${n}`);
484
+ if (!apply) return 0;
485
+ if (plan.settings) {
486
+ const sp = path.join(projectRoot, '.claude', 'settings.json');
487
+ fs.writeFileSync(sp, JSON.stringify(plan.settings, null, 2) + '\n'); // through the file: a link stays a link
488
+ }
489
+ if (plan.git) {
490
+ const r = plan.git.to ? plan.gitRun(['config', 'core.hooksPath', plan.git.to]) : plan.gitRun(['config', '--unset-all', 'core.hooksPath']);
491
+ if (r.status !== 0 && plan.git.to) { log(`remove: git config failed: ${String(r.stderr).trim()}`); return 1; }
492
+ plan.gitRun(['config', '--unset-all', 'crewforth.hookschain']);
493
+ }
494
+ if (plan.move.length) {
495
+ const d = path.join(projectRoot, ...dest.split('/'));
496
+ for (const rel of plan.move) {
497
+ const to = path.join(d, ...rel.split('/'));
498
+ fs.mkdirSync(path.dirname(to), { recursive: true });
499
+ fs.renameSync(path.join(projectRoot, '.claude', ...rel.split('/')), to);
500
+ }
501
+ }
502
+ log(`remove: done.${plan.move.length ? ` The files are in ${dest}/; delete that folder when you no longer want them.` : ''}`);
503
+ return 0;
504
+ }
505
+
376
506
  // ---- studio and add: pure Node, handled BEFORE the bash probe below, so neither needs Git Bash on Windows. ----
377
507
 
378
508
  // `studio`: run the panel straight from the package — no stage, no copy. It only READS its own directory (web/,
@@ -401,6 +531,12 @@ if (sub === 'studio') {
401
531
  return;
402
532
  }
403
533
 
534
+ if (sub === 'remove') {
535
+ let code;
536
+ try { code = removeCommand(process.cwd(), argv.slice(1)); } catch (e) { console.error(`crewforth remove: ${e.message}`); code = 1; }
537
+ process.exit(code);
538
+ }
539
+
404
540
  if (sub === 'add') {
405
541
  let code;
406
542
  try { code = addCommand(pkgDir, argv.slice(1)); } catch (e) { console.error(`crewforth add: ${e.message}`); code = 1; }