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
@@ -1,20 +1,98 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
1
3
  import type { Finding } from '../../core/gate/Finding';
2
4
  import type { GateStage } from '../../core/gate/GateStage';
3
5
  import { evaluateAiGate, type AiGateStage } from '../gate/evaluateAiGate';
4
- import { resolveRepoTrackingState } from '../lifecycle/trackingState';
5
6
 
6
7
  const AI_GATE_STAGES = new Set<AiGateStage>(['PRE_WRITE', 'PRE_COMMIT', 'PRE_PUSH', 'CI']);
7
8
 
8
9
  const REPO_POLICY_CODES = new Set<string>([
9
10
  'GITFLOW_PROTECTED_BRANCH',
10
- 'GITFLOW_BRANCH_NAMING_INVALID',
11
- 'TRACKING_CANONICAL_SOURCE_CONFLICT',
12
- 'TRACKING_CANONICAL_FILE_MISSING',
13
11
  'TRACKING_CANONICAL_IN_PROGRESS_INVALID',
14
12
  'EVIDENCE_PREWRITE_WORKTREE_OVER_LIMIT',
15
13
  'EVIDENCE_PREWRITE_WORKTREE_WARN',
16
14
  ]);
17
15
 
16
+ const TRACKING_CANDIDATE_FILES = [
17
+ 'docs/technical/08-validation/refactor/pumuki-integration-feedback.md',
18
+ 'docs/RURALGO_SEGUIMIENTO.md',
19
+ 'docs/pumuki/PUMUKI_BUGS_MEJORAS.md',
20
+ 'docs/BUGS_Y_MEJORAS_PUMUKI.md',
21
+ 'PUMUKI-RESET-MASTER-PLAN.md',
22
+ 'RURALGO_SEGUIMIENTO.md',
23
+ ] as const;
24
+
25
+ type TrackingActiveEntry = {
26
+ taskId: string | null;
27
+ lineNumber: number;
28
+ };
29
+
30
+ export const collectTrackingActiveEntriesFromMarkdown = (
31
+ markdown: string
32
+ ): ReadonlyArray<TrackingActiveEntry> => {
33
+ const entries: TrackingActiveEntry[] = [];
34
+ const lines = markdown.split(/\r?\n/u);
35
+ for (const [index, line] of lines.entries()) {
36
+ const boardRowMatch = line.match(/^\|\s*🚧\s*\|\s*([A-Z0-9-]+)\s*\|/u);
37
+ if (boardRowMatch) {
38
+ entries.push({
39
+ taskId: boardRowMatch[1]!.trim(),
40
+ lineNumber: index + 1,
41
+ });
42
+ continue;
43
+ }
44
+ const tableMatch = line.match(
45
+ /^\|\s*\d+\s*\|\s*`([^`]+)`\s*\|.*\|\s*🚧(?:\s+reported\s+activo|\s+En construcción|\s+En construccion)?\s*\|/u
46
+ );
47
+ if (tableMatch) {
48
+ entries.push({
49
+ taskId: tableMatch[1]!.trim(),
50
+ lineNumber: index + 1,
51
+ });
52
+ continue;
53
+ }
54
+ const bulletMatch = line.match(/^- 🚧 (`?P[0-9A-Za-z.-]+`?)/u);
55
+ if (bulletMatch) {
56
+ entries.push({
57
+ taskId: bulletMatch[1]!.replaceAll('`', '').trim(),
58
+ lineNumber: index + 1,
59
+ });
60
+ continue;
61
+ }
62
+ if (/^- Estado:\s*🚧/u.test(line)) {
63
+ entries.push({
64
+ taskId: null,
65
+ lineNumber: index + 1,
66
+ });
67
+ }
68
+ }
69
+ return entries;
70
+ };
71
+
72
+ const formatTrackingEntry = (entry: TrackingActiveEntry): string =>
73
+ entry.taskId ? `${entry.taskId}@L${entry.lineNumber}` : `line_${entry.lineNumber}`;
74
+
75
+ export const appendTrackingActionableContext = (params: {
76
+ repoRoot: string;
77
+ message: string;
78
+ }): string => {
79
+ for (const candidate of TRACKING_CANDIDATE_FILES) {
80
+ const candidatePath = resolve(params.repoRoot, candidate);
81
+ if (!existsSync(candidatePath)) {
82
+ continue;
83
+ }
84
+ const source = readFileSync(candidatePath, 'utf8');
85
+ const entries = collectTrackingActiveEntriesFromMarkdown(source);
86
+ if (entries.length === 0) {
87
+ continue;
88
+ }
89
+ const preview = entries.slice(0, 3).map(formatTrackingEntry).join(', ');
90
+ const overflow = entries.length > 3 ? ` (+${entries.length - 3} more)` : '';
91
+ return `${params.message} active_entries=${preview}${overflow} tracking_source=${candidate}`;
92
+ }
93
+ return params.message;
94
+ };
95
+
18
96
  const toRepoPolicyFinding = (params: {
19
97
  code: string;
20
98
  message: string;
@@ -28,18 +106,6 @@ const toRepoPolicyFinding = (params: {
28
106
  source: 'ai_gate:repo_policy',
29
107
  });
30
108
 
31
- const appendTrackingActionableContext = (repoRoot: string, message: string): string => {
32
- const tracking = resolveRepoTrackingState(repoRoot);
33
- const activeEntries = (tracking.in_progress_entries ?? [])
34
- .map((entry) => `${entry.task_id ?? 'UNKNOWN'}@L${entry.line_number}`)
35
- .join(', ');
36
- if (!activeEntries) {
37
- return message;
38
- }
39
- const lastRunStatus = tracking.last_run_status ?? 'absent';
40
- return `${message} active_entries=${activeEntries} last_run_status=${lastRunStatus}.`;
41
- };
42
-
43
109
  export const collectAiGateRepoPolicyFindings = (params: {
44
110
  repoRoot: string;
45
111
  stage: GateStage;
@@ -58,7 +124,10 @@ export const collectAiGateRepoPolicyFindings = (params: {
58
124
  code: v.code,
59
125
  message:
60
126
  v.code === 'TRACKING_CANONICAL_IN_PROGRESS_INVALID'
61
- ? appendTrackingActionableContext(params.repoRoot, v.message)
127
+ ? appendTrackingActionableContext({
128
+ repoRoot: params.repoRoot,
129
+ message: v.message,
130
+ })
62
131
  : v.message,
63
132
  severity: v.severity === 'ERROR' ? 'ERROR' : 'WARN',
64
133
  })
@@ -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
  }
@@ -19,6 +19,11 @@ export type GateScope =
19
19
  kind: 'workingTree';
20
20
  extensions?: string[];
21
21
  }
22
+ | {
23
+ kind: 'unstaged';
24
+ extensions?: string[];
25
+ includeUntracked?: boolean;
26
+ }
22
27
  | {
23
28
  kind: 'range';
24
29
  fromRef: string;
@@ -26,7 +31,17 @@ export type GateScope =
26
31
  extensions?: string[];
27
32
  };
28
33
 
29
- const DEFAULT_EXTENSIONS = ['.swift', '.ts', '.tsx', '.js', '.jsx', '.kt', '.kts'];
34
+ export const DEFAULT_FACT_FILE_EXTENSIONS: ReadonlyArray<string> = [
35
+ '.swift',
36
+ '.ts',
37
+ '.tsx',
38
+ '.js',
39
+ '.jsx',
40
+ '.kt',
41
+ '.kts',
42
+ ];
43
+
44
+ const DEFAULT_EXTENSIONS = DEFAULT_FACT_FILE_EXTENSIONS;
30
45
 
31
46
  export const countScannedFilesFromFacts = (facts: ReadonlyArray<Fact>): number => {
32
47
  const contentPaths = new Set<string>();
@@ -66,6 +81,9 @@ export const resolveFactsForGateScope = async (params: {
66
81
  if (params.scope.kind === 'workingTree') {
67
82
  return params.git.getStagedAndUnstagedFacts(extensions);
68
83
  }
84
+ if (params.scope.kind === 'unstaged') {
85
+ return params.git.getUnstagedFacts(extensions, params.scope.includeUntracked);
86
+ }
69
87
 
70
88
  return getFactsForCommitRange({
71
89
  fromRef: params.scope.fromRef,
@@ -1,5 +1,27 @@
1
1
  import type { Finding } from '../../core/gate/Finding';
2
- import { resolveGovernanceCatalogAction } from '../gate/governanceActionCatalog';
2
+
3
+ const BLOCK_NEXT_ACTION_BY_CODE: Readonly<Record<string, string>> = {
4
+ SDD_SESSION_MISSING:
5
+ 'npx --yes --package pumuki@latest pumuki sdd session --open --change=<id>',
6
+ SDD_SESSION_INVALID:
7
+ 'npx --yes --package pumuki@latest pumuki sdd session --refresh --ttl-minutes=90',
8
+ PRE_PUSH_UPSTREAM_MISSING:
9
+ 'git push --set-upstream origin <branch>',
10
+ PRE_PUSH_UPSTREAM_MISALIGNED:
11
+ 'git branch --unset-upstream && git push --set-upstream origin <branch>',
12
+ EVIDENCE_STALE:
13
+ 'npx --yes --package pumuki@latest pumuki-pre-commit',
14
+ EVIDENCE_BRANCH_MISMATCH:
15
+ 'npx --yes --package pumuki@latest pumuki-pre-commit',
16
+ GIT_ATOMICITY_TOO_MANY_FILES:
17
+ 'git restore --staged . && separa cambios en commits más pequeños',
18
+ GIT_ATOMICITY_TOO_MANY_SCOPES:
19
+ 'Revisa scope_files del bloqueo y aplica: git restore --staged . && git add <scope>/ && git commit -m "<tipo>: <scope>"',
20
+ ACTIVE_RULE_IDS_EMPTY_FOR_CODE_CHANGES_HIGH:
21
+ 'Reconcilia policy/skills y reintenta PRE_COMMIT: npx --yes --package pumuki@latest pumuki policy reconcile --strict --json && npx --yes --package pumuki@latest pumuki-pre-commit',
22
+ SKILLS_SKILLS_FRONTEND_NO_SOLID_VIOLATIONS:
23
+ 'Aplica refactor incremental: extrae 1 componente/hook por commit y vuelve a ejecutar PRE_COMMIT.',
24
+ };
3
25
 
4
26
  const severityWeight = (severity: string): number => {
5
27
  switch (severity.toUpperCase()) {
@@ -31,7 +53,13 @@ const resolvePrimaryFinding = (findings: ReadonlyArray<Finding>): Finding => {
31
53
  };
32
54
 
33
55
  const sortFindingsBySeverity = (findings: ReadonlyArray<Finding>): ReadonlyArray<Finding> =>
34
- [...findings].sort((left, right) => severityWeight(right.severity) - severityWeight(left.severity));
56
+ [...findings].sort((left, right) => {
57
+ const severityDelta = severityWeight(right.severity) - severityWeight(left.severity);
58
+ if (severityDelta !== 0) {
59
+ return severityDelta;
60
+ }
61
+ return left.ruleId.localeCompare(right.ruleId);
62
+ });
35
63
 
36
64
  const normalizeAnchorLine = (lines: Finding['lines']): number => {
37
65
  if (Array.isArray(lines)) {
@@ -75,56 +103,27 @@ const formatFinding = (finding: Finding): string => {
75
103
  return `[${severity}] ${finding.ruleId}: ${finding.message} -> ${location}`;
76
104
  };
77
105
 
78
- const resolveAtomicityFallback = (code: string): string | null => {
79
- switch (code) {
80
- case 'GIT_ATOMICITY_TOO_MANY_FILES':
81
- return 'git restore --staged . && separa cambios en commits más pequeños';
82
- case 'GIT_ATOMICITY_TOO_MANY_SCOPES':
83
- return 'Revisa scope_files del bloqueo y aplica: git restore --staged . && git add <scope>/ && git commit -m "<tipo>: <scope>"';
84
- default:
85
- return null;
86
- }
87
- };
88
-
89
- export const printGateFindings = (
90
- findings: ReadonlyArray<Finding>,
91
- params?: { stage?: 'PRE_COMMIT' | 'PRE_PUSH' | 'CI' }
92
- ): void => {
106
+ export const printGateFindings = (findings: ReadonlyArray<Finding>): void => {
93
107
  if (findings.length === 0) {
94
108
  return;
95
109
  }
96
- const primary = resolvePrimaryFinding(findings);
97
- const action = resolveGovernanceCatalogAction({
98
- code: primary.code,
99
- stage: params?.stage ?? 'PRE_COMMIT',
100
- fallback: {
101
- reason_code: primary.code,
102
- instruction: 'Corrige el bloqueante primario y vuelve a ejecutar la validación del stage actual.',
103
- next_action: {
104
- kind: 'info',
105
- message:
106
- resolveAtomicityFallback(primary.code)
107
- ?? 'Corrige el bloqueante primario y vuelve a ejecutar el mismo comando.',
108
- },
109
- },
110
- });
111
- const nextAction = action.next_action.command ?? action.next_action.message;
110
+ const orderedFindings = sortFindingsBySeverity(findings);
111
+ const primary = resolvePrimaryFinding(orderedFindings);
112
+ const nextAction =
113
+ BLOCK_NEXT_ACTION_BY_CODE[primary.code]
114
+ ?? 'Corrige el bloqueante primario y vuelve a ejecutar el mismo comando.';
112
115
  process.stdout.write(
113
116
  `[pumuki][block-summary] primary=${primary.code} severity=${primary.severity.toUpperCase()} rule=${primary.ruleId}\n`
114
117
  );
115
- process.stdout.write(`[pumuki][block-summary] reason_code=${action.reason_code}\n`);
116
- process.stdout.write(`[pumuki][block-summary] instruction=${action.instruction}\n`);
117
118
  process.stdout.write(`[pumuki][block-summary] next_action=${nextAction}\n`);
118
- if (action.next_action.command) {
119
- process.stdout.write(`[pumuki][block-summary] command=${action.next_action.command}\n`);
120
- }
121
- const orderedFindings = sortFindingsBySeverity(findings);
122
119
  const secondaryWarnings = orderedFindings.filter(
123
- (finding) => finding !== primary && finding.severity.toUpperCase() === 'WARN'
120
+ (finding) =>
121
+ finding !== primary &&
122
+ severityWeight(finding.severity) < severityWeight(primary.severity)
124
123
  );
125
- for (const warning of secondaryWarnings) {
124
+ for (const finding of secondaryWarnings) {
126
125
  process.stdout.write(
127
- `[pumuki][warning-summary] secondary=${warning.code} severity=${warning.severity.toUpperCase()} rule=${warning.ruleId}\n`
126
+ `[pumuki][warning-summary] secondary=${finding.code} severity=${finding.severity.toUpperCase()} rule=${finding.ruleId}\n`
128
127
  );
129
128
  }
130
129
  for (const finding of orderedFindings) {
@@ -31,6 +31,7 @@
31
31
  "repo": [
32
32
  {
33
33
  "path": ".pumuki/adapter.json",
34
+ "mode": "json-merge",
34
35
  "payload": {
35
36
  "hooks": {
36
37
  "pre_write": {
@@ -2,7 +2,6 @@ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
2
  import { homedir } from 'node:os';
3
3
  import { dirname, isAbsolute, join, resolve } from 'node:path';
4
4
  import { LifecycleGitService, type ILifecycleGitService } from './gitService';
5
- import { writeLifecycleBootstrapManifest } from './bootstrapManifest';
6
5
 
7
6
  export type AdapterAgent = string;
8
7
 
@@ -12,10 +11,6 @@ export type LifecycleAdapterInstallResult = {
12
11
  dryRun: boolean;
13
12
  written: boolean;
14
13
  changedFiles: ReadonlyArray<string>;
15
- bootstrapManifest: {
16
- path: string;
17
- changed: boolean;
18
- };
19
14
  };
20
15
 
21
16
  type AdapterTemplate = {
@@ -159,30 +154,11 @@ export const runLifecycleAdapterInstall = (params: {
159
154
  writeFileSync(absolutePath, nextContents, 'utf8');
160
155
  }
161
156
 
162
- let bootstrapManifest = {
163
- path: join(repoRoot, '.pumuki', 'bootstrap-manifest.json'),
164
- changed: false,
165
- };
166
- if (!dryRun) {
167
- const manifestWrite = writeLifecycleBootstrapManifest({
168
- git,
169
- repoRoot,
170
- });
171
- bootstrapManifest = {
172
- path: manifestWrite.path,
173
- changed: manifestWrite.changed,
174
- };
175
- if (manifestWrite.changed) {
176
- changedFiles.push('.pumuki/bootstrap-manifest.json');
177
- }
178
- }
179
-
180
157
  return {
181
158
  repoRoot,
182
159
  agent: params.agent,
183
160
  dryRun,
184
161
  written: !dryRun,
185
162
  changedFiles,
186
- bootstrapManifest,
187
163
  };
188
164
  };
@@ -0,0 +1,101 @@
1
+ import { readEvidence } from '../evidence/readEvidence';
2
+ import { GitService } from '../git/GitService';
3
+ import { hasAllowedExtension } from '../git/gitDiffUtils';
4
+ import { runPlatformGate } from '../git/runPlatformGate';
5
+ import { evaluatePlatformGateFindings } from '../git/runPlatformGateEvaluation';
6
+ import { DEFAULT_FACT_FILE_EXTENSIONS } from '../git/runPlatformGateFacts';
7
+ import { resolvePolicyForStage } from '../gate/stagePolicies';
8
+
9
+ export type LifecycleAuditStage = 'PRE_COMMIT' | 'PRE_PUSH' | 'CI';
10
+
11
+ export type LifecycleAuditResult = {
12
+ command: 'pumuki audit';
13
+ repo_root: string;
14
+ stage: LifecycleAuditStage;
15
+ scope: { kind: 'repo' };
16
+ audit_mode: 'gate' | 'engine';
17
+ gate_exit_code: number;
18
+ files_scanned: number | null;
19
+ untracked_matching_extensions_count: number;
20
+ snapshot_outcome: string | null;
21
+ policy_reconcile_hint: string;
22
+ };
23
+
24
+ const POLICY_RECONCILE_HINT =
25
+ 'If .pumuki/policy-as-code.json signatures drift after a pumuki upgrade, run: pumuki policy reconcile --apply';
26
+
27
+ const countUntrackedMatchingExtensions = (
28
+ git: GitService,
29
+ extensions: ReadonlyArray<string>
30
+ ): number => {
31
+ const repoRoot = git.resolveRepoRoot();
32
+ const raw = git.runGit(['ls-files', '--others', '--exclude-standard'], repoRoot);
33
+ return raw
34
+ .split('\n')
35
+ .map((line) => line.trim())
36
+ .filter((line) => line.length > 0)
37
+ .filter((path) => hasAllowedExtension(path, extensions)).length;
38
+ };
39
+
40
+ export const runLifecycleAudit = async (params: {
41
+ stage: LifecycleAuditStage;
42
+ auditMode: 'gate' | 'engine';
43
+ }): Promise<LifecycleAuditResult> => {
44
+ const git = new GitService();
45
+ const repoRoot = git.resolveRepoRoot();
46
+ const resolved = resolvePolicyForStage(params.stage, repoRoot);
47
+ const extensions = DEFAULT_FACT_FILE_EXTENSIONS;
48
+ const untrackedMatchingExtensionsCount = countUntrackedMatchingExtensions(git, extensions);
49
+
50
+ const gateParams =
51
+ params.auditMode === 'engine'
52
+ ? {
53
+ policy: resolved.policy,
54
+ policyTrace: resolved.trace,
55
+ scope: { kind: 'repo' as const },
56
+ silent: true,
57
+ auditMode: 'engine' as const,
58
+ dependencies: {
59
+ evaluatePlatformGateFindings: (
60
+ evalParams: Parameters<typeof evaluatePlatformGateFindings>[0]
61
+ ) =>
62
+ evaluatePlatformGateFindings(evalParams, {
63
+ loadHeuristicsConfig: () => ({
64
+ astSemanticEnabled: true,
65
+ typeScriptScope: 'all',
66
+ }),
67
+ }),
68
+ printGateFindings: () => {},
69
+ },
70
+ }
71
+ : {
72
+ policy: resolved.policy,
73
+ policyTrace: resolved.trace,
74
+ scope: { kind: 'repo' as const },
75
+ silent: true,
76
+ auditMode: 'gate' as const,
77
+ };
78
+
79
+ const gateExitCode = await runPlatformGate(gateParams);
80
+ const evidence = readEvidence(repoRoot);
81
+ const filesScanned =
82
+ typeof evidence?.snapshot.files_scanned === 'number' &&
83
+ Number.isFinite(evidence.snapshot.files_scanned)
84
+ ? evidence.snapshot.files_scanned
85
+ : null;
86
+ const snapshotOutcome =
87
+ typeof evidence?.snapshot.outcome === 'string' ? evidence.snapshot.outcome : null;
88
+
89
+ return {
90
+ command: 'pumuki audit',
91
+ repo_root: repoRoot,
92
+ stage: params.stage,
93
+ scope: { kind: 'repo' },
94
+ audit_mode: params.auditMode,
95
+ gate_exit_code: gateExitCode,
96
+ files_scanned: filesScanned,
97
+ untracked_matching_extensions_count: untrackedMatchingExtensionsCount,
98
+ snapshot_outcome: snapshotOutcome,
99
+ policy_reconcile_hint: POLICY_RECONCILE_HINT,
100
+ };
101
+ };