crewforth 3.1.0 → 3.2.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +151 -0
- package/README.md +4 -3
- package/VERSION +1 -1
- package/adopt.sh +91 -11
- package/kit/AGENT_TEMPLATE.md +2 -1
- package/kit/CLAUDE.md +9 -8
- package/kit/agents/crew-commit-agent.md +7 -2
- package/kit/agents/crew-docs-agent.md +71 -0
- package/kit/agents/crew-planner.md +2 -0
- package/kit/agents/crew-refuter.md +77 -0
- package/kit/agents/crew-security-expert.md +3 -1
- package/kit/agents/crew-session-manager.md +11 -9
- package/kit/eval/doctor.sh +91 -3
- package/kit/eval/golden-routing.txt +9 -0
- package/kit/eval/lib/crew-map/crew-map.sh +26 -0
- package/kit/eval/lib/crew-map/detect.mjs +238 -0
- package/kit/eval/lib/crew-map/narrative.mjs +98 -0
- package/kit/eval/lib/crew-map/parse.mjs +305 -0
- package/kit/eval/lib/crew-map/project.mjs +163 -0
- package/kit/eval/lib/crew-map/rules/names.json +38 -0
- package/kit/eval/lib/crew-map/rules/packages.json +142 -0
- package/kit/eval/lib/crew-map/rules/python-stdlib.json +197 -0
- package/kit/eval/lib/crew-map/scan.mjs +214 -0
- package/kit/eval/lib/crew-map/stacks.mjs +441 -0
- package/kit/eval/lib/crew-map/surface.mjs +457 -0
- package/kit/eval/lib/findings/findings.mjs +410 -0
- package/kit/eval/lib/findings/findings.schema.json +122 -0
- package/kit/eval/lib/findings/findings.sh +29 -0
- package/kit/eval/lib/settings-json.awk +5 -0
- package/kit/eval/lib/star.sh +23 -7
- package/kit/eval/lib/statusline.settings.json +6 -0
- package/kit/eval/lib/statusline.sh +115 -0
- package/kit/eval/smoke-test.sh +465 -38
- package/kit/hooks/agent-outcome.sh +6 -3
- package/kit/hooks/guard-agent-model.sh +15 -5
- package/kit/hooks/guard-bash.sh +240 -182
- package/kit/hooks/guard-question.sh +73 -0
- package/kit/hooks/guard-write.sh +21 -0
- package/kit/hooks/prompt-approval.sh +320 -103
- package/kit/hooks/refuter-ledger.sh +80 -0
- package/kit/owned-blobs.tsv +4 -1
- package/kit/settings.json +33 -0
- package/kit/skills/crew-approve/SKILL.md +7 -0
- package/kit/skills/crew-doctor/SKILL.md +5 -1
- package/kit/skills/crew-handoff/SKILL.md +3 -2
- package/kit/skills/crew-map/SKILL.md +44 -0
- package/kit/skills/crew-review/SKILL.md +2 -0
- package/kit/skills/crew-update/SKILL.md +1 -1
- package/kit/skills/incident-runbook/SKILL.md +2 -2
- package/kit/skills/security-scan/references/verify.md +6 -3
- package/kit/studio/README.md +12 -0
- package/kit/studio/server/index.js +114 -11
- package/kit/studio/server/lib/crew-map.js +113 -0
- package/kit/studio/server/lib/model-outcomes.js +130 -15
- package/kit/studio/server/lib/pricing.js +3 -0
- package/kit/studio/server/lib/remote.js +99 -0
- package/kit/studio/server/lib/usage.js +74 -14
- package/kit/studio/web/app.js +214 -12
- package/kit/studio/web/canvas.js +44 -120
- package/kit/studio/web/graph-plan.js +10 -4
- package/kit/studio/web/index.html +10 -5
- package/kit/studio/web/map-plan.js +341 -0
- package/kit/studio/web/map.js +503 -0
- package/kit/studio/web/models-plan.js +61 -7
- package/kit/studio/web/models.js +19 -3
- package/kit/studio/web/style.css +125 -0
- package/kit/studio/web/usage.js +12 -0
- package/kit/studio/web/viewport.js +160 -0
- package/package.json +6 -5
- package/start.sh +10 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,157 @@ 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.2.0
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **In `auto`, approve a commit or a push with one click.** The refusal now also offers a fixed question, asked with
|
|
13
|
+
AskUserQuestion: Commit only, Commit + push, Push only, No. A new gate, `guard-question.sh`, answers `ask` on it,
|
|
14
|
+
so a hook that fills in the answer cannot answer it for you (measured in 34 runs and by hand in the terminal), and
|
|
15
|
+
`prompt-approval.sh` records your click exactly as it records `/crew-approve`: tied to what is staged, `HEAD`, the
|
|
16
|
+
session, for 30 minutes.
|
|
17
|
+
Any other wording, a question with answers already in it, or an answer the hook did not ask for records nothing.
|
|
18
|
+
- **Studio's Models view shows normal work that ran above the table's model.** The gate holds a floor, not a
|
|
19
|
+
ceiling, so this is shown and nothing is blocked: the classes of normal work that ran higher than Sonnet and than
|
|
20
|
+
the floor the gate held them to, with what they cost as run and the same tokens at Sonnet's list price. That
|
|
21
|
+
Sonnet is enough for a class is said only when its last ten verified calls on Sonnet all passed first time, the
|
|
22
|
+
test a held class is offered for lowering by; otherwise the view says it is not measured.
|
|
23
|
+
- **Studio draws a project's crew map.** The Map view (`p`, or **Map** under a project in the navigator, beside its
|
|
24
|
+
number of findings) reads `.crewforth/crew-map.json` and scans nothing. Modules sit in bands from the clients down
|
|
25
|
+
to the external resources, and a module the map does not place goes to Unplaced. Lines are solid where a file
|
|
26
|
+
proves them, dotted where inferred, and marked where the model proposed them. The pane pans and zooms with the
|
|
27
|
+
graph's own code; a card placed by hand stays placed for the project, and modules sharing a folder fold into one
|
|
28
|
+
card. The findings are listed proven first, with the scanner's hidden rules counted until asked for. The inspector
|
|
29
|
+
copies any `file:line` and copies a finding as a task. Compare with matches another project's calls against this
|
|
30
|
+
one's endpoints. On a phone, the findings and a list of the modules stand in for the pane.
|
|
31
|
+
- **A crew map of the project.** `bash .claude/eval/lib/crew-map/crew-map.sh` reads the modules (Node and TypeScript,
|
|
32
|
+
Python, Go, .NET, Java and Kotlin), the external resources they touch, the endpoints they provide and call, and
|
|
33
|
+
writes `.crewforth/crew-map.json`. Every module, line and finding cites the `file:line` it was read from, the same
|
|
34
|
+
tree gives the same bytes (`--check` holds it in CI), and a configuration value is read only to name its kind. Ten
|
|
35
|
+
gap rules: five proven by the lines they cite, five concluded from absence and hidden until their false-positive
|
|
36
|
+
rate is measured.
|
|
37
|
+
- **Findings are held to the lines they cite.** The findings core (`.claude/eval/lib/findings/findings.sh`) checks
|
|
38
|
+
every citation: the file is inside the project, the lines exist, and they are still the lines that were read. A
|
|
39
|
+
document that does not verify is reported entry by entry and never written.
|
|
40
|
+
- **A finding is verified by an agent that tries to refute it.** `crew-refuter` gets one claim and its cited lines,
|
|
41
|
+
not the reasoning, and works on one lens. `refuter-ledger.sh` records its verdict from the agent's own task and
|
|
42
|
+
answer in `.claude/state/crew-refuter.tsv`. With `--verified`, a `medium` finding needs one verdict and a `high` or
|
|
43
|
+
`critical` one three, each a line of that ledger.
|
|
44
|
+
- **`crew-docs-agent` writes the text.** README, guides, release notes, legal and privacy texts and store listings had
|
|
45
|
+
no owner and ran outside model routing. Legal, privacy, consent and terms paths and store metadata are critical
|
|
46
|
+
paths, so that text runs on the critical tier and a legal or privacy text is audited by `crew-privacy-agent` before
|
|
47
|
+
it closes.
|
|
48
|
+
- **Doctor names three more things, none of which changes its verdict.** Both editions enabled at once (every hook
|
|
49
|
+
would run twice), read from `claude plugin list --json`; the advisor, whether it is on, on what and from which
|
|
50
|
+
settings file, or turned off; and, before any update check has run, the published version, said as not measured
|
|
51
|
+
instead of nothing.
|
|
52
|
+
- **Studio shows why work was critical, and says when an agent is outside routing.** The gate records its reason
|
|
53
|
+
(`risk_why`, a new last column of `model-outcomes.tsv`) and the inspector shows it under the task card; an older
|
|
54
|
+
line shows none rather than a guessed one. An agent that is not `crew-*` gets a Model routing note: the gate leaves
|
|
55
|
+
it alone, so it has no card.
|
|
56
|
+
- **Studio counts the advisor in what a session cost.** An advisor call is billed at the advisor model's rates and the
|
|
57
|
+
API keeps it out of a response's top-level usage; Studio now reads it from `usage.iterations` and prices it at its
|
|
58
|
+
own model. The session table has an Advisor line (calls, tokens, cost) when a call is recorded. Measured on a real
|
|
59
|
+
session: Studio read $0.0567 before, $0.2110 after, the same as Claude Code's own figure.
|
|
60
|
+
- **Studio's Models view marks the classes whose agents consulted the advisor.** The cost cell carries an advisor tag
|
|
61
|
+
whose hover says how many calls and how much of the cost they were, already included; a class whose agents consulted
|
|
62
|
+
none has no tag.
|
|
63
|
+
- **Read-only remote viewers for Studio.** The Machines tab makes a viewer link that only reads, lists each viewer
|
|
64
|
+
with its last request, and revokes it. The server still binds to `127.0.0.1`; watching from another device goes
|
|
65
|
+
through a tunnel you run (`ssh -L`, Tailscale).
|
|
66
|
+
- **A visible way to the repository.** `/crew-doctor` now shows the star line once per version under its own marker,
|
|
67
|
+
so a line already shown at install no longer silences it, and a plugin install shows it too, with the plugin's
|
|
68
|
+
version. `/crew-update` keeps it to one line per update. The READMEs and the site's home page have a Star on GitHub
|
|
69
|
+
button.
|
|
70
|
+
- **A status line.** One line under the prompt: the gate's last word in this session, a commit or push approval that
|
|
71
|
+
still holds, the crew agents still running, the measured context fill and the session's cost. The installers set it
|
|
72
|
+
only where no status line is set, in the project or in your own settings, and `/crew-doctor` says which one is in
|
|
73
|
+
use.
|
|
74
|
+
- **`/crew-map`.** It runs the scanner, then has the model describe what it found: at most two sentences per module on
|
|
75
|
+
what it is for, and a short overview, each citing lines the scan itself read. `crew-map.sh --validate` refuses a
|
|
76
|
+
description that cites anything else or nothing, or a scanner field changed by hand; a scan again keeps the
|
|
77
|
+
descriptions that still hold and drops the rest. Studio's Map shows them, and `crew-planner` reads the map on an
|
|
78
|
+
unfamiliar project.
|
|
79
|
+
|
|
80
|
+
### Changed
|
|
81
|
+
|
|
82
|
+
- **A commit's command is read by one reader.** `guard-bash.sh` paired quotes in three places: the reader of a call,
|
|
83
|
+
an older walk that looks for a path or `-a` in a commit, and the check of a call the user's approval covers. The
|
|
84
|
+
last two now take their text from the first, and keep only their own question. What a call the user approved may
|
|
85
|
+
hold is unchanged. The older walk read a here-document's body and a comment as arguments of the commit; it no
|
|
86
|
+
longer does, so a call that only holds the words of a commit inside a here-document body, or `-a` after a `#`,
|
|
87
|
+
asks as any other commit does instead of being refused.
|
|
88
|
+
- **npm releases are published over OIDC.** The release job proves its identity with a short-lived GitHub Actions
|
|
89
|
+
token (npm Trusted Publishing); the repository holds no npm token, and each version carries a provenance statement.
|
|
90
|
+
- **The session line shows the measured fill.** The last line of a reply was `🔋 Session: low fill · …`: the template
|
|
91
|
+
had no number, so the figure the hook measures every turn stayed out of sight. It is now `🔋 Session %NN → low fill ·
|
|
92
|
+
…`, the hook's own figure, never an estimate.
|
|
93
|
+
- **Studio keeps cards for a longer session.** At Auto density a picture may now run up to half again past the pane
|
|
94
|
+
before its cards become chips; it opens at the zoom cards are read at, from its top, and Fit still shows all of it.
|
|
95
|
+
A compact group card says how its agents went as well as their models (`5 done · 3 Sonnet · 2 Opus`).
|
|
96
|
+
- **The session's own write to a critical path is recorded.** Agents not on Opus are refused a critical path; the
|
|
97
|
+
session's own writes were not looked at at all. They are still not refused (the choice is yours), but each one now
|
|
98
|
+
leaves a NOTE line in the gate log with the path and the rule that made it critical, which Studio's Gates tab and
|
|
99
|
+
the status line show.
|
|
100
|
+
|
|
101
|
+
### Security
|
|
102
|
+
|
|
103
|
+
- **A `PermissionRequest` hook could approve a commit with nobody at the keyboard.** Such a hook answers a
|
|
104
|
+
permission prompt before the person does, and the answer cannot be told from a click: measured, it approved the
|
|
105
|
+
Bash prompt `guard-bash.sh` raises for a commit in `default`, and it answered the new question too. Before it
|
|
106
|
+
asks, the gate now looks for one in every source it can read (user, project and local settings, the
|
|
107
|
+
managed-settings files, skill, agent and command front matter, installed plugins). Where one may be wired the
|
|
108
|
+
question closes, and so does the gate's own prompt in `default` and `acceptEdits`: `/crew-approve`, which no hook
|
|
109
|
+
can type, is the approval left. The limit: settings passed with `--settings` and managed settings delivered by a
|
|
110
|
+
server cannot be read.
|
|
111
|
+
- **`crewforth.hookschain` is guarded the way `core.hooksPath` is.** The key holds the path of a project hook chain
|
|
112
|
+
the shim reads on every commit, and `git config crewforth.hookschain …` passed. A write is now refused in every form
|
|
113
|
+
`core.hooksPath` is (a value, `--unset`, `-c`, `--config-env`, `GIT_CONFIG_` variables); reading it still passes.
|
|
114
|
+
|
|
115
|
+
### Fixed
|
|
116
|
+
|
|
117
|
+
- **The crew map reads what two real projects showed it missing.** Scanned before release, they put three rules
|
|
118
|
+
well over a 20% false-positive rate, and one scan refused its own document. Read now: a route with an empty path,
|
|
119
|
+
a decorator whose path is on the next line, `api.get<T>(` and a call split over lines, a client made in one file
|
|
120
|
+
and called in another (it had read as a server), an import ending in a comment, twelve more import names, and a
|
|
121
|
+
string in a test that looks like an import. A call with a run-time host is matched but never reported missing. A
|
|
122
|
+
route a healthcheck or a Kubernetes probe calls has a caller, and a package that works without an import (a
|
|
123
|
+
server started from the command line, a database driver named in a URL, a pytest plugin) is not unused.
|
|
124
|
+
Source in a language the scanner has no reader for, and routes registered with a variable path, are listed in
|
|
125
|
+
`notMeasured` instead of being silently absent. On the Python project the proven `endpoint-missing` went from 2
|
|
126
|
+
real out of 10 to 4 out of 4.
|
|
127
|
+
- **Studio's cards no longer flicker while a window is resized.** At Auto density, an edge dragged across the limit
|
|
128
|
+
between cards and chips redrew every card on every frame (measured: 57 times in 60 frames with 43 agents). The view
|
|
129
|
+
still follows the edge at once; the choice between cards and chips is made once the window has stopped for 150 ms.
|
|
130
|
+
- **`crew-commit-agent` and `crew-session-manager` are called.** Both were exempt from the delegation cue and nothing
|
|
131
|
+
pulled them, so the main thread wrote every commit message and handover itself. The discipline and `/crew-handoff`
|
|
132
|
+
now call them, and smoke fails when a chain-step agent has no caller.
|
|
133
|
+
- **Doctor names the 2.x plugin enabled beside a 3.x install.** The 2.x plugin, under its old name, wires the same
|
|
134
|
+
hooks; the check looked for `crewforth@…` only and said one edition while every hook ran twice.
|
|
135
|
+
- **The crew map no longer reads a regex literal as an import.** `from '[^']*…` inside a regular expression was
|
|
136
|
+
reported as a proven undeclared dependency named `[^`; a specifier counts as a package only when it is named the way
|
|
137
|
+
npm names one.
|
|
138
|
+
- **A cache write the usage split leaves out is counted.** Studio dropped cache writes that the top-level split of a
|
|
139
|
+
response did not account for; they are now the five-minute kind, which is how they were billed.
|
|
140
|
+
- **Doctor's advisor lines match how Claude Code attaches it.** An advisor below the model it would advise is not
|
|
141
|
+
attached at all, so critical work runs without one; the warning said it would advise a stronger model.
|
|
142
|
+
- **`incident-runbook` is picked for an outage that keeps coming back.** Its description named only a production that
|
|
143
|
+
is degraded or broken; a request about a recurring outage picked it in none of three held-out eval runs before, and
|
|
144
|
+
in two of three after.
|
|
145
|
+
|
|
146
|
+
## [3.1.1] — 2026-10-12
|
|
147
|
+
|
|
148
|
+
### Fixed
|
|
149
|
+
|
|
150
|
+
- **An update could write this machine's path into `.claude/git-shim`, a file projects commit.** The updater knew
|
|
151
|
+
the kit's own hooks directory only when `core.hooksPath` was spelled `.claude/hooks`. Spelled as an absolute path
|
|
152
|
+
(seen in a real project) it was taken for a hook chain of the project's own, and the shim named
|
|
153
|
+
`$ROOT//Users/<name>/…/.claude/hooks`: a path that does not exist, so nothing ran twice, but the user name and
|
|
154
|
+
the folder layout went into the repository. `core.hooksPath` is now read for what it names (the same directory,
|
|
155
|
+
whatever the spelling), a chain inside the repository is named from the root, and a chain outside it is kept in
|
|
156
|
+
the clone's git config (`crewforth.hookschain`) and read by the shim when it runs. A shim an earlier update
|
|
157
|
+
wrote for that case is removed by the next update.
|
|
158
|
+
|
|
8
159
|
## [3.1.0] — 2026-10-12
|
|
9
160
|
|
|
10
161
|
### Security
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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:
|
|
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, 13 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
|
|
@@ -13,6 +13,7 @@ Then open Claude Code and run `/crew-doctor` to confirm the setup. Requires bash
|
|
|
13
13
|
|
|
14
14
|
[](https://www.npmjs.com/package/crewforth)
|
|
15
15
|
[](https://github.com/Crewforth/crewforth/blob/main/LICENSE)
|
|
16
|
+
[](https://github.com/Crewforth/crewforth)
|
|
16
17
|
|
|
17
18
|
<picture>
|
|
18
19
|
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/Crewforth/crewforth/main/assets/flow-en-dark.svg">
|
|
@@ -28,9 +29,9 @@ Then open Claude Code and run `/crew-doctor` to confirm the setup. Requires bash
|
|
|
28
29
|
|
|
29
30
|
## What you get
|
|
30
31
|
|
|
31
|
-
- **
|
|
32
|
+
- **14 specialist agents**, from `crew-planner` to `crew-commit-agent`, each owning one domain.
|
|
32
33
|
- **40 skills** holding the method: testing, migrations, API contracts, observability, accessibility, deployment.
|
|
33
|
-
- **
|
|
34
|
+
- **13 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-map`.
|
|
34
35
|
- 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.
|
|
35
36
|
- **Safe adoption.** `adopt` lands everything on a branch, staged and uncommitted; `main` is never touched.
|
|
36
37
|
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.2.0
|
package/adopt.sh
CHANGED
|
@@ -273,6 +273,8 @@ _mt() { # $1 = English text (the key); further args fill %s; result in _M
|
|
|
273
273
|
"custom hooks and every other permission PRESERVED") s="özel hook'lar ve diğer tüm izinler KORUNDU" ;;
|
|
274
274
|
"custom hooks/permissions PRESERVED") s="özel hook'lar ve izinler KORUNDU" ;;
|
|
275
275
|
"settings.json: was missing in the project -> Crewforth's was installed") s="settings.json: projede yoktu -> Crewforth'unki kuruldu" ;;
|
|
276
|
+
"Status line: Crewforth's (the gate's last word, a standing approval, crew agents, fill, cost).") s="Durum satırı: Crewforth'unki (kapının son sözü, geçerli onay, crew ajanları, doluluk, maliyet)." ;;
|
|
277
|
+
"Status line: yours is kept; Crewforth's is one command away (see /crew-doctor).") s="Durum satırı: seninki korundu; Crewforth'unki tek komutla açılır (/crew-doctor'a bak)." ;;
|
|
276
278
|
"settings.json: hook-aware MERGE (Crewforth hooks refreshed - %s)") s="settings.json: hook'ları gözeten BİRLEŞTİRME (Crewforth hook'ları yenilendi - %s)" ;;
|
|
277
279
|
"settings.json: %s") s='settings.json: %s' ;;
|
|
278
280
|
"merge failed -> project setting PRESERVED (not overwritten)") s='birleştirme başarısız -> proje ayarı KORUNDU (üzerine yazılmadı)' ;;
|
|
@@ -524,12 +526,53 @@ fi
|
|
|
524
526
|
rowm 'git' "$GITKIND" # every GITKIND value is a literal with its own table row
|
|
525
527
|
|
|
526
528
|
# existing hook system (decision #5 — single-hooksPath clash with husky/lefthook)
|
|
527
|
-
|
|
529
|
+
# WHAT core.hooksPath NAMES, however it is spelled. It was matched as text against `.claude/hooks`, so the same
|
|
530
|
+
# directory written as an absolute path (seen in a real project) was read as a hook chain of the project's own:
|
|
531
|
+
# the shim then named `$ROOT//Users/<name>/…/.claude/hooks`, a path that does not exist and that carries the
|
|
532
|
+
# machine's user name into a file projects commit; had it existed, every hook would have run twice.
|
|
533
|
+
# Asked of the file system and of git, not of the spelling: `-ef` says whether two names are one directory, and
|
|
534
|
+
# `git rev-parse --show-prefix` says where a directory is inside the repository.
|
|
535
|
+
_hp_class(){ # $1 = a hooks path as configured -> HP_KIND: none | own | shim | in (HP_REL, from the root) | out (HP_ABS)
|
|
536
|
+
local p="$1" top pfx r
|
|
537
|
+
HP_KIND=none; HP_REL=""; HP_ABS=""
|
|
538
|
+
[ -n "$p" ] || return 0
|
|
539
|
+
case "$p" in '~/'*) p="$HOME/${p#\~/}" ;; esac
|
|
540
|
+
while :; do case "$p" in ?*/) p="${p%/}" ;; *) break ;; esac; done
|
|
541
|
+
case "$p" in
|
|
542
|
+
/*|[A-Za-z]:[/\\]*)
|
|
543
|
+
if [ -d "$p" ]; then
|
|
544
|
+
if [ -d .claude/hooks ] && [ "$p" -ef .claude/hooks ]; then HP_KIND=own; return 0; fi
|
|
545
|
+
if [ -d .claude/git-shim ] && [ "$p" -ef .claude/git-shim ]; then HP_KIND=shim; return 0; fi
|
|
546
|
+
top="$(git -C "$p" rev-parse --show-toplevel 2>/dev/null || true)"
|
|
547
|
+
if [ -n "$top" ] && [ "$top" -ef . ]; then
|
|
548
|
+
pfx="$(git -C "$p" rev-parse --show-prefix 2>/dev/null || true)"; pfx="${pfx%/}"
|
|
549
|
+
if [ -n "$pfx" ]; then HP_KIND=in; HP_REL="$pfx"; return 0; fi
|
|
550
|
+
fi
|
|
551
|
+
fi
|
|
552
|
+
# Not there, or git cannot place it (.git/hooks is not in the work tree): the spelling is what is left.
|
|
553
|
+
for r in "$PWD" "$(pwd -P 2>/dev/null || true)"; do
|
|
554
|
+
[ -n "$r" ] || continue
|
|
555
|
+
case "$p" in "$r"/?*) p="${p#"$r"/}"
|
|
556
|
+
case "$p" in .claude/hooks) HP_KIND=own ;; .claude/git-shim) HP_KIND=shim ;; *) HP_KIND=in; HP_REL="$p" ;; esac
|
|
557
|
+
return 0 ;;
|
|
558
|
+
esac
|
|
559
|
+
done
|
|
560
|
+
HP_KIND=out; HP_ABS="$p" ;;
|
|
561
|
+
*)
|
|
562
|
+
while :; do case "$p" in ./?*) p="${p#./}" ;; *) break ;; esac; done
|
|
563
|
+
case "$p" in .claude/hooks) HP_KIND=own; return 0 ;; .claude/git-shim) HP_KIND=shim; return 0 ;; esac
|
|
564
|
+
if [ -d "$p" ] && [ -d .claude/hooks ] && [ "$p" -ef .claude/hooks ]; then HP_KIND=own; return 0; fi
|
|
565
|
+
if [ -d "$p" ] && [ -d .claude/git-shim ] && [ "$p" -ef .claude/git-shim ]; then HP_KIND=shim; return 0; fi
|
|
566
|
+
HP_KIND=in; HP_REL="$p" ;;
|
|
567
|
+
esac
|
|
568
|
+
}
|
|
569
|
+
HOOKSYS="none"; SHIM_CHAIN=""; SHIM_OUT=0; CUR_OUT=0
|
|
528
570
|
CURHP="$(git config --get core.hooksPath 2>/dev/null || true)"
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
571
|
+
_hp_class "$CURHP"
|
|
572
|
+
case "$HP_KIND" in
|
|
573
|
+
none) : ;;
|
|
574
|
+
own) HOOKSYS="Crewforth (already armed)"; CURHP="" ;; # kit's OWN path — not a foreign chain (re-adopt must not shim itself)
|
|
575
|
+
shim)
|
|
533
576
|
# Crewforth's shim, from an earlier run: git is pointed at it, and IT holds the project's own chain. That chain
|
|
534
577
|
# is read back from the shim, or the next lines would see "no chain" and point git straight at .claude/hooks:
|
|
535
578
|
# measured, the second update of a project with core.hooksPath=myhooks left its own pre-commit never running
|
|
@@ -540,14 +583,23 @@ case "$CURHP" in
|
|
|
540
583
|
[ -f "$_sf" ] || continue
|
|
541
584
|
while IFS= read -r _sl || [ -n "$_sl" ]; do
|
|
542
585
|
_sl="${_sl%$'\r'}"
|
|
586
|
+
# (A shim written before this was fixed can hold `P="$ROOT//abs/path/$H"`: what is left after `$ROOT/` is
|
|
587
|
+
# the absolute path, and it is classed like any other.)
|
|
543
588
|
case "$_sl" in 'P="'*'/$H"') _sl="${_sl#P=\"}"; _sl="${_sl%/\$H\"}"; _sl="${_sl#\$ROOT/}"
|
|
544
|
-
|
|
589
|
+
_hp_class "$_sl"
|
|
590
|
+
case "$HP_KIND" in in) SHIM_CHAIN="$HP_REL"; SHIM_OUT=0 ;; out) SHIM_CHAIN="$HP_ABS"; SHIM_OUT=1 ;; esac ;;
|
|
545
591
|
esac
|
|
546
592
|
done < "$_sf"
|
|
547
593
|
[ -n "$SHIM_CHAIN" ] && break
|
|
548
594
|
done
|
|
595
|
+
# A chain outside the repository is not written in the shim: it is in this clone's git config.
|
|
596
|
+
if [ -z "$SHIM_CHAIN" ]; then
|
|
597
|
+
_hp_class "$(git config --get crewforth.hookschain 2>/dev/null || true)"
|
|
598
|
+
case "$HP_KIND" in in) SHIM_CHAIN="$HP_REL"; SHIM_OUT=0 ;; out) SHIM_CHAIN="$HP_ABS"; SHIM_OUT=1 ;; esac
|
|
599
|
+
fi
|
|
549
600
|
[ -n "$SHIM_CHAIN" ] && HOOKSYS="core.hooksPath=$SHIM_CHAIN" ;;
|
|
550
|
-
|
|
601
|
+
in) CURHP="$HP_REL"; HOOKSYS="core.hooksPath=$CURHP" ;;
|
|
602
|
+
out) CURHP="$HP_ABS"; CUR_OUT=1; HOOKSYS="core.hooksPath=$CURHP" ;;
|
|
551
603
|
esac
|
|
552
604
|
[ -d .husky ] && HOOKSYS="husky (.husky/)"
|
|
553
605
|
{ [ -f lefthook.yml ] || [ -f .lefthook.yml ]; } && HOOKSYS="lefthook"
|
|
@@ -1623,13 +1675,22 @@ if [ -n "$SET_NOTE" ]; then HAND_SET="NOT merged — ${SET_NOTE%.}"
|
|
|
1623
1675
|
elif [ "$SET_FRESH" = 1 ]; then HAND_SET="Crewforth's settings.json installed (the project had none, so nothing was merged)"
|
|
1624
1676
|
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
|
|
1625
1677
|
|
|
1678
|
+
# The status line, only where none is set (a user's own would lose to the project's setting, so it is never
|
|
1679
|
+
# replaced). eval/lib/statusline.sh --install decides and writes; this only says what it did.
|
|
1680
|
+
if [ -f .claude/eval/lib/statusline.sh ]; then
|
|
1681
|
+
case "$(bash .claude/eval/lib/statusline.sh --install 2>/dev/null)" in
|
|
1682
|
+
installed) say "Status line: Crewforth's (the gate's last word, a standing approval, crew agents, fill, cost)." ;;
|
|
1683
|
+
kept) say "Status line: yours is kept; Crewforth's is one command away (see /crew-doctor)." ;;
|
|
1684
|
+
esac
|
|
1685
|
+
fi
|
|
1686
|
+
|
|
1626
1687
|
# ============ [STAGE 4] GIT-HOOK ARMING (SHIM) + PROOF ============
|
|
1627
1688
|
h1m 'Stage 4 — arm the git gates (SHIM via husky) + PROOF'
|
|
1628
1689
|
|
|
1629
1690
|
# 4a) location of the existing hook chain (the shim calls this too)
|
|
1630
|
-
ORIG_HOOKS=""
|
|
1631
|
-
if [ -n "$CURHP" ]; then ORIG_HOOKS="$CURHP"
|
|
1632
|
-
elif [ -n "$SHIM_CHAIN" ]; then ORIG_HOOKS="$SHIM_CHAIN" # the chain the shim of an earlier run already runs
|
|
1691
|
+
ORIG_HOOKS=""; ORIG_OUT=0
|
|
1692
|
+
if [ -n "$CURHP" ]; then ORIG_HOOKS="$CURHP"; ORIG_OUT="$CUR_OUT"
|
|
1693
|
+
elif [ -n "$SHIM_CHAIN" ]; then ORIG_HOOKS="$SHIM_CHAIN"; ORIG_OUT="$SHIM_OUT" # the chain the shim of an earlier run already runs
|
|
1633
1694
|
elif [ -d .husky ]; then ORIG_HOOKS=".husky"
|
|
1634
1695
|
elif [ -x .git/hooks/pre-commit ] || [ -x .git/hooks/commit-msg ]; then ORIG_HOOKS=".git/hooks"; fi
|
|
1635
1696
|
# never shim the kit onto its OWN hooks (re-adopt) — the shim would exec itself and recurse on every commit
|
|
@@ -1637,9 +1698,28 @@ case "$ORIG_HOOKS" in .claude/hooks|.claude/git-shim) ORIG_HOOKS="" ;; esac
|
|
|
1637
1698
|
|
|
1638
1699
|
if [ -z "$ORIG_HOOKS" ]; then
|
|
1639
1700
|
git config core.hooksPath .claude/hooks
|
|
1701
|
+
git config --unset-all crewforth.hookschain 2>/dev/null || true
|
|
1702
|
+
# A shim of an earlier run has no chain to run any more. It is Crewforth's own generated file (its second line
|
|
1703
|
+
# says so), and one written before the fix above can hold a path of this machine: it goes, and nothing else does.
|
|
1704
|
+
for hk in pre-commit commit-msg; do
|
|
1705
|
+
if [ -f ".claude/git-shim/$hk" ] && grep -q '^# kit git-shim:' ".claude/git-shim/$hk" 2>/dev/null; then rm -f ".claude/git-shim/$hk"; fi
|
|
1706
|
+
done
|
|
1707
|
+
[ -d .claude/git-shim ] && rmdir .claude/git-shim 2>/dev/null || true
|
|
1640
1708
|
say 'core.hooksPath -> .claude/hooks (no existing hook chain)'
|
|
1641
1709
|
else
|
|
1642
1710
|
mkdir -p .claude/git-shim
|
|
1711
|
+
# THE SHIM HOLDS NO PATH OF THIS MACHINE. A chain inside the repository is named from the root. One outside it
|
|
1712
|
+
# (a hooks directory in the user's home) is kept in this clone's git config, which is not committed, and the shim
|
|
1713
|
+
# reads it when it runs.
|
|
1714
|
+
if [ "$ORIG_OUT" = 1 ]; then
|
|
1715
|
+
git config crewforth.hookschain "$ORIG_HOOKS"
|
|
1716
|
+
_shp='C="$(git config --get crewforth.hookschain 2>/dev/null || true)"
|
|
1717
|
+
P=""
|
|
1718
|
+
[ -z "$C" ] || P="$C/$H"'
|
|
1719
|
+
else
|
|
1720
|
+
git config --unset-all crewforth.hookschain 2>/dev/null || true
|
|
1721
|
+
_shp="P=\"\$ROOT/$ORIG_HOOKS/\$H\""
|
|
1722
|
+
fi
|
|
1643
1723
|
for hk in pre-commit commit-msg; do
|
|
1644
1724
|
cat > ".claude/git-shim/$hk" <<SHIM
|
|
1645
1725
|
#!/usr/bin/env bash
|
|
@@ -1648,7 +1728,7 @@ set -e
|
|
|
1648
1728
|
H="\$(basename "\$0")"
|
|
1649
1729
|
ROOT="\$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
1650
1730
|
K="\$ROOT/.claude/hooks/\$H"; [ -x "\$K" ] && "\$K" "\$@"
|
|
1651
|
-
|
|
1731
|
+
$_shp
|
|
1652
1732
|
if [ -x "\$P" ]; then "\$P" "\$@"
|
|
1653
1733
|
elif [ -f "\$P" ]; then bash "\$P" "\$@"; fi
|
|
1654
1734
|
exit 0
|
package/kit/AGENT_TEMPLATE.md
CHANGED
|
@@ -49,7 +49,8 @@ verify: npm test -- orders
|
|
|
49
49
|
`architecture` · `test-run` · `test-write` · `audit`. `verify` is the command that proves the work, or `none`.
|
|
50
50
|
|
|
51
51
|
**Risk class.** `critical` when the change is `migration`, `security`, `architecture` or `fix-unknown`, or a file is
|
|
52
|
-
on a critical path (auth, payments, billing, migrations, security, crypto, secrets,
|
|
52
|
+
on a critical path (auth, payments, billing, migrations, security, crypto, secrets, legal, privacy, consent, terms,
|
|
53
|
+
`*.sql`, `schema.prisma`, store-listing text under `store/` or `fastlane/metadata/`; the
|
|
53
54
|
project's own are listed in `.claude/state/crew-critical-paths.auto`, and `.claude/crew-model-rules` adds or removes).
|
|
54
55
|
Otherwise `normal`.
|
|
55
56
|
|
package/kit/CLAUDE.md
CHANGED
|
@@ -34,12 +34,13 @@ recommend, with the rationale.**
|
|
|
34
34
|
code — RISK decides, not size: behaviour changes and a subagent's diff review go to the owner.
|
|
35
35
|
|
|
36
36
|
**Classify before the first tool call:** name the DOMAIN in your own words, then read its owner from
|
|
37
|
-
`.claude/agents/`. Classify *intent*,
|
|
37
|
+
`.claude/agents/`. Classify *intent*, not wording, in any language.
|
|
38
38
|
What the user sees → **crew-frontend-expert** · server behaviour → **crew-backend-expert** · stored data →
|
|
39
39
|
**crew-database-expert** · run/deploy/CI → **crew-devops-expert** · **crew-security-expert** ·
|
|
40
|
-
**crew-privacy-agent** (personal data) · **crew-test-expert** · **crew-performance-expert** ·
|
|
40
|
+
**crew-privacy-agent** (personal data) · **crew-test-expert** · **crew-performance-expert** · docs/legal/store
|
|
41
|
+
text → **crew-docs-agent** · what to build →
|
|
41
42
|
**crew-planner** · unknown cause → **general-purpose** + `systematic-debugging`. Two domains → delegate in
|
|
42
|
-
sequence.
|
|
43
|
+
sequence. All are installed: "no owner" is an `ls`.
|
|
43
44
|
|
|
44
45
|
1. **Diagnose, then plan** — unknown cause → `systematic-debugging` first; unclear scope → **crew-planner** (`/crew-plan`).
|
|
45
46
|
2. **Produce** — the domain owner above.
|
|
@@ -49,8 +50,8 @@ sequence. Every agent above is installed; "no owner" is an `ls`, not a default.
|
|
|
49
50
|
A finding or a red test goes back to the owner that wrote the code, and after the fix **all of them run
|
|
50
51
|
again**: the diff they cleared no longer exists.
|
|
51
52
|
4. **Close — only once 3 is clean.** DoD → **crew-review-agent** (LAST, never first) clean → **crew-commit-agent**
|
|
52
|
-
|
|
53
|
-
5. **Hand off** — phase boundary or
|
|
53
|
+
writes the message, not you; waits for approval (`/crew-ship`); held items close here.
|
|
54
|
+
5. **Hand off** — phase boundary or 🔋 ≥ medium → **crew-session-manager** → `handoff` → `/clear` (`/crew-handoff`).
|
|
54
55
|
|
|
55
56
|
**Model per call — RISK decides, not size.** A `crew-*` task opens with its card, and the call names `model`:
|
|
56
57
|
`files:` files/globs · `change:` text|feature|fix-known|fix-unknown|refactor|migration|security|architecture|test-run|test-write|audit
|
|
@@ -126,9 +127,9 @@ more tokens, because each one re-pays for its own context — plan for that.
|
|
|
126
127
|
> **Honest boundary.** Measuring fill **is a gate** (`context-usage.sh` + `session-guard.sh`). The four bullets above
|
|
127
128
|
> are **model discipline** — no exit code can judge a delegate-or-not call, so they rest on your reasoning.
|
|
128
129
|
|
|
129
|
-
## Session
|
|
130
|
-
Last line of a reply, after the next step: `🔋 Session
|
|
131
|
-
— **only
|
|
130
|
+
## Session status line
|
|
131
|
+
Last line of a reply, after the next step: `🔋 Session %NN → [low/medium/high] fill · Recommendation: [continue / handoff+clear / new session]`
|
|
132
|
+
— **only with a reading** (%NN is its measured figure); with none, omit the line.
|
|
132
133
|
|
|
133
134
|
**Never guess the fill.** You cannot run `/context`; the `UserPromptSubmit` hook injects the measured line
|
|
134
135
|
`🔋 Session %NN.N → level` every turn (`input + cache_read + cache_creation` = the `/context` figure). Use it. Exact
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
name: crew-commit-agent
|
|
3
3
|
color: cyan
|
|
4
4
|
description: |
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
Use immediately after the DoD is clean, before any git commit: proposes the Conventional Commits message
|
|
6
|
+
from the staged diff (`commit-message`). Commits only with user approval.
|
|
7
7
|
tools: Read, Grep, Glob, Bash, PowerShell
|
|
8
8
|
model: haiku
|
|
9
9
|
metadata:
|
|
@@ -102,6 +102,11 @@ write is refused because the file is on a critical path, do not reach it another
|
|
|
102
102
|
`/crew-approve push` or `/crew-approve commit+push`. The user approves by sending
|
|
103
103
|
that command alone; a sentence that says yes is not an approval, and you cannot run `/crew-approve` for them. It covers what is staged on that HEAD. If the gate says the approval path is closed in this session (the
|
|
104
104
|
recording hook is not wired), say so and offer Shift+Tab or their own terminal instead.
|
|
105
|
+
In `auto` the refusal may also offer the approval question: show the message, then call AskUserQuestion with
|
|
106
|
+
exactly the input the refusal prints (the fixed question and its four answers, no answers of your own). The
|
|
107
|
+
user's click is recorded the same way; read the line Crewforth adds and run what it names. Where a
|
|
108
|
+
`PermissionRequest` hook may be wired the gate closes the question, and in `default` / `acceptEdits` its own
|
|
109
|
+
prompt too, because such a hook can answer either before the user does: then `/crew-approve` is the way.
|
|
105
110
|
Then run `git commit -m '…'` alone in its call (single-quoted, or from a here-document with a quoted delimiter),
|
|
106
111
|
and a push as `git push <remote> <branch>`. Never write that message yourself. Under `plan` and
|
|
107
112
|
`bypassPermissions` the gate fails closed; there the user must switch modes or pre-authorise with
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: crew-docs-agent
|
|
3
|
+
color: yellow
|
|
4
|
+
description: |
|
|
5
|
+
Use proactively for docs: README, guides, release notes, legal, privacy and store texts. Writes no code.
|
|
6
|
+
tools: Read, Grep, Glob, Edit, Write
|
|
7
|
+
metadata:
|
|
8
|
+
stage: produce
|
|
9
|
+
skills: [docs-writer, i18n-integrity]
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Docs Agent
|
|
13
|
+
|
|
14
|
+
<!-- routing-eval reads the next line; why it sits in the body: AGENT_TEMPLATE.md -->
|
|
15
|
+
Trigger phrases: "readme", "user guide", "release notes", "changelog entry", "privacy policy", "terms of service", "terms of use", "cookie notice", "consent text", "legal text", "store listing", "app store description", "play store description", "documentation page"
|
|
16
|
+
|
|
17
|
+
Writes the text; the `docs-writer` skill holds how. Code is someone else's: a sentence inside a component goes to its
|
|
18
|
+
owner, a document beside it comes here.
|
|
19
|
+
|
|
20
|
+
## Expertise stance (technical writer who has been sued once)
|
|
21
|
+
- **Every claim is checkable.** A number, a flag, a behaviour is read from the code or the tool's own documentation
|
|
22
|
+
before it is written; what could not be checked is written as not checked, or left out.
|
|
23
|
+
- **The reader acts on it.** Lead with what they do, then why; one idea per paragraph, no history in a guide.
|
|
24
|
+
- **Legal and privacy text is not paraphrased into a promise.** It says what the product does, measured from the code
|
|
25
|
+
and the data flow, and nothing the product does not do. Anything that reads as a legal commitment the code does
|
|
26
|
+
not back goes to the user before it is written.
|
|
27
|
+
- **Both languages or neither.** A text the project ships in two languages changes in both in the same change
|
|
28
|
+
(`i18n-integrity`).
|
|
29
|
+
|
|
30
|
+
## When
|
|
31
|
+
README, guides, docs pages, release notes and CHANGELOG, legal and privacy texts (`legal/`, `privacy*`, `terms*`,
|
|
32
|
+
`consent*`), store listings (`fastlane/metadata/`, `store/`, `store-listing/`, `metadata/android|ios/`).
|
|
33
|
+
|
|
34
|
+
## How (follow its skill)
|
|
35
|
+
`docs-writer`: structure, what a README states and what it does not, the voice. `i18n-integrity` when the text has a
|
|
36
|
+
translation. The model follows the RISK: legal, privacy and store texts are critical paths, so their card runs on
|
|
37
|
+
the critical tier and the write itself is refused on a lower model.
|
|
38
|
+
|
|
39
|
+
## Coordination (cross-agent)
|
|
40
|
+
- A legal or privacy text is audited by **crew-privacy-agent** before it closes: what the text says against what the
|
|
41
|
+
code collects, keeps and shares.
|
|
42
|
+
- A sentence about behaviour the code does not have goes to the owner of that code first, not into the text.
|
|
43
|
+
|
|
44
|
+
## DoD
|
|
45
|
+
Every checkable statement checked; translations in step; the audit above clean for a legal or privacy text.
|
|
46
|
+
|
|
47
|
+
## Output & context (token)
|
|
48
|
+
To the main thread: what changed, in which files, and every statement that could not be checked. Not the text itself.
|
|
49
|
+
|
|
50
|
+
## Errors/escalation
|
|
51
|
+
A claim the code contradicts, or a legal commitment nobody has decided: stop and report it, do not soften it into
|
|
52
|
+
the text.
|
|
53
|
+
|
|
54
|
+
## Example delegation
|
|
55
|
+
- ✅ "update the privacy policy for the new retention period"
|
|
56
|
+
- ❌ "add a consent checkbox to the signup form" (UI: crew-frontend-expert)
|
|
57
|
+
|
|
58
|
+
## Confidence
|
|
59
|
+
The LAST line of every report is exactly `confidence: high` or exactly `confidence: low`: lower case, nothing else
|
|
60
|
+
on the line, nothing after it. `low` when you guessed, could not verify, or the task was above the model this run
|
|
61
|
+
was given; the caller then repeats it once, one model up. Your task opens with a card (`files:`, `change:`,
|
|
62
|
+
`verify:`): the `verify` command is run when you stop, and if it fails you are asked once to fix the work. If a
|
|
63
|
+
write is refused because the file is on a critical path, do not reach it another way: stop, and put
|
|
64
|
+
`escalate: <the file>` on the line before `confidence: low`.
|
|
65
|
+
|
|
66
|
+
## Constraints
|
|
67
|
+
- Writes text files only; no source, no configuration, no build files.
|
|
68
|
+
- Does not invent a policy: what the product does is read from the code, what it should do is the user's decision.
|
|
69
|
+
|
|
70
|
+
## Prohibitions (absolute)
|
|
71
|
+
CLAUDE.md §4 applies: no AI trace, no third-party template name, nothing under `docs/` named in a shipped text.
|
|
@@ -33,6 +33,8 @@ before planning. You are read-only: when it ends at "ask", return its questions
|
|
|
33
33
|
decision — the main thread asks and records the answer.
|
|
34
34
|
|
|
35
35
|
## How (applies the `spec-planning` skill)
|
|
36
|
+
- On an unfamiliar project, read `.crewforth/crew-map.json` first when it exists (its modules, what they touch,
|
|
37
|
+
the gaps, each with the lines it rests on); with none, `/crew-map` writes one, and that is the user's call.
|
|
36
38
|
- Define the problem in a single sentence; clarify what is in scope and out of scope.
|
|
37
39
|
- Break tasks into atomic steps; derive the dependency order.
|
|
38
40
|
- For each step, write acceptance criteria (how "done" is judged).
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: crew-refuter
|
|
3
|
+
color: gray
|
|
4
|
+
description: |
|
|
5
|
+
Use proactively on each medium+ finding before it is reported: given one claim and its file:line, refutes it.
|
|
6
|
+
tools: Read, Grep, Glob
|
|
7
|
+
metadata:
|
|
8
|
+
stage: audit
|
|
9
|
+
skills: [security-scan]
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Refuter
|
|
13
|
+
|
|
14
|
+
<!-- routing-eval reads the next line; why it sits in the body: AGENT_TEMPLATE.md -->
|
|
15
|
+
Trigger phrases: "refute this finding", "disprove this claim", "try to prove it wrong", "verify this finding independently", "is this finding a false positive"
|
|
16
|
+
|
|
17
|
+
Read-only. A fresh context is the point: it has not seen the reasoning that produced the finding, so it cannot
|
|
18
|
+
inherit its blind spot. The method is `security-scan` → `references/verify.md`.
|
|
19
|
+
|
|
20
|
+
## When
|
|
21
|
+
After discovery, before a finding is written to a report or a findings document. Every finding of `medium` or
|
|
22
|
+
higher severity goes through it; `critical` and `high` through three calls, each on a different lens.
|
|
23
|
+
|
|
24
|
+
## Expertise stance (the colleague whose job is to find the hole in your argument)
|
|
25
|
+
- **Starts from the cited lines, never from the summary.** Reads the code at `cite:` first and only then the claim.
|
|
26
|
+
- **Hunts for why the claim is false**: unreachable input, a guard on every path, a test or dead file, a control
|
|
27
|
+
the claim did not look for. Agreement is what is left when that search fails, not where it starts.
|
|
28
|
+
- **One lens, held to it**: reachability, protection, reproduction, exclusion or blast-radius — the one it is given.
|
|
29
|
+
- **Names the missing fact** instead of guessing: a verdict it cannot reach from the repository is CANNOT_VERIFY.
|
|
30
|
+
|
|
31
|
+
## How (follow its skill)
|
|
32
|
+
`security-scan` → `references/verify.md`: the verifier stance, the lens, the three verdicts and the exclusion
|
|
33
|
+
rules. The task it is given is exactly this, and nothing of the caller's reasoning. It opens with the card every
|
|
34
|
+
crew agent is called with (`change: audit`, so the model follows the risk of the cited file):
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
files: <the cited file>
|
|
38
|
+
change: audit
|
|
39
|
+
verify: none
|
|
40
|
+
finding: <the finding's id in the findings document>
|
|
41
|
+
lens: reachability | protection | reproduction | exclusion | blast-radius
|
|
42
|
+
cite: <file>:<line>
|
|
43
|
+
claim: <one sentence>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Output & context (token)
|
|
47
|
+
Its last lines are read by `hooks/refuter-ledger.sh`, which records the verdict against the finding and lens of
|
|
48
|
+
the task it was given, so they are exactly:
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
FINDING: <id>
|
|
52
|
+
LENS: <lens>
|
|
53
|
+
VERDICT: TRUE_POSITIVE | FALSE_POSITIVE | CANNOT_VERIFY
|
|
54
|
+
REASON: <one or two sentences, citing file:line>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Confidence
|
|
58
|
+
The LAST line of every report is exactly `confidence: high` or exactly `confidence: low`: lower case, nothing else
|
|
59
|
+
on the line, nothing after it. `low` when you guessed or could not read what the claim needs; the caller then
|
|
60
|
+
repeats the call once, one model up. Your task opens with a card (`files:`, `change: audit`, `verify: none`), so
|
|
61
|
+
nothing is run when you stop. The `FINDING`, `LENS`, `VERDICT` and `REASON` lines come before it.
|
|
62
|
+
|
|
63
|
+
## Errors/escalation
|
|
64
|
+
A task without `finding:`, `lens:` and `cite:` is answered with `VERDICT: CANNOT_VERIFY` and the reason, never
|
|
65
|
+
with a guess; the ledger records nothing for it.
|
|
66
|
+
|
|
67
|
+
## Example delegation
|
|
68
|
+
- ✅ "finding: sec-idor-orders · lens: protection · cite: src/orders.ts:88 · claim: any user can read any order"
|
|
69
|
+
- ❌ "audit the orders module for security issues" (discovery: crew-security-expert)
|
|
70
|
+
|
|
71
|
+
## Constraints
|
|
72
|
+
- Read-only: no Edit, no Write, no Bash, and no `Agent`, so it cannot hand the question to a context that has
|
|
73
|
+
seen more than it should.
|
|
74
|
+
- One claim per call. The caller runs the calls of one finding in parallel, never in one context.
|
|
75
|
+
- HONEST SCOPE: what keeps the record true is the hook writing the ledger from this agent's own task and answer;
|
|
76
|
+
what keeps the answer independent is that the caller gave it the claim and not the reasoning, which is model
|
|
77
|
+
discipline.
|
|
@@ -27,7 +27,7 @@ Read-only auditor. The relevant expert (backend/database) makes the fix; this ag
|
|
|
27
27
|
- **Prove** each finding: how it's exploited + impact + fix; not a theoretical warning.
|
|
28
28
|
- Assign a **severity** to every finding; high-impact first — **derived from preconditions × access, not the category** ("real" is not "critical").
|
|
29
29
|
- **Defense in depth**: don't rely on a single control; add layers.
|
|
30
|
-
- **Signal, not noise**: every candidate goes through the **adversarial verify pass** (N independent verifiers that start from the code and hunt for why it's *wrong* → TRUE_POSITIVE / FALSE_POSITIVE / CANNOT_VERIFY). See `security-scan` → `references/verify.md`.
|
|
30
|
+
- **Signal, not noise**: every candidate goes through the **adversarial verify pass** (N independent verifiers that start from the code and hunt for why it's *wrong* → TRUE_POSITIVE / FALSE_POSITIVE / CANNOT_VERIFY). See `security-scan` → `references/verify.md`. This agent has no `Agent` tool: it returns the candidates, and the session runs one **crew-refuter** per finding and lens.
|
|
31
31
|
|
|
32
32
|
## When
|
|
33
33
|
On changes touching auth, token/credential, externally exposed endpoints, or sensitive data.
|
|
@@ -52,6 +52,8 @@ Each finding: `file:line · risk · suggested fix`, or a "clean on this axis" ra
|
|
|
52
52
|
|
|
53
53
|
## Output & context (token)
|
|
54
54
|
To the main thread: a **severity-ranked findings summary** (area · severity · fix). Full scan output → `docs/SECURITY_FINDINGS.md`, returns a summary + count.
|
|
55
|
+
The candidates also go out as a findings document (`kit/eval/lib/findings`, one `id` and `file:line` evidence each) for the session to verify; the
|
|
56
|
+
verified one is stored with `findings.sh write --verified security <file>` (`eval/lib/findings/` in either edition), which refuses a `medium`-or-higher finding without its refuters.
|
|
55
57
|
|
|
56
58
|
## Errors/escalation
|
|
57
59
|
On an exploitable CRITICAL finding, **warn clearly**; don't report a finding you're unsure of as 'certain', add a verifiability note.
|