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,248 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
2
+ import { dirname, join } from 'node:path'
3
+ import { getPumukiHooksStatus, resolvePumukiHooksDirectory } from './hookManager'
4
+ import { LifecycleGitService, type ILifecycleGitService } from './gitService'
5
+ import {
6
+ readGovernanceObservationSnapshot,
7
+ type GovernanceContractSurface,
8
+ type GovernanceObservationSnapshot,
9
+ } from './governanceObservationSnapshot'
10
+ import { readGovernanceNextAction, type GovernanceNextActionSummary } from './governanceNextAction'
11
+ import { getCurrentPumukiPackageName, getCurrentPumukiVersion } from './packageInfo'
12
+ import { readLifecycleExperimentalFeaturesSnapshot } from './experimentalFeaturesSnapshot'
13
+ import { readLifecyclePolicyValidationSnapshot } from './policyValidationSnapshot'
14
+ import { readLifecycleState } from './state'
15
+
16
+ export const BOOTSTRAP_MANIFEST_RELATIVE_PATH = '.pumuki/bootstrap-manifest.json'
17
+
18
+ type AdapterCommandContract = {
19
+ path: string
20
+ present: boolean
21
+ hooks: {
22
+ pre_write?: string
23
+ pre_commit?: string
24
+ pre_push?: string
25
+ ci?: string
26
+ }
27
+ mcp: {
28
+ enterprise?: string
29
+ evidence?: string
30
+ }
31
+ }
32
+
33
+ export type LifecycleBootstrapManifest = {
34
+ schema_version: '1'
35
+ repo_root: string
36
+ package: {
37
+ name: string
38
+ version: string
39
+ }
40
+ lifecycle: {
41
+ installed: boolean
42
+ version: string | null
43
+ installed_at: string | null
44
+ managed_hooks: ReadonlyArray<string>
45
+ openspec_managed_artifacts: ReadonlyArray<string>
46
+ }
47
+ hooks_directory: {
48
+ path: string
49
+ source: 'git-rev-parse' | 'git-config' | 'default'
50
+ }
51
+ hook_status: Record<string, { managed_block_present: boolean; exists: boolean }>
52
+ contract_surface: GovernanceContractSurface
53
+ governance: {
54
+ effective: GovernanceObservationSnapshot['governance_effective']
55
+ attention_codes: ReadonlyArray<string>
56
+ next_action: GovernanceNextActionSummary
57
+ bootstrap_hints: ReadonlyArray<string>
58
+ }
59
+ sdd: {
60
+ effective_mode: GovernanceObservationSnapshot['sdd']['effective_mode']
61
+ experimental_source: string
62
+ session_active: boolean
63
+ session_valid: boolean
64
+ change_id: string | null
65
+ }
66
+ policy_strict: GovernanceObservationSnapshot['policy_strict']
67
+ git: GovernanceObservationSnapshot['git']
68
+ adapter: AdapterCommandContract
69
+ }
70
+
71
+ export type LifecycleBootstrapManifestWriteResult = {
72
+ path: string
73
+ changed: boolean
74
+ manifest: LifecycleBootstrapManifest
75
+ }
76
+
77
+ const isRecord = (value: unknown): value is Record<string, unknown> =>
78
+ typeof value === 'object' && value !== null && !Array.isArray(value)
79
+
80
+ const readOptionalCommand = (source: unknown): string | undefined => {
81
+ if (!isRecord(source)) {
82
+ return undefined
83
+ }
84
+ const command = source.command
85
+ return typeof command === 'string' && command.trim().length > 0 ? command.trim() : undefined
86
+ }
87
+
88
+ const readAdapterCommandContract = (repoRoot: string): AdapterCommandContract => {
89
+ const path = join(repoRoot, '.pumuki', 'adapter.json')
90
+ if (!existsSync(path)) {
91
+ return {
92
+ path: BOOTSTRAP_MANIFEST_RELATIVE_PATH.replace('bootstrap-manifest.json', 'adapter.json'),
93
+ present: false,
94
+ hooks: {},
95
+ mcp: {},
96
+ }
97
+ }
98
+
99
+ try {
100
+ const parsed = JSON.parse(readFileSync(path, 'utf8')) as unknown
101
+ const hooks = isRecord(parsed) && isRecord(parsed.hooks) ? parsed.hooks : {}
102
+ const mcp = isRecord(parsed) && isRecord(parsed.mcp) ? parsed.mcp : {}
103
+ return {
104
+ path: '.pumuki/adapter.json',
105
+ present: true,
106
+ hooks: {
107
+ pre_write: readOptionalCommand(isRecord(hooks) ? hooks.pre_write : undefined),
108
+ pre_commit: readOptionalCommand(isRecord(hooks) ? hooks.pre_commit : undefined),
109
+ pre_push: readOptionalCommand(isRecord(hooks) ? hooks.pre_push : undefined),
110
+ ci: readOptionalCommand(isRecord(hooks) ? hooks.ci : undefined),
111
+ },
112
+ mcp: {
113
+ enterprise: readOptionalCommand(isRecord(mcp) ? mcp.enterprise : undefined),
114
+ evidence: readOptionalCommand(isRecord(mcp) ? mcp.evidence : undefined),
115
+ },
116
+ }
117
+ } catch {
118
+ return {
119
+ path: '.pumuki/adapter.json',
120
+ present: false,
121
+ hooks: {},
122
+ mcp: {},
123
+ }
124
+ }
125
+ }
126
+
127
+ const parseManagedHooks = (raw?: string): string[] => {
128
+ if (typeof raw !== 'string' || raw.trim().length === 0) {
129
+ return []
130
+ }
131
+ return raw
132
+ .split(',')
133
+ .map((value) => value.trim())
134
+ .filter((value) => value.length > 0)
135
+ }
136
+
137
+ const parseManagedArtifacts = (raw?: string): string[] => {
138
+ if (typeof raw !== 'string' || raw.trim().length === 0) {
139
+ return []
140
+ }
141
+ return raw
142
+ .split(',')
143
+ .map((value) => value.trim())
144
+ .filter((value) => value.length > 0)
145
+ }
146
+
147
+ const toHookStatusSummary = (
148
+ hookStatus: ReturnType<typeof getPumukiHooksStatus>
149
+ ): Record<string, { managed_block_present: boolean; exists: boolean }> =>
150
+ Object.fromEntries(
151
+ Object.entries(hookStatus).map(([hook, entry]) => [
152
+ hook,
153
+ {
154
+ managed_block_present: entry.managedBlockPresent,
155
+ exists: entry.exists,
156
+ },
157
+ ])
158
+ )
159
+
160
+ export const buildLifecycleBootstrapManifest = (params: {
161
+ repoRoot: string
162
+ git?: ILifecycleGitService
163
+ }): LifecycleBootstrapManifest => {
164
+ const git = params.git ?? new LifecycleGitService()
165
+ const repoRoot = git.resolveRepoRoot(params.repoRoot)
166
+ const lifecycleState = readLifecycleState(git, repoRoot)
167
+ const experimentalFeatures = readLifecycleExperimentalFeaturesSnapshot()
168
+ const policyValidation = readLifecyclePolicyValidationSnapshot(repoRoot)
169
+ const governanceObservation = readGovernanceObservationSnapshot({
170
+ repoRoot,
171
+ experimentalFeatures,
172
+ policyValidation,
173
+ git,
174
+ })
175
+ const governanceNextAction = readGovernanceNextAction({
176
+ repoRoot,
177
+ stage: 'PRE_WRITE',
178
+ governanceObservation,
179
+ })
180
+ const hooksDirectory = resolvePumukiHooksDirectory(repoRoot)
181
+ const hookStatus = getPumukiHooksStatus(repoRoot)
182
+ const adapter = readAdapterCommandContract(repoRoot)
183
+
184
+ return {
185
+ schema_version: '1',
186
+ repo_root: repoRoot,
187
+ package: {
188
+ name: getCurrentPumukiPackageName(),
189
+ version: getCurrentPumukiVersion({ repoRoot }),
190
+ },
191
+ lifecycle: {
192
+ installed: lifecycleState.installed === 'true',
193
+ version: typeof lifecycleState.version === 'string' && lifecycleState.version.length > 0
194
+ ? lifecycleState.version
195
+ : null,
196
+ installed_at:
197
+ typeof lifecycleState.installedAt === 'string' && lifecycleState.installedAt.length > 0
198
+ ? lifecycleState.installedAt
199
+ : null,
200
+ managed_hooks: parseManagedHooks(lifecycleState.hooks),
201
+ openspec_managed_artifacts: parseManagedArtifacts(lifecycleState.openSpecManagedArtifacts),
202
+ },
203
+ hooks_directory: hooksDirectory,
204
+ hook_status: toHookStatusSummary(hookStatus),
205
+ contract_surface: governanceObservation.contract_surface,
206
+ governance: {
207
+ effective: governanceObservation.governance_effective,
208
+ attention_codes: governanceObservation.attention_codes,
209
+ next_action: governanceNextAction,
210
+ bootstrap_hints: governanceObservation.agent_bootstrap_hints,
211
+ },
212
+ sdd: {
213
+ effective_mode: governanceObservation.sdd.effective_mode,
214
+ experimental_source: governanceObservation.sdd.experimental_source,
215
+ session_active: governanceObservation.sdd_session.active,
216
+ session_valid: governanceObservation.sdd_session.valid,
217
+ change_id: governanceObservation.sdd_session.change_id,
218
+ },
219
+ policy_strict: governanceObservation.policy_strict,
220
+ git: governanceObservation.git,
221
+ adapter,
222
+ }
223
+ }
224
+
225
+ const serializeManifest = (manifest: LifecycleBootstrapManifest): string =>
226
+ `${JSON.stringify(manifest, null, 2)}\n`
227
+
228
+ export const writeLifecycleBootstrapManifest = (params: {
229
+ repoRoot: string
230
+ git?: ILifecycleGitService
231
+ }): LifecycleBootstrapManifestWriteResult => {
232
+ const git = params.git ?? new LifecycleGitService()
233
+ const repoRoot = git.resolveRepoRoot(params.repoRoot)
234
+ const path = join(repoRoot, BOOTSTRAP_MANIFEST_RELATIVE_PATH)
235
+ const manifest = buildLifecycleBootstrapManifest({ repoRoot, git })
236
+ const nextContents = serializeManifest(manifest)
237
+ const currentContents = existsSync(path) ? readFileSync(path, 'utf8') : ''
238
+ const changed = currentContents !== nextContents
239
+ if (changed) {
240
+ mkdirSync(dirname(path), { recursive: true })
241
+ writeFileSync(path, nextContents, 'utf8')
242
+ }
243
+ return {
244
+ path,
245
+ changed,
246
+ manifest,
247
+ }
248
+ }
@@ -4,11 +4,16 @@ import type { GatePolicy } from '../../core/gate/GatePolicy';
4
4
  import { runPlatformGate } from '../git/runPlatformGate';
5
5
  import { collectWorktreeAtomicSlices } from '../git/worktreeAtomicSlices';
6
6
  import {
7
+ doctorCommandShouldFailExit,
8
+ doctorCommandShouldWarnHuman,
7
9
  doctorHasBlockingIssues,
10
+ doctorHasGovernanceAttention,
8
11
  doctorHasParityMismatch,
9
12
  runLifecycleDoctor,
10
13
  type LifecycleDoctorReport,
11
14
  } from './doctor';
15
+ import { printGovernanceObservationHuman } from './governanceObservationSnapshot';
16
+ import { printGovernanceNextActionHuman } from './governanceNextAction';
12
17
  import { runLifecycleInstall } from './install';
13
18
  import { runLifecycleRemove } from './remove';
14
19
  import { readLifecycleStatus } from './status';
@@ -1517,6 +1522,8 @@ const printDoctorReport = (
1517
1522
  writeInfo(
1518
1523
  `[pumuki] hook pre-push: ${report.hookStatus['pre-push'].managedBlockPresent ? 'managed' : 'missing'}`
1519
1524
  );
1525
+ printGovernanceObservationHuman(report.governanceObservation);
1526
+ printGovernanceNextActionHuman(report.governanceNextAction);
1520
1527
  writeInfo(
1521
1528
  `[pumuki] policy-as-code: PRE_COMMIT=${report.policyValidation.stages.PRE_COMMIT.validationCode ?? 'n/a'} strict=${report.policyValidation.stages.PRE_COMMIT.strict ? 'yes' : 'no'} ` +
1522
1529
  `PRE_PUSH=${report.policyValidation.stages.PRE_PUSH.validationCode ?? 'n/a'} strict=${report.policyValidation.stages.PRE_PUSH.strict ? 'yes' : 'no'} ` +
@@ -1554,17 +1561,19 @@ const printDoctorReport = (
1554
1561
  }
1555
1562
  }
1556
1563
 
1557
- const hasBlocking =
1558
- doctorHasBlockingIssues(report) || doctorHasParityMismatch(report);
1559
- const hasWarnings =
1560
- report.issues.length > 0 ||
1561
- report.deep?.checks.some((check) => check.status !== 'pass') === true;
1564
+ const hasBlocking = doctorCommandShouldFailExit(report);
1565
+ const hasWarnings = doctorCommandShouldWarnHuman(report);
1562
1566
 
1563
1567
  if (!hasWarnings && !hasBlocking) {
1564
1568
  writeInfo('[pumuki] doctor verdict: PASS');
1565
1569
  } else {
1566
1570
  writeInfo(`[pumuki] doctor verdict: ${hasBlocking ? 'BLOCKED' : 'WARN'}`);
1567
1571
  }
1572
+ if (!hasBlocking && doctorHasGovernanceAttention(report)) {
1573
+ writeInfo(
1574
+ '[pumuki] doctor: governance_effective is not GREEN (see governance truth).'
1575
+ );
1576
+ }
1568
1577
 
1569
1578
  if (remoteCiDiagnostics) {
1570
1579
  printRemoteCiDiagnostics(remoteCiDiagnostics);
@@ -1610,8 +1619,8 @@ export type PreWriteOpenSpecBootstrapTrace = {
1610
1619
  details?: string;
1611
1620
  };
1612
1621
 
1613
- export const PRE_WRITE_ENABLE_ADVISORY_COMMAND =
1614
- 'PUMUKI_EXPERIMENTAL_PRE_WRITE=advisory npx --yes --package pumuki@latest pumuki sdd validate --stage=PRE_WRITE --json';
1622
+ export const PRE_WRITE_ENABLE_STRICT_COMMAND =
1623
+ 'PUMUKI_EXPERIMENTAL_PRE_WRITE=strict npx --yes --package pumuki@latest pumuki sdd validate --stage=PRE_WRITE --json';
1615
1624
  export const buildSddExperimentalEnableAdvisoryCommand = (stage: SddStage): string =>
1616
1625
  `PUMUKI_EXPERIMENTAL_SDD=advisory npx --yes --package pumuki@latest pumuki sdd validate --stage=${stage} --json`;
1617
1626
  const buildAnalyticsExperimentalEnableCommand = (action: AnalyticsHotspotsCommand): string =>
@@ -1705,6 +1714,7 @@ const buildSaasIngestionExperimentalDisabledEnvelope = (
1705
1714
  export const buildPreWriteExperimentalDisabledResult = (params: {
1706
1715
  stage: SddStage;
1707
1716
  status: SddEvaluateResult['status'];
1717
+ source: 'env' | 'legacy-env' | 'default';
1708
1718
  }): SddEvaluateResult => ({
1709
1719
  stage: params.stage,
1710
1720
  status: params.status,
@@ -1712,14 +1722,16 @@ export const buildPreWriteExperimentalDisabledResult = (params: {
1712
1722
  allowed: true,
1713
1723
  code: 'PRE_WRITE_EXPERIMENTAL_DISABLED',
1714
1724
  message:
1715
- 'PRE_WRITE pertenece al namespace experimental y está desactivado por defecto. Actívalo explícitamente con PUMUKI_EXPERIMENTAL_PRE_WRITE=advisory o strict si necesitas este flujo.',
1725
+ 'PRE_WRITE está desactivado explícitamente. Reactívalo con PUMUKI_EXPERIMENTAL_PRE_WRITE=strict si necesitas recuperar el gate previo a escritura.',
1716
1726
  details: {
1717
1727
  experimental: true,
1718
- default_off: true,
1728
+ default_off: false,
1729
+ disabled_explicitly: true,
1730
+ disabled_source: params.source,
1719
1731
  layer: 'experimental',
1720
1732
  activation_env: 'PUMUKI_EXPERIMENTAL_PRE_WRITE',
1721
1733
  legacy_activation_env: 'PUMUKI_PREWRITE_ENFORCEMENT',
1722
- activation_command: PRE_WRITE_ENABLE_ADVISORY_COMMAND,
1734
+ activation_command: PRE_WRITE_ENABLE_STRICT_COMMAND,
1723
1735
  },
1724
1736
  },
1725
1737
  });
@@ -2101,6 +2113,10 @@ export const runLifecycleCli = async (
2101
2113
  repo_root: installResult.repoRoot,
2102
2114
  version: installResult.version,
2103
2115
  hooks_changed: installResult.changedHooks,
2116
+ bootstrap_manifest: {
2117
+ path: installResult.bootstrapManifest.path,
2118
+ changed: installResult.bootstrapManifest.changed,
2119
+ },
2104
2120
  openspec: installResult.openSpecBootstrap
2105
2121
  ? {
2106
2122
  installed: installResult.openSpecBootstrap.packageInstalled,
@@ -2113,6 +2129,10 @@ export const runLifecycleCli = async (
2113
2129
  mcp: {
2114
2130
  agent: adapterResult.agent,
2115
2131
  changed_files: adapterResult.changedFiles,
2132
+ bootstrap_manifest: {
2133
+ path: adapterResult.bootstrapManifest.path,
2134
+ changed: adapterResult.bootstrapManifest.changed,
2135
+ },
2116
2136
  adapter_health: adapterCheck
2117
2137
  ? {
2118
2138
  status: adapterCheck.status,
@@ -2139,6 +2159,9 @@ export const runLifecycleCli = async (
2139
2159
  writeInfo(
2140
2160
  `[pumuki] bootstrap install: hooks changed=${installResult.changedHooks.join(', ') || 'none'}`
2141
2161
  );
2162
+ writeInfo(
2163
+ `[pumuki] bootstrap manifest: path=${installResult.bootstrapManifest.path} changed=${installResult.bootstrapManifest.changed ? 'yes' : 'no'}`
2164
+ );
2142
2165
  if (installResult.openSpecBootstrap) {
2143
2166
  writeInfo(
2144
2167
  `[pumuki] bootstrap openspec: installed=${installResult.openSpecBootstrap.packageInstalled ? 'yes' : 'no'} project=${installResult.openSpecBootstrap.projectInitialized ? 'yes' : 'no'} actions=${installResult.openSpecBootstrap.actions.join(', ') || 'none'}`
@@ -2179,6 +2202,9 @@ export const runLifecycleCli = async (
2179
2202
  writeInfo(
2180
2203
  `[pumuki] installed ${result.version} at ${result.repoRoot} (hooks changed: ${result.changedHooks.join(', ') || 'none'})`
2181
2204
  );
2205
+ writeInfo(
2206
+ `[pumuki] bootstrap manifest: path=${result.bootstrapManifest.path} changed=${result.bootstrapManifest.changed ? 'yes' : 'no'}`
2207
+ );
2182
2208
  if (result.openSpecBootstrap) {
2183
2209
  writeInfo(
2184
2210
  `[pumuki] openspec bootstrap: installed=${result.openSpecBootstrap.packageInstalled ? 'yes' : 'no'} project=${result.openSpecBootstrap.projectInitialized ? 'yes' : 'no'} actions=${result.openSpecBootstrap.actions.join(', ') || 'none'}`
@@ -2194,6 +2220,9 @@ export const runLifecycleCli = async (
2194
2220
  writeInfo(
2195
2221
  `[pumuki] mcp wiring: agent=${adapterResult.agent} changed=${adapterResult.changedFiles.length}`
2196
2222
  );
2223
+ writeInfo(
2224
+ `[pumuki] mcp manifest: path=${adapterResult.bootstrapManifest.path} changed=${adapterResult.bootstrapManifest.changed ? 'yes' : 'no'}`
2225
+ );
2197
2226
  if (adapterResult.changedFiles.length > 0) {
2198
2227
  writeInfo(`[pumuki] mcp files: ${adapterResult.changedFiles.join(', ')}`);
2199
2228
  }
@@ -2276,7 +2305,7 @@ export const runLifecycleCli = async (
2276
2305
  } else {
2277
2306
  printDoctorReport(report, remoteCiDiagnostics);
2278
2307
  }
2279
- return doctorHasBlockingIssues(report) || doctorHasParityMismatch(report) ? 1 : 0;
2308
+ return doctorCommandShouldFailExit(report) ? 1 : 0;
2280
2309
  }
2281
2310
  case 'status': {
2282
2311
  const status = readLifecycleStatus();
@@ -2329,6 +2358,8 @@ export const runLifecycleCli = async (
2329
2358
  writeInfo(
2330
2359
  `[pumuki] hooks: pre-commit=${status.hookStatus['pre-commit'].managedBlockPresent ? 'managed' : 'missing'}, pre-push=${status.hookStatus['pre-push'].managedBlockPresent ? 'managed' : 'missing'}`
2331
2360
  );
2361
+ printGovernanceObservationHuman(status.governanceObservation);
2362
+ printGovernanceNextActionHuman(status.governanceNextAction);
2332
2363
  writeInfo(
2333
2364
  `[pumuki] tracked node_modules paths: ${status.trackedNodeModulesCount}`
2334
2365
  );
@@ -2700,6 +2731,9 @@ export const runLifecycleCli = async (
2700
2731
  `[pumuki] adapter files: ${result.changedFiles.join(', ')}`
2701
2732
  );
2702
2733
  }
2734
+ writeInfo(
2735
+ `[pumuki] adapter manifest: path=${result.bootstrapManifest.path} changed=${result.bootstrapManifest.changed ? 'yes' : 'no'}`
2736
+ );
2703
2737
  }
2704
2738
  return 0;
2705
2739
  }
@@ -36,7 +36,7 @@ import {
36
36
  buildPreWriteExperimentalDisabledResult,
37
37
  buildSddExperimentalEnableAdvisoryCommand,
38
38
  runRawPreWriteAiGateCheck,
39
- PRE_WRITE_ENABLE_ADVISORY_COMMAND,
39
+ PRE_WRITE_ENABLE_STRICT_COMMAND,
40
40
  } from './cli';
41
41
 
42
42
  export const runSddCommand = async (parsed: ParsedArgs, activeDependencies: LifecycleCliDependencies): Promise<number> => {
@@ -84,6 +84,7 @@ export const runSddCommand = async (parsed: ParsedArgs, activeDependencies: Life
84
84
  const disabledResult = buildPreWriteExperimentalDisabledResult({
85
85
  stage: requestedStage,
86
86
  status: readSddStatus(process.cwd()),
87
+ source: preWriteEnforcement.source,
87
88
  });
88
89
  if (parsed.json) {
89
90
  writeInfo(
@@ -106,7 +107,7 @@ export const runSddCommand = async (parsed: ParsedArgs, activeDependencies: Life
106
107
  },
107
108
  next_action: {
108
109
  reason: 'PRE_WRITE_EXPERIMENTAL_DISABLED',
109
- command: PRE_WRITE_ENABLE_ADVISORY_COMMAND,
110
+ command: PRE_WRITE_ENABLE_STRICT_COMMAND,
110
111
  },
111
112
  },
112
113
  null,
@@ -127,7 +128,7 @@ export const runSddCommand = async (parsed: ParsedArgs, activeDependencies: Life
127
128
  `[pumuki][sdd] pre-write enforcement: mode=${preWriteEnforcement.mode} source=${preWriteEnforcement.source} blocking=no`
128
129
  );
129
130
  writeInfo(
130
- `[pumuki][sdd] next action (PRE_WRITE_EXPERIMENTAL_DISABLED): ${PRE_WRITE_ENABLE_ADVISORY_COMMAND}`
131
+ `[pumuki][sdd] next action (PRE_WRITE_EXPERIMENTAL_DISABLED): ${PRE_WRITE_ENABLE_STRICT_COMMAND}`
131
132
  );
132
133
  }
133
134
  return 0;
@@ -5,10 +5,25 @@ import { resolvePolicyForStage } from '../gate/stagePolicies';
5
5
  import { getPumukiHooksStatus, resolvePumukiHooksDirectory } from './hookManager';
6
6
  import { LifecycleGitService, type ILifecycleGitService } from './gitService';
7
7
  import { buildLifecycleVersionReport, getCurrentPumukiVersion } from './packageInfo';
8
+ import {
9
+ readLifecycleExperimentalFeaturesSnapshot,
10
+ type LifecycleExperimentalFeaturesSnapshot,
11
+ } from './experimentalFeaturesSnapshot';
8
12
  import {
9
13
  readLifecyclePolicyValidationSnapshot,
10
14
  type LifecyclePolicyValidationSnapshot,
11
15
  } 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';
12
27
  import { readLifecycleState, type LifecycleState } from './state';
13
28
  import {
14
29
  detectOpenSpecInstallation,
@@ -95,6 +110,9 @@ export type LifecycleDoctorReport = {
95
110
  hooksDirectory: string;
96
111
  hooksDirectoryResolution: 'git-rev-parse' | 'git-config' | 'default';
97
112
  policyValidation: LifecyclePolicyValidationSnapshot;
113
+ experimentalFeatures: LifecycleExperimentalFeaturesSnapshot;
114
+ governanceObservation: GovernanceObservationSnapshot;
115
+ governanceNextAction: GovernanceNextActionSummary;
98
116
  issues: ReadonlyArray<DoctorIssue>;
99
117
  deep?: DoctorDeepReport;
100
118
  parity_profile?: DoctorParityProfile;
@@ -797,6 +815,7 @@ export const runLifecycleDoctor = (params?: {
797
815
  git?: ILifecycleGitService;
798
816
  deep?: boolean;
799
817
  parity?: boolean;
818
+ governanceNextActionReader?: GovernanceNextActionReader;
800
819
  }): LifecycleDoctorReport => {
801
820
  const git = params?.git ?? new LifecycleGitService();
802
821
  const cwd = params?.cwd ?? process.cwd();
@@ -824,6 +843,19 @@ export const runLifecycleDoctor = (params?: {
824
843
  repoRoot,
825
844
  lifecycleVersion: lifecycleState.version,
826
845
  });
846
+ const policyValidation = readLifecyclePolicyValidationSnapshot(repoRoot);
847
+ const experimentalFeatures = readLifecycleExperimentalFeaturesSnapshot();
848
+ const governanceObservation = readGovernanceObservationSnapshot({
849
+ repoRoot,
850
+ experimentalFeatures,
851
+ policyValidation,
852
+ git,
853
+ });
854
+ const governanceNextAction = (params?.governanceNextActionReader ?? readGovernanceNextAction)({
855
+ repoRoot,
856
+ stage: 'PRE_WRITE',
857
+ governanceObservation,
858
+ });
827
859
 
828
860
  const parity_profile =
829
861
  params?.parity === true
@@ -846,7 +878,10 @@ export const runLifecycleDoctor = (params?: {
846
878
  hookStatus,
847
879
  hooksDirectory: hooksDirectory.path,
848
880
  hooksDirectoryResolution: hooksDirectory.source,
849
- policyValidation: readLifecyclePolicyValidationSnapshot(repoRoot),
881
+ policyValidation,
882
+ experimentalFeatures,
883
+ governanceNextAction,
884
+ governanceObservation,
850
885
  issues,
851
886
  deep,
852
887
  parity_profile,
@@ -859,3 +894,16 @@ export const doctorHasBlockingIssues = (report: LifecycleDoctorReport): boolean
859
894
 
860
895
  export const doctorHasParityMismatch = (report: LifecycleDoctorReport): boolean =>
861
896
  typeof report.parity_comparison !== 'undefined' && report.parity_comparison.matches === false;
897
+
898
+ export const doctorHasGovernanceAttention = (report: LifecycleDoctorReport): boolean =>
899
+ doctorGovernanceNeedsAttention(report.governanceObservation);
900
+
901
+ export const doctorCommandShouldWarnHuman = (report: LifecycleDoctorReport): boolean =>
902
+ report.issues.length > 0
903
+ || report.deep?.checks.some((check) => check.status !== 'pass') === true
904
+ || doctorHasGovernanceAttention(report);
905
+
906
+ export const doctorCommandShouldFailExit = (report: LifecycleDoctorReport): boolean =>
907
+ doctorHasBlockingIssues(report)
908
+ || doctorHasParityMismatch(report)
909
+ || doctorGovernanceIsBlocking(report.governanceObservation);