pumuki 6.3.113 → 6.3.114
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 +51 -5
- package/README.md +4 -2
- package/VERSION +1 -1
- package/core/facts/detectors/typescript/index.test.ts +0 -229
- package/core/facts/detectors/typescript/index.ts +0 -278
- package/core/facts/extractHeuristicFacts.ts +0 -4
- package/core/rules/presets/heuristics/typescript.test.ts +1 -21
- package/core/rules/presets/heuristics/typescript.ts +0 -72
- package/docs/README.md +13 -9
- package/docs/codex-skills/backend-enterprise-rules.md +3 -3
- package/docs/operations/RELEASE_NOTES.md +40 -4
- package/docs/product/API_REFERENCE.md +1 -1
- package/docs/product/HOW_IT_WORKS.md +6 -0
- package/docs/product/INSTALLATION.md +1 -1
- package/docs/product/USAGE.md +42 -5
- package/docs/tracking/plan-curso-pumuki-stack-my-architecture.md +100 -44
- package/docs/validation/README.md +6 -3
- package/integrations/config/skillsDetectorRegistry.ts +0 -24
- package/integrations/config/skillsMarkdownRules.ts +0 -57
- package/integrations/evidence/buildEvidence.ts +0 -24
- package/integrations/evidence/repoState.ts +0 -3
- package/integrations/evidence/schema.ts +0 -18
- package/integrations/evidence/writeEvidence.ts +0 -24
- package/integrations/gate/evaluateAiGate.ts +8 -251
- package/integrations/gate/remediationCatalog.ts +0 -8
- package/integrations/git/GitService.ts +44 -5
- package/integrations/git/aiGateRepoPolicyFindings.ts +86 -17
- package/integrations/git/runPlatformGate.ts +1 -9
- package/integrations/git/runPlatformGateFacts.ts +19 -1
- package/integrations/git/runPlatformGateOutput.ts +41 -42
- package/integrations/lifecycle/adapter.templates.json +1 -0
- package/integrations/lifecycle/adapter.ts +0 -24
- package/integrations/lifecycle/audit.ts +101 -0
- package/integrations/lifecycle/cli.ts +120 -99
- package/integrations/lifecycle/cliSdd.ts +4 -26
- package/integrations/lifecycle/doctor.ts +40 -102
- package/integrations/lifecycle/index.ts +2 -0
- package/integrations/lifecycle/install.ts +0 -21
- package/integrations/lifecycle/packageInfo.ts +1 -118
- package/integrations/lifecycle/state.ts +1 -8
- package/integrations/lifecycle/status.ts +40 -59
- package/integrations/lifecycle/watch.ts +1 -1
- package/integrations/mcp/aiGateCheck.ts +10 -194
- package/integrations/mcp/autoExecuteAiStart.ts +116 -92
- package/integrations/mcp/enterpriseServer.ts +7 -23
- package/integrations/mcp/enterpriseStdioServer.cli.ts +4 -31
- package/integrations/mcp/preFlightCheck.ts +5 -67
- package/integrations/platform/detectPlatforms.ts +37 -0
- package/integrations/sdd/policy.ts +28 -20
- package/package.json +1 -1
- package/scripts/check-tracking-single-active.sh +1 -1
- package/scripts/consumer-menu-matrix-baseline-report-lib.ts +13 -38
- package/scripts/consumer-postinstall-resolve-args.cjs +44 -0
- package/scripts/consumer-postinstall.cjs +76 -21
- package/scripts/framework-menu-advanced-view-lib.ts +0 -49
- package/scripts/framework-menu-consumer-actions-lib.ts +28 -4
- package/scripts/framework-menu-consumer-preflight-hints.ts +5 -2
- package/scripts/framework-menu-consumer-preflight-render.ts +0 -10
- package/scripts/framework-menu-consumer-preflight-run.ts +0 -23
- package/scripts/framework-menu-consumer-preflight-types.ts +0 -12
- package/scripts/framework-menu-consumer-runtime-actions.ts +87 -17
- package/scripts/framework-menu-consumer-runtime-audit.ts +36 -2
- package/scripts/framework-menu-consumer-runtime-evidence-classic.ts +140 -0
- package/scripts/framework-menu-consumer-runtime-lib.ts +2 -38
- package/scripts/framework-menu-consumer-runtime-menu.ts +4 -31
- package/scripts/framework-menu-consumer-runtime-types.ts +3 -5
- package/scripts/framework-menu-evidence-summary-lib.ts +1 -0
- package/scripts/framework-menu-evidence-summary-read.ts +57 -5
- package/scripts/framework-menu-evidence-summary-severity.ts +3 -1
- package/scripts/framework-menu-evidence-summary-types.ts +7 -0
- package/scripts/framework-menu-gate-lib.ts +9 -0
- package/scripts/framework-menu-layout-data.ts +5 -0
- package/scripts/framework-menu-matrix-baseline-lib.ts +15 -14
- package/scripts/framework-menu-matrix-canary-lib.ts +22 -1
- package/scripts/framework-menu-matrix-evidence-lib.ts +1 -0
- package/scripts/framework-menu-matrix-evidence-types.ts +13 -1
- package/scripts/framework-menu-matrix-runner-lib.ts +35 -0
- package/scripts/framework-menu-system-notifications-cause.ts +0 -3
- package/scripts/framework-menu-system-notifications-macos-swift-source.ts +24 -204
- package/scripts/framework-menu-system-notifications-macos.ts +4 -0
- package/scripts/framework-menu-system-notifications-payloads-blocked.ts +1 -1
- package/scripts/framework-menu-system-notifications-text.ts +1 -7
- package/scripts/framework-menu.ts +3 -24
- package/scripts/package-install-smoke-consumer-git-repo-lib.ts +1 -10
- package/scripts/package-install-smoke-consumer-npm-lib.ts +9 -46
- package/scripts/pumuki-full-surface-smoke-lib.ts +37 -0
- package/scripts/pumuki-full-surface-smoke.ts +346 -0
- package/scripts/pumuki-smoke-installed-wrapper.cjs +31 -0
- package/integrations/evidence/trackingContract.ts +0 -17
- package/integrations/gate/governanceActionCatalog.ts +0 -275
- package/integrations/lifecycle/bootstrapManifest.ts +0 -248
- package/integrations/lifecycle/cliGovernanceConsole.ts +0 -69
- package/integrations/lifecycle/governanceNextAction.ts +0 -171
- package/integrations/lifecycle/governanceObservationSnapshot.ts +0 -369
- package/integrations/lifecycle/trackingState.ts +0 -403
- package/integrations/mcp/alignedPlatformGate.ts +0 -232
- package/integrations/mcp/readMcpPrePushStdin.ts +0 -7
- package/scripts/build-ruralgo-s1-evidence-pack.ts +0 -85
- package/scripts/ruralgo-s1-evidence-pack-lib.ts +0 -200
|
@@ -1,403 +0,0 @@
|
|
|
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
|
-
};
|
|
@@ -1,232 +0,0 @@
|
|
|
1
|
-
import type { AiGateStage } from '../gate/evaluateAiGate';
|
|
2
|
-
import { resolvePolicyForStage } from '../gate/stagePolicies';
|
|
3
|
-
import type { SddDecision } from '../sdd';
|
|
4
|
-
import { GitService } from '../git/GitService';
|
|
5
|
-
import { runPlatformGate } from '../git/runPlatformGate';
|
|
6
|
-
import type { GateScope } from '../git/runPlatformGateFacts';
|
|
7
|
-
import { readMcpPrePushStdin } from './readMcpPrePushStdin';
|
|
8
|
-
|
|
9
|
-
const ZERO_HASH = /^0+$/;
|
|
10
|
-
|
|
11
|
-
const runGit = (repoRoot: string, args: ReadonlyArray<string>): string | null => {
|
|
12
|
-
try {
|
|
13
|
-
return new GitService().runGit(args, repoRoot).trim();
|
|
14
|
-
} catch {
|
|
15
|
-
return null;
|
|
16
|
-
}
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
const resolveUpstreamRefInRepo = (repoRoot: string): string | null =>
|
|
20
|
-
runGit(repoRoot, ['rev-parse', '@{u}']);
|
|
21
|
-
|
|
22
|
-
const resolveHeadOidInRepo = (repoRoot: string): string | null =>
|
|
23
|
-
runGit(repoRoot, ['rev-parse', 'HEAD']);
|
|
24
|
-
|
|
25
|
-
const resolveCiBaseRefInRepo = (repoRoot: string): string => {
|
|
26
|
-
const fromEnv = process.env.GITHUB_BASE_REF?.trim();
|
|
27
|
-
if (fromEnv) {
|
|
28
|
-
if (runGit(repoRoot, ['rev-parse', '--verify', fromEnv])) {
|
|
29
|
-
return fromEnv;
|
|
30
|
-
}
|
|
31
|
-
const remoteRef = `origin/${fromEnv}`;
|
|
32
|
-
if (runGit(repoRoot, ['rev-parse', '--verify', remoteRef])) {
|
|
33
|
-
return remoteRef;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
for (const candidate of ['origin/main', 'main', 'HEAD']) {
|
|
38
|
-
if (runGit(repoRoot, ['rev-parse', '--verify', candidate])) {
|
|
39
|
-
return candidate;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return 'HEAD';
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
const resolvePrePushBootstrapBaseRefInRepo = (repoRoot: string): string => {
|
|
47
|
-
const candidates = ['origin/develop', 'develop', resolveCiBaseRefInRepo(repoRoot)];
|
|
48
|
-
for (const candidate of candidates) {
|
|
49
|
-
if (runGit(repoRoot, ['rev-parse', '--verify', candidate])) {
|
|
50
|
-
return candidate;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return 'HEAD';
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
const shouldAllowBootstrapPrePush = (rawInput: string): boolean => {
|
|
58
|
-
const lines = rawInput
|
|
59
|
-
.split('\n')
|
|
60
|
-
.map((line) => line.trim())
|
|
61
|
-
.filter((line) => line.length > 0);
|
|
62
|
-
|
|
63
|
-
for (const line of lines) {
|
|
64
|
-
const [localRef, localOid, remoteRef, remoteOid] = line.split(/\s+/);
|
|
65
|
-
if (!localRef || !localOid || !remoteRef || !remoteOid) {
|
|
66
|
-
continue;
|
|
67
|
-
}
|
|
68
|
-
const localIsBranch = localRef.startsWith('refs/heads/');
|
|
69
|
-
const remoteIsBranch = remoteRef.startsWith('refs/heads/');
|
|
70
|
-
const localIsDeletion = ZERO_HASH.test(localOid);
|
|
71
|
-
const remoteIsNewBranch = ZERO_HASH.test(remoteOid);
|
|
72
|
-
|
|
73
|
-
if (localIsBranch && remoteIsBranch && !localIsDeletion && remoteIsNewBranch) {
|
|
74
|
-
return true;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return false;
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
const resolveExplicitPrePushRange = (
|
|
82
|
-
rawInput: string
|
|
83
|
-
): { fromRef: string; toRef: string } | undefined => {
|
|
84
|
-
const lines = rawInput
|
|
85
|
-
.split('\n')
|
|
86
|
-
.map((line) => line.trim())
|
|
87
|
-
.filter((line) => line.length > 0);
|
|
88
|
-
|
|
89
|
-
const eligibleRanges = lines
|
|
90
|
-
.map((line) => {
|
|
91
|
-
const [localRef, localOid, remoteRef, remoteOid] = line.split(/\s+/);
|
|
92
|
-
if (!localRef || !localOid || !remoteRef || !remoteOid) {
|
|
93
|
-
return undefined;
|
|
94
|
-
}
|
|
95
|
-
const localIsDeletion = ZERO_HASH.test(localOid);
|
|
96
|
-
const remoteIsNewBranch = ZERO_HASH.test(remoteOid);
|
|
97
|
-
if (localIsDeletion || remoteIsNewBranch) {
|
|
98
|
-
return undefined;
|
|
99
|
-
}
|
|
100
|
-
return {
|
|
101
|
-
fromRef: remoteOid,
|
|
102
|
-
toRef: localOid,
|
|
103
|
-
};
|
|
104
|
-
})
|
|
105
|
-
.filter((value): value is { fromRef: string; toRef: string } => Boolean(value));
|
|
106
|
-
|
|
107
|
-
if (eligibleRanges.length !== 1) {
|
|
108
|
-
return undefined;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return eligibleRanges[0];
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
type PrePushScopeResolution =
|
|
115
|
-
| { kind: 'scope'; scope: GateScope; sddDecisionOverride?: Pick<SddDecision, 'allowed' | 'code' | 'message'> }
|
|
116
|
-
| { kind: 'upstream_missing' };
|
|
117
|
-
|
|
118
|
-
const resolvePrePushScopeForMcp = (params: { repoRoot: string }): PrePushScopeResolution => {
|
|
119
|
-
const prePushInput = readMcpPrePushStdin();
|
|
120
|
-
const upstreamRef = resolveUpstreamRefInRepo(params.repoRoot);
|
|
121
|
-
if (!upstreamRef) {
|
|
122
|
-
const bootstrapBaseRef = resolvePrePushBootstrapBaseRefInRepo(params.repoRoot);
|
|
123
|
-
const bootstrapByPrePushStdIn = shouldAllowBootstrapPrePush(prePushInput);
|
|
124
|
-
const bootstrapByFallbackBase = !bootstrapByPrePushStdIn && bootstrapBaseRef !== 'HEAD';
|
|
125
|
-
const manualInvocationFallback =
|
|
126
|
-
!bootstrapByPrePushStdIn &&
|
|
127
|
-
!bootstrapByFallbackBase &&
|
|
128
|
-
prePushInput.trim().length === 0;
|
|
129
|
-
if (bootstrapByPrePushStdIn || bootstrapByFallbackBase) {
|
|
130
|
-
return {
|
|
131
|
-
kind: 'scope',
|
|
132
|
-
scope: {
|
|
133
|
-
kind: 'range',
|
|
134
|
-
fromRef: bootstrapBaseRef,
|
|
135
|
-
toRef: 'HEAD',
|
|
136
|
-
},
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
if (manualInvocationFallback) {
|
|
140
|
-
return { kind: 'scope', scope: { kind: 'workingTree' } };
|
|
141
|
-
}
|
|
142
|
-
return { kind: 'upstream_missing' };
|
|
143
|
-
}
|
|
144
|
-
const explicitPrePushRange = resolveExplicitPrePushRange(prePushInput);
|
|
145
|
-
const prePushFromRef = explicitPrePushRange?.fromRef ?? upstreamRef;
|
|
146
|
-
const prePushToRef = explicitPrePushRange?.toRef ?? 'HEAD';
|
|
147
|
-
const headOid = resolveHeadOidInRepo(params.repoRoot);
|
|
148
|
-
const sddDecisionOverride =
|
|
149
|
-
explicitPrePushRange && headOid && explicitPrePushRange.toRef !== headOid
|
|
150
|
-
? ({
|
|
151
|
-
allowed: true,
|
|
152
|
-
code: 'ALLOWED',
|
|
153
|
-
message:
|
|
154
|
-
`SDD enforcement suspended for PRE_PUSH historical publish targeting ${explicitPrePushRange.toRef.slice(0, 12)} ` +
|
|
155
|
-
`instead of current HEAD ${headOid.slice(0, 12)}.`,
|
|
156
|
-
} as Pick<SddDecision, 'allowed' | 'code' | 'message'>)
|
|
157
|
-
: undefined;
|
|
158
|
-
return {
|
|
159
|
-
kind: 'scope',
|
|
160
|
-
scope: {
|
|
161
|
-
kind: 'range',
|
|
162
|
-
fromRef: prePushFromRef,
|
|
163
|
-
toRef: prePushToRef,
|
|
164
|
-
},
|
|
165
|
-
sddDecisionOverride,
|
|
166
|
-
};
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
type RunAlignedParams = {
|
|
170
|
-
repoRoot: string;
|
|
171
|
-
stage: AiGateStage;
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
export const runMcpAlignedPlatformGate = async (
|
|
175
|
-
params: RunAlignedParams
|
|
176
|
-
): Promise<{ exitCode: number; aligned: boolean; skipReason: string | null }> => {
|
|
177
|
-
const git = new GitService();
|
|
178
|
-
const resolved = resolvePolicyForStage(params.stage, params.repoRoot);
|
|
179
|
-
if (params.stage === 'PRE_WRITE') {
|
|
180
|
-
const exitCode = await runPlatformGate({
|
|
181
|
-
policy: resolved.policy,
|
|
182
|
-
policyTrace: resolved.trace,
|
|
183
|
-
scope: { kind: 'workingTree' },
|
|
184
|
-
silent: true,
|
|
185
|
-
services: { git },
|
|
186
|
-
});
|
|
187
|
-
return { exitCode, aligned: true, skipReason: null };
|
|
188
|
-
}
|
|
189
|
-
if (params.stage === 'PRE_COMMIT') {
|
|
190
|
-
const exitCode = await runPlatformGate({
|
|
191
|
-
policy: resolved.policy,
|
|
192
|
-
policyTrace: resolved.trace,
|
|
193
|
-
scope: { kind: 'staged' },
|
|
194
|
-
silent: true,
|
|
195
|
-
services: { git },
|
|
196
|
-
});
|
|
197
|
-
return { exitCode, aligned: true, skipReason: null };
|
|
198
|
-
}
|
|
199
|
-
if (params.stage === 'CI') {
|
|
200
|
-
const ciBaseRef = resolveCiBaseRefInRepo(params.repoRoot);
|
|
201
|
-
const exitCode = await runPlatformGate({
|
|
202
|
-
policy: resolved.policy,
|
|
203
|
-
policyTrace: resolved.trace,
|
|
204
|
-
scope: {
|
|
205
|
-
kind: 'range',
|
|
206
|
-
fromRef: ciBaseRef,
|
|
207
|
-
toRef: 'HEAD',
|
|
208
|
-
},
|
|
209
|
-
silent: true,
|
|
210
|
-
services: { git },
|
|
211
|
-
});
|
|
212
|
-
return { exitCode, aligned: true, skipReason: null };
|
|
213
|
-
}
|
|
214
|
-
if (params.stage === 'PRE_PUSH') {
|
|
215
|
-
const scopeResolution = resolvePrePushScopeForMcp({ repoRoot: params.repoRoot });
|
|
216
|
-
if (scopeResolution.kind === 'upstream_missing') {
|
|
217
|
-
return { exitCode: 1, aligned: false, skipReason: 'PRE_PUSH_UPSTREAM_MISSING' };
|
|
218
|
-
}
|
|
219
|
-
const exitCode = await runPlatformGate({
|
|
220
|
-
policy: resolved.policy,
|
|
221
|
-
policyTrace: resolved.trace,
|
|
222
|
-
scope: scopeResolution.scope,
|
|
223
|
-
silent: true,
|
|
224
|
-
services: { git },
|
|
225
|
-
...(scopeResolution.sddDecisionOverride
|
|
226
|
-
? { sddDecisionOverride: scopeResolution.sddDecisionOverride }
|
|
227
|
-
: {}),
|
|
228
|
-
});
|
|
229
|
-
return { exitCode, aligned: true, skipReason: null };
|
|
230
|
-
}
|
|
231
|
-
throw new Error(`Unsupported MCP aligned stage: ${String(params.stage)}`);
|
|
232
|
-
};
|