@chemx/starter-kit 26.9.16-668 → 26.9.17-606

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/README.md +67 -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 +40 -0
  18. package/blueprints/molecule-capsule/m-tab-button/m-tab-button.spec.ts +40 -0
  19. package/blueprints/molecule-capsule/m-tab-button/m-tab-button.vue +38 -0
  20. package/blueprints/molecule-capsule/m-tab-button/types.d.ts +14 -0
  21. package/blueprints/molecule-capsule/types.d.ts +12 -0
  22. package/blueprints/shims-vue.d.ts +4 -0
  23. package/blueprints/view-template.tsx +20 -0
  24. package/blueprints/workflows/chemx-audit.yml +79 -0
  25. package/cli/audit/ast-visitors.js +73 -0
  26. package/cli/audit/autofix.js +4 -5
  27. package/cli/audit/extended-visitors.js +239 -0
  28. package/cli/audit/pattern-detector.js +90 -3
  29. package/cli/audit/pattern-detector.spec.js +36 -1
  30. package/cli/audit/reporter-utils.js +13 -22
  31. package/cli/audit/rules-predicates.js +3 -2
  32. package/cli/audit/rules-registry.js +25 -0
  33. package/cli/audit/rules.spec.js +205 -0
  34. package/cli/audit-preflight.spec.js +11 -0
  35. package/cli/build.d.ts +2 -1
  36. package/cli/build.js +8 -5
  37. package/cli/columnar.d.ts +14 -0
  38. package/cli/columnar.js +52 -0
  39. package/cli/columnar.spec.js +49 -0
  40. package/cli/generator-templates/controller.js +36 -0
  41. package/cli/generator-templates/hooks.js +60 -0
  42. package/cli/generator-templates/naming.js +14 -0
  43. package/cli/generator-templates/react.js +63 -0
  44. package/cli/generator-templates/specs.js +38 -0
  45. package/cli/generator-templates/styles.js +64 -0
  46. package/cli/generator-templates/svelte.js +66 -0
  47. package/cli/generator-templates/types.js +29 -0
  48. package/cli/generator-templates/views.js +76 -0
  49. package/cli/generator-templates/vue.js +62 -0
  50. package/cli/generator-templates.d.ts +35 -0
  51. package/cli/generator-templates.js +28 -480
  52. package/cli/generator-templates.spec.js +108 -0
  53. package/cli/generator.js +2 -2
  54. package/cli/generator.spec.js +61 -1
  55. package/cli/help.js +29 -4
  56. package/cli/index.js +54 -7
  57. package/cli/installer-templates.js +29 -8
  58. package/cli/installer.js +23 -6
  59. package/cli/license.js +22 -1
  60. package/cli/mcp/antigravity.js +59 -0
  61. package/cli/mcp/index.js +2 -1
  62. package/cli/mcp/installer.js +54 -22
  63. package/cli/mcp/installer.spec.js +23 -1
  64. package/cli/mcp/manifests.js +251 -0
  65. package/cli/mcp/prompts.js +28 -0
  66. package/cli/mcp/resources.js +175 -64
  67. package/cli/mcp/server.js +13 -0
  68. package/cli/mcp/server.spec.js +247 -1
  69. package/cli/mcp/tools.js +266 -193
  70. package/cli/mcp/types.d.ts +37 -0
  71. package/cli/navigator-actions.js +23 -10
  72. package/cli/navigator-banner.js +2 -3
  73. package/cli/navigator-guide.js +73 -0
  74. package/cli/navigator-menu.js +20 -0
  75. package/cli/navigator.js +1 -0
  76. package/cli/patcher.d.ts +73 -0
  77. package/cli/patcher.js +249 -4
  78. package/cli/patcher.spec.js +148 -0
  79. package/cli/postinstall.js +27 -0
  80. package/cli/project-detector.js +20 -2
  81. package/cli/project-detector.spec.js +19 -0
  82. package/cli/reader.js +18 -4
  83. package/cli/scaffold.js +26 -4
  84. package/cli/search-commands.js +10 -8
  85. package/cli/search-queries.js +8 -3
  86. package/cli/search-schema.js +1 -0
  87. package/cli/search.d.ts +16 -0
  88. package/cli/search.js +103 -11
  89. package/cli/silence-warnings.js +17 -0
  90. package/cli/terminal.js +36 -1
  91. package/cli/verify.js +377 -0
  92. package/cli/verify.spec.js +98 -0
  93. package/docs/CHANGELOG.md +10 -0
  94. package/package.json +10 -5
  95. package/scripts/postinstall.mjs +27 -0
  96. package/scripts/pre-commit.sh +135 -0
  97. package/scripts/publish-both.mjs +108 -0
  98. package/cli/mcp.js +0 -326
@@ -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
+
@@ -36,31 +36,22 @@ export const groupViolationsBySeverity = (violations) => {
36
36
  };
37
37
 
38
38
  export const getSeverityBadge = (severity) => {
39
- switch (severity) {
40
- case 'CRITICAL':
41
- return `${RED}[CRITICAL]${RESET}`;
42
- case 'HIGH':
43
- return `${ORANGE}[HIGH]${RESET}`;
44
- case 'MEDIUM':
45
- return `${ORANGE}[MEDIUM]${RESET}`;
46
- case 'LOW':
47
- return `${YELLOW}[LOW]${RESET}`;
48
- default:
49
- return `[${severity}]`;
50
- }
39
+ const SEVERITY_BADGES = {
40
+ CRITICAL: `${RED}[CRITICAL]${RESET}`,
41
+ HIGH: `${ORANGE}[HIGH]${RESET}`,
42
+ MEDIUM: `${ORANGE}[MEDIUM]${RESET}`,
43
+ LOW: `${YELLOW}[LOW]${RESET}`
44
+ };
45
+ return SEVERITY_BADGES[severity] || `[${severity}]`;
51
46
  };
52
47
 
53
48
  export const getStatusBadge = (status) => {
54
- switch (status) {
55
- case 'PASSED':
56
- return `${GREEN}✔ PASS${RESET}`;
57
- case 'WARN':
58
- return `${YELLOW}⚠ WARN${RESET}`;
59
- case 'FAILED':
60
- return `${RED}✕ FAIL${RESET}`;
61
- default:
62
- return status;
63
- }
49
+ const STATUS_BADGES = {
50
+ PASSED: `${GREEN}✔ PASS${RESET}`,
51
+ WARN: `${YELLOW}⚠ WARN${RESET}`,
52
+ FAILED: `${RED}✕ FAIL${RESET}`
53
+ };
54
+ return STATUS_BADGES[status] || status;
64
55
  };
65
56
 
66
57
  export const resolveIndividualPillarGrade = (pillarData) => {
@@ -8,7 +8,7 @@ const COMPONENT_EXTENSIONS = new Set(['.vue', '.tsx', '.jsx']);
8
8
  const TEMPLATE_EXTENSIONS = new Set(['.vue', '.html', '.svelte']);
9
9
  const COMMENT_PREFIXES = ['//', '*', '/*'];
10
10
  const CONSOLE_LOG_METHODS = ['log', 'info', 'warn'];
11
- const NON_INTERACTIVE_FLAGS = ['--non-interactive', '--no-interactive', '--ci'];
11
+ const NON_INTERACTIVE_FLAGS = ['--non-interactive', '--no-interactive', '--ci', '--headless', '--yes', '-y'];
12
12
  const FORMAT_FLAGS = ['--markdown', '--md', '--json'];
13
13
  const GRADE_RANKS = { 'A+': 5, 'A': 4, 'B': 3, 'C': 2, 'D': 1, 'F': 0 };
14
14
 
@@ -135,5 +135,6 @@ export const isNonInteractiveSession = (rawArgs, env = process.env) => {
135
135
  const hasOutputFlag = rawArgs.some((arg) => arg.startsWith('--output=') || arg.startsWith('-o='));
136
136
  const hasFormatFlag = FORMAT_FLAGS.some((flag) => rawArgs.includes(flag));
137
137
  const hasCiEnv = Boolean(env.CI || env.GIT_DIR);
138
- return [hasCliFlag, hasOutputFlag, hasFormatFlag, hasCiEnv].some(Boolean);
138
+ const isNotTTY = Boolean(process.stdout && process.stdout.isTTY === false);
139
+ return [hasCliFlag, hasOutputFlag, hasFormatFlag, hasCiEnv, isNotTTY].some(Boolean);
139
140
  };
@@ -36,6 +36,26 @@ export const RULE_REGISTRY = {
36
36
  severity: 'CRITICAL',
37
37
  directive: 'Extract hardcoded API keys and secrets into environment variables or secrets manager'
38
38
  },
39
+ SECURITY_REVERSE_TABNABBING: {
40
+ pillar: PILLARS.PILLAR_9,
41
+ severity: 'MEDIUM',
42
+ directive: 'Add rel="noopener noreferrer" to external links with target="_blank" to prevent tab hijacking'
43
+ },
44
+ SECURITY_JAVASCRIPT_URL: {
45
+ pillar: PILLARS.PILLAR_9,
46
+ severity: 'CRITICAL',
47
+ directive: 'Do not use javascript: pseudo-protocol in links or action attributes; use event handlers'
48
+ },
49
+ SECURITY_DYNAMIC_CODE_EXECUTION: {
50
+ pillar: PILLARS.PILLAR_9,
51
+ severity: 'CRITICAL',
52
+ directive: 'Avoid eval(), new Function(), and string-based setTimeout/setInterval code execution'
53
+ },
54
+ SECURITY_SENSITIVE_LOGGING: {
55
+ pillar: PILLARS.PILLAR_9,
56
+ severity: 'HIGH',
57
+ directive: 'Never log sensitive credentials, tokens, or passwords to console diagnostics'
58
+ },
39
59
 
40
60
  // Pillar 10: Testing Discipline
41
61
  TEST_FAKE_GREEN: {
@@ -86,6 +106,11 @@ export const RULE_REGISTRY = {
86
106
  severity: 'CRITICAL',
87
107
  directive: 'Extract display states into computed descriptor objects or early returns'
88
108
  },
109
+ CONTROL_FLOW_DISPATCH_SWITCH: {
110
+ pillar: PILLARS.PILLAR_2,
111
+ severity: 'MEDIUM',
112
+ directive: 'Replace repetitive dispatch switch with an O(1) keyed dictionary or method map (Directive 3.E)'
113
+ },
89
114
  HOOK_SATURATION: {
90
115
  pillar: PILLARS.PILLAR_3,
91
116
  severity: 'HIGH',
@@ -0,0 +1,205 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert";
3
+ import { auditCode } from "./rules.js";
4
+
5
+ test("Audit Rules: flags repetitive dispatch switch statement as CONTROL_FLOW_DISPATCH_SWITCH", () => {
6
+ const code = `
7
+ export const resolveColor = (type) => {
8
+ switch (type) {
9
+ case "primary":
10
+ return "blue";
11
+ case "secondary":
12
+ return "gray";
13
+ case "danger":
14
+ return "red";
15
+ case "warning":
16
+ return "yellow";
17
+ default:
18
+ return "white";
19
+ }
20
+ };
21
+ `;
22
+
23
+ const violations = auditCode(code, "src/utils/colors.js", "src/utils/colors.js");
24
+ const dispatchViolation = violations.find((v) => v.rule === "CONTROL_FLOW_DISPATCH_SWITCH");
25
+ assert.ok(dispatchViolation, "Expected CONTROL_FLOW_DISPATCH_SWITCH violation");
26
+ assert.strictEqual(dispatchViolation.severity, "MEDIUM");
27
+ assert.ok(dispatchViolation.hazard.includes("Dispatch switch smell detected"));
28
+ });
29
+
30
+ test("Audit Rules: does NOT flag non-dispatch complex switch statements", () => {
31
+ const code = `
32
+ export const processOrder = (status, order) => {
33
+ switch (status) {
34
+ case "pending": {
35
+ validatePayment(order);
36
+ sendNotification(order.userId);
37
+ return queueForFulfillment(order);
38
+ }
39
+ case "completed": {
40
+ archiveOrder(order);
41
+ emitAnalytics("order_completed", order);
42
+ break;
43
+ }
44
+ default:
45
+ throw new Error("Invalid status");
46
+ }
47
+ };
48
+ `;
49
+
50
+ const violations = auditCode(code, "src/order.js", "src/order.js");
51
+ const dispatchViolation = violations.find((v) => v.rule === "CONTROL_FLOW_DISPATCH_SWITCH");
52
+ assert.strictEqual(dispatchViolation, undefined, "Complex switch should not be flagged as dispatch smell");
53
+ });
54
+
55
+ test("Audit Rules: clean keyed map implementation produces 0 violations", () => {
56
+ const code = `
57
+ const COLOR_MAP = {
58
+ primary: "blue",
59
+ secondary: "gray",
60
+ danger: "red",
61
+ warning: "yellow"
62
+ };
63
+
64
+ export const resolveColor = (type) => {
65
+ return Object.prototype.hasOwnProperty.call(COLOR_MAP, type) ? COLOR_MAP[type] : "white";
66
+ };
67
+ `;
68
+
69
+ const violations = auditCode(code, "src/utils/colors.js", "src/utils/colors.js");
70
+ const dispatchViolation = violations.find((v) => v.rule === "CONTROL_FLOW_DISPATCH_SWITCH");
71
+ assert.strictEqual(dispatchViolation, undefined);
72
+ });
73
+
74
+ test("Audit Security: flags external link target=_blank missing rel=noopener noreferrer in JSX", () => {
75
+ const code = `
76
+ export const ExternalLink = () => (
77
+ <a href="https://example.com" target="_blank">External</a>
78
+ );
79
+ `;
80
+ const violations = auditCode(code, "src/Link.tsx", "src/Link.tsx");
81
+ const violation = violations.find((v) => v.rule === "SECURITY_REVERSE_TABNABBING");
82
+ assert.ok(violation, "Expected SECURITY_REVERSE_TABNABBING violation in JSX");
83
+ assert.strictEqual(violation.severity, "MEDIUM");
84
+ });
85
+
86
+ test("Audit Security: passes external link with rel=noopener noreferrer in JSX", () => {
87
+ const code = `
88
+ export const ExternalLink = () => (
89
+ <a href="https://example.com" target="_blank" rel="noopener noreferrer">External</a>
90
+ );
91
+ `;
92
+ const violations = auditCode(code, "src/Link.tsx", "src/Link.tsx");
93
+ const violation = violations.find((v) => v.rule === "SECURITY_REVERSE_TABNABBING");
94
+ assert.strictEqual(violation, undefined);
95
+ });
96
+
97
+ test("Audit Security: flags target=_blank missing rel in Vue template", () => {
98
+ const code = `
99
+ <template>
100
+ <a href="https://example.com" target="_blank">External</a>
101
+ </template>
102
+ `;
103
+ const violations = auditCode(code, "src/Link.vue", "src/Link.vue");
104
+ const violation = violations.find((v) => v.rule === "SECURITY_REVERSE_TABNABBING");
105
+ assert.ok(violation, "Expected SECURITY_REVERSE_TABNABBING in Vue template");
106
+ });
107
+
108
+ test("Audit Security: flags javascript: pseudo-protocol in JSX href", () => {
109
+ const code = `
110
+ export const BadLink = () => (
111
+ <a href="javascript:alert(1)">Click Me</a>
112
+ );
113
+ `;
114
+ const violations = auditCode(code, "src/BadLink.tsx", "src/BadLink.tsx");
115
+ const violation = violations.find((v) => v.rule === "SECURITY_JAVASCRIPT_URL");
116
+ assert.ok(violation, "Expected SECURITY_JAVASCRIPT_URL violation");
117
+ assert.strictEqual(violation.severity, "CRITICAL");
118
+ });
119
+
120
+ test("Audit Security: flags javascript: in template attributes", () => {
121
+ const code = `
122
+ <template>
123
+ <form action="javascript:void(0)">
124
+ <button type="submit">Submit</button>
125
+ </form>
126
+ </template>
127
+ `;
128
+ const violations = auditCode(code, "src/Form.vue", "src/Form.vue");
129
+ const violation = violations.find((v) => v.rule === "SECURITY_JAVASCRIPT_URL");
130
+ assert.ok(violation, "Expected SECURITY_JAVASCRIPT_URL in template");
131
+ });
132
+
133
+ test("Audit Security: flags dynamic code execution via eval()", () => {
134
+ const code = `
135
+ export const runDynamic = (str: string) => {
136
+ return eval(str);
137
+ };
138
+ `;
139
+ const violations = auditCode(code, "src/eval.ts", "src/eval.ts");
140
+ const violation = violations.find((v) => v.rule === "SECURITY_DYNAMIC_CODE_EXECUTION");
141
+ assert.ok(violation, "Expected SECURITY_DYNAMIC_CODE_EXECUTION for eval()");
142
+ assert.strictEqual(violation.severity, "CRITICAL");
143
+ });
144
+
145
+ test("Audit Security: flags dynamic code execution via new Function()", () => {
146
+ const code = `
147
+ export const buildFn = (code: string) => {
148
+ return new Function("return " + code);
149
+ };
150
+ `;
151
+ const violations = auditCode(code, "src/fn.ts", "src/fn.ts");
152
+ const violation = violations.find((v) => v.rule === "SECURITY_DYNAMIC_CODE_EXECUTION");
153
+ assert.ok(violation, "Expected SECURITY_DYNAMIC_CODE_EXECUTION for new Function()");
154
+ assert.strictEqual(violation.severity, "CRITICAL");
155
+ });
156
+
157
+ test("Audit Security: flags string-based execution in setTimeout", () => {
158
+ const code = `
159
+ export const scheduleCode = () => {
160
+ setTimeout("alert('hello')", 1000);
161
+ };
162
+ `;
163
+ const violations = auditCode(code, "src/timer.ts", "src/timer.ts");
164
+ const violation = violations.find((v) => v.rule === "SECURITY_DYNAMIC_CODE_EXECUTION");
165
+ assert.ok(violation, "Expected SECURITY_DYNAMIC_CODE_EXECUTION for string setTimeout");
166
+ });
167
+
168
+ test("Audit Security: does NOT flag function callback setTimeout", () => {
169
+ const code = `
170
+ export const scheduleCode = () => {
171
+ const timer = setTimeout(() => {
172
+ // legitimate callback
173
+ }, 1000);
174
+ return () => clearTimeout(timer);
175
+ };
176
+ `;
177
+ const violations = auditCode(code, "src/timer.ts", "src/timer.ts");
178
+ const violation = violations.find((v) => v.rule === "SECURITY_DYNAMIC_CODE_EXECUTION");
179
+ assert.strictEqual(violation, undefined);
180
+ });
181
+
182
+ test("Audit Security: flags sensitive variable logging in console", () => {
183
+ const code = `
184
+ export const debugAuth = (authToken: string, user: any) => {
185
+ console.log("Authenticated with token:", authToken);
186
+ console.info("User details:", { password: user.password });
187
+ };
188
+ `;
189
+ const violations = auditCode(code, "src/auth.ts", "src/auth.ts");
190
+ const loggingViolations = violations.filter((v) => v.rule === "SECURITY_SENSITIVE_LOGGING");
191
+ assert.strictEqual(loggingViolations.length >= 1, true, "Expected SECURITY_SENSITIVE_LOGGING violation");
192
+ assert.strictEqual(loggingViolations[0].severity, "HIGH");
193
+ });
194
+
195
+ test("Audit Security: does NOT flag normal non-sensitive console logging", () => {
196
+ const code = `
197
+ export const debugCount = (itemCount: number) => {
198
+ console.log("Items rendered:", itemCount);
199
+ };
200
+ `;
201
+ const violations = auditCode(code, "src/items.ts", "src/items.ts");
202
+ const violation = violations.find((v) => v.rule === "SECURITY_SENSITIVE_LOGGING");
203
+ assert.strictEqual(violation, undefined);
204
+ });
205
+
@@ -102,3 +102,14 @@ test('runAudit: fast mode returns valid health metrics with zero AST traversal',
102
102
  assert.ok(typeof report.health.score === 'number');
103
103
  assert.ok(typeof report.health.grade === 'string');
104
104
  });
105
+
106
+ test('runAuditPreflight: headless flag resolves immediately without interactive prompts', async () => {
107
+ const result = await runAuditPreflight(['--headless'], {
108
+ defaultDir: 'cli',
109
+ cwd: process.cwd()
110
+ });
111
+
112
+ assert.strictEqual(result.targetDir, 'cli');
113
+ assert.strictEqual(result.fast, false);
114
+ assert.strictEqual(result.fileList, null);
115
+ });
package/cli/build.d.ts CHANGED
@@ -2,7 +2,8 @@ import type { BuildAuditReport, BuildOptions } from './build/types.js';
2
2
 
3
3
  export function runBuildAudit(
4
4
  rawArgs?: readonly string[],
5
- isCli?: boolean
5
+ isCli?: boolean,
6
+ options?: { readonly print?: boolean; readonly silent?: boolean }
6
7
  ): Promise<BuildAuditReport>;
7
8
 
8
9
  export {
package/cli/build.js CHANGED
@@ -20,16 +20,17 @@ const parseCommandFromArgs = (args) => {
20
20
  return candidate ? candidate.trim() : null;
21
21
  };
22
22
 
23
- export const runBuildAudit = async (rawArgs = [], isCli = false) => {
23
+ export const runBuildAudit = async (rawArgs = [], isCli = false, options = {}) => {
24
24
  const isJson = rawArgs.includes('--json');
25
- const isSilent = rawArgs.includes('--silent');
25
+ const isSilent = rawArgs.includes('--silent') || options.silent === true;
26
26
  const isRaw = rawArgs.includes('--raw');
27
27
  const isSummary = rawArgs.includes('--summary');
28
+ const shouldPrint = options.print !== false;
28
29
 
29
30
  const customCommand = parseCommandFromArgs(rawArgs);
30
31
  const command = detectProjectBuildCommand(customCommand, process.cwd());
31
32
 
32
- const shouldPrintStart = !isJson && !isSilent && !isRaw;
33
+ const shouldPrintStart = shouldPrint && !isJson && !isSilent && !isRaw;
33
34
  if (shouldPrintStart) {
34
35
  process.stdout.write(`${ANSI.CYAN}Auditing build:${ANSI.RESET} ${ANSI.DIM}${command}${ANSI.RESET}\n`);
35
36
  }
@@ -39,13 +40,15 @@ export const runBuildAudit = async (rawArgs = [], isCli = false) => {
39
40
  const report = groupBuildDiagnostics(rawDiagnostics, executionResult);
40
41
 
41
42
  if (isJson) {
42
- process.stdout.write(formatJsonBuildReport(report) + '\n');
43
+ if (shouldPrint) {
44
+ process.stdout.write(formatJsonBuildReport(report) + '\n');
45
+ }
43
46
  if (isCli) process.exit(report.exitCode);
44
47
  return report;
45
48
  }
46
49
 
47
50
  const terminalOutput = formatTerminalBuildReport(report, { silent: isSilent, summary: isSummary });
48
- if (terminalOutput) {
51
+ if (terminalOutput && shouldPrint) {
49
52
  process.stdout.write(terminalOutput);
50
53
  }
51
54
 
@@ -0,0 +1,14 @@
1
+ export interface ColumnarData<T = any> {
2
+ readonly cols: readonly string[];
3
+ readonly rows: readonly T[][];
4
+ }
5
+
6
+ export declare function toColumnar<T extends Record<string, any>>(
7
+ items?: readonly T[],
8
+ cols?: readonly string[],
9
+ accessors?: Record<string, (item: T) => any>
10
+ ): ColumnarData;
11
+
12
+ export declare function fromColumnar<T extends Record<string, any>>(
13
+ columnar?: ColumnarData
14
+ ): readonly T[];
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Chemical X Protocol: Columnar Serialization Utility
3
+ * Converts object collections into compact single-schema matrices
4
+ * to eliminate repeated dictionary keys and slash token consumption.
5
+ */
6
+
7
+ /**
8
+ * Transforms an array of objects into a single-schema columnar structure.
9
+ *
10
+ * @param {readonly object[]} items Array of record objects.
11
+ * @param {readonly string[]} cols Column names to extract.
12
+ * @param {Record<string, (item: any) => any>} [accessors={}] Optional field transformers.
13
+ * @returns {{ cols: readonly string[], rows: readonly any[][] }}
14
+ */
15
+ export const toColumnar = (items = [], cols = [], accessors = {}) => {
16
+ if (!items || items.length === 0) {
17
+ return { cols, rows: [] };
18
+ }
19
+
20
+ const rows = items.map((item) => {
21
+ return cols.map((col) => {
22
+ const accessor = accessors[col];
23
+ if (typeof accessor === 'function') {
24
+ return accessor(item);
25
+ }
26
+ return item[col];
27
+ });
28
+ });
29
+
30
+ return { cols, rows };
31
+ };
32
+
33
+ /**
34
+ * Reconstructs an array of objects from a columnar structure.
35
+ *
36
+ * @param {{ cols: readonly string[], rows: readonly any[][] }} columnar
37
+ * @returns {readonly Record<string, any>[]}
38
+ */
39
+ export const fromColumnar = (columnar) => {
40
+ if (!columnar || !columnar.cols || !columnar.rows) {
41
+ return [];
42
+ }
43
+
44
+ const { cols, rows } = columnar;
45
+ return rows.map((row) => {
46
+ const obj = {};
47
+ cols.forEach((col, idx) => {
48
+ obj[col] = row[idx];
49
+ });
50
+ return obj;
51
+ });
52
+ };
@@ -0,0 +1,49 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert';
3
+ import { toColumnar, fromColumnar } from './columnar.js';
4
+
5
+ test('toColumnar: converts array of objects into cols and rows', () => {
6
+ const items = [
7
+ { file: 'src/a.ts', tier: 'atom', lines: 40 },
8
+ { file: 'src/b.ts', tier: 'molecule', lines: 75 }
9
+ ];
10
+
11
+ const result = toColumnar(items, ['file', 'tier', 'lines']);
12
+
13
+ assert.deepStrictEqual(result.cols, ['file', 'tier', 'lines']);
14
+ assert.strictEqual(result.rows.length, 2);
15
+ assert.deepStrictEqual(result.rows[0], ['src/a.ts', 'atom', 40]);
16
+ assert.deepStrictEqual(result.rows[1], ['src/b.ts', 'molecule', 75]);
17
+ });
18
+
19
+ test('toColumnar: handles empty or missing arrays cleanly', () => {
20
+ assert.deepStrictEqual(toColumnar([], ['file', 'tier']), { cols: ['file', 'tier'], rows: [] });
21
+ assert.deepStrictEqual(toColumnar(null, ['file']), { cols: ['file'], rows: [] });
22
+ });
23
+
24
+ test('toColumnar: supports custom accessor functions for nested fields', () => {
25
+ const items = [
26
+ { file: 'src/card.vue', symbols: [{ name: 'UserCard' }, { name: 'CardBadge' }] }
27
+ ];
28
+
29
+ const result = toColumnar(items, ['file', 'symbols'], {
30
+ symbols: (item) => item.symbols.map((s) => s.name)
31
+ });
32
+
33
+ assert.deepStrictEqual(result.rows[0], ['src/card.vue', ['UserCard', 'CardBadge']]);
34
+ });
35
+
36
+ test('fromColumnar: reconstructs array of objects from columnar data', () => {
37
+ const columnar = {
38
+ cols: ['file', 'tier', 'lines'],
39
+ rows: [
40
+ ['src/a.ts', 'atom', 40],
41
+ ['src/b.ts', 'molecule', 75]
42
+ ]
43
+ };
44
+
45
+ const items = fromColumnar(columnar);
46
+ assert.strictEqual(items.length, 2);
47
+ assert.deepStrictEqual(items[0], { file: 'src/a.ts', tier: 'atom', lines: 40 });
48
+ assert.deepStrictEqual(items[1], { file: 'src/b.ts', tier: 'molecule', lines: 75 });
49
+ });