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,350 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Common path utilities for Cowork Flow workflow.
4
+
5
+ Provides:
6
+ get_repo_root - Get repository root directory
7
+ get_developer - Get developer name
8
+ get_workspace_dir - Get developer workspace directory
9
+ get_tasks_dir - Get tasks directory
10
+ get_active_journal_file - Get current journal file
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import re
16
+ from datetime import datetime
17
+ from pathlib import Path
18
+
19
+
20
+ # =============================================================================
21
+ # Path Constants (change here to rename directories)
22
+ # =============================================================================
23
+
24
+ # Directory names
25
+ DIR_WORKFLOW = ".cowork-flow"
26
+ DIR_AGENT = ".agent"
27
+ DIR_CHANGES = "changes"
28
+ DIR_WORKSPACE = "workspace"
29
+ DIR_TASKS = "tasks"
30
+ DIR_ARCHIVE = "archive"
31
+ DIR_SPEC = "spec"
32
+ DIR_SCRIPTS = "scripts"
33
+
34
+ # File names
35
+ FILE_DEVELOPER = ".developer"
36
+ FILE_CURRENT_TASK = ".current-task"
37
+ FILE_TASK_JSON = "task.json"
38
+ FILE_JOURNAL_PREFIX = "journal-"
39
+
40
+
41
+ # =============================================================================
42
+ # Repository Root
43
+ # =============================================================================
44
+
45
+ def get_repo_root(start_path: Path | None = None) -> Path:
46
+ """Find the nearest directory containing .cowork-flow/ folder.
47
+
48
+ This handles nested git repos correctly (e.g., test project inside another repo).
49
+
50
+ Args:
51
+ start_path: Starting directory to search from. Defaults to current directory.
52
+
53
+ Returns:
54
+ Path to repository root, or current directory if no .cowork-flow/ found.
55
+ """
56
+ current = (start_path or Path.cwd()).absolute()
57
+
58
+ while current != current.parent:
59
+ if (current / DIR_WORKFLOW).is_dir():
60
+ return current
61
+ current = current.parent
62
+
63
+ # Fallback to current directory if no .cowork-flow/ found
64
+ return Path.cwd().absolute()
65
+
66
+
67
+ # =============================================================================
68
+ # Developer
69
+ # =============================================================================
70
+
71
+ def get_developer(repo_root: Path | None = None) -> str | None:
72
+ """Get developer name from .developer file.
73
+
74
+ Args:
75
+ repo_root: Repository root path. Defaults to auto-detected.
76
+
77
+ Returns:
78
+ Developer name or None if not initialized.
79
+ """
80
+ if repo_root is None:
81
+ repo_root = get_repo_root()
82
+
83
+ dev_file = repo_root / DIR_WORKFLOW / FILE_DEVELOPER
84
+
85
+ if not dev_file.is_file():
86
+ return None
87
+
88
+ try:
89
+ content = dev_file.read_text(encoding="utf-8")
90
+ for line in content.splitlines():
91
+ if line.startswith("name="):
92
+ return line.split("=", 1)[1].strip()
93
+ except (OSError, IOError):
94
+ pass
95
+
96
+ return None
97
+
98
+
99
+ def check_developer(repo_root: Path | None = None) -> bool:
100
+ """Check if developer is initialized.
101
+
102
+ Args:
103
+ repo_root: Repository root path. Defaults to auto-detected.
104
+
105
+ Returns:
106
+ True if developer is initialized.
107
+ """
108
+ return get_developer(repo_root) is not None
109
+
110
+
111
+ # =============================================================================
112
+ # Tasks Directory
113
+ # =============================================================================
114
+
115
+ def get_tasks_dir(repo_root: Path | None = None) -> Path:
116
+ """Get tasks directory path.
117
+
118
+ Args:
119
+ repo_root: Repository root path. Defaults to auto-detected.
120
+
121
+ Returns:
122
+ Path to tasks directory.
123
+ """
124
+ if repo_root is None:
125
+ repo_root = get_repo_root()
126
+ return repo_root / DIR_WORKFLOW / DIR_TASKS
127
+
128
+
129
+ # =============================================================================
130
+ # Workspace Directory
131
+ # =============================================================================
132
+
133
+ def get_workspace_dir(repo_root: Path | None = None) -> Path | None:
134
+ """Get developer workspace directory.
135
+
136
+ Args:
137
+ repo_root: Repository root path. Defaults to auto-detected.
138
+
139
+ Returns:
140
+ Path to workspace directory or None if developer not set.
141
+ """
142
+ if repo_root is None:
143
+ repo_root = get_repo_root()
144
+
145
+ developer = get_developer(repo_root)
146
+ if developer:
147
+ return repo_root / DIR_WORKFLOW / DIR_WORKSPACE / developer
148
+ return None
149
+
150
+
151
+ # =============================================================================
152
+ # Journal File
153
+ # =============================================================================
154
+
155
+ def get_active_journal_file(repo_root: Path | None = None) -> Path | None:
156
+ """Get the current active journal file.
157
+
158
+ Args:
159
+ repo_root: Repository root path. Defaults to auto-detected.
160
+
161
+ Returns:
162
+ Path to active journal file or None if not found.
163
+ """
164
+ if repo_root is None:
165
+ repo_root = get_repo_root()
166
+
167
+ workspace_dir = get_workspace_dir(repo_root)
168
+ if workspace_dir is None or not workspace_dir.is_dir():
169
+ return None
170
+
171
+ latest: Path | None = None
172
+ highest = 0
173
+
174
+ for f in workspace_dir.glob(f"{FILE_JOURNAL_PREFIX}*.md"):
175
+ if not f.is_file():
176
+ continue
177
+
178
+ # Extract number from filename
179
+ name = f.stem # e.g., "journal-1"
180
+ match = re.search(r"(\d+)$", name)
181
+ if match:
182
+ num = int(match.group(1))
183
+ if num > highest:
184
+ highest = num
185
+ latest = f
186
+
187
+ return latest
188
+
189
+
190
+ def count_lines(file_path: Path) -> int:
191
+ """Count lines in a file.
192
+
193
+ Args:
194
+ file_path: Path to file.
195
+
196
+ Returns:
197
+ Number of lines, or 0 if file doesn't exist.
198
+ """
199
+ if not file_path.is_file():
200
+ return 0
201
+
202
+ try:
203
+ with file_path.open("r", encoding="utf-8") as f:
204
+ return sum(1 for _ in f)
205
+ except OSError:
206
+ return 0
207
+
208
+
209
+ # =============================================================================
210
+ # Current Task Management
211
+ # =============================================================================
212
+
213
+ def _get_current_task_file(repo_root: Path | None = None) -> Path:
214
+ """Get .current-task file path.
215
+
216
+ Args:
217
+ repo_root: Repository root path. Defaults to auto-detected.
218
+
219
+ Returns:
220
+ Path to .current-task file.
221
+ """
222
+ if repo_root is None:
223
+ repo_root = get_repo_root()
224
+ return repo_root / DIR_WORKFLOW / FILE_CURRENT_TASK
225
+
226
+
227
+ def get_current_task(repo_root: Path | None = None) -> str | None:
228
+ """Get current task directory path (relative to repo_root).
229
+
230
+ Args:
231
+ repo_root: Repository root path. Defaults to auto-detected.
232
+
233
+ Returns:
234
+ Relative path to current task directory or None.
235
+ """
236
+ current_file = _get_current_task_file(repo_root)
237
+
238
+ if not current_file.is_file():
239
+ return None
240
+
241
+ try:
242
+ return current_file.read_text(encoding="utf-8").strip()
243
+ except (OSError, IOError):
244
+ return None
245
+
246
+
247
+ def get_current_task_abs(repo_root: Path | None = None) -> Path | None:
248
+ """Get current task directory absolute path.
249
+
250
+ Args:
251
+ repo_root: Repository root path. Defaults to auto-detected.
252
+
253
+ Returns:
254
+ Absolute path to current task directory or None.
255
+ """
256
+ if repo_root is None:
257
+ repo_root = get_repo_root()
258
+
259
+ relative = get_current_task(repo_root)
260
+ if relative:
261
+ return repo_root / relative
262
+ return None
263
+
264
+
265
+ def set_current_task(task_path: str, repo_root: Path | None = None) -> bool:
266
+ """Set current task.
267
+
268
+ Args:
269
+ task_path: Task directory path (relative to repo_root).
270
+ repo_root: Repository root path. Defaults to auto-detected.
271
+
272
+ Returns:
273
+ True on success, False on error.
274
+ """
275
+ if repo_root is None:
276
+ repo_root = get_repo_root()
277
+
278
+ if not task_path:
279
+ return False
280
+
281
+ # Verify task directory exists
282
+ full_path = repo_root / task_path
283
+ if not full_path.is_dir():
284
+ return False
285
+
286
+ current_file = _get_current_task_file(repo_root)
287
+
288
+ try:
289
+ current_file.write_text(task_path, encoding="utf-8")
290
+ return True
291
+ except (OSError, IOError):
292
+ return False
293
+
294
+
295
+ def clear_current_task(repo_root: Path | None = None) -> bool:
296
+ """Clear current task.
297
+
298
+ Args:
299
+ repo_root: Repository root path. Defaults to auto-detected.
300
+
301
+ Returns:
302
+ True on success.
303
+ """
304
+ current_file = _get_current_task_file(repo_root)
305
+
306
+ try:
307
+ if current_file.is_file():
308
+ current_file.unlink()
309
+ return True
310
+ except (OSError, IOError):
311
+ return False
312
+
313
+
314
+ def has_current_task(repo_root: Path | None = None) -> bool:
315
+ """Check if has current task.
316
+
317
+ Args:
318
+ repo_root: Repository root path. Defaults to auto-detected.
319
+
320
+ Returns:
321
+ True if current task is set.
322
+ """
323
+ return get_current_task(repo_root) is not None
324
+
325
+
326
+ # =============================================================================
327
+ # Task ID Generation
328
+ # =============================================================================
329
+
330
+ def generate_task_date_prefix() -> str:
331
+ """Generate task ID based on date (MM-DD format).
332
+
333
+ Returns:
334
+ Date prefix string (e.g., "01-21").
335
+ """
336
+ return datetime.now().strftime("%m-%d")
337
+
338
+
339
+ # =============================================================================
340
+ # Main Entry (for testing)
341
+ # =============================================================================
342
+
343
+ if __name__ == "__main__":
344
+ repo = get_repo_root()
345
+ print(f"Repository root: {repo}")
346
+ print(f"Developer: {get_developer(repo)}")
347
+ print(f"Tasks dir: {get_tasks_dir(repo)}")
348
+ print(f"Workspace dir: {get_workspace_dir(repo)}")
349
+ print(f"Journal file: {get_active_journal_file(repo)}")
350
+ print(f"Current task: {get_current_task(repo)}")
@@ -0,0 +1,200 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Task utility functions.
4
+
5
+ Provides:
6
+ is_safe_task_path - Validate task path is safe to operate on
7
+ find_task_by_name - Find task directory by name
8
+ archive_task_dir - Archive task to monthly directory
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import shutil
14
+ import sys
15
+ from datetime import datetime
16
+ from pathlib import Path
17
+
18
+ from .paths import get_repo_root
19
+
20
+ PATH_TRAVERSAL_PREFIXES = ("./", "../")
21
+ PATH_TRAVERSAL_PART = ".."
22
+ UNSAFE_TASK_PATHS = (".", "..")
23
+
24
+
25
+ # =============================================================================
26
+ # Path Safety
27
+ # =============================================================================
28
+
29
+ def is_safe_task_path(task_path: str, repo_root: Path | None = None) -> bool:
30
+ """Check if a relative task path is safe to operate on.
31
+
32
+ Args:
33
+ task_path: Task path (relative to repo_root).
34
+ repo_root: Repository root path. Defaults to auto-detected.
35
+
36
+ Returns:
37
+ True if safe, False if dangerous.
38
+ """
39
+ if repo_root is None:
40
+ repo_root = get_repo_root()
41
+
42
+ # Check empty or null
43
+ if not task_path or task_path == "null":
44
+ print("Error: empty or null task path", file=sys.stderr)
45
+ return False
46
+
47
+ # Reject absolute paths
48
+ if task_path.startswith("/"):
49
+ print(f"Error: absolute path not allowed: {task_path}", file=sys.stderr)
50
+ return False
51
+
52
+ # Reject ".", "..", paths starting with "./" or "../", or containing ".."
53
+ has_path_traversal = (
54
+ task_path in UNSAFE_TASK_PATHS
55
+ or task_path.startswith(PATH_TRAVERSAL_PREFIXES)
56
+ or PATH_TRAVERSAL_PART in task_path
57
+ )
58
+ if has_path_traversal:
59
+ print(f"Error: path traversal not allowed: {task_path}", file=sys.stderr)
60
+ return False
61
+
62
+ # Final check: ensure resolved path is not the repo root
63
+ abs_path = repo_root / task_path
64
+ if abs_path.exists():
65
+ try:
66
+ resolved = abs_path.resolve()
67
+ root_resolved = repo_root.resolve()
68
+ if resolved == root_resolved:
69
+ print(f"Error: path resolves to repo root: {task_path}", file=sys.stderr)
70
+ return False
71
+ except (OSError, IOError):
72
+ pass
73
+
74
+ return True
75
+
76
+
77
+ # =============================================================================
78
+ # Task Lookup
79
+ # =============================================================================
80
+
81
+ def find_task_by_name(task_name: str, tasks_dir: Path) -> Path | None:
82
+ """Find task directory by name (exact or suffix match).
83
+
84
+ Args:
85
+ task_name: Task name to find.
86
+ tasks_dir: Tasks directory path.
87
+
88
+ Returns:
89
+ Absolute path to task directory, or None if not found.
90
+ """
91
+ if not task_name or not tasks_dir or not tasks_dir.is_dir():
92
+ return None
93
+
94
+ # Try exact match first
95
+ exact_match = tasks_dir / task_name
96
+ if exact_match.is_dir():
97
+ return exact_match
98
+
99
+ # Try suffix match (e.g., "my-task" matches "01-21-my-task")
100
+ for candidate in tasks_dir.iterdir():
101
+ if candidate.is_dir() and candidate.name.endswith(f"-{task_name}"):
102
+ return candidate
103
+
104
+ return None
105
+
106
+
107
+ # =============================================================================
108
+ # Archive Operations
109
+ # =============================================================================
110
+
111
+ def archive_task_dir(task_dir_abs: Path, repo_root: Path | None = None) -> Path | None:
112
+ """Archive a task directory to archive/{YYYY-MM}/.
113
+
114
+ Args:
115
+ task_dir_abs: Absolute path to task directory.
116
+ repo_root: Repository root path. Defaults to auto-detected.
117
+
118
+ Returns:
119
+ Path to archived directory, or None on error.
120
+ """
121
+ if not task_dir_abs.is_dir():
122
+ print(f"Error: task directory not found: {task_dir_abs}", file=sys.stderr)
123
+ return None
124
+
125
+ # Get tasks directory (parent of the task)
126
+ tasks_dir = task_dir_abs.parent
127
+ archive_dir = tasks_dir / "archive"
128
+ year_month = datetime.now().strftime("%Y-%m")
129
+ month_dir = archive_dir / year_month
130
+
131
+ # Create archive directory
132
+ try:
133
+ month_dir.mkdir(parents=True, exist_ok=True)
134
+ except (OSError, IOError) as e:
135
+ print(f"Error: Failed to create archive directory: {e}", file=sys.stderr)
136
+ return None
137
+
138
+ # Move task to archive
139
+ task_name = task_dir_abs.name
140
+ archive_dest = month_dir / task_name
141
+
142
+ if archive_dest.exists():
143
+ print(f"Error: archive target already exists: {archive_dest}", file=sys.stderr)
144
+ print(f"Source task directory is still present: {task_dir_abs}", file=sys.stderr)
145
+ return None
146
+
147
+ try:
148
+ shutil.move(str(task_dir_abs), str(archive_dest))
149
+ except (OSError, IOError, shutil.Error) as e:
150
+ print(f"Error: Failed to move task to archive: {e}", file=sys.stderr)
151
+ if archive_dest.exists() and task_dir_abs.exists():
152
+ print("Error: archive move left both source and target paths present.", file=sys.stderr)
153
+ print(f" Source: {task_dir_abs}", file=sys.stderr)
154
+ print(f" Target: {archive_dest}", file=sys.stderr)
155
+ elif archive_dest.exists():
156
+ print(f"Error: archive target path exists after failure: {archive_dest}", file=sys.stderr)
157
+ elif task_dir_abs.exists():
158
+ print(f"Error: source task directory is still present: {task_dir_abs}", file=sys.stderr)
159
+ return None
160
+
161
+ return archive_dest
162
+
163
+
164
+ def archive_task_complete(
165
+ task_dir_abs: Path,
166
+ repo_root: Path | None = None
167
+ ) -> dict[str, str]:
168
+ """Complete archive workflow: archive directory.
169
+
170
+ Args:
171
+ task_dir_abs: Absolute path to task directory.
172
+ repo_root: Repository root path. Defaults to auto-detected.
173
+
174
+ Returns:
175
+ Dict with archive result info.
176
+ """
177
+ if not task_dir_abs.is_dir():
178
+ print(f"Error: task directory not found: {task_dir_abs}", file=sys.stderr)
179
+ return {}
180
+
181
+ archive_dest = archive_task_dir(task_dir_abs, repo_root)
182
+ if archive_dest:
183
+ return {"archived_to": str(archive_dest)}
184
+
185
+ return {}
186
+
187
+
188
+ # =============================================================================
189
+ # Main Entry (for testing)
190
+ # =============================================================================
191
+
192
+ if __name__ == "__main__":
193
+ from .paths import get_tasks_dir
194
+
195
+ repo = get_repo_root()
196
+ tasks = get_tasks_dir(repo)
197
+
198
+ print(f"Tasks dir: {tasks}")
199
+ print(f"is_safe_task_path('.cowork-flow/tasks/test'): {is_safe_task_path('.cowork-flow/tasks/test', repo)}")
200
+ print(f"is_safe_task_path('../test'): {is_safe_task_path('../test', repo)}")
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Get Session Context for AI Agent.
4
+
5
+ Usage:
6
+ python3 get_context.py Output context in text format
7
+ python3 get_context.py --json Output context in JSON format
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from common.git_context import main
13
+
14
+
15
+ if __name__ == "__main__":
16
+ main()
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Get current developer name.
4
+
5
+ This is a wrapper that uses common/paths.py
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import sys
11
+
12
+ from common.paths import get_developer
13
+
14
+
15
+ def main() -> None:
16
+ """CLI entry point."""
17
+ developer = get_developer()
18
+ if developer:
19
+ print(developer)
20
+ else:
21
+ print("Developer not initialized", file=sys.stderr)
22
+ sys.exit(1)
23
+
24
+
25
+ if __name__ == "__main__":
26
+ main()
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Initialize developer for workflow.
4
+
5
+ Usage:
6
+ python3 init_developer.py <developer-name>
7
+
8
+ This creates:
9
+ - .cowork-flow/.developer file with developer info
10
+ - .cowork-flow/workspace/<name>/ directory structure
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import sys
16
+
17
+ from common.paths import (
18
+ DIR_WORKFLOW,
19
+ FILE_DEVELOPER,
20
+ get_developer,
21
+ )
22
+ from common.developer import init_developer
23
+
24
+
25
+ def main() -> None:
26
+ """CLI entry point."""
27
+ if len(sys.argv) < 2:
28
+ print(f"Usage: {sys.argv[0]} <developer-name>")
29
+ print()
30
+ print("Example:")
31
+ print(f" {sys.argv[0]} john")
32
+ sys.exit(1)
33
+
34
+ name = sys.argv[1]
35
+
36
+ # Check if already initialized
37
+ existing = get_developer()
38
+ if existing:
39
+ print(f"Developer already initialized: {existing}")
40
+ print()
41
+ print(f"To reinitialize, remove {DIR_WORKFLOW}/{FILE_DEVELOPER} first")
42
+ sys.exit(0)
43
+
44
+ if init_developer(name):
45
+ sys.exit(0)
46
+ else:
47
+ sys.exit(1)
48
+
49
+
50
+ if __name__ == "__main__":
51
+ main()