Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bcec012c8b | ||
|
|
2ab5baa776 | ||
|
|
cf293d2382 |
@@ -0,0 +1,23 @@
|
|||||||
|
# 本机开发环境与解释器缓存
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
.pytest_cache/
|
||||||
|
.mypy_cache/
|
||||||
|
|
||||||
|
# 运行时生成的日志、截图和其他证据产物不得进入版本库。
|
||||||
|
logs/
|
||||||
|
artifacts/
|
||||||
|
runtime/
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# 本机凭据或环境覆盖仅可保存在未跟踪文件中。
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
secrets/
|
||||||
|
|
||||||
|
# 打包工具生成的本机产物
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
*.egg-info/
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=68"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "cmbuyer-client"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "cmbuyer 采购工具桌面端"
|
||||||
|
requires-python = ">=3.11"
|
||||||
|
dynamic = ["dependencies"]
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
cmbuyer-client = "cmbuyer_client.app:main"
|
||||||
|
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
where = ["src"]
|
||||||
|
|
||||||
|
[tool.setuptools.dynamic]
|
||||||
|
dependencies = { file = ["requirements.txt"] }
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# 桌面界面(Qt 官方 Python 绑定)。
|
||||||
|
PySide6
|
||||||
|
# 后续真机取证会使用;本阶段不导入或连接设备。
|
||||||
|
uiautomator2
|
||||||
|
# 后续截图完整性检查会使用。
|
||||||
|
Pillow
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
"""验证 wheel 元数据从 requirements.txt 声明了全部运行时依赖。"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
from email import policy
|
||||||
|
from email.parser import BytesParser
|
||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import zipfile
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_project_name(name: str) -> str:
|
||||||
|
"""使用足以比较 requirements 与 Core Metadata 的项目名规范化规则。"""
|
||||||
|
|
||||||
|
return re.sub(r"[-_.]+", "-", name).lower()
|
||||||
|
|
||||||
|
|
||||||
|
def dependencies_from_requirements(requirements_file: Path) -> set[str]:
|
||||||
|
"""从唯一依赖来源读取项目名;当前 requirements 不允许间接或可编辑依赖。"""
|
||||||
|
|
||||||
|
dependencies: set[str] = set()
|
||||||
|
for line in requirements_file.read_text(encoding="utf-8").splitlines():
|
||||||
|
requirement = line.partition("#")[0].strip()
|
||||||
|
if not requirement:
|
||||||
|
continue
|
||||||
|
match = re.match(r"[A-Za-z0-9][A-Za-z0-9._-]*", requirement)
|
||||||
|
if match is None:
|
||||||
|
raise ValueError(f"requirements.txt 包含不支持的依赖声明:{requirement}")
|
||||||
|
dependencies.add(normalize_project_name(match.group()))
|
||||||
|
return dependencies
|
||||||
|
|
||||||
|
|
||||||
|
def dependencies_from_wheel(wheel_file: Path) -> set[str]:
|
||||||
|
"""读取 wheel 的 Core Metadata 中声明的 Requires-Dist 项目名。"""
|
||||||
|
|
||||||
|
with zipfile.ZipFile(wheel_file) as wheel:
|
||||||
|
metadata_members = [name for name in wheel.namelist() if name.endswith(".dist-info/METADATA")]
|
||||||
|
if len(metadata_members) != 1:
|
||||||
|
raise ValueError("wheel 中必须恰有一个 .dist-info/METADATA 文件")
|
||||||
|
metadata = BytesParser(policy=policy.default).parsebytes(wheel.read(metadata_members[0]))
|
||||||
|
|
||||||
|
dependencies = set()
|
||||||
|
for requirement in metadata.get_all("Requires-Dist", []):
|
||||||
|
match = re.match(r"[A-Za-z0-9][A-Za-z0-9._-]*", requirement)
|
||||||
|
if match is None:
|
||||||
|
raise ValueError(f"wheel METADATA 包含无效的 Requires-Dist:{requirement}")
|
||||||
|
dependencies.add(normalize_project_name(match.group()))
|
||||||
|
return dependencies
|
||||||
|
|
||||||
|
|
||||||
|
def verify_wheel_metadata(wheel_file: Path, requirements_file: Path) -> set[str]:
|
||||||
|
"""返回没有被 wheel 元数据声明的 requirements 项目名。"""
|
||||||
|
|
||||||
|
return dependencies_from_requirements(requirements_file) - dependencies_from_wheel(wheel_file)
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv: list[str] | None = None) -> int:
|
||||||
|
parser = argparse.ArgumentParser(description="检查 wheel 是否包含 requirements.txt 的依赖元数据")
|
||||||
|
parser.add_argument("wheel", type=Path, help="待检查的 wheel 文件")
|
||||||
|
parser.add_argument(
|
||||||
|
"--requirements",
|
||||||
|
type=Path,
|
||||||
|
default=Path(__file__).resolve().parents[1] / "requirements.txt",
|
||||||
|
help="唯一依赖来源 requirements.txt 的路径",
|
||||||
|
)
|
||||||
|
arguments = parser.parse_args(argv)
|
||||||
|
|
||||||
|
missing = verify_wheel_metadata(arguments.wheel, arguments.requirements)
|
||||||
|
if missing:
|
||||||
|
print(f"wheel METADATA 缺少依赖:{', '.join(sorted(missing))}", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
print("wheel METADATA 已声明 requirements.txt 中的全部依赖。")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
"""采购工具桌面端包。
|
||||||
|
|
||||||
|
本包当前只提供应用骨架和安全的本地运行基础设施;不包含真机操作或采购流程。
|
||||||
|
"""
|
||||||
|
|
||||||
|
__version__ = "0.1.0"
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
"""支持通过 ``python -m cmbuyer_client`` 启动应用。"""
|
||||||
|
|
||||||
|
from .app import main
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
"""采购工具的最小桌面应用入口。"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from collections.abc import Sequence
|
||||||
|
|
||||||
|
from .logging_policy import configure_application_logger
|
||||||
|
from .runtime import RuntimePaths
|
||||||
|
|
||||||
|
|
||||||
|
def select_application_argv(argv: Sequence[str] | None) -> list[str]:
|
||||||
|
"""保留调用方明确给出的空参数列表,避免改变测试或打包入口的语义。"""
|
||||||
|
|
||||||
|
return list(sys.argv if argv is None else argv)
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv: Sequence[str] | None = None) -> int:
|
||||||
|
"""启动只表达当前工程状态的桌面外壳。
|
||||||
|
|
||||||
|
真机控制和采购执行必须在完成取证并实现后才可接入,因此此入口不导入
|
||||||
|
uiautomator2,也不提供任何会影响采购或支付状态的命令。
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
paths = RuntimePaths.default()
|
||||||
|
logger = configure_application_logger(paths)
|
||||||
|
except OSError as error:
|
||||||
|
print(f"无法创建采购工具运行目录:{error}", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
try:
|
||||||
|
from PySide6.QtCore import Qt
|
||||||
|
from PySide6.QtWidgets import QApplication, QLabel, QMainWindow
|
||||||
|
except ImportError:
|
||||||
|
logger.error("缺少 PySide6,无法启动桌面界面。")
|
||||||
|
print("无法启动采购工具:缺少 PySide6。请先安装 requirements.txt 中的依赖。", file=sys.stderr)
|
||||||
|
return 2
|
||||||
|
|
||||||
|
application = QApplication.instance() or QApplication(select_application_argv(argv))
|
||||||
|
application.setApplicationName("采购工具")
|
||||||
|
|
||||||
|
window = QMainWindow()
|
||||||
|
window.setWindowTitle("采购工具")
|
||||||
|
window.setAccessibleName("采购工具")
|
||||||
|
window.setMinimumSize(420, 240)
|
||||||
|
window.resize(560, 320)
|
||||||
|
|
||||||
|
message = QLabel("应用骨架已初始化。\n采购执行功能尚未启用。")
|
||||||
|
message.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||||
|
message.setWordWrap(True)
|
||||||
|
message.setAccessibleName("当前状态")
|
||||||
|
window.setCentralWidget(message)
|
||||||
|
|
||||||
|
logger.info("应用已启动;采购执行功能尚未启用。")
|
||||||
|
window.show()
|
||||||
|
return application.exec()
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
"""采购工具日志的最小脱敏策略。"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .runtime import RuntimePaths
|
||||||
|
|
||||||
|
|
||||||
|
LOGGER_NAME = "cmbuyer_client"
|
||||||
|
REDACTED = "[已隐藏]"
|
||||||
|
|
||||||
|
_SENSITIVE_KEY_PATTERN = (
|
||||||
|
r"token|authorization|password|secret|api[_-]?key|"
|
||||||
|
r"address|phone|mobile|payment|pay|card|bank[_-]?account|"
|
||||||
|
r"令牌|授权|密码|密钥|地址|手机号|电话|支付|银行卡"
|
||||||
|
)
|
||||||
|
_KEY_VALUE_PATTERN = re.compile(
|
||||||
|
rf"(?P<key>{_SENSITIVE_KEY_PATTERN})\s*(?P<separator>[:=])\s*"
|
||||||
|
r"(?P<value>\"[^\"]*\"|'[^']*'|[^\s,;]+)",
|
||||||
|
flags=re.IGNORECASE,
|
||||||
|
)
|
||||||
|
_PHONE_PATTERN = re.compile(r"(?<!\d)1[3-9]\d{9}(?!\d)")
|
||||||
|
|
||||||
|
|
||||||
|
def redact_text(message: str) -> str:
|
||||||
|
"""移除日志文本中的凭据、地址、手机号和支付字段值。"""
|
||||||
|
|
||||||
|
def replace_key_value(match: re.Match[str]) -> str:
|
||||||
|
return f"{match.group('key')}{match.group('separator')}{REDACTED}"
|
||||||
|
|
||||||
|
redacted = _KEY_VALUE_PATTERN.sub(replace_key_value, message)
|
||||||
|
return _PHONE_PATTERN.sub(REDACTED, redacted)
|
||||||
|
|
||||||
|
|
||||||
|
class SensitiveDataFilter(logging.Filter):
|
||||||
|
"""在任何 handler 格式化记录前,清除敏感字段。
|
||||||
|
|
||||||
|
该过滤器在日志写入前替换 ``msg`` 与 ``args``,确保文件 handler 不会得到原始值。
|
||||||
|
"""
|
||||||
|
|
||||||
|
def filter(self, record: logging.LogRecord) -> bool:
|
||||||
|
record.msg = redact_text(record.getMessage())
|
||||||
|
record.args = ()
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def configure_application_logger(paths: RuntimePaths) -> logging.Logger:
|
||||||
|
"""配置唯一的 UTF-8 文件日志,并确保其先经过脱敏过滤。"""
|
||||||
|
|
||||||
|
paths.ensure_exists()
|
||||||
|
logger = logging.getLogger(LOGGER_NAME)
|
||||||
|
logger.setLevel(logging.INFO)
|
||||||
|
logger.propagate = False
|
||||||
|
|
||||||
|
for handler in tuple(logger.handlers):
|
||||||
|
logger.removeHandler(handler)
|
||||||
|
handler.close()
|
||||||
|
|
||||||
|
handler = logging.FileHandler(Path(paths.logs) / "client.log", encoding="utf-8")
|
||||||
|
handler.addFilter(SensitiveDataFilter())
|
||||||
|
handler.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(message)s"))
|
||||||
|
logger.addHandler(handler)
|
||||||
|
return logger
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
"""采购工具的本地运行目录策略。"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class RuntimePaths:
|
||||||
|
"""仅包含应用生成物的本地目录。
|
||||||
|
|
||||||
|
日志和证据产物不落在源码树中,避免把含有现场信息的运行数据误提交到 Git。
|
||||||
|
"""
|
||||||
|
|
||||||
|
root: Path
|
||||||
|
logs: Path
|
||||||
|
artifacts: Path
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_root(cls, root: Path) -> "RuntimePaths":
|
||||||
|
resolved_root = root.expanduser()
|
||||||
|
return cls(
|
||||||
|
root=resolved_root,
|
||||||
|
logs=resolved_root / "logs",
|
||||||
|
artifacts=resolved_root / "artifacts",
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def default(cls) -> "RuntimePaths":
|
||||||
|
local_app_data = os.environ.get("LOCALAPPDATA")
|
||||||
|
if local_app_data:
|
||||||
|
return cls.from_root(Path(local_app_data) / "cmbuyer")
|
||||||
|
|
||||||
|
return cls.from_root(Path.home() / ".local" / "share" / "cmbuyer")
|
||||||
|
|
||||||
|
def ensure_exists(self) -> None:
|
||||||
|
"""创建运行目录;调用方负责向用户呈现无法创建目录的错误。"""
|
||||||
|
|
||||||
|
self.logs.mkdir(parents=True, exist_ok=True)
|
||||||
|
self.artifacts.mkdir(parents=True, exist_ok=True)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
"""采购工具的离线单元测试。"""
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
"""验证桌面入口的纯参数处理,不启动 PySide6。"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
import unittest
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
|
||||||
|
CLIENT_ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
sys.path.insert(0, str(CLIENT_ROOT / "src"))
|
||||||
|
|
||||||
|
from cmbuyer_client.app import select_application_argv
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationArgumentsTests(unittest.TestCase):
|
||||||
|
def test_empty_argument_list_is_not_replaced_with_process_arguments(self) -> None:
|
||||||
|
with mock.patch("cmbuyer_client.app.sys.argv", ["process-name", "--process-option"]):
|
||||||
|
self.assertEqual([], select_application_argv([]))
|
||||||
|
|
||||||
|
def test_none_uses_process_arguments(self) -> None:
|
||||||
|
with mock.patch("cmbuyer_client.app.sys.argv", ["process-name", "--process-option"]):
|
||||||
|
self.assertEqual(["process-name", "--process-option"], select_application_argv(None))
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
"""验证日志脱敏边界,不需要 PySide6 或真机。"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
|
||||||
|
CLIENT_ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
sys.path.insert(0, str(CLIENT_ROOT / "src"))
|
||||||
|
|
||||||
|
from cmbuyer_client.logging_policy import configure_application_logger, redact_text
|
||||||
|
from cmbuyer_client.runtime import RuntimePaths
|
||||||
|
|
||||||
|
|
||||||
|
class LoggingPolicyTests(unittest.TestCase):
|
||||||
|
def test_redact_text_hides_required_sensitive_values(self) -> None:
|
||||||
|
message = (
|
||||||
|
"token=secret-value authorization: Bearer-value "
|
||||||
|
"address='浙江省杭州市' phone=13800138000 payment=card-value"
|
||||||
|
)
|
||||||
|
|
||||||
|
redacted = redact_text(message)
|
||||||
|
|
||||||
|
for raw_value in ("secret-value", "Bearer-value", "浙江省杭州市", "13800138000", "card-value"):
|
||||||
|
self.assertNotIn(raw_value, redacted)
|
||||||
|
self.assertIn("token=[已隐藏]", redacted)
|
||||||
|
|
||||||
|
def test_file_handler_writes_only_redacted_text(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
paths = RuntimePaths.from_root(Path(directory))
|
||||||
|
logger = configure_application_logger(paths)
|
||||||
|
try:
|
||||||
|
logger.info("token=not-for-log phone=13900139000 payment=not-for-log")
|
||||||
|
|
||||||
|
for handler in logger.handlers:
|
||||||
|
handler.flush()
|
||||||
|
content = (paths.logs / "client.log").read_text(encoding="utf-8")
|
||||||
|
finally:
|
||||||
|
# Windows 不允许在 FileHandler 持有文件时删除临时目录。
|
||||||
|
for handler in tuple(logger.handlers):
|
||||||
|
logger.removeHandler(handler)
|
||||||
|
handler.close()
|
||||||
|
|
||||||
|
self.assertNotIn("not-for-log", content)
|
||||||
|
self.assertNotIn("13900139000", content)
|
||||||
|
self.assertIn("[已隐藏]", content)
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
"""验证本地运行目录策略,不需要连接设备。"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
|
||||||
|
CLIENT_ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
sys.path.insert(0, str(CLIENT_ROOT / "src"))
|
||||||
|
|
||||||
|
from cmbuyer_client.runtime import RuntimePaths
|
||||||
|
|
||||||
|
|
||||||
|
class RuntimePathsTests(unittest.TestCase):
|
||||||
|
def test_ensure_exists_creates_only_runtime_directories(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
paths = RuntimePaths.from_root(Path(directory) / "runtime")
|
||||||
|
|
||||||
|
paths.ensure_exists()
|
||||||
|
|
||||||
|
self.assertTrue(paths.logs.is_dir())
|
||||||
|
self.assertTrue(paths.artifacts.is_dir())
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
"""验证 wheel 元数据检查脚本,不构建 wheel 或安装运行时依赖。"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
import zipfile
|
||||||
|
|
||||||
|
|
||||||
|
CLIENT_ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
sys.path.insert(0, str(CLIENT_ROOT / "scripts"))
|
||||||
|
|
||||||
|
from verify_wheel_metadata import dependencies_from_requirements, verify_wheel_metadata
|
||||||
|
|
||||||
|
|
||||||
|
class WheelMetadataTests(unittest.TestCase):
|
||||||
|
def test_metadata_checker_accepts_dependencies_from_the_single_requirements_file(self) -> None:
|
||||||
|
expected_dependencies = dependencies_from_requirements(CLIENT_ROOT / "requirements.txt")
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
wheel_file = Path(directory) / "cmbuyer_client-0.1.0-py3-none-any.whl"
|
||||||
|
metadata = "Metadata-Version: 2.3\n" + "".join(
|
||||||
|
f"Requires-Dist: {dependency}\n" for dependency in sorted(expected_dependencies)
|
||||||
|
)
|
||||||
|
with zipfile.ZipFile(wheel_file, "w") as wheel:
|
||||||
|
wheel.writestr("cmbuyer_client-0.1.0.dist-info/METADATA", metadata)
|
||||||
|
|
||||||
|
self.assertEqual(set(), verify_wheel_metadata(wheel_file, CLIENT_ROOT / "requirements.txt"))
|
||||||
@@ -85,12 +85,12 @@ cmbuyer 是一个自动化采购系统:**采购服务**(网页端,`admin/`
|
|||||||
|
|
||||||
## 当前阶段
|
## 当前阶段
|
||||||
|
|
||||||
**Phase 0 · 地基。** 采购服务骨架已初始化,采购工具骨架仍在进行;尚无采购业务代码。
|
**Phase 0 · 地基。** 采购服务与采购工具骨架均已初始化,尚无采购业务代码。
|
||||||
|
|
||||||
执行按任务依赖驱动,**不按 Phase 整段串行等待**。当前优先路径:
|
执行按任务依赖驱动,**不按 Phase 整段串行等待**。当前优先路径:
|
||||||
|
|
||||||
1. 继续完成 T-002 采购工具骨架;采购服务方向可并行推进 T-004 数据模型。
|
1. T-004 核心数据模型正在进行;T-003 统一入口与 T-101 真机环境盘点可开始。
|
||||||
2. T-002 完成后立即推进 **T-101 → T-102 → T-103 真机取证**,并可并行补 T-003 统一入口。
|
2. T-002 已完成,立即推进 **T-101 → T-102 → T-103 真机取证**,并可并行补 T-003 统一入口。
|
||||||
3. T-103 结论确认后,才开始依赖真机可读字段的 Phase 2 生产页面;采购服务核心与
|
3. T-103 结论确认后,才开始依赖真机可读字段的 Phase 2 生产页面;采购服务核心与
|
||||||
T-104 → T-107 后续真机安全判据按依赖并行推进。
|
T-104 → T-107 后续真机安全判据按依赖并行推进。
|
||||||
4. Phase 3:双端打通与**第一趟试选**端到端。
|
4. Phase 3:双端打通与**第一趟试选**端到端。
|
||||||
@@ -172,22 +172,31 @@ cmbuyer 是一个自动化采购系统:**采购服务**(网页端,`admin/`
|
|||||||
|
|
||||||
## 验证命令
|
## 验证命令
|
||||||
|
|
||||||
> 采购服务命令已由 T-001 实际验证;采购工具命令在 T-002 完成前仍是占位路径。
|
> 采购服务命令已由 T-001 实际验证;采购工具的离线测试、编译和 wheel 元数据命令已由
|
||||||
|
> Python 3.12 实际验证。当前默认 `python` 是 Python 3.10.11,统一入口尚未正确选择
|
||||||
|
> Python 3.11+,此缺口由 T-003 修复。
|
||||||
|
|
||||||
```bash
|
```powershell
|
||||||
# 采购服务 admin/(改了 Go 代码后)
|
# 采购服务 admin/(改了 Go 代码后)
|
||||||
cd admin
|
cd admin
|
||||||
go test ./...
|
go test ./...
|
||||||
go vet ./...
|
go vet ./...
|
||||||
go build ./...
|
go build ./...
|
||||||
|
|
||||||
# 采购工具 client/(T-002 完成后启用)
|
# 采购工具 client/(使用 Python 3.11+;本机已验证 3.12)
|
||||||
cd client
|
cd client
|
||||||
python -m unittest discover -s tests -t .
|
py -3.12 -m unittest discover -s tests -t .
|
||||||
python -m compileall -q src tests
|
py -3.12 -m compileall -q src tests scripts
|
||||||
|
$wheelDir = Join-Path $env:TEMP ('cmbuyer-client-wheel-' + [guid]::NewGuid())
|
||||||
|
New-Item -ItemType Directory -Path $wheelDir | Out-Null
|
||||||
|
py -3.12 -m pip wheel --no-deps . --wheel-dir $wheelDir
|
||||||
|
py -3.12 scripts/verify_wheel_metadata.py (Get-ChildItem $wheelDir -Filter '*.whl').FullName
|
||||||
|
|
||||||
# 跨端契约改动:两端全跑
|
# 跨端契约改动:两端全跑
|
||||||
```
|
```
|
||||||
|
|
||||||
验证层级何时触发见 [`03-tech-stack.md`](03-tech-stack.md) 第六节验证矩阵。
|
验证层级何时触发见 [`03-tech-stack.md`](03-tech-stack.md) 第六节验证矩阵。
|
||||||
如果命令当前不可运行,必须在回复里如实说明原因。
|
`requirements.txt` 是采购工具唯一的运行时依赖来源,`pyproject.toml` 动态读取它写入 wheel
|
||||||
|
元数据。规范安装是 Python 3.11+ 虚拟环境中的 `python -m pip install -e .`,已由 Python 3.12
|
||||||
|
验证通过;桌面 GUI 和真机流程不属于本次验收。`init.ps1` 仍未正确选择 Python 3.11+,统一入口
|
||||||
|
由 T-003 修复。若命令当前不可运行,必须在回复里如实说明原因。
|
||||||
|
|||||||
+17
-7
@@ -59,15 +59,25 @@
|
|||||||
|
|
||||||
## 五、构建与运行命令
|
## 五、构建与运行命令
|
||||||
|
|
||||||
> 采购服务命令已由 T-001 实际验证;采购工具命令由 T-002 完成后替换剩余占位项。
|
> 采购服务命令已由 T-001 实际验证。采购工具以下离线测试、编译和 wheel 元数据命令已由
|
||||||
|
> Python 3.12 实际验证;桌面 GUI 和真机流程不属于 T-002 验收范围。
|
||||||
|
|
||||||
| 用途 | 采购服务(`admin/`) | 采购工具(`client/`) |
|
| 用途 | 采购服务(`admin/`) | 采购工具(`client/`) |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| 安装依赖 | `go mod download` | `python -m venv .venv` + `pip install -r requirements.txt` |
|
| 安装依赖 | `go mod download` | `py -3.12 -m venv .venv` 后运行 `.\.venv\Scripts\python.exe -m pip install -e .` |
|
||||||
| 本地开发 | `go run ./cmd/server` | 【T-002 填写】 |
|
| 本地开发 | `go run ./cmd/server` | `.\.venv\Scripts\python.exe -m cmbuyer_client` |
|
||||||
| 构建 | `go build ./...` | 【T-002 填写】 |
|
| 构建 | `go build ./...` | `py -3.12 -m pip wheel --no-deps . --wheel-dir <输出目录>`,再运行 `py -3.12 scripts/verify_wheel_metadata.py <wheel 路径>` |
|
||||||
| 测试 | `go test ./...` | `python -m unittest discover -s tests -t .` |
|
| 测试 | `go test ./...` | `py -3.12 -m unittest discover -s tests -t .` |
|
||||||
| 静态检查 | `go vet ./...` | `python -m compileall -q src tests` |
|
| 静态检查 | `go vet ./...` | `py -3.12 -m compileall -q src tests scripts` |
|
||||||
|
|
||||||
|
采购工具的运行时依赖只维护在 `client/requirements.txt`。`client/pyproject.toml` 通过 setuptools
|
||||||
|
动态读取该文件生成 wheel 的 `Requires-Dist`,避免两份依赖列表漂移;
|
||||||
|
`scripts/verify_wheel_metadata.py` 会验证生成 wheel 已声明全部这些依赖。
|
||||||
|
|
||||||
|
当前 Windows 默认 `python` 指向 Python 3.10.11,不满足采购工具的 Python 3.11+ 下限,
|
||||||
|
不得把未加版本选择器的 `python` 当作采购工具的已验证命令。T-003 负责统一入口与解释器选择;
|
||||||
|
在此之前使用 `py -3.12` 或 Python 3.11+ 虚拟环境。Python 3.12 虚拟环境中的完整
|
||||||
|
`pip install -e .` 已验证通过;桌面 GUI 与真机流程不属于 T-002 验收范围。
|
||||||
|
|
||||||
Windows PowerShell 差异:
|
Windows PowerShell 差异:
|
||||||
|
|
||||||
@@ -80,7 +90,7 @@ $env:GOTOOLCHAIN = "local"
|
|||||||
|
|
||||||
| 层级 | 触发条件 | 命令 / 操作 | 通过证据 |
|
| 层级 | 触发条件 | 命令 / 操作 | 通过证据 |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| 任务相关验证 | 每个任务必跑 | 改 `admin/` 跑 `go test ./...` + `go vet ./...`;改 `client/` 跑 `python -m unittest discover -s tests -t .` + `python -m compileall -q src tests` | 退出码 0、测试数 |
|
| 任务相关验证 | 每个任务必跑 | 改 `admin/` 跑 `go test ./...` + `go vet ./...`;改 `client/` 跑 `py -3.12 -m unittest discover -s tests -t .` + `py -3.12 -m compileall -q src tests scripts` | 退出码 0、测试数 |
|
||||||
| 完整门禁 | 发布前;修改 HTTP 契约、数据库 schema、依赖或构建配置时;跨端改动时 | 两端全部测试 + 静态检查 + 两端构建 | 退出码 0、测试数、产物路径 |
|
| 完整门禁 | 发布前;修改 HTTP 契约、数据库 schema、依赖或构建配置时;跨端改动时 | 两端全部测试 + 静态检查 + 两端构建 | 退出码 0、测试数、产物路径 |
|
||||||
| 人工 / 设备验收 | 任何涉及真机页面判据、下单动作或付款路径的任务 | 连接真机执行,记录设备型号、Android 版本、拼多多版本、goods_id、截图与页面 XML 路径 | 人工结论 + 证据文件路径 |
|
| 人工 / 设备验收 | 任何涉及真机页面判据、下单动作或付款路径的任务 | 连接真机执行,记录设备型号、Android 版本、拼多多版本、goods_id、截图与页面 XML 路径 | 人工结论 + 证据文件路径 |
|
||||||
|
|
||||||
|
|||||||
+39
-12
@@ -12,30 +12,33 @@
|
|||||||
## 当前快照
|
## 当前快照
|
||||||
|
|
||||||
- 日期:2026-08-03
|
- 日期:2026-08-03
|
||||||
- 阶段:**Phase 0 · 地基(采购服务骨架完成,采购工具骨架进行中)**
|
- 阶段:**Phase 0 · 地基(采购服务与采购工具骨架已初始化,尚无采购业务代码)**
|
||||||
- MVP 形态:手工填链接建单 → 批量开始试选 → 定时轮询 → **第一趟试选** → 人工确认 → **第二趟下单** → 待付款
|
- MVP 形态:手工填链接建单 → 批量开始试选 → 定时轮询 → **第一趟试选** → 人工确认 → **第二趟下单** → 待付款
|
||||||
- 技术栈:已定。采购服务(`admin/`)使用 Go 1.23+ / gin / SQLite;采购工具(`client/`)
|
- 技术栈:已定。采购服务(`admin/`)使用 Go 1.23+ / gin / SQLite;采购工具(`client/`)
|
||||||
使用 Python 3.11+ / uiautomator2 / PySide6。
|
使用 Python 3.11+ / uiautomator2 / PySide6。
|
||||||
详见 [`03-tech-stack.md`](03-tech-stack.md)
|
详见 [`03-tech-stack.md`](03-tech-stack.md)
|
||||||
- 生产代码:`admin/` 已有最小 Go 服务、`GET /healthz` 与 SQLite 驱动封装;尚无采购业务代码
|
- 生产代码:`admin/` 已有最小 Go 服务、`GET /healthz` 与 SQLite 驱动封装;`client/` 已有
|
||||||
- 测试:采购服务 2 项离线单元测试
|
Python 包、PySide6 最小入口、运行目录与日志脱敏策略;两端均尚无采购业务代码
|
||||||
|
- 测试:采购服务 2 项离线单元测试;采购工具 6 项离线单元测试(不连接真机)
|
||||||
- 数据:**无**
|
- 数据:**无**
|
||||||
- 标准启动路径:`./init.ps1`(Windows)/ `./init.sh`。当前会验证采购服务,再因
|
- 标准启动路径:`./init.ps1`(Windows)/ `./init.sh`。两端骨架现已存在,但 `init.ps1` 仍使用
|
||||||
`client/requirements.txt` 尚不存在以退出码 3 提示 T-002——这是预期行为
|
默认 Python 3.10,不能正确验证要求 Python 3.11+ 的采购工具;T-003 负责统一入口与解释器选择。
|
||||||
- 标准验证路径:`admin/` 下运行 `go test ./...`、`go vet ./...`、`go build ./...`;仓库根运行
|
- 标准验证路径:`admin/` 下运行 `go test ./...`、`go vet ./...`、`go build ./...`;`client/` 下使用
|
||||||
|
`py -3.12 -m unittest discover -s tests -t .`、`py -3.12 -m compileall -q src tests scripts`;仓库根运行
|
||||||
`python scripts/validate_agent_context.py`
|
`python scripts/validate_agent_context.py`
|
||||||
- 当前 blocker:无外部 blocker。T-002 正在实现;T-004 已因 T-001 完成而可领取。
|
- 当前 blocker:无外部 blocker。T-002 已完成;T-004 核心数据模型正在进行;T-003 统一入口与
|
||||||
|
T-101 真机环境盘点已就绪。当前不宣称统一入口或桌面 GUI 已验收。
|
||||||
|
|
||||||
## 当前目录要点
|
## 当前目录要点
|
||||||
|
|
||||||
| 路径 | 状态 | 说明 |
|
| 路径 | 状态 | 说明 |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `docs/` | 已有 | 项目规范化文档,本次已完整生成 |
|
| `docs/` | 已有 | 项目规范化文档,本次已完整生成 |
|
||||||
| `docs/tasks/` | 已有(T-001、T-002、T-005~T-009) | T-001 已完成;T-002 正在进行;其余既有任务已完成 |
|
| `docs/tasks/` | 已有(T-001、T-002、T-004~T-009) | T-001、T-002 已完成;T-004 正在进行;其余既有任务已完成 |
|
||||||
| `docs/design/` | 已有(6 个原型) | web 登录 / 建单 / 工作台 / 详情,desk 采购执行 / 配置;均已人工确认 |
|
| `docs/design/` | 已有(6 个原型) | web 登录 / 建单 / 工作台 / 详情,desk 采购执行 / 配置;均已人工确认 |
|
||||||
| `scripts/` | 已有 | 上下文门禁、Vikunja 单向导出与 MCP 启动包装 |
|
| `scripts/` | 已有 | 上下文门禁、Vikunja 单向导出与 MCP 启动包装 |
|
||||||
| `admin/` | 已初始化 | Go 1.23+ / gin / SQLite 骨架与健康检查;无采购业务路由 |
|
| `admin/` | 已初始化 | Go 1.23+ / gin / SQLite 骨架与健康检查;无采购业务路由 |
|
||||||
| `client/` | **待建** | “采购工具”Python 桌面端(T-002) |
|
| `client/` | 已初始化 | Python 3.11+ 包、依赖源、PySide6 最小入口、离线测试与 wheel 元数据检查;无真机或采购流程 |
|
||||||
| `init.ps1` / `init.sh` | 已有(骨架) | 统一入口。两端目录建好后由 T-003 补全并验证 |
|
| `init.ps1` / `init.sh` | 已有(骨架) | 统一入口。两端目录建好后由 T-003 补全并验证 |
|
||||||
|
|
||||||
## 任务状态
|
## 任务状态
|
||||||
@@ -45,8 +48,8 @@
|
|||||||
- 已完成:T-005(采购服务交互原型)、T-006(采购工具交互原型)、T-007(产品名称与
|
- 已完成:T-005(采购服务交互原型)、T-006(采购工具交互原型)、T-007(产品名称与
|
||||||
源码目录契约)、T-008(Vikunja 任务权威与单向导出)、T-009(MVP 关键路径与并行波次),
|
源码目录契约)、T-008(Vikunja 任务权威与单向导出)、T-009(MVP 关键路径与并行波次),
|
||||||
以及 T-001(采购服务 Go 骨架)。
|
以及 T-001(采购服务 Go 骨架)。
|
||||||
- 正在进行:T-002(采购工具 Python 骨架)。采购服务方向可并行落成 T-004;T-002 完成后
|
- 已完成:T-002(采购工具 Python 骨架)。正在进行:T-004(核心数据模型)。
|
||||||
立即推进 T-101 → T-102 → T-103,并可补 T-003 统一入口。
|
T-003(统一入口)与 T-101(真机环境盘点)已就绪,立即推进 T-101 → T-102 → T-103。
|
||||||
- T-103 是当前最高优先级和 MVP 生死线。通过前不开发依赖真机可读字段的 Phase 2 生产页面。
|
- T-103 是当前最高优先级和 MVP 生死线。通过前不开发依赖真机可读字段的 Phase 2 生产页面。
|
||||||
- 已确认原型继续只作信息架构依据;原型假数据不调用真实接口、不驱动真机。真机结论改变
|
- 已确认原型继续只作信息架构依据;原型假数据不调用真实接口、不驱动真机。真机结论改变
|
||||||
可读字段时必须先回修原型与交互清单。
|
可读字段时必须先回修原型与交互清单。
|
||||||
@@ -73,7 +76,31 @@ cd ..
|
|||||||
python scripts/validate_agent_context.py
|
python scripts/validate_agent_context.py
|
||||||
```
|
```
|
||||||
|
|
||||||
采购工具与统一入口的真实命令分别等待 T-002、T-003 完成后补齐。
|
采购工具当前可运行的离线验证与 wheel 元数据检查:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
cd client
|
||||||
|
py -3.12 -m unittest discover -s tests -t .
|
||||||
|
py -3.12 -m compileall -q src tests scripts
|
||||||
|
$wheelDir = Join-Path $env:TEMP ('cmbuyer-client-wheel-' + [guid]::NewGuid())
|
||||||
|
New-Item -ItemType Directory -Path $wheelDir | Out-Null
|
||||||
|
py -3.12 -m pip wheel --no-deps . --wheel-dir $wheelDir
|
||||||
|
py -3.12 scripts/verify_wheel_metadata.py (Get-ChildItem $wheelDir -Filter '*.whl').FullName
|
||||||
|
```
|
||||||
|
|
||||||
|
采购工具规范安装与本地开发入口:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
cd client
|
||||||
|
py -3.12 -m venv .venv
|
||||||
|
.\.venv\Scripts\python.exe -m pip install -e .
|
||||||
|
.\.venv\Scripts\python.exe -m cmbuyer_client
|
||||||
|
```
|
||||||
|
|
||||||
|
`client/requirements.txt` 是唯一依赖来源,`client/pyproject.toml` 动态读取它生成 wheel 的
|
||||||
|
`Requires-Dist`。Python 3.12 已验证 6 项离线测试、编译与 wheel 元数据;完整运行时依赖安装
|
||||||
|
(`pip install -e .`)已通过。`init.ps1` 当前仍选择 Python 3.10,因此不能声称统一入口已正确;
|
||||||
|
桌面 GUI 与真机流程未作为 T-002 验收执行。
|
||||||
|
|
||||||
## 关键背景
|
## 关键背景
|
||||||
|
|
||||||
|
|||||||
+10
-3
@@ -3,7 +3,7 @@ id: T-002
|
|||||||
title: 初始化采购工具 client Python 骨架
|
title: 初始化采购工具 client Python 骨架
|
||||||
phase: 0
|
phase: 0
|
||||||
deps: []
|
deps: []
|
||||||
status: DOING
|
status: DONE
|
||||||
created: 2026-08-03
|
created: 2026-08-03
|
||||||
vikunja_task_id: 17
|
vikunja_task_id: 17
|
||||||
context_ref: a63bdb8
|
context_ref: a63bdb8
|
||||||
@@ -13,9 +13,12 @@ needs_human_review: false
|
|||||||
write_paths:
|
write_paths:
|
||||||
- docs/tasks/T-002.md
|
- docs/tasks/T-002.md
|
||||||
- client/**
|
- client/**
|
||||||
|
- docs/03-tech-stack.md
|
||||||
|
- docs/00-ai-start-here.md
|
||||||
|
- docs/current-state.md
|
||||||
---
|
---
|
||||||
|
|
||||||
<!-- BEGIN VIKUNJA EXPORT id=17 synced=2026-08-03T09:57:46Z sha256=14061aa9aca2b854afd2b657c051f593e29272ed67327d0c840d499708f8c821 -->
|
<!-- BEGIN VIKUNJA EXPORT id=17 synced=2026-08-03T10:31:33Z sha256=6f7f0738647530406d746c46f898df8f572153cbce0180fe18fcc07c10e9e9d3 -->
|
||||||
## 问题 / 背景
|
## 问题 / 背景
|
||||||
|
|
||||||
仓库尚无 client/requirements.txt 和可运行的采购工具代码,init.ps1 因此按预期以退出码 3 停止。没有稳定的 Python 包、测试入口、日志与产物策略,后续 ADB/uiautomator2 真机取证无法开始。
|
仓库尚无 client/requirements.txt 和可运行的采购工具代码,init.ps1 因此按预期以退出码 3 停止。没有稳定的 Python 包、测试入口、日志与产物策略,后续 ADB/uiautomator2 真机取证无法开始。
|
||||||
@@ -44,7 +47,11 @@ write_paths:
|
|||||||
|
|
||||||
## 执行记录
|
## 执行记录
|
||||||
|
|
||||||
(暂无)
|
### 2026-08-03T10:30:56Z · ila
|
||||||
|
|
||||||
|
主 agent 终审通过:采购工具 Python 3.11+ 骨架、PySide6 最小入口、运行目录和敏感日志脱敏策略均已落成;无真机、页面判据、下单或付款逻辑。
|
||||||
|
独立验证:6 项 unittest 通过;compileall 通过;wheel 构建及 requirements.txt→Requires-Dist 元数据核对通过;Python 3.12 editable install 通过;离屏窗口构造通过;采购服务 race/vet/build 回归及 agent-context 门禁通过。
|
||||||
|
共享文档已更新为 T-002 完成快照,并如实保留 init.ps1 默认 Python 3.10 风险给 T-003。
|
||||||
<!-- END VIKUNJA EXPORT -->
|
<!-- END VIKUNJA EXPORT -->
|
||||||
|
|
||||||
## 边界
|
## 边界
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
---
|
||||||
|
id: T-004
|
||||||
|
title: 建立核心数据模型与状态机
|
||||||
|
phase: 0
|
||||||
|
deps: [T-001]
|
||||||
|
status: DOING
|
||||||
|
created: 2026-08-03
|
||||||
|
vikunja_task_id: 19
|
||||||
|
context_ref: c72371c
|
||||||
|
work_branch: task/t-004-core-data-state
|
||||||
|
needs_device: false
|
||||||
|
needs_human_review: false
|
||||||
|
write_paths:
|
||||||
|
- docs/tasks/T-004.md
|
||||||
|
- admin/go.mod
|
||||||
|
- admin/go.sum
|
||||||
|
- admin/cmd/migrate/**
|
||||||
|
- admin/internal/domain/**
|
||||||
|
- admin/internal/migrations/**
|
||||||
|
- admin/internal/storage/sqlite/**
|
||||||
|
- admin/migrations/**
|
||||||
|
- docs/04-architecture.md
|
||||||
|
- docs/api.md
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- BEGIN VIKUNJA EXPORT id=19 synced=2026-08-03T10:16:07Z sha256=dc6e71b81abdf95ef5942603c31944dd3eb76141e9d2edc8edef56d71a1b1d4c -->
|
||||||
|
## 问题 / 背景
|
||||||
|
|
||||||
|
T-001 已建立可运行的采购服务骨架,但尚无业务实体、数据库 schema 或可验证的状态机。后续登录、建单、领取、授权和提交围栏都依赖一致的数据模型;若先写 HTTP 页面再补状态约束,会把资金安全边界分散到处理器中。
|
||||||
|
|
||||||
|
## 关联需求与交互
|
||||||
|
|
||||||
|
- 功能:Phase 0 核心数据模型地基。
|
||||||
|
- 用户故事 / 交互:不适用,不实现生产页面。
|
||||||
|
- 架构 / API:以 docs/04-architecture.md 第四、第五节和 docs/api.md 为权威;schema 变化必须同步两者。
|
||||||
|
|
||||||
|
## 方案
|
||||||
|
|
||||||
|
1. 从现有架构/API逐字段提取 tasks、order_authorizations、order_submissions,不臆造接口字段。
|
||||||
|
2. 使用 goose 与 SQLite 建立可重复迁移及约束;金额只存十进制字符串,禁止 float。
|
||||||
|
3. 在 admin/internal/domain 建立显式状态与合法转换,非法转换 fail closed,并用表驱动测试覆盖。
|
||||||
|
4. 对一次性授权、提交围栏和不可重试结果只建立数据约束与纯状态规则,不实现 HTTP、真机点击或下单函数。
|
||||||
|
5. T-002 为 DOING 时不修改 docs/current-state.md;代码与架构/API完成后先记录验证,待 T-002 关闭再扩展 write_paths 并收口状态快照。
|
||||||
|
|
||||||
|
## 验收要点
|
||||||
|
|
||||||
|
- tasks、order_authorizations、order_submissions 表和约束与架构文档一致,迁移可重复向上/向下验证。
|
||||||
|
- 核心状态合法/非法流转有单元测试;第一趟状态路径不引用任何下单函数。
|
||||||
|
- 金额字段是十进制字符串,代码和 schema 不使用浮点数。
|
||||||
|
- go test ./...、go vet ./...、go build ./... 通过。
|
||||||
|
- 不实现 HTTP 业务端点、页面、真机判据、提交订单点击或付款。
|
||||||
|
|
||||||
|
## 执行记录
|
||||||
|
|
||||||
|
(暂无)
|
||||||
|
<!-- END VIKUNJA EXPORT -->
|
||||||
|
|
||||||
|
## 边界
|
||||||
|
|
||||||
|
- 不实现 HTTP 业务端点、管理页面、设备领取、真机自动化或 Phase 2+ 服务。
|
||||||
|
- 不编写或引用任何真机下单函数,不点击提交订单,不涉及付款。
|
||||||
|
- 不放宽 `docs/04-architecture.md` 第四节任何边界;代码与文档不一致时先停止并由主 agent
|
||||||
|
复核,不以“跑通”为理由改弱约束。
|
||||||
|
- 不修改 `client/`、需求、交互原型、启动入口或 T-002 当前占用的 `docs/current-state.md`。
|
||||||
|
- 如完成代码后仍需更新 `docs/current-state.md`,必须等待 T-002 关闭并先提交 `write_paths`
|
||||||
|
扩展;不得在两个 `DOING` 任务间制造共享路径冲突。
|
||||||
|
- 不使用前序项目 schema 作为事实来源;所有字段和状态只取自本仓库架构/API。
|
||||||
Reference in New Issue
Block a user