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.
Files changed (57) hide show
  1. package/README.md +91 -0
  2. package/bin/coding-engine.js +16 -0
  3. package/lib/cli.js +86 -0
  4. package/lib/index.js +6 -0
  5. package/lib/setup.js +290 -0
  6. package/package.json +32 -0
  7. package/templates/.agents/skills/coding-architecture/SKILL.md +347 -0
  8. package/templates/.agents/skills/coding-code-review/SKILL.md +95 -0
  9. package/templates/.agents/skills/coding-cve-remediation/SKILL.md +163 -0
  10. package/templates/.agents/skills/coding-db-schema/SKILL.md +273 -0
  11. package/templates/.agents/skills/coding-deploy-config/SKILL.md +129 -0
  12. package/templates/.agents/skills/coding-docs-audit/SKILL.md +285 -0
  13. package/templates/.agents/skills/coding-docs-audit-autofix/SKILL.md +33 -0
  14. package/templates/.agents/skills/coding-http-api/SKILL.md +269 -0
  15. package/templates/.agents/skills/coding-issue-autodev/SKILL.md +172 -0
  16. package/templates/.agents/skills/coding-merge-request/SKILL.md +199 -0
  17. package/templates/.agents/skills/coding-observability/SKILL.md +193 -0
  18. package/templates/.agents/skills/coding-refactor-insight/SKILL.md +89 -0
  19. package/templates/.agents/skills/coding-release-merge/SKILL.md +224 -0
  20. package/templates/.agents/skills/coding-runtime-adapter/SKILL.md +115 -0
  21. package/templates/.agents/skills/coding-testing/SKILL.md +169 -0
  22. package/templates/AGENTS.md +179 -0
  23. package/templates/MR-Template-Default.md +33 -0
  24. package/templates/Makefile +370 -0
  25. package/templates/REGISTRY.md +53 -0
  26. package/templates/scripts/check-adr.sh +283 -0
  27. package/templates/scripts/check-comment-i18n.py +209 -0
  28. package/templates/scripts/check-comment-i18n.sh +38 -0
  29. package/templates/scripts/check-doc-refs.sh +40 -0
  30. package/templates/scripts/check-docs.sh +103 -0
  31. package/templates/scripts/check-go-names.sh +59 -0
  32. package/templates/scripts/check-iam-actions.py +126 -0
  33. package/templates/scripts/check-migration-sql-immutability.sh +232 -0
  34. package/templates/scripts/check-mr-desc.sh +165 -0
  35. package/templates/scripts/check-mr-size.sh +128 -0
  36. package/templates/scripts/check-mr-title.sh +123 -0
  37. package/templates/scripts/check-openapi.py +221 -0
  38. package/templates/scripts/check-rdb-structured-queries.sh +98 -0
  39. package/templates/scripts/check-repository-transactions.sh +100 -0
  40. package/templates/scripts/check-runbooks.sh +229 -0
  41. package/templates/scripts/check-skill-router-sync.py +331 -0
  42. package/templates/scripts/check-skills.sh +243 -0
  43. package/templates/scripts/docs-audit-to-issues.py +373 -0
  44. package/templates/scripts/docs-verification-reminder.sh +63 -0
  45. package/templates/scripts/find-ci-failures.sh +133 -0
  46. package/templates/scripts/find-stale-mrs.sh +72 -0
  47. package/templates/scripts/gen-adr-index.sh +122 -0
  48. package/templates/scripts/open-mr.sh +536 -0
  49. package/templates/scripts/regen-agent-md.sh +149 -0
  50. package/templates/scripts/run-mr-hygiene.sh +140 -0
  51. package/templates/scripts/runbook.sh +91 -0
  52. package/templates/scripts/self-maintenance-digest.py +125 -0
  53. package/templates/scripts/send-self-maintenance-lark-card.py +116 -0
  54. package/templates/scripts/skill-graph.sh +114 -0
  55. package/templates/scripts/skill-impact.sh +134 -0
  56. package/templates/scripts/skill-propose.sh +302 -0
  57. 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"