@mu-cabin/muc-cli 0.5.6 → 0.6.1
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/README.md +35 -303
- package/REFERENCE.md +31 -768
- package/dist/muc.mjs +28049 -34213
- package/package.json +13 -18
- package/skills/muc/SKILL.md +30 -0
- package/skills/muc/references/mutations.md +85 -0
- package/skills/muc/references/operations.md +116 -0
- package/skills/muc/references/profiles-and-lifecycle.md +22 -0
- package/skills/muc/references/safety-and-output.md +13 -0
- package/dist/templates/code-handler/bin/prep-repo +0 -386
- package/dist/templates/code-handler/setup-notes.md +0 -63
- package/dist/templates/code-handler/system-prompt.md +0 -153
- package/dist/templates/code-handler/template.json +0 -15
- package/dist/templates/general-chat/setup-notes.md +0 -64
- package/dist/templates/general-chat/system-prompt.md +0 -44
- package/dist/templates/general-chat/template.json +0 -12
- package/dist/templates/image-generator/setup-notes.md +0 -64
- package/dist/templates/image-generator/system-prompt.md +0 -74
- package/dist/templates/image-generator/template.json +0 -14
|
@@ -1,386 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# prep-repo — bare-cache + per-turn-worktree provisioner for the
|
|
3
|
-
# code-review-gitlab assistant template.
|
|
4
|
-
#
|
|
5
|
-
# Modes:
|
|
6
|
-
# --repo <url> --author-uid <UID> --chat-id <id> --msg-id <id>
|
|
7
|
-
# [--branch-topic <topic>]
|
|
8
|
-
# Create a fresh worktree on a new branch
|
|
9
|
-
# muc-assistant/<topic> off origin/HEAD. If the branch exists, appends
|
|
10
|
-
# -2, -3, ... . Member-checks <UID> at Developer+. Sets git author to
|
|
11
|
-
# <UID> in the worktree. Writes the Muc-Assistant-Origin footer helper
|
|
12
|
-
# to ../muc-assistant-origin.txt and prints the absolute worktree path
|
|
13
|
-
# on stdout.
|
|
14
|
-
#
|
|
15
|
-
# --repo <url> --author-uid <UID> --reuse-branch <branch>
|
|
16
|
-
# Same, but check out an existing branch (used by /refine to
|
|
17
|
-
# continue an existing assistant-MR). Member-checks Developer+.
|
|
18
|
-
#
|
|
19
|
-
# --check-access <reporter|developer> --author-uid <UID> --repo <url>
|
|
20
|
-
# Member-check only; no worktree. Prints "ok" on success.
|
|
21
|
-
#
|
|
22
|
-
# Cache root: ${MUC_ASSISTANT_REPO_CACHE:-$HOME/.cache/muc-assistant/repos}
|
|
23
|
-
# The muc daemon sets MUC_ASSISTANT_REPO_CACHE to "<cwd>/.cache/repos"
|
|
24
|
-
# so the bare-cache lives inside the per-turn workspace and is wiped
|
|
25
|
-
# with the rest of the cwd on cleanup. Running this script outside the
|
|
26
|
-
# daemon falls back to $HOME/.cache/muc-assistant/repos.
|
|
27
|
-
# Worktree root for /implement /refine: $PWD/repo (the daemon's per-turn
|
|
28
|
-
# workspace dir, wiped after the turn).
|
|
29
|
-
#
|
|
30
|
-
# Failure: anything not OK -> non-zero exit + human message on stderr.
|
|
31
|
-
# The agent surfaces stderr verbatim back to the chat.
|
|
32
|
-
|
|
33
|
-
set -euo pipefail
|
|
34
|
-
|
|
35
|
-
CACHE_ROOT="${MUC_ASSISTANT_REPO_CACHE:-$HOME/.cache/muc-assistant/repos}"
|
|
36
|
-
|
|
37
|
-
die() {
|
|
38
|
-
printf 'prep-repo: %s\n' "$*" >&2
|
|
39
|
-
exit 1
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
require_cmd() {
|
|
43
|
-
command -v "$1" >/dev/null 2>&1 || die "missing required tool: $1"
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
require_cmd git
|
|
47
|
-
require_cmd glab
|
|
48
|
-
|
|
49
|
-
usage() {
|
|
50
|
-
cat >&2 <<'USAGE'
|
|
51
|
-
usage:
|
|
52
|
-
prep-repo --repo <url> --author-uid <UID> --chat-id <id> --msg-id <id> [--branch-topic <topic>]
|
|
53
|
-
prep-repo --repo <url> --author-uid <UID> --reuse-branch <branch>
|
|
54
|
-
prep-repo --check-access <reporter|developer> --author-uid <UID> --repo <url>
|
|
55
|
-
USAGE
|
|
56
|
-
exit 2
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
# --- arg parse -----------------------------------------------------------
|
|
60
|
-
REPO=""
|
|
61
|
-
AUTHOR_UID=""
|
|
62
|
-
CHAT_ID=""
|
|
63
|
-
MSG_ID=""
|
|
64
|
-
BRANCH_TOPIC=""
|
|
65
|
-
REUSE_BRANCH=""
|
|
66
|
-
CHECK_ACCESS=""
|
|
67
|
-
|
|
68
|
-
while [[ $# -gt 0 ]]; do
|
|
69
|
-
case "$1" in
|
|
70
|
-
--repo) REPO="${2:-}"; shift 2 ;;
|
|
71
|
-
--author-uid) AUTHOR_UID="${2:-}"; shift 2 ;;
|
|
72
|
-
--chat-id) CHAT_ID="${2:-}"; shift 2 ;;
|
|
73
|
-
--msg-id) MSG_ID="${2:-}"; shift 2 ;;
|
|
74
|
-
--branch-topic) BRANCH_TOPIC="${2:-}"; shift 2 ;;
|
|
75
|
-
--reuse-branch) REUSE_BRANCH="${2:-}"; shift 2 ;;
|
|
76
|
-
--check-access) CHECK_ACCESS="${2:-}"; shift 2 ;;
|
|
77
|
-
-h|--help) usage ;;
|
|
78
|
-
*) die "unknown arg: $1" ;;
|
|
79
|
-
esac
|
|
80
|
-
done
|
|
81
|
-
|
|
82
|
-
[[ -n "$REPO" ]] || { usage; }
|
|
83
|
-
[[ -n "$AUTHOR_UID" ]] || die "missing --author-uid"
|
|
84
|
-
|
|
85
|
-
# --- repo url -> group/repo + host + cache slug -------------------------
|
|
86
|
-
# Accept https://, http://, ssh://, git@host:path SSH-shorthand, and bare
|
|
87
|
-
# "group/repo" slugs. When an explicit protocol prefix is present we *know*
|
|
88
|
-
# the first segment after stripping it is the host, so strip it
|
|
89
|
-
# unconditionally (heuristics on dot-vs-no-dot fail for short LAN hosts
|
|
90
|
-
# like `git@gitlab:foo/bar`).
|
|
91
|
-
#
|
|
92
|
-
# Sets globals: HOST (empty when input was a bare slug), PROJECT, CLONE_REPO,
|
|
93
|
-
# and REQUESTED_REF_PATH (best-effort branch/ref path from GitLab UI URLs).
|
|
94
|
-
parse_repo_url() {
|
|
95
|
-
local s="$1" had_host=0 ui_suffix=""
|
|
96
|
-
HOST=""
|
|
97
|
-
CLONE_REPO=""
|
|
98
|
-
REQUESTED_REF_PATH=""
|
|
99
|
-
s="${s%%#*}"
|
|
100
|
-
s="${s%%\?*}"
|
|
101
|
-
if [[ "$s" == https://* ]]; then s="${s#https://}"; had_host=1
|
|
102
|
-
elif [[ "$s" == http://* ]]; then s="${s#http://}"; had_host=1
|
|
103
|
-
elif [[ "$s" == ssh://* ]]; then s="${s#ssh://}"
|
|
104
|
-
s="${s#*@}" # drop user@
|
|
105
|
-
had_host=1
|
|
106
|
-
elif [[ "$s" == git@* ]]; then s="${s#git@}"
|
|
107
|
-
s="${s/://}" # host:path -> host/path
|
|
108
|
-
had_host=1
|
|
109
|
-
fi
|
|
110
|
-
s="${s%.git}"
|
|
111
|
-
if [[ "$s" == *"/-/"* ]]; then
|
|
112
|
-
ui_suffix="${s#*/-/}"
|
|
113
|
-
s="${s%%/-/*}" # drop GitLab UI suffix /-/...
|
|
114
|
-
case "$ui_suffix" in
|
|
115
|
-
tree/*) REQUESTED_REF_PATH="${ui_suffix#tree/}" ;;
|
|
116
|
-
blob/*) REQUESTED_REF_PATH="${ui_suffix#blob/}" ;;
|
|
117
|
-
raw/*) REQUESTED_REF_PATH="${ui_suffix#raw/}" ;;
|
|
118
|
-
commits/*) REQUESTED_REF_PATH="${ui_suffix#commits/}" ;;
|
|
119
|
-
esac
|
|
120
|
-
REQUESTED_REF_PATH="${REQUESTED_REF_PATH%/}"
|
|
121
|
-
fi
|
|
122
|
-
if (( had_host )); then
|
|
123
|
-
case "$s" in
|
|
124
|
-
*/*) HOST="${s%%/*}"; s="${s#*/}" ;;
|
|
125
|
-
*) die "cannot parse repo (no path after host): $REPO" ;;
|
|
126
|
-
esac
|
|
127
|
-
fi
|
|
128
|
-
s="${s%/}"
|
|
129
|
-
case "$s" in
|
|
130
|
-
*/*) ;;
|
|
131
|
-
*) die "expected <group>/<repo> in: $REPO" ;;
|
|
132
|
-
esac
|
|
133
|
-
PROJECT="$s"
|
|
134
|
-
if (( had_host )); then
|
|
135
|
-
case "$REPO" in
|
|
136
|
-
https://*) CLONE_REPO="https://$HOST/$PROJECT.git" ;;
|
|
137
|
-
http://*) CLONE_REPO="http://$HOST/$PROJECT.git" ;;
|
|
138
|
-
ssh://*) CLONE_REPO="ssh://$HOST/$PROJECT.git" ;;
|
|
139
|
-
git@*) CLONE_REPO="git@$HOST:$PROJECT.git" ;;
|
|
140
|
-
*) CLONE_REPO="$PROJECT" ;;
|
|
141
|
-
esac
|
|
142
|
-
else
|
|
143
|
-
CLONE_REPO="$PROJECT"
|
|
144
|
-
fi
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
parse_repo_url "$REPO"
|
|
148
|
-
SLUG="${PROJECT//\//__}"
|
|
149
|
-
BARE="$CACHE_ROOT/$SLUG.git"
|
|
150
|
-
# When the URL omitted a host we let glab fall back to its configured
|
|
151
|
-
# default (typically the host the user `glab auth login`-ed against).
|
|
152
|
-
glab_api() {
|
|
153
|
-
if [[ -n "$HOST" ]]; then
|
|
154
|
-
glab api --hostname "$HOST" "$@"
|
|
155
|
-
else
|
|
156
|
-
glab api "$@"
|
|
157
|
-
fi
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
# --- glab encoding ------------------------------------------------------
|
|
162
|
-
url_encode() {
|
|
163
|
-
# %-encode every byte so subgroups (with /) and any special chars survive.
|
|
164
|
-
local s="$1" out="" i c
|
|
165
|
-
for ((i=0; i<${#s}; i++)); do
|
|
166
|
-
c="${s:i:1}"
|
|
167
|
-
case "$c" in
|
|
168
|
-
[a-zA-Z0-9._~-]) out+="$c" ;;
|
|
169
|
-
*) printf -v c '%%%02X' "'$c"; out+="$c" ;;
|
|
170
|
-
esac
|
|
171
|
-
done
|
|
172
|
-
printf '%s' "$out"
|
|
173
|
-
}
|
|
174
|
-
PROJECT_ENC="$(url_encode "$PROJECT")"
|
|
175
|
-
|
|
176
|
-
# --- uid resolution -----------------------------------------------------
|
|
177
|
-
# `members/all/<id>` only accepts usernames on GitLab >= 15.4. Older
|
|
178
|
-
# instances (e.g. gitlab.ceair.com) reject a username with HTTP 400
|
|
179
|
-
# "user_id is invalid", which the access check would then mis-report as
|
|
180
|
-
# "not a member". Resolve non-numeric AUTHOR_UID to a numeric user id via
|
|
181
|
-
# `users?username=...` first so the membership query always uses an id
|
|
182
|
-
# the server understands.
|
|
183
|
-
resolve_uid() {
|
|
184
|
-
local input="$1" enc body id
|
|
185
|
-
if [[ "$input" =~ ^[0-9]+$ ]]; then
|
|
186
|
-
printf '%s' "$input"
|
|
187
|
-
return
|
|
188
|
-
fi
|
|
189
|
-
enc="$(url_encode "$input")"
|
|
190
|
-
if ! body="$(glab_api "users?username=$enc" 2>/dev/null)"; then
|
|
191
|
-
die "failed to resolve username '$input' via glab API (host: ${HOST:-default}; check token + network)"
|
|
192
|
-
fi
|
|
193
|
-
id="$(printf '%s' "$body" | sed -n 's/.*"id"[[:space:]]*:[[:space:]]*\([0-9][0-9]*\).*/\1/p' | head -n1)"
|
|
194
|
-
if [[ -z "$id" ]]; then
|
|
195
|
-
die "no GitLab user named '$input' on ${HOST:-the configured glab host}"
|
|
196
|
-
fi
|
|
197
|
-
printf '%s' "$id"
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
AUTHOR_UID_NUM="$(resolve_uid "$AUTHOR_UID")"
|
|
201
|
-
|
|
202
|
-
# --- access level check -------------------------------------------------
|
|
203
|
-
# `members/all/<numeric-id>` returns 200 + JSON containing `access_level`
|
|
204
|
-
# (10=Guest 20=Reporter 30=Developer 40=Maintainer 50=Owner). 404 = not
|
|
205
|
-
# a member. We pass --hostname when the input URL gave us a host;
|
|
206
|
-
# otherwise glab falls back to its configured default host.
|
|
207
|
-
access_level() {
|
|
208
|
-
local uid="$1" body level
|
|
209
|
-
if ! body="$(glab_api "projects/$PROJECT_ENC/members/all/$uid" 2>/dev/null)"; then
|
|
210
|
-
printf '0'
|
|
211
|
-
return
|
|
212
|
-
fi
|
|
213
|
-
level="$(printf '%s' "$body" | sed -n 's/.*"access_level"[[:space:]]*:[[:space:]]*\([0-9][0-9]*\).*/\1/p' | head -n1)"
|
|
214
|
-
printf '%s' "${level:-0}"
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
require_access() {
|
|
218
|
-
local need="$1" lvl
|
|
219
|
-
lvl="$(access_level "$AUTHOR_UID_NUM")"
|
|
220
|
-
if (( lvl == 0 )); then
|
|
221
|
-
die "$AUTHOR_UID (id=$AUTHOR_UID_NUM) is not a member of $PROJECT (or $PROJECT does not exist / glab token can't see it)"
|
|
222
|
-
fi
|
|
223
|
-
if (( lvl < need )); then
|
|
224
|
-
die "$AUTHOR_UID has access_level=$lvl on $PROJECT, need >= $need"
|
|
225
|
-
fi
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
# --- check-access mode --------------------------------------------------
|
|
229
|
-
if [[ -n "$CHECK_ACCESS" ]]; then
|
|
230
|
-
case "$CHECK_ACCESS" in
|
|
231
|
-
reporter) require_access 20 ;;
|
|
232
|
-
developer) require_access 30 ;;
|
|
233
|
-
*) die "unknown --check-access value: $CHECK_ACCESS (expected reporter|developer)" ;;
|
|
234
|
-
esac
|
|
235
|
-
printf 'ok\n'
|
|
236
|
-
exit 0
|
|
237
|
-
fi
|
|
238
|
-
|
|
239
|
-
# Both write modes require Developer+.
|
|
240
|
-
require_access 30
|
|
241
|
-
|
|
242
|
-
# --- bare cache: ensure + fetch ----------------------------------------
|
|
243
|
-
mkdir -p "$CACHE_ROOT"
|
|
244
|
-
if [[ ! -d "$BARE/refs" ]]; then
|
|
245
|
-
# Initial mirror clone. Use the normalized project URL so GitLab UI links
|
|
246
|
-
# (/-/tree, /-/blob, /-/merge_requests, ...) clone the repository, not the
|
|
247
|
-
# web page URL. The host/protocol still comes from the user-supplied URL so
|
|
248
|
-
# glab/git pick up the right stored credential helper.
|
|
249
|
-
if ! git clone --bare --filter=blob:none "$CLONE_REPO" "$BARE" >&2; then
|
|
250
|
-
die "git clone --bare failed for $CLONE_REPO -> $BARE"
|
|
251
|
-
fi
|
|
252
|
-
fi
|
|
253
|
-
|
|
254
|
-
if ! git -C "$BARE" fetch --prune --quiet origin >&2; then
|
|
255
|
-
die "git fetch failed in $BARE"
|
|
256
|
-
fi
|
|
257
|
-
|
|
258
|
-
# Reap dead worktree registrations from prior crashed turns so a new add
|
|
259
|
-
# of the same branch name doesn't error out with "already checked out".
|
|
260
|
-
git -C "$BARE" worktree prune --quiet 2>/dev/null || true
|
|
261
|
-
|
|
262
|
-
# --- worktree --------------------------------------------------------------
|
|
263
|
-
# Always create the worktree as a "repo/" subdir of the current cwd so the
|
|
264
|
-
# daemon's per-turn workspace cleanup (rm -rf cwd) takes it with it.
|
|
265
|
-
WT="$PWD/repo"
|
|
266
|
-
[[ -e "$WT" ]] && rm -rf "$WT"
|
|
267
|
-
|
|
268
|
-
if [[ -n "$REUSE_BRANCH" ]]; then
|
|
269
|
-
# /refine path: check out an existing branch.
|
|
270
|
-
if ! git -C "$BARE" worktree add --quiet "$WT" "$REUSE_BRANCH" >&2; then
|
|
271
|
-
die "worktree add failed for branch $REUSE_BRANCH (does it exist on origin?)"
|
|
272
|
-
fi
|
|
273
|
-
else
|
|
274
|
-
# /implement path: brand new branch off origin/HEAD.
|
|
275
|
-
[[ -n "$CHAT_ID" ]] || die "missing --chat-id"
|
|
276
|
-
[[ -n "$MSG_ID" ]] || die "missing --msg-id"
|
|
277
|
-
|
|
278
|
-
# Sanitize the model-supplied topic into a compact, readable branch slug.
|
|
279
|
-
sanitize_topic() {
|
|
280
|
-
local s="$1"
|
|
281
|
-
s="$(printf '%s' "$s" | LC_ALL=C tr '[:upper:]' '[:lower:]')"
|
|
282
|
-
# Keep branch topics readable and portable: lowercase ascii + dashes.
|
|
283
|
-
s="$(printf '%s' "$s" | LC_ALL=C tr -c 'a-z0-9' '-')"
|
|
284
|
-
# Collapse runs of dashes.
|
|
285
|
-
s="$(printf '%s' "$s" | sed -E 's/-+/-/g; s/^-//; s/-$//')"
|
|
286
|
-
# Cap at 70 chars; suffixes like -2 are appended after this.
|
|
287
|
-
s="${s:0:70}"
|
|
288
|
-
s="$(printf '%s' "$s" | sed -E 's/-$//')"
|
|
289
|
-
printf '%s' "${s:-code-change}"
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
branch_exists() {
|
|
293
|
-
local branch="$1"
|
|
294
|
-
git -C "$BARE" show-ref --verify --quiet "refs/heads/$branch" ||
|
|
295
|
-
git -C "$BARE" show-ref --verify --quiet "refs/remotes/origin/$branch"
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
TOPIC_SAFE="$(sanitize_topic "$BRANCH_TOPIC")"
|
|
299
|
-
BRANCH_BASE="muc-assistant/$TOPIC_SAFE"
|
|
300
|
-
BRANCH="$BRANCH_BASE"
|
|
301
|
-
for ((n = 2; n <= 99; n++)); do
|
|
302
|
-
if ! branch_exists "$BRANCH"; then
|
|
303
|
-
break
|
|
304
|
-
fi
|
|
305
|
-
BRANCH="$BRANCH_BASE-$n"
|
|
306
|
-
done
|
|
307
|
-
if branch_exists "$BRANCH"; then
|
|
308
|
-
die "could not find a free branch name for $BRANCH_BASE after suffix -99"
|
|
309
|
-
fi
|
|
310
|
-
|
|
311
|
-
resolve_requested_ref() {
|
|
312
|
-
[[ -n "$REQUESTED_REF_PATH" ]] || return 0
|
|
313
|
-
if git -C "$BARE" rev-parse --verify --quiet "origin/$REQUESTED_REF_PATH^{commit}" >/dev/null; then
|
|
314
|
-
printf 'origin/%s' "$REQUESTED_REF_PATH"
|
|
315
|
-
return 0
|
|
316
|
-
fi
|
|
317
|
-
if git -C "$BARE" rev-parse --verify --quiet "$REQUESTED_REF_PATH^{commit}" >/dev/null; then
|
|
318
|
-
printf '%s' "$REQUESTED_REF_PATH"
|
|
319
|
-
return 0
|
|
320
|
-
fi
|
|
321
|
-
|
|
322
|
-
local ref best="" best_origin=""
|
|
323
|
-
while IFS= read -r ref; do
|
|
324
|
-
[[ "$ref" != HEAD ]] || continue
|
|
325
|
-
case "$REQUESTED_REF_PATH" in
|
|
326
|
-
"$ref"|"$ref"/*)
|
|
327
|
-
if (( ${#ref} > ${#best_origin} )); then
|
|
328
|
-
best_origin="$ref"
|
|
329
|
-
fi
|
|
330
|
-
;;
|
|
331
|
-
esac
|
|
332
|
-
done < <(git -C "$BARE" for-each-ref --format='%(refname:strip=3)' refs/remotes/origin)
|
|
333
|
-
if [[ -n "$best_origin" ]]; then
|
|
334
|
-
printf 'origin/%s' "$best_origin"
|
|
335
|
-
return 0
|
|
336
|
-
fi
|
|
337
|
-
|
|
338
|
-
while IFS= read -r ref; do
|
|
339
|
-
case "$REQUESTED_REF_PATH" in
|
|
340
|
-
"$ref"|"$ref"/*)
|
|
341
|
-
if (( ${#ref} > ${#best} )); then
|
|
342
|
-
best="$ref"
|
|
343
|
-
fi
|
|
344
|
-
;;
|
|
345
|
-
esac
|
|
346
|
-
done < <(git -C "$BARE" for-each-ref --format='%(refname:strip=2)' refs/heads)
|
|
347
|
-
|
|
348
|
-
if [[ -n "$best" ]]; then
|
|
349
|
-
printf '%s' "$best"
|
|
350
|
-
fi
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
# Prefer the branch encoded in a user-supplied GitLab URL (for example
|
|
354
|
-
# /-/tree/<branch> or /-/blob/<branch>/path). Fall back to the project's
|
|
355
|
-
# default branch only when the URL did not carry a resolvable branch.
|
|
356
|
-
HEAD_REF="$(resolve_requested_ref)"
|
|
357
|
-
if [[ -z "$HEAD_REF" ]]; then
|
|
358
|
-
HEAD_REF="$(git -C "$BARE" symbolic-ref --quiet refs/remotes/origin/HEAD 2>/dev/null || true)"
|
|
359
|
-
HEAD_REF="${HEAD_REF#refs/remotes/}"
|
|
360
|
-
fi
|
|
361
|
-
if [[ -z "$HEAD_REF" ]]; then
|
|
362
|
-
HEAD_REF="$(git -C "$BARE" symbolic-ref --quiet HEAD 2>/dev/null || true)"
|
|
363
|
-
HEAD_REF="${HEAD_REF#refs/heads/}"
|
|
364
|
-
fi
|
|
365
|
-
if [[ -z "$HEAD_REF" ]]; then
|
|
366
|
-
for candidate in origin/main main origin/master master; do
|
|
367
|
-
if git -C "$BARE" rev-parse --verify --quiet "$candidate^{commit}" >/dev/null; then
|
|
368
|
-
HEAD_REF="$candidate"
|
|
369
|
-
break
|
|
370
|
-
fi
|
|
371
|
-
done
|
|
372
|
-
fi
|
|
373
|
-
[[ -n "$HEAD_REF" ]] || die "cannot resolve default branch for $PROJECT"
|
|
374
|
-
|
|
375
|
-
if ! git -C "$BARE" worktree add --quiet -b "$BRANCH" "$WT" "$HEAD_REF" >&2; then
|
|
376
|
-
die "worktree add -b $BRANCH failed (branch already exists?)"
|
|
377
|
-
fi
|
|
378
|
-
|
|
379
|
-
printf 'Muc-Assistant-Origin: chat:%s msg:%s\n' "$CHAT_ID" "$MSG_ID" > "$PWD/muc-assistant-origin.txt"
|
|
380
|
-
fi
|
|
381
|
-
|
|
382
|
-
# --- author identity on the worktree ------------------------------------
|
|
383
|
-
git -C "$WT" config user.name "$AUTHOR_UID" >&2
|
|
384
|
-
git -C "$WT" config user.email "${AUTHOR_UID}@CEAIR.COM" >&2
|
|
385
|
-
|
|
386
|
-
printf '%s\n' "$WT"
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
# <ASSISTANT_NAME> 安装提示
|
|
2
|
-
|
|
3
|
-
模板已经把以下文件落到本机:
|
|
4
|
-
|
|
5
|
-
- 配置:`<MUC_ASSISTANT_DIR>/config.json`
|
|
6
|
-
- 系统 prompt:`<MUC_ASSISTANT_DIR>/system-prompt.md`
|
|
7
|
-
- 辅助脚本:`<MUC_ASSISTANT_BIN_DIR>/prep-repo`(已 chmod +x)
|
|
8
|
-
|
|
9
|
-
启动前请确认:
|
|
10
|
-
|
|
11
|
-
1. **glab 已登录到内网 GitLab**
|
|
12
|
-
```bash
|
|
13
|
-
glab auth status
|
|
14
|
-
# 期望看到 gitlab.ceair.com 已登录、token 有效。
|
|
15
|
-
# 如未登录:glab auth login --hostname gitlab.ceair.com
|
|
16
|
-
```
|
|
17
|
-
助手会复用本机 glab 凭据 push 分支与开 MR;MR 在 GitLab 上显示
|
|
18
|
-
"Opened by <你>",但每个 commit 的 author 是触发它的 MUC 用户
|
|
19
|
-
(例如 `WANGLULU8 <WANGLULU8@CEAIR.COM>`)。
|
|
20
|
-
|
|
21
|
-
2. **裸仓库缓存目录**(首次运行会自动创建,无需手动)
|
|
22
|
-
守护进程把 `MUC_ASSISTANT_REPO_CACHE` 指向当前 turn 的工作目录
|
|
23
|
-
(`~/.muc/assistants/<ASSISTANT_NAME>/workspace/turn-<msg-id>/.cache/repos`),
|
|
24
|
-
prep-repo 落在这里,turn 结束随工作目录一起被清理。
|
|
25
|
-
不需要再维护 `~/.cache/muc-assistant/repos/` —— 已不再使用。
|
|
26
|
-
|
|
27
|
-
3. **触发人是目标仓库成员**
|
|
28
|
-
glab 的登录身份始终是助手所属的机器人账号,但触发人可以是任何人。
|
|
29
|
-
助手会用 inbound 消息里的 MUC UID 调 `prep-repo --check-access` 查
|
|
30
|
-
GitLab 项目成员列表,只要触发人在项目成员里就放行;不在成员里则把
|
|
31
|
-
prep-repo 的 stderr 原样回给用户。
|
|
32
|
-
|
|
33
|
-
启动(先确保守护进程在运行):
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
# 第一次:先把守护进程拉起来(前台进程,建议放进 tmux/screen)。
|
|
37
|
-
muc assistantd-start
|
|
38
|
-
|
|
39
|
-
# 然后把这个助手挂到守护进程上:
|
|
40
|
-
muc assistant-start <ASSISTANT_NAME>
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
停止:
|
|
44
|
-
|
|
45
|
-
```bash
|
|
46
|
-
muc assistant-stop <ASSISTANT_NAME> # 摘掉这个助手
|
|
47
|
-
# 守护进程仍在运行;要彻底退出再:
|
|
48
|
-
muc assistantd-stop
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
修改 prompt:
|
|
52
|
-
|
|
53
|
-
```bash
|
|
54
|
-
muc assistant-edit <ASSISTANT_NAME>
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
测试触发(在 MUC 里 @ 助手账号):
|
|
58
|
-
|
|
59
|
-
```
|
|
60
|
-
/code http://gitlab.ceair.com/<group>/<repo>/-/merge_requests/<n>
|
|
61
|
-
/code http://gitlab.ceair.com/<group>/<repo> 把 X 加上 Y 的检查
|
|
62
|
-
/code 把刚才那个改成可选 # 引用上一条助手回的 MR 通知,或在同一会话里直接接着提
|
|
63
|
-
```
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
# Persona: <ASSISTANT_NAME> — GitLab Code Handler
|
|
2
|
-
|
|
3
|
-
你是 <ASSISTANT_NAME>,一个处理 GitLab 上各种代码任务的助手。回复用中文(除非
|
|
4
|
-
用户主动用英文)。
|
|
5
|
-
|
|
6
|
-
## 唯一入口
|
|
7
|
-
|
|
8
|
-
```
|
|
9
|
-
/code [<gitlab-url>] [<描述>]
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
只处理 gitlab 代码相关的问题,与代码无关也不是对你之前消息的有效追问的,一律 skip。
|
|
13
|
-
|
|
14
|
-
## 你要做什么
|
|
15
|
-
|
|
16
|
-
根据 URL 种类(MR / 项目 / Issue / Commit / File blob / ...)和用户描述,
|
|
17
|
-
自己判断这次是评审、实现、改 MR、解释代码、答疑还是别的什么;动手前用一句话
|
|
18
|
-
说出你的判断,别反复跟用户来回确认。
|
|
19
|
-
|
|
20
|
-
## 触发人识别
|
|
21
|
-
|
|
22
|
-
inbound 消息以 `[NEW ... ] from <UID>: <text>` 开头,`<UID>` 是触发人的公司
|
|
23
|
-
账号(如 `WANGLULU8`)。它用于:
|
|
24
|
-
|
|
25
|
-
1. GitLab 成员校验。
|
|
26
|
-
2. commit 作者(由 `prep-repo --author-uid <UID>` 写入 worktree 的 git config)。
|
|
27
|
-
|
|
28
|
-
解析不出 UID 就停下,回复「无法识别触发人,请联系管理员」。
|
|
29
|
-
|
|
30
|
-
## URL 解析顺序
|
|
31
|
-
|
|
32
|
-
URL 不要求出现在当前消息里。按顺序找:
|
|
33
|
-
|
|
34
|
-
1. 当前消息里的完整 `http(s)://gitlab.ceair.com/...` URL。
|
|
35
|
-
2. quote 引用:inbound 把回复渲染为 `↪ quote #N ... : <preview>`,从 preview 抽 URL。
|
|
36
|
-
3. 同一会话最近一次你自己回出去的 GitLab URL(在你的上下文里)。多个时优先 MR URL。
|
|
37
|
-
4. 都没有 → 回复「请贴一下 GitLab URL,或引用我之前的 MR 通知」并停止。
|
|
38
|
-
|
|
39
|
-
## commit message 前缀(凡是要新建 commit 都要有)
|
|
40
|
-
|
|
41
|
-
只要这次任务会落 commit(任何要 push 代码的活),commit message 必须带前缀。
|
|
42
|
-
常见两种:
|
|
43
|
-
|
|
44
|
-
- `#ignore_scan#` —— 跳过代码扫描门禁的默认前缀。
|
|
45
|
-
- JIRA故事号,例如 `DO23187-1977` —— 跟到具体需求 / Bug 单上。
|
|
46
|
-
|
|
47
|
-
按下面顺序确定,**确定不下来就先问**,别擅自挑一个:
|
|
48
|
-
|
|
49
|
-
1. 用户这次消息里直接写明了(如「commit 用 DO23187-1977」、「按 ignore_scan
|
|
50
|
-
提就行」),用它。
|
|
51
|
-
2. 用户消息里能明显看出工单号(形如 `[A-Z]{2,}\d+(-\d+)?`),用它。
|
|
52
|
-
3. 同一会话里你之前已经问过 / 用户已经回过一次:直接复用,别重复问。
|
|
53
|
-
4. 都没有:在动手前先一句话问清楚(默认 `#ignore_scan#` 还是有JIRA故事号),
|
|
54
|
-
等用户回了再继续 prep-repo / 改代码。
|
|
55
|
-
|
|
56
|
-
最终 commit 形如 `<前缀> <动词> <简述>`,例如:
|
|
57
|
-
|
|
58
|
-
```
|
|
59
|
-
#ignore_scan# feat(login): add captcha refresh
|
|
60
|
-
DO23187-1977 fix(api): null-check session before sign
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
前缀只放第一行,body 里别再重复。凡是新建 commit,commit body/footer 必须带
|
|
64
|
-
一行 `Muc-Assistant-Origin: chat:<chat-id> msg:<msg-id>`;新分支模式下
|
|
65
|
-
`prep-repo` 会把这行写到 `../muc-assistant-origin.txt`,直接复用它。
|
|
66
|
-
|
|
67
|
-
## 工具
|
|
68
|
-
|
|
69
|
-
### `$MUC_ASSISTANT_BIN_DIR/prep-repo` — 仓库脏活统一入口
|
|
70
|
-
|
|
71
|
-
做四件事:成员/access_level 校验、维护 per-turn 裸仓库缓存、创建或复用
|
|
72
|
-
worktree、把触发人写进 worktree 的 git config。建 worktree 时 stdout 是
|
|
73
|
-
worktree 绝对路径;`cd` 进去就能改代码、commit、push。
|
|
74
|
-
|
|
75
|
-
要不要建 worktree、建哪种,由你判断:纯评审 / 解释 / 答疑通常只需要
|
|
76
|
-
`glab` 读一读,加一个 `--check-access` 就够了,别为了改一行不改的活
|
|
77
|
-
也开一个 worktree。要落 commit 时默认开**全新分支**;只有在用户明确要求
|
|
78
|
-
继续某个已有 MR / source_branch 时,才用 `--reuse-branch`,不限制分支是否
|
|
79
|
-
由助手创建。其他写代码场景一律新建 `muc-assistant/<topic>` 分支。
|
|
80
|
-
|
|
81
|
-
```bash
|
|
82
|
-
# 仅查权限
|
|
83
|
-
"$MUC_ASSISTANT_BIN_DIR/prep-repo" --check-access reporter \
|
|
84
|
-
--author-uid <UID> --repo <url>
|
|
85
|
-
|
|
86
|
-
# 全新分支(默认的写模式)
|
|
87
|
-
"$MUC_ASSISTANT_BIN_DIR/prep-repo" --repo <url> \
|
|
88
|
-
--author-uid <UID> --chat-id <chat-id> --msg-id <msg-id> \
|
|
89
|
-
--branch-topic <english-kebab-topic>
|
|
90
|
-
|
|
91
|
-
# 接着已有 assistant MR 的分支
|
|
92
|
-
"$MUC_ASSISTANT_BIN_DIR/prep-repo" --repo <mr-url> \
|
|
93
|
-
--author-uid <UID> --reuse-branch <source_branch>
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
`--repo` 既能吃 project URL,也能吃 MR / issue / commit URL(自动剥到 project)。
|
|
97
|
-
如果用户贴的是 branch / file URL(如 `/-/tree/<branch>`、`/-/blob/<branch>/...`),
|
|
98
|
-
新分支会从该 URL 指向的 branch 派生;没有可解析 branch 时才从项目默认分支派生。
|
|
99
|
-
chat-id:group 是 `[NEW group=<TID>]` 里的 `<TID>`;p2p 是 `dm-with-<UID>`。
|
|
100
|
-
msg-id:prompt 没给就用 `t-<unix秒>`。
|
|
101
|
-
branch-topic:从用户需求提炼 2-7 个英文单词,kebab-case,优先包含工单号
|
|
102
|
-
(如 `do23187-add-guide-link` / `fix-login-timeout`)。脚本会生成
|
|
103
|
-
`muc-assistant/<topic>`;同名已存在时自动用 `-2` / `-3` 后缀。
|
|
104
|
-
|
|
105
|
-
不要跳过 prep-repo 自己 git clone —— 成员校验和作者设置都在里面。
|
|
106
|
-
成员/access_level 校验**永远走 `prep-repo --check-access`**。
|
|
107
|
-
|
|
108
|
-
### `glab` — GitLab CLI(已登录)
|
|
109
|
-
|
|
110
|
-
所有 GitLab 操作**只走 `glab` 一线子命令**(`glab mr ...` / `glab issue ...` /
|
|
111
|
-
`glab repo ...` 等)。**不要**直接打 GitLab REST API:不写 `glab api /...`,
|
|
112
|
-
也不 `curl https://gitlab.ceair.com/api/v4/...`。bot 账号不是管理员,
|
|
113
|
-
`/users/:id/projects`、members 列表这类接口本来就拿不到完整结果,硬查只会
|
|
114
|
-
误导用户。
|
|
115
|
-
|
|
116
|
-
```bash
|
|
117
|
-
glab mr view <mr-url> -F json
|
|
118
|
-
glab mr diff <mr-url>
|
|
119
|
-
glab mr note <mr-url> -m "<text>"
|
|
120
|
-
glab mr create --draft --fill -d "<desc>" # 必须在 worktree 里跑
|
|
121
|
-
glab issue view <issue-url> -F json
|
|
122
|
-
glab repo view <project-url>
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
push 到已有 MR 的 source_branch 后 MR 自动更新;新分支开 Draft MR 用
|
|
126
|
-
`glab mr create --draft`,MR 描述里也要包含同一行 origin footer:
|
|
127
|
-
`Muc-Assistant-Origin: chat:<chat-id> msg:<msg-id>`。不要跑 `npm install` /
|
|
128
|
-
`npm test`,CI 会跑。
|
|
129
|
-
|
|
130
|
-
### 写代码 / 读代码
|
|
131
|
-
|
|
132
|
-
worktree 里随便用 Read / Edit / Write / Grep / Bash。
|
|
133
|
-
|
|
134
|
-
## 安全 / 拒绝条件
|
|
135
|
-
|
|
136
|
-
- 任何 muc 链路的写操作(send / send-file / withdraw / login / init /
|
|
137
|
-
assistantd-* / assistant-* …)一律不准用(base prompt 已声明)。
|
|
138
|
-
- 触发人不是项目成员:把 prep-repo 的报错原文返给用户,不要尝试绕过。
|
|
139
|
-
- 不关闭/重开 issue,不 approve MR,不 force-push,不去 resolve 别人的
|
|
140
|
-
讨论 thread。
|
|
141
|
-
- 拿不准、做不了:直说,别糊弄。
|
|
142
|
-
|
|
143
|
-
## 回复
|
|
144
|
-
|
|
145
|
-
回复内容由你自己根据做了什么决定:
|
|
146
|
-
|
|
147
|
-
- 实现/修改完成:贴 MR URL,必要时附一两句改了什么。
|
|
148
|
-
- 评审完成:「已在 MR 留 N 条评论:<mr-url>」或「看完了,没有需要改的」。
|
|
149
|
-
- 解释 / 答疑:直接给答案,引用具体文件 / 行号。
|
|
150
|
-
- 拿不准、做不了、被拒:说清楚原因。
|
|
151
|
-
|
|
152
|
-
不要为了凑话术重复用户已经知道的东西。失败原因(prep-repo 报错、glab
|
|
153
|
-
报错等)也走正常回复,不要把异常 trace 直接吐到 chat。
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"id": "code-handler",
|
|
3
|
-
"description": "GitLab 代码助手:一个 /code 命令处理评审 / 实现 / 修改 / 解释 / 答疑等所有代码相关任务,由助手根据 URL 类型与上下文自行决定动作与回复形态。授权基于 GitLab 项目成员身份;commit author 取自触发人 UID。",
|
|
4
|
-
"backend": "claude-code",
|
|
5
|
-
"model": "claude-sonnet-4-6",
|
|
6
|
-
"commands": [
|
|
7
|
-
{ "name": "/code", "desc": "处理一个 GitLab 相关的代码任务(评审 / 实现 / 修改 / 解释 / 答疑等,由助手判断)", "usage": "/code [<gitlab-url>] [<描述>]" }
|
|
8
|
-
],
|
|
9
|
-
"placeholders": ["ASSISTANT_NAME", "USER_NAME", "MUC_ASSISTANT_DIR", "MUC_ASSISTANT_BIN_DIR"],
|
|
10
|
-
"prereqs": [
|
|
11
|
-
"本机已完成 `glab auth login`,且已登录到 gitlab.ceair.com(运行 `glab auth status` 确认)",
|
|
12
|
-
"本机有可用的 `git`",
|
|
13
|
-
"触发人是目标 GitLab 项目的成员(glab 用助手的机器人账号登录;触发人由 inbound 的 MUC UID 解析,prep-repo 只校验其在项目成员列表里)"
|
|
14
|
-
]
|
|
15
|
-
}
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
# <ASSISTANT_NAME> 安装提示
|
|
2
|
-
|
|
3
|
-
模板已经把以下文件落到本机:
|
|
4
|
-
|
|
5
|
-
- 配置:`<MUC_ASSISTANT_DIR>/config.json`
|
|
6
|
-
- 系统 prompt:`<MUC_ASSISTANT_DIR>/system-prompt.md`
|
|
7
|
-
|
|
8
|
-
启动前请确认:
|
|
9
|
-
|
|
10
|
-
1. **后端 CLI 已登录可用**
|
|
11
|
-
|
|
12
|
-
模板填了一个默认的 `backend` / `model`,只是图省事;任何 muc 支持的
|
|
13
|
-
后端都能跑通用聊天。想换:
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
muc assistant-new <ASSISTANT_NAME> --template general-chat \
|
|
17
|
-
--backend <claude-code|codex|gemini|cursor> \
|
|
18
|
-
--model <型号>
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
或事后改 `<MUC_ASSISTANT_DIR>/config.json` 里的 `backend` / `model`
|
|
22
|
-
字段。不论选哪个,先确认它已安装、已登录(按各自的登录命令操作),
|
|
23
|
-
再把这个助手挂上去。
|
|
24
|
-
|
|
25
|
-
2. **想让它操作在线文档 / 外部系统?先在后端里把对应能力配好**
|
|
26
|
-
|
|
27
|
-
助手不会自己注册外部凭据,全靠所选后端已有的 skill / MCP / 插件提供能力。
|
|
28
|
-
要让它能查飞书云文档、读知识库、操作 GitLab、访问公司内网 API 之类的场景,
|
|
29
|
-
先在所选后端里把对应技能 / MCP server / 插件配好;没配的话助手会回一句
|
|
30
|
-
"本机后端没装这个能力"。各后端如何添加 skill / MCP,请查各自官方文档。
|
|
31
|
-
|
|
32
|
-
3. **作为兜底接收者**
|
|
33
|
-
|
|
34
|
-
本模板的 `commands: []` —— 助手不绑任何 `/cmd`,会接住会话里**所有**非
|
|
35
|
-
`/help` / `/status` 且没被其他助手认领的消息。建议把它的 `priority` 设
|
|
36
|
-
高一些(数值越大越靠后),让 `/code`、`/image` 之类的专用助手先抢命令。
|
|
37
|
-
|
|
38
|
-
启动(先确保守护进程在运行):
|
|
39
|
-
|
|
40
|
-
```bash
|
|
41
|
-
muc assistantd-start
|
|
42
|
-
muc assistant-start <ASSISTANT_NAME>
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
停止:
|
|
46
|
-
|
|
47
|
-
```bash
|
|
48
|
-
muc assistant-stop <ASSISTANT_NAME> # 摘掉这个助手
|
|
49
|
-
muc assistantd-stop # 退守护进程
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
修改 prompt:
|
|
53
|
-
|
|
54
|
-
```bash
|
|
55
|
-
muc assistant-edit <ASSISTANT_NAME>
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
测试触发(在 MUC 里 @ 助手账号):
|
|
59
|
-
|
|
60
|
-
```
|
|
61
|
-
帮我看看 [文件] 写了啥
|
|
62
|
-
飞书云文档 https://... 里第三章总结一下
|
|
63
|
-
把刚才那段 Python 的异常处理改成 try/except 的标准写法
|
|
64
|
-
```
|