@hongmaple0820/scale-engine 0.47.0 → 0.48.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +8 -2
- package/README.md +9 -3
- package/dist/api/cli.js +975 -6222
- package/dist/api/cli.js.map +1 -1
- package/dist/cli/artifactCrudCommands.d.ts +67 -0
- package/dist/cli/artifactCrudCommands.js +182 -0
- package/dist/cli/artifactCrudCommands.js.map +1 -0
- package/dist/cli/codegraphCommands.d.ts +1 -0
- package/dist/cli/codegraphCommands.js +241 -0
- package/dist/cli/codegraphCommands.js.map +1 -0
- package/dist/cli/contextCommands.d.ts +1 -0
- package/dist/cli/contextCommands.js +415 -0
- package/dist/cli/contextCommands.js.map +1 -0
- package/dist/cli/dependencyTddCommands.d.ts +92 -0
- package/dist/cli/dependencyTddCommands.js +174 -0
- package/dist/cli/dependencyTddCommands.js.map +1 -0
- package/dist/cli/diagnoseHuntCommands.d.ts +135 -0
- package/dist/cli/diagnoseHuntCommands.js +224 -0
- package/dist/cli/diagnoseHuntCommands.js.map +1 -0
- package/dist/cli/engineBootstrap.d.ts +39 -0
- package/dist/cli/engineBootstrap.js +129 -0
- package/dist/cli/engineBootstrap.js.map +1 -0
- package/dist/cli/evalCommands.d.ts +1 -0
- package/dist/cli/evalCommands.js +262 -0
- package/dist/cli/evalCommands.js.map +1 -0
- package/dist/cli/evolveDoctorCommands.d.ts +18 -0
- package/dist/cli/evolveDoctorCommands.js +59 -0
- package/dist/cli/evolveDoctorCommands.js.map +1 -0
- package/dist/cli/gateInlineCommands.d.ts +43 -0
- package/dist/cli/gateInlineCommands.js +74 -0
- package/dist/cli/gateInlineCommands.js.map +1 -0
- package/dist/cli/initConfigCommands.d.ts +138 -0
- package/dist/cli/initConfigCommands.js +602 -0
- package/dist/cli/initConfigCommands.js.map +1 -0
- package/dist/cli/metaGovernanceCommands.d.ts +11 -0
- package/dist/cli/metaGovernanceCommands.js +55 -0
- package/dist/cli/metaGovernanceCommands.js.map +1 -0
- package/dist/cli/runtimeSkillCommands.d.ts +6 -0
- package/dist/cli/runtimeSkillCommands.js +1515 -0
- package/dist/cli/runtimeSkillCommands.js.map +1 -0
- package/dist/cli/sessionCommands.d.ts +17 -0
- package/dist/cli/sessionCommands.js +38 -0
- package/dist/cli/sessionCommands.js.map +1 -0
- package/dist/cli/toolAgentCommands.d.ts +3 -0
- package/dist/cli/toolAgentCommands.js +441 -0
- package/dist/cli/toolAgentCommands.js.map +1 -0
- package/dist/cli/transitionCommands.d.ts +62 -0
- package/dist/cli/transitionCommands.js +174 -0
- package/dist/cli/transitionCommands.js.map +1 -0
- package/dist/cli/upgradeAssetsCommands.d.ts +44 -0
- package/dist/cli/upgradeAssetsCommands.js +933 -0
- package/dist/cli/upgradeAssetsCommands.js.map +1 -0
- package/dist/cli/workflowEvidenceCommands.d.ts +34 -0
- package/dist/cli/workflowEvidenceCommands.js +130 -0
- package/dist/cli/workflowEvidenceCommands.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,1515 @@
|
|
|
1
|
+
// SCALE Engine — Runtime, Memory, Out-of-Scope, and Skill Commands
|
|
2
|
+
// Extracted from src/api/cli.ts for modular CLI architecture.
|
|
3
|
+
import { defineCommand } from 'citty';
|
|
4
|
+
import { dirname, join, resolve } from 'node:path';
|
|
5
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
6
|
+
import { getEngine, SCALE_DIR, PROJECT_DIR, isTruthyFlag, resolveScaleDirForProject, ensureDir } from './engineBootstrap.js';
|
|
7
|
+
import { ModelUsageLedger, RuntimeEvidenceLedger, SessionLedger, buildModelUsageInput, doctorRuntimeEvidence, evaluateFinalReportReadiness, } from '../runtime/index.js';
|
|
8
|
+
import { MemoryFabric, MemoryBrain, doctorMemoryFabric, renderContextPackMarkdown, renderMemoryLearningCandidateMarkdown, inspectMemoryProviders, recallMemoryProviders, settleMemoryLearning, useMemoryProvider, writeMemoryProvidersConfig, } from '../memory/index.js';
|
|
9
|
+
import { OutOfScopeStore } from '../workflow/OutOfScopeStore.js';
|
|
10
|
+
import { WorkflowArtifactWriter } from '../workflow/WorkflowArtifactWriter.js';
|
|
11
|
+
import { SkillDiscovery } from '../skills/SkillDiscovery.js';
|
|
12
|
+
import { inspectRequiredWorkflowSkills, inspectWorkflowSkills } from '../skills/SkillDoctor.js';
|
|
13
|
+
import { evaluateSkillInstallSafety, listSkillRepositoryEntries, recommendSkillWorkflow, renderSkillRepositoryMarkdown, } from '../skills/SkillRepository.js';
|
|
14
|
+
import { evaluateSkillRadar, inspectSkillSupplyChain, renderSkillRadarMarkdown, } from '../skills/SkillRadar.js';
|
|
15
|
+
import { createSkillPlan, evaluateSkillGate, loadSkillRoutingPolicy, skillPlanMarkdown } from '../skills/routing/index.js';
|
|
16
|
+
import { createThirdPartyUpdateReport } from '../workflow/UpgradeManager.js';
|
|
17
|
+
import { CerebrumManager } from '../knowledge/CerebrumManager.js';
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// Shared helpers
|
|
20
|
+
// ============================================================================
|
|
21
|
+
function parseCommaList(value) {
|
|
22
|
+
const raw = String(value ?? '').trim();
|
|
23
|
+
if (!raw)
|
|
24
|
+
return [];
|
|
25
|
+
return raw.split(',').map(item => item.trim()).filter(Boolean);
|
|
26
|
+
}
|
|
27
|
+
function parsePositiveIntArg(value, name) {
|
|
28
|
+
if (value === undefined || value === null || value === '')
|
|
29
|
+
return undefined;
|
|
30
|
+
const parsed = Number.parseInt(String(value), 10);
|
|
31
|
+
if (Number.isNaN(parsed) || parsed <= 0) {
|
|
32
|
+
throw new Error(`${name} must be a positive integer.`);
|
|
33
|
+
}
|
|
34
|
+
return parsed;
|
|
35
|
+
}
|
|
36
|
+
function parseSinceDays(value) {
|
|
37
|
+
if (value === undefined || value === null || value === '')
|
|
38
|
+
return undefined;
|
|
39
|
+
const parsed = Number.parseInt(String(value), 10);
|
|
40
|
+
if (Number.isNaN(parsed) || parsed <= 0)
|
|
41
|
+
return undefined;
|
|
42
|
+
return parsed;
|
|
43
|
+
}
|
|
44
|
+
function normalizeTaskArtifactLevel(value) {
|
|
45
|
+
const normalized = String(value ?? 'M').trim().toUpperCase();
|
|
46
|
+
if (normalized === 'S' || normalized === 'M' || normalized === 'L' || normalized === 'CRITICAL') {
|
|
47
|
+
return normalized;
|
|
48
|
+
}
|
|
49
|
+
throw new Error(`Invalid task level "${String(value)}"; expected S, M, L, or CRITICAL.`);
|
|
50
|
+
}
|
|
51
|
+
// ============================================================================
|
|
52
|
+
// runtime command - session ledger + completion evidence
|
|
53
|
+
// ============================================================================
|
|
54
|
+
function normalizeRuntimeEvidenceKind(value) {
|
|
55
|
+
const normalized = String(value ?? 'command').trim();
|
|
56
|
+
const allowed = ['command', 'gate', 'tool', 'skill', 'mcp', 'browser', 'desktop', 'manual', 'final-report'];
|
|
57
|
+
if (allowed.includes(normalized))
|
|
58
|
+
return normalized;
|
|
59
|
+
throw new Error(`Invalid runtime evidence kind "${normalized}"; expected ${allowed.join(', ')}.`);
|
|
60
|
+
}
|
|
61
|
+
function normalizeRuntimeEvidenceStatus(value) {
|
|
62
|
+
const normalized = String(value ?? '').trim();
|
|
63
|
+
if (normalized === 'passed' || normalized === 'failed' || normalized === 'skipped')
|
|
64
|
+
return normalized;
|
|
65
|
+
throw new Error(`Invalid runtime evidence status "${normalized}"; expected passed, failed, or skipped.`);
|
|
66
|
+
}
|
|
67
|
+
function normalizeRuntimeSessionStatus(value) {
|
|
68
|
+
const normalized = String(value ?? 'completed').trim();
|
|
69
|
+
if (normalized === 'active' || normalized === 'completed' || normalized === 'failed' || normalized === 'abandoned')
|
|
70
|
+
return normalized;
|
|
71
|
+
throw new Error(`Invalid runtime session status "${normalized}"; expected active, completed, failed, or abandoned.`);
|
|
72
|
+
}
|
|
73
|
+
function parseNonNegativeNumberArg(value, name) {
|
|
74
|
+
if (value === undefined || value === null || value === '')
|
|
75
|
+
return undefined;
|
|
76
|
+
const parsed = Number(value);
|
|
77
|
+
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
78
|
+
throw new Error(`${name} must be a non-negative number.`);
|
|
79
|
+
}
|
|
80
|
+
return parsed;
|
|
81
|
+
}
|
|
82
|
+
function parseJsonArg(value, name) {
|
|
83
|
+
try {
|
|
84
|
+
return JSON.parse(String(value ?? 'null'));
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
throw new Error(`${name} must be valid JSON.`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function parseMetadataJson(value, name = '--metadata-json') {
|
|
91
|
+
const parsed = parseJsonArg(value ?? '{}', name);
|
|
92
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
93
|
+
throw new Error(`${name} must be a JSON object.`);
|
|
94
|
+
}
|
|
95
|
+
return parsed;
|
|
96
|
+
}
|
|
97
|
+
function hasModelUsageArgs(args) {
|
|
98
|
+
return [
|
|
99
|
+
'provider',
|
|
100
|
+
'model',
|
|
101
|
+
'usage-json',
|
|
102
|
+
'usage-file',
|
|
103
|
+
'input-tokens',
|
|
104
|
+
'output-tokens',
|
|
105
|
+
'cache-eligible-tokens',
|
|
106
|
+
'cache-creation-input-tokens',
|
|
107
|
+
'cache-read-input-tokens',
|
|
108
|
+
'cached-tokens',
|
|
109
|
+
'estimated-cost-usd',
|
|
110
|
+
].some(key => args[key] !== undefined && args[key] !== '');
|
|
111
|
+
}
|
|
112
|
+
function buildModelUsageRecordInput(args, defaults = {}) {
|
|
113
|
+
const usagePayload = args['usage-file']
|
|
114
|
+
? parseJsonArg(readFileSync(resolve(PROJECT_DIR, String(args['usage-file'])), 'utf-8'), '--usage-file')
|
|
115
|
+
: args['usage-json']
|
|
116
|
+
? parseJsonArg(args['usage-json'], '--usage-json')
|
|
117
|
+
: undefined;
|
|
118
|
+
const provider = String(args.provider ?? defaults.provider ?? '').trim();
|
|
119
|
+
if (!provider)
|
|
120
|
+
throw new Error('Model usage recording requires --provider.');
|
|
121
|
+
return buildModelUsageInput({
|
|
122
|
+
provider,
|
|
123
|
+
model: args.model ? String(args.model) : undefined,
|
|
124
|
+
taskId: args['task-id'] ? String(args['task-id']) : defaults.taskId,
|
|
125
|
+
sessionId: args['session-id'] ? String(args['session-id']) : defaults.sessionId,
|
|
126
|
+
inputTokens: parseNonNegativeNumberArg(args['input-tokens'], '--input-tokens'),
|
|
127
|
+
outputTokens: parseNonNegativeNumberArg(args['output-tokens'], '--output-tokens'),
|
|
128
|
+
cacheEligibleTokens: parseNonNegativeNumberArg(args['cache-eligible-tokens'], '--cache-eligible-tokens'),
|
|
129
|
+
cacheCreationInputTokens: parseNonNegativeNumberArg(args['cache-creation-input-tokens'], '--cache-creation-input-tokens'),
|
|
130
|
+
cacheReadInputTokens: parseNonNegativeNumberArg(args['cache-read-input-tokens'], '--cache-read-input-tokens'),
|
|
131
|
+
cachedTokens: parseNonNegativeNumberArg(args['cached-tokens'], '--cached-tokens'),
|
|
132
|
+
estimatedCostUsd: parseNonNegativeNumberArg(args['estimated-cost-usd'], '--estimated-cost-usd'),
|
|
133
|
+
metadata: args['metadata-json'] !== undefined ? parseMetadataJson(args['metadata-json']) : undefined,
|
|
134
|
+
timestamp: args.timestamp ? String(args.timestamp) : undefined,
|
|
135
|
+
usagePayload,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
const tokenRecord = defineCommand({
|
|
139
|
+
meta: { name: 'record', description: 'Record real model usage from provider usage payloads or explicit token counts' },
|
|
140
|
+
args: {
|
|
141
|
+
provider: { type: 'string', required: true, description: 'Model provider: anthropic, openai, codex, etc.' },
|
|
142
|
+
model: { type: 'string', description: 'Optional model id' },
|
|
143
|
+
'task-id': { type: 'string', description: 'Task id linked to this model usage' },
|
|
144
|
+
'session-id': { type: 'string', description: 'Session id linked to this model usage' },
|
|
145
|
+
'usage-json': { type: 'string', description: 'Raw provider response or usage JSON to normalize into the usage ledger' },
|
|
146
|
+
'usage-file': { type: 'string', description: 'Path to a JSON file containing a raw provider response or usage payload' },
|
|
147
|
+
'input-tokens': { type: 'string', description: 'Explicit input token count; overrides usage JSON when provided' },
|
|
148
|
+
'output-tokens': { type: 'string', description: 'Explicit output token count; overrides usage JSON when provided' },
|
|
149
|
+
'cache-eligible-tokens': { type: 'string', description: 'Explicit cache-eligible token count' },
|
|
150
|
+
'cache-creation-input-tokens': { type: 'string', description: 'Explicit Anthropic cache creation token count' },
|
|
151
|
+
'cache-read-input-tokens': { type: 'string', description: 'Explicit Anthropic cache read token count' },
|
|
152
|
+
'cached-tokens': { type: 'string', description: 'Explicit OpenAI cached token count' },
|
|
153
|
+
'estimated-cost-usd': { type: 'string', description: 'Optional estimated cost in USD' },
|
|
154
|
+
timestamp: { type: 'string', description: 'Optional ISO timestamp' },
|
|
155
|
+
'metadata-json': { type: 'string', default: '{}', description: 'Additional JSON metadata' },
|
|
156
|
+
json: { type: 'boolean', default: false },
|
|
157
|
+
},
|
|
158
|
+
run({ args }) {
|
|
159
|
+
const record = new ModelUsageLedger(SCALE_DIR).record(buildModelUsageRecordInput(args));
|
|
160
|
+
if (args.json) {
|
|
161
|
+
console.log(JSON.stringify(record, null, 2));
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
console.log(`Model usage recorded: ${record.id}`);
|
|
165
|
+
console.log(` Provider: ${record.provider}`);
|
|
166
|
+
console.log(` Model: ${record.model ?? 'unknown'}`);
|
|
167
|
+
console.log(` Tokens: input ${record.inputTokens}, output ${record.outputTokens}, total ${record.totalTokens}`);
|
|
168
|
+
if (record.cacheSavingsTokens > 0)
|
|
169
|
+
console.log(` Cache savings: ${record.cacheSavingsTokens} tokens`);
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
const tokenReport = defineCommand({
|
|
173
|
+
meta: { name: 'report', description: 'Summarize recorded model usage by day, provider, model, and task' },
|
|
174
|
+
args: {
|
|
175
|
+
day: { type: 'string', description: 'Exact UTC day in YYYY-MM-DD format' },
|
|
176
|
+
since: { type: 'string', description: 'ISO timestamp lower bound' },
|
|
177
|
+
until: { type: 'string', description: 'ISO timestamp upper bound' },
|
|
178
|
+
'since-days': { type: 'string', default: '7d', description: 'Relative time window when day/since/until are omitted; use all to disable' },
|
|
179
|
+
provider: { type: 'string', description: 'Filter by provider' },
|
|
180
|
+
model: { type: 'string', description: 'Filter by model id' },
|
|
181
|
+
'task-id': { type: 'string', description: 'Filter by task id' },
|
|
182
|
+
'session-id': { type: 'string', description: 'Filter by session id' },
|
|
183
|
+
limit: { type: 'string', description: 'Maximum recent records to include in the report; defaults to 20' },
|
|
184
|
+
json: { type: 'boolean', default: false },
|
|
185
|
+
},
|
|
186
|
+
run({ args }) {
|
|
187
|
+
const limit = parsePositiveIntArg(args.limit, '--limit');
|
|
188
|
+
const sinceDays = args.day || args.since || args.until ? undefined : parseSinceDays(args['since-days']) ?? 7;
|
|
189
|
+
const since = args.since
|
|
190
|
+
? String(args.since)
|
|
191
|
+
: sinceDays
|
|
192
|
+
? new Date(Date.now() - sinceDays * 24 * 60 * 60 * 1000).toISOString()
|
|
193
|
+
: undefined;
|
|
194
|
+
const report = new ModelUsageLedger(SCALE_DIR).report({
|
|
195
|
+
day: args.day ? String(args.day) : undefined,
|
|
196
|
+
since,
|
|
197
|
+
until: args.until ? String(args.until) : undefined,
|
|
198
|
+
provider: args.provider ? String(args.provider) : undefined,
|
|
199
|
+
model: args.model ? String(args.model) : undefined,
|
|
200
|
+
taskId: args['task-id'] ? String(args['task-id']) : undefined,
|
|
201
|
+
sessionId: args['session-id'] ? String(args['session-id']) : undefined,
|
|
202
|
+
limit,
|
|
203
|
+
});
|
|
204
|
+
if (args.json) {
|
|
205
|
+
console.log(JSON.stringify(report, null, 2));
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
console.log('SCALE Token Report');
|
|
209
|
+
if (report.filters.day)
|
|
210
|
+
console.log(` Day: ${report.filters.day}`);
|
|
211
|
+
else if (report.filters.since || report.filters.until)
|
|
212
|
+
console.log(` Window: ${report.filters.since ?? '-inf'} -> ${report.filters.until ?? 'now'}`);
|
|
213
|
+
console.log(` Records: ${report.summary.totalRecords}`);
|
|
214
|
+
console.log(` Tokens: input ${report.summary.totalInputTokens}, output ${report.summary.totalOutputTokens}, total ${report.summary.totalTokens}`);
|
|
215
|
+
console.log(` Cache: eligible ${report.summary.cacheEligibleTokens}, create ${report.summary.cacheCreationInputTokens}, read ${report.summary.cacheReadInputTokens}, cached ${report.summary.cachedTokens}, saved ${report.summary.cacheSavingsTokens}`);
|
|
216
|
+
if (report.summary.estimatedCostUsd !== undefined)
|
|
217
|
+
console.log(` Estimated cost: $${report.summary.estimatedCostUsd.toFixed(6)}`);
|
|
218
|
+
for (const row of report.byProvider.slice(0, 5)) {
|
|
219
|
+
console.log(` Provider ${row.key}: ${row.records} record(s), ${row.totalTokens} total tokens, ${row.cacheSavingsTokens} saved`);
|
|
220
|
+
}
|
|
221
|
+
for (const row of report.byModel.slice(0, 5)) {
|
|
222
|
+
console.log(` Model ${row.key}: ${row.records} record(s), ${row.totalTokens} total tokens`);
|
|
223
|
+
}
|
|
224
|
+
for (const row of report.byTask.slice(0, 5)) {
|
|
225
|
+
console.log(` Task ${row.key}: ${row.records} record(s), ${row.totalTokens} total tokens`);
|
|
226
|
+
}
|
|
227
|
+
for (const row of report.records.slice(0, 10)) {
|
|
228
|
+
console.log(` Recent ${row.timestamp}: ${row.provider}/${row.model ?? 'unknown'} task=${row.taskId ?? '-'} total=${row.totalTokens}`);
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
const token = defineCommand({
|
|
233
|
+
meta: { name: 'token', description: 'Record and audit real model token usage' },
|
|
234
|
+
subCommands: { record: tokenRecord, report: tokenReport },
|
|
235
|
+
});
|
|
236
|
+
const runtimeStart = defineCommand({
|
|
237
|
+
meta: { name: 'start', description: 'Start a runtime session ledger' },
|
|
238
|
+
args: {
|
|
239
|
+
'session-id': { type: 'string', description: 'Session id; generated when omitted' },
|
|
240
|
+
'task-id': { type: 'string', description: 'Task id linked to this session' },
|
|
241
|
+
agent: { type: 'string', description: 'Agent name' },
|
|
242
|
+
level: { type: 'string', default: 'M', description: 'Task level: S, M, L, or CRITICAL' },
|
|
243
|
+
summary: { type: 'string', description: 'Short session summary' },
|
|
244
|
+
json: { type: 'boolean', default: false },
|
|
245
|
+
},
|
|
246
|
+
run({ args }) {
|
|
247
|
+
const ledger = new SessionLedger({ projectDir: PROJECT_DIR, scaleDir: SCALE_DIR });
|
|
248
|
+
const session = ledger.start({
|
|
249
|
+
sessionId: args['session-id'],
|
|
250
|
+
taskId: args['task-id'],
|
|
251
|
+
agent: args.agent,
|
|
252
|
+
level: normalizeTaskArtifactLevel(args.level),
|
|
253
|
+
summary: args.summary,
|
|
254
|
+
});
|
|
255
|
+
if (args.json) {
|
|
256
|
+
console.log(JSON.stringify(session, null, 2));
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
console.log(`Runtime session started: ${session.sessionId}`);
|
|
260
|
+
if (session.taskId)
|
|
261
|
+
console.log(` Task: ${session.taskId}`);
|
|
262
|
+
if (session.level)
|
|
263
|
+
console.log(` Level: ${session.level}`);
|
|
264
|
+
console.log(` Events: ${ledger.sessionFile(session.sessionId)}`);
|
|
265
|
+
},
|
|
266
|
+
});
|
|
267
|
+
const runtimeEnd = defineCommand({
|
|
268
|
+
meta: { name: 'end', description: 'End the current or named runtime session' },
|
|
269
|
+
args: {
|
|
270
|
+
'session-id': { type: 'string', description: 'Session id; current session is used when omitted' },
|
|
271
|
+
status: { type: 'string', default: 'completed', description: 'completed, failed, or abandoned' },
|
|
272
|
+
summary: { type: 'string', description: 'Completion summary' },
|
|
273
|
+
json: { type: 'boolean', default: false },
|
|
274
|
+
},
|
|
275
|
+
run({ args }) {
|
|
276
|
+
const ledger = new SessionLedger({ projectDir: PROJECT_DIR, scaleDir: SCALE_DIR });
|
|
277
|
+
const sessionId = args['session-id'] ?? ledger.current()?.sessionId;
|
|
278
|
+
if (!sessionId) {
|
|
279
|
+
console.error('No runtime session id provided and no current runtime session exists.');
|
|
280
|
+
process.exit(1);
|
|
281
|
+
}
|
|
282
|
+
const session = ledger.end(sessionId, normalizeRuntimeSessionStatus(args.status), args.summary);
|
|
283
|
+
if (args.json) {
|
|
284
|
+
console.log(JSON.stringify(session, null, 2));
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
console.log(`Runtime session ended: ${session.sessionId}`);
|
|
288
|
+
console.log(` Status: ${session.status}`);
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
const runtimeRecord = defineCommand({
|
|
292
|
+
meta: { name: 'record', description: 'Record command, gate, tool, browser, skill, or manual runtime evidence' },
|
|
293
|
+
args: {
|
|
294
|
+
'task-id': { type: 'string', description: 'Task id linked to this evidence' },
|
|
295
|
+
'session-id': { type: 'string', description: 'Session id linked to this evidence' },
|
|
296
|
+
kind: { type: 'string', default: 'command', description: 'command, gate, tool, skill, mcp, browser, desktop, manual, final-report' },
|
|
297
|
+
title: { type: 'string', required: true, description: 'Evidence title' },
|
|
298
|
+
status: { type: 'string', required: true, description: 'passed, failed, or skipped' },
|
|
299
|
+
command: { type: 'string', description: 'Exact command or tool invocation, with secrets redacted by SCALE' },
|
|
300
|
+
'exit-code': { type: 'string', description: 'Exit code when applicable' },
|
|
301
|
+
summary: { type: 'string', required: true, description: 'Short output summary' },
|
|
302
|
+
artifacts: { type: 'string', description: 'Comma-separated artifact paths' },
|
|
303
|
+
provider: { type: 'string', description: 'Optional model provider when attaching model usage: anthropic, openai, codex, etc.' },
|
|
304
|
+
model: { type: 'string', description: 'Optional model id when attaching model usage' },
|
|
305
|
+
'usage-json': { type: 'string', description: 'Raw provider response or usage JSON to normalize into the usage ledger' },
|
|
306
|
+
'usage-file': { type: 'string', description: 'Path to a JSON file containing a raw provider response or usage payload' },
|
|
307
|
+
'input-tokens': { type: 'string', description: 'Explicit input token count; overrides usage JSON when provided' },
|
|
308
|
+
'output-tokens': { type: 'string', description: 'Explicit output token count; overrides usage JSON when provided' },
|
|
309
|
+
'cache-eligible-tokens': { type: 'string', description: 'Explicit cache-eligible token count' },
|
|
310
|
+
'cache-creation-input-tokens': { type: 'string', description: 'Explicit Anthropic cache creation token count' },
|
|
311
|
+
'cache-read-input-tokens': { type: 'string', description: 'Explicit Anthropic cache read token count' },
|
|
312
|
+
'cached-tokens': { type: 'string', description: 'Explicit OpenAI cached token count' },
|
|
313
|
+
'estimated-cost-usd': { type: 'string', description: 'Optional estimated cost in USD' },
|
|
314
|
+
timestamp: { type: 'string', description: 'Optional ISO timestamp for the usage record' },
|
|
315
|
+
'metadata-json': { type: 'string', default: '{}', description: 'Additional JSON metadata' },
|
|
316
|
+
json: { type: 'boolean', default: false },
|
|
317
|
+
},
|
|
318
|
+
run({ args }) {
|
|
319
|
+
const current = new SessionLedger({ projectDir: PROJECT_DIR, scaleDir: SCALE_DIR }).current();
|
|
320
|
+
let metadata = {};
|
|
321
|
+
try {
|
|
322
|
+
metadata = JSON.parse(String(args['metadata-json'] ?? '{}'));
|
|
323
|
+
}
|
|
324
|
+
catch {
|
|
325
|
+
console.error('--metadata-json must be valid JSON.');
|
|
326
|
+
process.exit(1);
|
|
327
|
+
}
|
|
328
|
+
const exitCode = args['exit-code'] === undefined || args['exit-code'] === ''
|
|
329
|
+
? undefined
|
|
330
|
+
: Number.parseInt(String(args['exit-code']), 10);
|
|
331
|
+
if (exitCode !== undefined && Number.isNaN(exitCode)) {
|
|
332
|
+
console.error('--exit-code must be a number.');
|
|
333
|
+
process.exit(1);
|
|
334
|
+
}
|
|
335
|
+
const ledger = new RuntimeEvidenceLedger({ projectDir: PROJECT_DIR, scaleDir: SCALE_DIR });
|
|
336
|
+
const record = ledger.record({
|
|
337
|
+
taskId: args['task-id'] ?? current?.taskId,
|
|
338
|
+
sessionId: args['session-id'] ?? current?.sessionId,
|
|
339
|
+
kind: normalizeRuntimeEvidenceKind(args.kind),
|
|
340
|
+
title: args.title,
|
|
341
|
+
status: normalizeRuntimeEvidenceStatus(args.status),
|
|
342
|
+
command: args.command,
|
|
343
|
+
exitCode,
|
|
344
|
+
summary: args.summary,
|
|
345
|
+
artifacts: parseCommaList(args.artifacts),
|
|
346
|
+
metadata,
|
|
347
|
+
});
|
|
348
|
+
if (record.sessionId) {
|
|
349
|
+
new SessionLedger({ projectDir: PROJECT_DIR, scaleDir: SCALE_DIR }).append(record.sessionId, {
|
|
350
|
+
type: 'evidence.recorded',
|
|
351
|
+
message: `${record.status}: ${record.title}`,
|
|
352
|
+
data: {
|
|
353
|
+
evidenceId: record.id,
|
|
354
|
+
kind: record.kind,
|
|
355
|
+
taskId: record.taskId,
|
|
356
|
+
},
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
const usageRecord = hasModelUsageArgs(args)
|
|
360
|
+
? new ModelUsageLedger(SCALE_DIR).record(buildModelUsageRecordInput(args, {
|
|
361
|
+
taskId: record.taskId,
|
|
362
|
+
sessionId: record.sessionId,
|
|
363
|
+
}))
|
|
364
|
+
: undefined;
|
|
365
|
+
if (args.json) {
|
|
366
|
+
console.log(JSON.stringify(usageRecord ? { evidence: record, usage: usageRecord } : record, null, 2));
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
console.log(`Runtime evidence recorded: ${record.id}`);
|
|
370
|
+
console.log(` Status: ${record.status}`);
|
|
371
|
+
console.log(` Kind: ${record.kind}`);
|
|
372
|
+
if (usageRecord) {
|
|
373
|
+
console.log(` Model usage: ${usageRecord.provider}/${usageRecord.model ?? 'unknown'} ${usageRecord.totalTokens} total tokens`);
|
|
374
|
+
if (usageRecord.cacheSavingsTokens > 0)
|
|
375
|
+
console.log(` Cache savings: ${usageRecord.cacheSavingsTokens} tokens`);
|
|
376
|
+
}
|
|
377
|
+
if (record.redactionApplied)
|
|
378
|
+
console.log(' Redaction: applied');
|
|
379
|
+
},
|
|
380
|
+
});
|
|
381
|
+
const runtimeDoctor = defineCommand({
|
|
382
|
+
meta: { name: 'doctor', description: 'Check runtime session and completion evidence' },
|
|
383
|
+
args: {
|
|
384
|
+
'task-id': { type: 'string', description: 'Task id to inspect' },
|
|
385
|
+
'session-id': { type: 'string', description: 'Session id to inspect' },
|
|
386
|
+
level: { type: 'string', default: 'M', description: 'Task level: S, M, L, or CRITICAL' },
|
|
387
|
+
json: { type: 'boolean', default: false },
|
|
388
|
+
},
|
|
389
|
+
run({ args }) {
|
|
390
|
+
const report = doctorRuntimeEvidence({
|
|
391
|
+
projectDir: PROJECT_DIR,
|
|
392
|
+
scaleDir: SCALE_DIR,
|
|
393
|
+
taskId: args['task-id'],
|
|
394
|
+
sessionId: args['session-id'],
|
|
395
|
+
level: normalizeTaskArtifactLevel(args.level),
|
|
396
|
+
});
|
|
397
|
+
if (args.json) {
|
|
398
|
+
console.log(JSON.stringify(report, null, 2));
|
|
399
|
+
if (report.blocked)
|
|
400
|
+
process.exitCode = 1;
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
console.log('\nSCALE Runtime Doctor');
|
|
404
|
+
console.log(` Evidence: ${report.evidence.total} total, ${report.evidence.passed} passed, ${report.evidence.failed} failed, ${report.evidence.skipped} skipped`);
|
|
405
|
+
for (const check of report.checks) {
|
|
406
|
+
console.log(` [${check.status.toUpperCase()}] ${check.name}: ${check.message}`);
|
|
407
|
+
if (check.fix)
|
|
408
|
+
console.log(` Fix: ${check.fix}`);
|
|
409
|
+
}
|
|
410
|
+
if (report.blocked)
|
|
411
|
+
process.exitCode = 1;
|
|
412
|
+
},
|
|
413
|
+
});
|
|
414
|
+
const runtimeFinalCheck = defineCommand({
|
|
415
|
+
meta: { name: 'final-check', description: 'Block final delivery claims without passed runtime evidence' },
|
|
416
|
+
args: {
|
|
417
|
+
'task-id': { type: 'string', description: 'Task id to inspect' },
|
|
418
|
+
'session-id': { type: 'string', description: 'Session id to inspect' },
|
|
419
|
+
level: { type: 'string', default: 'M', description: 'Task level: S, M, L, or CRITICAL' },
|
|
420
|
+
json: { type: 'boolean', default: false },
|
|
421
|
+
},
|
|
422
|
+
run({ args }) {
|
|
423
|
+
const readiness = evaluateFinalReportReadiness({
|
|
424
|
+
projectDir: PROJECT_DIR,
|
|
425
|
+
scaleDir: SCALE_DIR,
|
|
426
|
+
taskId: args['task-id'],
|
|
427
|
+
sessionId: args['session-id'],
|
|
428
|
+
level: normalizeTaskArtifactLevel(args.level),
|
|
429
|
+
});
|
|
430
|
+
if (args.json) {
|
|
431
|
+
console.log(JSON.stringify(readiness, null, 2));
|
|
432
|
+
if (readiness.blocked)
|
|
433
|
+
process.exitCode = 1;
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
console.log('\nSCALE Runtime Final Check');
|
|
437
|
+
console.log(` Ready: ${readiness.ready}`);
|
|
438
|
+
for (const reason of readiness.reasons)
|
|
439
|
+
console.log(` [BLOCKER] ${reason}`);
|
|
440
|
+
if (readiness.blocked)
|
|
441
|
+
process.exitCode = 1;
|
|
442
|
+
},
|
|
443
|
+
});
|
|
444
|
+
export { token };
|
|
445
|
+
export const runtimeCommand = defineCommand({
|
|
446
|
+
meta: { name: 'runtime', description: 'Runtime session ledger and completion evidence governance' },
|
|
447
|
+
subCommands: {
|
|
448
|
+
start: runtimeStart,
|
|
449
|
+
end: runtimeEnd,
|
|
450
|
+
record: runtimeRecord,
|
|
451
|
+
doctor: runtimeDoctor,
|
|
452
|
+
'final-check': runtimeFinalCheck,
|
|
453
|
+
},
|
|
454
|
+
});
|
|
455
|
+
// ============================================================================
|
|
456
|
+
// memory command - runtime evidence + knowledge + graph context packs
|
|
457
|
+
// ============================================================================
|
|
458
|
+
function parseMemoryBudget(value) {
|
|
459
|
+
if (value === undefined || value === null || value === '')
|
|
460
|
+
return undefined;
|
|
461
|
+
const parsed = Number.parseInt(String(value), 10);
|
|
462
|
+
if (Number.isNaN(parsed) || parsed <= 0) {
|
|
463
|
+
throw new Error('--budget must be a positive integer.');
|
|
464
|
+
}
|
|
465
|
+
return parsed;
|
|
466
|
+
}
|
|
467
|
+
function normalizeMemorySource(value) {
|
|
468
|
+
const normalized = String(value ?? 'evidence').trim().toLowerCase();
|
|
469
|
+
if (normalized === 'evidence' || normalized === 'candidate' || normalized === 'failure')
|
|
470
|
+
return normalized;
|
|
471
|
+
throw new Error('--from must be evidence, candidate, or failure.');
|
|
472
|
+
}
|
|
473
|
+
function normalizeMemoryNodeType(value) {
|
|
474
|
+
if (value === undefined || value === null || value === '')
|
|
475
|
+
return undefined;
|
|
476
|
+
const normalized = String(value).trim().toLowerCase();
|
|
477
|
+
if (normalized === 'fact' || normalized === 'decision' || normalized === 'incident' || normalized === 'relation' || normalized === 'contradiction')
|
|
478
|
+
return normalized;
|
|
479
|
+
throw new Error('--type must be fact, decision, incident, relation, or contradiction.');
|
|
480
|
+
}
|
|
481
|
+
function normalizeMemoryScope(value) {
|
|
482
|
+
if (value === undefined || value === null || value === '')
|
|
483
|
+
return undefined;
|
|
484
|
+
const normalized = String(value).trim().toLowerCase();
|
|
485
|
+
if (normalized === 'project' || normalized === 'workspace' || normalized === 'global-candidate')
|
|
486
|
+
return normalized;
|
|
487
|
+
throw new Error('--scope must be project, workspace, or global-candidate.');
|
|
488
|
+
}
|
|
489
|
+
function memoryBrain() {
|
|
490
|
+
return new MemoryBrain({ projectDir: PROJECT_DIR, scaleDir: SCALE_DIR });
|
|
491
|
+
}
|
|
492
|
+
const memoryPack = defineCommand({
|
|
493
|
+
meta: { name: 'pack', description: 'Build a compact context pack from runtime evidence, session events, knowledge, and graph status' },
|
|
494
|
+
args: {
|
|
495
|
+
task: { type: 'string', required: true, description: 'Current task or question' },
|
|
496
|
+
'task-id': { type: 'string', description: 'Task id to scope evidence and session data' },
|
|
497
|
+
'session-id': { type: 'string', description: 'Session id to scope session events' },
|
|
498
|
+
level: { type: 'string', default: 'M', description: 'Task level: S, M, L, or CRITICAL' },
|
|
499
|
+
files: { type: 'string', description: 'Comma-separated files or modules in scope' },
|
|
500
|
+
budget: { type: 'string', description: 'Maximum estimated tokens for the context pack' },
|
|
501
|
+
json: { type: 'boolean', default: false },
|
|
502
|
+
},
|
|
503
|
+
async run({ args }) {
|
|
504
|
+
let budgetTokens;
|
|
505
|
+
try {
|
|
506
|
+
budgetTokens = parseMemoryBudget(args.budget);
|
|
507
|
+
}
|
|
508
|
+
catch (e) {
|
|
509
|
+
console.error(e.message);
|
|
510
|
+
process.exit(1);
|
|
511
|
+
}
|
|
512
|
+
const { kb } = getEngine();
|
|
513
|
+
const pack = await new MemoryFabric({
|
|
514
|
+
projectDir: PROJECT_DIR,
|
|
515
|
+
scaleDir: SCALE_DIR,
|
|
516
|
+
knowledgeBase: kb,
|
|
517
|
+
}).createContextPack({
|
|
518
|
+
task: args.task,
|
|
519
|
+
taskId: args['task-id'],
|
|
520
|
+
sessionId: args['session-id'],
|
|
521
|
+
level: normalizeTaskArtifactLevel(args.level),
|
|
522
|
+
files: parseCommaList(args.files),
|
|
523
|
+
budgetTokens,
|
|
524
|
+
});
|
|
525
|
+
if (args.json) {
|
|
526
|
+
console.log(JSON.stringify(pack, null, 2));
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
console.log(renderContextPackMarkdown(pack));
|
|
530
|
+
},
|
|
531
|
+
});
|
|
532
|
+
const memoryDoctor = defineCommand({
|
|
533
|
+
meta: { name: 'doctor', description: 'Check whether a task context pack is available and within token budget' },
|
|
534
|
+
args: {
|
|
535
|
+
task: { type: 'string', required: true, description: 'Current task or question' },
|
|
536
|
+
'task-id': { type: 'string', description: 'Task id to scope evidence and session data' },
|
|
537
|
+
'session-id': { type: 'string', description: 'Session id to scope session events' },
|
|
538
|
+
level: { type: 'string', default: 'M', description: 'Task level: S, M, L, or CRITICAL' },
|
|
539
|
+
files: { type: 'string', description: 'Comma-separated files or modules in scope' },
|
|
540
|
+
budget: { type: 'string', description: 'Maximum estimated tokens for the context pack' },
|
|
541
|
+
json: { type: 'boolean', default: false },
|
|
542
|
+
},
|
|
543
|
+
async run({ args }) {
|
|
544
|
+
let budgetTokens;
|
|
545
|
+
try {
|
|
546
|
+
budgetTokens = parseMemoryBudget(args.budget);
|
|
547
|
+
}
|
|
548
|
+
catch (e) {
|
|
549
|
+
console.error(e.message);
|
|
550
|
+
process.exit(1);
|
|
551
|
+
}
|
|
552
|
+
const { kb } = getEngine();
|
|
553
|
+
const report = await doctorMemoryFabric({
|
|
554
|
+
projectDir: PROJECT_DIR,
|
|
555
|
+
scaleDir: SCALE_DIR,
|
|
556
|
+
knowledgeBase: kb,
|
|
557
|
+
}, {
|
|
558
|
+
task: args.task,
|
|
559
|
+
taskId: args['task-id'],
|
|
560
|
+
sessionId: args['session-id'],
|
|
561
|
+
level: normalizeTaskArtifactLevel(args.level),
|
|
562
|
+
files: parseCommaList(args.files),
|
|
563
|
+
budgetTokens,
|
|
564
|
+
});
|
|
565
|
+
if (args.json) {
|
|
566
|
+
console.log(JSON.stringify(report, null, 2));
|
|
567
|
+
if (!report.ok)
|
|
568
|
+
process.exitCode = 1;
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
console.log('\nSCALE Memory Doctor');
|
|
572
|
+
console.log(` Budget: ${report.pack.budget.used}/${report.pack.budget.limit} estimated tokens`);
|
|
573
|
+
for (const check of report.checks) {
|
|
574
|
+
console.log(` [${check.status.toUpperCase()}] ${check.name}: ${check.message}`);
|
|
575
|
+
}
|
|
576
|
+
if (!report.ok)
|
|
577
|
+
process.exitCode = 1;
|
|
578
|
+
},
|
|
579
|
+
});
|
|
580
|
+
const memoryCerebrum = defineCommand({
|
|
581
|
+
meta: { name: 'cerebrum', description: 'Maintain .scale/cerebrum.md do-not-repeat rules and preferences' },
|
|
582
|
+
args: {
|
|
583
|
+
type: { type: 'string', description: 'Optional entry type: preference or do-not-repeat' },
|
|
584
|
+
pattern: { type: 'string', description: 'Pattern for do-not-repeat entries' },
|
|
585
|
+
description: { type: 'string', description: 'Entry description or preference text' },
|
|
586
|
+
tags: { type: 'string', description: 'Comma-separated tags for preferences' },
|
|
587
|
+
write: { type: 'boolean', default: false, description: 'Write .scale/cerebrum.md' },
|
|
588
|
+
json: { type: 'boolean', default: false },
|
|
589
|
+
},
|
|
590
|
+
async run({ args }) {
|
|
591
|
+
const { kb } = getEngine();
|
|
592
|
+
const manager = new CerebrumManager(kb);
|
|
593
|
+
const type = args.type ? String(args.type).toLowerCase() : '';
|
|
594
|
+
let created;
|
|
595
|
+
if (type) {
|
|
596
|
+
if (type === 'do-not-repeat' || type === 'do_not_repeat' || type === 'dnr') {
|
|
597
|
+
const pattern = String(args.pattern ?? '').trim();
|
|
598
|
+
const description = String(args.description ?? '').trim();
|
|
599
|
+
if (!pattern || !description) {
|
|
600
|
+
console.error('memory cerebrum --type do-not-repeat requires --pattern and --description.');
|
|
601
|
+
process.exit(1);
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
created = await manager.addDoNotRepeat(pattern, description);
|
|
605
|
+
}
|
|
606
|
+
else if (type === 'preference' || type === 'pref') {
|
|
607
|
+
const description = String(args.description ?? args.pattern ?? '').trim();
|
|
608
|
+
if (!description) {
|
|
609
|
+
console.error('memory cerebrum --type preference requires --description.');
|
|
610
|
+
process.exit(1);
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
created = await manager.addPreference(description, parseCommaList(args.tags));
|
|
614
|
+
}
|
|
615
|
+
else {
|
|
616
|
+
console.error('memory cerebrum --type must be preference or do-not-repeat.');
|
|
617
|
+
process.exit(1);
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
const entries = await manager.loadAll();
|
|
622
|
+
const outputPath = join(SCALE_DIR, 'cerebrum.md');
|
|
623
|
+
const shouldWrite = isTruthyFlag(args.write) || Boolean(created);
|
|
624
|
+
if (shouldWrite) {
|
|
625
|
+
ensureDir(SCALE_DIR);
|
|
626
|
+
writeFileSync(outputPath, manager.toMarkdown(), 'utf-8');
|
|
627
|
+
}
|
|
628
|
+
const summary = {
|
|
629
|
+
total: entries.length,
|
|
630
|
+
doNotRepeat: entries.filter(entry => entry.type === 'do_not_repeat').length,
|
|
631
|
+
preferences: entries.filter(entry => entry.type === 'preference').length,
|
|
632
|
+
};
|
|
633
|
+
if (args.json) {
|
|
634
|
+
console.log(JSON.stringify({
|
|
635
|
+
ok: true,
|
|
636
|
+
outputPath: shouldWrite ? outputPath : undefined,
|
|
637
|
+
created,
|
|
638
|
+
summary,
|
|
639
|
+
}, null, 2));
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
console.log('SCALE Cerebrum');
|
|
643
|
+
console.log(` Do-not-repeat: ${summary.doNotRepeat}`);
|
|
644
|
+
console.log(` Preferences: ${summary.preferences}`);
|
|
645
|
+
if (shouldWrite)
|
|
646
|
+
console.log(` Wrote: ${outputPath}`);
|
|
647
|
+
},
|
|
648
|
+
});
|
|
649
|
+
const memorySettle = defineCommand({
|
|
650
|
+
meta: { name: 'settle', description: 'Settle runtime evidence into a reviewable memory learning candidate' },
|
|
651
|
+
args: {
|
|
652
|
+
task: { type: 'string', required: true, description: 'Current task or question' },
|
|
653
|
+
'task-id': { type: 'string', description: 'Task id to scope evidence and session data' },
|
|
654
|
+
'session-id': { type: 'string', description: 'Session id to scope session events' },
|
|
655
|
+
level: { type: 'string', default: 'M', description: 'Task level: S, M, L, or CRITICAL' },
|
|
656
|
+
files: { type: 'string', description: 'Comma-separated files or modules in scope' },
|
|
657
|
+
budget: { type: 'string', description: 'Maximum estimated tokens for the context pack' },
|
|
658
|
+
json: { type: 'boolean', default: false },
|
|
659
|
+
},
|
|
660
|
+
async run({ args }) {
|
|
661
|
+
let budgetTokens;
|
|
662
|
+
try {
|
|
663
|
+
budgetTokens = parseMemoryBudget(args.budget);
|
|
664
|
+
}
|
|
665
|
+
catch (e) {
|
|
666
|
+
console.error(e.message);
|
|
667
|
+
process.exit(1);
|
|
668
|
+
}
|
|
669
|
+
const { kb } = getEngine();
|
|
670
|
+
const pack = await new MemoryFabric({
|
|
671
|
+
projectDir: PROJECT_DIR,
|
|
672
|
+
scaleDir: SCALE_DIR,
|
|
673
|
+
knowledgeBase: kb,
|
|
674
|
+
}).createContextPack({
|
|
675
|
+
task: args.task,
|
|
676
|
+
taskId: args['task-id'],
|
|
677
|
+
sessionId: args['session-id'],
|
|
678
|
+
level: normalizeTaskArtifactLevel(args.level),
|
|
679
|
+
files: parseCommaList(args.files),
|
|
680
|
+
budgetTokens,
|
|
681
|
+
});
|
|
682
|
+
const settlement = settleMemoryLearning({
|
|
683
|
+
projectDir: PROJECT_DIR,
|
|
684
|
+
scaleDir: SCALE_DIR,
|
|
685
|
+
pack,
|
|
686
|
+
});
|
|
687
|
+
if (args.json) {
|
|
688
|
+
console.log(JSON.stringify(settlement, null, 2));
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
console.log(renderMemoryLearningCandidateMarkdown(settlement.candidate));
|
|
692
|
+
console.log(`\nWrote: ${settlement.files.markdown}`);
|
|
693
|
+
},
|
|
694
|
+
});
|
|
695
|
+
const memoryIngest = defineCommand({
|
|
696
|
+
meta: { name: 'ingest', description: 'Ingest runtime evidence, learning candidates, or failure replays into the project memory brain' },
|
|
697
|
+
args: {
|
|
698
|
+
from: { type: 'string', default: 'evidence', description: 'Source: evidence, candidate, or failure' },
|
|
699
|
+
'task-id': { type: 'string', description: 'Task id to scope runtime evidence' },
|
|
700
|
+
'session-id': { type: 'string', description: 'Session id to scope runtime evidence' },
|
|
701
|
+
'candidate-id': { type: 'string', description: 'Memory learning candidate id' },
|
|
702
|
+
'failure-id': { type: 'string', description: 'Workflow eval failure replay id' },
|
|
703
|
+
type: { type: 'string', description: 'Memory type override: fact/decision/incident/relation/contradiction' },
|
|
704
|
+
scope: { type: 'string', description: 'Memory scope: project/workspace/global-candidate' },
|
|
705
|
+
json: { type: 'boolean', default: false },
|
|
706
|
+
},
|
|
707
|
+
run({ args }) {
|
|
708
|
+
let from;
|
|
709
|
+
let type;
|
|
710
|
+
let scope;
|
|
711
|
+
try {
|
|
712
|
+
from = normalizeMemorySource(args.from);
|
|
713
|
+
type = normalizeMemoryNodeType(args.type);
|
|
714
|
+
scope = normalizeMemoryScope(args.scope);
|
|
715
|
+
}
|
|
716
|
+
catch (error) {
|
|
717
|
+
console.error(error.message);
|
|
718
|
+
process.exit(1);
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
const report = memoryBrain().ingest({
|
|
722
|
+
from,
|
|
723
|
+
taskId: args['task-id'],
|
|
724
|
+
sessionId: args['session-id'],
|
|
725
|
+
candidateId: args['candidate-id'],
|
|
726
|
+
failureId: args['failure-id'],
|
|
727
|
+
type,
|
|
728
|
+
scope,
|
|
729
|
+
});
|
|
730
|
+
if (args.json) {
|
|
731
|
+
console.log(JSON.stringify(report, null, 2));
|
|
732
|
+
if (!report.ok)
|
|
733
|
+
process.exitCode = 1;
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
console.log('\nSCALE Memory Ingest');
|
|
737
|
+
console.log(` Source: ${report.source}`);
|
|
738
|
+
console.log(` Created: ${report.created}`);
|
|
739
|
+
console.log(` Skipped: ${report.skipped}`);
|
|
740
|
+
for (const node of report.nodes)
|
|
741
|
+
console.log(` [${node.status}] ${node.id}: ${node.title}`);
|
|
742
|
+
for (const warning of report.warnings)
|
|
743
|
+
console.log(` [WARN] ${warning}`);
|
|
744
|
+
if (!report.ok)
|
|
745
|
+
process.exitCode = 1;
|
|
746
|
+
},
|
|
747
|
+
});
|
|
748
|
+
const memoryQuery = defineCommand({
|
|
749
|
+
meta: { name: 'query', description: 'Query concise project-scoped long-term memory with evidence references' },
|
|
750
|
+
args: {
|
|
751
|
+
query: { type: 'positional', required: true, description: 'Search query' },
|
|
752
|
+
limit: { type: 'string', default: '8', description: 'Maximum number of memory nodes' },
|
|
753
|
+
status: { type: 'string', description: 'Filter by status: candidate/active/stale/rejected' },
|
|
754
|
+
json: { type: 'boolean', default: false },
|
|
755
|
+
},
|
|
756
|
+
run({ args }) {
|
|
757
|
+
const limit = Number.parseInt(String(args.limit ?? '8'), 10);
|
|
758
|
+
const status = args.status ? String(args.status) : undefined;
|
|
759
|
+
const report = memoryBrain().query(String(args.query), {
|
|
760
|
+
limit: Number.isFinite(limit) && limit > 0 ? limit : 8,
|
|
761
|
+
status,
|
|
762
|
+
});
|
|
763
|
+
if (args.json) {
|
|
764
|
+
console.log(JSON.stringify(report, null, 2));
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
console.log('\nSCALE Memory Query');
|
|
768
|
+
console.log(` Query: ${report.query}`);
|
|
769
|
+
console.log(` Results: ${report.count}`);
|
|
770
|
+
for (const node of report.nodes) {
|
|
771
|
+
console.log(` [${node.status}/${node.type}] ${node.id}: ${node.title}`);
|
|
772
|
+
console.log(` confidence: ${node.confidence}; evidence: ${node.evidencePaths.join(', ') || 'none'}`);
|
|
773
|
+
console.log(` ${node.summary}`);
|
|
774
|
+
}
|
|
775
|
+
},
|
|
776
|
+
});
|
|
777
|
+
const memoryContradictions = defineCommand({
|
|
778
|
+
meta: { name: 'contradictions', description: 'Report conflicting project memory instead of silently resolving it' },
|
|
779
|
+
args: {
|
|
780
|
+
json: { type: 'boolean', default: false },
|
|
781
|
+
},
|
|
782
|
+
run({ args }) {
|
|
783
|
+
const report = memoryBrain().contradictions();
|
|
784
|
+
if (args.json) {
|
|
785
|
+
console.log(JSON.stringify(report, null, 2));
|
|
786
|
+
if (!report.ok)
|
|
787
|
+
process.exitCode = 1;
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
console.log('\nSCALE Memory Contradictions');
|
|
791
|
+
console.log(` Count: ${report.count}`);
|
|
792
|
+
for (const item of report.contradictions) {
|
|
793
|
+
console.log(` [CONFLICT] ${item.title}`);
|
|
794
|
+
console.log(` nodes: ${item.nodeIds.join(', ')}`);
|
|
795
|
+
console.log(` evidence: ${item.evidencePaths.join(', ') || 'none'}`);
|
|
796
|
+
}
|
|
797
|
+
if (!report.ok)
|
|
798
|
+
process.exitCode = 1;
|
|
799
|
+
},
|
|
800
|
+
});
|
|
801
|
+
const memoryDream = defineCommand({
|
|
802
|
+
meta: { name: 'dream', description: 'Run memory maintenance: duplicates, stale memories, contradictions, and promotion candidates' },
|
|
803
|
+
args: {
|
|
804
|
+
json: { type: 'boolean', default: false },
|
|
805
|
+
},
|
|
806
|
+
run({ args }) {
|
|
807
|
+
const report = memoryBrain().dream();
|
|
808
|
+
if (args.json) {
|
|
809
|
+
console.log(JSON.stringify(report, null, 2));
|
|
810
|
+
if (!report.ok)
|
|
811
|
+
process.exitCode = 1;
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
console.log('\nSCALE Memory Dream');
|
|
815
|
+
console.log(` Total: ${report.summary.total}`);
|
|
816
|
+
console.log(` Active: ${report.summary.active}`);
|
|
817
|
+
console.log(` Candidates: ${report.summary.candidate}`);
|
|
818
|
+
console.log(` Contradictions: ${report.summary.contradictions}`);
|
|
819
|
+
console.log(` Duplicate groups: ${report.summary.duplicateGroups}`);
|
|
820
|
+
for (const item of report.promotionCandidates)
|
|
821
|
+
console.log(` [PROMOTE?] ${item.id}: ${item.title}`);
|
|
822
|
+
for (const item of report.staleCandidates)
|
|
823
|
+
console.log(` [STALE] ${item.id}: ${item.reason}`);
|
|
824
|
+
if (!report.ok)
|
|
825
|
+
process.exitCode = 1;
|
|
826
|
+
},
|
|
827
|
+
});
|
|
828
|
+
const memoryPromote = defineCommand({
|
|
829
|
+
meta: { name: 'promote', description: 'Promote a memory candidate to active project memory after evidence review' },
|
|
830
|
+
args: {
|
|
831
|
+
id: { type: 'positional', required: true, description: 'Memory node id or learning candidate id' },
|
|
832
|
+
scope: { type: 'string', description: 'Scope override: project/workspace/global-candidate' },
|
|
833
|
+
json: { type: 'boolean', default: false },
|
|
834
|
+
},
|
|
835
|
+
run({ args }) {
|
|
836
|
+
let scope;
|
|
837
|
+
try {
|
|
838
|
+
scope = normalizeMemoryScope(args.scope);
|
|
839
|
+
}
|
|
840
|
+
catch (error) {
|
|
841
|
+
console.error(error.message);
|
|
842
|
+
process.exit(1);
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
const report = memoryBrain().promote(String(args.id), { scope });
|
|
846
|
+
if (args.json) {
|
|
847
|
+
console.log(JSON.stringify(report, null, 2));
|
|
848
|
+
if (!report.ok)
|
|
849
|
+
process.exitCode = 1;
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
console.log('\nSCALE Memory Promote');
|
|
853
|
+
console.log(` Status: ${report.ok ? 'promoted' : 'blocked'}`);
|
|
854
|
+
if (report.node)
|
|
855
|
+
console.log(` Node: ${report.node.id} (${report.node.status})`);
|
|
856
|
+
for (const warning of report.warnings)
|
|
857
|
+
console.log(` [WARN] ${warning}`);
|
|
858
|
+
if (!report.ok)
|
|
859
|
+
process.exitCode = 1;
|
|
860
|
+
},
|
|
861
|
+
});
|
|
862
|
+
const memoryExport = defineCommand({
|
|
863
|
+
meta: { name: 'export', description: 'Export project memory as JSONL' },
|
|
864
|
+
args: {
|
|
865
|
+
output: { type: 'string', alias: 'o', description: 'Output JSONL file; stdout when omitted' },
|
|
866
|
+
json: { type: 'boolean', default: false },
|
|
867
|
+
},
|
|
868
|
+
run({ args }) {
|
|
869
|
+
const jsonl = memoryBrain().exportJsonl();
|
|
870
|
+
if (args.output) {
|
|
871
|
+
const outputPath = resolve(PROJECT_DIR, String(args.output));
|
|
872
|
+
ensureDir(dirname(outputPath));
|
|
873
|
+
writeFileSync(outputPath, jsonl, 'utf-8');
|
|
874
|
+
if (args.json) {
|
|
875
|
+
console.log(JSON.stringify({ ok: true, outputPath, bytes: jsonl.length }, null, 2));
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
console.log(`[OK] Memory JSONL exported: ${outputPath}`);
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
console.log(jsonl);
|
|
882
|
+
},
|
|
883
|
+
});
|
|
884
|
+
const memoryImport = defineCommand({
|
|
885
|
+
meta: { name: 'import', description: 'Import project memory from JSONL' },
|
|
886
|
+
args: {
|
|
887
|
+
file: { type: 'positional', required: true, description: 'Input JSONL file' },
|
|
888
|
+
json: { type: 'boolean', default: false },
|
|
889
|
+
},
|
|
890
|
+
run({ args }) {
|
|
891
|
+
const filePath = resolve(PROJECT_DIR, String(args.file));
|
|
892
|
+
const report = memoryBrain().importJsonl(filePath);
|
|
893
|
+
if (args.json) {
|
|
894
|
+
console.log(JSON.stringify(report, null, 2));
|
|
895
|
+
if (!report.ok)
|
|
896
|
+
process.exitCode = 1;
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
console.log('\nSCALE Memory Import');
|
|
900
|
+
console.log(` Imported: ${report.imported}`);
|
|
901
|
+
console.log(` Skipped: ${report.skipped}`);
|
|
902
|
+
for (const warning of report.warnings)
|
|
903
|
+
console.log(` [WARN] ${warning}`);
|
|
904
|
+
if (!report.ok)
|
|
905
|
+
process.exitCode = 1;
|
|
906
|
+
},
|
|
907
|
+
});
|
|
908
|
+
const memoryProviderInit = defineCommand({
|
|
909
|
+
meta: { name: 'init', description: 'Create .scale/memory-providers.json for autonomous memory provider routing' },
|
|
910
|
+
args: {
|
|
911
|
+
force: { type: 'boolean', default: false, description: 'Overwrite existing provider configuration' },
|
|
912
|
+
json: { type: 'boolean', default: false },
|
|
913
|
+
},
|
|
914
|
+
run({ args }) {
|
|
915
|
+
const result = writeMemoryProvidersConfig({
|
|
916
|
+
projectDir: PROJECT_DIR,
|
|
917
|
+
scaleDir: SCALE_DIR,
|
|
918
|
+
force: isTruthyFlag(args.force),
|
|
919
|
+
});
|
|
920
|
+
if (args.json) {
|
|
921
|
+
console.log(JSON.stringify(result, null, 2));
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
console.log(`\nSCALE Memory Provider Config: ${result.path}`);
|
|
925
|
+
console.log(` ${result.written ? 'written' : 'exists'}`);
|
|
926
|
+
console.log(` Order: ${result.config.routing.defaultOrder.join(' -> ')}`);
|
|
927
|
+
},
|
|
928
|
+
});
|
|
929
|
+
const memoryProviderStatus = defineCommand({
|
|
930
|
+
meta: { name: 'status', description: 'Inspect memory provider routing, availability, and safety boundaries' },
|
|
931
|
+
args: {
|
|
932
|
+
json: { type: 'boolean', default: false },
|
|
933
|
+
},
|
|
934
|
+
run({ args }) {
|
|
935
|
+
const report = inspectMemoryProviders({ projectDir: PROJECT_DIR, scaleDir: SCALE_DIR });
|
|
936
|
+
if (args.json) {
|
|
937
|
+
console.log(JSON.stringify(report, null, 2));
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
console.log('\nSCALE Memory Providers');
|
|
941
|
+
console.log(` Config: ${report.configExists ? report.configPath : 'default policy (not written)'}`);
|
|
942
|
+
console.log(` Mode: ${report.routing.mode}`);
|
|
943
|
+
for (const provider of report.providers) {
|
|
944
|
+
console.log(` [${provider.available ? 'AVAILABLE' : 'SKIP'}] ${provider.id} (${provider.kind})`);
|
|
945
|
+
console.log(` safety: ${provider.safetyLevel}; write: ${provider.writeMode}; reason: ${provider.reason}`);
|
|
946
|
+
}
|
|
947
|
+
for (const warning of report.warnings)
|
|
948
|
+
console.log(` [WARN] ${warning}`);
|
|
949
|
+
},
|
|
950
|
+
});
|
|
951
|
+
const memoryProviderRecall = defineCommand({
|
|
952
|
+
meta: { name: 'recall', description: 'Recall relevant memory through provider routing with local fallback' },
|
|
953
|
+
args: {
|
|
954
|
+
query: { type: 'positional', required: true, description: 'Memory query or task context' },
|
|
955
|
+
task: { type: 'string', description: 'Optional task text for provider routing context' },
|
|
956
|
+
files: { type: 'string', description: 'Comma-separated files or modules in scope' },
|
|
957
|
+
provider: { type: 'string', description: 'Force one provider id, such as agentmemory, gbrain, or scale-local' },
|
|
958
|
+
limit: { type: 'string', default: '5', description: 'Maximum results' },
|
|
959
|
+
'include-candidates': { type: 'boolean', default: false, description: 'Allow scale-local candidate memory fallback' },
|
|
960
|
+
json: { type: 'boolean', default: false },
|
|
961
|
+
},
|
|
962
|
+
async run({ args }) {
|
|
963
|
+
const limit = Number.parseInt(String(args.limit ?? '5'), 10);
|
|
964
|
+
const report = await recallMemoryProviders({
|
|
965
|
+
projectDir: PROJECT_DIR,
|
|
966
|
+
scaleDir: SCALE_DIR,
|
|
967
|
+
query: String(args.query),
|
|
968
|
+
task: args.task ? String(args.task) : undefined,
|
|
969
|
+
files: parseCommaList(args.files),
|
|
970
|
+
provider: args.provider ? String(args.provider) : undefined,
|
|
971
|
+
limit: Number.isFinite(limit) && limit > 0 ? limit : 5,
|
|
972
|
+
includeCandidates: isTruthyFlag(args['include-candidates']),
|
|
973
|
+
});
|
|
974
|
+
if (args.json) {
|
|
975
|
+
console.log(JSON.stringify(report, null, 2));
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
978
|
+
console.log('\nSCALE Memory Provider Recall');
|
|
979
|
+
console.log(` Query: ${report.query}`);
|
|
980
|
+
console.log(` Providers: ${report.providerOrder.join(' -> ')}`);
|
|
981
|
+
console.log(` Results: ${report.items.length}`);
|
|
982
|
+
for (const item of report.items) {
|
|
983
|
+
console.log(` [${item.provider}] ${item.id}: ${item.title}`);
|
|
984
|
+
console.log(` score: ${item.score}; confidence: ${item.confidence}; evidence: ${item.evidencePaths.join(', ') || 'none'}`);
|
|
985
|
+
console.log(` ${item.summary}`);
|
|
986
|
+
}
|
|
987
|
+
for (const warning of report.warnings)
|
|
988
|
+
console.log(` [WARN] ${warning}`);
|
|
989
|
+
},
|
|
990
|
+
});
|
|
991
|
+
const memoryProviderUse = defineCommand({
|
|
992
|
+
meta: { name: 'use', description: 'Promote one memory provider to the front of routing and persist the selection' },
|
|
993
|
+
args: {
|
|
994
|
+
provider: { type: 'positional', required: true, description: 'Provider id: gbrain, agentmemory, or scale-local' },
|
|
995
|
+
mode: { type: 'string', description: 'Optional routing mode override: auto, local-only, external-first' },
|
|
996
|
+
endpoint: { type: 'string', description: 'Optional provider endpoint to persist while switching' },
|
|
997
|
+
'write-mode': { type: 'string', description: 'Optional provider write mode: disabled, candidate-only, enabled' },
|
|
998
|
+
'allow-external-write': { type: 'boolean', default: false, description: 'Persist external write allowance when explicitly switching' },
|
|
999
|
+
json: { type: 'boolean', default: false },
|
|
1000
|
+
},
|
|
1001
|
+
run({ args }) {
|
|
1002
|
+
const mode = args.mode ? String(args.mode) : undefined;
|
|
1003
|
+
const writeMode = args['write-mode']
|
|
1004
|
+
? String(args['write-mode'])
|
|
1005
|
+
: undefined;
|
|
1006
|
+
const report = useMemoryProvider({
|
|
1007
|
+
projectDir: PROJECT_DIR,
|
|
1008
|
+
scaleDir: SCALE_DIR,
|
|
1009
|
+
provider: String(args.provider),
|
|
1010
|
+
mode: mode,
|
|
1011
|
+
endpoint: args.endpoint ? String(args.endpoint) : undefined,
|
|
1012
|
+
writeMode,
|
|
1013
|
+
allowExternalWrite: isTruthyFlag(args['allow-external-write']) ? true : undefined,
|
|
1014
|
+
});
|
|
1015
|
+
if (args.json) {
|
|
1016
|
+
console.log(JSON.stringify(report, null, 2));
|
|
1017
|
+
if (!report.ok)
|
|
1018
|
+
process.exitCode = 1;
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
1021
|
+
console.log('\nSCALE Memory Provider Switch');
|
|
1022
|
+
console.log(` Provider: ${report.provider}`);
|
|
1023
|
+
console.log(` Mode: ${report.mode}`);
|
|
1024
|
+
console.log(` Config: ${report.path}`);
|
|
1025
|
+
console.log(` Order: ${report.previousOrder.join(' -> ')} -> ${report.nextOrder.join(' -> ')}`);
|
|
1026
|
+
if (report.providerStatus) {
|
|
1027
|
+
console.log(` Status: ${report.providerStatus.available ? 'available' : 'not-ready'} (${report.providerStatus.reason})`);
|
|
1028
|
+
}
|
|
1029
|
+
for (const warning of report.warnings)
|
|
1030
|
+
console.log(` [WARN] ${warning}`);
|
|
1031
|
+
if (!report.ok)
|
|
1032
|
+
process.exitCode = 1;
|
|
1033
|
+
},
|
|
1034
|
+
});
|
|
1035
|
+
const memoryProvider = defineCommand({
|
|
1036
|
+
meta: { name: 'provider', description: 'Manage autonomous memory provider routing for agentmemory, gbrain, and scale-local' },
|
|
1037
|
+
subCommands: {
|
|
1038
|
+
init: memoryProviderInit,
|
|
1039
|
+
status: memoryProviderStatus,
|
|
1040
|
+
recall: memoryProviderRecall,
|
|
1041
|
+
use: memoryProviderUse,
|
|
1042
|
+
},
|
|
1043
|
+
});
|
|
1044
|
+
export const memoryCommand = defineCommand({
|
|
1045
|
+
meta: { name: 'memory', description: 'Memory Fabric context packs and project-scoped long-term memory' },
|
|
1046
|
+
subCommands: {
|
|
1047
|
+
pack: memoryPack,
|
|
1048
|
+
doctor: memoryDoctor,
|
|
1049
|
+
cerebrum: memoryCerebrum,
|
|
1050
|
+
settle: memorySettle,
|
|
1051
|
+
ingest: memoryIngest,
|
|
1052
|
+
query: memoryQuery,
|
|
1053
|
+
contradictions: memoryContradictions,
|
|
1054
|
+
dream: memoryDream,
|
|
1055
|
+
promote: memoryPromote,
|
|
1056
|
+
export: memoryExport,
|
|
1057
|
+
import: memoryImport,
|
|
1058
|
+
provider: memoryProvider,
|
|
1059
|
+
},
|
|
1060
|
+
});
|
|
1061
|
+
// ============================================================================
|
|
1062
|
+
// out-of-scope command
|
|
1063
|
+
// ============================================================================
|
|
1064
|
+
const outOfScopeAdd = defineCommand({
|
|
1065
|
+
meta: { name: 'add', description: 'Record a rejected concept to the out-of-scope knowledge base' },
|
|
1066
|
+
args: {
|
|
1067
|
+
concept: { type: 'positional', required: true, description: 'kebab-case concept name' },
|
|
1068
|
+
title: { type: 'string', required: true, description: 'Human-readable title' },
|
|
1069
|
+
reason: { type: 'string', required: true, description: 'Why this was rejected' },
|
|
1070
|
+
'tech-context': { type: 'string', description: 'Technical constraints that led to rejection' },
|
|
1071
|
+
'prior-requests': { type: 'string', description: 'Comma-separated issue IDs or URLs' },
|
|
1072
|
+
},
|
|
1073
|
+
run({ args }) {
|
|
1074
|
+
ensureDir(SCALE_DIR);
|
|
1075
|
+
const store = new OutOfScopeStore(SCALE_DIR);
|
|
1076
|
+
const entry = store.add({
|
|
1077
|
+
concept: args.concept,
|
|
1078
|
+
title: args.title,
|
|
1079
|
+
reason: args.reason,
|
|
1080
|
+
technicalContext: args['tech-context'],
|
|
1081
|
+
priorRequests: args['prior-requests']?.split(',').map(s => s.trim()) ?? [],
|
|
1082
|
+
});
|
|
1083
|
+
console.log(JSON.stringify({ ok: true, concept: entry.concept, title: entry.title, priorRequests: entry.priorRequests.length }, null, 2));
|
|
1084
|
+
},
|
|
1085
|
+
});
|
|
1086
|
+
const outOfScopeCheck = defineCommand({
|
|
1087
|
+
meta: { name: 'check', description: 'Check if a concept matches any existing out-of-scope entry' },
|
|
1088
|
+
args: {
|
|
1089
|
+
concept: { type: 'positional', required: true, description: 'Concept name to check' },
|
|
1090
|
+
description: { type: 'string', description: 'Optional description for fuzzy matching' },
|
|
1091
|
+
},
|
|
1092
|
+
run({ args }) {
|
|
1093
|
+
ensureDir(SCALE_DIR);
|
|
1094
|
+
const store = new OutOfScopeStore(SCALE_DIR);
|
|
1095
|
+
const match = store.check(args.concept, args.description);
|
|
1096
|
+
if (match) {
|
|
1097
|
+
console.log(JSON.stringify({ ok: true, matched: true, concept: match.concept, title: match.title, reason: match.reason, priorRequests: match.priorRequests }, null, 2));
|
|
1098
|
+
}
|
|
1099
|
+
else {
|
|
1100
|
+
console.log(JSON.stringify({ ok: true, matched: false }, null, 2));
|
|
1101
|
+
}
|
|
1102
|
+
},
|
|
1103
|
+
});
|
|
1104
|
+
const outOfScopeList = defineCommand({
|
|
1105
|
+
meta: { name: 'list', description: 'List all out-of-scope entries' },
|
|
1106
|
+
run() {
|
|
1107
|
+
ensureDir(SCALE_DIR);
|
|
1108
|
+
const store = new OutOfScopeStore(SCALE_DIR);
|
|
1109
|
+
const entries = store.list();
|
|
1110
|
+
console.log(JSON.stringify({ ok: true, total: entries.length, entries: entries.map(e => ({ concept: e.concept, title: e.title, priorRequests: e.priorRequests.length, updatedAt: new Date(e.updatedAt).toISOString() })) }, null, 2));
|
|
1111
|
+
},
|
|
1112
|
+
});
|
|
1113
|
+
const outOfScopeRemove = defineCommand({
|
|
1114
|
+
meta: { name: 'remove', description: 'Remove an out-of-scope entry (concept reconsidered)' },
|
|
1115
|
+
args: {
|
|
1116
|
+
concept: { type: 'positional', required: true, description: 'Concept name to remove' },
|
|
1117
|
+
},
|
|
1118
|
+
run({ args }) {
|
|
1119
|
+
ensureDir(SCALE_DIR);
|
|
1120
|
+
const store = new OutOfScopeStore(SCALE_DIR);
|
|
1121
|
+
const removed = store.remove(args.concept);
|
|
1122
|
+
console.log(JSON.stringify({ ok: removed, concept: args.concept }, null, 2));
|
|
1123
|
+
},
|
|
1124
|
+
});
|
|
1125
|
+
export const outOfScopeCommand = defineCommand({
|
|
1126
|
+
meta: { name: 'out-of-scope', description: 'Manage out-of-scope knowledge base (rejected concepts with institutional memory)' },
|
|
1127
|
+
subCommands: { add: outOfScopeAdd, check: outOfScopeCheck, list: outOfScopeList, remove: outOfScopeRemove },
|
|
1128
|
+
});
|
|
1129
|
+
// ============================================================================
|
|
1130
|
+
// skill command
|
|
1131
|
+
// ============================================================================
|
|
1132
|
+
const skillScan = defineCommand({
|
|
1133
|
+
meta: { name: 'scan', description: 'Scan for installed skills' },
|
|
1134
|
+
args: {
|
|
1135
|
+
dir: { type: 'string', default: '.', description: 'Project directory' },
|
|
1136
|
+
json: { type: 'boolean', default: false, description: 'Output scan result as JSON' },
|
|
1137
|
+
},
|
|
1138
|
+
async run({ args }) {
|
|
1139
|
+
const discovery = new SkillDiscovery(args.dir);
|
|
1140
|
+
const platform = discovery.detectPlatform();
|
|
1141
|
+
if (!platform && args.json) {
|
|
1142
|
+
console.log(JSON.stringify({
|
|
1143
|
+
ok: false,
|
|
1144
|
+
platform: null,
|
|
1145
|
+
skills: [],
|
|
1146
|
+
message: 'No agent platform detected. Run `scale init` first.',
|
|
1147
|
+
}, null, 2));
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
if (!platform) {
|
|
1151
|
+
console.log('\n⚠️ No agent platform detected. Run `scale init` first.');
|
|
1152
|
+
return;
|
|
1153
|
+
}
|
|
1154
|
+
const result = discovery.scanSkills(platform);
|
|
1155
|
+
if (args.json) {
|
|
1156
|
+
console.log(JSON.stringify({
|
|
1157
|
+
ok: true,
|
|
1158
|
+
platform: result.platform,
|
|
1159
|
+
count: result.skills.length,
|
|
1160
|
+
skills: result.skills,
|
|
1161
|
+
}, null, 2));
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
console.log(`\n🔍 Platform: ${result.platform}`);
|
|
1165
|
+
console.log(`📦 Skills found: ${result.skills.length}`);
|
|
1166
|
+
if (result.skills.length > 0) {
|
|
1167
|
+
for (const skill of result.skills) {
|
|
1168
|
+
const status = skill.enabled ? '✅' : '❌';
|
|
1169
|
+
const desc = skill.description ? ` — ${skill.description}` : '';
|
|
1170
|
+
console.log(` ${status} ${skill.name}${desc}`);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
else {
|
|
1174
|
+
console.log(' No skills found in platform skills directory.');
|
|
1175
|
+
}
|
|
1176
|
+
},
|
|
1177
|
+
});
|
|
1178
|
+
const skillPlanCommand = defineCommand({
|
|
1179
|
+
meta: { name: 'plan', description: 'Create or refresh a task skill plan' },
|
|
1180
|
+
args: {
|
|
1181
|
+
'task-id': { type: 'positional', required: true },
|
|
1182
|
+
dir: { type: 'string', description: 'Task artifact directory; defaults to current state artifactsDir' },
|
|
1183
|
+
json: { type: 'boolean', default: false },
|
|
1184
|
+
},
|
|
1185
|
+
async run({ args }) {
|
|
1186
|
+
const { store } = getEngine();
|
|
1187
|
+
const task = await store.get(args['task-id']);
|
|
1188
|
+
if (!task || task.type !== 'Task') {
|
|
1189
|
+
console.error(`Task not found: ${args['task-id']}`);
|
|
1190
|
+
process.exit(1);
|
|
1191
|
+
}
|
|
1192
|
+
const payload = task.payload;
|
|
1193
|
+
const level = normalizeTaskArtifactLevel(payload.workflowLevel ?? 'M');
|
|
1194
|
+
const policy = loadSkillRoutingPolicy(PROJECT_DIR, SCALE_DIR);
|
|
1195
|
+
const plan = createSkillPlan({
|
|
1196
|
+
taskId: task.id,
|
|
1197
|
+
taskName: task.title,
|
|
1198
|
+
description: payload.description,
|
|
1199
|
+
level,
|
|
1200
|
+
services: payload.servicesTouched ?? [],
|
|
1201
|
+
files: payload.filesInvolved ?? [],
|
|
1202
|
+
policy,
|
|
1203
|
+
});
|
|
1204
|
+
const updatedPayload = {
|
|
1205
|
+
...payload,
|
|
1206
|
+
skillIntents: plan.intents.map(intent => intent.domain),
|
|
1207
|
+
skillRoutingMode: plan.mode,
|
|
1208
|
+
skillPlanRequired: plan.required,
|
|
1209
|
+
requiredSkills: plan.requiredSkills,
|
|
1210
|
+
recommendedSkills: plan.recommendedSkills,
|
|
1211
|
+
requiredSkillArtifacts: plan.requiredArtifacts,
|
|
1212
|
+
requiredSkillVerification: plan.requiredVerification,
|
|
1213
|
+
};
|
|
1214
|
+
await store.update(task.id, { payload: updatedPayload });
|
|
1215
|
+
const state = new WorkflowArtifactWriter(SCALE_DIR).readCurrentState();
|
|
1216
|
+
const artifactsDir = args.dir ?? (state?.taskId === task.id ? state.artifactsDir : undefined);
|
|
1217
|
+
let writtenPath;
|
|
1218
|
+
if (artifactsDir) {
|
|
1219
|
+
const dir = resolve(PROJECT_DIR, artifactsDir);
|
|
1220
|
+
ensureDir(dir);
|
|
1221
|
+
writtenPath = join(dir, 'skill-plan.md');
|
|
1222
|
+
writeFileSync(writtenPath, skillPlanMarkdown(plan), 'utf-8');
|
|
1223
|
+
}
|
|
1224
|
+
new WorkflowArtifactWriter(SCALE_DIR).updateCurrentState({
|
|
1225
|
+
taskId: task.id,
|
|
1226
|
+
level,
|
|
1227
|
+
phase: 'plan',
|
|
1228
|
+
artifactsDir,
|
|
1229
|
+
skillIntents: plan.intents.map(intent => intent.domain),
|
|
1230
|
+
skillRoutingMode: plan.mode,
|
|
1231
|
+
skillPlanRequired: plan.required,
|
|
1232
|
+
skillPlanPath: writtenPath,
|
|
1233
|
+
requiredSkills: plan.requiredSkills,
|
|
1234
|
+
recommendedSkills: plan.recommendedSkills,
|
|
1235
|
+
requiredSkillArtifacts: plan.requiredArtifacts,
|
|
1236
|
+
requiredSkillVerification: plan.requiredVerification,
|
|
1237
|
+
});
|
|
1238
|
+
if (args.json) {
|
|
1239
|
+
console.log(JSON.stringify({ plan, writtenPath }, null, 2));
|
|
1240
|
+
return;
|
|
1241
|
+
}
|
|
1242
|
+
console.log('\nSkill Plan');
|
|
1243
|
+
console.log(` Task: ${task.id}`);
|
|
1244
|
+
console.log(` Intents: ${plan.intents.map(intent => intent.domain).join(', ') || 'none'}`);
|
|
1245
|
+
console.log(` Required skills: ${plan.requiredSkills.join(', ') || 'none'}`);
|
|
1246
|
+
console.log(` Recommended skills: ${plan.recommendedSkills.join(', ') || 'none'}`);
|
|
1247
|
+
console.log(` Required artifacts: ${plan.requiredArtifacts.join(', ') || 'none'}`);
|
|
1248
|
+
if (writtenPath)
|
|
1249
|
+
console.log(` Written: ${writtenPath}`);
|
|
1250
|
+
},
|
|
1251
|
+
});
|
|
1252
|
+
const skillDoctorCommand = defineCommand({
|
|
1253
|
+
meta: { name: 'doctor', description: 'Check workflow skill installation status' },
|
|
1254
|
+
args: {
|
|
1255
|
+
dir: { type: 'string', default: PROJECT_DIR, description: 'Project directory' },
|
|
1256
|
+
'supply-chain': { type: 'boolean', default: false, description: 'Include supply-chain safety review for known skill sources' },
|
|
1257
|
+
json: { type: 'boolean', default: false, description: 'Output skill doctor report as JSON' },
|
|
1258
|
+
},
|
|
1259
|
+
run({ args }) {
|
|
1260
|
+
const projectDir = resolve(String(args.dir ?? PROJECT_DIR));
|
|
1261
|
+
const report = inspectWorkflowSkills({ projectDir });
|
|
1262
|
+
const supplyChain = isTruthyFlag(args['supply-chain']) ? inspectSkillSupplyChain({ projectDir }) : undefined;
|
|
1263
|
+
if (args.json) {
|
|
1264
|
+
console.log(JSON.stringify(supplyChain ? { installation: report, supplyChain } : report, null, 2));
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1267
|
+
console.log('\nSCALE Skill Doctor');
|
|
1268
|
+
console.log(` Installed: ${report.installed}/${report.total}`);
|
|
1269
|
+
for (const skill of report.skills) {
|
|
1270
|
+
console.log(` ${skill.installed ? '[OK]' : '[MISSING]'} ${skill.id}`);
|
|
1271
|
+
if (skill.detectedPath)
|
|
1272
|
+
console.log(` path: ${skill.detectedPath}`);
|
|
1273
|
+
if (!skill.installed)
|
|
1274
|
+
console.log(` install: ${skill.installCommand}`);
|
|
1275
|
+
}
|
|
1276
|
+
if (supplyChain) {
|
|
1277
|
+
console.log('\nSkill Supply Chain');
|
|
1278
|
+
console.log(` Evaluated: ${supplyChain.evaluated}`);
|
|
1279
|
+
console.log(` Blocked: ${supplyChain.blocked}`);
|
|
1280
|
+
console.log(` Warnings: ${supplyChain.warnings}`);
|
|
1281
|
+
for (const entry of supplyChain.entries.filter(entry => entry.blocked || entry.findings.length > 0)) {
|
|
1282
|
+
console.log(` [${entry.blocked ? 'BLOCKED' : 'WARN'}] ${entry.id}: ${entry.risk}`);
|
|
1283
|
+
for (const finding of entry.findings)
|
|
1284
|
+
console.log(` - ${finding.rule}: ${finding.message}`);
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
if (!report.ok || supplyChain?.ok === false)
|
|
1288
|
+
process.exitCode = 1;
|
|
1289
|
+
},
|
|
1290
|
+
});
|
|
1291
|
+
const skillCheckCommand = defineCommand({
|
|
1292
|
+
meta: { name: 'check', description: 'Check required skill evidence artifacts' },
|
|
1293
|
+
args: {
|
|
1294
|
+
dir: { type: 'string', description: 'Task artifact directory; defaults to current state artifactsDir' },
|
|
1295
|
+
level: { type: 'string', description: 'Task level: S, M, L, or CRITICAL; defaults to current state level or M' },
|
|
1296
|
+
'require-installed': { type: 'boolean', default: false, description: 'Fail when required workflow skills are not installed locally' },
|
|
1297
|
+
json: { type: 'boolean', default: false },
|
|
1298
|
+
},
|
|
1299
|
+
run({ args }) {
|
|
1300
|
+
const state = new WorkflowArtifactWriter(SCALE_DIR).readCurrentState();
|
|
1301
|
+
const level = normalizeTaskArtifactLevel(args.level ?? state?.level ?? 'M');
|
|
1302
|
+
const policy = loadSkillRoutingPolicy(PROJECT_DIR, SCALE_DIR);
|
|
1303
|
+
const result = evaluateSkillGate({
|
|
1304
|
+
projectDir: PROJECT_DIR,
|
|
1305
|
+
artifactsDir: args.dir ?? state?.artifactsDir,
|
|
1306
|
+
level,
|
|
1307
|
+
requiredArtifacts: state?.requiredSkillArtifacts,
|
|
1308
|
+
requiredSkills: state?.requiredSkills,
|
|
1309
|
+
mode: state?.skillRoutingMode ?? policy.policy.mode,
|
|
1310
|
+
enforceLevels: policy.policy.enforceLevels,
|
|
1311
|
+
});
|
|
1312
|
+
const skillInstallation = inspectRequiredWorkflowSkills(state?.requiredSkills ?? [], { projectDir: PROJECT_DIR });
|
|
1313
|
+
const requireInstalled = isTruthyFlag(args['require-installed']);
|
|
1314
|
+
const blocked = result.blocked || (requireInstalled && !skillInstallation.ok);
|
|
1315
|
+
const output = {
|
|
1316
|
+
...result,
|
|
1317
|
+
complete: result.complete && (!requireInstalled || skillInstallation.ok),
|
|
1318
|
+
blocked,
|
|
1319
|
+
skillInstallation: {
|
|
1320
|
+
...skillInstallation,
|
|
1321
|
+
checked: requireInstalled,
|
|
1322
|
+
},
|
|
1323
|
+
};
|
|
1324
|
+
if (args.json) {
|
|
1325
|
+
console.log(JSON.stringify(output, null, 2));
|
|
1326
|
+
return;
|
|
1327
|
+
}
|
|
1328
|
+
console.log(`\nSkill Gate: ${output.complete ? 'COMPLETE' : 'INCOMPLETE'}`);
|
|
1329
|
+
console.log(` Mode: ${output.mode}`);
|
|
1330
|
+
console.log(` Required artifacts: ${output.required.join(', ') || 'none'}`);
|
|
1331
|
+
console.log(` Required skills: ${skillInstallation.required.join(', ') || 'none'}`);
|
|
1332
|
+
for (const file of output.missing)
|
|
1333
|
+
console.log(` [MISSING] ${file}`);
|
|
1334
|
+
for (const item of output.incomplete)
|
|
1335
|
+
console.log(` [INCOMPLETE] ${item.file}: ${item.reason}`);
|
|
1336
|
+
if (requireInstalled && !skillInstallation.ok) {
|
|
1337
|
+
for (const skill of skillInstallation.skills.filter(skill => !skill.installed)) {
|
|
1338
|
+
console.log(` [MISSING_SKILL] ${skill.id}: ${skill.installCommand}`);
|
|
1339
|
+
}
|
|
1340
|
+
for (const skill of skillInstallation.unknown)
|
|
1341
|
+
console.log(` [UNKNOWN_SKILL] ${skill}`);
|
|
1342
|
+
}
|
|
1343
|
+
if (blocked)
|
|
1344
|
+
process.exitCode = 1;
|
|
1345
|
+
},
|
|
1346
|
+
});
|
|
1347
|
+
const skillRepoCommand = defineCommand({
|
|
1348
|
+
meta: { name: 'repo', description: 'Show SCALE progressive skill repository guide' },
|
|
1349
|
+
args: {
|
|
1350
|
+
category: { type: 'string', description: 'Filter by category: ui/browser/desktop/testing/review/docs/agent-cli/role-library/discovery' },
|
|
1351
|
+
output: { type: 'string', alias: 'o', description: 'Write markdown guide to file' },
|
|
1352
|
+
json: { type: 'boolean', default: false },
|
|
1353
|
+
},
|
|
1354
|
+
run({ args }) {
|
|
1355
|
+
if (args.json) {
|
|
1356
|
+
console.log(JSON.stringify(listSkillRepositoryEntries(args.category ? { category: args.category } : undefined), null, 2));
|
|
1357
|
+
return;
|
|
1358
|
+
}
|
|
1359
|
+
const markdown = renderSkillRepositoryMarkdown();
|
|
1360
|
+
if (args.output) {
|
|
1361
|
+
const outputPath = resolve(PROJECT_DIR, args.output);
|
|
1362
|
+
ensureDir(resolve(outputPath, '..'));
|
|
1363
|
+
writeFileSync(outputPath, markdown, 'utf-8');
|
|
1364
|
+
console.log(`[OK] Skill 仓库指南已生成: ${outputPath}`);
|
|
1365
|
+
return;
|
|
1366
|
+
}
|
|
1367
|
+
console.log(markdown);
|
|
1368
|
+
},
|
|
1369
|
+
});
|
|
1370
|
+
const skillSafetyCommand = defineCommand({
|
|
1371
|
+
meta: { name: 'safety', description: 'Evaluate skill install command and source safety' },
|
|
1372
|
+
args: {
|
|
1373
|
+
source: { type: 'string', description: 'Skill source URL' },
|
|
1374
|
+
command: { type: 'string', description: 'Install command to review' },
|
|
1375
|
+
json: { type: 'boolean', default: false },
|
|
1376
|
+
},
|
|
1377
|
+
run({ args }) {
|
|
1378
|
+
const report = evaluateSkillInstallSafety({
|
|
1379
|
+
sourceUrl: args.source,
|
|
1380
|
+
installCommand: args.command,
|
|
1381
|
+
});
|
|
1382
|
+
if (args.json) {
|
|
1383
|
+
console.log(JSON.stringify(report, null, 2));
|
|
1384
|
+
return;
|
|
1385
|
+
}
|
|
1386
|
+
console.log('\nSCALE Skill Safety');
|
|
1387
|
+
console.log(` Risk: ${report.risk}`);
|
|
1388
|
+
console.log(` Blocked: ${report.blocked}`);
|
|
1389
|
+
for (const finding of report.findings) {
|
|
1390
|
+
console.log(` [${finding.severity.toUpperCase()}] ${finding.rule}: ${finding.message}`);
|
|
1391
|
+
}
|
|
1392
|
+
console.log(' Required checks:');
|
|
1393
|
+
for (const check of report.requiredChecks)
|
|
1394
|
+
console.log(` - ${check}`);
|
|
1395
|
+
if (report.blocked)
|
|
1396
|
+
process.exitCode = 1;
|
|
1397
|
+
},
|
|
1398
|
+
});
|
|
1399
|
+
const skillRadarCommand = defineCommand({
|
|
1400
|
+
meta: { name: 'radar', description: 'Recommend skills, MCP, and CLI capabilities with confidence, safety, and evidence requirements' },
|
|
1401
|
+
args: {
|
|
1402
|
+
task: { type: 'string', required: true, description: 'Task description' },
|
|
1403
|
+
phase: { type: 'string', description: 'Workflow phase' },
|
|
1404
|
+
level: { type: 'string', default: 'M', description: 'Task level: S, M, L, or CRITICAL' },
|
|
1405
|
+
files: { type: 'string', description: 'Comma-separated changed or relevant files' },
|
|
1406
|
+
services: { type: 'string', description: 'Comma-separated services or modules' },
|
|
1407
|
+
dir: { type: 'string', default: PROJECT_DIR, description: 'Project directory' },
|
|
1408
|
+
output: { type: 'string', alias: 'o', description: 'Write markdown report to file' },
|
|
1409
|
+
json: { type: 'boolean', default: false, description: 'Output radar report as JSON' },
|
|
1410
|
+
},
|
|
1411
|
+
run({ args }) {
|
|
1412
|
+
const projectDir = resolve(String(args.dir ?? PROJECT_DIR));
|
|
1413
|
+
const scaleDir = resolveScaleDirForProject(projectDir);
|
|
1414
|
+
const report = evaluateSkillRadar({
|
|
1415
|
+
projectDir,
|
|
1416
|
+
scaleDir,
|
|
1417
|
+
task: String(args.task),
|
|
1418
|
+
phase: args.phase ? String(args.phase) : undefined,
|
|
1419
|
+
level: String(args.level ?? 'M'),
|
|
1420
|
+
files: parseCommaList(args.files),
|
|
1421
|
+
services: parseCommaList(args.services),
|
|
1422
|
+
});
|
|
1423
|
+
if (args.output) {
|
|
1424
|
+
const outputPath = resolve(projectDir, String(args.output));
|
|
1425
|
+
ensureDir(dirname(outputPath));
|
|
1426
|
+
writeFileSync(outputPath, renderSkillRadarMarkdown(report), 'utf-8');
|
|
1427
|
+
}
|
|
1428
|
+
if (args.json) {
|
|
1429
|
+
console.log(JSON.stringify(report, null, 2));
|
|
1430
|
+
if (!report.ok)
|
|
1431
|
+
process.exitCode = 1;
|
|
1432
|
+
return;
|
|
1433
|
+
}
|
|
1434
|
+
console.log('\nSCALE Skill Radar');
|
|
1435
|
+
console.log(` Task: ${report.task}`);
|
|
1436
|
+
console.log(` Level: ${report.level}`);
|
|
1437
|
+
console.log(` Domains: ${report.detectedDomains.map(domain => `${domain.domain}:${domain.score}`).join(', ') || 'none'}`);
|
|
1438
|
+
console.log(` Policy: ${report.policyMode}`);
|
|
1439
|
+
console.log(` Tools: ${report.toolSummary.installed}/${report.toolSummary.total} installed`);
|
|
1440
|
+
for (const item of report.recommendations.slice(0, 8)) {
|
|
1441
|
+
console.log(` [${item.action}] ${item.id} confidence=${item.confidence.toFixed(2)} safety=${item.safetyLevel}`);
|
|
1442
|
+
console.log(` evidence: ${item.requiredEvidence.join(', ') || 'none'}`);
|
|
1443
|
+
if (item.safetyLevel === 'blocked' || item.action === 'suggest-fallback')
|
|
1444
|
+
console.log(` fallback: ${item.fallback}`);
|
|
1445
|
+
}
|
|
1446
|
+
if (args.output)
|
|
1447
|
+
console.log(` Report: ${resolve(projectDir, String(args.output))}`);
|
|
1448
|
+
if (!report.ok)
|
|
1449
|
+
process.exitCode = 1;
|
|
1450
|
+
},
|
|
1451
|
+
});
|
|
1452
|
+
const skillRecommendCommand = defineCommand({
|
|
1453
|
+
meta: { name: 'recommend', description: 'Recommend a composable skill workflow for a task' },
|
|
1454
|
+
args: {
|
|
1455
|
+
task: { type: 'string', required: true, description: 'Task description' },
|
|
1456
|
+
phase: { type: 'string', description: 'Workflow phase' },
|
|
1457
|
+
json: { type: 'boolean', default: false },
|
|
1458
|
+
},
|
|
1459
|
+
run({ args }) {
|
|
1460
|
+
const plan = recommendSkillWorkflow({
|
|
1461
|
+
description: args.task,
|
|
1462
|
+
phase: args.phase,
|
|
1463
|
+
});
|
|
1464
|
+
if (args.json) {
|
|
1465
|
+
console.log(JSON.stringify(plan, null, 2));
|
|
1466
|
+
return;
|
|
1467
|
+
}
|
|
1468
|
+
console.log('\nSCALE Skill Recommendation');
|
|
1469
|
+
console.log(` Primary: ${plan.primarySkills.join(', ') || 'none'}`);
|
|
1470
|
+
console.log(` Supporting: ${plan.supportingSkills.join(', ') || 'none'}`);
|
|
1471
|
+
console.log(` Safety required: ${plan.safetyRequired}`);
|
|
1472
|
+
console.log(` Evidence: ${plan.requiredEvidence.join(', ') || 'none'}`);
|
|
1473
|
+
for (const reason of plan.rationale)
|
|
1474
|
+
console.log(` - ${reason}`);
|
|
1475
|
+
},
|
|
1476
|
+
});
|
|
1477
|
+
const skillOutdatedCommand = defineCommand({
|
|
1478
|
+
meta: { name: 'outdated', description: 'List skill update surfaces without installing or upgrading anything' },
|
|
1479
|
+
args: {
|
|
1480
|
+
dir: { type: 'string', default: PROJECT_DIR, description: 'Project directory' },
|
|
1481
|
+
json: { type: 'boolean', default: false, description: 'Print JSON output' },
|
|
1482
|
+
},
|
|
1483
|
+
run({ args }) {
|
|
1484
|
+
const report = createThirdPartyUpdateReport('skill');
|
|
1485
|
+
if (args.json) {
|
|
1486
|
+
console.log(JSON.stringify({ ...report, projectDir: resolve(String(args.dir ?? PROJECT_DIR)) }, null, 2));
|
|
1487
|
+
return;
|
|
1488
|
+
}
|
|
1489
|
+
console.log('\nSCALE Skill Outdated');
|
|
1490
|
+
console.log(` Policy: ${report.policy}`);
|
|
1491
|
+
console.log(` Skills: ${report.summary.total}`);
|
|
1492
|
+
console.log(` Review required: ${report.reviewRequired}`);
|
|
1493
|
+
for (const entry of report.entries) {
|
|
1494
|
+
console.log(` [${entry.updatePolicy}] ${entry.id} trust=${entry.trust} latest=${entry.latestVersion}`);
|
|
1495
|
+
if (entry.source)
|
|
1496
|
+
console.log(` source: ${entry.source}`);
|
|
1497
|
+
console.log(` reason: ${entry.reason}`);
|
|
1498
|
+
}
|
|
1499
|
+
},
|
|
1500
|
+
});
|
|
1501
|
+
export const skillCommand = defineCommand({
|
|
1502
|
+
meta: { name: 'skill', description: 'Skill discovery and management' },
|
|
1503
|
+
subCommands: {
|
|
1504
|
+
scan: skillScan,
|
|
1505
|
+
doctor: skillDoctorCommand,
|
|
1506
|
+
plan: skillPlanCommand,
|
|
1507
|
+
check: skillCheckCommand,
|
|
1508
|
+
repo: skillRepoCommand,
|
|
1509
|
+
safety: skillSafetyCommand,
|
|
1510
|
+
radar: skillRadarCommand,
|
|
1511
|
+
recommend: skillRecommendCommand,
|
|
1512
|
+
outdated: skillOutdatedCommand,
|
|
1513
|
+
},
|
|
1514
|
+
});
|
|
1515
|
+
//# sourceMappingURL=runtimeSkillCommands.js.map
|