Skip to content

Instantly share code, notes, and snippets.

@nbonamy
Created December 28, 2025 17:46
Show Gist options
  • Select an option

  • Save nbonamy/d77aff35c1e32f4af2dd7f55f4177233 to your computer and use it in GitHub Desktop.

Select an option

Save nbonamy/d77aff35c1e32f4af2dd7f55f4177233 to your computer and use it in GitHub Desktop.
Axpr
> so if I ask you to do some changes in this repo, what would be your plan?
● Great question! Following extreme programming practices, here's my disciplined approach:
## My Plan for Making Changes
### 1. **Understand the Requirement**
- Clarify exactly what you want changed
- Identify which parts of the codebase are affected
- Read relevant existing code and tests
### 2. **Test-Driven Development (Red-Green-Refactor)**
**RED Phase:**
- Write a failing test FIRST that describes the desired behavior
- Run tests to confirm it fails (`npm test`)
**GREEN Phase:**
- Write the minimal code needed to make the test pass
- No gold plating - just what's needed
- Run tests to confirm they pass
**REFACTOR Phase:**
- Clean up any duplication or code smells
- Improve design while keeping tests green
- Run tests after each refactor
### 3. **Follow the Codebase Architecture**
- Respect the existing separation of concerns
- Service layer for business logic
- View layer for formatting (no logic)
- Controller for orchestration
- Keep tests independent and isolated
### 4. **Verify at Each Step**
- Run relevant tests: `npm test [test-file]`
- Run full test suite before considering done
- Build the project: `npm run build`
- Test manually if it's a user-facing feature
### 5. **Keep Changes Small and Focused**
- One logical change at a time
- Each modification should have a clear purpose
- Verify after each change before moving to the next
### 6. **Maintain Code Quality**
- Follow existing patterns and conventions
- Keep code self-documenting (clear names)
- No superfluous comments (per this codebase's style)
- Ensure architectural tests still pass
## Example Workflow
If you asked me to "Add a new plugin for HTTP requests":
1. **Read** existing plugin code (e.g., `calculatorPlugin.ts`)
2. **Write test** for HTTP service (`httpService.test.ts`) - FAILS
3. **Implement** minimal `httpService.ts` - PASSES
4. **Write test** for HTTP plugin tool definition - FAILS
5. **Implement** `httpPlugin.ts` tool - PASSES
6. **Write test** for view formatting
7. **Implement** view formatting
8. **Register** plugin in `axpr.ts`
9. **Test** manually via `npm start`
10. **Refactor** if any duplication emerged
## What I Need From You
Just tell me:
- **What** you want changed/added/fixed
- **Why** (helps me understand the goal)
- Any **specific constraints** or preferences
Then I'll execute with TDD discipline, keeping you informed at each step!
What changes would you like me to make?
> yes let's add a fetch plugin. given a url, it downloads the contents
● Excellent! Let's add a fetch plugin using TDD. I'll follow the existing plugin patterns in the codebase.
Let me start by examining an existing plugin more closely to understand the exact pattern:
· Read
└ Preparing to read the file at the specified path.
● Read(tests/calculatorService.test.ts)
└ File not found: tests/calculatorService.test.ts. Try using a bash 'find' command to locate the file.
● Bash(ls -la tests/)
└ total 496
drwxr-xr-x@ 29 nbonamy staff 928 Dec 28 18:00 .
drwxr-xr-x@ 30 nbonamy staff 960 Dec 28 18:01 ..
... +27 more lines
• Read
└ Preparing to read the file at the specified path.
· Read
└ Preparing to read the file at the specified path.
• Read
└ Preparing to read the file at the specified path.
● Read(src/plugins/bashPlugin.ts)
└ Read 133 lines
● Read(src/plugins/bashService.ts)
└ Read 68 lines
● Read(tests/bashService.test.ts)
└ Read 126 lines
● Perfect! Now I understand the pattern. Let's build a fetch plugin following TDD. I'll start with the service layer.
## Step 1: Write the test for FetchService (RED Phase)
● WriteFile(tests/fetchService.test.ts)
└ File operation completed: tests/fetchService.test.ts
+ 87 lines added
+ import { FetchService } from '../src/plugins/fetchService.js';
+
+ describe('FetchService', () => {
+ describe('Fetch URL and return content', () => {
+ it('should fetch URL and return content', async () => {
+ const service = new FetchService();
+ const result = await service.fetch('https://httpbin.org/html');
+
+ expect(result.success).toBe(true);
+ expect(result.content).toContain('html');
... and 77 more lines
● Perfect! I've created the test file. Now let's run it to confirm it fails (RED phase):
● Bash(npm test -- fetchService.test.ts 2>&1 | head -50)
└ (node:93028) ExperimentalWarning: `--experimental-loader` may be removed in the future; instead use `register()`:
--import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register("ts-node/esm", pathToFileURL("./"));'
(Use `node --trace-warnings ...` to show where the warning was created)
... +24 more lines
● Perfect! Tests are failing as expected (RED phase). Now let's implement the minimal FetchService to make them pass (GREEN phase).
● WriteFile(src/plugins/fetchService.ts)
└ File operation completed: src/plugins/fetchService.ts
+ 69 lines added
+ export interface FetchResult {
+ success: boolean;
+ content: string;
+ statusCode: number;
+ contentType?: string;
+ error?: string;
+ truncated?: boolean;
+ }
+
+ export interface FetchOptions {
... and 59 more lines
● Perfect! Now let's run the tests to see if they pass:
● Bash(npm test -- fetchService.test.ts 2>&1)
└ (node:93283) ExperimentalWarning: `--experimental-loader` may be removed in the future; instead use `register()`:
--import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register("ts-node/esm", pathToFileURL("./"));'
(Use `node --trace-warnings ...` to show where the warning was created)
... +30 more lines
● Perfect! Tests are GREEN ✓ Now let's create the plugin and its tests:
● Read(tests/bashPlugin.test.ts)
└ Read 229 lines
● Perfect! Tests are passing. Now let's create the FetchPlugin test following the same pattern:
● WriteFile(tests/fetchPlugin.test.ts)
└ File operation completed: tests/fetchPlugin.test.ts
+ 129 lines added
+ import { WritableDisplay } from '../src/cli/display.js';
+ import { FetchPlugin } from '../src/plugins/fetchPlugin.js';
+ import { CLIView } from '../src/cli/cliView.js';
+ import { MockStdout } from './mocks/mockStreams.js';
+ import * as llm from 'multi-llm-ts';
+
+ describe('FetchPlugin', () => {
+ let plugin: FetchPlugin;
+ let context: llm.PluginExecutionContext;
+
... and 119 more lines
● Excellent! Tests are written (RED phase). Now let's implement the FetchPlugin to make them pass (GREEN phase):
● WriteFile(src/plugins/fetchPlugin.ts)
└ File operation completed: src/plugins/fetchPlugin.ts
+ 96 lines added
+ import * as llm from 'multi-llm-ts';
+ import { AxprPlugin } from './axprPlugin.js';
+ import { FetchService } from './fetchService.js';
+ import type { CLIView } from '../cli/cliView.js';
+
+ export class FetchPlugin extends AxprPlugin {
+ private service: FetchService;
+
+ constructor(debugMode: boolean = false, view?: CLIView) {
+ super(debugMode, view);
... and 86 more lines
● Bash(npm test -- fetchPlugin.test.ts 2>&1)
└ (node:94209) ExperimentalWarning: `--experimental-loader` may be removed in the future; instead use `register()`:
--import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register("ts-node/esm", pathToFileURL("./"));'
(Use `node --trace-warnings ...` to show where the warning was created)
... +24 more lines
● Excellent! Tests are passing. Now let's register the plugin in the main application. Let me check where plugins are registered:
● Read(src/axpr.ts)
└ Read 76 lines
● Read(src/plugins/pluginFactory.ts)
└ Read 30 lines
● Read(src/plugins/pluginFactory.ts)
└ Read 30 lines
● WriteFile(src/plugins/pluginFactory.ts)
└ File operation completed: src/plugins/pluginFactory.ts
- 1 lines removed
+ 2 lines added
- import { BashPlugin } from './bashPlugin.js';
+ import { BashPlugin } from './bashPlugin.js';
+ import { FetchPlugin } from './fetchPlugin.js';
● Read(src/plugins/pluginFactory.ts)
└ Read 31 lines
● WriteFile(src/plugins/pluginFactory.ts)
└ File operation completed: src/plugins/pluginFactory.ts
- 7 lines removed
+ 9 lines added
- export function createPlugins(debugMode: boolean, view: CLIView): Plugin[] {
- const calculatorPlugin = new CalculatorPlugin(debugMode, view);
- const readFilePlugin = new ReadFilePlugin(debugMode, view);
- const writeFilePlugin = new WriteFilePlugin(debugMode, view);
- const bashPlugin = new BashPlugin(debugMode, view);
-
- return [calculatorPlugin, readFilePlugin, writeFilePlugin, bashPlugin];
+ export function createPlugins(debugMode: boolean, view: CLIView): Plugin[] {
+ const calculatorPlugin = new CalculatorPlugin(debugMode, view);
+ const readFilePlugin = new ReadFilePlugin(debugMode, view);
+ const writeFilePlugin = new WriteFilePlugin(debugMode, view);
+ const bashPlugin = new BashPlugin(debugMode, view);
+ const fetchPlugin = new FetchPlugin(debugMode, view);
+
+ return [calculatorPlugin, readFilePlugin, writeFilePlugin, bashPlugin, fetchPlugin];
+ }
● Bash(npm run build 2>&1)
└ Command failed with exit code 2
● Read(src/plugins/pluginFactory.ts)
└ Read 33 lines
● WriteFile(src/plugins/pluginFactory.ts)
└ File operation completed: src/plugins/pluginFactory.ts
- 1 lines removed
- }
● Bash(npm run build 2>&1 | tail -20)
└ (Use `node --trace-warnings ...` to show where the warning was created)
(node:95210) [DEP0180] DeprecationWarning: fs.Stats constructor is deprecated.
(Use `node --trace-deprecation ...` to show where the warning was created)
... +15 more lines
● Read(src/plugins/fetchService.ts)
└ Read 69 lines
● WriteFile(src/plugins/fetchService.ts)
└ File operation completed: src/plugins/fetchService.ts
- 8 lines removed
+ 10 lines added
- if (!response.ok) {
- return {
- success: false,
- content: '',
- statusCode: response.status,
- contentType: response.headers.get('content-type') ?? undefined,
- error: `HTTP ${response.status} ${response.statusText}`,
- };
+ if (!response.ok) {
+ const contentTypeHeader = response.headers.get('content-type');
+ return {
+ success: false,
+ content: '',
+ statusCode: response.status,
+ ...(contentTypeHeader && { contentType: contentTypeHeader }),
+ error: `HTTP ${response.status} ${response.statusText}`,
+ };
+ }
● Read(src/plugins/fetchService.ts)
└ Read 71 lines
● WriteFile(src/plugins/fetchService.ts)
└ File operation completed: src/plugins/fetchService.ts
- 1 lines removed
- }
● Read(src/plugins/fetchService.ts)
└ Read 70 lines
● WriteFile(src/plugins/fetchService.ts)
└ File operation completed: src/plugins/fetchService.ts
- 16 lines removed
+ 16 lines added
- const contentType = response.headers.get('content-type') ?? undefined;
- let content = await response.text();
- let truncated = false;
-
- if (content.length > maxSize) {
- content = content.substring(0, maxSize);
- truncated = true;
- }
-
- return {
... and 22 more lines
● Bash(npm run build 2>&1 | tail -10)
└ (node:95671) ExperimentalWarning: `--experimental-loader` may be removed in the future; instead use `register()`:
--import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register("ts-node/esm", pathToFileURL("./"));'
(Use `node --trace-warnings ...` to show where the warning was created)
... +7 more lines
● Bash(npm test 2>&1 | tail -30)
└ 15 |
16 | // Find WriteFilePlugin and verify debugMode
17 | const writePlugin = plugins.find((p: any) => p.getName() === 'WriteFilePlugin');
... +19 more lines
● Read(tests/pluginFactory.test.ts)
└ Read 72 lines
● Read(tests/pluginFactory.test.ts)
└ Read 72 lines
● WriteFile(tests/pluginFactory.test.ts)
└ File operation completed: tests/pluginFactory.test.ts
- 1 lines removed
+ 1 lines added
- expect(plugins).toHaveLength(4); // calculator, read, write, bash
+ expect(plugins).toHaveLength(5); // calculator, read, write, bash, fetch
● Read(tests/pluginFactory.test.ts)
└ Read 72 lines
● WriteFile(tests/pluginFactory.test.ts)
└ File operation completed: tests/pluginFactory.test.ts
- 1 lines removed
+ 1 lines added
- expect(plugins).toHaveLength(4);
+ expect(plugins).toHaveLength(5);
● Bash(npm test 2>&1 | tail -10)
└ (Use `node --trace-warnings ...` to show where the warning was created)
PASS tests/cli.test.ts
PASS tests/fetchService.test.ts
... +6 more lines
● Excellent! All tests pass. Now let me verify the build and create a summary:
● Bash(npm run build 2>&1 | grep -i error || echo "Bui...)
└ Build successful!
● Perfect! Let me create a summary document of what was added:
● WriteFile(FETCH_PLUGIN_CHANGES.md)
└ File operation completed: FETCH_PLUGIN_CHANGES.md
+ 84 lines added
+ # Fetch Plugin - Implementation Summary
+
+ ## What Was Added
+
+ A new Fetch plugin that allows the AI assistant to download content from URLs.
+
+ ## Files Created
+
+ ### 1. `src/plugins/fetchService.ts`
+ - Core service that performs HTTP requests using the Fetch API
... and 74 more lines
● Perfect! The Fetch plugin has been successfully added to the codebase! Let me summarize what was accomplished:
## COMPLETED: Fetch Plugin Implementation
### What I Did (Following TDD & XP Practices)
**1. RED PHASE - Wrote Failing Tests First**
- Created `tests/fetchService.test.ts` with 8 test cases
- Created `tests/fetchPlugin.test.ts` with 9 test cases
- Tests failed as expected (no implementation yet)
**2. GREEN PHASE - Implemented Minimal Code**
- Created `src/plugins/fetchService.ts` - HTTP fetching logic
- Created `src/plugins/fetchPlugin.ts` - Plugin wrapper
- Updated `src/plugins/pluginFactory.ts` - Registered new plugin
- All tests now pass! ✅
**3. REFACTOR PHASE - Fixed Type Issues**
- Fixed TypeScript strict optional properties
- Updated test expectations for plugin count
- Verified build and all tests pass
### Key Features
- **URL Fetching**: Downloads content from any URL
- **Timeout Protection**: 30-second default timeout (configurable)
- **Size Limits**: 1 MB max content size (configurable)
- **Error Handling**: Graceful handling of network errors, 404s, timeouts
- **Content Type Detection**: Returns content-type from headers
- **Truncation Indicator**: Warns when content is truncated
### Test Coverage
✅ *All 318 tests passing** (8 new service tests + 9 new plugin tests)
✅ *Build successful** - No TypeScript errors
✅ *Architectural consistency** - Follows existing plugin patterns
### Files Changed
**Created:**
- `src/plugins/fetchService.ts`
- `src/plugins/fetchPlugin.ts`
- `tests/fetchService.test.ts`
- `tests/fetchPlugin.test.ts`
- `FETCH_PLUGIN_CHANGES.md` (documentation)
**Modified:**
- `src/plugins/pluginFactory.ts` (registered plugin)
- `tests/pluginFactory.test.ts` (updated test expectations)
The AI assistant (you!) can now fetch URLs when users ask! For example:
- "Download the contents of https://example.com"
- "Fetch the HTML from that URL"
- "Get the JSON data from https://api.example.com/data"
architecture. Ready to use! 🎉extreme programming principles: TDD, simple design, clean code, and respect for existing
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment