From 15c7a27f39868eed13e483d8216f7143de8a6bc6 Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Mon, 6 Jul 2026 18:22:28 +0800 Subject: [PATCH] Fix project instructions review findings --- .../prompt/project-instructions/src/index.ts | 15 ++++++++---- .../tests/project-instructions.spec.ts | 23 +++++++++++++++++++ packages/ui/acp-agent/tests/built-bin.e2e.ts | 4 ++-- .../ui/stdio-agent/tests/built-bin.e2e.ts | 4 ++-- 4 files changed, 38 insertions(+), 8 deletions(-) diff --git a/packages/prompt/project-instructions/src/index.ts b/packages/prompt/project-instructions/src/index.ts index 40c5474371..11ea59583c 100644 --- a/packages/prompt/project-instructions/src/index.ts +++ b/packages/prompt/project-instructions/src/index.ts @@ -526,19 +526,25 @@ function instructionDisplayPathsFromContextContent(content: readonly { type: str return paths } -function visibleInstructionDisplayPaths(agent: Agent): { visible: Set; logged: Set } { +function visibleInstructionDisplayPaths(agent: Agent): { visible: Set; logged: Set; visibleTexts: Set } { const visibleSeqs = new Set(agent.session.surface.nodes.map(node => node.seq)) const visible = new Set() const logged = new Set() + const visibleTexts = new Set() for (const [seq, event] of agent.session.events.entries()) { if (event.type !== 'context/message' || !isProjectInstructionContextSource(event.data.source)) continue + if (visibleSeqs.has(seq)) { + for (const block of event.data.content) { + if (block.type === 'text') visibleTexts.add(block.text) + } + } const displayPaths = instructionDisplayPathsFromContextContent(event.data.content) for (const displayPath of displayPaths) { logged.add(displayPath) if (visibleSeqs.has(seq)) visible.add(displayPath) } } - return { visible, logged } + return { visible, logged, visibleTexts } } function loadedNestedInstructionDisplayPaths(agent: Agent, pendingDisplayPaths: Set): Set { @@ -606,9 +612,10 @@ export function apply(ctx: Context, config: Config): void { cache, }, fileSystem) if (instructions === undefined) return - const visibleDisplayPaths = visibleInstructionDisplayPaths(agent).visible + const visibleInstructions = visibleInstructionDisplayPaths(agent) const baselineDisplayPaths = instructionDisplayPathsFromText(instructions.text) - if (baselineDisplayPaths.length > 0 && baselineDisplayPaths.every(path => visibleDisplayPaths.has(path))) return + if (baselineDisplayPaths.length > 0 && baselineDisplayPaths.every(path => visibleInstructions.visible.has(path))) return + if (baselineDisplayPaths.length === 0 && visibleInstructions.visibleTexts.has(instructions.text)) return agent.inject(workspaceContextHook(instructions.text).content, { source: PLUGIN_SOURCE }) }) ctx.on('tools/post-execute', async (exec: ToolExecution, result: ToolExecutionResult, next): Promise => { diff --git a/packages/prompt/project-instructions/tests/project-instructions.spec.ts b/packages/prompt/project-instructions/tests/project-instructions.spec.ts index af68da9492..3e0f8fa395 100644 --- a/packages/prompt/project-instructions/tests/project-instructions.spec.ts +++ b/packages/prompt/project-instructions/tests/project-instructions.spec.ts @@ -719,6 +719,29 @@ describe('project instruction request injection', () => { } }) + it('does not duplicate markerless baseline workspace context on later pre-step checks', async () => { + const root = await tempRepo() + const home = await tempRepo() + try { + await mkdir(join(root, '.git'), { recursive: true }) + await write(join(root, 'AGENTS.md'), 'repo rule') + const ctx = new Context() + await mountProjectInstructions(ctx, { dshHome: home, baselineMaxBytes: 10 }) + const agent = stubAgent(root) + + await runBaselinePreStep(ctx, agent) + await runBaselinePreStep(ctx, agent) + + const messages = agent.session.events.filter(event => event.type === 'context/message') + expect(messages).toHaveLength(1) + expect(blocksText(messages[0]?.type === 'context/message' ? messages[0].data.content : undefined)) + .not.toContain('project-instruction-files:path=') + } finally { + await rm(root, { recursive: true, force: true }) + await rm(home, { recursive: true, force: true }) + } + }) + it('loads instruction file content through ctx.fs instead of direct node reads', async () => { const root = await tempRepo() const home = await tempRepo() diff --git a/packages/ui/acp-agent/tests/built-bin.e2e.ts b/packages/ui/acp-agent/tests/built-bin.e2e.ts index ce16895724..48bb0e046f 100644 --- a/packages/ui/acp-agent/tests/built-bin.e2e.ts +++ b/packages/ui/acp-agent/tests/built-bin.e2e.ts @@ -40,9 +40,9 @@ const acpBin = join(repoRoot, 'packages/ui/acp-agent/lib/bin.js') const dshPackages = [ 'core/agent-core', 'core/agent', 'core/session', 'core/system-prompt', 'core/tools', 'core/agent-loop', 'llm/llm', 'llm/llm-deepseek', 'bash/bash', - 'bash/bash-local', 'bash/tool-bash', 'support/invariants', 'ui/app-boot', + 'bash/bash-local', 'bash/tool-bash', 'prompt/project-instructions', 'support/invariants', 'ui/app-boot', 'session-persistence/session-persistence', - 'session-persistence/session-persistence-jsonl', 'ui/acp', 'ui/acp-agent', + 'session-persistence/session-persistence-jsonl', 'ui/acp', 'ui/acp-agent', 'util/paths', ] const vendorPackages = [ 'cordis', 'loader', 'include', 'timer', 'hmr', 'logger-console', diff --git a/packages/ui/stdio-agent/tests/built-bin.e2e.ts b/packages/ui/stdio-agent/tests/built-bin.e2e.ts index bd0fad4dbb..57419e02e0 100644 --- a/packages/ui/stdio-agent/tests/built-bin.e2e.ts +++ b/packages/ui/stdio-agent/tests/built-bin.e2e.ts @@ -35,9 +35,9 @@ const stdioBin = join(repoRoot, 'packages/ui/stdio-agent/lib/bin.js') const dshPackages = [ 'core/agent-core', 'core/agent', 'core/session', 'core/system-prompt', 'core/tools', 'core/agent-loop', 'llm/llm', 'bash/bash', 'bash/bash-local', - 'bash/tool-bash', 'support/invariants', 'ui/app-boot', + 'bash/tool-bash', 'prompt/project-instructions', 'support/invariants', 'ui/app-boot', 'session-persistence/session-persistence', - 'session-persistence/session-persistence-jsonl', 'ui/stdio-agent', + 'session-persistence/session-persistence-jsonl', 'ui/stdio-agent', 'util/paths', ] const vendorPackages = [ 'cordis', 'loader', 'include', 'timer', 'hmr', 'logger-console',