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,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(
|
|
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
|
|
52
|
-
experimentalFeatures
|
|
76
|
+
policyValidation,
|
|
77
|
+
experimentalFeatures,
|
|
78
|
+
governanceNextAction,
|
|
79
|
+
governanceObservation,
|
|
53
80
|
};
|
|
54
81
|
};
|