@mmerterden/multi-agent-pipeline 20.4.0 → 20.6.0
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 +34 -0
- package/LICENSE +10 -0
- package/docs/facts.json +5 -4
- package/install/_dev-only-files.mjs +1 -0
- package/manifest.json +44 -41
- package/package.json +4 -3
- package/pipeline/agents/android-architect.md +1 -0
- package/pipeline/agents/backend-architect.md +1 -0
- package/pipeline/agents/bulk-reader.md +1 -0
- package/pipeline/agents/code-reviewer.md +2 -1
- package/pipeline/agents/dev-critic.md +1 -0
- package/pipeline/agents/explorer.md +1 -0
- package/pipeline/agents/ios-architect.md +1 -0
- package/pipeline/agents/plan-critic.md +1 -0
- package/pipeline/agents/security-auditor.md +1 -0
- package/pipeline/agents/task-clarifier.md +1 -0
- package/pipeline/commands/multi-agent/autopilot-on/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/update/SKILL.md +3 -1
- package/pipeline/lib/confusables.json +100 -0
- package/pipeline/lib/normalize-text.mjs +48 -0
- package/pipeline/lib/outbound-gate.mjs +10 -1
- package/pipeline/lib/redact.mjs +53 -0
- package/pipeline/lib/vercel-deploy.sh +5 -7
- package/pipeline/multi-agent-refs/features/unattended-security.md +5 -0
- package/pipeline/schemas/autopilot-config.schema.json +5 -0
- package/pipeline/scripts/_autopilot-outcomes.mjs +2 -0
- package/pipeline/scripts/agent-guard.py +30 -1
- package/pipeline/scripts/audit-log-rotate.sh +3 -0
- package/pipeline/scripts/audit-log.sh +10 -2
- package/pipeline/scripts/autopilot-intake.mjs +17 -15
- package/pipeline/scripts/autopilot-publish.mjs +16 -1
- package/pipeline/scripts/autopilot-runner.mjs +20 -0
- package/pipeline/scripts/log-metric.sh +5 -0
- package/pipeline/scripts/pr-request.mjs +9 -3
- package/pipeline/scripts/pre-commit-check.sh +59 -16
- package/pipeline/scripts/pre-push-check.sh +3 -3
- package/pipeline/scripts/run-ui-tests.sh +5 -5
- package/pipeline/scripts/update-check.sh +73 -21
- package/pipeline/scripts/website-deploy-commit.sh +3 -2
- package/pipeline/skills/.skill-manifest.json +2 -2
- package/pipeline/skills/shared/core/multi-agent-autopilot-on/SKILL.md +1 -1
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
# Usage:
|
|
27
27
|
# bash pipeline/scripts/update-check.sh # auto-detect local version
|
|
28
28
|
# bash pipeline/scripts/update-check.sh --local 10.8.0 # explicit local version
|
|
29
|
-
# bash pipeline/scripts/update-check.sh --ttl-hours 24 # cache window (default
|
|
29
|
+
# bash pipeline/scripts/update-check.sh --ttl-hours 24 # cache window (default: prefs
|
|
30
|
+
# # updateCheck.ttlHours, else 24)
|
|
30
31
|
# bash pipeline/scripts/update-check.sh --force # ignore cache
|
|
31
32
|
# bash pipeline/scripts/update-check.sh --print-required # emit the floor only
|
|
32
33
|
#
|
|
@@ -41,7 +42,8 @@ set -euo pipefail
|
|
|
41
42
|
PKG="@mmerterden/multi-agent-pipeline"
|
|
42
43
|
REGISTRY_URL="https://registry.npmjs.org/${PKG/\//%2F}"
|
|
43
44
|
CACHE_FILE="${UPDATE_CHECK_CACHE:-$HOME/.claude/logs/multi-agent/.update-check}"
|
|
44
|
-
TTL_HOURS=
|
|
45
|
+
TTL_HOURS=""
|
|
46
|
+
FAIL_TTL_SECONDS=3600
|
|
45
47
|
LOCAL_VERSION=""
|
|
46
48
|
FORCE=0
|
|
47
49
|
PRINT_REQUIRED=0
|
|
@@ -49,37 +51,65 @@ PRINT_REQUIRED=0
|
|
|
49
51
|
while [ $# -gt 0 ]; do
|
|
50
52
|
case "$1" in
|
|
51
53
|
--local) LOCAL_VERSION="${2:-}"; shift; [ $# -gt 0 ] && shift ;;
|
|
52
|
-
--ttl-hours) TTL_HOURS="${2:-
|
|
54
|
+
--ttl-hours) TTL_HOURS="${2:-}"; shift; [ $# -gt 0 ] && shift ;;
|
|
53
55
|
--force) FORCE=1; shift ;;
|
|
54
56
|
--print-required) PRINT_REQUIRED=1; shift ;;
|
|
55
57
|
*) shift ;;
|
|
56
58
|
esac
|
|
57
59
|
done
|
|
58
60
|
|
|
59
|
-
# Local version: explicit arg, else
|
|
61
|
+
# Local version: explicit arg, else the marker the installer stamps. The
|
|
62
|
+
# marker is what is installed and what /multi-agent:update rewrites, so it is
|
|
63
|
+
# read before any repo clone; a clone is only a fallback for a machine that
|
|
64
|
+
# runs the pipeline from source without installing it.
|
|
60
65
|
if [ -z "$LOCAL_VERSION" ]; then
|
|
61
|
-
for
|
|
62
|
-
|
|
63
|
-
|
|
66
|
+
for marker in "$HOME/.claude/.pipeline-version" "$HOME/.copilot/.pipeline-version" \
|
|
67
|
+
"$HOME/.codex/.pipeline-version"; do
|
|
68
|
+
if [ -f "$marker" ]; then
|
|
69
|
+
LOCAL_VERSION=$(head -1 "$marker" 2>/dev/null | tr -d '[:space:]')
|
|
64
70
|
[ -n "$LOCAL_VERSION" ] && break
|
|
65
71
|
fi
|
|
66
72
|
done
|
|
67
73
|
fi
|
|
68
|
-
# npx-only installs have no repo clone; the installer stamps the version here.
|
|
69
74
|
if [ -z "$LOCAL_VERSION" ]; then
|
|
70
|
-
for
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
LOCAL_VERSION=$(head -1 "$marker" 2>/dev/null | tr -d '[:space:]')
|
|
75
|
+
for candidate in "$HOME/multi-agent-pipeline" "$HOME/dev/multi-agent-pipeline" "$HOME/projects/multi-agent-pipeline"; do
|
|
76
|
+
if [ -f "$candidate/package.json" ]; then
|
|
77
|
+
LOCAL_VERSION=$(node -p "require('$candidate/package.json').version" 2>/dev/null || true)
|
|
74
78
|
[ -n "$LOCAL_VERSION" ] && break
|
|
75
79
|
fi
|
|
76
80
|
done
|
|
77
81
|
fi
|
|
78
82
|
[ -z "$LOCAL_VERSION" ] && exit 0 # cannot determine local version -> silent no-op
|
|
79
83
|
|
|
80
|
-
#
|
|
81
|
-
|
|
82
|
-
|
|
84
|
+
# Cache lifetime: --ttl-hours, else prefs global.updateCheck.ttlHours, else 24.
|
|
85
|
+
# Anything that is not a whole number falls back to 24.
|
|
86
|
+
if [ -z "$TTL_HOURS" ]; then
|
|
87
|
+
PREFS_FILE="${MULTI_AGENT_PREFS:-$HOME/.claude/multi-agent-preferences.json}"
|
|
88
|
+
if [ -f "$PREFS_FILE" ] && command -v jq >/dev/null 2>&1; then
|
|
89
|
+
TTL_HOURS=$(jq -r '.global.updateCheck.ttlHours // empty' "$PREFS_FILE" 2>/dev/null || true)
|
|
90
|
+
fi
|
|
91
|
+
fi
|
|
92
|
+
case "$TTL_HOURS" in (""|*[!0-9]*) TTL_HOURS=24 ;; esac
|
|
93
|
+
|
|
94
|
+
# 0 when $1 sorts strictly below $2. Semver order: numeric major.minor.patch,
|
|
95
|
+
# and a prerelease (1.2.3-beta) below its release (1.2.3). A leading v is ignored.
|
|
96
|
+
version_lt() {
|
|
97
|
+
local a="${1#v}" b="${2#v}" ac ap bc bp i x y
|
|
98
|
+
ac="${a%%-*}"; bc="${b%%-*}"
|
|
99
|
+
ap=""; bp=""
|
|
100
|
+
[ "$ac" != "$a" ] && ap="${a#*-}"
|
|
101
|
+
[ "$bc" != "$b" ] && bp="${b#*-}"
|
|
102
|
+
for i in 1 2 3; do
|
|
103
|
+
x=$(printf '%s' "$ac" | cut -d. -f"$i"); y=$(printf '%s' "$bc" | cut -d. -f"$i")
|
|
104
|
+
case "$x" in (""|*[!0-9]*) x=0 ;; esac
|
|
105
|
+
case "$y" in (""|*[!0-9]*) y=0 ;; esac
|
|
106
|
+
[ "$x" -lt "$y" ] && return 0
|
|
107
|
+
[ "$x" -gt "$y" ] && return 1
|
|
108
|
+
done
|
|
109
|
+
[ -n "$ap" ] && [ -z "$bp" ] && return 0
|
|
110
|
+
[ -z "$ap" ] && [ -n "$bp" ] && return 1
|
|
111
|
+
[ -n "$ap" ] && [ "$(printf '%s\n%s\n' "$ap" "$bp" | sort | head -1)" = "$ap" ] && [ "$ap" != "$bp" ] && return 0
|
|
112
|
+
return 1
|
|
83
113
|
}
|
|
84
114
|
|
|
85
115
|
now=$(date +%s)
|
|
@@ -92,6 +122,8 @@ if [ "$FORCE" -eq 0 ] && [ -f "$CACHE_FILE" ]; then
|
|
|
92
122
|
cached_latest=$(cut -d'|' -f2 "$CACHE_FILE" 2>/dev/null || echo "")
|
|
93
123
|
cached_required=$(cut -d'|' -f3 "$CACHE_FILE" 2>/dev/null || echo "")
|
|
94
124
|
case "$cached_epoch" in (*[!0-9]*|"") cached_epoch=0 ;; esac
|
|
125
|
+
# A stamp in the future is a clock or copy mistake, not a fresh answer.
|
|
126
|
+
[ "$cached_epoch" -gt "$now" ] && cached_epoch=0
|
|
95
127
|
if [ $((now - cached_epoch)) -lt $((TTL_HOURS * 3600)) ] && [ -n "$cached_latest" ]; then
|
|
96
128
|
latest="$cached_latest"
|
|
97
129
|
required="$cached_required"
|
|
@@ -100,6 +132,16 @@ fi
|
|
|
100
132
|
|
|
101
133
|
# Stale or missing cache -> one bounded registry call (silent on any failure).
|
|
102
134
|
# The abbreviated packument carries dist-tags at a quarter of the full document.
|
|
135
|
+
# A failed read is remembered for FAIL_TTL_SECONDS in "<cache>.fail", so an
|
|
136
|
+
# offline machine does not wait out the timeout on every call.
|
|
137
|
+
if [ -z "$latest" ] && [ "$FORCE" -eq 0 ] && [ -f "$CACHE_FILE.fail" ]; then
|
|
138
|
+
failed_at=$(head -1 "$CACHE_FILE.fail" 2>/dev/null | tr -d '[:space:]')
|
|
139
|
+
case "$failed_at" in (*[!0-9]*|"") failed_at=0 ;; esac
|
|
140
|
+
if [ "$failed_at" -le "$now" ] && [ $((now - failed_at)) -lt "$FAIL_TTL_SECONDS" ]; then
|
|
141
|
+
exit 0
|
|
142
|
+
fi
|
|
143
|
+
fi
|
|
144
|
+
|
|
103
145
|
if [ -z "$latest" ]; then
|
|
104
146
|
tags=$(curl -sm 3 -H 'Accept: application/vnd.npm.install-v1+json' "$REGISTRY_URL" 2>/dev/null \
|
|
105
147
|
| { if command -v jq >/dev/null 2>&1; then
|
|
@@ -110,14 +152,25 @@ if [ -z "$latest" ]; then
|
|
|
110
152
|
latest="${tags%%|*}"
|
|
111
153
|
required="${tags#*|}"
|
|
112
154
|
[ "$required" = "$tags" ] && required=""
|
|
113
|
-
[ -z "$latest" ] && exit 0
|
|
114
155
|
mkdir -p "$(dirname "$CACHE_FILE")" 2>/dev/null || exit 0
|
|
115
|
-
|
|
156
|
+
if [ -z "$latest" ]; then
|
|
157
|
+
printf '%s\n' "$now" > "$CACHE_FILE.fail" 2>/dev/null || true
|
|
158
|
+
exit 0
|
|
159
|
+
fi
|
|
160
|
+
rm -f "$CACHE_FILE.fail" 2>/dev/null || true
|
|
161
|
+
# Written through a temp file and a rename, so a concurrent reader never
|
|
162
|
+
# sees a half-written line.
|
|
163
|
+
tmp_cache=$(mktemp "$CACHE_FILE.XXXXXX" 2>/dev/null) || exit 0
|
|
164
|
+
if printf '%s|%s|%s\n' "$now" "$latest" "$required" > "$tmp_cache" 2>/dev/null; then
|
|
165
|
+
mv -f "$tmp_cache" "$CACHE_FILE" 2>/dev/null || rm -f "$tmp_cache"
|
|
166
|
+
else
|
|
167
|
+
rm -f "$tmp_cache"
|
|
168
|
+
fi
|
|
116
169
|
fi
|
|
117
170
|
|
|
118
171
|
# A floor above latest is a publisher mistake (the `required` tag was moved to a
|
|
119
172
|
# version that `latest` no longer covers). Clamp rather than brick every user.
|
|
120
|
-
if [ -n "$required" ] &&
|
|
173
|
+
if [ -n "$required" ] && version_lt "$latest" "$required"; then
|
|
121
174
|
required="$latest"
|
|
122
175
|
fi
|
|
123
176
|
|
|
@@ -127,8 +180,7 @@ if [ "$PRINT_REQUIRED" -eq 1 ]; then
|
|
|
127
180
|
fi
|
|
128
181
|
|
|
129
182
|
# Forced update: local sorts strictly BELOW the required floor.
|
|
130
|
-
if [ -n "$required" ] &&
|
|
131
|
-
&& [ "$(highest_of "$LOCAL_VERSION" "$required")" = "$required" ]; then
|
|
183
|
+
if [ -n "$required" ] && version_lt "$LOCAL_VERSION" "$required"; then
|
|
132
184
|
printf '%s|%s|force\n' "$LOCAL_VERSION" "$latest"
|
|
133
185
|
exit 0
|
|
134
186
|
fi
|
|
@@ -137,7 +189,7 @@ fi
|
|
|
137
189
|
|
|
138
190
|
# Update available only when latest sorts strictly ABOVE local (a dev machine
|
|
139
191
|
# running ahead of the registry must not see an "update" prompt).
|
|
140
|
-
if
|
|
192
|
+
if version_lt "$LOCAL_VERSION" "$latest"; then
|
|
141
193
|
printf '%s|%s\n' "$LOCAL_VERSION" "$latest"
|
|
142
194
|
fi
|
|
143
195
|
exit 0
|
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
# refuses to build a commit whose author is not a contributor on the project. The
|
|
8
8
|
# push still succeeds, the deployment is created and sits at UNKNOWN with a 0ms
|
|
9
9
|
# build (API `readyState: BLOCKED`), and the live site keeps serving the previous
|
|
10
|
-
# version.
|
|
11
|
-
#
|
|
10
|
+
# version. A successful push is therefore not evidence of a deploy, so the script
|
|
11
|
+
# pins the author before committing and reads the deployment state back after
|
|
12
|
+
# pushing instead of reporting the website as synced on the push alone.
|
|
12
13
|
#
|
|
13
14
|
# Usage:
|
|
14
15
|
# website-deploy-commit.sh <identity-name> <identity-email> <version> [repo-dir]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "1.0.0",
|
|
3
|
-
"generatedAt": "2026-09-
|
|
3
|
+
"generatedAt": "2026-09-26T13:39:28Z",
|
|
4
4
|
"skillCount": 218,
|
|
5
5
|
"entries": [
|
|
6
6
|
{
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
31
|
"path": "shared/core/multi-agent-autopilot-on/SKILL.md",
|
|
32
|
-
"sha256": "
|
|
32
|
+
"sha256": "d4f23f6f7bb493a4efa2cfa252fab50b22b6506aeaf31985db4498b5f010c8c8"
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
"path": "shared/core/multi-agent-autopilot-status/SKILL.md",
|
|
@@ -74,7 +74,7 @@ The same name works on both sides and both mechanisms already exist:
|
|
|
74
74
|
|
|
75
75
|
| Source | Query | Extra condition |
|
|
76
76
|
|---|---|---|
|
|
77
|
-
| GitHub | `gh issue list --repo <r> --label <label> --state open` |
|
|
77
|
+
| GitHub | `gh issue list --repo <r> --label <label> --assignee @me --state open` | **assigned to you** (`githubAssignee`, `"*"` for any), so a label an issue form applies is not enough |
|
|
78
78
|
| Jira | `assignee = currentUser() AND labels = "<label>" AND resolution = EMPTY AND status not in (Done, Closed, Cancelled)` | **assigned to you**, so a label somebody else adds is not enough |
|
|
79
79
|
|
|
80
80
|
Two Jira caveats worth saying out loud when Jira is selected: labels are one
|