crewforth 0.0.1 → 3.0.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +3805 -0
- package/LICENSE +21 -0
- package/README.md +37 -7
- package/VERSION +1 -0
- package/adopt.sh +1739 -0
- package/bin/cli.js +497 -0
- package/kit/AGENT_TEMPLATE.md +92 -0
- package/kit/CLAUDE.md +228 -0
- package/kit/README.md +107 -0
- package/kit/agents/crew-backend-expert.md +95 -0
- package/kit/agents/crew-commit-agent.md +96 -0
- package/kit/agents/crew-database-expert.md +84 -0
- package/kit/agents/crew-devops-expert.md +82 -0
- package/kit/agents/crew-frontend-expert.md +85 -0
- package/kit/agents/crew-performance-expert.md +78 -0
- package/kit/agents/crew-planner.md +60 -0
- package/kit/agents/crew-privacy-agent.md +71 -0
- package/kit/agents/crew-review-agent.md +95 -0
- package/kit/agents/crew-security-expert.md +73 -0
- package/kit/agents/crew-session-manager.md +77 -0
- package/kit/agents/crew-test-expert.md +64 -0
- package/kit/eval/doctor.sh +639 -0
- package/kit/eval/gate-report.sh +137 -0
- package/kit/eval/golden-holdout.txt +53 -0
- package/kit/eval/golden-routing.txt +199 -0
- package/kit/eval/lib/crew-env.sh +14 -0
- package/kit/eval/lib/settings-json.awk +154 -0
- package/kit/eval/lib/skill-listing.awk +43 -0
- package/kit/eval/lib/star.sh +70 -0
- package/kit/eval/parser-conformance.sh +612 -0
- package/kit/eval/preflight.sh +186 -0
- package/kit/eval/routing-eval.sh +209 -0
- package/kit/eval/scan-skill.sh +162 -0
- package/kit/eval/smoke-test.sh +6736 -0
- package/kit/eval/update-guard.sh +81 -0
- package/kit/eval/utilization.sh +130 -0
- package/kit/hooks/board-sync.sh +106 -0
- package/kit/hooks/board.sh +962 -0
- package/kit/hooks/commit-msg +49 -0
- package/kit/hooks/context-usage.sh +353 -0
- package/kit/hooks/floor-blocklist.txt +124 -0
- package/kit/hooks/guard-bash.sh +1433 -0
- package/kit/hooks/guard-commit-scan.sh +511 -0
- package/kit/hooks/guard-write.sh +513 -0
- package/kit/hooks/pre-commit +577 -0
- package/kit/hooks/route-hint.sh +185 -0
- package/kit/hooks/secret-blocklist.txt +82 -0
- package/kit/hooks/session-guard.sh +149 -0
- package/kit/hooks/session-rehydrate.sh +41 -0
- package/kit/hooks/session-stats.sh +252 -0
- package/kit/hooks/session-update-check.sh +236 -0
- package/kit/hooks/skill-trust.sh +163 -0
- package/kit/hooks/trace-blocklist.txt +53 -0
- package/kit/settings.json +132 -0
- package/kit/skills/a11y/SKILL.md +44 -0
- package/kit/skills/adr/SKILL.md +74 -0
- package/kit/skills/api-design/SKILL.md +50 -0
- package/kit/skills/automode-policy/SKILL.md +91 -0
- package/kit/skills/automode-policy/references/policy.json +13 -0
- package/kit/skills/automode-policy/scripts/apply.sh +84 -0
- package/kit/skills/automode-policy/scripts/check.sh +102 -0
- package/kit/skills/backend-architecture/SKILL.md +103 -0
- package/kit/skills/brainstorm/SKILL.md +47 -0
- package/kit/skills/ci-pipeline/SKILL.md +38 -0
- package/kit/skills/commit-message/SKILL.md +82 -0
- package/kit/skills/commit-message/references/examples.md +8 -0
- package/kit/skills/confidence-check/SKILL.md +86 -0
- package/kit/skills/crew-board/SKILL.md +55 -0
- package/kit/skills/crew-brainstorm/SKILL.md +19 -0
- package/kit/skills/crew-code-review/SKILL.md +151 -0
- package/kit/skills/crew-code-review/references/history-search.md +19 -0
- package/kit/skills/crew-code-review/references/panel-mode.md +32 -0
- package/kit/skills/crew-code-review/references/receiving.md +23 -0
- package/kit/skills/crew-doctor/SKILL.md +46 -0
- package/kit/skills/crew-gates/SKILL.md +25 -0
- package/kit/skills/crew-handoff/SKILL.md +14 -0
- package/kit/skills/crew-plan/SKILL.md +18 -0
- package/kit/skills/crew-review/SKILL.md +26 -0
- package/kit/skills/crew-ship/SKILL.md +19 -0
- package/kit/skills/crew-skill/SKILL.md +62 -0
- package/kit/skills/crew-studio/SKILL.md +129 -0
- package/kit/skills/crew-update/SKILL.md +43 -0
- package/kit/skills/db-migration/SKILL.md +120 -0
- package/kit/skills/db-migration/references/backup-restore.md +33 -0
- package/kit/skills/db-migration/references/raw-sql.md +30 -0
- package/kit/skills/db-migration/references/tool-matrix.md +35 -0
- package/kit/skills/dependency-audit/SKILL.md +61 -0
- package/kit/skills/dependency-upgrade/SKILL.md +82 -0
- package/kit/skills/deploy/SKILL.md +100 -0
- package/kit/skills/deploy/references/proxy-ssl.md +56 -0
- package/kit/skills/deploy/references/rollback.md +18 -0
- package/kit/skills/deploy/references/scripts.md +9 -0
- package/kit/skills/deploy/references/topology-a.md +128 -0
- package/kit/skills/deploy/references/topology-b.md +44 -0
- package/kit/skills/docs-writer/SKILL.md +53 -0
- package/kit/skills/docs-writer/references/coauthoring.md +34 -0
- package/kit/skills/eval-grader/SKILL.md +75 -0
- package/kit/skills/eval-grader/references/method.md +51 -0
- package/kit/skills/frontend/SKILL.md +56 -0
- package/kit/skills/frontend/references/verify-contract.md +41 -0
- package/kit/skills/frontend-design/SKILL.md +71 -0
- package/kit/skills/frontend-design/references/design-review.md +34 -0
- package/kit/skills/frontend-rn-expo/SKILL.md +25 -0
- package/kit/skills/handoff/SKILL.md +71 -0
- package/kit/skills/handoff/references/cold-reader.md +51 -0
- package/kit/skills/i18n-integrity/SKILL.md +31 -0
- package/kit/skills/incident-runbook/SKILL.md +40 -0
- package/kit/skills/incident-runbook/references/postmortem.md +16 -0
- package/kit/skills/iterate/SKILL.md +40 -0
- package/kit/skills/mcp-builder/SKILL.md +54 -0
- package/kit/skills/mcp-builder/references/building.md +71 -0
- package/kit/skills/observability/SKILL.md +50 -0
- package/kit/skills/performance/SKILL.md +38 -0
- package/kit/skills/performance/references/bottlenecks.md +10 -0
- package/kit/skills/privacy-compliance/SKILL.md +75 -0
- package/kit/skills/red-team/SKILL.md +48 -0
- package/kit/skills/reflect/SKILL.md +57 -0
- package/kit/skills/release/SKILL.md +44 -0
- package/kit/skills/security-scan/SKILL.md +99 -0
- package/kit/skills/security-scan/references/ai-agents.md +107 -0
- package/kit/skills/security-scan/references/fronts.md +99 -0
- package/kit/skills/security-scan/references/prompting.md +23 -0
- package/kit/skills/security-scan/references/reporting.md +75 -0
- package/kit/skills/security-scan/references/verify.md +131 -0
- package/kit/skills/sonarqube-check/SKILL.md +108 -0
- package/kit/skills/sonarqube-check/references/local-engines.md +32 -0
- package/kit/skills/sonarqube-check/references/stand-up-server.md +25 -0
- package/kit/skills/spec-planning/SKILL.md +92 -0
- package/kit/skills/systematic-debugging/SKILL.md +57 -0
- package/kit/skills/systematic-debugging/references/techniques.md +44 -0
- package/kit/skills/teamboard/SKILL.md +88 -0
- package/kit/skills/teamboard/references/setup.md +37 -0
- package/kit/skills/testing/SKILL.md +49 -0
- package/kit/skills/testing/references/flaky-triage.md +51 -0
- package/kit/skills/threat-model/SKILL.md +57 -0
- package/kit/skills/threat-model/references/interview.md +51 -0
- package/kit/skills/threat-model/references/schema.md +42 -0
- package/kit/skills/token-budget/SKILL.md +51 -0
- package/kit/skills/trace-scan/SKILL.md +43 -0
- package/kit/skills/worktree/SKILL.md +40 -0
- package/kit/studio/README.md +266 -0
- package/kit/studio/ensure-node.sh +471 -0
- package/kit/studio/package.json +14 -0
- package/kit/studio/server/hooks/studio-gate.sh +169 -0
- package/kit/studio/server/index.js +902 -0
- package/kit/studio/server/lib/crew-env.js +11 -0
- package/kit/studio/server/lib/fleet.js +146 -0
- package/kit/studio/server/lib/graph.js +533 -0
- package/kit/studio/server/lib/instance.js +135 -0
- package/kit/studio/server/lib/kit-telemetry.js +182 -0
- package/kit/studio/server/lib/kit.js +158 -0
- package/kit/studio/server/lib/palette.js +118 -0
- package/kit/studio/server/lib/peers.js +72 -0
- package/kit/studio/server/lib/permissions.js +150 -0
- package/kit/studio/server/lib/projects.js +294 -0
- package/kit/studio/server/lib/roster.js +191 -0
- package/kit/studio/server/lib/session.js +309 -0
- package/kit/studio/server/lib/terminal.js +91 -0
- package/kit/studio/server/lib/transcript.js +100 -0
- package/kit/studio/web/app.js +1098 -0
- package/kit/studio/web/canvas.js +1179 -0
- package/kit/studio/web/chat.js +543 -0
- package/kit/studio/web/index.html +86 -0
- package/kit/studio/web/md.js +110 -0
- package/kit/studio/web/storage-migrate.js +23 -0
- package/kit/studio/web/style.css +1453 -0
- package/package.json +46 -8
- package/start.sh +764 -0
- package/bin/crewforth.js +0 -10
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# commit-msg gate, two independent checks:
|
|
3
|
+
# (1) §4.1 trace scanner — AI-authorship traces / vendor names / co-author in the commit message.
|
|
4
|
+
# (2) team board claim gate — a commit that names an item must be backed by a live claim held by YOU.
|
|
5
|
+
# Skipping either requires an explicit request per §4.5 (git commit --no-verify).
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
8
|
+
BL="$HERE/trace-blocklist.txt"
|
|
9
|
+
MSG_FILE="${1:-}"
|
|
10
|
+
[ -n "$MSG_FILE" ] && [ -f "$MSG_FILE" ] || exit 0
|
|
11
|
+
|
|
12
|
+
# (2) Board claim gate. It is a no-op in every repo that never ran `/crew-board init` — board.sh returns 0 when
|
|
13
|
+
# the board ref does not exist, so solo projects and Crewforth's own repo behave exactly as before. It reads the
|
|
14
|
+
# LOCAL ref only: committing must never require the network.
|
|
15
|
+
if [ -f "$HERE/board.sh" ]; then
|
|
16
|
+
bash "$HERE/board.sh" gate "$MSG_FILE" || exit 1
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
[ -f "$BL" ] || exit 0
|
|
20
|
+
|
|
21
|
+
# Repo-root exception list (optional): a FULL-line blocklist pattern is SKIPPED (not part of the payload).
|
|
22
|
+
AL=""
|
|
23
|
+
GITROOT="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
|
24
|
+
[ -n "$GITROOT" ] && [ -f "$GITROOT/.trace-allowlist.txt" ] && AL="$GITROOT/.trace-allowlist.txt"
|
|
25
|
+
|
|
26
|
+
HIT=0
|
|
27
|
+
while IFS= read -r pat || [ -n "$pat" ]; do
|
|
28
|
+
pat="${pat%$'\r'}" # tolerate a CRLF blocklist — pre-commit:176 has had this
|
|
29
|
+
case "$pat" in ''|\#*) continue;; esac # since the gate was written; its twin here did not, and
|
|
30
|
+
# the message scan was the ONLY reader with no fallback:
|
|
31
|
+
# pre-commit never sees the message, so a co-author
|
|
32
|
+
# trailer had exactly one gate and it was blind. Verified
|
|
33
|
+
# on two machines: LF blocklist rejects (rc=1), the same
|
|
34
|
+
# blocklist saved CRLF accepts (rc=0). Reachable without
|
|
35
|
+
# trying: Crewforth writes no .gitattributes into a user's
|
|
36
|
+
# project, Git for Windows defaults core.autocrlf=true,
|
|
37
|
+
# and this file is meant to be edited by hand.
|
|
38
|
+
if [ -n "$AL" ] && grep -qxF -- "$pat" "$AL"; then continue; fi
|
|
39
|
+
if grep -iqE -- "$pat" "$MSG_FILE"; then
|
|
40
|
+
echo "TRACE-SCANNER (message): forbidden expression -> '$pat' (§4.1)"
|
|
41
|
+
HIT=1
|
|
42
|
+
fi
|
|
43
|
+
done < "$BL"
|
|
44
|
+
|
|
45
|
+
if [ "$HIT" -ne 0 ]; then
|
|
46
|
+
echo "Commit message rejected. Write a human, technical message; leave no trace/brand."
|
|
47
|
+
exit 1
|
|
48
|
+
fi
|
|
49
|
+
exit 0
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Measures session context fill for REAL (NOT a guess): reads the API usage of the last main-context
|
|
3
|
+
# turn in the transcript JSONL -> input + cache_read + cache_creation = tokens in the context window.
|
|
4
|
+
# This is the number /context shows; since the assistant cannot run /context, it reads directly from here.
|
|
5
|
+
#
|
|
6
|
+
# Usage:
|
|
7
|
+
# bash context-usage.sh [transcript.jsonl] # if no arg is given, auto-finds from pwd
|
|
8
|
+
# bash context-usage.sh --verbose [transcript.jsonl] # long form, with the raw token counts
|
|
9
|
+
# echo '{"transcript_path":"..."}' | bash context-usage.sh # also accepts hook stdin JSON
|
|
10
|
+
# Window size: CONTEXT_WINDOW env (default 1000000).
|
|
11
|
+
#
|
|
12
|
+
# The default output is COMPACT on purpose. The UserPromptSubmit hook injects this line into the model's
|
|
13
|
+
# context on EVERY turn, and it stays there for the rest of the session — a long line is a per-turn tax that
|
|
14
|
+
# compounds through cache reads. The percentage carries all the signal; the raw counts are for humans, so they
|
|
15
|
+
# live behind --verbose (which is what session-guard.sh uses for its once-per-threshold user warning).
|
|
16
|
+
set -uo pipefail
|
|
17
|
+
# The script's own directory without `dirname`: an external a stripped PATH may lack, and a process on a hook that
|
|
18
|
+
# runs before every prompt. The JSON reader is found relative to it (../eval/lib).
|
|
19
|
+
case "$0" in */*) HERE="${0%/*}" ;; *) HERE=. ;; esac
|
|
20
|
+
HERE="$(cd "$HERE" && pwd)"
|
|
21
|
+
WINDOW="${CONTEXT_WINDOW:-1000000}"
|
|
22
|
+
VERBOSE=0
|
|
23
|
+
case "${1:-}" in --verbose|-v) VERBOSE=1; shift ;; esac
|
|
24
|
+
TR="${1:-}"
|
|
25
|
+
|
|
26
|
+
# A path that arrived inside the hook's stdin JSON is JSON-ENCODED, and on Windows that matters: the real value
|
|
27
|
+
# `C:\Users\me\.claude\projects\p\a.jsonl` is transmitted as `C:\\Users\\me\\...`. Slicing it out with sed hands
|
|
28
|
+
# back the doubled backslashes verbatim, so every `[ -f "$TR" ]` failed and the hook reported "transcript not
|
|
29
|
+
# found" on every single turn — on Windows CLI and on Claude Desktop alike. It read like the hook was being
|
|
30
|
+
# called without stdin; it was being called correctly and then throwing the answer away.
|
|
31
|
+
#
|
|
32
|
+
# So: undo the JSON escaping, then fold the separators. `\\` -> `/` first (the encoded form), then any lone `\`
|
|
33
|
+
# (a value that was never encoded, e.g. an argument typed by hand). Forward slashes resolve on every platform
|
|
34
|
+
# including Git Bash. A POSIX path contains neither, so this is a no-op there.
|
|
35
|
+
unjson_path(){ p="${1//\\\\//}"; p="${p//\\//}"; printf '%s' "$p"; }
|
|
36
|
+
|
|
37
|
+
IN=""
|
|
38
|
+
# 1) transcript_path from hook stdin (if present)
|
|
39
|
+
#
|
|
40
|
+
# `cat` on a pipe blocks until EOF, and "not a tty" does not mean "data is coming". Claude Code always writes
|
|
41
|
+
# the event JSON and closes, so this is invisible in normal use — but any wrapper that leaves stdin open and
|
|
42
|
+
# silent (a background shell, a CI runner, Crewforth's own suite launched as a job) hangs the hook forever, and
|
|
43
|
+
# it is a UserPromptSubmit hook: a hang here is a hang on every turn. Measured 2026-08-24: real JSON stdin 0s,
|
|
44
|
+
# closed stdin 0s, open-and-silent pipe still running after 20 minutes.
|
|
45
|
+
#
|
|
46
|
+
# So bound the wait. `read -t` on the first line costs nothing when the data is already there (the common case
|
|
47
|
+
# by far) and gives up otherwise; the rest is drained without a timeout because a producer that sent a first
|
|
48
|
+
# line is sending the whole object. Timeout is deliberately short — this runs before every prompt.
|
|
49
|
+
if [ -z "$TR" ] && [ ! -t 0 ]; then
|
|
50
|
+
# `read` returns NON-ZERO when it hits EOF without a trailing newline — and it still assigns what it read.
|
|
51
|
+
# Claude Code sends the event JSON with no trailing newline, so gating on the exit status alone throws the
|
|
52
|
+
# whole payload away and every measurement reports "unmeasurable". Measured the wrong way once already: the
|
|
53
|
+
# first version of this fix was timed, not checked for output, so it looked fine and was silently blind.
|
|
54
|
+
_first=""
|
|
55
|
+
IFS= read -r -t "${CREW_STDIN_TIMEOUT:-2}" _first 2>/dev/null || true
|
|
56
|
+
if [ -n "$_first" ]; then
|
|
57
|
+
IN="$_first
|
|
58
|
+
$(cat 2>/dev/null || true)"
|
|
59
|
+
fi
|
|
60
|
+
# Shortest-match parameter expansion, not `sed | head`: same FIRST-occurrence semantics, two processes
|
|
61
|
+
# cheaper, on a hook that runs before every prompt.
|
|
62
|
+
if [ -n "$IN" ]; then
|
|
63
|
+
_r="${IN#*\"transcript_path\"}"
|
|
64
|
+
if [ "$_r" != "$IN" ]; then _r="${_r#*\"}"; TR="${_r%%\"*}"; fi
|
|
65
|
+
fi
|
|
66
|
+
fi
|
|
67
|
+
# --- Permission mode: say it once, while it still saves a turn -----------------------------------------
|
|
68
|
+
# §4.4 already states that the commit gate FAILS CLOSED in auto, dontAsk, plan and bypassPermissions, because
|
|
69
|
+
# in those modes software answers the prompt and nothing can prove a person did. What the session had no way to
|
|
70
|
+
# know was which mode it was IN. guard-bash.sh reads permission_mode, but only when it is already refusing --
|
|
71
|
+
# which is one turn too late. Measured in the field: the user said "commit", the guard refused, the mode was
|
|
72
|
+
# switched, and the command ran again. Everything behaved correctly and a turn was still spent on a fact that
|
|
73
|
+
# was available from the start.
|
|
74
|
+
#
|
|
75
|
+
# The published UserPromptSubmit schema carries `permission_mode`, with a JSON example showing it, so this is
|
|
76
|
+
# read rather than assumed; if it is ever absent the slice comes back empty and nothing is printed.
|
|
77
|
+
#
|
|
78
|
+
# ONCE PER SESSION, not once per turn. The model needs this fact, not a reminder of it, and a line repeated
|
|
79
|
+
# before every prompt in a mode people leave on all day is pure tax. Keyed by the mode itself, so switching
|
|
80
|
+
# modes mid-session announces the new one -- the same marker pattern the stale-discipline gate below uses.
|
|
81
|
+
# Modes where the gate does NOT fail closed say nothing at all: there the prompt reaches a person and there is
|
|
82
|
+
# no turn to save.
|
|
83
|
+
# Placed HERE, before the transcript work, because it must not depend on it. The first version sat further
|
|
84
|
+
# down and never ran on a session whose transcript could not be read -- the hook exits early for that, and a
|
|
85
|
+
# session with no usable transcript is exactly one that has no other way to learn its own mode. It derives its
|
|
86
|
+
# own session id for the same reason: everything below it is allowed to give up.
|
|
87
|
+
if [ -n "${IN:-}" ]; then
|
|
88
|
+
PSID="${IN#*\"session_id\"}"
|
|
89
|
+
[ "$PSID" = "$IN" ] && PSID="" || { PSID="${PSID#*:}"; PSID="${PSID#*\"}"; PSID="${PSID%%\"*}"; }
|
|
90
|
+
case "$PSID" in ''|*[!A-Za-z0-9._-]*) PSID="" ;; esac
|
|
91
|
+
fi
|
|
92
|
+
if [ -n "${IN:-}" ] && [ -n "${PSID:-}" ]; then
|
|
93
|
+
PM="${IN#*\"permission_mode\"}"
|
|
94
|
+
if [ "$PM" != "$IN" ]; then
|
|
95
|
+
PM="${PM#*:}"; PM="${PM#*\"}"; PM="${PM%%\"*}"
|
|
96
|
+
case "$PM" in
|
|
97
|
+
auto|dontAsk|plan|bypassPermissions)
|
|
98
|
+
PMARK="${TMPDIR:-/tmp}/crew-permmode.${PSID}"
|
|
99
|
+
WASM=""
|
|
100
|
+
[ -f "$PMARK" ] && IFS= read -r WASM < "$PMARK" 2>/dev/null
|
|
101
|
+
if [ "$WASM" != "$PM" ]; then
|
|
102
|
+
printf '%s' "$PM" > "$PMARK" 2>/dev/null || true
|
|
103
|
+
echo "🔒 Permission mode: $PM. git commit/push fail closed in this mode (§4.4). Only when a commit or push comes up: say so before you ask for approval; a real yes still comes first, then the user switches mode (or exports CLAUDE_GIT_OK=1 in headless/CI). Otherwise do not mention it."
|
|
104
|
+
fi
|
|
105
|
+
;;
|
|
106
|
+
esac
|
|
107
|
+
fi
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
[ -n "$TR" ] && TR="$(unjson_path "$TR")"
|
|
112
|
+
# 2) still missing: derive the project dir from pwd.
|
|
113
|
+
# ---- CREW-TRANSCRIPT-DIR (kept byte-identical in context-usage.sh and session-stats.sh; smoke-test §6i3 pins it)
|
|
114
|
+
# Claude Code stores a session under $HOME/.claude/projects/<cwd encoded as a directory name>. Reproducing that
|
|
115
|
+
# encoding is the ONLY way a by-hand call (no hook payload on stdin) can find its own transcript.
|
|
116
|
+
crew_project_dirs(){ # candidate directory names for the current cwd, best first, one per line
|
|
117
|
+
local w p
|
|
118
|
+
# Windows first. Claude Code sees the NATIVE cwd (C:\repo\app) while Git Bash's `pwd` reports /c/repo/app, so
|
|
119
|
+
# the drive letter never matched and the by-hand call could not resolve a transcript on Windows AT ALL — the
|
|
120
|
+
# same "transcript not found" was reported from that machine three separate times before the cause was found.
|
|
121
|
+
# `pwd -W` is the MSYS builtin that returns the native form; elsewhere it just fails and is discarded.
|
|
122
|
+
w="$(pwd -W 2>/dev/null || true)"
|
|
123
|
+
# The encoder folds : \ / . AND _ to '-'. The underscore is the one that hides: a project named `report_api`
|
|
124
|
+
# lands in `...-report-api`, so folding only slashes and dots misses every path containing an underscore — on
|
|
125
|
+
# every platform, not just Windows.
|
|
126
|
+
for p in "$w" "$(pwd)"; do
|
|
127
|
+
[ -n "$p" ] && printf '%s\n' "$(printf '%s' "$p" | sed 's#[:\\/._]#-#g')"
|
|
128
|
+
done
|
|
129
|
+
# Legacy shapes, kept so a directory written by an older client still resolves.
|
|
130
|
+
printf '%s\n' "$(pwd | sed 's#[/.]#-#g')" "$(pwd | sed 's#/#-#g')"
|
|
131
|
+
}
|
|
132
|
+
if [ -z "$TR" ]; then
|
|
133
|
+
while IFS= read -r esc; do
|
|
134
|
+
[ -n "$esc" ] || continue
|
|
135
|
+
cand="$(ls -t "$HOME/.claude/projects/$esc"/*.jsonl 2>/dev/null | head -1)"
|
|
136
|
+
[ -n "$cand" ] && { TR="$cand"; break; }
|
|
137
|
+
done <<CREWEOF
|
|
138
|
+
$(crew_project_dirs)
|
|
139
|
+
CREWEOF
|
|
140
|
+
fi
|
|
141
|
+
# ---- /CREW-TRANSCRIPT-DIR
|
|
142
|
+
# Cannot measure. The two call sites want opposite things here, so they get opposite answers.
|
|
143
|
+
#
|
|
144
|
+
# Called BY HAND (a transcript passed as an argument): complain on stderr and exit non-zero. A person who typed a
|
|
145
|
+
# path wants to know it was wrong, and the suite asserts this — "never invent a fill" is checked by watching for
|
|
146
|
+
# a non-zero exit rather than by trusting the absence of output.
|
|
147
|
+
#
|
|
148
|
+
# Called AS A HOOK (payload on stdin, no argument): stay silent and exit 0. Nothing downstream reads the status —
|
|
149
|
+
# session-guard.sh parses the line and falls open without it — while a non-zero exit is a visible error in the
|
|
150
|
+
# user's session, once per turn, for a condition the discipline already handles (no 🔋 line, say so once, drop
|
|
151
|
+
# it). It also stops depending on the `|| true` in the hook command to hide it, which exec-form hooks cannot use.
|
|
152
|
+
if [ -z "$TR" ] || [ ! -f "$TR" ]; then
|
|
153
|
+
[ -n "$IN" ] && exit 0 # hook payload on stdin -> quiet
|
|
154
|
+
echo "context-usage: transcript not found (pass an arg or use hook stdin)" >&2
|
|
155
|
+
exit 1
|
|
156
|
+
fi
|
|
157
|
+
|
|
158
|
+
# Sum of usage for the last main-context turn: a non-sidechain ASSISTANT record that has a cache_read.
|
|
159
|
+
#
|
|
160
|
+
# Both engines require `"type":"assistant"`. When a subagent returns, its tool_result lands in the MAIN context
|
|
161
|
+
# (`isSidechain:false`) as a `type:"user"` record whose `toolUseResult.usage` is raw, unescaped JSON. jq is
|
|
162
|
+
# anchored at `.message.usage` and never saw it, but awk only sees text: it read the SUBAGENT's tokens as the
|
|
163
|
+
# session's. A 92.2%-full context reported 0.9% — "continue" — so the handoff gate stayed silent exactly when it
|
|
164
|
+
# was needed. Reachable by interrupting a subagent, which leaves that record last. The same predicate now guards
|
|
165
|
+
# both engines so they cannot drift; if a record ever lacks `.type` both go quiet, and a hook that says nothing
|
|
166
|
+
# is recoverable in a way that a hook confidently reporting 0.9% is not.
|
|
167
|
+
# ONE ENGINE on every machine: Crewforth's awk JSON reader (eval/lib/settings-json.awk, op=usage). There used to be a
|
|
168
|
+
# jq branch in front of a regex-over-the-line awk scan; the regex matched key names ANYWHERE, so a tool input or
|
|
169
|
+
# result holding an object with "input_tokens" or "type":"assistant" could replace the record's own usage — found in
|
|
170
|
+
# review, 50% read as 0% or 90% on constructed transcripts (0 of 507 real ones differed). The reader parses each
|
|
171
|
+
# candidate record and applies the jq predicate exactly, and it skips the byte window's cut first line.
|
|
172
|
+
# `scan` prints the total of the LAST matching record; callers take the last line with `${x##*NL}`, which is
|
|
173
|
+
# shell, where `| tail -1` was a process on a hook that runs before every prompt.
|
|
174
|
+
last_line() { printf '%s' "${1##*$'\n'}"; }
|
|
175
|
+
scan() { # reads a JSONL tail on stdin, prints the total of the last qualifying record
|
|
176
|
+
# Crewforth's JSON reader, which the plugin also ships at ../eval/lib. Without it there is nothing to read with:
|
|
177
|
+
# print nothing, and the caller reports "usage not found" rather than a number it did not measure.
|
|
178
|
+
[ -f "$HERE/../eval/lib/settings-json.awk" ] || { cat >/dev/null; return 0; }
|
|
179
|
+
awk -v op=usage -f "$HERE/../eval/lib/settings-json.awk" -
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
# Read the TAIL, not the file, and bound it by BYTES not lines. We want the LAST match; a tail hands back the
|
|
183
|
+
# bytes closest to EOF, so a match inside the window IS the last match, and too small a window can only come back
|
|
184
|
+
# empty, never stale. Bytes beat lines because one pasted payload can make a SINGLE JSONL record tens of MB: a
|
|
185
|
+
# line-based `tail -n 200` then drags that whole blob through the scanner (measured: 1.4s for a 60MB paste on a
|
|
186
|
+
# fast box; multiply by a slow Windows fork with no jq and it crosses the 10s hook timeout). A byte tail caps the
|
|
187
|
+
# work no matter how fat the lines are — the same 60MB case scans in ~12ms. A front-truncated partial first line
|
|
188
|
+
# simply fails to match, and since we keep the LAST match that is harmless. Widen once (256 KiB covers even a
|
|
189
|
+
# large subagent fan-out of small usage records), then a SIZE-GUARDED whole-file last resort.
|
|
190
|
+
#
|
|
191
|
+
# The partial first line has to go before the scan, and that is not a tidiness point — it is why the
|
|
192
|
+
# window never worked. The comment above reasons that a front-truncated line "simply fails to match",
|
|
193
|
+
# which is true of awk and FALSE of jq: measured, jq ABORTS the whole stream on a malformed first
|
|
194
|
+
# record and prints nothing, while awk skips the line and carries on. So on every machine that has jq
|
|
195
|
+
# — which is most of them, and every developer laptop — both windows returned empty and the hook fell
|
|
196
|
+
# through to scanning the ENTIRE transcript, every single turn. On a 45 MB session that is 45 MB read
|
|
197
|
+
# per prompt, and the byte bound that this block exists to enforce was never once in force.
|
|
198
|
+
#
|
|
199
|
+
# Dropped with `tail -n +2`, NOT with `${W#*$'\n'}`. The parameter expansion looks free and is a trap:
|
|
200
|
+
# on a window with no newline in it — a malformed transcript that is one enormous line, which the
|
|
201
|
+
# suite fixtures on purpose — bash walks every prefix looking for a match it will never find. Measured
|
|
202
|
+
# on a 4 MiB single-line file: it had not finished after 20 seconds, and it hung the gate for 36
|
|
203
|
+
# minutes before that was noticed. One streaming process is the cheap option here, not the expensive
|
|
204
|
+
# one. Only correct to drop when the tail actually truncated, hence the size test.
|
|
205
|
+
# The size is asked ONCE, up front, and it pays for itself: it is the only exact way to know whether
|
|
206
|
+
# a tail truncated, and knowing that is what lets the window work at all. Comparing the captured
|
|
207
|
+
# length against the window does NOT work — `$( )` strips the trailing newline, so the capture is
|
|
208
|
+
# never quite the window size and the drop never fires.
|
|
209
|
+
SZ="$(wc -c < "$TR" 2>/dev/null)"; SZ="${SZ//[!0-9]/}"; SZ="${SZ:-0}"
|
|
210
|
+
TOTAL=""
|
|
211
|
+
for B in 262144 4194304; do # 256 KiB, then 4 MiB
|
|
212
|
+
if [ "$SZ" -gt "$B" ]; then
|
|
213
|
+
TOTAL="$(tail -c "$B" "$TR" | tail -n +2 | scan)" # the tail cut a line in half; drop it
|
|
214
|
+
else
|
|
215
|
+
TOTAL="$(tail -c "$B" "$TR" | scan)" # whole file: the first line is intact
|
|
216
|
+
fi
|
|
217
|
+
TOTAL="$(last_line "$TOTAL")"
|
|
218
|
+
[ -n "$TOTAL" ] && break
|
|
219
|
+
done
|
|
220
|
+
if [ -z "$TOTAL" ]; then
|
|
221
|
+
# The record is further from EOF than 4 MiB — almost always because the CURRENT turn pasted a huge payload that
|
|
222
|
+
# now sits between EOF and the last assistant record. The whole file WOULD find it, but on a pathologically
|
|
223
|
+
# large transcript that scan is exactly what blows the hook timeout. So bound it: scan the whole file only when
|
|
224
|
+
# it is small enough to finish well inside the timeout (180MB ~= 4.7s under awk, so 200MB is safe under 30s);
|
|
225
|
+
# past the cap, fail OPEN. A missing 🔋 line is recoverable — the model answers "could not measure" — whereas a
|
|
226
|
+
# timed-out hook is just discarded noise.
|
|
227
|
+
CAP="${CREW_CONTEXT_MAX_BYTES:-209715200}" # 200 MiB; override per-repo
|
|
228
|
+
[ "${SZ:-0}" -le "$CAP" ] && TOTAL="$(last_line "$(scan < "$TR")")"
|
|
229
|
+
fi
|
|
230
|
+
# Same split as the missing-transcript case above: a hook stays quiet and exits 0, a by-hand call explains
|
|
231
|
+
# itself and exits non-zero. Exec-form hook commands carry no `|| true` to swallow a status, so anything that
|
|
232
|
+
# exits non-zero here becomes an error banner in the user's session once per turn.
|
|
233
|
+
if [ -z "${TOTAL:-}" ]; then
|
|
234
|
+
[ -n "$IN" ] && exit 0
|
|
235
|
+
# Name the missing reader rather than blaming the transcript's size: without it nothing was read at all.
|
|
236
|
+
if [ ! -f "$HERE/../eval/lib/settings-json.awk" ]; then
|
|
237
|
+
echo "context-usage: Crewforth's JSON reader is missing ($HERE/../eval/lib/settings-json.awk) — update Crewforth" >&2
|
|
238
|
+
exit 1
|
|
239
|
+
fi
|
|
240
|
+
echo "context-usage: usage not found in the byte-bounded window (transcript too large to scan within the hook timeout)" >&2
|
|
241
|
+
exit 1
|
|
242
|
+
fi
|
|
243
|
+
|
|
244
|
+
# LC_ALL=C: force a '.' decimal separator regardless of locale (tr_TR etc. would emit '77,2' and could
|
|
245
|
+
# mis-parse the percentage). Generation AND every comparison below run under C so they stay consistent.
|
|
246
|
+
# Integer arithmetic, not awk, and it ROUNDS rather than truncates — which is the whole reason awk was
|
|
247
|
+
# here. `(t*1000 + w/2) / w` is round-half-up on tenths; verified against awk's %.1f across the range
|
|
248
|
+
# before the swap, not after. That removes the single most expensive process this hook starts on Git
|
|
249
|
+
# Bash (measured there: 57 ms idle, 404 ms under load, against 62 ms for a bare `true`).
|
|
250
|
+
PCT_T=$(( (TOTAL * 1000 + WINDOW / 2) / WINDOW ))
|
|
251
|
+
PCT="$((PCT_T / 10)).$((PCT_T % 10))"
|
|
252
|
+
# The COMPARISONS take the integer part: the thresholds are whole numbers, so nothing below needs the tenth,
|
|
253
|
+
# exactly as session-guard.sh already argues for its own two. This paragraph used to end "the DISPLAYED
|
|
254
|
+
# percentage stays awk's, because %.1f rounds and shell arithmetic truncates" — a true statement about the
|
|
255
|
+
# obvious integer division, and the reason awk survived here for so long. It stopped being true one commit
|
|
256
|
+
# ago: the expression above rounds. A comment that describes a tool the code no longer calls is worse than
|
|
257
|
+
# no comment, because the next reader trusts it instead of the line.
|
|
258
|
+
PCTI="${PCT%%.*}"; case "$PCTI" in ''|*[!0-9]*) PCTI=0 ;; esac
|
|
259
|
+
if [ "$PCTI" -lt 50 ]; then LEVEL="continue"
|
|
260
|
+
elif [ "$PCTI" -lt 75 ]; then LEVEL="medium"
|
|
261
|
+
else LEVEL="handoff+clear"
|
|
262
|
+
fi
|
|
263
|
+
# The '%<number>' shape is a contract: session-guard.sh reads the percentage back out of this line.
|
|
264
|
+
if [ "$VERBOSE" = 1 ]; then
|
|
265
|
+
echo "🔋 Session: %$PCT ($TOTAL/$WINDOW token) → $LEVEL"
|
|
266
|
+
else
|
|
267
|
+
echo "🔋 Session %$PCT → $LEVEL"
|
|
268
|
+
fi
|
|
269
|
+
# >=75%: a short nudge for the model. The USER already gets the full, once-per-threshold warning from the
|
|
270
|
+
# Stop hook (session-guard.sh), so repeating the whole sentence here every turn would be pure duplication.
|
|
271
|
+
if [ "$PCTI" -ge 75 ]; then
|
|
272
|
+
echo "⚠️ >75% — hand off (handoff skill) then /clear; not automatic."
|
|
273
|
+
fi
|
|
274
|
+
|
|
275
|
+
# --- Stale-discipline gate ---------------------------------------------------------------------------
|
|
276
|
+
# CLAUDE.md and the discipline it imports are read ONCE, when the session starts. Update Crewforth while a
|
|
277
|
+
# session is running and every file on disk changes while the rules already in the model's context stay at
|
|
278
|
+
# the old version — it keeps quoting rules that no longer exist, and nothing says so. This does.
|
|
279
|
+
#
|
|
280
|
+
# Only meaningful on the UserPromptSubmit call: session-guard.sh pipes a Stop payload through this same
|
|
281
|
+
# script, and a by-hand run has no stdin at all. Fails open — no stdin, no session_id, no VERSION: silent.
|
|
282
|
+
case "$IN" in *'"hook_event_name"'*UserPromptSubmit*) ;; *) exit 0 ;; esac
|
|
283
|
+
|
|
284
|
+
# The session id is used twice below; computing it once is not a tidiness question. Measured on a corporate
|
|
285
|
+
# Windows machine: a process costs ~290ms there against ~2ms on Linux, so every spawn removed from a hook that
|
|
286
|
+
# runs on EVERY turn is a third of a second the user waits before the model has even started.
|
|
287
|
+
SID=""
|
|
288
|
+
case "$IN" in *'"session_id"'*)
|
|
289
|
+
# Extracted with parameter expansion, not `printf | sed | head | tr`. Four spawns, on every turn, to lift a
|
|
290
|
+
# UUID out of a string the shell already holds. It reads worse and costs ~1.2s a turn on a machine where a
|
|
291
|
+
# process is 290ms. Handles both `"session_id":"x"` and `"session_id": "x"` — the cut is at the next quote
|
|
292
|
+
# after the colon either way.
|
|
293
|
+
SID="${IN#*\"session_id\"}"; SID="${SID#*:}"; SID="${SID#*\"}"; SID="${SID%%\"*}"
|
|
294
|
+
# It becomes a filename, so it is VALIDATED rather than trusted. Anything unexpected falls back to the old
|
|
295
|
+
# scrub — correctness first, and the fallback costs nothing in the normal case because it never runs.
|
|
296
|
+
case "$SID" in
|
|
297
|
+
''|*[!A-Za-z0-9._-]*) SID="$(printf '%s' "$IN" | sed -n 's/.*"session_id"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -1 | tr -cd 'A-Za-z0-9._-')" ;;
|
|
298
|
+
esac ;;
|
|
299
|
+
esac
|
|
300
|
+
|
|
301
|
+
# --- Publish the measurement so the Stop hook does not have to repeat it ------------------------------
|
|
302
|
+
# session-guard.sh needs the same number, and it used to get it by running THIS script again as a nested
|
|
303
|
+
# `bash context-usage.sh --verbose` — a second shell startup plus a second full transcript scan, at the end of
|
|
304
|
+
# every single turn. On the machine above that pair costs seconds per turn, to recompute a figure that was
|
|
305
|
+
# already sitting in memory one hook earlier.
|
|
306
|
+
#
|
|
307
|
+
# The honest cost of sharing it: this reading is taken at the START of the turn, so the Stop hook sees a fill
|
|
308
|
+
# that does not include the turn's own output. It can therefore cross a threshold one turn later than a fresh
|
|
309
|
+
# measurement would. That is acceptable for an advisory warning and is NOT acceptable silently — session-guard
|
|
310
|
+
# falls back to measuring for itself whenever this file is missing, so the accurate path always exists.
|
|
311
|
+
if [ -n "$SID" ]; then
|
|
312
|
+
printf '%s %s %s %s\n' "$PCT" "$TOTAL" "$WINDOW" "$LEVEL" > "${TMPDIR:-/tmp}/crew-context.${SID}" 2>/dev/null || true
|
|
313
|
+
fi
|
|
314
|
+
|
|
315
|
+
# --- Stale-WIRING gate -------------------------------------------------------------------------------
|
|
316
|
+
# A resumed session keeps the hook wiring it was started with. Measured on Windows: settings.json on disk had
|
|
317
|
+
# already been corrected, and `--resume` still produced the error naming the OLD, mangled path — while the same
|
|
318
|
+
# event under a fresh session was clean. So after a kit update, `--resume` carries the previous wiring, and on
|
|
319
|
+
# the release that fixed the Windows path that means the gates are still the broken ones.
|
|
320
|
+
#
|
|
321
|
+
# The obvious gate cannot work: a hook cannot report its own absence, and under the old wiring on Windows NO
|
|
322
|
+
# hook launched at all. What this catches is the other half — a session where the hooks DO run, but not the way
|
|
323
|
+
# the file on disk says they should. `$0` is the evidence: Crewforth wires `bash .claude/hooks/<name>.sh`, so a
|
|
324
|
+
# correctly-launched hook sees a relative `$0`. Anything else means this session was launched from a different
|
|
325
|
+
# settings.json than the one now on disk.
|
|
326
|
+
#
|
|
327
|
+
# Silent unless settings.json actually carries Crewforth's current shape — a project that rewired its hooks by
|
|
328
|
+
# hand is not wrong, and warning it every turn would be noise it cannot fix.
|
|
329
|
+
CREWSET="$HERE/../settings.json"
|
|
330
|
+
if [ -f "$CREWSET" ] && grep -q 'bash \.claude/hooks/context-usage\.sh' "$CREWSET" 2>/dev/null; then
|
|
331
|
+
case "$0" in
|
|
332
|
+
.claude/hooks/*) ;; # launched exactly as the file on disk wires it
|
|
333
|
+
*) echo "⚠️ this session is running OLDER hook wiring than .claude/settings.json on disk (resumed across a Crewforth update). The gates in force are the previous ones — ask the user to quit the CLI and start a NEW session; --resume will not pick up the change." ;;
|
|
334
|
+
esac
|
|
335
|
+
fi
|
|
336
|
+
|
|
337
|
+
# --- Stale-discipline gate ---------------------------------------------------------------------------
|
|
338
|
+
KITVER="$HERE/../VERSION" # hooks live in .claude/hooks -> .claude/VERSION
|
|
339
|
+
[ -f "$KITVER" ] || exit 0
|
|
340
|
+
NOW="$(head -1 "$KITVER" 2>/dev/null | tr -cd '0-9A-Za-z.-')"
|
|
341
|
+
[ -n "$NOW" ] || exit 0
|
|
342
|
+
[ -n "$SID" ] || exit 0 # computed once, above
|
|
343
|
+
MARK="${TMPDIR:-/tmp}/crew-kit-version.${SID}"
|
|
344
|
+
if [ ! -e "$MARK" ]; then
|
|
345
|
+
printf '%s' "$NOW" > "$MARK" 2>/dev/null || true # first turn: remember the version, say nothing
|
|
346
|
+
else
|
|
347
|
+
WAS="$(head -1 "$MARK" 2>/dev/null)"
|
|
348
|
+
# Repeated on every turn on purpose: the loaded context stays stale until a new session starts (/clear or a
|
|
349
|
+
# relaunch). /compact re-reads CLAUDE.md but keeps the session id, so this warning would not stop after it.
|
|
350
|
+
[ -n "$WAS" ] && [ "$WAS" != "$NOW" ] && \
|
|
351
|
+
echo "⚠️ Crewforth updated $WAS → $NOW mid-session. The discipline in your context is the OLD one — do not act on it; ask the user to run /clear (or quit and relaunch Claude Code): a new session loads the new one."
|
|
352
|
+
fi
|
|
353
|
+
exit 0
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Floor guard — moves that LOWER the quality bar on the way to a green check (ERE, case-SENSITIVE).
|
|
2
|
+
#
|
|
3
|
+
# An agent that hits a red check does not invent a clever loophole; it takes the cheapest road to green. That road
|
|
4
|
+
# is nearly always one of a handful of shapes, and every one of them is visible in the lines a commit ADDS: a
|
|
5
|
+
# checker switched off where it fired, a test made to stop running, a stub or a swallowed error standing where the
|
|
6
|
+
# work should be. Tightening the bar needs no gate. Loosening it should be loud. This list is the loud part.
|
|
7
|
+
#
|
|
8
|
+
# Only ADDED lines in project code are scanned. Documentation (.md .mdx .txt .rst .adoc) is not: a suppression
|
|
9
|
+
# written in prose silences nothing. Generated files are not either — a file whose first lines carry a generator
|
|
10
|
+
# marker (`<auto-generated`, `Code generated … DO NOT EDIT`, `@generated`) was not written by the author of this
|
|
11
|
+
# commit, and EF Core alone puts a warning pragma in every model snapshot it emits (395k such files on GitHub,
|
|
12
|
+
# measured), so without that exemption an EF Core project could not commit a migration.
|
|
13
|
+
#
|
|
14
|
+
# A legitimate exception is a tracked decision, not a loosened rule: `.floor-allowlist.txt` at the repo root, one
|
|
15
|
+
# entry per line — `path:<glob>` exempts a path, `rule:<name>` turns one rule off, and any other line exempts the
|
|
16
|
+
# EXACT pattern line below. The allowlist change sits in the same diff as the code it excuses, where review sees it.
|
|
17
|
+
#
|
|
18
|
+
# `path:` IS A SHELL PATTERN, NOT A .gitignore GLOB, and it is wider than it reads. `*` also matches `/`, so
|
|
19
|
+
# `path:*.cs` exempts EVERY C# file in the repository, not the ones at the root; and `**` means nothing special, so
|
|
20
|
+
# `path:**/gen/*` exempts `a/gen/x.cs` but not a root-level `gen/x.cs`. Both were measured on Windows 11 and both err
|
|
21
|
+
# toward exempting more than the author meant, which is the dangerous direction for an exception. Write the narrowest
|
|
22
|
+
# path that names the thing: `path:src/legacy/*`, not `path:*legacy*`.
|
|
23
|
+
#
|
|
24
|
+
# Each pattern carries its own case below it (`#test:` must be caught, `#test-clean:` must not), and smoke-test.sh
|
|
25
|
+
# drives every one through the REAL pre-commit. A `# rule:` line names the rule for the patterns that follow it.
|
|
26
|
+
#
|
|
27
|
+
# A PATTERN MUST NOT BEGIN WITH `#`. Every loop that reads this file treats such a line as a comment and skips
|
|
28
|
+
# it, so `#pragma …` or `# noqa` written plainly is a pattern that looks armed and matches nothing. Write the
|
|
29
|
+
# character as a one-member bracket instead — `[#]pragma` is the same ERE and is read as a pattern.
|
|
30
|
+
#
|
|
31
|
+
# Written for Crewforth:
|
|
32
|
+
# bash rather than Node, the staged diff instead of a merge base, per-ecosystem patterns for the stacks Crewforth
|
|
33
|
+
# supports, and the generated-file and documentation exemptions that stack needs.
|
|
34
|
+
|
|
35
|
+
# rule: silenced-checker
|
|
36
|
+
@ts-(ignore|nocheck)
|
|
37
|
+
#test: const total = compute(order); // @ts-ignore
|
|
38
|
+
#test: /* @ts-nocheck */
|
|
39
|
+
eslint-disable
|
|
40
|
+
#test: // eslint-disable-next-line no-explicit-any
|
|
41
|
+
biome-ignore
|
|
42
|
+
#test: // biome-ignore lint/suspicious/noExplicitAny: legacy
|
|
43
|
+
(istanbul|c8) ignore
|
|
44
|
+
#test: /* istanbul ignore next */
|
|
45
|
+
[#][[:space:]]*noqa
|
|
46
|
+
#test: import os, sys # noqa: E401
|
|
47
|
+
[#][[:space:]]*type:[[:space:]]*ignore
|
|
48
|
+
#test: value = parse(raw) # type: ignore[arg-type]
|
|
49
|
+
[#][[:space:]]*pragma:[[:space:]]*no cover
|
|
50
|
+
#test: def debug_dump(): # pragma: no cover
|
|
51
|
+
pylint:[[:space:]]*disable
|
|
52
|
+
#test: # pylint: disable=broad-except
|
|
53
|
+
[#]pragma[[:space:]]+warning[[:space:]]+disable
|
|
54
|
+
#test: #pragma warning disable CS8618
|
|
55
|
+
SuppressMessage\(
|
|
56
|
+
#test: [SuppressMessage("Design", "CA1031")]
|
|
57
|
+
NOSONAR
|
|
58
|
+
#test: catch (Exception) { throw; } // NOSONAR
|
|
59
|
+
ExcludeFromCodeCoverage
|
|
60
|
+
#test: [ExcludeFromCodeCoverage]
|
|
61
|
+
//[[:space:]]*nolint
|
|
62
|
+
#test: if err := run(); err != nil { //nolint:errcheck
|
|
63
|
+
@SuppressWarnings
|
|
64
|
+
#test: @SuppressWarnings("unchecked")
|
|
65
|
+
@Suppress\(
|
|
66
|
+
#test: @Suppress("UNCHECKED_CAST")
|
|
67
|
+
[#]!?\[allow\(
|
|
68
|
+
#test: #[allow(dead_code)]
|
|
69
|
+
nosemgrep
|
|
70
|
+
#test: eval(userInput) // nosemgrep
|
|
71
|
+
gitleaks:allow
|
|
72
|
+
#test: const sample = "abc"; // gitleaks:allow
|
|
73
|
+
(^|[^A-Za-z0-9_])nosec([^A-Za-z0-9_]|$)
|
|
74
|
+
#test: subprocess.call(cmd, shell=True) # nosec
|
|
75
|
+
Stryker disable
|
|
76
|
+
#test: // Stryker disable next-line all
|
|
77
|
+
#test-clean: // @ts-expect-error: the upstream type is wrong, tracked upstream
|
|
78
|
+
#test-clean: const config = loadEslintConfig(root);
|
|
79
|
+
#test-clean: # the type annotation is ignored by the serializer
|
|
80
|
+
#test-clean: [JsonIgnore]
|
|
81
|
+
#test-clean: const nosecret = readVault();
|
|
82
|
+
|
|
83
|
+
# rule: test-made-easier
|
|
84
|
+
(^|[^A-Za-z0-9_])(it|test|describe|context|suite)\.(skip|only)\(
|
|
85
|
+
#test: it.skip('rejects an expired token', async () => {
|
|
86
|
+
#test: describe.only('checkout', () => {
|
|
87
|
+
(^|[^A-Za-z0-9_.])x(it|describe|test)\(
|
|
88
|
+
#test: xit('rounds half-cents up', () => {
|
|
89
|
+
@pytest\.mark\.skip
|
|
90
|
+
#test: @pytest.mark.skip(reason="flaky on CI")
|
|
91
|
+
(^|[^A-Za-z0-9_])t\.Skip(Now|f)?\(
|
|
92
|
+
#test: t.Skip("needs a database")
|
|
93
|
+
\[(Fact|Theory)\([^]]*Skip[[:space:]]*=
|
|
94
|
+
#test: [Fact(Skip = "broken after the refactor")]
|
|
95
|
+
\[Ignore(\(|\])
|
|
96
|
+
#test: [Ignore("revisit")]
|
|
97
|
+
@(Disabled|Ignore)([^A-Za-z0-9_]|$)
|
|
98
|
+
#test: @Disabled
|
|
99
|
+
#test-clean: return events.skip(1).take(3);
|
|
100
|
+
#test-clean: var page = query.Skip(20).Take(10);
|
|
101
|
+
#test-clean: [JsonIgnore(Condition = JsonIgnoreCondition.Always)]
|
|
102
|
+
#test-clean: it('rejects an expired token', async () => {
|
|
103
|
+
#test-clean: const exit = await shell.exec('npm test');
|
|
104
|
+
|
|
105
|
+
# rule: unfinished-work
|
|
106
|
+
throw[[:space:]]+new[[:space:]]+NotImplementedException
|
|
107
|
+
#test: public Task Handle() => throw new NotImplementedException();
|
|
108
|
+
throw[[:space:]]+new[[:space:]]+Error\([^)]*[Nn]ot[[:space:]]+implemented
|
|
109
|
+
#test: throw new Error("not implemented yet");
|
|
110
|
+
(^|[^A-Za-z0-9_])(todo|unimplemented)!\(
|
|
111
|
+
#test: fn settle(&self) -> Amount { todo!() }
|
|
112
|
+
panic\([^)]*[Nn]ot[[:space:]]+implemented
|
|
113
|
+
#test: panic("not implemented")
|
|
114
|
+
catch[[:space:]]*(\([^)]*\))?[[:space:]]*\{[[:space:]]*\}
|
|
115
|
+
#test: try { save(); } catch (e) {}
|
|
116
|
+
#test: } catch {}
|
|
117
|
+
except[^:]*:[[:space:]]*pass([[:space:]]|$)
|
|
118
|
+
#test: except Exception: pass
|
|
119
|
+
\.catch\([[:space:]]*\([^)]*\)[[:space:]]*=>[[:space:]]*\{[[:space:]]*\}[[:space:]]*\)
|
|
120
|
+
#test: fetchRates().catch(() => {});
|
|
121
|
+
#test-clean: catch (err) { logger.error(err); throw err; }
|
|
122
|
+
#test-clean: raise NotImplementedError # abstract: every provider overrides this
|
|
123
|
+
#test-clean: // TODO(ENG-441): tighten once the migration lands
|
|
124
|
+
#test-clean: const implemented = features.filter(f => f.done);
|