pumuki 6.3.289 → 6.3.291

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/VERSION CHANGED
@@ -1 +1 @@
1
- 6.3.289
1
+ 6.3.291
@@ -108,12 +108,123 @@ const hasActionableFindingLocation = (finding: Finding): boolean => {
108
108
  );
109
109
  };
110
110
 
111
+ const normalizeFindingLines = (lines: Finding['lines']): ReadonlyArray<number> => {
112
+ if (typeof lines === 'number') {
113
+ return Number.isFinite(lines) && lines > 0 ? [Math.trunc(lines)] : [];
114
+ }
115
+ if (Array.isArray(lines)) {
116
+ return [...new Set(lines.filter((line) => Number.isFinite(line) && line > 0).map((line) => Math.trunc(line)))].sort(
117
+ (left, right) => left - right
118
+ );
119
+ }
120
+ if (typeof lines === 'string') {
121
+ return [...new Set(
122
+ lines
123
+ .split(/[^0-9]+/)
124
+ .map((line) => Number.parseInt(line, 10))
125
+ .filter((line) => Number.isFinite(line) && line > 0)
126
+ )].sort((left, right) => left - right);
127
+ }
128
+ return [];
129
+ };
130
+
131
+ const parseChangedLinesByPathFromUnifiedDiff = (diff: string): Map<string, Set<number>> => {
132
+ const changedLinesByPath = new Map<string, Set<number>>();
133
+ let currentPath: string | undefined;
134
+
135
+ for (const rawLine of diff.split('\n')) {
136
+ if (rawLine.startsWith('+++ b/')) {
137
+ const nextPath = rawLine.slice('+++ b/'.length).trim();
138
+ currentPath = nextPath.length > 0 && nextPath !== '/dev/null' ? toNormalizedPath(nextPath) : undefined;
139
+ if (currentPath && !changedLinesByPath.has(currentPath)) {
140
+ changedLinesByPath.set(currentPath, new Set<number>());
141
+ }
142
+ continue;
143
+ }
144
+
145
+ if (!currentPath || !rawLine.startsWith('@@')) {
146
+ continue;
147
+ }
148
+
149
+ const hunkMatch = rawLine.match(/\+(\d+)(?:,(\d+))?/);
150
+ if (!hunkMatch) {
151
+ continue;
152
+ }
153
+ const startLine = Number.parseInt(hunkMatch[1] ?? '', 10);
154
+ const lineCount = hunkMatch[2] === undefined ? 1 : Number.parseInt(hunkMatch[2], 10);
155
+ if (!Number.isFinite(startLine) || startLine <= 0 || !Number.isFinite(lineCount)) {
156
+ continue;
157
+ }
158
+ const changedLines = changedLinesByPath.get(currentPath) ?? new Set<number>();
159
+ for (let offset = 0; offset < Math.max(0, lineCount); offset += 1) {
160
+ changedLines.add(startLine + offset);
161
+ }
162
+ changedLinesByPath.set(currentPath, changedLines);
163
+ }
164
+
165
+ return changedLinesByPath;
166
+ };
167
+
168
+ const collectStagedChangedLinesByPath = (
169
+ git: IGitService,
170
+ repoRoot: string
171
+ ): Map<string, Set<number>> => {
172
+ try {
173
+ return parseChangedLinesByPathFromUnifiedDiff(
174
+ git.runGit(['diff', '--cached', '--unified=0', '--no-ext-diff'], repoRoot)
175
+ );
176
+ } catch {
177
+ return new Map<string, Set<number>>();
178
+ }
179
+ };
180
+
181
+ const isFindingOutsideChangedLines = (
182
+ finding: Finding,
183
+ changedLinesByPath: ReadonlyMap<string, ReadonlySet<number>>
184
+ ): boolean => {
185
+ if (!finding.filePath) {
186
+ return false;
187
+ }
188
+ const changedLines = changedLinesByPath.get(toNormalizedPath(finding.filePath));
189
+ if (!changedLines || changedLines.size === 0) {
190
+ return false;
191
+ }
192
+ const findingLines = normalizeFindingLines(finding.lines);
193
+ if (findingLines.length === 0) {
194
+ return false;
195
+ }
196
+ return findingLines.every((line) => !changedLines.has(line));
197
+ };
198
+
111
199
  const normalizeScopedRuleEngineFindings = (params: {
112
200
  findings: ReadonlyArray<Finding>;
113
201
  scope: GateScope;
202
+ stage: GatePolicy['stage'];
203
+ changedLinesByPath: ReadonlyMap<string, ReadonlySet<number>>;
114
204
  }): ReadonlyArray<Finding> => {
115
- void params.scope;
116
- return params.findings;
205
+ if (
206
+ (params.scope.kind !== 'staged' && params.scope.kind !== 'repoAndStaged') ||
207
+ (params.stage !== 'PRE_WRITE' && params.stage !== 'PRE_COMMIT')
208
+ ) {
209
+ return params.findings;
210
+ }
211
+
212
+ return params.findings.map((finding) => {
213
+ if (!isAstRuleFinding(finding) || !isFindingOutsideChangedLines(finding, params.changedLinesByPath)) {
214
+ return finding;
215
+ }
216
+ return {
217
+ ...finding,
218
+ blocking: false,
219
+ severity: 'INFO',
220
+ message:
221
+ `${finding.message} ` +
222
+ 'Baseline brownfield outside the staged diff; tracked as advisory for this atomic slice.',
223
+ expected_fix:
224
+ finding.expected_fix ??
225
+ 'Plan a dedicated brownfield remediation slice for this pre-existing finding.',
226
+ };
227
+ });
117
228
  };
118
229
 
119
230
  const buildDefaultMemoryShadowRecommendation = (params: {
@@ -988,6 +1099,7 @@ export async function runPlatformGate(params: {
988
1099
  gateScopePathPrefixes
989
1100
  );
990
1101
  const stagedPaths = collectStagedPaths(git, repoRoot);
1102
+ const stagedChangedLinesByPath = collectStagedChangedLinesByPath(git, repoRoot);
991
1103
  const factsForPlatformEvaluation = shouldAugmentStagedSkillsContractFactsWithRepoFacts({
992
1104
  scope: params.scope,
993
1105
  facts,
@@ -1039,6 +1151,8 @@ export async function runPlatformGate(params: {
1039
1151
  const scopedRuleEngineFindings = normalizeScopedRuleEngineFindings({
1040
1152
  findings: ruleEngineFindings,
1041
1153
  scope: params.scope,
1154
+ stage: params.policy.stage,
1155
+ changedLinesByPath: stagedChangedLinesByPath,
1042
1156
  });
1043
1157
  const findings = [...aiGateRepoPolicyFindings, ...scopedRuleEngineFindings];
1044
1158
  const evaluationMetrics: SnapshotEvaluationMetrics = coverage
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki",
3
- "version": "6.3.289",
3
+ "version": "6.3.291",
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": {