Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions src/agents/shared/frictionGuidance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ import type { Capability } from '../capabilities/index.js';

export const FRICTION_REPORTING_GUIDANCE = `## Friction Reporting

When the ReportFriction tool is available, use it only for incidental papercuts in the environment, tooling, repository setup, documentation, or developer workflow that make the work harder than it should be.
When something makes your work harder than it strictly needs to be — at any point during the task — file it with \`ReportFriction\`. When in doubt, report. Better to over-report initial papercuts than let recurring friction go invisible.

Do not report core task difficulty, expected debugging effort, product ambiguity that belongs in the current work item, or issues you can resolve directly as part of the assigned task.

Keep working after reporting friction unless the issue blocks progress. If blocked, report the friction with concrete context and then explain the blocker in your final response.`;
After filing, keep working; only let friction block your task if it actually blocks it.`;

export function shouldAppendFrictionGuidance(capabilities: readonly Capability[]): boolean {
return capabilities.includes('pm:friction');
Expand Down
40 changes: 36 additions & 4 deletions tests/unit/agents/shared/frictionGuidance.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,41 @@ describe('friction reporting guidance', () => {
);
});

it('limits reports to incidental papercuts and tells agents to keep working unless blocked', () => {
expect(FRICTION_REPORTING_GUIDANCE).toContain('incidental papercuts');
expect(FRICTION_REPORTING_GUIDANCE).toContain('Do not report core task difficulty');
expect(FRICTION_REPORTING_GUIDANCE).toContain('Keep working after reporting friction unless');
it('uses an action-trigger framing that calibrates toward over-reporting', () => {
// Section heading the agent will see in its system prompt.
expect(FRICTION_REPORTING_GUIDANCE).toContain('## Friction Reporting');

// Direct imperative — the rewrite (2026-05-10) replaced the prior
// "use it only for incidental papercuts in the environment, tooling,
// repository setup, documentation, or developer workflow" framing
// because that scoping read as a constraint, not a trigger. The new
// framing is "when X happens, do Y" with explicit "when in doubt,
// report" calibration.
expect(FRICTION_REPORTING_GUIDANCE).toContain(
'makes your work harder than it strictly needs to be',
);
expect(FRICTION_REPORTING_GUIDANCE).toContain('When in doubt, report');
expect(FRICTION_REPORTING_GUIDANCE).toContain('Better to over-report');

// Non-blocking semantic preserved — friction stays a sidebar to the
// main task; it doesn't derail.
expect(FRICTION_REPORTING_GUIDANCE).toContain(
'only let friction block your task if it actually blocks it',
);

// Negative scoping intentionally REMOVED (was: "Do not report core
// task difficulty, expected debugging effort, product ambiguity..."
// — that was the source of under-reporting, surfaced live on
// 2026-05-10 PR #1303 where the implementation agent hit a
// CASCADE_ORG_ID env-var leak in tests, worked around it with
// `env -u`, and reported it in the PR body instead of via
// ReportFriction).
expect(FRICTION_REPORTING_GUIDANCE).not.toContain('Do not report');
expect(FRICTION_REPORTING_GUIDANCE).not.toContain('only for incidental papercuts');

// "When the ReportFriction tool is available" hedge REMOVED — the
// guidance is only injected when the capability is effective, so
// the conditional just creates agent doubt.
expect(FRICTION_REPORTING_GUIDANCE).not.toContain('When the ReportFriction tool is available');
});
});
12 changes: 9 additions & 3 deletions tests/unit/backends/secretOrchestrator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,9 +272,15 @@ describe('buildExecutionPlan', () => {
engine,
);

expect(withFriction.systemPrompt).toContain('Friction Reporting');
expect(withFriction.systemPrompt).toContain('incidental papercuts');
expect(withFriction.systemPrompt).toContain('Keep working after reporting friction unless');
// 2026-05-10 rewrite: friction guidance is now action-trigger framed
// without negative scoping — assert by the new content. Section
// heading + the "when in doubt, report" calibration anchor + the
// non-blocking semantic.
expect(withFriction.systemPrompt).toContain('## Friction Reporting');
expect(withFriction.systemPrompt).toContain('When in doubt, report');
expect(withFriction.systemPrompt).toContain(
'only let friction block your task if it actually blocks it',
);

mockResolveEffectiveCapabilities.mockReturnValueOnce(['fs:read']);

Expand Down
8 changes: 8 additions & 0 deletions tests/unit/web/pm-provider-registry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ function makeStubWizard(id: string): ProviderWizardDefinition {
id,
label: id,
steps: [],
auth: {
rawCredentials: [{ role: 'api_key', stateField: 'linearApiKey' }],
storedCredentials: { fallbackWhenStateFieldEmpty: 'linearApiKey' },
missingCredentialsMessage: 'Missing credentials',
},
credentialPersistence: [
{ envVarKey: 'STUB_API_KEY', stateField: 'linearApiKey', label: 'Stub API Key' },
],
buildIntegrationConfig: () => ({}),
isSetupComplete: () => true,
};
Expand Down
8 changes: 8 additions & 0 deletions tests/unit/web/pm-wizard-generic-renderer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ function makeStubWizard(id: string): ProviderWizardDefinition {
{ id: 'container', title: 'Container', Component: StubStep, isComplete: () => true },
{ id: 'fields', title: 'Field mappings', Component: StubStep, isComplete: () => true },
],
auth: {
rawCredentials: [{ role: 'api_key', stateField: 'linearApiKey' }],
storedCredentials: { fallbackWhenStateFieldEmpty: 'linearApiKey' },
missingCredentialsMessage: 'Missing credentials',
},
credentialPersistence: [
{ envVarKey: 'STUB_API_KEY', stateField: 'linearApiKey', label: 'Stub API Key' },
],
buildIntegrationConfig: () => ({}),
isSetupComplete: () => true,
};
Expand Down
Loading
Loading