@ismaelsoilet/synapsejs 1.1.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/LICENSE +21 -0
- package/README.md +206 -0
- package/bin/synapse.ts +919 -0
- package/package.json +70 -0
- package/src/client/components.tsx +508 -0
- package/src/client/context.tsx +62 -0
- package/src/client/hooks.ts +152 -0
- package/src/client/i18n.ts +40 -0
- package/src/client.ts +66 -0
- package/src/compiler/agent-diagnostic-json.ts +113 -0
- package/src/compiler/ast-daemon-compressor.ts +190 -0
- package/src/compiler/client-bundler.ts +356 -0
- package/src/compiler/context-bench.ts +191 -0
- package/src/compiler/contract.ts +260 -0
- package/src/compiler/db-schema-generator.ts +306 -0
- package/src/compiler/fields-parser.ts +267 -0
- package/src/compiler/impact-analyzer.ts +279 -0
- package/src/compiler/migration-runner.ts +555 -0
- package/src/compiler/oracle-runner.ts +293 -0
- package/src/compiler/scaffolder.ts +528 -0
- package/src/compiler/schema-dag.ts +137 -0
- package/src/compiler/schema-drift.ts +184 -0
- package/src/compiler/slice-discovery.ts +154 -0
- package/src/compiler/slice-splitter.ts +838 -0
- package/src/compiler/slice-templates.ts +1527 -0
- package/src/compiler/standalone-builder.ts +117 -0
- package/src/core/action-context.ts +152 -0
- package/src/core/config.ts +77 -0
- package/src/core/database-client.ts +246 -0
- package/src/core/database-factory.ts +46 -0
- package/src/core/image-optimizer.ts +83 -0
- package/src/core/index.ts +14 -0
- package/src/core/jobs.ts +53 -0
- package/src/core/machine-types.ts +75 -0
- package/src/core/oracle.ts +20 -0
- package/src/core/postgres-client.ts +164 -0
- package/src/core/postgres-queue.ts +288 -0
- package/src/core/query-builder.ts +343 -0
- package/src/core/rpc-client.ts +70 -0
- package/src/core/session-context.ts +87 -0
- package/src/core/session-cookie.ts +81 -0
- package/src/core/session-token.ts +170 -0
- package/src/core/sqlite-client.ts +201 -0
- package/src/core/storage.ts +317 -0
- package/src/index.ts +133 -0
- package/src/mcp/server.ts +553 -0
- package/src/runtime/client-entry.ts +65 -0
- package/src/runtime/discovery-rules.ts +63 -0
- package/src/runtime/event-hub.ts +97 -0
- package/src/runtime/postgres-event-hub.ts +148 -0
- package/src/runtime/queue-engine.ts +255 -0
- package/src/runtime/rate-limiter.ts +152 -0
- package/src/runtime/server.ts +1550 -0
- package/src/runtime/uploads.ts +192 -0
- package/templates/starter/.dockerignore +7 -0
- package/templates/starter/Dockerfile +40 -0
- package/templates/starter/README.md +40 -0
- package/templates/starter/gitignore +6 -0
- package/templates/starter/package.json +34 -0
- package/templates/starter/src/slices/welcome/hello-world.slice.tsx +172 -0
- package/templates/starter/tsconfig.json +21 -0
package/bin/synapse.ts
ADDED
|
@@ -0,0 +1,919 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SynapseJS - Agent CLI v1.1.0
|
|
5
|
+
*
|
|
6
|
+
* Provides headless, machine-readable interfaces for AI autonomous agents.
|
|
7
|
+
*
|
|
8
|
+
* Subcommands:
|
|
9
|
+
* synapse start - Start production Bun.serve HTTP server with graceful shutdown (SIGTERM/SIGINT)
|
|
10
|
+
* synapse dev - Start Bun.serve HTTP server with Zero-Wiring Router & Auto-Migrations
|
|
11
|
+
* synapse check - Run the TypeScript typechecker and print JSON diagnostics
|
|
12
|
+
* synapse migrate - Run declarative slice schema migrations on active DB
|
|
13
|
+
* synapse mcp - Start native Model Context Protocol (MCP) server over stdio
|
|
14
|
+
* synapse skeleton - Update AST skeleton map (.codebase/repo-map.d.ts)
|
|
15
|
+
* synapse split - Partition slices into shared/server/client modules (gated)
|
|
16
|
+
* synapse test - Run all property-based testing (PBT) oracles
|
|
17
|
+
* synapse new-slice - Scaffold a new atomic vertical slice
|
|
18
|
+
* synapse info - Machine metadata and framework metrics
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import * as fs from 'fs';
|
|
22
|
+
import * as path from 'path';
|
|
23
|
+
import { runMachineVerifications } from '../src/compiler/agent-diagnostic-json';
|
|
24
|
+
import { compressRepositoryAST } from '../src/compiler/ast-daemon-compressor';
|
|
25
|
+
import { buildAllClientBundles } from '../src/compiler/client-bundler';
|
|
26
|
+
import { renderContractJson, renderContractMarkdown } from '../src/compiler/contract';
|
|
27
|
+
import { generateDatabaseSchemaCatalog } from '../src/compiler/db-schema-generator';
|
|
28
|
+
import { analyzeImpact } from '../src/compiler/impact-analyzer';
|
|
29
|
+
import { rollbackSliceMigrations, runSliceMigrations } from '../src/compiler/migration-runner';
|
|
30
|
+
import { runSliceOracles } from '../src/compiler/oracle-runner';
|
|
31
|
+
import { scaffoldCrud, scaffoldSlice } from '../src/compiler/scaffolder';
|
|
32
|
+
import { checkSchemaDrift } from '../src/compiler/schema-drift';
|
|
33
|
+
import { findSliceFiles, resolveSlicesDir } from '../src/compiler/slice-discovery';
|
|
34
|
+
import { artifactDirectory, splitSlice, verifySplit, writeSplitArtifacts } from '../src/compiler/slice-splitter';
|
|
35
|
+
import { SLICE_TEMPLATES, type SliceTemplate } from '../src/compiler/slice-templates';
|
|
36
|
+
import { buildStandalone } from '../src/compiler/standalone-builder';
|
|
37
|
+
import { SynapseMcpServer } from '../src/mcp/server';
|
|
38
|
+
import { SynapseServer } from '../src/runtime/server';
|
|
39
|
+
|
|
40
|
+
const command = process.argv[2] || 'check';
|
|
41
|
+
const root = process.cwd();
|
|
42
|
+
|
|
43
|
+
async function main() {
|
|
44
|
+
switch (command) {
|
|
45
|
+
case 'dev': {
|
|
46
|
+
const watch = process.argv.includes('--watch');
|
|
47
|
+
|
|
48
|
+
if (watch && !process.env.SYNAPSE_WATCH_CHILD) {
|
|
49
|
+
const child = Bun.spawn(
|
|
50
|
+
[process.execPath, '--watch', ...process.argv.slice(1).filter((arg) => arg !== '--watch')],
|
|
51
|
+
{ stdio: ['inherit', 'inherit', 'inherit'], env: { ...process.env, SYNAPSE_WATCH_CHILD: '1' } }
|
|
52
|
+
);
|
|
53
|
+
process.exit(await child.exited);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const port = parseInt(process.argv[3] || process.env.PORT || '3000', 10);
|
|
57
|
+
const server = new SynapseServer(root, port);
|
|
58
|
+
await server.discoverSlices();
|
|
59
|
+
await server.start();
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
case 'start': {
|
|
64
|
+
const port = parseInt(process.argv[3] || process.env.PORT || '3000', 10);
|
|
65
|
+
const isProduction = process.env.NODE_ENV === 'production';
|
|
66
|
+
|
|
67
|
+
if (isProduction && !process.env.SYNAPSE_SESSION_SECRET) {
|
|
68
|
+
console.warn(
|
|
69
|
+
'⚠️ [AVISO DE SEGURANÇA] NODE_ENV=production ativo sem SYNAPSE_SESSION_SECRET configurado!\n' +
|
|
70
|
+
' Sessões de usuário permanecerão anônimas por padrão.'
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const server = new SynapseServer(root, port);
|
|
75
|
+
await server.discoverSlices();
|
|
76
|
+
await server.start();
|
|
77
|
+
|
|
78
|
+
let shuttingDown = false;
|
|
79
|
+
const gracefulShutdown = async (signal: string) => {
|
|
80
|
+
if (shuttingDown) return;
|
|
81
|
+
shuttingDown = true;
|
|
82
|
+
console.log(`\n🛑 [SynapseJS] Recebido sinal ${signal}, iniciando encerramento gracioso...`);
|
|
83
|
+
try {
|
|
84
|
+
await server.stop();
|
|
85
|
+
console.log('✅ [SynapseJS] Servidor e conexões de persistência encerrados com sucesso.');
|
|
86
|
+
process.exit(0);
|
|
87
|
+
} catch (err) {
|
|
88
|
+
console.error('❌ [SynapseJS] Erro durante encerramento gracioso:', err);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
|
94
|
+
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
case 'check': {
|
|
99
|
+
const arg1 = process.argv[3];
|
|
100
|
+
|
|
101
|
+
if (arg1?.startsWith('--')) {
|
|
102
|
+
process.stdout.write(
|
|
103
|
+
`${JSON.stringify(
|
|
104
|
+
{
|
|
105
|
+
status: 'FAIL',
|
|
106
|
+
code: 'UNKNOWN_FLAG',
|
|
107
|
+
message: `Flag desconhecida: ${arg1}. Uso: synapse check [arquivo]`
|
|
108
|
+
},
|
|
109
|
+
null,
|
|
110
|
+
2
|
|
111
|
+
)}\n`
|
|
112
|
+
);
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const report = runMachineVerifications(root, arg1);
|
|
117
|
+
|
|
118
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
119
|
+
process.exit(report.status === 'PASS' ? 0 : 1);
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
case 'migrate': {
|
|
124
|
+
const report = await runSliceMigrations(root);
|
|
125
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
126
|
+
process.exit(report.status === 'PASS' ? 0 : 1);
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
case 'rollback': {
|
|
131
|
+
let targetSlice: string | undefined;
|
|
132
|
+
let steps: number | undefined;
|
|
133
|
+
|
|
134
|
+
for (let i = 3; i < process.argv.length; i++) {
|
|
135
|
+
const arg = process.argv[i];
|
|
136
|
+
if (arg.startsWith('--steps=')) {
|
|
137
|
+
steps = parseInt(arg.replace('--steps=', ''), 10);
|
|
138
|
+
} else if (!arg.startsWith('--')) {
|
|
139
|
+
targetSlice = arg;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const report = await rollbackSliceMigrations(root, undefined, { targetSlice, steps });
|
|
144
|
+
process.stdout.write(
|
|
145
|
+
`${JSON.stringify(
|
|
146
|
+
{
|
|
147
|
+
operation: 'MIGRATION_ROLLBACK',
|
|
148
|
+
...report
|
|
149
|
+
},
|
|
150
|
+
null,
|
|
151
|
+
2
|
|
152
|
+
)}\n`
|
|
153
|
+
);
|
|
154
|
+
process.exit(report.status === 'PASS' ? 0 : 1);
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
case 'mcp': {
|
|
159
|
+
const mcpServer = new SynapseMcpServer(root);
|
|
160
|
+
mcpServer.start();
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
case 'skeleton': {
|
|
165
|
+
const repoMapPath = path.join(root, '.codebase/repo-map.d.ts');
|
|
166
|
+
const graphPath = path.join(root, '.codebase/architecture-graph.json');
|
|
167
|
+
const stats = compressRepositoryAST(root, repoMapPath, graphPath);
|
|
168
|
+
|
|
169
|
+
const dbSchemaPath = path.join(root, '.codebase/db-schema.d.ts');
|
|
170
|
+
const dbStats = generateDatabaseSchemaCatalog(root, dbSchemaPath);
|
|
171
|
+
|
|
172
|
+
const skeletonStatus = stats.totalModules > 0 ? 'PASS' : 'FAIL';
|
|
173
|
+
|
|
174
|
+
process.stdout.write(
|
|
175
|
+
`${JSON.stringify(
|
|
176
|
+
{
|
|
177
|
+
status: skeletonStatus,
|
|
178
|
+
operation: 'SKELETON_COMPRESS',
|
|
179
|
+
repoMapPath: '.codebase/repo-map.d.ts',
|
|
180
|
+
dbSchemaPath: '.codebase/db-schema.d.ts',
|
|
181
|
+
totalModules: stats.totalModules,
|
|
182
|
+
totalSlices: stats.totalSlices,
|
|
183
|
+
totalTables: dbStats.totalTables,
|
|
184
|
+
totalColumns: dbStats.totalColumns,
|
|
185
|
+
estimatedTokens: stats.manifestTokensEstimate,
|
|
186
|
+
budgetTokens: 3000,
|
|
187
|
+
...(skeletonStatus === 'FAIL'
|
|
188
|
+
? {
|
|
189
|
+
code: 'EMPTY_REPO_MAP',
|
|
190
|
+
message: `O tsconfig em '${root}/tsconfig.json' não inclui nenhum módulo — nada foi mapeado.`
|
|
191
|
+
}
|
|
192
|
+
: {})
|
|
193
|
+
},
|
|
194
|
+
null,
|
|
195
|
+
2
|
|
196
|
+
)}\n`
|
|
197
|
+
);
|
|
198
|
+
process.exit(skeletonStatus === 'PASS' ? 0 : 1);
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
case 'db-schema': {
|
|
203
|
+
const dbSchemaPath = path.join(root, '.codebase/db-schema.d.ts');
|
|
204
|
+
const dbStats = generateDatabaseSchemaCatalog(root, dbSchemaPath);
|
|
205
|
+
|
|
206
|
+
process.stdout.write(
|
|
207
|
+
`${JSON.stringify(
|
|
208
|
+
{
|
|
209
|
+
status: 'PASS',
|
|
210
|
+
operation: 'DB_SCHEMA_CATALOG',
|
|
211
|
+
catalogFile: '.codebase/db-schema.d.ts',
|
|
212
|
+
totalTables: dbStats.totalTables,
|
|
213
|
+
totalColumns: dbStats.totalColumns,
|
|
214
|
+
tables: dbStats.tables.map((t) => t.name)
|
|
215
|
+
},
|
|
216
|
+
null,
|
|
217
|
+
2
|
|
218
|
+
)}\n`
|
|
219
|
+
);
|
|
220
|
+
process.exit(0);
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
case 'db-drift': {
|
|
225
|
+
const report = await checkSchemaDrift(root);
|
|
226
|
+
process.stdout.write(
|
|
227
|
+
`${JSON.stringify(
|
|
228
|
+
{
|
|
229
|
+
operation: 'DB_DRIFT_CHECK',
|
|
230
|
+
...report
|
|
231
|
+
},
|
|
232
|
+
null,
|
|
233
|
+
2
|
|
234
|
+
)}\n`
|
|
235
|
+
);
|
|
236
|
+
process.exit(report.status === 'PASS' ? 0 : 1);
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
case 'impact': {
|
|
241
|
+
const target = process.argv[3];
|
|
242
|
+
if (!target) {
|
|
243
|
+
process.stdout.write(
|
|
244
|
+
`${JSON.stringify(
|
|
245
|
+
{
|
|
246
|
+
status: 'FAIL',
|
|
247
|
+
operation: 'IMPACT_ANALYSIS',
|
|
248
|
+
code: 'MISSING_TARGET',
|
|
249
|
+
message: 'Especifique o arquivo, fatia ou tabela: synapse impact <target>'
|
|
250
|
+
},
|
|
251
|
+
null,
|
|
252
|
+
2
|
|
253
|
+
)}\n`
|
|
254
|
+
);
|
|
255
|
+
process.exit(1);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const report = analyzeImpact(target, root);
|
|
259
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
260
|
+
process.exit(0);
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
case 'split': {
|
|
265
|
+
const resolution = resolveSlicesDir(root);
|
|
266
|
+
|
|
267
|
+
if (!resolution.ok) {
|
|
268
|
+
process.stdout.write(
|
|
269
|
+
`${JSON.stringify(
|
|
270
|
+
{
|
|
271
|
+
status: 'FAIL',
|
|
272
|
+
operation: 'SLICE_SPLIT',
|
|
273
|
+
code: resolution.error.code,
|
|
274
|
+
message: resolution.error.message,
|
|
275
|
+
candidates: resolution.error.candidates
|
|
276
|
+
},
|
|
277
|
+
null,
|
|
278
|
+
2
|
|
279
|
+
)}\n`
|
|
280
|
+
);
|
|
281
|
+
process.exit(1);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const sliceFiles = findSliceFiles(resolution.value.slicesDir);
|
|
285
|
+
const slices = [];
|
|
286
|
+
let failed = false;
|
|
287
|
+
|
|
288
|
+
for (const file of sliceFiles) {
|
|
289
|
+
const split = splitSlice(file, root);
|
|
290
|
+
|
|
291
|
+
if (!split.ok) {
|
|
292
|
+
failed = true;
|
|
293
|
+
slices.push({
|
|
294
|
+
slice: path.basename(file, '.slice.tsx'),
|
|
295
|
+
status: 'FAIL',
|
|
296
|
+
code: split.error.code,
|
|
297
|
+
message: split.error.message
|
|
298
|
+
});
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const outDir = artifactDirectory(root, split.value.sliceName);
|
|
303
|
+
const written = writeSplitArtifacts(split.value, outDir);
|
|
304
|
+
const verification = verifySplit(split.value, outDir);
|
|
305
|
+
|
|
306
|
+
if (verification.status === 'FAIL') {
|
|
307
|
+
failed = true;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
slices.push({
|
|
311
|
+
slice: split.value.sliceName,
|
|
312
|
+
status: verification.status,
|
|
313
|
+
outDir: path.relative(root, outDir),
|
|
314
|
+
artifacts: written.map((target) => path.relative(root, target)),
|
|
315
|
+
diagnostics: verification.diagnostics,
|
|
316
|
+
leaks: verification.leaks
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const splitStatus = failed || slices.length === 0 ? 'FAIL' : 'PASS';
|
|
321
|
+
|
|
322
|
+
process.stdout.write(
|
|
323
|
+
`${JSON.stringify(
|
|
324
|
+
{
|
|
325
|
+
status: splitStatus,
|
|
326
|
+
operation: 'SLICE_SPLIT',
|
|
327
|
+
slicesDir: path.relative(root, resolution.value.slicesDir),
|
|
328
|
+
totalProcessed: slices.length,
|
|
329
|
+
slices
|
|
330
|
+
},
|
|
331
|
+
null,
|
|
332
|
+
2
|
|
333
|
+
)}\n`
|
|
334
|
+
);
|
|
335
|
+
process.exit(splitStatus === 'PASS' ? 0 : 1);
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
case 'new-slice': {
|
|
340
|
+
const domain = process.argv[3];
|
|
341
|
+
const name = process.argv[4];
|
|
342
|
+
|
|
343
|
+
if (!domain || !name) {
|
|
344
|
+
process.stderr.write(
|
|
345
|
+
`${JSON.stringify({
|
|
346
|
+
status: 'ERROR',
|
|
347
|
+
message: 'Parâmetros obrigatórios ausentes. Uso: synapse new-slice <domain> <name>'
|
|
348
|
+
})}\n`
|
|
349
|
+
);
|
|
350
|
+
process.exit(1);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const templateArg = process.argv.find((arg) => arg.startsWith('--template='))?.split('=')[1];
|
|
354
|
+
const template = (templateArg ?? 'create') as SliceTemplate | 'crud';
|
|
355
|
+
const fieldsArg = process.argv.find((arg) => arg.startsWith('--fields='))?.slice('--fields='.length);
|
|
356
|
+
|
|
357
|
+
if (template !== 'crud' && !SLICE_TEMPLATES.includes(template)) {
|
|
358
|
+
process.stdout.write(
|
|
359
|
+
`${JSON.stringify(
|
|
360
|
+
{
|
|
361
|
+
status: 'ERROR',
|
|
362
|
+
operation: 'SCAFFOLD_SLICE',
|
|
363
|
+
code: 'UNKNOWN_TEMPLATE',
|
|
364
|
+
message: `Template desconhecido: ${template}`,
|
|
365
|
+
templates: [...SLICE_TEMPLATES, 'crud']
|
|
366
|
+
},
|
|
367
|
+
null,
|
|
368
|
+
2
|
|
369
|
+
)}\n`
|
|
370
|
+
);
|
|
371
|
+
process.exit(1);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (template === 'crud') {
|
|
375
|
+
const crud = scaffoldCrud(domain, name, root, fieldsArg);
|
|
376
|
+
|
|
377
|
+
if (!crud.ok) {
|
|
378
|
+
process.stdout.write(
|
|
379
|
+
`${JSON.stringify(
|
|
380
|
+
{
|
|
381
|
+
status: 'ERROR',
|
|
382
|
+
operation: 'SCAFFOLD_CRUD',
|
|
383
|
+
code: crud.error.code,
|
|
384
|
+
message: crud.error.message
|
|
385
|
+
},
|
|
386
|
+
null,
|
|
387
|
+
2
|
|
388
|
+
)}\n`
|
|
389
|
+
);
|
|
390
|
+
process.exit(1);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
process.stdout.write(
|
|
394
|
+
`${JSON.stringify(
|
|
395
|
+
{
|
|
396
|
+
status: 'PASS',
|
|
397
|
+
operation: 'SCAFFOLD_CRUD',
|
|
398
|
+
domain,
|
|
399
|
+
resource: name,
|
|
400
|
+
createdPaths: crud.value.map((file) => path.relative(root, file))
|
|
401
|
+
},
|
|
402
|
+
null,
|
|
403
|
+
2
|
|
404
|
+
)}\n`
|
|
405
|
+
);
|
|
406
|
+
process.exit(0);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const created = scaffoldSlice(domain, name, root, template, fieldsArg);
|
|
410
|
+
|
|
411
|
+
if (!created.ok) {
|
|
412
|
+
process.stderr.write(
|
|
413
|
+
`${JSON.stringify(
|
|
414
|
+
{
|
|
415
|
+
status: 'ERROR',
|
|
416
|
+
operation: 'SCAFFOLD_SLICE',
|
|
417
|
+
code: created.error.code,
|
|
418
|
+
message: created.error.message,
|
|
419
|
+
candidates: created.error.candidates
|
|
420
|
+
},
|
|
421
|
+
null,
|
|
422
|
+
2
|
|
423
|
+
)}\n`
|
|
424
|
+
);
|
|
425
|
+
process.exit(1);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
process.stdout.write(
|
|
429
|
+
`${JSON.stringify(
|
|
430
|
+
{
|
|
431
|
+
status: 'PASS',
|
|
432
|
+
operation: 'SCAFFOLD_SLICE',
|
|
433
|
+
domain,
|
|
434
|
+
name,
|
|
435
|
+
createdPath: path.relative(root, created.value)
|
|
436
|
+
},
|
|
437
|
+
null,
|
|
438
|
+
2
|
|
439
|
+
)}\n`
|
|
440
|
+
);
|
|
441
|
+
process.exit(0);
|
|
442
|
+
break;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
case 'build': {
|
|
446
|
+
const isStandalone = process.argv.includes('--standalone');
|
|
447
|
+
|
|
448
|
+
if (isStandalone) {
|
|
449
|
+
const report = await buildStandalone(root);
|
|
450
|
+
|
|
451
|
+
if (!report.ok) {
|
|
452
|
+
process.stdout.write(
|
|
453
|
+
`${JSON.stringify(
|
|
454
|
+
{
|
|
455
|
+
status: 'FAIL',
|
|
456
|
+
operation: 'BUILD_STANDALONE',
|
|
457
|
+
code: 'code' in report.error ? report.error.code : 'UNKNOWN',
|
|
458
|
+
message: report.error.message
|
|
459
|
+
},
|
|
460
|
+
null,
|
|
461
|
+
2
|
|
462
|
+
)}\n`
|
|
463
|
+
);
|
|
464
|
+
process.exit(1);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
process.stdout.write(
|
|
468
|
+
`${JSON.stringify(
|
|
469
|
+
{
|
|
470
|
+
status: 'PASS',
|
|
471
|
+
operation: 'BUILD_STANDALONE',
|
|
472
|
+
outDir: path.relative(root, report.value.outDir),
|
|
473
|
+
serverEntry: path.relative(root, report.value.serverEntry),
|
|
474
|
+
clientBundlesCount: report.value.clientBundlesCount,
|
|
475
|
+
totalBytes: report.value.totalBytes
|
|
476
|
+
},
|
|
477
|
+
null,
|
|
478
|
+
2
|
|
479
|
+
)}\n`
|
|
480
|
+
);
|
|
481
|
+
process.exit(0);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Pre-constroi os bundles de cliente que o runtime faria sob demanda no primeiro request.
|
|
485
|
+
const report = await buildAllClientBundles(root);
|
|
486
|
+
|
|
487
|
+
if (!report.ok) {
|
|
488
|
+
process.stdout.write(
|
|
489
|
+
`${JSON.stringify(
|
|
490
|
+
{
|
|
491
|
+
status: 'FAIL',
|
|
492
|
+
operation: 'BUILD_CLIENT_BUNDLES',
|
|
493
|
+
code: report.error.code,
|
|
494
|
+
message: report.error.message,
|
|
495
|
+
candidates: report.error.candidates
|
|
496
|
+
},
|
|
497
|
+
null,
|
|
498
|
+
2
|
|
499
|
+
)}\n`
|
|
500
|
+
);
|
|
501
|
+
process.exit(1);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const failed = report.value.entries.filter((entry) => entry.status === 'FAIL');
|
|
505
|
+
const built = report.value.entries.filter((entry) => entry.status === 'PASS');
|
|
506
|
+
const skipped = report.value.entries.filter((entry) => entry.status === 'SKIP');
|
|
507
|
+
|
|
508
|
+
process.stdout.write(
|
|
509
|
+
`${JSON.stringify(
|
|
510
|
+
{
|
|
511
|
+
status: failed.length === 0 ? 'PASS' : 'FAIL',
|
|
512
|
+
operation: 'BUILD_CLIENT_BUNDLES',
|
|
513
|
+
built: built.length,
|
|
514
|
+
skipped: skipped.length,
|
|
515
|
+
failed: failed.length,
|
|
516
|
+
totalBytes: built.reduce((sum, entry) => sum + (entry.bytes ?? 0), 0),
|
|
517
|
+
manifest: path.relative(root, report.value.manifestFile),
|
|
518
|
+
slices: report.value.entries
|
|
519
|
+
},
|
|
520
|
+
null,
|
|
521
|
+
2
|
|
522
|
+
)}\n`
|
|
523
|
+
);
|
|
524
|
+
process.exit(failed.length === 0 ? 0 : 1);
|
|
525
|
+
break;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
case 'contract': {
|
|
529
|
+
// O framework se descreve numa chamada: e o que o agente le em vez de adivinhar.
|
|
530
|
+
const asMarkdown = process.argv.includes('--markdown');
|
|
531
|
+
process.stdout.write(`${asMarkdown ? renderContractMarkdown() : renderContractJson()}\n`);
|
|
532
|
+
process.exit(0);
|
|
533
|
+
break;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
case 'test': {
|
|
537
|
+
const report = await runSliceOracles(root);
|
|
538
|
+
|
|
539
|
+
process.stdout.write(`${JSON.stringify({ operation: 'PBT_ORACLE_TEST_SUITE', ...report }, null, 2)}\n`);
|
|
540
|
+
process.exit(report.status === 'PASS' ? 0 : 1);
|
|
541
|
+
break;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
case 'new':
|
|
545
|
+
case 'create':
|
|
546
|
+
case 'init': {
|
|
547
|
+
const rawTarget = process.argv[3];
|
|
548
|
+
if (!rawTarget) {
|
|
549
|
+
process.stderr.write(
|
|
550
|
+
`${JSON.stringify({
|
|
551
|
+
status: 'ERROR',
|
|
552
|
+
message: 'Nome do projeto obrigatório. Uso: synapse new <project-name>'
|
|
553
|
+
})}\n`
|
|
554
|
+
);
|
|
555
|
+
process.exit(1);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
const targetDir = path.resolve(root, rawTarget);
|
|
559
|
+
const cleanProjectName = path
|
|
560
|
+
.basename(targetDir)
|
|
561
|
+
.toLowerCase()
|
|
562
|
+
.replace(/[^a-z0-9-_]/g, '-');
|
|
563
|
+
|
|
564
|
+
if (fs.existsSync(targetDir)) {
|
|
565
|
+
process.stderr.write(
|
|
566
|
+
`${JSON.stringify({
|
|
567
|
+
status: 'ERROR',
|
|
568
|
+
message: `O diretório '${rawTarget}' já existe em ${targetDir}.`
|
|
569
|
+
})}\n`
|
|
570
|
+
);
|
|
571
|
+
process.exit(1);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// Localiza o template oficial
|
|
575
|
+
const candidates = [
|
|
576
|
+
path.resolve(__dirname, '../templates/starter'),
|
|
577
|
+
path.resolve(__dirname, '../../templates/starter'),
|
|
578
|
+
path.resolve(__dirname, '../../../templates/starter'),
|
|
579
|
+
path.resolve(__dirname, './templates/starter'),
|
|
580
|
+
path.resolve(root, 'templates/starter')
|
|
581
|
+
];
|
|
582
|
+
const templateDir = candidates.find((dir) => fs.existsSync(dir));
|
|
583
|
+
|
|
584
|
+
if (!templateDir) {
|
|
585
|
+
process.stderr.write(
|
|
586
|
+
`${JSON.stringify({
|
|
587
|
+
status: 'ERROR',
|
|
588
|
+
message: 'Template oficial starter não encontrado nos diretórios candidatos.'
|
|
589
|
+
})}\n`
|
|
590
|
+
);
|
|
591
|
+
process.exit(1);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// Função recursiva de cópia filtrada
|
|
595
|
+
const copyRecursive = (src: string, dest: string) => {
|
|
596
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
597
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
598
|
+
if (['node_modules', '.git', '.synapse', 'dist', '.cache', '.tsbuildinfo'].includes(entry.name)) {
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
601
|
+
// O template publica o arquivo como `gitignore` porque empacotadores
|
|
602
|
+
// descartam `.gitignore`; aqui ele volta ao nome que o usuário espera.
|
|
603
|
+
const destinationName = entry.name === 'gitignore' ? '.gitignore' : entry.name;
|
|
604
|
+
const s = path.join(src, entry.name);
|
|
605
|
+
const d = path.join(dest, destinationName);
|
|
606
|
+
if (entry.isDirectory()) {
|
|
607
|
+
copyRecursive(s, d);
|
|
608
|
+
} else {
|
|
609
|
+
let content = fs.readFileSync(s, 'utf-8');
|
|
610
|
+
if (entry.name === 'package.json') {
|
|
611
|
+
content = content.replace('"starter-app"', `"${cleanProjectName}"`);
|
|
612
|
+
content = content.replace(
|
|
613
|
+
/"(@ismaelsoilet\/)?synapsejs":\s*"[^"]+"/g,
|
|
614
|
+
'"@ismaelsoilet/synapsejs": "^1.1.0"'
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
fs.writeFileSync(d, content, 'utf-8');
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
};
|
|
621
|
+
|
|
622
|
+
copyRecursive(templateDir, targetDir);
|
|
623
|
+
|
|
624
|
+
process.stdout.write(
|
|
625
|
+
`${JSON.stringify(
|
|
626
|
+
{
|
|
627
|
+
status: 'PASS',
|
|
628
|
+
operation: 'CREATE_PROJECT',
|
|
629
|
+
projectName: cleanProjectName,
|
|
630
|
+
targetDir,
|
|
631
|
+
message: `Projeto SynapseJS '${cleanProjectName}' criado com sucesso!`
|
|
632
|
+
},
|
|
633
|
+
null,
|
|
634
|
+
2
|
|
635
|
+
)}\n`
|
|
636
|
+
);
|
|
637
|
+
process.exit(0);
|
|
638
|
+
break;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
case 'worker': {
|
|
642
|
+
const { QueueEngine } = await import('../src/runtime/queue-engine');
|
|
643
|
+
const { getDatabase } = await import('../src/core/database-factory');
|
|
644
|
+
const { loadSynapseConfig } = await import('../src/core/config');
|
|
645
|
+
const { isJob } = await import('../src/runtime/discovery-rules');
|
|
646
|
+
|
|
647
|
+
const config = await loadSynapseConfig(root);
|
|
648
|
+
const queue = new QueueEngine();
|
|
649
|
+
const db = getDatabase();
|
|
650
|
+
|
|
651
|
+
const resolution = resolveSlicesDir(root);
|
|
652
|
+
if (resolution.ok) {
|
|
653
|
+
const sliceFiles = findSliceFiles(resolution.value.slicesDir);
|
|
654
|
+
for (const file of sliceFiles) {
|
|
655
|
+
try {
|
|
656
|
+
const mod = await import(path.resolve(file));
|
|
657
|
+
for (const [exportKey, exportVal] of Object.entries(mod)) {
|
|
658
|
+
if (
|
|
659
|
+
isJob(exportKey) &&
|
|
660
|
+
exportVal &&
|
|
661
|
+
typeof exportVal === 'object' &&
|
|
662
|
+
'name' in (exportVal as Record<string, unknown>)
|
|
663
|
+
) {
|
|
664
|
+
queue.registerJob(exportVal as any);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
} catch (err) {
|
|
668
|
+
console.error(`[Worker] Erro ao carregar jobs da fatia ${file}:`, err);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
console.log('⚡ [SynapseJS Worker] Fila de background jobs ativa e processando...');
|
|
674
|
+
let running = true;
|
|
675
|
+
const shutdown = () => {
|
|
676
|
+
running = false;
|
|
677
|
+
console.log('\n🛑 [SynapseJS Worker] Encerrando worker...');
|
|
678
|
+
queue.close();
|
|
679
|
+
process.exit(0);
|
|
680
|
+
};
|
|
681
|
+
process.on('SIGINT', shutdown);
|
|
682
|
+
process.on('SIGTERM', shutdown);
|
|
683
|
+
|
|
684
|
+
while (running) {
|
|
685
|
+
try {
|
|
686
|
+
const didWork = await queue.processNextJob({
|
|
687
|
+
db,
|
|
688
|
+
services: (config.services as Record<string, unknown>) || {}
|
|
689
|
+
});
|
|
690
|
+
if (!didWork) {
|
|
691
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
692
|
+
}
|
|
693
|
+
} catch (err) {
|
|
694
|
+
console.error('[Worker] Erro no ciclo de execução:', err);
|
|
695
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
break;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
case 'info': {
|
|
702
|
+
process.stdout.write(
|
|
703
|
+
`${JSON.stringify(
|
|
704
|
+
{
|
|
705
|
+
framework: 'SynapseJS',
|
|
706
|
+
version: '1.1.0',
|
|
707
|
+
runtime: 'Bun + Bun.serve',
|
|
708
|
+
database: 'Embedded SQLite (WAL) and PostgreSQL, both verified',
|
|
709
|
+
protocols: ['REST/HTTP', 'Isomorphic RPC', 'Model Context Protocol (MCP)'],
|
|
710
|
+
features: [
|
|
711
|
+
{
|
|
712
|
+
feature: 'Vertical slices (N = 1) with Locality of Behavior',
|
|
713
|
+
status: 'stable',
|
|
714
|
+
evidence: 'bun test packages/synapse/test/locality.test.ts'
|
|
715
|
+
},
|
|
716
|
+
{
|
|
717
|
+
feature: 'Result<T, E> control flow (no public throwing helper)',
|
|
718
|
+
status: 'stable',
|
|
719
|
+
evidence: 'bun test packages/synapse/test/machine-types.test.ts'
|
|
720
|
+
},
|
|
721
|
+
{
|
|
722
|
+
feature: 'TypeBox JIT input contracts',
|
|
723
|
+
status: 'stable',
|
|
724
|
+
evidence: 'bun test packages/synapse/test/slice-contract.test.ts'
|
|
725
|
+
},
|
|
726
|
+
{
|
|
727
|
+
feature: 'Declarative sliceSchema migrations, applied once per statement',
|
|
728
|
+
status: 'stable',
|
|
729
|
+
evidence: 'bun test packages/synapse/test/migration-runner.test.ts'
|
|
730
|
+
},
|
|
731
|
+
{
|
|
732
|
+
feature: 'Domain errors map to HTTP status (401/403/404/409/422/500)',
|
|
733
|
+
status: 'stable',
|
|
734
|
+
evidence: 'bun test packages/synapse/test/runtime-server.test.ts'
|
|
735
|
+
},
|
|
736
|
+
{
|
|
737
|
+
feature: 'React hydration from a client bundle the splitter produces',
|
|
738
|
+
status: 'stable',
|
|
739
|
+
evidence: 'bun test packages/synapse/test/runtime-server.test.ts'
|
|
740
|
+
},
|
|
741
|
+
{
|
|
742
|
+
feature: 'Signed sessions (SYNAPSE_SESSION_SECRET) and static files from public/',
|
|
743
|
+
status: 'stable',
|
|
744
|
+
evidence: 'bun test packages/synapse/test/runtime-server.test.ts'
|
|
745
|
+
},
|
|
746
|
+
{
|
|
747
|
+
feature: 'Login with Bun.password, signed token and cookie session (storeSession/clearSession)',
|
|
748
|
+
status: 'stable',
|
|
749
|
+
evidence:
|
|
750
|
+
'bun run --cwd apps/crm synapse test (login oracle: the server accepts the issued token) + bun test packages/synapse/test/session-cookie.test.ts'
|
|
751
|
+
},
|
|
752
|
+
{
|
|
753
|
+
feature: 'Cross-slice atomicity through src/shared modules (db.transaction, rollback proven on SQLite)',
|
|
754
|
+
status: 'stable',
|
|
755
|
+
evidence: 'bun test packages/synapse/test/shared-modules.test.ts'
|
|
756
|
+
},
|
|
757
|
+
{
|
|
758
|
+
feature: 'Slice boundary: a slice never imports another slice (SLICE_IMPORTS_SLICE)',
|
|
759
|
+
status: 'stable',
|
|
760
|
+
evidence: 'bun test packages/synapse/test/slice-splitter.test.ts'
|
|
761
|
+
},
|
|
762
|
+
{
|
|
763
|
+
feature: 'Browser RPC transport that never throws (RPC_MALFORMED/RPC_UNREACHABLE as values)',
|
|
764
|
+
status: 'stable',
|
|
765
|
+
evidence: 'bun test packages/synapse/test/rpc-client.test.ts'
|
|
766
|
+
},
|
|
767
|
+
{
|
|
768
|
+
feature: 'Pre-built client bundles with a manifest (synapse build)',
|
|
769
|
+
status: 'stable',
|
|
770
|
+
evidence: 'bun test packages/synapse/test/client-bundler.test.ts'
|
|
771
|
+
},
|
|
772
|
+
{
|
|
773
|
+
feature: 'Uploads with session, size limit and traversal guard (/_synapse/files)',
|
|
774
|
+
status: 'stable',
|
|
775
|
+
evidence: 'bun test packages/synapse/test/uploads.test.ts'
|
|
776
|
+
},
|
|
777
|
+
{
|
|
778
|
+
feature: 'Embedded SQLite engine (WAL, prepared-statement cache)',
|
|
779
|
+
status: 'stable',
|
|
780
|
+
evidence: 'bun test packages/synapse/test/sqlite-client.test.ts'
|
|
781
|
+
},
|
|
782
|
+
{
|
|
783
|
+
feature: 'Zero-wiring routing, SSR shell and RPC dispatcher',
|
|
784
|
+
status: 'stable',
|
|
785
|
+
evidence: 'bun --cwd examples/enterprise-crm test:e2e'
|
|
786
|
+
},
|
|
787
|
+
{
|
|
788
|
+
feature: 'Slice discovery resolution (never reports PASS with zero slices)',
|
|
789
|
+
status: 'stable',
|
|
790
|
+
evidence: 'bun test packages/synapse/test/slice-discovery.test.ts'
|
|
791
|
+
},
|
|
792
|
+
{
|
|
793
|
+
feature: 'MCP stdio server (11 tools)',
|
|
794
|
+
status: 'stable',
|
|
795
|
+
evidence: 'bun test packages/synapse/test/mcp-server.test.ts'
|
|
796
|
+
},
|
|
797
|
+
{
|
|
798
|
+
feature: 'AST skeletonizer to .codebase/repo-map.d.ts',
|
|
799
|
+
status: 'stable',
|
|
800
|
+
evidence: 'bun --cwd examples/enterprise-crm skeleton'
|
|
801
|
+
},
|
|
802
|
+
{
|
|
803
|
+
feature: 'Centralized Database Schema Catalog (.codebase/db-schema.d.ts)',
|
|
804
|
+
status: 'stable',
|
|
805
|
+
evidence: 'bun test packages/synapse/test/db-schema-generator.test.ts'
|
|
806
|
+
},
|
|
807
|
+
{
|
|
808
|
+
feature: 'Unified Object Storage (LocalStorage + AWS S3/R2 with SigV4)',
|
|
809
|
+
status: 'stable',
|
|
810
|
+
evidence: 'bun test packages/synapse/test/storage.test.ts'
|
|
811
|
+
},
|
|
812
|
+
{
|
|
813
|
+
feature: 'Distributed PostgreSQL Queue (SKIP LOCKED, Full Jitter, DLQ)',
|
|
814
|
+
status: 'stable',
|
|
815
|
+
evidence: 'bun test packages/synapse/test/postgres-queue.test.ts'
|
|
816
|
+
},
|
|
817
|
+
{
|
|
818
|
+
feature: 'Isomorphic slice splitter (shared/server/client modules)',
|
|
819
|
+
status: 'stable',
|
|
820
|
+
evidence: 'bun test packages/synapse/test/slice-splitter.test.ts'
|
|
821
|
+
},
|
|
822
|
+
{
|
|
823
|
+
feature: 'Slice invariants executed by bun:test (per-invariant reporting)',
|
|
824
|
+
status: 'stable',
|
|
825
|
+
evidence: 'bun test packages/synapse/test/oracle-runner.test.ts'
|
|
826
|
+
},
|
|
827
|
+
{
|
|
828
|
+
feature: 'Incremental diagnostics cache across processes',
|
|
829
|
+
status: 'roadmap',
|
|
830
|
+
evidence: 'removido na 0.4.0: medido mais lento que o check completo (2.5s vs 1.9s)'
|
|
831
|
+
},
|
|
832
|
+
{
|
|
833
|
+
feature: 'PostgreSQL parity (migrations, DDL, action round-trip)',
|
|
834
|
+
status: 'stable',
|
|
835
|
+
evidence: 'bun run test:postgres (CI roda contra um serviço postgres:16)'
|
|
836
|
+
},
|
|
837
|
+
{
|
|
838
|
+
feature: 'Context surface benchmark (files/tokens an agent must read)',
|
|
839
|
+
status: 'stable',
|
|
840
|
+
evidence: 'bun run bench'
|
|
841
|
+
},
|
|
842
|
+
{
|
|
843
|
+
feature: 'Isolated SQLite Queue & Background Jobs (defineJob, exponential backoff)',
|
|
844
|
+
status: 'stable',
|
|
845
|
+
evidence: 'bun test packages/synapse/test/jobs-queue.test.ts'
|
|
846
|
+
},
|
|
847
|
+
{
|
|
848
|
+
feature: 'Bidirectional DDL migrations with transactional rollback (synapse rollback)',
|
|
849
|
+
status: 'stable',
|
|
850
|
+
evidence: 'bun test packages/synapse/test/onda2.test.ts'
|
|
851
|
+
},
|
|
852
|
+
{
|
|
853
|
+
feature: 'Vendor code-splitting and dynamic metadata (sliceMeta)',
|
|
854
|
+
status: 'stable',
|
|
855
|
+
evidence: 'bun test packages/synapse/test/onda1.test.ts'
|
|
856
|
+
},
|
|
857
|
+
{
|
|
858
|
+
feature: 'Hierarchical domain layouts (_layout.tsx)',
|
|
859
|
+
status: 'stable',
|
|
860
|
+
evidence: 'bun test packages/synapse/test/onda1.test.ts'
|
|
861
|
+
},
|
|
862
|
+
{
|
|
863
|
+
feature: 'Sliding-window rate limiting & streaming upload guard',
|
|
864
|
+
status: 'stable',
|
|
865
|
+
evidence: 'bun test packages/synapse/test/onda2.test.ts'
|
|
866
|
+
},
|
|
867
|
+
{
|
|
868
|
+
feature: 'Distributed PostgreSQL Event Hub & Isomorphic i18n',
|
|
869
|
+
status: 'stable',
|
|
870
|
+
evidence: 'bun test packages/synapse/test/onda3.test.ts'
|
|
871
|
+
},
|
|
872
|
+
{
|
|
873
|
+
feature: 'Token revocation, TOTP 2FA, image optimization & plugin hooks',
|
|
874
|
+
status: 'stable',
|
|
875
|
+
evidence: 'bun test packages/synapse/test/onda4.test.ts'
|
|
876
|
+
}
|
|
877
|
+
],
|
|
878
|
+
commands: [
|
|
879
|
+
'new',
|
|
880
|
+
'dev',
|
|
881
|
+
'start',
|
|
882
|
+
'check',
|
|
883
|
+
'migrate',
|
|
884
|
+
'rollback',
|
|
885
|
+
'db-drift',
|
|
886
|
+
'impact',
|
|
887
|
+
'mcp',
|
|
888
|
+
'skeleton',
|
|
889
|
+
'db-schema',
|
|
890
|
+
'split',
|
|
891
|
+
'build',
|
|
892
|
+
'test',
|
|
893
|
+
'worker',
|
|
894
|
+
'new-slice',
|
|
895
|
+
'contract',
|
|
896
|
+
'info'
|
|
897
|
+
]
|
|
898
|
+
},
|
|
899
|
+
null,
|
|
900
|
+
2
|
|
901
|
+
)}\n`
|
|
902
|
+
);
|
|
903
|
+
process.exit(0);
|
|
904
|
+
break;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
default: {
|
|
908
|
+
process.stderr.write(
|
|
909
|
+
`Unknown command: ${command}\nAvailable: new, dev, check, migrate, rollback, mcp, skeleton, db-schema, split, build, test, worker, new-slice, contract, info\n`
|
|
910
|
+
);
|
|
911
|
+
process.exit(1);
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
main().catch((err) => {
|
|
917
|
+
process.stderr.write(`${JSON.stringify({ status: 'ERROR', error: String(err) })}\n`);
|
|
918
|
+
process.exit(1);
|
|
919
|
+
});
|