@sakanano-moe/agenthelm 6.8.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 +228 -147
- package/runtime/plugins/agent-pack/runtime/agenthelm_runtime.py +64 -59
- 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 +4 -4
- 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 +26 -1
- package/runtime/plugins/agents-init/skills/agents-init/project.schema.json +399 -80
- 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 +210 -76
- 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 +210 -62
- 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 +4 -0
- package/runtime/plugins/agents-init/skills/agents-init/skeleton.json +2 -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 +222 -189
- package/runtime/plugins/project-orchestrator/runtime/project_demo.py +130 -48
- package/runtime/plugins/project-orchestrator/runtime/project_maintain.py +241 -204
- package/runtime/plugins/project-orchestrator/runtime/project_menu.py +145 -89
- package/runtime/plugins/project-orchestrator/runtime/project_real.py +184 -105
- 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
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"""SOP 文件的结构:小节名、字段名、前置条件的写法与写进 Case 的文字,按项目文件语言(Project Profile 的 ``language``)。
|
|
2
|
+
|
|
3
|
+
和 Case 一样(见 case_structure.py),SOP 写进项目的文件用什么语言是项目的属性,随项目同步,不随命令行界面语言
|
|
4
|
+
(AGENTHELM_LANG)变化:``language`` 为 ``en`` 的项目新建的 SOP 用英文小节名与字段名,缺省或 ``zh`` 的项目保持中文。
|
|
5
|
+
读取时两套词表都认,所以手写的、从别的项目拷来的、换过语言的 SOP 都能照常解析;追加内容时沿用文件自己的写法。
|
|
6
|
+
|
|
7
|
+
frontmatter 的键、枚举值(ask / stop / retry …)本来就是英文,与语言无关,不在这里。
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import re
|
|
12
|
+
|
|
13
|
+
import case_structure
|
|
14
|
+
|
|
15
|
+
LANGUAGES = case_structure.LANGUAGES
|
|
16
|
+
language_of = case_structure.language_of
|
|
17
|
+
|
|
18
|
+
SECTIONS = {
|
|
19
|
+
"purpose": ("目的", "Purpose"),
|
|
20
|
+
"requires": ("前置条件", "Prerequisites"),
|
|
21
|
+
"steps": ("步骤", "Steps"),
|
|
22
|
+
"done": ("完成标准", "Completion criteria"),
|
|
23
|
+
"exceptions": ("例外与回退", "Exceptions and fallback"),
|
|
24
|
+
"revisions": ("修订记录", "Revision log"),
|
|
25
|
+
}
|
|
26
|
+
STRICT_SECTIONS = ("requires", "steps", "done")
|
|
27
|
+
|
|
28
|
+
STEP_KEYS = {
|
|
29
|
+
"tools": ("工具", "Tools"),
|
|
30
|
+
"inputs": ("输入", "Inputs"),
|
|
31
|
+
"intermediates": ("中间产物", "Intermediates"),
|
|
32
|
+
"finals": ("最终产物", "Final outputs"),
|
|
33
|
+
"checkpoint": ("检查点", "Checkpoint"),
|
|
34
|
+
"on_failure": ("失败时", "On failure"),
|
|
35
|
+
"confirm": ("需确认", "Needs confirmation"),
|
|
36
|
+
"note": ("说明", "Note"),
|
|
37
|
+
}
|
|
38
|
+
REQ_KINDS = {
|
|
39
|
+
"command": ("命令", "Command"),
|
|
40
|
+
"module": ("模块", "Module"),
|
|
41
|
+
"env": ("环境变量", "Environment variable"),
|
|
42
|
+
"path": ("路径", "Path"),
|
|
43
|
+
"skill": ("技能", "Skill"),
|
|
44
|
+
"mcp": ("MCP", "MCP"),
|
|
45
|
+
"manual": ("手动确认", "Manual check"),
|
|
46
|
+
}
|
|
47
|
+
REQ_KEYS = {
|
|
48
|
+
"purpose": ("用途", "Purpose"),
|
|
49
|
+
"install": ("安装", "Install"),
|
|
50
|
+
"alternative": ("替代", "Alternative"),
|
|
51
|
+
"if_missing": ("缺失时", "If missing"),
|
|
52
|
+
"version": ("版本", "Version"),
|
|
53
|
+
"when": ("仅当", "Only if"),
|
|
54
|
+
}
|
|
55
|
+
MISSING = {
|
|
56
|
+
"ask": ("询问用户", "Ask the user"),
|
|
57
|
+
"stop": ("停止", "Stop"),
|
|
58
|
+
"skip-step": ("跳过该步", "Skip the step"),
|
|
59
|
+
"alternative": ("使用替代", "Use the alternative"),
|
|
60
|
+
}
|
|
61
|
+
# 只在读取时认的别名(简写)。
|
|
62
|
+
KIND_ALIASES = {"手动": "manual"}
|
|
63
|
+
MISSING_ALIASES = {"询问": "ask", "跳过": "skip-step", "替代": "alternative", "ask": "ask", "skip": "skip-step"}
|
|
64
|
+
|
|
65
|
+
TRUE_WORDS = ("是", "y", "yes", "true", "需要", "required")
|
|
66
|
+
NONE_WORDS = ("无", "(无)", "(无)", "不需要", "none", "(none)", "n/a", "not needed")
|
|
67
|
+
LIST_NONE = ("无", "-", "—", "none")
|
|
68
|
+
|
|
69
|
+
PLACEHOLDERS = ("【待补充】", "[TO FILL IN]")
|
|
70
|
+
EMPTY_REVISIONS = ("暂无。", "None yet.")
|
|
71
|
+
|
|
72
|
+
# 执行 Case 里 SOP 自己加的小节(Case 的标准小节见 case_structure)。
|
|
73
|
+
CASE_SECTIONS = {
|
|
74
|
+
"checklist": ("SOP 执行清单", "SOP checklist"),
|
|
75
|
+
"intermediate": ("中间产物", "Intermediate artifacts"),
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# 写进 Case 与 SOP 文件的文字。{…} 是 str.format 的字段。
|
|
79
|
+
TEXTS = {
|
|
80
|
+
"revision_line": ("{date} v{version}:{note}", "{date} v{version}: {note}"),
|
|
81
|
+
"note_activated": ("启用(内容与执行约定已与用户确认)", "Activated (content and run conventions confirmed with the user)"),
|
|
82
|
+
"note_retired": ("退役", "Retired"),
|
|
83
|
+
"note_restored": ("恢复", "Restored"),
|
|
84
|
+
"from_case_description": ("{p}(从 {case} 提炼)", "{p} (distilled from {case})"),
|
|
85
|
+
"from_case_step": ("{index}. {text}\n - 说明:{p}整理这一步(来自 {case} 的工作记录,需改写成可重复执行的动作)",
|
|
86
|
+
"{index}. {text}\n - Note: {p} tidy this step (from the work log of {case}; rewrite it as a repeatable action)"),
|
|
87
|
+
"case_created_marker": ("Case 已创建", "Case created"),
|
|
88
|
+
# 执行清单
|
|
89
|
+
"cl_source": ("来源:`{id}@{version}`({note})", "Source: `{id}@{version}` ({note})"),
|
|
90
|
+
"snapshot_note": ("快照 {path}", "Snapshot {path}"),
|
|
91
|
+
"cl_conventions": ("执行约定(开始时从 SOP 与项目习惯固化,执行中不随 SOP 改动):",
|
|
92
|
+
"Run conventions (frozen from the SOP and the project habits at the start; they do not follow later SOP edits):"),
|
|
93
|
+
"cl_on_error": ("- 出错时:", "- On error: "),
|
|
94
|
+
"cl_retries": ("(最多重试 {n} 次)", " (at most {n} retries)"),
|
|
95
|
+
"cl_on_missing": ("- 缺少工具时:", "- On a missing tool: "),
|
|
96
|
+
"cl_artifacts_label": ("- 中间产物:", "- Intermediate artifacts: "),
|
|
97
|
+
"cl_artifacts": ("放在 {place};完成后:{keep}", "kept in {place}; afterwards: {keep}"),
|
|
98
|
+
"cl_data_dir": ("本 Case 的 data/ 目录", "this Case's data/ directory"),
|
|
99
|
+
"cl_resume": ("- 失败后重新执行:", "- Resuming after a failure: "),
|
|
100
|
+
"cl_costly": ("- 花钱或耗时的步骤:", "- Costly or slow steps: "),
|
|
101
|
+
"cl_private": ("- 私有目录:本次会用到 {dirs};读取前须经用户确认(用 sop confirm-read 记录),内容默认不进入 git;本 Case 已标为 private",
|
|
102
|
+
"- Private directories: this run will use {dirs}; the user must confirm before each read (record it with sop confirm-read), the content stays out of git by default; this Case is marked private"),
|
|
103
|
+
"cl_params": ("参数:", "Parameters:"),
|
|
104
|
+
"cl_steps": ("步骤:", "Steps:"),
|
|
105
|
+
"cl_tools": ("(工具:{tools})", " (tools: {tools})"),
|
|
106
|
+
"cl_event_param": ("触发事件:{event}({id}:{title})", "Trigger event: {event} ({id}: {title})"),
|
|
107
|
+
"event_note": ("由 {event} 触发({id})", "Triggered by {event} ({id})"),
|
|
108
|
+
"source_event": (";{note}", "; {note}"),
|
|
109
|
+
"next_first": ("步骤 1:{title}", "Step 1: {title}"),
|
|
110
|
+
"progress0": ("SOP 进度 0/{total}", "SOP progress 0/{total}"),
|
|
111
|
+
"empty_intermediates": ("暂无。", "None yet."),
|
|
112
|
+
"log_started": ("{stamp} 按 {id}@{version} 开始执行{event};执行前检查:就绪 {ok}、缺失 {missing}、待核对 {unknown}",
|
|
113
|
+
"{stamp} Started running {id}@{version}{event}; preflight: {ok} ready, {missing} missing, {unknown} to verify"),
|
|
114
|
+
"log_started_event": (",{note}", ", {note}"),
|
|
115
|
+
# 逐步记录
|
|
116
|
+
"state_done": ("完成", "done"),
|
|
117
|
+
"state_skip": ("跳过", "skipped"),
|
|
118
|
+
"state_fail": ("失败", "failed"),
|
|
119
|
+
"state_pending": ("重置为待办", "reset to to-do"),
|
|
120
|
+
"log_step": ("{stamp} SOP 步骤 {n}「{title}」{label}{note}", "{stamp} SOP step {n} \"{title}\" {label}{note}"),
|
|
121
|
+
"note_join": (":{note}", ": {note}"),
|
|
122
|
+
"artifact_line": ("步骤 {n}:`{location}`", "Step {n}: `{location}`"),
|
|
123
|
+
"artifact_what": ("({what})", " ({what})"),
|
|
124
|
+
"next_failed": ("处理步骤 {n} 的失败(约定:{habit})", "Handle the failure of step {n} (convention: {habit})"),
|
|
125
|
+
"next_step": ("步骤 {n}:{title}", "Step {n}: {title}"),
|
|
126
|
+
"next_done": ("核对完成标准(sop.py status)后关闭 Case 或登记交付物",
|
|
127
|
+
"Check the completion criteria (sop.py status), then close the Case or register deliverables"),
|
|
128
|
+
"state_progress": ("SOP 进度 {done}/{total}", "SOP progress {done}/{total}"),
|
|
129
|
+
"state_failed_suffix": (",步骤 {n} 失败", ", step {n} failed"),
|
|
130
|
+
"log_confirm_read": ("{stamp} 用户确认读取私有路径:{paths}{note}", "{stamp} The user confirmed reading private paths: {paths}{note}"),
|
|
131
|
+
"list_join": ("、", ", "),
|
|
132
|
+
"confirm_note": (";{note}", "; {note}"),
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
_TOOLS_SPLIT = re.compile(r"(工具|\s\(tools:")
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def text(key: str, language: str, /, **fields: object) -> str:
|
|
139
|
+
return TEXTS[key][LANGUAGES.index(language)].format(**fields)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def name(table: dict, key: str, language: str) -> str:
|
|
143
|
+
return table[key][LANGUAGES.index(language)]
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _lookup(table: dict) -> dict[str, str]:
|
|
147
|
+
return {variant.lower(): key for key, variants in table.items() for variant in variants}
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
_SECTION_LOOKUP = _lookup(SECTIONS)
|
|
151
|
+
_STEP_LOOKUP = _lookup(STEP_KEYS)
|
|
152
|
+
_KIND_LOOKUP = {**_lookup(REQ_KINDS), **KIND_ALIASES}
|
|
153
|
+
_REQ_LOOKUP = _lookup(REQ_KEYS)
|
|
154
|
+
_MISSING_LOOKUP = {**_lookup(MISSING), **{alias.lower(): key for alias, key in MISSING_ALIASES.items()}}
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def section_key(heading: str) -> str | None:
|
|
158
|
+
return _SECTION_LOOKUP.get(heading.strip().lower())
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def step_key(label: str) -> str | None:
|
|
162
|
+
return _STEP_LOOKUP.get(label.strip().lower())
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def kind_key(label: str) -> str | None:
|
|
166
|
+
return _KIND_LOOKUP.get(label.strip().lower())
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def req_key(label: str) -> str | None:
|
|
170
|
+
return _REQ_LOOKUP.get(label.strip().lower())
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def missing_value(label: str) -> str | None:
|
|
174
|
+
return _MISSING_LOOKUP.get(label.strip().lower())
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def section_text(sections: dict[str, str], key: str) -> str:
|
|
178
|
+
"""The body of a logical section, whichever language the file spells its heading in."""
|
|
179
|
+
for variant in SECTIONS[key]:
|
|
180
|
+
if variant in sections:
|
|
181
|
+
return sections[variant]
|
|
182
|
+
for heading, body in sections.items():
|
|
183
|
+
if section_key(heading) == key:
|
|
184
|
+
return body
|
|
185
|
+
return ""
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def detect(body: str, default: str = "zh") -> str:
|
|
189
|
+
"""The language this SOP body is written in, judged by its section headings."""
|
|
190
|
+
counts = {language: 0 for language in LANGUAGES}
|
|
191
|
+
for line in body.splitlines():
|
|
192
|
+
match = re.match(r"^##\s+(.+?)\s*$", line)
|
|
193
|
+
if match:
|
|
194
|
+
for index, language in enumerate(LANGUAGES):
|
|
195
|
+
counts[language] += any(variants[index] == match.group(1) for variants in SECTIONS.values())
|
|
196
|
+
if counts["zh"] == counts["en"]:
|
|
197
|
+
return default
|
|
198
|
+
return "zh" if counts["zh"] > counts["en"] else "en"
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def detect_case(body: str, default: str) -> str:
|
|
202
|
+
"""The language of a SOP run Case, judged by its checklist heading."""
|
|
203
|
+
for index, language in enumerate(LANGUAGES):
|
|
204
|
+
if f"## {CASE_SECTIONS['checklist'][index]}\n" in body or body.rstrip().endswith(f"## {CASE_SECTIONS['checklist'][index]}"):
|
|
205
|
+
return language
|
|
206
|
+
return default
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def checklist_headings() -> tuple[str, ...]:
|
|
210
|
+
return CASE_SECTIONS["checklist"]
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def prefixes(key: str) -> tuple[str, ...]:
|
|
214
|
+
return TEXTS[key]
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def strip_tools(step_text: str) -> str:
|
|
218
|
+
return _TOOLS_SPLIT.split(step_text)[0]
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def is_placeholder_free(value: str) -> bool:
|
|
222
|
+
return not any(marker in value for marker in PLACEHOLDERS)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def count_placeholders(value: str) -> int:
|
|
226
|
+
return sum(value.count(marker) for marker in PLACEHOLDERS)
|
|
@@ -1,19 +1,5 @@
|
|
|
1
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
|
-
"""
|
|
2
|
+
"""wf:人用的文件查找入口,基于交付物台账(runtime/assets.py),无需 Agent。"""
|
|
17
3
|
from __future__ import annotations
|
|
18
4
|
|
|
19
5
|
import json
|
|
@@ -28,10 +14,27 @@ from pathlib import Path
|
|
|
28
14
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
29
15
|
import assets # noqa: E402
|
|
30
16
|
from assets import AssetError, Project # noqa: E402
|
|
17
|
+
from i18n_orchestrator import _, N_ # noqa: E402
|
|
18
|
+
|
|
19
|
+
USAGE = N_("""wf:人用的文件查找入口,基于交付物台账(runtime/assets.py),无需 Agent。
|
|
20
|
+
|
|
21
|
+
wf 交互模式
|
|
22
|
+
wf 笔记 9月1号 直接查(日期、同义词自动识别)
|
|
23
|
+
wf -o 笔记 9月1号 查到后直接打开最新一个
|
|
24
|
+
wf recent 最近改动的交付物
|
|
25
|
+
wf cases [词] Case 列表(进行中在前)
|
|
26
|
+
wf refs <路径|序号> [层数] 谁引用它 / 它引用谁
|
|
27
|
+
wf o|f|c|p|v <序号> 打开 / 在文件管理器中显示 / 复制路径 / 打印路径 / 列出版本(序号指上一次结果)
|
|
28
|
+
wf check 台账巡检摘要
|
|
29
|
+
wf rebuild 强制重建台账
|
|
30
|
+
|
|
31
|
+
--project <目录> 与 --output json 可写在任意位置;缺省从当前目录向上查找 Project Profile。
|
|
32
|
+
打开、显示与复制只在 macOS 上调用系统命令,其他平台打印绝对路径。
|
|
33
|
+
""")
|
|
31
34
|
|
|
32
35
|
ACTIONS = {"o": "open", "f": "reveal", "c": "copy", "v": "versions", "r": "refs", "p": "path"}
|
|
33
|
-
STATUS_LABEL = {"active": "进行中", "waiting": "等待", "done": "已完成", "captured": "待澄清",
|
|
34
|
-
"suspended": "挂起", "abandoned": "放弃"}
|
|
36
|
+
STATUS_LABEL = {"active": N_("进行中"), "waiting": N_("等待"), "done": N_("已完成"), "captured": N_("待澄清"),
|
|
37
|
+
"suspended": N_("挂起"), "abandoned": N_("放弃")}
|
|
35
38
|
|
|
36
39
|
|
|
37
40
|
# ---------------------------------------------------------------- display
|
|
@@ -90,7 +93,7 @@ def status_text(item: dict) -> str:
|
|
|
90
93
|
status = item.get("status", "")
|
|
91
94
|
if not status:
|
|
92
95
|
return "project" if item.get("container_kind") == "project" else "-"
|
|
93
|
-
label = STATUS_LABEL.get(status, status)
|
|
96
|
+
label = _(STATUS_LABEL.get(status, status))
|
|
94
97
|
return S.green(label) if status == "active" else S.yellow(label) if status == "waiting" else S.dim(label)
|
|
95
98
|
|
|
96
99
|
|
|
@@ -109,8 +112,8 @@ def render_groups(groups: list[dict]) -> None:
|
|
|
109
112
|
print(S.dim(f" {clip(item['path'], width - 6)}"))
|
|
110
113
|
if item.get("versions"):
|
|
111
114
|
names = " · ".join(version["name"] for version in item["versions"][:4])
|
|
112
|
-
more =
|
|
113
|
-
print(S.dim(
|
|
115
|
+
more = _(" 等 {0} 个").format(len(item['versions'])) if len(item["versions"]) > 4 else ""
|
|
116
|
+
print(S.dim(_(" └ 同系列另有 {0} 个:{1}{2}").format(len(item['versions']), clip(names, width - 30), more)))
|
|
114
117
|
|
|
115
118
|
|
|
116
119
|
# ---------------------------------------------------------------- last results
|
|
@@ -141,7 +144,7 @@ def resolve_target(token: str, last: list[dict]) -> dict | None:
|
|
|
141
144
|
index = int(token) - 1
|
|
142
145
|
if 0 <= index < len(last):
|
|
143
146
|
return last[index]
|
|
144
|
-
print(S.red(
|
|
147
|
+
print(S.red(_("没有第 {0} 项(上一次结果共 {1} 项)").format(token, len(last))))
|
|
145
148
|
return None
|
|
146
149
|
return {"path": token, "name": Path(token).name, "title": "", "modified": "", "versions": []}
|
|
147
150
|
|
|
@@ -154,13 +157,13 @@ def do_action(action: str, relative: str, project: Project, platform: str | None
|
|
|
154
157
|
mac = (platform or sys.platform) == "darwin"
|
|
155
158
|
if action == "open" and mac and shutil.which("open"):
|
|
156
159
|
subprocess.run(["open", target], check=False)
|
|
157
|
-
print(S.green("已打开 ") + relative)
|
|
160
|
+
print(S.green(_("已打开 ")) + relative)
|
|
158
161
|
elif action == "reveal" and mac and shutil.which("open"):
|
|
159
162
|
subprocess.run(["open", "-R", target], check=False)
|
|
160
|
-
print(S.green("已在 Finder 中显示 ") + relative)
|
|
163
|
+
print(S.green(_("已在 Finder 中显示 ")) + relative)
|
|
161
164
|
elif action == "copy" and mac and shutil.which("pbcopy"):
|
|
162
165
|
subprocess.run(["pbcopy"], input=target.encode("utf-8"), check=False)
|
|
163
|
-
print(S.green("已复制绝对路径 ") + target)
|
|
166
|
+
print(S.green(_("已复制绝对路径 ")) + target)
|
|
164
167
|
else:
|
|
165
168
|
print(target)
|
|
166
169
|
return "printed"
|
|
@@ -169,7 +172,7 @@ def do_action(action: str, relative: str, project: Project, platform: str | None
|
|
|
169
172
|
|
|
170
173
|
def show_versions(group: dict) -> None:
|
|
171
174
|
print(S.bold(group.get("title") or group.get("name", "")))
|
|
172
|
-
print(f" {(group.get('modified') or '')[:16].replace('T', ' ')} {group['path']} {S.green('最新')}")
|
|
175
|
+
print(f" {(group.get('modified') or '')[:16].replace('T', ' ')} {group['path']} {S.green(_('最新'))}")
|
|
173
176
|
for version in group.get("versions", []):
|
|
174
177
|
print(f" {(version.get('modified') or '')[:16].replace('T', ' ')} {version['path']} "
|
|
175
178
|
f"{S.dim(version.get('git', ''))}")
|
|
@@ -179,38 +182,38 @@ def show_refs(project: Project, path: str, depth: int = 1) -> dict:
|
|
|
179
182
|
result = assets.refs(project, path, depth=depth)
|
|
180
183
|
print(S.bold(result["path"]))
|
|
181
184
|
if result["inbound"]:
|
|
182
|
-
print(S.cyan(" 被引用:"))
|
|
185
|
+
print(S.cyan(_(" 被引用:")))
|
|
183
186
|
for item in result["inbound"][:25]:
|
|
184
|
-
via = "" if item["via"] == "file" else S.dim("(目录级)")
|
|
187
|
+
via = "" if item["via"] == "file" else S.dim(_("(目录级)"))
|
|
185
188
|
print(f" ← {item['src']}{via}")
|
|
186
189
|
else:
|
|
187
|
-
print(S.dim(" 没有被任何 Case 或扫描根里的文件引用"))
|
|
190
|
+
print(S.dim(_(" 没有被任何 Case 或扫描根里的文件引用")))
|
|
188
191
|
if result["outbound"]:
|
|
189
|
-
print(S.cyan(" 引用了:"))
|
|
192
|
+
print(S.cyan(_(" 引用了:")))
|
|
190
193
|
for item in result["outbound"][:25]:
|
|
191
194
|
print(f" → {item['dst']}")
|
|
192
195
|
if result["affected_containers"]:
|
|
193
|
-
print(S.yellow(" 改动会波及:") + "、".join(result["affected_containers"][:8]))
|
|
196
|
+
print(S.yellow(_(" 改动会波及:")) + _("、").join(result["affected_containers"][:8]))
|
|
194
197
|
return result
|
|
195
198
|
|
|
196
199
|
|
|
197
200
|
def do_search(project: Project, terms: list[str], *, limit: int = 10, **filters: str) -> list[dict]:
|
|
198
201
|
if assets.status(project)["status"] != "fresh":
|
|
199
|
-
print(S.dim("(索引更新中…)"))
|
|
202
|
+
print(S.dim(_("(索引更新中…)")))
|
|
200
203
|
result = assets.find(project, terms, limit=limit, **filters)
|
|
201
204
|
query = result["query"]
|
|
202
|
-
description = " ".join(query["terms"]) or "全部"
|
|
205
|
+
description = " ".join(query["terms"]) or _("全部")
|
|
203
206
|
if query["dates"]:
|
|
204
|
-
description += " · 日期 " + ",".join(date.replace("*-", "") for date in query["dates"])
|
|
205
|
-
print(S.dim(
|
|
207
|
+
description += _(" · 日期 ") + ",".join(date.replace("*-", "") for date in query["dates"])
|
|
208
|
+
print(S.dim(_("查找:{0}").format(description)))
|
|
206
209
|
if not result["results"]:
|
|
207
|
-
print(S.yellow("没有找到。") + S.dim("试试更短的词、去掉日期,或 `wf cases` 先看 Case 列表。"))
|
|
210
|
+
print(S.yellow(_("没有找到。")) + S.dim(_("试试更短的词、去掉日期,或 `wf cases` 先看 Case 列表。")))
|
|
208
211
|
return []
|
|
209
212
|
render_groups(result["results"])
|
|
210
|
-
tail =
|
|
213
|
+
tail = _("共 {0} 组").format(result['total'])
|
|
211
214
|
if result["total"] > result["returned"]:
|
|
212
|
-
tail +=
|
|
213
|
-
print(S.dim(tail + "。序号可接 o 打开 / f 显示 / c 复制 / v 版本 / r 引用,例:o1"))
|
|
215
|
+
tail += _(",只显示前 {0} 组(加关键词收窄)").format(result['returned'])
|
|
216
|
+
print(S.dim(tail + _("。序号可接 o 打开 / f 显示 / c 复制 / v 版本 / r 引用,例:o1")))
|
|
214
217
|
save_last(project, result["results"])
|
|
215
218
|
return result["results"]
|
|
216
219
|
|
|
@@ -219,11 +222,11 @@ def do_cases(project: Project, term: str | None, limit: int = 30) -> dict:
|
|
|
219
222
|
result = assets.cases(project, term, limit=limit)
|
|
220
223
|
width = term_width()
|
|
221
224
|
for item in result["results"]:
|
|
222
|
-
status = STATUS_LABEL.get(item["status"], item["status"] or "-")
|
|
225
|
+
status = _(STATUS_LABEL.get(item["status"], item["status"] or "-"))
|
|
223
226
|
mark = S.green(f"{status:<4}") if item["status"] == "active" else S.dim(f"{status:<4}")
|
|
224
227
|
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(
|
|
228
|
+
f"{S.dim(str(item['files']) + _(' 个文件'))}")
|
|
229
|
+
print(S.dim(_("共 {0} 个 Case").format(result['total']) + (_(";用 `wf <Case 关键词>` 查其文件") if result["total"] else "")))
|
|
227
230
|
return result
|
|
228
231
|
|
|
229
232
|
|
|
@@ -238,26 +241,28 @@ def do_check(project: Project) -> dict:
|
|
|
238
241
|
report = assets.check(project)
|
|
239
242
|
totals, tiers, counts = report["totals"], report["claim_tiers"], report["broken_counts"]
|
|
240
243
|
git = totals["git"]
|
|
241
|
-
print(S.bold("台账巡检"))
|
|
242
|
-
print(
|
|
243
|
-
|
|
244
|
-
print(
|
|
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)}")
|
|
244
|
+
print(S.bold(_("台账巡检")))
|
|
245
|
+
print(_(" 文件 {0} · 引用 {1} · Git 已入库 {2} / 不入库 {3} / 新建未提交 {4} / 非 Git {5}").format(totals['files'], totals['edges'], git.get('tracked', 0), git.get('ignored', 0), git.get('untracked', 0), git.get('none', 0)))
|
|
246
|
+
print(_(" 交付物:文件级被引用 {0} · 仅目录覆盖 {1} · 无人认领 {2}").format(tiers.get('file', 0), tiers.get('directory', 0), S.yellow(str(tiers.get('none', 0)))))
|
|
247
|
+
print(_(" 断链:真断链 {0} · 说明文字 {1} · 主题链接 {2}").format(S.yellow(str(counts.get('real-broken', 0))), counts.get('prose-not-path', 0), counts.get('wikilink-topic', 0)))
|
|
248
248
|
for container, paths in list(report["unclaimed"].items())[:5]:
|
|
249
|
-
print(S.dim(
|
|
249
|
+
print(S.dim(_(" 无人认领 · {0}: {1}").format(container, paths[0])))
|
|
250
250
|
return report
|
|
251
251
|
|
|
252
252
|
|
|
253
253
|
# ---------------------------------------------------------------- entry
|
|
254
254
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
{
|
|
260
|
-
|
|
255
|
+
def help_text() -> str:
|
|
256
|
+
cyan = S.cyan
|
|
257
|
+
return (S.bold(_("用法")) + "\n"
|
|
258
|
+
+ _(" 直接输入关键词查找;可带日期(9月1号 / 0901 / 2026-09-01),同义词按项目的 asset-synonyms.json 展开。") + "\n"
|
|
259
|
+
+ _(" {0} 打开第 1 项 {1} 在文件管理器中显示 {2} 复制绝对路径 {3} 打印路径").format(
|
|
260
|
+
cyan("o1"), cyan("f1"), cyan("c1"), cyan("p1")) + "\n"
|
|
261
|
+
+ _(" {0} 查看全部版本 {1} 谁引用它(r1 2 为两层) {2} 直接打开").format(cyan("v1"), cyan("r1"), cyan("1")) + "\n"
|
|
262
|
+
+ _(" {0} 最近改动 {1} Case 列表 {2} 巡检 {3} 退出").format(
|
|
263
|
+
cyan("recent"), cyan("cases [词]"), cyan("check"), cyan("q")) + "\n"
|
|
264
|
+
+ _(" 过滤:{0} {1}(也可 draft/source/data) {2}(限定 Case/目录)").format(
|
|
265
|
+
cyan("ext:xlsx"), cyan("kind:deliverable"), cyan("in:片段")))
|
|
261
266
|
|
|
262
267
|
|
|
263
268
|
def parse_filters(tokens: list[str]) -> tuple[list[str], dict]:
|
|
@@ -277,7 +282,7 @@ def split_globals(argv: list[str]) -> tuple[list[str], Path | None, str]:
|
|
|
277
282
|
index = 0
|
|
278
283
|
while index < len(argv):
|
|
279
284
|
token = argv[index]
|
|
280
|
-
name,
|
|
285
|
+
name, _sep, inline = token.partition("=")
|
|
281
286
|
if name in ("--project", "--output"):
|
|
282
287
|
if inline:
|
|
283
288
|
value = inline
|
|
@@ -285,13 +290,13 @@ def split_globals(argv: list[str]) -> tuple[list[str], Path | None, str]:
|
|
|
285
290
|
index += 1
|
|
286
291
|
value = argv[index]
|
|
287
292
|
else:
|
|
288
|
-
raise AssetError(
|
|
293
|
+
raise AssetError(_("{0} 需要一个值").format(name))
|
|
289
294
|
if name == "--project":
|
|
290
295
|
project = Path(value)
|
|
291
296
|
elif value in ("text", "json"):
|
|
292
297
|
output = value
|
|
293
298
|
else:
|
|
294
|
-
raise AssetError("--output 只能是 text 或 json")
|
|
299
|
+
raise AssetError(_("--output 只能是 text 或 json"))
|
|
295
300
|
else:
|
|
296
301
|
rest.append(token)
|
|
297
302
|
index += 1
|
|
@@ -305,16 +310,15 @@ def interactive(project: Project) -> int:
|
|
|
305
310
|
pass
|
|
306
311
|
current = assets.status(project)
|
|
307
312
|
if current["status"] != "fresh":
|
|
308
|
-
print(S.dim("(索引更新中…)"))
|
|
313
|
+
print(S.dim(_("(索引更新中…)")))
|
|
309
314
|
assets.rebuild(project)
|
|
310
315
|
current = assets.status(project)
|
|
311
|
-
print(S.bold(S.cyan("交付物台账")) + S.dim(
|
|
312
|
-
|
|
313
|
-
print(S.dim("输入关键词查找;? 帮助;q 退出"))
|
|
316
|
+
print(S.bold(S.cyan(_("交付物台账"))) + S.dim(_(" · {0} 个文件 · 索引 {1}").format(current.get('file_count', '?'), current.get('built_at', '')[11:16])))
|
|
317
|
+
print(S.dim(_("输入关键词查找;? 帮助;q 退出")))
|
|
314
318
|
groups: list[dict] = []
|
|
315
319
|
while True:
|
|
316
320
|
try:
|
|
317
|
-
line = input(S.bold(S.cyan("\n查找 › "))).strip()
|
|
321
|
+
line = input(S.bold(S.cyan(_("\n查找 › ")))).strip()
|
|
318
322
|
except (EOFError, KeyboardInterrupt):
|
|
319
323
|
print()
|
|
320
324
|
return 0
|
|
@@ -323,7 +327,7 @@ def interactive(project: Project) -> int:
|
|
|
323
327
|
if line in ("q", "quit", "exit", "退出"):
|
|
324
328
|
return 0
|
|
325
329
|
if line in ("?", "help", "帮助"):
|
|
326
|
-
print(
|
|
330
|
+
print(help_text())
|
|
327
331
|
continue
|
|
328
332
|
try:
|
|
329
333
|
match = re.match(r"^([ofcvrp])\s*(\d+)(?:\s+(\d+))?$", line) or re.match(r"^()(\d+)()$", line)
|
|
@@ -351,7 +355,7 @@ def interactive(project: Project) -> int:
|
|
|
351
355
|
terms, filters = parse_filters(line.split())
|
|
352
356
|
groups = do_search(project, terms, **filters)
|
|
353
357
|
except AssetError as exc:
|
|
354
|
-
print(S.red(
|
|
358
|
+
print(S.red(_("出错:{0}").format(exc)))
|
|
355
359
|
|
|
356
360
|
|
|
357
361
|
def command(project: Project, argv: list[str], output: str) -> tuple[int, dict | None]:
|
|
@@ -387,7 +391,7 @@ def command(project: Project, argv: list[str], output: str) -> tuple[int, dict |
|
|
|
387
391
|
return 0, None
|
|
388
392
|
if head == "rebuild":
|
|
389
393
|
result = assets.rebuild(project, force=True)
|
|
390
|
-
print(
|
|
394
|
+
print(_("{0}:{1} 个文件").format(result['action'], result['file_count']))
|
|
391
395
|
return 0, None
|
|
392
396
|
if (head in ACTIONS and len(argv) > 1 and (argv[1].isdigit() or head == "r")) or (head == "refs" and len(argv) > 1):
|
|
393
397
|
target = resolve_target(argv[1], load_last(project))
|
|
@@ -415,23 +419,23 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
415
419
|
try:
|
|
416
420
|
argv, start, output = split_globals(argv)
|
|
417
421
|
if argv and argv[0] in ("-h", "--help", "help"):
|
|
418
|
-
print(
|
|
422
|
+
print(_(USAGE))
|
|
419
423
|
return 0
|
|
420
424
|
project = Project.discover(start, explicit=start is not None)
|
|
421
425
|
if not argv:
|
|
422
426
|
if output == "json":
|
|
423
|
-
raise AssetError("交互模式不支持 --output json")
|
|
427
|
+
raise AssetError(_("交互模式不支持 --output json"))
|
|
424
428
|
return interactive(project)
|
|
425
429
|
code, result = command(project, argv, output)
|
|
426
430
|
if output == "json":
|
|
427
431
|
print(json.dumps(result if result is not None else {"ok": False, "code": "not-found",
|
|
428
|
-
"error": "没有这一项"}, ensure_ascii=False, indent=2))
|
|
432
|
+
"error": _("没有这一项")}, ensure_ascii=False, indent=2))
|
|
429
433
|
return code
|
|
430
434
|
except AssetError as exc:
|
|
431
435
|
if output == "json":
|
|
432
436
|
print(json.dumps({"ok": False, "code": exc.code, "error": str(exc)}, ensure_ascii=False))
|
|
433
437
|
else:
|
|
434
|
-
print(S.red(
|
|
438
|
+
print(S.red(_("出错:{0}").format(exc)), file=sys.stderr)
|
|
435
439
|
return 2
|
|
436
440
|
|
|
437
441
|
|
|
@@ -116,6 +116,28 @@ Case 自有输入放 `refs/`,交付物放 `docs/`(草稿 `docs/drafts/`)
|
|
|
116
116
|
|
|
117
117
|
Project Profile 声明了 `memory` 时,恢复状态后读取记忆索引:`provider` 为 `project-orchestrator` 时运行随包的 `runtime/memory.py index --project <项目>`,为 `project` 时运行其 `index_command`。索引每条一行,说明何时应当想起该条记忆;只在当前任务命中时读取条目正文。依据某条记忆行动前,核对其中提到的文件、命令与外部对象仍然存在;标注为可能过期的条目先核实再使用。
|
|
118
118
|
|
|
119
|
+
### 3.1 流程(SOP)
|
|
120
|
+
|
|
121
|
+
用户可以把常用流程固化成 SOP(Standard Operating Procedure,标准作业流程)。契约与命令见 [SOP 契约](../../references/sop.md),与用户一起建立见 [建立 SOP](../../references/sop-interview.md)。
|
|
122
|
+
|
|
123
|
+
- **读索引**:存在 `sops/` 目录或用户级 SOP 时,恢复状态后运行随包的 `runtime/sop.py index --project <项目>`(一行一个已启用的 SOP,写明触发与不触发条件)。没有 SOP 时静默跳过。
|
|
124
|
+
- **判断触发**:用户明确指定、事件(新建或关闭 Case 时,`case.py` 与项目入口的结果带 `sop_events`;自有 Case 工具用 `sop.py events <事件> --case <Case>` 查询)、周期到期(`sop.py due`)、意图对照触发与不触发条件,按这个优先级。命中后默认先问用户一次是否按它执行;多个命中合并成一次编号提问;到期只是提醒。只读被命中的那一个 SOP 的正文。
|
|
125
|
+
- **执行**:`sop.py preflight` 检查所需程序与环境,缺失项一次性列给用户并等待处理,**不自行安装软件**;`sop.py run`(预览 → 用户确认 → `--apply`)建立执行 Case;逐步执行并用 `sop.py step --apply` 记录结果与中间产物;出错、缺工具、花钱或耗时步骤按 Case 里固化的“执行约定”处理;收尾用 `sop.py status` 与完成标准核对。
|
|
126
|
+
- **范围与私有目录**:会读取多个事项的流程(复盘、汇总)先让用户指定范围,不默认纳入全部事项;SOP 里用到私有目录(见 3.2)时,`sop.py run` 自动把执行 Case 标为 `private`,读取前按 3.2 取得用户确认。
|
|
127
|
+
- **授权不变**:SOP 不放宽任何授权,写入、外发、删除、push、安装、花钱类步骤仍须用户当次同意;与用户当次请求冲突时以请求为准并说明偏离。
|
|
128
|
+
- **创建与修改**:SOP 里的工具、出错处理、中间产物位置等是用户的习惯,只提问、整理、校验,不替用户决定;`decided` 只在用户确认后用 `sop.py decide` 写入;修改后用 `sop.py revise` 登记版本。
|
|
129
|
+
- 一次执行的状态只写在 Case 里,不在 SOP 目录里另记;SOP 不是 Opinion(表达偏好)也不是记忆(事实),不要写错位置。
|
|
130
|
+
|
|
131
|
+
### 3.2 私有目录
|
|
132
|
+
|
|
133
|
+
Profile 的 `privacy.forbidden_default_reads` 声明私有路径(没有声明但项目根有 `private/` 时,按约定它就是私有目录)。三条约定,其余照常:
|
|
134
|
+
|
|
135
|
+
- **读取前须经用户确认**:默认不读、不列目录、不搜索其中的内容。需要读时,先向用户列出要读的路径和用途,得到确认再读;确认后用 `sop.py confirm-read <Case> <路径…> --apply` 记入该 Case 的决策记录(同时把 Case 标为 `private`)。确认只覆盖当次列出的路径。
|
|
136
|
+
- **内容默认不进入 git**:私有目录里有 `.gitignore`(`*` 加 `!.gitignore`)。`sop.py private` 查看状态,`--apply` 补上缺失的 `.gitignore`;`health` 会提示未被忽略的私有目录和已被跟踪的文件。不要替用户 `git add`、提交或推送私有目录里的内容。
|
|
137
|
+
- **用到私有目录的 Case 标为 `sensitivity: private`**:`case.py update --add-deliverable/--add-evidence` 登记私有路径时自动改,也可 `--sensitivity private`;SOP 里引用了私有路径时 `sop.py run` 自动标。云同步盘同步它没有问题。
|
|
138
|
+
|
|
139
|
+
这是协议级约束:工具层挡不住 Agent 直接读文件,所以要靠这里的规则;`find` 照常索引 `private` 的 Case(索引只是本机缓存里的索引,不做过滤)。
|
|
140
|
+
|
|
119
141
|
## 4. 路由能力
|
|
120
142
|
|
|
121
143
|
根据三项输入构建执行路由:
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
schema_version: "1.0"
|
|
3
|
+
id: "{{id}}"
|
|
4
|
+
title: "{{title}}"
|
|
5
|
+
status: "draft"
|
|
6
|
+
version: "1.0.0"
|
|
7
|
+
description: "[TO FILL IN] One sentence: what this procedure does and when to think of it"
|
|
8
|
+
trigger: "[TO FILL IN] When this procedure should be used"
|
|
9
|
+
exclude: ""
|
|
10
|
+
confirm: "ask"
|
|
11
|
+
creates_case: "always"
|
|
12
|
+
created: "{{date}}"
|
|
13
|
+
aliases: []
|
|
14
|
+
examples_hit: []
|
|
15
|
+
examples_miss: []
|
|
16
|
+
on_events: []
|
|
17
|
+
outputs: []
|
|
18
|
+
sources: []
|
|
19
|
+
decided: []
|
|
20
|
+
---
|
|
21
|
+
# {{title}}
|
|
22
|
+
|
|
23
|
+
## Purpose
|
|
24
|
+
|
|
25
|
+
[TO FILL IN] What this procedure should achieve, in a sentence or two.
|
|
26
|
+
|
|
27
|
+
## Prerequisites
|
|
28
|
+
|
|
29
|
+
One item per line: `- Kind: name | Purpose: … | Install: … | Alternative: name of another item | If missing: Ask the user`.
|
|
30
|
+
Kinds: Command, Module (a Python module), Environment variable, Path, Skill, MCP, Manual check. If missing: Ask the user, Stop, Skip the step, Use the alternative.
|
|
31
|
+
For API keys write only the environment variable name, never the key itself. If no tool is needed at all, write "None".
|
|
32
|
+
|
|
33
|
+
- Command: [TO FILL IN] | Purpose: [TO FILL IN] | Install: [TO FILL IN] | If missing: Ask the user
|
|
34
|
+
|
|
35
|
+
## Steps
|
|
36
|
+
|
|
37
|
+
One number per step, with indented sub-items below it. Sub-items: Tools, Inputs, Intermediates, Final outputs, Checkpoint, On failure, Needs confirmation, Note.
|
|
38
|
+
Every name under "Tools" must be declared in the prerequisites above.
|
|
39
|
+
|
|
40
|
+
1. [TO FILL IN] What the first step does
|
|
41
|
+
- Tools: [TO FILL IN]
|
|
42
|
+
- Inputs: [TO FILL IN]
|
|
43
|
+
- Intermediates: [TO FILL IN]
|
|
44
|
+
- Checkpoint: [TO FILL IN] How to tell the step went right
|
|
45
|
+
- On failure: [TO FILL IN] (if left out, the "run conventions" apply)
|
|
46
|
+
|
|
47
|
+
## Completion criteria
|
|
48
|
+
|
|
49
|
+
- [TO FILL IN] How to tell the whole procedure is done (a condition that can be checked, for example "the output file exists at …")
|
|
50
|
+
|
|
51
|
+
## Exceptions and fallback
|
|
52
|
+
|
|
53
|
+
- [TO FILL IN] Common exceptions and how to handle them; write "None" if there are none.
|
|
54
|
+
|
|
55
|
+
## Revision log
|
|
56
|
+
|
|
57
|
+
None yet.
|