@hongmaple0820/scale-engine 0.40.2 → 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 (200) hide show
  1. package/README.md +30 -2
  2. package/dist/api/cli.js +19 -0
  3. package/dist/api/cli.js.map +1 -1
  4. package/dist/api/quickstart.d.ts +11 -0
  5. package/dist/api/quickstart.js +98 -1
  6. package/dist/api/quickstart.js.map +1 -1
  7. package/dist/artifact/fsmDefinitions.js +15 -2
  8. package/dist/artifact/fsmDefinitions.js.map +1 -1
  9. package/dist/artifact/types.d.ts +1 -1
  10. package/dist/artifact/types.js.map +1 -1
  11. package/dist/cache/ScanCache.d.ts +41 -0
  12. package/dist/cache/ScanCache.js +120 -0
  13. package/dist/cache/ScanCache.js.map +1 -0
  14. package/dist/capabilities/BrowserQACapability.d.ts +14 -0
  15. package/dist/capabilities/BrowserQACapability.js +94 -0
  16. package/dist/capabilities/BrowserQACapability.js.map +1 -1
  17. package/dist/cli/autofixCommands.d.ts +22 -0
  18. package/dist/cli/autofixCommands.js +32 -0
  19. package/dist/cli/autofixCommands.js.map +1 -0
  20. package/dist/cli/cortexCommands.d.ts +71 -0
  21. package/dist/cli/cortexCommands.js +335 -0
  22. package/dist/cli/cortexCommands.js.map +1 -0
  23. package/dist/cli/costCommands.d.ts +13 -0
  24. package/dist/cli/costCommands.js +48 -0
  25. package/dist/cli/costCommands.js.map +1 -0
  26. package/dist/cli/orchCommands.d.ts +43 -0
  27. package/dist/cli/orchCommands.js +135 -0
  28. package/dist/cli/orchCommands.js.map +1 -0
  29. package/dist/cli/phaseCommands.js +1 -2
  30. package/dist/cli/phaseCommands.js.map +1 -1
  31. package/dist/cli/qaCommands.d.ts +22 -0
  32. package/dist/cli/qaCommands.js +84 -0
  33. package/dist/cli/qaCommands.js.map +1 -0
  34. package/dist/cli/quickstartCommands.d.ts +17 -0
  35. package/dist/cli/quickstartCommands.js +47 -0
  36. package/dist/cli/quickstartCommands.js.map +1 -0
  37. package/dist/cli/shieldCommands.d.ts +30 -0
  38. package/dist/cli/shieldCommands.js +212 -0
  39. package/dist/cli/shieldCommands.js.map +1 -0
  40. package/dist/cli/tuiCommands.d.ts +7 -0
  41. package/dist/cli/tuiCommands.js +33 -0
  42. package/dist/cli/tuiCommands.js.map +1 -0
  43. package/dist/config/profiles.js +26 -0
  44. package/dist/config/profiles.js.map +1 -1
  45. package/dist/cortex/GovernanceMetrics.d.ts +66 -0
  46. package/dist/cortex/GovernanceMetrics.js +230 -0
  47. package/dist/cortex/GovernanceMetrics.js.map +1 -0
  48. package/dist/cortex/InstinctExtractor.d.ts +61 -0
  49. package/dist/cortex/InstinctExtractor.js +184 -0
  50. package/dist/cortex/InstinctExtractor.js.map +1 -0
  51. package/dist/cortex/InstinctStore.d.ts +54 -0
  52. package/dist/cortex/InstinctStore.js +266 -0
  53. package/dist/cortex/InstinctStore.js.map +1 -0
  54. package/dist/cortex/ReflexionEngine.d.ts +34 -0
  55. package/dist/cortex/ReflexionEngine.js +157 -0
  56. package/dist/cortex/ReflexionEngine.js.map +1 -0
  57. package/dist/cortex/SessionInjector.d.ts +44 -0
  58. package/dist/cortex/SessionInjector.js +127 -0
  59. package/dist/cortex/SessionInjector.js.map +1 -0
  60. package/dist/cortex/adapters/ClaudeAdapter.d.ts +17 -0
  61. package/dist/cortex/adapters/ClaudeAdapter.js +61 -0
  62. package/dist/cortex/adapters/ClaudeAdapter.js.map +1 -0
  63. package/dist/cortex/adapters/CodexAdapter.d.ts +10 -0
  64. package/dist/cortex/adapters/CodexAdapter.js +52 -0
  65. package/dist/cortex/adapters/CodexAdapter.js.map +1 -0
  66. package/dist/cortex/adapters/CursorAdapter.d.ts +10 -0
  67. package/dist/cortex/adapters/CursorAdapter.js +46 -0
  68. package/dist/cortex/adapters/CursorAdapter.js.map +1 -0
  69. package/dist/cortex/adapters/GeminiAdapter.d.ts +11 -0
  70. package/dist/cortex/adapters/GeminiAdapter.js +48 -0
  71. package/dist/cortex/adapters/GeminiAdapter.js.map +1 -0
  72. package/dist/eval/BenchmarkPublisher.d.ts +25 -0
  73. package/dist/eval/BenchmarkPublisher.js +27 -0
  74. package/dist/eval/BenchmarkPublisher.js.map +1 -0
  75. package/dist/guardrails/DependencyAuditor.js +10 -1
  76. package/dist/guardrails/DependencyAuditor.js.map +1 -1
  77. package/dist/orchestrator/OrchestratorDaemon.d.ts +44 -0
  78. package/dist/orchestrator/OrchestratorDaemon.js +150 -0
  79. package/dist/orchestrator/OrchestratorDaemon.js.map +1 -0
  80. package/dist/orchestrator/PolicyLoader.d.ts +80 -0
  81. package/dist/orchestrator/PolicyLoader.js +229 -0
  82. package/dist/orchestrator/PolicyLoader.js.map +1 -0
  83. package/dist/orchestrator/ReconciliationLoop.d.ts +71 -0
  84. package/dist/orchestrator/ReconciliationLoop.js +266 -0
  85. package/dist/orchestrator/ReconciliationLoop.js.map +1 -0
  86. package/dist/orchestrator/TrackerAdapter.d.ts +60 -0
  87. package/dist/orchestrator/TrackerAdapter.js +147 -0
  88. package/dist/orchestrator/TrackerAdapter.js.map +1 -0
  89. package/dist/orchestrator/WorkspaceManager.d.ts +66 -0
  90. package/dist/orchestrator/WorkspaceManager.js +257 -0
  91. package/dist/orchestrator/WorkspaceManager.js.map +1 -0
  92. package/dist/qa/BrowserDaemon.d.ts +23 -0
  93. package/dist/qa/BrowserDaemon.js +79 -0
  94. package/dist/qa/BrowserDaemon.js.map +1 -0
  95. package/dist/qa/E2ETestOrchestrator.d.ts +14 -0
  96. package/dist/qa/E2ETestOrchestrator.js +19 -0
  97. package/dist/qa/E2ETestOrchestrator.js.map +1 -0
  98. package/dist/review/CrossModelReviewer.d.ts +35 -0
  99. package/dist/review/CrossModelReviewer.js +75 -0
  100. package/dist/review/CrossModelReviewer.js.map +1 -0
  101. package/dist/review/ReviewAggregator.d.ts +13 -0
  102. package/dist/review/ReviewAggregator.js +28 -0
  103. package/dist/review/ReviewAggregator.js.map +1 -0
  104. package/dist/review/reviewCommands.d.ts +15 -0
  105. package/dist/review/reviewCommands.js +24 -0
  106. package/dist/review/reviewCommands.js.map +1 -0
  107. package/dist/routing/LocalModelProvider.d.ts +11 -0
  108. package/dist/routing/LocalModelProvider.js +21 -0
  109. package/dist/routing/LocalModelProvider.js.map +1 -0
  110. package/dist/routing/ModelRouter.d.ts +12 -0
  111. package/dist/routing/ModelRouter.js +31 -4
  112. package/dist/routing/ModelRouter.js.map +1 -1
  113. package/dist/runtime/AiOsRuntime.d.ts +1 -0
  114. package/dist/runtime/AiOsRuntime.js +15 -0
  115. package/dist/runtime/AiOsRuntime.js.map +1 -1
  116. package/dist/runtime/CostAnalyzer.d.ts +53 -0
  117. package/dist/runtime/CostAnalyzer.js +160 -0
  118. package/dist/runtime/CostAnalyzer.js.map +1 -0
  119. package/dist/runtime/CostOptimizer.d.ts +11 -0
  120. package/dist/runtime/CostOptimizer.js +21 -0
  121. package/dist/runtime/CostOptimizer.js.map +1 -0
  122. package/dist/shield/PolicyCompiler.d.ts +70 -0
  123. package/dist/shield/PolicyCompiler.js +540 -0
  124. package/dist/shield/PolicyCompiler.js.map +1 -0
  125. package/dist/shield/ProtectedPaths.d.ts +39 -0
  126. package/dist/shield/ProtectedPaths.js +179 -0
  127. package/dist/shield/ProtectedPaths.js.map +1 -0
  128. package/dist/shield/ShieldProtocol.d.ts +50 -0
  129. package/dist/shield/ShieldProtocol.js +103 -0
  130. package/dist/shield/ShieldProtocol.js.map +1 -0
  131. package/dist/skills/SkillMdStandard.d.ts +33 -0
  132. package/dist/skills/SkillMdStandard.js +88 -0
  133. package/dist/skills/SkillMdStandard.js.map +1 -0
  134. package/dist/skills/SkillRegistry.d.ts +9 -1
  135. package/dist/skills/SkillRegistry.js +20 -0
  136. package/dist/skills/SkillRegistry.js.map +1 -1
  137. package/dist/skills/interop/GStackInterop.d.ts +15 -0
  138. package/dist/skills/interop/GStackInterop.js +34 -0
  139. package/dist/skills/interop/GStackInterop.js.map +1 -0
  140. package/dist/skills/interop/OMCInterop.d.ts +15 -0
  141. package/dist/skills/interop/OMCInterop.js +34 -0
  142. package/dist/skills/interop/OMCInterop.js.map +1 -0
  143. package/dist/tui/TuiDashboard.d.ts +3 -0
  144. package/dist/tui/TuiDashboard.js +120 -0
  145. package/dist/tui/TuiDashboard.js.map +1 -0
  146. package/dist/workflow/GateCatalog.d.ts +2 -0
  147. package/dist/workflow/GateCatalog.js +59 -3
  148. package/dist/workflow/GateCatalog.js.map +1 -1
  149. package/dist/workflow/GovernanceTemplatePacks.d.ts +1 -1
  150. package/dist/workflow/GovernanceTemplatePacks.js +15 -0
  151. package/dist/workflow/GovernanceTemplatePacks.js.map +1 -1
  152. package/dist/workflow/TddLoop.d.ts +2 -0
  153. package/dist/workflow/TddLoop.js +2 -0
  154. package/dist/workflow/TddLoop.js.map +1 -1
  155. package/dist/workflow/UpgradeManager.d.ts +10 -1
  156. package/dist/workflow/UpgradeManager.js +55 -0
  157. package/dist/workflow/UpgradeManager.js.map +1 -1
  158. package/dist/workflow/VerificationProfile.d.ts +8 -0
  159. package/dist/workflow/VerificationProfile.js +61 -0
  160. package/dist/workflow/VerificationProfile.js.map +1 -1
  161. package/dist/workflow/VerificationSchema.d.ts +46 -0
  162. package/dist/workflow/VerificationSchema.js +97 -0
  163. package/dist/workflow/VerificationSchema.js.map +1 -0
  164. package/dist/workflow/autofix/AutoFixEngine.d.ts +37 -0
  165. package/dist/workflow/autofix/AutoFixEngine.js +169 -0
  166. package/dist/workflow/autofix/AutoFixEngine.js.map +1 -0
  167. package/dist/workflow/execution/RalphEngine.d.ts +18 -0
  168. package/dist/workflow/execution/RalphEngine.js +22 -0
  169. package/dist/workflow/execution/RalphEngine.js.map +1 -1
  170. package/dist/workflow/gates/EnhancedGates.d.ts +74 -0
  171. package/dist/workflow/gates/EnhancedGates.js +653 -0
  172. package/dist/workflow/gates/EnhancedGates.js.map +1 -0
  173. package/dist/workflow/gates/GateSystem.d.ts +3 -0
  174. package/dist/workflow/gates/GateSystem.js +94 -1
  175. package/dist/workflow/gates/GateSystem.js.map +1 -1
  176. package/dist/workflow/types.d.ts +1 -1
  177. package/docs/README.md +3 -0
  178. package/docs/guides/DEVELOPMENT_WORKFLOW.md +28 -9
  179. package/docs/guides/GETTING_STARTED.md +19 -0
  180. package/docs/guides/MIGRATION.md +119 -0
  181. package/docs/workflow/GATES_AND_SCORE.md +34 -1
  182. package/docs/workflow/README.md +58 -10
  183. package/package.json +5 -17
  184. package/docs/ACTIVE_SECURITY_VISUAL_GATES.md +0 -87
  185. package/docs/AI_ENGINEERING_OS_POSITIONING.md +0 -607
  186. package/docs/BACKGROUND_HUNTER.md +0 -62
  187. package/docs/CODE_INTELLIGENCE.md +0 -180
  188. package/docs/CONTEXT_BUDGET.md +0 -165
  189. package/docs/DEPENDENCY_AUDIT.md +0 -118
  190. package/docs/EVOLUTION_SHADOW_MODE.md +0 -63
  191. package/docs/GITLAB_FLOW.md +0 -125
  192. package/docs/GOVERNANCE_DASHBOARD.md +0 -92
  193. package/docs/MEMORY_BRAIN.md +0 -104
  194. package/docs/MEMORY_FABRIC.md +0 -161
  195. package/docs/RESOURCE_GOVERNANCE.md +0 -92
  196. package/docs/RUNTIME_EVIDENCE.md +0 -101
  197. package/docs/WORKFLOW_EVAL.md +0 -151
  198. package/image/wechat-public.jpg +0 -0
  199. package/image/wxPay.jpg +0 -0
  200. package/image/zfb.jpg +0 -0
@@ -0,0 +1,653 @@
1
+ // SCALE Engine — Enhanced Gates (G16-G22)
2
+ // Commit Discipline, Doc Hygiene, Runtime Evidence, Code Review, Supply Chain, Context Budget, Session Health
3
+ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+ import { execSync } from 'node:child_process';
6
+ function createEvidence(input) {
7
+ return {
8
+ id: `EVID-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
9
+ ...input,
10
+ };
11
+ }
12
+ function textEvidence(items) {
13
+ return items.map(item => `${item.label}: ${item.detail}`).join('\n');
14
+ }
15
+ function gitCommand(cmd, cwd) {
16
+ try {
17
+ return execSync(`git ${cmd}`, { encoding: 'utf-8', stdio: 'pipe', cwd: cwd ?? process.cwd() }).trim();
18
+ }
19
+ catch {
20
+ return '';
21
+ }
22
+ }
23
+ // ============================================================================
24
+ // G16: Commit Discipline — 提交纪律门禁
25
+ // ============================================================================
26
+ export class CommitDisciplineGate {
27
+ constructor(options = {}) {
28
+ this.stage = 'G16';
29
+ this.name = 'Commit Discipline';
30
+ this.description = 'Uncommitted changes must be within thresholds; no large files staged';
31
+ this.requiredLevel = 'M';
32
+ this.warnThreshold = options.warnThreshold ?? 10;
33
+ this.blockThreshold = options.blockThreshold ?? 25;
34
+ this.staleWarnMinutes = options.staleWarnMinutes ?? 60;
35
+ this.staleBlockMinutes = options.staleBlockMinutes ?? 180;
36
+ this.maxStagedFileBytes = options.maxStagedFileBytes ?? 1_000_000;
37
+ }
38
+ async execute() {
39
+ const blockers = [];
40
+ const evidenceItems = [];
41
+ const cwd = process.cwd();
42
+ // Check 1: Uncommitted file count
43
+ const statusOutput = gitCommand('status --porcelain', cwd);
44
+ const uncommittedFiles = statusOutput ? statusOutput.split('\n').filter(l => l.trim()) : [];
45
+ const uncommittedCount = uncommittedFiles.length;
46
+ const countPassed = uncommittedCount < this.blockThreshold;
47
+ if (uncommittedCount >= this.blockThreshold) {
48
+ blockers.push(`Uncommitted files (${uncommittedCount}) >= block threshold (${this.blockThreshold})`);
49
+ }
50
+ evidenceItems.push(createEvidence({
51
+ kind: 'command',
52
+ label: 'Uncommitted file count',
53
+ passed: countPassed,
54
+ detail: `${uncommittedFiles.length} uncommitted file(s) (warn=${this.warnThreshold}, block=${this.blockThreshold})`,
55
+ }));
56
+ // Check 2: Time since last commit
57
+ const lastCommitTime = gitCommand('log -1 --format=%ct', cwd);
58
+ if (lastCommitTime) {
59
+ const elapsed = (Date.now() / 1000) - parseInt(lastCommitTime, 10);
60
+ const elapsedMinutes = Math.floor(elapsed / 60);
61
+ const stalePassed = elapsedMinutes < this.staleBlockMinutes;
62
+ if (elapsedMinutes >= this.staleBlockMinutes) {
63
+ blockers.push(`Last commit was ${elapsedMinutes}min ago >= block threshold (${this.staleBlockMinutes}min)`);
64
+ }
65
+ evidenceItems.push(createEvidence({
66
+ kind: 'command',
67
+ label: 'Time since last commit',
68
+ passed: stalePassed,
69
+ detail: `${elapsedMinutes} minutes since last commit (warn=${this.staleWarnMinutes}, block=${this.staleBlockMinutes})`,
70
+ }));
71
+ }
72
+ // Check 3: Large staged files
73
+ const stagedOutput = gitCommand('diff --cached --name-only', cwd);
74
+ const stagedFiles = stagedOutput ? stagedOutput.split('\n').filter(Boolean) : [];
75
+ const largeFiles = [];
76
+ for (const file of stagedFiles) {
77
+ try {
78
+ const fullPath = join(cwd, file);
79
+ if (existsSync(fullPath)) {
80
+ const stat = statSync(fullPath);
81
+ if (stat.size > this.maxStagedFileBytes) {
82
+ largeFiles.push(`${file} (${(stat.size / 1024).toFixed(0)}KB)`);
83
+ }
84
+ }
85
+ }
86
+ catch {
87
+ // Skip files that can't be stat'd
88
+ }
89
+ }
90
+ const largePassed = largeFiles.length === 0;
91
+ if (!largePassed) {
92
+ blockers.push(`Large staged files detected: ${largeFiles.join(', ')}`);
93
+ }
94
+ evidenceItems.push(createEvidence({
95
+ kind: 'command',
96
+ label: 'Large staged files',
97
+ passed: largePassed,
98
+ detail: largePassed ? 'No large staged files' : `Large files: ${largeFiles.join(', ')}`,
99
+ }));
100
+ // Check 4: git diff --check (whitespace errors)
101
+ const diffCheck = gitCommand('diff --check', cwd);
102
+ const diffCheckPassed = !diffCheck;
103
+ if (!diffCheckPassed) {
104
+ blockers.push('git diff --check found whitespace errors');
105
+ }
106
+ evidenceItems.push(createEvidence({
107
+ kind: 'command',
108
+ label: 'Whitespace check',
109
+ passed: diffCheckPassed,
110
+ detail: diffCheckPassed ? 'No whitespace errors' : `Whitespace errors: ${diffCheck.slice(0, 200)}`,
111
+ }));
112
+ const passed = blockers.length === 0;
113
+ return {
114
+ gate: this.stage,
115
+ status: passed ? 'PASSED' : 'FAILED',
116
+ passed,
117
+ evidence: textEvidence(evidenceItems),
118
+ evidenceItems,
119
+ blockers,
120
+ durationMs: 0,
121
+ };
122
+ }
123
+ }
124
+ // ============================================================================
125
+ // G17: Documentation Hygiene — 文档卫生门禁
126
+ // ============================================================================
127
+ export class DocumentationHygieneGate {
128
+ constructor() {
129
+ this.stage = 'G17';
130
+ this.name = 'Documentation Hygiene';
131
+ this.description = 'Changed docs must have valid internal links and up-to-date references';
132
+ this.requiredLevel = 'M';
133
+ }
134
+ async execute() {
135
+ const blockers = [];
136
+ const evidenceItems = [];
137
+ const cwd = process.cwd();
138
+ // Check 1: Find changed markdown files
139
+ const changedMd = gitCommand('diff --name-only HEAD -- "*.md"', cwd);
140
+ const mdFiles = changedMd ? changedMd.split('\n').filter(f => f.endsWith('.md')) : [];
141
+ evidenceItems.push(createEvidence({
142
+ kind: 'command',
143
+ label: 'Changed markdown files',
144
+ passed: true,
145
+ detail: mdFiles.length > 0 ? `${mdFiles.length} markdown file(s) changed` : 'No markdown files changed',
146
+ }));
147
+ if (mdFiles.length === 0) {
148
+ return {
149
+ gate: this.stage,
150
+ status: 'PASSED',
151
+ passed: true,
152
+ evidence: textEvidence(evidenceItems),
153
+ evidenceItems,
154
+ blockers: [],
155
+ durationMs: 0,
156
+ };
157
+ }
158
+ // Check 2: Internal link validation for changed files
159
+ const brokenLinks = [];
160
+ for (const file of mdFiles) {
161
+ const fullPath = join(cwd, file);
162
+ if (!existsSync(fullPath))
163
+ continue;
164
+ try {
165
+ const content = readFileSync(fullPath, 'utf-8');
166
+ const linkPattern = /\[([^\]]*)\]\(([^)]+)\)/g;
167
+ let match;
168
+ while ((match = linkPattern.exec(content)) !== null) {
169
+ const linkTarget = match[2];
170
+ if (linkTarget.startsWith('http') || linkTarget.startsWith('#') || linkTarget.startsWith('mailto:'))
171
+ continue;
172
+ const [linkPath] = linkTarget.split('#');
173
+ if (!linkPath)
174
+ continue;
175
+ const resolvedTarget = join(cwd, require('path').dirname(file), linkPath);
176
+ if (!existsSync(resolvedTarget)) {
177
+ brokenLinks.push(`${file}: [${match[1]}](${linkTarget})`);
178
+ }
179
+ }
180
+ }
181
+ catch {
182
+ // Skip unreadable files
183
+ }
184
+ }
185
+ const linksPassed = brokenLinks.length === 0;
186
+ if (!linksPassed) {
187
+ // Broken links are warnings, not blockers for M level
188
+ evidenceItems.push(createEvidence({
189
+ kind: 'file',
190
+ label: 'Internal link check',
191
+ passed: false,
192
+ detail: `Broken links: ${brokenLinks.slice(0, 5).join('; ')}${brokenLinks.length > 5 ? ` (+${brokenLinks.length - 5} more)` : ''}`,
193
+ }));
194
+ }
195
+ else {
196
+ evidenceItems.push(createEvidence({
197
+ kind: 'file',
198
+ label: 'Internal link check',
199
+ passed: true,
200
+ detail: 'All internal links valid',
201
+ }));
202
+ }
203
+ // Check 3: Version reference freshness
204
+ const versionRefs = [];
205
+ for (const file of mdFiles) {
206
+ const fullPath = join(cwd, file);
207
+ if (!existsSync(fullPath))
208
+ continue;
209
+ try {
210
+ const content = readFileSync(fullPath, 'utf-8');
211
+ const outdatedVersionPattern = /v0\.(3[0-5])\.\d+/g;
212
+ let match;
213
+ while ((match = outdatedVersionPattern.exec(content)) !== null) {
214
+ versionRefs.push(`${file}: references ${match[0]}`);
215
+ }
216
+ }
217
+ catch {
218
+ // Skip
219
+ }
220
+ }
221
+ evidenceItems.push(createEvidence({
222
+ kind: 'file',
223
+ label: 'Version references',
224
+ passed: versionRefs.length === 0,
225
+ detail: versionRefs.length === 0 ? 'No outdated version references' : `Outdated: ${versionRefs.slice(0, 3).join('; ')}`,
226
+ }));
227
+ const passed = blockers.length === 0;
228
+ return {
229
+ gate: this.stage,
230
+ status: passed ? 'PASSED' : 'FAILED',
231
+ passed,
232
+ evidence: textEvidence(evidenceItems),
233
+ evidenceItems,
234
+ blockers,
235
+ durationMs: 0,
236
+ };
237
+ }
238
+ }
239
+ // ============================================================================
240
+ // G18: Runtime Evidence — 运行时证据门禁
241
+ // ============================================================================
242
+ export class RuntimeEvidenceGate {
243
+ constructor(scaleDir = '.scale') {
244
+ this.scaleDir = scaleDir;
245
+ this.stage = 'G18';
246
+ this.name = 'Runtime Evidence';
247
+ this.description = 'Task must have recorded runtime evidence with matching exit codes';
248
+ this.requiredLevel = 'M';
249
+ }
250
+ async execute() {
251
+ const blockers = [];
252
+ const evidenceItems = [];
253
+ // Check 1: Evidence directory exists
254
+ const evidenceDir = join(this.scaleDir, 'evidence');
255
+ if (!existsSync(evidenceDir)) {
256
+ evidenceItems.push(createEvidence({
257
+ kind: 'file',
258
+ label: 'Evidence directory',
259
+ passed: false,
260
+ detail: 'No .scale/evidence/ directory found',
261
+ }));
262
+ blockers.push('No runtime evidence directory');
263
+ return {
264
+ gate: this.stage,
265
+ status: 'BLOCKED',
266
+ passed: false,
267
+ evidence: textEvidence(evidenceItems),
268
+ evidenceItems,
269
+ blockers,
270
+ durationMs: 0,
271
+ };
272
+ }
273
+ // Check 2: Recent evidence files exist
274
+ const evidenceFiles = readdirSync(evidenceDir).filter(f => f.endsWith('.json')).sort().reverse();
275
+ const recentEvidence = evidenceFiles.slice(0, 10);
276
+ evidenceItems.push(createEvidence({
277
+ kind: 'file',
278
+ label: 'Evidence files',
279
+ passed: recentEvidence.length > 0,
280
+ detail: `${evidenceFiles.length} evidence file(s), ${recentEvidence.length} recent`,
281
+ }));
282
+ // Check 3: Evidence freshness (within 24h)
283
+ let freshEvidence = false;
284
+ if (recentEvidence.length > 0) {
285
+ try {
286
+ const latestPath = join(evidenceDir, recentEvidence[0]);
287
+ const latest = JSON.parse(readFileSync(latestPath, 'utf-8'));
288
+ const evidenceTime = new Date(latest.timestamp || latest.createdAt || 0).getTime();
289
+ const hoursSince = (Date.now() - evidenceTime) / (1000 * 60 * 60);
290
+ freshEvidence = hoursSince < 24;
291
+ evidenceItems.push(createEvidence({
292
+ kind: 'file',
293
+ label: 'Evidence freshness',
294
+ passed: freshEvidence,
295
+ detail: freshEvidence
296
+ ? `Latest evidence ${hoursSince.toFixed(1)}h ago (< 24h)`
297
+ : `Latest evidence ${hoursSince.toFixed(1)}h ago (>= 24h, stale)`,
298
+ }));
299
+ }
300
+ catch {
301
+ evidenceItems.push(createEvidence({
302
+ kind: 'file',
303
+ label: 'Evidence freshness',
304
+ passed: false,
305
+ detail: 'Could not parse latest evidence file',
306
+ }));
307
+ }
308
+ }
309
+ // Check 4: Passed evidence exists
310
+ let hasPassedEvidence = false;
311
+ for (const file of recentEvidence) {
312
+ try {
313
+ const content = JSON.parse(readFileSync(join(evidenceDir, file), 'utf-8'));
314
+ if (content.status === 'passed' || content.exitCode === 0) {
315
+ hasPassedEvidence = true;
316
+ break;
317
+ }
318
+ }
319
+ catch {
320
+ continue;
321
+ }
322
+ }
323
+ evidenceItems.push(createEvidence({
324
+ kind: 'file',
325
+ label: 'Passed evidence',
326
+ passed: hasPassedEvidence,
327
+ detail: hasPassedEvidence ? 'At least one passed evidence record found' : 'No passed evidence records found',
328
+ }));
329
+ if (!hasPassedEvidence)
330
+ blockers.push('No passed runtime evidence found');
331
+ const passed = blockers.length === 0;
332
+ return {
333
+ gate: this.stage,
334
+ status: passed ? 'PASSED' : 'BLOCKED',
335
+ passed,
336
+ evidence: textEvidence(evidenceItems),
337
+ evidenceItems,
338
+ blockers,
339
+ durationMs: 0,
340
+ };
341
+ }
342
+ }
343
+ // ============================================================================
344
+ // G19: Code Review — 代码评审门禁
345
+ // ============================================================================
346
+ export class CodeReviewGate {
347
+ constructor(scaleDir = '.scale') {
348
+ this.scaleDir = scaleDir;
349
+ this.stage = 'G19';
350
+ this.name = 'Code Review';
351
+ this.description = 'L/CRITICAL tasks require reviewed changes with resolved findings';
352
+ this.requiredLevel = 'L';
353
+ }
354
+ async execute() {
355
+ const blockers = [];
356
+ const evidenceItems = [];
357
+ // Check 1: Review artifacts exist
358
+ const reviewDir = join(this.scaleDir, 'state');
359
+ let hasReview = false;
360
+ let reviewCount = 0;
361
+ let unresolvedFindings = 0;
362
+ if (existsSync(reviewDir)) {
363
+ const stateFiles = readdirSync(reviewDir).filter(f => f.startsWith('review-') && f.endsWith('.json'));
364
+ reviewCount = stateFiles.length;
365
+ hasReview = reviewCount > 0;
366
+ for (const file of stateFiles) {
367
+ try {
368
+ const content = JSON.parse(readFileSync(join(reviewDir, file), 'utf-8'));
369
+ const findings = content.findings ?? [];
370
+ unresolvedFindings += findings.filter((f) => !f.resolved).length;
371
+ }
372
+ catch {
373
+ continue;
374
+ }
375
+ }
376
+ }
377
+ evidenceItems.push(createEvidence({
378
+ kind: 'file',
379
+ label: 'Review artifacts',
380
+ passed: hasReview,
381
+ detail: hasReview ? `${reviewCount} review file(s) found` : 'No review artifacts found',
382
+ }));
383
+ if (!hasReview) {
384
+ blockers.push('No code review artifacts found (required for L/CRITICAL)');
385
+ }
386
+ // Check 2: Unresolved findings
387
+ const findingsPassed = unresolvedFindings === 0;
388
+ if (!findingsPassed) {
389
+ blockers.push(`${unresolvedFindings} unresolved finding(s) in review`);
390
+ }
391
+ evidenceItems.push(createEvidence({
392
+ kind: 'file',
393
+ label: 'Unresolved findings',
394
+ passed: findingsPassed,
395
+ detail: findingsPassed ? 'No unresolved findings' : `${unresolvedFindings} unresolved finding(s)`,
396
+ }));
397
+ const passed = blockers.length === 0;
398
+ return {
399
+ gate: this.stage,
400
+ status: passed ? 'PASSED' : 'BLOCKED',
401
+ passed,
402
+ evidence: textEvidence(evidenceItems),
403
+ evidenceItems,
404
+ blockers,
405
+ durationMs: 0,
406
+ };
407
+ }
408
+ }
409
+ // ============================================================================
410
+ // G20: Supply Chain — 供应链安全门禁
411
+ // ============================================================================
412
+ export class SupplyChainGate {
413
+ constructor() {
414
+ this.stage = 'G20';
415
+ this.name = 'Supply Chain';
416
+ this.description = 'No CRITICAL/HIGH vulnerabilities; lock file must be consistent';
417
+ this.requiredLevel = 'ALWAYS';
418
+ }
419
+ async execute() {
420
+ const blockers = [];
421
+ const evidenceItems = [];
422
+ const cwd = process.cwd();
423
+ // Check 1: npm audit
424
+ const auditOutput = gitCommand('npm audit --json 2>/dev/null || true', cwd);
425
+ let criticalCount = 0;
426
+ let highCount = 0;
427
+ try {
428
+ const audit = JSON.parse(auditOutput);
429
+ const vulnerabilities = audit.vulnerabilities ?? {};
430
+ for (const [, vuln] of Object.entries(vulnerabilities)) {
431
+ if (vuln.severity === 'critical')
432
+ criticalCount++;
433
+ if (vuln.severity === 'high')
434
+ highCount++;
435
+ }
436
+ }
437
+ catch {
438
+ // npm audit not available or not JSON
439
+ }
440
+ const auditPassed = criticalCount === 0 && highCount === 0;
441
+ if (!auditPassed) {
442
+ blockers.push(`npm audit: ${criticalCount} critical, ${highCount} high vulnerabilities`);
443
+ }
444
+ evidenceItems.push(createEvidence({
445
+ kind: 'command',
446
+ label: 'npm audit',
447
+ passed: auditPassed,
448
+ detail: auditPassed ? 'No CRITICAL/HIGH vulnerabilities' : `${criticalCount} critical, ${highCount} high`,
449
+ }));
450
+ // Check 2: Lock file consistency
451
+ const hasLockFile = existsSync(join(cwd, 'package-lock.json')) || existsSync(join(cwd, 'pnpm-lock.yaml')) || existsSync(join(cwd, 'bun.lock'));
452
+ evidenceItems.push(createEvidence({
453
+ kind: 'file',
454
+ label: 'Lock file',
455
+ passed: hasLockFile,
456
+ detail: hasLockFile ? 'Lock file present' : 'No lock file found',
457
+ }));
458
+ // Check 3: package.json consistency (no phantom dependencies)
459
+ try {
460
+ const pkg = JSON.parse(readFileSync(join(cwd, 'package.json'), 'utf-8'));
461
+ const allDeps = {
462
+ ...(pkg.dependencies ?? {}),
463
+ ...(pkg.devDependencies ?? {}),
464
+ };
465
+ const depCount = Object.keys(allDeps).length;
466
+ evidenceItems.push(createEvidence({
467
+ kind: 'file',
468
+ label: 'Dependency count',
469
+ passed: true,
470
+ detail: `${depCount} declared dependencies`,
471
+ }));
472
+ }
473
+ catch {
474
+ // No package.json
475
+ }
476
+ const passed = blockers.length === 0;
477
+ return {
478
+ gate: this.stage,
479
+ status: passed ? 'PASSED' : 'FAILED',
480
+ passed,
481
+ evidence: textEvidence(evidenceItems),
482
+ evidenceItems,
483
+ blockers,
484
+ durationMs: 0,
485
+ };
486
+ }
487
+ }
488
+ // ============================================================================
489
+ // G21: Context Budget — 上下文预算门禁
490
+ // ============================================================================
491
+ export class ContextBudgetGate {
492
+ constructor(scaleDir = '.scale') {
493
+ this.scaleDir = scaleDir;
494
+ this.stage = 'G21';
495
+ this.name = 'Context Budget';
496
+ this.description = 'Task context must be within token budget; no redundant loading';
497
+ this.requiredLevel = 'M';
498
+ }
499
+ async execute() {
500
+ const evidenceItems = [];
501
+ // Check 1: Context budget configuration
502
+ const budgetPath = join(this.scaleDir, 'context-budget.json');
503
+ const hasBudget = existsSync(budgetPath);
504
+ evidenceItems.push(createEvidence({
505
+ kind: 'file',
506
+ label: 'Context budget config',
507
+ passed: hasBudget,
508
+ detail: hasBudget ? 'Context budget configured' : 'No context budget configuration',
509
+ }));
510
+ // Check 2: Context budget report
511
+ const reportPath = join(this.scaleDir, 'context-budget-report.json');
512
+ if (existsSync(reportPath)) {
513
+ try {
514
+ const report = JSON.parse(readFileSync(reportPath, 'utf-8'));
515
+ const totalTokens = report.summary?.totalTokens ?? 0;
516
+ const maxTokens = report.thresholds?.maxAlwaysTokens ?? 10000;
517
+ const withinBudget = totalTokens <= maxTokens;
518
+ evidenceItems.push(createEvidence({
519
+ kind: 'file',
520
+ label: 'Token budget',
521
+ passed: withinBudget,
522
+ detail: `${totalTokens} tokens used / ${maxTokens} max`,
523
+ }));
524
+ }
525
+ catch {
526
+ evidenceItems.push(createEvidence({
527
+ kind: 'file',
528
+ label: 'Token budget',
529
+ passed: false,
530
+ detail: 'Could not parse context budget report',
531
+ }));
532
+ }
533
+ }
534
+ else {
535
+ evidenceItems.push(createEvidence({
536
+ kind: 'file',
537
+ label: 'Token budget',
538
+ passed: true,
539
+ detail: 'No budget report (advisory only)',
540
+ }));
541
+ }
542
+ return {
543
+ gate: this.stage,
544
+ status: 'PASSED',
545
+ passed: true, // Advisory only, never blocks
546
+ evidence: textEvidence(evidenceItems),
547
+ evidenceItems,
548
+ blockers: [],
549
+ durationMs: 0,
550
+ };
551
+ }
552
+ }
553
+ // ============================================================================
554
+ // G22: Session Health — 会话健康门禁
555
+ // ============================================================================
556
+ export class SessionHealthGate {
557
+ constructor() {
558
+ this.stage = 'G22';
559
+ this.name = 'Session Health';
560
+ this.description = 'No leaked worktrees; session state is consistent';
561
+ this.requiredLevel = 'M';
562
+ }
563
+ async execute() {
564
+ const evidenceItems = [];
565
+ const cwd = process.cwd();
566
+ // Check 1: Stale worktrees
567
+ const worktreeLocations = [
568
+ join(cwd, '.claude', 'worktrees'),
569
+ join(cwd, '.scale', 'worktrees'),
570
+ join(cwd, '.codex', 'worktrees'),
571
+ ];
572
+ let staleWorktrees = 0;
573
+ for (const dir of worktreeLocations) {
574
+ if (existsSync(dir)) {
575
+ try {
576
+ const entries = readdirSync(dir);
577
+ staleWorktrees += entries.length;
578
+ }
579
+ catch {
580
+ // Skip
581
+ }
582
+ }
583
+ }
584
+ const worktreePassed = staleWorktrees === 0;
585
+ evidenceItems.push(createEvidence({
586
+ kind: 'file',
587
+ label: 'Stale worktrees',
588
+ passed: worktreePassed,
589
+ detail: worktreePassed ? 'No stale worktrees' : `${staleWorktrees} worktree(s) found in .claude/worktrees or .scale/worktrees`,
590
+ }));
591
+ // Check 2: Git worktree list
592
+ const gitWorktrees = gitCommand('worktree list --porcelain', cwd);
593
+ const worktreeCount = gitWorktrees ? gitWorktrees.split('\n').filter(l => l.startsWith('worktree ')).length : 0;
594
+ evidenceItems.push(createEvidence({
595
+ kind: 'command',
596
+ label: 'Git worktrees',
597
+ passed: worktreeCount <= 3,
598
+ detail: `${worktreeCount} git worktree(s)`,
599
+ }));
600
+ // Check 3: Session state file
601
+ const statePath = join(cwd, '.scale', 'state', 'current.json');
602
+ if (existsSync(statePath)) {
603
+ try {
604
+ const state = JSON.parse(readFileSync(statePath, 'utf-8'));
605
+ const hasOpenTasks = (state.openTasks ?? []).length > 0;
606
+ evidenceItems.push(createEvidence({
607
+ kind: 'file',
608
+ label: 'Session state',
609
+ passed: true,
610
+ detail: `Task: ${state.taskId ?? 'none'}, Phase: ${state.phase ?? 'none'}, Open tasks: ${state.openTasks?.length ?? 0}`,
611
+ }));
612
+ }
613
+ catch {
614
+ evidenceItems.push(createEvidence({
615
+ kind: 'file',
616
+ label: 'Session state',
617
+ passed: false,
618
+ detail: 'Could not parse session state',
619
+ }));
620
+ }
621
+ }
622
+ else {
623
+ evidenceItems.push(createEvidence({
624
+ kind: 'file',
625
+ label: 'Session state',
626
+ passed: true,
627
+ detail: 'No active session state',
628
+ }));
629
+ }
630
+ return {
631
+ gate: this.stage,
632
+ status: 'PASSED',
633
+ passed: true, // Advisory only, never blocks
634
+ evidence: textEvidence(evidenceItems),
635
+ evidenceItems,
636
+ blockers: [],
637
+ durationMs: 0,
638
+ };
639
+ }
640
+ }
641
+ // ============================================================================
642
+ // 注册所有增强门禁
643
+ // ============================================================================
644
+ export function registerEnhancedGates(gateSystem, scaleDir = '.scale') {
645
+ gateSystem.registerGate(new CommitDisciplineGate());
646
+ gateSystem.registerGate(new DocumentationHygieneGate());
647
+ gateSystem.registerGate(new RuntimeEvidenceGate(scaleDir));
648
+ gateSystem.registerGate(new CodeReviewGate(scaleDir));
649
+ gateSystem.registerGate(new SupplyChainGate());
650
+ gateSystem.registerGate(new ContextBudgetGate(scaleDir));
651
+ gateSystem.registerGate(new SessionHealthGate());
652
+ }
653
+ //# sourceMappingURL=EnhancedGates.js.map