create-universal-ai-context 2.4.0 → 2.6.0-final

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 (153) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +331 -294
  3. package/bin/create-ai-context.js +1507 -764
  4. package/lib/adapters/aider.js +131 -131
  5. package/lib/adapters/antigravity.js +205 -205
  6. package/lib/adapters/claude.js +397 -397
  7. package/lib/adapters/cline.js +125 -125
  8. package/lib/adapters/continue.js +138 -138
  9. package/lib/adapters/copilot.js +131 -131
  10. package/lib/adapters/index.js +78 -78
  11. package/lib/adapters/windsurf.js +138 -138
  12. package/lib/ai-context-generator.js +234 -234
  13. package/lib/ai-orchestrator.js +432 -432
  14. package/lib/call-tracer.js +444 -444
  15. package/lib/content-preservation.js +243 -243
  16. package/lib/cross-tool-sync/file-watcher.js +274 -274
  17. package/lib/cross-tool-sync/index.js +41 -40
  18. package/lib/cross-tool-sync/sync-manager.js +540 -512
  19. package/lib/cross-tool-sync/sync-service.js +297 -297
  20. package/lib/detector.js +726 -726
  21. package/lib/doc-discovery.js +741 -741
  22. package/lib/drift-checker.js +920 -920
  23. package/lib/environment-detector.js +239 -239
  24. package/lib/index.js +399 -399
  25. package/lib/install-hooks.js +82 -82
  26. package/lib/installer.js +419 -419
  27. package/lib/migrate.js +328 -328
  28. package/lib/placeholder.js +632 -632
  29. package/lib/prompts.js +341 -341
  30. package/lib/smart-merge.js +540 -540
  31. package/lib/spinner.js +60 -60
  32. package/lib/static-analyzer.js +729 -729
  33. package/lib/template-coordination.js +148 -148
  34. package/lib/template-populator.js +843 -843
  35. package/lib/template-renderer.js +392 -392
  36. package/lib/utils/fs-wrapper.js +79 -79
  37. package/lib/utils/path-utils.js +60 -60
  38. package/lib/validate.js +155 -155
  39. package/package.json +1 -1
  40. package/templates/AI_CONTEXT.md.template +245 -245
  41. package/templates/base/README.md +260 -257
  42. package/templates/base/RPI_WORKFLOW_PLAN.md +325 -320
  43. package/templates/base/agents/api-developer.md +76 -76
  44. package/templates/base/agents/context-engineer.md +525 -525
  45. package/templates/base/agents/core-architect.md +76 -76
  46. package/templates/base/agents/database-ops.md +76 -76
  47. package/templates/base/agents/deployment-ops.md +76 -76
  48. package/templates/base/agents/integration-hub.md +76 -76
  49. package/templates/base/analytics/README.md +114 -114
  50. package/templates/base/automation/config.json +58 -58
  51. package/templates/base/automation/generators/code-mapper.js +308 -308
  52. package/templates/base/automation/generators/index-builder.js +321 -321
  53. package/templates/base/automation/hooks/post-commit.sh +83 -83
  54. package/templates/base/automation/hooks/pre-commit.sh +103 -103
  55. package/templates/base/ci-templates/README.md +108 -108
  56. package/templates/base/ci-templates/github-actions/context-check.yml +144 -144
  57. package/templates/base/ci-templates/github-actions/validate-docs.yml +105 -105
  58. package/templates/base/commands/analytics.md +238 -238
  59. package/templates/base/commands/auto-sync.md +172 -172
  60. package/templates/base/commands/collab.md +194 -194
  61. package/templates/base/commands/context-optimize.md +226 -0
  62. package/templates/base/commands/help.md +485 -450
  63. package/templates/base/commands/rpi-implement.md +164 -115
  64. package/templates/base/commands/rpi-plan.md +147 -93
  65. package/templates/base/commands/rpi-research.md +145 -88
  66. package/templates/base/commands/session-resume.md +144 -144
  67. package/templates/base/commands/session-save.md +112 -112
  68. package/templates/base/commands/validate-all.md +77 -77
  69. package/templates/base/commands/verify-docs-current.md +86 -86
  70. package/templates/base/config/base.json +57 -57
  71. package/templates/base/config/environments/development.json +13 -13
  72. package/templates/base/config/environments/production.json +17 -17
  73. package/templates/base/config/environments/staging.json +13 -13
  74. package/templates/base/config/local.json.example +21 -21
  75. package/templates/base/context/.meta/generated-at.json +18 -18
  76. package/templates/base/context/ARCHITECTURE_SNAPSHOT.md +156 -156
  77. package/templates/base/context/CODE_TO_WORKFLOW_MAP.md +94 -94
  78. package/templates/base/context/FILE_OWNERSHIP.md +57 -57
  79. package/templates/base/context/INTEGRATION_POINTS.md +92 -92
  80. package/templates/base/context/KNOWN_GOTCHAS.md +195 -195
  81. package/templates/base/context/TESTING_MAP.md +95 -95
  82. package/templates/base/context/WORKFLOW_INDEX.md +129 -129
  83. package/templates/base/context/workflows/WORKFLOW_TEMPLATE.md +294 -294
  84. package/templates/base/indexes/agents/CAPABILITY_MATRIX.md +255 -255
  85. package/templates/base/indexes/agents/CATEGORY_INDEX.md +44 -44
  86. package/templates/base/indexes/code/CATEGORY_INDEX.md +38 -38
  87. package/templates/base/indexes/routing/CATEGORY_INDEX.md +39 -39
  88. package/templates/base/indexes/search/CATEGORY_INDEX.md +39 -39
  89. package/templates/base/indexes/workflows/CATEGORY_INDEX.md +38 -38
  90. package/templates/base/knowledge/README.md +98 -98
  91. package/templates/base/knowledge/sessions/README.md +88 -88
  92. package/templates/base/knowledge/sessions/TEMPLATE.md +150 -150
  93. package/templates/base/knowledge/shared/decisions/0001-adopt-context-engineering.md +144 -144
  94. package/templates/base/knowledge/shared/decisions/README.md +49 -49
  95. package/templates/base/knowledge/shared/decisions/TEMPLATE.md +123 -123
  96. package/templates/base/knowledge/shared/patterns/README.md +62 -62
  97. package/templates/base/knowledge/shared/patterns/TEMPLATE.md +120 -120
  98. package/templates/base/plans/PLAN_TEMPLATE.md +316 -250
  99. package/templates/base/research/RESEARCH_TEMPLATE.md +245 -153
  100. package/templates/base/schemas/agent.schema.json +141 -141
  101. package/templates/base/schemas/anchors.schema.json +54 -54
  102. package/templates/base/schemas/automation.schema.json +93 -93
  103. package/templates/base/schemas/command.schema.json +134 -134
  104. package/templates/base/schemas/hashes.schema.json +40 -40
  105. package/templates/base/schemas/manifest.schema.json +117 -117
  106. package/templates/base/schemas/plan.schema.json +136 -136
  107. package/templates/base/schemas/research.schema.json +115 -115
  108. package/templates/base/schemas/roles.schema.json +34 -34
  109. package/templates/base/schemas/session.schema.json +77 -77
  110. package/templates/base/schemas/settings.schema.json +244 -244
  111. package/templates/base/schemas/staleness.schema.json +53 -53
  112. package/templates/base/schemas/team-config.schema.json +42 -42
  113. package/templates/base/schemas/workflow.schema.json +126 -126
  114. package/templates/base/session/checkpoints/.gitkeep +2 -2
  115. package/templates/base/session/current/state.json +20 -20
  116. package/templates/base/session/history/.gitkeep +2 -2
  117. package/templates/base/settings.json +3 -3
  118. package/templates/base/standards/COMPATIBILITY.md +219 -219
  119. package/templates/base/standards/EXTENSION_GUIDELINES.md +280 -280
  120. package/templates/base/standards/QUALITY_CHECKLIST.md +211 -211
  121. package/templates/base/standards/README.md +66 -66
  122. package/templates/base/sync/anchors.json +6 -6
  123. package/templates/base/sync/hashes.json +6 -6
  124. package/templates/base/sync/staleness.json +10 -10
  125. package/templates/base/team/README.md +168 -168
  126. package/templates/base/team/config.json +79 -79
  127. package/templates/base/team/roles.json +145 -145
  128. package/templates/base/tools/bin/claude-context.js +151 -151
  129. package/templates/base/tools/lib/anchor-resolver.js +276 -276
  130. package/templates/base/tools/lib/config-loader.js +363 -363
  131. package/templates/base/tools/lib/detector.js +350 -350
  132. package/templates/base/tools/lib/diagnose.js +206 -206
  133. package/templates/base/tools/lib/drift-detector.js +373 -373
  134. package/templates/base/tools/lib/errors.js +199 -199
  135. package/templates/base/tools/lib/index.js +36 -36
  136. package/templates/base/tools/lib/init.js +192 -192
  137. package/templates/base/tools/lib/logger.js +230 -230
  138. package/templates/base/tools/lib/placeholder.js +201 -201
  139. package/templates/base/tools/lib/session-manager.js +354 -354
  140. package/templates/base/tools/lib/validate.js +521 -521
  141. package/templates/base/tools/package.json +49 -49
  142. package/templates/handlebars/aider-config.hbs +146 -80
  143. package/templates/handlebars/antigravity.hbs +377 -377
  144. package/templates/handlebars/claude.hbs +183 -183
  145. package/templates/handlebars/cline.hbs +62 -62
  146. package/templates/handlebars/continue-config.hbs +116 -116
  147. package/templates/handlebars/copilot.hbs +130 -130
  148. package/templates/handlebars/partials/gotcha-list.hbs +11 -11
  149. package/templates/handlebars/partials/header.hbs +3 -3
  150. package/templates/handlebars/partials/workflow-summary.hbs +16 -16
  151. package/templates/handlebars/windsurf-rules.hbs +69 -69
  152. package/templates/hooks/post-commit.hbs +28 -29
  153. package/templates/hooks/pre-commit.hbs +46 -46
@@ -1,397 +1,397 @@
1
- /**
2
- * Claude Adapter
3
- *
4
- * Generates AI_CONTEXT.md and .claude/ directory structure.
5
- * This is the primary/universal format.
6
- */
7
-
8
- const fs = require('fs');
9
- const path = require('path');
10
- const { renderTemplateByName, buildContext } = require('../template-renderer');
11
- const { isManagedFile } = require('../template-coordination');
12
- const { findCustomContentInClaude, migrateCustomContent } = require('../content-preservation');
13
-
14
- /**
15
- * Adapter metadata
16
- */
17
- const adapter = {
18
- name: 'claude',
19
- displayName: 'Claude Code',
20
- description: 'Universal AI context format for Claude Code and other AI assistants',
21
- outputType: 'multi-file',
22
- outputPath: '.claude/'
23
- };
24
-
25
- /**
26
- * Get output path for Claude context file
27
- * @param {string} projectRoot - Project root directory
28
- * @returns {string} Output file path
29
- */
30
- function getOutputPath(projectRoot) {
31
- return path.join(projectRoot, 'AI_CONTEXT.md');
32
- }
33
-
34
- /**
35
- * Check if Claude output already exists
36
- * @param {string} projectRoot - Project root directory
37
- * @returns {boolean}
38
- */
39
- function exists(projectRoot) {
40
- const aiContextPath = getOutputPath(projectRoot);
41
- const claudeDir = path.join(projectRoot, '.claude');
42
- return fs.existsSync(aiContextPath) || fs.existsSync(claudeDir);
43
- }
44
-
45
- /**
46
- * Generate Claude context file and .claude/ directory structure
47
- * @param {object} analysis - Analysis results from static analyzer
48
- * @param {object} config - Configuration from CLI
49
- * @param {string} projectRoot - Project root directory
50
- * @returns {object} Generation result
51
- */
52
- async function generate(analysis, config, projectRoot) {
53
- const result = {
54
- success: false,
55
- adapter: adapter.name,
56
- files: [],
57
- errors: []
58
- };
59
-
60
- try {
61
- // 1. Generate AI_CONTEXT.md at project root
62
- const outputPath = getOutputPath(projectRoot);
63
-
64
- // Check if file exists and is custom (not managed by us)
65
- if (fs.existsSync(outputPath) && !config.force) {
66
- if (!isManagedFile(outputPath)) {
67
- result.errors.push({
68
- message: 'AI_CONTEXT.md exists and appears to be custom. Use --force to overwrite.',
69
- code: 'EXISTS_CUSTOM',
70
- severity: 'error'
71
- });
72
- // Don't return early - still try to generate .claude/ directory
73
- } else {
74
- // File is managed by us, safe to overwrite
75
- const context = buildContext(analysis, config, 'claude');
76
- const content = renderTemplateByName('claude', context);
77
- fs.writeFileSync(outputPath, content, 'utf-8');
78
- result.files.push({
79
- path: outputPath,
80
- relativePath: 'AI_CONTEXT.md',
81
- size: content.length
82
- });
83
- }
84
- } else {
85
- // File doesn't exist or force is enabled
86
- const context = buildContext(analysis, config, 'claude');
87
- const content = renderTemplateByName('claude', context);
88
- fs.writeFileSync(outputPath, content, 'utf-8');
89
- result.files.push({
90
- path: outputPath,
91
- relativePath: 'AI_CONTEXT.md',
92
- size: content.length
93
- });
94
- }
95
-
96
- // 2. Generate .claude/ directory structure
97
- const context = buildContext(analysis, config, 'claude');
98
- const claudeDirResult = await generateClaudeDirectory(projectRoot, context, config, result);
99
- if (claudeDirResult) {
100
- result.files.push(...claudeDirResult);
101
- }
102
-
103
- // Success if no actual errors (warnings and info are OK)
104
- result.success = result.errors.length === 0 ||
105
- result.errors.every(e => e.code === 'EXISTS' || e.severity === 'info' || e.severity === 'warning');
106
- } catch (error) {
107
- result.errors.push({
108
- message: error.message,
109
- stack: error.stack
110
- });
111
- }
112
-
113
- return result;
114
- }
115
-
116
- /**
117
- * Generate .claude/ directory with symlinks to .ai-context/
118
- * @param {string} projectRoot - Project root directory
119
- * @param {object} context - Template context
120
- * @param {object} config - Configuration from CLI
121
- * @param {object} result - Result object to track files/errors
122
- * @returns {Array} List of generated files
123
- */
124
- async function generateClaudeDirectory(projectRoot, context, config, result) {
125
- const { copyDirectory } = require('../installer');
126
- const templatesDir = path.join(__dirname, '..', '..', 'templates', 'base');
127
- const aiContextDir = path.join(projectRoot, '.ai-context');
128
- const claudeDir = path.join(projectRoot, '.claude');
129
-
130
- // Check for existing .claude/ directory
131
- if (fs.existsSync(claudeDir) && !config.force) {
132
- // Check if it has custom files
133
- const hasCustomFiles = checkForCustomFiles(claudeDir);
134
- if (hasCustomFiles) {
135
- // Migrate custom content before skipping
136
- const customItems = findCustomContentInClaude(claudeDir);
137
- if (customItems.length > 0) {
138
- const migrated = migrateCustomContent(claudeDir, aiContextDir, customItems);
139
- result.errors.push({
140
- message: `Migrated ${migrated.length} custom items from .claude/ to .ai-context/custom/`,
141
- code: 'MIGRATED_CUSTOM',
142
- severity: 'info',
143
- migratedItems: migrated
144
- });
145
- }
146
-
147
- result.errors.push({
148
- message: '.claude/ directory exists and contains custom files. Use --force to overwrite. Skipping directory generation.',
149
- code: 'EXISTS_CUSTOM',
150
- severity: 'warning'
151
- });
152
- return [{
153
- path: claudeDir,
154
- relativePath: '.claude/',
155
- size: 0,
156
- skipped: true
157
- }];
158
- }
159
- // Directory exists but only has managed files, we can regenerate
160
- }
161
-
162
- try {
163
- // Create .claude/ directory
164
- fs.mkdirSync(claudeDir, { recursive: true });
165
-
166
- // Subdirectories to symlink from .ai-context/
167
- const subdirsToLink = [
168
- 'agents',
169
- 'commands',
170
- 'indexes',
171
- 'context',
172
- 'schemas',
173
- 'standards',
174
- 'tools'
175
- ];
176
-
177
- let linksCreated = 0;
178
- let filesCopied = 0;
179
-
180
- for (const subdir of subdirsToLink) {
181
- const srcPath = path.join(aiContextDir, subdir);
182
- const destPath = path.join(claudeDir, subdir);
183
-
184
- // Skip if source doesn't exist
185
- if (!fs.existsSync(srcPath)) {
186
- continue;
187
- }
188
-
189
- // Try to create symlink, fallback to copy
190
- try {
191
- // Remove dest if it exists (shouldn't, but safety)
192
- if (fs.existsSync(destPath)) {
193
- if (fs.lstatSync(destPath).isSymbolicLink()) {
194
- fs.unlinkSync(destPath);
195
- } else {
196
- // Existing directory, skip
197
- continue;
198
- }
199
- }
200
-
201
- // Create symlink
202
- fs.symlinkSync(srcPath, destPath, 'junction');
203
- linksCreated++;
204
- } catch (symlinkError) {
205
- // Symlink failed (likely Windows permissions or filesystem limitation)
206
- // Fallback: copy directory contents
207
- if (!fs.existsSync(destPath)) {
208
- fs.mkdirSync(destPath, { recursive: true });
209
- const count = await copyDirectory(srcPath, destPath);
210
- filesCopied += count;
211
- }
212
- }
213
- }
214
-
215
- // Create minimal .claude/settings.json
216
- const settingsPath = path.join(claudeDir, 'settings.json');
217
- const settings = {
218
- '$schema': './schemas/settings.schema.json',
219
- version: '2.2.2',
220
- project: {
221
- name: context.project?.name || 'Project',
222
- tech_stack: context.project?.tech_stack || 'Not detected'
223
- },
224
- agents: {
225
- context_engineer: 'enabled',
226
- core_architect: 'enabled',
227
- api_developer: 'enabled',
228
- database_ops: 'enabled',
229
- integration_hub: 'enabled',
230
- deployment_ops: 'enabled'
231
- },
232
- commands: {
233
- rpi_workflow: 'enabled',
234
- validation: 'enabled'
235
- }
236
- };
237
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
238
- filesCopied++;
239
-
240
- // Create .claude/README.md
241
- const readmePath = path.join(claudeDir, 'README.md');
242
- const readme = `# .claude Configuration - ${context.project?.name || 'Project'}
243
-
244
- This directory provides Claude Code with auto-discovered commands, agents, and configuration.
245
-
246
- ## Architecture
247
-
248
- This directory uses **symlinks** to \`../.ai-context/\` for all shared content:
249
-
250
- \`\`\`
251
- .claude/
252
- ├── agents → ../.ai-context/agents/
253
- ├── commands → ../.ai-context/commands/
254
- ├── indexes → ../.ai-context/indexes/
255
- ├── context → ../.ai-context/context/
256
- ├── schemas → ../.ai-context/schemas/
257
- ├── standards → ../.ai-context/standards/
258
- ├── tools → ../.ai-context/tools/
259
- ├── settings.json (this file - Claude-specific)
260
- └── README.md (this file)
261
- \`\`\`
262
-
263
- **Single source of truth:** All content lives in \`.ai-context/\`. Edit there, not here.
264
-
265
- ## Quick Start
266
-
267
- 1. Load agents: \`@context-engineer\`
268
- 2. Use commands: \`/rpi-research\`, \`/rpi-plan\`, \`/rpi-implement\`
269
- 3. Validate: \`/verify-docs-current\`
270
-
271
- ## Universal Context
272
-
273
- See \`AI_CONTEXT.md\` at project root for universal AI context (works with all tools).
274
-
275
- *Generated by create-universal-ai-context v${context.version || '2.3.0'}*
276
- `;
277
- fs.writeFileSync(readmePath, readme);
278
- filesCopied++;
279
-
280
- // Add info message about symlink approach
281
- if (linksCreated > 0) {
282
- result.errors.push({
283
- message: `Created ${linksCreated} symlinks from .claude/ to .ai-context/ (single source of truth)`,
284
- code: 'SYMLINKS_CREATED',
285
- severity: 'info'
286
- });
287
- }
288
-
289
- return [{
290
- path: claudeDir,
291
- relativePath: '.claude/',
292
- size: filesCopied,
293
- symlinks: linksCreated,
294
- details: `${linksCreated} symlinks, ${filesCopied} files`
295
- }];
296
-
297
- } catch (error) {
298
- result.errors.push({
299
- message: `Failed to generate .claude/ directory: ${error.message}`,
300
- stack: error.stack
301
- });
302
- return null;
303
- }
304
- }
305
-
306
- /**
307
- * Check if directory contains custom (non-managed) files
308
- * @param {string} dir - Directory to check
309
- * @returns {boolean} True if custom files found
310
- */
311
- function checkForCustomFiles(dir) {
312
- const walkDir = (currentDir, depth = 0) => {
313
- if (depth > 10) return false;
314
-
315
- const entries = fs.readdirSync(currentDir, { withFileTypes: true });
316
- for (const entry of entries) {
317
- if (entry.isDirectory()) {
318
- if (entry.name !== 'node_modules' && entry.name !== '.git') {
319
- if (walkDir(path.join(currentDir, entry.name), depth + 1)) {
320
- return true;
321
- }
322
- }
323
- } else if (entry.name.endsWith('.md')) {
324
- const filePath = path.join(currentDir, entry.name);
325
- if (!isManagedFile(filePath)) {
326
- return true;
327
- }
328
- }
329
- }
330
- return false;
331
- };
332
-
333
- return walkDir(dir);
334
- }
335
-
336
- /**
337
- * Validate Claude output
338
- * @param {string} projectRoot - Project root directory
339
- * @returns {object} Validation result
340
- */
341
- function validate(projectRoot) {
342
- const issues = [];
343
-
344
- // 1. Validate AI_CONTEXT.md
345
- const outputPath = getOutputPath(projectRoot);
346
- if (!fs.existsSync(outputPath)) {
347
- issues.push({ file: 'AI_CONTEXT.md', error: 'not found' });
348
- } else {
349
- const content = fs.readFileSync(outputPath, 'utf-8');
350
- const placeholderMatch = content.match(/\{\{[A-Z_]+\}\}/g);
351
- if (placeholderMatch && placeholderMatch.length > 0) {
352
- issues.push({
353
- file: 'AI_CONTEXT.md',
354
- error: `Found ${placeholderMatch.length} unreplaced placeholders`
355
- });
356
- }
357
- }
358
-
359
- // 2. Validate .claude/ directory (optional, warn if missing)
360
- const claudeDir = path.join(projectRoot, '.claude');
361
- if (!fs.existsSync(claudeDir)) {
362
- issues.push({
363
- file: '.claude/',
364
- error: 'directory not found (optional but recommended)',
365
- severity: 'warning'
366
- });
367
- } else {
368
- // Check for critical files
369
- const criticalFiles = [
370
- 'settings.json',
371
- 'README.md'
372
- ];
373
- for (const file of criticalFiles) {
374
- if (!fs.existsSync(path.join(claudeDir, file))) {
375
- issues.push({
376
- file: `.claude/${file}`,
377
- error: 'missing',
378
- severity: 'warning'
379
- });
380
- }
381
- }
382
- }
383
-
384
- return {
385
- valid: issues.filter(i => i.severity !== 'warning').length === 0,
386
- issues,
387
- warnings: issues.filter(i => i.severity === 'warning').length
388
- };
389
- }
390
-
391
- module.exports = {
392
- ...adapter,
393
- getOutputPath,
394
- exists,
395
- generate,
396
- validate
397
- };
1
+ /**
2
+ * Claude Adapter
3
+ *
4
+ * Generates AI_CONTEXT.md and .claude/ directory structure.
5
+ * This is the primary/universal format.
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const { renderTemplateByName, buildContext } = require('../template-renderer');
11
+ const { isManagedFile } = require('../template-coordination');
12
+ const { findCustomContentInClaude, migrateCustomContent } = require('../content-preservation');
13
+
14
+ /**
15
+ * Adapter metadata
16
+ */
17
+ const adapter = {
18
+ name: 'claude',
19
+ displayName: 'Claude Code',
20
+ description: 'Universal AI context format for Claude Code and other AI assistants',
21
+ outputType: 'multi-file',
22
+ outputPath: '.claude/'
23
+ };
24
+
25
+ /**
26
+ * Get output path for Claude context file
27
+ * @param {string} projectRoot - Project root directory
28
+ * @returns {string} Output file path
29
+ */
30
+ function getOutputPath(projectRoot) {
31
+ return path.join(projectRoot, 'AI_CONTEXT.md');
32
+ }
33
+
34
+ /**
35
+ * Check if Claude output already exists
36
+ * @param {string} projectRoot - Project root directory
37
+ * @returns {boolean}
38
+ */
39
+ function exists(projectRoot) {
40
+ const aiContextPath = getOutputPath(projectRoot);
41
+ const claudeDir = path.join(projectRoot, '.claude');
42
+ return fs.existsSync(aiContextPath) || fs.existsSync(claudeDir);
43
+ }
44
+
45
+ /**
46
+ * Generate Claude context file and .claude/ directory structure
47
+ * @param {object} analysis - Analysis results from static analyzer
48
+ * @param {object} config - Configuration from CLI
49
+ * @param {string} projectRoot - Project root directory
50
+ * @returns {object} Generation result
51
+ */
52
+ async function generate(analysis, config, projectRoot) {
53
+ const result = {
54
+ success: false,
55
+ adapter: adapter.name,
56
+ files: [],
57
+ errors: []
58
+ };
59
+
60
+ try {
61
+ // 1. Generate AI_CONTEXT.md at project root
62
+ const outputPath = getOutputPath(projectRoot);
63
+
64
+ // Check if file exists and is custom (not managed by us)
65
+ if (fs.existsSync(outputPath) && !config.force) {
66
+ if (!isManagedFile(outputPath)) {
67
+ result.errors.push({
68
+ message: 'AI_CONTEXT.md exists and appears to be custom. Use --force to overwrite.',
69
+ code: 'EXISTS_CUSTOM',
70
+ severity: 'error'
71
+ });
72
+ // Don't return early - still try to generate .claude/ directory
73
+ } else {
74
+ // File is managed by us, safe to overwrite
75
+ const context = buildContext(analysis, config, 'claude');
76
+ const content = renderTemplateByName('claude', context);
77
+ fs.writeFileSync(outputPath, content, 'utf-8');
78
+ result.files.push({
79
+ path: outputPath,
80
+ relativePath: 'AI_CONTEXT.md',
81
+ size: content.length
82
+ });
83
+ }
84
+ } else {
85
+ // File doesn't exist or force is enabled
86
+ const context = buildContext(analysis, config, 'claude');
87
+ const content = renderTemplateByName('claude', context);
88
+ fs.writeFileSync(outputPath, content, 'utf-8');
89
+ result.files.push({
90
+ path: outputPath,
91
+ relativePath: 'AI_CONTEXT.md',
92
+ size: content.length
93
+ });
94
+ }
95
+
96
+ // 2. Generate .claude/ directory structure
97
+ const context = buildContext(analysis, config, 'claude');
98
+ const claudeDirResult = await generateClaudeDirectory(projectRoot, context, config, result);
99
+ if (claudeDirResult) {
100
+ result.files.push(...claudeDirResult);
101
+ }
102
+
103
+ // Success if no actual errors (warnings and info are OK)
104
+ result.success = result.errors.length === 0 ||
105
+ result.errors.every(e => e.code === 'EXISTS' || e.severity === 'info' || e.severity === 'warning');
106
+ } catch (error) {
107
+ result.errors.push({
108
+ message: error.message,
109
+ stack: error.stack
110
+ });
111
+ }
112
+
113
+ return result;
114
+ }
115
+
116
+ /**
117
+ * Generate .claude/ directory with symlinks to .ai-context/
118
+ * @param {string} projectRoot - Project root directory
119
+ * @param {object} context - Template context
120
+ * @param {object} config - Configuration from CLI
121
+ * @param {object} result - Result object to track files/errors
122
+ * @returns {Array} List of generated files
123
+ */
124
+ async function generateClaudeDirectory(projectRoot, context, config, result) {
125
+ const { copyDirectory } = require('../installer');
126
+ const templatesDir = path.join(__dirname, '..', '..', 'templates', 'base');
127
+ const aiContextDir = path.join(projectRoot, '.ai-context');
128
+ const claudeDir = path.join(projectRoot, '.claude');
129
+
130
+ // Check for existing .claude/ directory
131
+ if (fs.existsSync(claudeDir) && !config.force) {
132
+ // Check if it has custom files
133
+ const hasCustomFiles = checkForCustomFiles(claudeDir);
134
+ if (hasCustomFiles) {
135
+ // Migrate custom content before skipping
136
+ const customItems = findCustomContentInClaude(claudeDir);
137
+ if (customItems.length > 0) {
138
+ const migrated = migrateCustomContent(claudeDir, aiContextDir, customItems);
139
+ result.errors.push({
140
+ message: `Migrated ${migrated.length} custom items from .claude/ to .ai-context/custom/`,
141
+ code: 'MIGRATED_CUSTOM',
142
+ severity: 'info',
143
+ migratedItems: migrated
144
+ });
145
+ }
146
+
147
+ result.errors.push({
148
+ message: '.claude/ directory exists and contains custom files. Use --force to overwrite. Skipping directory generation.',
149
+ code: 'EXISTS_CUSTOM',
150
+ severity: 'warning'
151
+ });
152
+ return [{
153
+ path: claudeDir,
154
+ relativePath: '.claude/',
155
+ size: 0,
156
+ skipped: true
157
+ }];
158
+ }
159
+ // Directory exists but only has managed files, we can regenerate
160
+ }
161
+
162
+ try {
163
+ // Create .claude/ directory
164
+ fs.mkdirSync(claudeDir, { recursive: true });
165
+
166
+ // Subdirectories to symlink from .ai-context/
167
+ const subdirsToLink = [
168
+ 'agents',
169
+ 'commands',
170
+ 'indexes',
171
+ 'context',
172
+ 'schemas',
173
+ 'standards',
174
+ 'tools'
175
+ ];
176
+
177
+ let linksCreated = 0;
178
+ let filesCopied = 0;
179
+
180
+ for (const subdir of subdirsToLink) {
181
+ const srcPath = path.join(aiContextDir, subdir);
182
+ const destPath = path.join(claudeDir, subdir);
183
+
184
+ // Skip if source doesn't exist
185
+ if (!fs.existsSync(srcPath)) {
186
+ continue;
187
+ }
188
+
189
+ // Try to create symlink, fallback to copy
190
+ try {
191
+ // Remove dest if it exists (shouldn't, but safety)
192
+ if (fs.existsSync(destPath)) {
193
+ if (fs.lstatSync(destPath).isSymbolicLink()) {
194
+ fs.unlinkSync(destPath);
195
+ } else {
196
+ // Existing directory, skip
197
+ continue;
198
+ }
199
+ }
200
+
201
+ // Create symlink
202
+ fs.symlinkSync(srcPath, destPath, 'junction');
203
+ linksCreated++;
204
+ } catch (symlinkError) {
205
+ // Symlink failed (likely Windows permissions or filesystem limitation)
206
+ // Fallback: copy directory contents
207
+ if (!fs.existsSync(destPath)) {
208
+ fs.mkdirSync(destPath, { recursive: true });
209
+ const count = await copyDirectory(srcPath, destPath);
210
+ filesCopied += count;
211
+ }
212
+ }
213
+ }
214
+
215
+ // Create minimal .claude/settings.json
216
+ const settingsPath = path.join(claudeDir, 'settings.json');
217
+ const settings = {
218
+ '$schema': './schemas/settings.schema.json',
219
+ version: '2.2.2',
220
+ project: {
221
+ name: context.project?.name || 'Project',
222
+ tech_stack: context.project?.tech_stack || 'Not detected'
223
+ },
224
+ agents: {
225
+ context_engineer: 'enabled',
226
+ core_architect: 'enabled',
227
+ api_developer: 'enabled',
228
+ database_ops: 'enabled',
229
+ integration_hub: 'enabled',
230
+ deployment_ops: 'enabled'
231
+ },
232
+ commands: {
233
+ rpi_workflow: 'enabled',
234
+ validation: 'enabled'
235
+ }
236
+ };
237
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
238
+ filesCopied++;
239
+
240
+ // Create .claude/README.md
241
+ const readmePath = path.join(claudeDir, 'README.md');
242
+ const readme = `# .claude Configuration - ${context.project?.name || 'Project'}
243
+
244
+ This directory provides Claude Code with auto-discovered commands, agents, and configuration.
245
+
246
+ ## Architecture
247
+
248
+ This directory uses **symlinks** to \`../.ai-context/\` for all shared content:
249
+
250
+ \`\`\`
251
+ .claude/
252
+ ├── agents → ../.ai-context/agents/
253
+ ├── commands → ../.ai-context/commands/
254
+ ├── indexes → ../.ai-context/indexes/
255
+ ├── context → ../.ai-context/context/
256
+ ├── schemas → ../.ai-context/schemas/
257
+ ├── standards → ../.ai-context/standards/
258
+ ├── tools → ../.ai-context/tools/
259
+ ├── settings.json (this file - Claude-specific)
260
+ └── README.md (this file)
261
+ \`\`\`
262
+
263
+ **Single source of truth:** All content lives in \`.ai-context/\`. Edit there, not here.
264
+
265
+ ## Quick Start
266
+
267
+ 1. Load agents: \`@context-engineer\`
268
+ 2. Use commands: \`/rpi-research\`, \`/rpi-plan\`, \`/rpi-implement\`
269
+ 3. Validate: \`/verify-docs-current\`
270
+
271
+ ## Universal Context
272
+
273
+ See \`AI_CONTEXT.md\` at project root for universal AI context (works with all tools).
274
+
275
+ *Generated by create-universal-ai-context v${context.version || '2.3.0'}*
276
+ `;
277
+ fs.writeFileSync(readmePath, readme);
278
+ filesCopied++;
279
+
280
+ // Add info message about symlink approach
281
+ if (linksCreated > 0) {
282
+ result.errors.push({
283
+ message: `Created ${linksCreated} symlinks from .claude/ to .ai-context/ (single source of truth)`,
284
+ code: 'SYMLINKS_CREATED',
285
+ severity: 'info'
286
+ });
287
+ }
288
+
289
+ return [{
290
+ path: claudeDir,
291
+ relativePath: '.claude/',
292
+ size: filesCopied,
293
+ symlinks: linksCreated,
294
+ details: `${linksCreated} symlinks, ${filesCopied} files`
295
+ }];
296
+
297
+ } catch (error) {
298
+ result.errors.push({
299
+ message: `Failed to generate .claude/ directory: ${error.message}`,
300
+ stack: error.stack
301
+ });
302
+ return null;
303
+ }
304
+ }
305
+
306
+ /**
307
+ * Check if directory contains custom (non-managed) files
308
+ * @param {string} dir - Directory to check
309
+ * @returns {boolean} True if custom files found
310
+ */
311
+ function checkForCustomFiles(dir) {
312
+ const walkDir = (currentDir, depth = 0) => {
313
+ if (depth > 10) return false;
314
+
315
+ const entries = fs.readdirSync(currentDir, { withFileTypes: true });
316
+ for (const entry of entries) {
317
+ if (entry.isDirectory()) {
318
+ if (entry.name !== 'node_modules' && entry.name !== '.git') {
319
+ if (walkDir(path.join(currentDir, entry.name), depth + 1)) {
320
+ return true;
321
+ }
322
+ }
323
+ } else if (entry.name.endsWith('.md')) {
324
+ const filePath = path.join(currentDir, entry.name);
325
+ if (!isManagedFile(filePath)) {
326
+ return true;
327
+ }
328
+ }
329
+ }
330
+ return false;
331
+ };
332
+
333
+ return walkDir(dir);
334
+ }
335
+
336
+ /**
337
+ * Validate Claude output
338
+ * @param {string} projectRoot - Project root directory
339
+ * @returns {object} Validation result
340
+ */
341
+ function validate(projectRoot) {
342
+ const issues = [];
343
+
344
+ // 1. Validate AI_CONTEXT.md
345
+ const outputPath = getOutputPath(projectRoot);
346
+ if (!fs.existsSync(outputPath)) {
347
+ issues.push({ file: 'AI_CONTEXT.md', error: 'not found' });
348
+ } else {
349
+ const content = fs.readFileSync(outputPath, 'utf-8');
350
+ const placeholderMatch = content.match(/\{\{[A-Z_]+\}\}/g);
351
+ if (placeholderMatch && placeholderMatch.length > 0) {
352
+ issues.push({
353
+ file: 'AI_CONTEXT.md',
354
+ error: `Found ${placeholderMatch.length} unreplaced placeholders`
355
+ });
356
+ }
357
+ }
358
+
359
+ // 2. Validate .claude/ directory (optional, warn if missing)
360
+ const claudeDir = path.join(projectRoot, '.claude');
361
+ if (!fs.existsSync(claudeDir)) {
362
+ issues.push({
363
+ file: '.claude/',
364
+ error: 'directory not found (optional but recommended)',
365
+ severity: 'warning'
366
+ });
367
+ } else {
368
+ // Check for critical files
369
+ const criticalFiles = [
370
+ 'settings.json',
371
+ 'README.md'
372
+ ];
373
+ for (const file of criticalFiles) {
374
+ if (!fs.existsSync(path.join(claudeDir, file))) {
375
+ issues.push({
376
+ file: `.claude/${file}`,
377
+ error: 'missing',
378
+ severity: 'warning'
379
+ });
380
+ }
381
+ }
382
+ }
383
+
384
+ return {
385
+ valid: issues.filter(i => i.severity !== 'warning').length === 0,
386
+ issues,
387
+ warnings: issues.filter(i => i.severity === 'warning').length
388
+ };
389
+ }
390
+
391
+ module.exports = {
392
+ ...adapter,
393
+ getOutputPath,
394
+ exists,
395
+ generate,
396
+ validate
397
+ };