pumuki 6.3.97 → 6.3.98
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/AGENTS.md +269 -0
- package/CHANGELOG.md +688 -0
- package/README.md +4 -2
- package/VERSION +1 -1
- package/docs/README.md +13 -9
- package/docs/operations/RELEASE_NOTES.md +9 -78
- package/docs/product/HOW_IT_WORKS.md +6 -0
- package/docs/product/INSTALLATION.md +1 -1
- package/docs/product/USAGE.md +41 -4
- package/docs/tracking/plan-curso-pumuki-stack-my-architecture.md +118 -0
- package/docs/validation/README.md +6 -3
- package/integrations/config/skillsCustomRules.ts +18 -99
- 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 +15 -232
- package/integrations/gate/remediationCatalog.ts +0 -8
- package/integrations/git/GitService.ts +44 -5
- package/integrations/git/aiGateRepoPolicyFindings.ts +0 -4
- package/integrations/git/runPlatformGate.ts +1 -9
- package/integrations/git/runPlatformGateFacts.ts +19 -1
- package/integrations/git/runPlatformGateOutput.ts +27 -36
- package/integrations/lifecycle/adapter.templates.json +7 -13
- package/integrations/lifecycle/adapter.ts +0 -24
- package/integrations/lifecycle/artifacts.ts +1 -6
- package/integrations/lifecycle/audit.ts +101 -0
- package/integrations/lifecycle/cli.ts +110 -70
- package/integrations/lifecycle/cliSdd.ts +13 -8
- package/integrations/lifecycle/doctor.ts +16 -48
- package/integrations/lifecycle/hookManager.ts +0 -77
- package/integrations/lifecycle/index.ts +2 -0
- package/integrations/lifecycle/install.ts +0 -21
- package/integrations/lifecycle/npmService.ts +3 -155
- package/integrations/lifecycle/preWriteAutomation.ts +7 -77
- package/integrations/lifecycle/state.ts +1 -8
- package/integrations/lifecycle/status.ts +2 -29
- package/integrations/mcp/aiGateCheck.ts +26 -206
- package/integrations/mcp/autoExecuteAiStart.ts +85 -92
- package/integrations/mcp/enterpriseServer.ts +7 -23
- package/integrations/mcp/enterpriseStdioServer.cli.ts +4 -31
- package/integrations/mcp/preFlightCheck.ts +5 -51
- package/integrations/platform/detectPlatforms.ts +37 -0
- package/integrations/policy/experimentalFeatures.ts +1 -1
- package/integrations/sdd/evidenceScaffold.ts +2 -109
- package/package.json +10 -2
- 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 -15
- 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 -10
- package/scripts/framework-menu-consumer-runtime-menu.ts +4 -18
- package/scripts/framework-menu-consumer-runtime-types.ts +3 -3
- 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 -24
- 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-remediation.ts +13 -24
- package/scripts/framework-menu-system-notifications-text.ts +1 -7
- package/scripts/framework-menu.ts +3 -2
- 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 -150
- 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 -164
- package/integrations/lifecycle/governanceObservationSnapshot.ts +0 -613
- 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
|
@@ -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,21 +1,16 @@
|
|
|
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,
|
|
13
12
|
loadRequiredSkillsLock,
|
|
14
13
|
} from '../config/skillsEffectiveLock';
|
|
15
|
-
import {
|
|
16
|
-
resolveSkillImportSourcesWithDiagnostics,
|
|
17
|
-
type SkillImportSourceDiagnostic,
|
|
18
|
-
} from '../config/skillsCustomRules';
|
|
19
14
|
import {
|
|
20
15
|
resolveSkillsEnforcement,
|
|
21
16
|
type SkillsEnforcementResolution,
|
|
@@ -60,7 +55,6 @@ export type AiGateSkillsContractAssessment = {
|
|
|
60
55
|
status: 'PASS' | 'FAIL' | 'NOT_APPLICABLE';
|
|
61
56
|
detected_platforms: ReadonlyArray<PreWriteSkillsPlatform>;
|
|
62
57
|
requirements: ReadonlyArray<AiGateSkillsContractPlatformRequirement>;
|
|
63
|
-
source_diagnostics: ReadonlyArray<SkillImportSourceDiagnostic>;
|
|
64
58
|
violations: ReadonlyArray<AiGateViolation>;
|
|
65
59
|
};
|
|
66
60
|
|
|
@@ -138,10 +132,6 @@ const PREWRITE_WORKTREE_HYGIENE_WARN_THRESHOLD_ENV = 'PUMUKI_PREWRITE_WORKTREE_W
|
|
|
138
132
|
const PREWRITE_WORKTREE_HYGIENE_BLOCK_THRESHOLD_ENV = 'PUMUKI_PREWRITE_WORKTREE_BLOCK_THRESHOLD';
|
|
139
133
|
|
|
140
134
|
const DEFAULT_PROTECTED_BRANCHES = new Set(['main', 'master', 'develop', 'dev']);
|
|
141
|
-
const DEFAULT_GITFLOW_BRANCH_PATTERNS = [
|
|
142
|
-
/^(?:feature|bugfix|hotfix|chore|refactor|docs)\/[a-z0-9]+(?:-[a-z0-9]+)*$/,
|
|
143
|
-
/^release\/\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/,
|
|
144
|
-
] as const;
|
|
145
135
|
const PREWRITE_SKILLS_PLATFORMS = ['ios', 'android', 'backend', 'frontend'] as const;
|
|
146
136
|
type PreWriteSkillsPlatform = (typeof PREWRITE_SKILLS_PLATFORMS)[number];
|
|
147
137
|
const PLATFORM_SKILLS_RULE_PREFIXES: Readonly<Record<PreWriteSkillsPlatform, string>> = {
|
|
@@ -467,27 +457,6 @@ const hasWorktreeCodePlatforms = (params: {
|
|
|
467
457
|
);
|
|
468
458
|
};
|
|
469
459
|
|
|
470
|
-
const toWorktreeDetectedPlatforms = (params: {
|
|
471
|
-
repoRoot: string;
|
|
472
|
-
requiredPlatforms: ReadonlyArray<PreWriteSkillsPlatform>;
|
|
473
|
-
}): ReadonlyArray<PreWriteSkillsPlatform> => {
|
|
474
|
-
const changedPaths = collectWorktreeChangedPaths(params.repoRoot);
|
|
475
|
-
if (changedPaths.length === 0) {
|
|
476
|
-
return [];
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
const detected = new Set<PreWriteSkillsPlatform>();
|
|
480
|
-
for (const filePath of changedPaths) {
|
|
481
|
-
for (const platform of params.requiredPlatforms) {
|
|
482
|
-
if (isPlatformPath(platform, filePath)) {
|
|
483
|
-
detected.add(platform);
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
return PREWRITE_SKILLS_PLATFORMS.filter((platform) => detected.has(platform));
|
|
489
|
-
};
|
|
490
|
-
|
|
491
460
|
const toLockRequiredPlatforms = (
|
|
492
461
|
requiredLock: SkillsLockV1 | undefined
|
|
493
462
|
): ReadonlyArray<PreWriteSkillsPlatform> => {
|
|
@@ -730,20 +699,6 @@ const collectPreWriteCrossPlatformCriticalViolations = (params: {
|
|
|
730
699
|
];
|
|
731
700
|
};
|
|
732
701
|
|
|
733
|
-
const toRequiredSkillSourceViolations = (
|
|
734
|
-
diagnostics: ReadonlyArray<SkillImportSourceDiagnostic>,
|
|
735
|
-
skillsEnforcement: SkillsEnforcementResolution
|
|
736
|
-
): AiGateViolation[] =>
|
|
737
|
-
diagnostics.map((diagnostic) =>
|
|
738
|
-
toSkillsViolation(
|
|
739
|
-
skillsEnforcement,
|
|
740
|
-
diagnostic.issue === 'missing'
|
|
741
|
-
? 'SKILLS_REQUIRED_SOURCE_MISSING'
|
|
742
|
-
: 'SKILLS_REQUIRED_SOURCE_UNREADABLE',
|
|
743
|
-
`La skill requerida "${diagnostic.skillName}" no está disponible en ${diagnostic.sourcePath}. ${diagnostic.resolution}`
|
|
744
|
-
)
|
|
745
|
-
);
|
|
746
|
-
|
|
747
702
|
const toSkillsContractAssessment = (params: {
|
|
748
703
|
stage: AiGateStage;
|
|
749
704
|
repoRoot: string;
|
|
@@ -753,22 +708,12 @@ const toSkillsContractAssessment = (params: {
|
|
|
753
708
|
skillsEnforcement: SkillsEnforcementResolution;
|
|
754
709
|
}): AiGateSkillsContractAssessment => {
|
|
755
710
|
const requiredPlatforms = toLockRequiredPlatforms(params.requiredLock);
|
|
756
|
-
const sourceDiagnostics = resolveSkillImportSourcesWithDiagnostics({
|
|
757
|
-
repoRoot: params.repoRoot,
|
|
758
|
-
}).diagnostics;
|
|
759
711
|
|
|
760
712
|
if (params.evidenceResult.kind !== 'valid') {
|
|
761
|
-
const sourceViolations = toRequiredSkillSourceViolations(
|
|
762
|
-
sourceDiagnostics,
|
|
763
|
-
params.skillsEnforcement
|
|
764
|
-
);
|
|
765
713
|
return {
|
|
766
714
|
stage: params.stage,
|
|
767
|
-
enforced: requiredPlatforms.length > 0
|
|
768
|
-
status:
|
|
769
|
-
requiredPlatforms.length > 0 || sourceDiagnostics.length > 0
|
|
770
|
-
? 'FAIL'
|
|
771
|
-
: 'NOT_APPLICABLE',
|
|
715
|
+
enforced: requiredPlatforms.length > 0,
|
|
716
|
+
status: requiredPlatforms.length > 0 ? 'FAIL' : 'NOT_APPLICABLE',
|
|
772
717
|
detected_platforms: [],
|
|
773
718
|
requirements: requiredPlatforms.map((platform) => ({
|
|
774
719
|
platform,
|
|
@@ -787,10 +732,8 @@ const toSkillsContractAssessment = (params: {
|
|
|
787
732
|
...PREWRITE_TRANSVERSAL_CRITICAL_SKILLS_RULES[platform],
|
|
788
733
|
],
|
|
789
734
|
})),
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
...sourceViolations,
|
|
793
|
-
...(requiredPlatforms.length > 0
|
|
735
|
+
violations:
|
|
736
|
+
requiredPlatforms.length > 0
|
|
794
737
|
? [
|
|
795
738
|
toSkillsViolation(
|
|
796
739
|
params.skillsEnforcement,
|
|
@@ -798,21 +741,13 @@ const toSkillsContractAssessment = (params: {
|
|
|
798
741
|
`Required repo skills exist, but active platforms could not be detected for ${params.stage}.`
|
|
799
742
|
),
|
|
800
743
|
]
|
|
801
|
-
: []
|
|
802
|
-
],
|
|
744
|
+
: [],
|
|
803
745
|
};
|
|
804
746
|
}
|
|
805
747
|
|
|
806
748
|
const coverage = params.evidenceResult.evidence.snapshot.rules_coverage;
|
|
807
749
|
const explicitlyDetectedPlatforms = toDetectedSkillsPlatforms(params.evidenceResult.evidence.platforms);
|
|
808
750
|
const inferredPlatforms = toCoverageInferredPlatforms(coverage);
|
|
809
|
-
const worktreeDetectedPlatforms =
|
|
810
|
-
params.stage === 'PRE_WRITE' && requiredPlatforms.length > 0
|
|
811
|
-
? toWorktreeDetectedPlatforms({
|
|
812
|
-
repoRoot: params.repoRoot,
|
|
813
|
-
requiredPlatforms,
|
|
814
|
-
})
|
|
815
|
-
: [];
|
|
816
751
|
const repoTreeDetectedPlatforms =
|
|
817
752
|
params.stage !== 'PRE_WRITE' && requiredPlatforms.length > 0
|
|
818
753
|
? toRepoTreeDetectedPlatforms({
|
|
@@ -832,8 +767,6 @@ const toSkillsContractAssessment = (params: {
|
|
|
832
767
|
? explicitlyDetectedEffectivePlatforms
|
|
833
768
|
: inferredPlatforms.length > 0
|
|
834
769
|
? inferredPlatforms
|
|
835
|
-
: worktreeDetectedPlatforms.length > 0
|
|
836
|
-
? worktreeDetectedPlatforms
|
|
837
770
|
: repoTreeDetectedPlatforms;
|
|
838
771
|
const pendingChanges = resolvePendingChanges(params.repoState);
|
|
839
772
|
const detectedPlatformSet = new Set(detectedPlatforms);
|
|
@@ -857,15 +790,11 @@ const toSkillsContractAssessment = (params: {
|
|
|
857
790
|
) {
|
|
858
791
|
return {
|
|
859
792
|
stage: params.stage,
|
|
860
|
-
enforced:
|
|
861
|
-
status:
|
|
793
|
+
enforced: false,
|
|
794
|
+
status: 'NOT_APPLICABLE',
|
|
862
795
|
detected_platforms: [],
|
|
863
796
|
requirements: [],
|
|
864
|
-
|
|
865
|
-
violations: toRequiredSkillSourceViolations(
|
|
866
|
-
sourceDiagnostics,
|
|
867
|
-
params.skillsEnforcement
|
|
868
|
-
),
|
|
797
|
+
violations: [],
|
|
869
798
|
};
|
|
870
799
|
}
|
|
871
800
|
const requirements: AiGateSkillsContractPlatformRequirement[] = requiredPlatforms.map((platform) => ({
|
|
@@ -892,12 +821,7 @@ const toSkillsContractAssessment = (params: {
|
|
|
892
821
|
status: 'FAIL',
|
|
893
822
|
detected_platforms: [],
|
|
894
823
|
requirements,
|
|
895
|
-
source_diagnostics: sourceDiagnostics,
|
|
896
824
|
violations: [
|
|
897
|
-
...toRequiredSkillSourceViolations(
|
|
898
|
-
sourceDiagnostics,
|
|
899
|
-
params.skillsEnforcement
|
|
900
|
-
),
|
|
901
825
|
toSkillsViolation(
|
|
902
826
|
params.skillsEnforcement,
|
|
903
827
|
'EVIDENCE_SKILLS_PLATFORMS_UNDETECTED',
|
|
@@ -909,15 +833,11 @@ const toSkillsContractAssessment = (params: {
|
|
|
909
833
|
if (assessmentPlatforms.length === 0) {
|
|
910
834
|
return {
|
|
911
835
|
stage: params.stage,
|
|
912
|
-
enforced:
|
|
913
|
-
status:
|
|
836
|
+
enforced: false,
|
|
837
|
+
status: 'NOT_APPLICABLE',
|
|
914
838
|
detected_platforms: [],
|
|
915
839
|
requirements: [],
|
|
916
|
-
|
|
917
|
-
violations: toRequiredSkillSourceViolations(
|
|
918
|
-
sourceDiagnostics,
|
|
919
|
-
params.skillsEnforcement
|
|
920
|
-
),
|
|
840
|
+
violations: [],
|
|
921
841
|
};
|
|
922
842
|
}
|
|
923
843
|
|
|
@@ -929,10 +849,7 @@ const toSkillsContractAssessment = (params: {
|
|
|
929
849
|
);
|
|
930
850
|
|
|
931
851
|
const requirements: AiGateSkillsContractPlatformRequirement[] = [];
|
|
932
|
-
const violations: AiGateViolation[] =
|
|
933
|
-
sourceDiagnostics,
|
|
934
|
-
params.skillsEnforcement
|
|
935
|
-
);
|
|
852
|
+
const violations: AiGateViolation[] = [];
|
|
936
853
|
if (requiredPlatforms.length > 0 && detectedPlatforms.length === 0) {
|
|
937
854
|
violations.push(
|
|
938
855
|
toSkillsViolation(
|
|
@@ -1043,7 +960,6 @@ const toSkillsContractAssessment = (params: {
|
|
|
1043
960
|
status: violations.length === 0 ? 'PASS' : 'FAIL',
|
|
1044
961
|
detected_platforms: detectedPlatforms,
|
|
1045
962
|
requirements,
|
|
1046
|
-
source_diagnostics: sourceDiagnostics,
|
|
1047
963
|
violations,
|
|
1048
964
|
};
|
|
1049
965
|
};
|
|
@@ -1248,63 +1164,6 @@ const collectEvidenceViolations = (
|
|
|
1248
1164
|
return { violations, ageSeconds };
|
|
1249
1165
|
};
|
|
1250
1166
|
|
|
1251
|
-
const severityOrder: ReadonlyArray<'CRITICAL' | 'ERROR' | 'WARN' | 'INFO'> = [
|
|
1252
|
-
'CRITICAL',
|
|
1253
|
-
'ERROR',
|
|
1254
|
-
'WARN',
|
|
1255
|
-
'INFO',
|
|
1256
|
-
];
|
|
1257
|
-
|
|
1258
|
-
const toHighestTriggeredSeverity = (
|
|
1259
|
-
severityCounts: Readonly<Record<'INFO' | 'WARN' | 'ERROR' | 'CRITICAL', number>>,
|
|
1260
|
-
threshold: 'INFO' | 'WARN' | 'ERROR' | 'CRITICAL'
|
|
1261
|
-
): 'INFO' | 'WARN' | 'ERROR' | 'CRITICAL' | null => {
|
|
1262
|
-
for (const severity of severityOrder) {
|
|
1263
|
-
if (severityCounts[severity] > 0 && isSeverityAtLeast(severity, threshold)) {
|
|
1264
|
-
return severity;
|
|
1265
|
-
}
|
|
1266
|
-
}
|
|
1267
|
-
return null;
|
|
1268
|
-
};
|
|
1269
|
-
|
|
1270
|
-
const collectEvidencePolicyThresholdViolations = (params: {
|
|
1271
|
-
evidenceResult: EvidenceReadResult;
|
|
1272
|
-
policy: ReturnType<typeof resolvePolicyForStage>['policy'];
|
|
1273
|
-
}): AiGateViolation[] => {
|
|
1274
|
-
if (params.evidenceResult.kind !== 'valid') {
|
|
1275
|
-
return [];
|
|
1276
|
-
}
|
|
1277
|
-
|
|
1278
|
-
const severityCounts = params.evidenceResult.evidence.severity_metrics.by_severity;
|
|
1279
|
-
const blockSeverity = toHighestTriggeredSeverity(
|
|
1280
|
-
severityCounts,
|
|
1281
|
-
params.policy.blockOnOrAbove
|
|
1282
|
-
);
|
|
1283
|
-
if (blockSeverity && params.evidenceResult.evidence.ai_gate.status !== 'BLOCKED') {
|
|
1284
|
-
return [
|
|
1285
|
-
toErrorViolation(
|
|
1286
|
-
'EVIDENCE_POLICY_THRESHOLD_BLOCK',
|
|
1287
|
-
`Evidence severities exceed block_on_or_above=${params.policy.blockOnOrAbove} (highest=${blockSeverity}).`
|
|
1288
|
-
),
|
|
1289
|
-
];
|
|
1290
|
-
}
|
|
1291
|
-
|
|
1292
|
-
const warnSeverity = toHighestTriggeredSeverity(
|
|
1293
|
-
severityCounts,
|
|
1294
|
-
params.policy.warnOnOrAbove
|
|
1295
|
-
);
|
|
1296
|
-
if (warnSeverity && params.evidenceResult.evidence.ai_gate.status === 'ALLOWED') {
|
|
1297
|
-
return [
|
|
1298
|
-
toWarnViolation(
|
|
1299
|
-
'EVIDENCE_POLICY_THRESHOLD_WARN',
|
|
1300
|
-
`Evidence severities exceed warn_on_or_above=${params.policy.warnOnOrAbove} (highest=${warnSeverity}).`
|
|
1301
|
-
),
|
|
1302
|
-
];
|
|
1303
|
-
}
|
|
1304
|
-
|
|
1305
|
-
return [];
|
|
1306
|
-
};
|
|
1307
|
-
|
|
1308
1167
|
const toEvidenceSourceDescriptor = (
|
|
1309
1168
|
result: EvidenceReadResult,
|
|
1310
1169
|
repoRoot: string
|
|
@@ -1334,81 +1193,17 @@ const collectGitflowViolations = (
|
|
|
1334
1193
|
if (!repoState.git.available) {
|
|
1335
1194
|
return violations;
|
|
1336
1195
|
}
|
|
1337
|
-
|
|
1338
|
-
const normalizedBranch = branch?.toLowerCase() ?? null;
|
|
1339
|
-
if (branch && normalizedBranch && protectedBranches.has(normalizedBranch)) {
|
|
1196
|
+
if (repoState.git.branch && protectedBranches.has(repoState.git.branch)) {
|
|
1340
1197
|
violations.push(
|
|
1341
1198
|
toErrorViolation(
|
|
1342
1199
|
'GITFLOW_PROTECTED_BRANCH',
|
|
1343
|
-
`Direct work on protected branch "${branch}" is not allowed.`
|
|
1344
|
-
)
|
|
1345
|
-
);
|
|
1346
|
-
return violations;
|
|
1347
|
-
}
|
|
1348
|
-
if (
|
|
1349
|
-
branch
|
|
1350
|
-
&& !DEFAULT_GITFLOW_BRANCH_PATTERNS.some((pattern) => pattern.test(branch))
|
|
1351
|
-
) {
|
|
1352
|
-
violations.push(
|
|
1353
|
-
toErrorViolation(
|
|
1354
|
-
'GITFLOW_BRANCH_NAMING_INVALID',
|
|
1355
|
-
`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.`
|
|
1356
1201
|
)
|
|
1357
1202
|
);
|
|
1358
1203
|
}
|
|
1359
1204
|
return violations;
|
|
1360
1205
|
};
|
|
1361
1206
|
|
|
1362
|
-
const DEFAULT_TRACKING_STATE: RepoTrackingState = {
|
|
1363
|
-
enforced: false,
|
|
1364
|
-
canonical_path: null,
|
|
1365
|
-
canonical_present: false,
|
|
1366
|
-
source_file: null,
|
|
1367
|
-
in_progress_count: null,
|
|
1368
|
-
single_in_progress_valid: null,
|
|
1369
|
-
conflict: false,
|
|
1370
|
-
declarations: [],
|
|
1371
|
-
};
|
|
1372
|
-
|
|
1373
|
-
const collectTrackingViolations = (repoState: RepoState): AiGateViolation[] => {
|
|
1374
|
-
const tracking = repoState.lifecycle.tracking ?? DEFAULT_TRACKING_STATE;
|
|
1375
|
-
if (!tracking.enforced) {
|
|
1376
|
-
return [];
|
|
1377
|
-
}
|
|
1378
|
-
|
|
1379
|
-
if (tracking.conflict) {
|
|
1380
|
-
const declaredPaths = tracking.declarations
|
|
1381
|
-
.map((entry) => `${entry.source_file}:${entry.resolved_path}`)
|
|
1382
|
-
.join(', ');
|
|
1383
|
-
return [
|
|
1384
|
-
toErrorViolation(
|
|
1385
|
-
'TRACKING_CANONICAL_SOURCE_CONFLICT',
|
|
1386
|
-
`Se ha detectado un conflicto entre fuentes canónicas de tracking (${declaredPaths}).`
|
|
1387
|
-
),
|
|
1388
|
-
];
|
|
1389
|
-
}
|
|
1390
|
-
|
|
1391
|
-
if (!tracking.canonical_path || !tracking.canonical_present) {
|
|
1392
|
-
return [
|
|
1393
|
-
toErrorViolation(
|
|
1394
|
-
'TRACKING_CANONICAL_FILE_MISSING',
|
|
1395
|
-
`Falta el archivo canónico de tracking (${tracking.canonical_path ?? 'sin-declarar'}).`
|
|
1396
|
-
),
|
|
1397
|
-
];
|
|
1398
|
-
}
|
|
1399
|
-
|
|
1400
|
-
if (tracking.single_in_progress_valid === false) {
|
|
1401
|
-
return [
|
|
1402
|
-
toErrorViolation(
|
|
1403
|
-
'TRACKING_CANONICAL_IN_PROGRESS_INVALID',
|
|
1404
|
-
`El archivo canónico de tracking debe contener exactamente una tarea o fase en construcción (conteo=${tracking.in_progress_count ?? 'n/d'}).`
|
|
1405
|
-
),
|
|
1406
|
-
];
|
|
1407
|
-
}
|
|
1408
|
-
|
|
1409
|
-
return [];
|
|
1410
|
-
};
|
|
1411
|
-
|
|
1412
1207
|
const resolvePendingChanges = (repoState: RepoState): number | null => {
|
|
1413
1208
|
if (!repoState.git.available) {
|
|
1414
1209
|
return null;
|
|
@@ -1627,7 +1422,6 @@ export const evaluateAiGate = (
|
|
|
1627
1422
|
readMcpAiGateReceipt: activeDependencies.readMcpAiGateReceipt,
|
|
1628
1423
|
});
|
|
1629
1424
|
const gitflowViolations = collectGitflowViolations(repoState, protectedBranches);
|
|
1630
|
-
const trackingViolations = collectTrackingViolations(repoState);
|
|
1631
1425
|
const skillsContract = toSkillsContractAssessment({
|
|
1632
1426
|
stage: params.stage,
|
|
1633
1427
|
repoRoot: params.repoRoot,
|
|
@@ -1645,27 +1439,16 @@ export const evaluateAiGate = (
|
|
|
1645
1439
|
|| (params.stage === 'PRE_WRITE' && requiredSkillsPlatforms.length === 0)
|
|
1646
1440
|
? []
|
|
1647
1441
|
: [
|
|
1648
|
-
...skillsContract.violations.filter(
|
|
1649
|
-
(violation) =>
|
|
1650
|
-
violation.code === 'SKILLS_REQUIRED_SOURCE_MISSING'
|
|
1651
|
-
|| violation.code === 'SKILLS_REQUIRED_SOURCE_UNREADABLE'
|
|
1652
|
-
),
|
|
1653
1442
|
toSkillsViolation(
|
|
1654
1443
|
skillsEnforcement,
|
|
1655
1444
|
'EVIDENCE_SKILLS_CONTRACT_INCOMPLETE',
|
|
1656
1445
|
`Skills contract incomplete for ${params.stage}: ${skillsContract.violations.map((violation) => violation.code).join(', ')}.`
|
|
1657
1446
|
),
|
|
1658
1447
|
];
|
|
1659
|
-
const policyThresholdViolations = collectEvidencePolicyThresholdViolations({
|
|
1660
|
-
evidenceResult,
|
|
1661
|
-
policy: resolvedPolicy.policy,
|
|
1662
|
-
});
|
|
1663
1448
|
const violations = [
|
|
1664
1449
|
...evidenceAssessment.violations,
|
|
1665
|
-
...policyThresholdViolations,
|
|
1666
1450
|
...stageSkillsContractViolations,
|
|
1667
1451
|
...gitflowViolations,
|
|
1668
|
-
...trackingViolations,
|
|
1669
1452
|
...mcpReceiptAssessment.violations,
|
|
1670
1453
|
];
|
|
1671
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
|
|
|
@@ -6,10 +6,6 @@ const AI_GATE_STAGES = new Set<AiGateStage>(['PRE_WRITE', 'PRE_COMMIT', 'PRE_PUS
|
|
|
6
6
|
|
|
7
7
|
const REPO_POLICY_CODES = new Set<string>([
|
|
8
8
|
'GITFLOW_PROTECTED_BRANCH',
|
|
9
|
-
'GITFLOW_BRANCH_NAMING_INVALID',
|
|
10
|
-
'TRACKING_CANONICAL_SOURCE_CONFLICT',
|
|
11
|
-
'TRACKING_CANONICAL_FILE_MISSING',
|
|
12
|
-
'TRACKING_CANONICAL_IN_PROGRESS_INVALID',
|
|
13
9
|
'EVIDENCE_PREWRITE_WORKTREE_OVER_LIMIT',
|
|
14
10
|
'EVIDENCE_PREWRITE_WORKTREE_WARN',
|
|
15
11
|
]);
|
|
@@ -1372,15 +1372,7 @@ export async function runPlatformGate(params: {
|
|
|
1372
1372
|
|
|
1373
1373
|
if (gateOutcome === 'BLOCK') {
|
|
1374
1374
|
if (params.silent !== true) {
|
|
1375
|
-
|
|
1376
|
-
params.policy.stage === 'PRE_PUSH'
|
|
1377
|
-
? 'PRE_PUSH'
|
|
1378
|
-
: params.policy.stage === 'CI'
|
|
1379
|
-
? 'CI'
|
|
1380
|
-
: 'PRE_COMMIT';
|
|
1381
|
-
dependencies.printGateFindings(findingsWithWaiver, {
|
|
1382
|
-
stage: renderStage,
|
|
1383
|
-
});
|
|
1375
|
+
dependencies.printGateFindings(findingsWithWaiver);
|
|
1384
1376
|
}
|
|
1385
1377
|
return 1;
|
|
1386
1378
|
}
|