@seanyao/roll 2026.508.2 → 2026.509.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/roll +418 -1
- package/package.json +1 -1
- package/skills/roll-build/SKILL.md +27 -0
- package/skills/roll-doctor/SKILL.md +25 -0
- package/skills/roll-peer/SKILL.md +241 -0
- package/skills/roll-research/SKILL.md +7 -0
- package/skills/roll-research/references/schema.json +4 -0
package/README.md
CHANGED
|
@@ -192,7 +192,7 @@ For larger changes, open an issue first to discuss the approach.
|
|
|
192
192
|
|
|
193
193
|
Roll builds on ideas and inspiration from the open-source community:
|
|
194
194
|
|
|
195
|
-
- **[khazix-skills](https://github.com/KKKKhazix/khazix-skills)** by Digital Life Khazix — The HV Analysis (Horizontal-Vertical Analysis) deep research framework
|
|
195
|
+
- **[khazix-skills](https://github.com/KKKKhazix/khazix-skills)** by Digital Life Khazix — The HV Analysis (Horizontal-Vertical Analysis) deep research framework and schema used by `$roll-research` are derived from this project, used under the MIT License. Copyright (c) 2026 数字生命卡兹克.
|
|
196
196
|
- **[superpowers](https://github.com/obra/superpowers)** by Jesse Vincent — A composable skills library for AI coding agents that informed several workflow patterns in Roll.
|
|
197
197
|
|
|
198
198
|
---
|
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.
|
|
7
|
+
VERSION="2026.509.3"
|
|
8
8
|
ROLL_HOME="${ROLL_HOME:-${HOME}/.roll}"
|
|
9
9
|
ROLL_CONFIG="${ROLL_HOME}/config.yaml"
|
|
10
10
|
ROLL_GLOBAL="${ROLL_HOME}/conventions/global"
|
|
@@ -539,6 +539,11 @@ cmd_setup() {
|
|
|
539
539
|
echo ""
|
|
540
540
|
_sync_skills "$force"
|
|
541
541
|
|
|
542
|
+
echo ""
|
|
543
|
+
info "Setting up peer review state... 正在初始化 peer review 状态..."
|
|
544
|
+
_peer_ensure_state_dir
|
|
545
|
+
ok "Peer state directory ready. Peer 状态目录已就绪。"
|
|
546
|
+
|
|
542
547
|
echo ""
|
|
543
548
|
ok "Setup complete. 初始化完成。"
|
|
544
549
|
|
|
@@ -1220,6 +1225,416 @@ check_sync_status() {
|
|
|
1220
1225
|
fi
|
|
1221
1226
|
}
|
|
1222
1227
|
|
|
1228
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
1229
|
+
# PEER REVIEW
|
|
1230
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
1231
|
+
|
|
1232
|
+
_PEER_STATE_DIR="${ROLL_HOME}/.peer-state"
|
|
1233
|
+
|
|
1234
|
+
_peer_ensure_state_dir() {
|
|
1235
|
+
mkdir -p "$_PEER_STATE_DIR"
|
|
1236
|
+
mkdir -p "${_PEER_STATE_DIR}/logs"
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
_peer_state_file() {
|
|
1240
|
+
local pair="$1"
|
|
1241
|
+
local key="$2"
|
|
1242
|
+
echo "${_PEER_STATE_DIR}/${pair}_${key}"
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
_peer_get_state() {
|
|
1246
|
+
local pair="$1"
|
|
1247
|
+
local key="$2"
|
|
1248
|
+
local file
|
|
1249
|
+
file="$(_peer_state_file "$pair" "$key")"
|
|
1250
|
+
if [[ -f "$file" ]]; then
|
|
1251
|
+
cat "$file"
|
|
1252
|
+
else
|
|
1253
|
+
echo ""
|
|
1254
|
+
fi
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
_peer_set_state() {
|
|
1258
|
+
local pair="$1"
|
|
1259
|
+
local key="$2"
|
|
1260
|
+
local val="$3"
|
|
1261
|
+
_peer_ensure_state_dir
|
|
1262
|
+
printf '%s\n' "$val" > "$(_peer_state_file "$pair" "$key")"
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
_peer_normalize_pair() {
|
|
1266
|
+
local from="$1"
|
|
1267
|
+
local to="$2"
|
|
1268
|
+
printf '%s→%s\n' "$from" "$to"
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
_peer_detect_peers() {
|
|
1272
|
+
local peers=""
|
|
1273
|
+
for tool in claude kimi pi codex opencode cursor; do
|
|
1274
|
+
if command -v "$tool" &>/dev/null; then
|
|
1275
|
+
peers="${peers}${peers:+ }${tool}"
|
|
1276
|
+
fi
|
|
1277
|
+
done
|
|
1278
|
+
printf '%s\n' "$peers"
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
_peer_route() {
|
|
1282
|
+
local from="$1"
|
|
1283
|
+
local tag="${2:-default}"
|
|
1284
|
+
|
|
1285
|
+
local map_val
|
|
1286
|
+
map_val="$(config_get "peer_capability_map_${tag}" "")"
|
|
1287
|
+
if [[ -z "$map_val" ]]; then
|
|
1288
|
+
map_val="$(config_get "peer_capability_map_default" "kimi claude pi")"
|
|
1289
|
+
fi
|
|
1290
|
+
|
|
1291
|
+
local installed
|
|
1292
|
+
installed="$(_peer_detect_peers)"
|
|
1293
|
+
|
|
1294
|
+
local candidate
|
|
1295
|
+
for candidate in $map_val; do
|
|
1296
|
+
[[ "$candidate" == "$from" ]] && continue
|
|
1297
|
+
if echo "$installed" | grep -qw "$candidate"; then
|
|
1298
|
+
local pair status
|
|
1299
|
+
pair="$(_peer_normalize_pair "$from" "$candidate")"
|
|
1300
|
+
status="$(_peer_get_state "$pair" "status")"
|
|
1301
|
+
if [[ "$status" != "abandoned" ]]; then
|
|
1302
|
+
printf '%s\n' "$candidate"
|
|
1303
|
+
return 0
|
|
1304
|
+
fi
|
|
1305
|
+
fi
|
|
1306
|
+
done
|
|
1307
|
+
|
|
1308
|
+
printf '%s\n' ""
|
|
1309
|
+
return 1
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
_peer_call() {
|
|
1313
|
+
local to="$1"
|
|
1314
|
+
local prompt="$2"
|
|
1315
|
+
local output=""
|
|
1316
|
+
local stderr_log
|
|
1317
|
+
stderr_log="${_PEER_STATE_DIR}/logs/.last_stderr.log"
|
|
1318
|
+
local call_timeout
|
|
1319
|
+
call_timeout="$(config_get "peer_call_timeout" "180")"
|
|
1320
|
+
|
|
1321
|
+
info "Peer call timeout: ${call_timeout}s Peer 调用超时: ${call_timeout}s"
|
|
1322
|
+
|
|
1323
|
+
case "$to" in
|
|
1324
|
+
claude)
|
|
1325
|
+
output="$(claude -p --output-format text "$prompt" 2>"$stderr_log" || true)"
|
|
1326
|
+
;;
|
|
1327
|
+
kimi)
|
|
1328
|
+
output="$(kimi --quiet -p "$prompt" 2>"$stderr_log" || true)"
|
|
1329
|
+
;;
|
|
1330
|
+
pi)
|
|
1331
|
+
output="$(pi -p --mode text "$prompt" 2>"$stderr_log" || true)"
|
|
1332
|
+
;;
|
|
1333
|
+
*)
|
|
1334
|
+
err "Unsupported peer: $to 不支持的 peer: $to"
|
|
1335
|
+
return 1
|
|
1336
|
+
;;
|
|
1337
|
+
esac
|
|
1338
|
+
|
|
1339
|
+
printf '%s\n' "$output"
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
_peer_parse_resolution() {
|
|
1343
|
+
local output="$1"
|
|
1344
|
+
local resolution
|
|
1345
|
+
# Match AGREE/REFINE/OBJECT/ESCALATE near line start (only non-letters before it)
|
|
1346
|
+
# Covers: **AGREE**, ### 结论:AGREE, - AGREE:, * REFINE, OBJECT — ...
|
|
1347
|
+
resolution="$(printf '%s\n' "$output" | grep -oiE '^[^a-zA-Z]*\b(AGREE|REFINE|OBJECT|ESCALATE)\b' | head -1 | grep -oiE '\b(AGREE|REFINE|OBJECT|ESCALATE)\b' | tr '[:lower:]' '[:upper:]')"
|
|
1348
|
+
printf '%s\n' "$resolution"
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
_peer_update_state() {
|
|
1352
|
+
local pair="$1"
|
|
1353
|
+
local outcome="$2"
|
|
1354
|
+
local streak=0
|
|
1355
|
+
|
|
1356
|
+
local prev_streak
|
|
1357
|
+
prev_streak="$(_peer_get_state "$pair" "streak")"
|
|
1358
|
+
if [[ "$prev_streak" =~ ^[0-9]+$ ]]; then
|
|
1359
|
+
streak="$prev_streak"
|
|
1360
|
+
fi
|
|
1361
|
+
|
|
1362
|
+
if [[ "$outcome" == "AGREE" ]]; then
|
|
1363
|
+
streak=0
|
|
1364
|
+
_peer_set_state "$pair" "status" "active"
|
|
1365
|
+
else
|
|
1366
|
+
streak=$((streak + 1))
|
|
1367
|
+
if [[ "$streak" -ge 3 ]]; then
|
|
1368
|
+
_peer_set_state "$pair" "status" "abandoned"
|
|
1369
|
+
else
|
|
1370
|
+
_peer_set_state "$pair" "status" "degraded"
|
|
1371
|
+
fi
|
|
1372
|
+
fi
|
|
1373
|
+
|
|
1374
|
+
_peer_set_state "$pair" "streak" "$streak"
|
|
1375
|
+
_peer_set_state "$pair" "last_outcome" "$outcome"
|
|
1376
|
+
_peer_set_state "$pair" "last_time" "$(date -Iseconds)"
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
cmd_peer() {
|
|
1380
|
+
local from_tool=""
|
|
1381
|
+
local to_tool=""
|
|
1382
|
+
local round=1
|
|
1383
|
+
local tag="default"
|
|
1384
|
+
local context_file=""
|
|
1385
|
+
local yolo=false
|
|
1386
|
+
local subcmd=""
|
|
1387
|
+
|
|
1388
|
+
while [[ $# -gt 0 ]]; do
|
|
1389
|
+
case "$1" in
|
|
1390
|
+
--from) from_tool="$2"; shift 2 ;;
|
|
1391
|
+
--to) to_tool="$2"; shift 2 ;;
|
|
1392
|
+
--round) round="$2"; shift 2 ;;
|
|
1393
|
+
--tag) tag="$2"; shift 2 ;;
|
|
1394
|
+
--context) context_file="$2"; shift 2 ;;
|
|
1395
|
+
--yes|--yolo) yolo=true; shift ;;
|
|
1396
|
+
status) subcmd="status"; shift ;;
|
|
1397
|
+
reset) subcmd="reset"; shift; break ;;
|
|
1398
|
+
help|--help|-h) subcmd="help"; shift ;;
|
|
1399
|
+
*) err "Unknown option: $1 未知选项: $1"; exit 1 ;;
|
|
1400
|
+
esac
|
|
1401
|
+
done
|
|
1402
|
+
|
|
1403
|
+
case "$subcmd" in
|
|
1404
|
+
status) cmd_peer_status; return ;;
|
|
1405
|
+
reset) cmd_peer_reset "$@"; return ;;
|
|
1406
|
+
help) cmd_peer_help; return ;;
|
|
1407
|
+
esac
|
|
1408
|
+
|
|
1409
|
+
if [[ -z "$from_tool" ]]; then
|
|
1410
|
+
err "--from is required. 必须指定 --from。"
|
|
1411
|
+
echo ""
|
|
1412
|
+
cmd_peer_help
|
|
1413
|
+
exit 1
|
|
1414
|
+
fi
|
|
1415
|
+
|
|
1416
|
+
if [[ -z "$to_tool" ]]; then
|
|
1417
|
+
to_tool="$(_peer_route "$from_tool" "$tag")"
|
|
1418
|
+
if [[ -z "$to_tool" ]]; then
|
|
1419
|
+
err "No available peer found for tag '$tag'. 未找到 tag '$tag' 的可用 peer。"
|
|
1420
|
+
echo ""
|
|
1421
|
+
info "Installed peers: $(_peer_detect_peers)"
|
|
1422
|
+
info "Capability map: $(config_get "peer_capability_map_${tag}" "$(config_get "peer_capability_map_default" "kimi claude pi")")"
|
|
1423
|
+
exit 1
|
|
1424
|
+
fi
|
|
1425
|
+
info "Auto-selected peer: $to_tool 自动选择 peer: $to_tool"
|
|
1426
|
+
fi
|
|
1427
|
+
|
|
1428
|
+
local pair
|
|
1429
|
+
pair="$(_peer_normalize_pair "$from_tool" "$to_tool")"
|
|
1430
|
+
|
|
1431
|
+
local status
|
|
1432
|
+
status="$(_peer_get_state "$pair" "status")"
|
|
1433
|
+
if [[ "$status" == "abandoned" ]]; then
|
|
1434
|
+
err "Peer pair $pair is abandoned. Run 'roll peer reset $from_tool $to_tool' to restore. Peer 对 $pair 已废弃。运行 'roll peer reset $from_tool $to_tool' 恢复。"
|
|
1435
|
+
exit 1
|
|
1436
|
+
fi
|
|
1437
|
+
|
|
1438
|
+
if [[ "$yolo" != "true" ]]; then
|
|
1439
|
+
local opt_out
|
|
1440
|
+
opt_out="$(config_get "peer_opt_out_seconds" "10")"
|
|
1441
|
+
info "Launching peer review: $from_tool → $to_tool (round $round, tag: $tag)"
|
|
1442
|
+
info "Press Enter to proceed or type 'n' to abort. Auto-executing in ${opt_out}s..."
|
|
1443
|
+
info "启动 peer review: $from_tool → $to_tool (第 $round 轮, tag: $tag)"
|
|
1444
|
+
info "按 Enter 执行或输入 n 取消。${opt_out} 秒后自动执行..."
|
|
1445
|
+
|
|
1446
|
+
local answer=""
|
|
1447
|
+
if IFS= read -r -t "$opt_out" answer 2>/dev/null; then
|
|
1448
|
+
if [[ "$answer" == "n" || "$answer" == "N" ]]; then
|
|
1449
|
+
info "Peer review aborted by user. 用户取消 peer review。"
|
|
1450
|
+
exit 0
|
|
1451
|
+
fi
|
|
1452
|
+
fi
|
|
1453
|
+
fi
|
|
1454
|
+
|
|
1455
|
+
local context=""
|
|
1456
|
+
if [[ -n "$context_file" && -f "$context_file" ]]; then
|
|
1457
|
+
context="$(cat "$context_file")"
|
|
1458
|
+
fi
|
|
1459
|
+
|
|
1460
|
+
local prompt
|
|
1461
|
+
prompt="[PEER_REVIEW round=${round} tool=${from_tool}→${to_tool}]\n\n${context}"
|
|
1462
|
+
|
|
1463
|
+
_peer_ensure_state_dir
|
|
1464
|
+
local log_file
|
|
1465
|
+
log_file="${_PEER_STATE_DIR}/logs/$(date +%Y%m%d_%H%M%S)_${from_tool}_${to_tool}.md"
|
|
1466
|
+
{
|
|
1467
|
+
echo "# Peer Review Log"
|
|
1468
|
+
echo ""
|
|
1469
|
+
echo "- **From**: $from_tool"
|
|
1470
|
+
echo "- **To**: $to_tool"
|
|
1471
|
+
echo "- **Round**: $round"
|
|
1472
|
+
echo "- **Tag**: $tag"
|
|
1473
|
+
echo "- **Time**: $(date -Iseconds)"
|
|
1474
|
+
echo ""
|
|
1475
|
+
echo "## Prompt"
|
|
1476
|
+
echo ""
|
|
1477
|
+
echo '```'
|
|
1478
|
+
printf '%s\n' "$prompt"
|
|
1479
|
+
echo '```'
|
|
1480
|
+
echo ""
|
|
1481
|
+
echo "## Response"
|
|
1482
|
+
echo ""
|
|
1483
|
+
} > "$log_file"
|
|
1484
|
+
|
|
1485
|
+
info "Calling $to_tool... 调用 $to_tool..."
|
|
1486
|
+
local response
|
|
1487
|
+
response="$(_peer_call "$to_tool" "$prompt")"
|
|
1488
|
+
|
|
1489
|
+
local stderr_log
|
|
1490
|
+
stderr_log="${_PEER_STATE_DIR}/logs/.last_stderr.log"
|
|
1491
|
+
if [[ -f "$stderr_log" && -s "$stderr_log" ]]; then
|
|
1492
|
+
echo ""
|
|
1493
|
+
echo -e "${BOLD}Peer stderr Peer 标准错误:${NC}"
|
|
1494
|
+
cat "$stderr_log"
|
|
1495
|
+
echo ""
|
|
1496
|
+
fi
|
|
1497
|
+
|
|
1498
|
+
printf '%s\n' "$response" >> "$log_file"
|
|
1499
|
+
|
|
1500
|
+
local resolution
|
|
1501
|
+
resolution="$(_peer_parse_resolution "$response")"
|
|
1502
|
+
|
|
1503
|
+
if [[ -z "$resolution" ]]; then
|
|
1504
|
+
warn "Could not parse resolution from peer response. 无法解析 peer 响应中的决议状态。"
|
|
1505
|
+
resolution="UNKNOWN"
|
|
1506
|
+
fi
|
|
1507
|
+
|
|
1508
|
+
_peer_update_state "$pair" "$resolution"
|
|
1509
|
+
|
|
1510
|
+
echo ""
|
|
1511
|
+
echo -e "${BOLD}Peer Review Result Peer Review 结果${NC}"
|
|
1512
|
+
echo " Pair: $pair"
|
|
1513
|
+
echo " Round: $round"
|
|
1514
|
+
echo " Resolution: $resolution"
|
|
1515
|
+
echo ""
|
|
1516
|
+
|
|
1517
|
+
case "$resolution" in
|
|
1518
|
+
AGREE)
|
|
1519
|
+
ok "Consensus reached. Proceed with execution. 达成共识,继续执行。"
|
|
1520
|
+
;;
|
|
1521
|
+
REFINE|OBJECT)
|
|
1522
|
+
if [[ "$round" -ge 3 ]]; then
|
|
1523
|
+
warn "Max rounds reached. Escalating to user. 达到最大轮数,升级给用户。"
|
|
1524
|
+
else
|
|
1525
|
+
info "Peer requests $resolution. Continue to round $((round + 1)). Peer 请求 $resolution,继续第 $((round + 1)) 轮。"
|
|
1526
|
+
fi
|
|
1527
|
+
;;
|
|
1528
|
+
ESCALATE|UNKNOWN)
|
|
1529
|
+
warn "Peer review escalated or failed. Human decision required. Peer review 升级或失败,需要人类决策。"
|
|
1530
|
+
;;
|
|
1531
|
+
esac
|
|
1532
|
+
|
|
1533
|
+
echo ""
|
|
1534
|
+
info "Log: $log_file"
|
|
1535
|
+
|
|
1536
|
+
case "$resolution" in
|
|
1537
|
+
AGREE) exit 0 ;;
|
|
1538
|
+
REFINE|OBJECT) exit 2 ;;
|
|
1539
|
+
*) exit 1 ;;
|
|
1540
|
+
esac
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
cmd_peer_status() {
|
|
1544
|
+
_peer_ensure_state_dir
|
|
1545
|
+
echo -e "${BOLD}Peer Review Status Peer Review 状态${NC}"
|
|
1546
|
+
echo ""
|
|
1547
|
+
|
|
1548
|
+
local found=0
|
|
1549
|
+
local status_file
|
|
1550
|
+
for status_file in "$_PEER_STATE_DIR"/*_status; do
|
|
1551
|
+
[[ -f "$status_file" ]] || continue
|
|
1552
|
+
found=1
|
|
1553
|
+
local pair status streak last_outcome last_time
|
|
1554
|
+
pair="$(basename "$status_file" | sed 's/_status$//')"
|
|
1555
|
+
status="$(_peer_get_state "$pair" "status")"
|
|
1556
|
+
streak="$(_peer_get_state "$pair" "streak")"
|
|
1557
|
+
last_outcome="$(_peer_get_state "$pair" "last_outcome")"
|
|
1558
|
+
last_time="$(_peer_get_state "$pair" "last_time")"
|
|
1559
|
+
|
|
1560
|
+
local sc="$GREEN"
|
|
1561
|
+
[[ "$status" == "degraded" ]] && sc="$YELLOW"
|
|
1562
|
+
[[ "$status" == "abandoned" ]] && sc="$RED"
|
|
1563
|
+
|
|
1564
|
+
echo -e " ${sc}${pair}${NC}"
|
|
1565
|
+
echo " Status: ${status:-active}"
|
|
1566
|
+
echo " Streak: ${streak:-0}"
|
|
1567
|
+
echo " Last: ${last_outcome:-none} @ ${last_time:-never}"
|
|
1568
|
+
echo ""
|
|
1569
|
+
done
|
|
1570
|
+
|
|
1571
|
+
if [[ "$found" -eq 0 ]]; then
|
|
1572
|
+
info "No peer review history yet. 暂无 peer review 记录。"
|
|
1573
|
+
fi
|
|
1574
|
+
|
|
1575
|
+
echo ""
|
|
1576
|
+
info "Installed peers: $(_peer_detect_peers)"
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
cmd_peer_reset() {
|
|
1580
|
+
local target_pair=""
|
|
1581
|
+
local reset_all=false
|
|
1582
|
+
|
|
1583
|
+
while [[ $# -gt 0 ]]; do
|
|
1584
|
+
case "$1" in
|
|
1585
|
+
--all) reset_all=true; shift ;;
|
|
1586
|
+
--from|--to|--round|--tag|--context|--yes|--yolo|status|reset|help|--help|-h) shift ;;
|
|
1587
|
+
*)
|
|
1588
|
+
if [[ -z "$target_pair" ]]; then
|
|
1589
|
+
target_pair="$1"
|
|
1590
|
+
fi
|
|
1591
|
+
shift
|
|
1592
|
+
;;
|
|
1593
|
+
esac
|
|
1594
|
+
done
|
|
1595
|
+
|
|
1596
|
+
_peer_ensure_state_dir
|
|
1597
|
+
|
|
1598
|
+
if [[ "$reset_all" == "true" ]]; then
|
|
1599
|
+
rm -f "$_PEER_STATE_DIR"/*_status
|
|
1600
|
+
rm -f "$_PEER_STATE_DIR"/*_streak
|
|
1601
|
+
rm -f "$_PEER_STATE_DIR"/*_last_outcome
|
|
1602
|
+
rm -f "$_PEER_STATE_DIR"/*_last_time
|
|
1603
|
+
ok "All peer states reset. 所有 peer 状态已重置。"
|
|
1604
|
+
return
|
|
1605
|
+
fi
|
|
1606
|
+
|
|
1607
|
+
if [[ -z "$target_pair" ]]; then
|
|
1608
|
+
err "Usage: roll peer reset <from>→<to> | --all 用法: roll peer reset <from>→<to> | --all"
|
|
1609
|
+
exit 1
|
|
1610
|
+
fi
|
|
1611
|
+
|
|
1612
|
+
rm -f "$(_peer_state_file "$target_pair" "status")"
|
|
1613
|
+
rm -f "$(_peer_state_file "$target_pair" "streak")"
|
|
1614
|
+
rm -f "$(_peer_state_file "$target_pair" "last_outcome")"
|
|
1615
|
+
rm -f "$(_peer_state_file "$target_pair" "last_time")"
|
|
1616
|
+
ok "Peer state reset: $target_pair Peer 状态已重置: $target_pair"
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
cmd_peer_help() {
|
|
1620
|
+
echo -e "${BOLD}roll peer — Cross-Agent Peer Review${NC}"
|
|
1621
|
+
echo ""
|
|
1622
|
+
echo "Usage: roll peer [options] 用法: roll peer [选项]"
|
|
1623
|
+
echo ""
|
|
1624
|
+
echo "Options:"
|
|
1625
|
+
echo " --from <tool> Originating agent (kimi, claude, pi) 发起方"
|
|
1626
|
+
echo " --to <tool> Target peer (auto-detected if omitted) 对端 peer(省略则自动选择)"
|
|
1627
|
+
echo " --round <N> Current round (default: 1) 当前轮数"
|
|
1628
|
+
echo " --tag <type> Task type for routing (architecture, security, test...) 任务类型"
|
|
1629
|
+
echo " --context <file> Context file to send to peer 上下文文件"
|
|
1630
|
+
echo " --yes, --yolo Skip opt-out prompt 跳过确认提示"
|
|
1631
|
+
echo ""
|
|
1632
|
+
echo "Subcommands:"
|
|
1633
|
+
echo " status Show peer review state 显示状态"
|
|
1634
|
+
echo " reset <pair|--all> Reset peer state 重置状态"
|
|
1635
|
+
echo " help Show this help 显示帮助"
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1223
1638
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
1224
1639
|
# MAIN
|
|
1225
1640
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
@@ -1241,6 +1656,7 @@ usage() {
|
|
|
1241
1656
|
echo " update [Upgrade] npm install latest + re-sync 一键升级到最新版"
|
|
1242
1657
|
echo " init [Project] Create AGENTS.md + BACKLOG.md + docs/ 初始化项目工作流文件"
|
|
1243
1658
|
echo " status [Diagnostic] Show current state 显示当前状态"
|
|
1659
|
+
echo " peer [Peer Review] Cross-agent negotiation 跨 Agent 协商对审"
|
|
1244
1660
|
echo ""
|
|
1245
1661
|
echo "Examples / 示例:"
|
|
1246
1662
|
echo " roll setup # New machine: first-time install 新机器首次安装"
|
|
@@ -1258,6 +1674,7 @@ main() {
|
|
|
1258
1674
|
update) cmd_update "$@" ;;
|
|
1259
1675
|
init) cmd_init "$@" ;;
|
|
1260
1676
|
status) cmd_status "$@" ;;
|
|
1677
|
+
peer) cmd_peer "$@" ;;
|
|
1261
1678
|
version|--version|-v) echo "roll v${VERSION}" ;;
|
|
1262
1679
|
help|--help|-h|"") usage ;;
|
|
1263
1680
|
*)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seanyao/roll",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.509.3",
|
|
4
4
|
"description": "Roll — Roll out features with AI agents",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "find tests/unit tests/integration -name '*.bats' | sort | xargs ./tests/helpers/bats-core/bin/bats"
|
|
@@ -173,6 +173,33 @@ Proceed to the **Shared TCR Workflow** (Phase 4 onward).
|
|
|
173
173
|
|
|
174
174
|
The following phases apply to both Story mode and Fly mode after planning is complete.
|
|
175
175
|
|
|
176
|
+
### Phase 3.5: Peer Review Gate
|
|
177
|
+
|
|
178
|
+
After planning is complete, before entering Test Design Review, assess whether the plan warrants peer review:
|
|
179
|
+
|
|
180
|
+
**Auto-trigger `$roll-peer` when any of the following is true:**
|
|
181
|
+
- Plan affects **>3 files** or **crosses modules**
|
|
182
|
+
- **Architecture decisions** or non-obvious trade-offs are involved
|
|
183
|
+
- **Destructive / irreversible operations** (deletions, migrations, production deploys)
|
|
184
|
+
- **High-risk signal words** detected in user request ("critical / important / don't break / 关键 / 别搞砸")
|
|
185
|
+
- User explicitly requests peer review ("/peer", "叫上 peer")
|
|
186
|
+
|
|
187
|
+
**With 10s opt-out:**
|
|
188
|
+
```
|
|
189
|
+
Plan affects N files across M modules. Estimated peer review: 2–3 rounds, ~X tokens.
|
|
190
|
+
Press Enter to launch peer review, or type 'n' to skip. Auto-executing in 10s...
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**After peer review result:**
|
|
194
|
+
- **AGREE** → proceed to Phase 4 (Test Design Review)
|
|
195
|
+
- **REFINE** → incorporate feedback, regenerate plan, re-run Phase 3.5
|
|
196
|
+
- **OBJECT** → consider alternative plan, re-run Phase 3.5 with revised proposal
|
|
197
|
+
- **ESCALATE** → present both proposals to user for final decision before proceeding
|
|
198
|
+
|
|
199
|
+
**Never trigger:**
|
|
200
|
+
- Single-file changes or well-defined fixes
|
|
201
|
+
- Plans with no cross-module impact and no architecture decisions
|
|
202
|
+
|
|
176
203
|
### Phase 4: Test Design Review
|
|
177
204
|
|
|
178
205
|
Before writing implementation code:
|
|
@@ -112,6 +112,30 @@ Check:
|
|
|
112
112
|
- File exists and is valid YAML
|
|
113
113
|
- Has required `ai_tool_name` or `ai_claude` entries
|
|
114
114
|
|
|
115
|
+
### 7. Peer Review
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
# Peer state directory
|
|
119
|
+
ls -la ~/.roll/.peer-state/ 2>/dev/null || echo "missing"
|
|
120
|
+
|
|
121
|
+
# Peer config in config.yaml
|
|
122
|
+
grep "peer_" ~/.roll/config.yaml 2>/dev/null || echo "no peer config"
|
|
123
|
+
|
|
124
|
+
# Available peer CLIs
|
|
125
|
+
for tool in claude kimi pi codex; do
|
|
126
|
+
command -v "$tool" &>/dev/null && echo "✓ $tool" || echo "✗ $tool"
|
|
127
|
+
done
|
|
128
|
+
|
|
129
|
+
# roll-peer skill
|
|
130
|
+
ls ~/.roll/skills/roll-peer/SKILL.md 2>/dev/null || echo "missing"
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Check:
|
|
134
|
+
- `~/.roll/.peer-state/` exists and is writable
|
|
135
|
+
- `~/.roll/config.yaml` contains `peer_call_timeout` (default: 180)
|
|
136
|
+
- At least one peer CLI (claude / kimi / pi) is installed
|
|
137
|
+
- `roll-peer` skill exists
|
|
138
|
+
|
|
115
139
|
## Report Format
|
|
116
140
|
|
|
117
141
|
```
|
|
@@ -123,6 +147,7 @@ Check:
|
|
|
123
147
|
[✓/✗] Conventions sync
|
|
124
148
|
[✓/✗] Templates integrity
|
|
125
149
|
[✓/✗] Config
|
|
150
|
+
[✓/✗] Peer Review
|
|
126
151
|
|
|
127
152
|
---
|
|
128
153
|
Issues found: N
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: roll-peer
|
|
3
|
+
license: MIT
|
|
4
|
+
allowed-tools: "Read, Bash, Write, Edit"
|
|
5
|
+
description: |
|
|
6
|
+
Cross-agent peer review skill. When a task enters a decision phase (planning,
|
|
7
|
+
high-risk, ambiguous, destructive), triggers a bidirectional negotiation with
|
|
8
|
+
another AI agent via a unified protocol. Up to 3 rounds. If consensus is not
|
|
9
|
+
reached, escalates to the human user. Includes adaptive peer routing based on
|
|
10
|
+
task type and historical success rate.
|
|
11
|
+
Trigger: /peer, "叫上 peer", "peer review", or auto-triggered at workflow gates.
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Roll Peer (Cross-Agent Peer Review)
|
|
15
|
+
|
|
16
|
+
> Follows the Architecture Constraints, Development Discipline, and Engineering
|
|
17
|
+
> Common Sense defined in the project AGENTS.md.
|
|
18
|
+
|
|
19
|
+
## Credits
|
|
20
|
+
|
|
21
|
+
Cross-agent consultation protocol inspired by
|
|
22
|
+
[friend-skill](https://github.com/fpyluck/friend-skill) (MIT) by fpyluck.
|
|
23
|
+
Independent implementation for the Roll toolchain.
|
|
24
|
+
|
|
25
|
+
## Trigger
|
|
26
|
+
|
|
27
|
+
**Manual:**
|
|
28
|
+
- `/peer`
|
|
29
|
+
- "叫上 peer"
|
|
30
|
+
- "peer review 一下"
|
|
31
|
+
- "和 peer 商量"
|
|
32
|
+
|
|
33
|
+
**Auto-triggered (with 10s opt-out):**
|
|
34
|
+
- `roll-build` enters Plan Mode (executable plans / architecture decisions)
|
|
35
|
+
- `roll-spar` Attacker and Defender disagree
|
|
36
|
+
- High context pressure (large number of files read / tools executed)
|
|
37
|
+
- Destructive / irreversible operations (`rm -rf`, production deploy, global config changes)
|
|
38
|
+
- High-risk signal words ("重要 / 关键 / 别搞砸 / important / critical")
|
|
39
|
+
- Cross-repository / cross-toolchain / ambiguous permission boundaries
|
|
40
|
+
|
|
41
|
+
**Never trigger:**
|
|
42
|
+
- Single-file changes
|
|
43
|
+
- Clear, well-defined fixes
|
|
44
|
+
- Simple refactoring
|
|
45
|
+
|
|
46
|
+
## Protocol: `[PEER_REVIEW]`
|
|
47
|
+
|
|
48
|
+
### Marker Format
|
|
49
|
+
|
|
50
|
+
The marker **must** appear on the first non-empty line of the message:
|
|
51
|
+
|
|
52
|
+
```markdown
|
|
53
|
+
[PEER_REVIEW round=N tool=<from>→<to>]
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
- `round=N`: Current round number (1–3)
|
|
57
|
+
- `tool=<from>→<to>`: Direction of this message (e.g., `kimi→claude`)
|
|
58
|
+
|
|
59
|
+
### Three-State Resolution + Escape
|
|
60
|
+
|
|
61
|
+
Allowed states only. No invented words.
|
|
62
|
+
|
|
63
|
+
- **AGREE**: Accept the current proposal. Proceed to execution.
|
|
64
|
+
- **REFINE**: Direction is correct, but specific changes are needed. Proceed to next round.
|
|
65
|
+
- **OBJECT**: The proposal is wrong. Provide an alternative. Proceed to next round.
|
|
66
|
+
- **ESCALATE**: Round 3 reached without AGREE, or a round fails due to API/token error. Hand off to the human user.
|
|
67
|
+
|
|
68
|
+
If information is insufficient:
|
|
69
|
+
```
|
|
70
|
+
REFINE: Need to confirm X/Y/Z with the user first.
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Context Handoff Card (required for round=1)
|
|
74
|
+
|
|
75
|
+
When the task involves a local project, the first message must include:
|
|
76
|
+
|
|
77
|
+
```markdown
|
|
78
|
+
## Project Handoff (round=1 required)
|
|
79
|
+
- Project root: <absolute path>
|
|
80
|
+
- Execution environment: <shell / container / devcontainer / remote / N/A>
|
|
81
|
+
- Project type: <language + framework>
|
|
82
|
+
- Virtual environment: <absolute path / conda env / container name / N/A>
|
|
83
|
+
- Activation command: <one-line executable string, or N/A>
|
|
84
|
+
- Key tool calls:
|
|
85
|
+
- test: <one-line command or N/A>
|
|
86
|
+
- build: <one-line command or N/A>
|
|
87
|
+
- run: <one-line command or N/A>
|
|
88
|
+
- lint: <one-line command or N/A>
|
|
89
|
+
- Key conventions / constraints: <2–3 items, or N/A>
|
|
90
|
+
- Related file pointers: <absolute paths or @references, or N/A>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Rules:
|
|
94
|
+
- Paths must be **absolute**.
|
|
95
|
+
- Commands must be **one-line executable strings**, not descriptions.
|
|
96
|
+
- Prefer commands that do **not** require an activated environment (absolute interpreter paths, `uv run`, `docker compose exec`).
|
|
97
|
+
- Do not copy README text. List file pointers only.
|
|
98
|
+
- Never include secrets, tokens, credentials, or `.env` content.
|
|
99
|
+
- Even if logically a continuation, treat as round=1 if the peer has **no prior context**.
|
|
100
|
+
|
|
101
|
+
### Anti-Hallucination Rule
|
|
102
|
+
|
|
103
|
+
When mentioning specific paths, function names, commands, line numbers, or tool results, **must cite the source** ("I read X at line Y"). If unverified, state "unverified" explicitly.
|
|
104
|
+
|
|
105
|
+
## State Machine
|
|
106
|
+
|
|
107
|
+
### Per Negotiation (Single Task)
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
Running
|
|
111
|
+
├── AGREE (any round) → Execute proposal
|
|
112
|
+
├── Round == 3, no AGREE → ESCALATE (failed_max_rounds)
|
|
113
|
+
├── API/token error → ESCALATE (failed_api_error)
|
|
114
|
+
└── User aborts → ESCALATE (user_abort)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Per Peer Pair (e.g., kimi→claude)
|
|
118
|
+
|
|
119
|
+
Stored in `~/.shared/roll/peer/state.yaml`:
|
|
120
|
+
|
|
121
|
+
```yaml
|
|
122
|
+
kimi→claude:
|
|
123
|
+
status: active # active | degraded | abandoned
|
|
124
|
+
streak: 0 # consecutive failure count
|
|
125
|
+
last_outcome: agreed
|
|
126
|
+
history:
|
|
127
|
+
- { time: "2026-05-08T23:30:00+08:00", outcome: agreed, rounds: 2, tag: architecture }
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Rules:
|
|
131
|
+
- `streak >= 3` → automatically mark as `abandoned`
|
|
132
|
+
- `abandoned` peer pairs are skipped by the bridge script
|
|
133
|
+
- Human can reset via `roll peer reset <from> <to>` or `roll peer reset --all`
|
|
134
|
+
- If a peer pair is abandoned, the bridge falls back to the next candidate in the capability map
|
|
135
|
+
|
|
136
|
+
## Peer Routing (Adaptive)
|
|
137
|
+
|
|
138
|
+
### Capability Map (Task Type → Preferred Peer Order)
|
|
139
|
+
|
|
140
|
+
```yaml
|
|
141
|
+
peer:
|
|
142
|
+
capability_map:
|
|
143
|
+
architecture: [claude, deepseek, kimi, pi]
|
|
144
|
+
security: [claude, deepseek, pi, kimi]
|
|
145
|
+
test: [codex, kimi, deepseek, claude]
|
|
146
|
+
refactor: [deepseek, kimi, claude, pi]
|
|
147
|
+
default: [deepseek, kimi, claude, pi]
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Adaptive Adjustment
|
|
151
|
+
|
|
152
|
+
After each negotiation, record:
|
|
153
|
+
- `outcome`: agreed / failed_max_rounds / failed_api_error
|
|
154
|
+
- `rounds`: number of rounds consumed
|
|
155
|
+
- `tag`: task type
|
|
156
|
+
|
|
157
|
+
If `streak` for a peer pair reaches the configured threshold (default: 3 consecutive failures), mark as `abandoned`. The next task of the same type will try the next candidate in `capability_map`.
|
|
158
|
+
|
|
159
|
+
### Peer Detection
|
|
160
|
+
|
|
161
|
+
The bridge script detects installed peers via `command -v <tool>`. Only installed tools are considered. The current running tool is excluded (`exclude_self: true`).
|
|
162
|
+
|
|
163
|
+
For `deepseek`, also check if serve mode is available as a more reliable alternative:
|
|
164
|
+
```bash
|
|
165
|
+
command -v deepseek && { deepseek serve --help 2>/dev/null; true; } | grep -q "\-\-http" && echo "serve_mode"
|
|
166
|
+
```
|
|
167
|
+
If serve mode is available, prefer HTTP transport over direct CLI invocation.
|
|
168
|
+
|
|
169
|
+
### Peer Invocation Reference
|
|
170
|
+
|
|
171
|
+
| Peer | Non-interactive command | Reliability | Notes |
|
|
172
|
+
|------|------------------------|-------------|-------|
|
|
173
|
+
| `claude` | `claude -p "<prompt>"` | ✅ High | Native, stable |
|
|
174
|
+
| `deepseek` | `deepseek "<prompt>"` | ✅ Good | No TTY dependency |
|
|
175
|
+
| `deepseek` (serve) | `curl localhost:<port>/v1/...` | ✅ High | Start with `deepseek serve --http`; preferred over direct CLI |
|
|
176
|
+
| `kimi` | `kimi --quiet "<prompt>"` | ⚠️ Unverified | Verify non-interactive support before use |
|
|
177
|
+
| `codex` | `codex exec --json --output-last-message "<prompt>"` | ⚠️ Unstable | Known CI failures due to Ink/TTY (issues [#1080](https://github.com/openai/codex/issues/1080), [#1340](https://github.com/openai/codex/issues/1340)); use only as fallback |
|
|
178
|
+
| `pi` | `pi -p "<prompt>"` | ⚠️ Unverified | Verify non-interactive support before use |
|
|
179
|
+
|
|
180
|
+
**CLI vs. API Key**: `claude`, `deepseek`, `kimi`, `codex` CLIs authenticate via existing subscription accounts — no separate API key required. This is the primary advantage of CLI transport over the MCP/HTTP approach.
|
|
181
|
+
|
|
182
|
+
## Workflow Integration
|
|
183
|
+
|
|
184
|
+
### `roll-build` Plan Mode
|
|
185
|
+
|
|
186
|
+
After generating an executable plan, before proceeding to TCR:
|
|
187
|
+
|
|
188
|
+
1. Assess plan complexity (file count, cross-module impact, risk level)
|
|
189
|
+
2. If complexity > threshold, prompt user:
|
|
190
|
+
```
|
|
191
|
+
This plan affects 5 files across 3 modules. Estimated peer review: 2–3 rounds, ~X tokens.
|
|
192
|
+
Press Enter to launch peer review, or type 'n' to skip. Auto-executing in 10s...
|
|
193
|
+
```
|
|
194
|
+
3. If user does not abort within 10s, invoke `roll peer` with `--tag architecture`
|
|
195
|
+
4. Wait for result:
|
|
196
|
+
- AGREE → proceed to TCR
|
|
197
|
+
- REFINE/OBJECT → incorporate feedback and regenerate plan
|
|
198
|
+
- ESCALATE → present both proposals to user for final decision
|
|
199
|
+
|
|
200
|
+
### `roll-spar`
|
|
201
|
+
|
|
202
|
+
When Attacker and Defender reach a stalemate (both tests pass but interpretations differ):
|
|
203
|
+
|
|
204
|
+
1. Auto-invoke `roll peer` with `--tag test`
|
|
205
|
+
2. Use the peer's verdict as tie-breaker
|
|
206
|
+
|
|
207
|
+
## Output Artifacts
|
|
208
|
+
|
|
209
|
+
- **Negotiation log**: `~/.shared/roll/peer/logs/<timestamp>_<from>_<to>.md`
|
|
210
|
+
- **State file**: `~/.shared/roll/peer/state.yaml`
|
|
211
|
+
- **Decision record**: If AGREE, append summary to `docs/decisions/` or `BACKLOG.md` (optional)
|
|
212
|
+
|
|
213
|
+
## Configuration
|
|
214
|
+
|
|
215
|
+
User overrides in `~/.roll/config.yaml`:
|
|
216
|
+
|
|
217
|
+
```yaml
|
|
218
|
+
peer:
|
|
219
|
+
max_rounds: 3
|
|
220
|
+
opt_out_seconds: 10
|
|
221
|
+
call_timeout: 180 # seconds per round; configure based on your API latency
|
|
222
|
+
fallback: file_mailbox # direct_cli | file_mailbox | auto
|
|
223
|
+
capability_map:
|
|
224
|
+
architecture: [claude, deepseek, kimi, pi]
|
|
225
|
+
security: [claude, deepseek, pi, kimi]
|
|
226
|
+
test: [codex, kimi, deepseek, claude]
|
|
227
|
+
refactor: [deepseek, kimi, claude, pi]
|
|
228
|
+
default: [deepseek, kimi, claude, pi]
|
|
229
|
+
adaptive:
|
|
230
|
+
streak_threshold: 3
|
|
231
|
+
min_samples: 3
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Limitations
|
|
235
|
+
|
|
236
|
+
1. **Reverse link reliability**: Direct CLI calls are preferred. Reliability varies by tool — see Peer Invocation Reference table. If a peer fails consistently, the adaptive streak tracker marks it `abandoned` and falls back to the next candidate. File mailbox (`~/.shared/roll/peer/mailbox/`) is the last-resort fallback.
|
|
237
|
+
- `deepseek serve --http` is the most reliable option when available — prefer it over direct `deepseek` CLI invocation.
|
|
238
|
+
- `codex exec` has known TTY/Ink issues in non-interactive environments; treat as low-priority fallback.
|
|
239
|
+
2. **Cost**: Every peer review consumes tokens on both sides. Only trigger for tasks where the cost of a wrong decision exceeds the cost of peer review. DeepSeek is the most cost-effective peer for general use.
|
|
240
|
+
3. **Context window**: Large project handoff cards may consume significant context. Keep file pointers concise.
|
|
241
|
+
4. **Tool differences**: Claude, DeepSeek, Kimi, Codex, and Pi interpret skills and AGENTS.md differently. The peer may apply the protocol slightly differently. This is expected and acceptable — the protocol is designed to tolerate variation.
|
|
@@ -12,6 +12,13 @@ description: |
|
|
|
12
12
|
|
|
13
13
|
> Follows the Architecture Constraints, Development Discipline, and Engineering Common Sense defined in the project AGENTS.md.
|
|
14
14
|
|
|
15
|
+
## Credits
|
|
16
|
+
|
|
17
|
+
The HV (Horizontal-Vertical) Analysis framework and the `references/schema.json`
|
|
18
|
+
used by this skill are derived from **[khazix-skills](https://github.com/KKKKhazix/khazix-skills)**
|
|
19
|
+
by 数字生命卡兹克 (Digital Life Khazix), used under the MIT License.
|
|
20
|
+
Copyright (c) 2026 数字生命卡兹克.
|
|
21
|
+
|
|
15
22
|
You are executing an HV Analysis deep research session. The final deliverable is a **professionally formatted PDF research report**.
|
|
16
23
|
|
|
17
24
|
## Prerequisites
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
// Copyright (c) 2026 数字生命卡兹克 (Digital Life Khazix)
|
|
2
|
+
// Released under the MIT License: https://github.com/KKKKhazix/khazix-skills/blob/main/LICENSE
|
|
3
|
+
// This file contains substantial portions of the Horizontal-Vertical Analysis Framework
|
|
4
|
+
// from khazix-skills (https://github.com/KKKKhazix/khazix-skills).
|
|
1
5
|
{
|
|
2
6
|
"$schema": "Horizontal-Vertical Analysis Framework",
|
|
3
7
|
"version": "1.0",
|