@seanyao/roll 2026.512.7 → 2026.512.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/README.md +6 -5
- package/bin/roll +114 -14
- package/package.json +1 -1
- package/skills/roll-.dream/SKILL.md +87 -4
- package/skills/roll-doc/SKILL.md +184 -0
- package/skills/roll-loop/SKILL.md +26 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v2026.512.8
|
|
4
|
+
- **Added**: `$roll-doc` — legacy 项目文档自动化技能:四阶段扫描(索引 + 缺口分析 + 草稿补全 + 报告),支持 `--dry-run` / `--force`,适用任何项目
|
|
5
|
+
- **Added**: `roll-.dream` Scan 6 — 文档新鲜度检测(滞后文档 / 未记录 ENV 变量 / 架构文档缺失),依赖 roll-doc,发现写入 REFACTOR 条目
|
|
6
|
+
- **Fixed**: loop CI gate 在 SSH config 改写 github.com 为 IP 的环境下失灵 — `gh` 自动识别失败被静默吞掉,loop 把 "gh 出错" 误判为 "gh 未装",在 CI 红的情况下继续把 story 标 ✅ Done;现从 git remote 推导 `owner/repo` 强制传 `-R`,gh 调用失败 = ALERT,loop 起跑前先验 HEAD CI 红绿,红则拒绝 build
|
|
7
|
+
|
|
3
8
|
## v2026.512.7
|
|
4
9
|
- **Added**: `roll alert` — 查看、确认、清除 loop 告警,不用再去翻 loop status
|
|
5
10
|
- **Added**: macOS 系统通知 — story 完成或告警写入时自动弹通知,静音模式下不打扰
|
package/README.md
CHANGED
|
@@ -19,12 +19,13 @@
|
|
|
19
19
|
|
|
20
20
|
## What is Roll?
|
|
21
21
|
|
|
22
|
-
Roll is an
|
|
22
|
+
Roll is an autonomous delivery system for software teams — AI agents pick stories from your BACKLOG, execute them with encoded engineering discipline, and ship continuously while you stay focused on what to build next.
|
|
23
23
|
|
|
24
|
-
**
|
|
25
|
-
1. **
|
|
26
|
-
2. **Skill
|
|
27
|
-
|
|
24
|
+
**Two core values:**
|
|
25
|
+
1. **Autonomous delivery** — `roll loop on` runs BACKLOG items hourly; Dream (nightly code-health scan) surfaces maintenance tasks; humans retain sole release authority
|
|
26
|
+
2. **Skill-driven execution** — 20+ skills encode TDD, TCR, and INVEST practices as reliable, repeatable workflows any agent can follow
|
|
27
|
+
|
|
28
|
+
_Works with Claude, Cursor, Codex, or your own agent._
|
|
28
29
|
|
|
29
30
|
---
|
|
30
31
|
|
package/bin/roll
CHANGED
|
@@ -4,7 +4,7 @@ set -euo pipefail
|
|
|
4
4
|
# Roll — AI Agent Convention Manager
|
|
5
5
|
# Single source of truth for how all AI coding agents behave.
|
|
6
6
|
|
|
7
|
-
VERSION="2026.512.
|
|
7
|
+
VERSION="2026.512.8"
|
|
8
8
|
ROLL_HOME="${ROLL_HOME:-${HOME}/.roll}"
|
|
9
9
|
ROLL_CONFIG="${ROLL_HOME}/config.yaml"
|
|
10
10
|
ROLL_GLOBAL="${ROLL_HOME}/conventions/global"
|
|
@@ -2595,11 +2595,12 @@ _loop_runs_dur() {
|
|
|
2595
2595
|
}
|
|
2596
2596
|
|
|
2597
2597
|
# Format a single JSONL run record for display.
|
|
2598
|
+
# Reads _LOOP_RUNS_BACKLOG global for ID→description lookup (set by _loop_runs).
|
|
2598
2599
|
_loop_runs_format_line() {
|
|
2599
|
-
local line="$1" show_project="$2"
|
|
2600
|
+
local line="$1" show_project="$2" is_darwin="$3"
|
|
2600
2601
|
command -v jq >/dev/null 2>&1 || { echo " (jq required)"; return 0; }
|
|
2601
2602
|
|
|
2602
|
-
local ts status project tcr duration alerts run_id reason built_count
|
|
2603
|
+
local ts status project tcr duration alerts run_id reason built_count skipped_count
|
|
2603
2604
|
ts=$(jq -r '.ts // ""' <<<"$line")
|
|
2604
2605
|
status=$(jq -r '.status // ""' <<<"$line")
|
|
2605
2606
|
project=$(jq -r '.project // ""' <<<"$line")
|
|
@@ -2609,10 +2610,16 @@ _loop_runs_format_line() {
|
|
|
2609
2610
|
run_id=$(jq -r '.run_id // ""' <<<"$line")
|
|
2610
2611
|
reason=$(jq -r '.reason // ""' <<<"$line")
|
|
2611
2612
|
built_count=$(jq -r '(.built // []) | length' <<<"$line")
|
|
2612
|
-
built_csv=$(jq -r '(.built // []) | join(", ")' <<<"$line")
|
|
2613
2613
|
skipped_count=$(jq -r '(.skipped // []) | length' <<<"$line")
|
|
2614
2614
|
|
|
2615
|
-
local hhmm
|
|
2615
|
+
local hhmm epoch=""
|
|
2616
|
+
if [[ "$is_darwin" == "1" ]]; then
|
|
2617
|
+
epoch=$(date -j -u -f "%Y-%m-%dT%H:%M:%SZ" "$ts" "+%s" 2>/dev/null) || epoch=""
|
|
2618
|
+
[[ -n "$epoch" ]] && hhmm=$(date -j -f "%s" "$epoch" "+%H:%M" 2>/dev/null) || hhmm=""
|
|
2619
|
+
else
|
|
2620
|
+
hhmm=$(date -d "$ts" "+%H:%M" 2>/dev/null) || hhmm=""
|
|
2621
|
+
fi
|
|
2622
|
+
[[ -z "$hhmm" ]] && hhmm=$(printf "%s" "$ts" | sed -E 's/.*T([0-9]{2}):([0-9]{2}).*/\1:\2/')
|
|
2616
2623
|
local prefix=""
|
|
2617
2624
|
if [[ "$show_project" == "true" ]]; then
|
|
2618
2625
|
prefix="[$(basename "$project")] "
|
|
@@ -2622,8 +2629,29 @@ _loop_runs_format_line() {
|
|
|
2622
2629
|
built)
|
|
2623
2630
|
local skipped_note=""
|
|
2624
2631
|
[[ "$skipped_count" -gt 0 ]] && skipped_note=", ${skipped_count} skipped"
|
|
2625
|
-
|
|
2626
|
-
|
|
2632
|
+
local items_word; [[ "$built_count" -eq 1 ]] && items_word="item" || items_word="items"
|
|
2633
|
+
printf " %s %s✅ built %d %s (%d tcr%s, %s)\n" \
|
|
2634
|
+
"$hhmm" "$prefix" "$built_count" "$items_word" "$tcr" "$skipped_note" "$(_loop_runs_dur "$duration")"
|
|
2635
|
+
local id desc
|
|
2636
|
+
while IFS= read -r id; do
|
|
2637
|
+
[[ -z "$id" ]] && continue
|
|
2638
|
+
desc=""
|
|
2639
|
+
if [[ -n "$_LOOP_RUNS_BACKLOG" ]]; then
|
|
2640
|
+
desc=$(printf "%s\n" "$_LOOP_RUNS_BACKLOG" | awk -F'|' -v id="$id" '
|
|
2641
|
+
NF >= 3 {
|
|
2642
|
+
cell = $2; gsub(/^[[:space:]]+|[[:space:]]+$/, "", cell)
|
|
2643
|
+
if (cell == id || cell ~ "^\\[" id "\\]") {
|
|
2644
|
+
gsub(/^[[:space:]]+|[[:space:]]+$/, "", $3); print $3; exit
|
|
2645
|
+
}
|
|
2646
|
+
}')
|
|
2647
|
+
fi
|
|
2648
|
+
if [[ -n "$desc" ]]; then
|
|
2649
|
+
[[ ${#desc} -gt 72 ]] && desc="${desc:0:69}..."
|
|
2650
|
+
printf " • %-14s %s\n" "$id" "$desc"
|
|
2651
|
+
else
|
|
2652
|
+
printf " • %s\n" "$id"
|
|
2653
|
+
fi
|
|
2654
|
+
done < <(jq -r '(.built // []) | .[]' <<<"$line")
|
|
2627
2655
|
;;
|
|
2628
2656
|
idle)
|
|
2629
2657
|
printf " %s %s○ idle — no Todo items\n" "$hhmm" "$prefix"
|
|
@@ -2676,10 +2704,17 @@ _loop_runs() {
|
|
|
2676
2704
|
local reversed; reversed=$(printf "%s\n" "$filtered" | awk '{a[NR]=$0} END{for(i=NR; i>=1; i--) print a[i]}')
|
|
2677
2705
|
local recent; recent=$(printf "%s\n" "$reversed" | head -n "$n")
|
|
2678
2706
|
|
|
2707
|
+
local _is_darwin=""
|
|
2708
|
+
[[ "$(uname)" == "Darwin" ]] && _is_darwin="1"
|
|
2709
|
+
|
|
2710
|
+
_LOOP_RUNS_BACKLOG=""
|
|
2711
|
+
[[ -f "$project_path/BACKLOG.md" ]] && _LOOP_RUNS_BACKLOG=$(cat "$project_path/BACKLOG.md")
|
|
2712
|
+
|
|
2679
2713
|
while IFS= read -r line; do
|
|
2680
2714
|
[[ -z "$line" ]] && continue
|
|
2681
|
-
_loop_runs_format_line "$line" "$all_flag"
|
|
2715
|
+
_loop_runs_format_line "$line" "$all_flag" "$_is_darwin"
|
|
2682
2716
|
done <<<"$recent"
|
|
2717
|
+
unset _LOOP_RUNS_BACKLOG
|
|
2683
2718
|
}
|
|
2684
2719
|
|
|
2685
2720
|
# Send a macOS system notification. No-op when muted, non-macOS, or osascript unavailable.
|
|
@@ -2699,9 +2734,27 @@ _loop_tcr_count() {
|
|
|
2699
2734
|
| awk '/^[a-f0-9]+ tcr:/{n++} END{print n+0}'
|
|
2700
2735
|
}
|
|
2701
2736
|
|
|
2737
|
+
# Parse origin remote URL → "owner/repo" for GitHub repos.
|
|
2738
|
+
# Returns non-zero if no origin, or origin is not github.com.
|
|
2739
|
+
# Decoupled from `gh` auto-detection so SSH config host rewrites don't break it.
|
|
2740
|
+
_gh_repo_slug() {
|
|
2741
|
+
local url
|
|
2742
|
+
url=$(git config --get remote.origin.url 2>/dev/null) || return 1
|
|
2743
|
+
case "$url" in
|
|
2744
|
+
git@github.com:*) url="${url#git@github.com:}" ;;
|
|
2745
|
+
ssh://git@github.com/*) url="${url#ssh://git@github.com/}" ;;
|
|
2746
|
+
https://github.com/*) url="${url#https://github.com/}" ;;
|
|
2747
|
+
http://github.com/*) url="${url#http://github.com/}" ;;
|
|
2748
|
+
*) return 1 ;;
|
|
2749
|
+
esac
|
|
2750
|
+
url="${url%.git}"
|
|
2751
|
+
[[ -z "$url" ]] && return 1
|
|
2752
|
+
printf "%s\n" "$url"
|
|
2753
|
+
}
|
|
2754
|
+
|
|
2702
2755
|
# Poll gh run list until current commit's CI completes.
|
|
2703
|
-
# Returns 0 on success or when gh
|
|
2704
|
-
# Returns 1 on CI failure or
|
|
2756
|
+
# Returns 0 on success (or when gh binary missing — graceful skip).
|
|
2757
|
+
# Returns 1 on CI failure, timeout, or any gh call failure.
|
|
2705
2758
|
_ci_wait() {
|
|
2706
2759
|
local timeout="${1:-300}"
|
|
2707
2760
|
local interval=15
|
|
@@ -2712,13 +2765,20 @@ _ci_wait() {
|
|
|
2712
2765
|
local commit; commit=$(git rev-parse HEAD 2>/dev/null) || { err "Not a git repo 非 git 仓库"; return 1; }
|
|
2713
2766
|
local short; short=$(git rev-parse --short HEAD 2>/dev/null)
|
|
2714
2767
|
|
|
2715
|
-
|
|
2768
|
+
# Resolve owner/repo from git remote so we don't depend on gh's auto-detection,
|
|
2769
|
+
# which breaks when ~/.ssh/config rewrites `Host github.com` → IP address.
|
|
2770
|
+
local repo_slug; repo_slug=$(_gh_repo_slug) || {
|
|
2771
|
+
err "Cannot determine GitHub repo from origin remote 无法从 origin 推导 GitHub 仓库"
|
|
2772
|
+
return 1
|
|
2773
|
+
}
|
|
2774
|
+
|
|
2775
|
+
ok "Waiting for CI on ${short} (${repo_slug}) 等待 CI: ${short}"
|
|
2716
2776
|
|
|
2717
2777
|
while (( elapsed < timeout )); do
|
|
2718
2778
|
local runs
|
|
2719
|
-
runs=$(gh run list --commit "$commit" --json status,conclusion 2
|
|
2720
|
-
|
|
2721
|
-
return
|
|
2779
|
+
runs=$(gh -R "$repo_slug" run list --commit "$commit" --json status,conclusion 2>&1) || {
|
|
2780
|
+
err "gh run list failed for ${repo_slug}@${short}: ${runs} gh 调用失败"
|
|
2781
|
+
return 1
|
|
2722
2782
|
}
|
|
2723
2783
|
|
|
2724
2784
|
if [[ -z "$runs" || "$runs" == "[]" ]]; then
|
|
@@ -2754,6 +2814,46 @@ _ci_wait() {
|
|
|
2754
2814
|
return 1
|
|
2755
2815
|
}
|
|
2756
2816
|
|
|
2817
|
+
# Pre-run CI health check — call before picking up new stories.
|
|
2818
|
+
# Refuses to build on a red base (HEAD CI failed). Lenient on unknown states
|
|
2819
|
+
# (gh missing, repo unparseable, no runs yet) — the post-build _loop_enforce_ci
|
|
2820
|
+
# is the strict gate.
|
|
2821
|
+
# Returns 0: ok to proceed (green / pending / unknown / no gh).
|
|
2822
|
+
# Returns 1: HEAD CI is definitively red → ALERT written, do not build.
|
|
2823
|
+
_loop_precheck_ci() {
|
|
2824
|
+
command -v gh &>/dev/null || return 0
|
|
2825
|
+
|
|
2826
|
+
local commit; commit=$(git rev-parse HEAD 2>/dev/null) || return 0
|
|
2827
|
+
local slug; slug=$(_gh_repo_slug) || return 0
|
|
2828
|
+
|
|
2829
|
+
local runs
|
|
2830
|
+
runs=$(gh -R "$slug" run list --commit "$commit" --json conclusion 2>/dev/null) || return 0
|
|
2831
|
+
[[ -z "$runs" || "$runs" == "[]" ]] && return 0
|
|
2832
|
+
|
|
2833
|
+
local failed
|
|
2834
|
+
failed=$(echo "$runs" | jq -r '[.[] | select(.conclusion != null and .conclusion != "success" and .conclusion != "skipped")] | length' 2>/dev/null || echo "0")
|
|
2835
|
+
|
|
2836
|
+
if [[ "$failed" -gt 0 ]]; then
|
|
2837
|
+
local short; short=$(git rev-parse --short HEAD 2>/dev/null || echo unknown)
|
|
2838
|
+
err "Pre-run CI check: HEAD CI is red — refuse to build on broken base (${short}) HEAD CI 红,拒绝在破损的基础上构建"
|
|
2839
|
+
mkdir -p "$(dirname "$_LOOP_ALERT")"
|
|
2840
|
+
cat > "$_LOOP_ALERT" << EOF
|
|
2841
|
+
# ALERT — Pre-run CI check failed (red base)
|
|
2842
|
+
|
|
2843
|
+
**Time**: $(date '+%Y-%m-%d %H:%M')
|
|
2844
|
+
**Commit**: ${short}
|
|
2845
|
+
**Reason**: HEAD CI is red — loop refused to build on a broken base HEAD CI 红,loop 拒绝在破损的基础上构建
|
|
2846
|
+
|
|
2847
|
+
**Action required**:
|
|
2848
|
+
- Investigate and fix CI: \`gh -R $(_gh_repo_slug) run list --commit ${commit}\`
|
|
2849
|
+
- After fixing and pushing green commit: \`roll loop now\`
|
|
2850
|
+
EOF
|
|
2851
|
+
_notify "roll ⚠ CI red" "loop refused to build on broken base (${short})"
|
|
2852
|
+
return 1
|
|
2853
|
+
fi
|
|
2854
|
+
return 0
|
|
2855
|
+
}
|
|
2856
|
+
|
|
2757
2857
|
# CI gate before marking a story Done.
|
|
2758
2858
|
# On CI failure: writes ALERT, returns 1 (caller keeps story 🔨 In Progress).
|
|
2759
2859
|
# When gh unavailable: returns 0 (graceful skip).
|
package/package.json
CHANGED
|
@@ -6,8 +6,9 @@ allowed-tools: "Read, Glob, Grep, Bash(git:*), Write, Edit"
|
|
|
6
6
|
description: |
|
|
7
7
|
Nightly code and architecture health scan. Passively triggered by scheduler
|
|
8
8
|
(cron or GitHub Actions), not invoked by users directly. Detects dead code,
|
|
9
|
-
architectural drift from domain model, pruning candidates,
|
|
10
|
-
|
|
9
|
+
architectural drift from domain model, pruning candidates, emerging patterns,
|
|
10
|
+
doc coverage gaps, and doc staleness (文档新鲜度). Outputs REFACTOR entries
|
|
11
|
+
to BACKLOG.md and a daily log to docs/dream/.
|
|
11
12
|
Distinct from roll-sentinel: sentinel monitors runtime behavior; dream reviews
|
|
12
13
|
code structure and architectural health.
|
|
13
14
|
---
|
|
@@ -33,7 +34,7 @@ in the morning brief.
|
|
|
33
34
|
|
|
34
35
|
## Scan Logic
|
|
35
36
|
|
|
36
|
-
Run all
|
|
37
|
+
Run all six scans every night. Each scan is independent.
|
|
37
38
|
|
|
38
39
|
### Scan 1 — Dead Code
|
|
39
40
|
|
|
@@ -68,6 +69,8 @@ Flag: modules that import directly from a different Bounded Context without
|
|
|
68
69
|
an Anti-Corruption Layer, or module names that have diverged from the
|
|
69
70
|
Ubiquitous Language.
|
|
70
71
|
|
|
72
|
+
**Distinction from Scan 6C**: Scan 2 flags *import boundary violations* (cross-context coupling). Scan 6C flags *missing documentation entries* (module exists but has no entry in `docs/domain/*.md`). Never double-flag — Scan 2 and Scan 6C are orthogonal checks.
|
|
73
|
+
|
|
71
74
|
### Scan 3 — Pruning Candidates
|
|
72
75
|
|
|
73
76
|
Find over-engineering that can be simplified:
|
|
@@ -137,6 +140,77 @@ Flag any `.md` file directly in `docs/` root (allowed subdirs: `guide/`, `domain
|
|
|
137
140
|
{发现内容 或 "文档结构符合规范,无缺口。"}
|
|
138
141
|
```
|
|
139
142
|
|
|
143
|
+
### Scan 6 — 文档新鲜度 (Doc Freshness)
|
|
144
|
+
|
|
145
|
+
**Dependency gate**: Skip Scan 6 entirely when `$roll-doc` (US-SKILL-008) is not yet deployed.
|
|
146
|
+
Check: `[ -f "$ROLL_HOME/skills/roll-doc/SKILL.md" ]`. If absent, log "Scan 6 skipped — roll-doc not deployed" in the dream log and stop. No fallback.
|
|
147
|
+
|
|
148
|
+
When deployed, each finding produces a REFACTOR entry with `$roll-doc` as execution hint:
|
|
149
|
+
```markdown
|
|
150
|
+
| REFACTOR-XXX | docs: <description> — flagged by dream <date> (hint: $roll-doc) | 📋 Todo |
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
#### Check A — Stale Docs
|
|
154
|
+
|
|
155
|
+
Flag source files whose owning doc is >30 days stale:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
# For each file listed in docs/features/*.md or README.md "## Files:" sections:
|
|
159
|
+
# owner_doc_commit = git log -1 --format="%ci" -- <doc_file>
|
|
160
|
+
# source_commit = git log -1 --format="%ci" -- <source_file>
|
|
161
|
+
# lag_days = (source_commit - owner_doc_commit) in days
|
|
162
|
+
# if lag_days > 30 AND doc contains at least one specific file path reference → flag
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
The "owner doc" for a source file is the nearest `README.md` or `docs/features/*.md` that lists the file path in a `## Files:` section. Skip docs that contain only conceptual descriptions (no specific file path references) — they cannot be objectively stale.
|
|
166
|
+
|
|
167
|
+
#### Check B — Undocumented ENV Vars
|
|
168
|
+
|
|
169
|
+
Flag environment variables that appear frequently in source but have no documentation:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
# Detect ENV var patterns in non-test source files:
|
|
173
|
+
patterns=(
|
|
174
|
+
'process\.env\.[A-Z_]+' # Node.js
|
|
175
|
+
'os\.getenv\("[A-Z_]+"\)' # Python
|
|
176
|
+
'ENV\["[A-Z_]+"\]' # Ruby
|
|
177
|
+
)
|
|
178
|
+
# For each matched variable name:
|
|
179
|
+
# count occurrences across all source files
|
|
180
|
+
# if count >= 5 AND zero mentions in any .md file → flag
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Flag variables appearing ≥5× in source with zero mentions in any `.md`.
|
|
184
|
+
"Other convention signals" (comment clusters, module structure templates) are explicitly deferred — too vague for deterministic detection.
|
|
185
|
+
|
|
186
|
+
#### Check C — Existence Drift
|
|
187
|
+
|
|
188
|
+
Find module directories that exist in code but are absent from architecture docs.
|
|
189
|
+
This is distinct from Scan 2 (which checks *import violations*) — Scan 6C checks *documentation existence*:
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
# Walk all non-excluded directories
|
|
193
|
+
# For each dir with >= 3 non-hidden, non-.md source files:
|
|
194
|
+
# check if any docs/domain/*.md contains the directory name
|
|
195
|
+
# if not found → flag as "existence drift"
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Exclusions: `node_modules/`, `.git/`, `dist/`, `build/`, `.shared/`, `docs/`, `tests/`.
|
|
199
|
+
|
|
200
|
+
Flag directories with ≥3 source files and zero name-match in `docs/domain/*.md`.
|
|
201
|
+
|
|
202
|
+
#### Dream Log Section
|
|
203
|
+
|
|
204
|
+
Add after `## 文档覆盖度` section:
|
|
205
|
+
|
|
206
|
+
```markdown
|
|
207
|
+
## 文档新鲜度
|
|
208
|
+
- 滞后文档:{N} 个(超过 30 天未更新但绑定了代码文件)
|
|
209
|
+
- 未记录 ENV 变量:{N} 个(出现 ≥5 次但无文档)
|
|
210
|
+
- 架构文档缺失模块:{N} 个(≥3 个源文件的目录未出现在 docs/domain/)
|
|
211
|
+
{发现内容列表 或 "文档新鲜度良好,无滞后或缺失项。"}
|
|
212
|
+
```
|
|
213
|
+
|
|
140
214
|
## Output
|
|
141
215
|
|
|
142
216
|
### REFACTOR Entry (BACKLOG.md)
|
|
@@ -161,7 +235,7 @@ without context switching:
|
|
|
161
235
|
# Dream Log {YYYY-MM-DD}
|
|
162
236
|
|
|
163
237
|
## 概要
|
|
164
|
-
- 扫描项:死代码 / 架构漂移 / 裁剪候选 / 新兴模式 / 文档覆盖度
|
|
238
|
+
- 扫描项:死代码 / 架构漂移 / 裁剪候选 / 新兴模式 / 文档覆盖度 / 文档新鲜度
|
|
165
239
|
- 发现:{N} 项标记,{M} 个 REFACTOR 条目已创建
|
|
166
240
|
|
|
167
241
|
## 死代码
|
|
@@ -176,6 +250,15 @@ without context switching:
|
|
|
176
250
|
## 新兴模式
|
|
177
251
|
{发现内容 或 "未发现可提取的重复模式。"}
|
|
178
252
|
|
|
253
|
+
## 文档覆盖度
|
|
254
|
+
{发现内容 或 "文档结构符合规范,无缺口。"}
|
|
255
|
+
|
|
256
|
+
## 文档新鲜度
|
|
257
|
+
- 滞后文档:{N} 个
|
|
258
|
+
- 未记录 ENV 变量:{N} 个
|
|
259
|
+
- 架构文档缺失模块:{N} 个
|
|
260
|
+
{发现内容 或 "文档新鲜度良好,无滞后或缺失项。"}
|
|
261
|
+
|
|
179
262
|
## 创建的 REFACTOR 条目
|
|
180
263
|
{列表 或 "无。"}
|
|
181
264
|
```
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: roll-doc
|
|
3
|
+
license: MIT
|
|
4
|
+
allowed-tools: "Read, Write, Edit, Glob, Grep, Bash(date:*,find:*,stat:*,wc:*)"
|
|
5
|
+
description: "Legacy project documentation automation. Scans all docs, builds/updates docs/INDEX.md, identifies undocumented modules, and generates draft fills for gaps. Works on any project."
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# roll-doc
|
|
9
|
+
|
|
10
|
+
Four-phase legacy documentation automation: scan → index → gap analysis → fill.
|
|
11
|
+
|
|
12
|
+
Works on any project root. No manual mode switching — reads the project state and decides what to do.
|
|
13
|
+
|
|
14
|
+
## When to Use
|
|
15
|
+
|
|
16
|
+
- Starting to work on a legacy project with scattered or missing documentation
|
|
17
|
+
- `docs/INDEX.md` is out of date or doesn't exist yet
|
|
18
|
+
- `roll-.dream` Scan 6 flagged undocumented modules (REFACTOR entry referencing roll-doc)
|
|
19
|
+
- You want a complete picture of what's documented and what isn't
|
|
20
|
+
|
|
21
|
+
## When Not to Use
|
|
22
|
+
|
|
23
|
+
- The project has up-to-date, maintained docs — no need to rebuild the index
|
|
24
|
+
- You need authoritative documentation reviewed and approved (drafts only; human reviews and commits)
|
|
25
|
+
- You want to write a specific known doc from scratch — write it directly
|
|
26
|
+
|
|
27
|
+
## Invocation
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
$roll-doc # Full four-phase run
|
|
31
|
+
$roll-doc --dry-run # Phases 1–2 only; print Phase 3 plan without writing any files
|
|
32
|
+
$roll-doc --force # Re-generate drafts even for existing files
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Phase 1 — Scan & Index
|
|
36
|
+
|
|
37
|
+
Scan the project root for all `*.md` files and known convention files.
|
|
38
|
+
|
|
39
|
+
**Exclusions — never scan these directories:**
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
node_modules/ .git/ dist/ build/ .shared/ docs/dream/ docs/briefs/
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Convention files — detect by filename anywhere in the tree:**
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
AGENTS.md CLAUDE.md GEMINI.md CONVENTIONS.md CONTRIBUTING.md
|
|
49
|
+
ARCHITECTURE.md ADR-*.md
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Classification rules:**
|
|
53
|
+
|
|
54
|
+
| Category | Criteria |
|
|
55
|
+
|----------|----------|
|
|
56
|
+
| `guide` | Path under `docs/guide/` |
|
|
57
|
+
| `domain` | Path under `docs/domain/` |
|
|
58
|
+
| `convention` | Filename matches convention markers list above |
|
|
59
|
+
| `module` | File is `<dir>/README.md` for a source directory |
|
|
60
|
+
| `stray` | None of the above (top-level, unorganized, or orphaned) |
|
|
61
|
+
|
|
62
|
+
**Output — produce/update `docs/INDEX.md`:**
|
|
63
|
+
|
|
64
|
+
```markdown
|
|
65
|
+
# Documentation Index
|
|
66
|
+
|
|
67
|
+
> Auto-generated by roll-doc on YYYY-MM-DD. Edit individual docs, not this file.
|
|
68
|
+
|
|
69
|
+
## Index
|
|
70
|
+
|
|
71
|
+
| Path | Title | Category | Last Modified |
|
|
72
|
+
|------|-------|----------|---------------|
|
|
73
|
+
| docs/guide/en/loop.md | Loop User Guide | guide | 2026-05-01 |
|
|
74
|
+
| AGENTS.md | Agent Conventions | convention | 2026-04-28 |
|
|
75
|
+
|
|
76
|
+
## Coverage Summary
|
|
77
|
+
|
|
78
|
+
- Total docs indexed: N
|
|
79
|
+
- By category: guide (N) / domain (N) / convention (N) / module (N) / stray (N)
|
|
80
|
+
|
|
81
|
+
## Gap Report
|
|
82
|
+
|
|
83
|
+
Directories with ≥3 source files and no linked documentation:
|
|
84
|
+
|
|
85
|
+
| Directory | Source Files | Missing Doc |
|
|
86
|
+
|-----------|-------------|-------------|
|
|
87
|
+
| src/commands/ | 8 | README.md |
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`docs/INDEX.md` is always overwritten on each run — it is a derived artifact, not authoritative content.
|
|
91
|
+
|
|
92
|
+
## Phase 2 — Gap Analysis
|
|
93
|
+
|
|
94
|
+
Walk every directory (applying Phase 1 exclusions):
|
|
95
|
+
|
|
96
|
+
1. Count non-hidden, non-`.md` source files directly in the directory
|
|
97
|
+
2. If count ≥ 3 AND no `README.md` in that directory AND no `docs/INDEX.md` entry links to it → **module gap**
|
|
98
|
+
|
|
99
|
+
**Special gaps (checked once per project):**
|
|
100
|
+
- No `docs/domain/` directory or empty → gap: `docs/domain/context-map.md`
|
|
101
|
+
- No `CONVENTIONS.md` or `docs/CONVENTIONS.md` exists → gap: `docs/CONVENTIONS.md`
|
|
102
|
+
|
|
103
|
+
**Threshold**: directories with ≥ 3 source files (default). Tune by editing the skill.
|
|
104
|
+
|
|
105
|
+
Record all gaps — they become Phase 3 input.
|
|
106
|
+
|
|
107
|
+
## Phase 3 — Fill
|
|
108
|
+
|
|
109
|
+
**Skip this phase entirely when:**
|
|
110
|
+
- `--dry-run` was passed (print the fill plan to stdout, write nothing)
|
|
111
|
+
- Phase 2 found zero gaps
|
|
112
|
+
|
|
113
|
+
**Idempotency rule**: Without `--force`, re-running when no new gaps exist is a **no-op** — no files are written, no existing drafts are modified.
|
|
114
|
+
|
|
115
|
+
For each gap:
|
|
116
|
+
1. Read up to 20 source files from the target directory to infer module purpose, key exports, dependencies, and configuration patterns
|
|
117
|
+
2. Generate a draft document at the conventional location (see table below)
|
|
118
|
+
3. **Skip if the target file already exists**, unless `--force` was passed
|
|
119
|
+
|
|
120
|
+
**Draft locations by gap type:**
|
|
121
|
+
|
|
122
|
+
| Gap Type | Draft Location |
|
|
123
|
+
|----------|---------------|
|
|
124
|
+
| Module with no README | `<dir>/README.md` |
|
|
125
|
+
| No `docs/domain/` entries | `docs/domain/context-map.md` |
|
|
126
|
+
| No conventions doc | `docs/CONVENTIONS.md` |
|
|
127
|
+
|
|
128
|
+
**Every generated file starts with this exact header line:**
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
> **Draft** — auto-generated by roll-doc on YYYY-MM-DD. Review before treating as authoritative.
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**Minimum draft content:**
|
|
135
|
+
|
|
136
|
+
- Module README: purpose (1–2 sentences), key files with one-line descriptions, dependencies (imports from / depended on by)
|
|
137
|
+
- Context map: bounded contexts identified in the project, their responsibilities, relationships
|
|
138
|
+
- Conventions: detected patterns — ENV vars, naming conventions, repeated file structure templates
|
|
139
|
+
|
|
140
|
+
Do not fabricate details — infer only from source files actually read.
|
|
141
|
+
|
|
142
|
+
## Phase 4 — Report
|
|
143
|
+
|
|
144
|
+
After all phases complete, output a summary:
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
📚 roll-doc complete
|
|
148
|
+
|
|
149
|
+
Phase 1 — Index
|
|
150
|
+
N docs scanned, docs/INDEX.md updated
|
|
151
|
+
Categories: guide(N) domain(N) convention(N) module(N) stray(N)
|
|
152
|
+
|
|
153
|
+
Phase 2 — Gaps
|
|
154
|
+
N undocumented module directories found
|
|
155
|
+
N special gaps (domain map / conventions)
|
|
156
|
+
|
|
157
|
+
Phase 3 — Fill
|
|
158
|
+
N drafts generated: [list of paths]
|
|
159
|
+
N skipped (already exist; use --force to regenerate)
|
|
160
|
+
|
|
161
|
+
📋 Review priority (largest / most active modules first):
|
|
162
|
+
1. src/commands/README.md — 8 source files
|
|
163
|
+
2. docs/CONVENTIONS.md — 6 patterns detected
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
If no gaps were found:
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
✅ roll-doc: no gaps found. docs/INDEX.md updated.
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
If `--dry-run`:
|
|
173
|
+
|
|
174
|
+
```
|
|
175
|
+
🔍 roll-doc --dry-run: N drafts would be generated (nothing written).
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Rules
|
|
179
|
+
|
|
180
|
+
- Never modify existing documentation files — only generate new drafts
|
|
181
|
+
- `docs/INDEX.md` is the only existing file that may be overwritten (derived artifact)
|
|
182
|
+
- Draft files are never committed by this skill — human reviews and commits them
|
|
183
|
+
- Works on any project; always read project structure before acting
|
|
184
|
+
- Do not fabricate module details — infer only from source files actually read
|
|
@@ -68,7 +68,21 @@ fi
|
|
|
68
68
|
2. For each such story, check `state.yaml`:
|
|
69
69
|
- If `current_item` matches the story id AND `status: running` → this is the resume case (handled above), leave it.
|
|
70
70
|
- Otherwise → this is an **orphan 🔨** (the loop that marked it crashed before finishing). Revert the status back to `📋 Todo`, commit `chore: revert orphan 🔨 US-XXX to 📋`, and append a line to `~/.shared/roll/loop/ALERT.md` recording the orphan id and time so the next brief surfaces it.
|
|
71
|
-
3. After orphan sweep, proceed to Step
|
|
71
|
+
3. After orphan sweep, proceed to Step 1.5 (Pre-run CI health check) before scanning.
|
|
72
|
+
|
|
73
|
+
### Step 1.5 — Pre-run CI Health Check
|
|
74
|
+
|
|
75
|
+
Call `_loop_precheck_ci` before scanning BACKLOG. This is a **defensive gate**
|
|
76
|
+
against building on a broken base — if the most recent commit on the branch
|
|
77
|
+
has red CI, the loop must not stack new commits on top (which would create the
|
|
78
|
+
exact stuck-red state FIX-026 traces to).
|
|
79
|
+
|
|
80
|
+
- HEAD CI green / pending / no-run-yet → proceed to Step 2.
|
|
81
|
+
- HEAD CI red → write ALERT, **do not pick up any stories this cycle**,
|
|
82
|
+
exit cleanly. The next cycle will retry; the human must fix CI manually
|
|
83
|
+
(typically by reverting or pushing a green commit) before the loop resumes.
|
|
84
|
+
- `gh` missing or repo unparseable → graceful skip (`_loop_precheck_ci`
|
|
85
|
+
returns 0); the post-build `_loop_enforce_ci` remains the strict gate.
|
|
72
86
|
|
|
73
87
|
### Step 2 — Scan BACKLOG
|
|
74
88
|
|
|
@@ -142,11 +156,18 @@ After each item completes:
|
|
|
142
156
|
- Count `tcr:` prefix commits since `started_at` via `git log --oneline --since=<started_at>`
|
|
143
157
|
- Count == 0 → revert story status in BACKLOG.md from ✅ Done → 📋 Todo; write ALERT to `~/.shared/roll/loop/ALERT.md` with story ID, time, reason "zero tcr: commits since story start", and suggested actions (`roll loop now` / `$roll-build <id>` / `roll loop reset`)
|
|
144
158
|
- Count > 0 → continue normally
|
|
145
|
-
2. **CI Gate** —
|
|
146
|
-
|
|
159
|
+
2. **CI Gate** — **MUST** invoke `roll ci --wait` (the `_loop_enforce_ci`
|
|
160
|
+
wrapper). **Do NOT call `gh` directly** (no `gh run list`, no `gh run watch`,
|
|
161
|
+
no ad-hoc shell checks): `roll ci --wait` is the only sanctioned entry —
|
|
162
|
+
it derives `owner/repo` from the git remote and uses `gh -R <slug>`, which
|
|
163
|
+
is required to work through `~/.ssh/config` host rewrites that break gh's
|
|
164
|
+
auto-detection.
|
|
147
165
|
- CI passes → continue normally
|
|
148
|
-
- CI fails
|
|
149
|
-
|
|
166
|
+
- CI fails / times out / `gh` call fails → keep story as `🔨 In Progress`
|
|
167
|
+
(do NOT mark ✅ Done); write ALERT; skip to next story
|
|
168
|
+
- `gh` binary not installed (`command -v gh` fails) → skip gracefully
|
|
169
|
+
(return 0). Any other `gh` error is **not** "gh unavailable" — it is a
|
|
170
|
+
hard failure and must block the gate.
|
|
150
171
|
3. Update state file: `status: idle`
|
|
151
172
|
4. Check if a Feature is now fully complete (all its Stories ✅)
|
|
152
173
|
5. If yes and `brief_on_feature_complete: true` → invoke `Skill("roll-brief")`
|