sinapse-ai 1.4.2 → 1.5.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/.sinapse-ai/core/external-executors/delegate-cli.js +585 -0
- package/.sinapse-ai/core/external-executors/index.js +13 -0
- package/.sinapse-ai/core/orchestration/fast-path-gate.js +356 -0
- package/.sinapse-ai/core/orchestration/index.js +23 -0
- package/.sinapse-ai/data/entity-registry.yaml +104 -23
- package/.sinapse-ai/data/registry-update-log.jsonl +8 -0
- package/.sinapse-ai/install-manifest.yaml +18 -6
- package/bin/sinapse-delegate.js +30 -0
- package/package.json +3 -2
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const DEFAULT_FAST_PATH_CONFIG = Object.freeze({
|
|
4
|
+
enabled: true,
|
|
5
|
+
externalExecutorsEnabled: false,
|
|
6
|
+
minConfidence: 0.58,
|
|
7
|
+
minBatchItems: 3,
|
|
8
|
+
externalExecutorThreshold: 0.78,
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const STRUCTURED_FILE_EXTENSIONS_FROZEN = Object.freeze([
|
|
12
|
+
'.csv',
|
|
13
|
+
'.json',
|
|
14
|
+
'.jsonl',
|
|
15
|
+
'.md',
|
|
16
|
+
'.toml',
|
|
17
|
+
'.tsv',
|
|
18
|
+
'.txt',
|
|
19
|
+
'.yaml',
|
|
20
|
+
'.yml',
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
const STRUCTURED_FILE_EXTENSION_SET = new Set(STRUCTURED_FILE_EXTENSIONS_FROZEN);
|
|
24
|
+
|
|
25
|
+
const AUTOMATION_PATTERNS_FROZEN = Object.freeze([
|
|
26
|
+
{
|
|
27
|
+
id: 'bulk-edit',
|
|
28
|
+
weight: 3,
|
|
29
|
+
pattern: /\b(batch|bulk|many files|multiple files|all files|in one shot|one shot)\b/i,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: 'structured-transform',
|
|
33
|
+
weight: 3,
|
|
34
|
+
pattern: /\b(yaml|json|csv|markdown|frontmatter|schema|variable|variables|field|fields)\b/i,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id: 'mechanical-edit',
|
|
38
|
+
weight: 3,
|
|
39
|
+
pattern: /\b(replace|rename|populate|fill|complete|update|convert|transform|normalize|format)\b/i,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: 'map-then-apply',
|
|
43
|
+
weight: 2,
|
|
44
|
+
pattern: /\b(map|extract|derive|template|codemod|script)\b/i,
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: 'repetition',
|
|
48
|
+
weight: 2,
|
|
49
|
+
pattern: /\b(repeated|repetitive|same change|similar change|dumb task|tedious)\b/i,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: 'parallelizable',
|
|
53
|
+
weight: 2,
|
|
54
|
+
pattern: /\b(parallel|independent|per file|per item|per record)\b/i,
|
|
55
|
+
},
|
|
56
|
+
].map(Object.freeze));
|
|
57
|
+
|
|
58
|
+
const RISK_PATTERNS_FROZEN = Object.freeze([
|
|
59
|
+
{
|
|
60
|
+
id: 'architecture',
|
|
61
|
+
weight: 3,
|
|
62
|
+
pattern: /\b(architecture|architectural|design decision|adr|contract)\b/i,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: 'security',
|
|
66
|
+
weight: 3,
|
|
67
|
+
pattern: /\b(security|secret|token|credential|auth|permission|pii|rls)\b/i,
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: 'destructive',
|
|
71
|
+
weight: 3,
|
|
72
|
+
pattern: /\b(delete|remove data|drop|reset|rewrite history|destructive)\b/i,
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: 'production',
|
|
76
|
+
weight: 2,
|
|
77
|
+
pattern: /\b(production|prod|release|billing|payment|customer)\b/i,
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: 'migration',
|
|
81
|
+
weight: 2,
|
|
82
|
+
pattern: /\b(migration|migrate|schema change|breaking change)\b/i,
|
|
83
|
+
},
|
|
84
|
+
].map(Object.freeze));
|
|
85
|
+
|
|
86
|
+
function clonePatternDefinition(patternDefinition) {
|
|
87
|
+
return {
|
|
88
|
+
id: patternDefinition.id,
|
|
89
|
+
weight: patternDefinition.weight,
|
|
90
|
+
pattern: new RegExp(patternDefinition.pattern.source, patternDefinition.pattern.flags),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function getStructuredFileExtensions() {
|
|
95
|
+
return new Set(STRUCTURED_FILE_EXTENSIONS_FROZEN);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function getAutomationPatterns() {
|
|
99
|
+
return AUTOMATION_PATTERNS_FROZEN.map(clonePatternDefinition);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function getRiskPatterns() {
|
|
103
|
+
return RISK_PATTERNS_FROZEN.map(clonePatternDefinition);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function parseBoolean(value, fallback) {
|
|
107
|
+
if (typeof value === 'boolean') {
|
|
108
|
+
return value;
|
|
109
|
+
}
|
|
110
|
+
if (typeof value === 'string') {
|
|
111
|
+
const normalized = value.trim().toLowerCase();
|
|
112
|
+
if (normalized === 'true') {
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
if (normalized === 'false') {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return fallback;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function normalizeConfig(config = {}) {
|
|
123
|
+
const clamp01 = (value, fallback) => {
|
|
124
|
+
const numericValue = Number(value);
|
|
125
|
+
if (!Number.isFinite(numericValue)) {
|
|
126
|
+
return fallback;
|
|
127
|
+
}
|
|
128
|
+
return Math.min(1, Math.max(0, numericValue));
|
|
129
|
+
};
|
|
130
|
+
const positiveInteger = (value, fallback) => {
|
|
131
|
+
const numericValue = Number(value);
|
|
132
|
+
if (!Number.isFinite(numericValue)) {
|
|
133
|
+
return fallback;
|
|
134
|
+
}
|
|
135
|
+
return Math.max(1, Math.floor(numericValue));
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
enabled: parseBoolean(config.enabled, DEFAULT_FAST_PATH_CONFIG.enabled),
|
|
140
|
+
externalExecutorsEnabled: parseBoolean(
|
|
141
|
+
config.externalExecutorsEnabled ?? config.external_executors_enabled,
|
|
142
|
+
DEFAULT_FAST_PATH_CONFIG.externalExecutorsEnabled,
|
|
143
|
+
),
|
|
144
|
+
minConfidence: clamp01(
|
|
145
|
+
config.minConfidence ?? config.min_confidence,
|
|
146
|
+
DEFAULT_FAST_PATH_CONFIG.minConfidence,
|
|
147
|
+
),
|
|
148
|
+
minBatchItems: positiveInteger(
|
|
149
|
+
config.minBatchItems ?? config.min_batch_items,
|
|
150
|
+
DEFAULT_FAST_PATH_CONFIG.minBatchItems,
|
|
151
|
+
),
|
|
152
|
+
externalExecutorThreshold: clamp01(
|
|
153
|
+
config.externalExecutorThreshold ?? config.external_executor_threshold,
|
|
154
|
+
DEFAULT_FAST_PATH_CONFIG.externalExecutorThreshold,
|
|
155
|
+
),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function normalizeTask(input = {}) {
|
|
160
|
+
const task = input.task || input;
|
|
161
|
+
return {
|
|
162
|
+
description: String(task.description || task.summary || task.title || ''),
|
|
163
|
+
files: Array.isArray(task.files) ? task.files : [],
|
|
164
|
+
acceptanceCriteria: Array.isArray(task.acceptanceCriteria)
|
|
165
|
+
? task.acceptanceCriteria
|
|
166
|
+
: Array.isArray(task.acceptance_criteria)
|
|
167
|
+
? task.acceptance_criteria
|
|
168
|
+
: [],
|
|
169
|
+
itemCount: Number.isFinite(task.itemCount)
|
|
170
|
+
? task.itemCount
|
|
171
|
+
: Number.isFinite(task.item_count)
|
|
172
|
+
? task.item_count
|
|
173
|
+
: null,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function normalizePathExtension(filePath) {
|
|
178
|
+
const match = String(filePath || '').toLowerCase().match(/(\.[a-z0-9]+)$/);
|
|
179
|
+
return match ? match[1] : '';
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function collectSignals(patterns, text) {
|
|
183
|
+
return patterns
|
|
184
|
+
.filter(({ pattern }) => pattern.test(text))
|
|
185
|
+
.map(({ id, weight }) => ({ id, weight }));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function getTaskText(task) {
|
|
189
|
+
return [
|
|
190
|
+
task.description,
|
|
191
|
+
...task.acceptanceCriteria.map((criterion) => String(criterion || '')),
|
|
192
|
+
...task.files.map((file) => String(file || '')),
|
|
193
|
+
].join('\n');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function scoreFastPath({ automationSignals, riskSignals, files, structuredFileCount, batchSize }) {
|
|
197
|
+
const automationWeight = automationSignals.reduce((sum, signal) => sum + signal.weight, 0);
|
|
198
|
+
const riskWeight = riskSignals.reduce((sum, signal) => sum + signal.weight, 0);
|
|
199
|
+
const fileWeight = Math.min(files.length, 8) * 0.45;
|
|
200
|
+
const structuredWeight = Math.min(structuredFileCount, 6) * 0.55;
|
|
201
|
+
const batchWeight = Math.min(batchSize, 10) * 0.35;
|
|
202
|
+
|
|
203
|
+
const rawScore = automationWeight + fileWeight + structuredWeight + batchWeight - riskWeight * 1.35;
|
|
204
|
+
return Math.max(0, Math.min(1, rawScore / 13));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function chooseMode({
|
|
208
|
+
confidence,
|
|
209
|
+
config,
|
|
210
|
+
externalExecutorsEnabled,
|
|
211
|
+
parallelizable,
|
|
212
|
+
structuredFileCount,
|
|
213
|
+
batchSize,
|
|
214
|
+
}) {
|
|
215
|
+
if (externalExecutorsEnabled && confidence >= config.externalExecutorThreshold) {
|
|
216
|
+
return 'external_executor';
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (parallelizable && batchSize >= config.minBatchItems) {
|
|
220
|
+
return 'parallel_batch';
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (structuredFileCount > 0 || batchSize >= config.minBatchItems) {
|
|
224
|
+
return 'deterministic_batch';
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return 'standard';
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function buildActions(mode) {
|
|
231
|
+
if (mode === 'external_executor') {
|
|
232
|
+
return [
|
|
233
|
+
'Prepare a bounded prompt with target files, schema, acceptance criteria, and validation commands.',
|
|
234
|
+
'Run a dry-run plan first, then delegate with the configured external executor sandbox.',
|
|
235
|
+
'Review the executor diff before mutating story or issue state.',
|
|
236
|
+
];
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (mode === 'parallel_batch') {
|
|
240
|
+
return [
|
|
241
|
+
'Map target files or records once before editing.',
|
|
242
|
+
'Group independent changes by file or record and apply them as a batch.',
|
|
243
|
+
'Run targeted validation on the changed surface before broader checks.',
|
|
244
|
+
];
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (mode === 'deterministic_batch') {
|
|
248
|
+
return [
|
|
249
|
+
'Extract the data shape and replacement rules before editing.',
|
|
250
|
+
'Use a deterministic transform or structured parser instead of conversational one-by-one edits.',
|
|
251
|
+
'Validate output syntax and diff size before continuing.',
|
|
252
|
+
];
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return [
|
|
256
|
+
'Use the standard story/task workflow.',
|
|
257
|
+
'Keep changes sequential when risk or ambiguity is higher than the automation signal.',
|
|
258
|
+
];
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function evaluateFastPath(input = {}) {
|
|
262
|
+
const config = normalizeConfig(input.config || input.fastPath || {});
|
|
263
|
+
const task = normalizeTask(input);
|
|
264
|
+
const text = getTaskText(task);
|
|
265
|
+
const automationSignals = collectSignals(AUTOMATION_PATTERNS_FROZEN, text);
|
|
266
|
+
const riskSignals = collectSignals(RISK_PATTERNS_FROZEN, text);
|
|
267
|
+
const structuredFileCount = task.files.filter((file) => (
|
|
268
|
+
STRUCTURED_FILE_EXTENSION_SET.has(normalizePathExtension(file))
|
|
269
|
+
)).length;
|
|
270
|
+
const batchSize = task.itemCount ?? Math.max(task.files.length, structuredFileCount);
|
|
271
|
+
const parallelizable = (
|
|
272
|
+
task.files.length >= config.minBatchItems ||
|
|
273
|
+
automationSignals.some((signal) => signal.id === 'parallelizable')
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
if (!config.enabled) {
|
|
277
|
+
return {
|
|
278
|
+
gate: 'fast_path',
|
|
279
|
+
enabled: false,
|
|
280
|
+
passed: false,
|
|
281
|
+
mode: 'standard',
|
|
282
|
+
confidence: 0,
|
|
283
|
+
parallelizable: false,
|
|
284
|
+
riskLevel: 'unknown',
|
|
285
|
+
reasons: ['fast path gate disabled by configuration'],
|
|
286
|
+
evidence: { automationSignals: [], riskSignals: [], fileCount: task.files.length, structuredFileCount, batchSize },
|
|
287
|
+
actions: buildActions('standard'),
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const confidence = scoreFastPath({
|
|
292
|
+
automationSignals,
|
|
293
|
+
riskSignals,
|
|
294
|
+
files: task.files,
|
|
295
|
+
structuredFileCount,
|
|
296
|
+
batchSize,
|
|
297
|
+
});
|
|
298
|
+
const passed = confidence >= config.minConfidence && riskSignals.length === 0;
|
|
299
|
+
const mode = passed
|
|
300
|
+
? chooseMode({
|
|
301
|
+
confidence,
|
|
302
|
+
config,
|
|
303
|
+
externalExecutorsEnabled: parseBoolean(
|
|
304
|
+
input.externalExecutorsEnabled ?? input.external_executors_enabled,
|
|
305
|
+
config.externalExecutorsEnabled,
|
|
306
|
+
),
|
|
307
|
+
parallelizable,
|
|
308
|
+
structuredFileCount,
|
|
309
|
+
batchSize,
|
|
310
|
+
})
|
|
311
|
+
: 'standard';
|
|
312
|
+
const riskLevel = riskSignals.length >= 2 ? 'high' : riskSignals.length === 1 ? 'medium' : 'low';
|
|
313
|
+
const reasons = [
|
|
314
|
+
...automationSignals.map((signal) => `automation signal: ${signal.id}`),
|
|
315
|
+
...riskSignals.map((signal) => `risk signal: ${signal.id}`),
|
|
316
|
+
];
|
|
317
|
+
|
|
318
|
+
if (structuredFileCount > 0) {
|
|
319
|
+
reasons.push(`structured files detected: ${structuredFileCount}`);
|
|
320
|
+
}
|
|
321
|
+
if (batchSize >= config.minBatchItems) {
|
|
322
|
+
reasons.push(`batch size meets threshold: ${batchSize}`);
|
|
323
|
+
}
|
|
324
|
+
if (!passed && reasons.length === 0) {
|
|
325
|
+
reasons.push('insufficient automation signal for fast path');
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return {
|
|
329
|
+
gate: 'fast_path',
|
|
330
|
+
enabled: true,
|
|
331
|
+
passed,
|
|
332
|
+
mode,
|
|
333
|
+
confidence,
|
|
334
|
+
parallelizable: passed && parallelizable,
|
|
335
|
+
riskLevel,
|
|
336
|
+
reasons,
|
|
337
|
+
evidence: {
|
|
338
|
+
automationSignals,
|
|
339
|
+
riskSignals,
|
|
340
|
+
fileCount: task.files.length,
|
|
341
|
+
structuredFileCount,
|
|
342
|
+
batchSize,
|
|
343
|
+
},
|
|
344
|
+
actions: buildActions(mode),
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
module.exports = {
|
|
349
|
+
DEFAULT_FAST_PATH_CONFIG,
|
|
350
|
+
evaluateFastPath,
|
|
351
|
+
getAutomationPatterns,
|
|
352
|
+
getRiskPatterns,
|
|
353
|
+
getStructuredFileExtensions,
|
|
354
|
+
normalizeConfig,
|
|
355
|
+
normalizeTask,
|
|
356
|
+
};
|
|
@@ -145,6 +145,20 @@ const {
|
|
|
145
145
|
PHASE_1_SEQUENCE,
|
|
146
146
|
} = require('./greenfield-handler');
|
|
147
147
|
|
|
148
|
+
// v1.5.0: Fast-Path Gate
|
|
149
|
+
// Heuristic gate to decide when a task can be executed via a faster mode
|
|
150
|
+
// (parallel_batch / deterministic_batch / external_executor) versus the
|
|
151
|
+
// standard sequential workflow. Used as input to orchestrator decisions.
|
|
152
|
+
const {
|
|
153
|
+
DEFAULT_FAST_PATH_CONFIG,
|
|
154
|
+
evaluateFastPath,
|
|
155
|
+
getAutomationPatterns,
|
|
156
|
+
getRiskPatterns,
|
|
157
|
+
getStructuredFileExtensions,
|
|
158
|
+
normalizeConfig: normalizeFastPathConfig,
|
|
159
|
+
normalizeTask: normalizeFastPathTask,
|
|
160
|
+
} = require('./fast-path-gate');
|
|
161
|
+
|
|
148
162
|
module.exports = {
|
|
149
163
|
// Main orchestrators
|
|
150
164
|
WorkflowOrchestrator,
|
|
@@ -319,5 +333,14 @@ module.exports = {
|
|
|
319
333
|
GreenfieldPhaseFailureAction,
|
|
320
334
|
DEFAULT_GREENFIELD_INDICATORS,
|
|
321
335
|
PHASE_1_SEQUENCE,
|
|
336
|
+
|
|
337
|
+
// v1.5.0: Fast-Path Gate
|
|
338
|
+
DEFAULT_FAST_PATH_CONFIG,
|
|
339
|
+
evaluateFastPath,
|
|
340
|
+
getAutomationPatterns,
|
|
341
|
+
getRiskPatterns,
|
|
342
|
+
getStructuredFileExtensions,
|
|
343
|
+
normalizeFastPathConfig,
|
|
344
|
+
normalizeFastPathTask,
|
|
322
345
|
};
|
|
323
346
|
|