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.
@@ -23,7 +23,14 @@ export type LifecycleAuditResult = {
23
23
  command: 'pumuki audit';
24
24
  repo_root: string;
25
25
  stage: LifecycleAuditStage;
26
- scope: { kind: 'repo' | 'staged'; staged_matching_extensions_count: number };
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 = { kind: 'repo' } | { kind: 'staged' };
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.204",
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": {