pumuki 6.3.113 → 6.3.114
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 +51 -5
- package/README.md +4 -2
- package/VERSION +1 -1
- package/core/facts/detectors/typescript/index.test.ts +0 -229
- package/core/facts/detectors/typescript/index.ts +0 -278
- package/core/facts/extractHeuristicFacts.ts +0 -4
- package/core/rules/presets/heuristics/typescript.test.ts +1 -21
- package/core/rules/presets/heuristics/typescript.ts +0 -72
- package/docs/README.md +13 -9
- package/docs/codex-skills/backend-enterprise-rules.md +3 -3
- package/docs/operations/RELEASE_NOTES.md +40 -4
- package/docs/product/API_REFERENCE.md +1 -1
- package/docs/product/HOW_IT_WORKS.md +6 -0
- package/docs/product/INSTALLATION.md +1 -1
- package/docs/product/USAGE.md +42 -5
- package/docs/tracking/plan-curso-pumuki-stack-my-architecture.md +100 -44
- package/docs/validation/README.md +6 -3
- package/integrations/config/skillsDetectorRegistry.ts +0 -24
- package/integrations/config/skillsMarkdownRules.ts +0 -57
- package/integrations/evidence/buildEvidence.ts +0 -24
- package/integrations/evidence/repoState.ts +0 -3
- package/integrations/evidence/schema.ts +0 -18
- package/integrations/evidence/writeEvidence.ts +0 -24
- package/integrations/gate/evaluateAiGate.ts +8 -251
- package/integrations/gate/remediationCatalog.ts +0 -8
- package/integrations/git/GitService.ts +44 -5
- package/integrations/git/aiGateRepoPolicyFindings.ts +86 -17
- package/integrations/git/runPlatformGate.ts +1 -9
- package/integrations/git/runPlatformGateFacts.ts +19 -1
- package/integrations/git/runPlatformGateOutput.ts +41 -42
- package/integrations/lifecycle/adapter.templates.json +1 -0
- package/integrations/lifecycle/adapter.ts +0 -24
- package/integrations/lifecycle/audit.ts +101 -0
- package/integrations/lifecycle/cli.ts +120 -99
- package/integrations/lifecycle/cliSdd.ts +4 -26
- package/integrations/lifecycle/doctor.ts +40 -102
- package/integrations/lifecycle/index.ts +2 -0
- package/integrations/lifecycle/install.ts +0 -21
- package/integrations/lifecycle/packageInfo.ts +1 -118
- package/integrations/lifecycle/state.ts +1 -8
- package/integrations/lifecycle/status.ts +40 -59
- package/integrations/lifecycle/watch.ts +1 -1
- package/integrations/mcp/aiGateCheck.ts +10 -194
- package/integrations/mcp/autoExecuteAiStart.ts +116 -92
- package/integrations/mcp/enterpriseServer.ts +7 -23
- package/integrations/mcp/enterpriseStdioServer.cli.ts +4 -31
- package/integrations/mcp/preFlightCheck.ts +5 -67
- package/integrations/platform/detectPlatforms.ts +37 -0
- package/integrations/sdd/policy.ts +28 -20
- package/package.json +1 -1
- package/scripts/check-tracking-single-active.sh +1 -1
- package/scripts/consumer-menu-matrix-baseline-report-lib.ts +13 -38
- package/scripts/consumer-postinstall-resolve-args.cjs +44 -0
- package/scripts/consumer-postinstall.cjs +76 -21
- package/scripts/framework-menu-advanced-view-lib.ts +0 -49
- package/scripts/framework-menu-consumer-actions-lib.ts +28 -4
- package/scripts/framework-menu-consumer-preflight-hints.ts +5 -2
- package/scripts/framework-menu-consumer-preflight-render.ts +0 -10
- package/scripts/framework-menu-consumer-preflight-run.ts +0 -23
- package/scripts/framework-menu-consumer-preflight-types.ts +0 -12
- package/scripts/framework-menu-consumer-runtime-actions.ts +87 -17
- package/scripts/framework-menu-consumer-runtime-audit.ts +36 -2
- package/scripts/framework-menu-consumer-runtime-evidence-classic.ts +140 -0
- package/scripts/framework-menu-consumer-runtime-lib.ts +2 -38
- package/scripts/framework-menu-consumer-runtime-menu.ts +4 -31
- package/scripts/framework-menu-consumer-runtime-types.ts +3 -5
- package/scripts/framework-menu-evidence-summary-lib.ts +1 -0
- package/scripts/framework-menu-evidence-summary-read.ts +57 -5
- package/scripts/framework-menu-evidence-summary-severity.ts +3 -1
- package/scripts/framework-menu-evidence-summary-types.ts +7 -0
- package/scripts/framework-menu-gate-lib.ts +9 -0
- package/scripts/framework-menu-layout-data.ts +5 -0
- package/scripts/framework-menu-matrix-baseline-lib.ts +15 -14
- package/scripts/framework-menu-matrix-canary-lib.ts +22 -1
- package/scripts/framework-menu-matrix-evidence-lib.ts +1 -0
- package/scripts/framework-menu-matrix-evidence-types.ts +13 -1
- package/scripts/framework-menu-matrix-runner-lib.ts +35 -0
- package/scripts/framework-menu-system-notifications-cause.ts +0 -3
- package/scripts/framework-menu-system-notifications-macos-swift-source.ts +24 -204
- package/scripts/framework-menu-system-notifications-macos.ts +4 -0
- package/scripts/framework-menu-system-notifications-payloads-blocked.ts +1 -1
- package/scripts/framework-menu-system-notifications-text.ts +1 -7
- package/scripts/framework-menu.ts +3 -24
- package/scripts/package-install-smoke-consumer-git-repo-lib.ts +1 -10
- package/scripts/package-install-smoke-consumer-npm-lib.ts +9 -46
- package/scripts/pumuki-full-surface-smoke-lib.ts +37 -0
- package/scripts/pumuki-full-surface-smoke.ts +346 -0
- package/scripts/pumuki-smoke-installed-wrapper.cjs +31 -0
- package/integrations/evidence/trackingContract.ts +0 -17
- package/integrations/gate/governanceActionCatalog.ts +0 -275
- package/integrations/lifecycle/bootstrapManifest.ts +0 -248
- package/integrations/lifecycle/cliGovernanceConsole.ts +0 -69
- package/integrations/lifecycle/governanceNextAction.ts +0 -171
- package/integrations/lifecycle/governanceObservationSnapshot.ts +0 -369
- package/integrations/lifecycle/trackingState.ts +0 -403
- package/integrations/mcp/alignedPlatformGate.ts +0 -232
- package/integrations/mcp/readMcpPrePushStdin.ts +0 -7
- package/scripts/build-ruralgo-s1-evidence-pack.ts +0 -85
- package/scripts/ruralgo-s1-evidence-pack-lib.ts +0 -200
|
@@ -440,63 +440,6 @@ const normalizeKnownRuleTarget = (
|
|
|
440
440
|
|
|
441
441
|
if (platform === 'backend' || platform === 'frontend') {
|
|
442
442
|
const prefix = platform === 'backend' ? 'skills.backend' : 'skills.frontend';
|
|
443
|
-
if (
|
|
444
|
-
platform === 'backend' &&
|
|
445
|
-
(includes('try-catch silenciosos') ||
|
|
446
|
-
includes('try catch silenciosos') ||
|
|
447
|
-
includes('silenciosos siempre loggear o propagar') ||
|
|
448
|
-
includes('siempre loggear o propagar'))
|
|
449
|
-
) {
|
|
450
|
-
return 'skills.backend.guideline.backend.try-catch-silenciosos-siempre-loggear-o-propagar';
|
|
451
|
-
}
|
|
452
|
-
if (
|
|
453
|
-
platform === 'backend' &&
|
|
454
|
-
(includes('hardcoded values') ||
|
|
455
|
-
includes('config en variables de entorno') ||
|
|
456
|
-
includes('hardcoded values - config en variables de entorno'))
|
|
457
|
-
) {
|
|
458
|
-
return 'skills.backend.guideline.backend.hardcoded-values-config-en-variables-de-entorno';
|
|
459
|
-
}
|
|
460
|
-
if (
|
|
461
|
-
platform === 'backend' &&
|
|
462
|
-
(includes('magic numbers') ||
|
|
463
|
-
includes('usar constantes con nombres descriptivos') ||
|
|
464
|
-
includes('magic numbers - usar constantes con nombres descriptivos'))
|
|
465
|
-
) {
|
|
466
|
-
return 'skills.backend.guideline.backend.magic-numbers-usar-constantes-con-nombres-descriptivos';
|
|
467
|
-
}
|
|
468
|
-
if (
|
|
469
|
-
platform === 'backend' &&
|
|
470
|
-
(includes('mocks en producción') ||
|
|
471
|
-
includes('mocks en produccion') ||
|
|
472
|
-
includes('usar fakes/spies de test') ||
|
|
473
|
-
includes('runtime productivo') ||
|
|
474
|
-
includes('solo adaptadores y datos reales'))
|
|
475
|
-
) {
|
|
476
|
-
return 'skills.backend.guideline.backend.mocks-en-produccion-usar-fakes-spies-de-test';
|
|
477
|
-
}
|
|
478
|
-
if (
|
|
479
|
-
platform === 'backend' &&
|
|
480
|
-
(includes('anemic domain models') ||
|
|
481
|
-
includes('entidades con comportamiento') ||
|
|
482
|
-
includes('no solo getters/setters'))
|
|
483
|
-
) {
|
|
484
|
-
return 'skills.backend.guideline.backend.anemic-domain-models-entidades-con-comportamiento';
|
|
485
|
-
}
|
|
486
|
-
if (
|
|
487
|
-
platform === 'backend' &&
|
|
488
|
-
(includes('lógica en controllers') ||
|
|
489
|
-
includes('logica en controllers') ||
|
|
490
|
-
includes('lo gica en controllers') ||
|
|
491
|
-
includes('mover lógica de negocio a casos de uso/servicios') ||
|
|
492
|
-
includes('mover logica de negocio a casos de uso/servicios') ||
|
|
493
|
-
includes('mover lo gica de negocio a casos de uso/servicios') ||
|
|
494
|
-
(includes('controllers') &&
|
|
495
|
-
includes('casos de uso') &&
|
|
496
|
-
includes('servicios')))
|
|
497
|
-
) {
|
|
498
|
-
return 'skills.backend.guideline.backend.logica-en-controllers-mover-logica-de-negocio-a-casos-de-uso-servicios';
|
|
499
|
-
}
|
|
500
443
|
if (includes('solid') || includes('single responsibility') || includes('srp')) {
|
|
501
444
|
return `${prefix}.no-solid-violations`;
|
|
502
445
|
}
|
|
@@ -687,16 +687,6 @@ const normalizeRepoState = (repoState?: RepoState): RepoState | undefined => {
|
|
|
687
687
|
if (!repoState) {
|
|
688
688
|
return undefined;
|
|
689
689
|
}
|
|
690
|
-
const tracking = repoState.lifecycle.tracking ?? {
|
|
691
|
-
enforced: false,
|
|
692
|
-
canonical_path: null,
|
|
693
|
-
canonical_present: false,
|
|
694
|
-
source_file: null,
|
|
695
|
-
in_progress_count: null,
|
|
696
|
-
single_in_progress_valid: null,
|
|
697
|
-
conflict: false,
|
|
698
|
-
declarations: [],
|
|
699
|
-
};
|
|
700
690
|
return {
|
|
701
691
|
repo_root: repoState.repo_root,
|
|
702
692
|
git: {
|
|
@@ -726,20 +716,6 @@ const normalizeRepoState = (repoState?: RepoState): RepoState | undefined => {
|
|
|
726
716
|
config_path: repoState.lifecycle.hard_mode.config_path,
|
|
727
717
|
}
|
|
728
718
|
: undefined,
|
|
729
|
-
tracking: {
|
|
730
|
-
enforced: tracking.enforced,
|
|
731
|
-
canonical_path: tracking.canonical_path,
|
|
732
|
-
canonical_present: tracking.canonical_present,
|
|
733
|
-
source_file: tracking.source_file,
|
|
734
|
-
in_progress_count: tracking.in_progress_count,
|
|
735
|
-
single_in_progress_valid: tracking.single_in_progress_valid,
|
|
736
|
-
conflict: tracking.conflict,
|
|
737
|
-
declarations: tracking.declarations.map((entry) => ({
|
|
738
|
-
source_file: entry.source_file,
|
|
739
|
-
declared_path: entry.declared_path,
|
|
740
|
-
resolved_path: entry.resolved_path,
|
|
741
|
-
})),
|
|
742
|
-
},
|
|
743
719
|
},
|
|
744
720
|
};
|
|
745
721
|
};
|
|
@@ -5,7 +5,6 @@ import { readLifecycleStatus } from '../lifecycle/status';
|
|
|
5
5
|
import { resolvePumukiVersionMetadata } from '../lifecycle/packageInfo';
|
|
6
6
|
import { readPersistedHardModeConfig } from '../policy/policyProfiles';
|
|
7
7
|
import type { RepoHardModeState, RepoHookState, RepoState } from './schema';
|
|
8
|
-
import { readRepoTrackingState } from './trackingContract';
|
|
9
8
|
|
|
10
9
|
type HookStateShape = { exists: boolean; managedBlockPresent: boolean };
|
|
11
10
|
|
|
@@ -124,7 +123,6 @@ export const captureRepoState = (repoRoot: string): RepoState => {
|
|
|
124
123
|
const consumerFacingVersion = versionMetadata.resolvedVersion;
|
|
125
124
|
const installedVersion = versionMetadata.consumerInstalledVersion;
|
|
126
125
|
const hardModeState = readHardModeState(repoRoot);
|
|
127
|
-
const trackingState = readRepoTrackingState(repoRoot);
|
|
128
126
|
|
|
129
127
|
return {
|
|
130
128
|
repo_root: repoRoot,
|
|
@@ -152,7 +150,6 @@ export const captureRepoState = (repoRoot: string): RepoState => {
|
|
|
152
150
|
pre_push: toHookState(lifecycle.hookStatus['pre-push']),
|
|
153
151
|
},
|
|
154
152
|
hard_mode: hardModeState,
|
|
155
|
-
tracking: trackingState,
|
|
156
153
|
},
|
|
157
154
|
};
|
|
158
155
|
};
|
|
@@ -171,23 +171,6 @@ export type RepoHardModeState = {
|
|
|
171
171
|
config_path: string;
|
|
172
172
|
};
|
|
173
173
|
|
|
174
|
-
export type RepoTrackingDeclaration = {
|
|
175
|
-
source_file: string;
|
|
176
|
-
declared_path: string;
|
|
177
|
-
resolved_path: string;
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
export type RepoTrackingState = {
|
|
181
|
-
enforced: boolean;
|
|
182
|
-
canonical_path: string | null;
|
|
183
|
-
canonical_present: boolean;
|
|
184
|
-
source_file: string | null;
|
|
185
|
-
in_progress_count: number | null;
|
|
186
|
-
single_in_progress_valid: boolean | null;
|
|
187
|
-
conflict: boolean;
|
|
188
|
-
declarations: ReadonlyArray<RepoTrackingDeclaration>;
|
|
189
|
-
};
|
|
190
|
-
|
|
191
174
|
export type RepoState = {
|
|
192
175
|
repo_root: string;
|
|
193
176
|
git: {
|
|
@@ -213,7 +196,6 @@ export type RepoState = {
|
|
|
213
196
|
pre_push: RepoHookState;
|
|
214
197
|
};
|
|
215
198
|
hard_mode?: RepoHardModeState;
|
|
216
|
-
tracking: RepoTrackingState;
|
|
217
199
|
};
|
|
218
200
|
};
|
|
219
201
|
|
|
@@ -188,16 +188,6 @@ const normalizeRepoState = (
|
|
|
188
188
|
if (!repoState) {
|
|
189
189
|
return undefined;
|
|
190
190
|
}
|
|
191
|
-
const tracking = repoState.lifecycle.tracking ?? {
|
|
192
|
-
enforced: false,
|
|
193
|
-
canonical_path: null,
|
|
194
|
-
canonical_present: false,
|
|
195
|
-
source_file: null,
|
|
196
|
-
in_progress_count: null,
|
|
197
|
-
single_in_progress_valid: null,
|
|
198
|
-
conflict: false,
|
|
199
|
-
declarations: [],
|
|
200
|
-
};
|
|
201
191
|
return {
|
|
202
192
|
repo_root: toRelativeRepoPath(repoRoot, repoState.repo_root),
|
|
203
193
|
git: {
|
|
@@ -227,20 +217,6 @@ const normalizeRepoState = (
|
|
|
227
217
|
config_path: toRelativeRepoPath(repoRoot, repoState.lifecycle.hard_mode.config_path),
|
|
228
218
|
}
|
|
229
219
|
: undefined,
|
|
230
|
-
tracking: {
|
|
231
|
-
enforced: tracking.enforced,
|
|
232
|
-
canonical_path: tracking.canonical_path,
|
|
233
|
-
canonical_present: tracking.canonical_present,
|
|
234
|
-
source_file: tracking.source_file,
|
|
235
|
-
in_progress_count: tracking.in_progress_count,
|
|
236
|
-
single_in_progress_valid: tracking.single_in_progress_valid,
|
|
237
|
-
conflict: tracking.conflict,
|
|
238
|
-
declarations: tracking.declarations.map((entry) => ({
|
|
239
|
-
source_file: entry.source_file,
|
|
240
|
-
declared_path: entry.declared_path,
|
|
241
|
-
resolved_path: entry.resolved_path,
|
|
242
|
-
})),
|
|
243
|
-
},
|
|
244
220
|
},
|
|
245
221
|
};
|
|
246
222
|
};
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import type { EvidenceReadResult } from '../evidence/readEvidence';
|
|
2
2
|
import { readEvidenceResult } from '../evidence/readEvidence';
|
|
3
3
|
import { captureRepoState } from '../evidence/repoState';
|
|
4
|
-
import type { RepoState
|
|
4
|
+
import type { RepoState } from '../evidence/schema';
|
|
5
5
|
import { resolvePolicyForStage } from './stagePolicies';
|
|
6
6
|
import { execFileSync } from 'node:child_process';
|
|
7
7
|
import { existsSync, realpathSync } from 'node:fs';
|
|
8
8
|
import { resolve } from 'node:path';
|
|
9
|
-
import { isSeverityAtLeast } from '../../core/rules/Severity';
|
|
10
9
|
import type { SkillsLockV1, SkillsStage } from '../config/skillsLock';
|
|
11
10
|
import {
|
|
12
11
|
loadEffectiveSkillsLock,
|
|
@@ -133,10 +132,6 @@ const PREWRITE_WORKTREE_HYGIENE_WARN_THRESHOLD_ENV = 'PUMUKI_PREWRITE_WORKTREE_W
|
|
|
133
132
|
const PREWRITE_WORKTREE_HYGIENE_BLOCK_THRESHOLD_ENV = 'PUMUKI_PREWRITE_WORKTREE_BLOCK_THRESHOLD';
|
|
134
133
|
|
|
135
134
|
const DEFAULT_PROTECTED_BRANCHES = new Set(['main', 'master', 'develop', 'dev']);
|
|
136
|
-
const DEFAULT_GITFLOW_BRANCH_PATTERNS = [
|
|
137
|
-
/^(?:feature|bugfix|hotfix|chore|refactor|docs)\/[a-z0-9]+(?:-[a-z0-9]+)*$/,
|
|
138
|
-
/^release\/\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/,
|
|
139
|
-
] as const;
|
|
140
135
|
const PREWRITE_SKILLS_PLATFORMS = ['ios', 'android', 'backend', 'frontend'] as const;
|
|
141
136
|
type PreWriteSkillsPlatform = (typeof PREWRITE_SKILLS_PLATFORMS)[number];
|
|
142
137
|
const PLATFORM_SKILLS_RULE_PREFIXES: Readonly<Record<PreWriteSkillsPlatform, string>> = {
|
|
@@ -403,45 +398,6 @@ const collectWorktreeChangedPaths = (repoRoot: string): ReadonlyArray<string> =>
|
|
|
403
398
|
}
|
|
404
399
|
};
|
|
405
400
|
|
|
406
|
-
const collectGitChangedPaths = (
|
|
407
|
-
repoRoot: string,
|
|
408
|
-
args: ReadonlyArray<string>
|
|
409
|
-
): ReadonlyArray<string> => {
|
|
410
|
-
try {
|
|
411
|
-
const output = execFileSync(
|
|
412
|
-
'git',
|
|
413
|
-
[...args],
|
|
414
|
-
{
|
|
415
|
-
cwd: repoRoot,
|
|
416
|
-
encoding: 'utf8',
|
|
417
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
418
|
-
}
|
|
419
|
-
);
|
|
420
|
-
const files = output
|
|
421
|
-
.split('\n')
|
|
422
|
-
.map((line) => parseChangedPath(line))
|
|
423
|
-
.filter((line): line is string => typeof line === 'string' && line.length > 0);
|
|
424
|
-
return [...new Set(files)];
|
|
425
|
-
} catch {
|
|
426
|
-
return [];
|
|
427
|
-
}
|
|
428
|
-
};
|
|
429
|
-
|
|
430
|
-
const collectStagedChangedPaths = (repoRoot: string): ReadonlyArray<string> =>
|
|
431
|
-
collectGitChangedPaths(repoRoot, ['diff', '--cached', '--name-status', '--find-renames']);
|
|
432
|
-
|
|
433
|
-
const collectRangeChangedPaths = (params: {
|
|
434
|
-
repoRoot: string;
|
|
435
|
-
fromRef: string;
|
|
436
|
-
toRef: string;
|
|
437
|
-
}): ReadonlyArray<string> =>
|
|
438
|
-
collectGitChangedPaths(params.repoRoot, [
|
|
439
|
-
'diff',
|
|
440
|
-
'--name-status',
|
|
441
|
-
'--find-renames',
|
|
442
|
-
`${params.fromRef}...${params.toRef}`,
|
|
443
|
-
]);
|
|
444
|
-
|
|
445
401
|
const isPlatformPath = (platform: PreWriteSkillsPlatform, filePath: string): boolean => {
|
|
446
402
|
const normalized = normalizeChangedPath(filePath).toLowerCase();
|
|
447
403
|
if (platform === 'ios') {
|
|
@@ -488,47 +444,6 @@ const isPlatformPath = (platform: PreWriteSkillsPlatform, filePath: string): boo
|
|
|
488
444
|
|| /(^|\/)(frontend|web|client)(\/|$)/.test(normalized);
|
|
489
445
|
};
|
|
490
446
|
|
|
491
|
-
const toDetectedPlatformsFromPaths = (params: {
|
|
492
|
-
changedPaths: ReadonlyArray<string>;
|
|
493
|
-
requiredPlatforms: ReadonlyArray<PreWriteSkillsPlatform>;
|
|
494
|
-
}): ReadonlyArray<PreWriteSkillsPlatform> => {
|
|
495
|
-
if (params.changedPaths.length === 0) {
|
|
496
|
-
return [];
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
const detected = new Set<PreWriteSkillsPlatform>();
|
|
500
|
-
for (const filePath of params.changedPaths) {
|
|
501
|
-
for (const platform of params.requiredPlatforms) {
|
|
502
|
-
if (isPlatformPath(platform, filePath)) {
|
|
503
|
-
detected.add(platform);
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
return PREWRITE_SKILLS_PLATFORMS.filter((platform) => detected.has(platform));
|
|
509
|
-
};
|
|
510
|
-
|
|
511
|
-
const collectStageScopedChangedPaths = (params: {
|
|
512
|
-
stage: AiGateStage;
|
|
513
|
-
repoRoot: string;
|
|
514
|
-
upstream: string | null;
|
|
515
|
-
}): ReadonlyArray<string> => {
|
|
516
|
-
if (params.stage === 'PRE_WRITE') {
|
|
517
|
-
return collectWorktreeChangedPaths(params.repoRoot);
|
|
518
|
-
}
|
|
519
|
-
if (params.stage === 'PRE_COMMIT') {
|
|
520
|
-
return collectStagedChangedPaths(params.repoRoot);
|
|
521
|
-
}
|
|
522
|
-
if (params.stage === 'PRE_PUSH' && typeof params.upstream === 'string' && params.upstream.length > 0) {
|
|
523
|
-
return collectRangeChangedPaths({
|
|
524
|
-
repoRoot: params.repoRoot,
|
|
525
|
-
fromRef: params.upstream,
|
|
526
|
-
toRef: 'HEAD',
|
|
527
|
-
});
|
|
528
|
-
}
|
|
529
|
-
return [];
|
|
530
|
-
};
|
|
531
|
-
|
|
532
447
|
const hasWorktreeCodePlatforms = (params: {
|
|
533
448
|
repoRoot: string;
|
|
534
449
|
requiredPlatforms: ReadonlyArray<PreWriteSkillsPlatform>;
|
|
@@ -542,22 +457,6 @@ const hasWorktreeCodePlatforms = (params: {
|
|
|
542
457
|
);
|
|
543
458
|
};
|
|
544
459
|
|
|
545
|
-
const toStageScopedDetectedPlatforms = (params: {
|
|
546
|
-
stage: AiGateStage;
|
|
547
|
-
repoRoot: string;
|
|
548
|
-
upstream: string | null;
|
|
549
|
-
requiredPlatforms: ReadonlyArray<PreWriteSkillsPlatform>;
|
|
550
|
-
}): ReadonlyArray<PreWriteSkillsPlatform> => {
|
|
551
|
-
return toDetectedPlatformsFromPaths({
|
|
552
|
-
changedPaths: collectStageScopedChangedPaths({
|
|
553
|
-
stage: params.stage,
|
|
554
|
-
repoRoot: params.repoRoot,
|
|
555
|
-
upstream: params.upstream,
|
|
556
|
-
}),
|
|
557
|
-
requiredPlatforms: params.requiredPlatforms,
|
|
558
|
-
});
|
|
559
|
-
};
|
|
560
|
-
|
|
561
460
|
const toLockRequiredPlatforms = (
|
|
562
461
|
requiredLock: SkillsLockV1 | undefined
|
|
563
462
|
): ReadonlyArray<PreWriteSkillsPlatform> => {
|
|
@@ -849,18 +748,8 @@ const toSkillsContractAssessment = (params: {
|
|
|
849
748
|
const coverage = params.evidenceResult.evidence.snapshot.rules_coverage;
|
|
850
749
|
const explicitlyDetectedPlatforms = toDetectedSkillsPlatforms(params.evidenceResult.evidence.platforms);
|
|
851
750
|
const inferredPlatforms = toCoverageInferredPlatforms(coverage);
|
|
852
|
-
const stageScopedDetectedPlatforms =
|
|
853
|
-
params.stage !== 'CI' && requiredPlatforms.length > 0
|
|
854
|
-
? toStageScopedDetectedPlatforms({
|
|
855
|
-
stage: params.stage,
|
|
856
|
-
repoRoot: params.repoRoot,
|
|
857
|
-
upstream: params.repoState.git.upstream,
|
|
858
|
-
requiredPlatforms,
|
|
859
|
-
})
|
|
860
|
-
: [];
|
|
861
|
-
const worktreeDetectedPlatforms = stageScopedDetectedPlatforms;
|
|
862
751
|
const repoTreeDetectedPlatforms =
|
|
863
|
-
params.stage
|
|
752
|
+
params.stage !== 'PRE_WRITE' && requiredPlatforms.length > 0
|
|
864
753
|
? toRepoTreeDetectedPlatforms({
|
|
865
754
|
repoRoot: params.repoRoot,
|
|
866
755
|
platforms: requiredPlatforms,
|
|
@@ -870,24 +759,20 @@ const toSkillsContractAssessment = (params: {
|
|
|
870
759
|
explicitlyDetectedPlatforms.length > 0
|
|
871
760
|
? toEffectiveSkillsPlatforms({
|
|
872
761
|
platforms: params.evidenceResult.evidence.platforms,
|
|
873
|
-
|
|
874
|
-
|
|
762
|
+
coverage,
|
|
763
|
+
})
|
|
875
764
|
: [];
|
|
876
765
|
const detectedPlatforms =
|
|
877
|
-
|
|
878
|
-
? stageScopedDetectedPlatforms
|
|
879
|
-
: explicitlyDetectedEffectivePlatforms.length > 0
|
|
766
|
+
explicitlyDetectedEffectivePlatforms.length > 0
|
|
880
767
|
? explicitlyDetectedEffectivePlatforms
|
|
881
768
|
: inferredPlatforms.length > 0
|
|
882
769
|
? inferredPlatforms
|
|
883
|
-
: worktreeDetectedPlatforms.length > 0
|
|
884
|
-
? worktreeDetectedPlatforms
|
|
885
770
|
: repoTreeDetectedPlatforms;
|
|
886
771
|
const pendingChanges = resolvePendingChanges(params.repoState);
|
|
887
772
|
const detectedPlatformSet = new Set(detectedPlatforms);
|
|
888
773
|
const assessmentPlatforms =
|
|
889
774
|
requiredPlatforms.length > 0
|
|
890
|
-
? params.stage
|
|
775
|
+
? params.stage === 'PRE_WRITE' && detectedPlatforms.length > 0
|
|
891
776
|
? requiredPlatforms.filter((platform) => detectedPlatformSet.has(platform))
|
|
892
777
|
: requiredPlatforms
|
|
893
778
|
: detectedPlatforms;
|
|
@@ -1279,63 +1164,6 @@ const collectEvidenceViolations = (
|
|
|
1279
1164
|
return { violations, ageSeconds };
|
|
1280
1165
|
};
|
|
1281
1166
|
|
|
1282
|
-
const severityOrder: ReadonlyArray<'CRITICAL' | 'ERROR' | 'WARN' | 'INFO'> = [
|
|
1283
|
-
'CRITICAL',
|
|
1284
|
-
'ERROR',
|
|
1285
|
-
'WARN',
|
|
1286
|
-
'INFO',
|
|
1287
|
-
];
|
|
1288
|
-
|
|
1289
|
-
const toHighestTriggeredSeverity = (
|
|
1290
|
-
severityCounts: Readonly<Record<'INFO' | 'WARN' | 'ERROR' | 'CRITICAL', number>>,
|
|
1291
|
-
threshold: 'INFO' | 'WARN' | 'ERROR' | 'CRITICAL'
|
|
1292
|
-
): 'INFO' | 'WARN' | 'ERROR' | 'CRITICAL' | null => {
|
|
1293
|
-
for (const severity of severityOrder) {
|
|
1294
|
-
if (severityCounts[severity] > 0 && isSeverityAtLeast(severity, threshold)) {
|
|
1295
|
-
return severity;
|
|
1296
|
-
}
|
|
1297
|
-
}
|
|
1298
|
-
return null;
|
|
1299
|
-
};
|
|
1300
|
-
|
|
1301
|
-
const collectEvidencePolicyThresholdViolations = (params: {
|
|
1302
|
-
evidenceResult: EvidenceReadResult;
|
|
1303
|
-
policy: ReturnType<typeof resolvePolicyForStage>['policy'];
|
|
1304
|
-
}): AiGateViolation[] => {
|
|
1305
|
-
if (params.evidenceResult.kind !== 'valid') {
|
|
1306
|
-
return [];
|
|
1307
|
-
}
|
|
1308
|
-
|
|
1309
|
-
const severityCounts = params.evidenceResult.evidence.severity_metrics.by_severity;
|
|
1310
|
-
const blockSeverity = toHighestTriggeredSeverity(
|
|
1311
|
-
severityCounts,
|
|
1312
|
-
params.policy.blockOnOrAbove
|
|
1313
|
-
);
|
|
1314
|
-
if (blockSeverity && params.evidenceResult.evidence.ai_gate.status !== 'BLOCKED') {
|
|
1315
|
-
return [
|
|
1316
|
-
toErrorViolation(
|
|
1317
|
-
'EVIDENCE_POLICY_THRESHOLD_BLOCK',
|
|
1318
|
-
`Evidence severities exceed block_on_or_above=${params.policy.blockOnOrAbove} (highest=${blockSeverity}).`
|
|
1319
|
-
),
|
|
1320
|
-
];
|
|
1321
|
-
}
|
|
1322
|
-
|
|
1323
|
-
const warnSeverity = toHighestTriggeredSeverity(
|
|
1324
|
-
severityCounts,
|
|
1325
|
-
params.policy.warnOnOrAbove
|
|
1326
|
-
);
|
|
1327
|
-
if (warnSeverity && params.evidenceResult.evidence.ai_gate.status === 'ALLOWED') {
|
|
1328
|
-
return [
|
|
1329
|
-
toWarnViolation(
|
|
1330
|
-
'EVIDENCE_POLICY_THRESHOLD_WARN',
|
|
1331
|
-
`Evidence severities exceed warn_on_or_above=${params.policy.warnOnOrAbove} (highest=${warnSeverity}).`
|
|
1332
|
-
),
|
|
1333
|
-
];
|
|
1334
|
-
}
|
|
1335
|
-
|
|
1336
|
-
return [];
|
|
1337
|
-
};
|
|
1338
|
-
|
|
1339
1167
|
const toEvidenceSourceDescriptor = (
|
|
1340
1168
|
result: EvidenceReadResult,
|
|
1341
1169
|
repoRoot: string
|
|
@@ -1365,81 +1193,17 @@ const collectGitflowViolations = (
|
|
|
1365
1193
|
if (!repoState.git.available) {
|
|
1366
1194
|
return violations;
|
|
1367
1195
|
}
|
|
1368
|
-
|
|
1369
|
-
const normalizedBranch = branch?.toLowerCase() ?? null;
|
|
1370
|
-
if (branch && normalizedBranch && protectedBranches.has(normalizedBranch)) {
|
|
1196
|
+
if (repoState.git.branch && protectedBranches.has(repoState.git.branch)) {
|
|
1371
1197
|
violations.push(
|
|
1372
1198
|
toErrorViolation(
|
|
1373
1199
|
'GITFLOW_PROTECTED_BRANCH',
|
|
1374
|
-
`Direct work on protected branch "${branch}" is not allowed.`
|
|
1375
|
-
)
|
|
1376
|
-
);
|
|
1377
|
-
return violations;
|
|
1378
|
-
}
|
|
1379
|
-
if (
|
|
1380
|
-
branch
|
|
1381
|
-
&& !DEFAULT_GITFLOW_BRANCH_PATTERNS.some((pattern) => pattern.test(branch))
|
|
1382
|
-
) {
|
|
1383
|
-
violations.push(
|
|
1384
|
-
toErrorViolation(
|
|
1385
|
-
'GITFLOW_BRANCH_NAMING_INVALID',
|
|
1386
|
-
`Branch "${branch}" does not comply with GitFlow naming. Use feature/*, bugfix/*, hotfix/*, release/*, chore/*, refactor/* or docs/*.`
|
|
1200
|
+
`Direct work on protected branch "${repoState.git.branch}" is not allowed.`
|
|
1387
1201
|
)
|
|
1388
1202
|
);
|
|
1389
1203
|
}
|
|
1390
1204
|
return violations;
|
|
1391
1205
|
};
|
|
1392
1206
|
|
|
1393
|
-
const DEFAULT_TRACKING_STATE: RepoTrackingState = {
|
|
1394
|
-
enforced: false,
|
|
1395
|
-
canonical_path: null,
|
|
1396
|
-
canonical_present: false,
|
|
1397
|
-
source_file: null,
|
|
1398
|
-
in_progress_count: null,
|
|
1399
|
-
single_in_progress_valid: null,
|
|
1400
|
-
conflict: false,
|
|
1401
|
-
declarations: [],
|
|
1402
|
-
};
|
|
1403
|
-
|
|
1404
|
-
const collectTrackingViolations = (repoState: RepoState): AiGateViolation[] => {
|
|
1405
|
-
const tracking = repoState.lifecycle.tracking ?? DEFAULT_TRACKING_STATE;
|
|
1406
|
-
if (!tracking.enforced) {
|
|
1407
|
-
return [];
|
|
1408
|
-
}
|
|
1409
|
-
|
|
1410
|
-
if (tracking.conflict) {
|
|
1411
|
-
const declaredPaths = tracking.declarations
|
|
1412
|
-
.map((entry) => `${entry.source_file}:${entry.resolved_path}`)
|
|
1413
|
-
.join(', ');
|
|
1414
|
-
return [
|
|
1415
|
-
toErrorViolation(
|
|
1416
|
-
'TRACKING_CANONICAL_SOURCE_CONFLICT',
|
|
1417
|
-
`Tracking canonical source conflict detected (${declaredPaths}).`
|
|
1418
|
-
),
|
|
1419
|
-
];
|
|
1420
|
-
}
|
|
1421
|
-
|
|
1422
|
-
if (!tracking.canonical_path || !tracking.canonical_present) {
|
|
1423
|
-
return [
|
|
1424
|
-
toErrorViolation(
|
|
1425
|
-
'TRACKING_CANONICAL_FILE_MISSING',
|
|
1426
|
-
`Tracking canonical file is missing (${tracking.canonical_path ?? 'undeclared'}).`
|
|
1427
|
-
),
|
|
1428
|
-
];
|
|
1429
|
-
}
|
|
1430
|
-
|
|
1431
|
-
if (tracking.single_in_progress_valid === false) {
|
|
1432
|
-
return [
|
|
1433
|
-
toErrorViolation(
|
|
1434
|
-
'TRACKING_CANONICAL_IN_PROGRESS_INVALID',
|
|
1435
|
-
`Tracking canonical file must contain exactly one in-progress task (count=${tracking.in_progress_count ?? 'n/a'}).`
|
|
1436
|
-
),
|
|
1437
|
-
];
|
|
1438
|
-
}
|
|
1439
|
-
|
|
1440
|
-
return [];
|
|
1441
|
-
};
|
|
1442
|
-
|
|
1443
1207
|
const resolvePendingChanges = (repoState: RepoState): number | null => {
|
|
1444
1208
|
if (!repoState.git.available) {
|
|
1445
1209
|
return null;
|
|
@@ -1658,7 +1422,6 @@ export const evaluateAiGate = (
|
|
|
1658
1422
|
readMcpAiGateReceipt: activeDependencies.readMcpAiGateReceipt,
|
|
1659
1423
|
});
|
|
1660
1424
|
const gitflowViolations = collectGitflowViolations(repoState, protectedBranches);
|
|
1661
|
-
const trackingViolations = collectTrackingViolations(repoState);
|
|
1662
1425
|
const skillsContract = toSkillsContractAssessment({
|
|
1663
1426
|
stage: params.stage,
|
|
1664
1427
|
repoRoot: params.repoRoot,
|
|
@@ -1682,16 +1445,10 @@ export const evaluateAiGate = (
|
|
|
1682
1445
|
`Skills contract incomplete for ${params.stage}: ${skillsContract.violations.map((violation) => violation.code).join(', ')}.`
|
|
1683
1446
|
),
|
|
1684
1447
|
];
|
|
1685
|
-
const policyThresholdViolations = collectEvidencePolicyThresholdViolations({
|
|
1686
|
-
evidenceResult,
|
|
1687
|
-
policy: resolvedPolicy.policy,
|
|
1688
|
-
});
|
|
1689
1448
|
const violations = [
|
|
1690
1449
|
...evidenceAssessment.violations,
|
|
1691
|
-
...policyThresholdViolations,
|
|
1692
1450
|
...stageSkillsContractViolations,
|
|
1693
1451
|
...gitflowViolations,
|
|
1694
|
-
...trackingViolations,
|
|
1695
1452
|
...mcpReceiptAssessment.violations,
|
|
1696
1453
|
];
|
|
1697
1454
|
const blocked = violations.some((violation) => violation.severity === 'ERROR');
|
|
@@ -17,14 +17,6 @@ export const REMEDIATION_HINT_BY_CODE: Readonly<Record<string, string>> = {
|
|
|
17
17
|
EVIDENCE_ACTIVE_RULE_IDS_EMPTY_FOR_CODE_CHANGES:
|
|
18
18
|
'Reconcilia policy/skills y revalida PRE_WRITE: npx --yes --package pumuki@latest pumuki policy reconcile --strict --json && npx --yes --package pumuki@latest pumuki sdd validate --stage=PRE_WRITE --json',
|
|
19
19
|
GITFLOW_PROTECTED_BRANCH: 'Trabaja en feature/* y evita ramas protegidas.',
|
|
20
|
-
GITFLOW_BRANCH_NAMING_INVALID:
|
|
21
|
-
'Renombra o recrea la rama con un prefijo GitFlow válido (feature/*, bugfix/*, hotfix/*, release/*, chore/*, refactor/* o docs/*).',
|
|
22
|
-
TRACKING_CANONICAL_SOURCE_CONFLICT:
|
|
23
|
-
'Alinea AGENTS.md y los README canónicos para que todos apunten al mismo MD de seguimiento.',
|
|
24
|
-
TRACKING_CANONICAL_FILE_MISSING:
|
|
25
|
-
'Crea o restaura el archivo canónico de tracking declarado por el repo.',
|
|
26
|
-
TRACKING_CANONICAL_IN_PROGRESS_INVALID:
|
|
27
|
-
'Deja exactamente una tarea o fase `🚧` en el MD canónico de seguimiento antes de continuar.',
|
|
28
20
|
EVIDENCE_PREWRITE_WORKTREE_OVER_LIMIT:
|
|
29
21
|
'Reduce archivos staged/unstaged por debajo del umbral (o ajusta PUMUKI_PREWRITE_WORKTREE_*); divide el trabajo en commits más pequeños.',
|
|
30
22
|
EVIDENCE_PREWRITE_WORKTREE_WARN:
|
|
@@ -9,12 +9,19 @@ export { parseNameStatus } from './gitDiffUtils';
|
|
|
9
9
|
export interface IGitService {
|
|
10
10
|
runGit(args: ReadonlyArray<string>, cwd?: string): string;
|
|
11
11
|
getStagedFacts(extensions: ReadonlyArray<string>): ReadonlyArray<Fact>;
|
|
12
|
+
getUnstagedFacts(
|
|
13
|
+
extensions: ReadonlyArray<string>,
|
|
14
|
+
includeUntracked?: boolean
|
|
15
|
+
): ReadonlyArray<Fact>;
|
|
12
16
|
getRepoFacts(extensions: ReadonlyArray<string>): ReadonlyArray<Fact>;
|
|
13
17
|
getRepoAndStagedFacts(extensions: ReadonlyArray<string>): ReadonlyArray<Fact>;
|
|
14
18
|
getStagedAndUnstagedFacts(extensions: ReadonlyArray<string>): ReadonlyArray<Fact>;
|
|
15
19
|
resolveRepoRoot(): string;
|
|
16
20
|
}
|
|
17
21
|
|
|
22
|
+
const shouldIncludeUntrackedFacts = (env = process.env) =>
|
|
23
|
+
env.PUMUKI_INCLUDE_UNTRACKED_WORKTREE === '1';
|
|
24
|
+
|
|
18
25
|
const assertSafeGitArgs = (args: ReadonlyArray<string>): void => {
|
|
19
26
|
for (const arg of args) {
|
|
20
27
|
if (arg.includes('\u0000') || arg.includes('\n') || arg.includes('\r')) {
|
|
@@ -44,6 +51,35 @@ export class GitService implements IGitService {
|
|
|
44
51
|
);
|
|
45
52
|
}
|
|
46
53
|
|
|
54
|
+
getUnstagedFacts(
|
|
55
|
+
extensions: ReadonlyArray<string>,
|
|
56
|
+
includeUntracked = shouldIncludeUntrackedFacts()
|
|
57
|
+
): ReadonlyArray<Fact> {
|
|
58
|
+
const nameStatus = this.runGit(['diff', '--name-status']);
|
|
59
|
+
const changes = parseNameStatus(nameStatus).filter((change) =>
|
|
60
|
+
hasAllowedExtension(change.path, extensions)
|
|
61
|
+
);
|
|
62
|
+
const untrackedPaths = includeUntracked
|
|
63
|
+
? this.runGit(['ls-files', '--others', '--exclude-standard'])
|
|
64
|
+
.split('\n')
|
|
65
|
+
.map((line) => line.trim())
|
|
66
|
+
.filter((line) => line.length > 0)
|
|
67
|
+
.filter((path) => hasAllowedExtension(path, extensions))
|
|
68
|
+
: [];
|
|
69
|
+
const unstagedPaths = new Set(changes.map((change) => change.path));
|
|
70
|
+
const untrackedChanges = untrackedPaths
|
|
71
|
+
.filter((path) => !unstagedPaths.has(path))
|
|
72
|
+
.map((path) => ({
|
|
73
|
+
path,
|
|
74
|
+
changeType: 'added' as const,
|
|
75
|
+
}));
|
|
76
|
+
const mergedChanges = [...changes, ...untrackedChanges];
|
|
77
|
+
const repoRoot = this.resolveRepoRoot();
|
|
78
|
+
return buildFactsFromChanges(mergedChanges, 'git:unstaged', (filePath) =>
|
|
79
|
+
this.readWorkingTreeFile(repoRoot, filePath)
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
47
83
|
getRepoFacts(extensions: ReadonlyArray<string>): ReadonlyArray<Fact> {
|
|
48
84
|
const trackedFiles = this.runGit(['ls-files'])
|
|
49
85
|
.split('\n')
|
|
@@ -83,6 +119,7 @@ export class GitService implements IGitService {
|
|
|
83
119
|
|
|
84
120
|
getStagedAndUnstagedFacts(extensions: ReadonlyArray<string>): ReadonlyArray<Fact> {
|
|
85
121
|
const hasHeadCommit = this.hasHeadCommit();
|
|
122
|
+
const includeUntracked = shouldIncludeUntrackedFacts();
|
|
86
123
|
const trackedNameStatus = hasHeadCommit
|
|
87
124
|
? this.runGit(['diff', '--name-status', 'HEAD'])
|
|
88
125
|
: [
|
|
@@ -93,11 +130,13 @@ export class GitService implements IGitService {
|
|
|
93
130
|
.join('\n');
|
|
94
131
|
const trackedChanges = this.deduplicateChangesByPath(parseNameStatus(trackedNameStatus))
|
|
95
132
|
.filter((change) => hasAllowedExtension(change.path, extensions));
|
|
96
|
-
const untrackedPaths =
|
|
97
|
-
.
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
133
|
+
const untrackedPaths = includeUntracked
|
|
134
|
+
? this.runGit(['ls-files', '--others', '--exclude-standard'])
|
|
135
|
+
.split('\n')
|
|
136
|
+
.map((line) => line.trim())
|
|
137
|
+
.filter((line) => line.length > 0)
|
|
138
|
+
.filter((path) => hasAllowedExtension(path, extensions))
|
|
139
|
+
: [];
|
|
101
140
|
const existingTracked = new Set(trackedChanges.map((change) => change.path));
|
|
102
141
|
const repoRoot = this.resolveRepoRoot();
|
|
103
142
|
|