asia-aidlc 1.14.0 → 1.14.2
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/.aidlc-engine/cli/flow.py +377 -6
- package/.aidlc-engine/cli/flow_mcp.py +20 -5
- package/.aidlc-engine/docs/design.md +2 -2
- package/.aidlc-engine/skills/team-init/SKILL.md +6 -1
- package/.trae/commands/flow-audit.md +13 -0
- package/.trae/commands/flow-branch.md +16 -0
- package/.trae/commands/flow-e2e.md +6 -0
- package/.trae/commands/flow-gate.md +28 -0
- package/.trae/commands/flow-matrix.md +6 -0
- package/.trae/commands/flow-status.md +6 -0
- package/.trae/commands/flow-transition.md +13 -0
- package/.trae/commands/flow-validate.md +8 -0
- package/.trae/hooks/gate_hook.py +334 -0
- package/.trae/hooks.json +60 -0
- package/.trae/mcp.json +11 -0
- package/.trae/rules/flow-orchestration.md +128 -0
- package/.trae/skills/audit-logger/SKILL.md +60 -0
- package/.trae/skills/audit-logger/scripts/append.py +107 -0
- package/.trae/skills/compliance-checker/SKILL.md +40 -0
- package/.trae/skills/compliance-checker/scripts/check.py +32 -0
- package/.trae/skills/design-doc-generator/SKILL.md +38 -0
- package/.trae/skills/gate-verifier/SKILL.md +61 -0
- package/.trae/skills/gate-verifier/scripts/verify.py +228 -0
- package/.trae/skills/observation-doc-generator/SKILL.md +36 -0
- package/.trae/skills/openspec-executor/SKILL.md +61 -0
- package/.trae/skills/release-doc-generator/SKILL.md +38 -0
- package/.trae/skills/render-views/SKILL.md +73 -0
- package/.trae/skills/req-doc-generator/SKILL.md +60 -0
- package/.trae/skills/req-doc-generator/scripts/create_req.py +222 -0
- package/.trae/skills/state-validator/SKILL.md +47 -0
- package/.trae/skills/state-validator/scripts/validate.py +312 -0
- package/.trae/skills/task-split/SKILL.md +114 -0
- package/.trae/skills/task-split/scripts/tasks.py +511 -0
- package/.trae/skills/team-init/SKILL.md +99 -0
- package/.trae/skills/team-init/scripts/init_owners.py +614 -0
- package/.trae/skills/test-manager/SKILL.md +38 -0
- package/.trae/skills/traceability-updater/SKILL.md +34 -0
- package/.trae/skills/traceability-updater/scripts/update.py +98 -0
- package/CHANGELOG.md +68 -0
- package/README.md +106 -152
- package/package.json +5 -3
|
@@ -5,10 +5,14 @@
|
|
|
5
5
|
子命令:
|
|
6
6
|
init 从 example 生成 workflow-state.json 骨架 + 按目录规约创建仓库脚手架
|
|
7
7
|
(产物目录 / 根级登记册 / .gitignore 合并,全部幂等;不动 openspec/ 与 .ai-sdd/)
|
|
8
|
-
+ Trae 接入:.trae/mcp.json 合并、斜杠命令同步、
|
|
8
|
+
+ Trae 接入:.trae/mcp.json 合并、斜杠命令同步、14 个 Skill 复制到
|
|
9
9
|
.trae/skills(--skills-dir 可改目标,--no-skills 跳过;脚本兼容复制安装)
|
|
10
10
|
+ --git-hooks:安装 git 钩子,pull/切分支/commit 后自动重建 viewer 视图
|
|
11
11
|
+ --sync-skills:维护模式,只补装缺失的 Skill(已初始化的仓库也能跑)
|
|
12
|
+
**收尾会问「是否配置团队成员」**(TTY 交互;它决定门控审批人校验、视图「配置负责人」
|
|
13
|
+
列、日历按人筛选)。非 TTY / --no-input 下不提问,改为打印 [next] 待办清单——
|
|
14
|
+
Agent 据此在对话里问用户。一行带全:--team "张敏:产品负责人,…" --admin 张敏
|
|
15
|
+
(按角色自动推断阶段归属并跑一次 validate;未配完只报 [info],不影响退出码)
|
|
12
16
|
hooks 安装/查看/卸载 git 钩子(install / status / uninstall)——让 .aidlc-engine/viewer/
|
|
13
17
|
的派生视图保持最新:钩子只跑 `render.py --if-stale`(新鲜即跳过、不阻断 git);
|
|
14
18
|
自己的钩子可重复安装,他人同名钩子一律不覆盖
|
|
@@ -138,6 +142,21 @@ def _repo_root_from_state(state):
|
|
|
138
142
|
VIEW_OUT_NAME = {"calendar": "calendar.html", "console": "workflow-view.html", "trace": "trace.html"}
|
|
139
143
|
_RENDER_MOD = None # None=未尝试 / False=加载失败 / module=已加载
|
|
140
144
|
_RENDER_NOTE = {} # 最近一次自动重建的结果,供 CLI/MCP 回给用户
|
|
145
|
+
_PENDING_SETUP = {} # init 的交互式收尾(团队/钩子):由 main() 在汇总打印之后执行
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _exec_no_pycache(spec, mod):
|
|
149
|
+
"""执行模块但**不写 __pycache__**。
|
|
150
|
+
|
|
151
|
+
引擎脚本是"源码"而非依赖:在 .aidlc-engine/ 下留 .pyc 会污染仓库,
|
|
152
|
+
而且会被 _install_skills 连带复制进 .trae/skills/。
|
|
153
|
+
"""
|
|
154
|
+
prev = sys.dont_write_bytecode
|
|
155
|
+
sys.dont_write_bytecode = True
|
|
156
|
+
try:
|
|
157
|
+
spec.loader.exec_module(mod)
|
|
158
|
+
finally:
|
|
159
|
+
sys.dont_write_bytecode = prev
|
|
141
160
|
|
|
142
161
|
|
|
143
162
|
def _load_render(system=None):
|
|
@@ -153,7 +172,7 @@ def _load_render(system=None):
|
|
|
153
172
|
import importlib.util
|
|
154
173
|
spec = importlib.util.spec_from_file_location("aidlc_viewer_render", cand)
|
|
155
174
|
mod = importlib.util.module_from_spec(spec)
|
|
156
|
-
spec
|
|
175
|
+
_exec_no_pycache(spec, mod)
|
|
157
176
|
_RENDER_MOD = mod
|
|
158
177
|
except Exception as exc: # fail-open:渲染器坏了也不能拖垮 flow
|
|
159
178
|
LOG.warning("加载 viewer/render.py 失败:%s", exc)
|
|
@@ -332,8 +351,277 @@ GITIGNORE_ENTRIES = ["aidlc-data/traceability-matrix.md", "*.tmp", "aidlc-data/g
|
|
|
332
351
|
"aidlc-data/trace.html", "aidlc-data/req/"]
|
|
333
352
|
|
|
334
353
|
|
|
354
|
+
# ---------------- 团队配置(team-init 的**进程内**调用,同 _load_render 的理由) ----------------
|
|
355
|
+
# Pyodide(npm 包)下没有子进程,进程内 import 才两边都能用;team.json 与状态文件同目录。
|
|
356
|
+
_TEAM_MOD = None
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def _load_team_init(system=None):
|
|
360
|
+
"""惰性加载 <system>/skills/team-init/scripts/init_owners.py;失败返回 None。"""
|
|
361
|
+
global _TEAM_MOD
|
|
362
|
+
if _TEAM_MOD is not None:
|
|
363
|
+
return _TEAM_MOD or None
|
|
364
|
+
cand = os.path.join(os.path.abspath(system or DEFAULT_SYSTEM),
|
|
365
|
+
"skills", "team-init", "scripts", "init_owners.py")
|
|
366
|
+
if not os.path.exists(cand):
|
|
367
|
+
_TEAM_MOD = False
|
|
368
|
+
return None
|
|
369
|
+
try:
|
|
370
|
+
import importlib.util
|
|
371
|
+
spec = importlib.util.spec_from_file_location("aidlc_team_init", cand)
|
|
372
|
+
mod = importlib.util.module_from_spec(spec)
|
|
373
|
+
_exec_no_pycache(spec, mod)
|
|
374
|
+
_TEAM_MOD = mod
|
|
375
|
+
except Exception as exc: # fail-open
|
|
376
|
+
LOG.warning("加载 team-init 失败:%s", exc)
|
|
377
|
+
_TEAM_MOD = False
|
|
378
|
+
return None
|
|
379
|
+
return _TEAM_MOD
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def team_path_of(state):
|
|
383
|
+
return os.path.join(os.path.dirname(os.path.abspath(state)), "team.json")
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def team_configured(state):
|
|
387
|
+
"""team.json 是否存在且至少配了一个阶段负责人。"""
|
|
388
|
+
p = team_path_of(state)
|
|
389
|
+
if not os.path.exists(p):
|
|
390
|
+
return False
|
|
391
|
+
try:
|
|
392
|
+
with open(p, encoding="utf-8") as f:
|
|
393
|
+
team = json.load(f)
|
|
394
|
+
except (OSError, ValueError):
|
|
395
|
+
return False
|
|
396
|
+
return any((n.get("owner") or "").strip() for n in (team.get("nodes") or []))
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def run_team_init(system, argv_list):
|
|
400
|
+
"""跑一次 team-init 子命令(进程内、捕获 stdout+stderr)。返回 (ok, 输出文本)。"""
|
|
401
|
+
mod = _load_team_init(system)
|
|
402
|
+
if mod is None:
|
|
403
|
+
return False, "找不到 skills/team-init/scripts/init_owners.py"
|
|
404
|
+
import io
|
|
405
|
+
buf, real_out, real_err = io.StringIO(), sys.stdout, sys.stderr
|
|
406
|
+
try:
|
|
407
|
+
sys.stdout = sys.stderr = buf
|
|
408
|
+
try:
|
|
409
|
+
rc = mod.main(list(argv_list))
|
|
410
|
+
except SystemExit as exc:
|
|
411
|
+
rc = exc.code if isinstance(exc.code, int) else 0
|
|
412
|
+
except Exception as exc:
|
|
413
|
+
return False, "team-init 执行失败:%s" % exc
|
|
414
|
+
finally:
|
|
415
|
+
sys.stdout, sys.stderr = real_out, real_err
|
|
416
|
+
text = buf.getvalue().strip()
|
|
417
|
+
return (not isinstance(rc, int)) or rc == 0, text
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
ROLE_NODE_HINT = (
|
|
421
|
+
("产品", ("N0", "N1")), ("需求", ("N0", "N1")), ("项目", ("N0", "N1")),
|
|
422
|
+
("技术", ("N2",)), ("架构", ("N2", "N3")),
|
|
423
|
+
("研发", ("N3",)), ("开发", ("N3",)),
|
|
424
|
+
("测试", ("N4",)), ("qa", ("N4",)), ("质量", ("N4",)),
|
|
425
|
+
("交付", ("N5",)), ("发布", ("N5",)), ("运维", ("N5",)),
|
|
426
|
+
)
|
|
427
|
+
NODE_LABEL = (("N0", "任务受理"), ("N1", "需求生成"), ("N2", "方案生成"),
|
|
428
|
+
("N3", "代码生成"), ("N4", "测试执行"), ("N5", "交付发布"))
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def parse_members(text):
|
|
432
|
+
"""把「张敏:产品负责人 / 李强:技术负责人 / 王磊」解析成 [(名字, 角色)];换行与逗号都当分隔。"""
|
|
433
|
+
items = []
|
|
434
|
+
for chunk in re.split(r"[\n,,;;]+", text or ""):
|
|
435
|
+
chunk = chunk.strip()
|
|
436
|
+
if not chunk:
|
|
437
|
+
continue
|
|
438
|
+
name, _, role = chunk.partition(":")
|
|
439
|
+
if not role:
|
|
440
|
+
name, _, role = chunk.partition(":")
|
|
441
|
+
name, role = name.strip(), role.strip()
|
|
442
|
+
if name:
|
|
443
|
+
items.append((name, role))
|
|
444
|
+
return items
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
def suggest_owners(members):
|
|
448
|
+
"""按角色名猜阶段归属(只作**默认值**给用户确认,不静默落盘)。"""
|
|
449
|
+
owners = {}
|
|
450
|
+
for name, role in members:
|
|
451
|
+
low = (role or "").lower()
|
|
452
|
+
for kw, nodes in ROLE_NODE_HINT:
|
|
453
|
+
if kw in role or kw in low:
|
|
454
|
+
for nid in nodes:
|
|
455
|
+
owners.setdefault(nid, name)
|
|
456
|
+
break
|
|
457
|
+
return owners
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
def can_prompt():
|
|
461
|
+
"""当前环境能否**真的**交互提问。
|
|
462
|
+
|
|
463
|
+
Pyodide(npm 包 `aidlc`)下 stdin 是只读的 EOF 流:`input()` 直接抛 EOFError、`isatty()` 为 False,
|
|
464
|
+
而且 stdout 是**按行批处理**的(没带换行的提示语要等下一次 flush 才冒出来)。
|
|
465
|
+
所以那里不能提问——必须明确告知并给一行命令,绝不能"假装在等输入"。
|
|
466
|
+
"""
|
|
467
|
+
if os.environ.get("AIDLC_NPM", "").strip() == "1":
|
|
468
|
+
return False
|
|
469
|
+
try:
|
|
470
|
+
return bool(sys.stdin) and sys.stdin.isatty()
|
|
471
|
+
except Exception:
|
|
472
|
+
return False
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
NO_TTY_HINT = ("[hint] 当前环境不支持交互提问(Pyodide/npm 形态,或没有终端),已跳过提问。\n"
|
|
476
|
+
" 一步配好团队:flow init --team \"张敏:产品负责人,李强:技术负责人\" --admin 张敏\n"
|
|
477
|
+
" (按角色自动推断阶段归属;也可随时补:skill team-init set --node N3 --owner 王磊)")
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
def _ask(prompt, default=""):
|
|
481
|
+
"""读一行输入。返回 None 表示**读不到输入**(EOF / 无终端 / Pyodide)——与「按回车用默认值」区分开。
|
|
482
|
+
|
|
483
|
+
提示语单独 write + flush:stdout 若被按行批处理(Pyodide 的 batched 模式),
|
|
484
|
+
交给 input(prompt) 的提示语不会及时显示。
|
|
485
|
+
"""
|
|
486
|
+
shown = (" [%s]" % default) if default else ""
|
|
487
|
+
try:
|
|
488
|
+
sys.stdout.write("%s%s: " % (prompt, shown))
|
|
489
|
+
sys.stdout.flush()
|
|
490
|
+
except Exception:
|
|
491
|
+
pass
|
|
492
|
+
try:
|
|
493
|
+
line = input()
|
|
494
|
+
except (EOFError, KeyboardInterrupt, OSError):
|
|
495
|
+
print()
|
|
496
|
+
return None
|
|
497
|
+
return line.strip() or default
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
def setup_team_wizard(system, state):
|
|
501
|
+
"""交互式配团队(最简版:单负责人)。返回要打印的文本。
|
|
502
|
+
|
|
503
|
+
**由 main() 在 init 汇总打印之后调用**——否则提问会插在汇总前面,看起来很乱。
|
|
504
|
+
"""
|
|
505
|
+
data_root = os.path.dirname(os.path.abspath(state))
|
|
506
|
+
tpath = team_path_of(state)
|
|
507
|
+
try:
|
|
508
|
+
project = (read_state(state).get("meta") or {}).get("project") or ""
|
|
509
|
+
except Exception:
|
|
510
|
+
project = ""
|
|
511
|
+
project = project or os.path.basename(os.path.dirname(data_root)) or "未命名项目"
|
|
512
|
+
|
|
513
|
+
print("\n" + "─" * 8 + " 下一步:配置团队成员 " + "─" * 8)
|
|
514
|
+
print(" 它决定:门控审批人校验、视图里的「配置负责人」、日历按人筛选。")
|
|
515
|
+
print(" [1] 现在配(约 30 秒) [2] 先跳过(随时可配:flow init --team \"张敏:产品负责人\")")
|
|
516
|
+
pick = _ask("选择", "1")
|
|
517
|
+
if pick is None: # 读不到输入:别假装在等,直接给一行命令
|
|
518
|
+
return NO_TTY_HINT
|
|
519
|
+
if pick not in ("1", "y", "Y", "yes", ""):
|
|
520
|
+
return " 已跳过。之后可用:flow init --team \"张敏:产品负责人,…\" --admin 张敏"
|
|
521
|
+
|
|
522
|
+
proj = _ask("项目名", project)
|
|
523
|
+
if proj is None:
|
|
524
|
+
return NO_TTY_HINT
|
|
525
|
+
admin = _ask("管理员 / 产品负责人(填入 N0/N1 与 req_review)", "")
|
|
526
|
+
if admin is None:
|
|
527
|
+
return NO_TTY_HINT
|
|
528
|
+
print("团队名单(每行一条,格式 名字:角色;空行结束)")
|
|
529
|
+
roster = []
|
|
530
|
+
while True:
|
|
531
|
+
try:
|
|
532
|
+
ln = input(" > ").strip()
|
|
533
|
+
except (EOFError, KeyboardInterrupt, OSError):
|
|
534
|
+
print()
|
|
535
|
+
break
|
|
536
|
+
if not ln:
|
|
537
|
+
break
|
|
538
|
+
roster += parse_members(ln)
|
|
539
|
+
if admin and all(n != admin for n, _ in roster):
|
|
540
|
+
roster.insert(0, (admin, "项目管理员"))
|
|
541
|
+
if not roster:
|
|
542
|
+
return " 名单为空,已跳过。随时可配:flow init --team \"张敏:产品负责人\" --admin 张敏"
|
|
543
|
+
|
|
544
|
+
sug = suggest_owners(roster)
|
|
545
|
+
print("六个阶段分别归谁?(格式 N0=张敏,N1=张敏,…;回车用下面的建议值;留空 = 暂不指定)")
|
|
546
|
+
for nid, label in NODE_LABEL:
|
|
547
|
+
print(" %s %-6s → %s" % (nid, label, sug.get(nid) or "(待定)"))
|
|
548
|
+
mapping = _ask("阶段归属", ",".join("%s=%s" % (nid, sug.get(nid, "")) for nid, _ in NODE_LABEL))
|
|
549
|
+
|
|
550
|
+
base = ["--file", tpath, "--state", os.path.abspath(state)]
|
|
551
|
+
init_argv = base + (["--project", proj] if proj else [])
|
|
552
|
+
outs = []
|
|
553
|
+
if not os.path.exists(tpath):
|
|
554
|
+
ok, t = run_team_init(system, ["init"] + init_argv +
|
|
555
|
+
(["--admin", admin] if admin else ["--force"]))
|
|
556
|
+
outs.append(t)
|
|
557
|
+
if not ok:
|
|
558
|
+
return "\n".join(outs + [" [warn] 初始化 team.json 失败,可重试:flow init --team \"…\""])
|
|
559
|
+
args = ["add-members"] + base
|
|
560
|
+
for name, role in roster:
|
|
561
|
+
args += ["--member", ("%s:%s" % (name, role)) if role else name]
|
|
562
|
+
ok, t = run_team_init(system, args)
|
|
563
|
+
outs.append(t)
|
|
564
|
+
if not ok:
|
|
565
|
+
return "\n".join(outs + [" [warn] 写入成员失败,已保留已写入的部分"])
|
|
566
|
+
pairs = [p.strip() for p in mapping.split(",") if "=" in p and p.split("=", 1)[1].strip()]
|
|
567
|
+
if pairs:
|
|
568
|
+
ok, t = run_team_init(system, ["apply"] + base + sum([["--pair", p] for p in pairs], []))
|
|
569
|
+
outs.append(t)
|
|
570
|
+
okv, tv = run_team_init(system, ["validate"] + base)
|
|
571
|
+
outs.append(" 项目:%s | 管理员:%s | 成员:%s" %
|
|
572
|
+
(proj, admin or "(未指定)", "、".join(n for n, _ in roster)))
|
|
573
|
+
outs += _team_report(tv)
|
|
574
|
+
outs.append(" 随时可改:flow init --team \"…\" / 直接编辑 aidlc-data/team.json")
|
|
575
|
+
return "\n".join(outs)
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
def _team_report(validate_text):
|
|
579
|
+
"""把 team-init validate 的结果整理成「硬问题 / 待补 / 通过」,并给出可执行的补救命令。
|
|
580
|
+
|
|
581
|
+
注意:**不要把 validate 的原始 [FAIL] 行直接拼进 init 的输出**——init 本身是成功的,
|
|
582
|
+
带着 [FAIL] 会让 `flow init` 退出码变成 1,CI 与 Agent 的错误处理都会被误触发。
|
|
583
|
+
"""
|
|
584
|
+
problems = [ln.strip()[2:].strip() for ln in (validate_text or "").splitlines()
|
|
585
|
+
if ln.strip().startswith("- ")]
|
|
586
|
+
hard = [p for p in problems if ("DUTY_CONFLICT" in p or "不在成员名单" in p)]
|
|
587
|
+
todo = [p for p in problems if p not in hard]
|
|
588
|
+
lines = []
|
|
589
|
+
if hard:
|
|
590
|
+
lines.append("[warn] 团队配置有硬问题(必须修):")
|
|
591
|
+
lines += [" - %s" % p for p in hard]
|
|
592
|
+
if todo:
|
|
593
|
+
lines.append("[info] 还有 %d 项待补(不影响现在继续用):" % len(todo))
|
|
594
|
+
lines += [" - %s" % p for p in todo]
|
|
595
|
+
if any("代码评审" in p or "code_review" in p for p in todo):
|
|
596
|
+
lines.append(" 代码评审需要**独立于 N3 负责人**的评审人(作者≠评审人),例如:")
|
|
597
|
+
lines.append(" aidlc skill team-init set --gate code_review --approver <独立的人>")
|
|
598
|
+
if any("未配置负责人" in p for p in todo):
|
|
599
|
+
lines.append(" 补齐阶段负责人:flow init --team \"名字:角色,…\""
|
|
600
|
+
"(或直接编辑 aidlc-data/team.json)")
|
|
601
|
+
if not hard and not todo:
|
|
602
|
+
lines.append("[ok] 团队配置校验通过")
|
|
603
|
+
return lines
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
def next_steps_block(state, team_done=False, hooks_done=False):
|
|
607
|
+
"""init 之后的待办清单:人类可读、Agent 可解析(`[next] key=value`)。"""
|
|
608
|
+
lines = []
|
|
609
|
+
if not team_done and not team_configured(state):
|
|
610
|
+
lines += [
|
|
611
|
+
"[next] step=team 状态=未配置 影响=门控审批人校验/视图「配置负责人」列/日历按人筛选",
|
|
612
|
+
"[next] step=team 一条命令配好=flow init --team \"张敏:产品负责人,李强:技术负责人\" --admin 张敏",
|
|
613
|
+
"[next] step=team 跳过=什么都不做即可(不影响 init 结果;随时可再配)",
|
|
614
|
+
]
|
|
615
|
+
if not hooks_done:
|
|
616
|
+
lines.append("[next] step=hooks 状态=未安装 影响=commit/切分支后视图不自动重建 "
|
|
617
|
+
"cmd=\"flow init --git-hooks\"")
|
|
618
|
+
if lines:
|
|
619
|
+
lines.append("[next] step=view cmd=\"flow view\"(渲染日历 + 每个需求一个文件,并给出可点开的路径)")
|
|
620
|
+
return "\n".join(lines)
|
|
621
|
+
|
|
622
|
+
|
|
335
623
|
def do_init(system, state, skills_dir=".trae/skills", no_skills=False, empty=False, git_hooks=False,
|
|
336
|
-
sync_skills=False):
|
|
624
|
+
sync_skills=False, team="", admin="", no_input=False):
|
|
337
625
|
src = os.path.join(STATE_DIR, "workflow-state.example.json")
|
|
338
626
|
# 钩子装到状态文件所在仓库(脚本自身会 git rev-parse 定位仓库根,传目录即可)
|
|
339
627
|
hook_start = os.path.dirname(os.path.abspath(state)) or os.getcwd()
|
|
@@ -390,6 +678,55 @@ def do_init(system, state, skills_dir=".trae/skills", no_skills=False, empty=Fal
|
|
|
390
678
|
out += "\n" + dep
|
|
391
679
|
if hint:
|
|
392
680
|
out += hint
|
|
681
|
+
|
|
682
|
+
# ---- 收尾:团队 + 下一步(让「init 完不知道该干嘛」这件事消失)----
|
|
683
|
+
global _PENDING_SETUP
|
|
684
|
+
team_done = team_configured(state)
|
|
685
|
+
if team.strip(): # 一行带全:调用方已知道答案(Agent / 脚本)
|
|
686
|
+
proj = ""
|
|
687
|
+
try:
|
|
688
|
+
proj = (data.get("meta") or {}).get("project") or ""
|
|
689
|
+
except Exception:
|
|
690
|
+
proj = ""
|
|
691
|
+
base = ["--file", team_path_of(state), "--state", os.path.abspath(state)]
|
|
692
|
+
init_argv = base + (["--project", proj] if proj else [])
|
|
693
|
+
members = parse_members(team)
|
|
694
|
+
if not team_done:
|
|
695
|
+
run_team_init(system, ["init"] + init_argv + (["--admin", admin] if admin else ["--force"]))
|
|
696
|
+
args2 = ["add-members"] + base
|
|
697
|
+
for name, role in members:
|
|
698
|
+
args2 += ["--member", ("%s:%s" % (name, role)) if role else name]
|
|
699
|
+
ok1, t2 = run_team_init(system, args2)
|
|
700
|
+
sug = suggest_owners(members)
|
|
701
|
+
pairs = [["--pair", "%s=%s" % (nid, sug[nid])] for nid, _ in NODE_LABEL if sug.get(nid)]
|
|
702
|
+
ok2, t3 = (run_team_init(system, ["apply"] + base + sum(pairs, [])) if pairs else (True, ""))
|
|
703
|
+
okv, t4 = run_team_init(system, ["validate"] + base)
|
|
704
|
+
out += "\n" + "\n".join(x for x in (t2, t3) if x)
|
|
705
|
+
out += "\n" + "\n".join(_team_report(t4))
|
|
706
|
+
if pairs:
|
|
707
|
+
out += "\n 阶段归属按角色推断:" + "、".join(
|
|
708
|
+
"%s=%s" % (p[1].split("=")[0], p[1].split("=")[1]) for p in pairs)
|
|
709
|
+
team_done = team_configured(state)
|
|
710
|
+
if admin:
|
|
711
|
+
out += "\nOK: 管理员 %s 已填入 N0/N1 与 req_review" % admin
|
|
712
|
+
if not (ok1 and ok2):
|
|
713
|
+
out += "\n[warn] 写入成员/阶段归属未成功,可重试:flow init --team \"…\" --admin <名字>"
|
|
714
|
+
elif not team_done and not no_input and can_prompt():
|
|
715
|
+
# 提问放在 main() 打印完汇总之后(否则提示会插在 OK 行前面)
|
|
716
|
+
_PENDING_SETUP = {"system": system, "state": state}
|
|
717
|
+
elif not team_done and not no_input:
|
|
718
|
+
# Pyodide / 无终端:明确说清"不会提问",并给一行命令——
|
|
719
|
+
# 否则用户会盯着屏幕等一个永远不会来的提示(曾如此)
|
|
720
|
+
out += "\n" + NO_TTY_HINT
|
|
721
|
+
|
|
722
|
+
if not team.strip() and can_prompt() and not no_input:
|
|
723
|
+
if not git_hooks:
|
|
724
|
+
_PENDING_SETUP.setdefault("hooks_dir", hook_start)
|
|
725
|
+
|
|
726
|
+
blk = next_steps_block(state, team_done=team_done or bool(team.strip()),
|
|
727
|
+
hooks_done=git_hooks or bool(_PENDING_SETUP.get("hooks_dir")))
|
|
728
|
+
if blk:
|
|
729
|
+
out += "\n\n下一步:\n" + blk
|
|
393
730
|
return out
|
|
394
731
|
|
|
395
732
|
|
|
@@ -568,7 +905,7 @@ def _install_skills(system, root, target_rel=".trae/skills"):
|
|
|
568
905
|
if os.path.exists(dst):
|
|
569
906
|
skipped.append(name)
|
|
570
907
|
continue
|
|
571
|
-
shutil.copytree(src, dst)
|
|
908
|
+
shutil.copytree(src, dst, ignore=shutil.ignore_patterns("__pycache__", "*.pyc", "*.pyo"))
|
|
572
909
|
copied.append(name)
|
|
573
910
|
msg = "技能 %d 个 → %s(%s)" % (len(copied), target_rel, ",".join(copied) or "无新增")
|
|
574
911
|
if skipped:
|
|
@@ -1703,7 +2040,7 @@ def main():
|
|
|
1703
2040
|
ap = argparse.ArgumentParser(prog="flow", description="OpenSpec+AI-DLC 研发流程统一 CLI")
|
|
1704
2041
|
sub = ap.add_subparsers(dest="cmd", required=True)
|
|
1705
2042
|
|
|
1706
|
-
p = sub.add_parser("init", help="
|
|
2043
|
+
p = sub.add_parser("init", help="初始化:状态骨架 + 脚手架 + Trae 接入件/技能;结束时会询问是否配团队")
|
|
1707
2044
|
p.add_argument("--state", default=DEFAULT_STATE)
|
|
1708
2045
|
p.add_argument("--system", default=DEFAULT_SYSTEM, help=".aidlc-engine 引擎目录(含 skills/ 与 state/)")
|
|
1709
2046
|
p.add_argument("--skills-dir", default=".trae/skills",
|
|
@@ -1716,6 +2053,12 @@ def main():
|
|
|
1716
2053
|
p.add_argument("--sync-skills", dest="sync_skills", action="store_true",
|
|
1717
2054
|
help="只补装缺失的 Skill 到 --skills-dir(已初始化的仓库也能跑;"
|
|
1718
2055
|
"新增 Skill 后用它在 .trae/skills 落地,不动状态与脚手架)")
|
|
2056
|
+
p.add_argument("--team", default="",
|
|
2057
|
+
help="一行配好团队:\"张敏:产品负责人,李强:技术负责人\"(逗号/换行分隔;"
|
|
2058
|
+
"按角色自动猜阶段归属,配完跑一次 validate)")
|
|
2059
|
+
p.add_argument("--admin", default="", help="项目管理员(填入 N0/N1 负责人与 req_review 审批人)")
|
|
2060
|
+
p.add_argument("--no-input", dest="no_input", action="store_true",
|
|
2061
|
+
help="不提问(CI/无头环境;只打印 [next] 待办清单,不读 stdin)")
|
|
1719
2062
|
|
|
1720
2063
|
p = sub.add_parser("hooks", help="git 钩子:让 .aidlc-engine/viewer/ 派生视图保持实时")
|
|
1721
2064
|
hsub = p.add_subparsers(dest="hooks_cmd", required=True)
|
|
@@ -1863,7 +2206,8 @@ def main():
|
|
|
1863
2206
|
|
|
1864
2207
|
dispatch = {
|
|
1865
2208
|
"init": lambda: do_init(system, a.state, a.skills_dir, a.no_skills, a.empty, a.git_hooks,
|
|
1866
|
-
a.sync_skills),
|
|
2209
|
+
a.sync_skills, getattr(a, "team", ""), getattr(a, "admin", ""),
|
|
2210
|
+
getattr(a, "no_input", False)),
|
|
1867
2211
|
"hooks install": lambda: install_viewer_hooks(os.getcwd()),
|
|
1868
2212
|
"hooks status": lambda: viewer_hooks_status(os.getcwd()),
|
|
1869
2213
|
"hooks uninstall": lambda: uninstall_viewer_hooks(os.getcwd()),
|
|
@@ -1902,6 +2246,33 @@ def main():
|
|
|
1902
2246
|
if note:
|
|
1903
2247
|
out = (out.rstrip() + "\n\n" + note) if out else note
|
|
1904
2248
|
print(out)
|
|
2249
|
+
|
|
2250
|
+
# init 的交互式收尾放在汇总之后:否则提问会插在 OK 行前面,读起来是乱的
|
|
2251
|
+
global _PENDING_SETUP
|
|
2252
|
+
if _PENDING_SETUP.get("system"):
|
|
2253
|
+
st = _PENDING_SETUP.get("state")
|
|
2254
|
+
try:
|
|
2255
|
+
print(setup_team_wizard(_PENDING_SETUP["system"], st))
|
|
2256
|
+
except (EOFError, KeyboardInterrupt):
|
|
2257
|
+
print("\n 已取消。随时可配:flow init --team \"张敏:产品负责人\" --admin 张敏")
|
|
2258
|
+
except Exception as exc: # 向导失败绝不拖垮 init
|
|
2259
|
+
LOG.warning("配团队向导失败:%s", exc)
|
|
2260
|
+
print(" [warn] 交互式配置团队失败(%s);可改用:"
|
|
2261
|
+
"flow init --team \"张敏:产品负责人\" --admin 张敏" % exc)
|
|
2262
|
+
else:
|
|
2263
|
+
_auto_render(st) # 团队变了,让视图跟上
|
|
2264
|
+
extra = view_note()
|
|
2265
|
+
if extra:
|
|
2266
|
+
print("\n" + extra)
|
|
2267
|
+
if _PENDING_SETUP.get("hooks_dir"):
|
|
2268
|
+
ans = _ask("是否安装 git 钩子(commit/切分支后自动重建视图)", "y")
|
|
2269
|
+
if ans is None:
|
|
2270
|
+
print(" 读不到输入,已跳过。之后可装:flow init --git-hooks / flow hooks install")
|
|
2271
|
+
elif str(ans).lower() in ("y", "yes", "1"):
|
|
2272
|
+
print("OK: " + install_viewer_hooks(_PENDING_SETUP["hooks_dir"]))
|
|
2273
|
+
else:
|
|
2274
|
+
print(" 已跳过。之后可装:flow init --git-hooks / flow hooks install")
|
|
2275
|
+
_PENDING_SETUP = {}
|
|
1905
2276
|
# 进程退出码反映判定结果(供 shell/CI 消费):失败标记 → 1
|
|
1906
2277
|
fail_markers = (
|
|
1907
2278
|
"[FAIL]", "[REJECT]", "[ERROR]", "Traceback",
|
|
@@ -40,11 +40,17 @@ DEFAULT_STATE = None # 由调用方 arguments 传 state,缺省用 cwd
|
|
|
40
40
|
def resolve_state(arg):
|
|
41
41
|
if arg and os.path.exists(arg):
|
|
42
42
|
return arg
|
|
43
|
-
# 两目录布局优先:cwd/aidlc-data/workflow-state.json
|
|
43
|
+
# 两目录布局优先:cwd/aidlc-data/workflow-state.json
|
|
44
44
|
new = os.path.join(os.getcwd(), flow.DATA_DIR, "workflow-state.json")
|
|
45
45
|
if os.path.exists(new):
|
|
46
46
|
return new
|
|
47
|
-
|
|
47
|
+
# 旧布局(根目录平铺)**只在真的存在时**才用;否则一律走两目录布局——
|
|
48
|
+
# 否则全新项目里 flow_init 会把 workflow-state.json / team.json 建到仓库根,
|
|
49
|
+
# 与 `flow init` 的缺省路径(aidlc-data/)不一致。
|
|
50
|
+
legacy = os.path.join(os.getcwd(), "workflow-state.json")
|
|
51
|
+
if os.path.exists(legacy):
|
|
52
|
+
return legacy
|
|
53
|
+
return new
|
|
48
54
|
|
|
49
55
|
|
|
50
56
|
TOOLS = [
|
|
@@ -81,10 +87,17 @@ TOOLS = [
|
|
|
81
87
|
"required": ["actor", "action", "entity", "result"]}},
|
|
82
88
|
{"name": "flow_matrix", "description": "渲染追溯矩阵(输出到状态文件同目录 traceability-matrix.md)",
|
|
83
89
|
"inputSchema": {"type": "object", "properties": {"state": {"type": "string"}}}},
|
|
84
|
-
{"name": "flow_init", "description": "初始化 workflow-state.json + 仓库脚手架 + Trae
|
|
90
|
+
{"name": "flow_init", "description": "初始化 workflow-state.json + 仓库脚手架 + Trae 接入件/技能/编排规则安装。"
|
|
91
|
+
"【初始化后请主动问用户是否配置团队成员】——它决定门控审批人校验、视图里的「配置负责人」列、日历按人筛选。"
|
|
92
|
+
"用户给了名单就用 team=\"张敏:产品负责人,李强:技术负责人\" + admin 一步配好(会按角色自动推断阶段归属);"
|
|
93
|
+
"用户说「先跳过」就直接不传(返回值里的 [next] 清单会说明后果与随时补配的命令),不要反复追问。"
|
|
94
|
+
"注意:本工具**不会**在返回前提问(MCP 无人机交互),提问由你在对话里完成。",
|
|
85
95
|
"inputSchema": {"type": "object",
|
|
86
96
|
"properties": {"state": {"type": "string"},
|
|
87
|
-
"empty": {"type": "boolean", "description": "空白初始化:清空示例实体,仅保留骨架"}
|
|
97
|
+
"empty": {"type": "boolean", "description": "空白初始化:清空示例实体,仅保留骨架"},
|
|
98
|
+
"team": {"type": "string",
|
|
99
|
+
"description": "团队成员名单,格式 \"名字:角色,名字:角色\"(用户确认后传入;不传则只输出待办)"},
|
|
100
|
+
"admin": {"type": "string", "description": "项目管理员(填入 N0/N1 负责人与 req_review 审批人)"}}}},
|
|
88
101
|
{"name": "flow_branch_create", "description": "建执行分支 feature/chg-<NNN> + 登记 base_commit/owned + 审计 branch.create(分支做容器,状态推进仍走 transition)",
|
|
89
102
|
"inputSchema": {"type": "object",
|
|
90
103
|
"properties": {"chg": {"type": "string", "description": "change 编号,如 CHG-003"},
|
|
@@ -147,7 +160,9 @@ HANDLERS = {
|
|
|
147
160
|
a["actor"], a["action"], a["entity"], a["result"], a.get("detail", "")),
|
|
148
161
|
"flow_matrix": lambda a: flow.do_matrix(DEFAULT_SYSTEM, resolve_state(a.get("state"))),
|
|
149
162
|
"flow_init": lambda a: flow.do_init(DEFAULT_SYSTEM, resolve_state(a.get("state")),
|
|
150
|
-
empty=bool(a.get("empty"))
|
|
163
|
+
empty=bool(a.get("empty")),
|
|
164
|
+
team=a.get("team", ""), admin=a.get("admin", ""),
|
|
165
|
+
no_input=True),
|
|
151
166
|
"flow_branch_create": lambda a: flow.do_branch_create(DEFAULT_SYSTEM, resolve_state(a.get("state")),
|
|
152
167
|
a["chg"], a.get("actor", "编排Agent")),
|
|
153
168
|
"flow_branch_merge": lambda a: flow.do_branch_merge(DEFAULT_SYSTEM, resolve_state(a.get("state")),
|
|
@@ -369,7 +369,7 @@ hook 一览(全部 fail-open:读盘失败放行,硬校验由 flow CLI 兜
|
|
|
369
369
|
|
|
370
370
|
| 子命令 | 行为 | 审计 |
|
|
371
371
|
|---|---|---|
|
|
372
|
-
| `flow init` | 从 example 生成 workflow-state.json 骨架 + 仓库脚手架 + Trae 接入安装。`_scaffold`:按目录规约创建 10 个产物目录;`risk-register.md`/`dependency-map.md` 模板落仓库根;派生状态条目合并进 `.gitignore`(逐行去重)。`_install_trae`:`.aidlc-engine/cli/.trae/mcp.json` 合并到项目根 `.trae/`(保留既有 server;utf-8-sig 兼容 BOM;python 路径含空格自动改写绝对路径)、斜杠命令覆盖式同步。`_install_agent_rule`:编排 Agent 提示词写入 `.trae/rules/flow-orchestration.md`(alwaysApply,已存在跳过)。`_install_skills`:14 个 Skill 复制到 `.trae/skills/`(默认,`--skills-dir` 可改、`--no-skills` 跳过;已存在跳过)。`--empty` 空白初始化(清空示例实体)。init 检测 jsonschema 缺失并提示;flow.py/flow_mcp.py 启动时 reconfigure stdout/stderr 为 UTF-8(消除 GBK 乱码)。全部幂等;不创建 `openspec/` 与 `.ai-sdd/`(SDD 工作区层)。复制安装的脚本(verify/validate/append)以 `_resolve_state_dir()` 解析 state:`__file__` 上溯 → `FLOW_SYSTEM_DIR` 环境变量 → cwd 向上查找 `.aidlc-engine/state`。`--git-hooks` 顺带安装 viewer
|
|
372
|
+
| `flow init` | 从 example 生成 workflow-state.json 骨架 + 仓库脚手架 + Trae 接入安装。`_scaffold`:按目录规约创建 10 个产物目录;`risk-register.md`/`dependency-map.md` 模板落仓库根;派生状态条目合并进 `.gitignore`(逐行去重)。`_install_trae`:`.aidlc-engine/cli/.trae/mcp.json` 合并到项目根 `.trae/`(保留既有 server;utf-8-sig 兼容 BOM;python 路径含空格自动改写绝对路径)、斜杠命令覆盖式同步。`_install_agent_rule`:编排 Agent 提示词写入 `.trae/rules/flow-orchestration.md`(alwaysApply,已存在跳过)。`_install_skills`:14 个 Skill 复制到 `.trae/skills/`(默认,`--skills-dir` 可改、`--no-skills` 跳过;已存在跳过)。`--empty` 空白初始化(清空示例实体)。init 检测 jsonschema 缺失并提示;flow.py/flow_mcp.py 启动时 reconfigure stdout/stderr 为 UTF-8(消除 GBK 乱码)。全部幂等;不创建 `openspec/` 与 `.ai-sdd/`(SDD 工作区层)。复制安装的脚本(verify/validate/append)以 `_resolve_state_dir()` 解析 state:`__file__` 上溯 → `FLOW_SYSTEM_DIR` 环境变量 → cwd 向上查找 `.aidlc-engine/state`。`--git-hooks` 顺带安装 viewer 钩子(见下)。**收尾询问团队**:TTY 下问「是否配置团队成员」(`_PENDING_SETUP` 延后到 main() 打印完汇总再交互,避免提问插在 OK 行前面),走 `setup_team_wizard()` → 进程内调 team-init 的 `init/add-members/apply/validate`;非 TTY(Agent/CI/管道)或 `--no-input` **绝不读 stdin**,改为输出 `[next] step=…` 结构化待办,由编排 Agent 在对话里问用户。`--team "名字:角色,…" --admin X` 一行带全(按角色经 `ROLE_NODE_HINT` 推断阶段归属)。**汇报分级**:`_team_report()` 把 validate 结果分成「硬问题(DUTY_CONFLICT/不在成员名单)」与「待补(未配置负责人/审批人)」,后者只报 `[info]`——**不把 validate 的原始 `[FAIL]` 拼进 init 输出**,否则 init 明明成功也会被 `fail_markers` 判成退出码 1。`code_review` 需独立于 N3 负责人的评审人,init 猜不出来,故给出补救命令。**维护模式**:`--git-hooks` / `--sync-skills` 在状态文件已存在时不再 `[FAIL]` 退出,而是打印 `[skip] … 不覆盖(维护模式)` 并只执行所请求的安装动作(补钩子 / 补 Skill),供已初始化仓库补装新 Skill。`_install_skills` 复制时忽略 `__pycache__`/`*.pyc` | 无 |
|
|
373
373
|
| `flow hooks install/status/uninstall` | **viewer 派生视图实时化**(`.aidlc-engine/viewer/README.md`「让视图保持最新」):把 `render.py --if-stale` 挂到 `post-merge`/`post-checkout`/`post-commit`/`post-rewrite`,pull / 切分支 / commit / rebase 后自动重建视图。脚本为 POSIX sh(Git for Windows 自带 sh 执行),内容恒为 `_viewer_hook_script()` 生成并带 `aidlc-viewer-hook` 标记。契约:**幂等**(本工具的钩子覆盖更新,他人同名钩子一律跳过不覆盖)、**fail-open**(任何失败都放行 git,末尾恒 `exit 0`)、**新鲜即跳过**(`--if-stale`)、**无真相不渲染**(`aidlc-data/workflow-state.json` 缺失时不动作,避免 render.py 回退样例快照污染仓库)、`post-checkout` 仅 `$3=1` 分支切换触发;逃生开关 `AIDLC_SKIP_VIEWER_HOOK=1`、`AIDLC_VIEWER_FORCE=1`、追加参数 `AIDLC_VIEWER_HOOK_ARGS`。钩子目录尊重 `core.hooksPath`,`.git` 为文件(worktree/submodule)按 `gitdir:` 解析;git 不可用时向上查找 `.git` 降级 | 无 |
|
|
374
374
|
| `flow status` | 输出需求看板/实体统计/覆盖率 | 无 |
|
|
375
375
|
| `flow view` | **交付可视化视图的唯一入口**:`kind` ∈ calendar(默认,研发日历 + 每个需求一个文件)/ console / trace。渲染后输出**可点开的绝对路径**(+ 单需求文件目录),`--open` 用系统默认程序打开;**只产出静态 HTML,不起任何服务**(UI 状态存 URL hash,刷新/分享不丢筛选)。与 MCP `flow_render`、Skill `render-views` 同源(都走 `do_view()`)。**状态写盘后自动重建**:`write_state()` 末尾调 `render_views(if_stale=True)`(幂等 + fail-open + 静默),并在命令输出末尾附 `[视图]` 提示行;`AIDLC_RENDER=off` 关闭 | 无 |
|
|
@@ -406,7 +406,7 @@ MCP stdio Server(JSON-RPC 2.0),暴露 14 个工具供 Trae 调用:
|
|
|
406
406
|
| `flow_compliance` | do_compliance |
|
|
407
407
|
| `flow_audit` | do_audit |
|
|
408
408
|
| `flow_matrix` | do_matrix |
|
|
409
|
-
| `flow_init` | do_init |
|
|
409
|
+
| `flow_init` | do_init。除 `empty` 外新增 **`team` / `admin`**:用户在对话里确认名单后一步配好(MCP 侧强制 `no_input=True`——**绝不在返回前提问**,提问由 Agent 在对话里完成;不传 `team` 则返回值带 `[next]` 待办清单,含「初始化后请主动问用户是否配团队」的触发指引)。`resolve_state` 两目录布局优先,旧布局**只在真的存在时才用**,避免全新项目把数据建到仓库根 |
|
|
410
410
|
| `flow_branch_create` / `flow_branch_merge` / `flow_branch_discard` | do_branch_create/merge/discard |
|
|
411
411
|
| `flow_req_create` | do_req_create |
|
|
412
412
|
| `flow_e2e` | do_e2e |
|
|
@@ -11,11 +11,16 @@ description: 初始化与维护「六阶段节点负责人 + 四门控审批人
|
|
|
11
11
|
|
|
12
12
|
| 场景 | 入口 |
|
|
13
13
|
|---|---|
|
|
14
|
+
| **`flow init` 收尾时被问到** | init 会主动问「是否配置团队成员」(TTY 交互;非 TTY 打印 `[next]` 待办让编排 Agent 在对话里问)。这时就是本 Skill 的活 |
|
|
14
15
|
| 项目开工,一次把团队配好 | 对话:"初始化团队,管理员张敏,方案李强,开发王磊,测试赵倩,发布陈昊" |
|
|
15
16
|
| 中途换人 | 对话:"把测试负责人改成赵倩" / "代码评审加上赵倩" |
|
|
16
17
|
| 查看现状 | 对话:"现在谁负责发布?" → `list` |
|
|
17
18
|
| 开工前体检 | 对话:"负责人配齐了吗?" → `validate` |
|
|
18
|
-
| 无对话环境(CI / 脚本 / 手工) | 直接跑下面的 CLI |
|
|
19
|
+
| 无对话环境(CI / 脚本 / 手工) | 直接跑下面的 CLI;Agent 已拿到名单时可用 `flow init --team "名字:角色,…" --admin X` 一步配好 |
|
|
20
|
+
|
|
21
|
+
> **多人**:当前模型是**每个阶段一个负责人**(`nodes[].owner` 单值);门控审批人已经是列表
|
|
22
|
+
> (`--approver` 可多次)。多负责人(`owners` 列表)是规划中的改动,尚未实现——需要时先用
|
|
23
|
+
> 门控审批人(可多人)表达"这条事多人参与"。
|
|
19
24
|
|
|
20
25
|
## 六个节点 / 四个门控(固定,来自 `directory-convention.md` 与 `agent-roles.md`)
|
|
21
26
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: flow-audit
|
|
3
|
+
description: 追加审计日志(append-only,action 必须属于封闭事件注册表)
|
|
4
|
+
argument-hint: --actor <执行者> --action <注册表事件> --entity <实体> --result ok|blocked|failed
|
|
5
|
+
---
|
|
6
|
+
追加一条审计日志。参数:--actor(人或Agent)、--action、--entity(如 REQ-100)、--result(ok/blocked/failed)、--detail(可选)。
|
|
7
|
+
若已配置 flow MCP,调用 mcp__flow__flow_audit 工具;否则运行 `python .aidlc-engine/cli/flow.py audit --actor ... --action ... --entity ... --result ...`。
|
|
8
|
+
|
|
9
|
+
硬性规则:
|
|
10
|
+
- action 必须属于 `.aidlc-engine/state/audit-events.json` 封闭注册表(25 事件,含 emitter 归属);未注册事件会被类型化拒绝([GUARD_EVENT_NOT_REGISTERED]),禁止先使用再补注册。
|
|
11
|
+
- 只增不删,seq 严格连续递增;禁止改历史条目。
|
|
12
|
+
- 审计先行:状态变更的审计事件必须先于(或与状态变更同一次原子写中)写入——状态迁移/门控请优先用 `flow transition` / `flow gate --record`,它们已内置审计先行。
|
|
13
|
+
- 新增事件类型:先改 audit-events.json(desc + emitter)+ .aidlc-engine/templates/audit-log.md,再使用。
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: flow-branch
|
|
3
|
+
description: 执行分支协作——建/查/合并/丢弃 Change 执行分支(feature/chg-<NNN>)
|
|
4
|
+
---
|
|
5
|
+
管理 Change 的 git 执行分支(分支归属制,CHG 实施强制前置):
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
python .aidlc-engine/cli/flow.py branch create --chg CHG-xxx # 建执行分支(前置:CHG=proposed、WU gate 已过、当前在 main、工作区干净)
|
|
9
|
+
python .aidlc-engine/cli/flow.py branch status # 只读查看各 CHG 分支归属与当前 git 分支
|
|
10
|
+
python .aidlc-engine/cli/flow.py branch merge --chg CHG-xxx # code_review 留痕 + trailer 预检 + ff-only 合并回 main(代码冻结)
|
|
11
|
+
python .aidlc-engine/cli/flow.py branch discard --chg CHG-xxx # 丢弃执行分支、归属回 main(状态回退另走 transition)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
若已配置 flow MCP,优先直接调用 `mcp__flow__flow_branch_create` / `flow_branch_merge` / `flow_branch_discard` 工具;未配置则运行上面的 CLI 命令。
|
|
15
|
+
|
|
16
|
+
**顺序铁律**:建分支 → `flow transition <CHG-ID> in_progress` → 分支内实施 → code_review 门留痕 → merge(或 discard 回退)→ `flow transition <CHG-ID> archived`。CHG 状态变更一律走 `flow transition`,禁止直接改 `workflow-state.json`。
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: flow-gate
|
|
3
|
+
description: 执行门控校验并留痕(req_review / workunit_gate / release_gate),两步确认(--request → 人工确认 → --record),支持冻结 hash 与 HUMAN_TURN 证据核验
|
|
4
|
+
argument-hint: <实体ID> <门控类型> [--request|--record|--reject] [--reviewer <人>] [--confirm <人>] [--note <说明>]
|
|
5
|
+
---
|
|
6
|
+
对指定实体执行门控(两步确认,P1-8)。参数:$1=实体ID(如 WU-03 / REL-100),$2=门控类型(req_review / workunit_gate / release_gate)。
|
|
7
|
+
|
|
8
|
+
若已配置 flow MCP,调用 mcp__flow__flow_gate 工具;否则运行 `python .aidlc-engine/cli/flow.py gate $1 $2 ...`。
|
|
9
|
+
|
|
10
|
+
**第一步(开启门禁)**:`flow_gate(request=true)` / `flow gate $1 $2 --request` 登记 pending 待确认请求。
|
|
11
|
+
|
|
12
|
+
**HARD STOP 铁律(登记后立即执行)**:
|
|
13
|
+
- 向用户完整呈现:门禁检查单逐项结论 + 确认问题(批准 / 修改 / 驳回 三选项)。
|
|
14
|
+
- 呈现后**立即结束回合等待用户回复**;未获用户回复前不得调用任何工具、不得自行推进。
|
|
15
|
+
(Trae hooks 强制此仪式:enforce-gate 阻断一切非只读工具,stop 阻断未提问的收尾。)
|
|
16
|
+
|
|
17
|
+
**第二步(人工决策后留痕)**:
|
|
18
|
+
- 用户批准 → `flow_gate(record=true, confirm=<用户/确认人>)` / `flow gate $1 $2 --record --confirm <确认人>`。
|
|
19
|
+
CLI 核验存在 ts>=requested_at 的用户真实输入(human.turn 证据)才接受留痕;Agent 转述无效。
|
|
20
|
+
- 用户修改 → 按修改意见修订产物,然后重新 --request。
|
|
21
|
+
- 用户驳回 → `flow_gate(reject=true, note=<原因>)` / `flow gate $1 $2 --reject --note <原因>`,进入修订。
|
|
22
|
+
|
|
23
|
+
硬性规则:
|
|
24
|
+
- 门控必须由 gate-verifier 判定,不得主观放行;不通过则列出缺失项并停止推进。
|
|
25
|
+
- 通过后必须用 `--record` 写入门控留痕 + 产物冻结 hash(gate.pass / gate.freeze 审计同一次原子写)。
|
|
26
|
+
- pending 门禁的 `--record` 必须 `--confirm <人类确认人>` 且有 HUMAN_TURN 证据;缺任一即 [REJECT]。
|
|
27
|
+
- 无 pending 请求的旧路径(直接 --record)仍可用但仅 WARN(兼容历史流程);新流程一律两步确认。
|
|
28
|
+
- 被门控产物事后任何变化 → validate 报 [GATE_INVALIDATED],门控失效,修改后须重新过门。
|