pumuki 6.3.204 → 6.3.205
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/integrations/lifecycle/audit.ts +170 -8
- package/package.json +1 -1
|
@@ -23,7 +23,14 @@ export type LifecycleAuditResult = {
|
|
|
23
23
|
command: 'pumuki audit';
|
|
24
24
|
repo_root: string;
|
|
25
25
|
stage: LifecycleAuditStage;
|
|
26
|
-
scope: {
|
|
26
|
+
scope: {
|
|
27
|
+
kind: 'repo' | 'staged' | 'range';
|
|
28
|
+
staged_matching_extensions_count: number;
|
|
29
|
+
range_matching_extensions_count?: number;
|
|
30
|
+
base_ref?: string;
|
|
31
|
+
from_ref?: string;
|
|
32
|
+
to_ref?: string;
|
|
33
|
+
};
|
|
27
34
|
audit_mode: 'gate' | 'engine';
|
|
28
35
|
gate_exit_code: number;
|
|
29
36
|
files_scanned: number | null;
|
|
@@ -54,7 +61,16 @@ type LifecycleAuditDependencies = {
|
|
|
54
61
|
runPlatformGate: typeof runPlatformGate;
|
|
55
62
|
};
|
|
56
63
|
|
|
57
|
-
type LifecycleAuditScope =
|
|
64
|
+
type LifecycleAuditScope =
|
|
65
|
+
| { kind: 'repo' }
|
|
66
|
+
| { kind: 'staged' }
|
|
67
|
+
| {
|
|
68
|
+
kind: 'range';
|
|
69
|
+
baseRef: string;
|
|
70
|
+
fromRef: string;
|
|
71
|
+
toRef: string;
|
|
72
|
+
matchingExtensions: ReadonlyArray<string>;
|
|
73
|
+
};
|
|
58
74
|
|
|
59
75
|
const POLICY_RECONCILE_HINT =
|
|
60
76
|
'If .pumuki/policy-as-code.json signatures drift after a pumuki upgrade, run: pumuki policy reconcile --apply';
|
|
@@ -84,16 +100,161 @@ const collectStagedMatchingExtensions = (
|
|
|
84
100
|
.filter((path) => hasAllowedExtension(path, extensions));
|
|
85
101
|
};
|
|
86
102
|
|
|
103
|
+
const runGitOrNull = (
|
|
104
|
+
git: Pick<IGitService, 'runGit'>,
|
|
105
|
+
args: ReadonlyArray<string>,
|
|
106
|
+
cwd: string
|
|
107
|
+
): string | null => {
|
|
108
|
+
try {
|
|
109
|
+
const output = git.runGit([...args], cwd).trim();
|
|
110
|
+
return output.length > 0 ? output : null;
|
|
111
|
+
} catch {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const isResolvableRef = (
|
|
117
|
+
git: Pick<IGitService, 'runGit'>,
|
|
118
|
+
repoRoot: string,
|
|
119
|
+
ref: string
|
|
120
|
+
): boolean => runGitOrNull(git, ['rev-parse', '--verify', ref], repoRoot) !== null;
|
|
121
|
+
|
|
122
|
+
const branchPrefersDevelopBase = (branch: string): boolean =>
|
|
123
|
+
/^(feature|bugfix|chore|refactor|docs)\//.test(branch);
|
|
124
|
+
|
|
125
|
+
const branchPrefersMainBase = (branch: string): boolean => /^hotfix\//.test(branch);
|
|
126
|
+
|
|
127
|
+
const collectRangeMatchingExtensions = (params: {
|
|
128
|
+
git: Pick<IGitService, 'runGit'>;
|
|
129
|
+
repoRoot: string;
|
|
130
|
+
fromRef: string;
|
|
131
|
+
toRef: string;
|
|
132
|
+
extensions: ReadonlyArray<string>;
|
|
133
|
+
}): string[] => {
|
|
134
|
+
const raw = runGitOrNull(
|
|
135
|
+
params.git,
|
|
136
|
+
['diff', '--name-only', `${params.fromRef}..${params.toRef}`],
|
|
137
|
+
params.repoRoot
|
|
138
|
+
);
|
|
139
|
+
return (raw ?? '')
|
|
140
|
+
.split('\n')
|
|
141
|
+
.map((line) => line.trim())
|
|
142
|
+
.filter((line) => line.length > 0)
|
|
143
|
+
.filter((path) => hasAllowedExtension(path, params.extensions));
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const resolvePrePushRangeScope = (params: {
|
|
147
|
+
git: Pick<IGitService, 'runGit'>;
|
|
148
|
+
repoRoot: string;
|
|
149
|
+
extensions: ReadonlyArray<string>;
|
|
150
|
+
}): LifecycleAuditScope | null => {
|
|
151
|
+
const branch =
|
|
152
|
+
runGitOrNull(params.git, ['rev-parse', '--abbrev-ref', 'HEAD'], params.repoRoot) ?? '';
|
|
153
|
+
if (branch === 'HEAD' || branch.length === 0) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const explicitBase =
|
|
158
|
+
process.env.PUMUKI_AUDIT_PRE_PUSH_BASE_REF?.trim() ??
|
|
159
|
+
process.env.PUMUKI_PRE_PUSH_BASE_REF?.trim() ??
|
|
160
|
+
'';
|
|
161
|
+
const upstreamTracking = runGitOrNull(
|
|
162
|
+
params.git,
|
|
163
|
+
['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{upstream}'],
|
|
164
|
+
params.repoRoot
|
|
165
|
+
);
|
|
166
|
+
const preferredBaseRefs = [
|
|
167
|
+
explicitBase,
|
|
168
|
+
...(branchPrefersDevelopBase(branch) ? ['origin/develop'] : []),
|
|
169
|
+
...(branchPrefersMainBase(branch) ? ['origin/main', 'origin/master'] : []),
|
|
170
|
+
upstreamTracking ?? '',
|
|
171
|
+
'origin/develop',
|
|
172
|
+
'origin/main',
|
|
173
|
+
'origin/master',
|
|
174
|
+
].filter((ref, index, refs) => ref.length > 0 && refs.indexOf(ref) === index);
|
|
175
|
+
|
|
176
|
+
const baseRef = preferredBaseRefs.find((ref) =>
|
|
177
|
+
isResolvableRef(params.git, params.repoRoot, ref)
|
|
178
|
+
);
|
|
179
|
+
if (typeof baseRef === 'undefined') {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const mergeBase = runGitOrNull(params.git, ['merge-base', baseRef, 'HEAD'], params.repoRoot);
|
|
184
|
+
if (mergeBase === null) {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
kind: 'range',
|
|
190
|
+
baseRef,
|
|
191
|
+
fromRef: mergeBase,
|
|
192
|
+
toRef: 'HEAD',
|
|
193
|
+
matchingExtensions: collectRangeMatchingExtensions({
|
|
194
|
+
git: params.git,
|
|
195
|
+
repoRoot: params.repoRoot,
|
|
196
|
+
fromRef: mergeBase,
|
|
197
|
+
toRef: 'HEAD',
|
|
198
|
+
extensions: params.extensions,
|
|
199
|
+
}),
|
|
200
|
+
};
|
|
201
|
+
};
|
|
202
|
+
|
|
87
203
|
const resolveLifecycleAuditScope = (params: {
|
|
88
204
|
stage: LifecycleAuditStage;
|
|
205
|
+
git: Pick<IGitService, 'runGit'>;
|
|
206
|
+
repoRoot: string;
|
|
207
|
+
extensions: ReadonlyArray<string>;
|
|
89
208
|
stagedMatchingExtensions: ReadonlyArray<string>;
|
|
90
209
|
}): LifecycleAuditScope => {
|
|
91
210
|
if (params.stage === 'PRE_WRITE' && params.stagedMatchingExtensions.length > 0) {
|
|
92
211
|
return { kind: 'staged' };
|
|
93
212
|
}
|
|
213
|
+
if (params.stage === 'PRE_PUSH') {
|
|
214
|
+
const rangeScope = resolvePrePushRangeScope({
|
|
215
|
+
git: params.git,
|
|
216
|
+
repoRoot: params.repoRoot,
|
|
217
|
+
extensions: params.extensions,
|
|
218
|
+
});
|
|
219
|
+
if (rangeScope !== null) {
|
|
220
|
+
return rangeScope;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
94
223
|
return { kind: 'repo' };
|
|
95
224
|
};
|
|
96
225
|
|
|
226
|
+
const toGateScope = (scope: LifecycleAuditScope) => {
|
|
227
|
+
if (scope.kind === 'range') {
|
|
228
|
+
return {
|
|
229
|
+
kind: 'range' as const,
|
|
230
|
+
fromRef: scope.fromRef,
|
|
231
|
+
toRef: scope.toRef,
|
|
232
|
+
extensions: [...DEFAULT_FACT_FILE_EXTENSIONS],
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
return { kind: scope.kind };
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const toResultScope = (params: {
|
|
239
|
+
scope: LifecycleAuditScope;
|
|
240
|
+
stagedMatchingExtensions: ReadonlyArray<string>;
|
|
241
|
+
}): LifecycleAuditResult['scope'] => {
|
|
242
|
+
const base = {
|
|
243
|
+
kind: params.scope.kind,
|
|
244
|
+
staged_matching_extensions_count: params.stagedMatchingExtensions.length,
|
|
245
|
+
};
|
|
246
|
+
if (params.scope.kind !== 'range') {
|
|
247
|
+
return base;
|
|
248
|
+
}
|
|
249
|
+
return {
|
|
250
|
+
...base,
|
|
251
|
+
range_matching_extensions_count: params.scope.matchingExtensions.length,
|
|
252
|
+
base_ref: params.scope.baseRef,
|
|
253
|
+
from_ref: params.scope.fromRef,
|
|
254
|
+
to_ref: params.scope.toRef,
|
|
255
|
+
};
|
|
256
|
+
};
|
|
257
|
+
|
|
97
258
|
const isFindingBlocking = (finding: SnapshotFinding): boolean => {
|
|
98
259
|
if (typeof finding.blocking === 'boolean') {
|
|
99
260
|
return finding.blocking;
|
|
@@ -246,15 +407,19 @@ export const runLifecycleAudit = async (params: {
|
|
|
246
407
|
const stagedMatchingExtensions = collectStagedMatchingExtensions(git, extensions);
|
|
247
408
|
const scope = resolveLifecycleAuditScope({
|
|
248
409
|
stage: params.stage,
|
|
410
|
+
git,
|
|
411
|
+
repoRoot,
|
|
412
|
+
extensions,
|
|
249
413
|
stagedMatchingExtensions,
|
|
250
414
|
});
|
|
415
|
+
const gateScope = toGateScope(scope);
|
|
251
416
|
|
|
252
417
|
const gateParams =
|
|
253
418
|
params.auditMode === 'engine'
|
|
254
419
|
? {
|
|
255
420
|
policy: resolved.policy,
|
|
256
421
|
policyTrace: resolved.trace,
|
|
257
|
-
scope,
|
|
422
|
+
scope: gateScope,
|
|
258
423
|
silent: true,
|
|
259
424
|
auditMode: 'engine' as const,
|
|
260
425
|
dependencies: {
|
|
@@ -273,7 +438,7 @@ export const runLifecycleAudit = async (params: {
|
|
|
273
438
|
: {
|
|
274
439
|
policy: resolved.policy,
|
|
275
440
|
policyTrace: resolved.trace,
|
|
276
|
-
scope,
|
|
441
|
+
scope: gateScope,
|
|
277
442
|
silent: true,
|
|
278
443
|
auditMode: 'gate' as const,
|
|
279
444
|
};
|
|
@@ -312,10 +477,7 @@ export const runLifecycleAudit = async (params: {
|
|
|
312
477
|
command: 'pumuki audit',
|
|
313
478
|
repo_root: repoRoot,
|
|
314
479
|
stage: params.stage,
|
|
315
|
-
scope: {
|
|
316
|
-
kind: scope.kind,
|
|
317
|
-
staged_matching_extensions_count: stagedMatchingExtensions.length,
|
|
318
|
-
},
|
|
480
|
+
scope: toResultScope({ scope, stagedMatchingExtensions }),
|
|
319
481
|
audit_mode: params.auditMode,
|
|
320
482
|
gate_exit_code: gateExitCode,
|
|
321
483
|
files_scanned: filesScanned,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.205",
|
|
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": {
|