-
Notifications
You must be signed in to change notification settings - Fork 1
fix(mcp): route product feedback prompts to packet #131
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,12 @@ | ||
| { | ||
| "channel": "latest-published", | ||
| "sourceHash": "sha256:500fcc0babd0db1184a4cb5b4487188af4f54911ae3ec99943d5902bb6229a50", | ||
| "compilerFingerprint": "sha256:64da4d22faf54820c33710eafe9cebac7012ecdda4ae9d2d99bb5ec4fd7f4de7", | ||
| "compilerFingerprint": "sha256:9cd395da915eb3ce5276fa51b3189840434a9bb6294035d08fa38b9881e95fc5", | ||
| "upstreamRef": "ee40821c9c49c642dfedb18d6915388dbec4e7df", | ||
| "upstreamRepoUrl": "https://github.com/ailev/FPF", | ||
| "upstreamCommittedAt": "2026-05-12T16:57:41Z", | ||
| "specPath": "published/current/FPF-Spec.md", | ||
| "snapshotPath": "published/current/fpf-index/snapshot.json", | ||
| "specBytes": 6566931, | ||
| "publishedAt": "2026-05-13T08:00:55.400Z" | ||
| "publishedAt": "2026-05-14T14:09:56.488Z" | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -3,6 +3,7 @@ import { | |||||||||||||||||||||||||
| getPartCDraftsByCluster, | ||||||||||||||||||||||||||
| selectBestAnchors, | ||||||||||||||||||||||||||
| } from './compiler.js'; | ||||||||||||||||||||||||||
| import { hasProductRoleFeedbackIntent } from './route-intent-signals.js'; | ||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||
| unique, | ||||||||||||||||||||||||||
| } from './text.js'; | ||||||||||||||||||||||||||
|
|
@@ -94,12 +95,19 @@ export function buildRouteAnswer( | |||||||||||||||||||||||||
| rebuilt: boolean, | ||||||||||||||||||||||||||
| ): QueryResult { | ||||||||||||||||||||||||||
| const route = snapshot.routeGraph.nodes[routeNodeId]!; | ||||||||||||||||||||||||||
| const productRoleFeedbackPacket = isProductRoleFeedbackPacket(routeNodeId, question); | ||||||||||||||||||||||||||
| const supplementalIds = productRoleFeedbackPacket | ||||||||||||||||||||||||||
| ? ['E.12', 'A.1.1', 'A.15', 'A.2.2', 'A.2.3'].filter( | ||||||||||||||||||||||||||
| (id) => Boolean(snapshot.compiledNodes[id]), | ||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||
|
Comment on lines
+100
to
+102
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||||||||||
| : []; | ||||||||||||||||||||||||||
| // Include routeSurfaces alongside the ordered/optional/landing lists. | ||||||||||||||||||||||||||
| // Some routes (e.g. boundary-unpacking-claim-routing) only declare | ||||||||||||||||||||||||||
| // surfaces; without this fallback the structured `ids` response would | ||||||||||||||||||||||||||
| // be just the route ID, hiding the patterns the answer prose names. | ||||||||||||||||||||||||||
| const ids = unique([ | ||||||||||||||||||||||||||
| routeNodeId, | ||||||||||||||||||||||||||
| ...supplementalIds, | ||||||||||||||||||||||||||
| ...route.orderedIds, | ||||||||||||||||||||||||||
| ...route.optionalIds, | ||||||||||||||||||||||||||
| ...route.landingIds, | ||||||||||||||||||||||||||
|
|
@@ -115,20 +123,29 @@ export function buildRouteAnswer( | |||||||||||||||||||||||||
| route.firstHonestBurden | ||||||||||||||||||||||||||
| ? `First honest burden: ${route.firstHonestBurden}.` | ||||||||||||||||||||||||||
| : 'Choose this route only when the stated burden matches the present problem.', | ||||||||||||||||||||||||||
| ...(productRoleFeedbackPacket | ||||||||||||||||||||||||||
| ? [ | ||||||||||||||||||||||||||
| 'Use the product-role feedback packet: E.12 plus A.1.1, A.15, A.2.2, and A.2.3 before escalating to broader spec-writing guidance.', | ||||||||||||||||||||||||||
| 'Do not paste the whole FPF, and do not load E.8/E.19 unless the feedback is actually about writing or revising FPF pattern text.', | ||||||||||||||||||||||||||
| ] | ||||||||||||||||||||||||||
| : []), | ||||||||||||||||||||||||||
| ...(route.constraints ?? []), | ||||||||||||||||||||||||||
| ]; | ||||||||||||||||||||||||||
| const answer = [ | ||||||||||||||||||||||||||
| `${route.id} (${route.name}) is the matched first-practical route.`, | ||||||||||||||||||||||||||
| route.firstHonestBurden ? `Burden: ${route.firstHonestBurden}.` : '', | ||||||||||||||||||||||||||
| productRoleFeedbackPacket | ||||||||||||||||||||||||||
| ? `Product-role feedback packet IDs: ${supplementalIds.join(', ')}.` | ||||||||||||||||||||||||||
| : '', | ||||||||||||||||||||||||||
| route.orderedIds.length > 0 | ||||||||||||||||||||||||||
| ? `Ordered entry IDs: ${route.orderedIds.join(' -> ')}.` | ||||||||||||||||||||||||||
| : '', | ||||||||||||||||||||||||||
| route.optionalIds.length > 0 | ||||||||||||||||||||||||||
| ? `Conditional additions: ${route.optionalIds.join(', ')}.` | ||||||||||||||||||||||||||
| : '', | ||||||||||||||||||||||||||
| route.landingIds.length > 0 ? `Landing surface: ${route.landingIds.join(', ')}.` : '', | ||||||||||||||||||||||||||
| `Acceptance check: ${routeAcceptanceCheck(routeNodeId, route)}.`, | ||||||||||||||||||||||||||
| `Next move: ${routeNextMove(routeNodeId, route)}.`, | ||||||||||||||||||||||||||
| `Acceptance check: ${routeAcceptanceCheck(routeNodeId, route, question)}.`, | ||||||||||||||||||||||||||
| `Next move: ${routeNextMove(routeNodeId, route, question)}.`, | ||||||||||||||||||||||||||
|
Comment on lines
+147
to
+148
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To avoid redundant intent detection (which involves string normalization and multiple substring checks), pass the already calculated
Suggested change
|
||||||||||||||||||||||||||
| route.routeSurfaces.length > 0 | ||||||||||||||||||||||||||
| ? `Route-bearing surfaces: ${route.routeSurfaces.join(', ')}.` | ||||||||||||||||||||||||||
| : '', | ||||||||||||||||||||||||||
|
|
@@ -179,7 +196,11 @@ export function buildRouteAnswer( | |||||||||||||||||||||||||
| function routeAcceptanceCheck( | ||||||||||||||||||||||||||
| routeNodeId: string, | ||||||||||||||||||||||||||
| route: Snapshot['routeGraph']['nodes'][string], | ||||||||||||||||||||||||||
| question: string, | ||||||||||||||||||||||||||
| ): string { | ||||||||||||||||||||||||||
| if (isProductRoleFeedbackPacket(routeNodeId, question)) { | ||||||||||||||||||||||||||
|
Comment on lines
196
to
+201
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Update the signature to accept the pre-calculated boolean to avoid redundant intent detection.
Suggested change
|
||||||||||||||||||||||||||
| return 'the role/job can be replayed by another person, the feedback points at exact evidence, and the output lands as one focused PR, issue, discussion, or no-new-feedback checkpoint'; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| switch (routeNodeId) { | ||||||||||||||||||||||||||
| case 'route:project-alignment': | ||||||||||||||||||||||||||
| return 'a shared kickoff packet names the bounded context, actor roles, role/method/work split, first work-plan item, evidence to collect, and UTS-ready terms'; | ||||||||||||||||||||||||||
|
|
@@ -197,7 +218,11 @@ function routeAcceptanceCheck( | |||||||||||||||||||||||||
| function routeNextMove( | ||||||||||||||||||||||||||
| routeNodeId: string, | ||||||||||||||||||||||||||
| route: Snapshot['routeGraph']['nodes'][string], | ||||||||||||||||||||||||||
| question: string, | ||||||||||||||||||||||||||
| ): string { | ||||||||||||||||||||||||||
| if (isProductRoleFeedbackPacket(routeNodeId, question)) { | ||||||||||||||||||||||||||
|
Comment on lines
218
to
+223
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Update the signature to accept the pre-calculated boolean to avoid redundant intent detection.
Suggested change
|
||||||||||||||||||||||||||
| return 'name the persona/job and surface first, use E.12 with A.1.1, A.15, A.2.2, and A.2.3 to capture one adoption friction, then stop at one proposed improvement with severity and validation path'; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| switch (routeNodeId) { | ||||||||||||||||||||||||||
| case 'route:project-alignment': | ||||||||||||||||||||||||||
| return 'read A.1.1 and A.15 first, draft the kickoff worksheet, then add A.15.2/A.15.3 only when the plan/run split must be made explicit'; | ||||||||||||||||||||||||||
|
|
@@ -212,6 +237,10 @@ function routeNextMove( | |||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| function isProductRoleFeedbackPacket(routeNodeId: string, question: string): boolean { | ||||||||||||||||||||||||||
| return routeNodeId === 'route:project-alignment' && hasProductRoleFeedbackIntent(question); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| export function buildPatternAnswer( | ||||||||||||||||||||||||||
| question: string, | ||||||||||||||||||||||||||
| mode: AnswerMode, | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -35,6 +35,8 @@ import { | |||||||||||||||
| AGENT_WORKFLOW_JOB_SIGNALS, | ||||||||||||||||
| BOUNDARY_BURDEN_SIGNALS, | ||||||||||||||||
| BOUNDARY_REVIEW_RULE_JOB_SIGNALS, | ||||||||||||||||
| PRODUCT_ROLE_FEEDBACK_OUTPUT_SIGNALS, | ||||||||||||||||
| PRODUCT_ROLE_FEEDBACK_ROLE_SIGNALS, | ||||||||||||||||
| } from './route-intent-signals.js'; | ||||||||||||||||
|
Comment on lines
+38
to
40
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Import the newly defined
Suggested change
|
||||||||||||||||
| import { buildValidation } from './validation-runner.js'; | ||||||||||||||||
| import type { | ||||||||||||||||
|
|
@@ -250,6 +252,20 @@ function buildHeuristicSeedRules( | |||||||||||||||
| routeId: alignmentRoute.id, | ||||||||||||||||
| routeScore: 88, | ||||||||||||||||
| }); | ||||||||||||||||
| const productRoleFeedbackNodeIds = ['E.12', 'A.1.1', 'A.15', 'A.2.2', 'A.2.3'].filter( | ||||||||||||||||
| (id) => id in patternNodes || id in routeNodes, | ||||||||||||||||
| ); | ||||||||||||||||
|
Comment on lines
+255
to
+257
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use the
Suggested change
|
||||||||||||||||
| rules.push({ | ||||||||||||||||
| name: 'product-role-feedback', | ||||||||||||||||
| allOf: [[...PRODUCT_ROLE_FEEDBACK_ROLE_SIGNALS]], | ||||||||||||||||
| anyOf: [[...PRODUCT_ROLE_FEEDBACK_OUTPUT_SIGNALS, ...AGENT_WORKFLOW_BOUNDED_RETRIEVAL_SIGNALS]], | ||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seed rule anyOf broader than projection intent checkLow Severity The Additional Locations (1)Reviewed by Cursor Bugbot for commit b0d1384. Configure here. |
||||||||||||||||
| seedNodeIds: productRoleFeedbackNodeIds, | ||||||||||||||||
| seedScore: 20, | ||||||||||||||||
| seedOrigin: 'route_expansion', | ||||||||||||||||
| initialNodeIds: [], | ||||||||||||||||
| routeId: alignmentRoute.id, | ||||||||||||||||
| routeScore: 92, | ||||||||||||||||
| }); | ||||||||||||||||
| rules.push({ | ||||||||||||||||
| name: 'vocabulary-alignment', | ||||||||||||||||
| allOf: [['vocabulary']], | ||||||||||||||||
|
|
||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -68,6 +68,25 @@ export const AGENT_WORKFLOW_BOUNDED_RETRIEVAL_SIGNALS = [ | |
| 'instead of pasting', | ||
| ] as const; | ||
|
|
||
| export const PRODUCT_ROLE_FEEDBACK_ROLE_SIGNALS = [ | ||
| 'product maintainer', | ||
| 'product feedback', | ||
| 'product-role feedback', | ||
| 'product role feedback', | ||
| 'role feedback', | ||
| 'role-feedback', | ||
| 'dogfood', | ||
| ] as const; | ||
|
|
||
| export const PRODUCT_ROLE_FEEDBACK_OUTPUT_SIGNALS = [ | ||
| 'adoption improvement', | ||
| 'live product smoke', | ||
| 'discussion-ready', | ||
| 'discussion ready', | ||
| 'severity', | ||
| 'validation path', | ||
| ] as const; | ||
|
|
||
|
Comment on lines
+88
to
+89
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The list of IDs for the product-role feedback packet is hardcoded in multiple locations. Consider defining it as a shared constant here to improve maintainability and ensure consistency across the runtime and compiler. ] as const;
export const PRODUCT_ROLE_FEEDBACK_PACKET_IDS = ['E.12', 'A.1.1', 'A.15', 'A.2.2', 'A.2.3'] as const; |
||
| export const WRITING_OR_REVIEWING_PATTERN_SIGNALS = [ | ||
| 'spec writer', | ||
| 'spec writing', | ||
|
|
@@ -79,3 +98,18 @@ export const WRITING_OR_REVIEWING_PATTERN_SIGNALS = [ | |
| export function hasBoundaryReviewNegation(normalizedQuestion: string): boolean { | ||
| return BOUNDARY_REVIEW_NEGATIONS.some((phrase) => normalizedQuestion.includes(phrase)); | ||
| } | ||
|
|
||
| export function hasProductRoleFeedbackIntent(question: string): boolean { | ||
| const normalizedQuestion = question.toLowerCase(); | ||
| const hasRoleSignal = PRODUCT_ROLE_FEEDBACK_ROLE_SIGNALS.some((phrase) => | ||
| normalizedQuestion.includes(phrase), | ||
| ); | ||
|
Comment on lines
+104
to
+106
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Useful? React with 👍 / 👎. |
||
| const hasQualifyingSignal = | ||
| PRODUCT_ROLE_FEEDBACK_OUTPUT_SIGNALS.some((phrase) => | ||
| normalizedQuestion.includes(phrase), | ||
| ) || | ||
| AGENT_WORKFLOW_BOUNDED_RETRIEVAL_SIGNALS.some((phrase) => | ||
| normalizedQuestion.includes(phrase), | ||
| ); | ||
| return hasRoleSignal && hasQualifyingSignal; | ||
| } | ||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Import the
PRODUCT_ROLE_FEEDBACK_PACKET_IDSconstant.