@webpieces/eslint-rules 0.0.1 → 0.2.113

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 (47) hide show
  1. package/package.json +3 -2
  2. package/src/index.d.ts +29 -0
  3. package/src/index.js +39 -0
  4. package/src/index.js.map +1 -0
  5. package/src/rules/catch-error-pattern.d.ts +11 -0
  6. package/src/rules/{catch-error-pattern.ts → catch-error-pattern.js} +30 -142
  7. package/src/rules/catch-error-pattern.js.map +1 -0
  8. package/src/rules/enforce-architecture.d.ts +15 -0
  9. package/src/rules/{enforce-architecture.ts → enforce-architecture.js} +61 -128
  10. package/src/rules/enforce-architecture.js.map +1 -0
  11. package/src/rules/max-file-lines.d.ts +12 -0
  12. package/src/rules/{max-file-lines.ts → max-file-lines.js} +22 -37
  13. package/src/rules/max-file-lines.js.map +1 -0
  14. package/src/rules/max-method-lines.d.ts +12 -0
  15. package/src/rules/{max-method-lines.ts → max-method-lines.js} +31 -81
  16. package/src/rules/max-method-lines.js.map +1 -0
  17. package/src/rules/no-json-property-primitive-type.d.ts +17 -0
  18. package/src/rules/no-json-property-primitive-type.js +57 -0
  19. package/src/rules/no-json-property-primitive-type.js.map +1 -0
  20. package/src/rules/no-mat-cell-def.d.ts +15 -0
  21. package/src/rules/{no-mat-cell-def.ts → no-mat-cell-def.js} +8 -21
  22. package/src/rules/no-mat-cell-def.js.map +1 -0
  23. package/src/rules/no-unmanaged-exceptions.d.ts +22 -0
  24. package/src/rules/{no-unmanaged-exceptions.ts → no-unmanaged-exceptions.js} +27 -52
  25. package/src/rules/no-unmanaged-exceptions.js.map +1 -0
  26. package/src/rules/require-typed-template.d.ts +17 -0
  27. package/src/rules/{require-typed-template.ts → require-typed-template.js} +11 -31
  28. package/src/rules/require-typed-template.js.map +1 -0
  29. package/src/toError.d.ts +5 -0
  30. package/src/{toError.ts → toError.js} +7 -6
  31. package/src/toError.js.map +1 -0
  32. package/.webpieces/instruct-ai/webpieces.exceptions.md +0 -5
  33. package/.webpieces/instruct-ai/webpieces.filesize.md +0 -146
  34. package/.webpieces/instruct-ai/webpieces.methods.md +0 -97
  35. package/LICENSE +0 -373
  36. package/jest.config.ts +0 -16
  37. package/project.json +0 -22
  38. package/src/__tests__/catch-error-pattern.test.ts +0 -374
  39. package/src/__tests__/max-file-lines.test.ts +0 -207
  40. package/src/__tests__/max-method-lines.test.ts +0 -258
  41. package/src/__tests__/no-unmanaged-exceptions.test.ts +0 -359
  42. package/src/index.ts +0 -38
  43. package/src/rules/no-json-property-primitive-type.ts +0 -85
  44. package/tmp/webpieces/webpieces.exceptions.md +0 -5
  45. package/tsconfig.json +0 -22
  46. package/tsconfig.lib.json +0 -10
  47. package/tsconfig.spec.json +0 -14
@@ -1,3 +1,4 @@
1
+ "use strict";
1
2
  /**
2
3
  * ESLint rule to enforce architecture boundaries
3
4
  *
@@ -10,12 +11,10 @@
10
11
  * Configuration:
11
12
  * '@webpieces/enforce-architecture': 'error'
12
13
  */
13
-
14
- import type { Rule } from 'eslint';
15
- import * as fs from 'fs';
16
- import * as path from 'path';
17
- import { toError } from '../toError';
18
-
14
+ const tslib_1 = require("tslib");
15
+ const fs = tslib_1.__importStar(require("fs"));
16
+ const path = tslib_1.__importStar(require("path"));
17
+ const toError_1 = require("../toError");
19
18
  const DEPENDENCIES_DOC_CONTENT = `# Instructions: Architecture Dependency Violation
20
19
 
21
20
  IN GENERAL, it is better to avoid these changes and find a different way by moving classes
@@ -153,70 +152,47 @@ Instead of importing, receive the dependency as a constructor or method paramete
153
152
  - The best dependency is the one you don't need
154
153
  - When in doubt, refactor rather than add dependencies
155
154
  `;
156
-
157
155
  // Module-level flag to prevent redundant file creation
158
156
  let dependenciesDocCreated = false;
159
-
160
157
  /**
161
158
  * Ensure a documentation file exists at the given path.
162
159
  */
163
- function ensureDocFile(docPath: string, content: string): boolean {
160
+ function ensureDocFile(docPath, content) {
164
161
  // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
165
162
  try {
166
163
  fs.mkdirSync(path.dirname(docPath), { recursive: true });
167
164
  fs.writeFileSync(docPath, content, 'utf-8');
168
165
  return true;
169
- } catch (err: unknown) {
166
+ }
167
+ catch (err) {
170
168
  //const error = toError(err);
171
169
  void err;
172
170
  console.warn(`[webpieces] Could not create doc file: ${docPath}`);
173
171
  return false;
174
172
  }
175
173
  }
176
-
177
174
  /**
178
175
  * Ensure the dependencies documentation file exists.
179
176
  * Called when an architecture violation is detected.
180
177
  */
181
- function ensureDependenciesDoc(workspaceRoot: string): void {
182
- if (dependenciesDocCreated) return;
178
+ function ensureDependenciesDoc(workspaceRoot) {
179
+ if (dependenciesDocCreated)
180
+ return;
183
181
  const docPath = path.join(workspaceRoot, 'tmp', 'webpieces', 'webpieces.dependencies.md');
184
182
  if (ensureDocFile(docPath, DEPENDENCIES_DOC_CONTENT)) {
185
183
  dependenciesDocCreated = true;
186
184
  }
187
185
  }
188
-
189
- /**
190
- * Graph entry format from .graphs/dependencies.json
191
- */
192
- interface GraphEntry {
193
- level: number;
194
- dependsOn: string[];
195
- }
196
-
197
- type EnhancedGraph = Record<string, GraphEntry>;
198
-
199
- /**
200
- * Project mapping entry
201
- */
202
- interface ProjectMapping {
203
- root: string;
204
- name: string;
205
- }
206
-
207
186
  // Cache for blessed graph (loaded once per lint run)
208
- let cachedGraph: EnhancedGraph | null = null;
209
- let cachedGraphPath: string | null = null;
210
-
187
+ let cachedGraph = null;
188
+ let cachedGraphPath = null;
211
189
  // Cache for project mappings
212
- let cachedProjectMappings: ProjectMapping[] | null = null;
213
-
190
+ let cachedProjectMappings = null;
214
191
  /**
215
192
  * Find workspace root by walking up from file location
216
193
  */
217
- function findWorkspaceRoot(startPath: string): string {
194
+ function findWorkspaceRoot(startPath) {
218
195
  let currentDir = path.dirname(startPath);
219
-
220
196
  for (let i = 0; i < 20; i++) {
221
197
  const packagePath = path.join(currentDir, 'package.json');
222
198
  if (fs.existsSync(packagePath)) {
@@ -226,56 +202,51 @@ function findWorkspaceRoot(startPath: string): string {
226
202
  if (pkg.workspaces || pkg.name === 'webpieces-ts') {
227
203
  return currentDir;
228
204
  }
229
- } catch (err: unknown) {
205
+ }
206
+ catch (err) {
230
207
  //const error = toError(err);
231
208
  void err;
232
209
  }
233
210
  }
234
-
235
211
  const parent = path.dirname(currentDir);
236
- if (parent === currentDir) break;
212
+ if (parent === currentDir)
213
+ break;
237
214
  currentDir = parent;
238
215
  }
239
-
240
216
  return process.cwd();
241
217
  }
242
-
243
218
  /**
244
219
  * Load blessed graph from architecture/dependencies.json
245
220
  */
246
- function loadBlessedGraph(workspaceRoot: string): EnhancedGraph | null {
221
+ function loadBlessedGraph(workspaceRoot) {
247
222
  const graphPath = path.join(workspaceRoot, 'architecture', 'dependencies.json');
248
-
249
223
  // Return cached if same path
250
224
  if (cachedGraphPath === graphPath && cachedGraph !== null) {
251
225
  return cachedGraph;
252
226
  }
253
-
254
227
  if (!fs.existsSync(graphPath)) {
255
228
  return null;
256
229
  }
257
-
258
230
  // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
259
231
  try {
260
232
  const content = fs.readFileSync(graphPath, 'utf-8');
261
- cachedGraph = JSON.parse(content) as EnhancedGraph;
233
+ cachedGraph = JSON.parse(content);
262
234
  cachedGraphPath = graphPath;
263
235
  return cachedGraph;
264
- } catch (err: unknown) {
265
- const error = toError(err);
236
+ }
237
+ catch (err) {
238
+ const error = (0, toError_1.toError)(err);
266
239
  console.error(`[ESLint @webpieces/enforce-architecture] Could not load graph: ${error.message}`);
267
240
  return null;
268
241
  }
269
242
  }
270
-
271
243
  /**
272
244
  * Build set of all workspace package names (from package.json files)
273
245
  * Used to detect workspace imports (works for any scope or unscoped)
274
246
  */
275
- function buildWorkspacePackageNames(workspaceRoot: string): Set<string> {
276
- const packageNames = new Set<string>();
247
+ function buildWorkspacePackageNames(workspaceRoot) {
248
+ const packageNames = new Set();
277
249
  const mappings = buildProjectMappings(workspaceRoot);
278
-
279
250
  for (const mapping of mappings) {
280
251
  const pkgJsonPath = path.join(workspaceRoot, mapping.root, 'package.json');
281
252
  if (fs.existsSync(pkgJsonPath)) {
@@ -285,32 +256,29 @@ function buildWorkspacePackageNames(workspaceRoot: string): Set<string> {
285
256
  if (pkgJson.name) {
286
257
  packageNames.add(pkgJson.name);
287
258
  }
288
- } catch (err: unknown) {
259
+ }
260
+ catch (err) {
289
261
  //const error = toError(err);
290
262
  void err; // Ignore parse errors
291
263
  }
292
264
  }
293
265
  }
294
-
295
266
  return packageNames;
296
267
  }
297
-
298
268
  /**
299
269
  * Check if an import path is a workspace project
300
270
  * Works for scoped (@scope/name) or unscoped (name) packages
301
271
  */
302
- function isWorkspaceImport(importPath: string, workspaceRoot: string): boolean {
272
+ function isWorkspaceImport(importPath, workspaceRoot) {
303
273
  const workspacePackages = buildWorkspacePackageNames(workspaceRoot);
304
274
  return workspacePackages.has(importPath);
305
275
  }
306
-
307
276
  /**
308
277
  * Get project name from package name
309
278
  * e.g., '@webpieces/client' → 'client', 'apis' → 'apis'
310
279
  */
311
- function getProjectNameFromPackageName(packageName: string, workspaceRoot: string): string {
280
+ function getProjectNameFromPackageName(packageName, workspaceRoot) {
312
281
  const mappings = buildProjectMappings(workspaceRoot);
313
-
314
282
  // Try to find by reading package.json files
315
283
  for (const mapping of mappings) {
316
284
  const pkgJsonPath = path.join(workspaceRoot, mapping.root, 'package.json');
@@ -321,59 +289,46 @@ function getProjectNameFromPackageName(packageName: string, workspaceRoot: strin
321
289
  if (pkgJson.name === packageName) {
322
290
  return mapping.name; // Return project name
323
291
  }
324
- } catch (err: unknown) {
292
+ }
293
+ catch (err) {
325
294
  //const error = toError(err);
326
295
  void err; // Ignore parse errors
327
296
  }
328
297
  }
329
298
  }
330
-
331
299
  // Fallback: return package name as-is (might be unscoped project name)
332
300
  return packageName;
333
301
  }
334
-
335
302
  /**
336
303
  * Build project mappings from project.json files in workspace
337
304
  */
338
- function buildProjectMappings(workspaceRoot: string): ProjectMapping[] {
305
+ function buildProjectMappings(workspaceRoot) {
339
306
  if (cachedProjectMappings !== null) {
340
307
  return cachedProjectMappings;
341
308
  }
342
-
343
- const mappings: ProjectMapping[] = [];
344
-
309
+ const mappings = [];
345
310
  // Scan common locations for project.json files
346
311
  const searchDirs = ['packages', 'apps', 'libs', 'libraries', 'services'];
347
-
348
312
  for (const searchDir of searchDirs) {
349
313
  const searchPath = path.join(workspaceRoot, searchDir);
350
- if (!fs.existsSync(searchPath)) continue;
351
-
314
+ if (!fs.existsSync(searchPath))
315
+ continue;
352
316
  scanForProjects(searchPath, workspaceRoot, mappings);
353
317
  }
354
-
355
318
  // Sort by path length (longest first) for more specific matching
356
319
  mappings.sort((a, b) => b.root.length - a.root.length);
357
-
358
320
  cachedProjectMappings = mappings;
359
321
  return mappings;
360
322
  }
361
-
362
323
  /**
363
324
  * Recursively scan for project.json files
364
325
  */
365
- function scanForProjects(
366
- dir: string,
367
- workspaceRoot: string,
368
- mappings: ProjectMapping[]
369
- ): void {
326
+ function scanForProjects(dir, workspaceRoot, mappings) {
370
327
  // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
371
328
  try {
372
329
  const entries = fs.readdirSync(dir, { withFileTypes: true });
373
-
374
330
  for (const entry of entries) {
375
331
  const fullPath = path.join(dir, entry.name);
376
-
377
332
  if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
378
333
  // Check for project.json in this directory
379
334
  const projectJsonPath = path.join(fullPath, 'project.json');
@@ -382,80 +337,72 @@ function scanForProjects(
382
337
  try {
383
338
  const projectJson = JSON.parse(fs.readFileSync(projectJsonPath, 'utf-8'));
384
339
  const projectRoot = path.relative(workspaceRoot, fullPath);
385
-
386
340
  // Use project name from project.json as-is (no scope forcing)
387
341
  const projectName = projectJson.name || entry.name;
388
-
389
342
  mappings.push({
390
343
  root: projectRoot,
391
344
  name: projectName,
392
345
  });
393
- } catch (err: unknown) {
346
+ }
347
+ catch (err) {
394
348
  //const error = toError(err);
395
349
  void err;
396
350
  }
397
351
  }
398
-
399
352
  // Continue scanning subdirectories
400
353
  scanForProjects(fullPath, workspaceRoot, mappings);
401
354
  }
402
355
  }
403
- } catch (err: unknown) {
356
+ }
357
+ catch (err) {
404
358
  //const error = toError(err);
405
359
  void err;
406
360
  }
407
361
  }
408
-
409
362
  /**
410
363
  * Get project name from file path
411
364
  */
412
- function getProjectFromFile(filePath: string, workspaceRoot: string): string | null {
365
+ function getProjectFromFile(filePath, workspaceRoot) {
413
366
  const relativePath = path.relative(workspaceRoot, filePath).replace(/\\/g, '/');
414
367
  const mappings = buildProjectMappings(workspaceRoot);
415
-
416
368
  for (const mapping of mappings) {
417
369
  if (relativePath.startsWith(mapping.root + '/') || relativePath.startsWith(mapping.root)) {
418
370
  return mapping.name;
419
371
  }
420
372
  }
421
-
422
373
  return null;
423
374
  }
424
-
425
375
  /**
426
376
  * Compute all transitive dependencies for a project
427
377
  */
428
- function computeTransitiveDependencies(project: string, graph: EnhancedGraph): Set<string> {
429
- const result = new Set<string>();
430
- const visited = new Set<string>();
431
-
432
- function visit(currentProject: string): void {
433
- if (visited.has(currentProject)) return;
378
+ function computeTransitiveDependencies(project, graph) {
379
+ const result = new Set();
380
+ const visited = new Set();
381
+ function visit(currentProject) {
382
+ if (visited.has(currentProject))
383
+ return;
434
384
  visited.add(currentProject);
435
-
436
385
  const entry = graph[currentProject];
437
- if (!entry || !entry.dependsOn) return;
438
-
386
+ if (!entry || !entry.dependsOn)
387
+ return;
439
388
  for (const dep of entry.dependsOn) {
440
389
  result.add(dep);
441
390
  visit(dep);
442
391
  }
443
392
  }
444
-
445
393
  visit(project);
446
394
  return result;
447
395
  }
448
-
449
- function buildAllowedDepsList(allowedDeps: Set<string>, graph: EnhancedGraph): string {
396
+ function buildAllowedDepsList(allowedDeps, graph) {
450
397
  const sorted = Array.from(allowedDeps).sort((a, b) => {
451
398
  const diff = (graph[b]?.level ?? 0) - (graph[a]?.level ?? 0);
452
399
  return diff !== 0 ? diff : a.localeCompare(b);
453
400
  });
454
- if (sorted.length === 0) return ' (none - this is a foundation project)';
401
+ if (sorted.length === 0)
402
+ return ' (none - this is a foundation project)';
455
403
  return sorted.map((dep) => ` - ${dep} (level ${graph[dep]?.level ?? '?'})`).join('\n');
456
404
  }
457
-
458
- const rule: Rule.RuleModule = {
405
+ const rule = {
459
406
  meta: {
460
407
  type: 'problem',
461
408
  docs: {
@@ -465,72 +412,58 @@ const rule: Rule.RuleModule = {
465
412
  url: 'https://github.com/deanhiller/webpieces-ts',
466
413
  },
467
414
  messages: {
468
- illegalImport:
469
- "⚠️ AI Agent: READ .webpieces/instruct-ai/webpieces.dependencies.md for resolution steps!\n\n" +
415
+ illegalImport: "⚠️ AI Agent: READ .webpieces/instruct-ai/webpieces.dependencies.md for resolution steps!\n\n" +
470
416
  "Import '{{imported}}' violates architecture boundaries.\n\n" +
471
417
  "Project '{{project}}' (level {{level}}) can only import from:\n" +
472
418
  '{{allowedList}}',
473
- noGraph:
474
- 'No architecture graph found at architecture/dependencies.json\n' +
419
+ noGraph: 'No architecture graph found at architecture/dependencies.json\n' +
475
420
  'Run: nx run architecture:validate --mode=update',
476
421
  },
477
422
  schema: [],
478
423
  },
479
-
480
424
  // webpieces-disable max-lines-new-methods -- ESLint rule create method with AST validation
481
- create(context: Rule.RuleContext): Rule.RuleListener {
425
+ create(context) {
482
426
  const filename = context.filename || context.getFilename();
483
427
  const workspaceRoot = findWorkspaceRoot(filename);
484
-
485
428
  return {
486
429
  // webpieces-disable no-any-unknown -- ESLint visitor callback receives untyped AST node
487
- ImportDeclaration(node: any): void {
488
- const importPath = node.source.value as string;
489
-
430
+ ImportDeclaration(node) {
431
+ const importPath = node.source.value;
490
432
  // Check if this is a workspace import (works for any scope or unscoped)
491
433
  if (!isWorkspaceImport(importPath, workspaceRoot)) {
492
434
  return; // Not a workspace import, skip validation
493
435
  }
494
-
495
436
  // Determine which project this file belongs to
496
437
  const sourceProject = getProjectFromFile(filename, workspaceRoot);
497
438
  if (!sourceProject) {
498
439
  // File not in any known project (e.g., tools/, scripts/)
499
440
  return;
500
441
  }
501
-
502
442
  // Convert import (package name) to project name
503
443
  const targetProject = getProjectNameFromPackageName(importPath, workspaceRoot);
504
-
505
444
  // Self-import is always allowed
506
445
  if (targetProject === sourceProject) {
507
446
  return;
508
447
  }
509
-
510
448
  // Load blessed graph
511
449
  const graph = loadBlessedGraph(workspaceRoot);
512
450
  if (!graph) {
513
451
  // No graph file - warn but don't fail (allows gradual adoption)
514
452
  return;
515
453
  }
516
-
517
454
  // Get project entry
518
455
  const projectEntry = graph[sourceProject];
519
456
  if (!projectEntry) {
520
457
  // Project not in graph (new project?) - allow
521
458
  return;
522
459
  }
523
-
524
460
  // Compute allowed dependencies (direct + transitive)
525
461
  const allowedDeps = computeTransitiveDependencies(sourceProject, graph);
526
-
527
462
  // Check if import is allowed (use project name, not package name)
528
463
  if (!allowedDeps.has(targetProject)) {
529
464
  // Write documentation file for AI/developer to read
530
465
  ensureDependenciesDoc(workspaceRoot);
531
-
532
466
  const allowedList = buildAllowedDepsList(allowedDeps, graph);
533
-
534
467
  context.report({
535
468
  node: node.source,
536
469
  messageId: 'illegalImport',
@@ -546,5 +479,5 @@ const rule: Rule.RuleModule = {
546
479
  };
547
480
  },
548
481
  };
549
-
550
- export = rule;
482
+ module.exports = rule;
483
+ //# sourceMappingURL=enforce-architecture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enforce-architecture.js","sourceRoot":"","sources":["../../../../../../packages/tooling/eslint-rules/src/rules/enforce-architecture.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * ESLint rule to enforce architecture boundaries\n *\n * Validates that imports from @webpieces/* packages comply with the\n * blessed dependency graph in .graphs/dependencies.json\n *\n * Supports transitive dependencies: if A depends on B and B depends on C,\n * then A can import from C.\n *\n * Configuration:\n * '@webpieces/enforce-architecture': 'error'\n */\n\nimport type { Rule } from 'eslint';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { toError } from '../toError';\n\nconst DEPENDENCIES_DOC_CONTENT = `# Instructions: Architecture Dependency Violation\n\nIN GENERAL, it is better to avoid these changes and find a different way by moving classes\naround to existing packages you already depend on. It is not always avoidable though.\nA clean dependency graph keeps you out of huge trouble later.\n\nIf you are a human, simply run these commands:\n* nx run architecture:visualize - to see the new dependencies and validate that change is desired\n* nx run architecture:generate - updates the dep graph\n* git diff architecture/dependencies.json - to see the deps changes you made\n\n**READ THIS FILE FIRST before making any changes!**\n\n## ⚠️ CRITICAL WARNING ⚠️\n\n**This is a VERY IMPORTANT change that has LARGE REPERCUSSIONS later!**\n\nAdding new dependencies creates technical debt that compounds over time:\n- Creates coupling between packages that may be hard to undo\n- Can create circular dependency tangles\n- Makes packages harder to test in isolation\n- Increases build times and bundle sizes\n- May force unnecessary upgrades across the codebase\n\n**DO NOT add dependencies without senior developer approval!**\n\n## Understanding the Error\n\nYou've attempted to import from a package that is not in your project's allowed dependencies.\nThe architecture enforces a layered dependency structure where:\n- Level 0 packages are foundation packages with NO dependencies on other @webpieces packages\n- Higher level packages can only depend on lower level packages\n- All dependencies must be explicitly declared\n\n## Steps to Resolve\n\n### Step 1: Generate Current Dependency Graph\nRun this command to see the current architecture:\n\\`\\`\\`bash\nnpx nx run architecture:generate\n\\`\\`\\`\nThis creates/updates \\`architecture/dependencies.json\\` showing all packages and their levels.\n\n### Step 2: Analyze the Proposed Change\nAsk yourself:\n1. **Is this import truly necessary?** Can you refactor to avoid it?\n2. **Should the code move instead?** Maybe the code belongs in a different package.\n3. **Will this create a cycle?** Use \\`npx nx graph\\` to visualize dependencies.\n4. **Can you use an interface/abstraction?** Define interface in lower-level package, implement in higher-level.\n\n### Step 3: Get Senior Developer Approval\n\n## 🛑 AI AGENTS: STOP HERE AND ASK FOR HUMAN APPROVAL! 🛑\n\n**YOU MUST NOT PROCEED TO STEP 4 WITHOUT EXPLICIT HUMAN APPROVAL!**\n\n**REQUIRED**: Discuss this architectural change with a senior developer before proceeding.\n- Explain why the dependency is needed\n- Show you've considered alternatives (Step 2)\n- **WAIT for explicit approval before making ANY changes to project.json or package.json**\n\n**AI Agent Instructions:**\n1. Present your analysis from Step 2 to the human\n2. Explain which package needs which dependency and why\n3. ASK: \"Do you approve adding this dependency?\"\n4. **DO NOT modify project.json or package.json until you receive explicit \"yes\" or approval**\n\n### Step 4: If Approved, Add the Dependency\n\n## ⛔ NEVER MODIFY THESE FILES WITHOUT HUMAN APPROVAL FROM STEP 3! ⛔\n\nOnly after receiving explicit human approval in Step 3, make these changes:\n\n1. **Update project.json** - Add to \\`build.dependsOn\\`:\n \\`\\`\\`json\n {\n \"targets\": {\n \"build\": {\n \"dependsOn\": [\"^build\", \"dep1:build\", \"NEW_PACKAGE:build\"]\n }\n }\n }\n \\`\\`\\`\n\n2. **Update package.json** - Add to \\`dependencies\\`:\n \\`\\`\\`json\n {\n \"dependencies\": {\n \"@webpieces/NEW_PACKAGE\": \"*\"\n }\n }\n \\`\\`\\`\n\n### Step 5: Update Architecture Definition\nRun this command to validate and update the architecture:\n\\`\\`\\`bash\nnpx nx run architecture:generate\n\\`\\`\\`\n\nThis will:\n- Detect any cycles (which MUST be fixed before proceeding)\n- Update \\`architecture/dependencies.json\\` with the new dependency\n- Recalculate package levels\n\n### Step 6: Verify No Cycles\n\\`\\`\\`bash\nnpx nx run architecture:validate-no-architecture-cycles\n\\`\\`\\`\n\nIf cycles are detected, you MUST refactor to break the cycle. Common strategies:\n- Move shared code to a lower-level package\n- Use dependency inversion (interfaces in low-level, implementations in high-level)\n- Restructure package boundaries\n\n## Alternative Solutions (Preferred over adding dependencies)\n\n### Option A: Move the Code\nIf you need functionality from another package, consider moving that code to a shared lower-level package.\n\n### Option B: Dependency Inversion\nDefine an interface in the lower-level package, implement it in the higher-level package:\n\\`\\`\\`typescript\n// In foundation package (level 0)\nexport interface Logger { log(msg: string): void; }\n\n// In higher-level package\nexport class ConsoleLogger implements Logger { ... }\n\\`\\`\\`\n\n### Option C: Pass Dependencies as Parameters\nInstead of importing, receive the dependency as a constructor or method parameter.\n\n## Remember\n- Every dependency you add today is technical debt for tomorrow\n- The best dependency is the one you don't need\n- When in doubt, refactor rather than add dependencies\n`;\n\n// Module-level flag to prevent redundant file creation\nlet dependenciesDocCreated = false;\n\n/**\n * Ensure a documentation file exists at the given path.\n */\nfunction ensureDocFile(docPath: string, content: string): boolean {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n fs.mkdirSync(path.dirname(docPath), { recursive: true });\n fs.writeFileSync(docPath, content, 'utf-8');\n return true;\n } catch (err: unknown) {\n //const error = toError(err);\n void err;\n console.warn(`[webpieces] Could not create doc file: ${docPath}`);\n return false;\n }\n}\n\n/**\n * Ensure the dependencies documentation file exists.\n * Called when an architecture violation is detected.\n */\nfunction ensureDependenciesDoc(workspaceRoot: string): void {\n if (dependenciesDocCreated) return;\n const docPath = path.join(workspaceRoot, 'tmp', 'webpieces', 'webpieces.dependencies.md');\n if (ensureDocFile(docPath, DEPENDENCIES_DOC_CONTENT)) {\n dependenciesDocCreated = true;\n }\n}\n\n/**\n * Graph entry format from .graphs/dependencies.json\n */\ninterface GraphEntry {\n level: number;\n dependsOn: string[];\n}\n\ntype EnhancedGraph = Record<string, GraphEntry>;\n\n/**\n * Project mapping entry\n */\ninterface ProjectMapping {\n root: string;\n name: string;\n}\n\n// Cache for blessed graph (loaded once per lint run)\nlet cachedGraph: EnhancedGraph | null = null;\nlet cachedGraphPath: string | null = null;\n\n// Cache for project mappings\nlet cachedProjectMappings: ProjectMapping[] | null = null;\n\n/**\n * Find workspace root by walking up from file location\n */\nfunction findWorkspaceRoot(startPath: string): string {\n let currentDir = path.dirname(startPath);\n\n for (let i = 0; i < 20; i++) {\n const packagePath = path.join(currentDir, 'package.json');\n if (fs.existsSync(packagePath)) {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf-8'));\n if (pkg.workspaces || pkg.name === 'webpieces-ts') {\n return currentDir;\n }\n } catch (err: unknown) {\n //const error = toError(err);\n void err;\n }\n }\n\n const parent = path.dirname(currentDir);\n if (parent === currentDir) break;\n currentDir = parent;\n }\n\n return process.cwd();\n}\n\n/**\n * Load blessed graph from architecture/dependencies.json\n */\nfunction loadBlessedGraph(workspaceRoot: string): EnhancedGraph | null {\n const graphPath = path.join(workspaceRoot, 'architecture', 'dependencies.json');\n\n // Return cached if same path\n if (cachedGraphPath === graphPath && cachedGraph !== null) {\n return cachedGraph;\n }\n\n if (!fs.existsSync(graphPath)) {\n return null;\n }\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const content = fs.readFileSync(graphPath, 'utf-8');\n cachedGraph = JSON.parse(content) as EnhancedGraph;\n cachedGraphPath = graphPath;\n return cachedGraph;\n } catch (err: unknown) {\n const error = toError(err);\n console.error(`[ESLint @webpieces/enforce-architecture] Could not load graph: ${error.message}`);\n return null;\n }\n}\n\n/**\n * Build set of all workspace package names (from package.json files)\n * Used to detect workspace imports (works for any scope or unscoped)\n */\nfunction buildWorkspacePackageNames(workspaceRoot: string): Set<string> {\n const packageNames = new Set<string>();\n const mappings = buildProjectMappings(workspaceRoot);\n\n for (const mapping of mappings) {\n const pkgJsonPath = path.join(workspaceRoot, mapping.root, 'package.json');\n if (fs.existsSync(pkgJsonPath)) {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));\n if (pkgJson.name) {\n packageNames.add(pkgJson.name);\n }\n } catch (err: unknown) {\n //const error = toError(err);\n void err; // Ignore parse errors\n }\n }\n }\n\n return packageNames;\n}\n\n/**\n * Check if an import path is a workspace project\n * Works for scoped (@scope/name) or unscoped (name) packages\n */\nfunction isWorkspaceImport(importPath: string, workspaceRoot: string): boolean {\n const workspacePackages = buildWorkspacePackageNames(workspaceRoot);\n return workspacePackages.has(importPath);\n}\n\n/**\n * Get project name from package name\n * e.g., '@webpieces/client' → 'client', 'apis' → 'apis'\n */\nfunction getProjectNameFromPackageName(packageName: string, workspaceRoot: string): string {\n const mappings = buildProjectMappings(workspaceRoot);\n\n // Try to find by reading package.json files\n for (const mapping of mappings) {\n const pkgJsonPath = path.join(workspaceRoot, mapping.root, 'package.json');\n if (fs.existsSync(pkgJsonPath)) {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));\n if (pkgJson.name === packageName) {\n return mapping.name; // Return project name\n }\n } catch (err: unknown) {\n //const error = toError(err);\n void err; // Ignore parse errors\n }\n }\n }\n\n // Fallback: return package name as-is (might be unscoped project name)\n return packageName;\n}\n\n/**\n * Build project mappings from project.json files in workspace\n */\nfunction buildProjectMappings(workspaceRoot: string): ProjectMapping[] {\n if (cachedProjectMappings !== null) {\n return cachedProjectMappings;\n }\n\n const mappings: ProjectMapping[] = [];\n\n // Scan common locations for project.json files\n const searchDirs = ['packages', 'apps', 'libs', 'libraries', 'services'];\n\n for (const searchDir of searchDirs) {\n const searchPath = path.join(workspaceRoot, searchDir);\n if (!fs.existsSync(searchPath)) continue;\n\n scanForProjects(searchPath, workspaceRoot, mappings);\n }\n\n // Sort by path length (longest first) for more specific matching\n mappings.sort((a, b) => b.root.length - a.root.length);\n\n cachedProjectMappings = mappings;\n return mappings;\n}\n\n/**\n * Recursively scan for project.json files\n */\nfunction scanForProjects(\n dir: string,\n workspaceRoot: string,\n mappings: ProjectMapping[]\n): void {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n\n if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {\n // Check for project.json in this directory\n const projectJsonPath = path.join(fullPath, 'project.json');\n if (fs.existsSync(projectJsonPath)) {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const projectJson = JSON.parse(fs.readFileSync(projectJsonPath, 'utf-8'));\n const projectRoot = path.relative(workspaceRoot, fullPath);\n\n // Use project name from project.json as-is (no scope forcing)\n const projectName = projectJson.name || entry.name;\n\n mappings.push({\n root: projectRoot,\n name: projectName,\n });\n } catch (err: unknown) {\n //const error = toError(err);\n void err;\n }\n }\n\n // Continue scanning subdirectories\n scanForProjects(fullPath, workspaceRoot, mappings);\n }\n }\n } catch (err: unknown) {\n //const error = toError(err);\n void err;\n }\n}\n\n/**\n * Get project name from file path\n */\nfunction getProjectFromFile(filePath: string, workspaceRoot: string): string | null {\n const relativePath = path.relative(workspaceRoot, filePath).replace(/\\\\/g, '/');\n const mappings = buildProjectMappings(workspaceRoot);\n\n for (const mapping of mappings) {\n if (relativePath.startsWith(mapping.root + '/') || relativePath.startsWith(mapping.root)) {\n return mapping.name;\n }\n }\n\n return null;\n}\n\n/**\n * Compute all transitive dependencies for a project\n */\nfunction computeTransitiveDependencies(project: string, graph: EnhancedGraph): Set<string> {\n const result = new Set<string>();\n const visited = new Set<string>();\n\n function visit(currentProject: string): void {\n if (visited.has(currentProject)) return;\n visited.add(currentProject);\n\n const entry = graph[currentProject];\n if (!entry || !entry.dependsOn) return;\n\n for (const dep of entry.dependsOn) {\n result.add(dep);\n visit(dep);\n }\n }\n\n visit(project);\n return result;\n}\n\nfunction buildAllowedDepsList(allowedDeps: Set<string>, graph: EnhancedGraph): string {\n const sorted = Array.from(allowedDeps).sort((a, b) => {\n const diff = (graph[b]?.level ?? 0) - (graph[a]?.level ?? 0);\n return diff !== 0 ? diff : a.localeCompare(b);\n });\n if (sorted.length === 0) return ' (none - this is a foundation project)';\n return sorted.map((dep) => ` - ${dep} (level ${graph[dep]?.level ?? '?'})`).join('\\n');\n}\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description: 'Enforce blessed architecture dependencies',\n category: 'Best Practices',\n recommended: true,\n url: 'https://github.com/deanhiller/webpieces-ts',\n },\n messages: {\n illegalImport:\n \"⚠️ AI Agent: READ .webpieces/instruct-ai/webpieces.dependencies.md for resolution steps!\\n\\n\" +\n \"Import '{{imported}}' violates architecture boundaries.\\n\\n\" +\n \"Project '{{project}}' (level {{level}}) can only import from:\\n\" +\n '{{allowedList}}',\n noGraph:\n 'No architecture graph found at architecture/dependencies.json\\n' +\n 'Run: nx run architecture:validate --mode=update',\n },\n schema: [],\n },\n\n // webpieces-disable max-lines-new-methods -- ESLint rule create method with AST validation\n create(context: Rule.RuleContext): Rule.RuleListener {\n const filename = context.filename || context.getFilename();\n const workspaceRoot = findWorkspaceRoot(filename);\n\n return {\n // webpieces-disable no-any-unknown -- ESLint visitor callback receives untyped AST node\n ImportDeclaration(node: any): void {\n const importPath = node.source.value as string;\n\n // Check if this is a workspace import (works for any scope or unscoped)\n if (!isWorkspaceImport(importPath, workspaceRoot)) {\n return; // Not a workspace import, skip validation\n }\n\n // Determine which project this file belongs to\n const sourceProject = getProjectFromFile(filename, workspaceRoot);\n if (!sourceProject) {\n // File not in any known project (e.g., tools/, scripts/)\n return;\n }\n\n // Convert import (package name) to project name\n const targetProject = getProjectNameFromPackageName(importPath, workspaceRoot);\n\n // Self-import is always allowed\n if (targetProject === sourceProject) {\n return;\n }\n\n // Load blessed graph\n const graph = loadBlessedGraph(workspaceRoot);\n if (!graph) {\n // No graph file - warn but don't fail (allows gradual adoption)\n return;\n }\n\n // Get project entry\n const projectEntry = graph[sourceProject];\n if (!projectEntry) {\n // Project not in graph (new project?) - allow\n return;\n }\n\n // Compute allowed dependencies (direct + transitive)\n const allowedDeps = computeTransitiveDependencies(sourceProject, graph);\n\n // Check if import is allowed (use project name, not package name)\n if (!allowedDeps.has(targetProject)) {\n // Write documentation file for AI/developer to read\n ensureDependenciesDoc(workspaceRoot);\n\n const allowedList = buildAllowedDepsList(allowedDeps, graph);\n\n context.report({\n node: node.source,\n messageId: 'illegalImport',\n data: {\n imported: importPath,\n project: sourceProject,\n level: String(projectEntry.level),\n allowedList: allowedList,\n },\n });\n }\n },\n };\n },\n};\n\nexport = rule;\n"]}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * ESLint rule to enforce maximum file length
3
+ *
4
+ * Enforces a configurable maximum line count for files.
5
+ * Default: 700 lines
6
+ *
7
+ * Configuration:
8
+ * '@webpieces/max-file-lines': ['error', { max: 700 }]
9
+ */
10
+ import type { Rule } from 'eslint';
11
+ declare const rule: Rule.RuleModule;
12
+ export = rule;
@@ -1,3 +1,4 @@
1
+ "use strict";
1
2
  /**
2
3
  * ESLint rule to enforce maximum file length
3
4
  *
@@ -7,16 +8,10 @@
7
8
  * Configuration:
8
9
  * '@webpieces/max-file-lines': ['error', { max: 700 }]
9
10
  */
10
-
11
- import type { Rule } from 'eslint';
12
- import * as fs from 'fs';
13
- import * as path from 'path';
14
- import { toError } from '../toError';
15
-
16
- interface FileLinesOptions {
17
- max: number;
18
- }
19
-
11
+ const tslib_1 = require("tslib");
12
+ const fs = tslib_1.__importStar(require("fs"));
13
+ const path = tslib_1.__importStar(require("path"));
14
+ const toError_1 = require("../toError");
20
15
  const FILE_DOC_CONTENT = `# AI Agent Instructions: File Too Long
21
16
 
22
17
  **READ THIS FILE to fix files that are too long**
@@ -164,14 +159,11 @@ export class MyController {
164
159
 
165
160
  Remember: Find the "child code" and pull it down into a new class. Once moved, the code's purpose becomes clear, making it easy to rename to a logical name.
166
161
  `;
167
-
168
162
  // Module-level flag to prevent redundant file creation
169
163
  let fileDocCreated = false;
170
-
171
- function getWorkspaceRoot(context: Rule.RuleContext): string {
164
+ function getWorkspaceRoot(context) {
172
165
  const filename = context.filename || context.getFilename();
173
166
  let dir = path.dirname(filename);
174
-
175
167
  // Walk up directory tree to find workspace root
176
168
  while (dir !== path.dirname(dir)) {
177
169
  const pkgPath = path.join(dir, 'package.json');
@@ -182,7 +174,8 @@ function getWorkspaceRoot(context: Rule.RuleContext): string {
182
174
  if (pkg.workspaces || pkg.name === 'webpieces-ts') {
183
175
  return dir;
184
176
  }
185
- } catch (err: unknown) {
177
+ }
178
+ catch (err) {
186
179
  //const error = toError(err);
187
180
  void err; // Continue searching if JSON parse fails
188
181
  }
@@ -191,32 +184,29 @@ function getWorkspaceRoot(context: Rule.RuleContext): string {
191
184
  }
192
185
  return process.cwd(); // Fallback
193
186
  }
194
-
195
- function ensureDocFile(docPath: string, content: string): boolean {
187
+ function ensureDocFile(docPath, content) {
196
188
  // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
197
189
  try {
198
190
  fs.mkdirSync(path.dirname(docPath), { recursive: true });
199
191
  fs.writeFileSync(docPath, content, 'utf-8');
200
192
  return true;
201
- } catch (err: unknown) {
202
- const error = toError(err);
193
+ }
194
+ catch (err) {
195
+ const error = (0, toError_1.toError)(err);
203
196
  console.warn(`[webpieces] Could not create doc file: ${docPath}`, error);
204
197
  return false;
205
198
  }
206
199
  }
207
-
208
- function ensureFileDoc(context: Rule.RuleContext): void {
209
- if (fileDocCreated) return; // Performance: only create once per lint run
210
-
200
+ function ensureFileDoc(context) {
201
+ if (fileDocCreated)
202
+ return; // Performance: only create once per lint run
211
203
  const workspaceRoot = getWorkspaceRoot(context);
212
204
  const docPath = path.join(workspaceRoot, '.webpieces', 'instruct-ai', 'webpieces.filesize.md');
213
-
214
205
  if (ensureDocFile(docPath, FILE_DOC_CONTENT)) {
215
206
  fileDocCreated = true;
216
207
  }
217
208
  }
218
-
219
- const rule: Rule.RuleModule = {
209
+ const rule = {
220
210
  meta: {
221
211
  type: 'suggestion',
222
212
  docs: {
@@ -226,8 +216,7 @@ const rule: Rule.RuleModule = {
226
216
  url: 'https://github.com/deanhiller/webpieces-ts',
227
217
  },
228
218
  messages: {
229
- tooLong:
230
- 'AI Agent: READ .webpieces/instruct-ai/webpieces.filesize.md for fix instructions. File has {{actual}} lines (max: {{max}})',
219
+ tooLong: 'AI Agent: READ .webpieces/instruct-ai/webpieces.filesize.md for fix instructions. File has {{actual}} lines (max: {{max}})',
231
220
  },
232
221
  fixable: undefined,
233
222
  schema: [
@@ -243,20 +232,16 @@ const rule: Rule.RuleModule = {
243
232
  },
244
233
  ],
245
234
  },
246
-
247
- create(context: Rule.RuleContext): Rule.RuleListener {
248
- const options = context.options[0] as FileLinesOptions | undefined;
235
+ create(context) {
236
+ const options = context.options[0];
249
237
  const maxLines = options?.max ?? 700;
250
-
251
238
  return {
252
239
  // webpieces-disable no-any-unknown -- ESTree AST nodes require any for dynamic properties
253
- Program(node: any): void {
240
+ Program(node) {
254
241
  ensureFileDoc(context);
255
-
256
242
  const sourceCode = context.sourceCode || context.getSourceCode();
257
243
  const lines = sourceCode.lines;
258
244
  const lineCount = lines.length;
259
-
260
245
  if (lineCount > maxLines) {
261
246
  context.report({
262
247
  node,
@@ -271,5 +256,5 @@ const rule: Rule.RuleModule = {
271
256
  };
272
257
  },
273
258
  };
274
-
275
- export = rule;
259
+ module.exports = rule;
260
+ //# sourceMappingURL=max-file-lines.js.map