trae-coding-engine 0.1.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/README.md +91 -0
- package/bin/coding-engine.js +16 -0
- package/lib/cli.js +86 -0
- package/lib/index.js +6 -0
- package/lib/setup.js +290 -0
- package/package.json +32 -0
- package/templates/.agents/skills/coding-architecture/SKILL.md +347 -0
- package/templates/.agents/skills/coding-code-review/SKILL.md +95 -0
- package/templates/.agents/skills/coding-cve-remediation/SKILL.md +163 -0
- package/templates/.agents/skills/coding-db-schema/SKILL.md +273 -0
- package/templates/.agents/skills/coding-deploy-config/SKILL.md +129 -0
- package/templates/.agents/skills/coding-docs-audit/SKILL.md +285 -0
- package/templates/.agents/skills/coding-docs-audit-autofix/SKILL.md +33 -0
- package/templates/.agents/skills/coding-http-api/SKILL.md +269 -0
- package/templates/.agents/skills/coding-issue-autodev/SKILL.md +172 -0
- package/templates/.agents/skills/coding-merge-request/SKILL.md +199 -0
- package/templates/.agents/skills/coding-observability/SKILL.md +193 -0
- package/templates/.agents/skills/coding-refactor-insight/SKILL.md +89 -0
- package/templates/.agents/skills/coding-release-merge/SKILL.md +224 -0
- package/templates/.agents/skills/coding-runtime-adapter/SKILL.md +115 -0
- package/templates/.agents/skills/coding-testing/SKILL.md +169 -0
- package/templates/AGENTS.md +179 -0
- package/templates/MR-Template-Default.md +33 -0
- package/templates/Makefile +370 -0
- package/templates/REGISTRY.md +53 -0
- package/templates/scripts/check-adr.sh +283 -0
- package/templates/scripts/check-comment-i18n.py +209 -0
- package/templates/scripts/check-comment-i18n.sh +38 -0
- package/templates/scripts/check-doc-refs.sh +40 -0
- package/templates/scripts/check-docs.sh +103 -0
- package/templates/scripts/check-go-names.sh +59 -0
- package/templates/scripts/check-iam-actions.py +126 -0
- package/templates/scripts/check-migration-sql-immutability.sh +232 -0
- package/templates/scripts/check-mr-desc.sh +165 -0
- package/templates/scripts/check-mr-size.sh +128 -0
- package/templates/scripts/check-mr-title.sh +123 -0
- package/templates/scripts/check-openapi.py +221 -0
- package/templates/scripts/check-rdb-structured-queries.sh +98 -0
- package/templates/scripts/check-repository-transactions.sh +100 -0
- package/templates/scripts/check-runbooks.sh +229 -0
- package/templates/scripts/check-skill-router-sync.py +331 -0
- package/templates/scripts/check-skills.sh +243 -0
- package/templates/scripts/docs-audit-to-issues.py +373 -0
- package/templates/scripts/docs-verification-reminder.sh +63 -0
- package/templates/scripts/find-ci-failures.sh +133 -0
- package/templates/scripts/find-stale-mrs.sh +72 -0
- package/templates/scripts/gen-adr-index.sh +122 -0
- package/templates/scripts/open-mr.sh +536 -0
- package/templates/scripts/regen-agent-md.sh +149 -0
- package/templates/scripts/run-mr-hygiene.sh +140 -0
- package/templates/scripts/runbook.sh +91 -0
- package/templates/scripts/self-maintenance-digest.py +125 -0
- package/templates/scripts/send-self-maintenance-lark-card.py +116 -0
- package/templates/scripts/skill-graph.sh +114 -0
- package/templates/scripts/skill-impact.sh +134 -0
- package/templates/scripts/skill-propose.sh +302 -0
- package/templates/scripts/skill-router-hook.sh +152 -0
|
@@ -0,0 +1,536 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Open or refresh a Codebase MR for the current branch and apply a
|
|
3
|
+
# model-written description.
|
|
4
|
+
#
|
|
5
|
+
# See AGENTS.md §"MR description" for the rule this script enforces.
|
|
6
|
+
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
cd "$(dirname "$0")/.."
|
|
9
|
+
|
|
10
|
+
usage() {
|
|
11
|
+
cat <<'USAGE'
|
|
12
|
+
Open or refresh a Codebase MR for the current branch and apply a model-written
|
|
13
|
+
description when one is provided.
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
scripts/open-mr.sh --title "<conventional-commit title>"
|
|
17
|
+
scripts/open-mr.sh --title "<conventional-commit title>" --body-file /tmp/mr-desc.md
|
|
18
|
+
scripts/open-mr.sh --title "feat(scope): subject" --base release --draft --body-file /tmp/mr-desc.md
|
|
19
|
+
scripts/open-mr.sh --title "feat(scope): subject" --repo epscp/coding_engine --body-file /tmp/mr-desc.md
|
|
20
|
+
scripts/open-mr.sh --body-file /tmp/mr-desc.md # refresh-only (MR must already exist)
|
|
21
|
+
|
|
22
|
+
Options:
|
|
23
|
+
--title <text> MR title (required when creating; updates title if MR exists)
|
|
24
|
+
--base <branch> Target branch (default: bytedcli's repo default, usually `release`)
|
|
25
|
+
-R, --repo <path> Codebase repository path, e.g. epscp/coding_engine
|
|
26
|
+
--body-file <path> Model-written MR description following the project template
|
|
27
|
+
--body-stdin Read the model-written MR description from stdin
|
|
28
|
+
--actor <mode> MR API mode: human|agent.
|
|
29
|
+
human: bytedcli (default).
|
|
30
|
+
agent: codebase CLI with CODEBASE_CI_APP_* or CODEBASE_CLI_APP_*.
|
|
31
|
+
--force Overwrite a hand-written MR description
|
|
32
|
+
--draft Create as draft
|
|
33
|
+
--dry-run Skip create/update; print what would happen
|
|
34
|
+
-h, --help Show this help
|
|
35
|
+
|
|
36
|
+
Behavior:
|
|
37
|
+
1. Refuse to run from a protected branch (release / main / master) or
|
|
38
|
+
detached HEAD.
|
|
39
|
+
2. Push the current branch with `-u origin` when no upstream is set.
|
|
40
|
+
3. Detect an existing MR for this branch via `bytedcli codebase mr get`.
|
|
41
|
+
If one exists, skip create and proceed to step 4 (refresh-only mode).
|
|
42
|
+
4. Otherwise create the MR with `--title` only (no inline body) so the
|
|
43
|
+
project template is preserved.
|
|
44
|
+
5. If a body is provided, apply it. If no body is provided, leave the
|
|
45
|
+
default/current MR description unchanged for manual editing.
|
|
46
|
+
|
|
47
|
+
Per AGENTS.md §"MR description": this is the **preferred** entry point for
|
|
48
|
+
opening a new MR. Agents should pass a model-written body with --body-file or
|
|
49
|
+
--body-stdin. Humans may omit the body so Codebase loads the default template
|
|
50
|
+
for manual editing. Direct `bytedcli codebase mr create --body "<heredoc>"` is
|
|
51
|
+
forbidden because it bypasses the template.
|
|
52
|
+
Agents should draft the body from the actual branch diff and validation
|
|
53
|
+
evidence.
|
|
54
|
+
|
|
55
|
+
Requires: python3, git, and bytedcli. Agent mode also requires codebase for MR
|
|
56
|
+
create/list/edit; issue status updates still use bytedcli codebase.
|
|
57
|
+
Agent-created MRs also best-effort rerun failed pipeline checks through the
|
|
58
|
+
current bytedcli human/PAT identity when scripts/rerun-mr-checks-as-human.sh is
|
|
59
|
+
available. Set CODING_MR_AUTO_RERUN_CHECKS=0 to skip that step.
|
|
60
|
+
USAGE
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
TITLE=""
|
|
64
|
+
BASE=""
|
|
65
|
+
REPO=""
|
|
66
|
+
DRAFT_FLAG=""
|
|
67
|
+
DRY_RUN=0
|
|
68
|
+
BODY_FILE=""
|
|
69
|
+
BODY_STDIN=0
|
|
70
|
+
FORCE=0
|
|
71
|
+
ACTOR="${CODING_ISSUE_AUTODEV_ACTOR:-human}"
|
|
72
|
+
|
|
73
|
+
# shellcheck source=scripts/lib/cli-args.sh
|
|
74
|
+
. scripts/lib/cli-args.sh
|
|
75
|
+
# shellcheck source=scripts/lib/codebase-json.sh
|
|
76
|
+
. scripts/lib/codebase-json.sh
|
|
77
|
+
# shellcheck source=scripts/lib/codebase-actor.sh
|
|
78
|
+
. scripts/lib/codebase-actor.sh
|
|
79
|
+
|
|
80
|
+
while [ $# -gt 0 ]; do
|
|
81
|
+
case "$1" in
|
|
82
|
+
--title) TITLE=$(require_value --title "${2:-}"); shift 2 ;;
|
|
83
|
+
--title=*) TITLE="${1#--title=}"; shift ;;
|
|
84
|
+
--base) BASE=$(require_value --base "${2:-}"); shift 2 ;;
|
|
85
|
+
--base=*) BASE="${1#--base=}"; shift ;;
|
|
86
|
+
-R|--repo) REPO=$(require_value --repo "${2:-}"); shift 2 ;;
|
|
87
|
+
--repo=*) REPO="${1#--repo=}"; shift ;;
|
|
88
|
+
--body-file) BODY_FILE=$(require_value --body-file "${2:-}"); shift 2 ;;
|
|
89
|
+
--body-file=*) BODY_FILE="${1#--body-file=}"; shift ;;
|
|
90
|
+
--body-stdin) BODY_STDIN=1; shift ;;
|
|
91
|
+
--actor) ACTOR=$(require_value --actor "${2:-}"); shift 2 ;;
|
|
92
|
+
--actor=*) ACTOR="${1#--actor=}"; shift ;;
|
|
93
|
+
--force) FORCE=1; shift ;;
|
|
94
|
+
--draft) DRAFT_FLAG="--draft"; shift ;;
|
|
95
|
+
--dry-run) DRY_RUN=1; shift ;;
|
|
96
|
+
-h|--help) usage; exit 0 ;;
|
|
97
|
+
*) echo "unknown arg: $1" >&2; usage >&2; exit 2 ;;
|
|
98
|
+
esac
|
|
99
|
+
done
|
|
100
|
+
|
|
101
|
+
if [ "$BODY_STDIN" -eq 1 ] && [ -n "$BODY_FILE" ]; then
|
|
102
|
+
echo "error: use only one of --body-file or --body-stdin" >&2
|
|
103
|
+
exit 2
|
|
104
|
+
fi
|
|
105
|
+
if [ -n "$BODY_FILE" ] && [ ! -f "$BODY_FILE" ]; then
|
|
106
|
+
echo "error: --body-file does not exist: $BODY_FILE" >&2
|
|
107
|
+
exit 2
|
|
108
|
+
fi
|
|
109
|
+
BODY_PROVIDED=0
|
|
110
|
+
if [ "$BODY_STDIN" -eq 1 ] || [ -n "$BODY_FILE" ]; then
|
|
111
|
+
BODY_PROVIDED=1
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
codebase_json_require
|
|
115
|
+
for cmd in git grep awk; do
|
|
116
|
+
command -v "$cmd" >/dev/null || { echo "missing dependency: $cmd" >&2; exit 1; }
|
|
117
|
+
done
|
|
118
|
+
codebase_actor_require_cli "$ACTOR"
|
|
119
|
+
if [ "$ACTOR" = "agent" ]; then
|
|
120
|
+
command -v codebase >/dev/null || { echo "missing dependency: codebase" >&2; exit 1; }
|
|
121
|
+
fi
|
|
122
|
+
codebase_actor_set_repo_args "$REPO"
|
|
123
|
+
|
|
124
|
+
codebase_mr_for_branch() {
|
|
125
|
+
codebase_actor_configure_app_env
|
|
126
|
+
local raw
|
|
127
|
+
if ! raw=$(codebase mr list ${CODEBASE_REPO_ARG[@]+"${CODEBASE_REPO_ARG[@]}"} --source-branch "$branch" --target-branch "$target_branch" --status open --verbose); then
|
|
128
|
+
printf '{"status":"error","error":"codebase mr list failed"}\n'
|
|
129
|
+
return 0
|
|
130
|
+
fi
|
|
131
|
+
if ! codebase_json_mr_normalize_list <<<"$raw"; then
|
|
132
|
+
printf '{"status":"error","error":"codebase mr list returned invalid JSON"}\n'
|
|
133
|
+
return 0
|
|
134
|
+
fi
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
mr_number_from_envelope() {
|
|
138
|
+
codebase_json_mr_field number <<<"$1"
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
mr_state_from_envelope() {
|
|
142
|
+
codebase_json_mr_field state <<<"$1"
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
mr_description_from_envelope() {
|
|
146
|
+
codebase_json_mr_field description <<<"$1"
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
mr_read_current_branch() {
|
|
150
|
+
# MR list/get/create/edit is the only Codebase operation family whose CLI
|
|
151
|
+
# transport differs by actor. Issue operations stay on bytedcli codebase.
|
|
152
|
+
case "$ACTOR" in
|
|
153
|
+
human) bytedcli --json codebase mr get ${BYTEDCLI_REPO_ARG[@]+"${BYTEDCLI_REPO_ARG[@]}"} ;;
|
|
154
|
+
agent) codebase_mr_for_branch ;;
|
|
155
|
+
esac
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
mr_create_current_branch() {
|
|
159
|
+
case "$ACTOR" in
|
|
160
|
+
human)
|
|
161
|
+
# shellcheck disable=SC2086
|
|
162
|
+
bytedcli codebase mr create \
|
|
163
|
+
${BYTEDCLI_REPO_ARG[@]+"${BYTEDCLI_REPO_ARG[@]}"} \
|
|
164
|
+
${BASE:+--base "$BASE"} \
|
|
165
|
+
--head "$branch" \
|
|
166
|
+
--title "$TITLE" \
|
|
167
|
+
$DRAFT_FLAG
|
|
168
|
+
;;
|
|
169
|
+
agent)
|
|
170
|
+
codebase_actor_configure_app_env
|
|
171
|
+
create_args=(mr create)
|
|
172
|
+
[ -n "$REPO" ] && create_args+=(-R "$REPO")
|
|
173
|
+
create_args+=(--source "$branch")
|
|
174
|
+
create_args+=(--target "$target_branch")
|
|
175
|
+
create_args+=(--title "$TITLE")
|
|
176
|
+
[ -n "$DRAFT_FLAG" ] && create_args+=(--draft)
|
|
177
|
+
codebase "${create_args[@]}"
|
|
178
|
+
;;
|
|
179
|
+
esac
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
mr_update_current() {
|
|
183
|
+
local number="$1"
|
|
184
|
+
shift
|
|
185
|
+
case "$ACTOR" in
|
|
186
|
+
human) bytedcli codebase mr update --number "$number" "$@" ${BYTEDCLI_REPO_ARG[@]+"${BYTEDCLI_REPO_ARG[@]}"} ;;
|
|
187
|
+
agent)
|
|
188
|
+
codebase_actor_configure_app_env
|
|
189
|
+
update_args=(mr edit -N "$number")
|
|
190
|
+
[ -n "$REPO" ] && update_args+=(-R "$REPO")
|
|
191
|
+
codebase "${update_args[@]}" "$@"
|
|
192
|
+
;;
|
|
193
|
+
esac
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
issue_status() {
|
|
197
|
+
local n="$1"
|
|
198
|
+
bytedcli --json codebase issue get "$n" ${BYTEDCLI_REPO_ARG[@]+"${BYTEDCLI_REPO_ARG[@]}"} 2>/dev/null | codebase_json_issue_status 2>/dev/null || true
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
issue_mark_in_progress() {
|
|
202
|
+
local n="$1"
|
|
203
|
+
bytedcli --json codebase issue update "$n" ${BYTEDCLI_REPO_ARG[@]+"${BYTEDCLI_REPO_ARG[@]}"} --status in_progress
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
branch=$(git rev-parse --abbrev-ref HEAD)
|
|
207
|
+
case "$branch" in
|
|
208
|
+
HEAD)
|
|
209
|
+
echo "error: detached HEAD; check out a feature branch first" >&2
|
|
210
|
+
exit 2
|
|
211
|
+
;;
|
|
212
|
+
release|main|master)
|
|
213
|
+
echo "error: refusing to open MR from protected branch '$branch'" >&2
|
|
214
|
+
echo " check out a feature branch and try again" >&2
|
|
215
|
+
exit 2
|
|
216
|
+
;;
|
|
217
|
+
esac
|
|
218
|
+
|
|
219
|
+
if [ "$BODY_PROVIDED" -eq 1 ]; then
|
|
220
|
+
if [ "$BODY_STDIN" -eq 1 ]; then
|
|
221
|
+
body=$(cat)
|
|
222
|
+
else
|
|
223
|
+
body=$(cat "$BODY_FILE")
|
|
224
|
+
fi
|
|
225
|
+
|
|
226
|
+
for section in "## 变更说明" "## Docs as Code" "## 测试" "## Reviewer 关注点"; do
|
|
227
|
+
if ! grep -qF "$section" <<<"$body"; then
|
|
228
|
+
echo "error: MR body is missing required section: $section" >&2
|
|
229
|
+
exit 2
|
|
230
|
+
fi
|
|
231
|
+
done
|
|
232
|
+
for placeholder in \
|
|
233
|
+
'<!-- 用简洁 bullet 分点说明这个 MR 做了什么,以及为什么;不要写成一整段长段落 -->'; do
|
|
234
|
+
if grep -qF "$placeholder" <<<"$body"; then
|
|
235
|
+
echo "error: MR body still contains the default template placeholder" >&2
|
|
236
|
+
exit 2
|
|
237
|
+
fi
|
|
238
|
+
done
|
|
239
|
+
_desc_check_tmp=$(mktemp)
|
|
240
|
+
printf '%s\n' "$body" >"$_desc_check_tmp"
|
|
241
|
+
if ! bash scripts/check-mr-desc.sh --description-file "$_desc_check_tmp"; then
|
|
242
|
+
rm -f "$_desc_check_tmp"
|
|
243
|
+
exit 2
|
|
244
|
+
fi
|
|
245
|
+
rm -f "$_desc_check_tmp"
|
|
246
|
+
fi
|
|
247
|
+
target_branch="${BASE:-release}"
|
|
248
|
+
|
|
249
|
+
closing_issue_numbers() {
|
|
250
|
+
local base range_base bodies kw
|
|
251
|
+
base="origin/${BASE:-release}"
|
|
252
|
+
range_base=$(git merge-base "$base" HEAD 2>/dev/null || true)
|
|
253
|
+
[ -n "$range_base" ] || return 0
|
|
254
|
+
bodies=$(git log --format=%B "$range_base"..HEAD 2>/dev/null || true)
|
|
255
|
+
[ -n "$bodies" ] || return 0
|
|
256
|
+
kw='(close[sd]?|fix(e[sd])?|resolve[sd]?)'
|
|
257
|
+
printf '%s\n' "$bodies" \
|
|
258
|
+
| grep -oiE "${kw}:?[[:space:]]+#[0-9]+([[:space:]]*(,|and)[[:space:]]*#[0-9]+)*" \
|
|
259
|
+
| grep -oE '#[0-9]+' | grep -oE '[0-9]+' | sort -u || true
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
has_closing_issue_ref_in_body() {
|
|
263
|
+
local n="$1"
|
|
264
|
+
local kw='(close[sd]?|fix(e[sd])?|resolve[sd]?)'
|
|
265
|
+
printf '%s\n' "$body" \
|
|
266
|
+
| grep -oiE "${kw}:?[[:space:]]+#[0-9]+([[:space:]]*(,|and)[[:space:]]*#[0-9]+)*" \
|
|
267
|
+
| grep -oE '#[0-9]+' | grep -oE '[0-9]+' | grep -qx "$n"
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
closing_refs=$(closing_issue_numbers)
|
|
271
|
+
if [ "$BODY_PROVIDED" -eq 1 ] && [ -n "$closing_refs" ]; then
|
|
272
|
+
missing_refs=()
|
|
273
|
+
for n in $closing_refs; do
|
|
274
|
+
if ! has_closing_issue_ref_in_body "$n"; then
|
|
275
|
+
missing_refs+=("#$n")
|
|
276
|
+
fi
|
|
277
|
+
done
|
|
278
|
+
if [ "${#missing_refs[@]}" -gt 0 ]; then
|
|
279
|
+
echo "error: MR body is missing closing issue reference(s) from commit trailer(s): ${missing_refs[*]}" >&2
|
|
280
|
+
echo "hint: keep the corresponding Closes/Fixes/Resolves reference in the model-written MR description" >&2
|
|
281
|
+
exit 2
|
|
282
|
+
fi
|
|
283
|
+
fi
|
|
284
|
+
|
|
285
|
+
SENTINEL='<!-- generated by scripts/open-mr.sh; remove this marker to keep manual edits across re-runs -->'
|
|
286
|
+
LEGACY_SENTINEL='<!-- generated by scripts/fill-mr-desc.sh; remove this marker to keep manual edits across re-runs -->'
|
|
287
|
+
description=""
|
|
288
|
+
if [ "$BODY_PROVIDED" -eq 1 ]; then
|
|
289
|
+
description=$(cat <<__OPEN_MR_DESC_BODY__
|
|
290
|
+
${SENTINEL}
|
|
291
|
+
|
|
292
|
+
${body}
|
|
293
|
+
__OPEN_MR_DESC_BODY__
|
|
294
|
+
)
|
|
295
|
+
fi
|
|
296
|
+
|
|
297
|
+
# Push -u to origin when upstream is unset OR points to a different branch on
|
|
298
|
+
# origin (a common artifact of `git checkout -b feat/foo origin/release`, which
|
|
299
|
+
# makes @{u} resolve to `origin/release` even though `origin/feat/foo` doesn't
|
|
300
|
+
# exist yet). We want `origin/$branch` as upstream so `bytedcli mr create
|
|
301
|
+
# --head $branch` resolves a real remote ref. If upstream points to a remote
|
|
302
|
+
# that isn't `origin` (e.g. fork workflow with `upstream/<branch>`), refuse:
|
|
303
|
+
# silently retargeting someone else's tracking remote is a footgun -- let the
|
|
304
|
+
# user decide.
|
|
305
|
+
upstream=$(git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null || true)
|
|
306
|
+
upstream_remote=${upstream%%/*}
|
|
307
|
+
if [ -n "$upstream" ] && [ "$upstream_remote" != "origin" ]; then
|
|
308
|
+
echo "error: branch '$branch' tracks '$upstream' (remote: $upstream_remote), not origin" >&2
|
|
309
|
+
echo " refusing to auto-retarget; push to origin manually or re-set upstream:" >&2
|
|
310
|
+
echo " git push -u origin $branch" >&2
|
|
311
|
+
exit 2
|
|
312
|
+
fi
|
|
313
|
+
if [ "$upstream" != "origin/$branch" ]; then
|
|
314
|
+
if [ "$DRY_RUN" -eq 1 ]; then
|
|
315
|
+
echo "[dry-run] would run: git push -u origin $branch (upstream is currently '${upstream:-<unset>}')"
|
|
316
|
+
else
|
|
317
|
+
echo "==> upstream is '${upstream:-<unset>}', not 'origin/$branch'; running: git push -u origin $branch"
|
|
318
|
+
git push -u origin "$branch"
|
|
319
|
+
fi
|
|
320
|
+
fi
|
|
321
|
+
|
|
322
|
+
# Capture CLI stderr separately so noisy runtime warnings don't drown the real
|
|
323
|
+
# auth / not-found message on failures.
|
|
324
|
+
_stderr_tmp=$(mktemp)
|
|
325
|
+
trap 'rm -f "$_stderr_tmp"' EXIT INT TERM
|
|
326
|
+
|
|
327
|
+
# "MR not found for this branch" is a normal pre-create state, not a failure.
|
|
328
|
+
# Any other error mode (auth / network / permission) -- replay the captured
|
|
329
|
+
# stderr (minus the Node warning) and exit.
|
|
330
|
+
mr_json=$(mr_read_current_branch 2>"$_stderr_tmp") || true
|
|
331
|
+
mr_status=$(codebase_json_mr_field status <<<"${mr_json:-}" 2>/dev/null || true)
|
|
332
|
+
existing_number=""
|
|
333
|
+
created_by_this_run=0
|
|
334
|
+
if [ "$mr_status" = "success" ]; then
|
|
335
|
+
existing_number=$(mr_number_from_envelope "$mr_json")
|
|
336
|
+
# merged/closed MRs share branch name but are not reusable -- a fresh MR is
|
|
337
|
+
# needed for any new work pushed to the same branch (e.g. follow-up commits
|
|
338
|
+
# after a squash-merge cleared the remote branch and we re-created it).
|
|
339
|
+
mr_state=$(mr_state_from_envelope "$mr_json")
|
|
340
|
+
case "$mr_state" in
|
|
341
|
+
merged|closed)
|
|
342
|
+
echo "==> MR !${existing_number} for branch '$branch' is ${mr_state}; treating branch as fresh"
|
|
343
|
+
existing_number=""
|
|
344
|
+
;;
|
|
345
|
+
esac
|
|
346
|
+
elif [ -n "$mr_json" ]; then
|
|
347
|
+
# bytedcli returned a JSON envelope but status != success. Distinguish
|
|
348
|
+
# "no MR exists for this branch yet" (acceptable) from real auth/network
|
|
349
|
+
# failures by inspecting the error message.
|
|
350
|
+
err_msg=$(codebase_json_mr_field error <<<"$mr_json" 2>/dev/null || true)
|
|
351
|
+
case "$err_msg" in
|
|
352
|
+
*"no MR"*|*"not found"*|*"NotFound"*|*"NoSuchMergeRequest"*) : ;; # expected pre-create
|
|
353
|
+
*)
|
|
354
|
+
echo "Codebase MR lookup failed:" >&2
|
|
355
|
+
grep -v 'ExperimentalWarning' "$_stderr_tmp" >&2 || true
|
|
356
|
+
[ -n "$err_msg" ] && echo " $err_msg" >&2
|
|
357
|
+
echo "check Codebase authentication and pass -R/--repo when origin cannot be inferred" >&2
|
|
358
|
+
exit 1
|
|
359
|
+
;;
|
|
360
|
+
esac
|
|
361
|
+
fi
|
|
362
|
+
|
|
363
|
+
if [ -n "$existing_number" ]; then
|
|
364
|
+
echo "==> MR !$existing_number already exists for branch '$branch'; skipping create"
|
|
365
|
+
else
|
|
366
|
+
if [ -z "$TITLE" ]; then
|
|
367
|
+
echo "error: no MR exists for branch '$branch' yet -- --title is required to create one" >&2
|
|
368
|
+
usage >&2
|
|
369
|
+
exit 2
|
|
370
|
+
fi
|
|
371
|
+
if [ "$DRY_RUN" -eq 1 ]; then
|
|
372
|
+
case "$ACTOR" in
|
|
373
|
+
human) echo "[dry-run] would run: bytedcli codebase mr create${REPO:+ -R $REPO}${BASE:+ --base $BASE} --head $branch --title \"$TITLE\" ${DRAFT_FLAG}" ;;
|
|
374
|
+
agent) echo "[dry-run] would run: codebase mr create${REPO:+ -R $REPO} --source $branch --target $target_branch --title \"$TITLE\" ${DRAFT_FLAG}" ;;
|
|
375
|
+
esac
|
|
376
|
+
else
|
|
377
|
+
echo "==> creating MR via $ACTOR Codebase CLI (title only)"
|
|
378
|
+
mr_create_current_branch
|
|
379
|
+
created_by_this_run=1
|
|
380
|
+
fi
|
|
381
|
+
fi
|
|
382
|
+
|
|
383
|
+
if [ "$created_by_this_run" -eq 1 ] && [ "$BODY_PROVIDED" -eq 0 ]; then
|
|
384
|
+
echo "==> no MR body provided; description left as the default template"
|
|
385
|
+
if [ -n "$closing_refs" ]; then
|
|
386
|
+
echo "warning: closing issue refs found in commits; add them to the MR description manually to keep Codebase links" >&2
|
|
387
|
+
fi
|
|
388
|
+
exit 0
|
|
389
|
+
fi
|
|
390
|
+
|
|
391
|
+
# Advance issues this branch references. Codebase auto-links `#N` mentions from
|
|
392
|
+
# the MR description, so the model-written body should keep any intended issue
|
|
393
|
+
# references from the drafting context. There is no relate API in bytedcli; what
|
|
394
|
+
# we can do here is move a `Closes #N` / `Fixes #N` issue to in_progress now
|
|
395
|
+
# that an MR is open for it. `Refs #N` is only surfaced.
|
|
396
|
+
progress_referenced_issues() {
|
|
397
|
+
# Scan commit bodies unique to this branch vs the target base.
|
|
398
|
+
local base range_base bodies closes refs n
|
|
399
|
+
base="origin/${BASE:-release}"
|
|
400
|
+
range_base=$(git merge-base "$base" HEAD 2>/dev/null || true)
|
|
401
|
+
[ -n "$range_base" ] || return 0
|
|
402
|
+
bodies=$(git log --format=%B "$range_base"..HEAD 2>/dev/null || true)
|
|
403
|
+
[ -n "$bodies" ] || return 0
|
|
404
|
+
|
|
405
|
+
# Closing keywords -> in_progress; bare Refs -> surface only. Case-insensitive.
|
|
406
|
+
# Capture comma/and-separated lists too, e.g. `Closes #12, #13 and #14`.
|
|
407
|
+
# Tolerate an optional colon and any whitespace: `Closes #1`, `Closes: #1`,
|
|
408
|
+
# `Fixes: #1`, plus comma/and-separated lists.
|
|
409
|
+
local kw='(close[sd]?|fix(e[sd])?|resolve[sd]?)'
|
|
410
|
+
closes=$(printf '%s\n' "$bodies" \
|
|
411
|
+
| grep -oiE "${kw}:?[[:space:]]+#[0-9]+([[:space:]]*(,|and)[[:space:]]*#[0-9]+)*" \
|
|
412
|
+
| grep -oE '#[0-9]+' | grep -oE '[0-9]+' | sort -u || true)
|
|
413
|
+
refs=$(printf '%s\n' "$bodies" \
|
|
414
|
+
| grep -oiE "refs?:?[[:space:]]+#[0-9]+([[:space:]]*(,|and)[[:space:]]*#[0-9]+)*" \
|
|
415
|
+
| grep -oE '#[0-9]+' | grep -oE '[0-9]+' | sort -u || true)
|
|
416
|
+
|
|
417
|
+
[ -n "$refs" ] && echo "==> referenced issues (will link via MR description): $(echo "$refs" | paste -sd' ' -)"
|
|
418
|
+
[ -n "$closes" ] || return 0
|
|
419
|
+
|
|
420
|
+
# Own temp file (created only on the path that uses it, so early returns
|
|
421
|
+
# above don't leak it); cleaned at the end of the function.
|
|
422
|
+
local _err; _err=$(mktemp)
|
|
423
|
+
for n in $closes; do
|
|
424
|
+
# Only advance forward from an un-started state -- never regress a `done` /
|
|
425
|
+
# `canceled` / already-`in_progress` issue when a commit references an old
|
|
426
|
+
# number or `make open-mr` is re-run.
|
|
427
|
+
local cur
|
|
428
|
+
cur=$(issue_status "$n")
|
|
429
|
+
case "$cur" in
|
|
430
|
+
backlog|todo) : ;;
|
|
431
|
+
"") echo "warning: could not read issue #$n status; leaving it unchanged" >&2; continue ;;
|
|
432
|
+
*) echo "==> issue #$n is '$cur'; not auto-advancing"; continue ;;
|
|
433
|
+
esac
|
|
434
|
+
if [ "$DRY_RUN" -eq 1 ]; then
|
|
435
|
+
echo "[dry-run] would run: bytedcli codebase issue update $n${REPO:+ -R $REPO} --status in_progress (current: $cur)"
|
|
436
|
+
continue
|
|
437
|
+
fi
|
|
438
|
+
echo "==> moving referenced issue #$n ($cur -> in_progress)"
|
|
439
|
+
if ! issue_mark_in_progress "$n" >/dev/null 2>"$_err"; then
|
|
440
|
+
# Non-fatal: opening the MR already succeeded; a bad/closed issue ref
|
|
441
|
+
# must not fail the whole flow.
|
|
442
|
+
echo "warning: could not update issue #$n:" >&2
|
|
443
|
+
grep -v 'ExperimentalWarning' "$_err" >&2 || true
|
|
444
|
+
fi
|
|
445
|
+
done
|
|
446
|
+
rm -f "$_err"
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if [ -z "$existing_number" ] && [ "$DRY_RUN" -ne 1 ]; then
|
|
450
|
+
mr_json=$(mr_read_current_branch 2>"$_stderr_tmp") || true
|
|
451
|
+
mr_status=$(codebase_json_mr_field status <<<"${mr_json:-}" 2>/dev/null || true)
|
|
452
|
+
if [ "$mr_status" != "success" ]; then
|
|
453
|
+
echo "error: MR was created but could not be read back for description update" >&2
|
|
454
|
+
grep -v 'ExperimentalWarning' "$_stderr_tmp" >&2 || true
|
|
455
|
+
exit 1
|
|
456
|
+
fi
|
|
457
|
+
existing_number=$(mr_number_from_envelope "$mr_json")
|
|
458
|
+
fi
|
|
459
|
+
|
|
460
|
+
mr_number="$existing_number"
|
|
461
|
+
if [ -z "$mr_number" ] && [ "$DRY_RUN" -ne 1 ]; then
|
|
462
|
+
echo "error: could not determine MR number for description update" >&2
|
|
463
|
+
exit 1
|
|
464
|
+
fi
|
|
465
|
+
|
|
466
|
+
if [ "$BODY_PROVIDED" -eq 0 ]; then
|
|
467
|
+
if [ "$DRY_RUN" -eq 1 ]; then
|
|
468
|
+
if [ -n "$mr_number" ] && [ -n "$TITLE" ]; then
|
|
469
|
+
echo "[dry-run] would update MR !${mr_number} title only"
|
|
470
|
+
echo "[dry-run] new title: $TITLE"
|
|
471
|
+
elif [ -n "$mr_number" ]; then
|
|
472
|
+
echo "[dry-run] MR !${mr_number} already exists; no body provided, description would remain unchanged"
|
|
473
|
+
else
|
|
474
|
+
echo "[dry-run] would leave the newly created MR description as the default template"
|
|
475
|
+
fi
|
|
476
|
+
exit 0
|
|
477
|
+
fi
|
|
478
|
+
if [ "$created_by_this_run" -ne 1 ] && [ -n "$mr_number" ] && [ -n "$TITLE" ] && [ -n "$existing_number" ]; then
|
|
479
|
+
echo "==> updating MR !${mr_number} title only; description left unchanged for manual editing"
|
|
480
|
+
mr_update_current "$mr_number" --title "$TITLE"
|
|
481
|
+
else
|
|
482
|
+
echo "==> no MR body provided; description left as the default/current template for manual editing"
|
|
483
|
+
fi
|
|
484
|
+
if [ -n "$closing_refs" ]; then
|
|
485
|
+
echo "warning: closing issue refs found in commits; add them to the MR description manually to keep Codebase links" >&2
|
|
486
|
+
fi
|
|
487
|
+
exit 0
|
|
488
|
+
fi
|
|
489
|
+
|
|
490
|
+
if [ -n "$mr_number" ]; then
|
|
491
|
+
if [ -n "${mr_json:-}" ]; then
|
|
492
|
+
existing_desc=$(mr_description_from_envelope "$mr_json")
|
|
493
|
+
else
|
|
494
|
+
existing_desc=""
|
|
495
|
+
fi
|
|
496
|
+
if [ "$created_by_this_run" -ne 1 ] \
|
|
497
|
+
&& [ -n "$existing_desc" ] && [ "$FORCE" -ne 1 ] \
|
|
498
|
+
&& ! grep -qF "$SENTINEL" <<<"$existing_desc" \
|
|
499
|
+
&& ! grep -qF "$LEGACY_SENTINEL" <<<"$existing_desc"; then
|
|
500
|
+
echo "MR !${mr_number} already has a hand-written description (no script sentinel found)." >&2
|
|
501
|
+
echo "Pass --force to overwrite, or remove the description first." >&2
|
|
502
|
+
exit 1
|
|
503
|
+
fi
|
|
504
|
+
fi
|
|
505
|
+
|
|
506
|
+
update_fields=()
|
|
507
|
+
[ -n "$TITLE" ] && update_fields+=(--title "$TITLE")
|
|
508
|
+
update_fields+=(--body "$description")
|
|
509
|
+
|
|
510
|
+
if [ "$DRY_RUN" -eq 1 ]; then
|
|
511
|
+
if [ -n "$mr_number" ]; then
|
|
512
|
+
echo "[dry-run] would update MR !${mr_number} description${TITLE:+ and title}"
|
|
513
|
+
[ -n "$TITLE" ] && echo "[dry-run] new title: $TITLE"
|
|
514
|
+
else
|
|
515
|
+
echo "[dry-run] would update the newly created MR body after create"
|
|
516
|
+
fi
|
|
517
|
+
echo "---"
|
|
518
|
+
printf '%s\n' "$description"
|
|
519
|
+
progress_referenced_issues
|
|
520
|
+
exit 0
|
|
521
|
+
fi
|
|
522
|
+
|
|
523
|
+
echo "==> updating MR !${mr_number} description${TITLE:+ and title}"
|
|
524
|
+
mr_update_current "$mr_number" "${update_fields[@]}"
|
|
525
|
+
progress_referenced_issues
|
|
526
|
+
|
|
527
|
+
if [ "$ACTOR" = "agent" ] \
|
|
528
|
+
&& [ "${CODING_MR_AUTO_RERUN_CHECKS:-1}" != "0" ] \
|
|
529
|
+
&& [ -x scripts/rerun-mr-checks-as-human.sh ]; then
|
|
530
|
+
echo "==> best-effort rerun of failed MR checks via bytedcli human/PAT identity"
|
|
531
|
+
if [ -n "$REPO" ]; then
|
|
532
|
+
scripts/rerun-mr-checks-as-human.sh --mr "$mr_number" -R "$REPO" || true
|
|
533
|
+
else
|
|
534
|
+
scripts/rerun-mr-checks-as-human.sh --mr "$mr_number" || true
|
|
535
|
+
fi
|
|
536
|
+
fi
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Regenerate the auto-managed skill registry table in .agents/skills/REGISTRY.md
|
|
3
|
+
# from each .agents/skills/coding-*/SKILL.md frontmatter (`name` + `description`).
|
|
4
|
+
#
|
|
5
|
+
# REGISTRY.md holds the verbose per-skill descriptions + lifecycle cadence; the
|
|
6
|
+
# path→skill routing table that every agent reads on each edit stays inline in
|
|
7
|
+
# AGENTS.md §Skill Router (Codex / Antigravity / Trae read it natively, the
|
|
8
|
+
# Claude Code hook + Cursor mirror duplicate it). Keeping the descriptions in
|
|
9
|
+
# REGISTRY.md keeps the always-loaded AGENTS.md lean while still syncing one
|
|
10
|
+
# source without per-tool file duplication.
|
|
11
|
+
#
|
|
12
|
+
# Modes:
|
|
13
|
+
# (no flag) : rewrite the target in place between markers
|
|
14
|
+
# --check : dry-run; exit 1 with diff if the target is out of sync
|
|
15
|
+
#
|
|
16
|
+
# The replaced block sits between these exact-line markers in the target:
|
|
17
|
+
# <!-- skills:start (auto-generated by make regen-agent-md) -->
|
|
18
|
+
# <!-- skills:end -->
|
|
19
|
+
#
|
|
20
|
+
# Anything outside the markers is preserved verbatim.
|
|
21
|
+
|
|
22
|
+
set -euo pipefail
|
|
23
|
+
|
|
24
|
+
cd "$(dirname "$0")/.."
|
|
25
|
+
|
|
26
|
+
# shellcheck source=scripts/lib/skill-frontmatter.sh
|
|
27
|
+
. scripts/lib/skill-frontmatter.sh
|
|
28
|
+
|
|
29
|
+
TARGET=".agents/skills/REGISTRY.md"
|
|
30
|
+
START_MARKER='<!-- skills:start (auto-generated by make regen-agent-md) -->'
|
|
31
|
+
END_MARKER='<!-- skills:end -->'
|
|
32
|
+
|
|
33
|
+
mode="write"
|
|
34
|
+
case "${1:-}" in
|
|
35
|
+
"" ) ;;
|
|
36
|
+
--check ) mode="check" ;;
|
|
37
|
+
-h|--help )
|
|
38
|
+
cat <<EOF
|
|
39
|
+
Usage: $0 [--check]
|
|
40
|
+
|
|
41
|
+
(no flag) rewrite $TARGET in place between skill registry markers
|
|
42
|
+
--check exit 1 with diff if $TARGET is out of sync (for CI / pre-commit)
|
|
43
|
+
EOF
|
|
44
|
+
exit 0
|
|
45
|
+
;;
|
|
46
|
+
* )
|
|
47
|
+
echo "error: unknown flag: $1" >&2
|
|
48
|
+
exit 2
|
|
49
|
+
;;
|
|
50
|
+
esac
|
|
51
|
+
|
|
52
|
+
if [[ ! -f "$TARGET" ]]; then
|
|
53
|
+
echo "error: $TARGET not found" >&2
|
|
54
|
+
exit 2
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
# AGENTS.md is the always-loaded routing layer for every agent CLI; keep it
|
|
58
|
+
# lean. Enforced in --check mode, which both the pre-commit `check-agent-md`
|
|
59
|
+
# hook and `make check-agent-md` invoke.
|
|
60
|
+
MAX_AGENT_MD_LINES=200
|
|
61
|
+
if [[ "$mode" == "check" && -f AGENTS.md ]]; then
|
|
62
|
+
agent_md_lines=$(wc -l < AGENTS.md | tr -d ' ')
|
|
63
|
+
if (( agent_md_lines > MAX_AGENT_MD_LINES )); then
|
|
64
|
+
echo "[FAIL] AGENTS.md is ${agent_md_lines} lines (max ${MAX_AGENT_MD_LINES}) — it is the always-loaded routing layer; move detail into a skill instead" >&2
|
|
65
|
+
exit 1
|
|
66
|
+
fi
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
# Marker presence is mandatory; do not silently insert markers into a file
|
|
70
|
+
# that someone has intentionally stripped.
|
|
71
|
+
if ! grep -qxF "$START_MARKER" "$TARGET"; then
|
|
72
|
+
echo "error: $TARGET missing start marker line:" >&2
|
|
73
|
+
echo " $START_MARKER" >&2
|
|
74
|
+
exit 2
|
|
75
|
+
fi
|
|
76
|
+
if ! grep -qxF "$END_MARKER" "$TARGET"; then
|
|
77
|
+
echo "error: $TARGET missing end marker line:" >&2
|
|
78
|
+
echo " $END_MARKER" >&2
|
|
79
|
+
exit 2
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
# ── Build the regenerated block from frontmatter ─────────────────────────
|
|
83
|
+
build_block() {
|
|
84
|
+
printf '%s\n\n' "$START_MARKER"
|
|
85
|
+
printf '| Skill | When to use |\n'
|
|
86
|
+
printf '|---|---|\n'
|
|
87
|
+
|
|
88
|
+
local skill_md name desc warned=0
|
|
89
|
+
for skill_md in "$SKILLS_ROOT"/coding-*/SKILL.md; do
|
|
90
|
+
[[ -f "$skill_md" ]] || continue
|
|
91
|
+
name=$(extract_field "$skill_md" name)
|
|
92
|
+
desc=$(extract_field "$skill_md" description)
|
|
93
|
+
if [[ -z "$name" ]]; then
|
|
94
|
+
echo "warn: $skill_md missing 'name' in frontmatter, skipping" >&2
|
|
95
|
+
warned=1
|
|
96
|
+
continue
|
|
97
|
+
fi
|
|
98
|
+
if [[ -z "$desc" ]]; then
|
|
99
|
+
echo "warn: $skill_md missing 'description' in frontmatter, skipping" >&2
|
|
100
|
+
warned=1
|
|
101
|
+
continue
|
|
102
|
+
fi
|
|
103
|
+
if [[ "$desc" == *'|'* ]]; then
|
|
104
|
+
# A literal pipe would break the markdown table. Don't try to
|
|
105
|
+
# auto-escape — flag it so the SKILL.md author fixes it.
|
|
106
|
+
echo "warn: $skill_md description contains '|'; this will break the table. Replace with '/' or HTML entity." >&2
|
|
107
|
+
warned=1
|
|
108
|
+
fi
|
|
109
|
+
printf '| `%s` | %s |\n' "$name" "$desc"
|
|
110
|
+
done
|
|
111
|
+
|
|
112
|
+
printf '%s\n' "$END_MARKER"
|
|
113
|
+
return $warned
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
# `build_block` returns 1 on soft warnings (missing frontmatter, pipe in
|
|
117
|
+
# description). The `|| build_status=$?` absorbs the non-zero return so
|
|
118
|
+
# `set -e` doesn't kill us before we splice + report — otherwise the
|
|
119
|
+
# warning path is dead code.
|
|
120
|
+
build_status=0
|
|
121
|
+
block=$(build_block) || build_status=$?
|
|
122
|
+
|
|
123
|
+
# ── Splice the new block into the target ────────────────────────────────
|
|
124
|
+
# Match marker lines exactly (full-line equality) to avoid accidental matches
|
|
125
|
+
# inside narrative paragraphs. BSD awk rejects newlines in -v values, so we
|
|
126
|
+
# stream the file in three pieces and inject `block` via printf in between.
|
|
127
|
+
tmp_new=$(mktemp -t regen-agent-md.XXXXXX)
|
|
128
|
+
trap 'rm -f "$tmp_new"' EXIT
|
|
129
|
+
|
|
130
|
+
{
|
|
131
|
+
awk -v marker="$START_MARKER" '$0 == marker { exit } { print }' "$TARGET"
|
|
132
|
+
printf '%s\n' "$block"
|
|
133
|
+
awk -v marker="$END_MARKER" 'found { print } $0 == marker { found = 1 }' "$TARGET"
|
|
134
|
+
} > "$tmp_new"
|
|
135
|
+
|
|
136
|
+
if cmp -s "$TARGET" "$tmp_new"; then
|
|
137
|
+
echo "[OK] $TARGET skill registry already up to date"
|
|
138
|
+
exit "$build_status"
|
|
139
|
+
fi
|
|
140
|
+
|
|
141
|
+
if [[ "$mode" == "check" ]]; then
|
|
142
|
+
echo "[FAIL] $TARGET skill registry is out of sync. Run: make regen-agent-md" >&2
|
|
143
|
+
diff -u "$TARGET" "$tmp_new" >&2 || true
|
|
144
|
+
exit 1
|
|
145
|
+
fi
|
|
146
|
+
|
|
147
|
+
cp "$tmp_new" "$TARGET"
|
|
148
|
+
echo "[OK] rewrote skill registry in $TARGET"
|
|
149
|
+
exit "$build_status"
|