Skip to content

Commit 1fbb24a

Browse files
committed
feat: implement iteration based guidelines generation from science doc
1 parent 1852644 commit 1fbb24a

File tree

8 files changed

+629
-908
lines changed

8 files changed

+629
-908
lines changed

chat-client/src/client/features/rules.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ describe('rules', () => {
148148
it('calls messager when create memory bank is clicked', () => {
149149
const createMemoryBankItem: DetailedListItem = {
150150
id: ContextRule.CreateMemoryBankId,
151-
description: 'Create Memory Bank',
151+
description: 'Generate Memory Bank',
152152
}
153153

154154
onItemClick(createMemoryBankItem)

chat-client/src/client/features/rules.ts

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,8 @@ export class RulesList {
8787
// The tabId should be the same as the one used for the rules list
8888
this.messager.onChatPrompt({
8989
prompt: {
90-
prompt: 'Create a Memory Bank for this project',
91-
escapedPrompt: 'Create a Memory Bank for this project',
90+
prompt: 'Generate a Memory Bank for this project',
91+
escapedPrompt: 'Generate a Memory Bank for this project',
9292
},
9393
tabId: this.tabId, // Use the current tab ID
9494
})
@@ -176,10 +176,36 @@ const createRuleListItem: DetailedListItem = {
176176
id: ContextRule.CreateRuleId,
177177
}
178178

179-
const createMemoryBankListItem: DetailedListItem = {
180-
description: 'Create Memory Bank',
181-
icon: MynahIcons.FOLDER,
182-
id: ContextRule.CreateMemoryBankId,
179+
function createMemoryBankListItem(rules: RulesFolder[]): DetailedListItem {
180+
// Check if memory-bank folder exists with files
181+
// Look for folder that contains memory bank files (product, structure, tech, guidelines)
182+
// Note: Server sends rule names WITHOUT .md extension (e.g., 'product' not 'product.md')
183+
const memoryBankFiles = ['product', 'structure', 'tech', 'guidelines']
184+
185+
console.log('[DEBUG] createMemoryBankListItem called with rules:', JSON.stringify(rules, null, 2))
186+
187+
// Find memory-bank folder
188+
const memoryBankFolder = rules.find(folder => folder.folderName === 'memory-bank')
189+
console.log('[DEBUG] Found memory-bank folder:', JSON.stringify(memoryBankFolder, null, 2))
190+
191+
// Check if any memory bank files exist
192+
const hasMemoryBankFiles =
193+
memoryBankFolder &&
194+
memoryBankFolder.rules.some(rule => {
195+
const matches = memoryBankFiles.includes(rule.name)
196+
console.log(`[DEBUG] Checking rule "${rule.name}" against memory bank files:`, matches)
197+
return matches
198+
})
199+
200+
console.log('[DEBUG] hasMemoryBankFiles result:', hasMemoryBankFiles)
201+
const buttonText = hasMemoryBankFiles ? 'Regenerate Memory Bank' : 'Generate Memory Bank'
202+
console.log('[DEBUG] Button text will be:', buttonText)
203+
204+
return {
205+
description: buttonText,
206+
icon: MynahIcons.FOLDER,
207+
id: ContextRule.CreateMemoryBankId,
208+
}
183209
}
184210

185211
export function convertRulesListToDetailedListGroup(rules: RulesFolder[]): DetailedListItemGroup[] {
@@ -207,7 +233,7 @@ export function convertRulesListToDetailedListGroup(rules: RulesFolder[]): Detai
207233
)
208234
.concat({
209235
groupName: 'Actions',
210-
children: [createMemoryBankListItem, createRuleListItem],
236+
children: [createMemoryBankListItem(rules), createRuleListItem],
211237
})
212238
}
213239

server/aws-lsp-codewhisperer/src/language-server/agenticChat/agenticChatController.ts

Lines changed: 72 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -851,19 +851,77 @@ export class AgenticChatController implements ChatHandlers {
851851

852852
IdleWorkspaceManager.recordActivityTimestamp()
853853

854-
// Memory Bank Creation Flow - Step 1: First 3 Files Only
854+
// Memory Bank Creation Flow - Delegate everything to MemoryBankController
855855
if (this.#memoryBankController.isMemoryBankCreationRequest(params.prompt.prompt)) {
856856
this.#features.logging.info(`[Memory Bank] Detected memory bank request for tabId: ${params.tabId}`)
857857

858-
// Step 1: Create first 3 files using standard agent analysis
859-
params.prompt.prompt = this.#memoryBankController.getFirst3FilesPrompt()
858+
// Delegate the entire workflow to MemoryBankController
859+
const workspaceFolders = workspaceUtils.getWorkspaceFolderPaths(this.#features.workspace)
860+
const workspaceUri = workspaceFolders.length > 0 ? workspaceFolders[0] : ''
861+
params.prompt.prompt = await this.#memoryBankController.prepareComprehensiveMemoryBankPrompt(
862+
workspaceUri,
863+
async (message: string) => {
864+
await this.#features.chat.sendChatUpdate({
865+
tabId: params.tabId,
866+
data: {
867+
messages: [
868+
{
869+
messageId: `memory-bank-status-${Date.now()}`,
870+
body: message,
871+
},
872+
],
873+
},
874+
})
875+
},
876+
async (prompt: string) => {
877+
// Direct LLM call for ranking - no agentic loop, just a simple API call
878+
this.#features.logging.info(`[Memory Bank] Making direct LLM call for file ranking`)
860879

861-
this.#features.logging.info(
862-
`[Memory Bank] Step 1: Creating first 3 memory bank files (product.md, structure.md, tech.md)`
880+
try {
881+
if (!this.#serviceManager) {
882+
throw new Error('amazonQServiceManager is not initialized')
883+
}
884+
885+
const client = this.#serviceManager.getStreamingClient()
886+
887+
// Create a simple message request for ranking
888+
const requestInput: SendMessageCommandInput = {
889+
conversationState: {
890+
chatTriggerType: ChatTriggerType.MANUAL,
891+
currentMessage: {
892+
userInputMessage: {
893+
content: prompt,
894+
},
895+
},
896+
},
897+
}
898+
899+
this.#features.logging.info(`[Memory Bank] Sending ranking request to LLM`)
900+
const response = await client.sendMessage(requestInput)
901+
902+
// Extract the response content from the streaming response
903+
let responseContent = ''
904+
if (response.sendMessageResponse) {
905+
for await (const chatEvent of response.sendMessageResponse) {
906+
if (chatEvent.assistantResponseEvent?.content) {
907+
responseContent += chatEvent.assistantResponseEvent.content
908+
}
909+
}
910+
}
911+
912+
this.#features.logging.info(
913+
`[Memory Bank] LLM ranking response received: ${responseContent.length} characters`
914+
)
915+
return responseContent.trim()
916+
} catch (error) {
917+
this.#features.logging.error(`[Memory Bank] LLM ranking failed: ${error}`)
918+
this.#features.logging.info(`[Memory Bank] Falling back to TF-IDF ranking`)
919+
return '' // Empty string triggers TF-IDF fallback
920+
}
921+
}
863922
)
864923

865-
// Mark this session for memory bank completion detection
866-
this.#markSessionForMemoryBankCompletion(params.tabId)
924+
this.#features.logging.info(`[Memory Bank] Starting comprehensive memory bank creation`)
867925
}
868926

869927
const maybeDefaultResponse = !params.prompt.command && getDefaultChatResponse(params.prompt.prompt)
@@ -942,7 +1000,8 @@ export class AgenticChatController implements ChatHandlers {
9421000
const additionalContext = await this.#additionalContextProvider.getAdditionalContext(
9431001
triggerContext,
9441002
params.tabId,
945-
params.context
1003+
params.context,
1004+
params.prompt.prompt
9461005
)
9471006
// Add active file to context list if it's not already there
9481007
const activeFile =
@@ -4387,10 +4446,7 @@ export class AgenticChatController implements ChatHandlers {
43874446
if (chatEvent.assistantResponseEvent || chatEvent.codeReferenceEvent) {
43884447
await streamWriter.write(result.data.chatResult)
43894448

4390-
// Memory Bank completion detection
4391-
if (chatEvent.assistantResponseEvent && result.data.chatResult.body) {
4392-
await this.#checkMemoryBankCompletion(tabId, result.data.chatResult.body)
4393-
}
4449+
// Memory Bank completion detection - removed from here to avoid timing issues
43944450
}
43954451

43964452
if (chatEvent.toolUseEvent) {
@@ -4418,6 +4474,8 @@ export class AgenticChatController implements ChatHandlers {
44184474
}
44194475
await streamWriter.close()
44204476

4477+
// No special completion detection needed - single agentic loop completes naturally
4478+
44214479
metric.mergeWith({
44224480
cwsprChatFullResponseLatency: metric.getTimeElapsed(),
44234481
cwsprChatFollowUpCount: chatEventParser.totalEvents.followupPromptEvent,
@@ -4749,94 +4807,7 @@ export class AgenticChatController implements ChatHandlers {
47494807
return value
47504808
}
47514809

4752-
// Memory Bank Completion Detection
4753-
#memoryBankPendingSessions = new Set<string>()
4754-
4755-
/**
4756-
* Mark a session for memory bank completion detection
4757-
*/
4758-
#markSessionForMemoryBankCompletion(tabId: string): void {
4759-
this.#memoryBankPendingSessions.add(tabId)
4760-
this.#features.logging.info(`[Memory Bank] Marked session ${tabId} for completion detection`)
4761-
}
4762-
4763-
/**
4764-
* Check if a session is pending memory bank completion
4765-
*/
4766-
#isMemoryBankPendingCompletion(tabId: string): boolean {
4767-
return this.#memoryBankPendingSessions.has(tabId)
4768-
}
4769-
4770-
/**
4771-
* Detect memory bank completion and trigger guidelines generation
4772-
*/
4773-
async #checkMemoryBankCompletion(tabId: string, messageContent: string): Promise<void> {
4774-
if (!this.#isMemoryBankPendingCompletion(tabId)) {
4775-
return
4776-
}
4777-
4778-
// Check if the agent has completed creating the first 3 files
4779-
const completionIndicators = [
4780-
'product.md',
4781-
'structure.md',
4782-
'tech.md',
4783-
'memory bank',
4784-
'created successfully',
4785-
'files have been created',
4786-
]
4787-
4788-
const hasCompletionIndicators = completionIndicators.some(indicator =>
4789-
messageContent.toLowerCase().includes(indicator.toLowerCase())
4790-
)
4791-
4792-
if (hasCompletionIndicators) {
4793-
this.#features.logging.info(`[Memory Bank] Step 1 completion detected for session ${tabId}`)
4794-
4795-
// Remove from pending set
4796-
this.#memoryBankPendingSessions.delete(tabId)
4797-
4798-
// Trigger Steps 2-5: Guidelines generation
4799-
await this.#triggerGuidelinesGeneration(tabId)
4800-
}
4801-
}
4802-
4803-
/**
4804-
* Trigger guidelines.md generation (Steps 2-5)
4805-
*/
4806-
async #triggerGuidelinesGeneration(tabId: string): Promise<void> {
4807-
try {
4808-
this.#features.logging.info(`[Memory Bank] Starting guidelines generation for session ${tabId}`)
4810+
// Memory Bank logic moved to MemoryBankController
48094811

4810-
// Get workspace folder URI - use empty string as default for root workspace
4811-
const workspaceFolderUri = ''
4812-
4813-
// Create LLM call function that uses the chat system
4814-
const llmCallFunction = async (prompt: string): Promise<string> => {
4815-
// TODO: Implement actual LLM call using the chat system
4816-
// For now, return a placeholder response
4817-
return 'LLM response placeholder'
4818-
}
4819-
4820-
// Delegate all complex logic to MemoryBankController
4821-
const result = await this.#memoryBankController.executeCompleteMemoryBankCreationWithLLM(
4822-
workspaceFolderUri,
4823-
llmCallFunction
4824-
)
4825-
4826-
// Send message to chat
4827-
await this.#features.chat.sendChatUpdate({
4828-
tabId,
4829-
data: {
4830-
messages: [
4831-
{
4832-
messageId: `memory-bank-complete-${Date.now()}`,
4833-
body: result.message,
4834-
},
4835-
],
4836-
},
4837-
})
4838-
} catch (error) {
4839-
this.#features.logging.error(`[Memory Bank] Error in guidelines generation: ${error}`)
4840-
}
4841-
}
4812+
// All Memory Bank methods moved to MemoryBankController and MemoryBankPrompts
48424813
}

server/aws-lsp-codewhisperer/src/language-server/agenticChat/context/additionalContextProvider.ts

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import { ChatDatabase } from '../tools/chatDb/chatDb'
3333
import { ChatMessage, ImageBlock, ImageFormat } from '@amzn/codewhisperer-streaming'
3434
import { getRelativePathWithUri, getRelativePathWithWorkspaceFolder } from '../../workspaceContext/util'
3535
import { isSupportedImageExtension, MAX_IMAGE_CONTEXT_COUNT } from '../../../shared/imageVerification'
36+
import { MemoryBankController } from './memorybank/memoryBankController'
3637
import { mergeFileLists } from './contextUtils'
3738

3839
export const ACTIVE_EDITOR_CONTEXT_ID = 'active-editor'
@@ -229,7 +230,8 @@ export class AdditionalContextProvider {
229230
async getAdditionalContext(
230231
triggerContext: TriggerContext,
231232
tabId: string,
232-
context?: ContextCommand[]
233+
context?: ContextCommand[],
234+
prompt?: string
233235
): Promise<AdditionalContentEntryAddition[]> {
234236
triggerContext.contextInfo = getInitialContextInfo()
235237

@@ -250,21 +252,43 @@ export class AdditionalContextProvider {
250252
: workspaceUtils.getWorkspaceFolderPaths(this.features.workspace)[0]
251253

252254
if (workspaceRules.length > 0) {
253-
// ALL workspace rules (including memory bank files) go to pinnedContextCommands
254-
// This follows the same pattern as regular rules - they go to conversation history
255-
pinnedContextCommands.push(...workspaceRules)
256-
257-
// Log memory bank files being included in chat context for verification
258-
const memoryBankFiles = workspaceRules.filter(rule => rule.id?.includes('memory-bank'))
259-
if (memoryBankFiles.length > 0) {
260-
this.features.logging.info(
261-
`[Memory Bank] Including ${memoryBankFiles.length} memory bank files in chat context for tabId: ${tabId}`
262-
)
263-
memoryBankFiles.forEach(file => {
264-
const fileName = path.basename(file.id)
265-
this.features.logging.info(`[Memory Bank] - Including: ${fileName} (${file.id})`)
266-
})
255+
// Check if this is a memory bank generation request
256+
const isMemoryBankRequest = prompt
257+
? new MemoryBankController(this.features).isMemoryBankCreationRequest(prompt)
258+
: false
259+
260+
let rulesToInclude = workspaceRules
261+
262+
if (isMemoryBankRequest) {
263+
// Exclude memory bank files from context when generating memory bank
264+
const memoryBankFiles = workspaceRules.filter(rule => rule.id?.includes('memory-bank'))
265+
rulesToInclude = workspaceRules.filter(rule => !rule.id?.includes('memory-bank'))
266+
267+
if (memoryBankFiles.length > 0) {
268+
this.features.logging.info(
269+
`[Memory Bank] Excluding ${memoryBankFiles.length} existing memory bank files from context for regeneration`
270+
)
271+
memoryBankFiles.forEach(file => {
272+
const fileName = path.basename(file.id)
273+
this.features.logging.info(`[Memory Bank] - Excluding: ${fileName} (${file.id})`)
274+
})
275+
}
276+
} else {
277+
// Normal behavior: include all workspace rules (including memory bank files)
278+
const memoryBankFiles = workspaceRules.filter(rule => rule.id?.includes('memory-bank'))
279+
if (memoryBankFiles.length > 0) {
280+
this.features.logging.info(
281+
`[Memory Bank] Including ${memoryBankFiles.length} memory bank files in chat context for tabId: ${tabId}`
282+
)
283+
memoryBankFiles.forEach(file => {
284+
const fileName = path.basename(file.id)
285+
this.features.logging.info(`[Memory Bank] - Including: ${fileName} (${file.id})`)
286+
})
287+
}
267288
}
289+
290+
// Add the filtered rules to pinned context
291+
pinnedContextCommands.push(...rulesToInclude)
268292
}
269293

270294
// Merge pinned context with context added to prompt, avoiding duplicates
@@ -700,6 +724,8 @@ export class AdditionalContextProvider {
700724
}
701725

702726
convertRulesToRulesFolders(workspaceRules: ContextCommandItem[], tabId: string): RulesFolder[] {
727+
this.features.logging.info(`[DEBUG] convertRulesToRulesFolders called with ${workspaceRules.length} rules`)
728+
703729
// Check if there's only one workspace folder
704730
const workspaceFolders = workspaceUtils.getWorkspaceFolderPaths(this.features.workspace)
705731
const isSingleWorkspace = workspaceFolders.length <= 1
@@ -719,7 +745,9 @@ export class AdditionalContextProvider {
719745
if (dirPath === '.') {
720746
folderName = undefined
721747
} else {
722-
folderName = dirPath
748+
// Use only the last part of the directory path for display
749+
// e.g., ".amazonq/rules/memory-bank" becomes "memory-bank"
750+
folderName = path.basename(dirPath)
723751
}
724752
} else {
725753
// In multi-workspace: include workspace folder name for all files
@@ -805,6 +833,8 @@ export class AdditionalContextProvider {
805833
return a.folderName.localeCompare(b.folderName)
806834
})
807835

836+
this.features.logging.info(`[DEBUG] Final rulesFolders structure: ${JSON.stringify(rulesFolders, null, 2)}`)
837+
808838
return rulesFolders
809839
}
810840

0 commit comments

Comments
 (0)