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.
- package/LICENSE +21 -21
- package/README.md +331 -294
- package/bin/create-ai-context.js +1507 -764
- package/lib/adapters/aider.js +131 -131
- package/lib/adapters/antigravity.js +205 -205
- package/lib/adapters/claude.js +397 -397
- package/lib/adapters/cline.js +125 -125
- package/lib/adapters/continue.js +138 -138
- package/lib/adapters/copilot.js +131 -131
- package/lib/adapters/index.js +78 -78
- package/lib/adapters/windsurf.js +138 -138
- package/lib/ai-context-generator.js +234 -234
- package/lib/ai-orchestrator.js +432 -432
- package/lib/call-tracer.js +444 -444
- package/lib/content-preservation.js +243 -243
- package/lib/cross-tool-sync/file-watcher.js +274 -274
- package/lib/cross-tool-sync/index.js +41 -40
- package/lib/cross-tool-sync/sync-manager.js +540 -512
- package/lib/cross-tool-sync/sync-service.js +297 -297
- package/lib/detector.js +726 -726
- package/lib/doc-discovery.js +741 -741
- package/lib/drift-checker.js +920 -920
- package/lib/environment-detector.js +239 -239
- package/lib/index.js +399 -399
- package/lib/install-hooks.js +82 -82
- package/lib/installer.js +419 -419
- package/lib/migrate.js +328 -328
- package/lib/placeholder.js +632 -632
- package/lib/prompts.js +341 -341
- package/lib/smart-merge.js +540 -540
- package/lib/spinner.js +60 -60
- package/lib/static-analyzer.js +729 -729
- package/lib/template-coordination.js +148 -148
- package/lib/template-populator.js +843 -843
- package/lib/template-renderer.js +392 -392
- package/lib/utils/fs-wrapper.js +79 -79
- package/lib/utils/path-utils.js +60 -60
- package/lib/validate.js +155 -155
- package/package.json +1 -1
- package/templates/AI_CONTEXT.md.template +245 -245
- package/templates/base/README.md +260 -257
- package/templates/base/RPI_WORKFLOW_PLAN.md +325 -320
- package/templates/base/agents/api-developer.md +76 -76
- package/templates/base/agents/context-engineer.md +525 -525
- package/templates/base/agents/core-architect.md +76 -76
- package/templates/base/agents/database-ops.md +76 -76
- package/templates/base/agents/deployment-ops.md +76 -76
- package/templates/base/agents/integration-hub.md +76 -76
- package/templates/base/analytics/README.md +114 -114
- package/templates/base/automation/config.json +58 -58
- package/templates/base/automation/generators/code-mapper.js +308 -308
- package/templates/base/automation/generators/index-builder.js +321 -321
- package/templates/base/automation/hooks/post-commit.sh +83 -83
- package/templates/base/automation/hooks/pre-commit.sh +103 -103
- package/templates/base/ci-templates/README.md +108 -108
- package/templates/base/ci-templates/github-actions/context-check.yml +144 -144
- package/templates/base/ci-templates/github-actions/validate-docs.yml +105 -105
- package/templates/base/commands/analytics.md +238 -238
- package/templates/base/commands/auto-sync.md +172 -172
- package/templates/base/commands/collab.md +194 -194
- package/templates/base/commands/context-optimize.md +226 -0
- package/templates/base/commands/help.md +485 -450
- package/templates/base/commands/rpi-implement.md +164 -115
- package/templates/base/commands/rpi-plan.md +147 -93
- package/templates/base/commands/rpi-research.md +145 -88
- package/templates/base/commands/session-resume.md +144 -144
- package/templates/base/commands/session-save.md +112 -112
- package/templates/base/commands/validate-all.md +77 -77
- package/templates/base/commands/verify-docs-current.md +86 -86
- package/templates/base/config/base.json +57 -57
- package/templates/base/config/environments/development.json +13 -13
- package/templates/base/config/environments/production.json +17 -17
- package/templates/base/config/environments/staging.json +13 -13
- package/templates/base/config/local.json.example +21 -21
- package/templates/base/context/.meta/generated-at.json +18 -18
- package/templates/base/context/ARCHITECTURE_SNAPSHOT.md +156 -156
- package/templates/base/context/CODE_TO_WORKFLOW_MAP.md +94 -94
- package/templates/base/context/FILE_OWNERSHIP.md +57 -57
- package/templates/base/context/INTEGRATION_POINTS.md +92 -92
- package/templates/base/context/KNOWN_GOTCHAS.md +195 -195
- package/templates/base/context/TESTING_MAP.md +95 -95
- package/templates/base/context/WORKFLOW_INDEX.md +129 -129
- package/templates/base/context/workflows/WORKFLOW_TEMPLATE.md +294 -294
- package/templates/base/indexes/agents/CAPABILITY_MATRIX.md +255 -255
- package/templates/base/indexes/agents/CATEGORY_INDEX.md +44 -44
- package/templates/base/indexes/code/CATEGORY_INDEX.md +38 -38
- package/templates/base/indexes/routing/CATEGORY_INDEX.md +39 -39
- package/templates/base/indexes/search/CATEGORY_INDEX.md +39 -39
- package/templates/base/indexes/workflows/CATEGORY_INDEX.md +38 -38
- package/templates/base/knowledge/README.md +98 -98
- package/templates/base/knowledge/sessions/README.md +88 -88
- package/templates/base/knowledge/sessions/TEMPLATE.md +150 -150
- package/templates/base/knowledge/shared/decisions/0001-adopt-context-engineering.md +144 -144
- package/templates/base/knowledge/shared/decisions/README.md +49 -49
- package/templates/base/knowledge/shared/decisions/TEMPLATE.md +123 -123
- package/templates/base/knowledge/shared/patterns/README.md +62 -62
- package/templates/base/knowledge/shared/patterns/TEMPLATE.md +120 -120
- package/templates/base/plans/PLAN_TEMPLATE.md +316 -250
- package/templates/base/research/RESEARCH_TEMPLATE.md +245 -153
- package/templates/base/schemas/agent.schema.json +141 -141
- package/templates/base/schemas/anchors.schema.json +54 -54
- package/templates/base/schemas/automation.schema.json +93 -93
- package/templates/base/schemas/command.schema.json +134 -134
- package/templates/base/schemas/hashes.schema.json +40 -40
- package/templates/base/schemas/manifest.schema.json +117 -117
- package/templates/base/schemas/plan.schema.json +136 -136
- package/templates/base/schemas/research.schema.json +115 -115
- package/templates/base/schemas/roles.schema.json +34 -34
- package/templates/base/schemas/session.schema.json +77 -77
- package/templates/base/schemas/settings.schema.json +244 -244
- package/templates/base/schemas/staleness.schema.json +53 -53
- package/templates/base/schemas/team-config.schema.json +42 -42
- package/templates/base/schemas/workflow.schema.json +126 -126
- package/templates/base/session/checkpoints/.gitkeep +2 -2
- package/templates/base/session/current/state.json +20 -20
- package/templates/base/session/history/.gitkeep +2 -2
- package/templates/base/settings.json +3 -3
- package/templates/base/standards/COMPATIBILITY.md +219 -219
- package/templates/base/standards/EXTENSION_GUIDELINES.md +280 -280
- package/templates/base/standards/QUALITY_CHECKLIST.md +211 -211
- package/templates/base/standards/README.md +66 -66
- package/templates/base/sync/anchors.json +6 -6
- package/templates/base/sync/hashes.json +6 -6
- package/templates/base/sync/staleness.json +10 -10
- package/templates/base/team/README.md +168 -168
- package/templates/base/team/config.json +79 -79
- package/templates/base/team/roles.json +145 -145
- package/templates/base/tools/bin/claude-context.js +151 -151
- package/templates/base/tools/lib/anchor-resolver.js +276 -276
- package/templates/base/tools/lib/config-loader.js +363 -363
- package/templates/base/tools/lib/detector.js +350 -350
- package/templates/base/tools/lib/diagnose.js +206 -206
- package/templates/base/tools/lib/drift-detector.js +373 -373
- package/templates/base/tools/lib/errors.js +199 -199
- package/templates/base/tools/lib/index.js +36 -36
- package/templates/base/tools/lib/init.js +192 -192
- package/templates/base/tools/lib/logger.js +230 -230
- package/templates/base/tools/lib/placeholder.js +201 -201
- package/templates/base/tools/lib/session-manager.js +354 -354
- package/templates/base/tools/lib/validate.js +521 -521
- package/templates/base/tools/package.json +49 -49
- package/templates/handlebars/aider-config.hbs +146 -80
- package/templates/handlebars/antigravity.hbs +377 -377
- package/templates/handlebars/claude.hbs +183 -183
- package/templates/handlebars/cline.hbs +62 -62
- package/templates/handlebars/continue-config.hbs +116 -116
- package/templates/handlebars/copilot.hbs +130 -130
- package/templates/handlebars/partials/gotcha-list.hbs +11 -11
- package/templates/handlebars/partials/header.hbs +3 -3
- package/templates/handlebars/partials/workflow-summary.hbs +16 -16
- package/templates/handlebars/windsurf-rules.hbs +69 -69
- package/templates/hooks/post-commit.hbs +28 -29
- package/templates/hooks/pre-commit.hbs +46 -46
package/lib/adapters/claude.js
CHANGED
|
@@ -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
|
+
};
|