@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/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 isProduction = process.env.NODE_ENV === 'production';
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 (isProduction && !process.env.SYNAPSE_SESSION_SECRET) {
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] NODE_ENV=production ativo sem SYNAPSE_SESSION_SECRET configurado!\n' +
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
- console.log('✅ [SynapseJS] Servidor e conexões de persistência encerrados com sucesso.');
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
- console.error('❌ [SynapseJS] Erro durante encerramento gracioso:', err);
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
- process.exit(0);
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.stderr.write(
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.stderr.write(
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.stderr.write(
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.stderr.write(
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.stderr.write(
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.stderr.write(
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.stderr.write(
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
- '"@ismaelsoilet/synapsejs": "^1.1.1"'
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
- console.log('\n🛑 [SynapseJS Worker] Encerrando worker...');
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
- framework: 'SynapseJS',
757
- version: '1.6.0',
758
- runtime: 'Bun + Bun.serve',
759
- database: 'Embedded SQLite (WAL) and PostgreSQL, both verified',
760
- protocols: ['REST/HTTP', 'Isomorphic RPC', 'Model Context Protocol (MCP)'],
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ismaelsoilet/synapsejs",
3
- "version": "1.8.0",
3
+ "version": "1.9.0",
4
4
  "description": "Bun-only fullstack framework where a feature is a single contiguous file",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -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
  }