@hongmaple0820/scale-engine 0.16.0 → 0.18.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/api/cli.js +494 -9
- package/dist/api/cli.js.map +1 -1
- package/dist/api/doctor.d.ts +1 -0
- package/dist/api/doctor.js +45 -0
- package/dist/api/doctor.js.map +1 -1
- package/dist/api/quickstart.js +5 -2
- package/dist/api/quickstart.js.map +1 -1
- package/dist/cli/phaseCommands.js +36 -6
- package/dist/cli/phaseCommands.js.map +1 -1
- package/dist/core/logger.js +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- 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/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 +57 -0
- package/dist/tools/ToolOrchestrator.js.map +1 -1
- 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/GovernanceTemplatePacks.d.ts +1 -1
- package/dist/workflow/GovernanceTemplatePacks.js +77 -19
- package/dist/workflow/GovernanceTemplatePacks.js.map +1 -1
- package/dist/workflow/GovernanceTemplates.js +36 -12
- package/dist/workflow/GovernanceTemplates.js.map +1 -1
- package/dist/workflow/ResourceGovernance.js +2 -1
- package/dist/workflow/ResourceGovernance.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
|
@@ -30,24 +30,30 @@ 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';
|
|
37
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';
|
|
38
42
|
import { TaskMetricsStore } from '../workflow/TaskMetricsStore.js';
|
|
39
|
-
import { checkTaskArtifactCompleteness } from '../workflow/TaskArtifactScaffolder.js';
|
|
43
|
+
import { appendContextGrillArtifact, appendDiagnosticLoopArtifact, appendTddSliceArtifact, checkTaskArtifactCompleteness, } from '../workflow/TaskArtifactScaffolder.js';
|
|
40
44
|
import { WorkflowArtifactWriter } from '../workflow/WorkflowArtifactWriter.js';
|
|
41
45
|
import { inspectToolCapabilities } from '../tools/ToolCapabilityRegistry.js';
|
|
42
46
|
import { evaluateToolEvidenceGate } from '../tools/ToolEvidenceGate.js';
|
|
43
47
|
import { ToolEvidenceStore } from '../tools/ToolEvidenceStore.js';
|
|
44
48
|
import { ToolOrchestrator } from '../tools/ToolOrchestrator.js';
|
|
45
49
|
import { loadToolPolicy, toolPolicyTemplate } from '../tools/ToolPolicy.js';
|
|
50
|
+
import { doctorHtmlArtifacts, renderHtmlArtifact, resolveHtmlArtifactForOpen, settleHtmlArtifacts, } from '../output/HTMLArtifactLayer.js';
|
|
46
51
|
import { cleanupWorkspaceLifecycle, inspectWorkspaceLifecycle, } from '../workflow/WorkspaceLifecycle.js';
|
|
47
52
|
import { resolveWorkspaceTopology, workspaceTopologyPath, workspaceTopologyTemplate, } from '../workflow/WorkspaceTopology.js';
|
|
48
53
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
49
54
|
import { join, resolve } from 'node:path';
|
|
50
55
|
import { execFileSync } from 'node:child_process';
|
|
56
|
+
import { pathToFileURL } from 'node:url';
|
|
51
57
|
import { SCALE_ENGINE_VERSION } from '../version.js';
|
|
52
58
|
// ============================================================================
|
|
53
59
|
// Engine bootstrap (单例 + lazy init)
|
|
@@ -69,6 +75,18 @@ function ensureDir(dir) {
|
|
|
69
75
|
function isTruthyFlag(value) {
|
|
70
76
|
return value === true || value === '' || value === 'true' || value === '1';
|
|
71
77
|
}
|
|
78
|
+
function commandEvidence(command, exitCode, summary) {
|
|
79
|
+
if (exitCode === undefined || exitCode === null || exitCode === '')
|
|
80
|
+
return undefined;
|
|
81
|
+
const parsed = Number.parseInt(String(exitCode), 10);
|
|
82
|
+
if (Number.isNaN(parsed))
|
|
83
|
+
return undefined;
|
|
84
|
+
return {
|
|
85
|
+
command,
|
|
86
|
+
exitCode: parsed,
|
|
87
|
+
outputSummary: summary ? String(summary) : `Command exited ${parsed}`,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
72
90
|
function normalizePreflightProfile(value) {
|
|
73
91
|
const normalized = String(value ?? 'quick').trim().toLowerCase();
|
|
74
92
|
if (normalized === 'full' || normalized === 'ci')
|
|
@@ -80,6 +98,26 @@ function gatesForPreflightProfile(profile) {
|
|
|
80
98
|
return ['G3', 'G0', 'G4', 'G5'];
|
|
81
99
|
return ['G3', 'G0', 'G4', 'G5', 'G6', 'G7'];
|
|
82
100
|
}
|
|
101
|
+
function shouldSkipPreflightCommandTargets(resolved, args) {
|
|
102
|
+
if (!resolved.matrix)
|
|
103
|
+
return false;
|
|
104
|
+
const requestedService = String(args.service ?? '').trim();
|
|
105
|
+
if (requestedService && requestedService !== 'all')
|
|
106
|
+
return false;
|
|
107
|
+
const hasCommandOverrides = [
|
|
108
|
+
args['build-cmd'],
|
|
109
|
+
args['lint-cmd'],
|
|
110
|
+
args['test-cmd'],
|
|
111
|
+
args['coverage-cmd'],
|
|
112
|
+
].some(value => typeof value === 'string' && value.trim().length > 0);
|
|
113
|
+
if (hasCommandOverrides)
|
|
114
|
+
return false;
|
|
115
|
+
const profile = resolved.matrix.profiles?.[resolved.profileName];
|
|
116
|
+
const hasProfileCommands = Object.values(profile?.commands ?? {})
|
|
117
|
+
.some(value => typeof value === 'string' && value.trim().length > 0);
|
|
118
|
+
const hasServices = (resolved.matrix.services ?? []).length > 0;
|
|
119
|
+
return !hasServices && !hasProfileCommands;
|
|
120
|
+
}
|
|
83
121
|
function evaluateEngineeringStandardsGate(options) {
|
|
84
122
|
const mode = normalizeEngineeringStandardsGateMode(options.policy.engineeringStandardsGate);
|
|
85
123
|
if (mode === 'off') {
|
|
@@ -776,9 +814,239 @@ const contextGlossary = defineCommand({
|
|
|
776
814
|
}
|
|
777
815
|
},
|
|
778
816
|
});
|
|
817
|
+
const contextInit = defineCommand({
|
|
818
|
+
meta: { name: 'init', description: 'Create CONTEXT.md and CONTEXT-MAP.md starter templates' },
|
|
819
|
+
args: {
|
|
820
|
+
dir: { type: 'string', default: PROJECT_DIR, description: 'Project directory' },
|
|
821
|
+
name: { type: 'string', description: 'Project display name' },
|
|
822
|
+
force: { type: 'boolean', default: false, description: 'Overwrite existing templates' },
|
|
823
|
+
json: { type: 'boolean', default: false },
|
|
824
|
+
},
|
|
825
|
+
run({ args }) {
|
|
826
|
+
const result = writeContextGovernanceTemplates({
|
|
827
|
+
projectDir: resolve(String(args.dir ?? PROJECT_DIR)),
|
|
828
|
+
projectName: args.name ? String(args.name) : undefined,
|
|
829
|
+
force: isTruthyFlag(args.force),
|
|
830
|
+
});
|
|
831
|
+
if (args.json) {
|
|
832
|
+
console.log(JSON.stringify(result, null, 2));
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
console.log('\nSCALE Context Templates');
|
|
836
|
+
for (const file of result.created)
|
|
837
|
+
console.log(` [CREATED] ${file}`);
|
|
838
|
+
for (const file of result.skipped)
|
|
839
|
+
console.log(` [SKIPPED] ${file}`);
|
|
840
|
+
},
|
|
841
|
+
});
|
|
842
|
+
const contextGrill = defineCommand({
|
|
843
|
+
meta: { name: 'grill', description: 'Check project context docs and generate request-specific grill questions' },
|
|
844
|
+
args: {
|
|
845
|
+
dir: { type: 'string', default: PROJECT_DIR, description: 'Project directory' },
|
|
846
|
+
'task-id': { type: 'string', description: 'Task id for workflow state and artifact linkage' },
|
|
847
|
+
task: { type: 'string', required: true, description: 'Task or requirement description' },
|
|
848
|
+
files: { type: 'string', description: 'Comma-separated changed or target files' },
|
|
849
|
+
'artifact-dir': { type: 'string', description: 'Task artifact directory where explore.md should be updated' },
|
|
850
|
+
write: { type: 'boolean', default: false, description: 'Append context grill output to the task explore artifact' },
|
|
851
|
+
json: { type: 'boolean', default: false },
|
|
852
|
+
},
|
|
853
|
+
run({ args }) {
|
|
854
|
+
const projectDir = resolve(String(args.dir ?? PROJECT_DIR));
|
|
855
|
+
const taskId = String(args['task-id'] ?? `context-${Date.now()}`);
|
|
856
|
+
const changedFiles = parseCommaList(args.files);
|
|
857
|
+
const report = analyzeContextGovernance({
|
|
858
|
+
projectDir,
|
|
859
|
+
request: String(args.task ?? ''),
|
|
860
|
+
changedFiles,
|
|
861
|
+
});
|
|
862
|
+
const artifactPath = isTruthyFlag(args.write)
|
|
863
|
+
? appendContextGrillArtifact({
|
|
864
|
+
projectDir,
|
|
865
|
+
artifactsDir: args['artifact-dir'] ? String(args['artifact-dir']) : undefined,
|
|
866
|
+
report,
|
|
867
|
+
}) ?? undefined
|
|
868
|
+
: undefined;
|
|
869
|
+
if (args['task-id'] || artifactPath) {
|
|
870
|
+
const writer = new WorkflowArtifactWriter(SCALE_DIR);
|
|
871
|
+
const current = writer.readCurrentState();
|
|
872
|
+
const currentOpenTasks = current?.taskId === taskId ? current.openTasks : [];
|
|
873
|
+
writer.updateCurrentState({
|
|
874
|
+
taskId,
|
|
875
|
+
phase: 'explore',
|
|
876
|
+
artifactsDir: args['artifact-dir'] ? String(args['artifact-dir']).replace(/\\/g, '/') : undefined,
|
|
877
|
+
exploredFiles: changedFiles,
|
|
878
|
+
fileCount: changedFiles.length,
|
|
879
|
+
mainContradiction: report.findings[0]?.message ?? 'context governance ready',
|
|
880
|
+
openTasks: removeWorkflowOpenTask(currentOpenTasks, 'context-grill'),
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
if (args.json) {
|
|
884
|
+
console.log(JSON.stringify({ ...report, artifactPath }, null, 2));
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
console.log(renderContextGrillPrompt(report));
|
|
888
|
+
if (artifactPath)
|
|
889
|
+
console.log(`\nArtifact: ${artifactPath}`);
|
|
890
|
+
},
|
|
891
|
+
});
|
|
779
892
|
const context = defineCommand({
|
|
780
893
|
meta: { name: 'context', description: 'Context assembly' },
|
|
781
|
-
subCommands: { build: contextBuild, status: contextStatus, inject: contextInject, glossary: contextGlossary },
|
|
894
|
+
subCommands: { build: contextBuild, status: contextStatus, inject: contextInject, glossary: contextGlossary, init: contextInit, grill: contextGrill },
|
|
895
|
+
});
|
|
896
|
+
// ============================================================================
|
|
897
|
+
// diagnose command - evidence-first debugging loop
|
|
898
|
+
// ============================================================================
|
|
899
|
+
const diagnosePlanCommand = defineCommand({
|
|
900
|
+
meta: { name: 'plan', description: 'Create a reproducible diagnostic loop before fixing a bug' },
|
|
901
|
+
args: {
|
|
902
|
+
'task-id': { type: 'string', required: true },
|
|
903
|
+
symptom: { type: 'string', required: true },
|
|
904
|
+
repro: { type: 'string', description: 'Command that reproduces the current failure' },
|
|
905
|
+
'expected-failure': { type: 'string', description: 'Expected failing behavior or assertion' },
|
|
906
|
+
files: { type: 'string', description: 'Comma-separated changed or suspicious files' },
|
|
907
|
+
verify: { type: 'string', description: 'Comma-separated verification commands after the fix' },
|
|
908
|
+
'artifact-dir': { type: 'string', description: 'Task artifact directory where plan.md should be updated' },
|
|
909
|
+
write: { type: 'boolean', default: false, description: 'Append diagnostic loop output to the task plan artifact' },
|
|
910
|
+
json: { type: 'boolean', default: false },
|
|
911
|
+
},
|
|
912
|
+
run({ args }) {
|
|
913
|
+
const changedFiles = parseCommaList(args.files);
|
|
914
|
+
const loop = createDiagnosticLoop({
|
|
915
|
+
taskId: String(args['task-id']),
|
|
916
|
+
symptom: String(args.symptom),
|
|
917
|
+
reproductionCommand: args.repro ? String(args.repro) : undefined,
|
|
918
|
+
expectedFailure: args['expected-failure'] ? String(args['expected-failure']) : undefined,
|
|
919
|
+
changedFiles,
|
|
920
|
+
verificationCommands: parseCommaList(args.verify),
|
|
921
|
+
});
|
|
922
|
+
const validation = validateDiagnosticLoop(loop);
|
|
923
|
+
const artifactPath = isTruthyFlag(args.write)
|
|
924
|
+
? appendDiagnosticLoopArtifact({
|
|
925
|
+
projectDir: PROJECT_DIR,
|
|
926
|
+
artifactsDir: args['artifact-dir'] ? String(args['artifact-dir']) : undefined,
|
|
927
|
+
loop,
|
|
928
|
+
validation,
|
|
929
|
+
}) ?? undefined
|
|
930
|
+
: undefined;
|
|
931
|
+
if (artifactPath || args['artifact-dir']) {
|
|
932
|
+
const writer = new WorkflowArtifactWriter(SCALE_DIR);
|
|
933
|
+
const current = writer.readCurrentState();
|
|
934
|
+
const currentOpenTasks = current?.taskId === loop.taskId ? current.openTasks : [];
|
|
935
|
+
writer.updateCurrentState({
|
|
936
|
+
taskId: loop.taskId,
|
|
937
|
+
phase: 'plan',
|
|
938
|
+
artifactsDir: args['artifact-dir'] ? String(args['artifact-dir']).replace(/\\/g, '/') : undefined,
|
|
939
|
+
filesModified: changedFiles,
|
|
940
|
+
openTasks: validation.ready
|
|
941
|
+
? removeWorkflowOpenTask(currentOpenTasks.filter(task => task.trim().startsWith('scale ')), 'diagnostic-loop')
|
|
942
|
+
: uniqueStrings([
|
|
943
|
+
...currentOpenTasks,
|
|
944
|
+
...validation.blockers,
|
|
945
|
+
]),
|
|
946
|
+
});
|
|
947
|
+
}
|
|
948
|
+
if (args.json) {
|
|
949
|
+
console.log(JSON.stringify({ loop, validation, artifactPath }, null, 2));
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
console.log(renderDiagnosticLoopMarkdown(loop));
|
|
953
|
+
if (!validation.ready) {
|
|
954
|
+
console.log('\nBlockers:');
|
|
955
|
+
for (const blocker of validation.blockers)
|
|
956
|
+
console.log(` - ${blocker}`);
|
|
957
|
+
}
|
|
958
|
+
if (artifactPath)
|
|
959
|
+
console.log(`\nArtifact: ${artifactPath}`);
|
|
960
|
+
},
|
|
961
|
+
});
|
|
962
|
+
const diagnose = defineCommand({
|
|
963
|
+
meta: { name: 'diagnose', description: 'Evidence-first debugging workflows' },
|
|
964
|
+
subCommands: { plan: diagnosePlanCommand },
|
|
965
|
+
});
|
|
966
|
+
// ============================================================================
|
|
967
|
+
// tdd command - vertical slice RED/GREEN/REFACTOR loop
|
|
968
|
+
// ============================================================================
|
|
969
|
+
const tddSliceCommand = defineCommand({
|
|
970
|
+
meta: { name: 'slice', description: 'Create and evaluate a TDD vertical slice' },
|
|
971
|
+
args: {
|
|
972
|
+
'task-id': { type: 'string', required: true },
|
|
973
|
+
behavior: { type: 'string', required: true },
|
|
974
|
+
'public-interface': { type: 'string', required: true },
|
|
975
|
+
'failing-test': { type: 'string', required: true },
|
|
976
|
+
'test-file': { type: 'string', required: true },
|
|
977
|
+
'impl-files': { type: 'string', required: true },
|
|
978
|
+
'red-exit-code': { type: 'string', description: 'Exit code from the RED command' },
|
|
979
|
+
'red-summary': { type: 'string', description: 'Short RED output summary' },
|
|
980
|
+
'green-exit-code': { type: 'string', description: 'Exit code from the GREEN command' },
|
|
981
|
+
'green-summary': { type: 'string', description: 'Short GREEN output summary' },
|
|
982
|
+
'refactor-exit-code': { type: 'string', description: 'Exit code from the REFACTOR command' },
|
|
983
|
+
'refactor-summary': { type: 'string', description: 'Short REFACTOR output summary' },
|
|
984
|
+
'artifact-dir': { type: 'string', description: 'Task artifact directory where verification.md should be updated' },
|
|
985
|
+
write: { type: 'boolean', default: false, description: 'Append TDD slice output to the task verification artifact' },
|
|
986
|
+
json: { type: 'boolean', default: false },
|
|
987
|
+
},
|
|
988
|
+
run({ args }) {
|
|
989
|
+
const failingTest = String(args['failing-test']);
|
|
990
|
+
const slice = createTddSlice({
|
|
991
|
+
taskId: String(args['task-id']),
|
|
992
|
+
behavior: String(args.behavior),
|
|
993
|
+
publicInterface: String(args['public-interface']),
|
|
994
|
+
failingTestCommand: failingTest,
|
|
995
|
+
testFile: String(args['test-file']),
|
|
996
|
+
implementationFiles: parseCommaList(args['impl-files']),
|
|
997
|
+
redEvidence: commandEvidence(failingTest, args['red-exit-code'], args['red-summary']),
|
|
998
|
+
greenEvidence: commandEvidence(failingTest, args['green-exit-code'], args['green-summary']),
|
|
999
|
+
refactorEvidence: commandEvidence(failingTest, args['refactor-exit-code'], args['refactor-summary']),
|
|
1000
|
+
});
|
|
1001
|
+
const evaluation = evaluateTddSlice(slice);
|
|
1002
|
+
const artifactPath = isTruthyFlag(args.write)
|
|
1003
|
+
? appendTddSliceArtifact({
|
|
1004
|
+
projectDir: PROJECT_DIR,
|
|
1005
|
+
artifactsDir: args['artifact-dir'] ? String(args['artifact-dir']) : undefined,
|
|
1006
|
+
slice,
|
|
1007
|
+
}) ?? undefined
|
|
1008
|
+
: undefined;
|
|
1009
|
+
let tddStatePath;
|
|
1010
|
+
if (slice.redEvidence && slice.greenEvidence && slice.refactorEvidence) {
|
|
1011
|
+
const writer = new WorkflowArtifactWriter(SCALE_DIR);
|
|
1012
|
+
writer.writeTDDEvidence({
|
|
1013
|
+
timestamp: new Date().toISOString(),
|
|
1014
|
+
taskId: slice.taskId,
|
|
1015
|
+
red: slice.redEvidence.exitCode !== 0,
|
|
1016
|
+
green: slice.greenEvidence.exitCode === 0,
|
|
1017
|
+
refactor: slice.refactorEvidence.exitCode === 0,
|
|
1018
|
+
testFirst: slice.redEvidence.exitCode !== 0,
|
|
1019
|
+
testFile: slice.testFile,
|
|
1020
|
+
implFile: slice.implementationFiles[0] ?? '',
|
|
1021
|
+
});
|
|
1022
|
+
writer.updateCurrentState({
|
|
1023
|
+
taskId: slice.taskId,
|
|
1024
|
+
phase: 'verify',
|
|
1025
|
+
artifactsDir: args['artifact-dir'] ? String(args['artifact-dir']).replace(/\\/g, '/') : undefined,
|
|
1026
|
+
filesModified: slice.implementationFiles,
|
|
1027
|
+
openTasks: removeWorkflowOpenTask(writer.readCurrentState()?.openTasks, 'tdd-slice'),
|
|
1028
|
+
});
|
|
1029
|
+
tddStatePath = join(writer.getStateDir(), `tdd-${slice.taskId}.json`);
|
|
1030
|
+
}
|
|
1031
|
+
if (args.json) {
|
|
1032
|
+
console.log(JSON.stringify({ slice, evaluation, artifactPath, tddStatePath }, null, 2));
|
|
1033
|
+
return;
|
|
1034
|
+
}
|
|
1035
|
+
console.log(renderTddSliceMarkdown(slice));
|
|
1036
|
+
if (evaluation.blockers.length > 0) {
|
|
1037
|
+
console.log('\nBlockers:');
|
|
1038
|
+
for (const blocker of evaluation.blockers)
|
|
1039
|
+
console.log(` - ${blocker}`);
|
|
1040
|
+
}
|
|
1041
|
+
if (artifactPath)
|
|
1042
|
+
console.log(`\nArtifact: ${artifactPath}`);
|
|
1043
|
+
if (tddStatePath)
|
|
1044
|
+
console.log(`TDD state: ${tddStatePath}`);
|
|
1045
|
+
},
|
|
1046
|
+
});
|
|
1047
|
+
const tdd = defineCommand({
|
|
1048
|
+
meta: { name: 'tdd', description: 'TDD vertical slice workflows' },
|
|
1049
|
+
subCommands: { slice: tddSliceCommand },
|
|
782
1050
|
});
|
|
783
1051
|
// ============================================================================
|
|
784
1052
|
// stats
|
|
@@ -1132,6 +1400,10 @@ const preflight = defineCommand({
|
|
|
1132
1400
|
profile: args.profile,
|
|
1133
1401
|
service: args.service,
|
|
1134
1402
|
});
|
|
1403
|
+
const commandTargetsSkipped = shouldSkipPreflightCommandTargets(resolved, args);
|
|
1404
|
+
if (commandTargetsSkipped) {
|
|
1405
|
+
resolved.warnings.push('No verification services or profile commands configured; command gates skipped for this governance-only project.');
|
|
1406
|
+
}
|
|
1135
1407
|
const engineeringStandards = evaluateEngineeringStandardsGate({
|
|
1136
1408
|
policy: resolved.policy,
|
|
1137
1409
|
});
|
|
@@ -1151,7 +1423,7 @@ const preflight = defineCommand({
|
|
|
1151
1423
|
console.log(' Engineering standards: skipped');
|
|
1152
1424
|
}
|
|
1153
1425
|
}
|
|
1154
|
-
for (const target of resolved.targets) {
|
|
1426
|
+
for (const target of commandTargetsSkipped ? [] : resolved.targets) {
|
|
1155
1427
|
if (!args.json) {
|
|
1156
1428
|
const label = target.service ? `${target.service.name} (${target.service.path})` : 'root';
|
|
1157
1429
|
console.log(`\n Target: ${label}`);
|
|
@@ -1181,8 +1453,7 @@ const preflight = defineCommand({
|
|
|
1181
1453
|
}
|
|
1182
1454
|
}
|
|
1183
1455
|
}
|
|
1184
|
-
const passed = targetResults.length
|
|
1185
|
-
targetResults.every(target => target.passed) &&
|
|
1456
|
+
const passed = (targetResults.length === 0 || targetResults.every(target => target.passed)) &&
|
|
1186
1457
|
!engineeringStandards.blocked;
|
|
1187
1458
|
const result = {
|
|
1188
1459
|
phase: 'PREFLIGHT',
|
|
@@ -1193,6 +1464,7 @@ const preflight = defineCommand({
|
|
|
1193
1464
|
policy: resolved.policy,
|
|
1194
1465
|
engineeringStandards,
|
|
1195
1466
|
targets: targetResults,
|
|
1467
|
+
commandTargetsSkipped,
|
|
1196
1468
|
passed,
|
|
1197
1469
|
};
|
|
1198
1470
|
if (args.json) {
|
|
@@ -1224,6 +1496,9 @@ const status = defineCommand({
|
|
|
1224
1496
|
const latestReviews = reviewStore.listReviews(5);
|
|
1225
1497
|
const latestTask = tasks[0];
|
|
1226
1498
|
const taskPayload = latestTask?.payload;
|
|
1499
|
+
const workflowState = new WorkflowArtifactWriter(SCALE_DIR).readCurrentState();
|
|
1500
|
+
const currentOpenTasks = workflowState?.taskId === latestTask?.id ? workflowState.openTasks ?? [] : [];
|
|
1501
|
+
const nextOpenTask = nextWorkflowOpenTask(currentOpenTasks);
|
|
1227
1502
|
const blockers = [];
|
|
1228
1503
|
const latestBlockingEvidence = latestEvidence.find(record => !record.passed);
|
|
1229
1504
|
const latestBlockingReview = latestReviews.find(record => !record.passed);
|
|
@@ -1244,6 +1519,10 @@ const status = defineCommand({
|
|
|
1244
1519
|
return `scale plan ${specs[0].id}`;
|
|
1245
1520
|
if (!latestTask)
|
|
1246
1521
|
return `scale build ${plans[0].id}`;
|
|
1522
|
+
if (nextOpenTask?.kind === 'command')
|
|
1523
|
+
return nextOpenTask.value;
|
|
1524
|
+
if (nextOpenTask?.kind === 'blocker')
|
|
1525
|
+
return `Resolve workflow blocker: ${nextOpenTask.value}`;
|
|
1247
1526
|
if (!taskPayload?.verificationEvidenceIds?.length)
|
|
1248
1527
|
return `scale verify ${latestTask.id}`;
|
|
1249
1528
|
if (latestTask.status !== 'COMPLETED')
|
|
@@ -1285,6 +1564,14 @@ const status = defineCommand({
|
|
|
1285
1564
|
summary: record.summary,
|
|
1286
1565
|
createdAt: record.createdAt,
|
|
1287
1566
|
})),
|
|
1567
|
+
workflowState: workflowState ? {
|
|
1568
|
+
taskId: workflowState.taskId,
|
|
1569
|
+
level: workflowState.level,
|
|
1570
|
+
phase: workflowState.phase,
|
|
1571
|
+
artifactsDir: workflowState.artifactsDir,
|
|
1572
|
+
openTasks: workflowState.openTasks,
|
|
1573
|
+
skillIntents: workflowState.skillIntents,
|
|
1574
|
+
} : null,
|
|
1288
1575
|
blockers,
|
|
1289
1576
|
nextCommand,
|
|
1290
1577
|
};
|
|
@@ -1314,6 +1601,11 @@ const status = defineCommand({
|
|
|
1314
1601
|
for (const blocker of blockers)
|
|
1315
1602
|
console.log(` - ${blocker}`);
|
|
1316
1603
|
}
|
|
1604
|
+
if (result.workflowState?.openTasks.length) {
|
|
1605
|
+
console.log('\nOpen Tasks:');
|
|
1606
|
+
for (const task of result.workflowState.openTasks)
|
|
1607
|
+
console.log(` - ${task}`);
|
|
1608
|
+
}
|
|
1317
1609
|
console.log(`\nNext: ${nextCommand}`);
|
|
1318
1610
|
},
|
|
1319
1611
|
});
|
|
@@ -1423,7 +1715,7 @@ const init = defineCommand({
|
|
|
1423
1715
|
const detection = qsResult.success ? undefined : detectPlatform(args.dir);
|
|
1424
1716
|
console.log(JSON.stringify({
|
|
1425
1717
|
ok: qsResult.success,
|
|
1426
|
-
mode: 'quick',
|
|
1718
|
+
mode: qsResult.success && !qsResult.platform ? 'governance-only' : 'quick',
|
|
1427
1719
|
platform: qsResult.platform,
|
|
1428
1720
|
created: qsResult.created,
|
|
1429
1721
|
skipped: qsResult.skipped,
|
|
@@ -1435,8 +1727,11 @@ const init = defineCommand({
|
|
|
1435
1727
|
}, null, 2));
|
|
1436
1728
|
return;
|
|
1437
1729
|
}
|
|
1438
|
-
if (qsResult.success
|
|
1439
|
-
|
|
1730
|
+
if (qsResult.success) {
|
|
1731
|
+
if (!qsResult.platform)
|
|
1732
|
+
console.log(`\nSCALE governance templates initialized`);
|
|
1733
|
+
else
|
|
1734
|
+
console.log(`\n✅ SCALE Engine Quick Start completed for ${qsResult.platform}`);
|
|
1440
1735
|
console.log(`\n📁 Created (${qsResult.created.length}):`);
|
|
1441
1736
|
for (const f of qsResult.created)
|
|
1442
1737
|
console.log(` + ${f}`);
|
|
@@ -1790,6 +2085,182 @@ const standards = defineCommand({
|
|
|
1790
2085
|
subCommands: { scan: standardsScan, doctor: standardsDoctor, settle: standardsSettle, baseline: standardsBaseline },
|
|
1791
2086
|
});
|
|
1792
2087
|
// ============================================================================
|
|
2088
|
+
// artifact command - Derived HTML artifacts for human review
|
|
2089
|
+
// ============================================================================
|
|
2090
|
+
const artifactRender = defineCommand({
|
|
2091
|
+
meta: { name: 'render', description: 'Render a task Markdown source set into a governed HTML artifact' },
|
|
2092
|
+
args: {
|
|
2093
|
+
dir: { type: 'string', default: '.', description: 'Project directory' },
|
|
2094
|
+
'task-id': { type: 'string', description: 'Task id under docs/worklog/tasks' },
|
|
2095
|
+
'artifact-dir': { type: 'string', description: 'Task artifact directory override' },
|
|
2096
|
+
type: { type: 'string', default: 'release-report', description: 'HTML artifact type' },
|
|
2097
|
+
source: { type: 'string', description: 'Comma or newline separated source Markdown files relative to the task directory' },
|
|
2098
|
+
theme: { type: 'string', default: 'auto', description: 'Theme mode: dark/light/auto' },
|
|
2099
|
+
lang: { type: 'string', default: 'zh', description: 'HTML language: zh/en' },
|
|
2100
|
+
title: { type: 'string', description: 'HTML document title override' },
|
|
2101
|
+
json: { type: 'boolean', default: false, description: 'Print JSON output' },
|
|
2102
|
+
},
|
|
2103
|
+
run({ args }) {
|
|
2104
|
+
const result = renderHtmlArtifact({
|
|
2105
|
+
projectDir: args.dir,
|
|
2106
|
+
taskId: args['task-id'],
|
|
2107
|
+
artifactDir: args['artifact-dir'],
|
|
2108
|
+
type: String(args.type ?? 'release-report'),
|
|
2109
|
+
sourcePaths: splitChangedFiles(typeof args.source === 'string' ? args.source : undefined),
|
|
2110
|
+
theme: normalizeThemeArg(args.theme),
|
|
2111
|
+
lang: normalizeLangArg(args.lang),
|
|
2112
|
+
title: typeof args.title === 'string' ? args.title : undefined,
|
|
2113
|
+
});
|
|
2114
|
+
if (args.json) {
|
|
2115
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2116
|
+
return;
|
|
2117
|
+
}
|
|
2118
|
+
console.log('SCALE HTML Artifact Render');
|
|
2119
|
+
console.log(` Type: ${result.type}`);
|
|
2120
|
+
console.log(` HTML: ${result.outputPath}`);
|
|
2121
|
+
console.log(` Index: ${result.indexPath}`);
|
|
2122
|
+
console.log(` Manifest: ${result.manifestPath}`);
|
|
2123
|
+
if (result.missingSources.length > 0) {
|
|
2124
|
+
console.log(` Missing sources: ${result.missingSources.join(', ')}`);
|
|
2125
|
+
}
|
|
2126
|
+
},
|
|
2127
|
+
});
|
|
2128
|
+
const artifactDoctor = defineCommand({
|
|
2129
|
+
meta: { name: 'doctor', description: 'Check HTML artifacts for traceability, stale sources, remote assets, and secret-like content' },
|
|
2130
|
+
args: {
|
|
2131
|
+
dir: { type: 'string', default: '.', description: 'Project directory' },
|
|
2132
|
+
'task-id': { type: 'string', description: 'Task id under docs/worklog/tasks' },
|
|
2133
|
+
'artifact-dir': { type: 'string', description: 'Task artifact directory override' },
|
|
2134
|
+
type: { type: 'string', description: 'Optional HTML artifact type to check' },
|
|
2135
|
+
json: { type: 'boolean', default: false, description: 'Print JSON output' },
|
|
2136
|
+
},
|
|
2137
|
+
run({ args }) {
|
|
2138
|
+
const report = doctorHtmlArtifacts({
|
|
2139
|
+
projectDir: args.dir,
|
|
2140
|
+
taskId: args['task-id'],
|
|
2141
|
+
artifactDir: args['artifact-dir'],
|
|
2142
|
+
type: typeof args.type === 'string' ? args.type : undefined,
|
|
2143
|
+
});
|
|
2144
|
+
if (args.json) {
|
|
2145
|
+
console.log(JSON.stringify(report, null, 2));
|
|
2146
|
+
if (!report.ok)
|
|
2147
|
+
process.exitCode = 1;
|
|
2148
|
+
return;
|
|
2149
|
+
}
|
|
2150
|
+
console.log(`SCALE HTML Artifact Doctor: ${report.ok ? 'OK' : 'FAILED'}`);
|
|
2151
|
+
console.log(` Manifest: ${report.manifestPath}`);
|
|
2152
|
+
console.log(` Artifacts: ${report.artifacts.length}`);
|
|
2153
|
+
if (report.findings.length === 0) {
|
|
2154
|
+
console.log(' No HTML artifact findings.');
|
|
2155
|
+
}
|
|
2156
|
+
else {
|
|
2157
|
+
for (const finding of report.findings) {
|
|
2158
|
+
const path = finding.path ? ` ${finding.path}` : '';
|
|
2159
|
+
console.log(` [${finding.severity.toUpperCase()}] ${finding.code}${path}: ${finding.message}`);
|
|
2160
|
+
if (finding.fix)
|
|
2161
|
+
console.log(` fix: ${finding.fix}`);
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
if (!report.ok)
|
|
2165
|
+
process.exitCode = 1;
|
|
2166
|
+
},
|
|
2167
|
+
});
|
|
2168
|
+
const artifactSettle = defineCommand({
|
|
2169
|
+
meta: { name: 'settle', description: 'Record HTML artifact settlement evidence for a task' },
|
|
2170
|
+
args: {
|
|
2171
|
+
dir: { type: 'string', default: '.', description: 'Project directory' },
|
|
2172
|
+
'task-id': { type: 'string', description: 'Task id under docs/worklog/tasks' },
|
|
2173
|
+
'artifact-dir': { type: 'string', description: 'Task artifact directory override' },
|
|
2174
|
+
json: { type: 'boolean', default: false, description: 'Print JSON output' },
|
|
2175
|
+
},
|
|
2176
|
+
run({ args }) {
|
|
2177
|
+
const report = settleHtmlArtifacts({
|
|
2178
|
+
projectDir: args.dir,
|
|
2179
|
+
taskId: args['task-id'],
|
|
2180
|
+
artifactDir: args['artifact-dir'],
|
|
2181
|
+
});
|
|
2182
|
+
if (args.json) {
|
|
2183
|
+
console.log(JSON.stringify(report, null, 2));
|
|
2184
|
+
if (!report.ok)
|
|
2185
|
+
process.exitCode = 1;
|
|
2186
|
+
return;
|
|
2187
|
+
}
|
|
2188
|
+
console.log(`SCALE HTML Artifact Settlement: ${report.ok ? 'OK' : 'FAILED'}`);
|
|
2189
|
+
console.log(` HTML impact: ${report.htmlImpactPath}`);
|
|
2190
|
+
for (const finding of report.doctor.findings) {
|
|
2191
|
+
const path = finding.path ? ` ${finding.path}` : '';
|
|
2192
|
+
console.log(` [${finding.severity.toUpperCase()}] ${finding.code}${path}: ${finding.message}`);
|
|
2193
|
+
}
|
|
2194
|
+
if (!report.ok)
|
|
2195
|
+
process.exitCode = 1;
|
|
2196
|
+
},
|
|
2197
|
+
});
|
|
2198
|
+
const artifactOpen = defineCommand({
|
|
2199
|
+
meta: { name: 'open', description: 'Open or print the local file URL for a rendered HTML artifact' },
|
|
2200
|
+
args: {
|
|
2201
|
+
dir: { type: 'string', default: '.', description: 'Project directory' },
|
|
2202
|
+
'task-id': { type: 'string', description: 'Task id under docs/worklog/tasks' },
|
|
2203
|
+
'artifact-dir': { type: 'string', description: 'Task artifact directory override' },
|
|
2204
|
+
type: { type: 'string', description: 'Optional HTML artifact type to open' },
|
|
2205
|
+
'print-only': { type: 'boolean', default: false, description: 'Only print the file URL without launching a browser' },
|
|
2206
|
+
json: { type: 'boolean', default: false, description: 'Print JSON output' },
|
|
2207
|
+
},
|
|
2208
|
+
run({ args }) {
|
|
2209
|
+
const path = resolveHtmlArtifactForOpen({
|
|
2210
|
+
projectDir: args.dir,
|
|
2211
|
+
taskId: args['task-id'],
|
|
2212
|
+
artifactDir: args['artifact-dir'],
|
|
2213
|
+
type: typeof args.type === 'string' ? args.type : undefined,
|
|
2214
|
+
});
|
|
2215
|
+
const url = pathToFileURL(path).toString();
|
|
2216
|
+
const exists = existsSync(path);
|
|
2217
|
+
if (!args['print-only'] && exists)
|
|
2218
|
+
launchLocalFile(path);
|
|
2219
|
+
const output = { ok: exists, path, url, launched: Boolean(!args['print-only'] && exists) };
|
|
2220
|
+
if (args.json) {
|
|
2221
|
+
console.log(JSON.stringify(output, null, 2));
|
|
2222
|
+
if (!exists)
|
|
2223
|
+
process.exitCode = 1;
|
|
2224
|
+
return;
|
|
2225
|
+
}
|
|
2226
|
+
if (!exists) {
|
|
2227
|
+
console.log(`HTML artifact not found: ${path}`);
|
|
2228
|
+
process.exitCode = 1;
|
|
2229
|
+
return;
|
|
2230
|
+
}
|
|
2231
|
+
console.log(url);
|
|
2232
|
+
},
|
|
2233
|
+
});
|
|
2234
|
+
const artifact = defineCommand({
|
|
2235
|
+
meta: { name: 'artifact', description: 'Derived HTML artifact rendering and safety checks' },
|
|
2236
|
+
subCommands: { render: artifactRender, doctor: artifactDoctor, settle: artifactSettle, open: artifactOpen },
|
|
2237
|
+
});
|
|
2238
|
+
function normalizeThemeArg(value) {
|
|
2239
|
+
const normalized = String(value ?? 'auto').trim().toLowerCase();
|
|
2240
|
+
if (normalized === 'dark' || normalized === 'light' || normalized === 'auto')
|
|
2241
|
+
return normalized;
|
|
2242
|
+
return 'auto';
|
|
2243
|
+
}
|
|
2244
|
+
function normalizeLangArg(value) {
|
|
2245
|
+
return String(value ?? 'zh').trim().toLowerCase() === 'en' ? 'en' : 'zh';
|
|
2246
|
+
}
|
|
2247
|
+
function launchLocalFile(path) {
|
|
2248
|
+
try {
|
|
2249
|
+
if (process.platform === 'win32') {
|
|
2250
|
+
execFileSync('cmd', ['/c', 'start', '', path], { stdio: 'ignore' });
|
|
2251
|
+
}
|
|
2252
|
+
else if (process.platform === 'darwin') {
|
|
2253
|
+
execFileSync('open', [path], { stdio: 'ignore' });
|
|
2254
|
+
}
|
|
2255
|
+
else {
|
|
2256
|
+
execFileSync('xdg-open', [path], { stdio: 'ignore' });
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
catch {
|
|
2260
|
+
// Opening is convenience-only; artifact doctor/render remains the source of truth.
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
// ============================================================================
|
|
1793
2264
|
// evolve command
|
|
1794
2265
|
// ============================================================================
|
|
1795
2266
|
const evolve = defineCommand({
|
|
@@ -2187,6 +2658,7 @@ const skillCheckCommand = defineCommand({
|
|
|
2187
2658
|
artifactsDir: args.dir ?? state?.artifactsDir,
|
|
2188
2659
|
level,
|
|
2189
2660
|
requiredArtifacts: state?.requiredSkillArtifacts,
|
|
2661
|
+
requiredSkills: state?.requiredSkills,
|
|
2190
2662
|
mode: state?.skillRoutingMode ?? policy.policy.mode,
|
|
2191
2663
|
enforceLevels: policy.policy.enforceLevels,
|
|
2192
2664
|
});
|
|
@@ -2480,6 +2952,16 @@ const toolRunCommand = defineCommand({
|
|
|
2480
2952
|
const report = await result.orchestrator.run(result.plan, {
|
|
2481
2953
|
dryRun: isTruthyFlag(args['dry-run']),
|
|
2482
2954
|
});
|
|
2955
|
+
if (toolEvidenceRunCompletesOpenTask(report)) {
|
|
2956
|
+
const writer = new WorkflowArtifactWriter(SCALE_DIR);
|
|
2957
|
+
const current = writer.readCurrentState();
|
|
2958
|
+
if (current?.taskId === report.taskId) {
|
|
2959
|
+
writer.updateCurrentState({
|
|
2960
|
+
taskId: report.taskId,
|
|
2961
|
+
openTasks: removeWorkflowOpenTask(current.openTasks, 'tool-evidence'),
|
|
2962
|
+
});
|
|
2963
|
+
}
|
|
2964
|
+
}
|
|
2483
2965
|
if (args.json) {
|
|
2484
2966
|
console.log(JSON.stringify(report, null, 2));
|
|
2485
2967
|
}
|
|
@@ -2729,6 +3211,7 @@ const main = defineCommand({
|
|
|
2729
3211
|
stats,
|
|
2730
3212
|
preflight,
|
|
2731
3213
|
governance,
|
|
3214
|
+
artifact,
|
|
2732
3215
|
assets,
|
|
2733
3216
|
standards,
|
|
2734
3217
|
metrics,
|
|
@@ -2737,6 +3220,8 @@ const main = defineCommand({
|
|
|
2737
3220
|
status,
|
|
2738
3221
|
workflow,
|
|
2739
3222
|
evidence,
|
|
3223
|
+
diagnose,
|
|
3224
|
+
tdd,
|
|
2740
3225
|
tool,
|
|
2741
3226
|
skill,
|
|
2742
3227
|
skills: skill,
|