@unrdf/project-engine 5.0.1 → 26.4.2

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 (39) hide show
  1. package/package.json +16 -15
  2. package/src/golden-structure.mjs +2 -2
  3. package/src/materialize-apply.mjs +2 -2
  4. package/README.md +0 -53
  5. package/src/api-contract-validator.mjs +0 -711
  6. package/src/auto-test-generator.mjs +0 -444
  7. package/src/autonomic-mapek.mjs +0 -511
  8. package/src/capabilities-manifest.mjs +0 -125
  9. package/src/code-complexity-js.mjs +0 -368
  10. package/src/dependency-graph.mjs +0 -276
  11. package/src/doc-drift-checker.mjs +0 -172
  12. package/src/doc-generator.mjs +0 -229
  13. package/src/domain-infer.mjs +0 -966
  14. package/src/drift-snapshot.mjs +0 -775
  15. package/src/file-roles.mjs +0 -94
  16. package/src/fs-scan.mjs +0 -305
  17. package/src/gap-finder.mjs +0 -376
  18. package/src/hotspot-analyzer.mjs +0 -412
  19. package/src/index.mjs +0 -151
  20. package/src/initialize.mjs +0 -957
  21. package/src/lens/project-structure.mjs +0 -74
  22. package/src/mapek-orchestration.mjs +0 -665
  23. package/src/materialize-plan.mjs +0 -422
  24. package/src/materialize.mjs +0 -137
  25. package/src/policy-derivation.mjs +0 -869
  26. package/src/project-config.mjs +0 -142
  27. package/src/project-diff.mjs +0 -28
  28. package/src/project-engine/build-utils.mjs +0 -237
  29. package/src/project-engine/code-analyzer.mjs +0 -248
  30. package/src/project-engine/doc-generator.mjs +0 -407
  31. package/src/project-engine/infrastructure.mjs +0 -213
  32. package/src/project-engine/metrics.mjs +0 -146
  33. package/src/project-model.mjs +0 -111
  34. package/src/project-report.mjs +0 -348
  35. package/src/refactoring-guide.mjs +0 -242
  36. package/src/stack-detect.mjs +0 -102
  37. package/src/stack-linter.mjs +0 -213
  38. package/src/template-infer.mjs +0 -674
  39. package/src/type-auditor.mjs +0 -609
@@ -1,142 +0,0 @@
1
- /**
2
- * @file Project engine configuration - central config for all capabilities
3
- * @module project-engine/project-config
4
- */
5
-
6
- import { z } from 'zod';
7
-
8
- /**
9
- * Full project engine configuration schema
10
- */
11
- export const ProjectEngineConfigSchema = z
12
- .object({
13
- // Filesystem scanning
14
- fs: z
15
- .object({
16
- ignorePatterns: z.array(z.string()).optional(),
17
- baseIri: z.string().default('http://example.org/unrdf/fs#'),
18
- scanHiddenFiles: z.boolean().default(false),
19
- })
20
- .optional(),
21
-
22
- // Project modeling
23
- project: z
24
- .object({
25
- conventions: z
26
- .object({
27
- sourcePaths: z.array(z.string()).default(['src']),
28
- featurePaths: z.array(z.string()).default(['features', 'modules']),
29
- testPaths: z.array(z.string()).default(['__tests__', 'test', 'tests']),
30
- })
31
- .optional(),
32
- baseIri: z.string().default('http://example.org/unrdf/project#'),
33
- })
34
- .optional(),
35
-
36
- // Golden structure
37
- golden: z
38
- .object({
39
- profile: z
40
- .enum([
41
- 'react-feature-v1',
42
- 'next-app-router-v1',
43
- 'next-pages-v1',
44
- 'nest-api-v1',
45
- 'express-api-v1',
46
- ])
47
- .optional(),
48
- loadFromPath: z.string().optional(),
49
- })
50
- .optional(),
51
-
52
- // Diff/comparison
53
- diff: z
54
- .object({
55
- structureLens: z.literal('project-structure').default('project-structure'),
56
- transactionLens: z.string().optional(),
57
- })
58
- .optional(),
59
-
60
- // Materialization
61
- materialize: z
62
- .object({
63
- templateConfig: z.record(z.string(), z.any()).optional(),
64
- outputRoot: z.string().default('.'),
65
- dryRun: z.boolean().default(false),
66
- })
67
- .optional(),
68
-
69
- // Observability
70
- observability: z
71
- .object({
72
- enableTracing: z.boolean().default(true),
73
- enableMetrics: z.boolean().default(true),
74
- })
75
- .optional(),
76
- })
77
- .strict();
78
-
79
- /**
80
- * Get project engine configuration from environment + defaults
81
- *
82
- * @param {Object} [overrides] - Config overrides
83
- * @returns {Object} Validated configuration
84
- */
85
- export function getProjectEngineConfig(overrides = {}) {
86
- const defaults = {
87
- fs: {
88
- ignorePatterns: ['node_modules', '.git', 'dist', 'build', '.next', '.turbo', 'coverage'],
89
- baseIri: 'http://example.org/unrdf/fs#',
90
- },
91
- project: {
92
- conventions: {
93
- sourcePaths: ['src'],
94
- featurePaths: ['features', 'modules'],
95
- testPaths: ['__tests__', 'test', 'tests', 'spec'],
96
- },
97
- baseIri: 'http://example.org/unrdf/project#',
98
- },
99
- golden: {
100
- profile: 'react-feature-v1',
101
- },
102
- diff: {
103
- structureLens: 'project-structure',
104
- },
105
- materialize: {
106
- outputRoot: '.',
107
- dryRun: false,
108
- },
109
- observability: {
110
- enableTracing: true,
111
- enableMetrics: true,
112
- },
113
- };
114
-
115
- // Merge overrides
116
- const merged = deepMerge(defaults, overrides);
117
-
118
- // Validate
119
- return ProjectEngineConfigSchema.parse(merged);
120
- }
121
-
122
- /**
123
- * Deep merge objects for configuration
124
- *
125
- * @private
126
- */
127
- function deepMerge(target, source) {
128
- const result = { ...target };
129
-
130
- for (const key in source) {
131
- if (Array.isArray(source[key]) && Array.isArray(result[key])) {
132
- // Merge arrays by combining them
133
- result[key] = [...result[key], ...source[key]];
134
- } else if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
135
- result[key] = deepMerge(result[key] || {}, source[key]);
136
- } else {
137
- result[key] = source[key];
138
- }
139
- }
140
-
141
- return result;
142
- }
@@ -1,28 +0,0 @@
1
- /**
2
- * @file Project structure diff - convenience wrapper over diff.mjs
3
- * @module project-engine/project-diff
4
- */
5
-
6
- import { diffOntologyFromStores } from '../diff.mjs';
7
- import { ProjectStructureLens } from './lens/project-structure.mjs';
8
- import { z } from 'zod';
9
-
10
- const ProjectDiffOptionsSchema = z.object({
11
- actualStore: z.object({}).passthrough(),
12
- goldenStore: z.object({}).passthrough(),
13
- });
14
-
15
- /**
16
- * Compute project structure diff using low-level diff.mjs
17
- *
18
- * @param {Object} options
19
- * @param {Store} options.actualStore - Current project graph
20
- * @param {Store} options.goldenStore - Expected golden structure
21
- * @returns {OntologyDiff}
22
- */
23
- export function diffProjectStructure(options) {
24
- const validated = ProjectDiffOptionsSchema.parse(options);
25
- const { actualStore, goldenStore } = validated;
26
-
27
- return diffOntologyFromStores(goldenStore, actualStore, ProjectStructureLens);
28
- }
@@ -1,237 +0,0 @@
1
- /**
2
- * @file Build Utilities - Package building and verification
3
- * @module @unrdf/project-engine/build-utils
4
- */
5
-
6
- import { readFile, readdir, access, stat } from 'node:fs/promises';
7
- import { join } from 'node:path';
8
- import { z } from 'zod';
9
-
10
- /**
11
- * Package verification schema
12
- */
13
- const VerificationResultSchema = z.object({
14
- valid: z.boolean(),
15
- errors: z.array(z.string()),
16
- warnings: z.array(z.string()),
17
- checkedFiles: z.array(z.string()),
18
- });
19
-
20
- /**
21
- * Build single package
22
- * @param {string} packagePath - Path to package directory
23
- * @returns {Promise<Object>} Build result
24
- *
25
- * @throws {TypeError} If packagePath is not a string
26
- * @throws {Error} If build fails
27
- *
28
- * @example
29
- * const result = await buildPackage('./packages/core');
30
- * console.log('Build status:', result.success);
31
- */
32
- export async function buildPackage(packagePath) {
33
- if (typeof packagePath !== 'string') {
34
- throw new TypeError('buildPackage: packagePath must be a string');
35
- }
36
-
37
- try {
38
- const { execFile } = await import('node:child_process');
39
- const { promisify } = await import('node:util');
40
- const execFileAsync = promisify(execFile);
41
-
42
- // Check if package has build script
43
- const packageJsonPath = join(packagePath, 'package.json');
44
- const packageJsonContent = await readFile(packageJsonPath, 'utf-8');
45
- const packageJson = JSON.parse(packageJsonContent);
46
-
47
- if (!packageJson.scripts || !packageJson.scripts.build) {
48
- return {
49
- success: true,
50
- message: 'No build script defined',
51
- output: '',
52
- };
53
- }
54
-
55
- // Run build script
56
- const { stdout, stderr } = await execFileAsync('pnpm', ['run', 'build'], {
57
- cwd: packagePath,
58
- });
59
-
60
- return {
61
- success: true,
62
- message: 'Build completed successfully',
63
- output: stdout,
64
- errors: stderr,
65
- };
66
- } catch (error) {
67
- throw new Error(`buildPackage failed: ${error.message}`);
68
- }
69
- }
70
-
71
- /**
72
- * Verify package integrity
73
- * @param {string} packagePath - Path to package directory
74
- * @returns {Promise<Object>} Verification result with errors/warnings
75
- *
76
- * @throws {TypeError} If packagePath is not a string
77
- * @throws {Error} If verification fails
78
- *
79
- * @example
80
- * const result = await verifyPackage('./packages/core');
81
- * console.log('Valid:', result.valid);
82
- * console.log('Errors:', result.errors);
83
- */
84
- export async function verifyPackage(packagePath) {
85
- if (typeof packagePath !== 'string') {
86
- throw new TypeError('verifyPackage: packagePath must be a string');
87
- }
88
-
89
- const errors = [];
90
- const warnings = [];
91
- const checkedFiles = [];
92
-
93
- try {
94
- // Check package.json exists
95
- const packageJsonPath = join(packagePath, 'package.json');
96
- try {
97
- await access(packageJsonPath);
98
- checkedFiles.push('package.json');
99
-
100
- const content = await readFile(packageJsonPath, 'utf-8');
101
- const packageJson = JSON.parse(content);
102
-
103
- // Validate required fields
104
- if (!packageJson.name) {
105
- errors.push('package.json missing "name" field');
106
- }
107
- if (!packageJson.version) {
108
- errors.push('package.json missing "version" field');
109
- }
110
- if (!packageJson.type || packageJson.type !== 'module') {
111
- errors.push('package.json must have "type": "module"');
112
- }
113
-
114
- // Check exports field
115
- if (!packageJson.exports) {
116
- warnings.push('package.json missing "exports" field');
117
- }
118
-
119
- // Verify main entry point exists
120
- if (packageJson.main) {
121
- const mainPath = join(packagePath, packageJson.main);
122
- try {
123
- await access(mainPath);
124
- checkedFiles.push(packageJson.main);
125
- } catch {
126
- errors.push(`Main entry point not found: ${packageJson.main}`);
127
- }
128
- }
129
-
130
- // Check for README
131
- const readmePath = join(packagePath, 'README.md');
132
- try {
133
- await access(readmePath);
134
- checkedFiles.push('README.md');
135
- } catch {
136
- warnings.push('README.md not found');
137
- }
138
-
139
- // Check for LICENSE
140
- const licensePath = join(packagePath, 'LICENSE');
141
- try {
142
- await access(licensePath);
143
- checkedFiles.push('LICENSE');
144
- } catch {
145
- warnings.push('LICENSE file not found');
146
- }
147
-
148
- // Check src directory exists
149
- const srcPath = join(packagePath, 'src');
150
- try {
151
- const srcStat = await stat(srcPath);
152
- if (!srcStat.isDirectory()) {
153
- errors.push('src must be a directory');
154
- }
155
- checkedFiles.push('src/');
156
- } catch {
157
- errors.push('src directory not found');
158
- }
159
-
160
- // Check test directory
161
- const testPath = join(packagePath, 'test');
162
- try {
163
- const testStat = await stat(testPath);
164
- if (!testStat.isDirectory()) {
165
- warnings.push('test should be a directory');
166
- }
167
- checkedFiles.push('test/');
168
- } catch {
169
- warnings.push('test directory not found');
170
- }
171
- } catch {
172
- errors.push('package.json not found or invalid JSON');
173
- }
174
-
175
- const result = {
176
- valid: errors.length === 0,
177
- errors,
178
- warnings,
179
- checkedFiles,
180
- };
181
-
182
- return VerificationResultSchema.parse(result);
183
- } catch (error) {
184
- throw new Error(`verifyPackage failed: ${error.message}`);
185
- }
186
- }
187
-
188
- /**
189
- * List all packages in monorepo
190
- * @param {string} [monorepoPath='.'] - Path to monorepo root
191
- * @returns {Promise<Array<Object>>} List of packages with metadata
192
- *
193
- * @throws {TypeError} If monorepoPath is not a string
194
- * @throws {Error} If monorepo cannot be scanned
195
- *
196
- * @example
197
- * const packages = await listPackages();
198
- * packages.forEach(pkg => console.log(pkg.name, pkg.path));
199
- */
200
- export async function listPackages(monorepoPath = '.') {
201
- if (typeof monorepoPath !== 'string') {
202
- throw new TypeError('listPackages: monorepoPath must be a string');
203
- }
204
-
205
- try {
206
- const packagesPath = join(monorepoPath, 'packages');
207
- const entries = await readdir(packagesPath, { withFileTypes: true });
208
-
209
- const packages = [];
210
-
211
- for (const entry of entries) {
212
- if (entry.isDirectory()) {
213
- const packagePath = join(packagesPath, entry.name);
214
- const packageJsonPath = join(packagePath, 'package.json');
215
-
216
- try {
217
- const content = await readFile(packageJsonPath, 'utf-8');
218
- const packageJson = JSON.parse(content);
219
-
220
- packages.push({
221
- name: packageJson.name,
222
- version: packageJson.version,
223
- description: packageJson.description || '',
224
- path: packagePath,
225
- private: packageJson.private || false,
226
- });
227
- } catch {
228
- // Skip invalid packages
229
- }
230
- }
231
- }
232
-
233
- return packages;
234
- } catch (error) {
235
- throw new Error(`listPackages failed: ${error.message}`);
236
- }
237
- }
@@ -1,248 +0,0 @@
1
- /**
2
- * @file Code Analyzer - Package quality metrics and analysis
3
- * @module @unrdf/project-engine/code-analyzer
4
- */
5
-
6
- import { readFile, readdir } from 'node:fs/promises';
7
- import { join } from 'node:path';
8
- import { z } from 'zod';
9
-
10
- /**
11
- * Package analysis schema
12
- */
13
- const PackageAnalysisSchema = z.object({
14
- name: z.string(),
15
- linesOfCode: z.number(),
16
- fileCount: z.number(),
17
- exportCount: z.number(),
18
- testCoverage: z.number().min(0).max(100),
19
- dependencies: z.array(z.string()),
20
- devDependencies: z.array(z.string()),
21
- publicApis: z.array(z.string()),
22
- complexity: z.enum(['low', 'medium', 'high']),
23
- });
24
-
25
- /**
26
- * Analyze package quality metrics
27
- * @param {string} packagePath - Path to package directory
28
- * @returns {Promise<Object>} Package analysis results
29
- *
30
- * @throws {TypeError} If packagePath is not a string
31
- * @throws {Error} If package cannot be analyzed
32
- *
33
- * @example
34
- * const analysis = await analyzePackage('./packages/core');
35
- * console.log('Lines of code:', analysis.linesOfCode);
36
- * console.log('Test coverage:', analysis.testCoverage);
37
- */
38
- export async function analyzePackage(packagePath) {
39
- if (typeof packagePath !== 'string') {
40
- throw new TypeError('analyzePackage: packagePath must be a string');
41
- }
42
-
43
- try {
44
- // Read package.json
45
- const packageJsonPath = join(packagePath, 'package.json');
46
- const packageJsonContent = await readFile(packageJsonPath, 'utf-8');
47
- const packageJson = JSON.parse(packageJsonContent);
48
-
49
- // Count lines of code
50
- const srcPath = join(packagePath, 'src');
51
- const sourceFiles = await findFiles(srcPath, '.mjs');
52
- let linesOfCode = 0;
53
-
54
- for (const file of sourceFiles) {
55
- const content = await readFile(file, 'utf-8');
56
- linesOfCode += content.split('\n').length;
57
- }
58
-
59
- // Find all exports
60
- const publicApis = [];
61
- for (const file of sourceFiles) {
62
- const exports = await findExports(file);
63
- publicApis.push(...exports);
64
- }
65
-
66
- // Estimate test coverage
67
- const testPath = join(packagePath, 'test');
68
- const testFiles = await findFiles(testPath, '.test.mjs');
69
- const testCoverage = await estimateTestCoverage(sourceFiles, testFiles);
70
-
71
- // Determine complexity
72
- const complexity = determineComplexity(linesOfCode, publicApis.length, sourceFiles.length);
73
-
74
- const analysis = {
75
- name: packageJson.name,
76
- linesOfCode,
77
- fileCount: sourceFiles.length,
78
- exportCount: publicApis.length,
79
- testCoverage,
80
- dependencies: Object.keys(packageJson.dependencies || {}),
81
- devDependencies: Object.keys(packageJson.devDependencies || {}),
82
- publicApis,
83
- complexity,
84
- };
85
-
86
- return PackageAnalysisSchema.parse(analysis);
87
- } catch (error) {
88
- throw new Error(`analyzePackage failed: ${error.message}`);
89
- }
90
- }
91
-
92
- /**
93
- * Find all exports from a module
94
- * @param {string} filePath - Path to module file
95
- * @returns {Promise<Array<string>>} List of exported identifiers
96
- *
97
- * @throws {TypeError} If filePath is not a string
98
- * @throws {Error} If file cannot be read
99
- *
100
- * @example
101
- * const exports = await findExports('./src/index.mjs');
102
- * console.log('Exports:', exports);
103
- */
104
- export async function findExports(filePath) {
105
- if (typeof filePath !== 'string') {
106
- throw new TypeError('findExports: filePath must be a string');
107
- }
108
-
109
- try {
110
- const content = await readFile(filePath, 'utf-8');
111
- const exports = [];
112
-
113
- // Named exports: export function/const/let/class name
114
- const namedMatches = content.matchAll(
115
- /export\s+(async\s+)?(function|const|let|class)\s+(\w+)/g
116
- );
117
- for (const match of namedMatches) {
118
- exports.push(match[3]);
119
- }
120
-
121
- // Export { name1, name2 }
122
- const braceMatches = content.matchAll(/export\s+\{([^}]+)\}/g);
123
- for (const match of braceMatches) {
124
- const names = match[1].split(',').map(n => n.trim().split(/\s+as\s+/)[0]);
125
- exports.push(...names);
126
- }
127
-
128
- return exports;
129
- } catch (error) {
130
- throw new Error(`findExports failed: ${error.message}`);
131
- }
132
- }
133
-
134
- /**
135
- * Estimate test coverage percentage
136
- * @param {Array<string>} sourceFiles - Source file paths
137
- * @param {Array<string>} testFiles - Test file paths
138
- * @returns {Promise<number>} Estimated coverage percentage
139
- *
140
- * @throws {TypeError} If arguments are not arrays
141
- *
142
- * @example
143
- * const coverage = await countCoverage(['src/a.mjs'], ['test/a.test.mjs']);
144
- * console.log('Coverage:', coverage + '%');
145
- */
146
- export async function countCoverage(sourceFiles, testFiles) {
147
- if (!Array.isArray(sourceFiles)) {
148
- throw new TypeError('countCoverage: sourceFiles must be an array');
149
- }
150
- if (!Array.isArray(testFiles)) {
151
- throw new TypeError('countCoverage: testFiles must be an array');
152
- }
153
-
154
- return estimateTestCoverage(sourceFiles, testFiles);
155
- }
156
-
157
- /**
158
- * Find files with specific extension
159
- * @param {string} dirPath - Directory path
160
- * @param {string} extension - File extension
161
- * @returns {Promise<Array<string>>} List of file paths
162
- */
163
- async function findFiles(dirPath, extension) {
164
- const files = [];
165
-
166
- try {
167
- const entries = await readdir(dirPath, { withFileTypes: true });
168
-
169
- for (const entry of entries) {
170
- const fullPath = join(dirPath, entry.name);
171
-
172
- if (entry.isDirectory()) {
173
- const subFiles = await findFiles(fullPath, extension);
174
- files.push(...subFiles);
175
- } else if (entry.isFile() && entry.name.endsWith(extension)) {
176
- files.push(fullPath);
177
- }
178
- }
179
- } catch (error) {
180
- // Directory doesn't exist
181
- }
182
-
183
- return files;
184
- }
185
-
186
- /**
187
- * Estimate test coverage heuristically
188
- * @param {Array<string>} sourceFiles - Source file paths
189
- * @param {Array<string>} testFiles - Test file paths
190
- * @returns {Promise<number>} Coverage percentage
191
- */
192
- async function estimateTestCoverage(sourceFiles, testFiles) {
193
- if (sourceFiles.length === 0) {
194
- return 0;
195
- }
196
-
197
- if (testFiles.length === 0) {
198
- return 0;
199
- }
200
-
201
- // Heuristic: ratio of test files to source files
202
- const ratio = testFiles.length / sourceFiles.length;
203
-
204
- // Count test assertions as proxy for coverage
205
- let totalAssertions = 0;
206
- for (const testFile of testFiles) {
207
- const content = await readFile(testFile, 'utf-8');
208
- const assertionCount = (content.match(/expect\(/g) || []).length;
209
- totalAssertions += assertionCount;
210
- }
211
-
212
- // Count source functions
213
- let totalFunctions = 0;
214
- for (const srcFile of sourceFiles) {
215
- const content = await readFile(srcFile, 'utf-8');
216
- const functionCount = (content.match(/export\s+(async\s+)?function/g) || []).length;
217
- totalFunctions += functionCount;
218
- }
219
-
220
- if (totalFunctions === 0) {
221
- return ratio * 50; // Base estimate
222
- }
223
-
224
- // Coverage estimate: assertions per function
225
- const assertionsPerFunction = totalAssertions / totalFunctions;
226
- const coverageEstimate = Math.min(assertionsPerFunction * 30, 100);
227
-
228
- return Math.round(coverageEstimate);
229
- }
230
-
231
- /**
232
- * Determine package complexity
233
- * @param {number} linesOfCode - Total lines of code
234
- * @param {number} exportCount - Number of exports
235
- * @param {number} fileCount - Number of files
236
- * @returns {string} Complexity level
237
- */
238
- function determineComplexity(linesOfCode, exportCount, fileCount) {
239
- const score = linesOfCode / 100 + exportCount * 2 + fileCount * 5;
240
-
241
- if (score < 50) {
242
- return 'low';
243
- } else if (score < 200) {
244
- return 'medium';
245
- } else {
246
- return 'high';
247
- }
248
- }