diff --git a/packages/prompt/workspace-context/src/files.ts b/packages/prompt/workspace-context/src/files.ts index 21f5c00968..feb6304b4c 100644 --- a/packages/prompt/workspace-context/src/files.ts +++ b/packages/prompt/workspace-context/src/files.ts @@ -101,6 +101,9 @@ async function fsStatFile( fileSystem: FileSystem, signal?: AbortSignal, ): Promise { + // TODO(instruction-symlink-race): replace this lstat -> resolve -> read + // protocol, including probeScopeInstruction below, with a provider-owned + // atomic no-follow read so the final component cannot change after validation. let pathInfo: FsPathInfo | undefined try { pathInfo = await fileSystem.lstat(path, undefined, signal) @@ -142,6 +145,8 @@ async function existsAsMarker(path: string, fileSystem?: FileSystem, signal?: Ab return await fileSystem.stat(target, signal) !== undefined } catch { signal?.throwIfAborted() + // TODO(root-marker-unavailable): preserve provider failure separately from + // absence and stop discovery; continuing upward can cross into an ancestor project. return false } } @@ -316,6 +321,9 @@ async function readBounded( fileSystem?: FileSystem, signal?: AbortSignal, ): Promise { + // TODO(total-instruction-read-bound): enforce an aggregate source budget + // across a complete baseline or reconciliation batch; the render budget is + // applied only after every accepted file has been read under this per-file cap. signal?.throwIfAborted() if (file.size !== undefined && file.size > maxSourceBytes) return undefined try { diff --git a/packages/prompt/workspace-context/src/render.ts b/packages/prompt/workspace-context/src/render.ts index 0151aa1796..34e427d0e3 100644 --- a/packages/prompt/workspace-context/src/render.ts +++ b/packages/prompt/workspace-context/src/render.ts @@ -61,6 +61,9 @@ function truncateUtf8(value: string, maxBytes: number): string { } function escapeInstructionContent(content: string): string { + // TODO(instruction-frame-paths): apply the same delimiter neutralization to + // every interpolated path, scope, and previous path; repository-controlled + // names can otherwise close the plugin-owned system-reminder frame. return content.replaceAll(SYSTEM_REMINDER_CLOSE, '<\\/system-reminder>') } @@ -132,6 +135,9 @@ export function renderInstructionChanges( const omitted = new Set(rendered.omitted.map(file => file.absolutePath)) return { text: rendered.text, + // TODO(rendered-change-proof): retain a transition only when its semantic + // notice survived rendering; a tiny compact budget can currently return + // unrelated notice text while still committing the full state transition. changes: items.filter(item => !omitted.has(item.file.absolutePath)).map(item => item.change), } } diff --git a/packages/prompt/workspace-context/src/state.ts b/packages/prompt/workspace-context/src/state.ts index c0da79a22e..ed73c4fa57 100644 --- a/packages/prompt/workspace-context/src/state.ts +++ b/packages/prompt/workspace-context/src/state.ts @@ -388,6 +388,8 @@ export async function reconcileInstructionContext( for (const [scope, change] of visible) effective.set(scope, change) /* v8 ignore next -- normal agents carry an absolute session cwd. */ const cwd = session.header.cwd ?? process.cwd() + // TODO(frozen-project-root): retain the baseline root for the loop instance; + // recomputing it after marker edits reinterprets the existing relative scope keys. const projectRoot = await findProjectRoot(cwd, resolved.projectRootMarkers, fileSystem, options.signal) const scopes = new Set() if (options.includeBaselineScopes) {