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.
Files changed (164) hide show
  1. package/.ai-team/agents/architect.md +144 -0
  2. package/.ai-team/agents/ci-analyst.md +188 -0
  3. package/.ai-team/agents/developer.md +176 -0
  4. package/.ai-team/agents/planner.md +355 -0
  5. package/.ai-team/agents/qa.md +189 -0
  6. package/.ai-team/agents/reviewer.md +211 -0
  7. package/.ai-team/agents/triage.md +146 -0
  8. package/.ai-team/commands/ci-analyze.md +116 -0
  9. package/.ai-team/commands/design.md +100 -0
  10. package/.ai-team/commands/implement.md +108 -0
  11. package/.ai-team/commands/release-check.md +142 -0
  12. package/.ai-team/commands/review.md +142 -0
  13. package/.ai-team/commands/test.md +115 -0
  14. package/.ai-team/commands/triage.md +138 -0
  15. package/.ai-team/memory/architect/architecture_notes.md +67 -0
  16. package/.ai-team/memory/architect/design_rationale.md +113 -0
  17. package/.ai-team/memory/architect/module_map.md +84 -0
  18. package/.ai-team/memory/ci-analyst/failure_patterns.md +102 -0
  19. package/.ai-team/memory/ci-analyst/runbook_references.md +87 -0
  20. package/.ai-team/memory/developer/bug_investigation.md +102 -0
  21. package/.ai-team/memory/developer/build_issues.md +115 -0
  22. package/.ai-team/memory/developer/implementation_notes.md +83 -0
  23. package/.ai-team/memory/failures/failure_library.md +103 -0
  24. package/.ai-team/memory/planner/decomposition_notes.md +82 -0
  25. package/.ai-team/memory/planner/flow_rules.md +86 -0
  26. package/.ai-team/memory/planner/github_issue_patterns.md +229 -0
  27. package/.ai-team/memory/qa/regression_cases.md +101 -0
  28. package/.ai-team/memory/qa/test_strategy.md +138 -0
  29. package/.ai-team/memory/qa/validation_notes.md +110 -0
  30. package/.ai-team/memory/reviewer/quality_rules.md +105 -0
  31. package/.ai-team/memory/reviewer/recurring_risks.md +109 -0
  32. package/.ai-team/memory/reviewer/review_notes.md +124 -0
  33. package/.ai-team/memory/triage/classification_heuristics.md +82 -0
  34. package/.ai-team/memory/triage/debug_notes.md +87 -0
  35. package/.ai-team/opencode.template.jsonc +216 -0
  36. package/.ai-team/skills/bugfix-playbook/skill.md +174 -0
  37. package/.ai-team/skills/ci-failure-analysis/skill.md +176 -0
  38. package/.ai-team/skills/issue-triage/skill.md +163 -0
  39. package/.ai-team/skills/regression-checklist/skill.md +176 -0
  40. package/.ai-team/skills/release-readiness-check/skill.md +216 -0
  41. package/.ai-team/skills/repo-architecture-reader/skill.md +139 -0
  42. package/.ai-team/skills/safe-refactor-checklist/skill.md +215 -0
  43. package/.ai-team/skills/task-breakdown-and-dispatch/skill.md +151 -0
  44. package/.ai-team/skills/test-first-feature-dev/skill.md +205 -0
  45. package/.ai-team/workflows/ci.yml +81 -0
  46. package/.ai-team/workflows/nightly-ai-maintenance.yml +129 -0
  47. package/.ai-team/workflows/opencode.yml +33 -0
  48. package/.ai-team/workflows/pr-check.yml +41 -0
  49. package/.foundation/foundation.lock +38 -0
  50. package/.foundation/local-overrides.md +97 -0
  51. package/.foundation/upgrade-history.md +38 -0
  52. package/.opencode/agents/architect.md +38 -0
  53. package/.opencode/agents/ci-analyst.md +38 -0
  54. package/.opencode/agents/developer.md +43 -0
  55. package/.opencode/agents/planner.md +47 -0
  56. package/.opencode/agents/qa.md +34 -0
  57. package/.opencode/agents/reviewer.md +38 -0
  58. package/.opencode/agents/triage.md +37 -0
  59. package/.opencode/commands/auto.md +264 -0
  60. package/.opencode/commands/breakdown-issue.md +94 -0
  61. package/.opencode/commands/ci-analyze.md +15 -0
  62. package/.opencode/commands/close-parent-task.md +122 -0
  63. package/.opencode/commands/design.md +15 -0
  64. package/.opencode/commands/dispatch-next.md +102 -0
  65. package/.opencode/commands/implement.md +16 -0
  66. package/.opencode/commands/release-check.md +16 -0
  67. package/.opencode/commands/resume.md +88 -0
  68. package/.opencode/commands/review.md +15 -0
  69. package/.opencode/commands/show-blockers.md +97 -0
  70. package/.opencode/commands/summarize-parent.md +121 -0
  71. package/.opencode/commands/test.md +15 -0
  72. package/.opencode/commands/triage.md +109 -0
  73. package/.opencode/skills/bugfix-playbook/SKILL.md +81 -0
  74. package/.opencode/skills/ci-failure-analysis/SKILL.md +94 -0
  75. package/.opencode/skills/issue-triage/SKILL.md +80 -0
  76. package/.opencode/skills/regression-checklist/SKILL.md +81 -0
  77. package/.opencode/skills/release-readiness-check/SKILL.md +81 -0
  78. package/.opencode/skills/repo-architecture-reader/SKILL.md +65 -0
  79. package/.opencode/skills/safe-refactor-checklist/SKILL.md +76 -0
  80. package/.opencode/skills/task-breakdown-and-dispatch/SKILL.md +255 -0
  81. package/.opencode/skills/test-first-feature-dev/SKILL.md +78 -0
  82. package/AGENTS.md +879 -0
  83. package/CHANGELOG.md +261 -0
  84. package/LICENSE +21 -0
  85. package/README.md +1215 -0
  86. package/VERSION +1 -0
  87. package/action/__tests__/downloader.test.js +251 -0
  88. package/action/__tests__/merger.test.js +156 -0
  89. package/action/__tests__/path-resolver.test.js +199 -0
  90. package/action/__tests__/validator.test.js +310 -0
  91. package/action/action.yml +61 -0
  92. package/action/index.js +223 -0
  93. package/action/lib/downloader.js +344 -0
  94. package/action/lib/merger.js +170 -0
  95. package/action/lib/path-resolver.js +176 -0
  96. package/action/lib/setup.js +286 -0
  97. package/action/lib/validator.js +324 -0
  98. package/cli/__tests__/cli.test.js +270 -0
  99. package/cli/amazingteam.cjs +225 -0
  100. package/cli/commands/check-update.cjs +159 -0
  101. package/cli/commands/init.cjs +412 -0
  102. package/cli/commands/local.cjs +264 -0
  103. package/cli/commands/migrate.cjs +316 -0
  104. package/cli/commands/status.cjs +241 -0
  105. package/cli/commands/upgrade.cjs +213 -0
  106. package/cli/commands/validate.cjs +259 -0
  107. package/cli/commands/version.cjs +59 -0
  108. package/cli/sync.cjs +237 -0
  109. package/dist/index.js +35 -0
  110. package/docs/architecture/overview.md +138 -0
  111. package/docs/blocker_resolution_design.md +372 -0
  112. package/docs/bootstrap-model.md +356 -0
  113. package/docs/config-reference.md +458 -0
  114. package/docs/how-to-use.md +178 -0
  115. package/docs/migration-to-v3.md +355 -0
  116. package/docs/overlay-guide.md +156 -0
  117. package/docs/patterns/README.md +67 -0
  118. package/docs/quick-start-v3.md +330 -0
  119. package/docs/releases/README.md +64 -0
  120. package/docs/runbooks/ci/README.md +62 -0
  121. package/docs/runbooks/ci/build-debug.md +120 -0
  122. package/docs/runbooks/ci/flaky-tests.md +127 -0
  123. package/docs/runbooks/getting-started.md +81 -0
  124. package/docs/upgrade-policy.md +188 -0
  125. package/docs/versioning.md +199 -0
  126. package/overlays/README.md +30 -0
  127. package/overlays/ai-agent-product/.ai-team/skills/llm-integration/skill.md +99 -0
  128. package/overlays/ai-agent-product/docs/ai-agent-architecture.md +68 -0
  129. package/overlays/ai-agent-product/overlay.yaml +26 -0
  130. package/overlays/cpp-qt-desktop/.ai-team/skills/qt-signals-slots/skill.md +60 -0
  131. package/overlays/cpp-qt-desktop/docs/qt-conventions.md +64 -0
  132. package/overlays/cpp-qt-desktop/overlay.yaml +22 -0
  133. package/overlays/python-backend/.ai-team/skills/python-testing/skill.md +90 -0
  134. package/overlays/python-backend/docs/python-style.md +78 -0
  135. package/overlays/python-backend/overlay.yaml +22 -0
  136. package/overlays/web-fullstack/.ai-team/skills/frontend-testing/skill.md +70 -0
  137. package/overlays/web-fullstack/docs/frontend-conventions.md +68 -0
  138. package/overlays/web-fullstack/overlay.yaml +26 -0
  139. package/package.json +84 -0
  140. package/presets/default.yaml +161 -0
  141. package/presets/go.yaml +43 -0
  142. package/presets/python.yaml +43 -0
  143. package/presets/typescript.yaml +40 -0
  144. package/schemas/config.schema.json +239 -0
  145. package/scripts/diff_foundation_vs_project.sh +134 -0
  146. package/scripts/generate_docs.sh +200 -0
  147. package/scripts/init_project.sh +455 -0
  148. package/scripts/plan_upgrade.sh +268 -0
  149. package/scripts/upgrade_foundation.sh +365 -0
  150. package/scripts/validate-foundation.cjs +278 -0
  151. package/scripts/validate_foundation.sh +192 -0
  152. package/scripts/validate_project_setup.sh +171 -0
  153. package/tasks/README.md +94 -0
  154. package/tasks/_template/analysis.md +76 -0
  155. package/tasks/_template/design.md +121 -0
  156. package/tasks/_template/implementation.md +121 -0
  157. package/tasks/_template/release.md +119 -0
  158. package/tasks/_template/review.md +131 -0
  159. package/tasks/_template/subtasks/task.yaml +24 -0
  160. package/tasks/_template/task.yaml +75 -0
  161. package/tasks/_template/validation.md +128 -0
  162. package/templates/amazingteam.yml +81 -0
  163. package/templates/gitignore +14 -0
  164. 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;