pumuki 6.3.113 → 6.3.114
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +51 -5
- package/README.md +4 -2
- package/VERSION +1 -1
- package/core/facts/detectors/typescript/index.test.ts +0 -229
- package/core/facts/detectors/typescript/index.ts +0 -278
- package/core/facts/extractHeuristicFacts.ts +0 -4
- package/core/rules/presets/heuristics/typescript.test.ts +1 -21
- package/core/rules/presets/heuristics/typescript.ts +0 -72
- package/docs/README.md +13 -9
- package/docs/codex-skills/backend-enterprise-rules.md +3 -3
- package/docs/operations/RELEASE_NOTES.md +40 -4
- package/docs/product/API_REFERENCE.md +1 -1
- package/docs/product/HOW_IT_WORKS.md +6 -0
- package/docs/product/INSTALLATION.md +1 -1
- package/docs/product/USAGE.md +42 -5
- package/docs/tracking/plan-curso-pumuki-stack-my-architecture.md +100 -44
- package/docs/validation/README.md +6 -3
- package/integrations/config/skillsDetectorRegistry.ts +0 -24
- package/integrations/config/skillsMarkdownRules.ts +0 -57
- package/integrations/evidence/buildEvidence.ts +0 -24
- package/integrations/evidence/repoState.ts +0 -3
- package/integrations/evidence/schema.ts +0 -18
- package/integrations/evidence/writeEvidence.ts +0 -24
- package/integrations/gate/evaluateAiGate.ts +8 -251
- package/integrations/gate/remediationCatalog.ts +0 -8
- package/integrations/git/GitService.ts +44 -5
- package/integrations/git/aiGateRepoPolicyFindings.ts +86 -17
- package/integrations/git/runPlatformGate.ts +1 -9
- package/integrations/git/runPlatformGateFacts.ts +19 -1
- package/integrations/git/runPlatformGateOutput.ts +41 -42
- package/integrations/lifecycle/adapter.templates.json +1 -0
- package/integrations/lifecycle/adapter.ts +0 -24
- package/integrations/lifecycle/audit.ts +101 -0
- package/integrations/lifecycle/cli.ts +120 -99
- package/integrations/lifecycle/cliSdd.ts +4 -26
- package/integrations/lifecycle/doctor.ts +40 -102
- package/integrations/lifecycle/index.ts +2 -0
- package/integrations/lifecycle/install.ts +0 -21
- package/integrations/lifecycle/packageInfo.ts +1 -118
- package/integrations/lifecycle/state.ts +1 -8
- package/integrations/lifecycle/status.ts +40 -59
- package/integrations/lifecycle/watch.ts +1 -1
- package/integrations/mcp/aiGateCheck.ts +10 -194
- package/integrations/mcp/autoExecuteAiStart.ts +116 -92
- package/integrations/mcp/enterpriseServer.ts +7 -23
- package/integrations/mcp/enterpriseStdioServer.cli.ts +4 -31
- package/integrations/mcp/preFlightCheck.ts +5 -67
- package/integrations/platform/detectPlatforms.ts +37 -0
- package/integrations/sdd/policy.ts +28 -20
- package/package.json +1 -1
- package/scripts/check-tracking-single-active.sh +1 -1
- package/scripts/consumer-menu-matrix-baseline-report-lib.ts +13 -38
- package/scripts/consumer-postinstall-resolve-args.cjs +44 -0
- package/scripts/consumer-postinstall.cjs +76 -21
- package/scripts/framework-menu-advanced-view-lib.ts +0 -49
- package/scripts/framework-menu-consumer-actions-lib.ts +28 -4
- package/scripts/framework-menu-consumer-preflight-hints.ts +5 -2
- package/scripts/framework-menu-consumer-preflight-render.ts +0 -10
- package/scripts/framework-menu-consumer-preflight-run.ts +0 -23
- package/scripts/framework-menu-consumer-preflight-types.ts +0 -12
- package/scripts/framework-menu-consumer-runtime-actions.ts +87 -17
- package/scripts/framework-menu-consumer-runtime-audit.ts +36 -2
- package/scripts/framework-menu-consumer-runtime-evidence-classic.ts +140 -0
- package/scripts/framework-menu-consumer-runtime-lib.ts +2 -38
- package/scripts/framework-menu-consumer-runtime-menu.ts +4 -31
- package/scripts/framework-menu-consumer-runtime-types.ts +3 -5
- package/scripts/framework-menu-evidence-summary-lib.ts +1 -0
- package/scripts/framework-menu-evidence-summary-read.ts +57 -5
- package/scripts/framework-menu-evidence-summary-severity.ts +3 -1
- package/scripts/framework-menu-evidence-summary-types.ts +7 -0
- package/scripts/framework-menu-gate-lib.ts +9 -0
- package/scripts/framework-menu-layout-data.ts +5 -0
- package/scripts/framework-menu-matrix-baseline-lib.ts +15 -14
- package/scripts/framework-menu-matrix-canary-lib.ts +22 -1
- package/scripts/framework-menu-matrix-evidence-lib.ts +1 -0
- package/scripts/framework-menu-matrix-evidence-types.ts +13 -1
- package/scripts/framework-menu-matrix-runner-lib.ts +35 -0
- package/scripts/framework-menu-system-notifications-cause.ts +0 -3
- package/scripts/framework-menu-system-notifications-macos-swift-source.ts +24 -204
- package/scripts/framework-menu-system-notifications-macos.ts +4 -0
- package/scripts/framework-menu-system-notifications-payloads-blocked.ts +1 -1
- package/scripts/framework-menu-system-notifications-text.ts +1 -7
- package/scripts/framework-menu.ts +3 -24
- package/scripts/package-install-smoke-consumer-git-repo-lib.ts +1 -10
- package/scripts/package-install-smoke-consumer-npm-lib.ts +9 -46
- package/scripts/pumuki-full-surface-smoke-lib.ts +37 -0
- package/scripts/pumuki-full-surface-smoke.ts +346 -0
- package/scripts/pumuki-smoke-installed-wrapper.cjs +31 -0
- package/integrations/evidence/trackingContract.ts +0 -17
- package/integrations/gate/governanceActionCatalog.ts +0 -275
- package/integrations/lifecycle/bootstrapManifest.ts +0 -248
- package/integrations/lifecycle/cliGovernanceConsole.ts +0 -69
- package/integrations/lifecycle/governanceNextAction.ts +0 -171
- package/integrations/lifecycle/governanceObservationSnapshot.ts +0 -369
- package/integrations/lifecycle/trackingState.ts +0 -403
- package/integrations/mcp/alignedPlatformGate.ts +0 -232
- package/integrations/mcp/readMcpPrePushStdin.ts +0 -7
- package/scripts/build-ruralgo-s1-evidence-pack.ts +0 -85
- package/scripts/ruralgo-s1-evidence-pack-lib.ts +0 -200
|
@@ -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(
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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) =>
|
|
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
|
|
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
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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) =>
|
|
120
|
+
(finding) =>
|
|
121
|
+
finding !== primary &&
|
|
122
|
+
severityWeight(finding.severity) < severityWeight(primary.severity)
|
|
124
123
|
);
|
|
125
|
-
for (const
|
|
124
|
+
for (const finding of secondaryWarnings) {
|
|
126
125
|
process.stdout.write(
|
|
127
|
-
`[pumuki][warning-summary] secondary=${
|
|
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) {
|
|
@@ -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
|
+
};
|