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,286 @@
1
+ /**
2
+ * Runtime Setup Module
3
+ * Initializes runtime directories and generates opencode.jsonc
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const PathResolver = require('./path-resolver');
9
+
10
+ /**
11
+ * Initialize runtime directories
12
+ * @param {string} projectDir - Project directory
13
+ * @returns {string[]} Created directories
14
+ */
15
+ function initializeRuntimeDirectories(projectDir) {
16
+ const resolver = new PathResolver(null, projectDir);
17
+ const created = [];
18
+
19
+ const dirs = [
20
+ '.amazingteam',
21
+ '.amazingteam/memory',
22
+ '.amazingteam/memory/planner',
23
+ '.amazingteam/memory/architect',
24
+ '.amazingteam/memory/developer',
25
+ '.amazingteam/memory/qa',
26
+ '.amazingteam/memory/reviewer',
27
+ '.amazingteam/memory/triage',
28
+ '.amazingteam/memory/ci-analyst',
29
+ '.amazingteam/memory/failures',
30
+ 'tasks',
31
+ 'tasks/_template'
32
+ ];
33
+
34
+ for (const dir of dirs) {
35
+ const dirPath = path.join(projectDir, dir);
36
+ if (!fs.existsSync(dirPath)) {
37
+ fs.mkdirSync(dirPath, { recursive: true });
38
+ created.push(dir);
39
+ }
40
+ }
41
+
42
+ // Create memory placeholder files if they don't exist
43
+ const memoryFiles = {
44
+ 'planner': ['decomposition_notes.md', 'flow_rules.md', 'github_issue_patterns.md'],
45
+ 'architect': ['architecture_notes.md', 'module_map.md', 'design_rationale.md'],
46
+ 'developer': ['implementation_notes.md', 'bug_investigation.md', 'build_issues.md'],
47
+ 'qa': ['test_strategy.md', 'regression_cases.md', 'validation_notes.md'],
48
+ 'reviewer': ['review_notes.md', 'quality_rules.md', 'recurring_risks.md'],
49
+ 'triage': ['classification_heuristics.md', 'debug_notes.md'],
50
+ 'ci-analyst': ['failure_patterns.md', 'runbook_references.md']
51
+ };
52
+
53
+ for (const [role, files] of Object.entries(memoryFiles)) {
54
+ for (const file of files) {
55
+ const filePath = path.join(projectDir, '.amazingteam', 'memory', role, file);
56
+ if (!fs.existsSync(filePath)) {
57
+ fs.writeFileSync(filePath, `# ${role} ${file.replace('.md', '').replace(/_/g, ' ')}\n\n`);
58
+ }
59
+ }
60
+ }
61
+
62
+ // Create failure library placeholder
63
+ const failuresPath = path.join(projectDir, '.amazingteam', 'memory', 'failures', 'failure_library.md');
64
+ if (!fs.existsSync(failuresPath)) {
65
+ fs.writeFileSync(failuresPath, `# Failure Library\n\nShared failure patterns documented by CI Analyst.\n\n## Categories\n\n### Build Failures\n\n### Test Failures\n\n### Infrastructure Issues\n\n`);
66
+ }
67
+
68
+ return created;
69
+ }
70
+
71
+ /**
72
+ * Generate opencode.jsonc from template
73
+ * @param {string} foundationDir - Foundation directory
74
+ * @param {string} projectDir - Project directory
75
+ * @param {Object} config - User configuration
76
+ * @returns {string} Path to generated opencode.jsonc
77
+ */
78
+ function generateOpenCodeConfig(foundationDir, projectDir, config) {
79
+ const resolver = new PathResolver(foundationDir, projectDir);
80
+ const templatePath = path.join(foundationDir, 'templates', 'opencode.jsonc');
81
+
82
+ let template;
83
+ if (fs.existsSync(templatePath)) {
84
+ template = fs.readFileSync(templatePath, 'utf-8');
85
+ } else {
86
+ // Use default template
87
+ template = getDefaultOpenCodeTemplate();
88
+ }
89
+
90
+ // Replace template variables
91
+ const vars = {
92
+ AI_TEAM_VERSION: config.ai_team?.version || '3.0.0',
93
+ PROJECT_NAME: config.project?.name || 'my-project',
94
+ PROJECT_DESCRIPTION: config.project?.description || '',
95
+ LANGUAGE: config.project?.language || 'typescript',
96
+ FRAMEWORK: config.project?.framework || 'node',
97
+ FOUNDATION_PATH: foundationDir
98
+ };
99
+
100
+ let content = resolver.resolveTemplateVars(template, vars);
101
+
102
+ // Write to project
103
+ const outputPath = resolver.resolveOpenCodeConfigPath();
104
+ fs.writeFileSync(outputPath, content);
105
+
106
+ return outputPath;
107
+ }
108
+
109
+ /**
110
+ * Get default OpenCode template
111
+ * @returns {string} Default template content
112
+ */
113
+ function getDefaultOpenCodeTemplate() {
114
+ return `{
115
+ "$schema": "https://opencode.ai/schema.json",
116
+ "version": "{{AI_TEAM_VERSION}}",
117
+ "project": {
118
+ "name": "{{PROJECT_NAME}}",
119
+ "description": "{{PROJECT_DESCRIPTION}}",
120
+ "language": "{{LANGUAGE}}",
121
+ "framework": "{{FRAMEWORK}}"
122
+ },
123
+ "instructions": ["AGENTS.md"],
124
+ "autoupdate": true,
125
+ "permission": {
126
+ "edit": "ask",
127
+ "bash": "ask"
128
+ },
129
+ "tools": {
130
+ "write": true,
131
+ "edit": true,
132
+ "bash": true
133
+ }
134
+ }`;
135
+ }
136
+
137
+ /**
138
+ * Copy task templates from foundation
139
+ * @param {string} foundationDir - Foundation directory
140
+ * @param {string} projectDir - Project directory
141
+ * @returns {string[]} Copied files
142
+ */
143
+ function copyTaskTemplates(foundationDir, projectDir) {
144
+ const templateDir = path.join(foundationDir, 'tasks', '_template');
145
+ const targetDir = path.join(projectDir, 'tasks', '_template');
146
+
147
+ const copied = [];
148
+
149
+ if (fs.existsSync(templateDir)) {
150
+ const files = fs.readdirSync(templateDir);
151
+
152
+ for (const file of files) {
153
+ const srcPath = path.join(templateDir, file);
154
+ const destPath = path.join(targetDir, file);
155
+
156
+ if (fs.statSync(srcPath).isFile()) {
157
+ fs.copyFileSync(srcPath, destPath);
158
+ copied.push(file);
159
+ }
160
+ }
161
+ }
162
+
163
+ return copied;
164
+ }
165
+
166
+ /**
167
+ * Update .gitignore
168
+ * @param {string} projectDir - Project directory
169
+ * @returns {boolean} Whether .gitignore was updated
170
+ */
171
+ function updateGitignore(projectDir) {
172
+ const gitignorePath = path.join(projectDir, '.gitignore');
173
+ const additions = [
174
+ '# AmazingTeam local foundation',
175
+ '.amazingteam-local/',
176
+ '',
177
+ '# AmazingTeam cache',
178
+ '.amazingteam-cache/'
179
+ ];
180
+
181
+ let content = '';
182
+ if (fs.existsSync(gitignorePath)) {
183
+ content = fs.readFileSync(gitignorePath, 'utf-8');
184
+
185
+ // Check if already has AmazingTeam entries
186
+ if (content.includes('.amazingteam-local/')) {
187
+ return false;
188
+ }
189
+ }
190
+
191
+ // Append additions
192
+ const newContent = content + '\n\n' + additions.join('\n') + '\n';
193
+ fs.writeFileSync(gitignorePath, newContent);
194
+
195
+ return true;
196
+ }
197
+
198
+ /**
199
+ * Full setup process
200
+ * @param {string} foundationDir - Foundation directory
201
+ * @param {string} projectDir - Project directory
202
+ * @param {Object} config - User configuration
203
+ * @returns {Object} Setup result
204
+ */
205
+ function setup(foundationDir, projectDir, config) {
206
+ console.log('Setting up AmazingTeam...');
207
+
208
+ // 1. Initialize runtime directories
209
+ console.log('Creating runtime directories...');
210
+ const createdDirs = initializeRuntimeDirectories(projectDir);
211
+ console.log(`Created ${createdDirs.length} directories`);
212
+
213
+ // 2. Copy task templates
214
+ console.log('Copying task templates...');
215
+ const copiedTemplates = copyTaskTemplates(foundationDir, projectDir);
216
+ console.log(`Copied ${copiedTemplates.length} templates`);
217
+
218
+ // 3. Generate opencode.jsonc
219
+ console.log('Generating opencode.jsonc...');
220
+ const opencodePath = generateOpenCodeConfig(foundationDir, projectDir, config);
221
+ console.log(`Generated: ${opencodePath}`);
222
+
223
+ // 4. Update .gitignore
224
+ console.log('Updating .gitignore...');
225
+ const gitignoreUpdated = updateGitignore(projectDir);
226
+ if (gitignoreUpdated) {
227
+ console.log('Updated .gitignore');
228
+ } else {
229
+ console.log('.gitignore already up to date');
230
+ }
231
+
232
+ return {
233
+ createdDirectories: createdDirs,
234
+ copiedTemplates,
235
+ opencodePath,
236
+ gitignoreUpdated
237
+ };
238
+ }
239
+
240
+ /**
241
+ * Verify setup
242
+ * @param {string} projectDir - Project directory
243
+ * @returns {{ valid: boolean, issues: string[] }}
244
+ */
245
+ function verifySetup(projectDir) {
246
+ const issues = [];
247
+
248
+ // Check required directories
249
+ const requiredDirs = [
250
+ '.amazingteam',
251
+ '.amazingteam/memory',
252
+ 'tasks'
253
+ ];
254
+
255
+ for (const dir of requiredDirs) {
256
+ if (!fs.existsSync(path.join(projectDir, dir))) {
257
+ issues.push(`Missing directory: ${dir}`);
258
+ }
259
+ }
260
+
261
+ // Check required files
262
+ const requiredFiles = [
263
+ 'amazingteam.config.yaml',
264
+ 'opencode.jsonc'
265
+ ];
266
+
267
+ for (const file of requiredFiles) {
268
+ if (!fs.existsSync(path.join(projectDir, file))) {
269
+ issues.push(`Missing file: ${file}`);
270
+ }
271
+ }
272
+
273
+ return {
274
+ valid: issues.length === 0,
275
+ issues
276
+ };
277
+ }
278
+
279
+ module.exports = {
280
+ initializeRuntimeDirectories,
281
+ generateOpenCodeConfig,
282
+ copyTaskTemplates,
283
+ updateGitignore,
284
+ setup,
285
+ verifySetup
286
+ };
@@ -0,0 +1,324 @@
1
+ /**
2
+ * Configuration Validator Module
3
+ * Validates user configuration against schema
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+ // Minimal JSON Schema validator (no external dependencies)
10
+ // For production, consider using ajv
11
+
12
+ /**
13
+ * Validate a value against a schema
14
+ * @param {*} value - Value to validate
15
+ * @param {Object} schema - JSON Schema
16
+ * @param {string} path - Current path (for error messages)
17
+ * @returns {{ valid: boolean, errors: string[] }}
18
+ */
19
+ function validateAgainstSchema(value, schema, path = 'root') {
20
+ const errors = [];
21
+
22
+ if (!schema) {
23
+ return { valid: true, errors: [] };
24
+ }
25
+
26
+ // Type validation
27
+ if (schema.type) {
28
+ const actualType = Array.isArray(value) ? 'array' : typeof value;
29
+ if (schema.type === 'integer') {
30
+ if (typeof value !== 'number' || !Number.isInteger(value)) {
31
+ errors.push(`${path}: expected type ${schema.type}, got ${actualType}`);
32
+ }
33
+ } else if (actualType !== schema.type && !(schema.type === 'object' && value === null)) {
34
+ if (schema.nullable !== true || value !== null) {
35
+ errors.push(`${path}: expected type ${schema.type}, got ${actualType}`);
36
+ }
37
+ }
38
+ }
39
+
40
+ // Enum validation
41
+ if (schema.enum && !schema.enum.includes(value)) {
42
+ errors.push(`${path}: value must be one of [${schema.enum.join(', ')}], got ${value}`);
43
+ }
44
+
45
+ // String validations
46
+ if (typeof value === 'string') {
47
+ if (schema.minLength !== undefined && value.length < schema.minLength) {
48
+ errors.push(`${path}: string length ${value.length} is less than minLength ${schema.minLength}`);
49
+ }
50
+ if (schema.maxLength !== undefined && value.length > schema.maxLength) {
51
+ errors.push(`${path}: string length ${value.length} exceeds maxLength ${schema.maxLength}`);
52
+ }
53
+ if (schema.pattern) {
54
+ const regex = new RegExp(schema.pattern);
55
+ if (!regex.test(value)) {
56
+ errors.push(`${path}: string does not match pattern ${schema.pattern}`);
57
+ }
58
+ }
59
+ }
60
+
61
+ // Number validations
62
+ if (typeof value === 'number') {
63
+ if (schema.minimum !== undefined && value < schema.minimum) {
64
+ errors.push(`${path}: value ${value} is less than minimum ${schema.minimum}`);
65
+ }
66
+ if (schema.maximum !== undefined && value > schema.maximum) {
67
+ errors.push(`${path}: value ${value} exceeds maximum ${schema.maximum}`);
68
+ }
69
+ }
70
+
71
+ // Array validations
72
+ if (Array.isArray(value) && schema.items) {
73
+ value.forEach((item, index) => {
74
+ const itemResult = validateAgainstSchema(item, schema.items, `${path}[${index}]`);
75
+ errors.push(...itemResult.errors);
76
+ });
77
+
78
+ if (schema.minItems !== undefined && value.length < schema.minItems) {
79
+ errors.push(`${path}: array length ${value.length} is less than minItems ${schema.minItems}`);
80
+ }
81
+ if (schema.maxItems !== undefined && value.length > schema.maxItems) {
82
+ errors.push(`${path}: array length ${value.length} exceeds maxItems ${schema.maxItems}`);
83
+ }
84
+ }
85
+
86
+ // Object validations
87
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
88
+ // Required properties
89
+ if (schema.required) {
90
+ for (const reqProp of schema.required) {
91
+ if (!(reqProp in value)) {
92
+ errors.push(`${path}: missing required property '${reqProp}'`);
93
+ }
94
+ }
95
+ }
96
+
97
+ // Property validations
98
+ if (schema.properties) {
99
+ for (const [propName, propSchema] of Object.entries(schema.properties)) {
100
+ if (propName in value) {
101
+ const propResult = validateAgainstSchema(value[propName], propSchema, `${path}.${propName}`);
102
+ errors.push(...propResult.errors);
103
+ }
104
+ }
105
+ }
106
+
107
+ // Additional properties
108
+ if (schema.additionalProperties === false) {
109
+ const allowedProps = new Set([
110
+ ...Object.keys(schema.properties || {}),
111
+ ...(schema.required || [])
112
+ ]);
113
+ for (const propName of Object.keys(value)) {
114
+ if (!allowedProps.has(propName) && !propName.startsWith('$')) {
115
+ errors.push(`${path}: unknown property '${propName}'`);
116
+ }
117
+ }
118
+ }
119
+ }
120
+
121
+ return {
122
+ valid: errors.length === 0,
123
+ errors
124
+ };
125
+ }
126
+
127
+ /**
128
+ * Load and parse JSON Schema
129
+ * @param {string} schemaPath - Path to schema file
130
+ * @returns {Object} Parsed schema
131
+ */
132
+ function loadSchema(schemaPath) {
133
+ const schemaContent = fs.readFileSync(schemaPath, 'utf-8');
134
+ return JSON.parse(schemaContent);
135
+ }
136
+
137
+ /**
138
+ * Validate user configuration
139
+ * @param {Object} config - User configuration object
140
+ * @param {Object} schema - JSON Schema object
141
+ * @returns {{ valid: boolean, errors: string[] }}
142
+ */
143
+ function validateConfig(config, schema) {
144
+ return validateAgainstSchema(config, schema);
145
+ }
146
+
147
+ /**
148
+ * Validate required files exist in project
149
+ * @param {string} projectDir - Project directory
150
+ * @param {string[]} requiredFiles - List of required files
151
+ * @returns {{ valid: boolean, missing: string[] }}
152
+ */
153
+ function validateRequiredFiles(projectDir, requiredFiles) {
154
+ const missing = [];
155
+
156
+ for (const file of requiredFiles) {
157
+ const filePath = path.join(projectDir, file);
158
+ if (!fs.existsSync(filePath)) {
159
+ missing.push(file);
160
+ }
161
+ }
162
+
163
+ return {
164
+ valid: missing.length === 0,
165
+ missing
166
+ };
167
+ }
168
+
169
+ /**
170
+ * Validate project structure
171
+ * @param {string} projectDir - Project directory
172
+ * @returns {{ valid: boolean, issues: string[] }}
173
+ */
174
+ function validateProjectStructure(projectDir) {
175
+ const issues = [];
176
+
177
+ // Check required directories
178
+ const requiredDirs = [
179
+ '.amazingteam',
180
+ '.amazingteam/memory',
181
+ 'tasks'
182
+ ];
183
+
184
+ for (const dir of requiredDirs) {
185
+ const dirPath = path.join(projectDir, dir);
186
+ if (!fs.existsSync(dirPath)) {
187
+ issues.push(`Missing directory: ${dir}`);
188
+ }
189
+ }
190
+
191
+ // Check required files
192
+ const requiredFiles = [
193
+ 'amazingteam.config.yaml'
194
+ ];
195
+
196
+ const fileResult = validateRequiredFiles(projectDir, requiredFiles);
197
+ issues.push(...fileResult.missing.map(f => `Missing file: ${f}`));
198
+
199
+ // Check opencode.jsonc exists
200
+ const opencodePath = path.join(projectDir, 'opencode.jsonc');
201
+ if (!fs.existsSync(opencodePath)) {
202
+ issues.push('Missing opencode.jsonc (run `amazingteam local` to generate)');
203
+ }
204
+
205
+ return {
206
+ valid: issues.length === 0,
207
+ issues
208
+ };
209
+ }
210
+
211
+ /**
212
+ * Validate configuration file
213
+ * @param {string} configPath - Path to config file
214
+ * @param {string} schemaPath - Path to schema file
215
+ * @returns {{ valid: boolean, errors: string[], warnings: string[] }}
216
+ */
217
+ function validateConfigFile(configPath, schemaPath) {
218
+ const errors = [];
219
+ const warnings = [];
220
+
221
+ // Check files exist
222
+ if (!fs.existsSync(configPath)) {
223
+ errors.push(`Config file not found: ${configPath}`);
224
+ return { valid: false, errors, warnings };
225
+ }
226
+
227
+ if (!fs.existsSync(schemaPath)) {
228
+ warnings.push(`Schema file not found: ${schemaPath}, skipping schema validation`);
229
+ return { valid: true, errors, warnings };
230
+ }
231
+
232
+ // Load and parse config
233
+ let config;
234
+ try {
235
+ const configContent = fs.readFileSync(configPath, 'utf-8');
236
+ config = parseYaml(configContent);
237
+ } catch (err) {
238
+ errors.push(`Failed to parse config: ${err.message}`);
239
+ return { valid: false, errors, warnings };
240
+ }
241
+
242
+ // Load schema
243
+ let schema;
244
+ try {
245
+ schema = loadSchema(schemaPath);
246
+ } catch (err) {
247
+ errors.push(`Failed to load schema: ${err.message}`);
248
+ return { valid: false, errors, warnings };
249
+ }
250
+
251
+ // Validate against schema
252
+ const result = validateConfig(config, schema);
253
+ errors.push(...result.errors);
254
+
255
+ return {
256
+ valid: errors.length === 0,
257
+ errors,
258
+ warnings
259
+ };
260
+ }
261
+
262
+ /**
263
+ * Simple YAML parser (handles basic YAML)
264
+ * For production, use a proper YAML library
265
+ * @param {string} content - YAML content
266
+ * @returns {Object} Parsed object
267
+ */
268
+ function parseYaml(content) {
269
+ // This is a minimal YAML parser for simple configs
270
+ // For production, use 'js-yaml' or similar
271
+ const lines = content.split('\n');
272
+ const result = {};
273
+ let currentPath = [];
274
+ let currentObj = result;
275
+
276
+ for (const line of lines) {
277
+ // Skip comments and empty lines
278
+ if (line.trim().startsWith('#') || line.trim() === '') continue;
279
+
280
+ const indent = line.search(/\S/);
281
+ const level = Math.floor(indent / 2);
282
+ const trimmed = line.trim();
283
+
284
+ // Handle key-value pairs
285
+ const colonIndex = trimmed.indexOf(':');
286
+ if (colonIndex > 0) {
287
+ const key = trimmed.substring(0, colonIndex).trim();
288
+ let value = trimmed.substring(colonIndex + 1).trim();
289
+
290
+ // Parse value
291
+ if (value === '') {
292
+ // Nested object
293
+ currentObj[key] = {};
294
+ } else if (value.startsWith('[') && value.endsWith(']')) {
295
+ // Array
296
+ currentObj[key] = value.slice(1, -1).split(',').map(s => s.trim().replace(/"/g, ''));
297
+ } else if (value.startsWith('"') && value.endsWith('"')) {
298
+ // String
299
+ currentObj[key] = value.slice(1, -1);
300
+ } else if (value === 'true' || value === 'false') {
301
+ // Boolean
302
+ currentObj[key] = value === 'true';
303
+ } else if (!isNaN(Number(value))) {
304
+ // Number
305
+ currentObj[key] = Number(value);
306
+ } else if (value === 'null') {
307
+ currentObj[key] = null;
308
+ } else {
309
+ currentObj[key] = value;
310
+ }
311
+ }
312
+ }
313
+
314
+ return result;
315
+ }
316
+
317
+ module.exports = {
318
+ validateAgainstSchema,
319
+ validateConfig,
320
+ validateConfigFile,
321
+ validateRequiredFiles,
322
+ validateProjectStructure,
323
+ loadSchema
324
+ };