@hongmaple0820/scale-engine 0.16.0 → 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/api/cli.js CHANGED
@@ -35,8 +35,12 @@ 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';
@@ -69,6 +73,18 @@ function ensureDir(dir) {
69
73
  function isTruthyFlag(value) {
70
74
  return value === true || value === '' || value === 'true' || value === '1';
71
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
+ }
72
88
  function normalizePreflightProfile(value) {
73
89
  const normalized = String(value ?? 'quick').trim().toLowerCase();
74
90
  if (normalized === 'full' || normalized === 'ci')
@@ -776,9 +792,239 @@ const contextGlossary = defineCommand({
776
792
  }
777
793
  },
778
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
+ });
779
870
  const context = defineCommand({
780
871
  meta: { name: 'context', description: 'Context assembly' },
781
- 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 },
782
1028
  });
783
1029
  // ============================================================================
784
1030
  // stats
@@ -1224,6 +1470,9 @@ const status = defineCommand({
1224
1470
  const latestReviews = reviewStore.listReviews(5);
1225
1471
  const latestTask = tasks[0];
1226
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);
1227
1476
  const blockers = [];
1228
1477
  const latestBlockingEvidence = latestEvidence.find(record => !record.passed);
1229
1478
  const latestBlockingReview = latestReviews.find(record => !record.passed);
@@ -1244,6 +1493,10 @@ const status = defineCommand({
1244
1493
  return `scale plan ${specs[0].id}`;
1245
1494
  if (!latestTask)
1246
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}`;
1247
1500
  if (!taskPayload?.verificationEvidenceIds?.length)
1248
1501
  return `scale verify ${latestTask.id}`;
1249
1502
  if (latestTask.status !== 'COMPLETED')
@@ -1285,6 +1538,14 @@ const status = defineCommand({
1285
1538
  summary: record.summary,
1286
1539
  createdAt: record.createdAt,
1287
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,
1288
1549
  blockers,
1289
1550
  nextCommand,
1290
1551
  };
@@ -1314,6 +1575,11 @@ const status = defineCommand({
1314
1575
  for (const blocker of blockers)
1315
1576
  console.log(` - ${blocker}`);
1316
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
+ }
1317
1583
  console.log(`\nNext: ${nextCommand}`);
1318
1584
  },
1319
1585
  });
@@ -2480,6 +2746,16 @@ const toolRunCommand = defineCommand({
2480
2746
  const report = await result.orchestrator.run(result.plan, {
2481
2747
  dryRun: isTruthyFlag(args['dry-run']),
2482
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
+ }
2483
2759
  if (args.json) {
2484
2760
  console.log(JSON.stringify(report, null, 2));
2485
2761
  }
@@ -2737,6 +3013,8 @@ const main = defineCommand({
2737
3013
  status,
2738
3014
  workflow,
2739
3015
  evidence,
3016
+ diagnose,
3017
+ tdd,
2740
3018
  tool,
2741
3019
  skill,
2742
3020
  skills: skill,