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,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
- experimentalFeatures: LifecycleExperimentalFeaturesSnapshot;
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 (params.tracking.enforced) {
174
- if (!params.tracking.canonical_path || !params.tracking.canonical_present) {
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: 'warning',
177
- message:
178
- 'Tracking contract is declared but the canonical tracking file is missing or unresolved.',
179
- });
180
- } else if (params.tracking.conflict) {
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 (!params.tracking.single_in_progress_valid) {
187
- const actionableContext = formatTrackingActionableContext(params.tracking);
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
- `Canonical tracking is inconsistent for ${params.tracking.canonical_path} (in_progress_count=${params.tracking.in_progress_count}, active_task=${params.tracking.active_task_id ?? 'unknown'}, last_run_status=${params.tracking.last_run_status ?? 'absent'}).${actionableContext ? ` ${actionableContext}` : ''}`,
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
- experimentalFeatures,
943
- governanceNextAction,
944
- governanceObservation,
945
- tracking,
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 [consumerNodeAlignmentCommand, installStep, runStep]
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 = (params: {
48
- governanceObservation: GovernanceObservationSnapshot;
49
- governanceNextAction: GovernanceNextActionSummary;
50
- tracking: RepoTrackingState;
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
- if (params.tracking.enforced && params.tracking.single_in_progress_valid === false) {
64
- const actionableContext = formatTrackingActionableContext(params.tracking);
65
- issues.push({
66
- severity: 'warning',
67
- message:
68
- `Canonical tracking is inconsistent for ${params.tracking.canonical_path ?? 'unknown'} (in_progress_count=${params.tracking.in_progress_count}, active_task=${params.tracking.active_task_id ?? 'unknown'}, last_run_status=${params.tracking.last_run_status ?? 'absent'}).${actionableContext ? ` ${actionableContext}` : ''}`,
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
- return issues;
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 governanceObservation = readGovernanceObservationSnapshot({
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, repoRoot)
739
+ ? buildLifecycleAlignmentCommand(versionMetadata.runtimeVersion)
740
740
  : null,
741
741
  },
742
742
  stage,