@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.
Files changed (86) hide show
  1. package/README.md +15 -0
  2. package/dist/api/cli.js +142 -40
  3. package/dist/api/cli.js.map +1 -1
  4. package/dist/api/doctor.js +1 -1
  5. package/dist/api/doctor.js.map +1 -1
  6. package/dist/bootstrap/DependencyBootstrap.d.ts +22 -1
  7. package/dist/bootstrap/DependencyBootstrap.js +420 -32
  8. package/dist/bootstrap/DependencyBootstrap.js.map +1 -1
  9. package/dist/bootstrap/DependencyBootstrapRenderer.d.ts +3 -0
  10. package/dist/bootstrap/DependencyBootstrapRenderer.js +140 -0
  11. package/dist/bootstrap/DependencyBootstrapRenderer.js.map +1 -0
  12. package/dist/cli/gateStatusCommands.d.ts +1 -0
  13. package/dist/cli/gateStatusCommands.js +52 -0
  14. package/dist/cli/gateStatusCommands.js.map +1 -0
  15. package/dist/cli/phaseCommands.js +15 -3
  16. package/dist/cli/phaseCommands.js.map +1 -1
  17. package/dist/cli/promptCommands.d.ts +1 -0
  18. package/dist/cli/promptCommands.js +57 -0
  19. package/dist/cli/promptCommands.js.map +1 -0
  20. package/dist/cli/scoreCommands.d.ts +1 -0
  21. package/dist/cli/scoreCommands.js +112 -0
  22. package/dist/cli/scoreCommands.js.map +1 -0
  23. package/dist/codegraph/CodeIntelligence.js +1 -1
  24. package/dist/codegraph/CodeIntelligence.js.map +1 -1
  25. package/dist/context/SessionStartSequence.js +13 -4
  26. package/dist/context/SessionStartSequence.js.map +1 -1
  27. package/dist/core/ExternalCommand.js +18 -4
  28. package/dist/core/ExternalCommand.js.map +1 -1
  29. package/dist/env/EnvironmentDoctor.d.ts +66 -0
  30. package/dist/env/EnvironmentDoctor.js +365 -0
  31. package/dist/env/EnvironmentDoctor.js.map +1 -0
  32. package/dist/i18n/Language.d.ts +9 -0
  33. package/dist/i18n/Language.js +38 -0
  34. package/dist/i18n/Language.js.map +1 -0
  35. package/dist/index.d.ts +1 -0
  36. package/dist/index.js +1 -0
  37. package/dist/index.js.map +1 -1
  38. package/dist/memory/MemoryProviders.js +38 -5
  39. package/dist/memory/MemoryProviders.js.map +1 -1
  40. package/dist/prompts/PromptOptimizer.d.ts +42 -0
  41. package/dist/prompts/PromptOptimizer.js +309 -0
  42. package/dist/prompts/PromptOptimizer.js.map +1 -0
  43. package/dist/setup/SetupWizard.d.ts +42 -0
  44. package/dist/setup/SetupWizard.js +156 -0
  45. package/dist/setup/SetupWizard.js.map +1 -0
  46. package/dist/skills/SkillRepository.js +2 -2
  47. package/dist/skills/SkillRepository.js.map +1 -1
  48. package/dist/testing/DiffTestSelector.js +1 -1
  49. package/dist/testing/DiffTestSelector.js.map +1 -1
  50. package/dist/tools/ToolCapabilityRegistry.d.ts +4 -0
  51. package/dist/tools/ToolCapabilityRegistry.js +11 -6
  52. package/dist/tools/ToolCapabilityRegistry.js.map +1 -1
  53. package/dist/workflow/CommitDiscipline.js +8 -7
  54. package/dist/workflow/CommitDiscipline.js.map +1 -1
  55. package/dist/workflow/CrossRepoOrchestrator.js +15 -7
  56. package/dist/workflow/CrossRepoOrchestrator.js.map +1 -1
  57. package/dist/workflow/GateCatalog.d.ts +61 -0
  58. package/dist/workflow/GateCatalog.js +212 -0
  59. package/dist/workflow/GateCatalog.js.map +1 -0
  60. package/dist/workflow/GovernanceTemplatePacks.js +19 -4
  61. package/dist/workflow/GovernanceTemplatePacks.js.map +1 -1
  62. package/dist/workflow/SessionPreamble.js +7 -2
  63. package/dist/workflow/SessionPreamble.js.map +1 -1
  64. package/dist/workflow/TaskScoreEngine.d.ts +42 -0
  65. package/dist/workflow/TaskScoreEngine.js +181 -0
  66. package/dist/workflow/TaskScoreEngine.js.map +1 -0
  67. package/dist/workflow/WorkspaceTopology.d.ts +3 -0
  68. package/dist/workflow/WorkspaceTopology.js +40 -3
  69. package/dist/workflow/WorkspaceTopology.js.map +1 -1
  70. package/dist/workflow/gates/GateSystem.js +2 -2
  71. package/dist/workflow/gates/GateSystem.js.map +1 -1
  72. package/dist/workflow/index.d.ts +2 -0
  73. package/dist/workflow/index.js +2 -0
  74. package/dist/workflow/index.js.map +1 -1
  75. package/docs/CODE_INTELLIGENCE.md +27 -2
  76. package/docs/MEMORY_FABRIC.md +22 -1
  77. package/docs/THIRD_PARTY_SKILLS.md +50 -1
  78. package/docs/guides/GETTING_STARTED.md +24 -0
  79. package/docs/start/quickstart.md +103 -76
  80. package/docs/workflow/GATES_AND_SCORE.md +56 -0
  81. package/docs/workflow/PROMPT_OPTIMIZATION.md +44 -0
  82. package/docs/workflow/README.md +7 -0
  83. package/docs/workflow/node-library.md +3 -3
  84. package/package.json +11 -4
  85. package/scripts/workflow/provider-rehearsal.mjs +425 -0
  86. package/scripts/workflow/setup-smoke.mjs +299 -0
@@ -1,23 +1,29 @@
1
- import { existsSync, mkdirSync } from 'node:fs';
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
7
  import { 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': 'npx skills add https://github.com/VoltAgent/awesome-design-md --skill awesome-design-md',
12
- 'ui-ux-pro-max': 'npx skills add https://github.com/nextlevelbuilder/ui-ux-pro-max-skill --skill ui-ux-pro-max',
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 GRAPHIFY_PIP_INSTALL = 'pip install graphifyy && graphify install';
18
- const GRAPHIFY_PIP3_INSTALL = 'pip3 install graphifyy && graphify install';
19
- const GRAPHIFY_PYTHON_INSTALL = 'python -m pip install graphifyy && graphify install';
20
- const GRAPHIFY_PYTHON3_INSTALL = 'python3 -m pip install graphifyy && graphify install';
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 and the local skills installer to be available before UI brand skills can be added.',
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 and the local skills installer to be available before UI review skills can be added.',
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: ['ui'],
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: 'Requires npm/npx and the local skills installer to be available before the implementation companion skill can be added.',
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: ['git', 'bun'],
75
- manualReason: 'The official standalone GBrain install currently needs git + bun and links the CLI from a local clone.',
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 working pip/python path before the CLI can be installed.',
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.status = result.ok && rechecked.installed ? 'installed-now' : 'failed';
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 && !complete)
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 ? 'installed' : installSupported ? 'ready' : 'manual-review',
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,114 @@ function buildGraphifyInstallCommand(context) {
318
603
  return null;
319
604
  }
320
605
  function buildGbrainInstallCommand(context) {
321
- if (!context.commandExists('git') || !context.commandExists('bun'))
606
+ if (!context.commandExists('bun'))
322
607
  return null;
323
- const vendorRoot = join(context.homeDir, '.scale', 'vendor');
324
- const installDir = join(vendorRoot, 'gbrain');
325
- mkdirSync(vendorRoot, { recursive: true });
326
- const changeDir = process.platform === 'win32' ? `cd /d "${installDir}"` : `cd "${installDir}"`;
327
- if (existsSync(installDir))
328
- return `${changeDir} && git pull --ff-only && bun install && bun link`;
329
- return `git clone ${GBRAIN_SOURCE}.git "${installDir}" && ${changeDir} && bun install && bun link`;
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 doctor = runHealthCommand('gbrain', ['doctor', '--json'], 10_000);
633
+ if (doctor.ok)
634
+ return { status: 'ok', reason: 'gbrain doctor passed; provider can be used for default memory routing.' };
635
+ const output = `${doctor.stdout}\n${doctor.stderr}`;
636
+ return {
637
+ status: 'warn',
638
+ bootstrapStatus: 'needs-init',
639
+ reason: /no brain configured/i.test(output)
640
+ ? 'gbrain CLI is installed but no brain is configured yet; cross-session recall will fail until initialized.'
641
+ : `gbrain CLI is installed but doctor failed: ${firstLine(output)}`,
642
+ nextCommands: ['gbrain init --pglite', 'gbrain doctor --json', 'scale memory provider status --json'],
643
+ };
644
+ }
645
+ function checkGraphifyHealth() {
646
+ const version = runHealthCommand('graphify', ['--version']);
647
+ const hook = runHealthCommand('graphify', ['hook', 'status'], 10_000);
648
+ const output = `${version.stdout}\n${version.stderr}\n${hook.stdout}\n${hook.stderr}`;
649
+ if (/skill.*version|package.*version|drift|outdated/i.test(output)) {
650
+ return {
651
+ status: 'warn',
652
+ bootstrapStatus: 'version-drift',
653
+ reason: 'graphify CLI is installed but its generated skill/hook assets appear out of sync with the package.',
654
+ nextCommands: ['graphify install --platform codex', 'graphify hook status', 'scale codegraph status --json'],
655
+ };
656
+ }
657
+ if (!hook.ok || /not installed|missing/i.test(output)) {
658
+ return {
659
+ status: 'warn',
660
+ bootstrapStatus: 'needs-init',
661
+ reason: 'graphify CLI is installed but Codex hooks are not fully configured.',
662
+ nextCommands: ['graphify install --platform codex', 'graphify hook status'],
663
+ };
664
+ }
665
+ return { status: 'ok', reason: 'graphify CLI and hook status are available.' };
666
+ }
667
+ function checkCodeGraphHealth(context) {
668
+ const indexPath = join(context.projectDir, '.codegraph');
669
+ if (!existsSync(indexPath)) {
670
+ return {
671
+ status: 'warn',
672
+ bootstrapStatus: 'needs-init',
673
+ reason: 'codegraph CLI is installed but this project has no .codegraph index yet.',
674
+ nextCommands: ['codegraph init -i', 'scale codegraph status --json'],
675
+ };
676
+ }
677
+ const status = runHealthCommand('codegraph', ['status', context.projectDir], 10_000);
678
+ if (!status.ok) {
679
+ return {
680
+ status: 'warn',
681
+ bootstrapStatus: 'needs-init',
682
+ reason: `codegraph index exists but status check failed: ${firstLine(`${status.stdout}\n${status.stderr}`)}`,
683
+ nextCommands: ['codegraph init -i', 'codegraph status .'],
684
+ };
685
+ }
686
+ return { status: 'ok', reason: 'codegraph CLI and project index are available.' };
687
+ }
688
+ function runHealthCommand(command, args, timeout = 5_000) {
689
+ try {
690
+ const result = execaSync(command, args, {
691
+ reject: false,
692
+ timeout,
693
+ });
694
+ return {
695
+ ok: (result.exitCode ?? 1) === 0,
696
+ stdout: result.stdout ?? '',
697
+ stderr: result.stderr ?? '',
698
+ };
699
+ }
700
+ catch (error) {
701
+ const err = error;
702
+ return {
703
+ ok: false,
704
+ stdout: String(err.stdout ?? ''),
705
+ stderr: String(err.stderr ?? err.message ?? ''),
706
+ };
707
+ }
708
+ }
709
+ function firstLine(value) {
710
+ return value.split(/\r?\n/).map(line => line.trim()).find(Boolean) ?? 'unknown error';
711
+ }
712
+ function quotePath(path) {
713
+ return `"${path.replace(/"/g, '\\"')}"`;
330
714
  }
331
715
  async function runInstallCommand(shellCommand) {
332
716
  const wrapped = wrapShellCommandWithRtk(shellCommand);
@@ -371,7 +755,11 @@ function buildPostCheckCommands(packIds, items) {
371
755
  const selectedPacks = new Set(packIds.includes('full') ? ['ui', 'memory', 'knowledge', 'external-cli'] : packIds);
372
756
  const ids = new Set(items.map(item => item.id));
373
757
  if (selectedPacks.has('ui')) {
374
- commands.add('scale tool doctor --tools awesome-design-md,ui-ux-pro-max,frontend-design --json');
758
+ const uiToolIds = items
759
+ .filter(item => item.kind === 'skill' && item.packs.includes('ui'))
760
+ .map(item => item.id);
761
+ if (uiToolIds.length > 0)
762
+ commands.add(`scale tool doctor --tools ${uiToolIds.join(',')} --json`);
375
763
  commands.add('scale skill doctor --json');
376
764
  }
377
765
  if (selectedPacks.has('memory') || ids.has('gbrain')) {
@@ -398,7 +786,7 @@ function buildRollbackHints(items) {
398
786
  hints.add('CodeGraph rollback: npm uninstall -g @colbymchenry/codegraph');
399
787
  break;
400
788
  case 'graphify':
401
- hints.add('Graphify rollback: pip uninstall graphifyy # or pip3/python -m pip uninstall graphifyy');
789
+ hints.add('Graphify rollback: pip uninstall graphify # or pip3/python -m pip uninstall graphify');
402
790
  break;
403
791
  case 'gbrain':
404
792
  hints.add('GBrain rollback: bun unlink gbrain, then remove ~/.scale/vendor/gbrain if you want a full local cleanup');