cowork-flow 0.0.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.
Files changed (101) hide show
  1. package/README.md +193 -0
  2. package/bin/cowork-flow.js +9 -0
  3. package/package.json +28 -0
  4. package/src/cli.js +61 -0
  5. package/src/commands/init.js +88 -0
  6. package/src/commands/sync.js +33 -0
  7. package/src/commands/update.js +60 -0
  8. package/src/lib/copy-template.js +189 -0
  9. package/src/lib/package-info.js +49 -0
  10. package/src/lib/paths.js +6 -0
  11. package/template/.agent/skills/break-loop/SKILL.md +131 -0
  12. package/template/.agent/skills/check-cross-layer/SKILL.md +158 -0
  13. package/template/.agent/skills/finish-work/SKILL.md +163 -0
  14. package/template/.agent/skills/record-session/SKILL.md +78 -0
  15. package/template/.agent/skills/start/SKILL.md +390 -0
  16. package/template/.agent/skills/update-spec/SKILL.md +335 -0
  17. package/template/.cowork-flow/.version +1 -0
  18. package/template/.cowork-flow/changes/archive/.gitkeep +1 -0
  19. package/template/.cowork-flow/config.yaml +49 -0
  20. package/template/.cowork-flow/plans/.gitkeep +1 -0
  21. package/template/.cowork-flow/scripts/add_session.py +441 -0
  22. package/template/.cowork-flow/scripts/change.py +314 -0
  23. package/template/.cowork-flow/scripts/common/__init__.py +84 -0
  24. package/template/.cowork-flow/scripts/common/config.py +128 -0
  25. package/template/.cowork-flow/scripts/common/developer.py +205 -0
  26. package/template/.cowork-flow/scripts/common/files.py +24 -0
  27. package/template/.cowork-flow/scripts/common/git_context.py +628 -0
  28. package/template/.cowork-flow/scripts/common/paths.py +350 -0
  29. package/template/.cowork-flow/scripts/common/task_utils.py +200 -0
  30. package/template/.cowork-flow/scripts/get_context.py +16 -0
  31. package/template/.cowork-flow/scripts/get_developer.py +26 -0
  32. package/template/.cowork-flow/scripts/init_developer.py +51 -0
  33. package/template/.cowork-flow/scripts/task.py +1378 -0
  34. package/template/.cowork-flow/spec/backend/database-guidelines.md +26 -0
  35. package/template/.cowork-flow/spec/backend/directory-structure.md +27 -0
  36. package/template/.cowork-flow/spec/backend/error-handling.md +20 -0
  37. package/template/.cowork-flow/spec/backend/index.md +36 -0
  38. package/template/.cowork-flow/spec/backend/logging-guidelines.md +20 -0
  39. package/template/.cowork-flow/spec/backend/quality-guidelines.md +24 -0
  40. package/template/.cowork-flow/spec/frontend/component-guidelines.md +19 -0
  41. package/template/.cowork-flow/spec/frontend/directory-structure.md +18 -0
  42. package/template/.cowork-flow/spec/frontend/hook-guidelines.md +17 -0
  43. package/template/.cowork-flow/spec/frontend/index.md +28 -0
  44. package/template/.cowork-flow/spec/frontend/quality-guidelines.md +23 -0
  45. package/template/.cowork-flow/spec/frontend/state-management.md +18 -0
  46. package/template/.cowork-flow/spec/frontend/type-safety.md +18 -0
  47. package/template/.cowork-flow/spec/guides/code-reuse-thinking-guide.md +26 -0
  48. package/template/.cowork-flow/spec/guides/cross-layer-thinking-guide.md +21 -0
  49. package/template/.cowork-flow/spec/guides/index.md +31 -0
  50. package/template/.cowork-flow/spec/guides/pre-implementation-checklist.md +15 -0
  51. package/template/.cowork-flow/tasks/.gitkeep +1 -0
  52. package/template/.cowork-flow/tasks/archive/.gitkeep +1 -0
  53. package/template/.cowork-flow/workflow.md +411 -0
  54. package/template/.cowork-flow/workspace/index.md +95 -0
  55. package/template/.superpowers/brainstorming/SKILL.md +164 -0
  56. package/template/.superpowers/brainstorming/scripts/frame-template.html +214 -0
  57. package/template/.superpowers/brainstorming/scripts/helper.js +88 -0
  58. package/template/.superpowers/brainstorming/scripts/server.cjs +354 -0
  59. package/template/.superpowers/brainstorming/scripts/start-server.sh +148 -0
  60. package/template/.superpowers/brainstorming/scripts/stop-server.sh +56 -0
  61. package/template/.superpowers/brainstorming/spec-document-reviewer-prompt.md +49 -0
  62. package/template/.superpowers/brainstorming/visual-companion.md +287 -0
  63. package/template/.superpowers/dispatching-parallel-agents/SKILL.md +182 -0
  64. package/template/.superpowers/executing-plans/SKILL.md +70 -0
  65. package/template/.superpowers/finishing-a-development-branch/SKILL.md +251 -0
  66. package/template/.superpowers/receiving-code-review/SKILL.md +213 -0
  67. package/template/.superpowers/requesting-code-review/SKILL.md +103 -0
  68. package/template/.superpowers/requesting-code-review/code-reviewer.md +168 -0
  69. package/template/.superpowers/subagent-driven-development/SKILL.md +279 -0
  70. package/template/.superpowers/subagent-driven-development/code-quality-reviewer-prompt.md +25 -0
  71. package/template/.superpowers/subagent-driven-development/implementer-prompt.md +113 -0
  72. package/template/.superpowers/subagent-driven-development/spec-reviewer-prompt.md +61 -0
  73. package/template/.superpowers/systematic-debugging/CREATION-LOG.md +119 -0
  74. package/template/.superpowers/systematic-debugging/SKILL.md +296 -0
  75. package/template/.superpowers/systematic-debugging/condition-based-waiting-example.ts +158 -0
  76. package/template/.superpowers/systematic-debugging/condition-based-waiting.md +115 -0
  77. package/template/.superpowers/systematic-debugging/defense-in-depth.md +122 -0
  78. package/template/.superpowers/systematic-debugging/find-polluter.sh +63 -0
  79. package/template/.superpowers/systematic-debugging/root-cause-tracing.md +169 -0
  80. package/template/.superpowers/systematic-debugging/test-academic.md +14 -0
  81. package/template/.superpowers/systematic-debugging/test-pressure-1.md +58 -0
  82. package/template/.superpowers/systematic-debugging/test-pressure-2.md +68 -0
  83. package/template/.superpowers/systematic-debugging/test-pressure-3.md +69 -0
  84. package/template/.superpowers/test-driven-development/SKILL.md +371 -0
  85. package/template/.superpowers/test-driven-development/testing-anti-patterns.md +299 -0
  86. package/template/.superpowers/using-git-worktrees/SKILL.md +215 -0
  87. package/template/.superpowers/using-superpowers/SKILL.md +117 -0
  88. package/template/.superpowers/using-superpowers/references/codex-tools.md +59 -0
  89. package/template/.superpowers/using-superpowers/references/copilot-tools.md +42 -0
  90. package/template/.superpowers/using-superpowers/references/gemini-tools.md +51 -0
  91. package/template/.superpowers/verification-before-completion/SKILL.md +139 -0
  92. package/template/.superpowers/writing-plans/SKILL.md +152 -0
  93. package/template/.superpowers/writing-plans/plan-document-reviewer-prompt.md +49 -0
  94. package/template/.superpowers/writing-skills/SKILL.md +655 -0
  95. package/template/.superpowers/writing-skills/anthropic-best-practices.md +1150 -0
  96. package/template/.superpowers/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
  97. package/template/.superpowers/writing-skills/graphviz-conventions.dot +172 -0
  98. package/template/.superpowers/writing-skills/persuasion-principles.md +187 -0
  99. package/template/.superpowers/writing-skills/render-graphs.js +168 -0
  100. package/template/.superpowers/writing-skills/testing-skills-with-subagents.md +384 -0
  101. package/template/AGENTS.md +116 -0
@@ -0,0 +1,441 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Add a new session to journal file and update index.md.
5
+
6
+ Usage:
7
+ python3 add_session.py --title "Title" --commit "hash" --summary "Summary"
8
+ echo "content" | python3 add_session.py --title "Title" --commit "hash"
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import argparse
14
+ import re
15
+ import subprocess
16
+ import sys
17
+ from datetime import datetime
18
+ from pathlib import Path
19
+
20
+ from common.paths import (
21
+ FILE_JOURNAL_PREFIX,
22
+ count_lines,
23
+ get_repo_root,
24
+ get_developer,
25
+ get_workspace_dir,
26
+ )
27
+ from common.developer import ensure_developer
28
+ from common.config import get_session_commit_message, get_max_journal_lines
29
+
30
+ MARKER_CURRENT_STATUS_START = "@@@auto:current-status"
31
+ MARKER_CURRENT_STATUS_END = "@@@/auto:current-status"
32
+ MARKER_ACTIVE_DOCUMENTS_START = "@@@auto:active-documents"
33
+ MARKER_ACTIVE_DOCUMENTS_END = "@@@/auto:active-documents"
34
+ MARKER_SESSION_HISTORY_START = "@@@auto:session-history"
35
+ MARKER_SESSION_HISTORY_END = "@@@/auto:session-history"
36
+
37
+
38
+ # =============================================================================
39
+ # Helper Functions
40
+ # =============================================================================
41
+
42
+ def get_latest_journal_info(dev_dir: Path) -> tuple[Path | None, int, int]:
43
+ """Get latest journal file info.
44
+
45
+ Returns:
46
+ Tuple of (file_path, file_number, line_count).
47
+ """
48
+ latest_file: Path | None = None
49
+ latest_num = -1
50
+
51
+ for f in dev_dir.glob(f"{FILE_JOURNAL_PREFIX}*.md"):
52
+ if not f.is_file():
53
+ continue
54
+
55
+ match = re.search(r"(\d+)$", f.stem)
56
+ if match:
57
+ num = int(match.group(1))
58
+ if num > latest_num:
59
+ latest_num = num
60
+ latest_file = f
61
+
62
+ if latest_file:
63
+ lines = count_lines(latest_file)
64
+ return latest_file, latest_num, lines
65
+
66
+ return None, 0, 0
67
+
68
+
69
+ def get_current_session(index_file: Path) -> int:
70
+ """Get current session number from index.md."""
71
+ if not index_file.is_file():
72
+ return 0
73
+
74
+ content = index_file.read_text(encoding="utf-8")
75
+ for line in content.splitlines():
76
+ if "Total Sessions" in line or "\u603b\u4f1a\u8bdd\u6570" in line:
77
+ match = re.search(r"[:\uFF1A]\s*(\d+)", line)
78
+ if match:
79
+ return int(match.group(1))
80
+ return 0
81
+
82
+
83
+ def _extract_journal_num(filename: str) -> int:
84
+ """Extract journal number from filename for sorting."""
85
+ match = re.search(r"(\d+)", filename)
86
+ return int(match.group(1)) if match else 0
87
+
88
+
89
+ def _format_commit_display(commit: str) -> str:
90
+ """Format commit hashes for display in index.md."""
91
+ if not commit or commit == "-":
92
+ return "-"
93
+ return re.sub(r"([a-f0-9]{7,})", r"`\1`", commit.replace(",", ", "))
94
+
95
+
96
+ def _format_commit_table(commit: str) -> str:
97
+ """Format commit hashes for a session entry."""
98
+ if not commit or commit == "-":
99
+ return "(no code commit; planning or sync session)"
100
+
101
+ commit_table = """| Hash | Note |
102
+ |------|------|"""
103
+ for c in commit.split(","):
104
+ c = c.strip()
105
+ commit_table += f"\n| `{c}` | See git log |"
106
+ return commit_table
107
+
108
+
109
+ def count_journal_files(dev_dir: Path, active_num: int) -> str:
110
+ """Count journal files and return table rows."""
111
+ active_file = f"{FILE_JOURNAL_PREFIX}{active_num}.md"
112
+ result_lines = []
113
+
114
+ files = sorted(
115
+ [f for f in dev_dir.glob(f"{FILE_JOURNAL_PREFIX}*.md") if f.is_file()],
116
+ key=lambda f: _extract_journal_num(f.stem),
117
+ reverse=True
118
+ )
119
+
120
+ for f in files:
121
+ filename = f.name
122
+ lines = count_lines(f)
123
+ status = "Current" if filename == active_file else "Archived"
124
+ result_lines.append(f"| `{filename}` | ~{lines} | {status} |")
125
+
126
+ return "\n".join(result_lines)
127
+
128
+
129
+ def create_new_journal_file(
130
+ dev_dir: Path, num: int, developer: str, today: str, max_lines: int = 2000,
131
+ ) -> Path:
132
+ """Create a new journal file."""
133
+ prev_num = num - 1
134
+ new_file = dev_dir / f"{FILE_JOURNAL_PREFIX}{num}.md"
135
+
136
+ content = f"""# Development Journal - {developer} (Part {num})
137
+
138
+ > Continued from `{FILE_JOURNAL_PREFIX}{prev_num}.md` (archived after about {max_lines} lines)
139
+ > Start date: {today}
140
+
141
+ ---
142
+
143
+ """
144
+ new_file.write_text(content, encoding="utf-8")
145
+ return new_file
146
+
147
+
148
+ def generate_session_content(
149
+ session_num: int,
150
+ title: str,
151
+ commit: str,
152
+ summary: str,
153
+ extra_content: str,
154
+ today: str
155
+ ) -> str:
156
+ """Generate session content."""
157
+ commit_table = _format_commit_table(commit)
158
+
159
+ return f"""
160
+
161
+ ## Session {session_num}: {title}
162
+
163
+ **Date**: {today}
164
+ **Task**: {title}
165
+
166
+ ### Summary
167
+
168
+ {summary}
169
+
170
+ ### Main Changes
171
+
172
+ {extra_content}
173
+
174
+ ### Git Commit
175
+
176
+ {commit_table}
177
+
178
+ ### Verification
179
+
180
+ - [OK] (add verification results)
181
+
182
+ ### Status
183
+
184
+ [OK] **Completed**
185
+
186
+ ### Follow-up Actions
187
+
188
+ - None, current task is complete
189
+ """
190
+
191
+
192
+ def update_index(
193
+ index_file: Path,
194
+ dev_dir: Path,
195
+ title: str,
196
+ commit: str,
197
+ new_session: int,
198
+ active_file: str,
199
+ today: str
200
+ ) -> bool:
201
+ """Update index.md with new session info."""
202
+ commit_display = _format_commit_display(commit)
203
+
204
+ # Get file number from active_file name
205
+ match = re.search(r"(\d+)", active_file)
206
+ active_num = int(match.group(1)) if match else 0
207
+ files_table = count_journal_files(dev_dir, active_num)
208
+
209
+ print(f"Updating index.md (session {new_session})...", file=sys.stderr)
210
+ print(f" Title: {title}", file=sys.stderr)
211
+ print(f" Commit: {commit_display}", file=sys.stderr)
212
+ print(f" Current file: {active_file}", file=sys.stderr)
213
+ print("", file=sys.stderr)
214
+
215
+ content = index_file.read_text(encoding="utf-8")
216
+
217
+ if MARKER_CURRENT_STATUS_START not in content:
218
+ print("Error: index.md is missing auto-update markers. Please restore the template first.", file=sys.stderr)
219
+ return False
220
+
221
+ # Process sections
222
+ lines = content.splitlines()
223
+ new_lines = []
224
+
225
+ in_current_status = False
226
+ in_active_documents = False
227
+ in_session_history = False
228
+ header_written = False
229
+
230
+ for line in lines:
231
+ if MARKER_CURRENT_STATUS_START in line:
232
+ new_lines.append(line)
233
+ in_current_status = True
234
+ new_lines.append(f"- **Current file**: `{active_file}`")
235
+ new_lines.append(f"- **Total Sessions**: {new_session}")
236
+ new_lines.append(f"- **Last Active**: {today}")
237
+ continue
238
+
239
+ if MARKER_CURRENT_STATUS_END in line:
240
+ in_current_status = False
241
+ new_lines.append(line)
242
+ continue
243
+
244
+ if MARKER_ACTIVE_DOCUMENTS_START in line:
245
+ new_lines.append(line)
246
+ in_active_documents = True
247
+ new_lines.append("| File | Lines | Status |")
248
+ new_lines.append("|------|------|------|")
249
+ new_lines.append(files_table)
250
+ continue
251
+
252
+ if MARKER_ACTIVE_DOCUMENTS_END in line:
253
+ in_active_documents = False
254
+ new_lines.append(line)
255
+ continue
256
+
257
+ if MARKER_SESSION_HISTORY_START in line:
258
+ new_lines.append(line)
259
+ in_session_history = True
260
+ header_written = False
261
+ continue
262
+
263
+ if MARKER_SESSION_HISTORY_END in line:
264
+ in_session_history = False
265
+ new_lines.append(line)
266
+ continue
267
+
268
+ if in_current_status:
269
+ continue
270
+
271
+ if in_active_documents:
272
+ continue
273
+
274
+ if in_session_history:
275
+ new_lines.append(line)
276
+ if re.match(r"^\|\s*-", line) and not header_written:
277
+ new_lines.append(f"| {new_session} | {today} | {title} | {commit_display} |")
278
+ header_written = True
279
+ continue
280
+
281
+ new_lines.append(line)
282
+
283
+ index_file.write_text("\n".join(new_lines), encoding="utf-8")
284
+ print("[OK] index.md updated.", file=sys.stderr)
285
+ return True
286
+
287
+
288
+ # =============================================================================
289
+ # Main Function
290
+ # =============================================================================
291
+
292
+ def _auto_commit_workspace(repo_root: Path) -> None:
293
+ """Stage .cowork-flow/workspace and .cowork-flow/tasks, then commit with a configured message."""
294
+ commit_msg = get_session_commit_message(repo_root)
295
+ subprocess.run(
296
+ ["git", "add", "-A", ".cowork-flow/workspace", ".cowork-flow/tasks"],
297
+ cwd=repo_root,
298
+ capture_output=True,
299
+ )
300
+ # Check if there are staged changes
301
+ result = subprocess.run(
302
+ ["git", "diff", "--cached", "--quiet", "--", ".cowork-flow/workspace", ".cowork-flow/tasks"],
303
+ cwd=repo_root,
304
+ )
305
+ if result.returncode == 0:
306
+ print("[OK] No workspace metadata changes to commit.", file=sys.stderr)
307
+ return
308
+ commit_result = subprocess.run(
309
+ ["git", "commit", "-m", commit_msg],
310
+ cwd=repo_root,
311
+ capture_output=True,
312
+ text=True,
313
+ )
314
+ if commit_result.returncode == 0:
315
+ print(f"[OK] Metadata auto-committed: {commit_msg}", file=sys.stderr)
316
+ else:
317
+ print(f"[WARN] Metadata auto-commit failed: {commit_result.stderr.strip()}", file=sys.stderr)
318
+
319
+
320
+ def add_session(
321
+ title: str,
322
+ commit: str = "-",
323
+ summary: str = "(add summary)",
324
+ extra_content: str = "(add details)",
325
+ auto_commit: bool = True,
326
+ ) -> int:
327
+ """Add a new session."""
328
+ repo_root = get_repo_root()
329
+ ensure_developer(repo_root)
330
+
331
+ developer = get_developer(repo_root)
332
+ if not developer:
333
+ print("Error: developer identity has not been initialized.", file=sys.stderr)
334
+ return 1
335
+
336
+ dev_dir = get_workspace_dir(repo_root)
337
+ if not dev_dir:
338
+ print("Error: workspace directory not found.", file=sys.stderr)
339
+ return 1
340
+
341
+ max_lines = get_max_journal_lines(repo_root)
342
+
343
+ index_file = dev_dir / "index.md"
344
+ today = datetime.now().strftime("%Y-%m-%d")
345
+
346
+ journal_file, current_num, current_lines = get_latest_journal_info(dev_dir)
347
+ current_session = get_current_session(index_file)
348
+ new_session = current_session + 1
349
+
350
+ session_content = generate_session_content(
351
+ new_session, title, commit, summary, extra_content, today
352
+ )
353
+ content_lines = len(session_content.splitlines())
354
+
355
+ print("========================================", file=sys.stderr)
356
+ print("Add Session Record", file=sys.stderr)
357
+ print("========================================", file=sys.stderr)
358
+ print("", file=sys.stderr)
359
+ print(f"Session: {new_session}", file=sys.stderr)
360
+ print(f"Title: {title}", file=sys.stderr)
361
+ print(f"Commit: {commit}", file=sys.stderr)
362
+ print("", file=sys.stderr)
363
+ print(f"Current journal file: {FILE_JOURNAL_PREFIX}{current_num}.md", file=sys.stderr)
364
+ print(f"Current lines: {current_lines}", file=sys.stderr)
365
+ print(f"New content lines: {content_lines}", file=sys.stderr)
366
+ print(f"Total lines after append: {current_lines + content_lines}", file=sys.stderr)
367
+ print("", file=sys.stderr)
368
+
369
+ target_file = journal_file
370
+ target_num = current_num
371
+
372
+ if current_lines + content_lines > max_lines:
373
+ target_num = current_num + 1
374
+ print(f"[!] Over {max_lines} lines, creating {FILE_JOURNAL_PREFIX}{target_num}.md", file=sys.stderr)
375
+ target_file = create_new_journal_file(dev_dir, target_num, developer, today, max_lines)
376
+ print(f"Created: {target_file}", file=sys.stderr)
377
+
378
+ # Append session content
379
+ if target_file:
380
+ with target_file.open("a", encoding="utf-8") as f:
381
+ f.write(session_content)
382
+ print(f"[OK] Appended session to {target_file.name}", file=sys.stderr)
383
+
384
+ print("", file=sys.stderr)
385
+
386
+ # Update index.md
387
+ active_file = f"{FILE_JOURNAL_PREFIX}{target_num}.md"
388
+ if not update_index(index_file, dev_dir, title, commit, new_session, active_file, today):
389
+ return 1
390
+
391
+ print("", file=sys.stderr)
392
+ print("========================================", file=sys.stderr)
393
+ print(f"[OK] Session {new_session} recorded successfully.", file=sys.stderr)
394
+ print("========================================", file=sys.stderr)
395
+ print("", file=sys.stderr)
396
+ print("Updated files:", file=sys.stderr)
397
+ print(f" - {target_file.name if target_file else 'journal'}", file=sys.stderr)
398
+ print(" - index.md", file=sys.stderr)
399
+
400
+ # Auto-commit workspace changes
401
+ if auto_commit:
402
+ print("", file=sys.stderr)
403
+ _auto_commit_workspace(repo_root)
404
+
405
+ return 0
406
+
407
+
408
+ # =============================================================================
409
+ # Main Entry
410
+ # =============================================================================
411
+
412
+ def main() -> int:
413
+ """CLI entry point."""
414
+ parser = argparse.ArgumentParser(
415
+ description="Append a session record to the journal and update index.md"
416
+ )
417
+ parser.add_argument("--title", required=True, help="Session title")
418
+ parser.add_argument("--commit", default="-", help="Comma-separated commit hashes")
419
+ parser.add_argument("--summary", default="(add summary)", help="Brief summary")
420
+ parser.add_argument("--content-file", help="Path to a file containing detailed content")
421
+ parser.add_argument("--no-commit", action="store_true",
422
+ help="Skip automatic .cowork-flow metadata commit")
423
+
424
+ args = parser.parse_args()
425
+
426
+ extra_content = "(add details)"
427
+ if args.content_file:
428
+ content_path = Path(args.content_file)
429
+ if content_path.is_file():
430
+ extra_content = content_path.read_text(encoding="utf-8")
431
+ elif not sys.stdin.isatty():
432
+ extra_content = sys.stdin.read()
433
+
434
+ return add_session(
435
+ args.title, args.commit, args.summary, extra_content,
436
+ auto_commit=not args.no_commit,
437
+ )
438
+
439
+
440
+ if __name__ == "__main__":
441
+ sys.exit(main())