agentsys 5.0.2 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (264) hide show
  1. package/.claude-plugin/marketplace.json +21 -14
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/AGENTS.md +2 -1
  4. package/CHANGELOG.md +24 -1
  5. package/README.md +7 -6
  6. package/adapters/codex/skills/agnix/SKILL.md +0 -1
  7. package/adapters/codex/skills/audit-project/SKILL.md +0 -1
  8. package/adapters/codex/skills/audit-project-agents/SKILL.md +0 -1
  9. package/adapters/codex/skills/audit-project-github/SKILL.md +0 -1
  10. package/adapters/codex/skills/consult/SKILL.md +133 -59
  11. package/adapters/codex/skills/debate/SKILL.md +214 -0
  12. package/adapters/codex/skills/delivery-approval/SKILL.md +0 -1
  13. package/adapters/codex/skills/deslop/SKILL.md +0 -1
  14. package/adapters/codex/skills/drift-detect/SKILL.md +0 -1
  15. package/adapters/codex/skills/enhance/SKILL.md +0 -1
  16. package/adapters/codex/skills/learn/SKILL.md +0 -1
  17. package/adapters/codex/skills/next-task/SKILL.md +0 -1
  18. package/adapters/codex/skills/perf/SKILL.md +0 -1
  19. package/adapters/codex/skills/repo-map/SKILL.md +0 -1
  20. package/adapters/codex/skills/ship/SKILL.md +0 -1
  21. package/adapters/codex/skills/ship-ci-review-loop/SKILL.md +0 -1
  22. package/adapters/codex/skills/ship-deployment/SKILL.md +0 -1
  23. package/adapters/codex/skills/ship-error-handling/SKILL.md +0 -1
  24. package/adapters/codex/skills/sync-docs/SKILL.md +0 -1
  25. package/adapters/opencode/agents/agent-enhancer.md +0 -1
  26. package/adapters/opencode/agents/agnix-agent.md +0 -1
  27. package/adapters/opencode/agents/ci-fixer.md +0 -1
  28. package/adapters/opencode/agents/ci-monitor.md +0 -1
  29. package/adapters/opencode/agents/claudemd-enhancer.md +0 -1
  30. package/adapters/opencode/agents/consult-agent.md +123 -31
  31. package/adapters/opencode/agents/cross-file-enhancer.md +0 -1
  32. package/adapters/opencode/agents/debate-orchestrator.md +169 -0
  33. package/adapters/opencode/agents/delivery-validator.md +0 -1
  34. package/adapters/opencode/agents/deslop-agent.md +0 -1
  35. package/adapters/opencode/agents/docs-enhancer.md +0 -1
  36. package/adapters/opencode/agents/exploration-agent.md +0 -1
  37. package/adapters/opencode/agents/hooks-enhancer.md +0 -1
  38. package/adapters/opencode/agents/implementation-agent.md +0 -1
  39. package/adapters/opencode/agents/learn-agent.md +0 -1
  40. package/adapters/opencode/agents/map-validator.md +0 -1
  41. package/adapters/opencode/agents/perf-analyzer.md +0 -1
  42. package/adapters/opencode/agents/perf-code-paths.md +0 -1
  43. package/adapters/opencode/agents/perf-investigation-logger.md +0 -1
  44. package/adapters/opencode/agents/perf-orchestrator.md +0 -1
  45. package/adapters/opencode/agents/perf-theory-gatherer.md +0 -1
  46. package/adapters/opencode/agents/perf-theory-tester.md +0 -1
  47. package/adapters/opencode/agents/plan-synthesizer.md +0 -1
  48. package/adapters/opencode/agents/planning-agent.md +0 -1
  49. package/adapters/opencode/agents/plugin-enhancer.md +0 -1
  50. package/adapters/opencode/agents/prompt-enhancer.md +0 -1
  51. package/adapters/opencode/agents/simple-fixer.md +0 -1
  52. package/adapters/opencode/agents/skills-enhancer.md +0 -1
  53. package/adapters/opencode/agents/sync-docs-agent.md +0 -1
  54. package/adapters/opencode/agents/task-discoverer.md +0 -1
  55. package/adapters/opencode/agents/test-coverage-checker.md +0 -1
  56. package/adapters/opencode/agents/worktree-manager.md +0 -1
  57. package/adapters/opencode/commands/agnix.md +0 -1
  58. package/adapters/opencode/commands/audit-project-agents.md +0 -1
  59. package/adapters/opencode/commands/audit-project-github.md +0 -1
  60. package/adapters/opencode/commands/audit-project.md +0 -1
  61. package/adapters/opencode/commands/consult.md +134 -59
  62. package/adapters/opencode/commands/debate.md +224 -0
  63. package/adapters/opencode/commands/delivery-approval.md +0 -1
  64. package/adapters/opencode/commands/deslop.md +0 -1
  65. package/adapters/opencode/commands/drift-detect.md +0 -1
  66. package/adapters/opencode/commands/enhance.md +0 -1
  67. package/adapters/opencode/commands/learn.md +0 -1
  68. package/adapters/opencode/commands/next-task.md +0 -1
  69. package/adapters/opencode/commands/perf.md +0 -1
  70. package/adapters/opencode/commands/repo-map.md +0 -1
  71. package/adapters/opencode/commands/ship-ci-review-loop.md +0 -1
  72. package/adapters/opencode/commands/ship-deployment.md +0 -1
  73. package/adapters/opencode/commands/ship-error-handling.md +0 -1
  74. package/adapters/opencode/commands/ship.md +0 -1
  75. package/adapters/opencode/commands/sync-docs.md +0 -1
  76. package/adapters/opencode/skills/agnix/SKILL.md +1 -2
  77. package/adapters/opencode/skills/consult/SKILL.md +41 -27
  78. package/adapters/opencode/skills/debate/SKILL.md +245 -0
  79. package/adapters/opencode/skills/deslop/SKILL.md +1 -2
  80. package/adapters/opencode/skills/discover-tasks/SKILL.md +1 -2
  81. package/adapters/opencode/skills/drift-analysis/SKILL.md +1 -2
  82. package/adapters/opencode/skills/enhance-agent-prompts/SKILL.md +1 -2
  83. package/adapters/opencode/skills/enhance-claude-memory/SKILL.md +1 -2
  84. package/adapters/opencode/skills/enhance-cross-file/SKILL.md +1 -2
  85. package/adapters/opencode/skills/enhance-docs/SKILL.md +1 -2
  86. package/adapters/opencode/skills/enhance-hooks/SKILL.md +1 -2
  87. package/adapters/opencode/skills/enhance-orchestrator/SKILL.md +1 -2
  88. package/adapters/opencode/skills/enhance-plugins/SKILL.md +1 -2
  89. package/adapters/opencode/skills/enhance-prompts/SKILL.md +1 -2
  90. package/adapters/opencode/skills/enhance-skills/SKILL.md +1 -2
  91. package/adapters/opencode/skills/learn/SKILL.md +1 -2
  92. package/adapters/opencode/skills/orchestrate-review/SKILL.md +0 -1
  93. package/adapters/opencode/skills/perf-analyzer/SKILL.md +1 -2
  94. package/adapters/opencode/skills/perf-baseline-manager/SKILL.md +1 -2
  95. package/adapters/opencode/skills/perf-benchmarker/SKILL.md +1 -2
  96. package/adapters/opencode/skills/perf-code-paths/SKILL.md +1 -2
  97. package/adapters/opencode/skills/perf-investigation-logger/SKILL.md +1 -2
  98. package/adapters/opencode/skills/perf-profiler/SKILL.md +1 -2
  99. package/adapters/opencode/skills/perf-theory-gatherer/SKILL.md +1 -2
  100. package/adapters/opencode/skills/perf-theory-tester/SKILL.md +1 -2
  101. package/adapters/opencode/skills/repo-mapping/SKILL.md +1 -2
  102. package/adapters/opencode/skills/sync-docs/SKILL.md +1 -2
  103. package/adapters/opencode/skills/validate-delivery/SKILL.md +1 -2
  104. package/lib/adapter-transforms.js +24 -4
  105. package/package.json +1 -1
  106. package/plugins/agnix/.claude-plugin/plugin.json +1 -1
  107. package/plugins/agnix/skills/agnix/SKILL.md +1 -1
  108. package/plugins/audit-project/.claude-plugin/plugin.json +1 -1
  109. package/plugins/audit-project/lib/adapter-transforms.js +24 -4
  110. package/plugins/consult/.claude-plugin/plugin.json +1 -1
  111. package/plugins/consult/agents/consult-agent.md +123 -30
  112. package/plugins/consult/commands/consult.md +136 -60
  113. package/plugins/consult/skills/consult/SKILL.md +39 -24
  114. package/plugins/debate/.claude-plugin/plugin.json +21 -0
  115. package/plugins/debate/agents/debate-orchestrator.md +175 -0
  116. package/plugins/debate/commands/debate.md +221 -0
  117. package/plugins/debate/lib/adapter-transforms.js +298 -0
  118. package/plugins/debate/lib/collectors/codebase.js +392 -0
  119. package/plugins/debate/lib/collectors/docs-patterns.js +713 -0
  120. package/plugins/debate/lib/collectors/documentation.js +219 -0
  121. package/plugins/debate/lib/collectors/github.js +330 -0
  122. package/plugins/debate/lib/collectors/index.js +126 -0
  123. package/plugins/debate/lib/config/index.js +14 -0
  124. package/plugins/debate/lib/cross-platform/index.js +539 -0
  125. package/plugins/debate/lib/discovery/index.js +352 -0
  126. package/plugins/debate/lib/drift-detect/collectors.js +37 -0
  127. package/plugins/debate/lib/enhance/agent-analyzer.js +421 -0
  128. package/plugins/debate/lib/enhance/agent-patterns.js +571 -0
  129. package/plugins/debate/lib/enhance/auto-suppression.js +622 -0
  130. package/plugins/debate/lib/enhance/benchmark.js +417 -0
  131. package/plugins/debate/lib/enhance/cross-file-analyzer.js +930 -0
  132. package/plugins/debate/lib/enhance/cross-file-patterns.js +370 -0
  133. package/plugins/debate/lib/enhance/docs-analyzer.js +325 -0
  134. package/plugins/debate/lib/enhance/docs-patterns.js +671 -0
  135. package/plugins/debate/lib/enhance/fixer.js +721 -0
  136. package/plugins/debate/lib/enhance/hook-analyzer.js +135 -0
  137. package/plugins/debate/lib/enhance/hook-patterns.js +40 -0
  138. package/plugins/debate/lib/enhance/index.js +127 -0
  139. package/plugins/debate/lib/enhance/plugin-analyzer.js +402 -0
  140. package/plugins/debate/lib/enhance/plugin-patterns.js +326 -0
  141. package/plugins/debate/lib/enhance/projectmemory-analyzer.js +551 -0
  142. package/plugins/debate/lib/enhance/projectmemory-patterns.js +617 -0
  143. package/plugins/debate/lib/enhance/prompt-analyzer.js +457 -0
  144. package/plugins/debate/lib/enhance/prompt-patterns.js +1484 -0
  145. package/plugins/debate/lib/enhance/reporter.js +1348 -0
  146. package/plugins/debate/lib/enhance/security-patterns.js +284 -0
  147. package/plugins/debate/lib/enhance/skill-analyzer.js +182 -0
  148. package/plugins/debate/lib/enhance/skill-patterns.js +147 -0
  149. package/plugins/debate/lib/enhance/suppression.js +352 -0
  150. package/plugins/debate/lib/enhance/tool-patterns.js +373 -0
  151. package/plugins/debate/lib/index.js +270 -0
  152. package/plugins/debate/lib/patterns/cli-enhancers.js +611 -0
  153. package/plugins/debate/lib/patterns/pipeline.js +948 -0
  154. package/plugins/debate/lib/patterns/review-patterns.js +558 -0
  155. package/plugins/debate/lib/patterns/slop-analyzers.js +2305 -0
  156. package/plugins/debate/lib/patterns/slop-patterns.js +1187 -0
  157. package/plugins/debate/lib/perf/analyzer/index.js +22 -0
  158. package/plugins/debate/lib/perf/argument-parser.js +105 -0
  159. package/plugins/debate/lib/perf/baseline-comparator.js +50 -0
  160. package/plugins/debate/lib/perf/baseline-store.js +127 -0
  161. package/plugins/debate/lib/perf/benchmark-runner.js +404 -0
  162. package/plugins/debate/lib/perf/breaking-point-finder.js +52 -0
  163. package/plugins/debate/lib/perf/breaking-point-runner.js +60 -0
  164. package/plugins/debate/lib/perf/checkpoint.js +123 -0
  165. package/plugins/debate/lib/perf/code-paths.js +86 -0
  166. package/plugins/debate/lib/perf/consolidation.js +37 -0
  167. package/plugins/debate/lib/perf/constraint-runner.js +71 -0
  168. package/plugins/debate/lib/perf/experiment-runner.js +32 -0
  169. package/plugins/debate/lib/perf/index.js +41 -0
  170. package/plugins/debate/lib/perf/investigation-state.js +874 -0
  171. package/plugins/debate/lib/perf/optimization-runner.js +79 -0
  172. package/plugins/debate/lib/perf/profilers/go.js +22 -0
  173. package/plugins/debate/lib/perf/profilers/index.js +46 -0
  174. package/plugins/debate/lib/perf/profilers/java.js +23 -0
  175. package/plugins/debate/lib/perf/profilers/node.js +27 -0
  176. package/plugins/debate/lib/perf/profilers/python.js +23 -0
  177. package/plugins/debate/lib/perf/profilers/rust.js +23 -0
  178. package/plugins/debate/lib/perf/profiling-runner.js +75 -0
  179. package/plugins/debate/lib/perf/schemas.js +140 -0
  180. package/plugins/debate/lib/platform/detect-platform.js +413 -0
  181. package/plugins/debate/lib/platform/detection-configs.js +93 -0
  182. package/plugins/debate/lib/platform/state-dir.js +132 -0
  183. package/plugins/debate/lib/platform/verify-tools.js +182 -0
  184. package/plugins/debate/lib/repo-map/cache.js +152 -0
  185. package/plugins/debate/lib/repo-map/concurrency.js +29 -0
  186. package/plugins/debate/lib/repo-map/index.js +222 -0
  187. package/plugins/debate/lib/repo-map/installer.js +212 -0
  188. package/plugins/debate/lib/repo-map/queries/go.js +27 -0
  189. package/plugins/debate/lib/repo-map/queries/index.js +100 -0
  190. package/plugins/debate/lib/repo-map/queries/java.js +38 -0
  191. package/plugins/debate/lib/repo-map/queries/javascript.js +55 -0
  192. package/plugins/debate/lib/repo-map/queries/python.js +24 -0
  193. package/plugins/debate/lib/repo-map/queries/rust.js +73 -0
  194. package/plugins/debate/lib/repo-map/queries/typescript.js +38 -0
  195. package/plugins/debate/lib/repo-map/runner.js +1364 -0
  196. package/plugins/debate/lib/repo-map/updater.js +562 -0
  197. package/plugins/debate/lib/repo-map/usage-analyzer.js +407 -0
  198. package/plugins/debate/lib/schemas/plugin-manifest.schema.json +57 -0
  199. package/plugins/debate/lib/schemas/validator.js +247 -0
  200. package/plugins/debate/lib/sources/custom-handler.js +199 -0
  201. package/plugins/debate/lib/sources/policy-questions.js +246 -0
  202. package/plugins/debate/lib/sources/source-cache.js +165 -0
  203. package/plugins/debate/lib/state/workflow-state.js +576 -0
  204. package/plugins/debate/lib/types/agent-frontmatter.d.ts +134 -0
  205. package/plugins/debate/lib/types/command-frontmatter.d.ts +107 -0
  206. package/plugins/debate/lib/types/hook-frontmatter.d.ts +115 -0
  207. package/plugins/debate/lib/types/index.d.ts +84 -0
  208. package/plugins/debate/lib/types/plugin-manifest.d.ts +102 -0
  209. package/plugins/debate/lib/types/skill-frontmatter.d.ts +89 -0
  210. package/plugins/debate/lib/utils/atomic-write.js +94 -0
  211. package/plugins/debate/lib/utils/cache-manager.js +159 -0
  212. package/plugins/debate/lib/utils/command-parser.js +0 -0
  213. package/plugins/debate/lib/utils/context-optimizer.js +300 -0
  214. package/plugins/debate/lib/utils/deprecation.js +37 -0
  215. package/plugins/debate/lib/utils/shell-escape.js +88 -0
  216. package/plugins/debate/lib/utils/state-helpers.js +61 -0
  217. package/plugins/debate/skills/debate/SKILL.md +264 -0
  218. package/plugins/deslop/.claude-plugin/plugin.json +1 -1
  219. package/plugins/deslop/lib/adapter-transforms.js +24 -4
  220. package/plugins/deslop/skills/deslop/SKILL.md +1 -1
  221. package/plugins/drift-detect/.claude-plugin/plugin.json +1 -1
  222. package/plugins/drift-detect/lib/adapter-transforms.js +24 -4
  223. package/plugins/drift-detect/skills/drift-analysis/SKILL.md +1 -1
  224. package/plugins/enhance/.claude-plugin/plugin.json +1 -1
  225. package/plugins/enhance/lib/adapter-transforms.js +24 -4
  226. package/plugins/enhance/skills/enhance-agent-prompts/SKILL.md +1 -1
  227. package/plugins/enhance/skills/enhance-claude-memory/SKILL.md +1 -1
  228. package/plugins/enhance/skills/enhance-cross-file/SKILL.md +1 -1
  229. package/plugins/enhance/skills/enhance-docs/SKILL.md +1 -1
  230. package/plugins/enhance/skills/enhance-hooks/SKILL.md +1 -1
  231. package/plugins/enhance/skills/enhance-orchestrator/SKILL.md +1 -1
  232. package/plugins/enhance/skills/enhance-plugins/SKILL.md +1 -1
  233. package/plugins/enhance/skills/enhance-prompts/SKILL.md +1 -1
  234. package/plugins/enhance/skills/enhance-skills/SKILL.md +1 -1
  235. package/plugins/learn/.claude-plugin/plugin.json +1 -1
  236. package/plugins/learn/agents/learn-agent.md +1 -1
  237. package/plugins/learn/lib/adapter-transforms.js +24 -4
  238. package/plugins/learn/skills/learn/SKILL.md +1 -1
  239. package/plugins/next-task/.claude-plugin/plugin.json +1 -1
  240. package/plugins/next-task/agents/exploration-agent.md +1 -1
  241. package/plugins/next-task/lib/adapter-transforms.js +24 -4
  242. package/plugins/next-task/skills/discover-tasks/SKILL.md +1 -1
  243. package/plugins/next-task/skills/validate-delivery/SKILL.md +1 -1
  244. package/plugins/perf/.claude-plugin/plugin.json +1 -1
  245. package/plugins/perf/lib/adapter-transforms.js +24 -4
  246. package/plugins/perf/skills/perf-analyzer/SKILL.md +1 -1
  247. package/plugins/perf/skills/perf-baseline-manager/SKILL.md +1 -1
  248. package/plugins/perf/skills/perf-benchmarker/SKILL.md +1 -1
  249. package/plugins/perf/skills/perf-code-paths/SKILL.md +1 -1
  250. package/plugins/perf/skills/perf-investigation-logger/SKILL.md +1 -1
  251. package/plugins/perf/skills/perf-profiler/SKILL.md +1 -1
  252. package/plugins/perf/skills/perf-theory-gatherer/SKILL.md +1 -1
  253. package/plugins/perf/skills/perf-theory-tester/SKILL.md +1 -1
  254. package/plugins/repo-map/.claude-plugin/plugin.json +1 -1
  255. package/plugins/repo-map/lib/adapter-transforms.js +24 -4
  256. package/plugins/ship/.claude-plugin/plugin.json +1 -1
  257. package/plugins/ship/lib/adapter-transforms.js +24 -4
  258. package/plugins/sync-docs/.claude-plugin/plugin.json +1 -1
  259. package/plugins/sync-docs/lib/adapter-transforms.js +24 -4
  260. package/plugins/sync-docs/skills/sync-docs/SKILL.md +1 -1
  261. package/scripts/gen-adapters.js +6 -7
  262. package/scripts/generate-docs.js +4 -2
  263. package/scripts/plugins.txt +1 -0
  264. package/site/content.json +6 -6
@@ -0,0 +1,576 @@
1
+ /**
2
+ * Simplified workflow state management
3
+ *
4
+ * Two files:
5
+ * - Main project: {stateDir}/tasks.json (tracks active worktree/task)
6
+ * - Worktree: {stateDir}/flow.json (tracks workflow progress)
7
+ *
8
+ * State directory is platform-aware:
9
+ * - Claude Code: .claude/
10
+ * - OpenCode: .opencode/
11
+ * - Codex CLI: .codex/
12
+ */
13
+
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+ const crypto = require('crypto');
17
+ const { getStateDir } = require('../platform/state-dir');
18
+ const { writeJsonAtomic } = require('../utils/atomic-write');
19
+ const { isPlainObject, updatesApplied, sleepForRetry } = require('../utils/state-helpers');
20
+
21
+ // File paths
22
+ const TASKS_FILE = 'tasks.json';
23
+ const FLOW_FILE = 'flow.json';
24
+ /**
25
+ * Validate and resolve path to prevent path traversal attacks
26
+ * @param {string} basePath - Base directory path
27
+ * @returns {string} Validated absolute path
28
+ * @throws {Error} If path is invalid
29
+ */
30
+ function validatePath(basePath) {
31
+ if (typeof basePath !== 'string' || basePath.length === 0) {
32
+ throw new Error('Path must be a non-empty string');
33
+ }
34
+ const resolved = path.resolve(basePath);
35
+ if (resolved.includes('\0')) {
36
+ throw new Error('Path contains invalid null byte');
37
+ }
38
+ return resolved;
39
+ }
40
+
41
+ /**
42
+ * Validate that target path is within base directory
43
+ * @param {string} targetPath - Target file path
44
+ * @param {string} basePath - Base directory
45
+ * @throws {Error} If path traversal detected
46
+ */
47
+ function validatePathWithinBase(targetPath, basePath) {
48
+ const resolvedTarget = path.resolve(targetPath);
49
+ const resolvedBase = path.resolve(basePath);
50
+ if (!resolvedTarget.startsWith(resolvedBase + path.sep) && resolvedTarget !== resolvedBase) {
51
+ throw new Error('Path traversal detected');
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Generate a unique workflow ID
57
+ * @returns {string} Workflow ID
58
+ */
59
+ function generateWorkflowId() {
60
+ const now = new Date();
61
+ const date = now.toISOString().slice(0, 10).replace(/-/g, '');
62
+ const time = now.toISOString().slice(11, 19).replace(/:/g, '');
63
+ const random = crypto.randomBytes(4).toString('hex');
64
+ return `workflow-${date}-${time}-${random}`;
65
+ }
66
+
67
+ // Valid phases for the workflow
68
+ const PHASES = [
69
+ 'policy-selection',
70
+ 'task-discovery',
71
+ 'worktree-setup',
72
+ 'exploration',
73
+ 'planning',
74
+ 'user-approval',
75
+ 'implementation',
76
+ 'review-loop',
77
+ 'delivery-validation',
78
+ 'shipping',
79
+ 'complete'
80
+ ];
81
+
82
+ /**
83
+ * Ensure state directory exists (platform-aware)
84
+ */
85
+ function ensureStateDir(basePath) {
86
+ const stateDir = path.join(basePath, getStateDir(basePath));
87
+ if (!fs.existsSync(stateDir)) {
88
+ fs.mkdirSync(stateDir, { recursive: true });
89
+ }
90
+ return stateDir;
91
+ }
92
+
93
+ // =============================================================================
94
+ // TASKS.JSON - Main project directory
95
+ // =============================================================================
96
+
97
+ /**
98
+ * Get path to tasks.json with validation
99
+ */
100
+ function getTasksPath(projectPath = process.cwd()) {
101
+ const validatedBase = validatePath(projectPath);
102
+ const tasksPath = path.join(validatedBase, getStateDir(projectPath), TASKS_FILE);
103
+ validatePathWithinBase(tasksPath, validatedBase);
104
+ return tasksPath;
105
+ }
106
+
107
+ /**
108
+ * Read tasks.json from main project
109
+ * Returns { active: null } if file doesn't exist or is corrupted
110
+ * Logs critical error on corruption to prevent silent data loss
111
+ */
112
+ function readTasks(projectPath = process.cwd()) {
113
+ const tasksPath = getTasksPath(projectPath);
114
+ if (!fs.existsSync(tasksPath)) {
115
+ return { active: null };
116
+ }
117
+ try {
118
+ const data = JSON.parse(fs.readFileSync(tasksPath, 'utf8'));
119
+ // Normalize legacy format that may not have 'active' field
120
+ if (!Object.prototype.hasOwnProperty.call(data, 'active')) {
121
+ return { active: null };
122
+ }
123
+ return data;
124
+ } catch (e) {
125
+ console.error(`[CRITICAL] Corrupted tasks.json at ${tasksPath}: ${e.message}`);
126
+ return { active: null };
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Write tasks.json to main project
132
+ */
133
+ function writeTasks(tasks, projectPath = process.cwd()) {
134
+ ensureStateDir(projectPath);
135
+ const tasksPath = getTasksPath(projectPath);
136
+ writeJsonAtomic(tasksPath, tasks);
137
+ return true;
138
+ }
139
+
140
+ /**
141
+ * Set active task in main project
142
+ */
143
+ function setActiveTask(task, projectPath = process.cwd()) {
144
+ const tasks = readTasks(projectPath);
145
+ tasks.active = {
146
+ ...task,
147
+ startedAt: new Date().toISOString()
148
+ };
149
+ return writeTasks(tasks, projectPath);
150
+ }
151
+
152
+ /**
153
+ * Clear active task
154
+ */
155
+ function clearActiveTask(projectPath = process.cwd()) {
156
+ const tasks = readTasks(projectPath);
157
+ tasks.active = null;
158
+ return writeTasks(tasks, projectPath);
159
+ }
160
+
161
+ /**
162
+ * Check if there's an active task
163
+ * Uses != null to catch both null and undefined (legacy format safety)
164
+ */
165
+ function hasActiveTask(projectPath = process.cwd()) {
166
+ const tasks = readTasks(projectPath);
167
+ return tasks.active != null;
168
+ }
169
+
170
+ // =============================================================================
171
+ // FLOW.JSON - Worktree directory
172
+ // =============================================================================
173
+
174
+ /**
175
+ * Get path to flow.json with validation
176
+ */
177
+ function getFlowPath(worktreePath = process.cwd()) {
178
+ const validatedBase = validatePath(worktreePath);
179
+ const flowPath = path.join(validatedBase, getStateDir(worktreePath), FLOW_FILE);
180
+ validatePathWithinBase(flowPath, validatedBase);
181
+ return flowPath;
182
+ }
183
+
184
+ /**
185
+ * Read flow.json from worktree
186
+ * Returns null if file doesn't exist or is corrupted
187
+ * Logs critical error on corruption to prevent silent data loss
188
+ */
189
+ function readFlow(worktreePath = process.cwd()) {
190
+ const flowPath = getFlowPath(worktreePath);
191
+ if (!fs.existsSync(flowPath)) {
192
+ return null;
193
+ }
194
+ try {
195
+ return JSON.parse(fs.readFileSync(flowPath, 'utf8'));
196
+ } catch (e) {
197
+ console.error(`[CRITICAL] Corrupted flow.json at ${flowPath}: ${e.message}`);
198
+ return null;
199
+ }
200
+ }
201
+
202
+ /**
203
+ * Write flow.json to worktree
204
+ * Creates a copy to avoid mutating the original object
205
+ * Increments version for optimistic locking
206
+ */
207
+ function writeFlow(flow, worktreePath = process.cwd()) {
208
+ ensureStateDir(worktreePath);
209
+ // Clone to avoid mutating the original object
210
+ const flowCopy = structuredClone(flow);
211
+ flowCopy.lastUpdate = new Date().toISOString();
212
+ // Increment version for optimistic locking (initialize if missing)
213
+ flowCopy._version = (flowCopy._version || 0) + 1;
214
+ const flowPath = getFlowPath(worktreePath);
215
+ writeJsonAtomic(flowPath, flowCopy);
216
+ return true;
217
+ }
218
+
219
+ /**
220
+ * Update flow.json with partial updates
221
+ * Handles null values correctly (null overwrites existing values)
222
+ * Deep merges nested objects when both exist
223
+ * Uses optimistic locking with version check and retry
224
+ */
225
+ function updateFlow(updates, worktreePath = process.cwd()) {
226
+ const MAX_RETRIES = 5;
227
+
228
+ for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
229
+ const flow = readFlow(worktreePath) || {};
230
+ const initialVersion = flow._version || 0;
231
+
232
+ for (const [key, value] of Object.entries(updates)) {
233
+ // Skip internal version field from updates
234
+ if (key === '_version') continue;
235
+
236
+ // Null explicitly overwrites
237
+ if (value === null) {
238
+ flow[key] = null;
239
+ }
240
+ // Deep merge if both source and target are non-null objects
241
+ else if (isPlainObject(value) && isPlainObject(flow[key])) {
242
+ flow[key] = { ...flow[key], ...value };
243
+ }
244
+ // Otherwise direct assignment
245
+ else {
246
+ flow[key] = value;
247
+ }
248
+ }
249
+
250
+ // Preserve version for write (writeFlow will increment it)
251
+ flow._version = initialVersion;
252
+
253
+ // Write and verify version wasn't changed by another process
254
+ writeFlow(flow, worktreePath);
255
+
256
+ // Re-read to verify our write succeeded
257
+ const afterWrite = readFlow(worktreePath);
258
+ if (afterWrite && afterWrite._version >= initialVersion + 1 && updatesApplied(afterWrite, updates)) {
259
+ return true; // Success
260
+ }
261
+
262
+ // Version conflict or overwrite - retry after brief delay
263
+ if (attempt < MAX_RETRIES - 1) {
264
+ const delay = Math.floor(Math.random() * 50) + 10;
265
+ sleepForRetry(delay);
266
+ }
267
+ }
268
+
269
+ // All retries exhausted. One final read can detect if another writer
270
+ // applied the same updates while we were retrying.
271
+ const latest = readFlow(worktreePath);
272
+ if (latest && updatesApplied(latest, updates)) {
273
+ return true;
274
+ }
275
+
276
+ console.error('[ERROR] updateFlow: failed to apply updates after max retries');
277
+ return false;
278
+ }
279
+
280
+ /**
281
+ * Create initial flow for a new task
282
+ * Also registers the task as active in the main project's tasks.json
283
+ * @param {Object} task - Task object with id, title, source, url
284
+ * @param {Object} policy - Policy object with stoppingPoint
285
+ * @param {string} worktreePath - Path to worktree
286
+ * @param {string} projectPath - Path to main project (for tasks.json registration)
287
+ */
288
+ function createFlow(task, policy, worktreePath = process.cwd(), projectPath = null) {
289
+ const flow = {
290
+ task: {
291
+ id: task.id,
292
+ title: task.title,
293
+ source: task.source,
294
+ url: task.url || null
295
+ },
296
+ policy: {
297
+ stoppingPoint: policy.stoppingPoint || 'merged'
298
+ },
299
+ phase: 'policy-selection',
300
+ status: 'in_progress',
301
+ lastUpdate: new Date().toISOString(),
302
+ userNotes: '',
303
+ git: {
304
+ branch: null,
305
+ baseBranch: 'main'
306
+ },
307
+ pr: null,
308
+ exploration: null,
309
+ plan: null,
310
+ // Store projectPath so completeWorkflow knows where to clear the task
311
+ projectPath: projectPath
312
+ };
313
+
314
+ writeFlow(flow, worktreePath);
315
+
316
+ // Register task as active in main project
317
+ if (projectPath) {
318
+ setActiveTask({
319
+ taskId: task.id,
320
+ title: task.title,
321
+ worktree: worktreePath,
322
+ branch: flow.git.branch
323
+ }, projectPath);
324
+ }
325
+
326
+ return flow;
327
+ }
328
+
329
+ /**
330
+ * Delete flow.json
331
+ */
332
+ function deleteFlow(worktreePath = process.cwd()) {
333
+ const flowPath = getFlowPath(worktreePath);
334
+ if (fs.existsSync(flowPath)) {
335
+ fs.unlinkSync(flowPath);
336
+ return true;
337
+ }
338
+ return false;
339
+ }
340
+
341
+ // =============================================================================
342
+ // PHASE MANAGEMENT
343
+ // =============================================================================
344
+
345
+ /**
346
+ * Check if phase is valid
347
+ */
348
+ function isValidPhase(phase) {
349
+ return PHASES.includes(phase);
350
+ }
351
+
352
+ /**
353
+ * Set current phase
354
+ */
355
+ function setPhase(phase, worktreePath = process.cwd()) {
356
+ if (!isValidPhase(phase)) {
357
+ throw new Error(`Invalid phase: ${phase}`);
358
+ }
359
+ return updateFlow({ phase, status: 'in_progress' }, worktreePath);
360
+ }
361
+
362
+ /**
363
+ * Start a phase (alias for setPhase, for backwards compatibility)
364
+ */
365
+ function startPhase(phase, worktreePath = process.cwd()) {
366
+ return setPhase(phase, worktreePath);
367
+ }
368
+
369
+ /**
370
+ * Fail the current phase
371
+ */
372
+ function failPhase(reason, context = {}, worktreePath = process.cwd()) {
373
+ const flow = readFlow(worktreePath);
374
+ if (!flow) return null;
375
+
376
+ return updateFlow({
377
+ status: 'failed',
378
+ error: reason,
379
+ failContext: context
380
+ }, worktreePath);
381
+ }
382
+
383
+ /**
384
+ * Skip to a specific phase
385
+ */
386
+ function skipToPhase(phase, reason = 'manual skip', worktreePath = process.cwd()) {
387
+ if (!isValidPhase(phase)) {
388
+ throw new Error(`Invalid phase: ${phase}`);
389
+ }
390
+ return updateFlow({
391
+ phase,
392
+ status: 'in_progress',
393
+ skipReason: reason
394
+ }, worktreePath);
395
+ }
396
+
397
+ /**
398
+ * Complete current phase and move to next
399
+ * Uses updateFlow pattern to avoid direct mutation issues
400
+ */
401
+ function completePhase(result = null, worktreePath = process.cwd()) {
402
+ const flow = readFlow(worktreePath);
403
+ if (!flow) return null;
404
+
405
+ const currentIndex = PHASES.indexOf(flow.phase);
406
+ const nextPhase = PHASES[currentIndex + 1] || 'complete';
407
+
408
+ // Build updates object
409
+ const updates = {
410
+ phase: nextPhase,
411
+ status: nextPhase === 'complete' ? 'completed' : 'in_progress'
412
+ };
413
+
414
+ // Store result in appropriate field
415
+ if (result) {
416
+ const resultField = getResultField(flow.phase);
417
+ if (resultField) {
418
+ updates[resultField] = result;
419
+ }
420
+ }
421
+
422
+ const updated = updateFlow(updates, worktreePath);
423
+ return updated ? readFlow(worktreePath) : null;
424
+ }
425
+
426
+ /**
427
+ * Map phase to result field
428
+ */
429
+ function getResultField(phase) {
430
+ const mapping = {
431
+ 'exploration': 'exploration',
432
+ 'planning': 'plan',
433
+ 'review-loop': 'reviewResult'
434
+ };
435
+ return mapping[phase] || null;
436
+ }
437
+
438
+ /**
439
+ * Mark workflow as failed
440
+ */
441
+ function failWorkflow(error, worktreePath = process.cwd()) {
442
+ return updateFlow({
443
+ status: 'failed',
444
+ error: error?.message || String(error)
445
+ }, worktreePath);
446
+ }
447
+
448
+ /**
449
+ * Mark workflow as complete
450
+ * Automatically clears the active task from tasks.json using stored projectPath
451
+ * @param {string} worktreePath - Path to worktree
452
+ */
453
+ function completeWorkflow(worktreePath = process.cwd()) {
454
+ const flow = readFlow(worktreePath);
455
+
456
+ const updated = updateFlow({
457
+ phase: 'complete',
458
+ status: 'completed',
459
+ completedAt: new Date().toISOString()
460
+ }, worktreePath);
461
+
462
+ if (updated && flow && flow.projectPath) {
463
+ clearActiveTask(flow.projectPath);
464
+ }
465
+
466
+ return updated;
467
+ }
468
+
469
+ /**
470
+ * Abort workflow
471
+ * Also clears the active task from tasks.json using stored projectPath
472
+ */
473
+ function abortWorkflow(reason, worktreePath = process.cwd()) {
474
+ const flow = readFlow(worktreePath);
475
+
476
+ const updated = updateFlow({
477
+ status: 'aborted',
478
+ abortReason: reason,
479
+ abortedAt: new Date().toISOString()
480
+ }, worktreePath);
481
+
482
+ if (updated && flow && flow.projectPath) {
483
+ clearActiveTask(flow.projectPath);
484
+ }
485
+
486
+ return updated;
487
+ }
488
+
489
+ // =============================================================================
490
+ // CONVENIENCE FUNCTIONS
491
+ // =============================================================================
492
+
493
+ /**
494
+ * Get workflow summary for display
495
+ */
496
+ function getFlowSummary(worktreePath = process.cwd()) {
497
+ const flow = readFlow(worktreePath);
498
+ if (!flow) return null;
499
+
500
+ return {
501
+ task: flow.task?.title || 'Unknown',
502
+ taskId: flow.task?.id,
503
+ phase: flow.phase,
504
+ status: flow.status,
505
+ lastUpdate: flow.lastUpdate,
506
+ pr: flow.pr?.number ? `#${flow.pr.number}` : null
507
+ };
508
+ }
509
+
510
+ /**
511
+ * Check if workflow can be resumed
512
+ */
513
+ function canResume(worktreePath = process.cwd()) {
514
+ const flow = readFlow(worktreePath);
515
+ if (!flow) return false;
516
+ return flow.status === 'in_progress' && flow.phase !== 'complete';
517
+ }
518
+
519
+ // =============================================================================
520
+ // BACKWARDS COMPATIBILITY ALIASES
521
+ // =============================================================================
522
+
523
+ // These maintain compatibility with existing agent code
524
+ const readState = readFlow;
525
+ const writeState = writeFlow;
526
+ const updateState = updateFlow;
527
+ const createState = (type, policy) => createFlow({ id: 'manual', title: 'Manual task', source: 'manual' }, policy);
528
+ const deleteState = deleteFlow;
529
+ const hasActiveWorkflow = hasActiveTask;
530
+ const getWorkflowSummary = getFlowSummary;
531
+
532
+ module.exports = {
533
+ // Constants
534
+ PHASES,
535
+
536
+ // Tasks (main project)
537
+ getTasksPath,
538
+ readTasks,
539
+ writeTasks,
540
+ setActiveTask,
541
+ clearActiveTask,
542
+ hasActiveTask,
543
+
544
+ // Flow (worktree)
545
+ getFlowPath,
546
+ readFlow,
547
+ writeFlow,
548
+ updateFlow,
549
+ createFlow,
550
+ deleteFlow,
551
+
552
+ // Phase management
553
+ isValidPhase,
554
+ setPhase,
555
+ startPhase,
556
+ completePhase,
557
+ failPhase,
558
+ skipToPhase,
559
+ failWorkflow,
560
+ completeWorkflow,
561
+ abortWorkflow,
562
+
563
+ // Convenience
564
+ getFlowSummary,
565
+ canResume,
566
+ generateWorkflowId,
567
+
568
+ // Backwards compatibility
569
+ readState,
570
+ writeState,
571
+ updateState,
572
+ createState,
573
+ deleteState,
574
+ hasActiveWorkflow,
575
+ getWorkflowSummary
576
+ };
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Agent Frontmatter Type Definitions
3
+ * Defines the structure of YAML frontmatter in agent markdown files
4
+ *
5
+ * @module lib/types/agent-frontmatter
6
+ * @author Avi Fenesh
7
+ * @license MIT
8
+ */
9
+
10
+ /**
11
+ * Tool permission for agent
12
+ */
13
+ export type AgentTool =
14
+ | 'Bash'
15
+ | 'Bash(git:*)'
16
+ | 'Bash(gh:*)'
17
+ | 'Bash(npm:*)'
18
+ | 'Bash(node:*)'
19
+ | 'Bash(deployment:*)'
20
+ | 'Read'
21
+ | 'Write'
22
+ | 'Edit'
23
+ | 'Glob'
24
+ | 'Grep'
25
+ | 'Task'
26
+ | 'LSP'
27
+ | 'EnterPlanMode'
28
+ | 'ExitPlanMode'
29
+ | 'AskUserQuestion'
30
+ | 'TodoWrite'
31
+ | 'WebFetch'
32
+ | 'WebSearch'
33
+ | string; // Allow custom tools
34
+
35
+ /**
36
+ * Agent color for UI identification
37
+ */
38
+ export type AgentColor =
39
+ | 'blue'
40
+ | 'green'
41
+ | 'purple'
42
+ | 'orange'
43
+ | 'red'
44
+ | 'yellow'
45
+ | 'pink'
46
+ | 'cyan';
47
+
48
+ /**
49
+ * Agent frontmatter structure
50
+ * YAML metadata at the top of agent markdown files
51
+ */
52
+ export interface AgentFrontmatter {
53
+ /** Agent unique identifier (kebab-case) */
54
+ agent: string;
55
+
56
+ /** Short description of agent purpose */
57
+ description: string;
58
+
59
+ /** Tools the agent has access to */
60
+ tools?: AgentTool[];
61
+
62
+ /** Preferred model for this agent (sonnet, opus, haiku) */
63
+ model?: 'sonnet' | 'opus' | 'haiku';
64
+
65
+ /** Maximum number of turns before stopping */
66
+ maxTurns?: number;
67
+
68
+ /** UI color for agent identification */
69
+ color?: AgentColor;
70
+
71
+ /** Whether agent can run in background */
72
+ canRunInBackground?: boolean;
73
+
74
+ /** Whether agent requires user approval before running */
75
+ requiresApproval?: boolean;
76
+
77
+ /** Agent category for organization */
78
+ category?: string;
79
+
80
+ /** Tags for searchability */
81
+ tags?: string[];
82
+
83
+ /** When this agent should be used (triggering conditions) */
84
+ 'when-to-use'?: string[];
85
+
86
+ /** Example usage scenarios */
87
+ examples?: string[];
88
+ }
89
+
90
+ /**
91
+ * Type guard to check if an object is valid AgentFrontmatter
92
+ */
93
+ export function isAgentFrontmatter(obj: unknown): obj is AgentFrontmatter {
94
+ if (typeof obj !== 'object' || obj === null) return false;
95
+ const fm = obj as Partial<AgentFrontmatter>;
96
+
97
+ return (
98
+ typeof fm.agent === 'string' &&
99
+ fm.agent.length > 0 &&
100
+ typeof fm.description === 'string' &&
101
+ fm.description.length > 0
102
+ );
103
+ }
104
+
105
+ /**
106
+ * Validates agent frontmatter
107
+ * @throws {Error} If frontmatter is invalid
108
+ */
109
+ export function validateAgentFrontmatter(
110
+ frontmatter: unknown
111
+ ): asserts frontmatter is AgentFrontmatter {
112
+ if (!isAgentFrontmatter(frontmatter)) {
113
+ throw new Error('Invalid agent frontmatter: missing required fields');
114
+ }
115
+
116
+ // Additional validations
117
+ if (frontmatter.tools && !Array.isArray(frontmatter.tools)) {
118
+ throw new Error('Invalid agent frontmatter: tools must be an array');
119
+ }
120
+
121
+ if (frontmatter.model && !['sonnet', 'opus', 'haiku'].includes(frontmatter.model)) {
122
+ throw new Error('Invalid agent frontmatter: model must be sonnet, opus, or haiku');
123
+ }
124
+
125
+ if (frontmatter.maxTurns !== undefined &&
126
+ (typeof frontmatter.maxTurns !== 'number' || frontmatter.maxTurns < 1)) {
127
+ throw new Error('Invalid agent frontmatter: maxTurns must be a positive number');
128
+ }
129
+
130
+ const validColors: AgentColor[] = ['blue', 'green', 'purple', 'orange', 'red', 'yellow', 'pink', 'cyan'];
131
+ if (frontmatter.color && !validColors.includes(frontmatter.color)) {
132
+ throw new Error(`Invalid agent frontmatter: color must be one of ${validColors.join(', ')}`);
133
+ }
134
+ }