@girardmedia/bootspring 1.2.0 → 2.0.3
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/README.md +107 -14
- package/bin/bootspring.js +166 -27
- package/cli/agent.js +189 -17
- package/cli/analyze.js +499 -0
- package/cli/audit.js +557 -0
- package/cli/auth.js +495 -38
- package/cli/billing.js +302 -0
- package/cli/build.js +695 -0
- package/cli/business.js +109 -26
- package/cli/checkpoint-utils.js +168 -0
- package/cli/checkpoint.js +639 -0
- package/cli/cloud-sync.js +447 -0
- package/cli/content.js +198 -0
- package/cli/context.js +1 -1
- package/cli/deploy.js +543 -0
- package/cli/fundraise.js +112 -50
- package/cli/github-cmd.js +435 -0
- package/cli/health.js +477 -0
- package/cli/init.js +84 -13
- package/cli/legal.js +107 -95
- package/cli/log.js +2 -2
- package/cli/loop.js +976 -73
- package/cli/manager.js +711 -0
- package/cli/metrics.js +480 -0
- package/cli/monitor.js +812 -0
- package/cli/onboard.js +521 -0
- package/cli/orchestrator.js +12 -24
- package/cli/prd.js +594 -0
- package/cli/preseed-start.js +1483 -0
- package/cli/preseed.js +2302 -0
- package/cli/project.js +436 -0
- package/cli/quality.js +233 -0
- package/cli/security.js +913 -0
- package/cli/seed.js +1441 -5
- package/cli/skill.js +273 -211
- package/cli/suggest.js +989 -0
- package/cli/switch.js +453 -0
- package/cli/visualize.js +527 -0
- package/cli/watch.js +769 -0
- package/cli/workspace.js +607 -0
- package/core/analyze-workflow.js +1134 -0
- package/core/api-client.js +535 -22
- package/core/audit-workflow.js +1350 -0
- package/core/build-orchestrator.js +480 -0
- package/core/build-state.js +577 -0
- package/core/checkpoint-engine.js +408 -0
- package/core/config.js +1109 -26
- package/core/context-loader.js +21 -1
- package/core/deploy-workflow.js +836 -0
- package/core/entitlements.js +93 -22
- package/core/github-sync.js +610 -0
- package/core/index.js +8 -1
- package/core/ingest.js +1111 -0
- package/core/metrics-engine.js +768 -0
- package/core/onboard-workflow.js +1007 -0
- package/core/preseed-workflow.js +934 -0
- package/core/preseed.js +1617 -0
- package/core/project-context.js +325 -0
- package/core/project-state.js +694 -0
- package/core/r2-sync.js +583 -0
- package/core/scaffold.js +525 -7
- package/core/session.js +258 -0
- package/core/task-extractor.js +758 -0
- package/core/telemetry.js +28 -6
- package/core/tier-enforcement.js +737 -0
- package/core/utils.js +38 -14
- package/generators/questionnaire.js +15 -12
- package/generators/sections/ai.js +7 -7
- package/generators/sections/content.js +300 -0
- package/generators/sections/index.js +3 -0
- package/generators/sections/plugins.js +7 -6
- package/generators/templates/build-planning.template.js +596 -0
- package/generators/templates/content.template.js +819 -0
- package/generators/templates/index.js +2 -1
- package/hooks/git-autopilot.js +1250 -0
- package/hooks/index.js +9 -0
- package/intelligence/agent-collab.js +2057 -0
- package/intelligence/auto-suggest.js +634 -0
- package/intelligence/content-gen.js +1589 -0
- package/intelligence/cross-project.js +1647 -0
- package/intelligence/index.js +184 -0
- package/intelligence/learning/insights.json +517 -7
- package/intelligence/learning/pattern-learner.js +1008 -14
- package/intelligence/memory/decision-tracker.js +1431 -31
- package/intelligence/memory/decisions.jsonl +0 -0
- package/intelligence/orchestrator.js +2896 -1
- package/intelligence/prd.js +92 -1
- package/intelligence/recommendation-weights.json +14 -2
- package/intelligence/recommendations.js +463 -9
- package/intelligence/workflow-composer.js +1451 -0
- package/marketplace/index.d.ts +324 -0
- package/marketplace/index.js +1921 -0
- package/mcp/contracts/mcp-contract.v1.json +342 -4
- package/mcp/registry.js +680 -3
- package/mcp/response-formatter.js +23 -0
- package/mcp/tools/assist-tool.js +78 -4
- package/mcp/tools/autopilot-tool.js +408 -0
- package/mcp/tools/content-tool.js +571 -0
- package/mcp/tools/dashboard-tool.js +251 -5
- package/mcp/tools/mvp-tool.js +344 -0
- package/mcp/tools/plugin-tool.js +23 -1
- package/mcp/tools/prd-tool.js +579 -0
- package/mcp/tools/seed-tool.js +447 -0
- package/mcp/tools/skill-tool.js +43 -14
- package/mcp/tools/suggest-tool.js +147 -0
- package/package.json +15 -6
- package/agents/README.md +0 -93
- package/agents/ai-integration-expert/context.md +0 -386
- package/agents/api-expert/context.md +0 -416
- package/agents/architecture-expert/context.md +0 -454
- package/agents/auth-expert/context.md +0 -399
- package/agents/backend-expert/context.md +0 -483
- package/agents/business-strategy-expert/context.md +0 -180
- package/agents/code-review-expert/context.md +0 -365
- package/agents/competitive-analysis-expert/context.md +0 -239
- package/agents/data-modeling-expert/context.md +0 -352
- package/agents/database-expert/context.md +0 -250
- package/agents/devops-expert/context.md +0 -446
- package/agents/email-expert/context.md +0 -379
- package/agents/financial-expert/context.md +0 -213
- package/agents/frontend-expert/context.md +0 -364
- package/agents/fundraising-expert/context.md +0 -257
- package/agents/growth-expert/context.md +0 -249
- package/agents/index.js +0 -140
- package/agents/investor-relations-expert/context.md +0 -266
- package/agents/legal-expert/context.md +0 -284
- package/agents/marketing-expert/context.md +0 -236
- package/agents/monitoring-expert/context.md +0 -362
- package/agents/operations-expert/context.md +0 -279
- package/agents/partnerships-expert/context.md +0 -286
- package/agents/payment-expert/context.md +0 -340
- package/agents/performance-expert/context.md +0 -377
- package/agents/private-equity-expert/context.md +0 -246
- package/agents/railway-expert/context.md +0 -284
- package/agents/research-expert/context.md +0 -245
- package/agents/sales-expert/context.md +0 -241
- package/agents/security-expert/context.md +0 -343
- package/agents/testing-expert/context.md +0 -414
- package/agents/ui-ux-expert/context.md +0 -448
- package/agents/vercel-expert/context.md +0 -426
- package/skills/index.js +0 -787
- package/skills/patterns/README.md +0 -163
- package/skills/patterns/ai/agents.md +0 -281
- package/skills/patterns/ai/claude.md +0 -138
- package/skills/patterns/ai/embeddings.md +0 -150
- package/skills/patterns/ai/rag.md +0 -266
- package/skills/patterns/ai/streaming.md +0 -170
- package/skills/patterns/ai/structured-output.md +0 -162
- package/skills/patterns/ai/tools.md +0 -154
- package/skills/patterns/analytics/tracking.md +0 -220
- package/skills/patterns/api/errors.md +0 -296
- package/skills/patterns/api/graphql.md +0 -440
- package/skills/patterns/api/middleware.md +0 -279
- package/skills/patterns/api/openapi.md +0 -285
- package/skills/patterns/api/rate-limiting.md +0 -231
- package/skills/patterns/api/route-handler.md +0 -217
- package/skills/patterns/api/server-action.md +0 -249
- package/skills/patterns/api/versioning.md +0 -443
- package/skills/patterns/api/webhooks.md +0 -247
- package/skills/patterns/auth/clerk.md +0 -132
- package/skills/patterns/auth/mfa.md +0 -313
- package/skills/patterns/auth/nextauth.md +0 -140
- package/skills/patterns/auth/oauth.md +0 -237
- package/skills/patterns/auth/rbac.md +0 -152
- package/skills/patterns/auth/session-management.md +0 -367
- package/skills/patterns/auth/session.md +0 -120
- package/skills/patterns/database/audit.md +0 -177
- package/skills/patterns/database/migrations.md +0 -177
- package/skills/patterns/database/pagination.md +0 -230
- package/skills/patterns/database/pooling.md +0 -357
- package/skills/patterns/database/prisma.md +0 -180
- package/skills/patterns/database/relations.md +0 -187
- package/skills/patterns/database/seeding.md +0 -246
- package/skills/patterns/database/soft-delete.md +0 -153
- package/skills/patterns/database/transactions.md +0 -162
- package/skills/patterns/deployment/ci-cd.md +0 -231
- package/skills/patterns/deployment/docker.md +0 -188
- package/skills/patterns/deployment/monitoring.md +0 -387
- package/skills/patterns/deployment/vercel.md +0 -160
- package/skills/patterns/email/resend.md +0 -143
- package/skills/patterns/email/templates.md +0 -245
- package/skills/patterns/email/transactional.md +0 -503
- package/skills/patterns/email/verification.md +0 -176
- package/skills/patterns/files/download.md +0 -243
- package/skills/patterns/files/upload.md +0 -239
- package/skills/patterns/i18n/nextintl.md +0 -188
- package/skills/patterns/logging/structured.md +0 -292
- package/skills/patterns/notifications/email-queue.md +0 -248
- package/skills/patterns/notifications/push.md +0 -279
- package/skills/patterns/payments/checkout.md +0 -303
- package/skills/patterns/payments/invoices.md +0 -287
- package/skills/patterns/payments/portal.md +0 -245
- package/skills/patterns/payments/stripe.md +0 -272
- package/skills/patterns/payments/subscriptions.md +0 -300
- package/skills/patterns/payments/usage.md +0 -279
- package/skills/patterns/performance/caching.md +0 -276
- package/skills/patterns/performance/code-splitting.md +0 -233
- package/skills/patterns/performance/edge.md +0 -254
- package/skills/patterns/performance/isr.md +0 -266
- package/skills/patterns/performance/lazy-loading.md +0 -281
- package/skills/patterns/realtime/sse.md +0 -327
- package/skills/patterns/realtime/websockets.md +0 -336
- package/skills/patterns/search/filtering.md +0 -329
- package/skills/patterns/search/fulltext.md +0 -260
- package/skills/patterns/security/audit-logging.md +0 -444
- package/skills/patterns/security/csrf.md +0 -234
- package/skills/patterns/security/headers.md +0 -252
- package/skills/patterns/security/sanitization.md +0 -258
- package/skills/patterns/security/secrets.md +0 -261
- package/skills/patterns/security/validation.md +0 -268
- package/skills/patterns/security/xss.md +0 -229
- package/skills/patterns/seo/metadata.md +0 -252
- package/skills/patterns/state/context.md +0 -349
- package/skills/patterns/state/react-query.md +0 -313
- package/skills/patterns/state/url-state.md +0 -482
- package/skills/patterns/state/zustand.md +0 -262
- package/skills/patterns/testing/api.md +0 -259
- package/skills/patterns/testing/component.md +0 -233
- package/skills/patterns/testing/coverage.md +0 -207
- package/skills/patterns/testing/fixtures.md +0 -225
- package/skills/patterns/testing/integration.md +0 -436
- package/skills/patterns/testing/mocking.md +0 -177
- package/skills/patterns/testing/playwright.md +0 -162
- package/skills/patterns/testing/snapshot.md +0 -175
- package/skills/patterns/testing/vitest.md +0 -307
- package/skills/patterns/ui/accordions.md +0 -395
- package/skills/patterns/ui/cards.md +0 -299
- package/skills/patterns/ui/dropdowns.md +0 -476
- package/skills/patterns/ui/empty-states.md +0 -320
- package/skills/patterns/ui/forms.md +0 -405
- package/skills/patterns/ui/inputs.md +0 -319
- package/skills/patterns/ui/layouts.md +0 -282
- package/skills/patterns/ui/loading.md +0 -291
- package/skills/patterns/ui/modals.md +0 -338
- package/skills/patterns/ui/navigation.md +0 -374
- package/skills/patterns/ui/tables.md +0 -407
- package/skills/patterns/ui/toasts.md +0 -300
- package/skills/patterns/ui/tooltips.md +0 -396
- package/skills/patterns/utils/dates.md +0 -435
- package/skills/patterns/utils/errors.md +0 -451
- package/skills/patterns/utils/formatting.md +0 -345
- package/skills/patterns/utils/validation.md +0 -434
- package/templates/bootspring.config.js +0 -83
- package/templates/business/business-model-canvas.md +0 -246
- package/templates/business/business-plan.md +0 -266
- package/templates/business/competitive-analysis.md +0 -312
- package/templates/fundraising/data-room-checklist.md +0 -300
- package/templates/fundraising/investor-research.md +0 -243
- package/templates/fundraising/pitch-deck-outline.md +0 -253
- package/templates/legal/gdpr-checklist.md +0 -339
- package/templates/legal/privacy-policy.md +0 -285
- package/templates/legal/terms-of-service.md +0 -222
- package/templates/mcp.json +0 -9
package/skills/index.js
DELETED
|
@@ -1,787 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Bootspring Skills Module
|
|
3
|
-
* Unified access to built-in patterns and optional external skills catalog.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const fs = require('fs');
|
|
7
|
-
const path = require('path');
|
|
8
|
-
const os = require('os');
|
|
9
|
-
const crypto = require('crypto');
|
|
10
|
-
|
|
11
|
-
const PATTERNS_DIR = path.join(__dirname, 'patterns');
|
|
12
|
-
const EXTERNAL_SKILLS_DIR = path.join(__dirname, 'external');
|
|
13
|
-
const EXTERNAL_MANIFEST_PATH = path.join(EXTERNAL_SKILLS_DIR, 'manifest.json');
|
|
14
|
-
const DEFAULT_EXTERNAL_CACHE_ROOT = path.join(os.homedir(), '.bootspring', 'skills-cache');
|
|
15
|
-
const EXTERNAL_CONTENT_FILENAME = 'SKILL.md';
|
|
16
|
-
const MANIFEST_SIGNATURE_OMIT_KEYS = new Set(['signature', 'signatureAlgorithm', 'signatureValue', 'signedBy']);
|
|
17
|
-
|
|
18
|
-
let builtInCache = null;
|
|
19
|
-
let externalCache = null;
|
|
20
|
-
|
|
21
|
-
function titleCase(value) {
|
|
22
|
-
return value
|
|
23
|
-
.split(/[-_]/)
|
|
24
|
-
.filter(Boolean)
|
|
25
|
-
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
|
26
|
-
.join(' ');
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function normalizeSlug(value) {
|
|
30
|
-
return String(value || '')
|
|
31
|
-
.toLowerCase()
|
|
32
|
-
.replace(/[^a-z0-9]+/g, '-')
|
|
33
|
-
.replace(/^-+|-+$/g, '');
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function parseBoolean(value) {
|
|
37
|
-
if (typeof value === 'boolean') return value;
|
|
38
|
-
if (value === null || value === undefined) return false;
|
|
39
|
-
const normalized = String(value).trim().toLowerCase();
|
|
40
|
-
return normalized === '1' || normalized === 'true' || normalized === 'yes' || normalized === 'on';
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function stableStringify(value) {
|
|
44
|
-
if (value === null) return 'null';
|
|
45
|
-
if (typeof value === 'number' || typeof value === 'boolean') return JSON.stringify(value);
|
|
46
|
-
if (typeof value === 'string') return JSON.stringify(value);
|
|
47
|
-
if (Array.isArray(value)) {
|
|
48
|
-
return `[${value.map(item => stableStringify(item)).join(',')}]`;
|
|
49
|
-
}
|
|
50
|
-
if (typeof value === 'object') {
|
|
51
|
-
const keys = Object.keys(value).sort();
|
|
52
|
-
const entries = keys.map(key => `${JSON.stringify(key)}:${stableStringify(value[key])}`);
|
|
53
|
-
return `{${entries.join(',')}}`;
|
|
54
|
-
}
|
|
55
|
-
return JSON.stringify(value);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function canonicalManifestPayload(manifest) {
|
|
59
|
-
const clone = {};
|
|
60
|
-
for (const [key, value] of Object.entries(manifest || {})) {
|
|
61
|
-
if (!MANIFEST_SIGNATURE_OMIT_KEYS.has(key)) {
|
|
62
|
-
clone[key] = value;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
return stableStringify(clone);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function parseFrontmatter(content) {
|
|
69
|
-
if (!content.startsWith('---\n')) {
|
|
70
|
-
return {};
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const end = content.indexOf('\n---\n', 4);
|
|
74
|
-
if (end === -1) {
|
|
75
|
-
return {};
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const block = content.slice(4, end);
|
|
79
|
-
const data = {};
|
|
80
|
-
for (const line of block.split('\n')) {
|
|
81
|
-
const separator = line.indexOf(':');
|
|
82
|
-
if (separator === -1) continue;
|
|
83
|
-
const key = line.slice(0, separator).trim();
|
|
84
|
-
const raw = line.slice(separator + 1).trim();
|
|
85
|
-
data[key] = raw.replace(/^['"]|['"]$/g, '');
|
|
86
|
-
}
|
|
87
|
-
return data;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function parseMarkdownMetadata(content, fallbackName) {
|
|
91
|
-
const titleMatch = content.match(/^#\s+(.+)$/m);
|
|
92
|
-
const title = titleMatch ? titleMatch[1].trim() : titleCase(fallbackName);
|
|
93
|
-
|
|
94
|
-
const withoutFrontmatter = content.replace(/^---[\s\S]*?---\n?/, '');
|
|
95
|
-
const lines = withoutFrontmatter.split('\n');
|
|
96
|
-
let description = '';
|
|
97
|
-
let h1Seen = false;
|
|
98
|
-
for (const line of lines) {
|
|
99
|
-
if (!h1Seen && /^#\s+/.test(line)) {
|
|
100
|
-
h1Seen = true;
|
|
101
|
-
continue;
|
|
102
|
-
}
|
|
103
|
-
if (h1Seen && line.trim() && !line.startsWith('#')) {
|
|
104
|
-
description = line.trim();
|
|
105
|
-
break;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return { title, description };
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function scanBuiltInSkills() {
|
|
113
|
-
const categories = {};
|
|
114
|
-
const metadata = {};
|
|
115
|
-
|
|
116
|
-
if (!fs.existsSync(PATTERNS_DIR)) {
|
|
117
|
-
return { categories, metadata };
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const categoryDirs = fs.readdirSync(PATTERNS_DIR, { withFileTypes: true })
|
|
121
|
-
.filter(entry => entry.isDirectory())
|
|
122
|
-
.map(entry => entry.name)
|
|
123
|
-
.sort();
|
|
124
|
-
|
|
125
|
-
for (const category of categoryDirs) {
|
|
126
|
-
const categoryPath = path.join(PATTERNS_DIR, category);
|
|
127
|
-
const patterns = fs.readdirSync(categoryPath, { withFileTypes: true })
|
|
128
|
-
.filter(entry => entry.isFile() && entry.name.endsWith('.md'))
|
|
129
|
-
.map(entry => entry.name.replace(/\.md$/, ''))
|
|
130
|
-
.sort();
|
|
131
|
-
|
|
132
|
-
categories[category] = patterns;
|
|
133
|
-
|
|
134
|
-
for (const pattern of patterns) {
|
|
135
|
-
const id = `${category}/${pattern}`;
|
|
136
|
-
const filePath = path.join(categoryPath, `${pattern}.md`);
|
|
137
|
-
const content = fs.readFileSync(filePath, 'utf-8');
|
|
138
|
-
const parsed = parseMarkdownMetadata(content, pattern);
|
|
139
|
-
const codeBlocks = Math.floor((content.match(/```/g) || []).length / 2);
|
|
140
|
-
|
|
141
|
-
metadata[id] = {
|
|
142
|
-
id,
|
|
143
|
-
source: 'built-in',
|
|
144
|
-
category,
|
|
145
|
-
name: parsed.title,
|
|
146
|
-
description: parsed.description,
|
|
147
|
-
title: parsed.title,
|
|
148
|
-
codeBlocks,
|
|
149
|
-
lines: content.split('\n').length,
|
|
150
|
-
path: filePath
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return { categories, metadata };
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function getExternalCacheRoot(options = {}) {
|
|
159
|
-
return path.resolve(String(options.cacheDir || process.env.BOOTSPRING_SKILL_CACHE_DIR || DEFAULT_EXTERNAL_CACHE_ROOT));
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function getExternalCacheManifestPath(options = {}) {
|
|
163
|
-
return path.join(getExternalCacheRoot(options), 'external', 'manifest.json');
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function parseExternalSkill(skillDirName, source = {}) {
|
|
167
|
-
const skillPath = path.join(source.skillsDir || EXTERNAL_SKILLS_DIR, skillDirName, EXTERNAL_CONTENT_FILENAME);
|
|
168
|
-
if (!fs.existsSync(skillPath)) {
|
|
169
|
-
return null;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const content = fs.readFileSync(skillPath, 'utf-8');
|
|
173
|
-
const frontmatter = parseFrontmatter(content);
|
|
174
|
-
const parsed = parseMarkdownMetadata(content, skillDirName);
|
|
175
|
-
|
|
176
|
-
const rawName = frontmatter.name || source.name || skillDirName;
|
|
177
|
-
const slug = normalizeSlug(rawName) || normalizeSlug(skillDirName) || skillDirName;
|
|
178
|
-
const title = parsed.title || titleCase(rawName);
|
|
179
|
-
const description = frontmatter.description || source.description || parsed.description || '';
|
|
180
|
-
|
|
181
|
-
return {
|
|
182
|
-
id: `external/${slug}`,
|
|
183
|
-
source: source.source || 'external',
|
|
184
|
-
slug,
|
|
185
|
-
name: title,
|
|
186
|
-
description,
|
|
187
|
-
category: 'external',
|
|
188
|
-
path: skillPath,
|
|
189
|
-
lines: content.split('\n').length,
|
|
190
|
-
tier: source.tier,
|
|
191
|
-
version: source.version,
|
|
192
|
-
checksum: source.checksum
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function loadExternalManifest(manifestPath = EXTERNAL_MANIFEST_PATH) {
|
|
197
|
-
if (!fs.existsSync(manifestPath)) {
|
|
198
|
-
return [];
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
try {
|
|
202
|
-
const raw = fs.readFileSync(manifestPath, 'utf-8');
|
|
203
|
-
const manifest = JSON.parse(raw);
|
|
204
|
-
if (!manifest || !Array.isArray(manifest.skills)) {
|
|
205
|
-
return [];
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
return manifest.skills
|
|
209
|
-
.map(item => {
|
|
210
|
-
if (typeof item === 'string') {
|
|
211
|
-
return { slug: String(item || '').trim() };
|
|
212
|
-
}
|
|
213
|
-
if (item && typeof item === 'object') {
|
|
214
|
-
return {
|
|
215
|
-
slug: normalizeSlug(item.slug || item.id || item.name || ''),
|
|
216
|
-
name: item.name,
|
|
217
|
-
description: item.description,
|
|
218
|
-
tier: item.tier,
|
|
219
|
-
version: item.version,
|
|
220
|
-
checksum: item.checksum
|
|
221
|
-
};
|
|
222
|
-
}
|
|
223
|
-
return null;
|
|
224
|
-
})
|
|
225
|
-
.filter(Boolean)
|
|
226
|
-
.filter(item => item.slug)
|
|
227
|
-
.map(item => ({ ...item, slug: String(item.slug).trim() }))
|
|
228
|
-
.filter(item => item.slug.length > 0);
|
|
229
|
-
} catch {
|
|
230
|
-
return [];
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
function scanExternalSkillsFromSource(source) {
|
|
235
|
-
const metadata = {};
|
|
236
|
-
const { skillsDir, manifestPath } = source;
|
|
237
|
-
|
|
238
|
-
if (!fs.existsSync(skillsDir) || !fs.existsSync(manifestPath)) {
|
|
239
|
-
return metadata;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
const manifestSkills = loadExternalManifest(manifestPath);
|
|
243
|
-
for (const item of manifestSkills) {
|
|
244
|
-
const parsed = parseExternalSkill(item.slug, {
|
|
245
|
-
skillsDir,
|
|
246
|
-
source: source.source,
|
|
247
|
-
name: item.name,
|
|
248
|
-
description: item.description,
|
|
249
|
-
tier: item.tier,
|
|
250
|
-
version: item.version,
|
|
251
|
-
checksum: item.checksum
|
|
252
|
-
});
|
|
253
|
-
if (!parsed) continue;
|
|
254
|
-
if (!metadata[parsed.id]) {
|
|
255
|
-
metadata[parsed.id] = parsed;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
return metadata;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
function scanExternalSkills(options = {}) {
|
|
263
|
-
const metadata = {};
|
|
264
|
-
const cacheRoot = getExternalCacheRoot(options);
|
|
265
|
-
const sourcePriority = String(process.env.BOOTSPRING_SKILL_CATALOG_SOURCE || 'auto').toLowerCase();
|
|
266
|
-
const localSource = {
|
|
267
|
-
source: 'external-local',
|
|
268
|
-
skillsDir: EXTERNAL_SKILLS_DIR,
|
|
269
|
-
manifestPath: EXTERNAL_MANIFEST_PATH
|
|
270
|
-
};
|
|
271
|
-
const cacheSource = {
|
|
272
|
-
source: 'external-cache',
|
|
273
|
-
skillsDir: path.join(cacheRoot, 'external'),
|
|
274
|
-
manifestPath: getExternalCacheManifestPath(options)
|
|
275
|
-
};
|
|
276
|
-
|
|
277
|
-
const sources = sourcePriority === 'cache'
|
|
278
|
-
? [cacheSource]
|
|
279
|
-
: sourcePriority === 'local'
|
|
280
|
-
? [localSource]
|
|
281
|
-
: [cacheSource, localSource];
|
|
282
|
-
|
|
283
|
-
for (const source of sources) {
|
|
284
|
-
const items = scanExternalSkillsFromSource(source);
|
|
285
|
-
for (const [id, item] of Object.entries(items)) {
|
|
286
|
-
if (!metadata[id]) {
|
|
287
|
-
metadata[id] = item;
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
return metadata;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
function hashContent(content) {
|
|
296
|
-
return crypto.createHash('sha256').update(content).digest('hex');
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
function normalizeChecksum(checksum) {
|
|
300
|
-
const raw = String(checksum || '').trim().toLowerCase();
|
|
301
|
-
if (!raw) return '';
|
|
302
|
-
return raw.startsWith('sha256:') ? raw.slice(7) : raw;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
function assertChecksum(content, checksum) {
|
|
306
|
-
const expected = normalizeChecksum(checksum);
|
|
307
|
-
if (!expected) return true;
|
|
308
|
-
return hashContent(content) === expected;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
function decodeSignature(signature) {
|
|
312
|
-
const normalized = String(signature || '').trim().replace(/-/g, '+').replace(/_/g, '/');
|
|
313
|
-
const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, '=');
|
|
314
|
-
return Buffer.from(padded, 'base64');
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
function verifyManifestSignature(manifest, options = {}) {
|
|
318
|
-
const signature = String(options.manifestSignature || manifest.signature || '').trim();
|
|
319
|
-
const publicKey = String(options.manifestPublicKey || process.env.BOOTSPRING_SKILL_MANIFEST_PUBLIC_KEY || '').trim();
|
|
320
|
-
const requireSignature = parseBoolean(
|
|
321
|
-
options.requireManifestSignature ?? process.env.BOOTSPRING_SKILL_MANIFEST_REQUIRE_SIGNATURE
|
|
322
|
-
);
|
|
323
|
-
|
|
324
|
-
if (!signature) {
|
|
325
|
-
if (requireSignature) {
|
|
326
|
-
throw new Error('Missing manifest signature while signature verification is required.');
|
|
327
|
-
}
|
|
328
|
-
return { verified: false, required: false, reason: 'not_provided' };
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
if (!publicKey) {
|
|
332
|
-
throw new Error('Manifest signature provided but no public key configured. Set BOOTSPRING_SKILL_MANIFEST_PUBLIC_KEY or pass manifestPublicKey.');
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
const algorithm = String(options.manifestSignatureAlgorithm || manifest.signatureAlgorithm || 'sha256')
|
|
336
|
-
.trim()
|
|
337
|
-
.toLowerCase();
|
|
338
|
-
if (algorithm !== 'sha256') {
|
|
339
|
-
throw new Error(`Unsupported manifest signature algorithm: ${algorithm}`);
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
const payload = canonicalManifestPayload(manifest);
|
|
343
|
-
const signatureBuffer = decodeSignature(signature);
|
|
344
|
-
const verified = crypto.verify('sha256', Buffer.from(payload, 'utf-8'), publicKey, signatureBuffer);
|
|
345
|
-
|
|
346
|
-
if (!verified) {
|
|
347
|
-
throw new Error('Manifest signature verification failed.');
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
return { verified: true, required: requireSignature, algorithm: 'sha256' };
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
function defaultHeaders(options = {}) {
|
|
354
|
-
const headers = { accept: 'application/json' };
|
|
355
|
-
const token = options.token || process.env.BOOTSPRING_SKILL_TOKEN;
|
|
356
|
-
if (token) {
|
|
357
|
-
headers.authorization = `Bearer ${token}`;
|
|
358
|
-
}
|
|
359
|
-
return headers;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
async function fetchJson(url, options = {}) {
|
|
363
|
-
const timeoutMs = Number(options.timeoutMs || 15000);
|
|
364
|
-
const controller = new AbortController();
|
|
365
|
-
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
366
|
-
|
|
367
|
-
try {
|
|
368
|
-
const response = await fetch(url, {
|
|
369
|
-
method: 'GET',
|
|
370
|
-
headers: defaultHeaders(options),
|
|
371
|
-
signal: controller.signal
|
|
372
|
-
});
|
|
373
|
-
if (!response.ok) {
|
|
374
|
-
throw new Error(`HTTP ${response.status} for ${url}`);
|
|
375
|
-
}
|
|
376
|
-
return await response.json();
|
|
377
|
-
} finally {
|
|
378
|
-
clearTimeout(timeoutId);
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
async function fetchText(url, options = {}) {
|
|
383
|
-
const timeoutMs = Number(options.timeoutMs || 15000);
|
|
384
|
-
const controller = new AbortController();
|
|
385
|
-
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
386
|
-
|
|
387
|
-
try {
|
|
388
|
-
const response = await fetch(url, {
|
|
389
|
-
method: 'GET',
|
|
390
|
-
headers: defaultHeaders(options),
|
|
391
|
-
signal: controller.signal
|
|
392
|
-
});
|
|
393
|
-
if (!response.ok) {
|
|
394
|
-
throw new Error(`HTTP ${response.status} for ${url}`);
|
|
395
|
-
}
|
|
396
|
-
return await response.text();
|
|
397
|
-
} finally {
|
|
398
|
-
clearTimeout(timeoutId);
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
function resolveContentUrl(entry, options = {}) {
|
|
403
|
-
if (entry.contentUrl) {
|
|
404
|
-
return entry.contentUrl;
|
|
405
|
-
}
|
|
406
|
-
const contentBaseUrl = options.contentBaseUrl || process.env.BOOTSPRING_SKILL_CONTENT_BASE_URL;
|
|
407
|
-
if (!contentBaseUrl) {
|
|
408
|
-
return '';
|
|
409
|
-
}
|
|
410
|
-
const base = String(contentBaseUrl).replace(/\/+$/, '');
|
|
411
|
-
return `${base}/${entry.slug}/${EXTERNAL_CONTENT_FILENAME}`;
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
function resetExternalCache() {
|
|
415
|
-
externalCache = null;
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
async function syncExternalCatalog(options = {}) {
|
|
419
|
-
const manifestUrl = options.manifestUrl || process.env.BOOTSPRING_SKILL_MANIFEST_URL;
|
|
420
|
-
if (!manifestUrl) {
|
|
421
|
-
throw new Error('Missing manifest URL. Set BOOTSPRING_SKILL_MANIFEST_URL or pass manifestUrl.');
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
const cacheRoot = getExternalCacheRoot(options);
|
|
425
|
-
const externalRoot = path.join(cacheRoot, 'external');
|
|
426
|
-
fs.mkdirSync(externalRoot, { recursive: true });
|
|
427
|
-
|
|
428
|
-
const manifest = await fetchJson(manifestUrl, options);
|
|
429
|
-
if (!manifest || !Array.isArray(manifest.skills)) {
|
|
430
|
-
throw new Error('Invalid remote manifest: expected { skills: [] }');
|
|
431
|
-
}
|
|
432
|
-
const signature = verifyManifestSignature(manifest, options);
|
|
433
|
-
|
|
434
|
-
const savedManifest = {
|
|
435
|
-
version: manifest.version || 1,
|
|
436
|
-
description: manifest.description || 'Remote external skill catalog',
|
|
437
|
-
syncedAt: new Date().toISOString(),
|
|
438
|
-
signatureVerified: Boolean(signature.verified),
|
|
439
|
-
skills: []
|
|
440
|
-
};
|
|
441
|
-
|
|
442
|
-
let fetched = 0;
|
|
443
|
-
const skipped = [];
|
|
444
|
-
|
|
445
|
-
for (const item of manifest.skills) {
|
|
446
|
-
const entry = typeof item === 'string'
|
|
447
|
-
? { slug: normalizeSlug(item) }
|
|
448
|
-
: {
|
|
449
|
-
slug: normalizeSlug(item.slug || item.id || item.name || ''),
|
|
450
|
-
name: item.name,
|
|
451
|
-
description: item.description,
|
|
452
|
-
tier: item.tier,
|
|
453
|
-
version: item.version,
|
|
454
|
-
checksum: item.checksum,
|
|
455
|
-
contentUrl: item.contentUrl,
|
|
456
|
-
content: item.content
|
|
457
|
-
};
|
|
458
|
-
|
|
459
|
-
if (!entry.slug) continue;
|
|
460
|
-
|
|
461
|
-
let content = typeof entry.content === 'string' ? entry.content : '';
|
|
462
|
-
if (!content) {
|
|
463
|
-
const contentUrl = resolveContentUrl(entry, options);
|
|
464
|
-
if (!contentUrl) {
|
|
465
|
-
skipped.push(entry.slug);
|
|
466
|
-
continue;
|
|
467
|
-
}
|
|
468
|
-
content = await fetchText(contentUrl, options);
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
if (!assertChecksum(content, entry.checksum)) {
|
|
472
|
-
skipped.push(entry.slug);
|
|
473
|
-
continue;
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
const skillDir = path.join(externalRoot, entry.slug);
|
|
477
|
-
fs.mkdirSync(skillDir, { recursive: true });
|
|
478
|
-
fs.writeFileSync(path.join(skillDir, EXTERNAL_CONTENT_FILENAME), content, 'utf-8');
|
|
479
|
-
|
|
480
|
-
savedManifest.skills.push({
|
|
481
|
-
slug: entry.slug,
|
|
482
|
-
name: entry.name || titleCase(entry.slug),
|
|
483
|
-
description: entry.description || '',
|
|
484
|
-
tier: entry.tier || 'pro',
|
|
485
|
-
version: entry.version || '1.0.0',
|
|
486
|
-
checksum: entry.checksum || `sha256:${hashContent(content)}`
|
|
487
|
-
});
|
|
488
|
-
fetched++;
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
fs.writeFileSync(path.join(externalRoot, 'manifest.json'), JSON.stringify(savedManifest, null, 2), 'utf-8');
|
|
492
|
-
resetExternalCache();
|
|
493
|
-
|
|
494
|
-
return {
|
|
495
|
-
fetched,
|
|
496
|
-
skipped,
|
|
497
|
-
cacheRoot,
|
|
498
|
-
manifestPath: path.join(externalRoot, 'manifest.json'),
|
|
499
|
-
signatureVerified: Boolean(signature.verified)
|
|
500
|
-
};
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
function loadExternalManifestIds(manifestPath = EXTERNAL_MANIFEST_PATH) {
|
|
504
|
-
return loadExternalManifest(manifestPath)
|
|
505
|
-
.map(item => String(item.slug || '').trim())
|
|
506
|
-
.filter(Boolean);
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
function getBuiltInCache() {
|
|
510
|
-
if (!builtInCache) {
|
|
511
|
-
builtInCache = scanBuiltInSkills();
|
|
512
|
-
}
|
|
513
|
-
return builtInCache;
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
function getExternalCache() {
|
|
517
|
-
if (!externalCache) {
|
|
518
|
-
externalCache = scanExternalSkills();
|
|
519
|
-
}
|
|
520
|
-
return externalCache;
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
function getCategories() {
|
|
524
|
-
return Object.keys(getBuiltInCache().categories);
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
function getPatternsByCategory(category) {
|
|
528
|
-
return getBuiltInCache().categories[category] || [];
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
function listSkills(options = {}) {
|
|
532
|
-
const { includeExternal = false } = options;
|
|
533
|
-
const builtIn = Object.keys(getBuiltInCache().metadata).sort();
|
|
534
|
-
if (!includeExternal) {
|
|
535
|
-
return builtIn;
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
const external = Object.keys(getExternalCache()).sort();
|
|
539
|
-
return [...builtIn, ...external];
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
function listExternalSkills(options = {}) {
|
|
543
|
-
const { limit } = options;
|
|
544
|
-
const all = Object.values(getExternalCache())
|
|
545
|
-
.map(skill => skill.id)
|
|
546
|
-
.sort();
|
|
547
|
-
if (typeof limit === 'number' && limit >= 0) {
|
|
548
|
-
return all.slice(0, limit);
|
|
549
|
-
}
|
|
550
|
-
return all;
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
function getSkillPath(skillName) {
|
|
554
|
-
const skill = getBuiltInCache().metadata[skillName];
|
|
555
|
-
return skill ? skill.path : undefined;
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
function resolveExternalSkillName(skillName) {
|
|
559
|
-
if (!skillName) return null;
|
|
560
|
-
if (skillName.startsWith('external/')) {
|
|
561
|
-
return skillName;
|
|
562
|
-
}
|
|
563
|
-
const direct = `external/${skillName}`;
|
|
564
|
-
if (getExternalCache()[direct]) {
|
|
565
|
-
return direct;
|
|
566
|
-
}
|
|
567
|
-
return null;
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
function loadSkill(skillName, options = {}) {
|
|
571
|
-
const { includeExternal = false } = options;
|
|
572
|
-
|
|
573
|
-
const builtInPath = getSkillPath(skillName);
|
|
574
|
-
if (builtInPath && fs.existsSync(builtInPath)) {
|
|
575
|
-
return fs.readFileSync(builtInPath, 'utf-8');
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
if (!includeExternal) {
|
|
579
|
-
return null;
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
const externalId = resolveExternalSkillName(skillName);
|
|
583
|
-
if (!externalId) {
|
|
584
|
-
return null;
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
const externalSkill = getExternalCache()[externalId];
|
|
588
|
-
if (!externalSkill || !fs.existsSync(externalSkill.path)) {
|
|
589
|
-
return null;
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
return fs.readFileSync(externalSkill.path, 'utf-8');
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
function searchSkills(query, options = {}) {
|
|
596
|
-
const { includeExternal = false, limit } = options;
|
|
597
|
-
const lowerQuery = String(query || '').toLowerCase();
|
|
598
|
-
if (!lowerQuery) return [];
|
|
599
|
-
|
|
600
|
-
const builtInResults = Object.values(getBuiltInCache().metadata)
|
|
601
|
-
.filter(skill => {
|
|
602
|
-
return skill.id.toLowerCase().includes(lowerQuery) ||
|
|
603
|
-
skill.name.toLowerCase().includes(lowerQuery) ||
|
|
604
|
-
skill.description.toLowerCase().includes(lowerQuery);
|
|
605
|
-
})
|
|
606
|
-
.map(skill => skill.id);
|
|
607
|
-
|
|
608
|
-
let results = builtInResults;
|
|
609
|
-
|
|
610
|
-
if (includeExternal) {
|
|
611
|
-
const externalResults = Object.values(getExternalCache())
|
|
612
|
-
.filter(skill => {
|
|
613
|
-
return skill.id.toLowerCase().includes(lowerQuery) ||
|
|
614
|
-
skill.slug.toLowerCase().includes(lowerQuery) ||
|
|
615
|
-
skill.name.toLowerCase().includes(lowerQuery) ||
|
|
616
|
-
skill.description.toLowerCase().includes(lowerQuery);
|
|
617
|
-
})
|
|
618
|
-
.map(skill => skill.id);
|
|
619
|
-
results = results.concat(externalResults);
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
const deduped = [...new Set(results)].sort();
|
|
623
|
-
if (typeof limit === 'number' && limit >= 0) {
|
|
624
|
-
return deduped.slice(0, limit);
|
|
625
|
-
}
|
|
626
|
-
return deduped;
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
function getSkillMetadata(skillName) {
|
|
630
|
-
const builtIn = getBuiltInCache().metadata[skillName];
|
|
631
|
-
if (builtIn) return builtIn;
|
|
632
|
-
|
|
633
|
-
const externalId = resolveExternalSkillName(skillName);
|
|
634
|
-
if (externalId) {
|
|
635
|
-
return getExternalCache()[externalId] || null;
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
return null;
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
function hasExternalSkillLibrary() {
|
|
642
|
-
const cacheManifestPath = getExternalCacheManifestPath();
|
|
643
|
-
return fs.existsSync(EXTERNAL_MANIFEST_PATH) || fs.existsSync(cacheManifestPath);
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
function parseHeadings(content) {
|
|
647
|
-
const headings = [];
|
|
648
|
-
const lines = content.split('\n');
|
|
649
|
-
for (let i = 0; i < lines.length; i++) {
|
|
650
|
-
const line = lines[i];
|
|
651
|
-
const match = line.match(/^(#{1,6})\s+(.+)$/);
|
|
652
|
-
if (match) {
|
|
653
|
-
headings.push({
|
|
654
|
-
level: match[1].length,
|
|
655
|
-
title: match[2].trim(),
|
|
656
|
-
line: i
|
|
657
|
-
});
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
return headings;
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
function buildSummary(content) {
|
|
664
|
-
const withoutFrontmatter = content.replace(/^---[\s\S]*?---\n?/, '');
|
|
665
|
-
const titleMatch = withoutFrontmatter.match(/^#\s+(.+)$/m);
|
|
666
|
-
const title = titleMatch ? titleMatch[1].trim() : 'Skill';
|
|
667
|
-
|
|
668
|
-
const lines = withoutFrontmatter.split('\n');
|
|
669
|
-
let firstParagraph = '';
|
|
670
|
-
let seenTitle = false;
|
|
671
|
-
for (const line of lines) {
|
|
672
|
-
if (!seenTitle && /^#\s+/.test(line)) {
|
|
673
|
-
seenTitle = true;
|
|
674
|
-
continue;
|
|
675
|
-
}
|
|
676
|
-
if (seenTitle && line.trim() && !line.startsWith('#')) {
|
|
677
|
-
firstParagraph = line.trim();
|
|
678
|
-
break;
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
const headings = parseHeadings(withoutFrontmatter)
|
|
683
|
-
.filter(h => h.level <= 3)
|
|
684
|
-
.slice(0, 8)
|
|
685
|
-
.map(h => `- ${h.title}`);
|
|
686
|
-
|
|
687
|
-
let out = `# ${title}\n\n`;
|
|
688
|
-
if (firstParagraph) {
|
|
689
|
-
out += `${firstParagraph}\n\n`;
|
|
690
|
-
}
|
|
691
|
-
if (headings.length > 0) {
|
|
692
|
-
out += '## Key Sections\n';
|
|
693
|
-
out += `${headings.join('\n')}\n`;
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
return out.trim();
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
function selectSections(content, requestedSections) {
|
|
700
|
-
const lines = content.split('\n');
|
|
701
|
-
const headings = parseHeadings(content);
|
|
702
|
-
const requested = requestedSections
|
|
703
|
-
.map(value => String(value || '').trim().toLowerCase())
|
|
704
|
-
.filter(Boolean);
|
|
705
|
-
|
|
706
|
-
if (requested.length === 0 || headings.length === 0) {
|
|
707
|
-
return content;
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
const blocks = [];
|
|
711
|
-
for (let i = 0; i < headings.length; i++) {
|
|
712
|
-
const current = headings[i];
|
|
713
|
-
const next = headings[i + 1];
|
|
714
|
-
const startLine = current.line;
|
|
715
|
-
const endLine = next ? next.line : lines.length;
|
|
716
|
-
const title = current.title.toLowerCase();
|
|
717
|
-
const match = requested.some(section => title.includes(section));
|
|
718
|
-
if (match) {
|
|
719
|
-
blocks.push(lines.slice(startLine, endLine).join('\n').trim());
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
if (blocks.length === 0) {
|
|
724
|
-
return '';
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
return blocks.join('\n\n');
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
function applyMaxChars(content, maxChars) {
|
|
731
|
-
if (!Number.isFinite(maxChars) || maxChars <= 0) {
|
|
732
|
-
return content;
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
if (content.length <= maxChars) {
|
|
736
|
-
return content;
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
const clipped = content.slice(0, maxChars).trimEnd();
|
|
740
|
-
return `${clipped}\n\n... [truncated]`;
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
function formatSkillContent(content, options = {}) {
|
|
744
|
-
const {
|
|
745
|
-
summary = false,
|
|
746
|
-
sections,
|
|
747
|
-
maxChars
|
|
748
|
-
} = options;
|
|
749
|
-
|
|
750
|
-
let output = summary ? buildSummary(content) : content;
|
|
751
|
-
|
|
752
|
-
const sectionList = Array.isArray(sections)
|
|
753
|
-
? sections
|
|
754
|
-
: (typeof sections === 'string' ? sections.split(',') : []);
|
|
755
|
-
if (!summary && sectionList.length > 0) {
|
|
756
|
-
output = selectSections(output, sectionList);
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
const parsedMax = Number(maxChars);
|
|
760
|
-
output = applyMaxChars(output, parsedMax);
|
|
761
|
-
|
|
762
|
-
return output;
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
module.exports = {
|
|
766
|
-
PATTERNS_DIR,
|
|
767
|
-
EXTERNAL_SKILLS_DIR,
|
|
768
|
-
EXTERNAL_MANIFEST_PATH,
|
|
769
|
-
DEFAULT_EXTERNAL_CACHE_ROOT,
|
|
770
|
-
getExternalCacheRoot,
|
|
771
|
-
getExternalCacheManifestPath,
|
|
772
|
-
syncExternalCatalog,
|
|
773
|
-
canonicalManifestPayload,
|
|
774
|
-
verifyManifestSignature,
|
|
775
|
-
resetExternalCache,
|
|
776
|
-
loadExternalManifestIds,
|
|
777
|
-
getCategories,
|
|
778
|
-
getPatternsByCategory,
|
|
779
|
-
listSkills,
|
|
780
|
-
listExternalSkills,
|
|
781
|
-
getSkillPath,
|
|
782
|
-
loadSkill,
|
|
783
|
-
searchSkills,
|
|
784
|
-
getSkillMetadata,
|
|
785
|
-
hasExternalSkillLibrary,
|
|
786
|
-
formatSkillContent
|
|
787
|
-
};
|