pumuki 6.3.72 → 6.3.75

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 (64) hide show
  1. package/docs/README.md +9 -7
  2. package/docs/operations/RELEASE_NOTES.md +0 -7
  3. package/docs/product/USAGE.md +2 -5
  4. package/docs/validation/README.md +3 -1
  5. package/integrations/evidence/buildEvidence.ts +14 -0
  6. package/integrations/evidence/repoState.ts +3 -0
  7. package/integrations/evidence/schema.ts +18 -0
  8. package/integrations/evidence/trackingContract.ts +146 -0
  9. package/integrations/evidence/writeEvidence.ts +14 -0
  10. package/integrations/gate/evaluateAiGate.ts +166 -3
  11. package/integrations/gate/governanceActionCatalog.ts +275 -0
  12. package/integrations/gate/remediationCatalog.ts +8 -0
  13. package/integrations/git/GitService.ts +0 -25
  14. package/integrations/git/aiGateRepoPolicyFindings.ts +4 -0
  15. package/integrations/git/runPlatformGate.ts +9 -1
  16. package/integrations/git/runPlatformGateFacts.ts +0 -7
  17. package/integrations/git/runPlatformGateOutput.ts +36 -27
  18. package/integrations/lifecycle/adapter.ts +24 -0
  19. package/integrations/lifecycle/bootstrapManifest.ts +248 -0
  20. package/integrations/lifecycle/cli.ts +45 -11
  21. package/integrations/lifecycle/cliSdd.ts +4 -3
  22. package/integrations/lifecycle/doctor.ts +49 -1
  23. package/integrations/lifecycle/governanceNextAction.ts +164 -0
  24. package/integrations/lifecycle/governanceObservationSnapshot.ts +315 -0
  25. package/integrations/lifecycle/install.ts +21 -0
  26. package/integrations/lifecycle/state.ts +8 -1
  27. package/integrations/lifecycle/status.ts +29 -2
  28. package/integrations/mcp/aiGateCheck.ts +140 -10
  29. package/integrations/mcp/alignedPlatformGate.ts +232 -0
  30. package/integrations/mcp/autoExecuteAiStart.ts +92 -85
  31. package/integrations/mcp/enterpriseServer.ts +6 -6
  32. package/integrations/mcp/preFlightCheck.ts +51 -5
  33. package/integrations/mcp/readMcpPrePushStdin.ts +7 -0
  34. package/integrations/policy/experimentalFeatures.ts +1 -1
  35. package/package.json +2 -4
  36. package/scripts/build-ruralgo-s1-evidence-pack.ts +85 -0
  37. package/scripts/consumer-menu-matrix-baseline-report-lib.ts +38 -13
  38. package/scripts/framework-menu-consumer-actions-lib.ts +4 -28
  39. package/scripts/framework-menu-consumer-preflight-hints.ts +2 -5
  40. package/scripts/framework-menu-consumer-preflight-render.ts +6 -0
  41. package/scripts/framework-menu-consumer-preflight-run.ts +19 -0
  42. package/scripts/framework-menu-consumer-preflight-types.ts +8 -0
  43. package/scripts/framework-menu-consumer-runtime-actions.ts +6 -86
  44. package/scripts/framework-menu-consumer-runtime-audit.ts +2 -36
  45. package/scripts/framework-menu-consumer-runtime-lib.ts +0 -2
  46. package/scripts/framework-menu-consumer-runtime-types.ts +1 -3
  47. package/scripts/framework-menu-evidence-summary-lib.ts +0 -1
  48. package/scripts/framework-menu-evidence-summary-read.ts +5 -57
  49. package/scripts/framework-menu-evidence-summary-severity.ts +1 -3
  50. package/scripts/framework-menu-evidence-summary-types.ts +0 -7
  51. package/scripts/framework-menu-gate-lib.ts +0 -9
  52. package/scripts/framework-menu-layout-data.ts +0 -5
  53. package/scripts/framework-menu-matrix-baseline-lib.ts +14 -15
  54. package/scripts/framework-menu-matrix-canary-lib.ts +1 -22
  55. package/scripts/framework-menu-matrix-evidence-lib.ts +0 -1
  56. package/scripts/framework-menu-matrix-evidence-types.ts +1 -13
  57. package/scripts/framework-menu-matrix-runner-lib.ts +0 -35
  58. package/scripts/framework-menu-system-notifications-macos.ts +0 -4
  59. package/scripts/framework-menu.ts +0 -3
  60. package/scripts/ruralgo-s1-evidence-pack-lib.ts +200 -0
  61. package/AGENTS.md +0 -269
  62. package/CHANGELOG.md +0 -666
  63. package/docs/tracking/plan-curso-pumuki-stack-my-architecture.md +0 -111
  64. package/scripts/framework-menu-consumer-runtime-evidence-classic.ts +0 -140
@@ -0,0 +1,164 @@
1
+ import type { AiGateStage } from '../gate/evaluateAiGate';
2
+ import { resolveGovernanceCatalogAction } from '../gate/governanceActionCatalog';
3
+ import type { GovernanceObservationSnapshot } from './governanceObservationSnapshot';
4
+ import { writeInfo } from './cliOutputs';
5
+
6
+ export type GovernanceNextActionSummary = {
7
+ stage: AiGateStage;
8
+ phase: 'GREEN' | 'RED';
9
+ action: 'proceed' | 'ask';
10
+ confidence_pct: number;
11
+ reason_code: string;
12
+ instruction: string;
13
+ message: string;
14
+ next_action: {
15
+ kind: 'info' | 'run_command';
16
+ message: string;
17
+ command?: string;
18
+ };
19
+ };
20
+
21
+ export type GovernanceNextActionReader = (params: {
22
+ repoRoot: string;
23
+ stage?: AiGateStage;
24
+ governanceObservation: GovernanceObservationSnapshot;
25
+ }) => GovernanceNextActionSummary;
26
+
27
+ const resolveBlockedAction = (
28
+ snapshot: GovernanceObservationSnapshot,
29
+ stage: AiGateStage
30
+ ): GovernanceNextActionSummary => {
31
+ if (snapshot.attention_codes.includes('EVIDENCE_INVALID_OR_CHAIN')) {
32
+ return {
33
+ stage,
34
+ phase: 'RED',
35
+ action: 'ask',
36
+ confidence_pct: 80,
37
+ ...resolveGovernanceCatalogAction({ code: 'EVIDENCE_INVALID_OR_CHAIN', stage }),
38
+ message: 'La evidencia actual no es fiable; detén la ejecución automática hasta regenerarla.',
39
+ };
40
+ }
41
+ if (
42
+ snapshot.attention_codes.includes('AI_GATE_BLOCKED')
43
+ || snapshot.attention_codes.includes('EVIDENCE_SNAPSHOT_BLOCK')
44
+ ) {
45
+ return {
46
+ stage,
47
+ phase: 'RED',
48
+ action: 'ask',
49
+ confidence_pct: 75,
50
+ ...resolveGovernanceCatalogAction({ code: 'AI_GATE_BLOCKED', stage }),
51
+ message: 'El gate efectivo sigue bloqueado; Pumuki no debe marcar verde ni dejar continuar.',
52
+ };
53
+ }
54
+ if (snapshot.attention_codes.includes('SDD_SESSION_INVALID_OR_EXPIRED')) {
55
+ return {
56
+ stage,
57
+ phase: 'RED',
58
+ action: 'ask',
59
+ confidence_pct: 70,
60
+ ...resolveGovernanceCatalogAction({ code: 'SDD_SESSION_INVALID_OR_EXPIRED', stage }),
61
+ message: 'Hay una sesión SDD activa pero inválida; eso rompe el loop documental esperado.',
62
+ };
63
+ }
64
+ if (snapshot.attention_codes.includes('GITFLOW_PROTECTED_BRANCH_CONTEXT')) {
65
+ return {
66
+ stage,
67
+ phase: 'RED',
68
+ action: 'ask',
69
+ confidence_pct: 65,
70
+ ...resolveGovernanceCatalogAction({ code: 'GITFLOW_PROTECTED_BRANCH_CONTEXT', stage }),
71
+ message: 'El contexto actual cae sobre una rama protegida; el flujo enterprise no debe continuar ahí.',
72
+ };
73
+ }
74
+ if (
75
+ snapshot.attention_codes.some((code) => code.startsWith('POLICY_'))
76
+ || snapshot.enterprise_warn_as_block_env
77
+ ) {
78
+ return {
79
+ stage,
80
+ phase: 'RED',
81
+ action: 'ask',
82
+ confidence_pct: 60,
83
+ ...resolveGovernanceCatalogAction({ code: 'POLICY_STAGE_NOT_STRICT', stage }),
84
+ message: 'La política efectiva todavía no es estricta en todos los stages requeridos.',
85
+ };
86
+ }
87
+ if (!snapshot.contract_surface.skills_lock_json || !snapshot.contract_surface.skills_sources_json) {
88
+ return {
89
+ stage,
90
+ phase: 'RED',
91
+ action: 'ask',
92
+ confidence_pct: 55,
93
+ ...resolveGovernanceCatalogAction({ code: 'SKILLS_CONTRACT_SURFACE_INCOMPLETE', stage }),
94
+ message: 'El contrato de skills todavía no está completamente materializado en el repo.',
95
+ };
96
+ }
97
+ if (!snapshot.contract_surface.pumuki_adapter_json) {
98
+ return {
99
+ stage,
100
+ phase: 'RED',
101
+ action: 'ask',
102
+ confidence_pct: 50,
103
+ ...resolveGovernanceCatalogAction({ code: 'ADAPTER_WIRING_MISSING', stage }),
104
+ message: 'La línea base Git puede operar, pero el wiring adaptador aún no está materializado.',
105
+ };
106
+ }
107
+ if (snapshot.attention_codes.includes('EVIDENCE_SNAPSHOT_WARN')) {
108
+ return {
109
+ stage,
110
+ phase: 'RED',
111
+ action: 'ask',
112
+ confidence_pct: 50,
113
+ ...resolveGovernanceCatalogAction({ code: 'EVIDENCE_SNAPSHOT_WARN', stage }),
114
+ message: 'La evidencia está en WARN; no conviene tratar el repo como completamente verde.',
115
+ };
116
+ }
117
+ return {
118
+ stage,
119
+ phase: 'RED',
120
+ action: 'ask',
121
+ confidence_pct: 40,
122
+ ...resolveGovernanceCatalogAction({ code: 'GOVERNANCE_ATTENTION', stage }),
123
+ message: 'Todavía hay señales de governance no resueltas.',
124
+ };
125
+ };
126
+
127
+ export const readGovernanceNextAction: GovernanceNextActionReader = (params) => {
128
+ const stage = params.stage ?? 'PRE_WRITE';
129
+ const snapshot = params.governanceObservation;
130
+ if (snapshot.governance_effective === 'green') {
131
+ return {
132
+ stage,
133
+ phase: 'GREEN',
134
+ action: 'proceed',
135
+ confidence_pct: 90,
136
+ ...resolveGovernanceCatalogAction({ code: 'READY', stage }),
137
+ message: 'Governance efectiva en verde: continúa con la implementación mínima.',
138
+ };
139
+ }
140
+ return resolveBlockedAction(snapshot, stage);
141
+ };
142
+
143
+ export const buildGovernanceNextActionSummaryLines = (
144
+ snapshot: GovernanceNextActionSummary
145
+ ): string[] => {
146
+ const lines = [
147
+ `Next action: stage=${snapshot.stage} phase=${snapshot.phase} action=${snapshot.action} confidence=${snapshot.confidence_pct}% reason=${snapshot.reason_code}`,
148
+ `Instruction: ${snapshot.instruction}`,
149
+ `Action detail: ${snapshot.next_action.message}`,
150
+ ];
151
+ if (snapshot.next_action.command) {
152
+ lines.push(`Command: ${snapshot.next_action.command}`);
153
+ }
154
+ return lines;
155
+ };
156
+
157
+ export const printGovernanceNextActionHuman = (
158
+ snapshot: GovernanceNextActionSummary
159
+ ): void => {
160
+ writeInfo('[pumuki] governance next action (S1 / governance console baseline):');
161
+ for (const line of buildGovernanceNextActionSummaryLines(snapshot)) {
162
+ writeInfo(`[pumuki] ${line}`);
163
+ }
164
+ };
@@ -0,0 +1,315 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { readEvidenceResult } from '../evidence/readEvidence';
4
+ import { readRepoTrackingState } from '../evidence/trackingContract';
5
+ import type { RepoTrackingState } from '../evidence/schema';
6
+ import { readSddStatus } from '../sdd';
7
+ import type { SddStatusPayload } from '../sdd/types';
8
+ import type { LifecycleExperimentalFeaturesSnapshot } from './experimentalFeaturesSnapshot';
9
+ import type { ILifecycleGitService } from './gitService';
10
+ import { LifecycleGitService } from './gitService';
11
+ import type { LifecyclePolicyValidationSnapshot } from './policyValidationSnapshot';
12
+ import { writeInfo } from './cliOutputs';
13
+
14
+ const DEFAULT_PROTECTED_BRANCHES = new Set(['main', 'master', 'develop', 'dev']);
15
+
16
+ export type GovernanceEvidenceSummary = {
17
+ path: string;
18
+ readable: 'missing' | 'invalid' | 'valid';
19
+ snapshot_stage?: string;
20
+ snapshot_outcome?: 'PASS' | 'WARN' | 'BLOCK';
21
+ matched_warn_count?: number;
22
+ matched_blocking_count?: number;
23
+ findings_count?: number;
24
+ ai_gate_status?: 'ALLOWED' | 'BLOCKED';
25
+ human_summary_preview: string[];
26
+ };
27
+
28
+ export type GovernanceContractSurface = {
29
+ agents_md: boolean;
30
+ skills_lock_json: boolean;
31
+ skills_sources_json: boolean;
32
+ vendor_skills_dir: boolean;
33
+ pumuki_adapter_json: boolean;
34
+ };
35
+
36
+ export type GovernanceObservationSnapshot = {
37
+ schema_version: '1';
38
+ sdd: {
39
+ experimental_raw: string | null;
40
+ effective_mode: 'off' | 'advisory' | 'strict';
41
+ experimental_source: string;
42
+ };
43
+ sdd_session: {
44
+ active: boolean;
45
+ valid: boolean;
46
+ change_id: string | null;
47
+ remaining_seconds: number | null;
48
+ };
49
+ policy_strict: {
50
+ pre_write: boolean;
51
+ pre_commit: boolean;
52
+ pre_push: boolean;
53
+ ci: boolean;
54
+ };
55
+ enterprise_warn_as_block_env: boolean;
56
+ evidence: GovernanceEvidenceSummary;
57
+ git: {
58
+ current_branch: string | null;
59
+ on_protected_branch_hint: boolean;
60
+ };
61
+ contract_surface: GovernanceContractSurface;
62
+ tracking: RepoTrackingState;
63
+ attention_codes: ReadonlyArray<string>;
64
+ governance_effective: 'green' | 'attention' | 'blocked';
65
+ agent_bootstrap_hints: ReadonlyArray<string>;
66
+ };
67
+
68
+ const truthyEnv = (value: string | undefined): boolean => {
69
+ if (typeof value !== 'string') {
70
+ return false;
71
+ }
72
+ const normalized = value.trim().toLowerCase();
73
+ return normalized === '1' || normalized === 'true' || normalized === 'yes' || normalized === 'strict';
74
+ };
75
+
76
+ const readCurrentBranch = (git: ILifecycleGitService, repoRoot: string): string | null => {
77
+ try {
78
+ const branch = git.runGit(['rev-parse', '--abbrev-ref', 'HEAD'], repoRoot).trim();
79
+ return branch.length > 0 ? branch : null;
80
+ } catch {
81
+ return null;
82
+ }
83
+ };
84
+
85
+ const readSddStatusSafe = (repoRoot: string): SddStatusPayload => {
86
+ try {
87
+ return readSddStatus(repoRoot);
88
+ } catch {
89
+ return {
90
+ repoRoot,
91
+ openspec: {
92
+ installed: false,
93
+ projectInitialized: false,
94
+ minimumVersion: '0.0.0',
95
+ recommendedVersion: '0.0.0',
96
+ compatible: false,
97
+ },
98
+ session: {
99
+ repoRoot,
100
+ active: false,
101
+ valid: false,
102
+ },
103
+ };
104
+ }
105
+ };
106
+
107
+ const buildContractSurface = (repoRoot: string): GovernanceContractSurface => ({
108
+ agents_md: existsSync(join(repoRoot, 'AGENTS.md')),
109
+ skills_lock_json: existsSync(join(repoRoot, 'skills.lock.json')),
110
+ skills_sources_json: existsSync(join(repoRoot, 'skills.sources.json')),
111
+ vendor_skills_dir: existsSync(join(repoRoot, 'vendor', 'skills')),
112
+ pumuki_adapter_json: existsSync(join(repoRoot, '.pumuki', 'adapter.json')),
113
+ });
114
+
115
+ const summarizeEvidence = (repoRoot: string): GovernanceEvidenceSummary => {
116
+ const evidenceResult = readEvidenceResult(repoRoot);
117
+ const path = evidenceResult.source_descriptor.path;
118
+ if (evidenceResult.kind === 'missing') {
119
+ return { path, readable: 'missing', human_summary_preview: [] };
120
+ }
121
+ if (evidenceResult.kind === 'invalid') {
122
+ return {
123
+ path,
124
+ readable: 'invalid',
125
+ human_summary_preview: [evidenceResult.detail ?? evidenceResult.reason],
126
+ };
127
+ }
128
+
129
+ const snapshot = evidenceResult.evidence.snapshot;
130
+ const hints = evidenceResult.evidence.operational_hints?.human_summary_lines ?? [];
131
+ const breakdown = evidenceResult.evidence.operational_hints?.rule_execution_breakdown;
132
+ return {
133
+ path,
134
+ readable: 'valid',
135
+ snapshot_stage: snapshot.stage,
136
+ snapshot_outcome: snapshot.outcome,
137
+ matched_warn_count: breakdown?.matched_warn_count,
138
+ matched_blocking_count: breakdown?.matched_blocking_count,
139
+ findings_count: Array.isArray(snapshot.findings) ? snapshot.findings.length : 0,
140
+ ai_gate_status: evidenceResult.evidence.ai_gate.status,
141
+ human_summary_preview: hints.slice(0, 5),
142
+ };
143
+ };
144
+
145
+ const buildHints = (
146
+ surface: GovernanceContractSurface,
147
+ branch: string | null,
148
+ protectedBranchHint: boolean,
149
+ tracking: RepoTrackingState
150
+ ): string[] => {
151
+ const hints: string[] = [];
152
+ if (surface.agents_md) {
153
+ hints.push('AGENTS.md presente: aplica el contrato del repo antes de dar governance en verde.');
154
+ }
155
+ if (!surface.skills_lock_json) {
156
+ hints.push('Falta skills.lock.json: genera lock canónico de skills antes de cerrar la gobernanza.');
157
+ }
158
+ if (!surface.pumuki_adapter_json) {
159
+ hints.push('Falta .pumuki/adapter.json: instala el adaptador si quieres wiring IDE/MCP explícito.');
160
+ }
161
+ if (protectedBranchHint && branch) {
162
+ hints.push(`La rama "${branch}" cae en el set protegido por defecto: usa feature/* o refactor/*.`);
163
+ }
164
+ if (tracking.conflict) {
165
+ hints.push('Tracking canónico en conflicto: AGENTS.md y los README del repo no apuntan al mismo MD.');
166
+ }
167
+ if (tracking.enforced && !tracking.canonical_present) {
168
+ hints.push(`Falta el tracking canónico declarado (${tracking.canonical_path ?? 'sin resolver'}).`);
169
+ }
170
+ if (tracking.enforced && tracking.single_in_progress_valid === false) {
171
+ hints.push(
172
+ `El tracking canónico debe dejar exactamente una 🚧 (actual=${tracking.in_progress_count ?? 'n/a'}).`
173
+ );
174
+ }
175
+ hints.push('SDD/OpenSpec: usa PUMUKI_EXPERIMENTAL_SDD=advisory|strict cuando el loop SDD esté activo.');
176
+ hints.push('WARN-as-BLOCK: activa PUMUKI_ENTERPRISE_STRICT_WARN_AS_BLOCK=1 si el repo exige promoción dura.');
177
+ return hints;
178
+ };
179
+
180
+ export const readGovernanceObservationSnapshot = (params: {
181
+ repoRoot: string;
182
+ experimentalFeatures: LifecycleExperimentalFeaturesSnapshot;
183
+ policyValidation: LifecyclePolicyValidationSnapshot;
184
+ git?: ILifecycleGitService;
185
+ }): GovernanceObservationSnapshot => {
186
+ const git = params.git ?? new LifecycleGitService();
187
+ const { repoRoot, experimentalFeatures, policyValidation } = params;
188
+ const rawSdd = process.env.PUMUKI_EXPERIMENTAL_SDD?.trim();
189
+ const sddStatus = readSddStatusSafe(repoRoot);
190
+ const evidence = summarizeEvidence(repoRoot);
191
+ const branch = readCurrentBranch(git, repoRoot);
192
+ const onProtected = typeof branch === 'string' && DEFAULT_PROTECTED_BRANCHES.has(branch.trim().toLowerCase());
193
+ const surface = buildContractSurface(repoRoot);
194
+ const tracking = readRepoTrackingState(repoRoot);
195
+ const warnAsBlock = truthyEnv(process.env.PUMUKI_ENTERPRISE_STRICT_WARN_AS_BLOCK);
196
+
197
+ const attention: string[] = [];
198
+ if (evidence.readable === 'invalid') {
199
+ attention.push('EVIDENCE_INVALID_OR_CHAIN');
200
+ }
201
+ if (evidence.readable === 'valid' && evidence.ai_gate_status === 'BLOCKED') {
202
+ attention.push('AI_GATE_BLOCKED');
203
+ }
204
+ if (evidence.readable === 'valid' && evidence.snapshot_outcome === 'WARN') {
205
+ attention.push('EVIDENCE_SNAPSHOT_WARN');
206
+ }
207
+ if (evidence.readable === 'valid' && evidence.snapshot_outcome === 'BLOCK') {
208
+ attention.push('EVIDENCE_SNAPSHOT_BLOCK');
209
+ }
210
+ if (sddStatus.session.active === true && sddStatus.session.valid !== true) {
211
+ attention.push('SDD_SESSION_INVALID_OR_EXPIRED');
212
+ }
213
+ if (!policyValidation.stages.PRE_WRITE.strict) {
214
+ attention.push('POLICY_PRE_WRITE_NOT_STRICT');
215
+ }
216
+ if (!policyValidation.stages.PRE_COMMIT.strict) {
217
+ attention.push('POLICY_PRE_COMMIT_NOT_STRICT');
218
+ }
219
+ if (!policyValidation.stages.PRE_PUSH.strict) {
220
+ attention.push('POLICY_PRE_PUSH_NOT_STRICT');
221
+ }
222
+ if (!policyValidation.stages.CI.strict) {
223
+ attention.push('POLICY_CI_NOT_STRICT');
224
+ }
225
+ if (onProtected) {
226
+ attention.push('GITFLOW_PROTECTED_BRANCH_CONTEXT');
227
+ }
228
+ if (tracking.conflict) {
229
+ attention.push('TRACKING_CANONICAL_SOURCE_CONFLICT');
230
+ }
231
+ if (tracking.enforced && !tracking.canonical_present) {
232
+ attention.push('TRACKING_CANONICAL_FILE_MISSING');
233
+ }
234
+ if (tracking.enforced && tracking.single_in_progress_valid === false) {
235
+ attention.push('TRACKING_CANONICAL_IN_PROGRESS_INVALID');
236
+ }
237
+
238
+ let governanceEffective: GovernanceObservationSnapshot['governance_effective'] = 'green';
239
+ if (
240
+ evidence.readable === 'invalid'
241
+ || (evidence.readable === 'valid' && evidence.ai_gate_status === 'BLOCKED')
242
+ || (evidence.readable === 'valid' && evidence.snapshot_outcome === 'BLOCK')
243
+ ) {
244
+ governanceEffective = 'blocked';
245
+ } else if (attention.length > 0) {
246
+ governanceEffective = 'attention';
247
+ }
248
+
249
+ return {
250
+ schema_version: '1',
251
+ sdd: {
252
+ experimental_raw: rawSdd && rawSdd.length > 0 ? rawSdd : null,
253
+ effective_mode: experimentalFeatures.features.sdd.mode,
254
+ experimental_source: experimentalFeatures.features.sdd.source,
255
+ },
256
+ sdd_session: {
257
+ active: sddStatus.session.active,
258
+ valid: sddStatus.session.valid,
259
+ change_id: sddStatus.session.changeId ?? null,
260
+ remaining_seconds:
261
+ typeof sddStatus.session.remainingSeconds === 'number' ? sddStatus.session.remainingSeconds : null,
262
+ },
263
+ policy_strict: {
264
+ pre_write: policyValidation.stages.PRE_WRITE.strict,
265
+ pre_commit: policyValidation.stages.PRE_COMMIT.strict,
266
+ pre_push: policyValidation.stages.PRE_PUSH.strict,
267
+ ci: policyValidation.stages.CI.strict,
268
+ },
269
+ enterprise_warn_as_block_env: warnAsBlock,
270
+ evidence,
271
+ git: {
272
+ current_branch: branch,
273
+ on_protected_branch_hint: onProtected,
274
+ },
275
+ contract_surface: surface,
276
+ tracking,
277
+ attention_codes: attention,
278
+ governance_effective: governanceEffective,
279
+ agent_bootstrap_hints: buildHints(surface, branch, onProtected, tracking),
280
+ };
281
+ };
282
+
283
+ export const buildGovernanceObservationSummaryLines = (
284
+ snapshot: GovernanceObservationSnapshot
285
+ ): string[] => {
286
+ const lines = [
287
+ `Governance: ${snapshot.governance_effective.toUpperCase()}`,
288
+ `Contract: AGENTS=${snapshot.contract_surface.agents_md ? 'yes' : 'no'} skills.lock=${snapshot.contract_surface.skills_lock_json ? 'yes' : 'no'} skills.sources=${snapshot.contract_surface.skills_sources_json ? 'yes' : 'no'} vendor/skills=${snapshot.contract_surface.vendor_skills_dir ? 'yes' : 'no'} adapter=${snapshot.contract_surface.pumuki_adapter_json ? 'yes' : 'no'}`,
289
+ `SDD: env=${snapshot.sdd.experimental_raw ?? '(unset)'} effective=${snapshot.sdd.effective_mode} session_active=${snapshot.sdd_session.active} session_valid=${snapshot.sdd_session.valid} change=${snapshot.sdd_session.change_id ?? 'none'}`,
290
+ `Evidence: readable=${snapshot.evidence.readable} stage=${snapshot.evidence.snapshot_stage ?? 'n/a'} outcome=${snapshot.evidence.snapshot_outcome ?? 'n/a'} ai_gate=${snapshot.evidence.ai_gate_status ?? 'n/a'} findings=${snapshot.evidence.findings_count ?? 'n/a'}`,
291
+ `GitFlow: branch=${snapshot.git.current_branch ?? 'unknown'} protected_hint=${snapshot.git.on_protected_branch_hint ? 'yes' : 'no'}`,
292
+ `Tracking: enforced=${snapshot.tracking.enforced} canonical=${snapshot.tracking.canonical_path ?? 'none'} present=${snapshot.tracking.canonical_present} single_active=${snapshot.tracking.single_in_progress_valid ?? 'n/a'} count=${snapshot.tracking.in_progress_count ?? 'n/a'} conflict=${snapshot.tracking.conflict}`,
293
+ `Policy strict: PRE_WRITE=${snapshot.policy_strict.pre_write} PRE_COMMIT=${snapshot.policy_strict.pre_commit} PRE_PUSH=${snapshot.policy_strict.pre_push} CI=${snapshot.policy_strict.ci}`,
294
+ ];
295
+ if (snapshot.attention_codes.length > 0) {
296
+ lines.push(`Attention: ${snapshot.attention_codes.join(', ')}`);
297
+ }
298
+ return lines;
299
+ };
300
+
301
+ export const printGovernanceObservationHuman = (snapshot: GovernanceObservationSnapshot): void => {
302
+ writeInfo('[pumuki] governance truth (S1 / governance console baseline):');
303
+ for (const line of buildGovernanceObservationSummaryLines(snapshot)) {
304
+ writeInfo(`[pumuki] ${line}`);
305
+ }
306
+ for (const hint of snapshot.evidence.human_summary_preview) {
307
+ writeInfo(`[pumuki] evidence hint: ${hint}`);
308
+ }
309
+ };
310
+
311
+ export const doctorGovernanceIsBlocking = (snapshot: GovernanceObservationSnapshot): boolean =>
312
+ snapshot.governance_effective === 'blocked';
313
+
314
+ export const doctorGovernanceNeedsAttention = (snapshot: GovernanceObservationSnapshot): boolean =>
315
+ snapshot.governance_effective !== 'green';
@@ -13,6 +13,7 @@ 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';
16
17
 
17
18
  export type LifecycleInstallResult = {
18
19
  repoRoot: string;
@@ -20,6 +21,10 @@ export type LifecycleInstallResult = {
20
21
  changedHooks: ReadonlyArray<string>;
21
22
  openSpecBootstrap?: OpenSpecBootstrapResult;
22
23
  degradedDoctorBypass?: boolean;
24
+ bootstrapManifest: {
25
+ path: string;
26
+ changed: boolean;
27
+ };
23
28
  };
24
29
 
25
30
  const shouldBootstrapEvidence = (repoRoot: string): boolean =>
@@ -103,12 +108,20 @@ export const runLifecycleInstall = (params?: {
103
108
  openSpecManagedArtifacts: priorArtifacts.length > 0 ? priorArtifacts : undefined,
104
109
  });
105
110
  ensureRepoBaselineAdapter(report.repoRoot);
111
+ const bootstrapManifest = writeLifecycleBootstrapManifest({
112
+ git,
113
+ repoRoot: report.repoRoot,
114
+ });
106
115
  return {
107
116
  repoRoot: report.repoRoot,
108
117
  version,
109
118
  changedHooks,
110
119
  openSpecBootstrap: undefined,
111
120
  degradedDoctorBypass: true,
121
+ bootstrapManifest: {
122
+ path: bootstrapManifest.path,
123
+ changed: bootstrapManifest.changed,
124
+ },
112
125
  };
113
126
  }
114
127
  const renderedIssues = report.issues.map((issue) => `- [${issue.severity}] ${issue.message}`).join('\n');
@@ -142,11 +155,19 @@ export const runLifecycleInstall = (params?: {
142
155
  openSpecManagedArtifacts: Array.from(mergedOpenSpecArtifacts),
143
156
  });
144
157
  ensureRepoBaselineAdapter(report.repoRoot);
158
+ const bootstrapManifest = writeLifecycleBootstrapManifest({
159
+ git,
160
+ repoRoot: report.repoRoot,
161
+ });
145
162
 
146
163
  return {
147
164
  repoRoot: report.repoRoot,
148
165
  version,
149
166
  changedHooks,
150
167
  openSpecBootstrap,
168
+ bootstrapManifest: {
169
+ path: bootstrapManifest.path,
170
+ changed: bootstrapManifest.changed,
171
+ },
151
172
  };
152
173
  };
@@ -49,10 +49,17 @@ 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);
52
53
  git.applyLocalConfig(repoRoot, PUMUKI_CONFIG_KEYS.installed, 'true');
53
54
  git.applyLocalConfig(repoRoot, PUMUKI_CONFIG_KEYS.version, version);
54
55
  git.applyLocalConfig(repoRoot, PUMUKI_CONFIG_KEYS.hooks, PUMUKI_MANAGED_HOOKS.join(','));
55
- git.applyLocalConfig(repoRoot, PUMUKI_CONFIG_KEYS.installedAt, new Date().toISOString());
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
+ );
56
63
  if (params.openSpecManagedArtifacts) {
57
64
  const serialized = serializeManagedArtifacts(params.openSpecManagedArtifacts);
58
65
  if (serialized) {
@@ -9,6 +9,15 @@ import {
9
9
  readLifecyclePolicyValidationSnapshot,
10
10
  type LifecyclePolicyValidationSnapshot,
11
11
  } from './policyValidationSnapshot';
12
+ import {
13
+ readGovernanceObservationSnapshot,
14
+ type GovernanceObservationSnapshot,
15
+ } from './governanceObservationSnapshot';
16
+ import {
17
+ readGovernanceNextAction,
18
+ type GovernanceNextActionReader,
19
+ type GovernanceNextActionSummary,
20
+ } from './governanceNextAction';
12
21
  import { readLifecycleState, type LifecycleState } from './state';
13
22
 
14
23
  export type LifecycleStatus = {
@@ -22,11 +31,14 @@ export type LifecycleStatus = {
22
31
  trackedNodeModulesCount: number;
23
32
  policyValidation: LifecyclePolicyValidationSnapshot;
24
33
  experimentalFeatures: LifecycleExperimentalFeaturesSnapshot;
34
+ governanceObservation: GovernanceObservationSnapshot;
35
+ governanceNextAction: GovernanceNextActionSummary;
25
36
  };
26
37
 
27
38
  export const readLifecycleStatus = (params?: {
28
39
  cwd?: string;
29
40
  git?: ILifecycleGitService;
41
+ governanceNextActionReader?: GovernanceNextActionReader;
30
42
  }): LifecycleStatus => {
31
43
  const git = params?.git ?? new LifecycleGitService();
32
44
  const cwd = params?.cwd ?? process.cwd();
@@ -38,6 +50,19 @@ export const readLifecycleStatus = (params?: {
38
50
  repoRoot,
39
51
  lifecycleVersion: lifecycleState.version,
40
52
  });
53
+ const policyValidation = readLifecyclePolicyValidationSnapshot(repoRoot);
54
+ const experimentalFeatures = readLifecycleExperimentalFeaturesSnapshot();
55
+ const governanceObservation = readGovernanceObservationSnapshot({
56
+ repoRoot,
57
+ experimentalFeatures,
58
+ policyValidation,
59
+ git,
60
+ });
61
+ const governanceNextAction = (params?.governanceNextActionReader ?? readGovernanceNextAction)({
62
+ repoRoot,
63
+ stage: 'PRE_WRITE',
64
+ governanceObservation,
65
+ });
41
66
 
42
67
  return {
43
68
  repoRoot,
@@ -48,7 +73,9 @@ export const readLifecycleStatus = (params?: {
48
73
  hooksDirectory: hooksDirectory.path,
49
74
  hooksDirectoryResolution: hooksDirectory.source,
50
75
  trackedNodeModulesCount,
51
- policyValidation: readLifecyclePolicyValidationSnapshot(repoRoot),
52
- experimentalFeatures: readLifecycleExperimentalFeaturesSnapshot(),
76
+ policyValidation,
77
+ experimentalFeatures,
78
+ governanceNextAction,
79
+ governanceObservation,
53
80
  };
54
81
  };