@mrpatronz/nexusflow 2.11.0 → 2.12.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 (194) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +151 -12
  3. package/dist/analyzers/detect-deps.js +1 -1
  4. package/dist/analyzers/detect-deps.js.map +1 -1
  5. package/dist/commands/commit.d.ts.map +1 -1
  6. package/dist/commands/commit.js +15 -0
  7. package/dist/commands/commit.js.map +1 -1
  8. package/dist/commands/create.d.ts +10 -1
  9. package/dist/commands/create.d.ts.map +1 -1
  10. package/dist/commands/create.js +44 -23
  11. package/dist/commands/create.js.map +1 -1
  12. package/dist/commands/finish.d.ts.map +1 -1
  13. package/dist/commands/finish.js +15 -0
  14. package/dist/commands/finish.js.map +1 -1
  15. package/dist/commands/flow.d.ts +13 -0
  16. package/dist/commands/flow.d.ts.map +1 -0
  17. package/dist/commands/flow.js +91 -0
  18. package/dist/commands/flow.js.map +1 -0
  19. package/dist/commands/skill.d.ts +28 -0
  20. package/dist/commands/skill.d.ts.map +1 -0
  21. package/dist/commands/skill.js +250 -0
  22. package/dist/commands/skill.js.map +1 -0
  23. package/dist/commands/status.d.ts.map +1 -1
  24. package/dist/commands/status.js +14 -0
  25. package/dist/commands/status.js.map +1 -1
  26. package/dist/commands/tag.d.ts +13 -0
  27. package/dist/commands/tag.d.ts.map +1 -0
  28. package/dist/commands/tag.js +179 -0
  29. package/dist/commands/tag.js.map +1 -0
  30. package/dist/commands/verify.d.ts +16 -0
  31. package/dist/commands/verify.d.ts.map +1 -0
  32. package/dist/commands/verify.js +91 -0
  33. package/dist/commands/verify.js.map +1 -0
  34. package/dist/core/adapters/local-storage.d.ts.map +1 -1
  35. package/dist/core/adapters/local-storage.js +2 -2
  36. package/dist/core/adapters/local-storage.js.map +1 -1
  37. package/dist/core/commit.d.ts +2 -0
  38. package/dist/core/commit.d.ts.map +1 -1
  39. package/dist/core/commit.js +15 -0
  40. package/dist/core/commit.js.map +1 -1
  41. package/dist/core/domain-catalog.d.ts +9 -0
  42. package/dist/core/domain-catalog.d.ts.map +1 -0
  43. package/dist/core/domain-catalog.js +47 -0
  44. package/dist/core/domain-catalog.js.map +1 -0
  45. package/dist/core/domain-packs.d.ts +81 -0
  46. package/dist/core/domain-packs.d.ts.map +1 -0
  47. package/dist/core/domain-packs.js +443 -0
  48. package/dist/core/domain-packs.js.map +1 -0
  49. package/dist/core/generation-lock.d.ts.map +1 -1
  50. package/dist/core/generation-lock.js +1 -3
  51. package/dist/core/generation-lock.js.map +1 -1
  52. package/dist/core/lifecycle.d.ts +28 -0
  53. package/dist/core/lifecycle.d.ts.map +1 -0
  54. package/dist/core/lifecycle.js +411 -0
  55. package/dist/core/lifecycle.js.map +1 -0
  56. package/dist/core/verify.d.ts +39 -0
  57. package/dist/core/verify.d.ts.map +1 -0
  58. package/dist/core/verify.js +397 -0
  59. package/dist/core/verify.js.map +1 -0
  60. package/dist/core/work-guidance.d.ts +201 -0
  61. package/dist/core/work-guidance.d.ts.map +1 -0
  62. package/dist/core/work-guidance.js +225 -0
  63. package/dist/core/work-guidance.js.map +1 -0
  64. package/dist/core/workspace-state.d.ts +24 -3
  65. package/dist/core/workspace-state.d.ts.map +1 -1
  66. package/dist/core/workspace-state.js +93 -31
  67. package/dist/core/workspace-state.js.map +1 -1
  68. package/dist/core/workspace.d.ts.map +1 -1
  69. package/dist/core/workspace.js +7 -0
  70. package/dist/core/workspace.js.map +1 -1
  71. package/dist/generators/base.d.ts.map +1 -1
  72. package/dist/generators/base.js +38 -4
  73. package/dist/generators/base.js.map +1 -1
  74. package/dist/generators/index.d.ts.map +1 -1
  75. package/dist/generators/index.js +4 -0
  76. package/dist/generators/index.js.map +1 -1
  77. package/dist/generators/plan-generator.d.ts.map +1 -1
  78. package/dist/generators/plan-generator.js +6 -1
  79. package/dist/generators/plan-generator.js.map +1 -1
  80. package/dist/generators/skills-generator.d.ts.map +1 -1
  81. package/dist/generators/skills-generator.js +36 -3
  82. package/dist/generators/skills-generator.js.map +1 -1
  83. package/dist/gui/assets/AgentsPage-GCFiyNSm.js +3 -0
  84. package/dist/gui/assets/DashboardPage-51JHl4Hg.js +1 -0
  85. package/dist/gui/assets/{GettingStartedPage-1MOTng3B.js → GettingStartedPage-DH-zXYgU.js} +1 -1
  86. package/dist/gui/assets/ProjectsPage-DUv2fhI8.js +1 -0
  87. package/dist/gui/assets/ScaffoldRepoInline-Asao9IqG.js +1 -0
  88. package/dist/gui/assets/SettingsPage--pnZKpmm.js +1 -0
  89. package/dist/gui/assets/SkillsPage-BPq8arO5.js +7 -0
  90. package/dist/gui/assets/StartWorkPage-CypGEQZ0.js +1 -0
  91. package/dist/gui/assets/{StrategiesPage-CdCbbIu-.js → StrategiesPage-BHAzXMnc.js} +2 -2
  92. package/dist/gui/assets/WorkroomsPage-BYC8c-rP.js +4 -0
  93. package/dist/gui/assets/WorkspacesPage-DRASo1aL.js +13 -0
  94. package/dist/gui/assets/{alert-dialog-u7qgb9Hq.js → alert-dialog-BD3wT0w3.js} +1 -1
  95. package/dist/gui/assets/badge-DTBzXOWn.js +1 -0
  96. package/dist/gui/assets/index-er-mkc2S.css +2 -0
  97. package/dist/gui/assets/index-jcGHcXQ1.js +57 -0
  98. package/dist/gui/assets/square-check-big-Bj8Rf_dH.js +1 -0
  99. package/dist/gui/assets/tabs-DfbdubLX.js +1 -0
  100. package/dist/gui/assets/{useAriaLabelledBy-C4zV-1Ra.js → useAriaLabelledBy-BmJErI5a.js} +1 -1
  101. package/dist/gui/assets/vsc-Dn-smJni.js +1 -0
  102. package/dist/gui/index.html +3 -5
  103. package/dist/http/work-guidance-routes.d.ts +4 -0
  104. package/dist/http/work-guidance-routes.d.ts.map +1 -0
  105. package/dist/http/work-guidance-routes.js +44 -0
  106. package/dist/http/work-guidance-routes.js.map +1 -0
  107. package/dist/index.js +169 -1
  108. package/dist/index.js.map +1 -1
  109. package/dist/mcp/tools.d.ts.map +1 -1
  110. package/dist/mcp/tools.js +183 -0
  111. package/dist/mcp/tools.js.map +1 -1
  112. package/dist/resources/contracts.d.ts.map +1 -1
  113. package/dist/resources/contracts.js +2 -1
  114. package/dist/resources/contracts.js.map +1 -1
  115. package/dist/resources/fs-safety.d.ts.map +1 -1
  116. package/dist/resources/fs-safety.js +2 -1
  117. package/dist/resources/fs-safety.js.map +1 -1
  118. package/dist/resources/materializer.d.ts +3 -0
  119. package/dist/resources/materializer.d.ts.map +1 -1
  120. package/dist/resources/materializer.js +45 -19
  121. package/dist/resources/materializer.js.map +1 -1
  122. package/dist/server.d.ts.map +1 -1
  123. package/dist/server.js +327 -4
  124. package/dist/server.js.map +1 -1
  125. package/dist/static-resources/skills/contextspace-verify-release/SKILL.md +51 -0
  126. package/dist/static-resources/skills/contextspace-verify-release/references/evidence.md +21 -0
  127. package/dist/static-resources/skills/nexusflow-dev/SKILL.md +49 -0
  128. package/dist/static-resources/skills/nexusflow-dev/references/cli-conventions.md +15 -0
  129. package/dist/static-resources/skills/nexusflow-dev/references/verification-checklist.md +28 -0
  130. package/dist/static-resources/skills/nexusflow-lifecycle/SKILL.md +62 -0
  131. package/dist/static-resources/skills/nexusflow-lifecycle/references/epic-deliverables.md +22 -0
  132. package/dist/static-resources/skills/nexusflow-lifecycle/references/flow-selection-matrix.md +21 -0
  133. package/dist/static-resources/skills/nexusflow-lifecycle/references/stage-outcomes.md +23 -0
  134. package/dist/static-resources/workflows/epic-multi-slice.md +21 -0
  135. package/dist/static-resources/workflows/plan-implement-review.md +17 -6
  136. package/dist/static-resources/workflows/research-verify.md +15 -4
  137. package/dist/static-resources/workflows/solo-developer.md +11 -2
  138. package/dist/test-setup.d.ts +2 -0
  139. package/dist/test-setup.d.ts.map +1 -0
  140. package/dist/test-setup.js +8 -0
  141. package/dist/test-setup.js.map +1 -0
  142. package/dist/types.d.ts +175 -0
  143. package/dist/types.d.ts.map +1 -1
  144. package/dist/utils/resolve-workspace.d.ts.map +1 -1
  145. package/dist/utils/resolve-workspace.js +15 -8
  146. package/dist/utils/resolve-workspace.js.map +1 -1
  147. package/dist/utils/skills-catalog.d.ts +13 -9
  148. package/dist/utils/skills-catalog.d.ts.map +1 -1
  149. package/dist/utils/skills-catalog.js +241 -341
  150. package/dist/utils/skills-catalog.js.map +1 -1
  151. package/dist/utils/workflow-advisor.d.ts +1 -1
  152. package/dist/utils/workflow-advisor.d.ts.map +1 -1
  153. package/dist/utils/workflow-advisor.js +16 -4
  154. package/dist/utils/workflow-advisor.js.map +1 -1
  155. package/dist/utils/workflows.d.ts.map +1 -1
  156. package/dist/utils/workflows.js +5 -2
  157. package/dist/utils/workflows.js.map +1 -1
  158. package/dist/workrooms/contracts.d.ts +3 -3
  159. package/dist/workrooms/local-resources.d.ts +4 -1
  160. package/dist/workrooms/local-resources.d.ts.map +1 -1
  161. package/dist/workrooms/manager.d.ts +5 -2
  162. package/dist/workrooms/manager.d.ts.map +1 -1
  163. package/dist/workrooms/store.d.ts +2 -2
  164. package/package.json +3 -2
  165. package/resources/skills/contextspace-verify-release/SKILL.md +51 -0
  166. package/resources/skills/contextspace-verify-release/references/evidence.md +21 -0
  167. package/resources/skills/nexusflow-dev/SKILL.md +49 -0
  168. package/resources/skills/nexusflow-dev/references/cli-conventions.md +15 -0
  169. package/resources/skills/nexusflow-dev/references/verification-checklist.md +28 -0
  170. package/resources/skills/nexusflow-lifecycle/SKILL.md +62 -0
  171. package/resources/skills/nexusflow-lifecycle/references/epic-deliverables.md +22 -0
  172. package/resources/skills/nexusflow-lifecycle/references/flow-selection-matrix.md +21 -0
  173. package/resources/skills/nexusflow-lifecycle/references/stage-outcomes.md +23 -0
  174. package/resources/workflows/epic-multi-slice.md +21 -0
  175. package/resources/workflows/plan-implement-review.md +17 -6
  176. package/resources/workflows/research-verify.md +15 -4
  177. package/resources/workflows/solo-developer.md +11 -2
  178. package/dist/gui/assets/AgentsPage-DbivhmK0.js +0 -3
  179. package/dist/gui/assets/DashboardPage-D50Qrm3e.js +0 -1
  180. package/dist/gui/assets/NullStore-tdPen1kp.js +0 -1
  181. package/dist/gui/assets/ProjectsPage-srKgf7VN.js +0 -1
  182. package/dist/gui/assets/ScaffoldRepoInline-CP0yn5mw.js +0 -1
  183. package/dist/gui/assets/SettingsPage-DWmrLV8o.js +0 -1
  184. package/dist/gui/assets/SkillsPage-CKjTXdrd.js +0 -6
  185. package/dist/gui/assets/StartWorkPage-BCUnJ9k8.js +0 -1
  186. package/dist/gui/assets/WorkroomsPage-BcT-mGtT.js +0 -4
  187. package/dist/gui/assets/WorkspacesPage-CYFCm-7_.js +0 -2
  188. package/dist/gui/assets/badge-DwXvB44H.js +0 -1
  189. package/dist/gui/assets/chunk-BV7QT456-DfUBKG-z.js +0 -3
  190. package/dist/gui/assets/index-3tD_IeyQ.js +0 -55
  191. package/dist/gui/assets/index-CK5YRfPV.css +0 -2
  192. package/dist/gui/assets/square-check-big-BSIiYXPI.js +0 -1
  193. package/dist/gui/assets/tabs-BrHKp5bD.js +0 -1
  194. package/dist/gui/assets/vsc-D80c4gBT.js +0 -1
package/dist/server.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { registerWorkGuidanceRoutes } from './http/work-guidance-routes.js';
1
2
  /**
2
3
  * @module server
3
4
  * Hono local web server for the NexusFlow GUI.
@@ -27,10 +28,11 @@ import { loadProjects, createProject, updateProject, removeProject, slugifyProje
27
28
  import { getSessionCwd, isInPlace, resolveFeatureRepoPath } from './utils/feature.js';
28
29
  import { listBranches } from './utils/git.js';
29
30
  import { checkRepoFreshness, checkReposFreshness, fastForwardRepos, } from './utils/repo-freshness.js';
30
- import { createWorkspace, listWorkspaces, loadFeatureConfig, loadWorkspaceManifest, deleteWorkspace, addRepoToWorkspace, isolateWorkspaceRepo } from './core/workspace.js';
31
+ import { createWorkspace, listWorkspaces, loadFeatureConfig, saveFeatureConfig, loadWorkspaceManifest, deleteWorkspace, addRepoToWorkspace, isolateWorkspaceRepo } from './core/workspace.js';
31
32
  import { loadWorkspaceState } from './core/workspace-state.js';
32
33
  import { analyzeAllRepos } from './analyzers/index.js';
33
34
  import { generateContextFiles } from './generators/index.js';
35
+ import { getAvailableOrganizations, getAvailableDomainPacks, getOrganization, getDomainPack, matchDomainPacks, resolveActiveDomainRules, saveDomainPack, deleteDomainPack, saveOrganization, deleteOrganization, } from './core/domain-packs.js';
34
36
  import { detectAIAssistants } from './utils/detect-ai.js';
35
37
  import { detectEditors } from './utils/detect-editors.js';
36
38
  import { buildWorkspaceLaunchPrompt, detectWorkspaceLaunchTargets, launchTargetIdForEditorCommand, launchWorkspaceTarget, } from './utils/workspace-launch.js';
@@ -1013,6 +1015,8 @@ async function runCreationJob(jobId, body, config) {
1013
1015
  const feature = {
1014
1016
  id: workspaceId,
1015
1017
  mode: inPlace ? 'in-place' : 'worktree',
1018
+ flowType: body.flowType,
1019
+ workType: body.workType,
1016
1020
  projectId: body.projectId,
1017
1021
  // In-place features never create a branch; keeping branchName populated
1018
1022
  // (= id) avoids breaking every consumer of the non-optional field.
@@ -1028,6 +1032,10 @@ async function runCreationJob(jobId, body, config) {
1028
1032
  createdAt: new Date().toISOString(),
1029
1033
  resumption: body.resumption,
1030
1034
  teamworkInstructions: body.teamworkInstructions,
1035
+ organizationId: body.organizationId,
1036
+ domainPacks: Array.isArray(body.domainPacks) && body.domainPacks.length > 0
1037
+ ? body.domainPacks
1038
+ : matchDomainPacks(body.description, (body.repos || []).map((r) => r.name)).map((p) => p.id),
1031
1039
  };
1032
1040
  if (job) {
1033
1041
  job.feature = feature;
@@ -1081,6 +1089,12 @@ async function runCreationJob(jobId, body, config) {
1081
1089
  app.post('/api/workspace', async (c) => {
1082
1090
  try {
1083
1091
  const body = await c.req.json();
1092
+ if (body.workType !== undefined && !['bug', 'feature', 'performance', 'refactor', 'rewrite'].includes(body.workType)) {
1093
+ return c.json({ error: 'Choose a valid work type.' }, 400);
1094
+ }
1095
+ if (body.flowType !== undefined && !['quick', 'feature', 'epic'].includes(body.flowType)) {
1096
+ return c.json({ error: 'flowType must be quick, feature, or epic.' }, 400);
1097
+ }
1084
1098
  const inPlace = body.mode === 'in-place';
1085
1099
  if (inPlace && !body.name?.trim()) {
1086
1100
  return c.json({ error: 'In-place workspaces need a "name"' }, 400);
@@ -2115,12 +2129,76 @@ app.get('/api/workspace/:id/plan', async (c) => {
2115
2129
  throw err;
2116
2130
  }
2117
2131
  }
2132
+ const state = await loadWorkspaceState(workspacePath);
2133
+ if (state.lifecycle) {
2134
+ const { renderLifecyclePlan } = await import('./core/lifecycle.js');
2135
+ const milestones = renderLifecyclePlan(state.lifecycle);
2136
+ const marker = /<!-- CONTEXTSPACE:MILESTONES:START -->[\s\S]*?<!-- CONTEXTSPACE:MILESTONES:END -->/;
2137
+ content = marker.test(content) ? content.replace(marker, () => milestones) : `${milestones}\n\n${content}`;
2138
+ }
2118
2139
  return c.json({ content });
2119
2140
  }
2120
2141
  catch (error) {
2121
2142
  return errorResponse(c, error);
2122
2143
  }
2123
2144
  });
2145
+ registerWorkGuidanceRoutes(app, async (id) => {
2146
+ const config = await loadConfig();
2147
+ return resolveExactWorkspaceById(config.workspacesDir, id);
2148
+ });
2149
+ // 13c-1. Get active workspace lifecycle and sister branch fleet
2150
+ app.get('/api/workspace/:id/lifecycle', async (c) => {
2151
+ try {
2152
+ const id = c.req.param('id');
2153
+ const config = await loadConfig();
2154
+ const workspacePath = resolveWorkspacePath(config.workspacesDir, id);
2155
+ const { loadWorkspaceLifecycle, renderLifecyclePlan } = await import('./core/lifecycle.js');
2156
+ const lifecycle = await loadWorkspaceLifecycle(workspacePath);
2157
+ const report = (await loadWorkspaceState(workspacePath)).lastVerification ?? null;
2158
+ return c.json({ lifecycle, report, plan: renderLifecyclePlan(lifecycle) });
2159
+ }
2160
+ catch (error) {
2161
+ return errorResponse(c, error);
2162
+ }
2163
+ });
2164
+ // 13c-2. Transition or advance a lifecycle step
2165
+ app.post('/api/workspace/:id/lifecycle/step', async (c) => {
2166
+ try {
2167
+ const id = c.req.param('id');
2168
+ const config = await loadConfig();
2169
+ const workspacePath = resolveWorkspacePath(config.workspacesDir, id);
2170
+ const body = (await c.req.json().catch(() => ({})));
2171
+ if (!body.stepId || !body.action) {
2172
+ return c.json({ error: 'stepId and action (start|verify|complete) are required.' }, 400);
2173
+ }
2174
+ const { advanceLifecycleStep } = await import('./core/lifecycle.js');
2175
+ const lifecycle = await advanceLifecycleStep(workspacePath, body.stepId, body.action);
2176
+ return c.json({ lifecycle });
2177
+ }
2178
+ catch (error) {
2179
+ return errorResponse(c, error);
2180
+ }
2181
+ });
2182
+ // 13c-3. Run mechanical verification gate for workspace
2183
+ app.post('/api/workspace/:id/verify', async (c) => {
2184
+ try {
2185
+ const id = c.req.param('id');
2186
+ const config = await loadConfig();
2187
+ const workspacePath = resolveWorkspacePath(config.workspacesDir, id);
2188
+ const body = (await c.req.json().catch(() => ({})));
2189
+ const { verifyWorkspace } = await import('./core/verify.js');
2190
+ const report = await verifyWorkspace(workspacePath, {
2191
+ repoName: body.repoName,
2192
+ filter: body.filter,
2193
+ command: body.command,
2194
+ allowDirty: body.allowDirty,
2195
+ });
2196
+ return c.json({ report });
2197
+ }
2198
+ catch (error) {
2199
+ return errorResponse(c, error);
2200
+ }
2201
+ });
2124
2202
  // 13c-2. Check workspace migration status (legacy NexusFlow files detected)
2125
2203
  app.get('/api/workspace/:id/migration-status', async (c) => {
2126
2204
  try {
@@ -2696,7 +2774,13 @@ app.delete('/api/skills/categories/:id', async (c) => {
2696
2774
  // materializations are deliberately never treated as catalog sources.
2697
2775
  app.get('/api/skills', async (c) => {
2698
2776
  try {
2699
- const skills = await getAllSkills();
2777
+ const config = await loadConfig();
2778
+ const wsParam = c.req.query('workspace');
2779
+ let wsPath;
2780
+ if (wsParam && wsParam !== 'global') {
2781
+ wsPath = (await resolveExactWorkspaceById(config.workspacesDir, wsParam)) || undefined;
2782
+ }
2783
+ const skills = await getAllSkills(wsPath);
2700
2784
  return c.json({ skills });
2701
2785
  }
2702
2786
  catch (error) {
@@ -2710,7 +2794,23 @@ app.post('/api/skills', async (c) => {
2710
2794
  if (!body.name || !body.content) {
2711
2795
  return c.json({ error: 'Skill name and content are required.' }, 400);
2712
2796
  }
2713
- const skill = await saveSkill(body);
2797
+ const config = await loadConfig();
2798
+ const wsParam = body.workspaceId || body.workspace || c.req.query('workspace');
2799
+ let wsPath;
2800
+ if (wsParam && wsParam !== 'global') {
2801
+ wsPath = (await resolveExactWorkspaceById(config.workspacesDir, wsParam)) || undefined;
2802
+ if (!wsPath) {
2803
+ return c.json({ error: `Workspace "${wsParam}" not found.` }, 404);
2804
+ }
2805
+ }
2806
+ if (body.scope === 'workspace' && !wsPath) {
2807
+ return c.json({ error: 'A valid workspace is required when saving a workspace-scoped skill.' }, 400);
2808
+ }
2809
+ const isWorkspaceScope = body.scope === 'workspace' || (Boolean(wsPath) && body.scope !== 'global');
2810
+ const options = isWorkspaceScope && wsPath
2811
+ ? { scope: 'workspace', workspacePath: wsPath }
2812
+ : { scope: 'global' };
2813
+ const skill = await saveSkill(body, options);
2714
2814
  return c.json({ success: true, skill });
2715
2815
  }
2716
2816
  catch (error) {
@@ -2723,6 +2823,18 @@ app.delete('/api/skills/:id', async (c) => {
2723
2823
  try {
2724
2824
  const id = decodeURIComponent(c.req.param('id'));
2725
2825
  const config = await loadConfig();
2826
+ const wsParam = c.req.query('workspace');
2827
+ let wsPath;
2828
+ if (wsParam && wsParam !== 'global') {
2829
+ wsPath = (await resolveExactWorkspaceById(config.workspacesDir, wsParam)) || undefined;
2830
+ if (!wsPath) {
2831
+ return c.json({ error: `Workspace "${wsParam}" not found.` }, 404);
2832
+ }
2833
+ }
2834
+ if (wsPath) {
2835
+ await deleteSkill(id, { scope: 'workspace', workspacePath: wsPath });
2836
+ return c.json({ success: true });
2837
+ }
2726
2838
  const assignments = await withResourceAdministrationLock(async () => {
2727
2839
  const currentAssignments = await findResourceAssignments(config.workspacesDir, id, 'skill');
2728
2840
  if (!currentAssignments.length)
@@ -2738,7 +2850,8 @@ app.delete('/api/skills/:id', async (c) => {
2738
2850
  return c.json({ success: true });
2739
2851
  }
2740
2852
  catch (error) {
2741
- return errorResponse(c, error);
2853
+ const message = error instanceof Error ? error.message : String(error);
2854
+ return c.json({ error: message }, 400);
2742
2855
  }
2743
2856
  });
2744
2857
  // Codex-native custom-agent catalog. Other harness agent formats are not
@@ -2841,6 +2954,216 @@ app.post('/api/skills/workspace/:id/assign', async (c) => {
2841
2954
  return errorResponse(c, error);
2842
2955
  }
2843
2956
  });
2957
+ // ─── Enterprise & Modular Domain Packs ─────────────────────────────────────
2958
+ // List available enterprise organizations
2959
+ app.get('/api/enterprise/organizations', (c) => {
2960
+ return c.json({ organizations: getAvailableOrganizations() });
2961
+ });
2962
+ // Register custom organization conventions
2963
+ app.post('/api/enterprise/organizations', async (c) => {
2964
+ try {
2965
+ const body = await c.req.json();
2966
+ if (!body.id || !body.name || !Array.isArray(body.rules)) {
2967
+ return c.json({ error: 'id, name, and rules array are required for an organization.' }, 400);
2968
+ }
2969
+ await saveOrganization(body);
2970
+ return c.json({ success: true, organization: getOrganization(body.id) });
2971
+ }
2972
+ catch (error) {
2973
+ return errorResponse(c, error);
2974
+ }
2975
+ });
2976
+ // Unregister custom organization
2977
+ app.delete('/api/enterprise/organizations/:id', async (c) => {
2978
+ const id = decodeURIComponent(c.req.param('id'));
2979
+ const deleted = await deleteOrganization(id);
2980
+ return c.json({ success: deleted });
2981
+ });
2982
+ // List available domain packs
2983
+ app.get('/api/enterprise/domain-packs', (c) => {
2984
+ return c.json({ domainPacks: getAvailableDomainPacks() });
2985
+ });
2986
+ // Get a single domain pack by ID
2987
+ app.get('/api/enterprise/domain-packs/:id', (c) => {
2988
+ const id = decodeURIComponent(c.req.param('id')).toLowerCase().trim();
2989
+ const pack = getDomainPack(id);
2990
+ if (!pack) {
2991
+ return c.json({ error: `Domain pack "${id}" not found.` }, 404);
2992
+ }
2993
+ return c.json({ domainPack: pack });
2994
+ });
2995
+ // Register custom domain pack
2996
+ app.post('/api/enterprise/domain-packs', async (c) => {
2997
+ try {
2998
+ const body = await c.req.json();
2999
+ if (!body.id || !body.name) {
3000
+ return c.json({ error: 'id and name are required for a domain pack.' }, 400);
3001
+ }
3002
+ const normalizedId = body.id.toLowerCase().trim();
3003
+ const tags = Array.isArray(body.tags) && body.tags.length > 0
3004
+ ? body.tags
3005
+ : [normalizedId, ...body.name.toLowerCase().split(/\s+/).filter(Boolean)];
3006
+ const packToRegister = {
3007
+ ...body,
3008
+ id: normalizedId,
3009
+ tags,
3010
+ isTemplate: false,
3011
+ };
3012
+ await saveDomainPack(packToRegister);
3013
+ return c.json({ success: true, domainPack: getDomainPack(normalizedId) });
3014
+ }
3015
+ catch (error) {
3016
+ return errorResponse(c, error);
3017
+ }
3018
+ });
3019
+ // Update or customize a domain pack
3020
+ app.put('/api/enterprise/domain-packs/:id', async (c) => {
3021
+ try {
3022
+ const id = decodeURIComponent(c.req.param('id')).toLowerCase().trim();
3023
+ const existing = getDomainPack(id);
3024
+ if (!existing) {
3025
+ return c.json({ error: `Domain pack "${id}" not found.` }, 404);
3026
+ }
3027
+ const body = await c.req.json();
3028
+ const tags = Array.isArray(body.tags)
3029
+ ? body.tags
3030
+ : (existing.tags ?? [id, ...(body.name || existing.name || id).toLowerCase().split(/\s+/).filter(Boolean)]);
3031
+ const updatedPack = {
3032
+ id,
3033
+ name: body.name ?? existing.name ?? id,
3034
+ description: body.description ?? existing.description ?? '',
3035
+ categoryType: body.categoryType ?? existing.categoryType ?? 'vertical',
3036
+ parent: body.parent !== undefined ? (body.parent ? body.parent.trim() : undefined) : existing.parent,
3037
+ organization: body.organization ?? existing.organization,
3038
+ tags,
3039
+ skills: Array.isArray(body.skills) ? body.skills : existing.skills,
3040
+ contextFiles: Array.isArray(body.contextFiles) ? body.contextFiles : existing.contextFiles,
3041
+ verifyCommand: body.verifyCommand !== undefined ? (body.verifyCommand ? body.verifyCommand.trim() : undefined) : existing.verifyCommand,
3042
+ rules: Array.isArray(body.rules) ? body.rules : existing.rules,
3043
+ defaultRepos: Array.isArray(body.defaultRepos) ? body.defaultRepos : existing.defaultRepos,
3044
+ microservices: Array.isArray(body.microservices) ? body.microservices : existing.microservices,
3045
+ isTemplate: false,
3046
+ };
3047
+ await saveDomainPack(updatedPack);
3048
+ return c.json({ success: true, domainPack: getDomainPack(id) });
3049
+ }
3050
+ catch (error) {
3051
+ return errorResponse(c, error);
3052
+ }
3053
+ });
3054
+ // Unregister custom domain pack
3055
+ app.delete('/api/enterprise/domain-packs/:id', async (c) => {
3056
+ const id = decodeURIComponent(c.req.param('id')).toLowerCase().trim();
3057
+ const deleted = await deleteDomainPack(id);
3058
+ return c.json({ success: deleted });
3059
+ });
3060
+ // Auto-match domain packs from specification or description
3061
+ app.post('/api/enterprise/match-domains', async (c) => {
3062
+ try {
3063
+ const body = await c.req.json();
3064
+ const text = body.description || '';
3065
+ const repos = Array.isArray(body.repos) ? body.repos : [];
3066
+ const matched = matchDomainPacks(text, repos);
3067
+ return c.json({ matched });
3068
+ }
3069
+ catch (error) {
3070
+ return errorResponse(c, error);
3071
+ }
3072
+ });
3073
+ // Get domain packs & organization conventions for a workspace
3074
+ app.get('/api/workspace/:id/domain-packs', async (c) => {
3075
+ try {
3076
+ const id = decodeURIComponent(c.req.param('id'));
3077
+ const config = await loadConfig();
3078
+ const wsPath = await resolveExactWorkspaceById(config.workspacesDir, id);
3079
+ if (!wsPath)
3080
+ return c.json({ error: 'Workspace not found.' }, 404);
3081
+ const feature = await loadFeatureConfig(wsPath);
3082
+ if (!feature)
3083
+ return c.json({ error: 'Failed to load workspace feature.' }, 404);
3084
+ const resolved = resolveActiveDomainRules(feature.organizationId, feature.domainPacks ?? []);
3085
+ return c.json({
3086
+ organizationId: feature.organizationId,
3087
+ assignedDomainPackIds: feature.domainPacks ?? [],
3088
+ ...resolved,
3089
+ });
3090
+ }
3091
+ catch (error) {
3092
+ return errorResponse(c, error);
3093
+ }
3094
+ });
3095
+ // Assign or update domain packs & organization for a workspace
3096
+ app.post('/api/workspace/:id/domain-packs', async (c) => {
3097
+ try {
3098
+ const id = decodeURIComponent(c.req.param('id'));
3099
+ const body = await c.req.json();
3100
+ const config = await loadConfig();
3101
+ const wsPath = await resolveExactWorkspaceById(config.workspacesDir, id);
3102
+ if (!wsPath)
3103
+ return c.json({ error: 'Workspace not found.' }, 404);
3104
+ const feature = await loadFeatureConfig(wsPath);
3105
+ if (!feature)
3106
+ return c.json({ error: 'Failed to load workspace feature.' }, 404);
3107
+ if (body.organizationId !== undefined) {
3108
+ feature.organizationId = body.organizationId;
3109
+ }
3110
+ if (Array.isArray(body.domainPacks)) {
3111
+ feature.domainPacks = body.domainPacks;
3112
+ }
3113
+ await saveFeatureConfig(wsPath, feature);
3114
+ // Refresh workspace context so AGENTS.md immediately reflects active domain rules
3115
+ await refreshWorkspace(wsPath, { force: true }).catch(() => { });
3116
+ const resolved = resolveActiveDomainRules(feature.organizationId, feature.domainPacks ?? []);
3117
+ return c.json({
3118
+ success: true,
3119
+ feature,
3120
+ ...resolved,
3121
+ });
3122
+ }
3123
+ catch (error) {
3124
+ return errorResponse(c, error);
3125
+ }
3126
+ });
3127
+ // Update workspace specification / PO requirements & adaptively match domain packs
3128
+ app.post('/api/workspace/:id/update-spec', async (c) => {
3129
+ try {
3130
+ const id = decodeURIComponent(c.req.param('id'));
3131
+ const body = await c.req.json();
3132
+ if (!body.description || typeof body.description !== 'string') {
3133
+ return c.json({ error: 'Description is required.' }, 400);
3134
+ }
3135
+ const config = await loadConfig();
3136
+ const wsPath = await resolveExactWorkspaceById(config.workspacesDir, id);
3137
+ if (!wsPath)
3138
+ return c.json({ error: 'Workspace not found.' }, 404);
3139
+ const feature = await loadFeatureConfig(wsPath);
3140
+ if (!feature)
3141
+ return c.json({ error: 'Failed to load workspace feature.' }, 404);
3142
+ feature.description = body.description.trim();
3143
+ const newlyMatched = [];
3144
+ if (body.autoMatchDomains !== false) {
3145
+ const matchedPacks = matchDomainPacks(feature.description);
3146
+ const currentPacks = new Set(feature.domainPacks ?? []);
3147
+ for (const pack of matchedPacks) {
3148
+ if (!currentPacks.has(pack.id)) {
3149
+ currentPacks.add(pack.id);
3150
+ newlyMatched.push(pack.id);
3151
+ }
3152
+ }
3153
+ feature.domainPacks = Array.from(currentPacks);
3154
+ }
3155
+ await saveFeatureConfig(wsPath, feature);
3156
+ await refreshWorkspace(wsPath, { force: true }).catch(() => { });
3157
+ return c.json({
3158
+ success: true,
3159
+ feature,
3160
+ newlyMatched,
3161
+ });
3162
+ }
3163
+ catch (error) {
3164
+ return errorResponse(c, error);
3165
+ }
3166
+ });
2844
3167
  // ─── Workrooms: opt-in LAN/VPN collaboration ─────────────────────────────
2845
3168
  const WORKROOM_HUMAN_SESSION_COOKIES = [
2846
3169
  'contextspace_workroom_human',