@seanyao/roll 2026.529.3 → 2026.529.4
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 +10 -0
- package/bin/roll +27 -12
- package/package.json +1 -1
- package/skills/roll-doc/SKILL.md +55 -0
package/CHANGELOG.md
CHANGED
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.529.
|
|
7
|
+
VERSION="2026.529.4"
|
|
8
8
|
ROLL_HOME="${ROLL_HOME:-${HOME}/.roll}"
|
|
9
9
|
ROLL_CONFIG="${ROLL_HOME}/config.yaml"
|
|
10
10
|
ROLL_GLOBAL="${ROLL_HOME}/conventions/global"
|
|
@@ -6031,6 +6031,10 @@ printf '%s:%s\n' "\$\$" "\$(date -u +%s)" > "\$INNER_LOCK"
|
|
|
6031
6031
|
# readers see "still alive in <phase>" during long-running silences (e.g.
|
|
6032
6032
|
# agent_invoke 5-45 min). CURRENT_PHASE is maintained by _phase_begin/_phase_end.
|
|
6033
6033
|
HEARTBEAT_FILE="\${_SHARED_ROOT:-\${HOME}/.shared/roll}/loop/.heartbeat-${slug}"
|
|
6034
|
+
# FIX-136: file-based phase tracking so forked heartbeat child can see phase changes.
|
|
6035
|
+
# CURRENT_PHASE is inherited at fork time — changes after fork are invisible to child.
|
|
6036
|
+
# Writing phase state to a file decouples the heartbeat from fork-time snapshot.
|
|
6037
|
+
HEARTBEAT_PHASE_FILE="\${_SHARED_ROOT:-\${HOME}/.shared/roll}/loop/.phase-${slug}"
|
|
6034
6038
|
CURRENT_PHASE=""
|
|
6035
6039
|
# bash 3.2 (macOS /bin/bash) lacks associative arrays — use namespaced
|
|
6036
6040
|
# variables via 'printf -v' + indirect '\${!VAR}' expansion instead.
|
|
@@ -6042,6 +6046,8 @@ _phase_begin() {
|
|
|
6042
6046
|
local _name="\$1"
|
|
6043
6047
|
printf -v "_PHASE_START_\${_name}" '%s' "\$(date +%s)"
|
|
6044
6048
|
CURRENT_PHASE="\$_name"
|
|
6049
|
+
# FIX-136: write phase+start_ts to file so forked heartbeat child can read it
|
|
6050
|
+
printf '%s %s' "\$_name" "\$(date +%s)" > "\$HEARTBEAT_PHASE_FILE"
|
|
6045
6051
|
_loop_event phase_start "\$_name" "" "" || true
|
|
6046
6052
|
}
|
|
6047
6053
|
_phase_end() {
|
|
@@ -6053,17 +6059,21 @@ _phase_end() {
|
|
|
6053
6059
|
printf -v "_PHASE_DUR_\${_name}" '%s' "\$_dur"
|
|
6054
6060
|
case " \$_PHASE_NAMES_DONE " in *" \$_name "*) ;; *) _PHASE_NAMES_DONE="\${_PHASE_NAMES_DONE} \$_name" ;; esac
|
|
6055
6061
|
CURRENT_PHASE=""
|
|
6062
|
+
# FIX-136: clear phase file so heartbeat knows no active phase
|
|
6063
|
+
echo -n > "\$HEARTBEAT_PHASE_FILE" 2>/dev/null || true
|
|
6056
6064
|
_loop_event phase_end "\$_name" "\${_dur}s" "\$_outcome" || true
|
|
6057
6065
|
}
|
|
6058
6066
|
_heartbeat_writer() {
|
|
6059
6067
|
while true; do
|
|
6060
6068
|
echo "\$(date -u +%s)" > "\$HEARTBEAT_FILE"
|
|
6061
|
-
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
|
|
6065
|
-
|
|
6066
|
-
|
|
6069
|
+
# FIX-136: read phase from file — CURRENT_PHASE is inherited at fork and
|
|
6070
|
+
# never updated. The parent writes phase+start_ts on _phase_begin, clears
|
|
6071
|
+
# on _phase_end. No phase file = no active phase = skip tick.
|
|
6072
|
+
if [ -f "\$HEARTBEAT_PHASE_FILE" ]; then
|
|
6073
|
+
read -r _hb_phase _hb_start_ts < "\$HEARTBEAT_PHASE_FILE" 2>/dev/null || true
|
|
6074
|
+
if [ -n "\$_hb_phase" ] && [ -n "\$_hb_start_ts" ]; then
|
|
6075
|
+
local _el=\$(( \$(date +%s) - _hb_start_ts ))
|
|
6076
|
+
_loop_event phase_tick "\$_hb_phase" "\${_el}s elapsed" "" 2>/dev/null || true
|
|
6067
6077
|
fi
|
|
6068
6078
|
fi
|
|
6069
6079
|
sleep 60
|
|
@@ -6304,7 +6314,7 @@ _runs_append "failed" 0 "[]" "\$_phases_t" 2>/dev/null || true
|
|
|
6304
6314
|
_keep="\${ROLL_CYCLE_LOG_KEEP:-50}"
|
|
6305
6315
|
( cd "\$_log_dir" && ls -t *.log 2>/dev/null | tail -n +\$((_keep + 1)) | xargs -r rm -f ) 2>/dev/null || true
|
|
6306
6316
|
fi
|
|
6307
|
-
rm -f "\$INNER_LOCK" "\$HEARTBEAT_FILE"
|
|
6317
|
+
rm -f "\$INNER_LOCK" "\$HEARTBEAT_FILE" "\$HEARTBEAT_PHASE_FILE"
|
|
6308
6318
|
exit "\$_rc"
|
|
6309
6319
|
}
|
|
6310
6320
|
trap '_inner_cleanup' EXIT
|
|
@@ -6468,6 +6478,11 @@ export LOOP_SHARED_ROOT="\${_SHARED_ROOT:-\$HOME/.shared/roll}"
|
|
|
6468
6478
|
export ROLL_LOOP_AGENT="\${CYCLE_AGENT:-\$(_project_agent)}"
|
|
6469
6479
|
export ROLL_LOOP_ROUTED_STORY ROLL_LOOP_ROUTED_AGENT ROLL_LOOP_ROUTED_RULE
|
|
6470
6480
|
_phase_begin agent_invoke
|
|
6481
|
+
# FIX-136: non-claude agents (pi/deepseek/kimi) buffer stdout when piped.
|
|
6482
|
+
# Force a pseudo-TTY via script(1) so loop-fmt.py's passthrough receives
|
|
6483
|
+
# output in real time — without this, tmux is black for the entire phase.
|
|
6484
|
+
_AGENT_PTY_PREFIX=""
|
|
6485
|
+
[ "\$ROLL_LOOP_AGENT" != "claude" ] && _AGENT_PTY_PREFIX="script -q /dev/null"
|
|
6471
6486
|
for _attempt in 1 2 3; do
|
|
6472
6487
|
# FIX-068: defensive reset before each attempt — _CYCLE_TIMED_OUT carries
|
|
6473
6488
|
# the SIGTERM result of the previous attempt and would otherwise force an
|
|
@@ -6493,11 +6508,11 @@ for _attempt in 1 2 3; do
|
|
|
6493
6508
|
# FIX-134: prefer the runtime-rebuilt command (routing-aware); fall back to
|
|
6494
6509
|
# the baked command (project agent at \`roll loop on\` time) when empty.
|
|
6495
6510
|
if [ -f "\$FMT" ]; then
|
|
6496
|
-
if [ -n "\$_CYCLE_CMD" ]; then ( cd "\$WT" && eval "\$_CYCLE_CMD" ) | python3 "\$FMT"
|
|
6497
|
-
else ( cd "\$WT" && ${agent_cmd} ) | python3 "\$FMT"; fi
|
|
6511
|
+
if [ -n "\$_CYCLE_CMD" ]; then ( cd "\$WT" && eval \$_AGENT_PTY_PREFIX "\$_CYCLE_CMD" ) | python3 "\$FMT"
|
|
6512
|
+
else ( cd "\$WT" && \$_AGENT_PTY_PREFIX ${agent_cmd} ) | python3 "\$FMT"; fi
|
|
6498
6513
|
else
|
|
6499
|
-
if [ -n "\$_CYCLE_CMD" ]; then ( cd "\$WT" && eval "\$_CYCLE_CMD" )
|
|
6500
|
-
else ( cd "\$WT" && ${agent_cmd} ); fi
|
|
6514
|
+
if [ -n "\$_CYCLE_CMD" ]; then ( cd "\$WT" && eval \$_AGENT_PTY_PREFIX "\$_CYCLE_CMD" )
|
|
6515
|
+
else ( cd "\$WT" && \$_AGENT_PTY_PREFIX ${agent_cmd} ); fi
|
|
6501
6516
|
fi
|
|
6502
6517
|
_exit=\$?
|
|
6503
6518
|
kill "\$_WATCHDOG_PID" 2>/dev/null
|
package/package.json
CHANGED
package/skills/roll-doc/SKILL.md
CHANGED
|
@@ -217,6 +217,61 @@ detection rule and a target output file. Skip any topic whose target file alread
|
|
|
217
217
|
| Agent 入口 (AGENTS.md) | Project root has no `AGENTS.md` AND `src/` (or equivalent source root) has ≥ 3 subdirectories | `AGENTS.md` |
|
|
218
218
|
| 高引用目录 | Directory imported by ≥ 5 other source files, even if directory itself has < 3 source files | `<dir>/README.md` |
|
|
219
219
|
|
|
220
|
+
#### Data Flow / Import Chain Tracing
|
|
221
|
+
|
|
222
|
+
**Entry point selection:** start from entry files — any file matching patterns:
|
|
223
|
+
`bin/*`, `cmd/**/*`, `main.*` (e.g. `main.ts`, `main.py`), `index.*` (e.g. `index.ts`, `index.jsx`),
|
|
224
|
+
`App.*`, `server.*`. Exclude `node_modules/`, `dist/`, `build/`, test files (`*.test.*`, `*.spec.*`, `tests/`).
|
|
225
|
+
|
|
226
|
+
**Chain construction:**
|
|
227
|
+
1. For each entry file, read its imports from the symbol table's `imports` field.
|
|
228
|
+
2. Recursively follow each imported file to its own imports, building a directed call graph.
|
|
229
|
+
3. Stop at leaf nodes — files that import nothing or whose imports all point to:
|
|
230
|
+
- External packages (node_modules / stdlib / third-party)
|
|
231
|
+
- Already-visited nodes (cycle termination)
|
|
232
|
+
4. Each distinct path from an entry file to a leaf is one call chain.
|
|
233
|
+
|
|
234
|
+
**Threshold (cross-directory filter):**
|
|
235
|
+
A call chain is valid for inclusion only if it spans **≥ 3 distinct source directories**.
|
|
236
|
+
Count based on the unique parent directories of files in the chain:
|
|
237
|
+
`src/cli/main.ts → src/commands/build.ts → lib/utils/fs.ts` = 3 directories ✅
|
|
238
|
+
`src/cli/main.ts → src/cli/config.ts → lib/utils/fs.ts` = 2 directories ❌
|
|
239
|
+
If no chain meets the ≥ 3 directory threshold, skip generation entirely (no empty `docs/data-flows.md`).
|
|
240
|
+
|
|
241
|
+
**Output document structure** (`docs/data-flows.md`):
|
|
242
|
+
|
|
243
|
+
```markdown
|
|
244
|
+
> **Draft** — auto-generated by roll-doc on YYYY-MM-DD. Review before treating as authoritative.
|
|
245
|
+
|
|
246
|
+
# Data Flows
|
|
247
|
+
|
|
248
|
+
## Flow: {short descriptive name from entry file purpose}
|
|
249
|
+
|
|
250
|
+
**Entry point:** `{entry_file}:{line}`
|
|
251
|
+
**Directories spanned:** N ({comma-separated list})
|
|
252
|
+
|
|
253
|
+
### Complete Call Chain
|
|
254
|
+
|
|
255
|
+
{entry_file}
|
|
256
|
+
→ import {symbol} from "{file}" ({line})
|
|
257
|
+
→ import {symbol} from "{file}" ({line})
|
|
258
|
+
→ ... (leaf node)
|
|
259
|
+
|
|
260
|
+
### Files Involved
|
|
261
|
+
|
|
262
|
+
| Step | File:Line | Function / Method |
|
|
263
|
+
|------|-----------|-------------------|
|
|
264
|
+
| 1 | `path/to/file:12` | `functionName` |
|
|
265
|
+
| 2 | `path/to/file:34` | `otherFunction` |
|
|
266
|
+
| ... | ... | ... |
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
- Sort flows by number of directories spanned, descending (widest cross-cutting flow first).
|
|
270
|
+
- If an entry file produces multiple distinct call chains, list each one as a separate flow entry.
|
|
271
|
+
- `file:line` annotations must come from actual symbol table records — do not fabricate.
|
|
272
|
+
|
|
273
|
+
**Idempotency:** skip (do not overwrite) if `docs/data-flows.md` already exists, unless `--force`.
|
|
274
|
+
|
|
220
275
|
### Step 3 — Source Annotations
|
|
221
276
|
|
|
222
277
|
Every topic document generated in Step 2 must cite `file:line` for each claim (function call,
|