pumuki 6.3.373 → 6.3.375

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.
@@ -104,6 +104,7 @@ type LifecycleCommand =
104
104
  type SddCommand =
105
105
  | 'status'
106
106
  | 'validate'
107
+ | 'guard'
107
108
  | 'session'
108
109
  | 'sync-docs'
109
110
  | 'learn'
@@ -219,6 +220,7 @@ Pumuki lifecycle commands:
219
220
  pumuki context repair [--json]
220
221
  pumuki sdd status [--json]
221
222
  pumuki sdd validate [--stage=PRE_WRITE|PRE_COMMIT|PRE_PUSH|CI] [--json]
223
+ pumuki sdd guard [--stage=PRE_WRITE] [--json]
222
224
  pumuki sdd session --open --change=<change-id|auto> [--ttl-minutes=<n>] [--json]
223
225
  pumuki sdd session --refresh [--ttl-minutes=<n>] [--json]
224
226
  pumuki sdd session --close [--json]
@@ -998,6 +1000,7 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
998
1000
  if (
999
1001
  subcommandRaw !== 'status' &&
1000
1002
  subcommandRaw !== 'validate' &&
1003
+ subcommandRaw !== 'guard' &&
1001
1004
  subcommandRaw !== 'session' &&
1002
1005
  subcommandRaw !== 'sync-docs' &&
1003
1006
  subcommandRaw !== 'sync' &&
@@ -1039,7 +1042,7 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
1039
1042
  throw new Error(`--dry-run is only supported with "pumuki sdd sync-docs", "pumuki sdd learn", "pumuki sdd auto-sync", "pumuki sdd evidence" or "pumuki sdd state-sync".\n\n${HELP_TEXT}`);
1040
1043
  }
1041
1044
  if (arg.startsWith('--stage=')) {
1042
- if (sddCommand === 'validate') {
1045
+ if (sddCommand === 'validate' || sddCommand === 'guard') {
1043
1046
  sddStage = parseSddStage(arg.slice('--stage='.length));
1044
1047
  continue;
1045
1048
  }
@@ -1055,7 +1058,7 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
1055
1058
  sddAutoSyncStage = parseSddStage(arg.slice('--stage='.length));
1056
1059
  continue;
1057
1060
  }
1058
- throw new Error(`--stage is only supported with "pumuki sdd validate", "pumuki sdd sync-docs", "pumuki sdd learn" or "pumuki sdd auto-sync".\n\n${HELP_TEXT}`);
1061
+ throw new Error(`--stage is only supported with "pumuki sdd validate", "pumuki sdd guard", "pumuki sdd sync-docs", "pumuki sdd learn" or "pumuki sdd auto-sync".\n\n${HELP_TEXT}`);
1059
1062
  }
1060
1063
  if (arg.startsWith('--status=')) {
1061
1064
  if (sddCommand !== 'state-sync') {
@@ -1272,6 +1275,15 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
1272
1275
  sddStage: sddStage ?? 'PRE_COMMIT',
1273
1276
  };
1274
1277
  }
1278
+ if (sddCommand === 'guard') {
1279
+ return {
1280
+ command: commandRaw,
1281
+ purgeArtifacts: false,
1282
+ json,
1283
+ sddCommand,
1284
+ sddStage: sddStage ?? 'PRE_WRITE',
1285
+ };
1286
+ }
1275
1287
  if (sddCommand === 'sync-docs') {
1276
1288
  if (
1277
1289
  sddSessionAction ||
@@ -22,6 +22,7 @@ import {
22
22
  } from '../policy/preWriteEnforcement';
23
23
  import { readLifecycleExperimentalFeaturesSnapshot } from './experimentalFeaturesSnapshot';
24
24
  import { GitService } from '../git/GitService';
25
+ import { evaluatePreWriteAgentGuard } from './preWriteLease';
25
26
 
26
27
  import {
27
28
  type ParsedArgs,
@@ -68,6 +69,39 @@ export const runSddCommand = async (parsed: ParsedArgs, activeDependencies: Life
68
69
  }
69
70
  return 0;
70
71
  }
72
+ if (parsed.sddCommand === 'guard') {
73
+ const requestedStage = parsed.sddStage ?? 'PRE_WRITE';
74
+ if (requestedStage !== 'PRE_WRITE') {
75
+ if (parsed.json) {
76
+ writeInfo(JSON.stringify({
77
+ allowed: false,
78
+ stage: requestedStage,
79
+ code: 'PRE_WRITE_AGENT_GUARD_UNSUPPORTED_STAGE',
80
+ message: 'Agent pre-write guard only supports PRE_WRITE.',
81
+ }, null, 2));
82
+ } else {
83
+ writeInfo('[pumuki][sdd] guard allowed=no code=PRE_WRITE_AGENT_GUARD_UNSUPPORTED_STAGE');
84
+ writeInfo('[pumuki][sdd] Agent pre-write guard only supports PRE_WRITE.');
85
+ }
86
+ return 1;
87
+ }
88
+ const guard = evaluatePreWriteAgentGuard({
89
+ repoRoot: process.cwd(),
90
+ git: new GitService(),
91
+ });
92
+ if (parsed.json) {
93
+ writeInfo(JSON.stringify(guard, null, 2));
94
+ } else {
95
+ writeInfo(
96
+ `[pumuki][sdd] guard stage=PRE_WRITE allowed=${guard.allowed ? 'yes' : 'no'} code=${guard.code}`
97
+ );
98
+ writeInfo(`[pumuki][sdd] ${guard.message}`);
99
+ if (!guard.allowed) {
100
+ writeInfo(`[pumuki][sdd] solution: ${guard.expected_fix}`);
101
+ }
102
+ }
103
+ return guard.allowed ? 0 : 1;
104
+ }
71
105
  if (parsed.sddCommand === 'validate') {
72
106
  const requestedStage = parsed.sddStage ?? 'PRE_COMMIT';
73
107
  const preWriteEnforcement = requestedStage === 'PRE_WRITE'
@@ -50,6 +50,24 @@ export type PreWriteLeaseWriteResult = {
50
50
  changedCodePaths: string[];
51
51
  };
52
52
 
53
+ export type PreWriteAgentGuardResult = {
54
+ allowed: boolean;
55
+ stage: 'PRE_WRITE';
56
+ code:
57
+ | 'PRE_WRITE_AGENT_GUARD_ALLOWED'
58
+ | 'PRE_WRITE_AGENT_GUARD_LEASE_MISSING'
59
+ | 'PRE_WRITE_AGENT_GUARD_LEASE_INVALID'
60
+ | 'PRE_WRITE_AGENT_GUARD_LEASE_EXPIRED'
61
+ | 'PRE_WRITE_AGENT_GUARD_LEASE_HEAD_MISMATCH'
62
+ | 'PRE_WRITE_AGENT_GUARD_LEASE_DIRTY_AT_ISSUE'
63
+ | 'PRE_WRITE_AGENT_GUARD_CODE_CHANGED_WITHOUT_LEASE';
64
+ message: string;
65
+ expected_fix: string;
66
+ path: string;
67
+ changedCodePaths: string[];
68
+ lease_status: PreWriteLeaseStatus;
69
+ };
70
+
53
71
  const PRE_WRITE_LEASE_TTL_MS = 4 * 60 * 60 * 1000;
54
72
 
55
73
  export const resolvePreWriteLeasePath = (repoRoot: string): string =>
@@ -287,6 +305,48 @@ export const writePreWriteLease = (params: {
287
305
  };
288
306
  };
289
307
 
308
+ export const evaluatePreWriteAgentGuard = (params: {
309
+ repoRoot: string;
310
+ git: Pick<IGitService, 'runGit'>;
311
+ now?: Date;
312
+ }): PreWriteAgentGuardResult => {
313
+ const leaseStatus = readPreWriteLeaseStatus(params);
314
+ if (leaseStatus.valid) {
315
+ return {
316
+ allowed: true,
317
+ stage: 'PRE_WRITE',
318
+ code: 'PRE_WRITE_AGENT_GUARD_ALLOWED',
319
+ message: 'PRE_WRITE agent guard passed. A valid PRE_WRITE lease exists for this repo and diff.',
320
+ expected_fix: 'Continue with the current slice; keep edits inside the validated scope.',
321
+ path: leaseStatus.path,
322
+ changedCodePaths: leaseStatus.changedCodePaths,
323
+ lease_status: leaseStatus,
324
+ };
325
+ }
326
+
327
+ const hasCodeChanges = leaseStatus.changedCodePaths.length > 0;
328
+ const code = hasCodeChanges
329
+ ? 'PRE_WRITE_AGENT_GUARD_CODE_CHANGED_WITHOUT_LEASE'
330
+ : (`PRE_WRITE_AGENT_GUARD_${leaseStatus.code.replace('PRE_WRITE_', '')}` as PreWriteAgentGuardResult['code']);
331
+ const changedDetails = hasCodeChanges
332
+ ? ` Code changes already exist: ${leaseStatus.changedCodePaths.join(', ')}.`
333
+ : '';
334
+
335
+ return {
336
+ allowed: false,
337
+ stage: 'PRE_WRITE',
338
+ code,
339
+ message:
340
+ `AI agent instruction: STOP; do not apply patches or write files until PRE_WRITE is valid. ` +
341
+ `${leaseStatus.message}${changedDetails}`,
342
+ expected_fix:
343
+ 'run `npx pumuki-pre-write` before editing code. If code changes already exist, stop and ask whether to validate the current diff, stash it, or restart from a clean PRE_WRITE lease.',
344
+ path: leaseStatus.path,
345
+ changedCodePaths: leaseStatus.changedCodePaths,
346
+ lease_status: leaseStatus,
347
+ };
348
+ };
349
+
290
350
  export const toPreWriteEnforcementGapFinding = (params: {
291
351
  stage: 'PRE_COMMIT' | 'PRE_PUSH' | 'CI';
292
352
  status: PreWriteLeaseStatus;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki",
3
- "version": "6.3.373",
3
+ "version": "6.3.375",
4
4
  "description": "Enterprise-grade AST Intelligence System with multi-platform support (iOS, Android, Backend, Frontend) and Feature-First + DDD + Clean Architecture enforcement. Includes dynamic violations API for intelligent querying.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -154,6 +154,9 @@ const formatLocation = (cause: NonNullable<Extract<PumukiCriticalNotificationEve
154
154
  };
155
155
 
156
156
  const humanizeRuleId = (ruleId: string): string => {
157
+ if (ruleId === 'governance.skills.ios-test-quality.incomplete') {
158
+ return 'Calidad de tests iOS incompleta';
159
+ }
157
160
  const knownRules: Record<string, string> = {
158
161
  'no-empty-catch': 'catch vacío',
159
162
  'prefer-swift-testing': 'Test XCTest legacy',
@@ -240,7 +243,9 @@ const isSkillCause = (
240
243
  cause: NonNullable<Extract<PumukiCriticalNotificationEvent, { kind: 'gate.blocked' }>['blockingCauses']>[number]
241
244
  ): boolean =>
242
245
  (cause.ruleId ?? '').startsWith('skills.') ||
243
- cause.code.startsWith('SKILLS_');
246
+ (cause.ruleId ?? '').startsWith('governance.skills.') ||
247
+ cause.code.startsWith('SKILLS_') ||
248
+ cause.code.startsWith('GOVERNANCE_SKILLS_');
244
249
 
245
250
  const isSkillsContractCause = (
246
251
  cause: NonNullable<Extract<PumukiCriticalNotificationEvent, { kind: 'gate.blocked' }>['blockingCauses']>[number]
@@ -139,6 +139,9 @@ const formatCauseLocation = (cause: BlockedCause): string => {
139
139
  const formatCauseRule = (cause: BlockedCause): string => cause.ruleId ?? cause.code;
140
140
 
141
141
  const humanizeRuleId = (ruleId: string): string => {
142
+ if (ruleId === 'governance.skills.ios-test-quality.incomplete') {
143
+ return 'Calidad de tests iOS incompleta';
144
+ }
142
145
  const knownRules: Record<string, string> = {
143
146
  'prefer-swift-testing': 'Test XCTest legacy',
144
147
  'no-xctassert': 'Assertions XCTest en Swift Testing',
@@ -1,5 +1,8 @@
1
1
  import type { SmokeMode } from './package-install-smoke-contract';
2
- import { runDefaultSmokeGateSteps } from './package-install-smoke-execution-steps-lib';
2
+ import {
3
+ runDefaultSmokeGateSteps,
4
+ runSmokeGoldenFlowEvidenceStep,
5
+ } from './package-install-smoke-execution-steps-lib';
3
6
  import {
4
7
  assertLifecycleStatusMatchesSnapshot,
5
8
  captureLifecycleStatusSnapshot,
@@ -41,6 +44,7 @@ export const runPackageInstallSmoke = (mode: SmokeMode): void => {
41
44
  runLifecycleInstallStep(workspace);
42
45
  runLifecyclePreWriteStep(workspace);
43
46
  writeStagedPayload(workspace, mode);
47
+ runSmokeGoldenFlowEvidenceStep(workspace);
44
48
 
45
49
  const lifecycleStatusSnapshot = captureLifecycleStatusSnapshot(workspace);
46
50
 
@@ -1,7 +1,15 @@
1
+ import { writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
1
3
  import type { SmokeExpectation } from './package-install-smoke-contract';
4
+ import {
5
+ assertNoFatalOutput,
6
+ assertSuccess,
7
+ runCommand,
8
+ } from './package-install-smoke-runner-common';
2
9
  import { runGateStep, type SmokeGateStep } from './package-install-smoke-gate-lib';
3
10
  import { resolveConsumerPumukiCommand } from './package-install-smoke-command-resolution-lib';
4
11
  import type { SmokeWorkspace } from './package-install-smoke-workspace-contract';
12
+ import { pushCommandLog } from './package-install-smoke-workspace-lib';
5
13
 
6
14
  export type SmokeStepResult = {
7
15
  label: SmokeGateStep['label'];
@@ -62,3 +70,54 @@ export const runDefaultSmokeGateSteps = (params: {
62
70
  exitCode: result.exitCode,
63
71
  };
64
72
  });
73
+
74
+ export const runSmokeGoldenFlowEvidenceStep = (workspace: SmokeWorkspace): void => {
75
+ writeFileSync(
76
+ join(workspace.consumerRepo, 'package-smoke-minimal.feature'),
77
+ [
78
+ 'Feature: Package smoke minimal',
79
+ '',
80
+ ' Scenario: package smoke minimal',
81
+ ' Given a minimal package consumer',
82
+ ' When Pumuki gates run',
83
+ ' Then the package smoke passes',
84
+ '',
85
+ ].join('\n'),
86
+ 'utf8'
87
+ );
88
+ const addFeatureResult = runCommand({
89
+ cwd: workspace.consumerRepo,
90
+ executable: 'git',
91
+ args: ['add', 'package-smoke-minimal.feature'],
92
+ });
93
+ pushCommandLog(workspace.commandLog, addFeatureResult);
94
+ assertSuccess(addFeatureResult, 'git add package smoke feature');
95
+
96
+ const resolvedCommand = resolveConsumerPumukiCommand({
97
+ consumerRepo: workspace.consumerRepo,
98
+ binary: 'pumuki',
99
+ args: [
100
+ 'sdd',
101
+ 'evidence',
102
+ '--scenario-id=package-smoke-minimal',
103
+ '--test-command=package-smoke-minimal',
104
+ '--test-status=passed',
105
+ '--json',
106
+ ],
107
+ });
108
+ const result = runCommand({
109
+ cwd: workspace.consumerRepo,
110
+ executable: resolvedCommand.executable,
111
+ args: resolvedCommand.args,
112
+ env: {
113
+ PUMUKI_DISABLE_SYSTEM_NOTIFICATIONS: '1',
114
+ PUMUKI_SYSTEM_NOTIFICATIONS: '0',
115
+ PUMUKI_NOTIFICATIONS: '0',
116
+ PUMUKI_SDD_BYPASS: '1',
117
+ },
118
+ });
119
+
120
+ pushCommandLog(workspace.commandLog, result);
121
+ assertNoFatalOutput(result, 'pumuki-sdd-evidence');
122
+ assertSuccess(result, 'pumuki-sdd-evidence');
123
+ };