hermes-task-framework 1.0.0__py3-none-any.whl

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.
Files changed (56) hide show
  1. hermes_task_framework-1.0.0.dist-info/METADATA +7 -0
  2. hermes_task_framework-1.0.0.dist-info/RECORD +56 -0
  3. hermes_task_framework-1.0.0.dist-info/WHEEL +5 -0
  4. hermes_task_framework-1.0.0.dist-info/licenses/LICENSE +1 -0
  5. hermes_task_framework-1.0.0.dist-info/top_level.txt +1 -0
  6. task-framework/__init__.py +2 -0
  7. task-framework/skills/task-external-repos-pattern/SKILL.md +165 -0
  8. task-framework/skills/task-framework/CHANGELOG.md +45 -0
  9. task-framework/skills/task-framework/SKILL.md +1365 -0
  10. task-framework/skills/task-framework/docs/TASKS.md +3 -0
  11. task-framework/skills/task-framework/docs/architecture.dot +65 -0
  12. task-framework/skills/task-framework/docs/architecture.svg +188 -0
  13. task-framework/skills/task-framework/docs/product-requirements.md +65 -0
  14. task-framework/skills/task-framework/references/auto-runner.md +32 -0
  15. task-framework/skills/task-framework/references/composites/code-review-session.md +30 -0
  16. task-framework/skills/task-framework/references/composites/research.md +45 -0
  17. task-framework/skills/task-framework/references/composites/software-dev.md +41 -0
  18. task-framework/skills/task-framework/references/document-analysis-workflow.md +95 -0
  19. task-framework/skills/task-framework/references/file-safety-lesson.md +38 -0
  20. task-framework/skills/task-framework/references/generating-output-documents.md +166 -0
  21. task-framework/skills/task-framework/references/operations/code-write.md +30 -0
  22. task-framework/skills/task-framework/references/operations/document-write.md +185 -0
  23. task-framework/skills/task-framework/references/operations/info-search.md +27 -0
  24. task-framework/skills/task-framework/references/operations/web-research.md +57 -0
  25. task-framework/skills/task-framework/references/policy-time-metadata.md +137 -0
  26. task-framework/skills/task-framework/references/research-workflow.md +220 -0
  27. task-framework/skills/task-framework/references/task-format-validation.md +25 -0
  28. task-framework/skills/task-framework/references/task-hash-naming.md +40 -0
  29. task-framework/skills/task-framework/references/task-lifecycle-example.md +122 -0
  30. task-framework/skills/task-framework/references/task-overview-discovery.md +31 -0
  31. task-framework/skills/task-framework/references/task-types/analysis.md +60 -0
  32. task-framework/skills/task-framework/references/task-types/external-audit.md +56 -0
  33. task-framework/skills/task-framework/references/task-types/video-production-pipeline.md +77 -0
  34. task-framework/skills/task-framework/scripts/__init__.py +0 -0
  35. task-framework/skills/task-framework/scripts/__pycache__/manage_task.cpython-312.pyc +0 -0
  36. task-framework/skills/task-framework/scripts/__pycache__/task_ref.cpython-312.pyc +0 -0
  37. task-framework/skills/task-framework/scripts/__pycache__/update-index.cpython-311.pyc +0 -0
  38. task-framework/skills/task-framework/scripts/__pycache__/update-index.cpython-312.pyc +0 -0
  39. task-framework/skills/task-framework/scripts/convert_md_to_pdf.py +236 -0
  40. task-framework/skills/task-framework/scripts/manage_task.py +442 -0
  41. task-framework/skills/task-framework/scripts/task-runner.sh +42 -0
  42. task-framework/skills/task-framework/scripts/task_ref.py +119 -0
  43. task-framework/skills/task-framework/scripts/update-index.py +293 -0
  44. task-framework/skills/task-framework/templates/TASK.md +114 -0
  45. task-framework/skills/task-framework/templates/TASK_MEMORY.md +27 -0
  46. task-framework/skills/task-framework/templates/run.py +187 -0
  47. task-framework/skills/task-lifecycle-edge-cases/SKILL.md +84 -0
  48. task-framework/skills/task-lifecycle-edge-cases/references/task-5d5a1a-recovery-example.md +67 -0
  49. task-framework/skills/task-lifecycle-portability/SKILL.md +128 -0
  50. task-framework/skills/task-lifecycle-portability/references/design-session-20260611.md +32 -0
  51. task-framework/skills/task-lifecycle-portability/references/output-model-design.md +55 -0
  52. task-framework/skills/task-lifecycle-portability/references/pipeline-output-transition.md +41 -0
  53. task-framework/skills/task-lifecycle-portability/references/task-recovery-5d5a1a-example.md +27 -0
  54. task-framework/skills/task-lifecycle-portability/references/task-recovery-procedure.md +63 -0
  55. task-framework/skills/task-timestamp-convention/SKILL.md +72 -0
  56. task-framework/skills/task-tracker/SKILL.md +80 -0
@@ -0,0 +1,42 @@
1
+ #!/bin/bash
2
+ # Task Runner — execute a command with timestamped stdout/stderr logging
3
+ # Usage: task-runner.sh <task-dir> <command...>
4
+ #
5
+ # Creates:
6
+ # <task-dir>/logs/output.YYYYMMDD-HHMMSS.log
7
+ # <task-dir>/logs/error.YYYYMMDD-HHMMSS.log
8
+ # stdout and stderr also go to terminal.
9
+ # Exit code preserved.
10
+
11
+ set -e
12
+
13
+ if [ $# -lt 2 ]; then
14
+ echo "Usage: $0 <task-dir> <command...>" >&2
15
+ exit 1
16
+ fi
17
+
18
+ TASK_DIR="$1"
19
+ shift
20
+
21
+ mkdir -p "$TASK_DIR/logs"
22
+
23
+ TIMESTAMP=$(date '+%Y%m%d-%H%M%S')
24
+ OUTPUT_LOG="$TASK_DIR/logs/output.${TIMESTAMP}.log"
25
+ ERROR_LOG="$TASK_DIR/logs/error.${TIMESTAMP}.log"
26
+
27
+ # Header
28
+ echo "-------- $(date '+%Y-%m-%d %H:%M:%S') --------" > "$OUTPUT_LOG"
29
+ echo "-------- $(date '+%Y-%m-%d %H:%M:%S') --------" > "$ERROR_LOG"
30
+ echo "Command: $*" >> "$OUTPUT_LOG"
31
+ echo "Command: $*" >> "$ERROR_LOG"
32
+ echo "--------" >> "$OUTPUT_LOG"
33
+ echo "--------" >> "$ERROR_LOG"
34
+
35
+ # Run command with split tee
36
+ # stdout → both terminal and output log
37
+ # stderr → both terminal and error log
38
+ "$@" 2> >(tee "$ERROR_LOG" >&2) | tee "$OUTPUT_LOG"
39
+
40
+ EXIT_CODE=$?
41
+ echo "Exit code: $EXIT_CODE" | tee -a "$OUTPUT_LOG" | tee -a "$ERROR_LOG" >/dev/null
42
+ exit $EXIT_CODE
@@ -0,0 +1,119 @@
1
+ """Task reference resolution utilities.
2
+
3
+ Usage:
4
+ from task_ref import resolve_ref, check_cycles, create_task_meta
5
+ """
6
+ import os, glob, json, hashlib, random, datetime, re, sys, subprocess
7
+
8
+ TASKS_ROOT = os.path.expanduser(os.environ.get("HERMES_TASKS_ROOT", "~/studio/hermes/tasks"))
9
+
10
+ def _hash6():
11
+ return hashlib.md5(str(random.random()).encode()).hexdigest()[:6]
12
+
13
+ def resolve_ref(ref_str):
14
+ """Resolve ref:hash/output_name to absolute path."""
15
+ if not ref_str.startswith('ref:'):
16
+ return ref_str
17
+ parts = ref_str[4:].split('/', 1)
18
+ hash_id = parts[0]
19
+ output_name = parts[1] if len(parts) > 1 else None
20
+
21
+ matches = glob.glob(os.path.join(TASKS_ROOT, f'*{hash_id}*'))
22
+ if not matches:
23
+ raise FileNotFoundError(f'Task with hash {hash_id} not found in {TASKS_ROOT}')
24
+
25
+ task_dir = matches[0]
26
+ meta_path = os.path.join(task_dir, '.hermes-task.json')
27
+ if not os.path.exists(meta_path):
28
+ raise FileNotFoundError(f'.hermes-task.json not found in {task_dir}')
29
+
30
+ with open(meta_path) as f:
31
+ meta = json.load(f)
32
+
33
+ if output_name:
34
+ if output_name not in meta.get('outputs', {}):
35
+ raise KeyError(f'Output "{output_name}" not declared in {task_dir}/.hermes-task.json. '
36
+ f'Available: {list(meta.get("outputs", {}).keys())}')
37
+ return os.path.join(task_dir, meta['outputs'][output_name])
38
+
39
+ return task_dir
40
+
41
+ def check_cycles(meta):
42
+ """Walk dependency graph, raise ValueError on cycles."""
43
+ seen = [meta['hash']]
44
+ stack = list(meta.get('dependencies', []))
45
+ while stack:
46
+ h = stack.pop()
47
+ if h in seen:
48
+ raise ValueError(f'Cycle detected: {h} already in path {" → ".join(seen)}')
49
+ seen.append(h)
50
+ matches = glob.glob(os.path.join(TASKS_ROOT, f'*{h}*'))
51
+ if matches:
52
+ dep_meta_path = os.path.join(matches[0], '.hermes-task.json')
53
+ if os.path.exists(dep_meta_path):
54
+ dep_meta = json.load(open(dep_meta_path))
55
+ stack.extend(dep_meta.get('dependencies', []))
56
+ return True
57
+
58
+ def create_task_meta(task_dir, name, dependencies=None, related=None, supersedes=None, board="default"):
59
+ """Create .hermes-task.json in a task directory. Returns the hash.
60
+ Also creates a kanban card on the specified board and stores the card ID."""
61
+ h = _hash6()
62
+ meta = {
63
+ "hash": h,
64
+ "name": name,
65
+ "created_at": datetime.datetime.now().isoformat(),
66
+ "outputs": {},
67
+ "dependencies": dependencies or [],
68
+ "related": related or [],
69
+ "supersedes": supersedes or [],
70
+ "kanban_card_id": "",
71
+ }
72
+
73
+ # Check cycles before writing
74
+ if meta['dependencies']:
75
+ check_cycles(meta)
76
+
77
+ meta_path = os.path.join(task_dir, '.hermes-task.json')
78
+ with open(meta_path, 'w') as f:
79
+ json.dump(meta, f, indent=2, ensure_ascii=False)
80
+
81
+ # Create kanban card
82
+ dirname = os.path.basename(task_dir)
83
+ title = f"{name} [task:{dirname}]"
84
+ env = {**os.environ}
85
+ env.pop("HERMES_KANBAN_BOARD", None) # Avoid env leak override
86
+ try:
87
+ result = subprocess.run(
88
+ ["hermes", "kanban", "boards", "switch", board],
89
+ capture_output=True, text=True, timeout=10, env=env,
90
+ )
91
+ result = subprocess.run(
92
+ ["hermes", "kanban", "create", title, "--assignee", "default"],
93
+ capture_output=True, text=True, timeout=15, env=env,
94
+ )
95
+ if result.returncode == 0:
96
+ # Parse card ID from output: "Created t_xxx (ready, ...)"
97
+ out = result.stdout.strip()
98
+ m = re.search(r'Created (t_[a-f0-9]+)', out)
99
+ if m:
100
+ card_id = m.group(1)
101
+ meta["kanban_card_id"] = card_id
102
+ # Update .hermes-task.json with card id
103
+ with open(meta_path, 'w') as f:
104
+ json.dump(meta, f, indent=2, ensure_ascii=False)
105
+ except Exception as e:
106
+ print(f" ⚠ kanban card creation failed: {e}", file=sys.stderr)
107
+
108
+ return h
109
+
110
+ def set_output(task_dir, name, path):
111
+ """Register a named output in .hermes-task.json."""
112
+ meta_path = os.path.join(task_dir, '.hermes-task.json')
113
+ if not os.path.exists(meta_path):
114
+ raise FileNotFoundError(f'No .hermes-task.json in {task_dir}')
115
+ with open(meta_path) as f:
116
+ meta = json.load(f)
117
+ meta['outputs'][name] = path
118
+ with open(meta_path, 'w') as f:
119
+ json.dump(meta, f, indent=2, ensure_ascii=False)
@@ -0,0 +1,293 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ update-index.py — Update tasks/ root index files.
4
+
5
+ Regenerates both:
6
+ 1. tasks/README.md — summary table (directory façade)
7
+ 2. tasks/TASKS.md — aggregated checklist view (deep overview)
8
+
9
+ Usage:
10
+ python3 scripts/update-index.py # default: ~/studio/hermes/tasks
11
+ python3 scripts/update-index.py --tasks-dir /path # custom path
12
+ """
13
+
14
+ import os, sys, re, glob
15
+
16
+ TASKS_ROOT = os.path.expanduser(os.environ.get("HERMES_TASKS_ROOT", "~/studio/hermes/tasks"))
17
+
18
+ def gather_tasks(tasks_root):
19
+ """Return sorted list of (dirname, ts, name, title, status, desc, checklist, notes, related_tasks, related_tickets, cluster_info)."""
20
+ task_dirs = sorted(glob.glob(os.path.join(tasks_root, "2*/")))
21
+ results = []
22
+ for d in task_dirs:
23
+ dirname = os.path.basename(d.rstrip('/'))
24
+ parts = dirname.split('.', 1)
25
+ ts = parts[0]
26
+ name = parts[1] if len(parts) > 1 else dirname
27
+
28
+ task_md_path = os.path.join(d, "TASK.md")
29
+ title = name
30
+ status = "—"
31
+ desc = ""
32
+ checklist = []
33
+ notes = []
34
+ related_tasks = []
35
+ related_tickets = []
36
+ cluster_info = {"affinity": "", "claimed_by": ""}
37
+
38
+ # Read .hermes-task.json for cluster fields
39
+ meta_path = os.path.join(d, ".hermes-task.json")
40
+ if os.path.exists(meta_path):
41
+ try:
42
+ import json
43
+ with open(meta_path) as f:
44
+ meta = json.load(f)
45
+ cluster_info["affinity"] = meta.get("affinity", "")
46
+ cluster_info["claimed_by"] = meta.get("claimed_by", "") or ""
47
+ except Exception:
48
+ pass
49
+
50
+ if os.path.exists(task_md_path):
51
+ content = open(task_md_path, encoding='utf-8').read()
52
+
53
+ # Title: # Task: <title> or first # heading
54
+ m = re.search(r'^# Task:\s*(.+)$', content, re.MULTILINE)
55
+ if m:
56
+ title = m.group(1).strip()
57
+ else:
58
+ m = re.search(r'^#\s+(.+)$', content, re.MULTILINE)
59
+ if m:
60
+ title = m.group(1).strip()
61
+
62
+ # Status: ## Status or ## 状态
63
+ m = re.search(r'^## (?:Status|状态)\s*\n\s*(.+?)\s*$', content, re.MULTILINE)
64
+ if m:
65
+ status = m.group(1).strip()
66
+
67
+ # Goal / Description: ## Goal or ## 目标 or ## 概述
68
+ m = re.search(r'^## (?:Goal|目标|概述)\s*\n\s*(.+?)\s*$', content, re.MULTILINE)
69
+ if m:
70
+ desc = m.group(1).strip()
71
+
72
+ # Checklist: ## Checklist or ## 步骤 or ## Checklist(步骤)
73
+ in_cl = False
74
+ for line in content.split('\n'):
75
+ if line.startswith('## Checklist') or line.startswith('## 步骤'):
76
+ in_cl = True
77
+ continue
78
+ if in_cl:
79
+ if line.startswith('## '):
80
+ break
81
+ if line.strip().startswith('- ['):
82
+ checklist.append(line.strip())
83
+
84
+ # Notes: ## Notes or ## 备注
85
+ in_nt = False
86
+ for line in content.split('\n'):
87
+ if line.startswith('## Notes') or line.startswith('## 备注'):
88
+ in_nt = True
89
+ continue
90
+ if in_nt and line.startswith('## '):
91
+ break
92
+ if in_nt and line.strip():
93
+ notes.append(line.strip())
94
+
95
+ # Related Tasks: ## Related Tasks table
96
+ in_rt = False
97
+ header_skipped = False
98
+ for line in content.split('\n'):
99
+ if line.startswith('## Related Tasks'):
100
+ in_rt = True
101
+ header_skipped = False
102
+ continue
103
+ if in_rt:
104
+ if line.startswith('## '):
105
+ break
106
+ if not line.strip():
107
+ continue
108
+ # Skip header row and separator row
109
+ striped = line.strip()
110
+ if striped.startswith('|') and '---' in striped:
111
+ header_skipped = True
112
+ continue
113
+ if striped.startswith('|') and header_skipped:
114
+ # Parse table row: | type | task_path | description |
115
+ cols = [c.strip() for c in striped.strip('|').split('|')]
116
+ if len(cols) >= 3:
117
+ rtype = cols[0].strip()
118
+ task_path = cols[1].strip().strip('`')
119
+ rdesc = cols[2].strip()
120
+ related_tasks.append({
121
+ 'type': rtype,
122
+ 'task_path': task_path,
123
+ 'description': rdesc
124
+ })
125
+
126
+ # Related Tickets: ## Related Tickets table
127
+ in_rti = False
128
+ header_skipped_rti = False
129
+ for line in content.split('\n'):
130
+ if line.startswith('## Related Tickets'):
131
+ in_rti = True
132
+ header_skipped_rti = False
133
+ continue
134
+ if in_rti:
135
+ if line.startswith('## '):
136
+ break
137
+ if not line.strip():
138
+ continue
139
+ striped = line.strip()
140
+ if striped.startswith('|') and '---' in striped:
141
+ header_skipped_rti = True
142
+ continue
143
+ if striped.startswith('|') and header_skipped_rti:
144
+ cols = [c.strip() for c in striped.strip('|').split('|')]
145
+ if len(cols) >= 2:
146
+ ticket_id = cols[0].strip().strip('`').lstrip('#')
147
+ rdesc_ti = cols[1].strip()
148
+ related_tickets.append({
149
+ 'ticket_id': ticket_id,
150
+ 'description': rdesc_ti
151
+ })
152
+
153
+ results.append((dirname, ts, name, title, status, desc, checklist, notes, related_tasks, related_tickets, cluster_info))
154
+ return results
155
+
156
+ def gen_readme(tasks):
157
+ """Generate README.md — summary table with ticket links."""
158
+ lines = ["# Tasks", "",
159
+ "Project tasks managed under `tasks/`. Each task is a self-contained unit of work.",
160
+ "",
161
+ "| Timestamp | Name | Status | Ticket | Description |",
162
+ "|---|---|---|---|---|"]
163
+ for dirname, ts, name, title, status, desc, _, _, _, related_tickets, cluster_info in tasks:
164
+ s = status.replace('\n', ' ').strip()
165
+ d = desc.replace('\n', ' ').strip()[:60]
166
+ if not d:
167
+ d = title[:60]
168
+ ticket_tag = ""
169
+ if related_tickets:
170
+ ids = ", ".join(f"#{t['ticket_id']}" for t in related_tickets)
171
+ ticket_tag = f"{ids}"
172
+ cl_tag = ""
173
+ if cluster_info.get("affinity") and cluster_info["affinity"] != "any":
174
+ cl_tag = f"[{cluster_info['affinity']}]"
175
+ if cluster_info.get("claimed_by"):
176
+ cl_tag += f" →{cluster_info['claimed_by']}"
177
+ lines.append(f"| {ts} | {name} | {s} {cl_tag} | {ticket_tag} | {d} |")
178
+
179
+ # Inbox
180
+ inbox_dir = os.path.join(TASKS_ROOT, "inbox")
181
+ inbox_items = []
182
+ if os.path.isdir(inbox_dir):
183
+ inbox_items = sorted(os.listdir(inbox_dir))
184
+ if inbox_items:
185
+ lines.extend(["", "### Inbox"] + [f"- {item}" for item in inbox_items])
186
+
187
+ lines.append("")
188
+ return '\n'.join(lines)
189
+
190
+ def gen_tasks_md(tasks):
191
+ """Generate TASKS.md — aggregated checklist view with relationship display."""
192
+ lines = ["# Tasks 聚合索引", "",
193
+ "所有任务的状态、Checklist 和关联关系汇总。由 `scripts/update-index.py` 自动生成,无需手动编辑。",
194
+ "", "---", ""]
195
+ for dirname, ts, name, title, status, desc, checklist, notes, related_tasks, related_tickets, cluster_info in tasks:
196
+ lines.append(f"## [{name}]({dirname}/)")
197
+ lines.append("")
198
+ lines.append(f"**状态:** {status.replace(chr(10), ' ').strip()}")
199
+ if cluster_info.get("affinity"):
200
+ cl_line = f"**Cluster:** affinity={cluster_info['affinity']}"
201
+ if cluster_info.get("claimed_by"):
202
+ cl_line += f", claimed_by={cluster_info['claimed_by']}"
203
+ lines.append(cl_line)
204
+ lines.append("")
205
+ if desc:
206
+ lines.append("")
207
+ lines.append(f"**目标:** {desc}")
208
+ lines.append("")
209
+ lines.append(f"**目录:** `{dirname}/`")
210
+
211
+ # Related tasks display
212
+ if related_tasks:
213
+ lines.append("")
214
+ lines.append("**关联任务:**")
215
+ lines.append("")
216
+ lines.append("| 关系 | 任务 | 说明 |")
217
+ lines.append("|------|------|------|")
218
+ for rt in related_tasks:
219
+ t = rt['type']
220
+ p = rt['task_path']
221
+ d = rt['description']
222
+ lines.append(f"| {t} | `{p}` | {d} |")
223
+
224
+ # Related tickets display
225
+ if related_tickets:
226
+ lines.append("")
227
+ lines.append("**关联 Ticket:**")
228
+ for rti in related_tickets:
229
+ tid = rti['ticket_id']
230
+ td = rti['description']
231
+ lines.append(f"- #{tid} — {td}")
232
+
233
+ if checklist:
234
+ done = sum(1 for c in checklist if c.startswith('- [x]'))
235
+ total = len(checklist)
236
+ lines.append("")
237
+ lines.append(f"### Checklist ({done}/{total} done)")
238
+ for item in checklist:
239
+ lines.append(f"- {item}")
240
+
241
+ if notes:
242
+ lines.append("")
243
+ lines.append("### Notes")
244
+ for n in notes[:5]:
245
+ lines.append(f"- {n}")
246
+
247
+ lines.extend(["", "---", ""])
248
+ return '\n'.join(lines)
249
+
250
+ def get_inbox(tasks_root):
251
+ inbox_dir = os.path.join(tasks_root, "inbox")
252
+ items = []
253
+ if os.path.isdir(inbox_dir):
254
+ items = [f for f in os.listdir(inbox_dir)
255
+ if os.path.isfile(os.path.join(inbox_dir, f)) or os.path.isdir(os.path.join(inbox_dir, f))]
256
+ return sorted(items)
257
+
258
+ def write_readme(tasks_root):
259
+ tasks = gather_tasks(tasks_root)
260
+ content = gen_readme(tasks)
261
+ path = os.path.join(tasks_root, "README.md")
262
+ with open(path, 'w', encoding='utf-8') as f:
263
+ f.write(content)
264
+ return path, len(content)
265
+
266
+ def write_tasks_md(tasks_root):
267
+ tasks = gather_tasks(tasks_root)
268
+ content = gen_tasks_md(tasks)
269
+ path = os.path.join(tasks_root, "TASKS.md")
270
+ with open(path, 'w', encoding='utf-8') as f:
271
+ f.write(content)
272
+ return path, len(content)
273
+
274
+ def main():
275
+ tasks_root = TASKS_ROOT
276
+ for i, arg in enumerate(sys.argv[1:]):
277
+ if arg == '--tasks-dir' and i+2 < len(sys.argv):
278
+ tasks_root = os.path.abspath(sys.argv[i+2])
279
+
280
+ if not os.path.isdir(tasks_root):
281
+ print(f"Error: tasks root not found: {tasks_root}", file=sys.stderr)
282
+ sys.exit(1)
283
+
284
+ p1, s1 = write_readme(tasks_root)
285
+ p2, s2 = write_tasks_md(tasks_root)
286
+ tasks = gather_tasks(tasks_root)
287
+ print(f"Updated:")
288
+ print(f" {p1} ({s1}B)")
289
+ print(f" {p2} ({s2}B)")
290
+ print(f" Found {len(tasks)} active tasks")
291
+
292
+ if __name__ == '__main__':
293
+ main()
@@ -0,0 +1,114 @@
1
+ # Task: <Name>
2
+
3
+ <!--
4
+ TASK.md 关键字段规范(所有 task 遵守):
5
+ - # Task: <Name> — 标题(英文)
6
+ - ## Status — 状态(英文)
7
+ - ## Goal — 目标(英文)
8
+ - ## Checklist — 步骤清单(英文)
9
+ - ## Notes — 备注(英文)
10
+ - ## Related Tasks — 关联任务关系表(可选)
11
+ 脚本 update-index.py 只解析以上英文关键字段。
12
+ 中文字段(## 状态、## 目标、## 步骤)为向下兼容而支持,新 task 请用英文。
13
+ -->
14
+
15
+ ## Status
16
+
17
+ active — <brief description of current work>
18
+
19
+ ## Goal
20
+
21
+ <one-liner>
22
+
23
+ ## Skills (用于跨 skill 组合任务)
24
+
25
+ - `<skill-name>` — <该 skill 在本任务中的用途>
26
+
27
+ ## Repo
28
+
29
+ <!-- 可选字段。若任务关联独立的项目仓库,在此声明路径。
30
+ 所有源码级产出(设计文档、代码、配置、测试)放到此仓库中,
31
+ 而非 task/output/。执行日志和调试信息仍放在 task/output/logs/。
32
+ 没有此字段时,所有产出放 task/output/(默认行为)。 -->
33
+
34
+ 路径: `<绝对路径,如 ~/studio/hermes/projects/agora/>`
35
+ 用途: <一句话描述>
36
+
37
+ ## Affinity (cluster task)
38
+
39
+ <!-- 控制此任务被哪个节点认领。影响 .hermes-task.json 的 affinity 字段。
40
+ 不带 cluster 时此字段无任何效果,保留无害。 -->
41
+
42
+ - `any` — 任何节点可认领
43
+ - `<hash6>` — 指定节点(推荐;跨机器同步安全)
44
+ - `<suite-name>` — 拥有此套件的节点(如 `hermes-personal-suite`)
45
+ - `<tag>` — 能力标签匹配(如 `gpu`, `docker`)
46
+ - 逗号分隔 → AND 逻辑(`hermes-personal-suite,gpu` 两样都有才匹配)
47
+
48
+ 注意:不要用 `local`。tasks repo 同步到其他机器后,每台都会把 `local` 当成自己的任务,造成混乱。用 node_id(hash6)代替。
49
+
50
+ ## 环境要求
51
+
52
+ | 项目 | 最低要求 | 说明 |
53
+ |------|---------|------|
54
+ | Python | <版本> | <说明> |
55
+ | <依赖项> | <版本> | <说明> |
56
+ | 磁盘 | <空闲空间> | <用途> |
57
+ | API Key | <Key 名称> | <用途说明> |
58
+
59
+ ## Data Flow
60
+
61
+ 跨 phase 文件传递路径。phase 执行时 CWD 在自己的子目录,跨目录引用使用相对路径(`../`)。
62
+
63
+ | 文件 | 来源 Phase | 被消费 Phase | 格式说明 |
64
+ |------|-----------|-------------|---------|
65
+ | `phase_01_<skill>/<file>` | Phase 1 | Phase N | <格式文档> |
66
+ | `phase_02_<skill>/<file>` | Phase 2 | Phase N | <格式文档> |
67
+
68
+ ## Phase 目录
69
+
70
+ - `phase_01_<skill>/` — <产出物说明>
71
+ - `phase_02_<skill>/` — <产出物说明>
72
+ - `phase_03_<skill>/` — <产出物说明>
73
+
74
+ ## Checklist
75
+
76
+ - [ ] Phase 1 — <investigation / reading / understanding>
77
+ - [ ] Phase 2 — <execution / writing / implementation>
78
+ - [ ] BREAK: <optional — delete if no pause needed here>
79
+ - [ ] Phase 3 — <verification / review / cleanup>
80
+
81
+ ## Tracking
82
+
83
+ <!-- 可选。每个 phase 完成后需要更新的跟踪记录。
84
+ 编排者(stratis/corvan/valros)读到本节后,自动注入到各 phase 的 card/worker brief 中。 -->
85
+
86
+ 加载 `task-tracker` skill,传入以下参数:
87
+
88
+ | 参数 | 值 | 说明 |
89
+ |------|-----|------|
90
+ | `task_dir` | 本 TASK.md 所在目录的绝对路径 | 让 tracker 定位任务 |
91
+ | `phase` | 当前完成的 phase 标识 | 如 Phase 0: scrutiny-pipeline |
92
+ | `executor` | 执行者名称 | 如 scrutaris |
93
+ | `findings` | 关键发现(每行一条) | 从产出中提炼 3-5 条 |
94
+ | `outputs` | 产出文件路径(相对 task_dir) | 如 output/docs/00-report.md |
95
+ | `next` | 下一个 phase | 如 Phase 1: domain-analysis |
96
+
97
+ ## Notes
98
+
99
+ ## Related Tickets
100
+
101
+ <!-- 可选。关联的 ticket。填写 ticket_id 后 task_create 会自动将 task hash 写回 ticket 的 task_id 字段。 -->
102
+
103
+ | Ticket ID | 关系说明 |
104
+ |-----------|---------|
105
+ | `#<ticket_id>` | 创建源 / 相关事项 |
106
+
107
+ ## Related Tasks
108
+
109
+ <!-- 可选。关联任务关系表:depends_on = 前置依赖(本任务开始前必须完成),related = 主题相关/互为参考,supersedes = 本任务替代/废弃了哪个旧任务 -->
110
+ | 类型 | 关联 Task | 关系说明 |
111
+ |------|----------|---------|
112
+ | depends_on | `YYYYMMDD-HHMMSS.<name>-<hash6>/` | 本任务依赖的前置任务 |
113
+ | related | `YYYYMMDD-HHMMSS.<name>-<hash6>/` | 主题相关、互为参考 |
114
+ | supersedes | `YYYYMMDD-HHMMSS.<name>-<hash6>/` | 本任务替代/废弃了哪个旧任务 |
@@ -0,0 +1,27 @@
1
+ # TASK_MEMORY.md — 任务操作记忆
2
+
3
+ 按时间倒序(最新在最上面)或正序(最新在最下面)追加。推荐正序,方便跨 session 顺读。
4
+
5
+ ## 2026-06-06 HH:MM
6
+
7
+ **操作:** 做了什么
8
+ **原因:** 为什么这么做
9
+ **改动:** 改了什么文件/目录,具体改动
10
+ **问题:** 遇到的问题
11
+ **解决:** 怎么解决的
12
+ **结果:** 最终结果
13
+ **素材状态:** phase-dir(内容✅/⚠️/❌) ...
14
+ **下一步:** 下次 session 要继续做什么
15
+
16
+ ---
17
+
18
+ ## 记录规范
19
+
20
+ - 每个 session(或每次连续操作)写一条 `## 日期 时间` 记录
21
+ - **操作:** 必填,一句话概括
22
+ - **原因:** 可选,决策理由
23
+ - **改动/问题/解决/结果:** 按需填写
24
+ - **素材状态:** 推荐维护,快速知道什么能复用
25
+ - **下一步:** 跨 session 必填,新 session 的起点
26
+ - 纯命令输出不要写这里(放 `logs/`)
27
+ - checklist 勾选不要写这里(放 `TASK.md`)