@sakanano-moe/agenthelm 6.5.2 → 6.6.0
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/bin/agenthelm.js +1 -1
- package/package.json +2 -2
- package/runtime/index.json +80 -0
- package/runtime/plugins/agent-pack/.codex-plugin/plugin.json +19 -0
- package/runtime/plugins/agent-pack/.mcp.json +12 -0
- package/runtime/plugins/agent-pack/hooks/hooks.json +16 -0
- package/runtime/plugins/agent-pack/mcp.json +14 -0
- package/runtime/plugins/agent-pack/plugin.json +20 -0
- package/runtime/plugins/agent-pack/references/capability-config.md +124 -0
- package/runtime/{agenthelm.py → plugins/agent-pack/runtime/agenthelm.py} +111 -28
- package/runtime/plugins/agent-pack/runtime/agenthelm_runtime.py +388 -0
- package/runtime/plugins/agent-pack/skills/agent-pack/SKILL.md +46 -0
- package/runtime/plugins/agents-init/.codex-plugin/plugin.json +22 -0
- package/runtime/plugins/agents-init/content.json +31 -0
- package/runtime/plugins/agents-init/plugin.json +19 -0
- package/runtime/plugins/agents-init/skills/agents-init/AGENT.template.md +376 -0
- package/runtime/plugins/agents-init/skills/agents-init/SKILL.md +119 -0
- package/runtime/plugins/agents-init/skills/agents-init/index.html +357 -0
- package/runtime/plugins/agents-init/skills/agents-init/launcher.sh +93 -0
- package/runtime/plugins/agents-init/skills/agents-init/project.schema.json +171 -0
- package/runtime/plugins/agents-init/skills/agents-init/scripts/init_project.py +1190 -0
- package/runtime/plugins/agents-init/skills/agents-init/scripts/test_init_project.py +678 -0
- package/runtime/plugins/agents-init/skills/agents-init/skill.json +122 -0
- package/runtime/plugins/opinion-manager/.codex-plugin/plugin.json +26 -0
- package/runtime/plugins/opinion-manager/catalog/README.md +15 -0
- package/runtime/plugins/opinion-manager/catalog/rules/.gitkeep +0 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.align-requirements/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.assumptions-authority/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.batch-questions/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.bilingual-terms/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.calculations/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.chinese-response/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.clean-deliverable/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.complete-options/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.conclusion-first/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.concrete-titles/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.consistent-items/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.device-readable/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.editable-diagrams/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.evidence-inference/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.maintenance-chinese/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.maintenance-project-language/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.markdown-structure/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.portable-files/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.report-context/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.report-formats/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.report-review/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.response-brief/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.response-dense/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.rules-tools/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.semantic-wrap/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.solutions-proportional/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.solutions-three-level/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.tables-compact/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.tables-list-first/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.test-evidence/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.versioned-files/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/templates/.gitkeep +0 -0
- package/runtime/plugins/opinion-manager/catalog/templates/delivery-standard/compact-tables/versions/1.0.0.json +21 -0
- package/runtime/plugins/opinion-manager/catalog/templates/delivery-standard/list-first/versions/1.0.0.json +21 -0
- package/runtime/plugins/opinion-manager/catalog/templates/development-standard/chinese-maintenance/versions/1.0.0.json +15 -0
- package/runtime/plugins/opinion-manager/catalog/templates/development-standard/project-language/versions/1.0.0.json +15 -0
- package/runtime/plugins/opinion-manager/catalog/templates/response-style/brief-chinese/versions/1.0.0.json +19 -0
- package/runtime/plugins/opinion-manager/catalog/templates/response-style/dense-chinese/versions/1.0.0.json +19 -0
- package/runtime/plugins/opinion-manager/catalog/templates/scenario/concise-project/versions/1.0.0.json +39 -0
- package/runtime/plugins/opinion-manager/catalog/templates/scenario/reviewed-project/versions/1.0.0.json +39 -0
- package/runtime/plugins/opinion-manager/catalog/templates/work-method/proportional/versions/1.0.0.json +20 -0
- package/runtime/plugins/opinion-manager/catalog/templates/work-method/three-level/versions/1.0.0.json +20 -0
- package/runtime/plugins/opinion-manager/content.json +63 -0
- package/runtime/plugins/opinion-manager/plugin.json +18 -0
- package/runtime/plugins/opinion-manager/references/lock.schema.json +14 -0
- package/runtime/plugins/opinion-manager/references/onboarding.md +57 -0
- package/runtime/plugins/opinion-manager/references/profile.schema.json +56 -0
- package/runtime/plugins/opinion-manager/references/rule.schema.json +16 -0
- package/runtime/plugins/opinion-manager/references/template.schema.json +71 -0
- package/runtime/plugins/opinion-manager/references/versioned-template.schema.json +19 -0
- package/runtime/plugins/opinion-manager/references/versioning.md +263 -0
- package/runtime/plugins/opinion-manager/runtime/constants.py +7 -0
- package/runtime/plugins/opinion-manager/runtime/context.py +180 -0
- package/runtime/plugins/opinion-manager/runtime/lifecycle.py +395 -0
- package/runtime/plugins/opinion-manager/runtime/opinion_manager.py +419 -0
- package/runtime/plugins/opinion-manager/runtime/versioned.py +705 -0
- package/runtime/plugins/opinion-manager/skills/opinion-manager/SKILL.md +62 -0
- package/runtime/plugins/opinion-manager/skills/opinion-manager/agents/openai.yaml +4 -0
- package/runtime/plugins/opinion-manager/templates/.gitkeep +1 -0
- package/runtime/plugins/project-orchestrator/.codex-plugin/plugin.json +18 -0
- package/runtime/plugins/project-orchestrator/content.json +37 -0
- package/runtime/plugins/project-orchestrator/plugin.json +19 -0
- package/runtime/plugins/project-orchestrator/references/asset-synonyms.example.json +8 -0
- package/runtime/plugins/project-orchestrator/references/case-workspaces.md +159 -0
- package/runtime/plugins/project-orchestrator/references/case.schema.json +31 -0
- package/runtime/plugins/project-orchestrator/runtime/assets.py +1288 -0
- package/runtime/plugins/project-orchestrator/runtime/case.py +557 -0
- package/runtime/plugins/project-orchestrator/runtime/case_workspace.py +350 -0
- package/runtime/plugins/project-orchestrator/runtime/memory.py +363 -0
- package/runtime/plugins/project-orchestrator/runtime/project_base.py +107 -0
- package/runtime/plugins/project-orchestrator/runtime/project_cli.py +599 -0
- package/runtime/plugins/project-orchestrator/runtime/project_demo.py +504 -0
- package/runtime/plugins/project-orchestrator/runtime/project_maintain.py +470 -0
- package/runtime/plugins/project-orchestrator/runtime/project_menu.py +394 -0
- package/runtime/plugins/project-orchestrator/runtime/project_real.py +522 -0
- package/runtime/plugins/project-orchestrator/runtime/project_write.py +329 -0
- package/runtime/plugins/project-orchestrator/runtime/wf.py +439 -0
- package/runtime/plugins/project-orchestrator/skills/project-orchestrator/SKILL.md +230 -0
- package/runtime/plugins/project-orchestrator/skills/project-orchestrator/agents/openai.yaml +4 -0
- package/runtime/plugins/project-orchestrator/templates/CASE.md +39 -0
- /package/runtime/{agent_pack_config.py → plugins/agent-pack/runtime/agent_pack_config.py} +0 -0
- /package/runtime/{mcp_env_launcher.py → plugins/agent-pack/runtime/mcp_env_launcher.py} +0 -0
- /package/runtime/{mcp_server.py → plugins/agent-pack/runtime/mcp_server.py} +0 -0
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""wf:人用的文件查找入口,基于交付物台账(runtime/assets.py),无需 Agent。
|
|
3
|
+
|
|
4
|
+
wf 交互模式
|
|
5
|
+
wf 笔记 9月1号 直接查(日期、同义词自动识别)
|
|
6
|
+
wf -o 笔记 9月1号 查到后直接打开最新一个
|
|
7
|
+
wf recent 最近改动的交付物
|
|
8
|
+
wf cases [词] Case 列表(进行中在前)
|
|
9
|
+
wf refs <路径|序号> [层数] 谁引用它 / 它引用谁
|
|
10
|
+
wf o|f|c|p|v <序号> 打开 / 在文件管理器中显示 / 复制路径 / 打印路径 / 列出版本(序号指上一次结果)
|
|
11
|
+
wf check 台账巡检摘要
|
|
12
|
+
wf rebuild 强制重建台账
|
|
13
|
+
|
|
14
|
+
--project <目录> 与 --output json 可写在任意位置;缺省从当前目录向上查找 Project Profile。
|
|
15
|
+
打开、显示与复制只在 macOS 上调用系统命令,其他平台打印绝对路径。
|
|
16
|
+
"""
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
import os
|
|
21
|
+
import re
|
|
22
|
+
import shutil
|
|
23
|
+
import subprocess
|
|
24
|
+
import sys
|
|
25
|
+
import unicodedata
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
|
|
28
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
29
|
+
import assets # noqa: E402
|
|
30
|
+
from assets import AssetError, Project # noqa: E402
|
|
31
|
+
|
|
32
|
+
ACTIONS = {"o": "open", "f": "reveal", "c": "copy", "v": "versions", "r": "refs", "p": "path"}
|
|
33
|
+
STATUS_LABEL = {"active": "进行中", "waiting": "等待", "done": "已完成", "captured": "待澄清",
|
|
34
|
+
"suspended": "挂起", "abandoned": "放弃"}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# ---------------------------------------------------------------- display
|
|
38
|
+
|
|
39
|
+
class Style:
|
|
40
|
+
def __init__(self) -> None:
|
|
41
|
+
self.on = sys.stdout.isatty() and "NO_COLOR" not in os.environ
|
|
42
|
+
|
|
43
|
+
def _wrap(self, code: str, text: str) -> str:
|
|
44
|
+
return f"\033[{code}m{text}\033[0m" if self.on else text
|
|
45
|
+
|
|
46
|
+
def bold(self, text: str) -> str:
|
|
47
|
+
return self._wrap("1", text)
|
|
48
|
+
|
|
49
|
+
def dim(self, text: str) -> str:
|
|
50
|
+
return self._wrap("2", text)
|
|
51
|
+
|
|
52
|
+
def cyan(self, text: str) -> str:
|
|
53
|
+
return self._wrap("36", text)
|
|
54
|
+
|
|
55
|
+
def green(self, text: str) -> str:
|
|
56
|
+
return self._wrap("32", text)
|
|
57
|
+
|
|
58
|
+
def yellow(self, text: str) -> str:
|
|
59
|
+
return self._wrap("33", text)
|
|
60
|
+
|
|
61
|
+
def red(self, text: str) -> str:
|
|
62
|
+
return self._wrap("31", text)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
S = Style()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def display_width(text: str) -> int:
|
|
69
|
+
return sum(2 if unicodedata.east_asian_width(character) in "WF" else 1 for character in text)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def clip(text: str, width: int) -> str:
|
|
73
|
+
if display_width(text) <= width:
|
|
74
|
+
return text
|
|
75
|
+
out, used = "", 0
|
|
76
|
+
for character in text:
|
|
77
|
+
size = 2 if unicodedata.east_asian_width(character) in "WF" else 1
|
|
78
|
+
if used + size > width - 1:
|
|
79
|
+
break
|
|
80
|
+
out += character
|
|
81
|
+
used += size
|
|
82
|
+
return out + "…"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def term_width() -> int:
|
|
86
|
+
return max(60, min(shutil.get_terminal_size((100, 24)).columns, 120))
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def status_text(item: dict) -> str:
|
|
90
|
+
status = item.get("status", "")
|
|
91
|
+
if not status:
|
|
92
|
+
return "project" if item.get("container_kind") == "project" else "-"
|
|
93
|
+
label = STATUS_LABEL.get(status, status)
|
|
94
|
+
return S.green(label) if status == "active" else S.yellow(label) if status == "waiting" else S.dim(label)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def render_groups(groups: list[dict]) -> None:
|
|
98
|
+
width = term_width()
|
|
99
|
+
for index, item in enumerate(groups, 1):
|
|
100
|
+
label = item["title"] or item["name"]
|
|
101
|
+
date = (item["modified"] or "")[:10]
|
|
102
|
+
left = f" {S.bold(S.cyan(f'{index:>2}'))} {S.bold(clip(label, width - 22))}"
|
|
103
|
+
visible = display_width(re.sub(r"\033\[[0-9;]*m", "", left))
|
|
104
|
+
print(left + " " * max(1, width - visible - len(date)) + S.dim(date))
|
|
105
|
+
owner = item["case_title"] or item["container"]
|
|
106
|
+
git = item["git"]
|
|
107
|
+
git_text = S.dim(git) if git.startswith(("tracked", "none")) else S.yellow(git)
|
|
108
|
+
print(f" {item['name']} · {clip(owner, width - 50)} ({status_text(item)}) · {git_text}")
|
|
109
|
+
print(S.dim(f" {clip(item['path'], width - 6)}"))
|
|
110
|
+
if item.get("versions"):
|
|
111
|
+
names = " · ".join(version["name"] for version in item["versions"][:4])
|
|
112
|
+
more = f" 等 {len(item['versions'])} 个" if len(item["versions"]) > 4 else ""
|
|
113
|
+
print(S.dim(f" └ 同系列另有 {len(item['versions'])} 个:{clip(names, width - 30)}{more}"))
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
# ---------------------------------------------------------------- last results
|
|
117
|
+
|
|
118
|
+
def save_last(project: Project, groups: list[dict]) -> None:
|
|
119
|
+
"""Remember the last result list (paths and versions) in the cache, never in the project."""
|
|
120
|
+
try:
|
|
121
|
+
path = project.state_dir(create=True) / assets.LAST_NAME
|
|
122
|
+
payload = [{"path": group["path"], "name": group["name"], "title": group["title"],
|
|
123
|
+
"modified": group["modified"], "versions": group.get("versions", [])} for group in groups]
|
|
124
|
+
path.write_text(json.dumps(payload, ensure_ascii=False), encoding="utf-8")
|
|
125
|
+
os.chmod(path, 0o600)
|
|
126
|
+
except (OSError, AssetError):
|
|
127
|
+
pass
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def load_last(project: Project) -> list[dict]:
|
|
131
|
+
try:
|
|
132
|
+
data = json.loads((project.state_dir() / assets.LAST_NAME).read_text(encoding="utf-8"))
|
|
133
|
+
except (OSError, ValueError, AssetError):
|
|
134
|
+
return []
|
|
135
|
+
return [item for item in data if isinstance(item, dict) and isinstance(item.get("path"), str)] \
|
|
136
|
+
if isinstance(data, list) else []
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def resolve_target(token: str, last: list[dict]) -> dict | None:
|
|
140
|
+
if token.isdigit():
|
|
141
|
+
index = int(token) - 1
|
|
142
|
+
if 0 <= index < len(last):
|
|
143
|
+
return last[index]
|
|
144
|
+
print(S.red(f"没有第 {token} 项(上一次结果共 {len(last)} 项)"))
|
|
145
|
+
return None
|
|
146
|
+
return {"path": token, "name": Path(token).name, "title": "", "modified": "", "versions": []}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
# ---------------------------------------------------------------- actions
|
|
150
|
+
|
|
151
|
+
def do_action(action: str, relative: str, project: Project, platform: str | None = None) -> str:
|
|
152
|
+
"""Open / reveal / copy on macOS; elsewhere (or without the commands) print the absolute path."""
|
|
153
|
+
target = str(project.root / relative)
|
|
154
|
+
mac = (platform or sys.platform) == "darwin"
|
|
155
|
+
if action == "open" and mac and shutil.which("open"):
|
|
156
|
+
subprocess.run(["open", target], check=False)
|
|
157
|
+
print(S.green("已打开 ") + relative)
|
|
158
|
+
elif action == "reveal" and mac and shutil.which("open"):
|
|
159
|
+
subprocess.run(["open", "-R", target], check=False)
|
|
160
|
+
print(S.green("已在 Finder 中显示 ") + relative)
|
|
161
|
+
elif action == "copy" and mac and shutil.which("pbcopy"):
|
|
162
|
+
subprocess.run(["pbcopy"], input=target.encode("utf-8"), check=False)
|
|
163
|
+
print(S.green("已复制绝对路径 ") + target)
|
|
164
|
+
else:
|
|
165
|
+
print(target)
|
|
166
|
+
return "printed"
|
|
167
|
+
return action
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def show_versions(group: dict) -> None:
|
|
171
|
+
print(S.bold(group.get("title") or group.get("name", "")))
|
|
172
|
+
print(f" {(group.get('modified') or '')[:16].replace('T', ' ')} {group['path']} {S.green('最新')}")
|
|
173
|
+
for version in group.get("versions", []):
|
|
174
|
+
print(f" {(version.get('modified') or '')[:16].replace('T', ' ')} {version['path']} "
|
|
175
|
+
f"{S.dim(version.get('git', ''))}")
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def show_refs(project: Project, path: str, depth: int = 1) -> dict:
|
|
179
|
+
result = assets.refs(project, path, depth=depth)
|
|
180
|
+
print(S.bold(result["path"]))
|
|
181
|
+
if result["inbound"]:
|
|
182
|
+
print(S.cyan(" 被引用:"))
|
|
183
|
+
for item in result["inbound"][:25]:
|
|
184
|
+
via = "" if item["via"] == "file" else S.dim("(目录级)")
|
|
185
|
+
print(f" ← {item['src']}{via}")
|
|
186
|
+
else:
|
|
187
|
+
print(S.dim(" 没有被任何 Case 或扫描根里的文件引用"))
|
|
188
|
+
if result["outbound"]:
|
|
189
|
+
print(S.cyan(" 引用了:"))
|
|
190
|
+
for item in result["outbound"][:25]:
|
|
191
|
+
print(f" → {item['dst']}")
|
|
192
|
+
if result["affected_containers"]:
|
|
193
|
+
print(S.yellow(" 改动会波及:") + "、".join(result["affected_containers"][:8]))
|
|
194
|
+
return result
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def do_search(project: Project, terms: list[str], *, limit: int = 10, **filters: str) -> list[dict]:
|
|
198
|
+
if assets.status(project)["status"] != "fresh":
|
|
199
|
+
print(S.dim("(索引更新中…)"))
|
|
200
|
+
result = assets.find(project, terms, limit=limit, **filters)
|
|
201
|
+
query = result["query"]
|
|
202
|
+
description = " ".join(query["terms"]) or "全部"
|
|
203
|
+
if query["dates"]:
|
|
204
|
+
description += " · 日期 " + ",".join(date.replace("*-", "") for date in query["dates"])
|
|
205
|
+
print(S.dim(f"查找:{description}"))
|
|
206
|
+
if not result["results"]:
|
|
207
|
+
print(S.yellow("没有找到。") + S.dim("试试更短的词、去掉日期,或 `wf cases` 先看 Case 列表。"))
|
|
208
|
+
return []
|
|
209
|
+
render_groups(result["results"])
|
|
210
|
+
tail = f"共 {result['total']} 组"
|
|
211
|
+
if result["total"] > result["returned"]:
|
|
212
|
+
tail += f",只显示前 {result['returned']} 组(加关键词收窄)"
|
|
213
|
+
print(S.dim(tail + "。序号可接 o 打开 / f 显示 / c 复制 / v 版本 / r 引用,例:o1"))
|
|
214
|
+
save_last(project, result["results"])
|
|
215
|
+
return result["results"]
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def do_cases(project: Project, term: str | None, limit: int = 30) -> dict:
|
|
219
|
+
result = assets.cases(project, term, limit=limit)
|
|
220
|
+
width = term_width()
|
|
221
|
+
for item in result["results"]:
|
|
222
|
+
status = STATUS_LABEL.get(item["status"], item["status"] or "-")
|
|
223
|
+
mark = S.green(f"{status:<4}") if item["status"] == "active" else S.dim(f"{status:<4}")
|
|
224
|
+
print(f" {mark} {S.bold(clip(item['title'] or item['id'], width - 52))} {S.dim(clip(item['id'], 44))} "
|
|
225
|
+
f"{S.dim(str(item['files']) + ' 个文件')}")
|
|
226
|
+
print(S.dim(f"共 {result['total']} 个 Case" + (";用 `wf <Case 关键词>` 查其文件" if result["total"] else "")))
|
|
227
|
+
return result
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def do_recent(project: Project, limit: int) -> dict:
|
|
231
|
+
result = assets.recent(project, limit=limit)
|
|
232
|
+
render_groups(result["results"])
|
|
233
|
+
save_last(project, result["results"])
|
|
234
|
+
return result
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def do_check(project: Project) -> dict:
|
|
238
|
+
report = assets.check(project)
|
|
239
|
+
totals, tiers, counts = report["totals"], report["claim_tiers"], report["broken_counts"]
|
|
240
|
+
git = totals["git"]
|
|
241
|
+
print(S.bold("台账巡检"))
|
|
242
|
+
print(f" 文件 {totals['files']} · 引用 {totals['edges']} · Git 已入库 {git.get('tracked', 0)} / "
|
|
243
|
+
f"不入库 {git.get('ignored', 0)} / 新建未提交 {git.get('untracked', 0)} / 非 Git {git.get('none', 0)}")
|
|
244
|
+
print(f" 交付物:文件级被引用 {tiers.get('file', 0)} · 仅目录覆盖 {tiers.get('directory', 0)} · "
|
|
245
|
+
f"无人认领 {S.yellow(str(tiers.get('none', 0)))}")
|
|
246
|
+
print(f" 断链:真断链 {S.yellow(str(counts.get('real-broken', 0)))} · "
|
|
247
|
+
f"说明文字 {counts.get('prose-not-path', 0)} · 主题链接 {counts.get('wikilink-topic', 0)}")
|
|
248
|
+
for container, paths in list(report["unclaimed"].items())[:5]:
|
|
249
|
+
print(S.dim(f" 无人认领 · {container}: {paths[0]}"))
|
|
250
|
+
return report
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
# ---------------------------------------------------------------- entry
|
|
254
|
+
|
|
255
|
+
HELP = f"""{S.bold('用法')}
|
|
256
|
+
直接输入关键词查找;可带日期(9月1号 / 0901 / 2026-09-01),同义词按项目的 asset-synonyms.json 展开。
|
|
257
|
+
{S.cyan('o1')} 打开第 1 项 {S.cyan('f1')} 在文件管理器中显示 {S.cyan('c1')} 复制绝对路径 {S.cyan('p1')} 打印路径
|
|
258
|
+
{S.cyan('v1')} 查看全部版本 {S.cyan('r1')} 谁引用它(r1 2 为两层) {S.cyan('1')} 直接打开
|
|
259
|
+
{S.cyan('recent')} 最近改动 {S.cyan('cases [词]')} Case 列表 {S.cyan('check')} 巡检 {S.cyan('q')} 退出
|
|
260
|
+
过滤:{S.cyan('ext:xlsx')} {S.cyan('kind:deliverable')}(也可 draft/source/data) {S.cyan('in:片段')}(限定 Case/目录)"""
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def parse_filters(tokens: list[str]) -> tuple[list[str], dict]:
|
|
264
|
+
terms, filters = [], {}
|
|
265
|
+
for token in tokens:
|
|
266
|
+
match = re.match(r"^(ext|kind|in):(.+)$", token)
|
|
267
|
+
if match:
|
|
268
|
+
filters[{"ext": "ext", "kind": "kind", "in": "container"}[match.group(1)]] = match.group(2)
|
|
269
|
+
else:
|
|
270
|
+
terms.append(token)
|
|
271
|
+
return terms, filters
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def split_globals(argv: list[str]) -> tuple[list[str], Path | None, str]:
|
|
275
|
+
"""Pull ``--project`` and ``--output`` out of any position."""
|
|
276
|
+
rest, project, output = [], None, "text"
|
|
277
|
+
index = 0
|
|
278
|
+
while index < len(argv):
|
|
279
|
+
token = argv[index]
|
|
280
|
+
name, _, inline = token.partition("=")
|
|
281
|
+
if name in ("--project", "--output"):
|
|
282
|
+
if inline:
|
|
283
|
+
value = inline
|
|
284
|
+
elif index + 1 < len(argv):
|
|
285
|
+
index += 1
|
|
286
|
+
value = argv[index]
|
|
287
|
+
else:
|
|
288
|
+
raise AssetError(f"{name} 需要一个值")
|
|
289
|
+
if name == "--project":
|
|
290
|
+
project = Path(value)
|
|
291
|
+
elif value in ("text", "json"):
|
|
292
|
+
output = value
|
|
293
|
+
else:
|
|
294
|
+
raise AssetError("--output 只能是 text 或 json")
|
|
295
|
+
else:
|
|
296
|
+
rest.append(token)
|
|
297
|
+
index += 1
|
|
298
|
+
return rest, project, output
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def interactive(project: Project) -> int:
|
|
302
|
+
try:
|
|
303
|
+
import readline # noqa: F401 line editing and history
|
|
304
|
+
except ImportError:
|
|
305
|
+
pass
|
|
306
|
+
current = assets.status(project)
|
|
307
|
+
if current["status"] != "fresh":
|
|
308
|
+
print(S.dim("(索引更新中…)"))
|
|
309
|
+
assets.rebuild(project)
|
|
310
|
+
current = assets.status(project)
|
|
311
|
+
print(S.bold(S.cyan("交付物台账")) + S.dim(f" · {current.get('file_count', '?')} 个文件 · 索引 "
|
|
312
|
+
f"{current.get('built_at', '')[11:16]}"))
|
|
313
|
+
print(S.dim("输入关键词查找;? 帮助;q 退出"))
|
|
314
|
+
groups: list[dict] = []
|
|
315
|
+
while True:
|
|
316
|
+
try:
|
|
317
|
+
line = input(S.bold(S.cyan("\n查找 › "))).strip()
|
|
318
|
+
except (EOFError, KeyboardInterrupt):
|
|
319
|
+
print()
|
|
320
|
+
return 0
|
|
321
|
+
if not line:
|
|
322
|
+
continue
|
|
323
|
+
if line in ("q", "quit", "exit", "退出"):
|
|
324
|
+
return 0
|
|
325
|
+
if line in ("?", "help", "帮助"):
|
|
326
|
+
print(HELP)
|
|
327
|
+
continue
|
|
328
|
+
try:
|
|
329
|
+
match = re.match(r"^([ofcvrp])\s*(\d+)(?:\s+(\d+))?$", line) or re.match(r"^()(\d+)()$", line)
|
|
330
|
+
if match:
|
|
331
|
+
key, number, extra = match.group(1) or "o", match.group(2), match.group(3)
|
|
332
|
+
target = resolve_target(number, groups or load_last(project))
|
|
333
|
+
if target is None:
|
|
334
|
+
continue
|
|
335
|
+
if key == "v":
|
|
336
|
+
show_versions(target)
|
|
337
|
+
elif key == "r":
|
|
338
|
+
show_refs(project, target["path"], depth=int(extra or 1))
|
|
339
|
+
else:
|
|
340
|
+
do_action(ACTIONS[key], target["path"], project)
|
|
341
|
+
continue
|
|
342
|
+
if line in ("recent", "最近"):
|
|
343
|
+
groups = do_recent(project, 15)["results"]
|
|
344
|
+
continue
|
|
345
|
+
if line == "cases" or line.startswith("cases "):
|
|
346
|
+
do_cases(project, line.split(None, 1)[1] if " " in line else None)
|
|
347
|
+
continue
|
|
348
|
+
if line == "check":
|
|
349
|
+
do_check(project)
|
|
350
|
+
continue
|
|
351
|
+
terms, filters = parse_filters(line.split())
|
|
352
|
+
groups = do_search(project, terms, **filters)
|
|
353
|
+
except AssetError as exc:
|
|
354
|
+
print(S.red(f"出错:{exc}"))
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def command(project: Project, argv: list[str], output: str) -> tuple[int, dict | None]:
|
|
358
|
+
head = argv[0]
|
|
359
|
+
|
|
360
|
+
def number(position: int, default: int) -> int:
|
|
361
|
+
return int(argv[position]) if len(argv) > position and argv[position].isdigit() else default
|
|
362
|
+
|
|
363
|
+
if output == "json":
|
|
364
|
+
if head in ("recent", "最近"):
|
|
365
|
+
return 0, assets.recent(project, limit=number(1, 15))
|
|
366
|
+
if head in ("cases", "case"):
|
|
367
|
+
return 0, assets.cases(project, " ".join(argv[1:]) or None)
|
|
368
|
+
if head == "check":
|
|
369
|
+
return 0, assets.check(project)
|
|
370
|
+
if head == "rebuild":
|
|
371
|
+
return 0, assets.rebuild(project, force=True)
|
|
372
|
+
if head in ("refs", "r") and len(argv) > 1:
|
|
373
|
+
target = resolve_target(argv[1], load_last(project))
|
|
374
|
+
return (1, None) if target is None else (0, assets.refs(project, target["path"], depth=number(2, 1)))
|
|
375
|
+
terms, filters = parse_filters(argv[1:] if head in ("-o", "-f", "-c") else argv)
|
|
376
|
+
result = assets.find(project, terms, **filters)
|
|
377
|
+
save_last(project, result["results"])
|
|
378
|
+
return (0 if result["results"] else 1), result
|
|
379
|
+
if head in ("recent", "最近"):
|
|
380
|
+
do_recent(project, number(1, 15))
|
|
381
|
+
return 0, None
|
|
382
|
+
if head in ("cases", "case"):
|
|
383
|
+
do_cases(project, " ".join(argv[1:]) or None)
|
|
384
|
+
return 0, None
|
|
385
|
+
if head == "check":
|
|
386
|
+
do_check(project)
|
|
387
|
+
return 0, None
|
|
388
|
+
if head == "rebuild":
|
|
389
|
+
result = assets.rebuild(project, force=True)
|
|
390
|
+
print(f"{result['action']}:{result['file_count']} 个文件")
|
|
391
|
+
return 0, None
|
|
392
|
+
if (head in ACTIONS and len(argv) > 1 and (argv[1].isdigit() or head == "r")) or (head == "refs" and len(argv) > 1):
|
|
393
|
+
target = resolve_target(argv[1], load_last(project))
|
|
394
|
+
if target is None:
|
|
395
|
+
return 1, None
|
|
396
|
+
if head in ("r", "refs"):
|
|
397
|
+
show_refs(project, target["path"], depth=number(2, 1))
|
|
398
|
+
elif head == "v":
|
|
399
|
+
show_versions(target)
|
|
400
|
+
else:
|
|
401
|
+
do_action(ACTIONS[head], target["path"], project)
|
|
402
|
+
return 0, None
|
|
403
|
+
flags = {"-o": "open", "-f": "reveal", "-c": "copy"}
|
|
404
|
+
action = flags.get(head)
|
|
405
|
+
terms, filters = parse_filters(argv[1:] if action else argv)
|
|
406
|
+
groups = do_search(project, terms, **filters)
|
|
407
|
+
if action and groups:
|
|
408
|
+
do_action(action, groups[0]["path"], project)
|
|
409
|
+
return (0 if groups else 1), None
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def main(argv: list[str] | None = None) -> int:
|
|
413
|
+
argv = list(sys.argv[1:] if argv is None else argv)
|
|
414
|
+
output = "text"
|
|
415
|
+
try:
|
|
416
|
+
argv, start, output = split_globals(argv)
|
|
417
|
+
if argv and argv[0] in ("-h", "--help", "help"):
|
|
418
|
+
print(__doc__)
|
|
419
|
+
return 0
|
|
420
|
+
project = Project.discover(start, explicit=start is not None)
|
|
421
|
+
if not argv:
|
|
422
|
+
if output == "json":
|
|
423
|
+
raise AssetError("交互模式不支持 --output json")
|
|
424
|
+
return interactive(project)
|
|
425
|
+
code, result = command(project, argv, output)
|
|
426
|
+
if output == "json":
|
|
427
|
+
print(json.dumps(result if result is not None else {"ok": False, "code": "not-found",
|
|
428
|
+
"error": "没有这一项"}, ensure_ascii=False, indent=2))
|
|
429
|
+
return code
|
|
430
|
+
except AssetError as exc:
|
|
431
|
+
if output == "json":
|
|
432
|
+
print(json.dumps({"ok": False, "code": exc.code, "error": str(exc)}, ensure_ascii=False))
|
|
433
|
+
else:
|
|
434
|
+
print(S.red(f"出错:{exc}"), file=sys.stderr)
|
|
435
|
+
return 2
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
if __name__ == "__main__":
|
|
439
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: project-orchestrator
|
|
3
|
+
description: 按项目契约统一执行会话发现、Task/Case 选择、能力路由、文件落位、验证、进度更新和限定范围的 Git 交付。当项目在 AGENTS.md 中声明 project-orchestrator,且当前工作可能创建或修改项目文件时使用。Agent 扩展(Plugin/Skill/MCP)的盘点、迁移与同步不在本 Skill 内——那是 agent-pack。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Project Orchestrator(项目编排器)
|
|
7
|
+
|
|
8
|
+
## 定位
|
|
9
|
+
|
|
10
|
+
作为已配置项目唯一的**项目管理生命周期编排器**。读取项目自身契约,选择最小且合适的工作容器,把领域工作路由给确定性 Tool 与叶子 Skill,再通过验证和限定范围的交付完成闭环。
|
|
11
|
+
|
|
12
|
+
本 Skill 通用且可移植。禁止嵌入个人偏好、私有路径、项目密钥或项目专属治理副本;这些内容归用户私有配置或项目自身所有。
|
|
13
|
+
|
|
14
|
+
边界(都是独立能力,本 Skill 不代管它们):
|
|
15
|
+
|
|
16
|
+
- **项目初始化与根目录骨架** 归 `agents-init`。本 Skill 只消费已初始化项目的契约。
|
|
17
|
+
- **Agent 扩展能力管理**(Plugin/Skill/MCP 的 inventory / doctor / bootstrap / sync / transfer / reconcile)归 **agent-pack**(`tools/agent-pack/`)。跨机恢复的单一入口是 `tools/agent-bootstrap`。
|
|
18
|
+
- **指导与审查规则** 归 Project Profile 声明的 Opinion provider;当前分发包提供 `opinion-manager`。它不负责 Task/Case 选择、文件结构、进度状态或 Git。
|
|
19
|
+
|
|
20
|
+
## 权威顺序
|
|
21
|
+
|
|
22
|
+
按以下顺序解析指令:
|
|
23
|
+
|
|
24
|
+
1. 用户当前请求与明确授权;
|
|
25
|
+
2. 平台安全与工具约束;
|
|
26
|
+
3. 最近的项目 `AGENTS.md` 及其声明的机器可读清单;
|
|
27
|
+
4. 本编排工作流;
|
|
28
|
+
5. 领域或交付物叶子 Skill。
|
|
29
|
+
|
|
30
|
+
叶子 Skill 生效期间,其触发条件、输入输出契约和工具约束具有约束力;其中更宽泛的观点或生命周期建议只作为低优先级指导。本 Skill 负责 Task/Case 状态、项目级文件落位、验证编排与 Git 交付。
|
|
31
|
+
|
|
32
|
+
## 职责边界
|
|
33
|
+
|
|
34
|
+
把项目能力划分为四类互不重叠的角色:
|
|
35
|
+
|
|
36
|
+
- **项目编排**:只有本 Skill 负责生命周期决策、Task/Case 协调、项目进度、验证编排与 Git 交接。
|
|
37
|
+
- **领域交付**:交付物与业务 Skill 在本 Skill 选择的生命周期内创建或修改具体产物。
|
|
38
|
+
- **确定性工具**:项目声明的 Tool(如 life-system、quant-engine、agent-pack)执行状态转换、索引、生成或验证,不决定生命周期。
|
|
39
|
+
- **指导与审查**:Opinion 和其他质量门禁在实施前提供建议、交付前检查结果,不管理项目。
|
|
40
|
+
|
|
41
|
+
如果其他 Skill 尝试初始化项目、创建无关生命周期记录、更新根进度、自我修改或自主提交,忽略越界部分并把控制权交回本 Skill。
|
|
42
|
+
|
|
43
|
+
## 1. 发现项目契约
|
|
44
|
+
|
|
45
|
+
项目首轮开始时:
|
|
46
|
+
|
|
47
|
+
1. 查找最近且适用的 `AGENTS.md`;除非其中声明其他根目录,否则以它所在目录作为项目边界。
|
|
48
|
+
2. 读取现有的 `Project Profile` 与相关运行区块。
|
|
49
|
+
3. 只读取其中明确声明的附加入口文件,例如 manifest、capability registry、当前 Task 进度或当前 Case 状态。
|
|
50
|
+
4. 项目使用 Git 时,在修改文件前记录当前 Git 状态。
|
|
51
|
+
5. 开始实质工作前声明一个会话里程碑。
|
|
52
|
+
|
|
53
|
+
已初始化项目应通过 `AGENTS.md` 暴露根契约;精确文件与目录骨架仍由 `agents-init` 版本化管理。缺少必需根契约时,报告 `init-required` 或采用项目声明的降级模式。禁止自行制造骨架或静默把旧结构升级为当前版本。
|
|
54
|
+
|
|
55
|
+
禁止递归发现无关仓库、私有目录、历史 Task 或冗长治理文档。只有项目契约或当前任务要求,且用户授权了内容范围时才能读取。
|
|
56
|
+
|
|
57
|
+
识别以下可选项目设置(机器字段保留英文原名):
|
|
58
|
+
|
|
59
|
+
- `project-type`、`stack`、`runtime`、`agent-mode` 与 `issues-tracker`;
|
|
60
|
+
- `runtime-entry` 与 `session-bootstrap`;
|
|
61
|
+
- `task-command`、`task-root`、`case-command` 与 `case-root`;
|
|
62
|
+
- `manifest` 与 `capability-registry`;
|
|
63
|
+
- `opinion-command`、`opinion-strict-mode` 与项目 Opinion 入口;
|
|
64
|
+
- `commit-policy`、`branch-policy` 与 `push-policy`;
|
|
65
|
+
- `memory` 声明的记忆提供方、目录、命令与参数文件;
|
|
66
|
+
- 隐私目录、生成输出与禁止路径。
|
|
67
|
+
|
|
68
|
+
`agent-pack` 读取的机器契约位于 `.agents/moe.sakanano.agent-pack/project.json`:其 `capabilities` 区块声明可移植 Plugin 根目录、外部 Plugin 目录、独立 Skill 根目录、可移植/原生 MCP 来源、客户端策略、本地凭据环境文件与导入目标。所有相对路径都必须限制在项目根目录内。本 Skill 不写该文件。
|
|
69
|
+
|
|
70
|
+
缺少可选设置时关闭对应集成。禁止杜撰命令或路径。
|
|
71
|
+
|
|
72
|
+
## 2. 分类当前请求
|
|
73
|
+
|
|
74
|
+
先读取 Project Profile 的 `work.mode`。新项目默认 `case-workspace`;字段缺失或为 `legacy` 时保持项目现有 Task/Case 模式,禁止因升级 Skill 自动迁移其他项目。
|
|
75
|
+
|
|
76
|
+
### Case 工作目录模式
|
|
77
|
+
|
|
78
|
+
`work.mode=case-workspace` 时,Case 是唯一持久工作容器。需要保存交付物、证据或跨会话状态的工作使用 Case;无持久资产的单轮回答直接完成。子任务留在同一 Case 的工作项清单,确需独立过程记录时放入 `work-items/`,不得建立平行的顶层 Task 生命周期。
|
|
79
|
+
|
|
80
|
+
Case 自有输入放 `refs/`,交付物放 `docs/`(草稿 `docs/drafts/`),数据放 `data/`,图片放 `images/`,专属脚本放 `scripts/`,过程记录放 `.agent-doc/`。只创建实际需要的子目录。`CASE.md` 保存目标、状态、下一动作、决策与完成证据,过程文件不得成为第二套状态权威。
|
|
81
|
+
|
|
82
|
+
专属于 Case 的来源包、会议纪要、核验结果和报告保存在 Case 内。跨 Case 的版本化公共模板、项目运行时代码和长期项目主文档保留原权威位置,Case 记录来源、版本和本地证据;公共参考需有明确复用范围,禁止将单个 Case 的资料扔入共享目录。自有文件使用 Case 相对引用,外部权威引用注明项目根目录或外部来源。隐私区不因自包含要求而越界复制。
|
|
83
|
+
|
|
84
|
+
项目没有声明自己的 Case 工具(Profile 无 `work.case.command`)时,用随包的 `runtime/case.py` 新建、更新与校验 Case,不手写 Frontmatter;用法见 [Case 工作目录工具](../../references/case-workspaces.md#默认-case-工具官方样例)。
|
|
85
|
+
|
|
86
|
+
人不经过 Agent 时用项目入口 `runtime/project_cli.py`(项目根目录的启动器或 `agenthelm project`)找资料、看进展、检查与记录;Agent 需要同样的信息时调用它的子命令并加 `--output json`,例如 `project_cli.py --project <项目> progress --output json`,不另写一套查找逻辑。写入类子命令(`fix`、`record`、`tools --run`)默认只预览,执行前按本 Skill 的授权规则取得用户同意。项目在 Profile 的 `entry` 段声明自有事项工具与项目工具,约定见 [项目入口](../../references/case-workspaces.md#项目入口不需要-ai-agent)。
|
|
87
|
+
|
|
88
|
+
检索和统计以所有 Case 为入口,通过标题、别名、旧 Task ID、标签、目标和文件名查找;正文只在候选确定后按需读取。查找具体文件、版本或引用影响面时先用随包的 `runtime/assets.py find` / `refs` / `check`(人用入口 `runtime/wf.py`),不靠记忆翻目录;见 [交付物台账](../../references/case-workspaces.md#交付物台账与-wf-查找官方样例)。恢复视图只展示未关闭状态,全量统计包括历史完成与放弃记录,禁止把迁移时间当作业务完成时间。
|
|
89
|
+
|
|
90
|
+
已授权迁移历史 Task 时,先列出全部源目录与目标映射,再做代表性 Demo、哈希验证和批量迁移。独立 Task 转独立 Case,确有同一目标和证据链的 Task 才合入既有 Case;标题相似不能作为合并证据。保留原 TASK.md、progress、原文件和旧 ID;关闭原因包含放弃时保留真实历史状态。工具入口和限制见 [Case 工作目录工具](../../references/case-workspaces.md)。
|
|
91
|
+
|
|
92
|
+
### 既有 Task/Case 模式
|
|
93
|
+
|
|
94
|
+
写入前分类当前请求:
|
|
95
|
+
|
|
96
|
+
- **回答或诊断**:检查并报告;除非用户要求,否则不创建生命周期记录或修改文件。
|
|
97
|
+
- **小型项目改动**:可在单个会话完成且项目未强制要求 Task 时直接编辑。
|
|
98
|
+
- **Task**:适用于边界明确,并需要工作目录、产物、决策或独立进度记录的交付。
|
|
99
|
+
- **Case**:适用于跨会话服务线索、不确定性调研、外部等待、回访或结果跟踪。
|
|
100
|
+
- **Task + Case**:只在长期 Case 协调具体 Task 工作区时使用。
|
|
101
|
+
|
|
102
|
+
仅在既有模式下使用以上 Task/Case 分类。Case 工作目录模式统一续接 Case。依据目标、证据链和交付物判断,不能只看标题相似度。
|
|
103
|
+
|
|
104
|
+
除非项目明确要求,否则寒暄、确认、简单问题、命令修正或一次性单文件编辑不创建 Task。禁止只为满足指标或流程而创建 Case。
|
|
105
|
+
|
|
106
|
+
## 3. 初始化或恢复状态
|
|
107
|
+
|
|
108
|
+
项目声明 bootstrap 命令时:
|
|
109
|
+
|
|
110
|
+
1. 首次收到服务输入时运行一次只读状态命令。
|
|
111
|
+
2. 只有状态明确报告 bootstrap 必需或已过期时才执行 bootstrap。
|
|
112
|
+
3. 创建新工作前,用配置命令发现未完成工作。
|
|
113
|
+
4. 只有当前任务授权覆盖私有 Case 或 Task 上下文时才能展开读取。
|
|
114
|
+
|
|
115
|
+
已经位于 Task 目录时,先读取任务简报和本地进度,再读项目 backlog。已经位于 Case 上下文时,使用配置的下一步命令,禁止依靠记忆重建状态。
|
|
116
|
+
|
|
117
|
+
Project Profile 声明了 `memory` 时,恢复状态后读取记忆索引:`provider` 为 `project-orchestrator` 时运行随包的 `runtime/memory.py index --project <项目>`,为 `project` 时运行其 `index_command`。索引每条一行,说明何时应当想起该条记忆;只在当前任务命中时读取条目正文。依据某条记忆行动前,核对其中提到的文件、命令与外部对象仍然存在;标注为可能过期的条目先核实再使用。
|
|
118
|
+
|
|
119
|
+
## 4. 路由能力
|
|
120
|
+
|
|
121
|
+
根据三项输入构建执行路由:
|
|
122
|
+
|
|
123
|
+
1. 用户要求的结果;
|
|
124
|
+
2. Project Profile 与当前 Task/Case 约束;
|
|
125
|
+
3. 项目已声明的 capability registry。
|
|
126
|
+
|
|
127
|
+
采用以下优先级:
|
|
128
|
+
|
|
129
|
+
1. 用确定性项目 Tool 执行状态转换、索引、生成或验证;
|
|
130
|
+
2. 用精确匹配的领域或交付物叶子 Skill 处理判断密集型工作;
|
|
131
|
+
3. 只有没有已注册能力适用时才采用通用实现。
|
|
132
|
+
|
|
133
|
+
一个交付物可以同时启用多个叶子 Skill。先应用最具体的交付物/领域 Skill,再执行无障碍、安全或 Opinion 等横切检查。两个 Skill 指令冲突时,在会改变交付物的最小决策点暂停并呈现冲突。
|
|
134
|
+
|
|
135
|
+
禁止宣称缺失能力已经存在。使用项目定义的机制记录能力缺口,或直接报告。
|
|
136
|
+
|
|
137
|
+
当前请求是"盘点/迁移/同步/校验 Agent 扩展"时,不在本 Skill 内处理:交给 `agent-pack`(`tools/agent-pack/runtime/agent_pack_config.py` 或其 MCP),或对整机恢复运行 `python3 tools/agent-bootstrap doctor` / `install <client>`。本 Skill 只负责把这类请求识别并路由过去。
|
|
138
|
+
|
|
139
|
+
## 5. 计划与执行
|
|
140
|
+
|
|
141
|
+
计划复杂度与工作量保持匹配:
|
|
142
|
+
|
|
143
|
+
- 小型可逆改动使用简短内部计划;
|
|
144
|
+
- Case 工作目录模式统一更新 CASE.md,子工作项仅保存过程;
|
|
145
|
+
- 既有模式中的多步骤交付物工作更新 Task 进度,跨会话连续工作或回访更新 Case 状态。
|
|
146
|
+
|
|
147
|
+
编辑前:
|
|
148
|
+
|
|
149
|
+
1. 检查相关现有文件和邻近测试;
|
|
150
|
+
2. 在 dirty worktree 中保留用户的无关改动;
|
|
151
|
+
3. 识别权威源与生成派生物;
|
|
152
|
+
4. 只确认会实质改变范围、权限、外部状态或不可逆影响的决策。
|
|
153
|
+
|
|
154
|
+
执行期间:
|
|
155
|
+
|
|
156
|
+
- 修改权威源后,通过已声明 Tool 重新生成派生物;
|
|
157
|
+
- 文件只能放在项目声明的位置;
|
|
158
|
+
- 保留可复用事实、决策与验证证据,避免提交缓存、临时日志或可重建中间产物;
|
|
159
|
+
- 由叶子 Skill 创建或审查交付物,本 Skill 继续拥有生命周期与 Git 职责;
|
|
160
|
+
- 证据改变执行路线时更新计划。
|
|
161
|
+
|
|
162
|
+
## 6. 调用独立指导与审查
|
|
163
|
+
|
|
164
|
+
Opinion 是独立的指导/审查能力。项目声明 Opinion provider,且 `OPINION.md` 含用户确认的规则时,在两个明确边界调用它:
|
|
165
|
+
|
|
166
|
+
1. **实施前**:使用明确的交付物信号请求适用指导。
|
|
167
|
+
2. **交付前**:请求独立检查,并通过 provider 自身的演化流程记录未解决冲突或可复用反馈。
|
|
168
|
+
|
|
169
|
+
provider 支持分层加载且规则已声明时,实施前读取核心规则与规则束索引,任务命中触发条件或判断不清时读取对应规则束,交付前用 provider 的核对命令比对本次交付的信号与已读取的规则束。provider 返回 `fallback` 或不可用时完整读取 `OPINION.md`。
|
|
170
|
+
|
|
171
|
+
禁止在本 Skill 内解释、存储或演化 Opinion 规则;禁止让 Opinion provider 选择 Task/Case 状态、路径、进度或 Git 操作。`OPINION.md` 仍为空白时跳过规则检查并记录未配置状态。provider 不可用时,明确报告指导/检查降级;只有项目策略允许时才能继续。
|
|
172
|
+
|
|
173
|
+
## 7. 验证与记录
|
|
174
|
+
|
|
175
|
+
按风险比例验证:
|
|
176
|
+
|
|
177
|
+
- 对改动范围运行聚焦测试或 validator;
|
|
178
|
+
- 验证机器可读 manifest 与生成索引;
|
|
179
|
+
- 布局重要时目视检查渲染产物;
|
|
180
|
+
- 运行项目声明的 Opinion 或其他横切质量门禁;
|
|
181
|
+
- 检查最终 diff 的范围、密钥、生成噪声与意外删除。
|
|
182
|
+
|
|
183
|
+
记录当前工作容器要求的最小持久证据:
|
|
184
|
+
|
|
185
|
+
- Task:状态、决策、验证证据、阻塞和下一步;
|
|
186
|
+
- Case:候选、决策、已观察结果;结果尚未发生时记录 follow-up;
|
|
187
|
+
- 直接改动:简洁交接与验证结果。
|
|
188
|
+
|
|
189
|
+
禁止把推断结果记录为已确认 outcome。
|
|
190
|
+
|
|
191
|
+
记录可复用的项目事实时遵守项目的记忆约定:
|
|
192
|
+
|
|
193
|
+
- 每类信息只有一个权威位置,其他位置只留指针;进行中的工作状态属于 Task 或 Case,行为与表达偏好属于 Opinion provider 的候选流程。
|
|
194
|
+
- 能从文件或版本历史直接得到的内容、已完成工作的流水、只对当前对话有用的内容不写入记忆。
|
|
195
|
+
- 客户端自带的记忆功能只保存该客户端自身的运行事实;项目事实写入仓库。
|
|
196
|
+
- 使用随包工具时,先用 `memory.py create` 预览再写入,`description` 写明何时应当想起这条记忆;内容已晋升为规则或已经失效时用 `retire` 退役。
|
|
197
|
+
|
|
198
|
+
结束一个里程碑前运行记忆检查:随包工具为 `memory.py check`,自有实现为 Profile 的 `check_command`。先处理结构问题与失效的来源路径;到期条目核实后用 `verify` 更新。体积预算、保留窗口与复核间隔由项目自己的参数文件规定。
|
|
199
|
+
|
|
200
|
+
## 8. 限定范围的 Git 交付
|
|
201
|
+
|
|
202
|
+
对 Git 项目:
|
|
203
|
+
|
|
204
|
+
1. 把最终状态与基线比较;
|
|
205
|
+
2. 只暂存属于当前请求的文件;
|
|
206
|
+
3. 检查 staged diff;
|
|
207
|
+
4. 只有用户明确要求/授权,或项目策略允许自动提交时才 commit;
|
|
208
|
+
5. push、发布、merge、tag 或创建 PR 必须有单独明确授权,或项目策略明确许可。
|
|
209
|
+
|
|
210
|
+
dirty worktree 中禁止宽泛暂存。禁止重写、丢弃或吸收无关改动。创建 commit 后报告 hash。
|
|
211
|
+
|
|
212
|
+
## 9. 保持边界清晰
|
|
213
|
+
|
|
214
|
+
- 项目初始化与骨架迁移归版本化的 `agents-init` 管理,不属于本 Skill。
|
|
215
|
+
- Agent Plugin、Skill 与 MCP 的盘点/迁移/同步归 **agent-pack**(`tools/agent-pack/`);跨机恢复入口是 `tools/agent-bootstrap`。禁止在本 Skill 内重建第二套配置管理逻辑。
|
|
216
|
+
- 除非本 Skill 明确委派,领域 Skill 禁止自我修改、创建无关 Task/Case、编辑全局进度或提交改动。
|
|
217
|
+
- 生成索引属于投影,源记录保持权威地位。
|
|
218
|
+
- 项目本地规则可以特化本工作流,应引用本 Skill,避免复制完整生命周期。
|
|
219
|
+
|
|
220
|
+
## 完成检查
|
|
221
|
+
|
|
222
|
+
结束前确认:
|
|
223
|
+
|
|
224
|
+
- 已交付用户要求的结果,或已识别具体阻塞;
|
|
225
|
+
- Task/Case 选择有依据且状态为最新;
|
|
226
|
+
- 已按声明能力路由项目 Tool 与叶子 Skill;
|
|
227
|
+
- 已运行相关测试、渲染和 Opinion 检查,或明确说明省略项;
|
|
228
|
+
- 项目声明了记忆提供方时,已运行记忆检查,新写入的条目符合项目的记忆约定;
|
|
229
|
+
- 无关 worktree 改动保持原样;
|
|
230
|
+
- commit 与 push 操作符合用户授权。
|