@devran-ai/kit 4.1.0
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/.agent/CheatSheet.md +350 -0
- package/.agent/README.md +76 -0
- package/.agent/agents/README.md +155 -0
- package/.agent/agents/architect.md +185 -0
- package/.agent/agents/backend-specialist.md +276 -0
- package/.agent/agents/build-error-resolver.md +207 -0
- package/.agent/agents/code-reviewer.md +162 -0
- package/.agent/agents/database-architect.md +138 -0
- package/.agent/agents/devops-engineer.md +144 -0
- package/.agent/agents/doc-updater.md +229 -0
- package/.agent/agents/e2e-runner.md +145 -0
- package/.agent/agents/explorer-agent.md +143 -0
- package/.agent/agents/frontend-specialist.md +144 -0
- package/.agent/agents/go-reviewer.md +128 -0
- package/.agent/agents/knowledge-agent.md +197 -0
- package/.agent/agents/mobile-developer.md +150 -0
- package/.agent/agents/performance-optimizer.md +175 -0
- package/.agent/agents/planner.md +133 -0
- package/.agent/agents/pr-reviewer.md +148 -0
- package/.agent/agents/python-reviewer.md +123 -0
- package/.agent/agents/refactor-cleaner.md +201 -0
- package/.agent/agents/reliability-engineer.md +156 -0
- package/.agent/agents/security-reviewer.md +141 -0
- package/.agent/agents/sprint-orchestrator.md +124 -0
- package/.agent/agents/tdd-guide.md +179 -0
- package/.agent/agents/typescript-reviewer.md +110 -0
- package/.agent/checklists/README.md +102 -0
- package/.agent/checklists/pre-commit.md +93 -0
- package/.agent/checklists/session-end.md +99 -0
- package/.agent/checklists/session-start.md +102 -0
- package/.agent/checklists/task-complete.md +81 -0
- package/.agent/commands/README.md +130 -0
- package/.agent/commands/adr.md +29 -0
- package/.agent/commands/ask.md +28 -0
- package/.agent/commands/build.md +30 -0
- package/.agent/commands/changelog.md +40 -0
- package/.agent/commands/checkpoint.md +28 -0
- package/.agent/commands/code-review.md +65 -0
- package/.agent/commands/compact.md +28 -0
- package/.agent/commands/cook.md +30 -0
- package/.agent/commands/db.md +30 -0
- package/.agent/commands/debug.md +31 -0
- package/.agent/commands/deploy.md +37 -0
- package/.agent/commands/design.md +29 -0
- package/.agent/commands/doc.md +30 -0
- package/.agent/commands/eval.md +30 -0
- package/.agent/commands/fix.md +32 -0
- package/.agent/commands/git.md +32 -0
- package/.agent/commands/help.md +273 -0
- package/.agent/commands/implement.md +30 -0
- package/.agent/commands/integrate.md +32 -0
- package/.agent/commands/learn.md +29 -0
- package/.agent/commands/perf.md +31 -0
- package/.agent/commands/plan.md +56 -0
- package/.agent/commands/pr-describe.md +65 -0
- package/.agent/commands/pr-fix.md +45 -0
- package/.agent/commands/pr-merge.md +45 -0
- package/.agent/commands/pr-review.md +50 -0
- package/.agent/commands/pr-split.md +54 -0
- package/.agent/commands/pr-status.md +56 -0
- package/.agent/commands/pr.md +58 -0
- package/.agent/commands/refactor.md +32 -0
- package/.agent/commands/research.md +28 -0
- package/.agent/commands/scout.md +30 -0
- package/.agent/commands/security-scan.md +33 -0
- package/.agent/commands/setup.md +31 -0
- package/.agent/commands/status.md +59 -0
- package/.agent/commands/tdd.md +73 -0
- package/.agent/commands/verify.md +58 -0
- package/.agent/contexts/brainstorm.md +26 -0
- package/.agent/contexts/debug.md +28 -0
- package/.agent/contexts/implement.md +29 -0
- package/.agent/contexts/plan-quality-log.md +30 -0
- package/.agent/contexts/review.md +27 -0
- package/.agent/contexts/ship.md +28 -0
- package/.agent/decisions/001-trust-grade-governance.md +46 -0
- package/.agent/decisions/002-cross-ide-generation.md +15 -0
- package/.agent/engine/identity.json +4 -0
- package/.agent/engine/loading-rules.json +193 -0
- package/.agent/engine/marketplace-index.json +29 -0
- package/.agent/engine/mcp-servers/filesystem.json +9 -0
- package/.agent/engine/mcp-servers/github.json +11 -0
- package/.agent/engine/mcp-servers/postgres.json +11 -0
- package/.agent/engine/mcp-servers/supabase.json +11 -0
- package/.agent/engine/mcp-servers/vercel.json +11 -0
- package/.agent/engine/reliability-config.json +14 -0
- package/.agent/engine/sdlc-map.json +50 -0
- package/.agent/engine/workflow-state.json +167 -0
- package/.agent/hooks/README.md +101 -0
- package/.agent/hooks/hooks.json +104 -0
- package/.agent/hooks/templates/session-end.md +110 -0
- package/.agent/hooks/templates/session-start.md +95 -0
- package/.agent/manifest.json +466 -0
- package/.agent/rules/agent-upgrade-policy.md +56 -0
- package/.agent/rules/architecture.md +111 -0
- package/.agent/rules/coding-style.md +75 -0
- package/.agent/rules/documentation.md +74 -0
- package/.agent/rules/git-workflow.md +140 -0
- package/.agent/rules/quality-gate.md +117 -0
- package/.agent/rules/security.md +67 -0
- package/.agent/rules/sprint-tracking.md +103 -0
- package/.agent/rules/testing.md +80 -0
- package/.agent/rules/workflow-standards.md +30 -0
- package/.agent/rules.md +293 -0
- package/.agent/session-context.md +69 -0
- package/.agent/session-state.json +27 -0
- package/.agent/skills/README.md +135 -0
- package/.agent/skills/api-patterns/SKILL.md +117 -0
- package/.agent/skills/app-builder/SKILL.md +202 -0
- package/.agent/skills/architecture/SKILL.md +101 -0
- package/.agent/skills/behavioral-modes/SKILL.md +295 -0
- package/.agent/skills/brainstorming/SKILL.md +156 -0
- package/.agent/skills/clean-code/SKILL.md +142 -0
- package/.agent/skills/context-budget/SKILL.md +78 -0
- package/.agent/skills/continuous-learning/SKILL.md +145 -0
- package/.agent/skills/database-design/SKILL.md +303 -0
- package/.agent/skills/debugging-strategies/SKILL.md +158 -0
- package/.agent/skills/deployment-procedures/SKILL.md +191 -0
- package/.agent/skills/docker-patterns/SKILL.md +161 -0
- package/.agent/skills/eval-harness/SKILL.md +89 -0
- package/.agent/skills/frontend-patterns/SKILL.md +141 -0
- package/.agent/skills/git-workflow/SKILL.md +159 -0
- package/.agent/skills/i18n-localization/SKILL.md +191 -0
- package/.agent/skills/intelligent-routing/SKILL.md +180 -0
- package/.agent/skills/mcp-integration/SKILL.md +240 -0
- package/.agent/skills/mobile-design/SKILL.md +191 -0
- package/.agent/skills/nodejs-patterns/SKILL.md +164 -0
- package/.agent/skills/parallel-agents/SKILL.md +200 -0
- package/.agent/skills/performance-profiling/SKILL.md +134 -0
- package/.agent/skills/plan-validation/SKILL.md +192 -0
- package/.agent/skills/plan-writing/SKILL.md +183 -0
- package/.agent/skills/plan-writing/domain-enhancers.md +184 -0
- package/.agent/skills/plan-writing/plan-retrospective.md +116 -0
- package/.agent/skills/plan-writing/plan-schema.md +119 -0
- package/.agent/skills/pr-toolkit/SKILL.md +174 -0
- package/.agent/skills/production-readiness/SKILL.md +126 -0
- package/.agent/skills/security-practices/SKILL.md +109 -0
- package/.agent/skills/shell-conventions/SKILL.md +92 -0
- package/.agent/skills/strategic-compact/SKILL.md +62 -0
- package/.agent/skills/testing-patterns/SKILL.md +141 -0
- package/.agent/skills/typescript-expert/SKILL.md +160 -0
- package/.agent/skills/ui-ux-pro-max/SKILL.md +137 -0
- package/.agent/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/.agent/skills/ui-ux-pro-max/data/colors.csv +97 -0
- package/.agent/skills/ui-ux-pro-max/data/icons.csv +101 -0
- package/.agent/skills/ui-ux-pro-max/data/landing.csv +31 -0
- package/.agent/skills/ui-ux-pro-max/data/products.csv +97 -0
- package/.agent/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/.agent/skills/ui-ux-pro-max/data/styles.csv +68 -0
- package/.agent/skills/ui-ux-pro-max/data/typography.csv +58 -0
- package/.agent/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/.agent/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/.agent/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/.agent/skills/ui-ux-pro-max/scripts/core.py +253 -0
- package/.agent/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
- package/.agent/skills/ui-ux-pro-max/scripts/search.py +114 -0
- package/.agent/skills/verification-loop/SKILL.md +89 -0
- package/.agent/skills/webapp-testing/SKILL.md +175 -0
- package/.agent/templates/adr-template.md +32 -0
- package/.agent/templates/bug-report.md +37 -0
- package/.agent/templates/feature-request.md +32 -0
- package/.agent/workflows/README.md +101 -0
- package/.agent/workflows/brainstorm.md +86 -0
- package/.agent/workflows/create.md +85 -0
- package/.agent/workflows/debug.md +83 -0
- package/.agent/workflows/deploy.md +114 -0
- package/.agent/workflows/enhance.md +85 -0
- package/.agent/workflows/orchestrate.md +106 -0
- package/.agent/workflows/plan.md +105 -0
- package/.agent/workflows/pr-fix.md +163 -0
- package/.agent/workflows/pr-merge.md +117 -0
- package/.agent/workflows/pr-review.md +178 -0
- package/.agent/workflows/pr-split.md +118 -0
- package/.agent/workflows/pr.md +184 -0
- package/.agent/workflows/preflight.md +107 -0
- package/.agent/workflows/preview.md +95 -0
- package/.agent/workflows/quality-gate.md +103 -0
- package/.agent/workflows/retrospective.md +100 -0
- package/.agent/workflows/review.md +104 -0
- package/.agent/workflows/status.md +89 -0
- package/.agent/workflows/test.md +98 -0
- package/.agent/workflows/ui-ux-pro-max.md +93 -0
- package/.agent/workflows/upgrade.md +97 -0
- package/LICENSE +21 -0
- package/README.md +218 -0
- package/bin/kit.js +773 -0
- package/lib/agent-registry.js +228 -0
- package/lib/agent-reputation.js +343 -0
- package/lib/circuit-breaker.js +195 -0
- package/lib/cli-commands.js +322 -0
- package/lib/config-validator.js +274 -0
- package/lib/conflict-detector.js +252 -0
- package/lib/constants.js +47 -0
- package/lib/engineering-manager.js +336 -0
- package/lib/error-budget.js +370 -0
- package/lib/hook-system.js +256 -0
- package/lib/ide-generator.js +434 -0
- package/lib/identity.js +240 -0
- package/lib/io.js +146 -0
- package/lib/learning-engine.js +163 -0
- package/lib/loading-engine.js +421 -0
- package/lib/logger.js +118 -0
- package/lib/marketplace.js +321 -0
- package/lib/plugin-system.js +604 -0
- package/lib/plugin-verifier.js +197 -0
- package/lib/rate-limiter.js +113 -0
- package/lib/security-scanner.js +312 -0
- package/lib/self-healing.js +468 -0
- package/lib/session-manager.js +264 -0
- package/lib/skill-sandbox.js +244 -0
- package/lib/task-governance.js +522 -0
- package/lib/task-model.js +332 -0
- package/lib/updater.js +240 -0
- package/lib/verify.js +279 -0
- package/lib/workflow-engine.js +373 -0
- package/lib/workflow-events.js +166 -0
- package/lib/workflow-persistence.js +160 -0
- package/package.json +57 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Devran AI Kit — Skill Marketplace
|
|
3
|
+
*
|
|
4
|
+
* GitHub-based hybrid marketplace for discovering, installing,
|
|
5
|
+
* and managing community skills and plugins.
|
|
6
|
+
*
|
|
7
|
+
* @module lib/marketplace
|
|
8
|
+
* @author Emre Dursun
|
|
9
|
+
* @since v3.0.0
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const { execFileSync } = require('child_process');
|
|
17
|
+
const { createCircuitBreaker } = require('./circuit-breaker');
|
|
18
|
+
const { createRateLimiter } = require('./rate-limiter');
|
|
19
|
+
|
|
20
|
+
const { AGENT_DIR, ENGINE_DIR } = require('./constants');
|
|
21
|
+
const { writeJsonAtomic } = require('./io');
|
|
22
|
+
const { createLogger } = require('./logger');
|
|
23
|
+
const log = createLogger('marketplace');
|
|
24
|
+
const INDEX_FILE = 'marketplace-index.json';
|
|
25
|
+
|
|
26
|
+
/** Registry index TTL in milliseconds (24 hours) */
|
|
27
|
+
const INDEX_TTL_MS = 24 * 60 * 60 * 1000;
|
|
28
|
+
|
|
29
|
+
/** Git clone timeout in milliseconds */
|
|
30
|
+
const GIT_CLONE_TIMEOUT_MS = 30000;
|
|
31
|
+
|
|
32
|
+
/** Circuit breaker for git clone operations */
|
|
33
|
+
const gitCloneBreaker = createCircuitBreaker('marketplace-git-clone', {
|
|
34
|
+
failureThreshold: 3,
|
|
35
|
+
resetTimeoutMs: 120000,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
/** Rate limiter for marketplace install operations (5 per minute) */
|
|
39
|
+
const installLimiter = createRateLimiter('marketplace-install', {
|
|
40
|
+
maxTokens: 5,
|
|
41
|
+
refillRateMs: 60000,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @typedef {object} MarketEntry
|
|
46
|
+
* @property {string} name - Plugin name
|
|
47
|
+
* @property {string} description - Plugin description
|
|
48
|
+
* @property {string} repository - GitHub repository URL
|
|
49
|
+
* @property {string} version - Latest version
|
|
50
|
+
* @property {string[]} tags - Discovery tags
|
|
51
|
+
* @property {string} author - Author name
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Resolves the marketplace index path.
|
|
56
|
+
*
|
|
57
|
+
* @param {string} projectRoot - Root directory
|
|
58
|
+
* @returns {string}
|
|
59
|
+
*/
|
|
60
|
+
function resolveIndexPath(projectRoot) {
|
|
61
|
+
return path.join(projectRoot, AGENT_DIR, ENGINE_DIR, INDEX_FILE);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Loads the marketplace index from disk.
|
|
66
|
+
*
|
|
67
|
+
* @param {string} projectRoot - Root directory
|
|
68
|
+
* @returns {{ entries: MarketEntry[], lastUpdated: string | null }}
|
|
69
|
+
*/
|
|
70
|
+
function loadIndex(projectRoot) {
|
|
71
|
+
const filePath = resolveIndexPath(projectRoot);
|
|
72
|
+
|
|
73
|
+
if (!fs.existsSync(filePath)) {
|
|
74
|
+
return { entries: [], lastUpdated: null };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
79
|
+
} catch {
|
|
80
|
+
return { entries: [], lastUpdated: null };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Writes the marketplace index atomically.
|
|
86
|
+
*
|
|
87
|
+
* @param {string} projectRoot - Root directory
|
|
88
|
+
* @param {{ entries: MarketEntry[], lastUpdated: string | null }} data
|
|
89
|
+
* @returns {void}
|
|
90
|
+
*/
|
|
91
|
+
function writeIndex(projectRoot, data) {
|
|
92
|
+
const filePath = resolveIndexPath(projectRoot);
|
|
93
|
+
writeJsonAtomic(filePath, data);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Checks if the index is stale (older than TTL).
|
|
98
|
+
*
|
|
99
|
+
* @param {string | null} lastUpdated - ISO timestamp of last update
|
|
100
|
+
* @returns {boolean}
|
|
101
|
+
*/
|
|
102
|
+
function isIndexStale(lastUpdated) {
|
|
103
|
+
if (!lastUpdated) {
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const age = Date.now() - new Date(lastUpdated).getTime();
|
|
108
|
+
return age > INDEX_TTL_MS;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Validates file paths in a plugin manifest for path traversal (D-6).
|
|
113
|
+
*
|
|
114
|
+
* @param {object} manifest - Plugin manifest (plugin.json)
|
|
115
|
+
* @returns {{ valid: boolean, violations: string[] }}
|
|
116
|
+
*/
|
|
117
|
+
function validateManifestPaths(manifest) {
|
|
118
|
+
const violations = [];
|
|
119
|
+
|
|
120
|
+
/** @type {string[]} */
|
|
121
|
+
const pathFields = [];
|
|
122
|
+
|
|
123
|
+
// Collect all file path references from manifest
|
|
124
|
+
if (manifest.file) {
|
|
125
|
+
pathFields.push(manifest.file);
|
|
126
|
+
}
|
|
127
|
+
if (manifest.files && Array.isArray(manifest.files)) {
|
|
128
|
+
pathFields.push(...manifest.files);
|
|
129
|
+
}
|
|
130
|
+
if (manifest.entry) {
|
|
131
|
+
pathFields.push(manifest.entry);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
for (const filePath of pathFields) {
|
|
135
|
+
if (typeof filePath !== 'string') {
|
|
136
|
+
violations.push(`Invalid path type: ${typeof filePath}`);
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Reject absolute paths
|
|
141
|
+
if (path.isAbsolute(filePath)) {
|
|
142
|
+
violations.push(`Absolute path not allowed: ${filePath}`);
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Reject path traversal
|
|
147
|
+
if (filePath.includes('..')) {
|
|
148
|
+
violations.push(`Path traversal not allowed: ${filePath}`);
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Reject paths that escape .agent/
|
|
153
|
+
const normalized = path.normalize(filePath);
|
|
154
|
+
if (normalized.startsWith('..') || path.isAbsolute(normalized)) {
|
|
155
|
+
violations.push(`Path escapes sandbox: ${filePath}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
valid: violations.length === 0,
|
|
161
|
+
violations,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Searches the marketplace index.
|
|
167
|
+
*
|
|
168
|
+
* @param {string} projectRoot - Root directory
|
|
169
|
+
* @param {string} query - Search query
|
|
170
|
+
* @returns {MarketEntry[]}
|
|
171
|
+
*/
|
|
172
|
+
function searchMarket(projectRoot, query) {
|
|
173
|
+
const index = loadIndex(projectRoot);
|
|
174
|
+
const search = query.toLowerCase();
|
|
175
|
+
|
|
176
|
+
return index.entries.filter((entry) => {
|
|
177
|
+
const searchable = [
|
|
178
|
+
entry.name,
|
|
179
|
+
entry.description,
|
|
180
|
+
entry.author,
|
|
181
|
+
...(entry.tags || []),
|
|
182
|
+
].join(' ').toLowerCase();
|
|
183
|
+
|
|
184
|
+
return searchable.includes(search);
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Gets detailed info for a specific marketplace entry.
|
|
190
|
+
*
|
|
191
|
+
* @param {string} projectRoot - Root directory
|
|
192
|
+
* @param {string} pluginName - Plugin name
|
|
193
|
+
* @returns {MarketEntry | null}
|
|
194
|
+
*/
|
|
195
|
+
function getMarketInfo(projectRoot, pluginName) {
|
|
196
|
+
const index = loadIndex(projectRoot);
|
|
197
|
+
return index.entries.find((e) => e.name === pluginName) || null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Installs a plugin from the marketplace via git clone.
|
|
202
|
+
* Validates paths before installation.
|
|
203
|
+
*
|
|
204
|
+
* @param {string} projectRoot - Root directory
|
|
205
|
+
* @param {string} pluginName - Plugin name from the index
|
|
206
|
+
* @returns {{ success: boolean, message: string }}
|
|
207
|
+
*/
|
|
208
|
+
function installFromMarket(projectRoot, pluginName) {
|
|
209
|
+
const entry = getMarketInfo(projectRoot, pluginName);
|
|
210
|
+
|
|
211
|
+
if (!entry) {
|
|
212
|
+
return { success: false, message: `Plugin not found in marketplace: ${pluginName}` };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Validate repository URL
|
|
216
|
+
if (!entry.repository || (!entry.repository.startsWith('https://') && !entry.repository.startsWith('git@'))) {
|
|
217
|
+
return { success: false, message: `Invalid repository URL: ${entry.repository}` };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Rate limit check
|
|
221
|
+
const rateCheck = installLimiter.tryAcquire();
|
|
222
|
+
if (!rateCheck.allowed) {
|
|
223
|
+
return {
|
|
224
|
+
success: false,
|
|
225
|
+
message: `Rate limit exceeded — retry after ${Math.ceil(rateCheck.retryAfterMs / 1000)}s`,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Create temp directory for clone
|
|
230
|
+
const tempDir = path.join(projectRoot, AGENT_DIR, ENGINE_DIR, `_temp_${Date.now()}`);
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
234
|
+
|
|
235
|
+
// Shallow clone with circuit breaker and timeout (uses execFileSync to prevent shell injection)
|
|
236
|
+
try {
|
|
237
|
+
gitCloneBreaker.execute(() => {
|
|
238
|
+
execFileSync(
|
|
239
|
+
'git',
|
|
240
|
+
['clone', '--depth', '1', '--single-branch', entry.repository, tempDir],
|
|
241
|
+
{ timeout: GIT_CLONE_TIMEOUT_MS, stdio: 'pipe' }
|
|
242
|
+
);
|
|
243
|
+
});
|
|
244
|
+
} catch (gitError) {
|
|
245
|
+
return { success: false, message: `Git clone failed: ${gitError.message || 'timeout or network error'}` };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Validate plugin.json exists
|
|
249
|
+
const pluginJsonPath = path.join(tempDir, 'plugin.json');
|
|
250
|
+
if (!fs.existsSync(pluginJsonPath)) {
|
|
251
|
+
return { success: false, message: 'Plugin missing plugin.json manifest' };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Parse and validate manifest paths (D-6)
|
|
255
|
+
let manifest;
|
|
256
|
+
try {
|
|
257
|
+
manifest = JSON.parse(fs.readFileSync(pluginJsonPath, 'utf-8'));
|
|
258
|
+
} catch {
|
|
259
|
+
return { success: false, message: 'Invalid plugin.json format' };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const pathValidation = validateManifestPaths(manifest);
|
|
263
|
+
if (!pathValidation.valid) {
|
|
264
|
+
return {
|
|
265
|
+
success: false,
|
|
266
|
+
message: `Security violation — path traversal detected: ${pathValidation.violations.join('; ')}`,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Delegate to plugin system for actual installation
|
|
271
|
+
try {
|
|
272
|
+
const pluginSystem = require('./plugin-system');
|
|
273
|
+
const result = pluginSystem.installPlugin(tempDir, projectRoot);
|
|
274
|
+
const message = result.errors?.length > 0
|
|
275
|
+
? result.errors.join('; ')
|
|
276
|
+
: 'Plugin installed successfully';
|
|
277
|
+
return { success: result.success, message };
|
|
278
|
+
} catch (installError) {
|
|
279
|
+
return { success: false, message: `Plugin installation failed: ${installError.message}` };
|
|
280
|
+
}
|
|
281
|
+
} finally {
|
|
282
|
+
// Always cleanup temp directory
|
|
283
|
+
try {
|
|
284
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
285
|
+
} catch {
|
|
286
|
+
// Cleanup failure is non-critical
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Updates the registry index from the bundled source.
|
|
293
|
+
*
|
|
294
|
+
* @param {string} projectRoot - Root directory
|
|
295
|
+
* @param {object} [options] - Update options
|
|
296
|
+
* @param {boolean} [options.force] - Force update regardless of TTL
|
|
297
|
+
* @returns {{ updated: boolean, entryCount: number }}
|
|
298
|
+
*/
|
|
299
|
+
function updateRegistryIndex(projectRoot, options = {}) {
|
|
300
|
+
const currentIndex = loadIndex(projectRoot);
|
|
301
|
+
|
|
302
|
+
if (!options.force && !isIndexStale(currentIndex.lastUpdated)) {
|
|
303
|
+
return { updated: false, entryCount: currentIndex.entries.length };
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// For MVP, the index is bundled — just stamp the update time
|
|
307
|
+
currentIndex.lastUpdated = new Date().toISOString();
|
|
308
|
+
writeIndex(projectRoot, currentIndex);
|
|
309
|
+
|
|
310
|
+
return { updated: true, entryCount: currentIndex.entries.length };
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
module.exports = {
|
|
314
|
+
searchMarket,
|
|
315
|
+
getMarketInfo,
|
|
316
|
+
installFromMarket,
|
|
317
|
+
updateRegistryIndex,
|
|
318
|
+
// Exported for testing
|
|
319
|
+
validateManifestPaths,
|
|
320
|
+
isIndexStale,
|
|
321
|
+
};
|