@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.
- package/LICENSE +1 -1
- package/README.md +154 -13
- package/dist/analyzers/detect-deps.js +1 -1
- package/dist/analyzers/detect-deps.js.map +1 -1
- package/dist/commands/commit.d.ts.map +1 -1
- package/dist/commands/commit.js +15 -0
- package/dist/commands/commit.js.map +1 -1
- package/dist/commands/create.d.ts +10 -1
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/create.js +44 -23
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/finish.d.ts.map +1 -1
- package/dist/commands/finish.js +15 -0
- package/dist/commands/finish.js.map +1 -1
- package/dist/commands/flow.d.ts +13 -0
- package/dist/commands/flow.d.ts.map +1 -0
- package/dist/commands/flow.js +91 -0
- package/dist/commands/flow.js.map +1 -0
- package/dist/commands/skill.d.ts +28 -0
- package/dist/commands/skill.d.ts.map +1 -0
- package/dist/commands/skill.js +250 -0
- package/dist/commands/skill.js.map +1 -0
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +14 -0
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/tag.d.ts +13 -0
- package/dist/commands/tag.d.ts.map +1 -0
- package/dist/commands/tag.js +179 -0
- package/dist/commands/tag.js.map +1 -0
- package/dist/commands/verify.d.ts +16 -0
- package/dist/commands/verify.d.ts.map +1 -0
- package/dist/commands/verify.js +91 -0
- package/dist/commands/verify.js.map +1 -0
- package/dist/core/adapters/local-storage.d.ts.map +1 -1
- package/dist/core/adapters/local-storage.js +2 -2
- package/dist/core/adapters/local-storage.js.map +1 -1
- package/dist/core/commit.d.ts +2 -0
- package/dist/core/commit.d.ts.map +1 -1
- package/dist/core/commit.js +15 -0
- package/dist/core/commit.js.map +1 -1
- package/dist/core/domain-catalog.d.ts +9 -0
- package/dist/core/domain-catalog.d.ts.map +1 -0
- package/dist/core/domain-catalog.js +47 -0
- package/dist/core/domain-catalog.js.map +1 -0
- package/dist/core/domain-packs.d.ts +81 -0
- package/dist/core/domain-packs.d.ts.map +1 -0
- package/dist/core/domain-packs.js +443 -0
- package/dist/core/domain-packs.js.map +1 -0
- package/dist/core/generation-lock.d.ts +1 -1
- package/dist/core/generation-lock.d.ts.map +1 -1
- package/dist/core/generation-lock.js +18 -11
- package/dist/core/generation-lock.js.map +1 -1
- package/dist/core/lifecycle.d.ts +28 -0
- package/dist/core/lifecycle.d.ts.map +1 -0
- package/dist/core/lifecycle.js +423 -0
- package/dist/core/lifecycle.js.map +1 -0
- package/dist/core/planning-notes.d.ts +11 -0
- package/dist/core/planning-notes.d.ts.map +1 -0
- package/dist/core/planning-notes.js +108 -0
- package/dist/core/planning-notes.js.map +1 -0
- package/dist/core/verify.d.ts +39 -0
- package/dist/core/verify.d.ts.map +1 -0
- package/dist/core/verify.js +397 -0
- package/dist/core/verify.js.map +1 -0
- package/dist/core/work-guidance.d.ts +202 -0
- package/dist/core/work-guidance.d.ts.map +1 -0
- package/dist/core/work-guidance.js +231 -0
- package/dist/core/work-guidance.js.map +1 -0
- package/dist/core/workspace-git.d.ts.map +1 -1
- package/dist/core/workspace-git.js +1 -0
- package/dist/core/workspace-git.js.map +1 -1
- package/dist/core/workspace-state.d.ts +24 -3
- package/dist/core/workspace-state.d.ts.map +1 -1
- package/dist/core/workspace-state.js +93 -31
- package/dist/core/workspace-state.js.map +1 -1
- package/dist/core/workspace-tools.d.ts +10 -0
- package/dist/core/workspace-tools.d.ts.map +1 -0
- package/dist/core/workspace-tools.js +123 -0
- package/dist/core/workspace-tools.js.map +1 -0
- package/dist/core/workspace.d.ts.map +1 -1
- package/dist/core/workspace.js +7 -17
- package/dist/core/workspace.js.map +1 -1
- package/dist/generators/base.d.ts.map +1 -1
- package/dist/generators/base.js +42 -5
- package/dist/generators/base.js.map +1 -1
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +11 -0
- package/dist/generators/index.js.map +1 -1
- package/dist/generators/plan-generator.d.ts.map +1 -1
- package/dist/generators/plan-generator.js +61 -58
- package/dist/generators/plan-generator.js.map +1 -1
- package/dist/generators/skills-generator.d.ts.map +1 -1
- package/dist/generators/skills-generator.js +36 -3
- package/dist/generators/skills-generator.js.map +1 -1
- package/dist/gui/assets/AgentsPage-C7Rh_dp7.js +3 -0
- package/dist/gui/assets/DashboardPage-YVONGtqS.js +1 -0
- package/dist/gui/assets/{GettingStartedPage-1MOTng3B.js → GettingStartedPage-C2Kbvz_N.js} +1 -1
- package/dist/gui/assets/ProjectsPage-DOpfV2Oj.js +1 -0
- package/dist/gui/assets/{ScaffoldRepoInline-CP0yn5mw.js → ScaffoldRepoInline-D91aitAi.js} +1 -1
- package/dist/gui/assets/{SettingsPage-DWmrLV8o.js → SettingsPage-BdaHN6fa.js} +1 -1
- package/dist/gui/assets/SkillsPage-DT9lRkCg.js +7 -0
- package/dist/gui/assets/StartWorkPage-D932lq9x.js +1 -0
- package/dist/gui/assets/{StrategiesPage-CdCbbIu-.js → StrategiesPage-DCfS-wdu.js} +2 -2
- package/dist/gui/assets/WorkroomsPage-BQU27nWS.js +4 -0
- package/dist/gui/assets/WorkspacesPage-D3TLVmq-.js +13 -0
- package/dist/gui/assets/{alert-dialog-u7qgb9Hq.js → alert-dialog-YynbGyj1.js} +1 -1
- package/dist/gui/assets/badge-DTBzXOWn.js +1 -0
- package/dist/gui/assets/index-er-mkc2S.css +2 -0
- package/dist/gui/assets/index-hTa55BQ6.js +57 -0
- package/dist/gui/assets/square-check-big-Bj8Rf_dH.js +1 -0
- package/dist/gui/assets/tabs-DfW7Akjs.js +1 -0
- package/dist/gui/assets/{useAriaLabelledBy-C4zV-1Ra.js → useAriaLabelledBy-BmJErI5a.js} +1 -1
- package/dist/gui/assets/vsc-BsVrJLSg.js +1 -0
- package/dist/gui/index.html +3 -5
- package/dist/http/work-guidance-routes.d.ts +4 -0
- package/dist/http/work-guidance-routes.d.ts.map +1 -0
- package/dist/http/work-guidance-routes.js +50 -0
- package/dist/http/work-guidance-routes.js.map +1 -0
- package/dist/index.js +169 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/tools.d.ts +0 -7
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +207 -0
- package/dist/mcp/tools.js.map +1 -1
- package/dist/resources/contracts.d.ts.map +1 -1
- package/dist/resources/contracts.js +2 -1
- package/dist/resources/contracts.js.map +1 -1
- package/dist/resources/fs-safety.d.ts.map +1 -1
- package/dist/resources/fs-safety.js +2 -1
- package/dist/resources/fs-safety.js.map +1 -1
- package/dist/resources/materializer.d.ts +3 -0
- package/dist/resources/materializer.d.ts.map +1 -1
- package/dist/resources/materializer.js +45 -19
- package/dist/resources/materializer.js.map +1 -1
- package/dist/resources/service.d.ts +1 -1
- package/dist/resources/service.d.ts.map +1 -1
- package/dist/resources/service.js +3 -2
- package/dist/resources/service.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +333 -8
- package/dist/server.js.map +1 -1
- package/dist/static-resources/skills/contextspace-verify-release/SKILL.md +51 -0
- package/dist/static-resources/skills/contextspace-verify-release/references/evidence.md +21 -0
- package/dist/static-resources/skills/nexusflow-dev/SKILL.md +49 -0
- package/dist/static-resources/skills/nexusflow-dev/references/cli-conventions.md +15 -0
- package/dist/static-resources/skills/nexusflow-dev/references/verification-checklist.md +28 -0
- package/dist/static-resources/skills/nexusflow-lifecycle/SKILL.md +66 -0
- package/dist/static-resources/skills/nexusflow-lifecycle/references/epic-deliverables.md +36 -0
- package/dist/static-resources/skills/nexusflow-lifecycle/references/flow-selection-matrix.md +21 -0
- package/dist/static-resources/skills/nexusflow-lifecycle/references/stage-outcomes.md +23 -0
- package/dist/static-resources/workflows/epic-multi-slice.md +21 -0
- package/dist/static-resources/workflows/plan-implement-review.md +17 -6
- package/dist/static-resources/workflows/research-verify.md +15 -4
- package/dist/static-resources/workflows/solo-developer.md +11 -2
- package/dist/test-setup.d.ts +2 -0
- package/dist/test-setup.d.ts.map +1 -0
- package/dist/test-setup.js +8 -0
- package/dist/test-setup.js.map +1 -0
- package/dist/types.d.ts +178 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/resolve-workspace.d.ts.map +1 -1
- package/dist/utils/resolve-workspace.js +15 -8
- package/dist/utils/resolve-workspace.js.map +1 -1
- package/dist/utils/skills-catalog.d.ts +18 -9
- package/dist/utils/skills-catalog.d.ts.map +1 -1
- package/dist/utils/skills-catalog.js +248 -344
- package/dist/utils/skills-catalog.js.map +1 -1
- package/dist/utils/workflow-advisor.d.ts +1 -1
- package/dist/utils/workflow-advisor.d.ts.map +1 -1
- package/dist/utils/workflow-advisor.js +16 -4
- package/dist/utils/workflow-advisor.js.map +1 -1
- package/dist/utils/workflows.d.ts.map +1 -1
- package/dist/utils/workflows.js +5 -2
- package/dist/utils/workflows.js.map +1 -1
- package/dist/workrooms/contracts.d.ts +3 -3
- package/dist/workrooms/local-resources.d.ts +4 -1
- package/dist/workrooms/local-resources.d.ts.map +1 -1
- package/dist/workrooms/manager.d.ts +5 -2
- package/dist/workrooms/manager.d.ts.map +1 -1
- package/dist/workrooms/store.d.ts +2 -2
- package/package.json +3 -2
- package/resources/skills/contextspace-verify-release/SKILL.md +51 -0
- package/resources/skills/contextspace-verify-release/references/evidence.md +21 -0
- package/resources/skills/nexusflow-dev/SKILL.md +49 -0
- package/resources/skills/nexusflow-dev/references/cli-conventions.md +15 -0
- package/resources/skills/nexusflow-dev/references/verification-checklist.md +28 -0
- package/resources/skills/nexusflow-lifecycle/SKILL.md +66 -0
- package/resources/skills/nexusflow-lifecycle/references/epic-deliverables.md +36 -0
- package/resources/skills/nexusflow-lifecycle/references/flow-selection-matrix.md +21 -0
- package/resources/skills/nexusflow-lifecycle/references/stage-outcomes.md +23 -0
- package/resources/workflows/epic-multi-slice.md +21 -0
- package/resources/workflows/plan-implement-review.md +17 -6
- package/resources/workflows/research-verify.md +15 -4
- package/resources/workflows/solo-developer.md +11 -2
- package/dist/gui/assets/AgentsPage-DbivhmK0.js +0 -3
- package/dist/gui/assets/DashboardPage-D50Qrm3e.js +0 -1
- package/dist/gui/assets/NullStore-tdPen1kp.js +0 -1
- package/dist/gui/assets/ProjectsPage-srKgf7VN.js +0 -1
- package/dist/gui/assets/SkillsPage-CKjTXdrd.js +0 -6
- package/dist/gui/assets/StartWorkPage-BCUnJ9k8.js +0 -1
- package/dist/gui/assets/WorkroomsPage-BcT-mGtT.js +0 -4
- package/dist/gui/assets/WorkspacesPage-CYFCm-7_.js +0 -2
- package/dist/gui/assets/badge-DwXvB44H.js +0 -1
- package/dist/gui/assets/chunk-BV7QT456-DfUBKG-z.js +0 -3
- package/dist/gui/assets/index-3tD_IeyQ.js +0 -55
- package/dist/gui/assets/index-CK5YRfPV.css +0 -2
- package/dist/gui/assets/square-check-big-BSIiYXPI.js +0 -1
- package/dist/gui/assets/tabs-BrHKp5bD.js +0 -1
- 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
|
-
//
|
|
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
|
|
2700
|
-
|
|
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
|
|
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
|
-
|
|
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',
|