@hongmaple0820/scale-engine 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +13 -5
- package/README.md +13 -5
- 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 +729 -5
- package/dist/api/cli.js.map +1 -1
- package/dist/api/doctor.d.ts +2 -0
- package/dist/api/doctor.js +83 -0
- package/dist/api/doctor.js.map +1 -1
- package/dist/api/mcp.js +2 -1
- package/dist/api/mcp.js.map +1 -1
- package/dist/artifact/types.d.ts +4 -0
- package/dist/artifact/types.js.map +1 -1
- package/dist/capabilities/InstalledSkillsIntegration.d.ts +3 -0
- package/dist/capabilities/InstalledSkillsIntegration.js +41 -17
- package/dist/capabilities/InstalledSkillsIntegration.js.map +1 -1
- package/dist/cli/phaseCommands.d.ts +14 -0
- package/dist/cli/phaseCommands.js +214 -5
- 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/core/logger.d.ts +2 -0
- package/dist/core/logger.js +33 -1
- package/dist/core/logger.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/output/HTMLDocumentRenderer.js +3 -2
- package/dist/output/HTMLDocumentRenderer.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/ExternalSkills.js +9 -4
- package/dist/skills/ExternalSkills.js.map +1 -1
- package/dist/skills/SkillDiscovery.js +5 -3
- package/dist/skills/SkillDiscovery.js.map +1 -1
- package/dist/skills/SkillDoctor.js +178 -1
- package/dist/skills/SkillDoctor.js.map +1 -1
- package/dist/skills/SkillInstaller.js +5 -0
- package/dist/skills/SkillInstaller.js.map +1 -1
- 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/skills/routing/SkillPolicy.js +168 -5
- package/dist/skills/routing/SkillPolicy.js.map +1 -1
- 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 +193 -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/version.d.ts +3 -0
- package/dist/version.js +15 -0
- package/dist/version.js.map +1 -0
- package/dist/workflow/EngineeringStandards.d.ts +212 -0
- package/dist/workflow/EngineeringStandards.js +1021 -0
- package/dist/workflow/EngineeringStandards.js.map +1 -0
- package/dist/workflow/GovernanceTemplatePacks.d.ts +1 -1
- package/dist/workflow/GovernanceTemplatePacks.js +101 -18
- package/dist/workflow/GovernanceTemplatePacks.js.map +1 -1
- package/dist/workflow/GovernanceTemplates.d.ts +1 -1
- package/dist/workflow/GovernanceTemplates.js +225 -37
- package/dist/workflow/GovernanceTemplates.js.map +1 -1
- package/dist/workflow/ResourceGovernance.d.ts +120 -0
- package/dist/workflow/ResourceGovernance.js +512 -0
- package/dist/workflow/ResourceGovernance.js.map +1 -0
- package/dist/workflow/TaskArtifactScaffolder.js +3 -0
- package/dist/workflow/TaskArtifactScaffolder.js.map +1 -1
- package/dist/workflow/VerificationProfile.d.ts +2 -0
- package/dist/workflow/VerificationProfile.js +7 -0
- package/dist/workflow/VerificationProfile.js.map +1 -1
- package/dist/workflow/index.d.ts +2 -0
- package/dist/workflow/index.js +2 -0
- package/dist/workflow/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -20,10 +20,17 @@ 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 { doctorEngineeringStandards, settleEngineeringStandards } from '../workflow/EngineeringStandards.js';
|
|
23
24
|
import { analyzeReview, parseChangedFiles, shouldReviewFile, summarizeFindings } from '../workflow/ReviewAnalyzer.js';
|
|
25
|
+
import { inspectWorkspaceLifecycle } from '../workflow/WorkspaceLifecycle.js';
|
|
26
|
+
import { evaluateToolEvidenceGate } from '../tools/ToolEvidenceGate.js';
|
|
27
|
+
import { ToolEvidenceStore } from '../tools/ToolEvidenceStore.js';
|
|
28
|
+
import { ToolOrchestrator } from '../tools/ToolOrchestrator.js';
|
|
29
|
+
import { loadToolPolicy } from '../tools/ToolPolicy.js';
|
|
24
30
|
import { join } from 'node:path';
|
|
25
31
|
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
26
32
|
import { HTMLDocumentRenderer } from '../output/HTMLDocumentRenderer.js';
|
|
33
|
+
import { SCALE_ENGINE_VERSION } from '../version.js';
|
|
27
34
|
const SCALE_DIR = process.env.SCALE_DIR ?? '.scale';
|
|
28
35
|
const PROJECT_DIR = process.env.SCALE_PROJECT_DIR ?? process.cwd();
|
|
29
36
|
function validateVerificationEvidence(ids) {
|
|
@@ -188,6 +195,85 @@ function evaluateArtifactGate(options) {
|
|
|
188
195
|
blocked: mode === 'block' && checked && complete === false,
|
|
189
196
|
};
|
|
190
197
|
}
|
|
198
|
+
function evaluateEngineeringStandardsGate(options) {
|
|
199
|
+
const mode = normalizeEngineeringStandardsGateMode(options.policy.engineeringStandardsGate);
|
|
200
|
+
if (mode === 'off') {
|
|
201
|
+
return {
|
|
202
|
+
mode,
|
|
203
|
+
checked: false,
|
|
204
|
+
blocked: false,
|
|
205
|
+
ok: true,
|
|
206
|
+
findings: [],
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
const settlement = options.settle && options.artifactsDir
|
|
210
|
+
? settleEngineeringStandards({
|
|
211
|
+
projectDir: PROJECT_DIR,
|
|
212
|
+
scaleDir: SCALE_DIR,
|
|
213
|
+
taskId: options.taskId,
|
|
214
|
+
artifactsDir: options.artifactsDir,
|
|
215
|
+
})
|
|
216
|
+
: undefined;
|
|
217
|
+
const doctor = settlement?.doctor ?? doctorEngineeringStandards({
|
|
218
|
+
projectDir: PROJECT_DIR,
|
|
219
|
+
scaleDir: SCALE_DIR,
|
|
220
|
+
});
|
|
221
|
+
return {
|
|
222
|
+
mode,
|
|
223
|
+
checked: true,
|
|
224
|
+
blocked: mode === 'block' && !doctor.ok,
|
|
225
|
+
ok: doctor.ok,
|
|
226
|
+
findings: doctor.findings,
|
|
227
|
+
summary: doctor.scan.summary,
|
|
228
|
+
standardsImpactPath: settlement?.standardsImpactPath,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
function normalizeEngineeringStandardsGateMode(value) {
|
|
232
|
+
return value === 'off' || value === 'block' ? value : 'warn';
|
|
233
|
+
}
|
|
234
|
+
function normalizeToolGateMode(value) {
|
|
235
|
+
if (value === undefined || value === null || value === '')
|
|
236
|
+
return undefined;
|
|
237
|
+
const normalized = String(value).trim().toLowerCase();
|
|
238
|
+
if (normalized === 'warn')
|
|
239
|
+
return 'advisory';
|
|
240
|
+
if (normalized === 'off' || normalized === 'advisory' || normalized === 'evidence-required' || normalized === 'block')
|
|
241
|
+
return normalized;
|
|
242
|
+
throw new Error(`Invalid tool gate mode "${String(value)}"; expected off, advisory, evidence-required, or block.`);
|
|
243
|
+
}
|
|
244
|
+
function resolvePhaseToolGateMode(options) {
|
|
245
|
+
if (isTruthyFlag(options.requireEvidence))
|
|
246
|
+
return 'evidence-required';
|
|
247
|
+
const cliMode = normalizeToolGateMode(options.cliMode);
|
|
248
|
+
if (cliMode)
|
|
249
|
+
return cliMode;
|
|
250
|
+
return options.policy.mode === 'block' ? 'block' : 'off';
|
|
251
|
+
}
|
|
252
|
+
function evaluateTaskToolEvidenceGate(options) {
|
|
253
|
+
if (!options.level || !options.skillPlan)
|
|
254
|
+
return undefined;
|
|
255
|
+
const policy = loadToolPolicy(PROJECT_DIR, SCALE_DIR);
|
|
256
|
+
const mode = resolvePhaseToolGateMode({
|
|
257
|
+
cliMode: options.cliMode,
|
|
258
|
+
requireEvidence: options.requireEvidence,
|
|
259
|
+
policy,
|
|
260
|
+
});
|
|
261
|
+
const effectivePolicy = { ...policy, mode };
|
|
262
|
+
const evidenceStore = new ToolEvidenceStore({ projectDir: PROJECT_DIR, scaleDir: SCALE_DIR });
|
|
263
|
+
const plan = new ToolOrchestrator({
|
|
264
|
+
projectDir: PROJECT_DIR,
|
|
265
|
+
policy: effectivePolicy,
|
|
266
|
+
evidenceStore,
|
|
267
|
+
}).plan({ skillPlan: options.skillPlan });
|
|
268
|
+
return evaluateToolEvidenceGate({
|
|
269
|
+
projectDir: PROJECT_DIR,
|
|
270
|
+
level: options.level,
|
|
271
|
+
plan,
|
|
272
|
+
evidenceStore,
|
|
273
|
+
mode,
|
|
274
|
+
allowSkipped: isTruthyFlag(options.allowSkipped),
|
|
275
|
+
});
|
|
276
|
+
}
|
|
191
277
|
async function countChangedFiles(taskPayload) {
|
|
192
278
|
const filesInvolved = taskPayload.filesInvolved ?? [];
|
|
193
279
|
if (filesInvolved.length > 0)
|
|
@@ -394,7 +480,7 @@ export const phaseDefine = defineCommand({
|
|
|
394
480
|
const renderer = new HTMLDocumentRenderer({
|
|
395
481
|
title: args.title,
|
|
396
482
|
brand: args.brand,
|
|
397
|
-
version:
|
|
483
|
+
version: SCALE_ENGINE_VERSION,
|
|
398
484
|
status: 'FROZEN',
|
|
399
485
|
});
|
|
400
486
|
const html = renderer.renderSpec({
|
|
@@ -544,7 +630,7 @@ export const phasePlan = defineCommand({
|
|
|
544
630
|
const planRenderer = new HTMLDocumentRenderer({
|
|
545
631
|
title: `Plan ${plan.id}`,
|
|
546
632
|
brand: args.brand,
|
|
547
|
-
version:
|
|
633
|
+
version: SCALE_ENGINE_VERSION,
|
|
548
634
|
status: 'APPROVED',
|
|
549
635
|
});
|
|
550
636
|
const planHtml = planRenderer.renderPlan({
|
|
@@ -771,6 +857,9 @@ export const phaseVerify = defineCommand({
|
|
|
771
857
|
'artifact-gate': { type: 'string', description: 'Task artifact policy override: off, warn, or block' },
|
|
772
858
|
'require-artifacts': { type: 'boolean', default: false, description: 'Fail verification when required M/L/CRITICAL artifacts are incomplete' },
|
|
773
859
|
'require-installed-skills': { type: 'boolean', default: false, description: 'Fail verification when required workflow skills are not installed locally' },
|
|
860
|
+
'tool-gate': { type: 'string', description: 'Tool evidence policy override: off, advisory, evidence-required, or block' },
|
|
861
|
+
'require-tool-evidence': { type: 'boolean', default: false, description: 'Fail verification when required tool execution evidence is missing or skipped' },
|
|
862
|
+
'allow-skipped-tool-evidence': { type: 'boolean', default: false, description: 'Allow skipped/manual fallback tool evidence to satisfy the tool gate' },
|
|
774
863
|
'tdd-evidence': { type: 'string', description: 'Path to JSON TDD evidence with red/green/refactor/testFirst=true' },
|
|
775
864
|
'tdd-strict': { type: 'boolean', default: false, description: 'Require TDD evidence before other gates' },
|
|
776
865
|
'residual-risk': { type: 'string', description: 'Residual risk statement to record in task metrics' },
|
|
@@ -920,6 +1009,12 @@ export const phaseVerify = defineCommand({
|
|
|
920
1009
|
requiredSkillArtifacts: updatedPayload.requiredSkillArtifacts,
|
|
921
1010
|
requiredSkillVerification: updatedPayload.requiredSkillVerification,
|
|
922
1011
|
});
|
|
1012
|
+
const engineeringStandards = evaluateEngineeringStandardsGate({
|
|
1013
|
+
policy: resolvedVerification.policy,
|
|
1014
|
+
taskId: args['task-id'],
|
|
1015
|
+
artifactsDir: workflowState.artifactsDir,
|
|
1016
|
+
settle: Boolean(workflowState.artifactsDir),
|
|
1017
|
+
});
|
|
923
1018
|
const metricLevel = metricLevelFromPayload(updatedPayload);
|
|
924
1019
|
const preArtifactCheck = metricLevel ? checkCurrentTaskArtifacts(metricLevel) : undefined;
|
|
925
1020
|
const artifactGate = evaluateArtifactGate({
|
|
@@ -942,6 +1037,13 @@ export const phaseVerify = defineCommand({
|
|
|
942
1037
|
const requireInstalledSkills = isTruthyFlag(args['require-installed-skills']);
|
|
943
1038
|
const skillInstallation = inspectRequiredWorkflowSkills(updatedPayload.requiredSkills ?? [], { projectDir: PROJECT_DIR });
|
|
944
1039
|
const skillInstallationBlocked = requireInstalledSkills && !skillInstallation.ok;
|
|
1040
|
+
const toolEvidenceGate = evaluateTaskToolEvidenceGate({
|
|
1041
|
+
skillPlan: verificationSkillPlan,
|
|
1042
|
+
level: metricLevel,
|
|
1043
|
+
cliMode: args['tool-gate'],
|
|
1044
|
+
requireEvidence: args['require-tool-evidence'],
|
|
1045
|
+
allowSkipped: args['allow-skipped-tool-evidence'],
|
|
1046
|
+
});
|
|
945
1047
|
// Attempt FSM transition to COMPLETED
|
|
946
1048
|
// Guards: build_passed, lint_passed, tests_passed, and optional artifact policy.
|
|
947
1049
|
const codePassed = results.buildStatus === 'success' &&
|
|
@@ -950,7 +1052,12 @@ export const phaseVerify = defineCommand({
|
|
|
950
1052
|
results.testPassed === true &&
|
|
951
1053
|
(results.testCoverage ?? 0) >= 80 &&
|
|
952
1054
|
results.securityPassed === true;
|
|
953
|
-
const completionEligible = codePassed &&
|
|
1055
|
+
const completionEligible = codePassed &&
|
|
1056
|
+
!artifactGate.blocked &&
|
|
1057
|
+
!(skillGate?.blocked ?? false) &&
|
|
1058
|
+
!skillInstallationBlocked &&
|
|
1059
|
+
!engineeringStandards.blocked &&
|
|
1060
|
+
!(toolEvidenceGate?.blocked ?? false);
|
|
954
1061
|
let transitionResult = null;
|
|
955
1062
|
if (completionEligible) {
|
|
956
1063
|
const completeResult = await fsm.canTransition(args['task-id'], 'complete');
|
|
@@ -982,6 +1089,12 @@ export const phaseVerify = defineCommand({
|
|
|
982
1089
|
else if (!args.json && skillInstallationBlocked) {
|
|
983
1090
|
console.log('\n Skill installation gate blocked completion - required workflow skills are missing');
|
|
984
1091
|
}
|
|
1092
|
+
else if (!args.json && engineeringStandards.blocked) {
|
|
1093
|
+
console.log('\n Engineering standards gate blocked completion - fix blocking standards findings');
|
|
1094
|
+
}
|
|
1095
|
+
else if (!args.json && toolEvidenceGate?.blocked) {
|
|
1096
|
+
console.log('\n Tool evidence gate blocked completion - required tools need passed execution evidence');
|
|
1097
|
+
}
|
|
985
1098
|
const passed = completionEligible && (transitionResult?.success ?? false);
|
|
986
1099
|
const verificationArtifactPath = appendVerificationArtifact({
|
|
987
1100
|
projectDir: PROJECT_DIR,
|
|
@@ -1011,15 +1124,29 @@ export const phaseVerify = defineCommand({
|
|
|
1011
1124
|
enforceLevels: skillPolicy.policy.enforceLevels,
|
|
1012
1125
|
})
|
|
1013
1126
|
: skillGate;
|
|
1127
|
+
const finalToolEvidenceGate = evaluateTaskToolEvidenceGate({
|
|
1128
|
+
skillPlan: verificationSkillPlan,
|
|
1129
|
+
level: metricLevel,
|
|
1130
|
+
cliMode: args['tool-gate'],
|
|
1131
|
+
requireEvidence: args['require-tool-evidence'],
|
|
1132
|
+
allowSkipped: args['allow-skipped-tool-evidence'],
|
|
1133
|
+
}) ?? toolEvidenceGate;
|
|
1014
1134
|
const finalPayload = {
|
|
1015
1135
|
...updatedPayload,
|
|
1016
1136
|
artifactGateMode: finalArtifactGate.mode,
|
|
1017
1137
|
artifactGatePassed: !finalArtifactGate.blocked,
|
|
1018
1138
|
artifactComplete: artifactCheck?.complete,
|
|
1019
1139
|
skillGatePassed: finalSkillGate ? !finalSkillGate.blocked && !skillInstallationBlocked : !skillInstallationBlocked,
|
|
1140
|
+
toolOrchestrationMode: finalToolEvidenceGate?.mode,
|
|
1141
|
+
requiredTools: finalToolEvidenceGate?.requiredTools,
|
|
1142
|
+
toolEvidenceIds: finalToolEvidenceGate?.passed.map(item => item.evidenceId).filter((id) => Boolean(id)),
|
|
1143
|
+
toolEvidenceGatePassed: finalToolEvidenceGate ? !finalToolEvidenceGate.blocked : true,
|
|
1020
1144
|
};
|
|
1021
1145
|
await store.update(args['task-id'], { payload: finalPayload });
|
|
1022
|
-
const metricGateStatus = codePassed &&
|
|
1146
|
+
const metricGateStatus = codePassed &&
|
|
1147
|
+
(finalArtifactGate.blocked || finalSkillGate?.blocked || skillInstallationBlocked || engineeringStandards.blocked || finalToolEvidenceGate?.blocked)
|
|
1148
|
+
? 'blocked'
|
|
1149
|
+
: undefined;
|
|
1023
1150
|
const metricRecord = await recordVerificationMetric({
|
|
1024
1151
|
taskId: args['task-id'],
|
|
1025
1152
|
taskName: task.title,
|
|
@@ -1040,7 +1167,9 @@ export const phaseVerify = defineCommand({
|
|
|
1040
1167
|
verificationArtifactPath,
|
|
1041
1168
|
artifactCheck,
|
|
1042
1169
|
artifactGate: finalArtifactGate,
|
|
1170
|
+
engineeringStandards,
|
|
1043
1171
|
skillGate: finalSkillGate,
|
|
1172
|
+
toolEvidenceGate: finalToolEvidenceGate,
|
|
1044
1173
|
skillInstallation: {
|
|
1045
1174
|
...skillInstallation,
|
|
1046
1175
|
checked: requireInstalledSkills,
|
|
@@ -1064,6 +1193,12 @@ export const phaseVerify = defineCommand({
|
|
|
1064
1193
|
if (skillInstallationBlocked) {
|
|
1065
1194
|
console.log(` Missing required workflow skills: ${skillInstallation.missing.join(', ')}`);
|
|
1066
1195
|
}
|
|
1196
|
+
if (engineeringStandards.blocked) {
|
|
1197
|
+
console.log(` Engineering standards blockers: ${engineeringStandards.findings.filter(finding => finding.severity === 'fail').length}`);
|
|
1198
|
+
}
|
|
1199
|
+
if (finalToolEvidenceGate?.blocked) {
|
|
1200
|
+
console.log(` Tool evidence gaps: ${finalToolEvidenceGate.missing.length} missing, ${finalToolEvidenceGate.failed.length} failed, ${finalToolEvidenceGate.skipped.length} skipped`);
|
|
1201
|
+
}
|
|
1067
1202
|
if (passed)
|
|
1068
1203
|
console.log(`\n Next: scale review\n`);
|
|
1069
1204
|
else
|
|
@@ -1194,6 +1329,57 @@ async function stageReviewedFiles(reviewRecords) {
|
|
|
1194
1329
|
}
|
|
1195
1330
|
return { stagedFiles, unreviewedFiles: [] };
|
|
1196
1331
|
}
|
|
1332
|
+
function isMultiRepositoryTopology(topology) {
|
|
1333
|
+
return topology === 'moe' || topology === 'submodule-workspace' || topology === 'polyrepo';
|
|
1334
|
+
}
|
|
1335
|
+
function hasChildRepositoryChange(path, childPath) {
|
|
1336
|
+
const normalized = normalizeGitPath(path);
|
|
1337
|
+
const child = normalizeGitPath(childPath).replace(/\/+$/, '');
|
|
1338
|
+
return normalized === child || normalized.startsWith(`${child}/`);
|
|
1339
|
+
}
|
|
1340
|
+
async function validateWorkspaceShipBoundary() {
|
|
1341
|
+
try {
|
|
1342
|
+
const report = await inspectWorkspaceLifecycle({ projectDir: PROJECT_DIR });
|
|
1343
|
+
const blockers = [];
|
|
1344
|
+
const warnings = [...report.topology.warnings];
|
|
1345
|
+
const policy = report.topology.finishPolicy;
|
|
1346
|
+
const rootChanges = await getReviewableGitChanges();
|
|
1347
|
+
if (!report.topology.configured && report.childRepositories.length > 0) {
|
|
1348
|
+
const changedChildRepositories = report.childRepositories
|
|
1349
|
+
.filter(child => rootChanges.some(file => hasChildRepositoryChange(file.path, child.relativePath)))
|
|
1350
|
+
.map(child => child.relativePath);
|
|
1351
|
+
const dirtyChildRepositories = report.childRepositories
|
|
1352
|
+
.filter(child => !child.clean || child.ahead > 0)
|
|
1353
|
+
.map(child => child.relativePath);
|
|
1354
|
+
const affected = Array.from(new Set([...changedChildRepositories, ...dirtyChildRepositories]));
|
|
1355
|
+
if (affected.length > 0) {
|
|
1356
|
+
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.`);
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
if (report.topology.configured && isMultiRepositoryTopology(report.topology.topology)) {
|
|
1360
|
+
for (const child of report.childRepositories) {
|
|
1361
|
+
if (policy.requireCleanRepositories && !child.clean) {
|
|
1362
|
+
blockers.push(`Child repository ${child.relativePath} has uncommitted changes`);
|
|
1363
|
+
}
|
|
1364
|
+
if (policy.requirePushedBranches && child.upstream && child.ahead > 0) {
|
|
1365
|
+
blockers.push(`Child repository ${child.relativePath} has unpushed commits`);
|
|
1366
|
+
}
|
|
1367
|
+
if (policy.requirePushedBranches && report.topology.topology === 'moe' && !child.upstream && child.branch) {
|
|
1368
|
+
blockers.push(`Child repository ${child.relativePath} has no upstream; push or explicitly disable requirePushedBranches before shipping`);
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
return { report, blockers, warnings };
|
|
1373
|
+
}
|
|
1374
|
+
catch (error) {
|
|
1375
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1376
|
+
return {
|
|
1377
|
+
report: null,
|
|
1378
|
+
blockers: [`Workspace boundary check could not inspect Git repositories: ${message}`],
|
|
1379
|
+
warnings: [],
|
|
1380
|
+
};
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1197
1383
|
function collectTaskReviewText(taskPayload) {
|
|
1198
1384
|
if (!taskPayload)
|
|
1199
1385
|
return '';
|
|
@@ -1357,7 +1543,7 @@ export const phaseReview = defineCommand({
|
|
|
1357
1543
|
const reviewRenderer = new HTMLDocumentRenderer({
|
|
1358
1544
|
title: `Review ${record.id}`,
|
|
1359
1545
|
brand: args.brand,
|
|
1360
|
-
version:
|
|
1546
|
+
version: SCALE_ENGINE_VERSION,
|
|
1361
1547
|
status: passed ? 'PASS' : 'FAIL',
|
|
1362
1548
|
});
|
|
1363
1549
|
const reviewHtml = reviewRenderer.renderReview({
|
|
@@ -1450,6 +1636,7 @@ export const phaseShip = defineCommand({
|
|
|
1450
1636
|
const reviewPassed = payload.reviewPassed === true && reviewValidation.ok;
|
|
1451
1637
|
const artifactGatePassed = payload.artifactGateMode !== 'block' || payload.artifactGatePassed !== false;
|
|
1452
1638
|
const skillGatePassed = payload.skillGatePassed !== false;
|
|
1639
|
+
const toolEvidenceGatePassed = payload.toolEvidenceGatePassed !== false;
|
|
1453
1640
|
if (!artifactGatePassed) {
|
|
1454
1641
|
console.error('\nTask artifact gate did not pass. Complete required task artifacts and re-run: scale verify ' + args['task-id'] + ' --artifact-gate block\n');
|
|
1455
1642
|
if (payload.artifactComplete === false) {
|
|
@@ -1461,6 +1648,13 @@ export const phaseShip = defineCommand({
|
|
|
1461
1648
|
console.error('\nTask skill gate did not pass. Complete required skill evidence artifacts and re-run: scale verify ' + args['task-id'] + '\n');
|
|
1462
1649
|
process.exit(1);
|
|
1463
1650
|
}
|
|
1651
|
+
if (!toolEvidenceGatePassed) {
|
|
1652
|
+
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');
|
|
1653
|
+
if (payload.requiredTools?.length) {
|
|
1654
|
+
console.error('Required tools: ' + payload.requiredTools.join(', '));
|
|
1655
|
+
}
|
|
1656
|
+
process.exit(1);
|
|
1657
|
+
}
|
|
1464
1658
|
if (task.status !== 'COMPLETED') {
|
|
1465
1659
|
if (!verificationPassed) {
|
|
1466
1660
|
console.error('\nTask not verified with persisted evidence. Run: scale verify ' + args['task-id'] + '\n');
|
|
@@ -1497,9 +1691,17 @@ export const phaseShip = defineCommand({
|
|
|
1497
1691
|
// Git operations
|
|
1498
1692
|
let commitHash = null;
|
|
1499
1693
|
let stagedFiles = [];
|
|
1694
|
+
let workspaceBoundary = null;
|
|
1500
1695
|
if (!shouldSkipCommit(args['skip-commit'])) {
|
|
1501
1696
|
const commitMessage = args.message ?? `feat: ${task.title ?? args['task-id']}`;
|
|
1502
1697
|
try {
|
|
1698
|
+
workspaceBoundary = await validateWorkspaceShipBoundary();
|
|
1699
|
+
if (workspaceBoundary.blockers.length > 0) {
|
|
1700
|
+
console.error('\nWorkspace boundary check failed. Resolve child repositories before shipping the root commit.');
|
|
1701
|
+
workspaceBoundary.blockers.forEach(blocker => console.error(' - ' + blocker));
|
|
1702
|
+
console.error('\nRun scale workspace finish --summary for the shortest fix list, or --json for the full workspace state.\n');
|
|
1703
|
+
process.exit(1);
|
|
1704
|
+
}
|
|
1503
1705
|
const reviewRecords = getValidatedReviewRecords(payload.reviewEvidenceIds);
|
|
1504
1706
|
const stageResult = await stageReviewedFiles(reviewRecords);
|
|
1505
1707
|
if (stageResult.unreviewedFiles.length > 0) {
|
|
@@ -1587,6 +1789,13 @@ export const phaseShip = defineCommand({
|
|
|
1587
1789
|
reviewValidation,
|
|
1588
1790
|
commitHash,
|
|
1589
1791
|
stagedFiles,
|
|
1792
|
+
workspaceBoundary: workspaceBoundary ? {
|
|
1793
|
+
topology: workspaceBoundary.report?.topology.topology ?? null,
|
|
1794
|
+
configured: workspaceBoundary.report?.topology.configured ?? false,
|
|
1795
|
+
childRepositories: workspaceBoundary.report?.childRepositories.length ?? 0,
|
|
1796
|
+
blockers: workspaceBoundary.blockers,
|
|
1797
|
+
warnings: workspaceBoundary.warnings,
|
|
1798
|
+
} : null,
|
|
1590
1799
|
};
|
|
1591
1800
|
if (args.json)
|
|
1592
1801
|
console.log(JSON.stringify(result, null, 2));
|