@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.
Files changed (104) hide show
  1. package/README.md +170 -269
  2. package/assets/demo.gif +0 -0
  3. package/assets/demo.tape +63 -0
  4. package/dist/agent.d.ts +5 -6
  5. package/dist/agent.d.ts.map +1 -1
  6. package/dist/agent.js +95 -217
  7. package/dist/agent.js.map +1 -1
  8. package/dist/cli/init.js +1 -1
  9. package/dist/cli/init.js.map +1 -1
  10. package/dist/config.d.ts +15 -26
  11. package/dist/config.d.ts.map +1 -1
  12. package/dist/errors.d.ts +58 -0
  13. package/dist/errors.d.ts.map +1 -0
  14. package/dist/errors.js +112 -0
  15. package/dist/errors.js.map +1 -0
  16. package/dist/guardrails.d.ts +35 -0
  17. package/dist/guardrails.d.ts.map +1 -0
  18. package/dist/guardrails.js +303 -0
  19. package/dist/guardrails.js.map +1 -0
  20. package/dist/index.js +1 -1
  21. package/dist/index.js.map +1 -1
  22. package/dist/logger.d.ts +36 -0
  23. package/dist/logger.d.ts.map +1 -0
  24. package/dist/logger.js +63 -0
  25. package/dist/logger.js.map +1 -0
  26. package/dist/metrics.d.ts +40 -0
  27. package/dist/metrics.d.ts.map +1 -0
  28. package/dist/metrics.js +97 -0
  29. package/dist/metrics.js.map +1 -0
  30. package/dist/profiles.d.ts +1 -1
  31. package/dist/profiles.d.ts.map +1 -1
  32. package/dist/profiles.js +240 -109
  33. package/dist/profiles.js.map +1 -1
  34. package/dist/prompts.js +1 -1
  35. package/dist/prompts.js.map +1 -1
  36. package/dist/tools/definitions.d.ts +104 -0
  37. package/dist/tools/definitions.d.ts.map +1 -0
  38. package/dist/tools/definitions.js +171 -0
  39. package/dist/tools/definitions.js.map +1 -0
  40. package/dist/tools/handlers/get-context.d.ts +12 -0
  41. package/dist/tools/handlers/get-context.d.ts.map +1 -0
  42. package/dist/tools/handlers/get-context.js +118 -0
  43. package/dist/tools/handlers/get-context.js.map +1 -0
  44. package/dist/tools/handlers/health.d.ts +11 -0
  45. package/dist/tools/handlers/health.d.ts.map +1 -0
  46. package/dist/tools/handlers/health.js +57 -0
  47. package/dist/tools/handlers/health.js.map +1 -0
  48. package/dist/tools/handlers/index.d.ts +11 -0
  49. package/dist/tools/handlers/index.d.ts.map +1 -0
  50. package/dist/tools/handlers/index.js +11 -0
  51. package/dist/tools/handlers/index.js.map +1 -0
  52. package/dist/tools/handlers/init.d.ts +12 -0
  53. package/dist/tools/handlers/init.d.ts.map +1 -0
  54. package/dist/tools/handlers/init.js +102 -0
  55. package/dist/tools/handlers/init.js.map +1 -0
  56. package/dist/tools/handlers/profiles.d.ts +11 -0
  57. package/dist/tools/handlers/profiles.d.ts.map +1 -0
  58. package/dist/tools/handlers/profiles.js +25 -0
  59. package/dist/tools/handlers/profiles.js.map +1 -0
  60. package/dist/tools/handlers/search.d.ts +12 -0
  61. package/dist/tools/handlers/search.d.ts.map +1 -0
  62. package/dist/tools/handlers/search.js +58 -0
  63. package/dist/tools/handlers/search.js.map +1 -0
  64. package/dist/tools/handlers/validate.d.ts +12 -0
  65. package/dist/tools/handlers/validate.d.ts.map +1 -0
  66. package/dist/tools/handlers/validate.js +76 -0
  67. package/dist/tools/handlers/validate.js.map +1 -0
  68. package/dist/tools/index.d.ts +22 -0
  69. package/dist/tools/index.d.ts.map +1 -0
  70. package/dist/tools/index.js +72 -0
  71. package/dist/tools/index.js.map +1 -0
  72. package/dist/tools/schemas.d.ts +29 -0
  73. package/dist/tools/schemas.d.ts.map +1 -0
  74. package/dist/tools/schemas.js +20 -0
  75. package/dist/tools/schemas.js.map +1 -0
  76. package/dist/tools.js +2 -2
  77. package/dist/tools.js.map +1 -1
  78. package/dist/types.d.ts +417 -2588
  79. package/dist/types.d.ts.map +1 -1
  80. package/dist/types.js +99 -47
  81. package/dist/types.js.map +1 -1
  82. package/dist/utils/index.d.ts +1 -1
  83. package/dist/utils/index.d.ts.map +1 -1
  84. package/dist/utils/index.js +1 -1
  85. package/dist/utils/index.js.map +1 -1
  86. package/dist/utils/retry.d.ts.map +1 -1
  87. package/dist/utils/retry.js +3 -2
  88. package/dist/utils/retry.js.map +1 -1
  89. package/package.json +10 -9
  90. package/profiles/examples/microservice-kafka.yaml +122 -0
  91. package/profiles/examples/startup-fast.yaml +67 -0
  92. package/profiles/examples/strict-enterprise.yaml +62 -0
  93. package/profiles/templates/angular.yaml +614 -0
  94. package/profiles/templates/csharp-dotnet.yaml +529 -0
  95. package/profiles/templates/flutter.yaml +547 -0
  96. package/profiles/templates/go.yaml +1276 -0
  97. package/profiles/templates/java-spring-backend.yaml +326 -0
  98. package/profiles/templates/kotlin-spring.yaml +417 -0
  99. package/profiles/templates/nextjs.yaml +536 -0
  100. package/profiles/templates/nodejs.yaml +594 -0
  101. package/profiles/templates/python.yaml +546 -0
  102. package/profiles/templates/react.yaml +456 -0
  103. package/profiles/templates/rust.yaml +508 -0
  104. 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"}
@@ -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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"}
@@ -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
  /**
@@ -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;AAyB1G;;GAEG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAKtC;AAqCD;;;;GAIG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAoBlE;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,CAiBjE;AA4CD;;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;AA8YD;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,MAAM,CAqBpF"}
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 { readFile, readdir, stat } from 'node:fs/promises';
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 profiles from a specific directory.
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
- for (const file of yamlFiles) {
45
- const filePath = join(dir, file);
46
- const fileStat = await stat(filePath);
47
- // Skip directories
48
- if (fileStat.isDirectory())
49
- continue;
50
- const content = await readFile(filePath, 'utf-8');
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
- // Load from templates first (official profiles)
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
- await loadProfilesFromDir(customDir, profilesCache);
83
- // Fallback: also check root profiles dir for backwards compatibility
84
- await loadProfilesFromDir(config.profilesDir, profilesCache);
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
- for (const entry of entries) {
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
- await scanStandardsDirectory(fullPath, entry);
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
- standardsCache?.push({
134
- id,
135
- name,
136
- category: category || 'general',
137
- content,
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 formatRemainingProfileSections(profile) {
430
- const lines = [];
431
- // API Documentation
432
- if (profile.apiDocumentation?.enabled) {
433
- lines.push('## API Documentation', '', `- Tool: ${profile.apiDocumentation.tool}`);
434
- if (profile.apiDocumentation.version)
435
- lines.push(`- Version: ${profile.apiDocumentation.version}`);
436
- if (profile.apiDocumentation.requirements) {
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
- // Security
445
- if (profile.security) {
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
- // Error Handling
459
- if (profile.errorHandling) {
460
- lines.push('## Error Handling', '', `- Format: ${profile.errorHandling.format}`);
461
- if (profile.errorHandling.globalHandler)
462
- lines.push(`- Global Handler: ${profile.errorHandling.globalHandler}`);
463
- if (profile.errorHandling.customExceptions?.domain) {
464
- lines.push('', '**Domain Exceptions:**', ...profile.errorHandling.customExceptions.domain.map((e) => `- ${e}`));
465
- }
466
- if (profile.errorHandling.customExceptions?.application) {
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
- // Database
472
- if (profile.database) {
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
- // Mapping
488
- if (profile.mapping) {
489
- lines.push('## Object Mapping', '', `- Tool: ${profile.mapping.tool}`);
490
- if (profile.mapping.componentModel)
491
- lines.push(`- Component Model: ${profile.mapping.componentModel}`);
492
- if (profile.mapping.patterns) {
493
- lines.push('', '**Patterns:**', ...profile.mapping.patterns.map((p) => `- ${p}`));
494
- }
495
- lines.push('');
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
- // Technologies
498
- if (profile.technologies?.length) {
499
- lines.push('## Technologies', '');
500
- for (const tech of profile.technologies) {
501
- lines.push(`### ${tech.name}${tech.version ? ` (${tech.version})` : ''}`);
502
- if (tech.tool)
503
- lines.push(`Tool: ${tech.tool}`);
504
- if (tech.specificRules && Object.keys(tech.specificRules).length > 0) {
505
- lines.push('**Rules:**');
506
- for (const [key, value] of Object.entries(tech.specificRules)) {
507
- lines.push(`- ${key}: ${value}`);
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
  */