@hongmaple0820/scale-engine 0.15.1 → 0.17.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/dist/agents/LeadershipPresets.d.ts +16 -0
- package/dist/agents/LeadershipPresets.js +152 -0
- package/dist/agents/LeadershipPresets.js.map +1 -0
- package/dist/api/cli.js +774 -8
- package/dist/api/cli.js.map +1 -1
- package/dist/artifact/types.d.ts +4 -0
- package/dist/artifact/types.js.map +1 -1
- package/dist/cli/phaseCommands.d.ts +14 -0
- package/dist/cli/phaseCommands.js +187 -6
- package/dist/cli/phaseCommands.js.map +1 -1
- package/dist/cli/vibeCommands.d.ts +20 -0
- package/dist/cli/vibeCommands.js +150 -173
- package/dist/cli/vibeCommands.js.map +1 -1
- package/dist/index.d.ts +9 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -1
- package/dist/prompts/VibeTemplateGallery.d.ts +25 -0
- package/dist/prompts/VibeTemplateGallery.js +295 -0
- package/dist/prompts/VibeTemplateGallery.js.map +1 -0
- package/dist/skills/SkillRepository.d.ts +63 -0
- package/dist/skills/SkillRepository.js +365 -0
- package/dist/skills/SkillRepository.js.map +1 -0
- package/dist/tools/ToolCapabilityRegistry.d.ts +46 -0
- package/dist/tools/ToolCapabilityRegistry.js +223 -0
- package/dist/tools/ToolCapabilityRegistry.js.map +1 -0
- package/dist/tools/ToolEvidenceGate.d.ts +39 -0
- package/dist/tools/ToolEvidenceGate.js +117 -0
- package/dist/tools/ToolEvidenceGate.js.map +1 -0
- package/dist/tools/ToolEvidenceStore.d.ts +58 -0
- package/dist/tools/ToolEvidenceStore.js +129 -0
- package/dist/tools/ToolEvidenceStore.js.map +1 -0
- package/dist/tools/ToolOrchestrator.d.ts +67 -0
- package/dist/tools/ToolOrchestrator.js +246 -0
- package/dist/tools/ToolOrchestrator.js.map +1 -0
- package/dist/tools/ToolPolicy.d.ts +33 -0
- package/dist/tools/ToolPolicy.js +157 -0
- package/dist/tools/ToolPolicy.js.map +1 -0
- package/dist/tools/index.d.ts +5 -0
- package/dist/tools/index.js +6 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/workflow/ContextGovernance.d.ts +51 -0
- package/dist/workflow/ContextGovernance.js +233 -0
- package/dist/workflow/ContextGovernance.js.map +1 -0
- package/dist/workflow/DiagnosticLoop.d.ts +40 -0
- package/dist/workflow/DiagnosticLoop.js +105 -0
- package/dist/workflow/DiagnosticLoop.js.map +1 -0
- package/dist/workflow/EngineeringStandards.d.ts +69 -0
- package/dist/workflow/EngineeringStandards.js +348 -6
- package/dist/workflow/EngineeringStandards.js.map +1 -1
- package/dist/workflow/GovernanceTemplatePacks.js +11 -9
- package/dist/workflow/GovernanceTemplatePacks.js.map +1 -1
- package/dist/workflow/GovernanceTemplates.js +15 -4
- package/dist/workflow/GovernanceTemplates.js.map +1 -1
- package/dist/workflow/TaskArtifactScaffolder.d.ts +22 -0
- package/dist/workflow/TaskArtifactScaffolder.js +55 -0
- package/dist/workflow/TaskArtifactScaffolder.js.map +1 -1
- package/dist/workflow/TddLoop.d.ts +47 -0
- package/dist/workflow/TddLoop.js +76 -0
- package/dist/workflow/TddLoop.js.map +1 -0
- package/dist/workflow/WorkflowGuidance.d.ts +26 -0
- package/dist/workflow/WorkflowGuidance.js +173 -0
- package/dist/workflow/WorkflowGuidance.js.map +1 -0
- package/dist/workflow/WorkflowOpenTasks.d.ts +16 -0
- package/dist/workflow/WorkflowOpenTasks.js +37 -0
- package/dist/workflow/WorkflowOpenTasks.js.map +1 -0
- package/dist/workflow/index.d.ts +5 -0
- package/dist/workflow/index.js +5 -0
- package/dist/workflow/index.js.map +1 -1
- package/package.json +2 -2
package/dist/api/cli.js
CHANGED
|
@@ -23,6 +23,8 @@ import { Doctor } from './doctor.js';
|
|
|
23
23
|
import { quickStart, detectPlatform } from './quickstart.js';
|
|
24
24
|
import { SkillDiscovery } from '../skills/SkillDiscovery.js';
|
|
25
25
|
import { inspectRequiredWorkflowSkills, inspectWorkflowSkills } from '../skills/SkillDoctor.js';
|
|
26
|
+
import { evaluateSkillInstallSafety, listSkillRepositoryEntries, recommendSkillWorkflow, renderSkillRepositoryMarkdown, } from '../skills/SkillRepository.js';
|
|
27
|
+
import { listLeadershipPresets, renderLeadershipPresetsMarkdown } from '../agents/LeadershipPresets.js';
|
|
26
28
|
import { listWorkflowPresets, getPresetsByScenario } from '../workflows/presets.js';
|
|
27
29
|
import { EvidenceStore } from '../workflow/EvidenceStore.js';
|
|
28
30
|
import { OutOfScopeStore } from '../workflow/OutOfScopeStore.js';
|
|
@@ -31,15 +33,25 @@ import { WorkflowEngine } from '../workflow/WorkflowEngine.js';
|
|
|
31
33
|
import { resolveVerificationTargets } from '../workflow/VerificationProfile.js';
|
|
32
34
|
import { writeGovernanceTemplates } from '../workflow/GovernanceTemplates.js';
|
|
33
35
|
import { computeGovernanceDrift } from '../workflow/GovernanceLock.js';
|
|
34
|
-
import { doctorEngineeringStandards, scanEngineeringStandards, settleEngineeringStandards, } from '../workflow/EngineeringStandards.js';
|
|
36
|
+
import { baselineEngineeringStandards, doctorEngineeringStandards, scanEngineeringStandards, settleEngineeringStandards, } from '../workflow/EngineeringStandards.js';
|
|
35
37
|
import { doctorResourceAssets, scanResourceAssets, settleResourceAssets } from '../workflow/ResourceGovernance.js';
|
|
38
|
+
import { analyzeContextGovernance, renderContextGrillPrompt, writeContextGovernanceTemplates, } from '../workflow/ContextGovernance.js';
|
|
39
|
+
import { createDiagnosticLoop, renderDiagnosticLoopMarkdown, validateDiagnosticLoop, } from '../workflow/DiagnosticLoop.js';
|
|
40
|
+
import { createTddSlice, evaluateTddSlice, renderTddSliceMarkdown, } from '../workflow/TddLoop.js';
|
|
41
|
+
import { nextWorkflowOpenTask, removeWorkflowOpenTask, toolEvidenceRunCompletesOpenTask } from '../workflow/WorkflowOpenTasks.js';
|
|
36
42
|
import { TaskMetricsStore } from '../workflow/TaskMetricsStore.js';
|
|
37
|
-
import { checkTaskArtifactCompleteness } from '../workflow/TaskArtifactScaffolder.js';
|
|
43
|
+
import { appendContextGrillArtifact, appendDiagnosticLoopArtifact, appendTddSliceArtifact, checkTaskArtifactCompleteness, } from '../workflow/TaskArtifactScaffolder.js';
|
|
38
44
|
import { WorkflowArtifactWriter } from '../workflow/WorkflowArtifactWriter.js';
|
|
45
|
+
import { inspectToolCapabilities } from '../tools/ToolCapabilityRegistry.js';
|
|
46
|
+
import { evaluateToolEvidenceGate } from '../tools/ToolEvidenceGate.js';
|
|
47
|
+
import { ToolEvidenceStore } from '../tools/ToolEvidenceStore.js';
|
|
48
|
+
import { ToolOrchestrator } from '../tools/ToolOrchestrator.js';
|
|
49
|
+
import { loadToolPolicy, toolPolicyTemplate } from '../tools/ToolPolicy.js';
|
|
39
50
|
import { cleanupWorkspaceLifecycle, inspectWorkspaceLifecycle, } from '../workflow/WorkspaceLifecycle.js';
|
|
40
51
|
import { resolveWorkspaceTopology, workspaceTopologyPath, workspaceTopologyTemplate, } from '../workflow/WorkspaceTopology.js';
|
|
41
52
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
42
53
|
import { join, resolve } from 'node:path';
|
|
54
|
+
import { execFileSync } from 'node:child_process';
|
|
43
55
|
import { SCALE_ENGINE_VERSION } from '../version.js';
|
|
44
56
|
// ============================================================================
|
|
45
57
|
// Engine bootstrap (单例 + lazy init)
|
|
@@ -61,6 +73,18 @@ function ensureDir(dir) {
|
|
|
61
73
|
function isTruthyFlag(value) {
|
|
62
74
|
return value === true || value === '' || value === 'true' || value === '1';
|
|
63
75
|
}
|
|
76
|
+
function commandEvidence(command, exitCode, summary) {
|
|
77
|
+
if (exitCode === undefined || exitCode === null || exitCode === '')
|
|
78
|
+
return undefined;
|
|
79
|
+
const parsed = Number.parseInt(String(exitCode), 10);
|
|
80
|
+
if (Number.isNaN(parsed))
|
|
81
|
+
return undefined;
|
|
82
|
+
return {
|
|
83
|
+
command,
|
|
84
|
+
exitCode: parsed,
|
|
85
|
+
outputSummary: summary ? String(summary) : `Command exited ${parsed}`,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
64
88
|
function normalizePreflightProfile(value) {
|
|
65
89
|
const normalized = String(value ?? 'quick').trim().toLowerCase();
|
|
66
90
|
if (normalized === 'full' || normalized === 'ci')
|
|
@@ -768,9 +792,239 @@ const contextGlossary = defineCommand({
|
|
|
768
792
|
}
|
|
769
793
|
},
|
|
770
794
|
});
|
|
795
|
+
const contextInit = defineCommand({
|
|
796
|
+
meta: { name: 'init', description: 'Create CONTEXT.md and CONTEXT-MAP.md starter templates' },
|
|
797
|
+
args: {
|
|
798
|
+
dir: { type: 'string', default: PROJECT_DIR, description: 'Project directory' },
|
|
799
|
+
name: { type: 'string', description: 'Project display name' },
|
|
800
|
+
force: { type: 'boolean', default: false, description: 'Overwrite existing templates' },
|
|
801
|
+
json: { type: 'boolean', default: false },
|
|
802
|
+
},
|
|
803
|
+
run({ args }) {
|
|
804
|
+
const result = writeContextGovernanceTemplates({
|
|
805
|
+
projectDir: resolve(String(args.dir ?? PROJECT_DIR)),
|
|
806
|
+
projectName: args.name ? String(args.name) : undefined,
|
|
807
|
+
force: isTruthyFlag(args.force),
|
|
808
|
+
});
|
|
809
|
+
if (args.json) {
|
|
810
|
+
console.log(JSON.stringify(result, null, 2));
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
console.log('\nSCALE Context Templates');
|
|
814
|
+
for (const file of result.created)
|
|
815
|
+
console.log(` [CREATED] ${file}`);
|
|
816
|
+
for (const file of result.skipped)
|
|
817
|
+
console.log(` [SKIPPED] ${file}`);
|
|
818
|
+
},
|
|
819
|
+
});
|
|
820
|
+
const contextGrill = defineCommand({
|
|
821
|
+
meta: { name: 'grill', description: 'Check project context docs and generate request-specific grill questions' },
|
|
822
|
+
args: {
|
|
823
|
+
dir: { type: 'string', default: PROJECT_DIR, description: 'Project directory' },
|
|
824
|
+
'task-id': { type: 'string', description: 'Task id for workflow state and artifact linkage' },
|
|
825
|
+
task: { type: 'string', required: true, description: 'Task or requirement description' },
|
|
826
|
+
files: { type: 'string', description: 'Comma-separated changed or target files' },
|
|
827
|
+
'artifact-dir': { type: 'string', description: 'Task artifact directory where explore.md should be updated' },
|
|
828
|
+
write: { type: 'boolean', default: false, description: 'Append context grill output to the task explore artifact' },
|
|
829
|
+
json: { type: 'boolean', default: false },
|
|
830
|
+
},
|
|
831
|
+
run({ args }) {
|
|
832
|
+
const projectDir = resolve(String(args.dir ?? PROJECT_DIR));
|
|
833
|
+
const taskId = String(args['task-id'] ?? `context-${Date.now()}`);
|
|
834
|
+
const changedFiles = parseCommaList(args.files);
|
|
835
|
+
const report = analyzeContextGovernance({
|
|
836
|
+
projectDir,
|
|
837
|
+
request: String(args.task ?? ''),
|
|
838
|
+
changedFiles,
|
|
839
|
+
});
|
|
840
|
+
const artifactPath = isTruthyFlag(args.write)
|
|
841
|
+
? appendContextGrillArtifact({
|
|
842
|
+
projectDir,
|
|
843
|
+
artifactsDir: args['artifact-dir'] ? String(args['artifact-dir']) : undefined,
|
|
844
|
+
report,
|
|
845
|
+
}) ?? undefined
|
|
846
|
+
: undefined;
|
|
847
|
+
if (args['task-id'] || artifactPath) {
|
|
848
|
+
const writer = new WorkflowArtifactWriter(SCALE_DIR);
|
|
849
|
+
const current = writer.readCurrentState();
|
|
850
|
+
const currentOpenTasks = current?.taskId === taskId ? current.openTasks : [];
|
|
851
|
+
writer.updateCurrentState({
|
|
852
|
+
taskId,
|
|
853
|
+
phase: 'explore',
|
|
854
|
+
artifactsDir: args['artifact-dir'] ? String(args['artifact-dir']).replace(/\\/g, '/') : undefined,
|
|
855
|
+
exploredFiles: changedFiles,
|
|
856
|
+
fileCount: changedFiles.length,
|
|
857
|
+
mainContradiction: report.findings[0]?.message ?? 'context governance ready',
|
|
858
|
+
openTasks: removeWorkflowOpenTask(currentOpenTasks, 'context-grill'),
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
if (args.json) {
|
|
862
|
+
console.log(JSON.stringify({ ...report, artifactPath }, null, 2));
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
console.log(renderContextGrillPrompt(report));
|
|
866
|
+
if (artifactPath)
|
|
867
|
+
console.log(`\nArtifact: ${artifactPath}`);
|
|
868
|
+
},
|
|
869
|
+
});
|
|
771
870
|
const context = defineCommand({
|
|
772
871
|
meta: { name: 'context', description: 'Context assembly' },
|
|
773
|
-
subCommands: { build: contextBuild, status: contextStatus, inject: contextInject, glossary: contextGlossary },
|
|
872
|
+
subCommands: { build: contextBuild, status: contextStatus, inject: contextInject, glossary: contextGlossary, init: contextInit, grill: contextGrill },
|
|
873
|
+
});
|
|
874
|
+
// ============================================================================
|
|
875
|
+
// diagnose command - evidence-first debugging loop
|
|
876
|
+
// ============================================================================
|
|
877
|
+
const diagnosePlanCommand = defineCommand({
|
|
878
|
+
meta: { name: 'plan', description: 'Create a reproducible diagnostic loop before fixing a bug' },
|
|
879
|
+
args: {
|
|
880
|
+
'task-id': { type: 'string', required: true },
|
|
881
|
+
symptom: { type: 'string', required: true },
|
|
882
|
+
repro: { type: 'string', description: 'Command that reproduces the current failure' },
|
|
883
|
+
'expected-failure': { type: 'string', description: 'Expected failing behavior or assertion' },
|
|
884
|
+
files: { type: 'string', description: 'Comma-separated changed or suspicious files' },
|
|
885
|
+
verify: { type: 'string', description: 'Comma-separated verification commands after the fix' },
|
|
886
|
+
'artifact-dir': { type: 'string', description: 'Task artifact directory where plan.md should be updated' },
|
|
887
|
+
write: { type: 'boolean', default: false, description: 'Append diagnostic loop output to the task plan artifact' },
|
|
888
|
+
json: { type: 'boolean', default: false },
|
|
889
|
+
},
|
|
890
|
+
run({ args }) {
|
|
891
|
+
const changedFiles = parseCommaList(args.files);
|
|
892
|
+
const loop = createDiagnosticLoop({
|
|
893
|
+
taskId: String(args['task-id']),
|
|
894
|
+
symptom: String(args.symptom),
|
|
895
|
+
reproductionCommand: args.repro ? String(args.repro) : undefined,
|
|
896
|
+
expectedFailure: args['expected-failure'] ? String(args['expected-failure']) : undefined,
|
|
897
|
+
changedFiles,
|
|
898
|
+
verificationCommands: parseCommaList(args.verify),
|
|
899
|
+
});
|
|
900
|
+
const validation = validateDiagnosticLoop(loop);
|
|
901
|
+
const artifactPath = isTruthyFlag(args.write)
|
|
902
|
+
? appendDiagnosticLoopArtifact({
|
|
903
|
+
projectDir: PROJECT_DIR,
|
|
904
|
+
artifactsDir: args['artifact-dir'] ? String(args['artifact-dir']) : undefined,
|
|
905
|
+
loop,
|
|
906
|
+
validation,
|
|
907
|
+
}) ?? undefined
|
|
908
|
+
: undefined;
|
|
909
|
+
if (artifactPath || args['artifact-dir']) {
|
|
910
|
+
const writer = new WorkflowArtifactWriter(SCALE_DIR);
|
|
911
|
+
const current = writer.readCurrentState();
|
|
912
|
+
const currentOpenTasks = current?.taskId === loop.taskId ? current.openTasks : [];
|
|
913
|
+
writer.updateCurrentState({
|
|
914
|
+
taskId: loop.taskId,
|
|
915
|
+
phase: 'plan',
|
|
916
|
+
artifactsDir: args['artifact-dir'] ? String(args['artifact-dir']).replace(/\\/g, '/') : undefined,
|
|
917
|
+
filesModified: changedFiles,
|
|
918
|
+
openTasks: validation.ready
|
|
919
|
+
? removeWorkflowOpenTask(currentOpenTasks.filter(task => task.trim().startsWith('scale ')), 'diagnostic-loop')
|
|
920
|
+
: uniqueStrings([
|
|
921
|
+
...currentOpenTasks,
|
|
922
|
+
...validation.blockers,
|
|
923
|
+
]),
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
if (args.json) {
|
|
927
|
+
console.log(JSON.stringify({ loop, validation, artifactPath }, null, 2));
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
console.log(renderDiagnosticLoopMarkdown(loop));
|
|
931
|
+
if (!validation.ready) {
|
|
932
|
+
console.log('\nBlockers:');
|
|
933
|
+
for (const blocker of validation.blockers)
|
|
934
|
+
console.log(` - ${blocker}`);
|
|
935
|
+
}
|
|
936
|
+
if (artifactPath)
|
|
937
|
+
console.log(`\nArtifact: ${artifactPath}`);
|
|
938
|
+
},
|
|
939
|
+
});
|
|
940
|
+
const diagnose = defineCommand({
|
|
941
|
+
meta: { name: 'diagnose', description: 'Evidence-first debugging workflows' },
|
|
942
|
+
subCommands: { plan: diagnosePlanCommand },
|
|
943
|
+
});
|
|
944
|
+
// ============================================================================
|
|
945
|
+
// tdd command - vertical slice RED/GREEN/REFACTOR loop
|
|
946
|
+
// ============================================================================
|
|
947
|
+
const tddSliceCommand = defineCommand({
|
|
948
|
+
meta: { name: 'slice', description: 'Create and evaluate a TDD vertical slice' },
|
|
949
|
+
args: {
|
|
950
|
+
'task-id': { type: 'string', required: true },
|
|
951
|
+
behavior: { type: 'string', required: true },
|
|
952
|
+
'public-interface': { type: 'string', required: true },
|
|
953
|
+
'failing-test': { type: 'string', required: true },
|
|
954
|
+
'test-file': { type: 'string', required: true },
|
|
955
|
+
'impl-files': { type: 'string', required: true },
|
|
956
|
+
'red-exit-code': { type: 'string', description: 'Exit code from the RED command' },
|
|
957
|
+
'red-summary': { type: 'string', description: 'Short RED output summary' },
|
|
958
|
+
'green-exit-code': { type: 'string', description: 'Exit code from the GREEN command' },
|
|
959
|
+
'green-summary': { type: 'string', description: 'Short GREEN output summary' },
|
|
960
|
+
'refactor-exit-code': { type: 'string', description: 'Exit code from the REFACTOR command' },
|
|
961
|
+
'refactor-summary': { type: 'string', description: 'Short REFACTOR output summary' },
|
|
962
|
+
'artifact-dir': { type: 'string', description: 'Task artifact directory where verification.md should be updated' },
|
|
963
|
+
write: { type: 'boolean', default: false, description: 'Append TDD slice output to the task verification artifact' },
|
|
964
|
+
json: { type: 'boolean', default: false },
|
|
965
|
+
},
|
|
966
|
+
run({ args }) {
|
|
967
|
+
const failingTest = String(args['failing-test']);
|
|
968
|
+
const slice = createTddSlice({
|
|
969
|
+
taskId: String(args['task-id']),
|
|
970
|
+
behavior: String(args.behavior),
|
|
971
|
+
publicInterface: String(args['public-interface']),
|
|
972
|
+
failingTestCommand: failingTest,
|
|
973
|
+
testFile: String(args['test-file']),
|
|
974
|
+
implementationFiles: parseCommaList(args['impl-files']),
|
|
975
|
+
redEvidence: commandEvidence(failingTest, args['red-exit-code'], args['red-summary']),
|
|
976
|
+
greenEvidence: commandEvidence(failingTest, args['green-exit-code'], args['green-summary']),
|
|
977
|
+
refactorEvidence: commandEvidence(failingTest, args['refactor-exit-code'], args['refactor-summary']),
|
|
978
|
+
});
|
|
979
|
+
const evaluation = evaluateTddSlice(slice);
|
|
980
|
+
const artifactPath = isTruthyFlag(args.write)
|
|
981
|
+
? appendTddSliceArtifact({
|
|
982
|
+
projectDir: PROJECT_DIR,
|
|
983
|
+
artifactsDir: args['artifact-dir'] ? String(args['artifact-dir']) : undefined,
|
|
984
|
+
slice,
|
|
985
|
+
}) ?? undefined
|
|
986
|
+
: undefined;
|
|
987
|
+
let tddStatePath;
|
|
988
|
+
if (slice.redEvidence && slice.greenEvidence && slice.refactorEvidence) {
|
|
989
|
+
const writer = new WorkflowArtifactWriter(SCALE_DIR);
|
|
990
|
+
writer.writeTDDEvidence({
|
|
991
|
+
timestamp: new Date().toISOString(),
|
|
992
|
+
taskId: slice.taskId,
|
|
993
|
+
red: slice.redEvidence.exitCode !== 0,
|
|
994
|
+
green: slice.greenEvidence.exitCode === 0,
|
|
995
|
+
refactor: slice.refactorEvidence.exitCode === 0,
|
|
996
|
+
testFirst: slice.redEvidence.exitCode !== 0,
|
|
997
|
+
testFile: slice.testFile,
|
|
998
|
+
implFile: slice.implementationFiles[0] ?? '',
|
|
999
|
+
});
|
|
1000
|
+
writer.updateCurrentState({
|
|
1001
|
+
taskId: slice.taskId,
|
|
1002
|
+
phase: 'verify',
|
|
1003
|
+
artifactsDir: args['artifact-dir'] ? String(args['artifact-dir']).replace(/\\/g, '/') : undefined,
|
|
1004
|
+
filesModified: slice.implementationFiles,
|
|
1005
|
+
openTasks: removeWorkflowOpenTask(writer.readCurrentState()?.openTasks, 'tdd-slice'),
|
|
1006
|
+
});
|
|
1007
|
+
tddStatePath = join(writer.getStateDir(), `tdd-${slice.taskId}.json`);
|
|
1008
|
+
}
|
|
1009
|
+
if (args.json) {
|
|
1010
|
+
console.log(JSON.stringify({ slice, evaluation, artifactPath, tddStatePath }, null, 2));
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
console.log(renderTddSliceMarkdown(slice));
|
|
1014
|
+
if (evaluation.blockers.length > 0) {
|
|
1015
|
+
console.log('\nBlockers:');
|
|
1016
|
+
for (const blocker of evaluation.blockers)
|
|
1017
|
+
console.log(` - ${blocker}`);
|
|
1018
|
+
}
|
|
1019
|
+
if (artifactPath)
|
|
1020
|
+
console.log(`\nArtifact: ${artifactPath}`);
|
|
1021
|
+
if (tddStatePath)
|
|
1022
|
+
console.log(`TDD state: ${tddStatePath}`);
|
|
1023
|
+
},
|
|
1024
|
+
});
|
|
1025
|
+
const tdd = defineCommand({
|
|
1026
|
+
meta: { name: 'tdd', description: 'TDD vertical slice workflows' },
|
|
1027
|
+
subCommands: { slice: tddSliceCommand },
|
|
774
1028
|
});
|
|
775
1029
|
// ============================================================================
|
|
776
1030
|
// stats
|
|
@@ -893,6 +1147,52 @@ function printWorkspaceLifecycle(report) {
|
|
|
893
1147
|
for (const action of report.finish.nextActions)
|
|
894
1148
|
console.log(` [NEXT] ${action}`);
|
|
895
1149
|
}
|
|
1150
|
+
function compactList(values, limit = 5) {
|
|
1151
|
+
if (values.length <= limit)
|
|
1152
|
+
return values.join(', ');
|
|
1153
|
+
return `${values.slice(0, limit).join(', ')} (+${values.length - limit} more)`;
|
|
1154
|
+
}
|
|
1155
|
+
function printWorkspaceSummary(report) {
|
|
1156
|
+
const dirtyChildren = report.childRepositories
|
|
1157
|
+
.filter(child => !child.clean)
|
|
1158
|
+
.map(child => child.relativePath);
|
|
1159
|
+
const unpushedChildren = report.childRepositories
|
|
1160
|
+
.filter(child => child.ahead > 0 || (report.topology.finishPolicy.requirePushedBranches && report.topology.topology === 'moe' && !child.upstream && Boolean(child.branch)))
|
|
1161
|
+
.map(child => child.relativePath);
|
|
1162
|
+
const noUpstreamChildren = report.childRepositories
|
|
1163
|
+
.filter(child => !child.upstream && Boolean(child.branch))
|
|
1164
|
+
.map(child => child.relativePath);
|
|
1165
|
+
const rootStatus = report.root.clean
|
|
1166
|
+
? 'clean'
|
|
1167
|
+
: `dirty (staged=${report.root.staged}, unstaged=${report.root.unstaged}, untracked=${report.root.untracked})`;
|
|
1168
|
+
const status = report.finish.blockers.length > 0 ? 'BLOCKED' : 'READY';
|
|
1169
|
+
console.log('\nSCALE Workspace Summary');
|
|
1170
|
+
console.log(` Status: ${status}`);
|
|
1171
|
+
console.log(` Topology: ${report.topology.topology}${report.topology.configured ? '' : ' (default)'}`);
|
|
1172
|
+
console.log(` Root: ${rootStatus}`);
|
|
1173
|
+
console.log(` Children: ${report.childRepositories.length} total, ${dirtyChildren.length} dirty, ${unpushedChildren.length} unpushed, ${noUpstreamChildren.length} no upstream`);
|
|
1174
|
+
if (dirtyChildren.length > 0)
|
|
1175
|
+
console.log(` Dirty child repositories: ${compactList(dirtyChildren)}`);
|
|
1176
|
+
if (unpushedChildren.length > 0)
|
|
1177
|
+
console.log(` Unpushed child repositories: ${compactList(unpushedChildren)}`);
|
|
1178
|
+
if (report.finish.blockers.length > 0) {
|
|
1179
|
+
console.log('\n Blockers:');
|
|
1180
|
+
for (const blocker of report.finish.blockers.slice(0, 8))
|
|
1181
|
+
console.log(` - ${blocker}`);
|
|
1182
|
+
if (report.finish.blockers.length > 8)
|
|
1183
|
+
console.log(` - ... ${report.finish.blockers.length - 8} more blocker(s)`);
|
|
1184
|
+
}
|
|
1185
|
+
if (report.finish.warnings.length > 0) {
|
|
1186
|
+
console.log(`\n Warnings: ${report.finish.warnings.length} warning(s); run scale workspace finish --json for details`);
|
|
1187
|
+
}
|
|
1188
|
+
console.log('\n Next:');
|
|
1189
|
+
const nextActions = report.finish.blockers.length > 0
|
|
1190
|
+
? report.finish.nextActions
|
|
1191
|
+
: ['Proceed with scale ship <task-id> or cleanup when the branch policy is satisfied'];
|
|
1192
|
+
for (const action of nextActions.slice(0, 3))
|
|
1193
|
+
console.log(` - ${action}`);
|
|
1194
|
+
console.log(' - Run scale workspace finish --json for full details');
|
|
1195
|
+
}
|
|
896
1196
|
function printWorkspaceTopology(topology, written) {
|
|
897
1197
|
console.log('\nSCALE Workspace Topology');
|
|
898
1198
|
console.log(` Topology: ${topology.topology}${topology.configured ? '' : ' (default)'}`);
|
|
@@ -925,6 +1225,7 @@ const workspaceStatus = defineCommand({
|
|
|
925
1225
|
meta: { name: 'status', description: 'Inspect root worktree and child repository lifecycle state' },
|
|
926
1226
|
args: {
|
|
927
1227
|
dir: { type: 'string', description: 'Repository or worktree directory; defaults to current project directory' },
|
|
1228
|
+
summary: { type: 'boolean', default: false, description: 'Print concise human summary instead of the full repository listing' },
|
|
928
1229
|
json: { type: 'boolean', default: false },
|
|
929
1230
|
},
|
|
930
1231
|
async run({ args }) {
|
|
@@ -932,6 +1233,9 @@ const workspaceStatus = defineCommand({
|
|
|
932
1233
|
if (args.json) {
|
|
933
1234
|
console.log(JSON.stringify(report, null, 2));
|
|
934
1235
|
}
|
|
1236
|
+
else if (isTruthyFlag(args.summary)) {
|
|
1237
|
+
printWorkspaceSummary(report);
|
|
1238
|
+
}
|
|
935
1239
|
else {
|
|
936
1240
|
printWorkspaceLifecycle(report);
|
|
937
1241
|
}
|
|
@@ -972,6 +1276,7 @@ const workspaceFinish = defineCommand({
|
|
|
972
1276
|
meta: { name: 'finish', description: 'Check whether a temporary worktree can be safely finished or cleaned up' },
|
|
973
1277
|
args: {
|
|
974
1278
|
dir: { type: 'string', description: 'Repository or worktree directory; defaults to current project directory' },
|
|
1279
|
+
summary: { type: 'boolean', default: false, description: 'Print concise human summary instead of the full repository listing' },
|
|
975
1280
|
json: { type: 'boolean', default: false },
|
|
976
1281
|
},
|
|
977
1282
|
async run({ args }) {
|
|
@@ -985,6 +1290,9 @@ const workspaceFinish = defineCommand({
|
|
|
985
1290
|
if (args.json) {
|
|
986
1291
|
console.log(JSON.stringify(result, null, 2));
|
|
987
1292
|
}
|
|
1293
|
+
else if (isTruthyFlag(args.summary)) {
|
|
1294
|
+
printWorkspaceSummary(report);
|
|
1295
|
+
}
|
|
988
1296
|
else {
|
|
989
1297
|
printWorkspaceLifecycle(report);
|
|
990
1298
|
}
|
|
@@ -999,6 +1307,7 @@ const workspaceCleanup = defineCommand({
|
|
|
999
1307
|
'dry-run': { type: 'boolean', default: false, description: 'Preview cleanup; this is the default unless --apply is set' },
|
|
1000
1308
|
apply: { type: 'boolean', default: false, description: 'Actually run git worktree remove after safety checks' },
|
|
1001
1309
|
confirm: { type: 'string', description: 'Required confirmation token for --apply, usually the worktree branch name' },
|
|
1310
|
+
summary: { type: 'boolean', default: false, description: 'Print concise human summary before the cleanup plan' },
|
|
1002
1311
|
json: { type: 'boolean', default: false },
|
|
1003
1312
|
},
|
|
1004
1313
|
async run({ args }) {
|
|
@@ -1010,6 +1319,14 @@ const workspaceCleanup = defineCommand({
|
|
|
1010
1319
|
if (args.json) {
|
|
1011
1320
|
console.log(JSON.stringify(result, null, 2));
|
|
1012
1321
|
}
|
|
1322
|
+
else if (isTruthyFlag(args.summary)) {
|
|
1323
|
+
printWorkspaceSummary(result.report);
|
|
1324
|
+
console.log('\n Cleanup:');
|
|
1325
|
+
console.log(` Mode: ${result.mode}`);
|
|
1326
|
+
console.log(` Can apply: ${result.canApply ? 'yes' : 'no'}`);
|
|
1327
|
+
console.log(` Applied: ${result.applied ? 'yes' : 'no'}`);
|
|
1328
|
+
console.log(` Confirmation token: ${result.confirmationToken ?? '(unavailable)'}`);
|
|
1329
|
+
}
|
|
1013
1330
|
else {
|
|
1014
1331
|
printWorkspaceCleanup(result);
|
|
1015
1332
|
}
|
|
@@ -1153,6 +1470,9 @@ const status = defineCommand({
|
|
|
1153
1470
|
const latestReviews = reviewStore.listReviews(5);
|
|
1154
1471
|
const latestTask = tasks[0];
|
|
1155
1472
|
const taskPayload = latestTask?.payload;
|
|
1473
|
+
const workflowState = new WorkflowArtifactWriter(SCALE_DIR).readCurrentState();
|
|
1474
|
+
const currentOpenTasks = workflowState?.taskId === latestTask?.id ? workflowState.openTasks ?? [] : [];
|
|
1475
|
+
const nextOpenTask = nextWorkflowOpenTask(currentOpenTasks);
|
|
1156
1476
|
const blockers = [];
|
|
1157
1477
|
const latestBlockingEvidence = latestEvidence.find(record => !record.passed);
|
|
1158
1478
|
const latestBlockingReview = latestReviews.find(record => !record.passed);
|
|
@@ -1173,6 +1493,10 @@ const status = defineCommand({
|
|
|
1173
1493
|
return `scale plan ${specs[0].id}`;
|
|
1174
1494
|
if (!latestTask)
|
|
1175
1495
|
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}`;
|
|
1176
1500
|
if (!taskPayload?.verificationEvidenceIds?.length)
|
|
1177
1501
|
return `scale verify ${latestTask.id}`;
|
|
1178
1502
|
if (latestTask.status !== 'COMPLETED')
|
|
@@ -1214,6 +1538,14 @@ const status = defineCommand({
|
|
|
1214
1538
|
summary: record.summary,
|
|
1215
1539
|
createdAt: record.createdAt,
|
|
1216
1540
|
})),
|
|
1541
|
+
workflowState: workflowState ? {
|
|
1542
|
+
taskId: workflowState.taskId,
|
|
1543
|
+
level: workflowState.level,
|
|
1544
|
+
phase: workflowState.phase,
|
|
1545
|
+
artifactsDir: workflowState.artifactsDir,
|
|
1546
|
+
openTasks: workflowState.openTasks,
|
|
1547
|
+
skillIntents: workflowState.skillIntents,
|
|
1548
|
+
} : null,
|
|
1217
1549
|
blockers,
|
|
1218
1550
|
nextCommand,
|
|
1219
1551
|
};
|
|
@@ -1243,6 +1575,11 @@ const status = defineCommand({
|
|
|
1243
1575
|
for (const blocker of blockers)
|
|
1244
1576
|
console.log(` - ${blocker}`);
|
|
1245
1577
|
}
|
|
1578
|
+
if (result.workflowState?.openTasks.length) {
|
|
1579
|
+
console.log('\nOpen Tasks:');
|
|
1580
|
+
for (const task of result.workflowState.openTasks)
|
|
1581
|
+
console.log(` - ${task}`);
|
|
1582
|
+
}
|
|
1246
1583
|
console.log(`\nNext: ${nextCommand}`);
|
|
1247
1584
|
},
|
|
1248
1585
|
});
|
|
@@ -1559,14 +1896,48 @@ const assets = defineCommand({
|
|
|
1559
1896
|
// ============================================================================
|
|
1560
1897
|
// standards command - Engineering standards governance
|
|
1561
1898
|
// ============================================================================
|
|
1899
|
+
function resolveChangedFilesArg(args) {
|
|
1900
|
+
const explicit = splitChangedFiles(args['changed-files']);
|
|
1901
|
+
if (explicit.length > 0)
|
|
1902
|
+
return explicit;
|
|
1903
|
+
if (!args.changed)
|
|
1904
|
+
return undefined;
|
|
1905
|
+
return readGitChangedFiles(args.dir ?? '.');
|
|
1906
|
+
}
|
|
1907
|
+
function splitChangedFiles(value) {
|
|
1908
|
+
if (!value)
|
|
1909
|
+
return [];
|
|
1910
|
+
return value
|
|
1911
|
+
.split(/[\n,]/)
|
|
1912
|
+
.map(item => item.trim())
|
|
1913
|
+
.filter(Boolean);
|
|
1914
|
+
}
|
|
1915
|
+
function readGitChangedFiles(projectDir) {
|
|
1916
|
+
const tracked = readGitPathList(projectDir, ['diff', '--name-only', '--diff-filter=ACMRTUXB', 'HEAD', '--']);
|
|
1917
|
+
const untracked = readGitPathList(projectDir, ['ls-files', '--others', '--exclude-standard']);
|
|
1918
|
+
return Array.from(new Set([...tracked, ...untracked]));
|
|
1919
|
+
}
|
|
1920
|
+
function readGitPathList(projectDir, args) {
|
|
1921
|
+
try {
|
|
1922
|
+
return execFileSync('git', ['-C', projectDir, ...args], { encoding: 'utf-8' })
|
|
1923
|
+
.split(/\r?\n/)
|
|
1924
|
+
.map(item => item.trim())
|
|
1925
|
+
.filter(Boolean);
|
|
1926
|
+
}
|
|
1927
|
+
catch {
|
|
1928
|
+
return [];
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1562
1931
|
const standardsScan = defineCommand({
|
|
1563
1932
|
meta: { name: 'scan', description: 'Scan source files for engineering standard violations' },
|
|
1564
1933
|
args: {
|
|
1565
1934
|
dir: { type: 'string', default: '.', description: 'Project directory' },
|
|
1935
|
+
changed: { type: 'boolean', default: false, description: 'Scan changed Git files only' },
|
|
1936
|
+
'changed-files': { type: 'string', description: 'Comma or newline separated file list to scan' },
|
|
1566
1937
|
json: { type: 'boolean', default: false, description: 'Print JSON output' },
|
|
1567
1938
|
},
|
|
1568
1939
|
run({ args }) {
|
|
1569
|
-
const report = scanEngineeringStandards({ projectDir: args.dir });
|
|
1940
|
+
const report = scanEngineeringStandards({ projectDir: args.dir, changedFiles: resolveChangedFilesArg(args) });
|
|
1570
1941
|
if (args.json) {
|
|
1571
1942
|
console.log(JSON.stringify(report, null, 2));
|
|
1572
1943
|
return;
|
|
@@ -1588,10 +1959,12 @@ const standardsDoctor = defineCommand({
|
|
|
1588
1959
|
meta: { name: 'doctor', description: 'Find blocking engineering standards problems' },
|
|
1589
1960
|
args: {
|
|
1590
1961
|
dir: { type: 'string', default: '.', description: 'Project directory' },
|
|
1962
|
+
changed: { type: 'boolean', default: false, description: 'Scan changed Git files only' },
|
|
1963
|
+
'changed-files': { type: 'string', description: 'Comma or newline separated file list to scan' },
|
|
1591
1964
|
json: { type: 'boolean', default: false, description: 'Print JSON output' },
|
|
1592
1965
|
},
|
|
1593
1966
|
run({ args }) {
|
|
1594
|
-
const report = doctorEngineeringStandards({ projectDir: args.dir });
|
|
1967
|
+
const report = doctorEngineeringStandards({ projectDir: args.dir, changedFiles: resolveChangedFilesArg(args) });
|
|
1595
1968
|
if (args.json) {
|
|
1596
1969
|
console.log(JSON.stringify(report, null, 2));
|
|
1597
1970
|
if (!report.ok)
|
|
@@ -1619,6 +1992,8 @@ const standardsSettle = defineCommand({
|
|
|
1619
1992
|
dir: { type: 'string', default: '.', description: 'Project directory' },
|
|
1620
1993
|
'task-id': { type: 'string', description: 'Task id for the settlement record' },
|
|
1621
1994
|
'artifact-dir': { type: 'string', description: 'Task artifact directory where standards-impact.md should be updated' },
|
|
1995
|
+
changed: { type: 'boolean', default: false, description: 'Scan changed Git files only' },
|
|
1996
|
+
'changed-files': { type: 'string', description: 'Comma or newline separated file list to scan' },
|
|
1622
1997
|
json: { type: 'boolean', default: false, description: 'Print JSON output' },
|
|
1623
1998
|
},
|
|
1624
1999
|
run({ args }) {
|
|
@@ -1626,6 +2001,7 @@ const standardsSettle = defineCommand({
|
|
|
1626
2001
|
projectDir: args.dir,
|
|
1627
2002
|
taskId: args['task-id'],
|
|
1628
2003
|
artifactsDir: args['artifact-dir'],
|
|
2004
|
+
changedFiles: resolveChangedFilesArg(args),
|
|
1629
2005
|
});
|
|
1630
2006
|
if (args.json) {
|
|
1631
2007
|
console.log(JSON.stringify(report, null, 2));
|
|
@@ -1643,9 +2019,41 @@ const standardsSettle = defineCommand({
|
|
|
1643
2019
|
process.exitCode = 1;
|
|
1644
2020
|
},
|
|
1645
2021
|
});
|
|
2022
|
+
const standardsBaseline = defineCommand({
|
|
2023
|
+
meta: { name: 'baseline', description: 'Generate a legacy standards baseline and classification report' },
|
|
2024
|
+
args: {
|
|
2025
|
+
dir: { type: 'string', default: '.', description: 'Project directory' },
|
|
2026
|
+
write: { type: 'boolean', default: false, description: 'Write .scale/engineering-standards-baseline.json' },
|
|
2027
|
+
'task-id': { type: 'string', description: 'Task id for the legacy debt report' },
|
|
2028
|
+
'artifact-dir': { type: 'string', description: 'Directory where standards-legacy-debt.md should be written' },
|
|
2029
|
+
reason: { type: 'string', default: 'legacy standards debt accepted for staged remediation', description: 'Reason recorded on generated baseline entries' },
|
|
2030
|
+
json: { type: 'boolean', default: false, description: 'Print JSON output' },
|
|
2031
|
+
},
|
|
2032
|
+
run({ args }) {
|
|
2033
|
+
const report = baselineEngineeringStandards({
|
|
2034
|
+
projectDir: args.dir,
|
|
2035
|
+
writeBaseline: args.write,
|
|
2036
|
+
taskId: args['task-id'],
|
|
2037
|
+
artifactsDir: args['artifact-dir'],
|
|
2038
|
+
reason: args.reason,
|
|
2039
|
+
});
|
|
2040
|
+
if (args.json) {
|
|
2041
|
+
console.log(JSON.stringify(report, null, 2));
|
|
2042
|
+
return;
|
|
2043
|
+
}
|
|
2044
|
+
console.log(`Standards baseline: ${report.wroteBaseline ? 'written' : 'dry-run'}`);
|
|
2045
|
+
console.log(` Baseline entries: ${report.baselineEntries.length}`);
|
|
2046
|
+
console.log(` Blocking findings: ${report.debt.blockingFindings}`);
|
|
2047
|
+
console.log(` Baseline path: ${report.baselinePath}`);
|
|
2048
|
+
if (report.legacyDebtPath)
|
|
2049
|
+
console.log(` Legacy debt report: ${report.legacyDebtPath}`);
|
|
2050
|
+
if (!report.wroteBaseline)
|
|
2051
|
+
console.log(' Re-run with --write to update .scale/engineering-standards-baseline.json.');
|
|
2052
|
+
},
|
|
2053
|
+
});
|
|
1646
2054
|
const standards = defineCommand({
|
|
1647
2055
|
meta: { name: 'standards', description: 'Engineering standards governance for logs, security, architecture, database, and code quality' },
|
|
1648
|
-
subCommands: { scan: standardsScan, doctor: standardsDoctor, settle: standardsSettle },
|
|
2056
|
+
subCommands: { scan: standardsScan, doctor: standardsDoctor, settle: standardsSettle, baseline: standardsBaseline },
|
|
1649
2057
|
});
|
|
1650
2058
|
// ============================================================================
|
|
1651
2059
|
// evolve command
|
|
@@ -2083,9 +2491,341 @@ const skillCheckCommand = defineCommand({
|
|
|
2083
2491
|
process.exitCode = 1;
|
|
2084
2492
|
},
|
|
2085
2493
|
});
|
|
2494
|
+
const skillRepoCommand = defineCommand({
|
|
2495
|
+
meta: { name: 'repo', description: 'Show SCALE progressive skill repository guide' },
|
|
2496
|
+
args: {
|
|
2497
|
+
category: { type: 'string', description: 'Filter by category: ui/browser/desktop/testing/review/docs/agent-cli/role-library/discovery' },
|
|
2498
|
+
output: { type: 'string', alias: 'o', description: 'Write markdown guide to file' },
|
|
2499
|
+
json: { type: 'boolean', default: false },
|
|
2500
|
+
},
|
|
2501
|
+
run({ args }) {
|
|
2502
|
+
if (args.json) {
|
|
2503
|
+
console.log(JSON.stringify(listSkillRepositoryEntries(args.category ? { category: args.category } : undefined), null, 2));
|
|
2504
|
+
return;
|
|
2505
|
+
}
|
|
2506
|
+
const markdown = renderSkillRepositoryMarkdown();
|
|
2507
|
+
if (args.output) {
|
|
2508
|
+
const outputPath = resolve(PROJECT_DIR, args.output);
|
|
2509
|
+
ensureDir(resolve(outputPath, '..'));
|
|
2510
|
+
writeFileSync(outputPath, markdown, 'utf-8');
|
|
2511
|
+
console.log(`[OK] Skill 仓库指南已生成: ${outputPath}`);
|
|
2512
|
+
return;
|
|
2513
|
+
}
|
|
2514
|
+
console.log(markdown);
|
|
2515
|
+
},
|
|
2516
|
+
});
|
|
2517
|
+
const skillSafetyCommand = defineCommand({
|
|
2518
|
+
meta: { name: 'safety', description: 'Evaluate skill install command and source safety' },
|
|
2519
|
+
args: {
|
|
2520
|
+
source: { type: 'string', description: 'Skill source URL' },
|
|
2521
|
+
command: { type: 'string', description: 'Install command to review' },
|
|
2522
|
+
json: { type: 'boolean', default: false },
|
|
2523
|
+
},
|
|
2524
|
+
run({ args }) {
|
|
2525
|
+
const report = evaluateSkillInstallSafety({
|
|
2526
|
+
sourceUrl: args.source,
|
|
2527
|
+
installCommand: args.command,
|
|
2528
|
+
});
|
|
2529
|
+
if (args.json) {
|
|
2530
|
+
console.log(JSON.stringify(report, null, 2));
|
|
2531
|
+
return;
|
|
2532
|
+
}
|
|
2533
|
+
console.log('\nSCALE Skill Safety');
|
|
2534
|
+
console.log(` Risk: ${report.risk}`);
|
|
2535
|
+
console.log(` Blocked: ${report.blocked}`);
|
|
2536
|
+
for (const finding of report.findings) {
|
|
2537
|
+
console.log(` [${finding.severity.toUpperCase()}] ${finding.rule}: ${finding.message}`);
|
|
2538
|
+
}
|
|
2539
|
+
console.log(' Required checks:');
|
|
2540
|
+
for (const check of report.requiredChecks)
|
|
2541
|
+
console.log(` - ${check}`);
|
|
2542
|
+
if (report.blocked)
|
|
2543
|
+
process.exitCode = 1;
|
|
2544
|
+
},
|
|
2545
|
+
});
|
|
2546
|
+
const skillRecommendCommand = defineCommand({
|
|
2547
|
+
meta: { name: 'recommend', description: 'Recommend a composable skill workflow for a task' },
|
|
2548
|
+
args: {
|
|
2549
|
+
task: { type: 'string', required: true, description: 'Task description' },
|
|
2550
|
+
phase: { type: 'string', description: 'Workflow phase' },
|
|
2551
|
+
json: { type: 'boolean', default: false },
|
|
2552
|
+
},
|
|
2553
|
+
run({ args }) {
|
|
2554
|
+
const plan = recommendSkillWorkflow({
|
|
2555
|
+
description: args.task,
|
|
2556
|
+
phase: args.phase,
|
|
2557
|
+
});
|
|
2558
|
+
if (args.json) {
|
|
2559
|
+
console.log(JSON.stringify(plan, null, 2));
|
|
2560
|
+
return;
|
|
2561
|
+
}
|
|
2562
|
+
console.log('\nSCALE Skill Recommendation');
|
|
2563
|
+
console.log(` Primary: ${plan.primarySkills.join(', ') || 'none'}`);
|
|
2564
|
+
console.log(` Supporting: ${plan.supportingSkills.join(', ') || 'none'}`);
|
|
2565
|
+
console.log(` Safety required: ${plan.safetyRequired}`);
|
|
2566
|
+
console.log(` Evidence: ${plan.requiredEvidence.join(', ') || 'none'}`);
|
|
2567
|
+
for (const reason of plan.rationale)
|
|
2568
|
+
console.log(` - ${reason}`);
|
|
2569
|
+
},
|
|
2570
|
+
});
|
|
2086
2571
|
const skill = defineCommand({
|
|
2087
2572
|
meta: { name: 'skill', description: 'Skill discovery and management' },
|
|
2088
|
-
subCommands: {
|
|
2573
|
+
subCommands: {
|
|
2574
|
+
scan: skillScan,
|
|
2575
|
+
doctor: skillDoctorCommand,
|
|
2576
|
+
plan: skillPlanCommand,
|
|
2577
|
+
check: skillCheckCommand,
|
|
2578
|
+
repo: skillRepoCommand,
|
|
2579
|
+
safety: skillSafetyCommand,
|
|
2580
|
+
recommend: skillRecommendCommand,
|
|
2581
|
+
},
|
|
2582
|
+
});
|
|
2583
|
+
// ============================================================================
|
|
2584
|
+
// tool command - Skills/MCP/CLI orchestration governance
|
|
2585
|
+
// ============================================================================
|
|
2586
|
+
function normalizeToolMode(value) {
|
|
2587
|
+
const normalized = String(value ?? 'evidence-required');
|
|
2588
|
+
if (normalized === 'off' || normalized === 'advisory' || normalized === 'evidence-required' || normalized === 'block')
|
|
2589
|
+
return normalized;
|
|
2590
|
+
return 'evidence-required';
|
|
2591
|
+
}
|
|
2592
|
+
function parseToolIds(value) {
|
|
2593
|
+
const raw = String(value ?? '').trim();
|
|
2594
|
+
if (!raw)
|
|
2595
|
+
return undefined;
|
|
2596
|
+
return raw.split(',').map(item => item.trim()).filter(Boolean);
|
|
2597
|
+
}
|
|
2598
|
+
function parseCommaList(value) {
|
|
2599
|
+
return parseToolIds(value) ?? [];
|
|
2600
|
+
}
|
|
2601
|
+
function createToolExecutionPlanFromArgs(args) {
|
|
2602
|
+
const projectDir = resolve(String(args.dir ?? PROJECT_DIR));
|
|
2603
|
+
const level = normalizeTaskArtifactLevel(args.level ?? 'M');
|
|
2604
|
+
const skillPolicy = loadSkillRoutingPolicy(projectDir, SCALE_DIR);
|
|
2605
|
+
const skillPlan = createSkillPlan({
|
|
2606
|
+
taskId: String(args['task-id'] ?? `TOOL-${Date.now()}`),
|
|
2607
|
+
taskName: String(args.task ?? 'Tool orchestration task'),
|
|
2608
|
+
description: String(args.task ?? ''),
|
|
2609
|
+
level,
|
|
2610
|
+
files: parseCommaList(args.files),
|
|
2611
|
+
services: parseCommaList(args.services),
|
|
2612
|
+
policy: skillPolicy,
|
|
2613
|
+
});
|
|
2614
|
+
const toolPolicy = loadToolPolicy(projectDir, SCALE_DIR);
|
|
2615
|
+
const toolIds = uniqueStrings([
|
|
2616
|
+
...skillPlan.requiredSkills,
|
|
2617
|
+
...skillPlan.recommendedSkills,
|
|
2618
|
+
...Object.keys(toolPolicy.tools).filter(toolId => {
|
|
2619
|
+
const config = toolPolicy.tools[toolId];
|
|
2620
|
+
const domains = new Set(skillPlan.intents.map(intent => intent.domain));
|
|
2621
|
+
return config.enabled && (config.requiredFor.some(domain => domains.has(domain)) ||
|
|
2622
|
+
(config.recommendedFor ?? []).some(domain => domains.has(domain)));
|
|
2623
|
+
}),
|
|
2624
|
+
]);
|
|
2625
|
+
const capabilityReport = inspectToolCapabilities({
|
|
2626
|
+
projectDir,
|
|
2627
|
+
toolIds,
|
|
2628
|
+
});
|
|
2629
|
+
const orchestrator = new ToolOrchestrator({
|
|
2630
|
+
projectDir,
|
|
2631
|
+
policy: toolPolicy,
|
|
2632
|
+
capabilityReport,
|
|
2633
|
+
evidenceStore: new ToolEvidenceStore({ projectDir, scaleDir: SCALE_DIR }),
|
|
2634
|
+
});
|
|
2635
|
+
return {
|
|
2636
|
+
projectDir,
|
|
2637
|
+
skillPlan,
|
|
2638
|
+
orchestrator,
|
|
2639
|
+
plan: orchestrator.plan({ skillPlan }),
|
|
2640
|
+
capabilityReport,
|
|
2641
|
+
};
|
|
2642
|
+
}
|
|
2643
|
+
function uniqueStrings(items) {
|
|
2644
|
+
return [...new Set(items)];
|
|
2645
|
+
}
|
|
2646
|
+
const toolPolicyCommand = defineCommand({
|
|
2647
|
+
meta: { name: 'policy', description: 'Show resolved tool orchestration policy' },
|
|
2648
|
+
args: {
|
|
2649
|
+
dir: { type: 'string', default: '.', description: 'Project directory' },
|
|
2650
|
+
mode: { type: 'string', description: 'Render a starter policy mode instead of reading .scale/tools.json' },
|
|
2651
|
+
json: { type: 'boolean', default: false },
|
|
2652
|
+
},
|
|
2653
|
+
run({ args }) {
|
|
2654
|
+
const policy = args.mode
|
|
2655
|
+
? JSON.parse(toolPolicyTemplate(normalizeToolMode(args.mode)))
|
|
2656
|
+
: loadToolPolicy(args.dir, SCALE_DIR);
|
|
2657
|
+
if (args.json) {
|
|
2658
|
+
console.log(JSON.stringify(policy, null, 2));
|
|
2659
|
+
return;
|
|
2660
|
+
}
|
|
2661
|
+
console.log('\nSCALE Tool Policy');
|
|
2662
|
+
console.log(` Mode: ${policy.mode}`);
|
|
2663
|
+
console.log(` Tools: ${Object.keys(policy.tools).length}`);
|
|
2664
|
+
for (const [id, config] of Object.entries(policy.tools)) {
|
|
2665
|
+
const state = config.enabled ? '[ON]' : '[OFF]';
|
|
2666
|
+
console.log(` ${state} ${id}: requiredFor=${config.requiredFor.join(',') || 'none'}`);
|
|
2667
|
+
}
|
|
2668
|
+
},
|
|
2669
|
+
});
|
|
2670
|
+
const toolDoctorCommand = defineCommand({
|
|
2671
|
+
meta: { name: 'doctor', description: 'Check skill, MCP, and CLI tool availability' },
|
|
2672
|
+
args: {
|
|
2673
|
+
dir: { type: 'string', default: '.', description: 'Project directory' },
|
|
2674
|
+
tools: { type: 'string', description: 'Comma-separated tool ids to check' },
|
|
2675
|
+
json: { type: 'boolean', default: false },
|
|
2676
|
+
},
|
|
2677
|
+
run({ args }) {
|
|
2678
|
+
const report = inspectToolCapabilities({
|
|
2679
|
+
projectDir: args.dir,
|
|
2680
|
+
toolIds: parseToolIds(args.tools),
|
|
2681
|
+
});
|
|
2682
|
+
if (args.json) {
|
|
2683
|
+
console.log(JSON.stringify(report, null, 2));
|
|
2684
|
+
}
|
|
2685
|
+
else {
|
|
2686
|
+
console.log('\nSCALE Tool Doctor');
|
|
2687
|
+
console.log(` Installed: ${report.summary.installed}/${report.summary.total}`);
|
|
2688
|
+
for (const entry of report.tools) {
|
|
2689
|
+
console.log(` ${entry.installed ? '[OK]' : '[MISSING]'} ${entry.id}`);
|
|
2690
|
+
if (entry.detectedPath)
|
|
2691
|
+
console.log(` path: ${entry.detectedPath}`);
|
|
2692
|
+
if (entry.version)
|
|
2693
|
+
console.log(` version: ${entry.version}`);
|
|
2694
|
+
if (entry.missingReason)
|
|
2695
|
+
console.log(` reason: ${entry.missingReason}`);
|
|
2696
|
+
}
|
|
2697
|
+
}
|
|
2698
|
+
if (!report.ok)
|
|
2699
|
+
process.exitCode = 1;
|
|
2700
|
+
},
|
|
2701
|
+
});
|
|
2702
|
+
const toolPlanCommand = defineCommand({
|
|
2703
|
+
meta: { name: 'plan', description: 'Create a tool execution plan from task intent' },
|
|
2704
|
+
args: {
|
|
2705
|
+
dir: { type: 'string', default: '.', description: 'Project directory' },
|
|
2706
|
+
'task-id': { type: 'string', required: true, description: 'Task id for evidence linkage' },
|
|
2707
|
+
task: { type: 'string', required: true, description: 'Task description' },
|
|
2708
|
+
level: { type: 'string', default: 'M', description: 'Task level: S, M, L, or CRITICAL' },
|
|
2709
|
+
files: { type: 'string', description: 'Comma-separated changed or target files' },
|
|
2710
|
+
services: { type: 'string', description: 'Comma-separated affected services' },
|
|
2711
|
+
json: { type: 'boolean', default: false },
|
|
2712
|
+
},
|
|
2713
|
+
run({ args }) {
|
|
2714
|
+
const result = createToolExecutionPlanFromArgs(args);
|
|
2715
|
+
if (args.json) {
|
|
2716
|
+
console.log(JSON.stringify(result.plan, null, 2));
|
|
2717
|
+
return;
|
|
2718
|
+
}
|
|
2719
|
+
console.log('\nSCALE Tool Plan');
|
|
2720
|
+
console.log(` Task: ${result.plan.taskId}`);
|
|
2721
|
+
console.log(` Mode: ${result.plan.mode}`);
|
|
2722
|
+
console.log(` Steps: ${result.plan.steps.length}`);
|
|
2723
|
+
for (const step of result.plan.steps) {
|
|
2724
|
+
console.log(` ${step.status === 'ready' ? '[READY]' : '[MISSING]'} ${step.toolId} (${step.adapter}) required=${step.required}`);
|
|
2725
|
+
}
|
|
2726
|
+
for (const blocker of result.plan.blockers)
|
|
2727
|
+
console.log(` [BLOCKER] ${blocker}`);
|
|
2728
|
+
for (const warning of result.plan.warnings)
|
|
2729
|
+
console.log(` [WARN] ${warning}`);
|
|
2730
|
+
},
|
|
2731
|
+
});
|
|
2732
|
+
const toolRunCommand = defineCommand({
|
|
2733
|
+
meta: { name: 'run', description: 'Run or dry-run a tool execution plan and write tool evidence' },
|
|
2734
|
+
args: {
|
|
2735
|
+
dir: { type: 'string', default: '.', description: 'Project directory' },
|
|
2736
|
+
'task-id': { type: 'string', required: true, description: 'Task id for evidence linkage' },
|
|
2737
|
+
task: { type: 'string', required: true, description: 'Task description' },
|
|
2738
|
+
level: { type: 'string', default: 'M', description: 'Task level: S, M, L, or CRITICAL' },
|
|
2739
|
+
files: { type: 'string', description: 'Comma-separated changed or target files' },
|
|
2740
|
+
services: { type: 'string', description: 'Comma-separated affected services' },
|
|
2741
|
+
'dry-run': { type: 'boolean', default: false, description: 'Plan and record skipped evidence without executing tools' },
|
|
2742
|
+
json: { type: 'boolean', default: false },
|
|
2743
|
+
},
|
|
2744
|
+
async run({ args }) {
|
|
2745
|
+
const result = createToolExecutionPlanFromArgs(args);
|
|
2746
|
+
const report = await result.orchestrator.run(result.plan, {
|
|
2747
|
+
dryRun: isTruthyFlag(args['dry-run']),
|
|
2748
|
+
});
|
|
2749
|
+
if (toolEvidenceRunCompletesOpenTask(report)) {
|
|
2750
|
+
const writer = new WorkflowArtifactWriter(SCALE_DIR);
|
|
2751
|
+
const current = writer.readCurrentState();
|
|
2752
|
+
if (current?.taskId === report.taskId) {
|
|
2753
|
+
writer.updateCurrentState({
|
|
2754
|
+
taskId: report.taskId,
|
|
2755
|
+
openTasks: removeWorkflowOpenTask(current.openTasks, 'tool-evidence'),
|
|
2756
|
+
});
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
2759
|
+
if (args.json) {
|
|
2760
|
+
console.log(JSON.stringify(report, null, 2));
|
|
2761
|
+
}
|
|
2762
|
+
else {
|
|
2763
|
+
console.log('\nSCALE Tool Run');
|
|
2764
|
+
console.log(` Task: ${report.taskId}`);
|
|
2765
|
+
console.log(` Dry-run: ${report.dryRun}`);
|
|
2766
|
+
console.log(` Evidence: ${report.evidence.length}`);
|
|
2767
|
+
for (const record of report.evidence) {
|
|
2768
|
+
console.log(` [${record.status.toUpperCase()}] ${record.tool} -> ${record.id}`);
|
|
2769
|
+
}
|
|
2770
|
+
for (const blocker of report.blockers)
|
|
2771
|
+
console.log(` [BLOCKER] ${blocker}`);
|
|
2772
|
+
for (const warning of report.warnings)
|
|
2773
|
+
console.log(` [WARN] ${warning}`);
|
|
2774
|
+
}
|
|
2775
|
+
if (!report.ok)
|
|
2776
|
+
process.exitCode = 1;
|
|
2777
|
+
},
|
|
2778
|
+
});
|
|
2779
|
+
const toolEvidenceCommand = defineCommand({
|
|
2780
|
+
meta: { name: 'evidence', description: 'Check required tool execution evidence for a task' },
|
|
2781
|
+
args: {
|
|
2782
|
+
dir: { type: 'string', default: '.', description: 'Project directory' },
|
|
2783
|
+
'task-id': { type: 'string', required: true, description: 'Task id for evidence linkage' },
|
|
2784
|
+
task: { type: 'string', required: true, description: 'Task description' },
|
|
2785
|
+
level: { type: 'string', default: 'M', description: 'Task level: S, M, L, or CRITICAL' },
|
|
2786
|
+
files: { type: 'string', description: 'Comma-separated changed or target files' },
|
|
2787
|
+
services: { type: 'string', description: 'Comma-separated affected services' },
|
|
2788
|
+
mode: { type: 'string', description: 'Override tool gate mode: off, advisory, evidence-required, or block' },
|
|
2789
|
+
'allow-skipped': { type: 'boolean', default: false, description: 'Allow skipped/manual fallback evidence to satisfy required tools' },
|
|
2790
|
+
json: { type: 'boolean', default: false },
|
|
2791
|
+
},
|
|
2792
|
+
run({ args }) {
|
|
2793
|
+
const result = createToolExecutionPlanFromArgs(args);
|
|
2794
|
+
const gate = evaluateToolEvidenceGate({
|
|
2795
|
+
projectDir: result.projectDir,
|
|
2796
|
+
level: normalizeTaskArtifactLevel(args.level ?? 'M'),
|
|
2797
|
+
plan: result.plan,
|
|
2798
|
+
evidenceStore: new ToolEvidenceStore({ projectDir: result.projectDir, scaleDir: SCALE_DIR }),
|
|
2799
|
+
mode: args.mode ? normalizeToolMode(args.mode) : result.plan.mode,
|
|
2800
|
+
allowSkipped: isTruthyFlag(args['allow-skipped']),
|
|
2801
|
+
});
|
|
2802
|
+
if (args.json) {
|
|
2803
|
+
console.log(JSON.stringify(gate, null, 2));
|
|
2804
|
+
}
|
|
2805
|
+
else {
|
|
2806
|
+
console.log('\nSCALE Tool Evidence Gate');
|
|
2807
|
+
console.log(` Task: ${gate.taskId ?? args['task-id']}`);
|
|
2808
|
+
console.log(` Mode: ${gate.mode}`);
|
|
2809
|
+
console.log(` Complete: ${gate.complete}`);
|
|
2810
|
+
console.log(` Required tools: ${gate.requiredTools.join(', ') || 'none'}`);
|
|
2811
|
+
for (const item of gate.missing)
|
|
2812
|
+
console.log(` [MISSING] ${item.toolId}: ${item.reason}`);
|
|
2813
|
+
for (const item of gate.failed)
|
|
2814
|
+
console.log(` [FAILED] ${item.toolId}: ${item.reason}`);
|
|
2815
|
+
for (const item of gate.skipped)
|
|
2816
|
+
console.log(` [SKIPPED] ${item.toolId}: ${item.reason}`);
|
|
2817
|
+
for (const item of gate.passed)
|
|
2818
|
+
console.log(` [PASS] ${item.toolId}: ${item.evidenceId ?? 'evidence'}`);
|
|
2819
|
+
for (const warning of gate.warnings)
|
|
2820
|
+
console.log(` [WARN] ${warning}`);
|
|
2821
|
+
}
|
|
2822
|
+
if (gate.blocked)
|
|
2823
|
+
process.exitCode = 1;
|
|
2824
|
+
},
|
|
2825
|
+
});
|
|
2826
|
+
const tool = defineCommand({
|
|
2827
|
+
meta: { name: 'tool', description: 'Skills, MCP, browser, desktop, and external CLI governance' },
|
|
2828
|
+
subCommands: { policy: toolPolicyCommand, doctor: toolDoctorCommand, plan: toolPlanCommand, run: toolRunCommand, evidence: toolEvidenceCommand },
|
|
2089
2829
|
});
|
|
2090
2830
|
// ============================================================================
|
|
2091
2831
|
// agent commands — Multi-Agent 协作系统 (Phase 9)
|
|
@@ -2142,9 +2882,32 @@ const agentProfiles = defineCommand({
|
|
|
2142
2882
|
}
|
|
2143
2883
|
},
|
|
2144
2884
|
});
|
|
2885
|
+
const agentLeaders = defineCommand({
|
|
2886
|
+
meta: { name: 'leaders', description: 'List SCALE leader presets such as CEO and CTO' },
|
|
2887
|
+
args: {
|
|
2888
|
+
output: { type: 'string', alias: 'o', description: 'Write markdown guide to file' },
|
|
2889
|
+
json: { type: 'boolean', default: false },
|
|
2890
|
+
},
|
|
2891
|
+
async run({ args }) {
|
|
2892
|
+
const presets = listLeadershipPresets();
|
|
2893
|
+
if (args.json) {
|
|
2894
|
+
console.log(JSON.stringify(presets, null, 2));
|
|
2895
|
+
return;
|
|
2896
|
+
}
|
|
2897
|
+
const markdown = renderLeadershipPresetsMarkdown();
|
|
2898
|
+
if (args.output) {
|
|
2899
|
+
const outputPath = resolve(PROJECT_DIR, args.output);
|
|
2900
|
+
ensureDir(resolve(outputPath, '..'));
|
|
2901
|
+
writeFileSync(outputPath, markdown, 'utf-8');
|
|
2902
|
+
console.log(`[OK] 领导者角色指南已生成: ${outputPath}`);
|
|
2903
|
+
return;
|
|
2904
|
+
}
|
|
2905
|
+
console.log(markdown);
|
|
2906
|
+
},
|
|
2907
|
+
});
|
|
2145
2908
|
const agent = defineCommand({
|
|
2146
2909
|
meta: { name: 'agent', description: 'Multi-Agent system management' },
|
|
2147
|
-
subCommands: { spawn: agentSpawn, list: agentList, profiles: agentProfiles },
|
|
2910
|
+
subCommands: { spawn: agentSpawn, list: agentList, profiles: agentProfiles, leaders: agentLeaders },
|
|
2148
2911
|
});
|
|
2149
2912
|
// ============================================================================
|
|
2150
2913
|
// team commands — 团队协作 (Phase 9)
|
|
@@ -2250,6 +3013,9 @@ const main = defineCommand({
|
|
|
2250
3013
|
status,
|
|
2251
3014
|
workflow,
|
|
2252
3015
|
evidence,
|
|
3016
|
+
diagnose,
|
|
3017
|
+
tdd,
|
|
3018
|
+
tool,
|
|
2253
3019
|
skill,
|
|
2254
3020
|
skills: skill,
|
|
2255
3021
|
agent,
|