@hongmaple0820/scale-engine 0.39.0 → 0.40.1
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/dist/api/cli.js +154 -19
- package/dist/api/cli.js.map +1 -1
- package/dist/bootstrap/DependencyBootstrap.d.ts +1 -0
- package/dist/bootstrap/DependencyBootstrap.js +128 -23
- package/dist/bootstrap/DependencyBootstrap.js.map +1 -1
- package/dist/bootstrap/DependencyBootstrapRenderer.js +2 -4
- package/dist/bootstrap/DependencyBootstrapRenderer.js.map +1 -1
- package/dist/memory/MemoryProviders.d.ts +8 -0
- package/dist/memory/MemoryProviders.js +183 -15
- package/dist/memory/MemoryProviders.js.map +1 -1
- package/dist/setup/SetupWizard.d.ts +3 -0
- package/dist/setup/SetupWizard.js +79 -19
- package/dist/setup/SetupWizard.js.map +1 -1
- package/dist/skills/SkillDoctor.js +2 -2
- package/dist/skills/SkillDoctor.js.map +1 -1
- package/dist/skills/SkillRepository.js +2 -2
- package/dist/skills/SkillRepository.js.map +1 -1
- package/dist/tools/ToolCapabilityRegistry.js +2 -2
- package/dist/tools/ToolCapabilityRegistry.js.map +1 -1
- package/docs/CODE_INTELLIGENCE.md +1 -1
- package/docs/MEMORY_FABRIC.md +1 -1
- package/docs/THIRD_PARTY_SKILLS.md +13 -5
- package/docs/start/README.md +2 -2
- package/docs/start/quickstart.md +54 -45
- package/docs/start/workflow-upgrade.md +8 -1
- package/package.json +2 -2
- package/scripts/workflow/provider-rehearsal.mjs +135 -44
|
@@ -11,6 +11,7 @@ const options = parseArgs(process.argv.slice(2))
|
|
|
11
11
|
const runId = `provider-rehearsal-${Date.now()}-${Math.random().toString(16).slice(2)}`
|
|
12
12
|
const workRoot = options.outDir ? resolve(options.outDir) : join(tmpdir(), runId)
|
|
13
13
|
const results = []
|
|
14
|
+
const RTK_BYPASS_COMMANDS = new Set(['gbrain'])
|
|
14
15
|
|
|
15
16
|
mkdirSync(workRoot, { recursive: true })
|
|
16
17
|
|
|
@@ -26,15 +27,19 @@ try {
|
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
function runGbrainReplay() {
|
|
29
|
-
const
|
|
30
|
-
|
|
30
|
+
const healthCheck = runCommand('gbrain-health', 'gbrain', ['list', '-n', '1'], { timeoutMs: 60_000 })
|
|
31
|
+
const health = evaluateGbrainList(healthCheck)
|
|
32
|
+
if (!health.available) {
|
|
31
33
|
return capability('gbrain', options.requireGbrain ? 'failed' : 'blocked', {
|
|
32
|
-
reason: failureLine(`${
|
|
34
|
+
reason: health.reason || failureLine(`${healthCheck.stdout}\n${healthCheck.stderr}`) || 'gbrain health check failed',
|
|
33
35
|
required: options.requireGbrain,
|
|
34
|
-
|
|
36
|
+
health,
|
|
37
|
+
commands: [healthCheck],
|
|
35
38
|
nextCommands: [
|
|
39
|
+
'gbrain init --pglite --no-embedding',
|
|
36
40
|
'gbrain init --supabase',
|
|
37
41
|
'gbrain init --url <remote-gbrain-url>',
|
|
42
|
+
'gbrain list -n 1',
|
|
38
43
|
'gbrain doctor --json',
|
|
39
44
|
'npm run smoke:gbrain',
|
|
40
45
|
],
|
|
@@ -43,39 +48,50 @@ function runGbrainReplay() {
|
|
|
43
48
|
|
|
44
49
|
const slug = `scale-rehearsal-${Date.now()}-${Math.random().toString(16).slice(2)}`
|
|
45
50
|
const sentinel = `scale-engine-gbrain-replay-${Date.now()}-${Math.random().toString(16).slice(2)}`
|
|
46
|
-
const body =
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const get = runCommand('gbrain-get', 'gbrain', ['get', slug], { timeoutMs: 60_000 })
|
|
56
|
-
const query = runCommand('gbrain-query', 'gbrain', ['query', sentinel], { timeoutMs: 60_000 })
|
|
57
|
-
const search = query.exitCode === 0 ? undefined : runCommand('gbrain-search', 'gbrain', ['search', sentinel], { timeoutMs: 60_000 })
|
|
51
|
+
const body = `# ${slug}. Sentinel: ${sentinel}. This page verifies that SCALE can write memory in one process and read/query it in later processes.`
|
|
52
|
+
|
|
53
|
+
const put = runCommand('gbrain-put', 'gbrain', ['put', slug, '--content', body], { timeoutMs: 60_000 })
|
|
54
|
+
const get = runCommand('gbrain-get', 'gbrain', ['get', slug], { timeoutMs: 8_000 })
|
|
55
|
+
const query = runCommand('gbrain-query', 'gbrain', ['query', sentinel], { timeoutMs: 8_000 })
|
|
56
|
+
const queryOutput = `${query.stdout}\n${query.stderr}`
|
|
57
|
+
const search = query.exitCode === 0 && (queryOutput.includes(sentinel) || queryOutput.includes(slug))
|
|
58
|
+
? undefined
|
|
59
|
+
: runCommand('gbrain-search', 'gbrain', ['search', sentinel], { timeoutMs: 8_000 })
|
|
58
60
|
const cleanup = options.keepGbrainPage ? undefined : runCommand('gbrain-delete', 'gbrain', ['delete', slug], { timeoutMs: 60_000 })
|
|
59
61
|
|
|
60
|
-
const recallOutput = `${
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
&& get.stdout.includes(sentinel)
|
|
64
|
-
&& (query.exitCode === 0 || search?.exitCode === 0)
|
|
62
|
+
const recallOutput = `${queryOutput}\n${search?.stdout ?? ''}\n${search?.stderr ?? ''}`
|
|
63
|
+
const getPassed = (get.exitCode === 0 || get.timedOut) && get.stdout.includes(sentinel)
|
|
64
|
+
const recallPassed = (query.exitCode === 0 || query.timedOut || search?.exitCode === 0 || search?.timedOut)
|
|
65
65
|
&& (recallOutput.includes(sentinel) || recallOutput.includes(slug))
|
|
66
|
+
const replayPassed = put.exitCode === 0
|
|
67
|
+
&& getPassed
|
|
68
|
+
&& recallPassed
|
|
66
69
|
|
|
67
70
|
return capability('gbrain', replayPassed ? 'passed' : 'failed', {
|
|
68
71
|
reason: replayPassed
|
|
69
|
-
? '
|
|
72
|
+
? 'gbrain write/get/query replay passed across separate CLI processes'
|
|
70
73
|
: 'gbrain was configured, but write/get/query replay did not prove recall',
|
|
71
74
|
required: options.requireGbrain,
|
|
75
|
+
health,
|
|
72
76
|
sentinel,
|
|
73
77
|
slug,
|
|
74
|
-
commands: [
|
|
78
|
+
commands: [healthCheck, put, get, query, search, cleanup].filter(Boolean),
|
|
75
79
|
nextCommands: replayPassed ? [] : ['gbrain doctor --json', `gbrain get ${slug}`, `gbrain query ${sentinel}`],
|
|
76
80
|
})
|
|
77
81
|
}
|
|
78
82
|
|
|
83
|
+
function evaluateGbrainList(command) {
|
|
84
|
+
const output = `${command.stdout}\n${command.stderr}`
|
|
85
|
+
if (/no brain configured/i.test(output)) {
|
|
86
|
+
return { available: false, degraded: false, reason: 'gbrain CLI is installed but no brain is configured' }
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
available: command.exitCode === 0,
|
|
90
|
+
degraded: false,
|
|
91
|
+
reason: command.exitCode === 0 ? 'gbrain list passed; brain is reachable' : failureLine(output),
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
79
95
|
function runGraphifyRehearsal() {
|
|
80
96
|
const help = runCommand('graphify-help', 'graphify', ['--help'], { timeoutMs: 30_000 })
|
|
81
97
|
if (help.exitCode !== 0) {
|
|
@@ -91,24 +107,32 @@ function runGraphifyRehearsal() {
|
|
|
91
107
|
})
|
|
92
108
|
}
|
|
93
109
|
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
110
|
+
const projectPath = resolve(options.largeProject)
|
|
111
|
+
const graphOut = options.semanticExtract ? resolve(workRoot, 'graphify-out') : join(projectPath, 'graphify-out')
|
|
112
|
+
if (options.semanticExtract) mkdirSync(graphOut, { recursive: true })
|
|
113
|
+
const extractCommand = options.semanticExtract ? 'extract' : 'update'
|
|
114
|
+
const extractArgs = options.semanticExtract
|
|
115
|
+
? ['extract', projectPath, '--out', graphOut]
|
|
116
|
+
: ['update', projectPath]
|
|
117
|
+
if (!options.semanticExtract) removeGeneratedGraphJson(graphOut)
|
|
118
|
+
if (options.semanticExtract && options.graphifyBackend) extractArgs.push('--backend', options.graphifyBackend)
|
|
98
119
|
if (options.noCluster) extractArgs.push('--no-cluster')
|
|
99
|
-
if (options.globalExtract) extractArgs.push('--global')
|
|
120
|
+
if (options.semanticExtract && options.globalExtract) extractArgs.push('--global')
|
|
100
121
|
|
|
101
|
-
const extract = runCommand(
|
|
122
|
+
const extract = runCommand(`graphify-${extractCommand}`, 'graphify', extractArgs, {
|
|
123
|
+
timeoutMs: options.timeoutMs,
|
|
124
|
+
env: graphifyNoModelEnv(),
|
|
125
|
+
})
|
|
102
126
|
if (extract.exitCode !== 0) {
|
|
103
127
|
return capability('graphify', options.requireGraphify ? 'failed' : 'blocked', {
|
|
104
|
-
reason: failureLine(`${extract.stdout}\n${extract.stderr}`) ||
|
|
128
|
+
reason: failureLine(`${extract.stdout}\n${extract.stderr}`) || `graphify ${extractCommand} failed`,
|
|
105
129
|
required: options.requireGraphify,
|
|
106
130
|
commands: [help, extract],
|
|
107
131
|
nextCommands: [
|
|
108
132
|
'graphify install --platform codex',
|
|
109
133
|
'graphify hook status',
|
|
110
|
-
|
|
111
|
-
|
|
134
|
+
`graphify update ${quoteArg(projectPath)} --no-cluster`,
|
|
135
|
+
'Use --semantic-extract only when semantic LLM extraction is explicitly allowed.',
|
|
112
136
|
],
|
|
113
137
|
})
|
|
114
138
|
}
|
|
@@ -116,7 +140,7 @@ function runGraphifyRehearsal() {
|
|
|
116
140
|
const graphPath = findGraphJson(graphOut)
|
|
117
141
|
if (!graphPath) {
|
|
118
142
|
return capability('graphify', options.requireGraphify ? 'failed' : 'blocked', {
|
|
119
|
-
reason: `graphify
|
|
143
|
+
reason: `graphify ${extractCommand} completed but graph.json was not found under ${graphOut}`,
|
|
120
144
|
required: options.requireGraphify,
|
|
121
145
|
commands: [help, extract],
|
|
122
146
|
nextCommands: [`Get-ChildItem -Recurse ${quoteArg(graphOut)}`],
|
|
@@ -129,19 +153,26 @@ function runGraphifyRehearsal() {
|
|
|
129
153
|
options.graphifyQuestion,
|
|
130
154
|
'--graph',
|
|
131
155
|
graphPath,
|
|
132
|
-
], { timeoutMs: 120_000 })
|
|
133
|
-
const benchmark = runCommand('graphify-benchmark', 'graphify', ['benchmark', graphPath], {
|
|
156
|
+
], { timeoutMs: 120_000, env: graphifyNoModelEnv() })
|
|
157
|
+
const benchmark = runCommand('graphify-benchmark', 'graphify', ['benchmark', graphPath], {
|
|
158
|
+
timeoutMs: 120_000,
|
|
159
|
+
env: graphifyNoModelEnv(),
|
|
160
|
+
})
|
|
134
161
|
const globalAdd = options.globalAdd
|
|
135
|
-
? runCommand('graphify-global-add', 'graphify', ['global', 'add', graphPath, '--as', options.globalTag], {
|
|
162
|
+
? runCommand('graphify-global-add', 'graphify', ['global', 'add', graphPath, '--as', options.globalTag], {
|
|
163
|
+
timeoutMs: 120_000,
|
|
164
|
+
env: graphifyNoModelEnv(),
|
|
165
|
+
})
|
|
136
166
|
: undefined
|
|
137
167
|
|
|
138
168
|
const passed = stats.ok && query.exitCode === 0
|
|
139
169
|
return capability('graphify', passed ? 'passed' : options.requireGraphify ? 'failed' : 'blocked', {
|
|
140
170
|
reason: passed
|
|
141
|
-
?
|
|
171
|
+
? `graphify ${extractCommand} built a real project graph and answered a graph query`
|
|
142
172
|
: 'graphify generated an artifact but graph stats or query validation failed',
|
|
143
173
|
required: options.requireGraphify,
|
|
144
|
-
project:
|
|
174
|
+
project: projectPath,
|
|
175
|
+
mode: options.semanticExtract ? 'semantic-extract' : 'ast-update-no-llm',
|
|
145
176
|
graphPath,
|
|
146
177
|
stats,
|
|
147
178
|
commands: [help, extract, query, benchmark, globalAdd].filter(Boolean),
|
|
@@ -237,7 +268,7 @@ function runCommand(name, command, args, opts = {}) {
|
|
|
237
268
|
if (options.verbose) process.stderr.write(`[RUN] ${commandLine}\n`)
|
|
238
269
|
const result = spawnStructured(invocation.command, invocation.args, {
|
|
239
270
|
cwd: repoRoot,
|
|
240
|
-
env: process.env,
|
|
271
|
+
env: opts.env ?? process.env,
|
|
241
272
|
input: opts.input,
|
|
242
273
|
encoding: 'utf8',
|
|
243
274
|
timeout: opts.timeoutMs ?? options.timeoutMs,
|
|
@@ -246,11 +277,13 @@ function runCommand(name, command, args, opts = {}) {
|
|
|
246
277
|
const stdout = String(result.stdout ?? '')
|
|
247
278
|
const stderr = String(result.stderr ?? '') + (result.error ? `\n${result.error.message}` : '')
|
|
248
279
|
const exitCode = typeof result.status === 'number' ? result.status : 1
|
|
280
|
+
const timedOut = /ETIMEDOUT/i.test(String(result.error?.message ?? ''))
|
|
249
281
|
const entry = {
|
|
250
282
|
name,
|
|
251
283
|
command: commandLine,
|
|
252
284
|
wrappedByRtk: invocation.wrapped,
|
|
253
285
|
exitCode,
|
|
286
|
+
timedOut,
|
|
254
287
|
startedAt,
|
|
255
288
|
endedAt: new Date().toISOString(),
|
|
256
289
|
stdoutTail: tail(stdout),
|
|
@@ -260,16 +293,49 @@ function runCommand(name, command, args, opts = {}) {
|
|
|
260
293
|
return { ...entry, stdout, stderr }
|
|
261
294
|
}
|
|
262
295
|
|
|
296
|
+
function graphifyNoModelEnv() {
|
|
297
|
+
const env = { ...process.env }
|
|
298
|
+
for (const key of [
|
|
299
|
+
'GEMINI_API_KEY',
|
|
300
|
+
'GOOGLE_API_KEY',
|
|
301
|
+
'MOONSHOT_API_KEY',
|
|
302
|
+
'ANTHROPIC_API_KEY',
|
|
303
|
+
'OPENAI_API_KEY',
|
|
304
|
+
'DEEPSEEK_API_KEY',
|
|
305
|
+
'KIMI_API_KEY',
|
|
306
|
+
]) {
|
|
307
|
+
delete env[key]
|
|
308
|
+
}
|
|
309
|
+
return env
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function removeGeneratedGraphJson(graphOut) {
|
|
313
|
+
const outputDir = resolve(graphOut)
|
|
314
|
+
if (outputDir.split(/[\\/]/).at(-1) !== 'graphify-out') {
|
|
315
|
+
throw new Error(`Refusing to clean graphify output outside a graphify-out directory: ${outputDir}`)
|
|
316
|
+
}
|
|
317
|
+
rmSync(join(outputDir, 'graph.json'), { force: true })
|
|
318
|
+
}
|
|
319
|
+
|
|
263
320
|
function wrapWithRtk(command, args) {
|
|
321
|
+
if (RTK_BYPASS_COMMANDS.has(command)) return { command, args, wrapped: false }
|
|
264
322
|
if (!options.useRtk || command === 'rtk' || !commandExists('rtk')) return { command, args, wrapped: false }
|
|
265
323
|
return { command: 'rtk', args: [command, ...args], wrapped: true }
|
|
266
324
|
}
|
|
267
325
|
|
|
268
326
|
function spawnStructured(command, args, options) {
|
|
327
|
+
const direct = resolveDirectWindowsInvocation(command, args)
|
|
328
|
+
if (direct) {
|
|
329
|
+
return spawnSync(direct.command, direct.args, {
|
|
330
|
+
...options,
|
|
331
|
+
shell: false,
|
|
332
|
+
windowsHide: true,
|
|
333
|
+
})
|
|
334
|
+
}
|
|
269
335
|
const resolved = resolveWindowsCommandShim(resolveCommandPath(command) ?? command)
|
|
270
336
|
if (process.platform === 'win32' && /\.(cmd|bat)$/i.test(resolved)) {
|
|
271
|
-
const
|
|
272
|
-
return spawnSync(
|
|
337
|
+
const commandLine = ['&', powershellQuote(resolved), ...args.map(powershellQuote)].join(' ')
|
|
338
|
+
return spawnSync('powershell', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', commandLine], {
|
|
273
339
|
...options,
|
|
274
340
|
shell: false,
|
|
275
341
|
windowsHide: true,
|
|
@@ -282,6 +348,24 @@ function spawnStructured(command, args, options) {
|
|
|
282
348
|
})
|
|
283
349
|
}
|
|
284
350
|
|
|
351
|
+
function resolveDirectWindowsInvocation(command, args) {
|
|
352
|
+
if (process.platform !== 'win32' || command !== 'gbrain') return null
|
|
353
|
+
const gbrainShim = resolveCommandPath('gbrain')
|
|
354
|
+
if (!gbrainShim || !/\.cmd$/i.test(gbrainShim) || !existsSync(gbrainShim)) return null
|
|
355
|
+
try {
|
|
356
|
+
const content = readFileSync(gbrainShim, 'utf8')
|
|
357
|
+
const cliPath = content.match(/"([^"]*gbrain[^"]*src[\\/]cli\.ts)"/i)?.[1]
|
|
358
|
+
const bunShim = resolveCommandPath('bun')
|
|
359
|
+
const bunExe = bunShim ? join(dirname(bunShim), 'node_modules', 'bun', 'bin', 'bun.exe') : ''
|
|
360
|
+
if (cliPath && bunExe && existsSync(bunExe)) {
|
|
361
|
+
return { command: bunExe, args: [cliPath, ...args] }
|
|
362
|
+
}
|
|
363
|
+
} catch {
|
|
364
|
+
return null
|
|
365
|
+
}
|
|
366
|
+
return null
|
|
367
|
+
}
|
|
368
|
+
|
|
285
369
|
function resolveWindowsCommandShim(command) {
|
|
286
370
|
if (process.platform !== 'win32') return command
|
|
287
371
|
if (!/[\\/]/.test(command) || extname(command)) return command
|
|
@@ -322,6 +406,7 @@ function parseArgs(args) {
|
|
|
322
406
|
verbose: false,
|
|
323
407
|
useRtk: true,
|
|
324
408
|
noCluster: true,
|
|
409
|
+
semanticExtract: false,
|
|
325
410
|
globalExtract: false,
|
|
326
411
|
globalAdd: false,
|
|
327
412
|
globalTag: 'scale-engine-rehearsal',
|
|
@@ -344,6 +429,7 @@ function parseArgs(args) {
|
|
|
344
429
|
else if (arg === '--verbose') parsed.verbose = true
|
|
345
430
|
else if (arg === '--no-rtk') parsed.useRtk = false
|
|
346
431
|
else if (arg === '--cluster') parsed.noCluster = false
|
|
432
|
+
else if (arg === '--semantic-extract') parsed.semanticExtract = true
|
|
347
433
|
else if (arg === '--global') parsed.globalExtract = true
|
|
348
434
|
else if (arg === '--global-add') parsed.globalAdd = true
|
|
349
435
|
else if (arg === '--global-tag') parsed.globalTag = requireValue(args, ++index, arg)
|
|
@@ -389,9 +475,10 @@ Options:
|
|
|
389
475
|
--out PATH Output directory for temporary graphify artifacts
|
|
390
476
|
--keep-output Keep temporary output when --out is not supplied
|
|
391
477
|
--keep-gbrain-page Do not delete the temporary gbrain page
|
|
392
|
-
--
|
|
393
|
-
--graphify-
|
|
394
|
-
--
|
|
478
|
+
--semantic-extract Use graphify extract, which may call an LLM; default is graphify update with no LLM
|
|
479
|
+
--graphify-backend ID Pass --backend to graphify extract only with --semantic-extract
|
|
480
|
+
--graphify-question Q Query to ask graphify after graph generation
|
|
481
|
+
--global Pass --global to graphify extract only with --semantic-extract
|
|
395
482
|
--global-add Add generated graph to graphify global store
|
|
396
483
|
--global-tag TAG Tag for --global-add, default scale-engine-rehearsal
|
|
397
484
|
--write-report Write JSON report to .scale/reports
|
|
@@ -420,6 +507,10 @@ function quoteArg(value) {
|
|
|
420
507
|
return `"${raw.replace(/(["\\$`])/g, '\\$1')}"`
|
|
421
508
|
}
|
|
422
509
|
|
|
510
|
+
function powershellQuote(value) {
|
|
511
|
+
return `'${String(value).replace(/'/g, "''")}'`
|
|
512
|
+
}
|
|
513
|
+
|
|
423
514
|
function tail(value, max = 4000) {
|
|
424
515
|
return value.length > max ? value.slice(-max) : value
|
|
425
516
|
}
|