@mrpatronz/nexusflow 2.11.0 → 2.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +154 -13
  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 +1 -1
  50. package/dist/core/generation-lock.d.ts.map +1 -1
  51. package/dist/core/generation-lock.js +18 -11
  52. package/dist/core/generation-lock.js.map +1 -1
  53. package/dist/core/lifecycle.d.ts +28 -0
  54. package/dist/core/lifecycle.d.ts.map +1 -0
  55. package/dist/core/lifecycle.js +423 -0
  56. package/dist/core/lifecycle.js.map +1 -0
  57. package/dist/core/planning-notes.d.ts +11 -0
  58. package/dist/core/planning-notes.d.ts.map +1 -0
  59. package/dist/core/planning-notes.js +108 -0
  60. package/dist/core/planning-notes.js.map +1 -0
  61. package/dist/core/verify.d.ts +39 -0
  62. package/dist/core/verify.d.ts.map +1 -0
  63. package/dist/core/verify.js +397 -0
  64. package/dist/core/verify.js.map +1 -0
  65. package/dist/core/work-guidance.d.ts +202 -0
  66. package/dist/core/work-guidance.d.ts.map +1 -0
  67. package/dist/core/work-guidance.js +231 -0
  68. package/dist/core/work-guidance.js.map +1 -0
  69. package/dist/core/workspace-git.d.ts.map +1 -1
  70. package/dist/core/workspace-git.js +1 -0
  71. package/dist/core/workspace-git.js.map +1 -1
  72. package/dist/core/workspace-state.d.ts +24 -3
  73. package/dist/core/workspace-state.d.ts.map +1 -1
  74. package/dist/core/workspace-state.js +93 -31
  75. package/dist/core/workspace-state.js.map +1 -1
  76. package/dist/core/workspace-tools.d.ts +10 -0
  77. package/dist/core/workspace-tools.d.ts.map +1 -0
  78. package/dist/core/workspace-tools.js +123 -0
  79. package/dist/core/workspace-tools.js.map +1 -0
  80. package/dist/core/workspace.d.ts.map +1 -1
  81. package/dist/core/workspace.js +7 -17
  82. package/dist/core/workspace.js.map +1 -1
  83. package/dist/generators/base.d.ts.map +1 -1
  84. package/dist/generators/base.js +42 -5
  85. package/dist/generators/base.js.map +1 -1
  86. package/dist/generators/index.d.ts.map +1 -1
  87. package/dist/generators/index.js +11 -0
  88. package/dist/generators/index.js.map +1 -1
  89. package/dist/generators/plan-generator.d.ts.map +1 -1
  90. package/dist/generators/plan-generator.js +61 -58
  91. package/dist/generators/plan-generator.js.map +1 -1
  92. package/dist/generators/skills-generator.d.ts.map +1 -1
  93. package/dist/generators/skills-generator.js +36 -3
  94. package/dist/generators/skills-generator.js.map +1 -1
  95. package/dist/gui/assets/AgentsPage-C7Rh_dp7.js +3 -0
  96. package/dist/gui/assets/DashboardPage-YVONGtqS.js +1 -0
  97. package/dist/gui/assets/{GettingStartedPage-1MOTng3B.js → GettingStartedPage-C2Kbvz_N.js} +1 -1
  98. package/dist/gui/assets/ProjectsPage-DOpfV2Oj.js +1 -0
  99. package/dist/gui/assets/{ScaffoldRepoInline-CP0yn5mw.js → ScaffoldRepoInline-D91aitAi.js} +1 -1
  100. package/dist/gui/assets/{SettingsPage-DWmrLV8o.js → SettingsPage-BdaHN6fa.js} +1 -1
  101. package/dist/gui/assets/SkillsPage-DT9lRkCg.js +7 -0
  102. package/dist/gui/assets/StartWorkPage-D932lq9x.js +1 -0
  103. package/dist/gui/assets/{StrategiesPage-CdCbbIu-.js → StrategiesPage-DCfS-wdu.js} +2 -2
  104. package/dist/gui/assets/WorkroomsPage-BQU27nWS.js +4 -0
  105. package/dist/gui/assets/WorkspacesPage-D3TLVmq-.js +13 -0
  106. package/dist/gui/assets/{alert-dialog-u7qgb9Hq.js → alert-dialog-YynbGyj1.js} +1 -1
  107. package/dist/gui/assets/badge-DTBzXOWn.js +1 -0
  108. package/dist/gui/assets/index-er-mkc2S.css +2 -0
  109. package/dist/gui/assets/index-hTa55BQ6.js +57 -0
  110. package/dist/gui/assets/square-check-big-Bj8Rf_dH.js +1 -0
  111. package/dist/gui/assets/tabs-DfW7Akjs.js +1 -0
  112. package/dist/gui/assets/{useAriaLabelledBy-C4zV-1Ra.js → useAriaLabelledBy-BmJErI5a.js} +1 -1
  113. package/dist/gui/assets/vsc-BsVrJLSg.js +1 -0
  114. package/dist/gui/index.html +3 -5
  115. package/dist/http/work-guidance-routes.d.ts +4 -0
  116. package/dist/http/work-guidance-routes.d.ts.map +1 -0
  117. package/dist/http/work-guidance-routes.js +50 -0
  118. package/dist/http/work-guidance-routes.js.map +1 -0
  119. package/dist/index.js +169 -1
  120. package/dist/index.js.map +1 -1
  121. package/dist/mcp/tools.d.ts +0 -7
  122. package/dist/mcp/tools.d.ts.map +1 -1
  123. package/dist/mcp/tools.js +207 -0
  124. package/dist/mcp/tools.js.map +1 -1
  125. package/dist/resources/contracts.d.ts.map +1 -1
  126. package/dist/resources/contracts.js +2 -1
  127. package/dist/resources/contracts.js.map +1 -1
  128. package/dist/resources/fs-safety.d.ts.map +1 -1
  129. package/dist/resources/fs-safety.js +2 -1
  130. package/dist/resources/fs-safety.js.map +1 -1
  131. package/dist/resources/materializer.d.ts +3 -0
  132. package/dist/resources/materializer.d.ts.map +1 -1
  133. package/dist/resources/materializer.js +45 -19
  134. package/dist/resources/materializer.js.map +1 -1
  135. package/dist/resources/service.d.ts +1 -1
  136. package/dist/resources/service.d.ts.map +1 -1
  137. package/dist/resources/service.js +3 -2
  138. package/dist/resources/service.js.map +1 -1
  139. package/dist/server.d.ts.map +1 -1
  140. package/dist/server.js +333 -8
  141. package/dist/server.js.map +1 -1
  142. package/dist/static-resources/skills/contextspace-verify-release/SKILL.md +51 -0
  143. package/dist/static-resources/skills/contextspace-verify-release/references/evidence.md +21 -0
  144. package/dist/static-resources/skills/nexusflow-dev/SKILL.md +49 -0
  145. package/dist/static-resources/skills/nexusflow-dev/references/cli-conventions.md +15 -0
  146. package/dist/static-resources/skills/nexusflow-dev/references/verification-checklist.md +28 -0
  147. package/dist/static-resources/skills/nexusflow-lifecycle/SKILL.md +66 -0
  148. package/dist/static-resources/skills/nexusflow-lifecycle/references/epic-deliverables.md +36 -0
  149. package/dist/static-resources/skills/nexusflow-lifecycle/references/flow-selection-matrix.md +21 -0
  150. package/dist/static-resources/skills/nexusflow-lifecycle/references/stage-outcomes.md +23 -0
  151. package/dist/static-resources/workflows/epic-multi-slice.md +21 -0
  152. package/dist/static-resources/workflows/plan-implement-review.md +17 -6
  153. package/dist/static-resources/workflows/research-verify.md +15 -4
  154. package/dist/static-resources/workflows/solo-developer.md +11 -2
  155. package/dist/test-setup.d.ts +2 -0
  156. package/dist/test-setup.d.ts.map +1 -0
  157. package/dist/test-setup.js +8 -0
  158. package/dist/test-setup.js.map +1 -0
  159. package/dist/types.d.ts +178 -0
  160. package/dist/types.d.ts.map +1 -1
  161. package/dist/utils/resolve-workspace.d.ts.map +1 -1
  162. package/dist/utils/resolve-workspace.js +15 -8
  163. package/dist/utils/resolve-workspace.js.map +1 -1
  164. package/dist/utils/skills-catalog.d.ts +18 -9
  165. package/dist/utils/skills-catalog.d.ts.map +1 -1
  166. package/dist/utils/skills-catalog.js +248 -344
  167. package/dist/utils/skills-catalog.js.map +1 -1
  168. package/dist/utils/workflow-advisor.d.ts +1 -1
  169. package/dist/utils/workflow-advisor.d.ts.map +1 -1
  170. package/dist/utils/workflow-advisor.js +16 -4
  171. package/dist/utils/workflow-advisor.js.map +1 -1
  172. package/dist/utils/workflows.d.ts.map +1 -1
  173. package/dist/utils/workflows.js +5 -2
  174. package/dist/utils/workflows.js.map +1 -1
  175. package/dist/workrooms/contracts.d.ts +3 -3
  176. package/dist/workrooms/local-resources.d.ts +4 -1
  177. package/dist/workrooms/local-resources.d.ts.map +1 -1
  178. package/dist/workrooms/manager.d.ts +5 -2
  179. package/dist/workrooms/manager.d.ts.map +1 -1
  180. package/dist/workrooms/store.d.ts +2 -2
  181. package/package.json +3 -2
  182. package/resources/skills/contextspace-verify-release/SKILL.md +51 -0
  183. package/resources/skills/contextspace-verify-release/references/evidence.md +21 -0
  184. package/resources/skills/nexusflow-dev/SKILL.md +49 -0
  185. package/resources/skills/nexusflow-dev/references/cli-conventions.md +15 -0
  186. package/resources/skills/nexusflow-dev/references/verification-checklist.md +28 -0
  187. package/resources/skills/nexusflow-lifecycle/SKILL.md +66 -0
  188. package/resources/skills/nexusflow-lifecycle/references/epic-deliverables.md +36 -0
  189. package/resources/skills/nexusflow-lifecycle/references/flow-selection-matrix.md +21 -0
  190. package/resources/skills/nexusflow-lifecycle/references/stage-outcomes.md +23 -0
  191. package/resources/workflows/epic-multi-slice.md +21 -0
  192. package/resources/workflows/plan-implement-review.md +17 -6
  193. package/resources/workflows/research-verify.md +15 -4
  194. package/resources/workflows/solo-developer.md +11 -2
  195. package/dist/gui/assets/AgentsPage-DbivhmK0.js +0 -3
  196. package/dist/gui/assets/DashboardPage-D50Qrm3e.js +0 -1
  197. package/dist/gui/assets/NullStore-tdPen1kp.js +0 -1
  198. package/dist/gui/assets/ProjectsPage-srKgf7VN.js +0 -1
  199. package/dist/gui/assets/SkillsPage-CKjTXdrd.js +0 -6
  200. package/dist/gui/assets/StartWorkPage-BCUnJ9k8.js +0 -1
  201. package/dist/gui/assets/WorkroomsPage-BcT-mGtT.js +0 -4
  202. package/dist/gui/assets/WorkspacesPage-CYFCm-7_.js +0 -2
  203. package/dist/gui/assets/badge-DwXvB44H.js +0 -1
  204. package/dist/gui/assets/chunk-BV7QT456-DfUBKG-z.js +0 -3
  205. package/dist/gui/assets/index-3tD_IeyQ.js +0 -55
  206. package/dist/gui/assets/index-CK5YRfPV.css +0 -2
  207. package/dist/gui/assets/square-check-big-BSIiYXPI.js +0 -1
  208. package/dist/gui/assets/tabs-BrHKp5bD.js +0 -1
  209. 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 {
@@ -2692,12 +2770,20 @@ app.delete('/api/skills/categories/:id', async (c) => {
2692
2770
  return errorResponse(c, error);
2693
2771
  }
2694
2772
  });
2695
- // Get authoritative skills (built-ins + personal catalog). Workspace
2696
- // materializations are deliberately never treated as catalog sources.
2773
+ // Include authored workspace packages; generated global copies still resolve to their catalog source.
2697
2774
  app.get('/api/skills', async (c) => {
2698
2775
  try {
2699
- const skills = await getAllSkills();
2700
- return c.json({ skills });
2776
+ const config = await loadConfig();
2777
+ const wsParam = c.req.query('workspace');
2778
+ let wsPath;
2779
+ if (wsParam && wsParam !== 'global') {
2780
+ wsPath = (await resolveExactWorkspaceById(config.workspacesDir, wsParam)) || undefined;
2781
+ if (!wsPath)
2782
+ return c.json({ error: 'Workspace not found.' }, 404);
2783
+ }
2784
+ const diagnostics = [];
2785
+ const skills = await getAllSkills(wsPath, diagnostics);
2786
+ return c.json({ skills, diagnostics });
2701
2787
  }
2702
2788
  catch (error) {
2703
2789
  return errorResponse(c, error);
@@ -2710,7 +2796,23 @@ app.post('/api/skills', async (c) => {
2710
2796
  if (!body.name || !body.content) {
2711
2797
  return c.json({ error: 'Skill name and content are required.' }, 400);
2712
2798
  }
2713
- const skill = await saveSkill(body);
2799
+ const config = await loadConfig();
2800
+ const wsParam = body.workspaceId || body.workspace || c.req.query('workspace');
2801
+ let wsPath;
2802
+ if (wsParam && wsParam !== 'global') {
2803
+ wsPath = (await resolveExactWorkspaceById(config.workspacesDir, wsParam)) || undefined;
2804
+ if (!wsPath) {
2805
+ return c.json({ error: `Workspace "${wsParam}" not found.` }, 404);
2806
+ }
2807
+ }
2808
+ if (body.scope === 'workspace' && !wsPath) {
2809
+ return c.json({ error: 'A valid workspace is required when saving a workspace-scoped skill.' }, 400);
2810
+ }
2811
+ const isWorkspaceScope = body.scope === 'workspace' || (Boolean(wsPath) && body.scope !== 'global');
2812
+ const options = isWorkspaceScope && wsPath
2813
+ ? { scope: 'workspace', workspacePath: wsPath }
2814
+ : { scope: 'global' };
2815
+ const skill = await saveSkill(body, options);
2714
2816
  return c.json({ success: true, skill });
2715
2817
  }
2716
2818
  catch (error) {
@@ -2723,6 +2825,18 @@ app.delete('/api/skills/:id', async (c) => {
2723
2825
  try {
2724
2826
  const id = decodeURIComponent(c.req.param('id'));
2725
2827
  const config = await loadConfig();
2828
+ const wsParam = c.req.query('workspace');
2829
+ let wsPath;
2830
+ if (wsParam && wsParam !== 'global') {
2831
+ wsPath = (await resolveExactWorkspaceById(config.workspacesDir, wsParam)) || undefined;
2832
+ if (!wsPath) {
2833
+ return c.json({ error: `Workspace "${wsParam}" not found.` }, 404);
2834
+ }
2835
+ }
2836
+ if (wsPath) {
2837
+ await deleteSkill(id, { scope: 'workspace', workspacePath: wsPath });
2838
+ return c.json({ success: true });
2839
+ }
2726
2840
  const assignments = await withResourceAdministrationLock(async () => {
2727
2841
  const currentAssignments = await findResourceAssignments(config.workspacesDir, id, 'skill');
2728
2842
  if (!currentAssignments.length)
@@ -2738,7 +2852,8 @@ app.delete('/api/skills/:id', async (c) => {
2738
2852
  return c.json({ success: true });
2739
2853
  }
2740
2854
  catch (error) {
2741
- return errorResponse(c, error);
2855
+ const message = error instanceof Error ? error.message : String(error);
2856
+ return c.json({ error: message }, 400);
2742
2857
  }
2743
2858
  });
2744
2859
  // Codex-native custom-agent catalog. Other harness agent formats are not
@@ -2828,7 +2943,7 @@ app.post('/api/skills/workspace/:id/assign', async (c) => {
2828
2943
  if (!wsPath)
2829
2944
  return c.json({ error: 'Workspace not found.' }, 404);
2830
2945
  const saved = await withResourceAdministrationLock(async () => {
2831
- await validateResourceSelections(body.enabledSkills, body.enabledAgents);
2946
+ await validateResourceSelections(body.enabledSkills, body.enabledAgents, wsPath);
2832
2947
  return saveWorkspaceSkillsConfig(wsPath, {
2833
2948
  enabledSkills: body.enabledSkills,
2834
2949
  enabledAgents: body.enabledAgents,
@@ -2841,6 +2956,216 @@ app.post('/api/skills/workspace/:id/assign', async (c) => {
2841
2956
  return errorResponse(c, error);
2842
2957
  }
2843
2958
  });
2959
+ // ─── Enterprise & Modular Domain Packs ─────────────────────────────────────
2960
+ // List available enterprise organizations
2961
+ app.get('/api/enterprise/organizations', (c) => {
2962
+ return c.json({ organizations: getAvailableOrganizations() });
2963
+ });
2964
+ // Register custom organization conventions
2965
+ app.post('/api/enterprise/organizations', async (c) => {
2966
+ try {
2967
+ const body = await c.req.json();
2968
+ if (!body.id || !body.name || !Array.isArray(body.rules)) {
2969
+ return c.json({ error: 'id, name, and rules array are required for an organization.' }, 400);
2970
+ }
2971
+ await saveOrganization(body);
2972
+ return c.json({ success: true, organization: getOrganization(body.id) });
2973
+ }
2974
+ catch (error) {
2975
+ return errorResponse(c, error);
2976
+ }
2977
+ });
2978
+ // Unregister custom organization
2979
+ app.delete('/api/enterprise/organizations/:id', async (c) => {
2980
+ const id = decodeURIComponent(c.req.param('id'));
2981
+ const deleted = await deleteOrganization(id);
2982
+ return c.json({ success: deleted });
2983
+ });
2984
+ // List available domain packs
2985
+ app.get('/api/enterprise/domain-packs', (c) => {
2986
+ return c.json({ domainPacks: getAvailableDomainPacks() });
2987
+ });
2988
+ // Get a single domain pack by ID
2989
+ app.get('/api/enterprise/domain-packs/:id', (c) => {
2990
+ const id = decodeURIComponent(c.req.param('id')).toLowerCase().trim();
2991
+ const pack = getDomainPack(id);
2992
+ if (!pack) {
2993
+ return c.json({ error: `Domain pack "${id}" not found.` }, 404);
2994
+ }
2995
+ return c.json({ domainPack: pack });
2996
+ });
2997
+ // Register custom domain pack
2998
+ app.post('/api/enterprise/domain-packs', async (c) => {
2999
+ try {
3000
+ const body = await c.req.json();
3001
+ if (!body.id || !body.name) {
3002
+ return c.json({ error: 'id and name are required for a domain pack.' }, 400);
3003
+ }
3004
+ const normalizedId = body.id.toLowerCase().trim();
3005
+ const tags = Array.isArray(body.tags) && body.tags.length > 0
3006
+ ? body.tags
3007
+ : [normalizedId, ...body.name.toLowerCase().split(/\s+/).filter(Boolean)];
3008
+ const packToRegister = {
3009
+ ...body,
3010
+ id: normalizedId,
3011
+ tags,
3012
+ isTemplate: false,
3013
+ };
3014
+ await saveDomainPack(packToRegister);
3015
+ return c.json({ success: true, domainPack: getDomainPack(normalizedId) });
3016
+ }
3017
+ catch (error) {
3018
+ return errorResponse(c, error);
3019
+ }
3020
+ });
3021
+ // Update or customize a domain pack
3022
+ app.put('/api/enterprise/domain-packs/:id', async (c) => {
3023
+ try {
3024
+ const id = decodeURIComponent(c.req.param('id')).toLowerCase().trim();
3025
+ const existing = getDomainPack(id);
3026
+ if (!existing) {
3027
+ return c.json({ error: `Domain pack "${id}" not found.` }, 404);
3028
+ }
3029
+ const body = await c.req.json();
3030
+ const tags = Array.isArray(body.tags)
3031
+ ? body.tags
3032
+ : (existing.tags ?? [id, ...(body.name || existing.name || id).toLowerCase().split(/\s+/).filter(Boolean)]);
3033
+ const updatedPack = {
3034
+ id,
3035
+ name: body.name ?? existing.name ?? id,
3036
+ description: body.description ?? existing.description ?? '',
3037
+ categoryType: body.categoryType ?? existing.categoryType ?? 'vertical',
3038
+ parent: body.parent !== undefined ? (body.parent ? body.parent.trim() : undefined) : existing.parent,
3039
+ organization: body.organization ?? existing.organization,
3040
+ tags,
3041
+ skills: Array.isArray(body.skills) ? body.skills : existing.skills,
3042
+ contextFiles: Array.isArray(body.contextFiles) ? body.contextFiles : existing.contextFiles,
3043
+ verifyCommand: body.verifyCommand !== undefined ? (body.verifyCommand ? body.verifyCommand.trim() : undefined) : existing.verifyCommand,
3044
+ rules: Array.isArray(body.rules) ? body.rules : existing.rules,
3045
+ defaultRepos: Array.isArray(body.defaultRepos) ? body.defaultRepos : existing.defaultRepos,
3046
+ microservices: Array.isArray(body.microservices) ? body.microservices : existing.microservices,
3047
+ isTemplate: false,
3048
+ };
3049
+ await saveDomainPack(updatedPack);
3050
+ return c.json({ success: true, domainPack: getDomainPack(id) });
3051
+ }
3052
+ catch (error) {
3053
+ return errorResponse(c, error);
3054
+ }
3055
+ });
3056
+ // Unregister custom domain pack
3057
+ app.delete('/api/enterprise/domain-packs/:id', async (c) => {
3058
+ const id = decodeURIComponent(c.req.param('id')).toLowerCase().trim();
3059
+ const deleted = await deleteDomainPack(id);
3060
+ return c.json({ success: deleted });
3061
+ });
3062
+ // Auto-match domain packs from specification or description
3063
+ app.post('/api/enterprise/match-domains', async (c) => {
3064
+ try {
3065
+ const body = await c.req.json();
3066
+ const text = body.description || '';
3067
+ const repos = Array.isArray(body.repos) ? body.repos : [];
3068
+ const matched = matchDomainPacks(text, repos);
3069
+ return c.json({ matched });
3070
+ }
3071
+ catch (error) {
3072
+ return errorResponse(c, error);
3073
+ }
3074
+ });
3075
+ // Get domain packs & organization conventions for a workspace
3076
+ app.get('/api/workspace/:id/domain-packs', async (c) => {
3077
+ try {
3078
+ const id = decodeURIComponent(c.req.param('id'));
3079
+ const config = await loadConfig();
3080
+ const wsPath = await resolveExactWorkspaceById(config.workspacesDir, id);
3081
+ if (!wsPath)
3082
+ return c.json({ error: 'Workspace not found.' }, 404);
3083
+ const feature = await loadFeatureConfig(wsPath);
3084
+ if (!feature)
3085
+ return c.json({ error: 'Failed to load workspace feature.' }, 404);
3086
+ const resolved = resolveActiveDomainRules(feature.organizationId, feature.domainPacks ?? []);
3087
+ return c.json({
3088
+ organizationId: feature.organizationId,
3089
+ assignedDomainPackIds: feature.domainPacks ?? [],
3090
+ ...resolved,
3091
+ });
3092
+ }
3093
+ catch (error) {
3094
+ return errorResponse(c, error);
3095
+ }
3096
+ });
3097
+ // Assign or update domain packs & organization for a workspace
3098
+ app.post('/api/workspace/:id/domain-packs', async (c) => {
3099
+ try {
3100
+ const id = decodeURIComponent(c.req.param('id'));
3101
+ const body = await c.req.json();
3102
+ const config = await loadConfig();
3103
+ const wsPath = await resolveExactWorkspaceById(config.workspacesDir, id);
3104
+ if (!wsPath)
3105
+ return c.json({ error: 'Workspace not found.' }, 404);
3106
+ const feature = await loadFeatureConfig(wsPath);
3107
+ if (!feature)
3108
+ return c.json({ error: 'Failed to load workspace feature.' }, 404);
3109
+ if (body.organizationId !== undefined) {
3110
+ feature.organizationId = body.organizationId;
3111
+ }
3112
+ if (Array.isArray(body.domainPacks)) {
3113
+ feature.domainPacks = body.domainPacks;
3114
+ }
3115
+ await saveFeatureConfig(wsPath, feature);
3116
+ // Refresh workspace context so AGENTS.md immediately reflects active domain rules
3117
+ await refreshWorkspace(wsPath, { force: true }).catch(() => { });
3118
+ const resolved = resolveActiveDomainRules(feature.organizationId, feature.domainPacks ?? []);
3119
+ return c.json({
3120
+ success: true,
3121
+ feature,
3122
+ ...resolved,
3123
+ });
3124
+ }
3125
+ catch (error) {
3126
+ return errorResponse(c, error);
3127
+ }
3128
+ });
3129
+ // Update workspace specification / PO requirements & adaptively match domain packs
3130
+ app.post('/api/workspace/:id/update-spec', async (c) => {
3131
+ try {
3132
+ const id = decodeURIComponent(c.req.param('id'));
3133
+ const body = await c.req.json();
3134
+ if (!body.description || typeof body.description !== 'string') {
3135
+ return c.json({ error: 'Description is required.' }, 400);
3136
+ }
3137
+ const config = await loadConfig();
3138
+ const wsPath = await resolveExactWorkspaceById(config.workspacesDir, id);
3139
+ if (!wsPath)
3140
+ return c.json({ error: 'Workspace not found.' }, 404);
3141
+ const feature = await loadFeatureConfig(wsPath);
3142
+ if (!feature)
3143
+ return c.json({ error: 'Failed to load workspace feature.' }, 404);
3144
+ feature.description = body.description.trim();
3145
+ const newlyMatched = [];
3146
+ if (body.autoMatchDomains !== false) {
3147
+ const matchedPacks = matchDomainPacks(feature.description);
3148
+ const currentPacks = new Set(feature.domainPacks ?? []);
3149
+ for (const pack of matchedPacks) {
3150
+ if (!currentPacks.has(pack.id)) {
3151
+ currentPacks.add(pack.id);
3152
+ newlyMatched.push(pack.id);
3153
+ }
3154
+ }
3155
+ feature.domainPacks = Array.from(currentPacks);
3156
+ }
3157
+ await saveFeatureConfig(wsPath, feature);
3158
+ await refreshWorkspace(wsPath, { force: true }).catch(() => { });
3159
+ return c.json({
3160
+ success: true,
3161
+ feature,
3162
+ newlyMatched,
3163
+ });
3164
+ }
3165
+ catch (error) {
3166
+ return errorResponse(c, error);
3167
+ }
3168
+ });
2844
3169
  // ─── Workrooms: opt-in LAN/VPN collaboration ─────────────────────────────
2845
3170
  const WORKROOM_HUMAN_SESSION_COOKIES = [
2846
3171
  'contextspace_workroom_human',