@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.
- package/README.md +68 -8
- package/blueprints/atoms/a-button.svelte +30 -0
- package/blueprints/atoms/a-button.tsx +36 -0
- package/blueprints/molecule-capsule/m-chemx-badge/m-chemx-badge.svelte +35 -0
- package/blueprints/molecule-capsule/m-sample-card.svelte +58 -0
- package/blueprints/molecule-capsule/m-sample-card.tsx +3 -3
- package/blueprints/molecule-capsule/m-sample-card.vue +60 -0
- package/blueprints/view-template.svelte +18 -0
- package/blueprints/view-template.vue +18 -0
- package/blueprints/workflows/chemx-audit.yml +36 -0
- package/cli/audit/ast-visitors.js +25 -13
- package/cli/audit/csharp-analyzer.js +80 -0
- package/cli/audit/extended-text-patterns.js +11 -2
- package/cli/audit/pattern-detector.js +5 -3
- package/cli/audit/pattern-detector.spec.js +17 -0
- package/cli/audit/reporter-grades.js +1 -0
- package/cli/audit/reporter-markdown.js +2 -0
- package/cli/audit/reporter-sections.js +3 -0
- package/cli/audit/rules-registry.js +27 -0
- package/cli/audit/rules.js +14 -3
- package/cli/audit/rules.spec.js +27 -0
- package/cli/audit/structural-weight-evaluator.js +121 -0
- package/cli/audit/structural-weight.js +92 -0
- package/cli/audit/structural-weight.spec.js +116 -0
- package/cli/audit-preflight-scope.js +4 -1
- package/cli/audit.js +9 -10
- package/cli/commands/cmd-audit.js +1 -1
- package/cli/commands/cmd-router.js +2 -1
- package/cli/config/config.spec.js +51 -0
- package/cli/config/index.js +37 -0
- package/cli/config/loader.js +83 -0
- package/cli/config/profiles.js +56 -0
- package/cli/config.spec.js +1 -0
- package/cli/create.js +21 -8
- package/cli/create.spec.js +91 -0
- package/cli/generator-help.js +48 -0
- package/cli/generator-indexer.js +41 -0
- package/cli/generator.js +8 -43
- package/cli/generator.spec.js +29 -0
- package/cli/languages.js +122 -0
- package/cli/languages.spec.js +44 -0
- package/cli/mcp/tools-patch.js +7 -5
- package/cli/mcp/tools-team-tasks.js +56 -10
- package/cli/polyglot.spec.js +111 -0
- package/cli/scaffold-frameworks.js +133 -0
- package/cli/scaffold.js +67 -37
- package/cli/search-ast.js +41 -9
- package/cli/search-db.js +2 -8
- package/cli/search-queries-semantic.js +28 -9
- package/cli/search-tokenizer.js +52 -0
- package/cli/search.js +2 -5
- package/cli/search.spec.js +51 -3
- package/cli/team/team-commands-vds.js +70 -0
- package/cli/team/team-commands.js +72 -3
- package/cli/team/team-db-lock-promotion.js +29 -18
- package/cli/team/team-db-locks.js +54 -43
- package/cli/team/team-db-task-helpers.js +18 -0
- package/cli/team/team-db-tasks.js +45 -51
- package/cli/team/team-db-transaction.js +52 -0
- package/cli/team/team-format.js +60 -0
- package/cli/team/team-multiprocess-concurrency.spec.js +193 -0
- package/cli/team/team-release-train.js +65 -0
- package/cli/team/team-release-train.spec.js +91 -0
- package/cli/team/team-schema-vds.js +39 -0
- package/cli/team/team-schema.js +21 -23
- package/cli/team/team-tasks-asana.spec.js +60 -0
- package/cli/team/team-triage.js +12 -0
- package/cli/team/team-vds.js +88 -0
- package/cli/team/team-vds.spec.js +125 -0
- package/cli/team/team-verification-gate.spec.js +27 -0
- package/cli/ui-actions-vds.js +48 -0
- package/cli/ui-actions.js +1 -0
- package/cli/ui-client-api.js +8 -0
- package/cli/ui-client-script.js +33 -29
- package/cli/ui-dev-watcher.js +61 -0
- package/cli/ui-direct-routes.spec.js +55 -0
- package/cli/ui-handlers.js +7 -8
- package/cli/ui-index.html +416 -26
- package/cli/ui-kanban.spec.js +11 -0
- package/cli/ui-server-routes.js +14 -6
- package/cli/ui-server.js +17 -21
- package/cli/ui-sse.js +23 -10
- package/cli/ui-sse.spec.js +26 -0
- package/cli/ui-styles.js +20 -4
- package/cli/ui-template-kanban.js +23 -27
- package/cli/ui-template-task-sheet.js +87 -0
- package/cli/ui-template-vds-badges.js +34 -0
- package/cli/ui-template.js +13 -10
- package/cli/verify-helpers.js +173 -0
- package/cli/verify.js +132 -176
- package/cli/verify.spec.js +61 -0
- package/package.json +5 -1
- package/scripts/pre-commit.sh +4 -0
- 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
|
|
260
|
-
|
|
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
|
-
<
|
|
60
|
-
type="button"
|
|
60
|
+
<AtomButton
|
|
61
61
|
className="m-sample-card__action"
|
|
62
62
|
onClick={handleActionClick}
|
|
63
63
|
>
|
|
64
64
|
Action
|
|
65
|
-
</
|
|
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
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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('');
|