crewforth 3.2.0-rc.1 → 3.2.0-rc.3
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 +133 -3
- package/README.md +2 -0
- package/VERSION +1 -1
- package/adopt.sh +55 -6
- package/bin/cli.js +3 -2
- package/kit/CLAUDE.md +6 -6
- package/kit/agents/crew-review-agent.md +2 -0
- package/kit/eval/doctor.sh +37 -3
- package/kit/eval/gate-report.sh +2 -0
- package/kit/eval/lib/crew-map/narrative.mjs +42 -8
- package/kit/eval/lib/crew-map/project.mjs +3 -2
- package/kit/eval/lib/crew-map/rules/tech.json +35 -0
- package/kit/eval/lib/crew-map/scan.mjs +56 -4
- package/kit/eval/lib/crew-map/stacks.mjs +114 -8
- package/kit/eval/lib/crew-map/surface.mjs +62 -1
- package/kit/eval/lib/gate-log.sh +52 -0
- package/kit/eval/lib/json-string.awk +27 -0
- package/kit/eval/lib/preset.sh +109 -0
- package/kit/eval/lib/presets/default.json +7 -0
- package/kit/eval/lib/presets/solo.json +7 -0
- package/kit/eval/lib/presets/strict.json +11 -0
- package/kit/eval/lib/preview.sh +150 -0
- package/kit/eval/lib/review-bench/bench.mjs +273 -0
- package/kit/eval/lib/review-prepass/prepass.mjs +203 -0
- package/kit/eval/lib/review-prepass/prepass.sh +23 -0
- package/kit/eval/lib/settings-default.sh +36 -0
- package/kit/eval/lib/settings-json.awk +14 -4
- package/kit/eval/lib/statusline.sh +4 -19
- package/kit/eval/lib/verify-cmd.sh +17 -0
- package/kit/eval/lib/version.sh +41 -0
- package/kit/eval/review-bench.sh +26 -0
- package/kit/eval/smoke-test.sh +251 -21
- package/kit/hooks/agent-outcome.sh +20 -9
- package/kit/hooks/board-sync.sh +2 -23
- package/kit/hooks/guard-agent-model.sh +114 -15
- package/kit/hooks/guard-bash.sh +36 -21
- package/kit/hooks/guard-write.sh +3 -7
- package/kit/hooks/precompact-snapshot.sh +75 -0
- package/kit/hooks/session-rehydrate.sh +25 -2
- package/kit/hooks/session-update-check.sh +9 -8
- package/kit/settings.json +12 -0
- package/kit/skills/crew-code-review/SKILL.md +16 -0
- package/kit/skills/crew-map/SKILL.md +16 -5
- package/kit/skills/crew-review/SKILL.md +2 -1
- package/kit/studio/README.md +21 -10
- package/kit/studio/server/index.js +85 -22
- package/kit/studio/server/lib/code-stamp.js +35 -0
- package/kit/studio/server/lib/graph.js +9 -1
- package/kit/studio/server/lib/kit-telemetry.js +30 -5
- package/kit/studio/server/lib/lan.js +87 -0
- package/kit/studio/server/lib/owned-stream.js +56 -0
- package/kit/studio/server/lib/projects.js +11 -4
- package/kit/studio/server/lib/redact.js +60 -0
- package/kit/studio/server/lib/remote.js +104 -18
- package/kit/studio/server/lib/session.js +1 -1
- package/kit/studio/server/lib/terminal.js +51 -13
- package/kit/studio/server/lib/transcript.js +32 -0
- package/kit/studio/web/app.js +79 -11
- package/kit/studio/web/canvas.js +14 -2
- package/kit/studio/web/chat.js +114 -24
- package/kit/studio/web/graph-plan.js +3 -2
- package/kit/studio/web/liveness.js +14 -0
- package/kit/studio/web/map-plan.js +72 -3
- package/kit/studio/web/map.js +103 -10
- package/kit/studio/web/qr.js +200 -0
- package/kit/studio/web/style.css +40 -0
- package/kit/studio/web/tokens.css +6 -0
- package/package.json +1 -1
- package/start.sh +14 -1
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,81 @@ versioning follows [SemVer](https://semver.org/).
|
|
|
9
9
|
|
|
10
10
|
### Added
|
|
11
11
|
|
|
12
|
+
- **Watch Studio from a phone on the same Wi-Fi.** A viewer is a second token that only reads: the Machines tab makes
|
|
13
|
+
one, lists each with its last request, and revokes it. **Add viewer** shows its link as a QR code; a phone on
|
|
14
|
+
the same network scans it and the panel opens there, with no ssh and no tunnel. While a viewer is live the server
|
|
15
|
+
also listens on the machine's private Wi-Fi address, for viewer tokens only (the panel's own token gets `401` from
|
|
16
|
+
the network); with none, nothing listens beyond `127.0.0.1`. A viewer lasts eight hours, then the phone shows
|
|
17
|
+
Session expired; revoking it closes its stream at once, and the last one closes the Wi-Fi listener. Transcripts
|
|
18
|
+
are hidden by default: the server sends such a link no transcript text at all (an allow-list of fields, applied to
|
|
19
|
+
every answer and to the event stream), and shows them only for a link added with them ticked. Every viewer
|
|
20
|
+
connection and refusal is logged. Measured with an Android phone; the firewall asks once (macOS: allow `node`;
|
|
21
|
+
Windows: Private networks).
|
|
22
|
+
|
|
23
|
+
- **The advisor, measured.** `review-bench --advisor <model>` runs the agent arm with an advisor and reports each
|
|
24
|
+
run's cost by model and its advisor calls; a case of `kind: "task"` is an implementation graded by a check the
|
|
25
|
+
session never saw. Against Opus alone on five review cases and a task (24 runs), Sonnet with an Opus advisor
|
|
26
|
+
never called it: 6 of 6 defects caught against 5 of 6, for $1.06 against $2.23. No rule is added; the runs and
|
|
27
|
+
their numbers are in `packaging/advisor-bench/`.
|
|
28
|
+
|
|
29
|
+
- **An organisation preset.** `--preset <name|file>` on the installers (and `preset.sh --apply` later) writes an
|
|
30
|
+
organisation's critical paths, model floors, verify commands and language as one marked block at the top of
|
|
31
|
+
`.claude/crew-model-rules`; the project's own lines stay below it and win where the rules let them. Three ship:
|
|
32
|
+
`default`, `strict`, `solo`. An invalid preset is refused before anything is installed; `doctor` names the one
|
|
33
|
+
in force. It covers the surfaces the gates read today; which agents a project gets is not part of it.
|
|
34
|
+
|
|
35
|
+
- **Continue here says what it will do before it is pressed.** Studio continues a session it did not start as
|
|
36
|
+
that same session when nothing else holds it open, and starts a copy when something does; the read-only foot now
|
|
37
|
+
says which it will be before the press, from the check it acts on, and offers the command that opens the session
|
|
38
|
+
in the desktop app again (`claude --desktop --resume <id>`) to copy. Measured with a real session: its id stayed
|
|
39
|
+
the same from a terminal, through Studio, and back to a terminal, in one transcript.
|
|
40
|
+
On Windows the desktop command is a PowerShell line, and the terminal Studio opens runs `claude --resume <id>`
|
|
41
|
+
without single quotes, which cmd.exe handed to the program as part of the id. An id that is not a session id
|
|
42
|
+
builds no command at all.
|
|
43
|
+
The terminal Studio opens runs `claude --resume <id>` and nothing else: the route no longer takes a `command`
|
|
44
|
+
from the request, which it ran unchecked and the panel never sent.
|
|
45
|
+
|
|
46
|
+
- **A stopped command says what it would have touched.** When the shell gate stops `git reset --hard`,
|
|
47
|
+
`git clean -f`, `git push --force` or `rm -r`, or asks about a commit or a push, one line says what it would
|
|
48
|
+
have done: the uncommitted changes and commits it discards, the paths a dry run lists, the remote commits it
|
|
49
|
+
overwrites, the files under a target inside the project, what a commit takes and a push sends. Read only, after
|
|
50
|
+
the decision and in a subshell of its own, so a failing preview never changes the decision. The push question
|
|
51
|
+
became cheaper too: the branch is read from HEAD without a process.
|
|
52
|
+
|
|
53
|
+
- **What a compaction keeps is written down first.** A PreCompact hook writes the state of the work from the files
|
|
54
|
+
and the gates (branch, uncommitted files, the last gate refusal, the findings on record, a handover if there is
|
|
55
|
+
one) inside the repository's git directory, and SessionStart gives it back after the compaction, to the same
|
|
56
|
+
session only. It never blocks the compaction. Studio's session card adds how much of the context since the last
|
|
57
|
+
compaction is tool output, and how many times the session was compacted.
|
|
58
|
+
|
|
59
|
+
- **The Map reads like a project, not a graph.** Studio's Map draws modules in the layers `/crew-map` wrote (UI,
|
|
60
|
+
API, Service, Data, Infrastructure, Library, Tools), each card naming the module in full with its domain, up to
|
|
61
|
+
three of what it is built with and the outside services it calls folded into one line under it. A search dims
|
|
62
|
+
what it does not name; the tour `/crew-map` wrote steps through the map over the bottom of the pane; the
|
|
63
|
+
inspector shows the layer, the domain and what is inside a module (its entry point, its folders and their file
|
|
64
|
+
counts). A map with no layers is drawn in the bands the scan gives, as before.
|
|
65
|
+
|
|
66
|
+
- **The Crew Map says where each module sits, and how to read the project.** `/crew-map` now has the model add,
|
|
67
|
+
to each module, its layer (`ui`, `api`, `service`, `data`, `infra`, `tool`, `library`) and the part of the
|
|
68
|
+
business it serves, and to the map a tour of up to eight stops a newcomer reads in order. Each rests on lines
|
|
69
|
+
the scan read; `--validate` refuses a layer not on the list, a domain written as a sentence, a tour stop at no
|
|
70
|
+
node, citing another node's lines or visiting a node twice, and a scan again drops what no longer holds.
|
|
71
|
+
A module's README is read too: its first line of prose is a line a description may rest on (measured on a real
|
|
72
|
+
project, four of ten modules had nothing else to describe them by).
|
|
73
|
+
|
|
74
|
+
- **How right the review is, in numbers.** `eval/review-bench.sh` runs the review on labelled cases and reports
|
|
75
|
+
recall, precision and every false alarm by name; the pre-pass arm costs nothing, the agent arm runs Claude Code
|
|
76
|
+
headless as the review agent under a per-case and a per-run dollar cap, isolated from the user's own settings.
|
|
77
|
+
Crewforth's 27 cases come from its own history (a fix undone, its explanation left out) and 8 are held out; the
|
|
78
|
+
pre-pass's floor on them is a CI gate. Measured with Sonnet: 18 of 20 defects found, 34 of 49 findings about the defect, $1.62.
|
|
79
|
+
|
|
80
|
+
- **The review starts from a deterministic pre-pass.** `eval/lib/review-prepass/prepass.sh` reads the lines a
|
|
81
|
+
change adds (staged, or `--base <ref>` for a branch) and writes `.crewforth/review-prepass.json` in the findings
|
|
82
|
+
shape: a secret pattern (cited by hash), a debug call or a TODO comment left in code, shellcheck on shell files, a lock
|
|
83
|
+
file that moved without its manifest, a very large change. The review agent carries those over and writes its
|
|
84
|
+
own findings to `.crewforth/review.json`, blockers through the refuter. What it could not check (no shellcheck,
|
|
85
|
+
a file edited after staging, no Node) is said, never read as clean.
|
|
86
|
+
|
|
12
87
|
- **In `auto`, approve a commit or a push with one click.** The refusal now also offers a fixed question, asked with
|
|
13
88
|
AskUserQuestion: Commit only, Commit + push, Push only, No. A new gate, `guard-question.sh`, answers `ask` on it,
|
|
14
89
|
so a hook that fills in the answer cannot answer it for you (measured in 34 runs and by hand in the terminal), and
|
|
@@ -60,9 +135,6 @@ versioning follows [SemVer](https://semver.org/).
|
|
|
60
135
|
- **Studio's Models view marks the classes whose agents consulted the advisor.** The cost cell carries an advisor tag
|
|
61
136
|
whose hover says how many calls and how much of the cost they were, already included; a class whose agents consulted
|
|
62
137
|
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
138
|
- **A visible way to the repository.** `/crew-doctor` now shows the star line once per version under its own marker,
|
|
67
139
|
so a line already shown at install no longer silences it, and a plugin install shows it too, with the plugin's
|
|
68
140
|
version. `/crew-update` keeps it to one line per update. The READMEs and the site's home page have a Star on GitHub
|
|
@@ -79,6 +151,17 @@ versioning follows [SemVer](https://semver.org/).
|
|
|
79
151
|
|
|
80
152
|
### Changed
|
|
81
153
|
|
|
154
|
+
- **A model is chosen to cost what the work needs.** Measured on a real project: 186 crew tasks, 0 on haiku, 73 (39%)
|
|
155
|
+
above their card's floor, and no verify command ever passing, so the cheaper path never opened. Now:
|
|
156
|
+
- the card's model is the default: a call above it runs on it (the gate rewrites the call's model), unless the
|
|
157
|
+
card adds `above: <why>`;
|
|
158
|
+
- outside the crew, a call asking for opus or fable runs on sonnet, and so does a general-purpose call that names
|
|
159
|
+
no model in a session that is not on haiku (it took the session's model; measured on Claude Code 2.1.299, the
|
|
160
|
+
project's settings `env` could not change that, so the gate does);
|
|
161
|
+
- a verify command in a sub-folder (`cd apps/x && <command>`, 41 of 71 refusals) is run in that folder;
|
|
162
|
+
`none (why)` is none; and `/crew-doctor` lists the commands that were never allowed to run, with the line that
|
|
163
|
+
allows each.
|
|
164
|
+
|
|
82
165
|
- **A commit's command is read by one reader.** `guard-bash.sh` paired quotes in three places: the reader of a call,
|
|
83
166
|
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
167
|
last two now take their text from the first, and keep only their own question. What a call the user approved may
|
|
@@ -114,6 +197,53 @@ versioning follows [SemVer](https://semver.org/).
|
|
|
114
197
|
|
|
115
198
|
### Fixed
|
|
116
199
|
|
|
200
|
+
- **The Crew Map says what a project is made of.** A Flutter project read as a pile of Gradle modules and its
|
|
201
|
+
server as an external API (seen on a real project). The scanner now reads Dart: one module per `pubspec.yaml`,
|
|
202
|
+
its packages and `path:` references, and a Flutter app's `android/` and `ios/` folders as platforms of the app.
|
|
203
|
+
Each module says what it is built with (Hono, Cloudflare Workers, Flutter, Riverpod…, from `rules/tech.json`)
|
|
204
|
+
and what is inside it (folders with file counts, tests apart). A module served at a host its wrangler routes or
|
|
205
|
+
`fly.toml` name is called, module to module, by any module that writes that host; a Dart call to that host or a
|
|
206
|
+
Dio call's relative path is matched to the endpoints provided. A `.dart` file no `pubspec.yaml` owns is listed
|
|
207
|
+
as not measured, as is Dart package usage: Flutter declares packages nothing imports.
|
|
208
|
+
|
|
209
|
+
- **A commit message and a reworded paragraph no longer go to the expensive model.** Two floors of the model gate
|
|
210
|
+
raised prose for no gain (seen on a real project): with no verify command a commit message was held to sonnet,
|
|
211
|
+
though the commit hook checks it, and a two-sentence fix under a critical path such as `legal/` was held to opus.
|
|
212
|
+
A text change now runs on haiku without a verify command, the commit agent too; on a critical path a text change
|
|
213
|
+
whose every file is prose (`.md`, `.txt`, `.rst`, `.adoc`) is written on sonnet, and the audit of it stays on opus.
|
|
214
|
+
One code file on the card keeps it on opus, and an agent on sonnet still writes no code there. A project that
|
|
215
|
+
wants opus for such text says so in `.claude/crew-model-rules` (`floor * text critical opus`).
|
|
216
|
+
- **The gate log is written in every project, and Studio shows a refused agent call.** A project that tracks
|
|
217
|
+
`.claude/` had no gate log at all: no record of a refusal, no 🛡 on the status line, no note of a model lowered to
|
|
218
|
+
its card's floor (seen on a real project). The log now goes inside the repository's own `.git` there
|
|
219
|
+
(`crewforth-gate-log.tsv`, never committed); one rule (`eval/lib/gate-log.sh`) serves every gate and reader. A
|
|
220
|
+
call the model gate refused starts no agent, so the canvas showed nothing while the session said "2 failed"; the
|
|
221
|
+
session's card now says how many calls were refused and not made again, with the gate's words on hover.
|
|
222
|
+
- **`/crew-doctor` offers a verify rule that works, in every mode.** For `cd sub && ./check.sh` it offered the line
|
|
223
|
+
with the `cd` in it, which allows nothing (the rule names the command alone), and in default mode it offered
|
|
224
|
+
nothing, because that refusal is worded differently. Measured on Windows.
|
|
225
|
+
- **Studio keeps answering with many conversations open.** Each conversation pane held its own stream, and a
|
|
226
|
+
browser opens six connections to one host: at five panes a message sent and every request the page made waited
|
|
227
|
+
for a pane to close (on Windows a send did not arrive in 35 s; on macOS the page's own request timed out at 4 s).
|
|
228
|
+
Every open conversation now shares one stream, resumed from each session's last event when it reconnects.
|
|
229
|
+
- **Studio finds sessions under a moved config dir.** It read `~/.claude/projects` only, so sessions under
|
|
230
|
+
`CLAUDE_CONFIG_DIR` were not listed and read a cost of 0. It now reads `projects` there, as Claude Code writes.
|
|
231
|
+
- **Studio says when it needs a restart.** A running server keeps the code it started with, and an update
|
|
232
|
+
replaces the files the page is read from: on a real project the Map view of the new page asked a server from
|
|
233
|
+
before it and showed "Not read — HTTP 404". The server now says whether its code changed on disk since it
|
|
234
|
+
started; a page that hears so, or talks to a server too old to say, shows a note to restart it, and Map says the
|
|
235
|
+
same instead of the 404.
|
|
236
|
+
- **Two agents starting together no longer share one model record on Windows.** A record was taken by renaming
|
|
237
|
+
it, which is a lock on macOS and Linux; on Windows two hook processes both reported the rename as done (seen
|
|
238
|
+
twice on the Windows CI). Each record is now locked first with an exclusive create, a shell builtin that costs no
|
|
239
|
+
process, and only the hook that made the lock takes it.
|
|
240
|
+
- **An rc says it is one, and its final is offered.** A release candidate installed `.claude/VERSION` as the final's
|
|
241
|
+
number (3.2.0 for 3.2.0-rc.1), so the update check, doctor and the refresh's "what's new" all read it as the final,
|
|
242
|
+
and someone who tried the rc was never told the final shipped. The rc's archive and npm package now carry its own
|
|
243
|
+
version, and one definition (`eval/lib/version.sh`) orders an rc before its final. A version off the network is
|
|
244
|
+
still held to X.Y.Z.
|
|
245
|
+
- **`adopt.sh --help` prints its usage.** It ran the project detection instead; the usage now comes in English and
|
|
246
|
+
Turkish, before anything is read.
|
|
117
247
|
- **The crew map reads what two real projects showed it missing.** Scanned before release, they put three rules
|
|
118
248
|
well over a 20% false-positive rate, and one scan refused its own document. Read now: a route with an empty path,
|
|
119
249
|
a decorator whose path is on the next line, `api.get<T>(` and a call split over lines, a client made in one file
|
package/README.md
CHANGED
|
@@ -33,6 +33,8 @@ Then open Claude Code and run `/crew-doctor` to confirm the setup. Requires bash
|
|
|
33
33
|
- **40 skills** holding the method: testing, migrations, API contracts, observability, accessibility, deployment.
|
|
34
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`.
|
|
35
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.
|
|
36
|
+
- A stopped destructive command says what it would have touched; a review starts from a deterministic pre-pass, and `crew-refuter` tries to refute each finding that blocks.
|
|
37
|
+
- **Organisation presets.** `npx crewforth init --preset strict` (or `solo`, or your own JSON file) carries critical paths, model floors and verify commands to every project.
|
|
36
38
|
- **Safe adoption.** `adopt` lands everything on a branch, staged and uncommitted; `main` is never touched.
|
|
37
39
|
|
|
38
40
|
## Updating
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.2.0
|
|
1
|
+
3.2.0-rc.3
|
package/adopt.sh
CHANGED
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
set -uo pipefail
|
|
12
12
|
# The 2.x names of the variables a user can set still work (one helper: eval/lib/crew-env.sh).
|
|
13
13
|
_crew_d="${BASH_SOURCE%/*}"; [ "$_crew_d" = "${BASH_SOURCE}" ] && _crew_d=.
|
|
14
|
-
[ -f "$_crew_d/kit/eval/lib/crew-env.sh" ] && . "$_crew_d/kit/eval/lib/crew-env.sh"
|
|
14
|
+
[ -f "$_crew_d/kit/eval/lib/crew-env.sh" ] && . "$_crew_d/kit/eval/lib/crew-env.sh"
|
|
15
|
+
[ -f "$_crew_d/kit/eval/lib/version.sh" ] && . "$_crew_d/kit/eval/lib/version.sh"; unset _crew_d # an rc orders before its final
|
|
15
16
|
HERE="$(CDPATH= cd "$(dirname "$0")" && pwd)"
|
|
16
17
|
|
|
17
18
|
# --version (or -v) is answered first, before the payload check below: it reads only VERSION, so it works
|
|
@@ -23,24 +24,68 @@ for _a in "$@"; do
|
|
|
23
24
|
fi
|
|
24
25
|
done
|
|
25
26
|
|
|
27
|
+
# --help (or -h) is answered here too, before anything is read: an unknown flag is ignored further down, so without
|
|
28
|
+
# this `adopt.sh --help` ran the detection instead of saying what the script does. The language comes from --lang or
|
|
29
|
+
# CREW_LANG; the rest of the script asks for it later, which is too late for a help text.
|
|
30
|
+
_hl="${CREW_LANG:-}"; _hh=0; _ht=0
|
|
31
|
+
for _a in "$@"; do
|
|
32
|
+
if [ "$_ht" = 1 ]; then _ht=0; _hl="$_a"; continue; fi
|
|
33
|
+
case "$_a" in -h|--help) _hh=1 ;; --lang) _ht=1 ;; --lang=*) _hl="${_a#--lang=}" ;; esac
|
|
34
|
+
done
|
|
35
|
+
if [ "$_hh" = 1 ]; then
|
|
36
|
+
if [ "$_hl" = tr ]; then
|
|
37
|
+
cat <<'USAGE_TR'
|
|
38
|
+
Kullanım: bash adopt.sh [SEÇENEKLER] (ya da: npx crewforth adopt [SEÇENEKLER])
|
|
39
|
+
Crewforth'u MEVCUT bir projeye kurar ya da kurulu olanı yeniler. Önce projeyi okur ve ne yapacağını gösterir;
|
|
40
|
+
projenin kendi dosyalarına, hook'larına ve status line'ına dokunmaz. Değişiklikleri hazırlar (stage), commit atmaz.
|
|
41
|
+
--here değişiklikleri şu anki dalda yap
|
|
42
|
+
--new-branch değişiklikleri yeni bir inceleme dalında yap (ilk kurulumda varsayılan)
|
|
43
|
+
--yes, -y her soruya evet de (gözetimsiz kurulum)
|
|
44
|
+
--lang tr|en dil (verilmezse sorulur)
|
|
45
|
+
--version, -v Crewforth sürümünü yazdırıp çık
|
|
46
|
+
--help, -h bu metni yazdırıp çık
|
|
47
|
+
USAGE_TR
|
|
48
|
+
else
|
|
49
|
+
cat <<'USAGE'
|
|
50
|
+
Usage: bash adopt.sh [OPTIONS] (or: npx crewforth adopt [OPTIONS])
|
|
51
|
+
Installs Crewforth into an EXISTING project, or refreshes an install. It reads the project first and shows what it
|
|
52
|
+
will do; the project's own files, hooks and status line are left alone. Changes are staged, never committed.
|
|
53
|
+
--here make the changes on the current branch
|
|
54
|
+
--new-branch make the changes on a fresh review branch (the default for a first adopt)
|
|
55
|
+
--yes, -y answer yes at every gate (unattended)
|
|
56
|
+
--lang tr|en language (asked when not given)
|
|
57
|
+
--version, -v print Crewforth's version and exit
|
|
58
|
+
--help, -h print this text and exit
|
|
59
|
+
USAGE
|
|
60
|
+
fi
|
|
61
|
+
exit 0
|
|
62
|
+
fi
|
|
63
|
+
unset _hl _hh _ht
|
|
64
|
+
|
|
26
65
|
SRC="$HERE/kit"
|
|
27
66
|
[ -d "$SRC" ] || { echo "ERROR: kit/ not found (must be in the same directory as adopt.sh)."; exit 1; }
|
|
28
67
|
|
|
29
68
|
# --- flags (B5 — where a refresh lands) --- --here: the current branch · --new-branch: a fresh review branch.
|
|
30
69
|
# Left empty, Stage 2 picks a smart default (first adopt -> new; update + untracked .claude -> here; update +
|
|
31
70
|
# tracked -> ask). Unknown flags are ignored here (start.sh owns --backend/--frontend/… ; adopt auto-detects shape).
|
|
32
|
-
BRANCH_MODE=""; ASSUME_YES=0
|
|
71
|
+
BRANCH_MODE=""; ASSUME_YES=0; PRESET=""; _preset_take=0
|
|
33
72
|
_lang_flag=""; _lang_take=0
|
|
34
73
|
for _a in "$@"; do
|
|
35
74
|
# `--lang` with no value must not swallow the next flag: `--lang --yes` is --yes with no language given.
|
|
36
75
|
if [ "$_lang_take" = 1 ]; then _lang_take=0; case "$_a" in -*) ;; *) _lang_flag="$_a"; continue ;; esac; fi
|
|
76
|
+
if [ "$_preset_take" = 1 ]; then _preset_take=0; PRESET="$_a"; continue; fi
|
|
37
77
|
case "$_a" in
|
|
78
|
+
--preset=*) PRESET="${_a#--preset=}" ;;
|
|
79
|
+
--preset) _preset_take=1 ;;
|
|
38
80
|
--here) BRANCH_MODE=here ;;
|
|
39
81
|
--new-branch) BRANCH_MODE=new ;;
|
|
40
82
|
--yes|-y) ASSUME_YES=1 ;; # assume "yes" at every gate — for agent-driven / CI updates (no TTY to prompt)
|
|
41
83
|
--lang=*) _lang_flag="${_a#--lang=}" ;;
|
|
42
84
|
--lang) _lang_take=1 ;;
|
|
43
85
|
esac; done
|
|
86
|
+
[ "$_preset_take" = 1 ] && { echo "ERROR: --preset needs a name or a file (default, strict, solo, or ./org.json)."; exit 1; }
|
|
87
|
+
# A preset is checked before anything is changed: one that is not valid stops the update here.
|
|
88
|
+
if [ -n "$PRESET" ]; then bash "$SRC/eval/lib/preset.sh" --check "$PRESET" || exit 1; fi
|
|
44
89
|
|
|
45
90
|
# ---- CREW-I18N (the twin of start.sh's; see the long note there for why the English string is the key) ----
|
|
46
91
|
# Short version, because the reasoning belongs in one place: `m 'text'` prints the translation of that text
|
|
@@ -1535,19 +1580,23 @@ fi
|
|
|
1535
1580
|
# set up in Turkish to English from its first unattended update on.
|
|
1536
1581
|
[ "$_LANG_CHOSEN" = 1 ] && echo "lang=$CREW_LANG"
|
|
1537
1582
|
} > .claude/kit.conf
|
|
1583
|
+
# The organisation's preset (checked before anything was changed): a block at the top of crew-model-rules. Without
|
|
1584
|
+
# --preset an update leaves the block that is there, with the project's own lines, as they were.
|
|
1585
|
+
if [ -n "$PRESET" ]; then bash .claude/eval/lib/preset.sh --apply "$PRESET" || exit 1; fi
|
|
1538
1586
|
# What changed, for /crew-update to report: the package's own CHANGELOG sections newer than the version this
|
|
1539
1587
|
# project had, and no newer than the one being installed — read from the package on disk, never from the network.
|
|
1540
1588
|
# A fresh adopt has no "before", so it gets no file (and a stale one from an earlier update is removed).
|
|
1541
1589
|
rm -f .claude/.state/whats-new.md 2>/dev/null
|
|
1542
1590
|
_kv="$(printf '%s' "$KIT_VER" | tr -d '\r')"
|
|
1543
|
-
|
|
1591
|
+
_kv="$(crew_local_version "$_kv" 2>/dev/null)" || _kv="" # an odd old version compares as 0.0.0 — every section would look new
|
|
1544
1592
|
if [ "$KIT_PRESENT" = 1 ] && [ -n "$_kv" ] && [ -f "$HERE/CHANGELOG.md" ] && [ -f "$HERE/VERSION" ]; then
|
|
1545
1593
|
mkdir -p .claude/.state 2>/dev/null
|
|
1546
|
-
|
|
1547
|
-
|
|
1594
|
+
# The upper bound is the target's X.Y.Z: an rc's notes are its final's section ("[Unreleased] — 3.2.0").
|
|
1595
|
+
awk -v from="$_kv" -v to="$(head -1 "$HERE/VERSION" | tr -d '\r')" "${CREW_VCMP_AWK:-}"'
|
|
1596
|
+
BEGIN { sub(/-.*/, "", to) }
|
|
1548
1597
|
/^## \[/ { v=$0; sub(/^## \[/,"",v); w=v; sub(/\].*/,"",v)
|
|
1549
1598
|
if (v=="Unreleased") { v=w; sub(/^[^—]*— */,"",v); sub(/[^0-9.].*/,"",v) }
|
|
1550
|
-
keep = (v ~ /^[0-9]+\.[0-9]+\.[0-9]+$/ &&
|
|
1599
|
+
keep = (v ~ /^[0-9]+\.[0-9]+\.[0-9]+$/ && vcmp(v,from)>0 && vcmp(v,to)<=0) }
|
|
1551
1600
|
keep' "$HERE/CHANGELOG.md" | tr -d '\r' > .claude/.state/whats-new.md 2>/dev/null
|
|
1552
1601
|
[ -s .claude/.state/whats-new.md ] || rm -f .claude/.state/whats-new.md
|
|
1553
1602
|
fi
|
package/bin/cli.js
CHANGED
|
@@ -18,12 +18,13 @@ if (sub === '--help' || sub === '-h' || sub === 'help') {
|
|
|
18
18
|
console.log(`Crewforth
|
|
19
19
|
|
|
20
20
|
Usage:
|
|
21
|
-
npx crewforth [init] [--private|--shared] [--yes]
|
|
21
|
+
npx crewforth [init] [--private|--shared] [--yes] [--preset <name|file.json>]
|
|
22
22
|
Set up Crewforth in a fresh project (start.sh wizard). Every install ships everything and
|
|
23
23
|
is stack-agnostic: the stack is recorded per project in CLAUDE.md ## Stack.
|
|
24
24
|
(--dotnet was removed in 3.0; it is still accepted, warns, and installs the same setup.)
|
|
25
|
-
npx crewforth adopt
|
|
25
|
+
npx crewforth adopt [--preset <name|file.json>]
|
|
26
26
|
Hand Crewforth over onto an existing project (adopt.sh).
|
|
27
|
+
--preset applies an organisation preset (default, strict, solo, or your own JSON file).
|
|
27
28
|
npx crewforth@latest update
|
|
28
29
|
Refresh a project that already has Crewforth. Alias of 'adopt': it reads .claude/kit.conf,
|
|
29
30
|
migrates a pre-3.0 .NET install (its pattern skill stays, as a project skill), and
|
package/kit/CLAUDE.md
CHANGED
|
@@ -55,16 +55,16 @@ sequence. All are installed: "no owner" is an `ls`.
|
|
|
55
55
|
|
|
56
56
|
**Model per call — RISK decides, not size.** A `crew-*` task opens with its card, and the call names `model`:
|
|
57
57
|
`files:` files/globs · `change:` text|feature|fix-known|fix-unknown|refactor|migration|security|architecture|test-run|test-write|audit
|
|
58
|
-
· `verify:` the command that proves it, or `none`. **crew-planner** writes the card
|
|
58
|
+
· `verify:` the command that proves it, or `none`. **crew-planner** writes the card, or you do.
|
|
59
59
|
|
|
60
|
-
| `model` | the card |
|
|
60
|
+
| `model` (`guard-agent-model.sh`: floor = default; `above: <why>` for more) | the card |
|
|
61
61
|
|---|---|
|
|
62
|
-
| `haiku` | text, test-run, mechanical work
|
|
63
|
-
| `sonnet` | feature, fix-known, refactor, test-write, audit, review;
|
|
64
|
-
| `opus` | **critical**: migration, security, architecture, fix-unknown, or a
|
|
62
|
+
| `haiku` | text, commit agent; test-run, mechanical work with `verify:` |
|
|
63
|
+
| `sonnet` | feature, fix-known, refactor, test-write, audit, review; other `verify: none`; critical-path prose |
|
|
64
|
+
| `opus` | **critical**: migration, security, architecture, fix-unknown, or a critical-path file (auth, payments, legal…), and its referees |
|
|
65
65
|
|
|
66
66
|
The referee follows the RISK, not the author's model. Verify fails → the agent fixes once; again → re-run ONCE one
|
|
67
|
-
model up; again → AskUserQuestion. The same for a report that ends `confidence: low`.
|
|
67
|
+
model up; again → AskUserQuestion. The same for a report that ends `confidence: low`.
|
|
68
68
|
|
|
69
69
|
**Naming an agent in prose is a hope; `@agent-<name>` is a guarantee** — measured here: 0/3 vs 3/3. Use that form
|
|
70
70
|
whenever an agent must run, and tell the user they can too.
|
|
@@ -30,6 +30,8 @@ Read-only; the trigger for the `crew-code-review` skill.
|
|
|
30
30
|
Before a work package closes (pre-commit), on the changed diff.
|
|
31
31
|
|
|
32
32
|
## How (applies the `crew-code-review` skill)
|
|
33
|
+
- First the deterministic pre-pass (`eval/lib/review-prepass/prepass.sh`), carried over as it is; your own findings
|
|
34
|
+
go to `.crewforth/review.json` in the same findings shape, blockers through crew-refuter (skill §0).
|
|
33
35
|
- Simplicity: flag when 200 lines could be 50.
|
|
34
36
|
- Surgical: catch out-of-scope touches.
|
|
35
37
|
- Readability: naming, dead code, comment traps (S125 — commented-out code-like prose).
|
package/kit/eval/doctor.sh
CHANGED
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
set -uo pipefail
|
|
8
8
|
# The 2.x names of the variables a user can set still work (one helper: eval/lib/crew-env.sh).
|
|
9
9
|
_crew_d="${BASH_SOURCE%/*}"; [ "$_crew_d" = "${BASH_SOURCE}" ] && _crew_d=.
|
|
10
|
-
[ -f "$_crew_d/lib/crew-env.sh" ] && . "$_crew_d/lib/crew-env.sh"
|
|
10
|
+
[ -f "$_crew_d/lib/crew-env.sh" ] && . "$_crew_d/lib/crew-env.sh"
|
|
11
|
+
[ -f "$_crew_d/lib/version.sh" ] && . "$_crew_d/lib/version.sh" # an rc orders before its final
|
|
12
|
+
_DOC_LIB="$(cd "$_crew_d/lib" 2>/dev/null && pwd)"; unset _crew_d # absolute: the doctor cd's into ROOT below
|
|
11
13
|
ROOT="${1:-.}"
|
|
12
14
|
cd "$ROOT" 2>/dev/null || { echo "doctor: cannot enter '$ROOT'"; exit 2; }
|
|
13
15
|
|
|
@@ -106,6 +108,8 @@ _mt() {
|
|
|
106
108
|
'advisor: off (CLAUDE_CODE_DISABLE_ADVISOR_TOOL=1)') s='advisor: kapalı (CLAUDE_CODE_DISABLE_ADVISOR_TOOL=1)' ;;
|
|
107
109
|
"status line: Crewforth's (from %s)") s="durum satırı: Crewforth'unki (%s içinden)" ;;
|
|
108
110
|
"status line: your own (from %s); Crewforth's is not used") s="durum satırı: seninki (%s içinden); Crewforth'unki kullanılmıyor" ;;
|
|
111
|
+
"organisation preset: %s") s="kurum preset'i: %s" ;;
|
|
112
|
+
"verify commands never run, %s time(s), because nothing allows them; to let the hook run one, add its line to .claude/crew-model-rules: %s") s="doğrulama komutları %s kez hiç koşmadı, çünkü hiçbir şey izin vermiyor; hook'un koşturması için satırını .claude/crew-model-rules dosyasına ekle: %s" ;;
|
|
109
113
|
"status line: none; Crewforth's: bash .claude/eval/lib/statusline.sh --install (Bash tool, not PowerShell)") s="durum satırı: yok; Crewforth'unki: bash .claude/eval/lib/statusline.sh --install (Bash tool, not PowerShell)" ;;
|
|
110
114
|
"advisor: off (no advisorModel set; Crewforth never turns it on)") s='advisor: kapalı (advisorModel ayarlı değil; Crewforth onu hiç açmaz)' ;;
|
|
111
115
|
'advisorModel is %s in %s, but CLAUDE_CODE_DISABLE_ADVISOR_TOOL=1 turns the advisor off') s="advisorModel %s (%s içinde), ama CLAUDE_CODE_DISABLE_ADVISOR_TOOL=1 advisor'ı kapatıyor" ;;
|
|
@@ -218,8 +222,7 @@ if [ -f .claude/VERSION ] && [ -f .claude/.state/update-check ]; then
|
|
|
218
222
|
read -r DLATEST _ < .claude/.state/update-check 2>/dev/null || DLATEST=""
|
|
219
223
|
DLATEST="$(printf '%s' "${DLATEST:-}" | tr -cd '0-9A-Za-z.-')"
|
|
220
224
|
DCUR="$(head -1 .claude/VERSION 2>/dev/null | tr -cd '0-9A-Za-z.-')"
|
|
221
|
-
if [ -n "$DLATEST" ] && awk -v a="$DLATEST" -v b="$DCUR" 'BEGIN{
|
|
222
|
-
for(i=1;i<=3;i++){if(x[i]+0>y[i]+0)exit 0; if(x[i]+0<y[i]+0)exit 1} exit 1}'; then
|
|
225
|
+
if [ -n "$DLATEST" ] && awk -v a="$DLATEST" -v b="$DCUR" "${CREW_VCMP_AWK:-}"' BEGIN { exit !(vcmp(a, b) > 0) }' 2>/dev/null; then
|
|
223
226
|
warn "Crewforth v%s installed, v%s published — update with /crew-update" "$DCUR" "$DLATEST"
|
|
224
227
|
fi
|
|
225
228
|
elif [ -f .claude/VERSION ]; then
|
|
@@ -772,6 +775,37 @@ case "$_sl" in
|
|
|
772
775
|
*) [ -f .claude/eval/lib/statusline.sh ] && ok "status line: none; Crewforth's: bash .claude/eval/lib/statusline.sh --install (Bash tool, not PowerShell)" ;;
|
|
773
776
|
esac
|
|
774
777
|
|
|
778
|
+
# 9e) What model routing costs. Advisory. A verify command the hook was never allowed to run cannot vouch for
|
|
779
|
+
# cheaper work: the ones refused for that reason are listed with the line that allows them (measured on one
|
|
780
|
+
# project: 23 of 71 refusals).
|
|
781
|
+
[ -f .claude/eval/lib/verify-cmd.sh ] && . .claude/eval/lib/verify-cmd.sh # the rule names the command a cd prefix leads to
|
|
782
|
+
_vbn=0; _vbl=""
|
|
783
|
+
for _f in .claude/state/crew-model/results/*; do
|
|
784
|
+
[ -f "$_f" ] || continue
|
|
785
|
+
_vb=0; _vw=""; _vc=""
|
|
786
|
+
while IFS= read -r _l || [ -n "$_l" ]; do
|
|
787
|
+
case "$_l" in verify=blocked) _vb=1 ;; whynot=*) _vw="${_l#whynot=}" ;; cmd=*) _vc="${_l#cmd=}" ;; esac
|
|
788
|
+
done < "$_f"
|
|
789
|
+
[ "$_vb" = 1 ] || continue
|
|
790
|
+
# Both ways of saying "nothing allows it": in auto/bypassPermissions (not on the runner list) and in the other
|
|
791
|
+
# modes (Claude Code itself would not run it unallowed). Measured on Windows: the second was not listed at all.
|
|
792
|
+
case "$_vw" in *"not a test or build command on this hook's list"*|*"does not run a command the user has not allowed"*) ;; *) continue ;; esac
|
|
793
|
+
# A rule names the command alone: `cd sub && ./check.sh` is allowed by `verify ./check.sh` (measured on Windows:
|
|
794
|
+
# the line with the cd in it did not allow it).
|
|
795
|
+
declare -F crew_verify_split >/dev/null && { crew_verify_split "$_vc" .; _vc="$_AMVC"; }
|
|
796
|
+
_vc="${_vc:0:80}"; [ -n "$_vc" ] || continue
|
|
797
|
+
_vbn=$((_vbn+1))
|
|
798
|
+
case "$_vc" in *'('*|*' + '*) continue ;; esac # a sentence, not a command: counted, not offered as a rule
|
|
799
|
+
case "$_vbl" in *"| verify $_vc |"*) ;; *) _vbl="$_vbl| verify $_vc |" ;; esac
|
|
800
|
+
done
|
|
801
|
+
if [ "$_vbn" -gt 0 ]; then
|
|
802
|
+
_vsep=' || '; _vdot=' · '; _vbs="${_vbl//"$_vsep"/$_vdot}"; _vbs="${_vbs#| }"; _vbs="${_vbs% |}"; _vbs="${_vbs:0:300}"
|
|
803
|
+
warn "verify commands never run, %s time(s), because nothing allows them; to let the hook run one, add its line to .claude/crew-model-rules: %s" "$_vbn" "$_vbs"
|
|
804
|
+
fi
|
|
805
|
+
# The organisation preset in force: the block preset.sh wrote at the top of crew-model-rules, asked of preset.sh itself.
|
|
806
|
+
_pre=""; [ -n "$_DOC_LIB" ] && [ -f "$_DOC_LIB/preset.sh" ] && _pre="$(bash "$_DOC_LIB/preset.sh" --show 2>/dev/null)"
|
|
807
|
+
ok "organisation preset: %s" "${_pre:-none}"
|
|
808
|
+
|
|
775
809
|
# 10) Gate activity. The suite proves the gates CAN fire; this reports whether anything actually tripped them.
|
|
776
810
|
# Recording is on by default (rule names only, never the command), so "no log" here means no gate has
|
|
777
811
|
# fired yet — a measured zero, not a gap. Never a failure either way.
|
package/kit/eval/gate-report.sh
CHANGED
|
@@ -29,6 +29,8 @@ HOOKS="./.claude/hooks"; [ -d "$HOOKS" ] || HOOKS="./hooks"
|
|
|
29
29
|
[ -f "$HOOKS/guard-bash.sh" ] || { echo "gate-report: guard-bash.sh not found (run from the project root)" >&2; exit 4; }
|
|
30
30
|
|
|
31
31
|
# Default log location, so `--log` is not required once logging is on.
|
|
32
|
+
# The same rule the gates write by (eval/lib/gate-log.sh): .claude/gate-log.tsv, or the repository's .git.
|
|
33
|
+
if [ -z "$LOG" ] && [ -f ./.claude/eval/lib/gate-log.sh ]; then . ./.claude/eval/lib/gate-log.sh; crew_gate_log_read; LOG="$_CREW_GL"; fi
|
|
32
34
|
[ -z "$LOG" ] && [ -f "./.claude/gate-log.tsv" ] && LOG="./.claude/gate-log.tsv"
|
|
33
35
|
|
|
34
36
|
TMP="${TMPDIR:-/tmp}/crew-gate-$$"; mkdir -p "$TMP"; trap 'rm -rf "$TMP"' EXIT
|
|
@@ -5,18 +5,27 @@
|
|
|
5
5
|
// model fill them, and nothing else:
|
|
6
6
|
// node.narrative { text, cites } what the module is for, in at most two sentences; `cites` are "file:line"
|
|
7
7
|
// strings, each one a line the scanner itself cited for THAT node (its evidence)
|
|
8
|
+
// and, optionally, `layer` (one of LAYERS: where the module sits) and `domain` (what part of the
|
|
9
|
+
// business it serves, a short phrase), both resting on the same cites
|
|
8
10
|
// doc.overview { text, cites } a short orientation for someone new to the project; its cites may be any
|
|
9
11
|
// node's evidence
|
|
12
|
+
// doc.tour [{ node, text, cites }] one to eight stops a newcomer reads in order: each names a node of the
|
|
13
|
+
// map, says in at most two sentences why to look there, and cites that node's own lines
|
|
10
14
|
// A narrative with no citation, or one citing a line the scan did not read, is refused: the model may describe
|
|
11
15
|
// what the scan found, not add to it. A scanner field changed by hand is refused too (validate).
|
|
12
16
|
|
|
13
17
|
const MAX_NODE = 400;
|
|
14
18
|
const MAX_OVERVIEW = 2000;
|
|
19
|
+
const MAX_STOP = 300;
|
|
20
|
+
const MAX_STOPS = 8;
|
|
21
|
+
const MAX_DOMAIN = 60;
|
|
22
|
+
export const LAYERS = ['ui', 'api', 'service', 'data', 'infra', 'tool', 'library'];
|
|
15
23
|
|
|
16
|
-
/** The "file:line" a
|
|
24
|
+
/** The "file:line" a narrative of this node may cite. */
|
|
17
25
|
export function citesOf(node) {
|
|
18
26
|
const out = new Set();
|
|
19
|
-
|
|
27
|
+
// Its evidence, its entry point and what its README says about it: every line the scan read for this node.
|
|
28
|
+
for (const e of [node?.evidence, node?.entryPoint?.evidence, node?.about?.evidence].flatMap((x) => (Array.isArray(x) ? x : []))) {
|
|
20
29
|
if (e && typeof e.file === 'string' && Number.isInteger(e.line)) out.add(`${e.file}:${e.line}`);
|
|
21
30
|
}
|
|
22
31
|
return out;
|
|
@@ -35,11 +44,14 @@ const sentences = (t) => (String(t).trim().match(/[.!?](\s|$)/g) ?? []).length;
|
|
|
35
44
|
* Why a narrative is not acceptable, or null when it is (null itself is: not written yet).
|
|
36
45
|
* @param allowed the set of "file:line" it may cite
|
|
37
46
|
*/
|
|
38
|
-
export function problemOf(n, allowed, { max = MAX_NODE, twoSentences = true } = {}) {
|
|
47
|
+
export function problemOf(n, allowed, { max = MAX_NODE, twoSentences = true, module = false } = {}) {
|
|
39
48
|
if (n === null || n === undefined) return null;
|
|
40
49
|
if (typeof n !== 'object' || Array.isArray(n)) return 'is not { text, cites }';
|
|
41
|
-
const
|
|
42
|
-
|
|
50
|
+
const may = module ? ['cites', 'domain', 'layer', 'text'] : ['cites', 'text'];
|
|
51
|
+
const odd = Object.keys(n).filter((k) => !may.includes(k));
|
|
52
|
+
if (odd.length || !('text' in n) || !('cites' in n)) return `has fields other than ${may.join(', ')} (${Object.keys(n).sort().join()})`;
|
|
53
|
+
if ('layer' in n && !LAYERS.includes(n.layer)) return `names the layer ${JSON.stringify(n.layer)}, not one of ${LAYERS.join(', ')}`;
|
|
54
|
+
if ('domain' in n && (typeof n.domain !== 'string' || !n.domain.trim() || n.domain.length > MAX_DOMAIN || /[.!?]/.test(n.domain))) return `has a domain that is not a short phrase (at most ${MAX_DOMAIN} characters, no sentence)`;
|
|
43
55
|
if (typeof n.text !== 'string' || !n.text.trim()) return 'has no text';
|
|
44
56
|
if (n.text.length > max) return `is ${n.text.length} characters, more than ${max}`;
|
|
45
57
|
if (twoSentences && sentences(n.text) > 2) return 'is more than two sentences';
|
|
@@ -52,7 +64,24 @@ export function problemOf(n, allowed, { max = MAX_NODE, twoSentences = true } =
|
|
|
52
64
|
/** The document without what the model may add: the scanner's fields alone. */
|
|
53
65
|
export function strip(doc) {
|
|
54
66
|
if (!doc || typeof doc !== 'object') return doc;
|
|
55
|
-
return { ...doc, nodes: (doc.nodes ?? []).map((n) => ({ ...n, narrative: null })), overview: null };
|
|
67
|
+
return { ...doc, nodes: (doc.nodes ?? []).map((n) => ({ ...n, narrative: null })), overview: null, tour: null };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Why a tour is not acceptable, or null when it is (null itself is: not written yet). */
|
|
71
|
+
export function tourProblem(tour, doc) {
|
|
72
|
+
if (tour === null || tour === undefined) return null;
|
|
73
|
+
if (!Array.isArray(tour) || !tour.length || tour.length > MAX_STOPS) return `is not a list of 1 to ${MAX_STOPS} stops`;
|
|
74
|
+
const byId = new Map((doc?.nodes ?? []).map((n) => [n.id, n]));
|
|
75
|
+
const seen = new Set();
|
|
76
|
+
for (const [i, s] of tour.entries()) {
|
|
77
|
+
if (!s || typeof s !== 'object' || Array.isArray(s) || Object.keys(s).sort().join() !== 'cites,node,text') return `stop ${i + 1} is not { node, text, cites }`;
|
|
78
|
+
if (!byId.has(s.node)) return `stop ${i + 1} names ${JSON.stringify(s.node)}, which is not a node of this map`;
|
|
79
|
+
if (seen.has(s.node)) return `stop ${i + 1} names ${s.node} again: a tour stops at a node once`;
|
|
80
|
+
seen.add(s.node);
|
|
81
|
+
const p = problemOf({ text: s.text, cites: s.cites }, citesOf(byId.get(s.node)), { max: MAX_STOP });
|
|
82
|
+
if (p) return `stop ${i + 1} (${s.node}) ${p}`;
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
56
85
|
}
|
|
57
86
|
|
|
58
87
|
/**
|
|
@@ -65,7 +94,7 @@ export function carry(old, fresh) {
|
|
|
65
94
|
const nodes = fresh.nodes.map((n) => {
|
|
66
95
|
const nar = was.get(n.id);
|
|
67
96
|
if (nar === null || nar === undefined) return n;
|
|
68
|
-
if (problemOf(nar, citesOf(n)) === null) { kept += 1; return { ...n, narrative: nar }; }
|
|
97
|
+
if (problemOf(nar, citesOf(n), { module: n.kind === 'module' }) === null) { kept += 1; return { ...n, narrative: nar }; }
|
|
69
98
|
dropped += 1; return n;
|
|
70
99
|
});
|
|
71
100
|
const doc = { ...fresh, nodes };
|
|
@@ -73,6 +102,9 @@ export function carry(old, fresh) {
|
|
|
73
102
|
if (problemOf(old.overview, allCites(doc), { max: MAX_OVERVIEW, twoSentences: false }) === null) { doc.overview = old.overview; kept += 1; }
|
|
74
103
|
else dropped += 1;
|
|
75
104
|
}
|
|
105
|
+
if (old?.tour != null) {
|
|
106
|
+
if (tourProblem(old.tour, doc) === null) { doc.tour = old.tour; kept += 1; } else dropped += 1;
|
|
107
|
+
}
|
|
76
108
|
return { doc, kept, dropped };
|
|
77
109
|
}
|
|
78
110
|
|
|
@@ -89,10 +121,12 @@ export function validate(file, fresh, serialize) {
|
|
|
89
121
|
}
|
|
90
122
|
const byId = new Map((fresh.nodes ?? []).map((n) => [n.id, n]));
|
|
91
123
|
for (const n of file.nodes ?? []) {
|
|
92
|
-
const p = problemOf(n.narrative, citesOf(byId.get(n.id) ?? n));
|
|
124
|
+
const p = problemOf(n.narrative, citesOf(byId.get(n.id) ?? n), { module: (byId.get(n.id) ?? n).kind === 'module' });
|
|
93
125
|
if (p) out.push({ at: n.id, message: `the narrative ${p}` });
|
|
94
126
|
}
|
|
95
127
|
const p = problemOf(file.overview, allCites(fresh), { max: MAX_OVERVIEW, twoSentences: false });
|
|
96
128
|
if (p) out.push({ at: 'overview', message: `the overview ${p}` });
|
|
129
|
+
const t = tourProblem(file.tour, fresh);
|
|
130
|
+
if (t) out.push({ at: 'tour', message: `the tour ${t}` });
|
|
97
131
|
return out;
|
|
98
132
|
}
|
|
@@ -130,14 +130,15 @@ export function risky(text, patterns) {
|
|
|
130
130
|
// A host is the only part of a connection string or URL the map keeps. Credentials are cut off first; then a host
|
|
131
131
|
// that looks like a secret by Crewforth's own scanner patterns is replaced. Without those patterns every host is
|
|
132
132
|
// replaced: a map with fewer names is a smaller loss than one that published a token.
|
|
133
|
-
|
|
133
|
+
// skip: pattern lines left out, as written (a project's .secret-allowlist.txt names the exact line it allows).
|
|
134
|
+
export function loadSecretPatterns(fromDir, skip = new Set()) {
|
|
134
135
|
for (const rel of ['../../../hooks/secret-blocklist.txt', '../../../../hooks/secret-blocklist.txt']) {
|
|
135
136
|
const f = path.join(fromDir, rel);
|
|
136
137
|
let text; try { text = fs.readFileSync(f, 'utf8'); } catch { continue; }
|
|
137
138
|
const out = [];
|
|
138
139
|
for (const raw of text.split('\n')) {
|
|
139
140
|
const l = raw.replace(/\r$/, '');
|
|
140
|
-
if (!l || l.startsWith('#')) continue;
|
|
141
|
+
if (!l || l.startsWith('#') || skip.has(l)) continue;
|
|
141
142
|
const js = l.replace(/\[\[:alnum:\]\]/g, '[A-Za-z0-9]').replace(/\[:alnum:\]/g, 'A-Za-z0-9')
|
|
142
143
|
.replace(/\[:space:\]/g, '\\s').replace(/\[:alpha:\]/g, 'A-Za-z').replace(/\[:digit:\]/g, '0-9');
|
|
143
144
|
try { out.push(new RegExp(js)); } catch { /* a pattern JS cannot read is skipped; the rest still apply */ }
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"about": "What a module is built with, in the words a newcomer reads: a package a manifest declares, or a file the module holds, gives a label. One list for every stack; a label is shown once per module, frameworks before tools, in the order of this file. Versions are not part of a label: the manifest line the label was read from is the module's evidence.",
|
|
3
|
+
"packages": {
|
|
4
|
+
"npm": [
|
|
5
|
+
["next", "Next.js"], ["react", "React"], ["vue", "Vue"], ["svelte", "Svelte"], ["@angular/core", "Angular"],
|
|
6
|
+
["astro", "Astro"], ["react-native", "React Native"], ["expo", "Expo"], ["electron", "Electron"],
|
|
7
|
+
["hono", "Hono"], ["express", "Express"], ["fastify", "Fastify"], ["@nestjs/core", "NestJS"], ["koa", "Koa"],
|
|
8
|
+
["graphql", "GraphQL"], ["socket.io", "Socket.IO"], ["wrangler", "Cloudflare Workers"],
|
|
9
|
+
["@prisma/client", "Prisma"], ["prisma", "Prisma"], ["drizzle-orm", "Drizzle"], ["typeorm", "TypeORM"],
|
|
10
|
+
["mongoose", "Mongoose"], ["tailwindcss", "Tailwind CSS"], ["vite", "Vite"], ["typescript", "TypeScript"],
|
|
11
|
+
["vitest", "Vitest"], ["jest", "Jest"], ["@playwright/test", "Playwright"]
|
|
12
|
+
],
|
|
13
|
+
"pypi": [
|
|
14
|
+
["django", "Django"], ["fastapi", "FastAPI"], ["flask", "Flask"], ["sqlalchemy", "SQLAlchemy"],
|
|
15
|
+
["celery", "Celery"], ["pydantic", "Pydantic"], ["langchain", "LangChain"], ["torch", "PyTorch"],
|
|
16
|
+
["transformers", "Transformers"], ["spacy", "spaCy"], ["uvicorn", "Uvicorn"], ["pytest", "pytest"]
|
|
17
|
+
],
|
|
18
|
+
"pub": [
|
|
19
|
+
["flutter_riverpod", "Riverpod"], ["hooks_riverpod", "Riverpod"], ["riverpod", "Riverpod"],
|
|
20
|
+
["flutter_bloc", "Bloc"], ["bloc", "Bloc"], ["provider", "Provider"], ["get", "GetX"], ["go_router", "go_router"],
|
|
21
|
+
["dio", "Dio"], ["http", "http"], ["drift", "Drift"], ["sqflite", "sqflite"], ["hive", "Hive"], ["isar", "Isar"],
|
|
22
|
+
["firebase_core", "Firebase"], ["freezed", "Freezed"], ["google_mobile_ads", "Google Mobile Ads"],
|
|
23
|
+
["in_app_purchase", "In-app purchase"], ["flutter_test", "flutter_test"]
|
|
24
|
+
],
|
|
25
|
+
"go": [
|
|
26
|
+
["github.com/gin-gonic/gin", "Gin"], ["github.com/labstack/echo/v4", "Echo"], ["github.com/go-chi/chi/v5", "chi"],
|
|
27
|
+
["gorm.io/gorm", "GORM"]
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
["wrangler.toml", "Cloudflare Workers"], ["wrangler.jsonc", "Cloudflare Workers"], ["tsconfig.json", "TypeScript"],
|
|
32
|
+
["Dockerfile", "Docker"], ["vercel.json", "Vercel"], ["netlify.toml", "Netlify"], ["fly.toml", "Fly.io"],
|
|
33
|
+
["firebase.json", "Firebase"]
|
|
34
|
+
]
|
|
35
|
+
}
|