pumuki 6.3.116 → 6.3.118
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.
- package/CHANGELOG.md +15 -0
- package/VERSION +1 -1
- package/integrations/lifecycle/audit.ts +105 -8
- package/integrations/lifecycle/cli.ts +2 -8
- package/integrations/mcp/enterpriseServer.ts +40 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,21 @@ This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [6.3.118] - 2026-04-28
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **Guard PRE_WRITE para editores/agentes vía MCP:** el catálogo enterprise expone `pre_write_guard`, una tool no mutante que ejecuta `audit --stage=PRE_WRITE --json` y devuelve `findings` accionables antes de permitir continuar una edición/restauración de ficheros.
|
|
14
|
+
- **Cierre operativo de `PUMUKI-INC-109`:** RuralGo ya tiene una ruta MCP explícita para bloquear antes de escribir, no sólo en commit/gate posterior.
|
|
15
|
+
|
|
16
|
+
## [6.3.117] - 2026-04-28
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- **`pumuki audit --json` queda accionable cuando bloquea:** la salida ahora expone `findings_count`, `blocking_findings_count` y `findings`; si el gate bloquea sin findings persistidos, emite un blocker sintético `AUDIT_BLOCKED_WITHOUT_FINDINGS` para evitar JSON no accionable.
|
|
21
|
+
- **Auditoría previa a edición:** `pumuki audit --stage=PRE_WRITE --json` queda soportado para dar a consumers como RuralGo un contrato machine-friendly antes de continuar feature work.
|
|
22
|
+
- **Tracking externo prioritario:** el reset interno queda alineado con el bloqueo vivo de RuralGo `PUMUKI-INC-109`/`PUMUKI-INC-110`.
|
|
23
|
+
|
|
9
24
|
## [6.3.116] - 2026-04-25
|
|
10
25
|
|
|
11
26
|
### Fixed
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
v6.3.
|
|
1
|
+
v6.3.118
|
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
import { readEvidence } from '../evidence/readEvidence';
|
|
2
|
-
import {
|
|
2
|
+
import type { AiEvidenceV2_1, SnapshotFinding } from '../evidence/schema';
|
|
3
|
+
import { GitService, type IGitService } from '../git/GitService';
|
|
3
4
|
import { hasAllowedExtension } from '../git/gitDiffUtils';
|
|
4
5
|
import { runPlatformGate } from '../git/runPlatformGate';
|
|
5
6
|
import { evaluatePlatformGateFindings } from '../git/runPlatformGateEvaluation';
|
|
6
7
|
import { DEFAULT_FACT_FILE_EXTENSIONS } from '../git/runPlatformGateFacts';
|
|
7
|
-
import { resolvePolicyForStage } from '../gate/stagePolicies';
|
|
8
|
+
import { resolvePolicyForStage, type ResolvedStagePolicy } from '../gate/stagePolicies';
|
|
8
9
|
|
|
9
|
-
export type LifecycleAuditStage = 'PRE_COMMIT' | 'PRE_PUSH' | 'CI';
|
|
10
|
+
export type LifecycleAuditStage = 'PRE_WRITE' | 'PRE_COMMIT' | 'PRE_PUSH' | 'CI';
|
|
11
|
+
|
|
12
|
+
export type LifecycleAuditFinding = {
|
|
13
|
+
ruleId: string;
|
|
14
|
+
severity: string;
|
|
15
|
+
code: string;
|
|
16
|
+
message: string;
|
|
17
|
+
file: string;
|
|
18
|
+
lines?: SnapshotFinding['lines'];
|
|
19
|
+
blocking: boolean;
|
|
20
|
+
};
|
|
10
21
|
|
|
11
22
|
export type LifecycleAuditResult = {
|
|
12
23
|
command: 'pumuki audit';
|
|
@@ -18,14 +29,24 @@ export type LifecycleAuditResult = {
|
|
|
18
29
|
files_scanned: number | null;
|
|
19
30
|
untracked_matching_extensions_count: number;
|
|
20
31
|
snapshot_outcome: string | null;
|
|
32
|
+
findings_count: number;
|
|
33
|
+
blocking_findings_count: number;
|
|
34
|
+
findings: ReadonlyArray<LifecycleAuditFinding>;
|
|
21
35
|
policy_reconcile_hint: string;
|
|
22
36
|
};
|
|
23
37
|
|
|
38
|
+
type LifecycleAuditDependencies = {
|
|
39
|
+
git: IGitService;
|
|
40
|
+
readEvidence: typeof readEvidence;
|
|
41
|
+
resolvePolicyForStage: typeof resolvePolicyForStage;
|
|
42
|
+
runPlatformGate: typeof runPlatformGate;
|
|
43
|
+
};
|
|
44
|
+
|
|
24
45
|
const POLICY_RECONCILE_HINT =
|
|
25
46
|
'If .pumuki/policy-as-code.json signatures drift after a pumuki upgrade, run: pumuki policy reconcile --apply';
|
|
26
47
|
|
|
27
48
|
const countUntrackedMatchingExtensions = (
|
|
28
|
-
git:
|
|
49
|
+
git: Pick<IGitService, 'resolveRepoRoot' | 'runGit'>,
|
|
29
50
|
extensions: ReadonlyArray<string>
|
|
30
51
|
): number => {
|
|
31
52
|
const repoRoot = git.resolveRepoRoot();
|
|
@@ -37,13 +58,80 @@ const countUntrackedMatchingExtensions = (
|
|
|
37
58
|
.filter((path) => hasAllowedExtension(path, extensions)).length;
|
|
38
59
|
};
|
|
39
60
|
|
|
61
|
+
const isFindingBlocking = (finding: SnapshotFinding): boolean => {
|
|
62
|
+
if (typeof finding.blocking === 'boolean') {
|
|
63
|
+
return finding.blocking;
|
|
64
|
+
}
|
|
65
|
+
return finding.severity === 'CRITICAL' || finding.severity === 'ERROR';
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const toLifecycleAuditFinding = (finding: SnapshotFinding): LifecycleAuditFinding => ({
|
|
69
|
+
ruleId: finding.ruleId,
|
|
70
|
+
severity: finding.severity,
|
|
71
|
+
code: finding.code,
|
|
72
|
+
message: finding.message,
|
|
73
|
+
file: finding.file,
|
|
74
|
+
...(typeof finding.lines !== 'undefined' ? { lines: finding.lines } : {}),
|
|
75
|
+
blocking: isFindingBlocking(finding),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const buildBlockedWithoutFindingsFallback = (params: {
|
|
79
|
+
stage: LifecycleAuditStage;
|
|
80
|
+
gateExitCode: number;
|
|
81
|
+
snapshotOutcome: string | null;
|
|
82
|
+
}): LifecycleAuditFinding => ({
|
|
83
|
+
ruleId: 'audit.gate.blocked-without-findings',
|
|
84
|
+
severity: 'ERROR',
|
|
85
|
+
code: 'AUDIT_BLOCKED_WITHOUT_FINDINGS',
|
|
86
|
+
message:
|
|
87
|
+
`Audit ${params.stage} exited ${params.gateExitCode} with outcome=${params.snapshotOutcome ?? 'unknown'} ` +
|
|
88
|
+
'but produced no machine-readable findings. Re-run with this Pumuki version and inspect policy/SDD output; this fallback keeps JSON actionable.',
|
|
89
|
+
file: '.ai_evidence.json',
|
|
90
|
+
blocking: true,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const extractAuditFindings = (params: {
|
|
94
|
+
evidence: AiEvidenceV2_1 | undefined;
|
|
95
|
+
gateExitCode: number;
|
|
96
|
+
stage: LifecycleAuditStage;
|
|
97
|
+
snapshotOutcome: string | null;
|
|
98
|
+
}): ReadonlyArray<LifecycleAuditFinding> => {
|
|
99
|
+
const findings = Array.isArray(params.evidence?.snapshot.findings)
|
|
100
|
+
? params.evidence.snapshot.findings.map(toLifecycleAuditFinding)
|
|
101
|
+
: [];
|
|
102
|
+
if (findings.length > 0) {
|
|
103
|
+
return findings;
|
|
104
|
+
}
|
|
105
|
+
if (params.gateExitCode !== 0 || params.snapshotOutcome === 'BLOCK') {
|
|
106
|
+
return [
|
|
107
|
+
buildBlockedWithoutFindingsFallback({
|
|
108
|
+
stage: params.stage,
|
|
109
|
+
gateExitCode: params.gateExitCode,
|
|
110
|
+
snapshotOutcome: params.snapshotOutcome,
|
|
111
|
+
}),
|
|
112
|
+
];
|
|
113
|
+
}
|
|
114
|
+
return [];
|
|
115
|
+
};
|
|
116
|
+
|
|
40
117
|
export const runLifecycleAudit = async (params: {
|
|
41
118
|
stage: LifecycleAuditStage;
|
|
42
119
|
auditMode: 'gate' | 'engine';
|
|
120
|
+
dependencies?: Partial<LifecycleAuditDependencies>;
|
|
43
121
|
}): Promise<LifecycleAuditResult> => {
|
|
44
|
-
const
|
|
122
|
+
const activeDependencies: LifecycleAuditDependencies = {
|
|
123
|
+
git: new GitService(),
|
|
124
|
+
readEvidence,
|
|
125
|
+
resolvePolicyForStage,
|
|
126
|
+
runPlatformGate,
|
|
127
|
+
...params.dependencies,
|
|
128
|
+
};
|
|
129
|
+
const git = activeDependencies.git;
|
|
45
130
|
const repoRoot = git.resolveRepoRoot();
|
|
46
|
-
const resolved = resolvePolicyForStage(
|
|
131
|
+
const resolved: ResolvedStagePolicy = activeDependencies.resolvePolicyForStage(
|
|
132
|
+
params.stage,
|
|
133
|
+
repoRoot
|
|
134
|
+
);
|
|
47
135
|
const extensions = DEFAULT_FACT_FILE_EXTENSIONS;
|
|
48
136
|
const untrackedMatchingExtensionsCount = countUntrackedMatchingExtensions(git, extensions);
|
|
49
137
|
|
|
@@ -76,8 +164,8 @@ export const runLifecycleAudit = async (params: {
|
|
|
76
164
|
auditMode: 'gate' as const,
|
|
77
165
|
};
|
|
78
166
|
|
|
79
|
-
const gateExitCode = await runPlatformGate(gateParams);
|
|
80
|
-
const evidence = readEvidence(repoRoot);
|
|
167
|
+
const gateExitCode = await activeDependencies.runPlatformGate(gateParams);
|
|
168
|
+
const evidence = activeDependencies.readEvidence(repoRoot);
|
|
81
169
|
const filesScanned =
|
|
82
170
|
typeof evidence?.snapshot.files_scanned === 'number' &&
|
|
83
171
|
Number.isFinite(evidence.snapshot.files_scanned)
|
|
@@ -85,6 +173,12 @@ export const runLifecycleAudit = async (params: {
|
|
|
85
173
|
: null;
|
|
86
174
|
const snapshotOutcome =
|
|
87
175
|
typeof evidence?.snapshot.outcome === 'string' ? evidence.snapshot.outcome : null;
|
|
176
|
+
const findings = extractAuditFindings({
|
|
177
|
+
evidence,
|
|
178
|
+
gateExitCode,
|
|
179
|
+
stage: params.stage,
|
|
180
|
+
snapshotOutcome,
|
|
181
|
+
});
|
|
88
182
|
|
|
89
183
|
return {
|
|
90
184
|
command: 'pumuki audit',
|
|
@@ -96,6 +190,9 @@ export const runLifecycleAudit = async (params: {
|
|
|
96
190
|
files_scanned: filesScanned,
|
|
97
191
|
untracked_matching_extensions_count: untrackedMatchingExtensionsCount,
|
|
98
192
|
snapshot_outcome: snapshotOutcome,
|
|
193
|
+
findings_count: findings.length,
|
|
194
|
+
blocking_findings_count: findings.filter((finding) => finding.blocking).length,
|
|
195
|
+
findings,
|
|
99
196
|
policy_reconcile_hint: POLICY_RECONCILE_HINT,
|
|
100
197
|
};
|
|
101
198
|
};
|
|
@@ -188,7 +188,7 @@ Pumuki lifecycle commands:
|
|
|
188
188
|
pumuki remove
|
|
189
189
|
pumuki update [--latest|--spec=<package-spec>]
|
|
190
190
|
pumuki doctor [--remote-checks] [--deep] [--parity] [--json]
|
|
191
|
-
pumuki audit [--stage=PRE_COMMIT|PRE_PUSH|CI] [--engine] [--json]
|
|
191
|
+
pumuki audit [--stage=PRE_WRITE|PRE_COMMIT|PRE_PUSH|CI] [--engine] [--json]
|
|
192
192
|
pumuki status [--json] [--remote-checks]
|
|
193
193
|
pumuki watch [--stage=PRE_COMMIT|PRE_PUSH|CI] [--scope=workingTree|staged|repoAndStaged|repo] [--severity=critical|high|medium|low] [--interval-ms=<n>] [--notify-cooldown-ms=<n>] [--no-notify] [--once|--iterations=<n>] [--json]
|
|
194
194
|
pumuki loop run --objective=<text> [--max-attempts=<n>] [--json]
|
|
@@ -266,13 +266,7 @@ const parseSddStage = (value?: string): SddStage => {
|
|
|
266
266
|
};
|
|
267
267
|
|
|
268
268
|
const parseAuditStage = (value?: string): LifecycleAuditStage => {
|
|
269
|
-
|
|
270
|
-
if (stage === 'PRE_WRITE') {
|
|
271
|
-
throw new Error(
|
|
272
|
-
'PRE_WRITE is not supported for "pumuki audit". Use PRE_COMMIT, PRE_PUSH or CI (aliases GREEN, REFACTOR, CLOSE).'
|
|
273
|
-
);
|
|
274
|
-
}
|
|
275
|
-
return stage;
|
|
269
|
+
return parseSddStage(value);
|
|
276
270
|
};
|
|
277
271
|
|
|
278
272
|
const parseSddEvidencePath = (value: string): string => {
|
|
@@ -4,7 +4,7 @@ import type { Server } from 'node:http';
|
|
|
4
4
|
import { execFileSync as runBinarySync } from 'node:child_process';
|
|
5
5
|
import { existsSync } from 'node:fs';
|
|
6
6
|
import { join } from 'node:path';
|
|
7
|
-
import { readLifecycleStatus } from '../lifecycle';
|
|
7
|
+
import { readLifecycleStatus, runLifecycleAudit } from '../lifecycle';
|
|
8
8
|
import { resolveMcpEnterpriseExperimentalFeature } from '../policy/experimentalFeatures';
|
|
9
9
|
import { evaluateSddPolicy, readSddStatus } from '../sdd';
|
|
10
10
|
import type { SddStage } from '../sdd';
|
|
@@ -75,6 +75,7 @@ const ENTERPRISE_RESOURCE_DESCRIPTORS: ReadonlyArray<{
|
|
|
75
75
|
];
|
|
76
76
|
|
|
77
77
|
const ENTERPRISE_TOOLS = [
|
|
78
|
+
'pre_write_guard',
|
|
78
79
|
'ai_gate_check',
|
|
79
80
|
'pre_flight_check',
|
|
80
81
|
'auto_execute_ai_start',
|
|
@@ -91,6 +92,12 @@ const ENTERPRISE_TOOL_DESCRIPTORS: ReadonlyArray<{
|
|
|
91
92
|
mutating: boolean;
|
|
92
93
|
safeByDefault: boolean;
|
|
93
94
|
}> = [
|
|
95
|
+
{
|
|
96
|
+
name: 'pre_write_guard',
|
|
97
|
+
description: 'Runs the PRE_WRITE audit gate and returns actionable findings before an editor/agent writes code.',
|
|
98
|
+
mutating: false,
|
|
99
|
+
safeByDefault: true,
|
|
100
|
+
},
|
|
94
101
|
{
|
|
95
102
|
name: 'ai_gate_check',
|
|
96
103
|
description: 'Reads .ai_evidence.json and reports AI gate compatibility status.',
|
|
@@ -382,13 +389,41 @@ const evaluateCriticalToolGuard = (
|
|
|
382
389
|
}
|
|
383
390
|
};
|
|
384
391
|
|
|
385
|
-
const executeEnterpriseTool = (
|
|
392
|
+
const executeEnterpriseTool = async (
|
|
386
393
|
repoRoot: string,
|
|
387
394
|
toolName: EnterpriseToolName,
|
|
388
395
|
args: Record<string, string | number | boolean | bigint | symbol | null | Date | object>,
|
|
389
396
|
dryRun: boolean
|
|
390
|
-
): EnterpriseToolExecution => {
|
|
397
|
+
): Promise<EnterpriseToolExecution> => {
|
|
391
398
|
switch (toolName) {
|
|
399
|
+
case 'pre_write_guard': {
|
|
400
|
+
const audit = await runLifecycleAudit({
|
|
401
|
+
repoRoot,
|
|
402
|
+
stage: 'PRE_WRITE',
|
|
403
|
+
auditMode: 'gate',
|
|
404
|
+
});
|
|
405
|
+
return {
|
|
406
|
+
name: toolName,
|
|
407
|
+
success: audit.gate_exit_code === 0 && audit.blocking_findings_count === 0,
|
|
408
|
+
dryRun: true,
|
|
409
|
+
executed: true,
|
|
410
|
+
warnings: audit.blocking_findings_count > 0
|
|
411
|
+
? ['PRE_WRITE guard blocked before editor write; inspect result.findings.']
|
|
412
|
+
: [],
|
|
413
|
+
data: {
|
|
414
|
+
...audit,
|
|
415
|
+
next_action: audit.blocking_findings_count > 0
|
|
416
|
+
? {
|
|
417
|
+
kind: 'inspect_findings',
|
|
418
|
+
message: 'Corrige los findings bloqueantes antes de escribir o restaurar ficheros.',
|
|
419
|
+
}
|
|
420
|
+
: {
|
|
421
|
+
kind: 'continue',
|
|
422
|
+
message: 'PRE_WRITE guard passed.',
|
|
423
|
+
},
|
|
424
|
+
},
|
|
425
|
+
};
|
|
426
|
+
}
|
|
392
427
|
case 'ai_gate_check': {
|
|
393
428
|
const stage = toSddStage(args.stage, 'PRE_COMMIT');
|
|
394
429
|
const execution = runEnterpriseAiGateCheck({
|
|
@@ -741,7 +776,7 @@ export const startEnterpriseMcpServer = (
|
|
|
741
776
|
return;
|
|
742
777
|
}
|
|
743
778
|
void readJsonBody(req)
|
|
744
|
-
.then((body) => {
|
|
779
|
+
.then(async (body) => {
|
|
745
780
|
if (typeof body !== 'object' || body === null) {
|
|
746
781
|
sendJson(res, 400, {
|
|
747
782
|
error: 'Invalid request body.',
|
|
@@ -814,7 +849,7 @@ export const startEnterpriseMcpServer = (
|
|
|
814
849
|
}
|
|
815
850
|
let result: EnterpriseToolExecution;
|
|
816
851
|
try {
|
|
817
|
-
result = executeEnterpriseTool(
|
|
852
|
+
result = await executeEnterpriseTool(
|
|
818
853
|
repoRoot,
|
|
819
854
|
toolName,
|
|
820
855
|
args,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.118",
|
|
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": {
|