@corbat-tech/coding-standards-mcp 1.0.2 → 1.1.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/README.md +170 -269
- package/assets/demo.gif +0 -0
- package/assets/demo.tape +63 -0
- package/dist/agent.d.ts +5 -6
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +95 -217
- package/dist/agent.js.map +1 -1
- package/dist/cli/init.js +1 -1
- package/dist/cli/init.js.map +1 -1
- package/dist/config.d.ts +15 -26
- package/dist/config.d.ts.map +1 -1
- package/dist/errors.d.ts +58 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +112 -0
- package/dist/errors.js.map +1 -0
- package/dist/guardrails.d.ts +35 -0
- package/dist/guardrails.d.ts.map +1 -0
- package/dist/guardrails.js +303 -0
- package/dist/guardrails.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +36 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +63 -0
- package/dist/logger.js.map +1 -0
- package/dist/metrics.d.ts +40 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +97 -0
- package/dist/metrics.js.map +1 -0
- package/dist/profiles.d.ts +1 -1
- package/dist/profiles.d.ts.map +1 -1
- package/dist/profiles.js +240 -109
- package/dist/profiles.js.map +1 -1
- package/dist/prompts.js +1 -1
- package/dist/prompts.js.map +1 -1
- package/dist/tools/definitions.d.ts +104 -0
- package/dist/tools/definitions.d.ts.map +1 -0
- package/dist/tools/definitions.js +171 -0
- package/dist/tools/definitions.js.map +1 -0
- package/dist/tools/handlers/get-context.d.ts +12 -0
- package/dist/tools/handlers/get-context.d.ts.map +1 -0
- package/dist/tools/handlers/get-context.js +118 -0
- package/dist/tools/handlers/get-context.js.map +1 -0
- package/dist/tools/handlers/health.d.ts +11 -0
- package/dist/tools/handlers/health.d.ts.map +1 -0
- package/dist/tools/handlers/health.js +57 -0
- package/dist/tools/handlers/health.js.map +1 -0
- package/dist/tools/handlers/index.d.ts +11 -0
- package/dist/tools/handlers/index.d.ts.map +1 -0
- package/dist/tools/handlers/index.js +11 -0
- package/dist/tools/handlers/index.js.map +1 -0
- package/dist/tools/handlers/init.d.ts +12 -0
- package/dist/tools/handlers/init.d.ts.map +1 -0
- package/dist/tools/handlers/init.js +102 -0
- package/dist/tools/handlers/init.js.map +1 -0
- package/dist/tools/handlers/profiles.d.ts +11 -0
- package/dist/tools/handlers/profiles.d.ts.map +1 -0
- package/dist/tools/handlers/profiles.js +25 -0
- package/dist/tools/handlers/profiles.js.map +1 -0
- package/dist/tools/handlers/search.d.ts +12 -0
- package/dist/tools/handlers/search.d.ts.map +1 -0
- package/dist/tools/handlers/search.js +58 -0
- package/dist/tools/handlers/search.js.map +1 -0
- package/dist/tools/handlers/validate.d.ts +12 -0
- package/dist/tools/handlers/validate.d.ts.map +1 -0
- package/dist/tools/handlers/validate.js +76 -0
- package/dist/tools/handlers/validate.js.map +1 -0
- package/dist/tools/index.d.ts +22 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +72 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/schemas.d.ts +29 -0
- package/dist/tools/schemas.d.ts.map +1 -0
- package/dist/tools/schemas.js +20 -0
- package/dist/tools/schemas.js.map +1 -0
- package/dist/tools.js +2 -2
- package/dist/tools.js.map +1 -1
- package/dist/types.d.ts +417 -2588
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +99 -47
- package/dist/types.js.map +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/retry.d.ts.map +1 -1
- package/dist/utils/retry.js +3 -2
- package/dist/utils/retry.js.map +1 -1
- package/package.json +10 -9
- package/profiles/examples/microservice-kafka.yaml +122 -0
- package/profiles/examples/startup-fast.yaml +67 -0
- package/profiles/examples/strict-enterprise.yaml +62 -0
- package/profiles/templates/angular.yaml +614 -0
- package/profiles/templates/csharp-dotnet.yaml +529 -0
- package/profiles/templates/flutter.yaml +547 -0
- package/profiles/templates/go.yaml +1276 -0
- package/profiles/templates/java-spring-backend.yaml +326 -0
- package/profiles/templates/kotlin-spring.yaml +417 -0
- package/profiles/templates/nextjs.yaml +536 -0
- package/profiles/templates/nodejs.yaml +594 -0
- package/profiles/templates/python.yaml +546 -0
- package/profiles/templates/react.yaml +456 -0
- package/profiles/templates/rust.yaml +508 -0
- package/profiles/templates/vue.yaml +483 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple in-memory metrics for Corbat MCP.
|
|
3
|
+
* Tracks tool calls, profiles used, and errors.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Record a tool call.
|
|
7
|
+
*/
|
|
8
|
+
export declare function recordToolCall(toolName: string): void;
|
|
9
|
+
/**
|
|
10
|
+
* Record a profile being used.
|
|
11
|
+
*/
|
|
12
|
+
export declare function recordProfileUsed(profileId: string): void;
|
|
13
|
+
/**
|
|
14
|
+
* Record a task type being processed.
|
|
15
|
+
*/
|
|
16
|
+
export declare function recordTaskType(taskType: string): void;
|
|
17
|
+
/**
|
|
18
|
+
* Record an error.
|
|
19
|
+
*/
|
|
20
|
+
export declare function recordError(): void;
|
|
21
|
+
/**
|
|
22
|
+
* Get current metrics with computed values.
|
|
23
|
+
*/
|
|
24
|
+
export declare function getMetrics(): {
|
|
25
|
+
toolCalls: Record<string, number>;
|
|
26
|
+
profilesUsed: Record<string, number>;
|
|
27
|
+
taskTypes: Record<string, number>;
|
|
28
|
+
errors: number;
|
|
29
|
+
uptimeMs: number;
|
|
30
|
+
uptimeFormatted: string;
|
|
31
|
+
totalToolCalls: number;
|
|
32
|
+
mostUsedTool: string | null;
|
|
33
|
+
mostUsedProfile: string | null;
|
|
34
|
+
mostCommonTaskType: string | null;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Reset all metrics (useful for testing).
|
|
38
|
+
*/
|
|
39
|
+
export declare function resetMetrics(): void;
|
|
40
|
+
//# sourceMappingURL=metrics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../src/metrics.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAqBH;;GAEG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAErD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAEzD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAErD;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,IAAI,CAElC;AAoCD;;GAEG;AACH,wBAAgB,UAAU,IAAI;IAC5B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC,CAgBA;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,IAAI,CAMnC"}
|
package/dist/metrics.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple in-memory metrics for Corbat MCP.
|
|
3
|
+
* Tracks tool calls, profiles used, and errors.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Global metrics state.
|
|
7
|
+
*/
|
|
8
|
+
const metrics = {
|
|
9
|
+
toolCalls: {},
|
|
10
|
+
profilesUsed: {},
|
|
11
|
+
taskTypes: {},
|
|
12
|
+
errors: 0,
|
|
13
|
+
startTime: Date.now(),
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Record a tool call.
|
|
17
|
+
*/
|
|
18
|
+
export function recordToolCall(toolName) {
|
|
19
|
+
metrics.toolCalls[toolName] = (metrics.toolCalls[toolName] || 0) + 1;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Record a profile being used.
|
|
23
|
+
*/
|
|
24
|
+
export function recordProfileUsed(profileId) {
|
|
25
|
+
metrics.profilesUsed[profileId] = (metrics.profilesUsed[profileId] || 0) + 1;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Record a task type being processed.
|
|
29
|
+
*/
|
|
30
|
+
export function recordTaskType(taskType) {
|
|
31
|
+
metrics.taskTypes[taskType] = (metrics.taskTypes[taskType] || 0) + 1;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Record an error.
|
|
35
|
+
*/
|
|
36
|
+
export function recordError() {
|
|
37
|
+
metrics.errors++;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Get the most used item from a record.
|
|
41
|
+
*/
|
|
42
|
+
function getMostUsed(record) {
|
|
43
|
+
let maxKey = null;
|
|
44
|
+
let maxValue = 0;
|
|
45
|
+
for (const [key, value] of Object.entries(record)) {
|
|
46
|
+
if (value > maxValue) {
|
|
47
|
+
maxValue = value;
|
|
48
|
+
maxKey = key;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return maxKey;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Format duration in human-readable format.
|
|
55
|
+
*/
|
|
56
|
+
function formatDuration(ms) {
|
|
57
|
+
const seconds = Math.floor(ms / 1000);
|
|
58
|
+
const minutes = Math.floor(seconds / 60);
|
|
59
|
+
const hours = Math.floor(minutes / 60);
|
|
60
|
+
if (hours > 0) {
|
|
61
|
+
return `${hours}h ${minutes % 60}m`;
|
|
62
|
+
}
|
|
63
|
+
if (minutes > 0) {
|
|
64
|
+
return `${minutes}m ${seconds % 60}s`;
|
|
65
|
+
}
|
|
66
|
+
return `${seconds}s`;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Get current metrics with computed values.
|
|
70
|
+
*/
|
|
71
|
+
export function getMetrics() {
|
|
72
|
+
const uptimeMs = Date.now() - metrics.startTime;
|
|
73
|
+
const totalToolCalls = Object.values(metrics.toolCalls).reduce((a, b) => a + b, 0);
|
|
74
|
+
return {
|
|
75
|
+
toolCalls: { ...metrics.toolCalls },
|
|
76
|
+
profilesUsed: { ...metrics.profilesUsed },
|
|
77
|
+
taskTypes: { ...metrics.taskTypes },
|
|
78
|
+
errors: metrics.errors,
|
|
79
|
+
uptimeMs,
|
|
80
|
+
uptimeFormatted: formatDuration(uptimeMs),
|
|
81
|
+
totalToolCalls,
|
|
82
|
+
mostUsedTool: getMostUsed(metrics.toolCalls),
|
|
83
|
+
mostUsedProfile: getMostUsed(metrics.profilesUsed),
|
|
84
|
+
mostCommonTaskType: getMostUsed(metrics.taskTypes),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Reset all metrics (useful for testing).
|
|
89
|
+
*/
|
|
90
|
+
export function resetMetrics() {
|
|
91
|
+
metrics.toolCalls = {};
|
|
92
|
+
metrics.profilesUsed = {};
|
|
93
|
+
metrics.taskTypes = {};
|
|
94
|
+
metrics.errors = 0;
|
|
95
|
+
metrics.startTime = Date.now();
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=metrics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics.js","sourceRoot":"","sources":["../src/metrics.ts"],"names":[],"mappings":"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"}
|
package/dist/profiles.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ export declare function listProfiles(): Promise<Array<{
|
|
|
21
21
|
profile: Profile;
|
|
22
22
|
}>>;
|
|
23
23
|
/**
|
|
24
|
-
* Load all standards from markdown files.
|
|
24
|
+
* Load all standards from markdown files (parallelized).
|
|
25
25
|
*/
|
|
26
26
|
export declare function loadStandards(): Promise<StandardDocument[]>;
|
|
27
27
|
/**
|
package/dist/profiles.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"profiles.d.ts","sourceRoot":"","sources":["../src/profiles.ts"],"names":[],"mappings":"AAIA,OAAO,EAA4B,KAAK,OAAO,EAAiB,KAAK,gBAAgB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"profiles.d.ts","sourceRoot":"","sources":["../src/profiles.ts"],"names":[],"mappings":"AAIA,OAAO,EAA4B,KAAK,OAAO,EAAiB,KAAK,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAuG1G;;GAEG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAKtC;AAqDD;;;;GAIG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CA2ClE;AAED;;GAEG;AACH,wBAAsB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAG3E;AAED;;GAEG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC,CAGrF;AAED;;GAEG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAgBjE;AAwDD;;GAEG;AACH,wBAAsB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAG1F;AAED;;GAEG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAIvD;AAiaD;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,MAAM,CAqBpF"}
|
package/dist/profiles.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readdir, readFile, stat } from 'node:fs/promises';
|
|
2
2
|
import { basename, join } from 'node:path';
|
|
3
3
|
import { parse } from 'yaml';
|
|
4
4
|
import { config } from './config.js';
|
|
@@ -8,6 +8,62 @@ import { DEFAULT_HEXAGONAL_LAYERS, ProfileSchema } from './types.js';
|
|
|
8
8
|
* Set to 0 to disable cache expiration.
|
|
9
9
|
*/
|
|
10
10
|
const CACHE_TTL_MS = 60_000;
|
|
11
|
+
/**
|
|
12
|
+
* Deep merge two objects. Child values override parent values.
|
|
13
|
+
* Arrays are replaced, not merged.
|
|
14
|
+
*/
|
|
15
|
+
function deepMerge(parent, child) {
|
|
16
|
+
const result = { ...parent };
|
|
17
|
+
for (const key of Object.keys(child)) {
|
|
18
|
+
const childValue = child[key];
|
|
19
|
+
const parentValue = parent[key];
|
|
20
|
+
if (childValue === undefined) {
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (childValue !== null &&
|
|
24
|
+
typeof childValue === 'object' &&
|
|
25
|
+
!Array.isArray(childValue) &&
|
|
26
|
+
parentValue !== null &&
|
|
27
|
+
typeof parentValue === 'object' &&
|
|
28
|
+
!Array.isArray(parentValue)) {
|
|
29
|
+
// Recursively merge objects
|
|
30
|
+
result[key] = deepMerge(parentValue, childValue);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
// Replace value (including arrays)
|
|
34
|
+
result[key] = childValue;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Resolve profile inheritance chain.
|
|
41
|
+
* Returns a merged profile with all inherited properties.
|
|
42
|
+
*/
|
|
43
|
+
function resolveProfileInheritance(profile, allProfiles, visited = new Set()) {
|
|
44
|
+
const extendsId = profile.extends;
|
|
45
|
+
if (!extendsId) {
|
|
46
|
+
return profile;
|
|
47
|
+
}
|
|
48
|
+
// Prevent circular inheritance
|
|
49
|
+
if (visited.has(extendsId)) {
|
|
50
|
+
console.error(`Circular inheritance detected: ${extendsId}`);
|
|
51
|
+
return profile;
|
|
52
|
+
}
|
|
53
|
+
const parentProfile = allProfiles.get(extendsId);
|
|
54
|
+
if (!parentProfile) {
|
|
55
|
+
console.error(`Parent profile "${extendsId}" not found for inheritance`);
|
|
56
|
+
return profile;
|
|
57
|
+
}
|
|
58
|
+
visited.add(extendsId);
|
|
59
|
+
// Recursively resolve parent's inheritance first
|
|
60
|
+
const resolvedParent = resolveProfileInheritance(parentProfile, allProfiles, visited);
|
|
61
|
+
// Merge parent into child (child overrides parent)
|
|
62
|
+
const merged = deepMerge(resolvedParent, profile);
|
|
63
|
+
// Remove the 'extends' field from the merged result
|
|
64
|
+
delete merged.extends;
|
|
65
|
+
return merged;
|
|
66
|
+
}
|
|
11
67
|
/**
|
|
12
68
|
* Cache for loaded profiles and standards.
|
|
13
69
|
*/
|
|
@@ -35,27 +91,43 @@ export function invalidateCache() {
|
|
|
35
91
|
standardsCacheTime = null;
|
|
36
92
|
}
|
|
37
93
|
/**
|
|
38
|
-
* Load
|
|
94
|
+
* Load a single profile from a YAML file.
|
|
95
|
+
*/
|
|
96
|
+
async function loadProfileFromFile(filePath) {
|
|
97
|
+
try {
|
|
98
|
+
const fileStat = await stat(filePath);
|
|
99
|
+
if (fileStat.isDirectory())
|
|
100
|
+
return null;
|
|
101
|
+
const content = await readFile(filePath, 'utf-8');
|
|
102
|
+
const rawData = parse(content);
|
|
103
|
+
const fileName = basename(filePath);
|
|
104
|
+
const profileId = basename(fileName, fileName.endsWith('.yaml') ? '.yaml' : '.yml');
|
|
105
|
+
const profile = ProfileSchema.parse(rawData);
|
|
106
|
+
// Apply default hexagonal layers if not specified
|
|
107
|
+
if (profile.architecture?.type === 'hexagonal' && !profile.architecture.layers) {
|
|
108
|
+
profile.architecture.layers = DEFAULT_HEXAGONAL_LAYERS;
|
|
109
|
+
}
|
|
110
|
+
return { id: profileId, profile };
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Load profiles from a specific directory (parallelized).
|
|
39
118
|
*/
|
|
40
119
|
async function loadProfilesFromDir(dir, profiles) {
|
|
41
120
|
try {
|
|
42
121
|
const files = await readdir(dir);
|
|
43
122
|
const yamlFiles = files.filter((f) => (f.endsWith('.yaml') || f.endsWith('.yml')) && !f.startsWith('_'));
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const rawData = parse(content);
|
|
52
|
-
const profileId = basename(file, file.endsWith('.yaml') ? '.yaml' : '.yml');
|
|
53
|
-
const profile = ProfileSchema.parse(rawData);
|
|
54
|
-
// Apply default hexagonal layers if not specified
|
|
55
|
-
if (profile.architecture?.type === 'hexagonal' && !profile.architecture.layers) {
|
|
56
|
-
profile.architecture.layers = DEFAULT_HEXAGONAL_LAYERS;
|
|
123
|
+
// Load all profiles in parallel
|
|
124
|
+
const loadPromises = yamlFiles.map((file) => loadProfileFromFile(join(dir, file)));
|
|
125
|
+
const results = await Promise.all(loadPromises);
|
|
126
|
+
// Add loaded profiles to the map
|
|
127
|
+
for (const result of results) {
|
|
128
|
+
if (result) {
|
|
129
|
+
profiles.set(result.id, result.profile);
|
|
57
130
|
}
|
|
58
|
-
profiles.set(profileId, profile);
|
|
59
131
|
}
|
|
60
132
|
}
|
|
61
133
|
catch (error) {
|
|
@@ -74,14 +146,35 @@ export async function loadProfiles() {
|
|
|
74
146
|
return profilesCache;
|
|
75
147
|
}
|
|
76
148
|
profilesCache = new Map();
|
|
77
|
-
//
|
|
149
|
+
// Define directories to load from (in priority order - later overrides earlier)
|
|
78
150
|
const templatesDir = join(config.profilesDir, 'templates');
|
|
79
|
-
await loadProfilesFromDir(templatesDir, profilesCache);
|
|
80
|
-
// Load from custom (user profiles - can override templates)
|
|
81
151
|
const customDir = join(config.profilesDir, 'custom');
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
152
|
+
// Create separate maps for each source
|
|
153
|
+
const templateProfiles = new Map();
|
|
154
|
+
const customProfiles = new Map();
|
|
155
|
+
const rootProfiles = new Map();
|
|
156
|
+
// Load all directories in parallel
|
|
157
|
+
await Promise.all([
|
|
158
|
+
loadProfilesFromDir(templatesDir, templateProfiles),
|
|
159
|
+
loadProfilesFromDir(customDir, customProfiles),
|
|
160
|
+
loadProfilesFromDir(config.profilesDir, rootProfiles),
|
|
161
|
+
]);
|
|
162
|
+
// Merge in priority order: templates < root < custom
|
|
163
|
+
for (const [id, profile] of templateProfiles) {
|
|
164
|
+
profilesCache.set(id, profile);
|
|
165
|
+
}
|
|
166
|
+
for (const [id, profile] of rootProfiles) {
|
|
167
|
+
profilesCache.set(id, profile);
|
|
168
|
+
}
|
|
169
|
+
for (const [id, profile] of customProfiles) {
|
|
170
|
+
profilesCache.set(id, profile);
|
|
171
|
+
}
|
|
172
|
+
// Resolve inheritance for all profiles
|
|
173
|
+
const resolvedProfiles = new Map();
|
|
174
|
+
for (const [id, profile] of profilesCache) {
|
|
175
|
+
resolvedProfiles.set(id, resolveProfileInheritance(profile, profilesCache));
|
|
176
|
+
}
|
|
177
|
+
profilesCache = resolvedProfiles;
|
|
85
178
|
profilesCacheTime = Date.now();
|
|
86
179
|
return profilesCache;
|
|
87
180
|
}
|
|
@@ -100,44 +193,54 @@ export async function listProfiles() {
|
|
|
100
193
|
return Array.from(profiles.entries()).map(([id, profile]) => ({ id, profile }));
|
|
101
194
|
}
|
|
102
195
|
/**
|
|
103
|
-
* Load all standards from markdown files.
|
|
196
|
+
* Load all standards from markdown files (parallelized).
|
|
104
197
|
*/
|
|
105
198
|
export async function loadStandards() {
|
|
106
199
|
if (standardsCache && isCacheValid(standardsCacheTime)) {
|
|
107
200
|
return standardsCache;
|
|
108
201
|
}
|
|
109
|
-
standardsCache = [];
|
|
110
202
|
try {
|
|
111
|
-
await scanStandardsDirectory(config.standardsDir, '');
|
|
203
|
+
standardsCache = await scanStandardsDirectory(config.standardsDir, '');
|
|
112
204
|
}
|
|
113
205
|
catch (error) {
|
|
114
206
|
if (error.code !== 'ENOENT') {
|
|
115
207
|
throw error;
|
|
116
208
|
}
|
|
209
|
+
standardsCache = [];
|
|
117
210
|
}
|
|
118
211
|
standardsCacheTime = Date.now();
|
|
119
212
|
return standardsCache;
|
|
120
213
|
}
|
|
121
214
|
async function scanStandardsDirectory(dir, category) {
|
|
122
215
|
const entries = await readdir(dir);
|
|
123
|
-
|
|
216
|
+
const documents = [];
|
|
217
|
+
// Process all entries in parallel
|
|
218
|
+
const processEntry = async (entry) => {
|
|
124
219
|
const fullPath = join(dir, entry);
|
|
125
220
|
const stats = await stat(fullPath);
|
|
126
221
|
if (stats.isDirectory()) {
|
|
127
|
-
|
|
222
|
+
return scanStandardsDirectory(fullPath, entry);
|
|
128
223
|
}
|
|
129
224
|
else if (entry.endsWith('.md')) {
|
|
130
225
|
const content = await readFile(fullPath, 'utf-8');
|
|
131
226
|
const id = generateStandardId(fullPath);
|
|
132
227
|
const name = extractStandardName(content, entry);
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
228
|
+
return [
|
|
229
|
+
{
|
|
230
|
+
id,
|
|
231
|
+
name,
|
|
232
|
+
category: category || 'general',
|
|
233
|
+
content,
|
|
234
|
+
},
|
|
235
|
+
];
|
|
139
236
|
}
|
|
237
|
+
return [];
|
|
238
|
+
};
|
|
239
|
+
const results = await Promise.all(entries.map(processEntry));
|
|
240
|
+
for (const result of results) {
|
|
241
|
+
documents.push(...result);
|
|
140
242
|
}
|
|
243
|
+
return documents;
|
|
141
244
|
}
|
|
142
245
|
function generateStandardId(filePath) {
|
|
143
246
|
return filePath
|
|
@@ -426,92 +529,120 @@ function formatObservabilitySection(profile) {
|
|
|
426
529
|
}
|
|
427
530
|
return lines;
|
|
428
531
|
}
|
|
429
|
-
function
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
lines.push('', '**Requirements:**', ...profile.apiDocumentation.requirements.map((r) => `- ${r}`));
|
|
438
|
-
}
|
|
439
|
-
if (profile.apiDocumentation.output) {
|
|
440
|
-
lines.push('', '**Output:**', ...profile.apiDocumentation.output.map((o) => `- ${o}`));
|
|
441
|
-
}
|
|
442
|
-
lines.push('');
|
|
532
|
+
function formatApiDocSection(profile) {
|
|
533
|
+
if (!profile.apiDocumentation?.enabled)
|
|
534
|
+
return [];
|
|
535
|
+
const lines = ['## API Documentation', '', `- Tool: ${profile.apiDocumentation.tool}`];
|
|
536
|
+
if (profile.apiDocumentation.version)
|
|
537
|
+
lines.push(`- Version: ${profile.apiDocumentation.version}`);
|
|
538
|
+
if (profile.apiDocumentation.requirements) {
|
|
539
|
+
lines.push('', '**Requirements:**', ...profile.apiDocumentation.requirements.map((r) => `- ${r}`));
|
|
443
540
|
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
lines.push('## Security', '');
|
|
447
|
-
if (profile.security.authentication) {
|
|
448
|
-
lines.push(`- Authentication: ${profile.security.authentication.method || 'N/A'}`);
|
|
449
|
-
}
|
|
450
|
-
if (profile.security.authorization) {
|
|
451
|
-
lines.push(`- Authorization: ${profile.security.authorization.method || 'N/A'} (${profile.security.authorization.framework || 'N/A'})`);
|
|
452
|
-
}
|
|
453
|
-
if (profile.security.practices) {
|
|
454
|
-
lines.push('', '**Practices:**', ...profile.security.practices.map((p) => `- ${p}`));
|
|
455
|
-
}
|
|
456
|
-
lines.push('');
|
|
541
|
+
if (profile.apiDocumentation.output) {
|
|
542
|
+
lines.push('', '**Output:**', ...profile.apiDocumentation.output.map((o) => `- ${o}`));
|
|
457
543
|
}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
lines.push('', '**Application Exceptions:**', ...profile.errorHandling.customExceptions.application.map((e) => `- ${e}`));
|
|
468
|
-
}
|
|
469
|
-
lines.push('');
|
|
544
|
+
lines.push('');
|
|
545
|
+
return lines;
|
|
546
|
+
}
|
|
547
|
+
function formatSecuritySection(profile) {
|
|
548
|
+
if (!profile.security)
|
|
549
|
+
return [];
|
|
550
|
+
const lines = ['## Security', ''];
|
|
551
|
+
if (profile.security.authentication) {
|
|
552
|
+
lines.push(`- Authentication: ${profile.security.authentication.method || 'N/A'}`);
|
|
470
553
|
}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
lines.push('## Database', '');
|
|
474
|
-
if (profile.database.migrations) {
|
|
475
|
-
lines.push(`- Migrations: ${profile.database.migrations.tool}`);
|
|
476
|
-
if (profile.database.migrations.naming)
|
|
477
|
-
lines.push(`- Naming: ${profile.database.migrations.naming}`);
|
|
478
|
-
}
|
|
479
|
-
if (profile.database.auditing?.enabled) {
|
|
480
|
-
lines.push(`- Auditing: enabled (fields: ${profile.database.auditing.fields?.join(', ') || 'N/A'})`);
|
|
481
|
-
}
|
|
482
|
-
if (profile.database.softDelete?.recommended) {
|
|
483
|
-
lines.push(`- Soft Delete: recommended (field: ${profile.database.softDelete.field || 'deletedAt'})`);
|
|
484
|
-
}
|
|
485
|
-
lines.push('');
|
|
554
|
+
if (profile.security.authorization) {
|
|
555
|
+
lines.push(`- Authorization: ${profile.security.authorization.method || 'N/A'} (${profile.security.authorization.framework || 'N/A'})`);
|
|
486
556
|
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
557
|
+
if (profile.security.practices) {
|
|
558
|
+
lines.push('', '**Practices:**', ...profile.security.practices.map((p) => `- ${p}`));
|
|
559
|
+
}
|
|
560
|
+
lines.push('');
|
|
561
|
+
return lines;
|
|
562
|
+
}
|
|
563
|
+
function formatErrorHandlingSection(profile) {
|
|
564
|
+
if (!profile.errorHandling)
|
|
565
|
+
return [];
|
|
566
|
+
const lines = ['## Error Handling', '', `- Format: ${profile.errorHandling.format}`];
|
|
567
|
+
if (profile.errorHandling.globalHandler)
|
|
568
|
+
lines.push(`- Global Handler: ${profile.errorHandling.globalHandler}`);
|
|
569
|
+
if (profile.errorHandling.customExceptions?.domain) {
|
|
570
|
+
lines.push('', '**Domain Exceptions:**', ...profile.errorHandling.customExceptions.domain.map((e) => `- ${e}`));
|
|
571
|
+
}
|
|
572
|
+
if (profile.errorHandling.customExceptions?.application) {
|
|
573
|
+
lines.push('', '**Application Exceptions:**', ...profile.errorHandling.customExceptions.application.map((e) => `- ${e}`));
|
|
574
|
+
}
|
|
575
|
+
lines.push('');
|
|
576
|
+
return lines;
|
|
577
|
+
}
|
|
578
|
+
function formatDatabaseSection(profile) {
|
|
579
|
+
if (!profile.database || Object.keys(profile.database).length === 0)
|
|
580
|
+
return [];
|
|
581
|
+
const lines = ['## Database', ''];
|
|
582
|
+
const db = profile.database;
|
|
583
|
+
const migrations = db.migrations;
|
|
584
|
+
const auditing = db.auditing;
|
|
585
|
+
const softDelete = db.softDelete;
|
|
586
|
+
if (migrations) {
|
|
587
|
+
if (migrations.tool)
|
|
588
|
+
lines.push(`- Migrations: ${migrations.tool}`);
|
|
589
|
+
if (migrations.naming)
|
|
590
|
+
lines.push(`- Naming: ${migrations.naming}`);
|
|
591
|
+
}
|
|
592
|
+
if (auditing?.enabled) {
|
|
593
|
+
const fields = auditing.fields;
|
|
594
|
+
lines.push(`- Auditing: enabled (fields: ${fields?.join(', ') || 'N/A'})`);
|
|
595
|
+
}
|
|
596
|
+
if (softDelete?.recommended) {
|
|
597
|
+
lines.push(`- Soft Delete: recommended (field: ${softDelete.field || 'deletedAt'})`);
|
|
598
|
+
}
|
|
599
|
+
if (db.orm)
|
|
600
|
+
lines.push(`- ORM: ${db.orm}`);
|
|
601
|
+
if (db.driver)
|
|
602
|
+
lines.push(`- Driver: ${db.driver}`);
|
|
603
|
+
lines.push('');
|
|
604
|
+
return lines;
|
|
605
|
+
}
|
|
606
|
+
function formatMappingSection(profile) {
|
|
607
|
+
if (!profile.mapping)
|
|
608
|
+
return [];
|
|
609
|
+
const lines = ['## Object Mapping', '', `- Tool: ${profile.mapping.tool}`];
|
|
610
|
+
if (profile.mapping.componentModel)
|
|
611
|
+
lines.push(`- Component Model: ${profile.mapping.componentModel}`);
|
|
612
|
+
if (profile.mapping.patterns) {
|
|
613
|
+
lines.push('', '**Patterns:**', ...profile.mapping.patterns.map((p) => `- ${p}`));
|
|
496
614
|
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
615
|
+
lines.push('');
|
|
616
|
+
return lines;
|
|
617
|
+
}
|
|
618
|
+
function formatTechnologiesSection(profile) {
|
|
619
|
+
if (!profile.technologies?.length)
|
|
620
|
+
return [];
|
|
621
|
+
const lines = ['## Technologies', ''];
|
|
622
|
+
for (const tech of profile.technologies) {
|
|
623
|
+
lines.push(`### ${tech.name}${tech.version ? ` (${tech.version})` : ''}`);
|
|
624
|
+
if (tech.tool)
|
|
625
|
+
lines.push(`Tool: ${tech.tool}`);
|
|
626
|
+
if (tech.specificRules && Object.keys(tech.specificRules).length > 0) {
|
|
627
|
+
lines.push('**Rules:**');
|
|
628
|
+
for (const [key, value] of Object.entries(tech.specificRules)) {
|
|
629
|
+
lines.push(`- ${key}: ${value}`);
|
|
509
630
|
}
|
|
510
|
-
lines.push('');
|
|
511
631
|
}
|
|
632
|
+
lines.push('');
|
|
512
633
|
}
|
|
513
634
|
return lines;
|
|
514
635
|
}
|
|
636
|
+
function formatRemainingProfileSections(profile) {
|
|
637
|
+
return [
|
|
638
|
+
...formatApiDocSection(profile),
|
|
639
|
+
...formatSecuritySection(profile),
|
|
640
|
+
...formatErrorHandlingSection(profile),
|
|
641
|
+
...formatDatabaseSection(profile),
|
|
642
|
+
...formatMappingSection(profile),
|
|
643
|
+
...formatTechnologiesSection(profile),
|
|
644
|
+
];
|
|
645
|
+
}
|
|
515
646
|
/**
|
|
516
647
|
* Format a profile as markdown for LLM context.
|
|
517
648
|
*/
|