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,283 @@
1
+ #!/usr/bin/env bash
2
+ # Verify every ADR under docs/adr/ is in a terminal Status.
3
+ # See docs/adr/CONVENTIONS.md.
4
+ set -euo pipefail
5
+
6
+ cd "$(dirname "$0")/.."
7
+
8
+ # shellcheck source=scripts/lib/adr-meta.sh
9
+ . scripts/lib/adr-meta.sh
10
+ ADR_DIR="${ADR_DIR:-docs/adr}"
11
+
12
+ # Grandfathered violations — historical ADRs that predate the convention.
13
+ # Each entry MUST have a corresponding "现存待清理项" row in
14
+ # docs/adr/CONVENTIONS.md so cleanup pressure is visible.
15
+ GRANDFATHERED=()
16
+
17
+ is_grandfathered() {
18
+ local f=$1
19
+ for g in "${GRANDFATHERED[@]+"${GRANDFATHERED[@]}"}"; do
20
+ [ "$f" = "$g" ] && return 0
21
+ done
22
+ return 1
23
+ }
24
+
25
+ # Intentionally-unused ADR numbers. Each entry needs a matching note in
26
+ # docs/adr/CONVENTIONS.md (现存待清理项 / KNOWN_GAPS).
27
+ KNOWN_GAPS=(0011)
28
+ # Test override hook (space-separated). Empty string means "no gaps allowed".
29
+ if [ "${KNOWN_GAPS_OVERRIDE+set}" = set ]; then
30
+ # shellcheck disable=SC2206
31
+ KNOWN_GAPS=(${KNOWN_GAPS_OVERRIDE})
32
+ fi
33
+
34
+ is_known_gap() {
35
+ local n=$1 g
36
+ for g in "${KNOWN_GAPS[@]+"${KNOWN_GAPS[@]}"}"; do
37
+ [ "$n" = "$g" ] && return 0
38
+ done
39
+ return 1
40
+ }
41
+
42
+ # Topic taxonomy comes from $ADR_TOPICS, defined once in scripts/lib/adr-meta.sh
43
+ # (sourced above) so the linter and the index generator can't drift. The linter
44
+ # validates **Topic** against this list, never against AGENTS.md §Skill Router
45
+ # directly (the 7 path-skills are coarser). Topic→skill mapping is documented in
46
+ # docs/adr/CONVENTIONS.md.
47
+
48
+ VIOLATIONS=0
49
+ WARNINGS=0
50
+ # Back-citation enforcement mode: "warn" (Phase 2) or "hard" (Phase 3).
51
+ BACKCITE_MODE="${BACKCITE_MODE:-hard}"
52
+ GRANDFATHERED_HITS=0
53
+ for f in "$ADR_DIR"/[0-9]*.md; do
54
+ [ -f "$f" ] || continue
55
+ status=$(awk '/^\*\*Status\*\*:/ { sub(/^\*\*Status\*\*:[[:space:]]*/, ""); print; exit }' "$f")
56
+
57
+ case "$status" in
58
+ Accepted|Deprecated|Withdrawn)
59
+ ;;
60
+ "Superseded by "*)
61
+ target="${status#Superseded by }"
62
+ if printf '%s' "$target" | grep -qE '^[0-9]{4}$'; then
63
+ if ! ls "$ADR_DIR/${target}-"*.md >/dev/null 2>&1; then
64
+ echo " [VIOLATION] $f — Superseded by $target but no $ADR_DIR/${target}-*.md exists"
65
+ VIOLATIONS=$((VIOLATIONS + 1))
66
+ fi
67
+ else
68
+ echo " [VIOLATION] $f — Superseded by '$target' is not a 4-digit ADR number (use 'Superseded by NNNN'; if no successor ADR, use Deprecated)"
69
+ VIOLATIONS=$((VIOLATIONS + 1))
70
+ fi
71
+ ;;
72
+ Proposed)
73
+ if is_grandfathered "$f"; then
74
+ echo " [GRANDFATHERED] $f — Status=Proposed (cleanup tracked in CONVENTIONS.md)"
75
+ GRANDFATHERED_HITS=$((GRANDFATHERED_HITS + 1))
76
+ else
77
+ echo " [VIOLATION] $f — Status=Proposed not allowed on main; flip to Accepted before merging"
78
+ VIOLATIONS=$((VIOLATIONS + 1))
79
+ fi
80
+ ;;
81
+ "")
82
+ if is_grandfathered "$f"; then
83
+ echo " [GRANDFATHERED] $f — missing **Status** header (cleanup tracked in CONVENTIONS.md)"
84
+ GRANDFATHERED_HITS=$((GRANDFATHERED_HITS + 1))
85
+ else
86
+ echo " [VIOLATION] $f — missing '**Status**:' header"
87
+ VIOLATIONS=$((VIOLATIONS + 1))
88
+ fi
89
+ ;;
90
+ *)
91
+ echo " [VIOLATION] $f — unknown Status: '$status'"
92
+ VIOLATIONS=$((VIOLATIONS + 1))
93
+ ;;
94
+ esac
95
+ done
96
+
97
+ # Reference integrity: every ADR-NNNN / ADR NNNN token inside the ADR set must
98
+ # resolve to an existing $ADR_DIR/NNNN-*.md. (Repo-wide scanning is intentionally
99
+ # left to a future widening; ADR-internal cross-refs are the highest-value guard.)
100
+ check_adr_refs() {
101
+ local f tok num
102
+ while IFS= read -r f; do
103
+ [ -f "$f" ] || continue
104
+ while IFS= read -r tok; do
105
+ num="$(printf '%s' "$tok" | grep -oE '[0-9]{4}')"
106
+ [ -z "$num" ] && continue
107
+ if ! ls "$ADR_DIR/${num}-"*.md >/dev/null 2>&1; then
108
+ echo " [VIOLATION] $f — references ${tok} but no $ADR_DIR/${num}-*.md exists"
109
+ VIOLATIONS=$((VIOLATIONS + 1))
110
+ fi
111
+ done < <(grep -oE 'ADR[- ][0-9]{4}' "$f" | sort -u)
112
+ done < <(adr_files)
113
+ }
114
+ check_adr_refs
115
+
116
+ # Numbering: contiguous from the lowest present number to the highest; any
117
+ # missing number must be allowlisted in KNOWN_GAPS.
118
+ check_numbering() {
119
+ local nums max min n
120
+ nums="$(adr_files | while IFS= read -r f; do adr_num "$f"; done | sort -u)"
121
+ [ -z "$nums" ] && return 0
122
+ min="$(printf '%s\n' "$nums" | head -1)"
123
+ max="$(printf '%s\n' "$nums" | tail -1)"
124
+ for n in $(seq "$((10#$min))" "$((10#$max))"); do
125
+ local padded; padded="$(printf '%04d' "$n")"
126
+ if ! printf '%s\n' "$nums" | grep -qx "$padded"; then
127
+ if is_known_gap "$padded"; then
128
+ echo " [KNOWN_GAP] $padded — intentionally unused (tracked in CONVENTIONS.md)"
129
+ else
130
+ echo " [VIOLATION] missing ADR $padded — add the file or allowlist it in KNOWN_GAPS"
131
+ VIOLATIONS=$((VIOLATIONS + 1))
132
+ fi
133
+ fi
134
+ done
135
+ }
136
+ check_numbering
137
+
138
+ check_topics() {
139
+ local f t ok w
140
+ while IFS= read -r f; do
141
+ [ -f "$f" ] || continue
142
+ t="$(adr_field "$f" Topic)"
143
+ if [ -z "$t" ]; then
144
+ echo " [VIOLATION] $f — missing '**Topic**:' header"
145
+ VIOLATIONS=$((VIOLATIONS + 1)); continue
146
+ fi
147
+ ok=0
148
+ for w in $ADR_TOPICS; do [ "$t" = "$w" ] && ok=1 && break; done
149
+ if [ "$ok" = 0 ]; then
150
+ echo " [VIOLATION] $f — Topic '$t' not in taxonomy ($ADR_TOPICS)"
151
+ VIOLATIONS=$((VIOLATIONS + 1))
152
+ fi
153
+ done < <(adr_files)
154
+ }
155
+ check_topics
156
+
157
+ # Keep the "valid topics" list in docs/adr/CONVENTIONS.md (the block between the
158
+ # `adr-topics` markers) in EXACT set agreement with $ADR_TOPICS — both ways: a
159
+ # topic in ADR_TOPICS but absent from the documented block, OR a documented topic
160
+ # that is not a valid taxonomy value, is a violation. Only the marker block is
161
+ # parsed (the Topic→skill routing table elsewhere uses the same backticked names
162
+ # but must not count). Doc path is overridable for tests; default is the real one.
163
+ check_topic_doc_sync() {
164
+ # Doc path is fixed in production; only a dedicated, clearly test-scoped var can
165
+ # repoint it (same discipline as ADR_TOPICS_TEST_OVERRIDE) — a stray env var
166
+ # must not be able to silently bypass this gate.
167
+ local conv="${ADR_CONV_DOC_TEST_OVERRIDE:-docs/adr/CONVENTIONS.md}" t
168
+ if [ ! -f "$conv" ]; then
169
+ echo " [VIOLATION] $conv — conventions doc not found; cannot verify taxonomy doc-sync"
170
+ VIOLATIONS=$((VIOLATIONS + 1)); return
171
+ fi
172
+ # Marker block must be well-formed first: exactly one start, exactly one end,
173
+ # end after start. Otherwise the awk extractor would run to EOF and harvest
174
+ # topics from outside the intended block (e.g. the routing table), letting a
175
+ # malformed/unclosed block pass doc-sync silently.
176
+ local n_start n_end l_start l_end
177
+ # `|| true`: grep -c exits 1 on zero matches, which under set -e would abort
178
+ # the script before we can report the malformed-block violation.
179
+ n_start="$(grep -c '<!-- adr-topics:start' "$conv" || true)"
180
+ n_end="$(grep -c '<!-- adr-topics:end' "$conv" || true)"
181
+ if [ "$n_start" != 1 ] || [ "$n_end" != 1 ]; then
182
+ echo " [VIOLATION] $conv — adr-topics marker block malformed (need exactly one start + one end; got start=$n_start end=$n_end)"
183
+ VIOLATIONS=$((VIOLATIONS + 1)); return
184
+ fi
185
+ l_start="$(grep -n '<!-- adr-topics:start' "$conv" | cut -d: -f1)"
186
+ l_end="$(grep -n '<!-- adr-topics:end' "$conv" | cut -d: -f1)"
187
+ if [ "$l_end" -le "$l_start" ]; then
188
+ echo " [VIOLATION] $conv — adr-topics:end must come after adr-topics:start"
189
+ VIOLATIONS=$((VIOLATIONS + 1)); return
190
+ fi
191
+ local doc_topics want
192
+ doc_topics="$(awk '
193
+ /<!-- adr-topics:start/ { on=1; next }
194
+ /<!-- adr-topics:end/ { on=0 }
195
+ on { while (match($0, /`[a-z0-9-]+`/)) { print substr($0, RSTART+1, RLENGTH-2); $0 = substr($0, RSTART+RLENGTH) } }
196
+ ' "$conv" | sort -u)"
197
+ if [ -z "$doc_topics" ]; then
198
+ echo " [VIOLATION] $conv — missing the adr-topics marker block; cannot verify taxonomy doc-sync"
199
+ VIOLATIONS=$((VIOLATIONS + 1)); return
200
+ fi
201
+ want="$(printf '%s\n' $ADR_TOPICS | sort -u)"
202
+ while IFS= read -r t; do
203
+ [ -z "$t" ] && continue
204
+ printf '%s\n' "$doc_topics" | grep -qx "$t" || {
205
+ echo " [VIOLATION] $conv — taxonomy topic '$t' (in ADR_TOPICS) is missing from the documented list"
206
+ VIOLATIONS=$((VIOLATIONS + 1))
207
+ }
208
+ done <<< "$want"
209
+ while IFS= read -r t; do
210
+ [ -z "$t" ] && continue
211
+ printf '%s\n' "$want" | grep -qx "$t" || {
212
+ echo " [VIOLATION] $conv — documented topic '$t' is not a valid taxonomy value (not in ADR_TOPICS)"
213
+ VIOLATIONS=$((VIOLATIONS + 1))
214
+ }
215
+ done <<< "$doc_topics"
216
+ }
217
+ check_topic_doc_sync
218
+
219
+ # Back-citation: if A is "Superseded by B", B's body must cite A (its 4-digit
220
+ # number). CONVENTIONS L51 requires it. Ships as WARN; flips to hard in Phase 3.
221
+ check_back_citation() {
222
+ local f tgt a bfile cand
223
+ while IFS= read -r f; do
224
+ tgt="$(adr_supersede_target "$f")"
225
+ printf '%s' "$tgt" | grep -qE '^[0-9]{4}$' || continue
226
+ a="$(adr_num "$f")"
227
+ # Resolve the successor file via the same defensive-glob idiom as adr_files,
228
+ # NOT `ls ... | head` in a command substitution: under set -e + pipefail a
229
+ # missing target would make ls fail and abort the whole script before later
230
+ # checks and the summary run.
231
+ bfile=""
232
+ for cand in "$ADR_DIR/${tgt}-"*.md; do
233
+ [ -e "$cand" ] && { bfile="$cand"; break; }
234
+ done
235
+ [ -n "$bfile" ] || continue # dangling target already caught by the supersede-target check
236
+ if grep -qE "(^|[^0-9])${a}([^0-9]|$)" "$bfile"; then
237
+ continue
238
+ fi
239
+ # Fail safe: only an explicit "warn" downgrades; any other value (including a
240
+ # typo'd BACKCITE_MODE) stays hard so enforcement is never silently lost.
241
+ if [ "$BACKCITE_MODE" = warn ]; then
242
+ echo " [WARN] $bfile — supersedes $a but never cites it (CONVENTIONS L51); backfill required"
243
+ WARNINGS=$((WARNINGS + 1))
244
+ else
245
+ echo " [VIOLATION] $bfile — supersedes $a but never cites it (CONVENTIONS L51)"
246
+ VIOLATIONS=$((VIOLATIONS + 1))
247
+ fi
248
+ done < <(adr_files)
249
+ }
250
+ check_back_citation
251
+
252
+ # Impl-drift hint: an Accepted ADR with no **Implementation** pointer may have
253
+ # rotted or never shipped. WARN only — without a machine-readable **Code** glob
254
+ # (deferred) we can only hint, and a hard gate on a heuristic trains people to
255
+ # ignore the linter.
256
+ check_impl_drift() {
257
+ local f s impl
258
+ while IFS= read -r f; do
259
+ s="$(adr_field "$f" Status)"
260
+ [ "$s" = Accepted ] || continue
261
+ impl="$(adr_field "$f" Implementation)"
262
+ if [ -z "$impl" ]; then
263
+ echo " [WARN] $f — Accepted but no **Implementation** pointer (possible drift)"
264
+ WARNINGS=$((WARNINGS + 1))
265
+ fi
266
+ done < <(adr_files)
267
+ }
268
+ check_impl_drift
269
+
270
+
271
+ if [ $VIOLATIONS -eq 0 ]; then
272
+ if [ $GRANDFATHERED_HITS -gt 0 ]; then
273
+ echo " [OK] no new violations (${GRANDFATHERED_HITS} grandfathered awaiting cleanup)"
274
+ else
275
+ echo " [OK] all ADRs in terminal state"
276
+ fi
277
+ [ $WARNINGS -gt 0 ] && echo " [NOTE] ${WARNINGS} warning(s) — see WARN lines above (non-blocking)"
278
+ exit 0
279
+ fi
280
+
281
+ echo
282
+ echo " ${VIOLATIONS} new violation(s). See docs/adr/CONVENTIONS.md."
283
+ exit 1
@@ -0,0 +1,209 @@
1
+ #!/usr/bin/env python3
2
+ """check-comment-i18n: fail if CJK appears in genuine code comments.
3
+
4
+ A COMMENT gate, not a whole-repo CJK gate. CJK inside string literals, raw
5
+ strings (Go backticks), rune/char literals, URLs, or thrift annotation values
6
+ is RUNTIME DATA and is explicitly OUT of scope — it must not be flagged.
7
+
8
+ Inputs: newline-separated file paths on stdin (already filtered/excluded by
9
+ the bash wrapper). Output: `path:line: <text>` per hit; exit 1 on any hit
10
+ (plus a stderr summary), exit 0 otherwise.
11
+
12
+ Pure python3 stdlib — no third-party imports.
13
+ """
14
+
15
+ import json
16
+ import re
17
+ import sys
18
+
19
+ # Ideographs (+Ext-A), kana, hangul, CJK/full-width punctuation.
20
+ # Same ranges as the historical text-based gate.
21
+ CJK = re.compile('[ -〿぀-ヿ㐀-䶿'
22
+ '一-鿿가-힯＀-￯]')
23
+
24
+
25
+ def _read_or_fail(path):
26
+ """Read a file as UTF-8, failing CLOSED on any OSError.
27
+
28
+ This is a gate: "cannot verify == do not pass". A swallowed read error
29
+ would let a file with CJK comments slip through silently, so emit a clear
30
+ stderr signal and exit non-zero instead of skipping the file.
31
+ """
32
+ try:
33
+ with open(path, encoding='utf-8', errors='replace') as f:
34
+ return f.read()
35
+ except OSError as e:
36
+ print(f'check-comment-i18n: ERROR: cannot read {path}: {e}',
37
+ file=sys.stderr)
38
+ sys.exit(2)
39
+
40
+
41
+ def comment_hits(path, *, hash_line_comment, backtick_rawstring):
42
+ """Character-level state machine; return [(line, text)] for CJK comments.
43
+
44
+ States: code, line_comment, block_comment, string (``"`` or ``'``), and
45
+ (Go only) backtick_rawstring. Only text genuinely inside a comment is
46
+ checked for CJK. Block comments report the line where ``/*`` began.
47
+ """
48
+ out = []
49
+ data = _read_or_fail(path)
50
+
51
+ state = 'code'
52
+ i = 0
53
+ n = len(data)
54
+ line = 1
55
+ buf = [] # collected comment text
56
+ start_line = 0 # line where the current comment started
57
+ prefix = '' # opener of the current line comment ('//' or '#')
58
+ string_end = '' # closing delimiter of the open string literal
59
+
60
+ while i < n:
61
+ c = data[i]
62
+ nxt = data[i + 1] if i + 1 < n else ''
63
+
64
+ if state == 'code':
65
+ if c == '"' or c == "'":
66
+ state = 'string'
67
+ string_end = c
68
+ elif backtick_rawstring and c == '`':
69
+ state = 'backtick_rawstring'
70
+ elif c == '/' and nxt == '/':
71
+ state = 'line_comment'
72
+ buf = []
73
+ prefix = '//'
74
+ start_line = line
75
+ i += 2
76
+ continue
77
+ elif c == '/' and nxt == '*':
78
+ state = 'block_comment'
79
+ buf = []
80
+ start_line = line
81
+ i += 2
82
+ continue
83
+ elif hash_line_comment and c == '#':
84
+ state = 'line_comment'
85
+ buf = []
86
+ prefix = '#'
87
+ start_line = line
88
+ # any other char (incl. a lone '/') stays in code
89
+
90
+ elif state == 'string':
91
+ if c == '\\':
92
+ i += 2
93
+ # a backslash-newline still advances the line counter
94
+ if i - 1 < n and data[i - 1] == '\n':
95
+ line += 1
96
+ continue
97
+ elif c == string_end:
98
+ state = 'code'
99
+
100
+ elif state == 'backtick_rawstring':
101
+ # No escapes; spans newlines; only a closing backtick ends it.
102
+ if c == '`':
103
+ state = 'code'
104
+
105
+ elif state == 'line_comment':
106
+ if c == '\n':
107
+ text = ''.join(buf)
108
+ if CJK.search(text):
109
+ out.append((start_line, (prefix + text).rstrip()))
110
+ state = 'code'
111
+ else:
112
+ buf.append(c)
113
+
114
+ elif state == 'block_comment':
115
+ if c == '*' and nxt == '/':
116
+ text = ''.join(buf)
117
+ if CJK.search(text):
118
+ first = text.split('\n', 1)[0]
119
+ out.append((start_line, ('/*' + first).rstrip()))
120
+ state = 'code'
121
+ i += 2
122
+ continue
123
+ else:
124
+ buf.append(c)
125
+
126
+ if c == '\n':
127
+ line += 1
128
+ i += 1
129
+
130
+ # Unterminated trailing comment (EOF without newline / closing */).
131
+ if state == 'line_comment':
132
+ text = ''.join(buf)
133
+ if CJK.search(text):
134
+ out.append((start_line, (prefix + text).rstrip()))
135
+ elif state == 'block_comment':
136
+ text = ''.join(buf)
137
+ if CJK.search(text):
138
+ first = text.split('\n', 1)[0]
139
+ out.append((start_line, ('/*' + first).rstrip()))
140
+
141
+ return out
142
+
143
+
144
+ def _json_line_for(raw, value):
145
+ """Best-effort: 1-based line of a "Comment" value containing CJK.
146
+
147
+ Scan raw text for a "Comment" key whose value substring matches. Falls
148
+ back to scanning for the value substring alone, else 1.
149
+ """
150
+ lines = raw.splitlines()
151
+ for idx, ln in enumerate(lines, 1):
152
+ if '"Comment"' in ln and value in ln:
153
+ return idx
154
+ for idx, ln in enumerate(lines, 1):
155
+ if value in ln:
156
+ return idx
157
+ return 1
158
+
159
+
160
+ def json_hits(path):
161
+ """Find CJK in JSON ``Comment`` string values; report real line numbers."""
162
+ out = []
163
+ raw = _read_or_fail(path)
164
+ try:
165
+ data = json.loads(raw)
166
+ except ValueError:
167
+ return out
168
+
169
+ def walk(o):
170
+ if isinstance(o, dict):
171
+ for k, v in o.items():
172
+ if k == 'Comment' and isinstance(v, str) and CJK.search(v):
173
+ out.append((_json_line_for(raw, v), f'Comment: {v}'))
174
+ walk(v)
175
+ elif isinstance(o, list):
176
+ for x in o:
177
+ walk(x)
178
+
179
+ walk(data)
180
+ return out
181
+
182
+
183
+ def hits_for(path):
184
+ if path.endswith('.go'):
185
+ return comment_hits(path, hash_line_comment=False,
186
+ backtick_rawstring=True)
187
+ if path.endswith('.thrift'):
188
+ return comment_hits(path, hash_line_comment=True,
189
+ backtick_rawstring=False)
190
+ if path.endswith('.json'):
191
+ return json_hits(path)
192
+ return []
193
+
194
+
195
+ def main():
196
+ bad = 0
197
+ for path in (p.strip() for p in sys.stdin if p.strip()):
198
+ for n, text in sorted(set(hits_for(path))):
199
+ bad += 1
200
+ print(f'{path}:{n}: {text}')
201
+
202
+ if bad:
203
+ print(f'\n[check-comment-i18n] {bad} CJK comment(s) found — '
204
+ f'comments must be English.', file=sys.stderr)
205
+ sys.exit(1)
206
+
207
+
208
+ if __name__ == '__main__':
209
+ main()
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env bash
2
+ # check-comment-i18n.sh — fail if CJK appears in Go code comments. A COMMENT
3
+ # gate for Go source only: runtime strings / prompts are out of scope, and so
4
+ # are the external-facing API contracts (IDL .thrift comments, OpenAPI, and
5
+ # errno JSON Comment fields) which are intentionally kept in Chinese for
6
+ # external integrators. Tokenization lives in the committed
7
+ # check-comment-i18n.py; this wrapper only does file discovery + exclusion.
8
+ #
9
+ # Usage: check-comment-i18n.sh [PATH...] (default: .)
10
+ # - A PATH that is a directory is scanned recursively for *.go files.
11
+ # - A PATH that is a FILE is included iff it is a *.go file. The same exclude
12
+ # regex applies.
13
+ set -euo pipefail
14
+ HERE="$(cd "$(dirname "$0")" && pwd)"
15
+
16
+ # Default to whole tree when no path given.
17
+ if [ "$#" -eq 0 ]; then
18
+ set -- .
19
+ fi
20
+
21
+ EXCLUDE='thrift_gen|\.pb\.go|_gen\.go|mock_|/mocks/|zz_generated|/vendor/|(^|/)\.claude/|(^|/)\.codex/'
22
+
23
+ discover() {
24
+ local target
25
+ for target in "$@"; do
26
+ if [ -d "$target" ]; then
27
+ find "$target" -type f -name '*.go' 2>/dev/null
28
+ elif [ -f "$target" ]; then
29
+ case "$target" in
30
+ *.go) printf '%s\n' "$target" ;;
31
+ esac
32
+ fi
33
+ done
34
+ }
35
+
36
+ discover "$@" \
37
+ | { grep -vE "$EXCLUDE" || true; } \
38
+ | python3 "$HERE/check-comment-i18n.py"
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env bash
2
+ # Verify that repo paths cited in component docs (docs/NN-*.md) exist on disk.
3
+ #
4
+ # This complements check-docs.sh (which only lints filename/title/numbering
5
+ # structure) with a lightweight content<->code guard: it is the cheapest way to
6
+ # catch the "doc names a file/dir that was renamed or deleted" drift class that
7
+ # previously slipped through CI.
8
+ #
9
+ # Scope / non-goals:
10
+ # - Only docs/NN-*.md (component & architecture docs). ADRs are immutable
11
+ # historical records and are intentionally NOT scanned.
12
+ # - configs/ is out of scope (runtime artifacts; owned by coding-deploy-config).
13
+ # - Removal / placeholder / 规划中 lines are ignored on purpose.
14
+ set -euo pipefail
15
+ cd "$(dirname "$0")/.."
16
+
17
+ # shellcheck source=scripts/lib/doc-drift.sh
18
+ . scripts/lib/doc-drift.sh
19
+
20
+ echo "==> Checking repo path references in docs/NN-*.md resolve on disk..."
21
+
22
+ broken=0
23
+ for f in docs/[0-9]*-*.md; do
24
+ [[ -f "$f" ]] || continue
25
+ while IFS= read -r hit; do
26
+ [[ -z "$hit" ]] && continue
27
+ echo " [FAIL] doc references missing path -> ${hit}"
28
+ broken=$((broken + 1))
29
+ done < <(doc_drift_check_paths "$f")
30
+ done
31
+
32
+ if [[ "$broken" -gt 0 ]]; then
33
+ echo
34
+ echo " ${broken} broken path reference(s). Fix the path in the doc, or — if the"
35
+ echo " path is intentionally absent — phrase it as a removal/placeholder/规划中,"
36
+ echo " which this check skips. configs/ paths are out of scope."
37
+ exit 1
38
+ fi
39
+
40
+ echo " [OK] all repo path references resolve"
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env bash
2
+ # Validate filename pattern, title-number alignment, and number uniqueness for:
3
+ # docs/NN-*.md (component/architecture, 2-digit prefix)
4
+ # docs/adr/NNNN-*.md (architecture decisions, 4-digit prefix)
5
+ #
6
+ # Status field (Accepted/Deprecated/...) is enforced by scripts/check-adr.sh.
7
+ # See docs/adr/CONVENTIONS.md.
8
+ set -euo pipefail
9
+
10
+ cd "$(dirname "$0")/.."
11
+
12
+ # Grandfathered violations — pending cleanup tracked in CONVENTIONS.md.
13
+ # Removing an entry here MUST coincide with cleaning up CONVENTIONS.md table
14
+ # AND scripts/check-adr.sh GRANDFATHERED if applicable.
15
+ GRANDFATHERED=()
16
+
17
+ is_grandfathered() {
18
+ local f=$1
19
+ for g in "${GRANDFATHERED[@]+"${GRANDFATHERED[@]}"}"; do
20
+ [ "$f" = "$g" ] && return 0
21
+ done
22
+ return 1
23
+ }
24
+
25
+ VIOLATIONS=0
26
+ GRANDFATHERED_HITS=0
27
+
28
+ # check_dir <directory> <digit-count>
29
+ check_dir() {
30
+ local dir=$1 digits=$2
31
+ local seen_nums=""
32
+ local fname_pattern="^[0-9]{${digits}}-[a-z0-9-]+\.md$"
33
+ local title_pattern="^#[[:space:]]+([0-9]+)[[:space:]]+-[[:space:]]+.+$"
34
+
35
+ for f in "$dir"/[0-9]*.md; do
36
+ [ -f "$f" ] || continue
37
+ local base num title title_num grandfather_msg
38
+ base=$(basename "$f")
39
+ grandfather_msg=""
40
+ is_grandfathered "$f" && grandfather_msg=" (cleanup tracked in CONVENTIONS.md)"
41
+
42
+ # 1. Filename pattern
43
+ if ! [[ "$base" =~ $fname_pattern ]]; then
44
+ if is_grandfathered "$f"; then
45
+ echo " [GRANDFATHERED] $f — filename does not match ^[0-9]{${digits}}-[a-z0-9-]+\\.md\$$grandfather_msg"
46
+ GRANDFATHERED_HITS=$((GRANDFATHERED_HITS + 1))
47
+ else
48
+ echo " [VIOLATION] $f — filename does not match ^[0-9]{${digits}}-[a-z0-9-]+\\.md\$"
49
+ VIOLATIONS=$((VIOLATIONS + 1))
50
+ fi
51
+ continue
52
+ fi
53
+
54
+ num=${base:0:$digits}
55
+
56
+ # 2. Number uniqueness within directory
57
+ if echo "$seen_nums" | grep -qx "$num"; then
58
+ echo " [VIOLATION] $f — number $num already used in $dir"
59
+ VIOLATIONS=$((VIOLATIONS + 1))
60
+ else
61
+ seen_nums="$seen_nums"$'\n'"$num"
62
+ fi
63
+
64
+ # 3. Title pattern + number alignment
65
+ title=$(head -1 "$f")
66
+ if ! [[ "$title" =~ $title_pattern ]]; then
67
+ if is_grandfathered "$f"; then
68
+ echo " [GRANDFATHERED] $f — title does not match '# NUM - ...'$grandfather_msg"
69
+ GRANDFATHERED_HITS=$((GRANDFATHERED_HITS + 1))
70
+ else
71
+ echo " [VIOLATION] $f — title '$title' does not match '# NUM - ...'"
72
+ VIOLATIONS=$((VIOLATIONS + 1))
73
+ fi
74
+ continue
75
+ fi
76
+ title_num=${BASH_REMATCH[1]}
77
+ if [ "$title_num" != "$num" ]; then
78
+ if is_grandfathered "$f"; then
79
+ echo " [GRANDFATHERED] $f — title number '$title_num' ≠ filename '$num'$grandfather_msg"
80
+ GRANDFATHERED_HITS=$((GRANDFATHERED_HITS + 1))
81
+ else
82
+ echo " [VIOLATION] $f — title number '$title_num' does not match filename '$num'"
83
+ VIOLATIONS=$((VIOLATIONS + 1))
84
+ fi
85
+ fi
86
+ done
87
+ }
88
+
89
+ check_dir docs 2
90
+ check_dir docs/adr 4
91
+
92
+ if [ $VIOLATIONS -eq 0 ]; then
93
+ if [ $GRANDFATHERED_HITS -gt 0 ]; then
94
+ echo " [OK] no new violations (${GRANDFATHERED_HITS} grandfathered awaiting cleanup)"
95
+ else
96
+ echo " [OK] filename / title / numbering all consistent"
97
+ fi
98
+ exit 0
99
+ fi
100
+
101
+ echo
102
+ echo " ${VIOLATIONS} new violation(s). See docs/adr/CONVENTIONS.md."
103
+ exit 1