aisef 0.1.0__py3-none-any.whl
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.
- aisdlc/__init__.py +0 -0
- aisdlc/cli/__init__.py +101 -0
- aisdlc/cli/__main__.py +9 -0
- aisdlc/cli/_common.py +72 -0
- aisdlc/cli/doctor.py +286 -0
- aisdlc/cli/harness.py +270 -0
- aisdlc/cli/implement.py +552 -0
- aisdlc/cli/parser.py +248 -0
- aisdlc/cli/plan.py +280 -0
- aisdlc/clients/__init__.py +0 -0
- aisdlc/clients/base.py +156 -0
- aisdlc/clients/claude_code.py +151 -0
- aisdlc/clients/compile.py +250 -0
- aisdlc/clients/opencode.py +162 -0
- aisdlc/clients/stream.py +340 -0
- aisdlc/config.py +363 -0
- aisdlc/control/__init__.py +0 -0
- aisdlc/control/acceptance.py +41 -0
- aisdlc/control/approvals.py +367 -0
- aisdlc/control/bmad_status.py +181 -0
- aisdlc/control/change.py +138 -0
- aisdlc/control/complexity.py +533 -0
- aisdlc/control/conformance.py +178 -0
- aisdlc/control/design_contract.py +218 -0
- aisdlc/control/experience.py +214 -0
- aisdlc/control/gate.py +800 -0
- aisdlc/control/impact.py +364 -0
- aisdlc/control/journal.py +356 -0
- aisdlc/control/ledger.py +686 -0
- aisdlc/control/machine_gate.py +318 -0
- aisdlc/control/normalize.py +696 -0
- aisdlc/control/outcome.py +116 -0
- aisdlc/control/preflight.py +741 -0
- aisdlc/control/replay.py +121 -0
- aisdlc/control/scheduler.py +199 -0
- aisdlc/control/security.py +133 -0
- aisdlc/control/state.py +332 -0
- aisdlc/control/tdd.py +84 -0
- aisdlc/control/worktree.py +392 -0
- aisdlc/harness/__init__.py +0 -0
- aisdlc/harness/aria.py +159 -0
- aisdlc/harness/assets/render.mjs +122 -0
- aisdlc/harness/browser.py +152 -0
- aisdlc/harness/context.py +306 -0
- aisdlc/harness/guardrails.py +821 -0
- aisdlc/harness/mockup_map.py +164 -0
- aisdlc/harness/mockup_verify.py +297 -0
- aisdlc/harness/observe.py +320 -0
- aisdlc/harness/prompts.py +116 -0
- aisdlc/harness/routing.py +146 -0
- aisdlc/harness/sandbox.py +489 -0
- aisdlc/harness/testlog.py +245 -0
- aisdlc/harness/tools.py +373 -0
- aisdlc/kit/__init__.py +0 -0
- aisdlc/kit/catalog.json +89 -0
- aisdlc/kit/catalog.py +122 -0
- aisdlc/kit/constitution.py +144 -0
- aisdlc/kit/detect_stack.py +160 -0
- aisdlc/kit/docs.py +102 -0
- aisdlc/kit/fetch.py +128 -0
- aisdlc/kit/install.py +218 -0
- aisdlc/kit/prompts/devsecops.md +50 -0
- aisdlc/kit/prompts/mockup-screen.md +27 -0
- aisdlc/kit/prompts/story-implement.md +112 -0
- aisdlc/kit/prompts/story-review.md +120 -0
- aisdlc/kit/prompts/story-security-review.md +127 -0
- aisdlc/kit/registry.py +434 -0
- aisdlc/kit/router.py +204 -0
- aisdlc/kit/rules/must_never.md +40 -0
- aisdlc/kit/rules/principles.md +64 -0
- aisdlc/kit/security_filter.py +264 -0
- aisdlc/kit/skill_scan.py +225 -0
- aisdlc/kit/skills/aisdlc-mockup-html/SKILL.md +125 -0
- aisdlc/kit/skills.py +132 -0
- aisdlc/phases/__init__.py +1 -0
- aisdlc/phases/deploy.py +486 -0
- aisdlc/phases/implement.py +2020 -0
- aisdlc/phases/improve.py +524 -0
- aisdlc/phases/mockup.py +302 -0
- aisdlc/phases/plan.py +437 -0
- aisdlc/phases/qa.py +455 -0
- aisdlc/phases/report.py +430 -0
- aisdlc/phases/run.py +525 -0
- aisdlc/phases/story_split.py +362 -0
- aisef-0.1.0.dist-info/METADATA +282 -0
- aisef-0.1.0.dist-info/RECORD +89 -0
- aisef-0.1.0.dist-info/WHEEL +5 -0
- aisef-0.1.0.dist-info/entry_points.txt +2 -0
- aisef-0.1.0.dist-info/top_level.txt +1 -0
aisdlc/__init__.py
ADDED
|
File without changes
|
aisdlc/cli/__init__.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""Giao diện dòng lệnh.
|
|
2
|
+
|
|
3
|
+
Mọi lệnh đều **gọi-một-lần**: đọc trạng thái trên đĩa, làm việc, ghi lại,
|
|
4
|
+
rồi thoát. Không lệnh nào giả định mình là tiến trình chính, không lệnh
|
|
5
|
+
nào chạy nền. Đó là điều kiện để cùng bộ lệnh này dùng được ở hai chế độ
|
|
6
|
+
(quyết định Đ2):
|
|
7
|
+
|
|
8
|
+
* **driver-led** — script hoặc CI gọi ``aisdlc run``;
|
|
9
|
+
* **agent-led** — chính agent gọi ``aisdlc next`` / ``verify`` / ``complete``
|
|
10
|
+
qua Bash, ngay trong phiên chat của Claude Desktop hay OpenCode.
|
|
11
|
+
|
|
12
|
+
Quy ước mã thoát: ``0`` thành công · ``1`` lỗi dùng sai · ``2`` trạng thái
|
|
13
|
+
chưa đạt (cổng chưa duyệt, doctor không đạt) — để CI phân biệt được
|
|
14
|
+
"hỏng" với "chưa xong".
|
|
15
|
+
|
|
16
|
+
Bố cục gói (chia theo pha, không đổi hành vi):
|
|
17
|
+
|
|
18
|
+
* :mod:`._common` — mã thoát, gốc artifact, kho duyệt/trạng thái, adapter;
|
|
19
|
+
* :mod:`.doctor` — lệnh ``doctor``;
|
|
20
|
+
* :mod:`.plan` — cổng người + pha lập kế hoạch;
|
|
21
|
+
* :mod:`.implement` — pha hiện thực và nghiệm thu;
|
|
22
|
+
* :mod:`.harness` — dựng và vận hành harness;
|
|
23
|
+
* :mod:`.parser` — bộ phân tích tham số và ``main``.
|
|
24
|
+
|
|
25
|
+
Mọi tên module-level cũ được xuất lại ở đây, nên ``from aisdlc.cli import
|
|
26
|
+
main`` và bạn bè vẫn chạy y như khi còn là một file.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
from ._common import (
|
|
32
|
+
ARTIFACT_ROOT,
|
|
33
|
+
_GATE_MARK,
|
|
34
|
+
EXIT_NOT_READY,
|
|
35
|
+
EXIT_OK,
|
|
36
|
+
EXIT_USAGE,
|
|
37
|
+
_approvals,
|
|
38
|
+
_artifact_root,
|
|
39
|
+
_client,
|
|
40
|
+
_gate_arg,
|
|
41
|
+
_state,
|
|
42
|
+
)
|
|
43
|
+
from .doctor import _hook_paths_elsewhere, cmd_doctor
|
|
44
|
+
from .harness import cmd_compile, cmd_doc, cmd_guard, cmd_init, cmd_setup, cmd_skill
|
|
45
|
+
from .implement import (
|
|
46
|
+
_project_has_ui,
|
|
47
|
+
cmd_devsecops,
|
|
48
|
+
cmd_evidence,
|
|
49
|
+
cmd_predeploy,
|
|
50
|
+
cmd_qa,
|
|
51
|
+
cmd_report,
|
|
52
|
+
cmd_run,
|
|
53
|
+
cmd_status,
|
|
54
|
+
cmd_tool,
|
|
55
|
+
cmd_verify,
|
|
56
|
+
)
|
|
57
|
+
from .parser import build_parser, main
|
|
58
|
+
from .plan import (
|
|
59
|
+
_preflight_lines,
|
|
60
|
+
cmd_approve,
|
|
61
|
+
cmd_auto_approve,
|
|
62
|
+
cmd_change,
|
|
63
|
+
cmd_gates,
|
|
64
|
+
cmd_mockup,
|
|
65
|
+
cmd_plan,
|
|
66
|
+
cmd_reject,
|
|
67
|
+
cmd_review,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
__all__ = [
|
|
71
|
+
"ARTIFACT_ROOT",
|
|
72
|
+
"EXIT_NOT_READY",
|
|
73
|
+
"EXIT_OK",
|
|
74
|
+
"EXIT_USAGE",
|
|
75
|
+
"build_parser",
|
|
76
|
+
"cmd_approve",
|
|
77
|
+
"cmd_auto_approve",
|
|
78
|
+
"cmd_change",
|
|
79
|
+
"cmd_compile",
|
|
80
|
+
"cmd_devsecops",
|
|
81
|
+
"cmd_doc",
|
|
82
|
+
"cmd_doctor",
|
|
83
|
+
"cmd_evidence",
|
|
84
|
+
"cmd_gates",
|
|
85
|
+
"cmd_guard",
|
|
86
|
+
"cmd_init",
|
|
87
|
+
"cmd_mockup",
|
|
88
|
+
"cmd_plan",
|
|
89
|
+
"cmd_predeploy",
|
|
90
|
+
"cmd_qa",
|
|
91
|
+
"cmd_reject",
|
|
92
|
+
"cmd_report",
|
|
93
|
+
"cmd_review",
|
|
94
|
+
"cmd_run",
|
|
95
|
+
"cmd_setup",
|
|
96
|
+
"cmd_skill",
|
|
97
|
+
"cmd_status",
|
|
98
|
+
"cmd_tool",
|
|
99
|
+
"cmd_verify",
|
|
100
|
+
"main",
|
|
101
|
+
]
|
aisdlc/cli/__main__.py
ADDED
aisdlc/cli/_common.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Mảnh dùng chung của bộ lệnh: mã thoát, gốc artifact, kho duyệt/trạng
|
|
2
|
+
thái, adapter client, và bộ chuyển tham số tên cổng.
|
|
3
|
+
|
|
4
|
+
Mọi module lệnh khác nhập từ đây, nên file này không được nhập ngược lại
|
|
5
|
+
chúng — giữ đồ thị nhập một chiều.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import argparse
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
from ..control.approvals import GATE_ORDER, ApprovalStore, Gate, Status
|
|
15
|
+
from ..control.state import StateStore
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
ARTIFACT_ROOT = "_bmad-output"
|
|
19
|
+
|
|
20
|
+
EXIT_OK = 0
|
|
21
|
+
EXIT_USAGE = 1
|
|
22
|
+
EXIT_NOT_READY = 2
|
|
23
|
+
|
|
24
|
+
#: Ký hiệu trạng thái cổng, đủ để đọc lướt.
|
|
25
|
+
_GATE_MARK = {
|
|
26
|
+
Status.APPROVED: "✅",
|
|
27
|
+
Status.PENDING: "⏳",
|
|
28
|
+
Status.CHANGES_REQUESTED: "✗",
|
|
29
|
+
Status.STALE: "⚠️",
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _artifact_root(args) -> Path:
|
|
34
|
+
"""Gốc artifact — **một** gốc cho cả dự án (bất biến 2).
|
|
35
|
+
|
|
36
|
+
Agent chạy trong worktree của story, nên `--project .` ở đó trỏ vào
|
|
37
|
+
worktree. Ghi bằng chứng vào đấy thì cổng đọc ở gốc chính không thấy
|
|
38
|
+
gì, và story "chưa từng chạy test" dù nó vừa chạy.
|
|
39
|
+
"""
|
|
40
|
+
from ..control.worktree import main_repo
|
|
41
|
+
|
|
42
|
+
return main_repo(args.project) / ARTIFACT_ROOT
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _approvals(args) -> ApprovalStore:
|
|
46
|
+
return ApprovalStore(_artifact_root(args))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _state(args) -> StateStore:
|
|
50
|
+
return StateStore(_artifact_root(args))
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _gate_arg(value: str) -> Gate:
|
|
54
|
+
try:
|
|
55
|
+
return Gate(value)
|
|
56
|
+
except ValueError:
|
|
57
|
+
valid = ", ".join(g.value for g in GATE_ORDER)
|
|
58
|
+
raise argparse.ArgumentTypeError(f"cổng không hợp lệ: {value}. Hợp lệ: {valid}")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _client(args):
|
|
62
|
+
"""Adapter đã kiểm tên và tình trạng cài đặt. None nếu không dùng được."""
|
|
63
|
+
from ..clients.compile import ADAPTERS
|
|
64
|
+
|
|
65
|
+
if args.client not in ADAPTERS:
|
|
66
|
+
print(f"✗ client không hỗ trợ: {args.client}", file=sys.stderr)
|
|
67
|
+
return None, EXIT_USAGE
|
|
68
|
+
adapter = ADAPTERS[args.client]()
|
|
69
|
+
if not adapter.available():
|
|
70
|
+
print(f"✗ chưa cài {args.client} trên máy này", file=sys.stderr)
|
|
71
|
+
return None, EXIT_NOT_READY
|
|
72
|
+
return adapter, EXIT_OK
|
aisdlc/cli/doctor.py
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
"""Lệnh ``doctor`` — kiểm môi trường, cấu hình, hook và skill đã cài."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import shutil
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from ..config import Config, ConfigError
|
|
12
|
+
from ._common import EXIT_NOT_READY, EXIT_OK
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _hook_paths_elsewhere(project: Path) -> list[str] | None:
|
|
16
|
+
"""Đường dẫn dự án ghi trong hook/plugin biên dịch mà **khác** dự án này.
|
|
17
|
+
None khi không có hook nào để so."""
|
|
18
|
+
import re as _re
|
|
19
|
+
|
|
20
|
+
goc = project.resolve()
|
|
21
|
+
thay: list[str] = []
|
|
22
|
+
hook = project / ".claude" / "settings.json"
|
|
23
|
+
if hook.is_file():
|
|
24
|
+
thay += _re.findall(r"--project\s+(\S+)\s+guard", hook.read_text(encoding="utf-8", errors="replace"))
|
|
25
|
+
plugin = project / ".opencode" / "plugin" / "aisdlc-guard.ts"
|
|
26
|
+
if plugin.is_file():
|
|
27
|
+
thay += _re.findall(r'const PROJECT = "([^"]+)"', plugin.read_text(encoding="utf-8", errors="replace"))
|
|
28
|
+
if not thay:
|
|
29
|
+
return None
|
|
30
|
+
return sorted({p for p in thay if Path(p).resolve() != goc})
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def cmd_doctor(args) -> int:
|
|
34
|
+
"""Kiểm tra môi trường có đủ chạy framework không."""
|
|
35
|
+
project = Path(args.project)
|
|
36
|
+
problems: list[str] = []
|
|
37
|
+
lines: list[str] = []
|
|
38
|
+
|
|
39
|
+
def check(label: str, ok: bool, detail: str = "", *, required: bool = True) -> None:
|
|
40
|
+
mark = "✅" if ok else ("✗" if required else "○")
|
|
41
|
+
lines.append(f" {mark} {label}{(' — ' + detail) if detail else ''}")
|
|
42
|
+
if required and not ok:
|
|
43
|
+
problems.append(label)
|
|
44
|
+
|
|
45
|
+
lines.append("Môi trường:")
|
|
46
|
+
check("python >= 3.11", sys.version_info >= (3, 11), sys.version.split()[0])
|
|
47
|
+
check("git", bool(shutil.which("git")))
|
|
48
|
+
|
|
49
|
+
claude = shutil.which("claude")
|
|
50
|
+
check("claude CLI", bool(claude), claude or "không tìm thấy", required=False)
|
|
51
|
+
opencode = shutil.which("opencode")
|
|
52
|
+
check("opencode CLI", bool(opencode), opencode or "không tìm thấy", required=False)
|
|
53
|
+
|
|
54
|
+
docker_ok = False
|
|
55
|
+
if shutil.which("docker"):
|
|
56
|
+
try:
|
|
57
|
+
docker_ok = subprocess.run(
|
|
58
|
+
["docker", "info"], capture_output=True, timeout=10
|
|
59
|
+
).returncode == 0
|
|
60
|
+
except (subprocess.TimeoutExpired, OSError):
|
|
61
|
+
docker_ok = False
|
|
62
|
+
check(
|
|
63
|
+
"docker daemon",
|
|
64
|
+
docker_ok,
|
|
65
|
+
"chạy" if docker_ok else "không chạy — sandbox sẽ suy biến, bảo đảm thấp hơn",
|
|
66
|
+
required=False,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
from ..harness.browser import availability as browser_availability
|
|
70
|
+
from ..harness.tools import image_for
|
|
71
|
+
|
|
72
|
+
browser_why = browser_availability(project)
|
|
73
|
+
check(
|
|
74
|
+
"playwright + chromium",
|
|
75
|
+
not browser_why,
|
|
76
|
+
"sẵn sàng" if not browser_why else f"{browser_why} — không trích được hợp đồng mockup",
|
|
77
|
+
required=False,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
lines.append("Dự án:")
|
|
81
|
+
check("thư mục dự án", project.is_dir(), str(project))
|
|
82
|
+
from ..harness import sandbox as _sandbox
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
cfg_for_sandbox = Config.load(project)
|
|
86
|
+
image = image_for(project, cfg_for_sandbox)
|
|
87
|
+
spec = _sandbox.SandboxSpec(
|
|
88
|
+
workspace=project, cmd=["true"],
|
|
89
|
+
use_docker=cfg_for_sandbox["sandbox.use_docker"],
|
|
90
|
+
provider=cfg_for_sandbox["sandbox.provider"],
|
|
91
|
+
)
|
|
92
|
+
except ConfigError:
|
|
93
|
+
image, spec = "?", _sandbox.SandboxSpec(workspace=project, cmd=["true"])
|
|
94
|
+
# Provider đang dùng và bảo đảm nó khai (ADR-005 V5): người đọc biết
|
|
95
|
+
# bằng chứng sắp ghi `degraded` vì **thiếu gì**, trước khi chạy story nào.
|
|
96
|
+
try:
|
|
97
|
+
prov, missing = _sandbox.select_provider(spec)
|
|
98
|
+
except (RuntimeError, ValueError, ImportError, AttributeError) as e:
|
|
99
|
+
prov, missing = None, []
|
|
100
|
+
check("sandbox provider", False, str(e), required=False)
|
|
101
|
+
if prov is not None:
|
|
102
|
+
co = sorted(g.value for g, s in prov.guarantees(spec.level).items() if s.blocks_at_source)
|
|
103
|
+
check(
|
|
104
|
+
"sandbox provider", not missing,
|
|
105
|
+
f"{prov.id} — bảo đảm ở {spec.level.value}: {', '.join(co) or 'không có'}"
|
|
106
|
+
+ (f"; THIẾU {', '.join(missing)} — công cụ chạy ngoài cách ly, bằng chứng ghi degraded"
|
|
107
|
+
if missing else ""),
|
|
108
|
+
required=False,
|
|
109
|
+
)
|
|
110
|
+
if prov is not None and prov.id == "docker":
|
|
111
|
+
# Chỉ Docker mới bàn về ảnh; provider khác chạy công cụ của máy.
|
|
112
|
+
check(
|
|
113
|
+
"ảnh sandbox",
|
|
114
|
+
image != "alpine:latest",
|
|
115
|
+
image + (" — không có công cụ của stack nào, test sẽ đỏ vì thiếu công cụ"
|
|
116
|
+
if image == "alpine:latest" else " (hợp stack)"),
|
|
117
|
+
required=False,
|
|
118
|
+
)
|
|
119
|
+
req = project / "docs" / "requirements.md"
|
|
120
|
+
check("docs/requirements.md", req.is_file(), str(req))
|
|
121
|
+
|
|
122
|
+
# Hook sinh ra ≠ hook chạy. Story chạy trong worktree; worktree chỉ có
|
|
123
|
+
# `.claude/` nếu dự án commit nó. Harness nay truyền `--settings` tường
|
|
124
|
+
# minh (G4), nhưng thứ đó chỉ được chứng minh bằng hợp quy — ở đây nói
|
|
125
|
+
# thẳng tình trạng để người đọc biết mình đang dựa vào lớp nào.
|
|
126
|
+
plugin = project / ".opencode" / "plugin" / "aisdlc-guard.ts"
|
|
127
|
+
if plugin.is_file():
|
|
128
|
+
tracked = subprocess.run(
|
|
129
|
+
["git", "-C", str(project), "ls-files", "--error-unmatch", str(plugin)],
|
|
130
|
+
capture_output=True, text=True,
|
|
131
|
+
).returncode == 0
|
|
132
|
+
check(
|
|
133
|
+
"plugin OpenCode trong worktree",
|
|
134
|
+
tracked,
|
|
135
|
+
"`.opencode/plugin` đã commit — worktree tự có plugin" if tracked else
|
|
136
|
+
"`.opencode/plugin` chưa commit — worktree KHÔNG có plugin, OpenCode chạy "
|
|
137
|
+
"story với zero guard (đo hợp quy 2026-09-05); commit `.opencode/` hoặc "
|
|
138
|
+
"không dùng OpenCode cho `run`",
|
|
139
|
+
required=False,
|
|
140
|
+
)
|
|
141
|
+
hook = project / ".claude" / "settings.json"
|
|
142
|
+
if hook.is_file():
|
|
143
|
+
tracked = subprocess.run(
|
|
144
|
+
["git", "-C", str(project), "ls-files", "--error-unmatch", str(hook)],
|
|
145
|
+
capture_output=True, text=True,
|
|
146
|
+
).returncode == 0
|
|
147
|
+
check(
|
|
148
|
+
"hook trong worktree",
|
|
149
|
+
tracked,
|
|
150
|
+
"`.claude/settings.json` đã commit — worktree tự có hook" if tracked else
|
|
151
|
+
"`.claude/settings.json` chưa commit — worktree không có hook; harness "
|
|
152
|
+
"truyền `--settings` tường minh, cổng story mục \"guard có chạy\" sẽ "
|
|
153
|
+
"trượt nếu hook không tới",
|
|
154
|
+
required=False,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
# Guard framework thêm sau lần `compile` cuối thì hook cũ không biết —
|
|
158
|
+
# ví dụ `process-ref` (luật 6, 2026-09-05). Không kiểm thì guard mới chỉ
|
|
159
|
+
# có trong mã, không có trong phiên agent nào.
|
|
160
|
+
rep = project / "_bmad-output" / "compile-report.json"
|
|
161
|
+
if rep.is_file():
|
|
162
|
+
from ..harness.guardrails import GUARD_MATCHERS
|
|
163
|
+
|
|
164
|
+
try:
|
|
165
|
+
data = json.loads(rep.read_text(encoding="utf-8"))
|
|
166
|
+
except (OSError, json.JSONDecodeError):
|
|
167
|
+
data = {}
|
|
168
|
+
for c in data.get("clients", []):
|
|
169
|
+
thieu = sorted(set(GUARD_MATCHERS) - set(c.get("guards_wired") or []) - set(c.get("guards_post_hoc") or []))
|
|
170
|
+
check(
|
|
171
|
+
f"hook {c.get('client')} đủ guard", not thieu,
|
|
172
|
+
f"{len(GUARD_MATCHERS)} guard" if not thieu else
|
|
173
|
+
f"thiếu {', '.join(thieu)} — framework có guard mới sau lần biên dịch; chạy `aisdlc compile --client {c.get('client')}`",
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Ngưỡng cỡ story chỉ đáng tin chừng nào nó còn khớp dữ liệu thật
|
|
177
|
+
# (ADR-004 R5). Bảng hiệu chuẩn do `run` tự ghi sau mỗi story.
|
|
178
|
+
from ..control import complexity
|
|
179
|
+
|
|
180
|
+
rows = complexity.load_calibration(project / "_bmad-output")
|
|
181
|
+
if rows:
|
|
182
|
+
lech_nguong = complexity.divergence(rows)
|
|
183
|
+
check(
|
|
184
|
+
"ngưỡng cỡ story khớp dữ liệu", not lech_nguong,
|
|
185
|
+
f"{len(rows)} story đã đo, không thấy lệch" if not lech_nguong else
|
|
186
|
+
"; ".join(lech_nguong) + " — chỉnh `story.max_complexity` trong "
|
|
187
|
+
"`.ai/config.json` hoặc xem lại trọng số ở `control/complexity.py`",
|
|
188
|
+
required=False,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
lech = _hook_paths_elsewhere(project)
|
|
192
|
+
if lech is not None:
|
|
193
|
+
check(
|
|
194
|
+
"hook trỏ đúng dự án", not lech,
|
|
195
|
+
"đường dẫn `--project`/`PROJECT` trong hook khớp thư mục này" if not lech else
|
|
196
|
+
f"hook trỏ sang {', '.join(lech[:2])} — dự án bị chép/di chuyển? guard sẽ ghi bằng "
|
|
197
|
+
"chứng vào dự án ấy; chạy `aisdlc compile` lại",
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
try:
|
|
201
|
+
cfg = Config.load(project)
|
|
202
|
+
check("cấu hình", True, cfg.source)
|
|
203
|
+
lenh = str(cfg.get("tools.test", "") or "")
|
|
204
|
+
if lenh:
|
|
205
|
+
co_cov = any(k in lenh for k in ("--coverage", "--cov", "--experimental-test-coverage", "c8 ", "nyc "))
|
|
206
|
+
check(
|
|
207
|
+
"lệnh test in coverage", co_cov,
|
|
208
|
+
"cổng `coverage.min` đọc được số" if co_cov else
|
|
209
|
+
f"`tools.test` = `{lenh}` không in coverage → mục coverage của cổng sẽ là **chưa cấu hình** "
|
|
210
|
+
"(thêm `--coverage` / `--cov` / `--experimental-test-coverage`)",
|
|
211
|
+
required=False,
|
|
212
|
+
)
|
|
213
|
+
# Tên test đọc được không (ADR-005 V9)? Có bằng chứng thì tin bằng
|
|
214
|
+
# chứng: lần `tool_run test` gần nhất ghi `test_format`; chưa có
|
|
215
|
+
# thì đoán từ cờ lệnh. e9/par: 220/377 lần test không đọc được tên.
|
|
216
|
+
from ..harness.observe import TOOL_RUN, EvidenceStore
|
|
217
|
+
|
|
218
|
+
store = EvidenceStore(project / "_bmad-output")
|
|
219
|
+
lan = [e for sid in store.stories() for e in store.read(sid).of(TOOL_RUN)
|
|
220
|
+
if e.name in ("test", "test:baseline") and not e.detail.get("skipped")]
|
|
221
|
+
if lan:
|
|
222
|
+
doc_ten = bool(lan[-1].detail.get("test_format"))
|
|
223
|
+
vi = f"lần test gần nhất ghi test_format={lan[-1].detail.get('test_format') or ''!r}"
|
|
224
|
+
else:
|
|
225
|
+
doc_ten = any(k in lenh for k in ("-v", "--verbose", "--reporter=verbose",
|
|
226
|
+
"--test-reporter", "node --test", "ctrf"))
|
|
227
|
+
vi = "chưa có bằng chứng — đoán từ cờ lệnh"
|
|
228
|
+
check(
|
|
229
|
+
"lệnh test in tên test", doc_ten,
|
|
230
|
+
f"cổng đọc được tên test ({vi})" if doc_ten else
|
|
231
|
+
f"{vi} → mục `tiêu chí có test`/`không làm đỏ test có sẵn` là **chưa cấu hình**. "
|
|
232
|
+
"Dùng reporter in tên hoặc CTRF: pytest `-v` hay `pip install pytest-json-ctrf` + "
|
|
233
|
+
"`pytest --ctrf /dev/stdout`; vitest `--reporter=verbose` hay `vitest-ctrf-json-reporter` "
|
|
234
|
+
"(in `vitest-ctrf/report.json` ra stdout, giữ mã thoát); node `--test-reporter=spec|tap`",
|
|
235
|
+
required=False,
|
|
236
|
+
)
|
|
237
|
+
except ConfigError as e:
|
|
238
|
+
check("cấu hình", False, str(e))
|
|
239
|
+
|
|
240
|
+
skills_dir = project / ".claude" / "skills"
|
|
241
|
+
if skills_dir.is_dir():
|
|
242
|
+
from ..kit.security_filter import Verdict, classify_all
|
|
243
|
+
|
|
244
|
+
installed = [d for d in skills_dir.iterdir() if d.is_dir()]
|
|
245
|
+
lines.append("Skill đã cài:")
|
|
246
|
+
check("có skill", bool(installed), f"{len(installed)} thư mục")
|
|
247
|
+
|
|
248
|
+
# Bất biến: skill tấn công không bao giờ được có mặt trong dự án.
|
|
249
|
+
offensive = [
|
|
250
|
+
c.skill.name
|
|
251
|
+
for c in classify_all(skills_dir)
|
|
252
|
+
if c.verdict is Verdict.OFFENSIVE
|
|
253
|
+
]
|
|
254
|
+
# Skill của framework nằm trong kho framework; dự án giữ một bản
|
|
255
|
+
# sao. Sửa skill mà không cài lại thì agent vẫn chạy bản cũ, và
|
|
256
|
+
# cách duy nhất phát hiện là ngồi so từng file.
|
|
257
|
+
from ..kit.install import OWN_SKILLS
|
|
258
|
+
|
|
259
|
+
stale = []
|
|
260
|
+
for own in sorted(OWN_SKILLS.glob("*/SKILL.md")) if OWN_SKILLS.is_dir() else []:
|
|
261
|
+
copied = skills_dir / own.parent.name / "SKILL.md"
|
|
262
|
+
if copied.is_file() and copied.read_bytes() != own.read_bytes():
|
|
263
|
+
stale.append(own.parent.name)
|
|
264
|
+
check(
|
|
265
|
+
"skill framework cập nhật",
|
|
266
|
+
not stale,
|
|
267
|
+
"khớp bản gốc" if not stale
|
|
268
|
+
else f"cũ hơn kho: {', '.join(stale)} — chạy `aisdlc setup`",
|
|
269
|
+
required=False,
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
check(
|
|
273
|
+
"không có skill tấn công",
|
|
274
|
+
not offensive,
|
|
275
|
+
"sạch" if not offensive else f"LỌT: {', '.join(offensive[:5])}",
|
|
276
|
+
)
|
|
277
|
+
else:
|
|
278
|
+
lines.append("Skill đã cài:")
|
|
279
|
+
check("đã chạy setup", False, "chưa — chạy: aisdlc setup", required=False)
|
|
280
|
+
|
|
281
|
+
print("\n".join(lines))
|
|
282
|
+
if problems:
|
|
283
|
+
print(f"\n✗ thiếu: {', '.join(problems)}")
|
|
284
|
+
return EXIT_NOT_READY
|
|
285
|
+
print("\n✅ sẵn sàng")
|
|
286
|
+
return EXIT_OK
|