@chemx/starter-kit 26.9.16-41 → 26.9.17-1100

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/README.md +181 -2
  2. package/assets/chemx-starter-kit-logo.gif +0 -0
  3. package/blueprints/_view-template.scss +24 -0
  4. package/blueprints/atoms/a-button.vue +43 -0
  5. package/blueprints/molecule-capsule/_m-sample-card.scss +75 -0
  6. package/blueprints/molecule-capsule/index.ts +2 -0
  7. package/blueprints/molecule-capsule/m-chemx-badge/_m-chemx-badge.scss +76 -0
  8. package/blueprints/molecule-capsule/m-chemx-badge/index.ts +3 -0
  9. package/blueprints/molecule-capsule/m-chemx-badge/m-chemx-badge.spec.ts +74 -0
  10. package/blueprints/molecule-capsule/m-chemx-badge/m-chemx-badge.tsx +39 -0
  11. package/blueprints/molecule-capsule/m-chemx-badge/m-chemx-badge.vue +42 -0
  12. package/blueprints/molecule-capsule/m-chemx-badge/types.d.ts +12 -0
  13. package/blueprints/molecule-capsule/m-sample-card.spec.ts +74 -0
  14. package/blueprints/molecule-capsule/m-sample-card.tsx +72 -0
  15. package/blueprints/molecule-capsule/m-tab-button/_m-tab-button.scss +49 -0
  16. package/blueprints/molecule-capsule/m-tab-button/index.ts +3 -0
  17. package/blueprints/molecule-capsule/m-tab-button/m-tab-button.controller.ts +40 -0
  18. package/blueprints/molecule-capsule/m-tab-button/m-tab-button.spec.ts +40 -0
  19. package/blueprints/molecule-capsule/m-tab-button/m-tab-button.vue +38 -0
  20. package/blueprints/molecule-capsule/m-tab-button/types.d.ts +14 -0
  21. package/blueprints/molecule-capsule/types.d.ts +12 -0
  22. package/blueprints/shims-vue.d.ts +4 -0
  23. package/blueprints/view-template.tsx +20 -0
  24. package/blueprints/workflows/chemx-audit.yml +79 -0
  25. package/cli/audit/ast-visitors.js +73 -0
  26. package/cli/audit/autofix.js +4 -5
  27. package/cli/audit/extended-visitors.js +243 -0
  28. package/cli/audit/pattern-detector.js +90 -3
  29. package/cli/audit/pattern-detector.spec.js +36 -1
  30. package/cli/audit/prompts.js +10 -12
  31. package/cli/audit/reporter-utils.js +13 -22
  32. package/cli/audit/roadmap.js +5 -20
  33. package/cli/audit/rules-predicates.js +3 -2
  34. package/cli/audit/rules-registry.js +25 -0
  35. package/cli/audit/rules.js +4 -0
  36. package/cli/audit/rules.spec.js +205 -0
  37. package/cli/audit-preflight-git.js +54 -0
  38. package/cli/audit-preflight-scope.js +87 -0
  39. package/cli/audit-preflight.d.ts +16 -0
  40. package/cli/audit-preflight.js +85 -0
  41. package/cli/audit-preflight.spec.js +115 -0
  42. package/cli/audit.js +48 -22
  43. package/cli/build/detector.js +40 -23
  44. package/cli/build/detector.spec.js +81 -0
  45. package/cli/build.d.ts +2 -1
  46. package/cli/build.js +16 -12
  47. package/cli/columnar.d.ts +14 -0
  48. package/cli/columnar.js +52 -0
  49. package/cli/columnar.spec.js +49 -0
  50. package/cli/generator-templates/controller.js +36 -0
  51. package/cli/generator-templates/hooks.js +60 -0
  52. package/cli/generator-templates/naming.js +14 -0
  53. package/cli/generator-templates/react.js +63 -0
  54. package/cli/generator-templates/specs.js +38 -0
  55. package/cli/generator-templates/styles.js +64 -0
  56. package/cli/generator-templates/svelte.js +66 -0
  57. package/cli/generator-templates/types.js +29 -0
  58. package/cli/generator-templates/views.js +76 -0
  59. package/cli/generator-templates/vue.js +62 -0
  60. package/cli/generator-templates.d.ts +35 -0
  61. package/cli/generator-templates.js +28 -480
  62. package/cli/generator-templates.spec.js +108 -0
  63. package/cli/generator.js +2 -2
  64. package/cli/generator.spec.js +61 -1
  65. package/cli/help.js +38 -4
  66. package/cli/index.js +112 -14
  67. package/cli/installer-templates.js +67 -13
  68. package/cli/installer.d.ts +2 -0
  69. package/cli/installer.js +48 -21
  70. package/cli/installer.spec.js +72 -0
  71. package/cli/license.js +22 -1
  72. package/cli/mcp/antigravity.js +59 -0
  73. package/cli/mcp/index.js +2 -1
  74. package/cli/mcp/installer.js +54 -22
  75. package/cli/mcp/installer.spec.js +24 -1
  76. package/cli/mcp/manifests.js +332 -0
  77. package/cli/mcp/prompts.js +28 -0
  78. package/cli/mcp/resources.js +175 -64
  79. package/cli/mcp/server.js +51 -5
  80. package/cli/mcp/server.spec.js +247 -1
  81. package/cli/mcp/tools.js +386 -194
  82. package/cli/mcp/types.d.ts +37 -0
  83. package/cli/navigator-actions.js +23 -10
  84. package/cli/navigator-banner-helpers.js +33 -0
  85. package/cli/navigator-banner.js +19 -38
  86. package/cli/navigator-banner.spec.js +68 -0
  87. package/cli/navigator-guide.js +73 -0
  88. package/cli/navigator-menu.js +20 -0
  89. package/cli/navigator.js +1 -0
  90. package/cli/patcher.d.ts +73 -0
  91. package/cli/patcher.js +249 -4
  92. package/cli/patcher.spec.js +148 -0
  93. package/cli/postinstall.js +27 -0
  94. package/cli/project-detector.js +20 -2
  95. package/cli/project-detector.spec.js +19 -0
  96. package/cli/reader.js +18 -4
  97. package/cli/scaffold.js +26 -4
  98. package/cli/search-commands.js +10 -8
  99. package/cli/search-queries.js +8 -3
  100. package/cli/search-schema.js +5 -0
  101. package/cli/search.d.ts +16 -0
  102. package/cli/search.js +103 -11
  103. package/cli/silence-warnings.js +17 -0
  104. package/cli/team/index.js +30 -0
  105. package/cli/team/team-adversarial-locks.spec.js +105 -0
  106. package/cli/team/team-adversarial-telemetry.spec.js +93 -0
  107. package/cli/team/team-commands.js +199 -0
  108. package/cli/team/team-db-agents.js +79 -0
  109. package/cli/team/team-db-feed.js +84 -0
  110. package/cli/team/team-db-lock-promotion.js +91 -0
  111. package/cli/team/team-db-locks.js +94 -0
  112. package/cli/team/team-db-task-helpers.js +94 -0
  113. package/cli/team/team-db-tasks.js +90 -0
  114. package/cli/team/team-db.js +93 -0
  115. package/cli/team/team-format.js +80 -0
  116. package/cli/team/team-lock-concurrency.spec.js +182 -0
  117. package/cli/team/team-schema.js +74 -0
  118. package/cli/team/team-telemetry.js +104 -0
  119. package/cli/team/team-triage.js +88 -0
  120. package/cli/team/team.spec.js +344 -0
  121. package/cli/terminal.js +36 -1
  122. package/cli/ui-actions-helpers.js +66 -0
  123. package/cli/ui-actions.js +85 -0
  124. package/cli/ui-attention.js +74 -0
  125. package/cli/ui-client-api.js +49 -0
  126. package/cli/ui-client-cert.js +27 -0
  127. package/cli/ui-client-script.js +83 -0
  128. package/cli/ui-db-studio.js +79 -0
  129. package/cli/ui-handlers.js +95 -0
  130. package/cli/ui-html.js +35 -0
  131. package/cli/ui-server-routes.js +63 -0
  132. package/cli/ui-server.js +75 -0
  133. package/cli/ui-styles.js +34 -0
  134. package/cli/ui-template-views.js +63 -0
  135. package/cli/ui-template.js +91 -0
  136. package/cli/ui.spec.js +305 -0
  137. package/cli/verify.js +367 -0
  138. package/cli/verify.spec.js +98 -0
  139. package/docs/CHANGELOG.md +10 -0
  140. package/package.json +13 -15
  141. package/scripts/postinstall.mjs +27 -0
  142. package/scripts/pre-commit.sh +135 -0
  143. package/scripts/publish-both.mjs +108 -0
  144. package/cli/mcp.js +0 -326
@@ -33,6 +33,59 @@ const FAKE_GREEN_PATTERNS = [
33
33
  const IMG_WITHOUT_ALT_PATTERN = new RegExp(['<', 'img\\b', '(?![^>]*\\balt\\s*=)', '[^>]*>'].join(''), 'i');
34
34
  const CLICKABLE_CONTAINER_PATTERN = new RegExp(['<', '(div|span)\\b', '(?![^>]*\\brole\\s*=\\s*[\'"](?:button|link|tab)[\'"])', '[^>]*\\b(?:@click|v-on:click)\\s*='].join(''), 'i');
35
35
 
36
+ const SENSITIVE_KEYWORDS = new Set([
37
+ 'password', 'passwd', 'secret', 'token', 'apikey', 'api_key',
38
+ 'authtoken', 'auth_token', 'accesstoken', 'access_token',
39
+ 'refreshtoken', 'refresh_token', 'privatekey', 'private_key',
40
+ 'creditcard', 'credit_card', 'ssn'
41
+ ]);
42
+
43
+ const isSensitiveName = (name = '') => {
44
+ const normalized = String(name).toLowerCase();
45
+ if (SENSITIVE_KEYWORDS.has(normalized)) return true;
46
+ return /^(pass(word)?|token|secret|api[_-]?key)$/i.test(normalized);
47
+ };
48
+
49
+ const JAVASCRIPT_URL_REGEX = /^\s*javascript:/i;
50
+ const TABNABBING_TEMPLATE_PATTERN = /<a\b[^>]*\btarget\s*=\s*["']_blank["'][^>]*>/i;
51
+ const JAVASCRIPT_URL_TEMPLATE_PATTERN = /\b(?:href|src|action|formaction)\s*=\s*["']\s*javascript:/i;
52
+
53
+ const resolvePropertyKeyName = (key) => {
54
+ const isIdentifier = t.isIdentifier(key);
55
+ if (isIdentifier) return key.name;
56
+ const isStringLiteral = t.isStringLiteral(key);
57
+ if (isStringLiteral) return key.value;
58
+ return '';
59
+ };
60
+
61
+ const isSensitiveExpression = (node) => {
62
+ if (!node) return false;
63
+ if (t.isIdentifier(node)) {
64
+ return isSensitiveName(node.name);
65
+ }
66
+ if (t.isMemberExpression(node)) {
67
+ if (t.isIdentifier(node.property)) {
68
+ return isSensitiveName(node.property.name);
69
+ }
70
+ if (t.isStringLiteral(node.property)) {
71
+ return isSensitiveName(node.property.value);
72
+ }
73
+ }
74
+ if (t.isObjectExpression(node)) {
75
+ return node.properties.some((prop) => {
76
+ if (t.isObjectProperty(prop)) {
77
+ const keyName = resolvePropertyKeyName(prop.key);
78
+ return isSensitiveName(keyName);
79
+ }
80
+ return false;
81
+ });
82
+ }
83
+ if (t.isTemplateLiteral(node)) {
84
+ return node.expressions.some(isSensitiveExpression);
85
+ }
86
+ return false;
87
+ };
88
+
36
89
  /**
37
90
  * Pillar 10: Check for co-located test files for molecule capsules.
38
91
  */
@@ -136,6 +189,41 @@ export const checkExtendedTextPatterns = (content, lines, relativePath, filePath
136
189
  }
137
190
  }
138
191
 
192
+ // Pillar 9: Reverse Tabnabbing in HTML / Vue templates
193
+ const hasTargetBlank = isTemplateFile && TABNABBING_TEMPLATE_PATTERN.test(lineText);
194
+ if (hasTargetBlank) {
195
+ const hasRelProtection = /\brel\s*=\s*["'][^"']*(?:noopener|noreferrer)[^"']*["']/i.test(lineText);
196
+ if (!hasRelProtection) {
197
+ const meta = RULE_REGISTRY.SECURITY_REVERSE_TABNABBING;
198
+ violations.push({
199
+ filePath: relativePath,
200
+ line: lineNum,
201
+ column: 1,
202
+ hazard: 'External link with target="_blank" missing rel="noopener noreferrer"',
203
+ rule: 'SECURITY_REVERSE_TABNABBING',
204
+ severity: meta.severity,
205
+ pillar: meta.pillar,
206
+ directive: meta.directive
207
+ });
208
+ }
209
+ }
210
+
211
+ // Pillar 9: javascript: pseudo-protocol in HTML / Vue templates
212
+ const hasJsUrl = isTemplateFile && JAVASCRIPT_URL_TEMPLATE_PATTERN.test(lineText);
213
+ if (hasJsUrl) {
214
+ const meta = RULE_REGISTRY.SECURITY_JAVASCRIPT_URL;
215
+ violations.push({
216
+ filePath: relativePath,
217
+ line: lineNum,
218
+ column: 1,
219
+ hazard: 'javascript: pseudo-protocol detected in template attribute',
220
+ rule: 'SECURITY_JAVASCRIPT_URL',
221
+ severity: meta.severity,
222
+ pillar: meta.pillar,
223
+ directive: meta.directive
224
+ });
225
+ }
226
+
139
227
  // Pillar 8: Non-semantic clickable containers in templates (@click on div/span)
140
228
  const hasClickableContainer = isTemplateFile && CLICKABLE_CONTAINER_PATTERN.test(lineText);
141
229
  if (hasClickableContainer) {
@@ -244,6 +332,49 @@ export const createExtendedVisitors = ({ relativePath, violations }) => {
244
332
  });
245
333
  }
246
334
  }
335
+
336
+ // Pillar 9: External link target="_blank" without rel="noopener noreferrer"
337
+ const isAnchorElement = tagName === 'a';
338
+ if (isAnchorElement) {
339
+ const targetAttr = astPath.node.attributes.find((attr) => t.isJSXAttribute(attr) && attr.name?.name === 'target');
340
+ let isTargetBlank = false;
341
+ if (targetAttr) {
342
+ if (t.isStringLiteral(targetAttr.value)) {
343
+ isTargetBlank = targetAttr.value.value.trim().toLowerCase() === '_blank';
344
+ } else if (t.isJSXExpressionContainer(targetAttr.value) && t.isStringLiteral(targetAttr.value.expression)) {
345
+ isTargetBlank = targetAttr.value.expression.value.trim().toLowerCase() === '_blank';
346
+ }
347
+ }
348
+
349
+ if (isTargetBlank) {
350
+ const relAttr = astPath.node.attributes.find((attr) => t.isJSXAttribute(attr) && attr.name?.name === 'rel');
351
+ let hasRelProtection = false;
352
+ if (relAttr) {
353
+ if (t.isStringLiteral(relAttr.value)) {
354
+ const val = relAttr.value.value.toLowerCase();
355
+ hasRelProtection = val.includes('noopener') || val.includes('noreferrer');
356
+ } else if (t.isJSXExpressionContainer(relAttr.value) && t.isStringLiteral(relAttr.value.expression)) {
357
+ const val = relAttr.value.expression.value.toLowerCase();
358
+ hasRelProtection = val.includes('noopener') || val.includes('noreferrer');
359
+ }
360
+ }
361
+
362
+ if (!hasRelProtection) {
363
+ const line = astPath.node.loc?.start.line || 1;
364
+ const meta = RULE_REGISTRY.SECURITY_REVERSE_TABNABBING;
365
+ violations.push({
366
+ filePath: relativePath,
367
+ line,
368
+ column: astPath.node.loc?.start.column || 1,
369
+ hazard: 'External link with target="_blank" missing rel="noopener noreferrer"',
370
+ rule: 'SECURITY_REVERSE_TABNABBING',
371
+ severity: meta.severity,
372
+ pillar: meta.pillar,
373
+ directive: meta.directive
374
+ });
375
+ }
376
+ }
377
+ }
247
378
  },
248
379
 
249
380
  JSXAttribute(astPath) {
@@ -314,6 +445,118 @@ export const createExtendedVisitors = ({ relativePath, violations }) => {
314
445
  }
315
446
  }
316
447
  }
448
+
449
+ // Pillar 9: javascript: pseudo-protocol URL in JSX
450
+ const isUrlAttribute = ['href', 'src', 'action', 'formAction'].includes(attrName);
451
+ if (isUrlAttribute) {
452
+ let isJsUrl = false;
453
+ const val = astPath.node.value;
454
+ if (t.isStringLiteral(val)) {
455
+ isJsUrl = JAVASCRIPT_URL_REGEX.test(val.value);
456
+ } else if (t.isJSXExpressionContainer(val)) {
457
+ const expr = val.expression;
458
+ if (t.isStringLiteral(expr)) {
459
+ isJsUrl = JAVASCRIPT_URL_REGEX.test(expr.value);
460
+ } else if (t.isTemplateLiteral(expr) && expr.quasis.length > 0) {
461
+ isJsUrl = JAVASCRIPT_URL_REGEX.test(expr.quasis[0].value.raw);
462
+ }
463
+ }
464
+
465
+ if (isJsUrl) {
466
+ const line = astPath.node.loc?.start.line || 1;
467
+ const meta = RULE_REGISTRY.SECURITY_JAVASCRIPT_URL;
468
+ violations.push({
469
+ filePath: relativePath,
470
+ line,
471
+ column: astPath.node.loc?.start.column || 1,
472
+ hazard: `javascript: pseudo-protocol detected in ${attrName} attribute`,
473
+ rule: 'SECURITY_JAVASCRIPT_URL',
474
+ severity: meta.severity,
475
+ pillar: meta.pillar,
476
+ directive: meta.directive
477
+ });
478
+ }
479
+ }
480
+ },
481
+
482
+ CallExpression(astPath) {
483
+ const callee = astPath.node.callee;
484
+
485
+ // Pillar 9: Dynamic code execution via eval()
486
+ if (t.isIdentifier(callee) && callee.name === 'eval') {
487
+ const line = astPath.node.loc?.start.line || 1;
488
+ const meta = RULE_REGISTRY.SECURITY_DYNAMIC_CODE_EXECUTION;
489
+ violations.push({
490
+ filePath: relativePath,
491
+ line,
492
+ column: astPath.node.loc?.start.column || 1,
493
+ hazard: 'Dynamic code execution via eval() detected',
494
+ rule: 'SECURITY_DYNAMIC_CODE_EXECUTION',
495
+ severity: meta.severity,
496
+ pillar: meta.pillar,
497
+ directive: meta.directive
498
+ });
499
+ }
500
+
501
+ // Pillar 9: String-based code execution in setTimeout/setInterval
502
+ if (t.isIdentifier(callee) && (callee.name === 'setTimeout' || callee.name === 'setInterval')) {
503
+ const firstArg = astPath.node.arguments[0];
504
+ const isStringExecution = t.isStringLiteral(firstArg) || t.isTemplateLiteral(firstArg);
505
+ if (isStringExecution) {
506
+ const line = astPath.node.loc?.start.line || 1;
507
+ const meta = RULE_REGISTRY.SECURITY_DYNAMIC_CODE_EXECUTION;
508
+ violations.push({
509
+ filePath: relativePath,
510
+ line,
511
+ column: astPath.node.loc?.start.column || 1,
512
+ hazard: `String-based dynamic code execution in ${callee.name} detected`,
513
+ rule: 'SECURITY_DYNAMIC_CODE_EXECUTION',
514
+ severity: meta.severity,
515
+ pillar: meta.pillar,
516
+ directive: meta.directive
517
+ });
518
+ }
519
+ }
520
+
521
+ // Pillar 9: Sensitive credential/token logging in console
522
+ const isConsoleCall = t.isMemberExpression(callee) && t.isIdentifier(callee.object) && callee.object.name === 'console';
523
+ if (isConsoleCall) {
524
+ const args = astPath.node.arguments || [];
525
+ const hasSensitiveArg = args.some(isSensitiveExpression);
526
+ if (hasSensitiveArg) {
527
+ const line = astPath.node.loc?.start.line || 1;
528
+ const meta = RULE_REGISTRY.SECURITY_SENSITIVE_LOGGING;
529
+ violations.push({
530
+ filePath: relativePath,
531
+ line,
532
+ column: astPath.node.loc?.start.column || 1,
533
+ hazard: 'Sensitive variable (credential, token, or password) logged to console',
534
+ rule: 'SECURITY_SENSITIVE_LOGGING',
535
+ severity: meta.severity,
536
+ pillar: meta.pillar,
537
+ directive: meta.directive
538
+ });
539
+ }
540
+ }
541
+ },
542
+
543
+ NewExpression(astPath) {
544
+ // Pillar 9: Dynamic code execution via new Function()
545
+ const callee = astPath.node.callee;
546
+ if (t.isIdentifier(callee) && callee.name === 'Function') {
547
+ const line = astPath.node.loc?.start.line || 1;
548
+ const meta = RULE_REGISTRY.SECURITY_DYNAMIC_CODE_EXECUTION;
549
+ violations.push({
550
+ filePath: relativePath,
551
+ line,
552
+ column: astPath.node.loc?.start.column || 1,
553
+ hazard: 'Dynamic code execution via new Function() constructor detected',
554
+ rule: 'SECURITY_DYNAMIC_CODE_EXECUTION',
555
+ severity: meta.severity,
556
+ pillar: meta.pillar,
557
+ directive: meta.directive
558
+ });
559
+ }
317
560
  },
318
561
 
319
562
  VariableDeclarator(astPath) {
@@ -49,9 +49,10 @@ export const createPatternRegistry = () => {
49
49
  suggestedCapsule = 'types/state.d.ts';
50
50
  recommendation = 'Extract shared discriminated union into domain types to eliminate duplicate definitions';
51
51
  } else if (bucket.type === 'UI_STRUCTURE') {
52
- label = `Shared UI Layout Structure (${bucket.detail})`;
53
- suggestedCapsule = 'm-feature-card';
54
- recommendation = 'Extract canonical molecule capsule before slicing consumer monoliths';
52
+ const categorization = categorizeUiStructure(bucket.detail);
53
+ label = categorization.label;
54
+ suggestedCapsule = categorization.suggestedCapsule;
55
+ recommendation = categorization.recommendation;
55
56
  } else if (bucket.type === 'PREDICATE_LOGIC') {
56
57
  label = `Duplicated Boolean Predicate Topology (${bucket.detail})`;
57
58
  suggestedCapsule = 'usePredicateFilter.ts';
@@ -84,6 +85,92 @@ export const createPatternRegistry = () => {
84
85
  return { record, resolveHarmonizationCandidates };
85
86
  };
86
87
 
88
+ export const categorizeUiStructure = (signature = '') => {
89
+ const normalized = signature.toLowerCase();
90
+
91
+ const isSvg =
92
+ normalized.startsWith('svg>') ||
93
+ normalized.includes('path+path') ||
94
+ normalized.includes('polygon') ||
95
+ normalized.includes('circle');
96
+ if (isSvg) {
97
+ const isMultiLayer = (normalized.match(/path|circle|rect|polygon|g/g) || []).length > 3;
98
+ return {
99
+ label: `Shared Vector Glyph (${signature})`,
100
+ suggestedCapsule: isMultiLayer ? 'm-vector-glyph' : 'a-icon',
101
+ recommendation: 'Extract foundational icon atom or vector glyph capsule'
102
+ };
103
+ }
104
+
105
+ const hasButton =
106
+ normalized.includes('btn') ||
107
+ normalized.includes('button');
108
+ if (hasButton) {
109
+ const hasTextOrTitle =
110
+ normalized.includes('span') ||
111
+ normalized.includes('title') ||
112
+ normalized.includes('h1') ||
113
+ normalized.includes('h2') ||
114
+ normalized.includes('h3') ||
115
+ normalized.includes('h4') ||
116
+ normalized.includes('p');
117
+
118
+ if (hasTextOrTitle) {
119
+ return {
120
+ label: `Shared Action Header (${signature})`,
121
+ suggestedCapsule: 'm-action-header',
122
+ recommendation: 'Extract action header molecule capsule to standardize button actions'
123
+ };
124
+ }
125
+
126
+ return {
127
+ label: `Shared Button Row (${signature})`,
128
+ suggestedCapsule: 'm-button-row',
129
+ recommendation: 'Extract button row molecule capsule to consolidate action bar'
130
+ };
131
+ }
132
+
133
+ const hasChipOrBadge =
134
+ normalized.includes('chip') ||
135
+ normalized.includes('badge') ||
136
+ normalized.includes('pill') ||
137
+ normalized.includes('tag');
138
+ if (hasChipOrBadge) {
139
+ const chipCount = (normalized.match(/chip|badge|pill|tag/g) || []).length;
140
+ if (chipCount > 1) {
141
+ return {
142
+ label: `Shared Pill Row (${signature})`,
143
+ suggestedCapsule: 'm-pill-row',
144
+ recommendation: 'Extract pill row molecule capsule to group status indicators'
145
+ };
146
+ }
147
+
148
+ return {
149
+ label: `Shared Status Badge (${signature})`,
150
+ suggestedCapsule: 'm-status-badge',
151
+ recommendation: 'Extract status badge atom or capsule to unify state tags'
152
+ };
153
+ }
154
+
155
+ const hasFormInput =
156
+ normalized.includes('input') ||
157
+ normalized.includes('textarea') ||
158
+ normalized.includes('select');
159
+ if (hasFormInput) {
160
+ return {
161
+ label: `Shared Form Field (${signature})`,
162
+ suggestedCapsule: 'm-form-field',
163
+ recommendation: 'Extract form field molecule encapsulating label and input atom'
164
+ };
165
+ }
166
+
167
+ return {
168
+ label: `Shared UI Layout Structure (${signature})`,
169
+ suggestedCapsule: 'm-feature-card',
170
+ recommendation: 'Extract canonical molecule capsule before slicing consumer monoliths'
171
+ };
172
+ };
173
+
87
174
  const VOID_ELEMENTS = new Set([
88
175
  'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr'
89
176
  ]);
@@ -4,7 +4,8 @@ import {
4
4
  createPatternRegistry,
5
5
  extractTemplateTokens,
6
6
  buildTagTree,
7
- recordTemplatePatterns
7
+ recordTemplatePatterns,
8
+ categorizeUiStructure
8
9
  } from './pattern-detector.js';
9
10
 
10
11
  test('extractTemplateTokens: extracts tags, ignoring comments and handling quotes', () => {
@@ -56,6 +57,40 @@ test('buildTagTree and recordTemplatePatterns: captures UI_STRUCTURE from Vue te
56
57
 
57
58
  assert.ok(uiCandidate, 'Expected UI_STRUCTURE candidate to be found');
58
59
  assert.equal(uiCandidate.detail, 'v-sheet>(x-btn+x-btn)');
60
+ assert.equal(uiCandidate.suggestedCapsule, 'm-button-row');
59
61
  assert.equal(uiCandidate.fileCount, 2);
60
62
  assert.deepEqual(uiCandidate.uniqueFiles.sort(), ['src/components/A.vue', 'src/components/B.vue']);
61
63
  });
64
+
65
+ test('categorizeUiStructure: maps semantic AST topologies to specialized capsules', () => {
66
+ // SVG dual path -> a-icon
67
+ const svgRes = categorizeUiStructure('svg>(path+path)');
68
+ assert.equal(svgRes.suggestedCapsule, 'a-icon');
69
+ assert.ok(svgRes.label.includes('Vector Glyph'));
70
+
71
+ // Action header -> m-action-header
72
+ const actionRes = categorizeUiStructure('div>(span+v-btn)');
73
+ assert.equal(actionRes.suggestedCapsule, 'm-action-header');
74
+ assert.ok(actionRes.label.includes('Action Header'));
75
+
76
+ // Button row -> m-button-row
77
+ const btnRowRes = categorizeUiStructure('div>(v-btn+v-btn)');
78
+ assert.equal(btnRowRes.suggestedCapsule, 'm-button-row');
79
+
80
+ // Status badge -> m-status-badge
81
+ const badgeRes = categorizeUiStructure('div>(span+v-chip)');
82
+ assert.equal(badgeRes.suggestedCapsule, 'm-status-badge');
83
+
84
+ // Pill row -> m-pill-row
85
+ const pillRowRes = categorizeUiStructure('div>(v-chip+v-chip)');
86
+ assert.equal(pillRowRes.suggestedCapsule, 'm-pill-row');
87
+
88
+ // Form field -> m-form-field
89
+ const formRes = categorizeUiStructure('div>(label+input)');
90
+ assert.equal(formRes.suggestedCapsule, 'm-form-field');
91
+
92
+ // Fallback -> m-feature-card
93
+ const fallbackRes = categorizeUiStructure('div>(div+div)');
94
+ assert.equal(fallbackRes.suggestedCapsule, 'm-feature-card');
95
+ });
96
+
@@ -293,13 +293,12 @@ export const buildHotspotsPrompt = (report, options = {}) => {
293
293
  }
294
294
 
295
295
  lines.push('### STRICT EXECUTION RULES:');
296
- lines.push('1. Branch First: Change to a new branch prefixed with `chem-x/NAMEOFIMPROVEMENT` before making changes, then commit the branch and create a PR to main.');
297
- lines.push('2. Pre-Split Pattern Discovery: Audit recurring UI layouts, state machines, and predicates across monoliths first; extract canonical shared capsules before slicing to prevent bespoke pattern duplication.');
298
- lines.push('3. Molecular Limits: Molecule capsules must strictly remain under 100 lines per file.');
299
- lines.push('4. Table-of-Contents Views: Top-level page views must be 10 to 20 line declarative Table of Contents assembling molecules via named slots.');
300
- lines.push('5. Composable Return Contracts: Custom hooks/composables must strictly limit returns to 3 to 5 properties (State + Status + Actions).');
301
- lines.push('6. Type Co-location: Co-locate granular types/*.d.ts files inside each feature capsule (< 100 lines per type file) instead of creating type monoliths.');
302
- lines.push('7. Zero breaking changes to external component APIs, route exports, or existing props.');
296
+ lines.push('1. Pre-Split Pattern Discovery: Audit recurring UI layouts, state machines, and predicates across monoliths first; extract canonical shared capsules before slicing to prevent bespoke pattern duplication.');
297
+ lines.push('2. Molecular Limits: Molecule capsules must strictly remain under 100 lines per file.');
298
+ lines.push('3. Table-of-Contents Views: Top-level page views must be 10 to 20 line declarative Table of Contents assembling molecules via named slots.');
299
+ lines.push('4. Composable Return Contracts: Custom hooks/composables must strictly limit returns to 3 to 5 properties (State + Status + Actions).');
300
+ lines.push('5. Type Co-location: Co-locate granular types/*.d.ts files inside each feature capsule (< 100 lines per type file) instead of creating type monoliths.');
301
+ lines.push('6. Zero breaking changes to external component APIs, route exports, or existing props.');
303
302
  if (!isSubSection) {
304
303
  lines.push('');
305
304
  lines.push(buildAgentCommandsSection());
@@ -340,11 +339,10 @@ export const buildPillarPrompt = (report, pillarName, options = {}) => {
340
339
  }
341
340
 
342
341
  lines.push('### STRICT EXECUTION RULES:');
343
- lines.push('1. Branch First: Change to a new branch prefixed with `chem-x/NAMEOFIMPROVEMENT` before making changes.');
344
- lines.push('2. Pre-Split Pattern Discovery: Survey cross-file patterns before slicing; extract canonical shared capsules first.');
345
- lines.push('3. Molecular Capsule Limit: Maximum 100 lines per molecule capsule file.');
346
- lines.push('4. Table-of-Contents Views: Top-level page views must be 10 to 20 line declarative templates assembling components via named slots.');
347
- lines.push('5. Zero breaking changes to external component APIs, route exports, or existing props.');
342
+ lines.push('1. Pre-Split Pattern Discovery: Survey cross-file patterns before slicing; extract canonical shared capsules first.');
343
+ lines.push('2. Molecular Capsule Limit: Maximum 100 lines per molecule capsule file.');
344
+ lines.push('3. Table-of-Contents Views: Top-level page views must be 10 to 20 line declarative templates assembling components via named slots.');
345
+ lines.push('4. Zero breaking changes to external component APIs, route exports, or existing props.');
348
346
  if (!isSubSection) {
349
347
  lines.push('');
350
348
  lines.push(buildAgentCommandsSection());
@@ -36,31 +36,22 @@ export const groupViolationsBySeverity = (violations) => {
36
36
  };
37
37
 
38
38
  export const getSeverityBadge = (severity) => {
39
- switch (severity) {
40
- case 'CRITICAL':
41
- return `${RED}[CRITICAL]${RESET}`;
42
- case 'HIGH':
43
- return `${ORANGE}[HIGH]${RESET}`;
44
- case 'MEDIUM':
45
- return `${ORANGE}[MEDIUM]${RESET}`;
46
- case 'LOW':
47
- return `${YELLOW}[LOW]${RESET}`;
48
- default:
49
- return `[${severity}]`;
50
- }
39
+ const SEVERITY_BADGES = {
40
+ CRITICAL: `${RED}[CRITICAL]${RESET}`,
41
+ HIGH: `${ORANGE}[HIGH]${RESET}`,
42
+ MEDIUM: `${ORANGE}[MEDIUM]${RESET}`,
43
+ LOW: `${YELLOW}[LOW]${RESET}`
44
+ };
45
+ return SEVERITY_BADGES[severity] || `[${severity}]`;
51
46
  };
52
47
 
53
48
  export const getStatusBadge = (status) => {
54
- switch (status) {
55
- case 'PASSED':
56
- return `${GREEN}✔ PASS${RESET}`;
57
- case 'WARN':
58
- return `${YELLOW}⚠ WARN${RESET}`;
59
- case 'FAILED':
60
- return `${RED}✕ FAIL${RESET}`;
61
- default:
62
- return status;
63
- }
49
+ const STATUS_BADGES = {
50
+ PASSED: `${GREEN}✔ PASS${RESET}`,
51
+ WARN: `${YELLOW}⚠ WARN${RESET}`,
52
+ FAILED: `${RED}✕ FAIL${RESET}`
53
+ };
54
+ return STATUS_BADGES[status] || status;
64
55
  };
65
56
 
66
57
  export const resolveIndividualPillarGrade = (pillarData) => {
@@ -128,12 +128,6 @@ export const buildRemediationRoadmap = (report) => {
128
128
  target: '.git/hooks/pre-commit & .github/workflows/',
129
129
  action: 'Run npx chemx install to lock in line budgets and prevent future monolith regressions.',
130
130
  locations: []
131
- },
132
- {
133
- title: 'Branch, Commit & Pull Request Workflow',
134
- target: 'git checkout -b chem-x/NAMEOFIMPROVEMENT',
135
- action: 'Change to a branch prefixed with chem-x/NAMEOFIMPROVEMENT to make changes, commit the branch, and create a PR to main.',
136
- locations: []
137
131
  }
138
132
  ]
139
133
  });
@@ -202,13 +196,6 @@ export const buildSelfHealingRoadmapPrompt = (report) => {
202
196
 
203
197
  lines.push('Act as a Principal Systems Architect. Execute a self-healing architectural remediation of our codebase by following this strict multi-phase sequence in order:\n');
204
198
 
205
- lines.push('### MANDATORY GIT BRANCHING & PR WORKFLOW:');
206
- lines.push('1. Before modifying any files, change to a dedicated feature branch prefixed with `chem-x/NAMEOFIMPROVEMENT`:');
207
- lines.push(' `git checkout -b chem-x/NAMEOFIMPROVEMENT`');
208
- lines.push('2. Execute each remediation phase sequentially on this branch.');
209
- lines.push('3. Commit the branch with atomic, intention-revealing commit messages.');
210
- lines.push('4. Create a Pull Request (PR) to `main` upon completing the remediation phases.\n');
211
-
212
199
  lines.push('### AI AGENT DISCOVERY & REFACTORING COMMANDS:');
213
200
  lines.push('- Discovery & Inspect: Run `pnpm q "<target>" --inspect` (or `npx chemx search "<target>" --inspect`) to inspect component props and hooks before editing.');
214
201
  lines.push('- Tier Filter: Run `pnpm q "<query>" --tier=molecule` (or `atom`, `organism`, `hook`) to find related capsules.');
@@ -234,13 +221,11 @@ export const buildSelfHealingRoadmapPrompt = (report) => {
234
221
  });
235
222
 
236
223
  lines.push('### EXECUTION DISCIPLINE:');
237
- lines.push('1. Branch First: Change to a new branch prefixed with `chem-x/NAMEOFIMPROVEMENT` (e.g. `git checkout -b chem-x/<name-of-improvement>`) before making any changes.');
238
- lines.push('2. Complete Step 1 (Pattern Harvesting) completely before touching any monolithic file in Step 3.');
239
- lines.push('3. Spliced views in Step 3 must directly import and bind to the canonical capsules created in Step 1.');
240
- lines.push('4. Every new molecule component must stay under 100 lines.');
241
- lines.push('5. Top-level page views must be 10-20 line declarative Table-of-Contents templates assembling molecules via named slots.');
242
- lines.push('6. Zero synthetic or mock data; zero em dashes (use hyphens or colons).');
243
- lines.push('7. PR to Main: Commit the branch and create a Pull Request to main.');
224
+ lines.push('1. Complete Step 1 (Pattern Harvesting) completely before touching any monolithic file in Step 3.');
225
+ lines.push('2. Spliced views in Step 3 must directly import and bind to the canonical capsules created in Step 1.');
226
+ lines.push('3. Every new molecule component must stay under 100 lines.');
227
+ lines.push('4. Top-level page views must be 10-20 line declarative Table-of-Contents templates assembling molecules via named slots.');
228
+ lines.push('5. Zero synthetic or mock data; zero em dashes (use hyphens or colons).');
244
229
 
245
230
  return lines.join('\n');
246
231
  };
@@ -8,7 +8,7 @@ const COMPONENT_EXTENSIONS = new Set(['.vue', '.tsx', '.jsx']);
8
8
  const TEMPLATE_EXTENSIONS = new Set(['.vue', '.html', '.svelte']);
9
9
  const COMMENT_PREFIXES = ['//', '*', '/*'];
10
10
  const CONSOLE_LOG_METHODS = ['log', 'info', 'warn'];
11
- const NON_INTERACTIVE_FLAGS = ['--non-interactive', '--no-interactive', '--ci'];
11
+ const NON_INTERACTIVE_FLAGS = ['--non-interactive', '--no-interactive', '--ci', '--headless', '--yes', '-y'];
12
12
  const FORMAT_FLAGS = ['--markdown', '--md', '--json'];
13
13
  const GRADE_RANKS = { 'A+': 5, 'A': 4, 'B': 3, 'C': 2, 'D': 1, 'F': 0 };
14
14
 
@@ -135,5 +135,6 @@ export const isNonInteractiveSession = (rawArgs, env = process.env) => {
135
135
  const hasOutputFlag = rawArgs.some((arg) => arg.startsWith('--output=') || arg.startsWith('-o='));
136
136
  const hasFormatFlag = FORMAT_FLAGS.some((flag) => rawArgs.includes(flag));
137
137
  const hasCiEnv = Boolean(env.CI || env.GIT_DIR);
138
- return [hasCliFlag, hasOutputFlag, hasFormatFlag, hasCiEnv].some(Boolean);
138
+ const isNotTTY = Boolean(process.stdout && process.stdout.isTTY === false);
139
+ return [hasCliFlag, hasOutputFlag, hasFormatFlag, hasCiEnv, isNotTTY].some(Boolean);
139
140
  };
@@ -36,6 +36,26 @@ export const RULE_REGISTRY = {
36
36
  severity: 'CRITICAL',
37
37
  directive: 'Extract hardcoded API keys and secrets into environment variables or secrets manager'
38
38
  },
39
+ SECURITY_REVERSE_TABNABBING: {
40
+ pillar: PILLARS.PILLAR_9,
41
+ severity: 'MEDIUM',
42
+ directive: 'Add rel="noopener noreferrer" to external links with target="_blank" to prevent tab hijacking'
43
+ },
44
+ SECURITY_JAVASCRIPT_URL: {
45
+ pillar: PILLARS.PILLAR_9,
46
+ severity: 'CRITICAL',
47
+ directive: 'Do not use javascript: pseudo-protocol in links or action attributes; use event handlers'
48
+ },
49
+ SECURITY_DYNAMIC_CODE_EXECUTION: {
50
+ pillar: PILLARS.PILLAR_9,
51
+ severity: 'CRITICAL',
52
+ directive: 'Avoid eval(), new Function(), and string-based setTimeout/setInterval code execution'
53
+ },
54
+ SECURITY_SENSITIVE_LOGGING: {
55
+ pillar: PILLARS.PILLAR_9,
56
+ severity: 'HIGH',
57
+ directive: 'Never log sensitive credentials, tokens, or passwords to console diagnostics'
58
+ },
39
59
 
40
60
  // Pillar 10: Testing Discipline
41
61
  TEST_FAKE_GREEN: {
@@ -86,6 +106,11 @@ export const RULE_REGISTRY = {
86
106
  severity: 'CRITICAL',
87
107
  directive: 'Extract display states into computed descriptor objects or early returns'
88
108
  },
109
+ CONTROL_FLOW_DISPATCH_SWITCH: {
110
+ pillar: PILLARS.PILLAR_2,
111
+ severity: 'MEDIUM',
112
+ directive: 'Replace repetitive dispatch switch with an O(1) keyed dictionary or method map (Directive 3.E)'
113
+ },
89
114
  HOOK_SATURATION: {
90
115
  pillar: PILLARS.PILLAR_3,
91
116
  severity: 'HIGH',
@@ -102,6 +102,10 @@ export const auditCode = (content, filePath, relativePath, options = {}) => {
102
102
  // Pillars 8-11: Extended Fast Checks (A11y, Security secrets, Fake tests, Co-located specs)
103
103
  checkExtendedTextPatterns(content, lines, relativePath, filePath, violations);
104
104
 
105
+ if (options.fast) {
106
+ return violations;
107
+ }
108
+
105
109
  // Vue template structural pattern discovery
106
110
  if (ext === '.vue' && options.patternRegistry) {
107
111
  const templateMatch = content.match(/<template\b[^>]*>([\s\S]*?)<\/template>/i);