crewforth 3.2.0-rc.6 → 3.2.0-rc.8
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 +10 -0
- package/VERSION +1 -1
- package/kit/eval/lib/crew-map/surface.mjs +19 -2
- package/kit/eval/smoke-test.sh +19 -0
- package/kit/hooks/guard-bash.sh +20 -2
- package/kit/hooks/prompt-approval.sh +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -220,6 +220,16 @@ versioning follows [SemVer](https://semver.org/).
|
|
|
220
220
|
|
|
221
221
|
### Fixed
|
|
222
222
|
|
|
223
|
+
- **Crew Map reads a `fetch` call's method from its own options only.** The method was taken from the next four
|
|
224
|
+
lines, so in a file of one-line calls a `{ method: 'POST' }` on a later call was given to the earlier ones, and the
|
|
225
|
+
map reported `POST /api/products is called but no endpoint in this repo provides it` (endpoint-missing) for a
|
|
226
|
+
plain `fetch('/api/products')`. The scan now reads the call's arguments up to its own closing parenthesis, over
|
|
227
|
+
strings and nested brackets; a method on a later line of the same call is still read.
|
|
228
|
+
- **A push approval covers a tag on the approved commit.** `git push <remote> <tag>` had no way to be approved: the
|
|
229
|
+
click and `/crew-approve push` both record a push of the branch, and the tag push of a release was refused after
|
|
230
|
+
either, so the session handed the user the command to run. It now passes when the tag points at the commit the
|
|
231
|
+
approval covers (after a commit + push, the new one) and no branch has its name; a tag on another commit, a full
|
|
232
|
+
ref, a refspec and `--tags` are still refused.
|
|
223
233
|
- **`change: review` is read as `audit`.** The model table of the working rules names review with audit, and
|
|
224
234
|
`crew-review-agent` cards that said `change: review` were refused. It now takes the audit floor, on a critical path
|
|
225
235
|
too; a kind that is not on the list is still refused.
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.2.0-rc.
|
|
1
|
+
3.2.0-rc.8
|
|
@@ -270,6 +270,24 @@ function firstArg(L, i, from) {
|
|
|
270
270
|
}
|
|
271
271
|
return null;
|
|
272
272
|
}
|
|
273
|
+
// The arguments of a call whose `(` ends at `from` on line i, up to its own `)`, across at most four lines: strings
|
|
274
|
+
// and nested brackets are stepped over, so `method:` is read from this call's options and never from the next call's.
|
|
275
|
+
function callArgs(L, i, from) {
|
|
276
|
+
let out = ''; let depth = 0; let q = null;
|
|
277
|
+
for (let j = i; j < Math.min(L.length, i + 4); j++) {
|
|
278
|
+
const s = j === i ? L[i].slice(from) : L[j];
|
|
279
|
+
for (let k = 0; k < s.length; k++) {
|
|
280
|
+
const c = s[k];
|
|
281
|
+
if (q) { if (c === '\\') { out += c + (s[k + 1] ?? ''); k += 1; continue; } if (c === q) q = null; }
|
|
282
|
+
else if (c === '"' || c === "'" || c === '`') q = c;
|
|
283
|
+
else if (c === '(' || c === '[' || c === '{') depth += 1;
|
|
284
|
+
else if (c === ')' || c === ']' || c === '}') { if (depth === 0) return out; depth -= 1; }
|
|
285
|
+
out += c;
|
|
286
|
+
}
|
|
287
|
+
out += ' ';
|
|
288
|
+
}
|
|
289
|
+
return out;
|
|
290
|
+
}
|
|
273
291
|
// The names a module calls HTTP through: axios, and every `x = axios.create(…)` in any of its files (a client made
|
|
274
292
|
// in lib/api.ts is called from every component). A name a file itself makes a server of is not one there.
|
|
275
293
|
function httpClients(pj, m) {
|
|
@@ -358,8 +376,7 @@ export function endpoints(pj, st, out) {
|
|
|
358
376
|
if (nm && nest !== null) add(provides, m, nm[1] === 'All' ? '*' : nm[1], join(nest, nm[2] || ''), f, i, `@${nm[1]}`);
|
|
359
377
|
const fe = /\bfetch\(\s*(['"`])([^'"`]+)\1/.exec(l);
|
|
360
378
|
if (fe && !/^https?:/.test(fe[2])) {
|
|
361
|
-
const
|
|
362
|
-
const mt = /method\s*:\s*['"](\w+)['"]/.exec(win);
|
|
379
|
+
const mt = /method\s*:\s*['"](\w+)['"]/.exec(callArgs(L, i, fe.index + l.slice(fe.index).indexOf('(') + 1));
|
|
363
380
|
add(consumes, m, mt ? mt[1] : 'GET', fe[2], f, i, fe[2]);
|
|
364
381
|
}
|
|
365
382
|
// `api.get<User[]>("/x")` and a call whose path is on the next line read alike.
|
package/kit/eval/smoke-test.sh
CHANGED
|
@@ -7689,6 +7689,25 @@ _pa_say auto 'approve: push' >/dev/null; ( cd "$_paw" && git checkout -q -b othe
|
|
|
7689
7689
|
_pa_run auto 'git push origin feat/x'; [ "$_par" = 2 ] || _pabad="$_pabad other-branch-checked-out:rc=$_par"
|
|
7690
7690
|
[ -z "$_pabad" ] && pass "a push approval is for the commit HEAD was on and the branch that was checked out: one more commit, or another branch, and it is refused" \
|
|
7691
7691
|
|| fail "push binding:$_pabad"
|
|
7692
|
+
# A tag on the approved commit is part of that push (a release: `/crew-approve push`, then `git push origin v2.0.0`
|
|
7693
|
+
# was refused on a real project). A tag elsewhere, a name that is also a branch, or a full ref is not.
|
|
7694
|
+
_pa_new; _pabad=""; ( cd "$_paw" && git commit -qm second && git tag old HEAD^ && git tag v1 && git tag -a v1a -m v1a && git tag both && git branch both ) >/dev/null 2>&1
|
|
7695
|
+
_pa_say auto 'approve: commit' >/dev/null
|
|
7696
|
+
_pa_run auto 'git push origin v1'; [ "$_par" = 2 ] || _pabad="$_pabad [a commit approval pushed a tag: rc=$_par]"
|
|
7697
|
+
_pa_say auto 'approve: push' >/dev/null
|
|
7698
|
+
for _pc in 'git push origin v1' 'git push origin v1a' 'git push -q origin v1'; do
|
|
7699
|
+
_pa_run auto "$_pc"; [ "$_par" = 0 ] || _pabad="$_pabad [$_pc → $_par $(head -c 120 "$_PA/err")]"
|
|
7700
|
+
done
|
|
7701
|
+
_pa_run auto 'git push origin old'; { [ "$_par" = 2 ] && grep -q 'the tag old is on' "$_PA/err"; } || _pabad="$_pabad [a tag on another commit → $_par]"
|
|
7702
|
+
for _pc in 'git push origin both' 'git push origin refs/tags/v1' 'git push origin nosuch' 'git push origin v1:v2' 'git push other v1'; do
|
|
7703
|
+
_pa_run auto "$_pc"; [ "$_par" = 2 ] || _pabad="$_pabad [$_pc → $_par]"
|
|
7704
|
+
done
|
|
7705
|
+
_pa_new; ( cd "$_paw" && git tag before ) >/dev/null 2>&1; _pa_say auto 'approve: commit+push' >/dev/null
|
|
7706
|
+
_pa_run auto 'git commit -m x' >/dev/null; ( cd "$_paw" && git commit -qm approved && git tag after ) >/dev/null 2>&1
|
|
7707
|
+
_pa_run auto 'git push origin after'; [ "$_par" = 0 ] || _pabad="$_pabad [commit+push: a tag on the new commit → $_par $(head -c 120 "$_PA/err")]"
|
|
7708
|
+
_pa_run auto 'git push origin before'; { [ "$_par" = 2 ] && grep -q 'the tag before is on' "$_PA/err"; } || _pabad="$_pabad [commit+push: a tag on the old commit → $_par]"
|
|
7709
|
+
[ -z "$_pabad" ] && pass "a push approval covers a tag on the approved commit (lightweight or annotated; after commit+push, on the new commit): a tag on another commit, a name that is also a branch, a full ref, a refspec, another remote or a commit-only approval is refused" \
|
|
7710
|
+
|| fail "tag push:$_pabad"
|
|
7692
7711
|
|
|
7693
7712
|
# ---- commit+push: the push is for the commit the approved diff became -----------------------------------------
|
|
7694
7713
|
_pa_new; _pabad=""
|
package/kit/hooks/guard-bash.sh
CHANGED
|
@@ -2437,7 +2437,7 @@ _p44_scan(){ # $1 = the collapsed `git push …` -> 0 when it is `git push [-u]
|
|
|
2437
2437
|
return 0
|
|
2438
2438
|
}
|
|
2439
2439
|
_approval_ok(){ # 0 = the user's recorded approval covers THIS call. Otherwise _APW says why not, for the message.
|
|
2440
|
-
local k v op="" tr="" h="" b="" r="" u="" sid="" ts="" now age dir="${_CWD:-.}" hc=1 hp=1 cur tool
|
|
2440
|
+
local k v op="" tr="" h="" b="" r="" u="" sid="" ts="" now age dir="${_CWD:-.}" hc=1 hp=1 cur tool tag _h2
|
|
2441
2441
|
_APW="no approval from the user is on record"
|
|
2442
2442
|
git_has "$CMD_SEEN" 'commit' && hc=0; git_has "$CMD_SEEN" 'push' && hp=0
|
|
2443
2443
|
if [ "$hc" = 0 ] && [ "$hp" = 0 ]; then
|
|
@@ -2478,7 +2478,17 @@ _approval_ok(){ # 0 = the user's recorded approval covers THIS call. Otherwise
|
|
|
2478
2478
|
_p44_scan "$_A44_S" || { _APW="the push is not in the one form an approval covers, git push <remote> <branch> ($_P44_WHY)"; return 1; }
|
|
2479
2479
|
[ -n "$b" ] && [ -n "$r" ] && [ -n "$u" ] || { _APW="the approval record could not be read"; return 1; }
|
|
2480
2480
|
if [ "$_P44_REMOTE" != "$r" ]; then _APW="the user approved a push to $r, not to $_P44_REMOTE"; return 1; fi
|
|
2481
|
-
|
|
2481
|
+
# A TAG ON THE APPROVED COMMIT is part of that push (a release is tagged on what was approved): `git push <remote>
|
|
2482
|
+
# <tag>` passes when <tag> is a tag and not also a branch, and it points at the commit the approval covers. A tag
|
|
2483
|
+
# on any other commit, --tags, and a refspec are refused as before.
|
|
2484
|
+
tag=""
|
|
2485
|
+
case "$_P44_REF" in
|
|
2486
|
+
"$b"|HEAD) ;;
|
|
2487
|
+
*[!A-Za-z0-9._/-]*|-*|*..*|*/) _APW="the user approved a push of $b, not of $_P44_REF"; return 1 ;;
|
|
2488
|
+
*) if git -C "$dir" show-ref --verify --quiet "refs/tags/$_P44_REF" 2>/dev/null \
|
|
2489
|
+
&& ! git -C "$dir" show-ref --verify --quiet "refs/heads/$_P44_REF" 2>/dev/null; then tag="$_P44_REF"
|
|
2490
|
+
else _APW="the user approved a push of $b, not of $_P44_REF"; return 1; fi ;;
|
|
2491
|
+
esac
|
|
2482
2492
|
cur="$(git -C "$dir" symbolic-ref --short -q HEAD 2>/dev/null || true)"
|
|
2483
2493
|
[ "$cur" = "$b" ] || { _APW="the user approved a push of $b, and the branch checked out now is ${cur:-<detached>}"; return 1; }
|
|
2484
2494
|
# Where the remote pushes TO, with pushurl and pushInsteadOf applied: the name alone can be pointed elsewhere.
|
|
@@ -2491,8 +2501,16 @@ _approval_ok(){ # 0 = the user's recorded approval covers THIS call. Otherwise
|
|
|
2491
2501
|
cur="$(git -C "$dir" rev-parse --verify --quiet HEAD 2>/dev/null || echo NONE)"
|
|
2492
2502
|
if [ "$op" = push ]; then
|
|
2493
2503
|
[ "$cur" = "$h" ] || { _APW="HEAD moved after the user approved the push (approved ${h:0:7}, now ${cur:0:7})"; return 1; }
|
|
2504
|
+
if [ -n "$tag" ]; then
|
|
2505
|
+
cur="$(git -C "$dir" rev-parse --verify --quiet "refs/tags/$tag^{commit}" 2>/dev/null || echo NONE)"
|
|
2506
|
+
[ "$cur" = "$h" ] || { _APW="the tag $tag is on ${cur:0:7}, not on the commit the user approved for push (${h:0:7})"; return 1; }
|
|
2507
|
+
fi
|
|
2494
2508
|
return 0
|
|
2495
2509
|
fi
|
|
2510
|
+
if [ -n "$tag" ]; then
|
|
2511
|
+
_h2="$cur"; cur="$(git -C "$dir" rev-parse --verify --quiet "refs/tags/$tag^{commit}" 2>/dev/null || echo NONE)"
|
|
2512
|
+
[ "$cur" = "$_h2" ] || { _APW="the tag $tag is on ${cur:0:7}, not on the commit the user approved for push (${_h2:0:7})"; return 1; }
|
|
2513
|
+
fi
|
|
2496
2514
|
# commit+push: HEAD has to be the commit the approved index became.
|
|
2497
2515
|
cur="$(git -C "$dir" rev-parse --verify --quiet 'HEAD^' 2>/dev/null || echo NONE)"
|
|
2498
2516
|
if [ "$h" = NONE ] || [ "$cur" != "$h" ]; then
|
|
@@ -707,9 +707,9 @@ _pa_record(){
|
|
|
707
707
|
commit) WHAT="commit of what is staged now (tree $_d) on HEAD $_h"
|
|
708
708
|
HOW="Run git commit -m with the message, alone in its call: no other option than -q, -s or -v, no paths, no cd, no pipe, nothing chained. Single-quote the message, or read it from a here-document with a quoted delimiter. The review record of §4.6 is still required." ;;
|
|
709
709
|
push) WHAT="push of $BR at $_h to $RM"
|
|
710
|
-
HOW="Run exactly: git push $RM $BR (alone in its call: the remote and the branch written out, no cd, no pipe, nothing chained)." ;;
|
|
710
|
+
HOW="Run exactly: git push $RM $BR (alone in its call: the remote and the branch written out, no cd, no pipe, nothing chained). A tag on that commit goes the same way: git push $RM <tag>." ;;
|
|
711
711
|
commit+push) WHAT="commit of what is staged now (tree $_d) on HEAD $_h, then push of that commit on $BR to $RM"
|
|
712
|
-
HOW="Run git commit -m with the message, alone in its call (no other option than -q, -s or -v, no paths, no cd, no pipe, nothing chained; single-quote the message; §4.6 still applies), then in a separate call exactly: git push $RM $BR" ;;
|
|
712
|
+
HOW="Run git commit -m with the message, alone in its call (no other option than -q, -s or -v, no paths, no cd, no pipe, nothing chained; single-quote the message; §4.6 still applies), then in a separate call exactly: git push $RM $BR (a tag on the new commit: git push $RM <tag>)" ;;
|
|
713
713
|
esac
|
|
714
714
|
[ -n "$_u" ] && _u=" ($RM pushes to $_u)"
|
|
715
715
|
_bw=""; [ "$BARE" = 1 ] && _bw="approved: $OP (what this session was refused). "
|
package/package.json
CHANGED