asia-aidlc 1.14.1 → 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 +54 -12
- package/CHANGELOG.md +68 -0
- package/package.json +5 -3
- package/.tmp-init-team-test.py +0 -119
- package/.tmp-mcp-init-test.py +0 -53
|
@@ -457,14 +457,43 @@ def suggest_owners(members):
|
|
|
457
457
|
return owners
|
|
458
458
|
|
|
459
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
|
+
|
|
460
480
|
def _ask(prompt, default=""):
|
|
461
|
-
"""
|
|
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
|
|
462
492
|
try:
|
|
463
|
-
|
|
464
|
-
line = input("%s%s: " % (prompt, shown))
|
|
493
|
+
line = input()
|
|
465
494
|
except (EOFError, KeyboardInterrupt, OSError):
|
|
466
495
|
print()
|
|
467
|
-
return
|
|
496
|
+
return None
|
|
468
497
|
return line.strip() or default
|
|
469
498
|
|
|
470
499
|
|
|
@@ -484,11 +513,18 @@ def setup_team_wizard(system, state):
|
|
|
484
513
|
print("\n" + "─" * 8 + " 下一步:配置团队成员 " + "─" * 8)
|
|
485
514
|
print(" 它决定:门控审批人校验、视图里的「配置负责人」、日历按人筛选。")
|
|
486
515
|
print(" [1] 现在配(约 30 秒) [2] 先跳过(随时可配:flow init --team \"张敏:产品负责人\")")
|
|
487
|
-
|
|
516
|
+
pick = _ask("选择", "1")
|
|
517
|
+
if pick is None: # 读不到输入:别假装在等,直接给一行命令
|
|
518
|
+
return NO_TTY_HINT
|
|
519
|
+
if pick not in ("1", "y", "Y", "yes", ""):
|
|
488
520
|
return " 已跳过。之后可用:flow init --team \"张敏:产品负责人,…\" --admin 张敏"
|
|
489
521
|
|
|
490
522
|
proj = _ask("项目名", project)
|
|
523
|
+
if proj is None:
|
|
524
|
+
return NO_TTY_HINT
|
|
491
525
|
admin = _ask("管理员 / 产品负责人(填入 N0/N1 与 req_review)", "")
|
|
526
|
+
if admin is None:
|
|
527
|
+
return NO_TTY_HINT
|
|
492
528
|
print("团队名单(每行一条,格式 名字:角色;空行结束)")
|
|
493
529
|
roster = []
|
|
494
530
|
while True:
|
|
@@ -574,7 +610,7 @@ def next_steps_block(state, team_done=False, hooks_done=False):
|
|
|
574
610
|
lines += [
|
|
575
611
|
"[next] step=team 状态=未配置 影响=门控审批人校验/视图「配置负责人」列/日历按人筛选",
|
|
576
612
|
"[next] step=team 一条命令配好=flow init --team \"张敏:产品负责人,李强:技术负责人\" --admin 张敏",
|
|
577
|
-
"[next] step=team
|
|
613
|
+
"[next] step=team 跳过=什么都不做即可(不影响 init 结果;随时可再配)",
|
|
578
614
|
]
|
|
579
615
|
if not hooks_done:
|
|
580
616
|
lines.append("[next] step=hooks 状态=未安装 影响=commit/切分支后视图不自动重建 "
|
|
@@ -675,14 +711,17 @@ def do_init(system, state, skills_dir=".trae/skills", no_skills=False, empty=Fal
|
|
|
675
711
|
out += "\nOK: 管理员 %s 已填入 N0/N1 与 req_review" % admin
|
|
676
712
|
if not (ok1 and ok2):
|
|
677
713
|
out += "\n[warn] 写入成员/阶段归属未成功,可重试:flow init --team \"…\" --admin <名字>"
|
|
678
|
-
elif not team_done and not no_input and
|
|
714
|
+
elif not team_done and not no_input and can_prompt():
|
|
679
715
|
# 提问放在 main() 打印完汇总之后(否则提示会插在 OK 行前面)
|
|
680
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
|
|
681
721
|
|
|
682
|
-
if not
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
pass
|
|
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)
|
|
686
725
|
|
|
687
726
|
blk = next_steps_block(state, team_done=team_done or bool(team.strip()),
|
|
688
727
|
hooks_done=git_hooks or bool(_PENDING_SETUP.get("hooks_dir")))
|
|
@@ -2226,7 +2265,10 @@ def main():
|
|
|
2226
2265
|
if extra:
|
|
2227
2266
|
print("\n" + extra)
|
|
2228
2267
|
if _PENDING_SETUP.get("hooks_dir"):
|
|
2229
|
-
|
|
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"):
|
|
2230
2272
|
print("OK: " + install_viewer_hooks(_PENDING_SETUP["hooks_dir"]))
|
|
2231
2273
|
else:
|
|
2232
2274
|
print(" 已跳过。之后可装:flow init --git-hooks / flow hooks install")
|
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,74 @@
|
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
+
## v1.23 · 2026-10-09 — `flow init` 收尾主动询问是否配团队(并修掉 npm 形态下的"假提示")
|
|
9
|
+
|
|
10
|
+
### 背景
|
|
11
|
+
|
|
12
|
+
`flow init` 生成本地文件后就断了:用户看到一串 `OK:` 不知道该干什么;而「配团队」这件事**只存在于
|
|
13
|
+
Skill description 里**,Agent 想不到、用户也不知道——偏偏它是门控能否过的前置(门控审批人校验、
|
|
14
|
+
视图「配置负责人」列、日历按人筛选都读 `team.json`)。
|
|
15
|
+
|
|
16
|
+
### 变更明细(最简版:**单负责人**,不动数据模型)
|
|
17
|
+
|
|
18
|
+
- **`flow init` 收尾询问团队**(TTY 真交互):[1] 现在配 / [2] 先跳过 → 项目名 → 管理员 →
|
|
19
|
+
名单(`名字:角色` 多行或逗号)→ 六阶段归属(**回车用按角色推断的建议值**)→ 落盘 + `validate`。
|
|
20
|
+
· 交互放在 `main()` 打印完汇总**之后**(`_PENDING_SETUP`),否则提问会插在 `OK:` 行前面;
|
|
21
|
+
· 项目名/管理员/阶段归属都有默认值,可一路回车;名单自动补上管理员;
|
|
22
|
+
· `parse_members` 兼容中英文冒号、逗号、换行、无角色。
|
|
23
|
+
- **非 TTY / `--no-input` 绝不读 stdin**,改为结构化待办:
|
|
24
|
+
`[next] step=team 状态=未配置 影响=… 一条命令配好=… 跳过=什么都不做即可` + `step=hooks` + `step=view`。
|
|
25
|
+
这是给编排 Agent 的触发契约:它据此**在对话里**问用户。
|
|
26
|
+
- **一行带全**:`flow init --team "张敏:产品负责人,李强:技术负责人,…" --admin 张敏`
|
|
27
|
+
(`ROLE_NODE_HINT` 按角色推断阶段归属),配完自动跑 `validate`;已有 `team.json` 时走增量不覆盖。
|
|
28
|
+
- **MCP `flow_init` 增加 `team` / `admin`**(工具数仍 14):description 写明「初始化后请主动问用户是否配团队」
|
|
29
|
+
「用户说先跳过就不要反复追问」,并**强制 `no_input=True`**(MCP 无人交互,提问必须由 Agent 在对话里完成)。
|
|
30
|
+
|
|
31
|
+
### 修掉的四个缺陷
|
|
32
|
+
|
|
33
|
+
1. **🔴 npm 形态下"假提示"(用户报障:输入 1 之后没反应)**:根因在 `lib/runner.js`——
|
|
34
|
+
`py.setStdout({batched})` **按行回调**,而 `input("选择 [1]: ")` 的提示语没有换行 → 被缓冲不显示;
|
|
35
|
+
且全程没有 `py.setStdin(...)`。**探针实测**:Pyodide 下 `input()` 直接 `EOFError: EOF when reading a line`、
|
|
36
|
+
`sys.stdin.isatty()` 为 `False`。
|
|
37
|
+
修法:新增 `can_prompt()`——`AIDLC_NPM=1`(npm/Pyodide 形态,由 `lib/index.js` 注入)一律**不问**,
|
|
38
|
+
改为明确输出 `[hint] 当前环境不支持交互提问…` + 一行命令;`_ask()` 改为「先 `write`+`flush` 提示语再 `input()`」,
|
|
39
|
+
并**用 `None` 区分"读不到输入"与"按回车用默认"**——读不到就中止向导,绝不再"假装在等输入"。
|
|
40
|
+
`next_steps_block` 里「跳过=回答『先跳过』即可」也改成「什么都不做即可」,不再诱导用户敲键盘。
|
|
41
|
+
2. **`[FAIL]` 污染 init 输出 → 退出码误判**:team-init `validate` 的原始输出含 `[FAIL]`,直接拼进 init
|
|
42
|
+
会让 `flow init` **退出码变 1**(明明成功)。新增 `_team_report()` 分级:硬问题(`DUTY_CONFLICT` /
|
|
43
|
+
不在成员名单)→ `[warn]`;待补(未配置负责人/审批人)→ `[info]`;原始 `[FAIL]` 不再外泄。
|
|
44
|
+
3. **MCP 全新项目把数据建到仓库根**:`flow_mcp.resolve_state()` 在 `aidlc-data/` 不存在时回退旧布局,
|
|
45
|
+
与 `flow init` 的缺省两目录布局不一致。改为旧布局**只在真的存在时**才用。
|
|
46
|
+
4. **`__pycache__` 被复制进 `.trae/skills/`**:进程内 import 引擎脚本会在源码树留 `.pyc`,再被
|
|
47
|
+
`_install_skills` 连带复制。新增 `_exec_no_pycache()`(加载时临时 `sys.dont_write_bytecode`),
|
|
48
|
+
并让 `_install_skills` `ignore_patterns("__pycache__","*.pyc","*.pyo")`。
|
|
49
|
+
|
|
50
|
+
### 验证
|
|
51
|
+
|
|
52
|
+
- **Pyodide 形态**:`AIDLC_NPM=1 flow init --empty` → 无任何提问、有 `[hint]` + `[next]` + 一行命令,退出码 0。
|
|
53
|
+
- **`--no-input`**:同上(不读 stdin)。
|
|
54
|
+
- **`--team` 一行带全(5 人 + `--git-hooks`)**:六阶段负责人与门控审批人齐备
|
|
55
|
+
(`req_review=[张敏]`、`workunit_gate=[王磊]`、`release_gate=[陈昊]`、`code_review=[]` 待指定),退出码 0,
|
|
56
|
+
并给出 `code_review` 的补救命令;部分名单(2 人)报 `[info] 还有 6 项待补` + 两条补救命令,退出码 0。
|
|
57
|
+
- **向导逻辑(打桩 `input()`)21 项断言全过**,含新增:**EOF 时不落盘**、EOF 时明确提示"不支持交互"并给一行命令、
|
|
58
|
+
`can_prompt()` 在 `AIDLC_NPM=1` 下为 False、提示语确实被 `write+flush` 写出。
|
|
59
|
+
- **MCP 路径 6 项断言全过**:不向 stdout 泄漏、返回文本**不含**交互提问、含 `[next]`、`team.json` 落在 `aidlc-data/`、
|
|
60
|
+
阶段归属按角色推断、待补项用 `[info]` 而非 `[FAIL]`。
|
|
61
|
+
|
|
62
|
+
### 未做(明确记录)
|
|
63
|
+
|
|
64
|
+
- **阶段负责人多人(`owners` 列表)**:按「先以最初最简单的」暂不做;模型仍是单值 `owner`,
|
|
65
|
+
门控审批人已是列表(`--approver` 可多次)。改动面(`render.py` 3 处读取 + `init_owners.py` 写入/校验 +
|
|
66
|
+
职责分离按集合判定)已在设计稿列明。
|
|
67
|
+
- `flow setup` 独立向导命令、`team-init preset` 模板、「跳过」偏好持久化:未做(本次内联在 init 里)。
|
|
68
|
+
|
|
69
|
+
> ⚠️ 协作提示:本次编辑期间发现**同一工作区有第二个写者**(`CHANGELOG` v1.22 的 npm 打包/改名条目、
|
|
70
|
+
> `package.json` 改名 `asia-aidlc` + 版本 1.14.1 + 加 `build` 脚本 + **`description` 被清空**)。
|
|
71
|
+
> 这解释了更早 v1.17 那批改动"凭空消失"——并发写入互相覆盖。本次已把 `description` 填回;
|
|
72
|
+
> 建议后续串行编辑,避免继续互相覆盖。
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
8
76
|
## v1.22 · 2026-10-09 — 补齐 npm 打包入口(`npm run build`)+ 包名统一为 `asia-aidlc`
|
|
9
77
|
|
|
10
78
|
### 背景(用户报障)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "asia-aidlc",
|
|
3
|
-
"version": "1.14.
|
|
4
|
-
"description": "",
|
|
3
|
+
"version": "1.14.2",
|
|
4
|
+
"description": "OpenSpec + AI-DLC 研发流程能力包:14 个 Skill(SKILL.md + 确定性 Python 脚本)、flow CLI、状态机真相源、研发日历 / 交付控制台 / 追溯图谱三个静态视图。内置 Pyodide 运行时,装完即可执行 Python 脚本,无需本机安装 Python。",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"aidlc",
|
|
7
7
|
"openspec",
|
|
@@ -30,6 +30,8 @@
|
|
|
30
30
|
"test:viewer": "node test/viewer-js.js"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"
|
|
33
|
+
"jsonschema": "^1.5.0",
|
|
34
|
+
"pyodide": "^314.0.7",
|
|
35
|
+
"validate": "^5.2.0"
|
|
34
36
|
}
|
|
35
37
|
}
|
package/.tmp-init-team-test.py
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
"""临时验证:init 的团队向导(打桩 input)+ 待办块 + MCP 入参。"""
|
|
3
|
-
import builtins
|
|
4
|
-
import importlib.util
|
|
5
|
-
import io
|
|
6
|
-
import json
|
|
7
|
-
import os
|
|
8
|
-
import sys
|
|
9
|
-
import contextlib
|
|
10
|
-
|
|
11
|
-
ROOT = os.path.abspath(os.path.dirname(os.path.abspath(__file__)))
|
|
12
|
-
CLI = os.path.join(ROOT, ".aidlc-engine", "cli")
|
|
13
|
-
sys.path.insert(0, CLI)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def load(name, path):
|
|
17
|
-
spec = importlib.util.spec_from_file_location(name, path)
|
|
18
|
-
mod = importlib.util.module_from_spec(spec)
|
|
19
|
-
spec.loader.exec_module(mod)
|
|
20
|
-
return mod
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
flow = load("flow_t", os.path.join(CLI, "flow.py"))
|
|
24
|
-
fails = []
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def ck(name, cond, extra=""):
|
|
28
|
-
print((" OK " if cond else " FAIL ") + name + ("" if cond else " " + str(extra)))
|
|
29
|
-
if not cond:
|
|
30
|
-
fails.append(name)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
state = os.path.join(ROOT, ".tmp-wz", "aidlc-data", "workflow-state.json")
|
|
34
|
-
os.makedirs(os.path.dirname(state), exist_ok=True)
|
|
35
|
-
with open(state, "w", encoding="utf-8") as f:
|
|
36
|
-
json.dump({"meta": {"project": "订单中心", "updated_at": "2026-10-09T00:00:00+08:00",
|
|
37
|
-
"owner": "张敏"}}, f, ensure_ascii=False)
|
|
38
|
-
|
|
39
|
-
# ---- 1) 名单解析 + 角色推断 ----
|
|
40
|
-
ck("parse_members 支持逗号/换行/中英文冒号",
|
|
41
|
-
flow.parse_members("张敏:产品负责人, 李强:技术负责人\n王磊") ==
|
|
42
|
-
[("张敏", "产品负责人"), ("李强", "技术负责人"), ("王磊", "")],
|
|
43
|
-
flow.parse_members("张敏:产品负责人, 李强:技术负责人\n王磊"))
|
|
44
|
-
sug = flow.suggest_owners([("张敏", "产品负责人"), ("李强", "技术负责人"),
|
|
45
|
-
("王磊", "研发负责人"), ("赵倩", "QA负责人"), ("陈昊", "交付负责人")])
|
|
46
|
-
ck("角色推断铺满六阶段", sorted(sug) == ["N0", "N1", "N2", "N3", "N4", "N5"], sug)
|
|
47
|
-
ck("研发负责人 → N3", sug.get("N3") == "王磊", sug)
|
|
48
|
-
|
|
49
|
-
# ---- 2) 打桩 input 跑向导 ----
|
|
50
|
-
answers = iter(["1", # 现在配
|
|
51
|
-
"订单中心", # 项目名
|
|
52
|
-
"张敏", # 管理员
|
|
53
|
-
"张敏:产品负责人", "李强:技术负责人", "王磊:研发负责人",
|
|
54
|
-
"赵倩:QA负责人", "陈昊:交付负责人", "", # 名单 + 空行结束
|
|
55
|
-
""]) # 阶段归属:回车用建议值
|
|
56
|
-
asked = []
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def fake_input(prompt=""):
|
|
60
|
-
asked.append(prompt)
|
|
61
|
-
try:
|
|
62
|
-
return next(answers)
|
|
63
|
-
except StopIteration:
|
|
64
|
-
raise EOFError
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
builtins.input = fake_input
|
|
68
|
-
buf = io.StringIO()
|
|
69
|
-
with contextlib.redirect_stdout(buf):
|
|
70
|
-
text = flow.setup_team_wizard(os.path.join(ROOT, ".aidlc-engine"), state)
|
|
71
|
-
out = buf.getvalue()
|
|
72
|
-
print("--- 向导输出 ---")
|
|
73
|
-
print(out + "\n" + text)
|
|
74
|
-
|
|
75
|
-
team_p = os.path.join(os.path.dirname(state), "team.json")
|
|
76
|
-
team = json.load(open(team_p, encoding="utf-8")) if os.path.exists(team_p) else {}
|
|
77
|
-
owners = {n["id"]: n.get("owner") for n in team.get("nodes", [])}
|
|
78
|
-
ck("向导写出 team.json", os.path.exists(team_p))
|
|
79
|
-
ck("六阶段负责人齐备", all(owners.get("N%d" % i) for i in range(6)), owners)
|
|
80
|
-
ck("N3 = 王磊", owners.get("N3") == "王磊", owners)
|
|
81
|
-
ck("成员 5 人", len(team.get("members", [])) == 5, [m["name"] for m in team.get("members", [])])
|
|
82
|
-
ck("req_review 审批人随 N1 联动",
|
|
83
|
-
next((g["approvers"] for n in team["nodes"] if n["id"] == "N1" for g in n["gates"]
|
|
84
|
-
if g["type"] == "req_review"), None) == ["张敏"])
|
|
85
|
-
ck("code_review 未被自动同步(职责分离例外)",
|
|
86
|
-
next((g["approvers"] for n in team["nodes"] if n["id"] == "N3" for g in n["gates"]
|
|
87
|
-
if g["type"] == "code_review"), None) == [], "应为空并要求另行指定")
|
|
88
|
-
ck("向导给出了 code_review 的补救命令",
|
|
89
|
-
"code_review" in text and "set --gate code_review" in text, text[-300:])
|
|
90
|
-
ck("向导问了「是否现在配」", any("选择" in a for a in asked), asked[:2])
|
|
91
|
-
|
|
92
|
-
# ---- 3) 待办块 ----
|
|
93
|
-
blk = flow.next_steps_block(state, team_done=True, hooks_done=False)
|
|
94
|
-
ck("钩子未装 → [next] step=hooks", "step=hooks" in blk, blk)
|
|
95
|
-
ck("团队已配 → 不再提 step=team", "step=team" not in blk, blk)
|
|
96
|
-
# 用一份「还没有 team.json」的状态验证未配分支
|
|
97
|
-
fresh = os.path.join(ROOT, ".tmp-wz2", "aidlc-data", "workflow-state.json")
|
|
98
|
-
os.makedirs(os.path.dirname(fresh), exist_ok=True)
|
|
99
|
-
with open(fresh, "w", encoding="utf-8") as f:
|
|
100
|
-
json.dump({"meta": {"project": "p"}}, f, ensure_ascii=False)
|
|
101
|
-
blk2 = flow.next_steps_block(fresh, team_done=False, hooks_done=True)
|
|
102
|
-
ck("团队未配 → [next] step=team 且含一行命令",
|
|
103
|
-
"step=team" in blk2 and "--team" in blk2, blk2)
|
|
104
|
-
ck("已配置的仓库再跑 init 不重复唠叨",
|
|
105
|
-
"step=team" not in flow.next_steps_block(state, team_done=False, hooks_done=True))
|
|
106
|
-
|
|
107
|
-
# ---- 4) MCP 入参 ----
|
|
108
|
-
mcp = load("mcp_t", os.path.join(CLI, "flow_mcp.py"))
|
|
109
|
-
tool = next(t for t in mcp.TOOLS if t["name"] == "flow_init")
|
|
110
|
-
props = set(tool["inputSchema"]["properties"])
|
|
111
|
-
ck("MCP flow_init 增加 team/admin", {"team", "admin"} <= props, props)
|
|
112
|
-
ck("MCP flow_init description 要求主动问团队", "主动问" in tool["description"], "")
|
|
113
|
-
import inspect
|
|
114
|
-
src = inspect.getsource(mcp)
|
|
115
|
-
ck("MCP 侧强制 no_input=True(不会在返回前提问)",
|
|
116
|
-
"no_input=True" in src, "")
|
|
117
|
-
|
|
118
|
-
print("\n" + ("失败 %d 项" % len(fails) if fails else "INIT-TEAM TEST PASS"))
|
|
119
|
-
sys.exit(1 if fails else 0)
|
package/.tmp-mcp-init-test.py
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
"""临时验证:MCP flow_init 的 Agent 路径(传 team/admin,不提问)。"""
|
|
3
|
-
import importlib.util
|
|
4
|
-
import io
|
|
5
|
-
import json
|
|
6
|
-
import os
|
|
7
|
-
import sys
|
|
8
|
-
import contextlib
|
|
9
|
-
|
|
10
|
-
ROOT = os.path.abspath(os.path.dirname(os.path.abspath(__file__)))
|
|
11
|
-
CLI = os.path.join(ROOT, ".aidlc-engine", "cli")
|
|
12
|
-
sys.path.insert(0, CLI)
|
|
13
|
-
|
|
14
|
-
work = os.path.join(ROOT, ".tmp-mcpinit")
|
|
15
|
-
os.makedirs(os.path.join(work, ".git", "hooks"), exist_ok=True)
|
|
16
|
-
os.chdir(work)
|
|
17
|
-
|
|
18
|
-
spec = importlib.util.spec_from_file_location("mcp_i", os.path.join(CLI, "flow_mcp.py"))
|
|
19
|
-
mcp = importlib.util.module_from_spec(spec)
|
|
20
|
-
spec.loader.exec_module(mcp)
|
|
21
|
-
|
|
22
|
-
fails = []
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def ck(name, cond, extra=""):
|
|
26
|
-
print((" OK " if cond else " FAIL ") + name + ("" if cond else " " + str(extra)))
|
|
27
|
-
if not cond:
|
|
28
|
-
fails.append(name)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
buf = io.StringIO()
|
|
32
|
-
with contextlib.redirect_stdout(buf):
|
|
33
|
-
text = mcp.HANDLERS["flow_init"]({"empty": True, "team": "张敏:产品负责人,李强:技术负责人",
|
|
34
|
-
"admin": "张敏"})
|
|
35
|
-
leaked = buf.getvalue()
|
|
36
|
-
|
|
37
|
-
ck("handler 不向 stdout 泄漏(MCP 协议安全)", leaked.strip() == "", repr(leaked[:200]))
|
|
38
|
-
ck("返回文本不含交互提问", "选择 [1]" not in text and "团队名单(每行" not in text,
|
|
39
|
-
[l for l in text.splitlines() if "选择" in l])
|
|
40
|
-
ck("返回文本含待办清单 [next]", "[next]" in text, text[-400:])
|
|
41
|
-
team_p = os.path.join(work, "aidlc-data", "team.json")
|
|
42
|
-
ck("team.json 已落盘", os.path.exists(team_p))
|
|
43
|
-
team = json.load(open(team_p, encoding="utf-8")) if os.path.exists(team_p) else {}
|
|
44
|
-
owners = {n["id"]: n.get("owner") for n in team.get("nodes", [])}
|
|
45
|
-
ck("阶段归属按角色推断(N0/N1=张敏,N2=李强)",
|
|
46
|
-
owners.get("N0") == "张敏" and owners.get("N1") == "张敏" and owners.get("N2") == "李强", owners)
|
|
47
|
-
ck("待补项用 [info] 而不是 [FAIL](否则 init 会被判失败)",
|
|
48
|
-
"[FAIL]" not in text and "[info]" in text, [l for l in text.splitlines() if "FAIL" in l])
|
|
49
|
-
|
|
50
|
-
print("\n--- flow_init 返回(尾部)---")
|
|
51
|
-
print("\n".join(text.splitlines()[-12:]))
|
|
52
|
-
print("\n" + ("失败 %d 项" % len(fails) if fails else "MCP INIT TEST PASS"))
|
|
53
|
-
sys.exit(1 if fails else 0)
|