pumuki 6.3.97 → 6.3.99

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.
Files changed (96) hide show
  1. package/AGENTS.md +269 -0
  2. package/CHANGELOG.md +697 -0
  3. package/README.md +4 -2
  4. package/VERSION +1 -1
  5. package/docs/README.md +13 -9
  6. package/docs/operations/RELEASE_NOTES.md +12 -76
  7. package/docs/product/HOW_IT_WORKS.md +6 -0
  8. package/docs/product/INSTALLATION.md +1 -1
  9. package/docs/product/USAGE.md +41 -4
  10. package/docs/tracking/plan-curso-pumuki-stack-my-architecture.md +118 -0
  11. package/docs/validation/README.md +6 -3
  12. package/integrations/config/skillsCustomRules.ts +18 -99
  13. package/integrations/evidence/buildEvidence.ts +0 -24
  14. package/integrations/evidence/repoState.ts +0 -3
  15. package/integrations/evidence/schema.ts +0 -18
  16. package/integrations/evidence/writeEvidence.ts +0 -24
  17. package/integrations/gate/evaluateAiGate.ts +15 -232
  18. package/integrations/gate/remediationCatalog.ts +0 -8
  19. package/integrations/git/GitService.ts +44 -5
  20. package/integrations/git/aiGateRepoPolicyFindings.ts +0 -4
  21. package/integrations/git/runPlatformGate.ts +1 -9
  22. package/integrations/git/runPlatformGateFacts.ts +19 -1
  23. package/integrations/git/runPlatformGateOutput.ts +27 -36
  24. package/integrations/lifecycle/adapter.templates.json +7 -13
  25. package/integrations/lifecycle/adapter.ts +0 -24
  26. package/integrations/lifecycle/artifacts.ts +1 -6
  27. package/integrations/lifecycle/audit.ts +101 -0
  28. package/integrations/lifecycle/cli.ts +110 -70
  29. package/integrations/lifecycle/cliSdd.ts +13 -8
  30. package/integrations/lifecycle/doctor.ts +16 -48
  31. package/integrations/lifecycle/hookManager.ts +0 -77
  32. package/integrations/lifecycle/index.ts +2 -0
  33. package/integrations/lifecycle/install.ts +0 -21
  34. package/integrations/lifecycle/npmService.ts +3 -155
  35. package/integrations/lifecycle/policyValidationSnapshot.ts +8 -2
  36. package/integrations/lifecycle/preWriteAutomation.ts +7 -77
  37. package/integrations/lifecycle/state.ts +1 -8
  38. package/integrations/lifecycle/status.ts +2 -29
  39. package/integrations/mcp/aiGateCheck.ts +26 -206
  40. package/integrations/mcp/autoExecuteAiStart.ts +87 -94
  41. package/integrations/mcp/enterpriseServer.ts +7 -23
  42. package/integrations/mcp/enterpriseStdioServer.cli.ts +4 -31
  43. package/integrations/mcp/preFlightCheck.ts +5 -51
  44. package/integrations/platform/detectPlatforms.ts +37 -0
  45. package/integrations/policy/experimentalFeatures.ts +1 -1
  46. package/integrations/sdd/evidenceScaffold.ts +2 -109
  47. package/package.json +10 -2
  48. package/scripts/check-tracking-single-active.sh +1 -1
  49. package/scripts/consumer-menu-matrix-baseline-report-lib.ts +13 -38
  50. package/scripts/consumer-postinstall-resolve-args.cjs +44 -0
  51. package/scripts/consumer-postinstall.cjs +76 -21
  52. package/scripts/framework-menu-advanced-view-lib.ts +0 -15
  53. package/scripts/framework-menu-consumer-actions-lib.ts +28 -4
  54. package/scripts/framework-menu-consumer-preflight-hints.ts +5 -2
  55. package/scripts/framework-menu-consumer-preflight-render.ts +0 -10
  56. package/scripts/framework-menu-consumer-preflight-run.ts +0 -23
  57. package/scripts/framework-menu-consumer-preflight-types.ts +0 -12
  58. package/scripts/framework-menu-consumer-runtime-actions.ts +87 -17
  59. package/scripts/framework-menu-consumer-runtime-audit.ts +36 -2
  60. package/scripts/framework-menu-consumer-runtime-evidence-classic.ts +140 -0
  61. package/scripts/framework-menu-consumer-runtime-lib.ts +2 -10
  62. package/scripts/framework-menu-consumer-runtime-menu.ts +4 -18
  63. package/scripts/framework-menu-consumer-runtime-types.ts +3 -3
  64. package/scripts/framework-menu-evidence-summary-lib.ts +1 -0
  65. package/scripts/framework-menu-evidence-summary-read.ts +57 -5
  66. package/scripts/framework-menu-evidence-summary-severity.ts +3 -1
  67. package/scripts/framework-menu-evidence-summary-types.ts +7 -0
  68. package/scripts/framework-menu-gate-lib.ts +9 -0
  69. package/scripts/framework-menu-layout-data.ts +5 -0
  70. package/scripts/framework-menu-matrix-baseline-lib.ts +15 -14
  71. package/scripts/framework-menu-matrix-canary-lib.ts +22 -1
  72. package/scripts/framework-menu-matrix-evidence-lib.ts +1 -0
  73. package/scripts/framework-menu-matrix-evidence-types.ts +13 -1
  74. package/scripts/framework-menu-matrix-runner-lib.ts +35 -0
  75. package/scripts/framework-menu-system-notifications-cause.ts +0 -24
  76. package/scripts/framework-menu-system-notifications-macos-swift-source.ts +24 -204
  77. package/scripts/framework-menu-system-notifications-macos.ts +4 -0
  78. package/scripts/framework-menu-system-notifications-payloads-blocked.ts +1 -1
  79. package/scripts/framework-menu-system-notifications-remediation.ts +13 -24
  80. package/scripts/framework-menu-system-notifications-text.ts +1 -7
  81. package/scripts/framework-menu.ts +3 -2
  82. package/scripts/package-install-smoke-consumer-git-repo-lib.ts +1 -10
  83. package/scripts/package-install-smoke-consumer-npm-lib.ts +9 -46
  84. package/scripts/pumuki-full-surface-smoke-lib.ts +37 -0
  85. package/scripts/pumuki-full-surface-smoke.ts +346 -0
  86. package/scripts/pumuki-smoke-installed-wrapper.cjs +31 -0
  87. package/integrations/evidence/trackingContract.ts +0 -150
  88. package/integrations/gate/governanceActionCatalog.ts +0 -275
  89. package/integrations/lifecycle/bootstrapManifest.ts +0 -248
  90. package/integrations/lifecycle/cliGovernanceConsole.ts +0 -69
  91. package/integrations/lifecycle/governanceNextAction.ts +0 -164
  92. package/integrations/lifecycle/governanceObservationSnapshot.ts +0 -613
  93. package/integrations/mcp/alignedPlatformGate.ts +0 -232
  94. package/integrations/mcp/readMcpPrePushStdin.ts +0 -7
  95. package/scripts/build-ruralgo-s1-evidence-pack.ts +0 -85
  96. 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, RepoTrackingState } from '../evidence/schema';
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 || sourceDiagnostics.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
- source_diagnostics: sourceDiagnostics,
791
- violations: [
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: sourceDiagnostics.length > 0,
861
- status: sourceDiagnostics.length > 0 ? 'FAIL' : 'NOT_APPLICABLE',
793
+ enforced: false,
794
+ status: 'NOT_APPLICABLE',
862
795
  detected_platforms: [],
863
796
  requirements: [],
864
- source_diagnostics: sourceDiagnostics,
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: sourceDiagnostics.length > 0,
913
- status: sourceDiagnostics.length > 0 ? 'FAIL' : 'NOT_APPLICABLE',
836
+ enforced: false,
837
+ status: 'NOT_APPLICABLE',
914
838
  detected_platforms: [],
915
839
  requirements: [],
916
- source_diagnostics: sourceDiagnostics,
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[] = toRequiredSkillSourceViolations(
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
- const branch = repoState.git.branch?.trim() ?? null;
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 = this.runGit(['ls-files', '--others', '--exclude-standard'])
97
- .split('\n')
98
- .map((line) => line.trim())
99
- .filter((line) => line.length > 0)
100
- .filter((path) => hasAllowedExtension(path, extensions));
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
- const renderStage =
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
  }