@arcadialdev/arcality 3.0.3 → 4.0.0
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 +75 -6
- package/bin/arcality.mjs +26 -17
- package/package.json +2 -1
- package/playwright.config.js +22 -21
- package/scripts/gen-and-run.mjs +2610 -2591
- package/scripts/generate.mjs +215 -0
- package/scripts/init.mjs +278 -253
- package/scripts/rebrand-report.mjs +19 -18
- package/src/KnowledgeService.ts +29 -29
- package/src/arcalityClient.mjs +31 -18
- package/src/configLoader.mjs +35 -35
- package/src/configManager.mjs +57 -51
- package/src/services/codebaseAnalyzer.mjs +59 -0
- package/src/services/generatedMissionSchema.mjs +76 -0
- package/src/services/generatedMissionStore.mjs +117 -0
- package/src/services/generationContext.mjs +242 -0
- package/src/services/missionGenerator.mjs +328 -0
- package/src/services/routeDiscovery.mjs +747 -0
- package/src/testRunner.ts +2 -1
- package/tests/_helpers/ArcalityReporter.js +15 -0
- package/tests/_helpers/agentic-runner.bundle.spec.js +1053 -151
|
@@ -0,0 +1,215 @@
|
|
|
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
|
+
|
|
82
|
+
function limitEntries(entries, count) {
|
|
83
|
+
if (!Number.isFinite(count) || count <= 0) return entries;
|
|
84
|
+
return entries.slice(0, Math.floor(count));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function limitMissions(missions, count) {
|
|
88
|
+
if (!Number.isFinite(count) || count <= 0) return missions;
|
|
89
|
+
return missions.slice(0, Math.floor(count));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function printSummary(analysis, selectedRoutes, options, generationContext) {
|
|
93
|
+
console.log('\nArcality Generate - Discovery Summary');
|
|
94
|
+
console.log(`Project: ${analysis.projectName}`);
|
|
95
|
+
console.log(`Framework: ${analysis.framework}`);
|
|
96
|
+
console.log(`Routes detected: ${analysis.routeCount}`);
|
|
97
|
+
console.log(`Routes selected: ${selectedRoutes.length}`);
|
|
98
|
+
console.log(`Mission cap: ${Number.isFinite(options.count) && options.count > 0 ? Math.floor(options.count) : 'auto'}`);
|
|
99
|
+
console.log(`Output target: ${options.outputTarget}`);
|
|
100
|
+
console.log(`Mode: ${options.dryRun ? 'dry-run' : 'write-yaml'}`);
|
|
101
|
+
console.log(`QA context: ${generationContext.qaContext.exists ? generationContext.qaContext.status : 'missing'}`);
|
|
102
|
+
console.log(`Feasibility guidance: ${generationContext.feasibility.exists ? 'loaded' : 'missing'}`);
|
|
103
|
+
|
|
104
|
+
if (options.prompt) {
|
|
105
|
+
console.log(`Prompt focus: ${options.prompt}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (analysis.warnings.length > 0) {
|
|
109
|
+
console.log('\nWarnings:');
|
|
110
|
+
for (const warning of analysis.warnings) {
|
|
111
|
+
console.log(`- ${warning}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (selectedRoutes.length === 0) {
|
|
116
|
+
console.log('\nNo candidate routes matched the provided filters.');
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
console.log('\nCandidate routes:');
|
|
121
|
+
selectedRoutes.forEach((entry, index) => {
|
|
122
|
+
const guidance = resolveRouteGuidance(entry, generationContext);
|
|
123
|
+
console.log(`${index + 1}. ${entry.route} -> ${entry.pagePath} [${entry.routerKind}]`);
|
|
124
|
+
if (options.debug) {
|
|
125
|
+
console.log(` name: ${entry.name}`);
|
|
126
|
+
console.log(` tags: ${entry.tags.join(', ')}`);
|
|
127
|
+
console.log(` signals: ${Object.entries(entry.signals || {}).filter(([, value]) => value === true).map(([key]) => key).join(', ') || 'none'}`);
|
|
128
|
+
console.log(` route_params: ${(entry.routeParams || []).map(param => `${param.name}:${param.kind}`).join(', ') || 'none'}`);
|
|
129
|
+
console.log(` route_example: ${entry.routeExamplePath || entry.route}`);
|
|
130
|
+
console.log(` matched_rules: ${guidance.matchedRules.map(rule => rule.text).join(' | ') || 'none'}`);
|
|
131
|
+
console.log(` feasibility_hints: ${guidance.feasibilityHints.join(' | ') || 'none'}`);
|
|
132
|
+
console.log(` expected_result: ${entry.expectedResult}`);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function printPhaseNotice(options) {
|
|
138
|
+
if (options.dryRun) {
|
|
139
|
+
console.log('\nNo files were written because --dry-run is active.');
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function printWriteSummary(summary, options) {
|
|
145
|
+
console.log('\nGeneration result:');
|
|
146
|
+
console.log(`- Requested missions: ${summary.total_requested}`);
|
|
147
|
+
console.log(`- Created: ${summary.created_count}`);
|
|
148
|
+
console.log(`- Skipped: ${summary.skipped_count}`);
|
|
149
|
+
|
|
150
|
+
if (summary.created.length > 0) {
|
|
151
|
+
console.log('\nCreated files:');
|
|
152
|
+
for (const item of summary.created) {
|
|
153
|
+
console.log(`- ${item.filePath}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (summary.skipped.length > 0 && options.debug) {
|
|
158
|
+
console.log('\nSkipped entries:');
|
|
159
|
+
for (const item of summary.skipped) {
|
|
160
|
+
console.log(`- ${item.name} (${item.page_path}) -> ${item.reason}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function resolveOutputTarget(projectRoot, explicitOutputDir) {
|
|
166
|
+
if (explicitOutputDir) {
|
|
167
|
+
return path.resolve(projectRoot, explicitOutputDir);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const config = loadProjectConfig(projectRoot);
|
|
171
|
+
if (config) {
|
|
172
|
+
return path.join(getYamlOutputDir(config, projectRoot), 'generated');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return path.join(projectRoot, '.arcality', 'generated');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function main() {
|
|
179
|
+
const args = process.argv.slice(2);
|
|
180
|
+
const options = parseArgs(args);
|
|
181
|
+
const projectRoot = process.cwd();
|
|
182
|
+
options.outputTarget = resolveOutputTarget(projectRoot, options.outputDir);
|
|
183
|
+
const projectConfig = loadProjectConfig(projectRoot);
|
|
184
|
+
|
|
185
|
+
const analysis = analyzeCodebase(projectRoot, {
|
|
186
|
+
baseUrl: projectConfig?.project?.baseUrl || ''
|
|
187
|
+
});
|
|
188
|
+
const generationContext = loadGenerationContext(projectRoot);
|
|
189
|
+
const filtered = analysis.routes.filter(route => matchesPageFilters(route, options.page));
|
|
190
|
+
const selectedRoutes = limitEntries(filtered, options.page.length > 0 ? null : Math.max(filtered.length, 1));
|
|
191
|
+
|
|
192
|
+
printSummary(analysis, selectedRoutes, options, generationContext);
|
|
193
|
+
|
|
194
|
+
const missions = limitMissions(
|
|
195
|
+
selectedRoutes.flatMap(route => generateMissionsForRoute(route, {
|
|
196
|
+
...options,
|
|
197
|
+
routeGuidance: resolveRouteGuidance(route, generationContext)
|
|
198
|
+
})),
|
|
199
|
+
options.count
|
|
200
|
+
);
|
|
201
|
+
const summary = saveGeneratedMissions({
|
|
202
|
+
projectRoot,
|
|
203
|
+
targetDir: options.outputTarget,
|
|
204
|
+
missions,
|
|
205
|
+
dryRun: options.dryRun
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
printWriteSummary(summary, options);
|
|
209
|
+
printPhaseNotice(options);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
main().catch(err => {
|
|
213
|
+
console.error(`\n[generate] ${err.message}`);
|
|
214
|
+
process.exit(1);
|
|
215
|
+
});
|