pumuki 6.3.113 → 6.3.115

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 (99) hide show
  1. package/CHANGELOG.md +52 -5
  2. package/README.md +4 -2
  3. package/VERSION +1 -1
  4. package/core/facts/detectors/typescript/index.test.ts +0 -229
  5. package/core/facts/detectors/typescript/index.ts +0 -278
  6. package/core/facts/extractHeuristicFacts.ts +0 -4
  7. package/core/rules/presets/heuristics/typescript.test.ts +1 -21
  8. package/core/rules/presets/heuristics/typescript.ts +0 -72
  9. package/docs/README.md +13 -9
  10. package/docs/codex-skills/backend-enterprise-rules.md +3 -3
  11. package/docs/operations/RELEASE_NOTES.md +41 -4
  12. package/docs/product/API_REFERENCE.md +1 -1
  13. package/docs/product/HOW_IT_WORKS.md +6 -0
  14. package/docs/product/INSTALLATION.md +1 -1
  15. package/docs/product/USAGE.md +42 -5
  16. package/docs/tracking/plan-curso-pumuki-stack-my-architecture.md +100 -44
  17. package/docs/validation/README.md +6 -3
  18. package/integrations/config/skillsDetectorRegistry.ts +0 -24
  19. package/integrations/config/skillsMarkdownRules.ts +0 -57
  20. package/integrations/evidence/buildEvidence.ts +0 -24
  21. package/integrations/evidence/repoState.ts +9 -7
  22. package/integrations/evidence/schema.ts +0 -18
  23. package/integrations/evidence/writeEvidence.ts +0 -24
  24. package/integrations/gate/evaluateAiGate.ts +8 -251
  25. package/integrations/gate/remediationCatalog.ts +0 -8
  26. package/integrations/git/GitService.ts +44 -5
  27. package/integrations/git/aiGateRepoPolicyFindings.ts +86 -17
  28. package/integrations/git/runPlatformGate.ts +1 -9
  29. package/integrations/git/runPlatformGateFacts.ts +19 -1
  30. package/integrations/git/runPlatformGateOutput.ts +41 -42
  31. package/integrations/lifecycle/adapter.templates.json +1 -0
  32. package/integrations/lifecycle/adapter.ts +0 -24
  33. package/integrations/lifecycle/audit.ts +101 -0
  34. package/integrations/lifecycle/cli.ts +120 -99
  35. package/integrations/lifecycle/cliSdd.ts +4 -26
  36. package/integrations/lifecycle/doctor.ts +40 -102
  37. package/integrations/lifecycle/index.ts +2 -0
  38. package/integrations/lifecycle/install.ts +0 -21
  39. package/integrations/lifecycle/packageInfo.ts +1 -118
  40. package/integrations/lifecycle/state.ts +1 -8
  41. package/integrations/lifecycle/status.ts +40 -59
  42. package/integrations/lifecycle/watch.ts +1 -1
  43. package/integrations/mcp/aiGateCheck.ts +10 -194
  44. package/integrations/mcp/autoExecuteAiStart.ts +116 -92
  45. package/integrations/mcp/enterpriseServer.ts +7 -23
  46. package/integrations/mcp/enterpriseStdioServer.cli.ts +4 -31
  47. package/integrations/mcp/preFlightCheck.ts +5 -67
  48. package/integrations/platform/detectPlatforms.ts +37 -0
  49. package/integrations/sdd/policy.ts +28 -20
  50. package/package.json +1 -1
  51. package/scripts/check-tracking-single-active.sh +1 -1
  52. package/scripts/consumer-menu-matrix-baseline-report-lib.ts +13 -38
  53. package/scripts/consumer-postinstall-resolve-args.cjs +44 -0
  54. package/scripts/consumer-postinstall.cjs +76 -21
  55. package/scripts/framework-menu-advanced-view-lib.ts +0 -49
  56. package/scripts/framework-menu-consumer-actions-lib.ts +28 -4
  57. package/scripts/framework-menu-consumer-preflight-hints.ts +5 -2
  58. package/scripts/framework-menu-consumer-preflight-render.ts +0 -10
  59. package/scripts/framework-menu-consumer-preflight-run.ts +0 -23
  60. package/scripts/framework-menu-consumer-preflight-types.ts +0 -12
  61. package/scripts/framework-menu-consumer-runtime-actions.ts +87 -17
  62. package/scripts/framework-menu-consumer-runtime-audit.ts +36 -2
  63. package/scripts/framework-menu-consumer-runtime-evidence-classic.ts +140 -0
  64. package/scripts/framework-menu-consumer-runtime-lib.ts +2 -38
  65. package/scripts/framework-menu-consumer-runtime-menu.ts +4 -31
  66. package/scripts/framework-menu-consumer-runtime-types.ts +3 -5
  67. package/scripts/framework-menu-evidence-summary-lib.ts +1 -0
  68. package/scripts/framework-menu-evidence-summary-read.ts +57 -5
  69. package/scripts/framework-menu-evidence-summary-severity.ts +3 -1
  70. package/scripts/framework-menu-evidence-summary-types.ts +7 -0
  71. package/scripts/framework-menu-gate-lib.ts +9 -0
  72. package/scripts/framework-menu-layout-data.ts +5 -0
  73. package/scripts/framework-menu-matrix-baseline-lib.ts +15 -14
  74. package/scripts/framework-menu-matrix-canary-lib.ts +22 -1
  75. package/scripts/framework-menu-matrix-evidence-lib.ts +1 -0
  76. package/scripts/framework-menu-matrix-evidence-types.ts +13 -1
  77. package/scripts/framework-menu-matrix-runner-lib.ts +35 -0
  78. package/scripts/framework-menu-system-notifications-cause.ts +0 -3
  79. package/scripts/framework-menu-system-notifications-macos-swift-source.ts +24 -204
  80. package/scripts/framework-menu-system-notifications-macos.ts +4 -0
  81. package/scripts/framework-menu-system-notifications-payloads-blocked.ts +1 -1
  82. package/scripts/framework-menu-system-notifications-text.ts +1 -7
  83. package/scripts/framework-menu.ts +3 -24
  84. package/scripts/package-install-smoke-consumer-git-repo-lib.ts +1 -10
  85. package/scripts/package-install-smoke-consumer-npm-lib.ts +9 -46
  86. package/scripts/pumuki-full-surface-smoke-lib.ts +37 -0
  87. package/scripts/pumuki-full-surface-smoke.ts +346 -0
  88. package/scripts/pumuki-smoke-installed-wrapper.cjs +31 -0
  89. package/integrations/evidence/trackingContract.ts +0 -17
  90. package/integrations/gate/governanceActionCatalog.ts +0 -275
  91. package/integrations/lifecycle/bootstrapManifest.ts +0 -248
  92. package/integrations/lifecycle/cliGovernanceConsole.ts +0 -69
  93. package/integrations/lifecycle/governanceNextAction.ts +0 -171
  94. package/integrations/lifecycle/governanceObservationSnapshot.ts +0 -369
  95. package/integrations/lifecycle/trackingState.ts +0 -403
  96. package/integrations/mcp/alignedPlatformGate.ts +0 -232
  97. package/integrations/mcp/readMcpPrePushStdin.ts +0 -7
  98. package/scripts/build-ruralgo-s1-evidence-pack.ts +0 -85
  99. 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
  };
@@ -1,11 +1,12 @@
1
1
  import { execFileSync as runBinarySync } from 'node:child_process';
2
2
  import { existsSync, readFileSync } from 'node:fs';
3
3
  import { join } from 'node:path';
4
- import { readLifecycleStatus } from '../lifecycle/status';
4
+ import { LifecycleGitService } from '../lifecycle/gitService';
5
+ import { getPumukiHooksStatus } from '../lifecycle/hookManager';
5
6
  import { resolvePumukiVersionMetadata } from '../lifecycle/packageInfo';
7
+ import { readLifecycleState } from '../lifecycle/state';
6
8
  import { readPersistedHardModeConfig } from '../policy/policyProfiles';
7
9
  import type { RepoHardModeState, RepoHookState, RepoState } from './schema';
8
- import { readRepoTrackingState } from './trackingContract';
9
10
 
10
11
  type HookStateShape = { exists: boolean; managedBlockPresent: boolean };
11
12
 
@@ -74,10 +75,13 @@ const toAheadBehind = (
74
75
  };
75
76
 
76
77
  const readLifecycleStatusSafe = (repoRoot: string): LifecycleStatusShape => {
78
+ const git = new LifecycleGitService();
77
79
  try {
78
- return readLifecycleStatus({
79
- cwd: repoRoot,
80
- });
80
+ return {
81
+ lifecycleState: readLifecycleState(git, repoRoot),
82
+ packageVersion: resolvePumukiVersionMetadata({ repoRoot }).resolvedVersion,
83
+ hookStatus: getPumukiHooksStatus(repoRoot),
84
+ };
81
85
  } catch {
82
86
  return {
83
87
  lifecycleState: {},
@@ -124,7 +128,6 @@ export const captureRepoState = (repoRoot: string): RepoState => {
124
128
  const consumerFacingVersion = versionMetadata.resolvedVersion;
125
129
  const installedVersion = versionMetadata.consumerInstalledVersion;
126
130
  const hardModeState = readHardModeState(repoRoot);
127
- const trackingState = readRepoTrackingState(repoRoot);
128
131
 
129
132
  return {
130
133
  repo_root: repoRoot,
@@ -152,7 +155,6 @@ export const captureRepoState = (repoRoot: string): RepoState => {
152
155
  pre_push: toHookState(lifecycle.hookStatus['pre-push']),
153
156
  },
154
157
  hard_mode: hardModeState,
155
- tracking: trackingState,
156
158
  },
157
159
  };
158
160
  };
@@ -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, 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,
@@ -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 === 'CI' && requiredPlatforms.length > 0
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
- coverage,
874
- })
762
+ coverage,
763
+ })
875
764
  : [];
876
765
  const detectedPlatforms =
877
- stageScopedDetectedPlatforms.length > 0
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 !== 'CI' && detectedPlatforms.length > 0
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
- const branch = repoState.git.branch?.trim() ?? null;
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 = 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