aios-core 2.1.4 → 2.1.6

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 (27) hide show
  1. package/.aios-core/development/tasks/analyze-brownfield.md +456 -456
  2. package/.aios-core/development/tasks/setup-project-docs.md +440 -444
  3. package/.aios-core/infrastructure/scripts/documentation-integrity/brownfield-analyzer.js +501 -501
  4. package/.aios-core/infrastructure/scripts/documentation-integrity/config-generator.js +368 -329
  5. package/.aios-core/infrastructure/scripts/documentation-integrity/deployment-config-loader.js +308 -282
  6. package/.aios-core/infrastructure/scripts/documentation-integrity/doc-generator.js +331 -331
  7. package/.aios-core/infrastructure/scripts/documentation-integrity/gitignore-generator.js +312 -312
  8. package/.aios-core/infrastructure/scripts/documentation-integrity/index.js +74 -74
  9. package/.aios-core/infrastructure/scripts/documentation-integrity/mode-detector.js +389 -358
  10. package/.aios-core/infrastructure/scripts/llm-routing/install-llm-routing.js +6 -6
  11. package/.aios-core/infrastructure/templates/core-config/core-config-brownfield.tmpl.yaml +176 -182
  12. package/.aios-core/infrastructure/templates/core-config/core-config-greenfield.tmpl.yaml +127 -127
  13. package/.aios-core/infrastructure/templates/project-docs/coding-standards-tmpl.md +346 -346
  14. package/.aios-core/infrastructure/templates/project-docs/source-tree-tmpl.md +177 -177
  15. package/.aios-core/infrastructure/templates/project-docs/tech-stack-tmpl.md +267 -267
  16. package/package.json +1 -1
  17. package/packages/installer/src/config/templates/env-template.js +2 -2
  18. package/packages/installer/src/wizard/wizard.js +1 -1
  19. package/packages/installer/tests/integration/environment-configuration.test.js +2 -1
  20. package/packages/installer/tests/unit/env-template.test.js +3 -2
  21. package/src/wizard/index.js +2 -2
  22. package/.aios-core/development/tasks/validate-structure.md +0 -243
  23. package/.aios-core/infrastructure/scripts/source-tree-guardian/index.js +0 -375
  24. package/.aios-core/infrastructure/scripts/source-tree-guardian/manifest-generator.js +0 -410
  25. package/.aios-core/infrastructure/scripts/source-tree-guardian/rules/naming-rules.yaml +0 -285
  26. package/.aios-core/infrastructure/scripts/source-tree-guardian/rules/placement-rules.yaml +0 -262
  27. package/.aios-core/infrastructure/scripts/source-tree-guardian/validator.js +0 -468
@@ -1,329 +1,368 @@
1
- /**
2
- * Config Generator Module
3
- *
4
- * Generates project-specific core-config.yaml from templates.
5
- * Supports greenfield and brownfield modes with deployment configuration.
6
- *
7
- * @module documentation-integrity/config-generator
8
- * @version 1.0.0
9
- * @story 6.9
10
- */
11
-
12
- const fs = require('fs');
13
- const path = require('path');
14
- const yaml = require('yaml');
15
-
16
- // Template directory
17
- const TEMPLATES_DIR = path.join(__dirname, '..', '..', 'templates', 'core-config');
18
-
19
- /**
20
- * Template file names
21
- * @enum {string}
22
- */
23
- const ConfigTemplates = {
24
- GREENFIELD: 'core-config-greenfield.tmpl.yaml',
25
- BROWNFIELD: 'core-config-brownfield.tmpl.yaml',
26
- };
27
-
28
- /**
29
- * Deployment workflow types
30
- * @enum {string}
31
- */
32
- const DeploymentWorkflow = {
33
- STAGING_FIRST: 'staging-first',
34
- DIRECT_TO_MAIN: 'direct-to-main',
35
- };
36
-
37
- /**
38
- * Deployment platform options
39
- * @enum {string}
40
- */
41
- const DeploymentPlatform = {
42
- RAILWAY: 'Railway',
43
- VERCEL: 'Vercel',
44
- AWS: 'AWS',
45
- DOCKER: 'Docker',
46
- NONE: 'None',
47
- };
48
-
49
- /**
50
- * Default deployment configuration
51
- * @type {Object}
52
- */
53
- const DEFAULT_DEPLOYMENT_CONFIG = {
54
- workflow: DeploymentWorkflow.STAGING_FIRST,
55
- stagingBranch: 'staging',
56
- productionBranch: 'main',
57
- defaultTarget: 'staging',
58
- stagingEnvName: 'Staging',
59
- productionEnvName: 'Production',
60
- platform: DeploymentPlatform.NONE,
61
- qualityGates: {
62
- lint: true,
63
- typecheck: true,
64
- tests: true,
65
- security: false,
66
- minCoverage: 50,
67
- },
68
- };
69
-
70
- /**
71
- * Builds config context from project info and deployment settings
72
- *
73
- * @param {string} projectName - Project name
74
- * @param {string} mode - Installation mode (greenfield/brownfield)
75
- * @param {Object} deploymentConfig - Deployment configuration
76
- * @param {Object} [analysisResults] - Brownfield analysis results (if applicable)
77
- * @returns {Object} Config context for template rendering
78
- */
79
- function buildConfigContext(projectName, mode, deploymentConfig = {}, analysisResults = {}) {
80
- const config = { ...DEFAULT_DEPLOYMENT_CONFIG, ...deploymentConfig };
81
- const isStaging = config.workflow === DeploymentWorkflow.STAGING_FIRST;
82
-
83
- const context = {
84
- // Basic info
85
- PROJECT_NAME: projectName,
86
- GENERATED_DATE: new Date().toISOString().split('T')[0],
87
- PROJECT_VERSION: analysisResults.version || '0.1.0',
88
-
89
- // Deployment workflow
90
- DEPLOYMENT_WORKFLOW: config.workflow,
91
-
92
- // Branch configuration
93
- STAGING_BRANCH: isStaging ? config.stagingBranch : 'null',
94
- PRODUCTION_BRANCH: config.productionBranch,
95
- DEFAULT_TARGET: isStaging ? config.stagingBranch : config.productionBranch,
96
-
97
- // Environment names
98
- STAGING_ENV_NAME: config.stagingEnvName,
99
- PRODUCTION_ENV_NAME: config.productionEnvName,
100
-
101
- // Platform
102
- DEPLOYMENT_PLATFORM: config.platform,
103
-
104
- // Quality gates
105
- QUALITY_LINT: config.qualityGates.lint,
106
- QUALITY_TYPECHECK: config.qualityGates.typecheck,
107
- QUALITY_TESTS: config.qualityGates.tests,
108
- QUALITY_SECURITY: config.qualityGates.security || false,
109
- MIN_COVERAGE: config.qualityGates.minCoverage || 50,
110
-
111
- // Brownfield specific (defaults for greenfield)
112
- HAS_EXISTING_STRUCTURE: analysisResults.hasExistingStructure || false,
113
- HAS_EXISTING_WORKFLOWS: analysisResults.hasExistingWorkflows || false,
114
- HAS_EXISTING_STANDARDS: analysisResults.hasExistingStandards || false,
115
- MERGE_STRATEGY: analysisResults.mergeStrategy || 'parallel',
116
-
117
- // Detected configs (brownfield)
118
- DETECTED_TECH_STACK: JSON.stringify(analysisResults.techStack || []),
119
- DETECTED_FRAMEWORKS: JSON.stringify(analysisResults.frameworks || []),
120
- DETECTED_LINTING: analysisResults.linting || 'none',
121
- DETECTED_FORMATTING: analysisResults.formatting || 'none',
122
- DETECTED_TESTING: analysisResults.testing || 'none',
123
-
124
- // Auto deploy settings
125
- STAGING_AUTO_DEPLOY: config.stagingAutoDeploy !== false,
126
- PRODUCTION_AUTO_DEPLOY: config.productionAutoDeploy !== false,
127
-
128
- // PR settings
129
- AUTO_ASSIGN_REVIEWERS: config.autoAssignReviewers || false,
130
- DRAFT_BY_DEFAULT: config.draftByDefault || false,
131
-
132
- // Existing config paths (brownfield)
133
- ESLINT_CONFIG_PATH: analysisResults.eslintPath || 'null',
134
- PRETTIER_CONFIG_PATH: analysisResults.prettierPath || 'null',
135
- TSCONFIG_PATH: analysisResults.tsconfigPath || 'null',
136
- FLAKE8_CONFIG_PATH: analysisResults.flake8Path || 'null',
137
- GITHUB_WORKFLOWS_PATH: analysisResults.githubWorkflowsPath || 'null',
138
- GITLAB_CI_PATH: analysisResults.gitlabCiPath || 'null',
139
- PACKAGE_JSON_PATH: analysisResults.packageJsonPath || 'null',
140
- REQUIREMENTS_PATH: analysisResults.requirementsPath || 'null',
141
- GO_MOD_PATH: analysisResults.goModPath || 'null',
142
-
143
- // Merge settings
144
- MERGE_WORKFLOWS: analysisResults.mergeWorkflows || false,
145
-
146
- // Migration notes (brownfield)
147
- MIGRATION_SUMMARY: analysisResults.summary || 'No analysis performed',
148
- MANUAL_REVIEW_ITEMS: analysisResults.manualReviewItems || [],
149
- CONFLICTS: analysisResults.conflicts || [],
150
- RECOMMENDATIONS: analysisResults.recommendations || [],
151
- };
152
-
153
- return context;
154
- }
155
-
156
- /**
157
- * Renders a YAML template with context
158
- *
159
- * @param {string} template - Template content
160
- * @param {Object} context - Context object
161
- * @returns {string} Rendered YAML content
162
- */
163
- function renderConfigTemplate(template, context) {
164
- let result = template;
165
-
166
- // Process {{#each}} blocks
167
- result = processEachBlocks(result, context);
168
-
169
- // Replace simple variables {{variable}}
170
- result = result.replace(/\{\{([^#/}][^}]*)\}\}/g, (match, key) => {
171
- const value = context[key.trim()];
172
- if (value === undefined) return match;
173
- if (typeof value === 'boolean') return value.toString();
174
- if (typeof value === 'number') return value.toString();
175
- return String(value);
176
- });
177
-
178
- return result;
179
- }
180
-
181
- /**
182
- * Process {{#each array}}...{{/each}} blocks
183
- *
184
- * @param {string} template - Template string
185
- * @param {Object} context - Context object
186
- * @returns {string} Processed template
187
- */
188
- function processEachBlocks(template, context) {
189
- const eachRegex = /\{\{#each\s+(\w+)\}\}([\s\S]*?)\{\{\/each\}\}/g;
190
-
191
- return template.replace(eachRegex, (match, arrayName, content) => {
192
- const array = context[arrayName];
193
- if (!Array.isArray(array) || array.length === 0) {
194
- return '';
195
- }
196
-
197
- return array
198
- .map((item) => {
199
- return content.replace(/\{\{this\}\}/g, String(item));
200
- })
201
- .join('');
202
- });
203
- }
204
-
205
- /**
206
- * Loads a config template
207
- *
208
- * @param {string} templateName - Template file name
209
- * @returns {string} Template content
210
- * @throws {Error} If template not found
211
- */
212
- function loadConfigTemplate(templateName) {
213
- const templatePath = path.join(TEMPLATES_DIR, templateName);
214
-
215
- if (!fs.existsSync(templatePath)) {
216
- throw new Error(`Config template not found: ${templatePath}`);
217
- }
218
-
219
- return fs.readFileSync(templatePath, 'utf8');
220
- }
221
-
222
- /**
223
- * Generates core-config.yaml for a project
224
- *
225
- * @param {string} targetDir - Target directory
226
- * @param {string} mode - Installation mode (greenfield/brownfield)
227
- * @param {Object} context - Config context
228
- * @param {Object} [options] - Generation options
229
- * @param {boolean} [options.dryRun] - Don't write file, just return content
230
- * @returns {Object} Generation result
231
- */
232
- function generateConfig(targetDir, mode, context, options = {}) {
233
- const templateName =
234
- mode === 'brownfield' ? ConfigTemplates.BROWNFIELD : ConfigTemplates.GREENFIELD;
235
-
236
- try {
237
- const template = loadConfigTemplate(templateName);
238
- const rendered = renderConfigTemplate(template, context);
239
-
240
- // Validate YAML syntax
241
- try {
242
- yaml.parse(rendered);
243
- } catch (yamlError) {
244
- return {
245
- success: false,
246
- error: `Generated YAML is invalid: ${yamlError.message}`,
247
- content: rendered,
248
- };
249
- }
250
-
251
- const configDir = path.join(targetDir, '.aios-core');
252
- const configPath = path.join(configDir, 'core-config.yaml');
253
-
254
- if (!options.dryRun) {
255
- fs.mkdirSync(configDir, { recursive: true });
256
- fs.writeFileSync(configPath, rendered, 'utf8');
257
- }
258
-
259
- return {
260
- success: true,
261
- path: configPath,
262
- content: rendered,
263
- };
264
- } catch (error) {
265
- return {
266
- success: false,
267
- error: error.message,
268
- content: null,
269
- };
270
- }
271
- }
272
-
273
- /**
274
- * Generates deployment config context from user inputs
275
- *
276
- * @param {Object} inputs - User inputs from wizard
277
- * @returns {Object} Deployment configuration
278
- */
279
- function buildDeploymentConfig(inputs = {}) {
280
- return {
281
- workflow: inputs.workflow || DeploymentWorkflow.STAGING_FIRST,
282
- stagingBranch: inputs.stagingBranch || 'staging',
283
- productionBranch: inputs.productionBranch || 'main',
284
- stagingEnvName: inputs.stagingEnvName || 'Staging',
285
- productionEnvName: inputs.productionEnvName || 'Production',
286
- platform: inputs.platform || DeploymentPlatform.NONE,
287
- qualityGates: {
288
- lint: inputs.lint !== false,
289
- typecheck: inputs.typecheck !== false,
290
- tests: inputs.tests !== false,
291
- security: inputs.security || false,
292
- minCoverage: inputs.minCoverage || 50,
293
- },
294
- autoAssignReviewers: inputs.autoAssignReviewers || false,
295
- draftByDefault: inputs.draftByDefault || false,
296
- };
297
- }
298
-
299
- /**
300
- * Gets default deployment config for a mode
301
- *
302
- * @param {string} mode - Installation mode
303
- * @returns {Object} Default deployment config
304
- */
305
- function getDefaultDeploymentConfig(mode) {
306
- if (mode === 'brownfield') {
307
- // Brownfield might use direct-to-main if solo project
308
- return {
309
- ...DEFAULT_DEPLOYMENT_CONFIG,
310
- // Keep staging-first as default, but brownfield analyzer may change this
311
- };
312
- }
313
-
314
- return { ...DEFAULT_DEPLOYMENT_CONFIG };
315
- }
316
-
317
- module.exports = {
318
- buildConfigContext,
319
- renderConfigTemplate,
320
- loadConfigTemplate,
321
- generateConfig,
322
- buildDeploymentConfig,
323
- getDefaultDeploymentConfig,
324
- ConfigTemplates,
325
- DeploymentWorkflow,
326
- DeploymentPlatform,
327
- DEFAULT_DEPLOYMENT_CONFIG,
328
- TEMPLATES_DIR,
329
- };
1
+ /**
2
+ * Config Generator Module
3
+ *
4
+ * Generates project-specific core-config.yaml from templates.
5
+ * Supports greenfield and brownfield modes with deployment configuration.
6
+ *
7
+ * @module documentation-integrity/config-generator
8
+ * @version 1.0.0
9
+ * @story 6.9
10
+ */
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+ const yaml = require('yaml');
15
+
16
+ // Template directory
17
+ const TEMPLATES_DIR = path.join(__dirname, '..', '..', 'templates', 'core-config');
18
+
19
+ /**
20
+ * Template file names
21
+ * @enum {string}
22
+ */
23
+ const ConfigTemplates = {
24
+ GREENFIELD: 'core-config-greenfield.tmpl.yaml',
25
+ BROWNFIELD: 'core-config-brownfield.tmpl.yaml',
26
+ };
27
+
28
+ /**
29
+ * Deployment workflow types
30
+ * @enum {string}
31
+ */
32
+ const DeploymentWorkflow = {
33
+ STAGING_FIRST: 'staging-first',
34
+ DIRECT_TO_MAIN: 'direct-to-main',
35
+ };
36
+
37
+ /**
38
+ * Deployment platform options
39
+ * @enum {string}
40
+ */
41
+ const DeploymentPlatform = {
42
+ RAILWAY: 'Railway',
43
+ VERCEL: 'Vercel',
44
+ AWS: 'AWS',
45
+ DOCKER: 'Docker',
46
+ NONE: 'None',
47
+ };
48
+
49
+ /**
50
+ * Default deployment configuration
51
+ * @type {Object}
52
+ */
53
+ const DEFAULT_DEPLOYMENT_CONFIG = {
54
+ workflow: DeploymentWorkflow.STAGING_FIRST,
55
+ stagingBranch: 'staging',
56
+ productionBranch: 'main',
57
+ defaultTarget: 'staging',
58
+ stagingEnvName: 'Staging',
59
+ productionEnvName: 'Production',
60
+ platform: DeploymentPlatform.NONE,
61
+ qualityGates: {
62
+ lint: true,
63
+ typecheck: true,
64
+ tests: true,
65
+ securityScan: false,
66
+ minCoverage: 50,
67
+ },
68
+ };
69
+
70
+ /**
71
+ * Escapes a string for use in YAML double-quoted strings
72
+ *
73
+ * @param {string} str - String to escape
74
+ * @returns {string} Escaped string safe for YAML
75
+ */
76
+ function escapeYamlString(str) {
77
+ return String(str)
78
+ .replace(/\\/g, '\\\\') // Escape backslashes first
79
+ .replace(/"/g, '\\"') // Escape double quotes
80
+ .replace(/\n/g, '\\n') // Escape newlines
81
+ .replace(/\r/g, '\\r') // Escape carriage returns
82
+ .replace(/\t/g, '\\t'); // Escape tabs
83
+ }
84
+
85
+ /**
86
+ * Formats an array as YAML list string for template substitution
87
+ *
88
+ * @param {Array} arr - Array to format
89
+ * @param {number} [indent=4] - Number of spaces for indentation
90
+ * @returns {string} YAML-formatted array string
91
+ */
92
+ function formatArrayAsYaml(arr, indent = 4) {
93
+ if (!Array.isArray(arr) || arr.length === 0) {
94
+ return '[]';
95
+ }
96
+ const spaces = ' '.repeat(indent);
97
+ const items = arr.map((item) => `\n${spaces}- "${escapeYamlString(item)}"`).join('');
98
+ return items;
99
+ }
100
+
101
+ /**
102
+ * Builds config context from project info and deployment settings
103
+ *
104
+ * @param {string} projectName - Project name
105
+ * @param {string} mode - Installation mode (greenfield/brownfield)
106
+ * @param {Object} deploymentConfig - Deployment configuration
107
+ * @param {Object} [analysisResults] - Brownfield analysis results (if applicable)
108
+ * @returns {Object} Config context for template rendering
109
+ */
110
+ function buildConfigContext(projectName, mode, deploymentConfig = {}, analysisResults = {}) {
111
+ const config = { ...DEFAULT_DEPLOYMENT_CONFIG, ...deploymentConfig };
112
+ const isStaging = config.workflow === DeploymentWorkflow.STAGING_FIRST;
113
+
114
+ const context = {
115
+ // Basic info
116
+ PROJECT_NAME: projectName,
117
+ GENERATED_DATE: new Date().toISOString().split('T')[0],
118
+ PROJECT_VERSION: analysisResults.version || '0.1.0',
119
+
120
+ // Deployment workflow
121
+ DEPLOYMENT_WORKFLOW: config.workflow,
122
+
123
+ // Branch configuration
124
+ STAGING_BRANCH: isStaging ? config.stagingBranch : 'null',
125
+ PRODUCTION_BRANCH: config.productionBranch,
126
+ // Use symbolic name ('staging'/'production') - deployment-config-loader resolves to actual branch
127
+ DEFAULT_TARGET: isStaging ? 'staging' : 'production',
128
+
129
+ // Environment names
130
+ STAGING_ENV_NAME: config.stagingEnvName,
131
+ PRODUCTION_ENV_NAME: config.productionEnvName,
132
+
133
+ // Platform
134
+ DEPLOYMENT_PLATFORM: config.platform,
135
+
136
+ // Quality gates
137
+ QUALITY_LINT: config.qualityGates.lint,
138
+ QUALITY_TYPECHECK: config.qualityGates.typecheck,
139
+ QUALITY_TESTS: config.qualityGates.tests,
140
+ QUALITY_SECURITY: config.qualityGates.securityScan || false,
141
+ MIN_COVERAGE: config.qualityGates.minCoverage || 50,
142
+
143
+ // Brownfield specific (defaults for greenfield)
144
+ HAS_EXISTING_STRUCTURE: analysisResults.hasExistingStructure || false,
145
+ HAS_EXISTING_WORKFLOWS: analysisResults.hasExistingWorkflows || false,
146
+ HAS_EXISTING_STANDARDS: analysisResults.hasExistingStandards || false,
147
+ MERGE_STRATEGY: analysisResults.mergeStrategy || 'parallel',
148
+
149
+ // Detected configs (brownfield)
150
+ DETECTED_TECH_STACK: JSON.stringify(analysisResults.techStack || []),
151
+ DETECTED_FRAMEWORKS: JSON.stringify(analysisResults.frameworks || []),
152
+ DETECTED_LINTING: analysisResults.linting || 'none',
153
+ DETECTED_FORMATTING: analysisResults.formatting || 'none',
154
+ DETECTED_TESTING: analysisResults.testing || 'none',
155
+
156
+ // Auto deploy settings
157
+ STAGING_AUTO_DEPLOY: config.stagingAutoDeploy !== false,
158
+ PRODUCTION_AUTO_DEPLOY: config.productionAutoDeploy !== false,
159
+
160
+ // PR settings
161
+ AUTO_ASSIGN_REVIEWERS: config.autoAssignReviewers || false,
162
+ DRAFT_BY_DEFAULT: config.draftByDefault || false,
163
+
164
+ // Existing config paths (brownfield)
165
+ ESLINT_CONFIG_PATH: analysisResults.eslintPath || 'null',
166
+ PRETTIER_CONFIG_PATH: analysisResults.prettierPath || 'null',
167
+ TSCONFIG_PATH: analysisResults.tsconfigPath || 'null',
168
+ FLAKE8_CONFIG_PATH: analysisResults.flake8Path || 'null',
169
+ GITHUB_WORKFLOWS_PATH: analysisResults.githubWorkflowsPath || 'null',
170
+ GITLAB_CI_PATH: analysisResults.gitlabCiPath || 'null',
171
+ PACKAGE_JSON_PATH: analysisResults.packageJsonPath || 'null',
172
+ REQUIREMENTS_PATH: analysisResults.requirementsPath || 'null',
173
+ GO_MOD_PATH: analysisResults.goModPath || 'null',
174
+
175
+ // Merge settings
176
+ MERGE_WORKFLOWS: analysisResults.mergeWorkflows || false,
177
+
178
+ // Migration notes (brownfield)
179
+ MIGRATION_SUMMARY: analysisResults.summary || 'No analysis performed',
180
+ MANUAL_REVIEW_ITEMS: analysisResults.manualReviewItems || [],
181
+ CONFLICTS: analysisResults.conflicts || [],
182
+ RECOMMENDATIONS: analysisResults.recommendations || [],
183
+
184
+ // Pre-formatted YAML arrays for template substitution (avoids Handlebars #each)
185
+ MANUAL_REVIEW_ITEMS_YAML: formatArrayAsYaml(analysisResults.manualReviewItems || []),
186
+ CONFLICTS_YAML: formatArrayAsYaml(analysisResults.conflicts || []),
187
+ RECOMMENDATIONS_YAML: formatArrayAsYaml(analysisResults.recommendations || []),
188
+ };
189
+
190
+ return context;
191
+ }
192
+
193
+ /**
194
+ * Renders a YAML template with context
195
+ *
196
+ * @param {string} template - Template content
197
+ * @param {Object} context - Context object
198
+ * @returns {string} Rendered YAML content
199
+ */
200
+ function renderConfigTemplate(template, context) {
201
+ let result = template;
202
+
203
+ // Process {{#each}} blocks
204
+ result = processEachBlocks(result, context);
205
+
206
+ // Replace simple variables {{variable}}
207
+ result = result.replace(/\{\{([^#/}][^}]*)\}\}/g, (match, key) => {
208
+ const value = context[key.trim()];
209
+ if (value === undefined) return match;
210
+ if (typeof value === 'boolean') return value.toString();
211
+ if (typeof value === 'number') return value.toString();
212
+ return String(value);
213
+ });
214
+
215
+ return result;
216
+ }
217
+
218
+ /**
219
+ * Process {{#each array}}...{{/each}} blocks
220
+ *
221
+ * @param {string} template - Template string
222
+ * @param {Object} context - Context object
223
+ * @returns {string} Processed template
224
+ */
225
+ function processEachBlocks(template, context) {
226
+ const eachRegex = /\{\{#each\s+(\w+)\}\}([\s\S]*?)\{\{\/each\}\}/g;
227
+
228
+ return template.replace(eachRegex, (match, arrayName, content) => {
229
+ const array = context[arrayName];
230
+ if (!Array.isArray(array) || array.length === 0) {
231
+ return '';
232
+ }
233
+
234
+ return array
235
+ .map((item) => {
236
+ return content.replace(/\{\{this\}\}/g, String(item));
237
+ })
238
+ .join('');
239
+ });
240
+ }
241
+
242
+ /**
243
+ * Loads a config template
244
+ *
245
+ * @param {string} templateName - Template file name
246
+ * @returns {string} Template content
247
+ * @throws {Error} If template not found
248
+ */
249
+ function loadConfigTemplate(templateName) {
250
+ const templatePath = path.join(TEMPLATES_DIR, templateName);
251
+
252
+ if (!fs.existsSync(templatePath)) {
253
+ throw new Error(`Config template not found: ${templatePath}`);
254
+ }
255
+
256
+ return fs.readFileSync(templatePath, 'utf8');
257
+ }
258
+
259
+ /**
260
+ * Generates core-config.yaml for a project
261
+ *
262
+ * @param {string} targetDir - Target directory
263
+ * @param {string} mode - Installation mode (greenfield/brownfield)
264
+ * @param {Object} context - Config context
265
+ * @param {Object} [options] - Generation options
266
+ * @param {boolean} [options.dryRun] - Don't write file, just return content
267
+ * @returns {Object} Generation result
268
+ */
269
+ function generateConfig(targetDir, mode, context, options = {}) {
270
+ const templateName =
271
+ mode === 'brownfield' ? ConfigTemplates.BROWNFIELD : ConfigTemplates.GREENFIELD;
272
+
273
+ try {
274
+ const template = loadConfigTemplate(templateName);
275
+ const rendered = renderConfigTemplate(template, context);
276
+
277
+ // Validate YAML syntax
278
+ try {
279
+ yaml.parse(rendered);
280
+ } catch (yamlError) {
281
+ return {
282
+ success: false,
283
+ error: `Generated YAML is invalid: ${yamlError.message}`,
284
+ content: rendered,
285
+ };
286
+ }
287
+
288
+ const configDir = path.join(targetDir, '.aios-core');
289
+ const configPath = path.join(configDir, 'core-config.yaml');
290
+
291
+ if (!options.dryRun) {
292
+ fs.mkdirSync(configDir, { recursive: true });
293
+ fs.writeFileSync(configPath, rendered, 'utf8');
294
+ }
295
+
296
+ return {
297
+ success: true,
298
+ path: configPath,
299
+ content: rendered,
300
+ };
301
+ } catch (error) {
302
+ return {
303
+ success: false,
304
+ error: error.message,
305
+ content: null,
306
+ };
307
+ }
308
+ }
309
+
310
+ /**
311
+ * Generates deployment config context from user inputs
312
+ *
313
+ * @param {Object} inputs - User inputs from wizard
314
+ * @returns {Object} Deployment configuration
315
+ */
316
+ function buildDeploymentConfig(inputs = {}) {
317
+ return {
318
+ workflow: inputs.workflow || DeploymentWorkflow.STAGING_FIRST,
319
+ stagingBranch: inputs.stagingBranch || 'staging',
320
+ productionBranch: inputs.productionBranch || 'main',
321
+ stagingEnvName: inputs.stagingEnvName || 'Staging',
322
+ productionEnvName: inputs.productionEnvName || 'Production',
323
+ platform: inputs.platform || DeploymentPlatform.NONE,
324
+ qualityGates: {
325
+ lint: inputs.lint !== false,
326
+ typecheck: inputs.typecheck !== false,
327
+ tests: inputs.tests !== false,
328
+ securityScan: inputs.securityScan || false,
329
+ minCoverage: inputs.minCoverage || 50,
330
+ },
331
+ autoAssignReviewers: inputs.autoAssignReviewers || false,
332
+ draftByDefault: inputs.draftByDefault || false,
333
+ };
334
+ }
335
+
336
+ /**
337
+ * Gets default deployment config for a mode
338
+ *
339
+ * @param {string} mode - Installation mode
340
+ * @returns {Object} Default deployment config
341
+ */
342
+ function getDefaultDeploymentConfig(mode) {
343
+ if (mode === 'brownfield') {
344
+ // Brownfield might use direct-to-main if solo project
345
+ return {
346
+ ...DEFAULT_DEPLOYMENT_CONFIG,
347
+ // Keep staging-first as default, but brownfield analyzer may change this
348
+ };
349
+ }
350
+
351
+ return { ...DEFAULT_DEPLOYMENT_CONFIG };
352
+ }
353
+
354
+ module.exports = {
355
+ buildConfigContext,
356
+ renderConfigTemplate,
357
+ loadConfigTemplate,
358
+ generateConfig,
359
+ buildDeploymentConfig,
360
+ getDefaultDeploymentConfig,
361
+ formatArrayAsYaml,
362
+ escapeYamlString,
363
+ ConfigTemplates,
364
+ DeploymentWorkflow,
365
+ DeploymentPlatform,
366
+ DEFAULT_DEPLOYMENT_CONFIG,
367
+ TEMPLATES_DIR,
368
+ };