@arcadialdev/arcality 3.0.4 → 4.0.1

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.
@@ -0,0 +1,236 @@
1
+ #!/usr/bin/env node
2
+ import path from 'node:path';
3
+ import { analyzeCodebase } from '../src/services/codebaseAnalyzer.mjs';
4
+ import { loadProjectConfig, getYamlOutputDir } from '../src/configManager.mjs';
5
+ import { generateMissionsForRoute } from '../src/services/missionGenerator.mjs';
6
+ import { saveGeneratedMissions } from '../src/services/generatedMissionStore.mjs';
7
+ import { loadGenerationContext, resolveRouteGuidance } from '../src/services/generationContext.mjs';
8
+
9
+ function parseArgs(rawArgs) {
10
+ const options = {
11
+ count: null,
12
+ page: [],
13
+ prompt: '',
14
+ dryRun: false,
15
+ outputDir: null,
16
+ debug: false
17
+ };
18
+
19
+ for (let i = 0; i < rawArgs.length; i++) {
20
+ const token = rawArgs[i];
21
+ if (token === '--dry-run') {
22
+ options.dryRun = true;
23
+ continue;
24
+ }
25
+ if (token === '--debug') {
26
+ options.debug = true;
27
+ continue;
28
+ }
29
+ if (token.startsWith('--count=')) {
30
+ options.count = Number(token.split('=').slice(1).join('='));
31
+ continue;
32
+ }
33
+ if (token === '--count' && rawArgs[i + 1]) {
34
+ options.count = Number(rawArgs[i + 1]);
35
+ i += 1;
36
+ continue;
37
+ }
38
+ if (token.startsWith('--page=')) {
39
+ options.page = token.split('=').slice(1).join('=').split(',').map(v => v.trim()).filter(Boolean);
40
+ continue;
41
+ }
42
+ if (token === '--page' && rawArgs[i + 1]) {
43
+ options.page = rawArgs[i + 1].split(',').map(v => v.trim()).filter(Boolean);
44
+ i += 1;
45
+ continue;
46
+ }
47
+ if (token.startsWith('--prompt=')) {
48
+ options.prompt = token.split('=').slice(1).join('=').trim();
49
+ continue;
50
+ }
51
+ if (token === '--prompt' && rawArgs[i + 1]) {
52
+ options.prompt = rawArgs[i + 1].trim();
53
+ i += 1;
54
+ continue;
55
+ }
56
+ if (token.startsWith('--output-dir=')) {
57
+ options.outputDir = token.split('=').slice(1).join('=').trim();
58
+ continue;
59
+ }
60
+ if (token === '--output-dir' && rawArgs[i + 1]) {
61
+ options.outputDir = rawArgs[i + 1].trim();
62
+ i += 1;
63
+ continue;
64
+ }
65
+ }
66
+
67
+ return options;
68
+ }
69
+
70
+ function matchesPageFilters(routeEntry, filters) {
71
+ if (!filters || filters.length === 0) return true;
72
+ const haystacks = [
73
+ routeEntry.pagePath.toLowerCase(),
74
+ routeEntry.route.toLowerCase()
75
+ ];
76
+ return filters.some(filter => {
77
+ const normalized = filter.toLowerCase();
78
+ return haystacks.some(value => value === normalized || value.includes(normalized));
79
+ });
80
+ }
81
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
82
+
83
+ function isLikelyUuidFilter(filter) {
84
+ return UUID_RE.test(String(filter || '').trim());
85
+ }
86
+
87
+ function formatRouteExamples(routes, maxItems = 3) {
88
+ return routes
89
+ .slice(0, maxItems)
90
+ .map(entry => `${entry.route} -> ${entry.pagePath}`)
91
+ .join('\n- ');
92
+ }
93
+
94
+
95
+ function limitEntries(entries, count) {
96
+ if (!Number.isFinite(count) || count <= 0) return entries;
97
+ return entries.slice(0, Math.floor(count));
98
+ }
99
+
100
+ function limitMissions(missions, count) {
101
+ if (!Number.isFinite(count) || count <= 0) return missions;
102
+ return missions.slice(0, Math.floor(count));
103
+ }
104
+
105
+ function printSummary(analysis, selectedRoutes, options, generationContext) {
106
+ console.log('\nArcality Generate - Discovery Summary');
107
+ console.log(`Project: ${analysis.projectName}`);
108
+ console.log(`Framework: ${analysis.framework}`);
109
+ console.log(`Routes detected: ${analysis.routeCount}`);
110
+ console.log(`Routes selected: ${selectedRoutes.length}`);
111
+ console.log(`Mission cap: ${Number.isFinite(options.count) && options.count > 0 ? Math.floor(options.count) : 'auto'}`);
112
+ console.log(`Output target: ${options.outputTarget}`);
113
+ console.log(`Mode: ${options.dryRun ? 'dry-run' : 'write-yaml'}`);
114
+ console.log(`QA context: ${generationContext.qaContext.exists ? generationContext.qaContext.status : 'missing'}`);
115
+ console.log(`Feasibility guidance: ${generationContext.feasibility.exists ? 'loaded' : 'missing'}`);
116
+
117
+ if (options.prompt) {
118
+ console.log(`Prompt focus: ${options.prompt}`);
119
+ }
120
+
121
+ if (analysis.warnings.length > 0) {
122
+ console.log('\nWarnings:');
123
+ for (const warning of analysis.warnings) {
124
+ console.log(`- ${warning}`);
125
+ }
126
+ }
127
+
128
+ if (selectedRoutes.length === 0) {
129
+ console.log('\nNo candidate routes matched the provided filters.');
130
+ const uuidFilters = (options.page || []).filter(isLikelyUuidFilter);
131
+ if (uuidFilters.length > 0) {
132
+ console.log('Tip: the current --page filter only matches route fragments or page file paths, not backend page IDs or UUIDs.');
133
+ }
134
+ if ((options.page || []).length > 0 && analysis.routes.length > 0) {
135
+ console.log('Try values such as a route (`/checkout`) or a page path fragment (`app/checkout/page.tsx`).');
136
+ console.log(`Examples:\n- ${formatRouteExamples(analysis.routes)}`);
137
+ }
138
+ return;
139
+ }
140
+
141
+ console.log('\nCandidate routes:');
142
+ selectedRoutes.forEach((entry, index) => {
143
+ const guidance = resolveRouteGuidance(entry, generationContext);
144
+ console.log(`${index + 1}. ${entry.route} -> ${entry.pagePath} [${entry.routerKind}]`);
145
+ if (options.debug) {
146
+ console.log(` name: ${entry.name}`);
147
+ console.log(` tags: ${entry.tags.join(', ')}`);
148
+ console.log(` signals: ${Object.entries(entry.signals || {}).filter(([, value]) => value === true).map(([key]) => key).join(', ') || 'none'}`);
149
+ console.log(` route_params: ${(entry.routeParams || []).map(param => `${param.name}:${param.kind}`).join(', ') || 'none'}`);
150
+ console.log(` route_example: ${entry.routeExamplePath || entry.route}`);
151
+ console.log(` matched_rules: ${guidance.matchedRules.map(rule => rule.text).join(' | ') || 'none'}`);
152
+ console.log(` feasibility_hints: ${guidance.feasibilityHints.join(' | ') || 'none'}`);
153
+ console.log(` expected_result: ${entry.expectedResult}`);
154
+ }
155
+ });
156
+ }
157
+
158
+ function printPhaseNotice(options) {
159
+ if (options.dryRun) {
160
+ console.log('\nNo files were written because --dry-run is active.');
161
+ return;
162
+ }
163
+ }
164
+
165
+ function printWriteSummary(summary, options) {
166
+ console.log('\nGeneration result:');
167
+ console.log(`- Requested missions: ${summary.total_requested}`);
168
+ console.log(`- Created: ${summary.created_count}`);
169
+ console.log(`- Skipped: ${summary.skipped_count}`);
170
+
171
+ if (summary.created.length > 0) {
172
+ console.log('\nCreated files:');
173
+ for (const item of summary.created) {
174
+ console.log(`- ${item.filePath}`);
175
+ }
176
+ }
177
+
178
+ if (summary.skipped.length > 0 && options.debug) {
179
+ console.log('\nSkipped entries:');
180
+ for (const item of summary.skipped) {
181
+ console.log(`- ${item.name} (${item.page_path}) -> ${item.reason}`);
182
+ }
183
+ }
184
+ }
185
+
186
+ function resolveOutputTarget(projectRoot, explicitOutputDir) {
187
+ if (explicitOutputDir) {
188
+ return path.resolve(projectRoot, explicitOutputDir);
189
+ }
190
+
191
+ const config = loadProjectConfig(projectRoot);
192
+ if (config) {
193
+ return path.join(getYamlOutputDir(config, projectRoot), 'generated');
194
+ }
195
+
196
+ return path.join(projectRoot, '.arcality', 'generated');
197
+ }
198
+
199
+ async function main() {
200
+ const args = process.argv.slice(2);
201
+ const options = parseArgs(args);
202
+ const projectRoot = process.cwd();
203
+ options.outputTarget = resolveOutputTarget(projectRoot, options.outputDir);
204
+ const projectConfig = loadProjectConfig(projectRoot);
205
+
206
+ const analysis = analyzeCodebase(projectRoot, {
207
+ baseUrl: projectConfig?.project?.baseUrl || ''
208
+ });
209
+ const generationContext = loadGenerationContext(projectRoot);
210
+ const filtered = analysis.routes.filter(route => matchesPageFilters(route, options.page));
211
+ const selectedRoutes = limitEntries(filtered, options.page.length > 0 ? null : Math.max(filtered.length, 1));
212
+
213
+ printSummary(analysis, selectedRoutes, options, generationContext);
214
+
215
+ const missions = limitMissions(
216
+ selectedRoutes.flatMap(route => generateMissionsForRoute(route, {
217
+ ...options,
218
+ routeGuidance: resolveRouteGuidance(route, generationContext)
219
+ })),
220
+ options.count
221
+ );
222
+ const summary = saveGeneratedMissions({
223
+ projectRoot,
224
+ targetDir: options.outputTarget,
225
+ missions,
226
+ dryRun: options.dryRun
227
+ });
228
+
229
+ printWriteSummary(summary, options);
230
+ printPhaseNotice(options);
231
+ }
232
+
233
+ main().catch(err => {
234
+ console.error(`\n[generate] ${err.message}`);
235
+ process.exit(1);
236
+ });