@hongmaple0820/scale-engine 0.40.0 → 0.40.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/cli.js +421 -26
- 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 +2 -0
- package/dist/bootstrap/DependencyBootstrap.js +250 -39
- package/dist/bootstrap/DependencyBootstrap.js.map +1 -1
- package/dist/bootstrap/DependencyBootstrapRenderer.js +2 -4
- package/dist/bootstrap/DependencyBootstrapRenderer.js.map +1 -1
- package/dist/capabilities/InstalledSkillsIntegration.js +29 -9
- package/dist/capabilities/InstalledSkillsIntegration.js.map +1 -1
- package/dist/context/ContextBudget.js +2 -2
- package/dist/core/GbrainRuntime.d.ts +25 -0
- package/dist/core/GbrainRuntime.js +270 -0
- package/dist/core/GbrainRuntime.js.map +1 -0
- package/dist/env/EnvironmentDoctor.js +221 -5
- package/dist/env/EnvironmentDoctor.js.map +1 -1
- package/dist/memory/MemoryProviders.js +38 -91
- package/dist/memory/MemoryProviders.js.map +1 -1
- package/dist/runtime/ModelUsageLedger.d.ts +53 -2
- package/dist/runtime/ModelUsageLedger.js +243 -39
- package/dist/runtime/ModelUsageLedger.js.map +1 -1
- package/dist/setup/SetupVerification.d.ts +42 -0
- package/dist/setup/SetupVerification.js +180 -0
- package/dist/setup/SetupVerification.js.map +1 -0
- package/dist/setup/SetupWizard.d.ts +3 -0
- package/dist/setup/SetupWizard.js +79 -19
- package/dist/setup/SetupWizard.js.map +1 -1
- package/dist/skills/SkillDoctor.js +2 -2
- package/dist/skills/SkillDoctor.js.map +1 -1
- package/dist/skills/SkillRepository.js +2 -2
- package/dist/skills/SkillRepository.js.map +1 -1
- package/dist/tools/ToolCapabilityRegistry.js +12 -2
- package/dist/tools/ToolCapabilityRegistry.js.map +1 -1
- package/dist/workflow/VerificationProfile.js +1 -1
- package/dist/workflow/VerificationProfile.js.map +1 -1
- package/docs/CONTEXT_BUDGET.md +12 -2
- package/docs/GOVERNANCE_DASHBOARD.md +7 -0
- package/docs/THIRD_PARTY_SKILLS.md +12 -4
- package/docs/start/README.md +2 -2
- package/docs/start/quickstart.md +54 -44
- package/docs/start/workflow-upgrade.md +8 -1
- package/package.json +3 -2
- package/scripts/workflow/lib/gbrain-runtime.mjs +185 -0
- package/scripts/workflow/lib/report-output.mjs +107 -0
- package/scripts/workflow/provider-rehearsal.mjs +129 -48
- package/scripts/workflow/setup-smoke.mjs +142 -8
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs';
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { join, resolve } from 'node:path';
|
|
4
4
|
import { execa, execaSync } from 'execa';
|
|
@@ -9,14 +9,22 @@ import { wrapShellCommandWithRtk } from '../tools/RtkRuntime.js';
|
|
|
9
9
|
import { inspectToolCapabilities } from '../tools/ToolCapabilityRegistry.js';
|
|
10
10
|
const UI_SKILL_INSTALLS = {
|
|
11
11
|
'awesome-design-md': (ctx) => {
|
|
12
|
-
const
|
|
13
|
-
|
|
12
|
+
const skillDir = quotePath(join(ctx.homeDir, '.agents', 'skills', 'awesome-design-md'));
|
|
13
|
+
const vendorDir = quotePath(join(ctx.homeDir, '.scale', 'vendor', 'awesome-design-md'));
|
|
14
|
+
return `install skill adapter ${skillDir} from VoltAgent/awesome-design-md into ${vendorDir}`;
|
|
15
|
+
},
|
|
16
|
+
'ui-ux-pro-max': (ctx) => {
|
|
17
|
+
const skillDir = quotePath(join(ctx.homeDir, '.agents', 'skills', 'ui-ux-pro-max'));
|
|
18
|
+
const vendorDir = quotePath(join(ctx.homeDir, '.scale', 'vendor', 'ui-ux-pro-max'));
|
|
19
|
+
return `install skill adapter ${skillDir} from nextlevelbuilder/ui-ux-pro-max-skill into ${vendorDir}`;
|
|
14
20
|
},
|
|
15
|
-
'ui-ux-pro-max': 'npx uipro-cli init --ai codex',
|
|
16
21
|
'frontend-design': 'npx skills add anthropics/skills --skill frontend-design',
|
|
17
22
|
};
|
|
18
23
|
const RTK_INSTALL = 'cargo install --git https://github.com/rtk-ai/rtk';
|
|
24
|
+
const RTK_INIT = 'rtk init -g --codex';
|
|
19
25
|
const CODEGRAPH_INSTALL = 'npm install -g @colbymchenry/codegraph';
|
|
26
|
+
const GRAPHIFY_CODEX_INSTALL = 'graphify install --platform codex';
|
|
27
|
+
const GRAPHIFY_HOOK_INSTALL = 'graphify hook install';
|
|
20
28
|
const GRAPHIFY_UV_INSTALL = 'uv tool install graphify && graphify install --platform codex';
|
|
21
29
|
const GRAPHIFY_PIPX_INSTALL = 'pipx install graphify && graphify install --platform codex';
|
|
22
30
|
const GRAPHIFY_PIP_INSTALL = 'pip install graphify && graphify install --platform codex';
|
|
@@ -33,10 +41,20 @@ const DEPENDENCY_BOOTSTRAP_DEFINITIONS = [
|
|
|
33
41
|
packs: ['ui'],
|
|
34
42
|
source: 'https://github.com/VoltAgent/awesome-design-md',
|
|
35
43
|
detectSkillId: 'awesome-design-md',
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
44
|
+
prerequisites: ['git|npx'],
|
|
45
|
+
manualReason: 'Requires Git or npm/npx to sync the upstream DESIGN.md catalog and create a local SCALE skill adapter.',
|
|
46
|
+
installCommand: ctx => requirementSatisfied('git|npx', ctx.commandExists) ? UI_SKILL_INSTALLS['awesome-design-md'](ctx) : null,
|
|
47
|
+
install: ctx => installSkillAdapter(ctx, {
|
|
48
|
+
id: 'awesome-design-md',
|
|
49
|
+
sourceUrl: 'https://github.com/VoltAgent/awesome-design-md.git',
|
|
50
|
+
repoSpecifier: 'VoltAgent/awesome-design-md',
|
|
51
|
+
description: 'Brand, visual language, and DESIGN.md source skill for SCALE UI work.',
|
|
52
|
+
instructions: [
|
|
53
|
+
'Use this skill before UI implementation when brand direction, visual language, DESIGN.md, color, typography, layout mood, or product identity must be defined.',
|
|
54
|
+
'Read the upstream vendor repository under ~/.scale/vendor/awesome-design-md, then create or update the project DESIGN.md with concrete visual decisions.',
|
|
55
|
+
'Do not use it as the accessibility or responsive QA gate; route those checks to ui-ux-pro-max.',
|
|
56
|
+
],
|
|
57
|
+
}),
|
|
40
58
|
},
|
|
41
59
|
{
|
|
42
60
|
id: 'ui-ux-pro-max',
|
|
@@ -45,9 +63,20 @@ const DEPENDENCY_BOOTSTRAP_DEFINITIONS = [
|
|
|
45
63
|
packs: ['ui'],
|
|
46
64
|
source: 'https://github.com/nextlevelbuilder/ui-ux-pro-max-skill',
|
|
47
65
|
detectSkillId: 'ui-ux-pro-max',
|
|
48
|
-
prerequisites: ['npx'],
|
|
49
|
-
manualReason: 'Requires npm/npx
|
|
50
|
-
installCommand: ctx =>
|
|
66
|
+
prerequisites: ['git|npx'],
|
|
67
|
+
manualReason: 'Requires Git or npm/npx to sync the upstream UX skill and create a local SCALE skill adapter.',
|
|
68
|
+
installCommand: ctx => requirementSatisfied('git|npx', ctx.commandExists) ? UI_SKILL_INSTALLS['ui-ux-pro-max'](ctx) : null,
|
|
69
|
+
install: ctx => installSkillAdapter(ctx, {
|
|
70
|
+
id: 'ui-ux-pro-max',
|
|
71
|
+
sourceUrl: 'https://github.com/nextlevelbuilder/ui-ux-pro-max-skill.git',
|
|
72
|
+
repoSpecifier: 'nextlevelbuilder/ui-ux-pro-max-skill',
|
|
73
|
+
description: 'UX flow, UI state, accessibility, and responsive acceptance skill for SCALE UI work.',
|
|
74
|
+
instructions: [
|
|
75
|
+
'Use this skill after visual direction is selected to review UX flow, state coverage, empty/error/loading behavior, keyboard access, accessibility, and responsive acceptance.',
|
|
76
|
+
'Read the upstream vendor repository under ~/.scale/vendor/ui-ux-pro-max for the current checklist and apply it as an acceptance gate.',
|
|
77
|
+
'Do not replace awesome-design-md for brand or visual-language decisions.',
|
|
78
|
+
],
|
|
79
|
+
}),
|
|
51
80
|
},
|
|
52
81
|
{
|
|
53
82
|
id: 'frontend-design',
|
|
@@ -70,6 +99,7 @@ const DEPENDENCY_BOOTSTRAP_DEFINITIONS = [
|
|
|
70
99
|
prerequisites: ['cargo'],
|
|
71
100
|
manualReason: 'RTK currently installs from the official Rust toolchain path and needs Cargo on PATH.',
|
|
72
101
|
installCommand: ctx => ctx.commandExists('cargo') ? RTK_INSTALL : null,
|
|
102
|
+
initializeCommands: () => [RTK_INIT],
|
|
73
103
|
healthCheck: checkRtkHealth,
|
|
74
104
|
},
|
|
75
105
|
{
|
|
@@ -82,6 +112,7 @@ const DEPENDENCY_BOOTSTRAP_DEFINITIONS = [
|
|
|
82
112
|
prerequisites: ['bun'],
|
|
83
113
|
manualReason: 'The official standalone GBrain install needs Bun and then a configured brain (`gbrain init --pglite`) before cross-session recall is usable.',
|
|
84
114
|
installCommand: ctx => buildGbrainInstallCommand(ctx),
|
|
115
|
+
initializeCommands: () => ['gbrain init --pglite --no-embedding'],
|
|
85
116
|
healthCheck: checkGbrainHealth,
|
|
86
117
|
},
|
|
87
118
|
{
|
|
@@ -94,6 +125,11 @@ const DEPENDENCY_BOOTSTRAP_DEFINITIONS = [
|
|
|
94
125
|
prerequisites: ['uv|pipx|pip|pip3|python|python3'],
|
|
95
126
|
manualReason: 'Graphify requires Python 3.10+ and a supported installer; uv tool install is preferred to avoid polluting project virtualenvs.',
|
|
96
127
|
installCommand: ctx => buildGraphifyInstallCommand(ctx),
|
|
128
|
+
initializeCommands: ctx => [
|
|
129
|
+
GRAPHIFY_CODEX_INSTALL,
|
|
130
|
+
GRAPHIFY_HOOK_INSTALL,
|
|
131
|
+
`graphify update ${quotePath(ctx.projectDir)} --no-cluster`,
|
|
132
|
+
],
|
|
97
133
|
healthCheck: checkGraphifyHealth,
|
|
98
134
|
},
|
|
99
135
|
{
|
|
@@ -106,6 +142,7 @@ const DEPENDENCY_BOOTSTRAP_DEFINITIONS = [
|
|
|
106
142
|
prerequisites: ['npm'],
|
|
107
143
|
manualReason: 'CodeGraph installs through npm and needs Node.js/npm available on PATH.',
|
|
108
144
|
installCommand: ctx => ctx.commandExists('npm') ? CODEGRAPH_INSTALL : null,
|
|
145
|
+
initializeCommands: ctx => [codegraphInitCommand(ctx.projectDir)],
|
|
109
146
|
healthCheck: checkCodeGraphHealth,
|
|
110
147
|
},
|
|
111
148
|
];
|
|
@@ -115,7 +152,8 @@ export async function bootstrapDependencies(options = {}) {
|
|
|
115
152
|
const homeDir = homedir();
|
|
116
153
|
const packIds = normalizePackIds(options.packIds);
|
|
117
154
|
const includeIds = unique((options.includeIds ?? []).map(value => value.trim()).filter(Boolean));
|
|
118
|
-
const
|
|
155
|
+
const onlyIds = unique((options.onlyIds ?? []).map(value => value.trim()).filter(Boolean));
|
|
156
|
+
const definitions = selectDefinitions(packIds, includeIds, onlyIds);
|
|
119
157
|
const context = {
|
|
120
158
|
projectDir,
|
|
121
159
|
homeDir,
|
|
@@ -127,10 +165,12 @@ export async function bootstrapDependencies(options = {}) {
|
|
|
127
165
|
for (const item of reports.filter(entry => entry.status === 'ready')) {
|
|
128
166
|
if (!item.installCommand)
|
|
129
167
|
continue;
|
|
130
|
-
const
|
|
168
|
+
const definition = definitions.find(entry => entry.id === item.id);
|
|
169
|
+
const result = definition?.install
|
|
170
|
+
? await definition.install(context)
|
|
171
|
+
: await runInstallCommand(item.installCommand, context.projectDir);
|
|
131
172
|
item.output = result.output;
|
|
132
173
|
item.error = result.error;
|
|
133
|
-
const definition = definitions.find(entry => entry.id === item.id);
|
|
134
174
|
const rechecked = definition ? inspectDefinition(definition, context) : item;
|
|
135
175
|
item.installed = rechecked.installed;
|
|
136
176
|
item.detectedBy = rechecked.detectedBy;
|
|
@@ -143,6 +183,7 @@ export async function bootstrapDependencies(options = {}) {
|
|
|
143
183
|
if (!result.ok)
|
|
144
184
|
item.installSupported = true;
|
|
145
185
|
}
|
|
186
|
+
await reconcileDependencyBootstrapItems(definitions, reports, context);
|
|
146
187
|
}
|
|
147
188
|
const postActions = options.apply ? applyDependencyBootstrapPostActions(projectDir, scaleDir, reports) : [];
|
|
148
189
|
const postChecks = options.apply
|
|
@@ -151,17 +192,22 @@ export async function bootstrapDependencies(options = {}) {
|
|
|
151
192
|
return buildReport(projectDir, scaleDir, packIds, includeIds, Boolean(options.apply), runtimeChecks, reports, postActions, postChecks);
|
|
152
193
|
}
|
|
153
194
|
function buildReport(projectDir, scaleDir, packIds, includeIds, apply, runtimeChecks, items, postActions, postChecks) {
|
|
195
|
+
const nonBlockingItems = new Set(items
|
|
196
|
+
.filter(item => isNonBlockingOptionalItem(item, postChecks))
|
|
197
|
+
.map(item => item.id));
|
|
154
198
|
const summary = {
|
|
155
199
|
total: items.length,
|
|
156
200
|
installed: items.filter(item => item.status === 'installed').length,
|
|
157
201
|
ready: items.filter(item => item.status === 'ready').length,
|
|
158
202
|
manualReview: items.filter(item => item.status === 'manual-review').length,
|
|
159
|
-
needsInit: items.filter(item => item.status === 'needs-init').length,
|
|
203
|
+
needsInit: items.filter(item => item.status === 'needs-init' && !nonBlockingItems.has(item.id)).length,
|
|
160
204
|
versionDrift: items.filter(item => item.status === 'version-drift').length,
|
|
161
205
|
installedNow: items.filter(item => item.status === 'installed-now').length,
|
|
162
206
|
failed: items.filter(item => item.status === 'failed').length,
|
|
163
207
|
};
|
|
164
|
-
const complete = items.length > 0 && items.every(item => item.status === 'installed'
|
|
208
|
+
const complete = items.length > 0 && items.every(item => item.status === 'installed'
|
|
209
|
+
|| item.status === 'installed-now'
|
|
210
|
+
|| nonBlockingItems.has(item.id));
|
|
165
211
|
const postCheckSummary = {
|
|
166
212
|
total: postChecks.length,
|
|
167
213
|
passed: postChecks.filter(item => item.status === 'passed').length,
|
|
@@ -463,6 +509,12 @@ export function runDependencyBootstrapPostChecks(input, deps = {}) {
|
|
|
463
509
|
const inspectCode = deps.inspectCode ?? inspectCodeIntelligence;
|
|
464
510
|
const toolIds = unique(input.items.map(item => item.id).filter(id => ['awesome-design-md', 'ui-ux-pro-max', 'frontend-design', 'rtk', 'gbrain', 'codegraph', 'graphify'].includes(id)));
|
|
465
511
|
const results = [];
|
|
512
|
+
const memoryReport = input.items.some(item => item.id === 'gbrain')
|
|
513
|
+
? inspectMemory({ projectDir: input.projectDir, scaleDir: input.scaleDir })
|
|
514
|
+
: undefined;
|
|
515
|
+
const gbrain = memoryReport?.providers.find(provider => provider.id === 'gbrain');
|
|
516
|
+
const memoryFallback = memoryReport?.providers.find(provider => provider.kind === 'scale-local' && provider.available);
|
|
517
|
+
const softBlockedGbrain = Boolean(gbrain && !gbrain.available && memoryFallback);
|
|
466
518
|
if (toolIds.length > 0) {
|
|
467
519
|
const toolReport = inspectTools({
|
|
468
520
|
projectDir: input.projectDir,
|
|
@@ -470,23 +522,31 @@ export function runDependencyBootstrapPostChecks(input, deps = {}) {
|
|
|
470
522
|
toolIds,
|
|
471
523
|
});
|
|
472
524
|
const missing = toolReport.tools.filter(tool => !tool.installed).map(tool => tool.id);
|
|
525
|
+
const degraded = missing.filter(id => id === 'gbrain' && softBlockedGbrain);
|
|
526
|
+
const blockingMissing = missing.filter(id => !degraded.includes(id));
|
|
473
527
|
results.push({
|
|
474
528
|
id: 'tool-capabilities',
|
|
475
529
|
label: 'Tool Doctor',
|
|
476
530
|
command: `scale tool doctor --tools ${toolIds.join(',')} --json`,
|
|
477
|
-
status:
|
|
478
|
-
summary:
|
|
531
|
+
status: blockingMissing.length > 0 ? 'failed' : degraded.length > 0 ? 'warn' : 'passed',
|
|
532
|
+
summary: [
|
|
533
|
+
`${toolReport.summary.installed}/${toolReport.summary.total} selected tools are available`,
|
|
534
|
+
blockingMissing.length > 0 ? `missing: ${blockingMissing.join(', ')}` : undefined,
|
|
535
|
+
degraded.length > 0 ? `degraded: ${degraded.join(', ')} (scale-local fallback active)` : undefined,
|
|
536
|
+
].filter(Boolean).join('; '),
|
|
479
537
|
details: {
|
|
480
538
|
toolIds,
|
|
481
|
-
missing,
|
|
539
|
+
missing: blockingMissing,
|
|
540
|
+
degraded,
|
|
541
|
+
fallbackProvider: memoryFallback?.id,
|
|
482
542
|
},
|
|
483
543
|
});
|
|
484
544
|
}
|
|
485
|
-
if (
|
|
486
|
-
const memoryReport = inspectMemory({ projectDir: input.projectDir, scaleDir: input.scaleDir });
|
|
487
|
-
const gbrain = memoryReport.providers.find(provider => provider.id === 'gbrain');
|
|
545
|
+
if (memoryReport) {
|
|
488
546
|
const warnings = [...memoryReport.warnings];
|
|
489
|
-
|
|
547
|
+
if (softBlockedGbrain)
|
|
548
|
+
warnings.push(`gbrain is unavailable in this runtime; falling back to ${memoryFallback.id}`);
|
|
549
|
+
const status = !gbrain?.available ? (softBlockedGbrain ? 'warn' : 'failed') : warnings.length > 0 ? 'warn' : 'passed';
|
|
490
550
|
results.push({
|
|
491
551
|
id: 'memory-provider',
|
|
492
552
|
label: 'Memory Provider',
|
|
@@ -498,6 +558,7 @@ export function runDependencyBootstrapPostChecks(input, deps = {}) {
|
|
|
498
558
|
details: {
|
|
499
559
|
warnings,
|
|
500
560
|
gbrainReason: gbrain?.reason,
|
|
561
|
+
fallbackProvider: memoryFallback?.id,
|
|
501
562
|
},
|
|
502
563
|
});
|
|
503
564
|
}
|
|
@@ -533,6 +594,13 @@ export function runDependencyBootstrapPostChecks(input, deps = {}) {
|
|
|
533
594
|
}
|
|
534
595
|
return results;
|
|
535
596
|
}
|
|
597
|
+
function isNonBlockingOptionalItem(item, postChecks) {
|
|
598
|
+
if (item.id !== 'gbrain' || item.status !== 'needs-init')
|
|
599
|
+
return false;
|
|
600
|
+
return postChecks.some(check => check.id === 'memory-provider'
|
|
601
|
+
&& check.status === 'warn'
|
|
602
|
+
&& check.details?.fallbackProvider === 'scale-local');
|
|
603
|
+
}
|
|
536
604
|
function inspectDefinition(definition, context) {
|
|
537
605
|
const detectedBy = detectDefinition(definition, context.projectDir, context.homeDir);
|
|
538
606
|
const installed = detectedBy !== 'missing';
|
|
@@ -607,7 +675,25 @@ function buildGbrainInstallCommand(context) {
|
|
|
607
675
|
return null;
|
|
608
676
|
return GBRAIN_INSTALL;
|
|
609
677
|
}
|
|
610
|
-
function
|
|
678
|
+
function codegraphInitCommand(projectDir) {
|
|
679
|
+
const command = `codegraph init -i ${quotePath(projectDir)}`;
|
|
680
|
+
return process.platform === 'win32' ? `cmd /d /c ${command}` : command;
|
|
681
|
+
}
|
|
682
|
+
export function hasCodexRtkInstructions(homeDir) {
|
|
683
|
+
const codexDir = join(homeDir, '.codex');
|
|
684
|
+
const rtkPath = join(codexDir, 'RTK.md');
|
|
685
|
+
const agentsPath = join(codexDir, 'AGENTS.md');
|
|
686
|
+
if (!existsSync(rtkPath) || !existsSync(agentsPath))
|
|
687
|
+
return false;
|
|
688
|
+
try {
|
|
689
|
+
const agents = readFileSync(agentsPath, 'utf-8');
|
|
690
|
+
return /RTK\.md/i.test(agents);
|
|
691
|
+
}
|
|
692
|
+
catch {
|
|
693
|
+
return false;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
function checkRtkHealth(context) {
|
|
611
697
|
const gain = runHealthCommand('rtk', ['gain']);
|
|
612
698
|
if (!gain.ok) {
|
|
613
699
|
return {
|
|
@@ -619,11 +705,15 @@ function checkRtkHealth() {
|
|
|
619
705
|
}
|
|
620
706
|
const output = `${gain.stdout}\n${gain.stderr}`;
|
|
621
707
|
if (/no hook installed/i.test(output)) {
|
|
708
|
+
if (hasCodexRtkInstructions(context.homeDir)) {
|
|
709
|
+
return {
|
|
710
|
+
status: 'ok',
|
|
711
|
+
reason: 'rtk gain evidence is available; Codex mode uses RTK.md instructions instead of an automatic shell hook.',
|
|
712
|
+
};
|
|
713
|
+
}
|
|
622
714
|
return {
|
|
623
|
-
status: '
|
|
624
|
-
|
|
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'],
|
|
715
|
+
status: 'ok',
|
|
716
|
+
reason: 'rtk gain evidence is available; automatic hook installation is not active, so SCALE should keep using the explicit `rtk <command>` proxy path.',
|
|
627
717
|
};
|
|
628
718
|
}
|
|
629
719
|
return { status: 'ok', reason: 'rtk CLI and gain evidence are available.' };
|
|
@@ -633,7 +723,6 @@ function checkGbrainHealth() {
|
|
|
633
723
|
if (health.available) {
|
|
634
724
|
return {
|
|
635
725
|
status: health.degraded ? 'warn' : 'ok',
|
|
636
|
-
bootstrapStatus: health.degraded ? 'manual-review' : undefined,
|
|
637
726
|
reason: health.degraded
|
|
638
727
|
? `${health.reason}; provider can still be used for read-only recall.`
|
|
639
728
|
: 'gbrain doctor passed; provider can be used for default memory routing.',
|
|
@@ -648,9 +737,52 @@ function checkGbrainHealth() {
|
|
|
648
737
|
nextCommands: ['gbrain init --pglite', 'gbrain doctor --json', 'scale memory provider status --json'],
|
|
649
738
|
};
|
|
650
739
|
}
|
|
651
|
-
function
|
|
652
|
-
const
|
|
653
|
-
|
|
740
|
+
async function reconcileDependencyBootstrapItems(definitions, reports, context) {
|
|
741
|
+
const definitionsById = new Map(definitions.map(definition => [definition.id, definition]));
|
|
742
|
+
for (const item of reports) {
|
|
743
|
+
if (item.status !== 'needs-init' && item.status !== 'version-drift')
|
|
744
|
+
continue;
|
|
745
|
+
const definition = definitionsById.get(item.id);
|
|
746
|
+
const commands = definition?.initializeCommands?.(context) ?? [];
|
|
747
|
+
if (commands.length === 0)
|
|
748
|
+
continue;
|
|
749
|
+
const outputs = [item.output].filter(Boolean);
|
|
750
|
+
const errors = [item.error].filter(Boolean);
|
|
751
|
+
let ok = true;
|
|
752
|
+
for (const command of commands) {
|
|
753
|
+
const result = await runInstallCommand(command, context.projectDir);
|
|
754
|
+
if (result.output)
|
|
755
|
+
outputs.push(result.output);
|
|
756
|
+
if (result.error)
|
|
757
|
+
errors.push(result.error);
|
|
758
|
+
if (!result.ok) {
|
|
759
|
+
ok = false;
|
|
760
|
+
if (!result.error)
|
|
761
|
+
errors.push(`Initialization command failed: ${command}`);
|
|
762
|
+
break;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
item.output = outputs.join('\n') || undefined;
|
|
766
|
+
item.error = errors.join('\n') || undefined;
|
|
767
|
+
if (!ok) {
|
|
768
|
+
const rechecked = definition ? inspectDefinition(definition, context) : item;
|
|
769
|
+
item.installed = rechecked.installed;
|
|
770
|
+
item.detectedBy = rechecked.detectedBy;
|
|
771
|
+
item.health = rechecked.health;
|
|
772
|
+
item.status = definition?.id === 'gbrain' && rechecked.installed ? rechecked.status : 'failed';
|
|
773
|
+
continue;
|
|
774
|
+
}
|
|
775
|
+
const rechecked = definition ? inspectDefinition(definition, context) : item;
|
|
776
|
+
item.installed = rechecked.installed;
|
|
777
|
+
item.detectedBy = rechecked.detectedBy;
|
|
778
|
+
item.health = rechecked.health;
|
|
779
|
+
item.status = rechecked.status === 'installed' ? 'installed-now' : rechecked.status;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
function checkGraphifyHealth(context) {
|
|
783
|
+
const graphPath = join(context.projectDir, 'graphify-out', 'graph.json');
|
|
784
|
+
const version = runHealthCommand('graphify', ['--version'], 5_000, context.projectDir);
|
|
785
|
+
const hook = runHealthCommand('graphify', ['hook', 'status'], 10_000, context.projectDir);
|
|
654
786
|
const output = `${version.stdout}\n${version.stderr}\n${hook.stdout}\n${hook.stderr}`;
|
|
655
787
|
if (/skill.*version|package.*version|drift|outdated/i.test(output)) {
|
|
656
788
|
return {
|
|
@@ -668,7 +800,15 @@ function checkGraphifyHealth() {
|
|
|
668
800
|
nextCommands: ['graphify install --platform codex', 'graphify hook status'],
|
|
669
801
|
};
|
|
670
802
|
}
|
|
671
|
-
|
|
803
|
+
if (!existsSync(graphPath)) {
|
|
804
|
+
return {
|
|
805
|
+
status: 'warn',
|
|
806
|
+
bootstrapStatus: 'needs-init',
|
|
807
|
+
reason: 'graphify CLI is installed but graphify-out/graph.json is not present yet.',
|
|
808
|
+
nextCommands: [`graphify update ${quotePath(context.projectDir)} --no-cluster`, 'scale codegraph status --json'],
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
return { status: 'ok', reason: 'graphify CLI, hooks, and graph artifact are available.' };
|
|
672
812
|
}
|
|
673
813
|
function checkCodeGraphHealth(context) {
|
|
674
814
|
const indexPath = join(context.projectDir, '.codegraph');
|
|
@@ -691,11 +831,12 @@ function checkCodeGraphHealth(context) {
|
|
|
691
831
|
}
|
|
692
832
|
return { status: 'ok', reason: 'codegraph CLI and project index are available.' };
|
|
693
833
|
}
|
|
694
|
-
function runHealthCommand(command, args, timeout = 5_000) {
|
|
834
|
+
function runHealthCommand(command, args, timeout = 5_000, cwd) {
|
|
695
835
|
try {
|
|
696
836
|
const result = execaSync(command, args, {
|
|
697
837
|
reject: false,
|
|
698
838
|
timeout,
|
|
839
|
+
cwd,
|
|
699
840
|
});
|
|
700
841
|
return {
|
|
701
842
|
ok: (result.exitCode ?? 1) === 0,
|
|
@@ -718,17 +859,85 @@ function firstLine(value) {
|
|
|
718
859
|
function quotePath(path) {
|
|
719
860
|
return `"${path.replace(/"/g, '\\"')}"`;
|
|
720
861
|
}
|
|
721
|
-
async function runInstallCommand(shellCommand) {
|
|
862
|
+
async function runInstallCommand(shellCommand, cwd) {
|
|
722
863
|
const wrapped = wrapShellCommandWithRtk(shellCommand);
|
|
723
864
|
const result = wrapped
|
|
724
|
-
? await execa(wrapped.command, wrapped.args, { reject: false, timeout: 300_000, all: false })
|
|
725
|
-
: await execa(shellCommand, { shell: true, reject: false, timeout: 300_000, all: false });
|
|
865
|
+
? await execa(wrapped.command, wrapped.args, { reject: false, timeout: 300_000, all: false, cwd })
|
|
866
|
+
: await execa(shellCommand, { shell: true, reject: false, timeout: 300_000, all: false, cwd });
|
|
726
867
|
return {
|
|
727
868
|
ok: (result.exitCode ?? 1) === 0,
|
|
728
869
|
output: result.stdout ?? '',
|
|
729
870
|
error: result.stderr ?? '',
|
|
730
871
|
};
|
|
731
872
|
}
|
|
873
|
+
async function installSkillAdapter(context, input) {
|
|
874
|
+
const vendorDir = join(context.homeDir, '.scale', 'vendor', input.id);
|
|
875
|
+
const skillDir = join(context.homeDir, '.agents', 'skills', input.id);
|
|
876
|
+
const outputs = [];
|
|
877
|
+
const errors = [];
|
|
878
|
+
mkdirSync(join(context.homeDir, '.scale', 'vendor'), { recursive: true });
|
|
879
|
+
mkdirSync(skillDir, { recursive: true });
|
|
880
|
+
if (!existsSync(vendorDir) || isEmptyDirectory(vendorDir)) {
|
|
881
|
+
if (context.commandExists('git')) {
|
|
882
|
+
const clone = await execa('git', ['clone', '--depth', '1', input.sourceUrl, vendorDir], { reject: false });
|
|
883
|
+
outputs.push(clone.stdout ?? '');
|
|
884
|
+
errors.push(clone.stderr ?? '');
|
|
885
|
+
if ((clone.exitCode ?? 1) !== 0)
|
|
886
|
+
return { ok: false, output: outputs.join('\n'), error: errors.join('\n') };
|
|
887
|
+
}
|
|
888
|
+
else if (context.commandExists('npx')) {
|
|
889
|
+
const degit = await execa('npx', ['degit', input.repoSpecifier, vendorDir], { reject: false });
|
|
890
|
+
outputs.push(degit.stdout ?? '');
|
|
891
|
+
errors.push(degit.stderr ?? '');
|
|
892
|
+
if ((degit.exitCode ?? 1) !== 0)
|
|
893
|
+
return { ok: false, output: outputs.join('\n'), error: errors.join('\n') };
|
|
894
|
+
}
|
|
895
|
+
else {
|
|
896
|
+
return { ok: false, error: 'Git or npx is required to install third-party skills.' };
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
else if (existsSync(join(vendorDir, '.git')) && context.commandExists('git')) {
|
|
900
|
+
const pull = await execa('git', ['-C', vendorDir, 'pull', '--ff-only'], { reject: false });
|
|
901
|
+
outputs.push(pull.stdout ?? '');
|
|
902
|
+
errors.push(pull.stderr ?? '');
|
|
903
|
+
if ((pull.exitCode ?? 1) !== 0)
|
|
904
|
+
return { ok: false, output: outputs.join('\n'), error: errors.join('\n') };
|
|
905
|
+
}
|
|
906
|
+
else {
|
|
907
|
+
outputs.push(`Reused existing vendor directory: ${vendorDir}`);
|
|
908
|
+
}
|
|
909
|
+
writeFileSync(join(skillDir, 'SKILL.md'), renderSkillAdapter(input, vendorDir), 'utf-8');
|
|
910
|
+
outputs.push(`Wrote skill adapter: ${join(skillDir, 'SKILL.md')}`);
|
|
911
|
+
return { ok: true, output: outputs.filter(Boolean).join('\n'), error: errors.filter(Boolean).join('\n') };
|
|
912
|
+
}
|
|
913
|
+
function isEmptyDirectory(path) {
|
|
914
|
+
return existsSync(path) && readdirSync(path).length === 0;
|
|
915
|
+
}
|
|
916
|
+
function renderSkillAdapter(input, vendorDir) {
|
|
917
|
+
const lines = [
|
|
918
|
+
'---',
|
|
919
|
+
`name: ${input.id}`,
|
|
920
|
+
`description: ${input.description}`,
|
|
921
|
+
'---',
|
|
922
|
+
'',
|
|
923
|
+
`# ${input.id}`,
|
|
924
|
+
'',
|
|
925
|
+
`Source: ${input.sourceUrl}`,
|
|
926
|
+
`Vendor path: ${vendorDir}`,
|
|
927
|
+
'',
|
|
928
|
+
'## When To Use',
|
|
929
|
+
'',
|
|
930
|
+
...input.instructions.map(item => `- ${item}`),
|
|
931
|
+
'',
|
|
932
|
+
'## Operating Rules',
|
|
933
|
+
'',
|
|
934
|
+
'- Prefer concrete project artifacts over generic advice.',
|
|
935
|
+
'- If the vendor directory is missing or outdated, run `scale setup --pack ui --apply` again.',
|
|
936
|
+
'- Keep generated project outputs, such as DESIGN.md, inside the target project rather than this skill directory.',
|
|
937
|
+
'',
|
|
938
|
+
];
|
|
939
|
+
return `${lines.join('\n')}\n`;
|
|
940
|
+
}
|
|
732
941
|
export function applyDependencyBootstrapPostActions(projectDir, scaleDir, items, deps = {}) {
|
|
733
942
|
const writeMemoryConfig = deps.writeMemoryConfig ?? writeMemoryProvidersConfig;
|
|
734
943
|
const switchMemoryProvider = deps.switchMemoryProvider ?? useMemoryProvider;
|
|
@@ -751,10 +960,12 @@ export function applyDependencyBootstrapPostActions(projectDir, scaleDir, items,
|
|
|
751
960
|
}
|
|
752
961
|
return actions;
|
|
753
962
|
}
|
|
754
|
-
function selectDefinitions(packIds, includeIds) {
|
|
963
|
+
function selectDefinitions(packIds, includeIds, onlyIds = []) {
|
|
755
964
|
const selectedPacks = new Set(packIds.includes('full') ? ['ui', 'memory', 'knowledge', 'external-cli'] : packIds);
|
|
756
965
|
const selectedIds = new Set(includeIds);
|
|
757
|
-
|
|
966
|
+
const only = new Set(onlyIds);
|
|
967
|
+
return DEPENDENCY_BOOTSTRAP_DEFINITIONS.filter(definition => (only.size === 0 || only.has(definition.id)) &&
|
|
968
|
+
(definition.packs.some(pack => selectedPacks.has(pack)) || selectedIds.has(definition.id)));
|
|
758
969
|
}
|
|
759
970
|
function buildPostCheckCommands(packIds, items) {
|
|
760
971
|
const commands = new Set();
|