@mmerterden/multi-agent-pipeline 11.4.0 → 12.0.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 +172 -0
- package/README.md +1 -0
- package/docs/adr/0007-multi-tool-adapter-framework.md +2 -2
- package/index.js +9 -2
- package/install/_common.mjs +107 -5
- package/install/_telemetry.mjs +5 -23
- package/install/claude.mjs +95 -11
- package/install/copilot.mjs +75 -6
- package/install/index.mjs +34 -1
- package/package.json +4 -10
- package/pipeline/commands/multi-agent/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/analysis/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/analysis-resolve/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/autopilot/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/build-optimize/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/channels/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/create-jira/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/dev/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/dev-autopilot/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/dev-local/SKILL.md +2 -1
- package/pipeline/commands/multi-agent/dev-local-autopilot/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/diff-explain/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/finish/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/garbage-collect/SKILL.md +29 -3
- package/pipeline/commands/multi-agent/help/SKILL.md +3 -2
- package/pipeline/commands/multi-agent/issue/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/jira/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/kill/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/language/SKILL.md +20 -1
- package/pipeline/commands/multi-agent/local/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/local-autopilot/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/log/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/manual-test/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/prune-logs/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/purge/SKILL.md +27 -24
- package/pipeline/commands/multi-agent/refactor/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/resume/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/review/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/review-issue/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/review-jira/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/scan/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/search/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/setup/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/stack/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/status/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/sync/SKILL.md +20 -2
- package/pipeline/commands/multi-agent/test/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/uninstall/SKILL.md +87 -0
- package/pipeline/commands/multi-agent/update/SKILL.md +1 -0
- package/pipeline/lib/account-resolver.sh +61 -23
- package/pipeline/lib/channels-multi-repo.sh +7 -2
- package/pipeline/lib/count-lib.sh +27 -0
- package/pipeline/lib/credential-store.sh +23 -6
- package/pipeline/lib/extract-conventions.sh +27 -21
- package/pipeline/lib/fetch-confluence.sh +25 -13
- package/pipeline/lib/fetch-crashlytics.sh +30 -8
- package/pipeline/lib/fetch-fortify.sh +11 -2
- package/pipeline/lib/fetch-swagger.sh +18 -7
- package/pipeline/lib/figma-mcp-refresh.sh +26 -11
- package/pipeline/lib/figma-screenshot.sh +11 -2
- package/pipeline/lib/issue-fetcher.sh +31 -6
- package/pipeline/lib/multi-repo-pipeline.sh +84 -20
- package/pipeline/lib/plan-todos.sh +12 -3
- package/pipeline/lib/post-pr-review.sh +5 -3
- package/pipeline/lib/repo-cache.sh +53 -9
- package/pipeline/lib/review-watch.sh +26 -6
- package/pipeline/lib/shadow-git.sh +45 -4
- package/pipeline/lib/vercel-deploy.sh +10 -1
- package/pipeline/multi-agent-refs/cross-cli-contract.md +5 -5
- package/pipeline/multi-agent-refs/phases/phase-0-init.md +12 -1
- package/pipeline/scripts/audit-log-rotate.sh +38 -10
- package/pipeline/scripts/check-md-links.mjs +34 -9
- package/pipeline/scripts/diff-risk-score.mjs +4 -1
- package/pipeline/scripts/evidence-gate.mjs +10 -1
- package/pipeline/scripts/fixtures/install-layout.tsv +6 -4
- package/pipeline/scripts/fixtures/pack-expected-count.txt +1 -0
- package/pipeline/scripts/gc-tmp.sh +67 -8
- package/pipeline/scripts/gc-worktrees.sh +140 -0
- package/pipeline/scripts/keychain-save.sh +13 -9
- package/pipeline/scripts/lint-skills.mjs +40 -2
- package/pipeline/scripts/localize-commands.mjs +130 -0
- package/pipeline/scripts/migrate-prefs.mjs +26 -7
- package/pipeline/scripts/phase-tracker.sh +71 -0
- package/pipeline/scripts/pre-commit-check.sh +39 -0
- package/pipeline/scripts/prune-logs.sh +100 -15
- package/pipeline/scripts/purge.sh +235 -0
- package/pipeline/scripts/scan-skills.sh +217 -127
- package/pipeline/scripts/smoke-bitbucket-contract.sh +21 -5
- package/pipeline/scripts/smoke-description-tr.sh +82 -0
- package/pipeline/scripts/smoke-fetchers-offline.sh +382 -0
- package/pipeline/scripts/smoke-gc-tmp.sh +50 -4
- package/pipeline/scripts/smoke-gc-worktrees.sh +125 -0
- package/pipeline/scripts/smoke-install-layout.sh +11 -3
- package/pipeline/scripts/smoke-lib-scripts.sh +54 -1
- package/pipeline/scripts/smoke-pack-contents.sh +140 -0
- package/pipeline/scripts/smoke-plugin-validate.sh +64 -0
- package/pipeline/scripts/smoke-prune-logs.sh +57 -7
- package/pipeline/scripts/smoke-purge.sh +138 -0
- package/pipeline/scripts/smoke-shadow-git.sh +48 -1
- package/pipeline/scripts/smoke-skill-authoring.sh +1 -1
- package/pipeline/scripts/smoke-update-check.sh +13 -0
- package/pipeline/scripts/smoke-workflow-audit.sh +69 -0
- package/pipeline/scripts/test-gap-scan.mjs +12 -1
- package/pipeline/scripts/triage-memory.mjs +10 -1
- package/pipeline/scripts/uninstall.mjs +146 -42
- package/pipeline/scripts/update-check.sh +9 -0
- package/pipeline/scripts/update-issue-progress.sh +60 -41
- package/pipeline/scripts/write-state.mjs +14 -4
- package/pipeline/skills/.skill-manifest.json +7 -7
- package/pipeline/skills/.skills-index.json +9 -9
- package/pipeline/skills/shared/README.md +19 -10
- package/pipeline/skills/shared/core/multi-agent/SKILL.md +1 -0
- package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +2 -2
- package/pipeline/skills/shared/core/multi-agent-uninstall/SKILL.md +74 -0
- package/pipeline/skills/skills-index.md +1 -1
- package/pipeline/commands/multi-agent/delete/SKILL.md +0 -67
- package/pipeline/skills/shared/core/multi-agent-delete/SKILL.md +0 -66
|
@@ -59,6 +59,36 @@ if [ $REFRESH -eq 0 ] && cache_fresh; then
|
|
|
59
59
|
exit 0
|
|
60
60
|
fi
|
|
61
61
|
|
|
62
|
+
# Refresh staging: providers write to $CACHE_TMP, then commit_cache validates
|
|
63
|
+
# and mv's it into place. Writing straight to $CACHE_FILE truncated the cache
|
|
64
|
+
# BEFORE the fetch ran, so any failure left an empty file that cache_fresh
|
|
65
|
+
# then served for the whole TTL.
|
|
66
|
+
CACHE_TMP="${CACHE_FILE}.tmp.$$"
|
|
67
|
+
cleanup_tmp() { rm -f "$CACHE_TMP" "$CACHE_TMP.list" 2>/dev/null || true; }
|
|
68
|
+
trap cleanup_tmp EXIT
|
|
69
|
+
|
|
70
|
+
# Validate the staged file (non-empty valid JSON) and move it into place.
|
|
71
|
+
# On failure keep the previous cache and serve it with a warning; hard-fail
|
|
72
|
+
# only when there is no previous cache to fall back to.
|
|
73
|
+
commit_cache() {
|
|
74
|
+
if [ -s "$CACHE_TMP" ] \
|
|
75
|
+
&& python3 -c 'import json,sys; json.load(open(sys.argv[1]))' "$CACHE_TMP" 2>/dev/null; then
|
|
76
|
+
mv "$CACHE_TMP" "$CACHE_FILE"
|
|
77
|
+
return 0
|
|
78
|
+
fi
|
|
79
|
+
rm -f "$CACHE_TMP"
|
|
80
|
+
if [ -f "$CACHE_FILE" ]; then
|
|
81
|
+
echo "WARN: repo-cache refresh failed for $PROVIDER/$SCOPE; serving previous cache" >&2
|
|
82
|
+
return 0
|
|
83
|
+
fi
|
|
84
|
+
echo "ERR: repo-cache refresh failed for $PROVIDER/$SCOPE and no previous cache exists" >&2
|
|
85
|
+
exit 3
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
# Basic-auth via a curl config fed through process substitution so the
|
|
89
|
+
# credential never appears in argv (argv is visible to `ps`).
|
|
90
|
+
bb_auth_cfg() { printf 'user = "%s:%s"\n' "$1" "$2"; }
|
|
91
|
+
|
|
62
92
|
case "$PROVIDER" in
|
|
63
93
|
github)
|
|
64
94
|
if ! command -v gh >/dev/null 2>&1; then
|
|
@@ -100,20 +130,32 @@ for r in data:
|
|
|
100
130
|
"provider": "github"
|
|
101
131
|
})
|
|
102
132
|
print(json.dumps(out))
|
|
103
|
-
' > "$
|
|
133
|
+
' > "$CACHE_TMP" || true
|
|
134
|
+
commit_cache
|
|
104
135
|
;;
|
|
105
136
|
|
|
106
137
|
bitbucket)
|
|
107
138
|
: "${BB_TOKEN_KEY:?BB_TOKEN_KEY required for bitbucket}"
|
|
108
139
|
: "${BB_HOST:?BB_HOST required (e.g. bitbucket.example.com)}"
|
|
109
|
-
|
|
110
|
-
|
|
140
|
+
# Resolve the credential helper via the shared resolver (works from the
|
|
141
|
+
# repo checkout and from both install trees) - never hardcode the path.
|
|
142
|
+
# A pre-set CRED_STORE (tests, custom installs) is honored as-is.
|
|
143
|
+
if [ -z "${CRED_STORE:-}" ]; then
|
|
144
|
+
# shellcheck disable=SC1090,SC1091
|
|
145
|
+
. "$(cd "$(dirname "$0")" && pwd)/credential-store-resolver.sh" 2>/dev/null \
|
|
146
|
+
|| . "$HOME/.claude/lib/credential-store-resolver.sh" 2>/dev/null \
|
|
147
|
+
|| . "$HOME/.copilot/lib/credential-store-resolver.sh" 2>/dev/null \
|
|
148
|
+
|| { echo "ERR: credential helper not found" >&2; exit 3; }
|
|
149
|
+
fi
|
|
150
|
+
BB_USER="${BB_USER:-${BB_USER_KEY:+$("$CRED_STORE" get "$BB_USER_KEY" 2>/dev/null || true)}}"
|
|
151
|
+
BB_TOKEN=$("$CRED_STORE" get "$BB_TOKEN_KEY" 2>/dev/null || true)
|
|
111
152
|
if [ -z "$BB_TOKEN" ]; then
|
|
112
153
|
echo "ERR: bitbucket token not found in credential store ($BB_TOKEN_KEY)" >&2
|
|
113
154
|
exit 3
|
|
114
155
|
fi
|
|
115
156
|
# Bitbucket Server REST: /rest/api/1.0/projects/{key}/repos?limit=200
|
|
116
|
-
curl
|
|
157
|
+
# Auth via curl config file (-K) so the token never appears on argv.
|
|
158
|
+
curl -sf -K <(bb_auth_cfg "${BB_USER:-bot}" "$BB_TOKEN") \
|
|
117
159
|
"https://${BB_HOST}/rest/api/1.0/projects/${SCOPE}/repos?limit=200" \
|
|
118
160
|
| python3 -c '
|
|
119
161
|
import json,sys
|
|
@@ -147,7 +189,8 @@ for r in vals:
|
|
|
147
189
|
"provider": "bitbucket"
|
|
148
190
|
})
|
|
149
191
|
print(json.dumps(out))
|
|
150
|
-
' > "$
|
|
192
|
+
' > "$CACHE_TMP" || true
|
|
193
|
+
commit_cache
|
|
151
194
|
;;
|
|
152
195
|
|
|
153
196
|
local)
|
|
@@ -161,7 +204,7 @@ print(json.dumps(out))
|
|
|
161
204
|
if [ -d "$SCOPE" ]; then
|
|
162
205
|
ROOTS="$SCOPE"
|
|
163
206
|
fi
|
|
164
|
-
: > "$
|
|
207
|
+
: > "$CACHE_TMP.list"
|
|
165
208
|
for root in $ROOTS; do
|
|
166
209
|
[ -d "$root" ] || continue
|
|
167
210
|
# -prune avoids descending into already-found .git trees (nested submodules
|
|
@@ -178,7 +221,7 @@ print(json.dumps(out))
|
|
|
178
221
|
# truly offline-only or a clone with an upstream we just chose
|
|
179
222
|
# to treat as local.
|
|
180
223
|
remote=$(git -C "$repo_dir" remote get-url origin 2>/dev/null || true)
|
|
181
|
-
printf '%s\t%s\t%s\n' "$name" "$repo_dir" "$remote" >> "$
|
|
224
|
+
printf '%s\t%s\t%s\n' "$name" "$repo_dir" "$remote" >> "$CACHE_TMP.list"
|
|
182
225
|
done
|
|
183
226
|
done
|
|
184
227
|
python3 -c '
|
|
@@ -218,8 +261,9 @@ for raw in open(sys.argv[1]):
|
|
|
218
261
|
"provider": "local"
|
|
219
262
|
})
|
|
220
263
|
print(json.dumps(out))
|
|
221
|
-
' "$
|
|
222
|
-
rm -f "$
|
|
264
|
+
' "$CACHE_TMP.list" > "$CACHE_TMP" || true
|
|
265
|
+
rm -f "$CACHE_TMP.list"
|
|
266
|
+
commit_cache
|
|
223
267
|
;;
|
|
224
268
|
|
|
225
269
|
*)
|
|
@@ -180,6 +180,10 @@ review_one_repo() {
|
|
|
180
180
|
title=$(jq -r '.title' <<<"$pr")
|
|
181
181
|
url=$(jq -r '.url' <<<"$pr")
|
|
182
182
|
|
|
183
|
+
# Advance the cursor candidate for every PR seen (including skips), so a
|
|
184
|
+
# PR that keeps its reviewed head SHA is not re-fetched on every poll.
|
|
185
|
+
if [ "$updated_at" \> "$newest_iso" ]; then newest_iso="$updated_at"; fi
|
|
186
|
+
|
|
183
187
|
# Did we already review THIS head SHA?
|
|
184
188
|
local prev_sha
|
|
185
189
|
prev_sha=$(jq -r --arg n "$pr_num" '.reviewed_prs[$n] // empty' <<<"$state")
|
|
@@ -196,12 +200,18 @@ review_one_repo() {
|
|
|
196
200
|
# (Claude Code session or future webhook handler). review-watch only
|
|
197
201
|
# produces the dispatch list - it does not run the LLM itself, because
|
|
198
202
|
# token cost decisions stay with the user.
|
|
199
|
-
state=$(jq --arg n "$pr_num" --arg s "$head_sha"
|
|
200
|
-
'.reviewed_prs[$n] = $s
|
|
201
|
-
|
|
202
|
-
if [ "$updated_at" \> "$newest_iso" ]; then newest_iso="$updated_at"; fi
|
|
203
|
+
state=$(jq --arg n "$pr_num" --arg s "$head_sha" \
|
|
204
|
+
'.reviewed_prs[$n] = $s' <<<"$state")
|
|
203
205
|
done < <(jq -c '.[]' <<<"$prs_json")
|
|
204
206
|
|
|
207
|
+
# Persist the NEWEST updatedAt seen this scan - the previous per-PR
|
|
208
|
+
# assignment stamped whichever PR the search returned last, which moves
|
|
209
|
+
# the cursor backwards whenever results are not oldest-first and causes
|
|
210
|
+
# permanent re-scans. newest_iso only ever grows (seeded from last_seen).
|
|
211
|
+
if [ -n "$newest_iso" ] && [ "$newest_iso" != "$last_seen" ]; then
|
|
212
|
+
state=$(jq --arg t "$newest_iso" '.last_seen_iso = $t' <<<"$state")
|
|
213
|
+
fi
|
|
214
|
+
|
|
205
215
|
save_state "$repo" "$state"
|
|
206
216
|
}
|
|
207
217
|
|
|
@@ -218,7 +228,13 @@ case "$CMD" in
|
|
|
218
228
|
while IFS= read -r r; do [ -n "$r" ] && REPOS+=("$r"); done < <(read_repos_from_prefs)
|
|
219
229
|
fi
|
|
220
230
|
[ "${#REPOS[@]}" -eq 0 ] && { err "no repos configured (prefs.reviewWatch.repos)"; exit 2; }
|
|
221
|
-
|
|
231
|
+
# Per-repo failures (transient gh/network errors) must not kill the run
|
|
232
|
+
# under set -e; log, continue, and reflect them in the exit code.
|
|
233
|
+
RC=0
|
|
234
|
+
for r in "${REPOS[@]}"; do
|
|
235
|
+
review_one_repo "$r" || { err "scan failed for $r (continuing)"; RC=3; }
|
|
236
|
+
done
|
|
237
|
+
exit "$RC"
|
|
222
238
|
;;
|
|
223
239
|
--watch)
|
|
224
240
|
require_jq
|
|
@@ -227,7 +243,11 @@ case "$CMD" in
|
|
|
227
243
|
[ "$interval" -lt 30 ] && interval=30 # GitHub rate limit guard
|
|
228
244
|
printf 'review-watch: starting loop (interval=%ds)\n' "$interval"
|
|
229
245
|
while :; do
|
|
230
|
-
|
|
246
|
+
# A transient failure on one repo must not kill the watch loop.
|
|
247
|
+
while IFS= read -r r; do
|
|
248
|
+
[ -n "$r" ] || continue
|
|
249
|
+
review_one_repo "$r" || err "scan failed for $r (continuing)"
|
|
250
|
+
done < <(read_repos_from_prefs)
|
|
231
251
|
sleep "$interval"
|
|
232
252
|
done
|
|
233
253
|
;;
|
|
@@ -61,6 +61,23 @@ usage() {
|
|
|
61
61
|
|
|
62
62
|
shadow_dir() { printf '%s/%s' "$SHADOW_ROOT" "$1"; }
|
|
63
63
|
|
|
64
|
+
# Task ids become path components under $SHADOW_ROOT, so an unvalidated id
|
|
65
|
+
# like ".." would make prune's `rm -rf` walk out of the shadow tree and
|
|
66
|
+
# delete ~/.claude/state. Allow only [A-Za-z0-9._-]+ and explicitly reject
|
|
67
|
+
# ".", "..", empty, and anything containing a slash.
|
|
68
|
+
require_task_id() {
|
|
69
|
+
case "${1:-}" in
|
|
70
|
+
"" | . | ..)
|
|
71
|
+
err "invalid task id: '${1:-}' (empty, '.' and '..' are rejected)"
|
|
72
|
+
exit 2
|
|
73
|
+
;;
|
|
74
|
+
*[!A-Za-z0-9._-]*)
|
|
75
|
+
err "invalid task id: '$1' (allowed characters: A-Z a-z 0-9 . _ -)"
|
|
76
|
+
exit 2
|
|
77
|
+
;;
|
|
78
|
+
esac
|
|
79
|
+
}
|
|
80
|
+
|
|
64
81
|
# Wrap git so .git lives in $SHADOW_DIR but worktree is the project root.
|
|
65
82
|
sg() {
|
|
66
83
|
local sd="$1" wt="$2"; shift 2
|
|
@@ -74,6 +91,7 @@ iso_now() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
|
|
|
74
91
|
do_init() {
|
|
75
92
|
local task_id="${1:-}" project_root="${2:-}"
|
|
76
93
|
[ -z "$task_id" ] || [ -z "$project_root" ] && { err "usage: init <task-id> <project-root>"; exit 1; }
|
|
94
|
+
require_task_id "$task_id"
|
|
77
95
|
[ ! -d "$project_root" ] && { err "project root not a directory: $project_root"; exit 1; }
|
|
78
96
|
local sd; sd=$(shadow_dir "$task_id")
|
|
79
97
|
if [ -d "$sd/.git" ]; then
|
|
@@ -83,12 +101,16 @@ do_init() {
|
|
|
83
101
|
mkdir -p "$sd"
|
|
84
102
|
# Bare-ish: .git inside $sd, worktree is the project root.
|
|
85
103
|
git init --quiet "$sd" >/dev/null
|
|
86
|
-
#
|
|
104
|
+
# Ignore rules must live in $GIT_DIR/info/exclude: the work tree is the
|
|
105
|
+
# project root, so a .gitignore placed next to the shadow .git is never
|
|
106
|
+
# read by git. Seed the exclude file with the project's own .gitignore
|
|
107
|
+
# (protects snapshots even if the project file is deleted mid-run) plus
|
|
108
|
+
# forced excludes for build artifacts the project may not ignore.
|
|
109
|
+
mkdir -p "$sd/.git/info"
|
|
87
110
|
if [ -f "$project_root/.gitignore" ]; then
|
|
88
|
-
|
|
111
|
+
cat "$project_root/.gitignore" >> "$sd/.git/info/exclude"
|
|
89
112
|
fi
|
|
90
|
-
|
|
91
|
-
cat >> "$sd/.gitignore" <<EOF
|
|
113
|
+
cat >> "$sd/.git/info/exclude" <<EOF
|
|
92
114
|
node_modules/
|
|
93
115
|
Pods/
|
|
94
116
|
.build/
|
|
@@ -111,6 +133,7 @@ do_snapshot() {
|
|
|
111
133
|
local task_id="${1:-}" project_root="${2:-}" label="${3:-}"
|
|
112
134
|
[ -z "$task_id" ] || [ -z "$project_root" ] || [ -z "$label" ] && {
|
|
113
135
|
err "usage: snapshot <task-id> <project-root> <label>"; exit 1; }
|
|
136
|
+
require_task_id "$task_id"
|
|
114
137
|
local sd; sd=$(shadow_dir "$task_id")
|
|
115
138
|
[ ! -d "$sd/.git" ] && { err "shadow not initialized for $task_id - run 'init' first"; exit 1; }
|
|
116
139
|
sg "$sd" "$project_root" add -A >/dev/null 2>&1 || true
|
|
@@ -129,6 +152,7 @@ do_snapshot() {
|
|
|
129
152
|
do_list() {
|
|
130
153
|
local task_id="${1:-}"
|
|
131
154
|
[ -z "$task_id" ] && { err "usage: list <task-id>"; exit 1; }
|
|
155
|
+
require_task_id "$task_id"
|
|
132
156
|
local sd; sd=$(shadow_dir "$task_id")
|
|
133
157
|
[ ! -d "$sd/.git" ] && { err "shadow not initialized for $task_id"; exit 1; }
|
|
134
158
|
# Use the embedded `iso:` line from the commit message body so the listing
|
|
@@ -141,12 +165,28 @@ do_restore() {
|
|
|
141
165
|
local task_id="${1:-}" project_root="${2:-}" sha="${3:-}" mode="${4:---files}"
|
|
142
166
|
[ -z "$task_id" ] || [ -z "$project_root" ] || [ -z "$sha" ] && {
|
|
143
167
|
err "usage: restore <task-id> <project-root> <sha> [--files|--state|--both]"; exit 1; }
|
|
168
|
+
require_task_id "$task_id"
|
|
144
169
|
local sd; sd=$(shadow_dir "$task_id")
|
|
145
170
|
[ ! -d "$sd/.git" ] && { err "shadow not initialized for $task_id"; exit 1; }
|
|
146
171
|
case "$mode" in
|
|
147
172
|
--files|--both)
|
|
148
173
|
sg "$sd" "$project_root" checkout --quiet "$sha" -- . \
|
|
149
174
|
|| { err "git checkout failed"; exit 3; }
|
|
175
|
+
# `checkout <sha> -- .` restores tracked content but leaves files that
|
|
176
|
+
# did not exist in the snapshot. Remove both classes of leftovers so a
|
|
177
|
+
# restore is a full rollback, staying strictly inside the work tree:
|
|
178
|
+
# a) files committed in snapshots AFTER $sha (added between sha..HEAD)
|
|
179
|
+
# b) files created since the last snapshot (still untracked; `clean`
|
|
180
|
+
# respects the info/exclude rules, so ignored trees are untouched)
|
|
181
|
+
local extra
|
|
182
|
+
extra=$(sg "$sd" "$project_root" diff --name-only --diff-filter=A "$sha" HEAD 2>/dev/null || true)
|
|
183
|
+
if [ -n "$extra" ]; then
|
|
184
|
+
printf '%s\n' "$extra" | while IFS= read -r f; do
|
|
185
|
+
[ -n "$f" ] && rm -f "$project_root/$f"
|
|
186
|
+
done
|
|
187
|
+
fi
|
|
188
|
+
( cd "$project_root" && \
|
|
189
|
+
git --git-dir="$sd/.git" --work-tree="$project_root" clean -fdq ) || true
|
|
150
190
|
printf 'shadow-git: restored files to %s\n' "$sha" >&2
|
|
151
191
|
;;
|
|
152
192
|
--state)
|
|
@@ -161,6 +201,7 @@ do_restore() {
|
|
|
161
201
|
do_prune() {
|
|
162
202
|
local task_id="${1:-}"; shift || true
|
|
163
203
|
[ -z "$task_id" ] && { err "usage: prune <task-id> [--older-than-days N]"; exit 1; }
|
|
204
|
+
require_task_id "$task_id"
|
|
164
205
|
local older=""
|
|
165
206
|
while [ "$#" -gt 0 ]; do
|
|
166
207
|
case "$1" in
|
|
@@ -110,7 +110,16 @@ cmd_deploy() {
|
|
|
110
110
|
fi
|
|
111
111
|
|
|
112
112
|
if [ -z "${VERCEL_TOKEN:-}" ]; then
|
|
113
|
-
|
|
113
|
+
# Route the credential-helper hint through the resolver instead of a
|
|
114
|
+
# hardcoded ~/.claude path so Copilot-only installs get a usable command.
|
|
115
|
+
# The resolver ships alongside this wrapper in lib/; fall back to the
|
|
116
|
+
# per-CLI install locations when running from a different cwd layout.
|
|
117
|
+
# shellcheck source=/dev/null
|
|
118
|
+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/credential-store-resolver.sh" 2>/dev/null \
|
|
119
|
+
|| . "$HOME/.claude/lib/credential-store-resolver.sh" 2>/dev/null \
|
|
120
|
+
|| . "$HOME/.copilot/lib/credential-store-resolver.sh" 2>/dev/null \
|
|
121
|
+
|| true
|
|
122
|
+
echo "ERROR: VERCEL_TOKEN not set. Resolve via env or keychain ('${CRED_STORE:-credential-store.sh} get <vercel-key>')." >&2
|
|
114
123
|
echo "Hint: vercel CLI accepts the token via env var; do NOT pass --token= on argv (leaks on retry)." >&2
|
|
115
124
|
exit 1
|
|
116
125
|
fi
|
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
## 1. Command Inventory (38 commands)
|
|
10
10
|
|
|
11
11
|
```
|
|
12
|
-
analysis, analysis-resolve, autopilot, build-optimize, channels, create-jira,
|
|
12
|
+
analysis, analysis-resolve, autopilot, build-optimize, channels, create-jira, dev,
|
|
13
13
|
dev-autopilot, dev-local, dev-local-autopilot, diff-explain, finish, garbage-collect,
|
|
14
14
|
help, issue, jira, kill, language, local,
|
|
15
15
|
local-autopilot, log, manual-test, prune-logs, purge, refactor, resume, review, review-issue, review-jira,
|
|
16
|
-
scan, search, setup, stack, status, sync, test, update
|
|
16
|
+
scan, search, setup, stack, status, sync, test, uninstall, update
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
Categories:
|
|
@@ -23,7 +23,7 @@ Categories:
|
|
|
23
23
|
- **Full 8-phase modes**: `autopilot`, `local`, `local-autopilot`
|
|
24
24
|
- **Fast modes** (Init -> Dev(Opus) -> Commit -> Report): `dev`, `dev-autopilot`, `dev-local`, `dev-local-autopilot`
|
|
25
25
|
- **Tail modes** (run the pipeline tail over already-done local work): `finish`
|
|
26
|
-
- **Ops commands** (one-shot, no worktree): `status`, `log`, `kill`, `purge`, `
|
|
26
|
+
- **Ops commands** (one-shot, no worktree): `status`, `log`, `kill`, `purge`, `uninstall`, `resume`, `review`, `review-jira`, `review-issue`, `analysis`, `analysis-resolve`, `build-optimize`, `channels`, `scan`, `search`, `diff-explain`, `garbage-collect`, `prune-logs`
|
|
27
27
|
- **Meta-ops**: `setup`, `sync`, `update`, `help`, `refactor`, `test`, `stack`, `manual-test`, `language`
|
|
28
28
|
|
|
29
29
|
> **Inventory drift is a contract violation.** Adding a slash command under `pipeline/commands/multi-agent/` without updating this list + its counterpart Copilot dir (`pipeline/skills/shared/core/multi-agent-<cmd>/`) is a merge blocker. `smoke-commands-skills-parity.sh` enforces command ↔ skill directory parity; `smoke-cross-cli-behavior.sh` enforces behavior parity. This doc is the authoritative command list - bump the count + table together.
|
|
@@ -163,10 +163,10 @@ argument-hint: "<input hint>"
|
|
|
163
163
|
|
|
164
164
|
| Direction | Action |
|
|
165
165
|
|---|---|
|
|
166
|
-
| Claude → Copilot | Add `name`, `user-invocable: true`, `argument-hint`; remove `allowed-tools`;
|
|
166
|
+
| Claude → Copilot | Add `name`, `user-invocable: true`, `argument-hint`; remove `allowed-tools`; `description` stays English (localization happens only in the installed Claude tree via `description-tr` + `localize-commands.mjs`, never in repo or Copilot files) |
|
|
167
167
|
| Copilot → Claude | Remove `name`, `user-invocable`, `argument-hint`; add `allowed-tools` (inferred from command's actual tool calls) |
|
|
168
168
|
|
|
169
|
-
**Invariant**: the `description`
|
|
169
|
+
**Invariant**: the English `description` must have the same meaning on both sides, and a `description-tr` line (when present) must be a faithful translation of it. Semantic drift is not allowed. `description-en` sidecars are an installed-tree artifact and must never appear in repo or Copilot files.
|
|
170
170
|
|
|
171
171
|
---
|
|
172
172
|
|
|
@@ -425,7 +425,7 @@ git -C $PROJECT_ROOT config user.email "{identity.email}"
|
|
|
425
425
|
|
|
426
426
|
`worktreePath` = `$PROJECT_ROOT`, `localMode` = `true`.
|
|
427
427
|
|
|
428
|
-
**If normal mode** (worktree - default): 2. Worktree path: Jira → `.worktrees/{jiraId}/`, GitHub → `.worktrees/GH{issueNo}/`, free-text → `.worktrees/task-{shortId}/` 3. **Heal stale admin state first** (see "Worktree stale-lock heal" below), then `git -C $PROJECT_ROOT worktree add {path} -b {branch} origin/{baseBranch}` (if exists: enter, pull) 4. Set identity: `git -C {worktree-path} config user.name/email` 5. Create log dir + `agent-log.md` + `agent-state.json`:
|
|
428
|
+
**If normal mode** (worktree - default): 2. Worktree path: Jira → `.worktrees/{jiraId}/`, GitHub → `.worktrees/GH{issueNo}/`, free-text → `.worktrees/task-{shortId}/` 3. **Heal stale admin state first** (see "Worktree stale-lock heal" below) and **apply the residue guard** (see "Worktree residue guard" below), then `git -C $PROJECT_ROOT worktree add {path} -b {branch} origin/{baseBranch}` (if exists: enter, pull) 4. Set identity: `git -C {worktree-path} config user.name/email` 5. Create log dir + `agent-log.md` + `agent-state.json`:
|
|
429
429
|
|
|
430
430
|
**Worktree stale-lock heal (required before every `worktree add`):** a run killed mid-`worktree add` (OOM, SIGTERM, disk full) leaves a locked or broken admin entry under `.git/worktrees/{id}/`, so the retry fails with `fatal: '<path>' already exists`. Always run the heal first - it is a no-op on a clean repo:
|
|
431
431
|
|
|
@@ -438,6 +438,14 @@ fi
|
|
|
438
438
|
|
|
439
439
|
If the path is still registered and healthy after the heal, enter + pull instead of re-adding (existing behavior). Only when `worktree add` still fails after the heal do the rollback / collision flow in the multi-repo block below.
|
|
440
440
|
|
|
441
|
+
**Worktree residue guard (required once per repo, idempotent):** every worktree dir contains a `.git` file, so a blanket `git add -A` / `git add .` in the parent tree records `.worktrees/{id}` as a gitlink ("Subproject commit ...") that pollutes the branch and later needs a manual removal commit. Keep `.worktrees/` out of the index via the clone-local exclude file (never committed, so no PR noise in shared repos):
|
|
442
|
+
|
|
443
|
+
```bash
|
|
444
|
+
ex="$(git -C "$PROJECT_ROOT" rev-parse --path-format=absolute --git-common-dir)/info/exclude"
|
|
445
|
+
mkdir -p "$(dirname "$ex")"
|
|
446
|
+
grep -qxF '.worktrees/' "$ex" 2>/dev/null || printf '.worktrees/\n' >> "$ex"
|
|
447
|
+
```
|
|
448
|
+
|
|
441
449
|
**v2.1.0+ Multi-Repo Worktree Setup**:
|
|
442
450
|
|
|
443
451
|
When `state.projects[].length > 1`, repeat steps 2-4 **serially per repo** (worktrees are cheap; serial keeps git index sane and surfaces collisions one at a time):
|
|
@@ -448,6 +456,9 @@ for proj in "${PROJECTS[@]}"; do
|
|
|
448
456
|
git -C "$proj" worktree prune 2>/dev/null || true # heal stale admin state
|
|
449
457
|
git -C "$proj" worktree list --porcelain | grep -qF "$WT_PATH" \
|
|
450
458
|
&& git -C "$proj" worktree unlock "$WT_PATH" 2>/dev/null || true
|
|
459
|
+
ex="$(git -C "$proj" rev-parse --path-format=absolute --git-common-dir)/info/exclude"
|
|
460
|
+
mkdir -p "$(dirname "$ex")"
|
|
461
|
+
grep -qxF '.worktrees/' "$ex" 2>/dev/null || printf '.worktrees/\n' >> "$ex" # residue guard
|
|
451
462
|
git -C "$proj" worktree add "$WT_PATH" -b "$BRANCH" "origin/$BASE_BRANCH"
|
|
452
463
|
git -C "$WT_PATH" config user.name "${proj_identity_name}"
|
|
453
464
|
git -C "$WT_PATH" config user.email "${proj_identity_email}"
|
|
@@ -34,24 +34,52 @@ mkdir -p "$AUDIT_DIR"
|
|
|
34
34
|
|
|
35
35
|
# Rotate today's file (only if it has content)
|
|
36
36
|
if [ -s "$AUDIT_FILE" ]; then
|
|
37
|
-
|
|
37
|
+
# Portable file-mtime read. BSD `date -r` takes epoch SECONDS (not a file
|
|
38
|
+
# path), so the previous `date -r "$AUDIT_FILE"` always errored on macOS and
|
|
39
|
+
# mislabeled every rotated file with today's date.
|
|
40
|
+
case "$(uname -s)" in
|
|
41
|
+
Darwin|*BSD*) MTIME="$(stat -f %m "$AUDIT_FILE" 2>/dev/null || true)" ;;
|
|
42
|
+
*) MTIME="$(stat -c %Y "$AUDIT_FILE" 2>/dev/null || true)" ;;
|
|
43
|
+
esac
|
|
44
|
+
if [ -n "$MTIME" ]; then
|
|
45
|
+
# BSD: date -r <epoch>; GNU: date -d @<epoch>
|
|
46
|
+
STAMP="$(date -u -r "$MTIME" +"%Y-%m-%d" 2>/dev/null \
|
|
47
|
+
|| date -u -d "@$MTIME" +"%Y-%m-%d" 2>/dev/null \
|
|
48
|
+
|| date -u +"%Y-%m-%d")"
|
|
49
|
+
else
|
|
50
|
+
STAMP="$(date -u +"%Y-%m-%d")"
|
|
51
|
+
fi
|
|
38
52
|
ROTATED="$AUDIT_DIR/audit.${STAMP}.jsonl"
|
|
39
53
|
|
|
40
|
-
#
|
|
41
|
-
|
|
42
|
-
|
|
54
|
+
# Atomic against concurrent appenders: mv the live inode FIRST, then create
|
|
55
|
+
# a fresh empty live file. Writers appending via >> to the moved inode land
|
|
56
|
+
# in the spool and are preserved; the old cat-then-truncate flow lost any
|
|
57
|
+
# event appended between the copy and the truncate.
|
|
58
|
+
SPOOL="$AUDIT_DIR/.audit.rotate.$$"
|
|
59
|
+
mv "$AUDIT_FILE" "$SPOOL"
|
|
60
|
+
: > "$AUDIT_FILE"
|
|
61
|
+
chmod 600 "$AUDIT_FILE" 2>/dev/null || true
|
|
62
|
+
|
|
63
|
+
# Don't clobber a rotated file from earlier today (multiple invocations).
|
|
64
|
+
# Earlier rotations may already be gzipped; appending a second gzip member
|
|
65
|
+
# is valid (gunzip concatenates members).
|
|
66
|
+
if [ -f "$ROTATED.gz" ] && command -v gzip >/dev/null 2>&1; then
|
|
67
|
+
if gzip -c "$SPOOL" >> "$ROTATED.gz" 2>/dev/null; then
|
|
68
|
+
rm -f "$SPOOL"
|
|
69
|
+
else
|
|
70
|
+
mv "$SPOOL" "$ROTATED"
|
|
71
|
+
fi
|
|
72
|
+
elif [ -f "$ROTATED" ]; then
|
|
73
|
+
cat "$SPOOL" >> "$ROTATED"
|
|
74
|
+
rm -f "$SPOOL"
|
|
43
75
|
else
|
|
44
|
-
mv "$
|
|
76
|
+
mv "$SPOOL" "$ROTATED"
|
|
45
77
|
fi
|
|
46
78
|
|
|
47
79
|
# gzip immediately to save space
|
|
48
|
-
if command -v gzip >/dev/null 2>&1; then
|
|
80
|
+
if [ -f "$ROTATED" ] && command -v gzip >/dev/null 2>&1; then
|
|
49
81
|
gzip -f "$ROTATED" 2>/dev/null || true
|
|
50
82
|
fi
|
|
51
|
-
|
|
52
|
-
# Reset live file (mode 0600 - sensitive)
|
|
53
|
-
: > "$AUDIT_FILE"
|
|
54
|
-
chmod 600 "$AUDIT_FILE" 2>/dev/null || true
|
|
55
83
|
fi
|
|
56
84
|
|
|
57
85
|
# Drop archives older than KEEP_DAYS
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// Internal markdown link checker for the user-facing docs surface
|
|
2
|
+
// Internal markdown link checker for the user-facing docs surface and the
|
|
3
|
+
// pipeline instruction tree.
|
|
3
4
|
//
|
|
4
|
-
// Scope: root *.md + docs/**/*.md + examples/**/*.md
|
|
5
|
-
// relative link target ([text](./path) or
|
|
6
|
-
// an existing file or directory.
|
|
7
|
-
// pure #anchors
|
|
8
|
-
//
|
|
5
|
+
// Scope: root *.md + docs/**/*.md + examples/**/*.md + pipeline/**/*.md.
|
|
6
|
+
// Verifies that every relative link target ([text](./path) or
|
|
7
|
+
// [text](path#anchor)) resolves to an existing file or directory. Skipped:
|
|
8
|
+
// external (http/https/mailto) links, pure #anchors, template links whose
|
|
9
|
+
// path contains a {placeholder} variable, and links through shell
|
|
10
|
+
// variables ($HOME, ...) - except $HOME/.claude/multi-agent-refs/, which
|
|
11
|
+
// is checked against its repo source (pipeline/multi-agent-refs/) so a
|
|
12
|
+
// ref rename cannot leave stale runtime links behind. This is a drift
|
|
13
|
+
// gate for file moves/renames, not a network crawler.
|
|
14
|
+
//
|
|
15
|
+
// Excluded trees: pipeline/skills/shared/external/ is vendored community
|
|
16
|
+
// skill content - several packs (the Apple HIG set) index upstream
|
|
17
|
+
// references/ companion files that are deliberately not vendored here.
|
|
9
18
|
|
|
10
19
|
import { readFileSync, existsSync, readdirSync, statSync } from "node:fs";
|
|
11
20
|
import { dirname, join, resolve } from "node:path";
|
|
@@ -25,10 +34,13 @@ function mdFiles(dir, acc = []) {
|
|
|
25
34
|
|
|
26
35
|
const targets = [
|
|
27
36
|
...readdirSync(repoRoot).filter((f) => f.endsWith(".md")).map((f) => join(repoRoot, f)),
|
|
28
|
-
...["docs", "examples"].filter((d) => existsSync(join(repoRoot, d))).flatMap((d) => mdFiles(join(repoRoot, d))),
|
|
37
|
+
...["docs", "examples", "pipeline"].filter((d) => existsSync(join(repoRoot, d))).flatMap((d) => mdFiles(join(repoRoot, d))),
|
|
29
38
|
// CHANGELOG-archive.md is frozen history - it legitimately references docs
|
|
30
39
|
// that were deleted in later releases.
|
|
31
|
-
]
|
|
40
|
+
]
|
|
41
|
+
.filter((f) => !f.endsWith("CHANGELOG-archive.md"))
|
|
42
|
+
// Vendored community skill packs (see header note).
|
|
43
|
+
.filter((f) => !f.includes(join("pipeline", "skills", "shared", "external") + "/"));
|
|
32
44
|
|
|
33
45
|
const LINK_RE = /\[[^\]]*\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;
|
|
34
46
|
const errors = [];
|
|
@@ -42,9 +54,22 @@ for (const file of targets) {
|
|
|
42
54
|
for (const match of text.matchAll(LINK_RE)) {
|
|
43
55
|
const raw = match[1];
|
|
44
56
|
if (/^(https?:|mailto:|#)/.test(raw)) continue;
|
|
57
|
+
// Template placeholders ({fileKey}, {slug}, ...) resolve at runtime,
|
|
58
|
+
// never on disk - skip them.
|
|
59
|
+
if (raw.includes("{") || raw.includes("}")) continue;
|
|
45
60
|
const target = raw.split("#")[0];
|
|
46
61
|
if (target === "") continue;
|
|
47
|
-
|
|
62
|
+
let resolved;
|
|
63
|
+
const refsPrefix = "$HOME/.claude/multi-agent-refs/";
|
|
64
|
+
if (target.startsWith(refsPrefix)) {
|
|
65
|
+
// Runtime install path with a repo source - check the source tree.
|
|
66
|
+
resolved = join(repoRoot, "pipeline", "multi-agent-refs", target.slice(refsPrefix.length));
|
|
67
|
+
} else if (target.includes("$") || target.startsWith("~")) {
|
|
68
|
+
// Other shell-variable / home paths resolve at runtime only.
|
|
69
|
+
continue;
|
|
70
|
+
} else {
|
|
71
|
+
resolved = resolve(dirname(file), decodeURIComponent(target));
|
|
72
|
+
}
|
|
48
73
|
if (!existsSync(resolved)) {
|
|
49
74
|
errors.push(`${file.slice(repoRoot.length + 1)}: broken link -> ${raw}`);
|
|
50
75
|
}
|
|
@@ -85,7 +85,6 @@ function loadDiff() {
|
|
|
85
85
|
if (!existsSync(DIFF_FILE)) throw new Error(`diff file not found: ${DIFF_FILE}`);
|
|
86
86
|
return readFileSync(DIFF_FILE, "utf8");
|
|
87
87
|
}
|
|
88
|
-
BASE = resolveBase();
|
|
89
88
|
return git(["diff", "--unified=0", `${BASE}...${HEAD}`]);
|
|
90
89
|
}
|
|
91
90
|
|
|
@@ -295,6 +294,10 @@ function buildRow(stat, addedLines, allChangedPaths) {
|
|
|
295
294
|
}
|
|
296
295
|
|
|
297
296
|
function main() {
|
|
297
|
+
// Resolve the base ref ONCE, before any git diff runs. loadNumstat() and
|
|
298
|
+
// loadDiff() both interpolate BASE; resolving it lazily inside loadDiff()
|
|
299
|
+
// used to leave BASE=null for the numstat call (git diff null...HEAD).
|
|
300
|
+
if (!DIFF_FILE) BASE = resolveBase();
|
|
298
301
|
const numstat = loadNumstat();
|
|
299
302
|
if (numstat.length === 0) {
|
|
300
303
|
process.stderr.write("diff-risk-score: empty diff\n");
|
|
@@ -138,7 +138,16 @@ if (body.trim().length === 0) {
|
|
|
138
138
|
const successRe = userRegExp(args["success-pattern"], DEFAULT_MARKERS[claim].success, "success");
|
|
139
139
|
const failureRe = userRegExp(args["failure-pattern"], DEFAULT_MARKERS[claim].failure, "failure");
|
|
140
140
|
|
|
141
|
-
|
|
141
|
+
// Zero-count summaries ("0 failed", "0 tests failed out of 10", "0 failures")
|
|
142
|
+
// are success statements, not failure markers. The default \bFAILED\b marker
|
|
143
|
+
// would otherwise flip a passing log to fail - and failure is decisive. Only
|
|
144
|
+
// applied when the DEFAULT failure marker is in play; a caller-supplied
|
|
145
|
+
// --failure-pattern is matched against the raw body untouched.
|
|
146
|
+
const ZERO_FAIL_SUMMARY = /\b0\s+(?:\w+\s+){0,2}(?:failed|failures?|failing)\b/gi;
|
|
147
|
+
const usingDefaultFailure = !args["failure-pattern"] || args["failure-pattern"] === true;
|
|
148
|
+
const failureBody = usingDefaultFailure ? body.replace(ZERO_FAIL_SUMMARY, "") : body;
|
|
149
|
+
|
|
150
|
+
const showsFailure = failureRe.test(failureBody);
|
|
142
151
|
const showsSuccess = successRe.test(body);
|
|
143
152
|
|
|
144
153
|
// Failure is DECISIVE. A log that shows a definitive failure marker cannot
|
|
@@ -1,17 +1,19 @@
|
|
|
1
|
+
.claude/.pipeline-version 1
|
|
1
2
|
.claude/CLAUDE.md 1
|
|
2
3
|
.claude/agents 8
|
|
3
4
|
.claude/commands 44
|
|
4
|
-
.claude/lib
|
|
5
|
+
.claude/lib 25
|
|
5
6
|
.claude/multi-agent-preferences.json 1
|
|
6
7
|
.claude/multi-agent-refs 51
|
|
7
8
|
.claude/rules 12
|
|
8
9
|
.claude/schemas 23
|
|
9
|
-
.claude/scripts
|
|
10
|
+
.claude/scripts 190
|
|
10
11
|
.claude/settings.json 1
|
|
11
12
|
.claude/skills 428
|
|
13
|
+
.copilot/.pipeline-version 1
|
|
12
14
|
.copilot/agents 8
|
|
13
15
|
.copilot/copilot-instructions.md 1
|
|
14
|
-
.copilot/lib
|
|
16
|
+
.copilot/lib 25
|
|
15
17
|
.copilot/schemas 23
|
|
16
|
-
.copilot/scripts
|
|
18
|
+
.copilot/scripts 190
|
|
17
19
|
.copilot/skills 467
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
943
|