@polymorphism-tech/morph-spec 3.1.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +534 -0
- package/README.md +78 -4
- package/bin/morph-spec.js +50 -1
- package/bin/render-template.js +56 -10
- package/bin/task-manager.cjs +101 -7
- package/docs/cli-auto-detection.md +219 -0
- package/docs/llm-interaction-config.md +735 -0
- package/docs/troubleshooting.md +269 -0
- package/package.json +5 -1
- package/src/commands/advance-phase.js +93 -2
- package/src/commands/approve.js +221 -0
- package/src/commands/capture-pattern.js +121 -0
- package/src/commands/generate.js +128 -1
- package/src/commands/init.js +37 -0
- package/src/commands/migrate-state.js +158 -0
- package/src/commands/search-patterns.js +126 -0
- package/src/commands/spawn-team.js +172 -0
- package/src/commands/task.js +2 -2
- package/src/commands/update.js +36 -0
- package/src/commands/upgrade.js +346 -0
- package/src/generator/.gitkeep +0 -0
- package/src/generator/config-generator.js +206 -0
- package/src/generator/templates/config.json.template +40 -0
- package/src/generator/templates/project.md.template +67 -0
- package/src/lib/checkpoint-hooks.js +258 -0
- package/src/lib/metadata-extractor.js +380 -0
- package/src/lib/phase-state-machine.js +214 -0
- package/src/lib/state-manager.js +120 -0
- package/src/lib/template-data-sources.js +325 -0
- package/src/lib/validators/content-validator.js +351 -0
- package/src/llm/.gitkeep +0 -0
- package/src/llm/analyzer.js +215 -0
- package/src/llm/environment-detector.js +43 -0
- package/src/llm/few-shot-examples.js +216 -0
- package/src/llm/project-config-schema.json +188 -0
- package/src/llm/prompt-builder.js +96 -0
- package/src/llm/schema-validator.js +121 -0
- package/src/orchestrator.js +206 -0
- package/src/sanitizer/.gitkeep +0 -0
- package/src/sanitizer/context-sanitizer.js +221 -0
- package/src/sanitizer/patterns.js +163 -0
- package/src/scanner/.gitkeep +0 -0
- package/src/scanner/project-scanner.js +242 -0
- package/src/types/index.js +477 -0
- package/src/ui/.gitkeep +0 -0
- package/src/ui/diff-display.js +91 -0
- package/src/ui/interactive-wizard.js +96 -0
- package/src/ui/user-review.js +211 -0
- package/src/ui/wizard-questions.js +190 -0
- package/src/writer/.gitkeep +0 -0
- package/src/writer/file-writer.js +86 -0
package/src/lib/state-manager.js
CHANGED
|
@@ -139,6 +139,12 @@ function ensureFeature(featureName) {
|
|
|
139
139
|
createdAt: new Date().toISOString(),
|
|
140
140
|
updatedAt: new Date().toISOString(),
|
|
141
141
|
activeAgents: [],
|
|
142
|
+
approvalGates: {
|
|
143
|
+
proposal: { approved: false, timestamp: null, approvedBy: null },
|
|
144
|
+
uiux: { approved: false, timestamp: null, approvedBy: null },
|
|
145
|
+
design: { approved: false, timestamp: null, approvedBy: null },
|
|
146
|
+
tasks: { approved: false, timestamp: null, approvedBy: null }
|
|
147
|
+
},
|
|
142
148
|
outputs: {
|
|
143
149
|
proposal: { created: false, path: `.morph/project/outputs/${featureName}/proposal.md` },
|
|
144
150
|
spec: { created: false, path: `.morph/project/outputs/${featureName}/spec.md` },
|
|
@@ -412,3 +418,117 @@ export function getSummary() {
|
|
|
412
418
|
featuresCount: Object.keys(state.features).length
|
|
413
419
|
};
|
|
414
420
|
}
|
|
421
|
+
|
|
422
|
+
// ============================================================================
|
|
423
|
+
// Approval Gates Operations
|
|
424
|
+
// ============================================================================
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Set approval gate status
|
|
428
|
+
* @param {string} featureName - Feature name
|
|
429
|
+
* @param {string} gate - Gate name (proposal, uiux, design, tasks)
|
|
430
|
+
* @param {boolean} approved - Approval status
|
|
431
|
+
* @param {Object} metadata - Additional metadata (approvedBy, reason, etc.)
|
|
432
|
+
*/
|
|
433
|
+
export function setApprovalGate(featureName, gate, approved, metadata = {}) {
|
|
434
|
+
const state = loadState();
|
|
435
|
+
const feature = ensureFeature(featureName);
|
|
436
|
+
|
|
437
|
+
if (!feature.approvalGates) {
|
|
438
|
+
feature.approvalGates = {
|
|
439
|
+
proposal: { approved: false, timestamp: null, approvedBy: null },
|
|
440
|
+
uiux: { approved: false, timestamp: null, approvedBy: null },
|
|
441
|
+
design: { approved: false, timestamp: null, approvedBy: null },
|
|
442
|
+
tasks: { approved: false, timestamp: null, approvedBy: null }
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
feature.approvalGates[gate] = {
|
|
447
|
+
approved,
|
|
448
|
+
timestamp: metadata.approvedAt || metadata.rejectedAt || new Date().toISOString(),
|
|
449
|
+
approvedBy: metadata.approvedBy || metadata.rejectedBy || null,
|
|
450
|
+
...metadata
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
state.features[featureName] = feature;
|
|
454
|
+
saveState(state);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Get approval gate status
|
|
459
|
+
* @param {string} featureName - Feature name
|
|
460
|
+
* @param {string} gate - Gate name
|
|
461
|
+
* @returns {Object|null} Gate object or null
|
|
462
|
+
*/
|
|
463
|
+
export function getApprovalGate(featureName, gate) {
|
|
464
|
+
const state = loadState();
|
|
465
|
+
const feature = state.features[featureName];
|
|
466
|
+
|
|
467
|
+
if (!feature || !feature.approvalGates) {
|
|
468
|
+
return null;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return feature.approvalGates[gate] || null;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Check if feature is pending approval
|
|
476
|
+
* @param {string} featureName - Feature name
|
|
477
|
+
* @returns {boolean} True if any gate is pending approval
|
|
478
|
+
*/
|
|
479
|
+
export function isPendingApproval(featureName) {
|
|
480
|
+
const state = loadState();
|
|
481
|
+
const feature = state.features[featureName];
|
|
482
|
+
|
|
483
|
+
if (!feature || !feature.approvalGates) {
|
|
484
|
+
return false;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const currentPhase = feature.phase;
|
|
488
|
+
|
|
489
|
+
// Check if current phase has an approval gate
|
|
490
|
+
const phaseGateMap = {
|
|
491
|
+
'design': 'design',
|
|
492
|
+
'tasks': 'tasks',
|
|
493
|
+
'uiux': 'uiux'
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
const relevantGate = phaseGateMap[currentPhase];
|
|
497
|
+
if (!relevantGate) {
|
|
498
|
+
return false;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
const gate = feature.approvalGates[relevantGate];
|
|
502
|
+
return gate && !gate.approved;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Get approval history for a feature
|
|
507
|
+
* @param {string} featureName - Feature name
|
|
508
|
+
* @returns {Array} Array of approval events
|
|
509
|
+
*/
|
|
510
|
+
export function getApprovalHistory(featureName) {
|
|
511
|
+
const state = loadState();
|
|
512
|
+
const feature = state.features[featureName];
|
|
513
|
+
|
|
514
|
+
if (!feature || !feature.approvalGates) {
|
|
515
|
+
return [];
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
const history = [];
|
|
519
|
+
|
|
520
|
+
Object.entries(feature.approvalGates).forEach(([gate, data]) => {
|
|
521
|
+
if (data.timestamp) {
|
|
522
|
+
history.push({
|
|
523
|
+
gate,
|
|
524
|
+
approved: data.approved,
|
|
525
|
+
timestamp: data.timestamp,
|
|
526
|
+
approvedBy: data.approvedBy || data.rejectedBy,
|
|
527
|
+
reason: data.reason
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
// Sort by timestamp
|
|
533
|
+
return history.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));
|
|
534
|
+
}
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import { glob } from 'glob';
|
|
2
|
+
import { readFileSync, existsSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { execSync } from 'child_process';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Template Data Sources - Inject dynamic MCP data into templates
|
|
8
|
+
*
|
|
9
|
+
* Provides project context, compliance status, and recent activity
|
|
10
|
+
* for enriching template placeholders.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Get project structure statistics
|
|
15
|
+
* @param {string} projectPath - Project root path
|
|
16
|
+
* @returns {Promise<Object>} Project structure data
|
|
17
|
+
*/
|
|
18
|
+
export async function getProjectStructure(projectPath = process.cwd()) {
|
|
19
|
+
try {
|
|
20
|
+
// Find all files (excluding common ignore patterns)
|
|
21
|
+
const files = await glob('**/*', {
|
|
22
|
+
cwd: projectPath,
|
|
23
|
+
ignore: [
|
|
24
|
+
'node_modules/**',
|
|
25
|
+
'bin/**',
|
|
26
|
+
'obj/**',
|
|
27
|
+
'.git/**',
|
|
28
|
+
'.morph/**',
|
|
29
|
+
'dist/**',
|
|
30
|
+
'build/**',
|
|
31
|
+
'*.log'
|
|
32
|
+
],
|
|
33
|
+
nodir: true
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Calculate language stats
|
|
37
|
+
const languageStats = {};
|
|
38
|
+
files.forEach(file => {
|
|
39
|
+
const ext = file.split('.').pop();
|
|
40
|
+
languageStats[ext] = (languageStats[ext] || 0) + 1;
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Get test coverage if available
|
|
44
|
+
let testCoverage = null;
|
|
45
|
+
const coveragePath = join(projectPath, 'coverage/coverage-summary.json');
|
|
46
|
+
if (existsSync(coveragePath)) {
|
|
47
|
+
const coverageData = JSON.parse(readFileSync(coveragePath, 'utf8'));
|
|
48
|
+
testCoverage = coverageData.total?.lines?.pct || null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Get last commit info
|
|
52
|
+
let lastCommit = null;
|
|
53
|
+
try {
|
|
54
|
+
const gitLog = execSync('git log -1 --format="%H|%an|%ar|%s"', {
|
|
55
|
+
cwd: projectPath,
|
|
56
|
+
encoding: 'utf8',
|
|
57
|
+
stdio: 'pipe'
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const [hash, author, time, message] = gitLog.trim().split('|');
|
|
61
|
+
lastCommit = { hash: hash.substring(0, 7), author, time, message };
|
|
62
|
+
} catch {
|
|
63
|
+
// Git not available or not a repo
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
totalFiles: files.length,
|
|
68
|
+
languageStats,
|
|
69
|
+
testCoverage: testCoverage ? Math.round(testCoverage) : null,
|
|
70
|
+
lastCommit,
|
|
71
|
+
filesByExtension: {
|
|
72
|
+
cs: languageStats.cs || 0,
|
|
73
|
+
js: languageStats.js || 0,
|
|
74
|
+
ts: languageStats.ts || 0,
|
|
75
|
+
tsx: languageStats.tsx || 0,
|
|
76
|
+
razor: languageStats.razor || 0,
|
|
77
|
+
css: languageStats.css || 0,
|
|
78
|
+
md: languageStats.md || 0
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
} catch (error) {
|
|
82
|
+
return {
|
|
83
|
+
error: error.message,
|
|
84
|
+
totalFiles: 0,
|
|
85
|
+
testCoverage: null
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get dependency information
|
|
92
|
+
* @param {string} projectPath - Project root path
|
|
93
|
+
* @returns {Promise<Object>} Dependency data
|
|
94
|
+
*/
|
|
95
|
+
export async function getDependencyInfo(projectPath = process.cwd()) {
|
|
96
|
+
const dependencies = {
|
|
97
|
+
nuget: [],
|
|
98
|
+
npm: [],
|
|
99
|
+
outdated: []
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
// Check for NuGet packages (C# projects)
|
|
104
|
+
const csprojFiles = await glob('**/*.csproj', {
|
|
105
|
+
cwd: projectPath,
|
|
106
|
+
ignore: ['**/bin/**', '**/obj/**']
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
if (csprojFiles.length > 0) {
|
|
110
|
+
const csprojPath = join(projectPath, csprojFiles[0]);
|
|
111
|
+
const csprojContent = readFileSync(csprojPath, 'utf8');
|
|
112
|
+
|
|
113
|
+
// Extract PackageReference elements
|
|
114
|
+
const packageRegex = /<PackageReference\s+Include="([^"]+)"\s+Version="([^"]+)"/g;
|
|
115
|
+
let match;
|
|
116
|
+
|
|
117
|
+
while ((match = packageRegex.exec(csprojContent)) !== null) {
|
|
118
|
+
dependencies.nuget.push({
|
|
119
|
+
name: match[1],
|
|
120
|
+
version: match[2]
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Check for npm packages
|
|
126
|
+
const packageJsonPath = join(projectPath, 'package.json');
|
|
127
|
+
if (existsSync(packageJsonPath)) {
|
|
128
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
129
|
+
|
|
130
|
+
const allDeps = {
|
|
131
|
+
...packageJson.dependencies,
|
|
132
|
+
...packageJson.devDependencies
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
Object.entries(allDeps).forEach(([name, version]) => {
|
|
136
|
+
dependencies.npm.push({ name, version });
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
} catch (error) {
|
|
141
|
+
dependencies.error = error.message;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return dependencies;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Get compliance status from validators
|
|
149
|
+
* @param {string} projectPath - Project root path
|
|
150
|
+
* @returns {Promise<Object>} Compliance data
|
|
151
|
+
*/
|
|
152
|
+
export async function getComplianceStatus(projectPath = process.cwd()) {
|
|
153
|
+
const compliance = {
|
|
154
|
+
architectureViolations: 0,
|
|
155
|
+
packageConflicts: 0,
|
|
156
|
+
designSystemCompliance: 100,
|
|
157
|
+
securityIssues: 0,
|
|
158
|
+
overall: 100
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
// Run validators if available
|
|
163
|
+
const validators = ['architecture', 'packages', 'design-system', 'security'];
|
|
164
|
+
|
|
165
|
+
for (const validator of validators) {
|
|
166
|
+
try {
|
|
167
|
+
const result = execSync(
|
|
168
|
+
`node bin/validate.js ${validator} --json`,
|
|
169
|
+
{
|
|
170
|
+
cwd: projectPath,
|
|
171
|
+
encoding: 'utf8',
|
|
172
|
+
stdio: 'pipe'
|
|
173
|
+
}
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
const parsed = JSON.parse(result);
|
|
177
|
+
|
|
178
|
+
if (validator === 'architecture') {
|
|
179
|
+
compliance.architectureViolations = parsed.errors || 0;
|
|
180
|
+
} else if (validator === 'packages') {
|
|
181
|
+
compliance.packageConflicts = parsed.errors || 0;
|
|
182
|
+
} else if (validator === 'design-system') {
|
|
183
|
+
const total = parsed.total || 100;
|
|
184
|
+
const errors = parsed.errors || 0;
|
|
185
|
+
compliance.designSystemCompliance = Math.round(((total - errors) / total) * 100);
|
|
186
|
+
} else if (validator === 'security') {
|
|
187
|
+
compliance.securityIssues = parsed.errors || 0;
|
|
188
|
+
}
|
|
189
|
+
} catch {
|
|
190
|
+
// Validator not available or failed - skip
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Calculate overall compliance score
|
|
195
|
+
const violations = compliance.architectureViolations +
|
|
196
|
+
compliance.packageConflicts +
|
|
197
|
+
compliance.securityIssues;
|
|
198
|
+
|
|
199
|
+
compliance.overall = Math.max(0, 100 - (violations * 5));
|
|
200
|
+
|
|
201
|
+
} catch (error) {
|
|
202
|
+
compliance.error = error.message;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return compliance;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Get recent activity and history
|
|
210
|
+
* @param {string} projectPath - Project root path
|
|
211
|
+
* @returns {Promise<Object>} Activity data
|
|
212
|
+
*/
|
|
213
|
+
export async function getRecentActivity(projectPath = process.cwd()) {
|
|
214
|
+
const activity = {
|
|
215
|
+
lastFeature: null,
|
|
216
|
+
recentCommits: [],
|
|
217
|
+
activeBranches: [],
|
|
218
|
+
lastDeployment: null
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
try {
|
|
222
|
+
// Check state.json for last feature
|
|
223
|
+
const statePath = join(projectPath, '.morph/state.json');
|
|
224
|
+
if (existsSync(statePath)) {
|
|
225
|
+
const state = JSON.parse(readFileSync(statePath, 'utf8'));
|
|
226
|
+
|
|
227
|
+
// Find most recently updated feature
|
|
228
|
+
let lastFeature = null;
|
|
229
|
+
let lastTimestamp = null;
|
|
230
|
+
|
|
231
|
+
Object.entries(state.features || {}).forEach(([name, feature]) => {
|
|
232
|
+
const updatedAt = feature.updatedAt || feature.createdAt;
|
|
233
|
+
if (updatedAt && (!lastTimestamp || updatedAt > lastTimestamp)) {
|
|
234
|
+
lastTimestamp = updatedAt;
|
|
235
|
+
lastFeature = name;
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
activity.lastFeature = lastFeature;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Get recent commits
|
|
243
|
+
try {
|
|
244
|
+
const gitLog = execSync('git log -5 --format="%h|%an|%ar|%s"', {
|
|
245
|
+
cwd: projectPath,
|
|
246
|
+
encoding: 'utf8',
|
|
247
|
+
stdio: 'pipe'
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
activity.recentCommits = gitLog.trim().split('\n').map(line => {
|
|
251
|
+
const [hash, author, time, message] = line.split('|');
|
|
252
|
+
return { hash, author, time, message };
|
|
253
|
+
});
|
|
254
|
+
} catch {
|
|
255
|
+
// Git not available
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Get active branches
|
|
259
|
+
try {
|
|
260
|
+
const branches = execSync('git branch -a', {
|
|
261
|
+
cwd: projectPath,
|
|
262
|
+
encoding: 'utf8',
|
|
263
|
+
stdio: 'pipe'
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
activity.activeBranches = branches
|
|
267
|
+
.split('\n')
|
|
268
|
+
.map(b => b.trim().replace(/^\*\s+/, ''))
|
|
269
|
+
.filter(b => b && !b.includes('->'));
|
|
270
|
+
} catch {
|
|
271
|
+
// Git not available
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
} catch (error) {
|
|
275
|
+
activity.error = error.message;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return activity;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Get all template data sources combined
|
|
283
|
+
* @param {string} projectPath - Project root path
|
|
284
|
+
* @returns {Promise<Object>} All data sources
|
|
285
|
+
*/
|
|
286
|
+
export async function getAllTemplatePlaceholders(projectPath = process.cwd()) {
|
|
287
|
+
const [structure, dependencies, compliance, activity] = await Promise.all([
|
|
288
|
+
getProjectStructure(projectPath),
|
|
289
|
+
getDependencyInfo(projectPath),
|
|
290
|
+
getComplianceStatus(projectPath),
|
|
291
|
+
getRecentActivity(projectPath)
|
|
292
|
+
]);
|
|
293
|
+
|
|
294
|
+
return {
|
|
295
|
+
MCP_PROJECT_FILES: structure.totalFiles,
|
|
296
|
+
MCP_TEST_COVERAGE: structure.testCoverage || 'N/A',
|
|
297
|
+
MCP_COMPLIANCE_SCORE: compliance.overall,
|
|
298
|
+
MCP_LAST_FEATURE: activity.lastFeature || 'None',
|
|
299
|
+
MCP_LAST_COMMIT: structure.lastCommit?.message || 'Unknown',
|
|
300
|
+
MCP_LAST_COMMIT_AUTHOR: structure.lastCommit?.author || 'Unknown',
|
|
301
|
+
MCP_LAST_COMMIT_TIME: structure.lastCommit?.time || 'Unknown',
|
|
302
|
+
|
|
303
|
+
MCP_CS_FILES: structure.filesByExtension?.cs || 0,
|
|
304
|
+
MCP_RAZOR_FILES: structure.filesByExtension?.razor || 0,
|
|
305
|
+
MCP_TS_FILES: structure.filesByExtension?.ts || 0,
|
|
306
|
+
MCP_JS_FILES: structure.filesByExtension?.js || 0,
|
|
307
|
+
|
|
308
|
+
MCP_NUGET_PACKAGES: dependencies.nuget.length,
|
|
309
|
+
MCP_NPM_PACKAGES: dependencies.npm.length,
|
|
310
|
+
|
|
311
|
+
MCP_ARCHITECTURE_VIOLATIONS: compliance.architectureViolations,
|
|
312
|
+
MCP_PACKAGE_CONFLICTS: compliance.packageConflicts,
|
|
313
|
+
MCP_SECURITY_ISSUES: compliance.securityIssues,
|
|
314
|
+
|
|
315
|
+
MCP_RECENT_BRANCHES: activity.activeBranches.slice(0, 3).join(', '),
|
|
316
|
+
|
|
317
|
+
// Full objects for advanced usage
|
|
318
|
+
_raw: {
|
|
319
|
+
structure,
|
|
320
|
+
dependencies,
|
|
321
|
+
compliance,
|
|
322
|
+
activity
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
}
|