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.
- package/CHANGELOG.md +52 -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 +41 -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 +9 -7
- 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,40 +1,21 @@
|
|
|
1
1
|
import { existsSync, readFileSync, realpathSync } from 'node:fs';
|
|
2
2
|
import { join, resolve } from 'node:path';
|
|
3
3
|
import { readEvidenceResult } from '../evidence/readEvidence';
|
|
4
|
+
import { appendTrackingActionableContext } from '../git/aiGateRepoPolicyFindings';
|
|
4
5
|
import { resolvePolicyForStage } from '../gate/stagePolicies';
|
|
5
6
|
import { getPumukiHooksStatus, resolvePumukiHooksDirectory } from './hookManager';
|
|
6
7
|
import { LifecycleGitService, type ILifecycleGitService } from './gitService';
|
|
7
8
|
import { buildLifecycleVersionReport, getCurrentPumukiVersion } from './packageInfo';
|
|
8
|
-
import {
|
|
9
|
-
readLifecycleExperimentalFeaturesSnapshot,
|
|
10
|
-
type LifecycleExperimentalFeaturesSnapshot,
|
|
11
|
-
} from './experimentalFeaturesSnapshot';
|
|
12
9
|
import {
|
|
13
10
|
readLifecyclePolicyValidationSnapshot,
|
|
14
11
|
type LifecyclePolicyValidationSnapshot,
|
|
15
12
|
} from './policyValidationSnapshot';
|
|
16
|
-
import {
|
|
17
|
-
doctorGovernanceIsBlocking,
|
|
18
|
-
doctorGovernanceNeedsAttention,
|
|
19
|
-
readGovernanceObservationSnapshot,
|
|
20
|
-
type GovernanceObservationSnapshot,
|
|
21
|
-
} from './governanceObservationSnapshot';
|
|
22
|
-
import {
|
|
23
|
-
readGovernanceNextAction,
|
|
24
|
-
type GovernanceNextActionReader,
|
|
25
|
-
type GovernanceNextActionSummary,
|
|
26
|
-
} from './governanceNextAction';
|
|
27
13
|
import { readLifecycleState, type LifecycleState } from './state';
|
|
28
14
|
import {
|
|
29
15
|
detectOpenSpecInstallation,
|
|
30
16
|
evaluateOpenSpecCompatibility,
|
|
31
17
|
isOpenSpecProjectInitialized,
|
|
32
18
|
} from '../sdd/openSpecCli';
|
|
33
|
-
import {
|
|
34
|
-
formatTrackingActionableContext,
|
|
35
|
-
resolveRepoTrackingState,
|
|
36
|
-
type RepoTrackingState,
|
|
37
|
-
} from './trackingState';
|
|
38
19
|
|
|
39
20
|
export type DoctorIssueSeverity = 'warning' | 'error';
|
|
40
21
|
|
|
@@ -115,10 +96,7 @@ export type LifecycleDoctorReport = {
|
|
|
115
96
|
hooksDirectory: string;
|
|
116
97
|
hooksDirectoryResolution: 'git-rev-parse' | 'git-config' | 'default';
|
|
117
98
|
policyValidation: LifecyclePolicyValidationSnapshot;
|
|
118
|
-
|
|
119
|
-
governanceObservation: GovernanceObservationSnapshot;
|
|
120
|
-
governanceNextAction: GovernanceNextActionSummary;
|
|
121
|
-
tracking: RepoTrackingState;
|
|
99
|
+
policy_signature_remediation?: string;
|
|
122
100
|
issues: ReadonlyArray<DoctorIssue>;
|
|
123
101
|
deep?: DoctorDeepReport;
|
|
124
102
|
parity_profile?: DoctorParityProfile;
|
|
@@ -126,13 +104,14 @@ export type LifecycleDoctorReport = {
|
|
|
126
104
|
};
|
|
127
105
|
|
|
128
106
|
const buildDoctorIssues = (params: {
|
|
107
|
+
repoRoot: string;
|
|
129
108
|
trackedNodeModulesPaths: ReadonlyArray<string>;
|
|
130
109
|
hookStatus: ReturnType<typeof getPumukiHooksStatus>;
|
|
131
110
|
hooksDirectory: string;
|
|
132
111
|
lifecycleState: LifecycleState;
|
|
133
|
-
tracking: RepoTrackingState;
|
|
134
112
|
}): ReadonlyArray<DoctorIssue> => {
|
|
135
113
|
const issues: DoctorIssue[] = [];
|
|
114
|
+
const evidenceResult = readEvidenceResult(params.repoRoot);
|
|
136
115
|
|
|
137
116
|
if (params.trackedNodeModulesPaths.length > 0) {
|
|
138
117
|
issues.push({
|
|
@@ -170,25 +149,29 @@ const buildDoctorIssues = (params: {
|
|
|
170
149
|
});
|
|
171
150
|
}
|
|
172
151
|
|
|
173
|
-
if (
|
|
174
|
-
|
|
152
|
+
if (evidenceResult.kind === 'valid') {
|
|
153
|
+
const evidence = evidenceResult.evidence;
|
|
154
|
+
const blocked =
|
|
155
|
+
evidence.snapshot.outcome === 'BLOCK' ||
|
|
156
|
+
evidence.ai_gate.status === 'BLOCKED' ||
|
|
157
|
+
evidence.severity_metrics.gate_status === 'BLOCKED';
|
|
158
|
+
if (blocked) {
|
|
159
|
+
const blockedStage = evidence?.snapshot?.stage ?? 'PRE_WRITE';
|
|
175
160
|
issues.push({
|
|
176
|
-
severity: '
|
|
177
|
-
message:
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
issues.push({
|
|
182
|
-
severity: 'warning',
|
|
183
|
-
message:
|
|
184
|
-
`Tracking contract has conflicting canonical declarations (${params.tracking.declarations.map((declaration) => declaration.resolved_path).join(', ')}).`,
|
|
161
|
+
severity: 'error',
|
|
162
|
+
message: appendTrackingActionableContext({
|
|
163
|
+
repoRoot: params.repoRoot,
|
|
164
|
+
message: `Governance is blocked (${blockedStage}).`,
|
|
165
|
+
}),
|
|
185
166
|
});
|
|
186
|
-
} else if (
|
|
187
|
-
const
|
|
167
|
+
} else if (evidence.snapshot.outcome === 'WARN') {
|
|
168
|
+
const warnStage = evidence?.snapshot?.stage ?? 'PRE_WRITE';
|
|
188
169
|
issues.push({
|
|
189
170
|
severity: 'warning',
|
|
190
|
-
message:
|
|
191
|
-
|
|
171
|
+
message: appendTrackingActionableContext({
|
|
172
|
+
repoRoot: params.repoRoot,
|
|
173
|
+
message: `Governance requires attention (${warnStage}).`,
|
|
174
|
+
}),
|
|
192
175
|
});
|
|
193
176
|
}
|
|
194
177
|
}
|
|
@@ -196,28 +179,6 @@ const buildDoctorIssues = (params: {
|
|
|
196
179
|
return issues;
|
|
197
180
|
};
|
|
198
181
|
|
|
199
|
-
const appendGovernanceBlockingIssue = (params: {
|
|
200
|
-
issues: ReadonlyArray<DoctorIssue>;
|
|
201
|
-
governanceObservation: GovernanceObservationSnapshot;
|
|
202
|
-
governanceNextAction: GovernanceNextActionSummary;
|
|
203
|
-
}): ReadonlyArray<DoctorIssue> => {
|
|
204
|
-
if (!doctorGovernanceIsBlocking(params.governanceObservation)) {
|
|
205
|
-
return params.issues;
|
|
206
|
-
}
|
|
207
|
-
if (params.issues.some((issue) => issue.severity === 'error')) {
|
|
208
|
-
return params.issues;
|
|
209
|
-
}
|
|
210
|
-
return [
|
|
211
|
-
...params.issues,
|
|
212
|
-
{
|
|
213
|
-
severity: 'error',
|
|
214
|
-
message:
|
|
215
|
-
`Governance is blocked (${params.governanceNextAction.reason_code}). ` +
|
|
216
|
-
params.governanceNextAction.instruction,
|
|
217
|
-
},
|
|
218
|
-
];
|
|
219
|
-
};
|
|
220
|
-
|
|
221
182
|
const DEEP_EVIDENCE_MAX_AGE_SECONDS = 1800;
|
|
222
183
|
|
|
223
184
|
const toCanonicalPath = (value: string): string => {
|
|
@@ -862,12 +823,20 @@ const compareDoctorParityProfile = (params: {
|
|
|
862
823
|
};
|
|
863
824
|
};
|
|
864
825
|
|
|
826
|
+
const buildPolicySignatureRemediation = (
|
|
827
|
+
policyValidation: LifecyclePolicyValidationSnapshot
|
|
828
|
+
): string | undefined => {
|
|
829
|
+
const mismatch = Object.values(policyValidation.stages).some(
|
|
830
|
+
(stage) => stage.validationCode === 'POLICY_AS_CODE_SIGNATURE_MISMATCH'
|
|
831
|
+
);
|
|
832
|
+
return mismatch ? 'pumuki policy reconcile --apply' : undefined;
|
|
833
|
+
};
|
|
834
|
+
|
|
865
835
|
export const runLifecycleDoctor = (params?: {
|
|
866
836
|
cwd?: string;
|
|
867
837
|
git?: ILifecycleGitService;
|
|
868
838
|
deep?: boolean;
|
|
869
839
|
parity?: boolean;
|
|
870
|
-
governanceNextActionReader?: GovernanceNextActionReader;
|
|
871
840
|
}): LifecycleDoctorReport => {
|
|
872
841
|
const git = params?.git ?? new LifecycleGitService();
|
|
873
842
|
const cwd = params?.cwd ?? process.cwd();
|
|
@@ -876,14 +845,13 @@ export const runLifecycleDoctor = (params?: {
|
|
|
876
845
|
const hooksDirectory = resolvePumukiHooksDirectory(repoRoot);
|
|
877
846
|
const hookStatus = getPumukiHooksStatus(repoRoot);
|
|
878
847
|
const lifecycleState = readLifecycleState(git, repoRoot);
|
|
879
|
-
const tracking = resolveRepoTrackingState(repoRoot);
|
|
880
848
|
|
|
881
849
|
const issues = buildDoctorIssues({
|
|
850
|
+
repoRoot,
|
|
882
851
|
trackedNodeModulesPaths,
|
|
883
852
|
hookStatus,
|
|
884
853
|
hooksDirectory: hooksDirectory.path,
|
|
885
854
|
lifecycleState,
|
|
886
|
-
tracking,
|
|
887
855
|
});
|
|
888
856
|
const deep = params?.deep
|
|
889
857
|
? buildDoctorDeepReport({
|
|
@@ -897,25 +865,6 @@ export const runLifecycleDoctor = (params?: {
|
|
|
897
865
|
repoRoot,
|
|
898
866
|
lifecycleVersion: lifecycleState.version,
|
|
899
867
|
});
|
|
900
|
-
const policyValidation = readLifecyclePolicyValidationSnapshot(repoRoot);
|
|
901
|
-
const experimentalFeatures = readLifecycleExperimentalFeaturesSnapshot();
|
|
902
|
-
const governanceObservation = readGovernanceObservationSnapshot({
|
|
903
|
-
repoRoot,
|
|
904
|
-
experimentalFeatures,
|
|
905
|
-
policyValidation,
|
|
906
|
-
git,
|
|
907
|
-
});
|
|
908
|
-
const governanceStage = governanceObservation.evidence.snapshot_stage ?? 'PRE_WRITE';
|
|
909
|
-
const governanceNextAction = (params?.governanceNextActionReader ?? readGovernanceNextAction)({
|
|
910
|
-
repoRoot,
|
|
911
|
-
stage: governanceStage,
|
|
912
|
-
governanceObservation,
|
|
913
|
-
});
|
|
914
|
-
const canonicalIssues = appendGovernanceBlockingIssue({
|
|
915
|
-
issues,
|
|
916
|
-
governanceObservation,
|
|
917
|
-
governanceNextAction,
|
|
918
|
-
});
|
|
919
868
|
|
|
920
869
|
const parity_profile =
|
|
921
870
|
params?.parity === true
|
|
@@ -929,6 +878,9 @@ export const runLifecycleDoctor = (params?: {
|
|
|
929
878
|
? compareDoctorParityProfile({ repoRoot, actual: parity_profile })
|
|
930
879
|
: undefined;
|
|
931
880
|
|
|
881
|
+
const policyValidation = readLifecyclePolicyValidationSnapshot(repoRoot);
|
|
882
|
+
const policySignatureRemediation = buildPolicySignatureRemediation(policyValidation);
|
|
883
|
+
|
|
932
884
|
return {
|
|
933
885
|
repoRoot,
|
|
934
886
|
packageVersion: version.effective,
|
|
@@ -939,11 +891,10 @@ export const runLifecycleDoctor = (params?: {
|
|
|
939
891
|
hooksDirectory: hooksDirectory.path,
|
|
940
892
|
hooksDirectoryResolution: hooksDirectory.source,
|
|
941
893
|
policyValidation,
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
issues: canonicalIssues,
|
|
894
|
+
...(policySignatureRemediation
|
|
895
|
+
? { policy_signature_remediation: policySignatureRemediation }
|
|
896
|
+
: {}),
|
|
897
|
+
issues,
|
|
947
898
|
deep,
|
|
948
899
|
parity_profile,
|
|
949
900
|
parity_comparison,
|
|
@@ -955,16 +906,3 @@ export const doctorHasBlockingIssues = (report: LifecycleDoctorReport): boolean
|
|
|
955
906
|
|
|
956
907
|
export const doctorHasParityMismatch = (report: LifecycleDoctorReport): boolean =>
|
|
957
908
|
typeof report.parity_comparison !== 'undefined' && report.parity_comparison.matches === false;
|
|
958
|
-
|
|
959
|
-
export const doctorHasGovernanceAttention = (report: LifecycleDoctorReport): boolean =>
|
|
960
|
-
doctorGovernanceNeedsAttention(report.governanceObservation);
|
|
961
|
-
|
|
962
|
-
export const doctorCommandShouldWarnHuman = (report: LifecycleDoctorReport): boolean =>
|
|
963
|
-
report.issues.length > 0
|
|
964
|
-
|| report.deep?.checks.some((check) => check.status !== 'pass') === true
|
|
965
|
-
|| doctorHasGovernanceAttention(report);
|
|
966
|
-
|
|
967
|
-
export const doctorCommandShouldFailExit = (report: LifecycleDoctorReport): boolean =>
|
|
968
|
-
doctorHasBlockingIssues(report)
|
|
969
|
-
|| doctorHasParityMismatch(report)
|
|
970
|
-
|| doctorGovernanceIsBlocking(report.governanceObservation);
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { runLifecycleDoctor, doctorHasBlockingIssues } from './doctor';
|
|
2
|
+
export { runLifecycleAudit } from './audit';
|
|
3
|
+
export type { LifecycleAuditResult, LifecycleAuditStage } from './audit';
|
|
2
4
|
export { runLifecycleInstall } from './install';
|
|
3
5
|
export { runLifecycleUninstall } from './uninstall';
|
|
4
6
|
export { runLifecycleRemove } from './remove';
|
|
@@ -13,7 +13,6 @@ import { createEmptyEvaluationMetrics } from '../evidence/evaluationMetrics';
|
|
|
13
13
|
import { readOpenSpecManagedArtifacts, writeLifecycleState } from './state';
|
|
14
14
|
import { ensureRuntimeArtifactsIgnored } from './artifacts';
|
|
15
15
|
import { runLifecycleAdapterInstall } from './adapter';
|
|
16
|
-
import { writeLifecycleBootstrapManifest } from './bootstrapManifest';
|
|
17
16
|
import { runPolicyReconcile } from './policyReconcile';
|
|
18
17
|
|
|
19
18
|
export type LifecycleInstallResult = {
|
|
@@ -22,10 +21,6 @@ export type LifecycleInstallResult = {
|
|
|
22
21
|
changedHooks: ReadonlyArray<string>;
|
|
23
22
|
openSpecBootstrap?: OpenSpecBootstrapResult;
|
|
24
23
|
degradedDoctorBypass?: boolean;
|
|
25
|
-
bootstrapManifest: {
|
|
26
|
-
path: string;
|
|
27
|
-
changed: boolean;
|
|
28
|
-
};
|
|
29
24
|
};
|
|
30
25
|
|
|
31
26
|
const shouldBootstrapEvidence = (repoRoot: string): boolean =>
|
|
@@ -130,20 +125,12 @@ export const runLifecycleInstall = (params?: {
|
|
|
130
125
|
});
|
|
131
126
|
ensureRepoBaselineAdapter(report.repoRoot);
|
|
132
127
|
materializeStrictPolicyAsCode(report.repoRoot);
|
|
133
|
-
const bootstrapManifest = writeLifecycleBootstrapManifest({
|
|
134
|
-
git,
|
|
135
|
-
repoRoot: report.repoRoot,
|
|
136
|
-
});
|
|
137
128
|
return {
|
|
138
129
|
repoRoot: report.repoRoot,
|
|
139
130
|
version,
|
|
140
131
|
changedHooks,
|
|
141
132
|
openSpecBootstrap: undefined,
|
|
142
133
|
degradedDoctorBypass: true,
|
|
143
|
-
bootstrapManifest: {
|
|
144
|
-
path: bootstrapManifest.path,
|
|
145
|
-
changed: bootstrapManifest.changed,
|
|
146
|
-
},
|
|
147
134
|
};
|
|
148
135
|
}
|
|
149
136
|
const renderedIssues = report.issues.map((issue) => `- [${issue.severity}] ${issue.message}`).join('\n');
|
|
@@ -178,19 +165,11 @@ export const runLifecycleInstall = (params?: {
|
|
|
178
165
|
});
|
|
179
166
|
ensureRepoBaselineAdapter(report.repoRoot);
|
|
180
167
|
materializeStrictPolicyAsCode(report.repoRoot);
|
|
181
|
-
const bootstrapManifest = writeLifecycleBootstrapManifest({
|
|
182
|
-
git,
|
|
183
|
-
repoRoot: report.repoRoot,
|
|
184
|
-
});
|
|
185
168
|
|
|
186
169
|
return {
|
|
187
170
|
repoRoot: report.repoRoot,
|
|
188
171
|
version,
|
|
189
172
|
changedHooks,
|
|
190
173
|
openSpecBootstrap,
|
|
191
|
-
bootstrapManifest: {
|
|
192
|
-
path: bootstrapManifest.path,
|
|
193
|
-
changed: bootstrapManifest.changed,
|
|
194
|
-
},
|
|
195
174
|
};
|
|
196
175
|
};
|
|
@@ -49,126 +49,15 @@ const hasPathExecutionHazard = (repoRoot?: string): boolean =>
|
|
|
49
49
|
repoRoot.trim().length > 0 &&
|
|
50
50
|
repoRoot.includes(delimiter);
|
|
51
51
|
|
|
52
|
-
type ConsumerNodeRuntimeSpec = {
|
|
53
|
-
version: string;
|
|
54
|
-
source: 'volta' | '.nvmrc' | 'package.engines';
|
|
55
|
-
commandPrefix: 'volta' | 'nvm';
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
const normalizeNodeVersionToken = (value: string): string =>
|
|
59
|
-
value.trim().replace(/^node@/i, '').replace(/^v/i, '');
|
|
60
|
-
|
|
61
|
-
const extractNodeVersionToken = (value: string): string | null => {
|
|
62
|
-
const normalized = normalizeNodeVersionToken(value);
|
|
63
|
-
const exactVersion = normalized.match(/\d+\.\d+\.\d+/)?.[0];
|
|
64
|
-
if (exactVersion) {
|
|
65
|
-
return exactVersion;
|
|
66
|
-
}
|
|
67
|
-
const majorOnly = normalized.match(/^\d+$/)?.[0];
|
|
68
|
-
return majorOnly ?? null;
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
72
|
-
typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
73
|
-
|
|
74
|
-
const readNestedString = (
|
|
75
|
-
source: Record<string, unknown>,
|
|
76
|
-
path: ReadonlyArray<string>
|
|
77
|
-
): string | undefined => {
|
|
78
|
-
let cursor: unknown = source;
|
|
79
|
-
for (const segment of path) {
|
|
80
|
-
if (!isRecord(cursor)) {
|
|
81
|
-
return undefined;
|
|
82
|
-
}
|
|
83
|
-
cursor = cursor[segment];
|
|
84
|
-
}
|
|
85
|
-
return typeof cursor === 'string' && cursor.trim().length > 0 ? cursor.trim() : undefined;
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
const readConsumerNodeRuntimeSpec = (repoRoot?: string): ConsumerNodeRuntimeSpec | null => {
|
|
89
|
-
if (typeof repoRoot !== 'string' || repoRoot.trim().length === 0) {
|
|
90
|
-
return null;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const packageJsonPath = join(repoRoot, 'package.json');
|
|
94
|
-
if (existsSync(packageJsonPath)) {
|
|
95
|
-
try {
|
|
96
|
-
const parsed = JSON.parse(readFileSync(packageJsonPath, 'utf8')) as unknown;
|
|
97
|
-
if (isRecord(parsed)) {
|
|
98
|
-
const voltaNode = readNestedString(parsed, ['volta', 'node']);
|
|
99
|
-
const voltaVersion = voltaNode ? extractNodeVersionToken(voltaNode) : null;
|
|
100
|
-
if (voltaVersion) {
|
|
101
|
-
return {
|
|
102
|
-
version: voltaVersion,
|
|
103
|
-
source: 'volta',
|
|
104
|
-
commandPrefix: 'volta',
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const enginesNode = readNestedString(parsed, ['engines', 'node']);
|
|
109
|
-
const enginesVersion = enginesNode ? extractNodeVersionToken(enginesNode) : null;
|
|
110
|
-
if (enginesVersion) {
|
|
111
|
-
return {
|
|
112
|
-
version: enginesVersion,
|
|
113
|
-
source: 'package.engines',
|
|
114
|
-
commandPrefix: 'nvm',
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
} catch {
|
|
119
|
-
return null;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const nvmrcPath = join(repoRoot, '.nvmrc');
|
|
124
|
-
if (existsSync(nvmrcPath)) {
|
|
125
|
-
try {
|
|
126
|
-
const nvmrcVersion = extractNodeVersionToken(readFileSync(nvmrcPath, 'utf8'));
|
|
127
|
-
if (nvmrcVersion) {
|
|
128
|
-
return {
|
|
129
|
-
version: nvmrcVersion,
|
|
130
|
-
source: '.nvmrc',
|
|
131
|
-
commandPrefix: 'nvm',
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
} catch {
|
|
135
|
-
return null;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
return null;
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
const buildConsumerNodeAlignmentCommand = (repoRoot?: string): string | null => {
|
|
143
|
-
const runtimeSpec = readConsumerNodeRuntimeSpec(repoRoot);
|
|
144
|
-
if (!runtimeSpec) {
|
|
145
|
-
return null;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const currentNodeVersion = normalizeNodeVersionToken(process.version);
|
|
149
|
-
if (currentNodeVersion === runtimeSpec.version) {
|
|
150
|
-
return null;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
if (runtimeSpec.commandPrefix === 'volta') {
|
|
154
|
-
return `volta install node@${runtimeSpec.version} && volta pin node@${runtimeSpec.version}`;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
return `nvm install ${runtimeSpec.version} && nvm use ${runtimeSpec.version}`;
|
|
158
|
-
};
|
|
159
|
-
|
|
160
52
|
export const buildLifecycleAlignmentCommand = (
|
|
161
53
|
runtimeVersion: string,
|
|
162
54
|
repoRoot?: string
|
|
163
55
|
): string => {
|
|
164
|
-
const consumerNodeAlignmentCommand = buildConsumerNodeAlignmentCommand(repoRoot);
|
|
165
56
|
const installStep = `npm install --save-exact pumuki@${runtimeVersion}`;
|
|
166
57
|
const runStep = hasPathExecutionHazard(repoRoot)
|
|
167
58
|
? `${buildLocalPumukiCommand()} install`
|
|
168
59
|
: `npx --yes --package pumuki@${runtimeVersion} pumuki install`;
|
|
169
|
-
return
|
|
170
|
-
.filter((value): value is string => typeof value === 'string' && value.length > 0)
|
|
171
|
-
.join(' && ');
|
|
60
|
+
return `${installStep} && ${runStep}`;
|
|
172
61
|
};
|
|
173
62
|
|
|
174
63
|
export const resolvePumukiVersionMetadata = (params?: { repoRoot?: string }): PumukiVersionMetadata => {
|
|
@@ -225,17 +114,11 @@ export const buildLifecycleVersionReport = (params?: {
|
|
|
225
114
|
const driftFromConsumerInstalled =
|
|
226
115
|
metadata.consumerInstalledVersion !== null &&
|
|
227
116
|
metadata.consumerInstalledVersion !== metadata.runtimeVersion;
|
|
228
|
-
const consumerNodeSpec = readConsumerNodeRuntimeSpec(params?.repoRoot);
|
|
229
|
-
const consumerNodeVersion = consumerNodeSpec?.version ?? null;
|
|
230
|
-
const currentNodeVersion = normalizeNodeVersionToken(process.version);
|
|
231
|
-
const driftFromConsumerNode =
|
|
232
|
-
consumerNodeVersion !== null && currentNodeVersion !== consumerNodeVersion;
|
|
233
117
|
const driftFromLifecycleInstalled =
|
|
234
118
|
lifecycleInstalled !== null && metadata.resolvedVersion !== lifecycleInstalled;
|
|
235
119
|
const driftTargets = [
|
|
236
120
|
driftFromRuntime ? `runtime=${metadata.runtimeVersion}` : null,
|
|
237
121
|
driftFromConsumerInstalled ? `consumer=${metadata.consumerInstalledVersion}` : null,
|
|
238
|
-
driftFromConsumerNode ? `node=${consumerNodeVersion}` : null,
|
|
239
122
|
driftFromLifecycleInstalled ? `lifecycle=${lifecycleInstalled}` : null,
|
|
240
123
|
].filter((value): value is string => value !== null);
|
|
241
124
|
const pathExecutionHazard = hasPathExecutionHazard(params?.repoRoot);
|
|
@@ -49,17 +49,10 @@ export const writeLifecycleState = (params: {
|
|
|
49
49
|
openSpecManagedArtifacts?: ReadonlyArray<string>;
|
|
50
50
|
}): void => {
|
|
51
51
|
const { git, repoRoot, version } = params;
|
|
52
|
-
const existingInstalledAt = git.localConfig(repoRoot, PUMUKI_CONFIG_KEYS.installedAt);
|
|
53
52
|
git.applyLocalConfig(repoRoot, PUMUKI_CONFIG_KEYS.installed, 'true');
|
|
54
53
|
git.applyLocalConfig(repoRoot, PUMUKI_CONFIG_KEYS.version, version);
|
|
55
54
|
git.applyLocalConfig(repoRoot, PUMUKI_CONFIG_KEYS.hooks, PUMUKI_MANAGED_HOOKS.join(','));
|
|
56
|
-
git.applyLocalConfig(
|
|
57
|
-
repoRoot,
|
|
58
|
-
PUMUKI_CONFIG_KEYS.installedAt,
|
|
59
|
-
typeof existingInstalledAt === 'string' && existingInstalledAt.trim().length > 0
|
|
60
|
-
? existingInstalledAt
|
|
61
|
-
: new Date().toISOString()
|
|
62
|
-
);
|
|
55
|
+
git.applyLocalConfig(repoRoot, PUMUKI_CONFIG_KEYS.installedAt, new Date().toISOString());
|
|
63
56
|
if (params.openSpecManagedArtifacts) {
|
|
64
57
|
const serialized = serializeManagedArtifacts(params.openSpecManagedArtifacts);
|
|
65
58
|
if (serialized) {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { getPumukiHooksStatus, resolvePumukiHooksDirectory } from './hookManager';
|
|
2
2
|
import { LifecycleGitService, type ILifecycleGitService } from './gitService';
|
|
3
3
|
import { buildLifecycleVersionReport } from './packageInfo';
|
|
4
|
+
import { readEvidenceResult } from '../evidence/readEvidence';
|
|
5
|
+
import { appendTrackingActionableContext } from '../git/aiGateRepoPolicyFindings';
|
|
4
6
|
import {
|
|
5
7
|
readLifecycleExperimentalFeaturesSnapshot,
|
|
6
8
|
type LifecycleExperimentalFeaturesSnapshot,
|
|
@@ -9,23 +11,8 @@ import {
|
|
|
9
11
|
readLifecyclePolicyValidationSnapshot,
|
|
10
12
|
type LifecyclePolicyValidationSnapshot,
|
|
11
13
|
} from './policyValidationSnapshot';
|
|
12
|
-
import {
|
|
13
|
-
readGovernanceObservationSnapshot,
|
|
14
|
-
doctorGovernanceIsBlocking,
|
|
15
|
-
type GovernanceObservationSnapshot,
|
|
16
|
-
} from './governanceObservationSnapshot';
|
|
17
|
-
import {
|
|
18
|
-
readGovernanceNextAction,
|
|
19
|
-
type GovernanceNextActionReader,
|
|
20
|
-
type GovernanceNextActionSummary,
|
|
21
|
-
} from './governanceNextAction';
|
|
22
|
-
import type { DoctorIssue } from './doctor';
|
|
23
14
|
import { readLifecycleState, type LifecycleState } from './state';
|
|
24
|
-
import {
|
|
25
|
-
formatTrackingActionableContext,
|
|
26
|
-
resolveRepoTrackingState,
|
|
27
|
-
type RepoTrackingState,
|
|
28
|
-
} from './trackingState';
|
|
15
|
+
import type { DoctorIssue } from './doctor';
|
|
29
16
|
|
|
30
17
|
export type LifecycleStatus = {
|
|
31
18
|
repoRoot: string;
|
|
@@ -38,44 +25,54 @@ export type LifecycleStatus = {
|
|
|
38
25
|
trackedNodeModulesCount: number;
|
|
39
26
|
policyValidation: LifecyclePolicyValidationSnapshot;
|
|
40
27
|
experimentalFeatures: LifecycleExperimentalFeaturesSnapshot;
|
|
41
|
-
governanceObservation: GovernanceObservationSnapshot;
|
|
42
|
-
governanceNextAction: GovernanceNextActionSummary;
|
|
43
|
-
tracking: RepoTrackingState;
|
|
44
28
|
issues: ReadonlyArray<DoctorIssue>;
|
|
45
29
|
};
|
|
46
30
|
|
|
47
|
-
const buildLifecycleIssues = (
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}): ReadonlyArray<DoctorIssue> => {
|
|
52
|
-
const issues: DoctorIssue[] = [];
|
|
53
|
-
|
|
54
|
-
if (doctorGovernanceIsBlocking(params.governanceObservation)) {
|
|
55
|
-
issues.push({
|
|
56
|
-
severity: 'error',
|
|
57
|
-
message:
|
|
58
|
-
`Governance is blocked (${params.governanceNextAction.reason_code}). ` +
|
|
59
|
-
params.governanceNextAction.instruction,
|
|
60
|
-
});
|
|
31
|
+
const buildLifecycleIssues = (repoRoot: string): ReadonlyArray<DoctorIssue> => {
|
|
32
|
+
const evidenceResult = readEvidenceResult(repoRoot);
|
|
33
|
+
if (evidenceResult.kind !== 'valid') {
|
|
34
|
+
return [];
|
|
61
35
|
}
|
|
62
36
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
37
|
+
const evidence = evidenceResult.evidence;
|
|
38
|
+
const blocked =
|
|
39
|
+
evidence.snapshot.outcome === 'BLOCK' ||
|
|
40
|
+
evidence.ai_gate.status === 'BLOCKED' ||
|
|
41
|
+
evidence.severity_metrics.gate_status === 'BLOCKED';
|
|
42
|
+
|
|
43
|
+
if (!blocked) {
|
|
44
|
+
if (evidence.snapshot.outcome !== 'WARN') {
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const warnStage = evidence?.snapshot?.stage ?? 'PRE_WRITE';
|
|
49
|
+
return [
|
|
50
|
+
{
|
|
51
|
+
severity: 'warning',
|
|
52
|
+
message: appendTrackingActionableContext({
|
|
53
|
+
repoRoot,
|
|
54
|
+
message: `Governance requires attention (${warnStage}).`,
|
|
55
|
+
}),
|
|
56
|
+
},
|
|
57
|
+
];
|
|
70
58
|
}
|
|
71
59
|
|
|
72
|
-
|
|
60
|
+
const blockedStage = evidence?.snapshot?.stage ?? 'PRE_WRITE';
|
|
61
|
+
const message = appendTrackingActionableContext({
|
|
62
|
+
repoRoot,
|
|
63
|
+
message: `Governance is blocked (${blockedStage}).`,
|
|
64
|
+
});
|
|
65
|
+
return [
|
|
66
|
+
{
|
|
67
|
+
severity: 'error',
|
|
68
|
+
message,
|
|
69
|
+
},
|
|
70
|
+
];
|
|
73
71
|
};
|
|
74
72
|
|
|
75
73
|
export const readLifecycleStatus = (params?: {
|
|
76
74
|
cwd?: string;
|
|
77
75
|
git?: ILifecycleGitService;
|
|
78
|
-
governanceNextActionReader?: GovernanceNextActionReader;
|
|
79
76
|
}): LifecycleStatus => {
|
|
80
77
|
const git = params?.git ?? new LifecycleGitService();
|
|
81
78
|
const cwd = params?.cwd ?? process.cwd();
|
|
@@ -89,20 +86,7 @@ export const readLifecycleStatus = (params?: {
|
|
|
89
86
|
});
|
|
90
87
|
const policyValidation = readLifecyclePolicyValidationSnapshot(repoRoot);
|
|
91
88
|
const experimentalFeatures = readLifecycleExperimentalFeaturesSnapshot();
|
|
92
|
-
const
|
|
93
|
-
repoRoot,
|
|
94
|
-
experimentalFeatures,
|
|
95
|
-
policyValidation,
|
|
96
|
-
git,
|
|
97
|
-
});
|
|
98
|
-
const governanceStage = governanceObservation.evidence.snapshot_stage ?? 'PRE_WRITE';
|
|
99
|
-
const governanceNextAction = (params?.governanceNextActionReader ?? readGovernanceNextAction)({
|
|
100
|
-
repoRoot,
|
|
101
|
-
stage: governanceStage,
|
|
102
|
-
governanceObservation,
|
|
103
|
-
});
|
|
104
|
-
const tracking = resolveRepoTrackingState(repoRoot);
|
|
105
|
-
const issues = buildLifecycleIssues({ governanceObservation, governanceNextAction, tracking });
|
|
89
|
+
const issues = buildLifecycleIssues(repoRoot);
|
|
106
90
|
|
|
107
91
|
return {
|
|
108
92
|
repoRoot,
|
|
@@ -115,9 +99,6 @@ export const readLifecycleStatus = (params?: {
|
|
|
115
99
|
trackedNodeModulesCount,
|
|
116
100
|
policyValidation,
|
|
117
101
|
experimentalFeatures,
|
|
118
|
-
governanceNextAction,
|
|
119
|
-
governanceObservation,
|
|
120
|
-
tracking,
|
|
121
102
|
issues,
|
|
122
103
|
};
|
|
123
104
|
};
|
|
@@ -736,7 +736,7 @@ export const runLifecycleWatch = async (
|
|
|
736
736
|
driftFromRuntime,
|
|
737
737
|
driftWarning,
|
|
738
738
|
alignmentCommand: driftFromRuntime
|
|
739
|
-
? buildLifecycleAlignmentCommand(versionMetadata.runtimeVersion
|
|
739
|
+
? buildLifecycleAlignmentCommand(versionMetadata.runtimeVersion)
|
|
740
740
|
: null,
|
|
741
741
|
},
|
|
742
742
|
stage,
|