@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
|
@@ -20,8 +20,15 @@ import { EvidenceStore } from '../workflow/EvidenceStore.js';
|
|
|
20
20
|
import { ReviewStore } from '../workflow/ReviewStore.js';
|
|
21
21
|
import { TaskMetricsStore } from '../workflow/TaskMetricsStore.js';
|
|
22
22
|
import { appendVerificationArtifact, checkTaskArtifactCompleteness, scaffoldTaskArtifacts } from '../workflow/TaskArtifactScaffolder.js';
|
|
23
|
+
import { createWorkflowGuidance, renderWorkflowGuidance } from '../workflow/WorkflowGuidance.js';
|
|
24
|
+
import { blockingWorkflowOpenTasks, removeWorkflowOpenTask } from '../workflow/WorkflowOpenTasks.js';
|
|
23
25
|
import { doctorEngineeringStandards, settleEngineeringStandards } from '../workflow/EngineeringStandards.js';
|
|
24
26
|
import { analyzeReview, parseChangedFiles, shouldReviewFile, summarizeFindings } from '../workflow/ReviewAnalyzer.js';
|
|
27
|
+
import { inspectWorkspaceLifecycle } from '../workflow/WorkspaceLifecycle.js';
|
|
28
|
+
import { evaluateToolEvidenceGate } from '../tools/ToolEvidenceGate.js';
|
|
29
|
+
import { ToolEvidenceStore } from '../tools/ToolEvidenceStore.js';
|
|
30
|
+
import { ToolOrchestrator } from '../tools/ToolOrchestrator.js';
|
|
31
|
+
import { loadToolPolicy } from '../tools/ToolPolicy.js';
|
|
25
32
|
import { join } from 'node:path';
|
|
26
33
|
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
27
34
|
import { HTMLDocumentRenderer } from '../output/HTMLDocumentRenderer.js';
|
|
@@ -226,6 +233,49 @@ function evaluateEngineeringStandardsGate(options) {
|
|
|
226
233
|
function normalizeEngineeringStandardsGateMode(value) {
|
|
227
234
|
return value === 'off' || value === 'block' ? value : 'warn';
|
|
228
235
|
}
|
|
236
|
+
function normalizeToolGateMode(value) {
|
|
237
|
+
if (value === undefined || value === null || value === '')
|
|
238
|
+
return undefined;
|
|
239
|
+
const normalized = String(value).trim().toLowerCase();
|
|
240
|
+
if (normalized === 'warn')
|
|
241
|
+
return 'advisory';
|
|
242
|
+
if (normalized === 'off' || normalized === 'advisory' || normalized === 'evidence-required' || normalized === 'block')
|
|
243
|
+
return normalized;
|
|
244
|
+
throw new Error(`Invalid tool gate mode "${String(value)}"; expected off, advisory, evidence-required, or block.`);
|
|
245
|
+
}
|
|
246
|
+
function resolvePhaseToolGateMode(options) {
|
|
247
|
+
if (isTruthyFlag(options.requireEvidence))
|
|
248
|
+
return 'evidence-required';
|
|
249
|
+
const cliMode = normalizeToolGateMode(options.cliMode);
|
|
250
|
+
if (cliMode)
|
|
251
|
+
return cliMode;
|
|
252
|
+
return options.policy.mode === 'block' ? 'block' : 'off';
|
|
253
|
+
}
|
|
254
|
+
function evaluateTaskToolEvidenceGate(options) {
|
|
255
|
+
if (!options.level || !options.skillPlan)
|
|
256
|
+
return undefined;
|
|
257
|
+
const policy = loadToolPolicy(PROJECT_DIR, SCALE_DIR);
|
|
258
|
+
const mode = resolvePhaseToolGateMode({
|
|
259
|
+
cliMode: options.cliMode,
|
|
260
|
+
requireEvidence: options.requireEvidence,
|
|
261
|
+
policy,
|
|
262
|
+
});
|
|
263
|
+
const effectivePolicy = { ...policy, mode };
|
|
264
|
+
const evidenceStore = new ToolEvidenceStore({ projectDir: PROJECT_DIR, scaleDir: SCALE_DIR });
|
|
265
|
+
const plan = new ToolOrchestrator({
|
|
266
|
+
projectDir: PROJECT_DIR,
|
|
267
|
+
policy: effectivePolicy,
|
|
268
|
+
evidenceStore,
|
|
269
|
+
}).plan({ skillPlan: options.skillPlan });
|
|
270
|
+
return evaluateToolEvidenceGate({
|
|
271
|
+
projectDir: PROJECT_DIR,
|
|
272
|
+
level: options.level,
|
|
273
|
+
plan,
|
|
274
|
+
evidenceStore,
|
|
275
|
+
mode,
|
|
276
|
+
allowSkipped: isTruthyFlag(options.allowSkipped),
|
|
277
|
+
});
|
|
278
|
+
}
|
|
229
279
|
async function countChangedFiles(taskPayload) {
|
|
230
280
|
const filesInvolved = taskPayload.filesInvolved ?? [];
|
|
231
281
|
if (filesInvolved.length > 0)
|
|
@@ -729,6 +779,15 @@ export const phaseBuild = defineCommand({
|
|
|
729
779
|
skillPlan,
|
|
730
780
|
});
|
|
731
781
|
}
|
|
782
|
+
const workflowGuidance = createWorkflowGuidance({
|
|
783
|
+
taskId: task.id,
|
|
784
|
+
description: taskPayloadWithSkills.description,
|
|
785
|
+
level: workflowLevel,
|
|
786
|
+
artifactDir: taskArtifacts?.relativeDir,
|
|
787
|
+
files: taskPayloadWithSkills.filesInvolved,
|
|
788
|
+
skillIntents: taskPayloadWithSkills.skillIntents,
|
|
789
|
+
requiredSkillVerification: taskPayloadWithSkills.requiredSkillVerification,
|
|
790
|
+
});
|
|
732
791
|
new WorkflowArtifactWriter(SCALE_DIR).updateCurrentState({
|
|
733
792
|
taskId: task.id,
|
|
734
793
|
level: workflowLevel,
|
|
@@ -743,6 +802,7 @@ export const phaseBuild = defineCommand({
|
|
|
743
802
|
recommendedSkills: skillPlan.recommendedSkills,
|
|
744
803
|
requiredSkillArtifacts: skillPlan.requiredArtifacts,
|
|
745
804
|
requiredSkillVerification: skillPlan.requiredVerification,
|
|
805
|
+
openTasks: workflowGuidance.items.filter(item => item.required).map(item => item.command),
|
|
746
806
|
});
|
|
747
807
|
// FSM transitions: PENDING -> READY -> RUNNING
|
|
748
808
|
// Phase 1: schedule (PENDING -> READY) - no guards
|
|
@@ -765,7 +825,7 @@ export const phaseBuild = defineCommand({
|
|
|
765
825
|
if (implResult.allowed) {
|
|
766
826
|
await fsm.transition(args['plan-id'], 'implement', { actor: { kind: 'system', component: 'phase-build' } });
|
|
767
827
|
}
|
|
768
|
-
const result = { phase: 'BUILD', task: { ...task, payload: taskPayloadWithSkills }, status: 'RUNNING', artifactDir: taskArtifacts?.relativeDir, artifactFiles: taskArtifacts?.created ?? [], skillPlan };
|
|
828
|
+
const result = { phase: 'BUILD', task: { ...task, payload: taskPayloadWithSkills }, status: 'RUNNING', artifactDir: taskArtifacts?.relativeDir, artifactFiles: taskArtifacts?.created ?? [], skillPlan, workflowGuidance };
|
|
769
829
|
if (args.json)
|
|
770
830
|
console.log(JSON.stringify(result, null, 2));
|
|
771
831
|
else {
|
|
@@ -780,7 +840,8 @@ export const phaseBuild = defineCommand({
|
|
|
780
840
|
console.log(` Recommended skills: ${skillPlan.recommendedSkills.join(', ')}`);
|
|
781
841
|
if (taskArtifacts?.relativeDir)
|
|
782
842
|
console.log(` Artifacts: ${taskArtifacts.relativeDir}`);
|
|
783
|
-
console.log(`\n
|
|
843
|
+
console.log(`\n${renderWorkflowGuidance(workflowGuidance)}`);
|
|
844
|
+
console.log('');
|
|
784
845
|
}
|
|
785
846
|
},
|
|
786
847
|
});
|
|
@@ -809,6 +870,9 @@ export const phaseVerify = defineCommand({
|
|
|
809
870
|
'artifact-gate': { type: 'string', description: 'Task artifact policy override: off, warn, or block' },
|
|
810
871
|
'require-artifacts': { type: 'boolean', default: false, description: 'Fail verification when required M/L/CRITICAL artifacts are incomplete' },
|
|
811
872
|
'require-installed-skills': { type: 'boolean', default: false, description: 'Fail verification when required workflow skills are not installed locally' },
|
|
873
|
+
'tool-gate': { type: 'string', description: 'Tool evidence policy override: off, advisory, evidence-required, or block' },
|
|
874
|
+
'require-tool-evidence': { type: 'boolean', default: false, description: 'Fail verification when required tool execution evidence is missing or skipped' },
|
|
875
|
+
'allow-skipped-tool-evidence': { type: 'boolean', default: false, description: 'Allow skipped/manual fallback tool evidence to satisfy the tool gate' },
|
|
812
876
|
'tdd-evidence': { type: 'string', description: 'Path to JSON TDD evidence with red/green/refactor/testFirst=true' },
|
|
813
877
|
'tdd-strict': { type: 'boolean', default: false, description: 'Require TDD evidence before other gates' },
|
|
814
878
|
'residual-risk': { type: 'string', description: 'Residual risk statement to record in task metrics' },
|
|
@@ -945,7 +1009,8 @@ export const phaseVerify = defineCommand({
|
|
|
945
1009
|
verifiedAt: Date.now(),
|
|
946
1010
|
};
|
|
947
1011
|
await store.update(args['task-id'], { payload: updatedPayload });
|
|
948
|
-
const
|
|
1012
|
+
const workflowWriter = new WorkflowArtifactWriter(SCALE_DIR);
|
|
1013
|
+
let workflowState = workflowWriter.updateCurrentState({
|
|
949
1014
|
taskId: args['task-id'],
|
|
950
1015
|
phase: 'verify',
|
|
951
1016
|
lastTaskId: args['task-id'],
|
|
@@ -986,8 +1051,17 @@ export const phaseVerify = defineCommand({
|
|
|
986
1051
|
const requireInstalledSkills = isTruthyFlag(args['require-installed-skills']);
|
|
987
1052
|
const skillInstallation = inspectRequiredWorkflowSkills(updatedPayload.requiredSkills ?? [], { projectDir: PROJECT_DIR });
|
|
988
1053
|
const skillInstallationBlocked = requireInstalledSkills && !skillInstallation.ok;
|
|
1054
|
+
const toolEvidenceGate = evaluateTaskToolEvidenceGate({
|
|
1055
|
+
skillPlan: verificationSkillPlan,
|
|
1056
|
+
level: metricLevel,
|
|
1057
|
+
cliMode: args['tool-gate'],
|
|
1058
|
+
requireEvidence: args['require-tool-evidence'],
|
|
1059
|
+
allowSkipped: args['allow-skipped-tool-evidence'],
|
|
1060
|
+
});
|
|
1061
|
+
const workflowOpenTaskBlockers = blockingWorkflowOpenTasks(workflowState.openTasks, args['task-id']);
|
|
1062
|
+
const workflowOpenTasksBlocked = workflowOpenTaskBlockers.length > 0;
|
|
989
1063
|
// Attempt FSM transition to COMPLETED
|
|
990
|
-
// Guards: build_passed, lint_passed, tests_passed, and optional artifact policy.
|
|
1064
|
+
// Guards: build_passed, lint_passed, tests_passed, open workflow tasks, and optional artifact policy.
|
|
991
1065
|
const codePassed = results.buildStatus === 'success' &&
|
|
992
1066
|
(results.buildExitCode ?? 1) === 0 &&
|
|
993
1067
|
results.lintStatus === 'success' &&
|
|
@@ -998,7 +1072,9 @@ export const phaseVerify = defineCommand({
|
|
|
998
1072
|
!artifactGate.blocked &&
|
|
999
1073
|
!(skillGate?.blocked ?? false) &&
|
|
1000
1074
|
!skillInstallationBlocked &&
|
|
1001
|
-
!engineeringStandards.blocked
|
|
1075
|
+
!engineeringStandards.blocked &&
|
|
1076
|
+
!(toolEvidenceGate?.blocked ?? false) &&
|
|
1077
|
+
!workflowOpenTasksBlocked;
|
|
1002
1078
|
let transitionResult = null;
|
|
1003
1079
|
if (completionEligible) {
|
|
1004
1080
|
const completeResult = await fsm.canTransition(args['task-id'], 'complete');
|
|
@@ -1033,7 +1109,18 @@ export const phaseVerify = defineCommand({
|
|
|
1033
1109
|
else if (!args.json && engineeringStandards.blocked) {
|
|
1034
1110
|
console.log('\n Engineering standards gate blocked completion - fix blocking standards findings');
|
|
1035
1111
|
}
|
|
1112
|
+
else if (!args.json && toolEvidenceGate?.blocked) {
|
|
1113
|
+
console.log('\n Tool evidence gate blocked completion - required tools need passed execution evidence');
|
|
1114
|
+
}
|
|
1115
|
+
else if (!args.json && workflowOpenTasksBlocked) {
|
|
1116
|
+
console.log('\n Workflow open tasks blocked completion - finish required workflow commands first');
|
|
1117
|
+
}
|
|
1036
1118
|
const passed = completionEligible && (transitionResult?.success ?? false);
|
|
1119
|
+
if (passed) {
|
|
1120
|
+
workflowState = workflowWriter.updateCurrentState({
|
|
1121
|
+
openTasks: removeWorkflowOpenTask(workflowState.openTasks, 'verification'),
|
|
1122
|
+
});
|
|
1123
|
+
}
|
|
1037
1124
|
const verificationArtifactPath = appendVerificationArtifact({
|
|
1038
1125
|
projectDir: PROJECT_DIR,
|
|
1039
1126
|
artifactsDir: workflowState.artifactsDir,
|
|
@@ -1062,16 +1149,27 @@ export const phaseVerify = defineCommand({
|
|
|
1062
1149
|
enforceLevels: skillPolicy.policy.enforceLevels,
|
|
1063
1150
|
})
|
|
1064
1151
|
: skillGate;
|
|
1152
|
+
const finalToolEvidenceGate = evaluateTaskToolEvidenceGate({
|
|
1153
|
+
skillPlan: verificationSkillPlan,
|
|
1154
|
+
level: metricLevel,
|
|
1155
|
+
cliMode: args['tool-gate'],
|
|
1156
|
+
requireEvidence: args['require-tool-evidence'],
|
|
1157
|
+
allowSkipped: args['allow-skipped-tool-evidence'],
|
|
1158
|
+
}) ?? toolEvidenceGate;
|
|
1065
1159
|
const finalPayload = {
|
|
1066
1160
|
...updatedPayload,
|
|
1067
1161
|
artifactGateMode: finalArtifactGate.mode,
|
|
1068
1162
|
artifactGatePassed: !finalArtifactGate.blocked,
|
|
1069
1163
|
artifactComplete: artifactCheck?.complete,
|
|
1070
1164
|
skillGatePassed: finalSkillGate ? !finalSkillGate.blocked && !skillInstallationBlocked : !skillInstallationBlocked,
|
|
1165
|
+
toolOrchestrationMode: finalToolEvidenceGate?.mode,
|
|
1166
|
+
requiredTools: finalToolEvidenceGate?.requiredTools,
|
|
1167
|
+
toolEvidenceIds: finalToolEvidenceGate?.passed.map(item => item.evidenceId).filter((id) => Boolean(id)),
|
|
1168
|
+
toolEvidenceGatePassed: finalToolEvidenceGate ? !finalToolEvidenceGate.blocked : true,
|
|
1071
1169
|
};
|
|
1072
1170
|
await store.update(args['task-id'], { payload: finalPayload });
|
|
1073
1171
|
const metricGateStatus = codePassed &&
|
|
1074
|
-
(finalArtifactGate.blocked || finalSkillGate?.blocked || skillInstallationBlocked || engineeringStandards.blocked)
|
|
1172
|
+
(finalArtifactGate.blocked || finalSkillGate?.blocked || skillInstallationBlocked || engineeringStandards.blocked || finalToolEvidenceGate?.blocked || workflowOpenTasksBlocked)
|
|
1075
1173
|
? 'blocked'
|
|
1076
1174
|
: undefined;
|
|
1077
1175
|
const metricRecord = await recordVerificationMetric({
|
|
@@ -1096,6 +1194,12 @@ export const phaseVerify = defineCommand({
|
|
|
1096
1194
|
artifactGate: finalArtifactGate,
|
|
1097
1195
|
engineeringStandards,
|
|
1098
1196
|
skillGate: finalSkillGate,
|
|
1197
|
+
toolEvidenceGate: finalToolEvidenceGate,
|
|
1198
|
+
workflowOpenTasks: {
|
|
1199
|
+
blocked: workflowOpenTasksBlocked,
|
|
1200
|
+
blockers: workflowOpenTaskBlockers,
|
|
1201
|
+
openTasks: workflowState.openTasks ?? [],
|
|
1202
|
+
},
|
|
1099
1203
|
skillInstallation: {
|
|
1100
1204
|
...skillInstallation,
|
|
1101
1205
|
checked: requireInstalledSkills,
|
|
@@ -1122,6 +1226,9 @@ export const phaseVerify = defineCommand({
|
|
|
1122
1226
|
if (engineeringStandards.blocked) {
|
|
1123
1227
|
console.log(` Engineering standards blockers: ${engineeringStandards.findings.filter(finding => finding.severity === 'fail').length}`);
|
|
1124
1228
|
}
|
|
1229
|
+
if (finalToolEvidenceGate?.blocked) {
|
|
1230
|
+
console.log(` Tool evidence gaps: ${finalToolEvidenceGate.missing.length} missing, ${finalToolEvidenceGate.failed.length} failed, ${finalToolEvidenceGate.skipped.length} skipped`);
|
|
1231
|
+
}
|
|
1125
1232
|
if (passed)
|
|
1126
1233
|
console.log(`\n Next: scale review\n`);
|
|
1127
1234
|
else
|
|
@@ -1252,6 +1359,57 @@ async function stageReviewedFiles(reviewRecords) {
|
|
|
1252
1359
|
}
|
|
1253
1360
|
return { stagedFiles, unreviewedFiles: [] };
|
|
1254
1361
|
}
|
|
1362
|
+
function isMultiRepositoryTopology(topology) {
|
|
1363
|
+
return topology === 'moe' || topology === 'submodule-workspace' || topology === 'polyrepo';
|
|
1364
|
+
}
|
|
1365
|
+
function hasChildRepositoryChange(path, childPath) {
|
|
1366
|
+
const normalized = normalizeGitPath(path);
|
|
1367
|
+
const child = normalizeGitPath(childPath).replace(/\/+$/, '');
|
|
1368
|
+
return normalized === child || normalized.startsWith(`${child}/`);
|
|
1369
|
+
}
|
|
1370
|
+
async function validateWorkspaceShipBoundary() {
|
|
1371
|
+
try {
|
|
1372
|
+
const report = await inspectWorkspaceLifecycle({ projectDir: PROJECT_DIR });
|
|
1373
|
+
const blockers = [];
|
|
1374
|
+
const warnings = [...report.topology.warnings];
|
|
1375
|
+
const policy = report.topology.finishPolicy;
|
|
1376
|
+
const rootChanges = await getReviewableGitChanges();
|
|
1377
|
+
if (!report.topology.configured && report.childRepositories.length > 0) {
|
|
1378
|
+
const changedChildRepositories = report.childRepositories
|
|
1379
|
+
.filter(child => rootChanges.some(file => hasChildRepositoryChange(file.path, child.relativePath)))
|
|
1380
|
+
.map(child => child.relativePath);
|
|
1381
|
+
const dirtyChildRepositories = report.childRepositories
|
|
1382
|
+
.filter(child => !child.clean || child.ahead > 0)
|
|
1383
|
+
.map(child => child.relativePath);
|
|
1384
|
+
const affected = Array.from(new Set([...changedChildRepositories, ...dirtyChildRepositories]));
|
|
1385
|
+
if (affected.length > 0) {
|
|
1386
|
+
blockers.push(`Workspace topology is not configured; child repository state is present at ${affected.join(', ')}. Create .scale/workspace.json with scale workspace map --write --topology moe before shipping.`);
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
if (report.topology.configured && isMultiRepositoryTopology(report.topology.topology)) {
|
|
1390
|
+
for (const child of report.childRepositories) {
|
|
1391
|
+
if (policy.requireCleanRepositories && !child.clean) {
|
|
1392
|
+
blockers.push(`Child repository ${child.relativePath} has uncommitted changes`);
|
|
1393
|
+
}
|
|
1394
|
+
if (policy.requirePushedBranches && child.upstream && child.ahead > 0) {
|
|
1395
|
+
blockers.push(`Child repository ${child.relativePath} has unpushed commits`);
|
|
1396
|
+
}
|
|
1397
|
+
if (policy.requirePushedBranches && report.topology.topology === 'moe' && !child.upstream && child.branch) {
|
|
1398
|
+
blockers.push(`Child repository ${child.relativePath} has no upstream; push or explicitly disable requirePushedBranches before shipping`);
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
return { report, blockers, warnings };
|
|
1403
|
+
}
|
|
1404
|
+
catch (error) {
|
|
1405
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1406
|
+
return {
|
|
1407
|
+
report: null,
|
|
1408
|
+
blockers: [`Workspace boundary check could not inspect Git repositories: ${message}`],
|
|
1409
|
+
warnings: [],
|
|
1410
|
+
};
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1255
1413
|
function collectTaskReviewText(taskPayload) {
|
|
1256
1414
|
if (!taskPayload)
|
|
1257
1415
|
return '';
|
|
@@ -1508,6 +1666,7 @@ export const phaseShip = defineCommand({
|
|
|
1508
1666
|
const reviewPassed = payload.reviewPassed === true && reviewValidation.ok;
|
|
1509
1667
|
const artifactGatePassed = payload.artifactGateMode !== 'block' || payload.artifactGatePassed !== false;
|
|
1510
1668
|
const skillGatePassed = payload.skillGatePassed !== false;
|
|
1669
|
+
const toolEvidenceGatePassed = payload.toolEvidenceGatePassed !== false;
|
|
1511
1670
|
if (!artifactGatePassed) {
|
|
1512
1671
|
console.error('\nTask artifact gate did not pass. Complete required task artifacts and re-run: scale verify ' + args['task-id'] + ' --artifact-gate block\n');
|
|
1513
1672
|
if (payload.artifactComplete === false) {
|
|
@@ -1519,6 +1678,13 @@ export const phaseShip = defineCommand({
|
|
|
1519
1678
|
console.error('\nTask skill gate did not pass. Complete required skill evidence artifacts and re-run: scale verify ' + args['task-id'] + '\n');
|
|
1520
1679
|
process.exit(1);
|
|
1521
1680
|
}
|
|
1681
|
+
if (!toolEvidenceGatePassed) {
|
|
1682
|
+
console.error('\nTask tool evidence gate did not pass. Run required tools and re-run: scale verify ' + args['task-id'] + ' --tool-gate ' + (payload.toolOrchestrationMode ?? 'evidence-required') + '\n');
|
|
1683
|
+
if (payload.requiredTools?.length) {
|
|
1684
|
+
console.error('Required tools: ' + payload.requiredTools.join(', '));
|
|
1685
|
+
}
|
|
1686
|
+
process.exit(1);
|
|
1687
|
+
}
|
|
1522
1688
|
if (task.status !== 'COMPLETED') {
|
|
1523
1689
|
if (!verificationPassed) {
|
|
1524
1690
|
console.error('\nTask not verified with persisted evidence. Run: scale verify ' + args['task-id'] + '\n');
|
|
@@ -1555,9 +1721,17 @@ export const phaseShip = defineCommand({
|
|
|
1555
1721
|
// Git operations
|
|
1556
1722
|
let commitHash = null;
|
|
1557
1723
|
let stagedFiles = [];
|
|
1724
|
+
let workspaceBoundary = null;
|
|
1558
1725
|
if (!shouldSkipCommit(args['skip-commit'])) {
|
|
1559
1726
|
const commitMessage = args.message ?? `feat: ${task.title ?? args['task-id']}`;
|
|
1560
1727
|
try {
|
|
1728
|
+
workspaceBoundary = await validateWorkspaceShipBoundary();
|
|
1729
|
+
if (workspaceBoundary.blockers.length > 0) {
|
|
1730
|
+
console.error('\nWorkspace boundary check failed. Resolve child repositories before shipping the root commit.');
|
|
1731
|
+
workspaceBoundary.blockers.forEach(blocker => console.error(' - ' + blocker));
|
|
1732
|
+
console.error('\nRun scale workspace finish --summary for the shortest fix list, or --json for the full workspace state.\n');
|
|
1733
|
+
process.exit(1);
|
|
1734
|
+
}
|
|
1561
1735
|
const reviewRecords = getValidatedReviewRecords(payload.reviewEvidenceIds);
|
|
1562
1736
|
const stageResult = await stageReviewedFiles(reviewRecords);
|
|
1563
1737
|
if (stageResult.unreviewedFiles.length > 0) {
|
|
@@ -1645,6 +1819,13 @@ export const phaseShip = defineCommand({
|
|
|
1645
1819
|
reviewValidation,
|
|
1646
1820
|
commitHash,
|
|
1647
1821
|
stagedFiles,
|
|
1822
|
+
workspaceBoundary: workspaceBoundary ? {
|
|
1823
|
+
topology: workspaceBoundary.report?.topology.topology ?? null,
|
|
1824
|
+
configured: workspaceBoundary.report?.topology.configured ?? false,
|
|
1825
|
+
childRepositories: workspaceBoundary.report?.childRepositories.length ?? 0,
|
|
1826
|
+
blockers: workspaceBoundary.blockers,
|
|
1827
|
+
warnings: workspaceBoundary.warnings,
|
|
1828
|
+
} : null,
|
|
1648
1829
|
};
|
|
1649
1830
|
if (args.json)
|
|
1650
1831
|
console.log(JSON.stringify(result, null, 2));
|