@chemx/starter-kit 26.9.20-408 → 26.9.21-8
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 +146 -0
- package/blueprints/_package.json +3 -1
- package/blueprints/workflows/chemx-audit.yml +1 -1
- package/cli/audit/extended-text-patterns.js +123 -0
- package/cli/audit/rules-registry.js +5 -0
- package/cli/blast-radius.spec.js +113 -0
- package/cli/commands/cmd-audit.js +201 -0
- package/cli/commands/cmd-router.js +190 -0
- package/cli/commands-schema.js +198 -0
- package/cli/create.js +72 -0
- package/cli/create.spec.js +200 -0
- package/cli/embeddings/vectorizer.js +68 -0
- package/cli/embeddings.spec.js +92 -0
- package/cli/generator-framework.spec.js +243 -0
- package/cli/generator-templates/archetypes/collection-archetypes.js +45 -17
- package/cli/generator-templates/archetypes/domain-archetypes.js +2 -2
- package/cli/generator-templates/archetypes/index.js +46 -7
- package/cli/generator-templates/controller.js +157 -3
- package/cli/generator-templates/hooks.js +27 -3
- package/cli/generator-templates/react.js +3 -3
- package/cli/generator-templates/specs.js +48 -2
- package/cli/generator-templates/svelte.js +89 -4
- package/cli/generator-templates/types.js +14 -5
- package/cli/generator-templates/views.js +28 -3
- package/cli/generator-templates/vue.js +90 -4
- package/cli/generator-templates.spec.js +11 -0
- package/cli/generator.js +82 -48
- package/cli/generator.spec.js +57 -0
- package/cli/help.js +37 -233
- package/cli/help.spec.js +56 -0
- package/cli/index.js +95 -381
- package/cli/installer-templates.js +1 -1
- package/cli/installer.js +17 -9
- package/cli/license.js +4 -3
- package/cli/mcp/antigravity.js +21 -6
- package/cli/mcp/index.js +10 -1
- package/cli/mcp/manifests.js +51 -5
- package/cli/mcp/server.js +4 -0
- package/cli/mcp/tools-search.js +64 -7
- package/cli/mcp/tools-team.js +16 -1
- package/cli/mcp/tools.js +7 -3
- package/cli/mutators.js +32 -10
- package/cli/patcher.js +13 -3
- package/cli/patcher.spec.js +23 -0
- package/cli/pillars-schema.js +150 -0
- package/cli/pillars-wizard.js +158 -0
- package/cli/pillars.spec.js +138 -0
- package/cli/project-detector.js +54 -13
- package/cli/scaffold.js +4 -20
- package/cli/search-commands-graph.js +82 -0
- package/cli/search-commands-semantic.js +98 -0
- package/cli/search-commands.js +4 -0
- package/cli/search-db.js +66 -4
- package/cli/search-queries-graph.js +109 -0
- package/cli/search-queries-semantic.js +137 -0
- package/cli/search-queries.js +4 -0
- package/cli/search-schema.js +41 -0
- package/cli/search.js +88 -4
- package/cli/team/team-commands.js +134 -7
- package/cli/team/team-triage.js +109 -7
- package/cli/team/team.spec.js +129 -2
- package/cli/ui-e2e-verification.spec.js +2 -1
- package/package.json +3 -3
- package/scripts/publish-both.mjs +6 -6
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cmd-router.js — CLI command dispatch table.
|
|
3
|
+
* Single responsibility: map first-arg tokens to their lazy-loaded command handlers.
|
|
4
|
+
* Extracted from cli/index.js per Directive 1.A (Monolith Decomposition).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { printHelp } from '../help.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Dispatch the resolved CLI command to its handler module.
|
|
11
|
+
* @param {string} firstArg
|
|
12
|
+
* @param {string[]} rawArgs
|
|
13
|
+
* @param {(dir: string|null, isCli: boolean) => Promise<object>} runAudit
|
|
14
|
+
* @param {() => string} getPackageVersion
|
|
15
|
+
* @param {(arg: string) => boolean} isCapsulePrefix
|
|
16
|
+
*/
|
|
17
|
+
export const dispatchCommand = async (firstArg, rawArgs, runAudit, getPackageVersion, isCapsulePrefix) => {
|
|
18
|
+
switch (firstArg) {
|
|
19
|
+
case 'team':
|
|
20
|
+
case 'swarm':
|
|
21
|
+
case 'feed': {
|
|
22
|
+
const { runTeamCli } = await import('../team/index.js');
|
|
23
|
+
runTeamCli(rawArgs.slice(1), true);
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
case 'mcp':
|
|
27
|
+
case 'mcp-server':
|
|
28
|
+
case 'server': {
|
|
29
|
+
const { runMcpServer } = await import('../mcp/index.js');
|
|
30
|
+
await runMcpServer(rawArgs.slice(1));
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
case 'read':
|
|
34
|
+
case 'view': {
|
|
35
|
+
const { runReaderCli } = await import('../reader.js');
|
|
36
|
+
runReaderCli(rawArgs.slice(1), true);
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
case 'patch':
|
|
40
|
+
case 'edit': {
|
|
41
|
+
const { runPatcherCli } = await import('../patcher.js');
|
|
42
|
+
runPatcherCli(rawArgs.slice(1), true);
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
case 'write': {
|
|
46
|
+
const { runWriterCli } = await import('../patcher.js');
|
|
47
|
+
runWriterCli(rawArgs.slice(1), true);
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
case 'search':
|
|
51
|
+
case 'q':
|
|
52
|
+
case 'query':
|
|
53
|
+
case 'find': {
|
|
54
|
+
const { runSearch } = await import('../search.js');
|
|
55
|
+
await runSearch(rawArgs.slice(1), true);
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
case 'build':
|
|
59
|
+
case 'run':
|
|
60
|
+
case 'wrap': {
|
|
61
|
+
const { runBuildAudit } = await import('../build.js');
|
|
62
|
+
await runBuildAudit(rawArgs.slice(1), true);
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
case 'audit': {
|
|
66
|
+
const posDir = (rawArgs[1] && !rawArgs[1].startsWith('-')) ? rawArgs[1] : null;
|
|
67
|
+
await runAudit(posDir, true);
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
case 'init': {
|
|
71
|
+
const { runInit } = await import('../scaffold.js');
|
|
72
|
+
await runInit(rawArgs[1] || 'src/chemical-x', rawArgs, runAudit);
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
case 'create':
|
|
76
|
+
case 'scaffold': {
|
|
77
|
+
const { runScaffold } = await import('../scaffold.js');
|
|
78
|
+
const nonFlagArgs = rawArgs.slice(1).filter((arg) => !arg.startsWith('-'));
|
|
79
|
+
await runScaffold(nonFlagArgs[0], rawArgs, runAudit);
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
case 'hook':
|
|
83
|
+
case 'hooks':
|
|
84
|
+
case 'install-hooks':
|
|
85
|
+
case 'setup-ci': {
|
|
86
|
+
const { runInstallWizard } = await import('../installer.js');
|
|
87
|
+
await runInstallWizard(rawArgs[1] || process.cwd());
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
case 'pillars':
|
|
91
|
+
case 'rules':
|
|
92
|
+
case 'config:pillars': {
|
|
93
|
+
const { runPillarsWizard } = await import('../pillars-wizard.js');
|
|
94
|
+
await runPillarsWizard(rawArgs.slice(1), process.cwd());
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
case 'check': {
|
|
98
|
+
const { handleCheckCommand } = await import('../search.js');
|
|
99
|
+
handleCheckCommand(rawArgs[1], { isJson: rawArgs.includes('--json'), isCli: true });
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
case 'add:prop':
|
|
103
|
+
case 'add:state':
|
|
104
|
+
case 'add:action':
|
|
105
|
+
case 'fix': {
|
|
106
|
+
const { runMutatorCli } = await import('../mutators.js');
|
|
107
|
+
await runMutatorCli(rawArgs, true);
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
case 'add': {
|
|
111
|
+
if (['prop', 'state', 'action'].includes(rawArgs[1])) {
|
|
112
|
+
const { runMutatorCli } = await import('../mutators.js');
|
|
113
|
+
await runMutatorCli(rawArgs, true);
|
|
114
|
+
} else {
|
|
115
|
+
const { runGenerateWizard } = await import('../scaffold.js');
|
|
116
|
+
await runGenerateWizard(rawArgs.slice(1));
|
|
117
|
+
}
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
case 'g':
|
|
121
|
+
case 'gen':
|
|
122
|
+
case 'generate':
|
|
123
|
+
case 'capsule': {
|
|
124
|
+
const { runGenerateWizard } = await import('../scaffold.js');
|
|
125
|
+
await runGenerateWizard(rawArgs.slice(1));
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
case 'badge':
|
|
129
|
+
case 'badges': {
|
|
130
|
+
const { runBadgeCommand } = await import('../badge.js');
|
|
131
|
+
await runBadgeCommand(rawArgs.slice(1));
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
case 'install-mcp':
|
|
135
|
+
case 'setup-mcp': {
|
|
136
|
+
const { runMcpInstaller } = await import('../mcp/index.js');
|
|
137
|
+
await runMcpInstaller(rawArgs.slice(1));
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
case '-v':
|
|
141
|
+
case '--version':
|
|
142
|
+
case 'version':
|
|
143
|
+
process.stdout.write(`create-chemx v${getPackageVersion()}\n`);
|
|
144
|
+
break;
|
|
145
|
+
case 'verify':
|
|
146
|
+
case 'check:all': {
|
|
147
|
+
const { runProjectVerify } = await import('../verify.js');
|
|
148
|
+
await runProjectVerify(rawArgs.slice(1), true);
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
case 'typecheck':
|
|
152
|
+
case 'check:types':
|
|
153
|
+
case 'tsc': {
|
|
154
|
+
const { runTypecheckAudit } = await import('../verify.js');
|
|
155
|
+
await runTypecheckAudit(rawArgs.slice(1), true);
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
case 'test':
|
|
159
|
+
case 'tests':
|
|
160
|
+
case 'check:test': {
|
|
161
|
+
const { runTestAudit } = await import('../verify.js');
|
|
162
|
+
await runTestAudit(rawArgs.slice(1), true);
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
case 'ui':
|
|
166
|
+
case 'preview':
|
|
167
|
+
case 'dashboard': {
|
|
168
|
+
const { startUiServer } = await import('../ui-server.js');
|
|
169
|
+
const portArg = rawArgs.find((a) => a.startsWith('--port='));
|
|
170
|
+
const port = portArg ? parseInt(portArg.split('=')[1], 10) : 4173;
|
|
171
|
+
await startUiServer({ port, isCli: true, cwd: process.cwd() });
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
case 'help':
|
|
175
|
+
case '--help':
|
|
176
|
+
case '-h':
|
|
177
|
+
printHelp();
|
|
178
|
+
break;
|
|
179
|
+
default: {
|
|
180
|
+
if (isCapsulePrefix(firstArg)) {
|
|
181
|
+
const { runGenerateWizard } = await import('../scaffold.js');
|
|
182
|
+
await runGenerateWizard(rawArgs);
|
|
183
|
+
} else {
|
|
184
|
+
process.stderr.write(`Unknown command "${firstArg}". Run --help for usage.\n`);
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
};
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chemical X Protocol: Canonical Command Schema
|
|
3
|
+
* Single source of truth for CLI commands, aliases, flags, descriptions, and examples.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const COMMANDS_SCHEMA = [
|
|
7
|
+
{
|
|
8
|
+
name: 'search',
|
|
9
|
+
aliases: ['q', 'query', 'find'],
|
|
10
|
+
usage: 'npx chemx search [query] [options]',
|
|
11
|
+
summary: 'Architecture-aware AST codebase query engine powered by SQLite (.chemx/index.db).',
|
|
12
|
+
description: 'Instantly indexes component tiers (atom/molecule/organism), exported symbols, props, and hooks. Designed for AI agents to replace token-heavy grep/cat scans.',
|
|
13
|
+
flags: [
|
|
14
|
+
{ flag: '--json', desc: 'Minified JSON format for LLM agents' },
|
|
15
|
+
{ flag: '--columnar', desc: 'Token-compact columnar format (cols/rows) for agent pipelines' },
|
|
16
|
+
{ flag: '-i, --inspect', desc: 'Inspect props and hooks without full source' },
|
|
17
|
+
{ flag: '--tier=<tier>', desc: 'Filter by tier: atom, molecule, organism, hook, view' },
|
|
18
|
+
{ flag: '--blast-radius', desc: 'Map direct consumers, transitive dependents & impacted tiers (aliases: --blast, --impact)' },
|
|
19
|
+
{ flag: '--max-depth=<N>', desc: 'Max depth for blast radius traversal (default: 5)' },
|
|
20
|
+
{ flag: '--semantic', desc: 'Concept search via vector cosine similarity' },
|
|
21
|
+
{ flag: '--hybrid', desc: 'Blended BM25 keyword + Vector RRF ranking' },
|
|
22
|
+
{ flag: '--hazards', desc: 'Query architectural rule violations directly (flags: --rule=<id>, --critical)' },
|
|
23
|
+
{ flag: '--pack', desc: 'Assemble token-packed context bundle for target symbol or file' },
|
|
24
|
+
{ flag: '--reindex', desc: 'Force re-index before running query' }
|
|
25
|
+
],
|
|
26
|
+
examples: [
|
|
27
|
+
'npx chemx search "badge"',
|
|
28
|
+
'pnpm q a-button --blast-radius --json',
|
|
29
|
+
'pnpm q "button click handler" --semantic',
|
|
30
|
+
'pnpm q "useAttentionCardController" --hybrid --json',
|
|
31
|
+
'pnpm q "useTheme" --inspect',
|
|
32
|
+
'npx chemx search "card" --tier=molecule'
|
|
33
|
+
]
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'read',
|
|
37
|
+
aliases: ['view'],
|
|
38
|
+
usage: 'npx chemx read <file> [options]',
|
|
39
|
+
summary: 'Token-minified file reader with AST outline extraction.',
|
|
40
|
+
description: 'Extracts structural AST outlines, stripped comments, line ranges, or targeted symbol declarations to minimize LLM context token consumption.',
|
|
41
|
+
flags: [
|
|
42
|
+
{ flag: '--outline', desc: 'Signatures only (80%+ token savings)' },
|
|
43
|
+
{ flag: '--symbol=<name>', desc: 'Target a specific symbol definition' },
|
|
44
|
+
{ flag: '--connections', desc: 'Include caller graph and dependent references alongside symbol' },
|
|
45
|
+
{ flag: '--strip-comments', desc: 'Remove all code comments' },
|
|
46
|
+
{ flag: '--compact', desc: 'Remove blank lines and indentation' },
|
|
47
|
+
{ flag: '--start=<N>', desc: 'Starting line number' },
|
|
48
|
+
{ flag: '--end=<N>', desc: 'Ending line number' },
|
|
49
|
+
{ flag: '--json', desc: 'Output metadata as JSON' }
|
|
50
|
+
],
|
|
51
|
+
examples: [
|
|
52
|
+
'npx chemx read src/store.ts --outline',
|
|
53
|
+
'npx chemx read api.ts --symbol=login --connections',
|
|
54
|
+
'npx chemx read src/ui/atoms/a-button/types.d.ts --symbol=ButtonVariant'
|
|
55
|
+
]
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: 'patch',
|
|
59
|
+
aliases: ['edit'],
|
|
60
|
+
usage: 'npx chemx patch <file> [options]',
|
|
61
|
+
summary: 'Surgically patch a target file using exact search and replacement blocks.',
|
|
62
|
+
description: 'Replaces targeted text blocks with automatic SQLite micro-indexing and architectural line budget verification.',
|
|
63
|
+
flags: [
|
|
64
|
+
{ flag: '--target="<old>"', desc: 'Exact text block to replace' },
|
|
65
|
+
{ flag: '--replacement="<new>"', desc: 'New replacement content' },
|
|
66
|
+
{ flag: '--multiple', desc: 'Allow replacing multiple occurrences' },
|
|
67
|
+
{ flag: '--dry-run', desc: 'Preview patch without writing to disk' },
|
|
68
|
+
{ flag: '--json', desc: 'Output result as minified JSON' }
|
|
69
|
+
],
|
|
70
|
+
examples: [
|
|
71
|
+
'npx chemx patch src/api.ts --target="v1" --replacement="v2"',
|
|
72
|
+
'npx chemx patch src/App.tsx --target="oldCode" --replacement="newCode" --dry-run'
|
|
73
|
+
]
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: 'mcp',
|
|
77
|
+
aliases: ['server', 'mcp-server'],
|
|
78
|
+
usage: 'npx chemx mcp [options]',
|
|
79
|
+
summary: 'Launch the native Chemical X Model Context Protocol (MCP) Stdio server.',
|
|
80
|
+
description: 'Exposes token-minified tools (chemx_q, chemx_read, chemx_patch, chemx_audit, chemx_team_task) over JSON-RPC 2.0 stdio transport.',
|
|
81
|
+
flags: [
|
|
82
|
+
{ flag: '--install', desc: 'Install and register MCP server in host client settings' },
|
|
83
|
+
{ flag: '--sync', desc: 'Sync Antigravity MCP tool schema definitions and instructions' }
|
|
84
|
+
],
|
|
85
|
+
examples: [
|
|
86
|
+
'npx chemx mcp',
|
|
87
|
+
'pnpm chemx:mcp',
|
|
88
|
+
'npx chemx mcp --sync'
|
|
89
|
+
]
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: 'build',
|
|
93
|
+
aliases: ['run', 'wrap'],
|
|
94
|
+
usage: 'npx chemx build [options] [-- <command>]',
|
|
95
|
+
summary: 'Wrap and audit build scripts with token-conserving silent execution.',
|
|
96
|
+
description: 'Suppresses multi-thousand-line compiler noise, catalogs diagnostics into grouped categories, and provides actionable hints for AI agents.',
|
|
97
|
+
flags: [
|
|
98
|
+
{ flag: '--json', desc: 'Minified JSON format with categorised diagnostics' },
|
|
99
|
+
{ flag: '--command="<cmd>"', desc: 'Explicit build command (default: npm run build)' }
|
|
100
|
+
],
|
|
101
|
+
examples: [
|
|
102
|
+
'npx chemx build',
|
|
103
|
+
'npx chemx build -- vite build'
|
|
104
|
+
]
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: 'audit',
|
|
108
|
+
aliases: [],
|
|
109
|
+
usage: 'npx chemx audit [directory] [options]',
|
|
110
|
+
summary: 'Execute full architectural AST audit on codebase.',
|
|
111
|
+
description: 'Validates line budgets, molecular separation, zero-raw-DOM rules, and populates SQLite index tables.',
|
|
112
|
+
flags: [
|
|
113
|
+
{ flag: '--triage', desc: 'Auto-convert unassigned hazards into team tasks' },
|
|
114
|
+
{ flag: '--strict', desc: 'Fail on any architectural hazard' },
|
|
115
|
+
{ flag: '--json', desc: 'Output structured audit report as JSON' },
|
|
116
|
+
{ flag: '--markdown', desc: 'Generate Markdown audit report' },
|
|
117
|
+
{ flag: '--min-grade=<A|B|C|D|F>', desc: 'Minimum acceptable architectural grade' }
|
|
118
|
+
],
|
|
119
|
+
examples: [
|
|
120
|
+
'npx chemx audit',
|
|
121
|
+
'npx chemx audit --triage',
|
|
122
|
+
'npx chemx audit --strict --min-grade=A'
|
|
123
|
+
]
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
name: 'generate',
|
|
127
|
+
aliases: ['g', 'gen', 'capsule'],
|
|
128
|
+
usage: 'npx chemx generate [tier] <name> [options]',
|
|
129
|
+
summary: 'Scaffold crystalline molecular capsules under 100 lines.',
|
|
130
|
+
description: 'Generates self-contained component capsules with co-located controllers, granular domain types, styles, and specs.',
|
|
131
|
+
flags: [
|
|
132
|
+
{ flag: '--desc="<text>"', desc: 'Describe functionality to tailor archetype and state' },
|
|
133
|
+
{ flag: '--dry-run', desc: 'Preview planned files and lines without touching disk' },
|
|
134
|
+
{ flag: '--tier=<tier>', desc: 'Specify tier: atom, molecule, organism, hook, view' },
|
|
135
|
+
{ flag: '--framework=<id>', desc: 'Framework flavor: react, vue, svelte' },
|
|
136
|
+
{ flag: '--lean', desc: 'Generate minimal capsule without controller/spec' }
|
|
137
|
+
],
|
|
138
|
+
examples: [
|
|
139
|
+
'npx chemx generate m-task-list --framework=react',
|
|
140
|
+
'npx chemx generate m-task-list --desc="add, toggle, remove items"',
|
|
141
|
+
'npx chemx generate m-task-list --dry-run'
|
|
142
|
+
]
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
name: 'team',
|
|
146
|
+
aliases: ['swarm', 'feed'],
|
|
147
|
+
usage: 'npx chemx team <status|feed|task|lock|post> [options]',
|
|
148
|
+
summary: 'Multi-agent coordination layer backed by SQLite (.chemx/index.db).',
|
|
149
|
+
description: 'Manages autonomous agent task queues, status transitions, AST verification on completion, and file locks.',
|
|
150
|
+
flags: [
|
|
151
|
+
{ flag: '--as=<@handle>', desc: 'Agent identity handle (e.g. @claude, @antigravity)' },
|
|
152
|
+
{ flag: '--target=<path>', desc: 'Target file path for task AST verification' },
|
|
153
|
+
{ flag: '--no-target-confirm', desc: 'Confirm completing a task that has no target file without verification' },
|
|
154
|
+
{ flag: '--force, -f', desc: 'Force complete task even if hazards remain' },
|
|
155
|
+
{ flag: '--tokens=<N>', desc: 'Prompt tokens consumed by task' },
|
|
156
|
+
{ flag: '--cost=<USD>', desc: 'Estimated dollar cost for task' },
|
|
157
|
+
{ flag: '--json', desc: 'Output data as JSON' }
|
|
158
|
+
],
|
|
159
|
+
examples: [
|
|
160
|
+
'npx chemx team task list',
|
|
161
|
+
'npx chemx team task triage',
|
|
162
|
+
'npx chemx team task add "Implement dark mode toggle" --prio=1',
|
|
163
|
+
'npx chemx team task claim 1 --as=@coder',
|
|
164
|
+
'npx chemx team task done 1 --as=@coder --force'
|
|
165
|
+
]
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
name: 'verify',
|
|
169
|
+
aliases: ['check:all'],
|
|
170
|
+
usage: 'npx chemx verify [options]',
|
|
171
|
+
summary: 'Runs full verification pipeline: AST audit, TypeScript typecheck, and test suite.',
|
|
172
|
+
description: 'Compact single-card verification returning token-efficient output for AI agents (~45 tokens when green).',
|
|
173
|
+
flags: [
|
|
174
|
+
{ flag: '--json', desc: 'Minified JSON format' }
|
|
175
|
+
],
|
|
176
|
+
examples: [
|
|
177
|
+
'npx chemx verify',
|
|
178
|
+
'pnpm chemx verify --json'
|
|
179
|
+
]
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
name: 'pillars',
|
|
183
|
+
aliases: ['config:pillars', 'rules'],
|
|
184
|
+
usage: 'npx chemx pillars [options]',
|
|
185
|
+
summary: 'Interactive wizard to configure architectural pillars and agent steering directives.',
|
|
186
|
+
description: 'Customize which pillars and directives (AGENTS.md, .cursorrules) are installed in your codebase.',
|
|
187
|
+
flags: [
|
|
188
|
+
{ flag: '--preset=<recommended|strict|minimal|none>', desc: 'Apply a predefined pillar configuration preset' },
|
|
189
|
+
{ flag: '--dry-run', desc: 'Preview pillar configuration without writing files' },
|
|
190
|
+
{ flag: '--json', desc: 'Output configuration as JSON' }
|
|
191
|
+
],
|
|
192
|
+
examples: [
|
|
193
|
+
'npx chemx pillars',
|
|
194
|
+
'npx chemx pillars --preset=recommended',
|
|
195
|
+
'npx chemx pillars --preset=minimal --dry-run'
|
|
196
|
+
]
|
|
197
|
+
}
|
|
198
|
+
];
|
package/cli/create.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import { runScaffold } from './scaffold.js';
|
|
5
|
+
import { runAudit } from './index.js';
|
|
6
|
+
import { sanitizeOutputStreams } from './terminal.js';
|
|
7
|
+
import { installGlobalErrorCatcher } from './errors/index.js';
|
|
8
|
+
|
|
9
|
+
sanitizeOutputStreams();
|
|
10
|
+
installGlobalErrorCatcher();
|
|
11
|
+
|
|
12
|
+
const rawArgs = process.argv.slice(2);
|
|
13
|
+
|
|
14
|
+
if (rawArgs.includes('--help') || rawArgs.includes('-h')) {
|
|
15
|
+
process.stdout.write(`
|
|
16
|
+
Chemical X Protocol: Project Scaffolder
|
|
17
|
+
|
|
18
|
+
USAGE
|
|
19
|
+
npm create chemx [directory] [options]
|
|
20
|
+
npx create-chemx [directory] [options]
|
|
21
|
+
|
|
22
|
+
OPTIONS
|
|
23
|
+
--yes, -y Skip interactive prompts and scaffold Community Edition immediately
|
|
24
|
+
--headless Run in headless mode for CI/CD and AI agent automation
|
|
25
|
+
-h, --help Show this help message
|
|
26
|
+
-v, --version Show version number
|
|
27
|
+
|
|
28
|
+
EXAMPLES
|
|
29
|
+
npm create chemx my-molecular-app
|
|
30
|
+
npx create-chemx my-molecular-app --yes
|
|
31
|
+
pnpm create chemx my-app
|
|
32
|
+
|
|
33
|
+
DOCUMENTATION
|
|
34
|
+
https://chemicalx.xophz.com
|
|
35
|
+
\n`);
|
|
36
|
+
process.exit(0);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (rawArgs.includes('--version') || rawArgs.includes('-v')) {
|
|
40
|
+
try {
|
|
41
|
+
const pkgPath = new URL('../package.json', import.meta.url);
|
|
42
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
43
|
+
process.stdout.write(`create-chemx v${pkg.version || 'unknown'}\n`);
|
|
44
|
+
} catch {
|
|
45
|
+
process.stdout.write('create-chemx\n');
|
|
46
|
+
}
|
|
47
|
+
process.exit(0);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const KNOWN_FLAGS = new Set([
|
|
51
|
+
'--yes', '-y',
|
|
52
|
+
'--headless',
|
|
53
|
+
'-h', '--help',
|
|
54
|
+
'-v', '--version',
|
|
55
|
+
'--ci',
|
|
56
|
+
'--non-interactive',
|
|
57
|
+
'--no-interactive'
|
|
58
|
+
]);
|
|
59
|
+
|
|
60
|
+
for (const arg of rawArgs) {
|
|
61
|
+
if (arg.startsWith('-') && !KNOWN_FLAGS.has(arg)) {
|
|
62
|
+
process.stderr.write(`Unknown option "${arg}". Run --help for usage.\n`);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const nonFlagArgs = rawArgs.filter((arg) => !arg.startsWith('-'));
|
|
68
|
+
const dirArg = (nonFlagArgs[0] === 'create' || nonFlagArgs[0] === 'init' || nonFlagArgs[0] === 'scaffold')
|
|
69
|
+
? nonFlagArgs[1]
|
|
70
|
+
: nonFlagArgs[0];
|
|
71
|
+
|
|
72
|
+
await runScaffold(dirArg, rawArgs, runAudit);
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { test, describe, afterEach } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import os from 'node:os';
|
|
7
|
+
|
|
8
|
+
const CLI_DIR = path.resolve(import.meta.dirname);
|
|
9
|
+
const CREATE_BIN = path.join(CLI_DIR, 'create.js');
|
|
10
|
+
const INDEX_BIN = path.join(CLI_DIR, 'index.js');
|
|
11
|
+
|
|
12
|
+
describe('Scaffolding Entrypoint Invocations', () => {
|
|
13
|
+
const cleanupDirs = [];
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
while (cleanupDirs.length > 0) {
|
|
17
|
+
const dir = cleanupDirs.pop();
|
|
18
|
+
if (fs.existsSync(dir)) {
|
|
19
|
+
try {
|
|
20
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
21
|
+
} catch {
|
|
22
|
+
// ignore cleanup errors
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('node cli/create.js <dir> --yes scaffolds project directly into target directory', () => {
|
|
29
|
+
const tmpBase = fs.mkdtempSync(path.join(os.tmpdir(), 'chemx-test-'));
|
|
30
|
+
cleanupDirs.push(tmpBase);
|
|
31
|
+
const targetDir = path.join(tmpBase, 'test-target-app');
|
|
32
|
+
|
|
33
|
+
const result = spawnSync('node', [CREATE_BIN, targetDir, '--yes'], {
|
|
34
|
+
encoding: 'utf-8',
|
|
35
|
+
cwd: tmpBase,
|
|
36
|
+
timeout: 15000
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
assert.equal(result.status, 0, `Expected exit 0, got: ${result.status}\nStdout: ${result.stdout}\nStderr: ${result.stderr}`);
|
|
40
|
+
assert.ok(fs.existsSync(path.join(targetDir, 'package.json')), 'package.json should be created in targetDir');
|
|
41
|
+
assert.ok(fs.existsSync(path.join(targetDir, 'molecule-capsule')), 'molecule-capsule/ should be created in targetDir');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('node cli/create.js --yes (no dir argument) scaffolds into default my-molecular-app', () => {
|
|
45
|
+
const tmpBase = fs.mkdtempSync(path.join(os.tmpdir(), 'chemx-test-'));
|
|
46
|
+
cleanupDirs.push(tmpBase);
|
|
47
|
+
const defaultTarget = path.join(tmpBase, 'my-molecular-app');
|
|
48
|
+
|
|
49
|
+
const result = spawnSync('node', [CREATE_BIN, '--yes'], {
|
|
50
|
+
encoding: 'utf-8',
|
|
51
|
+
cwd: tmpBase,
|
|
52
|
+
timeout: 15000
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
assert.equal(result.status, 0, `Expected exit 0, got: ${result.status}\nStdout: ${result.stdout}\nStderr: ${result.stderr}`);
|
|
56
|
+
assert.ok(fs.existsSync(path.join(defaultTarget, 'package.json')), 'package.json should exist in default directory');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('node cli/create.js --help prints usage and does not scaffold', () => {
|
|
60
|
+
const tmpBase = fs.mkdtempSync(path.join(os.tmpdir(), 'chemx-test-'));
|
|
61
|
+
cleanupDirs.push(tmpBase);
|
|
62
|
+
|
|
63
|
+
const result = spawnSync('node', [CREATE_BIN, '--help'], {
|
|
64
|
+
encoding: 'utf-8',
|
|
65
|
+
cwd: tmpBase,
|
|
66
|
+
timeout: 5000
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
assert.equal(result.status, 0, 'Should exit with code 0 on --help');
|
|
70
|
+
assert.match(result.stdout, /Chemical X Protocol: Project Scaffolder/, 'Should show scaffolder help');
|
|
71
|
+
assert.ok(!fs.existsSync(path.join(tmpBase, 'my-molecular-app')), 'Must not scaffold any directory on --help');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('node cli/create.js --version prints version and does not scaffold', () => {
|
|
75
|
+
const tmpBase = fs.mkdtempSync(path.join(os.tmpdir(), 'chemx-test-'));
|
|
76
|
+
cleanupDirs.push(tmpBase);
|
|
77
|
+
|
|
78
|
+
const result = spawnSync('node', [CREATE_BIN, '--version'], {
|
|
79
|
+
encoding: 'utf-8',
|
|
80
|
+
cwd: tmpBase,
|
|
81
|
+
timeout: 5000
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
assert.equal(result.status, 0, 'Should exit with code 0 on --version');
|
|
85
|
+
assert.match(result.stdout, /create-chemx v/, 'Should print create-chemx version');
|
|
86
|
+
assert.ok(!fs.existsSync(path.join(tmpBase, 'my-molecular-app')), 'Must not scaffold any directory on --version');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('node cli/create.js create <dir> --yes tolerates redundant create argument', () => {
|
|
90
|
+
const tmpBase = fs.mkdtempSync(path.join(os.tmpdir(), 'chemx-test-'));
|
|
91
|
+
cleanupDirs.push(tmpBase);
|
|
92
|
+
const targetDir = path.join(tmpBase, 'redundant-create-app');
|
|
93
|
+
|
|
94
|
+
const result = spawnSync('node', [CREATE_BIN, 'create', targetDir, '--yes'], {
|
|
95
|
+
encoding: 'utf-8',
|
|
96
|
+
cwd: tmpBase,
|
|
97
|
+
timeout: 15000
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
assert.equal(result.status, 0, `Expected exit 0, got: ${result.status}\nStdout: ${result.stdout}\nStderr: ${result.stderr}`);
|
|
101
|
+
assert.ok(fs.existsSync(path.join(targetDir, 'package.json')), 'package.json should be created in targetDir');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('node cli/index.js create <dir> --yes scaffolds project via chemx create', () => {
|
|
105
|
+
const tmpBase = fs.mkdtempSync(path.join(os.tmpdir(), 'chemx-test-'));
|
|
106
|
+
cleanupDirs.push(tmpBase);
|
|
107
|
+
const targetDir = path.join(tmpBase, 'chemx-create-app');
|
|
108
|
+
|
|
109
|
+
const result = spawnSync('node', [INDEX_BIN, 'create', targetDir, '--yes'], {
|
|
110
|
+
encoding: 'utf-8',
|
|
111
|
+
cwd: tmpBase,
|
|
112
|
+
timeout: 15000
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
assert.equal(result.status, 0, `Expected exit 0, got: ${result.status}\nStdout: ${result.stdout}\nStderr: ${result.stderr}`);
|
|
116
|
+
assert.ok(fs.existsSync(path.join(targetDir, 'package.json')), 'package.json should exist');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test('node cli/index.js create --yes scaffolds project via chemx create without positional dir', () => {
|
|
120
|
+
const tmpBase = fs.mkdtempSync(path.join(os.tmpdir(), 'chemx-test-'));
|
|
121
|
+
cleanupDirs.push(tmpBase);
|
|
122
|
+
const defaultTarget = path.join(tmpBase, 'my-molecular-app');
|
|
123
|
+
|
|
124
|
+
const result = spawnSync('node', [INDEX_BIN, 'create', '--yes'], {
|
|
125
|
+
encoding: 'utf-8',
|
|
126
|
+
cwd: tmpBase,
|
|
127
|
+
timeout: 15000
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
assert.equal(result.status, 0, `Expected exit 0, got: ${result.status}\nStdout: ${result.stdout}\nStderr: ${result.stderr}`);
|
|
131
|
+
assert.ok(fs.existsSync(path.join(defaultTarget, 'package.json')), 'package.json should exist');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('executing via create-chemx symlink scaffolds successfully', () => {
|
|
135
|
+
const tmpBase = fs.mkdtempSync(path.join(os.tmpdir(), 'chemx-test-'));
|
|
136
|
+
cleanupDirs.push(tmpBase);
|
|
137
|
+
const symlinkPath = path.join(tmpBase, 'create-chemx');
|
|
138
|
+
fs.symlinkSync(CREATE_BIN, symlinkPath);
|
|
139
|
+
const targetDir = path.join(tmpBase, 'symlink-app');
|
|
140
|
+
|
|
141
|
+
const result = spawnSync(symlinkPath, [targetDir, '--yes'], {
|
|
142
|
+
encoding: 'utf-8',
|
|
143
|
+
cwd: tmpBase,
|
|
144
|
+
timeout: 15000
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
assert.equal(result.status, 0, `Expected exit 0, got: ${result.status}\nStdout: ${result.stdout}\nStderr: ${result.stderr}`);
|
|
148
|
+
assert.ok(fs.existsSync(path.join(targetDir, 'package.json')), 'package.json should exist');
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test('node cli/create.js <dir> -y (short flag) scaffolds successfully', () => {
|
|
152
|
+
const tmpBase = fs.mkdtempSync(path.join(os.tmpdir(), 'chemx-test-'));
|
|
153
|
+
cleanupDirs.push(tmpBase);
|
|
154
|
+
const targetDir = path.join(tmpBase, 'short-flag-app');
|
|
155
|
+
|
|
156
|
+
const result = spawnSync('node', [CREATE_BIN, targetDir, '-y'], {
|
|
157
|
+
encoding: 'utf-8',
|
|
158
|
+
cwd: tmpBase,
|
|
159
|
+
timeout: 15000
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
assert.equal(result.status, 0, `Expected exit 0, got: ${result.status}\nStdout: ${result.stdout}\nStderr: ${result.stderr}`);
|
|
163
|
+
assert.ok(fs.existsSync(path.join(targetDir, 'package.json')), 'package.json should exist');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test('node cli/index.js config fails fast as unknown command without creating directory', () => {
|
|
167
|
+
const tmpBase = fs.mkdtempSync(path.join(os.tmpdir(), 'chemx-test-'));
|
|
168
|
+
cleanupDirs.push(tmpBase);
|
|
169
|
+
|
|
170
|
+
const result = spawnSync('node', [INDEX_BIN, 'config'], {
|
|
171
|
+
encoding: 'utf-8',
|
|
172
|
+
cwd: tmpBase,
|
|
173
|
+
timeout: 5000
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
assert.equal(result.status, 1, 'Unknown command should exit with code 1');
|
|
177
|
+
assert.match(result.stderr, /Unknown command "config"/, 'Should report unknown command');
|
|
178
|
+
assert.ok(!fs.existsSync(path.join(tmpBase, 'config')), 'Must NOT create a config directory');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test('node cli/index.js under create-chemx path STILL fails fast on unknown command (regression test)', () => {
|
|
182
|
+
const tmpBase = fs.mkdtempSync(path.join(os.tmpdir(), 'chemx-test-'));
|
|
183
|
+
cleanupDirs.push(tmpBase);
|
|
184
|
+
// Simulate being installed inside a path with "create-chemx" in its name
|
|
185
|
+
const pkgDir = path.join(tmpBase, 'node_modules', '@chemx', 'create-chemx', 'cli');
|
|
186
|
+
fs.mkdirSync(pkgDir, { recursive: true });
|
|
187
|
+
const fakeIndex = path.join(pkgDir, 'index.js');
|
|
188
|
+
fs.symlinkSync(INDEX_BIN, fakeIndex);
|
|
189
|
+
|
|
190
|
+
const result = spawnSync('node', [fakeIndex, 'config'], {
|
|
191
|
+
encoding: 'utf-8',
|
|
192
|
+
cwd: tmpBase,
|
|
193
|
+
timeout: 5000
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
assert.equal(result.status, 1, 'Unknown command should exit with code 1');
|
|
197
|
+
assert.match(result.stderr, /Unknown command "config"/, 'Should report unknown command');
|
|
198
|
+
assert.ok(!fs.existsSync(path.join(tmpBase, 'config')), 'Must NOT create a config directory when installed under create-chemx path');
|
|
199
|
+
});
|
|
200
|
+
});
|