pumuki 6.3.129 → 6.3.130
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/AGENTS.md +16 -1
- package/CHANGELOG.md +9 -0
- package/README.md +14 -10
- package/VERSION +1 -1
- package/core/facts/detectors/text/android.test.ts +2583 -24
- package/core/facts/detectors/text/android.ts +4633 -34
- package/core/facts/detectors/typescript/index.test.ts +3639 -73
- package/core/facts/detectors/typescript/index.ts +4819 -270
- package/core/facts/extractHeuristicFacts.ts +271 -6
- package/core/rules/presets/heuristics/android.test.ts +314 -1
- package/core/rules/presets/heuristics/android.ts +1220 -50
- package/core/rules/presets/heuristics/typescript.test.ts +158 -2
- package/core/rules/presets/heuristics/typescript.ts +508 -0
- package/docs/README.md +3 -3
- package/docs/operations/RELEASE_NOTES.md +7 -1
- package/docs/operations/framework-menu-consumer-walkthrough.md +18 -15
- package/docs/product/API_REFERENCE.md +1 -1
- package/docs/product/USAGE.md +1 -1
- package/docs/validation/README.md +3 -1
- package/integrations/config/skillsCompilerTemplates.test.ts +131 -0
- package/integrations/config/skillsCompilerTemplates.ts +953 -7
- package/integrations/config/skillsDetectorRegistry.ts +451 -8
- package/integrations/config/skillsMarkdownRules.ts +884 -2
- package/integrations/evidence/buildEvidence.ts +29 -1
- package/integrations/evidence/platformSummary.test.ts +73 -9
- package/integrations/evidence/platformSummary.ts +165 -7
- package/integrations/evidence/repoState.ts +3 -0
- package/integrations/evidence/schema.ts +18 -0
- package/integrations/evidence/trackingContract.ts +17 -0
- package/integrations/evidence/writeEvidence.test.ts +3 -0
- package/integrations/evidence/writeEvidence.ts +29 -1
- package/integrations/gate/evaluateAiGate.ts +251 -8
- package/integrations/gate/governanceActionCatalog.ts +275 -0
- package/integrations/gate/remediationCatalog.ts +8 -0
- package/integrations/git/runPlatformGate.ts +9 -1
- package/integrations/lifecycle/adapter.ts +24 -0
- package/integrations/lifecycle/bootstrapManifest.ts +248 -0
- package/integrations/lifecycle/cliGovernanceConsole.ts +69 -0
- package/integrations/lifecycle/governanceNextAction.ts +171 -0
- package/integrations/lifecycle/governanceObservationSnapshot.ts +369 -0
- package/integrations/lifecycle/packageInfo.ts +118 -1
- package/integrations/lifecycle/state.ts +8 -1
- package/integrations/lifecycle/trackingState.ts +403 -0
- package/integrations/lifecycle/watch.ts +1 -1
- package/integrations/mcp/aiGateCheck.ts +194 -10
- package/integrations/mcp/alignedPlatformGate.ts +232 -0
- package/integrations/mcp/enterpriseServer.ts +19 -3
- package/integrations/mcp/preFlightCheck.ts +66 -3
- package/integrations/mcp/readMcpPrePushStdin.ts +7 -0
- package/package.json +1 -1
- package/scripts/build-ruralgo-s1-evidence-pack.ts +85 -0
- package/scripts/check-tracking-single-active.sh +1 -1
- package/scripts/framework-menu-advanced-view-lib.ts +49 -0
- package/scripts/framework-menu-consumer-actions-lib.ts +32 -32
- package/scripts/framework-menu-consumer-preflight-render.ts +10 -0
- package/scripts/framework-menu-consumer-preflight-run.ts +23 -0
- package/scripts/framework-menu-consumer-preflight-types.ts +12 -0
- package/scripts/framework-menu-consumer-runtime-actions.ts +11 -5
- package/scripts/framework-menu-consumer-runtime-audit.ts +0 -28
- package/scripts/framework-menu-consumer-runtime-evidence-classic.ts +118 -42
- package/scripts/framework-menu-consumer-runtime-lib.ts +38 -0
- package/scripts/framework-menu-consumer-runtime-menu.ts +55 -15
- package/scripts/framework-menu-consumer-runtime-types.ts +4 -0
- package/scripts/framework-menu-evidence-summary-read.ts +17 -1
- package/scripts/framework-menu-evidence-summary-types.ts +3 -0
- package/scripts/framework-menu-layout-data.ts +3 -23
- package/scripts/framework-menu-system-notifications-macos-applescript-dialog.ts +1 -1
- package/scripts/framework-menu-system-notifications-macos-dialog-payload.ts +14 -2
- package/scripts/framework-menu-system-notifications-macos-swift-source.ts +1 -1
- package/scripts/framework-menu-system-notifications-payloads-blocked.ts +128 -4
- package/scripts/framework-menu-system-notifications-payloads.ts +8 -1
- package/scripts/framework-menu-system-notifications-text.ts +7 -1
- package/scripts/framework-menu.ts +37 -2
- package/scripts/package-install-smoke-consumer-git-repo-lib.ts +10 -1
- package/scripts/package-install-smoke-consumer-npm-lib.ts +46 -9
- package/scripts/ruralgo-s1-evidence-pack-lib.ts +200 -0
- package/skills.lock.json +613 -698
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, relative, resolve } from 'node:path';
|
|
3
|
+
|
|
4
|
+
export type TrackingDeclaration = {
|
|
5
|
+
source_file: string;
|
|
6
|
+
declared_path: string;
|
|
7
|
+
resolved_path: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type RepoTrackingState = {
|
|
11
|
+
enforced: boolean;
|
|
12
|
+
canonical_path: string | null;
|
|
13
|
+
canonical_present: boolean;
|
|
14
|
+
source_file: string | null;
|
|
15
|
+
in_progress_count: number;
|
|
16
|
+
single_in_progress_valid: boolean;
|
|
17
|
+
conflict: boolean;
|
|
18
|
+
declarations: ReadonlyArray<TrackingDeclaration>;
|
|
19
|
+
in_progress_entries?: ReadonlyArray<{
|
|
20
|
+
line_number: number;
|
|
21
|
+
task_id: string | null;
|
|
22
|
+
}>;
|
|
23
|
+
active_task_id?: string | null;
|
|
24
|
+
last_run_path?: string | null;
|
|
25
|
+
last_run_status?: string | null;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const formatTrackingActionableContext = (tracking: RepoTrackingState): string | null => {
|
|
29
|
+
const activeEntries = (tracking.in_progress_entries ?? [])
|
|
30
|
+
.map((entry) => `${entry.task_id ?? 'UNKNOWN'}@L${entry.line_number}`)
|
|
31
|
+
.join(', ');
|
|
32
|
+
if (!activeEntries) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
const lastRunStatus = tracking.last_run_status ?? 'absent';
|
|
36
|
+
return `active_entries=${activeEntries} last_run_status=${lastRunStatus}`;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
type TrackingDeclarationSource = {
|
|
40
|
+
sourceFile: string;
|
|
41
|
+
priority: number;
|
|
42
|
+
patterns: ReadonlyArray<RegExp>;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
type TrackingCandidate = TrackingDeclaration & {
|
|
46
|
+
priority: number;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
type InProgressEntry = {
|
|
50
|
+
lineNumber: number;
|
|
51
|
+
taskId: string | null;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const TRACKING_DECLARATION_SOURCES: ReadonlyArray<TrackingDeclarationSource> = [
|
|
55
|
+
{
|
|
56
|
+
sourceFile: 'AGENTS.md',
|
|
57
|
+
priority: 100,
|
|
58
|
+
patterns: [
|
|
59
|
+
/[uú]nica fuente viva del tracking interno es [`']([^`'\n]+)[`']/i,
|
|
60
|
+
/ningun documento de seguimiento fuera de [`']([^`'\n]+)[`']/i,
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
sourceFile: 'docs/README.md',
|
|
65
|
+
priority: 80,
|
|
66
|
+
patterns: [
|
|
67
|
+
/todo el seguimiento operativo[\s\S]{0,240}?\[[^\]]+\]\(([^)]+)\)/i,
|
|
68
|
+
/[uú]nica fuente de verdad[^`\n]*[`']([^`'\n]+)[`']/i,
|
|
69
|
+
/fuente viva del tracking interno:\s*[`']([^`'\n]+)[`']/i,
|
|
70
|
+
/quiero saber en qué estamos ahora:\s*[\s\S]{0,80}[-*]\s*[`']([^`'\n]+)[`']/i,
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
sourceFile: 'docs/validation/README.md',
|
|
75
|
+
priority: 60,
|
|
76
|
+
patterns: [/[uú]nica fuente de seguimiento:\s*[`']([^`'\n]+)[`']/i],
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
const toRepoRelativePath = (repoRoot: string, candidatePath: string): string | null => {
|
|
81
|
+
const repoRootAbsolute = resolve(repoRoot);
|
|
82
|
+
const resolved = resolve(candidatePath);
|
|
83
|
+
const rel = relative(repoRootAbsolute, resolved);
|
|
84
|
+
if (
|
|
85
|
+
rel === '..' ||
|
|
86
|
+
rel.startsWith('../') ||
|
|
87
|
+
rel.startsWith('..\\')
|
|
88
|
+
) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
return rel.length > 0 ? rel.split('\\').join('/') : '.';
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const normalizeDeclaredPath = (value: string): string => value.trim().replace(/[#?].*$/, '');
|
|
95
|
+
|
|
96
|
+
const resolveDeclaredPath = (params: {
|
|
97
|
+
repoRoot: string;
|
|
98
|
+
sourceFile: string;
|
|
99
|
+
declaredPath: string;
|
|
100
|
+
}): string | null => {
|
|
101
|
+
const sourceAbsolutePath = resolve(params.repoRoot, params.sourceFile);
|
|
102
|
+
const candidate = normalizeDeclaredPath(params.declaredPath);
|
|
103
|
+
if (candidate.length === 0) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
const resolved = candidate.startsWith('./') || candidate.startsWith('../')
|
|
107
|
+
? resolve(dirname(sourceAbsolutePath), candidate)
|
|
108
|
+
: resolve(params.repoRoot, candidate);
|
|
109
|
+
return toRepoRelativePath(params.repoRoot, resolved);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const collectTrackingCandidates = (repoRoot: string): ReadonlyArray<TrackingCandidate> => {
|
|
113
|
+
const candidates = new Map<string, TrackingCandidate>();
|
|
114
|
+
|
|
115
|
+
for (const source of TRACKING_DECLARATION_SOURCES) {
|
|
116
|
+
const sourcePath = resolve(repoRoot, source.sourceFile);
|
|
117
|
+
if (!existsSync(sourcePath)) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const markdown = readFileSync(sourcePath, 'utf8');
|
|
121
|
+
for (const pattern of source.patterns) {
|
|
122
|
+
const match = pattern.exec(markdown);
|
|
123
|
+
const declaredPath = match?.[1]?.trim();
|
|
124
|
+
if (!declaredPath) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const resolvedPath = resolveDeclaredPath({
|
|
128
|
+
repoRoot,
|
|
129
|
+
sourceFile: source.sourceFile,
|
|
130
|
+
declaredPath,
|
|
131
|
+
});
|
|
132
|
+
if (!resolvedPath) {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
const key = `${source.sourceFile}::${resolvedPath}`;
|
|
136
|
+
if (candidates.has(key)) {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
candidates.set(key, {
|
|
140
|
+
source_file: source.sourceFile,
|
|
141
|
+
declared_path: declaredPath,
|
|
142
|
+
resolved_path: resolvedPath,
|
|
143
|
+
priority: source.priority,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return Array.from(candidates.values()).sort((left, right) => {
|
|
149
|
+
if (right.priority !== left.priority) {
|
|
150
|
+
return right.priority - left.priority;
|
|
151
|
+
}
|
|
152
|
+
const bySource = left.source_file.localeCompare(right.source_file);
|
|
153
|
+
return bySource !== 0 ? bySource : left.resolved_path.localeCompare(right.resolved_path);
|
|
154
|
+
});
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const extractTaskId = (value: string): string | null => {
|
|
158
|
+
const directMatch = value.match(/\b([A-Z][A-Z0-9]*(?:-[A-Z0-9]+)+)\b/);
|
|
159
|
+
if (directMatch?.[1]) {
|
|
160
|
+
return directMatch[1];
|
|
161
|
+
}
|
|
162
|
+
const dottedMatch = value.match(/\b([A-Z][A-Z0-9]*(?:[.-][A-Z0-9]+)+)\b/);
|
|
163
|
+
return dottedMatch?.[1] ?? null;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const collectInProgressEntries = (markdown: string): ReadonlyArray<InProgressEntry> => {
|
|
167
|
+
const lines = markdown.split(/\r?\n/);
|
|
168
|
+
const entries: InProgressEntry[] = [];
|
|
169
|
+
|
|
170
|
+
lines.forEach((line, index) => {
|
|
171
|
+
const trimmed = line.trim();
|
|
172
|
+
if (trimmed.length === 0) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (trimmed.startsWith('|')) {
|
|
177
|
+
const cells = trimmed
|
|
178
|
+
.split('|')
|
|
179
|
+
.map((cell) => cell.trim())
|
|
180
|
+
.filter((cell) => cell.length > 0);
|
|
181
|
+
const firstCell = cells[0] ?? '';
|
|
182
|
+
const secondCell = cells[1] ?? '';
|
|
183
|
+
if (firstCell === '🚧') {
|
|
184
|
+
entries.push({
|
|
185
|
+
lineNumber: index + 1,
|
|
186
|
+
taskId: extractTaskId(secondCell),
|
|
187
|
+
});
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const bracketCell = cells.find((cell) => /\[\s*🚧\s*\]\s*-/.test(cell));
|
|
191
|
+
if (bracketCell) {
|
|
192
|
+
entries.push({
|
|
193
|
+
lineNumber: index + 1,
|
|
194
|
+
taskId: extractTaskId(bracketCell),
|
|
195
|
+
});
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const emojiCell = cells.find((cell) => /^🚧(?:\b|\s|[*_`])/.test(cell));
|
|
199
|
+
if (emojiCell) {
|
|
200
|
+
const taskCell = cells.find((cell) => cell !== emojiCell && !/^[:\- ]+$/.test(cell)) ?? emojiCell;
|
|
201
|
+
entries.push({
|
|
202
|
+
lineNumber: index + 1,
|
|
203
|
+
taskId: extractTaskId(taskCell),
|
|
204
|
+
});
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (/^- Estado:\s*🚧(?:\s|$)/.test(trimmed) || /\[\s*🚧\s*\]\s*-/.test(trimmed)) {
|
|
210
|
+
entries.push({
|
|
211
|
+
lineNumber: index + 1,
|
|
212
|
+
taskId: extractTaskId(trimmed),
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
return entries;
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const readActiveTaskHeader = (markdown: string): string | null => {
|
|
221
|
+
const explicitHeader = markdown.match(/- Tarea activa principal:\s*`([^`]+)`/i)?.[1]?.trim();
|
|
222
|
+
if (explicitHeader && explicitHeader.length > 0) {
|
|
223
|
+
return extractTaskId(explicitHeader) ?? explicitHeader;
|
|
224
|
+
}
|
|
225
|
+
const bracketTask = markdown.match(/\[\s*🚧\s*\]\s*-\s*(.+)$/m)?.[1]?.trim();
|
|
226
|
+
if (!bracketTask || bracketTask.length === 0) {
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
return extractTaskId(bracketTask) ?? bracketTask;
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const readLastRunAlignment = (params: {
|
|
233
|
+
repoRoot: string;
|
|
234
|
+
canonicalPath: string;
|
|
235
|
+
activeTaskId: string | null;
|
|
236
|
+
inProgressCount: number;
|
|
237
|
+
activeTaskHeader: string | null;
|
|
238
|
+
}): {
|
|
239
|
+
path: string | null;
|
|
240
|
+
status: string | null;
|
|
241
|
+
valid: boolean;
|
|
242
|
+
} => {
|
|
243
|
+
const lastRunPath = resolve(params.repoRoot, 'docs', 'validation', 'refactor', 'last-run.json');
|
|
244
|
+
if (!existsSync(lastRunPath)) {
|
|
245
|
+
return {
|
|
246
|
+
path: null,
|
|
247
|
+
status: null,
|
|
248
|
+
valid: params.inProgressCount === 1,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
let parsed: unknown;
|
|
253
|
+
try {
|
|
254
|
+
parsed = JSON.parse(readFileSync(lastRunPath, 'utf8')) as unknown;
|
|
255
|
+
} catch {
|
|
256
|
+
return {
|
|
257
|
+
path: toRepoRelativePath(params.repoRoot, lastRunPath),
|
|
258
|
+
status: null,
|
|
259
|
+
valid: false,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
264
|
+
return {
|
|
265
|
+
path: toRepoRelativePath(params.repoRoot, lastRunPath),
|
|
266
|
+
status: null,
|
|
267
|
+
valid: false,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const payload = parsed as Record<string, unknown>;
|
|
272
|
+
const status = typeof payload.status === 'string' ? payload.status.trim().toUpperCase() : null;
|
|
273
|
+
const planFile = typeof payload.plan_file === 'string' ? payload.plan_file.trim() : null;
|
|
274
|
+
const nextValue = typeof payload.next === 'string' ? payload.next : '';
|
|
275
|
+
const lastRunRelativePath = toRepoRelativePath(params.repoRoot, lastRunPath);
|
|
276
|
+
const planMatches = planFile === params.canonicalPath;
|
|
277
|
+
|
|
278
|
+
if (status === 'IN_PROGRESS') {
|
|
279
|
+
const headerMatches =
|
|
280
|
+
!params.activeTaskHeader ||
|
|
281
|
+
params.activeTaskHeader === params.activeTaskId ||
|
|
282
|
+
(params.activeTaskHeader === 'NINGUNA' && params.activeTaskId === null);
|
|
283
|
+
const nextMatches = !params.activeTaskId || nextValue.includes(params.activeTaskId);
|
|
284
|
+
return {
|
|
285
|
+
path: lastRunRelativePath,
|
|
286
|
+
status,
|
|
287
|
+
valid:
|
|
288
|
+
planMatches &&
|
|
289
|
+
params.inProgressCount === 1 &&
|
|
290
|
+
headerMatches &&
|
|
291
|
+
nextMatches,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (status === 'DONE') {
|
|
296
|
+
return {
|
|
297
|
+
path: lastRunRelativePath,
|
|
298
|
+
status,
|
|
299
|
+
valid:
|
|
300
|
+
planMatches &&
|
|
301
|
+
params.inProgressCount === 0 &&
|
|
302
|
+
(params.activeTaskHeader === null || params.activeTaskHeader === 'NINGUNA'),
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return {
|
|
307
|
+
path: lastRunRelativePath,
|
|
308
|
+
status,
|
|
309
|
+
valid: false,
|
|
310
|
+
};
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
export const resolveRepoTrackingState = (repoRoot: string): RepoTrackingState => {
|
|
314
|
+
const declarations = collectTrackingCandidates(repoRoot);
|
|
315
|
+
const enforced = declarations.length > 0;
|
|
316
|
+
if (!enforced) {
|
|
317
|
+
return {
|
|
318
|
+
enforced: false,
|
|
319
|
+
canonical_path: null,
|
|
320
|
+
canonical_present: false,
|
|
321
|
+
source_file: null,
|
|
322
|
+
in_progress_count: 0,
|
|
323
|
+
single_in_progress_valid: false,
|
|
324
|
+
conflict: false,
|
|
325
|
+
declarations: [],
|
|
326
|
+
in_progress_entries: [],
|
|
327
|
+
active_task_id: null,
|
|
328
|
+
last_run_path: null,
|
|
329
|
+
last_run_status: null,
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const highestPriority = declarations[0]?.priority ?? 0;
|
|
334
|
+
const highestPriorityDeclarations = declarations.filter(
|
|
335
|
+
(declaration) => declaration.priority === highestPriority
|
|
336
|
+
);
|
|
337
|
+
const uniqueCanonicalPaths = Array.from(
|
|
338
|
+
new Set(highestPriorityDeclarations.map((declaration) => declaration.resolved_path))
|
|
339
|
+
);
|
|
340
|
+
const allDeclaredPaths = Array.from(
|
|
341
|
+
new Set(declarations.map((declaration) => declaration.resolved_path))
|
|
342
|
+
);
|
|
343
|
+
const canonicalPath = uniqueCanonicalPaths.length === 1 ? uniqueCanonicalPaths[0] : null;
|
|
344
|
+
const canonicalPresent =
|
|
345
|
+
typeof canonicalPath === 'string' && canonicalPath.length > 0
|
|
346
|
+
? existsSync(resolve(repoRoot, canonicalPath))
|
|
347
|
+
: false;
|
|
348
|
+
const sourceFile =
|
|
349
|
+
canonicalPath === null
|
|
350
|
+
? null
|
|
351
|
+
: highestPriorityDeclarations.find(
|
|
352
|
+
(declaration) => declaration.resolved_path === canonicalPath
|
|
353
|
+
)?.source_file ?? null;
|
|
354
|
+
|
|
355
|
+
if (!canonicalPath || !canonicalPresent) {
|
|
356
|
+
return {
|
|
357
|
+
enforced: true,
|
|
358
|
+
canonical_path: canonicalPath,
|
|
359
|
+
canonical_present: canonicalPresent,
|
|
360
|
+
source_file: sourceFile,
|
|
361
|
+
in_progress_count: 0,
|
|
362
|
+
single_in_progress_valid: false,
|
|
363
|
+
conflict: allDeclaredPaths.length > 1,
|
|
364
|
+
declarations: declarations.map(({ priority: _priority, ...declaration }) => declaration),
|
|
365
|
+
in_progress_entries: [],
|
|
366
|
+
active_task_id: null,
|
|
367
|
+
last_run_path: null,
|
|
368
|
+
last_run_status: null,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const markdown = readFileSync(resolve(repoRoot, canonicalPath), 'utf8');
|
|
373
|
+
const inProgressEntries = collectInProgressEntries(markdown);
|
|
374
|
+
const inProgressCount = inProgressEntries.length;
|
|
375
|
+
const activeTaskId = inProgressEntries[0]?.taskId ?? null;
|
|
376
|
+
const activeTaskHeader = readActiveTaskHeader(markdown);
|
|
377
|
+
const lastRunAlignment = readLastRunAlignment({
|
|
378
|
+
repoRoot,
|
|
379
|
+
canonicalPath,
|
|
380
|
+
activeTaskId,
|
|
381
|
+
inProgressCount,
|
|
382
|
+
activeTaskHeader,
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
return {
|
|
386
|
+
enforced: true,
|
|
387
|
+
canonical_path: canonicalPath,
|
|
388
|
+
canonical_present: true,
|
|
389
|
+
source_file: sourceFile,
|
|
390
|
+
in_progress_count: inProgressCount,
|
|
391
|
+
single_in_progress_valid:
|
|
392
|
+
uniqueCanonicalPaths.length === 1 && lastRunAlignment.valid,
|
|
393
|
+
conflict: allDeclaredPaths.length > 1,
|
|
394
|
+
declarations: declarations.map(({ priority: _priority, ...declaration }) => declaration),
|
|
395
|
+
in_progress_entries: inProgressEntries.map((entry) => ({
|
|
396
|
+
line_number: entry.lineNumber,
|
|
397
|
+
task_id: entry.taskId,
|
|
398
|
+
})),
|
|
399
|
+
active_task_id: activeTaskId,
|
|
400
|
+
last_run_path: lastRunAlignment.path,
|
|
401
|
+
last_run_status: lastRunAlignment.status,
|
|
402
|
+
};
|
|
403
|
+
};
|
|
@@ -736,7 +736,7 @@ export const runLifecycleWatch = async (
|
|
|
736
736
|
driftFromRuntime,
|
|
737
737
|
driftWarning,
|
|
738
738
|
alignmentCommand: driftFromRuntime
|
|
739
|
-
? buildLifecycleAlignmentCommand(versionMetadata.runtimeVersion)
|
|
739
|
+
? buildLifecycleAlignmentCommand(versionMetadata.runtimeVersion, repoRoot)
|
|
740
740
|
: null,
|
|
741
741
|
},
|
|
742
742
|
stage,
|
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
import { evaluateAiGate, type AiGateStage } from '../gate/evaluateAiGate';
|
|
2
2
|
import { resolveRemediationHintForViolationCode } from '../gate/remediationCatalog';
|
|
3
|
+
import { readLifecyclePolicyValidationSnapshot } from '../lifecycle/policyValidationSnapshot';
|
|
3
4
|
import { resolveLearningContextExperimentalFeature } from '../policy/experimentalFeatures';
|
|
5
|
+
import { resolvePreWriteEnforcement } from '../policy/preWriteEnforcement';
|
|
4
6
|
import { readSddLearningContext, type SddLearningContext } from '../sdd/learningInsights';
|
|
7
|
+
import { runMcpAlignedPlatformGate } from './alignedPlatformGate';
|
|
5
8
|
|
|
6
9
|
const PROTECTED_BRANCHES = new Set(['main', 'master', 'develop', 'dev']);
|
|
7
10
|
|
|
11
|
+
type PlatformGateAlignment = {
|
|
12
|
+
mode: 'full' | 'policy';
|
|
13
|
+
exit_code: number;
|
|
14
|
+
aligned: boolean;
|
|
15
|
+
skip_reason: string | null;
|
|
16
|
+
};
|
|
17
|
+
|
|
8
18
|
export type EnterpriseAiGateCheckResult = {
|
|
9
19
|
tool: 'ai_gate_check';
|
|
10
20
|
dryRun: true;
|
|
@@ -16,6 +26,19 @@ export type EnterpriseAiGateCheckResult = {
|
|
|
16
26
|
timestamp: string | null;
|
|
17
27
|
branch: string | null;
|
|
18
28
|
message: string;
|
|
29
|
+
reason_code: string;
|
|
30
|
+
instruction: string;
|
|
31
|
+
prewrite_effective: {
|
|
32
|
+
mode: ReturnType<typeof resolvePreWriteEnforcement>['mode'];
|
|
33
|
+
source: ReturnType<typeof resolvePreWriteEnforcement>['source'];
|
|
34
|
+
blocking: boolean;
|
|
35
|
+
strict_policy: boolean;
|
|
36
|
+
};
|
|
37
|
+
next_action: {
|
|
38
|
+
kind: 'info';
|
|
39
|
+
reason: string;
|
|
40
|
+
message: string;
|
|
41
|
+
};
|
|
19
42
|
stage: ReturnType<typeof evaluateAiGate>['stage'];
|
|
20
43
|
policy: ReturnType<typeof evaluateAiGate>['policy'];
|
|
21
44
|
violations: ReturnType<typeof evaluateAiGate>['violations'];
|
|
@@ -31,6 +54,7 @@ export type EnterpriseAiGateCheckResult = {
|
|
|
31
54
|
reason_code: 'HOOK_RUNNER_CAN_REFRESH_EVIDENCE' | null;
|
|
32
55
|
message: string;
|
|
33
56
|
};
|
|
57
|
+
platform_gate_alignment?: PlatformGateAlignment;
|
|
34
58
|
};
|
|
35
59
|
};
|
|
36
60
|
|
|
@@ -41,23 +65,18 @@ const isHookRefreshableEvidenceCode = (code: string): boolean =>
|
|
|
41
65
|
|
|
42
66
|
type AiGateCheckDependencies = {
|
|
43
67
|
evaluateAiGate: typeof evaluateAiGate;
|
|
68
|
+
runMcpAlignedPlatformGate: typeof runMcpAlignedPlatformGate;
|
|
44
69
|
};
|
|
45
70
|
|
|
46
71
|
const defaultDependencies: AiGateCheckDependencies = {
|
|
47
72
|
evaluateAiGate,
|
|
73
|
+
runMcpAlignedPlatformGate,
|
|
48
74
|
};
|
|
49
75
|
|
|
50
76
|
const buildConsistencyHint = (
|
|
51
|
-
evaluation: ReturnType<typeof evaluateAiGate
|
|
77
|
+
evaluation: ReturnType<typeof evaluateAiGate>,
|
|
78
|
+
platform?: { exitCode: number; aligned: boolean; skipReason: string | null }
|
|
52
79
|
): EnterpriseAiGateCheckResult['result']['consistency_hint'] => {
|
|
53
|
-
if (!HOOK_STAGE_SET.has(evaluation.stage)) {
|
|
54
|
-
return {
|
|
55
|
-
comparable_with_hook_runner: true,
|
|
56
|
-
reason_code: null,
|
|
57
|
-
message: 'Stage is directly comparable with ai_gate_check semantics.',
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
80
|
const hasRefreshableEvidenceViolation = evaluation.violations.some((violation) =>
|
|
62
81
|
isHookRefreshableEvidenceCode(violation.code)
|
|
63
82
|
);
|
|
@@ -72,6 +91,24 @@ const buildConsistencyHint = (
|
|
|
72
91
|
};
|
|
73
92
|
}
|
|
74
93
|
|
|
94
|
+
if (platform?.aligned) {
|
|
95
|
+
return {
|
|
96
|
+
comparable_with_hook_runner: true,
|
|
97
|
+
reason_code: null,
|
|
98
|
+
message:
|
|
99
|
+
`ai_gate_check ejecutó runPlatformGate después de leer la evidencia actual (exit_code=${platform.exitCode}); ` +
|
|
100
|
+
'alineación hook-like habilitada explícitamente para este stage.',
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!HOOK_STAGE_SET.has(evaluation.stage)) {
|
|
105
|
+
return {
|
|
106
|
+
comparable_with_hook_runner: true,
|
|
107
|
+
reason_code: null,
|
|
108
|
+
message: 'Stage is directly comparable with ai_gate_check semantics.',
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
75
112
|
return {
|
|
76
113
|
comparable_with_hook_runner: true,
|
|
77
114
|
reason_code: null,
|
|
@@ -118,7 +155,31 @@ const buildAutoFixes = (
|
|
|
118
155
|
return fixes;
|
|
119
156
|
};
|
|
120
157
|
|
|
121
|
-
const
|
|
158
|
+
const buildReasonCode = (evaluation: ReturnType<typeof evaluateAiGate>): string =>
|
|
159
|
+
evaluation.violations[0]?.code ?? 'AI_GATE_ALLOWED';
|
|
160
|
+
|
|
161
|
+
const buildInstruction = (evaluation: ReturnType<typeof evaluateAiGate>): string =>
|
|
162
|
+
evaluation.allowed
|
|
163
|
+
? 'Continúa con el stage actual manteniendo el vocabulario canónico de governance.'
|
|
164
|
+
: 'Corrige el bloqueante primario y vuelve a ejecutar ai_gate_check en el mismo stage.';
|
|
165
|
+
|
|
166
|
+
const buildNextAction = (
|
|
167
|
+
evaluation: ReturnType<typeof evaluateAiGate>,
|
|
168
|
+
autoFixes: ReadonlyArray<string>
|
|
169
|
+
): EnterpriseAiGateCheckResult['result']['next_action'] => ({
|
|
170
|
+
kind: 'info',
|
|
171
|
+
reason: buildReasonCode(evaluation),
|
|
172
|
+
message: autoFixes[0] ?? buildInstruction(evaluation),
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const buildMessage = (
|
|
176
|
+
evaluation: ReturnType<typeof evaluateAiGate>,
|
|
177
|
+
platform?: { exitCode: number; skipReason: string | null }
|
|
178
|
+
): string => {
|
|
179
|
+
if (platform && platform.exitCode !== 0) {
|
|
180
|
+
const suffix = platform.skipReason ? ` (${platform.skipReason})` : '';
|
|
181
|
+
return `🔴 runPlatformGate exit_code=${platform.exitCode}${suffix}.`;
|
|
182
|
+
}
|
|
122
183
|
if (evaluation.allowed) {
|
|
123
184
|
return `✅ Gate ${evaluation.stage} ALLOWED.`;
|
|
124
185
|
}
|
|
@@ -129,6 +190,26 @@ const buildMessage = (evaluation: ReturnType<typeof evaluateAiGate>): string =>
|
|
|
129
190
|
return `🔴 ${firstViolation.code}: ${firstViolation.message}`;
|
|
130
191
|
};
|
|
131
192
|
|
|
193
|
+
const resolveAiGateCheckMode = (): PlatformGateAlignment['mode'] => {
|
|
194
|
+
const raw = process.env.PUMUKI_MCP_AI_GATE_CHECK_MODE?.trim().toLowerCase();
|
|
195
|
+
return raw === 'full' || raw === 'aligned' ? 'full' : 'policy';
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const toPlatformGateAlignment = (
|
|
199
|
+
mode: PlatformGateAlignment['mode'],
|
|
200
|
+
platform?: { exitCode: number; aligned: boolean; skipReason: string | null }
|
|
201
|
+
): PlatformGateAlignment | undefined => {
|
|
202
|
+
if (mode !== 'full' || !platform) {
|
|
203
|
+
return undefined;
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
mode,
|
|
207
|
+
exit_code: platform.exitCode,
|
|
208
|
+
aligned: platform.aligned,
|
|
209
|
+
skip_reason: platform.skipReason,
|
|
210
|
+
};
|
|
211
|
+
};
|
|
212
|
+
|
|
132
213
|
export const runEnterpriseAiGateCheck = (params: {
|
|
133
214
|
repoRoot: string;
|
|
134
215
|
stage: AiGateStage;
|
|
@@ -151,6 +232,8 @@ export const runEnterpriseAiGateCheck = (params: {
|
|
|
151
232
|
: readSddLearningContext({
|
|
152
233
|
repoRoot: params.repoRoot,
|
|
153
234
|
});
|
|
235
|
+
const preWriteEnforcement = resolvePreWriteEnforcement();
|
|
236
|
+
const policyValidation = readLifecyclePolicyValidationSnapshot(params.repoRoot);
|
|
154
237
|
const warnings = buildWarnings(evaluation);
|
|
155
238
|
const autoFixes = buildAutoFixes(evaluation, learningContext);
|
|
156
239
|
const message = buildMessage(evaluation);
|
|
@@ -166,6 +249,15 @@ export const runEnterpriseAiGateCheck = (params: {
|
|
|
166
249
|
timestamp,
|
|
167
250
|
branch,
|
|
168
251
|
message,
|
|
252
|
+
reason_code: buildReasonCode(evaluation),
|
|
253
|
+
instruction: buildInstruction(evaluation),
|
|
254
|
+
prewrite_effective: {
|
|
255
|
+
mode: preWriteEnforcement.mode,
|
|
256
|
+
source: preWriteEnforcement.source,
|
|
257
|
+
blocking: preWriteEnforcement.blocking,
|
|
258
|
+
strict_policy: policyValidation.stages.PRE_WRITE.strict,
|
|
259
|
+
},
|
|
260
|
+
next_action: buildNextAction(evaluation, autoFixes),
|
|
169
261
|
stage: evaluation.stage,
|
|
170
262
|
policy: evaluation.policy,
|
|
171
263
|
violations: evaluation.violations,
|
|
@@ -180,3 +272,95 @@ export const runEnterpriseAiGateCheck = (params: {
|
|
|
180
272
|
},
|
|
181
273
|
};
|
|
182
274
|
};
|
|
275
|
+
|
|
276
|
+
export const runEnterpriseAiGateCheckAsync = async (params: {
|
|
277
|
+
repoRoot: string;
|
|
278
|
+
stage: AiGateStage;
|
|
279
|
+
requireMcpReceipt?: boolean;
|
|
280
|
+
}, dependencies: Partial<AiGateCheckDependencies> = {}): Promise<EnterpriseAiGateCheckResult> => {
|
|
281
|
+
const mode = resolveAiGateCheckMode();
|
|
282
|
+
const activeDependencies: AiGateCheckDependencies = {
|
|
283
|
+
...defaultDependencies,
|
|
284
|
+
...dependencies,
|
|
285
|
+
};
|
|
286
|
+
const evaluation = activeDependencies.evaluateAiGate({
|
|
287
|
+
repoRoot: params.repoRoot,
|
|
288
|
+
stage: params.stage,
|
|
289
|
+
requireMcpReceipt: params.requireMcpReceipt ?? false,
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
let platform:
|
|
293
|
+
| { exitCode: number; aligned: boolean; skipReason: string | null }
|
|
294
|
+
| undefined;
|
|
295
|
+
if (mode === 'full') {
|
|
296
|
+
platform = await activeDependencies.runMcpAlignedPlatformGate({
|
|
297
|
+
repoRoot: params.repoRoot,
|
|
298
|
+
stage: params.stage,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const platformBlocks = Boolean(platform && platform.exitCode !== 0);
|
|
303
|
+
const allowed = evaluation.allowed && !platformBlocks;
|
|
304
|
+
const status: 'ALLOWED' | 'BLOCKED' = allowed ? 'ALLOWED' : 'BLOCKED';
|
|
305
|
+
const violations = platformBlocks && platform
|
|
306
|
+
? [
|
|
307
|
+
...evaluation.violations,
|
|
308
|
+
{
|
|
309
|
+
code: 'PLATFORM_GATE_EXIT_NON_ZERO',
|
|
310
|
+
message:
|
|
311
|
+
`runPlatformGate devolvió exit_code=${platform.exitCode}` +
|
|
312
|
+
(platform.skipReason ? ` (${platform.skipReason})` : ''),
|
|
313
|
+
severity: 'ERROR' as const,
|
|
314
|
+
},
|
|
315
|
+
]
|
|
316
|
+
: evaluation.violations;
|
|
317
|
+
const branch = evaluation.repo_state.git.branch;
|
|
318
|
+
const timestamp = evaluation.evidence.source.generated_at;
|
|
319
|
+
const learningContextFeature = resolveLearningContextExperimentalFeature();
|
|
320
|
+
const learningContext = learningContextFeature.mode === 'off'
|
|
321
|
+
? null
|
|
322
|
+
: readSddLearningContext({
|
|
323
|
+
repoRoot: params.repoRoot,
|
|
324
|
+
});
|
|
325
|
+
const evaluationForHints = { ...evaluation, allowed, status, violations };
|
|
326
|
+
const warnings = buildWarnings(evaluationForHints);
|
|
327
|
+
const autoFixes = buildAutoFixes(evaluationForHints, learningContext);
|
|
328
|
+
const message = buildMessage(evaluationForHints, platform);
|
|
329
|
+
const preWriteEnforcement = resolvePreWriteEnforcement();
|
|
330
|
+
const policyValidation = readLifecyclePolicyValidationSnapshot(params.repoRoot);
|
|
331
|
+
|
|
332
|
+
return {
|
|
333
|
+
tool: 'ai_gate_check',
|
|
334
|
+
dryRun: true,
|
|
335
|
+
executed: true,
|
|
336
|
+
success: allowed,
|
|
337
|
+
result: {
|
|
338
|
+
allowed,
|
|
339
|
+
status,
|
|
340
|
+
timestamp,
|
|
341
|
+
branch,
|
|
342
|
+
message,
|
|
343
|
+
reason_code: buildReasonCode(evaluationForHints),
|
|
344
|
+
instruction: buildInstruction(evaluationForHints),
|
|
345
|
+
prewrite_effective: {
|
|
346
|
+
mode: preWriteEnforcement.mode,
|
|
347
|
+
source: preWriteEnforcement.source,
|
|
348
|
+
blocking: preWriteEnforcement.blocking,
|
|
349
|
+
strict_policy: policyValidation.stages.PRE_WRITE.strict,
|
|
350
|
+
},
|
|
351
|
+
next_action: buildNextAction(evaluationForHints, autoFixes),
|
|
352
|
+
stage: evaluation.stage,
|
|
353
|
+
policy: evaluation.policy,
|
|
354
|
+
violations,
|
|
355
|
+
warnings,
|
|
356
|
+
auto_fixes: autoFixes,
|
|
357
|
+
learning_context: learningContext,
|
|
358
|
+
evidence: evaluation.evidence,
|
|
359
|
+
mcp_receipt: evaluation.mcp_receipt,
|
|
360
|
+
skills_contract: evaluation.skills_contract,
|
|
361
|
+
repo_state: evaluation.repo_state,
|
|
362
|
+
consistency_hint: buildConsistencyHint(evaluationForHints, platform),
|
|
363
|
+
platform_gate_alignment: toPlatformGateAlignment(mode, platform),
|
|
364
|
+
},
|
|
365
|
+
};
|
|
366
|
+
};
|