@hongmaple0820/scale-engine 0.17.0 → 0.19.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 +296 -237
- package/README.md +157 -63
- package/dist/api/cli.js +661 -33
- package/dist/api/cli.js.map +1 -1
- package/dist/api/doctor.d.ts +5 -1
- package/dist/api/doctor.js +130 -1
- package/dist/api/doctor.js.map +1 -1
- package/dist/api/quickstart.d.ts +3 -0
- package/dist/api/quickstart.js +12 -4
- package/dist/api/quickstart.js.map +1 -1
- package/dist/cli/phaseCommands.js +7 -0
- package/dist/cli/phaseCommands.js.map +1 -1
- package/dist/core/logger.js +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/memory/MemoryFabric.d.ts +118 -0
- package/dist/memory/MemoryFabric.js +281 -0
- package/dist/memory/MemoryFabric.js.map +1 -0
- package/dist/memory/MemoryLearning.d.ts +61 -0
- package/dist/memory/MemoryLearning.js +203 -0
- package/dist/memory/MemoryLearning.js.map +1 -0
- package/dist/memory/index.d.ts +2 -0
- package/dist/memory/index.js +3 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/output/HTMLArtifactLayer.d.ts +97 -0
- package/dist/output/HTMLArtifactLayer.js +576 -0
- package/dist/output/HTMLArtifactLayer.js.map +1 -0
- package/dist/output/index.d.ts +2 -0
- package/dist/output/index.js +1 -0
- package/dist/output/index.js.map +1 -1
- package/dist/prompts/VibeTemplateGallery.js +121 -121
- package/dist/runtime/FinalReportGuard.d.ts +16 -0
- package/dist/runtime/FinalReportGuard.js +14 -0
- package/dist/runtime/FinalReportGuard.js.map +1 -0
- package/dist/runtime/RuntimeDoctor.d.ts +23 -0
- package/dist/runtime/RuntimeDoctor.js +151 -0
- package/dist/runtime/RuntimeDoctor.js.map +1 -0
- package/dist/runtime/RuntimeEvidenceLedger.d.ts +50 -0
- package/dist/runtime/RuntimeEvidenceLedger.js +89 -0
- package/dist/runtime/RuntimeEvidenceLedger.js.map +1 -0
- package/dist/runtime/SessionLedger.d.ts +53 -0
- package/dist/runtime/SessionLedger.js +104 -0
- package/dist/runtime/SessionLedger.js.map +1 -0
- package/dist/runtime/index.d.ts +4 -0
- package/dist/runtime/index.js +5 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/skills/routing/SkillGate.js +26 -2
- package/dist/skills/routing/SkillGate.js.map +1 -1
- package/dist/skills/routing/SkillPolicy.js +2 -2
- package/dist/skills/routing/SkillPolicy.js.map +1 -1
- package/dist/tools/ToolCapabilityRegistry.d.ts +1 -1
- package/dist/tools/ToolCapabilityRegistry.js +4 -4
- package/dist/tools/ToolCapabilityRegistry.js.map +1 -1
- package/dist/tools/ToolOrchestrator.js +5 -1
- package/dist/tools/ToolOrchestrator.js.map +1 -1
- package/dist/workflow/EngineeringStandards.js +69 -66
- package/dist/workflow/EngineeringStandards.js.map +1 -1
- package/dist/workflow/GovernanceTemplatePacks.d.ts +1 -1
- package/dist/workflow/GovernanceTemplatePacks.js +137 -79
- package/dist/workflow/GovernanceTemplatePacks.js.map +1 -1
- package/dist/workflow/GovernanceTemplates.d.ts +1 -1
- package/dist/workflow/GovernanceTemplates.js +494 -199
- package/dist/workflow/GovernanceTemplates.js.map +1 -1
- package/dist/workflow/ResourceGovernance.js +29 -19
- package/dist/workflow/ResourceGovernance.js.map +1 -1
- package/dist/workflow/VerificationCommands.d.ts +11 -0
- package/dist/workflow/VerificationCommands.js +2 -0
- package/dist/workflow/VerificationCommands.js.map +1 -1
- package/dist/workflow/VerificationProfile.d.ts +2 -1
- package/dist/workflow/VerificationProfile.js +3 -0
- package/dist/workflow/VerificationProfile.js.map +1 -1
- package/dist/workflow/WorkflowArtifactWriter.js +2 -1
- package/dist/workflow/WorkflowArtifactWriter.js.map +1 -1
- package/dist/workflow/WorkflowEngine.js +4 -1
- package/dist/workflow/WorkflowEngine.js.map +1 -1
- package/dist/workflow/WorkspaceSafety.d.ts +9 -0
- package/dist/workflow/WorkspaceSafety.js +49 -0
- package/dist/workflow/WorkspaceSafety.js.map +1 -0
- package/dist/workflow/gates/GateSystem.d.ts +12 -1
- package/dist/workflow/gates/GateSystem.js +106 -0
- package/dist/workflow/gates/GateSystem.js.map +1 -1
- package/dist/workflow/types.d.ts +1 -1
- package/docs/MEMORY_FABRIC.md +107 -0
- package/docs/README.md +68 -0
- package/docs/RUNTIME_EVIDENCE.md +101 -0
- package/docs/start/README.md +42 -0
- package/docs/start/agent-governance-demo.md +107 -0
- package/docs/start/quickstart.md +127 -0
- package/examples/demo-projects/agent-governance-demo/README.md +37 -0
- package/examples/demo-projects/agent-governance-demo/package.json +16 -0
- package/examples/demo-projects/agent-governance-demo/src/oauth-state.ts +39 -0
- package/examples/demo-projects/agent-governance-demo/tests/oauth-state.test.ts +52 -0
- package/package.json +8 -3
package/dist/api/cli.js
CHANGED
|
@@ -20,7 +20,7 @@ import { createSkillPlan, evaluateSkillGate, loadSkillRoutingPolicy, skillPlanMa
|
|
|
20
20
|
import { createAdapter, SUPPORTED_AGENTS } from '../adapters/index.js';
|
|
21
21
|
import { LessonExtractor, RuleProposer, HookGenerator, EvolutionEngine } from '../evolution/EvolutionEngine.js';
|
|
22
22
|
import { Doctor } from './doctor.js';
|
|
23
|
-
import { quickStart, detectPlatform } from './quickstart.js';
|
|
23
|
+
import { quickStart, detectPlatform, governanceNextSteps } from './quickstart.js';
|
|
24
24
|
import { SkillDiscovery } from '../skills/SkillDiscovery.js';
|
|
25
25
|
import { inspectRequiredWorkflowSkills, inspectWorkflowSkills } from '../skills/SkillDoctor.js';
|
|
26
26
|
import { evaluateSkillInstallSafety, listSkillRepositoryEntries, recommendSkillWorkflow, renderSkillRepositoryMarkdown, } from '../skills/SkillRepository.js';
|
|
@@ -30,7 +30,7 @@ import { EvidenceStore } from '../workflow/EvidenceStore.js';
|
|
|
30
30
|
import { OutOfScopeStore } from '../workflow/OutOfScopeStore.js';
|
|
31
31
|
import { ReviewStore } from '../workflow/ReviewStore.js';
|
|
32
32
|
import { WorkflowEngine } from '../workflow/WorkflowEngine.js';
|
|
33
|
-
import { resolveVerificationTargets } from '../workflow/VerificationProfile.js';
|
|
33
|
+
import { resolveVerificationTargets, } from '../workflow/VerificationProfile.js';
|
|
34
34
|
import { writeGovernanceTemplates } from '../workflow/GovernanceTemplates.js';
|
|
35
35
|
import { computeGovernanceDrift } from '../workflow/GovernanceLock.js';
|
|
36
36
|
import { baselineEngineeringStandards, doctorEngineeringStandards, scanEngineeringStandards, settleEngineeringStandards, } from '../workflow/EngineeringStandards.js';
|
|
@@ -47,11 +47,16 @@ import { evaluateToolEvidenceGate } from '../tools/ToolEvidenceGate.js';
|
|
|
47
47
|
import { ToolEvidenceStore } from '../tools/ToolEvidenceStore.js';
|
|
48
48
|
import { ToolOrchestrator } from '../tools/ToolOrchestrator.js';
|
|
49
49
|
import { loadToolPolicy, toolPolicyTemplate } from '../tools/ToolPolicy.js';
|
|
50
|
+
import { doctorHtmlArtifacts, renderHtmlArtifact, resolveHtmlArtifactForOpen, settleHtmlArtifacts, } from '../output/HTMLArtifactLayer.js';
|
|
50
51
|
import { cleanupWorkspaceLifecycle, inspectWorkspaceLifecycle, } from '../workflow/WorkspaceLifecycle.js';
|
|
52
|
+
import { inspectWorkspaceSafety } from '../workflow/WorkspaceSafety.js';
|
|
53
|
+
import { RuntimeEvidenceLedger, SessionLedger, doctorRuntimeEvidence, evaluateFinalReportReadiness, } from '../runtime/index.js';
|
|
54
|
+
import { MemoryFabric, doctorMemoryFabric, renderContextPackMarkdown, renderMemoryLearningCandidateMarkdown, settleMemoryLearning, } from '../memory/index.js';
|
|
51
55
|
import { resolveWorkspaceTopology, workspaceTopologyPath, workspaceTopologyTemplate, } from '../workflow/WorkspaceTopology.js';
|
|
52
56
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
53
|
-
import { join, resolve } from 'node:path';
|
|
57
|
+
import { isAbsolute, join, resolve } from 'node:path';
|
|
54
58
|
import { execFileSync } from 'node:child_process';
|
|
59
|
+
import { pathToFileURL } from 'node:url';
|
|
55
60
|
import { SCALE_ENGINE_VERSION } from '../version.js';
|
|
56
61
|
// ============================================================================
|
|
57
62
|
// Engine bootstrap (单例 + lazy init)
|
|
@@ -96,6 +101,26 @@ function gatesForPreflightProfile(profile) {
|
|
|
96
101
|
return ['G3', 'G0', 'G4', 'G5'];
|
|
97
102
|
return ['G3', 'G0', 'G4', 'G5', 'G6', 'G7'];
|
|
98
103
|
}
|
|
104
|
+
function shouldSkipPreflightCommandTargets(resolved, args) {
|
|
105
|
+
if (!resolved.matrix)
|
|
106
|
+
return false;
|
|
107
|
+
const requestedService = String(args.service ?? '').trim();
|
|
108
|
+
if (requestedService && requestedService !== 'all')
|
|
109
|
+
return false;
|
|
110
|
+
const hasCommandOverrides = [
|
|
111
|
+
args['build-cmd'],
|
|
112
|
+
args['lint-cmd'],
|
|
113
|
+
args['test-cmd'],
|
|
114
|
+
args['coverage-cmd'],
|
|
115
|
+
].some(value => typeof value === 'string' && value.trim().length > 0);
|
|
116
|
+
if (hasCommandOverrides)
|
|
117
|
+
return false;
|
|
118
|
+
const profile = resolved.matrix.profiles?.[resolved.profileName];
|
|
119
|
+
const hasProfileCommands = Object.values(profile?.commands ?? {})
|
|
120
|
+
.some(value => typeof value === 'string' && value.trim().length > 0);
|
|
121
|
+
const hasServices = (resolved.matrix.services ?? []).length > 0;
|
|
122
|
+
return !hasServices && !hasProfileCommands;
|
|
123
|
+
}
|
|
99
124
|
function evaluateEngineeringStandardsGate(options) {
|
|
100
125
|
const mode = normalizeEngineeringStandardsGateMode(options.policy.engineeringStandardsGate);
|
|
101
126
|
if (mode === 'off') {
|
|
@@ -109,15 +134,15 @@ function evaluateEngineeringStandardsGate(options) {
|
|
|
109
134
|
}
|
|
110
135
|
const settlement = options.settle && options.artifactsDir
|
|
111
136
|
? settleEngineeringStandards({
|
|
112
|
-
projectDir: PROJECT_DIR,
|
|
113
|
-
scaleDir: SCALE_DIR,
|
|
137
|
+
projectDir: options.projectDir ?? PROJECT_DIR,
|
|
138
|
+
scaleDir: options.scaleDir ?? SCALE_DIR,
|
|
114
139
|
taskId: options.taskId,
|
|
115
140
|
artifactsDir: options.artifactsDir,
|
|
116
141
|
})
|
|
117
142
|
: undefined;
|
|
118
143
|
const doctor = settlement?.doctor ?? doctorEngineeringStandards({
|
|
119
|
-
projectDir: PROJECT_DIR,
|
|
120
|
-
scaleDir: SCALE_DIR,
|
|
144
|
+
projectDir: options.projectDir ?? PROJECT_DIR,
|
|
145
|
+
scaleDir: options.scaleDir ?? SCALE_DIR,
|
|
121
146
|
});
|
|
122
147
|
return {
|
|
123
148
|
mode,
|
|
@@ -129,6 +154,16 @@ function evaluateEngineeringStandardsGate(options) {
|
|
|
129
154
|
standardsImpactPath: settlement?.standardsImpactPath,
|
|
130
155
|
};
|
|
131
156
|
}
|
|
157
|
+
function skippedEngineeringStandardsGate(reason, policy) {
|
|
158
|
+
void reason;
|
|
159
|
+
return {
|
|
160
|
+
mode: normalizeEngineeringStandardsGateMode(policy.engineeringStandardsGate),
|
|
161
|
+
checked: false,
|
|
162
|
+
blocked: false,
|
|
163
|
+
ok: true,
|
|
164
|
+
findings: [],
|
|
165
|
+
};
|
|
166
|
+
}
|
|
132
167
|
function normalizeEngineeringStandardsGateMode(value) {
|
|
133
168
|
return value === 'off' || value === 'block' ? value : 'warn';
|
|
134
169
|
}
|
|
@@ -172,6 +207,23 @@ function createEngine() {
|
|
|
172
207
|
});
|
|
173
208
|
return { eventBus, store, fsm, gateway, roleGate, kb, ctx, fsmAgentBridge, workflowEngine };
|
|
174
209
|
}
|
|
210
|
+
function resolveScaleDirForProject(projectDir) {
|
|
211
|
+
return isAbsolute(SCALE_DIR) ? SCALE_DIR : join(projectDir, SCALE_DIR);
|
|
212
|
+
}
|
|
213
|
+
function createVerificationWorkflowEngine(scaleDir) {
|
|
214
|
+
ensureDir(scaleDir);
|
|
215
|
+
const eventBus = new EventBus({ eventsDir: join(scaleDir, 'events') });
|
|
216
|
+
const capabilityRegistry = new CapabilityRegistry(eventBus);
|
|
217
|
+
const skillRegistry = new SkillRegistry(eventBus);
|
|
218
|
+
registerCoreSkills(skillRegistry);
|
|
219
|
+
registerExternalSkills(skillRegistry, eventBus);
|
|
220
|
+
return new WorkflowEngine({
|
|
221
|
+
eventBus,
|
|
222
|
+
capabilityRegistry,
|
|
223
|
+
skillRegistry,
|
|
224
|
+
scaleDir,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
175
227
|
// ============================================================================
|
|
176
228
|
// session commands
|
|
177
229
|
// ============================================================================
|
|
@@ -1357,6 +1409,7 @@ function normalizeWorkspaceTopologyKind(value) {
|
|
|
1357
1409
|
const preflight = defineCommand({
|
|
1358
1410
|
meta: { name: 'preflight', description: 'Run service-aware verification without a task artifact' },
|
|
1359
1411
|
args: {
|
|
1412
|
+
dir: { type: 'string', default: PROJECT_DIR, description: 'Project directory' },
|
|
1360
1413
|
'build-cmd': { type: 'string', description: 'Override build command' },
|
|
1361
1414
|
'lint-cmd': { type: 'string', description: 'Override lint command' },
|
|
1362
1415
|
'test-cmd': { type: 'string', description: 'Override test command' },
|
|
@@ -1369,18 +1422,32 @@ const preflight = defineCommand({
|
|
|
1369
1422
|
json: { type: 'boolean', default: false },
|
|
1370
1423
|
},
|
|
1371
1424
|
async run({ args }) {
|
|
1372
|
-
const
|
|
1425
|
+
const projectDir = resolve(String(args.dir ?? PROJECT_DIR));
|
|
1426
|
+
const scaleDir = resolveScaleDirForProject(projectDir);
|
|
1427
|
+
const workflowEngine = createVerificationWorkflowEngine(scaleDir);
|
|
1373
1428
|
const preflightProfile = normalizePreflightProfile(args['preflight-profile']);
|
|
1374
|
-
const gateStages = gatesForPreflightProfile(preflightProfile);
|
|
1375
1429
|
const resolved = resolveVerificationTargets({
|
|
1376
|
-
projectDir
|
|
1377
|
-
scaleDir
|
|
1430
|
+
projectDir,
|
|
1431
|
+
scaleDir,
|
|
1378
1432
|
profile: args.profile,
|
|
1379
1433
|
service: args.service,
|
|
1380
1434
|
});
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1435
|
+
let gateStages = gatesForPreflightProfile(preflightProfile);
|
|
1436
|
+
if (resolved.targets.some(target => target.config.smoke)) {
|
|
1437
|
+
gateStages = ['G8'];
|
|
1438
|
+
}
|
|
1439
|
+
const commandTargetsSkipped = shouldSkipPreflightCommandTargets(resolved, args);
|
|
1440
|
+
if (commandTargetsSkipped) {
|
|
1441
|
+
resolved.warnings.push('No verification services or profile commands configured; command gates skipped for this governance-only project.');
|
|
1442
|
+
}
|
|
1443
|
+
const workspaceSafety = inspectWorkspaceSafety(projectDir);
|
|
1444
|
+
const engineeringStandards = workspaceSafety.blocked
|
|
1445
|
+
? skippedEngineeringStandardsGate('Workspace has unresolved git conflicts; resolve them before standards scanning.', resolved.policy)
|
|
1446
|
+
: evaluateEngineeringStandardsGate({
|
|
1447
|
+
policy: resolved.policy,
|
|
1448
|
+
projectDir,
|
|
1449
|
+
scaleDir,
|
|
1450
|
+
});
|
|
1384
1451
|
const targetResults = [];
|
|
1385
1452
|
if (!args.json) {
|
|
1386
1453
|
console.log('\nSCALE Preflight');
|
|
@@ -1389,6 +1456,9 @@ const preflight = defineCommand({
|
|
|
1389
1456
|
console.log(` Profile: ${resolved.profileName}`);
|
|
1390
1457
|
console.log(` Preflight profile: ${preflightProfile}`);
|
|
1391
1458
|
console.log(` Gates: ${gateStages.join(', ')}`);
|
|
1459
|
+
if (workspaceSafety.blocked) {
|
|
1460
|
+
console.log(` Workspace safety: BLOCKED - ${workspaceSafety.message}`);
|
|
1461
|
+
}
|
|
1392
1462
|
if (engineeringStandards.checked) {
|
|
1393
1463
|
const status = engineeringStandards.blocked ? 'BLOCKED' : engineeringStandards.ok ? 'OK' : 'WARN';
|
|
1394
1464
|
console.log(` Engineering standards: ${status} (${engineeringStandards.mode})`);
|
|
@@ -1397,7 +1467,7 @@ const preflight = defineCommand({
|
|
|
1397
1467
|
console.log(' Engineering standards: skipped');
|
|
1398
1468
|
}
|
|
1399
1469
|
}
|
|
1400
|
-
for (const target of resolved.targets) {
|
|
1470
|
+
for (const target of commandTargetsSkipped || workspaceSafety.blocked ? [] : resolved.targets) {
|
|
1401
1471
|
if (!args.json) {
|
|
1402
1472
|
const label = target.service ? `${target.service.name} (${target.service.path})` : 'root';
|
|
1403
1473
|
console.log(`\n Target: ${label}`);
|
|
@@ -1408,6 +1478,12 @@ const preflight = defineCommand({
|
|
|
1408
1478
|
lint: args['lint-cmd'] ?? target.config.lint,
|
|
1409
1479
|
test: args['test-cmd'] ?? target.config.test,
|
|
1410
1480
|
coverage: args['coverage-cmd'] ?? target.config.coverage,
|
|
1481
|
+
smoke: target.config.smoke,
|
|
1482
|
+
runtimeEvidence: {
|
|
1483
|
+
projectDir,
|
|
1484
|
+
scaleDir,
|
|
1485
|
+
profile: resolved.profileName,
|
|
1486
|
+
},
|
|
1411
1487
|
tddEvidence: args['tdd-evidence'],
|
|
1412
1488
|
tddStrict: isTruthyFlag(args['tdd-strict']),
|
|
1413
1489
|
gates: gateStages,
|
|
@@ -1415,7 +1491,7 @@ const preflight = defineCommand({
|
|
|
1415
1491
|
const passed = gates.every(gate => gate.passed);
|
|
1416
1492
|
targetResults.push({
|
|
1417
1493
|
service: target.service?.name,
|
|
1418
|
-
cwd: target.config.cwd ??
|
|
1494
|
+
cwd: target.config.cwd ?? projectDir,
|
|
1419
1495
|
gates,
|
|
1420
1496
|
passed,
|
|
1421
1497
|
});
|
|
@@ -1427,8 +1503,8 @@ const preflight = defineCommand({
|
|
|
1427
1503
|
}
|
|
1428
1504
|
}
|
|
1429
1505
|
}
|
|
1430
|
-
const passed = targetResults.length
|
|
1431
|
-
|
|
1506
|
+
const passed = (targetResults.length === 0 || targetResults.every(target => target.passed)) &&
|
|
1507
|
+
!workspaceSafety.blocked &&
|
|
1432
1508
|
!engineeringStandards.blocked;
|
|
1433
1509
|
const result = {
|
|
1434
1510
|
phase: 'PREFLIGHT',
|
|
@@ -1437,8 +1513,10 @@ const preflight = defineCommand({
|
|
|
1437
1513
|
gates: gateStages,
|
|
1438
1514
|
services: targetResults.map(target => target.service).filter(Boolean),
|
|
1439
1515
|
policy: resolved.policy,
|
|
1516
|
+
workspaceSafety,
|
|
1440
1517
|
engineeringStandards,
|
|
1441
1518
|
targets: targetResults,
|
|
1519
|
+
commandTargetsSkipped,
|
|
1442
1520
|
passed,
|
|
1443
1521
|
};
|
|
1444
1522
|
if (args.json) {
|
|
@@ -1471,7 +1549,7 @@ const status = defineCommand({
|
|
|
1471
1549
|
const latestTask = tasks[0];
|
|
1472
1550
|
const taskPayload = latestTask?.payload;
|
|
1473
1551
|
const workflowState = new WorkflowArtifactWriter(SCALE_DIR).readCurrentState();
|
|
1474
|
-
const currentOpenTasks = workflowState?.
|
|
1552
|
+
const currentOpenTasks = workflowState?.openTasks ?? [];
|
|
1475
1553
|
const nextOpenTask = nextWorkflowOpenTask(currentOpenTasks);
|
|
1476
1554
|
const blockers = [];
|
|
1477
1555
|
const latestBlockingEvidence = latestEvidence.find(record => !record.passed);
|
|
@@ -1487,16 +1565,16 @@ const status = defineCommand({
|
|
|
1487
1565
|
blockers.push(`Task ${latestTask.id} has no persisted review evidence`);
|
|
1488
1566
|
}
|
|
1489
1567
|
const nextCommand = (() => {
|
|
1568
|
+
if (nextOpenTask?.kind === 'command')
|
|
1569
|
+
return nextOpenTask.value;
|
|
1570
|
+
if (nextOpenTask?.kind === 'blocker')
|
|
1571
|
+
return `Resolve workflow blocker: ${nextOpenTask.value}`;
|
|
1490
1572
|
if (!specs[0])
|
|
1491
1573
|
return 'scale define "<feature>" --description "<what to build>"';
|
|
1492
1574
|
if (!plans[0])
|
|
1493
1575
|
return `scale plan ${specs[0].id}`;
|
|
1494
1576
|
if (!latestTask)
|
|
1495
1577
|
return `scale build ${plans[0].id}`;
|
|
1496
|
-
if (nextOpenTask?.kind === 'command')
|
|
1497
|
-
return nextOpenTask.value;
|
|
1498
|
-
if (nextOpenTask?.kind === 'blocker')
|
|
1499
|
-
return `Resolve workflow blocker: ${nextOpenTask.value}`;
|
|
1500
1578
|
if (!taskPayload?.verificationEvidenceIds?.length)
|
|
1501
1579
|
return `scale verify ${latestTask.id}`;
|
|
1502
1580
|
if (latestTask.status !== 'COMPLETED')
|
|
@@ -1543,7 +1621,7 @@ const status = defineCommand({
|
|
|
1543
1621
|
level: workflowState.level,
|
|
1544
1622
|
phase: workflowState.phase,
|
|
1545
1623
|
artifactsDir: workflowState.artifactsDir,
|
|
1546
|
-
openTasks: workflowState.openTasks,
|
|
1624
|
+
openTasks: workflowState.openTasks ?? [],
|
|
1547
1625
|
skillIntents: workflowState.skillIntents,
|
|
1548
1626
|
} : null,
|
|
1549
1627
|
blockers,
|
|
@@ -1575,7 +1653,7 @@ const status = defineCommand({
|
|
|
1575
1653
|
for (const blocker of blockers)
|
|
1576
1654
|
console.log(` - ${blocker}`);
|
|
1577
1655
|
}
|
|
1578
|
-
if (result.workflowState?.openTasks.length) {
|
|
1656
|
+
if ((result.workflowState?.openTasks.length ?? 0) > 0) {
|
|
1579
1657
|
console.log('\nOpen Tasks:');
|
|
1580
1658
|
for (const task of result.workflowState.openTasks)
|
|
1581
1659
|
console.log(` - ${task}`);
|
|
@@ -1678,8 +1756,8 @@ const init = defineCommand({
|
|
|
1678
1756
|
console.log(` Data dir: ${result.scaleDir}`);
|
|
1679
1757
|
console.log(` Scenario: ${scenarioMode}`);
|
|
1680
1758
|
console.log(`\n📋 Next steps:`);
|
|
1681
|
-
|
|
1682
|
-
|
|
1759
|
+
for (const step of governanceNextSteps())
|
|
1760
|
+
console.log(` → ${step}`);
|
|
1683
1761
|
return;
|
|
1684
1762
|
}
|
|
1685
1763
|
// One-click quick start mode
|
|
@@ -1689,7 +1767,7 @@ const init = defineCommand({
|
|
|
1689
1767
|
const detection = qsResult.success ? undefined : detectPlatform(args.dir);
|
|
1690
1768
|
console.log(JSON.stringify({
|
|
1691
1769
|
ok: qsResult.success,
|
|
1692
|
-
mode: 'quick',
|
|
1770
|
+
mode: qsResult.success && !qsResult.platform ? 'governance-only' : 'quick',
|
|
1693
1771
|
platform: qsResult.platform,
|
|
1694
1772
|
created: qsResult.created,
|
|
1695
1773
|
skipped: qsResult.skipped,
|
|
@@ -1701,8 +1779,11 @@ const init = defineCommand({
|
|
|
1701
1779
|
}, null, 2));
|
|
1702
1780
|
return;
|
|
1703
1781
|
}
|
|
1704
|
-
if (qsResult.success
|
|
1705
|
-
|
|
1782
|
+
if (qsResult.success) {
|
|
1783
|
+
if (!qsResult.platform)
|
|
1784
|
+
console.log(`\nSCALE governance templates initialized`);
|
|
1785
|
+
else
|
|
1786
|
+
console.log(`\n✅ SCALE Engine Quick Start completed for ${qsResult.platform}`);
|
|
1706
1787
|
console.log(`\n📁 Created (${qsResult.created.length}):`);
|
|
1707
1788
|
for (const f of qsResult.created)
|
|
1708
1789
|
console.log(` + ${f}`);
|
|
@@ -1748,7 +1829,7 @@ const init = defineCommand({
|
|
|
1748
1829
|
scaleDir: result.scaleDir,
|
|
1749
1830
|
created: result.created,
|
|
1750
1831
|
skipped: result.skipped,
|
|
1751
|
-
nextSteps:
|
|
1832
|
+
nextSteps: governanceNextSteps(),
|
|
1752
1833
|
}, null, 2));
|
|
1753
1834
|
return;
|
|
1754
1835
|
}
|
|
@@ -1765,8 +1846,8 @@ const init = defineCommand({
|
|
|
1765
1846
|
console.log(`\n📖 Knowledge: ${result.knowledgeDocPath}`);
|
|
1766
1847
|
console.log(`\n📂 Data dir: ${result.scaleDir}`);
|
|
1767
1848
|
console.log(`\n📋 Next steps:`);
|
|
1768
|
-
|
|
1769
|
-
|
|
1849
|
+
for (const step of governanceNextSteps())
|
|
1850
|
+
console.log(` → ${step}`);
|
|
1770
1851
|
},
|
|
1771
1852
|
});
|
|
1772
1853
|
// ============================================================================
|
|
@@ -2056,6 +2137,182 @@ const standards = defineCommand({
|
|
|
2056
2137
|
subCommands: { scan: standardsScan, doctor: standardsDoctor, settle: standardsSettle, baseline: standardsBaseline },
|
|
2057
2138
|
});
|
|
2058
2139
|
// ============================================================================
|
|
2140
|
+
// artifact command - Derived HTML artifacts for human review
|
|
2141
|
+
// ============================================================================
|
|
2142
|
+
const artifactRender = defineCommand({
|
|
2143
|
+
meta: { name: 'render', description: 'Render a task Markdown source set into a governed HTML artifact' },
|
|
2144
|
+
args: {
|
|
2145
|
+
dir: { type: 'string', default: '.', description: 'Project directory' },
|
|
2146
|
+
'task-id': { type: 'string', description: 'Task id under docs/worklog/tasks' },
|
|
2147
|
+
'artifact-dir': { type: 'string', description: 'Task artifact directory override' },
|
|
2148
|
+
type: { type: 'string', default: 'release-report', description: 'HTML artifact type' },
|
|
2149
|
+
source: { type: 'string', description: 'Comma or newline separated source Markdown files relative to the task directory' },
|
|
2150
|
+
theme: { type: 'string', default: 'auto', description: 'Theme mode: dark/light/auto' },
|
|
2151
|
+
lang: { type: 'string', default: 'zh', description: 'HTML language: zh/en' },
|
|
2152
|
+
title: { type: 'string', description: 'HTML document title override' },
|
|
2153
|
+
json: { type: 'boolean', default: false, description: 'Print JSON output' },
|
|
2154
|
+
},
|
|
2155
|
+
run({ args }) {
|
|
2156
|
+
const result = renderHtmlArtifact({
|
|
2157
|
+
projectDir: args.dir,
|
|
2158
|
+
taskId: args['task-id'],
|
|
2159
|
+
artifactDir: args['artifact-dir'],
|
|
2160
|
+
type: String(args.type ?? 'release-report'),
|
|
2161
|
+
sourcePaths: splitChangedFiles(typeof args.source === 'string' ? args.source : undefined),
|
|
2162
|
+
theme: normalizeThemeArg(args.theme),
|
|
2163
|
+
lang: normalizeLangArg(args.lang),
|
|
2164
|
+
title: typeof args.title === 'string' ? args.title : undefined,
|
|
2165
|
+
});
|
|
2166
|
+
if (args.json) {
|
|
2167
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2168
|
+
return;
|
|
2169
|
+
}
|
|
2170
|
+
console.log('SCALE HTML Artifact Render');
|
|
2171
|
+
console.log(` Type: ${result.type}`);
|
|
2172
|
+
console.log(` HTML: ${result.outputPath}`);
|
|
2173
|
+
console.log(` Index: ${result.indexPath}`);
|
|
2174
|
+
console.log(` Manifest: ${result.manifestPath}`);
|
|
2175
|
+
if (result.missingSources.length > 0) {
|
|
2176
|
+
console.log(` Missing sources: ${result.missingSources.join(', ')}`);
|
|
2177
|
+
}
|
|
2178
|
+
},
|
|
2179
|
+
});
|
|
2180
|
+
const artifactDoctor = defineCommand({
|
|
2181
|
+
meta: { name: 'doctor', description: 'Check HTML artifacts for traceability, stale sources, remote assets, and secret-like content' },
|
|
2182
|
+
args: {
|
|
2183
|
+
dir: { type: 'string', default: '.', description: 'Project directory' },
|
|
2184
|
+
'task-id': { type: 'string', description: 'Task id under docs/worklog/tasks' },
|
|
2185
|
+
'artifact-dir': { type: 'string', description: 'Task artifact directory override' },
|
|
2186
|
+
type: { type: 'string', description: 'Optional HTML artifact type to check' },
|
|
2187
|
+
json: { type: 'boolean', default: false, description: 'Print JSON output' },
|
|
2188
|
+
},
|
|
2189
|
+
run({ args }) {
|
|
2190
|
+
const report = doctorHtmlArtifacts({
|
|
2191
|
+
projectDir: args.dir,
|
|
2192
|
+
taskId: args['task-id'],
|
|
2193
|
+
artifactDir: args['artifact-dir'],
|
|
2194
|
+
type: typeof args.type === 'string' ? args.type : undefined,
|
|
2195
|
+
});
|
|
2196
|
+
if (args.json) {
|
|
2197
|
+
console.log(JSON.stringify(report, null, 2));
|
|
2198
|
+
if (!report.ok)
|
|
2199
|
+
process.exitCode = 1;
|
|
2200
|
+
return;
|
|
2201
|
+
}
|
|
2202
|
+
console.log(`SCALE HTML Artifact Doctor: ${report.ok ? 'OK' : 'FAILED'}`);
|
|
2203
|
+
console.log(` Manifest: ${report.manifestPath}`);
|
|
2204
|
+
console.log(` Artifacts: ${report.artifacts.length}`);
|
|
2205
|
+
if (report.findings.length === 0) {
|
|
2206
|
+
console.log(' No HTML artifact findings.');
|
|
2207
|
+
}
|
|
2208
|
+
else {
|
|
2209
|
+
for (const finding of report.findings) {
|
|
2210
|
+
const path = finding.path ? ` ${finding.path}` : '';
|
|
2211
|
+
console.log(` [${finding.severity.toUpperCase()}] ${finding.code}${path}: ${finding.message}`);
|
|
2212
|
+
if (finding.fix)
|
|
2213
|
+
console.log(` fix: ${finding.fix}`);
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
if (!report.ok)
|
|
2217
|
+
process.exitCode = 1;
|
|
2218
|
+
},
|
|
2219
|
+
});
|
|
2220
|
+
const artifactSettle = defineCommand({
|
|
2221
|
+
meta: { name: 'settle', description: 'Record HTML artifact settlement evidence for a task' },
|
|
2222
|
+
args: {
|
|
2223
|
+
dir: { type: 'string', default: '.', description: 'Project directory' },
|
|
2224
|
+
'task-id': { type: 'string', description: 'Task id under docs/worklog/tasks' },
|
|
2225
|
+
'artifact-dir': { type: 'string', description: 'Task artifact directory override' },
|
|
2226
|
+
json: { type: 'boolean', default: false, description: 'Print JSON output' },
|
|
2227
|
+
},
|
|
2228
|
+
run({ args }) {
|
|
2229
|
+
const report = settleHtmlArtifacts({
|
|
2230
|
+
projectDir: args.dir,
|
|
2231
|
+
taskId: args['task-id'],
|
|
2232
|
+
artifactDir: args['artifact-dir'],
|
|
2233
|
+
});
|
|
2234
|
+
if (args.json) {
|
|
2235
|
+
console.log(JSON.stringify(report, null, 2));
|
|
2236
|
+
if (!report.ok)
|
|
2237
|
+
process.exitCode = 1;
|
|
2238
|
+
return;
|
|
2239
|
+
}
|
|
2240
|
+
console.log(`SCALE HTML Artifact Settlement: ${report.ok ? 'OK' : 'FAILED'}`);
|
|
2241
|
+
console.log(` HTML impact: ${report.htmlImpactPath}`);
|
|
2242
|
+
for (const finding of report.doctor.findings) {
|
|
2243
|
+
const path = finding.path ? ` ${finding.path}` : '';
|
|
2244
|
+
console.log(` [${finding.severity.toUpperCase()}] ${finding.code}${path}: ${finding.message}`);
|
|
2245
|
+
}
|
|
2246
|
+
if (!report.ok)
|
|
2247
|
+
process.exitCode = 1;
|
|
2248
|
+
},
|
|
2249
|
+
});
|
|
2250
|
+
const artifactOpen = defineCommand({
|
|
2251
|
+
meta: { name: 'open', description: 'Open or print the local file URL for a rendered HTML artifact' },
|
|
2252
|
+
args: {
|
|
2253
|
+
dir: { type: 'string', default: '.', description: 'Project directory' },
|
|
2254
|
+
'task-id': { type: 'string', description: 'Task id under docs/worklog/tasks' },
|
|
2255
|
+
'artifact-dir': { type: 'string', description: 'Task artifact directory override' },
|
|
2256
|
+
type: { type: 'string', description: 'Optional HTML artifact type to open' },
|
|
2257
|
+
'print-only': { type: 'boolean', default: false, description: 'Only print the file URL without launching a browser' },
|
|
2258
|
+
json: { type: 'boolean', default: false, description: 'Print JSON output' },
|
|
2259
|
+
},
|
|
2260
|
+
run({ args }) {
|
|
2261
|
+
const path = resolveHtmlArtifactForOpen({
|
|
2262
|
+
projectDir: args.dir,
|
|
2263
|
+
taskId: args['task-id'],
|
|
2264
|
+
artifactDir: args['artifact-dir'],
|
|
2265
|
+
type: typeof args.type === 'string' ? args.type : undefined,
|
|
2266
|
+
});
|
|
2267
|
+
const url = pathToFileURL(path).toString();
|
|
2268
|
+
const exists = existsSync(path);
|
|
2269
|
+
if (!args['print-only'] && exists)
|
|
2270
|
+
launchLocalFile(path);
|
|
2271
|
+
const output = { ok: exists, path, url, launched: Boolean(!args['print-only'] && exists) };
|
|
2272
|
+
if (args.json) {
|
|
2273
|
+
console.log(JSON.stringify(output, null, 2));
|
|
2274
|
+
if (!exists)
|
|
2275
|
+
process.exitCode = 1;
|
|
2276
|
+
return;
|
|
2277
|
+
}
|
|
2278
|
+
if (!exists) {
|
|
2279
|
+
console.log(`HTML artifact not found: ${path}`);
|
|
2280
|
+
process.exitCode = 1;
|
|
2281
|
+
return;
|
|
2282
|
+
}
|
|
2283
|
+
console.log(url);
|
|
2284
|
+
},
|
|
2285
|
+
});
|
|
2286
|
+
const artifact = defineCommand({
|
|
2287
|
+
meta: { name: 'artifact', description: 'Derived HTML artifact rendering and safety checks' },
|
|
2288
|
+
subCommands: { render: artifactRender, doctor: artifactDoctor, settle: artifactSettle, open: artifactOpen },
|
|
2289
|
+
});
|
|
2290
|
+
function normalizeThemeArg(value) {
|
|
2291
|
+
const normalized = String(value ?? 'auto').trim().toLowerCase();
|
|
2292
|
+
if (normalized === 'dark' || normalized === 'light' || normalized === 'auto')
|
|
2293
|
+
return normalized;
|
|
2294
|
+
return 'auto';
|
|
2295
|
+
}
|
|
2296
|
+
function normalizeLangArg(value) {
|
|
2297
|
+
return String(value ?? 'zh').trim().toLowerCase() === 'en' ? 'en' : 'zh';
|
|
2298
|
+
}
|
|
2299
|
+
function launchLocalFile(path) {
|
|
2300
|
+
try {
|
|
2301
|
+
if (process.platform === 'win32') {
|
|
2302
|
+
execFileSync('cmd', ['/c', 'start', '', path], { stdio: 'ignore' });
|
|
2303
|
+
}
|
|
2304
|
+
else if (process.platform === 'darwin') {
|
|
2305
|
+
execFileSync('open', [path], { stdio: 'ignore' });
|
|
2306
|
+
}
|
|
2307
|
+
else {
|
|
2308
|
+
execFileSync('xdg-open', [path], { stdio: 'ignore' });
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
catch {
|
|
2312
|
+
// Opening is convenience-only; artifact doctor/render remains the source of truth.
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
// ============================================================================
|
|
2059
2316
|
// evolve command
|
|
2060
2317
|
// ============================================================================
|
|
2061
2318
|
const evolve = defineCommand({
|
|
@@ -2221,6 +2478,373 @@ const evidence = defineCommand({
|
|
|
2221
2478
|
subCommands: { list: evidenceList, show: evidenceShow },
|
|
2222
2479
|
});
|
|
2223
2480
|
// ============================================================================
|
|
2481
|
+
// runtime command - session ledger + completion evidence
|
|
2482
|
+
// ============================================================================
|
|
2483
|
+
function normalizeRuntimeEvidenceKind(value) {
|
|
2484
|
+
const normalized = String(value ?? 'command').trim();
|
|
2485
|
+
const allowed = ['command', 'gate', 'tool', 'skill', 'mcp', 'browser', 'desktop', 'manual', 'final-report'];
|
|
2486
|
+
if (allowed.includes(normalized))
|
|
2487
|
+
return normalized;
|
|
2488
|
+
throw new Error(`Invalid runtime evidence kind "${normalized}"; expected ${allowed.join(', ')}.`);
|
|
2489
|
+
}
|
|
2490
|
+
function normalizeRuntimeEvidenceStatus(value) {
|
|
2491
|
+
const normalized = String(value ?? '').trim();
|
|
2492
|
+
if (normalized === 'passed' || normalized === 'failed' || normalized === 'skipped')
|
|
2493
|
+
return normalized;
|
|
2494
|
+
throw new Error(`Invalid runtime evidence status "${normalized}"; expected passed, failed, or skipped.`);
|
|
2495
|
+
}
|
|
2496
|
+
function normalizeRuntimeSessionStatus(value) {
|
|
2497
|
+
const normalized = String(value ?? 'completed').trim();
|
|
2498
|
+
if (normalized === 'active' || normalized === 'completed' || normalized === 'failed' || normalized === 'abandoned')
|
|
2499
|
+
return normalized;
|
|
2500
|
+
throw new Error(`Invalid runtime session status "${normalized}"; expected active, completed, failed, or abandoned.`);
|
|
2501
|
+
}
|
|
2502
|
+
const runtimeStart = defineCommand({
|
|
2503
|
+
meta: { name: 'start', description: 'Start a runtime session ledger' },
|
|
2504
|
+
args: {
|
|
2505
|
+
'session-id': { type: 'string', description: 'Session id; generated when omitted' },
|
|
2506
|
+
'task-id': { type: 'string', description: 'Task id linked to this session' },
|
|
2507
|
+
agent: { type: 'string', description: 'Agent name' },
|
|
2508
|
+
level: { type: 'string', default: 'M', description: 'Task level: S, M, L, or CRITICAL' },
|
|
2509
|
+
summary: { type: 'string', description: 'Short session summary' },
|
|
2510
|
+
json: { type: 'boolean', default: false },
|
|
2511
|
+
},
|
|
2512
|
+
run({ args }) {
|
|
2513
|
+
const ledger = new SessionLedger({ projectDir: PROJECT_DIR, scaleDir: SCALE_DIR });
|
|
2514
|
+
const session = ledger.start({
|
|
2515
|
+
sessionId: args['session-id'],
|
|
2516
|
+
taskId: args['task-id'],
|
|
2517
|
+
agent: args.agent,
|
|
2518
|
+
level: normalizeTaskArtifactLevel(args.level),
|
|
2519
|
+
summary: args.summary,
|
|
2520
|
+
});
|
|
2521
|
+
if (args.json) {
|
|
2522
|
+
console.log(JSON.stringify(session, null, 2));
|
|
2523
|
+
return;
|
|
2524
|
+
}
|
|
2525
|
+
console.log(`Runtime session started: ${session.sessionId}`);
|
|
2526
|
+
if (session.taskId)
|
|
2527
|
+
console.log(` Task: ${session.taskId}`);
|
|
2528
|
+
if (session.level)
|
|
2529
|
+
console.log(` Level: ${session.level}`);
|
|
2530
|
+
console.log(` Events: ${ledger.sessionFile(session.sessionId)}`);
|
|
2531
|
+
},
|
|
2532
|
+
});
|
|
2533
|
+
const runtimeEnd = defineCommand({
|
|
2534
|
+
meta: { name: 'end', description: 'End the current or named runtime session' },
|
|
2535
|
+
args: {
|
|
2536
|
+
'session-id': { type: 'string', description: 'Session id; current session is used when omitted' },
|
|
2537
|
+
status: { type: 'string', default: 'completed', description: 'completed, failed, or abandoned' },
|
|
2538
|
+
summary: { type: 'string', description: 'Completion summary' },
|
|
2539
|
+
json: { type: 'boolean', default: false },
|
|
2540
|
+
},
|
|
2541
|
+
run({ args }) {
|
|
2542
|
+
const ledger = new SessionLedger({ projectDir: PROJECT_DIR, scaleDir: SCALE_DIR });
|
|
2543
|
+
const sessionId = args['session-id'] ?? ledger.current()?.sessionId;
|
|
2544
|
+
if (!sessionId) {
|
|
2545
|
+
console.error('No runtime session id provided and no current runtime session exists.');
|
|
2546
|
+
process.exit(1);
|
|
2547
|
+
}
|
|
2548
|
+
const session = ledger.end(sessionId, normalizeRuntimeSessionStatus(args.status), args.summary);
|
|
2549
|
+
if (args.json) {
|
|
2550
|
+
console.log(JSON.stringify(session, null, 2));
|
|
2551
|
+
return;
|
|
2552
|
+
}
|
|
2553
|
+
console.log(`Runtime session ended: ${session.sessionId}`);
|
|
2554
|
+
console.log(` Status: ${session.status}`);
|
|
2555
|
+
},
|
|
2556
|
+
});
|
|
2557
|
+
const runtimeRecord = defineCommand({
|
|
2558
|
+
meta: { name: 'record', description: 'Record command, gate, tool, browser, skill, or manual runtime evidence' },
|
|
2559
|
+
args: {
|
|
2560
|
+
'task-id': { type: 'string', description: 'Task id linked to this evidence' },
|
|
2561
|
+
'session-id': { type: 'string', description: 'Session id linked to this evidence' },
|
|
2562
|
+
kind: { type: 'string', default: 'command', description: 'command, gate, tool, skill, mcp, browser, desktop, manual, final-report' },
|
|
2563
|
+
title: { type: 'string', required: true, description: 'Evidence title' },
|
|
2564
|
+
status: { type: 'string', required: true, description: 'passed, failed, or skipped' },
|
|
2565
|
+
command: { type: 'string', description: 'Exact command or tool invocation, with secrets redacted by SCALE' },
|
|
2566
|
+
'exit-code': { type: 'string', description: 'Exit code when applicable' },
|
|
2567
|
+
summary: { type: 'string', required: true, description: 'Short output summary' },
|
|
2568
|
+
artifacts: { type: 'string', description: 'Comma-separated artifact paths' },
|
|
2569
|
+
'metadata-json': { type: 'string', default: '{}', description: 'Additional JSON metadata' },
|
|
2570
|
+
json: { type: 'boolean', default: false },
|
|
2571
|
+
},
|
|
2572
|
+
run({ args }) {
|
|
2573
|
+
const current = new SessionLedger({ projectDir: PROJECT_DIR, scaleDir: SCALE_DIR }).current();
|
|
2574
|
+
let metadata = {};
|
|
2575
|
+
try {
|
|
2576
|
+
metadata = JSON.parse(String(args['metadata-json'] ?? '{}'));
|
|
2577
|
+
}
|
|
2578
|
+
catch {
|
|
2579
|
+
console.error('--metadata-json must be valid JSON.');
|
|
2580
|
+
process.exit(1);
|
|
2581
|
+
}
|
|
2582
|
+
const exitCode = args['exit-code'] === undefined || args['exit-code'] === ''
|
|
2583
|
+
? undefined
|
|
2584
|
+
: Number.parseInt(String(args['exit-code']), 10);
|
|
2585
|
+
if (exitCode !== undefined && Number.isNaN(exitCode)) {
|
|
2586
|
+
console.error('--exit-code must be a number.');
|
|
2587
|
+
process.exit(1);
|
|
2588
|
+
}
|
|
2589
|
+
const ledger = new RuntimeEvidenceLedger({ projectDir: PROJECT_DIR, scaleDir: SCALE_DIR });
|
|
2590
|
+
const record = ledger.record({
|
|
2591
|
+
taskId: args['task-id'] ?? current?.taskId,
|
|
2592
|
+
sessionId: args['session-id'] ?? current?.sessionId,
|
|
2593
|
+
kind: normalizeRuntimeEvidenceKind(args.kind),
|
|
2594
|
+
title: args.title,
|
|
2595
|
+
status: normalizeRuntimeEvidenceStatus(args.status),
|
|
2596
|
+
command: args.command,
|
|
2597
|
+
exitCode,
|
|
2598
|
+
summary: args.summary,
|
|
2599
|
+
artifacts: parseCommaList(args.artifacts),
|
|
2600
|
+
metadata,
|
|
2601
|
+
});
|
|
2602
|
+
if (record.sessionId) {
|
|
2603
|
+
new SessionLedger({ projectDir: PROJECT_DIR, scaleDir: SCALE_DIR }).append(record.sessionId, {
|
|
2604
|
+
type: 'evidence.recorded',
|
|
2605
|
+
message: `${record.status}: ${record.title}`,
|
|
2606
|
+
data: {
|
|
2607
|
+
evidenceId: record.id,
|
|
2608
|
+
kind: record.kind,
|
|
2609
|
+
taskId: record.taskId,
|
|
2610
|
+
},
|
|
2611
|
+
});
|
|
2612
|
+
}
|
|
2613
|
+
if (args.json) {
|
|
2614
|
+
console.log(JSON.stringify(record, null, 2));
|
|
2615
|
+
return;
|
|
2616
|
+
}
|
|
2617
|
+
console.log(`Runtime evidence recorded: ${record.id}`);
|
|
2618
|
+
console.log(` Status: ${record.status}`);
|
|
2619
|
+
console.log(` Kind: ${record.kind}`);
|
|
2620
|
+
if (record.redactionApplied)
|
|
2621
|
+
console.log(' Redaction: applied');
|
|
2622
|
+
},
|
|
2623
|
+
});
|
|
2624
|
+
const runtimeDoctor = defineCommand({
|
|
2625
|
+
meta: { name: 'doctor', description: 'Check runtime session and completion evidence' },
|
|
2626
|
+
args: {
|
|
2627
|
+
'task-id': { type: 'string', description: 'Task id to inspect' },
|
|
2628
|
+
'session-id': { type: 'string', description: 'Session id to inspect' },
|
|
2629
|
+
level: { type: 'string', default: 'M', description: 'Task level: S, M, L, or CRITICAL' },
|
|
2630
|
+
json: { type: 'boolean', default: false },
|
|
2631
|
+
},
|
|
2632
|
+
run({ args }) {
|
|
2633
|
+
const report = doctorRuntimeEvidence({
|
|
2634
|
+
projectDir: PROJECT_DIR,
|
|
2635
|
+
scaleDir: SCALE_DIR,
|
|
2636
|
+
taskId: args['task-id'],
|
|
2637
|
+
sessionId: args['session-id'],
|
|
2638
|
+
level: normalizeTaskArtifactLevel(args.level),
|
|
2639
|
+
});
|
|
2640
|
+
if (args.json) {
|
|
2641
|
+
console.log(JSON.stringify(report, null, 2));
|
|
2642
|
+
if (report.blocked)
|
|
2643
|
+
process.exitCode = 1;
|
|
2644
|
+
return;
|
|
2645
|
+
}
|
|
2646
|
+
console.log('\nSCALE Runtime Doctor');
|
|
2647
|
+
console.log(` Evidence: ${report.evidence.total} total, ${report.evidence.passed} passed, ${report.evidence.failed} failed, ${report.evidence.skipped} skipped`);
|
|
2648
|
+
for (const check of report.checks) {
|
|
2649
|
+
console.log(` [${check.status.toUpperCase()}] ${check.name}: ${check.message}`);
|
|
2650
|
+
if (check.fix)
|
|
2651
|
+
console.log(` Fix: ${check.fix}`);
|
|
2652
|
+
}
|
|
2653
|
+
if (report.blocked)
|
|
2654
|
+
process.exitCode = 1;
|
|
2655
|
+
},
|
|
2656
|
+
});
|
|
2657
|
+
const runtimeFinalCheck = defineCommand({
|
|
2658
|
+
meta: { name: 'final-check', description: 'Block final delivery claims without passed runtime evidence' },
|
|
2659
|
+
args: {
|
|
2660
|
+
'task-id': { type: 'string', description: 'Task id to inspect' },
|
|
2661
|
+
'session-id': { type: 'string', description: 'Session id to inspect' },
|
|
2662
|
+
level: { type: 'string', default: 'M', description: 'Task level: S, M, L, or CRITICAL' },
|
|
2663
|
+
json: { type: 'boolean', default: false },
|
|
2664
|
+
},
|
|
2665
|
+
run({ args }) {
|
|
2666
|
+
const readiness = evaluateFinalReportReadiness({
|
|
2667
|
+
projectDir: PROJECT_DIR,
|
|
2668
|
+
scaleDir: SCALE_DIR,
|
|
2669
|
+
taskId: args['task-id'],
|
|
2670
|
+
sessionId: args['session-id'],
|
|
2671
|
+
level: normalizeTaskArtifactLevel(args.level),
|
|
2672
|
+
});
|
|
2673
|
+
if (args.json) {
|
|
2674
|
+
console.log(JSON.stringify(readiness, null, 2));
|
|
2675
|
+
if (readiness.blocked)
|
|
2676
|
+
process.exitCode = 1;
|
|
2677
|
+
return;
|
|
2678
|
+
}
|
|
2679
|
+
console.log('\nSCALE Runtime Final Check');
|
|
2680
|
+
console.log(` Ready: ${readiness.ready}`);
|
|
2681
|
+
for (const reason of readiness.reasons)
|
|
2682
|
+
console.log(` [BLOCKER] ${reason}`);
|
|
2683
|
+
if (readiness.blocked)
|
|
2684
|
+
process.exitCode = 1;
|
|
2685
|
+
},
|
|
2686
|
+
});
|
|
2687
|
+
const runtime = defineCommand({
|
|
2688
|
+
meta: { name: 'runtime', description: 'Runtime session ledger and completion evidence governance' },
|
|
2689
|
+
subCommands: {
|
|
2690
|
+
start: runtimeStart,
|
|
2691
|
+
end: runtimeEnd,
|
|
2692
|
+
record: runtimeRecord,
|
|
2693
|
+
doctor: runtimeDoctor,
|
|
2694
|
+
'final-check': runtimeFinalCheck,
|
|
2695
|
+
},
|
|
2696
|
+
});
|
|
2697
|
+
// ============================================================================
|
|
2698
|
+
// memory command - runtime evidence + knowledge + graph context packs
|
|
2699
|
+
// ============================================================================
|
|
2700
|
+
function parseMemoryBudget(value) {
|
|
2701
|
+
if (value === undefined || value === null || value === '')
|
|
2702
|
+
return undefined;
|
|
2703
|
+
const parsed = Number.parseInt(String(value), 10);
|
|
2704
|
+
if (Number.isNaN(parsed) || parsed <= 0) {
|
|
2705
|
+
throw new Error('--budget must be a positive integer.');
|
|
2706
|
+
}
|
|
2707
|
+
return parsed;
|
|
2708
|
+
}
|
|
2709
|
+
const memoryPack = defineCommand({
|
|
2710
|
+
meta: { name: 'pack', description: 'Build a compact context pack from runtime evidence, session events, knowledge, and graph status' },
|
|
2711
|
+
args: {
|
|
2712
|
+
task: { type: 'string', required: true, description: 'Current task or question' },
|
|
2713
|
+
'task-id': { type: 'string', description: 'Task id to scope evidence and session data' },
|
|
2714
|
+
'session-id': { type: 'string', description: 'Session id to scope session events' },
|
|
2715
|
+
level: { type: 'string', default: 'M', description: 'Task level: S, M, L, or CRITICAL' },
|
|
2716
|
+
files: { type: 'string', description: 'Comma-separated files or modules in scope' },
|
|
2717
|
+
budget: { type: 'string', description: 'Maximum estimated tokens for the context pack' },
|
|
2718
|
+
json: { type: 'boolean', default: false },
|
|
2719
|
+
},
|
|
2720
|
+
async run({ args }) {
|
|
2721
|
+
let budgetTokens;
|
|
2722
|
+
try {
|
|
2723
|
+
budgetTokens = parseMemoryBudget(args.budget);
|
|
2724
|
+
}
|
|
2725
|
+
catch (e) {
|
|
2726
|
+
console.error(e.message);
|
|
2727
|
+
process.exit(1);
|
|
2728
|
+
}
|
|
2729
|
+
const { kb } = getEngine();
|
|
2730
|
+
const pack = await new MemoryFabric({
|
|
2731
|
+
projectDir: PROJECT_DIR,
|
|
2732
|
+
scaleDir: SCALE_DIR,
|
|
2733
|
+
knowledgeBase: kb,
|
|
2734
|
+
}).createContextPack({
|
|
2735
|
+
task: args.task,
|
|
2736
|
+
taskId: args['task-id'],
|
|
2737
|
+
sessionId: args['session-id'],
|
|
2738
|
+
level: normalizeTaskArtifactLevel(args.level),
|
|
2739
|
+
files: parseCommaList(args.files),
|
|
2740
|
+
budgetTokens,
|
|
2741
|
+
});
|
|
2742
|
+
if (args.json) {
|
|
2743
|
+
console.log(JSON.stringify(pack, null, 2));
|
|
2744
|
+
return;
|
|
2745
|
+
}
|
|
2746
|
+
console.log(renderContextPackMarkdown(pack));
|
|
2747
|
+
},
|
|
2748
|
+
});
|
|
2749
|
+
const memoryDoctor = defineCommand({
|
|
2750
|
+
meta: { name: 'doctor', description: 'Check whether a task context pack is available and within token budget' },
|
|
2751
|
+
args: {
|
|
2752
|
+
task: { type: 'string', required: true, description: 'Current task or question' },
|
|
2753
|
+
'task-id': { type: 'string', description: 'Task id to scope evidence and session data' },
|
|
2754
|
+
'session-id': { type: 'string', description: 'Session id to scope session events' },
|
|
2755
|
+
level: { type: 'string', default: 'M', description: 'Task level: S, M, L, or CRITICAL' },
|
|
2756
|
+
files: { type: 'string', description: 'Comma-separated files or modules in scope' },
|
|
2757
|
+
budget: { type: 'string', description: 'Maximum estimated tokens for the context pack' },
|
|
2758
|
+
json: { type: 'boolean', default: false },
|
|
2759
|
+
},
|
|
2760
|
+
async run({ args }) {
|
|
2761
|
+
let budgetTokens;
|
|
2762
|
+
try {
|
|
2763
|
+
budgetTokens = parseMemoryBudget(args.budget);
|
|
2764
|
+
}
|
|
2765
|
+
catch (e) {
|
|
2766
|
+
console.error(e.message);
|
|
2767
|
+
process.exit(1);
|
|
2768
|
+
}
|
|
2769
|
+
const { kb } = getEngine();
|
|
2770
|
+
const report = await doctorMemoryFabric({
|
|
2771
|
+
projectDir: PROJECT_DIR,
|
|
2772
|
+
scaleDir: SCALE_DIR,
|
|
2773
|
+
knowledgeBase: kb,
|
|
2774
|
+
}, {
|
|
2775
|
+
task: args.task,
|
|
2776
|
+
taskId: args['task-id'],
|
|
2777
|
+
sessionId: args['session-id'],
|
|
2778
|
+
level: normalizeTaskArtifactLevel(args.level),
|
|
2779
|
+
files: parseCommaList(args.files),
|
|
2780
|
+
budgetTokens,
|
|
2781
|
+
});
|
|
2782
|
+
if (args.json) {
|
|
2783
|
+
console.log(JSON.stringify(report, null, 2));
|
|
2784
|
+
if (!report.ok)
|
|
2785
|
+
process.exitCode = 1;
|
|
2786
|
+
return;
|
|
2787
|
+
}
|
|
2788
|
+
console.log('\nSCALE Memory Doctor');
|
|
2789
|
+
console.log(` Budget: ${report.pack.budget.used}/${report.pack.budget.limit} estimated tokens`);
|
|
2790
|
+
for (const check of report.checks) {
|
|
2791
|
+
console.log(` [${check.status.toUpperCase()}] ${check.name}: ${check.message}`);
|
|
2792
|
+
}
|
|
2793
|
+
if (!report.ok)
|
|
2794
|
+
process.exitCode = 1;
|
|
2795
|
+
},
|
|
2796
|
+
});
|
|
2797
|
+
const memorySettle = defineCommand({
|
|
2798
|
+
meta: { name: 'settle', description: 'Settle runtime evidence into a reviewable memory learning candidate' },
|
|
2799
|
+
args: {
|
|
2800
|
+
task: { type: 'string', required: true, description: 'Current task or question' },
|
|
2801
|
+
'task-id': { type: 'string', description: 'Task id to scope evidence and session data' },
|
|
2802
|
+
'session-id': { type: 'string', description: 'Session id to scope session events' },
|
|
2803
|
+
level: { type: 'string', default: 'M', description: 'Task level: S, M, L, or CRITICAL' },
|
|
2804
|
+
files: { type: 'string', description: 'Comma-separated files or modules in scope' },
|
|
2805
|
+
budget: { type: 'string', description: 'Maximum estimated tokens for the context pack' },
|
|
2806
|
+
json: { type: 'boolean', default: false },
|
|
2807
|
+
},
|
|
2808
|
+
async run({ args }) {
|
|
2809
|
+
let budgetTokens;
|
|
2810
|
+
try {
|
|
2811
|
+
budgetTokens = parseMemoryBudget(args.budget);
|
|
2812
|
+
}
|
|
2813
|
+
catch (e) {
|
|
2814
|
+
console.error(e.message);
|
|
2815
|
+
process.exit(1);
|
|
2816
|
+
}
|
|
2817
|
+
const { kb } = getEngine();
|
|
2818
|
+
const pack = await new MemoryFabric({
|
|
2819
|
+
projectDir: PROJECT_DIR,
|
|
2820
|
+
scaleDir: SCALE_DIR,
|
|
2821
|
+
knowledgeBase: kb,
|
|
2822
|
+
}).createContextPack({
|
|
2823
|
+
task: args.task,
|
|
2824
|
+
taskId: args['task-id'],
|
|
2825
|
+
sessionId: args['session-id'],
|
|
2826
|
+
level: normalizeTaskArtifactLevel(args.level),
|
|
2827
|
+
files: parseCommaList(args.files),
|
|
2828
|
+
budgetTokens,
|
|
2829
|
+
});
|
|
2830
|
+
const settlement = settleMemoryLearning({
|
|
2831
|
+
projectDir: PROJECT_DIR,
|
|
2832
|
+
scaleDir: SCALE_DIR,
|
|
2833
|
+
pack,
|
|
2834
|
+
});
|
|
2835
|
+
if (args.json) {
|
|
2836
|
+
console.log(JSON.stringify(settlement, null, 2));
|
|
2837
|
+
return;
|
|
2838
|
+
}
|
|
2839
|
+
console.log(renderMemoryLearningCandidateMarkdown(settlement.candidate));
|
|
2840
|
+
console.log(`\nWrote: ${settlement.files.markdown}`);
|
|
2841
|
+
},
|
|
2842
|
+
});
|
|
2843
|
+
const memory = defineCommand({
|
|
2844
|
+
meta: { name: 'memory', description: 'Memory Fabric context packs and budget diagnostics' },
|
|
2845
|
+
subCommands: { pack: memoryPack, doctor: memoryDoctor, settle: memorySettle },
|
|
2846
|
+
});
|
|
2847
|
+
// ============================================================================
|
|
2224
2848
|
// out-of-scope command — 借鉴 mattpocock/skills 的 .out-of-scope/ 设计
|
|
2225
2849
|
// ============================================================================
|
|
2226
2850
|
const outOfScopeAdd = defineCommand({
|
|
@@ -2453,6 +3077,7 @@ const skillCheckCommand = defineCommand({
|
|
|
2453
3077
|
artifactsDir: args.dir ?? state?.artifactsDir,
|
|
2454
3078
|
level,
|
|
2455
3079
|
requiredArtifacts: state?.requiredSkillArtifacts,
|
|
3080
|
+
requiredSkills: state?.requiredSkills,
|
|
2456
3081
|
mode: state?.skillRoutingMode ?? policy.policy.mode,
|
|
2457
3082
|
enforceLevels: policy.policy.enforceLevels,
|
|
2458
3083
|
});
|
|
@@ -3005,6 +3630,7 @@ const main = defineCommand({
|
|
|
3005
3630
|
stats,
|
|
3006
3631
|
preflight,
|
|
3007
3632
|
governance,
|
|
3633
|
+
artifact,
|
|
3008
3634
|
assets,
|
|
3009
3635
|
standards,
|
|
3010
3636
|
metrics,
|
|
@@ -3013,6 +3639,8 @@ const main = defineCommand({
|
|
|
3013
3639
|
status,
|
|
3014
3640
|
workflow,
|
|
3015
3641
|
evidence,
|
|
3642
|
+
runtime,
|
|
3643
|
+
memory,
|
|
3016
3644
|
diagnose,
|
|
3017
3645
|
tdd,
|
|
3018
3646
|
tool,
|