plan-governance-cli 3.0.1 → 3.0.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/README.md
CHANGED
|
@@ -140,6 +140,8 @@ AGENTS.md
|
|
|
140
140
|
|
|
141
141
|
## 更新已有项目
|
|
142
142
|
|
|
143
|
+
全局 npm 安装、`setup` 同步 skill、项目入口刷新是三个独立步骤。前两步不更新各项目的 `AGENTS.md`;`check` 会对已有地图项目的治理受管块提示差异、损坏或不可读取,严格模式也仅告警,不自动覆盖。差异可能来自定制,先核对再更新;规则来源与诊断边界见 [CLI 参考](resources/skill/references/cli.md#分发与资源)。
|
|
144
|
+
|
|
143
145
|
如果项目已经有 `docs/PLAN_MAP.md` 和计划文件,不要重新初始化计划文档。只更新代理执行规则时运行:
|
|
144
146
|
|
|
145
147
|
```bash
|
package/package.json
CHANGED
|
@@ -10,17 +10,19 @@
|
|
|
10
10
|
|
|
11
11
|
| 计划 | 状态 | 当前阶段 | 最后更新 | 依赖 | 证据 |
|
|
12
12
|
|---|---|---|---|---|---|
|
|
13
|
-
|
|
13
|
+
{{unfinished_row}}
|
|
14
14
|
|
|
15
15
|
### 已完成
|
|
16
16
|
|
|
17
17
|
| 计划 | 状态 | 当前阶段 | 最后更新 | 依赖 | 证据 |
|
|
18
18
|
|---|---|---|---|---|---|
|
|
19
|
+
{{completed_row}}
|
|
19
20
|
|
|
20
21
|
### 已废弃
|
|
21
22
|
|
|
22
23
|
| 计划 | 状态 | 当前阶段 | 最后更新 | 依赖 | 证据 |
|
|
23
24
|
|---|---|---|---|---|---|
|
|
25
|
+
{{deprecated_row}}
|
|
24
26
|
|
|
25
27
|
允许状态:`候选`、`设计中`、`待实施`、`实施中`、`已完成`、`已替代`、`已合并`、`已废弃`。
|
|
26
28
|
|
|
@@ -34,6 +36,7 @@
|
|
|
34
36
|
- 阶段 N 完成后,阶段 N+1 默认保持 `设计中`;专项计划自己的准入复核通过后才能进入 `待实施`。
|
|
35
37
|
- 地图只更新当前摘要和有效证据入口;实施、部署、测试和反馈详情写入所属计划或报告,不在页首、索引或单元格内追加日志。
|
|
36
38
|
- 阶段准入摘要、样本矩阵和复核记录只写入专项计划,不复制到本索引。
|
|
39
|
+
- 已有草案、历史设计和临时分析不再作为规范事实源;后续新规范默认进入当前计划或适用契约,旧记录保留为背景。
|
|
37
40
|
- 专项计划记录本次行为差异和验收;现行契约优先引用已有 Schema/OpenAPI,无合适来源且需长期维护时才按需建立 `docs/specs/*.md`。同一事实不重复维护。
|
|
38
41
|
- 专项计划可在当前阶段声明阶段证据和追加式实施/验证记录;完成快照默认保持旧 JSON 兼容格式,发布/合规场景再显式使用 purpose 关系。
|
|
39
42
|
- `--drift`/`--pre-commit` 的关闭窗口只在已完成计划自身也发生变更时覆盖该计划自身和显式阶段证据,不复用已完成计划的完整影响范围。
|
|
@@ -45,13 +48,13 @@
|
|
|
45
48
|
|
|
46
49
|
只列仍需推进的事项及下一步依据,完成过程留在所属计划。
|
|
47
50
|
|
|
48
|
-
1. `
|
|
51
|
+
1. `{{plan_slug}}`
|
|
49
52
|
|
|
50
53
|
## 依赖关系
|
|
51
54
|
|
|
52
55
|
| 计划 | 依赖 | 原因 |
|
|
53
56
|
|---|---|---|
|
|
54
|
-
|
|
|
57
|
+
| {{plan_slug}} | - | - |
|
|
55
58
|
|
|
56
59
|
## 替代、合并和废弃
|
|
57
60
|
|
|
@@ -26,6 +26,12 @@
|
|
|
26
26
|
|
|
27
27
|
仅更新计划格式不等于更新项目入口。已有项目在任务交接、没有进行中的实施或复核时更新受管入口;升级不批量回写历史,旧策略名和六列格式按 verification 解释为默认自验,保留真实历史发现。安装副本与仓库开发规则有差异时说明本次选用来源,不能因版本号相同就混读正文。
|
|
28
28
|
|
|
29
|
+
项目受管入口只保留规范发现、授权、计划定位和必要CLI约束;复核策略、阶段门及记录细节引用同源 verification/planning/cli,不在入口重复维护。
|
|
30
|
+
|
|
31
|
+
升级分三层:npm 安装更新 CLI 与包内资源,`setup` 更新用户目录 skill,项目的 `AGENTS.md` 则由 `init --update-agent-rules-only` 单独刷新。前两步不会自动更新各项目。已有地图的 `check`(含聚焦/严格模式)会只读对比 AGENTS 受管块与所用工具自带规则:`AGENT_RULES_DRIFT` 表示内容不同,可能过期或含定制,核对后可执行诊断给出的刷新命令;仅忽略换行方式和行尾空白,受管块外自定义内容不参与比较。无文件/无受管标记时跳过,代码围栏中的示例标记不算入口。
|
|
32
|
+
|
|
33
|
+
受管标记缺失配对、重复或倒序报告 `AGENT_RULES_INVALID`,先人工核对;显式入口刷新也在写入前拒绝这些损坏标记,并保留围栏中的示例及受管块外原文。读取失败、非 UTF-8、非普通文件或符号链接报告 `AGENT_RULES_UNAVAILABLE`,明确未完成核对。三类诊断均为项目级 advisory/WARNING,严格模式也不提升为业务错误,不改变 workset 的阶段判断或自动覆盖文件;原有业务错误照常失败。复制到项目的单文件 checker 只能比较其自身版本规则,不能发现线上最新版本。
|
|
34
|
+
|
|
29
35
|
## 初始化、模板和检查脚本
|
|
30
36
|
|
|
31
37
|
- `../assets/PLAN_MAP.template.md`:治理入口模板。
|
|
@@ -50,6 +56,8 @@ plan-governance-cli init \
|
|
|
50
56
|
|
|
51
57
|
新计划仅允许 候选/设计中,phase 必须是阶段身份。初始化先预检全部可预测目标冲突和模式参数,再执行 `git init` 和写入;已有 `.git/` 时跳过。升级/入口-only/迁移模式拒绝无关参数。
|
|
52
58
|
|
|
59
|
+
地图正文只维护在同包 `assets/PLAN_MAP.template.md`,初始化器渲染日期路径、计划行和分类占位符;模板缺失、空、占位符错误或基本索引结构损坏时,在任何项目写入前失败,不回退内嵌旧模板。该能力不重排已有地图,入口-only/升级模式也不依赖地图模板。
|
|
60
|
+
|
|
53
61
|
只更新已有项目的代理规则,不修改 `docs/`:
|
|
54
62
|
|
|
55
63
|
```bash
|
|
@@ -5,6 +5,8 @@ import json
|
|
|
5
5
|
import posixpath
|
|
6
6
|
import re
|
|
7
7
|
import secrets
|
|
8
|
+
import shlex
|
|
9
|
+
import stat
|
|
8
10
|
import subprocess
|
|
9
11
|
import sys
|
|
10
12
|
from datetime import date, datetime, timezone
|
|
@@ -33,6 +35,21 @@ ATTESTATION_REVIEW_STATUSES = {"current", "superseded", "needs_review"}
|
|
|
33
35
|
ATTESTATION_SNAPSHOT_RE = re.compile(r"^\d{8}T\d{6}Z-[0-9a-f]{8}$")
|
|
34
36
|
|
|
35
37
|
|
|
38
|
+
# 与初始化器共用正文;单文件复制的检查器也能核对同版本入口。
|
|
39
|
+
def agent_rules_body():
|
|
40
|
+
return """## 计划治理
|
|
41
|
+
|
|
42
|
+
处理已有计划或需要持续协调的工作时,读取实际发现的 `plan-governance` skill;开发或审查该 skill 自身时使用仓库源及对应 CLI。规范来源一次选定后复用,按当前动作读取同源 references。
|
|
43
|
+
|
|
44
|
+
只有 CLI 可用时,以 `plan-governance-cli guide` 为入口,再按需读取 `guide planning`、`guide verification` 或 `guide cli`。所需规则或能力不可用时说明缺口,不自动安装,不拼接不同版本。
|
|
45
|
+
|
|
46
|
+
- 按用户授权和当前计划范围推进;未解决问题如实保留,流程建议不扩大实施或外部操作授权。阶段准入、复核方式、验证与适用用户验收统一按所选规范的 verification 执行。
|
|
47
|
+
- 从 `docs/PLAN_MAP.md` 定位相关计划、当前阶段、依赖和证据,再按索引读取必要文档;恢复先核对当前事实与实际差异。文档职责、记录格式和状态同步统一按 planning 执行。
|
|
48
|
+
- 多计划/多会话并行时,在 check/workset/hook 中显式传入本会话的 `--plan ID`;必要前置、共享写入及写入前复核按 cli 执行,不设置全项目可变焦点。
|
|
49
|
+
- 完成时运行 `plan-governance-cli check .`;准入/发布使用 `--strict-readiness`。机械检查不替代实际验证和用户验收。
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
|
|
36
53
|
def fail(errors, message):
|
|
37
54
|
errors.append(message)
|
|
38
55
|
|
|
@@ -41,6 +58,51 @@ def warn(warnings, message):
|
|
|
41
58
|
warnings.append(message)
|
|
42
59
|
|
|
43
60
|
|
|
61
|
+
def agent_rules_markers(text, begin="<!-- plan-governance:start -->", end="<!-- plan-governance:end -->"):
|
|
62
|
+
"""读取与更新共用边界;位置保留原换行字节,忽略代码围栏示例。"""
|
|
63
|
+
visible = mask_fenced_code(text)
|
|
64
|
+
starts = list(re.finditer(rf"^{re.escape(begin)}(?=[ \t]*\r?$)", visible, re.MULTILINE))
|
|
65
|
+
ends = list(re.finditer(rf"^{re.escape(end)}(?=[ \t]*\r?$)", visible, re.MULTILINE))
|
|
66
|
+
if not starts and not ends:
|
|
67
|
+
return None
|
|
68
|
+
if len(starts) != 1 or len(ends) != 1 or starts[0].end() >= ends[0].start():
|
|
69
|
+
raise ValueError("AGENTS.md 治理受管标记缺失配对、重复或倒序;请先人工核对")
|
|
70
|
+
return starts[0], ends[0]
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def agent_rules_issue(root):
|
|
74
|
+
"""仅核对受管入口;返回项目级建议,不推断业务准入或自动覆盖。"""
|
|
75
|
+
path = root / "AGENTS.md"
|
|
76
|
+
try:
|
|
77
|
+
info = path.lstat()
|
|
78
|
+
except FileNotFoundError:
|
|
79
|
+
return None
|
|
80
|
+
except OSError:
|
|
81
|
+
return "AGENT_RULES_UNAVAILABLE", "AGENTS.md 无法读取,未完成治理入口核对"
|
|
82
|
+
if not stat.S_ISREG(info.st_mode):
|
|
83
|
+
return "AGENT_RULES_UNAVAILABLE", "AGENTS.md 不是普通文件(含符号链接),未完成治理入口核对"
|
|
84
|
+
try:
|
|
85
|
+
text = path.read_text(encoding="utf-8")
|
|
86
|
+
except (OSError, UnicodeError):
|
|
87
|
+
return "AGENT_RULES_UNAVAILABLE", "AGENTS.md 无法读取为 UTF-8,未完成治理入口核对"
|
|
88
|
+
try:
|
|
89
|
+
markers = agent_rules_markers(text)
|
|
90
|
+
except ValueError as exc:
|
|
91
|
+
return "AGENT_RULES_INVALID", f"{exc},未比较规则内容"
|
|
92
|
+
if not markers:
|
|
93
|
+
return None
|
|
94
|
+
|
|
95
|
+
def normalized(value):
|
|
96
|
+
return "\n".join(line.rstrip() for line in value.strip().splitlines())
|
|
97
|
+
|
|
98
|
+
start, end = markers
|
|
99
|
+
actual = text[start.end():end.start()]
|
|
100
|
+
if normalized(actual) != normalized(agent_rules_body()):
|
|
101
|
+
command = f"plan-governance-cli init --root {shlex.quote(str(root))} --update-agent-rules-only"
|
|
102
|
+
return "AGENT_RULES_DRIFT", f"AGENTS.md 治理受管块与当前工具不同,可能过期或含定制;核对差异后可执行 `{command}`。不会自动覆盖"
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
|
|
44
106
|
def read_utf8(path, errors):
|
|
45
107
|
try:
|
|
46
108
|
return path.read_text(encoding="utf-8")
|
|
@@ -2998,6 +3060,12 @@ def main(argv=None):
|
|
|
2998
3060
|
readiness_issue(warnings, errors, args.strict_readiness or bool(args.plan), "未找到 docs/PLAN_MAP.md;当前仓库尚未初始化计划治理")
|
|
2999
3061
|
return finish_check(args, records, reports=reports, not_applicable=not errors)
|
|
3000
3062
|
|
|
3063
|
+
issue = agent_rules_issue(root)
|
|
3064
|
+
if issue:
|
|
3065
|
+
code, message = issue
|
|
3066
|
+
warnings.context(category="advisory", code=code, path="AGENTS.md", section="计划治理")
|
|
3067
|
+
warnings.append(message)
|
|
3068
|
+
warnings.context()
|
|
3001
3069
|
text = read_utf8(plan_map, errors)
|
|
3002
3070
|
_, blocker_index_issues = map_blockers(text)
|
|
3003
3071
|
for issue in plan_index_issues(text):
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
import argparse
|
|
3
|
+
import html
|
|
3
4
|
import importlib.util
|
|
4
5
|
import re
|
|
5
6
|
import shutil
|
|
@@ -59,21 +60,7 @@ def init_git(root):
|
|
|
59
60
|
|
|
60
61
|
|
|
61
62
|
def agent_rules_body():
|
|
62
|
-
return
|
|
63
|
-
|
|
64
|
-
本项目使用轻量计划治理。处理已有计划或需要持续协调的工作时,读取实际发现的 `plan-governance` skill;开发或审查该 skill 自身时使用仓库源及对应 CLI。规范来源一次选定后复用,按当前动作读取同源 references。
|
|
65
|
-
|
|
66
|
-
只有 CLI 可用时,运行 `plan-governance-cli guide` 读取入口,再按需运行 `guide planning`、`guide verification` 或 `guide cli`。两者均不可用或所需能力缺失时说明前提,不自动安装,也不猜测/拼接不同版本的规则。
|
|
67
|
-
|
|
68
|
-
最小执行边界:
|
|
69
|
-
|
|
70
|
-
- 普通一次性任务直接处理;已有计划按共享 verification 验证。新旧计划均默认自验,仅在用户明确要求时独立复核;未解决问题继续阻塞。
|
|
71
|
-
- 只实施已授权、已准入的当前阶段。每个阶段有自身 Step 0、验证/完成和失败边界;未解决问题不能记为完成,不可用或超时不能冒充已检查。已有授权不重复索取,新增高影响外部动作按实际授权处理。
|
|
72
|
-
- 首次读取 `docs/PLAN_MAP.md`、索引登记的相关 `docs/plans/` 计划及适用契约/ADR/migration;恢复先核对当前工作集、最近证据和实际 diff,缺失或冲突再展开。
|
|
73
|
-
- 多计划/多会话并行时,每个会话在 check/workset/hook 中显式传入自己的 `--plan ID`;必要前置与共享写入约束仍需读取,写入前重读相关事实,不设置全项目可变焦点。
|
|
74
|
-
- 地图维护状态、阶段、关系、阻塞与证据入口;计划记录本次差异及验证,现行契约优先复用 Schema/OpenAPI。新事实不写回历史草案,变化同步地图及相关引用。
|
|
75
|
-
- 完成时按共享规范记录实际验证与适用用户验收,运行 `plan-governance-cli check .`;准入/CI/发布显式使用 `--strict-readiness`,机械通过不等于业务验收。
|
|
76
|
-
"""
|
|
63
|
+
return checker.agent_rules_body()
|
|
77
64
|
|
|
78
65
|
|
|
79
66
|
def managed_section(begin, end):
|
|
@@ -92,12 +79,10 @@ def update_managed_file(root, filename, section, begin, end):
|
|
|
92
79
|
|
|
93
80
|
# Keep line endings and trailing whitespace outside the managed block intact.
|
|
94
81
|
current = target.read_bytes().decode("utf-8")
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
if pattern.search(current):
|
|
100
|
-
updated = pattern.sub(lambda match: section.rstrip(), current)
|
|
82
|
+
markers = checker.agent_rules_markers(current, begin, end)
|
|
83
|
+
if markers:
|
|
84
|
+
start, finish = markers
|
|
85
|
+
updated = current[:start.start()] + section.rstrip() + current[finish.end():]
|
|
101
86
|
else:
|
|
102
87
|
newline = "\r\n" if "\r\n" in current else "\n"
|
|
103
88
|
if not current or current.endswith(newline * 2):
|
|
@@ -124,82 +109,54 @@ def update_agent_rules(root):
|
|
|
124
109
|
return [update_agents_md(root)]
|
|
125
110
|
|
|
126
111
|
|
|
112
|
+
def plan_map_template_path():
|
|
113
|
+
return Path(__file__).resolve().parents[1] / "resources" / "skill" / "assets" / "PLAN_MAP.template.md"
|
|
114
|
+
|
|
115
|
+
|
|
127
116
|
def plan_map_content(plan_slug, title, status, phase, created_on=None):
|
|
128
117
|
created_on = created_on or date.today()
|
|
129
|
-
|
|
118
|
+
template = plan_map_template_path().read_text(encoding="utf-8")
|
|
119
|
+
if not template.startswith("# PLAN_MAP\n") or len(re.findall(r"^# ", template, re.MULTILINE)) != 1:
|
|
120
|
+
raise ValueError("地图模板须以唯一一级标题开始")
|
|
121
|
+
required = {"plan_slug", "unfinished_row", "completed_row", "deprecated_row"}
|
|
122
|
+
tokens = re.findall(r"{{(.*?)}}", template)
|
|
123
|
+
if set(tokens) != required or any(tokens.count(key) != 1 for key in required - {"plan_slug"}):
|
|
124
|
+
raise ValueError("地图模板缺失、重复或包含未知占位符")
|
|
125
|
+
headings = ["## 计划索引", "### 未完成", "### 已完成", "### 已废弃", "## 治理范围"]
|
|
126
|
+
positions = []
|
|
127
|
+
for heading in headings:
|
|
128
|
+
matches = list(re.finditer(rf"^{re.escape(heading)}$", template, re.MULTILINE))
|
|
129
|
+
if len(matches) != 1:
|
|
130
|
+
raise ValueError("地图模板缺少或重复索引分类章节")
|
|
131
|
+
positions.append(matches[0].start())
|
|
132
|
+
if positions != sorted(positions):
|
|
133
|
+
raise ValueError("地图模板索引必须位于治理说明前,分类顺序固定")
|
|
134
|
+
|
|
135
|
+
# 标签不能注入新表格列、换行或 Markdown 链接。
|
|
136
|
+
label = html.escape(" ".join(title.splitlines()), quote=False)
|
|
137
|
+
for char, entity in [("\\", "\"), ("|", "|"), ("[", "["), ("]", "]")]:
|
|
138
|
+
label = label.replace(char, entity)
|
|
130
139
|
plan_relative = f"plans/{created_on:%Y%m%d}/{plan_slug}.md"
|
|
131
|
-
row = f"| [{
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
- 计划索引固定分为 `未完成`、`已完成`、`已废弃` 三张表;`已替代`、`已合并`等不再推进的终态归入 `已废弃` 表,但保留真实状态值。
|
|
152
|
-
|
|
153
|
-
## 计划索引
|
|
154
|
-
|
|
155
|
-
### 未完成
|
|
156
|
-
|
|
157
|
-
| 计划 | 状态 | 当前阶段 | 最后更新 | 依赖 | 证据 |
|
|
158
|
-
|---|---|---|---|---|---|
|
|
159
|
-
{unfinished_row}
|
|
160
|
-
|
|
161
|
-
### 已完成
|
|
162
|
-
|
|
163
|
-
| 计划 | 状态 | 当前阶段 | 最后更新 | 依赖 | 证据 |
|
|
164
|
-
|---|---|---|---|---|---|
|
|
165
|
-
{completed_row}
|
|
166
|
-
|
|
167
|
-
### 已废弃
|
|
168
|
-
|
|
169
|
-
| 计划 | 状态 | 当前阶段 | 最后更新 | 依赖 | 证据 |
|
|
170
|
-
|---|---|---|---|---|---|
|
|
171
|
-
{deprecated_row}
|
|
172
|
-
|
|
173
|
-
允许状态:`候选`、`设计中`、`待实施`、`实施中`、`已完成`、`已替代`、`已合并`、`已废弃`。
|
|
174
|
-
|
|
175
|
-
## 推荐顺序
|
|
176
|
-
|
|
177
|
-
1. `{plan_slug}`
|
|
178
|
-
|
|
179
|
-
## 依赖关系
|
|
180
|
-
|
|
181
|
-
| 计划 | 依赖 | 原因 |
|
|
182
|
-
|---|---|---|
|
|
183
|
-
| {plan_slug} | - | - |
|
|
184
|
-
|
|
185
|
-
## 替代、合并和废弃
|
|
186
|
-
|
|
187
|
-
| 计划 | 关系 | 目标 | 原因 |
|
|
188
|
-
|---|---|---|---|
|
|
189
|
-
| - | - | - | - |
|
|
190
|
-
|
|
191
|
-
## 当前阻塞项
|
|
192
|
-
|
|
193
|
-
| 问题 | 推荐方案 | 影响范围 | 是否阻塞当前阶段 | 状态 |
|
|
194
|
-
|---|---|---|---|---|
|
|
195
|
-
| - | - | - | 否 | 已延后 |
|
|
196
|
-
|
|
197
|
-
## 完成证据
|
|
198
|
-
|
|
199
|
-
| 计划 | 阶段 | 证据 |
|
|
200
|
-
|---|---|---|
|
|
201
|
-
| - | - | - |
|
|
202
|
-
"""
|
|
140
|
+
row = f"| [{label}]({plan_relative}) | {status} | {phase} | {created_on.isoformat()} | - | - |"
|
|
141
|
+
groups = {"unfinished_row": {"候选", "设计中", "待实施", "实施中"},
|
|
142
|
+
"completed_row": {"已完成"}, "deprecated_row": {"已替代", "已合并", "已废弃"}}
|
|
143
|
+
if status not in VALID_STATUSES:
|
|
144
|
+
raise ValueError(f"计划状态不合法:{status}")
|
|
145
|
+
values = {key: row if status in states else "" for key, states in groups.items()}
|
|
146
|
+
values["plan_slug"] = plan_slug
|
|
147
|
+
rendered = re.sub(r"{{(.*?)}}", lambda match: values[match.group(1)], template)
|
|
148
|
+
header = ["计划", "状态", "当前阶段", "最后更新", "依赖", "证据"]
|
|
149
|
+
for heading, key in zip(headings[1:4], groups):
|
|
150
|
+
section = rendered.split(heading + "\n", 1)[1].split("\n#", 1)[0]
|
|
151
|
+
rows = checker.markdown_table_rows(section)
|
|
152
|
+
expected = 2 if status in groups[key] else 1
|
|
153
|
+
lines = section.strip().splitlines()
|
|
154
|
+
valid_separator = len(lines) >= 2 and re.fullmatch(r"\|(?:\s*:?-{3,}:?\s*\|){6}", lines[1])
|
|
155
|
+
valid_data_row = expected == 1 or (len(lines) >= 3 and lines[2] == row)
|
|
156
|
+
if (not valid_separator or not valid_data_row or len(rows) != expected
|
|
157
|
+
or rows[0] != header or any(len(item) != 6 for item in rows)):
|
|
158
|
+
raise ValueError("地图模板分类表必须保持六列且每个计划仅出现一次")
|
|
159
|
+
return rendered
|
|
203
160
|
|
|
204
161
|
|
|
205
162
|
def plan_template_path():
|
|
@@ -369,7 +326,7 @@ def main(argv=None):
|
|
|
369
326
|
args.update_agents_md, args.update_agent_rules)):
|
|
370
327
|
target = root / "AGENTS.md"
|
|
371
328
|
if target.exists():
|
|
372
|
-
target.read_text(encoding="utf-8")
|
|
329
|
+
checker.agent_rules_markers(target.read_text(encoding="utf-8"))
|
|
373
330
|
if args.upgrade_existing:
|
|
374
331
|
target = root / "scripts/check_plan_governance.py"
|
|
375
332
|
if target.exists() and not target.is_file():
|