@ismaelsoilet/synapsejs 1.1.1 → 1.2.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 +5 -1
- package/package.json +2 -1
- package/src/compiler/migration-runner.ts +18 -1
- package/src/compiler/oracle-runner.ts +38 -4
- package/src/mcp/server.ts +95 -1
- package/src/runtime/server.ts +1 -1
- package/templates/starter/.jev.json +6 -0
- package/templates/starter/package.json +1 -0
package/bin/synapse.ts
CHANGED
|
@@ -534,7 +534,11 @@ async function main() {
|
|
|
534
534
|
}
|
|
535
535
|
|
|
536
536
|
case 'test': {
|
|
537
|
-
const
|
|
537
|
+
const enableGate =
|
|
538
|
+
process.argv.includes('--gate') ||
|
|
539
|
+
fs.existsSync(path.join(root, '.jev.json')) ||
|
|
540
|
+
Boolean(process.env.JEV_ACTIVE || process.env.JEV_PROVIDER || process.env.TYPESAFE_API_KEY);
|
|
541
|
+
const report = await runSliceOracles(root, { enableJevTriage: enableGate });
|
|
538
542
|
|
|
539
543
|
process.stdout.write(`${JSON.stringify({ operation: 'PBT_ORACLE_TEST_SUITE', ...report }, null, 2)}\n`);
|
|
540
544
|
process.exit(report.status === 'PASS' ? 0 : 1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ismaelsoilet/synapsejs",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.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",
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
"access": "public"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
+
"@ismaelsoilet/jev-harness": "^0.2.1",
|
|
53
54
|
"@sinclair/typebox": "^0.34.40",
|
|
54
55
|
"fast-check": "^3.23.2",
|
|
55
56
|
"postgres": "^3.4.9",
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* Preserves Locality of Behavior (LoB): each slice owns its own table contracts!
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import { type AbortGateResult, shouldAbortTrajectory } from '@ismaelsoilet/jev-harness';
|
|
9
10
|
import * as fs from 'fs';
|
|
10
11
|
import * as path from 'path';
|
|
11
12
|
import * as ts from 'typescript';
|
|
@@ -34,6 +35,7 @@ export interface MigrationReport {
|
|
|
34
35
|
code?: SlicesDirErrorCode;
|
|
35
36
|
message?: string;
|
|
36
37
|
candidates?: string[];
|
|
38
|
+
abortSignal?: AbortGateResult;
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
export interface BidirectionalDdl {
|
|
@@ -404,13 +406,28 @@ export async function runSliceMigrations(
|
|
|
404
406
|
}
|
|
405
407
|
}
|
|
406
408
|
|
|
409
|
+
let abortSignal: AbortGateResult | undefined;
|
|
410
|
+
if (hasFailure) {
|
|
411
|
+
try {
|
|
412
|
+
const failureDetails = migrations
|
|
413
|
+
.filter((m) => m.status === 'FAILED')
|
|
414
|
+
.map((m) => `Slice ${m.slice}: ${m.error}`)
|
|
415
|
+
.join('\n');
|
|
416
|
+
abortSignal = await shouldAbortTrajectory(
|
|
417
|
+
`Apply sliceSchema migrations across discovered slices in ${baseDir}`,
|
|
418
|
+
failureDetails
|
|
419
|
+
);
|
|
420
|
+
} catch {}
|
|
421
|
+
}
|
|
422
|
+
|
|
407
423
|
return {
|
|
408
424
|
status: hasFailure ? 'FAIL' : 'PASS',
|
|
409
425
|
totalDiscovered: migrations.length,
|
|
410
426
|
appliedCount,
|
|
411
427
|
skippedCount,
|
|
412
428
|
statementsApplied: statementsAppliedTotal,
|
|
413
|
-
migrations
|
|
429
|
+
migrations,
|
|
430
|
+
abortSignal
|
|
414
431
|
};
|
|
415
432
|
} finally {
|
|
416
433
|
if (isPostgres) {
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* Nothing here may report PASS when no invariant was executed.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
import { type TestTriageResult, triageTestFailure } from '@ismaelsoilet/jev-harness';
|
|
12
13
|
import * as fs from 'fs';
|
|
13
14
|
import * as path from 'path';
|
|
14
15
|
import { findSliceFiles, resolveSlicesDir, type SlicesDirErrorCode } from './slice-discovery';
|
|
@@ -42,6 +43,7 @@ export interface OracleReport {
|
|
|
42
43
|
code?: OracleErrorCode;
|
|
43
44
|
message?: string;
|
|
44
45
|
candidates?: string[];
|
|
46
|
+
triage?: TestTriageResult;
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
export interface JunitSuite {
|
|
@@ -154,6 +156,8 @@ export function oracleWrapperSource(importPath: string, sliceName: string): stri
|
|
|
154
156
|
export interface OracleRunOptions {
|
|
155
157
|
/** Injected by tests: runs a command and returns its exit code and output. */
|
|
156
158
|
spawn?: (args: string[], cwd: string) => Promise<{ exitCode: number; output: string }>;
|
|
159
|
+
/** Enable Jev System One semantic test triage on failures. Default: true */
|
|
160
|
+
enableJevTriage?: boolean;
|
|
157
161
|
}
|
|
158
162
|
|
|
159
163
|
async function defaultSpawn(args: string[], cwd: string): Promise<{ exitCode: number; output: string }> {
|
|
@@ -241,6 +245,12 @@ export async function runSliceOracles(
|
|
|
241
245
|
);
|
|
242
246
|
|
|
243
247
|
if (!fs.existsSync(reportPath)) {
|
|
248
|
+
let triageResult: TestTriageResult | undefined;
|
|
249
|
+
if (options.enableJevTriage !== false) {
|
|
250
|
+
try {
|
|
251
|
+
triageResult = await triageTestFailure(output.trim() || `O runner Bun test falhou com exit ${exitCode}`);
|
|
252
|
+
} catch {}
|
|
253
|
+
}
|
|
244
254
|
return {
|
|
245
255
|
status: 'FAIL',
|
|
246
256
|
totalSlices: wrappers.size,
|
|
@@ -249,7 +259,8 @@ export async function runSliceOracles(
|
|
|
249
259
|
passedCases: 0,
|
|
250
260
|
results: [],
|
|
251
261
|
code: 'RUNNER_FAILED',
|
|
252
|
-
message: `O runner não produziu relatório (exit ${exitCode}). ${output.trim().slice(-400)}`.trim()
|
|
262
|
+
message: `O runner não produziu relatório (exit ${exitCode}). ${output.trim().slice(-400)}`.trim(),
|
|
263
|
+
triage: triageResult
|
|
253
264
|
};
|
|
254
265
|
}
|
|
255
266
|
|
|
@@ -268,6 +279,12 @@ export async function runSliceOracles(
|
|
|
268
279
|
const passedSlices = results.filter((item) => item.passed).length;
|
|
269
280
|
|
|
270
281
|
if (totalCases === 0) {
|
|
282
|
+
let triageResult: TestTriageResult | undefined;
|
|
283
|
+
if (options.enableJevTriage !== false) {
|
|
284
|
+
try {
|
|
285
|
+
triageResult = await triageTestFailure(`Nenhum invariante foi executado em ${results.length} fatia(s).`);
|
|
286
|
+
} catch {}
|
|
287
|
+
}
|
|
271
288
|
return {
|
|
272
289
|
status: 'FAIL',
|
|
273
290
|
slicesDir: path.relative(root, slicesDir),
|
|
@@ -277,17 +294,34 @@ export async function runSliceOracles(
|
|
|
277
294
|
passedCases: 0,
|
|
278
295
|
results,
|
|
279
296
|
code: 'NO_CASES',
|
|
280
|
-
message: `Nenhum invariante foi executado em ${results.length} fatia(s)
|
|
297
|
+
message: `Nenhum invariante foi executado em ${results.length} fatia(s).`,
|
|
298
|
+
triage: triageResult
|
|
281
299
|
};
|
|
282
300
|
}
|
|
283
301
|
|
|
302
|
+
const isPass = passedSlices === results.length;
|
|
303
|
+
let triageResult: TestTriageResult | undefined;
|
|
304
|
+
if (!isPass && options.enableJevTriage !== false) {
|
|
305
|
+
try {
|
|
306
|
+
const failedSummaries = results
|
|
307
|
+
.filter((r) => !r.passed)
|
|
308
|
+
.flatMap((r) =>
|
|
309
|
+
r.cases.filter((c) => !c.passed).map((c) => `[${r.slice}] ${c.name}: ${c.message || 'assertion failed'}`)
|
|
310
|
+
)
|
|
311
|
+
.join('\n');
|
|
312
|
+
const failureTrace = failedSummaries || output.trim() || 'Slice oracle invariants failed';
|
|
313
|
+
triageResult = await triageTestFailure(failureTrace);
|
|
314
|
+
} catch {}
|
|
315
|
+
}
|
|
316
|
+
|
|
284
317
|
return {
|
|
285
|
-
status:
|
|
318
|
+
status: isPass ? 'PASS' : 'FAIL',
|
|
286
319
|
slicesDir: path.relative(root, slicesDir),
|
|
287
320
|
totalSlices: results.length,
|
|
288
321
|
passedSlices,
|
|
289
322
|
totalCases,
|
|
290
323
|
passedCases,
|
|
291
|
-
results
|
|
324
|
+
results,
|
|
325
|
+
triage: triageResult
|
|
292
326
|
};
|
|
293
327
|
}
|
package/src/mcp/server.ts
CHANGED
|
@@ -6,6 +6,16 @@
|
|
|
6
6
|
* directly to AI Agents (Cursor, Claude Code, Windsurf, Antigravity) via JSON-RPC 2.0 over stdio.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import {
|
|
10
|
+
type AbortGateResult,
|
|
11
|
+
modulateReasoningEffort,
|
|
12
|
+
type ReasoningEffortResult,
|
|
13
|
+
shouldAbortTrajectory,
|
|
14
|
+
type TestTriageResult,
|
|
15
|
+
triageTestFailure,
|
|
16
|
+
type VerificationResult,
|
|
17
|
+
verifyStepCompletion
|
|
18
|
+
} from '@ismaelsoilet/jev-harness';
|
|
9
19
|
import * as fs from 'fs';
|
|
10
20
|
import * as path from 'path';
|
|
11
21
|
import * as readline from 'readline';
|
|
@@ -80,7 +90,7 @@ export class SynapseMcpServer {
|
|
|
80
90
|
},
|
|
81
91
|
serverInfo: {
|
|
82
92
|
name: 'synapse-mcp',
|
|
83
|
-
version: '1.
|
|
93
|
+
version: '1.2.0'
|
|
84
94
|
}
|
|
85
95
|
}
|
|
86
96
|
};
|
|
@@ -201,6 +211,67 @@ export class SynapseMcpServer {
|
|
|
201
211
|
steps: { type: 'number', description: 'Number of recent migrations to rollback (default: 1)' }
|
|
202
212
|
}
|
|
203
213
|
}
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
name: 'synapse_test_gate',
|
|
217
|
+
description:
|
|
218
|
+
'Execute Fast-Check PBT oracles and immediately triage failures with Jev System One semantic test-gate to prevent LLM token waste',
|
|
219
|
+
inputSchema: {
|
|
220
|
+
type: 'object',
|
|
221
|
+
properties: {}
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
name: 'synapse_abort_check',
|
|
226
|
+
description:
|
|
227
|
+
'Evaluate plan and error history with Jev System One to detect doomed trajectories, circular loops, or high-risk refactor dead ends',
|
|
228
|
+
inputSchema: {
|
|
229
|
+
type: 'object',
|
|
230
|
+
required: ['plan', 'history'],
|
|
231
|
+
properties: {
|
|
232
|
+
plan: { type: 'string', description: 'Proposed implementation plan or architectural change' },
|
|
233
|
+
history: { type: 'string', description: 'Recent attempt history, error logs, or failure reasons' }
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
name: 'synapse_verify_completion',
|
|
239
|
+
description:
|
|
240
|
+
'Verify slice implementation and test outputs against acceptance criteria using Jev System One before committing work',
|
|
241
|
+
inputSchema: {
|
|
242
|
+
type: 'object',
|
|
243
|
+
required: ['criteria', 'output'],
|
|
244
|
+
properties: {
|
|
245
|
+
criteria: { type: 'string', description: 'Acceptance criteria or requirement specifications' },
|
|
246
|
+
output: {
|
|
247
|
+
type: 'string',
|
|
248
|
+
description: 'Actual implementation summary, test output, or slice behavior'
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
name: 'synapse_reasoning_effort',
|
|
255
|
+
description:
|
|
256
|
+
'Dynamically modulate agent reasoning effort (Astra-Jev) to low/medium/high based on step context (lowering effort for mechanical commands like split/migrate/skeleton)',
|
|
257
|
+
inputSchema: {
|
|
258
|
+
type: 'object',
|
|
259
|
+
required: ['context'],
|
|
260
|
+
properties: {
|
|
261
|
+
context: {
|
|
262
|
+
type: 'string',
|
|
263
|
+
description: 'Immediate command or task context, e.g. "synapse split", "git commit"'
|
|
264
|
+
},
|
|
265
|
+
provider: {
|
|
266
|
+
type: 'string',
|
|
267
|
+
description: 'Target LLM provider, e.g. "deepseek", "anthropic", "openai"'
|
|
268
|
+
},
|
|
269
|
+
sessionContextTokens: {
|
|
270
|
+
type: 'number',
|
|
271
|
+
description: 'Estimated session context token count to protect prompt cache'
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
204
275
|
}
|
|
205
276
|
]
|
|
206
277
|
}
|
|
@@ -503,6 +574,29 @@ export class SynapseMcpServer {
|
|
|
503
574
|
steps: args.steps
|
|
504
575
|
});
|
|
505
576
|
contentText = JSON.stringify(report, null, 2);
|
|
577
|
+
} else if (toolName === 'synapse_test_gate') {
|
|
578
|
+
const report = await runSliceOracles(this.root, { enableJevTriage: true });
|
|
579
|
+
contentText = JSON.stringify(report, null, 2);
|
|
580
|
+
} else if (toolName === 'synapse_abort_check') {
|
|
581
|
+
const plan = String(args.plan || '');
|
|
582
|
+
const history = String(args.history || '');
|
|
583
|
+
const result: AbortGateResult = await shouldAbortTrajectory(plan, history);
|
|
584
|
+
contentText = JSON.stringify(result, null, 2);
|
|
585
|
+
} else if (toolName === 'synapse_verify_completion') {
|
|
586
|
+
const criteria = String(args.criteria || '');
|
|
587
|
+
const output = String(args.output || '');
|
|
588
|
+
const result: VerificationResult = await verifyStepCompletion(criteria, output);
|
|
589
|
+
contentText = JSON.stringify(result, null, 2);
|
|
590
|
+
} else if (toolName === 'synapse_reasoning_effort') {
|
|
591
|
+
const context = String(args.context || '');
|
|
592
|
+
const provider = args.provider ? String(args.provider) : undefined;
|
|
593
|
+
const sessionContextTokens =
|
|
594
|
+
typeof args.sessionContextTokens === 'number' ? args.sessionContextTokens : undefined;
|
|
595
|
+
const result: ReasoningEffortResult = await modulateReasoningEffort(context, {
|
|
596
|
+
provider,
|
|
597
|
+
sessionContextTokens
|
|
598
|
+
});
|
|
599
|
+
contentText = JSON.stringify(result, null, 2);
|
|
506
600
|
} else {
|
|
507
601
|
return {
|
|
508
602
|
jsonrpc: '2.0',
|
package/src/runtime/server.ts
CHANGED
|
@@ -993,7 +993,7 @@ ${this.renderStylesheets()}
|
|
|
993
993
|
{
|
|
994
994
|
status,
|
|
995
995
|
framework: 'SynapseJS',
|
|
996
|
-
version: '1.
|
|
996
|
+
version: '1.2.0',
|
|
997
997
|
uptime: process.uptime(),
|
|
998
998
|
database: dbHealthy ? 'connected' : 'disconnected',
|
|
999
999
|
...(dbError ? { databaseError: dbError } : {}),
|