amazingteam 3.0.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/.ai-team/agents/architect.md +144 -0
- package/.ai-team/agents/ci-analyst.md +188 -0
- package/.ai-team/agents/developer.md +176 -0
- package/.ai-team/agents/planner.md +355 -0
- package/.ai-team/agents/qa.md +189 -0
- package/.ai-team/agents/reviewer.md +211 -0
- package/.ai-team/agents/triage.md +146 -0
- package/.ai-team/commands/ci-analyze.md +116 -0
- package/.ai-team/commands/design.md +100 -0
- package/.ai-team/commands/implement.md +108 -0
- package/.ai-team/commands/release-check.md +142 -0
- package/.ai-team/commands/review.md +142 -0
- package/.ai-team/commands/test.md +115 -0
- package/.ai-team/commands/triage.md +138 -0
- package/.ai-team/memory/architect/architecture_notes.md +67 -0
- package/.ai-team/memory/architect/design_rationale.md +113 -0
- package/.ai-team/memory/architect/module_map.md +84 -0
- package/.ai-team/memory/ci-analyst/failure_patterns.md +102 -0
- package/.ai-team/memory/ci-analyst/runbook_references.md +87 -0
- package/.ai-team/memory/developer/bug_investigation.md +102 -0
- package/.ai-team/memory/developer/build_issues.md +115 -0
- package/.ai-team/memory/developer/implementation_notes.md +83 -0
- package/.ai-team/memory/failures/failure_library.md +103 -0
- package/.ai-team/memory/planner/decomposition_notes.md +82 -0
- package/.ai-team/memory/planner/flow_rules.md +86 -0
- package/.ai-team/memory/planner/github_issue_patterns.md +229 -0
- package/.ai-team/memory/qa/regression_cases.md +101 -0
- package/.ai-team/memory/qa/test_strategy.md +138 -0
- package/.ai-team/memory/qa/validation_notes.md +110 -0
- package/.ai-team/memory/reviewer/quality_rules.md +105 -0
- package/.ai-team/memory/reviewer/recurring_risks.md +109 -0
- package/.ai-team/memory/reviewer/review_notes.md +124 -0
- package/.ai-team/memory/triage/classification_heuristics.md +82 -0
- package/.ai-team/memory/triage/debug_notes.md +87 -0
- package/.ai-team/opencode.template.jsonc +216 -0
- package/.ai-team/skills/bugfix-playbook/skill.md +174 -0
- package/.ai-team/skills/ci-failure-analysis/skill.md +176 -0
- package/.ai-team/skills/issue-triage/skill.md +163 -0
- package/.ai-team/skills/regression-checklist/skill.md +176 -0
- package/.ai-team/skills/release-readiness-check/skill.md +216 -0
- package/.ai-team/skills/repo-architecture-reader/skill.md +139 -0
- package/.ai-team/skills/safe-refactor-checklist/skill.md +215 -0
- package/.ai-team/skills/task-breakdown-and-dispatch/skill.md +151 -0
- package/.ai-team/skills/test-first-feature-dev/skill.md +205 -0
- package/.ai-team/workflows/ci.yml +81 -0
- package/.ai-team/workflows/nightly-ai-maintenance.yml +129 -0
- package/.ai-team/workflows/opencode.yml +33 -0
- package/.ai-team/workflows/pr-check.yml +41 -0
- package/.foundation/foundation.lock +38 -0
- package/.foundation/local-overrides.md +97 -0
- package/.foundation/upgrade-history.md +38 -0
- package/.opencode/agents/architect.md +38 -0
- package/.opencode/agents/ci-analyst.md +38 -0
- package/.opencode/agents/developer.md +43 -0
- package/.opencode/agents/planner.md +47 -0
- package/.opencode/agents/qa.md +34 -0
- package/.opencode/agents/reviewer.md +38 -0
- package/.opencode/agents/triage.md +37 -0
- package/.opencode/commands/auto.md +264 -0
- package/.opencode/commands/breakdown-issue.md +94 -0
- package/.opencode/commands/ci-analyze.md +15 -0
- package/.opencode/commands/close-parent-task.md +122 -0
- package/.opencode/commands/design.md +15 -0
- package/.opencode/commands/dispatch-next.md +102 -0
- package/.opencode/commands/implement.md +16 -0
- package/.opencode/commands/release-check.md +16 -0
- package/.opencode/commands/resume.md +88 -0
- package/.opencode/commands/review.md +15 -0
- package/.opencode/commands/show-blockers.md +97 -0
- package/.opencode/commands/summarize-parent.md +121 -0
- package/.opencode/commands/test.md +15 -0
- package/.opencode/commands/triage.md +109 -0
- package/.opencode/skills/bugfix-playbook/SKILL.md +81 -0
- package/.opencode/skills/ci-failure-analysis/SKILL.md +94 -0
- package/.opencode/skills/issue-triage/SKILL.md +80 -0
- package/.opencode/skills/regression-checklist/SKILL.md +81 -0
- package/.opencode/skills/release-readiness-check/SKILL.md +81 -0
- package/.opencode/skills/repo-architecture-reader/SKILL.md +65 -0
- package/.opencode/skills/safe-refactor-checklist/SKILL.md +76 -0
- package/.opencode/skills/task-breakdown-and-dispatch/SKILL.md +255 -0
- package/.opencode/skills/test-first-feature-dev/SKILL.md +78 -0
- package/AGENTS.md +879 -0
- package/CHANGELOG.md +261 -0
- package/LICENSE +21 -0
- package/README.md +1215 -0
- package/VERSION +1 -0
- package/action/__tests__/downloader.test.js +251 -0
- package/action/__tests__/merger.test.js +156 -0
- package/action/__tests__/path-resolver.test.js +199 -0
- package/action/__tests__/validator.test.js +310 -0
- package/action/action.yml +61 -0
- package/action/index.js +223 -0
- package/action/lib/downloader.js +344 -0
- package/action/lib/merger.js +170 -0
- package/action/lib/path-resolver.js +176 -0
- package/action/lib/setup.js +286 -0
- package/action/lib/validator.js +324 -0
- package/cli/__tests__/cli.test.js +270 -0
- package/cli/amazingteam.cjs +225 -0
- package/cli/commands/check-update.cjs +159 -0
- package/cli/commands/init.cjs +412 -0
- package/cli/commands/local.cjs +264 -0
- package/cli/commands/migrate.cjs +316 -0
- package/cli/commands/status.cjs +241 -0
- package/cli/commands/upgrade.cjs +213 -0
- package/cli/commands/validate.cjs +259 -0
- package/cli/commands/version.cjs +59 -0
- package/cli/sync.cjs +237 -0
- package/dist/index.js +35 -0
- package/docs/architecture/overview.md +138 -0
- package/docs/blocker_resolution_design.md +372 -0
- package/docs/bootstrap-model.md +356 -0
- package/docs/config-reference.md +458 -0
- package/docs/how-to-use.md +178 -0
- package/docs/migration-to-v3.md +355 -0
- package/docs/overlay-guide.md +156 -0
- package/docs/patterns/README.md +67 -0
- package/docs/quick-start-v3.md +330 -0
- package/docs/releases/README.md +64 -0
- package/docs/runbooks/ci/README.md +62 -0
- package/docs/runbooks/ci/build-debug.md +120 -0
- package/docs/runbooks/ci/flaky-tests.md +127 -0
- package/docs/runbooks/getting-started.md +81 -0
- package/docs/upgrade-policy.md +188 -0
- package/docs/versioning.md +199 -0
- package/overlays/README.md +30 -0
- package/overlays/ai-agent-product/.ai-team/skills/llm-integration/skill.md +99 -0
- package/overlays/ai-agent-product/docs/ai-agent-architecture.md +68 -0
- package/overlays/ai-agent-product/overlay.yaml +26 -0
- package/overlays/cpp-qt-desktop/.ai-team/skills/qt-signals-slots/skill.md +60 -0
- package/overlays/cpp-qt-desktop/docs/qt-conventions.md +64 -0
- package/overlays/cpp-qt-desktop/overlay.yaml +22 -0
- package/overlays/python-backend/.ai-team/skills/python-testing/skill.md +90 -0
- package/overlays/python-backend/docs/python-style.md +78 -0
- package/overlays/python-backend/overlay.yaml +22 -0
- package/overlays/web-fullstack/.ai-team/skills/frontend-testing/skill.md +70 -0
- package/overlays/web-fullstack/docs/frontend-conventions.md +68 -0
- package/overlays/web-fullstack/overlay.yaml +26 -0
- package/package.json +84 -0
- package/presets/default.yaml +161 -0
- package/presets/go.yaml +43 -0
- package/presets/python.yaml +43 -0
- package/presets/typescript.yaml +40 -0
- package/schemas/config.schema.json +239 -0
- package/scripts/diff_foundation_vs_project.sh +134 -0
- package/scripts/generate_docs.sh +200 -0
- package/scripts/init_project.sh +455 -0
- package/scripts/plan_upgrade.sh +268 -0
- package/scripts/upgrade_foundation.sh +365 -0
- package/scripts/validate-foundation.cjs +278 -0
- package/scripts/validate_foundation.sh +192 -0
- package/scripts/validate_project_setup.sh +171 -0
- package/tasks/README.md +94 -0
- package/tasks/_template/analysis.md +76 -0
- package/tasks/_template/design.md +121 -0
- package/tasks/_template/implementation.md +121 -0
- package/tasks/_template/release.md +119 -0
- package/tasks/_template/review.md +131 -0
- package/tasks/_template/subtasks/task.yaml +24 -0
- package/tasks/_template/task.yaml +75 -0
- package/tasks/_template/validation.md +128 -0
- package/templates/amazingteam.yml +81 -0
- package/templates/gitignore +14 -0
- package/templates/opencode.jsonc +216 -0
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Foundation Downloader Module
|
|
3
|
+
* Downloads AmazingTeam Foundation from NPM or GitHub
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const https = require('https');
|
|
7
|
+
const http = require('http');
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const { execSync } = require('child_process');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Download options
|
|
14
|
+
* @typedef {Object} DownloadOptions
|
|
15
|
+
* @property {string} version - Foundation version
|
|
16
|
+
* @property {string} [registry] - NPM registry URL
|
|
17
|
+
* @property {string} [githubToken] - GitHub token for private repos
|
|
18
|
+
* @property {string} [cacheDir] - Cache directory
|
|
19
|
+
* @property {number} [retries] - Number of retries (default: 3)
|
|
20
|
+
* @property {number} [timeout] - Timeout in ms (default: 60000)
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const DEFAULT_REGISTRY = 'https://registry.npmjs.org';
|
|
24
|
+
const GITHUB_REPO = 'your-org/amazingteam';
|
|
25
|
+
const DEFAULT_CACHE_DIR = path.join(process.env.HOME || process.env.USERPROFILE, '.amazingteam-cache');
|
|
26
|
+
const MAX_RETRIES = 3;
|
|
27
|
+
const TIMEOUT = 60000; // 60 seconds
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Download a file from URL
|
|
31
|
+
* @param {string} url - URL to download
|
|
32
|
+
* @param {string} dest - Destination path
|
|
33
|
+
* @param {number} timeout - Timeout in ms
|
|
34
|
+
* @returns {Promise<void>}
|
|
35
|
+
*/
|
|
36
|
+
function downloadFile(url, dest, timeout = TIMEOUT) {
|
|
37
|
+
return new Promise((resolve, reject) => {
|
|
38
|
+
const protocol = url.startsWith('https') ? https : http;
|
|
39
|
+
|
|
40
|
+
const request = protocol.get(url, {
|
|
41
|
+
timeout,
|
|
42
|
+
headers: {
|
|
43
|
+
'User-Agent': 'amazingteam-downloader/1.0'
|
|
44
|
+
}
|
|
45
|
+
}, (response) => {
|
|
46
|
+
// Handle redirects
|
|
47
|
+
if (response.statusCode === 301 || response.statusCode === 302) {
|
|
48
|
+
const redirectUrl = response.headers.location;
|
|
49
|
+
downloadFile(redirectUrl, dest, timeout).then(resolve).catch(reject);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (response.statusCode !== 200) {
|
|
54
|
+
reject(new Error(`Download failed: HTTP ${response.statusCode}`));
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const file = fs.createWriteStream(dest);
|
|
59
|
+
response.pipe(file);
|
|
60
|
+
|
|
61
|
+
file.on('finish', () => {
|
|
62
|
+
file.close();
|
|
63
|
+
resolve();
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
request.on('error', (err) => {
|
|
68
|
+
fs.unlink(dest, () => {}); // Delete partial file
|
|
69
|
+
reject(err);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
request.on('timeout', () => {
|
|
73
|
+
request.destroy();
|
|
74
|
+
reject(new Error('Download timeout'));
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Download from NPM registry
|
|
81
|
+
* @param {string} version - Foundation version
|
|
82
|
+
* @param {string} destDir - Destination directory
|
|
83
|
+
* @param {string} [registry] - NPM registry URL
|
|
84
|
+
* @returns {Promise<string>} Path to downloaded package
|
|
85
|
+
*/
|
|
86
|
+
async function downloadFromNpm(version, destDir, registry = DEFAULT_REGISTRY) {
|
|
87
|
+
const packageName = 'amazingteam';
|
|
88
|
+
const tarballUrl = `${registry}/${packageName}/-/${packageName}-${version}.tgz`;
|
|
89
|
+
const tarballPath = path.join(destDir, `${packageName}-${version}.tgz`);
|
|
90
|
+
|
|
91
|
+
console.log(`Downloading from NPM: ${tarballUrl}`);
|
|
92
|
+
|
|
93
|
+
await downloadFile(tarballUrl, tarballPath);
|
|
94
|
+
|
|
95
|
+
// Extract tarball
|
|
96
|
+
const extractDir = path.join(destDir, 'package');
|
|
97
|
+
if (fs.existsSync(extractDir)) {
|
|
98
|
+
fs.rmSync(extractDir, { recursive: true });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Use tar command (available on most systems)
|
|
102
|
+
try {
|
|
103
|
+
execSync(`tar -xzf "${tarballPath}" -C "${destDir}"`, { stdio: 'inherit' });
|
|
104
|
+
} catch (err) {
|
|
105
|
+
throw new Error(`Failed to extract tarball: ${err.message}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Clean up tarball
|
|
109
|
+
fs.unlinkSync(tarballPath);
|
|
110
|
+
|
|
111
|
+
return extractDir;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Download from GitHub releases
|
|
116
|
+
* @param {string} version - Foundation version
|
|
117
|
+
* @param {string} destDir - Destination directory
|
|
118
|
+
* @param {string} [githubToken] - GitHub token for private repos
|
|
119
|
+
* @returns {Promise<string>} Path to downloaded package
|
|
120
|
+
*/
|
|
121
|
+
async function downloadFromGitHub(version, destDir, githubToken) {
|
|
122
|
+
const tarballUrl = `https://github.com/${GITHUB_REPO}/archive/v${version}.tar.gz`;
|
|
123
|
+
const tarballPath = path.join(destDir, `amazingteam-${version}.tar.gz`);
|
|
124
|
+
|
|
125
|
+
console.log(`Downloading from GitHub: ${tarballUrl}`);
|
|
126
|
+
|
|
127
|
+
const headers = {
|
|
128
|
+
'User-Agent': 'amazingteam-downloader/1.0'
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
if (githubToken) {
|
|
132
|
+
headers['Authorization'] = `token ${githubToken}`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
await new Promise((resolve, reject) => {
|
|
136
|
+
const request = https.get(tarballUrl, {
|
|
137
|
+
headers,
|
|
138
|
+
timeout: TIMEOUT
|
|
139
|
+
}, (response) => {
|
|
140
|
+
// Handle redirects
|
|
141
|
+
if (response.statusCode === 301 || response.statusCode === 302) {
|
|
142
|
+
const redirectUrl = response.headers.location;
|
|
143
|
+
downloadFile(redirectUrl, tarballPath).then(resolve).catch(reject);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (response.statusCode !== 200) {
|
|
148
|
+
reject(new Error(`GitHub download failed: HTTP ${response.statusCode}`));
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const file = fs.createWriteStream(tarballPath);
|
|
153
|
+
response.pipe(file);
|
|
154
|
+
|
|
155
|
+
file.on('finish', () => {
|
|
156
|
+
file.close();
|
|
157
|
+
resolve();
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
request.on('error', reject);
|
|
162
|
+
request.on('timeout', () => {
|
|
163
|
+
request.destroy();
|
|
164
|
+
reject(new Error('GitHub download timeout'));
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Extract tarball
|
|
169
|
+
try {
|
|
170
|
+
execSync(`tar -xzf "${tarballPath}" -C "${destDir}"`, { stdio: 'inherit' });
|
|
171
|
+
} catch (err) {
|
|
172
|
+
throw new Error(`Failed to extract GitHub tarball: ${err.message}`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Clean up tarball
|
|
176
|
+
fs.unlinkSync(tarballPath);
|
|
177
|
+
|
|
178
|
+
// GitHub extracts to amazingteam-{version}
|
|
179
|
+
const extractDir = path.join(destDir, `amazingteam-${version}`);
|
|
180
|
+
return extractDir;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Download with retry
|
|
185
|
+
* @param {Function} downloadFn - Download function
|
|
186
|
+
* @param {number} retries - Number of retries
|
|
187
|
+
* @returns {Promise<string>}
|
|
188
|
+
*/
|
|
189
|
+
async function withRetry(downloadFn, retries = MAX_RETRIES) {
|
|
190
|
+
let lastError;
|
|
191
|
+
|
|
192
|
+
for (let i = 0; i < retries; i++) {
|
|
193
|
+
try {
|
|
194
|
+
return await downloadFn();
|
|
195
|
+
} catch (err) {
|
|
196
|
+
lastError = err;
|
|
197
|
+
console.log(`Download attempt ${i + 1} failed: ${err.message}`);
|
|
198
|
+
if (i < retries - 1) {
|
|
199
|
+
const delay = Math.pow(2, i) * 1000; // Exponential backoff
|
|
200
|
+
console.log(`Retrying in ${delay}ms...`);
|
|
201
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
throw lastError;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Download foundation
|
|
211
|
+
* @param {DownloadOptions} options - Download options
|
|
212
|
+
* @returns {Promise<string>} Path to downloaded foundation
|
|
213
|
+
*/
|
|
214
|
+
async function downloadFoundation(options) {
|
|
215
|
+
const {
|
|
216
|
+
version,
|
|
217
|
+
registry = DEFAULT_REGISTRY,
|
|
218
|
+
githubToken,
|
|
219
|
+
cacheDir = DEFAULT_CACHE_DIR,
|
|
220
|
+
retries = MAX_RETRIES
|
|
221
|
+
} = options;
|
|
222
|
+
|
|
223
|
+
// Create cache directory
|
|
224
|
+
if (!fs.existsSync(cacheDir)) {
|
|
225
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Check cache
|
|
229
|
+
const cachedPath = path.join(cacheDir, `v${version}`);
|
|
230
|
+
if (fs.existsSync(cachedPath)) {
|
|
231
|
+
console.log(`Using cached foundation v${version}`);
|
|
232
|
+
return cachedPath;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Create temp directory for download
|
|
236
|
+
const tempDir = path.join(cacheDir, 'temp');
|
|
237
|
+
if (!fs.existsSync(tempDir)) {
|
|
238
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
let foundationPath;
|
|
242
|
+
|
|
243
|
+
// Try NPM first, then GitHub
|
|
244
|
+
try {
|
|
245
|
+
foundationPath = await withRetry(() => downloadFromNpm(version, tempDir, registry), retries);
|
|
246
|
+
} catch (npmError) {
|
|
247
|
+
console.log(`NPM download failed: ${npmError.message}`);
|
|
248
|
+
console.log('Falling back to GitHub releases...');
|
|
249
|
+
|
|
250
|
+
try {
|
|
251
|
+
foundationPath = await withRetry(() => downloadFromGitHub(version, tempDir, githubToken), retries);
|
|
252
|
+
} catch (githubError) {
|
|
253
|
+
throw new Error(`Both NPM and GitHub downloads failed. NPM: ${npmError.message}, GitHub: ${githubError.message}`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Move to cache
|
|
258
|
+
fs.renameSync(foundationPath, cachedPath);
|
|
259
|
+
|
|
260
|
+
// Clean up temp directory
|
|
261
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
262
|
+
|
|
263
|
+
return cachedPath;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Check if a version exists
|
|
268
|
+
* @param {string} version - Version to check
|
|
269
|
+
* @param {string} [registry] - NPM registry URL
|
|
270
|
+
* @returns {Promise<boolean>}
|
|
271
|
+
*/
|
|
272
|
+
async function versionExists(version, registry = DEFAULT_REGISTRY) {
|
|
273
|
+
const packageName = 'amazingteam';
|
|
274
|
+
const url = `${registry}/${packageName}/${version}`;
|
|
275
|
+
|
|
276
|
+
return new Promise((resolve) => {
|
|
277
|
+
https.get(url, {
|
|
278
|
+
timeout: 10000,
|
|
279
|
+
headers: {
|
|
280
|
+
'User-Agent': 'amazingteam-downloader/1.0'
|
|
281
|
+
}
|
|
282
|
+
}, (response) => {
|
|
283
|
+
resolve(response.statusCode === 200);
|
|
284
|
+
}).on('error', () => {
|
|
285
|
+
resolve(false);
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Get latest version from NPM
|
|
292
|
+
* @param {string} [registry] - NPM registry URL
|
|
293
|
+
* @returns {Promise<string>} Latest version
|
|
294
|
+
*/
|
|
295
|
+
async function getLatestVersion(registry = DEFAULT_REGISTRY) {
|
|
296
|
+
const packageName = 'amazingteam';
|
|
297
|
+
const url = `${registry}/${packageName}/latest`;
|
|
298
|
+
|
|
299
|
+
return new Promise((resolve, reject) => {
|
|
300
|
+
https.get(url, {
|
|
301
|
+
timeout: 10000,
|
|
302
|
+
headers: {
|
|
303
|
+
'User-Agent': 'amazingteam-downloader/1.0'
|
|
304
|
+
}
|
|
305
|
+
}, (response) => {
|
|
306
|
+
if (response.statusCode !== 200) {
|
|
307
|
+
reject(new Error(`Failed to get latest version: HTTP ${response.statusCode}`));
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
let data = '';
|
|
312
|
+
response.on('data', chunk => { data += chunk; });
|
|
313
|
+
response.on('end', () => {
|
|
314
|
+
try {
|
|
315
|
+
const pkg = JSON.parse(data);
|
|
316
|
+
resolve(pkg.version);
|
|
317
|
+
} catch (err) {
|
|
318
|
+
reject(new Error(`Failed to parse response: ${err.message}`));
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
}).on('error', reject);
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Clear download cache
|
|
327
|
+
* @param {string} [cacheDir] - Cache directory
|
|
328
|
+
*/
|
|
329
|
+
function clearCache(cacheDir = DEFAULT_CACHE_DIR) {
|
|
330
|
+
if (fs.existsSync(cacheDir)) {
|
|
331
|
+
fs.rmSync(cacheDir, { recursive: true, force: true });
|
|
332
|
+
console.log('Cache cleared');
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
module.exports = {
|
|
337
|
+
downloadFoundation,
|
|
338
|
+
downloadFromNpm,
|
|
339
|
+
downloadFromGitHub,
|
|
340
|
+
versionExists,
|
|
341
|
+
getLatestVersion,
|
|
342
|
+
clearCache,
|
|
343
|
+
DEFAULT_CACHE_DIR
|
|
344
|
+
};
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration Merger Module
|
|
3
|
+
* Merges user configuration with foundation defaults
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Check if value is a plain object
|
|
8
|
+
* @param {*} value
|
|
9
|
+
* @returns {boolean}
|
|
10
|
+
*/
|
|
11
|
+
function isObject(value) {
|
|
12
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Deep clone an object
|
|
17
|
+
* @param {*} obj
|
|
18
|
+
* @returns {*}
|
|
19
|
+
*/
|
|
20
|
+
function deepClone(obj) {
|
|
21
|
+
if (obj === null || typeof obj !== 'object') {
|
|
22
|
+
return obj;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (Array.isArray(obj)) {
|
|
26
|
+
return obj.map(item => deepClone(item));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const cloned = {};
|
|
30
|
+
for (const key of Object.keys(obj)) {
|
|
31
|
+
cloned[key] = deepClone(obj[key]);
|
|
32
|
+
}
|
|
33
|
+
return cloned;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Deep merge two objects
|
|
38
|
+
* User config overrides foundation defaults
|
|
39
|
+
* Arrays are replaced, not merged
|
|
40
|
+
*
|
|
41
|
+
* @param {Object} foundationDefaults - Base configuration from foundation
|
|
42
|
+
* @param {Object} userConfig - User overrides
|
|
43
|
+
* @returns {Object} Merged configuration
|
|
44
|
+
*/
|
|
45
|
+
function mergeConfig(foundationDefaults, userConfig) {
|
|
46
|
+
const result = deepClone(foundationDefaults);
|
|
47
|
+
|
|
48
|
+
if (!userConfig || typeof userConfig !== 'object') {
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
for (const key of Object.keys(userConfig)) {
|
|
53
|
+
if (userConfig[key] === undefined || userConfig[key] === null) {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Handle $preset key - skip it (it's just a reference)
|
|
58
|
+
if (key === '$preset') {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// If both are objects (not arrays), deep merge
|
|
63
|
+
if (isObject(userConfig[key]) && isObject(result[key])) {
|
|
64
|
+
result[key] = mergeConfig(result[key], userConfig[key]);
|
|
65
|
+
} else {
|
|
66
|
+
// Otherwise, user value replaces default
|
|
67
|
+
result[key] = deepClone(userConfig[key]);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Merge preset with user config
|
|
76
|
+
* @param {Object} presetConfig - Preset configuration (e.g., typescript.yaml)
|
|
77
|
+
* @param {Object} defaultConfig - Default configuration
|
|
78
|
+
* @param {Object} userConfig - User configuration
|
|
79
|
+
* @returns {Object} Final merged configuration
|
|
80
|
+
*/
|
|
81
|
+
function mergeWithPreset(presetConfig, defaultConfig, userConfig) {
|
|
82
|
+
// First merge preset with defaults
|
|
83
|
+
let result = mergeConfig(defaultConfig, presetConfig || {});
|
|
84
|
+
|
|
85
|
+
// Then merge user config
|
|
86
|
+
result = mergeConfig(result, userConfig || {});
|
|
87
|
+
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Validate merged configuration
|
|
93
|
+
* @param {Object} config - Merged configuration
|
|
94
|
+
* @returns {{ valid: boolean, errors: string[] }}
|
|
95
|
+
*/
|
|
96
|
+
function validateMergedConfig(config) {
|
|
97
|
+
const errors = [];
|
|
98
|
+
|
|
99
|
+
// Check required fields
|
|
100
|
+
if (!config.version) {
|
|
101
|
+
errors.push('Missing required field: version');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!config.project) {
|
|
105
|
+
errors.push('Missing required field: project');
|
|
106
|
+
} else {
|
|
107
|
+
if (!config.project.name) {
|
|
108
|
+
errors.push('Missing required field: project.name');
|
|
109
|
+
}
|
|
110
|
+
if (!config.project.language) {
|
|
111
|
+
errors.push('Missing required field: project.language');
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Validate agents
|
|
116
|
+
if (config.agents) {
|
|
117
|
+
const validAgents = ['planner', 'architect', 'developer', 'qa', 'reviewer', 'triage', 'ci_analyst'];
|
|
118
|
+
for (const agentName of Object.keys(config.agents)) {
|
|
119
|
+
if (!validAgents.includes(agentName)) {
|
|
120
|
+
errors.push(`Unknown agent: ${agentName}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Validate workflows
|
|
126
|
+
if (config.workflows) {
|
|
127
|
+
const validRoles = ['planner', 'architect', 'developer', 'qa', 'reviewer', 'triage', 'ci_analyst'];
|
|
128
|
+
for (const [workflowName, workflow] of Object.entries(config.workflows)) {
|
|
129
|
+
if (!workflow.sequence || !Array.isArray(workflow.sequence)) {
|
|
130
|
+
errors.push(`Workflow '${workflowName}' must have a sequence array`);
|
|
131
|
+
} else {
|
|
132
|
+
for (const role of workflow.sequence) {
|
|
133
|
+
if (!validRoles.includes(role)) {
|
|
134
|
+
errors.push(`Unknown role '${role}' in workflow '${workflowName}'`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Validate rules
|
|
142
|
+
if (config.rules) {
|
|
143
|
+
if (config.rules.test_coverage_threshold !== undefined) {
|
|
144
|
+
const threshold = config.rules.test_coverage_threshold;
|
|
145
|
+
if (typeof threshold !== 'number' || threshold < 0 || threshold > 100) {
|
|
146
|
+
errors.push('test_coverage_threshold must be a number between 0 and 100');
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (config.rules.max_function_lines !== undefined) {
|
|
151
|
+
const lines = config.rules.max_function_lines;
|
|
152
|
+
if (typeof lines !== 'number' || lines < 1) {
|
|
153
|
+
errors.push('max_function_lines must be a positive number');
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
valid: errors.length === 0,
|
|
160
|
+
errors
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
module.exports = {
|
|
165
|
+
mergeConfig,
|
|
166
|
+
mergeWithPreset,
|
|
167
|
+
validateMergedConfig,
|
|
168
|
+
deepClone,
|
|
169
|
+
isObject
|
|
170
|
+
};
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path Resolver Module
|
|
3
|
+
* Resolves paths for skills, commands, memory, and tasks
|
|
4
|
+
* Handles cross-platform path resolution
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Path Resolver Class
|
|
11
|
+
* Handles all path resolution for AmazingTeam Foundation
|
|
12
|
+
*/
|
|
13
|
+
class PathResolver {
|
|
14
|
+
/**
|
|
15
|
+
* @param {string} foundationDir - Path to downloaded foundation
|
|
16
|
+
* @param {string} projectDir - Path to user project
|
|
17
|
+
*/
|
|
18
|
+
constructor(foundationDir, projectDir) {
|
|
19
|
+
this.foundationDir = foundationDir;
|
|
20
|
+
this.projectDir = projectDir;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Resolve a skill path (skills are in foundation)
|
|
25
|
+
* @param {string} relativePath - Relative path from foundation
|
|
26
|
+
* @returns {string} Absolute path to skill
|
|
27
|
+
*/
|
|
28
|
+
resolveSkillPath(relativePath) {
|
|
29
|
+
return path.join(this.foundationDir, relativePath);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Resolve a command path (commands are in foundation)
|
|
34
|
+
* @param {string} relativePath - Relative path from foundation
|
|
35
|
+
* @returns {string} Absolute path to command
|
|
36
|
+
*/
|
|
37
|
+
resolveCommandPath(relativePath) {
|
|
38
|
+
return path.join(this.foundationDir, relativePath);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Resolve memory path (memory is in user project - runtime state)
|
|
43
|
+
* @param {string} role - Agent role name
|
|
44
|
+
* @returns {string} Absolute path to role memory directory
|
|
45
|
+
*/
|
|
46
|
+
resolveMemoryPath(role) {
|
|
47
|
+
return path.join(this.projectDir, '.amazingteam', 'memory', role);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Resolve failures library path
|
|
52
|
+
* @returns {string} Absolute path to failures directory
|
|
53
|
+
*/
|
|
54
|
+
resolveFailuresPath() {
|
|
55
|
+
return path.join(this.projectDir, '.amazingteam', 'memory', 'failures');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Resolve task path
|
|
60
|
+
* @param {string|number} taskId - Issue ID
|
|
61
|
+
* @returns {string} Absolute path to task directory
|
|
62
|
+
*/
|
|
63
|
+
resolveTaskPath(taskId) {
|
|
64
|
+
return path.join(this.projectDir, 'tasks', `issue-${taskId}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Resolve task template path
|
|
69
|
+
* @returns {string} Absolute path to task template directory
|
|
70
|
+
*/
|
|
71
|
+
resolveTaskTemplatePath() {
|
|
72
|
+
return path.join(this.projectDir, 'tasks', '_template');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Resolve AGENTS.md path (from foundation, but user can override)
|
|
77
|
+
* @param {boolean} userOverride - Whether user has local AGENTS.md
|
|
78
|
+
* @returns {string} Path to AGENTS.md
|
|
79
|
+
*/
|
|
80
|
+
resolveAgentsPath(userOverride = false) {
|
|
81
|
+
if (userOverride) {
|
|
82
|
+
return path.join(this.projectDir, 'AGENTS.md');
|
|
83
|
+
}
|
|
84
|
+
return path.join(this.foundationDir, 'AGENTS.md');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Resolve OpenCode config path (in user project)
|
|
89
|
+
* @returns {string} Absolute path to opencode.jsonc
|
|
90
|
+
*/
|
|
91
|
+
resolveOpenCodeConfigPath() {
|
|
92
|
+
return path.join(this.projectDir, 'opencode.jsonc');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Resolve user config path
|
|
97
|
+
* @param {string} configPath - Config file name (default: amazingteam.config.yaml)
|
|
98
|
+
* @returns {string} Absolute path to user config
|
|
99
|
+
*/
|
|
100
|
+
resolveUserConfigPath(configPath = 'amazingteam.config.yaml') {
|
|
101
|
+
return path.join(this.projectDir, configPath);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get all memory directories that need to be created
|
|
106
|
+
* @returns {string[]} Array of memory directory paths
|
|
107
|
+
*/
|
|
108
|
+
getMemoryDirectories() {
|
|
109
|
+
const roles = ['planner', 'architect', 'developer', 'qa', 'reviewer', 'triage', 'ci-analyst'];
|
|
110
|
+
return roles.map(role => this.resolveMemoryPath(role));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Get all runtime directories that need to exist
|
|
115
|
+
* @returns {string[]} Array of directory paths
|
|
116
|
+
*/
|
|
117
|
+
getRuntimeDirectories() {
|
|
118
|
+
return [
|
|
119
|
+
...this.getMemoryDirectories(),
|
|
120
|
+
this.resolveFailuresPath(),
|
|
121
|
+
this.resolveTaskTemplatePath(),
|
|
122
|
+
path.join(this.projectDir, '.amazingteam'),
|
|
123
|
+
path.join(this.projectDir, 'tasks')
|
|
124
|
+
];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Get foundation skills directory
|
|
129
|
+
* @returns {string} Path to foundation .opencode/skills
|
|
130
|
+
*/
|
|
131
|
+
getFoundationSkillsDir() {
|
|
132
|
+
return path.join(this.foundationDir, '.opencode', 'skills');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Get foundation commands directory
|
|
137
|
+
* @returns {string} Path to foundation .amazingteam/commands
|
|
138
|
+
*/
|
|
139
|
+
getFoundationCommandsDir() {
|
|
140
|
+
return path.join(this.foundationDir, '.amazingteam', 'commands');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Get foundation agents directory
|
|
145
|
+
* @returns {string} Path to foundation .amazingteam/agents
|
|
146
|
+
*/
|
|
147
|
+
getFoundationAgentsDir() {
|
|
148
|
+
return path.join(this.foundationDir, '.amazingteam', 'agents');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Resolve template variables in a string
|
|
153
|
+
* @param {string} str - String with template variables
|
|
154
|
+
* @param {Object} vars - Variables to substitute
|
|
155
|
+
* @returns {string} Resolved string
|
|
156
|
+
*/
|
|
157
|
+
resolveTemplateVars(str, vars) {
|
|
158
|
+
let result = str;
|
|
159
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
160
|
+
const regex = new RegExp(`\\{\\{${key}\\}\\}`, 'g');
|
|
161
|
+
result = result.replace(regex, value);
|
|
162
|
+
}
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Normalize path for cross-platform compatibility
|
|
168
|
+
* @param {string} p - Path to normalize
|
|
169
|
+
* @returns {string} Normalized path
|
|
170
|
+
*/
|
|
171
|
+
static normalize(p) {
|
|
172
|
+
return path.normalize(p).replace(/\\/g, '/');
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
module.exports = PathResolver;
|