pumuki 6.3.39 → 6.3.40

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 (63) hide show
  1. package/README.md +21 -12
  2. package/VERSION +1 -1
  3. package/core/gate/evaluateRules.test.ts +40 -0
  4. package/core/gate/evaluateRules.ts +7 -1
  5. package/core/rules/Consequence.ts +1 -0
  6. package/docs/CONFIGURATION.md +50 -0
  7. package/docs/INSTALLATION.md +38 -11
  8. package/docs/MCP_SERVERS.md +1 -1
  9. package/docs/README.md +1 -0
  10. package/docs/RELEASE_NOTES.md +44 -0
  11. package/docs/USAGE.md +191 -9
  12. package/docs/registro-maestro-de-seguimiento.md +2 -2
  13. package/docs/seguimiento-activo-pumuki-saas-supermercados.md +1592 -1
  14. package/docs/validation/README.md +2 -1
  15. package/docs/validation/ast-intelligence-roadmap.md +96 -0
  16. package/integrations/config/skillsCustomRules.ts +14 -0
  17. package/integrations/config/skillsDetectorRegistry.ts +11 -1
  18. package/integrations/config/skillsLock.ts +30 -0
  19. package/integrations/config/skillsMarkdownRules.ts +14 -3
  20. package/integrations/config/skillsRuleSet.ts +25 -3
  21. package/integrations/evidence/readEvidence.test.ts +3 -2
  22. package/integrations/evidence/readEvidence.ts +14 -4
  23. package/integrations/evidence/repoState.ts +10 -2
  24. package/integrations/evidence/schema.test.ts +3 -2
  25. package/integrations/evidence/schema.ts +3 -0
  26. package/integrations/evidence/writeEvidence.test.ts +3 -2
  27. package/integrations/gate/evaluateAiGate.ts +511 -2
  28. package/integrations/git/GitService.ts +5 -1
  29. package/integrations/git/astIntelligenceDualValidation.ts +275 -0
  30. package/integrations/git/gitAtomicity.ts +42 -9
  31. package/integrations/git/resolveGitRefs.ts +37 -0
  32. package/integrations/git/runPlatformGate.ts +228 -1
  33. package/integrations/git/runPlatformGateEvaluation.ts +4 -0
  34. package/integrations/git/stageRunners.ts +116 -2
  35. package/integrations/lifecycle/cli.ts +759 -22
  36. package/integrations/lifecycle/doctor.ts +62 -0
  37. package/integrations/lifecycle/index.ts +1 -0
  38. package/integrations/lifecycle/packageInfo.ts +25 -3
  39. package/integrations/lifecycle/policyReconcile.ts +304 -0
  40. package/integrations/lifecycle/preWriteAutomation.ts +42 -2
  41. package/integrations/lifecycle/watch.ts +365 -0
  42. package/integrations/mcp/aiGateCheck.ts +59 -2
  43. package/integrations/mcp/autoExecuteAiStart.ts +25 -1
  44. package/integrations/mcp/preFlightCheck.ts +13 -0
  45. package/integrations/sdd/evidenceScaffold.ts +223 -0
  46. package/integrations/sdd/index.ts +2 -0
  47. package/integrations/sdd/stateSync.ts +400 -0
  48. package/integrations/sdd/syncDocs.ts +97 -2
  49. package/package.json +4 -1
  50. package/scripts/backlog-action-reasons-lib.ts +38 -0
  51. package/scripts/backlog-id-issue-map-lib.ts +69 -0
  52. package/scripts/backlog-json-contract-lib.ts +3 -0
  53. package/scripts/framework-menu-consumer-preflight-lib.ts +6 -0
  54. package/scripts/package-install-smoke-command-resolution-lib.ts +64 -0
  55. package/scripts/package-install-smoke-consumer-npm-lib.ts +43 -0
  56. package/scripts/package-install-smoke-consumer-repo-setup-lib.ts +2 -0
  57. package/scripts/package-install-smoke-execution-steps-lib.ts +27 -9
  58. package/scripts/package-install-smoke-lifecycle-lib.ts +15 -4
  59. package/scripts/package-install-smoke-workspace-factory-lib.ts +4 -1
  60. package/scripts/reconcile-consumer-backlog-issues-lib.ts +651 -0
  61. package/scripts/reconcile-consumer-backlog-issues.ts +348 -0
  62. package/scripts/watch-consumer-backlog-lib.ts +465 -0
  63. package/scripts/watch-consumer-backlog.ts +326 -0
@@ -169,6 +169,34 @@ const readNestedString = (
169
169
  return typeof cursor === 'string' && cursor.trim().length > 0 ? cursor : undefined;
170
170
  };
171
171
 
172
+ const hasRobustPumukiCommandResolution = (command: string): boolean => {
173
+ const normalized = command.trim();
174
+ if (normalized.length === 0) {
175
+ return false;
176
+ }
177
+
178
+ if (/node_modules[\\/]\.bin[\\/]pumuki/i.test(normalized)) {
179
+ return true;
180
+ }
181
+
182
+ if (
183
+ /node\s+.*node_modules[\\/]pumuki[\\/]bin[\\/]pumuki(?:-[a-z0-9-]+)?\.js/i.test(
184
+ normalized
185
+ )
186
+ ) {
187
+ return true;
188
+ }
189
+
190
+ if (/--package\s+pumuki(?:@[^\s]+)?/i.test(normalized)) {
191
+ return true;
192
+ }
193
+
194
+ return false;
195
+ };
196
+
197
+ const mutatesPathForCommandExecution = (command: string): boolean =>
198
+ /\bPATH\s*=\s*[^\n]*\$PATH/i.test(command);
199
+
172
200
  const buildDeepCheck = (
173
201
  check: Omit<DoctorDeepCheck, 'status' | 'severity'> & {
174
202
  status: DoctorDeepCheck['status'];
@@ -283,6 +311,40 @@ const evaluateAdapterWiringCheck = (repoRoot: string): DoctorDeepCheck => {
283
311
  });
284
312
  }
285
313
 
314
+ const weakResolutionPaths = requiredCommandPaths
315
+ .map((path) => {
316
+ const command = readNestedString(parsed, path);
317
+ return {
318
+ path: path.join('.'),
319
+ command,
320
+ };
321
+ })
322
+ .filter(
323
+ (entry) =>
324
+ entry.command &&
325
+ (!hasRobustPumukiCommandResolution(entry.command) ||
326
+ mutatesPathForCommandExecution(entry.command))
327
+ );
328
+
329
+ if (weakResolutionPaths.length > 0) {
330
+ const pathMutationCount = weakResolutionPaths.filter(
331
+ (entry) => entry.command && mutatesPathForCommandExecution(entry.command)
332
+ ).length;
333
+ return buildDeepCheck({
334
+ id: 'adapter-wiring',
335
+ status: 'fail',
336
+ severity: 'warning',
337
+ message: `Adapter wiring commands are present but use fragile binary resolution or PATH mutation (${weakResolutionPaths.map((entry) => entry.path).join(', ')}).`,
338
+ remediation:
339
+ 'Re-run "pumuki adapter install --agent=codex" to restore robust local/bin-or-package command resolution.',
340
+ metadata: {
341
+ path: adapterPath,
342
+ weak_resolution_count: weakResolutionPaths.length,
343
+ path_mutation_count: pathMutationCount,
344
+ },
345
+ });
346
+ }
347
+
286
348
  return buildDeepCheck({
287
349
  id: 'adapter-wiring',
288
350
  status: 'pass',
@@ -88,6 +88,7 @@ export {
88
88
  resolveLoopSessionsDirectory,
89
89
  updateLoopSession,
90
90
  } from './loopSessionStore';
91
+ export { runPolicyReconcile } from './policyReconcile';
91
92
  export type {
92
93
  CreateHotspotsSaasIngestionPayloadParams,
93
94
  HotspotsSaasIngestionPayloadBodyCompat,
@@ -17,15 +17,37 @@ const readConsumerInstalledVersion = (repoRoot: string): string | null => {
17
17
  }
18
18
  };
19
19
 
20
- export const getCurrentPumukiVersion = (params?: { repoRoot?: string }): string => {
20
+ export type PumukiVersionMetadata = {
21
+ resolvedVersion: string;
22
+ runtimeVersion: string;
23
+ consumerInstalledVersion: string | null;
24
+ source: 'consumer-node-modules' | 'runtime-package';
25
+ };
26
+
27
+ export const resolvePumukiVersionMetadata = (params?: { repoRoot?: string }): PumukiVersionMetadata => {
28
+ const runtimeVersion = packageJson.version;
21
29
  const repoRoot = params?.repoRoot;
22
30
  if (typeof repoRoot === 'string' && repoRoot.trim().length > 0) {
23
31
  const installedVersion = readConsumerInstalledVersion(repoRoot.trim());
24
32
  if (installedVersion) {
25
- return installedVersion;
33
+ return {
34
+ resolvedVersion: installedVersion,
35
+ runtimeVersion,
36
+ consumerInstalledVersion: installedVersion,
37
+ source: 'consumer-node-modules',
38
+ };
26
39
  }
27
40
  }
28
- return packageJson.version;
41
+ return {
42
+ resolvedVersion: runtimeVersion,
43
+ runtimeVersion,
44
+ consumerInstalledVersion: null,
45
+ source: 'runtime-package',
46
+ };
47
+ };
48
+
49
+ export const getCurrentPumukiVersion = (params?: { repoRoot?: string }): string => {
50
+ return resolvePumukiVersionMetadata(params).resolvedVersion;
29
51
  };
30
52
 
31
53
  export const getCurrentPumukiPackageName = (): string => packageJson.name;
@@ -0,0 +1,304 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import {
4
+ readLifecyclePolicyValidationSnapshot,
5
+ type LifecyclePolicyValidationSnapshot,
6
+ } from './policyValidationSnapshot';
7
+
8
+ type PolicyReconcileSeverity = 'INFO' | 'WARN' | 'ERROR';
9
+
10
+ type PolicyReconcileDriftCode =
11
+ | 'AGENTS_FILE_MISSING'
12
+ | 'AGENTS_REQUIRED_SKILLS_EMPTY'
13
+ | 'SKILLS_LOCK_MISSING'
14
+ | 'SKILLS_LOCK_INVALID'
15
+ | 'AGENTS_REQUIRED_SKILL_MISSING_IN_LOCK'
16
+ | 'POLICY_STAGE_INVALID'
17
+ | 'POLICY_STAGE_UNSIGNED_OR_COMPUTED'
18
+ | 'POLICY_STAGE_SIGNATURE_MISSING'
19
+ | 'POLICY_HASH_DIVERGENCE'
20
+ | 'POLICY_STAGE_NON_STRICT';
21
+
22
+ export type PolicyReconcileDrift = {
23
+ code: PolicyReconcileDriftCode;
24
+ severity: PolicyReconcileSeverity;
25
+ blocking: boolean;
26
+ message: string;
27
+ remediation: string | null;
28
+ context?: Record<string, unknown>;
29
+ };
30
+
31
+ export type PolicyReconcileReport = {
32
+ command: 'pumuki policy reconcile';
33
+ repoRoot: string;
34
+ generatedAt: string;
35
+ strictRequested: boolean;
36
+ summary: {
37
+ total: number;
38
+ blocking: number;
39
+ errors: number;
40
+ warnings: number;
41
+ infos: number;
42
+ status: 'PASS' | 'BLOCKED';
43
+ };
44
+ requiredSkills: string[];
45
+ stages: LifecyclePolicyValidationSnapshot['stages'];
46
+ drifts: PolicyReconcileDrift[];
47
+ };
48
+
49
+ const REQUIRED_SKILL_IDS = [
50
+ 'windsurf-rules-ios',
51
+ 'swift-concurrency',
52
+ 'swiftui-expert-skill',
53
+ 'windsurf-rules-frontend',
54
+ 'windsurf-rules-backend',
55
+ 'windsurf-rules-android',
56
+ ] as const;
57
+
58
+ type SkillsLockBundle = {
59
+ name?: string;
60
+ source?: string;
61
+ };
62
+
63
+ const addDrift = (
64
+ drifts: PolicyReconcileDrift[],
65
+ drift: PolicyReconcileDrift
66
+ ): void => {
67
+ drifts.push(drift);
68
+ };
69
+
70
+ const hasRequiredSkillInLock = (params: {
71
+ requiredSkill: string;
72
+ bundles: SkillsLockBundle[];
73
+ }): boolean => {
74
+ const required = params.requiredSkill.toLowerCase();
75
+ return params.bundles.some((bundle) => {
76
+ const name = typeof bundle.name === 'string' ? bundle.name.toLowerCase() : '';
77
+ const source = typeof bundle.source === 'string' ? bundle.source.toLowerCase() : '';
78
+ return name.includes(required) || source.includes(required);
79
+ });
80
+ };
81
+
82
+ export const runPolicyReconcile = (params?: {
83
+ repoRoot?: string;
84
+ now?: () => Date;
85
+ strict?: boolean;
86
+ }): PolicyReconcileReport => {
87
+ const repoRoot = resolve(params?.repoRoot ?? process.cwd());
88
+ const now = params?.now ?? (() => new Date());
89
+ const strictRequested = params?.strict === true;
90
+ const drifts: PolicyReconcileDrift[] = [];
91
+
92
+ const agentsPath = resolve(repoRoot, 'AGENTS.md');
93
+ const agentsExists = existsSync(agentsPath);
94
+ let agentsContent = '';
95
+ if (!agentsExists) {
96
+ addDrift(drifts, {
97
+ code: 'AGENTS_FILE_MISSING',
98
+ severity: 'ERROR',
99
+ blocking: true,
100
+ message: 'AGENTS.md is missing. Hard rules cannot be reconciled.',
101
+ remediation: 'Restore AGENTS.md at repository root before continuing.',
102
+ context: {
103
+ path: agentsPath,
104
+ },
105
+ });
106
+ } else {
107
+ agentsContent = readFileSync(agentsPath, 'utf8');
108
+ }
109
+
110
+ const requiredSkills = REQUIRED_SKILL_IDS.filter((skillId) =>
111
+ agentsContent.includes(skillId)
112
+ );
113
+ if (agentsExists && requiredSkills.length === 0) {
114
+ addDrift(drifts, {
115
+ code: 'AGENTS_REQUIRED_SKILLS_EMPTY',
116
+ severity: 'WARN',
117
+ blocking: false,
118
+ message:
119
+ 'AGENTS.md does not declare any known required skills from hard contract list.',
120
+ remediation: 'Declare required skills explicitly in AGENTS.md to keep policy reconciliation deterministic.',
121
+ context: {
122
+ skills: [...REQUIRED_SKILL_IDS],
123
+ },
124
+ });
125
+ }
126
+
127
+ const skillsLockPath = resolve(repoRoot, 'skills.lock.json');
128
+ const skillsLockExists = existsSync(skillsLockPath);
129
+ let bundles: SkillsLockBundle[] = [];
130
+ if (!skillsLockExists) {
131
+ addDrift(drifts, {
132
+ code: 'SKILLS_LOCK_MISSING',
133
+ severity: 'ERROR',
134
+ blocking: true,
135
+ message: 'skills.lock.json is missing.',
136
+ remediation: 'Regenerate skills lock before running strict policy reconciliation.',
137
+ context: {
138
+ path: skillsLockPath,
139
+ },
140
+ });
141
+ } else {
142
+ try {
143
+ const parsed = JSON.parse(readFileSync(skillsLockPath, 'utf8')) as {
144
+ bundles?: SkillsLockBundle[];
145
+ };
146
+ bundles = Array.isArray(parsed.bundles) ? parsed.bundles : [];
147
+ if (!Array.isArray(parsed.bundles)) {
148
+ addDrift(drifts, {
149
+ code: 'SKILLS_LOCK_INVALID',
150
+ severity: 'ERROR',
151
+ blocking: true,
152
+ message: 'skills.lock.json does not contain a valid bundles array.',
153
+ remediation: 'Rebuild skills lock with deterministic compiler output.',
154
+ context: {
155
+ path: skillsLockPath,
156
+ },
157
+ });
158
+ }
159
+ } catch (error) {
160
+ addDrift(drifts, {
161
+ code: 'SKILLS_LOCK_INVALID',
162
+ severity: 'ERROR',
163
+ blocking: true,
164
+ message: 'skills.lock.json is not valid JSON.',
165
+ remediation: 'Regenerate skills lock and retry policy reconciliation.',
166
+ context: {
167
+ path: skillsLockPath,
168
+ error: error instanceof Error ? error.message : String(error),
169
+ },
170
+ });
171
+ }
172
+ }
173
+
174
+ if (bundles.length > 0) {
175
+ for (const requiredSkill of requiredSkills) {
176
+ if (!hasRequiredSkillInLock({ requiredSkill, bundles })) {
177
+ addDrift(drifts, {
178
+ code: 'AGENTS_REQUIRED_SKILL_MISSING_IN_LOCK',
179
+ severity: 'ERROR',
180
+ blocking: true,
181
+ message: `Required skill "${requiredSkill}" is not mapped in skills.lock.json bundles.`,
182
+ remediation: 'Recompile skills lock and ensure required skill source is included.',
183
+ context: {
184
+ required_skill: requiredSkill,
185
+ lock_path: skillsLockPath,
186
+ },
187
+ });
188
+ }
189
+ }
190
+ }
191
+
192
+ const snapshot = readLifecyclePolicyValidationSnapshot(repoRoot);
193
+ const stageHashes = new Set<string>();
194
+ for (const [stage, stageSnapshot] of Object.entries(snapshot.stages)) {
195
+ stageHashes.add(stageSnapshot.hash);
196
+ const validationStatus = stageSnapshot.validationStatus;
197
+ const validationCode = stageSnapshot.validationCode;
198
+ if (validationStatus !== 'valid' || validationCode !== 'POLICY_AS_CODE_VALID') {
199
+ addDrift(drifts, {
200
+ code: 'POLICY_STAGE_INVALID',
201
+ severity: 'ERROR',
202
+ blocking: true,
203
+ message: `Stage ${stage} policy is not valid (${validationCode ?? 'UNKNOWN'}).`,
204
+ remediation: 'Fix policy-as-code contract and rerun reconciliation.',
205
+ context: {
206
+ stage,
207
+ validation_status: validationStatus,
208
+ validation_code: validationCode,
209
+ source: stageSnapshot.source,
210
+ hash: stageSnapshot.hash,
211
+ },
212
+ });
213
+ }
214
+ if (!stageSnapshot.strict) {
215
+ addDrift(drifts, {
216
+ code: 'POLICY_STAGE_NON_STRICT',
217
+ severity: strictRequested ? 'ERROR' : 'WARN',
218
+ blocking: strictRequested,
219
+ message: strictRequested
220
+ ? `Stage ${stage} runs with strict=false under strict reconcile mode.`
221
+ : `Stage ${stage} runs with strict=false.`,
222
+ remediation: strictRequested
223
+ ? 'Enable strict mode (PUMUKI_POLICY_STRICT=1) and rerun reconciliation.'
224
+ : 'Enable strict mode for enterprise fail-closed behavior.',
225
+ context: {
226
+ stage,
227
+ hash: stageSnapshot.hash,
228
+ },
229
+ });
230
+ }
231
+ if (
232
+ strictRequested &&
233
+ (typeof stageSnapshot.policySource !== 'string' ||
234
+ !stageSnapshot.policySource.startsWith('file:'))
235
+ ) {
236
+ addDrift(drifts, {
237
+ code: 'POLICY_STAGE_UNSIGNED_OR_COMPUTED',
238
+ severity: 'ERROR',
239
+ blocking: true,
240
+ message: `Stage ${stage} is not backed by a file-based policy-as-code contract.`,
241
+ remediation:
242
+ 'Provide .pumuki/policy-as-code.json with valid signatures and rerun strict reconcile.',
243
+ context: {
244
+ stage,
245
+ policy_source: stageSnapshot.policySource,
246
+ validation_code: stageSnapshot.validationCode,
247
+ },
248
+ });
249
+ }
250
+ if (
251
+ strictRequested &&
252
+ (typeof stageSnapshot.signature !== 'string' || stageSnapshot.signature.trim().length === 0)
253
+ ) {
254
+ addDrift(drifts, {
255
+ code: 'POLICY_STAGE_SIGNATURE_MISSING',
256
+ severity: 'ERROR',
257
+ blocking: true,
258
+ message: `Stage ${stage} policy signature is missing.`,
259
+ remediation:
260
+ 'Regenerate signed policy-as-code contract and rerun strict reconcile.',
261
+ context: {
262
+ stage,
263
+ policy_source: stageSnapshot.policySource,
264
+ },
265
+ });
266
+ }
267
+ }
268
+
269
+ if (stageHashes.size > 1) {
270
+ addDrift(drifts, {
271
+ code: 'POLICY_HASH_DIVERGENCE',
272
+ severity: 'WARN',
273
+ blocking: false,
274
+ message: 'Policy hashes differ across PRE_COMMIT/PRE_PUSH/CI stages.',
275
+ remediation: 'Align stage policy bundles to reduce contract drift.',
276
+ context: {
277
+ hashes: [...stageHashes],
278
+ },
279
+ });
280
+ }
281
+
282
+ const blocking = drifts.filter((drift) => drift.blocking).length;
283
+ const errors = drifts.filter((drift) => drift.severity === 'ERROR').length;
284
+ const warnings = drifts.filter((drift) => drift.severity === 'WARN').length;
285
+ const infos = drifts.filter((drift) => drift.severity === 'INFO').length;
286
+
287
+ return {
288
+ command: 'pumuki policy reconcile',
289
+ repoRoot,
290
+ generatedAt: now().toISOString(),
291
+ strictRequested,
292
+ summary: {
293
+ total: drifts.length,
294
+ blocking,
295
+ errors,
296
+ warnings,
297
+ infos,
298
+ status: blocking > 0 ? 'BLOCKED' : 'PASS',
299
+ },
300
+ requiredSkills,
301
+ stages: snapshot.stages,
302
+ drifts,
303
+ };
304
+ };
@@ -11,6 +11,7 @@ const PRE_WRITE_AUTOFIXABLE_EVIDENCE_CODES = new Set<string>([
11
11
  'EVIDENCE_INVALID',
12
12
  'EVIDENCE_CHAIN_INVALID',
13
13
  'EVIDENCE_STALE',
14
+ 'EVIDENCE_GATE_BLOCKED',
14
15
  'EVIDENCE_REPO_ROOT_MISMATCH',
15
16
  'EVIDENCE_BRANCH_MISMATCH',
16
17
  'EVIDENCE_RULES_COVERAGE_MISSING',
@@ -64,6 +65,9 @@ const defaultDependencies: PreWriteAutomationDependencies = {
64
65
  const hasAutoFixableEvidenceViolation = (aiGate: ReturnType<typeof evaluateAiGate>): boolean =>
65
66
  aiGate.violations.some((violation) => PRE_WRITE_AUTOFIXABLE_EVIDENCE_CODES.has(violation.code));
66
67
 
68
+ const hasEvidenceGateBlockedViolation = (aiGate: ReturnType<typeof evaluateAiGate>): boolean =>
69
+ aiGate.violations.some((violation) => violation.code === 'EVIDENCE_GATE_BLOCKED');
70
+
67
71
  const hasAutoFixableMcpReceiptViolation = (aiGate: ReturnType<typeof evaluateAiGate>): boolean =>
68
72
  aiGate.violations.some((violation) => PRE_WRITE_AUTOFIXABLE_MCP_RECEIPT_CODES.has(violation.code));
69
73
 
@@ -122,7 +126,43 @@ export const buildPreWriteAutomationTrace = async (params: {
122
126
  trace.actions.push({
123
127
  action: 'refresh_evidence',
124
128
  status: 'OK',
125
- details: `runPlatformGate exit_code=${gateExitCode}`,
129
+ details: `stage=PRE_COMMIT runPlatformGate exit_code=${gateExitCode}`,
130
+ });
131
+ aiGate = activeDependencies.runEnterpriseAiGateCheck({
132
+ repoRoot: params.repoRoot,
133
+ stage: 'PRE_WRITE',
134
+ requireMcpReceipt: true,
135
+ }).result;
136
+ } catch (error) {
137
+ trace.actions.push({
138
+ action: 'refresh_evidence',
139
+ status: 'FAILED',
140
+ details: error instanceof Error ? error.message : 'Unknown PRE_COMMIT evidence refresh error',
141
+ });
142
+ }
143
+ }
144
+
145
+ if (!aiGate.allowed && hasEvidenceGateBlockedViolation(aiGate)) {
146
+ trace.attempted = true;
147
+ try {
148
+ const gateExitCode = await params.runPlatformGate({
149
+ policy: {
150
+ stage: 'PRE_PUSH',
151
+ blockOnOrAbove: 'ERROR',
152
+ warnOnOrAbove: 'WARN',
153
+ },
154
+ scope: {
155
+ kind: 'workingTree',
156
+ },
157
+ auditMode: 'gate',
158
+ dependencies: {
159
+ printGateFindings: () => {},
160
+ },
161
+ });
162
+ trace.actions.push({
163
+ action: 'refresh_evidence',
164
+ status: 'OK',
165
+ details: `stage=PRE_PUSH runPlatformGate exit_code=${gateExitCode}`,
126
166
  });
127
167
  aiGate = activeDependencies.runEnterpriseAiGateCheck({
128
168
  repoRoot: params.repoRoot,
@@ -133,7 +173,7 @@ export const buildPreWriteAutomationTrace = async (params: {
133
173
  trace.actions.push({
134
174
  action: 'refresh_evidence',
135
175
  status: 'FAILED',
136
- details: error instanceof Error ? error.message : 'Unknown evidence refresh error',
176
+ details: error instanceof Error ? error.message : 'Unknown PRE_PUSH evidence refresh error',
137
177
  });
138
178
  }
139
179
  }