@chemx/create-chemx 26.9.17-309 → 26.9.20-228

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 (98) hide show
  1. package/README.md +177 -2
  2. package/blueprints/molecule-capsule/m-tab-button/m-tab-button.controller.ts +8 -11
  3. package/cli/audit/ast-visitors.js +73 -0
  4. package/cli/audit/extended-text-patterns.js +218 -0
  5. package/cli/audit/extended-visitors.js +200 -171
  6. package/cli/audit/reporter-utils.js +13 -22
  7. package/cli/audit/rules-registry.js +25 -0
  8. package/cli/audit/rules.spec.js +205 -0
  9. package/cli/audit.js +1 -0
  10. package/cli/build/detector.js +40 -23
  11. package/cli/build/detector.spec.js +81 -0
  12. package/cli/build.d.ts +2 -1
  13. package/cli/build.js +37 -12
  14. package/cli/columnar.d.ts +14 -0
  15. package/cli/columnar.js +52 -0
  16. package/cli/columnar.spec.js +49 -0
  17. package/cli/errors/catcher.js +80 -0
  18. package/cli/errors/errors.spec.js +163 -0
  19. package/cli/errors/formatter.js +94 -0
  20. package/cli/errors/index.js +52 -0
  21. package/cli/errors/publisher.js +76 -0
  22. package/cli/errors/sanitizer.js +50 -0
  23. package/cli/errors/storage.js +39 -0
  24. package/cli/errors/types.d.ts +42 -0
  25. package/cli/help.js +29 -4
  26. package/cli/index.js +84 -9
  27. package/cli/installer-templates.js +1 -1
  28. package/cli/installer.d.ts +2 -0
  29. package/cli/installer.js +51 -22
  30. package/cli/installer.spec.js +72 -0
  31. package/cli/mcp/antigravity.js +78 -0
  32. package/cli/mcp/index.js +2 -1
  33. package/cli/mcp/installer.js +34 -9
  34. package/cli/mcp/installer.spec.js +24 -1
  35. package/cli/mcp/manifests.js +399 -0
  36. package/cli/mcp/prompts.js +28 -0
  37. package/cli/mcp/resources.js +94 -90
  38. package/cli/mcp/server.js +55 -6
  39. package/cli/mcp/server.spec.js +315 -10
  40. package/cli/mcp/tools-search.js +237 -0
  41. package/cli/mcp/tools-team.js +145 -0
  42. package/cli/mcp/tools.js +216 -191
  43. package/cli/mcp/types.d.ts +37 -0
  44. package/cli/navigator-actions.js +23 -10
  45. package/cli/navigator-banner-helpers.js +33 -0
  46. package/cli/navigator-banner.js +19 -38
  47. package/cli/navigator-banner.spec.js +68 -0
  48. package/cli/navigator-guide.js +73 -0
  49. package/cli/navigator-menu.js +20 -0
  50. package/cli/navigator.js +1 -0
  51. package/cli/patcher.d.ts +73 -0
  52. package/cli/patcher.js +249 -4
  53. package/cli/patcher.spec.js +148 -0
  54. package/cli/reader.js +65 -5
  55. package/cli/scaffold.js +7 -1
  56. package/cli/search-commands.js +10 -8
  57. package/cli/search-queries.js +8 -3
  58. package/cli/search-schema.js +5 -0
  59. package/cli/search.d.ts +16 -0
  60. package/cli/search.js +103 -11
  61. package/cli/silence-warnings.js +17 -0
  62. package/cli/team/index.js +30 -0
  63. package/cli/team/team-adversarial-locks.spec.js +105 -0
  64. package/cli/team/team-adversarial-telemetry.spec.js +93 -0
  65. package/cli/team/team-commands.js +199 -0
  66. package/cli/team/team-db-agents.js +79 -0
  67. package/cli/team/team-db-feed.js +84 -0
  68. package/cli/team/team-db-lock-promotion.js +91 -0
  69. package/cli/team/team-db-locks.js +94 -0
  70. package/cli/team/team-db-task-helpers.js +94 -0
  71. package/cli/team/team-db-tasks.js +90 -0
  72. package/cli/team/team-db.js +93 -0
  73. package/cli/team/team-format.js +80 -0
  74. package/cli/team/team-lock-concurrency.spec.js +182 -0
  75. package/cli/team/team-schema.js +74 -0
  76. package/cli/team/team-telemetry.js +104 -0
  77. package/cli/team/team-triage.js +97 -0
  78. package/cli/team/team.spec.js +344 -0
  79. package/cli/ui-actions-helpers.js +66 -0
  80. package/cli/ui-actions.js +85 -0
  81. package/cli/ui-attention.js +74 -0
  82. package/cli/ui-client-api.js +49 -0
  83. package/cli/ui-client-cert.js +27 -0
  84. package/cli/ui-client-script.js +83 -0
  85. package/cli/ui-db-studio.js +79 -0
  86. package/cli/ui-handlers.js +95 -0
  87. package/cli/ui-html.js +35 -0
  88. package/cli/ui-server-routes.js +63 -0
  89. package/cli/ui-server.js +75 -0
  90. package/cli/ui-styles.js +34 -0
  91. package/cli/ui-template-views.js +63 -0
  92. package/cli/ui-template.js +91 -0
  93. package/cli/ui.spec.js +305 -0
  94. package/cli/verify.js +367 -0
  95. package/cli/verify.spec.js +98 -0
  96. package/package.json +11 -14
  97. package/scripts/pre-commit.sh +1 -1
  98. package/cli/mcp.js +0 -326
package/README.md CHANGED
@@ -31,15 +31,190 @@ starter-kit/
31
31
  │ ├── useSelfCleaningTimer.ts # Unmount-safe timer and RAF hook
32
32
  │ └── useTwoStageDecision.ts # Concept to Decision composition
33
33
  └── cli/
34
- └── index.js # Interactive capsule generator
34
+ ├── index.js # CLI router & capsule generator
35
+ ├── verify.js # Zero-token-burn verification engine
36
+ ├── audit.js # 7-Pillar static AST audit
37
+ └── mcp/ # Model Context Protocol stdio server
35
38
  ```
36
39
 
40
+ ---
41
+
42
+ ## Zero-Token-Burn Verification Pipeline
43
+
44
+ Running raw, unthrottled `npm test`, `tsc --noEmit`, or build tools dumps thousands of lines of passing checkmarks, compiler noise, and bundle asset tables into an AI agent's context window. This burns tokens prematurely and degrades context quality.
45
+
46
+ Chemical X wraps tests, typechecks, and builds into silent, failure-focused verification tools available via CLI and native MCP tools:
47
+
48
+ ### Side-by-Side Benchmark Reports
49
+
50
+ #### 1. Test Suite: `npm test` vs `chemx test`
51
+
52
+ Ran across the starter-kit test suite (143 test cases across 8 spec suites):
53
+
54
+ | Metric | Raw `npm test` | Chemical X `chemx test` | Improvement |
55
+ | :--- | :--- | :--- | :--- |
56
+ | **Execution Time** | **~3.1s - 7.5s** | **~3.5s - 3.7s** | Identical underlying test speed |
57
+ | **Output Lines** | **957 lines** | **1 line** | **99.9% line reduction** |
58
+ | **Characters Streamed** | **49,246 characters** | **44 characters** | **99.9% character reduction** |
59
+ | **Agent Token Cost** | **~12,960 tokens** | **~12 tokens** | **12,948 tokens saved (99.9%)** |
60
+
61
+ ```
62
+ # Raw npm test: Floods context with ~13,000 tokens of passing checkmarks
63
+ ✔ resolveTargetDir: returns custom directory if provided as first argument (1.64ms)
64
+ ✔ search-db: indexes symbols with line ranges and finds definition (56.65ms)
65
+ ✔ Verify: parseTestOutput strips passing checkmarks and extracts only failing tests (1.01ms)
66
+ ... [940 MORE LINES OF PASSING CHECKMARKS & TIMINGS] ...
67
+ ℹ tests 143 | pass 143 | fail 0 | duration_ms 3002.57ms
68
+
69
+ # Chemical X: Pinpoint single-line summary
70
+ $ npx chemx test
71
+ ✔ All tests passed (143 tests in 3500ms)
72
+ ```
73
+
74
+ #### 2. TypeScript Typecheck: `npm run typecheck` vs `chemx typecheck`
75
+
76
+ | Metric | Raw `npm run typecheck` | Chemical X `chemx typecheck` | Difference |
77
+ | :--- | :--- | :--- | :--- |
78
+ | **Execution Time** | **1.85s** | **2.06s** | **+0.21s** (lightweight wrapper) |
79
+ | **Output Characters** | 54 characters | **40 characters** | **26% reduction** |
80
+ | **Context Token Cost** | ~14 tokens | **~10 tokens** | **~28% reduction** |
81
+ | **Clean Output** | npm script banner noise | Single clean status line | Zero CLI banner noise |
82
+ | **JSON Mode (`--json`)** | Not supported | **~30 tokens** structured JSON | Direct agent ingestion |
83
+
84
+ ```bash
85
+ # Clean, banner-free terminal verification
86
+ $ npx chemx typecheck
87
+ ✔ TypeScript typecheck clean (1820ms)
88
+
89
+ # Machine-readable output for AI agents
90
+ $ npx chemx typecheck --json
91
+ {
92
+ "success": true,
93
+ "exitCode": 0,
94
+ "command": "npm run typecheck",
95
+ "durationMs": 1820,
96
+ "errorCount": 0,
97
+ "errors": []
98
+ }
99
+ ```
100
+
101
+ #### 3. Production Build: Raw Build vs `chemx build`
102
+
103
+ | Metric | Raw Production Build | Chemical X `chemx build` | Chemical X `--silent` |
104
+ | :--- | :--- | :--- | :--- |
105
+ | **Terminal Output** | Multi-page asset size tables & chunks | Single status line | **0 lines (completely silent)** |
106
+ | **Token Cost (Success)** | **~2,000 - 8,000 tokens** | **~15 tokens** | **0 tokens** |
107
+ | **Failure Diagnosis** | Scatted across hundreds of lines | Categorized diagnostic buckets | Exact failure lines only |
108
+
109
+ ```bash
110
+ $ npx chemx build
111
+ Auditing build: npx tsc --noEmit
112
+ ✔ Build Succeeded (5.20s)
113
+ ```
114
+
115
+ #### 4. Full Pipeline: `chemx verify` Grand Total
116
+
117
+ When an AI agent runs a complete pre-commit or post-refactor verification pass:
118
+
119
+ | Verification Stage | Raw Unthrottled Shell Commands | Chemical X `chemx verify` |
120
+ | :--- | :--- | :--- |
121
+ | **7-Pillar AST Audit** | ~4,000 tokens | Included |
122
+ | **TypeScript Compilation** | ~14 tokens | Included |
123
+ | **Project Test Suite** | ~12,960 tokens | Included |
124
+ | **Total Context Burn** | **~17,000+ tokens** | **~45 tokens** |
125
+ | **Context Savings** | Baseline | **99.7% Token Reduction** |
126
+
127
+ ```
128
+ $ npx chemx verify --dir=blueprints
129
+
130
+ ⚡ Chemical X: Token-Conserving Project Verification
131
+
132
+ ✔ AST Architecture: A+ (100/100, 0 violations)
133
+ ✔ TypeScript: Clean (0 errors)
134
+ ✔ Test Suite: Passed (143/143)
135
+
136
+ All verification checks passed with zero context burn!
137
+ ```
138
+
139
+ ---
140
+
141
+ ## Model Context Protocol (MCP) Server
142
+
143
+ Chemical X includes a high-performance, zero-dependency JSON-RPC 2.0 Stdio MCP server that connects directly to AI agent hosts (Cursor, Claude Desktop, Windsurf, Antigravity, VS Code).
144
+
145
+ ### Automatic 1-Step Installation
146
+
147
+ Run the interactive installer in your workspace root:
148
+
149
+ ```bash
150
+ npx chemx install-mcp
151
+ ```
152
+
153
+ This automatically registers the Chemical X server in:
154
+ - `.cursor/mcp.json` (Cursor IDE)
155
+ - `.vscode/mcp.json` (VS Code)
156
+ - `~/.gemini/config/mcp_config.json` (Antigravity)
157
+ - Injects `chemx:mcp`, `chemx:verify`, `chemx:test`, and `chemx:typecheck` into your `package.json` scripts
158
+
159
+ ### Manual MCP Server Configuration
160
+
161
+ To configure manually in your MCP client settings (e.g. `claude_desktop_config.json` or `.cursor/mcp.json`):
162
+
163
+ ```json
164
+ {
165
+ "mcpServers": {
166
+ "chemical-x": {
167
+ "command": "npx",
168
+ "args": ["-y", "chemx", "mcp"]
169
+ }
170
+ }
171
+ }
172
+ ```
173
+
174
+ ### Available MCP Tools (14 Tools)
175
+
176
+ | Tool Name | Scope | Purpose |
177
+ | :--- | :--- | :--- |
178
+ | `chemx_verify` | Verification | Full pipeline gatekeeper: AST Audit + Typecheck + Tests into a ~45-token card. |
179
+ | `chemx_typecheck` | Verification | Silent TypeScript typecheck audit. Drops compiler noise; returns structured diagnostics on error. |
180
+ | `chemx_test` | Verification | Silent project test runner. Suppresses passing checkmarks; returns ONLY failing test assertions. |
181
+ | `chemx_audit_build` | Verification | Wrap build commands with silent execution and catalog compiler diagnostics into structured categories. |
182
+ | `chemx_audit` | Quality | Run the full 7-Pillar Chemical X static AST audit. Returns health score, grade (A+ to F), and hazard list. |
183
+ | `chemx_check` | Quality | Verify a single file or capsule against molecular boundary rules (< 100L, 2-stage booleans, zero raw DOM). |
184
+ | `chemx_q` | Discovery | AST search index query machine. Query symbols, capsules, props, and hooks with minimal token burn. |
185
+ | `chemx_query_patterns` | Discovery | Detect duplicated state machines, cloned UI layouts, and parallel hooks before decomposing monoliths. |
186
+ | `chemx_read` | Reading | Token-minified file reader. Extracts AST outlines, stripped comments, or symbol blocks (80%+ token savings). |
187
+ | `chemx_patch` | Editing | Surgically patch files with exact search and replace blocks without whole-file context dumps. |
188
+ | `chemx_write` | Editing | Create or overwrite files with automatic SQLite AST indexing and boundary compliance checks. |
189
+ | `chemx_autofix` | Remediation | Deterministically remediate safe violations (typography hyphens, markdown fences, AI slop comments). |
190
+ | `chemx_generate_capsule` | Scaffolding | Deterministically generate a crystalline capsule directory (component, controller, SCSS, types, index). |
191
+ | `chemx_get_refactor_prompt` | Prompting | Synthesize targeted refactoring prompts for Grade F critical hazards, hotspots, and slop artifacts. |
192
+
193
+ ### Living Resources & Prompts
194
+
195
+ * **Resources**:
196
+ * `chemx://directives`: Mandatory Chemical X Molecular Architecture directives (AGENTS.md).
197
+ * `chemx://scorecard`: Live Molecular Health Index (MHI) grade, score, metrics, and active hazards.
198
+ * `chemx://blueprints/molecule`: Compliant molecule blueprint adhering to zero raw DOM standards.
199
+ * `chemx://blueprints/view-template`: Declarative 10-20 line Table-of-Contents view blueprint.
200
+ * **Prompts**:
201
+ * `chemx_remediate_hotspot`: Dynamically inspects candidate file AST metrics and generates targeted decomposition instructions.
202
+ * `chemx_harmonize_patterns`: Generates Pre-Split Pattern Discovery instructions to extract shared capsules.
203
+
204
+ ---
205
+
37
206
  ## Release Versioning: Minute-Precision CalVer
38
207
 
39
208
  This package adheres to **Minute-Precision Calendar Versioning** (`YY.MM.DD-MMMM`):
40
- - `YY.MM.DD`: Release date (e.g. `26.9.14` for Sept 14, 2026).
209
+ - `YY.MM.DD`: Release date (e.g. `26.9.17` for Sept 17, 2026).
41
210
  - `MMMM`: Minute of the day (0 to 1439).
42
211
 
212
+ Check installed version at any time:
213
+ ```bash
214
+ npx chemx --version
215
+ # create-chemx v26.9.17-606
216
+ ```
217
+
43
218
  Because autonomous coding agent models update frequently, this package is continuously integrated and deployed via automated CI whenever new agent directives, AST checks, or framework rules are tuned. Rapid version iterations reflect active, daily alignment rather than breaking SemVer shifts.
44
219
 
45
220
  > [!NOTE]
@@ -1,21 +1,18 @@
1
1
  import { computed } from 'vue';
2
2
  import type { TabButtonProps, TabTone } from './types';
3
3
 
4
+ const TONE_CLASSES: Record<TabTone, string> = {
5
+ lime: 'm-tab-button--lime',
6
+ pink: 'm-tab-button--pink',
7
+ slate: 'm-tab-button--slate',
8
+ sky: 'm-tab-button--sky'
9
+ };
10
+
4
11
  export function useTabButtonController(props: TabButtonProps, emit: (event: 'click' | 'select') => void) {
5
12
  const isSelected = computed((): boolean => Boolean(props.isActive));
6
13
 
7
14
  const resolveToneClass = (tone: TabTone = 'sky'): string => {
8
- switch (tone) {
9
- case 'lime':
10
- return 'm-tab-button--lime';
11
- case 'pink':
12
- return 'm-tab-button--pink';
13
- case 'slate':
14
- return 'm-tab-button--slate';
15
- case 'sky':
16
- default:
17
- return 'm-tab-button--sky';
18
- }
15
+ return TONE_CLASSES[tone] || TONE_CLASSES.sky;
19
16
  };
20
17
 
21
18
  const stateClass = computed((): string => {
@@ -89,6 +89,29 @@ export const createAstVisitors = ({ relativePath, violations }) => {
89
89
  }
90
90
  },
91
91
 
92
+ IfStatement(astPath) {
93
+ const expr = astPath.node.test;
94
+ if (t.isLogicalExpression(expr)) {
95
+ const opCount = countLogicalOperators(expr);
96
+ const hasBinaryClauses = (t.isBinaryExpression(expr.left) || t.isLogicalExpression(expr.left)) &&
97
+ (t.isBinaryExpression(expr.right) || t.isLogicalExpression(expr.right));
98
+ if (opCount > 2 || (opCount >= 2 && hasBinaryClauses)) {
99
+ const line = resolveStartLine(expr, astPath.node, 1);
100
+ const meta = RULE_REGISTRY.CONTROL_FLOW_INLINE_BOOLEAN;
101
+ violations.push({
102
+ filePath: relativePath,
103
+ line,
104
+ column: expr.loc?.start.column || 1,
105
+ hazard: `Inline multi-clause boolean comparison in if statement (${opCount} operators). Decompose into 2-stage booleans per Directive 3.A.`,
106
+ rule: 'CONTROL_FLOW_INLINE_BOOLEAN',
107
+ severity: meta.severity,
108
+ pillar: meta.pillar,
109
+ directive: meta.directive
110
+ });
111
+ }
112
+ }
113
+ },
114
+
92
115
  ConditionalExpression(astPath) {
93
116
  if (t.isConditionalExpression(astPath.node.consequent) || t.isConditionalExpression(astPath.node.alternate)) {
94
117
  const line = astPath.node.loc?.start.line || 1;
@@ -106,6 +129,56 @@ export const createAstVisitors = ({ relativePath, violations }) => {
106
129
  }
107
130
  },
108
131
 
132
+ SwitchStatement(astPath) {
133
+ const cases = astPath.node.cases || [];
134
+ if (cases.length < 3) return;
135
+
136
+ const isDispatchConsequent = (statements) => {
137
+ if (!statements || statements.length === 0) return false;
138
+ if (statements.length === 1) {
139
+ const s = statements[0];
140
+ if (t.isReturnStatement(s)) return true;
141
+ if (t.isBlockStatement(s)) {
142
+ return s.body.length === 1 && t.isReturnStatement(s.body[0]);
143
+ }
144
+ }
145
+ if (statements.length === 2) {
146
+ const [first, second] = statements;
147
+ if (t.isBreakStatement(second)) {
148
+ return t.isExpressionStatement(first) || t.isAssignmentExpression(first);
149
+ }
150
+ }
151
+ return false;
152
+ };
153
+
154
+ let dispatchCaseCount = 0;
155
+ let nonDefaultCount = 0;
156
+
157
+ for (const switchCase of cases) {
158
+ if (!switchCase.test) continue;
159
+ nonDefaultCount += 1;
160
+ if (isDispatchConsequent(switchCase.consequent)) {
161
+ dispatchCaseCount += 1;
162
+ }
163
+ }
164
+
165
+ const isDispatchSmell = nonDefaultCount >= 3 && (dispatchCaseCount / nonDefaultCount) >= 0.7;
166
+ if (isDispatchSmell) {
167
+ const line = astPath.node.loc?.start.line || 1;
168
+ const meta = RULE_REGISTRY.CONTROL_FLOW_DISPATCH_SWITCH;
169
+ violations.push({
170
+ filePath: relativePath,
171
+ line,
172
+ column: astPath.node.loc?.start.column || 1,
173
+ hazard: `Dispatch switch smell detected (${dispatchCaseCount} repetitive case branches)`,
174
+ rule: 'CONTROL_FLOW_DISPATCH_SWITCH',
175
+ severity: meta.severity,
176
+ pillar: meta.pillar,
177
+ directive: meta.directive
178
+ });
179
+ }
180
+ },
181
+
109
182
  // Pillar 5: Design System & Inline Styles
110
183
  JSXAttribute(astPath) {
111
184
  const attrName = astPath.node.name?.name;
@@ -0,0 +1,218 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { RULE_REGISTRY } from './rules-registry.js';
4
+ import { toResultSync } from './rules-helpers.js';
5
+ import {
6
+ isComponentExtension,
7
+ isTemplateExtension,
8
+ isCommentLine
9
+ } from './rules-predicates.js';
10
+
11
+ const SECRET_PATTERNS = [
12
+ /(['"])(?:sk-[a-zA-Z0-9_-]{24,}|ghp_[a-zA-Z0-9]{30,}|AKIA[0-9A-Z]{16}|AIza[0-9A-Za-z-_]{35})\1/
13
+ ];
14
+
15
+ const FAKE_GREEN_PATTERNS = [
16
+ /expect\s*\(\s*(true|false|1|0)\s*\)\s*\.\s*to(?:Be|Equal|StrictEqual)\s*\(\s*\1\s*\)/,
17
+ /assert\s*\.\s*(?:strictEqual|equal|deepEqual)\s*\(\s*(true|false|1|0)\s*,\s*\1\s*\)/,
18
+ /assert\s*\.\s*ok\s*\(\s*(?:true|1)\s*\)/
19
+ ];
20
+
21
+ const IMG_WITHOUT_ALT_PATTERN = new RegExp(['<', 'img\\b', '(?![^>]*\\balt\\s*=)', '[^>]*>'].join(''), 'i');
22
+ const CLICKABLE_CONTAINER_PATTERN = new RegExp(['<', '(div|span)\\b', '(?![^>]*\\brole\\s*=\\s*[\'"](?:button|link|tab)[\'"])', '[^>]*\\b(?:@click|v-on:click)\\s*='].join(''), 'i');
23
+ const TABNABBING_TEMPLATE_PATTERN = /<a\b[^>]*\btarget\s*=\s*["']_blank["'][^>]*>/i;
24
+ const JAVASCRIPT_URL_TEMPLATE_PATTERN = /\b(?:href|src|action|formaction)\s*=\s*["']\s*javascript:/i;
25
+
26
+ /**
27
+ * Pillar 10: Check for co-located test files for molecule capsules.
28
+ */
29
+ export const checkMoleculeCoLocatedTest = (filePath, relativePath, violations) => {
30
+ const baseName = path.basename(filePath);
31
+ const ext = path.extname(filePath);
32
+
33
+ // Stage 1: Atomic Concept Declarations
34
+ const isMoleculePath = relativePath.includes('molecules') || baseName.startsWith('m-');
35
+ const isComponentExt = isComponentExtension(ext);
36
+ const isTestOrSpecFile = baseName.includes('.spec.') || baseName.includes('.test.');
37
+
38
+ // Stage 2: Unified Decision Variable & Early Guard Clause
39
+ const isCandidateComponent = isMoleculePath && isComponentExt;
40
+ const isMoleculeComponent = isCandidateComponent && !isTestOrSpecFile;
41
+ if (!isMoleculeComponent) return;
42
+
43
+ const dir = path.dirname(filePath);
44
+ const [siblings, dirError] = toResultSync(() => (fs.existsSync(dir) ? fs.readdirSync(dir) : []));
45
+
46
+ // Stage 1: Atomic Guard Check
47
+ const hasDirError = Boolean(dirError);
48
+ const hasSiblings = Boolean(siblings);
49
+ const canInspectSiblings = !hasDirError && hasSiblings;
50
+ if (!canInspectSiblings) return;
51
+
52
+ const isTestSibling = (fileName) => /\.(test|spec)\.[jt]sx?$/.test(fileName);
53
+ const hasCoLocatedTest = siblings.some(isTestSibling);
54
+ if (hasCoLocatedTest) return;
55
+
56
+ const meta = RULE_REGISTRY.TEST_MISSING_COLOCATED;
57
+ violations.push({
58
+ filePath: relativePath,
59
+ line: 1,
60
+ column: 1,
61
+ hazard: 'Molecule capsule missing co-located test file (*.spec.ts or *.test.ts)',
62
+ rule: 'TEST_MISSING_COLOCATED',
63
+ severity: meta.severity,
64
+ pillar: meta.pillar,
65
+ directive: meta.directive
66
+ });
67
+ };
68
+
69
+ export const checkExtendedTextPatterns = (content, lines, relativePath, filePath, violations) => {
70
+ checkMoleculeCoLocatedTest(filePath, relativePath, violations);
71
+
72
+ const isTestFile = /\.(test|spec)\.[jt]sx?$/.test(filePath);
73
+ const ext = path.extname(filePath);
74
+ const isTemplateFile = isTemplateExtension(ext);
75
+
76
+ lines.forEach((lineText, idx) => {
77
+ const lineNum = idx + 1;
78
+ const trimmed = lineText.trim();
79
+ const isComment = isCommentLine(trimmed);
80
+
81
+ // Pillar 9: Hardcoded Secret Detection
82
+ const hasEnvReference = trimmed.includes('process.env') || trimmed.includes('import.meta.env');
83
+ const canScanSecrets = !isComment && !hasEnvReference;
84
+
85
+ if (canScanSecrets) {
86
+ for (const pat of SECRET_PATTERNS) {
87
+ const matchesSecretPattern = pat.test(lineText);
88
+ const hasPlaceholder = lineText.includes('dummy') || lineText.includes('placeholder');
89
+ const isHardcodedSecret = matchesSecretPattern && !hasPlaceholder;
90
+
91
+ if (isHardcodedSecret) {
92
+ const meta = RULE_REGISTRY.SECURITY_HARDCODED_SECRET;
93
+ violations.push({
94
+ filePath: relativePath,
95
+ line: lineNum,
96
+ column: 1,
97
+ hazard: 'High-entropy API key or secret token detected in source code',
98
+ rule: 'SECURITY_HARDCODED_SECRET',
99
+ severity: meta.severity,
100
+ pillar: meta.pillar,
101
+ directive: meta.directive
102
+ });
103
+ break;
104
+ }
105
+ }
106
+ }
107
+
108
+ // Pillar 9: Unsanitized v-html injection (Vue / HTML templates)
109
+ const hasVHtml = isTemplateFile && /\bv-html\s*=\s*["']/.test(lineText);
110
+ if (hasVHtml) {
111
+ const hasSanitizer = /(?:sanitize|DOMPurify|escapeHtml|filterXSS)/i.test(lineText);
112
+ const isUnsanitizedVHtml = !hasSanitizer;
113
+
114
+ if (isUnsanitizedVHtml) {
115
+ const meta = RULE_REGISTRY.SECURITY_RAW_HTML_INJECTION;
116
+ violations.push({
117
+ filePath: relativePath,
118
+ line: lineNum,
119
+ column: 1,
120
+ hazard: 'Unsanitized HTML injection via v-html detected without DOMPurify',
121
+ rule: 'SECURITY_RAW_HTML_INJECTION',
122
+ severity: meta.severity,
123
+ pillar: meta.pillar,
124
+ directive: meta.directive
125
+ });
126
+ }
127
+ }
128
+
129
+ // Pillar 9: Reverse Tabnabbing in HTML / Vue templates
130
+ const hasTargetBlank = isTemplateFile && TABNABBING_TEMPLATE_PATTERN.test(lineText);
131
+ if (hasTargetBlank) {
132
+ const hasRelProtection = /\brel\s*=\s*["'][^"']*(?:noopener|noreferrer)[^"']*["']/i.test(lineText);
133
+ if (!hasRelProtection) {
134
+ const meta = RULE_REGISTRY.SECURITY_REVERSE_TABNABBING;
135
+ violations.push({
136
+ filePath: relativePath,
137
+ line: lineNum,
138
+ column: 1,
139
+ hazard: 'External link with target="_blank" missing rel="noopener noreferrer"',
140
+ rule: 'SECURITY_REVERSE_TABNABBING',
141
+ severity: meta.severity,
142
+ pillar: meta.pillar,
143
+ directive: meta.directive
144
+ });
145
+ }
146
+ }
147
+
148
+ // Pillar 9: javascript: pseudo-protocol in HTML / Vue templates
149
+ const hasJsUrl = isTemplateFile && JAVASCRIPT_URL_TEMPLATE_PATTERN.test(lineText);
150
+ if (hasJsUrl) {
151
+ const meta = RULE_REGISTRY.SECURITY_JAVASCRIPT_URL;
152
+ violations.push({
153
+ filePath: relativePath,
154
+ line: lineNum,
155
+ column: 1,
156
+ hazard: 'javascript: pseudo-protocol detected in template attribute',
157
+ rule: 'SECURITY_JAVASCRIPT_URL',
158
+ severity: meta.severity,
159
+ pillar: meta.pillar,
160
+ directive: meta.directive
161
+ });
162
+ }
163
+
164
+ // Pillar 8: Non-semantic clickable containers in templates (@click on div/span)
165
+ const hasClickableContainer = isTemplateFile && CLICKABLE_CONTAINER_PATTERN.test(lineText);
166
+ if (hasClickableContainer) {
167
+ const meta = RULE_REGISTRY.A11Y_CLICKABLE_NON_SEMANTIC;
168
+ violations.push({
169
+ filePath: relativePath,
170
+ line: lineNum,
171
+ column: 1,
172
+ hazard: 'Clickable generic container detected without native button/link semantics',
173
+ rule: 'A11Y_CLICKABLE_NON_SEMANTIC',
174
+ severity: meta.severity,
175
+ pillar: meta.pillar,
176
+ directive: meta.directive
177
+ });
178
+ }
179
+
180
+ // Pillar 8: Missing alt attribute on img tags
181
+ const hasMissingAltImg = isTemplateFile && IMG_WITHOUT_ALT_PATTERN.test(lineText);
182
+ if (hasMissingAltImg) {
183
+ const meta = RULE_REGISTRY.A11Y_IMAGE_MISSING_ALT;
184
+ violations.push({
185
+ filePath: relativePath,
186
+ line: lineNum,
187
+ column: 1,
188
+ hazard: 'Image element missing alt text attribute',
189
+ rule: 'A11Y_IMAGE_MISSING_ALT',
190
+ severity: meta.severity,
191
+ pillar: meta.pillar,
192
+ directive: meta.directive
193
+ });
194
+ }
195
+
196
+ // Pillar 10: Fake green tests (only in spec/test files)
197
+ const canCheckFakeGreen = isTestFile && !isComment;
198
+ if (canCheckFakeGreen) {
199
+ for (const pat of FAKE_GREEN_PATTERNS) {
200
+ const isFakeGreenMatch = pat.test(lineText);
201
+ if (isFakeGreenMatch) {
202
+ const meta = RULE_REGISTRY.TEST_FAKE_GREEN;
203
+ violations.push({
204
+ filePath: relativePath,
205
+ line: lineNum,
206
+ column: 1,
207
+ hazard: 'Fake green test assertion detected (asserting literal against itself)',
208
+ rule: 'TEST_FAKE_GREEN',
209
+ severity: meta.severity,
210
+ pillar: meta.pillar,
211
+ directive: meta.directive
212
+ });
213
+ break;
214
+ }
215
+ }
216
+ }
217
+ });
218
+ };