@ismaelsoilet/synapsejs 1.8.0 → 1.9.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 +95 -4
- package/bin/synapse.ts +217 -239
- package/package.json +1 -1
- package/src/compiler/context-bench.ts +7 -1
- package/src/compiler/contract.ts +31 -4
- package/src/compiler/db-schema-generator.ts +39 -8
- package/src/compiler/fields-parser.ts +28 -1
- package/src/compiler/impact-analyzer.ts +31 -1
- package/src/compiler/migration-runner.ts +10 -2
- package/src/compiler/optional-coverage.ts +137 -0
- package/src/compiler/oracle-runner.ts +8 -3
- package/src/compiler/scaffolder.ts +114 -5
- package/src/compiler/slice-discovery.ts +8 -1
- package/src/compiler/slice-splitter.ts +47 -3
- package/src/core/action-context.ts +51 -14
- package/src/core/config.ts +88 -10
- package/src/core/database-client.ts +68 -6
- package/src/core/index.ts +3 -0
- package/src/core/path-guard.ts +105 -0
- package/src/core/postgres-queue.ts +25 -7
- package/src/core/session-token.ts +108 -7
- package/src/core/storage.ts +60 -19
- package/src/core/topics.ts +84 -0
- package/src/core/webhook-signature.ts +151 -0
- package/src/index.ts +2 -1
- package/src/mcp/server.ts +60 -217
- package/src/mcp/tools.ts +238 -0
- package/src/project-status.ts +330 -0
- package/src/runtime/network-guard.ts +3 -1
- package/src/runtime/queue-engine.ts +47 -0
- package/src/runtime/rate-limiter.ts +14 -4
- package/src/runtime/server.ts +876 -180
- package/src/runtime/uploads.ts +199 -19
- package/src/version.ts +9 -2
- package/templates/starter/tsconfig.json +1 -0
package/bin/synapse.ts
CHANGED
|
@@ -27,6 +27,7 @@ import { renderContractJson, renderContractMarkdown } from '../src/compiler/cont
|
|
|
27
27
|
import { generateDatabaseSchemaCatalog } from '../src/compiler/db-schema-generator';
|
|
28
28
|
import { analyzeImpact } from '../src/compiler/impact-analyzer';
|
|
29
29
|
import { rollbackSliceMigrations, runSliceMigrations } from '../src/compiler/migration-runner';
|
|
30
|
+
import { analyzeOptionalItemCoverage, unionCoverage } from '../src/compiler/optional-coverage';
|
|
30
31
|
import { runSliceOracles } from '../src/compiler/oracle-runner';
|
|
31
32
|
import { scaffoldCrud, scaffoldShared, scaffoldSlice } from '../src/compiler/scaffolder';
|
|
32
33
|
import { checkSchemaDrift } from '../src/compiler/schema-drift';
|
|
@@ -34,13 +35,83 @@ import { findSliceFiles, resolveSlicesDir } from '../src/compiler/slice-discover
|
|
|
34
35
|
import { artifactDirectory, splitSlice, verifySplit, writeSplitArtifacts } from '../src/compiler/slice-splitter';
|
|
35
36
|
import { SLICE_TEMPLATES, type SliceTemplate } from '../src/compiler/slice-templates';
|
|
36
37
|
import { buildStandalone } from '../src/compiler/standalone-builder';
|
|
38
|
+
import { isInsideBase } from '../src/core/path-guard';
|
|
37
39
|
import { SynapseMcpServer } from '../src/mcp/server';
|
|
40
|
+
import { projectStatus } from '../src/project-status';
|
|
38
41
|
import { SynapseServer } from '../src/runtime/server';
|
|
42
|
+
import { SYNAPSE_VERSION } from '../src/version';
|
|
39
43
|
|
|
40
44
|
const command = process.argv[2] || 'check';
|
|
41
45
|
const root = process.cwd();
|
|
42
46
|
|
|
47
|
+
/**
|
|
48
|
+
* The flags each subcommand accepts. An unrecognised flag is refused on every
|
|
49
|
+
* command, with the same machine-readable failure code — a machine consumer must
|
|
50
|
+
* not be told "PASS" for a flag the command silently ignored.
|
|
51
|
+
*/
|
|
52
|
+
const KNOWN_FLAGS: Record<string, string[]> = {
|
|
53
|
+
dev: ['--watch'],
|
|
54
|
+
start: ['--acknowledge-insecure'],
|
|
55
|
+
check: [],
|
|
56
|
+
migrate: [],
|
|
57
|
+
rollback: ['--steps'],
|
|
58
|
+
mcp: [],
|
|
59
|
+
skeleton: [],
|
|
60
|
+
'db-schema': [],
|
|
61
|
+
'db-drift': [],
|
|
62
|
+
impact: [],
|
|
63
|
+
split: [],
|
|
64
|
+
'new-slice': ['--template', '--fields'],
|
|
65
|
+
'new-shared': [],
|
|
66
|
+
build: ['--standalone'],
|
|
67
|
+
contract: ['--markdown'],
|
|
68
|
+
test: ['--gate'],
|
|
69
|
+
new: [],
|
|
70
|
+
create: [],
|
|
71
|
+
init: [],
|
|
72
|
+
worker: [],
|
|
73
|
+
info: [],
|
|
74
|
+
coverage: []
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/** Returns the first unrecognised flag, or null when every flag is declared. */
|
|
78
|
+
function unknownFlag(argv: string[]): string | null {
|
|
79
|
+
const known = KNOWN_FLAGS[command] ?? [];
|
|
80
|
+
|
|
81
|
+
for (const argument of argv.slice(3)) {
|
|
82
|
+
if (!argument.startsWith('--')) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const name = argument.split('=')[0];
|
|
87
|
+
|
|
88
|
+
if (!known.includes(name)) {
|
|
89
|
+
return argument;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
43
96
|
async function main() {
|
|
97
|
+
const offendingFlag = unknownFlag(process.argv);
|
|
98
|
+
|
|
99
|
+
if (offendingFlag) {
|
|
100
|
+
process.stdout.write(
|
|
101
|
+
`${JSON.stringify(
|
|
102
|
+
{
|
|
103
|
+
status: 'FAIL',
|
|
104
|
+
code: 'UNKNOWN_FLAG',
|
|
105
|
+
operation: 'FLAG_VALIDATION',
|
|
106
|
+
message: `Flag desconhecida: ${offendingFlag}. Flags aceitas por '${command}': ${(KNOWN_FLAGS[command] ?? []).join(', ') || 'nenhuma'}.`
|
|
107
|
+
},
|
|
108
|
+
null,
|
|
109
|
+
2
|
|
110
|
+
)}\n`
|
|
111
|
+
);
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
|
|
44
115
|
switch (command) {
|
|
45
116
|
case 'dev': {
|
|
46
117
|
const watch = process.argv.includes('--watch');
|
|
@@ -62,12 +133,32 @@ async function main() {
|
|
|
62
133
|
|
|
63
134
|
case 'start': {
|
|
64
135
|
const port = parseInt(process.argv[3] || process.env.PORT || '3000', 10);
|
|
65
|
-
const
|
|
136
|
+
const { isProductionLikeMode } = await import('../src/runtime/server');
|
|
137
|
+
const acknowledged =
|
|
138
|
+
process.argv.includes('--acknowledge-insecure') ||
|
|
139
|
+
process.env.SYNAPSE_ACKNOWLEDGE_INSECURE === 'true' ||
|
|
140
|
+
process.env.SYNAPSE_DEV_HEADERS === 'true';
|
|
66
141
|
|
|
67
|
-
if (
|
|
142
|
+
if (isProductionLikeMode() && !process.env.SYNAPSE_SESSION_SECRET && !acknowledged) {
|
|
143
|
+
process.stdout.write(
|
|
144
|
+
`${JSON.stringify(
|
|
145
|
+
{
|
|
146
|
+
status: 'FAIL',
|
|
147
|
+
operation: 'START',
|
|
148
|
+
code: 'MISSING_SESSION_SECRET',
|
|
149
|
+
message:
|
|
150
|
+
'Modo de produção sem SYNAPSE_SESSION_SECRET: toda requisição resolveria para anônimo. Configure o segredo ou passe --acknowledge-insecure para assumir o risco explicitamente.'
|
|
151
|
+
},
|
|
152
|
+
null,
|
|
153
|
+
2
|
|
154
|
+
)}\n`
|
|
155
|
+
);
|
|
156
|
+
process.exit(1);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (!process.env.SYNAPSE_SESSION_SECRET && acknowledged) {
|
|
68
160
|
console.warn(
|
|
69
|
-
'⚠️ [AVISO DE SEGURANÇA]
|
|
70
|
-
' Sessões de usuário permanecerão anônimas por padrão.'
|
|
161
|
+
'⚠️ [AVISO DE SEGURANÇA] Iniciando sem SYNAPSE_SESSION_SECRET: identidade e papéis vindos de headers do chamador (x-user-id, x-user-roles) NÃO serão confiados; requisições permanecem anônimas.'
|
|
71
162
|
);
|
|
72
163
|
}
|
|
73
164
|
|
|
@@ -79,13 +170,24 @@ async function main() {
|
|
|
79
170
|
const gracefulShutdown = async (signal: string) => {
|
|
80
171
|
if (shuttingDown) return;
|
|
81
172
|
shuttingDown = true;
|
|
82
|
-
console.log(`\n🛑 [SynapseJS] Recebido sinal ${signal}, iniciando encerramento gracioso...`);
|
|
83
173
|
try {
|
|
84
|
-
await server.stop();
|
|
85
|
-
|
|
174
|
+
const summary = await server.stop();
|
|
175
|
+
process.stdout.write(
|
|
176
|
+
`${JSON.stringify({
|
|
177
|
+
status: 'PASS',
|
|
178
|
+
operation: 'GRACEFUL_SHUTDOWN',
|
|
179
|
+
signal,
|
|
180
|
+
drainedRequests: summary.drained,
|
|
181
|
+
abortedRequests: summary.aborted,
|
|
182
|
+
jobsRequeued: summary.jobsRequeued,
|
|
183
|
+
jobsFailed: summary.jobsFailed
|
|
184
|
+
})}\n`
|
|
185
|
+
);
|
|
86
186
|
process.exit(0);
|
|
87
187
|
} catch (err) {
|
|
88
|
-
|
|
188
|
+
process.stderr.write(
|
|
189
|
+
`${JSON.stringify({ status: 'ERROR', operation: 'GRACEFUL_SHUTDOWN', error: String(err) })}\n`
|
|
190
|
+
);
|
|
89
191
|
process.exit(1);
|
|
90
192
|
}
|
|
91
193
|
};
|
|
@@ -257,7 +359,9 @@ async function main() {
|
|
|
257
359
|
|
|
258
360
|
const report = analyzeImpact(target, root);
|
|
259
361
|
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
260
|
-
|
|
362
|
+
// A target that does not exist is a failure; a valid target with no
|
|
363
|
+
// dependents is not.
|
|
364
|
+
process.exit(report.status === 'PASS' ? 0 : 1);
|
|
261
365
|
break;
|
|
262
366
|
}
|
|
263
367
|
|
|
@@ -341,7 +445,7 @@ async function main() {
|
|
|
341
445
|
const name = process.argv[4];
|
|
342
446
|
|
|
343
447
|
if (!domain || !name) {
|
|
344
|
-
process.
|
|
448
|
+
process.stdout.write(
|
|
345
449
|
`${JSON.stringify({
|
|
346
450
|
status: 'ERROR',
|
|
347
451
|
message: 'Parâmetros obrigatórios ausentes. Uso: synapse new-slice <domain> <name>'
|
|
@@ -409,7 +513,7 @@ async function main() {
|
|
|
409
513
|
const created = scaffoldSlice(domain, name, root, template, fieldsArg);
|
|
410
514
|
|
|
411
515
|
if (!created.ok) {
|
|
412
|
-
process.
|
|
516
|
+
process.stdout.write(
|
|
413
517
|
`${JSON.stringify(
|
|
414
518
|
{
|
|
415
519
|
status: 'ERROR',
|
|
@@ -445,7 +549,7 @@ async function main() {
|
|
|
445
549
|
case 'new-shared': {
|
|
446
550
|
const name = process.argv[3];
|
|
447
551
|
if (!name) {
|
|
448
|
-
process.
|
|
552
|
+
process.stdout.write(
|
|
449
553
|
`${JSON.stringify({
|
|
450
554
|
status: 'ERROR',
|
|
451
555
|
message: 'Nome do módulo compartilhado ausente. Uso: synapse new-shared <nome>'
|
|
@@ -458,7 +562,7 @@ async function main() {
|
|
|
458
562
|
const created = scaffoldShared(name, root);
|
|
459
563
|
|
|
460
564
|
if (!created.ok) {
|
|
461
|
-
process.
|
|
565
|
+
process.stdout.write(
|
|
462
566
|
`${JSON.stringify(
|
|
463
567
|
{
|
|
464
568
|
status: 'ERROR',
|
|
@@ -572,6 +676,52 @@ async function main() {
|
|
|
572
676
|
break;
|
|
573
677
|
}
|
|
574
678
|
|
|
679
|
+
case 'coverage': {
|
|
680
|
+
// Which optional contract items the committed slices exercise — across every
|
|
681
|
+
// shipped application, so the report covers the union.
|
|
682
|
+
const targets = [
|
|
683
|
+
root,
|
|
684
|
+
...(fs.existsSync(path.join(root, 'examples'))
|
|
685
|
+
? fs
|
|
686
|
+
.readdirSync(path.join(root, 'examples'), { withFileTypes: true })
|
|
687
|
+
.filter((entry) => entry.isDirectory())
|
|
688
|
+
.map((entry) => path.join(root, 'examples', entry.name))
|
|
689
|
+
: [])
|
|
690
|
+
];
|
|
691
|
+
|
|
692
|
+
const reports = targets.map((target) => analyzeOptionalItemCoverage(target));
|
|
693
|
+
const resolvable = reports.filter((report) => report.status !== 'FAIL' || report.totalSlices > 0);
|
|
694
|
+
|
|
695
|
+
if (resolvable.length === 0) {
|
|
696
|
+
process.stdout.write(
|
|
697
|
+
`${JSON.stringify(reports[0] ?? { status: 'FAIL', operation: 'OPTIONAL_ITEM_COVERAGE' }, null, 2)}\n`
|
|
698
|
+
);
|
|
699
|
+
process.exit(1);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
const union = unionCoverage(resolvable);
|
|
703
|
+
|
|
704
|
+
process.stdout.write(
|
|
705
|
+
`${JSON.stringify(
|
|
706
|
+
{
|
|
707
|
+
status: union.unexercised.length === 0 ? 'PASS' : 'FAIL',
|
|
708
|
+
operation: 'OPTIONAL_ITEM_COVERAGE',
|
|
709
|
+
applications: resolvable.map((report) => ({
|
|
710
|
+
appDir: report.appDir,
|
|
711
|
+
totalSlices: report.totalSlices,
|
|
712
|
+
items: report.items
|
|
713
|
+
})),
|
|
714
|
+
covered: union.covered,
|
|
715
|
+
unexercised: union.unexercised
|
|
716
|
+
},
|
|
717
|
+
null,
|
|
718
|
+
2
|
|
719
|
+
)}\n`
|
|
720
|
+
);
|
|
721
|
+
process.exit(union.unexercised.length === 0 ? 0 : 1);
|
|
722
|
+
break;
|
|
723
|
+
}
|
|
724
|
+
|
|
575
725
|
case 'contract': {
|
|
576
726
|
// O framework se descreve numa chamada: e o que o agente le em vez de adivinhar.
|
|
577
727
|
const asMarkdown = process.argv.includes('--markdown');
|
|
@@ -597,7 +747,7 @@ async function main() {
|
|
|
597
747
|
case 'init': {
|
|
598
748
|
const rawTarget = process.argv[3];
|
|
599
749
|
if (!rawTarget) {
|
|
600
|
-
process.
|
|
750
|
+
process.stdout.write(
|
|
601
751
|
`${JSON.stringify({
|
|
602
752
|
status: 'ERROR',
|
|
603
753
|
message: 'Nome do projeto obrigatório. Uso: synapse new <project-name>'
|
|
@@ -607,15 +757,34 @@ async function main() {
|
|
|
607
757
|
}
|
|
608
758
|
|
|
609
759
|
const targetDir = path.resolve(root, rawTarget);
|
|
760
|
+
|
|
761
|
+
if (!isInsideBase(root, targetDir)) {
|
|
762
|
+
process.stdout.write(
|
|
763
|
+
`${JSON.stringify(
|
|
764
|
+
{
|
|
765
|
+
status: 'ERROR',
|
|
766
|
+
operation: 'CREATE_PROJECT',
|
|
767
|
+
code: 'INVALID_PATH_SEGMENT',
|
|
768
|
+
message: `O caminho '${rawTarget}' resolve para fora do diretório atual (${root}). Um projeto novo é criado dentro dele; use um nome simples ou um subdiretório.`
|
|
769
|
+
},
|
|
770
|
+
null,
|
|
771
|
+
2
|
|
772
|
+
)}\n`
|
|
773
|
+
);
|
|
774
|
+
process.exit(1);
|
|
775
|
+
}
|
|
776
|
+
|
|
610
777
|
const cleanProjectName = path
|
|
611
778
|
.basename(targetDir)
|
|
612
779
|
.toLowerCase()
|
|
613
780
|
.replace(/[^a-z0-9-_]/g, '-');
|
|
614
781
|
|
|
615
782
|
if (fs.existsSync(targetDir)) {
|
|
616
|
-
process.
|
|
783
|
+
process.stdout.write(
|
|
617
784
|
`${JSON.stringify({
|
|
618
785
|
status: 'ERROR',
|
|
786
|
+
operation: 'CREATE_PROJECT',
|
|
787
|
+
code: 'TARGET_EXISTS',
|
|
619
788
|
message: `O diretório '${rawTarget}' já existe em ${targetDir}.`
|
|
620
789
|
})}\n`
|
|
621
790
|
);
|
|
@@ -633,9 +802,11 @@ async function main() {
|
|
|
633
802
|
const templateDir = candidates.find((dir) => fs.existsSync(dir));
|
|
634
803
|
|
|
635
804
|
if (!templateDir) {
|
|
636
|
-
process.
|
|
805
|
+
process.stdout.write(
|
|
637
806
|
`${JSON.stringify({
|
|
638
807
|
status: 'ERROR',
|
|
808
|
+
operation: 'CREATE_PROJECT',
|
|
809
|
+
code: 'TEMPLATE_NOT_FOUND',
|
|
639
810
|
message: 'Template oficial starter não encontrado nos diretórios candidatos.'
|
|
640
811
|
})}\n`
|
|
641
812
|
);
|
|
@@ -660,9 +831,11 @@ async function main() {
|
|
|
660
831
|
let content = fs.readFileSync(s, 'utf-8');
|
|
661
832
|
if (entry.name === 'package.json') {
|
|
662
833
|
content = content.replace('"starter-app"', `"${cleanProjectName}"`);
|
|
834
|
+
// The scaffold depends on the version this CLI ships, never on a frozen
|
|
835
|
+
// older pin: a new project must receive every fix since then.
|
|
663
836
|
content = content.replace(
|
|
664
837
|
/"(@ismaelsoilet\/)?synapsejs":\s*"[^"]+"/g,
|
|
665
|
-
|
|
838
|
+
`"@ismaelsoilet/synapsejs": "^${SYNAPSE_VERSION}"`
|
|
666
839
|
);
|
|
667
840
|
}
|
|
668
841
|
fs.writeFileSync(d, content, 'utf-8');
|
|
@@ -712,6 +885,7 @@ async function main() {
|
|
|
712
885
|
typeof exportVal === 'object' &&
|
|
713
886
|
'name' in (exportVal as Record<string, unknown>)
|
|
714
887
|
) {
|
|
888
|
+
// biome-ignore lint/suspicious/noExplicitAny: slice export boundary, validated by the shape check above
|
|
715
889
|
queue.registerJob(exportVal as any);
|
|
716
890
|
}
|
|
717
891
|
}
|
|
@@ -724,9 +898,21 @@ async function main() {
|
|
|
724
898
|
console.log('⚡ [SynapseJS Worker] Fila de background jobs ativa e processando...');
|
|
725
899
|
let running = true;
|
|
726
900
|
const shutdown = () => {
|
|
901
|
+
if (!running) return;
|
|
727
902
|
running = false;
|
|
728
|
-
|
|
903
|
+
// Stop claiming first, then return whatever was running to the queue with its
|
|
904
|
+
// attempt counter already advanced by the interrupted claim.
|
|
905
|
+
queue.stopClaiming();
|
|
906
|
+
const interrupted = queue.interruptRunningJobs();
|
|
729
907
|
queue.close();
|
|
908
|
+
process.stdout.write(
|
|
909
|
+
`${JSON.stringify({
|
|
910
|
+
status: 'PASS',
|
|
911
|
+
operation: 'WORKER_SHUTDOWN',
|
|
912
|
+
jobsRequeued: interrupted.requeued,
|
|
913
|
+
jobsFailed: interrupted.failed
|
|
914
|
+
})}\n`
|
|
915
|
+
);
|
|
730
916
|
process.exit(0);
|
|
731
917
|
};
|
|
732
918
|
process.on('SIGINT', shutdown);
|
|
@@ -750,235 +936,27 @@ async function main() {
|
|
|
750
936
|
}
|
|
751
937
|
|
|
752
938
|
case 'info': {
|
|
939
|
+
// One machine-readable source: version, adoption, tool inventory and feature
|
|
940
|
+
// status all come from the same module the docs gate reads.
|
|
941
|
+
process.stdout.write(`${JSON.stringify(projectStatus(), null, 2)}\n`);
|
|
942
|
+
process.exit(0);
|
|
943
|
+
break;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
default: {
|
|
753
947
|
process.stdout.write(
|
|
754
948
|
`${JSON.stringify(
|
|
755
949
|
{
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
features: [
|
|
762
|
-
{
|
|
763
|
-
feature: 'Vertical slices (N = 1) with Locality of Behavior',
|
|
764
|
-
status: 'stable',
|
|
765
|
-
evidence: 'bun test packages/synapse/test/locality.test.ts'
|
|
766
|
-
},
|
|
767
|
-
{
|
|
768
|
-
feature: 'Result<T, E> control flow (no public throwing helper)',
|
|
769
|
-
status: 'stable',
|
|
770
|
-
evidence: 'bun test packages/synapse/test/machine-types.test.ts'
|
|
771
|
-
},
|
|
772
|
-
{
|
|
773
|
-
feature: 'TypeBox JIT input contracts',
|
|
774
|
-
status: 'stable',
|
|
775
|
-
evidence: 'bun test packages/synapse/test/slice-contract.test.ts'
|
|
776
|
-
},
|
|
777
|
-
{
|
|
778
|
-
feature: 'Declarative sliceSchema migrations, applied once per statement',
|
|
779
|
-
status: 'stable',
|
|
780
|
-
evidence: 'bun test packages/synapse/test/migration-runner.test.ts'
|
|
781
|
-
},
|
|
782
|
-
{
|
|
783
|
-
feature: 'Domain errors map to HTTP status (401/403/404/409/422/500)',
|
|
784
|
-
status: 'stable',
|
|
785
|
-
evidence: 'bun test packages/synapse/test/runtime-server.test.ts'
|
|
786
|
-
},
|
|
787
|
-
{
|
|
788
|
-
feature: 'React hydration from a client bundle the splitter produces',
|
|
789
|
-
status: 'stable',
|
|
790
|
-
evidence: 'bun test packages/synapse/test/runtime-server.test.ts'
|
|
791
|
-
},
|
|
792
|
-
{
|
|
793
|
-
feature: 'Signed sessions (SYNAPSE_SESSION_SECRET) and static files from public/',
|
|
794
|
-
status: 'stable',
|
|
795
|
-
evidence: 'bun test packages/synapse/test/runtime-server.test.ts'
|
|
796
|
-
},
|
|
797
|
-
{
|
|
798
|
-
feature: 'Login with Bun.password, signed token and cookie session (storeSession/clearSession)',
|
|
799
|
-
status: 'stable',
|
|
800
|
-
evidence:
|
|
801
|
-
'bun run --cwd apps/crm synapse test (login oracle: the server accepts the issued token) + bun test packages/synapse/test/session-cookie.test.ts'
|
|
802
|
-
},
|
|
803
|
-
{
|
|
804
|
-
feature: 'Cross-slice atomicity through src/shared modules (db.transaction, rollback proven on SQLite)',
|
|
805
|
-
status: 'stable',
|
|
806
|
-
evidence: 'bun test packages/synapse/test/shared-modules.test.ts'
|
|
807
|
-
},
|
|
808
|
-
{
|
|
809
|
-
feature: 'Slice boundary: a slice never imports another slice (SLICE_IMPORTS_SLICE)',
|
|
810
|
-
status: 'stable',
|
|
811
|
-
evidence: 'bun test packages/synapse/test/slice-splitter.test.ts'
|
|
812
|
-
},
|
|
813
|
-
{
|
|
814
|
-
feature: 'Browser RPC transport that never throws (RPC_MALFORMED/RPC_UNREACHABLE as values)',
|
|
815
|
-
status: 'stable',
|
|
816
|
-
evidence: 'bun test packages/synapse/test/rpc-client.test.ts'
|
|
817
|
-
},
|
|
818
|
-
{
|
|
819
|
-
feature: 'Pre-built client bundles with a manifest (synapse build)',
|
|
820
|
-
status: 'stable',
|
|
821
|
-
evidence: 'bun test packages/synapse/test/client-bundler.test.ts'
|
|
822
|
-
},
|
|
823
|
-
{
|
|
824
|
-
feature: 'Uploads with session, size limit and traversal guard (/_synapse/files)',
|
|
825
|
-
status: 'stable',
|
|
826
|
-
evidence: 'bun test packages/synapse/test/uploads.test.ts'
|
|
827
|
-
},
|
|
828
|
-
{
|
|
829
|
-
feature: 'Embedded SQLite engine (WAL, prepared-statement cache)',
|
|
830
|
-
status: 'stable',
|
|
831
|
-
evidence: 'bun test packages/synapse/test/sqlite-client.test.ts'
|
|
832
|
-
},
|
|
833
|
-
{
|
|
834
|
-
feature: 'Zero-wiring routing, SSR shell and RPC dispatcher',
|
|
835
|
-
status: 'stable',
|
|
836
|
-
evidence: 'bun --cwd examples/enterprise-crm test:e2e'
|
|
837
|
-
},
|
|
838
|
-
{
|
|
839
|
-
feature: 'Slice discovery resolution (never reports PASS with zero slices)',
|
|
840
|
-
status: 'stable',
|
|
841
|
-
evidence: 'bun test packages/synapse/test/slice-discovery.test.ts'
|
|
842
|
-
},
|
|
843
|
-
{
|
|
844
|
-
feature: 'MCP stdio server (11 tools)',
|
|
845
|
-
status: 'stable',
|
|
846
|
-
evidence: 'bun test packages/synapse/test/mcp-server.test.ts'
|
|
847
|
-
},
|
|
848
|
-
{
|
|
849
|
-
feature: 'AST skeletonizer to .codebase/repo-map.d.ts',
|
|
850
|
-
status: 'stable',
|
|
851
|
-
evidence: 'bun --cwd examples/enterprise-crm skeleton'
|
|
852
|
-
},
|
|
853
|
-
{
|
|
854
|
-
feature: 'Centralized Database Schema Catalog (.codebase/db-schema.d.ts)',
|
|
855
|
-
status: 'stable',
|
|
856
|
-
evidence: 'bun test packages/synapse/test/db-schema-generator.test.ts'
|
|
857
|
-
},
|
|
858
|
-
{
|
|
859
|
-
feature: 'Unified Object Storage (LocalStorage + AWS S3/R2 with SigV4)',
|
|
860
|
-
status: 'stable',
|
|
861
|
-
evidence: 'bun test packages/synapse/test/storage.test.ts'
|
|
862
|
-
},
|
|
863
|
-
{
|
|
864
|
-
feature: 'Distributed PostgreSQL Queue (SKIP LOCKED, Full Jitter, DLQ)',
|
|
865
|
-
status: 'stable',
|
|
866
|
-
evidence: 'bun test packages/synapse/test/postgres-queue.test.ts'
|
|
867
|
-
},
|
|
868
|
-
{
|
|
869
|
-
feature: 'Isomorphic slice splitter (shared/server/client modules)',
|
|
870
|
-
status: 'stable',
|
|
871
|
-
evidence: 'bun test packages/synapse/test/slice-splitter.test.ts'
|
|
872
|
-
},
|
|
873
|
-
{
|
|
874
|
-
feature: 'Slice invariants executed by bun:test (per-invariant reporting)',
|
|
875
|
-
status: 'stable',
|
|
876
|
-
evidence: 'bun test packages/synapse/test/oracle-runner.test.ts'
|
|
877
|
-
},
|
|
878
|
-
{
|
|
879
|
-
feature: 'Incremental diagnostics cache across processes',
|
|
880
|
-
status: 'roadmap',
|
|
881
|
-
evidence: 'removido na 0.4.0: medido mais lento que o check completo (2.5s vs 1.9s)'
|
|
882
|
-
},
|
|
883
|
-
{
|
|
884
|
-
feature: 'PostgreSQL parity (migrations, DDL, action round-trip)',
|
|
885
|
-
status: 'stable',
|
|
886
|
-
evidence: 'bun run test:postgres (CI roda contra um serviço postgres:16)'
|
|
887
|
-
},
|
|
888
|
-
{
|
|
889
|
-
feature: 'Context surface benchmark (files/tokens an agent must read)',
|
|
890
|
-
status: 'stable',
|
|
891
|
-
evidence: 'bun run bench'
|
|
892
|
-
},
|
|
893
|
-
{
|
|
894
|
-
feature: 'Isolated SQLite Queue & Background Jobs (defineJob, exponential backoff)',
|
|
895
|
-
status: 'stable',
|
|
896
|
-
evidence: 'bun test packages/synapse/test/jobs-queue.test.ts'
|
|
897
|
-
},
|
|
898
|
-
{
|
|
899
|
-
feature: 'Bidirectional DDL migrations with transactional rollback (synapse rollback)',
|
|
900
|
-
status: 'stable',
|
|
901
|
-
evidence: 'bun test packages/synapse/test/onda2.test.ts'
|
|
902
|
-
},
|
|
903
|
-
{
|
|
904
|
-
feature: 'Vendor code-splitting and dynamic metadata (sliceMeta)',
|
|
905
|
-
status: 'stable',
|
|
906
|
-
evidence: 'bun test packages/synapse/test/onda1.test.ts'
|
|
907
|
-
},
|
|
908
|
-
{
|
|
909
|
-
feature: 'Hierarchical domain layouts (_layout.tsx)',
|
|
910
|
-
status: 'stable',
|
|
911
|
-
evidence: 'bun test packages/synapse/test/onda1.test.ts'
|
|
912
|
-
},
|
|
913
|
-
{
|
|
914
|
-
feature: 'Sliding-window rate limiting & streaming upload guard',
|
|
915
|
-
status: 'stable',
|
|
916
|
-
evidence: 'bun test packages/synapse/test/onda2.test.ts'
|
|
917
|
-
},
|
|
918
|
-
{
|
|
919
|
-
feature: 'Distributed PostgreSQL Event Hub & Isomorphic i18n',
|
|
920
|
-
status: 'stable',
|
|
921
|
-
evidence: 'bun test packages/synapse/test/onda3.test.ts'
|
|
922
|
-
},
|
|
923
|
-
{
|
|
924
|
-
feature: 'Token revocation, TOTP 2FA, image optimization & plugin hooks',
|
|
925
|
-
status: 'stable',
|
|
926
|
-
evidence: 'bun test packages/synapse/test/onda4.test.ts'
|
|
927
|
-
},
|
|
928
|
-
{
|
|
929
|
-
feature: 'SSRF & Private Network Guard',
|
|
930
|
-
status: 'stable',
|
|
931
|
-
evidence: 'bun test packages/synapse/test/production-hardening.test.ts'
|
|
932
|
-
},
|
|
933
|
-
{
|
|
934
|
-
feature: 'Bounded LRU SSR Cache & Query Normalization',
|
|
935
|
-
status: 'stable',
|
|
936
|
-
evidence: 'bun test packages/synapse/test/production-hardening.test.ts'
|
|
937
|
-
},
|
|
938
|
-
{
|
|
939
|
-
feature: 'SQLite Queue Zombie Job Visibility Recovery',
|
|
940
|
-
status: 'stable',
|
|
941
|
-
evidence: 'bun test packages/synapse/test/production-hardening.test.ts'
|
|
942
|
-
},
|
|
943
|
-
{
|
|
944
|
-
feature: 'HttpOnly Cookie Authentication via ActionContext',
|
|
945
|
-
status: 'stable',
|
|
946
|
-
evidence: 'bun test packages/synapse/test/production-hardening.test.ts'
|
|
947
|
-
}
|
|
948
|
-
],
|
|
949
|
-
commands: [
|
|
950
|
-
'new',
|
|
951
|
-
'dev',
|
|
952
|
-
'start',
|
|
953
|
-
'check',
|
|
954
|
-
'migrate',
|
|
955
|
-
'rollback',
|
|
956
|
-
'db-drift',
|
|
957
|
-
'impact',
|
|
958
|
-
'mcp',
|
|
959
|
-
'skeleton',
|
|
960
|
-
'db-schema',
|
|
961
|
-
'split',
|
|
962
|
-
'build',
|
|
963
|
-
'test',
|
|
964
|
-
'worker',
|
|
965
|
-
'new-slice',
|
|
966
|
-
'contract',
|
|
967
|
-
'info'
|
|
968
|
-
]
|
|
950
|
+
status: 'FAIL',
|
|
951
|
+
code: 'UNKNOWN_COMMAND',
|
|
952
|
+
operation: 'COMMAND_VALIDATION',
|
|
953
|
+
message: `Comando desconhecido: ${command}.`,
|
|
954
|
+
commands: Object.keys(KNOWN_FLAGS)
|
|
969
955
|
},
|
|
970
956
|
null,
|
|
971
957
|
2
|
|
972
958
|
)}\n`
|
|
973
959
|
);
|
|
974
|
-
process.exit(0);
|
|
975
|
-
break;
|
|
976
|
-
}
|
|
977
|
-
|
|
978
|
-
default: {
|
|
979
|
-
process.stderr.write(
|
|
980
|
-
`Unknown command: ${command}\nAvailable: start, dev, check, migrate, rollback, mcp, skeleton, db-schema, db-drift, impact, split, build, test, worker, new, new-slice, contract, info\n`
|
|
981
|
-
);
|
|
982
960
|
process.exit(1);
|
|
983
961
|
}
|
|
984
962
|
}
|
package/package.json
CHANGED
|
@@ -174,7 +174,7 @@ export function measureAppSurface(appRoot: string): AppSurface {
|
|
|
174
174
|
|
|
175
175
|
export function renderSurfaceReport(surfaces: AppSurface[]): string {
|
|
176
176
|
const lines = [
|
|
177
|
-
'| app | feature | arquivos (app) | tokens (app, est.) | arquivos (total) | tokens (total, est
|
|
177
|
+
'| app | feature | arquivos (app) | tokens (app, est.) | arquivos (total, não comparável) | tokens (total, est., não comparável) |',
|
|
178
178
|
'|---|---|---|---|---|---|'
|
|
179
179
|
];
|
|
180
180
|
|
|
@@ -187,5 +187,11 @@ export function renderSurfaceReport(surfaces: AppSurface[]): string {
|
|
|
187
187
|
}
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
lines.push(
|
|
191
|
+
'',
|
|
192
|
+
'As colunas `app` são as comparáveis (os arquivos da própria funcionalidade em cada aplicação).',
|
|
193
|
+
'As colunas `total` cobrem conjuntos de arquivos diferentes por repositório e não são comparáveis'
|
|
194
|
+
);
|
|
195
|
+
|
|
190
196
|
return lines.join('\n');
|
|
191
197
|
}
|