pumuki 6.3.72 → 6.3.73
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/CHANGELOG.md +20 -0
- package/VERSION +1 -1
- package/docs/README.md +1 -1
- package/docs/operations/RELEASE_NOTES.md +12 -1
- package/docs/product/USAGE.md +2 -5
- package/docs/tracking/plan-curso-pumuki-stack-my-architecture.md +56 -105
- package/integrations/gate/governanceActionCatalog.ts +230 -0
- package/integrations/git/runPlatformGate.ts +9 -1
- package/integrations/git/runPlatformGateFacts.ts +1 -7
- package/integrations/git/runPlatformGateOutput.ts +36 -27
- package/integrations/lifecycle/adapter.templates.json +3 -0
- package/integrations/lifecycle/audit.ts +101 -0
- package/integrations/lifecycle/cli.ts +80 -8
- package/integrations/lifecycle/doctor.ts +64 -1
- package/integrations/lifecycle/governanceNextAction.ts +164 -0
- package/integrations/lifecycle/governanceObservationSnapshot.ts +288 -0
- package/integrations/lifecycle/index.ts +2 -0
- package/integrations/lifecycle/status.ts +29 -2
- package/integrations/mcp/autoExecuteAiStart.ts +86 -84
- package/integrations/mcp/preFlightCheck.ts +41 -5
- package/integrations/platform/detectPlatforms.ts +37 -0
- package/package.json +8 -1
- package/scripts/build-ruralgo-s1-evidence-pack.ts +85 -0
- package/scripts/consumer-menu-matrix-baseline-report-lib.ts +38 -13
- package/scripts/consumer-postinstall-resolve-args.cjs +38 -0
- package/scripts/consumer-postinstall.cjs +10 -1
- 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/pumuki-full-surface-smoke-lib.ts +37 -0
- package/scripts/pumuki-full-surface-smoke.ts +261 -0
- package/scripts/pumuki-smoke-installed-wrapper.cjs +31 -0
- package/scripts/ruralgo-s1-evidence-pack-lib.ts +200 -0
- package/scripts/framework-menu-consumer-runtime-evidence-classic.ts +0 -140
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { readEvidenceResult } from '../evidence/readEvidence';
|
|
4
|
+
import { readSddStatus } from '../sdd';
|
|
5
|
+
import type { SddStatusPayload } from '../sdd/types';
|
|
6
|
+
import type { LifecycleExperimentalFeaturesSnapshot } from './experimentalFeaturesSnapshot';
|
|
7
|
+
import type { ILifecycleGitService } from './gitService';
|
|
8
|
+
import { LifecycleGitService } from './gitService';
|
|
9
|
+
import type { LifecyclePolicyValidationSnapshot } from './policyValidationSnapshot';
|
|
10
|
+
import { writeInfo } from './cliOutputs';
|
|
11
|
+
|
|
12
|
+
const DEFAULT_PROTECTED_BRANCHES = new Set(['main', 'master', 'develop', 'dev']);
|
|
13
|
+
|
|
14
|
+
export type GovernanceEvidenceSummary = {
|
|
15
|
+
path: string;
|
|
16
|
+
readable: 'missing' | 'invalid' | 'valid';
|
|
17
|
+
snapshot_stage?: string;
|
|
18
|
+
snapshot_outcome?: 'PASS' | 'WARN' | 'BLOCK';
|
|
19
|
+
matched_warn_count?: number;
|
|
20
|
+
matched_blocking_count?: number;
|
|
21
|
+
findings_count?: number;
|
|
22
|
+
ai_gate_status?: 'ALLOWED' | 'BLOCKED';
|
|
23
|
+
human_summary_preview: string[];
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type GovernanceContractSurface = {
|
|
27
|
+
agents_md: boolean;
|
|
28
|
+
skills_lock_json: boolean;
|
|
29
|
+
skills_sources_json: boolean;
|
|
30
|
+
vendor_skills_dir: boolean;
|
|
31
|
+
pumuki_adapter_json: boolean;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type GovernanceObservationSnapshot = {
|
|
35
|
+
schema_version: '1';
|
|
36
|
+
sdd: {
|
|
37
|
+
experimental_raw: string | null;
|
|
38
|
+
effective_mode: 'off' | 'advisory' | 'strict';
|
|
39
|
+
experimental_source: string;
|
|
40
|
+
};
|
|
41
|
+
sdd_session: {
|
|
42
|
+
active: boolean;
|
|
43
|
+
valid: boolean;
|
|
44
|
+
change_id: string | null;
|
|
45
|
+
remaining_seconds: number | null;
|
|
46
|
+
};
|
|
47
|
+
policy_strict: {
|
|
48
|
+
pre_write: boolean;
|
|
49
|
+
pre_commit: boolean;
|
|
50
|
+
pre_push: boolean;
|
|
51
|
+
ci: boolean;
|
|
52
|
+
};
|
|
53
|
+
enterprise_warn_as_block_env: boolean;
|
|
54
|
+
evidence: GovernanceEvidenceSummary;
|
|
55
|
+
git: {
|
|
56
|
+
current_branch: string | null;
|
|
57
|
+
on_protected_branch_hint: boolean;
|
|
58
|
+
};
|
|
59
|
+
contract_surface: GovernanceContractSurface;
|
|
60
|
+
attention_codes: ReadonlyArray<string>;
|
|
61
|
+
governance_effective: 'green' | 'attention' | 'blocked';
|
|
62
|
+
agent_bootstrap_hints: ReadonlyArray<string>;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const truthyEnv = (value: string | undefined): boolean => {
|
|
66
|
+
if (typeof value !== 'string') {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
const normalized = value.trim().toLowerCase();
|
|
70
|
+
return normalized === '1' || normalized === 'true' || normalized === 'yes' || normalized === 'strict';
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const readCurrentBranch = (git: ILifecycleGitService, repoRoot: string): string | null => {
|
|
74
|
+
try {
|
|
75
|
+
const branch = git.runGit(['rev-parse', '--abbrev-ref', 'HEAD'], repoRoot).trim();
|
|
76
|
+
return branch.length > 0 ? branch : null;
|
|
77
|
+
} catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const readSddStatusSafe = (repoRoot: string): SddStatusPayload => {
|
|
83
|
+
try {
|
|
84
|
+
return readSddStatus(repoRoot);
|
|
85
|
+
} catch {
|
|
86
|
+
return {
|
|
87
|
+
repoRoot,
|
|
88
|
+
openspec: {
|
|
89
|
+
installed: false,
|
|
90
|
+
projectInitialized: false,
|
|
91
|
+
minimumVersion: '0.0.0',
|
|
92
|
+
recommendedVersion: '0.0.0',
|
|
93
|
+
compatible: false,
|
|
94
|
+
},
|
|
95
|
+
session: {
|
|
96
|
+
repoRoot,
|
|
97
|
+
active: false,
|
|
98
|
+
valid: false,
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const buildContractSurface = (repoRoot: string): GovernanceContractSurface => ({
|
|
105
|
+
agents_md: existsSync(join(repoRoot, 'AGENTS.md')),
|
|
106
|
+
skills_lock_json: existsSync(join(repoRoot, 'skills.lock.json')),
|
|
107
|
+
skills_sources_json: existsSync(join(repoRoot, 'skills.sources.json')),
|
|
108
|
+
vendor_skills_dir: existsSync(join(repoRoot, 'vendor', 'skills')),
|
|
109
|
+
pumuki_adapter_json: existsSync(join(repoRoot, '.pumuki', 'adapter.json')),
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const summarizeEvidence = (repoRoot: string): GovernanceEvidenceSummary => {
|
|
113
|
+
const evidenceResult = readEvidenceResult(repoRoot);
|
|
114
|
+
const path = evidenceResult.source_descriptor.path;
|
|
115
|
+
if (evidenceResult.kind === 'missing') {
|
|
116
|
+
return { path, readable: 'missing', human_summary_preview: [] };
|
|
117
|
+
}
|
|
118
|
+
if (evidenceResult.kind === 'invalid') {
|
|
119
|
+
return {
|
|
120
|
+
path,
|
|
121
|
+
readable: 'invalid',
|
|
122
|
+
human_summary_preview: [evidenceResult.detail ?? evidenceResult.reason],
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const snapshot = evidenceResult.evidence.snapshot;
|
|
127
|
+
const hints = evidenceResult.evidence.operational_hints?.human_summary_lines ?? [];
|
|
128
|
+
const breakdown = evidenceResult.evidence.operational_hints?.rule_execution_breakdown;
|
|
129
|
+
return {
|
|
130
|
+
path,
|
|
131
|
+
readable: 'valid',
|
|
132
|
+
snapshot_stage: snapshot.stage,
|
|
133
|
+
snapshot_outcome: snapshot.outcome,
|
|
134
|
+
matched_warn_count: breakdown?.matched_warn_count,
|
|
135
|
+
matched_blocking_count: breakdown?.matched_blocking_count,
|
|
136
|
+
findings_count: Array.isArray(snapshot.findings) ? snapshot.findings.length : 0,
|
|
137
|
+
ai_gate_status: evidenceResult.evidence.ai_gate.status,
|
|
138
|
+
human_summary_preview: hints.slice(0, 5),
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const buildHints = (
|
|
143
|
+
surface: GovernanceContractSurface,
|
|
144
|
+
branch: string | null,
|
|
145
|
+
protectedBranchHint: boolean
|
|
146
|
+
): string[] => {
|
|
147
|
+
const hints: string[] = [];
|
|
148
|
+
if (surface.agents_md) {
|
|
149
|
+
hints.push('AGENTS.md presente: aplica el contrato del repo antes de dar governance en verde.');
|
|
150
|
+
}
|
|
151
|
+
if (!surface.skills_lock_json) {
|
|
152
|
+
hints.push('Falta skills.lock.json: genera lock canónico de skills antes de cerrar la gobernanza.');
|
|
153
|
+
}
|
|
154
|
+
if (!surface.pumuki_adapter_json) {
|
|
155
|
+
hints.push('Falta .pumuki/adapter.json: instala el adaptador si quieres wiring IDE/MCP explícito.');
|
|
156
|
+
}
|
|
157
|
+
if (protectedBranchHint && branch) {
|
|
158
|
+
hints.push(`La rama "${branch}" cae en el set protegido por defecto: usa feature/* o refactor/*.`);
|
|
159
|
+
}
|
|
160
|
+
hints.push('SDD/OpenSpec: usa PUMUKI_EXPERIMENTAL_SDD=advisory|strict cuando el loop SDD esté activo.');
|
|
161
|
+
hints.push('WARN-as-BLOCK: activa PUMUKI_ENTERPRISE_STRICT_WARN_AS_BLOCK=1 si el repo exige promoción dura.');
|
|
162
|
+
return hints;
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
export const readGovernanceObservationSnapshot = (params: {
|
|
166
|
+
repoRoot: string;
|
|
167
|
+
experimentalFeatures: LifecycleExperimentalFeaturesSnapshot;
|
|
168
|
+
policyValidation: LifecyclePolicyValidationSnapshot;
|
|
169
|
+
git?: ILifecycleGitService;
|
|
170
|
+
}): GovernanceObservationSnapshot => {
|
|
171
|
+
const git = params.git ?? new LifecycleGitService();
|
|
172
|
+
const { repoRoot, experimentalFeatures, policyValidation } = params;
|
|
173
|
+
const rawSdd = process.env.PUMUKI_EXPERIMENTAL_SDD?.trim();
|
|
174
|
+
const sddStatus = readSddStatusSafe(repoRoot);
|
|
175
|
+
const evidence = summarizeEvidence(repoRoot);
|
|
176
|
+
const branch = readCurrentBranch(git, repoRoot);
|
|
177
|
+
const onProtected = typeof branch === 'string' && DEFAULT_PROTECTED_BRANCHES.has(branch.trim().toLowerCase());
|
|
178
|
+
const surface = buildContractSurface(repoRoot);
|
|
179
|
+
const warnAsBlock = truthyEnv(process.env.PUMUKI_ENTERPRISE_STRICT_WARN_AS_BLOCK);
|
|
180
|
+
|
|
181
|
+
const attention: string[] = [];
|
|
182
|
+
if (evidence.readable === 'invalid') {
|
|
183
|
+
attention.push('EVIDENCE_INVALID_OR_CHAIN');
|
|
184
|
+
}
|
|
185
|
+
if (evidence.readable === 'valid' && evidence.ai_gate_status === 'BLOCKED') {
|
|
186
|
+
attention.push('AI_GATE_BLOCKED');
|
|
187
|
+
}
|
|
188
|
+
if (evidence.readable === 'valid' && evidence.snapshot_outcome === 'WARN') {
|
|
189
|
+
attention.push('EVIDENCE_SNAPSHOT_WARN');
|
|
190
|
+
}
|
|
191
|
+
if (evidence.readable === 'valid' && evidence.snapshot_outcome === 'BLOCK') {
|
|
192
|
+
attention.push('EVIDENCE_SNAPSHOT_BLOCK');
|
|
193
|
+
}
|
|
194
|
+
if (sddStatus.session.active === true && sddStatus.session.valid !== true) {
|
|
195
|
+
attention.push('SDD_SESSION_INVALID_OR_EXPIRED');
|
|
196
|
+
}
|
|
197
|
+
if (!policyValidation.stages.PRE_WRITE.strict) {
|
|
198
|
+
attention.push('POLICY_PRE_WRITE_NOT_STRICT');
|
|
199
|
+
}
|
|
200
|
+
if (!policyValidation.stages.PRE_COMMIT.strict) {
|
|
201
|
+
attention.push('POLICY_PRE_COMMIT_NOT_STRICT');
|
|
202
|
+
}
|
|
203
|
+
if (!policyValidation.stages.PRE_PUSH.strict) {
|
|
204
|
+
attention.push('POLICY_PRE_PUSH_NOT_STRICT');
|
|
205
|
+
}
|
|
206
|
+
if (!policyValidation.stages.CI.strict) {
|
|
207
|
+
attention.push('POLICY_CI_NOT_STRICT');
|
|
208
|
+
}
|
|
209
|
+
if (onProtected) {
|
|
210
|
+
attention.push('GITFLOW_PROTECTED_BRANCH_CONTEXT');
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
let governanceEffective: GovernanceObservationSnapshot['governance_effective'] = 'green';
|
|
214
|
+
if (
|
|
215
|
+
evidence.readable === 'invalid'
|
|
216
|
+
|| (evidence.readable === 'valid' && evidence.ai_gate_status === 'BLOCKED')
|
|
217
|
+
|| (evidence.readable === 'valid' && evidence.snapshot_outcome === 'BLOCK')
|
|
218
|
+
) {
|
|
219
|
+
governanceEffective = 'blocked';
|
|
220
|
+
} else if (attention.length > 0) {
|
|
221
|
+
governanceEffective = 'attention';
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
schema_version: '1',
|
|
226
|
+
sdd: {
|
|
227
|
+
experimental_raw: rawSdd && rawSdd.length > 0 ? rawSdd : null,
|
|
228
|
+
effective_mode: experimentalFeatures.features.sdd.mode,
|
|
229
|
+
experimental_source: experimentalFeatures.features.sdd.source,
|
|
230
|
+
},
|
|
231
|
+
sdd_session: {
|
|
232
|
+
active: sddStatus.session.active,
|
|
233
|
+
valid: sddStatus.session.valid,
|
|
234
|
+
change_id: sddStatus.session.changeId ?? null,
|
|
235
|
+
remaining_seconds:
|
|
236
|
+
typeof sddStatus.session.remainingSeconds === 'number' ? sddStatus.session.remainingSeconds : null,
|
|
237
|
+
},
|
|
238
|
+
policy_strict: {
|
|
239
|
+
pre_write: policyValidation.stages.PRE_WRITE.strict,
|
|
240
|
+
pre_commit: policyValidation.stages.PRE_COMMIT.strict,
|
|
241
|
+
pre_push: policyValidation.stages.PRE_PUSH.strict,
|
|
242
|
+
ci: policyValidation.stages.CI.strict,
|
|
243
|
+
},
|
|
244
|
+
enterprise_warn_as_block_env: warnAsBlock,
|
|
245
|
+
evidence,
|
|
246
|
+
git: {
|
|
247
|
+
current_branch: branch,
|
|
248
|
+
on_protected_branch_hint: onProtected,
|
|
249
|
+
},
|
|
250
|
+
contract_surface: surface,
|
|
251
|
+
attention_codes: attention,
|
|
252
|
+
governance_effective: governanceEffective,
|
|
253
|
+
agent_bootstrap_hints: buildHints(surface, branch, onProtected),
|
|
254
|
+
};
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
export const buildGovernanceObservationSummaryLines = (
|
|
258
|
+
snapshot: GovernanceObservationSnapshot
|
|
259
|
+
): string[] => {
|
|
260
|
+
const lines = [
|
|
261
|
+
`Governance: ${snapshot.governance_effective.toUpperCase()}`,
|
|
262
|
+
`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'}`,
|
|
263
|
+
`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'}`,
|
|
264
|
+
`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'}`,
|
|
265
|
+
`GitFlow: branch=${snapshot.git.current_branch ?? 'unknown'} protected_hint=${snapshot.git.on_protected_branch_hint ? 'yes' : 'no'}`,
|
|
266
|
+
`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}`,
|
|
267
|
+
];
|
|
268
|
+
if (snapshot.attention_codes.length > 0) {
|
|
269
|
+
lines.push(`Attention: ${snapshot.attention_codes.join(', ')}`);
|
|
270
|
+
}
|
|
271
|
+
return lines;
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
export const printGovernanceObservationHuman = (snapshot: GovernanceObservationSnapshot): void => {
|
|
275
|
+
writeInfo('[pumuki] governance truth (S1 / governance console baseline):');
|
|
276
|
+
for (const line of buildGovernanceObservationSummaryLines(snapshot)) {
|
|
277
|
+
writeInfo(`[pumuki] ${line}`);
|
|
278
|
+
}
|
|
279
|
+
for (const hint of snapshot.evidence.human_summary_preview) {
|
|
280
|
+
writeInfo(`[pumuki] evidence hint: ${hint}`);
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
export const doctorGovernanceIsBlocking = (snapshot: GovernanceObservationSnapshot): boolean =>
|
|
285
|
+
snapshot.governance_effective === 'blocked';
|
|
286
|
+
|
|
287
|
+
export const doctorGovernanceNeedsAttention = (snapshot: GovernanceObservationSnapshot): boolean =>
|
|
288
|
+
snapshot.governance_effective !== 'green';
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { runLifecycleDoctor, doctorHasBlockingIssues } from './doctor';
|
|
2
|
+
export { runLifecycleAudit } from './audit';
|
|
3
|
+
export type { LifecycleAuditResult, LifecycleAuditStage } from './audit';
|
|
2
4
|
export { runLifecycleInstall } from './install';
|
|
3
5
|
export { runLifecycleUninstall } from './uninstall';
|
|
4
6
|
export { runLifecycleRemove } from './remove';
|
|
@@ -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
|
};
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildGovernanceValidateCommand,
|
|
3
|
+
resolveGovernanceCatalogAction,
|
|
4
|
+
} from '../gate/governanceActionCatalog';
|
|
1
5
|
import { evaluateAiGate, type AiGateStage, type AiGateViolation } from '../gate/evaluateAiGate';
|
|
2
6
|
import { collectWorktreeAtomicSlices } from '../git/worktreeAtomicSlices';
|
|
3
7
|
import { resolveLearningContextExperimentalFeature } from '../policy/experimentalFeatures';
|
|
@@ -56,90 +60,87 @@ const confidenceFromViolation = (violationCode: string | null): number => {
|
|
|
56
60
|
return 50;
|
|
57
61
|
};
|
|
58
62
|
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
repoRoot: string
|
|
62
|
-
): AutoExecuteNextAction => {
|
|
63
|
-
if (!violation) {
|
|
64
|
-
return {
|
|
65
|
-
kind: 'info',
|
|
66
|
-
message: 'Gate listo. Puedes continuar con implementación.',
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
switch (violation.code) {
|
|
70
|
-
case 'EVIDENCE_MISSING':
|
|
63
|
+
const normalizeGovernanceCatalogCode = (code: string): string => {
|
|
64
|
+
switch (code) {
|
|
71
65
|
case 'EVIDENCE_INVALID':
|
|
72
66
|
case 'EVIDENCE_CHAIN_INVALID':
|
|
73
|
-
|
|
74
|
-
return {
|
|
75
|
-
kind: 'run_command',
|
|
76
|
-
message: 'Regenera o refresca evidencia y vuelve a evaluar PRE_WRITE.',
|
|
77
|
-
command: 'npx --yes --package pumuki@latest pumuki sdd validate --stage=PRE_WRITE --json',
|
|
78
|
-
};
|
|
79
|
-
case 'EVIDENCE_ACTIVE_RULE_IDS_EMPTY_FOR_CODE_CHANGES':
|
|
80
|
-
return {
|
|
81
|
-
kind: 'run_command',
|
|
82
|
-
message:
|
|
83
|
-
'No hay active_rule_ids para plataforma de código detectada. Reconciliación strict de policy/skills y revalidación PRE_WRITE.',
|
|
84
|
-
command:
|
|
85
|
-
'npx --yes --package pumuki@latest pumuki policy reconcile --strict --json && npx --yes --package pumuki@latest pumuki sdd validate --stage=PRE_WRITE --json',
|
|
86
|
-
};
|
|
87
|
-
case 'EVIDENCE_PLATFORM_SKILLS_SCOPE_INCOMPLETE':
|
|
88
|
-
case 'EVIDENCE_PLATFORM_SKILLS_BUNDLES_MISSING':
|
|
89
|
-
case 'EVIDENCE_SKILLS_CONTRACT_INCOMPLETE':
|
|
90
|
-
return {
|
|
91
|
-
kind: 'run_command',
|
|
92
|
-
message:
|
|
93
|
-
'Completa cobertura de skills por plataforma (prefijos + bundles) y revalida PRE_WRITE.',
|
|
94
|
-
command: 'npx --yes --package pumuki@latest pumuki sdd validate --stage=PRE_WRITE --json',
|
|
95
|
-
};
|
|
96
|
-
case 'EVIDENCE_PLATFORM_CRITICAL_SKILLS_RULES_MISSING':
|
|
97
|
-
case 'EVIDENCE_CROSS_PLATFORM_CRITICAL_ENFORCEMENT_INCOMPLETE':
|
|
98
|
-
return {
|
|
99
|
-
kind: 'run_command',
|
|
100
|
-
message:
|
|
101
|
-
'Reconcilia policy/skills en modo estricto (incluida skills.ios.critical-test-quality cuando aplique) y revalida PRE_WRITE.',
|
|
102
|
-
command:
|
|
103
|
-
'npx --yes --package pumuki@latest pumuki policy reconcile --strict --json && npx --yes --package pumuki@latest pumuki sdd validate --stage=PRE_WRITE --json',
|
|
104
|
-
};
|
|
105
|
-
case 'EVIDENCE_PREWRITE_WORKTREE_OVER_LIMIT':
|
|
106
|
-
case 'EVIDENCE_PREWRITE_WORKTREE_WARN':
|
|
107
|
-
{
|
|
108
|
-
const plan = collectWorktreeAtomicSlices({
|
|
109
|
-
repoRoot,
|
|
110
|
-
maxSlices: 3,
|
|
111
|
-
maxFilesPerSlice: 4,
|
|
112
|
-
});
|
|
113
|
-
if (plan.slices.length > 0) {
|
|
114
|
-
const firstSlice = plan.slices[0];
|
|
115
|
-
return {
|
|
116
|
-
kind: 'run_command',
|
|
117
|
-
message:
|
|
118
|
-
`Particiona el worktree en slices atómicos por scope. Primer lote sugerido: ${firstSlice?.scope ?? 'scope-desconocido'}.`,
|
|
119
|
-
command:
|
|
120
|
-
`${firstSlice?.staged_command ?? 'git add -p'} && npx --yes --package pumuki@latest pumuki sdd validate --stage=PRE_WRITE --json`,
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
return {
|
|
125
|
-
kind: 'run_command',
|
|
126
|
-
message:
|
|
127
|
-
'Particiona el worktree en slices atómicos y revalida PRE_WRITE para continuar sin fricción.',
|
|
128
|
-
command:
|
|
129
|
-
'git status --short && git add -p && npx --yes --package pumuki@latest pumuki sdd validate --stage=PRE_WRITE --json',
|
|
130
|
-
};
|
|
67
|
+
return 'EVIDENCE_INVALID_OR_CHAIN';
|
|
131
68
|
case 'GITFLOW_PROTECTED_BRANCH':
|
|
132
|
-
return
|
|
133
|
-
kind: 'run_command',
|
|
134
|
-
message: 'Cambia a una rama feature/* antes de continuar.',
|
|
135
|
-
command: 'git checkout -b feature/<descripcion-kebab-case>',
|
|
136
|
-
};
|
|
69
|
+
return 'GITFLOW_PROTECTED_BRANCH_CONTEXT';
|
|
137
70
|
default:
|
|
71
|
+
return code;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const resolveAutoExecuteRemediation = (params: {
|
|
76
|
+
violation: AiGateViolation | undefined;
|
|
77
|
+
repoRoot: string;
|
|
78
|
+
stage: AiGateStage;
|
|
79
|
+
allowed: boolean;
|
|
80
|
+
}): {
|
|
81
|
+
instruction: string;
|
|
82
|
+
nextAction: AutoExecuteNextAction;
|
|
83
|
+
} => {
|
|
84
|
+
if (!params.violation) {
|
|
85
|
+
const readyAction = resolveGovernanceCatalogAction({
|
|
86
|
+
code: 'READY',
|
|
87
|
+
stage: params.stage,
|
|
88
|
+
});
|
|
89
|
+
return {
|
|
90
|
+
instruction: readyAction.instruction,
|
|
91
|
+
nextAction: readyAction.next_action,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (
|
|
96
|
+
params.violation.code === 'EVIDENCE_PREWRITE_WORKTREE_OVER_LIMIT'
|
|
97
|
+
|| params.violation.code === 'EVIDENCE_PREWRITE_WORKTREE_WARN'
|
|
98
|
+
) {
|
|
99
|
+
const validateCommand = buildGovernanceValidateCommand(params.stage);
|
|
100
|
+
const plan = collectWorktreeAtomicSlices({
|
|
101
|
+
repoRoot: params.repoRoot,
|
|
102
|
+
maxSlices: 3,
|
|
103
|
+
maxFilesPerSlice: 4,
|
|
104
|
+
});
|
|
105
|
+
if (plan.slices.length > 0) {
|
|
106
|
+
const firstSlice = plan.slices[0];
|
|
138
107
|
return {
|
|
139
|
-
|
|
140
|
-
|
|
108
|
+
instruction: 'Particiona el worktree en slices atómicos antes de continuar.',
|
|
109
|
+
nextAction: {
|
|
110
|
+
kind: params.allowed ? 'info' : 'run_command',
|
|
111
|
+
message:
|
|
112
|
+
`Particiona el worktree en slices atómicos por scope. Primer lote sugerido: ${firstSlice?.scope ?? 'scope-desconocido'}.`,
|
|
113
|
+
command: params.allowed
|
|
114
|
+
? undefined
|
|
115
|
+
: `${firstSlice?.staged_command ?? 'git add -p'} && ${validateCommand}`,
|
|
116
|
+
},
|
|
141
117
|
};
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
instruction: 'Particiona el worktree en slices atómicos antes de continuar.',
|
|
121
|
+
nextAction: {
|
|
122
|
+
kind: params.allowed ? 'info' : 'run_command',
|
|
123
|
+
message: 'Particiona el worktree en slices atómicos y revalida PRE_WRITE para continuar sin fricción.',
|
|
124
|
+
command: params.allowed
|
|
125
|
+
? undefined
|
|
126
|
+
: `git status --short && git add -p && ${validateCommand}`,
|
|
127
|
+
},
|
|
128
|
+
};
|
|
142
129
|
}
|
|
130
|
+
|
|
131
|
+
const governanceAction = resolveGovernanceCatalogAction({
|
|
132
|
+
code: normalizeGovernanceCatalogCode(params.violation.code),
|
|
133
|
+
stage: params.stage,
|
|
134
|
+
});
|
|
135
|
+
return {
|
|
136
|
+
instruction: governanceAction.instruction,
|
|
137
|
+
nextAction: params.allowed
|
|
138
|
+
? {
|
|
139
|
+
kind: 'info',
|
|
140
|
+
message: governanceAction.next_action.message,
|
|
141
|
+
}
|
|
142
|
+
: governanceAction.next_action,
|
|
143
|
+
};
|
|
143
144
|
};
|
|
144
145
|
|
|
145
146
|
export type EnterpriseAutoExecuteAiStartResult = {
|
|
@@ -190,19 +191,20 @@ export const runEnterpriseAutoExecuteAiStart = (params: {
|
|
|
190
191
|
const action: AutoExecuteAction = evaluation.allowed ? 'proceed' : 'ask';
|
|
191
192
|
const phase = toAutoExecutePhase(action);
|
|
192
193
|
const confidencePct = confidenceFromViolation(firstViolation?.code ?? null);
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
194
|
+
const remediation = resolveAutoExecuteRemediation({
|
|
195
|
+
violation: firstViolation,
|
|
196
|
+
repoRoot: params.repoRoot,
|
|
197
|
+
stage,
|
|
198
|
+
allowed: evaluation.allowed,
|
|
199
|
+
});
|
|
200
|
+
const nextAction = remediation.nextAction;
|
|
199
201
|
|
|
200
202
|
let message = toHumanMessage({
|
|
201
203
|
action,
|
|
202
204
|
confidencePct,
|
|
203
205
|
reasonCode,
|
|
204
206
|
});
|
|
205
|
-
let instruction =
|
|
207
|
+
let instruction = remediation.instruction;
|
|
206
208
|
if (learningContext?.recommended_actions[0]) {
|
|
207
209
|
message = `${message} Learning: ${learningContext.recommended_actions[0]}`;
|
|
208
210
|
instruction = `${instruction} Learning: ${learningContext.recommended_actions[0]}`;
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolveGovernanceCatalogAction,
|
|
3
|
+
type GovernanceCatalogNextAction,
|
|
4
|
+
} from '../gate/governanceActionCatalog';
|
|
1
5
|
import { evaluateAiGate, type AiGateStage, type AiGateViolation } from '../gate/evaluateAiGate';
|
|
2
6
|
import { collectWorktreeAtomicSlices } from '../git/worktreeAtomicSlices';
|
|
3
7
|
import { resolveLearningContextExperimentalFeature } from '../policy/experimentalFeatures';
|
|
4
8
|
import { readSddLearningContext, type SddLearningContext } from '../sdd/learningInsights';
|
|
5
9
|
|
|
6
10
|
const ACTIONABLE_HINTS_BY_CODE: Readonly<Record<string, string>> = {
|
|
7
|
-
EVIDENCE_MISSING:
|
|
8
|
-
'Ejecuta una auditoría (1/2/3/4 u opciones de motor 11–14) para regenerar .ai_evidence.json.',
|
|
11
|
+
EVIDENCE_MISSING: 'Ejecuta una auditoría (1/2/3/4) para regenerar .ai_evidence.json.',
|
|
9
12
|
EVIDENCE_INVALID: 'Regenera .ai_evidence.json desde una opción de auditoría.',
|
|
10
13
|
EVIDENCE_INTEGRITY_MISSING: 'Refresca evidencia para regenerar metadatos de integridad.',
|
|
11
14
|
EVIDENCE_ACTIVE_RULE_IDS_EMPTY_FOR_CODE_CHANGES:
|
|
@@ -41,6 +44,18 @@ const ACTIONABLE_HINTS_BY_CODE: Readonly<Record<string, string>> = {
|
|
|
41
44
|
GITFLOW_PROTECTED_BRANCH: 'Evita trabajo directo en ramas protegidas (usa feature/*).',
|
|
42
45
|
};
|
|
43
46
|
|
|
47
|
+
const normalizeGovernanceCatalogCode = (code: string): string => {
|
|
48
|
+
switch (code) {
|
|
49
|
+
case 'EVIDENCE_INVALID':
|
|
50
|
+
case 'EVIDENCE_CHAIN_INVALID':
|
|
51
|
+
return 'EVIDENCE_INVALID_OR_CHAIN';
|
|
52
|
+
case 'GITFLOW_PROTECTED_BRANCH':
|
|
53
|
+
return 'GITFLOW_PROTECTED_BRANCH_CONTEXT';
|
|
54
|
+
default:
|
|
55
|
+
return code;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
44
59
|
const buildPreFlightHints = (params: {
|
|
45
60
|
repoRoot: string;
|
|
46
61
|
stage: AiGateStage;
|
|
@@ -115,6 +130,8 @@ export type EnterprisePreFlightCheckResult = {
|
|
|
115
130
|
phase: 'GREEN' | 'RED';
|
|
116
131
|
message: string;
|
|
117
132
|
instruction: string;
|
|
133
|
+
reason_code: string;
|
|
134
|
+
next_action: GovernanceCatalogNextAction;
|
|
118
135
|
stage: ReturnType<typeof evaluateAiGate>['stage'];
|
|
119
136
|
policy: ReturnType<typeof evaluateAiGate>['policy'];
|
|
120
137
|
violations: ReturnType<typeof evaluateAiGate>['violations'];
|
|
@@ -154,13 +171,30 @@ export const runEnterprisePreFlightCheck = (params: {
|
|
|
154
171
|
upstream: evaluation.repo_state.git.upstream,
|
|
155
172
|
learningContext,
|
|
156
173
|
});
|
|
174
|
+
const firstViolation = evaluation.violations[0];
|
|
175
|
+
const reasonCode = firstViolation?.code ?? 'READY';
|
|
176
|
+
const governanceAction = firstViolation
|
|
177
|
+
? resolveGovernanceCatalogAction({
|
|
178
|
+
code: normalizeGovernanceCatalogCode(firstViolation.code),
|
|
179
|
+
stage: evaluation.stage,
|
|
180
|
+
})
|
|
181
|
+
: resolveGovernanceCatalogAction({
|
|
182
|
+
code: 'READY',
|
|
183
|
+
stage: evaluation.stage,
|
|
184
|
+
});
|
|
157
185
|
const phase: 'GREEN' | 'RED' = evaluation.allowed ? 'GREEN' : 'RED';
|
|
158
186
|
const message = evaluation.allowed
|
|
159
187
|
? '✅ Pre-flight aprobado: puedes continuar con la implementación.'
|
|
160
|
-
: `🔴 Pre-flight bloqueado: corrige ${
|
|
161
|
-
const instruction = evaluation.allowed
|
|
188
|
+
: `🔴 Pre-flight bloqueado: corrige ${reasonCode} y vuelve a ejecutar.`;
|
|
189
|
+
const instruction = !firstViolation && evaluation.allowed
|
|
162
190
|
? 'Implementa el cambio mínimo para pasar en verde y vuelve a validar.'
|
|
163
|
-
:
|
|
191
|
+
: governanceAction.instruction;
|
|
192
|
+
const nextAction: GovernanceCatalogNextAction = evaluation.allowed
|
|
193
|
+
? {
|
|
194
|
+
kind: 'info',
|
|
195
|
+
message: governanceAction.next_action.message,
|
|
196
|
+
}
|
|
197
|
+
: governanceAction.next_action;
|
|
164
198
|
|
|
165
199
|
return {
|
|
166
200
|
tool: 'pre_flight_check',
|
|
@@ -173,6 +207,8 @@ export const runEnterprisePreFlightCheck = (params: {
|
|
|
173
207
|
phase,
|
|
174
208
|
message,
|
|
175
209
|
instruction,
|
|
210
|
+
reason_code: reasonCode,
|
|
211
|
+
next_action: nextAction,
|
|
176
212
|
stage: evaluation.stage,
|
|
177
213
|
policy: evaluation.policy,
|
|
178
214
|
violations: evaluation.violations,
|