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.
- package/docs/README.md +9 -7
- package/docs/operations/RELEASE_NOTES.md +0 -7
- package/docs/product/USAGE.md +2 -5
- package/docs/validation/README.md +3 -1
- package/integrations/evidence/buildEvidence.ts +14 -0
- package/integrations/evidence/repoState.ts +3 -0
- package/integrations/evidence/schema.ts +18 -0
- package/integrations/evidence/trackingContract.ts +146 -0
- package/integrations/evidence/writeEvidence.ts +14 -0
- package/integrations/gate/evaluateAiGate.ts +166 -3
- package/integrations/gate/governanceActionCatalog.ts +275 -0
- package/integrations/gate/remediationCatalog.ts +8 -0
- package/integrations/git/GitService.ts +0 -25
- package/integrations/git/aiGateRepoPolicyFindings.ts +4 -0
- package/integrations/git/runPlatformGate.ts +9 -1
- package/integrations/git/runPlatformGateFacts.ts +0 -7
- package/integrations/git/runPlatformGateOutput.ts +36 -27
- package/integrations/lifecycle/adapter.ts +24 -0
- package/integrations/lifecycle/bootstrapManifest.ts +248 -0
- package/integrations/lifecycle/cli.ts +45 -11
- package/integrations/lifecycle/cliSdd.ts +4 -3
- package/integrations/lifecycle/doctor.ts +49 -1
- package/integrations/lifecycle/governanceNextAction.ts +164 -0
- package/integrations/lifecycle/governanceObservationSnapshot.ts +315 -0
- package/integrations/lifecycle/install.ts +21 -0
- package/integrations/lifecycle/state.ts +8 -1
- package/integrations/lifecycle/status.ts +29 -2
- package/integrations/mcp/aiGateCheck.ts +140 -10
- package/integrations/mcp/alignedPlatformGate.ts +232 -0
- package/integrations/mcp/autoExecuteAiStart.ts +92 -85
- package/integrations/mcp/enterpriseServer.ts +6 -6
- package/integrations/mcp/preFlightCheck.ts +51 -5
- package/integrations/mcp/readMcpPrePushStdin.ts +7 -0
- package/integrations/policy/experimentalFeatures.ts +1 -1
- package/package.json +2 -4
- package/scripts/build-ruralgo-s1-evidence-pack.ts +85 -0
- package/scripts/consumer-menu-matrix-baseline-report-lib.ts +38 -13
- package/scripts/framework-menu-consumer-actions-lib.ts +4 -28
- package/scripts/framework-menu-consumer-preflight-hints.ts +2 -5
- package/scripts/framework-menu-consumer-preflight-render.ts +6 -0
- package/scripts/framework-menu-consumer-preflight-run.ts +19 -0
- package/scripts/framework-menu-consumer-preflight-types.ts +8 -0
- package/scripts/framework-menu-consumer-runtime-actions.ts +6 -86
- package/scripts/framework-menu-consumer-runtime-audit.ts +2 -36
- package/scripts/framework-menu-consumer-runtime-lib.ts +0 -2
- package/scripts/framework-menu-consumer-runtime-types.ts +1 -3
- package/scripts/framework-menu-evidence-summary-lib.ts +0 -1
- package/scripts/framework-menu-evidence-summary-read.ts +5 -57
- package/scripts/framework-menu-evidence-summary-severity.ts +1 -3
- package/scripts/framework-menu-evidence-summary-types.ts +0 -7
- package/scripts/framework-menu-gate-lib.ts +0 -9
- package/scripts/framework-menu-layout-data.ts +0 -5
- package/scripts/framework-menu-matrix-baseline-lib.ts +14 -15
- package/scripts/framework-menu-matrix-canary-lib.ts +1 -22
- package/scripts/framework-menu-matrix-evidence-lib.ts +0 -1
- package/scripts/framework-menu-matrix-evidence-types.ts +1 -13
- package/scripts/framework-menu-matrix-runner-lib.ts +0 -35
- package/scripts/framework-menu-system-notifications-macos.ts +0 -4
- package/scripts/framework-menu.ts +0 -3
- package/scripts/ruralgo-s1-evidence-pack-lib.ts +200 -0
- package/AGENTS.md +0 -269
- package/CHANGELOG.md +0 -666
- package/docs/tracking/plan-curso-pumuki-stack-my-architecture.md +0 -111
- 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
|
-
|
|
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
|
|
1614
|
-
'PUMUKI_EXPERIMENTAL_PRE_WRITE=
|
|
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
|
|
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:
|
|
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:
|
|
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
|
|
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
|
-
|
|
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:
|
|
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): ${
|
|
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
|
|
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);
|