devmind 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 (122) hide show
  1. package/README.md +126 -162
  2. package/dist/cli/handlers.d.ts +2 -0
  3. package/dist/cli/handlers.js +140 -0
  4. package/dist/cli/handlers.js.map +1 -0
  5. package/dist/cli/register-all.d.ts +2 -0
  6. package/dist/cli/register-all.js +11 -0
  7. package/dist/cli/register-all.js.map +1 -0
  8. package/dist/cli/register-analysis.d.ts +2 -0
  9. package/dist/cli/register-analysis.js +137 -0
  10. package/dist/cli/register-analysis.js.map +1 -0
  11. package/dist/cli/register-codebase.d.ts +2 -0
  12. package/dist/cli/register-codebase.js +22 -0
  13. package/dist/cli/register-codebase.js.map +1 -0
  14. package/dist/cli/register-database.d.ts +2 -0
  15. package/dist/cli/register-database.js +44 -0
  16. package/dist/cli/register-database.js.map +1 -0
  17. package/dist/cli/register-memory.d.ts +2 -0
  18. package/dist/cli/register-memory.js +58 -0
  19. package/dist/cli/register-memory.js.map +1 -0
  20. package/dist/cli.js +9 -316
  21. package/dist/cli.js.map +1 -1
  22. package/dist/codebase/index.js +2 -2
  23. package/dist/codebase/index.js.map +1 -1
  24. package/dist/codebase/parsers/typescript.d.ts +1 -0
  25. package/dist/codebase/parsers/typescript.js +3 -0
  26. package/dist/codebase/parsers/typescript.js.map +1 -1
  27. package/dist/codebase/scanners/filesystem.d.ts +1 -0
  28. package/dist/codebase/scanners/filesystem.js +131 -4
  29. package/dist/codebase/scanners/filesystem.js.map +1 -1
  30. package/dist/commands/analyze.d.ts +1 -0
  31. package/dist/commands/analyze.js +166 -24
  32. package/dist/commands/analyze.js.map +1 -1
  33. package/dist/commands/audit-design.d.ts +8 -0
  34. package/dist/commands/audit-design.js +158 -0
  35. package/dist/commands/audit-design.js.map +1 -0
  36. package/dist/commands/audit-report.d.ts +18 -0
  37. package/dist/commands/audit-report.js +30 -0
  38. package/dist/commands/audit-report.js.map +1 -0
  39. package/dist/commands/audit-source.d.ts +21 -0
  40. package/dist/commands/audit-source.js +57 -0
  41. package/dist/commands/audit-source.js.map +1 -0
  42. package/dist/commands/audit.d.ts +1 -0
  43. package/dist/commands/audit.js +174 -73
  44. package/dist/commands/audit.js.map +1 -1
  45. package/dist/commands/claude-plugin.d.ts +8 -0
  46. package/dist/commands/claude-plugin.js +123 -0
  47. package/dist/commands/claude-plugin.js.map +1 -0
  48. package/dist/commands/codex-plugin.d.ts +9 -0
  49. package/dist/commands/codex-plugin.js +145 -0
  50. package/dist/commands/codex-plugin.js.map +1 -0
  51. package/dist/commands/context.js +66 -5
  52. package/dist/commands/context.js.map +1 -1
  53. package/dist/commands/design-system.d.ts +8 -0
  54. package/dist/commands/design-system.js +95 -0
  55. package/dist/commands/design-system.js.map +1 -0
  56. package/dist/commands/extract.d.ts +13 -0
  57. package/dist/commands/extract.js +79 -16
  58. package/dist/commands/extract.js.map +1 -1
  59. package/dist/commands/openclaw-plugin.d.ts +8 -0
  60. package/dist/commands/openclaw-plugin.js +103 -0
  61. package/dist/commands/openclaw-plugin.js.map +1 -0
  62. package/dist/commands/retrieve.d.ts +12 -0
  63. package/dist/commands/retrieve.js +230 -0
  64. package/dist/commands/retrieve.js.map +1 -0
  65. package/dist/commands/status.d.ts +1 -0
  66. package/dist/commands/status.js +54 -18
  67. package/dist/commands/status.js.map +1 -1
  68. package/dist/core/cache-json.d.ts +7 -0
  69. package/dist/core/cache-json.js +60 -0
  70. package/dist/core/cache-json.js.map +1 -0
  71. package/dist/core/errors.d.ts +2 -0
  72. package/dist/core/errors.js +49 -1
  73. package/dist/core/errors.js.map +1 -1
  74. package/dist/core/index.d.ts +4 -0
  75. package/dist/core/index.js +4 -0
  76. package/dist/core/index.js.map +1 -1
  77. package/dist/core/learning-parser.d.ts +6 -0
  78. package/dist/core/learning-parser.js +23 -0
  79. package/dist/core/learning-parser.js.map +1 -0
  80. package/dist/core/profile.d.ts +18 -0
  81. package/dist/core/profile.js +37 -0
  82. package/dist/core/profile.js.map +1 -0
  83. package/dist/core/source-file-cache.d.ts +13 -0
  84. package/dist/core/source-file-cache.js +85 -0
  85. package/dist/core/source-file-cache.js.map +1 -0
  86. package/dist/database/cli/register-all.d.ts +2 -0
  87. package/dist/database/cli/register-all.js +9 -0
  88. package/dist/database/cli/register-all.js.map +1 -0
  89. package/dist/database/cli/register-database.d.ts +2 -0
  90. package/dist/database/cli/register-database.js +44 -0
  91. package/dist/database/cli/register-database.js.map +1 -0
  92. package/dist/database/cli/register-interactive.d.ts +2 -0
  93. package/dist/database/cli/register-interactive.js +29 -0
  94. package/dist/database/cli/register-interactive.js.map +1 -0
  95. package/dist/database/cli/register-memory.d.ts +2 -0
  96. package/dist/database/cli/register-memory.js +47 -0
  97. package/dist/database/cli/register-memory.js.map +1 -0
  98. package/dist/database/cli.js +5 -112
  99. package/dist/database/cli.js.map +1 -1
  100. package/dist/database/commands/checkpoint.js +8 -10
  101. package/dist/database/commands/checkpoint.js.map +1 -1
  102. package/dist/database/commands/generate.js +6 -7
  103. package/dist/database/commands/generate.js.map +1 -1
  104. package/dist/database/commands/handoff.js +9 -8
  105. package/dist/database/commands/handoff.js.map +1 -1
  106. package/dist/database/commands/learn.d.ts +4 -0
  107. package/dist/database/commands/learn.js +245 -43
  108. package/dist/database/commands/learn.js.map +1 -1
  109. package/dist/database/commands/memory.js +1 -1
  110. package/dist/database/commands/validate.js +6 -4
  111. package/dist/database/commands/validate.js.map +1 -1
  112. package/dist/database/commands/watch.js +1 -1
  113. package/dist/database/commands/watch.js.map +1 -1
  114. package/dist/database/generators/templates.js +26 -26
  115. package/dist/database/generators/templates.js.map +1 -1
  116. package/dist/database/utils/json-output.d.ts +4 -0
  117. package/dist/database/utils/json-output.js +7 -0
  118. package/dist/database/utils/json-output.js.map +1 -1
  119. package/dist/generators/unified.d.ts +13 -0
  120. package/dist/generators/unified.js +390 -46
  121. package/dist/generators/unified.js.map +1 -1
  122. package/package.json +2 -2
@@ -0,0 +1,158 @@
1
+ import * as path from 'path';
2
+ import * as fs from 'fs';
3
+ import { readFileSafe } from '../core/index.js';
4
+ function isLikelyUiFile(filePath) {
5
+ const normalized = filePath.toLowerCase();
6
+ return (normalized.endsWith('.tsx') ||
7
+ normalized.endsWith('.jsx') ||
8
+ normalized.includes('/components/') ||
9
+ normalized.includes('/ui/') ||
10
+ normalized.includes('/pages/') ||
11
+ normalized.includes('/app/'));
12
+ }
13
+ function collectImports(content) {
14
+ const values = new Set();
15
+ const importFromRegex = /import\s+[^'"]*from\s+['"]([^'"]+)['"]/g;
16
+ const dynamicImportRegex = /import\(\s*['"]([^'"]+)['"]\s*\)/g;
17
+ let match;
18
+ while ((match = importFromRegex.exec(content)) !== null)
19
+ values.add(match[1]);
20
+ while ((match = dynamicImportRegex.exec(content)) !== null)
21
+ values.add(match[1]);
22
+ return [...values];
23
+ }
24
+ function isUiLibraryImport(value) {
25
+ const uiCandidates = [
26
+ '@mui/',
27
+ '@material-ui/',
28
+ 'antd',
29
+ '@chakra-ui/',
30
+ 'semantic-ui-react',
31
+ 'rebass',
32
+ 'theme-ui',
33
+ '@headlessui/',
34
+ ];
35
+ return uiCandidates.some((candidate) => value === candidate || value.startsWith(candidate));
36
+ }
37
+ export async function collectDesignAuditFindings(rootPath, designSystemPath, fileContents) {
38
+ const designFindings = [];
39
+ if (!fs.existsSync(designSystemPath))
40
+ return designFindings;
41
+ try {
42
+ const parsed = JSON.parse(await readFileSafe(designSystemPath));
43
+ const uiFileContents = new Map();
44
+ for (const [relPath, content] of fileContents.entries()) {
45
+ if (isLikelyUiFile(relPath) || relPath.endsWith('.css') || relPath.endsWith('.scss')) {
46
+ uiFileContents.set(relPath, content);
47
+ }
48
+ }
49
+ const bannedRules = (parsed.bannedRegexRules || []).filter((rule) => !!rule.pattern && !!rule.id);
50
+ const compiledBannedRules = bannedRules.flatMap((rule) => {
51
+ try {
52
+ return [
53
+ {
54
+ rule,
55
+ regex: new RegExp(rule.pattern, 'm'),
56
+ },
57
+ ];
58
+ }
59
+ catch {
60
+ designFindings.push({
61
+ severity: 'warn',
62
+ rule: rule.id,
63
+ message: `Invalid regex pattern skipped: ${rule.pattern}`,
64
+ });
65
+ return [];
66
+ }
67
+ });
68
+ for (const [relPath, content] of uiFileContents.entries()) {
69
+ for (const { rule, regex } of compiledBannedRules) {
70
+ if (regex.test(content)) {
71
+ designFindings.push({
72
+ severity: 'error',
73
+ rule: rule.id,
74
+ file: relPath,
75
+ message: rule.message || `Matched banned pattern: ${rule.pattern}`,
76
+ });
77
+ }
78
+ }
79
+ }
80
+ const tokenSources = parsed.tokenSources || [];
81
+ for (const tokenSource of tokenSources) {
82
+ const abs = path.resolve(rootPath, tokenSource);
83
+ if (!fs.existsSync(abs)) {
84
+ designFindings.push({
85
+ severity: 'warn',
86
+ rule: 'token-source-missing',
87
+ file: tokenSource,
88
+ message: 'Token source path not found.',
89
+ });
90
+ }
91
+ }
92
+ const wrappers = parsed.requiredWrappers || [];
93
+ if (wrappers.length > 0) {
94
+ const appShellCandidates = [...uiFileContents.entries()].filter(([file]) => /(app|root|layout|provider)s?\.(tsx|jsx|ts|js)$/i.test(path.basename(file)));
95
+ const shellContent = appShellCandidates.map(([, content]) => content).join('\n');
96
+ for (const wrapper of wrappers) {
97
+ if (!shellContent.includes(wrapper)) {
98
+ designFindings.push({
99
+ severity: 'warn',
100
+ rule: 'required-wrapper',
101
+ message: `Required wrapper not detected in app/root/layout/provider files: ${wrapper}`,
102
+ });
103
+ }
104
+ }
105
+ }
106
+ const allowList = parsed.allowedComponentImports || [];
107
+ if (allowList.length > 0) {
108
+ for (const [file, content] of uiFileContents.entries()) {
109
+ if (!isLikelyUiFile(file))
110
+ continue;
111
+ const imports = collectImports(content);
112
+ const disallowed = imports.filter((value) => {
113
+ if (!isUiLibraryImport(value))
114
+ return false;
115
+ return !allowList.some((allowed) => value === allowed || value.startsWith(`${allowed}/`));
116
+ });
117
+ for (const importPath of disallowed) {
118
+ designFindings.push({
119
+ severity: 'warn',
120
+ rule: 'allowed-component-imports',
121
+ file,
122
+ message: `Import not in allowedComponentImports: ${importPath}`,
123
+ });
124
+ }
125
+ }
126
+ }
127
+ }
128
+ catch (error) {
129
+ designFindings.push({
130
+ severity: 'error',
131
+ rule: 'profile-parse',
132
+ message: `Failed to parse design-system.json: ${error.message}`,
133
+ });
134
+ }
135
+ return designFindings;
136
+ }
137
+ export function buildDesignAuditReport(designSystemPath, findings) {
138
+ let designReport = '# Design System Audit Report\n\n';
139
+ designReport += `Generated: ${new Date().toISOString()}\n\n`;
140
+ designReport += `- Profile: \`${designSystemPath}\`\n`;
141
+ designReport += `- Findings: ${findings.length}\n`;
142
+ designReport += `- Errors: ${findings.filter((f) => f.severity === 'error').length}\n`;
143
+ designReport += `- Warnings: ${findings.filter((f) => f.severity === 'warn').length}\n\n`;
144
+ if (findings.length === 0) {
145
+ designReport += 'No design-system violations detected.\n';
146
+ }
147
+ else {
148
+ for (const finding of findings) {
149
+ designReport += `## ${finding.severity.toUpperCase()} - ${finding.rule}\n\n`;
150
+ designReport += `${finding.message}\n\n`;
151
+ if (finding.file) {
152
+ designReport += `File: \`${finding.file}\`\n\n`;
153
+ }
154
+ }
155
+ }
156
+ return designReport;
157
+ }
158
+ //# sourceMappingURL=audit-design.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-design.js","sourceRoot":"","sources":["../../src/commands/audit-design.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import type { DesignAuditFinding } from './audit-design.js';
2
+ interface LearningItem {
3
+ timestamp: string;
4
+ category: string;
5
+ content: string;
6
+ }
7
+ interface AuditResult {
8
+ learning: LearningItem;
9
+ status: 'covered' | 'needs-review';
10
+ matchedFiles: string[];
11
+ }
12
+ export declare function buildLearningAuditReport(results: AuditResult[], covered: number, needsReview: number): string;
13
+ export declare function summarizeDesignFindings(findings: DesignAuditFinding[]): {
14
+ findings: number;
15
+ errors: number;
16
+ warnings: number;
17
+ };
18
+ export {};
@@ -0,0 +1,30 @@
1
+ export function buildLearningAuditReport(results, covered, needsReview) {
2
+ let report = '# Learning Audit Report\n\n';
3
+ report += `Generated: ${new Date().toISOString()}\n\n`;
4
+ report += `- Total learnings: ${results.length}\n`;
5
+ report += `- Covered: ${covered}\n`;
6
+ report += `- Needs review: ${needsReview}\n\n`;
7
+ for (const item of results) {
8
+ report += `## ${item.status === 'covered' ? 'Covered' : 'Needs Review'} - ${item.learning.category}\n\n`;
9
+ report += `${item.learning.content}\n\n`;
10
+ if (item.matchedFiles.length > 0) {
11
+ report += 'Matched files:\n';
12
+ for (const file of item.matchedFiles) {
13
+ report += `- \`${file}\`\n`;
14
+ }
15
+ report += '\n';
16
+ }
17
+ else {
18
+ report += 'Matched files: none\n\n';
19
+ }
20
+ }
21
+ return report;
22
+ }
23
+ export function summarizeDesignFindings(findings) {
24
+ return {
25
+ findings: findings.length,
26
+ errors: findings.filter((f) => f.severity === 'error').length,
27
+ warnings: findings.filter((f) => f.severity === 'warn').length,
28
+ };
29
+ }
30
+ //# sourceMappingURL=audit-report.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-report.js","sourceRoot":"","sources":["../../src/commands/audit-report.ts"],"names":[],"mappings":"AAcA,MAAM,UAAU,wBAAwB,CAAC,OAAsB,EAAE,OAAe,EAAE,WAAmB;IACnG,IAAI,MAAM,GAAG,6BAA6B,CAAC;IAC3C,MAAM,IAAI,cAAc,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,MAAM,CAAC;IACvD,MAAM,IAAI,sBAAsB,OAAO,CAAC,MAAM,IAAI,CAAC;IACnD,MAAM,IAAI,cAAc,OAAO,IAAI,CAAC;IACpC,MAAM,IAAI,mBAAmB,WAAW,MAAM,CAAC;IAE/C,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;QAC3B,MAAM,IAAI,MAAM,IAAI,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,cAAc,MAAM,IAAI,CAAC,QAAQ,CAAC,QAAQ,MAAM,CAAC;QACzG,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,MAAM,CAAC;QACzC,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACjC,MAAM,IAAI,kBAAkB,CAAC;YAC7B,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;gBACrC,MAAM,IAAI,OAAO,IAAI,MAAM,CAAC;YAC9B,CAAC;YACD,MAAM,IAAI,IAAI,CAAC;QACjB,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,yBAAyB,CAAC;QACtC,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,QAA8B;IAKpE,OAAO;QACL,QAAQ,EAAE,QAAQ,CAAC,MAAM;QACzB,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,MAAM;QAC7D,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC,CAAC,MAAM;KAC/D,CAAC;AACJ,CAAC"}
@@ -0,0 +1,21 @@
1
+ export interface AnalyzeCacheEntry {
2
+ mtimeMs: number;
3
+ size: number;
4
+ matches: string[];
5
+ content?: string;
6
+ }
7
+ export interface LoadedAuditSources {
8
+ fileContents: Map<string, string>;
9
+ fileSignatures: Map<string, string>;
10
+ cacheReused: number;
11
+ diskReads: number;
12
+ }
13
+ interface LoadAuditSourcesOptions {
14
+ files: string[];
15
+ rootPath: string;
16
+ analyzeCache: Record<string, AnalyzeCacheEntry>;
17
+ batchSize?: number;
18
+ }
19
+ export declare function loadAuditSources(options: LoadAuditSourcesOptions): Promise<LoadedAuditSources>;
20
+ export declare function buildProjectFingerprint(fileSignatures: Map<string, string>): string;
21
+ export {};
@@ -0,0 +1,57 @@
1
+ import * as path from 'path';
2
+ import * as fsPromises from 'fs/promises';
3
+ import { createHash } from 'crypto';
4
+ const DEFAULT_BATCH_SIZE = 48;
5
+ export async function loadAuditSources(options) {
6
+ const batchSize = options.batchSize ?? DEFAULT_BATCH_SIZE;
7
+ const fileContents = new Map();
8
+ const fileSignatures = new Map();
9
+ let cacheReused = 0;
10
+ let diskReads = 0;
11
+ for (let i = 0; i < options.files.length; i += batchSize) {
12
+ const batch = options.files.slice(i, i + batchSize);
13
+ const batchResults = await Promise.all(batch.map(async (relPath) => {
14
+ const absPath = path.join(options.rootPath, relPath);
15
+ let stat;
16
+ try {
17
+ stat = await fsPromises.stat(absPath);
18
+ }
19
+ catch {
20
+ return null;
21
+ }
22
+ const signature = `${stat.size}:${stat.mtimeMs}`;
23
+ const cached = options.analyzeCache[relPath];
24
+ if (cached?.content !== undefined && stat.mtimeMs === cached.mtimeMs && stat.size === cached.size) {
25
+ return { relPath, signature, content: cached.content, reused: true };
26
+ }
27
+ try {
28
+ const content = await fsPromises.readFile(absPath, 'utf-8');
29
+ return { relPath, signature, content, reused: false };
30
+ }
31
+ catch {
32
+ return { relPath, signature, content: null, reused: false };
33
+ }
34
+ }));
35
+ for (const result of batchResults) {
36
+ if (!result)
37
+ continue;
38
+ fileSignatures.set(result.relPath, result.signature);
39
+ if (result.content === null)
40
+ continue;
41
+ fileContents.set(result.relPath, result.content);
42
+ if (result.reused)
43
+ cacheReused += 1;
44
+ else
45
+ diskReads += 1;
46
+ }
47
+ }
48
+ return { fileContents, fileSignatures, cacheReused, diskReads };
49
+ }
50
+ export function buildProjectFingerprint(fileSignatures) {
51
+ const stable = [...fileSignatures.entries()]
52
+ .sort((a, b) => a[0].localeCompare(b[0]))
53
+ .map(([file, sig]) => `${file}:${sig}`)
54
+ .join('|');
55
+ return createHash('sha256').update(stable, 'utf8').digest('hex').slice(0, 16);
56
+ }
57
+ //# sourceMappingURL=audit-source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-source.js","sourceRoot":"","sources":["../../src/commands/audit-source.ts"],"names":[],"mappings":"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"}
@@ -2,6 +2,7 @@ interface AuditOptions {
2
2
  output?: string;
3
3
  path?: string;
4
4
  json?: boolean;
5
+ profile?: boolean;
5
6
  }
6
7
  export declare function audit(options: AuditOptions): Promise<void>;
7
8
  export {};
@@ -1,7 +1,10 @@
1
1
  import * as path from 'path';
2
2
  import * as fs from 'fs';
3
- import { glob } from 'glob';
4
- import { logger, ensureDir, readFileSafe, writeFileSafe } from '../core/index.js';
3
+ import { createHash } from 'crypto';
4
+ import { logger, ensureDir, readFileSafe, writeFileSafe, createProfiler, readCacheJson, writeCacheJson, getSourceFilesWithCache, parseLearningEntries, } from '../core/index.js';
5
+ import { loadAuditSources, buildProjectFingerprint } from './audit-source.js';
6
+ import { collectDesignAuditFindings, buildDesignAuditReport, } from './audit-design.js';
7
+ import { buildLearningAuditReport, summarizeDesignFindings } from './audit-report.js';
5
8
  const STOPWORDS = new Set([
6
9
  'with',
7
10
  'from',
@@ -34,26 +37,6 @@ const STOPWORDS = new Set([
34
37
  'more',
35
38
  'less',
36
39
  ]);
37
- function parseLearnings(content) {
38
- const sections = content
39
- .split('\n---')
40
- .map((section) => section.trim())
41
- .filter((section) => section.startsWith('## '));
42
- const parsed = [];
43
- for (const section of sections) {
44
- const lines = section.split('\n').filter((line) => line.trim().length > 0);
45
- const header = lines[0].replace(/^##\s+/, '');
46
- const match = header.match(/^(.+?)\s+-\s+(.+)$/);
47
- if (!match)
48
- continue;
49
- parsed.push({
50
- timestamp: match[1].trim(),
51
- category: match[2].trim(),
52
- content: lines.slice(1).join(' ').trim(),
53
- });
54
- }
55
- return parsed;
56
- }
57
40
  function buildKeywords(text) {
58
41
  const tokens = text
59
42
  .toLowerCase()
@@ -63,18 +46,27 @@ function buildKeywords(text) {
63
46
  .filter((token) => token.length >= 4 && !STOPWORDS.has(token));
64
47
  return [...new Set(tokens)].slice(0, 6);
65
48
  }
66
- function hasKeywordCoverage(content, keywords) {
67
- const lc = content.toLowerCase();
49
+ function hasKeywordCoverageLowercase(lowercaseContent, keywords) {
68
50
  let hits = 0;
69
51
  for (const keyword of keywords) {
70
- if (lc.includes(keyword))
52
+ if (lowercaseContent.includes(keyword))
71
53
  hits += 1;
72
54
  if (hits >= 2)
73
55
  return true;
74
56
  }
75
57
  return false;
76
58
  }
59
+ function buildLearningKey(learning) {
60
+ return createHash('sha256')
61
+ .update(`${learning.timestamp}|${learning.category}|${learning.content}`, 'utf8')
62
+ .digest('hex')
63
+ .slice(0, 16);
64
+ }
65
+ function normalizeMatchedFiles(files) {
66
+ return [...new Set(files)].sort((a, b) => a.localeCompare(b));
67
+ }
77
68
  export async function audit(options) {
69
+ const profiler = createProfiler(!!options.profile);
78
70
  const outputDir = options.output || '.devmind';
79
71
  const rootPath = path.resolve(options.path || '.');
80
72
  const learnPath = path.join(outputDir, 'memory', 'LEARN.md');
@@ -84,76 +76,170 @@ export async function audit(options) {
84
76
  return;
85
77
  }
86
78
  const learnContent = await readFileSafe(learnPath);
87
- const learnings = parseLearnings(learnContent);
79
+ const learnings = parseLearningEntries(learnContent).map((entry) => ({
80
+ ...entry,
81
+ content: entry.content.replace(/\s+/g, ' ').trim(),
82
+ }));
88
83
  if (learnings.length === 0) {
89
84
  logger.warn('No learnings found to audit.');
90
85
  return;
91
86
  }
92
- const files = await glob('**/*.{ts,tsx,js,jsx,py,go,java,rb,php,rs}', {
93
- cwd: rootPath,
87
+ const sourceList = await profiler.section('audit.listSources', async () => getSourceFilesWithCache({
88
+ outputDir,
89
+ rootPath,
90
+ includeGlob: '**/*.{ts,tsx,js,jsx,py,go,java,rb,php,rs,css,scss}',
94
91
  ignore: ['node_modules/**', '.git/**', '.devmind/**', 'dist/**', 'build/**'],
95
- nodir: true,
92
+ }));
93
+ const files = sourceList.files;
94
+ const analyzeCache = await profiler.section('audit.loadAnalyzeCache', async () => {
95
+ const cachePath = path.join(outputDir, 'cache', 'analyze-cache.json');
96
+ const parsed = await readCacheJson(cachePath);
97
+ return parsed?.files || {};
96
98
  });
97
- const fileContents = new Map();
98
- for (const relPath of files) {
99
- const absPath = path.join(rootPath, relPath);
100
- try {
101
- fileContents.set(relPath, fs.readFileSync(absPath, 'utf-8'));
102
- }
103
- catch {
104
- // Ignore unreadable files.
99
+ const sourceData = await profiler.section('audit.readSources', async () => loadAuditSources({
100
+ files,
101
+ rootPath,
102
+ analyzeCache,
103
+ }));
104
+ const { fileContents, fileSignatures, cacheReused, diskReads } = sourceData;
105
+ const projectFingerprint = buildProjectFingerprint(fileSignatures);
106
+ const coverageCachePath = path.join(outputDir, 'cache', 'audit-coverage.json');
107
+ const existingCoverageCache = await profiler.section('audit.loadCoverageCache', async () => {
108
+ const parsed = await readCacheJson(coverageCachePath);
109
+ if (!parsed || parsed.version !== 1 || typeof parsed.items !== 'object') {
110
+ return {
111
+ version: 1,
112
+ projectFingerprint,
113
+ items: {},
114
+ };
105
115
  }
106
- }
116
+ return parsed;
117
+ });
118
+ const coverageCache = existingCoverageCache.projectFingerprint === projectFingerprint
119
+ ? existingCoverageCache
120
+ : {
121
+ version: 1,
122
+ projectFingerprint,
123
+ items: {},
124
+ };
107
125
  const results = [];
108
- for (const learning of learnings) {
109
- const keywords = buildKeywords(learning.content);
110
- const matchedFiles = [];
111
- if (keywords.length > 0) {
112
- for (const [relPath, content] of fileContents.entries()) {
113
- if (hasKeywordCoverage(content, keywords)) {
114
- matchedFiles.push(relPath);
115
- if (matchedFiles.length >= 5)
116
- break;
126
+ let coverageCacheHits = 0;
127
+ let coverageCacheMisses = 0;
128
+ await profiler.section('audit.learningCoverage', async () => {
129
+ const lowerContents = new Map();
130
+ for (const [relPath, content] of fileContents.entries()) {
131
+ lowerContents.set(relPath, content.toLowerCase());
132
+ }
133
+ const keywordFileCache = new Map();
134
+ const getFilesForKeyword = (keyword) => {
135
+ const cached = keywordFileCache.get(keyword);
136
+ if (cached)
137
+ return cached;
138
+ const matched = [];
139
+ for (const [relPath, lc] of lowerContents.entries()) {
140
+ if (lc.includes(keyword))
141
+ matched.push(relPath);
142
+ }
143
+ keywordFileCache.set(keyword, matched);
144
+ return matched;
145
+ };
146
+ for (const learning of learnings) {
147
+ const learningKey = buildLearningKey(learning);
148
+ const cachedCoverage = coverageCache.items[learningKey];
149
+ if (cachedCoverage) {
150
+ const normalized = normalizeMatchedFiles(cachedCoverage.matchedFiles || []);
151
+ coverageCacheHits += 1;
152
+ results.push({
153
+ learning,
154
+ status: cachedCoverage.status,
155
+ matchedFiles: normalized,
156
+ });
157
+ coverageCache.items[learningKey] = {
158
+ status: cachedCoverage.status,
159
+ matchedFiles: normalized,
160
+ };
161
+ continue;
162
+ }
163
+ coverageCacheMisses += 1;
164
+ const keywords = buildKeywords(learning.content);
165
+ const matchedFiles = [];
166
+ if (keywords.length > 0) {
167
+ const prioritizedKeywords = [...keywords].sort((a, b) => getFilesForKeyword(a).length - getFilesForKeyword(b).length);
168
+ const primaryCandidates = getFilesForKeyword(prioritizedKeywords[0]);
169
+ for (const relPath of primaryCandidates) {
170
+ const lc = lowerContents.get(relPath);
171
+ if (!lc)
172
+ continue;
173
+ if (hasKeywordCoverageLowercase(lc, keywords)) {
174
+ matchedFiles.push(relPath);
175
+ if (matchedFiles.length >= 5)
176
+ break;
177
+ }
117
178
  }
118
179
  }
180
+ const normalizedMatchedFiles = normalizeMatchedFiles(matchedFiles);
181
+ results.push({
182
+ learning,
183
+ status: normalizedMatchedFiles.length > 0 ? 'covered' : 'needs-review',
184
+ matchedFiles: normalizedMatchedFiles,
185
+ });
186
+ coverageCache.items[learningKey] = {
187
+ status: normalizedMatchedFiles.length > 0 ? 'covered' : 'needs-review',
188
+ matchedFiles: normalizedMatchedFiles,
189
+ };
119
190
  }
120
- results.push({
121
- learning,
122
- status: matchedFiles.length > 0 ? 'covered' : 'needs-review',
123
- matchedFiles,
191
+ });
192
+ await profiler.section('audit.writeCoverageCache', async () => {
193
+ await writeCacheJson(coverageCachePath, coverageCache, {
194
+ compressAboveBytes: 512 * 1024,
195
+ pretty: false,
124
196
  });
125
- }
197
+ });
126
198
  const covered = results.filter((r) => r.status === 'covered').length;
127
199
  const needsReview = results.length - covered;
128
200
  const reportPath = path.join(outputDir, 'analysis', 'AUDIT_REPORT.md');
129
201
  await ensureDir(path.dirname(reportPath));
130
- let report = '# Learning Audit Report\n\n';
131
- report += `Generated: ${new Date().toISOString()}\n\n`;
132
- report += `- Total learnings: ${results.length}\n`;
133
- report += `- Covered: ${covered}\n`;
134
- report += `- Needs review: ${needsReview}\n\n`;
135
- for (const item of results) {
136
- report += `## ${item.status === 'covered' ? 'Covered' : 'Needs Review'} - ${item.learning.category}\n\n`;
137
- report += `${item.learning.content}\n\n`;
138
- if (item.matchedFiles.length > 0) {
139
- report += 'Matched files:\n';
140
- for (const file of item.matchedFiles) {
141
- report += `- \`${file}\`\n`;
142
- }
143
- report += '\n';
144
- }
145
- else {
146
- report += 'Matched files: none\n\n';
147
- }
202
+ const report = buildLearningAuditReport(results, covered, needsReview);
203
+ await profiler.section('audit.writeLearningReport', async () => writeFileSafe(reportPath, report));
204
+ let designFindings = [];
205
+ const designSystemPath = path.join(outputDir, 'design-system.json');
206
+ const designReportPath = path.join(outputDir, 'analysis', 'DESIGN_SYSTEM_AUDIT.md');
207
+ if (fs.existsSync(designSystemPath)) {
208
+ designFindings = await collectDesignAuditFindings(rootPath, designSystemPath, fileContents);
209
+ designFindings.sort((a, b) => a.severity.localeCompare(b.severity) ||
210
+ a.rule.localeCompare(b.rule) ||
211
+ (a.file || '').localeCompare(b.file || '') ||
212
+ a.message.localeCompare(b.message));
213
+ }
214
+ if (fs.existsSync(designSystemPath)) {
215
+ const designReport = buildDesignAuditReport(designSystemPath, designFindings);
216
+ await profiler.section('audit.writeDesignReport', async () => writeFileSafe(designReportPath, designReport));
148
217
  }
149
- await writeFileSafe(reportPath, report);
150
218
  if (options.json) {
151
- console.log(JSON.stringify({
219
+ const profile = profiler.report();
220
+ const payload = {
152
221
  total: results.length,
153
222
  covered,
154
223
  needsReview,
155
224
  report: reportPath,
156
- }, null, 2));
225
+ sourceCache: {
226
+ reused: cacheReused,
227
+ diskReads,
228
+ },
229
+ coverageCache: {
230
+ reused: coverageCacheHits,
231
+ recomputed: coverageCacheMisses,
232
+ },
233
+ sourceListCache: sourceList.cacheHit,
234
+ designSystem: fs.existsSync(designSystemPath)
235
+ ? {
236
+ report: designReportPath,
237
+ ...summarizeDesignFindings(designFindings),
238
+ }
239
+ : null,
240
+ ...(profile ? { profile } : {}),
241
+ };
242
+ console.log(JSON.stringify(payload, null, 2));
157
243
  return;
158
244
  }
159
245
  logger.info('Learning audit complete.');
@@ -161,5 +247,20 @@ export async function audit(options) {
161
247
  logger.info(`Covered: ${covered}`);
162
248
  logger.info(`Needs review: ${needsReview}`);
163
249
  logger.info(`Report: ${reportPath}`);
250
+ logger.info(`Source cache reuse: ${cacheReused}, disk reads: ${diskReads}`);
251
+ logger.info(`Coverage cache: ${coverageCacheHits} reused, ${coverageCacheMisses} recomputed`);
252
+ logger.info(`Source list cache: ${sourceList.cacheHit ? 'hit' : 'miss'}`);
253
+ if (fs.existsSync(designSystemPath)) {
254
+ logger.info(`Design system findings: ${designFindings.length}`);
255
+ logger.info(`Design report: ${designReportPath}`);
256
+ }
257
+ const profile = profiler.report();
258
+ if (profile) {
259
+ logger.info('Performance Profile');
260
+ logger.info(`Total: ${profile.totalMs.toFixed(1)}ms`);
261
+ for (const step of profile.steps) {
262
+ logger.info(`- ${step.name}: ${step.ms.toFixed(1)}ms`);
263
+ }
264
+ }
164
265
  }
165
266
  //# sourceMappingURL=audit.js.map