@hongmaple0820/scale-engine 0.33.0 → 0.39.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.en.md +86 -376
- package/README.md +95 -540
- package/dist/api/cli.js +293 -18
- package/dist/api/cli.js.map +1 -1
- package/dist/api/doctor.d.ts +38 -3
- package/dist/api/doctor.js +269 -44
- package/dist/api/doctor.js.map +1 -1
- package/dist/api/mcp.js +2 -2
- package/dist/api/mcp.js.map +1 -1
- package/dist/api/quickstart.d.ts +34 -4
- package/dist/api/quickstart.js +90 -73
- package/dist/api/quickstart.js.map +1 -1
- package/dist/bootstrap/DependencyBootstrap.d.ts +110 -0
- package/dist/bootstrap/DependencyBootstrap.js +829 -0
- package/dist/bootstrap/DependencyBootstrap.js.map +1 -0
- package/dist/bootstrap/DependencyBootstrapRenderer.d.ts +3 -0
- package/dist/bootstrap/DependencyBootstrapRenderer.js +140 -0
- package/dist/bootstrap/DependencyBootstrapRenderer.js.map +1 -0
- package/dist/capabilities/InstalledSkillsIntegration.js +14 -6
- package/dist/capabilities/InstalledSkillsIntegration.js.map +1 -1
- package/dist/cli/gateStatusCommands.d.ts +1 -0
- package/dist/cli/gateStatusCommands.js +52 -0
- package/dist/cli/gateStatusCommands.js.map +1 -0
- package/dist/cli/phaseCommands.js +15 -3
- package/dist/cli/phaseCommands.js.map +1 -1
- package/dist/cli/promptCommands.d.ts +1 -0
- package/dist/cli/promptCommands.js +57 -0
- package/dist/cli/promptCommands.js.map +1 -0
- package/dist/cli/scoreCommands.d.ts +1 -0
- package/dist/cli/scoreCommands.js +112 -0
- package/dist/cli/scoreCommands.js.map +1 -0
- package/dist/codegraph/CodeIntelligence.d.ts +12 -0
- package/dist/codegraph/CodeIntelligence.js +251 -30
- package/dist/codegraph/CodeIntelligence.js.map +1 -1
- package/dist/config/profiles.d.ts +12 -0
- package/dist/config/profiles.js +39 -4
- package/dist/config/profiles.js.map +1 -1
- package/dist/context/SessionStartSequence.js +13 -4
- package/dist/context/SessionStartSequence.js.map +1 -1
- package/dist/core/ExternalCommand.d.ts +9 -0
- package/dist/core/ExternalCommand.js +70 -0
- package/dist/core/ExternalCommand.js.map +1 -0
- package/dist/env/EnvironmentDoctor.d.ts +66 -0
- package/dist/env/EnvironmentDoctor.js +365 -0
- package/dist/env/EnvironmentDoctor.js.map +1 -0
- package/dist/i18n/Language.d.ts +9 -0
- package/dist/i18n/Language.js +38 -0
- package/dist/i18n/Language.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/knowledge/CerebrumManager.d.ts +2 -2
- package/dist/knowledge/CerebrumManager.js.map +1 -1
- package/dist/knowledge/GraphifyKnowledgeBase.d.ts +38 -0
- package/dist/knowledge/GraphifyKnowledgeBase.js +409 -0
- package/dist/knowledge/GraphifyKnowledgeBase.js.map +1 -0
- package/dist/memory/MemoryFabric.js +1 -0
- package/dist/memory/MemoryFabric.js.map +1 -1
- package/dist/memory/MemoryIntelligence.d.ts +42 -0
- package/dist/memory/MemoryIntelligence.js +215 -0
- package/dist/memory/MemoryIntelligence.js.map +1 -0
- package/dist/memory/MemoryProviders.d.ts +22 -0
- package/dist/memory/MemoryProviders.js +171 -5
- package/dist/memory/MemoryProviders.js.map +1 -1
- package/dist/memory/index.d.ts +1 -0
- package/dist/memory/index.js +1 -0
- package/dist/memory/index.js.map +1 -1
- package/dist/prompts/PromptOptimizer.d.ts +42 -0
- package/dist/prompts/PromptOptimizer.js +309 -0
- package/dist/prompts/PromptOptimizer.js.map +1 -0
- package/dist/runtime/AiOsRuntime.d.ts +2 -0
- package/dist/runtime/AiOsRuntime.js +2 -0
- package/dist/runtime/AiOsRuntime.js.map +1 -1
- package/dist/runtime/ExecutionLedger.d.ts +46 -0
- package/dist/runtime/ExecutionLedger.js +71 -0
- package/dist/runtime/ExecutionLedger.js.map +1 -0
- package/dist/runtime/index.d.ts +1 -0
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/index.js.map +1 -1
- package/dist/setup/SetupWizard.d.ts +42 -0
- package/dist/setup/SetupWizard.js +156 -0
- package/dist/setup/SetupWizard.js.map +1 -0
- package/dist/skills/SkillRepository.js +7 -7
- package/dist/skills/SkillRepository.js.map +1 -1
- package/dist/skills/routing/SkillPolicy.js +2 -2
- package/dist/skills/routing/SkillPolicy.js.map +1 -1
- package/dist/testing/DiffTestSelector.js +1 -1
- package/dist/testing/DiffTestSelector.js.map +1 -1
- package/dist/tools/RtkRuntime.d.ts +9 -0
- package/dist/tools/RtkRuntime.js +43 -0
- package/dist/tools/RtkRuntime.js.map +1 -0
- package/dist/tools/ToolCapabilityRegistry.d.ts +5 -0
- package/dist/tools/ToolCapabilityRegistry.js +75 -13
- package/dist/tools/ToolCapabilityRegistry.js.map +1 -1
- package/dist/tools/ToolOrchestrator.js +6 -4
- package/dist/tools/ToolOrchestrator.js.map +1 -1
- package/dist/tools/ToolPolicy.js +16 -1
- package/dist/tools/ToolPolicy.js.map +1 -1
- package/dist/workflow/AdaptiveWorkflowRouter.d.ts +1 -0
- package/dist/workflow/AdaptiveWorkflowRouter.js +3 -0
- package/dist/workflow/AdaptiveWorkflowRouter.js.map +1 -1
- package/dist/workflow/CommitDiscipline.d.ts +68 -0
- package/dist/workflow/CommitDiscipline.js +328 -0
- package/dist/workflow/CommitDiscipline.js.map +1 -0
- package/dist/workflow/CrossRepoOrchestrator.d.ts +92 -0
- package/dist/workflow/CrossRepoOrchestrator.js +408 -0
- package/dist/workflow/CrossRepoOrchestrator.js.map +1 -0
- package/dist/workflow/GateCatalog.d.ts +61 -0
- package/dist/workflow/GateCatalog.js +212 -0
- package/dist/workflow/GateCatalog.js.map +1 -0
- package/dist/workflow/GovernanceRoi.d.ts +52 -0
- package/dist/workflow/GovernanceRoi.js +204 -0
- package/dist/workflow/GovernanceRoi.js.map +1 -0
- package/dist/workflow/GovernanceTemplatePacks.js +19 -4
- package/dist/workflow/GovernanceTemplatePacks.js.map +1 -1
- package/dist/workflow/GovernanceTemplates.js +2 -2
- package/dist/workflow/McpGovernance.d.ts +63 -0
- package/dist/workflow/McpGovernance.js +198 -0
- package/dist/workflow/McpGovernance.js.map +1 -0
- package/dist/workflow/SessionCoordinator.d.ts +103 -0
- package/dist/workflow/SessionCoordinator.js +401 -0
- package/dist/workflow/SessionCoordinator.js.map +1 -0
- package/dist/workflow/SessionPreamble.js +7 -2
- package/dist/workflow/SessionPreamble.js.map +1 -1
- package/dist/workflow/TaskDependencyGraph.d.ts +73 -0
- package/dist/workflow/TaskDependencyGraph.js +245 -0
- package/dist/workflow/TaskDependencyGraph.js.map +1 -0
- package/dist/workflow/TaskScoreEngine.d.ts +42 -0
- package/dist/workflow/TaskScoreEngine.js +181 -0
- package/dist/workflow/TaskScoreEngine.js.map +1 -0
- package/dist/workflow/WorkflowTemplates.d.ts +38 -0
- package/dist/workflow/WorkflowTemplates.js +371 -0
- package/dist/workflow/WorkflowTemplates.js.map +1 -0
- package/dist/workflow/WorkspacePolicy.d.ts +46 -0
- package/dist/workflow/WorkspacePolicy.js +141 -0
- package/dist/workflow/WorkspacePolicy.js.map +1 -0
- package/dist/workflow/WorkspaceTopology.d.ts +3 -0
- package/dist/workflow/WorkspaceTopology.js +40 -3
- package/dist/workflow/WorkspaceTopology.js.map +1 -1
- package/dist/workflow/gates/GateSystem.js +14 -11
- package/dist/workflow/gates/GateSystem.js.map +1 -1
- package/dist/workflow/index.d.ts +9 -0
- package/dist/workflow/index.js +9 -0
- package/dist/workflow/index.js.map +1 -1
- package/docs/CODE_INTELLIGENCE.md +48 -6
- package/docs/EXTERNAL_REFERENCES.md +5 -2
- package/docs/MEMORY_FABRIC.md +28 -3
- package/docs/SKILL-REPOSITORY.md +3 -3
- package/docs/THIRD_PARTY_SKILLS.md +50 -1
- package/docs/guides/GETTING_STARTED.md +24 -0
- package/docs/start/quickstart.md +107 -69
- package/docs/workflow/GATES_AND_SCORE.md +56 -0
- package/docs/workflow/PROMPT_OPTIMIZATION.md +44 -0
- package/docs/workflow/README.md +7 -0
- package/docs/workflow/node-library.md +3 -3
- package/docs/workflow/templates/skill-plan.md +1 -1
- package/package.json +13 -5
- package/scripts/workflow/provider-rehearsal.mjs +425 -0
- package/scripts/workflow/setup-smoke.mjs +299 -0
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from 'node:child_process'
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
4
|
+
import { tmpdir } from 'node:os'
|
|
5
|
+
import { dirname, extname, join, resolve } from 'node:path'
|
|
6
|
+
import { fileURLToPath } from 'node:url'
|
|
7
|
+
|
|
8
|
+
const scriptDir = dirname(fileURLToPath(import.meta.url))
|
|
9
|
+
const repoRoot = resolve(scriptDir, '..', '..')
|
|
10
|
+
const options = parseArgs(process.argv.slice(2))
|
|
11
|
+
const runId = `provider-rehearsal-${Date.now()}-${Math.random().toString(16).slice(2)}`
|
|
12
|
+
const workRoot = options.outDir ? resolve(options.outDir) : join(tmpdir(), runId)
|
|
13
|
+
const results = []
|
|
14
|
+
|
|
15
|
+
mkdirSync(workRoot, { recursive: true })
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const gbrain = options.skipGbrain ? skipped('gbrain') : runGbrainReplay()
|
|
19
|
+
const graphify = options.skipGraphify ? skipped('graphify') : runGraphifyRehearsal()
|
|
20
|
+
const report = buildReport([gbrain, graphify])
|
|
21
|
+
if (options.writeReport || options.reportFile) writeReport(report)
|
|
22
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`)
|
|
23
|
+
if (!report.ok) process.exitCode = 1
|
|
24
|
+
} finally {
|
|
25
|
+
if (!options.keepOutput && !options.outDir) rmSync(workRoot, { recursive: true, force: true })
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function runGbrainReplay() {
|
|
29
|
+
const doctor = runCommand('gbrain-doctor', 'gbrain', ['doctor', '--json'], { timeoutMs: 30_000 })
|
|
30
|
+
if (doctor.exitCode !== 0) {
|
|
31
|
+
return capability('gbrain', options.requireGbrain ? 'failed' : 'blocked', {
|
|
32
|
+
reason: failureLine(`${doctor.stdout}\n${doctor.stderr}`) || 'gbrain doctor failed',
|
|
33
|
+
required: options.requireGbrain,
|
|
34
|
+
commands: [doctor],
|
|
35
|
+
nextCommands: [
|
|
36
|
+
'gbrain init --supabase',
|
|
37
|
+
'gbrain init --url <remote-gbrain-url>',
|
|
38
|
+
'gbrain doctor --json',
|
|
39
|
+
'npm run smoke:gbrain',
|
|
40
|
+
],
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const slug = `scale-rehearsal-${Date.now()}-${Math.random().toString(16).slice(2)}`
|
|
45
|
+
const sentinel = `scale-engine-gbrain-replay-${Date.now()}-${Math.random().toString(16).slice(2)}`
|
|
46
|
+
const body = [
|
|
47
|
+
`# ${slug}`,
|
|
48
|
+
'',
|
|
49
|
+
`Sentinel: ${sentinel}`,
|
|
50
|
+
'',
|
|
51
|
+
'This page verifies that SCALE can write memory in one process and read/query it in later processes.',
|
|
52
|
+
].join('\n')
|
|
53
|
+
|
|
54
|
+
const put = runCommand('gbrain-put', 'gbrain', ['put', slug], { input: body, timeoutMs: 60_000 })
|
|
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 })
|
|
58
|
+
const cleanup = options.keepGbrainPage ? undefined : runCommand('gbrain-delete', 'gbrain', ['delete', slug], { timeoutMs: 60_000 })
|
|
59
|
+
|
|
60
|
+
const recallOutput = `${query.stdout}\n${query.stderr}\n${search?.stdout ?? ''}\n${search?.stderr ?? ''}`
|
|
61
|
+
const replayPassed = put.exitCode === 0
|
|
62
|
+
&& get.exitCode === 0
|
|
63
|
+
&& get.stdout.includes(sentinel)
|
|
64
|
+
&& (query.exitCode === 0 || search?.exitCode === 0)
|
|
65
|
+
&& (recallOutput.includes(sentinel) || recallOutput.includes(slug))
|
|
66
|
+
|
|
67
|
+
return capability('gbrain', replayPassed ? 'passed' : 'failed', {
|
|
68
|
+
reason: replayPassed
|
|
69
|
+
? 'remote gbrain write/get/query replay passed across separate CLI processes'
|
|
70
|
+
: 'gbrain was configured, but write/get/query replay did not prove recall',
|
|
71
|
+
required: options.requireGbrain,
|
|
72
|
+
sentinel,
|
|
73
|
+
slug,
|
|
74
|
+
commands: [doctor, put, get, query, search, cleanup].filter(Boolean),
|
|
75
|
+
nextCommands: replayPassed ? [] : ['gbrain doctor --json', `gbrain get ${slug}`, `gbrain query ${sentinel}`],
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function runGraphifyRehearsal() {
|
|
80
|
+
const help = runCommand('graphify-help', 'graphify', ['--help'], { timeoutMs: 30_000 })
|
|
81
|
+
if (help.exitCode !== 0) {
|
|
82
|
+
return capability('graphify', options.requireGraphify ? 'failed' : 'blocked', {
|
|
83
|
+
reason: failureLine(`${help.stdout}\n${help.stderr}`) || 'graphify CLI is not available',
|
|
84
|
+
required: options.requireGraphify,
|
|
85
|
+
commands: [help],
|
|
86
|
+
nextCommands: [
|
|
87
|
+
'uv tool install graphify',
|
|
88
|
+
'graphify install --platform codex',
|
|
89
|
+
'npm run smoke:graphify',
|
|
90
|
+
],
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const graphOut = resolve(workRoot, 'graphify-out')
|
|
95
|
+
mkdirSync(graphOut, { recursive: true })
|
|
96
|
+
const extractArgs = ['extract', resolve(options.largeProject), '--out', graphOut]
|
|
97
|
+
if (options.graphifyBackend) extractArgs.push('--backend', options.graphifyBackend)
|
|
98
|
+
if (options.noCluster) extractArgs.push('--no-cluster')
|
|
99
|
+
if (options.globalExtract) extractArgs.push('--global')
|
|
100
|
+
|
|
101
|
+
const extract = runCommand('graphify-extract', 'graphify', extractArgs, { timeoutMs: options.timeoutMs })
|
|
102
|
+
if (extract.exitCode !== 0) {
|
|
103
|
+
return capability('graphify', options.requireGraphify ? 'failed' : 'blocked', {
|
|
104
|
+
reason: failureLine(`${extract.stdout}\n${extract.stderr}`) || 'graphify extract failed',
|
|
105
|
+
required: options.requireGraphify,
|
|
106
|
+
commands: [help, extract],
|
|
107
|
+
nextCommands: [
|
|
108
|
+
'graphify install --platform codex',
|
|
109
|
+
'graphify hook status',
|
|
110
|
+
'Set one LLM key: GEMINI_API_KEY, GOOGLE_API_KEY, MOONSHOT_API_KEY, ANTHROPIC_API_KEY, OPENAI_API_KEY, or DEEPSEEK_API_KEY',
|
|
111
|
+
`graphify extract ${quoteArg(resolve(options.largeProject))} --out ${quoteArg(graphOut)} --no-cluster`,
|
|
112
|
+
],
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const graphPath = findGraphJson(graphOut)
|
|
117
|
+
if (!graphPath) {
|
|
118
|
+
return capability('graphify', options.requireGraphify ? 'failed' : 'blocked', {
|
|
119
|
+
reason: `graphify extract completed but graph.json was not found under ${graphOut}`,
|
|
120
|
+
required: options.requireGraphify,
|
|
121
|
+
commands: [help, extract],
|
|
122
|
+
nextCommands: [`Get-ChildItem -Recurse ${quoteArg(graphOut)}`],
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const stats = parseGraphStats(graphPath)
|
|
127
|
+
const query = runCommand('graphify-query', 'graphify', [
|
|
128
|
+
'query',
|
|
129
|
+
options.graphifyQuestion,
|
|
130
|
+
'--graph',
|
|
131
|
+
graphPath,
|
|
132
|
+
], { timeoutMs: 120_000 })
|
|
133
|
+
const benchmark = runCommand('graphify-benchmark', 'graphify', ['benchmark', graphPath], { timeoutMs: 120_000 })
|
|
134
|
+
const globalAdd = options.globalAdd
|
|
135
|
+
? runCommand('graphify-global-add', 'graphify', ['global', 'add', graphPath, '--as', options.globalTag], { timeoutMs: 120_000 })
|
|
136
|
+
: undefined
|
|
137
|
+
|
|
138
|
+
const passed = stats.ok && query.exitCode === 0
|
|
139
|
+
return capability('graphify', passed ? 'passed' : options.requireGraphify ? 'failed' : 'blocked', {
|
|
140
|
+
reason: passed
|
|
141
|
+
? 'graphify extracted a real project graph and answered a graph query'
|
|
142
|
+
: 'graphify generated an artifact but graph stats or query validation failed',
|
|
143
|
+
required: options.requireGraphify,
|
|
144
|
+
project: resolve(options.largeProject),
|
|
145
|
+
graphPath,
|
|
146
|
+
stats,
|
|
147
|
+
commands: [help, extract, query, benchmark, globalAdd].filter(Boolean),
|
|
148
|
+
nextCommands: passed ? [] : [`graphify query ${quoteArg(options.graphifyQuestion)} --graph ${quoteArg(graphPath)}`],
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function parseGraphStats(graphPath) {
|
|
153
|
+
try {
|
|
154
|
+
const graph = JSON.parse(readFileSync(graphPath, 'utf8'))
|
|
155
|
+
const nodes = firstArray(graph.nodes, graph.graph?.nodes, graph.data?.nodes)
|
|
156
|
+
const edges = firstArray(graph.edges, graph.links, graph.graph?.edges, graph.graph?.links, graph.data?.edges, graph.data?.links)
|
|
157
|
+
return {
|
|
158
|
+
ok: nodes.length > 0,
|
|
159
|
+
nodes: nodes.length,
|
|
160
|
+
edges: edges.length,
|
|
161
|
+
}
|
|
162
|
+
} catch (error) {
|
|
163
|
+
return {
|
|
164
|
+
ok: false,
|
|
165
|
+
nodes: 0,
|
|
166
|
+
edges: 0,
|
|
167
|
+
error: String(error.message ?? error),
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function firstArray(...values) {
|
|
173
|
+
return values.find(Array.isArray) ?? []
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function findGraphJson(root) {
|
|
177
|
+
const direct = [
|
|
178
|
+
join(root, 'graph.json'),
|
|
179
|
+
join(root, 'graphify-out', 'graph.json'),
|
|
180
|
+
join(root, 'graph', 'graph.json'),
|
|
181
|
+
].find(candidate => existsSync(candidate))
|
|
182
|
+
if (direct) return direct
|
|
183
|
+
|
|
184
|
+
const scan = runCommand('find-graph-json', process.platform === 'win32' ? 'powershell' : 'find', process.platform === 'win32'
|
|
185
|
+
? ['-NoProfile', '-Command', `Get-ChildItem -Path ${quoteArg(root)} -Recurse -Filter graph.json | Select-Object -First 1 -ExpandProperty FullName`]
|
|
186
|
+
: [root, '-name', 'graph.json', '-print', '-quit'], { timeoutMs: 30_000, wrapRtk: false })
|
|
187
|
+
const found = scan.stdout.trim().split(/\r?\n/).find(Boolean)
|
|
188
|
+
return found && existsSync(found) ? found : null
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function skipped(id) {
|
|
192
|
+
return capability(id, 'skipped', { reason: `${id} rehearsal was skipped`, required: false, commands: [], nextCommands: [] })
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function capability(id, status, details) {
|
|
196
|
+
return {
|
|
197
|
+
id,
|
|
198
|
+
status,
|
|
199
|
+
required: Boolean(details.required),
|
|
200
|
+
reason: details.reason,
|
|
201
|
+
nextCommands: details.nextCommands ?? [],
|
|
202
|
+
...Object.fromEntries(Object.entries(details).filter(([key]) => !['required', 'reason', 'nextCommands'].includes(key))),
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function buildReport(capabilities) {
|
|
207
|
+
const failedRequired = capabilities.filter(item => item.required && item.status !== 'passed' && item.status !== 'skipped')
|
|
208
|
+
return {
|
|
209
|
+
version: 1,
|
|
210
|
+
ok: failedRequired.length === 0,
|
|
211
|
+
status: failedRequired.length === 0 ? 'completed' : 'failed',
|
|
212
|
+
runId,
|
|
213
|
+
generatedAt: new Date().toISOString(),
|
|
214
|
+
repoRoot,
|
|
215
|
+
workRoot,
|
|
216
|
+
rtkWrapped: options.useRtk && commandExists('rtk'),
|
|
217
|
+
capabilities,
|
|
218
|
+
results,
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function writeReport(report) {
|
|
223
|
+
const target = options.reportFile
|
|
224
|
+
? resolve(options.reportFile)
|
|
225
|
+
: join(repoRoot, '.scale', 'reports', `${runId}.json`)
|
|
226
|
+
mkdirSync(dirname(target), { recursive: true })
|
|
227
|
+
report.reportFile = target
|
|
228
|
+
writeFileSync(target, `${JSON.stringify(report, null, 2)}\n`, 'utf8')
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function runCommand(name, command, args, opts = {}) {
|
|
232
|
+
const invocation = opts.wrapRtk === false
|
|
233
|
+
? { command, args, wrapped: false }
|
|
234
|
+
: wrapWithRtk(command, args)
|
|
235
|
+
const startedAt = new Date().toISOString()
|
|
236
|
+
const commandLine = formatCommand(invocation.command, invocation.args)
|
|
237
|
+
if (options.verbose) process.stderr.write(`[RUN] ${commandLine}\n`)
|
|
238
|
+
const result = spawnStructured(invocation.command, invocation.args, {
|
|
239
|
+
cwd: repoRoot,
|
|
240
|
+
env: process.env,
|
|
241
|
+
input: opts.input,
|
|
242
|
+
encoding: 'utf8',
|
|
243
|
+
timeout: opts.timeoutMs ?? options.timeoutMs,
|
|
244
|
+
maxBuffer: 80 * 1024 * 1024,
|
|
245
|
+
})
|
|
246
|
+
const stdout = String(result.stdout ?? '')
|
|
247
|
+
const stderr = String(result.stderr ?? '') + (result.error ? `\n${result.error.message}` : '')
|
|
248
|
+
const exitCode = typeof result.status === 'number' ? result.status : 1
|
|
249
|
+
const entry = {
|
|
250
|
+
name,
|
|
251
|
+
command: commandLine,
|
|
252
|
+
wrappedByRtk: invocation.wrapped,
|
|
253
|
+
exitCode,
|
|
254
|
+
startedAt,
|
|
255
|
+
endedAt: new Date().toISOString(),
|
|
256
|
+
stdoutTail: tail(stdout),
|
|
257
|
+
stderrTail: tail(stderr),
|
|
258
|
+
}
|
|
259
|
+
results.push(entry)
|
|
260
|
+
return { ...entry, stdout, stderr }
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function wrapWithRtk(command, args) {
|
|
264
|
+
if (!options.useRtk || command === 'rtk' || !commandExists('rtk')) return { command, args, wrapped: false }
|
|
265
|
+
return { command: 'rtk', args: [command, ...args], wrapped: true }
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function spawnStructured(command, args, options) {
|
|
269
|
+
const resolved = resolveWindowsCommandShim(resolveCommandPath(command) ?? command)
|
|
270
|
+
if (process.platform === 'win32' && /\.(cmd|bat)$/i.test(resolved)) {
|
|
271
|
+
const comspec = process.env.ComSpec || 'cmd.exe'
|
|
272
|
+
return spawnSync(comspec, ['/d', '/c', 'call', resolved, ...args], {
|
|
273
|
+
...options,
|
|
274
|
+
shell: false,
|
|
275
|
+
windowsHide: true,
|
|
276
|
+
})
|
|
277
|
+
}
|
|
278
|
+
return spawnSync(resolved, args, {
|
|
279
|
+
...options,
|
|
280
|
+
shell: false,
|
|
281
|
+
windowsHide: true,
|
|
282
|
+
})
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function resolveWindowsCommandShim(command) {
|
|
286
|
+
if (process.platform !== 'win32') return command
|
|
287
|
+
if (!/[\\/]/.test(command) || extname(command)) return command
|
|
288
|
+
for (const extension of ['.cmd', '.exe', '.bat', '.com']) {
|
|
289
|
+
const candidate = `${command}${extension}`
|
|
290
|
+
if (existsSync(candidate)) return candidate
|
|
291
|
+
}
|
|
292
|
+
return command
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function resolveCommandPath(command) {
|
|
296
|
+
if (/[\\/]/.test(command)) return command
|
|
297
|
+
const lookup = process.platform === 'win32' ? 'where.exe' : 'which'
|
|
298
|
+
const result = spawnSync(lookup, [command], {
|
|
299
|
+
encoding: 'utf8',
|
|
300
|
+
shell: false,
|
|
301
|
+
windowsHide: true,
|
|
302
|
+
})
|
|
303
|
+
if (result.status !== 0) return null
|
|
304
|
+
return String(result.stdout ?? '')
|
|
305
|
+
.split(/\r?\n/)
|
|
306
|
+
.map(line => line.trim())
|
|
307
|
+
.find(Boolean) ?? null
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function commandExists(command) {
|
|
311
|
+
return Boolean(resolveCommandPath(command))
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function parseArgs(args) {
|
|
315
|
+
const parsed = {
|
|
316
|
+
skipGbrain: false,
|
|
317
|
+
skipGraphify: false,
|
|
318
|
+
requireGbrain: false,
|
|
319
|
+
requireGraphify: false,
|
|
320
|
+
keepOutput: false,
|
|
321
|
+
keepGbrainPage: false,
|
|
322
|
+
verbose: false,
|
|
323
|
+
useRtk: true,
|
|
324
|
+
noCluster: true,
|
|
325
|
+
globalExtract: false,
|
|
326
|
+
globalAdd: false,
|
|
327
|
+
globalTag: 'scale-engine-rehearsal',
|
|
328
|
+
largeProject: repoRoot,
|
|
329
|
+
outDir: undefined,
|
|
330
|
+
reportFile: undefined,
|
|
331
|
+
writeReport: false,
|
|
332
|
+
graphifyBackend: undefined,
|
|
333
|
+
graphifyQuestion: 'Where are SCALE setup, memory provider, and graphify knowledge integration implemented?',
|
|
334
|
+
timeoutMs: 900_000,
|
|
335
|
+
}
|
|
336
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
337
|
+
const arg = args[index]
|
|
338
|
+
if (arg === '--skip-gbrain') parsed.skipGbrain = true
|
|
339
|
+
else if (arg === '--skip-graphify') parsed.skipGraphify = true
|
|
340
|
+
else if (arg === '--require-gbrain') parsed.requireGbrain = true
|
|
341
|
+
else if (arg === '--require-graphify') parsed.requireGraphify = true
|
|
342
|
+
else if (arg === '--keep-output') parsed.keepOutput = true
|
|
343
|
+
else if (arg === '--keep-gbrain-page') parsed.keepGbrainPage = true
|
|
344
|
+
else if (arg === '--verbose') parsed.verbose = true
|
|
345
|
+
else if (arg === '--no-rtk') parsed.useRtk = false
|
|
346
|
+
else if (arg === '--cluster') parsed.noCluster = false
|
|
347
|
+
else if (arg === '--global') parsed.globalExtract = true
|
|
348
|
+
else if (arg === '--global-add') parsed.globalAdd = true
|
|
349
|
+
else if (arg === '--global-tag') parsed.globalTag = requireValue(args, ++index, arg)
|
|
350
|
+
else if (arg === '--large-project') parsed.largeProject = requireValue(args, ++index, arg)
|
|
351
|
+
else if (arg === '--out') parsed.outDir = requireValue(args, ++index, arg)
|
|
352
|
+
else if (arg === '--report-file') parsed.reportFile = requireValue(args, ++index, arg)
|
|
353
|
+
else if (arg === '--write-report') parsed.writeReport = true
|
|
354
|
+
else if (arg === '--graphify-backend') parsed.graphifyBackend = requireValue(args, ++index, arg)
|
|
355
|
+
else if (arg === '--graphify-question') parsed.graphifyQuestion = requireValue(args, ++index, arg)
|
|
356
|
+
else if (arg === '--timeout-ms') parsed.timeoutMs = Number.parseInt(requireValue(args, ++index, arg), 10)
|
|
357
|
+
else if (arg === '--help' || arg === '-h') {
|
|
358
|
+
printHelp()
|
|
359
|
+
process.exit(0)
|
|
360
|
+
} else {
|
|
361
|
+
throw new Error(`Unknown argument: ${arg}`)
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
if (!Number.isFinite(parsed.timeoutMs) || parsed.timeoutMs <= 0) parsed.timeoutMs = 900_000
|
|
365
|
+
return parsed
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function requireValue(args, index, flag) {
|
|
369
|
+
const value = args[index]
|
|
370
|
+
if (!value) throw new Error(`${flag} requires a value`)
|
|
371
|
+
return value
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function printHelp() {
|
|
375
|
+
process.stdout.write(`Usage: node scripts/workflow/provider-rehearsal.mjs [options]
|
|
376
|
+
|
|
377
|
+
Runs real provider checks for:
|
|
378
|
+
- gbrain cross-process write/get/query replay
|
|
379
|
+
- graphify real-project extraction and graph query
|
|
380
|
+
|
|
381
|
+
Default mode records blocked capabilities without failing unless --require-gbrain or --require-graphify is set.
|
|
382
|
+
|
|
383
|
+
Options:
|
|
384
|
+
--require-gbrain Fail when gbrain is missing, unconfigured, or replay fails
|
|
385
|
+
--require-graphify Fail when graphify is missing or graph extraction/query fails
|
|
386
|
+
--skip-gbrain Skip gbrain replay
|
|
387
|
+
--skip-graphify Skip graphify rehearsal
|
|
388
|
+
--large-project PATH Project to extract with graphify, default current repo
|
|
389
|
+
--out PATH Output directory for temporary graphify artifacts
|
|
390
|
+
--keep-output Keep temporary output when --out is not supplied
|
|
391
|
+
--keep-gbrain-page Do not delete the temporary gbrain page
|
|
392
|
+
--graphify-backend ID Pass --backend to graphify extract
|
|
393
|
+
--graphify-question Q Query to ask graphify after extraction
|
|
394
|
+
--global Pass --global to graphify extract
|
|
395
|
+
--global-add Add generated graph to graphify global store
|
|
396
|
+
--global-tag TAG Tag for --global-add, default scale-engine-rehearsal
|
|
397
|
+
--write-report Write JSON report to .scale/reports
|
|
398
|
+
--report-file PATH Write JSON report to the supplied path
|
|
399
|
+
--timeout-ms N Command timeout, default 900000
|
|
400
|
+
--no-rtk Do not wrap provider CLIs through rtk
|
|
401
|
+
--verbose Print command lines to stderr
|
|
402
|
+
`)
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function failureLine(value) {
|
|
406
|
+
const lines = value.split(/\r?\n/).map(line => line.trim()).filter(Boolean)
|
|
407
|
+
return lines.find(line => /^error[:\s]/i.test(line))
|
|
408
|
+
?? lines.find(line => !/^warning[:\s]/i.test(line))
|
|
409
|
+
?? lines[0]
|
|
410
|
+
?? ''
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function formatCommand(command, args) {
|
|
414
|
+
return [command, ...args].map(quoteArg).join(' ')
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function quoteArg(value) {
|
|
418
|
+
const raw = String(value)
|
|
419
|
+
if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(raw)) return raw
|
|
420
|
+
return `"${raw.replace(/(["\\$`])/g, '\\$1')}"`
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function tail(value, max = 4000) {
|
|
424
|
+
return value.length > max ? value.slice(-max) : value
|
|
425
|
+
}
|