@hongmaple0820/scale-engine 0.38.0 → 0.40.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 +427 -33
- 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.d.ts +8 -0
- package/dist/memory/MemoryProviders.js +217 -16
- 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 +12 -5
- package/scripts/workflow/provider-rehearsal.mjs +516 -0
- package/scripts/workflow/setup-smoke.mjs +299 -0
|
@@ -1,23 +1,29 @@
|
|
|
1
|
-
import { existsSync
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { join, resolve } from 'node:path';
|
|
4
|
-
import { execa } from 'execa';
|
|
4
|
+
import { execa, execaSync } from 'execa';
|
|
5
5
|
import { inspectCodeIntelligence, writeCodeIntelligenceConfig } from '../codegraph/CodeIntelligence.js';
|
|
6
6
|
import { externalCommandExists } from '../core/ExternalCommand.js';
|
|
7
|
-
import { inspectMemoryProviders, useMemoryProvider, writeMemoryProvidersConfig } from '../memory/MemoryProviders.js';
|
|
7
|
+
import { inspectGbrainCliHealth, inspectMemoryProviders, useMemoryProvider, writeMemoryProvidersConfig } from '../memory/MemoryProviders.js';
|
|
8
8
|
import { wrapShellCommandWithRtk } from '../tools/RtkRuntime.js';
|
|
9
9
|
import { inspectToolCapabilities } from '../tools/ToolCapabilityRegistry.js';
|
|
10
10
|
const UI_SKILL_INSTALLS = {
|
|
11
|
-
'awesome-design-md':
|
|
12
|
-
|
|
11
|
+
'awesome-design-md': (ctx) => {
|
|
12
|
+
const installDir = quotePath(join(ctx.homeDir, '.scale', 'vendor', 'awesome-design-md'));
|
|
13
|
+
return `npx degit VoltAgent/awesome-design-md ${installDir}`;
|
|
14
|
+
},
|
|
15
|
+
'ui-ux-pro-max': 'npx uipro-cli init --ai codex',
|
|
13
16
|
'frontend-design': 'npx skills add anthropics/skills --skill frontend-design',
|
|
14
17
|
};
|
|
15
18
|
const RTK_INSTALL = 'cargo install --git https://github.com/rtk-ai/rtk';
|
|
16
19
|
const CODEGRAPH_INSTALL = 'npm install -g @colbymchenry/codegraph';
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
const
|
|
20
|
+
const GRAPHIFY_UV_INSTALL = 'uv tool install graphify && graphify install --platform codex';
|
|
21
|
+
const GRAPHIFY_PIPX_INSTALL = 'pipx install graphify && graphify install --platform codex';
|
|
22
|
+
const GRAPHIFY_PIP_INSTALL = 'pip install graphify && graphify install --platform codex';
|
|
23
|
+
const GRAPHIFY_PIP3_INSTALL = 'pip3 install graphify && graphify install --platform codex';
|
|
24
|
+
const GRAPHIFY_PYTHON_INSTALL = 'python -m pip install graphify && graphify install --platform codex';
|
|
25
|
+
const GRAPHIFY_PYTHON3_INSTALL = 'python3 -m pip install graphify && graphify install --platform codex';
|
|
26
|
+
const GBRAIN_INSTALL = 'bun install -g github:garrytan/gbrain && gbrain init --pglite';
|
|
21
27
|
const GBRAIN_SOURCE = 'https://github.com/garrytan/gbrain';
|
|
22
28
|
const DEPENDENCY_BOOTSTRAP_DEFINITIONS = [
|
|
23
29
|
{
|
|
@@ -27,9 +33,10 @@ const DEPENDENCY_BOOTSTRAP_DEFINITIONS = [
|
|
|
27
33
|
packs: ['ui'],
|
|
28
34
|
source: 'https://github.com/VoltAgent/awesome-design-md',
|
|
29
35
|
detectSkillId: 'awesome-design-md',
|
|
36
|
+
detectPaths: ctx => [join(ctx.homeDir, '.scale', 'vendor', 'awesome-design-md', 'README.md')],
|
|
30
37
|
prerequisites: ['npx'],
|
|
31
|
-
manualReason: 'Requires npm/npx
|
|
32
|
-
installCommand: ctx => ctx.commandExists('npx') ? UI_SKILL_INSTALLS['awesome-design-md'] : null,
|
|
38
|
+
manualReason: 'Requires npm/npx to sync the upstream DESIGN.md catalog that drives brand and visual-language decisions.',
|
|
39
|
+
installCommand: ctx => ctx.commandExists('npx') ? UI_SKILL_INSTALLS['awesome-design-md'](ctx) : null,
|
|
33
40
|
},
|
|
34
41
|
{
|
|
35
42
|
id: 'ui-ux-pro-max',
|
|
@@ -39,18 +46,18 @@ const DEPENDENCY_BOOTSTRAP_DEFINITIONS = [
|
|
|
39
46
|
source: 'https://github.com/nextlevelbuilder/ui-ux-pro-max-skill',
|
|
40
47
|
detectSkillId: 'ui-ux-pro-max',
|
|
41
48
|
prerequisites: ['npx'],
|
|
42
|
-
manualReason: 'Requires npm/npx
|
|
49
|
+
manualReason: 'Requires npm/npx so the official uipro-cli installer can configure the UX, state, accessibility, and responsive-review skill.',
|
|
43
50
|
installCommand: ctx => ctx.commandExists('npx') ? UI_SKILL_INSTALLS['ui-ux-pro-max'] : null,
|
|
44
51
|
},
|
|
45
52
|
{
|
|
46
53
|
id: 'frontend-design',
|
|
47
54
|
name: 'Frontend Design',
|
|
48
55
|
kind: 'skill',
|
|
49
|
-
packs: [
|
|
56
|
+
packs: [],
|
|
50
57
|
source: 'https://github.com/anthropics/skills/tree/main/skills/frontend-design',
|
|
51
58
|
detectSkillId: 'frontend-design',
|
|
52
59
|
prerequisites: ['npx'],
|
|
53
|
-
manualReason: '
|
|
60
|
+
manualReason: 'Optional implementation companion only; awesome-design-md and ui-ux-pro-max are the default UI stack.',
|
|
54
61
|
installCommand: ctx => ctx.commandExists('npx') ? UI_SKILL_INSTALLS['frontend-design'] : null,
|
|
55
62
|
},
|
|
56
63
|
{
|
|
@@ -63,6 +70,7 @@ const DEPENDENCY_BOOTSTRAP_DEFINITIONS = [
|
|
|
63
70
|
prerequisites: ['cargo'],
|
|
64
71
|
manualReason: 'RTK currently installs from the official Rust toolchain path and needs Cargo on PATH.',
|
|
65
72
|
installCommand: ctx => ctx.commandExists('cargo') ? RTK_INSTALL : null,
|
|
73
|
+
healthCheck: checkRtkHealth,
|
|
66
74
|
},
|
|
67
75
|
{
|
|
68
76
|
id: 'gbrain',
|
|
@@ -71,9 +79,10 @@ const DEPENDENCY_BOOTSTRAP_DEFINITIONS = [
|
|
|
71
79
|
packs: ['memory'],
|
|
72
80
|
source: GBRAIN_SOURCE,
|
|
73
81
|
detectCommand: 'gbrain',
|
|
74
|
-
prerequisites: ['
|
|
75
|
-
manualReason: 'The official standalone GBrain install
|
|
82
|
+
prerequisites: ['bun'],
|
|
83
|
+
manualReason: 'The official standalone GBrain install needs Bun and then a configured brain (`gbrain init --pglite`) before cross-session recall is usable.',
|
|
76
84
|
installCommand: ctx => buildGbrainInstallCommand(ctx),
|
|
85
|
+
healthCheck: checkGbrainHealth,
|
|
77
86
|
},
|
|
78
87
|
{
|
|
79
88
|
id: 'graphify',
|
|
@@ -82,9 +91,10 @@ const DEPENDENCY_BOOTSTRAP_DEFINITIONS = [
|
|
|
82
91
|
packs: ['knowledge'],
|
|
83
92
|
source: 'https://github.com/safishamsi/graphify',
|
|
84
93
|
detectCommand: 'graphify',
|
|
85
|
-
prerequisites: ['pip|pip3|python|python3'],
|
|
86
|
-
manualReason: 'Graphify requires Python 3.10+ and a
|
|
94
|
+
prerequisites: ['uv|pipx|pip|pip3|python|python3'],
|
|
95
|
+
manualReason: 'Graphify requires Python 3.10+ and a supported installer; uv tool install is preferred to avoid polluting project virtualenvs.',
|
|
87
96
|
installCommand: ctx => buildGraphifyInstallCommand(ctx),
|
|
97
|
+
healthCheck: checkGraphifyHealth,
|
|
88
98
|
},
|
|
89
99
|
{
|
|
90
100
|
id: 'codegraph',
|
|
@@ -96,6 +106,7 @@ const DEPENDENCY_BOOTSTRAP_DEFINITIONS = [
|
|
|
96
106
|
prerequisites: ['npm'],
|
|
97
107
|
manualReason: 'CodeGraph installs through npm and needs Node.js/npm available on PATH.',
|
|
98
108
|
installCommand: ctx => ctx.commandExists('npm') ? CODEGRAPH_INSTALL : null,
|
|
109
|
+
healthCheck: checkCodeGraphHealth,
|
|
99
110
|
},
|
|
100
111
|
];
|
|
101
112
|
export async function bootstrapDependencies(options = {}) {
|
|
@@ -111,6 +122,7 @@ export async function bootstrapDependencies(options = {}) {
|
|
|
111
122
|
commandExists: externalCommandExists,
|
|
112
123
|
};
|
|
113
124
|
const reports = definitions.map(definition => inspectDefinition(definition, context));
|
|
125
|
+
const runtimeChecks = buildRuntimeChecks(reports);
|
|
114
126
|
if (options.apply) {
|
|
115
127
|
for (const item of reports.filter(entry => entry.status === 'ready')) {
|
|
116
128
|
if (!item.installCommand)
|
|
@@ -122,7 +134,10 @@ export async function bootstrapDependencies(options = {}) {
|
|
|
122
134
|
const rechecked = definition ? inspectDefinition(definition, context) : item;
|
|
123
135
|
item.installed = rechecked.installed;
|
|
124
136
|
item.detectedBy = rechecked.detectedBy;
|
|
125
|
-
item.
|
|
137
|
+
item.health = rechecked.health;
|
|
138
|
+
item.status = result.ok && rechecked.installed
|
|
139
|
+
? rechecked.status === 'installed' ? 'installed-now' : rechecked.status
|
|
140
|
+
: 'failed';
|
|
126
141
|
if (!result.ok && !item.error)
|
|
127
142
|
item.error = `Installation command failed: ${item.installCommand}`;
|
|
128
143
|
if (!result.ok)
|
|
@@ -133,14 +148,16 @@ export async function bootstrapDependencies(options = {}) {
|
|
|
133
148
|
const postChecks = options.apply
|
|
134
149
|
? runDependencyBootstrapPostChecks({ projectDir, scaleDir, packIds, items: reports, homeDir })
|
|
135
150
|
: [];
|
|
136
|
-
return buildReport(projectDir, scaleDir, packIds, includeIds, Boolean(options.apply), reports, postActions, postChecks);
|
|
151
|
+
return buildReport(projectDir, scaleDir, packIds, includeIds, Boolean(options.apply), runtimeChecks, reports, postActions, postChecks);
|
|
137
152
|
}
|
|
138
|
-
function buildReport(projectDir, scaleDir, packIds, includeIds, apply, items, postActions, postChecks) {
|
|
153
|
+
function buildReport(projectDir, scaleDir, packIds, includeIds, apply, runtimeChecks, items, postActions, postChecks) {
|
|
139
154
|
const summary = {
|
|
140
155
|
total: items.length,
|
|
141
156
|
installed: items.filter(item => item.status === 'installed').length,
|
|
142
157
|
ready: items.filter(item => item.status === 'ready').length,
|
|
143
158
|
manualReview: items.filter(item => item.status === 'manual-review').length,
|
|
159
|
+
needsInit: items.filter(item => item.status === 'needs-init').length,
|
|
160
|
+
versionDrift: items.filter(item => item.status === 'version-drift').length,
|
|
144
161
|
installedNow: items.filter(item => item.status === 'installed-now').length,
|
|
145
162
|
failed: items.filter(item => item.status === 'failed').length,
|
|
146
163
|
};
|
|
@@ -154,10 +171,24 @@ function buildReport(projectDir, scaleDir, packIds, includeIds, apply, items, po
|
|
|
154
171
|
const recommendations = [];
|
|
155
172
|
const postCheckCommands = buildPostCheckCommands(packIds, items);
|
|
156
173
|
const rollbackHints = buildRollbackHints(items);
|
|
157
|
-
if (!apply &&
|
|
174
|
+
if (!apply && summary.ready > 0)
|
|
158
175
|
recommendations.push('Re-run with --apply to install all ready dependencies in one pass.');
|
|
176
|
+
if (runtimeChecks.some(check => check.status === 'missing'))
|
|
177
|
+
recommendations.push('Install missing runtime dependencies before running --apply.');
|
|
178
|
+
if (runtimeChecks.some(check => check.status === 'warn'))
|
|
179
|
+
recommendations.push('Review runtime warnings; some tools may install but fail initialization or post-checks.');
|
|
159
180
|
if (summary.manualReview > 0)
|
|
160
181
|
recommendations.push('Review manual-review items for missing package managers, PATH setup, or platform-specific configuration.');
|
|
182
|
+
if (summary.needsInit > 0)
|
|
183
|
+
recommendations.push('Run the listed initialization commands before treating installed CLIs as ready for autonomous use.');
|
|
184
|
+
if (summary.versionDrift > 0)
|
|
185
|
+
recommendations.push('Resolve version-drift items before relying on their generated skills, hooks, or artifacts.');
|
|
186
|
+
if (items.some(item => item.id === 'frontend-design'))
|
|
187
|
+
recommendations.push('frontend-design is optional; keep it as an explicit companion only when implementation ideation is needed.');
|
|
188
|
+
if (items.some(item => item.id === 'awesome-design-md'))
|
|
189
|
+
recommendations.push('Use awesome-design-md as the source of DESIGN.md, brand direction, and visual-language selection.');
|
|
190
|
+
if (items.some(item => item.id === 'ui-ux-pro-max'))
|
|
191
|
+
recommendations.push('Use ui-ux-pro-max for UX flow, UI state, accessibility, and responsive acceptance checks.');
|
|
161
192
|
if (items.some(item => item.id === 'gbrain'))
|
|
162
193
|
recommendations.push('After GBrain is installed, validate remote or thin-client health with `scale memory provider status --json`.');
|
|
163
194
|
if (items.some(item => item.id === 'graphify' || item.id === 'codegraph'))
|
|
@@ -174,6 +205,7 @@ function buildReport(projectDir, scaleDir, packIds, includeIds, apply, items, po
|
|
|
174
205
|
packIds,
|
|
175
206
|
includeIds,
|
|
176
207
|
apply,
|
|
208
|
+
runtimeChecks,
|
|
177
209
|
items,
|
|
178
210
|
summary,
|
|
179
211
|
postActions,
|
|
@@ -184,6 +216,247 @@ function buildReport(projectDir, scaleDir, packIds, includeIds, apply, items, po
|
|
|
184
216
|
recommendations,
|
|
185
217
|
};
|
|
186
218
|
}
|
|
219
|
+
function buildRuntimeChecks(items) {
|
|
220
|
+
const ids = new Set(items.map(item => item.id));
|
|
221
|
+
const requirements = new Map();
|
|
222
|
+
const requireRuntime = (runtimeId, requiredFor) => {
|
|
223
|
+
const existing = requirements.get(runtimeId) ?? new Set();
|
|
224
|
+
for (const value of requiredFor)
|
|
225
|
+
existing.add(value);
|
|
226
|
+
requirements.set(runtimeId, existing);
|
|
227
|
+
};
|
|
228
|
+
const uiIds = ['awesome-design-md', 'ui-ux-pro-max', 'frontend-design'].filter(id => ids.has(id));
|
|
229
|
+
if (uiIds.length > 0) {
|
|
230
|
+
requireRuntime('node', uiIds);
|
|
231
|
+
requireRuntime('npx', uiIds);
|
|
232
|
+
}
|
|
233
|
+
if (ids.has('rtk'))
|
|
234
|
+
requireRuntime('cargo', ['rtk']);
|
|
235
|
+
if (ids.has('gbrain'))
|
|
236
|
+
requireRuntime('bun', ['gbrain']);
|
|
237
|
+
if (ids.has('graphify')) {
|
|
238
|
+
requireRuntime('python', ['graphify']);
|
|
239
|
+
requireRuntime('python-installer', ['graphify']);
|
|
240
|
+
}
|
|
241
|
+
if (ids.has('codegraph')) {
|
|
242
|
+
requireRuntime('node', ['codegraph']);
|
|
243
|
+
requireRuntime('npm', ['codegraph']);
|
|
244
|
+
}
|
|
245
|
+
const checks = [];
|
|
246
|
+
const requiredFor = (runtimeId) => [...(requirements.get(runtimeId) ?? [])];
|
|
247
|
+
if (requirements.has('node'))
|
|
248
|
+
checks.push(nodeRuntimeCheck(requiredFor('node')));
|
|
249
|
+
if (requirements.has('npx'))
|
|
250
|
+
checks.push(commandRuntimeCheck({
|
|
251
|
+
id: 'npx',
|
|
252
|
+
label: 'npx',
|
|
253
|
+
candidates: [{ command: 'npx', args: ['--version'], display: 'npx' }],
|
|
254
|
+
requiredFor: requiredFor('npx'),
|
|
255
|
+
installHint: 'Install Node.js 20+; npm/npx are bundled with the official Node.js installer.',
|
|
256
|
+
}));
|
|
257
|
+
if (requirements.has('npm'))
|
|
258
|
+
checks.push(commandRuntimeCheck({
|
|
259
|
+
id: 'npm',
|
|
260
|
+
label: 'npm',
|
|
261
|
+
candidates: [{ command: 'npm', args: ['--version'], display: 'npm' }],
|
|
262
|
+
requiredFor: requiredFor('npm'),
|
|
263
|
+
installHint: 'Install Node.js 20+; npm is bundled with the official Node.js installer.',
|
|
264
|
+
}));
|
|
265
|
+
if (requirements.has('cargo'))
|
|
266
|
+
checks.push(commandRuntimeCheck({
|
|
267
|
+
id: 'cargo',
|
|
268
|
+
label: 'Rust/Cargo',
|
|
269
|
+
candidates: [{ command: 'cargo', args: ['--version'], display: 'cargo' }],
|
|
270
|
+
requiredFor: requiredFor('cargo'),
|
|
271
|
+
installHint: 'Install Rust with rustup, then re-open the shell so cargo is on PATH.',
|
|
272
|
+
}));
|
|
273
|
+
if (requirements.has('bun'))
|
|
274
|
+
checks.push(commandRuntimeCheck({
|
|
275
|
+
id: 'bun',
|
|
276
|
+
label: 'Bun',
|
|
277
|
+
candidates: [{ command: 'bun', args: ['--version'], display: 'bun' }],
|
|
278
|
+
requiredFor: requiredFor('bun'),
|
|
279
|
+
installHint: 'Install Bun from https://bun.sh and re-open the shell so bun is on PATH.',
|
|
280
|
+
}));
|
|
281
|
+
if (requirements.has('python'))
|
|
282
|
+
checks.push(pythonRuntimeCheck(requiredFor('python')));
|
|
283
|
+
if (requirements.has('python-installer'))
|
|
284
|
+
checks.push(pythonInstallerRuntimeCheck(requiredFor('python-installer')));
|
|
285
|
+
return checks;
|
|
286
|
+
}
|
|
287
|
+
function commandRuntimeCheck(input) {
|
|
288
|
+
const detected = firstAvailableRuntimeTool(input.candidates);
|
|
289
|
+
if (!detected) {
|
|
290
|
+
return {
|
|
291
|
+
id: input.id,
|
|
292
|
+
label: input.label,
|
|
293
|
+
commands: input.candidates.map(candidate => candidate.display),
|
|
294
|
+
status: 'missing',
|
|
295
|
+
requiredFor: input.requiredFor,
|
|
296
|
+
reason: `${input.label} was not detected on PATH.`,
|
|
297
|
+
installHint: input.installHint,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
return {
|
|
301
|
+
id: input.id,
|
|
302
|
+
label: input.label,
|
|
303
|
+
commands: input.candidates.map(candidate => candidate.display),
|
|
304
|
+
status: 'ok',
|
|
305
|
+
requiredFor: input.requiredFor,
|
|
306
|
+
detectedCommand: detected.display,
|
|
307
|
+
version: firstLine(detected.output),
|
|
308
|
+
reason: `${input.label} is available via ${detected.display}.`,
|
|
309
|
+
installHint: input.installHint,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
function nodeRuntimeCheck(requiredFor) {
|
|
313
|
+
const detected = firstAvailableRuntimeTool([{ command: 'node', args: ['--version'], display: 'node' }]);
|
|
314
|
+
const commands = ['node'];
|
|
315
|
+
const installHint = 'Install Node.js 20+ from https://nodejs.org.';
|
|
316
|
+
if (!detected) {
|
|
317
|
+
return {
|
|
318
|
+
id: 'node',
|
|
319
|
+
label: 'Node.js',
|
|
320
|
+
commands,
|
|
321
|
+
status: 'missing',
|
|
322
|
+
requiredFor,
|
|
323
|
+
reason: 'Node.js was not detected on PATH.',
|
|
324
|
+
installHint,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
const version = firstLine(detected.output);
|
|
328
|
+
const parsed = parseSemver(version);
|
|
329
|
+
if (parsed && parsed.major < 20) {
|
|
330
|
+
return {
|
|
331
|
+
id: 'node',
|
|
332
|
+
label: 'Node.js',
|
|
333
|
+
commands,
|
|
334
|
+
status: 'warn',
|
|
335
|
+
requiredFor,
|
|
336
|
+
detectedCommand: detected.display,
|
|
337
|
+
version,
|
|
338
|
+
reason: `Node.js ${version} is installed, but SCALE setup expects Node.js 20+ for current third-party installers.`,
|
|
339
|
+
installHint,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
return {
|
|
343
|
+
id: 'node',
|
|
344
|
+
label: 'Node.js',
|
|
345
|
+
commands,
|
|
346
|
+
status: 'ok',
|
|
347
|
+
requiredFor,
|
|
348
|
+
detectedCommand: detected.display,
|
|
349
|
+
version,
|
|
350
|
+
reason: `Node.js ${version} is available.`,
|
|
351
|
+
installHint,
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
function pythonRuntimeCheck(requiredFor) {
|
|
355
|
+
const candidates = [
|
|
356
|
+
{ command: 'python', args: ['--version'], display: 'python' },
|
|
357
|
+
{ command: 'python3', args: ['--version'], display: 'python3' },
|
|
358
|
+
{ command: 'py', args: ['--version'], display: 'py' },
|
|
359
|
+
];
|
|
360
|
+
const detected = firstAvailableRuntimeTool(candidates);
|
|
361
|
+
const installHint = 'Install Python 3.10+ and prefer uv or pipx for graphify installation.';
|
|
362
|
+
if (!detected) {
|
|
363
|
+
return {
|
|
364
|
+
id: 'python',
|
|
365
|
+
label: 'Python',
|
|
366
|
+
commands: candidates.map(candidate => candidate.display),
|
|
367
|
+
status: 'missing',
|
|
368
|
+
requiredFor,
|
|
369
|
+
reason: 'Python 3.10+ was not detected on PATH.',
|
|
370
|
+
installHint,
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
const version = firstLine(detected.output);
|
|
374
|
+
const parsed = parseSemver(version);
|
|
375
|
+
if (!parsed || parsed.major < 3 || (parsed.major === 3 && parsed.minor < 10)) {
|
|
376
|
+
return {
|
|
377
|
+
id: 'python',
|
|
378
|
+
label: 'Python',
|
|
379
|
+
commands: candidates.map(candidate => candidate.display),
|
|
380
|
+
status: 'warn',
|
|
381
|
+
requiredFor,
|
|
382
|
+
detectedCommand: detected.display,
|
|
383
|
+
version,
|
|
384
|
+
reason: `${version} is detected, but Graphify requires Python 3.10+.`,
|
|
385
|
+
installHint,
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
return {
|
|
389
|
+
id: 'python',
|
|
390
|
+
label: 'Python',
|
|
391
|
+
commands: candidates.map(candidate => candidate.display),
|
|
392
|
+
status: 'ok',
|
|
393
|
+
requiredFor,
|
|
394
|
+
detectedCommand: detected.display,
|
|
395
|
+
version,
|
|
396
|
+
reason: `${version} is available for Graphify.`,
|
|
397
|
+
installHint,
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
function pythonInstallerRuntimeCheck(requiredFor) {
|
|
401
|
+
const candidates = [
|
|
402
|
+
{ command: 'uv', args: ['--version'], display: 'uv' },
|
|
403
|
+
{ command: 'pipx', args: ['--version'], display: 'pipx' },
|
|
404
|
+
{ command: 'pip', args: ['--version'], display: 'pip' },
|
|
405
|
+
{ command: 'pip3', args: ['--version'], display: 'pip3' },
|
|
406
|
+
{ command: 'python', args: ['-m', 'pip', '--version'], display: 'python -m pip' },
|
|
407
|
+
{ command: 'python3', args: ['-m', 'pip', '--version'], display: 'python3 -m pip' },
|
|
408
|
+
];
|
|
409
|
+
const detected = firstAvailableRuntimeTool(candidates);
|
|
410
|
+
const installHint = 'Install uv or pipx first; pip is supported only as a fallback for graphify.';
|
|
411
|
+
if (!detected) {
|
|
412
|
+
return {
|
|
413
|
+
id: 'python-installer',
|
|
414
|
+
label: 'Python installer',
|
|
415
|
+
commands: candidates.map(candidate => candidate.display),
|
|
416
|
+
status: 'missing',
|
|
417
|
+
requiredFor,
|
|
418
|
+
reason: 'No supported Python installer was detected for graphify.',
|
|
419
|
+
installHint,
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
const version = firstLine(detected.output);
|
|
423
|
+
const preferred = detected.display === 'uv' || detected.display === 'pipx';
|
|
424
|
+
return {
|
|
425
|
+
id: 'python-installer',
|
|
426
|
+
label: 'Python installer',
|
|
427
|
+
commands: candidates.map(candidate => candidate.display),
|
|
428
|
+
status: preferred ? 'ok' : 'warn',
|
|
429
|
+
requiredFor,
|
|
430
|
+
detectedCommand: detected.display,
|
|
431
|
+
version,
|
|
432
|
+
reason: preferred
|
|
433
|
+
? `${detected.display} is available for isolated graphify installation.`
|
|
434
|
+
: `${detected.display} is available, but uv or pipx is preferred to avoid polluting project environments.`,
|
|
435
|
+
installHint,
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
function firstAvailableRuntimeTool(candidates) {
|
|
439
|
+
for (const candidate of candidates) {
|
|
440
|
+
const result = runHealthCommand(candidate.command, candidate.args);
|
|
441
|
+
if (!result.ok)
|
|
442
|
+
continue;
|
|
443
|
+
return {
|
|
444
|
+
display: candidate.display,
|
|
445
|
+
output: `${result.stdout}\n${result.stderr}`.trim(),
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
return undefined;
|
|
449
|
+
}
|
|
450
|
+
function parseSemver(value) {
|
|
451
|
+
const match = value.match(/(\d+)\.(\d+)(?:\.(\d+))?/);
|
|
452
|
+
if (!match)
|
|
453
|
+
return null;
|
|
454
|
+
return {
|
|
455
|
+
major: Number.parseInt(match[1], 10),
|
|
456
|
+
minor: Number.parseInt(match[2], 10),
|
|
457
|
+
patch: Number.parseInt(match[3] ?? '0', 10),
|
|
458
|
+
};
|
|
459
|
+
}
|
|
187
460
|
export function runDependencyBootstrapPostChecks(input, deps = {}) {
|
|
188
461
|
const inspectTools = deps.inspectTools ?? inspectToolCapabilities;
|
|
189
462
|
const inspectMemory = deps.inspectMemory ?? inspectMemoryProviders;
|
|
@@ -267,6 +540,7 @@ function inspectDefinition(definition, context) {
|
|
|
267
540
|
command: requirement,
|
|
268
541
|
present: requirementSatisfied(requirement, context.commandExists),
|
|
269
542
|
}));
|
|
543
|
+
const health = installed && definition.healthCheck ? definition.healthCheck(context) : undefined;
|
|
270
544
|
const installCommand = installed ? undefined : definition.installCommand(context) ?? undefined;
|
|
271
545
|
const installSupported = Boolean(installCommand);
|
|
272
546
|
return {
|
|
@@ -276,15 +550,22 @@ function inspectDefinition(definition, context) {
|
|
|
276
550
|
packs: definition.packs,
|
|
277
551
|
source: definition.source,
|
|
278
552
|
installed,
|
|
279
|
-
status: installed
|
|
553
|
+
status: installed
|
|
554
|
+
? health?.bootstrapStatus ?? 'installed'
|
|
555
|
+
: installSupported ? 'ready' : 'manual-review',
|
|
280
556
|
installCommand,
|
|
281
557
|
installSupported,
|
|
282
558
|
detectedBy,
|
|
283
559
|
prerequisites,
|
|
284
560
|
manualReason: installed ? undefined : definition.manualReason,
|
|
561
|
+
health,
|
|
285
562
|
};
|
|
286
563
|
}
|
|
287
564
|
function detectDefinition(definition, projectDir, homeDir) {
|
|
565
|
+
const context = { projectDir, homeDir, commandExists: externalCommandExists };
|
|
566
|
+
const detectedPath = definition.detectPaths?.(context).find(candidate => existsSync(candidate));
|
|
567
|
+
if (detectedPath)
|
|
568
|
+
return detectedPath;
|
|
288
569
|
if (definition.detectSkillId) {
|
|
289
570
|
const path = skillCandidatePaths(projectDir, homeDir, definition.detectSkillId).find(candidate => existsSync(candidate));
|
|
290
571
|
return path ?? 'missing';
|
|
@@ -307,6 +588,10 @@ function requirementSatisfied(requirement, commandExists) {
|
|
|
307
588
|
return requirement.split('|').some(command => commandExists(command));
|
|
308
589
|
}
|
|
309
590
|
function buildGraphifyInstallCommand(context) {
|
|
591
|
+
if (context.commandExists('uv'))
|
|
592
|
+
return GRAPHIFY_UV_INSTALL;
|
|
593
|
+
if (context.commandExists('pipx'))
|
|
594
|
+
return GRAPHIFY_PIPX_INSTALL;
|
|
310
595
|
if (context.commandExists('pip'))
|
|
311
596
|
return GRAPHIFY_PIP_INSTALL;
|
|
312
597
|
if (context.commandExists('pip3'))
|
|
@@ -318,15 +603,120 @@ function buildGraphifyInstallCommand(context) {
|
|
|
318
603
|
return null;
|
|
319
604
|
}
|
|
320
605
|
function buildGbrainInstallCommand(context) {
|
|
321
|
-
if (!context.commandExists('
|
|
606
|
+
if (!context.commandExists('bun'))
|
|
322
607
|
return null;
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
const
|
|
327
|
-
if (
|
|
328
|
-
return
|
|
329
|
-
|
|
608
|
+
return GBRAIN_INSTALL;
|
|
609
|
+
}
|
|
610
|
+
function checkRtkHealth() {
|
|
611
|
+
const gain = runHealthCommand('rtk', ['gain']);
|
|
612
|
+
if (!gain.ok) {
|
|
613
|
+
return {
|
|
614
|
+
status: 'warn',
|
|
615
|
+
bootstrapStatus: 'needs-init',
|
|
616
|
+
reason: 'rtk CLI is installed but `rtk gain` did not run successfully; token-savings evidence is not available yet.',
|
|
617
|
+
nextCommands: ['rtk init -g --codex', 'rtk gain'],
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
const output = `${gain.stdout}\n${gain.stderr}`;
|
|
621
|
+
if (/no hook installed/i.test(output)) {
|
|
622
|
+
return {
|
|
623
|
+
status: 'warn',
|
|
624
|
+
bootstrapStatus: 'needs-init',
|
|
625
|
+
reason: 'rtk CLI is installed, but the shell hook is not installed so command-output compression is not automatic yet.',
|
|
626
|
+
nextCommands: ['rtk init -g --codex', 'rtk gain'],
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
return { status: 'ok', reason: 'rtk CLI and gain evidence are available.' };
|
|
630
|
+
}
|
|
631
|
+
function checkGbrainHealth() {
|
|
632
|
+
const health = inspectGbrainCliHealth();
|
|
633
|
+
if (health.available) {
|
|
634
|
+
return {
|
|
635
|
+
status: health.degraded ? 'warn' : 'ok',
|
|
636
|
+
bootstrapStatus: health.degraded ? 'manual-review' : undefined,
|
|
637
|
+
reason: health.degraded
|
|
638
|
+
? `${health.reason}; provider can still be used for read-only recall.`
|
|
639
|
+
: 'gbrain doctor passed; provider can be used for default memory routing.',
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
return {
|
|
643
|
+
status: 'warn',
|
|
644
|
+
bootstrapStatus: 'needs-init',
|
|
645
|
+
reason: /no brain configured/i.test(health.reason)
|
|
646
|
+
? 'gbrain CLI is installed but no brain is configured yet; cross-session recall will fail until initialized.'
|
|
647
|
+
: health.reason,
|
|
648
|
+
nextCommands: ['gbrain init --pglite', 'gbrain doctor --json', 'scale memory provider status --json'],
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
function checkGraphifyHealth() {
|
|
652
|
+
const version = runHealthCommand('graphify', ['--version']);
|
|
653
|
+
const hook = runHealthCommand('graphify', ['hook', 'status'], 10_000);
|
|
654
|
+
const output = `${version.stdout}\n${version.stderr}\n${hook.stdout}\n${hook.stderr}`;
|
|
655
|
+
if (/skill.*version|package.*version|drift|outdated/i.test(output)) {
|
|
656
|
+
return {
|
|
657
|
+
status: 'warn',
|
|
658
|
+
bootstrapStatus: 'version-drift',
|
|
659
|
+
reason: 'graphify CLI is installed but its generated skill/hook assets appear out of sync with the package.',
|
|
660
|
+
nextCommands: ['graphify install --platform codex', 'graphify hook status', 'scale codegraph status --json'],
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
if (!hook.ok || /not installed|missing/i.test(output)) {
|
|
664
|
+
return {
|
|
665
|
+
status: 'warn',
|
|
666
|
+
bootstrapStatus: 'needs-init',
|
|
667
|
+
reason: 'graphify CLI is installed but Codex hooks are not fully configured.',
|
|
668
|
+
nextCommands: ['graphify install --platform codex', 'graphify hook status'],
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
return { status: 'ok', reason: 'graphify CLI and hook status are available.' };
|
|
672
|
+
}
|
|
673
|
+
function checkCodeGraphHealth(context) {
|
|
674
|
+
const indexPath = join(context.projectDir, '.codegraph');
|
|
675
|
+
if (!existsSync(indexPath)) {
|
|
676
|
+
return {
|
|
677
|
+
status: 'warn',
|
|
678
|
+
bootstrapStatus: 'needs-init',
|
|
679
|
+
reason: 'codegraph CLI is installed but this project has no .codegraph index yet.',
|
|
680
|
+
nextCommands: ['codegraph init -i', 'scale codegraph status --json'],
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
const status = runHealthCommand('codegraph', ['status', context.projectDir], 10_000);
|
|
684
|
+
if (!status.ok) {
|
|
685
|
+
return {
|
|
686
|
+
status: 'warn',
|
|
687
|
+
bootstrapStatus: 'needs-init',
|
|
688
|
+
reason: `codegraph index exists but status check failed: ${firstLine(`${status.stdout}\n${status.stderr}`)}`,
|
|
689
|
+
nextCommands: ['codegraph init -i', 'codegraph status .'],
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
return { status: 'ok', reason: 'codegraph CLI and project index are available.' };
|
|
693
|
+
}
|
|
694
|
+
function runHealthCommand(command, args, timeout = 5_000) {
|
|
695
|
+
try {
|
|
696
|
+
const result = execaSync(command, args, {
|
|
697
|
+
reject: false,
|
|
698
|
+
timeout,
|
|
699
|
+
});
|
|
700
|
+
return {
|
|
701
|
+
ok: (result.exitCode ?? 1) === 0,
|
|
702
|
+
stdout: result.stdout ?? '',
|
|
703
|
+
stderr: result.stderr ?? '',
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
catch (error) {
|
|
707
|
+
const err = error;
|
|
708
|
+
return {
|
|
709
|
+
ok: false,
|
|
710
|
+
stdout: String(err.stdout ?? ''),
|
|
711
|
+
stderr: String(err.stderr ?? err.message ?? ''),
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
function firstLine(value) {
|
|
716
|
+
return value.split(/\r?\n/).map(line => line.trim()).find(Boolean) ?? 'unknown error';
|
|
717
|
+
}
|
|
718
|
+
function quotePath(path) {
|
|
719
|
+
return `"${path.replace(/"/g, '\\"')}"`;
|
|
330
720
|
}
|
|
331
721
|
async function runInstallCommand(shellCommand) {
|
|
332
722
|
const wrapped = wrapShellCommandWithRtk(shellCommand);
|
|
@@ -371,7 +761,11 @@ function buildPostCheckCommands(packIds, items) {
|
|
|
371
761
|
const selectedPacks = new Set(packIds.includes('full') ? ['ui', 'memory', 'knowledge', 'external-cli'] : packIds);
|
|
372
762
|
const ids = new Set(items.map(item => item.id));
|
|
373
763
|
if (selectedPacks.has('ui')) {
|
|
374
|
-
|
|
764
|
+
const uiToolIds = items
|
|
765
|
+
.filter(item => item.kind === 'skill' && item.packs.includes('ui'))
|
|
766
|
+
.map(item => item.id);
|
|
767
|
+
if (uiToolIds.length > 0)
|
|
768
|
+
commands.add(`scale tool doctor --tools ${uiToolIds.join(',')} --json`);
|
|
375
769
|
commands.add('scale skill doctor --json');
|
|
376
770
|
}
|
|
377
771
|
if (selectedPacks.has('memory') || ids.has('gbrain')) {
|
|
@@ -398,7 +792,7 @@ function buildRollbackHints(items) {
|
|
|
398
792
|
hints.add('CodeGraph rollback: npm uninstall -g @colbymchenry/codegraph');
|
|
399
793
|
break;
|
|
400
794
|
case 'graphify':
|
|
401
|
-
hints.add('Graphify rollback: pip uninstall
|
|
795
|
+
hints.add('Graphify rollback: pip uninstall graphify # or pip3/python -m pip uninstall graphify');
|
|
402
796
|
break;
|
|
403
797
|
case 'gbrain':
|
|
404
798
|
hints.add('GBrain rollback: bun unlink gbrain, then remove ~/.scale/vendor/gbrain if you want a full local cleanup');
|