@chemx/create-chemx 26.9.24-393 → 26.9.25-747

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 (94) hide show
  1. package/README.md +68 -8
  2. package/blueprints/atoms/a-button.svelte +30 -0
  3. package/blueprints/atoms/a-button.tsx +36 -0
  4. package/blueprints/molecule-capsule/m-chemx-badge/m-chemx-badge.svelte +35 -0
  5. package/blueprints/molecule-capsule/m-sample-card.svelte +58 -0
  6. package/blueprints/molecule-capsule/m-sample-card.tsx +3 -3
  7. package/blueprints/molecule-capsule/m-sample-card.vue +60 -0
  8. package/blueprints/view-template.svelte +18 -0
  9. package/blueprints/view-template.vue +18 -0
  10. package/blueprints/workflows/chemx-audit.yml +36 -0
  11. package/cli/audit/ast-visitors.js +25 -13
  12. package/cli/audit/csharp-analyzer.js +80 -0
  13. package/cli/audit/extended-text-patterns.js +11 -2
  14. package/cli/audit/pattern-detector.js +5 -3
  15. package/cli/audit/pattern-detector.spec.js +17 -0
  16. package/cli/audit/reporter-grades.js +1 -0
  17. package/cli/audit/reporter-markdown.js +2 -0
  18. package/cli/audit/reporter-sections.js +3 -0
  19. package/cli/audit/rules-registry.js +27 -0
  20. package/cli/audit/rules.js +14 -3
  21. package/cli/audit/rules.spec.js +27 -0
  22. package/cli/audit/structural-weight-evaluator.js +121 -0
  23. package/cli/audit/structural-weight.js +92 -0
  24. package/cli/audit/structural-weight.spec.js +116 -0
  25. package/cli/audit-preflight-scope.js +4 -1
  26. package/cli/audit.js +9 -10
  27. package/cli/commands/cmd-audit.js +1 -1
  28. package/cli/commands/cmd-router.js +2 -1
  29. package/cli/config/config.spec.js +51 -0
  30. package/cli/config/index.js +37 -0
  31. package/cli/config/loader.js +83 -0
  32. package/cli/config/profiles.js +56 -0
  33. package/cli/config.spec.js +1 -0
  34. package/cli/create.js +21 -8
  35. package/cli/create.spec.js +91 -0
  36. package/cli/generator-help.js +48 -0
  37. package/cli/generator-indexer.js +41 -0
  38. package/cli/generator.js +8 -43
  39. package/cli/generator.spec.js +29 -0
  40. package/cli/languages.js +122 -0
  41. package/cli/languages.spec.js +44 -0
  42. package/cli/mcp/tools-patch.js +7 -5
  43. package/cli/mcp/tools-team-tasks.js +56 -10
  44. package/cli/polyglot.spec.js +111 -0
  45. package/cli/scaffold-frameworks.js +133 -0
  46. package/cli/scaffold.js +67 -37
  47. package/cli/search-ast.js +41 -9
  48. package/cli/search-db.js +2 -8
  49. package/cli/search-queries-semantic.js +28 -9
  50. package/cli/search-tokenizer.js +52 -0
  51. package/cli/search.js +2 -5
  52. package/cli/search.spec.js +51 -3
  53. package/cli/team/team-commands-vds.js +70 -0
  54. package/cli/team/team-commands.js +72 -3
  55. package/cli/team/team-db-lock-promotion.js +29 -18
  56. package/cli/team/team-db-locks.js +54 -43
  57. package/cli/team/team-db-task-helpers.js +18 -0
  58. package/cli/team/team-db-tasks.js +45 -51
  59. package/cli/team/team-db-transaction.js +52 -0
  60. package/cli/team/team-format.js +60 -0
  61. package/cli/team/team-multiprocess-concurrency.spec.js +193 -0
  62. package/cli/team/team-release-train.js +65 -0
  63. package/cli/team/team-release-train.spec.js +91 -0
  64. package/cli/team/team-schema-vds.js +39 -0
  65. package/cli/team/team-schema.js +21 -23
  66. package/cli/team/team-tasks-asana.spec.js +60 -0
  67. package/cli/team/team-triage.js +12 -0
  68. package/cli/team/team-vds.js +88 -0
  69. package/cli/team/team-vds.spec.js +125 -0
  70. package/cli/team/team-verification-gate.spec.js +27 -0
  71. package/cli/ui-actions-vds.js +48 -0
  72. package/cli/ui-actions.js +1 -0
  73. package/cli/ui-client-api.js +8 -0
  74. package/cli/ui-client-script.js +33 -29
  75. package/cli/ui-dev-watcher.js +61 -0
  76. package/cli/ui-direct-routes.spec.js +55 -0
  77. package/cli/ui-handlers.js +7 -8
  78. package/cli/ui-index.html +416 -26
  79. package/cli/ui-kanban.spec.js +11 -0
  80. package/cli/ui-server-routes.js +14 -6
  81. package/cli/ui-server.js +17 -21
  82. package/cli/ui-sse.js +23 -10
  83. package/cli/ui-sse.spec.js +26 -0
  84. package/cli/ui-styles.js +20 -4
  85. package/cli/ui-template-kanban.js +23 -27
  86. package/cli/ui-template-task-sheet.js +87 -0
  87. package/cli/ui-template-vds-badges.js +34 -0
  88. package/cli/ui-template.js +13 -10
  89. package/cli/verify-helpers.js +173 -0
  90. package/cli/verify.js +132 -176
  91. package/cli/verify.spec.js +61 -0
  92. package/package.json +5 -1
  93. package/scripts/pre-commit.sh +4 -0
  94. package/docs/CHANGELOG.md +0 -332
package/README.md CHANGED
@@ -26,23 +26,35 @@ Create a brand new molecular architecture project in seconds using your preferre
26
26
  # npm (interactive or pass project directory)
27
27
  npm create chemx my-molecular-app
28
28
 
29
+ # Choose framework (react, vue, or svelte)
30
+ npm create chemx my-molecular-app -- --framework=react
31
+
32
+ # Auto-install dependencies upon scaffolding
33
+ npm create chemx my-molecular-app -- --framework=react --install
34
+
29
35
  # Automated / Agent / Headless mode (skips prompts, scaffolds Community Edition immediately)
30
36
  npm create chemx my-molecular-app -- --yes
31
37
 
32
38
  # npx
33
- npx create-chemx my-molecular-app --yes
39
+ npx create-chemx my-molecular-app --framework=react --install --yes
34
40
 
35
41
  # pnpm / yarn / bun
36
- pnpm create chemx my-molecular-app
37
- yarn create chemx my-molecular-app
42
+ pnpm create chemx my-molecular-app --framework=vue
43
+ yarn create chemx my-molecular-app --framework=svelte
38
44
  bun create chemx my-molecular-app
39
45
  ```
40
46
 
47
+ ### Framework & Installation Flags
48
+ * `--framework=<react|vue|svelte>` (or `-f <name>`): Selects target framework. Scaffolds strictly matching components, file templates, and dependencies with zero cross-framework leakage.
49
+ * `--install`: Runs package manager install immediately after project creation.
50
+ * `--skip-install` (or `--no-install`): Explicitly skips automatic dependency installation (default).
51
+ * `--yes` (or `-y`): Automatically confirms prompts with standard molecular presets.
52
+
41
53
  ### Headless & Autonomous Agent Mode
42
54
  When running in unattended environments (CI/CD pipelines, Cursor Agent, Windsurf, Claude Code, Antigravity), pass `--yes` (or `-y`, `--ci`, `--headless`) to bypass interactive terminal menus and immediately scaffold the free Community Edition with recommended architectural pillars:
43
55
 
44
56
  ```bash
45
- npx create-chemx my-molecular-app --yes
57
+ npx create-chemx my-molecular-app --framework=react --install --yes
46
58
  ```
47
59
 
48
60
  ### Package Architecture: Scaffolder vs Command Engine
@@ -233,12 +245,13 @@ Scaffold production-ready component capsules matching strict zero-raw-DOM standa
233
245
  ```bash
234
246
  # Molecule capsule (component, controller, glass styling, types, spec)
235
247
  chemx m-task-card
248
+ chemx m-task-card --framework=react # Explicit framework (react, vue, or svelte)
236
249
 
237
250
  # Atom foundation (the only tier permitted raw HTML elements)
238
- chemx a-status-pill
251
+ chemx a-status-pill --framework=vue
239
252
 
240
253
  # Organism module (complex grouping of molecules and atoms)
241
- chemx o-workspace-header
254
+ chemx o-workspace-header --framework=svelte
242
255
 
243
256
  # Pure reactive hook / domain composable
244
257
  chemx use-task-filter
@@ -256,13 +269,60 @@ chemx team task list --agent=@agent-alpha
256
269
  # Claim an open task
257
270
  chemx team task claim 1 --as=@agent-alpha
258
271
 
259
- # Mark task complete (automatically re-audits target file on disk to guarantee zero hazards)
260
- chemx team task done 1 --as=@agent-alpha
272
+ # Mark task complete with inline AST verification gate
273
+ # Re-audits target file on disk to guarantee zero blocking hazards before completion:
274
+ chemx team task done 1 --as=@agent-alpha --target=src/components/m-card.vue
275
+
276
+ # Emergency override / escape hatch: complete task despite non-blocking warnings:
277
+ chemx team task done 1 --as=@agent-alpha --target=src/components/m-card.vue --force
261
278
 
262
279
  # Inspect multi-agent swarm status and lock queues
263
280
  chemx team status
264
281
  ```
265
282
 
283
+ ### 5. Live Swarm Web UI & Direct Task Routing
284
+ Launch the real-time Chemical X Swarm Control Panel backed by SQLite (`.chemx/index.db`) with full SPA routing and deep linking:
285
+ ```bash
286
+ # Launch Live Swarm Web UI (default: http://localhost:4173)
287
+ npx chemx ui
288
+
289
+ # Launch on a custom port
290
+ npx chemx ui --port=8080
291
+ ```
292
+
293
+ * **Direct Task Routing:** Link straight to any task in the Kanban board: `http://localhost:4173/tasks/:id` (e.g. `http://localhost:4173/tasks/42`)
294
+ * **Auto-Focus & Highlighting:** Opening a task route automatically switches to the **📋 Tasks & Kanban** view, smoothly centers the card, and illuminates it with a cyan highlight glow.
295
+ * **One-Click Share:** Click the `🔗` icon on any Kanban card to copy its direct URL straight to your clipboard.
296
+ * **Single-Task API:** Fetch individual task state and verification diff receipts directly via `GET /api/tasks/:id`.
297
+
298
+ ---
299
+
300
+ ## Language-Agnostic Polyglot Architecture
301
+
302
+ Chemical X is **language-agnostic**. The core physics of AI agent code generation—**Zero Context Rot, Monolith Slicing, and Verified Anti-Hallucination Gating**—apply universally across backend, frontend, and systems stacks.
303
+
304
+ ### Supported Language Ecosystems
305
+
306
+ | Language / Framework | Extensions | Parser Engine | Capabilities |
307
+ | :--- | :--- | :--- | :--- |
308
+ | **C# / .NET 10** | `.cs` | Structural Regex / AST | Line budgets, MediatR handlers, shallow catch detection, fake in-memory stubs (`UseInMemoryDatabase`), `using` namespace indexing |
309
+ | **Python** | `.py` | Structural Regex / AST | Line budgets, AI slop text patterns, fake assertions (`assert True`), `def`/`class` and `import` indexing |
310
+ | **Go** | `.go` | Structural Regex / AST | Line budgets, mock data scanning, tautological assertions, `func`/`type` package indexing |
311
+ | **Rust** | `.rs` | Structural Regex / AST | Line budgets, AI slop text patterns, secret scanning, struct and fn indexing |
312
+ | **TypeScript / JavaScript** | `.ts`, `.tsx`, `.js`, `.jsx` | `@babel/parser` AST | Full 11-Pillar AST visitors, Zero-Raw-DOM, hook contracts, 2-stage booleans |
313
+ | **Vue & Svelte** | `.vue`, `.svelte` | Babel + Template Registry | SFC template clone detection, reactive state verification |
314
+
315
+ ### Two-Tier Decoupled Audit Pipeline
316
+ 1. **Tier 1: Universal Polyglot Rules (Runs on ALL languages)**
317
+ * **Sliding-Scale Line Budgets:** Flags files exceeding 100, 500, or 1,000 lines (`LINE_BUDGET_FILE`) to eliminate LLM context rot.
318
+ * **AI Slop Text Patterns:** Strips conversational residue (*"Here is the code"*), leaked markdown code fences, and lazy truncation placeholders (`// ... rest of implementation`).
319
+ * **Synthetic Mock Data Scanners:** Catches fake emails (`@example.com`), `555-` phone numbers, and hardcoded dummy collections in services.
320
+ * **Security & Secret Guards:** Scans for high-entropy API keys, JWTs, AWS credentials, and unmanaged sensitive logging.
321
+ * **Polyglot Fake Green Tests:** Catches tautological assertions in C# (`Assert.True(true)`), Python (`assert True`), and Go (`assert.True(t, true)`).
322
+ 2. **Tier 2: Deep Language-Specific Analyzers**
323
+ * **Babel Engine:** Deep AST inspection for JS/TS/Vue/Svelte (zero false-positive syntax errors on non-JS code).
324
+ * **C# / Clean Architecture Analyzer:** Flags empty `catch (Exception) {}` blocks, simulated delays (`Task.Delay`), and monolithic controllers.
325
+
266
326
  ---
267
327
 
268
328
  ## The 7 Molecular Architecture Pillars
@@ -0,0 +1,30 @@
1
+ <script lang="ts">
2
+ let {
3
+ type = 'button',
4
+ disabled = false,
5
+ ariaLabel = undefined,
6
+ variant = 'primary',
7
+ class: className = '',
8
+ onclick,
9
+ children
10
+ } = $props<{
11
+ type?: 'button' | 'submit' | 'reset';
12
+ disabled?: boolean;
13
+ ariaLabel?: string;
14
+ variant?: 'primary' | 'ghost' | 'glass';
15
+ class?: string;
16
+ onclick?: (e: MouseEvent) => void;
17
+ children?: import('svelte').Snippet;
18
+ }>();
19
+ </script>
20
+
21
+ <button
22
+ {type}
23
+ {disabled}
24
+ aria-label={ariaLabel}
25
+ class="a-button cursor-pointer {className}"
26
+ data-variant={variant}
27
+ {onclick}
28
+ >
29
+ {@render children?.()}
30
+ </button>
@@ -0,0 +1,36 @@
1
+ import React from 'react';
2
+
3
+ export interface AtomButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
4
+ variant?: 'primary' | 'ghost' | 'glass';
5
+ ariaLabel?: string;
6
+ children?: React.ReactNode;
7
+ }
8
+
9
+ export const AtomButton: React.FC<AtomButtonProps> = ({
10
+ type = 'button',
11
+ disabled = false,
12
+ ariaLabel,
13
+ variant = 'primary',
14
+ children,
15
+ className = '',
16
+ onClick,
17
+ ...rest
18
+ }) => {
19
+ const rootClass = `a-button cursor-pointer ${className}`.trim();
20
+
21
+ return (
22
+ <button
23
+ type={type}
24
+ disabled={disabled}
25
+ aria-label={ariaLabel}
26
+ className={rootClass}
27
+ data-variant={variant}
28
+ onClick={onClick}
29
+ {...rest}
30
+ >
31
+ {children}
32
+ </button>
33
+ );
34
+ };
35
+
36
+ export default AtomButton;
@@ -0,0 +1,35 @@
1
+ <script lang="ts">
2
+ import type { MChemxBadgeProps } from './types';
3
+
4
+ export const resolveGradeClass = (grade: string): string => {
5
+ const g = grade.toUpperCase();
6
+ if (g.startsWith('A')) return 'm-chemx-badge__grade--a';
7
+ if (g.startsWith('B')) return 'm-chemx-badge__grade--b';
8
+ if (g.startsWith('C')) return 'm-chemx-badge__grade--c';
9
+ if (g.startsWith('D')) return 'm-chemx-badge__grade--d';
10
+ return 'm-chemx-badge__grade--f';
11
+ };
12
+
13
+ let {
14
+ label = 'AI Slop cleaned with Chemical X',
15
+ grade = 'A+',
16
+ href = 'https://chemicalx.xophz.com',
17
+ reportUrl = undefined,
18
+ className = ''
19
+ } = $props<MChemxBadgeProps>();
20
+
21
+ const targetHref = $derived(reportUrl || href);
22
+ const gradeClass = $derived(resolveGradeClass(grade));
23
+ </script>
24
+
25
+ <a
26
+ href={targetHref}
27
+ target="_blank"
28
+ rel="noopener noreferrer"
29
+ class="m-chemx-badge {className}"
30
+ title="Verified by Chemical X Molecular Architecture Protocol"
31
+ >
32
+ <span class="m-chemx-badge__dot"></span>
33
+ <span class="m-chemx-badge__label">{label}</span>
34
+ <span class="m-chemx-badge__grade {gradeClass}">[{grade}]</span>
35
+ </a>
@@ -0,0 +1,58 @@
1
+ <script lang="ts">
2
+ import AButton from '../../atoms/a-button.svelte';
3
+ import type { MSampleCardProps, SampleCardBadgeDescriptor } from './types';
4
+
5
+ let {
6
+ title,
7
+ subtitle = undefined,
8
+ value,
9
+ status = 'active',
10
+ onAction = undefined
11
+ } = $props<MSampleCardProps>();
12
+
13
+ const isHighValue = $derived(value > 1000);
14
+
15
+ const badge = $derived.by((): SampleCardBadgeDescriptor => {
16
+ if (status !== 'active') {
17
+ return {
18
+ text: status,
19
+ className: 'm-sample-card__badge m-sample-card__badge--archived'
20
+ };
21
+ }
22
+ if (isHighValue) {
23
+ return {
24
+ text: status,
25
+ className: 'm-sample-card__badge m-sample-card__badge--high-value'
26
+ };
27
+ }
28
+ return {
29
+ text: status,
30
+ className: 'm-sample-card__badge m-sample-card__badge--standard'
31
+ };
32
+ });
33
+ </script>
34
+
35
+ <div class="m-sample-card">
36
+ <div class="m-sample-card__header">
37
+ <div class="m-sample-card__title-group">
38
+ <h3 class="m-sample-card__title">{title}</h3>
39
+ {#if subtitle}
40
+ <p class="m-sample-card__subtitle">{subtitle}</p>
41
+ {/if}
42
+ </div>
43
+ <span class={badge.className}>{badge.text}</span>
44
+ </div>
45
+ <div class="m-sample-card__body">
46
+ <div class="m-sample-card__value">
47
+ ${value.toLocaleString()}
48
+ </div>
49
+ {#if onAction}
50
+ <AButton
51
+ class="m-sample-card__action"
52
+ onclick={onAction}
53
+ >
54
+ Action
55
+ </AButton>
56
+ {/if}
57
+ </div>
58
+ </div>
@@ -1,4 +1,5 @@
1
1
  import React from 'react';
2
+ import AtomButton from '../atoms/a-button';
2
3
  import type { MSampleCardProps, SampleCardBadgeDescriptor } from './types';
3
4
 
4
5
  export const resolveBadgeDescriptor = (
@@ -56,13 +57,12 @@ export const MSampleCard: React.FC<MSampleCardProps> = ({
56
57
  ${value.toLocaleString()}
57
58
  </div>
58
59
  {onAction && (
59
- <button
60
- type="button"
60
+ <AtomButton
61
61
  className="m-sample-card__action"
62
62
  onClick={handleActionClick}
63
63
  >
64
64
  Action
65
- </button>
65
+ </AtomButton>
66
66
  )}
67
67
  </div>
68
68
  </div>
@@ -0,0 +1,60 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue';
3
+ import AButton from '../../atoms/a-button.vue';
4
+ import type { MSampleCardProps, SampleCardBadgeDescriptor } from './types';
5
+
6
+ const props = withDefaults(defineProps<MSampleCardProps>(), {
7
+ subtitle: undefined,
8
+ status: 'active',
9
+ onAction: undefined
10
+ });
11
+
12
+ const isHighValue = computed(() => props.value > 1000);
13
+
14
+ const badge = computed((): SampleCardBadgeDescriptor => {
15
+ if (props.status !== 'active') {
16
+ return {
17
+ text: props.status,
18
+ className: 'm-sample-card__badge m-sample-card__badge--archived'
19
+ };
20
+ }
21
+ if (isHighValue.value) {
22
+ return {
23
+ text: props.status,
24
+ className: 'm-sample-card__badge m-sample-card__badge--high-value'
25
+ };
26
+ }
27
+ return {
28
+ text: props.status,
29
+ className: 'm-sample-card__badge m-sample-card__badge--standard'
30
+ };
31
+ });
32
+
33
+ const handleActionClick = () => {
34
+ if (props.onAction) props.onAction();
35
+ };
36
+ </script>
37
+
38
+ <template>
39
+ <div class="m-sample-card">
40
+ <div class="m-sample-card__header">
41
+ <div class="m-sample-card__title-group">
42
+ <h3 class="m-sample-card__title">{{ title }}</h3>
43
+ <p v-if="subtitle" class="m-sample-card__subtitle">{{ subtitle }}</p>
44
+ </div>
45
+ <span :class="badge.className">{{ badge.text }}</span>
46
+ </div>
47
+ <div class="m-sample-card__body">
48
+ <div class="m-sample-card__value">
49
+ ${{ value.toLocaleString() }}
50
+ </div>
51
+ <AButton
52
+ v-if="onAction"
53
+ class="m-sample-card__action"
54
+ @click="handleActionClick"
55
+ >
56
+ Action
57
+ </AButton>
58
+ </div>
59
+ </div>
60
+ </template>
@@ -0,0 +1,18 @@
1
+ <script lang="ts">
2
+ let {
3
+ title,
4
+ children
5
+ } = $props<{
6
+ title: string;
7
+ children?: import('svelte').Snippet;
8
+ }>();
9
+ </script>
10
+
11
+ <main class="view-template">
12
+ <header class="view-template__header">
13
+ <h1 class="view-template__title">{title}</h1>
14
+ </header>
15
+ <div class="view-template__content">
16
+ {@render children?.()}
17
+ </div>
18
+ </main>
@@ -0,0 +1,18 @@
1
+ <script setup lang="ts">
2
+ interface Props {
3
+ title: string;
4
+ }
5
+
6
+ defineProps<Props>();
7
+ </script>
8
+
9
+ <template>
10
+ <main class="view-template">
11
+ <header class="view-template__header">
12
+ <h1 class="view-template__title">{{ title }}</h1>
13
+ </header>
14
+ <div class="view-template__content">
15
+ <slot />
16
+ </div>
17
+ </main>
18
+ </template>
@@ -76,3 +76,39 @@ jobs:
76
76
  gh pr comment "\${PR_NUMBER}" --body-file PR_COMMENT.md
77
77
  fi
78
78
 
79
+ scaffold-smoke-test:
80
+ name: Framework Scaffolding Smoke Test
81
+ runs-on: ubuntu-latest
82
+ steps:
83
+ - name: Checkout Code
84
+ uses: actions/checkout@v4
85
+ - name: Setup Node.js
86
+ uses: actions/setup-node@v4
87
+ with:
88
+ node-version: 22
89
+ - name: Install Dependencies
90
+ run: npm install
91
+ - name: Run Test Suite
92
+ run: npm test
93
+ - name: Multi-Framework Scaffolding Smoke Test
94
+ run: |
95
+ for fw in react vue svelte; do
96
+ echo "Smoke testing scaffolding for framework: $fw"
97
+ node cli/create.js test-app-$fw --framework=$fw --yes --skip-install
98
+ if [ "$fw" = "react" ]; then
99
+ test -f test-app-react/src/components/m-sample-card/m-sample-card.tsx
100
+ ! ls test-app-react/src/components/m-sample-card/*.vue 2>/dev/null
101
+ ! ls test-app-react/src/components/m-sample-card/*.svelte 2>/dev/null
102
+ elif [ "$fw" = "vue" ]; then
103
+ test -f test-app-vue/src/components/m-sample-card/m-sample-card.vue
104
+ ! ls test-app-vue/src/components/m-sample-card/*.tsx 2>/dev/null
105
+ ! ls test-app-vue/src/components/m-sample-card/*.svelte 2>/dev/null
106
+ elif [ "$fw" = "svelte" ]; then
107
+ test -f test-app-svelte/src/components/m-sample-card/m-sample-card.svelte
108
+ ! ls test-app-svelte/src/components/m-sample-card/*.vue 2>/dev/null
109
+ ! ls test-app-svelte/src/components/m-sample-card/*.tsx 2>/dev/null
110
+ fi
111
+ echo "Framework $fw scaffolded cleanly."
112
+ done
113
+
114
+
@@ -8,10 +8,18 @@ import {
8
8
  isUnguardedConsoleCall
9
9
  } from './rules-predicates.js';
10
10
  import { validateHookReturnShape } from './hook-shape-validator.js';
11
+ import { evaluateComponentStructuralWeight } from './structural-weight-evaluator.js';
11
12
 
12
- export const createAstVisitors = ({ relativePath, violations, hookRegistry }) => {
13
+ export const createAstVisitors = ({ relativePath, violations, hookRegistry, config }) => {
13
14
  return {
14
15
  Function(astPath) {
16
+ evaluateComponentStructuralWeight({
17
+ funcPath: astPath,
18
+ relativePath,
19
+ violations,
20
+ config
21
+ });
22
+
15
23
  const isCustomHook = isCustomHookFunction(astPath);
16
24
 
17
25
  // Pillar 3: Hook Saturation
@@ -178,18 +186,22 @@ export const createAstVisitors = ({ relativePath, violations, hookRegistry }) =>
178
186
  if (attrName === 'style') {
179
187
  const value = astPath.node.value;
180
188
  if (t.isJSXExpressionContainer(value) && t.isObjectExpression(value.expression)) {
181
- const line = astPath.node.loc?.start.line || 1;
182
- const meta = RULE_REGISTRY.RAW_INLINE_STYLE;
183
- violations.push({
184
- filePath: relativePath,
185
- line,
186
- column: astPath.node.loc?.start.column || 1,
187
- hazard: 'Raw inline style attribute detected in JSX',
188
- rule: 'RAW_INLINE_STYLE',
189
- severity: meta.severity,
190
- pillar: meta.pillar,
191
- directive: meta.directive
192
- });
189
+ const preferTokens = config?.preferDesignTokens || 'warning';
190
+ if (preferTokens !== 'off') {
191
+ const line = astPath.node.loc?.start.line || 1;
192
+ const meta = RULE_REGISTRY.RAW_INLINE_STYLE;
193
+ const severity = preferTokens === 'error' ? 'HIGH' : meta.severity;
194
+ violations.push({
195
+ filePath: relativePath,
196
+ line,
197
+ column: astPath.node.loc?.start.column || 1,
198
+ hazard: 'Raw inline style attribute detected in JSX',
199
+ rule: 'RAW_INLINE_STYLE',
200
+ severity,
201
+ pillar: meta.pillar,
202
+ directive: meta.directive
203
+ });
204
+ }
193
205
  }
194
206
  } else if (attrName === 'className' || attrName === 'class') {
195
207
  const value = astPath.node.value;
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Chemical X C# / .NET Structural Analyzer
3
+ * Evaluates .NET Clean Architecture, MediatR handlers, EF Core usage, and AI slop.
4
+ */
5
+ import { RULE_REGISTRY } from './rules-registry.js';
6
+
7
+ const SHALLOW_CATCH_PATTERN = /catch\s*(?:\([^)]*\))?\s*\{\s*(?:\/\/[^\n]*)?\s*\}/;
8
+ const IN_MEMORY_DB_PATTERN = /\bUseInMemoryDatabase\s*\(/;
9
+ const SIMULATED_DELAY_PATTERN = /\bTask\.Delay\s*\(\s*\d+\s*\)/;
10
+ const HARDCODED_LIST_MOCK_PATTERN = /new\s+List<[A-Za-z0-9_]+>\s*\{\s*new\s+[A-Za-z0-9_]+\s*\{/;
11
+
12
+ export const analyzeCSharpCode = (content, relativePath, violations) => {
13
+ const lines = content.split('\n');
14
+
15
+ // Multi-line and single-line shallow catch detection
16
+ const catchMatches = content.matchAll(/catch\s*(?:\([^)]*\))?\s*\{(?:\s*|\s*\/\/[^\n]*\s*)\}/g);
17
+ for (const m of catchMatches) {
18
+ const lineNum = content.slice(0, m.index).split('\n').length;
19
+ const meta = RULE_REGISTRY.AI_SLOP_SHALLOW_CATCH;
20
+ violations.push({
21
+ filePath: relativePath,
22
+ line: lineNum,
23
+ column: 1,
24
+ hazard: 'Empty or shallow catch block detected in C# handler/service',
25
+ rule: 'AI_SLOP_SHALLOW_CATCH',
26
+ severity: meta.severity,
27
+ pillar: meta.pillar,
28
+ directive: meta.directive
29
+ });
30
+ }
31
+
32
+ lines.forEach((lineText, idx) => {
33
+ const lineNum = idx + 1;
34
+
35
+ // Simulated Mock Data / Stubs
36
+ if (IN_MEMORY_DB_PATTERN.test(lineText)) {
37
+ const meta = RULE_REGISTRY.SYNTHETIC_MOCK_DATA;
38
+ violations.push({
39
+ filePath: relativePath,
40
+ line: lineNum,
41
+ column: 1,
42
+ hazard: 'UseInMemoryDatabase mock provider detected (unverified live persistence)',
43
+ rule: 'SYNTHETIC_MOCK_DATA',
44
+ severity: meta.severity,
45
+ pillar: meta.pillar,
46
+ directive: meta.directive
47
+ });
48
+ }
49
+
50
+ if (SIMULATED_DELAY_PATTERN.test(lineText)) {
51
+ const meta = RULE_REGISTRY.AI_SLOP_LAZY_PLACEHOLDER;
52
+ violations.push({
53
+ filePath: relativePath,
54
+ line: lineNum,
55
+ column: 1,
56
+ hazard: 'Simulated Task.Delay detected (artificial asynchronous latency stub)',
57
+ rule: 'AI_SLOP_LAZY_PLACEHOLDER',
58
+ severity: meta.severity,
59
+ pillar: meta.pillar,
60
+ directive: meta.directive
61
+ });
62
+ }
63
+
64
+ if (HARDCODED_LIST_MOCK_PATTERN.test(lineText)) {
65
+ const meta = RULE_REGISTRY.SYNTHETIC_MOCK_DATA;
66
+ violations.push({
67
+ filePath: relativePath,
68
+ line: lineNum,
69
+ column: 1,
70
+ hazard: 'Hardcoded synthetic entity collection detected in C# service',
71
+ rule: 'SYNTHETIC_MOCK_DATA',
72
+ severity: meta.severity,
73
+ pillar: meta.pillar,
74
+ directive: meta.directive
75
+ });
76
+ }
77
+ });
78
+
79
+ return violations;
80
+ };
@@ -15,7 +15,13 @@ const SECRET_PATTERNS = [
15
15
  const FAKE_GREEN_PATTERNS = [
16
16
  /expect\s*\(\s*(true|false|1|0)\s*\)\s*\.\s*to(?:Be|Equal|StrictEqual)\s*\(\s*\1\s*\)/,
17
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*\)/
18
+ /assert\s*\.\s*ok\s*\(\s*(?:true|1)\s*\)/,
19
+ /\bAssert\.(?:True|IsTrue)\s*\(\s*true\s*\)/i,
20
+ /\bAssert\.(?:False|IsFalse)\s*\(\s*false\s*\)/i,
21
+ /\bAssert\.(?:Equal|AreEqual)\s*\(\s*(true|false|1|0)\s*,\s*\1\s*\)/i,
22
+ /\bassert\s+(?:True|1\s*==\s*1)\b/,
23
+ /\bself\.assert(?:True|Equal)\s*\(\s*(?:True|1\s*,\s*1)\s*\)/,
24
+ /\bassert\.(?:True|Equal)\s*\(\s*t\s*,\s*(?:true|1\s*,\s*1)\s*\)/
19
25
  ];
20
26
 
21
27
  const IMG_WITHOUT_ALT_PATTERN = new RegExp(['<', 'img\\b', '(?![^>]*\\balt\\s*=)', '[^>]*>'].join(''), 'i');
@@ -192,7 +198,10 @@ export const checkExtendedTextPatterns = (content, lines, relativePath, filePath
192
198
  checkMoleculeCoLocatedTest(filePath, relativePath, violations);
193
199
  checkControllerViewContract(filePath, relativePath, content, violations);
194
200
 
195
- const isTestFile = /\.(test|spec)\.[jt]sx?$/.test(filePath);
201
+ const isTestFile = /\.(test|spec)\.[jt]sx?$/.test(filePath)
202
+ || /(?:^|[\\/])(?:test_[^\\/]+\.py|[^\\/]+_test\.(?:py|go)|[^\\/]+Tests?\.cs)$/i.test(filePath)
203
+ || /(?:^|[\\/])tests?[\\/]/i.test(filePath)
204
+ || (filePath.endsWith('.cs') && (content.includes('[Fact]') || content.includes('[Test]')));
196
205
  const ext = path.extname(filePath);
197
206
  const isTemplateFile = isTemplateExtension(ext);
198
207
 
@@ -29,15 +29,17 @@ export const createPatternRegistry = () => {
29
29
  });
30
30
  };
31
31
 
32
- const resolveHarmonizationCandidates = (hotspots = []) => {
32
+ const resolveHarmonizationCandidates = (hotspots = [], options = {}) => {
33
33
  const hotspotFiles = new Set(hotspots.map((h) => h.filePath));
34
34
  const candidates = [];
35
+ const minFiles = options.ruleOfThree ? 3 : 2;
35
36
 
36
37
  for (const [key, bucket] of buckets.entries()) {
37
38
  const uniqueFiles = Array.from(new Set(bucket.occurrences.map((o) => o.filePath)));
38
- if (uniqueFiles.length < 2) continue;
39
-
40
39
  const hasHotspot = uniqueFiles.some((f) => hotspotFiles.has(f));
40
+ const isCandidateEligible = hasHotspot ? uniqueFiles.length >= 2 : uniqueFiles.length >= minFiles;
41
+ if (!isCandidateEligible) continue;
42
+
41
43
  const impactScore = uniqueFiles.length * (hasHotspot ? 3 : 1.5) + bucket.occurrences.length;
42
44
 
43
45
  let label = 'Recurring Pattern';
@@ -94,3 +94,20 @@ test('categorizeUiStructure: maps semantic AST topologies to specialized capsule
94
94
  assert.equal(fallbackRes.suggestedCapsule, 'm-feature-card');
95
95
  });
96
96
 
97
+ test('Rule of Three: requires pattern across 3+ files unless a hotspot is involved', () => {
98
+ const registry = createPatternRegistry();
99
+ const template = `<template><v-sheet><x-btn>1</x-btn><x-btn>2</x-btn></v-sheet></template>`;
100
+
101
+ recordTemplatePatterns(registry, template, 'src/components/A.vue');
102
+ recordTemplatePatterns(registry, template, 'src/components/B.vue');
103
+
104
+ // With Rule of Three enabled and no hotspots, 2 files do not trigger premature abstraction
105
+ const strictCandidates = registry.resolveHarmonizationCandidates([], { ruleOfThree: true });
106
+ assert.equal(strictCandidates.length, 0, '2 occurrences without hotspot should not trigger premature abstraction');
107
+
108
+ // When a 3rd file adopts the pattern, Rule of Three threshold is satisfied
109
+ recordTemplatePatterns(registry, template, 'src/components/C.vue');
110
+ const satisfiedCandidates = registry.resolveHarmonizationCandidates([], { ruleOfThree: true });
111
+ assert.equal(satisfiedCandidates.length, 1, '3 occurrences should satisfy Rule of Three threshold');
112
+ });
113
+
@@ -267,6 +267,7 @@ export const formatGradeASection = (report) => {
267
267
  lines.push(` ${GREEN}✔${RESET} Low Context Hazard & Token Burn Risk`);
268
268
  }
269
269
 
270
+ lines.push(`\n ${DIM}ℹ Notice: Architectural grade measures AST rules only. Run chemx verify for full typecheck and test verification.${RESET}`);
270
271
  lines.push(`\n${GREEN}======================================================================${RESET}\n`);
271
272
  return lines.join('\n');
272
273
  };
@@ -39,6 +39,8 @@ export const generateMarkdownReport = (report) => {
39
39
  lines.push('');
40
40
  }
41
41
  lines.push('> *Comprehensive static analysis report enforcing Chemical X Molecular Architecture standards.*');
42
+ lines.push('> ');
43
+ lines.push('> **Notice**: Architectural grades measure AST rule compliance only. Run `chemx verify` for full production readiness (typecheck + test suite).');
42
44
  lines.push('');
43
45
  lines.push('## 1. Executive Summary & Scorecard');
44
46
  lines.push('');