@webpieces/nx-webpieces-rules 0.3.238 → 0.3.240

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/nx-webpieces-rules",
3
- "version": "0.3.238",
3
+ "version": "0.3.240",
4
4
  "description": "Nx-specific webpieces validation rules and graph tooling. Bundles all @webpieces rule packages with Nx graph validators and an inference plugin.",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -18,11 +18,11 @@
18
18
  "README.md"
19
19
  ],
20
20
  "dependencies": {
21
- "@webpieces/ai-hook-rules": "0.3.238",
22
- "@webpieces/code-rules": "0.3.238",
23
- "@webpieces/eslint-rules": "0.3.238",
24
- "@webpieces/pr-gate": "0.3.238",
25
- "@webpieces/rules-config": "0.3.238",
21
+ "@webpieces/ai-hook-rules": "0.3.240",
22
+ "@webpieces/code-rules": "0.3.240",
23
+ "@webpieces/eslint-rules": "0.3.240",
24
+ "@webpieces/pr-gate": "0.3.240",
25
+ "@webpieces/rules-config": "0.3.240",
26
26
  "madge": "8.0.0"
27
27
  },
28
28
  "peerDependencies": {
@@ -9,9 +9,11 @@
9
9
  */
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
11
  exports.default = runExecutor;
12
+ const rules_config_1 = require("@webpieces/rules-config");
12
13
  const graph_generator_1 = require("../../lib/graph-generator");
13
14
  const graph_sorter_1 = require("../../lib/graph-sorter");
14
15
  const graph_loader_1 = require("../../lib/graph-loader");
16
+ const graph_metadata_1 = require("../../lib/graph-metadata");
15
17
  const runtime_markers_1 = require("../../lib/runtime-markers");
16
18
  const runtime_graph_1 = require("../../lib/runtime-graph");
17
19
  const runtime_config_1 = require("../../lib/runtime-config");
@@ -46,11 +48,17 @@ async function runExecutor(options, context) {
46
48
  // Step 2: Topological sort (to assign levels for visualization)
47
49
  console.log('šŸ”„ Computing topological layers...');
48
50
  const enhancedGraph = (0, graph_sorter_1.sortGraphTopologically)(reducedGraph);
49
- // Step 3: Save the graph
51
+ // Step 3: Enrich with AI metadata (framework, shortDescription, file
52
+ // pointers). This VALIDATES (responsibilities.md required per project)
53
+ // and throws before any write, so a failure never clobbers the file.
54
+ console.log('šŸ·ļø Enriching graph with framework + responsibilities metadata...');
55
+ const projectInfos = await (0, graph_metadata_1.collectProjectInfo)();
56
+ (0, graph_metadata_1.enrichGraph)(enhancedGraph, projectInfos, workspaceRoot);
57
+ // Step 4: Save the graph
50
58
  console.log('šŸ’¾ Saving graph to architecture/dependencies.json...');
51
59
  (0, graph_loader_1.saveGraph)(enhancedGraph, workspaceRoot, graphPath);
52
60
  console.log('āœ… Graph saved successfully');
53
- // Step 4: Generate the runtime microservice graph at the same time
61
+ // Step 5: Generate the runtime microservice graph at the same time
54
62
  await generateRuntimeGraph(workspaceRoot);
55
63
  // Print summary
56
64
  const projectCount = Object.keys(enhancedGraph).length;
@@ -63,6 +71,11 @@ async function runExecutor(options, context) {
63
71
  catch (err) {
64
72
  const error = (0, toError_1.toError)(err);
65
73
  console.error('āŒ Graph generation failed:', error.message);
74
+ if (error instanceof graph_metadata_1.MetadataValidationError) {
75
+ const mdPath = (0, rules_config_1.writeTemplate)(workspaceRoot, 'webpieces.responsibilities.md');
76
+ console.error('');
77
+ console.error('āš ļø *** Refer to ' + mdPath + ' for how to author responsibilities.md files *** āš ļø');
78
+ }
66
79
  return { success: false };
67
80
  }
68
81
  }
@@ -1 +1 @@
1
- {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/generate/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Generate Executor\n *\n * Generates the architecture dependency graph and saves it to architecture/dependencies.json.\n *\n * Usage:\n * nx run architecture:generate\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { generateReducedGraph } from '../../lib/graph-generator';\nimport { sortGraphTopologically } from '../../lib/graph-sorter';\nimport { saveGraph } from '../../lib/graph-loader';\nimport { buildWorkspaceModel } from '../../lib/runtime-markers';\nimport { assembleRuntimeGraph, saveRuntimeGraph } from '../../lib/runtime-graph';\nimport { loadRuntimeConfig } from '../../lib/runtime-config';\nimport { toError } from '../../toError';\n\nexport interface GenerateExecutorOptions {\n graphPath?: string;\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\n/**\n * Generate the runtime microservice graph alongside the compile-time graph, so\n * one regenerate produces both committed files. Skipped when the\n * runtime-architecture rule is OFF or no apiProjectPaths are configured.\n */\nasync function generateRuntimeGraph(workspaceRoot: string): Promise<void> {\n const config = loadRuntimeConfig(workspaceRoot);\n if (config.off || config.servicePaths.length === 0) {\n console.log('ā­ļø Runtime graph skipped (runtime-architecture OFF or no servicePaths)');\n return;\n }\n console.log('šŸ“” Generating runtime graph from service-contract.json files...');\n const model = await buildWorkspaceModel(workspaceRoot, config.apiProjectPaths, config.servicePaths);\n const runtimeGraph = assembleRuntimeGraph(model, workspaceRoot);\n saveRuntimeGraph(runtimeGraph, workspaceRoot);\n const serviceCount = Object.keys(runtimeGraph.services).length;\n console.log(\n `āœ… Runtime graph saved (${serviceCount} services, ${runtimeGraph.runtimeEdges.length} runtime edges)`,\n );\n}\n\nexport default async function runExecutor(\n options: GenerateExecutorOptions,\n context: ExecutorContext\n): Promise<ExecutorResult> {\n const graphPath = options.graphPath;\n const workspaceRoot = context.root;\n\n console.log('\\nšŸ“Š Architecture Graph Generator\\n');\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // Step 1: Build the full graph from nx, then transitively reduce it to the view\n console.log(\"šŸ“Š Generating dependency graph from nx's project graph...\");\n const reducedGraph = await generateReducedGraph();\n\n // Step 2: Topological sort (to assign levels for visualization)\n console.log('šŸ”„ Computing topological layers...');\n const enhancedGraph = sortGraphTopologically(reducedGraph);\n\n // Step 3: Save the graph\n console.log('šŸ’¾ Saving graph to architecture/dependencies.json...');\n saveGraph(enhancedGraph, workspaceRoot, graphPath);\n console.log('āœ… Graph saved successfully');\n\n // Step 4: Generate the runtime microservice graph at the same time\n await generateRuntimeGraph(workspaceRoot);\n\n // Print summary\n const projectCount = Object.keys(enhancedGraph).length;\n const levels = new Set(Object.values(enhancedGraph).map((e) => e.level));\n console.log(`\\nšŸ“ˆ Graph Summary:`);\n console.log(` Projects: ${projectCount}`);\n console.log(` Levels: ${levels.size} (0-${Math.max(...levels)})`);\n\n return { success: true };\n } catch (err: unknown) {\n const error = toError(err);\n console.error('āŒ Graph generation failed:', error.message);\n return { success: false };\n }\n}\n"]}
1
+ {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/generate/executor.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;AA0CH,8BAoDC;AA3FD,0DAAwD;AACxD,+DAAiE;AACjE,yDAAgE;AAChE,yDAAmD;AACnD,6DAAoG;AACpG,+DAAgE;AAChE,2DAAiF;AACjF,6DAA6D;AAC7D,2CAAwC;AAUxC;;;;GAIG;AACH,KAAK,UAAU,oBAAoB,CAAC,aAAqB;IACrD,MAAM,MAAM,GAAG,IAAA,kCAAiB,EAAC,aAAa,CAAC,CAAC;IAChD,IAAI,MAAM,CAAC,GAAG,IAAI,MAAM,CAAC,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACjD,OAAO,CAAC,GAAG,CAAC,yEAAyE,CAAC,CAAC;QACvF,OAAO;IACX,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,iEAAiE,CAAC,CAAC;IAC/E,MAAM,KAAK,GAAG,MAAM,IAAA,qCAAmB,EAAC,aAAa,EAAE,MAAM,CAAC,eAAe,EAAE,MAAM,CAAC,YAAY,CAAC,CAAC;IACpG,MAAM,YAAY,GAAG,IAAA,oCAAoB,EAAC,KAAK,EAAE,aAAa,CAAC,CAAC;IAChE,IAAA,gCAAgB,EAAC,YAAY,EAAE,aAAa,CAAC,CAAC;IAC9C,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC;IAC/D,OAAO,CAAC,GAAG,CACP,0BAA0B,YAAY,cAAc,YAAY,CAAC,YAAY,CAAC,MAAM,iBAAiB,CACxG,CAAC;AACN,CAAC;AAEc,KAAK,UAAU,WAAW,CACrC,OAAgC,EAChC,OAAwB;IAExB,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;IACpC,MAAM,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAEnC,OAAO,CAAC,GAAG,CAAC,qCAAqC,CAAC,CAAC;IAEnD,8DAA8D;IAC9D,IAAI,CAAC;QACD,gFAAgF;QAChF,OAAO,CAAC,GAAG,CAAC,2DAA2D,CAAC,CAAC;QACzE,MAAM,YAAY,GAAG,MAAM,IAAA,sCAAoB,GAAE,CAAC;QAElD,gEAAgE;QAChE,OAAO,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC;QAClD,MAAM,aAAa,GAAG,IAAA,qCAAsB,EAAC,YAAY,CAAC,CAAC;QAE3D,qEAAqE;QACrE,uEAAuE;QACvE,qEAAqE;QACrE,OAAO,CAAC,GAAG,CAAC,oEAAoE,CAAC,CAAC;QAClF,MAAM,YAAY,GAAG,MAAM,IAAA,mCAAkB,GAAE,CAAC;QAChD,IAAA,4BAAW,EAAC,aAAa,EAAE,YAAY,EAAE,aAAa,CAAC,CAAC;QAExD,yBAAyB;QACzB,OAAO,CAAC,GAAG,CAAC,sDAAsD,CAAC,CAAC;QACpE,IAAA,wBAAS,EAAC,aAAa,EAAE,aAAa,EAAE,SAAS,CAAC,CAAC;QACnD,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;QAE1C,mEAAmE;QACnE,MAAM,oBAAoB,CAAC,aAAa,CAAC,CAAC;QAE1C,gBAAgB;QAChB,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,MAAM,CAAC;QACvD,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;QACzE,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;QACnC,OAAO,CAAC,GAAG,CAAC,gBAAgB,YAAY,EAAE,CAAC,CAAC;QAC5C,OAAO,CAAC,GAAG,CAAC,cAAc,MAAM,CAAC,IAAI,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QAEpE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC7B,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAA,iBAAO,EAAC,GAAG,CAAC,CAAC;QAC3B,OAAO,CAAC,KAAK,CAAC,4BAA4B,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;QAC3D,IAAI,KAAK,YAAY,wCAAuB,EAAE,CAAC;YAC3C,MAAM,MAAM,GAAG,IAAA,4BAAa,EAAC,aAAa,EAAE,+BAA+B,CAAC,CAAC;YAC7E,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAClB,OAAO,CAAC,KAAK,CAAC,mBAAmB,GAAG,MAAM,GAAG,qDAAqD,CAAC,CAAC;QACxG,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC9B,CAAC;AACL,CAAC","sourcesContent":["/**\n * Generate Executor\n *\n * Generates the architecture dependency graph and saves it to architecture/dependencies.json.\n *\n * Usage:\n * nx run architecture:generate\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { writeTemplate } from '@webpieces/rules-config';\nimport { generateReducedGraph } from '../../lib/graph-generator';\nimport { sortGraphTopologically } from '../../lib/graph-sorter';\nimport { saveGraph } from '../../lib/graph-loader';\nimport { collectProjectInfo, enrichGraph, MetadataValidationError } from '../../lib/graph-metadata';\nimport { buildWorkspaceModel } from '../../lib/runtime-markers';\nimport { assembleRuntimeGraph, saveRuntimeGraph } from '../../lib/runtime-graph';\nimport { loadRuntimeConfig } from '../../lib/runtime-config';\nimport { toError } from '../../toError';\n\nexport interface GenerateExecutorOptions {\n graphPath?: string;\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\n/**\n * Generate the runtime microservice graph alongside the compile-time graph, so\n * one regenerate produces both committed files. Skipped when the\n * runtime-architecture rule is OFF or no apiProjectPaths are configured.\n */\nasync function generateRuntimeGraph(workspaceRoot: string): Promise<void> {\n const config = loadRuntimeConfig(workspaceRoot);\n if (config.off || config.servicePaths.length === 0) {\n console.log('ā­ļø Runtime graph skipped (runtime-architecture OFF or no servicePaths)');\n return;\n }\n console.log('šŸ“” Generating runtime graph from service-contract.json files...');\n const model = await buildWorkspaceModel(workspaceRoot, config.apiProjectPaths, config.servicePaths);\n const runtimeGraph = assembleRuntimeGraph(model, workspaceRoot);\n saveRuntimeGraph(runtimeGraph, workspaceRoot);\n const serviceCount = Object.keys(runtimeGraph.services).length;\n console.log(\n `āœ… Runtime graph saved (${serviceCount} services, ${runtimeGraph.runtimeEdges.length} runtime edges)`,\n );\n}\n\nexport default async function runExecutor(\n options: GenerateExecutorOptions,\n context: ExecutorContext\n): Promise<ExecutorResult> {\n const graphPath = options.graphPath;\n const workspaceRoot = context.root;\n\n console.log('\\nšŸ“Š Architecture Graph Generator\\n');\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // Step 1: Build the full graph from nx, then transitively reduce it to the view\n console.log(\"šŸ“Š Generating dependency graph from nx's project graph...\");\n const reducedGraph = await generateReducedGraph();\n\n // Step 2: Topological sort (to assign levels for visualization)\n console.log('šŸ”„ Computing topological layers...');\n const enhancedGraph = sortGraphTopologically(reducedGraph);\n\n // Step 3: Enrich with AI metadata (framework, shortDescription, file\n // pointers). This VALIDATES (responsibilities.md required per project)\n // and throws before any write, so a failure never clobbers the file.\n console.log('šŸ·ļø Enriching graph with framework + responsibilities metadata...');\n const projectInfos = await collectProjectInfo();\n enrichGraph(enhancedGraph, projectInfos, workspaceRoot);\n\n // Step 4: Save the graph\n console.log('šŸ’¾ Saving graph to architecture/dependencies.json...');\n saveGraph(enhancedGraph, workspaceRoot, graphPath);\n console.log('āœ… Graph saved successfully');\n\n // Step 5: Generate the runtime microservice graph at the same time\n await generateRuntimeGraph(workspaceRoot);\n\n // Print summary\n const projectCount = Object.keys(enhancedGraph).length;\n const levels = new Set(Object.values(enhancedGraph).map((e) => e.level));\n console.log(`\\nšŸ“ˆ Graph Summary:`);\n console.log(` Projects: ${projectCount}`);\n console.log(` Levels: ${levels.size} (0-${Math.max(...levels)})`);\n\n return { success: true };\n } catch (err: unknown) {\n const error = toError(err);\n console.error('āŒ Graph generation failed:', error.message);\n if (error instanceof MetadataValidationError) {\n const mdPath = writeTemplate(workspaceRoot, 'webpieces.responsibilities.md');\n console.error('');\n console.error('āš ļø *** Refer to ' + mdPath + ' for how to author responsibilities.md files *** āš ļø');\n }\n return { success: false };\n }\n}\n"]}
@@ -15,6 +15,7 @@ const graph_generator_1 = require("../../lib/graph-generator");
15
15
  const graph_sorter_1 = require("../../lib/graph-sorter");
16
16
  const graph_comparator_1 = require("../../lib/graph-comparator");
17
17
  const graph_loader_1 = require("../../lib/graph-loader");
18
+ const graph_metadata_1 = require("../../lib/graph-metadata");
18
19
  const toError_1 = require("../../toError");
19
20
  const TMP_MD_FILE = 'webpieces.dependencies.md';
20
21
  /**
@@ -25,6 +26,22 @@ function writeTmpInstructionsFile(workspaceRoot) {
25
26
  const mdPath = (0, rules_config_1.writeTemplate)(workspaceRoot, TMP_MD_FILE);
26
27
  return mdPath;
27
28
  }
29
+ /**
30
+ * Report a current-vs-saved graph mismatch and write the AI instructions file.
31
+ */
32
+ function reportMismatch(summary, workspaceRoot) {
33
+ const mdPath = writeTmpInstructionsFile(workspaceRoot);
34
+ console.error('āŒ Architecture has changed since last update!');
35
+ console.error('\nDifferences:');
36
+ console.error(summary);
37
+ console.error('');
38
+ console.error('āš ļø *** Refer to ' + mdPath + ' for instructions on how to fix *** āš ļø');
39
+ console.error('');
40
+ console.error('To fix:');
41
+ console.error(' 1. Review the changes above');
42
+ console.error(' 2. If intentional, ASK USER to run: nx run architecture:generate since this is a critical change');
43
+ console.error(' 3. Commit the updated architecture/dependencies.json');
44
+ }
28
45
  async function runExecutor(options, context) {
29
46
  const graphPath = options.graphPath;
30
47
  const workspaceRoot = context.root;
@@ -48,39 +65,36 @@ async function runExecutor(options, context) {
48
65
  // Step 2: Topological sort (to get enhanced graph with levels)
49
66
  console.log('šŸ”„ Computing topological layers...');
50
67
  const currentGraph = (0, graph_sorter_1.sortGraphTopologically)(reducedGraph);
51
- // Step 3: Load saved graph
68
+ // Step 3: Enrich with AI metadata so edits to responsibilities.md,
69
+ // framework tags, or project moves are caught as graph changes
70
+ console.log('šŸ·ļø Enriching graph with framework + responsibilities metadata...');
71
+ const projectInfos = await (0, graph_metadata_1.collectProjectInfo)();
72
+ (0, graph_metadata_1.enrichGraph)(currentGraph, projectInfos, workspaceRoot);
73
+ // Step 4: Load saved graph
52
74
  console.log('šŸ“‚ Loading saved graph...');
53
75
  const savedGraph = (0, graph_loader_1.loadBlessedGraph)(workspaceRoot, graphPath);
54
76
  if (!savedGraph) {
55
77
  console.error('āŒ Could not load saved graph');
56
78
  return { success: false };
57
79
  }
58
- // Step 4: Compare graphs
80
+ // Step 5: Compare graphs
59
81
  console.log('šŸ” Comparing current graph to saved graph...');
60
- const comparison = (0, graph_comparator_1.compareGraphs)(currentGraph, savedGraph);
82
+ const comparison = (0, graph_comparator_1.compareGraphs)(currentGraph, savedGraph.projects);
61
83
  if (comparison.identical) {
62
84
  console.log('āœ… Architecture unchanged - current graph matches saved graph');
63
85
  return { success: true };
64
86
  }
65
- else {
66
- // Write instructions file for AI agent
67
- const mdPath = writeTmpInstructionsFile(workspaceRoot);
68
- console.error('āŒ Architecture has changed since last update!');
69
- console.error('\nDifferences:');
70
- console.error(comparison.summary);
71
- console.error('');
72
- console.error('āš ļø *** Refer to ' + mdPath + ' for instructions on how to fix *** āš ļø');
73
- console.error('');
74
- console.error('To fix:');
75
- console.error(' 1. Review the changes above');
76
- console.error(' 2. If intentional, ASK USER to run: nx run architecture:generate since this is a critical change');
77
- console.error(' 3. Commit the updated architecture/dependencies.json');
78
- return { success: false };
79
- }
87
+ reportMismatch(comparison.summary, workspaceRoot);
88
+ return { success: false };
80
89
  }
81
90
  catch (err) {
82
91
  const error = (0, toError_1.toError)(err);
83
92
  console.error('āŒ Architecture validation failed:', error.message);
93
+ if (error instanceof graph_metadata_1.MetadataValidationError) {
94
+ const mdPath = (0, rules_config_1.writeTemplate)(workspaceRoot, 'webpieces.responsibilities.md');
95
+ console.error('');
96
+ console.error('āš ļø *** Refer to ' + mdPath + ' for how to author responsibilities.md files *** āš ļø');
97
+ }
84
98
  return { success: false };
85
99
  }
86
100
  }
@@ -1 +1 @@
1
- {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/validate-architecture-unchanged/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate Architecture Unchanged Executor\n *\n * Validates that the current architecture graph matches the saved blessed graph.\n * This ensures no unapproved architecture changes have been made.\n *\n * Usage:\n * nx run architecture:validate-architecture-unchanged\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { writeTemplate } from '@webpieces/rules-config';\nimport { generateReducedGraph } from '../../lib/graph-generator';\nimport { sortGraphTopologically } from '../../lib/graph-sorter';\nimport { compareGraphs } from '../../lib/graph-comparator';\nimport { loadBlessedGraph, graphFileExists } from '../../lib/graph-loader';\nimport { toError } from '../../toError';\n\nexport interface ValidateArchitectureUnchangedOptions {\n graphPath?: string;\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\nconst TMP_MD_FILE = 'webpieces.dependencies.md';\n\n/**\n * Write the instructions documentation to .webpieces/instruct-ai/.\n * Sourced from @webpieces/rules-config.\n */\nfunction writeTmpInstructionsFile(workspaceRoot: string): string {\n const mdPath = writeTemplate(workspaceRoot, TMP_MD_FILE);\n\n return mdPath;\n}\n\nexport default async function runExecutor(\n options: ValidateArchitectureUnchangedOptions,\n context: ExecutorContext\n): Promise<ExecutorResult> {\n const graphPath = options.graphPath;\n const workspaceRoot = context.root;\n\n console.log('\\nšŸ” Validating Architecture Unchanged\\n');\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // Check if saved graph exists\n if (!graphFileExists(workspaceRoot, graphPath)) {\n console.error('āŒ No saved graph found at architecture/dependencies.json');\n console.error('');\n console.error('To initialize:');\n console.error(' 1. Run: nx run architecture:generate');\n console.error(' 2. Run: nx run architecture:visualize');\n console.error(' 3. Manually inspect the generated graph to confirm it is the desired architecture');\n console.error(' 4. Commit architecture/dependencies.json');\n return { success: false };\n }\n\n // Step 1: Build the full graph from nx, then transitively reduce it\n console.log('šŸ“Š Generating current dependency graph...');\n const reducedGraph = await generateReducedGraph();\n\n // Step 2: Topological sort (to get enhanced graph with levels)\n console.log('šŸ”„ Computing topological layers...');\n const currentGraph = sortGraphTopologically(reducedGraph);\n\n // Step 3: Load saved graph\n console.log('šŸ“‚ Loading saved graph...');\n const savedGraph = loadBlessedGraph(workspaceRoot, graphPath);\n\n if (!savedGraph) {\n console.error('āŒ Could not load saved graph');\n return { success: false };\n }\n\n // Step 4: Compare graphs\n console.log('šŸ” Comparing current graph to saved graph...');\n const comparison = compareGraphs(currentGraph, savedGraph);\n\n if (comparison.identical) {\n console.log('āœ… Architecture unchanged - current graph matches saved graph');\n return { success: true };\n } else {\n // Write instructions file for AI agent\n const mdPath = writeTmpInstructionsFile(workspaceRoot);\n\n console.error('āŒ Architecture has changed since last update!');\n console.error('\\nDifferences:');\n console.error(comparison.summary);\n console.error('');\n console.error('āš ļø *** Refer to ' + mdPath + ' for instructions on how to fix *** āš ļø');\n console.error('');\n console.error('To fix:');\n console.error(' 1. Review the changes above');\n console.error(' 2. If intentional, ASK USER to run: nx run architecture:generate since this is a critical change');\n console.error(' 3. Commit the updated architecture/dependencies.json');\n return { success: false };\n }\n } catch (err: unknown) {\n const error = toError(err);\n console.error('āŒ Architecture validation failed:', error.message);\n return { success: false };\n }\n}\n"]}
1
+ {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/validate-architecture-unchanged/executor.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;AAiDH,8BAkEC;AAhHD,0DAAwD;AACxD,+DAAiE;AACjE,yDAAgE;AAChE,iEAA2D;AAC3D,yDAA2E;AAC3E,6DAAoG;AACpG,2CAAwC;AAUxC,MAAM,WAAW,GAAG,2BAA2B,CAAC;AAEhD;;;GAGG;AACH,SAAS,wBAAwB,CAAC,aAAqB;IACnD,MAAM,MAAM,GAAG,IAAA,4BAAa,EAAC,aAAa,EAAE,WAAW,CAAC,CAAC;IAEzD,OAAO,MAAM,CAAC;AAClB,CAAC;AAED;;GAEG;AACH,SAAS,cAAc,CAAC,OAAe,EAAE,aAAqB;IAC1D,MAAM,MAAM,GAAG,wBAAwB,CAAC,aAAa,CAAC,CAAC;IAEvD,OAAO,CAAC,KAAK,CAAC,+CAA+C,CAAC,CAAC;IAC/D,OAAO,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;IAChC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACvB,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAClB,OAAO,CAAC,KAAK,CAAC,mBAAmB,GAAG,MAAM,GAAG,wCAAwC,CAAC,CAAC;IACvF,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAClB,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IACzB,OAAO,CAAC,KAAK,CAAC,+BAA+B,CAAC,CAAC;IAC/C,OAAO,CAAC,KAAK,CAAC,oGAAoG,CAAC,CAAC;IACpH,OAAO,CAAC,KAAK,CAAC,wDAAwD,CAAC,CAAC;AAC5E,CAAC;AAEc,KAAK,UAAU,WAAW,CACrC,OAA6C,EAC7C,OAAwB;IAExB,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;IACpC,MAAM,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAEnC,OAAO,CAAC,GAAG,CAAC,0CAA0C,CAAC,CAAC;IAExD,8DAA8D;IAC9D,IAAI,CAAC;QACD,8BAA8B;QAC9B,IAAI,CAAC,IAAA,8BAAe,EAAC,aAAa,EAAE,SAAS,CAAC,EAAE,CAAC;YAC7C,OAAO,CAAC,KAAK,CAAC,0DAA0D,CAAC,CAAC;YAC1E,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAClB,OAAO,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;YAChC,OAAO,CAAC,KAAK,CAAC,wCAAwC,CAAC,CAAC;YACxD,OAAO,CAAC,KAAK,CAAC,yCAAyC,CAAC,CAAC;YACzD,OAAO,CAAC,KAAK,CAAC,qFAAqF,CAAC,CAAC;YACrG,OAAO,CAAC,KAAK,CAAC,4CAA4C,CAAC,CAAC;YAC5D,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QAC9B,CAAC;QAED,oEAAoE;QACpE,OAAO,CAAC,GAAG,CAAC,2CAA2C,CAAC,CAAC;QACzD,MAAM,YAAY,GAAG,MAAM,IAAA,sCAAoB,GAAE,CAAC;QAElD,+DAA+D;QAC/D,OAAO,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC;QAClD,MAAM,YAAY,GAAG,IAAA,qCAAsB,EAAC,YAAY,CAAC,CAAC;QAE1D,mEAAmE;QACnE,+DAA+D;QAC/D,OAAO,CAAC,GAAG,CAAC,oEAAoE,CAAC,CAAC;QAClF,MAAM,YAAY,GAAG,MAAM,IAAA,mCAAkB,GAAE,CAAC;QAChD,IAAA,4BAAW,EAAC,YAAY,EAAE,YAAY,EAAE,aAAa,CAAC,CAAC;QAEvD,2BAA2B;QAC3B,OAAO,CAAC,GAAG,CAAC,2BAA2B,CAAC,CAAC;QACzC,MAAM,UAAU,GAAG,IAAA,+BAAgB,EAAC,aAAa,EAAE,SAAS,CAAC,CAAC;QAE9D,IAAI,CAAC,UAAU,EAAE,CAAC;YACd,OAAO,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAAC;YAC9C,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QAC9B,CAAC;QAED,yBAAyB;QACzB,OAAO,CAAC,GAAG,CAAC,8CAA8C,CAAC,CAAC;QAC5D,MAAM,UAAU,GAAG,IAAA,gCAAa,EAAC,YAAY,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC;QAEpE,IAAI,UAAU,CAAC,SAAS,EAAE,CAAC;YACvB,OAAO,CAAC,GAAG,CAAC,8DAA8D,CAAC,CAAC;YAC5E,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QAC7B,CAAC;QACD,cAAc,CAAC,UAAU,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;QAClD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC9B,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAA,iBAAO,EAAC,GAAG,CAAC,CAAC;QAC3B,OAAO,CAAC,KAAK,CAAC,mCAAmC,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;QAClE,IAAI,KAAK,YAAY,wCAAuB,EAAE,CAAC;YAC3C,MAAM,MAAM,GAAG,IAAA,4BAAa,EAAC,aAAa,EAAE,+BAA+B,CAAC,CAAC;YAC7E,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAClB,OAAO,CAAC,KAAK,CAAC,mBAAmB,GAAG,MAAM,GAAG,qDAAqD,CAAC,CAAC;QACxG,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC9B,CAAC;AACL,CAAC","sourcesContent":["/**\n * Validate Architecture Unchanged Executor\n *\n * Validates that the current architecture graph matches the saved blessed graph.\n * This ensures no unapproved architecture changes have been made.\n *\n * Usage:\n * nx run architecture:validate-architecture-unchanged\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { writeTemplate } from '@webpieces/rules-config';\nimport { generateReducedGraph } from '../../lib/graph-generator';\nimport { sortGraphTopologically } from '../../lib/graph-sorter';\nimport { compareGraphs } from '../../lib/graph-comparator';\nimport { loadBlessedGraph, graphFileExists } from '../../lib/graph-loader';\nimport { collectProjectInfo, enrichGraph, MetadataValidationError } from '../../lib/graph-metadata';\nimport { toError } from '../../toError';\n\nexport interface ValidateArchitectureUnchangedOptions {\n graphPath?: string;\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\nconst TMP_MD_FILE = 'webpieces.dependencies.md';\n\n/**\n * Write the instructions documentation to .webpieces/instruct-ai/.\n * Sourced from @webpieces/rules-config.\n */\nfunction writeTmpInstructionsFile(workspaceRoot: string): string {\n const mdPath = writeTemplate(workspaceRoot, TMP_MD_FILE);\n\n return mdPath;\n}\n\n/**\n * Report a current-vs-saved graph mismatch and write the AI instructions file.\n */\nfunction reportMismatch(summary: string, workspaceRoot: string): void {\n const mdPath = writeTmpInstructionsFile(workspaceRoot);\n\n console.error('āŒ Architecture has changed since last update!');\n console.error('\\nDifferences:');\n console.error(summary);\n console.error('');\n console.error('āš ļø *** Refer to ' + mdPath + ' for instructions on how to fix *** āš ļø');\n console.error('');\n console.error('To fix:');\n console.error(' 1. Review the changes above');\n console.error(' 2. If intentional, ASK USER to run: nx run architecture:generate since this is a critical change');\n console.error(' 3. Commit the updated architecture/dependencies.json');\n}\n\nexport default async function runExecutor(\n options: ValidateArchitectureUnchangedOptions,\n context: ExecutorContext\n): Promise<ExecutorResult> {\n const graphPath = options.graphPath;\n const workspaceRoot = context.root;\n\n console.log('\\nšŸ” Validating Architecture Unchanged\\n');\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // Check if saved graph exists\n if (!graphFileExists(workspaceRoot, graphPath)) {\n console.error('āŒ No saved graph found at architecture/dependencies.json');\n console.error('');\n console.error('To initialize:');\n console.error(' 1. Run: nx run architecture:generate');\n console.error(' 2. Run: nx run architecture:visualize');\n console.error(' 3. Manually inspect the generated graph to confirm it is the desired architecture');\n console.error(' 4. Commit architecture/dependencies.json');\n return { success: false };\n }\n\n // Step 1: Build the full graph from nx, then transitively reduce it\n console.log('šŸ“Š Generating current dependency graph...');\n const reducedGraph = await generateReducedGraph();\n\n // Step 2: Topological sort (to get enhanced graph with levels)\n console.log('šŸ”„ Computing topological layers...');\n const currentGraph = sortGraphTopologically(reducedGraph);\n\n // Step 3: Enrich with AI metadata so edits to responsibilities.md,\n // framework tags, or project moves are caught as graph changes\n console.log('šŸ·ļø Enriching graph with framework + responsibilities metadata...');\n const projectInfos = await collectProjectInfo();\n enrichGraph(currentGraph, projectInfos, workspaceRoot);\n\n // Step 4: Load saved graph\n console.log('šŸ“‚ Loading saved graph...');\n const savedGraph = loadBlessedGraph(workspaceRoot, graphPath);\n\n if (!savedGraph) {\n console.error('āŒ Could not load saved graph');\n return { success: false };\n }\n\n // Step 5: Compare graphs\n console.log('šŸ” Comparing current graph to saved graph...');\n const comparison = compareGraphs(currentGraph, savedGraph.projects);\n\n if (comparison.identical) {\n console.log('āœ… Architecture unchanged - current graph matches saved graph');\n return { success: true };\n }\n reportMismatch(comparison.summary, workspaceRoot);\n return { success: false };\n } catch (err: unknown) {\n const error = toError(err);\n console.error('āŒ Architecture validation failed:', error.message);\n if (error instanceof MetadataValidationError) {\n const mdPath = writeTemplate(workspaceRoot, 'webpieces.responsibilities.md');\n console.error('');\n console.error('āš ļø *** Refer to ' + mdPath + ' for how to author responsibilities.md files *** āš ļø');\n }\n return { success: false };\n }\n}\n"]}
@@ -21,12 +21,13 @@ async function runExecutor(options, context) {
21
21
  try {
22
22
  // Load the saved graph
23
23
  console.log('šŸ“‚ Loading saved graph...');
24
- const graph = (0, graph_loader_1.loadBlessedGraph)(workspaceRoot, graphPath);
25
- if (!graph) {
24
+ const graphFile = (0, graph_loader_1.loadBlessedGraph)(workspaceRoot, graphPath);
25
+ if (!graphFile) {
26
26
  console.error('āŒ No saved graph found at architecture/dependencies.json');
27
27
  console.error(' Run: nx run architecture:generate first');
28
28
  return { success: false };
29
29
  }
30
+ const graph = graphFile.projects;
30
31
  // Generate visualization
31
32
  console.log('šŸŽØ Generating visualization...');
32
33
  const vizPaths = (0, graph_visualizer_1.writeVisualization)(graph, workspaceRoot);
@@ -1 +1 @@
1
- {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/visualize/executor.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;AAeH,8BAyCC;AArDD,yDAA0D;AAC1D,iEAAmF;AACnF,2CAAwC;AAUzB,KAAK,UAAU,WAAW,CACrC,OAAiC,EACjC,OAAwB;IAExB,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;IACpC,MAAM,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAEnC,OAAO,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC;IAEjD,8DAA8D;IAC9D,IAAI,CAAC;QACD,uBAAuB;QACvB,OAAO,CAAC,GAAG,CAAC,2BAA2B,CAAC,CAAC;QACzC,MAAM,KAAK,GAAG,IAAA,+BAAgB,EAAC,aAAa,EAAE,SAAS,CAAC,CAAC;QAEzD,IAAI,CAAC,KAAK,EAAE,CAAC;YACT,OAAO,CAAC,KAAK,CAAC,0DAA0D,CAAC,CAAC;YAC1E,OAAO,CAAC,KAAK,CAAC,4CAA4C,CAAC,CAAC;YAC5D,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QAC9B,CAAC;QAED,yBAAyB;QACzB,OAAO,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC;QAC9C,MAAM,QAAQ,GAAG,IAAA,qCAAkB,EAAC,KAAK,EAAE,aAAa,CAAC,CAAC;QAC1D,OAAO,CAAC,GAAG,CAAC,gBAAgB,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;QAChD,OAAO,CAAC,GAAG,CAAC,gBAAgB,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;QAEjD,yBAAyB;QACzB,OAAO,CAAC,GAAG,CAAC,0CAA0C,CAAC,CAAC;QACxD,IAAI,IAAA,oCAAiB,EAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;YACvC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;QACpC,CAAC;aAAM,CAAC;YACJ,OAAO,CAAC,GAAG,CAAC,2CAA2C,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;QAChF,CAAC;QAED,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC7B,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAA,iBAAO,EAAC,GAAG,CAAC,CAAC;QAC3B,OAAO,CAAC,KAAK,CAAC,yBAAyB,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;QACxD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC9B,CAAC;AACL,CAAC","sourcesContent":["/**\n * Visualize Executor\n *\n * Generates visual representations of the architecture graph (DOT + HTML)\n * and opens the visualization in a browser.\n *\n * Usage:\n * nx run architecture:visualize\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { loadBlessedGraph } from '../../lib/graph-loader';\nimport { writeVisualization, openVisualization } from '../../lib/graph-visualizer';\nimport { toError } from '../../toError';\n\nexport interface VisualizeExecutorOptions {\n graphPath?: string;\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\nexport default async function runExecutor(\n options: VisualizeExecutorOptions,\n context: ExecutorContext\n): Promise<ExecutorResult> {\n const graphPath = options.graphPath;\n const workspaceRoot = context.root;\n\n console.log('\\nšŸŽØ Architecture Visualization\\n');\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // Load the saved graph\n console.log('šŸ“‚ Loading saved graph...');\n const graph = loadBlessedGraph(workspaceRoot, graphPath);\n\n if (!graph) {\n console.error('āŒ No saved graph found at architecture/dependencies.json');\n console.error(' Run: nx run architecture:generate first');\n return { success: false };\n }\n\n // Generate visualization\n console.log('šŸŽØ Generating visualization...');\n const vizPaths = writeVisualization(graph, workspaceRoot);\n console.log(`āœ… Generated: ${vizPaths.dotPath}`);\n console.log(`āœ… Generated: ${vizPaths.htmlPath}`);\n\n // Try to open in browser\n console.log('\\n🌐 Opening visualization in browser...');\n if (openVisualization(vizPaths.htmlPath)) {\n console.log('āœ… Browser opened');\n } else {\n console.log(`āš ļø Could not auto-open. Open manually: ${vizPaths.htmlPath}`);\n }\n\n return { success: true };\n } catch (err: unknown) {\n const error = toError(err);\n console.error('āŒ Visualization failed:', error.message);\n return { success: false };\n }\n}\n"]}
1
+ {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/visualize/executor.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;AAeH,8BA0CC;AAtDD,yDAA0D;AAC1D,iEAAmF;AACnF,2CAAwC;AAUzB,KAAK,UAAU,WAAW,CACrC,OAAiC,EACjC,OAAwB;IAExB,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;IACpC,MAAM,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAEnC,OAAO,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC;IAEjD,8DAA8D;IAC9D,IAAI,CAAC;QACD,uBAAuB;QACvB,OAAO,CAAC,GAAG,CAAC,2BAA2B,CAAC,CAAC;QACzC,MAAM,SAAS,GAAG,IAAA,+BAAgB,EAAC,aAAa,EAAE,SAAS,CAAC,CAAC;QAE7D,IAAI,CAAC,SAAS,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,0DAA0D,CAAC,CAAC;YAC1E,OAAO,CAAC,KAAK,CAAC,4CAA4C,CAAC,CAAC;YAC5D,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QAC9B,CAAC;QACD,MAAM,KAAK,GAAG,SAAS,CAAC,QAAQ,CAAC;QAEjC,yBAAyB;QACzB,OAAO,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC;QAC9C,MAAM,QAAQ,GAAG,IAAA,qCAAkB,EAAC,KAAK,EAAE,aAAa,CAAC,CAAC;QAC1D,OAAO,CAAC,GAAG,CAAC,gBAAgB,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;QAChD,OAAO,CAAC,GAAG,CAAC,gBAAgB,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;QAEjD,yBAAyB;QACzB,OAAO,CAAC,GAAG,CAAC,0CAA0C,CAAC,CAAC;QACxD,IAAI,IAAA,oCAAiB,EAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;YACvC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;QACpC,CAAC;aAAM,CAAC;YACJ,OAAO,CAAC,GAAG,CAAC,2CAA2C,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;QAChF,CAAC;QAED,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC7B,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAA,iBAAO,EAAC,GAAG,CAAC,CAAC;QAC3B,OAAO,CAAC,KAAK,CAAC,yBAAyB,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;QACxD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC9B,CAAC;AACL,CAAC","sourcesContent":["/**\n * Visualize Executor\n *\n * Generates visual representations of the architecture graph (DOT + HTML)\n * and opens the visualization in a browser.\n *\n * Usage:\n * nx run architecture:visualize\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { loadBlessedGraph } from '../../lib/graph-loader';\nimport { writeVisualization, openVisualization } from '../../lib/graph-visualizer';\nimport { toError } from '../../toError';\n\nexport interface VisualizeExecutorOptions {\n graphPath?: string;\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\nexport default async function runExecutor(\n options: VisualizeExecutorOptions,\n context: ExecutorContext\n): Promise<ExecutorResult> {\n const graphPath = options.graphPath;\n const workspaceRoot = context.root;\n\n console.log('\\nšŸŽØ Architecture Visualization\\n');\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // Load the saved graph\n console.log('šŸ“‚ Loading saved graph...');\n const graphFile = loadBlessedGraph(workspaceRoot, graphPath);\n\n if (!graphFile) {\n console.error('āŒ No saved graph found at architecture/dependencies.json');\n console.error(' Run: nx run architecture:generate first');\n return { success: false };\n }\n const graph = graphFile.projects;\n\n // Generate visualization\n console.log('šŸŽØ Generating visualization...');\n const vizPaths = writeVisualization(graph, workspaceRoot);\n console.log(`āœ… Generated: ${vizPaths.dotPath}`);\n console.log(`āœ… Generated: ${vizPaths.htmlPath}`);\n\n // Try to open in browser\n console.log('\\n🌐 Opening visualization in browser...');\n if (openVisualization(vizPaths.htmlPath)) {\n console.log('āœ… Browser opened');\n } else {\n console.log(`āš ļø Could not auto-open. Open manually: ${vizPaths.htmlPath}`);\n }\n\n return { success: true };\n } catch (err: unknown) {\n const error = toError(err);\n console.error('āŒ Visualization failed:', error.message);\n return { success: false };\n }\n}\n"]}
package/src/index.d.ts CHANGED
@@ -4,6 +4,10 @@ export * from './lib/graph-comparator';
4
4
  export * from './lib/package-validator';
5
5
  export * from './lib/graph-loader';
6
6
  export * from './lib/graph-visualizer';
7
+ export * from './lib/graph-metadata';
8
+ export * from './lib/framework-resolver';
9
+ export * from './lib/project-info';
10
+ export * from './lib/responsibilities';
7
11
  import { createNodesV2 } from './plugin';
8
12
  export { createNodesV2 };
9
13
  declare const _default: {
package/src/index.js CHANGED
@@ -8,6 +8,10 @@ tslib_1.__exportStar(require("./lib/graph-comparator"), exports);
8
8
  tslib_1.__exportStar(require("./lib/package-validator"), exports);
9
9
  tslib_1.__exportStar(require("./lib/graph-loader"), exports);
10
10
  tslib_1.__exportStar(require("./lib/graph-visualizer"), exports);
11
+ tslib_1.__exportStar(require("./lib/graph-metadata"), exports);
12
+ tslib_1.__exportStar(require("./lib/framework-resolver"), exports);
13
+ tslib_1.__exportStar(require("./lib/project-info"), exports);
14
+ tslib_1.__exportStar(require("./lib/responsibilities"), exports);
11
15
  const plugin_1 = require("./plugin");
12
16
  Object.defineProperty(exports, "createNodesV2", { enumerable: true, get: function () { return plugin_1.createNodesV2; } });
13
17
  exports.default = { name: '@webpieces/nx-webpieces-rules', createNodesV2: plugin_1.createNodesV2 };
package/src/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/tooling/nx-webpieces-rules/src/index.ts"],"names":[],"mappings":";;;;AAAA,gEAAsC;AACtC,6DAAmC;AACnC,iEAAuC;AACvC,kEAAwC;AACxC,6DAAmC;AACnC,iEAAuC;AACvC,qCAAyC;AAChC,8FADA,sBAAa,OACA;AACtB,kBAAe,EAAE,IAAI,EAAE,+BAA+B,EAAE,aAAa,EAAb,sBAAa,EAAE,CAAC","sourcesContent":["export * from './lib/graph-generator';\nexport * from './lib/graph-sorter';\nexport * from './lib/graph-comparator';\nexport * from './lib/package-validator';\nexport * from './lib/graph-loader';\nexport * from './lib/graph-visualizer';\nimport { createNodesV2 } from './plugin';\nexport { createNodesV2 };\nexport default { name: '@webpieces/nx-webpieces-rules', createNodesV2 };\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/tooling/nx-webpieces-rules/src/index.ts"],"names":[],"mappings":";;;;AAAA,gEAAsC;AACtC,6DAAmC;AACnC,iEAAuC;AACvC,kEAAwC;AACxC,6DAAmC;AACnC,iEAAuC;AACvC,+DAAqC;AACrC,mEAAyC;AACzC,6DAAmC;AACnC,iEAAuC;AACvC,qCAAyC;AAChC,8FADA,sBAAa,OACA;AACtB,kBAAe,EAAE,IAAI,EAAE,+BAA+B,EAAE,aAAa,EAAb,sBAAa,EAAE,CAAC","sourcesContent":["export * from './lib/graph-generator';\nexport * from './lib/graph-sorter';\nexport * from './lib/graph-comparator';\nexport * from './lib/package-validator';\nexport * from './lib/graph-loader';\nexport * from './lib/graph-visualizer';\nexport * from './lib/graph-metadata';\nexport * from './lib/framework-resolver';\nexport * from './lib/project-info';\nexport * from './lib/responsibilities';\nimport { createNodesV2 } from './plugin';\nexport { createNodesV2 };\nexport default { name: '@webpieces/nx-webpieces-rules', createNodesV2 };\n"]}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Framework Resolver
3
+ *
4
+ * Determines the `framework` field written per project into
5
+ * architecture/dependencies.json (angular, react, express, all-ts, ...).
6
+ *
7
+ * Resolution order:
8
+ * 1. Explicit nx tag `framework:<value>` on the project (project.json tags) —
9
+ * any value is allowed so new frameworks need no code change here.
10
+ * 2. Inference from the project's package.json dependencies.
11
+ * 3. Fallback: 'all-ts' (plain TypeScript library).
12
+ */
13
+ import { ProjectInfo } from './project-info';
14
+ export declare const FRAMEWORK_TAG_PREFIX = "framework:";
15
+ export declare class FrameworkResolution {
16
+ /** Resolved framework name, or null when resolution failed */
17
+ readonly framework: string | null;
18
+ /** Problem description when resolution failed, otherwise null */
19
+ readonly problem: string | null;
20
+ constructor(
21
+ /** Resolved framework name, or null when resolution failed */
22
+ framework: string | null,
23
+ /** Problem description when resolution failed, otherwise null */
24
+ problem: string | null);
25
+ }
26
+ export declare function resolveFramework(info: ProjectInfo, workspaceRoot: string): FrameworkResolution;
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ /**
3
+ * Framework Resolver
4
+ *
5
+ * Determines the `framework` field written per project into
6
+ * architecture/dependencies.json (angular, react, express, all-ts, ...).
7
+ *
8
+ * Resolution order:
9
+ * 1. Explicit nx tag `framework:<value>` on the project (project.json tags) —
10
+ * any value is allowed so new frameworks need no code change here.
11
+ * 2. Inference from the project's package.json dependencies.
12
+ * 3. Fallback: 'all-ts' (plain TypeScript library).
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.FrameworkResolution = exports.FRAMEWORK_TAG_PREFIX = void 0;
16
+ exports.resolveFramework = resolveFramework;
17
+ const tslib_1 = require("tslib");
18
+ const fs = tslib_1.__importStar(require("fs"));
19
+ const path = tslib_1.__importStar(require("path"));
20
+ const toError_1 = require("../toError");
21
+ exports.FRAMEWORK_TAG_PREFIX = 'framework:';
22
+ /**
23
+ * A package.json dependency name that identifies a framework.
24
+ */
25
+ class FrameworkMarker {
26
+ dependency;
27
+ framework;
28
+ constructor(dependency, framework) {
29
+ this.dependency = dependency;
30
+ this.framework = framework;
31
+ }
32
+ }
33
+ /**
34
+ * Dependency name → framework, checked in order (first match wins).
35
+ */
36
+ const FRAMEWORK_DEPENDENCY_MARKERS = [
37
+ new FrameworkMarker('@angular/core', 'angular'),
38
+ new FrameworkMarker('react', 'react'),
39
+ new FrameworkMarker('express', 'express'),
40
+ ];
41
+ class FrameworkResolution {
42
+ framework;
43
+ problem;
44
+ constructor(
45
+ /** Resolved framework name, or null when resolution failed */
46
+ framework,
47
+ /** Problem description when resolution failed, otherwise null */
48
+ problem) {
49
+ this.framework = framework;
50
+ this.problem = problem;
51
+ }
52
+ }
53
+ exports.FrameworkResolution = FrameworkResolution;
54
+ function resolveFramework(info, workspaceRoot) {
55
+ const tagValues = info.tags
56
+ .filter((tag) => tag.startsWith(exports.FRAMEWORK_TAG_PREFIX))
57
+ .map((tag) => tag.slice(exports.FRAMEWORK_TAG_PREFIX.length).trim());
58
+ if (tagValues.length > 1) {
59
+ return new FrameworkResolution(null, `${info.name}: has ${tagValues.length} 'framework:' tags (${tagValues.join(', ')}) — a project must have at most one`);
60
+ }
61
+ if (tagValues.length === 1) {
62
+ if (tagValues[0].length === 0) {
63
+ return new FrameworkResolution(null, `${info.name}: 'framework:' tag has an empty value`);
64
+ }
65
+ return new FrameworkResolution(tagValues[0], null);
66
+ }
67
+ return new FrameworkResolution(inferFromPackageJson(info, workspaceRoot), null);
68
+ }
69
+ function inferFromPackageJson(info, workspaceRoot) {
70
+ const pkgJsonPath = path.join(workspaceRoot, info.root, 'package.json');
71
+ if (!fs.existsSync(pkgJsonPath)) {
72
+ return 'all-ts';
73
+ }
74
+ let allDeps;
75
+ // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
76
+ try {
77
+ const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
78
+ allDeps = {
79
+ ...(pkgJson.dependencies ?? {}),
80
+ ...(pkgJson.devDependencies ?? {}),
81
+ ...(pkgJson.peerDependencies ?? {}),
82
+ };
83
+ }
84
+ catch (err) {
85
+ const error = (0, toError_1.toError)(err);
86
+ throw new Error(`Failed to parse ${pkgJsonPath} while inferring framework for ${info.name}`, {
87
+ cause: error,
88
+ });
89
+ }
90
+ for (const marker of FRAMEWORK_DEPENDENCY_MARKERS) {
91
+ if (marker.dependency in allDeps) {
92
+ return marker.framework;
93
+ }
94
+ }
95
+ return 'all-ts';
96
+ }
97
+ //# sourceMappingURL=framework-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"framework-resolver.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/framework-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Framework Resolver\n *\n * Determines the `framework` field written per project into\n * architecture/dependencies.json (angular, react, express, all-ts, ...).\n *\n * Resolution order:\n * 1. Explicit nx tag `framework:<value>` on the project (project.json tags) —\n * any value is allowed so new frameworks need no code change here.\n * 2. Inference from the project's package.json dependencies.\n * 3. Fallback: 'all-ts' (plain TypeScript library).\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { ProjectInfo } from './project-info';\nimport { toError } from '../toError';\n\nexport const FRAMEWORK_TAG_PREFIX = 'framework:';\n\n/**\n * A package.json dependency name that identifies a framework.\n */\nclass FrameworkMarker {\n constructor(\n public readonly dependency: string,\n public readonly framework: string\n ) {}\n}\n\n/**\n * Dependency name → framework, checked in order (first match wins).\n */\nconst FRAMEWORK_DEPENDENCY_MARKERS: ReadonlyArray<FrameworkMarker> = [\n new FrameworkMarker('@angular/core', 'angular'),\n new FrameworkMarker('react', 'react'),\n new FrameworkMarker('express', 'express'),\n];\n\nexport class FrameworkResolution {\n constructor(\n /** Resolved framework name, or null when resolution failed */\n public readonly framework: string | null,\n /** Problem description when resolution failed, otherwise null */\n public readonly problem: string | null\n ) {}\n}\n\nexport function resolveFramework(info: ProjectInfo, workspaceRoot: string): FrameworkResolution {\n const tagValues = info.tags\n .filter((tag: string) => tag.startsWith(FRAMEWORK_TAG_PREFIX))\n .map((tag: string) => tag.slice(FRAMEWORK_TAG_PREFIX.length).trim());\n\n if (tagValues.length > 1) {\n return new FrameworkResolution(\n null,\n `${info.name}: has ${tagValues.length} 'framework:' tags (${tagValues.join(', ')}) — a project must have at most one`\n );\n }\n if (tagValues.length === 1) {\n if (tagValues[0].length === 0) {\n return new FrameworkResolution(null, `${info.name}: 'framework:' tag has an empty value`);\n }\n return new FrameworkResolution(tagValues[0], null);\n }\n\n return new FrameworkResolution(inferFromPackageJson(info, workspaceRoot), null);\n}\n\nfunction inferFromPackageJson(info: ProjectInfo, workspaceRoot: string): string {\n const pkgJsonPath = path.join(workspaceRoot, info.root, 'package.json');\n if (!fs.existsSync(pkgJsonPath)) {\n return 'all-ts';\n }\n\n let allDeps: Record<string, string>;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));\n allDeps = {\n ...(pkgJson.dependencies ?? {}),\n ...(pkgJson.devDependencies ?? {}),\n ...(pkgJson.peerDependencies ?? {}),\n };\n } catch (err: unknown) {\n const error = toError(err);\n throw new Error(`Failed to parse ${pkgJsonPath} while inferring framework for ${info.name}`, {\n cause: error,\n });\n }\n\n for (const marker of FRAMEWORK_DEPENDENCY_MARKERS) {\n if (marker.dependency in allDeps) {\n return marker.framework;\n }\n }\n return 'all-ts';\n}\n"]}
@@ -5,6 +5,14 @@
5
5
  * Used in validate mode to ensure developers have updated the graph file.
6
6
  */
7
7
  import type { EnhancedGraph } from './graph-sorter';
8
+ /**
9
+ * A changed metadata field on a project (framework, shortDescription, ...)
10
+ */
11
+ export interface FieldChange {
12
+ field: string;
13
+ from: string | undefined;
14
+ to: string | undefined;
15
+ }
8
16
  /**
9
17
  * Difference between two graphs
10
18
  */
@@ -19,6 +27,7 @@ export interface GraphDiff {
19
27
  from: number;
20
28
  to: number;
21
29
  } | null;
30
+ changedFields: FieldChange[];
22
31
  }[];
23
32
  }
24
33
  /**
@@ -7,6 +7,15 @@
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.compareGraphs = compareGraphs;
10
+ /**
11
+ * Metadata fields compared per project (beyond level + dependsOn)
12
+ */
13
+ const METADATA_FIELDS = [
14
+ 'framework',
15
+ 'shortDescription',
16
+ 'responsibilitiesFile',
17
+ 'designFile',
18
+ ];
10
19
  /**
11
20
  * Compare two graphs and return the differences
12
21
  *
@@ -67,16 +76,37 @@ function findModifiedProjects(current, saved, currentProjects, savedProjects, di
67
76
  const levelChanged = currentEntry.level !== savedEntry.level
68
77
  ? { from: savedEntry.level, to: currentEntry.level }
69
78
  : null;
70
- if (addedDeps.length > 0 || removedDeps.length > 0 || levelChanged) {
79
+ const changedFields = findChangedFields(currentEntry, savedEntry);
80
+ if (addedDeps.length > 0 || removedDeps.length > 0 || levelChanged || changedFields.length > 0) {
71
81
  diff.modified.push({
72
82
  project,
73
83
  addedDeps,
74
84
  removedDeps,
75
85
  levelChanged,
86
+ changedFields,
76
87
  });
77
88
  }
78
89
  }
79
90
  }
91
+ function findChangedFields(currentEntry, savedEntry) {
92
+ const changes = [];
93
+ for (const field of METADATA_FIELDS) {
94
+ const from = savedEntry[field];
95
+ const to = currentEntry[field];
96
+ if (from !== to) {
97
+ changes.push({ field, from, to });
98
+ }
99
+ }
100
+ return changes;
101
+ }
102
+ function formatFieldValue(value) {
103
+ if (value === undefined)
104
+ return '(none)';
105
+ const MAX_SUMMARY_VALUE_CHARS = 60;
106
+ return value.length > MAX_SUMMARY_VALUE_CHARS
107
+ ? `"${value.slice(0, MAX_SUMMARY_VALUE_CHARS)}..."`
108
+ : `"${value}"`;
109
+ }
80
110
  function buildSummary(diff) {
81
111
  const summaryParts = [];
82
112
  if (diff.added.length > 0) {
@@ -96,6 +126,9 @@ function buildSummary(diff) {
96
126
  if (mod.levelChanged) {
97
127
  parts.push(`level: ${mod.levelChanged.from} -> ${mod.levelChanged.to}`);
98
128
  }
129
+ for (const change of mod.changedFields) {
130
+ parts.push(`${change.field}: ${formatFieldValue(change.from)} -> ${formatFieldValue(change.to)}`);
131
+ }
99
132
  if (parts.length > 0) {
100
133
  summaryParts.push(`${mod.project}: ${parts.join('; ')}`);
101
134
  }
@@ -1 +1 @@
1
- {"version":3,"file":"graph-comparator.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-comparator.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Graph Comparator\n *\n * Compares the current generated graph with the saved (blessed) graph.\n * Used in validate mode to ensure developers have updated the graph file.\n */\n\nimport type { EnhancedGraph } from './graph-sorter';\n\n/**\n * Difference between two graphs\n */\nexport interface GraphDiff {\n added: string[];\n removed: string[];\n modified: {\n project: string;\n addedDeps: string[];\n removedDeps: string[];\n levelChanged: { from: number; to: number } | null;\n }[];\n}\n\n/**\n * Comparison result\n */\nexport interface ComparisonResult {\n identical: boolean;\n diff: GraphDiff;\n summary: string;\n}\n\n/**\n * Compare two graphs and return the differences\n *\n * @param current - Currently generated graph\n * @param saved - Previously saved (blessed) graph\n * @returns Comparison result with detailed diff\n */\nexport function compareGraphs(current: EnhancedGraph, saved: EnhancedGraph): ComparisonResult {\n const currentProjects = new Set(Object.keys(current));\n const savedProjects = new Set(Object.keys(saved));\n\n const diff: GraphDiff = {\n added: [],\n removed: [],\n modified: [],\n };\n\n // Find added projects\n for (const project of currentProjects) {\n if (!savedProjects.has(project)) {\n diff.added.push(project);\n }\n }\n\n // Find removed projects\n for (const project of savedProjects) {\n if (!currentProjects.has(project)) {\n diff.removed.push(project);\n }\n }\n\n // Find modified projects\n findModifiedProjects(current, saved, currentProjects, savedProjects, diff);\n\n const identical =\n diff.added.length === 0 && diff.removed.length === 0 && diff.modified.length === 0;\n\n const summary = identical ? 'Graphs are identical' : buildSummary(diff);\n\n return {\n identical,\n diff,\n summary,\n };\n}\n\nfunction findModifiedProjects(\n current: EnhancedGraph,\n saved: EnhancedGraph,\n currentProjects: Set<string>,\n savedProjects: Set<string>,\n diff: GraphDiff\n): void {\n for (const project of currentProjects) {\n if (!savedProjects.has(project)) continue;\n\n const currentEntry = current[project];\n const savedEntry = saved[project];\n\n const currentDeps = new Set(currentEntry.dependsOn);\n const savedDeps = new Set(savedEntry.dependsOn);\n\n const addedDeps: string[] = [];\n const removedDeps: string[] = [];\n\n for (const dep of currentDeps) {\n if (!savedDeps.has(dep)) {\n addedDeps.push(dep);\n }\n }\n\n for (const dep of savedDeps) {\n if (!currentDeps.has(dep)) {\n removedDeps.push(dep);\n }\n }\n\n const levelChanged =\n currentEntry.level !== savedEntry.level\n ? { from: savedEntry.level, to: currentEntry.level }\n : null;\n\n if (addedDeps.length > 0 || removedDeps.length > 0 || levelChanged) {\n diff.modified.push({\n project,\n addedDeps,\n removedDeps,\n levelChanged,\n });\n }\n }\n}\n\nfunction buildSummary(diff: GraphDiff): string {\n const summaryParts: string[] = [];\n\n if (diff.added.length > 0) {\n summaryParts.push(`Added projects: ${diff.added.join(', ')}`);\n }\n\n if (diff.removed.length > 0) {\n summaryParts.push(`Removed projects: ${diff.removed.join(', ')}`);\n }\n\n for (const mod of diff.modified) {\n const parts: string[] = [];\n if (mod.addedDeps.length > 0) {\n parts.push(`+deps: ${mod.addedDeps.join(', ')}`);\n }\n if (mod.removedDeps.length > 0) {\n parts.push(`-deps: ${mod.removedDeps.join(', ')}`);\n }\n if (mod.levelChanged) {\n parts.push(`level: ${mod.levelChanged.from} -> ${mod.levelChanged.to}`);\n }\n if (parts.length > 0) {\n summaryParts.push(`${mod.project}: ${parts.join('; ')}`);\n }\n }\n\n return summaryParts.join('\\n');\n}\n"]}
1
+ {"version":3,"file":"graph-comparator.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-comparator.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;AAsDH,sCAqCC;AA/DD;;GAEG;AACH,MAAM,eAAe,GAA6C;IAC9D,WAAW;IACX,kBAAkB;IAClB,sBAAsB;IACtB,YAAY;CACf,CAAC;AAWF;;;;;;GAMG;AACH,SAAgB,aAAa,CAAC,OAAsB,EAAE,KAAoB;IACtE,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;IACtD,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IAElD,MAAM,IAAI,GAAc;QACpB,KAAK,EAAE,EAAE;QACT,OAAO,EAAE,EAAE;QACX,QAAQ,EAAE,EAAE;KACf,CAAC;IAEF,sBAAsB;IACtB,KAAK,MAAM,OAAO,IAAI,eAAe,EAAE,CAAC;QACpC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YAC9B,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC7B,CAAC;IACL,CAAC;IAED,wBAAwB;IACxB,KAAK,MAAM,OAAO,IAAI,aAAa,EAAE,CAAC;QAClC,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YAChC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC/B,CAAC;IACL,CAAC;IAED,yBAAyB;IACzB,oBAAoB,CAAC,OAAO,EAAE,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,IAAI,CAAC,CAAC;IAE3E,MAAM,SAAS,GACX,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC;IAEvF,MAAM,OAAO,GAAG,SAAS,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IAExE,OAAO;QACH,SAAS;QACT,IAAI;QACJ,OAAO;KACV,CAAC;AACN,CAAC;AAED,SAAS,oBAAoB,CACzB,OAAsB,EACtB,KAAoB,EACpB,eAA4B,EAC5B,aAA0B,EAC1B,IAAe;IAEf,KAAK,MAAM,OAAO,IAAI,eAAe,EAAE,CAAC;QACpC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC;YAAE,SAAS;QAE1C,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;QACtC,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC;QAElC,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC;QACpD,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;QAEhD,MAAM,SAAS,GAAa,EAAE,CAAC;QAC/B,MAAM,WAAW,GAAa,EAAE,CAAC;QAEjC,KAAK,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;YAC5B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBACtB,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACxB,CAAC;QACL,CAAC;QAED,KAAK,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;YAC1B,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBACxB,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC1B,CAAC;QACL,CAAC;QAED,MAAM,YAAY,GACd,YAAY,CAAC,KAAK,KAAK,UAAU,CAAC,KAAK;YACnC,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,CAAC,KAAK,EAAE,EAAE,EAAE,YAAY,CAAC,KAAK,EAAE;YACpD,CAAC,CAAC,IAAI,CAAC;QAEf,MAAM,aAAa,GAAG,iBAAiB,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;QAElE,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,IAAI,YAAY,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC7F,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;gBACf,OAAO;gBACP,SAAS;gBACT,WAAW;gBACX,YAAY;gBACZ,aAAa;aAChB,CAAC,CAAC;QACP,CAAC;IACL,CAAC;AACL,CAAC;AAED,SAAS,iBAAiB,CAAC,YAAwB,EAAE,UAAsB;IACvE,MAAM,OAAO,GAAkB,EAAE,CAAC;IAClC,KAAK,MAAM,KAAK,IAAI,eAAe,EAAE,CAAC;QAClC,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,CAAuB,CAAC;QACrD,MAAM,EAAE,GAAG,YAAY,CAAC,KAAK,CAAuB,CAAC;QACrD,IAAI,IAAI,KAAK,EAAE,EAAE,CAAC;YACd,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;QACtC,CAAC;IACL,CAAC;IACD,OAAO,OAAO,CAAC;AACnB,CAAC;AAED,SAAS,gBAAgB,CAAC,KAAyB;IAC/C,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,QAAQ,CAAC;IACzC,MAAM,uBAAuB,GAAG,EAAE,CAAC;IACnC,OAAO,KAAK,CAAC,MAAM,GAAG,uBAAuB;QACzC,CAAC,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,uBAAuB,CAAC,MAAM;QACnD,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC;AACvB,CAAC;AAED,SAAS,YAAY,CAAC,IAAe;IACjC,MAAM,YAAY,GAAa,EAAE,CAAC;IAElC,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,YAAY,CAAC,IAAI,CAAC,mBAAmB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAClE,CAAC;IAED,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,YAAY,CAAC,IAAI,CAAC,qBAAqB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACtE,CAAC;IAED,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC9B,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,IAAI,GAAG,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC3B,KAAK,CAAC,IAAI,CAAC,UAAU,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACrD,CAAC;QACD,IAAI,GAAG,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC7B,KAAK,CAAC,IAAI,CAAC,UAAU,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACvD,CAAC;QACD,IAAI,GAAG,CAAC,YAAY,EAAE,CAAC;YACnB,KAAK,CAAC,IAAI,CAAC,UAAU,GAAG,CAAC,YAAY,CAAC,IAAI,OAAO,GAAG,CAAC,YAAY,CAAC,EAAE,EAAE,CAAC,CAAC;QAC5E,CAAC;QACD,KAAK,MAAM,MAAM,IAAI,GAAG,CAAC,aAAa,EAAE,CAAC;YACrC,KAAK,CAAC,IAAI,CACN,GAAG,MAAM,CAAC,KAAK,KAAK,gBAAgB,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,gBAAgB,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CACxF,CAAC;QACN,CAAC;QACD,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACnB,YAAY,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,OAAO,KAAK,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC7D,CAAC;IACL,CAAC;IAED,OAAO,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACnC,CAAC","sourcesContent":["/**\n * Graph Comparator\n *\n * Compares the current generated graph with the saved (blessed) graph.\n * Used in validate mode to ensure developers have updated the graph file.\n */\n\nimport type { EnhancedGraph, GraphEntry } from './graph-sorter';\n\n/**\n * A changed metadata field on a project (framework, shortDescription, ...)\n */\nexport interface FieldChange {\n field: string;\n from: string | undefined;\n to: string | undefined;\n}\n\n/**\n * Difference between two graphs\n */\nexport interface GraphDiff {\n added: string[];\n removed: string[];\n modified: {\n project: string;\n addedDeps: string[];\n removedDeps: string[];\n levelChanged: { from: number; to: number } | null;\n changedFields: FieldChange[];\n }[];\n}\n\n/**\n * Metadata fields compared per project (beyond level + dependsOn)\n */\nconst METADATA_FIELDS: ReadonlyArray<keyof GraphEntry & string> = [\n 'framework',\n 'shortDescription',\n 'responsibilitiesFile',\n 'designFile',\n];\n\n/**\n * Comparison result\n */\nexport interface ComparisonResult {\n identical: boolean;\n diff: GraphDiff;\n summary: string;\n}\n\n/**\n * Compare two graphs and return the differences\n *\n * @param current - Currently generated graph\n * @param saved - Previously saved (blessed) graph\n * @returns Comparison result with detailed diff\n */\nexport function compareGraphs(current: EnhancedGraph, saved: EnhancedGraph): ComparisonResult {\n const currentProjects = new Set(Object.keys(current));\n const savedProjects = new Set(Object.keys(saved));\n\n const diff: GraphDiff = {\n added: [],\n removed: [],\n modified: [],\n };\n\n // Find added projects\n for (const project of currentProjects) {\n if (!savedProjects.has(project)) {\n diff.added.push(project);\n }\n }\n\n // Find removed projects\n for (const project of savedProjects) {\n if (!currentProjects.has(project)) {\n diff.removed.push(project);\n }\n }\n\n // Find modified projects\n findModifiedProjects(current, saved, currentProjects, savedProjects, diff);\n\n const identical =\n diff.added.length === 0 && diff.removed.length === 0 && diff.modified.length === 0;\n\n const summary = identical ? 'Graphs are identical' : buildSummary(diff);\n\n return {\n identical,\n diff,\n summary,\n };\n}\n\nfunction findModifiedProjects(\n current: EnhancedGraph,\n saved: EnhancedGraph,\n currentProjects: Set<string>,\n savedProjects: Set<string>,\n diff: GraphDiff\n): void {\n for (const project of currentProjects) {\n if (!savedProjects.has(project)) continue;\n\n const currentEntry = current[project];\n const savedEntry = saved[project];\n\n const currentDeps = new Set(currentEntry.dependsOn);\n const savedDeps = new Set(savedEntry.dependsOn);\n\n const addedDeps: string[] = [];\n const removedDeps: string[] = [];\n\n for (const dep of currentDeps) {\n if (!savedDeps.has(dep)) {\n addedDeps.push(dep);\n }\n }\n\n for (const dep of savedDeps) {\n if (!currentDeps.has(dep)) {\n removedDeps.push(dep);\n }\n }\n\n const levelChanged =\n currentEntry.level !== savedEntry.level\n ? { from: savedEntry.level, to: currentEntry.level }\n : null;\n\n const changedFields = findChangedFields(currentEntry, savedEntry);\n\n if (addedDeps.length > 0 || removedDeps.length > 0 || levelChanged || changedFields.length > 0) {\n diff.modified.push({\n project,\n addedDeps,\n removedDeps,\n levelChanged,\n changedFields,\n });\n }\n }\n}\n\nfunction findChangedFields(currentEntry: GraphEntry, savedEntry: GraphEntry): FieldChange[] {\n const changes: FieldChange[] = [];\n for (const field of METADATA_FIELDS) {\n const from = savedEntry[field] as string | undefined;\n const to = currentEntry[field] as string | undefined;\n if (from !== to) {\n changes.push({ field, from, to });\n }\n }\n return changes;\n}\n\nfunction formatFieldValue(value: string | undefined): string {\n if (value === undefined) return '(none)';\n const MAX_SUMMARY_VALUE_CHARS = 60;\n return value.length > MAX_SUMMARY_VALUE_CHARS\n ? `\"${value.slice(0, MAX_SUMMARY_VALUE_CHARS)}...\"`\n : `\"${value}\"`;\n}\n\nfunction buildSummary(diff: GraphDiff): string {\n const summaryParts: string[] = [];\n\n if (diff.added.length > 0) {\n summaryParts.push(`Added projects: ${diff.added.join(', ')}`);\n }\n\n if (diff.removed.length > 0) {\n summaryParts.push(`Removed projects: ${diff.removed.join(', ')}`);\n }\n\n for (const mod of diff.modified) {\n const parts: string[] = [];\n if (mod.addedDeps.length > 0) {\n parts.push(`+deps: ${mod.addedDeps.join(', ')}`);\n }\n if (mod.removedDeps.length > 0) {\n parts.push(`-deps: ${mod.removedDeps.join(', ')}`);\n }\n if (mod.levelChanged) {\n parts.push(`level: ${mod.levelChanged.from} -> ${mod.levelChanged.to}`);\n }\n for (const change of mod.changedFields) {\n parts.push(\n `${change.field}: ${formatFieldValue(change.from)} -> ${formatFieldValue(change.to)}`\n );\n }\n if (parts.length > 0) {\n summaryParts.push(`${mod.project}: ${parts.join('; ')}`);\n }\n }\n\n return summaryParts.join('\\n');\n}\n"]}
@@ -3,6 +3,19 @@
3
3
  *
4
4
  * Handles loading and saving the blessed dependency graph file.
5
5
  * The graph is stored at architecture/dependencies.json in the workspace root.
6
+ *
7
+ * File format (schema aimed at AI consumers):
8
+ * {
9
+ * "aiInstructions": "...how AI should use the per-project fields...",
10
+ * "projects": {
11
+ * "<project>": { level, framework, shortDescription,
12
+ * responsibilitiesFile, designFile, dependsOn }
13
+ * }
14
+ * }
15
+ *
16
+ * The legacy format (flat { "<project>": { level, dependsOn } } map) is still
17
+ * readable so validation against a pre-upgrade file produces a clean
18
+ * "re-run architecture:generate" diff instead of a parse failure.
6
19
  */
7
20
  import type { EnhancedGraph } from './graph-sorter';
8
21
  /**
@@ -10,19 +23,33 @@ import type { EnhancedGraph } from './graph-sorter';
10
23
  */
11
24
  export declare const DEFAULT_GRAPH_PATH = "architecture/dependencies.json";
12
25
  /**
13
- * Load the blessed graph from disk
26
+ * Top-level instructions embedded in dependencies.json telling AI how to use
27
+ * the per-project metadata fields.
28
+ */
29
+ export declare const AI_INSTRUCTIONS: string;
30
+ /**
31
+ * The full contents of architecture/dependencies.json.
32
+ */
33
+ export declare class DependenciesFile {
34
+ readonly aiInstructions: string;
35
+ readonly projects: EnhancedGraph;
36
+ constructor(aiInstructions: string, projects: EnhancedGraph);
37
+ }
38
+ /**
39
+ * Load the blessed graph from disk. Understands both the current wrapper
40
+ * format and the legacy flat map (which loads with empty aiInstructions).
14
41
  *
15
42
  * @param workspaceRoot - Absolute path to workspace root
16
- * @param graphPath - Relative path to graph file (default: .graphs/dependencies.json)
17
- * @returns The blessed graph, or null if file doesn't exist
43
+ * @param graphPath - Relative path to graph file (default: architecture/dependencies.json)
44
+ * @returns The blessed graph file, or null if it doesn't exist
18
45
  */
19
- export declare function loadBlessedGraph(workspaceRoot: string, graphPath?: string): EnhancedGraph | null;
46
+ export declare function loadBlessedGraph(workspaceRoot: string, graphPath?: string): DependenciesFile | null;
20
47
  /**
21
- * Save the graph to disk
48
+ * Save the graph to disk in the wrapper format with the standard aiInstructions.
22
49
  *
23
- * @param graph - The graph to save
50
+ * @param graph - The enriched project graph to save
24
51
  * @param workspaceRoot - Absolute path to workspace root
25
- * @param graphPath - Relative path to graph file (default: .graphs/dependencies.json)
52
+ * @param graphPath - Relative path to graph file (default: architecture/dependencies.json)
26
53
  */
27
54
  export declare function saveGraph(graph: EnhancedGraph, workspaceRoot: string, graphPath?: string): void;
28
55
  /**
@@ -4,9 +4,22 @@
4
4
  *
5
5
  * Handles loading and saving the blessed dependency graph file.
6
6
  * The graph is stored at architecture/dependencies.json in the workspace root.
7
+ *
8
+ * File format (schema aimed at AI consumers):
9
+ * {
10
+ * "aiInstructions": "...how AI should use the per-project fields...",
11
+ * "projects": {
12
+ * "<project>": { level, framework, shortDescription,
13
+ * responsibilitiesFile, designFile, dependsOn }
14
+ * }
15
+ * }
16
+ *
17
+ * The legacy format (flat { "<project>": { level, dependsOn } } map) is still
18
+ * readable so validation against a pre-upgrade file produces a clean
19
+ * "re-run architecture:generate" diff instead of a parse failure.
7
20
  */
8
21
  Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.DEFAULT_GRAPH_PATH = void 0;
22
+ exports.DependenciesFile = exports.AI_INSTRUCTIONS = exports.DEFAULT_GRAPH_PATH = void 0;
10
23
  exports.loadBlessedGraph = loadBlessedGraph;
11
24
  exports.saveGraph = saveGraph;
12
25
  exports.graphFileExists = graphFileExists;
@@ -19,11 +32,32 @@ const toError_1 = require("../toError");
19
32
  */
20
33
  exports.DEFAULT_GRAPH_PATH = 'architecture/dependencies.json';
21
34
  /**
22
- * Load the blessed graph from disk
35
+ * Top-level instructions embedded in dependencies.json telling AI how to use
36
+ * the per-project metadata fields.
37
+ */
38
+ exports.AI_INSTRUCTIONS = "Each project's shortDescription is only a summary. BEFORE adding code to a project, " +
39
+ 'read its responsibilitiesFile for the full responsibilities (what belongs in that ' +
40
+ 'project and what does not), and read its designFile to understand the DI design ' +
41
+ 'before reading the code.';
42
+ /**
43
+ * The full contents of architecture/dependencies.json.
44
+ */
45
+ class DependenciesFile {
46
+ aiInstructions;
47
+ projects;
48
+ constructor(aiInstructions, projects) {
49
+ this.aiInstructions = aiInstructions;
50
+ this.projects = projects;
51
+ }
52
+ }
53
+ exports.DependenciesFile = DependenciesFile;
54
+ /**
55
+ * Load the blessed graph from disk. Understands both the current wrapper
56
+ * format and the legacy flat map (which loads with empty aiInstructions).
23
57
  *
24
58
  * @param workspaceRoot - Absolute path to workspace root
25
- * @param graphPath - Relative path to graph file (default: .graphs/dependencies.json)
26
- * @returns The blessed graph, or null if file doesn't exist
59
+ * @param graphPath - Relative path to graph file (default: architecture/dependencies.json)
60
+ * @returns The blessed graph file, or null if it doesn't exist
27
61
  */
28
62
  function loadBlessedGraph(workspaceRoot, graphPath = exports.DEFAULT_GRAPH_PATH) {
29
63
  const fullPath = path.join(workspaceRoot, graphPath);
@@ -33,7 +67,12 @@ function loadBlessedGraph(workspaceRoot, graphPath = exports.DEFAULT_GRAPH_PATH)
33
67
  // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
34
68
  try {
35
69
  const content = fs.readFileSync(fullPath, 'utf-8');
36
- return JSON.parse(content);
70
+ const parsed = JSON.parse(content);
71
+ if (parsed !== null && typeof parsed === 'object' && 'projects' in parsed) {
72
+ return new DependenciesFile(typeof parsed.aiInstructions === 'string' ? parsed.aiInstructions : '', parsed.projects);
73
+ }
74
+ // Legacy flat format: the whole object is the project map
75
+ return new DependenciesFile('', parsed);
37
76
  }
38
77
  catch (err) {
39
78
  const error = (0, toError_1.toError)(err);
@@ -41,39 +80,63 @@ function loadBlessedGraph(workspaceRoot, graphPath = exports.DEFAULT_GRAPH_PATH)
41
80
  }
42
81
  }
43
82
  /**
44
- * Format a graph as JSON with multi-line arrays for readability
83
+ * Format the dependencies file as JSON with multi-line arrays for readability
45
84
  */
46
- function formatGraphJson(graph) {
85
+ function formatGraphJson(file) {
47
86
  const lines = ['{'];
48
- const keys = Object.keys(graph).sort();
87
+ lines.push(` "aiInstructions": ${JSON.stringify(file.aiInstructions)},`);
88
+ lines.push(` "projects": {`);
89
+ const keys = Object.keys(file.projects).sort();
49
90
  keys.forEach((key, index) => {
50
- const entry = graph[key];
91
+ const entry = file.projects[key];
51
92
  const isLast = index === keys.length - 1;
52
93
  const comma = isLast ? '' : ',';
53
- lines.push(` "${key}": {`);
54
- lines.push(` "level": ${entry.level},`);
55
- if (entry.dependsOn.length === 0) {
56
- lines.push(` "dependsOn": []`);
57
- }
58
- else {
59
- lines.push(` "dependsOn": [`);
60
- entry.dependsOn.forEach((dep, depIndex) => {
61
- const depComma = depIndex === entry.dependsOn.length - 1 ? '' : ',';
62
- lines.push(` "${dep}"${depComma}`);
63
- });
64
- lines.push(` ]`);
65
- }
66
- lines.push(` }${comma}`);
94
+ lines.push(` ${JSON.stringify(key)}: {`);
95
+ lines.push(...formatEntryLines(entry));
96
+ lines.push(` }${comma}`);
67
97
  });
98
+ lines.push(' }');
68
99
  lines.push('}');
69
100
  return lines.join('\n') + '\n';
70
101
  }
71
102
  /**
72
- * Save the graph to disk
103
+ * Format one project entry's fields (12-space indent). Optional metadata
104
+ * fields are only emitted when present.
105
+ */
106
+ function formatEntryLines(entry) {
107
+ const lines = [];
108
+ lines.push(` "level": ${entry.level},`);
109
+ pushOptionalField(lines, 'framework', entry.framework);
110
+ pushOptionalField(lines, 'shortDescription', entry.shortDescription);
111
+ pushOptionalField(lines, 'responsibilitiesFile', entry.responsibilitiesFile);
112
+ pushOptionalField(lines, 'designFile', entry.designFile);
113
+ if (entry.dependsOn.length === 0) {
114
+ lines.push(` "dependsOn": []`);
115
+ }
116
+ else {
117
+ lines.push(` "dependsOn": [`);
118
+ entry.dependsOn.forEach((dep, depIndex) => {
119
+ const depComma = depIndex === entry.dependsOn.length - 1 ? '' : ',';
120
+ lines.push(` ${JSON.stringify(dep)}${depComma}`);
121
+ });
122
+ lines.push(` ]`);
123
+ }
124
+ return lines;
125
+ }
126
+ /**
127
+ * Emit one optional string field (12-space indent), skipped when undefined.
128
+ */
129
+ function pushOptionalField(lines, field, value) {
130
+ if (value !== undefined) {
131
+ lines.push(` ${JSON.stringify(field)}: ${JSON.stringify(value)},`);
132
+ }
133
+ }
134
+ /**
135
+ * Save the graph to disk in the wrapper format with the standard aiInstructions.
73
136
  *
74
- * @param graph - The graph to save
137
+ * @param graph - The enriched project graph to save
75
138
  * @param workspaceRoot - Absolute path to workspace root
76
- * @param graphPath - Relative path to graph file (default: .graphs/dependencies.json)
139
+ * @param graphPath - Relative path to graph file (default: architecture/dependencies.json)
77
140
  */
78
141
  function saveGraph(graph, workspaceRoot, graphPath = exports.DEFAULT_GRAPH_PATH) {
79
142
  const fullPath = path.join(workspaceRoot, graphPath);
@@ -88,7 +151,7 @@ function saveGraph(graph, workspaceRoot, graphPath = exports.DEFAULT_GRAPH_PATH)
88
151
  for (const key of sortedKeys) {
89
152
  sortedGraph[key] = graph[key];
90
153
  }
91
- const content = formatGraphJson(sortedGraph);
154
+ const content = formatGraphJson(new DependenciesFile(exports.AI_INSTRUCTIONS, sortedGraph));
92
155
  fs.writeFileSync(fullPath, content, 'utf-8');
93
156
  }
94
157
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"graph-loader.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-loader.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Graph Loader\n *\n * Handles loading and saving the blessed dependency graph file.\n * The graph is stored at architecture/dependencies.json in the workspace root.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport type { EnhancedGraph } from './graph-sorter';\nimport { toError } from '../toError';\n\n/**\n * Default path for the dependencies file (relative to workspace root)\n */\nexport const DEFAULT_GRAPH_PATH = 'architecture/dependencies.json';\n\n/**\n * Load the blessed graph from disk\n *\n * @param workspaceRoot - Absolute path to workspace root\n * @param graphPath - Relative path to graph file (default: .graphs/dependencies.json)\n * @returns The blessed graph, or null if file doesn't exist\n */\nexport function loadBlessedGraph(\n workspaceRoot: string,\n graphPath: string = DEFAULT_GRAPH_PATH\n): EnhancedGraph | null {\n const fullPath = path.join(workspaceRoot, graphPath);\n\n if (!fs.existsSync(fullPath)) {\n return null;\n }\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const content = fs.readFileSync(fullPath, 'utf-8');\n return JSON.parse(content) as EnhancedGraph;\n } catch (err: unknown) {\n const error = toError(err);\n throw new Error(`Failed to load graph from ${fullPath}`, { cause: error });\n }\n}\n\n/**\n * Format a graph as JSON with multi-line arrays for readability\n */\nfunction formatGraphJson(graph: EnhancedGraph): string {\n const lines: string[] = ['{'];\n const keys = Object.keys(graph).sort();\n\n keys.forEach((key, index) => {\n const entry = graph[key];\n const isLast = index === keys.length - 1;\n const comma = isLast ? '' : ',';\n\n lines.push(` \"${key}\": {`);\n lines.push(` \"level\": ${entry.level},`);\n\n if (entry.dependsOn.length === 0) {\n lines.push(` \"dependsOn\": []`);\n } else {\n lines.push(` \"dependsOn\": [`);\n entry.dependsOn.forEach((dep, depIndex) => {\n const depComma = depIndex === entry.dependsOn.length - 1 ? '' : ',';\n lines.push(` \"${dep}\"${depComma}`);\n });\n lines.push(` ]`);\n }\n\n lines.push(` }${comma}`);\n\n });\n\n lines.push('}');\n return lines.join('\\n') + '\\n';\n}\n\n/**\n * Save the graph to disk\n *\n * @param graph - The graph to save\n * @param workspaceRoot - Absolute path to workspace root\n * @param graphPath - Relative path to graph file (default: .graphs/dependencies.json)\n */\nexport function saveGraph(\n graph: EnhancedGraph,\n workspaceRoot: string,\n graphPath: string = DEFAULT_GRAPH_PATH\n): void {\n const fullPath = path.join(workspaceRoot, graphPath);\n const dir = path.dirname(fullPath);\n\n // Ensure directory exists\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n\n // Sort keys for deterministic output\n const sortedGraph: EnhancedGraph = {};\n const sortedKeys = Object.keys(graph).sort();\n for (const key of sortedKeys) {\n sortedGraph[key] = graph[key];\n }\n\n const content = formatGraphJson(sortedGraph);\n fs.writeFileSync(fullPath, content, 'utf-8');\n}\n\n/**\n * Check if the graph file exists\n */\nexport function graphFileExists(\n workspaceRoot: string,\n graphPath: string = DEFAULT_GRAPH_PATH\n): boolean {\n const fullPath = path.join(workspaceRoot, graphPath);\n return fs.existsSync(fullPath);\n}\n"]}
1
+ {"version":3,"file":"graph-loader.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-loader.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Graph Loader\n *\n * Handles loading and saving the blessed dependency graph file.\n * The graph is stored at architecture/dependencies.json in the workspace root.\n *\n * File format (schema aimed at AI consumers):\n * {\n * \"aiInstructions\": \"...how AI should use the per-project fields...\",\n * \"projects\": {\n * \"<project>\": { level, framework, shortDescription,\n * responsibilitiesFile, designFile, dependsOn }\n * }\n * }\n *\n * The legacy format (flat { \"<project>\": { level, dependsOn } } map) is still\n * readable so validation against a pre-upgrade file produces a clean\n * \"re-run architecture:generate\" diff instead of a parse failure.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport type { EnhancedGraph, GraphEntry } from './graph-sorter';\nimport { toError } from '../toError';\n\n/**\n * Default path for the dependencies file (relative to workspace root)\n */\nexport const DEFAULT_GRAPH_PATH = 'architecture/dependencies.json';\n\n/**\n * Top-level instructions embedded in dependencies.json telling AI how to use\n * the per-project metadata fields.\n */\nexport const AI_INSTRUCTIONS =\n \"Each project's shortDescription is only a summary. BEFORE adding code to a project, \" +\n 'read its responsibilitiesFile for the full responsibilities (what belongs in that ' +\n 'project and what does not), and read its designFile to understand the DI design ' +\n 'before reading the code.';\n\n/**\n * The full contents of architecture/dependencies.json.\n */\nexport class DependenciesFile {\n constructor(\n public readonly aiInstructions: string,\n public readonly projects: EnhancedGraph\n ) {}\n}\n\n/**\n * Load the blessed graph from disk. Understands both the current wrapper\n * format and the legacy flat map (which loads with empty aiInstructions).\n *\n * @param workspaceRoot - Absolute path to workspace root\n * @param graphPath - Relative path to graph file (default: architecture/dependencies.json)\n * @returns The blessed graph file, or null if it doesn't exist\n */\nexport function loadBlessedGraph(\n workspaceRoot: string,\n graphPath: string = DEFAULT_GRAPH_PATH\n): DependenciesFile | null {\n const fullPath = path.join(workspaceRoot, graphPath);\n\n if (!fs.existsSync(fullPath)) {\n return null;\n }\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const content = fs.readFileSync(fullPath, 'utf-8');\n const parsed = JSON.parse(content);\n if (parsed !== null && typeof parsed === 'object' && 'projects' in parsed) {\n return new DependenciesFile(\n typeof parsed.aiInstructions === 'string' ? parsed.aiInstructions : '',\n parsed.projects as EnhancedGraph\n );\n }\n // Legacy flat format: the whole object is the project map\n return new DependenciesFile('', parsed as EnhancedGraph);\n } catch (err: unknown) {\n const error = toError(err);\n throw new Error(`Failed to load graph from ${fullPath}`, { cause: error });\n }\n}\n\n/**\n * Format the dependencies file as JSON with multi-line arrays for readability\n */\nfunction formatGraphJson(file: DependenciesFile): string {\n const lines: string[] = ['{'];\n lines.push(` \"aiInstructions\": ${JSON.stringify(file.aiInstructions)},`);\n lines.push(` \"projects\": {`);\n\n const keys = Object.keys(file.projects).sort();\n keys.forEach((key: string, index: number) => {\n const entry = file.projects[key];\n const isLast = index === keys.length - 1;\n const comma = isLast ? '' : ',';\n\n lines.push(` ${JSON.stringify(key)}: {`);\n lines.push(...formatEntryLines(entry));\n lines.push(` }${comma}`);\n });\n\n lines.push(' }');\n lines.push('}');\n return lines.join('\\n') + '\\n';\n}\n\n/**\n * Format one project entry's fields (12-space indent). Optional metadata\n * fields are only emitted when present.\n */\nfunction formatEntryLines(entry: GraphEntry): string[] {\n const lines: string[] = [];\n lines.push(` \"level\": ${entry.level},`);\n\n pushOptionalField(lines, 'framework', entry.framework);\n pushOptionalField(lines, 'shortDescription', entry.shortDescription);\n pushOptionalField(lines, 'responsibilitiesFile', entry.responsibilitiesFile);\n pushOptionalField(lines, 'designFile', entry.designFile);\n\n if (entry.dependsOn.length === 0) {\n lines.push(` \"dependsOn\": []`);\n } else {\n lines.push(` \"dependsOn\": [`);\n entry.dependsOn.forEach((dep: string, depIndex: number) => {\n const depComma = depIndex === entry.dependsOn.length - 1 ? '' : ',';\n lines.push(` ${JSON.stringify(dep)}${depComma}`);\n });\n lines.push(` ]`);\n }\n return lines;\n}\n\n/**\n * Emit one optional string field (12-space indent), skipped when undefined.\n */\nfunction pushOptionalField(lines: string[], field: string, value: string | undefined): void {\n if (value !== undefined) {\n lines.push(` ${JSON.stringify(field)}: ${JSON.stringify(value)},`);\n }\n}\n\n/**\n * Save the graph to disk in the wrapper format with the standard aiInstructions.\n *\n * @param graph - The enriched project graph to save\n * @param workspaceRoot - Absolute path to workspace root\n * @param graphPath - Relative path to graph file (default: architecture/dependencies.json)\n */\nexport function saveGraph(\n graph: EnhancedGraph,\n workspaceRoot: string,\n graphPath: string = DEFAULT_GRAPH_PATH\n): void {\n const fullPath = path.join(workspaceRoot, graphPath);\n const dir = path.dirname(fullPath);\n\n // Ensure directory exists\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n\n // Sort keys for deterministic output\n const sortedGraph: EnhancedGraph = {};\n const sortedKeys = Object.keys(graph).sort();\n for (const key of sortedKeys) {\n sortedGraph[key] = graph[key];\n }\n\n const content = formatGraphJson(new DependenciesFile(AI_INSTRUCTIONS, sortedGraph));\n fs.writeFileSync(fullPath, content, 'utf-8');\n}\n\n/**\n * Check if the graph file exists\n */\nexport function graphFileExists(\n workspaceRoot: string,\n graphPath: string = DEFAULT_GRAPH_PATH\n): boolean {\n const fullPath = path.join(workspaceRoot, graphPath);\n return fs.existsSync(fullPath);\n}\n"]}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Graph Metadata Enrichment
3
+ *
4
+ * Fills the AI-oriented fields on each architecture/dependencies.json entry:
5
+ * framework — from `framework:<x>` nx tag or package.json inference
6
+ * shortDescription — first paragraph of the project's responsibilities.md
7
+ * responsibilitiesFile — repo-relative path to the required responsibilities.md
8
+ * designFile — repo-relative path to the generated DI design.json
9
+ *
10
+ * Validation is aggregated: ALL problems across ALL projects are collected and
11
+ * thrown as one MetadataValidationError so a repo adopting this sees the full
12
+ * seeding list in a single run. Callers must enrich BEFORE writing any file so
13
+ * a failed run never clobbers dependencies.json.
14
+ */
15
+ import type { EnhancedGraph } from './graph-sorter';
16
+ import { ProjectInfo } from './project-info';
17
+ export declare const RESPONSIBILITIES_FILE_NAME = "responsibilities.md";
18
+ /**
19
+ * Thrown when one or more projects fail metadata validation (missing/invalid
20
+ * responsibilities.md, bad framework tags, ...). Executors catch this to point
21
+ * AI at the webpieces.responsibilities.md instructions template.
22
+ */
23
+ export declare class MetadataValidationError extends Error {
24
+ readonly problems: string[];
25
+ constructor(problems: string[]);
26
+ }
27
+ /**
28
+ * Read per-project root + tags from nx's project graph.
29
+ */
30
+ export declare function collectProjectInfo(): Promise<Map<string, ProjectInfo>>;
31
+ /**
32
+ * Enrich every graph entry in place with framework, shortDescription,
33
+ * responsibilitiesFile and designFile. Throws MetadataValidationError listing
34
+ * every problem when any project fails validation.
35
+ */
36
+ export declare function enrichGraph(graph: EnhancedGraph, infos: Map<string, ProjectInfo>, workspaceRoot: string): void;
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+ /**
3
+ * Graph Metadata Enrichment
4
+ *
5
+ * Fills the AI-oriented fields on each architecture/dependencies.json entry:
6
+ * framework — from `framework:<x>` nx tag or package.json inference
7
+ * shortDescription — first paragraph of the project's responsibilities.md
8
+ * responsibilitiesFile — repo-relative path to the required responsibilities.md
9
+ * designFile — repo-relative path to the generated DI design.json
10
+ *
11
+ * Validation is aggregated: ALL problems across ALL projects are collected and
12
+ * thrown as one MetadataValidationError so a repo adopting this sees the full
13
+ * seeding list in a single run. Callers must enrich BEFORE writing any file so
14
+ * a failed run never clobbers dependencies.json.
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.MetadataValidationError = exports.RESPONSIBILITIES_FILE_NAME = void 0;
18
+ exports.collectProjectInfo = collectProjectInfo;
19
+ exports.enrichGraph = enrichGraph;
20
+ const tslib_1 = require("tslib");
21
+ const fs = tslib_1.__importStar(require("fs"));
22
+ const path = tslib_1.__importStar(require("path"));
23
+ const devkit_1 = require("@nx/devkit");
24
+ const project_info_1 = require("./project-info");
25
+ const framework_resolver_1 = require("./framework-resolver");
26
+ const responsibilities_1 = require("./responsibilities");
27
+ exports.RESPONSIBILITIES_FILE_NAME = 'responsibilities.md';
28
+ /**
29
+ * Thrown when one or more projects fail metadata validation (missing/invalid
30
+ * responsibilities.md, bad framework tags, ...). Executors catch this to point
31
+ * AI at the webpieces.responsibilities.md instructions template.
32
+ */
33
+ class MetadataValidationError extends Error {
34
+ problems;
35
+ constructor(problems) {
36
+ super(`Architecture metadata validation failed (${problems.length} problem(s)):\n` +
37
+ problems.map((problem) => ` - ${problem}`).join('\n'));
38
+ this.problems = problems;
39
+ this.name = 'MetadataValidationError';
40
+ }
41
+ }
42
+ exports.MetadataValidationError = MetadataValidationError;
43
+ /**
44
+ * Read per-project root + tags from nx's project graph.
45
+ */
46
+ async function collectProjectInfo() {
47
+ const projectGraph = await (0, devkit_1.createProjectGraphAsync)();
48
+ const infos = new Map();
49
+ for (const [name, node] of Object.entries(projectGraph.nodes)) {
50
+ infos.set(name, new project_info_1.ProjectInfo(name, node.data.root, node.data.tags ?? []));
51
+ }
52
+ return infos;
53
+ }
54
+ /**
55
+ * Enrich every graph entry in place with framework, shortDescription,
56
+ * responsibilitiesFile and designFile. Throws MetadataValidationError listing
57
+ * every problem when any project fails validation.
58
+ */
59
+ function enrichGraph(graph, infos, workspaceRoot) {
60
+ const problems = [];
61
+ for (const [projectName, entry] of Object.entries(graph)) {
62
+ const info = infos.get(projectName);
63
+ if (!info) {
64
+ problems.push(`${projectName}: not found in nx project graph`);
65
+ continue;
66
+ }
67
+ const resolution = (0, framework_resolver_1.resolveFramework)(info, workspaceRoot);
68
+ if (resolution.problem !== null) {
69
+ problems.push(resolution.problem);
70
+ }
71
+ else if (resolution.framework !== null) {
72
+ entry.framework = resolution.framework;
73
+ }
74
+ enrichResponsibilities(entry, info, workspaceRoot, problems);
75
+ // Only project.json projects get a generated design.json (see di-graph-targets.ts)
76
+ if (fs.existsSync(path.join(workspaceRoot, info.root, 'project.json'))) {
77
+ entry.designFile = toRepoRelative(info.root, 'design.json');
78
+ }
79
+ }
80
+ if (problems.length > 0) {
81
+ throw new MetadataValidationError(problems);
82
+ }
83
+ }
84
+ function enrichResponsibilities(entry, info, workspaceRoot, problems) {
85
+ const responsibilitiesFile = toRepoRelative(info.root, exports.RESPONSIBILITIES_FILE_NAME);
86
+ entry.responsibilitiesFile = responsibilitiesFile;
87
+ const absolutePath = path.join(workspaceRoot, info.root, exports.RESPONSIBILITIES_FILE_NAME);
88
+ if (!fs.existsSync(absolutePath)) {
89
+ problems.push(`${info.name}: missing required ${responsibilitiesFile} — create it with a heading, ` +
90
+ `one short summary paragraph, then the full responsibilities of the module`);
91
+ return;
92
+ }
93
+ const summary = (0, responsibilities_1.extractShortDescription)(fs.readFileSync(absolutePath, 'utf-8'));
94
+ const summaryProblem = (0, responsibilities_1.validateShortDescription)(summary, responsibilitiesFile);
95
+ if (summaryProblem !== null) {
96
+ problems.push(`${info.name}: ${summaryProblem}`);
97
+ return;
98
+ }
99
+ entry.shortDescription = summary;
100
+ }
101
+ /**
102
+ * Repo-relative path with forward slashes (stable across platforms in the
103
+ * committed JSON).
104
+ */
105
+ function toRepoRelative(projectRoot, fileName) {
106
+ return [projectRoot.replace(/\\/g, '/').replace(/\/+$/, ''), fileName].join('/');
107
+ }
108
+ //# sourceMappingURL=graph-metadata.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph-metadata.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-metadata.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Graph Metadata Enrichment\n *\n * Fills the AI-oriented fields on each architecture/dependencies.json entry:\n * framework — from `framework:<x>` nx tag or package.json inference\n * shortDescription — first paragraph of the project's responsibilities.md\n * responsibilitiesFile — repo-relative path to the required responsibilities.md\n * designFile — repo-relative path to the generated DI design.json\n *\n * Validation is aggregated: ALL problems across ALL projects are collected and\n * thrown as one MetadataValidationError so a repo adopting this sees the full\n * seeding list in a single run. Callers must enrich BEFORE writing any file so\n * a failed run never clobbers dependencies.json.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { createProjectGraphAsync } from '@nx/devkit';\nimport type { EnhancedGraph } from './graph-sorter';\nimport { ProjectInfo } from './project-info';\nimport { resolveFramework } from './framework-resolver';\nimport { extractShortDescription, validateShortDescription } from './responsibilities';\n\nexport const RESPONSIBILITIES_FILE_NAME = 'responsibilities.md';\n\n/**\n * Thrown when one or more projects fail metadata validation (missing/invalid\n * responsibilities.md, bad framework tags, ...). Executors catch this to point\n * AI at the webpieces.responsibilities.md instructions template.\n */\nexport class MetadataValidationError extends Error {\n constructor(public readonly problems: string[]) {\n super(\n `Architecture metadata validation failed (${problems.length} problem(s)):\\n` +\n problems.map((problem: string) => ` - ${problem}`).join('\\n')\n );\n this.name = 'MetadataValidationError';\n }\n}\n\n/**\n * Read per-project root + tags from nx's project graph.\n */\nexport async function collectProjectInfo(): Promise<Map<string, ProjectInfo>> {\n const projectGraph = await createProjectGraphAsync();\n const infos = new Map<string, ProjectInfo>();\n for (const [name, node] of Object.entries(projectGraph.nodes)) {\n infos.set(name, new ProjectInfo(name, node.data.root, node.data.tags ?? []));\n }\n return infos;\n}\n\n/**\n * Enrich every graph entry in place with framework, shortDescription,\n * responsibilitiesFile and designFile. Throws MetadataValidationError listing\n * every problem when any project fails validation.\n */\nexport function enrichGraph(\n graph: EnhancedGraph,\n infos: Map<string, ProjectInfo>,\n workspaceRoot: string\n): void {\n const problems: string[] = [];\n\n for (const [projectName, entry] of Object.entries(graph)) {\n const info = infos.get(projectName);\n if (!info) {\n problems.push(`${projectName}: not found in nx project graph`);\n continue;\n }\n\n const resolution = resolveFramework(info, workspaceRoot);\n if (resolution.problem !== null) {\n problems.push(resolution.problem);\n } else if (resolution.framework !== null) {\n entry.framework = resolution.framework;\n }\n\n enrichResponsibilities(entry, info, workspaceRoot, problems);\n\n // Only project.json projects get a generated design.json (see di-graph-targets.ts)\n if (fs.existsSync(path.join(workspaceRoot, info.root, 'project.json'))) {\n entry.designFile = toRepoRelative(info.root, 'design.json');\n }\n }\n\n if (problems.length > 0) {\n throw new MetadataValidationError(problems);\n }\n}\n\nfunction enrichResponsibilities(\n entry: EnhancedGraph[string],\n info: ProjectInfo,\n workspaceRoot: string,\n problems: string[]\n): void {\n const responsibilitiesFile = toRepoRelative(info.root, RESPONSIBILITIES_FILE_NAME);\n entry.responsibilitiesFile = responsibilitiesFile;\n\n const absolutePath = path.join(workspaceRoot, info.root, RESPONSIBILITIES_FILE_NAME);\n if (!fs.existsSync(absolutePath)) {\n problems.push(\n `${info.name}: missing required ${responsibilitiesFile} — create it with a heading, ` +\n `one short summary paragraph, then the full responsibilities of the module`\n );\n return;\n }\n\n const summary = extractShortDescription(fs.readFileSync(absolutePath, 'utf-8'));\n const summaryProblem = validateShortDescription(summary, responsibilitiesFile);\n if (summaryProblem !== null) {\n problems.push(`${info.name}: ${summaryProblem}`);\n return;\n }\n entry.shortDescription = summary;\n}\n\n/**\n * Repo-relative path with forward slashes (stable across platforms in the\n * committed JSON).\n */\nfunction toRepoRelative(projectRoot: string, fileName: string): string {\n return [projectRoot.replace(/\\\\/g, '/').replace(/\\/+$/, ''), fileName].join('/');\n}\n"]}
@@ -7,11 +7,21 @@
7
7
  * 3. Group projects into layers for deterministic ordering
8
8
  */
9
9
  /**
10
- * Graph entry with level metadata
10
+ * Graph entry with level metadata plus AI-oriented metadata filled in by
11
+ * enrichGraph() (lib/graph-metadata.ts) before the graph is saved:
12
+ * - framework: angular | react | express | all-ts | ... (nx tag or inferred)
13
+ * - shortDescription: summary extracted from the project's responsibilities.md
14
+ * - responsibilitiesFile: repo-relative path to the FULL responsibilities doc
15
+ * - designFile: repo-relative path to the generated DI design.json (only for
16
+ * project.json projects)
11
17
  */
12
18
  export interface GraphEntry {
13
19
  level: number;
14
20
  dependsOn: string[];
21
+ framework?: string;
22
+ shortDescription?: string;
23
+ responsibilitiesFile?: string;
24
+ designFile?: string;
15
25
  }
16
26
  /**
17
27
  * Enhanced graph format with level information
@@ -1 +1 @@
1
- {"version":3,"file":"graph-sorter.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-sorter.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Graph Sorter\n *\n * Performs topological sorting on the dependency graph to:\n * 1. Detect circular dependencies (fails if cycle found)\n * 2. Assign level numbers to each project (level 0 = no deps, level 1 = depends on level 0, etc.)\n * 3. Group projects into layers for deterministic ordering\n */\n\n/**\n * Graph entry with level metadata\n */\nexport interface GraphEntry {\n level: number;\n dependsOn: string[];\n}\n\n/**\n * Enhanced graph format with level information\n */\nexport type EnhancedGraph = Record<string, GraphEntry>;\n\n/**\n * Compute topological layers for dependency graph using Kahn's algorithm\n *\n * Projects are grouped into layers where each layer only depends on previous layers.\n * Throws an error if a circular dependency is detected.\n *\n * @param graph - Dependency graph { project: [deps] }\n * @returns Array of layers, each containing sorted project names\n */\nexport function computeTopologicalLayers(graph: Record<string, string[]>): string[][] {\n const layers: string[][] = [];\n const processed = new Set<string>();\n const allProjects = Object.keys(graph);\n\n while (processed.size < allProjects.length) {\n const currentLayer: string[] = [];\n\n for (const project of allProjects) {\n if (processed.has(project)) continue;\n\n const deps = graph[project] || [];\n // Check if all dependencies are in previous layers (already processed)\n const allDepsInPrevLayers = deps.every((dep) => processed.has(dep));\n\n if (allDepsInPrevLayers) {\n currentLayer.push(project);\n }\n }\n\n if (currentLayer.length === 0) {\n // No progress made = circular dependency detected\n const remaining = allProjects.filter((p) => !processed.has(p));\n\n // Try to identify the cycle\n const cycleInfo = findCycle(graph, remaining);\n\n throw new Error(\n `Circular dependency detected among: ${remaining.join(', ')}\\n` +\n (cycleInfo ? `Cycle: ${cycleInfo}\\n` : '') +\n 'Fix: Remove one of the dependencies to break the cycle.'\n );\n }\n\n // Sort alphabetically within layer for deterministic output\n currentLayer.sort();\n layers.push(currentLayer);\n\n // Mark as processed\n currentLayer.forEach((p) => processed.add(p));\n }\n\n return layers;\n}\n\n/**\n * Try to find and describe a cycle in the graph\n */\nfunction findCycle(graph: Record<string, string[]>, remaining: string[]): string | null {\n const visited = new Set<string>();\n const path: string[] = [];\n\n function dfs(node: string): string | null {\n if (path.includes(node)) {\n const cycleStart = path.indexOf(node);\n return [...path.slice(cycleStart), node].join(' -> ');\n }\n if (visited.has(node)) return null;\n\n visited.add(node);\n path.push(node);\n\n const deps = graph[node] || [];\n for (const dep of deps) {\n if (remaining.includes(dep)) {\n const result = dfs(dep);\n if (result) return result;\n }\n }\n\n path.pop();\n return null;\n }\n\n for (const node of remaining) {\n const cycle = dfs(node);\n if (cycle) return cycle;\n }\n\n return null;\n}\n\n/**\n * Sort graph in topological order with alphabetical sorting within layers\n * Returns enhanced format with level metadata\n *\n * @param graph - Unsorted dependency graph { project: [deps] }\n * @returns Sorted graph with level metadata { project: { level: number, dependsOn: [deps] } }\n */\nexport function sortGraphTopologically(graph: Record<string, string[]>): EnhancedGraph {\n const layers = computeTopologicalLayers(graph);\n const result: EnhancedGraph = {};\n\n // Add projects layer by layer (dependencies before dependents)\n layers.forEach((layer, levelIndex) => {\n for (const project of layer) {\n // Already sorted alphabetically within layer\n result[project] = {\n level: levelIndex,\n dependsOn: (graph[project] || []).sort(),\n };\n }\n });\n\n return result;\n}\n"]}
1
+ {"version":3,"file":"graph-sorter.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-sorter.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;AAkCH,4DA2CC;AA8CD,wDAgBC;AAlHD;;;;;;;;GAQG;AACH,SAAgB,wBAAwB,CAAC,KAA+B;IACpE,MAAM,MAAM,GAAe,EAAE,CAAC;IAC9B,MAAM,SAAS,GAAG,IAAI,GAAG,EAAU,CAAC;IACpC,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAEvC,OAAO,SAAS,CAAC,IAAI,GAAG,WAAW,CAAC,MAAM,EAAE,CAAC;QACzC,MAAM,YAAY,GAAa,EAAE,CAAC;QAElC,KAAK,MAAM,OAAO,IAAI,WAAW,EAAE,CAAC;YAChC,IAAI,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC;gBAAE,SAAS;YAErC,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;YAClC,uEAAuE;YACvE,MAAM,mBAAmB,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;YAEpE,IAAI,mBAAmB,EAAE,CAAC;gBACtB,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAC/B,CAAC;QACL,CAAC;QAED,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC5B,kDAAkD;YAClD,MAAM,SAAS,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;YAE/D,4BAA4B;YAC5B,MAAM,SAAS,GAAG,SAAS,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;YAE9C,MAAM,IAAI,KAAK,CACX,uCAAuC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;gBAC3D,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,SAAS,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC1C,yDAAyD,CAChE,CAAC;QACN,CAAC;QAED,4DAA4D;QAC5D,YAAY,CAAC,IAAI,EAAE,CAAC;QACpB,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAE1B,oBAAoB;QACpB,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAClD,CAAC;IAED,OAAO,MAAM,CAAC;AAClB,CAAC;AAED;;GAEG;AACH,SAAS,SAAS,CAAC,KAA+B,EAAE,SAAmB;IACnE,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAClC,MAAM,IAAI,GAAa,EAAE,CAAC;IAE1B,SAAS,GAAG,CAAC,IAAY;QACrB,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YACtB,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YACtC,OAAO,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC1D,CAAC;QACD,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;QAEnC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAClB,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAEhB,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QAC/B,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACrB,IAAI,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC1B,MAAM,MAAM,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;gBACxB,IAAI,MAAM;oBAAE,OAAO,MAAM,CAAC;YAC9B,CAAC;QACL,CAAC;QAED,IAAI,CAAC,GAAG,EAAE,CAAC;QACX,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;QAC3B,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC;QACxB,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC;IAC5B,CAAC;IAED,OAAO,IAAI,CAAC;AAChB,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,sBAAsB,CAAC,KAA+B;IAClE,MAAM,MAAM,GAAG,wBAAwB,CAAC,KAAK,CAAC,CAAC;IAC/C,MAAM,MAAM,GAAkB,EAAE,CAAC;IAEjC,+DAA+D;IAC/D,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE;QACjC,KAAK,MAAM,OAAO,IAAI,KAAK,EAAE,CAAC;YAC1B,6CAA6C;YAC7C,MAAM,CAAC,OAAO,CAAC,GAAG;gBACd,KAAK,EAAE,UAAU;gBACjB,SAAS,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE;aAC3C,CAAC;QACN,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAClB,CAAC","sourcesContent":["/**\n * Graph Sorter\n *\n * Performs topological sorting on the dependency graph to:\n * 1. Detect circular dependencies (fails if cycle found)\n * 2. Assign level numbers to each project (level 0 = no deps, level 1 = depends on level 0, etc.)\n * 3. Group projects into layers for deterministic ordering\n */\n\n/**\n * Graph entry with level metadata plus AI-oriented metadata filled in by\n * enrichGraph() (lib/graph-metadata.ts) before the graph is saved:\n * - framework: angular | react | express | all-ts | ... (nx tag or inferred)\n * - shortDescription: summary extracted from the project's responsibilities.md\n * - responsibilitiesFile: repo-relative path to the FULL responsibilities doc\n * - designFile: repo-relative path to the generated DI design.json (only for\n * project.json projects)\n */\nexport interface GraphEntry {\n level: number;\n dependsOn: string[];\n framework?: string;\n shortDescription?: string;\n responsibilitiesFile?: string;\n designFile?: string;\n}\n\n/**\n * Enhanced graph format with level information\n */\nexport type EnhancedGraph = Record<string, GraphEntry>;\n\n/**\n * Compute topological layers for dependency graph using Kahn's algorithm\n *\n * Projects are grouped into layers where each layer only depends on previous layers.\n * Throws an error if a circular dependency is detected.\n *\n * @param graph - Dependency graph { project: [deps] }\n * @returns Array of layers, each containing sorted project names\n */\nexport function computeTopologicalLayers(graph: Record<string, string[]>): string[][] {\n const layers: string[][] = [];\n const processed = new Set<string>();\n const allProjects = Object.keys(graph);\n\n while (processed.size < allProjects.length) {\n const currentLayer: string[] = [];\n\n for (const project of allProjects) {\n if (processed.has(project)) continue;\n\n const deps = graph[project] || [];\n // Check if all dependencies are in previous layers (already processed)\n const allDepsInPrevLayers = deps.every((dep) => processed.has(dep));\n\n if (allDepsInPrevLayers) {\n currentLayer.push(project);\n }\n }\n\n if (currentLayer.length === 0) {\n // No progress made = circular dependency detected\n const remaining = allProjects.filter((p) => !processed.has(p));\n\n // Try to identify the cycle\n const cycleInfo = findCycle(graph, remaining);\n\n throw new Error(\n `Circular dependency detected among: ${remaining.join(', ')}\\n` +\n (cycleInfo ? `Cycle: ${cycleInfo}\\n` : '') +\n 'Fix: Remove one of the dependencies to break the cycle.'\n );\n }\n\n // Sort alphabetically within layer for deterministic output\n currentLayer.sort();\n layers.push(currentLayer);\n\n // Mark as processed\n currentLayer.forEach((p) => processed.add(p));\n }\n\n return layers;\n}\n\n/**\n * Try to find and describe a cycle in the graph\n */\nfunction findCycle(graph: Record<string, string[]>, remaining: string[]): string | null {\n const visited = new Set<string>();\n const path: string[] = [];\n\n function dfs(node: string): string | null {\n if (path.includes(node)) {\n const cycleStart = path.indexOf(node);\n return [...path.slice(cycleStart), node].join(' -> ');\n }\n if (visited.has(node)) return null;\n\n visited.add(node);\n path.push(node);\n\n const deps = graph[node] || [];\n for (const dep of deps) {\n if (remaining.includes(dep)) {\n const result = dfs(dep);\n if (result) return result;\n }\n }\n\n path.pop();\n return null;\n }\n\n for (const node of remaining) {\n const cycle = dfs(node);\n if (cycle) return cycle;\n }\n\n return null;\n}\n\n/**\n * Sort graph in topological order with alphabetical sorting within layers\n * Returns enhanced format with level metadata\n *\n * @param graph - Unsorted dependency graph { project: [deps] }\n * @returns Sorted graph with level metadata { project: { level: number, dependsOn: [deps] } }\n */\nexport function sortGraphTopologically(graph: Record<string, string[]>): EnhancedGraph {\n const layers = computeTopologicalLayers(graph);\n const result: EnhancedGraph = {};\n\n // Add projects layer by layer (dependencies before dependents)\n layers.forEach((layer, levelIndex) => {\n for (const project of layer) {\n // Already sorted alphabetically within layer\n result[project] = {\n level: levelIndex,\n dependsOn: (graph[project] || []).sort(),\n };\n }\n });\n\n return result;\n}\n"]}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Project Info
3
+ *
4
+ * Per-project metadata pulled from nx's project graph (root + tags), used to
5
+ * enrich architecture/dependencies.json with AI-oriented fields.
6
+ */
7
+ export declare class ProjectInfo {
8
+ readonly name: string;
9
+ /** Project root, relative to the workspace root (e.g. packages/http/http-routing) */
10
+ readonly root: string;
11
+ readonly tags: string[];
12
+ constructor(name: string,
13
+ /** Project root, relative to the workspace root (e.g. packages/http/http-routing) */
14
+ root: string, tags: string[]);
15
+ }
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ /**
3
+ * Project Info
4
+ *
5
+ * Per-project metadata pulled from nx's project graph (root + tags), used to
6
+ * enrich architecture/dependencies.json with AI-oriented fields.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.ProjectInfo = void 0;
10
+ class ProjectInfo {
11
+ name;
12
+ root;
13
+ tags;
14
+ constructor(name,
15
+ /** Project root, relative to the workspace root (e.g. packages/http/http-routing) */
16
+ root, tags) {
17
+ this.name = name;
18
+ this.root = root;
19
+ this.tags = tags;
20
+ }
21
+ }
22
+ exports.ProjectInfo = ProjectInfo;
23
+ //# sourceMappingURL=project-info.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-info.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/project-info.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAEH,MAAa,WAAW;IAEA;IAEA;IACA;IAJpB,YACoB,IAAY;IAC5B,qFAAqF;IACrE,IAAY,EACZ,IAAc;QAHd,SAAI,GAAJ,IAAI,CAAQ;QAEZ,SAAI,GAAJ,IAAI,CAAQ;QACZ,SAAI,GAAJ,IAAI,CAAU;IAC/B,CAAC;CACP;AAPD,kCAOC","sourcesContent":["/**\n * Project Info\n *\n * Per-project metadata pulled from nx's project graph (root + tags), used to\n * enrich architecture/dependencies.json with AI-oriented fields.\n */\n\nexport class ProjectInfo {\n constructor(\n public readonly name: string,\n /** Project root, relative to the workspace root (e.g. packages/http/http-routing) */\n public readonly root: string,\n public readonly tags: string[]\n ) {}\n}\n"]}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Responsibilities Extraction
3
+ *
4
+ * Every project must have a human-authored `responsibilities.md` at its root
5
+ * describing the FULL responsibilities of the module (what belongs in it and
6
+ * what does not). The first paragraph of that file is embedded into
7
+ * architecture/dependencies.json as the project's `shortDescription` so AI
8
+ * gets a summary just by reading the graph file.
9
+ */
10
+ /**
11
+ * Maximum length of the shortDescription embedded in dependencies.json.
12
+ * The summary must stay small — full detail belongs in responsibilities.md.
13
+ */
14
+ export declare const MAX_SHORT_DESCRIPTION_LENGTH = 300;
15
+ /**
16
+ * Extract the shortDescription from responsibilities.md content:
17
+ * the first non-empty paragraph after any leading markdown headings,
18
+ * with newlines collapsed to single spaces.
19
+ *
20
+ * Returns '' when the file has no summary paragraph.
21
+ */
22
+ export declare function extractShortDescription(markdown: string): string;
23
+ /**
24
+ * Validate an extracted shortDescription. Returns a problem description,
25
+ * or null when the summary is valid.
26
+ */
27
+ export declare function validateShortDescription(summary: string, sourceFile: string): string | null;
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ /**
3
+ * Responsibilities Extraction
4
+ *
5
+ * Every project must have a human-authored `responsibilities.md` at its root
6
+ * describing the FULL responsibilities of the module (what belongs in it and
7
+ * what does not). The first paragraph of that file is embedded into
8
+ * architecture/dependencies.json as the project's `shortDescription` so AI
9
+ * gets a summary just by reading the graph file.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.MAX_SHORT_DESCRIPTION_LENGTH = void 0;
13
+ exports.extractShortDescription = extractShortDescription;
14
+ exports.validateShortDescription = validateShortDescription;
15
+ /**
16
+ * Maximum length of the shortDescription embedded in dependencies.json.
17
+ * The summary must stay small — full detail belongs in responsibilities.md.
18
+ */
19
+ exports.MAX_SHORT_DESCRIPTION_LENGTH = 300;
20
+ /**
21
+ * Extract the shortDescription from responsibilities.md content:
22
+ * the first non-empty paragraph after any leading markdown headings,
23
+ * with newlines collapsed to single spaces.
24
+ *
25
+ * Returns '' when the file has no summary paragraph.
26
+ */
27
+ function extractShortDescription(markdown) {
28
+ const lines = markdown.split('\n');
29
+ const paragraph = [];
30
+ for (const rawLine of lines) {
31
+ const line = rawLine.trim();
32
+ if (line.length === 0 || line.startsWith('#')) {
33
+ // Blank line or heading ends the paragraph once started;
34
+ // before that, keep scanning for the first paragraph.
35
+ if (paragraph.length > 0)
36
+ break;
37
+ continue;
38
+ }
39
+ paragraph.push(line);
40
+ }
41
+ return paragraph.join(' ').replace(/\s+/g, ' ').trim();
42
+ }
43
+ /**
44
+ * Validate an extracted shortDescription. Returns a problem description,
45
+ * or null when the summary is valid.
46
+ */
47
+ function validateShortDescription(summary, sourceFile) {
48
+ if (summary.length === 0) {
49
+ return (`${sourceFile} has no summary paragraph. ` +
50
+ `Start the file with a heading, then ONE short paragraph ` +
51
+ `(max ${exports.MAX_SHORT_DESCRIPTION_LENGTH} chars) summarizing the module.`);
52
+ }
53
+ if (summary.length > exports.MAX_SHORT_DESCRIPTION_LENGTH) {
54
+ return (`${sourceFile} summary paragraph is ${summary.length} chars ` +
55
+ `(max ${exports.MAX_SHORT_DESCRIPTION_LENGTH}). Shorten the first paragraph; ` +
56
+ `move detail into later sections of the file.`);
57
+ }
58
+ return null;
59
+ }
60
+ //# sourceMappingURL=responsibilities.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"responsibilities.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/responsibilities.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;AAeH,0DAkBC;AAMD,4DAgBC;AArDD;;;GAGG;AACU,QAAA,4BAA4B,GAAG,GAAG,CAAC;AAEhD;;;;;;GAMG;AACH,SAAgB,uBAAuB,CAAC,QAAgB;IACpD,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACnC,MAAM,SAAS,GAAa,EAAE,CAAC;IAE/B,KAAK,MAAM,OAAO,IAAI,KAAK,EAAE,CAAC;QAC1B,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;QAE5B,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YAC5C,yDAAyD;YACzD,sDAAsD;YACtD,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC;gBAAE,MAAM;YAChC,SAAS;QACb,CAAC;QAED,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC;IAED,OAAO,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;AAC3D,CAAC;AAED;;;GAGG;AACH,SAAgB,wBAAwB,CAAC,OAAe,EAAE,UAAkB;IACxE,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,CACH,GAAG,UAAU,6BAA6B;YAC1C,0DAA0D;YAC1D,QAAQ,oCAA4B,iCAAiC,CACxE,CAAC;IACN,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,GAAG,oCAA4B,EAAE,CAAC;QAChD,OAAO,CACH,GAAG,UAAU,yBAAyB,OAAO,CAAC,MAAM,SAAS;YAC7D,QAAQ,oCAA4B,kCAAkC;YACtE,8CAA8C,CACjD,CAAC;IACN,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC","sourcesContent":["/**\n * Responsibilities Extraction\n *\n * Every project must have a human-authored `responsibilities.md` at its root\n * describing the FULL responsibilities of the module (what belongs in it and\n * what does not). The first paragraph of that file is embedded into\n * architecture/dependencies.json as the project's `shortDescription` so AI\n * gets a summary just by reading the graph file.\n */\n\n/**\n * Maximum length of the shortDescription embedded in dependencies.json.\n * The summary must stay small — full detail belongs in responsibilities.md.\n */\nexport const MAX_SHORT_DESCRIPTION_LENGTH = 300;\n\n/**\n * Extract the shortDescription from responsibilities.md content:\n * the first non-empty paragraph after any leading markdown headings,\n * with newlines collapsed to single spaces.\n *\n * Returns '' when the file has no summary paragraph.\n */\nexport function extractShortDescription(markdown: string): string {\n const lines = markdown.split('\\n');\n const paragraph: string[] = [];\n\n for (const rawLine of lines) {\n const line = rawLine.trim();\n\n if (line.length === 0 || line.startsWith('#')) {\n // Blank line or heading ends the paragraph once started;\n // before that, keep scanning for the first paragraph.\n if (paragraph.length > 0) break;\n continue;\n }\n\n paragraph.push(line);\n }\n\n return paragraph.join(' ').replace(/\\s+/g, ' ').trim();\n}\n\n/**\n * Validate an extracted shortDescription. Returns a problem description,\n * or null when the summary is valid.\n */\nexport function validateShortDescription(summary: string, sourceFile: string): string | null {\n if (summary.length === 0) {\n return (\n `${sourceFile} has no summary paragraph. ` +\n `Start the file with a heading, then ONE short paragraph ` +\n `(max ${MAX_SHORT_DESCRIPTION_LENGTH} chars) summarizing the module.`\n );\n }\n if (summary.length > MAX_SHORT_DESCRIPTION_LENGTH) {\n return (\n `${sourceFile} summary paragraph is ${summary.length} chars ` +\n `(max ${MAX_SHORT_DESCRIPTION_LENGTH}). Shorten the first paragraph; ` +\n `move detail into later sections of the file.`\n );\n }\n return null;\n}\n"]}