mirror of
https://github.com/garrytan/gstack.git
synced 2026-10-31 22:14:25 +01:00
* feat: add configurable model tiers and advisory freshness * fix: disclose role selections before Claude review execution * fix: reject malformed model policies and keep model pins portable * fix: separate readiness diagnostics and bound version checks * fix: preserve actionable model failures and validate review inputs before probes * fix: retry released probe locks and retain late-rejection repair context * feat: finalize configurable model tiers and verified release repairs * fix: preserve strict model policy reads on Windows * fix: keep fixture probes read-only and clarify pending CEO decisions * fix: verify native terminal closure before bounded descriptor settlement
54 lines
3.9 KiB
TypeScript
54 lines
3.9 KiB
TypeScript
import { readBoundedResponse, sha256, type Fetcher } from './model-policy-freshness-sources';
|
|
import { FRESHNESS_ISSUE_AUTHOR, type FreshnessGitHub, type TrackingIssue } from './model-policy-freshness-publication';
|
|
|
|
const object = (value: unknown): value is Record<string, unknown> => !!value && typeof value === 'object' && !Array.isArray(value);
|
|
|
|
function issueFromJson(value: unknown): TrackingIssue {
|
|
if (!object(value) || !Number.isSafeInteger(value.number) || Number(value.number) <= 0 || (value.body !== null && typeof value.body !== 'string') || !['open', 'closed'].includes(String(value.state)) || !object(value.user) || typeof value.user.login !== 'string') throw new Error('github-invalid-issue');
|
|
return { number: Number(value.number), body: value.body === null ? '' : value.body as string, state: value.state as 'open' | 'closed', author: value.user.login };
|
|
}
|
|
|
|
export function freshnessGitHub(repository: string, token: string, fetcher: Fetcher = fetch): FreshnessGitHub {
|
|
if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(repository) || !token) throw new Error('github-publication-prerequisites-missing');
|
|
const root = `https://api.github.com/repos/${repository}`;
|
|
async function request(path: string, signal: AbortSignal, method = 'GET', body?: unknown): Promise<unknown> {
|
|
if (signal.aborted) throw new Error('github-publication-aborted');
|
|
const response = await fetcher(`${root}${path}`, {
|
|
method, signal, redirect: 'error',
|
|
headers: { Accept: 'application/vnd.github+json', Authorization: `Bearer ${token}`, 'X-GitHub-Api-Version': '2022-11-28', 'Content-Type': 'application/json' },
|
|
...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
|
});
|
|
if (!response.ok) { await response.body?.cancel(); throw new Error(`github-http-${response.status}`); }
|
|
try { return JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(await readBoundedResponse(response))); }
|
|
catch { throw new Error('github-invalid-or-oversized-response'); }
|
|
}
|
|
return {
|
|
async defaultCatalog(signal) {
|
|
const repo = await request('', signal);
|
|
if (!object(repo) || typeof repo.default_branch !== 'string' || !repo.default_branch) throw new Error('github-default-branch-unavailable');
|
|
const file = await request(`/contents/lib/model-catalog.ts?ref=${encodeURIComponent(repo.default_branch)}`, signal);
|
|
if (!object(file) || file.encoding !== 'base64' || typeof file.content !== 'string' || typeof file.size !== 'number' || file.size <= 0 || file.size > 2 * 1024 * 1024 || !/^[A-Za-z0-9+/=\n\r]+$/.test(file.content)) throw new Error('github-default-catalog-unavailable');
|
|
const bytes = Buffer.from(file.content, 'base64');
|
|
if (bytes.length !== file.size) throw new Error('github-default-catalog-size-mismatch');
|
|
return { defaultBranch: repo.default_branch, sourceSha256: sha256(bytes) };
|
|
},
|
|
async listIssues(signal) {
|
|
const issues: TrackingIssue[] = [];
|
|
for (let page = 1; page <= 100; page++) {
|
|
const result = await request(`/issues?state=all&creator=${encodeURIComponent(FRESHNESS_ISSUE_AUTHOR)}&per_page=100&page=${page}`, signal);
|
|
if (!Array.isArray(result)) throw new Error('github-issue-list-unavailable');
|
|
for (const value of result) {
|
|
const issue = issueFromJson(value);
|
|
if (issue.author !== FRESHNESS_ISSUE_AUTHOR) throw new Error('github-issue-owner-filter-mismatch');
|
|
if (object(value) && !value.pull_request) issues.push(issue);
|
|
}
|
|
if (result.length < 100) return issues;
|
|
}
|
|
throw new Error('github-issue-pagination-limit');
|
|
},
|
|
async readIssue(number, signal) { return issueFromJson(await request(`/issues/${number}`, signal)); },
|
|
async createIssue(body, signal) { return issueFromJson(await request('/issues', signal, 'POST', { title: 'Model-policy freshness: advisory maintenance', body })); },
|
|
async updateIssue(number, update, signal) { await request(`/issues/${number}`, signal, 'PATCH', update); },
|
|
};
|
|
}
|