@hongmaple0820/scale-engine 0.38.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.md +15 -0
- package/dist/api/cli.js +142 -40
- package/dist/api/cli.js.map +1 -1
- package/dist/api/doctor.js +1 -1
- package/dist/api/doctor.js.map +1 -1
- package/dist/bootstrap/DependencyBootstrap.d.ts +22 -1
- package/dist/bootstrap/DependencyBootstrap.js +420 -32
- package/dist/bootstrap/DependencyBootstrap.js.map +1 -1
- 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/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.js +1 -1
- package/dist/codegraph/CodeIntelligence.js.map +1 -1
- package/dist/context/SessionStartSequence.js +13 -4
- package/dist/context/SessionStartSequence.js.map +1 -1
- package/dist/core/ExternalCommand.js +18 -4
- package/dist/core/ExternalCommand.js.map +1 -1
- 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 +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/memory/MemoryProviders.js +38 -5
- package/dist/memory/MemoryProviders.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/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 +2 -2
- package/dist/skills/SkillRepository.js.map +1 -1
- package/dist/testing/DiffTestSelector.js +1 -1
- package/dist/testing/DiffTestSelector.js.map +1 -1
- package/dist/tools/ToolCapabilityRegistry.d.ts +4 -0
- package/dist/tools/ToolCapabilityRegistry.js +11 -6
- package/dist/tools/ToolCapabilityRegistry.js.map +1 -1
- package/dist/workflow/CommitDiscipline.js +8 -7
- package/dist/workflow/CommitDiscipline.js.map +1 -1
- package/dist/workflow/CrossRepoOrchestrator.js +15 -7
- package/dist/workflow/CrossRepoOrchestrator.js.map +1 -1
- 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/GovernanceTemplatePacks.js +19 -4
- package/dist/workflow/GovernanceTemplatePacks.js.map +1 -1
- package/dist/workflow/SessionPreamble.js +7 -2
- package/dist/workflow/SessionPreamble.js.map +1 -1
- 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/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 +2 -2
- package/dist/workflow/gates/GateSystem.js.map +1 -1
- package/dist/workflow/index.d.ts +2 -0
- package/dist/workflow/index.js +2 -0
- package/dist/workflow/index.js.map +1 -1
- package/docs/CODE_INTELLIGENCE.md +27 -2
- package/docs/MEMORY_FABRIC.md +22 -1
- package/docs/THIRD_PARTY_SKILLS.md +50 -1
- package/docs/guides/GETTING_STARTED.md +24 -0
- package/docs/start/quickstart.md +103 -76
- 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/package.json +11 -4
- package/scripts/workflow/provider-rehearsal.mjs +425 -0
- package/scripts/workflow/setup-smoke.mjs +299 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from 'node:child_process'
|
|
3
|
+
import { existsSync, mkdirSync, rmSync } 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 smokeRoot = options.tempDir
|
|
12
|
+
? resolve(options.tempDir)
|
|
13
|
+
: mkSmokeRoot()
|
|
14
|
+
const projectDir = join(smokeRoot, 'project')
|
|
15
|
+
const scaleDir = join(smokeRoot, '.scale')
|
|
16
|
+
const scaleInvocation = parseCommandLine(options.scaleCommand ?? 'node --import tsx src/api/cli.ts')
|
|
17
|
+
const scaleCommand = formatCommand(scaleInvocation.file, scaleInvocation.args)
|
|
18
|
+
const results = []
|
|
19
|
+
|
|
20
|
+
mkdirSync(projectDir, { recursive: true })
|
|
21
|
+
mkdirSync(scaleDir, { recursive: true })
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
runSetupSmoke()
|
|
25
|
+
writeSummary('passed')
|
|
26
|
+
} catch (error) {
|
|
27
|
+
writeSummary('failed', error)
|
|
28
|
+
process.exitCode = 1
|
|
29
|
+
} finally {
|
|
30
|
+
if (!options.keepTemp && !options.tempDir) rmSync(smokeRoot, { recursive: true, force: true })
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function runSetupSmoke() {
|
|
34
|
+
const baseEnv = {
|
|
35
|
+
...process.env,
|
|
36
|
+
SCALE_DIR: scaleDir,
|
|
37
|
+
SCALE_PROJECT_DIR: projectDir,
|
|
38
|
+
SCALE_LOG_LEVEL: '',
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const zh = runCommand('bootstrap-ui-zh', ['bootstrap', 'deps', '--dir', projectDir, '--pack', 'ui', '--lang', 'zh'], baseEnv)
|
|
42
|
+
assertIncludes(zh.stdout, 'SCALE 依赖安装计划', 'Chinese bootstrap output should use Chinese title')
|
|
43
|
+
assertIncludes(zh.stdout, '运行时依赖:', 'Chinese bootstrap output should show runtime dependencies')
|
|
44
|
+
assertIncludes(zh.stdout, 'awesome-design-md', 'Chinese bootstrap output should include awesome-design-md')
|
|
45
|
+
assertIncludes(zh.stdout, 'ui-ux-pro-max', 'Chinese bootstrap output should include ui-ux-pro-max')
|
|
46
|
+
|
|
47
|
+
const en = runCommand('bootstrap-ui-en', ['bootstrap', 'deps', '--dir', projectDir, '--pack', 'ui', '--lang', 'en'], baseEnv)
|
|
48
|
+
assertIncludes(en.stdout, 'SCALE Dependency Bootstrap', 'English bootstrap output should use English title')
|
|
49
|
+
assertIncludes(en.stdout, 'Runtime dependencies:', 'English bootstrap output should show runtime dependencies')
|
|
50
|
+
|
|
51
|
+
const deps = runJson('bootstrap-external-memory-knowledge-json', [
|
|
52
|
+
'bootstrap',
|
|
53
|
+
'deps',
|
|
54
|
+
'--dir',
|
|
55
|
+
projectDir,
|
|
56
|
+
'--pack',
|
|
57
|
+
'external-cli,memory,knowledge',
|
|
58
|
+
'--json',
|
|
59
|
+
], baseEnv)
|
|
60
|
+
assertArrayContains(deps.runtimeChecks?.map(check => check.id), ['node', 'npm', 'cargo', 'bun', 'python', 'python-installer'], 'bootstrap deps should report all runtime dependency checks')
|
|
61
|
+
assertArrayContains(deps.items?.map(item => item.id), ['rtk', 'gbrain', 'graphify', 'codegraph'], 'bootstrap deps should include governed third-party capabilities')
|
|
62
|
+
assert(deps.apply === false, 'bootstrap smoke must not run installers')
|
|
63
|
+
|
|
64
|
+
const envDoctor = runJson('doctor-env-json', ['doctor', 'env', '--json'], baseEnv)
|
|
65
|
+
assert(envDoctor.ok === true, 'environment doctor should pass when required core commands are available')
|
|
66
|
+
assertArrayContains(envDoctor.checks?.map(check => check.id), ['git', 'npm', 'npx', 'rtk', 'gbrain', 'graphify', 'codegraph'], 'environment doctor should report core and third-party commands')
|
|
67
|
+
|
|
68
|
+
const localMemory = runJson('setup-memory-scale-local-json', [
|
|
69
|
+
'setup',
|
|
70
|
+
'--dir',
|
|
71
|
+
projectDir,
|
|
72
|
+
'--pack',
|
|
73
|
+
'memory',
|
|
74
|
+
'--memory-provider',
|
|
75
|
+
'scale-local',
|
|
76
|
+
'--json',
|
|
77
|
+
], baseEnv)
|
|
78
|
+
assert(localMemory.memoryProviderSwitch?.provider === 'scale-local', 'setup should switch to scale-local provider')
|
|
79
|
+
assert(localMemory.memoryProviderSwitch?.mode === 'local-only', 'scale-local provider should force local-only mode')
|
|
80
|
+
assert(localMemory.memoryProviderSwitch?.nextOrder?.[0] === 'scale-local', 'scale-local should become the first provider')
|
|
81
|
+
assert(existsSync(localMemory.memoryProviderSwitch?.path ?? ''), 'setup should write memory provider config')
|
|
82
|
+
assert(localMemory.final?.runtimeChecks?.some(check => check.id === 'bun'), 'memory setup should still expose Bun runtime check for gbrain')
|
|
83
|
+
|
|
84
|
+
const gbrainMemory = runJson('setup-memory-gbrain-json', [
|
|
85
|
+
'setup',
|
|
86
|
+
'--dir',
|
|
87
|
+
projectDir,
|
|
88
|
+
'--pack',
|
|
89
|
+
'memory',
|
|
90
|
+
'--memory-provider',
|
|
91
|
+
'gbrain',
|
|
92
|
+
'--memory-mode',
|
|
93
|
+
'external-first',
|
|
94
|
+
'--json',
|
|
95
|
+
], baseEnv)
|
|
96
|
+
assert(gbrainMemory.memoryProviderSwitch?.provider === 'gbrain', 'setup should switch to gbrain provider')
|
|
97
|
+
assert(gbrainMemory.memoryProviderSwitch?.mode === 'external-first', 'gbrain provider should support external-first mode')
|
|
98
|
+
assert(gbrainMemory.memoryProviderSwitch?.nextOrder?.[0] === 'gbrain', 'gbrain should become the first provider')
|
|
99
|
+
|
|
100
|
+
const codegraph = runJson('codegraph-status-json', ['codegraph', 'status', '--dir', repoRoot, '--json'], baseEnv)
|
|
101
|
+
assertArrayContains(codegraph.providers?.map(provider => provider.id), ['codegraph', 'graphify'], 'codegraph status should expose CodeGraph and Graphify providers')
|
|
102
|
+
assert(typeof codegraph.projectIndexExists === 'boolean', 'codegraph status should report project index state')
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function runJson(name, args, env) {
|
|
106
|
+
const result = runCommand(name, args, env)
|
|
107
|
+
try {
|
|
108
|
+
return JSON.parse(result.stdout)
|
|
109
|
+
} catch (error) {
|
|
110
|
+
throw new Error(`${name} did not return valid JSON: ${error.message}\n${result.stdout}`)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function runCommand(name, args, env) {
|
|
115
|
+
const commandArgs = [...scaleInvocation.args, ...args]
|
|
116
|
+
const commandLine = formatCommand(scaleInvocation.file, commandArgs)
|
|
117
|
+
if (options.verbose) process.stdout.write(`[RUN] ${commandLine}\n`)
|
|
118
|
+
const startedAt = new Date().toISOString()
|
|
119
|
+
const result = spawnStructured(scaleInvocation.file, commandArgs, {
|
|
120
|
+
cwd: repoRoot,
|
|
121
|
+
env,
|
|
122
|
+
encoding: 'utf8',
|
|
123
|
+
timeout: options.timeoutMs,
|
|
124
|
+
maxBuffer: 20 * 1024 * 1024,
|
|
125
|
+
})
|
|
126
|
+
const stdout = String(result.stdout ?? '')
|
|
127
|
+
const stderr = String(result.stderr ?? '')
|
|
128
|
+
const exitCode = typeof result.status === 'number' ? result.status : 1
|
|
129
|
+
const entry = {
|
|
130
|
+
name,
|
|
131
|
+
command: commandLine,
|
|
132
|
+
exitCode,
|
|
133
|
+
startedAt,
|
|
134
|
+
endedAt: new Date().toISOString(),
|
|
135
|
+
stdoutTail: tail(stdout),
|
|
136
|
+
stderrTail: tail(stderr + (result.error ? `\n${result.error.message}` : '')),
|
|
137
|
+
}
|
|
138
|
+
results.push(entry)
|
|
139
|
+
if (exitCode !== 0) {
|
|
140
|
+
throw new Error(`${name} failed with exit code ${exitCode}\n${entry.stderrTail || entry.stdoutTail}`)
|
|
141
|
+
}
|
|
142
|
+
return { stdout, stderr, exitCode }
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function spawnStructured(command, args, options) {
|
|
146
|
+
const resolved = resolveWindowsCommandShim(resolveCommandPath(command) ?? command)
|
|
147
|
+
if (process.platform === 'win32' && /\.(cmd|bat)$/i.test(resolved)) {
|
|
148
|
+
const comspec = process.env.ComSpec || 'cmd.exe'
|
|
149
|
+
return spawnSync(comspec, ['/d', '/c', 'call', resolved, ...args], {
|
|
150
|
+
...options,
|
|
151
|
+
shell: false,
|
|
152
|
+
windowsHide: true,
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
return spawnSync(resolved, args, {
|
|
156
|
+
...options,
|
|
157
|
+
shell: false,
|
|
158
|
+
windowsHide: true,
|
|
159
|
+
})
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function resolveWindowsCommandShim(command) {
|
|
163
|
+
if (process.platform !== 'win32') return command
|
|
164
|
+
if (!/[\\/]/.test(command) || extname(command)) return command
|
|
165
|
+
for (const extension of ['.cmd', '.exe', '.bat', '.com']) {
|
|
166
|
+
const candidate = `${command}${extension}`
|
|
167
|
+
if (existsSync(candidate)) return candidate
|
|
168
|
+
}
|
|
169
|
+
return command
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function resolveCommandPath(command) {
|
|
173
|
+
if (/[\\/]/.test(command)) return command
|
|
174
|
+
const lookup = process.platform === 'win32' ? 'where.exe' : 'which'
|
|
175
|
+
const result = spawnSync(lookup, [command], {
|
|
176
|
+
encoding: 'utf8',
|
|
177
|
+
shell: false,
|
|
178
|
+
windowsHide: true,
|
|
179
|
+
})
|
|
180
|
+
if (result.status !== 0) return null
|
|
181
|
+
return String(result.stdout ?? '')
|
|
182
|
+
.split(/\r?\n/)
|
|
183
|
+
.map(line => line.trim())
|
|
184
|
+
.find(Boolean) ?? null
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function parseArgs(args) {
|
|
188
|
+
const parsed = {
|
|
189
|
+
keepTemp: false,
|
|
190
|
+
verbose: false,
|
|
191
|
+
timeoutMs: 120_000,
|
|
192
|
+
scaleCommand: undefined,
|
|
193
|
+
tempDir: undefined,
|
|
194
|
+
}
|
|
195
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
196
|
+
const arg = args[index]
|
|
197
|
+
if (arg === '--keep-temp') parsed.keepTemp = true
|
|
198
|
+
else if (arg === '--verbose') parsed.verbose = true
|
|
199
|
+
else if (arg === '--scale-command') parsed.scaleCommand = args[++index]
|
|
200
|
+
else if (arg === '--temp-dir') parsed.tempDir = args[++index]
|
|
201
|
+
else if (arg === '--timeout-ms') parsed.timeoutMs = Number.parseInt(args[++index] ?? '', 10)
|
|
202
|
+
else if (arg === '--help' || arg === '-h') {
|
|
203
|
+
process.stdout.write(`Usage: node scripts/workflow/setup-smoke.mjs [--scale-command "scale"] [--keep-temp] [--verbose]\n`)
|
|
204
|
+
process.exit(0)
|
|
205
|
+
} else {
|
|
206
|
+
throw new Error(`Unknown argument: ${arg}`)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (!Number.isFinite(parsed.timeoutMs) || parsed.timeoutMs <= 0) parsed.timeoutMs = 120_000
|
|
210
|
+
return parsed
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function mkSmokeRoot() {
|
|
214
|
+
return join(tmpdir(), `scale-setup-smoke-${Date.now()}-${Math.random().toString(16).slice(2)}`)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function parseCommandLine(command) {
|
|
218
|
+
const tokens = []
|
|
219
|
+
let current = ''
|
|
220
|
+
let quote = null
|
|
221
|
+
|
|
222
|
+
for (let index = 0; index < command.length; index += 1) {
|
|
223
|
+
const char = command[index]
|
|
224
|
+
if (quote) {
|
|
225
|
+
if (char === quote) {
|
|
226
|
+
quote = null
|
|
227
|
+
} else if (quote === '"' && char === '\\' && index + 1 < command.length) {
|
|
228
|
+
index += 1
|
|
229
|
+
current += command[index]
|
|
230
|
+
} else {
|
|
231
|
+
current += char
|
|
232
|
+
}
|
|
233
|
+
continue
|
|
234
|
+
}
|
|
235
|
+
if (char === '"' || char === "'") {
|
|
236
|
+
quote = char
|
|
237
|
+
continue
|
|
238
|
+
}
|
|
239
|
+
if (/\s/.test(char)) {
|
|
240
|
+
if (current) {
|
|
241
|
+
tokens.push(current)
|
|
242
|
+
current = ''
|
|
243
|
+
}
|
|
244
|
+
continue
|
|
245
|
+
}
|
|
246
|
+
if (char === '\\' && index + 1 < command.length) {
|
|
247
|
+
index += 1
|
|
248
|
+
current += command[index]
|
|
249
|
+
continue
|
|
250
|
+
}
|
|
251
|
+
current += char
|
|
252
|
+
}
|
|
253
|
+
if (quote) throw new Error(`Unterminated quote in scale command: ${command}`)
|
|
254
|
+
if (current) tokens.push(current)
|
|
255
|
+
if (tokens.length === 0) throw new Error('Scale command is empty')
|
|
256
|
+
return { file: tokens[0], args: tokens.slice(1) }
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function formatCommand(command, args) {
|
|
260
|
+
return [command, ...args].map(quoteArg).join(' ')
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function quoteArg(value) {
|
|
264
|
+
const raw = String(value)
|
|
265
|
+
if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(raw)) return raw
|
|
266
|
+
return `"${raw.replace(/(["\\$`])/g, '\\$1')}"`
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function assert(condition, message) {
|
|
270
|
+
if (!condition) throw new Error(message)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function assertIncludes(value, expected, message) {
|
|
274
|
+
if (!String(value).includes(expected)) throw new Error(`${message}; missing ${expected}`)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function assertArrayContains(actual, expected, message) {
|
|
278
|
+
const values = new Set(Array.isArray(actual) ? actual : [])
|
|
279
|
+
const missing = expected.filter(value => !values.has(value))
|
|
280
|
+
if (missing.length > 0) throw new Error(`${message}; missing: ${missing.join(', ')}`)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function writeSummary(status, error) {
|
|
284
|
+
const report = {
|
|
285
|
+
version: 1,
|
|
286
|
+
status,
|
|
287
|
+
scaleCommand,
|
|
288
|
+
repoRoot,
|
|
289
|
+
projectDir,
|
|
290
|
+
scaleDir,
|
|
291
|
+
results,
|
|
292
|
+
error: error ? String(error.stack ?? error.message ?? error) : undefined,
|
|
293
|
+
}
|
|
294
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function tail(value, max = 4000) {
|
|
298
|
+
return value.length > max ? value.slice(-max) : value
|
|
299
|
+
}
|