@polymorphism-tech/morph-spec 2.4.0 → 3.0.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 (218) hide show
  1. package/CLAUDE.md +158 -26
  2. package/LICENSE +72 -72
  3. package/bin/detect-agents.js +225 -225
  4. package/bin/morph-spec.js +8 -0
  5. package/bin/render-template.js +302 -302
  6. package/bin/semantic-detect-agents.js +246 -246
  7. package/bin/validate-agents-skills.js +251 -251
  8. package/bin/validate-agents.js +69 -69
  9. package/bin/validate-phase.js +263 -263
  10. package/content/.azure/README.md +293 -293
  11. package/content/.azure/docs/azure-devops-setup.md +454 -454
  12. package/content/.azure/docs/branch-strategy.md +398 -398
  13. package/content/.azure/docs/local-development.md +515 -515
  14. package/content/.azure/pipelines/pipeline-variables.yml +34 -34
  15. package/content/.azure/pipelines/prod-pipeline.yml +319 -319
  16. package/content/.azure/pipelines/staging-pipeline.yml +234 -234
  17. package/content/.azure/pipelines/templates/build-dotnet.yml +75 -75
  18. package/content/.azure/pipelines/templates/deploy-app-service.yml +94 -94
  19. package/content/.azure/pipelines/templates/deploy-container-app.yml +120 -120
  20. package/content/.azure/pipelines/templates/infra-deploy.yml +90 -90
  21. package/content/.claude/commands/morph-archive.md +79 -79
  22. package/content/.claude/commands/morph-deploy.md +529 -0
  23. package/content/.claude/commands/morph-infra.md +209 -209
  24. package/content/.claude/commands/morph-preflight.md +227 -227
  25. package/content/.claude/commands/morph-troubleshoot.md +122 -122
  26. package/content/.claude/settings.local.json +15 -15
  27. package/content/.claude/skills/infra/azure-deploy-specialist.md +699 -0
  28. package/content/.claude/skills/level-0-meta/README.md +7 -0
  29. package/content/.claude/skills/{checklists → level-0-meta}/morph-checklist.md +117 -117
  30. package/content/.claude/skills/level-1-workflows/README.md +7 -0
  31. package/content/.claude/skills/{workflows → level-1-workflows}/morph-replicate.md +213 -213
  32. package/content/.claude/skills/{workflows → level-1-workflows}/phase-clarify.md +131 -131
  33. package/content/.claude/skills/{workflows → level-1-workflows}/phase-design.md +213 -205
  34. package/content/.claude/skills/{workflows → level-1-workflows}/phase-setup.md +106 -92
  35. package/content/.claude/skills/{workflows → level-1-workflows}/phase-tasks.md +164 -164
  36. package/content/.claude/skills/{workflows → level-1-workflows}/phase-uiux.md +169 -138
  37. package/content/.claude/skills/level-2-domains/README.md +14 -0
  38. package/content/.claude/skills/{specialists → level-2-domains/quality}/testing-specialist.md +126 -126
  39. package/content/.claude/skills/level-3-technologies/README.md +7 -0
  40. package/content/.claude/skills/level-4-patterns/README.md +7 -0
  41. package/content/.claude/skills/specialists/prompt-engineer.md +189 -0
  42. package/content/.claude/skills/specialists/seo-growth-hacker.md +320 -0
  43. package/content/.morph/.morphversion +5 -5
  44. package/content/.morph/archive/.gitkeep +25 -25
  45. package/content/.morph/config/agents.json +742 -358
  46. package/content/.morph/config/config.template.json +33 -0
  47. package/content/.morph/docs/STORY-DRIVEN-DEVELOPMENT.md +392 -392
  48. package/content/.morph/docs/workflows/enforcement-pipeline.md +668 -0
  49. package/content/.morph/examples/api-nextjs/README.md +241 -241
  50. package/content/.morph/examples/api-nextjs/contracts.ts +307 -307
  51. package/content/.morph/examples/api-nextjs/spec.md +399 -399
  52. package/content/.morph/examples/api-nextjs/tasks.md +168 -168
  53. package/content/.morph/examples/micro-saas/README.md +125 -125
  54. package/content/.morph/examples/micro-saas/contracts.cs +358 -358
  55. package/content/.morph/examples/micro-saas/decisions.md +246 -246
  56. package/content/.morph/examples/micro-saas/spec.md +236 -236
  57. package/content/.morph/examples/micro-saas/tasks.md +150 -150
  58. package/content/.morph/examples/multi-agent/README.md +309 -309
  59. package/content/.morph/examples/multi-agent/contracts.cs +433 -433
  60. package/content/.morph/examples/multi-agent/spec.md +479 -479
  61. package/content/.morph/examples/multi-agent/tasks.md +185 -185
  62. package/content/.morph/examples/scheduled-reports/decisions.md +158 -158
  63. package/content/.morph/examples/scheduled-reports/proposal.md +95 -95
  64. package/content/.morph/examples/scheduled-reports/spec.md +267 -267
  65. package/content/.morph/examples/state-v3.json +188 -188
  66. package/content/.morph/features/.gitkeep +25 -25
  67. package/content/.morph/hooks/README.md +158 -0
  68. package/content/.morph/hooks/pre-commit-all.sh +48 -48
  69. package/content/.morph/hooks/pre-commit-specs.sh +49 -49
  70. package/content/.morph/hooks/pre-commit-tests.sh +60 -60
  71. package/content/.morph/hooks/task-completed.js +73 -0
  72. package/content/.morph/hooks/teammate-idle.js +68 -0
  73. package/content/.morph/project.md +160 -160
  74. package/content/.morph/schemas/agent.schema.json +296 -296
  75. package/content/.morph/schemas/tasks.schema.json +220 -220
  76. package/content/.morph/specs/.gitkeep +20 -20
  77. package/content/.morph/standards/agent-teams-workflow.md +474 -0
  78. package/content/.morph/standards/coding.md +377 -377
  79. package/content/.morph/standards/fluent-ui-setup.md +590 -590
  80. package/content/.morph/standards/migration-guide.md +514 -514
  81. package/content/.morph/standards/passkeys-auth.md +423 -423
  82. package/content/.morph/standards/vector-search-rag.md +536 -536
  83. package/content/.morph/state.json +17 -17
  84. package/content/.morph/templates/CONTEXT-FEATURE.md +276 -0
  85. package/content/.morph/templates/CONTEXT.md +170 -0
  86. package/content/.morph/templates/FluentDesignTheme.cs +149 -149
  87. package/content/.morph/templates/MudTheme.cs +281 -281
  88. package/content/.morph/templates/clarify-questions.md +159 -159
  89. package/content/.morph/templates/component.razor +239 -239
  90. package/content/.morph/templates/contracts/Commands.cs +74 -74
  91. package/content/.morph/templates/contracts/Entities.cs +25 -25
  92. package/content/.morph/templates/contracts/Queries.cs +74 -74
  93. package/content/.morph/templates/contracts/README.md +74 -74
  94. package/content/.morph/templates/contracts.cs +217 -217
  95. package/content/.morph/templates/design-system.css +226 -226
  96. package/content/.morph/templates/infra/.dockerignore.example +89 -89
  97. package/content/.morph/templates/infra/Dockerfile.example +82 -82
  98. package/content/.morph/templates/infra/README.md +286 -286
  99. package/content/.morph/templates/infra/app-insights.bicep +63 -63
  100. package/content/.morph/templates/infra/app-service.bicep +164 -164
  101. package/content/.morph/templates/infra/azure-pipelines-deploy.yml +480 -0
  102. package/content/.morph/templates/infra/container-app-env.bicep +49 -49
  103. package/content/.morph/templates/infra/container-app.bicep +156 -156
  104. package/content/.morph/templates/infra/deploy-checklist.md +426 -426
  105. package/content/.morph/templates/infra/deploy.ps1 +229 -229
  106. package/content/.morph/templates/infra/deploy.sh +208 -208
  107. package/content/.morph/templates/infra/key-vault.bicep +91 -91
  108. package/content/.morph/templates/infra/main.bicep +189 -189
  109. package/content/.morph/templates/infra/parameters.dev.json +29 -29
  110. package/content/.morph/templates/infra/parameters.prod.json +29 -29
  111. package/content/.morph/templates/infra/parameters.staging.json +29 -29
  112. package/content/.morph/templates/infra/sql-database.bicep +103 -103
  113. package/content/.morph/templates/infra/storage.bicep +106 -106
  114. package/content/.morph/templates/integrations/asaas-client.cs +387 -387
  115. package/content/.morph/templates/integrations/asaas-webhook.cs +351 -351
  116. package/content/.morph/templates/integrations/azure-identity-config.cs +288 -288
  117. package/content/.morph/templates/integrations/clerk-config.cs +258 -258
  118. package/content/.morph/templates/job.cs +171 -171
  119. package/content/.morph/templates/migration.cs +83 -83
  120. package/content/.morph/templates/repository.cs +141 -141
  121. package/content/.morph/templates/saas/subscription.cs +347 -347
  122. package/content/.morph/templates/saas/tenant.cs +338 -338
  123. package/content/.morph/templates/service.cs +139 -139
  124. package/content/.morph/templates/sprint-status.yaml +68 -68
  125. package/content/.morph/templates/story.md +143 -143
  126. package/content/.morph/templates/test.cs +239 -239
  127. package/content/.morph/templates/ui-design-system.md +286 -286
  128. package/content/.morph/templates/ui-flows.md +336 -336
  129. package/content/.morph/templates/ui-mockups.md +133 -133
  130. package/content/.morph/test-infra/example.bicep +59 -59
  131. package/content/README.md +79 -79
  132. package/detectors/config-detector.js +223 -223
  133. package/detectors/conversation-analyzer.js +163 -163
  134. package/detectors/index.js +84 -84
  135. package/detectors/standards-generator.js +275 -275
  136. package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +977 -977
  137. package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +1048 -1048
  138. package/docs/api/scripts/collapse.js +38 -38
  139. package/docs/api/scripts/commonNav.js +28 -28
  140. package/docs/api/scripts/linenumber.js +25 -25
  141. package/docs/api/scripts/nav.js +12 -12
  142. package/docs/api/scripts/polyfill.js +3 -3
  143. package/docs/api/scripts/prettify/Apache-License-2.0.txt +202 -202
  144. package/docs/api/scripts/prettify/lang-css.js +2 -2
  145. package/docs/api/scripts/prettify/prettify.js +28 -28
  146. package/docs/api/scripts/search.js +98 -98
  147. package/docs/api/styles/jsdoc.css +776 -776
  148. package/docs/api/styles/prettify.css +80 -80
  149. package/docs/examples.md +328 -328
  150. package/docs/templates.md +418 -418
  151. package/package.json +1 -1
  152. package/scripts/postinstall.js +132 -132
  153. package/src/commands/advance-phase.js +83 -0
  154. package/src/commands/analyze-blazor-concurrency.js +193 -193
  155. package/src/commands/create-story.js +351 -351
  156. package/src/commands/deploy.js +780 -0
  157. package/src/commands/detect-agents.js +34 -6
  158. package/src/commands/detect.js +104 -104
  159. package/src/commands/generate-context.js +40 -0
  160. package/src/commands/generate.js +149 -149
  161. package/src/commands/lint-fluent.js +352 -352
  162. package/src/commands/rollback-phase.js +185 -185
  163. package/src/commands/session-summary.js +291 -291
  164. package/src/commands/shard-spec.js +224 -224
  165. package/src/commands/sprint-status.js +250 -250
  166. package/src/commands/state.js +333 -333
  167. package/src/commands/sync.js +167 -167
  168. package/src/commands/troubleshoot.js +222 -222
  169. package/src/commands/validate-blazor-state.js +210 -210
  170. package/src/commands/validate-blazor.js +156 -156
  171. package/src/commands/validate-css.js +84 -84
  172. package/src/commands/validate-phase.js +221 -221
  173. package/src/lib/blazor-concurrency-analyzer.js +288 -288
  174. package/src/lib/blazor-state-validator.js +291 -291
  175. package/src/lib/blazor-validator.js +374 -374
  176. package/src/lib/context-generator.js +513 -0
  177. package/src/lib/css-validator.js +352 -352
  178. package/src/lib/design-system-detector.js +187 -0
  179. package/src/lib/design-system-generator.js +298 -298
  180. package/src/lib/design-system-scaffolder.js +299 -0
  181. package/src/lib/hook-executor.js +256 -0
  182. package/src/lib/learning-system.js +520 -520
  183. package/src/lib/mockup-generator.js +366 -366
  184. package/src/lib/spec-validator.js +258 -0
  185. package/src/lib/standards-context-injector.js +287 -0
  186. package/src/lib/team-orchestrator.js +322 -0
  187. package/src/lib/troubleshoot-grep.js +194 -194
  188. package/src/lib/troubleshoot-index.js +144 -144
  189. package/src/lib/ui-detector.js +350 -350
  190. package/src/lib/validation-runner.js +65 -13
  191. package/src/lib/validators/architecture-validator.js +387 -387
  192. package/src/lib/validators/design-system-validator.js +231 -0
  193. package/src/lib/validators/package-validator.js +360 -360
  194. package/src/lib/validators/ui-contrast-validator.js +422 -422
  195. package/src/utils/file-copier.js +9 -1
  196. package/src/utils/logger.js +32 -32
  197. package/src/utils/version-checker.js +175 -175
  198. /package/content/.claude/skills/{checklists → level-0-meta}/code-review.md +0 -0
  199. /package/content/.claude/skills/{checklists → level-0-meta}/simulation-checklist.md +0 -0
  200. /package/content/.claude/skills/{specialists → level-2-domains/ai-agents}/ai-system-architect.md +0 -0
  201. /package/content/.claude/skills/{specialists → level-2-domains/architecture}/po-pm-advisor.md +0 -0
  202. /package/content/.claude/skills/{specialists → level-2-domains/architecture}/standards-architect.md +0 -0
  203. /package/content/.claude/skills/{specialists → level-2-domains/backend}/dotnet-senior.md +0 -0
  204. /package/content/.claude/skills/{specialists → level-2-domains/backend}/ef-modeler.md +0 -0
  205. /package/content/.claude/skills/{specialists → level-2-domains/backend}/hangfire-orchestrator.md +0 -0
  206. /package/content/.claude/skills/{specialists → level-2-domains/backend}/ms-agent-expert.md +0 -0
  207. /package/content/.claude/skills/{stacks/dotnet-blazor.md → level-2-domains/frontend/blazor-builder.md} +0 -0
  208. /package/content/.claude/skills/{stacks/dotnet-nextjs.md → level-2-domains/frontend/nextjs-expert.md} +0 -0
  209. /package/content/.claude/skills/{specialists → level-2-domains/frontend}/ui-ux-designer.md +0 -0
  210. /package/content/.claude/skills/{specialists → level-2-domains/infrastructure}/azure-architect.md +0 -0
  211. /package/content/.claude/skills/{infra → level-2-domains/infrastructure}/bicep-architect.md +0 -0
  212. /package/content/.claude/skills/{infra → level-2-domains/infrastructure}/container-specialist.md +0 -0
  213. /package/content/.claude/skills/{infra → level-2-domains/infrastructure}/devops-engineer.md +0 -0
  214. /package/content/.claude/skills/{integrations → level-2-domains/integrations}/asaas-financial.md +0 -0
  215. /package/content/.claude/skills/{integrations → level-2-domains/integrations}/azure-identity.md +0 -0
  216. /package/content/.claude/skills/{integrations → level-2-domains/integrations}/clerk-auth.md +0 -0
  217. /package/content/.claude/skills/{integrations → level-2-domains/integrations}/resend-email.md +0 -0
  218. /package/content/.claude/skills/{specialists → level-2-domains/quality}/code-analyzer.md +0 -0
@@ -1,520 +1,520 @@
1
- /**
2
- * Learning System
3
- *
4
- * Tracks user decisions and learns preferences over time.
5
- * Provides contextual suggestions based on historical patterns.
6
- *
7
- * Learns from:
8
- * - decisions.md files (ADRs from each feature)
9
- * - .morph/project/standards/*.md (project standards)
10
- * - state.json (feature outcomes and complexity)
11
- *
12
- * MORPH-SPEC 3.0 - Sprint 4
13
- */
14
-
15
- import { readFileSync, writeFileSync, existsSync } from 'fs';
16
- import { glob } from 'glob';
17
- import { join } from 'path';
18
- import chalk from 'chalk';
19
-
20
- /**
21
- * Decision Categories
22
- */
23
- const DECISION_CATEGORIES = {
24
- uiLibrary: {
25
- keywords: ['fluent', 'mudblazor', 'radzen', 'ui library', 'component library'],
26
- options: ['Fluent UI', 'MudBlazor', 'Radzen', 'Custom']
27
- },
28
- architecture: {
29
- keywords: ['cqrs', 'repository', 'mediator', 'clean architecture', 'pattern'],
30
- options: ['CQRS', 'Repository Pattern', 'Service Layer', 'Direct EF', 'DDD']
31
- },
32
- infrastructure: {
33
- keywords: ['azure', 'sql', 'cosmos', 'storage', 'container apps', 'app service'],
34
- options: ['Azure SQL', 'Cosmos DB', 'Container Apps', 'App Service', 'Functions', 'Service Bus', 'Storage Queue']
35
- },
36
- authentication: {
37
- keywords: ['auth', 'login', 'identity', 'clerk', 'entra'],
38
- options: ['Clerk', 'Azure AD/Entra', 'Identity', 'Custom']
39
- },
40
- stateManagement: {
41
- keywords: ['state', 'fluxor', 'redux', 'signal'],
42
- options: ['Fluxor', 'Blazor State', 'In-Memory', 'API-driven']
43
- },
44
- testing: {
45
- keywords: ['test', 'xunit', 'nunit', 'bunit'],
46
- options: ['xUnit', 'NUnit', 'bUnit', 'Playwright']
47
- }
48
- };
49
-
50
- /**
51
- * Learning System Class
52
- */
53
- export class LearningSystem {
54
- constructor(projectPath = '.') {
55
- this.projectPath = projectPath;
56
- this.knowledgeBasePath = join(projectPath, '.morph', 'knowledge-base.json');
57
- this.knowledgeBase = this.loadKnowledgeBase();
58
- }
59
-
60
- /**
61
- * Load knowledge base from disk
62
- */
63
- loadKnowledgeBase() {
64
- if (existsSync(this.knowledgeBasePath)) {
65
- try {
66
- const content = readFileSync(this.knowledgeBasePath, 'utf-8');
67
- return JSON.parse(content);
68
- } catch (error) {
69
- console.warn(chalk.yellow('⚠️ Failed to load knowledge base, creating new one'));
70
- }
71
- }
72
-
73
- return this.createEmptyKnowledgeBase();
74
- }
75
-
76
- /**
77
- * Create empty knowledge base
78
- */
79
- createEmptyKnowledgeBase() {
80
- return {
81
- version: '3.0.0',
82
- lastUpdated: new Date().toISOString(),
83
- totalDecisions: 0,
84
- categories: Object.keys(DECISION_CATEGORIES).reduce((acc, category) => {
85
- acc[category] = {
86
- total: 0,
87
- choices: {},
88
- patterns: []
89
- };
90
- return acc;
91
- }, {}),
92
- features: [],
93
- insights: []
94
- };
95
- }
96
-
97
- /**
98
- * Save knowledge base to disk
99
- */
100
- saveKnowledgeBase() {
101
- this.knowledgeBase.lastUpdated = new Date().toISOString();
102
-
103
- try {
104
- writeFileSync(
105
- this.knowledgeBasePath,
106
- JSON.stringify(this.knowledgeBase, null, 2),
107
- 'utf-8'
108
- );
109
- } catch (error) {
110
- console.error(chalk.red('❌ Failed to save knowledge base:', error.message));
111
- }
112
- }
113
-
114
- /**
115
- * Learn from all features in project
116
- */
117
- async learnFromProject() {
118
- console.log(chalk.cyan('🧠 Learning from project history...\n'));
119
-
120
- // Find all decisions.md files
121
- const decisionFiles = await glob('.morph/project/outputs/*/decisions.md', {
122
- cwd: this.projectPath
123
- });
124
-
125
- if (decisionFiles.length === 0) {
126
- console.log(chalk.yellow('⚠️ No decisions.md files found'));
127
- return;
128
- }
129
-
130
- let newDecisions = 0;
131
-
132
- for (const file of decisionFiles) {
133
- const featureName = file.split('/')[3]; // Extract feature name from path
134
- const learned = await this.learnFromDecisionFile(file, featureName);
135
- newDecisions += learned;
136
- }
137
-
138
- // Generate insights
139
- this.generateInsights();
140
-
141
- // Save knowledge base
142
- this.saveKnowledgeBase();
143
-
144
- console.log(chalk.green(`\n✅ Learned ${newDecisions} new decisions from ${decisionFiles.length} features`));
145
- console.log(chalk.gray(` Total knowledge: ${this.knowledgeBase.totalDecisions} decisions`));
146
- }
147
-
148
- /**
149
- * Learn from single decisions.md file
150
- */
151
- async learnFromDecisionFile(filePath, featureName) {
152
- const content = readFileSync(join(this.projectPath, filePath), 'utf-8');
153
-
154
- // Check if already processed
155
- if (this.knowledgeBase.features.includes(featureName)) {
156
- return 0; // Already learned from this feature
157
- }
158
-
159
- let decisionsLearned = 0;
160
-
161
- // Parse ADRs (Architectural Decision Records)
162
- const adrs = this.parseADRs(content);
163
-
164
- for (const adr of adrs) {
165
- const category = this.categorizeDecision(adr);
166
-
167
- if (category) {
168
- this.recordDecision(category, adr, featureName);
169
- decisionsLearned++;
170
- }
171
- }
172
-
173
- // Mark feature as processed
174
- this.knowledgeBase.features.push(featureName);
175
-
176
- return decisionsLearned;
177
- }
178
-
179
- /**
180
- * Parse ADRs from decisions.md content
181
- */
182
- parseADRs(content) {
183
- const adrs = [];
184
- const adrPattern = /##\s+ADR-\d+:\s+(.+?)\n([\s\S]*?)(?=##\s+ADR-\d+:|$)/g;
185
- let match;
186
-
187
- while ((match = adrPattern.exec(content)) !== null) {
188
- const title = match[1].trim();
189
- const body = match[2].trim();
190
-
191
- // Extract decision, alternatives, rationale
192
- const decision = this.extractSection(body, 'Decision|Choice|Selected');
193
- const alternatives = this.extractSection(body, 'Alternatives|Options|Considered');
194
- const rationale = this.extractSection(body, 'Rationale|Justification|Why');
195
-
196
- adrs.push({
197
- title,
198
- decision,
199
- alternatives,
200
- rationale,
201
- fullText: body
202
- });
203
- }
204
-
205
- return adrs;
206
- }
207
-
208
- /**
209
- * Extract section from ADR body
210
- */
211
- extractSection(body, sectionPattern) {
212
- const regex = new RegExp(`\\*\\*${sectionPattern}\\*\\*:?\\s*([^*]+)`, 'i');
213
- const match = body.match(regex);
214
- return match ? match[1].trim() : '';
215
- }
216
-
217
- /**
218
- * Categorize decision into known categories
219
- */
220
- categorizeDecision(adr) {
221
- const text = `${adr.title} ${adr.decision} ${adr.fullText}`.toLowerCase();
222
-
223
- for (const [category, config] of Object.entries(DECISION_CATEGORIES)) {
224
- for (const keyword of config.keywords) {
225
- if (text.includes(keyword.toLowerCase())) {
226
- return category;
227
- }
228
- }
229
- }
230
-
231
- return null; // Uncategorized
232
- }
233
-
234
- /**
235
- * Record decision in knowledge base
236
- */
237
- recordDecision(category, adr, featureName) {
238
- const categoryData = this.knowledgeBase.categories[category];
239
-
240
- // Extract chosen option
241
- const chosen = this.extractChosenOption(adr, category);
242
-
243
- if (chosen) {
244
- // Increment choice count
245
- if (!categoryData.choices[chosen]) {
246
- categoryData.choices[chosen] = { count: 0, features: [], rationales: [] };
247
- }
248
-
249
- categoryData.choices[chosen].count++;
250
- categoryData.choices[chosen].features.push(featureName);
251
-
252
- if (adr.rationale) {
253
- categoryData.choices[chosen].rationales.push(adr.rationale);
254
- }
255
- }
256
-
257
- // Store pattern
258
- categoryData.patterns.push({
259
- feature: featureName,
260
- decision: adr.decision,
261
- rationale: adr.rationale
262
- });
263
-
264
- categoryData.total++;
265
- this.knowledgeBase.totalDecisions++;
266
- }
267
-
268
- /**
269
- * Extract chosen option from ADR
270
- */
271
- extractChosenOption(adr, category) {
272
- const config = DECISION_CATEGORIES[category];
273
- const text = `${adr.title} ${adr.decision}`.toLowerCase();
274
-
275
- for (const option of config.options) {
276
- if (text.includes(option.toLowerCase())) {
277
- return option;
278
- }
279
- }
280
-
281
- return null;
282
- }
283
-
284
- /**
285
- * Generate insights from learned patterns
286
- */
287
- generateInsights() {
288
- const insights = [];
289
-
290
- for (const [category, data] of Object.entries(this.knowledgeBase.categories)) {
291
- if (data.total === 0) continue;
292
-
293
- // Find most common choice
294
- const sortedChoices = Object.entries(data.choices)
295
- .sort(([, a], [, b]) => b.count - a.count);
296
-
297
- if (sortedChoices.length > 0) {
298
- const [topChoice, topData] = sortedChoices[0];
299
- const percentage = ((topData.count / data.total) * 100).toFixed(0);
300
-
301
- insights.push({
302
- category,
303
- type: 'preference',
304
- insight: `User prefers ${topChoice} for ${category} (${percentage}% of decisions)`,
305
- confidence: percentage >= 70 ? 'high' : percentage >= 50 ? 'medium' : 'low',
306
- suggestion: `Suggest ${topChoice} by default for ${category} decisions`,
307
- evidence: {
308
- totalDecisions: data.total,
309
- choiceCount: topData.count,
310
- features: topData.features
311
- }
312
- });
313
- }
314
-
315
- // Detect patterns (e.g., always use CQRS with complex features)
316
- if (category === 'architecture' && data.patterns.length > 3) {
317
- const cqrsPattern = data.patterns.filter(p =>
318
- p.decision.toLowerCase().includes('cqrs')
319
- );
320
-
321
- if (cqrsPattern.length > 0) {
322
- insights.push({
323
- category,
324
- type: 'pattern',
325
- insight: 'User tends to use CQRS for complex features',
326
- confidence: 'medium',
327
- suggestion: 'Suggest CQRS when feature complexity is high',
328
- evidence: {
329
- occurrences: cqrsPattern.length,
330
- features: cqrsPattern.map(p => p.feature)
331
- }
332
- });
333
- }
334
- }
335
- }
336
-
337
- this.knowledgeBase.insights = insights;
338
- }
339
-
340
- /**
341
- * Get suggestion for a category
342
- */
343
- getSuggestion(category, context = {}) {
344
- const categoryData = this.knowledgeBase.categories[category];
345
-
346
- if (!categoryData || categoryData.total === 0) {
347
- return {
348
- category,
349
- suggestion: null,
350
- confidence: 'none',
351
- reason: 'No historical data for this category'
352
- };
353
- }
354
-
355
- // Find most common choice
356
- const sortedChoices = Object.entries(categoryData.choices)
357
- .sort(([, a], [, b]) => b.count - a.count);
358
-
359
- if (sortedChoices.length === 0) {
360
- return {
361
- category,
362
- suggestion: null,
363
- confidence: 'none',
364
- reason: 'No choices recorded'
365
- };
366
- }
367
-
368
- const [topChoice, topData] = sortedChoices[0];
369
- const percentage = (topData.count / categoryData.total) * 100;
370
-
371
- const confidence = percentage >= 70 ? 'high' : percentage >= 50 ? 'medium' : 'low';
372
-
373
- return {
374
- category,
375
- suggestion: topChoice,
376
- confidence,
377
- percentage: percentage.toFixed(0),
378
- reason: `You chose ${topChoice} in ${topData.count} of ${categoryData.total} previous ${category} decisions`,
379
- alternatives: sortedChoices.slice(1, 3).map(([choice, data]) => ({
380
- name: choice,
381
- count: data.count,
382
- percentage: ((data.count / categoryData.total) * 100).toFixed(0)
383
- })),
384
- recentFeatures: topData.features.slice(-3)
385
- };
386
- }
387
-
388
- /**
389
- * Get all suggestions for current context
390
- */
391
- getAllSuggestions(context = {}) {
392
- const suggestions = {};
393
-
394
- for (const category of Object.keys(DECISION_CATEGORIES)) {
395
- suggestions[category] = this.getSuggestion(category, context);
396
- }
397
-
398
- return suggestions;
399
- }
400
-
401
- /**
402
- * Format suggestions for console output
403
- */
404
- formatSuggestions(suggestions) {
405
- console.log(chalk.cyan('\n💡 AI Suggestions based on your preferences:\n'));
406
-
407
- for (const [category, suggestion] of Object.entries(suggestions)) {
408
- if (suggestion.confidence === 'none') continue;
409
-
410
- const confidenceColor = {
411
- high: chalk.green,
412
- medium: chalk.yellow,
413
- low: chalk.gray
414
- }[suggestion.confidence];
415
-
416
- console.log(confidenceColor(` ${category}:`));
417
- console.log(chalk.white(` → ${suggestion.suggestion} (${suggestion.percentage}% confidence)`));
418
- console.log(chalk.gray(` ${suggestion.reason}`));
419
-
420
- if (suggestion.alternatives && suggestion.alternatives.length > 0) {
421
- console.log(chalk.gray(` Alternatives: ${suggestion.alternatives.map(a => `${a.name} (${a.percentage}%)`).join(', ')}`));
422
- }
423
-
424
- console.log('');
425
- }
426
- }
427
-
428
- /**
429
- * Get insights summary
430
- */
431
- getInsightsSummary() {
432
- const insights = this.knowledgeBase.insights;
433
-
434
- if (insights.length === 0) {
435
- return {
436
- totalInsights: 0,
437
- message: 'No insights generated yet. Build more features to learn preferences.'
438
- };
439
- }
440
-
441
- const highConfidence = insights.filter(i => i.confidence === 'high');
442
- const mediumConfidence = insights.filter(i => i.confidence === 'medium');
443
-
444
- return {
445
- totalInsights: insights.length,
446
- highConfidence: highConfidence.length,
447
- mediumConfidence: mediumConfidence.length,
448
- insights: insights.map(i => ({
449
- category: i.category,
450
- insight: i.insight,
451
- confidence: i.confidence,
452
- suggestion: i.suggestion
453
- }))
454
- };
455
- }
456
-
457
- /**
458
- * Format insights for console output
459
- */
460
- formatInsights() {
461
- const summary = this.getInsightsSummary();
462
-
463
- if (summary.totalInsights === 0) {
464
- console.log(chalk.yellow(`\n⚠️ ${summary.message}\n`));
465
- return;
466
- }
467
-
468
- console.log(chalk.cyan(`\n🧠 Learning Insights (${summary.totalInsights} patterns detected):\n`));
469
-
470
- for (const insight of summary.insights) {
471
- const confidenceEmoji = {
472
- high: '🟢',
473
- medium: '🟡',
474
- low: '🔵'
475
- }[insight.confidence];
476
-
477
- console.log(`${confidenceEmoji} ${chalk.bold(insight.category)}: ${insight.insight}`);
478
- console.log(chalk.gray(` Suggestion: ${insight.suggestion}\n`));
479
- }
480
- }
481
-
482
- /**
483
- * Reset knowledge base (for testing or fresh start)
484
- */
485
- reset() {
486
- this.knowledgeBase = this.createEmptyKnowledgeBase();
487
- this.saveKnowledgeBase();
488
- console.log(chalk.yellow('⚠️ Knowledge base reset'));
489
- }
490
-
491
- /**
492
- * Export knowledge base for analysis
493
- */
494
- export() {
495
- return {
496
- ...this.knowledgeBase,
497
- summary: this.getInsightsSummary()
498
- };
499
- }
500
- }
501
-
502
- /**
503
- * Quick functions for imports
504
- */
505
- export async function learnFromProject(projectPath = '.') {
506
- const learner = new LearningSystem(projectPath);
507
- await learner.learnFromProject();
508
- return learner;
509
- }
510
-
511
- export function getSuggestions(projectPath = '.', context = {}) {
512
- const learner = new LearningSystem(projectPath);
513
- return learner.getAllSuggestions(context);
514
- }
515
-
516
- export function showInsights(projectPath = '.') {
517
- const learner = new LearningSystem(projectPath);
518
- learner.formatInsights();
519
- return learner.getInsightsSummary();
520
- }
1
+ /**
2
+ * Learning System
3
+ *
4
+ * Tracks user decisions and learns preferences over time.
5
+ * Provides contextual suggestions based on historical patterns.
6
+ *
7
+ * Learns from:
8
+ * - decisions.md files (ADRs from each feature)
9
+ * - .morph/project/standards/*.md (project standards)
10
+ * - state.json (feature outcomes and complexity)
11
+ *
12
+ * MORPH-SPEC 3.0 - Sprint 4
13
+ */
14
+
15
+ import { readFileSync, writeFileSync, existsSync } from 'fs';
16
+ import { glob } from 'glob';
17
+ import { join } from 'path';
18
+ import chalk from 'chalk';
19
+
20
+ /**
21
+ * Decision Categories
22
+ */
23
+ const DECISION_CATEGORIES = {
24
+ uiLibrary: {
25
+ keywords: ['fluent', 'mudblazor', 'radzen', 'ui library', 'component library'],
26
+ options: ['Fluent UI', 'MudBlazor', 'Radzen', 'Custom']
27
+ },
28
+ architecture: {
29
+ keywords: ['cqrs', 'repository', 'mediator', 'clean architecture', 'pattern'],
30
+ options: ['CQRS', 'Repository Pattern', 'Service Layer', 'Direct EF', 'DDD']
31
+ },
32
+ infrastructure: {
33
+ keywords: ['azure', 'sql', 'cosmos', 'storage', 'container apps', 'app service'],
34
+ options: ['Azure SQL', 'Cosmos DB', 'Container Apps', 'App Service', 'Functions', 'Service Bus', 'Storage Queue']
35
+ },
36
+ authentication: {
37
+ keywords: ['auth', 'login', 'identity', 'clerk', 'entra'],
38
+ options: ['Clerk', 'Azure AD/Entra', 'Identity', 'Custom']
39
+ },
40
+ stateManagement: {
41
+ keywords: ['state', 'fluxor', 'redux', 'signal'],
42
+ options: ['Fluxor', 'Blazor State', 'In-Memory', 'API-driven']
43
+ },
44
+ testing: {
45
+ keywords: ['test', 'xunit', 'nunit', 'bunit'],
46
+ options: ['xUnit', 'NUnit', 'bUnit', 'Playwright']
47
+ }
48
+ };
49
+
50
+ /**
51
+ * Learning System Class
52
+ */
53
+ export class LearningSystem {
54
+ constructor(projectPath = '.') {
55
+ this.projectPath = projectPath;
56
+ this.knowledgeBasePath = join(projectPath, '.morph', 'knowledge-base.json');
57
+ this.knowledgeBase = this.loadKnowledgeBase();
58
+ }
59
+
60
+ /**
61
+ * Load knowledge base from disk
62
+ */
63
+ loadKnowledgeBase() {
64
+ if (existsSync(this.knowledgeBasePath)) {
65
+ try {
66
+ const content = readFileSync(this.knowledgeBasePath, 'utf-8');
67
+ return JSON.parse(content);
68
+ } catch (error) {
69
+ console.warn(chalk.yellow('⚠️ Failed to load knowledge base, creating new one'));
70
+ }
71
+ }
72
+
73
+ return this.createEmptyKnowledgeBase();
74
+ }
75
+
76
+ /**
77
+ * Create empty knowledge base
78
+ */
79
+ createEmptyKnowledgeBase() {
80
+ return {
81
+ version: '3.0.0',
82
+ lastUpdated: new Date().toISOString(),
83
+ totalDecisions: 0,
84
+ categories: Object.keys(DECISION_CATEGORIES).reduce((acc, category) => {
85
+ acc[category] = {
86
+ total: 0,
87
+ choices: {},
88
+ patterns: []
89
+ };
90
+ return acc;
91
+ }, {}),
92
+ features: [],
93
+ insights: []
94
+ };
95
+ }
96
+
97
+ /**
98
+ * Save knowledge base to disk
99
+ */
100
+ saveKnowledgeBase() {
101
+ this.knowledgeBase.lastUpdated = new Date().toISOString();
102
+
103
+ try {
104
+ writeFileSync(
105
+ this.knowledgeBasePath,
106
+ JSON.stringify(this.knowledgeBase, null, 2),
107
+ 'utf-8'
108
+ );
109
+ } catch (error) {
110
+ console.error(chalk.red('❌ Failed to save knowledge base:', error.message));
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Learn from all features in project
116
+ */
117
+ async learnFromProject() {
118
+ console.log(chalk.cyan('🧠 Learning from project history...\n'));
119
+
120
+ // Find all decisions.md files
121
+ const decisionFiles = await glob('.morph/project/outputs/*/decisions.md', {
122
+ cwd: this.projectPath
123
+ });
124
+
125
+ if (decisionFiles.length === 0) {
126
+ console.log(chalk.yellow('⚠️ No decisions.md files found'));
127
+ return;
128
+ }
129
+
130
+ let newDecisions = 0;
131
+
132
+ for (const file of decisionFiles) {
133
+ const featureName = file.split('/')[3]; // Extract feature name from path
134
+ const learned = await this.learnFromDecisionFile(file, featureName);
135
+ newDecisions += learned;
136
+ }
137
+
138
+ // Generate insights
139
+ this.generateInsights();
140
+
141
+ // Save knowledge base
142
+ this.saveKnowledgeBase();
143
+
144
+ console.log(chalk.green(`\n✅ Learned ${newDecisions} new decisions from ${decisionFiles.length} features`));
145
+ console.log(chalk.gray(` Total knowledge: ${this.knowledgeBase.totalDecisions} decisions`));
146
+ }
147
+
148
+ /**
149
+ * Learn from single decisions.md file
150
+ */
151
+ async learnFromDecisionFile(filePath, featureName) {
152
+ const content = readFileSync(join(this.projectPath, filePath), 'utf-8');
153
+
154
+ // Check if already processed
155
+ if (this.knowledgeBase.features.includes(featureName)) {
156
+ return 0; // Already learned from this feature
157
+ }
158
+
159
+ let decisionsLearned = 0;
160
+
161
+ // Parse ADRs (Architectural Decision Records)
162
+ const adrs = this.parseADRs(content);
163
+
164
+ for (const adr of adrs) {
165
+ const category = this.categorizeDecision(adr);
166
+
167
+ if (category) {
168
+ this.recordDecision(category, adr, featureName);
169
+ decisionsLearned++;
170
+ }
171
+ }
172
+
173
+ // Mark feature as processed
174
+ this.knowledgeBase.features.push(featureName);
175
+
176
+ return decisionsLearned;
177
+ }
178
+
179
+ /**
180
+ * Parse ADRs from decisions.md content
181
+ */
182
+ parseADRs(content) {
183
+ const adrs = [];
184
+ const adrPattern = /##\s+ADR-\d+:\s+(.+?)\n([\s\S]*?)(?=##\s+ADR-\d+:|$)/g;
185
+ let match;
186
+
187
+ while ((match = adrPattern.exec(content)) !== null) {
188
+ const title = match[1].trim();
189
+ const body = match[2].trim();
190
+
191
+ // Extract decision, alternatives, rationale
192
+ const decision = this.extractSection(body, 'Decision|Choice|Selected');
193
+ const alternatives = this.extractSection(body, 'Alternatives|Options|Considered');
194
+ const rationale = this.extractSection(body, 'Rationale|Justification|Why');
195
+
196
+ adrs.push({
197
+ title,
198
+ decision,
199
+ alternatives,
200
+ rationale,
201
+ fullText: body
202
+ });
203
+ }
204
+
205
+ return adrs;
206
+ }
207
+
208
+ /**
209
+ * Extract section from ADR body
210
+ */
211
+ extractSection(body, sectionPattern) {
212
+ const regex = new RegExp(`\\*\\*${sectionPattern}\\*\\*:?\\s*([^*]+)`, 'i');
213
+ const match = body.match(regex);
214
+ return match ? match[1].trim() : '';
215
+ }
216
+
217
+ /**
218
+ * Categorize decision into known categories
219
+ */
220
+ categorizeDecision(adr) {
221
+ const text = `${adr.title} ${adr.decision} ${adr.fullText}`.toLowerCase();
222
+
223
+ for (const [category, config] of Object.entries(DECISION_CATEGORIES)) {
224
+ for (const keyword of config.keywords) {
225
+ if (text.includes(keyword.toLowerCase())) {
226
+ return category;
227
+ }
228
+ }
229
+ }
230
+
231
+ return null; // Uncategorized
232
+ }
233
+
234
+ /**
235
+ * Record decision in knowledge base
236
+ */
237
+ recordDecision(category, adr, featureName) {
238
+ const categoryData = this.knowledgeBase.categories[category];
239
+
240
+ // Extract chosen option
241
+ const chosen = this.extractChosenOption(adr, category);
242
+
243
+ if (chosen) {
244
+ // Increment choice count
245
+ if (!categoryData.choices[chosen]) {
246
+ categoryData.choices[chosen] = { count: 0, features: [], rationales: [] };
247
+ }
248
+
249
+ categoryData.choices[chosen].count++;
250
+ categoryData.choices[chosen].features.push(featureName);
251
+
252
+ if (adr.rationale) {
253
+ categoryData.choices[chosen].rationales.push(adr.rationale);
254
+ }
255
+ }
256
+
257
+ // Store pattern
258
+ categoryData.patterns.push({
259
+ feature: featureName,
260
+ decision: adr.decision,
261
+ rationale: adr.rationale
262
+ });
263
+
264
+ categoryData.total++;
265
+ this.knowledgeBase.totalDecisions++;
266
+ }
267
+
268
+ /**
269
+ * Extract chosen option from ADR
270
+ */
271
+ extractChosenOption(adr, category) {
272
+ const config = DECISION_CATEGORIES[category];
273
+ const text = `${adr.title} ${adr.decision}`.toLowerCase();
274
+
275
+ for (const option of config.options) {
276
+ if (text.includes(option.toLowerCase())) {
277
+ return option;
278
+ }
279
+ }
280
+
281
+ return null;
282
+ }
283
+
284
+ /**
285
+ * Generate insights from learned patterns
286
+ */
287
+ generateInsights() {
288
+ const insights = [];
289
+
290
+ for (const [category, data] of Object.entries(this.knowledgeBase.categories)) {
291
+ if (data.total === 0) continue;
292
+
293
+ // Find most common choice
294
+ const sortedChoices = Object.entries(data.choices)
295
+ .sort(([, a], [, b]) => b.count - a.count);
296
+
297
+ if (sortedChoices.length > 0) {
298
+ const [topChoice, topData] = sortedChoices[0];
299
+ const percentage = ((topData.count / data.total) * 100).toFixed(0);
300
+
301
+ insights.push({
302
+ category,
303
+ type: 'preference',
304
+ insight: `User prefers ${topChoice} for ${category} (${percentage}% of decisions)`,
305
+ confidence: percentage >= 70 ? 'high' : percentage >= 50 ? 'medium' : 'low',
306
+ suggestion: `Suggest ${topChoice} by default for ${category} decisions`,
307
+ evidence: {
308
+ totalDecisions: data.total,
309
+ choiceCount: topData.count,
310
+ features: topData.features
311
+ }
312
+ });
313
+ }
314
+
315
+ // Detect patterns (e.g., always use CQRS with complex features)
316
+ if (category === 'architecture' && data.patterns.length > 3) {
317
+ const cqrsPattern = data.patterns.filter(p =>
318
+ p.decision.toLowerCase().includes('cqrs')
319
+ );
320
+
321
+ if (cqrsPattern.length > 0) {
322
+ insights.push({
323
+ category,
324
+ type: 'pattern',
325
+ insight: 'User tends to use CQRS for complex features',
326
+ confidence: 'medium',
327
+ suggestion: 'Suggest CQRS when feature complexity is high',
328
+ evidence: {
329
+ occurrences: cqrsPattern.length,
330
+ features: cqrsPattern.map(p => p.feature)
331
+ }
332
+ });
333
+ }
334
+ }
335
+ }
336
+
337
+ this.knowledgeBase.insights = insights;
338
+ }
339
+
340
+ /**
341
+ * Get suggestion for a category
342
+ */
343
+ getSuggestion(category, context = {}) {
344
+ const categoryData = this.knowledgeBase.categories[category];
345
+
346
+ if (!categoryData || categoryData.total === 0) {
347
+ return {
348
+ category,
349
+ suggestion: null,
350
+ confidence: 'none',
351
+ reason: 'No historical data for this category'
352
+ };
353
+ }
354
+
355
+ // Find most common choice
356
+ const sortedChoices = Object.entries(categoryData.choices)
357
+ .sort(([, a], [, b]) => b.count - a.count);
358
+
359
+ if (sortedChoices.length === 0) {
360
+ return {
361
+ category,
362
+ suggestion: null,
363
+ confidence: 'none',
364
+ reason: 'No choices recorded'
365
+ };
366
+ }
367
+
368
+ const [topChoice, topData] = sortedChoices[0];
369
+ const percentage = (topData.count / categoryData.total) * 100;
370
+
371
+ const confidence = percentage >= 70 ? 'high' : percentage >= 50 ? 'medium' : 'low';
372
+
373
+ return {
374
+ category,
375
+ suggestion: topChoice,
376
+ confidence,
377
+ percentage: percentage.toFixed(0),
378
+ reason: `You chose ${topChoice} in ${topData.count} of ${categoryData.total} previous ${category} decisions`,
379
+ alternatives: sortedChoices.slice(1, 3).map(([choice, data]) => ({
380
+ name: choice,
381
+ count: data.count,
382
+ percentage: ((data.count / categoryData.total) * 100).toFixed(0)
383
+ })),
384
+ recentFeatures: topData.features.slice(-3)
385
+ };
386
+ }
387
+
388
+ /**
389
+ * Get all suggestions for current context
390
+ */
391
+ getAllSuggestions(context = {}) {
392
+ const suggestions = {};
393
+
394
+ for (const category of Object.keys(DECISION_CATEGORIES)) {
395
+ suggestions[category] = this.getSuggestion(category, context);
396
+ }
397
+
398
+ return suggestions;
399
+ }
400
+
401
+ /**
402
+ * Format suggestions for console output
403
+ */
404
+ formatSuggestions(suggestions) {
405
+ console.log(chalk.cyan('\n💡 AI Suggestions based on your preferences:\n'));
406
+
407
+ for (const [category, suggestion] of Object.entries(suggestions)) {
408
+ if (suggestion.confidence === 'none') continue;
409
+
410
+ const confidenceColor = {
411
+ high: chalk.green,
412
+ medium: chalk.yellow,
413
+ low: chalk.gray
414
+ }[suggestion.confidence];
415
+
416
+ console.log(confidenceColor(` ${category}:`));
417
+ console.log(chalk.white(` → ${suggestion.suggestion} (${suggestion.percentage}% confidence)`));
418
+ console.log(chalk.gray(` ${suggestion.reason}`));
419
+
420
+ if (suggestion.alternatives && suggestion.alternatives.length > 0) {
421
+ console.log(chalk.gray(` Alternatives: ${suggestion.alternatives.map(a => `${a.name} (${a.percentage}%)`).join(', ')}`));
422
+ }
423
+
424
+ console.log('');
425
+ }
426
+ }
427
+
428
+ /**
429
+ * Get insights summary
430
+ */
431
+ getInsightsSummary() {
432
+ const insights = this.knowledgeBase.insights;
433
+
434
+ if (insights.length === 0) {
435
+ return {
436
+ totalInsights: 0,
437
+ message: 'No insights generated yet. Build more features to learn preferences.'
438
+ };
439
+ }
440
+
441
+ const highConfidence = insights.filter(i => i.confidence === 'high');
442
+ const mediumConfidence = insights.filter(i => i.confidence === 'medium');
443
+
444
+ return {
445
+ totalInsights: insights.length,
446
+ highConfidence: highConfidence.length,
447
+ mediumConfidence: mediumConfidence.length,
448
+ insights: insights.map(i => ({
449
+ category: i.category,
450
+ insight: i.insight,
451
+ confidence: i.confidence,
452
+ suggestion: i.suggestion
453
+ }))
454
+ };
455
+ }
456
+
457
+ /**
458
+ * Format insights for console output
459
+ */
460
+ formatInsights() {
461
+ const summary = this.getInsightsSummary();
462
+
463
+ if (summary.totalInsights === 0) {
464
+ console.log(chalk.yellow(`\n⚠️ ${summary.message}\n`));
465
+ return;
466
+ }
467
+
468
+ console.log(chalk.cyan(`\n🧠 Learning Insights (${summary.totalInsights} patterns detected):\n`));
469
+
470
+ for (const insight of summary.insights) {
471
+ const confidenceEmoji = {
472
+ high: '🟢',
473
+ medium: '🟡',
474
+ low: '🔵'
475
+ }[insight.confidence];
476
+
477
+ console.log(`${confidenceEmoji} ${chalk.bold(insight.category)}: ${insight.insight}`);
478
+ console.log(chalk.gray(` Suggestion: ${insight.suggestion}\n`));
479
+ }
480
+ }
481
+
482
+ /**
483
+ * Reset knowledge base (for testing or fresh start)
484
+ */
485
+ reset() {
486
+ this.knowledgeBase = this.createEmptyKnowledgeBase();
487
+ this.saveKnowledgeBase();
488
+ console.log(chalk.yellow('⚠️ Knowledge base reset'));
489
+ }
490
+
491
+ /**
492
+ * Export knowledge base for analysis
493
+ */
494
+ export() {
495
+ return {
496
+ ...this.knowledgeBase,
497
+ summary: this.getInsightsSummary()
498
+ };
499
+ }
500
+ }
501
+
502
+ /**
503
+ * Quick functions for imports
504
+ */
505
+ export async function learnFromProject(projectPath = '.') {
506
+ const learner = new LearningSystem(projectPath);
507
+ await learner.learnFromProject();
508
+ return learner;
509
+ }
510
+
511
+ export function getSuggestions(projectPath = '.', context = {}) {
512
+ const learner = new LearningSystem(projectPath);
513
+ return learner.getAllSuggestions(context);
514
+ }
515
+
516
+ export function showInsights(projectPath = '.') {
517
+ const learner = new LearningSystem(projectPath);
518
+ learner.formatInsights();
519
+ return learner.getInsightsSummary();
520
+ }