mirror of
https://github.com/garrytan/gstack.git
synced 2026-10-08 04:11:18 +02:00
- A1: the retired Eng lexical oracle (evaluateEngSeedCoverage, isEngSeedDecisionAUQ), the completion-handoff detector and the retained corpus had no paid caller since v1.87.6; delete their 26 replay files, ~2.6k helper LOC and fixtures, and the dead blocks in 8 mixed files (live hasNativePlanTerminal / batching assertions stay). - A2: dead viewport approvers in autoplan-artifact-permission and their 11 replay files + fixtures; recorder/launcher cases stay. - A3: never-wired oracles and seeders (autoplan-phase-order, eng-finding-fixture, ceo-paired-fixture, design-ui-scope, plan-skill-completion, pty-current-screen, required-reads, transcript-section-logger); plan-seed-submission now decodes through the production createPtyScreen; section manifests name their actual guard. - A4: zero-reference helper exports, plus execGit and invokeAndObserve found by the reachability pass. - 52 fixtures orphaned by the deletions; touchfile and selection-table entries for every deleted path.
93 lines
5.5 KiB
TypeScript
93 lines
5.5 KiB
TypeScript
/** One-time input for a cropped native Edit of an already-owned review artifact. */
|
|
import * as fs from 'node:fs';
|
|
import * as path from 'node:path';
|
|
import type { PendingAutoplanArtifact } from './autoplan-artifact-recorder';
|
|
import type { NativePublicToolEvent } from './plan-count-transcript';
|
|
|
|
interface ArtifactPermissionContext {
|
|
cwd: string;
|
|
/** Set only by the launcher that created HOME/.gstack, never from ambient env. */
|
|
ownedStateRoot?: string;
|
|
/** Optional native plans root supplied by the same isolated launcher. */
|
|
ownedNativePlansRoot?: string;
|
|
commandStartedAt: number;
|
|
now?: number;
|
|
transcriptStatus: string;
|
|
publicTools: NativePublicToolEvent[];
|
|
}
|
|
|
|
const MAX_BYTES = 1024 * 1024;
|
|
const compact = (text: string) => text.replace(/\s/g, '');
|
|
|
|
/** A completed write distinguishes a new same-looking file confirmation. */
|
|
export function autoplanPermissionProgressKey(viewport: string, events: readonly NativePublicToolEvent[]): string | undefined {
|
|
const file = /^ {0,3}Do you want to (?:create|overwrite|edit) ([^\n?]+)\? *$/m.exec(viewport)?.[1];
|
|
if (!file || new Set(events.map(event => event.sessionId)).size !== 1) return;
|
|
const menu = compact(viewport);
|
|
for (let i = events.length - 1; i >= 0; i--) {
|
|
const result = events[i]!;
|
|
if (result.kind !== 'result' || result.isError !== false) continue;
|
|
const uses = events.slice(0, i).filter(event => event.kind === 'use' &&
|
|
event.sessionId === result.sessionId && event.toolUseId === result.toolUseId);
|
|
if (uses.length !== 1) continue;
|
|
const use = uses[0]!, target = use.input?.file_path;
|
|
if (!['Write', 'Edit'].includes(use.name ?? '') || typeof target !== 'string' ||
|
|
!path.isAbsolute(target) || path.basename(target) !== file ||
|
|
!menu.includes(`alwaysallowaccessto${compact(path.dirname(target))}forthissession`) ||
|
|
!Number.isFinite(Date.parse(use.timestamp)) || Date.parse(result.timestamp) < Date.parse(use.timestamp) ||
|
|
!Number.isFinite(Date.parse(result.timestamp))) continue;
|
|
return `${result.sessionId}:${result.toolUseId}`;
|
|
}
|
|
}
|
|
|
|
export function ownedAutoplanArtifact(file: string, context: Pick<ArtifactPermissionContext, 'cwd' | 'ownedStateRoot'>): boolean {
|
|
if (!context.ownedStateRoot || !path.isAbsolute(file) || path.resolve(file) !== file) return false;
|
|
const project = path.join(context.ownedStateRoot, 'projects', path.basename(context.cwd));
|
|
const relative = path.relative(project, file).split(path.sep).join('/');
|
|
// Current CEO plan and both explicit Eng test-plan layouts. Design/DX amend
|
|
// ACTIVE_PLAN; they have no separate state-root plan directory. Do not admit
|
|
// restore points, methodology snapshots, task logs, config, or mockups.
|
|
if (!/^ceo-plans\/\d{4}-\d{2}-\d{2}-[a-z0-9][a-z0-9-]*\.md$/.test(relative) &&
|
|
!/^[a-zA-Z0-9][a-zA-Z0-9_.-]*-test-plan-\d{8}-\d{6}\.md$/.test(relative)) return false;
|
|
try {
|
|
// System temp parents may be aliases (/var -> /private/var on macOS).
|
|
// Canonicalize above the owned root; no symlink at or below it is admitted.
|
|
const expected = path.join(fs.realpathSync(context.ownedStateRoot), path.relative(context.ownedStateRoot, file));
|
|
return fs.lstatSync(context.ownedStateRoot).isDirectory() &&
|
|
fs.realpathSync(file) === expected && fs.lstatSync(file).isFile() && fs.statSync(file).size <= MAX_BYTES;
|
|
} catch { return false; }
|
|
}
|
|
|
|
/** Same native-history gate for an unpublished pending Edit, independent of its display. */
|
|
export function hasPendingAutoplanArtifactHistory(
|
|
context: ArtifactPermissionContext & { pending?: PendingAutoplanArtifact },
|
|
): boolean {
|
|
const p = context.pending, now = context.now ?? Date.now();
|
|
if (!p || p.source !== 'pre_tool_use' || p.tool !== 'Edit' || context.transcriptStatus !== 'ready' ||
|
|
!Number.isFinite(now) || !Number.isFinite(context.commandStartedAt) ||
|
|
!/^[A-Za-z0-9_-]{1,160}$/.test(p.sessionId) || !/^[A-Za-z0-9_-]{1,160}$/.test(p.toolUseId) ||
|
|
!ownedAutoplanArtifact(p.file, context) || context.publicTools.length > 10_000) return false;
|
|
const pendingTime = Date.parse(p.timestamp);
|
|
if (!Number.isFinite(pendingTime) || pendingTime < context.commandStartedAt || pendingTime > now) return false;
|
|
const events = context.publicTools.filter(e => Date.parse(e.timestamp) >= context.commandStartedAt);
|
|
if (!events.length || context.publicTools.some(e => !Number.isFinite(Date.parse(e.timestamp)))) return false;
|
|
const uses = new Map<string, NativePublicToolEvent>(), results = new Map<string, NativePublicToolEvent>();
|
|
let last = context.commandStartedAt;
|
|
for (const event of events) {
|
|
const time = Date.parse(event.timestamp);
|
|
if (event.sessionId !== p.sessionId || !event.toolUseId || event.toolUseId === p.toolUseId || time < last || time > now) return false;
|
|
last = time;
|
|
const map = event.kind === 'use' ? uses : results;
|
|
if (map.has(event.toolUseId) || (event.kind === 'result' && !uses.has(event.toolUseId))) return false;
|
|
map.set(event.toolUseId, event);
|
|
}
|
|
const mutations = [...uses.values()].filter(e => e.name === 'Write' || e.name === 'Edit');
|
|
// Hook metadata cannot replace a published request/result or an unresolved
|
|
// mutation. Public successful same-file history remains mandatory.
|
|
if (mutations.some(e => !results.has(e.toolUseId) || Date.parse(e.timestamp) > pendingTime ||
|
|
Date.parse(results.get(e.toolUseId)!.timestamp) > pendingTime) ||
|
|
!mutations.some(e => e.input?.file_path === p.file && results.get(e.toolUseId)?.isError === false &&
|
|
Date.parse(results.get(e.toolUseId)!.timestamp) <= pendingTime)) return false;
|
|
return true;
|
|
}
|