@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.
Files changed (89) hide show
  1. package/README.en.md +13 -5
  2. package/README.md +13 -5
  3. package/dist/agents/LeadershipPresets.d.ts +16 -0
  4. package/dist/agents/LeadershipPresets.js +152 -0
  5. package/dist/agents/LeadershipPresets.js.map +1 -0
  6. package/dist/api/cli.js +729 -5
  7. package/dist/api/cli.js.map +1 -1
  8. package/dist/api/doctor.d.ts +2 -0
  9. package/dist/api/doctor.js +83 -0
  10. package/dist/api/doctor.js.map +1 -1
  11. package/dist/api/mcp.js +2 -1
  12. package/dist/api/mcp.js.map +1 -1
  13. package/dist/artifact/types.d.ts +4 -0
  14. package/dist/artifact/types.js.map +1 -1
  15. package/dist/capabilities/InstalledSkillsIntegration.d.ts +3 -0
  16. package/dist/capabilities/InstalledSkillsIntegration.js +41 -17
  17. package/dist/capabilities/InstalledSkillsIntegration.js.map +1 -1
  18. package/dist/cli/phaseCommands.d.ts +14 -0
  19. package/dist/cli/phaseCommands.js +214 -5
  20. package/dist/cli/phaseCommands.js.map +1 -1
  21. package/dist/cli/vibeCommands.d.ts +20 -0
  22. package/dist/cli/vibeCommands.js +150 -173
  23. package/dist/cli/vibeCommands.js.map +1 -1
  24. package/dist/core/logger.d.ts +2 -0
  25. package/dist/core/logger.js +33 -1
  26. package/dist/core/logger.js.map +1 -1
  27. package/dist/index.d.ts +4 -0
  28. package/dist/index.js +5 -0
  29. package/dist/index.js.map +1 -1
  30. package/dist/output/HTMLDocumentRenderer.js +3 -2
  31. package/dist/output/HTMLDocumentRenderer.js.map +1 -1
  32. package/dist/prompts/VibeTemplateGallery.d.ts +25 -0
  33. package/dist/prompts/VibeTemplateGallery.js +295 -0
  34. package/dist/prompts/VibeTemplateGallery.js.map +1 -0
  35. package/dist/skills/ExternalSkills.js +9 -4
  36. package/dist/skills/ExternalSkills.js.map +1 -1
  37. package/dist/skills/SkillDiscovery.js +5 -3
  38. package/dist/skills/SkillDiscovery.js.map +1 -1
  39. package/dist/skills/SkillDoctor.js +178 -1
  40. package/dist/skills/SkillDoctor.js.map +1 -1
  41. package/dist/skills/SkillInstaller.js +5 -0
  42. package/dist/skills/SkillInstaller.js.map +1 -1
  43. package/dist/skills/SkillRepository.d.ts +63 -0
  44. package/dist/skills/SkillRepository.js +365 -0
  45. package/dist/skills/SkillRepository.js.map +1 -0
  46. package/dist/skills/routing/SkillPolicy.js +168 -5
  47. package/dist/skills/routing/SkillPolicy.js.map +1 -1
  48. package/dist/tools/ToolCapabilityRegistry.d.ts +46 -0
  49. package/dist/tools/ToolCapabilityRegistry.js +223 -0
  50. package/dist/tools/ToolCapabilityRegistry.js.map +1 -0
  51. package/dist/tools/ToolEvidenceGate.d.ts +39 -0
  52. package/dist/tools/ToolEvidenceGate.js +117 -0
  53. package/dist/tools/ToolEvidenceGate.js.map +1 -0
  54. package/dist/tools/ToolEvidenceStore.d.ts +58 -0
  55. package/dist/tools/ToolEvidenceStore.js +129 -0
  56. package/dist/tools/ToolEvidenceStore.js.map +1 -0
  57. package/dist/tools/ToolOrchestrator.d.ts +67 -0
  58. package/dist/tools/ToolOrchestrator.js +193 -0
  59. package/dist/tools/ToolOrchestrator.js.map +1 -0
  60. package/dist/tools/ToolPolicy.d.ts +33 -0
  61. package/dist/tools/ToolPolicy.js +157 -0
  62. package/dist/tools/ToolPolicy.js.map +1 -0
  63. package/dist/tools/index.d.ts +5 -0
  64. package/dist/tools/index.js +6 -0
  65. package/dist/tools/index.js.map +1 -0
  66. package/dist/version.d.ts +3 -0
  67. package/dist/version.js +15 -0
  68. package/dist/version.js.map +1 -0
  69. package/dist/workflow/EngineeringStandards.d.ts +212 -0
  70. package/dist/workflow/EngineeringStandards.js +1021 -0
  71. package/dist/workflow/EngineeringStandards.js.map +1 -0
  72. package/dist/workflow/GovernanceTemplatePacks.d.ts +1 -1
  73. package/dist/workflow/GovernanceTemplatePacks.js +101 -18
  74. package/dist/workflow/GovernanceTemplatePacks.js.map +1 -1
  75. package/dist/workflow/GovernanceTemplates.d.ts +1 -1
  76. package/dist/workflow/GovernanceTemplates.js +225 -37
  77. package/dist/workflow/GovernanceTemplates.js.map +1 -1
  78. package/dist/workflow/ResourceGovernance.d.ts +120 -0
  79. package/dist/workflow/ResourceGovernance.js +512 -0
  80. package/dist/workflow/ResourceGovernance.js.map +1 -0
  81. package/dist/workflow/TaskArtifactScaffolder.js +3 -0
  82. package/dist/workflow/TaskArtifactScaffolder.js.map +1 -1
  83. package/dist/workflow/VerificationProfile.d.ts +2 -0
  84. package/dist/workflow/VerificationProfile.js +7 -0
  85. package/dist/workflow/VerificationProfile.js.map +1 -1
  86. package/dist/workflow/index.d.ts +2 -0
  87. package/dist/workflow/index.js +2 -0
  88. package/dist/workflow/index.js.map +1 -1
  89. 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: '0.13.0',
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: '0.13.0',
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 && !artifactGate.blocked && !(skillGate?.blocked ?? false) && !skillInstallationBlocked;
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 && (finalArtifactGate.blocked || finalSkillGate?.blocked || skillInstallationBlocked) ? 'blocked' : undefined;
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: '0.13.0',
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));