@sakanano-moe/agenthelm 6.7.0 → 6.11.0-rc.1
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/package.json +1 -1
- package/runtime/index.json +6 -6
- package/runtime/plugins/agent-pack/.codex-plugin/plugin.json +1 -1
- package/runtime/plugins/agent-pack/hooks/hooks.json +32 -0
- package/runtime/plugins/agent-pack/plugin.json +1 -1
- package/runtime/plugins/agent-pack/references/local-only.md +126 -0
- package/runtime/plugins/agent-pack/references/nudge.en.md +58 -0
- package/runtime/plugins/agent-pack/references/nudge.md +58 -0
- package/runtime/plugins/agent-pack/runtime/agent_pack_nudge.py +573 -0
- package/runtime/plugins/agent-pack/runtime/agenthelm.py +419 -107
- package/runtime/plugins/agent-pack/runtime/agenthelm_runtime.py +77 -53
- package/runtime/plugins/agent-pack/runtime/i18n_agent_pack.py +78 -0
- package/runtime/plugins/agent-pack/runtime/local_only.py +1130 -0
- package/runtime/plugins/agent-pack/runtime/locale/en.json +385 -0
- package/runtime/plugins/agent-pack/runtime/mcp_server.py +25 -0
- package/runtime/plugins/agent-pack/runtime/nudge_text.py +107 -0
- package/runtime/plugins/agent-pack/skills/agent-pack/SKILL.md +9 -1
- package/runtime/plugins/agents-init/.codex-plugin/plugin.json +1 -1
- package/runtime/plugins/agents-init/content.json +17 -2
- package/runtime/plugins/agents-init/plugin.json +1 -1
- package/runtime/plugins/agents-init/skills/agents-init/AGENT.template.md +47 -0
- package/runtime/plugins/agents-init/skills/agents-init/SKILL.md +39 -1
- package/runtime/plugins/agents-init/skills/agents-init/project.schema.json +404 -70
- package/runtime/plugins/agents-init/skills/agents-init/scripts/adopt.py +596 -0
- package/runtime/plugins/agents-init/skills/agents-init/scripts/i18n_init.py +78 -0
- package/runtime/plugins/agents-init/skills/agents-init/scripts/init_project.py +245 -72
- package/runtime/plugins/agents-init/skills/agents-init/scripts/locale/en.json +210 -0
- package/runtime/plugins/agents-init/skills/agents-init/scripts/regions.py +99 -0
- package/runtime/plugins/agents-init/skills/agents-init/scripts/scaffold.py +1081 -0
- package/runtime/plugins/agents-init/skills/agents-init/scripts/test_adopt.py +551 -0
- package/runtime/plugins/agents-init/skills/agents-init/scripts/test_init_project.py +46 -2
- package/runtime/plugins/agents-init/skills/agents-init/scripts/test_scaffold.py +555 -0
- package/runtime/plugins/agents-init/skills/agents-init/skeleton.json +66 -0
- package/runtime/plugins/agents-init/skills/agents-init/skill.json +1 -1
- package/runtime/plugins/opinion-manager/.codex-plugin/plugin.json +1 -1
- package/runtime/plugins/opinion-manager/plugin.json +1 -1
- package/runtime/plugins/opinion-manager/references/inbox.en.md +48 -0
- package/runtime/plugins/opinion-manager/references/inbox.md +48 -0
- package/runtime/plugins/opinion-manager/runtime/context.py +19 -18
- package/runtime/plugins/opinion-manager/runtime/i18n_opinion.py +78 -0
- package/runtime/plugins/opinion-manager/runtime/inbox.py +397 -0
- package/runtime/plugins/opinion-manager/runtime/inbox_text.py +101 -0
- package/runtime/plugins/opinion-manager/runtime/lifecycle.py +45 -44
- package/runtime/plugins/opinion-manager/runtime/locale/en.json +184 -0
- package/runtime/plugins/opinion-manager/runtime/opinion_manager.py +55 -50
- package/runtime/plugins/opinion-manager/runtime/versioned.py +84 -83
- package/runtime/plugins/opinion-manager/skills/opinion-manager/SKILL.md +15 -2
- package/runtime/plugins/project-orchestrator/.codex-plugin/plugin.json +1 -1
- package/runtime/plugins/project-orchestrator/content.json +30 -3
- package/runtime/plugins/project-orchestrator/plugin.json +1 -1
- package/runtime/plugins/project-orchestrator/references/case-workspaces.md +9 -1
- package/runtime/plugins/project-orchestrator/references/case.schema.json +1 -0
- package/runtime/plugins/project-orchestrator/references/sop-interview.md +83 -0
- package/runtime/plugins/project-orchestrator/references/sop.md +156 -0
- package/runtime/plugins/project-orchestrator/runtime/assets.py +52 -57
- package/runtime/plugins/project-orchestrator/runtime/case.py +139 -80
- package/runtime/plugins/project-orchestrator/runtime/case_structure.py +71 -0
- package/runtime/plugins/project-orchestrator/runtime/case_workspace.py +47 -46
- package/runtime/plugins/project-orchestrator/runtime/i18n_orchestrator.py +78 -0
- package/runtime/plugins/project-orchestrator/runtime/locale/en.json +1409 -0
- package/runtime/plugins/project-orchestrator/runtime/memory.py +44 -43
- package/runtime/plugins/project-orchestrator/runtime/private_paths.py +119 -0
- package/runtime/plugins/project-orchestrator/runtime/project_base.py +37 -19
- package/runtime/plugins/project-orchestrator/runtime/project_cli.py +267 -174
- package/runtime/plugins/project-orchestrator/runtime/project_demo.py +130 -48
- package/runtime/plugins/project-orchestrator/runtime/project_maintain.py +524 -134
- package/runtime/plugins/project-orchestrator/runtime/project_menu.py +159 -85
- package/runtime/plugins/project-orchestrator/runtime/project_real.py +191 -106
- package/runtime/plugins/project-orchestrator/runtime/project_write.py +58 -55
- package/runtime/plugins/project-orchestrator/runtime/sop.py +1670 -0
- package/runtime/plugins/project-orchestrator/runtime/sop_structure.py +226 -0
- package/runtime/plugins/project-orchestrator/runtime/wf.py +74 -70
- package/runtime/plugins/project-orchestrator/skills/project-orchestrator/SKILL.md +22 -0
- package/runtime/plugins/project-orchestrator/sop-templates/blank/SOP.en.md +57 -0
- package/runtime/plugins/project-orchestrator/sop-templates/blank/SOP.md +57 -0
- package/runtime/plugins/project-orchestrator/sop-templates/knowledge-digest/SOP.en.md +77 -0
- package/runtime/plugins/project-orchestrator/sop-templates/knowledge-digest/SOP.md +77 -0
- package/runtime/plugins/project-orchestrator/sop-templates/knowledge-digest/templates/digest.en.md +11 -0
- package/runtime/plugins/project-orchestrator/sop-templates/knowledge-digest/templates/digest.md +11 -0
- package/runtime/plugins/project-orchestrator/sop-templates/video-to-transcript/SOP.en.md +87 -0
- package/runtime/plugins/project-orchestrator/sop-templates/video-to-transcript/SOP.md +87 -0
- package/runtime/plugins/project-orchestrator/sop-templates/weekly-review/SOP.en.md +75 -0
- package/runtime/plugins/project-orchestrator/sop-templates/weekly-review/SOP.md +75 -0
- package/runtime/plugins/project-orchestrator/sop-templates/weekly-review/templates/review.en.md +11 -0
- package/runtime/plugins/project-orchestrator/sop-templates/weekly-review/templates/review.md +12 -0
- package/runtime/plugins/project-orchestrator/templates/CASE.en.md +39 -0
|
@@ -14,6 +14,7 @@ from pathlib import Path
|
|
|
14
14
|
from typing import Any
|
|
15
15
|
|
|
16
16
|
import versioned
|
|
17
|
+
from i18n_opinion import _ # noqa: E402
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
DEFAULT_ROOTS = ("tasks", "cases", "reviews", "projects", "journal")
|
|
@@ -33,9 +34,9 @@ def fail(message: str) -> None:
|
|
|
33
34
|
def confined(root: Path, relative: str | Path) -> Path:
|
|
34
35
|
path = root / relative
|
|
35
36
|
if Path(relative).is_absolute() or ".." in Path(relative).parts:
|
|
36
|
-
fail("路径必须是项目内的相对路径")
|
|
37
|
+
fail(_("路径必须是项目内的相对路径"))
|
|
37
38
|
if any(item.is_symlink() for item in (path, *path.parents) if item != root.parent):
|
|
38
|
-
fail("拒绝使用符号链接")
|
|
39
|
+
fail(_("拒绝使用符号链接"))
|
|
39
40
|
path.resolve().relative_to(root.resolve())
|
|
40
41
|
return path
|
|
41
42
|
|
|
@@ -44,14 +45,14 @@ def current_lock(root: Path) -> dict[str, Any]:
|
|
|
44
45
|
try:
|
|
45
46
|
versioned.verify_project(root)
|
|
46
47
|
except (versioned.OpinionVersionError, OSError, json.JSONDecodeError) as exc:
|
|
47
|
-
raise LifecycleError(
|
|
48
|
+
raise LifecycleError(_("当前 Opinion 必须先通过 verify:{0}").format(exc)) from exc
|
|
48
49
|
return versioned.read_object(root / "opinion.lock.json")
|
|
49
50
|
|
|
50
51
|
|
|
51
52
|
def occurrence(path: str, roots: tuple[str, ...]) -> str:
|
|
52
53
|
parts = Path(path).parts
|
|
53
54
|
if len(parts) < 2 or parts[0] not in roots:
|
|
54
|
-
fail(
|
|
55
|
+
fail(_("occurrence 必须指向项目内的工作记录,根目录为:{0}").format(_('、').join(roots)))
|
|
55
56
|
return "/".join(parts[:2])
|
|
56
57
|
|
|
57
58
|
|
|
@@ -88,28 +89,28 @@ def record_use(event: dict[str, Any], lock: dict[str, Any], roots: tuple[str, ..
|
|
|
88
89
|
profile = lock["profile"]
|
|
89
90
|
known = {source["ref"] for source in profile["sources"]["rules"]}
|
|
90
91
|
if not event.get("rules") or not set(event["rules"]) <= known:
|
|
91
|
-
fail("use 必须记录适用规则的精确引用")
|
|
92
|
+
fail(_("use 必须记录适用规则的精确引用"))
|
|
92
93
|
use = {key: event[key] for key in ("evidence", "evidence_sha256", "rules", "passed", "user_changed")}
|
|
93
94
|
if type(use["passed"]) is not bool or type(use["user_changed"]) is not bool:
|
|
94
|
-
fail("passed 与 user_changed 必须是明确的布尔值")
|
|
95
|
+
fail(_("passed 与 user_changed 必须是明确的布尔值"))
|
|
95
96
|
results = event.get("results")
|
|
96
97
|
if results is not None:
|
|
97
98
|
if not isinstance(results, dict) or set(results) != set(event["rules"]) or not set(results.values()) <= set(RESULTS):
|
|
98
|
-
fail(
|
|
99
|
+
fail(_("results 必须为每条规则给出结果:{0}").format(_('、').join(RESULTS)))
|
|
99
100
|
if use["passed"] and "violated" in results.values():
|
|
100
|
-
fail("存在违反的规则时 passed 不能为 true")
|
|
101
|
+
fail(_("存在违反的规则时 passed 不能为 true"))
|
|
101
102
|
use["results"] = results
|
|
102
103
|
bundles = {bundle["family"] for bundle in profile["loading"]["bundles"]} if "loading" in profile else None
|
|
103
104
|
for key in ("loaded_bundles", "routing_miss"):
|
|
104
105
|
if key in event:
|
|
105
106
|
if bundles is None:
|
|
106
|
-
fail(
|
|
107
|
+
fail(_("当前 Profile 未声明分层加载,不能记录 {0}").format(key))
|
|
107
108
|
if not isinstance(event[key], list) or not set(event[key]) <= bundles or len(event[key]) != len(set(event[key])):
|
|
108
|
-
fail(
|
|
109
|
+
fail(_("{0} 必须是已声明规则束的不重复列表").format(key))
|
|
109
110
|
use[key] = event[key]
|
|
110
111
|
if "date" in event:
|
|
111
112
|
if not isinstance(event["date"], str) or not DATE.fullmatch(event["date"]):
|
|
112
|
-
fail("date 必须为 YYYY-MM-DD")
|
|
113
|
+
fail(_("date 必须为 YYYY-MM-DD"))
|
|
113
114
|
use["date"] = event["date"]
|
|
114
115
|
use.update(occurrence=occurrence(event["occurrence"], roots), fixture=bool(event.get("fixture")))
|
|
115
116
|
return use
|
|
@@ -124,16 +125,16 @@ def reduce(state: dict[str, Any], event: dict[str, Any], lock: dict[str, Any] |
|
|
|
124
125
|
kind = event["kind"]
|
|
125
126
|
receipt = {key: event[key] for key in ("evidence", "evidence_sha256", "evidence_origin") if key in event}
|
|
126
127
|
if "fixture" in event and type(event["fixture"]) is not bool:
|
|
127
|
-
fail("fixture 必须是明确的布尔值")
|
|
128
|
+
fail(_("fixture 必须是明确的布尔值"))
|
|
128
129
|
if kind == "capture":
|
|
129
130
|
identity, content = event["id"], event["content"]
|
|
130
131
|
if not all(isinstance(content.get(key), str) and content[key].strip() for key in ("title", "text")):
|
|
131
|
-
fail("候选需要完整的 title 与 text")
|
|
132
|
+
fail(_("候选需要完整的 title 与 text"))
|
|
132
133
|
if not content.get("scopes") or not all(isinstance(item, str) and item.strip() for item in content["scopes"]):
|
|
133
|
-
fail("候选需要明确的 scopes")
|
|
134
|
+
fail(_("候选需要明确的 scopes"))
|
|
134
135
|
if identity in result["candidates"]:
|
|
135
136
|
if result["candidates"][identity]["content"] != content:
|
|
136
|
-
fail("候选内容变化时必须使用新的候选编号")
|
|
137
|
+
fail(_("候选内容变化时必须使用新的候选编号"))
|
|
137
138
|
else:
|
|
138
139
|
result["candidates"][identity] = {"id": identity, "content": content, "capture_evidence": receipt,
|
|
139
140
|
"evidence": [], "presentations": [], "credit": 0.0, "decision": None}
|
|
@@ -143,7 +144,7 @@ def reduce(state: dict[str, Any], event: dict[str, Any], lock: dict[str, Any] |
|
|
|
143
144
|
before = {item["occurrence"] for item in candidate["evidence"] if not item.get("fixture")}
|
|
144
145
|
evidence = {key: event[key] for key in ("evidence", "evidence_sha256", "validation")}
|
|
145
146
|
if evidence["validation"] not in ("pending", "passed", "failed"):
|
|
146
|
-
fail("validation 必须为 pending、passed 或 failed")
|
|
147
|
+
fail(_("validation 必须为 pending、passed 或 failed"))
|
|
147
148
|
evidence.update(occurrence=group, fixture=bool(event.get("fixture")))
|
|
148
149
|
candidate["evidence"].append(evidence)
|
|
149
150
|
if not evidence["fixture"] and group not in before:
|
|
@@ -152,15 +153,15 @@ def reduce(state: dict[str, Any], event: dict[str, Any], lock: dict[str, Any] |
|
|
|
152
153
|
elif kind == "presented":
|
|
153
154
|
candidate = result["candidates"][event["id"]]
|
|
154
155
|
if event["content_sha256"] != versioned.digest(candidate["content"]):
|
|
155
|
-
fail("展示的内容与候选不一致")
|
|
156
|
+
fail(_("展示的内容与候选不一致"))
|
|
156
157
|
candidate["credit"] = max(0.0, candidate["credit"] - 1)
|
|
157
158
|
candidate.setdefault("presentations", []).append(receipt)
|
|
158
159
|
elif kind == "decision":
|
|
159
160
|
candidate = result["candidates"][event["id"]]
|
|
160
161
|
if event["content_sha256"] != versioned.digest(candidate["content"]):
|
|
161
|
-
fail("人工裁决指向的候选内容不一致")
|
|
162
|
+
fail(_("人工裁决指向的候选内容不一致"))
|
|
162
163
|
if event["decision"] not in ("accepted", "rejected", "held") or event.get("human_confirmed") is not True:
|
|
163
|
-
fail("需要明确的人工裁决")
|
|
164
|
+
fail(_("需要明确的人工裁决"))
|
|
164
165
|
candidate["decision"] = event["decision"]
|
|
165
166
|
candidate["decision_evidence"] = event["evidence"]
|
|
166
167
|
candidate["decision_receipt"] = receipt
|
|
@@ -168,16 +169,16 @@ def reduce(state: dict[str, Any], event: dict[str, Any], lock: dict[str, Any] |
|
|
|
168
169
|
elif kind == "adopt":
|
|
169
170
|
ref = profile_ref(lock["profile"])
|
|
170
171
|
if event.get("human_confirmed") is not True or event["profile_sha256"] != lock["profile_sha256"]:
|
|
171
|
-
fail("采用需要人工批准当前精确的 Profile")
|
|
172
|
+
fail(_("采用需要人工批准当前精确的 Profile"))
|
|
172
173
|
if ref in result["rollouts"] and result["rollouts"][ref]["profile_sha256"] != lock["profile_sha256"]:
|
|
173
|
-
fail("不可变的 Profile 身份发生了变化")
|
|
174
|
+
fail(_("不可变的 Profile 身份发生了变化"))
|
|
174
175
|
result["rollouts"].setdefault(ref, {"state": "gray", "profile_sha256": lock["profile_sha256"],
|
|
175
176
|
"opinion_sha256": lock["opinion_sha256"], "approval_evidence": event["evidence"],
|
|
176
177
|
"approval_receipt": receipt, "uses": [], "blockers": []})
|
|
177
178
|
elif kind == "use":
|
|
178
179
|
rollout = result["rollouts"][profile_ref(lock["profile"])]
|
|
179
180
|
if rollout["profile_sha256"] != lock["profile_sha256"] or rollout["opinion_sha256"] != lock["opinion_sha256"]:
|
|
180
|
-
fail("use 指向的 Opinion 内容已经变化")
|
|
181
|
+
fail(_("use 指向的 Opinion 内容已经变化"))
|
|
181
182
|
use = record_use(event, lock, roots)
|
|
182
183
|
rollout["uses"].append(use)
|
|
183
184
|
if not use["fixture"] and (not use["passed"] or use["user_changed"] or use.get("routing_miss")):
|
|
@@ -185,16 +186,16 @@ def reduce(state: dict[str, Any], event: dict[str, Any], lock: dict[str, Any] |
|
|
|
185
186
|
elif kind == "formalize":
|
|
186
187
|
rollout = result["rollouts"][profile_ref(lock["profile"])]
|
|
187
188
|
if event.get("human_confirmed") is not True or event["profile_sha256"] != lock["profile_sha256"]:
|
|
188
|
-
fail("复盘必须确认未变化的当前 Profile")
|
|
189
|
+
fail(_("复盘必须确认未变化的当前 Profile"))
|
|
189
190
|
if rollout["profile_sha256"] != lock["profile_sha256"] or rollout["opinion_sha256"] != lock["opinion_sha256"]:
|
|
190
|
-
fail("灰度内容已经变化;请采用新的已核验版本")
|
|
191
|
+
fail(_("灰度内容已经变化;请采用新的已核验版本"))
|
|
191
192
|
if rollout["blockers"]:
|
|
192
|
-
fail("转正式前先在新版本中解决灰度失败")
|
|
193
|
+
fail(_("转正式前先在新版本中解决灰度失败"))
|
|
193
194
|
rollout["state"] = "formal"
|
|
194
195
|
rollout["review_evidence"] = event["evidence"]
|
|
195
196
|
rollout["review_receipt"] = receipt
|
|
196
197
|
else:
|
|
197
|
-
fail("未知的生命周期事件")
|
|
198
|
+
fail(_("未知的生命周期事件"))
|
|
198
199
|
result["events"].append(event_hash)
|
|
199
200
|
return result
|
|
200
201
|
|
|
@@ -210,7 +211,7 @@ def evidence_valid(root: Path, state: dict[str, Any]) -> None:
|
|
|
210
211
|
receipts.extend(rollout[key] for key in ("approval_receipt", "review_receipt") if rollout.get(key))
|
|
211
212
|
for item in receipts:
|
|
212
213
|
if hashlib.sha256(confined(root, item["evidence"]).read_bytes()).hexdigest() != item["evidence_sha256"]:
|
|
213
|
-
fail("不可变的生命周期证据发生变化;修改状态前先重新核验")
|
|
214
|
+
fail(_("不可变的生命周期证据发生变化;修改状态前先重新核验"))
|
|
214
215
|
|
|
215
216
|
|
|
216
217
|
def successful(use: dict[str, Any]) -> bool:
|
|
@@ -230,10 +231,10 @@ def carried_uses(state: dict[str, Any], profile: dict[str, Any], profiles_root:
|
|
|
230
231
|
try:
|
|
231
232
|
previous = versioned.load_profile(path)
|
|
232
233
|
except (versioned.OpinionVersionError, OSError, json.JSONDecodeError):
|
|
233
|
-
skipped.append({"profile": ref, "reason": "找不到可核验的旧 Profile"})
|
|
234
|
+
skipped.append({"profile": ref, "reason": _("找不到可核验的旧 Profile")})
|
|
234
235
|
continue
|
|
235
236
|
if versioned.digest(previous) != rollout["profile_sha256"]:
|
|
236
|
-
skipped.append({"profile": ref, "reason": "旧 Profile 与灰度记录的指纹不一致"})
|
|
237
|
+
skipped.append({"profile": ref, "reason": _("旧 Profile 与灰度记录的指纹不一致")})
|
|
237
238
|
continue
|
|
238
239
|
before = rule_fingerprints(previous)
|
|
239
240
|
for use in rollout["uses"]:
|
|
@@ -301,16 +302,16 @@ def status_view(state: dict[str, Any], root: Path, profiles_root: Path) -> dict[
|
|
|
301
302
|
|
|
302
303
|
|
|
303
304
|
def add_command(subparsers: Any) -> None:
|
|
304
|
-
parser = subparsers.add_parser("lifecycle", help="项目私有的候选、灰度与使用记录;不发布规则")
|
|
305
|
+
parser = subparsers.add_parser("lifecycle", help=_("项目私有的候选、灰度与使用记录;不发布规则"))
|
|
305
306
|
parser.add_argument("action", choices=("queue", "status", "event"))
|
|
306
307
|
parser.add_argument("--project", type=Path, required=True)
|
|
307
|
-
parser.add_argument("--store", required=True, help="项目内保存状态与证据快照的相对目录")
|
|
308
|
-
parser.add_argument("--file", type=Path, help="event 的事件 JSON,必须位于项目内")
|
|
309
|
-
parser.add_argument("--profiles-root", help="保存历史 Profile 的项目内相对目录,默认 .opinion/profiles")
|
|
310
|
-
parser.add_argument("--occurrence-root", action="append", default=[], help="允许作为独立发生场景的顶层目录,可重复")
|
|
311
|
-
parser.add_argument("--titles", action="store_true", help="queue 只输出未裁决候选的标题与适用范围")
|
|
308
|
+
parser.add_argument("--store", required=True, help=_("项目内保存状态与证据快照的相对目录"))
|
|
309
|
+
parser.add_argument("--file", type=Path, help=_("event 的事件 JSON,必须位于项目内"))
|
|
310
|
+
parser.add_argument("--profiles-root", help=_("保存历史 Profile 的项目内相对目录,默认 .opinion/profiles"))
|
|
311
|
+
parser.add_argument("--occurrence-root", action="append", default=[], help=_("允许作为独立发生场景的顶层目录,可重复"))
|
|
312
|
+
parser.add_argument("--titles", action="store_true", help=_("queue 只输出未裁决候选的标题与适用范围"))
|
|
312
313
|
parser.add_argument("--apply", action="store_true")
|
|
313
|
-
parser.add_argument("--confirm", help="预览返回的 confirmation_sha256")
|
|
314
|
+
parser.add_argument("--confirm", help=_("预览返回的 confirmation_sha256"))
|
|
314
315
|
parser.add_argument("--output", choices=("json",), default="json")
|
|
315
316
|
|
|
316
317
|
|
|
@@ -325,7 +326,7 @@ def run(args: argparse.Namespace) -> dict[str, Any]:
|
|
|
325
326
|
evidence_valid(root, state)
|
|
326
327
|
if args.action in ("queue", "status"):
|
|
327
328
|
if args.apply:
|
|
328
|
-
fail("只读命令不能使用 --apply")
|
|
329
|
+
fail(_("只读命令不能使用 --apply"))
|
|
329
330
|
if args.action == "status":
|
|
330
331
|
return status_view(state, root, profiles_root)
|
|
331
332
|
views = sorted((candidate_view(item) for item in state["candidates"].values()), key=lambda item: (-item["priority"], item["id"]))
|
|
@@ -335,14 +336,14 @@ def run(args: argparse.Namespace) -> dict[str, Any]:
|
|
|
335
336
|
for item in views if item["status"] not in ("accepted", "rejected", "held")]
|
|
336
337
|
return {"ok": True, "status": "read-only", "value": views}
|
|
337
338
|
if not args.file:
|
|
338
|
-
fail("event 需要 --file")
|
|
339
|
+
fail(_("event 需要 --file"))
|
|
339
340
|
event_path = confined(root, os.path.relpath(args.file.resolve(), root))
|
|
340
341
|
event = json.loads(event_path.read_text(encoding="utf-8"))
|
|
341
342
|
source = confined(root, event["evidence"])
|
|
342
343
|
source_bytes = source.read_bytes()
|
|
343
344
|
source_hash = hashlib.sha256(source_bytes).hexdigest()
|
|
344
345
|
if event.get("evidence_sha256") and event["evidence_sha256"] != source_hash:
|
|
345
|
-
fail("事件证据已经变化")
|
|
346
|
+
fail(_("事件证据已经变化"))
|
|
346
347
|
event["evidence_sha256"] = source_hash
|
|
347
348
|
event["evidence_origin"] = event["evidence"]
|
|
348
349
|
snapshot = store / "evidence" / f"{source_hash}.blob"
|
|
@@ -352,22 +353,22 @@ def run(args: argparse.Namespace) -> dict[str, Any]:
|
|
|
352
353
|
fingerprint = versioned.digest({"before": state, "after": after, "event": event})
|
|
353
354
|
if args.apply:
|
|
354
355
|
if args.confirm != fingerprint:
|
|
355
|
-
fail("确认指纹不一致")
|
|
356
|
+
fail(_("确认指纹不一致"))
|
|
356
357
|
store.mkdir(parents=True, exist_ok=True)
|
|
357
358
|
with (store / ".lifecycle.lock").open("a") as guard:
|
|
358
359
|
fcntl.flock(guard, fcntl.LOCK_EX)
|
|
359
360
|
live = state_path.read_text(encoding="utf-8") if state_path.exists() else None
|
|
360
361
|
if live != raw:
|
|
361
|
-
fail("状态已经变化;请重新生成预览")
|
|
362
|
+
fail(_("状态已经变化;请重新生成预览"))
|
|
362
363
|
evidence_valid(root, state)
|
|
363
364
|
if lock is not None and current_lock(root) != lock:
|
|
364
|
-
fail("Opinion 在写入期间发生变化;请重新生成预览")
|
|
365
|
+
fail(_("Opinion 在写入期间发生变化;请重新生成预览"))
|
|
365
366
|
if hashlib.sha256(source.read_bytes()).hexdigest() != source_hash:
|
|
366
|
-
fail("证据在预览后发生变化")
|
|
367
|
+
fail(_("证据在预览后发生变化"))
|
|
367
368
|
snapshot.parent.mkdir(parents=True, exist_ok=True)
|
|
368
369
|
if snapshot.exists():
|
|
369
370
|
if snapshot.read_bytes() != source_bytes:
|
|
370
|
-
fail("不可变的证据快照被改动")
|
|
371
|
+
fail(_("不可变的证据快照被改动"))
|
|
371
372
|
else:
|
|
372
373
|
with snapshot.open("xb") as handle:
|
|
373
374
|
handle.write(source_bytes)
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
{
|
|
2
|
+
"拒绝使用符号链接:{0}": "Refusing to use a symbolic link: {0}",
|
|
3
|
+
"必须使用普通文件:{0}": "A regular file is required: {0}",
|
|
4
|
+
"JSON 根节点必须是对象:{0}": "The JSON root must be an object: {0}",
|
|
5
|
+
"{0} 字段必须为:{1}": "{0} must have exactly these fields: {1}",
|
|
6
|
+
"{0} 必须是非空字符串": "{0} must be a non-empty string",
|
|
7
|
+
"编号无效:{0!r}": "Invalid id: {0!r}",
|
|
8
|
+
"版本必须是无前导零的三段数字:{0!r}": "The version must be three numeric parts without leading zeros: {0!r}",
|
|
9
|
+
"{0} 必须是字符串数组": "{0} must be an array of strings",
|
|
10
|
+
"{0} 包含重复项": "{0} contains duplicates",
|
|
11
|
+
"{0} 必须精确指定 @VERSION:{1!r}": "{0} must name an exact @VERSION: {1!r}",
|
|
12
|
+
"{0} 引用格式无效:{1}": "{0} reference has an invalid format: {1}",
|
|
13
|
+
"规则内容": "rule content",
|
|
14
|
+
"level 必须为 required 或 preferred": "level must be required or preferred",
|
|
15
|
+
"发布内容必须是对象": "The published content must be an object",
|
|
16
|
+
"发布内容": "published content",
|
|
17
|
+
"目录不存在:{0}": "Directory does not exist: {0}",
|
|
18
|
+
"发布文件路径与身份不一致:{0}": "The published file path does not match its identity: {0}",
|
|
19
|
+
"同一模板系列只能选择一个变体和版本:{0}": "A template family can have only one variant and version selected: {0}",
|
|
20
|
+
"未知模板:{0}": "Unknown template: {0}",
|
|
21
|
+
"未知规则:{0}": "Unknown rule: {0}",
|
|
22
|
+
"规则选择了不同版本:{0}": "The rule was selected in different versions: {0}",
|
|
23
|
+
"规则 slot 冲突:{0}({1}、{2})": "Rule slot conflict: {0} ({1}, {2})",
|
|
24
|
+
"loading.signals 必须是非空对象": "loading.signals must be a non-empty object",
|
|
25
|
+
"loading.bundles 至少包含一个按需规则束": "loading.bundles needs at least one on-demand rule bundle",
|
|
26
|
+
"loading.core 引用了未选择的模板系列:{0}": "loading.core refers to a template family that is not selected: {0}",
|
|
27
|
+
"loading.bundles 项": "loading.bundles entry",
|
|
28
|
+
"loading.bundles 引用了未选择的模板系列:{0!r}": "loading.bundles refers to a template family that is not selected: {0!r}",
|
|
29
|
+
"模板系列在 loading 中重复出现:{0}": "The template family appears more than once in loading: {0}",
|
|
30
|
+
"规则束至少需要一条路由:{0}": "A rule bundle needs at least one route: {0}",
|
|
31
|
+
"路由必须是非空对象:{0}": "A route must be a non-empty object: {0}",
|
|
32
|
+
"路由使用了未声明的信号:{0}.{1}": "A route uses an undeclared signal: {0}.{1}",
|
|
33
|
+
"Profile schema_version 必须为 2.0 或 2.1": "Profile schema_version must be 2.0 or 2.1",
|
|
34
|
+
"sources 项必须是数组": "sources entries must be an array",
|
|
35
|
+
"source 类型不一致": "source types are inconsistent",
|
|
36
|
+
"source 指纹或引用不一致:{0}": "source fingerprint or reference is inconsistent: {0}",
|
|
37
|
+
"重复 source:{0}": "Duplicate source: {0}",
|
|
38
|
+
"Profile 引用与 sources 不一致": "The Profile references do not match sources",
|
|
39
|
+
"overrides 必须是对象": "overrides must be an object",
|
|
40
|
+
"override 必须使用匹配编号的完整规则": "An override must use a complete rule with the matching id",
|
|
41
|
+
"停用规则不存在:{0}": "The disabled rule does not exist: {0}",
|
|
42
|
+
"derived_from 指纹无效": "The derived_from fingerprint is invalid",
|
|
43
|
+
"Profile 至少需要一条有效规则或自定义内容": "A Profile needs at least one effective rule or custom content",
|
|
44
|
+
"同一次选择包含重复身份:{0}": "The same selection contains a duplicate identity: {0}",
|
|
45
|
+
"已经发布的版本不可修改:{0}": "A published version cannot be modified: {0}",
|
|
46
|
+
"写入后核对失败:{0}": "Verification after writing failed: {0}",
|
|
47
|
+
"请先审阅预览,再使用 --confirm {0} --apply": "Review the preview first, then use --confirm {0} --apply",
|
|
48
|
+
"项目目录不存在:{0}": "Project directory does not exist: {0}",
|
|
49
|
+
"修订 Profile 必须增加版本;私有派生必须使用独立编号": "A revised Profile must increase the version; a private derivation must use its own id",
|
|
50
|
+
"无法移除未选择的模板系列:{0}": "Cannot remove a template family that is not selected: {0}",
|
|
51
|
+
"无法移除未直接选择的规则:{0}": "Cannot remove a rule that was not selected directly: {0}",
|
|
52
|
+
"已经发布的来源内容发生变化:{0}": "The published source content has changed: {0}",
|
|
53
|
+
"--loading-file 与 --remove-loading 不能同时使用": "--loading-file and --remove-loading cannot be used together",
|
|
54
|
+
"resolutions 包含没有冲突的编号": "resolutions contains an id that has no conflict",
|
|
55
|
+
"冲突处理必须为 keep-local 或 use-upstream": "A conflict resolution must be keep-local or use-upstream",
|
|
56
|
+
"override 引用未知规则:{0}": "An override refers to an unknown rule: {0}",
|
|
57
|
+
"升级存在未确认的冲突:{0}": "The upgrade has unresolved conflicts: {0}",
|
|
58
|
+
"patch 版本只允许不改变行为的修改": "A patch version may only contain changes that do not alter behavior",
|
|
59
|
+
"patch 版本不能改变适用范围": "A patch version cannot change the scope",
|
|
60
|
+
"patch 版本不能改变有效规则": "A patch version cannot change the effective rules",
|
|
61
|
+
"公开内容需要用户批准完整文件,并使用 --public-approved": "Public content needs the user's approval of the complete file and --public-approved",
|
|
62
|
+
"锁定文件版本不受支持": "The lock file version is not supported",
|
|
63
|
+
"锁定文件指纹与内容不一致": "The lock file fingerprint does not match the content",
|
|
64
|
+
"--profile 必须独立使用;通过 profile 命令修改选择和自定义内容": "--profile must be used on its own; change selections and custom content through the profile command",
|
|
65
|
+
"已经锁定的 Profile 版本不可改变内容;请发布新的 Profile 版本": "The content of a locked Profile version cannot change; publish a new Profile version",
|
|
66
|
+
"正文缺失;请先使用原锁定 Profile 重建正文,再进行升级": "The body is missing; rebuild it from the originally locked Profile before upgrading",
|
|
67
|
+
"OPINION.md 已被人工修改;请读取并纳入新的 Profile,审阅后用 --accept-current 当前文件SHA256 确认": "OPINION.md was edited by hand; read it and fold it into the new Profile, then confirm with --accept-current <SHA-256 of the current file> after review",
|
|
68
|
+
"已有 OPINION.md 需要审阅并使用 --replace": "An existing OPINION.md must be reviewed and replaced with --replace",
|
|
69
|
+
"OPINION.md 与锁定文件不一致;请读取人工修改或检查中断的写入": "OPINION.md does not match the lock file; read the manual edits or check for an interrupted write",
|
|
70
|
+
"预览返回的完整内容 SHA-256": "SHA-256 of the full content returned by the preview",
|
|
71
|
+
"人工修改或写入中断后的当前 OPINION.md SHA-256;读取并保留内容后使用": "SHA-256 of the current OPINION.md after a manual edit or an interrupted write; use it after reading and keeping the content",
|
|
72
|
+
"预览、派生和发布不可变的个人 Profile": "Preview, derive and publish an immutable personal Profile",
|
|
73
|
+
"完整的分层加载声明 JSON;整体替换原声明": "Complete tiered-loading declaration JSON; replaces the existing declaration as a whole",
|
|
74
|
+
"移除分层加载声明,恢复为完整读取": "Remove the tiered-loading declaration and return to full reading",
|
|
75
|
+
"预览或发布规则、模板版本": "Preview or publish rule and template versions",
|
|
76
|
+
"只读检查同一变体的新版本": "Read-only check for newer versions of the same variant",
|
|
77
|
+
"查看两份 Profile 的差异和三方冲突": "Show the differences and three-way conflicts between two Profiles",
|
|
78
|
+
"核对正文、Profile 快照和锁定指纹": "Verify the body, the Profile snapshot and the lock fingerprints",
|
|
79
|
+
"拒绝读取非普通文件:{0}": "Refusing to read a non-regular file: {0}",
|
|
80
|
+
"模板 {0} 的 rules 项必须是对象": "The rules entry of template {0} must be an object",
|
|
81
|
+
"规则缺少字段 {0}:{1}": "The rule is missing fields {0}: {1}",
|
|
82
|
+
"规则包含未知字段 {0}:{1}": "The rule has unknown fields {0}: {1}",
|
|
83
|
+
"规则编号无效:{0!r}": "Invalid rule id: {0!r}",
|
|
84
|
+
"规则 level 无效:{0}": "Invalid rule level: {0}",
|
|
85
|
+
"规则标题为空:{0}": "The rule title is empty: {0}",
|
|
86
|
+
"规则正文为空:{0}": "The rule text is empty: {0}",
|
|
87
|
+
"规则 scopes 必须是非空字符串数组:{0}": "The rule scopes must be a non-empty array of strings: {0}",
|
|
88
|
+
"模板缺少字段 {0}:{1}": "The template is missing fields {0}: {1}",
|
|
89
|
+
"模板包含未知字段 {0}:{1}": "The template has unknown fields {0}: {1}",
|
|
90
|
+
"模板编号无效:{0!r}": "Invalid template id: {0!r}",
|
|
91
|
+
"模板文件名必须与编号一致:{0}": "The template file name must match its id: {0}",
|
|
92
|
+
"模板版本必须使用三段数字:{0}": "The template version must use three numeric parts: {0}",
|
|
93
|
+
"模板字段 {0} 不能为空:{1}": "Template field {0} must not be empty: {1}",
|
|
94
|
+
"模板 scopes 必须是非空数组:{0}": "The template scopes must be a non-empty array: {0}",
|
|
95
|
+
"模板 rules 必须是非空数组:{0}": "The template rules must be a non-empty array: {0}",
|
|
96
|
+
"模板内存在重复规则编号:{0}": "The template contains a duplicate rule id: {0}",
|
|
97
|
+
"模板 questions 必须是数组:{0}": "The template questions must be an array: {0}",
|
|
98
|
+
"模板问题结构无效:{0}": "Invalid template question structure: {0}",
|
|
99
|
+
"模板问题编号无效:{0}": "Invalid template question id: {0}",
|
|
100
|
+
"模板问题编号重复:{0}": "Duplicate template question id: {0}",
|
|
101
|
+
"模板问题正文为空:{0}": "The template question text is empty: {0}",
|
|
102
|
+
"模板问题至少需要两个选项:{0}": "A template question needs at least two options: {0}",
|
|
103
|
+
"模板问题选项结构无效:{0}": "Invalid template question option structure: {0}",
|
|
104
|
+
"模板问题选项标签为空:{0}": "A template question option label is empty: {0}",
|
|
105
|
+
"模板问题引用了未知规则:{0}": "A template question refers to an unknown rule: {0}",
|
|
106
|
+
"模板目录不可用:{0}": "The template directory is not available: {0}",
|
|
107
|
+
"模板编号重复:{0}": "Duplicate template id: {0}",
|
|
108
|
+
"相同规则编号的内容不一致:{0}": "Rules with the same id have different content: {0}",
|
|
109
|
+
"模板版本不匹配:{0}@{1},当前版本为 {2}": "Template version mismatch: {0}@{1}; the current version is {2}",
|
|
110
|
+
"自定义规则必须来自普通文件:{0}": "Custom rules must come from a regular file: {0}",
|
|
111
|
+
"自定义规则文件为空": "The custom rules file is empty",
|
|
112
|
+
"拒绝写入符号链接:{0}": "Refusing to write to a symbolic link: {0}",
|
|
113
|
+
"目标路径不是普通文件:{0}": "The target path is not a regular file: {0}",
|
|
114
|
+
"OPINION.md 已包含内容;预览差异并明确使用 --replace": "OPINION.md already has content; preview the difference and use --replace explicitly",
|
|
115
|
+
"写入后核对 OPINION.md 失败": "Verification of OPINION.md after writing failed",
|
|
116
|
+
"# Opinion 模板目录": "# Opinion template catalog",
|
|
117
|
+
"当前没有已批准的公开模板。": "There are no approved public templates yet.",
|
|
118
|
+
"、": ", ",
|
|
119
|
+
"规则数量:{0};适用场景:{1}。": "Rules: {0}; scenarios: {1}.",
|
|
120
|
+
"管理版本化 Opinion 模板并生成统一 OPINION.md": "Manage versioned Opinion templates and generate a single OPINION.md",
|
|
121
|
+
"查看模板、规则和引导问题": "Show templates, rules and guiding questions",
|
|
122
|
+
"组合模板、规则和自定义内容": "Compose templates, rules and custom content",
|
|
123
|
+
"项目已经使用版本锁定;请用 compose --profile 更新": "The project already uses version locking; update it with compose --profile",
|
|
124
|
+
"至少选择一个模板、一个规则或一份自定义规则文件": "Select at least one template, one rule or one custom rules file",
|
|
125
|
+
"错误:{0}\n": "Error: {0}\n",
|
|
126
|
+
"路径必须是项目内的相对路径": "The path must be a relative path inside the project",
|
|
127
|
+
"拒绝使用符号链接": "Refusing to use a symbolic link",
|
|
128
|
+
"当前 Opinion 必须先通过 verify:{0}": "The current Opinion must pass verify first: {0}",
|
|
129
|
+
"occurrence 必须指向项目内的工作记录,根目录为:{0}": "occurrence must point at a work record inside the project; the roots are: {0}",
|
|
130
|
+
"use 必须记录适用规则的精确引用": "use must record exact references of the rules that applied",
|
|
131
|
+
"passed 与 user_changed 必须是明确的布尔值": "passed and user_changed must be explicit booleans",
|
|
132
|
+
"results 必须为每条规则给出结果:{0}": "results must give a result for every rule: {0}",
|
|
133
|
+
"存在违反的规则时 passed 不能为 true": "passed cannot be true while a rule is violated",
|
|
134
|
+
"当前 Profile 未声明分层加载,不能记录 {0}": "The current Profile does not declare tiered loading, so {0} cannot be recorded",
|
|
135
|
+
"{0} 必须是已声明规则束的不重复列表": "{0} must be a duplicate-free list of declared rule bundles",
|
|
136
|
+
"date 必须为 YYYY-MM-DD": "date must be YYYY-MM-DD",
|
|
137
|
+
"fixture 必须是明确的布尔值": "fixture must be an explicit boolean",
|
|
138
|
+
"候选需要完整的 title 与 text": "A candidate needs a complete title and text",
|
|
139
|
+
"候选需要明确的 scopes": "A candidate needs explicit scopes",
|
|
140
|
+
"候选内容变化时必须使用新的候选编号": "A candidate whose content changed must use a new candidate id",
|
|
141
|
+
"validation 必须为 pending、passed 或 failed": "validation must be pending, passed or failed",
|
|
142
|
+
"展示的内容与候选不一致": "The presented content does not match the candidate",
|
|
143
|
+
"人工裁决指向的候选内容不一致": "The human decision points at a different candidate content",
|
|
144
|
+
"需要明确的人工裁决": "An explicit human decision is required",
|
|
145
|
+
"采用需要人工批准当前精确的 Profile": "Adoption needs a human to approve the exact current Profile",
|
|
146
|
+
"不可变的 Profile 身份发生了变化": "The immutable Profile identity has changed",
|
|
147
|
+
"use 指向的 Opinion 内容已经变化": "The Opinion content that use points at has changed",
|
|
148
|
+
"复盘必须确认未变化的当前 Profile": "A review must confirm the unchanged current Profile",
|
|
149
|
+
"灰度内容已经变化;请采用新的已核验版本": "The trial content has changed; adopt the new verified version",
|
|
150
|
+
"转正式前先在新版本中解决灰度失败": "Resolve the trial failures in a new version before making it formal",
|
|
151
|
+
"未知的生命周期事件": "Unknown lifecycle event",
|
|
152
|
+
"不可变的生命周期证据发生变化;修改状态前先重新核验": "The immutable lifecycle evidence has changed; verify again before changing state",
|
|
153
|
+
"找不到可核验的旧 Profile": "No verifiable earlier Profile found",
|
|
154
|
+
"旧 Profile 与灰度记录的指纹不一致": "The earlier Profile does not match the fingerprint of the trial record",
|
|
155
|
+
"项目私有的候选、灰度与使用记录;不发布规则": "Project-private candidate, trial and usage records; never publishes rules",
|
|
156
|
+
"项目内保存状态与证据快照的相对目录": "relative directory inside the project that holds state and evidence snapshots",
|
|
157
|
+
"event 的事件 JSON,必须位于项目内": "event JSON of the event; must be inside the project",
|
|
158
|
+
"保存历史 Profile 的项目内相对目录,默认 .opinion/profiles": "relative directory inside the project that holds historical Profiles; default .opinion/profiles",
|
|
159
|
+
"允许作为独立发生场景的顶层目录,可重复": "top-level directories allowed as independent occurrence scenes; repeatable",
|
|
160
|
+
"queue 只输出未裁决候选的标题与适用范围": "queue: print only the title and scope of undecided candidates",
|
|
161
|
+
"预览返回的 confirmation_sha256": "confirmation_sha256 returned by the preview",
|
|
162
|
+
"只读命令不能使用 --apply": "A read-only command cannot use --apply",
|
|
163
|
+
"event 需要 --file": "event needs --file",
|
|
164
|
+
"事件证据已经变化": "The event evidence has changed",
|
|
165
|
+
"确认指纹不一致": "The confirmation fingerprint does not match",
|
|
166
|
+
"状态已经变化;请重新生成预览": "The state has changed; generate the preview again",
|
|
167
|
+
"Opinion 在写入期间发生变化;请重新生成预览": "The Opinion changed during writing; generate the preview again",
|
|
168
|
+
"证据在预览后发生变化": "The evidence changed after the preview",
|
|
169
|
+
"不可变的证据快照被改动": "The immutable evidence snapshot was modified",
|
|
170
|
+
"完整读取项目根目录 OPINION.md": "Read the full OPINION.md in the project root",
|
|
171
|
+
"Project Profile 的 opinion.loading_mode 必须为 tiered 或 full:{0!r}": "The Project Profile's opinion.loading_mode must be tiered or full: {0!r}",
|
|
172
|
+
"context 需要 --project 或 --profile": "context needs --project or --profile",
|
|
173
|
+
"Project Profile 设为完整读取": "The Project Profile is set to full reading",
|
|
174
|
+
"当前 Profile 未声明分层加载": "The current Profile does not declare tiered loading",
|
|
175
|
+
"未知的按需规则束:{0}": "Unknown on-demand rule bundle: {0}",
|
|
176
|
+
"bundle 需要至少一个模板系列或精确模板引用": "bundle needs at least one template family or exact template reference",
|
|
177
|
+
"未声明的交付信号:{0}": "Undeclared delivery signal: {0}",
|
|
178
|
+
"check 至少需要一个 --signal": "check needs at least one --signal",
|
|
179
|
+
"按已确认的加载声明只读切分正文,并核对交付信号": "Split the body read-only by the confirmed loading declaration and verify delivery signals",
|
|
180
|
+
"bundle 的模板系列或精确模板引用": "template family or exact template reference of the bundle",
|
|
181
|
+
"用候选或已保存的 Profile 预览,不读取项目锁": "preview with a candidate or saved Profile, without reading the project lock",
|
|
182
|
+
"交付信号,格式为 键=值,可重复": "delivery signal as key=value; repeatable",
|
|
183
|
+
"本次已读取的规则束,可重复": "rule bundles read this time; repeatable"
|
|
184
|
+
}
|