crewforth 0.0.1 → 3.0.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.
- package/CHANGELOG.md +3805 -0
- package/LICENSE +21 -0
- package/README.md +37 -7
- package/VERSION +1 -0
- package/adopt.sh +1739 -0
- package/bin/cli.js +497 -0
- package/kit/AGENT_TEMPLATE.md +92 -0
- package/kit/CLAUDE.md +228 -0
- package/kit/README.md +107 -0
- package/kit/agents/crew-backend-expert.md +95 -0
- package/kit/agents/crew-commit-agent.md +96 -0
- package/kit/agents/crew-database-expert.md +84 -0
- package/kit/agents/crew-devops-expert.md +82 -0
- package/kit/agents/crew-frontend-expert.md +85 -0
- package/kit/agents/crew-performance-expert.md +78 -0
- package/kit/agents/crew-planner.md +60 -0
- package/kit/agents/crew-privacy-agent.md +71 -0
- package/kit/agents/crew-review-agent.md +95 -0
- package/kit/agents/crew-security-expert.md +73 -0
- package/kit/agents/crew-session-manager.md +77 -0
- package/kit/agents/crew-test-expert.md +64 -0
- package/kit/eval/doctor.sh +639 -0
- package/kit/eval/gate-report.sh +137 -0
- package/kit/eval/golden-holdout.txt +53 -0
- package/kit/eval/golden-routing.txt +199 -0
- package/kit/eval/lib/crew-env.sh +14 -0
- package/kit/eval/lib/settings-json.awk +154 -0
- package/kit/eval/lib/skill-listing.awk +43 -0
- package/kit/eval/lib/star.sh +70 -0
- package/kit/eval/parser-conformance.sh +612 -0
- package/kit/eval/preflight.sh +186 -0
- package/kit/eval/routing-eval.sh +209 -0
- package/kit/eval/scan-skill.sh +162 -0
- package/kit/eval/smoke-test.sh +6736 -0
- package/kit/eval/update-guard.sh +81 -0
- package/kit/eval/utilization.sh +130 -0
- package/kit/hooks/board-sync.sh +106 -0
- package/kit/hooks/board.sh +962 -0
- package/kit/hooks/commit-msg +49 -0
- package/kit/hooks/context-usage.sh +353 -0
- package/kit/hooks/floor-blocklist.txt +124 -0
- package/kit/hooks/guard-bash.sh +1433 -0
- package/kit/hooks/guard-commit-scan.sh +511 -0
- package/kit/hooks/guard-write.sh +513 -0
- package/kit/hooks/pre-commit +577 -0
- package/kit/hooks/route-hint.sh +185 -0
- package/kit/hooks/secret-blocklist.txt +82 -0
- package/kit/hooks/session-guard.sh +149 -0
- package/kit/hooks/session-rehydrate.sh +41 -0
- package/kit/hooks/session-stats.sh +252 -0
- package/kit/hooks/session-update-check.sh +236 -0
- package/kit/hooks/skill-trust.sh +163 -0
- package/kit/hooks/trace-blocklist.txt +53 -0
- package/kit/settings.json +132 -0
- package/kit/skills/a11y/SKILL.md +44 -0
- package/kit/skills/adr/SKILL.md +74 -0
- package/kit/skills/api-design/SKILL.md +50 -0
- package/kit/skills/automode-policy/SKILL.md +91 -0
- package/kit/skills/automode-policy/references/policy.json +13 -0
- package/kit/skills/automode-policy/scripts/apply.sh +84 -0
- package/kit/skills/automode-policy/scripts/check.sh +102 -0
- package/kit/skills/backend-architecture/SKILL.md +103 -0
- package/kit/skills/brainstorm/SKILL.md +47 -0
- package/kit/skills/ci-pipeline/SKILL.md +38 -0
- package/kit/skills/commit-message/SKILL.md +82 -0
- package/kit/skills/commit-message/references/examples.md +8 -0
- package/kit/skills/confidence-check/SKILL.md +86 -0
- package/kit/skills/crew-board/SKILL.md +55 -0
- package/kit/skills/crew-brainstorm/SKILL.md +19 -0
- package/kit/skills/crew-code-review/SKILL.md +151 -0
- package/kit/skills/crew-code-review/references/history-search.md +19 -0
- package/kit/skills/crew-code-review/references/panel-mode.md +32 -0
- package/kit/skills/crew-code-review/references/receiving.md +23 -0
- package/kit/skills/crew-doctor/SKILL.md +46 -0
- package/kit/skills/crew-gates/SKILL.md +25 -0
- package/kit/skills/crew-handoff/SKILL.md +14 -0
- package/kit/skills/crew-plan/SKILL.md +18 -0
- package/kit/skills/crew-review/SKILL.md +26 -0
- package/kit/skills/crew-ship/SKILL.md +19 -0
- package/kit/skills/crew-skill/SKILL.md +62 -0
- package/kit/skills/crew-studio/SKILL.md +129 -0
- package/kit/skills/crew-update/SKILL.md +43 -0
- package/kit/skills/db-migration/SKILL.md +120 -0
- package/kit/skills/db-migration/references/backup-restore.md +33 -0
- package/kit/skills/db-migration/references/raw-sql.md +30 -0
- package/kit/skills/db-migration/references/tool-matrix.md +35 -0
- package/kit/skills/dependency-audit/SKILL.md +61 -0
- package/kit/skills/dependency-upgrade/SKILL.md +82 -0
- package/kit/skills/deploy/SKILL.md +100 -0
- package/kit/skills/deploy/references/proxy-ssl.md +56 -0
- package/kit/skills/deploy/references/rollback.md +18 -0
- package/kit/skills/deploy/references/scripts.md +9 -0
- package/kit/skills/deploy/references/topology-a.md +128 -0
- package/kit/skills/deploy/references/topology-b.md +44 -0
- package/kit/skills/docs-writer/SKILL.md +53 -0
- package/kit/skills/docs-writer/references/coauthoring.md +34 -0
- package/kit/skills/eval-grader/SKILL.md +75 -0
- package/kit/skills/eval-grader/references/method.md +51 -0
- package/kit/skills/frontend/SKILL.md +56 -0
- package/kit/skills/frontend/references/verify-contract.md +41 -0
- package/kit/skills/frontend-design/SKILL.md +71 -0
- package/kit/skills/frontend-design/references/design-review.md +34 -0
- package/kit/skills/frontend-rn-expo/SKILL.md +25 -0
- package/kit/skills/handoff/SKILL.md +71 -0
- package/kit/skills/handoff/references/cold-reader.md +51 -0
- package/kit/skills/i18n-integrity/SKILL.md +31 -0
- package/kit/skills/incident-runbook/SKILL.md +40 -0
- package/kit/skills/incident-runbook/references/postmortem.md +16 -0
- package/kit/skills/iterate/SKILL.md +40 -0
- package/kit/skills/mcp-builder/SKILL.md +54 -0
- package/kit/skills/mcp-builder/references/building.md +71 -0
- package/kit/skills/observability/SKILL.md +50 -0
- package/kit/skills/performance/SKILL.md +38 -0
- package/kit/skills/performance/references/bottlenecks.md +10 -0
- package/kit/skills/privacy-compliance/SKILL.md +75 -0
- package/kit/skills/red-team/SKILL.md +48 -0
- package/kit/skills/reflect/SKILL.md +57 -0
- package/kit/skills/release/SKILL.md +44 -0
- package/kit/skills/security-scan/SKILL.md +99 -0
- package/kit/skills/security-scan/references/ai-agents.md +107 -0
- package/kit/skills/security-scan/references/fronts.md +99 -0
- package/kit/skills/security-scan/references/prompting.md +23 -0
- package/kit/skills/security-scan/references/reporting.md +75 -0
- package/kit/skills/security-scan/references/verify.md +131 -0
- package/kit/skills/sonarqube-check/SKILL.md +108 -0
- package/kit/skills/sonarqube-check/references/local-engines.md +32 -0
- package/kit/skills/sonarqube-check/references/stand-up-server.md +25 -0
- package/kit/skills/spec-planning/SKILL.md +92 -0
- package/kit/skills/systematic-debugging/SKILL.md +57 -0
- package/kit/skills/systematic-debugging/references/techniques.md +44 -0
- package/kit/skills/teamboard/SKILL.md +88 -0
- package/kit/skills/teamboard/references/setup.md +37 -0
- package/kit/skills/testing/SKILL.md +49 -0
- package/kit/skills/testing/references/flaky-triage.md +51 -0
- package/kit/skills/threat-model/SKILL.md +57 -0
- package/kit/skills/threat-model/references/interview.md +51 -0
- package/kit/skills/threat-model/references/schema.md +42 -0
- package/kit/skills/token-budget/SKILL.md +51 -0
- package/kit/skills/trace-scan/SKILL.md +43 -0
- package/kit/skills/worktree/SKILL.md +40 -0
- package/kit/studio/README.md +266 -0
- package/kit/studio/ensure-node.sh +471 -0
- package/kit/studio/package.json +14 -0
- package/kit/studio/server/hooks/studio-gate.sh +169 -0
- package/kit/studio/server/index.js +902 -0
- package/kit/studio/server/lib/crew-env.js +11 -0
- package/kit/studio/server/lib/fleet.js +146 -0
- package/kit/studio/server/lib/graph.js +533 -0
- package/kit/studio/server/lib/instance.js +135 -0
- package/kit/studio/server/lib/kit-telemetry.js +182 -0
- package/kit/studio/server/lib/kit.js +158 -0
- package/kit/studio/server/lib/palette.js +118 -0
- package/kit/studio/server/lib/peers.js +72 -0
- package/kit/studio/server/lib/permissions.js +150 -0
- package/kit/studio/server/lib/projects.js +294 -0
- package/kit/studio/server/lib/roster.js +191 -0
- package/kit/studio/server/lib/session.js +309 -0
- package/kit/studio/server/lib/terminal.js +91 -0
- package/kit/studio/server/lib/transcript.js +100 -0
- package/kit/studio/web/app.js +1098 -0
- package/kit/studio/web/canvas.js +1179 -0
- package/kit/studio/web/chat.js +543 -0
- package/kit/studio/web/index.html +86 -0
- package/kit/studio/web/md.js +110 -0
- package/kit/studio/web/storage-migrate.js +23 -0
- package/kit/studio/web/style.css +1453 -0
- package/package.json +46 -8
- package/start.sh +764 -0
- package/bin/crewforth.js +0 -10
package/start.sh
ADDED
|
@@ -0,0 +1,764 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Setup wizard: asks who the install is for, shows a summary and asks for confirmation; then installs the WHOLE
|
|
3
|
+
# kit (./.claude + ./CLAUDE.md); finally deletes kit/ and itself.
|
|
4
|
+
# Every install is identical — there is no frontend/backend/mobile split. Measured before it was removed: the
|
|
5
|
+
# widest profile pruning saved ~400 tokens of listing, while the split cost a per-profile e2e matrix, a second
|
|
6
|
+
# prune path in adopt.sh, and shipped a set the plugin channel never matched. Since 3.0 the backend pattern does
|
|
7
|
+
# not vary either: the stack is decided per project by the backend-architecture skill and recorded in CLAUDE.md.
|
|
8
|
+
# start.sh + kit/ must be in the SAME directory. At the project root: bash start.sh [flags]
|
|
9
|
+
set -euo pipefail
|
|
10
|
+
# The 2.x names of the variables a user can set still work (one helper: eval/lib/crew-env.sh).
|
|
11
|
+
_crew_d="${BASH_SOURCE%/*}"; [ "$_crew_d" = "${BASH_SOURCE}" ] && _crew_d=.
|
|
12
|
+
[ -f "$_crew_d/kit/eval/lib/crew-env.sh" ] && . "$_crew_d/kit/eval/lib/crew-env.sh"; unset _crew_d
|
|
13
|
+
HERE="$(CDPATH= cd "$(dirname "$0")" && pwd)"
|
|
14
|
+
|
|
15
|
+
# --version (or -v) is answered first, before the source-checkout and payload checks below: it reads only VERSION, so it
|
|
16
|
+
# must work wherever start.sh sits, including a directory those checks would refuse.
|
|
17
|
+
for a in "$@"; do
|
|
18
|
+
if [ "$a" = "--version" ] || [ "$a" = "-v" ]; then
|
|
19
|
+
if [ -f "$HERE/VERSION" ]; then head -1 "$HERE/VERSION" | tr -d '\r'; else echo unknown; fi
|
|
20
|
+
exit 0
|
|
21
|
+
fi
|
|
22
|
+
done
|
|
23
|
+
|
|
24
|
+
SRC="$HERE/kit"
|
|
25
|
+
|
|
26
|
+
if [ ! -d "$SRC" ]; then
|
|
27
|
+
echo "ERROR: 'kit/' folder not found."
|
|
28
|
+
echo "start.sh and kit/ must be in the SAME directory (both come together when you unzip)."
|
|
29
|
+
exit 1
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
# Refuse to run inside a checkout of the kit's own source repository. This script ends by deleting
|
|
33
|
+
# kit/ and itself, which is correct when the kit has been unpacked into a project and is being
|
|
34
|
+
# consumed — and destroys the source when someone invokes it by absolute path from somewhere else while
|
|
35
|
+
# developing the kit. That is not hypothetical: it removed 122 tracked files during this kit's own
|
|
36
|
+
# development, recovered only because they were committed.
|
|
37
|
+
#
|
|
38
|
+
# The kit's developer instructions already said "do not run start.sh in this repo". A rule that only holds
|
|
39
|
+
# while someone remembers it is the exact thing this kit exists to replace with a gate, so here is the gate.
|
|
40
|
+
# The three markers together appear in the source repo and in no install: an installed kit has .claude/ and
|
|
41
|
+
# CLAUDE.md, never packaging/ next to a kit/ it has not yet consumed.
|
|
42
|
+
if [ -d "$HERE/packaging" ] && [ -d "$HERE/.git" ] && [ -f "$HERE/VERSION" ]; then
|
|
43
|
+
if [ "${CREW_ALLOW_SOURCE_INSTALL:-0}" = 1 ]; then
|
|
44
|
+
echo "WARNING: CREW_ALLOW_SOURCE_INSTALL=1 — installing from Crewforth's own source checkout."
|
|
45
|
+
echo " $SRC and this script will be deleted when the install finishes."
|
|
46
|
+
else
|
|
47
|
+
echo "ERROR: this is Crewforth's own source repository, not a project to install into."
|
|
48
|
+
echo " Running here would delete $SRC and this script at the end — that is what the installer does."
|
|
49
|
+
echo " To try the installer, copy this checkout somewhere else first:"
|
|
50
|
+
echo " cp -R \"$HERE\" /tmp/kit-trial && cd /tmp/kit-trial && bash start.sh"
|
|
51
|
+
echo " Set CREW_ALLOW_SOURCE_INSTALL=1 if you really mean to consume this checkout."
|
|
52
|
+
exit 1
|
|
53
|
+
fi
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
# ---- CREW-I18N ------------------------------------------------------------------------------------------
|
|
57
|
+
# The installer speaks the user's language; the artefacts it writes do not.
|
|
58
|
+
#
|
|
59
|
+
# THE ENGLISH STRING IS THE KEY. `m 'Cancelled — nothing changed.'` looks that text up and prints the
|
|
60
|
+
# translation, or the text itself when there is none. Three things fall out of that and they are why it is
|
|
61
|
+
# written this way rather than with invented keys like `msg.cancelled`:
|
|
62
|
+
# * A missing translation cannot produce a blank line or a bare key — the fallback IS English, structurally.
|
|
63
|
+
# * The English text stays in the source, so a grep for it (the suite has two) keeps matching.
|
|
64
|
+
# * The call site says what it prints. `m msg.cancelled` does not.
|
|
65
|
+
# The cost is that editing an English string silently drops its translation and the line reverts to English.
|
|
66
|
+
# That is a regression, not a break, and it is catchable: every pattern in the table below must match a
|
|
67
|
+
# string that occurs in this file.
|
|
68
|
+
#
|
|
69
|
+
# RULES FOR THE TABLE, each one paid for:
|
|
70
|
+
# * No colour and no escape codes inside a message. Colour lives in the helpers (h1/sub/add/...), which
|
|
71
|
+
# take an already-translated string. A translator copying an ANSI sequence is a translator breaking it.
|
|
72
|
+
# * Interpolated values go through %s, never concatenation — Turkish word order and suffixes differ.
|
|
73
|
+
# * A literal percent must be written %% because the message IS the printf format. "%100 yerel" would
|
|
74
|
+
# otherwise eat an argument.
|
|
75
|
+
#
|
|
76
|
+
# NOT TRANSLATED, deliberately: the source-repo refusal above and --version. Both answer before the flags
|
|
77
|
+
# are parsed, and language selection cannot run ahead of them without putting a locale lookup in front of a
|
|
78
|
+
# gate whose whole job is to refuse. A gate that parses a locale before it can say no is a worse gate.
|
|
79
|
+
# The inherited CREW_LANG is captured before the working variable is cleared — otherwise this very line
|
|
80
|
+
# would destroy the environment setting it is meant to read.
|
|
81
|
+
CREW_LANG_ENV="${CREW_LANG:-}"
|
|
82
|
+
CREW_LANG=""
|
|
83
|
+
# `_mt` puts the result in _M with printf -v, so a call site pays no subshell: `$(m …)` is a FORK, and on Git
|
|
84
|
+
# Bash a fork is ~50 ms. Measured on Windows: the $(m …) form added 22 forks and ~1 s to one install; adopt.sh's
|
|
85
|
+
# printf -v twin removed them. `m` stays as a thin wrapper for the rare nested case that needs a value inline.
|
|
86
|
+
m() { _mt "$@"; printf '%s' "$_M"; }
|
|
87
|
+
_mt() { # $1 = English text (the key); further args fill %s; result in _M
|
|
88
|
+
# An empty key must still ASSIGN: bash 3.2's `printf -v _M ""` leaves _M holding the previous translation.
|
|
89
|
+
[ -n "${1:-}" ] || { _M=""; return 0; }
|
|
90
|
+
local s="$1"; shift
|
|
91
|
+
if [ "$CREW_LANG" = tr ]; then
|
|
92
|
+
case "$s" in
|
|
93
|
+
"Crewforth · setup wizard") s='Crewforth · kurulum' ;;
|
|
94
|
+
"stack-agnostic") s='her yığınla çalışır' ;;
|
|
95
|
+
"[1/2] Who is this install for?") s='[1/2] Kurulumu kim kullanacak?' ;;
|
|
96
|
+
"Decides whether your teammates get Crewforth's configuration — and what goes into .gitignore.") s="Crewforth ayarlarının ekiple paylaşılıp paylaşılmayacağını ve .gitignore'a nelerin ekleneceğini belirler." ;;
|
|
97
|
+
"Just me") s='Yalnızca ben' ;;
|
|
98
|
+
"private") s='kişisel' ;;
|
|
99
|
+
"The whole team") s='Tüm ekip' ;;
|
|
100
|
+
"shared") s='paylaşımlı' ;;
|
|
101
|
+
".claude/ and CLAUDE.md stay out of git — nothing appears in your teammates' checkouts") s=".claude/ ve CLAUDE.md git'e girmez — ekip arkadaşlarınız hiçbir şey görmez" ;;
|
|
102
|
+
".claude/ and CLAUDE.md are committable — everyone gets the same agents, skills and gates") s=".claude/ ve CLAUDE.md commit'lenebilir — herkes aynı ajanlarla, skill'lerle ve kapılarla çalışır" ;;
|
|
103
|
+
"internal working documents (docs/) stay private in BOTH answers") s='iç çalışma belgeleri (docs/) HER İKİ seçenekte de dışarıda kalır' ;;
|
|
104
|
+
"[2/2] Summary · see what will be installed before you confirm") s='[2/2] Özet · onaylamadan önce neyin kurulacağına bakın' ;;
|
|
105
|
+
"Security gates armed on every install:") s='Her kurulumda devreye giren güvenlik kapıları:' ;;
|
|
106
|
+
"commit/push approval gate — even in auto/bypass mode (guard-bash)") s='commit ve push için onay şartı — auto/bypass modunda bile (guard-bash)' ;;
|
|
107
|
+
"trace scan — a git hook blocks AI traces / vendor names") s="iz taraması — AI izlerini ve sağlayıcı adlarını bir git hook'u yakalar" ;;
|
|
108
|
+
"real context measurement + handoff at 75%% (Stop hook)") s="bağlam doluluğu gerçekten ölçülür, %%75'te devir önerilir (Stop hook)" ;;
|
|
109
|
+
"destructive command guard (rm -rf / force-push, etc.)") s='yıkıcı komut koruması (rm -rf, force-push vb.)' ;;
|
|
110
|
+
"Install with these settings?") s='Bu ayarlarla kurayım mı?' ;;
|
|
111
|
+
"Cancelled — nothing changed.") s='İptal edildi, hiçbir şey değişmedi.' ;;
|
|
112
|
+
"Choice") s='Seçiminiz' ;;
|
|
113
|
+
"empty=1") s='boş=1' ;;
|
|
114
|
+
"(default)") s='(varsayılan)' ;;
|
|
115
|
+
"Scope") s='Kapsam' ;;
|
|
116
|
+
"Included") s='Kurulacaklar' ;;
|
|
117
|
+
"Backend pattern") s='Mimari' ;;
|
|
118
|
+
"stack-agnostic — the stack comes from CLAUDE.md ## Stack or the repo (backend-architecture)") s="yığından bağımsız — yığın CLAUDE.md ## Stack bölümünden ya da depodan okunur (backend-architecture)" ;;
|
|
119
|
+
"The --dotnet flag is ignored: the .NET-specific path was removed in 3.0; installing the stack-agnostic setup.") s="Bayrak yok sayıldı (--dotnet): .NET'e özel kurulum yolu 3.0'da kaldırıldı; yığından bağımsız kurulum yapılıyor." ;;
|
|
120
|
+
"Will write") s='Yazılacaklar' ;;
|
|
121
|
+
"Install visibility:") s='Kurulum türü:' ;;
|
|
122
|
+
"full install") s='tam kurulum' ;;
|
|
123
|
+
"no effect:") s='etkisi yok:' ;;
|
|
124
|
+
"Installing:") s='Kuruluyor:' ;;
|
|
125
|
+
"Tip: open Claude Code and run /crew-doctor — it checks the install is wired (hooks executable, core.hooksPath set, discipline imported) and scores the project's readiness. CLAUDE.md loads the discipline every session.") s="İpucu: Claude Code'u açıp /crew-doctor çalıştırın — kurulumun eksiksiz bağlandığını denetler (hook'lar çalıştırılabilir mi, core.hooksPath ayarlı mı, disiplin import edilmiş mi) ve projenin ne kadar hazır olduğunu puanlar. Disiplin, CLAUDE.md sayesinde her oturumda yüklenir." ;;
|
|
126
|
+
"%s agents, %s skills and %s commands installed.") s="%s ajan, %s skill ve %s komut kuruldu." ;;
|
|
127
|
+
".claude/DISCIPLINE.md written — owned by Crewforth; an update overwrites it, so keep your own rules out of it.") s=".claude/DISCIPLINE.md yazıldı. Bu dosya Crewforth'a ait ve her güncellemede yeniden yazılır; kendi kurallarınızı buraya eklemeyin." ;;
|
|
128
|
+
"./CLAUDE.md created — EDIT the project section.") s='./CLAUDE.md oluşturuldu — proje bölümünü sizin DOLDURMANIZ gerekiyor.' ;;
|
|
129
|
+
"trace scan: core.hooksPath -> .claude/hooks (§4.1/§4.2 commit gate active)") s='iz taraması: core.hooksPath -> .claude/hooks (§4.1/§4.2 commit kapısı açık)' ;;
|
|
130
|
+
"Done. ./.claude + ./CLAUDE.md ready (full install); the installer files are removed.") s='Tamamlandı. ./.claude ve ./CLAUDE.md hazır (tam kurulum); kurulum dosyaları silindi.' ;;
|
|
131
|
+
"Next: 1) fill in the CLAUDE.md project section 2) open Claude Code at the repo root") s="Sıradaki adımlar: 1) CLAUDE.md'deki proje bölümünü doldurun 2) Claude Code'u deponun kökünde açın" ;;
|
|
132
|
+
"Note: if Claude Code is ALREADY running here, restart it — CLAUDE.md and the discipline load at session start.") s='Not: Claude Code bu klasörde ZATEN açıksa yeniden başlatın — CLAUDE.md ve disiplin oturum açılırken yüklenir.' ;;
|
|
133
|
+
"Panel: /crew-studio opens the Studio panel from this project (or: node .claude/studio/server/index.js --open).") s='Panel: /crew-studio komutu Studio panelini bu projeden açar (alternatif: node .claude/studio/server/index.js --open).' ;;
|
|
134
|
+
"— backend + web + mobile (RN/Expo), every agent and skill") s="— backend, web ve mobil (RN/Expo); tüm ajanlar ve skill'ler" ;;
|
|
135
|
+
"%s agents · %s skills · %s commands will be installed") s='%s ajan · %s skill · %s komut' ;;
|
|
136
|
+
"(shared: .claude/ and CLAUDE.md stay committable)") s="(paylaşımlı: .claude/ ve CLAUDE.md commit'lenebilir kalır)" ;;
|
|
137
|
+
"(default — pass --shared to commit .claude/ and CLAUDE.md)") s="(varsayılan — .claude/ ve CLAUDE.md'yi commit'lemek için --shared verin)" ;;
|
|
138
|
+
"no") s='hayır' ;;
|
|
139
|
+
"yes") s='evet' ;;
|
|
140
|
+
"[yes/no]") s='[evet/hayır]' ;;
|
|
141
|
+
"Crewforth always installs in full (all agents · all skills).") s="Crewforth her zaman eksiksiz kurulur (tüm ajanlar · tüm skill'ler)." ;;
|
|
142
|
+
"2 steps: who it is for -> summary & confirm.") s='2 adım: kim kullanacak -> özet ve onay.' ;;
|
|
143
|
+
"AGENT_TEMPLATE.md missing from the payload — /crew-skill will have nothing to read.") s='AGENT_TEMPLATE.md pakette yok — /crew-skill okuyacak bir şablon bulamayacak.' ;;
|
|
144
|
+
"./CLAUDE.md kept as-is (already imports the discipline) — the refresh landed in DISCIPLINE.md.") s="./CLAUDE.md'ye dokunulmadı (disiplini zaten import ediyor); güncelleme DISCIPLINE.md'ye yazıldı." ;;
|
|
145
|
+
"! ./CLAUDE.md carries the discipline INLINE (pre-1.1 layout) — left untouched.") s='! ./CLAUDE.md disiplini dosyanın İÇİNDE taşıyor (1.1 öncesi düzen) — dokunulmadı.' ;;
|
|
146
|
+
"Discipline updates will NOT reach it. To migrate: delete everything above your") s='Disiplin güncellemeleri bu dosyaya ULAŞMAZ. Geçiş için şu başlığın üstündeki her şeyi silin:' ;;
|
|
147
|
+
"%s heading and leave this single line in its place:") s='%s — sildiğiniz yere de yalnızca şu satırı koyun:' ;;
|
|
148
|
+
"./CLAUDE.md existed — prepended the discipline @import; your content is untouched.") s='./CLAUDE.md zaten vardı — en başa disiplinin @import satırı eklendi, içeriğinize dokunulmadı.' ;;
|
|
149
|
+
"%s eol pin(s) so shared hooks stay LF") s="%s eol kuralı eklendi; paylaşılan hook'lar LF olarak kalır" ;;
|
|
150
|
+
"NOTE: no git repository at this level; after %s run: %s") s='NOT: bu klasörde git deposu yok. %s yaptıktan sonra şunu çalıştırın: %s' ;;
|
|
151
|
+
"Panel: needs Node 18+, which is not on this machine — but that is no longer a dead end.") s='Panel: Node 18+ gerekiyor ve bu makinede yok — ama bunun da bir çözümü var.' ;;
|
|
152
|
+
"Crewforth fetches one for the panel: %s (asks first;") s='Crewforth, panel için Node indirebilir: %s (önce sorar;' ;;
|
|
153
|
+
"verified against the published checksum, into %s, nothing else touched).") s='yayımlanan checksum ile doğrular, yalnızca %s içine kurar, başka hiçbir şeye dokunmaz).' ;;
|
|
154
|
+
"Every gate still holds meanwhile; the panel is the only part that needs node.") s="Bu arada tüm kapılar çalışmaya devam eder; Node'a yalnızca panel ihtiyaç duyar." ;;
|
|
155
|
+
"ERROR: the %s sentinel line is missing from %s — refusing to guess the discipline/project split.") s='HATA: %s işaret satırı %s içinde bulunamadı — disiplinin nerede bitip proje bölümünün nerede başladığı tahmin edilmeyecek.' ;;
|
|
156
|
+
"Unknown parameter: %s") s='Bilinmeyen parametre: %s' ;;
|
|
157
|
+
".gitignore") ;; # identifier, printed as is
|
|
158
|
+
# No row: the line prints in English. CREW_I18N_MISS (set by e2e case 18) collects every such key, so a
|
|
159
|
+
# missing translation is caught by NAME rather than guessed from which English words it happens to contain.
|
|
160
|
+
*) [ -n "${CREW_I18N_MISS:-}" ] && printf '%s\n' "$s" >> "$CREW_I18N_MISS" ;;
|
|
161
|
+
esac
|
|
162
|
+
fi
|
|
163
|
+
# shellcheck disable=SC2059
|
|
164
|
+
# `--` ends option parsing: without it ANY format starting with '-' ("--dotnet …", "- x") is read as an option
|
|
165
|
+
# and printf exits 2 — measured on bash 3.2 (macOS) and Git Bash 5.3; with it both assign normally.
|
|
166
|
+
printf -v _M -- "$s" "$@"
|
|
167
|
+
}
|
|
168
|
+
# ---- /CREW-I18N -----------------------------------------------------------------------------------------
|
|
169
|
+
|
|
170
|
+
usage() {
|
|
171
|
+
# A heredoc cannot go through m() line by line without breaking its layout, so the Turkish help is its own
|
|
172
|
+
# block. Flags and commands are identical in both; only the prose differs.
|
|
173
|
+
if [ "${CREW_LANG:-}" = tr ]; then
|
|
174
|
+
cat <<'USAGE_TR'
|
|
175
|
+
Kullanım: bash start.sh [SEÇENEKLER]
|
|
176
|
+
Seçenek vermezseniz kurulum sihirbazı her şeyi adım adım sorar.
|
|
177
|
+
|
|
178
|
+
Backend her zaman yığından bağımsızdır: yığın projeye göre backend-architecture skill'iyle seçilir ve
|
|
179
|
+
CLAUDE.md'deki ## Stack bölümüne yazılır.
|
|
180
|
+
--dotnet 3.0'da kaldırıldı; kabul edilir, uyarı basar ve yığından bağımsız kurulumu yapar
|
|
181
|
+
--generic kabul edilir, etkisi yok (artık tek kurulum biçimi bu)
|
|
182
|
+
|
|
183
|
+
Crewforth her zaman eksiksiz kurulur: tüm ajanlar ve skill'ler — backend, web ve mobil (RN/Expo).
|
|
184
|
+
--backend | --frontend | --mobile | --fullstack hâlâ kabul edilir ama etkisi yok (eski komutlar bozulmasın diye)
|
|
185
|
+
--private | --shared kurulum yalnızca sizin mi, yoksa ekiple paylaşılıp commit'lenecek mi? (varsayılan: private)
|
|
186
|
+
--lang tr|en kurulum dili (verilmezse sihirbaz sorar)
|
|
187
|
+
--yes, -y tüm sorulara evet de (gözetimsiz kurulum)
|
|
188
|
+
--version, -v Crewforth sürümünü yazdırıp çık
|
|
189
|
+
USAGE_TR
|
|
190
|
+
return
|
|
191
|
+
fi
|
|
192
|
+
cat <<'USAGE'
|
|
193
|
+
Usage: bash start.sh [OPTIONS]
|
|
194
|
+
If no flag is given, the script asks interactively (wizard).
|
|
195
|
+
The backend is always stack-agnostic: the stack is chosen per project by the backend-architecture skill and
|
|
196
|
+
recorded in the ## Stack section of CLAUDE.md.
|
|
197
|
+
--dotnet removed in 3.0; accepted, prints a warning and installs the stack-agnostic setup
|
|
198
|
+
--generic accepted, no effect (this is the only install shape now)
|
|
199
|
+
|
|
200
|
+
Every install ships everything: all agents, all skills — backend, web and mobile (RN/Expo) together.
|
|
201
|
+
--backend | --frontend | --mobile | --fullstack accepted, no effect (kept so older commands still run)
|
|
202
|
+
--private | --shared is the install yours alone, or committed for the team? (default: private)
|
|
203
|
+
--lang tr|en installer language (asked interactively when not given)
|
|
204
|
+
--yes, -y answer every question with yes (unattended install)
|
|
205
|
+
--version, -v print the Crewforth version and exit
|
|
206
|
+
USAGE
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
# Read one answer without hanging an unattended run — and be honest about which hang this closes.
|
|
210
|
+
#
|
|
211
|
+
# Four stdin shapes, measured (bare `read`, perl alarm as the clock, each shape calibrated first):
|
|
212
|
+
# a terminal with a human ....... blocks until they type <- correct, must not change
|
|
213
|
+
# a terminal with NOBODY ........ blocks forever <- the Claude Code pty case
|
|
214
|
+
# a pipe carrying data .......... returns at once
|
|
215
|
+
# a pipe at EOF (</dev/null) .... returns at once, answer ""
|
|
216
|
+
# a pipe open and empty ......... blocks forever <- closed by the timeout below
|
|
217
|
+
#
|
|
218
|
+
# The timeout applies ONLY when stdin is not a terminal. On a terminal a human may take as long as they
|
|
219
|
+
# like, and cutting them off would be a worse bug than the one being fixed.
|
|
220
|
+
#
|
|
221
|
+
# WHAT THIS DOES NOT FIX, stated plainly because the opposite was nearly written here: a pty IS a terminal,
|
|
222
|
+
# so `[ -t 0 ]` is TRUE for it and this takes the blocking branch. Measured under expect: `[ -t 0 ]` says
|
|
223
|
+
# yes and the read blocks with no input. An unattended run under a pty — which is what Claude Code creates —
|
|
224
|
+
# is therefore still answered by --yes and by nothing else. That is not a gap in the timeout; stdin carries
|
|
225
|
+
# no signal that distinguishes "a terminal nobody is watching" from "a terminal with a slow typist".
|
|
226
|
+
#
|
|
227
|
+
# 10 seconds rather than 5: the cost of being too short is a declined install, which is visible and
|
|
228
|
+
# recoverable, but a producer that legitimately takes a moment to write the answer should still win. The
|
|
229
|
+
# value is an integer because bash 3.2 rejects a fractional -t ("invalid timeout specification", measured).
|
|
230
|
+
crew_read() { # $1 = name of the variable to set
|
|
231
|
+
local __v="$1" __a=""
|
|
232
|
+
if [ -t 0 ]; then read -r __a || __a=""
|
|
233
|
+
else read -t 10 -r __a || __a=""
|
|
234
|
+
fi
|
|
235
|
+
eval "$__v=\$__a"
|
|
236
|
+
}
|
|
237
|
+
ask_yes() { # $1 = question; returns 0 if the user says 'yes'
|
|
238
|
+
local a
|
|
239
|
+
# --yes ALWAYS wins, and it is answered BEFORE stdin is touched at all. Claude Code runs the installer
|
|
240
|
+
# under a pty, so stdin IS a terminal there and a bare `read` blocks forever on input nobody will type:
|
|
241
|
+
# testing the terminal first would ignore a --yes that was passed precisely to avoid that. adopt.sh:50
|
|
242
|
+
# carries the same rule for the same reason, and this script was the one place that never learned it.
|
|
243
|
+
if [ "${ASSUME_YES:-0}" = 1 ]; then _mt 'yes'; printf '%s %s %s(--yes)%s\n' "$1" "$_M" "$D" "$R"; return 0; fi
|
|
244
|
+
# Deliberately NOT adopt.sh's `[ -t 0 ]` shape. adopt.sh declines outright when stdin is not a terminal;
|
|
245
|
+
# here `printf 'yes\n' | bash start.sh` is the documented CI form (see the note at the confirm prompt) and
|
|
246
|
+
# that shape would silently turn every piped install into a cancellation. A pipe reaching EOF already
|
|
247
|
+
# answers "" => no, so the unattended case stays safe without special-casing it.
|
|
248
|
+
_mt '[yes/no]'; printf '%s %s: ' "$1" "$_M"
|
|
249
|
+
crew_read a
|
|
250
|
+
case "$a" in [yY]|[yY][eE][sS]|[eE]|[eE][vV][eE][tT]) return 0 ;; *) return 1 ;; esac
|
|
251
|
+
}
|
|
252
|
+
# Append entries to .gitignore. Three callers had three copies of the same two bugs (start.sh's four-entry
|
|
253
|
+
# loop, and adopt.sh's review-pass.json line), so it lives here and adopt.sh carries the twin.
|
|
254
|
+
# * A file whose last line has NO trailing newline concatenates the first appended entry onto it —
|
|
255
|
+
# `node_modules` + `.claude/` becomes `node_modules.claude/`, ignoring neither. `touch` does not help:
|
|
256
|
+
# it changes the timestamp, not the last byte. So read the last byte and add the newline ourselves.
|
|
257
|
+
# * `grep -qxF` is an exact-literal test, so a repo that already ignores `.claude` (no trailing slash)
|
|
258
|
+
# gets a second, redundant line. Ask git the real question instead — `git check-ignore` answers about
|
|
259
|
+
# the PATH, whatever spelling the existing rule uses. It is only asked inside a repo; outside one we
|
|
260
|
+
# fall back to the literal test, which is all that is knowable there.
|
|
261
|
+
gi_add() { # $@ = entries to ensure in ./.gitignore; prints nothing, sets GI_WROTE to what it added
|
|
262
|
+
local e
|
|
263
|
+
GI_WROTE=""
|
|
264
|
+
[ -e .gitignore ] || : > .gitignore
|
|
265
|
+
for e in "$@"; do
|
|
266
|
+
if git rev-parse --git-dir >/dev/null 2>&1; then
|
|
267
|
+
git check-ignore -q --no-index "$e" 2>/dev/null && continue # --no-index: a dir holding tracked files (docs/HANDOVER.md) still counts as ignored
|
|
268
|
+
else
|
|
269
|
+
grep -qxF "$e" .gitignore 2>/dev/null && continue
|
|
270
|
+
fi
|
|
271
|
+
# last byte is not a newline (and the file is not empty) -> close the line first
|
|
272
|
+
if [ -s .gitignore ] && [ "$(tail -c 1 .gitignore | od -An -tx1 | tr -d ' \n')" != "0a" ]; then
|
|
273
|
+
printf '\n' >> .gitignore
|
|
274
|
+
fi
|
|
275
|
+
printf '%s\n' "$e" >> .gitignore
|
|
276
|
+
GI_WROTE="$GI_WROTE $e"
|
|
277
|
+
done
|
|
278
|
+
GI_WROTE="${GI_WROTE# }"
|
|
279
|
+
}
|
|
280
|
+
# Append eol pins to .gitattributes. ONLY in shared mode, and only for what cannot defend itself.
|
|
281
|
+
#
|
|
282
|
+
# MEASURED, not inferred — the ROADMAP called this an inference. A bare repo, a project that TRACKS .claude/,
|
|
283
|
+
# and a second clone with core.autocrlf=true (a git setting, so the mechanism reproduces anywhere):
|
|
284
|
+
# the committed blob 0 CR
|
|
285
|
+
# the working tree after that checkout 1345 CR in guard-bash.sh · 575 in pre-commit · 49 in commit-msg
|
|
286
|
+
# Reproduced on a REAL Windows machine, both installers, all three autocrlf settings, with the pin and without
|
|
287
|
+
# it — the numbers above are that run's, not the simulation's.
|
|
288
|
+
# WHO THE VICTIM IS, corrected after a real Windows measurement, because the obvious answer is wrong.
|
|
289
|
+
# A CRLF hook does NOT die on Git Bash: the 1345-CR copy from an unpinned clone was run against a destructive
|
|
290
|
+
# payload through the real invocation and answered exactly like the LF copy — rc=2, same GUARD line. The kit's
|
|
291
|
+
# own .gitattributes already records this ("Git Bash happens to tolerate that … but WSL does not"). The death
|
|
292
|
+
# shapes below are real but were measured on macOS bash, and macOS never receives CRLF from autocrlf=true in
|
|
293
|
+
# the first place, since that is a Windows default:
|
|
294
|
+
# ./hook -> env: bash\r: No such file or directory
|
|
295
|
+
# bash hook -> syntax error: unexpected end of file
|
|
296
|
+
# case … in\r -> syntax error near unexpected token `newline`
|
|
297
|
+
# f(){\r -> syntax error near unexpected token `{`
|
|
298
|
+
# So what the pin protects is a NON-MSYS bash reading that same Windows working tree — WSL is the documented
|
|
299
|
+
# case, and it is UNMEASURED by either of us: `wsl.exe` resolves on the Windows desk with no distro installed,
|
|
300
|
+
# which is the Store-python3 shape and not evidence. What IS measured is that the pin removes a difference
|
|
301
|
+
# nobody should have to reason about: with it the working tree matches the blob on every autocrlf setting.
|
|
302
|
+
#
|
|
303
|
+
# AND IT PROTECTS AGAINST EXACTLY ONE SETTING. Unpinned, `core.autocrlf=input` and `=false` already come back
|
|
304
|
+
# with 0 CR; only `true` corrupts. That one is the Git for Windows default and it arrives from the SYSTEM
|
|
305
|
+
# config, not the global one — so the person the pin is for is the person who changed nothing.
|
|
306
|
+
#
|
|
307
|
+
# WHY ONLY THE SCRIPTS. The data files the hooks read line by line (blocklists, profiles.conf) already strip a
|
|
308
|
+
# trailing CR themselves — the kit's own .gitattributes says so, and calls that strip "the real defence" for
|
|
309
|
+
# exactly this case, a user's project where nothing pins anything. A shell script cannot do that: it cannot
|
|
310
|
+
# strip its own carriage returns before bash parses it. So the pins below are the scripts plus, belt and
|
|
311
|
+
# braces, the data types that are cheap to include; they are deliberately NOT a blanket `.claude/**`, which
|
|
312
|
+
# would apply text conversion to any binary a skill might carry.
|
|
313
|
+
#
|
|
314
|
+
# WHY ONLY SHARED MODE. In the private install .claude/ is gitignored, so git never checks it out and there is
|
|
315
|
+
# nothing to convert. Writing repo-wide attributes for someone who did not share their config would be editing
|
|
316
|
+
# a file they own to fix a problem they do not have.
|
|
317
|
+
#
|
|
318
|
+
# The two lessons gi_add carries apply here too — a missing trailing newline concatenates the first entry onto
|
|
319
|
+
# the last line, and the real question is not "is this text in the file" but "does git already answer lf for
|
|
320
|
+
# this path". `git check-attr` is the equivalent of gi_add's `git check-ignore`: it answers about the PATH,
|
|
321
|
+
# whatever pattern spelling an existing rule uses, so a project that already pins `* text eol=lf` gets nothing.
|
|
322
|
+
GA_LINES='.claude/**/*.sh text eol=lf
|
|
323
|
+
.claude/hooks/pre-commit text eol=lf
|
|
324
|
+
.claude/hooks/commit-msg text eol=lf
|
|
325
|
+
.claude/**/*.txt text eol=lf
|
|
326
|
+
.claude/**/*.conf text eol=lf'
|
|
327
|
+
ga_add() { # no args; prints nothing, sets GA_WROTE to the number of lines added
|
|
328
|
+
local line
|
|
329
|
+
GA_WROTE=0
|
|
330
|
+
if git rev-parse --git-dir >/dev/null 2>&1; then
|
|
331
|
+
case "$(git check-attr eol -- .claude/hooks/guard-bash.sh 2>/dev/null)" in
|
|
332
|
+
*": lf") return 0 ;;
|
|
333
|
+
esac
|
|
334
|
+
fi
|
|
335
|
+
[ -e .gitattributes ] || : > .gitattributes
|
|
336
|
+
printf '%s\n' "$GA_LINES" | while IFS= read -r line; do
|
|
337
|
+
[ -n "$line" ] || continue
|
|
338
|
+
if grep -qxF "$line" .gitattributes 2>/dev/null; then continue; fi
|
|
339
|
+
if [ -s .gitattributes ] && [ "$(tail -c 1 .gitattributes | od -An -tx1 | tr -d ' \n')" != "0a" ]; then
|
|
340
|
+
printf '\n' >> .gitattributes
|
|
341
|
+
fi
|
|
342
|
+
printf '%s\n' "$line" >> .gitattributes
|
|
343
|
+
done
|
|
344
|
+
# The loop above runs in a subshell (it is the right-hand side of a pipe), so it cannot report back through a
|
|
345
|
+
# variable — the same trap this kit spent a night on. Count the result from the FILE instead.
|
|
346
|
+
GA_WROTE="$(printf '%s\n' "$GA_LINES" | while IFS= read -r line; do
|
|
347
|
+
[ -n "$line" ] && grep -qxF "$line" .gitattributes 2>/dev/null && echo x
|
|
348
|
+
done | wc -l | tr -d ' ')"
|
|
349
|
+
}
|
|
350
|
+
# --- CLAUDE.md split (shared contract with adopt.sh) ---
|
|
351
|
+
# The payload CLAUDE.md carries the kit discipline, then a one-line sentinel, then the project template.
|
|
352
|
+
# The discipline half is installed as .claude/DISCIPLINE.md (kit-owned, overwritten on every update) and
|
|
353
|
+
# the project half becomes ./CLAUDE.md (yours, written once). A single @import line joins them.
|
|
354
|
+
IMPORT_LINE='@.claude/DISCIPLINE.md'
|
|
355
|
+
# The sentinel is matched ANCHORED to the start of the line, so prose that merely mentions the token
|
|
356
|
+
# (in this comment, in the docs, in the discipline text itself) can never be mistaken for the split point.
|
|
357
|
+
# Abort loudly if it is gone: a silent miss would ship the ENTIRE template as "discipline" — exactly how the
|
|
358
|
+
# old '<PROJE ADI>' marker failed once the payload was translated to English.
|
|
359
|
+
kit_require_sentinel() { grep -qE '^<!-- KIT:DISCIPLINE-END' "$1" || { printf '%s\n' "$(m 'ERROR: the %s sentinel line is missing from %s — refusing to guess the discipline/project split.' "'<!-- KIT:DISCIPLINE-END'" "$1")"; exit 1; }; }
|
|
360
|
+
kit_discipline_of() { awk '/^<!-- KIT:DISCIPLINE-END/{exit} {print}' "$1"; }
|
|
361
|
+
kit_project_of() { awk 'f{print} /^<!-- KIT:DISCIPLINE-END/{f=1}' "$1"; }
|
|
362
|
+
# Anchored: the import must BE the line, not merely be mentioned in prose (the discipline text names the path).
|
|
363
|
+
kit_has_import() { grep -qE '^[[:space:]]*@\.claude/DISCIPLINE\.md[[:space:]]*$' "$1" 2>/dev/null; }
|
|
364
|
+
# A pre-1.1 install pasted the whole discipline inline into CLAUDE.md; adding the @import would load it twice.
|
|
365
|
+
# Both markers are required: a project that happens to write its own "Four working principles" heading is NOT a
|
|
366
|
+
# legacy kit install, and treating it as one would leave it without the discipline forever.
|
|
367
|
+
kit_claude_md_is_legacy() {
|
|
368
|
+
grep -q '^## Four working principles' "$1" 2>/dev/null && grep -qE '^### 4\.[45] ' "$1" 2>/dev/null
|
|
369
|
+
}
|
|
370
|
+
# --- Flag parsing (silent/CI mode) ---
|
|
371
|
+
# The profile flags are ACCEPTED and ignored rather than rejected: they appear in older READMEs, CI steps and
|
|
372
|
+
# copy-pasted commands, and erroring out there breaks a pipeline over a flag whose absence changes nothing.
|
|
373
|
+
# A one-line notice is printed after the colour helpers load, so the user learns the flag no longer selects
|
|
374
|
+
# anything instead of quietly getting a different set than the one they typed.
|
|
375
|
+
# Language is resolved BEFORE any other flag, because every message below it goes through m(). Four
|
|
376
|
+
# sources, first answer wins: --lang, then CREW_LANG, then the locale variables, then English.
|
|
377
|
+
#
|
|
378
|
+
# English is the default rather than the locale's language on purpose: that is what this installer printed
|
|
379
|
+
# before it could speak anything else, and a default that changes under people is not a default. The locale
|
|
380
|
+
# branch only ever ADDS Turkish for someone whose environment already says Turkish.
|
|
381
|
+
#
|
|
382
|
+
# Measured, and recorded here rather than treated as a defect: on stock Windows LANG, LC_ALL and
|
|
383
|
+
# LC_MESSAGES are ALL empty (Git Bash defaults only LC_CTYPE). So auto-detect never fires there, and a
|
|
384
|
+
# Turkish-speaking Windows user lands on English unless they pass --lang tr or export CREW_LANG.
|
|
385
|
+
#
|
|
386
|
+
# AN INTERACTIVE INSTALL ASKS. Detection alone was not enough: on macOS the system language can be Turkish
|
|
387
|
+
# while the shell exports LANG=C.UTF-8 (measured on a Turkish desk), so the locale said English and the user
|
|
388
|
+
# never saw a choice. So when a human is at the terminal and nothing named a language (no --lang, no
|
|
389
|
+
# CREW_LANG), the first thing printed is a two-line menu, and the locale only decides which entry is the
|
|
390
|
+
# default. It is skipped under --yes and whenever stdin is not a terminal — for the same reason the
|
|
391
|
+
# visibility question is: every piped caller feeds a fixed answer sequence, and one more read would shift it.
|
|
392
|
+
_lang_flag=""; _lang_take=0; _lang_yes=0
|
|
393
|
+
for a in "$@"; do
|
|
394
|
+
# `--lang` with no value must not swallow the next flag: `--lang --yes` is --yes with no language given.
|
|
395
|
+
if [ "$_lang_take" = 1 ]; then _lang_take=0; case "$a" in -*) ;; *) _lang_flag="$a"; continue ;; esac; fi
|
|
396
|
+
case "$a" in
|
|
397
|
+
--lang=*) _lang_flag="${a#--lang=}" ;;
|
|
398
|
+
--lang) _lang_take=1 ;;
|
|
399
|
+
--yes|-y) _lang_yes=1 ;;
|
|
400
|
+
-h|--help|-v|--version) _lang_yes=1 ;; # help and version answer at once, not after a language menu
|
|
401
|
+
esac
|
|
402
|
+
done
|
|
403
|
+
_loc="${LC_ALL:-}"; [ -n "$_loc" ] || _loc="${LC_MESSAGES:-}"; [ -n "$_loc" ] || _loc="${LANG:-}"
|
|
404
|
+
case "$_loc" in tr*|TR*) _lang_detected=tr ;; *) _lang_detected=en ;; esac
|
|
405
|
+
if [ -n "$_lang_flag" ]; then
|
|
406
|
+
CREW_LANG="$_lang_flag"
|
|
407
|
+
elif [ -n "${CREW_LANG_ENV:-}" ]; then
|
|
408
|
+
CREW_LANG="$CREW_LANG_ENV"
|
|
409
|
+
elif [ -t 0 ] && [ "$_lang_yes" = 0 ]; then
|
|
410
|
+
if [ "$_lang_detected" = tr ]; then _lang_def=2; else _lang_def=1; fi
|
|
411
|
+
printf '\n Language / Dil\n 1) English\n 2) Türkçe\n -> [1-2, empty/boş=%s]: ' "$_lang_def"
|
|
412
|
+
crew_read _lang_ans
|
|
413
|
+
case "${_lang_ans:-$_lang_def}" in
|
|
414
|
+
2|tr|TR|t|T|[tT]ürkçe|[tT]urkce|[tT]urkish) CREW_LANG=tr ;;
|
|
415
|
+
*) CREW_LANG=en ;;
|
|
416
|
+
esac
|
|
417
|
+
else
|
|
418
|
+
CREW_LANG="$_lang_detected"; _LANG_GUESSED=1
|
|
419
|
+
fi
|
|
420
|
+
# Anything that is not a language we actually carry falls back to English rather than printing keys.
|
|
421
|
+
case "$CREW_LANG" in tr|en) ;; *) CREW_LANG=en ;; esac
|
|
422
|
+
# Exported because eval/preflight.sh runs as a child and resolves its own language from the environment: a
|
|
423
|
+
# choice made by --lang or the menu above stayed in this shell, and the preflight block printed English.
|
|
424
|
+
export CREW_LANG
|
|
425
|
+
|
|
426
|
+
STACK="generic"; DOTNET_FLAG=0; LEGACY_FLAGS=""; ASSUME_YES=0; VISIBILITY=""
|
|
427
|
+
for a in "$@"; do
|
|
428
|
+
case "$a" in
|
|
429
|
+
--lang) ;; # value consumed in the language pass above
|
|
430
|
+
--lang=*) ;;
|
|
431
|
+
tr|en) ;; # the value of a separated --lang
|
|
432
|
+
--backend|--frontend|--mobile|--fullstack) LEGACY_FLAGS="$LEGACY_FLAGS $a" ;;
|
|
433
|
+
--dotnet) DOTNET_FLAG=1 ;; # removed in 3.0 — warned below, never an error (old scripts keep running)
|
|
434
|
+
--generic) ;; # the only shape now; accepted silently
|
|
435
|
+
--yes|-y) ASSUME_YES=1 ;;
|
|
436
|
+
--private) VISIBILITY="private" ;;
|
|
437
|
+
--shared) VISIBILITY="shared" ;;
|
|
438
|
+
-h|--help) usage; exit 0 ;;
|
|
439
|
+
*) printf '%s\n' "$(m 'Unknown parameter: %s' "$a")"; echo; usage; exit 1 ;;
|
|
440
|
+
esac
|
|
441
|
+
done
|
|
442
|
+
|
|
443
|
+
# ===================== COLOR / STYLE HELPERS =====================
|
|
444
|
+
# Color is emitted ONLY on an interactive TTY + TERM!=dumb + NO_COLOR empty.
|
|
445
|
+
# Otherwise all codes are '' => raw \033 does NOT leak in CI/pipe/dumb (NO_COLOR is respected).
|
|
446
|
+
if [ -t 1 ] && [ "${TERM:-dumb}" != "dumb" ] && [ -z "${NO_COLOR:-}" ]; then
|
|
447
|
+
R=$'\033[0m'; B=$'\033[1m'; D=$'\033[2m'
|
|
448
|
+
CY=$'\033[36m'; GR=$'\033[32m'; YE=$'\033[33m'; MG=$'\033[35m'
|
|
449
|
+
else
|
|
450
|
+
R=''; B=''; D=''; CY=''; GR=''; YE=''; MG=''
|
|
451
|
+
fi
|
|
452
|
+
# printf's %-Ns pads by BYTES, so a Turkish label (İ, ç, ı are two bytes each) came out short and knocked
|
|
453
|
+
# its column out of line. Pad by characters instead: drop the UTF-8 continuation bytes under the C locale
|
|
454
|
+
# and count what is left. Builtins only — no fork per row.
|
|
455
|
+
padr() { # $1 = text, $2 = width; sets PADDED
|
|
456
|
+
local LC_ALL=C n
|
|
457
|
+
n="${1//[$'\200'-$'\277']/}"; n=$(( $2 - ${#n} ))
|
|
458
|
+
PADDED="$1"
|
|
459
|
+
while [ "$n" -gt 0 ]; do PADDED="$PADDED "; n=$((n-1)); done
|
|
460
|
+
}
|
|
461
|
+
# The helpers below take the ENGLISH key and translate it themselves, so no call site needs a $(m …) fork.
|
|
462
|
+
h1() { _mt "$@"; printf '\n%s%s%s%s\n' "$B" "$CY" "$_M" "$R"; } # section heading
|
|
463
|
+
sub() { _mt "$@"; printf '%s%s%s\n' "$D" "$_M" "$R"; } # dim description
|
|
464
|
+
opt() { # $1=no $2=label $3=is_default $4=right-badge
|
|
465
|
+
local mark='' lbl; [ "${3:-0}" = 1 ] && { _mt '(default)'; mark=" ${GR}${B}${_M}${R}"; }
|
|
466
|
+
_mt "${4:-}"; local badge="$_M"; _mt "$2"; lbl="$_M"
|
|
467
|
+
padr "$lbl" 24
|
|
468
|
+
printf ' %s%s%s)%s %s%s%s %s%s%s%s\n' "$B" "$YE" "$1" "$R" "$B" "$PADDED" "$R" "$MG" "$badge" "$R" "$mark"
|
|
469
|
+
}
|
|
470
|
+
add() { _mt "$@"; printf ' %s+%s %s\n' "$GR" "$R" "$_M"; } # INSTALLED
|
|
471
|
+
skip() { _mt "$@"; printf ' %s-%s %s%s%s\n' "$YE" "$R" "$D" "$_M" "$R"; } # NOT INSTALLED (tradeoff)
|
|
472
|
+
gate() { _mt "$@"; printf ' %s>%s %s\n' "$CY" "$R" "$_M"; } # gate to be armed
|
|
473
|
+
row() { _mt "$1"; padr "$_M" 15; printf ' %s%s%s %s\n' "$B" "$PADDED" "$R" "$2"; } # summary row; $1 = key
|
|
474
|
+
rule() { printf ' %s------------------------------------------------%s\n' "$D" "$R"; }
|
|
475
|
+
|
|
476
|
+
h1 'Crewforth · setup wizard'
|
|
477
|
+
sub '2 steps: who it is for -> summary & confirm.'
|
|
478
|
+
if [ -n "$LEGACY_FLAGS" ]; then
|
|
479
|
+
_mt 'no effect:'; _a="$_M"; _mt 'Crewforth always installs in full (all agents · all skills).'
|
|
480
|
+
printf '\n %s!%s%s %s%s %s\n' "$YE" "$R" "$B$LEGACY_FLAGS" "$_a" "$R" "$_M"
|
|
481
|
+
fi
|
|
482
|
+
|
|
483
|
+
# --dotnet used to pick a .NET-only install (a pattern skill, a backend agent variant, and a base project
|
|
484
|
+
# cloned from the network). 3.0 removed that path: every install is the same stack-agnostic kit, and the
|
|
485
|
+
# backend-architecture skill decides the stack per project. The flag stays ACCEPTED so an old script or README
|
|
486
|
+
# command keeps working — but it says what it no longer does, rather than silently meaning something else.
|
|
487
|
+
if [ "$DOTNET_FLAG" = 1 ]; then
|
|
488
|
+
# This key once started with '--' and `printf -v _M "$s"` read it as an option (rc=2, nothing installed); _mt
|
|
489
|
+
# now passes `--`, so a leading '-' is safe — the wording stays as it is.
|
|
490
|
+
_mt 'The --dotnet flag is ignored: the .NET-specific path was removed in 3.0; installing the stack-agnostic setup.'
|
|
491
|
+
printf '\n %s!%s %s\n' "$YE" "$R" "$_M"
|
|
492
|
+
fi
|
|
493
|
+
|
|
494
|
+
# ===================== STEP 1 · WHO IS THIS INSTALL FOR =====================
|
|
495
|
+
# ONE question about intent, not four about paths. Until now the installer wrote four .gitignore entries
|
|
496
|
+
# unconditionally and the summary never mentioned .gitignore at all — so "confirm the install" silently
|
|
497
|
+
# edited a TRACKED file, which is a change nobody agreed to. The answer decides two of the four entries;
|
|
498
|
+
# the summary below lists the exact lines either way.
|
|
499
|
+
#
|
|
500
|
+
# Two entries are NOT part of the question, and both are guarantees rather than preferences:
|
|
501
|
+
# * .private-terms.txt is the list of strings that must never be published (internal project names,
|
|
502
|
+
# client names, hosts). Publishing the list defeats its purpose, so it is ignored in both answers.
|
|
503
|
+
# * docs/ holds internal working documents — PLAN.md, SESSION_STATE.md, THREAT_MODEL.md,
|
|
504
|
+
# SECURITY_FINDINGS.md, DISCOVERY.md, EVAL.md. §4.3 promises they stay private and README.md says so
|
|
505
|
+
# too; a team that shares its kit config has not asked to publish its threat model.
|
|
506
|
+
#
|
|
507
|
+
# ASKED ONLY WHEN SOMEONE IS THERE TO ANSWER. A new prompt consumes a line of stdin, and every existing
|
|
508
|
+
# non-interactive caller feeds a FIXED sequence — `printf 'yes\n' | bash start.sh --generic` is the form in
|
|
509
|
+
# this repo's own e2e and in user scripts. Adding a read shifts that sequence by one: the visibility question
|
|
510
|
+
# ate the 'yes', the confirm prompt hit EOF, and the install silently CANCELLED. Measured: e2e went from
|
|
511
|
+
# green to rc=127 because the installed tree never existed. So a non-interactive run keeps today's behaviour
|
|
512
|
+
# (private) without reading anything, and --private/--shared are how a script chooses instead.
|
|
513
|
+
if [ -z "$VISIBILITY" ] && { [ ! -t 0 ] || [ "$ASSUME_YES" = 1 ]; }; then
|
|
514
|
+
VISIBILITY="private"
|
|
515
|
+
_mt 'Install visibility:'; _a="$_M"; _mt 'private'; _b="$_M"; _mt '(default — pass --shared to commit .claude/ and CLAUDE.md)'
|
|
516
|
+
printf ' %s%s%s %s %s%s%s\n' "$B" "$_a" "$R" "$_b" "$D" "$_M" "$R"
|
|
517
|
+
fi
|
|
518
|
+
if [ -z "$VISIBILITY" ]; then
|
|
519
|
+
h1 '[1/2] Who is this install for?'
|
|
520
|
+
sub "Decides whether your teammates get Crewforth's configuration — and what goes into .gitignore."
|
|
521
|
+
echo
|
|
522
|
+
opt 1 'Just me' 1 'private'
|
|
523
|
+
add ".claude/ and CLAUDE.md stay out of git — nothing appears in your teammates' checkouts"
|
|
524
|
+
echo
|
|
525
|
+
opt 2 'The whole team' 0 'shared'
|
|
526
|
+
add '.claude/ and CLAUDE.md are committable — everyone gets the same agents, skills and gates'
|
|
527
|
+
skip 'internal working documents (docs/) stay private in BOTH answers'
|
|
528
|
+
echo
|
|
529
|
+
_mt 'Choice'; _a="$_M"; _mt 'empty=1'
|
|
530
|
+
printf ' %s->%s %s %s[1-2, %s]%s: ' "$CY" "$R" "$_a" "$D" "$_M" "$R"
|
|
531
|
+
crew_read s # empty => default (private = today's behaviour)
|
|
532
|
+
case "$s" in 2) VISIBILITY="shared" ;; *) VISIBILITY="private" ;; esac
|
|
533
|
+
fi
|
|
534
|
+
# The exact lines this install will append, resolved once so the summary and the writer cannot disagree.
|
|
535
|
+
if [ "$VISIBILITY" = "shared" ]; then
|
|
536
|
+
GI_PLAN='docs/ .private-terms.txt .claude/.state/' # .state is runtime state (update cache, snapshot), never shared
|
|
537
|
+
else
|
|
538
|
+
GI_PLAN='docs/ .claude/ CLAUDE.md .private-terms.txt'
|
|
539
|
+
fi
|
|
540
|
+
|
|
541
|
+
# ===================== STEP 2 · SUMMARY + CONFIRM =====================
|
|
542
|
+
# Count the agents/skills to install LIVE FROM SOURCE (not a hardcoded constant; self-corrects if the payload changes).
|
|
543
|
+
count_installed() { # $1=glob -> count to install
|
|
544
|
+
local n=0
|
|
545
|
+
for p in $1; do
|
|
546
|
+
[ -e "$p" ] && n=$((n+1))
|
|
547
|
+
done
|
|
548
|
+
printf '%s' "$n"
|
|
549
|
+
}
|
|
550
|
+
N_AG="$(count_installed "$SRC/agents/*.md")"
|
|
551
|
+
# Skills and commands the way the README and crewforth.com count them, so the install does not print a third number:
|
|
552
|
+
# a skill whose metadata says `kind: command` is a command, one marked `experimental: true` is neither (it is
|
|
553
|
+
# installed and works when typed, but nothing advertises it). Read from the frontmatter with builtins only.
|
|
554
|
+
count_showcase() { # $1 = a skills directory -> N_SKL N_CMD
|
|
555
|
+
N_SKL=0; N_CMD=0; local f l k e d
|
|
556
|
+
for f in "$1"/*/SKILL.md; do
|
|
557
|
+
[ -f "$f" ] || continue; k=0; e=0; d=0
|
|
558
|
+
while IFS= read -r l || [ -n "$l" ]; do
|
|
559
|
+
l="${l%$'\r'}"
|
|
560
|
+
case "$l" in ---) d=$((d+1)); [ "$d" -ge 2 ] && break ;; " kind: command") k=1 ;; " experimental: true") e=1 ;; esac
|
|
561
|
+
done < "$f"
|
|
562
|
+
[ "$e" = 1 ] && continue
|
|
563
|
+
if [ "$k" = 1 ]; then N_CMD=$((N_CMD+1)); else N_SKL=$((N_SKL+1)); fi
|
|
564
|
+
done
|
|
565
|
+
}
|
|
566
|
+
count_showcase "$SRC/skills"
|
|
567
|
+
|
|
568
|
+
h1 '[2/2] Summary · see what will be installed before you confirm'
|
|
569
|
+
echo
|
|
570
|
+
_mt 'full install'; _a="$_M"; _mt '— backend + web + mobile (RN/Expo), every agent and skill'
|
|
571
|
+
row 'Scope' "${B}${_a}${D} ${_M}${R}"
|
|
572
|
+
_mt '%s agents · %s skills · %s commands will be installed' "${MG}${B}${N_AG}${R}" "${MG}${B}${N_SKL}${R}" "${MG}${B}${N_CMD}${R}"
|
|
573
|
+
row 'Included' "$_M"
|
|
574
|
+
_mt 'stack-agnostic — the stack comes from CLAUDE.md ## Stack or the repo (backend-architecture)'
|
|
575
|
+
row 'Backend pattern' "$_M"
|
|
576
|
+
echo
|
|
577
|
+
_mt 'Security gates armed on every install:'; printf ' %s%s%s\n' "$B" "$_M" "$R"
|
|
578
|
+
gate 'commit/push approval gate — even in auto/bypass mode (guard-bash)'
|
|
579
|
+
gate 'trace scan — a git hook blocks AI traces / vendor names'
|
|
580
|
+
gate 'real context measurement + handoff at 75%% (Stop hook)'
|
|
581
|
+
gate 'destructive command guard (rm -rf / force-push, etc.)'
|
|
582
|
+
echo
|
|
583
|
+
row 'Will write' "${D}./.claude (agents·skills·commands·hooks·eval·studio·settings.json) + ./CLAUDE.md${R}"
|
|
584
|
+
# .gitignore is a TRACKED file in most repos, so appending to it is a change to the project — it belongs in
|
|
585
|
+
# the summary, named line by line, not discovered afterwards in `git diff`. Entries this repo already
|
|
586
|
+
# ignores are dropped at write time, so what is listed here is the upper bound, not a promise of four lines.
|
|
587
|
+
if [ "$VISIBILITY" = "shared" ]; then
|
|
588
|
+
_mt '(shared: .claude/ and CLAUDE.md stay committable)'
|
|
589
|
+
row ".gitignore" "${D}$(printf '%s · ' $GI_PLAN | sed 's/ · $//') ${YE}${_M}${R}"
|
|
590
|
+
else
|
|
591
|
+
_mt 'private'; _a="$_M"
|
|
592
|
+
row ".gitignore" "${D}$(printf '%s · ' $GI_PLAN | sed 's/ · $//') ${GR}(${_a})${R}"
|
|
593
|
+
fi
|
|
594
|
+
# What this machine is missing, BEFORE the confirm prompt — not after, when it becomes a symptom pointing
|
|
595
|
+
# somewhere else. Report-only and never blocking: the kit degrades rather than breaks, and that is exactly why
|
|
596
|
+
# a gap is otherwise invisible. See kit/eval/preflight.sh for the reasoning per tool.
|
|
597
|
+
[ -f "$SRC/eval/preflight.sh" ] && bash "$SRC/eval/preflight.sh"
|
|
598
|
+
rule
|
|
599
|
+
echo
|
|
600
|
+
# ask_yes reads from stdin => in CI `printf 'yes\n' | bash start.sh` works; 'no' on EOF (no accidental install).
|
|
601
|
+
_mt 'Install with these settings?'
|
|
602
|
+
if ! ask_yes " $_M"; then
|
|
603
|
+
_mt 'Cancelled — nothing changed.'; printf ' %s%s%s\n' "$YE" "$_M" "$R"
|
|
604
|
+
exit 0
|
|
605
|
+
fi
|
|
606
|
+
echo
|
|
607
|
+
|
|
608
|
+
# --- Kit installation (./.claude + ./CLAUDE.md) — everything, no prune ---
|
|
609
|
+
{ _mt 'Installing:'; echo "== ${_M} ./.claude + ./CLAUDE.md =="; }
|
|
610
|
+
mkdir -p .claude/agents .claude/skills .claude/hooks .claude/eval .claude/studio
|
|
611
|
+
cp -R "$SRC/agents/." .claude/agents/
|
|
612
|
+
cp -R "$SRC/skills/." .claude/skills/
|
|
613
|
+
# The slash commands are skills since 3.0 (Claude Code merged custom commands into skills): they arrive with
|
|
614
|
+
# skills/ above, marked `metadata: kind: command`. There is no commands/ directory any more.
|
|
615
|
+
VENDOR_ARMED=0; grep -qxE $'DevArchitecture\r?' .claude/hooks/trace-blocklist.txt 2>/dev/null && VENDOR_ARMED=1 # before the copy resets it; \r? = a CRLF copy still counts
|
|
616
|
+
cp -R "$SRC/hooks/." .claude/hooks/ 2>/dev/null || true
|
|
617
|
+
cp -R "$SRC/eval/." .claude/eval/ 2>/dev/null || true
|
|
618
|
+
# The Studio panel — launched by /crew-studio from this project's root. One `cp -R`
|
|
619
|
+
# plus one `rm`, not a selective walk: on Git Bash a per-file copy of 25 files is 25
|
|
620
|
+
# process spawns at 62-135 ms each. test/ is dropped because its assertions read the
|
|
621
|
+
# The panel's own suite is NOT here to delete: it lives in packaging/studio-test/,
|
|
622
|
+
# outside the payload, because kit/ ships whole and 104 KB of test code
|
|
623
|
+
# would travel through all four channels only to be removed on arrival.
|
|
624
|
+
cp -R "$SRC/studio/." .claude/studio/ 2>/dev/null || true
|
|
625
|
+
count_showcase .claude/skills
|
|
626
|
+
{ _mt "%s agents, %s skills and %s commands installed." "$(ls .claude/agents/*.md 2>/dev/null | wc -l | tr -d ' ')" "$N_SKL" "$N_CMD"; echo " ${_M}"; }
|
|
627
|
+
[ -f "$SRC/settings.json" ] && cp "$SRC/settings.json" .claude/settings.json
|
|
628
|
+
[ -f "$HERE/VERSION" ] && cp "$HERE/VERSION" .claude/VERSION # make the kit version trackable in the installed project
|
|
629
|
+
# Glob form so every shipped hook/eval is made executable — including ones added later (guard-write.sh,
|
|
630
|
+
# session-rehydrate.sh, …). An explicit list silently missed new hooks and left them non-executable.
|
|
631
|
+
# studio's gate hook is invoked as `bash <path>` today, so the bit is not load-bearing
|
|
632
|
+
# yet; set it anyway, because a future direct exec would fail silently.
|
|
633
|
+
chmod +x .claude/hooks/*.sh .claude/hooks/pre-commit .claude/hooks/commit-msg .claude/eval/*.sh .claude/studio/server/hooks/*.sh 2>/dev/null || true
|
|
634
|
+
|
|
635
|
+
# §4.2: a vendor line that was armed before this run stays armed (the copy above resets it to a comment); nothing
|
|
636
|
+
# else arms it. Rationale, and the measured failure of "arm it whenever the pattern skill exists", in adopt.sh.
|
|
637
|
+
# The `#test:` line comes with it: smoke-test fails any active pattern that has no case.
|
|
638
|
+
if [ "$VENDOR_ARMED" = 1 ] && [ -f .claude/hooks/trace-blocklist.txt ] \
|
|
639
|
+
&& grep -qx '# DevArchitecture' .claude/hooks/trace-blocklist.txt; then
|
|
640
|
+
awk '/^# DevArchitecture$/ { print "DevArchitecture"; print "#test: ported the handler from DevArchitecture"; next } { print }' \
|
|
641
|
+
.claude/hooks/trace-blocklist.txt > .claude/hooks/trace-blocklist.txt.kit-tmp \
|
|
642
|
+
&& mv .claude/hooks/trace-blocklist.txt.kit-tmp .claude/hooks/trace-blocklist.txt
|
|
643
|
+
fi
|
|
644
|
+
# `|| true` here used to swallow a missing payload file entirely: the install reported success and
|
|
645
|
+
# /crew-skill opened with `Read .claude/AGENT_TEMPLATE.md` against nothing. A best-effort copy is right —
|
|
646
|
+
# a missing doc must not abort an otherwise good install — but it has to be AUDIBLE, or the gap is
|
|
647
|
+
# invisible until someone runs the command. adopt.sh copies the same file for the same reason.
|
|
648
|
+
cp "$SRC/AGENT_TEMPLATE.md" .claude/ 2>/dev/null || { _mt 'AGENT_TEMPLATE.md missing from the payload — /crew-skill will have nothing to read.'; printf ' %s!%s %s\n' "$YE" "$R" "$_M"; }
|
|
649
|
+
cp "$SRC/README.md" .claude/ 2>/dev/null || true
|
|
650
|
+
|
|
651
|
+
# Install manifest — the names the KIT ships. It is the only way to tell kit-owned from project-owned later:
|
|
652
|
+
# the readiness check reads it to find the project's own skills, and the trust gate reads it to spot a skill
|
|
653
|
+
# the kit never shipped. Generated from the PAYLOAD, not from disk: a brownfield adopt preserves a pre-existing
|
|
654
|
+
# project file under a kit name, and reading disk would then brand that project file "kit-owned". Erring this
|
|
655
|
+
# way under-reports project ownership and can never raise a false alarm on a kit file — the safe direction for
|
|
656
|
+
# a gate. Rewritten on every install/update, so a component the kit drops stops counting as kit-owned.
|
|
657
|
+
# studio/ is deliberately NOT listed. Both consumers walk components: skill-trust.sh iterates skills/*/ and
|
|
658
|
+
# agents/*.md, and the doctor readiness check separates kit skills from the project's. A studio/ line would be
|
|
659
|
+
# read by nobody, and adopt.sh's stale sweep only considers commands/, agents/ and skills/ entries anyway.
|
|
660
|
+
{ for d in "$SRC"/skills/*/; do [ -d "$d" ] && echo "skills/$(basename "$d")"; done
|
|
661
|
+
for f in "$SRC"/agents/*.md; do [ -e "$f" ] && echo "agents/$(basename "$f")"; done
|
|
662
|
+
} > .claude/kit-manifest.txt 2>/dev/null || true
|
|
663
|
+
|
|
664
|
+
# stack= is always 'generic' since 3.0. The key is KEPT because an older updater reads it, and a file without it
|
|
665
|
+
# would send that updater down its stack-detection branch. No 'profile=' key — adopt.sh treats a leftover one
|
|
666
|
+
# from a pre-2.0 install as a migration signal, not as a shape.
|
|
667
|
+
{ echo "# Written by the Crewforth installer. stack= is always generic since 3.0; the key is kept for older updaters."
|
|
668
|
+
echo "stack=$STACK"
|
|
669
|
+
echo "installer=start.sh"
|
|
670
|
+
echo "version=$( [ -f "$HERE/VERSION" ] && head -1 "$HERE/VERSION" || echo unknown )"
|
|
671
|
+
# A locale guess (--yes or no terminal, nothing named) is used but not recorded: the next update would reuse it
|
|
672
|
+
# as if somebody had chosen it. --lang, CREW_LANG and the menu are choices, and are.
|
|
673
|
+
[ "${_LANG_GUESSED:-0}" = 1 ] || echo "lang=${CREW_LANG:-en}"
|
|
674
|
+
} > .claude/kit.conf
|
|
675
|
+
|
|
676
|
+
# Discipline (kit-owned, refreshed on every update) vs project section (yours, written once), joined by @import.
|
|
677
|
+
kit_require_sentinel "$SRC/CLAUDE.md"
|
|
678
|
+
kit_discipline_of "$SRC/CLAUDE.md" > .claude/DISCIPLINE.md
|
|
679
|
+
{ _mt '.claude/DISCIPLINE.md written — owned by Crewforth; an update overwrites it, so keep your own rules out of it.'; echo " ${_M}"; }
|
|
680
|
+
if [ ! -f ./CLAUDE.md ]; then
|
|
681
|
+
{ printf '<!-- Crewforth discipline · on conflict the project rules BELOW win -->\n%s\n' "$IMPORT_LINE"
|
|
682
|
+
kit_project_of "$SRC/CLAUDE.md"; } > ./CLAUDE.md
|
|
683
|
+
{ _mt './CLAUDE.md created — EDIT the project section.'; echo " ${_M}"; }
|
|
684
|
+
elif kit_has_import ./CLAUDE.md; then
|
|
685
|
+
{ _mt './CLAUDE.md kept as-is (already imports the discipline) — the refresh landed in DISCIPLINE.md.'; echo " ${_M}"; }
|
|
686
|
+
elif kit_claude_md_is_legacy ./CLAUDE.md; then
|
|
687
|
+
{ _mt '! ./CLAUDE.md carries the discipline INLINE (pre-1.1 layout) — left untouched.'; echo " ${_M}"; }
|
|
688
|
+
{ _mt 'Discipline updates will NOT reach it. To migrate: delete everything above your'; echo " ${_M}"; }
|
|
689
|
+
{ _mt '%s heading and leave this single line in its place:' "'# CLAUDE.md — <project>'"; echo " ${_M}"; }
|
|
690
|
+
echo " $IMPORT_LINE"
|
|
691
|
+
else
|
|
692
|
+
{ printf '<!-- Crewforth discipline · on conflict the project rules BELOW win -->\n%s\n\n' "$IMPORT_LINE"; cat ./CLAUDE.md; } > ./CLAUDE.md.kit-tmp \
|
|
693
|
+
&& mv ./CLAUDE.md.kit-tmp ./CLAUDE.md
|
|
694
|
+
{ _mt './CLAUDE.md existed — prepended the discipline @import; your content is untouched.'; echo " ${_M}"; }
|
|
695
|
+
fi
|
|
696
|
+
# The entries were decided in step 2 and printed in the summary; gi_add drops the ones this repo already
|
|
697
|
+
# ignores and fixes a missing trailing newline before appending. Word-split on purpose: GI_PLAN is a
|
|
698
|
+
# space-separated list this script built, not user input.
|
|
699
|
+
# shellcheck disable=SC2086
|
|
700
|
+
gi_add $GI_PLAN
|
|
701
|
+
[ -n "$GI_WROTE" ] && printf ' %s+%s .gitignore: %s\n' "$GR" "$R" "$GI_WROTE"
|
|
702
|
+
# Pinned only when git will actually CHECK .claude/ OUT — and that question goes to git, not to the mode
|
|
703
|
+
# variable, for the same reason gi_add asks `git check-ignore` instead of grepping the file: the variable is
|
|
704
|
+
# what we intended, the answer is what is true. They agree in the normal case (a private install has just
|
|
705
|
+
# added `.claude/` to .gitignore) and they differ in the ones that matter — a repo that already ignored
|
|
706
|
+
# `.claude/` before this install, or a shared install inside a repo whose parent rules ignore it anyway.
|
|
707
|
+
# Where git will never check the directory out there is no conversion to prevent, and writing repo-wide
|
|
708
|
+
# attributes for that project would be editing a file its owner did not need touched.
|
|
709
|
+
if ! git check-ignore -q .claude 2>/dev/null; then
|
|
710
|
+
ga_add
|
|
711
|
+
[ "${GA_WROTE:-0}" != 0 ] && { _mt '%s eol pin(s) so shared hooks stay LF' "$GA_WROTE"; printf ' %s+%s .gitattributes: %s\n' "$GR" "$R" "$_M"; }
|
|
712
|
+
fi
|
|
713
|
+
# `[ -d .git ]` is a proxy for the answer, and it lies exactly where it matters: in a worktree or a submodule
|
|
714
|
+
# `.git` is a FILE, so the commit gate was never armed there and the installer said nothing was wrong. adopt.sh
|
|
715
|
+
# already names this (red-team hole #6) and start.sh was never taught it. Measured: in a worktree the installer
|
|
716
|
+
# printed "no git repository", core.hooksPath stayed empty, and a commit carrying a forbidden expression landed;
|
|
717
|
+
# arming by hand and retrying, the trace scanner rejected it. start.sh deletes itself afterwards, so there is no
|
|
718
|
+
# second chance from the installer.
|
|
719
|
+
#
|
|
720
|
+
# Anchored on the TOPLEVEL, not merely on being inside a work tree: a relative core.hooksPath resolves against
|
|
721
|
+
# the work-tree root, so arming from a subdirectory stores the value and runs no hook at all — a gate reported
|
|
722
|
+
# active over a dead path. `pwd -P` because git answers with the physical path, and the arming call itself is
|
|
723
|
+
# the probe, so a git that resolves and fails falls into the NOTE instead of a false "active".
|
|
724
|
+
# Asked as `--show-prefix`, not by comparing two spellings of the same path. Comparing them is what the line
|
|
725
|
+
# above this one used to do, and on Windows the two sides NEVER match: git answers `C:/repo/app` while
|
|
726
|
+
# `pwd -P` answers `/c/repo/app`, so the arming call was never reached — in a worktree AND in an ordinary
|
|
727
|
+
# repository. Measured on a Windows 11 desktop against this commit: `core.hooksPath` came back EMPTY in both,
|
|
728
|
+
# the installer exited 0, and it printed "no git repository at this level" standing inside one. That is the
|
|
729
|
+
# §4.1/§4.2 commit gate silently absent on every Windows install, reported as a successful one.
|
|
730
|
+
# `--show-prefix` is the question itself: empty at the work-tree root, `sub/dir/` below it, non-zero exit
|
|
731
|
+
# outside a repo — and it carries no path spelling to disagree about. Verified here at a normal root, a
|
|
732
|
+
# worktree root, a subdirectory and a non-repo.
|
|
733
|
+
if PFX="$(git rev-parse --show-prefix 2>/dev/null)" && [ -z "$PFX" ] && git config core.hooksPath .claude/hooks 2>/dev/null; then
|
|
734
|
+
{ _mt 'trace scan: core.hooksPath -> .claude/hooks (§4.1/§4.2 commit gate active)'; echo " ${_M}"; }
|
|
735
|
+
else
|
|
736
|
+
{ _mt 'NOTE: no git repository at this level; after %s run: %s' "'git init'" 'git config core.hooksPath .claude/hooks'; echo " ${_M}"; }
|
|
737
|
+
fi
|
|
738
|
+
rm -rf "$SRC"
|
|
739
|
+
echo
|
|
740
|
+
{ _mt 'Done. ./.claude + ./CLAUDE.md ready (full install); the installer files are removed.'; echo "== ${_M} =="; }
|
|
741
|
+
{ _mt 'Next: 1) fill in the CLAUDE.md project section 2) open Claude Code at the repo root'; echo "${_M}"; }
|
|
742
|
+
{ _mt 'Note: if Claude Code is ALREADY running here, restart it — CLAUDE.md and the discipline load at session start.'; echo "${_M}"; }
|
|
743
|
+
{ _mt "Tip: open Claude Code and run /crew-doctor — it checks the install is wired (hooks executable, core.hooksPath set, discipline imported) and scores the project's readiness. CLAUDE.md loads the discipline every session."; echo "${_M}"; }
|
|
744
|
+
# Say what is true of THIS machine, not what is true in general. The line used to
|
|
745
|
+
# print identically with or without node, so on a machine that cannot start the
|
|
746
|
+
# panel it read as a footnote rather than as the reason nothing will happen. The
|
|
747
|
+
# question goes to preflight so the version floor stays defined in one place, and
|
|
748
|
+
# it is asked of the INSTALLED copy: $SRC is deleted at line 397, a few lines
|
|
749
|
+
# above this, so asking there answered "no node" on every machine.
|
|
750
|
+
if bash .claude/eval/preflight.sh --has node 2>/dev/null; then
|
|
751
|
+
{ _mt 'Panel: /crew-studio opens the Studio panel from this project (or: node .claude/studio/server/index.js --open).'; echo "${_M}"; }
|
|
752
|
+
else
|
|
753
|
+
{ _mt 'Panel: needs Node 18+, which is not on this machine — but that is no longer a dead end.'; echo "${_M}"; }
|
|
754
|
+
{ _mt 'Crewforth fetches one for the panel: %s (asks first;' 'bash .claude/studio/ensure-node.sh --plan'; echo " ${_M}"; }
|
|
755
|
+
{ _mt 'verified against the published checksum, into %s, nothing else touched).' '~/.claude/studio-runtime'; echo " ${_M}"; }
|
|
756
|
+
{ _mt 'Every gate still holds meanwhile; the panel is the only part that needs node.'; echo " ${_M}"; }
|
|
757
|
+
fi
|
|
758
|
+
# The star line, once per kit version (lib/star.sh keeps the marker in the git dir; text, URL and the
|
|
759
|
+
# CREW_NO_STAR / CI silence live there too). `_S` first so the blank line appears only when the line does.
|
|
760
|
+
if [ -f .claude/eval/lib/star.sh ]; then
|
|
761
|
+
_S="$(bash .claude/eval/lib/star.sh --once . 2>/dev/null || true)"
|
|
762
|
+
if [ -n "$_S" ]; then printf '\n%s\n' "$_S"; fi # an `&&` here was the script's LAST status: rc=1 on every update
|
|
763
|
+
fi
|
|
764
|
+
rm -f -- "$0"
|