pumuki 6.3.204 → 6.3.206
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 +204 -9
- 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;
|
|
@@ -211,6 +372,17 @@ const isScopedPreWriteGlobalEnforcementOnly = (params: {
|
|
|
211
372
|
(finding) => finding.code === 'SKILLS_GLOBAL_ENFORCEMENT_INCOMPLETE_CRITICAL'
|
|
212
373
|
);
|
|
213
374
|
|
|
375
|
+
const isRangePrePushWithoutSupportedCodeSddOnly = (params: {
|
|
376
|
+
stage: LifecycleAuditStage;
|
|
377
|
+
scope: LifecycleAuditScope;
|
|
378
|
+
findings: ReadonlyArray<LifecycleAuditFinding>;
|
|
379
|
+
}): boolean =>
|
|
380
|
+
params.stage === 'PRE_PUSH' &&
|
|
381
|
+
params.scope.kind === 'range' &&
|
|
382
|
+
params.scope.matchingExtensions.length === 0 &&
|
|
383
|
+
params.findings.length > 0 &&
|
|
384
|
+
params.findings.every((finding) => finding.code === 'SDD_CHANGE_MISSING');
|
|
385
|
+
|
|
214
386
|
const toScopedAuditAdvisoryFinding = (
|
|
215
387
|
finding: LifecycleAuditFinding
|
|
216
388
|
): LifecycleAuditFinding => ({
|
|
@@ -223,6 +395,18 @@ const toScopedAuditAdvisoryFinding = (
|
|
|
223
395
|
blocking: false,
|
|
224
396
|
});
|
|
225
397
|
|
|
398
|
+
const toRangeNoSupportedCodeAuditAdvisoryFinding = (
|
|
399
|
+
finding: LifecycleAuditFinding
|
|
400
|
+
): LifecycleAuditFinding => ({
|
|
401
|
+
...finding,
|
|
402
|
+
severity: 'INFO',
|
|
403
|
+
code: 'AUDIT_RANGE_NO_SUPPORTED_CODE_ADVISORY',
|
|
404
|
+
message:
|
|
405
|
+
'Range PRE_PUSH audit found no supported code files in the branch delta; SDD baseline debt is retained as advisory for this atomic split slice. ' +
|
|
406
|
+
finding.message,
|
|
407
|
+
blocking: false,
|
|
408
|
+
});
|
|
409
|
+
|
|
226
410
|
export const runLifecycleAudit = async (params: {
|
|
227
411
|
stage: LifecycleAuditStage;
|
|
228
412
|
auditMode: 'gate' | 'engine';
|
|
@@ -246,15 +430,19 @@ export const runLifecycleAudit = async (params: {
|
|
|
246
430
|
const stagedMatchingExtensions = collectStagedMatchingExtensions(git, extensions);
|
|
247
431
|
const scope = resolveLifecycleAuditScope({
|
|
248
432
|
stage: params.stage,
|
|
433
|
+
git,
|
|
434
|
+
repoRoot,
|
|
435
|
+
extensions,
|
|
249
436
|
stagedMatchingExtensions,
|
|
250
437
|
});
|
|
438
|
+
const gateScope = toGateScope(scope);
|
|
251
439
|
|
|
252
440
|
const gateParams =
|
|
253
441
|
params.auditMode === 'engine'
|
|
254
442
|
? {
|
|
255
443
|
policy: resolved.policy,
|
|
256
444
|
policyTrace: resolved.trace,
|
|
257
|
-
scope,
|
|
445
|
+
scope: gateScope,
|
|
258
446
|
silent: true,
|
|
259
447
|
auditMode: 'engine' as const,
|
|
260
448
|
dependencies: {
|
|
@@ -273,7 +461,7 @@ export const runLifecycleAudit = async (params: {
|
|
|
273
461
|
: {
|
|
274
462
|
policy: resolved.policy,
|
|
275
463
|
policyTrace: resolved.trace,
|
|
276
|
-
scope,
|
|
464
|
+
scope: gateScope,
|
|
277
465
|
silent: true,
|
|
278
466
|
auditMode: 'gate' as const,
|
|
279
467
|
};
|
|
@@ -298,13 +486,23 @@ export const runLifecycleAudit = async (params: {
|
|
|
298
486
|
scope,
|
|
299
487
|
findings,
|
|
300
488
|
});
|
|
489
|
+
const rangePrePushWithoutSupportedCodeSddOnly = isRangePrePushWithoutSupportedCodeSddOnly({
|
|
490
|
+
stage: params.stage,
|
|
491
|
+
scope,
|
|
492
|
+
findings,
|
|
493
|
+
});
|
|
301
494
|
const gateAllowed = originalGateExitCode === 0;
|
|
302
495
|
const effectiveFindings = scopedGlobalEnforcementOnly
|
|
303
496
|
? findings.map(toScopedAuditAdvisoryFinding)
|
|
497
|
+
: rangePrePushWithoutSupportedCodeSddOnly
|
|
498
|
+
? findings.map(toRangeNoSupportedCodeAuditAdvisoryFinding)
|
|
304
499
|
: gateAllowed
|
|
305
500
|
? findings.map(toGateAllowedAuditAdvisoryFinding)
|
|
306
501
|
: findings;
|
|
307
|
-
const gateExitCode =
|
|
502
|
+
const gateExitCode =
|
|
503
|
+
scopedGlobalEnforcementOnly || rangePrePushWithoutSupportedCodeSddOnly
|
|
504
|
+
? 0
|
|
505
|
+
: originalGateExitCode;
|
|
308
506
|
const effectiveSnapshotOutcome =
|
|
309
507
|
gateExitCode === 0 && snapshotOutcome === 'BLOCK' ? 'PASS' : snapshotOutcome;
|
|
310
508
|
|
|
@@ -312,10 +510,7 @@ export const runLifecycleAudit = async (params: {
|
|
|
312
510
|
command: 'pumuki audit',
|
|
313
511
|
repo_root: repoRoot,
|
|
314
512
|
stage: params.stage,
|
|
315
|
-
scope: {
|
|
316
|
-
kind: scope.kind,
|
|
317
|
-
staged_matching_extensions_count: stagedMatchingExtensions.length,
|
|
318
|
-
},
|
|
513
|
+
scope: toResultScope({ scope, stagedMatchingExtensions }),
|
|
319
514
|
audit_mode: params.auditMode,
|
|
320
515
|
gate_exit_code: gateExitCode,
|
|
321
516
|
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.206",
|
|
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": {
|