@agile-vibe-coding/avc 0.2.3 → 0.3.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/cli/agents/agent-selector.md +23 -0
- package/cli/agents/code-implementer.md +117 -0
- package/cli/agents/code-validator.md +80 -0
- package/cli/agents/context-reviewer-epic.md +101 -0
- package/cli/agents/context-reviewer-story.md +92 -0
- package/cli/agents/context-writer-epic.md +145 -0
- package/cli/agents/context-writer-story.md +111 -0
- package/cli/agents/doc-writer-epic.md +42 -0
- package/cli/agents/doc-writer-story.md +43 -0
- package/cli/agents/duplicate-detector.md +110 -0
- package/cli/agents/epic-story-decomposer.md +318 -39
- package/cli/agents/mission-scope-generator.md +68 -4
- package/cli/agents/mission-scope-validator.md +40 -6
- package/cli/agents/project-context-extractor.md +21 -6
- package/cli/agents/scaffolding-generator.md +99 -0
- package/cli/agents/seed-validator.md +71 -0
- package/cli/agents/story-scope-reviewer.md +147 -0
- package/cli/agents/story-splitter.md +83 -0
- package/cli/agents/validator-documentation.json +31 -0
- package/cli/agents/validator-documentation.md +3 -1
- package/cli/api-reference-tool.js +368 -0
- package/cli/checks/catalog.json +76 -0
- package/cli/checks/code/quality.json +26 -0
- package/cli/checks/code/testing.json +14 -0
- package/cli/checks/code/traceability.json +26 -0
- package/cli/checks/cross-refs/epic.json +171 -0
- package/cli/checks/cross-refs/story.json +149 -0
- package/cli/checks/epic/api.json +114 -0
- package/cli/checks/epic/backend.json +126 -0
- package/cli/checks/epic/cloud.json +126 -0
- package/cli/checks/epic/data.json +102 -0
- package/cli/checks/epic/database.json +114 -0
- package/cli/checks/epic/developer.json +182 -0
- package/cli/checks/epic/devops.json +174 -0
- package/cli/checks/epic/frontend.json +162 -0
- package/cli/checks/epic/mobile.json +102 -0
- package/cli/checks/epic/qa.json +90 -0
- package/cli/checks/epic/security.json +184 -0
- package/cli/checks/epic/solution-architect.json +192 -0
- package/cli/checks/epic/test-architect.json +90 -0
- package/cli/checks/epic/ui.json +102 -0
- package/cli/checks/epic/ux.json +90 -0
- package/cli/checks/fixes/epic-fix-template.md +10 -0
- package/cli/checks/fixes/story-fix-template.md +10 -0
- package/cli/checks/story/api.json +186 -0
- package/cli/checks/story/backend.json +102 -0
- package/cli/checks/story/cloud.json +102 -0
- package/cli/checks/story/data.json +210 -0
- package/cli/checks/story/database.json +102 -0
- package/cli/checks/story/developer.json +168 -0
- package/cli/checks/story/devops.json +102 -0
- package/cli/checks/story/frontend.json +174 -0
- package/cli/checks/story/mobile.json +102 -0
- package/cli/checks/story/qa.json +210 -0
- package/cli/checks/story/security.json +198 -0
- package/cli/checks/story/solution-architect.json +230 -0
- package/cli/checks/story/test-architect.json +210 -0
- package/cli/checks/story/ui.json +102 -0
- package/cli/checks/story/ux.json +102 -0
- package/cli/coding-order.js +401 -0
- package/cli/dependency-checker.js +72 -0
- package/cli/epic-story-validator.js +284 -799
- package/cli/index.js +0 -0
- package/cli/init-model-config.js +17 -10
- package/cli/init.js +514 -92
- package/cli/kanban-server-manager.js +1 -2
- package/cli/llm-claude.js +98 -31
- package/cli/llm-gemini.js +29 -5
- package/cli/llm-local.js +493 -0
- package/cli/llm-openai.js +262 -41
- package/cli/llm-provider.js +147 -8
- package/cli/llm-token-limits.js +113 -4
- package/cli/llm-verifier.js +209 -1
- package/cli/llm-xiaomi.js +143 -0
- package/cli/message-constants.js +3 -12
- package/cli/messaging-api.js +6 -12
- package/cli/micro-check-fixer.js +335 -0
- package/cli/micro-check-runner.js +449 -0
- package/cli/micro-check-scorer.js +148 -0
- package/cli/micro-check-validator.js +538 -0
- package/cli/model-pricing.js +23 -0
- package/cli/model-selector.js +3 -2
- package/cli/prompt-logger.js +57 -0
- package/cli/repl-ink.js +106 -346
- package/cli/repl-old.js +1 -2
- package/cli/seed-processor.js +194 -24
- package/cli/sprint-planning-processor.js +2638 -289
- package/cli/template-processor.js +50 -3
- package/cli/token-tracker.js +50 -23
- package/cli/tools/generate-story-validators.js +1 -1
- package/cli/validation-router.js +70 -8
- package/cli/worktree-runner.js +654 -0
- package/kanban/client/dist/assets/index-D_KC5EQT.css +1 -0
- package/kanban/client/dist/assets/index-DjY5zqW7.js +351 -0
- package/kanban/client/dist/index.html +2 -2
- package/kanban/client/src/App.jsx +43 -14
- package/kanban/client/src/components/ceremony/AskArchPopup.jsx +7 -3
- package/kanban/client/src/components/ceremony/AskModelPopup.jsx +23 -10
- package/kanban/client/src/components/ceremony/CeremonyWorkflowModal.jsx +320 -133
- package/kanban/client/src/components/ceremony/ProviderSwitcherButton.jsx +290 -0
- package/kanban/client/src/components/ceremony/SponsorCallModal.jsx +80 -13
- package/kanban/client/src/components/ceremony/SprintPlanningModal.jsx +156 -22
- package/kanban/client/src/components/ceremony/steps/ArchitectureStep.jsx +11 -11
- package/kanban/client/src/components/ceremony/steps/CompleteStep.jsx +3 -21
- package/kanban/client/src/components/ceremony/steps/ReviewAnswersStep.jsx +214 -10
- package/kanban/client/src/components/ceremony/steps/RunningStep.jsx +23 -2
- package/kanban/client/src/components/kanban/CardDetailModal.jsx +97 -10
- package/kanban/client/src/components/kanban/GroupingSelector.jsx +7 -1
- package/kanban/client/src/components/kanban/KanbanCard.jsx +23 -14
- package/kanban/client/src/components/kanban/RefineWorkItemPopup.jsx +9 -14
- package/kanban/client/src/components/kanban/RunButton.jsx +162 -0
- package/kanban/client/src/components/kanban/SeedButton.jsx +176 -0
- package/kanban/client/src/components/settings/AgentsTab.jsx +103 -75
- package/kanban/client/src/components/settings/ApiKeysTab.jsx +31 -2
- package/kanban/client/src/components/settings/CeremonyModelsTab.jsx +9 -2
- package/kanban/client/src/components/settings/CheckEditorPopup.jsx +507 -0
- package/kanban/client/src/components/settings/CostThresholdsTab.jsx +3 -2
- package/kanban/client/src/components/settings/ModelPricingTab.jsx +72 -7
- package/kanban/client/src/components/settings/OpenAIAuthSection.jsx +412 -0
- package/kanban/client/src/components/settings/SettingsModal.jsx +4 -4
- package/kanban/client/src/components/stats/CostModal.jsx +34 -3
- package/kanban/client/src/hooks/useGrouping.js +59 -0
- package/kanban/client/src/lib/api.js +118 -4
- package/kanban/client/src/lib/status-grouping.js +10 -0
- package/kanban/client/src/store/kanbanStore.js +8 -0
- package/kanban/server/index.js +23 -2
- package/kanban/server/routes/ceremony.js +153 -4
- package/kanban/server/routes/costs.js +9 -3
- package/kanban/server/routes/openai-oauth.js +366 -0
- package/kanban/server/routes/settings.js +447 -14
- package/kanban/server/routes/websocket.js +7 -2
- package/kanban/server/routes/work-items.js +141 -1
- package/kanban/server/services/CeremonyService.js +275 -24
- package/kanban/server/services/TaskRunnerService.js +261 -0
- package/kanban/server/workers/run-task-worker.js +121 -0
- package/kanban/server/workers/seed-worker.js +94 -0
- package/kanban/server/workers/sponsor-call-worker.js +14 -6
- package/kanban/server/workers/sprint-planning-worker.js +94 -12
- package/package.json +2 -3
- package/cli/agents/solver-epic-api.json +0 -15
- package/cli/agents/solver-epic-api.md +0 -39
- package/cli/agents/solver-epic-backend.json +0 -15
- package/cli/agents/solver-epic-backend.md +0 -39
- package/cli/agents/solver-epic-cloud.json +0 -15
- package/cli/agents/solver-epic-cloud.md +0 -39
- package/cli/agents/solver-epic-data.json +0 -15
- package/cli/agents/solver-epic-data.md +0 -39
- package/cli/agents/solver-epic-database.json +0 -15
- package/cli/agents/solver-epic-database.md +0 -39
- package/cli/agents/solver-epic-developer.json +0 -15
- package/cli/agents/solver-epic-developer.md +0 -39
- package/cli/agents/solver-epic-devops.json +0 -15
- package/cli/agents/solver-epic-devops.md +0 -39
- package/cli/agents/solver-epic-frontend.json +0 -15
- package/cli/agents/solver-epic-frontend.md +0 -39
- package/cli/agents/solver-epic-mobile.json +0 -15
- package/cli/agents/solver-epic-mobile.md +0 -39
- package/cli/agents/solver-epic-qa.json +0 -15
- package/cli/agents/solver-epic-qa.md +0 -39
- package/cli/agents/solver-epic-security.json +0 -15
- package/cli/agents/solver-epic-security.md +0 -39
- package/cli/agents/solver-epic-solution-architect.json +0 -15
- package/cli/agents/solver-epic-solution-architect.md +0 -39
- package/cli/agents/solver-epic-test-architect.json +0 -15
- package/cli/agents/solver-epic-test-architect.md +0 -39
- package/cli/agents/solver-epic-ui.json +0 -15
- package/cli/agents/solver-epic-ui.md +0 -39
- package/cli/agents/solver-epic-ux.json +0 -15
- package/cli/agents/solver-epic-ux.md +0 -39
- package/cli/agents/solver-story-api.json +0 -15
- package/cli/agents/solver-story-api.md +0 -39
- package/cli/agents/solver-story-backend.json +0 -15
- package/cli/agents/solver-story-backend.md +0 -39
- package/cli/agents/solver-story-cloud.json +0 -15
- package/cli/agents/solver-story-cloud.md +0 -39
- package/cli/agents/solver-story-data.json +0 -15
- package/cli/agents/solver-story-data.md +0 -39
- package/cli/agents/solver-story-database.json +0 -15
- package/cli/agents/solver-story-database.md +0 -39
- package/cli/agents/solver-story-developer.json +0 -15
- package/cli/agents/solver-story-developer.md +0 -39
- package/cli/agents/solver-story-devops.json +0 -15
- package/cli/agents/solver-story-devops.md +0 -39
- package/cli/agents/solver-story-frontend.json +0 -15
- package/cli/agents/solver-story-frontend.md +0 -39
- package/cli/agents/solver-story-mobile.json +0 -15
- package/cli/agents/solver-story-mobile.md +0 -39
- package/cli/agents/solver-story-qa.json +0 -15
- package/cli/agents/solver-story-qa.md +0 -39
- package/cli/agents/solver-story-security.json +0 -15
- package/cli/agents/solver-story-security.md +0 -39
- package/cli/agents/solver-story-solution-architect.json +0 -15
- package/cli/agents/solver-story-solution-architect.md +0 -39
- package/cli/agents/solver-story-test-architect.json +0 -15
- package/cli/agents/solver-story-test-architect.md +0 -39
- package/cli/agents/solver-story-ui.json +0 -15
- package/cli/agents/solver-story-ui.md +0 -39
- package/cli/agents/solver-story-ux.json +0 -15
- package/cli/agents/solver-story-ux.md +0 -39
- package/cli/agents/validator-epic-api.json +0 -93
- package/cli/agents/validator-epic-api.md +0 -137
- package/cli/agents/validator-epic-backend.json +0 -93
- package/cli/agents/validator-epic-backend.md +0 -130
- package/cli/agents/validator-epic-cloud.json +0 -93
- package/cli/agents/validator-epic-cloud.md +0 -137
- package/cli/agents/validator-epic-data.json +0 -93
- package/cli/agents/validator-epic-data.md +0 -130
- package/cli/agents/validator-epic-database.json +0 -93
- package/cli/agents/validator-epic-database.md +0 -137
- package/cli/agents/validator-epic-developer.json +0 -74
- package/cli/agents/validator-epic-developer.md +0 -153
- package/cli/agents/validator-epic-devops.json +0 -74
- package/cli/agents/validator-epic-devops.md +0 -153
- package/cli/agents/validator-epic-frontend.json +0 -74
- package/cli/agents/validator-epic-frontend.md +0 -153
- package/cli/agents/validator-epic-mobile.json +0 -93
- package/cli/agents/validator-epic-mobile.md +0 -130
- package/cli/agents/validator-epic-qa.json +0 -93
- package/cli/agents/validator-epic-qa.md +0 -130
- package/cli/agents/validator-epic-security.json +0 -74
- package/cli/agents/validator-epic-security.md +0 -154
- package/cli/agents/validator-epic-solution-architect.json +0 -74
- package/cli/agents/validator-epic-solution-architect.md +0 -156
- package/cli/agents/validator-epic-test-architect.json +0 -93
- package/cli/agents/validator-epic-test-architect.md +0 -130
- package/cli/agents/validator-epic-ui.json +0 -93
- package/cli/agents/validator-epic-ui.md +0 -130
- package/cli/agents/validator-epic-ux.json +0 -93
- package/cli/agents/validator-epic-ux.md +0 -130
- package/cli/agents/validator-story-api.json +0 -104
- package/cli/agents/validator-story-api.md +0 -152
- package/cli/agents/validator-story-backend.json +0 -104
- package/cli/agents/validator-story-backend.md +0 -152
- package/cli/agents/validator-story-cloud.json +0 -104
- package/cli/agents/validator-story-cloud.md +0 -152
- package/cli/agents/validator-story-data.json +0 -104
- package/cli/agents/validator-story-data.md +0 -152
- package/cli/agents/validator-story-database.json +0 -104
- package/cli/agents/validator-story-database.md +0 -152
- package/cli/agents/validator-story-developer.json +0 -104
- package/cli/agents/validator-story-developer.md +0 -152
- package/cli/agents/validator-story-devops.json +0 -104
- package/cli/agents/validator-story-devops.md +0 -152
- package/cli/agents/validator-story-frontend.json +0 -104
- package/cli/agents/validator-story-frontend.md +0 -152
- package/cli/agents/validator-story-mobile.json +0 -104
- package/cli/agents/validator-story-mobile.md +0 -152
- package/cli/agents/validator-story-qa.json +0 -104
- package/cli/agents/validator-story-qa.md +0 -152
- package/cli/agents/validator-story-security.json +0 -104
- package/cli/agents/validator-story-security.md +0 -152
- package/cli/agents/validator-story-solution-architect.json +0 -104
- package/cli/agents/validator-story-solution-architect.md +0 -152
- package/cli/agents/validator-story-test-architect.json +0 -104
- package/cli/agents/validator-story-test-architect.md +0 -152
- package/cli/agents/validator-story-ui.json +0 -104
- package/cli/agents/validator-story-ui.md +0 -152
- package/cli/agents/validator-story-ux.json +0 -104
- package/cli/agents/validator-story-ux.md +0 -152
- package/kanban/client/dist/assets/index-CiD8PS2e.js +0 -306
- package/kanban/client/dist/assets/index-nLh0m82Q.css +0 -1
|
@@ -244,8 +244,15 @@ export async function runCeremony(requirements) {
|
|
|
244
244
|
});
|
|
245
245
|
}
|
|
246
246
|
|
|
247
|
-
export async function runSprintPlanning() {
|
|
248
|
-
return apiFetch('/ceremony/sprint-planning/run', {
|
|
247
|
+
export async function runSprintPlanning(resumeFrom = null) {
|
|
248
|
+
return apiFetch('/ceremony/sprint-planning/run', {
|
|
249
|
+
method: 'POST',
|
|
250
|
+
...(resumeFrom ? { body: JSON.stringify({ resumeFrom }) } : {}),
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export async function getSprintPlanningResumable() {
|
|
255
|
+
return apiFetch('/ceremony/sprint-planning/resumable');
|
|
249
256
|
}
|
|
250
257
|
|
|
251
258
|
export const confirmSprintPlanningSelection = (selectedEpicIds, selectedStoryIds) =>
|
|
@@ -256,12 +263,31 @@ export const confirmSprintPlanningSelection = (selectedEpicIds, selectedStoryIds
|
|
|
256
263
|
|
|
257
264
|
export const pauseCeremony = () => apiFetch('/ceremony/pause', { method: 'POST' });
|
|
258
265
|
export const resumeCeremony = () => apiFetch('/ceremony/resume', { method: 'POST' });
|
|
259
|
-
export const cancelCeremony = () => apiFetch('/ceremony/cancel',
|
|
266
|
+
export const cancelCeremony = (keepItems = false) => apiFetch('/ceremony/cancel', { method: 'POST', body: JSON.stringify({ keepItems }) });
|
|
260
267
|
export const resetCeremony = () => apiFetch('/ceremony/reset', { method: 'POST' });
|
|
261
268
|
export const continuePastCostLimit = () => apiFetch('/ceremony/cost-limit-continue', { method: 'POST' });
|
|
269
|
+
export const continueAfterQuota = (newProvider = null, newModel = null) =>
|
|
270
|
+
apiFetch('/ceremony/quota-continue', { method: 'POST', body: JSON.stringify({ newProvider, newModel }) });
|
|
262
271
|
|
|
263
272
|
export async function getModels() {
|
|
264
|
-
|
|
273
|
+
const [models, local] = await Promise.all([
|
|
274
|
+
apiFetch('/ceremony/models'),
|
|
275
|
+
apiFetch('/settings/local-models').catch(() => ({ servers: [] })),
|
|
276
|
+
]);
|
|
277
|
+
// Merge discovered local models into the list
|
|
278
|
+
const localModels = (local.servers || []).flatMap((srv) =>
|
|
279
|
+
srv.models.map((lm) => ({
|
|
280
|
+
modelId: lm.id,
|
|
281
|
+
displayName: `${lm.id} (${srv.label})`,
|
|
282
|
+
provider: 'local',
|
|
283
|
+
hasApiKey: true,
|
|
284
|
+
}))
|
|
285
|
+
);
|
|
286
|
+
return [...models, ...localModels];
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export async function getLocalModels() {
|
|
290
|
+
return apiFetch('/settings/local-models');
|
|
265
291
|
}
|
|
266
292
|
|
|
267
293
|
export async function generateMission(description, modelId, provider, validatorModelId, validatorProvider) {
|
|
@@ -292,6 +318,13 @@ export async function refineArchitecture(currentArch, refinementRequest, modelId
|
|
|
292
318
|
});
|
|
293
319
|
}
|
|
294
320
|
|
|
321
|
+
export async function refineField(fieldKey, fieldLabel, currentValue, refinementRequest, context, modelId, provider) {
|
|
322
|
+
return apiFetch('/ceremony/refine-field', {
|
|
323
|
+
method: 'POST',
|
|
324
|
+
body: JSON.stringify({ fieldKey, fieldLabel, currentValue, refinementRequest, context, modelId, provider }),
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
295
328
|
// ── Sponsor-call draft (resume support) ──────────────────────────────────────
|
|
296
329
|
|
|
297
330
|
export async function getSponsorCallDraft() {
|
|
@@ -336,6 +369,27 @@ export async function resetAgent(name) {
|
|
|
336
369
|
return apiFetch(`/settings/agents/${encodeURIComponent(name)}`, { method: 'DELETE' });
|
|
337
370
|
}
|
|
338
371
|
|
|
372
|
+
// ── Micro-Check Definitions API ──────────────────────────────────────────────
|
|
373
|
+
|
|
374
|
+
export async function getCheckList() {
|
|
375
|
+
return apiFetch('/settings/checks');
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
export async function getCheckContent(scope, perspective) {
|
|
379
|
+
return apiFetch(`/settings/checks/${encodeURIComponent(scope)}/${encodeURIComponent(perspective)}`);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
export async function saveCheckContent(scope, perspective, content) {
|
|
383
|
+
return apiFetch(`/settings/checks/${encodeURIComponent(scope)}/${encodeURIComponent(perspective)}`, {
|
|
384
|
+
method: 'PUT',
|
|
385
|
+
body: JSON.stringify({ content }),
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
export async function resetCheck(scope, perspective) {
|
|
390
|
+
return apiFetch(`/settings/checks/${encodeURIComponent(scope)}/${encodeURIComponent(perspective)}`, { method: 'DELETE' });
|
|
391
|
+
}
|
|
392
|
+
|
|
339
393
|
// ── Processes API ─────────────────────────────────────────────────────────────
|
|
340
394
|
|
|
341
395
|
export async function getProcesses() {
|
|
@@ -378,6 +432,28 @@ export async function applyWorkItemChanges(id, proposedItem, storyChanges = [])
|
|
|
378
432
|
});
|
|
379
433
|
}
|
|
380
434
|
|
|
435
|
+
// ── OpenAI OAuth API ──────────────────────────────────────────────────────────
|
|
436
|
+
|
|
437
|
+
export async function connectOpenAIOAuth() {
|
|
438
|
+
return apiFetch('/settings/openai-oauth/login', { method: 'POST' });
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
export async function disconnectOpenAIOAuth() {
|
|
442
|
+
return apiFetch('/settings/openai-oauth/logout', { method: 'POST' });
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
export async function getOpenAIOAuthStatus() {
|
|
446
|
+
return apiFetch('/settings/openai-oauth/status');
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
export async function testOpenAIOAuth() {
|
|
450
|
+
return apiFetch('/settings/openai-oauth/test', { method: 'POST' });
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
export async function setOpenAIOAuthFallback(enabled) {
|
|
454
|
+
return apiFetch('/settings/openai-oauth/fallback', { method: 'POST', body: JSON.stringify({ enabled }) });
|
|
455
|
+
}
|
|
456
|
+
|
|
381
457
|
// ── Costs API ─────────────────────────────────────────────────────────────────
|
|
382
458
|
|
|
383
459
|
/**
|
|
@@ -399,3 +475,41 @@ export async function getCostHistory(range = 30) {
|
|
|
399
475
|
}
|
|
400
476
|
return apiFetch(`/costs/history?from=${range.from}&to=${range.to}`);
|
|
401
477
|
}
|
|
478
|
+
|
|
479
|
+
// ---- Task Runner: Seed + Run ----
|
|
480
|
+
|
|
481
|
+
export async function updateWorkItemStatus(id, status) {
|
|
482
|
+
return apiFetch(`/work-items/${id}/status`, {
|
|
483
|
+
method: 'PUT',
|
|
484
|
+
body: JSON.stringify({ status }),
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
export async function getDependencyStatus(id) {
|
|
489
|
+
return apiFetch(`/work-items/${id}/dependency-status`);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
export async function startSeedCeremony(storyId) {
|
|
493
|
+
return apiFetch('/ceremony/seed/run', {
|
|
494
|
+
method: 'POST',
|
|
495
|
+
body: JSON.stringify({ storyId }),
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
export async function startRunTask(taskId) {
|
|
500
|
+
return apiFetch('/ceremony/run-task/run', {
|
|
501
|
+
method: 'POST',
|
|
502
|
+
body: JSON.stringify({ taskId }),
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
export async function getTaskRunnerStatus() {
|
|
507
|
+
return apiFetch('/ceremony/task-runner/status');
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
export async function cancelTaskRun(processId) {
|
|
511
|
+
return apiFetch('/ceremony/task-runner/cancel', {
|
|
512
|
+
method: 'POST',
|
|
513
|
+
body: JSON.stringify({ processId }),
|
|
514
|
+
});
|
|
515
|
+
}
|
|
@@ -118,6 +118,16 @@ export function groupItemsByColumn(workItems) {
|
|
|
118
118
|
}
|
|
119
119
|
});
|
|
120
120
|
|
|
121
|
+
// Sort items within each column by coding order (if available), then by ID
|
|
122
|
+
for (const column of COLUMN_ORDER) {
|
|
123
|
+
grouped[column].sort((a, b) => {
|
|
124
|
+
const orderA = a.metadata?.codingOrder ?? Infinity;
|
|
125
|
+
const orderB = b.metadata?.codingOrder ?? Infinity;
|
|
126
|
+
if (orderA !== orderB) return orderA - orderB;
|
|
127
|
+
return (a.id || '').localeCompare(b.id || '');
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
121
131
|
return grouped;
|
|
122
132
|
}
|
|
123
133
|
|
|
@@ -12,6 +12,7 @@ export const useKanbanStore = create((set, get) => ({
|
|
|
12
12
|
loading: false,
|
|
13
13
|
error: null,
|
|
14
14
|
lastUpdated: null,
|
|
15
|
+
ceremonyActive: false, // true while sprint-planning is writing items — board is read-only
|
|
15
16
|
|
|
16
17
|
// Actions
|
|
17
18
|
|
|
@@ -112,4 +113,11 @@ export const useKanbanStore = create((set, get) => ({
|
|
|
112
113
|
clearError: () => {
|
|
113
114
|
set({ error: null });
|
|
114
115
|
},
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Set ceremony active state (read-only mode for work items)
|
|
119
|
+
*/
|
|
120
|
+
setCeremonyActive: (active) => {
|
|
121
|
+
set({ ceremonyActive: active });
|
|
122
|
+
},
|
|
115
123
|
}));
|
package/kanban/server/index.js
CHANGED
|
@@ -13,10 +13,12 @@ import { createWorkItemsRouter } from './routes/work-items.js';
|
|
|
13
13
|
import { createCeremonyRouter } from './routes/ceremony.js';
|
|
14
14
|
import { createProcessesRouter } from './routes/processes.js';
|
|
15
15
|
import { createSettingsRouter } from './routes/settings.js';
|
|
16
|
+
import { createOpenAIOAuthRouter } from './routes/openai-oauth.js';
|
|
16
17
|
import { createCostsRouter } from './routes/costs.js';
|
|
17
18
|
import { setupWebSocket } from './routes/websocket.js';
|
|
18
19
|
import { renderMarkdown } from './utils/markdown.js';
|
|
19
20
|
import { CeremonyService } from './services/CeremonyService.js';
|
|
21
|
+
import { TaskRunnerService } from './services/TaskRunnerService.js';
|
|
20
22
|
import { ProcessRegistry } from './services/ProcessRegistry.js';
|
|
21
23
|
import { WorkItemRefineService } from './services/WorkItemRefineService.js';
|
|
22
24
|
|
|
@@ -51,6 +53,9 @@ export class KanbanServer {
|
|
|
51
53
|
this.ceremonyService = new CeremonyService(projectRoot);
|
|
52
54
|
this.processRegistry = new ProcessRegistry();
|
|
53
55
|
|
|
56
|
+
// Task runner service for seed + run-task operations
|
|
57
|
+
this.taskRunnerService = new TaskRunnerService(projectRoot);
|
|
58
|
+
|
|
54
59
|
// Work item refine service (websocket injected after start())
|
|
55
60
|
this.refineService = new WorkItemRefineService(projectRoot);
|
|
56
61
|
|
|
@@ -186,6 +191,13 @@ export class KanbanServer {
|
|
|
186
191
|
const settingsRouter = createSettingsRouter(this.projectRoot);
|
|
187
192
|
this.app.use('/api/settings', settingsRouter);
|
|
188
193
|
|
|
194
|
+
// OpenAI OAuth router
|
|
195
|
+
const openaiOAuthRouter = createOpenAIOAuthRouter(
|
|
196
|
+
this.projectRoot,
|
|
197
|
+
() => this.websocket,
|
|
198
|
+
);
|
|
199
|
+
this.app.use('/api/settings/openai-oauth', openaiOAuthRouter);
|
|
200
|
+
|
|
189
201
|
// Board title setting (read/write from avc.json)
|
|
190
202
|
const avcJsonPath = path.join(this.projectRoot, '.avc', 'avc.json');
|
|
191
203
|
const DEFAULT_TITLE = 'AVC Kanban Board';
|
|
@@ -227,11 +239,11 @@ export class KanbanServer {
|
|
|
227
239
|
});
|
|
228
240
|
|
|
229
241
|
// Work items routes
|
|
230
|
-
const workItemsRouter = createWorkItemsRouter(this, this.refineService);
|
|
242
|
+
const workItemsRouter = createWorkItemsRouter(this, this.refineService, this.ceremonyService);
|
|
231
243
|
this.app.use('/api/work-items', workItemsRouter);
|
|
232
244
|
|
|
233
245
|
// Ceremony routes
|
|
234
|
-
const ceremonyRouter = createCeremonyRouter(this.ceremonyService, this.processRegistry);
|
|
246
|
+
const ceremonyRouter = createCeremonyRouter(this.ceremonyService, this.processRegistry, this.taskRunnerService);
|
|
235
247
|
this.app.use('/api/ceremony', ceremonyRouter);
|
|
236
248
|
|
|
237
249
|
// Process monitor routes
|
|
@@ -420,6 +432,11 @@ export class KanbanServer {
|
|
|
420
432
|
// Create HTTP server
|
|
421
433
|
this.server = http.createServer(this.app);
|
|
422
434
|
|
|
435
|
+
// Allow long-running LLM requests (local models can be very slow)
|
|
436
|
+
this.server.timeout = 30 * 60 * 1000; // 30 min socket timeout
|
|
437
|
+
this.server.headersTimeout = 30 * 60 * 1000; // 30 min headers timeout
|
|
438
|
+
this.server.requestTimeout = 30 * 60 * 1000; // 30 min request timeout
|
|
439
|
+
|
|
423
440
|
// Setup WebSocket (pass processRegistry for init message to new clients)
|
|
424
441
|
this.websocket = setupWebSocket(this.server, this, this.processRegistry, this.ceremonyService);
|
|
425
442
|
|
|
@@ -427,6 +444,10 @@ export class KanbanServer {
|
|
|
427
444
|
this.ceremonyService.setWebSocket(this.websocket);
|
|
428
445
|
this.ceremonyService.setReloadCallback(() => this.reloadWorkItems());
|
|
429
446
|
|
|
447
|
+
// Wire task runner service to WebSocket for broadcasting
|
|
448
|
+
this.taskRunnerService.setWebSocket(this.websocket);
|
|
449
|
+
this.taskRunnerService.setReloadCallback(() => this.reloadWorkItems());
|
|
450
|
+
|
|
430
451
|
// Wire refine service to WebSocket
|
|
431
452
|
this.refineService.websocket = this.websocket;
|
|
432
453
|
|
|
@@ -7,7 +7,7 @@ import path from 'path';
|
|
|
7
7
|
* Handles all /api/ceremony/* endpoints for the sponsor-call wizard.
|
|
8
8
|
* @param {CeremonyService} ceremonyService
|
|
9
9
|
*/
|
|
10
|
-
export function createCeremonyRouter(ceremonyService, processRegistry) {
|
|
10
|
+
export function createCeremonyRouter(ceremonyService, processRegistry, taskRunnerService = null) {
|
|
11
11
|
const router = express.Router();
|
|
12
12
|
|
|
13
13
|
// GET /api/ceremony/status — current ceremony state
|
|
@@ -194,6 +194,24 @@ export function createCeremonyRouter(ceremonyService, processRegistry) {
|
|
|
194
194
|
}
|
|
195
195
|
});
|
|
196
196
|
|
|
197
|
+
// POST /api/ceremony/refine-field
|
|
198
|
+
// Body: { fieldKey, fieldLabel, currentValue, refinementRequest, context: { mission, scope }, modelId, provider }
|
|
199
|
+
router.post('/refine-field', async (req, res) => {
|
|
200
|
+
const { fieldKey, fieldLabel, currentValue, refinementRequest, context, modelId, provider } = req.body;
|
|
201
|
+
if (!fieldKey || !currentValue?.trim() || !refinementRequest?.trim() || !context?.mission || !modelId || !provider) {
|
|
202
|
+
return res.status(400).json({ error: 'fieldKey, currentValue, refinementRequest, context.mission, modelId and provider are required' });
|
|
203
|
+
}
|
|
204
|
+
const start = Date.now();
|
|
205
|
+
try {
|
|
206
|
+
const result = await ceremonyService.refineField(fieldKey, fieldLabel, currentValue, refinementRequest, context, modelId, provider);
|
|
207
|
+
console.log(`[ceremony] refine-field completed in ${Date.now() - start}ms`, { fieldKey });
|
|
208
|
+
res.json(result);
|
|
209
|
+
} catch (err) {
|
|
210
|
+
console.error(`[ceremony] refine-field failed in ${Date.now() - start}ms:`, err.message);
|
|
211
|
+
res.status(500).json({ error: err.message });
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
|
|
197
215
|
// POST /api/ceremony/run
|
|
198
216
|
// Body: { requirements } — all 7 template variables
|
|
199
217
|
router.post('/run', async (req, res) => {
|
|
@@ -215,11 +233,64 @@ export function createCeremonyRouter(ceremonyService, processRegistry) {
|
|
|
215
233
|
}
|
|
216
234
|
});
|
|
217
235
|
|
|
236
|
+
// GET /api/ceremony/sprint-planning/resumable — check if previous run can be resumed
|
|
237
|
+
router.get('/sprint-planning/resumable', async (req, res) => {
|
|
238
|
+
try {
|
|
239
|
+
const { CeremonyHistory } = await import('../../../cli/ceremony-history.js');
|
|
240
|
+
const historyPath = path.join(ceremonyService.projectRoot, '.avc');
|
|
241
|
+
const history = new CeremonyHistory(historyPath);
|
|
242
|
+
history.init();
|
|
243
|
+
|
|
244
|
+
const resumableStages = ['files-written', 'docs-generated', 'enrichment-complete'];
|
|
245
|
+
const ceremonies = history.data?.ceremonies?.['sprint-planning'];
|
|
246
|
+
const executions = ceremonies?.executions || [];
|
|
247
|
+
const last = executions[executions.length - 1];
|
|
248
|
+
|
|
249
|
+
if (!last) return res.json({ resumable: false });
|
|
250
|
+
|
|
251
|
+
const isAborted = last.status === 'aborted' || last.outcome === 'abrupt-termination' ||
|
|
252
|
+
(last.status === 'in-progress');
|
|
253
|
+
const hasResumableCheckpoint = resumableStages.includes(last.stage);
|
|
254
|
+
|
|
255
|
+
if (!isAborted || !hasResumableCheckpoint) {
|
|
256
|
+
return res.json({ resumable: false, reason: isAborted ? 'checkpoint-too-early' : 'not-aborted' });
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Validate files exist on disk
|
|
260
|
+
const projectDir = path.join(ceremonyService.projectRoot, '.avc', 'project');
|
|
261
|
+
const epicDirs = fs.existsSync(projectDir)
|
|
262
|
+
? fs.readdirSync(projectDir).filter(d => d.startsWith('context-') && fs.existsSync(path.join(projectDir, d, 'work.json')))
|
|
263
|
+
: [];
|
|
264
|
+
|
|
265
|
+
if (epicDirs.length === 0) {
|
|
266
|
+
return res.json({ resumable: false, reason: 'no-files-on-disk' });
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const stageLabels = {
|
|
270
|
+
'files-written': 'File Writing (doc generation pending)',
|
|
271
|
+
'docs-generated': 'Doc Generation (enrichment pending)',
|
|
272
|
+
'enrichment-complete': 'Enrichment (summary pending)',
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
res.json({
|
|
276
|
+
resumable: true,
|
|
277
|
+
checkpoint: last.stage,
|
|
278
|
+
checkpointLabel: stageLabels[last.stage] || last.stage,
|
|
279
|
+
executionId: last.id,
|
|
280
|
+
timestamp: last.lastCheckpoint || last.startTime,
|
|
281
|
+
epics: epicDirs.length,
|
|
282
|
+
});
|
|
283
|
+
} catch (err) {
|
|
284
|
+
res.json({ resumable: false, reason: err.message });
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
218
288
|
// POST /api/ceremony/sprint-planning/run
|
|
219
289
|
router.post('/sprint-planning/run', async (req, res) => {
|
|
220
290
|
try {
|
|
221
|
-
const
|
|
222
|
-
|
|
291
|
+
const { resumeFrom } = req.body || {};
|
|
292
|
+
const processId = await ceremonyService.runSprintPlanningInProcess(processRegistry, resumeFrom || null);
|
|
293
|
+
console.log('[ceremony] sprint-planning/run started in process', processId, resumeFrom ? `(resume from ${resumeFrom})` : '');
|
|
223
294
|
res.json({ started: true, processId });
|
|
224
295
|
} catch (err) {
|
|
225
296
|
console.error('[ceremony] sprint-planning/run error:', err.message);
|
|
@@ -252,7 +323,8 @@ export function createCeremonyRouter(ceremonyService, processRegistry) {
|
|
|
252
323
|
|
|
253
324
|
// POST /api/ceremony/cancel
|
|
254
325
|
router.post('/cancel', (req, res) => {
|
|
255
|
-
|
|
326
|
+
const keepItems = req.body?.keepItems === true;
|
|
327
|
+
ceremonyService.cancel({ keepItems });
|
|
256
328
|
res.json({ ok: true });
|
|
257
329
|
});
|
|
258
330
|
|
|
@@ -262,6 +334,15 @@ export function createCeremonyRouter(ceremonyService, processRegistry) {
|
|
|
262
334
|
res.json({ ok: true });
|
|
263
335
|
});
|
|
264
336
|
|
|
337
|
+
// POST /api/ceremony/quota-continue
|
|
338
|
+
// Body: { newProvider?: string, newModel?: string }
|
|
339
|
+
// Omit newProvider to retry with same model (e.g. user added credits).
|
|
340
|
+
router.post('/quota-continue', (req, res) => {
|
|
341
|
+
const { newProvider = null, newModel = null } = req.body || {};
|
|
342
|
+
ceremonyService.continueAfterQuota(newProvider, newModel);
|
|
343
|
+
res.json({ ok: true });
|
|
344
|
+
});
|
|
345
|
+
|
|
265
346
|
// POST /api/ceremony/reset — force-stop any running ceremony and reset state immediately
|
|
266
347
|
router.post('/reset', (req, res) => {
|
|
267
348
|
ceremonyService.forceReset();
|
|
@@ -301,5 +382,73 @@ export function createCeremonyRouter(ceremonyService, processRegistry) {
|
|
|
301
382
|
res.json({ ok: true });
|
|
302
383
|
});
|
|
303
384
|
|
|
385
|
+
// ---- Task Runner: Seed + Run ----
|
|
386
|
+
|
|
387
|
+
// POST /api/ceremony/seed/run — seed a story into tasks/subtasks
|
|
388
|
+
router.post('/seed/run', async (req, res) => {
|
|
389
|
+
if (!taskRunnerService) {
|
|
390
|
+
return res.status(503).json({ error: 'TaskRunnerService not available' });
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const { storyId } = req.body;
|
|
394
|
+
if (!storyId || !/^context-\d{4}-\d{4}[a-z]?$/.test(storyId)) {
|
|
395
|
+
return res.status(400).json({ error: 'Valid storyId is required (format: context-XXXX-XXXX)' });
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (taskRunnerService.isRunning(storyId)) {
|
|
399
|
+
return res.status(409).json({ error: `Seed already running for ${storyId}` });
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
try {
|
|
403
|
+
const processId = taskRunnerService.runSeed(storyId);
|
|
404
|
+
res.json({ started: true, processId, storyId });
|
|
405
|
+
} catch (err) {
|
|
406
|
+
res.status(500).json({ error: err.message });
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
// POST /api/ceremony/run-task/run — run a task in a worktree
|
|
411
|
+
router.post('/run-task/run', async (req, res) => {
|
|
412
|
+
if (!taskRunnerService) {
|
|
413
|
+
return res.status(503).json({ error: 'TaskRunnerService not available' });
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const { taskId } = req.body;
|
|
417
|
+
if (!taskId || !/^context-\d{4}-\d{4}[a-z]?-\d{4}$/.test(taskId)) {
|
|
418
|
+
return res.status(400).json({ error: 'Valid taskId is required (format: context-XXXX-XXXX-XXXX)' });
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (taskRunnerService.isRunning(taskId)) {
|
|
422
|
+
return res.status(409).json({ error: `Run already active for ${taskId}` });
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
try {
|
|
426
|
+
const processId = taskRunnerService.runTask(taskId);
|
|
427
|
+
res.json({ started: true, processId, taskId });
|
|
428
|
+
} catch (err) {
|
|
429
|
+
res.status(500).json({ error: err.message });
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
// GET /api/ceremony/task-runner/status — list all active runs
|
|
434
|
+
router.get('/task-runner/status', (req, res) => {
|
|
435
|
+
if (!taskRunnerService) {
|
|
436
|
+
return res.status(503).json({ error: 'TaskRunnerService not available' });
|
|
437
|
+
}
|
|
438
|
+
res.json({ runs: taskRunnerService.listRuns() });
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
// POST /api/ceremony/task-runner/cancel — cancel a run
|
|
442
|
+
router.post('/task-runner/cancel', (req, res) => {
|
|
443
|
+
if (!taskRunnerService) {
|
|
444
|
+
return res.status(503).json({ error: 'TaskRunnerService not available' });
|
|
445
|
+
}
|
|
446
|
+
const { processId } = req.body;
|
|
447
|
+
if (!processId) return res.status(400).json({ error: 'processId is required' });
|
|
448
|
+
|
|
449
|
+
const cancelled = taskRunnerService.cancel(processId);
|
|
450
|
+
res.json({ cancelled });
|
|
451
|
+
});
|
|
452
|
+
|
|
304
453
|
return router;
|
|
305
454
|
}
|
|
@@ -96,7 +96,9 @@ export function createCostsRouter(projectRoot) {
|
|
|
96
96
|
.map(([date, data]) => ({
|
|
97
97
|
date,
|
|
98
98
|
cost: data.cost?.total ?? 0,
|
|
99
|
+
saved: data.cost?.saved ?? 0,
|
|
99
100
|
tokens: data.total ?? 0,
|
|
101
|
+
cached: data.cached ?? 0,
|
|
100
102
|
executions: data.executions ?? 0,
|
|
101
103
|
}));
|
|
102
104
|
|
|
@@ -104,7 +106,7 @@ export function createCostsRouter(projectRoot) {
|
|
|
104
106
|
const SKIP_KEYS = new Set(['version', 'lastUpdated', 'totals']);
|
|
105
107
|
const parentNodes = {};
|
|
106
108
|
for (const p of PARENT_CEREMONIES) {
|
|
107
|
-
parentNodes[p] = { name: p, calls: 0, tokens: 0, cost: 0, stages: [] };
|
|
109
|
+
parentNodes[p] = { name: p, calls: 0, tokens: 0, cost: 0, cached: 0, saved: 0, stages: [] };
|
|
108
110
|
}
|
|
109
111
|
const orphans = []; // keys that don't map to a known parent
|
|
110
112
|
|
|
@@ -112,7 +114,7 @@ export function createCostsRouter(projectRoot) {
|
|
|
112
114
|
if (SKIP_KEYS.has(key)) continue;
|
|
113
115
|
if (!value || typeof value !== 'object') continue;
|
|
114
116
|
|
|
115
|
-
let totalInput = 0, totalOutput = 0, totalCost = 0, totalExec = 0;
|
|
117
|
+
let totalInput = 0, totalOutput = 0, totalCost = 0, totalExec = 0, totalCached = 0, totalSaved = 0;
|
|
116
118
|
const dailyForKey = value.daily ?? {};
|
|
117
119
|
for (const [date, data] of Object.entries(dailyForKey)) {
|
|
118
120
|
const d = new Date(date);
|
|
@@ -121,12 +123,14 @@ export function createCostsRouter(projectRoot) {
|
|
|
121
123
|
totalOutput += data.output ?? 0;
|
|
122
124
|
totalCost += data.cost?.total ?? 0;
|
|
123
125
|
totalExec += data.executions ?? 0;
|
|
126
|
+
totalCached += data.cached ?? 0;
|
|
127
|
+
totalSaved += data.cost?.saved ?? 0;
|
|
124
128
|
}
|
|
125
129
|
}
|
|
126
130
|
|
|
127
131
|
if (totalExec === 0 && totalInput === 0 && totalOutput === 0) continue;
|
|
128
132
|
|
|
129
|
-
const entry = { name: key, calls: totalExec, tokens: totalInput + totalOutput, cost: totalCost };
|
|
133
|
+
const entry = { name: key, calls: totalExec, tokens: totalInput + totalOutput, cost: totalCost, cached: totalCached, saved: totalSaved };
|
|
130
134
|
const parent = getParentCeremony(key);
|
|
131
135
|
|
|
132
136
|
if (parent && parentNodes[parent]) {
|
|
@@ -137,6 +141,8 @@ export function createCostsRouter(projectRoot) {
|
|
|
137
141
|
parentNodes[parent].calls += totalExec;
|
|
138
142
|
parentNodes[parent].tokens += totalInput + totalOutput;
|
|
139
143
|
parentNodes[parent].cost += totalCost;
|
|
144
|
+
parentNodes[parent].cached += totalCached;
|
|
145
|
+
parentNodes[parent].saved += totalSaved;
|
|
140
146
|
} else {
|
|
141
147
|
orphans.push({ ...entry, stages: [] });
|
|
142
148
|
}
|