@agile-vibe-coding/avc 0.1.1 → 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/agent-loader.js +21 -0
- package/cli/agents/agent-selector.md +152 -0
- package/cli/agents/architecture-recommender.md +418 -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/database-deep-dive.md +470 -0
- package/cli/agents/database-recommender.md +634 -0
- package/cli/agents/doc-distributor.md +176 -0
- package/cli/agents/doc-writer-epic.md +42 -0
- package/cli/agents/doc-writer-story.md +43 -0
- package/cli/agents/documentation-updater.md +203 -0
- package/cli/agents/duplicate-detector.md +110 -0
- package/cli/agents/epic-story-decomposer.md +559 -0
- package/cli/agents/feature-context-generator.md +91 -0
- package/cli/agents/gap-checker-epic.md +52 -0
- package/cli/agents/impact-checker-story.md +51 -0
- package/cli/agents/migration-guide-generator.md +305 -0
- package/cli/agents/mission-scope-generator.md +143 -0
- package/cli/agents/mission-scope-validator.md +146 -0
- package/cli/agents/project-context-extractor.md +122 -0
- package/cli/agents/project-documentation-creator.json +226 -0
- package/cli/agents/project-documentation-creator.md +595 -0
- package/cli/agents/question-prefiller.md +269 -0
- package/cli/agents/refiner-epic.md +39 -0
- package/cli/agents/refiner-story.md +42 -0
- package/cli/agents/scaffolding-generator.md +99 -0
- package/cli/agents/seed-validator.md +71 -0
- package/cli/agents/story-doc-enricher.md +133 -0
- package/cli/agents/story-scope-reviewer.md +147 -0
- package/cli/agents/story-splitter.md +83 -0
- package/cli/agents/suggestion-business-analyst.md +88 -0
- package/cli/agents/suggestion-deployment-architect.md +263 -0
- package/cli/agents/suggestion-product-manager.md +129 -0
- package/cli/agents/suggestion-security-specialist.md +156 -0
- package/cli/agents/suggestion-technical-architect.md +269 -0
- package/cli/agents/suggestion-ux-researcher.md +93 -0
- package/cli/agents/task-subtask-decomposer.md +188 -0
- package/cli/agents/validator-documentation.json +183 -0
- package/cli/agents/validator-documentation.md +455 -0
- package/cli/agents/validator-selector.md +211 -0
- package/cli/ansi-colors.js +21 -0
- package/cli/api-reference-tool.js +368 -0
- package/cli/build-docs.js +29 -8
- package/cli/ceremony-history.js +369 -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/command-logger.js +49 -12
- package/cli/components/static-output.js +63 -0
- package/cli/console-output-manager.js +94 -0
- package/cli/dependency-checker.js +72 -0
- package/cli/docs-sync.js +306 -0
- package/cli/epic-story-validator.js +659 -0
- package/cli/evaluation-prompts.js +1008 -0
- package/cli/execution-context.js +195 -0
- package/cli/generate-summary-table.js +340 -0
- package/cli/init-model-config.js +704 -0
- package/cli/init.js +1737 -278
- package/cli/kanban-server-manager.js +227 -0
- package/cli/llm-claude.js +150 -1
- package/cli/llm-gemini.js +109 -0
- package/cli/llm-local.js +493 -0
- package/cli/llm-mock.js +233 -0
- package/cli/llm-openai.js +454 -0
- package/cli/llm-provider.js +379 -3
- package/cli/llm-token-limits.js +211 -0
- package/cli/llm-verifier.js +662 -0
- package/cli/llm-xiaomi.js +143 -0
- package/cli/message-constants.js +49 -0
- package/cli/message-manager.js +334 -0
- package/cli/message-types.js +96 -0
- package/cli/messaging-api.js +291 -0
- 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 +192 -0
- package/cli/model-query-engine.js +468 -0
- package/cli/model-recommendation-analyzer.js +495 -0
- package/cli/model-selector.js +270 -0
- package/cli/output-buffer.js +107 -0
- package/cli/process-manager.js +73 -2
- package/cli/prompt-logger.js +57 -0
- package/cli/repl-ink.js +4625 -1094
- package/cli/repl-old.js +3 -4
- package/cli/seed-processor.js +962 -0
- package/cli/sprint-planning-processor.js +4162 -0
- package/cli/template-processor.js +2149 -105
- package/cli/templates/project.md +25 -8
- package/cli/templates/vitepress-config.mts.template +5 -4
- package/cli/token-tracker.js +547 -0
- package/cli/tools/generate-story-validators.js +317 -0
- package/cli/tools/generate-validators.js +669 -0
- package/cli/update-checker.js +19 -17
- package/cli/update-notifier.js +4 -4
- package/cli/validation-router.js +667 -0
- package/cli/verification-tracker.js +563 -0
- package/cli/worktree-runner.js +654 -0
- package/kanban/README.md +386 -0
- package/kanban/client/README.md +205 -0
- package/kanban/client/components.json +20 -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 +16 -0
- package/kanban/client/dist/vite.svg +1 -0
- package/kanban/client/index.html +15 -0
- package/kanban/client/package-lock.json +9442 -0
- package/kanban/client/package.json +44 -0
- package/kanban/client/postcss.config.js +6 -0
- package/kanban/client/public/vite.svg +1 -0
- package/kanban/client/src/App.jsx +651 -0
- package/kanban/client/src/components/ProjectFileEditorPopup.jsx +117 -0
- package/kanban/client/src/components/ceremony/AskArchPopup.jsx +420 -0
- package/kanban/client/src/components/ceremony/AskModelPopup.jsx +629 -0
- package/kanban/client/src/components/ceremony/CeremonyWorkflowModal.jsx +1133 -0
- package/kanban/client/src/components/ceremony/EpicStorySelectionModal.jsx +254 -0
- package/kanban/client/src/components/ceremony/ProviderSwitcherButton.jsx +290 -0
- package/kanban/client/src/components/ceremony/SponsorCallModal.jsx +686 -0
- package/kanban/client/src/components/ceremony/SprintPlanningModal.jsx +838 -0
- package/kanban/client/src/components/ceremony/steps/ArchitectureStep.jsx +150 -0
- package/kanban/client/src/components/ceremony/steps/CompleteStep.jsx +136 -0
- package/kanban/client/src/components/ceremony/steps/DatabaseStep.jsx +202 -0
- package/kanban/client/src/components/ceremony/steps/DeploymentStep.jsx +123 -0
- package/kanban/client/src/components/ceremony/steps/MissionStep.jsx +106 -0
- package/kanban/client/src/components/ceremony/steps/ReviewAnswersStep.jsx +329 -0
- package/kanban/client/src/components/ceremony/steps/RunningStep.jsx +249 -0
- package/kanban/client/src/components/kanban/CardDetailModal.jsx +646 -0
- package/kanban/client/src/components/kanban/EpicSection.jsx +146 -0
- package/kanban/client/src/components/kanban/FilterToolbar.jsx +222 -0
- package/kanban/client/src/components/kanban/GroupingSelector.jsx +63 -0
- package/kanban/client/src/components/kanban/KanbanBoard.jsx +211 -0
- package/kanban/client/src/components/kanban/KanbanCard.jsx +147 -0
- package/kanban/client/src/components/kanban/KanbanColumn.jsx +90 -0
- package/kanban/client/src/components/kanban/RefineWorkItemPopup.jsx +784 -0
- 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/layout/LoadingScreen.jsx +82 -0
- package/kanban/client/src/components/process/ProcessMonitorBar.jsx +80 -0
- package/kanban/client/src/components/settings/AgentEditorPopup.jsx +171 -0
- package/kanban/client/src/components/settings/AgentsTab.jsx +381 -0
- package/kanban/client/src/components/settings/ApiKeysTab.jsx +142 -0
- package/kanban/client/src/components/settings/CeremonyModelsTab.jsx +105 -0
- package/kanban/client/src/components/settings/CheckEditorPopup.jsx +507 -0
- package/kanban/client/src/components/settings/CostThresholdsTab.jsx +95 -0
- package/kanban/client/src/components/settings/ModelPricingTab.jsx +269 -0
- package/kanban/client/src/components/settings/OpenAIAuthSection.jsx +412 -0
- package/kanban/client/src/components/settings/ServersTab.jsx +121 -0
- package/kanban/client/src/components/settings/SettingsModal.jsx +84 -0
- package/kanban/client/src/components/stats/CostModal.jsx +384 -0
- package/kanban/client/src/components/ui/badge.jsx +27 -0
- package/kanban/client/src/components/ui/dialog.jsx +121 -0
- package/kanban/client/src/components/ui/tabs.jsx +85 -0
- package/kanban/client/src/hooks/__tests__/useGrouping.test.js +232 -0
- package/kanban/client/src/hooks/useGrouping.js +177 -0
- package/kanban/client/src/hooks/useWebSocket.js +120 -0
- package/kanban/client/src/lib/__tests__/api.test.js +196 -0
- package/kanban/client/src/lib/__tests__/status-grouping.test.js +94 -0
- package/kanban/client/src/lib/api.js +515 -0
- package/kanban/client/src/lib/status-grouping.js +154 -0
- package/kanban/client/src/lib/utils.js +11 -0
- package/kanban/client/src/main.jsx +10 -0
- package/kanban/client/src/store/__tests__/kanbanStore.test.js +164 -0
- package/kanban/client/src/store/ceremonyStore.js +172 -0
- package/kanban/client/src/store/filterStore.js +201 -0
- package/kanban/client/src/store/kanbanStore.js +123 -0
- package/kanban/client/src/store/processStore.js +65 -0
- package/kanban/client/src/store/sprintPlanningStore.js +33 -0
- package/kanban/client/src/styles/globals.css +59 -0
- package/kanban/client/tailwind.config.js +77 -0
- package/kanban/client/vite.config.js +28 -0
- package/kanban/client/vitest.config.js +28 -0
- package/kanban/dev-start.sh +47 -0
- package/kanban/package.json +12 -0
- package/kanban/server/index.js +537 -0
- package/kanban/server/routes/ceremony.js +454 -0
- package/kanban/server/routes/costs.js +163 -0
- package/kanban/server/routes/openai-oauth.js +366 -0
- package/kanban/server/routes/processes.js +50 -0
- package/kanban/server/routes/settings.js +736 -0
- package/kanban/server/routes/websocket.js +281 -0
- package/kanban/server/routes/work-items.js +487 -0
- package/kanban/server/services/CeremonyService.js +1441 -0
- package/kanban/server/services/FileSystemScanner.js +95 -0
- package/kanban/server/services/FileWatcher.js +144 -0
- package/kanban/server/services/HierarchyBuilder.js +196 -0
- package/kanban/server/services/ProcessRegistry.js +122 -0
- package/kanban/server/services/TaskRunnerService.js +261 -0
- package/kanban/server/services/WorkItemReader.js +123 -0
- package/kanban/server/services/WorkItemRefineService.js +510 -0
- package/kanban/server/start.js +49 -0
- package/kanban/server/utils/kanban-logger.js +132 -0
- package/kanban/server/utils/markdown.js +91 -0
- package/kanban/server/utils/status-grouping.js +107 -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 +92 -0
- package/kanban/server/workers/sprint-planning-worker.js +212 -0
- package/package.json +19 -7
- package/cli/agents/documentation.md +0 -302
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* api-reference-tool.js
|
|
3
|
+
*
|
|
4
|
+
* Provides an OpenAI-compatible tool definition and handler that fetches
|
|
5
|
+
* real API reference documentation from the internet. Designed to be used
|
|
6
|
+
* with local LLMs that support tool/function calling so that context writers
|
|
7
|
+
* can look up accurate payload formats, field names, and auth mechanisms
|
|
8
|
+
* instead of hallucinating them.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Known API documentation sources
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Maps service names to documentation URLs.
|
|
17
|
+
* Each entry can have:
|
|
18
|
+
* - `openapi` — OpenAPI/Swagger spec URL (JSON or YAML)
|
|
19
|
+
* - `docs` — Human-readable docs index URL
|
|
20
|
+
* - `topics` — Map of topic keywords → specific doc URLs
|
|
21
|
+
*/
|
|
22
|
+
// No hardcoded URLs — all API references fetched dynamically via Context7.
|
|
23
|
+
// This avoids stale/broken URLs and ensures consistent, up-to-date documentation.
|
|
24
|
+
const SERVICE_REGISTRY = {};
|
|
25
|
+
|
|
26
|
+
// Aliases: normalize common service name variants
|
|
27
|
+
const SERVICE_ALIASES = {
|
|
28
|
+
// Twilio variants
|
|
29
|
+
'twilio-whatsapp': 'twilio',
|
|
30
|
+
'twilio-sms': 'twilio',
|
|
31
|
+
// Meta/WhatsApp variants (model uses many forms)
|
|
32
|
+
'meta': 'whatsapp',
|
|
33
|
+
'meta-cloud-api': 'whatsapp',
|
|
34
|
+
'meta cloud api': 'whatsapp',
|
|
35
|
+
'whatsapp-cloud': 'whatsapp',
|
|
36
|
+
'whatsapp-business': 'whatsapp',
|
|
37
|
+
'whatsapp cloud api': 'whatsapp',
|
|
38
|
+
'whatsapp business api': 'whatsapp',
|
|
39
|
+
// Express variants
|
|
40
|
+
'express.js': 'express',
|
|
41
|
+
'expressjs': 'express',
|
|
42
|
+
// AWS variants
|
|
43
|
+
's3': 'aws-s3',
|
|
44
|
+
'amazon-s3': 'aws-s3',
|
|
45
|
+
// React ecosystem
|
|
46
|
+
'react-query': 'tanstack-query',
|
|
47
|
+
'tanstack query': 'tanstack-query',
|
|
48
|
+
'@tanstack/react-query': 'tanstack-query',
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// Dynamic discovery via Context7 (Upstash)
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
const CONTEXT7_BASE = 'https://context7.com/api/v2';
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Fetch topic-specific documentation from Context7.
|
|
59
|
+
* Two-step: resolve library ID → fetch docs.
|
|
60
|
+
* Works without API key; optional CONTEXT7_API_KEY env var for higher rate limits.
|
|
61
|
+
* @returns {{ libraryId: string, text: string } | null}
|
|
62
|
+
*/
|
|
63
|
+
async function fetchFromContext7(serviceName, topic) {
|
|
64
|
+
const headers = {};
|
|
65
|
+
const apiKey = process.env.CONTEXT7_API_KEY;
|
|
66
|
+
if (apiKey) headers['Authorization'] = `Bearer ${apiKey}`;
|
|
67
|
+
|
|
68
|
+
// Step 1: Resolve library ID
|
|
69
|
+
const searchUrl = `${CONTEXT7_BASE}/libs/search?query=${encodeURIComponent(serviceName)}`;
|
|
70
|
+
const searchResult = await fetchWithLimit(searchUrl, 10_000, headers);
|
|
71
|
+
if (!searchResult.ok) return null;
|
|
72
|
+
|
|
73
|
+
let libraryId;
|
|
74
|
+
try {
|
|
75
|
+
const data = JSON.parse(searchResult.body);
|
|
76
|
+
const libs = data.libraries || data;
|
|
77
|
+
if (!Array.isArray(libs) || libs.length === 0) return null;
|
|
78
|
+
// Pick highest trust match — require trust >= 5 to avoid fuzzy noise
|
|
79
|
+
// (Context7 never returns empty results; garbage queries get fuzzy matches)
|
|
80
|
+
const best = libs.sort((a, b) => (b.trust || 0) - (a.trust || 0))[0];
|
|
81
|
+
if ((best.trust || 0) < 5) return null;
|
|
82
|
+
libraryId = best.id; // format: "owner/repo" e.g. "stripe/stripe-node"
|
|
83
|
+
} catch { return null; }
|
|
84
|
+
|
|
85
|
+
// Step 2: Fetch topic-specific documentation
|
|
86
|
+
const contextUrl = `${CONTEXT7_BASE}/context?libraryId=${encodeURIComponent(libraryId)}&query=${encodeURIComponent(topic)}&type=txt&tokenLimit=5000`;
|
|
87
|
+
const contextResult = await fetchWithLimit(contextUrl, 12_000, headers);
|
|
88
|
+
if (!contextResult.ok) return null;
|
|
89
|
+
|
|
90
|
+
const text = contextResult.body?.trim();
|
|
91
|
+
if (!text || text.length < 50) return null;
|
|
92
|
+
|
|
93
|
+
return { libraryId, text };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// Tool definition (OpenAI function-calling format)
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
export const API_REFERENCE_TOOL = {
|
|
101
|
+
type: 'function',
|
|
102
|
+
function: {
|
|
103
|
+
name: 'fetch_api_reference',
|
|
104
|
+
description:
|
|
105
|
+
'Fetch API reference documentation for any external service or API. ' +
|
|
106
|
+
'Supports thousands of libraries and APIs — documentation is discovered automatically. ' +
|
|
107
|
+
'Use this when you need accurate payload formats, field names, authentication mechanisms, ' +
|
|
108
|
+
'endpoint signatures, or webhook specifications.',
|
|
109
|
+
parameters: {
|
|
110
|
+
type: 'object',
|
|
111
|
+
properties: {
|
|
112
|
+
service: {
|
|
113
|
+
type: 'string',
|
|
114
|
+
description:
|
|
115
|
+
'Service or API name (e.g. "twilio", "stripe", "prisma", "shopify", "express", "supabase")',
|
|
116
|
+
},
|
|
117
|
+
topic: {
|
|
118
|
+
type: 'string',
|
|
119
|
+
description:
|
|
120
|
+
'Specific topic to look up (e.g. "webhook-payload", "authentication", "send-message", "signature-verification", "rate-limit")',
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
required: ['service', 'topic'],
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
// Fetch helper with timeout and size limit
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
|
|
132
|
+
const FETCH_TIMEOUT_MS = 15_000;
|
|
133
|
+
const MAX_BODY_BYTES = 512_000; // 500 KB — enough for most doc pages
|
|
134
|
+
|
|
135
|
+
async function fetchWithLimit(url, timeoutMs = FETCH_TIMEOUT_MS, extraHeaders = {}) {
|
|
136
|
+
const controller = new AbortController();
|
|
137
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
138
|
+
try {
|
|
139
|
+
const resp = await fetch(url, {
|
|
140
|
+
signal: controller.signal,
|
|
141
|
+
headers: { 'Accept': 'text/html, application/json, text/plain, */*', ...extraHeaders },
|
|
142
|
+
});
|
|
143
|
+
if (!resp.ok) {
|
|
144
|
+
return { ok: false, error: `HTTP ${resp.status} ${resp.statusText}` };
|
|
145
|
+
}
|
|
146
|
+
const contentType = resp.headers.get('content-type') || '';
|
|
147
|
+
const buffer = await resp.arrayBuffer();
|
|
148
|
+
const body = new TextDecoder().decode(buffer.slice(0, MAX_BODY_BYTES));
|
|
149
|
+
return { ok: true, body, contentType, truncated: buffer.byteLength > MAX_BODY_BYTES };
|
|
150
|
+
} catch (err) {
|
|
151
|
+
return { ok: false, error: err.name === 'AbortError' ? 'Request timed out' : err.message };
|
|
152
|
+
} finally {
|
|
153
|
+
clearTimeout(timer);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ---------------------------------------------------------------------------
|
|
158
|
+
// Content extractors
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Extract relevant text from an HTML page — strips tags, scripts, styles,
|
|
163
|
+
* and collapses whitespace. Returns a readable text summary.
|
|
164
|
+
*/
|
|
165
|
+
function extractTextFromHTML(html, maxChars = 8000) {
|
|
166
|
+
let text = html;
|
|
167
|
+
// Remove script, style, nav, footer, header blocks
|
|
168
|
+
text = text.replace(/<(script|style|nav|footer|header|aside|svg)\b[^>]*>[\s\S]*?<\/\1>/gi, ' ');
|
|
169
|
+
// Remove HTML comments
|
|
170
|
+
text = text.replace(/<!--[\s\S]*?-->/g, ' ');
|
|
171
|
+
// Convert common block elements to newlines
|
|
172
|
+
text = text.replace(/<\/(p|div|h[1-6]|li|tr|br|hr)\s*>/gi, '\n');
|
|
173
|
+
text = text.replace(/<(br|hr)\s*\/?>/gi, '\n');
|
|
174
|
+
// Strip remaining tags
|
|
175
|
+
text = text.replace(/<[^>]+>/g, ' ');
|
|
176
|
+
// Decode basic HTML entities
|
|
177
|
+
text = text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
178
|
+
.replace(/"/g, '"').replace(/'/g, "'").replace(/ /g, ' ');
|
|
179
|
+
// Collapse whitespace
|
|
180
|
+
text = text.replace(/[ \t]+/g, ' ').replace(/\n[ \t]+/g, '\n').replace(/\n{3,}/g, '\n\n');
|
|
181
|
+
return text.trim().slice(0, maxChars);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Extract a relevant section from an OpenAPI spec JSON for a given topic.
|
|
186
|
+
*/
|
|
187
|
+
function extractFromOpenAPI(specJson, topic) {
|
|
188
|
+
const topicLower = topic.toLowerCase();
|
|
189
|
+
const lines = [];
|
|
190
|
+
const paths = specJson.paths || {};
|
|
191
|
+
|
|
192
|
+
// Find paths matching the topic keywords
|
|
193
|
+
const keywords = topicLower.split(/[-_\s]+/);
|
|
194
|
+
const matchingPaths = Object.entries(paths).filter(([pathStr]) => {
|
|
195
|
+
const pathLower = pathStr.toLowerCase();
|
|
196
|
+
return keywords.some(kw => kw.length >= 3 && pathLower.includes(kw));
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// Limit to first 5 matching paths
|
|
200
|
+
for (const [pathStr, methods] of matchingPaths.slice(0, 5)) {
|
|
201
|
+
lines.push(`\n### ${pathStr}`);
|
|
202
|
+
for (const [method, spec] of Object.entries(methods)) {
|
|
203
|
+
if (typeof spec !== 'object' || !spec) continue;
|
|
204
|
+
lines.push(`**${method.toUpperCase()}** — ${spec.summary || spec.description || ''}`);
|
|
205
|
+
// Request body schema
|
|
206
|
+
const reqBody = spec.requestBody?.content;
|
|
207
|
+
if (reqBody) {
|
|
208
|
+
for (const [contentType, media] of Object.entries(reqBody)) {
|
|
209
|
+
lines.push(`- Content-Type: ${contentType}`);
|
|
210
|
+
if (media.schema?.properties) {
|
|
211
|
+
const props = Object.entries(media.schema.properties).slice(0, 15);
|
|
212
|
+
for (const [prop, propSpec] of props) {
|
|
213
|
+
const req = (media.schema.required || []).includes(prop) ? ' (required)' : '';
|
|
214
|
+
lines.push(` - ${prop}: ${propSpec.type || 'any'}${req} — ${propSpec.description || ''}`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// Response schema summary
|
|
220
|
+
const responses = spec.responses || {};
|
|
221
|
+
const successResp = responses['200'] || responses['201'] || responses['204'];
|
|
222
|
+
if (successResp) {
|
|
223
|
+
lines.push(`- Success: ${successResp.description || 'OK'}`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (lines.length === 0) {
|
|
229
|
+
// Fallback: show info section
|
|
230
|
+
const info = specJson.info || {};
|
|
231
|
+
return `API: ${info.title || 'Unknown'} (v${info.version || '?'})\n` +
|
|
232
|
+
`${info.description || ''}\n\n` +
|
|
233
|
+
`No paths matching topic "${topic}" found. Available paths (first 20):\n` +
|
|
234
|
+
Object.keys(paths).slice(0, 20).map(p => `- ${p}`).join('\n');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const info = specJson.info || {};
|
|
238
|
+
return `API: ${info.title || 'Unknown'} (v${info.version || '?'})\n` +
|
|
239
|
+
`Topic: ${topic}\n` +
|
|
240
|
+
lines.join('\n');
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ---------------------------------------------------------------------------
|
|
244
|
+
// Tool handler
|
|
245
|
+
// ---------------------------------------------------------------------------
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Handle a fetch_api_reference tool call.
|
|
249
|
+
* @param {{ service: string, topic: string }} args - Tool call arguments
|
|
250
|
+
* @returns {Promise<string>} The fetched API reference text
|
|
251
|
+
*/
|
|
252
|
+
export async function handleFetchApiReference({ service, topic }) {
|
|
253
|
+
const serviceLower = (service || '').toLowerCase().trim();
|
|
254
|
+
const topicLower = (topic || '').toLowerCase().trim();
|
|
255
|
+
|
|
256
|
+
// Resolve aliases
|
|
257
|
+
const resolvedService = SERVICE_ALIASES[serviceLower] || serviceLower;
|
|
258
|
+
const registry = SERVICE_REGISTRY[resolvedService];
|
|
259
|
+
|
|
260
|
+
// Tier 0: Curated registry (fastest, best quality for known services)
|
|
261
|
+
if (registry) {
|
|
262
|
+
// 1. Try topic-specific URL first
|
|
263
|
+
const topicUrl = findTopicUrl(registry.topics, topicLower);
|
|
264
|
+
if (topicUrl) {
|
|
265
|
+
console.log(`[DEBUG] API reference tool: fetching ${resolvedService}/${topicLower} from ${topicUrl}`);
|
|
266
|
+
const result = await fetchWithLimit(topicUrl);
|
|
267
|
+
if (result.ok) {
|
|
268
|
+
const text = extractTextFromHTML(result.body);
|
|
269
|
+
if (text.length > 100) {
|
|
270
|
+
return `## ${resolvedService} — ${topic}\nSource: ${topicUrl}\n\n${text}`;
|
|
271
|
+
}
|
|
272
|
+
} else {
|
|
273
|
+
console.log(`[DEBUG] API reference fetch failed: ${result.error}`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// 2. Try OpenAPI spec for structured data
|
|
278
|
+
if (registry.openapi) {
|
|
279
|
+
console.log(`[DEBUG] API reference tool: fetching OpenAPI spec for ${resolvedService}`);
|
|
280
|
+
const specResult = await fetchWithLimit(registry.openapi, 20_000);
|
|
281
|
+
if (specResult.ok) {
|
|
282
|
+
try {
|
|
283
|
+
const spec = JSON.parse(specResult.body);
|
|
284
|
+
const extracted = extractFromOpenAPI(spec, topicLower);
|
|
285
|
+
if (extracted) {
|
|
286
|
+
return `## ${resolvedService} — ${topic} (from OpenAPI spec)\n\n${extracted}`;
|
|
287
|
+
}
|
|
288
|
+
} catch {
|
|
289
|
+
// Not valid JSON or truncated — fall through
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// 3. Fall back to general docs page
|
|
295
|
+
if (registry.docs) {
|
|
296
|
+
console.log(`[DEBUG] API reference tool: falling back to general docs for ${resolvedService}`);
|
|
297
|
+
const docsResult = await fetchWithLimit(registry.docs);
|
|
298
|
+
if (docsResult.ok) {
|
|
299
|
+
const text = extractTextFromHTML(docsResult.body, 4000);
|
|
300
|
+
return `## ${resolvedService} — general documentation\nSource: ${registry.docs}\n\n${text}\n\n` +
|
|
301
|
+
`Note: Could not find specific docs for topic "${topic}". Above is the general API docs page.`;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Tier 1: Dynamic discovery via Context7
|
|
307
|
+
console.log(`[DEBUG] API reference: "${service}" not in registry, trying Context7`);
|
|
308
|
+
const ctx7 = await fetchFromContext7(serviceLower, topicLower);
|
|
309
|
+
if (ctx7) {
|
|
310
|
+
return `## ${service} — ${topic} (via Context7: ${ctx7.libraryId})\n\n${ctx7.text}`;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return `Could not find API documentation for "${service}/${topic}". ` +
|
|
314
|
+
`The service may not have indexed documentation available. ` +
|
|
315
|
+
`Use your training knowledge as a fallback.`;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Find the best matching topic URL from the topics map.
|
|
320
|
+
*/
|
|
321
|
+
function findTopicUrl(topics, topicLower) {
|
|
322
|
+
if (!topics) return null;
|
|
323
|
+
|
|
324
|
+
// Exact match
|
|
325
|
+
if (topics[topicLower]) return topics[topicLower];
|
|
326
|
+
|
|
327
|
+
// Partial match — find topic key that shares keywords with the query
|
|
328
|
+
const queryWords = topicLower.split(/[-_\s]+/);
|
|
329
|
+
let bestMatch = null;
|
|
330
|
+
let bestOverlap = 0;
|
|
331
|
+
|
|
332
|
+
for (const [key, url] of Object.entries(topics)) {
|
|
333
|
+
const keyWords = key.split(/[-_\s]+/);
|
|
334
|
+
const overlap = queryWords.filter(w => keyWords.includes(w)).length;
|
|
335
|
+
if (overlap > bestOverlap) {
|
|
336
|
+
bestOverlap = overlap;
|
|
337
|
+
bestMatch = url;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return bestOverlap > 0 ? bestMatch : null;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// ---------------------------------------------------------------------------
|
|
345
|
+
// Exports for provider integration
|
|
346
|
+
// ---------------------------------------------------------------------------
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* All tools available for context generation.
|
|
350
|
+
* Providers can pass this array in the `tools` parameter of chat completions.
|
|
351
|
+
*/
|
|
352
|
+
export const CONTEXT_GENERATION_TOOLS = [API_REFERENCE_TOOL];
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Dispatch a tool call by name.
|
|
356
|
+
* @param {string} name - Tool function name
|
|
357
|
+
* @param {Object} args - Tool call arguments (parsed JSON)
|
|
358
|
+
* @returns {Promise<string>} Tool result text
|
|
359
|
+
*/
|
|
360
|
+
export async function dispatchToolCall(name, args) {
|
|
361
|
+
if (name === 'fetch_api_reference') {
|
|
362
|
+
return handleFetchApiReference(args);
|
|
363
|
+
}
|
|
364
|
+
return `Unknown tool: ${name}`;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Exported for unit testing
|
|
368
|
+
export { findTopicUrl, extractFromOpenAPI, extractTextFromHTML, fetchFromContext7 };
|
package/cli/build-docs.js
CHANGED
|
@@ -25,6 +25,13 @@ export class DocumentationBuilder {
|
|
|
25
25
|
return fs.existsSync(this.docsDir);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Check if synced project docs directory exists inside documentation
|
|
30
|
+
*/
|
|
31
|
+
hasProjectDocs() {
|
|
32
|
+
return fs.existsSync(path.join(this.docsDir, 'project'));
|
|
33
|
+
}
|
|
34
|
+
|
|
28
35
|
/**
|
|
29
36
|
* Get documentation server port from avc.json config
|
|
30
37
|
* Returns default port 4173 if not configured
|
|
@@ -40,7 +47,7 @@ export class DocumentationBuilder {
|
|
|
40
47
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
41
48
|
return config.settings?.documentation?.port || 4173;
|
|
42
49
|
} catch (error) {
|
|
43
|
-
console.warn(
|
|
50
|
+
console.warn(`Could not read port from avc.json: ${error.message}`);
|
|
44
51
|
return 4173;
|
|
45
52
|
}
|
|
46
53
|
}
|
|
@@ -197,13 +204,9 @@ export class DocumentationBuilder {
|
|
|
197
204
|
const port = this.getPort();
|
|
198
205
|
|
|
199
206
|
try {
|
|
200
|
-
//
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
// Start the preview server
|
|
206
|
-
const serverProcess = spawn('npx', ['vitepress', 'preview', '--port', String(port)], {
|
|
207
|
+
// Start the dev server — no initial build needed, vitepress dev builds on-demand
|
|
208
|
+
// and hot-reloads the browser whenever source .md files change
|
|
209
|
+
const serverProcess = spawn('npx', ['vitepress', 'dev', '--port', String(port)], {
|
|
207
210
|
cwd: this.docsDir,
|
|
208
211
|
stdio: 'pipe'
|
|
209
212
|
});
|
|
@@ -256,6 +259,22 @@ export class DocumentationBuilder {
|
|
|
256
259
|
}
|
|
257
260
|
}
|
|
258
261
|
|
|
262
|
+
/**
|
|
263
|
+
* Ensure ignoreDeadLinks: true is present in the VitePress config.
|
|
264
|
+
* Patches existing project configs that were created before this option was added.
|
|
265
|
+
*/
|
|
266
|
+
_ensureIgnoreDeadLinks() {
|
|
267
|
+
const configPath = path.join(this.docsDir, '.vitepress', 'config.mts');
|
|
268
|
+
if (!fs.existsSync(configPath)) return;
|
|
269
|
+
const content = fs.readFileSync(configPath, 'utf8');
|
|
270
|
+
if (content.includes('ignoreDeadLinks')) return;
|
|
271
|
+
const patched = content.replace(
|
|
272
|
+
/defineConfig\(\{/,
|
|
273
|
+
'defineConfig({\n ignoreDeadLinks: true,'
|
|
274
|
+
);
|
|
275
|
+
fs.writeFileSync(configPath, patched, 'utf8');
|
|
276
|
+
}
|
|
277
|
+
|
|
259
278
|
/**
|
|
260
279
|
* Build documentation without starting server
|
|
261
280
|
*/
|
|
@@ -264,6 +283,8 @@ export class DocumentationBuilder {
|
|
|
264
283
|
throw new Error('Documentation not found. Run /init first to create documentation structure.');
|
|
265
284
|
}
|
|
266
285
|
|
|
286
|
+
this._ensureIgnoreDeadLinks();
|
|
287
|
+
|
|
267
288
|
try {
|
|
268
289
|
// Build asynchronously to avoid blocking the event loop
|
|
269
290
|
await execAsync('npx vitepress build', {
|