@hongmaple0820/scale-engine 0.40.1 → 0.43.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 (228) hide show
  1. package/README.md +30 -2
  2. package/dist/api/cli.js +286 -7
  3. package/dist/api/cli.js.map +1 -1
  4. package/dist/api/doctor.js +1 -1
  5. package/dist/api/doctor.js.map +1 -1
  6. package/dist/api/quickstart.d.ts +11 -0
  7. package/dist/api/quickstart.js +98 -1
  8. package/dist/api/quickstart.js.map +1 -1
  9. package/dist/artifact/fsmDefinitions.js +15 -2
  10. package/dist/artifact/fsmDefinitions.js.map +1 -1
  11. package/dist/artifact/types.d.ts +1 -1
  12. package/dist/artifact/types.js.map +1 -1
  13. package/dist/bootstrap/DependencyBootstrap.d.ts +1 -0
  14. package/dist/bootstrap/DependencyBootstrap.js +137 -25
  15. package/dist/bootstrap/DependencyBootstrap.js.map +1 -1
  16. package/dist/cache/ScanCache.d.ts +41 -0
  17. package/dist/cache/ScanCache.js +120 -0
  18. package/dist/cache/ScanCache.js.map +1 -0
  19. package/dist/capabilities/BrowserQACapability.d.ts +14 -0
  20. package/dist/capabilities/BrowserQACapability.js +94 -0
  21. package/dist/capabilities/BrowserQACapability.js.map +1 -1
  22. package/dist/capabilities/InstalledSkillsIntegration.js +29 -9
  23. package/dist/capabilities/InstalledSkillsIntegration.js.map +1 -1
  24. package/dist/cli/autofixCommands.d.ts +22 -0
  25. package/dist/cli/autofixCommands.js +32 -0
  26. package/dist/cli/autofixCommands.js.map +1 -0
  27. package/dist/cli/cortexCommands.d.ts +71 -0
  28. package/dist/cli/cortexCommands.js +335 -0
  29. package/dist/cli/cortexCommands.js.map +1 -0
  30. package/dist/cli/costCommands.d.ts +13 -0
  31. package/dist/cli/costCommands.js +48 -0
  32. package/dist/cli/costCommands.js.map +1 -0
  33. package/dist/cli/orchCommands.d.ts +43 -0
  34. package/dist/cli/orchCommands.js +135 -0
  35. package/dist/cli/orchCommands.js.map +1 -0
  36. package/dist/cli/phaseCommands.js +1 -2
  37. package/dist/cli/phaseCommands.js.map +1 -1
  38. package/dist/cli/qaCommands.d.ts +22 -0
  39. package/dist/cli/qaCommands.js +84 -0
  40. package/dist/cli/qaCommands.js.map +1 -0
  41. package/dist/cli/quickstartCommands.d.ts +17 -0
  42. package/dist/cli/quickstartCommands.js +47 -0
  43. package/dist/cli/quickstartCommands.js.map +1 -0
  44. package/dist/cli/shieldCommands.d.ts +30 -0
  45. package/dist/cli/shieldCommands.js +212 -0
  46. package/dist/cli/shieldCommands.js.map +1 -0
  47. package/dist/cli/tuiCommands.d.ts +7 -0
  48. package/dist/cli/tuiCommands.js +33 -0
  49. package/dist/cli/tuiCommands.js.map +1 -0
  50. package/dist/config/profiles.js +26 -0
  51. package/dist/config/profiles.js.map +1 -1
  52. package/dist/context/ContextBudget.js +2 -2
  53. package/dist/core/GbrainRuntime.d.ts +25 -0
  54. package/dist/core/GbrainRuntime.js +270 -0
  55. package/dist/core/GbrainRuntime.js.map +1 -0
  56. package/dist/cortex/GovernanceMetrics.d.ts +66 -0
  57. package/dist/cortex/GovernanceMetrics.js +230 -0
  58. package/dist/cortex/GovernanceMetrics.js.map +1 -0
  59. package/dist/cortex/InstinctExtractor.d.ts +61 -0
  60. package/dist/cortex/InstinctExtractor.js +184 -0
  61. package/dist/cortex/InstinctExtractor.js.map +1 -0
  62. package/dist/cortex/InstinctStore.d.ts +54 -0
  63. package/dist/cortex/InstinctStore.js +266 -0
  64. package/dist/cortex/InstinctStore.js.map +1 -0
  65. package/dist/cortex/ReflexionEngine.d.ts +34 -0
  66. package/dist/cortex/ReflexionEngine.js +157 -0
  67. package/dist/cortex/ReflexionEngine.js.map +1 -0
  68. package/dist/cortex/SessionInjector.d.ts +44 -0
  69. package/dist/cortex/SessionInjector.js +127 -0
  70. package/dist/cortex/SessionInjector.js.map +1 -0
  71. package/dist/cortex/adapters/ClaudeAdapter.d.ts +17 -0
  72. package/dist/cortex/adapters/ClaudeAdapter.js +61 -0
  73. package/dist/cortex/adapters/ClaudeAdapter.js.map +1 -0
  74. package/dist/cortex/adapters/CodexAdapter.d.ts +10 -0
  75. package/dist/cortex/adapters/CodexAdapter.js +52 -0
  76. package/dist/cortex/adapters/CodexAdapter.js.map +1 -0
  77. package/dist/cortex/adapters/CursorAdapter.d.ts +10 -0
  78. package/dist/cortex/adapters/CursorAdapter.js +46 -0
  79. package/dist/cortex/adapters/CursorAdapter.js.map +1 -0
  80. package/dist/cortex/adapters/GeminiAdapter.d.ts +11 -0
  81. package/dist/cortex/adapters/GeminiAdapter.js +48 -0
  82. package/dist/cortex/adapters/GeminiAdapter.js.map +1 -0
  83. package/dist/env/EnvironmentDoctor.js +221 -5
  84. package/dist/env/EnvironmentDoctor.js.map +1 -1
  85. package/dist/eval/BenchmarkPublisher.d.ts +25 -0
  86. package/dist/eval/BenchmarkPublisher.js +27 -0
  87. package/dist/eval/BenchmarkPublisher.js.map +1 -0
  88. package/dist/guardrails/DependencyAuditor.js +10 -1
  89. package/dist/guardrails/DependencyAuditor.js.map +1 -1
  90. package/dist/memory/MemoryProviders.js +38 -91
  91. package/dist/memory/MemoryProviders.js.map +1 -1
  92. package/dist/orchestrator/OrchestratorDaemon.d.ts +44 -0
  93. package/dist/orchestrator/OrchestratorDaemon.js +150 -0
  94. package/dist/orchestrator/OrchestratorDaemon.js.map +1 -0
  95. package/dist/orchestrator/PolicyLoader.d.ts +80 -0
  96. package/dist/orchestrator/PolicyLoader.js +229 -0
  97. package/dist/orchestrator/PolicyLoader.js.map +1 -0
  98. package/dist/orchestrator/ReconciliationLoop.d.ts +71 -0
  99. package/dist/orchestrator/ReconciliationLoop.js +266 -0
  100. package/dist/orchestrator/ReconciliationLoop.js.map +1 -0
  101. package/dist/orchestrator/TrackerAdapter.d.ts +60 -0
  102. package/dist/orchestrator/TrackerAdapter.js +147 -0
  103. package/dist/orchestrator/TrackerAdapter.js.map +1 -0
  104. package/dist/orchestrator/WorkspaceManager.d.ts +66 -0
  105. package/dist/orchestrator/WorkspaceManager.js +257 -0
  106. package/dist/orchestrator/WorkspaceManager.js.map +1 -0
  107. package/dist/qa/BrowserDaemon.d.ts +23 -0
  108. package/dist/qa/BrowserDaemon.js +79 -0
  109. package/dist/qa/BrowserDaemon.js.map +1 -0
  110. package/dist/qa/E2ETestOrchestrator.d.ts +14 -0
  111. package/dist/qa/E2ETestOrchestrator.js +19 -0
  112. package/dist/qa/E2ETestOrchestrator.js.map +1 -0
  113. package/dist/review/CrossModelReviewer.d.ts +35 -0
  114. package/dist/review/CrossModelReviewer.js +75 -0
  115. package/dist/review/CrossModelReviewer.js.map +1 -0
  116. package/dist/review/ReviewAggregator.d.ts +13 -0
  117. package/dist/review/ReviewAggregator.js +28 -0
  118. package/dist/review/ReviewAggregator.js.map +1 -0
  119. package/dist/review/reviewCommands.d.ts +15 -0
  120. package/dist/review/reviewCommands.js +24 -0
  121. package/dist/review/reviewCommands.js.map +1 -0
  122. package/dist/routing/LocalModelProvider.d.ts +11 -0
  123. package/dist/routing/LocalModelProvider.js +21 -0
  124. package/dist/routing/LocalModelProvider.js.map +1 -0
  125. package/dist/routing/ModelRouter.d.ts +12 -0
  126. package/dist/routing/ModelRouter.js +31 -4
  127. package/dist/routing/ModelRouter.js.map +1 -1
  128. package/dist/runtime/AiOsRuntime.d.ts +1 -0
  129. package/dist/runtime/AiOsRuntime.js +15 -0
  130. package/dist/runtime/AiOsRuntime.js.map +1 -1
  131. package/dist/runtime/CostAnalyzer.d.ts +53 -0
  132. package/dist/runtime/CostAnalyzer.js +160 -0
  133. package/dist/runtime/CostAnalyzer.js.map +1 -0
  134. package/dist/runtime/CostOptimizer.d.ts +11 -0
  135. package/dist/runtime/CostOptimizer.js +21 -0
  136. package/dist/runtime/CostOptimizer.js.map +1 -0
  137. package/dist/runtime/ModelUsageLedger.d.ts +53 -2
  138. package/dist/runtime/ModelUsageLedger.js +243 -39
  139. package/dist/runtime/ModelUsageLedger.js.map +1 -1
  140. package/dist/setup/SetupVerification.d.ts +42 -0
  141. package/dist/setup/SetupVerification.js +180 -0
  142. package/dist/setup/SetupVerification.js.map +1 -0
  143. package/dist/shield/PolicyCompiler.d.ts +70 -0
  144. package/dist/shield/PolicyCompiler.js +540 -0
  145. package/dist/shield/PolicyCompiler.js.map +1 -0
  146. package/dist/shield/ProtectedPaths.d.ts +39 -0
  147. package/dist/shield/ProtectedPaths.js +179 -0
  148. package/dist/shield/ProtectedPaths.js.map +1 -0
  149. package/dist/shield/ShieldProtocol.d.ts +50 -0
  150. package/dist/shield/ShieldProtocol.js +103 -0
  151. package/dist/shield/ShieldProtocol.js.map +1 -0
  152. package/dist/skills/SkillMdStandard.d.ts +33 -0
  153. package/dist/skills/SkillMdStandard.js +88 -0
  154. package/dist/skills/SkillMdStandard.js.map +1 -0
  155. package/dist/skills/SkillRegistry.d.ts +9 -1
  156. package/dist/skills/SkillRegistry.js +20 -0
  157. package/dist/skills/SkillRegistry.js.map +1 -1
  158. package/dist/skills/interop/GStackInterop.d.ts +15 -0
  159. package/dist/skills/interop/GStackInterop.js +34 -0
  160. package/dist/skills/interop/GStackInterop.js.map +1 -0
  161. package/dist/skills/interop/OMCInterop.d.ts +15 -0
  162. package/dist/skills/interop/OMCInterop.js +34 -0
  163. package/dist/skills/interop/OMCInterop.js.map +1 -0
  164. package/dist/tools/ToolCapabilityRegistry.js +10 -0
  165. package/dist/tools/ToolCapabilityRegistry.js.map +1 -1
  166. package/dist/tui/TuiDashboard.d.ts +3 -0
  167. package/dist/tui/TuiDashboard.js +120 -0
  168. package/dist/tui/TuiDashboard.js.map +1 -0
  169. package/dist/workflow/GateCatalog.d.ts +2 -0
  170. package/dist/workflow/GateCatalog.js +59 -3
  171. package/dist/workflow/GateCatalog.js.map +1 -1
  172. package/dist/workflow/GovernanceTemplatePacks.d.ts +1 -1
  173. package/dist/workflow/GovernanceTemplatePacks.js +15 -0
  174. package/dist/workflow/GovernanceTemplatePacks.js.map +1 -1
  175. package/dist/workflow/TddLoop.d.ts +2 -0
  176. package/dist/workflow/TddLoop.js +2 -0
  177. package/dist/workflow/TddLoop.js.map +1 -1
  178. package/dist/workflow/UpgradeManager.d.ts +10 -1
  179. package/dist/workflow/UpgradeManager.js +55 -0
  180. package/dist/workflow/UpgradeManager.js.map +1 -1
  181. package/dist/workflow/VerificationProfile.d.ts +8 -0
  182. package/dist/workflow/VerificationProfile.js +62 -1
  183. package/dist/workflow/VerificationProfile.js.map +1 -1
  184. package/dist/workflow/VerificationSchema.d.ts +46 -0
  185. package/dist/workflow/VerificationSchema.js +97 -0
  186. package/dist/workflow/VerificationSchema.js.map +1 -0
  187. package/dist/workflow/autofix/AutoFixEngine.d.ts +37 -0
  188. package/dist/workflow/autofix/AutoFixEngine.js +169 -0
  189. package/dist/workflow/autofix/AutoFixEngine.js.map +1 -0
  190. package/dist/workflow/execution/RalphEngine.d.ts +18 -0
  191. package/dist/workflow/execution/RalphEngine.js +22 -0
  192. package/dist/workflow/execution/RalphEngine.js.map +1 -1
  193. package/dist/workflow/gates/EnhancedGates.d.ts +74 -0
  194. package/dist/workflow/gates/EnhancedGates.js +653 -0
  195. package/dist/workflow/gates/EnhancedGates.js.map +1 -0
  196. package/dist/workflow/gates/GateSystem.d.ts +3 -0
  197. package/dist/workflow/gates/GateSystem.js +94 -1
  198. package/dist/workflow/gates/GateSystem.js.map +1 -1
  199. package/dist/workflow/types.d.ts +1 -1
  200. package/docs/README.md +3 -0
  201. package/docs/guides/DEVELOPMENT_WORKFLOW.md +28 -9
  202. package/docs/guides/GETTING_STARTED.md +19 -0
  203. package/docs/guides/MIGRATION.md +119 -0
  204. package/docs/start/quickstart.md +1 -0
  205. package/docs/workflow/GATES_AND_SCORE.md +34 -1
  206. package/docs/workflow/README.md +58 -10
  207. package/package.json +7 -18
  208. package/scripts/workflow/lib/gbrain-runtime.mjs +185 -0
  209. package/scripts/workflow/lib/report-output.mjs +107 -0
  210. package/scripts/workflow/provider-rehearsal.mjs +129 -48
  211. package/scripts/workflow/setup-smoke.mjs +142 -8
  212. package/docs/ACTIVE_SECURITY_VISUAL_GATES.md +0 -87
  213. package/docs/AI_ENGINEERING_OS_POSITIONING.md +0 -607
  214. package/docs/BACKGROUND_HUNTER.md +0 -62
  215. package/docs/CODE_INTELLIGENCE.md +0 -180
  216. package/docs/CONTEXT_BUDGET.md +0 -155
  217. package/docs/DEPENDENCY_AUDIT.md +0 -118
  218. package/docs/EVOLUTION_SHADOW_MODE.md +0 -63
  219. package/docs/GITLAB_FLOW.md +0 -125
  220. package/docs/GOVERNANCE_DASHBOARD.md +0 -85
  221. package/docs/MEMORY_BRAIN.md +0 -104
  222. package/docs/MEMORY_FABRIC.md +0 -161
  223. package/docs/RESOURCE_GOVERNANCE.md +0 -92
  224. package/docs/RUNTIME_EVIDENCE.md +0 -101
  225. package/docs/WORKFLOW_EVAL.md +0 -151
  226. package/image/wechat-public.jpg +0 -0
  227. package/image/wxPay.jpg +0 -0
  228. package/image/zfb.jpg +0 -0
@@ -1,9 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawnSync } from 'node:child_process'
3
- import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
3
+ import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
4
4
  import { tmpdir } from 'node:os'
5
5
  import { dirname, extname, join, resolve } from 'node:path'
6
6
  import { fileURLToPath } from 'node:url'
7
+ import {
8
+ ensureMirroredGbrainInvocation,
9
+ normalizeGbrainSpawnResult,
10
+ resolveDirectWindowsGbrainInvocation,
11
+ shouldRetryWithMirroredGbrain,
12
+ } from './lib/gbrain-runtime.mjs'
13
+ import { summarizeCommandOutput, summarizeCommandRecord } from './lib/report-output.mjs'
7
14
 
8
15
  const scriptDir = dirname(fileURLToPath(import.meta.url))
9
16
  const repoRoot = resolve(scriptDir, '..', '..')
@@ -12,6 +19,7 @@ const runId = `provider-rehearsal-${Date.now()}-${Math.random().toString(16).sli
12
19
  const workRoot = options.outDir ? resolve(options.outDir) : join(tmpdir(), runId)
13
20
  const results = []
14
21
  const RTK_BYPASS_COMMANDS = new Set(['gbrain'])
22
+ const GRAPHIFY_ALLOWED_HIDDEN_TOP_LEVEL_DIRS = new Set(['.github', '.scale', '.storybook', '.vscode', '.cursor'])
15
23
 
16
24
  mkdirSync(workRoot, { recursive: true })
17
25
 
@@ -27,14 +35,34 @@ try {
27
35
  }
28
36
 
29
37
  function runGbrainReplay() {
30
- const healthCheck = runCommand('gbrain-health', 'gbrain', ['list', '-n', '1'], { timeoutMs: 60_000 })
38
+ const init = runCommand('gbrain-init', 'gbrain', ['init', '--pglite', '--no-embedding'], {
39
+ timeoutMs: 120_000,
40
+ env: gbrainCommandEnv(),
41
+ })
42
+ if (init.exitCode !== 0) {
43
+ return capability('gbrain', options.requireGbrain ? 'failed' : 'blocked', {
44
+ reason: failureLine(`${init.stdout}\n${init.stderr}`) || 'gbrain init failed for the isolated smoke brain',
45
+ required: options.requireGbrain,
46
+ commands: [init],
47
+ nextCommands: [
48
+ 'gbrain init --pglite --no-embedding',
49
+ 'gbrain doctor --json',
50
+ 'npm run smoke:gbrain',
51
+ ],
52
+ })
53
+ }
54
+
55
+ const healthCheck = runCommand('gbrain-health', 'gbrain', ['list', '-n', '1'], {
56
+ timeoutMs: 60_000,
57
+ env: gbrainCommandEnv(),
58
+ })
31
59
  const health = evaluateGbrainList(healthCheck)
32
60
  if (!health.available) {
33
61
  return capability('gbrain', options.requireGbrain ? 'failed' : 'blocked', {
34
62
  reason: health.reason || failureLine(`${healthCheck.stdout}\n${healthCheck.stderr}`) || 'gbrain health check failed',
35
63
  required: options.requireGbrain,
36
64
  health,
37
- commands: [healthCheck],
65
+ commands: [init, healthCheck],
38
66
  nextCommands: [
39
67
  'gbrain init --pglite --no-embedding',
40
68
  'gbrain init --supabase',
@@ -50,22 +78,27 @@ function runGbrainReplay() {
50
78
  const sentinel = `scale-engine-gbrain-replay-${Date.now()}-${Math.random().toString(16).slice(2)}`
51
79
  const body = `# ${slug}. Sentinel: ${sentinel}. This page verifies that SCALE can write memory in one process and read/query it in later processes.`
52
80
 
53
- const put = runCommand('gbrain-put', 'gbrain', ['put', slug, '--content', body], { timeoutMs: 60_000 })
54
- const get = runCommand('gbrain-get', 'gbrain', ['get', slug], { timeoutMs: 8_000 })
55
- const query = runCommand('gbrain-query', 'gbrain', ['query', sentinel], { timeoutMs: 8_000 })
81
+ const put = runCommand('gbrain-put', 'gbrain', ['put', slug, '--content', body], { timeoutMs: 60_000, env: gbrainCommandEnv() })
82
+ const get = runCommand('gbrain-get', 'gbrain', ['get', slug], { timeoutMs: 8_000, env: gbrainCommandEnv() })
83
+ const query = runCommand('gbrain-query', 'gbrain', ['query', sentinel], { timeoutMs: 8_000, env: gbrainCommandEnv() })
56
84
  const queryOutput = `${query.stdout}\n${query.stderr}`
57
85
  const search = query.exitCode === 0 && (queryOutput.includes(sentinel) || queryOutput.includes(slug))
58
86
  ? undefined
59
- : runCommand('gbrain-search', 'gbrain', ['search', sentinel], { timeoutMs: 8_000 })
60
- const cleanup = options.keepGbrainPage ? undefined : runCommand('gbrain-delete', 'gbrain', ['delete', slug], { timeoutMs: 60_000 })
87
+ : runCommand('gbrain-search', 'gbrain', ['search', sentinel], { timeoutMs: 8_000, env: gbrainCommandEnv() })
88
+ const cleanup = options.keepGbrainPage ? undefined : runCommand('gbrain-delete', 'gbrain', ['delete', slug], { timeoutMs: 60_000, env: gbrainCommandEnv() })
61
89
 
62
90
  const recallOutput = `${queryOutput}\n${search?.stdout ?? ''}\n${search?.stderr ?? ''}`
63
- const getPassed = (get.exitCode === 0 || get.timedOut) && get.stdout.includes(sentinel)
64
- const recallPassed = (query.exitCode === 0 || query.timedOut || search?.exitCode === 0 || search?.timedOut)
91
+ const getPassed = get.exitCode === 0 && get.stdout.includes(sentinel)
92
+ const recallPassed = (query.exitCode === 0 || search?.exitCode === 0)
65
93
  && (recallOutput.includes(sentinel) || recallOutput.includes(slug))
66
94
  const replayPassed = put.exitCode === 0
67
95
  && getPassed
68
96
  && recallPassed
97
+ const recoveredCommands = [get, query, search]
98
+ .filter(Boolean)
99
+ .filter(command => command.recoveredTimeout)
100
+ .map(command => command.name)
101
+ const recoveredTimeouts = replayPassed && recoveredCommands.length > 0
69
102
 
70
103
  return capability('gbrain', replayPassed ? 'passed' : 'failed', {
71
104
  reason: replayPassed
@@ -73,10 +106,17 @@ function runGbrainReplay() {
73
106
  : 'gbrain was configured, but write/get/query replay did not prove recall',
74
107
  required: options.requireGbrain,
75
108
  health,
109
+ degraded: false,
110
+ recoveredTimeouts,
111
+ recoveredCommands,
76
112
  sentinel,
77
113
  slug,
78
- commands: [healthCheck, put, get, query, search, cleanup].filter(Boolean),
79
- nextCommands: replayPassed ? [] : ['gbrain doctor --json', `gbrain get ${slug}`, `gbrain query ${sentinel}`],
114
+ commands: summarizeCommands([init, healthCheck, put, get, query, search, cleanup]),
115
+ notes: recoveredCommands.map(name => `${name} returned complete output after Bun shutdown recovery`),
116
+ warnings: [],
117
+ nextCommands: replayPassed
118
+ ? []
119
+ : ['gbrain doctor --json', `gbrain get ${slug}`, `gbrain query ${sentinel}`],
80
120
  })
81
121
  }
82
122
 
@@ -107,9 +147,9 @@ function runGraphifyRehearsal() {
107
147
  })
108
148
  }
109
149
 
110
- const projectPath = resolve(options.largeProject)
111
- const graphOut = options.semanticExtract ? resolve(workRoot, 'graphify-out') : join(projectPath, 'graphify-out')
112
- if (options.semanticExtract) mkdirSync(graphOut, { recursive: true })
150
+ const sourceProjectPath = resolve(options.largeProject)
151
+ const projectPath = prepareGraphifyProject(sourceProjectPath)
152
+ const graphOut = join(projectPath, 'graphify-out')
113
153
  const extractCommand = options.semanticExtract ? 'extract' : 'update'
114
154
  const extractArgs = options.semanticExtract
115
155
  ? ['extract', projectPath, '--out', graphOut]
@@ -131,7 +171,7 @@ function runGraphifyRehearsal() {
131
171
  nextCommands: [
132
172
  'graphify install --platform codex',
133
173
  'graphify hook status',
134
- `graphify update ${quoteArg(projectPath)} --no-cluster`,
174
+ `graphify update ${quoteArg(sourceProjectPath)} --no-cluster`,
135
175
  'Use --semantic-extract only when semantic LLM extraction is explicitly allowed.',
136
176
  ],
137
177
  })
@@ -171,11 +211,12 @@ function runGraphifyRehearsal() {
171
211
  ? `graphify ${extractCommand} built a real project graph and answered a graph query`
172
212
  : 'graphify generated an artifact but graph stats or query validation failed',
173
213
  required: options.requireGraphify,
174
- project: projectPath,
214
+ project: sourceProjectPath,
215
+ analyzedProject: projectPath,
175
216
  mode: options.semanticExtract ? 'semantic-extract' : 'ast-update-no-llm',
176
217
  graphPath,
177
218
  stats,
178
- commands: [help, extract, query, benchmark, globalAdd].filter(Boolean),
219
+ commands: summarizeCommands([help, extract, query, benchmark, globalAdd]),
179
220
  nextCommands: passed ? [] : [`graphify query ${quoteArg(options.graphifyQuestion)} --graph ${quoteArg(graphPath)}`],
180
221
  })
181
222
  }
@@ -250,6 +291,15 @@ function buildReport(capabilities) {
250
291
  }
251
292
  }
252
293
 
294
+ function gbrainCommandEnv() {
295
+ return {
296
+ ...process.env,
297
+ GBRAIN_HOME: join(workRoot, 'gbrain-home'),
298
+ GBRAIN_AUDIT_DIR: join(workRoot, 'gbrain-audit'),
299
+ GBRAIN_NO_BANNER: '1',
300
+ }
301
+ }
302
+
253
303
  function writeReport(report) {
254
304
  const target = options.reportFile
255
305
  ? resolve(options.reportFile)
@@ -259,6 +309,10 @@ function writeReport(report) {
259
309
  writeFileSync(target, `${JSON.stringify(report, null, 2)}\n`, 'utf8')
260
310
  }
261
311
 
312
+ function summarizeCommands(commands) {
313
+ return commands.filter(Boolean).map(command => summarizeCommandRecord(command))
314
+ }
315
+
262
316
  function runCommand(name, command, args, opts = {}) {
263
317
  const invocation = opts.wrapRtk === false
264
318
  ? { command, args, wrapped: false }
@@ -274,20 +328,21 @@ function runCommand(name, command, args, opts = {}) {
274
328
  timeout: opts.timeoutMs ?? options.timeoutMs,
275
329
  maxBuffer: 80 * 1024 * 1024,
276
330
  })
277
- const stdout = String(result.stdout ?? '')
278
- const stderr = String(result.stderr ?? '') + (result.error ? `\n${result.error.message}` : '')
279
- const exitCode = typeof result.status === 'number' ? result.status : 1
280
- const timedOut = /ETIMEDOUT/i.test(String(result.error?.message ?? ''))
331
+ const normalized = command === 'gbrain'
332
+ ? normalizeGbrainSpawnResult(args, result)
333
+ : normalizeSpawnResult(result)
334
+ const { stdout, stderr, exitCode, timedOut, recoveredTimeout } = normalized
281
335
  const entry = {
282
336
  name,
283
337
  command: commandLine,
284
338
  wrappedByRtk: invocation.wrapped,
285
339
  exitCode,
286
340
  timedOut,
341
+ recoveredTimeout,
287
342
  startedAt,
288
343
  endedAt: new Date().toISOString(),
289
- stdoutTail: tail(stdout),
290
- stderrTail: tail(stderr),
344
+ stdoutTail: summarizeCommandOutput(name, 'stdout', stdout),
345
+ stderrTail: summarizeCommandOutput(name, 'stderr', stderr),
291
346
  }
292
347
  results.push(entry)
293
348
  return { ...entry, stdout, stderr }
@@ -317,17 +372,65 @@ function removeGeneratedGraphJson(graphOut) {
317
372
  rmSync(join(outputDir, 'graph.json'), { force: true })
318
373
  }
319
374
 
375
+ function prepareGraphifyProject(sourceProjectPath) {
376
+ const snapshotPath = join(workRoot, 'graphify-project')
377
+ rmSync(snapshotPath, { recursive: true, force: true })
378
+ cpSync(sourceProjectPath, snapshotPath, {
379
+ recursive: true,
380
+ force: true,
381
+ filter: sourcePath => shouldIncludeGraphifySnapshotPath(sourceProjectPath, sourcePath),
382
+ })
383
+ return snapshotPath
384
+ }
385
+
386
+ function shouldIncludeGraphifySnapshotPath(sourceProjectPath, sourcePath) {
387
+ const relativePath = sourcePath.slice(sourceProjectPath.length).replace(/^[\\/]+/, '')
388
+ if (!relativePath) return true
389
+ const segments = relativePath.split(/[\\/]+/)
390
+ if (segments[0].startsWith('.') && !GRAPHIFY_ALLOWED_HIDDEN_TOP_LEVEL_DIRS.has(segments[0])) return false
391
+ if (segments.some(segment => ['.git', '.codegraph', 'node_modules', '.tmp', 'graphify-out', 'dist', 'coverage'].includes(segment))) {
392
+ return false
393
+ }
394
+ for (let index = 0; index < segments.length - 1; index += 1) {
395
+ if (segments[index] === '.scale' && ['reports', 'ai-os', 'runtime', 'model-usage', 'cache', 'events'].includes(segments[index + 1])) {
396
+ return false
397
+ }
398
+ }
399
+ return true
400
+ }
401
+
320
402
  function wrapWithRtk(command, args) {
321
403
  if (RTK_BYPASS_COMMANDS.has(command)) return { command, args, wrapped: false }
322
404
  if (!options.useRtk || command === 'rtk' || !commandExists('rtk')) return { command, args, wrapped: false }
323
405
  return { command: 'rtk', args: [command, ...args], wrapped: true }
324
406
  }
325
407
 
408
+ function normalizeSpawnResult(result) {
409
+ const stdout = String(result.stdout ?? '')
410
+ const stderr = `${String(result.stderr ?? '')}${result.error ? `\n${result.error.message}` : ''}`.trim()
411
+ return {
412
+ stdout,
413
+ stderr,
414
+ exitCode: typeof result.status === 'number' ? result.status : 1,
415
+ timedOut: /ETIMEDOUT/i.test(String(result.error?.message ?? '')),
416
+ recoveredTimeout: false,
417
+ }
418
+ }
419
+
326
420
  function spawnStructured(command, args, options) {
327
- const direct = resolveDirectWindowsInvocation(command, args)
421
+ const direct = resolveDirectWindowsGbrainInvocation(command, args, resolveCommandPath)
328
422
  if (direct) {
329
- return spawnSync(direct.command, direct.args, {
423
+ const result = spawnSync(direct.command, direct.args, {
330
424
  ...options,
425
+ cwd: options.cwd ?? direct.cwd,
426
+ shell: false,
427
+ windowsHide: true,
428
+ })
429
+ if (!shouldRetryWithMirroredGbrain(direct, result)) return result
430
+ const mirrored = ensureMirroredGbrainInvocation(direct)
431
+ return spawnSync(mirrored.command, mirrored.args, {
432
+ ...options,
433
+ cwd: options.cwd ?? mirrored.cwd,
331
434
  shell: false,
332
435
  windowsHide: true,
333
436
  })
@@ -348,24 +451,6 @@ function spawnStructured(command, args, options) {
348
451
  })
349
452
  }
350
453
 
351
- function resolveDirectWindowsInvocation(command, args) {
352
- if (process.platform !== 'win32' || command !== 'gbrain') return null
353
- const gbrainShim = resolveCommandPath('gbrain')
354
- if (!gbrainShim || !/\.cmd$/i.test(gbrainShim) || !existsSync(gbrainShim)) return null
355
- try {
356
- const content = readFileSync(gbrainShim, 'utf8')
357
- const cliPath = content.match(/"([^"]*gbrain[^"]*src[\\/]cli\.ts)"/i)?.[1]
358
- const bunShim = resolveCommandPath('bun')
359
- const bunExe = bunShim ? join(dirname(bunShim), 'node_modules', 'bun', 'bin', 'bun.exe') : ''
360
- if (cliPath && bunExe && existsSync(bunExe)) {
361
- return { command: bunExe, args: [cliPath, ...args] }
362
- }
363
- } catch {
364
- return null
365
- }
366
- return null
367
- }
368
-
369
454
  function resolveWindowsCommandShim(command) {
370
455
  if (process.platform !== 'win32') return command
371
456
  if (!/[\\/]/.test(command) || extname(command)) return command
@@ -510,7 +595,3 @@ function quoteArg(value) {
510
595
  function powershellQuote(value) {
511
596
  return `'${String(value).replace(/'/g, "''")}'`
512
597
  }
513
-
514
- function tail(value, max = 4000) {
515
- return value.length > max ? value.slice(-max) : value
516
- }
@@ -1,9 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawnSync } from 'node:child_process'
3
- import { existsSync, mkdirSync, rmSync } from 'node:fs'
3
+ import { existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'
4
4
  import { tmpdir } from 'node:os'
5
5
  import { dirname, extname, join, resolve } from 'node:path'
6
6
  import { fileURLToPath } from 'node:url'
7
+ import {
8
+ ensureMirroredGbrainInvocation,
9
+ normalizeGbrainSpawnResult,
10
+ resolveDirectWindowsGbrainInvocation,
11
+ shouldRetryWithMirroredGbrain,
12
+ } from './lib/gbrain-runtime.mjs'
13
+ import { summarizeCommandOutput } from './lib/report-output.mjs'
7
14
 
8
15
  const scriptDir = dirname(fileURLToPath(import.meta.url))
9
16
  const repoRoot = resolve(scriptDir, '..', '..')
@@ -13,12 +20,19 @@ const smokeRoot = options.tempDir
13
20
  : mkSmokeRoot()
14
21
  const projectDir = join(smokeRoot, 'project')
15
22
  const scaleDir = join(smokeRoot, '.scale')
23
+ const homeDir = join(smokeRoot, 'home')
24
+ const appDataDir = join(homeDir, 'AppData', 'Roaming')
25
+ const localAppDataDir = join(homeDir, 'AppData', 'Local')
26
+ const gbrainHomeDir = join(smokeRoot, 'gbrain-home')
27
+ const gbrainAuditDir = join(smokeRoot, 'gbrain-audit')
16
28
  const scaleInvocation = parseCommandLine(options.scaleCommand ?? 'node --import tsx src/api/cli.ts')
17
29
  const scaleCommand = formatCommand(scaleInvocation.file, scaleInvocation.args)
18
30
  const results = []
19
31
 
20
32
  mkdirSync(projectDir, { recursive: true })
21
33
  mkdirSync(scaleDir, { recursive: true })
34
+ mkdirSync(appDataDir, { recursive: true })
35
+ mkdirSync(localAppDataDir, { recursive: true })
22
36
 
23
37
  try {
24
38
  runSetupSmoke()
@@ -33,10 +47,19 @@ try {
33
47
  function runSetupSmoke() {
34
48
  const baseEnv = {
35
49
  ...process.env,
50
+ HOME: homeDir,
51
+ USERPROFILE: homeDir,
52
+ APPDATA: appDataDir,
53
+ LOCALAPPDATA: localAppDataDir,
36
54
  SCALE_DIR: scaleDir,
37
55
  SCALE_PROJECT_DIR: projectDir,
38
56
  SCALE_LOG_LEVEL: '',
57
+ GBRAIN_HOME: gbrainHomeDir,
58
+ GBRAIN_AUDIT_DIR: gbrainAuditDir,
59
+ GBRAIN_NO_BANNER: '1',
39
60
  }
61
+ initProject(baseEnv)
62
+ initIsolatedGbrain(baseEnv)
40
63
 
41
64
  const zh = runCommand('bootstrap-ui-zh', ['bootstrap', 'deps', '--dir', projectDir, '--pack', 'ui', '--lang', 'zh'], baseEnv)
42
65
  assertIncludes(zh.stdout, 'SCALE 依赖安装计划', 'Chinese bootstrap output should use Chinese title')
@@ -64,6 +87,7 @@ function runSetupSmoke() {
64
87
  const envDoctor = runJson('doctor-env-json', ['doctor', 'env', '--json'], baseEnv)
65
88
  assert(envDoctor.ok === true, 'environment doctor should pass when required core commands are available')
66
89
  assertArrayContains(envDoctor.checks?.map(check => check.id), ['git', 'npm', 'npx', 'rtk', 'gbrain', 'graphify', 'codegraph'], 'environment doctor should report core and third-party commands')
90
+ assert(envDoctor.checks?.find(check => check.id === 'gbrain')?.status === 'ok', 'environment doctor should validate gbrain when an isolated brain is initialized')
67
91
 
68
92
  const localMemory = runJson('setup-memory-scale-local-json', [
69
93
  'setup',
@@ -97,11 +121,107 @@ function runSetupSmoke() {
97
121
  assert(gbrainMemory.memoryProviderSwitch?.mode === 'external-first', 'gbrain provider should support external-first mode')
98
122
  assert(gbrainMemory.memoryProviderSwitch?.nextOrder?.[0] === 'gbrain', 'gbrain should become the first provider')
99
123
 
124
+ const apply = runJson('setup-governed-apply-json', [
125
+ 'setup',
126
+ '--dir',
127
+ projectDir,
128
+ '--pack',
129
+ 'external-cli,memory,knowledge',
130
+ '--apply',
131
+ '--memory-provider',
132
+ 'gbrain',
133
+ '--memory-mode',
134
+ 'external-first',
135
+ '--json',
136
+ ], baseEnv)
137
+ assert(apply.final?.ok === true, 'setup apply should succeed for external-cli, memory, and knowledge packs')
138
+ assert(apply.final?.complete === true, 'setup apply should leave the selected packs fully installed')
139
+ assert(apply.final?.summary?.needsInit === 0, 'setup apply should not leave RTK, GBrain, Graphify, or CodeGraph uninitialized')
140
+ assert(apply.memoryProviderSwitch?.provider === 'gbrain', 'setup apply should keep gbrain as the selected provider')
141
+ assert(existsSync(join(projectDir, '.codegraph')), 'setup apply should initialize a CodeGraph index in the target project')
142
+ assert(existsSync(join(projectDir, 'graphify-out', 'graph.json')), 'setup apply should generate a Graphify graph artifact without LLM usage')
143
+ assert(existsSync(join(projectDir, '.git', 'hooks', 'post-commit')), 'setup apply should install the Graphify post-commit hook')
144
+ assert(existsSync(join(projectDir, '.git', 'hooks', 'post-checkout')), 'setup apply should install the Graphify post-checkout hook')
145
+ assert(
146
+ ['installed', 'installed-now'].includes(apply.final?.items?.find(item => item.id === 'rtk')?.status),
147
+ 'setup apply should leave RTK in an installed state after Codex initialization',
148
+ )
149
+ assert(
150
+ ['installed', 'installed-now'].includes(apply.final?.items?.find(item => item.id === 'graphify')?.status),
151
+ 'setup apply should leave Graphify in an installed state after hook and graph initialization',
152
+ )
153
+ assert((apply.final?.postCheckSummary?.warned ?? 0) === 0, 'setup apply should not leave memory/code post-checks in a warned state')
154
+
155
+ const verify = runJson('setup-governed-verify-json', [
156
+ 'setup',
157
+ '--verify',
158
+ '--dir',
159
+ projectDir,
160
+ '--pack',
161
+ 'external-cli,memory,knowledge',
162
+ '--json',
163
+ ], baseEnv)
164
+ assert(verify.ok === true, 'setup verify should pass after governed apply in the isolated home')
165
+ assert((verify.summary?.blockingIssues?.length ?? 0) === 0, 'setup verify should not report any blocking dependency issues after apply')
166
+
100
167
  const codegraph = runJson('codegraph-status-json', ['codegraph', 'status', '--dir', repoRoot, '--json'], baseEnv)
101
168
  assertArrayContains(codegraph.providers?.map(provider => provider.id), ['codegraph', 'graphify'], 'codegraph status should expose CodeGraph and Graphify providers')
102
169
  assert(typeof codegraph.projectIndexExists === 'boolean', 'codegraph status should report project index state')
103
170
  }
104
171
 
172
+ function initProject(env) {
173
+ writeFileSync(join(projectDir, 'AGENTS.md'), '# Setup smoke project\n', 'utf-8')
174
+ writeFileSync(join(projectDir, 'smoke.ts'), 'export const setupSmoke = "ready"\n', 'utf-8')
175
+ const result = spawnStructured('git', ['init'], {
176
+ cwd: projectDir,
177
+ env,
178
+ encoding: 'utf8',
179
+ timeout: options.timeoutMs,
180
+ maxBuffer: 20 * 1024 * 1024,
181
+ })
182
+ const stdout = String(result.stdout ?? '')
183
+ const stderr = String(result.stderr ?? '')
184
+ results.push({
185
+ name: 'git-init-project',
186
+ command: 'git init',
187
+ exitCode: typeof result.status === 'number' ? result.status : 1,
188
+ startedAt: new Date().toISOString(),
189
+ endedAt: new Date().toISOString(),
190
+ stdoutTail: summarizeCommandOutput('git-init-project', 'stdout', stdout),
191
+ stderrTail: summarizeCommandOutput('git-init-project', 'stderr', stderr + (result.error ? `\n${result.error.message}` : '')),
192
+ })
193
+ if (result.status !== 0) {
194
+ throw new Error(`git-init-project failed with exit code ${result.status ?? 1}\n${stderr || stdout}`)
195
+ }
196
+ }
197
+
198
+ function initIsolatedGbrain(env) {
199
+ const startedAt = new Date().toISOString()
200
+ const result = spawnStructured('gbrain', ['init', '--pglite', '--no-embedding'], {
201
+ cwd: repoRoot,
202
+ env,
203
+ encoding: 'utf8',
204
+ timeout: options.timeoutMs,
205
+ maxBuffer: 20 * 1024 * 1024,
206
+ })
207
+ const normalized = normalizeGbrainSpawnResult(['init', '--pglite', '--no-embedding'], result)
208
+ const { stdout, stderr, exitCode, timedOut, recoveredTimeout } = normalized
209
+ results.push({
210
+ name: 'gbrain-init-isolated-home',
211
+ command: 'gbrain init --pglite --no-embedding',
212
+ exitCode,
213
+ timedOut,
214
+ recoveredTimeout,
215
+ startedAt,
216
+ endedAt: new Date().toISOString(),
217
+ stdoutTail: summarizeCommandOutput('gbrain-init-isolated-home', 'stdout', stdout),
218
+ stderrTail: summarizeCommandOutput('gbrain-init-isolated-home', 'stderr', stderr),
219
+ })
220
+ if (exitCode !== 0) {
221
+ throw new Error(`gbrain-init-isolated-home failed with exit code ${exitCode}\n${summarizeCommandOutput('gbrain-init-isolated-home', 'stderr', stderr) || summarizeCommandOutput('gbrain-init-isolated-home', 'stdout', stdout)}`)
222
+ }
223
+ }
224
+
105
225
  function runJson(name, args, env) {
106
226
  const result = runCommand(name, args, env)
107
227
  try {
@@ -132,8 +252,8 @@ function runCommand(name, args, env) {
132
252
  exitCode,
133
253
  startedAt,
134
254
  endedAt: new Date().toISOString(),
135
- stdoutTail: tail(stdout),
136
- stderrTail: tail(stderr + (result.error ? `\n${result.error.message}` : '')),
255
+ stdoutTail: summarizeCommandOutput(name, 'stdout', stdout),
256
+ stderrTail: summarizeCommandOutput(name, 'stderr', stderr + (result.error ? `\n${result.error.message}` : '')),
137
257
  }
138
258
  results.push(entry)
139
259
  if (exitCode !== 0) {
@@ -143,6 +263,23 @@ function runCommand(name, args, env) {
143
263
  }
144
264
 
145
265
  function spawnStructured(command, args, options) {
266
+ const direct = resolveDirectWindowsGbrainInvocation(command, args, resolveCommandPath)
267
+ if (direct) {
268
+ const result = spawnSync(direct.command, direct.args, {
269
+ ...options,
270
+ cwd: options.cwd ?? direct.cwd,
271
+ shell: false,
272
+ windowsHide: true,
273
+ })
274
+ if (!shouldRetryWithMirroredGbrain(direct, result)) return result
275
+ const mirrored = ensureMirroredGbrainInvocation(direct)
276
+ return spawnSync(mirrored.command, mirrored.args, {
277
+ ...options,
278
+ cwd: options.cwd ?? mirrored.cwd,
279
+ shell: false,
280
+ windowsHide: true,
281
+ })
282
+ }
146
283
  const resolved = resolveWindowsCommandShim(resolveCommandPath(command) ?? command)
147
284
  if (process.platform === 'win32' && /\.(cmd|bat)$/i.test(resolved)) {
148
285
  const comspec = process.env.ComSpec || 'cmd.exe'
@@ -200,7 +337,7 @@ function parseArgs(args) {
200
337
  else if (arg === '--temp-dir') parsed.tempDir = args[++index]
201
338
  else if (arg === '--timeout-ms') parsed.timeoutMs = Number.parseInt(args[++index] ?? '', 10)
202
339
  else if (arg === '--help' || arg === '-h') {
203
- process.stdout.write(`Usage: node scripts/workflow/setup-smoke.mjs [--scale-command "scale"] [--keep-temp] [--verbose]\n`)
340
+ process.stdout.write('Usage: node scripts/workflow/setup-smoke.mjs [--scale-command "scale"] [--keep-temp] [--verbose]\n')
204
341
  process.exit(0)
205
342
  } else {
206
343
  throw new Error(`Unknown argument: ${arg}`)
@@ -288,12 +425,9 @@ function writeSummary(status, error) {
288
425
  repoRoot,
289
426
  projectDir,
290
427
  scaleDir,
428
+ homeDir,
291
429
  results,
292
430
  error: error ? String(error.stack ?? error.message ?? error) : undefined,
293
431
  }
294
432
  process.stdout.write(`${JSON.stringify(report, null, 2)}\n`)
295
433
  }
296
-
297
- function tail(value, max = 4000) {
298
- return value.length > max ? value.slice(-max) : value
299
- }
@@ -1,87 +0,0 @@
1
- # Active Security And Visual Gates
2
-
3
- SCALE V2 adds two optional verification layers for projects that can provide a runnable local target:
4
-
5
- - `ActiveRedTeam`: bounded dynamic security probes for configured HTTP targets.
6
- - `VisualGate`: structured visual review evidence for UI routes and UI specs.
7
-
8
- Both are conditional. A library or backend project with no runtime target should not pay the cost.
9
-
10
- ## Active Security
11
-
12
- Active security is configured under `.scale/verification.json`:
13
-
14
- ```json
15
- {
16
- "security": {
17
- "active": {
18
- "enabled": true,
19
- "baseUrl": "http://localhost:3000",
20
- "startCommand": "npm run dev",
21
- "targets": ["/api/login", "/api/users"],
22
- "timeoutMs": 5000,
23
- "maxRequests": 20
24
- }
25
- }
26
- }
27
- ```
28
-
29
- Behavior:
30
-
31
- - missing or disabled config returns `SKIPPED`
32
- - invalid enabled config returns `FAILED` before sending probes
33
- - probes are capped by `maxRequests`
34
- - every request has a timeout
35
- - reflected probe payloads are `HIGH` findings and block
36
- - request errors and server errors are recorded as findings, but only configured blocker severity should fail the gate
37
-
38
- The first implementation exposes `runActiveRedTeam()` as a library API. It does not start a server by itself yet. CLI orchestration can wire `startCommand` later, but startup failure must become a `FAILED` result when that runner is added.
39
-
40
- ## Visual Gate
41
-
42
- Visual verification is configured under `.scale/verification.json`:
43
-
44
- ```json
45
- {
46
- "visual": {
47
- "enabled": true,
48
- "baseUrl": "http://localhost:5173",
49
- "specPath": "docs/ui/UI-SPEC.md",
50
- "routes": ["/", "/settings"],
51
- "reportPath": "docs/worklog/tasks/TASK-123/visual-report.json",
52
- "blockingSeverities": ["critical", "high"]
53
- }
54
- }
55
- ```
56
-
57
- `VisualGate` consumes a structured report:
58
-
59
- ```json
60
- {
61
- "screenshots": [
62
- { "route": "/", "path": "screenshots/home.png" }
63
- ],
64
- "findings": [
65
- {
66
- "severity": "high",
67
- "route": "/",
68
- "message": "Primary action overlaps the navigation bar.",
69
- "evidence": "overlap ratio 0.42"
70
- }
71
- ]
72
- }
73
- ```
74
-
75
- Behavior:
76
-
77
- - missing or disabled config passes with a `Visual gate skipped` evidence item
78
- - enabled config requires `baseUrl`, `specPath`, `routes`, and `reportPath`
79
- - missing or invalid visual report fails
80
- - default blockers are `critical` and `high`
81
- - VLM comments may be recorded in the report, but the gate blocks only on structured severity thresholds
82
-
83
- ## Gate Numbering
84
-
85
- `VisualGate` uses `G9` when explicitly registered. It is not registered by default because meta governance also uses the G9-G15 range. Projects should register it only in UI verification profiles or dedicated task flows.
86
-
87
- Active security remains a security sub-check instead of a fractional gate number. It belongs under the broader G7 security lifecycle when wired into a concrete workflow.