@hongmaple0820/scale-engine 0.48.0 → 0.50.1

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 (242) hide show
  1. package/README.en.md +2 -2
  2. package/README.md +2 -2
  3. package/dist/agents/evidenceDiscipline.d.ts +7 -0
  4. package/dist/agents/evidenceDiscipline.js +21 -0
  5. package/dist/agents/evidenceDiscipline.js.map +1 -0
  6. package/dist/agents/profiles.js +8 -1
  7. package/dist/agents/profiles.js.map +1 -1
  8. package/dist/agents/types.d.ts +1 -0
  9. package/dist/api/DashboardHttpConfig.d.ts +28 -0
  10. package/dist/api/DashboardHttpConfig.js +110 -0
  11. package/dist/api/DashboardHttpConfig.js.map +1 -0
  12. package/dist/api/cli.js +102 -11
  13. package/dist/api/cli.js.map +1 -1
  14. package/dist/api/http.d.ts +1 -0
  15. package/dist/api/http.js +50 -0
  16. package/dist/api/http.js.map +1 -0
  17. package/dist/artifact/types.d.ts +64 -0
  18. package/dist/artifact/types.js.map +1 -1
  19. package/dist/bootstrap/DependencyBootstrap.d.ts +1 -0
  20. package/dist/bootstrap/DependencyBootstrap.js +14 -3
  21. package/dist/bootstrap/DependencyBootstrap.js.map +1 -1
  22. package/dist/cli/cortexApplyCommand.d.ts +26 -0
  23. package/dist/cli/cortexApplyCommand.js +74 -0
  24. package/dist/cli/cortexApplyCommand.js.map +1 -0
  25. package/dist/cli/cortexCandidateCommands.d.ts +42 -0
  26. package/dist/cli/cortexCandidateCommands.js +119 -0
  27. package/dist/cli/cortexCandidateCommands.js.map +1 -0
  28. package/dist/cli/cortexCommands.d.ts +51 -0
  29. package/dist/cli/cortexCommands.js +127 -13
  30. package/dist/cli/cortexCommands.js.map +1 -1
  31. package/dist/cli/engineBootstrap.d.ts +1 -1
  32. package/dist/cli/engineBootstrap.js +2 -0
  33. package/dist/cli/engineBootstrap.js.map +1 -1
  34. package/dist/cli/evalCommands.js +13 -1
  35. package/dist/cli/evalCommands.js.map +1 -1
  36. package/dist/cli/phaseCommands.d.ts +81 -1
  37. package/dist/cli/phaseCommands.js +465 -31
  38. package/dist/cli/phaseCommands.js.map +1 -1
  39. package/dist/cli/runtimeSkillCommands.js +12 -2
  40. package/dist/cli/runtimeSkillCommands.js.map +1 -1
  41. package/dist/cli/shieldCommands.d.ts +1 -0
  42. package/dist/cli/shieldCommands.js +20 -7
  43. package/dist/cli/shieldCommands.js.map +1 -1
  44. package/dist/cli/workflowEvidenceCommands.d.ts +120 -0
  45. package/dist/cli/workflowEvidenceCommands.js +228 -2
  46. package/dist/cli/workflowEvidenceCommands.js.map +1 -1
  47. package/dist/cortex/AutoFixEventObservations.d.ts +11 -0
  48. package/dist/cortex/AutoFixEventObservations.js +72 -0
  49. package/dist/cortex/AutoFixEventObservations.js.map +1 -0
  50. package/dist/cortex/GateEvidenceObservations.d.ts +22 -0
  51. package/dist/cortex/GateEvidenceObservations.js +179 -0
  52. package/dist/cortex/GateEvidenceObservations.js.map +1 -0
  53. package/dist/cortex/GovernanceMetrics.d.ts +2 -0
  54. package/dist/cortex/GovernanceMetrics.js +112 -22
  55. package/dist/cortex/GovernanceMetrics.js.map +1 -1
  56. package/dist/cortex/InstinctApplicationRecorder.d.ts +28 -0
  57. package/dist/cortex/InstinctApplicationRecorder.js +145 -0
  58. package/dist/cortex/InstinctApplicationRecorder.js.map +1 -0
  59. package/dist/cortex/InstinctCandidateAudit.d.ts +3 -0
  60. package/dist/cortex/InstinctCandidateAudit.js +39 -0
  61. package/dist/cortex/InstinctCandidateAudit.js.map +1 -0
  62. package/dist/cortex/InstinctCandidateReview.d.ts +32 -0
  63. package/dist/cortex/InstinctCandidateReview.js +125 -0
  64. package/dist/cortex/InstinctCandidateReview.js.map +1 -0
  65. package/dist/cortex/InstinctExtractor.d.ts +1 -0
  66. package/dist/cortex/InstinctExtractor.js +24 -17
  67. package/dist/cortex/InstinctExtractor.js.map +1 -1
  68. package/dist/cortex/InstinctRuntimeEvidence.d.ts +14 -0
  69. package/dist/cortex/InstinctRuntimeEvidence.js +120 -0
  70. package/dist/cortex/InstinctRuntimeEvidence.js.map +1 -0
  71. package/dist/cortex/InstinctStore.d.ts +50 -4
  72. package/dist/cortex/InstinctStore.js +262 -48
  73. package/dist/cortex/InstinctStore.js.map +1 -1
  74. package/dist/cortex/InstinctValidation.d.ts +9 -0
  75. package/dist/cortex/InstinctValidation.js +55 -0
  76. package/dist/cortex/InstinctValidation.js.map +1 -0
  77. package/dist/cortex/SessionInjector.d.ts +1 -0
  78. package/dist/cortex/SessionInjector.js +28 -8
  79. package/dist/cortex/SessionInjector.js.map +1 -1
  80. package/dist/dashboard/DashboardServer.d.ts +79 -0
  81. package/dist/dashboard/DashboardServer.js +330 -6
  82. package/dist/dashboard/DashboardServer.js.map +1 -1
  83. package/dist/dashboard/spa/app.js +515 -0
  84. package/dist/dashboard/spa/components/DataTable.js +53 -0
  85. package/dist/dashboard/spa/components/EventStream.js +66 -0
  86. package/dist/dashboard/spa/components/LoadingState.js +39 -0
  87. package/dist/dashboard/spa/components/MetricCard.js +30 -0
  88. package/dist/dashboard/spa/components/Panel.js +27 -0
  89. package/dist/dashboard/spa/components/StatusBadge.js +51 -0
  90. package/dist/dashboard/spa/i18n.js +767 -0
  91. package/dist/dashboard/spa/index.html +463 -0
  92. package/dist/dashboard/spa/pages/costs.js +522 -0
  93. package/dist/dashboard/spa/pages/documents.js +540 -0
  94. package/dist/dashboard/spa/pages/knowledge.js +457 -0
  95. package/dist/dashboard/spa/pages/monitoring.js +361 -0
  96. package/dist/dashboard/spa/pages/overview.js +301 -0
  97. package/dist/dashboard/spa/pages/topology-renderers.js +251 -0
  98. package/dist/dashboard/spa/pages/topology.js +370 -0
  99. package/dist/dashboard/spa/pages/workflow-renderers.js +239 -0
  100. package/dist/dashboard/spa/pages/workflow.js +217 -0
  101. package/dist/env/EnvironmentDoctor.js +12 -7
  102. package/dist/env/EnvironmentDoctor.js.map +1 -1
  103. package/dist/eval/BenchmarkPublisher.d.ts +2 -0
  104. package/dist/eval/BenchmarkPublisher.js +43 -0
  105. package/dist/eval/BenchmarkPublisher.js.map +1 -1
  106. package/dist/eval/WorkflowEval.d.ts +9 -0
  107. package/dist/eval/WorkflowEval.js +348 -2
  108. package/dist/eval/WorkflowEval.js.map +1 -1
  109. package/dist/guardrails/ast/confirmers.d.ts +18 -0
  110. package/dist/guardrails/ast/confirmers.js +69 -0
  111. package/dist/guardrails/ast/confirmers.js.map +1 -0
  112. package/dist/guardrails/ast/parse.d.ts +20 -0
  113. package/dist/guardrails/ast/parse.js +51 -0
  114. package/dist/guardrails/ast/parse.js.map +1 -0
  115. package/dist/memory/MemoryBrain.d.ts +13 -0
  116. package/dist/memory/MemoryBrain.js +47 -0
  117. package/dist/memory/MemoryBrain.js.map +1 -1
  118. package/dist/memory/MemoryFabric.d.ts +1 -0
  119. package/dist/memory/MemoryFabric.js +12 -8
  120. package/dist/memory/MemoryFabric.js.map +1 -1
  121. package/dist/memory/MemoryLearning.d.ts +1 -0
  122. package/dist/memory/MemoryLearning.js +6 -3
  123. package/dist/memory/MemoryLearning.js.map +1 -1
  124. package/dist/memory/MemoryProviders.d.ts +8 -1
  125. package/dist/memory/MemoryProviders.js +143 -29
  126. package/dist/memory/MemoryProviders.js.map +1 -1
  127. package/dist/output/HTMLDocumentRenderer.d.ts +9 -0
  128. package/dist/output/HTMLDocumentRenderer.js +19 -0
  129. package/dist/output/HTMLDocumentRenderer.js.map +1 -1
  130. package/dist/review/FreshContextVerifier.d.ts +35 -0
  131. package/dist/review/FreshContextVerifier.js +120 -0
  132. package/dist/review/FreshContextVerifier.js.map +1 -0
  133. package/dist/review/JsonLlmClient.d.ts +37 -0
  134. package/dist/review/JsonLlmClient.js +94 -0
  135. package/dist/review/JsonLlmClient.js.map +1 -0
  136. package/dist/review/LlmJudge.d.ts +61 -0
  137. package/dist/review/LlmJudge.js +167 -0
  138. package/dist/review/LlmJudge.js.map +1 -0
  139. package/dist/runtime/AiOsRuntime.d.ts +14 -1
  140. package/dist/runtime/AiOsRuntime.js +59 -3
  141. package/dist/runtime/AiOsRuntime.js.map +1 -1
  142. package/dist/runtime/RuntimeDoctor.js +3 -1
  143. package/dist/runtime/RuntimeDoctor.js.map +1 -1
  144. package/dist/runtime/RuntimeEvidenceLedger.d.ts +6 -0
  145. package/dist/runtime/RuntimeEvidenceLedger.js +52 -1
  146. package/dist/runtime/RuntimeEvidenceLedger.js.map +1 -1
  147. package/dist/runtime/SessionLedger.d.ts +2 -0
  148. package/dist/runtime/SessionLedger.js +4 -0
  149. package/dist/runtime/SessionLedger.js.map +1 -1
  150. package/dist/setup/SetupVerification.js +53 -5
  151. package/dist/setup/SetupVerification.js.map +1 -1
  152. package/dist/shield/PolicyCompiler.js +73 -12
  153. package/dist/shield/PolicyCompiler.js.map +1 -1
  154. package/dist/shield/ProtectedPaths.js +4 -2
  155. package/dist/shield/ProtectedPaths.js.map +1 -1
  156. package/dist/skills/SkillCatalog.d.ts +2 -0
  157. package/dist/skills/SkillCatalog.js +8 -0
  158. package/dist/skills/SkillCatalog.js.map +1 -1
  159. package/dist/skills/SkillDoctor.d.ts +19 -2
  160. package/dist/skills/SkillDoctor.js +163 -13
  161. package/dist/skills/SkillDoctor.js.map +1 -1
  162. package/dist/tools/SafeCommandRunner.d.ts +1 -0
  163. package/dist/tools/SafeCommandRunner.js +1 -0
  164. package/dist/tools/SafeCommandRunner.js.map +1 -1
  165. package/dist/tools/ToolCapabilityRegistry.js +25 -3
  166. package/dist/tools/ToolCapabilityRegistry.js.map +1 -1
  167. package/dist/tools/ToolOrchestrator.js +21 -0
  168. package/dist/tools/ToolOrchestrator.js.map +1 -1
  169. package/dist/version.d.ts +1 -1
  170. package/dist/version.js +1 -1
  171. package/dist/workflow/AgentLoopReadiness.d.ts +103 -0
  172. package/dist/workflow/AgentLoopReadiness.js +371 -0
  173. package/dist/workflow/AgentLoopReadiness.js.map +1 -0
  174. package/dist/workflow/BoundaryEnforcement.d.ts +60 -0
  175. package/dist/workflow/BoundaryEnforcement.js +182 -0
  176. package/dist/workflow/BoundaryEnforcement.js.map +1 -0
  177. package/dist/workflow/EcosystemReadinessGate.d.ts +46 -0
  178. package/dist/workflow/EcosystemReadinessGate.js +126 -0
  179. package/dist/workflow/EcosystemReadinessGate.js.map +1 -0
  180. package/dist/workflow/EngineeringStandards.js +67 -12
  181. package/dist/workflow/EngineeringStandards.js.map +1 -1
  182. package/dist/workflow/GateCatalog.js +21 -2
  183. package/dist/workflow/GateCatalog.js.map +1 -1
  184. package/dist/workflow/GovernanceTemplatePacks.js +2 -26
  185. package/dist/workflow/GovernanceTemplatePacks.js.map +1 -1
  186. package/dist/workflow/GovernanceTemplates.js +8 -1
  187. package/dist/workflow/GovernanceTemplates.js.map +1 -1
  188. package/dist/workflow/ProfileEnforcement.d.ts +7 -0
  189. package/dist/workflow/ProfileEnforcement.js +12 -0
  190. package/dist/workflow/ProfileEnforcement.js.map +1 -0
  191. package/dist/workflow/ReleaseDeploymentLedger.d.ts +63 -0
  192. package/dist/workflow/ReleaseDeploymentLedger.js +154 -0
  193. package/dist/workflow/ReleaseDeploymentLedger.js.map +1 -0
  194. package/dist/workflow/ReviewAnalyzer.js +50 -3
  195. package/dist/workflow/ReviewAnalyzer.js.map +1 -1
  196. package/dist/workflow/ReviewStore.d.ts +10 -0
  197. package/dist/workflow/ReviewStore.js.map +1 -1
  198. package/dist/workflow/SessionPreamble.d.ts +7 -0
  199. package/dist/workflow/SessionPreamble.js +48 -9
  200. package/dist/workflow/SessionPreamble.js.map +1 -1
  201. package/dist/workflow/SurfaceCoverage.d.ts +19 -0
  202. package/dist/workflow/SurfaceCoverage.js +57 -0
  203. package/dist/workflow/SurfaceCoverage.js.map +1 -0
  204. package/dist/workflow/VerificationCommands.d.ts +1 -0
  205. package/dist/workflow/VerificationCommands.js.map +1 -1
  206. package/dist/workflow/VerificationProfile.d.ts +5 -0
  207. package/dist/workflow/VerificationProfile.js +26 -0
  208. package/dist/workflow/VerificationProfile.js.map +1 -1
  209. package/dist/workflow/VerificationSchema.d.ts +3 -0
  210. package/dist/workflow/VerificationSchema.js +6 -0
  211. package/dist/workflow/VerificationSchema.js.map +1 -1
  212. package/dist/workflow/WorkflowEffectiveness.d.ts +97 -0
  213. package/dist/workflow/WorkflowEffectiveness.js +302 -0
  214. package/dist/workflow/WorkflowEffectiveness.js.map +1 -0
  215. package/dist/workflow/WorkflowEffectivenessRenderer.d.ts +2 -0
  216. package/dist/workflow/WorkflowEffectivenessRenderer.js +67 -0
  217. package/dist/workflow/WorkflowEffectivenessRenderer.js.map +1 -0
  218. package/dist/workflow/WorkflowEffectivenessScoring.d.ts +6 -0
  219. package/dist/workflow/WorkflowEffectivenessScoring.js +243 -0
  220. package/dist/workflow/WorkflowEffectivenessScoring.js.map +1 -0
  221. package/dist/workflow/gates/EnhancedGates.js +2 -0
  222. package/dist/workflow/gates/EnhancedGates.js.map +1 -1
  223. package/dist/workflow/gates/GateSystem.d.ts +16 -0
  224. package/dist/workflow/gates/GateSystem.js +208 -41
  225. package/dist/workflow/gates/GateSystem.js.map +1 -1
  226. package/dist/workflow/gates/MetaGovernanceGates.js +269 -8
  227. package/dist/workflow/gates/MetaGovernanceGates.js.map +1 -1
  228. package/dist/workflow/gates/TestIntegrityGate.d.ts +51 -0
  229. package/dist/workflow/gates/TestIntegrityGate.js +175 -0
  230. package/dist/workflow/gates/TestIntegrityGate.js.map +1 -0
  231. package/dist/workflow/types.d.ts +1 -1
  232. package/docs/guides/DEVELOPMENT_WORKFLOW.md +28 -0
  233. package/docs/reference/cli.md +2 -1
  234. package/docs/start/agent-governance-demo.md +1 -1
  235. package/docs/workflow/E2E_EXAMPLE.md +133 -0
  236. package/docs/workflow/README.md +7 -1
  237. package/docs/workflow/TEMPLATE_GUIDE.md +162 -0
  238. package/docs/workflow/templates/github-actions-scale-preflight.yml +4 -1
  239. package/docs/workflow/templates/plan.md +26 -0
  240. package/docs/workflow/templates/spec.md +28 -0
  241. package/package.json +7 -3
  242. package/scripts/workflow/run-vitest.mjs +123 -0
@@ -7,12 +7,32 @@ import { registerMetaGovernanceGates } from './MetaGovernanceGates.js';
7
7
  import { registerEnhancedGates } from './EnhancedGates.js';
8
8
  import { META_GOVERNANCE_GATE_STAGES, ENHANCED_GATE_STAGES } from '../GateCatalog.js';
9
9
  import { createHash } from 'node:crypto';
10
+ import { mkdtempSync } from 'node:fs';
11
+ import { tmpdir } from 'node:os';
12
+ import { join } from 'node:path';
10
13
  import { RuntimeEvidenceLedger } from '../../runtime/RuntimeEvidenceLedger.js';
11
14
  import { compressCommandOutput } from '../../tools/CommandOutputCompressor.js';
12
15
  import { CommandRunLedger } from '../../tools/CommandRunLedger.js';
13
16
  import { auditDependencies } from '../../guardrails/DependencyAuditor.js';
14
17
  import { runSafeCommand } from '../../tools/SafeCommandRunner.js';
15
18
  import { logger } from '../../core/logger.js';
19
+ const DEFAULT_BUILD_TIMEOUT_MS = 120_000;
20
+ const DEFAULT_LINT_TIMEOUT_MS = 60_000;
21
+ const DEFAULT_TEST_TIMEOUT_MS = 900_000;
22
+ const DEFAULT_COVERAGE_TIMEOUT_MS = 300_000;
23
+ const DEFAULT_PRODUCT_SMOKE_TIMEOUT_MS = 180_000;
24
+ const GATE_TIMEOUT_ENV = {
25
+ G0: 'SCALE_GATE_BUILD_TIMEOUT_MS',
26
+ G4: 'SCALE_GATE_LINT_TIMEOUT_MS',
27
+ G5: 'SCALE_GATE_TEST_TIMEOUT_MS',
28
+ G6: 'SCALE_GATE_COVERAGE_TIMEOUT_MS',
29
+ G8: 'SCALE_GATE_SMOKE_TIMEOUT_MS',
30
+ };
31
+ export function resolveGateTimeoutMs(stage, fallbackMs) {
32
+ const value = process.env[GATE_TIMEOUT_ENV[stage] ?? ''] ?? process.env.SCALE_GATE_COMMAND_TIMEOUT_MS;
33
+ const parsed = Number.parseInt(value ?? '', 10);
34
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallbackMs;
35
+ }
16
36
  function tail(value, maxLength = 1000) {
17
37
  return value.length > maxLength ? value.slice(-maxLength) : value;
18
38
  }
@@ -22,7 +42,7 @@ function sha256(value) {
22
42
  export async function runShellCommand(command, timeout, cwd = process.cwd(), options = {}) {
23
43
  const start = Date.now();
24
44
  try {
25
- const result = await runSafeCommand(command, { timeout, cwd });
45
+ const result = await runSafeCommand(command, { timeout, cwd, env: options.env });
26
46
  const end = Date.now();
27
47
  return finalizeCommandResult(command, {
28
48
  code: result.exitCode,
@@ -98,8 +118,10 @@ export class GateSystem {
98
118
  this.gates = new Map();
99
119
  this.results = new Map();
100
120
  this.eventBus = eventBus;
101
- this.evidenceStore = new EvidenceStore();
102
- this.commands = detectVerificationCommands(commandConfig.cwd ?? process.cwd(), commandConfig);
121
+ this.rootDir = commandConfig.cwd ?? process.cwd();
122
+ this.scaleDir = commandConfig.scaleDir ?? commandConfig.runtimeEvidence?.scaleDir ?? '.scale';
123
+ this.evidenceStore = new EvidenceStore(this.scaleDir);
124
+ this.commands = detectVerificationCommands(this.rootDir, commandConfig);
103
125
  this.artifactWriter = artifactWriter ?? new WorkflowArtifactWriter();
104
126
  this.registerDefaultGates();
105
127
  }
@@ -132,9 +154,10 @@ export class GateSystem {
132
154
  if (cacheableGates.includes(stage)) {
133
155
  try {
134
156
  const { ScanCache } = await import('../../cache/ScanCache.js');
135
- const scanCache = new ScanCache();
157
+ const scanCache = new ScanCache(this.rootDir);
136
158
  const changedFiles = await this.getChangedFiles();
137
159
  const fileHashes = scanCache.hashFiles(changedFiles);
160
+ fileHashes['__gate_context__'] = this.cacheContextForGate(stage);
138
161
  const cacheKey = scanCache.computeKey(fileHashes);
139
162
  const cached = scanCache.get(stage, cacheKey);
140
163
  if (cached) {
@@ -192,7 +215,8 @@ export class GateSystem {
192
215
  return result;
193
216
  }
194
217
  }
195
- catch {
218
+ catch (err) {
219
+ logger.debug({ err, stage }, 'Gate cache lookup failed; executing gate directly');
196
220
  // Cache infrastructure failed — fall through to direct execution
197
221
  }
198
222
  }
@@ -250,7 +274,8 @@ export class GateSystem {
250
274
  const record = this.evidenceStore.saveGateResult(result);
251
275
  result.evidenceRecordId = record.id;
252
276
  }
253
- catch {
277
+ catch (err) {
278
+ logger.debug({ err, gate: result.gate }, 'Gate evidence persistence failed');
254
279
  // Evidence persistence must not mask the gate decision itself.
255
280
  }
256
281
  }
@@ -309,14 +334,36 @@ export class GateSystem {
309
334
  }
310
335
  async getChangedFiles() {
311
336
  const { execSync } = await import('node:child_process');
337
+ const { resolve } = await import('node:path');
312
338
  try {
313
- const output = execSync('git diff --name-only HEAD', { encoding: 'utf-8', stdio: 'pipe' });
314
- return output.trim().split('\n').filter(Boolean);
339
+ const output = execSync('git diff --name-only HEAD', { cwd: this.rootDir, encoding: 'utf-8', stdio: 'pipe' });
340
+ return output.trim().split('\n').filter(Boolean).map(file => resolve(this.rootDir, file));
315
341
  }
316
342
  catch {
317
343
  return [];
318
344
  }
319
345
  }
346
+ cacheContextForGate(stage) {
347
+ const commandByStage = {
348
+ G0: this.commands.build,
349
+ G4: this.commands.lint,
350
+ G5: this.commands.test,
351
+ G6: this.commands.coverage,
352
+ };
353
+ const command = commandByStage[stage];
354
+ return JSON.stringify({
355
+ gateCacheSchema: 3,
356
+ rootDir: this.rootDir,
357
+ scaleDir: this.scaleDir,
358
+ stage,
359
+ command: command?.command,
360
+ source: command?.source,
361
+ reason: command?.reason,
362
+ cwd: command?.cwd,
363
+ tddStrict: this.commands.tddStrict,
364
+ tddEvidence: this.commands.tddEvidence,
365
+ });
366
+ }
320
367
  registerDefaultGates() {
321
368
  this.registerGate(new ExplorationGate(this.artifactWriter));
322
369
  this.registerGate(new PlanningGate(this.artifactWriter));
@@ -325,7 +372,11 @@ export class GateSystem {
325
372
  this.registerGate(new LintGate(this.commands.lint, this.commands.runtimeEvidence));
326
373
  this.registerGate(new TestGate(this.commands.test, this.commands.runtimeEvidence));
327
374
  this.registerGate(new CoverageGate(this.commands.coverage, this.commands.runtimeEvidence));
328
- this.registerGate(new SecurityGate());
375
+ this.registerGate(new SecurityGate({
376
+ rootDir: this.rootDir,
377
+ scaleDir: this.scaleDir,
378
+ changedFilesProvider: () => this.getChangedFiles(),
379
+ }));
329
380
  this.registerGate(new ProductSmokeGate(this.commands.smoke, this.commands.runtimeEvidence));
330
381
  }
331
382
  }
@@ -385,10 +436,16 @@ function commandEvidence(label, command, passed, commandResult, fallbackDetail =
385
436
  });
386
437
  }
387
438
  function gateCommandOptions(stage, command, runtimeEvidence) {
388
- if (!runtimeEvidence)
389
- return {};
390
- return {
391
- commandRunEvidence: {
439
+ const options = {};
440
+ if (stage === 'G5') {
441
+ const isolatedScaleDir = mkdtempSync(join(tmpdir(), 'scale-g5-'));
442
+ options.env = {
443
+ SCALE_DIR: isolatedScaleDir,
444
+ SCALE_PROJECT_DIR: runtimeEvidence?.projectDir ?? command.cwd ?? process.cwd(),
445
+ };
446
+ }
447
+ if (runtimeEvidence) {
448
+ options.commandRunEvidence = {
392
449
  projectDir: runtimeEvidence.projectDir ?? command.cwd ?? process.cwd(),
393
450
  scaleDir: runtimeEvidence.scaleDir,
394
451
  taskId: runtimeEvidence.taskId,
@@ -396,8 +453,9 @@ function gateCommandOptions(stage, command, runtimeEvidence) {
396
453
  profile: runtimeEvidence.profile,
397
454
  gate: stage,
398
455
  source: command.source,
399
- },
400
- };
456
+ };
457
+ }
458
+ return options;
401
459
  }
402
460
  export class ExplorationGate {
403
461
  constructor(artifactWriter) {
@@ -509,7 +567,8 @@ export class ExplorationGate {
509
567
  await fs.access(candidate);
510
568
  return candidate;
511
569
  }
512
- catch {
570
+ catch (err) {
571
+ logger.debug({ err, candidate }, 'Knowledge file candidate not found');
513
572
  // Try the next platform-specific knowledge file.
514
573
  }
515
574
  }
@@ -522,7 +581,8 @@ export class ExplorationGate {
522
581
  await fs.access(candidate);
523
582
  return true;
524
583
  }
525
- catch {
584
+ catch (err) {
585
+ logger.debug({ err, candidate }, 'Knowledge graph artifact candidate not found');
526
586
  // Try the next graphify artifact candidate.
527
587
  }
528
588
  }
@@ -595,7 +655,8 @@ export class PlanningGate {
595
655
  const entries = await fs.readdir(specDir);
596
656
  return entries.some(entry => entry.endsWith('.md'));
597
657
  }
598
- catch {
658
+ catch (err) {
659
+ logger.debug({ err }, 'Security scan skipped unreadable path');
599
660
  return false;
600
661
  }
601
662
  }
@@ -738,7 +799,7 @@ export class BuildGate {
738
799
  const blockers = [];
739
800
  let commandResult = null;
740
801
  try {
741
- commandResult = await runShellCommand(this.command.command, 120000, this.command.cwd, gateCommandOptions(this.stage, this.command, this.runtimeEvidence));
802
+ commandResult = await runShellCommand(this.command.command, resolveGateTimeoutMs(this.stage, DEFAULT_BUILD_TIMEOUT_MS), this.command.cwd, gateCommandOptions(this.stage, this.command, this.runtimeEvidence));
742
803
  if (commandResult.code !== 0) {
743
804
  blockers.push(`Build failed: ${commandResult.stderr}`);
744
805
  }
@@ -776,7 +837,7 @@ export class LintGate {
776
837
  const blockers = [];
777
838
  let commandResult = null;
778
839
  try {
779
- commandResult = await runShellCommand(this.command.command, 60000, this.command.cwd, gateCommandOptions(this.stage, this.command, this.runtimeEvidence));
840
+ commandResult = await runShellCommand(this.command.command, resolveGateTimeoutMs(this.stage, DEFAULT_LINT_TIMEOUT_MS), this.command.cwd, gateCommandOptions(this.stage, this.command, this.runtimeEvidence));
780
841
  if (commandResult.code !== 0) {
781
842
  blockers.push(`Lint failed: ${commandResult.stderr}`);
782
843
  }
@@ -814,7 +875,7 @@ export class TestGate {
814
875
  const blockers = [];
815
876
  let commandResult = null;
816
877
  try {
817
- commandResult = await runShellCommand(this.command.command, 120000, this.command.cwd, gateCommandOptions(this.stage, this.command, this.runtimeEvidence));
878
+ commandResult = await runShellCommand(this.command.command, resolveGateTimeoutMs(this.stage, DEFAULT_TEST_TIMEOUT_MS), this.command.cwd, gateCommandOptions(this.stage, this.command, this.runtimeEvidence));
818
879
  if (commandResult.code !== 0) {
819
880
  blockers.push(`Tests failed: ${commandResult.stderr}`);
820
881
  }
@@ -853,13 +914,12 @@ export class CoverageGate {
853
914
  let detail = '';
854
915
  let commandResult = null;
855
916
  try {
856
- commandResult = await runShellCommand(this.command.command, 120000, this.command.cwd, gateCommandOptions(this.stage, this.command, this.runtimeEvidence));
917
+ commandResult = await runShellCommand(this.command.command, resolveGateTimeoutMs(this.stage, DEFAULT_COVERAGE_TIMEOUT_MS), this.command.cwd, gateCommandOptions(this.stage, this.command, this.runtimeEvidence));
857
918
  if (commandResult.code !== 0) {
858
919
  blockers.push(`Coverage command failed: ${commandResult.stderr}`);
859
920
  }
860
- const coverageMatch = commandResult.stdout.match(/All files[^|]*\|[^|]*\|[^|]*\|[^|]*\|[^|]*\|\s*(\d+\.?\d*)/);
861
- if (coverageMatch) {
862
- const coverage = parseFloat(coverageMatch[1]);
921
+ const coverage = parseCoveragePercentage(commandResult.stdout);
922
+ if (coverage !== null) {
863
923
  detail = `Coverage: ${coverage}%`;
864
924
  if (coverage < 80) {
865
925
  blockers.push(`Coverage ${coverage}% below 80% threshold`);
@@ -890,6 +950,26 @@ export class CoverageGate {
890
950
  };
891
951
  }
892
952
  }
953
+ function parseCoveragePercentage(output) {
954
+ const allFilesLine = output
955
+ .split(/\r?\n/)
956
+ .find(line => /^\s*All files\s*\|/.test(line));
957
+ if (!allFilesLine)
958
+ return null;
959
+ const values = allFilesLine
960
+ .split('|')
961
+ .slice(1)
962
+ .map(part => part.trim().match(/^(\d+(?:\.\d+)?)/)?.[1])
963
+ .filter((value) => Boolean(value))
964
+ .map(Number);
965
+ if (values.length >= 5)
966
+ return values[values.length - 1];
967
+ if (values.length >= 4)
968
+ return values[3];
969
+ if (values.length > 0)
970
+ return values[values.length - 1];
971
+ return null;
972
+ }
893
973
  export class SecurityGate {
894
974
  constructor(options = {}) {
895
975
  this.stage = 'G7';
@@ -905,9 +985,12 @@ export class SecurityGate {
905
985
  this.dependencyAudit = options.dependencyAudit ?? true;
906
986
  this.dependencyAuditMode = options.dependencyAuditMode;
907
987
  this.dependencyAuditChangedPackages = options.dependencyAuditChangedPackages;
988
+ this.changedFiles = options.changedFiles;
989
+ this.changedFilesProvider = options.changedFilesProvider;
908
990
  }
909
991
  async execute() {
910
- const findings = await this.scan();
992
+ const changedFiles = await this.resolveChangedFiles();
993
+ const findings = await this.scan(changedFiles);
911
994
  const dependencyReport = this.dependencyAudit ? auditDependencies({
912
995
  projectDir: this.rootDir,
913
996
  scaleDir: this.scaleDir,
@@ -915,11 +998,12 @@ export class SecurityGate {
915
998
  changedPackages: this.dependencyAuditChangedPackages,
916
999
  }) : null;
917
1000
  const blockers = findings
918
- .filter(finding => finding.severity === 'CRITICAL' || (this.strict && finding.severity === 'HIGH'))
1001
+ .filter(finding => this.blocksFinding(finding, changedFiles))
919
1002
  .map(finding => `${finding.severity} ${finding.ruleId} in ${finding.file}:${finding.line} - ${finding.description}`);
920
1003
  blockers.push(...(dependencyReport?.blockers ?? []));
921
1004
  const passed = blockers.length === 0;
922
1005
  const summary = this.summarize(findings);
1006
+ const changedHigh = findings.filter(finding => finding.severity === 'HIGH' && this.isChangedFinding(finding, changedFiles)).length;
923
1007
  const evidenceItems = [
924
1008
  createEvidence({
925
1009
  kind: 'scan',
@@ -927,14 +1011,14 @@ export class SecurityGate {
927
1011
  passed,
928
1012
  path: this.scanDirs.join(','),
929
1013
  detail: findings.length > 0
930
- ? `${findings.length} finding(s): critical=${summary.CRITICAL}, high=${summary.HIGH}, medium=${summary.MEDIUM}, low=${summary.LOW}, strict=${this.strict}`
1014
+ ? `${findings.length} finding(s): critical=${summary.CRITICAL}, high=${summary.HIGH}, medium=${summary.MEDIUM}, low=${summary.LOW}, changedHigh=${changedHigh}, strict=${this.strict}`
931
1015
  : 'no built-in security findings detected',
932
1016
  source: 'built-in-security-scan',
933
1017
  }),
934
1018
  ...findings.slice(0, this.maxFindings).map(finding => createEvidence({
935
1019
  kind: 'scan',
936
1020
  label: `Security finding ${finding.ruleId}`,
937
- passed: finding.severity !== 'CRITICAL' && finding.severity !== 'HIGH',
1021
+ passed: !this.blocksFinding(finding, changedFiles),
938
1022
  path: finding.file,
939
1023
  detail: `${finding.severity} line ${finding.line}: ${finding.description}; ${finding.evidence}`,
940
1024
  source: 'built-in-security-scan',
@@ -950,14 +1034,20 @@ export class SecurityGate {
950
1034
  blockers
951
1035
  };
952
1036
  }
953
- async scan() {
1037
+ async scan(changedFiles) {
954
1038
  const findings = [];
955
1039
  try {
956
- const fs = await import('fs/promises');
957
- const { join, relative } = await import('path');
958
- const files = [];
1040
+ const fs = await import('node:fs/promises');
1041
+ const { join, relative } = await import('node:path');
1042
+ const files = new Set();
1043
+ for (const file of changedFiles) {
1044
+ if (!/\.(ts|tsx|js|jsx|mjs|cjs)$/.test(file))
1045
+ continue;
1046
+ files.add(join(this.rootDir, file));
1047
+ }
959
1048
  for (const dir of this.scanDirs) {
960
- files.push(...await this.walkDir(join(this.rootDir, dir)));
1049
+ for (const file of await this.walkDir(join(this.rootDir, dir)))
1050
+ files.add(file);
961
1051
  }
962
1052
  for (const file of files) {
963
1053
  if (findings.length >= this.maxFindings)
@@ -972,11 +1062,37 @@ export class SecurityGate {
972
1062
  findings.push(...this.scanFile(displayPath, content).slice(0, this.maxFindings - findings.length));
973
1063
  }
974
1064
  }
975
- catch {
1065
+ catch (err) {
1066
+ logger.debug({ err, rootDir: this.rootDir, scanDirs: this.scanDirs }, 'Security scan skipped unreadable path');
976
1067
  // A missing scan directory should not mask the rest of the verification run.
977
1068
  }
978
1069
  return findings;
979
1070
  }
1071
+ async resolveChangedFiles() {
1072
+ const raw = this.changedFilesProvider
1073
+ ? await this.changedFilesProvider()
1074
+ : (this.changedFiles ?? []);
1075
+ return new Set(raw.map(file => this.normalizeChangedFile(file)).filter(Boolean));
1076
+ }
1077
+ normalizeChangedFile(file) {
1078
+ const normalized = file.replace(/\\/g, '/');
1079
+ const root = this.rootDir.replace(/\\/g, '/').replace(/\/$/, '');
1080
+ if (normalized === root)
1081
+ return '';
1082
+ if (normalized.startsWith(`${root}/`))
1083
+ return normalized.slice(root.length + 1);
1084
+ return normalized.replace(/^\.\//, '');
1085
+ }
1086
+ blocksFinding(finding, changedFiles) {
1087
+ if (finding.severity === 'CRITICAL')
1088
+ return true;
1089
+ if (finding.severity !== 'HIGH')
1090
+ return false;
1091
+ return this.strict || this.isChangedFinding(finding, changedFiles);
1092
+ }
1093
+ isChangedFinding(finding, changedFiles) {
1094
+ return changedFiles.has(finding.file);
1095
+ }
980
1096
  scanFile(file, content) {
981
1097
  const findings = [];
982
1098
  const lines = content.split('\n');
@@ -1019,8 +1135,8 @@ export class SecurityGate {
1019
1135
  }
1020
1136
  }
1021
1137
  }
1022
- catch {
1023
- // Ignore unreadable directories.
1138
+ catch (err) {
1139
+ logger.debug({ err, dir }, 'Security scan skipped unreadable directory');
1024
1140
  }
1025
1141
  return results;
1026
1142
  }
@@ -1163,13 +1279,64 @@ export class SecurityGate {
1163
1279
  }
1164
1280
  isRuleDefinition(file, line) {
1165
1281
  const trimmed = line.trim();
1166
- return file.endsWith('GateSystem.ts') && (/^pattern:\s*\/.*\/[dgimsuy]*,?$/.test(trimmed) || /^id:\s*['"`][^'"`]+['"`],?$/.test(trimmed));
1282
+ if (this.isSecurityRuleSource(file) && this.isSecurityRuleDefinitionLine(trimmed)) {
1283
+ return true;
1284
+ }
1285
+ return /\/.*(?:dangerouslySetInnerHTML|\\\.innerHTML|document\\\.write|password|api\[_-\]\?key|secret|token|shell:\s*true|@ts-ignore|catch).*\/[dgimsuy]*\.test\(\s*(?:line|trimmed)\s*\)/i.test(trimmed);
1286
+ }
1287
+ isSecurityRuleSource(file) {
1288
+ return this.isGeneratedShieldHook(file) || [
1289
+ 'src/guardrails/advancedDetectors.ts',
1290
+ 'src/guardrails/ast/confirmers.ts',
1291
+ 'src/guardrails/OWASPDetector.ts',
1292
+ 'src/shield/PolicyCompiler.ts',
1293
+ 'src/shield/ProtectedPaths.ts',
1294
+ 'src/skills/SkillRepository.ts',
1295
+ 'src/cli/shieldCommands.ts',
1296
+ 'src/workflow/gates/GateSystem.ts',
1297
+ 'src/workflow/ReviewAnalyzer.ts',
1298
+ 'src/workflow/SecurityAudit.ts',
1299
+ ].some(ruleSource => file.endsWith(ruleSource));
1300
+ }
1301
+ isGeneratedShieldHook(file) {
1302
+ return /(^|\/)\.claude\/hooks\/shield-[^/]+\.js$/i.test(file);
1303
+ }
1304
+ isSecurityRuleDefinitionLine(trimmed) {
1305
+ if (/^id:\s*['"`][^'"`]+['"`],?$/.test(trimmed))
1306
+ return true;
1307
+ if (/^\{?\s*pattern:\s*\/.*\/[dgimsuy]*,?/.test(trimmed))
1308
+ return true;
1309
+ if (/^patterns:\s*\[/.test(trimmed))
1310
+ return true;
1311
+ if (/^\/.*\/[dgimsuy]*,?(?:\s*\/\/.*)?$/.test(trimmed))
1312
+ return true;
1313
+ if (/^\{?\s*(?:re|pattern):\s*\/.*\/[dgimsuy]*/i.test(trimmed))
1314
+ return true;
1315
+ if (/^(?:const|let|var)\s+\w*pattern\w*\s*=\s*\/.*\/[dgimsuy]*/i.test(trimmed))
1316
+ return true;
1317
+ if (/^(?:const|let|var)\s+\w+\s*=\s*\[.*(?:rm\s+-rf|curl\s*\|\s*bash|wget\s*\|\s*bash|Invoke-Expression|chmod\s+777|git reset --hard|git push --force)/i.test(trimmed))
1318
+ return true;
1319
+ if (/^['"`].*(?:rm\s+-rf|curl\s*\|\s*bash|wget\s*\|\s*bash|Invoke-Expression|chmod\s+777|git reset --hard|git push --force|DROP TABLE|DELETE without WHERE).*['"`],?$/i.test(trimmed))
1320
+ return true;
1321
+ if (/^['"`][^'"`]+['"`](?:\s*,\s*['"`][^'"`]+['"`])*[,]?$/.test(trimmed) && /(?:rm\s+-rf|curl\s*\|\s*bash|wget\s*\|\s*bash|Invoke-Expression|chmod\s+777|git reset --hard|git push --force|DROP TABLE|DELETE without WHERE)/i.test(trimmed))
1322
+ return true;
1323
+ if (/\bexpect:\s*['"`]block['"`]/.test(trimmed) && /\b(?:input|command|label):/.test(trimmed))
1324
+ return true;
1325
+ if (/^(?:name|title|description|recommendation|remediation):\s*['"`].*(?:dangerouslySetInnerHTML|innerHTML|document\.write|eval\(|new Function|curl pipe|shell:\s*true)/i.test(trimmed))
1326
+ return true;
1327
+ return /^(?:if\s*\(|return\s+)?\/?.*(?:dangerouslySetInnerHTML|innerHTML|document\.write|eval\(|new Function|curl|wget|Invoke-WebRequest|Invoke-Expression|rm\s+-rf|shell:\s*true).*\/[dgimsuy]*\.test\(/i.test(trimmed) ||
1328
+ /^(?:\/\/|\/\*\*?|\*)\s*.*(?:dangerouslySetInnerHTML|innerHTML|document\.write|eval\(|new Function|curl pipe|shell:\s*true)/i.test(trimmed);
1167
1329
  }
1168
1330
  isSecurityTestFixture(file, line) {
1169
1331
  if (!this.isTestPath(file))
1170
1332
  return false;
1171
- return /\b(?:text|content|diff|source)\b\s*[:=]/.test(line) &&
1172
- /['"`].*(?:password|api[_-]?key|secret|token|auth|credential|private[_-]?key|git add|shell: true|@ts-ignore|catch)/i.test(line);
1333
+ const riskyFixtureText = /['"`].*(?:password|api[_-]?key|secret|token|auth|credential|private[_-]?key|git add|rm\s+-rf|curl\b.*\|.*(?:bash|sh)|Invoke-WebRequest\b.*\|\s*iex|shell: true|dangerouslySetInnerHTML|innerHTML|document\.write|eval\(|new Function|@ts-ignore|catch)/i.test(line);
1334
+ if (!riskyFixtureText)
1335
+ return false;
1336
+ const fixtureField = /\b(?:text|content|diff|source|command|input|tool_input)\b\s*[:=]/.test(line);
1337
+ const fixtureCodePath = /^\s*['"`][^'"`]+\.(?:ts|tsx|js|jsx|mjs|cjs)['"`]\s*:\s*['"`]/.test(line);
1338
+ const fixtureLiteral = /^\s*['"`].*['"`]\s*,?$/.test(line);
1339
+ return fixtureField || fixtureCodePath || fixtureLiteral;
1173
1340
  }
1174
1341
  }
1175
1342
  function parseProductSmokeReport(commandResult) {
@@ -1213,7 +1380,7 @@ export class ProductSmokeGate {
1213
1380
  const blockers = [];
1214
1381
  let commandResult = null;
1215
1382
  try {
1216
- commandResult = await runShellCommand(this.command.command, 180000, this.command.cwd, gateCommandOptions(this.stage, this.command, this.runtimeEvidence));
1383
+ commandResult = await runShellCommand(this.command.command, resolveGateTimeoutMs(this.stage, DEFAULT_PRODUCT_SMOKE_TIMEOUT_MS), this.command.cwd, gateCommandOptions(this.stage, this.command, this.runtimeEvidence));
1217
1384
  if (commandResult.code !== 0) {
1218
1385
  blockers.push(`Product smoke failed: ${commandResult.stderr || commandResult.stdout || `exit code ${commandResult.code}`}`);
1219
1386
  }