ai-sdlc 0.2.0-alpha.9 → 0.2.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/README.md +53 -1058
  2. package/dist/agents/implementation.d.ts +6 -0
  3. package/dist/agents/implementation.d.ts.map +1 -1
  4. package/dist/agents/implementation.js +87 -13
  5. package/dist/agents/implementation.js.map +1 -1
  6. package/dist/agents/planning.d.ts.map +1 -1
  7. package/dist/agents/planning.js +22 -3
  8. package/dist/agents/planning.js.map +1 -1
  9. package/dist/agents/refinement.d.ts.map +1 -1
  10. package/dist/agents/refinement.js +22 -3
  11. package/dist/agents/refinement.js.map +1 -1
  12. package/dist/agents/research.d.ts +85 -1
  13. package/dist/agents/research.d.ts.map +1 -1
  14. package/dist/agents/research.js +506 -16
  15. package/dist/agents/research.js.map +1 -1
  16. package/dist/agents/review.d.ts +67 -2
  17. package/dist/agents/review.d.ts.map +1 -1
  18. package/dist/agents/review.js +477 -68
  19. package/dist/agents/review.js.map +1 -1
  20. package/dist/agents/rework.d.ts.map +1 -1
  21. package/dist/agents/rework.js +22 -3
  22. package/dist/agents/rework.js.map +1 -1
  23. package/dist/agents/state-assessor.d.ts +3 -3
  24. package/dist/agents/state-assessor.d.ts.map +1 -1
  25. package/dist/agents/state-assessor.js +6 -6
  26. package/dist/agents/state-assessor.js.map +1 -1
  27. package/dist/agents/test-pattern-detector.d.ts +49 -0
  28. package/dist/agents/test-pattern-detector.d.ts.map +1 -0
  29. package/dist/agents/test-pattern-detector.js +273 -0
  30. package/dist/agents/test-pattern-detector.js.map +1 -0
  31. package/dist/agents/verification.d.ts +11 -0
  32. package/dist/agents/verification.d.ts.map +1 -1
  33. package/dist/agents/verification.js +74 -1
  34. package/dist/agents/verification.js.map +1 -1
  35. package/dist/cli/commands/migrate.js +1 -1
  36. package/dist/cli/commands/migrate.js.map +1 -1
  37. package/dist/cli/commands.d.ts +43 -3
  38. package/dist/cli/commands.d.ts.map +1 -1
  39. package/dist/cli/commands.js +588 -150
  40. package/dist/cli/commands.js.map +1 -1
  41. package/dist/cli/daemon.d.ts.map +1 -1
  42. package/dist/cli/daemon.js +20 -3
  43. package/dist/cli/daemon.js.map +1 -1
  44. package/dist/cli/runner.d.ts.map +1 -1
  45. package/dist/cli/runner.js +18 -6
  46. package/dist/cli/runner.js.map +1 -1
  47. package/dist/core/auth.d.ts +43 -0
  48. package/dist/core/auth.d.ts.map +1 -1
  49. package/dist/core/auth.js +105 -1
  50. package/dist/core/auth.js.map +1 -1
  51. package/dist/core/client.d.ts +6 -0
  52. package/dist/core/client.d.ts.map +1 -1
  53. package/dist/core/client.js +57 -3
  54. package/dist/core/client.js.map +1 -1
  55. package/dist/core/config.d.ts +5 -1
  56. package/dist/core/config.d.ts.map +1 -1
  57. package/dist/core/config.js +27 -0
  58. package/dist/core/config.js.map +1 -1
  59. package/dist/core/conflict-detector.d.ts +108 -0
  60. package/dist/core/conflict-detector.d.ts.map +1 -0
  61. package/dist/core/conflict-detector.js +413 -0
  62. package/dist/core/conflict-detector.js.map +1 -0
  63. package/dist/core/git-utils.d.ts +10 -1
  64. package/dist/core/git-utils.d.ts.map +1 -1
  65. package/dist/core/git-utils.js +55 -4
  66. package/dist/core/git-utils.js.map +1 -1
  67. package/dist/core/index.d.ts +17 -0
  68. package/dist/core/index.d.ts.map +1 -0
  69. package/dist/core/index.js +17 -0
  70. package/dist/core/index.js.map +1 -0
  71. package/dist/core/kanban.d.ts +1 -1
  72. package/dist/core/kanban.d.ts.map +1 -1
  73. package/dist/core/kanban.js +3 -3
  74. package/dist/core/kanban.js.map +1 -1
  75. package/dist/core/logger.d.ts +92 -0
  76. package/dist/core/logger.d.ts.map +1 -0
  77. package/dist/core/logger.js +221 -0
  78. package/dist/core/logger.js.map +1 -0
  79. package/dist/core/story-logger.d.ts +102 -0
  80. package/dist/core/story-logger.d.ts.map +1 -0
  81. package/dist/core/story-logger.js +265 -0
  82. package/dist/core/story-logger.js.map +1 -0
  83. package/dist/core/story.d.ts +79 -20
  84. package/dist/core/story.d.ts.map +1 -1
  85. package/dist/core/story.js +221 -39
  86. package/dist/core/story.js.map +1 -1
  87. package/dist/core/workflow-state.d.ts +45 -6
  88. package/dist/core/workflow-state.d.ts.map +1 -1
  89. package/dist/core/workflow-state.js +201 -12
  90. package/dist/core/workflow-state.js.map +1 -1
  91. package/dist/core/worktree.d.ts +9 -0
  92. package/dist/core/worktree.d.ts.map +1 -1
  93. package/dist/core/worktree.js +52 -1
  94. package/dist/core/worktree.js.map +1 -1
  95. package/dist/index.js +112 -6
  96. package/dist/index.js.map +1 -1
  97. package/dist/types/index.d.ts +123 -1
  98. package/dist/types/index.d.ts.map +1 -1
  99. package/dist/types/index.js +1 -0
  100. package/dist/types/index.js.map +1 -1
  101. package/package.json +3 -1
@@ -1,5 +1,5 @@
1
1
  import { AgentProgressCallback } from '../core/client.js';
2
- import { AgentResult } from '../types/index.js';
2
+ import { Story, AgentResult, FARScore } from '../types/index.js';
3
3
  export interface AgentOptions {
4
4
  /** Context from a previous review failure - must address these issues */
5
5
  reworkContext?: string;
@@ -13,4 +13,88 @@ export interface AgentOptions {
13
13
  * and gathering relevant information.
14
14
  */
15
15
  export declare function runResearchAgent(storyPath: string, sdlcRoot: string, options?: AgentOptions): Promise<AgentResult>;
16
+ /**
17
+ * Gather context about the codebase for research.
18
+ *
19
+ * Collects information about:
20
+ * - Project configuration files (package.json, tsconfig.json, etc.)
21
+ * - Directory structure
22
+ * - Source files
23
+ * - Test files (for understanding testing patterns)
24
+ * - Configuration files (for discovering config patterns)
25
+ *
26
+ * @param sdlcRoot - Path to the .ai-sdlc directory
27
+ * @returns Formatted context string with codebase information
28
+ */
29
+ export declare function gatherCodebaseContext(sdlcRoot: string): Promise<string>;
30
+ /**
31
+ * Determine if web research would add value based on story content and codebase context.
32
+ *
33
+ * Web research is triggered when:
34
+ * 1. External dependencies are referenced (libraries, APIs, frameworks)
35
+ * 2. Unfamiliar APIs/patterns are mentioned
36
+ * 3. Library-specific documentation is needed
37
+ * 4. Best practices are requested
38
+ *
39
+ * Web research is skipped when:
40
+ * - Topic is purely internal (refactoring, moving code, internal utilities)
41
+ * - No external dependencies mentioned
42
+ */
43
+ export declare function shouldPerformWebResearch(story: Story, codebaseContext: string): boolean;
44
+ /**
45
+ * Sanitize web research content for safe storage and display.
46
+ * Removes ANSI escape sequences, control characters, and potential injection vectors.
47
+ *
48
+ * Security rationale: Web research content comes from external sources (LLM, web tools)
49
+ * and must be sanitized before storage to prevent:
50
+ * - ANSI injection (terminal control sequence attacks)
51
+ * - Markdown injection (malicious formatting)
52
+ * - Control character injection (null bytes, bell characters, etc.)
53
+ *
54
+ * @param text - Raw web research content from external source
55
+ * @returns Sanitized text safe for storage in markdown files
56
+ */
57
+ export declare function sanitizeWebResearchContent(text: string): string;
58
+ /**
59
+ * Sanitize text for logging to prevent log injection attacks.
60
+ * Replaces newlines with spaces and truncates for readability.
61
+ *
62
+ * Security rationale: Log injection attacks use newlines to inject fake log entries.
63
+ * By replacing newlines with spaces, we ensure each log() call produces exactly one log line.
64
+ *
65
+ * @param text - Raw text that will be logged
66
+ * @returns Sanitized text safe for logging (single line, truncated)
67
+ */
68
+ export declare function sanitizeForLogging(text: string): string;
69
+ /**
70
+ * Sanitize codebase context before including in LLM prompts.
71
+ * Escapes dangerous patterns that could cause prompt injection.
72
+ *
73
+ * Security rationale: Codebase files may contain malicious content from:
74
+ * - Compromised dependencies
75
+ * - Malicious commits
76
+ * - Untrusted contributors
77
+ *
78
+ * We must prevent prompt injection by escaping patterns that could:
79
+ * - Terminate the prompt early (triple backticks)
80
+ * - Inject commands or instructions
81
+ * - Confuse the LLM's understanding of structure
82
+ *
83
+ * @param text - Raw codebase context
84
+ * @returns Sanitized text safe for LLM prompts
85
+ */
86
+ export declare function sanitizeCodebaseContext(text: string): string;
87
+ /**
88
+ * Parse FAR evaluation from web research finding text.
89
+ * Expected format from LLM:
90
+ * **FAR Score**: Factuality: 5, Actionability: 4, Relevance: 5
91
+ * **Justification**: Official documentation provides...
92
+ *
93
+ * Returns default scores (2, 2, 2) with parsingSucceeded: false if parsing fails.
94
+ * Default of 2 (rather than 3) indicates uncertainty rather than average quality.
95
+ *
96
+ * @param finding - Web research finding text to parse
97
+ * @returns FARScore with parsed or default values and parsing status
98
+ */
99
+ export declare function evaluateFAR(finding: string): FARScore;
16
100
  //# sourceMappingURL=research.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"research.d.ts","sourceRoot":"","sources":["../../src/agents/research.ts"],"names":[],"mappings":"AAIA,OAAO,EAAiB,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AACzE,OAAO,EAAS,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAavD,MAAM,WAAW,YAAY;IAC3B,yEAAyE;IACzE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,mEAAmE;IACnE,UAAU,CAAC,EAAE,qBAAqB,CAAC;CACpC;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,WAAW,CAAC,CAsEtB"}
1
+ {"version":3,"file":"research.d.ts","sourceRoot":"","sources":["../../src/agents/research.ts"],"names":[],"mappings":"AAIA,OAAO,EAAiB,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AACzE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAgLjE,MAAM,WAAW,YAAY;IAC3B,yEAAyE;IACzE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,mEAAmE;IACnE,UAAU,CAAC,EAAE,qBAAqB,CAAC;CACpC;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,WAAW,CAAC,CAsGtB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAmG7E;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,KAAK,EAAE,eAAe,EAAE,MAAM,GAAG,OAAO,CAiCvF;AAeD;;;;;;;;;;;;GAYG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CA8B/D;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAkBvD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CA6B5D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,CAuDrD"}
@@ -3,16 +3,176 @@ import path from 'path';
3
3
  import { glob } from 'glob';
4
4
  import { parseStory, appendToSection, updateStoryField } from '../core/story.js';
5
5
  import { runAgentQuery } from '../core/client.js';
6
- const RESEARCH_SYSTEM_PROMPT = `You are a technical research specialist. Your job is to research how to implement a user story by analyzing the existing codebase and external best practices.
6
+ import { getLogger } from '../core/logger.js';
7
+ const RESEARCH_SYSTEM_PROMPT = `You are a technical research specialist analyzing how to implement a user story by deeply examining the existing codebase.
7
8
 
8
- When researching a story, you should:
9
- 1. Identify relevant existing code patterns in the codebase
10
- 2. Suggest which files/modules need to be modified
11
- 3. Research external best practices if applicable
12
- 4. Identify potential challenges or risks
13
- 5. Note any dependencies or prerequisites
9
+ ## Research Methodology
14
10
 
15
- Output your research findings in markdown format. Be specific about file paths and code patterns.`;
11
+ **Phase 1: Problem Understanding**
12
+ - Parse the story requirements to extract core needs
13
+ - Identify key terms and concepts that map to codebase elements
14
+ - Clarify the scope: what is being asked vs what is NOT being asked
15
+
16
+ **Phase 2: Codebase Exploration**
17
+ - Use the provided codebase context to locate relevant code
18
+ - Trace dependencies and call chains from entry points
19
+ - Identify architectural boundaries and interfaces
20
+ - Find similar implementations that can serve as templates
21
+
22
+ **Phase 3: Solution Mapping**
23
+ - Map story requirements to specific code locations
24
+ - Identify the change surface area (all affected files)
25
+ - Consider alternative approaches and trade-offs
26
+ - Determine the sequence of changes (what depends on what)
27
+
28
+ ## Required Output Structure
29
+
30
+ Your research MUST include these five sections:
31
+
32
+ ### Problem Summary
33
+ [Restate the problem in your own words to confirm understanding. What is the core goal?]
34
+
35
+ ### Codebase Context
36
+ [Describe relevant architecture, patterns, and existing implementations you found. Reference specific files and patterns.]
37
+
38
+ ### Files Requiring Changes
39
+
40
+ For each file that needs modification:
41
+ - **Path**: \`path/to/file\`
42
+ - **Change Type**: [Create New | Modify Existing | Delete]
43
+ - **Reason**: [Why this file needs to change]
44
+ - **Specific Changes**: [What aspects need modification]
45
+ - **Dependencies**: [What other changes must happen first]
46
+
47
+ ### Testing Strategy
48
+ - **Test Files to Modify**: [Existing test files that need updates]
49
+ - **New Tests Needed**: [New test files or test cases required]
50
+ - **Test Scenarios**: [Specific scenarios to cover: happy path, edge cases, error handling]
51
+
52
+ ### Additional Context
53
+ - **Relevant Patterns**: [Existing code patterns to follow for consistency]
54
+ - **Potential Risks**: [Things to watch out for, breaking changes]
55
+ - **Performance Considerations**: [If applicable]
56
+ - **Security Implications**: [If applicable]
57
+
58
+ ## Quality Standards
59
+ - Be SPECIFIC about file paths (e.g., \`src/core/story.ts:42\` not just "the story module")
60
+ - Reference existing patterns when suggesting new code
61
+ - Identify at least 3-5 relevant files in the codebase
62
+ - Provide concrete examples, not abstract concepts
63
+ - If you cannot find relevant code, say so explicitly`;
64
+ /**
65
+ * Keywords that indicate a story is purely internal and does not require web research.
66
+ * These keywords suggest refactoring, code organization, or internal maintenance tasks.
67
+ */
68
+ const WEB_RESEARCH_INTERNAL_KEYWORDS = [
69
+ 'refactor internal',
70
+ 'move function',
71
+ 'rename variable',
72
+ 'rename function',
73
+ 'move utility',
74
+ 'internal refactor',
75
+ 'move code',
76
+ 'reorganize internal',
77
+ ];
78
+ /**
79
+ * Keywords that indicate external dependencies or integrations requiring web research.
80
+ * These keywords suggest the need for library documentation, API references, or best practices.
81
+ */
82
+ const WEB_RESEARCH_EXTERNAL_KEYWORDS = [
83
+ 'integrate',
84
+ 'api',
85
+ 'library',
86
+ 'framework',
87
+ 'best practices',
88
+ 'npm package',
89
+ 'external',
90
+ 'third-party',
91
+ 'sdk',
92
+ 'documentation',
93
+ 'webhook',
94
+ 'rest api',
95
+ 'graphql',
96
+ 'oauth',
97
+ 'authentication provider',
98
+ ];
99
+ /**
100
+ * Web research prompt template for supplementary research phase.
101
+ * Instructs the agent on tool usage, FAR evaluation, and output formatting.
102
+ */
103
+ const WEB_RESEARCH_PROMPT_TEMPLATE = (storyTitle, storyContent, codebaseContext) => `You are performing supplementary web research for a software development story.
104
+
105
+ **Story Title**: ${storyTitle}
106
+
107
+ **Story Content**:
108
+ ${storyContent}
109
+
110
+ **Codebase Context** (already analyzed):
111
+ ${codebaseContext}... [truncated]
112
+
113
+ ---
114
+
115
+ ## Web Research Instructions
116
+
117
+ You have access to these web research tools:
118
+
119
+ 1. **Context7** (if available) - Use FIRST for library/framework documentation
120
+ - Best for: npm packages, Python libraries, popular frameworks
121
+ - Example: "Search Context7 for React Query documentation on data fetching"
122
+
123
+ 2. **WebSearch** - Use for community knowledge and best practices
124
+ - Best for: Stack Overflow patterns, blog posts, tutorials
125
+ - Example: "Search the web for TypeScript error handling best practices"
126
+
127
+ 3. **WebFetch** - Use to read specific authoritative URLs
128
+ - Best for: Official documentation, specific articles
129
+ - Example: "Fetch https://docs.anthropic.com/claude/reference"
130
+
131
+ ## Research Strategy
132
+
133
+ - Try Context7 FIRST for any npm packages or popular frameworks mentioned in the story
134
+ - Fall back to WebSearch for general solutions and community patterns
135
+ - Use WebFetch only when you have specific authoritative URLs to read
136
+ - Limit research to 3-5 high-quality sources to avoid information overload
137
+ - Focus on actionable information that directly addresses the story requirements
138
+
139
+ ## Output Format
140
+
141
+ For EACH finding, provide:
142
+
143
+ ### [Topic Name]
144
+ **Source**: [Context7 - Library Name] or [Web Search Result] or [URL]
145
+ **FAR Score**: Factuality: [1-5], Actionability: [1-5], Relevance: [1-5]
146
+ **Justification**: [Why these scores? How does this help the story?]
147
+
148
+ [Finding content with code examples, patterns, or instructions]
149
+
150
+ ---
151
+
152
+ ## FAR Scale Definitions
153
+
154
+ **Factuality (1-5)**: How accurate and verifiable is the information?
155
+ - 1: Unverified/speculative
156
+ - 3: Community knowledge (Stack Overflow, blogs)
157
+ - 5: Official documentation or peer-reviewed
158
+
159
+ **Actionability (1-5)**: Can this be directly applied to the task?
160
+ - 1: Abstract concepts only
161
+ - 3: General patterns or approaches
162
+ - 5: Copy-paste code examples or step-by-step instructions
163
+
164
+ **Relevance (1-5)**: How closely does this match the story requirements?
165
+ - 1: Tangentially related
166
+ - 3: Related but not specific to story
167
+ - 5: Directly addresses a story acceptance criterion
168
+
169
+ ## Important Notes
170
+
171
+ - If web tools are unavailable (offline, not configured), simply state: "Web research tools unavailable - skipping web research"
172
+ - If a finding contradicts patterns in the codebase context, note the discrepancy and defer to local patterns
173
+ - Focus on NEW information not already present in codebase analysis
174
+
175
+ Begin web research now. Provide 3-5 high-quality findings with FAR evaluations.`;
16
176
  /**
17
177
  * Research Agent
18
178
  *
@@ -25,6 +185,8 @@ export async function runResearchAgent(storyPath, sdlcRoot, options = {}) {
25
185
  try {
26
186
  // Gather codebase context
27
187
  const codebaseContext = await gatherCodebaseContext(sdlcRoot);
188
+ // Sanitize codebase context before including in prompt (prevent prompt injection)
189
+ const sanitizedContext = sanitizeCodebaseContext(codebaseContext);
28
190
  // Build the prompt, including rework context if this is a refinement iteration
29
191
  let prompt = `Please research how to implement this story:
30
192
 
@@ -34,7 +196,7 @@ Story content:
34
196
  ${story.content}
35
197
 
36
198
  Codebase context:
37
- ${codebaseContext}`;
199
+ ${sanitizedContext}`;
38
200
  if (options.reworkContext) {
39
201
  prompt += `
40
202
 
@@ -62,11 +224,33 @@ Format your response as markdown for the Research section of the story.`;
62
224
  workingDirectory: path.dirname(sdlcRoot),
63
225
  onProgress: options.onProgress,
64
226
  });
65
- // Append research to the story
66
- appendToSection(story, 'Research', researchContent);
67
- changesMade.push('Added research findings');
68
- // Mark research as complete
69
- updateStoryField(story, 'research_complete', true);
227
+ // Sanitize research content before storage (prevent ANSI/markdown injection)
228
+ const sanitizedResearch = sanitizeWebResearchContent(researchContent);
229
+ // Append codebase research to the story
230
+ await appendToSection(story, 'Research', sanitizedResearch);
231
+ changesMade.push('Added codebase research findings');
232
+ // Phase 2: Web Research (conditional)
233
+ if (shouldPerformWebResearch(story, sanitizedContext)) {
234
+ const webResearchContent = await performWebResearch(story, sanitizedContext, path.dirname(sdlcRoot), options.onProgress);
235
+ if (webResearchContent.trim()) {
236
+ // Sanitize web research content before storage (prevent ANSI/markdown injection)
237
+ const sanitizedWebResearch = sanitizeWebResearchContent(webResearchContent);
238
+ // Re-parse story to get updated content after codebase research
239
+ const updatedStory = parseStory(storyPath);
240
+ await appendToSection(updatedStory, 'Research', '\n## Web Research Findings\n\n' + sanitizedWebResearch);
241
+ changesMade.push('Added web research findings');
242
+ }
243
+ else {
244
+ getLogger().info('web-research', 'Web research returned empty - tools may be unavailable');
245
+ changesMade.push('Web research skipped: tools unavailable');
246
+ }
247
+ }
248
+ else {
249
+ changesMade.push('Web research skipped: no external dependencies detected');
250
+ }
251
+ // Mark research as complete - re-parse to get latest content including web research
252
+ const finalStory = parseStory(storyPath);
253
+ await updateStoryField(finalStory, 'research_complete', true);
70
254
  changesMade.push('Marked research_complete: true');
71
255
  return {
72
256
  success: true,
@@ -84,9 +268,19 @@ Format your response as markdown for the Research section of the story.`;
84
268
  }
85
269
  }
86
270
  /**
87
- * Gather context about the codebase for research
271
+ * Gather context about the codebase for research.
272
+ *
273
+ * Collects information about:
274
+ * - Project configuration files (package.json, tsconfig.json, etc.)
275
+ * - Directory structure
276
+ * - Source files
277
+ * - Test files (for understanding testing patterns)
278
+ * - Configuration files (for discovering config patterns)
279
+ *
280
+ * @param sdlcRoot - Path to the .ai-sdlc directory
281
+ * @returns Formatted context string with codebase information
88
282
  */
89
- async function gatherCodebaseContext(sdlcRoot) {
283
+ export async function gatherCodebaseContext(sdlcRoot) {
90
284
  const workingDir = path.dirname(sdlcRoot);
91
285
  const context = [];
92
286
  // Check for common project files
@@ -136,6 +330,302 @@ async function gatherCodebaseContext(sdlcRoot) {
136
330
  catch {
137
331
  // Ignore glob errors
138
332
  }
333
+ // Look for test files (helps identify testing patterns)
334
+ try {
335
+ const testFiles = await glob('**/*.test.{ts,js,tsx,jsx}', {
336
+ cwd: workingDir,
337
+ ignore: ['node_modules/**', 'dist/**', 'build/**'],
338
+ });
339
+ // Also look for tests in dedicated test directories
340
+ const testDirFiles = await glob('{tests,test,__tests__}/**/*.{ts,js,tsx,jsx}', {
341
+ cwd: workingDir,
342
+ ignore: ['node_modules/**'],
343
+ });
344
+ const allTestFiles = [...new Set([...testFiles, ...testDirFiles])];
345
+ if (allTestFiles.length > 0) {
346
+ context.push(`=== Test Files ===\n${allTestFiles.slice(0, 15).join('\n')}`);
347
+ }
348
+ }
349
+ catch {
350
+ // Ignore glob errors
351
+ }
352
+ // Look for configuration files (helps identify config patterns)
353
+ try {
354
+ const configFiles = await glob('**/*.config.{ts,js,json,mjs,cjs}', {
355
+ cwd: workingDir,
356
+ ignore: ['node_modules/**', 'dist/**'],
357
+ });
358
+ // Also look for common config file patterns
359
+ const commonConfigs = await glob('{.eslintrc*,.prettierrc*,jest.config.*,vitest.config.*,vite.config.*}', {
360
+ cwd: workingDir,
361
+ dot: true,
362
+ });
363
+ const allConfigFiles = [...new Set([...configFiles, ...commonConfigs])];
364
+ if (allConfigFiles.length > 0) {
365
+ context.push(`=== Config Files ===\n${allConfigFiles.slice(0, 10).join('\n')}`);
366
+ }
367
+ }
368
+ catch {
369
+ // Ignore glob errors
370
+ }
139
371
  return context.join('\n\n') || 'No codebase context available.';
140
372
  }
373
+ /**
374
+ * Determine if web research would add value based on story content and codebase context.
375
+ *
376
+ * Web research is triggered when:
377
+ * 1. External dependencies are referenced (libraries, APIs, frameworks)
378
+ * 2. Unfamiliar APIs/patterns are mentioned
379
+ * 3. Library-specific documentation is needed
380
+ * 4. Best practices are requested
381
+ *
382
+ * Web research is skipped when:
383
+ * - Topic is purely internal (refactoring, moving code, internal utilities)
384
+ * - No external dependencies mentioned
385
+ */
386
+ export function shouldPerformWebResearch(story, codebaseContext) {
387
+ const content = story.content.toLowerCase();
388
+ const title = story.frontmatter.title.toLowerCase();
389
+ const combinedText = `${title} ${content}`;
390
+ // Skip if purely internal keywords are dominant
391
+ for (const keyword of WEB_RESEARCH_INTERNAL_KEYWORDS) {
392
+ if (combinedText.includes(keyword)) {
393
+ // Sanitize keyword for logging (prevent log injection)
394
+ getLogger().info('web-research', `Skipping web research: purely internal topic detected (${sanitizeForLogging(keyword)})`);
395
+ return false;
396
+ }
397
+ }
398
+ // Trigger if external library/API/framework mentioned
399
+ for (const keyword of WEB_RESEARCH_EXTERNAL_KEYWORDS) {
400
+ if (combinedText.includes(keyword)) {
401
+ // Sanitize keyword for logging (prevent log injection)
402
+ getLogger().info('web-research', `Web research triggered: external keyword detected (${sanitizeForLogging(keyword)})`);
403
+ return true;
404
+ }
405
+ }
406
+ // Check for package.json mentions (suggests npm dependencies)
407
+ if (codebaseContext.includes('package.json') &&
408
+ (combinedText.includes('npm') || combinedText.includes('install') || combinedText.includes('dependency'))) {
409
+ getLogger().info('web-research', 'Web research triggered: npm dependency context detected');
410
+ return true;
411
+ }
412
+ // Default: skip web research for codebase-only topics
413
+ getLogger().info('web-research', 'Skipping web research: no external dependencies detected');
414
+ return false;
415
+ }
416
+ /**
417
+ * Maximum input length to prevent DoS attacks.
418
+ * Set to 10,000 chars to accommodate long research findings
419
+ * while preventing memory exhaustion from malicious inputs.
420
+ */
421
+ const MAX_INPUT_LENGTH = 10000;
422
+ /**
423
+ * Maximum log string length for readability and security.
424
+ * Prevents log injection and maintains log file readability.
425
+ */
426
+ const MAX_LOG_LENGTH = 200;
427
+ /**
428
+ * Sanitize web research content for safe storage and display.
429
+ * Removes ANSI escape sequences, control characters, and potential injection vectors.
430
+ *
431
+ * Security rationale: Web research content comes from external sources (LLM, web tools)
432
+ * and must be sanitized before storage to prevent:
433
+ * - ANSI injection (terminal control sequence attacks)
434
+ * - Markdown injection (malicious formatting)
435
+ * - Control character injection (null bytes, bell characters, etc.)
436
+ *
437
+ * @param text - Raw web research content from external source
438
+ * @returns Sanitized text safe for storage in markdown files
439
+ */
440
+ export function sanitizeWebResearchContent(text) {
441
+ if (!text)
442
+ return '';
443
+ // Enforce maximum length to prevent DoS
444
+ if (text.length > MAX_INPUT_LENGTH) {
445
+ text = text.substring(0, MAX_INPUT_LENGTH);
446
+ }
447
+ // Remove ANSI CSI sequences (colors, cursor movement) - e.g., \x1B[31m
448
+ text = text.replace(/\x1B\[[^a-zA-Z\x1B]*[a-zA-Z]?/g, '');
449
+ // Remove OSC sequences (hyperlinks, window titles) - terminated by BEL (\x07) or ST (\x1B\\)
450
+ text = text.replace(/\x1B\][^\x07]*\x07/g, '');
451
+ text = text.replace(/\x1B\][^\x1B]*\x1B\\/g, '');
452
+ // Remove any remaining standalone escape characters
453
+ text = text.replace(/\x1B/g, '');
454
+ // Remove control characters (0x00-0x08, 0x0B-0x0C, 0x0E-0x1F, 0x7F-0x9F)
455
+ // eslint-disable-next-line no-control-regex
456
+ text = text.replace(/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F-\x9F]/g, '');
457
+ // Normalize Unicode to prevent homograph attacks and ensure consistent representation
458
+ text = text.normalize('NFC');
459
+ // Validate markdown structure - escape dangerous patterns
460
+ // Triple backticks could be used to break out of code blocks
461
+ text = text.replace(/```/g, '\\`\\`\\`');
462
+ return text;
463
+ }
464
+ /**
465
+ * Sanitize text for logging to prevent log injection attacks.
466
+ * Replaces newlines with spaces and truncates for readability.
467
+ *
468
+ * Security rationale: Log injection attacks use newlines to inject fake log entries.
469
+ * By replacing newlines with spaces, we ensure each log() call produces exactly one log line.
470
+ *
471
+ * @param text - Raw text that will be logged
472
+ * @returns Sanitized text safe for logging (single line, truncated)
473
+ */
474
+ export function sanitizeForLogging(text) {
475
+ if (!text)
476
+ return '';
477
+ // Remove ANSI escape sequences
478
+ text = text.replace(/\x1B\[[^a-zA-Z\x1B]*[a-zA-Z]?/g, '');
479
+ text = text.replace(/\x1B\][^\x07]*\x07/g, '');
480
+ text = text.replace(/\x1B\][^\x1B]*\x1B\\/g, '');
481
+ text = text.replace(/\x1B/g, '');
482
+ // Replace newlines and carriage returns with spaces to prevent log injection
483
+ text = text.replace(/[\n\r]/g, ' ');
484
+ // Truncate to reasonable length for logs
485
+ if (text.length > MAX_LOG_LENGTH) {
486
+ text = text.substring(0, MAX_LOG_LENGTH) + '...';
487
+ }
488
+ return text.trim();
489
+ }
490
+ /**
491
+ * Sanitize codebase context before including in LLM prompts.
492
+ * Escapes dangerous patterns that could cause prompt injection.
493
+ *
494
+ * Security rationale: Codebase files may contain malicious content from:
495
+ * - Compromised dependencies
496
+ * - Malicious commits
497
+ * - Untrusted contributors
498
+ *
499
+ * We must prevent prompt injection by escaping patterns that could:
500
+ * - Terminate the prompt early (triple backticks)
501
+ * - Inject commands or instructions
502
+ * - Confuse the LLM's understanding of structure
503
+ *
504
+ * @param text - Raw codebase context
505
+ * @returns Sanitized text safe for LLM prompts
506
+ */
507
+ export function sanitizeCodebaseContext(text) {
508
+ if (!text)
509
+ return '';
510
+ // Remove ANSI escape sequences
511
+ text = text.replace(/\x1B\[[^a-zA-Z\x1B]*[a-zA-Z]?/g, '');
512
+ text = text.replace(/\x1B\][^\x07]*\x07/g, '');
513
+ text = text.replace(/\x1B\][^\x1B]*\x1B\\/g, '');
514
+ text = text.replace(/\x1B/g, '');
515
+ // Escape triple backticks to prevent breaking out of code blocks
516
+ text = text.replace(/```/g, '\\`\\`\\`');
517
+ // Validate UTF-8 boundaries at truncation points
518
+ // If we need to truncate, ensure we don't split multi-byte characters
519
+ if (text.length > MAX_INPUT_LENGTH) {
520
+ // Use substring which is UTF-16 safe, then validate
521
+ let truncated = text.substring(0, MAX_INPUT_LENGTH);
522
+ // Check if we split a surrogate pair (0xD800-0xDFFF)
523
+ const lastCharCode = truncated.charCodeAt(truncated.length - 1);
524
+ if (lastCharCode >= 0xD800 && lastCharCode <= 0xDFFF) {
525
+ // We split a surrogate pair, remove the incomplete character
526
+ truncated = truncated.substring(0, truncated.length - 1);
527
+ }
528
+ text = truncated;
529
+ }
530
+ return text;
531
+ }
532
+ /**
533
+ * Parse FAR evaluation from web research finding text.
534
+ * Expected format from LLM:
535
+ * **FAR Score**: Factuality: 5, Actionability: 4, Relevance: 5
536
+ * **Justification**: Official documentation provides...
537
+ *
538
+ * Returns default scores (2, 2, 2) with parsingSucceeded: false if parsing fails.
539
+ * Default of 2 (rather than 3) indicates uncertainty rather than average quality.
540
+ *
541
+ * @param finding - Web research finding text to parse
542
+ * @returns FARScore with parsed or default values and parsing status
543
+ */
544
+ export function evaluateFAR(finding) {
545
+ // Enforce maximum length to prevent ReDoS attacks
546
+ if (finding.length > MAX_INPUT_LENGTH) {
547
+ finding = finding.substring(0, MAX_INPUT_LENGTH);
548
+ }
549
+ try {
550
+ // Look for FAR score pattern
551
+ const scoreMatch = finding.match(/\*\*FAR Score\*\*:.*?Factuality:\s*(\d+).*?Actionability:\s*(\d+).*?Relevance:\s*(\d+)/i);
552
+ const justificationMatch = finding.match(/\*\*Justification\*\*:\s*(.+?)(?:\n\n|\n#|$)/is);
553
+ if (scoreMatch && justificationMatch) {
554
+ const factuality = parseInt(scoreMatch[1], 10);
555
+ const actionability = parseInt(scoreMatch[2], 10);
556
+ const relevance = parseInt(scoreMatch[3], 10);
557
+ const justification = justificationMatch[1].trim();
558
+ // Validate scores are in range 1-5
559
+ if ([factuality, actionability, relevance].every(s => s >= 1 && s <= 5)) {
560
+ return {
561
+ factuality,
562
+ actionability,
563
+ relevance,
564
+ justification,
565
+ parsingSucceeded: true,
566
+ };
567
+ }
568
+ else {
569
+ getLogger().warn('web-research', 'FAR scores out of valid range (1-5), using defaults');
570
+ }
571
+ }
572
+ else if (scoreMatch && !justificationMatch) {
573
+ getLogger().warn('web-research', 'FAR justification missing, using defaults');
574
+ }
575
+ else if (!scoreMatch && justificationMatch) {
576
+ getLogger().warn('web-research', 'FAR scores not found in finding, using defaults');
577
+ }
578
+ else {
579
+ getLogger().warn('web-research', 'FAR scores and justification not found in finding, using defaults');
580
+ }
581
+ // If parsing failed, return default scores (2/5 indicates uncertainty)
582
+ return {
583
+ factuality: 2,
584
+ actionability: 2,
585
+ relevance: 2,
586
+ justification: 'FAR scores could not be parsed from finding. Default scores (2/5) applied.',
587
+ parsingSucceeded: false,
588
+ };
589
+ }
590
+ catch (error) {
591
+ getLogger().error('web-research', 'Error parsing FAR scores', { error });
592
+ return {
593
+ factuality: 2,
594
+ actionability: 2,
595
+ relevance: 2,
596
+ justification: 'Error parsing FAR evaluation',
597
+ parsingSucceeded: false,
598
+ };
599
+ }
600
+ }
601
+ /**
602
+ * Perform web research using Context7/WebSearch/WebFetch.
603
+ * Returns formatted markdown with FAR evaluations, or empty string if all tools unavailable.
604
+ */
605
+ async function performWebResearch(story, codebaseContext, workingDir, onProgress) {
606
+ const logger = getLogger();
607
+ logger.info('web-research', 'Starting web research phase', { storyId: story.frontmatter.id });
608
+ try {
609
+ const sanitizedContext = sanitizeCodebaseContext(codebaseContext.substring(0, 2000));
610
+ const webResearchPrompt = WEB_RESEARCH_PROMPT_TEMPLATE(story.frontmatter.title, story.content, sanitizedContext);
611
+ const webResearchResult = await runAgentQuery({
612
+ prompt: webResearchPrompt,
613
+ systemPrompt: 'You are a web research specialist. Use available tools to find authoritative documentation and best practices.',
614
+ workingDirectory: workingDir,
615
+ onProgress,
616
+ });
617
+ // Check if web tools were unavailable
618
+ if (webResearchResult.toLowerCase().includes('web research tools unavailable')) {
619
+ logger.info('web-research', 'Web research tools unavailable, skipping');
620
+ return '';
621
+ }
622
+ logger.info('web-research', 'Web research completed successfully');
623
+ return webResearchResult;
624
+ }
625
+ catch (error) {
626
+ logger.error('web-research', 'Web research failed', { error });
627
+ // Gracefully degrade - return empty string to continue with codebase-only research
628
+ return '';
629
+ }
630
+ }
141
631
  //# sourceMappingURL=research.js.map