scc-universal 1.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 (271) hide show
  1. package/.claude-plugin/plugin.json +44 -0
  2. package/.cursor/agents/deep-researcher.md +142 -0
  3. package/.cursor/agents/doc-updater.md +219 -0
  4. package/.cursor/agents/eval-runner.md +335 -0
  5. package/.cursor/agents/learning-engine.md +210 -0
  6. package/.cursor/agents/loop-operator.md +245 -0
  7. package/.cursor/agents/refactor-cleaner.md +119 -0
  8. package/.cursor/agents/sf-admin-agent.md +127 -0
  9. package/.cursor/agents/sf-agentforce-agent.md +126 -0
  10. package/.cursor/agents/sf-apex-agent.md +117 -0
  11. package/.cursor/agents/sf-architect.md +426 -0
  12. package/.cursor/agents/sf-aura-reviewer.md +369 -0
  13. package/.cursor/agents/sf-bugfix-agent.md +101 -0
  14. package/.cursor/agents/sf-flow-agent.md +155 -0
  15. package/.cursor/agents/sf-integration-agent.md +141 -0
  16. package/.cursor/agents/sf-lwc-agent.md +123 -0
  17. package/.cursor/agents/sf-review-agent.md +357 -0
  18. package/.cursor/agents/sf-visualforce-reviewer.md +465 -0
  19. package/.cursor/hooks/adapter.js +81 -0
  20. package/.cursor/hooks/after-file-edit.js +26 -0
  21. package/.cursor/hooks/after-mcp-execution.js +12 -0
  22. package/.cursor/hooks/after-shell-execution.js +30 -0
  23. package/.cursor/hooks/after-tab-file-edit.js +12 -0
  24. package/.cursor/hooks/before-mcp-execution.js +11 -0
  25. package/.cursor/hooks/before-read-file.js +13 -0
  26. package/.cursor/hooks/before-shell-execution.js +29 -0
  27. package/.cursor/hooks/before-submit-prompt.js +23 -0
  28. package/.cursor/hooks/pre-compact.js +7 -0
  29. package/.cursor/hooks/session-end.js +10 -0
  30. package/.cursor/hooks/session-start.js +10 -0
  31. package/.cursor/hooks/stop.js +18 -0
  32. package/.cursor/hooks/subagent-start.js +10 -0
  33. package/.cursor/hooks/subagent-stop.js +10 -0
  34. package/.cursor/hooks.json +107 -0
  35. package/.cursor/skills/aside/SKILL.md +115 -0
  36. package/.cursor/skills/checkpoint/SKILL.md +50 -0
  37. package/.cursor/skills/configure-scc/SKILL.md +160 -0
  38. package/.cursor/skills/continuous-agent-loop/SKILL.md +260 -0
  39. package/.cursor/skills/mcp-server-patterns/SKILL.md +142 -0
  40. package/.cursor/skills/model-route/SKILL.md +81 -0
  41. package/.cursor/skills/prompt-optimizer/SKILL.md +366 -0
  42. package/.cursor/skills/refactor-clean/SKILL.md +133 -0
  43. package/.cursor/skills/resume-session/SKILL.md +111 -0
  44. package/.cursor/skills/save-session/SKILL.md +183 -0
  45. package/.cursor/skills/search-first/SKILL.md +140 -0
  46. package/.cursor/skills/security-scan/SKILL.md +142 -0
  47. package/.cursor/skills/sessions/SKILL.md +124 -0
  48. package/.cursor/skills/sf-agentforce-development/SKILL.md +449 -0
  49. package/.cursor/skills/sf-apex-async-patterns/SKILL.md +324 -0
  50. package/.cursor/skills/sf-apex-best-practices/SKILL.md +421 -0
  51. package/.cursor/skills/sf-apex-constraints/SKILL.md +79 -0
  52. package/.cursor/skills/sf-apex-cursor/SKILL.md +336 -0
  53. package/.cursor/skills/sf-apex-enterprise-patterns/SKILL.md +344 -0
  54. package/.cursor/skills/sf-apex-testing/SKILL.md +407 -0
  55. package/.cursor/skills/sf-api-design/SKILL.md +237 -0
  56. package/.cursor/skills/sf-approval-processes/SKILL.md +312 -0
  57. package/.cursor/skills/sf-aura-development/SKILL.md +260 -0
  58. package/.cursor/skills/sf-build-fix/SKILL.md +120 -0
  59. package/.cursor/skills/sf-data-modeling/SKILL.md +274 -0
  60. package/.cursor/skills/sf-debugging/SKILL.md +362 -0
  61. package/.cursor/skills/sf-deployment/SKILL.md +291 -0
  62. package/.cursor/skills/sf-deployment-constraints/SKILL.md +153 -0
  63. package/.cursor/skills/sf-devops-ci-cd/SKILL.md +322 -0
  64. package/.cursor/skills/sf-docs-lookup/SKILL.md +100 -0
  65. package/.cursor/skills/sf-e2e-testing/SKILL.md +321 -0
  66. package/.cursor/skills/sf-experience-cloud/SKILL.md +248 -0
  67. package/.cursor/skills/sf-flow-development/SKILL.md +376 -0
  68. package/.cursor/skills/sf-governor-limits/SKILL.md +319 -0
  69. package/.cursor/skills/sf-harness-audit/SKILL.md +139 -0
  70. package/.cursor/skills/sf-help/SKILL.md +156 -0
  71. package/.cursor/skills/sf-integration/SKILL.md +479 -0
  72. package/.cursor/skills/sf-lwc-constraints/SKILL.md +128 -0
  73. package/.cursor/skills/sf-lwc-development/SKILL.md +302 -0
  74. package/.cursor/skills/sf-lwc-testing/SKILL.md +387 -0
  75. package/.cursor/skills/sf-metadata-management/SKILL.md +285 -0
  76. package/.cursor/skills/sf-platform-events-cdc/SKILL.md +372 -0
  77. package/.cursor/skills/sf-quickstart/SKILL.md +170 -0
  78. package/.cursor/skills/sf-security/SKILL.md +330 -0
  79. package/.cursor/skills/sf-security-constraints/SKILL.md +125 -0
  80. package/.cursor/skills/sf-soql-constraints/SKILL.md +129 -0
  81. package/.cursor/skills/sf-soql-optimization/SKILL.md +353 -0
  82. package/.cursor/skills/sf-tdd-workflow/SKILL.md +332 -0
  83. package/.cursor/skills/sf-testing-constraints/SKILL.md +198 -0
  84. package/.cursor/skills/sf-trigger-constraints/SKILL.md +88 -0
  85. package/.cursor/skills/sf-trigger-frameworks/SKILL.md +343 -0
  86. package/.cursor/skills/sf-visualforce-development/SKILL.md +259 -0
  87. package/.cursor/skills/strategic-compact/SKILL.md +205 -0
  88. package/.cursor/skills/update-docs/SKILL.md +162 -0
  89. package/.cursor/skills/update-platform-docs/SKILL.md +86 -0
  90. package/.cursor-plugin/plugin.json +26 -0
  91. package/LICENSE +21 -0
  92. package/README.md +522 -0
  93. package/agents/deep-researcher.md +145 -0
  94. package/agents/doc-updater.md +222 -0
  95. package/agents/eval-runner.md +340 -0
  96. package/agents/learning-engine.md +211 -0
  97. package/agents/loop-operator.md +247 -0
  98. package/agents/refactor-cleaner.md +122 -0
  99. package/agents/sf-admin-agent.md +131 -0
  100. package/agents/sf-agentforce-agent.md +132 -0
  101. package/agents/sf-apex-agent.md +124 -0
  102. package/agents/sf-architect.md +435 -0
  103. package/agents/sf-aura-reviewer.md +372 -0
  104. package/agents/sf-bugfix-agent.md +105 -0
  105. package/agents/sf-flow-agent.md +159 -0
  106. package/agents/sf-integration-agent.md +146 -0
  107. package/agents/sf-lwc-agent.md +127 -0
  108. package/agents/sf-review-agent.md +366 -0
  109. package/agents/sf-visualforce-reviewer.md +468 -0
  110. package/assets/logo.svg +18 -0
  111. package/docs/ARCHITECTURE.md +133 -0
  112. package/docs/authoring-guide.md +373 -0
  113. package/docs/hook-development.md +578 -0
  114. package/docs/token-optimization.md +139 -0
  115. package/docs/workflow-examples.md +645 -0
  116. package/examples/agentforce-action/README.md +227 -0
  117. package/examples/apex-trigger-handler/README.md +114 -0
  118. package/examples/devops-pipeline/README.md +325 -0
  119. package/examples/flow-automation/README.md +188 -0
  120. package/examples/integration-pattern/README.md +416 -0
  121. package/examples/lwc-component/README.md +180 -0
  122. package/examples/platform-events/README.md +492 -0
  123. package/examples/scratch-org-setup/README.md +138 -0
  124. package/examples/security-audit/README.md +244 -0
  125. package/examples/visualforce-migration/README.md +314 -0
  126. package/hooks/hooks.json +338 -0
  127. package/hooks/memory-persistence/README.md +73 -0
  128. package/manifests/install-modules.json +217 -0
  129. package/manifests/install-profiles.json +17 -0
  130. package/mcp-configs/mcp-servers.json +19 -0
  131. package/package.json +89 -0
  132. package/schemas/hooks.schema.json +123 -0
  133. package/schemas/install-modules.schema.json +76 -0
  134. package/schemas/install-profiles.schema.json +28 -0
  135. package/schemas/install-state.schema.json +73 -0
  136. package/schemas/package-manager.schema.json +18 -0
  137. package/schemas/plugin.schema.json +112 -0
  138. package/schemas/scc-install-config.schema.json +29 -0
  139. package/schemas/state-store.schema.json +111 -0
  140. package/scripts/cli/install-apply.js +170 -0
  141. package/scripts/cli/uninstall.js +193 -0
  142. package/scripts/hooks/check-console-log.js +101 -0
  143. package/scripts/hooks/check-hook-enabled.js +17 -0
  144. package/scripts/hooks/check-platform-docs-age.js +48 -0
  145. package/scripts/hooks/cost-tracker.js +78 -0
  146. package/scripts/hooks/doc-file-warning.js +63 -0
  147. package/scripts/hooks/evaluate-session.js +98 -0
  148. package/scripts/hooks/governor-check.js +220 -0
  149. package/scripts/hooks/learning-observe.sh +206 -0
  150. package/scripts/hooks/mcp-health-check.js +588 -0
  151. package/scripts/hooks/post-bash-build-complete.js +34 -0
  152. package/scripts/hooks/post-bash-pr-created.js +43 -0
  153. package/scripts/hooks/post-edit-console-warn.js +61 -0
  154. package/scripts/hooks/post-edit-format.js +79 -0
  155. package/scripts/hooks/post-edit-typecheck.js +98 -0
  156. package/scripts/hooks/post-write.js +168 -0
  157. package/scripts/hooks/pre-bash-git-push-reminder.js +35 -0
  158. package/scripts/hooks/pre-bash-tmux-reminder.js +47 -0
  159. package/scripts/hooks/pre-compact.js +51 -0
  160. package/scripts/hooks/pre-tool-use.js +163 -0
  161. package/scripts/hooks/pre-write-doc-warn.js +9 -0
  162. package/scripts/hooks/quality-gate.js +251 -0
  163. package/scripts/hooks/run-with-flags-shell.sh +32 -0
  164. package/scripts/hooks/run-with-flags.js +135 -0
  165. package/scripts/hooks/session-end-marker.js +29 -0
  166. package/scripts/hooks/session-end.js +311 -0
  167. package/scripts/hooks/session-start.js +202 -0
  168. package/scripts/hooks/sfdx-scanner-check.js +142 -0
  169. package/scripts/hooks/sfdx-validate.js +119 -0
  170. package/scripts/hooks/stop-hook.js +170 -0
  171. package/scripts/hooks/suggest-compact.js +67 -0
  172. package/scripts/lib/agent-adapter.js +82 -0
  173. package/scripts/lib/apex-analysis.js +194 -0
  174. package/scripts/lib/hook-flags.js +74 -0
  175. package/scripts/lib/install-config.js +73 -0
  176. package/scripts/lib/install-executor.js +363 -0
  177. package/scripts/lib/install-state.js +121 -0
  178. package/scripts/lib/orchestration-session.js +299 -0
  179. package/scripts/lib/package-manager.js +124 -0
  180. package/scripts/lib/project-detect.js +228 -0
  181. package/scripts/lib/schema-validator.js +190 -0
  182. package/scripts/lib/skill-adapter.js +100 -0
  183. package/scripts/lib/state-store.js +376 -0
  184. package/scripts/lib/tmux-worktree-orchestrator.js +598 -0
  185. package/scripts/lib/utils.js +313 -0
  186. package/scripts/scc.js +164 -0
  187. package/skills/_reference/AGENTFORCE_PATTERNS.md +112 -0
  188. package/skills/_reference/APEX_CURSOR.md +159 -0
  189. package/skills/_reference/API_VERSIONS.md +78 -0
  190. package/skills/_reference/APPROVAL_PROCESSES.md +105 -0
  191. package/skills/_reference/ASYNC_PATTERNS.md +163 -0
  192. package/skills/_reference/AURA_COMPONENTS.md +146 -0
  193. package/skills/_reference/DATA_MIGRATION_PATTERNS.md +151 -0
  194. package/skills/_reference/DATA_MODELING.md +124 -0
  195. package/skills/_reference/DEBUGGING_TOOLS.md +140 -0
  196. package/skills/_reference/DEPLOYMENT_CHECKLIST.md +87 -0
  197. package/skills/_reference/DEPRECATIONS.md +79 -0
  198. package/skills/_reference/DOCKER_CI_PATTERNS.md +138 -0
  199. package/skills/_reference/ENTERPRISE_PATTERNS.md +122 -0
  200. package/skills/_reference/EXPERIENCE_CLOUD.md +143 -0
  201. package/skills/_reference/FLOW_PATTERNS.md +113 -0
  202. package/skills/_reference/GOVERNOR_LIMITS.md +77 -0
  203. package/skills/_reference/INTEGRATION_PATTERNS.md +105 -0
  204. package/skills/_reference/LWC_PATTERNS.md +79 -0
  205. package/skills/_reference/METADATA_TYPES.md +115 -0
  206. package/skills/_reference/NAMING_CONVENTIONS.md +84 -0
  207. package/skills/_reference/PACKAGE_DEVELOPMENT.md +150 -0
  208. package/skills/_reference/PLATFORM_EVENTS.md +121 -0
  209. package/skills/_reference/REPORTING_API.md +143 -0
  210. package/skills/_reference/SCRATCH_ORG_PATTERNS.md +126 -0
  211. package/skills/_reference/SECURITY_PATTERNS.md +127 -0
  212. package/skills/_reference/SHARING_MODEL.md +120 -0
  213. package/skills/_reference/SOQL_PATTERNS.md +119 -0
  214. package/skills/_reference/TESTING_STANDARDS.md +96 -0
  215. package/skills/_reference/TRIGGER_PATTERNS.md +114 -0
  216. package/skills/_reference/VISUALFORCE_PATTERNS.md +121 -0
  217. package/skills/aside/SKILL.md +118 -0
  218. package/skills/checkpoint/SKILL.md +53 -0
  219. package/skills/configure-scc/SKILL.md +163 -0
  220. package/skills/continuous-agent-loop/SKILL.md +264 -0
  221. package/skills/mcp-server-patterns/SKILL.md +146 -0
  222. package/skills/model-route/SKILL.md +84 -0
  223. package/skills/prompt-optimizer/SKILL.md +369 -0
  224. package/skills/refactor-clean/SKILL.md +136 -0
  225. package/skills/resume-session/SKILL.md +114 -0
  226. package/skills/save-session/SKILL.md +186 -0
  227. package/skills/search-first/SKILL.md +144 -0
  228. package/skills/security-scan/SKILL.md +146 -0
  229. package/skills/sessions/SKILL.md +127 -0
  230. package/skills/sf-agentforce-development/SKILL.md +450 -0
  231. package/skills/sf-apex-async-patterns/SKILL.md +326 -0
  232. package/skills/sf-apex-best-practices/SKILL.md +425 -0
  233. package/skills/sf-apex-constraints/SKILL.md +81 -0
  234. package/skills/sf-apex-cursor/SKILL.md +338 -0
  235. package/skills/sf-apex-enterprise-patterns/SKILL.md +348 -0
  236. package/skills/sf-apex-testing/SKILL.md +409 -0
  237. package/skills/sf-api-design/SKILL.md +238 -0
  238. package/skills/sf-approval-processes/SKILL.md +315 -0
  239. package/skills/sf-aura-development/SKILL.md +263 -0
  240. package/skills/sf-build-fix/SKILL.md +121 -0
  241. package/skills/sf-data-modeling/SKILL.md +278 -0
  242. package/skills/sf-debugging/SKILL.md +363 -0
  243. package/skills/sf-deployment/SKILL.md +295 -0
  244. package/skills/sf-deployment-constraints/SKILL.md +155 -0
  245. package/skills/sf-devops-ci-cd/SKILL.md +325 -0
  246. package/skills/sf-docs-lookup/SKILL.md +103 -0
  247. package/skills/sf-e2e-testing/SKILL.md +324 -0
  248. package/skills/sf-experience-cloud/SKILL.md +249 -0
  249. package/skills/sf-flow-development/SKILL.md +377 -0
  250. package/skills/sf-governor-limits/SKILL.md +323 -0
  251. package/skills/sf-harness-audit/SKILL.md +142 -0
  252. package/skills/sf-help/SKILL.md +159 -0
  253. package/skills/sf-integration/SKILL.md +483 -0
  254. package/skills/sf-lwc-constraints/SKILL.md +130 -0
  255. package/skills/sf-lwc-development/SKILL.md +303 -0
  256. package/skills/sf-lwc-testing/SKILL.md +388 -0
  257. package/skills/sf-metadata-management/SKILL.md +288 -0
  258. package/skills/sf-platform-events-cdc/SKILL.md +375 -0
  259. package/skills/sf-quickstart/SKILL.md +173 -0
  260. package/skills/sf-security/SKILL.md +334 -0
  261. package/skills/sf-security-constraints/SKILL.md +127 -0
  262. package/skills/sf-soql-constraints/SKILL.md +131 -0
  263. package/skills/sf-soql-optimization/SKILL.md +354 -0
  264. package/skills/sf-tdd-workflow/SKILL.md +336 -0
  265. package/skills/sf-testing-constraints/SKILL.md +200 -0
  266. package/skills/sf-trigger-constraints/SKILL.md +90 -0
  267. package/skills/sf-trigger-frameworks/SKILL.md +347 -0
  268. package/skills/sf-visualforce-development/SKILL.md +260 -0
  269. package/skills/strategic-compact/SKILL.md +208 -0
  270. package/skills/update-docs/SKILL.md +165 -0
  271. package/skills/update-platform-docs/SKILL.md +90 -0
@@ -0,0 +1,598 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { spawnSync } = require('child_process');
6
+
7
+ function slugify(value, fallback = 'worker') {
8
+ const normalized = String(value || '')
9
+ .trim()
10
+ .toLowerCase()
11
+ .replace(/[^a-z0-9]+/g, '-')
12
+ .replace(/^-+|-+$/g, '');
13
+ return normalized || fallback;
14
+ }
15
+
16
+ function renderTemplate(template, variables) {
17
+ if (typeof template !== 'string' || template.trim().length === 0) {
18
+ throw new Error('launcherCommand must be a non-empty string');
19
+ }
20
+
21
+ return template.replace(/\{([a-z_]+)\}/g, (match, key) => {
22
+ if (!(key in variables)) {
23
+ throw new Error(`Unknown template variable: ${key}`);
24
+ }
25
+ return String(variables[key]);
26
+ });
27
+ }
28
+
29
+ function shellQuote(value) {
30
+ return `'${String(value).replace(/'/g, `'\\''`)}'`;
31
+ }
32
+
33
+ function formatCommand(program, args) {
34
+ return [program, ...args.map(shellQuote)].join(' ');
35
+ }
36
+
37
+ function buildTemplateVariables(values) {
38
+ return Object.entries(values).reduce((accumulator, [key, value]) => {
39
+ const stringValue = String(value);
40
+ const quotedValue = shellQuote(stringValue);
41
+
42
+ accumulator[key] = stringValue;
43
+ accumulator[`${key}_raw`] = stringValue;
44
+ accumulator[`${key}_sh`] = quotedValue;
45
+ return accumulator;
46
+ }, {});
47
+ }
48
+
49
+ function buildSessionBannerCommand(sessionName, coordinationDir) {
50
+ return `printf '%s\\n' ${shellQuote(`Session: ${sessionName}`)} ${shellQuote(`Coordination: ${coordinationDir}`)}`;
51
+ }
52
+
53
+ function normalizeSeedPaths(seedPaths, repoRoot) {
54
+ const resolvedRepoRoot = path.resolve(repoRoot);
55
+ const entries = Array.isArray(seedPaths) ? seedPaths : [];
56
+ const seen = new Set();
57
+ const normalized = [];
58
+
59
+ for (const entry of entries) {
60
+ if (typeof entry !== 'string' || entry.trim().length === 0) {
61
+ continue;
62
+ }
63
+
64
+ const absolutePath = path.resolve(resolvedRepoRoot, entry);
65
+ const relativePath = path.relative(resolvedRepoRoot, absolutePath);
66
+
67
+ if (
68
+ relativePath.startsWith('..') ||
69
+ path.isAbsolute(relativePath)
70
+ ) {
71
+ throw new Error(`seedPaths entries must stay inside repoRoot: ${entry}`);
72
+ }
73
+
74
+ const normalizedPath = relativePath.split(path.sep).join('/');
75
+ if (seen.has(normalizedPath)) {
76
+ continue;
77
+ }
78
+
79
+ seen.add(normalizedPath);
80
+ normalized.push(normalizedPath);
81
+ }
82
+
83
+ return normalized;
84
+ }
85
+
86
+ function overlaySeedPaths({ repoRoot, seedPaths, worktreePath }) {
87
+ const normalizedSeedPaths = normalizeSeedPaths(seedPaths, repoRoot);
88
+
89
+ for (const seedPath of normalizedSeedPaths) {
90
+ const sourcePath = path.join(repoRoot, seedPath);
91
+ const destinationPath = path.join(worktreePath, seedPath);
92
+
93
+ if (!fs.existsSync(sourcePath)) {
94
+ throw new Error(`Seed path does not exist in repoRoot: ${seedPath}`);
95
+ }
96
+
97
+ fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
98
+ fs.rmSync(destinationPath, { force: true, recursive: true });
99
+ fs.cpSync(sourcePath, destinationPath, {
100
+ dereference: false,
101
+ force: true,
102
+ preserveTimestamps: true,
103
+ recursive: true
104
+ });
105
+ }
106
+ }
107
+
108
+ function buildWorkerArtifacts(workerPlan) {
109
+ const seededPathsSection = workerPlan.seedPaths.length > 0
110
+ ? [
111
+ '',
112
+ '## Seeded Local Overlays',
113
+ ...workerPlan.seedPaths.map(seedPath => `- \`${seedPath}\``)
114
+ ]
115
+ : [];
116
+
117
+ return {
118
+ dir: workerPlan.coordinationDir,
119
+ files: [
120
+ {
121
+ path: workerPlan.taskFilePath,
122
+ content: [
123
+ `# Worker Task: ${workerPlan.workerName}`,
124
+ '',
125
+ `- Session: \`${workerPlan.sessionName}\``,
126
+ `- Repo root: \`${workerPlan.repoRoot}\``,
127
+ `- Worktree: \`${workerPlan.worktreePath}\``,
128
+ `- Branch: \`${workerPlan.branchName}\``,
129
+ `- Launcher status file: \`${workerPlan.statusFilePath}\``,
130
+ `- Launcher handoff file: \`${workerPlan.handoffFilePath}\``,
131
+ ...seededPathsSection,
132
+ '',
133
+ '## Objective',
134
+ workerPlan.task,
135
+ '',
136
+ '## Completion',
137
+ 'Do not spawn subagents or external agents for this task.',
138
+ 'Report results in your final response.',
139
+ `The worker launcher captures your response in \`${workerPlan.handoffFilePath}\` automatically.`,
140
+ `The worker launcher updates \`${workerPlan.statusFilePath}\` automatically.`
141
+ ].join('\n')
142
+ },
143
+ {
144
+ path: workerPlan.handoffFilePath,
145
+ content: [
146
+ `# Handoff: ${workerPlan.workerName}`,
147
+ '',
148
+ '## Summary',
149
+ '- Pending',
150
+ '',
151
+ '## Files Changed',
152
+ '- Pending',
153
+ '',
154
+ '## Tests / Verification',
155
+ '- Pending',
156
+ '',
157
+ '## Follow-ups',
158
+ '- Pending'
159
+ ].join('\n')
160
+ },
161
+ {
162
+ path: workerPlan.statusFilePath,
163
+ content: [
164
+ `# Status: ${workerPlan.workerName}`,
165
+ '',
166
+ '- State: not started',
167
+ `- Worktree: \`${workerPlan.worktreePath}\``,
168
+ `- Branch: \`${workerPlan.branchName}\``
169
+ ].join('\n')
170
+ }
171
+ ]
172
+ };
173
+ }
174
+
175
+ function buildOrchestrationPlan(config = {}) {
176
+ const repoRoot = path.resolve(config.repoRoot || process.cwd());
177
+ const repoName = path.basename(repoRoot);
178
+ const workers = Array.isArray(config.workers) ? config.workers : [];
179
+ const globalSeedPaths = normalizeSeedPaths(config.seedPaths, repoRoot);
180
+ const sessionName = slugify(config.sessionName || repoName, 'session');
181
+ const worktreeRoot = path.resolve(config.worktreeRoot || path.dirname(repoRoot));
182
+ const coordinationRoot = path.resolve(
183
+ config.coordinationRoot || path.join(repoRoot, '.orchestration')
184
+ );
185
+ const coordinationDir = path.join(coordinationRoot, sessionName);
186
+ const baseRef = config.baseRef || 'HEAD';
187
+ const defaultLauncher = config.launcherCommand || '';
188
+
189
+ if (workers.length === 0) {
190
+ throw new Error('buildOrchestrationPlan requires at least one worker');
191
+ }
192
+
193
+ const seenSlugs = new Set();
194
+ const workerPlans = workers.map((worker, index) => {
195
+ if (!worker || typeof worker.task !== 'string' || worker.task.trim().length === 0) {
196
+ throw new Error(`Worker ${index + 1} is missing a task`);
197
+ }
198
+
199
+ const workerName = worker.name || `worker-${index + 1}`;
200
+ const workerSlug = slugify(workerName, `worker-${index + 1}`);
201
+
202
+ if (seenSlugs.has(workerSlug)) {
203
+ throw new Error(`Workers must have unique slugs — duplicate: ${workerSlug}`);
204
+ }
205
+ seenSlugs.add(workerSlug);
206
+
207
+ const branchName = `orchestrator-${sessionName}-${workerSlug}`;
208
+ const worktreePath = path.join(worktreeRoot, `${repoName}-${sessionName}-${workerSlug}`);
209
+ const workerCoordinationDir = path.join(coordinationDir, workerSlug);
210
+ const taskFilePath = path.join(workerCoordinationDir, 'task.md');
211
+ const handoffFilePath = path.join(workerCoordinationDir, 'handoff.md');
212
+ const statusFilePath = path.join(workerCoordinationDir, 'status.md');
213
+ const launcherCommand = worker.launcherCommand || defaultLauncher;
214
+ const workerSeedPaths = normalizeSeedPaths(worker.seedPaths, repoRoot);
215
+ const seedPaths = normalizeSeedPaths([...globalSeedPaths, ...workerSeedPaths], repoRoot);
216
+ const templateVariables = buildTemplateVariables({
217
+ branch_name: branchName,
218
+ handoff_file: handoffFilePath,
219
+ repo_root: repoRoot,
220
+ session_name: sessionName,
221
+ status_file: statusFilePath,
222
+ task_file: taskFilePath,
223
+ worker_name: workerName,
224
+ worker_slug: workerSlug,
225
+ worktree_path: worktreePath
226
+ });
227
+
228
+ if (!launcherCommand) {
229
+ throw new Error(`Worker ${workerName} is missing a launcherCommand`);
230
+ }
231
+
232
+ const gitArgs = ['worktree', 'add', '-b', branchName, worktreePath, baseRef];
233
+
234
+ return {
235
+ branchName,
236
+ coordinationDir: workerCoordinationDir,
237
+ gitArgs,
238
+ gitCommand: formatCommand('git', gitArgs),
239
+ handoffFilePath,
240
+ launchCommand: renderTemplate(launcherCommand, templateVariables),
241
+ repoRoot,
242
+ sessionName,
243
+ seedPaths,
244
+ statusFilePath,
245
+ task: worker.task.trim(),
246
+ taskFilePath,
247
+ workerName,
248
+ workerSlug,
249
+ worktreePath
250
+ };
251
+ });
252
+
253
+ const tmuxCommands = [
254
+ {
255
+ cmd: 'tmux',
256
+ args: ['new-session', '-d', '-s', sessionName, '-n', 'orchestrator', '-c', repoRoot],
257
+ description: 'Create detached tmux session'
258
+ },
259
+ {
260
+ cmd: 'tmux',
261
+ args: [
262
+ 'send-keys',
263
+ '-t',
264
+ sessionName,
265
+ buildSessionBannerCommand(sessionName, coordinationDir),
266
+ 'C-m'
267
+ ],
268
+ description: 'Print orchestrator session details'
269
+ }
270
+ ];
271
+
272
+ for (const workerPlan of workerPlans) {
273
+ tmuxCommands.push(
274
+ {
275
+ cmd: 'tmux',
276
+ args: ['split-window', '-d', '-t', sessionName, '-c', workerPlan.worktreePath],
277
+ description: `Create pane for ${workerPlan.workerName}`
278
+ },
279
+ {
280
+ cmd: 'tmux',
281
+ args: ['select-layout', '-t', sessionName, 'tiled'],
282
+ description: 'Arrange panes in tiled layout'
283
+ },
284
+ {
285
+ cmd: 'tmux',
286
+ args: ['select-pane', '-t', '<pane-id>', '-T', workerPlan.workerSlug],
287
+ description: `Label pane ${workerPlan.workerSlug}`
288
+ },
289
+ {
290
+ cmd: 'tmux',
291
+ args: [
292
+ 'send-keys',
293
+ '-t',
294
+ '<pane-id>',
295
+ `cd ${shellQuote(workerPlan.worktreePath)} && ${workerPlan.launchCommand}`,
296
+ 'C-m'
297
+ ],
298
+ description: `Launch worker ${workerPlan.workerName}`
299
+ }
300
+ );
301
+ }
302
+
303
+ return {
304
+ baseRef,
305
+ coordinationDir,
306
+ replaceExisting: Boolean(config.replaceExisting),
307
+ repoRoot,
308
+ sessionName,
309
+ tmuxCommands,
310
+ workerPlans
311
+ };
312
+ }
313
+
314
+ function materializePlan(plan) {
315
+ for (const workerPlan of plan.workerPlans) {
316
+ const artifacts = buildWorkerArtifacts(workerPlan);
317
+ fs.mkdirSync(artifacts.dir, { recursive: true });
318
+ for (const file of artifacts.files) {
319
+ fs.writeFileSync(file.path, file.content + '\n', 'utf8');
320
+ }
321
+ }
322
+ }
323
+
324
+ function runCommand(program, args, options = {}) {
325
+ const result = spawnSync(program, args, {
326
+ cwd: options.cwd,
327
+ encoding: 'utf8',
328
+ stdio: ['ignore', 'pipe', 'pipe']
329
+ });
330
+
331
+ if (result.error) {
332
+ throw result.error;
333
+ }
334
+ if (result.status !== 0) {
335
+ const stderr = (result.stderr || '').trim();
336
+ throw new Error(`${program} ${args.join(' ')} failed${stderr ? `: ${stderr}` : ''}`);
337
+ }
338
+ return result;
339
+ }
340
+
341
+ function commandSucceeds(program, args, options = {}) {
342
+ const result = spawnSync(program, args, {
343
+ cwd: options.cwd,
344
+ encoding: 'utf8',
345
+ stdio: ['ignore', 'pipe', 'pipe']
346
+ });
347
+ return result.status === 0;
348
+ }
349
+
350
+ function canonicalizePath(targetPath) {
351
+ const resolvedPath = path.resolve(targetPath);
352
+
353
+ try {
354
+ return fs.realpathSync.native(resolvedPath);
355
+ } catch (_error) {
356
+ const parentPath = path.dirname(resolvedPath);
357
+
358
+ try {
359
+ return path.join(fs.realpathSync.native(parentPath), path.basename(resolvedPath));
360
+ } catch (_parentError) {
361
+ return resolvedPath;
362
+ }
363
+ }
364
+ }
365
+
366
+ function branchExists(repoRoot, branchName) {
367
+ return commandSucceeds('git', ['show-ref', '--verify', '--quiet', `refs/heads/${branchName}`], {
368
+ cwd: repoRoot
369
+ });
370
+ }
371
+
372
+ function listWorktrees(repoRoot) {
373
+ const listed = runCommand('git', ['worktree', 'list', '--porcelain'], { cwd: repoRoot });
374
+ const lines = (listed.stdout || '').split('\n');
375
+ const worktrees = [];
376
+
377
+ for (const line of lines) {
378
+ if (line.startsWith('worktree ')) {
379
+ const listedPath = line.slice('worktree '.length).trim();
380
+ worktrees.push({
381
+ listedPath,
382
+ canonicalPath: canonicalizePath(listedPath)
383
+ });
384
+ }
385
+ }
386
+
387
+ return worktrees;
388
+ }
389
+
390
+ function cleanupExisting(plan) {
391
+ runCommand('git', ['worktree', 'prune', '--expire', 'now'], { cwd: plan.repoRoot });
392
+
393
+ const hasSession = spawnSync('tmux', ['has-session', '-t', plan.sessionName], {
394
+ encoding: 'utf8',
395
+ stdio: ['ignore', 'pipe', 'pipe']
396
+ });
397
+
398
+ if (hasSession.status === 0) {
399
+ runCommand('tmux', ['kill-session', '-t', plan.sessionName], { cwd: plan.repoRoot });
400
+ }
401
+
402
+ for (const workerPlan of plan.workerPlans) {
403
+ const expectedWorktreePath = canonicalizePath(workerPlan.worktreePath);
404
+ const existingWorktree = listWorktrees(plan.repoRoot).find(
405
+ worktree => worktree.canonicalPath === expectedWorktreePath
406
+ );
407
+
408
+ if (existingWorktree) {
409
+ runCommand('git', ['worktree', 'remove', '--force', existingWorktree.listedPath], {
410
+ cwd: plan.repoRoot
411
+ });
412
+ }
413
+
414
+ if (fs.existsSync(workerPlan.worktreePath)) {
415
+ fs.rmSync(workerPlan.worktreePath, { force: true, recursive: true });
416
+ }
417
+
418
+ runCommand('git', ['worktree', 'prune', '--expire', 'now'], { cwd: plan.repoRoot });
419
+
420
+ if (branchExists(plan.repoRoot, workerPlan.branchName)) {
421
+ runCommand('git', ['branch', '-D', workerPlan.branchName], { cwd: plan.repoRoot });
422
+ }
423
+ }
424
+ }
425
+
426
+ function rollbackCreatedResources(plan, createdState, runtime = {}) {
427
+ const runCommandImpl = runtime.runCommand || runCommand;
428
+ const listWorktreesImpl = runtime.listWorktrees || listWorktrees;
429
+ const branchExistsImpl = runtime.branchExists || branchExists;
430
+ const errors = [];
431
+
432
+ if (createdState.sessionCreated) {
433
+ try {
434
+ runCommandImpl('tmux', ['kill-session', '-t', plan.sessionName], { cwd: plan.repoRoot });
435
+ } catch (error) {
436
+ errors.push(error.message);
437
+ }
438
+ }
439
+
440
+ for (const workerPlan of [...createdState.workerPlans].reverse()) {
441
+ const expectedWorktreePath = canonicalizePath(workerPlan.worktreePath);
442
+ const existingWorktree = listWorktreesImpl(plan.repoRoot).find(
443
+ worktree => worktree.canonicalPath === expectedWorktreePath
444
+ );
445
+
446
+ if (existingWorktree) {
447
+ try {
448
+ runCommandImpl('git', ['worktree', 'remove', '--force', existingWorktree.listedPath], {
449
+ cwd: plan.repoRoot
450
+ });
451
+ } catch (error) {
452
+ errors.push(error.message);
453
+ }
454
+ } else if (fs.existsSync(workerPlan.worktreePath)) {
455
+ fs.rmSync(workerPlan.worktreePath, { force: true, recursive: true });
456
+ }
457
+
458
+ try {
459
+ runCommandImpl('git', ['worktree', 'prune', '--expire', 'now'], { cwd: plan.repoRoot });
460
+ } catch (error) {
461
+ errors.push(error.message);
462
+ }
463
+
464
+ if (branchExistsImpl(plan.repoRoot, workerPlan.branchName)) {
465
+ try {
466
+ runCommandImpl('git', ['branch', '-D', workerPlan.branchName], { cwd: plan.repoRoot });
467
+ } catch (error) {
468
+ errors.push(error.message);
469
+ }
470
+ }
471
+ }
472
+
473
+ if (createdState.removeCoordinationDir && fs.existsSync(plan.coordinationDir)) {
474
+ fs.rmSync(plan.coordinationDir, { force: true, recursive: true });
475
+ }
476
+
477
+ if (errors.length > 0) {
478
+ throw new Error(`rollback failed: ${errors.join('; ')}`);
479
+ }
480
+ }
481
+
482
+ function executePlan(plan, runtime = {}) {
483
+ const spawnSyncImpl = runtime.spawnSync || spawnSync;
484
+ const runCommandImpl = runtime.runCommand || runCommand;
485
+ const materializePlanImpl = runtime.materializePlan || materializePlan;
486
+ const overlaySeedPathsImpl = runtime.overlaySeedPaths || overlaySeedPaths;
487
+ const cleanupExistingImpl = runtime.cleanupExisting || cleanupExisting;
488
+ const rollbackCreatedResourcesImpl = runtime.rollbackCreatedResources || rollbackCreatedResources;
489
+ const createdState = {
490
+ workerPlans: [],
491
+ sessionCreated: false,
492
+ removeCoordinationDir: !fs.existsSync(plan.coordinationDir)
493
+ };
494
+
495
+ runCommandImpl('git', ['rev-parse', '--is-inside-work-tree'], { cwd: plan.repoRoot });
496
+ runCommandImpl('tmux', ['-V']);
497
+
498
+ if (plan.replaceExisting) {
499
+ cleanupExistingImpl(plan);
500
+ } else {
501
+ const hasSession = spawnSyncImpl('tmux', ['has-session', '-t', plan.sessionName], {
502
+ encoding: 'utf8',
503
+ stdio: ['ignore', 'pipe', 'pipe']
504
+ });
505
+ if (hasSession.status === 0) {
506
+ throw new Error(`tmux session already exists: ${plan.sessionName}`);
507
+ }
508
+ }
509
+
510
+ try {
511
+ materializePlanImpl(plan);
512
+
513
+ for (const workerPlan of plan.workerPlans) {
514
+ runCommandImpl('git', workerPlan.gitArgs, { cwd: plan.repoRoot });
515
+ createdState.workerPlans.push(workerPlan);
516
+ overlaySeedPathsImpl({
517
+ repoRoot: plan.repoRoot,
518
+ seedPaths: workerPlan.seedPaths,
519
+ worktreePath: workerPlan.worktreePath
520
+ });
521
+ }
522
+
523
+ runCommandImpl(
524
+ 'tmux',
525
+ ['new-session', '-d', '-s', plan.sessionName, '-n', 'orchestrator', '-c', plan.repoRoot],
526
+ { cwd: plan.repoRoot }
527
+ );
528
+ createdState.sessionCreated = true;
529
+ runCommandImpl(
530
+ 'tmux',
531
+ [
532
+ 'send-keys',
533
+ '-t',
534
+ plan.sessionName,
535
+ buildSessionBannerCommand(plan.sessionName, plan.coordinationDir),
536
+ 'C-m'
537
+ ],
538
+ { cwd: plan.repoRoot }
539
+ );
540
+
541
+ for (const workerPlan of plan.workerPlans) {
542
+ const splitResult = runCommandImpl(
543
+ 'tmux',
544
+ ['split-window', '-d', '-P', '-F', '#{pane_id}', '-t', plan.sessionName, '-c', workerPlan.worktreePath],
545
+ { cwd: plan.repoRoot }
546
+ );
547
+ const paneId = splitResult.stdout.trim();
548
+
549
+ if (!paneId) {
550
+ throw new Error(`tmux split-window did not return a pane id for ${workerPlan.workerName}`);
551
+ }
552
+
553
+ runCommandImpl('tmux', ['select-layout', '-t', plan.sessionName, 'tiled'], { cwd: plan.repoRoot });
554
+ runCommandImpl('tmux', ['select-pane', '-t', paneId, '-T', workerPlan.workerSlug], {
555
+ cwd: plan.repoRoot
556
+ });
557
+ runCommandImpl(
558
+ 'tmux',
559
+ [
560
+ 'send-keys',
561
+ '-t',
562
+ paneId,
563
+ `cd ${shellQuote(workerPlan.worktreePath)} && ${workerPlan.launchCommand}`,
564
+ 'C-m'
565
+ ],
566
+ { cwd: plan.repoRoot }
567
+ );
568
+ }
569
+ } catch (error) {
570
+ try {
571
+ rollbackCreatedResourcesImpl(plan, createdState, {
572
+ branchExists: runtime.branchExists,
573
+ listWorktrees: runtime.listWorktrees,
574
+ runCommand: runCommandImpl
575
+ });
576
+ } catch (cleanupError) {
577
+ error.message = `${error.message}; cleanup failed: ${cleanupError.message}`;
578
+ }
579
+ throw error;
580
+ }
581
+
582
+ return {
583
+ coordinationDir: plan.coordinationDir,
584
+ sessionName: plan.sessionName,
585
+ workerCount: plan.workerPlans.length
586
+ };
587
+ }
588
+
589
+ module.exports = {
590
+ buildOrchestrationPlan,
591
+ executePlan,
592
+ materializePlan,
593
+ normalizeSeedPaths,
594
+ overlaySeedPaths,
595
+ rollbackCreatedResources,
596
+ renderTemplate,
597
+ slugify
598
+ };