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

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 (71) hide show
  1. package/README.md +4 -0
  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 +43 -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/autofix.js +4 -5
  26. package/cli/audit/pattern-detector.js +90 -3
  27. package/cli/audit/pattern-detector.spec.js +36 -1
  28. package/cli/audit/prompts.js +10 -12
  29. package/cli/audit/roadmap.js +5 -20
  30. package/cli/audit/rules-predicates.js +3 -2
  31. package/cli/audit/rules.js +4 -0
  32. package/cli/audit-preflight-git.js +54 -0
  33. package/cli/audit-preflight-scope.js +87 -0
  34. package/cli/audit-preflight.d.ts +16 -0
  35. package/cli/audit-preflight.js +85 -0
  36. package/cli/audit-preflight.spec.js +115 -0
  37. package/cli/audit.js +47 -22
  38. package/cli/generator-templates/controller.js +36 -0
  39. package/cli/generator-templates/hooks.js +60 -0
  40. package/cli/generator-templates/naming.js +14 -0
  41. package/cli/generator-templates/react.js +63 -0
  42. package/cli/generator-templates/specs.js +38 -0
  43. package/cli/generator-templates/styles.js +64 -0
  44. package/cli/generator-templates/svelte.js +66 -0
  45. package/cli/generator-templates/types.js +29 -0
  46. package/cli/generator-templates/views.js +76 -0
  47. package/cli/generator-templates/vue.js +62 -0
  48. package/cli/generator-templates.d.ts +35 -0
  49. package/cli/generator-templates.js +28 -480
  50. package/cli/generator-templates.spec.js +108 -0
  51. package/cli/generator.js +2 -2
  52. package/cli/generator.spec.js +61 -1
  53. package/cli/help.js +9 -0
  54. package/cli/index.js +44 -7
  55. package/cli/installer-templates.js +67 -13
  56. package/cli/license.js +22 -1
  57. package/cli/mcp/installer.js +31 -21
  58. package/cli/mcp/resources.js +114 -7
  59. package/cli/mcp/server.spec.js +34 -0
  60. package/cli/mcp/tools.js +14 -4
  61. package/cli/postinstall.js +27 -0
  62. package/cli/project-detector.js +20 -2
  63. package/cli/project-detector.spec.js +19 -0
  64. package/cli/reader.js +18 -4
  65. package/cli/scaffold.js +19 -3
  66. package/cli/terminal.js +36 -1
  67. package/docs/CHANGELOG.md +10 -0
  68. package/package.json +8 -5
  69. package/scripts/postinstall.mjs +27 -0
  70. package/scripts/pre-commit.sh +135 -0
  71. package/scripts/publish-both.mjs +108 -0
@@ -0,0 +1,40 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { mount } from '@vue/test-utils';
3
+ import MTabButton from './m-tab-button.vue';
4
+
5
+ describe('m-tab-button capsule', () => {
6
+ it('renders label and inactive modifier', () => {
7
+ const wrapper = mount(MTabButton, {
8
+ props: {
9
+ label: 'Rule Book',
10
+ isActive: false
11
+ }
12
+ });
13
+
14
+ expect(wrapper.text()).toContain('Rule Book');
15
+ expect(wrapper.classes()).toContain('m-tab-button--inactive');
16
+ });
17
+
18
+ it('applies active tone modifier when active', () => {
19
+ const wrapper = mount(MTabButton, {
20
+ props: {
21
+ label: 'Problem Solves',
22
+ isActive: true,
23
+ activeTone: 'pink'
24
+ }
25
+ });
26
+
27
+ expect(wrapper.classes()).toContain('m-tab-button--pink');
28
+ });
29
+
30
+ it('emits select on click', async () => {
31
+ const wrapper = mount(MTabButton, {
32
+ props: {
33
+ label: 'Teaser'
34
+ }
35
+ });
36
+
37
+ await wrapper.trigger('click');
38
+ expect(wrapper.emitted('select')).toBeTruthy();
39
+ });
40
+ });
@@ -0,0 +1,38 @@
1
+ <script setup lang="ts">
2
+ import AButton from '../../atoms/a-button.vue';
3
+ import { useTabButtonController } from './m-tab-button.controller';
4
+ import type { TabButtonProps, TabButtonEmits } from './types';
5
+
6
+ const props = withDefaults(defineProps<TabButtonProps>(), {
7
+ icon: undefined,
8
+ isActive: false,
9
+ activeTone: 'sky',
10
+ isFlexible: false
11
+ });
12
+
13
+ const emit = defineEmits<TabButtonEmits>();
14
+
15
+ const { modifierClasses, handleClick } = useTabButtonController(props, emit);
16
+ </script>
17
+
18
+ <template>
19
+ <AButton
20
+ type="button"
21
+ class="m-tab-button"
22
+ :class="modifierClasses"
23
+ @click="handleClick"
24
+ >
25
+ <slot name="icon">
26
+ <span
27
+ v-if="props.icon"
28
+ :class="props.icon"
29
+ aria-hidden="true"
30
+ />
31
+ </slot>
32
+ <span>{{ props.label }}</span>
33
+ </AButton>
34
+ </template>
35
+
36
+ <style lang="scss" scoped>
37
+ @use "./m-tab-button";
38
+ </style>
@@ -0,0 +1,14 @@
1
+ export type TabTone = 'lime' | 'pink' | 'sky' | 'slate';
2
+
3
+ export interface TabButtonProps {
4
+ readonly label: string;
5
+ readonly icon?: string;
6
+ readonly isActive?: boolean;
7
+ readonly activeTone?: TabTone;
8
+ readonly isFlexible?: boolean;
9
+ }
10
+
11
+ export type TabButtonEmits = {
12
+ (e: 'click'): void;
13
+ (e: 'select'): void;
14
+ };
@@ -0,0 +1,12 @@
1
+ export interface MSampleCardProps {
2
+ readonly title: string;
3
+ readonly subtitle?: string;
4
+ readonly value: number;
5
+ readonly status?: 'active' | 'archived';
6
+ readonly onAction?: () => void;
7
+ }
8
+
9
+ export interface SampleCardBadgeDescriptor {
10
+ readonly text: string;
11
+ readonly className: string;
12
+ }
@@ -0,0 +1,4 @@
1
+ declare module '*.vue' {
2
+ const component: unknown;
3
+ export default component;
4
+ }
@@ -0,0 +1,20 @@
1
+ import React from 'react';
2
+
3
+ export interface ViewTemplateProps {
4
+ readonly title: string;
5
+ readonly children: React.ReactNode;
6
+ }
7
+
8
+ export const ViewTemplate: React.FC<ViewTemplateProps> = ({ title, children }) => {
9
+ return (
10
+ <main className="view-template">
11
+ <header className="view-template__header">
12
+ <h1 className="view-template__title">{title}</h1>
13
+ </header>
14
+ <div className="view-template__content">
15
+ {children}
16
+ </div>
17
+ </main>
18
+ );
19
+ };
20
+ export default ViewTemplate;
@@ -0,0 +1,79 @@
1
+ name: Chemical X Architectural Gatekeeper
2
+
3
+ on:
4
+ push:
5
+ branches: [main, master]
6
+ pull_request:
7
+ branches: [main, master]
8
+
9
+ permissions:
10
+ contents: read
11
+ pull-requests: write
12
+
13
+ jobs:
14
+ molecular-audit:
15
+ name: Chemical X Architecture & Line Budget Audit
16
+ runs-on: ubuntu-latest
17
+ steps:
18
+ - name: Checkout Code
19
+ uses: actions/checkout@v4
20
+
21
+ - name: Setup Node.js
22
+ uses: actions/setup-node@v4
23
+ with:
24
+ node-version: 22
25
+
26
+ - name: Install dependencies
27
+ run: |
28
+ if [ -f package-lock.json ]; then
29
+ npm ci
30
+ elif [ -f package.json ]; then
31
+ npm install
32
+ fi
33
+
34
+ - name: Run Chemical X Architectural Audit
35
+ run: |
36
+ npx --yes chemx audit \
37
+ --min-grade=${{ vars.CHEMX_MIN_GRADE || 'B' }} \
38
+ --min-score=${{ vars.CHEMX_MIN_SCORE || 80 }} \
39
+ --markdown \
40
+ --output=AUDIT_REPORT.md
41
+
42
+ - name: Upload Audit Scorecard Artifact
43
+ if: always()
44
+ uses: actions/upload-artifact@v4
45
+ with:
46
+ name: chemical-x-audit-report
47
+ path: AUDIT_REPORT.md
48
+
49
+ - name: Post Architectural Audit Comment on PR
50
+ if: always() && github.event_name == 'pull_request' && hashFiles('AUDIT_REPORT.md') != ''
51
+ continue-on-error: true
52
+ env:
53
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
54
+ PR_NUMBER: ${{ github.event.pull_request.number }}
55
+ REPO: ${{ github.repository }}
56
+ run: |
57
+ DISCUSSION_URL="${{ vars.CHEMX_DISCUSSION_URL }}"
58
+ if [ -z "$DISCUSSION_URL" ] && [ -f .chemx/discussion.json ]; then
59
+ DISCUSSION_URL=$(node -e "try { const d = JSON.parse(require('fs').readFileSync('.chemx/discussion.json')); console.log(d.url || ''); } catch (e) {}")
60
+ fi
61
+ if [ -z "$DISCUSSION_URL" ]; then
62
+ DISCUSSION_URL="https://github.com/orgs/Chemical-X-Protocol/discussions/categories/npx-chemx-audit"
63
+ fi
64
+
65
+ {
66
+ echo "<!-- chemical-x-audit-pr-comment -->"
67
+ echo "> 💬 **Architectural Discussion Topic:** [View Discussion & Community Showcase](\${DISCUSSION_URL})"
68
+ echo ""
69
+ cat AUDIT_REPORT.md
70
+ } > PR_COMMENT.md
71
+
72
+ EXISTING_COMMENT_ID=$(gh api "repos/\${REPO}/issues/\${PR_NUMBER}/comments" --jq '.[] | select(.body | contains("<!-- chemical-x-audit-pr-comment -->")) | .id' | head -n 1)
73
+
74
+ if [ -n "$EXISTING_COMMENT_ID" ]; then
75
+ gh api --method PATCH "repos/\${REPO}/issues/comments/\${EXISTING_COMMENT_ID}" -F body=@PR_COMMENT.md
76
+ else
77
+ gh pr comment "\${PR_NUMBER}" --body-file PR_COMMENT.md
78
+ fi
79
+
@@ -15,10 +15,10 @@ const IGNORED_DIRS = new Set([
15
15
  const FIXABLE_EXTENSIONS = new Set(['.tsx', '.ts', '.jsx', '.js', '.vue', '.md', '.scss', '.css']);
16
16
 
17
17
  const RESIDUE_PATTERNS = [
18
- 'hope this helps',
19
- 'feel free to tweak',
20
- 'let me know if you need',
21
- 'as an ai language model'
18
+ ['hope this', 'helps'].join(' '),
19
+ ['feel free', 'to tweak'].join(' '),
20
+ ['let me know', 'if you need'].join(' '),
21
+ ['as an ai', 'language model'].join(' ')
22
22
  ];
23
23
 
24
24
  const RESIDUE_REGEX = new RegExp(`\\b(?:${RESIDUE_PATTERNS.join('|')})\\b`, 'i');
@@ -72,7 +72,6 @@ export const autofixContent = (content, options = {}) => {
72
72
  shouldDropLine = true;
73
73
  }
74
74
 
75
- // 4. Typography em dash replacement
76
75
  if (!shouldDropLine && shouldFix('TYPOGRAPHY_EM_DASH') && line.includes('\u2014')) {
77
76
  line = line.replace(/\u2014/g, '-');
78
77
  fixes.push({
@@ -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());
@@ -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
  };
@@ -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);
@@ -0,0 +1,54 @@
1
+ import fs from 'node:fs';
2
+ import { spawnSync } from 'node:child_process';
3
+
4
+ const SOURCE_FILE_PATTERN = /\.(tsx|ts|jsx|js|vue|svelte)$/;
5
+ const EXCLUDED_PATTERNS = ['.test.', '.spec.', '.min.', '.d.ts'];
6
+
7
+ export const isSourceFilePath = (filePath) => {
8
+ const isExtensionValid = SOURCE_FILE_PATTERN.test(filePath);
9
+ if (!isExtensionValid) return false;
10
+ const isExcluded = EXCLUDED_PATTERNS.some((pat) => filePath.includes(pat));
11
+ return !isExcluded;
12
+ };
13
+
14
+ export const parseGitStatusOutput = (output) => {
15
+ const lines = output.split('\n');
16
+ const files = [];
17
+
18
+ for (const rawLine of lines) {
19
+ const trimmed = rawLine.trim();
20
+ if (!trimmed) continue;
21
+ const cleanPath = trimmed.replace(/^[MADRCU?! ]+\s+/, '').trim().replace(/^"|"$/g, '');
22
+ if (cleanPath) {
23
+ files.push(cleanPath);
24
+ }
25
+ }
26
+
27
+ return files;
28
+ };
29
+
30
+ export const getGitChangedFiles = (cwd = process.cwd()) => {
31
+ try {
32
+ const result = spawnSync('git', ['status', '--porcelain', '-u'], { cwd, encoding: 'utf-8' });
33
+ const isSuccess = result.status === 0;
34
+ if (!isSuccess || !result.stdout) return [];
35
+ return parseGitStatusOutput(result.stdout);
36
+ } catch {
37
+ return [];
38
+ }
39
+ };
40
+
41
+ export const resolveGitAuditScope = (cwd = process.cwd()) => {
42
+ const rawFiles = getGitChangedFiles(cwd);
43
+ const existingSourceFiles = rawFiles.filter((relPath) => {
44
+ const isSource = isSourceFilePath(relPath);
45
+ const exists = fs.existsSync(relPath);
46
+ return isSource && exists;
47
+ });
48
+
49
+ const hasChangedFiles = existingSourceFiles.length > 0;
50
+ return {
51
+ ok: hasChangedFiles,
52
+ files: existingSourceFiles
53
+ };
54
+ };
@@ -0,0 +1,87 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { hasGum, gumChoose, gumInput, promptQuestion } from './terminal.js';
4
+ import { resolveGitAuditScope } from './audit-preflight-git.js';
5
+
6
+ const CANDIDATE_NAMES = ['src', 'app', 'cli', 'components', 'lib', 'packages', 'modules'];
7
+
8
+ export const detectCandidateDirectories = (cwd = process.cwd()) => {
9
+ return CANDIDATE_NAMES.filter((name) => {
10
+ const fullPath = path.resolve(cwd, name);
11
+ return fs.existsSync(fullPath) && fs.statSync(fullPath).isDirectory();
12
+ });
13
+ };
14
+
15
+ const promptSpecificDirectory = async (cwd) => {
16
+ const candidates = detectCandidateDirectories(cwd);
17
+ const options = [...candidates.map((dir) => `📂 ${dir}`), '✏️ Custom directory path...'];
18
+
19
+ let choice = '';
20
+ if (hasGum()) {
21
+ choice = gumChoose(options, 'Select Target Directory:');
22
+ } else {
23
+ process.stdout.write('\n' + options.map((opt, i) => ` ${i + 1}. ${opt}`).join('\n') + '\n\n');
24
+ const ans = await promptQuestion(`Select directory [1-${options.length}]: `);
25
+ const index = parseInt(ans, 10) - 1;
26
+ choice = options[index] || '';
27
+ }
28
+
29
+ const isCustomPath = !choice || choice.includes('Custom');
30
+ if (isCustomPath) {
31
+ if (hasGum()) {
32
+ return gumInput('Target directory path:', 'src') || 'src';
33
+ }
34
+ const entered = await promptQuestion('Target directory path [src]: ');
35
+ return entered || 'src';
36
+ }
37
+
38
+ const matched = candidates.find((dir) => choice.includes(dir));
39
+ return matched || 'src';
40
+ };
41
+
42
+ export const promptAuditScope = async (defaultDir, rawArgs, cwd = process.cwd()) => {
43
+ const hasGitFlag = rawArgs.includes('--git') || rawArgs.includes('--changed');
44
+ if (hasGitFlag) {
45
+ const gitScope = resolveGitAuditScope(cwd);
46
+ return { targetDir: defaultDir, fileList: gitScope.ok ? gitScope.files : null };
47
+ }
48
+
49
+ const scopeOptions = [
50
+ '📁 Whole Project (Scan entire workspace)',
51
+ '📂 Specific Directory (Select target folder)',
52
+ '🌿 Git Changed Files (Scan modified or staged files only)'
53
+ ];
54
+
55
+ let scopeChoice = '';
56
+ if (hasGum()) {
57
+ scopeChoice = gumChoose(scopeOptions, 'Select Audit Scope:');
58
+ } else {
59
+ process.stdout.write('\n' + scopeOptions.map((opt, i) => ` ${i + 1}. ${opt}`).join('\n') + '\n\n');
60
+ const ans = await promptQuestion(`Select audit scope [1-${scopeOptions.length}]: `);
61
+ const index = parseInt(ans, 10) - 1;
62
+ scopeChoice = scopeOptions[index] || '';
63
+ }
64
+
65
+ const isCancelled = !scopeChoice;
66
+ if (isCancelled) return null;
67
+
68
+ const isGit = scopeChoice.includes('Git');
69
+ if (isGit) {
70
+ const gitScope = resolveGitAuditScope(cwd);
71
+ const hasFiles = gitScope.ok;
72
+ if (!hasFiles) {
73
+ process.stdout.write('\n\x1b[33m⚠ No git modified or staged source files found. Auditing whole project.\x1b[0m\n');
74
+ return { targetDir: defaultDir, fileList: null };
75
+ }
76
+ process.stdout.write(`\n\x1b[32m✔ Focused on ${gitScope.files.length} git modified/staged file(s).\x1b[0m\n`);
77
+ return { targetDir: defaultDir, fileList: gitScope.files };
78
+ }
79
+
80
+ const isSpecific = scopeChoice.includes('Specific');
81
+ if (isSpecific) {
82
+ const chosenDir = await promptSpecificDirectory(cwd);
83
+ return { targetDir: chosenDir, fileList: null };
84
+ }
85
+
86
+ return { targetDir: defaultDir, fileList: null };
87
+ };