@kjerneverk/agentic 1.0.5-dev.0
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.
- package/.kodrdriv-test-cache.json +6 -0
- package/LICENSE +18 -0
- package/README.md +167 -0
- package/SECURITY.md +94 -0
- package/dist/index.d.ts +770 -0
- package/dist/index.js +1822 -0
- package/dist/index.js.map +1 -0
- package/output/kodrdriv/260110-1207-commit-message.md +1 -0
- package/output/kodrdriv/260110-1208-commit-message.md +1 -0
- package/output/kodrdriv/260110-1210-release-notes.md +27 -0
- package/output/kodrdriv/260110-1232-commit-message.md +1 -0
- package/output/kodrdriv/260110-1234-release-notes.md +46 -0
- package/output/kodrdriv/260110-1308-release-notes.md +20 -0
- package/output/kodrdriv/260112-2155-commit-message.md +1 -0
- package/output/kodrdriv/260112-2156-release-notes.md +33 -0
- package/output/kodrdriv/260112-2340-commit-message.md +1 -0
- package/output/kodrdriv/260113-0017-commit-message.md +1 -0
- package/output/kodrdriv/260113-0017-release-notes.md +53 -0
- package/output/kodrdriv/260115-0616-commit-message.md +4 -0
- package/output/kodrdriv/260115-0739-commit-message.md +1 -0
- package/output/kodrdriv/260115-0746-commit-message.md +1 -0
- package/output/kodrdriv/260115-0747-release-notes.md +91 -0
- package/output/kodrdriv/260126-0851-commit-message.md +1 -0
- package/output/kodrdriv/260128-0900-commit-message.md +1 -0
- package/output/kodrdriv/260128-0900-release-notes.md +29 -0
- package/output/kodrdriv/260128-0905-commit-message.md +1 -0
- package/output/kodrdriv/260128-0913-commit-message.md +1 -0
- package/output/kodrdriv/260128-0913-release-notes.md +23 -0
- package/output/kodrdriv/260128-0919-commit-message.md +1 -0
- package/output/kodrdriv/260128-0919-release-notes.md +21 -0
- package/output/kodrdriv/260130-1642-commit-message.md +1 -0
- package/output/kodrdriv/260130-1642-release-notes.md +29 -0
- package/output/kodrdriv/RELEASE_NOTES.md +27 -0
- package/output/kodrdriv/RELEASE_TITLE.md +1 -0
- package/output/kodrdriv/agentic-reflection-commit-2026-01-10T19-18-11-913Z.md +187 -0
- package/output/kodrdriv/agentic-reflection-commit-2026-01-10T20-03-27-409Z.md +121 -0
- package/output/kodrdriv/agentic-reflection-commit-2026-01-10T20-07-27-813Z.md +114 -0
- package/output/kodrdriv/agentic-reflection-commit-2026-01-10T20-08-06-279Z.md +97 -0
- package/output/kodrdriv/agentic-reflection-commit-2026-01-10T20-32-25-791Z.md +114 -0
- package/output/kodrdriv/agentic-reflection-commit-2026-01-13T05-55-30-733Z.md +97 -0
- package/output/kodrdriv/agentic-reflection-commit-2026-01-13T07-40-28-878Z.md +97 -0
- package/output/kodrdriv/agentic-reflection-commit-2026-01-13T08-17-22-230Z.md +114 -0
- package/output/kodrdriv/agentic-reflection-commit-2026-01-14T14-55-10-122Z.md +177 -0
- package/output/kodrdriv/agentic-reflection-commit-2026-01-15T14-16-05-605Z.md +100 -0
- package/output/kodrdriv/agentic-reflection-commit-2026-01-15T15-39-32-200Z.md +97 -0
- package/output/kodrdriv/agentic-reflection-commit-2026-01-15T15-46-32-840Z.md +114 -0
- package/output/kodrdriv/agentic-reflection-commit-2026-01-26T16-51-00-446Z.md +152 -0
- package/output/kodrdriv/agentic-reflection-commit-2026-01-31T00-38-59-868Z.md +236 -0
- package/output/kodrdriv/agentic-reflection-commit-2026-01-31T00-42-31-993Z.md +152 -0
- package/output/kodrdriv/agentic-reflection-release-2026-01-10T20-10-49-531Z.md +329 -0
- package/output/kodrdriv/agentic-reflection-release-2026-01-10T20-34-32-038Z.md +319 -0
- package/output/kodrdriv/agentic-reflection-release-2026-01-10T21-08-36-314Z.md +168 -0
- package/output/kodrdriv/agentic-reflection-release-2026-01-13T05-56-04-802Z.md +264 -0
- package/output/kodrdriv/agentic-reflection-release-2026-01-13T08-17-56-556Z.md +255 -0
- package/output/kodrdriv/agentic-reflection-release-2026-01-15T15-47-32-509Z.md +460 -0
- package/output/kodrdriv/agentic-reflection-release-2026-01-31T00-42-59-683Z.md +400 -0
- package/package.json +59 -0
- package/package.json~ +58 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Version 1.0.1
|
|
2
|
+
|
|
3
|
+
### Overview
|
|
4
|
+
Version 1.0.1 contains important updates primarily focused on dependency management and configuration improvements. This release resolves issues, enhances existing functionality, and ensures compatibility with newer dependencies.
|
|
5
|
+
|
|
6
|
+
### What's New
|
|
7
|
+
- **Dependency Updates**: The `package.json` has been revised to include updated versions of essential libraries:
|
|
8
|
+
- The `@fjell/logging` package version has been updated to `^4.4.68`.
|
|
9
|
+
- The `zod` package version has been updated to `^4.3.5`.
|
|
10
|
+
- **Development Tools**: Enhancements made in the `devDependencies` section provide tools for better testing, linting, and coverage reporting.
|
|
11
|
+
- **Configuration of Build Scripts**: Refinements in build scripts ensure a smoother workflow during development.
|
|
12
|
+
|
|
13
|
+
### Problem Solved
|
|
14
|
+
This release addresses potential compatibility issues stemming from outdated dependencies. By upgrading these libraries, it enhances the overall stability and functionality of the software.
|
|
15
|
+
|
|
16
|
+
### Impact on Users and Developers
|
|
17
|
+
- **Users**: End-users should notice improved performance and reliability due to the updates in underlying libraries.
|
|
18
|
+
- **Developers**: Developers will benefit from enhanced build and testing processes, streamlining development tasks including linting and testing.
|
|
19
|
+
|
|
20
|
+
### Breaking Changes
|
|
21
|
+
No breaking changes have been detected in this release. However, users are encouraged to review the updates in the dependency versions to ensure compatibility with their usage.
|
|
22
|
+
|
|
23
|
+
### File Changes Overview
|
|
24
|
+
1. **Updated files**:
|
|
25
|
+
- **`package.json`**: 5 insertions and 3 deletions reflecting dependency updates, build script adjustments, and metadata changes.
|
|
26
|
+
- **`.gitignore`**: Minor updates to ensure untracked files remain free from unwanted commits.
|
|
27
|
+
|
|
28
|
+
### Conclusion
|
|
29
|
+
Version 1.0.1 is an essential step forward in maintaining the health of the project through dependency management. Users and developers should upgrade to ensure they benefit from the latest fixes and improvements.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
No changes to commit; the specified file does not exist in the current working tree or may have been deleted.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
No changes to commit; file not found or deleted.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Version 1.0.2
|
|
2
|
+
|
|
3
|
+
### Overview
|
|
4
|
+
Version 1.0.2 introduces important changes to the `package.json` file, primarily focused on resolving dependency conflicts. This release addresses a specific issue with the `ajv` library, ensuring compatibility and stability for users.
|
|
5
|
+
|
|
6
|
+
### Key Changes
|
|
7
|
+
- **Dependency Resolution**: This update specifically resolves a dependency conflict involving the `ajv` library by adding an explicit override to ensure compatibility with peer dependencies.
|
|
8
|
+
- **File Modifications**: The `package.json` file was modified, resulting in:
|
|
9
|
+
- **2 insertions**: New entries for dependency overrides.
|
|
10
|
+
- **6 deletions**: Cleanup of unnecessary or conflicting entries.
|
|
11
|
+
|
|
12
|
+
### Impact on Users and Developers
|
|
13
|
+
- **Users**: This release is critical for those utilizing the `ajv` library as part of their workflow. Ensuring dependency resolution will lead to a smoother integration and less likelihood of encountering runtime errors related to this library.
|
|
14
|
+
- **Developers**: Developers need to be aware of the updates to the `package.json` file. It will enhance the development experience by reducing issues related to dependency management and ensuring that packages are up to date.
|
|
15
|
+
|
|
16
|
+
### Breaking Changes
|
|
17
|
+
- There are currently no identified breaking changes in this release. However, it is advisable to conduct a manual review of any features that interact with the updated dependencies to confirm compatibility.
|
|
18
|
+
|
|
19
|
+
### Recommendations
|
|
20
|
+
- Users should update to version 1.0.2 to benefit from these dependency resolutions.
|
|
21
|
+
- Review changes in the `package.json` file to understand the new state of dependencies and ensure that your applications leverage the updates effectively.
|
|
22
|
+
|
|
23
|
+
This release underscores a commitment to maintaining a stable product while responding to user needs for dependency management.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
fix: ensure all changes are logically grouped for improved clarity and maintainability.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Version 1.0.2
|
|
2
|
+
|
|
3
|
+
### Overview
|
|
4
|
+
This release (1.0.2) introduces several improvements to the npm publish workflow, aiming to enhance package publishing automation and address minor issues that affect the release process.
|
|
5
|
+
|
|
6
|
+
### Key Changes
|
|
7
|
+
- **NPM Publish Workflow Update**: The GitHub Actions workflow file `.github/workflows/npm-publish.yml` has been modified to streamline the npm publishing process. The changes include:
|
|
8
|
+
- Introduced a condition to determine the npm tag based on the package version. Packages with a pre-release version (containing a dash `-`) will be tagged as `dev`, while stable releases will use the `latest` tag.
|
|
9
|
+
- Added steps to ensure the latest npm version is installed and operational.
|
|
10
|
+
|
|
11
|
+
### Problem Resolution
|
|
12
|
+
This release addresses a dependency conflict during the npm publishing process. By explicitly managing package versions and utilizing a workflow, it minimizes the likelihood of manual errors and inconsistencies that could affect the packaging pipeline.
|
|
13
|
+
|
|
14
|
+
### Impact on Users and Developers
|
|
15
|
+
- The improved workflow simplifies the development process, particularly for those contributing to npm package management. Developers can expect a more reliable and consistent publishing experience, reducing the odds of publishing errors.
|
|
16
|
+
- As no breaking changes have been introduced, existing functionality remains unchanged, ensuring a smooth transition to this version.
|
|
17
|
+
|
|
18
|
+
### Breaking Changes
|
|
19
|
+
No breaking changes have been detected in this release. Manual review of API changes is recommended, but no immediate disruption is expected for users or current integrations.
|
|
20
|
+
|
|
21
|
+
This version enhances the automation of the npm publish process and helps ensure that packages are published correctly with appropriate tags based on their versioning status.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
chore(package): bump version to 1.0.4
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# 1.0.4 — Silent-by-default logging and package metadata updates
|
|
2
|
+
|
|
3
|
+
## Main changes
|
|
4
|
+
|
|
5
|
+
### Logging: `DEFAULT_LOGGER` is now silent unless explicitly enabled
|
|
6
|
+
The library’s default logger has been changed to **discard all output by default** to reduce the risk of accidentally logging sensitive data.
|
|
7
|
+
|
|
8
|
+
Logging is now only enabled when one of the following is true:
|
|
9
|
+
- `AGENTIC_LOGGING=true`
|
|
10
|
+
- `DEBUG` includes `agentic` (for example: `DEBUG=*agentic*`)
|
|
11
|
+
- `NODE_ENV=development`
|
|
12
|
+
|
|
13
|
+
**Why this matters**
|
|
14
|
+
- **Safer defaults** for production usage (less chance of leaking tool inputs/outputs, secrets, etc.).
|
|
15
|
+
- Still supports structured/masked logging via the Fjell-backed logger when enabled.
|
|
16
|
+
|
|
17
|
+
**Impact**
|
|
18
|
+
- If you previously relied on logs appearing without configuration, you may now see no output until you set one of the environment variables above.
|
|
19
|
+
- This affects components that use the default logger internally (for example: `ToolGuard`, `ToolSandbox`, `ToolRegistry`, and `ContextManager`), since they wrap `DEFAULT_LOGGER`.
|
|
20
|
+
|
|
21
|
+
### Package metadata/dependency adjustments
|
|
22
|
+
- Updated `package.json` metadata and dependency declarations.
|
|
23
|
+
- Version is now `1.0.4`.
|
|
24
|
+
|
|
25
|
+
### Security documentation updates
|
|
26
|
+
- `SECURITY.md` was updated to better document supported versions, security features, and recommended configuration (including logging enablement).
|
|
27
|
+
|
|
28
|
+
## Breaking changes
|
|
29
|
+
No explicit API-breaking changes were detected in this range, but **the default logging behavior change** may be user-visible if your integration depended on default log output. To restore logs, set `AGENTIC_LOGGING=true` or configure `DEBUG`/`NODE_ENV` as described above.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
## Main changes
|
|
2
|
+
|
|
3
|
+
### Logging: `DEFAULT_LOGGER` is now silent unless explicitly enabled
|
|
4
|
+
The library’s default logger has been changed to **discard all output by default** to reduce the risk of accidentally logging sensitive data.
|
|
5
|
+
|
|
6
|
+
Logging is now only enabled when one of the following is true:
|
|
7
|
+
- `AGENTIC_LOGGING=true`
|
|
8
|
+
- `DEBUG` includes `agentic` (for example: `DEBUG=*agentic*`)
|
|
9
|
+
- `NODE_ENV=development`
|
|
10
|
+
|
|
11
|
+
**Why this matters**
|
|
12
|
+
- **Safer defaults** for production usage (less chance of leaking tool inputs/outputs, secrets, etc.).
|
|
13
|
+
- Still supports structured/masked logging via the Fjell-backed logger when enabled.
|
|
14
|
+
|
|
15
|
+
**Impact**
|
|
16
|
+
- If you previously relied on logs appearing without configuration, you may now see no output until you set one of the environment variables above.
|
|
17
|
+
- This affects components that use the default logger internally (for example: `ToolGuard`, `ToolSandbox`, `ToolRegistry`, and `ContextManager`), since they wrap `DEFAULT_LOGGER`.
|
|
18
|
+
|
|
19
|
+
### Package metadata/dependency adjustments
|
|
20
|
+
- Updated `package.json` metadata and dependency declarations.
|
|
21
|
+
- Version is now `1.0.4`.
|
|
22
|
+
|
|
23
|
+
### Security documentation updates
|
|
24
|
+
- `SECURITY.md` was updated to better document supported versions, security features, and recommended configuration (including logging enablement).
|
|
25
|
+
|
|
26
|
+
## Breaking changes
|
|
27
|
+
No explicit API-breaking changes were detected in this range, but **the default logging behavior change** may be user-visible if your integration depended on default log output. To restore logs, set `AGENTIC_LOGGING=true` or configure `DEBUG`/`NODE_ENV` as described above.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1.0.4 — Silent-by-default logging and package metadata updates
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# Agentic Workflow - Self-Reflection Report
|
|
2
|
+
|
|
3
|
+
Generated: 2026-01-10T19:18:11.913Z
|
|
4
|
+
|
|
5
|
+
## Execution Summary
|
|
6
|
+
|
|
7
|
+
- **Iterations**: 2
|
|
8
|
+
- **Tool Calls**: 2
|
|
9
|
+
- **Unique Tools**: 3
|
|
10
|
+
|
|
11
|
+
## Tool Effectiveness Analysis
|
|
12
|
+
|
|
13
|
+
| Tool | Calls | Success | Failures | Success Rate | Avg Duration |
|
|
14
|
+
|------|-------|---------|----------|--------------|--------------|
|
|
15
|
+
| get_file_modification_times | 1 | 1 | 0 | 100.0% | 1ms |
|
|
16
|
+
| group_files_by_concern | 1 | 1 | 0 | 100.0% | 0ms |
|
|
17
|
+
| get_recent_commits | 1 | 0 | 1 | 0.0% | 18ms |
|
|
18
|
+
|
|
19
|
+
### Tool Performance Insights
|
|
20
|
+
|
|
21
|
+
**Tools with Failures:**
|
|
22
|
+
- get_recent_commits: 1/1 failures (100.0%)
|
|
23
|
+
|
|
24
|
+
**Most Frequently Used:**
|
|
25
|
+
- get_file_modification_times: 1 calls
|
|
26
|
+
- group_files_by_concern: 1 calls
|
|
27
|
+
- get_recent_commits: 1 calls
|
|
28
|
+
|
|
29
|
+
## Recommendations for Improvement
|
|
30
|
+
|
|
31
|
+
- **Tool Failures**: Investigate and fix tools that are failing. This may indicate issues with error handling or tool implementation.
|
|
32
|
+
|
|
33
|
+
## Detailed Execution Timeline
|
|
34
|
+
|
|
35
|
+
| Time | Iteration | Tool | Result | Duration |
|
|
36
|
+
|------|-----------|------|--------|----------|
|
|
37
|
+
| 11:17:18 AM | 1 | get_file_modification_times | ✅ Success | 1ms |
|
|
38
|
+
| 11:17:18 AM | 1 | group_files_by_concern | ✅ Success | 0ms |
|
|
39
|
+
| 11:17:18 AM | 1 | get_recent_commits | ❌ Tool "get_recent_commits" execution failed: Failed to get recent commits: Command failed: git log --format="%h - %s (%an, %ar)" --since="1 week ago" -n 5 -- package.json src/index.ts src/tools.ts src/context-manager.ts
|
|
40
|
+
fatal: your current branch 'main' does not have any commits yet
|
|
41
|
+
| 18ms |
|
|
42
|
+
|
|
43
|
+
## Conversation History
|
|
44
|
+
|
|
45
|
+
<details>
|
|
46
|
+
<summary>Click to expand full agentic interaction</summary>
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
[
|
|
50
|
+
{
|
|
51
|
+
"role": "system",
|
|
52
|
+
"content": "You are a professional software engineer writing commit messages for your team.\n\n## Available Tools\n\n### Understanding\n\n**get_file_history** _(cheap)_\nGet git commit history for one or more files to understand their evolution and past changes\n\nParameters:\n- `filePaths` (required): Array of file paths to get history for\n- `limit`: Maximum number of commits to return (default: 10)\n- `format`: Output format: \"summary\" for brief or \"detailed\" for full messages\n\n**When to use:** Get git commit history for one or more files to understand their evolution and past changes\n\n**Examples:**\n- Check if file has recent refactoring: `get_file_history({\"filePaths\":[\"src/auth.ts\"],\"limit\":5})`\n- Understand evolution of multiple related files: `get_file_history({\"filePaths\":[\"src/user.ts\",\"src/auth.ts\"],\"limit\":10,\"format\":\"detailed\"})`\n\n---\n\n**get_file_content** _(moderate)_\nGet the complete current content of a file to understand context around changes. Returns a message if the file does not exist (e.g., if it was deleted).\n\nParameters:\n- `filePath` (required): Path to the file\n- `includeLineNumbers`: Include line numbers in output (default: false)\n\n**When to use:** Get the complete current content of a file to understand context around changes. Returns a message if the file does not exist (e.g., if it was deleted).\n\n**Examples:**\n- See full class definition for modified method: `get_file_content({\"filePath\":\"src/services/auth.ts\",\"includeLineNumbers\":true})`\n- Check imports and structure: `get_file_content({\"filePath\":\"src/utils/helpers.ts\"})`\n\n---\n\n**get_related_tests** _(cheap)_\nFind test files related to production files to understand what the code is supposed to do\n\nParameters:\n- `filePaths` (required): Production file paths\n\n**When to use:** Find test files related to production files to understand what the code is supposed to do\n\n**Examples:**\n- Find tests for modified service: `get_related_tests({\"filePaths\":[\"src/services/auth.ts\"]})`\n\n---\n\n**analyze_diff_section** _(cheap)_\nGet expanded context around specific lines in a file to better understand changes\n\nParameters:\n- `filePath` (required): File containing the section to analyze\n- `startLine` (required): Starting line number\n- `endLine` (required): Ending line number\n- `contextLines`: Number of additional context lines to show before and after (default: 10)\n\n**When to use:** Get expanded context around specific lines in a file to better understand changes\n\n**Examples:**\n- See context around confusing change: `analyze_diff_section({\"filePath\":\"src/auth.ts\",\"startLine\":45,\"endLine\":52,\"contextLines\":15})`\n\n---\n\n**get_recent_commits** _(cheap)_\nGet recent commits that modified the same files to understand recent work in this area\n\nParameters:\n- `filePaths` (required): Files to check for recent commits\n- `since`: Time period to look back (e.g., \"1 week ago\", \"2 days ago\")\n- `limit`: Maximum number of commits to return (default: 5)\n\n**When to use:** Get recent commits that modified the same files to understand recent work in this area\n\n**Examples:**\n- Check for duplicate commit messages: `get_recent_commits({\"filePaths\":[\"src/auth.ts\"],\"since\":\"1 week ago\",\"limit\":5})`\n\n---\n\n### Analysis\n\n**search_codebase** _(moderate)_\nSearch for code patterns, function names, or text across the codebase using git grep\n\nParameters:\n- `query` (required): Search pattern (can be plain text or regex)\n- `fileTypes`: Limit search to specific file extensions (e.g., [\"ts\", \"js\"])\n- `contextLines`: Number of context lines to show around matches (default: 2)\n\n**When to use:** Search for code patterns, function names, or text across the codebase using git grep\n\n**Examples:**\n- Find all uses of a renamed function: `search_codebase({\"query\":\"oldFunctionName\",\"fileTypes\":[\"ts\",\"tsx\"]})`\n- Check if pattern exists elsewhere: `search_codebase({\"query\":\"pattern.*string\",\"contextLines\":3})`\n\n---\n\n**get_file_dependencies** _(moderate)_\nFind which files import or depend on the changed files to assess change impact\n\nParameters:\n- `filePaths` (required): Files to analyze dependencies for\n\n**When to use:** Find which files import or depend on the changed files to assess change impact\n\n**Examples:**\n- Check impact of util function change: `get_file_dependencies({\"filePaths\":[\"src/utils/format.ts\"]})`\n\n---\n\n### Organization\n\n**group_files_by_concern** _(cheap)_\nAnalyze changed files and suggest logical groupings that might represent separate commits\n\nParameters:\n- `filePaths` (required): All changed files to analyze\n\n**When to use:** Analyze changed files and suggest logical groupings that might represent separate commits\n\n**Examples:**\n- Check if multiple unrelated changes should be split: `group_files_by_concern({\"filePaths\":[\"src/auth.ts\",\"README.md\",\"tests/auth.test.ts\",\"package.json\"]})`\n\n---\n\n**get_file_modification_times** _(cheap)_\nGet modification timestamps for changed files. Temporal proximity is one signal (among others) that can help identify related changes. Files modified close together MAY be part of the same logical change, but this is not guaranteed - always cross-reference with logical groupings.\n\nParameters:\n- `filePaths` (required): All changed files to analyze\n\n**When to use:** Get modification timestamps for changed files. Temporal proximity is one signal (among others) that can help identify related changes. Files modified close together MAY be part of the same logical change, but this is not guaranteed - always cross-reference with logical groupings.\n\n**Examples:**\n- Identify files modified together vs at different times: `get_file_modification_times({\"filePaths\":[\"src/auth.ts\",\"src/user.ts\",\"README.md\",\"tests/auth.test.ts\"]})`\n\n---\n\n\n\n## Your Task\n\nAnalyze the staged changes and determine the best way to commit them. Your primary goal is to create **meaningful, atomic commits** that each represent a single logical change.\n\n**CRITICAL**: When there are many changed files, especially after a long work session (multiple hours), you should almost always split them into multiple commits. A single commit with 10+ files usually indicates multiple distinct changes that should be separated.\n\nThink about:\n- What distinct features, fixes, or improvements are represented?\n- Are there natural groupings by functionality, module, or purpose?\n- When were files modified relative to each other?\n- What should reviewers focus on in each commit?\n\n## Investigation Strategy\n\nFor any non-trivial set of changes, gather multiple signals to understand how to group commits:\n\n1. **Check file modification times** using `get_file_modification_times`\n - This reveals *when* files were changed relative to each other\n - Files modified close together *may* be part of the same logical change\n - Large temporal gaps *can* indicate separate work sessions\n - Note: temporal proximity is one signal, not a guarantee of relatedness\n\n2. **Understand logical relationships** using `group_files_by_concern`\n - Groups by module, type (tests, docs, source), and directory structure\n - Reveals which files are functionally related regardless of when they were modified\n\n3. **Cross-reference both signals** to make informed decisions:\n - Files modified together AND logically related → strong candidate for same commit\n - Files modified apart BUT logically related → might still belong together\n - Files modified together BUT logically unrelated → consider splitting\n - Use your judgment - neither signal is definitive on its own\n\n4. **Investigate further** as needed:\n - get_file_content - see full context when diffs are unclear\n - get_file_history - understand how code evolved\n - get_file_dependencies - assess impact of changes\n - get_recent_commits - avoid duplicate messages\n - get_related_tests - understand behavior changes\n - search_codebase - find usage patterns\n\n## When to Split Commits\n\n**Prefer multiple commits when:**\n- Changes span multiple hours of work (check modification times!)\n- Different logical features or fixes are included\n- Test changes could be separated from production code changes\n- Documentation updates are unrelated to code changes\n- Refactoring is mixed with feature work\n- Configuration changes are mixed with implementation changes\n- Files in different modules/packages are changed for different reasons\n\n**Keep as one commit when:**\n- All changes are part of a single, cohesive feature\n- Files were modified together in a focused work session\n- Test changes directly test the production code changes\n- The changes tell a single coherent story\n\n**Default bias**: When in doubt about whether to split, **prefer splitting**. It's better to have too many focused commits than too few bloated ones. A good commit should be understandable and reviewable in isolation.\n\n## Important Context Guidelines\n\nIf additional context is provided (from context files or other sources), use your judgment:\n- If the context is relevant to these specific changes, incorporate it\n- If the context describes unrelated changes or other packages, ignore it\n- Don't force connections between unrelated information\n- Focus on accurately describing what actually changed\n\n## Writing Style\n\nWrite naturally and directly:\n- Use plain language, not corporate speak\n- Be specific and concrete\n- Avoid buzzwords and jargon\n- No emojis or excessive punctuation\n- No phrases like \"this commit\" or \"this PR\"\n- No meta-commentary about the commit itself\n\nFollow conventional commit format when appropriate (feat:, fix:, refactor:, docs:, test:, chore:), but prioritize clarity over formality.\n\n## Output Format\n\nWhen ready, format your response as:\n\nCOMMIT_MESSAGE:\n[Your commit message here - only for the FIRST group of changes if splitting]\n\nIf changes should be split into multiple commits (which is often the case with large changesets):\n\nSUGGESTED_SPLITS:\nSplit 1:\nFiles: [list of files]\nRationale: [why these belong together - mention temporal clustering and/or logical relationship]\nMessage: [commit message for this split]\n\nSplit 2:\nFiles: [list of files]\nRationale: [why these belong together]\nMessage: [commit message for this split]\n\nSplit 3:\n...\n\nOutput only the commit message and splits. No conversational remarks or follow-up offers."
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"role": "user",
|
|
56
|
+
"content": "I have staged changes that need a commit message.\n\nChanged files (24):\n - .github/dependabot.yml\n - .github/workflows/npm-publish.yml\n - .github/workflows/test.yml\n - .gitignore\n - .kodrdriv-test-cache.json\n - .npmignore\n - .npmrc\n - .nvmrc\n - LICENSE\n - README.md\n - eslint.config.mjs\n - guide/index.md\n - package-lock.json\n - package.json\n - src/context-manager.test.ts\n - src/context-manager.ts\n - src/index.ts\n - src/logger.ts\n - src/tools.test.ts\n - src/tools.ts\n - src/types.ts\n - tsconfig.json\n - vite.config.ts\n - vitest.config.ts\n\nDiff:\ndiff --git a/.github/dependabot.yml b/.github/dependabot.yml\nnew file mode 100644\nindex 0000000..74cf653\n--- /dev/null\n+++ b/.github/dependabot.yml\n@@ -0,0 +1,7 @@\n+version: 2\n+updates:\n+ - package-ecosystem: \"npm\"\n+ directory: \"/\"\n+ schedule:\n+ interval: \"weekly\"\n+\ndiff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml\nnew file mode 100644\nindex 0000000..c73cc11\n--- /dev/null\n+++ b/.github/workflows/npm-publish.yml\n@@ -0,0 +1,26 @@\n+name: Node.js Package (npm)\n+\n+on:\n+ release:\n+ types: [created]\n+\n+permissions:\n+ contents: write\n+ packages: write\n+ id-token: write\n+\n+jobs:\n+ publish-npm:\n+ runs-on: ubuntu-latest\n+ timeout-minutes: 15\n+ steps:\n+ - uses: actions/checkout@v4\n+ - uses: actions/setup-node@v4\n+ with:\n+ node-version: 22\n+ registry-url: https://registry.npmjs.org/\n+ - run: npm install -g npm@latest\n+ - run: npm ci --verbose --foreground-scripts\n+ timeout-minutes: 10\n+ - run: npm publish --access public\n+\ndiff --git a/.github/workflows/test.yml b/.github/workflows/test.yml\nnew file mode 100644\nindex 0000000..b4245ba\n--- /dev/null\n+++ b/.github/workflows/test.yml\n@@ -0,0 +1,27 @@\n+name: Run Tests\n+\n+permissions:\n+ contents: read\n+ statuses: write\n+ id-token: write\n+\n+on:\n+ pull_request:\n+ branches: [main, working]\n+ push:\n+ branches: [working, main]\n+\n+jobs:\n+ test:\n+ runs-on: ubuntu-latest\n+ steps:\n+ - uses: actions/checkout@v4\n+ - uses: actions/setup-node@v4\n+ with:\n+ node-version: 22\n+ cache: 'npm'\n+ - run: npm ci\n+ - run: npm run lint\n+ - run: npm run build\n+ - run: npm run test\n+\ndiff --git a/.kodrdriv-test-cache.json b/.kodrdriv-test-cache.json\nnew file mode 100644\nindex 0000000..be3032e\n--- /dev/null\n+++ b/.kodrdriv-test-cache.json\n@@ -0,0 +1,6 @@\n+{\n+ \"/Users/tobrien/gitw/kjerneverk/agentic\": {\n+ \"lastTestRun\": 1768072514551,\n+ \"lastCommitHash\": \"unknown\"\n+ }\n+}\n\\ndiff --git a/.npmignore b/.npmignore\nnew file mode 100644\nindex 0000000..b8a9fb2\n--- /dev/null\n+++ b/.npmignore\n@@ -0,0 +1,16 @@\n+coverage/\n+node_modules/\n+tests/\n+src/\n+*.log\n+.env\n+README.md\n+guide/\n+.nvmrc\n+eslint.config.mjs\n+vite.config.ts\n+vitest.config.ts\n+.github/\n+.gitignore\n+tsconfig.json\n+\ndiff --git a/.npmrc b/.npmrc\nnew file mode 100644\nindex 0000000..268c392\n--- /dev/null\n+++ b/.npmrc\n@@ -0,0 +1 @@\n+provenance=true\ndiff --git a/.nvmrc b/.nvmrc\nnew file mode 100644\nindex 0000000..a41f71c\n--- /dev/null\n+++ b/.nvmrc\n@@ -0,0 +1,2 @@\n+v22.0.0\n+\ndiff --git a/LICENSE b/LICENSE\nnew file mode 100644\nindex 0000000..ba6e62b\n--- /dev/null\n+++ b/LICENSE\n@@ -0,0 +1,18 @@\n+ Apache License\n+ Version 2.0, January 2004\n+ http://www.apache.org/licenses/\n+\n+ Copyright 2025 Tim O'Brien\n+\n+ Licensed under the Apache License, Version 2.0 (the \"License\");\n+ you may not use this file except in compliance with the License.\n+ You may obtain a copy of the License at\n+\n+ http://www.apache.org/licenses/LICENSE-2.0\n+\n+ Unless required by applicable law or agreed to in writing, software\n+ distributed under the License is distributed on an \"AS IS\" BASIS,\n+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n+ See the License for the specific language governing permissions and\n+ limitations under the License.\n+\ndiff --git a/README.md b/README.md\nnew file mode 100644\nindex 0000000..f6a5308\n--- /dev/null\n+++ b/README.md\n@@ -0,0 +1,165 @@\n+# agentic\n+\n+Components for building agentic AI workflows - tool registry, context management, and conversation state tracking.\n+\n+## Installation\n+\n+```bash\n+npm install agentic\n+```\n+\n+## Features\n+\n+- **ToolRegistry** - Register, execute, and manage tools with usage tracking\n+- **ContextManager** - Track and deduplicate injected context\n+- **OpenAI/Anthropic Format** - Export tools in provider-specific formats\n+\n+## Usage\n+\n+### Tool Registry\n+\n+```typescript\n+import { ToolRegistry, type Tool } from 'agentic';\n+\n+// Create registry\n+const registry = ToolRegistry.create({\n+ workingDirectory: process.cwd(),\n+});\n+\n+// Register a tool\n+registry.register({\n+ name: 'read_file',\n+ description: 'Read contents of a file',\n+ parameters: {\n+ type: 'object',\n+ properties: {\n+ path: { type: 'string', description: 'File path to read' },\n+ },\n+ required: ['path'],\n+ },\n+ execute: async ({ path }) => {\n+ const fs = await import('fs/promises');\n+ return await fs.readFile(path, 'utf-8');\n+ },\n+ category: 'filesystem',\n+ cost: 'cheap',\n+});\n+\n+// Execute a tool\n+const content = await registry.execute('read_file', { path: './README.md' });\n+\n+// Export for OpenAI\n+const openAITools = registry.toOpenAIFormat();\n+\n+// Export for Anthropic\n+const anthropicTools = registry.toAnthropicFormat();\n+\n+// Get usage statistics\n+const stats = registry.getUsageStats();\n+console.log(stats.get('read_file')); // { calls: 5, failures: 0, successRate: 1 }\n+```\n+\n+### Context Manager\n+\n+```typescript\n+import { ContextManager } from 'agentic';\n+\n+const context = new ContextManager();\n+\n+// Track context with deduplication\n+context.track({\n+ id: 'file:main.ts',\n+ content: fileContent,\n+ title: 'Main File',\n+ category: 'source-code',\n+ priority: 'high',\n+}, 5); // position in conversation\n+\n+// Check for duplicates\n+if (context.hasContext('file:main.ts')) {\n+ console.log('Already provided');\n+}\n+\n+// Query by category\n+const sourceFiles = context.getByCategory('source-code');\n+\n+// Get statistics\n+const stats = context.getStats();\n+console.log(stats.totalItems);\n+console.log(stats.byCategory);\n+```\n+\n+### Tool Definition\n+\n+```typescript\n+interface Tool {\n+ name: string;\n+ description: string;\n+ parameters: {\n+ type: 'object';\n+ properties: Record<string, ToolParameter>;\n+ required?: string[];\n+ };\n+ execute: (params: any, context?: ToolContext) => Promise<any>;\n+ category?: string;\n+ cost?: 'cheap' | 'moderate' | 'expensive';\n+ examples?: ToolExample[];\n+}\n+```\n+\n+### Context Item\n+\n+```typescript\n+interface DynamicContentItem {\n+ content: string;\n+ title?: string;\n+ weight?: number;\n+ id?: string;\n+ category?: string;\n+ source?: string;\n+ priority?: 'high' | 'medium' | 'low';\n+ timestamp?: Date;\n+}\n+```\n+\n+## API Reference\n+\n+### ToolRegistry\n+\n+| Method | Description |\n+|--------|-------------|\n+| `create(context?)` | Create a new registry |\n+| `register(tool)` | Register a tool |\n+| `registerAll(tools)` | Register multiple tools |\n+| `execute(name, params)` | Execute a tool |\n+| `executeBatch(calls)` | Execute multiple tools |\n+| `toOpenAIFormat()` | Export for OpenAI |\n+| `toAnthropicFormat()` | Export for Anthropic |\n+| `getUsageStats()` | Get usage statistics |\n+| `getMostUsed(limit)` | Get most used tools |\n+| `getByCategory(category)` | Filter by category |\n+\n+### ContextManager\n+\n+| Method | Description |\n+|--------|-------------|\n+| `track(item, position)` | Track a context item |\n+| `hasContext(id)` | Check if ID exists |\n+| `hasContentHash(content)` | Check by content hash |\n+| `hasSimilarContent(content)` | Fuzzy content match |\n+| `getByCategory(category)` | Filter by category |\n+| `getByPriority(priority)` | Filter by priority |\n+| `getStats()` | Get statistics |\n+| `remove(id)` | Remove by ID |\n+| `clear()` | Clear all |\n+\n+## Related Packages\n+\n+- `execution` - Core provider interfaces\n+- `execution-openai` - OpenAI provider\n+- `execution-anthropic` - Anthropic provider\n+- `execution-gemini` - Gemini provider\n+\n+## License\n+\n+Apache-2.0\ndiff --git a/eslint.config.mjs b/eslint.config.mjs\nnew file mode 100644\nindex 0000000..068fa9a\n--- /dev/null\n+++ b/eslint.config.mjs\n@@ -0,0 +1,26 @@\n+import { defineConfig, globalIgnores } from \"eslint/config\";\n+import typescriptEslint from \"@typescript-eslint/eslint-plugin\";\n+import globals from \"globals\";\n+import tsParser from \"@typescript-eslint/parser\";\n+import js from \"@eslint/js\";\n+import { FlatCompat } from \"@eslint/eslintrc\";\n+import { fileURLToPath } from \"node:url\";\n+import path from \"node:path\";\n+\n+const __dirname = path.dirname(fileURLToPath(import.meta.url));\n+const compat = new FlatCompat({ baseDirectory: __dirname, recommendedConfig: js.configs.recommended });\n+\n+export default defineConfig([\n+ globalIgnores([\"dist/**\", \"node_modules/**\", \"**/*.test.ts\"]),\n+ {\n+ extends: compat.extends(\"eslint:recommended\", \"plugin:@typescript-eslint/recommended\"),\n+ plugins: { \"@typescript-eslint\": typescriptEslint },\n+ languageOptions: { globals: { ...globals.node }, parser: tsParser, sourceType: \"module\" },\n+ rules: {\n+ \"@typescript-eslint/no-explicit-any\": \"off\",\n+ \"@typescript-eslint/no-unused-vars\": [\"warn\", { argsIgnorePattern: \"^_\" }],\n+ indent: [\"error\", 4],\n+ \"no-console\": [\"error\"],\n+ },\n+ }]);\n+\ndiff --git a/guide/index.md b/guide/index.md\nnew file mode 100644\nindex 0000000..f6a1cbd\n--- /dev/null\n+++ b/guide/index.md\n@@ -0,0 +1,86 @@\n+# AI Agent Guide: agentic\n+\n+Shared framework for multi-turn AI conversations and tool orchestration.\n+\n+## Core Components\n+\n+### ConversationBuilder\n+\n+Build and manage multi-turn conversations:\n+\n+```typescript\n+import { ConversationBuilder } from 'agentic';\n+\n+const conversation = ConversationBuilder.fromPrompt(prompt)\n+ .addUserMessage('Hello!')\n+ .addAssistantMessage('Hi!')\n+ .addAssistantWithToolCalls([{ name: 'search', args: { q: 'test' } }])\n+ .addToolResult('result-id', 'Search results...')\n+ .build();\n+```\n+\n+### ToolRegistry\n+\n+Register and execute tools:\n+\n+```typescript\n+import { ToolRegistry } from 'agentic';\n+\n+const registry = new ToolRegistry();\n+\n+registry.register({\n+ name: 'search',\n+ description: 'Search the web',\n+ parameters: {\n+ query: { type: 'string', required: true }\n+ },\n+ execute: async ({ query }) => ({ results: [] })\n+});\n+\n+// Export for different LLM formats\n+registry.toOpenAIFormat();\n+registry.toAnthropicFormat();\n+```\n+\n+### IterationStrategy\n+\n+Define phased execution:\n+\n+```typescript\n+import { IterationStrategyFactory, StrategyExecutor } from 'agentic';\n+\n+const strategy = IterationStrategyFactory.create('iterative-refinement', {\n+ maxIterations: 5\n+});\n+\n+const executor = new StrategyExecutor(strategy, llmClient);\n+```\n+\n+### Reflection\n+\n+Analyze execution:\n+\n+```typescript\n+import { MetricsCollector, ReflectionReportGenerator } from 'agentic';\n+\n+const collector = new MetricsCollector();\n+collector.recordToolCall('search', 150, true);\n+\n+const report = ReflectionReportGenerator.generate(collector.getMetrics());\n+```\n+\n+## Dependencies\n+\n+- `zod` - Schema validation\n+- `execution` - Provider interfaces (peer, optional)\n+\n+## Source\n+\n+Components extracted from `riotprompt`:\n+- `conversation.ts` → ConversationBuilder\n+- `context-manager.ts` → ContextManager\n+- `token-budget.ts` → TokenBudgetManager\n+- `tools.ts` → ToolRegistry\n+- `iteration-strategy.ts` → StrategyExecutor, IterationStrategyFactory\n+- `reflection.ts` → MetricsCollector, ReflectionReportGenerator\n+\ndiff --git a/package.json b/package.json\nnew file mode 100644\nindex 0000000..29b26f0\n--- /dev/null\n+++ b/package.json\n@@ -0,0 +1,46 @@\n+{\n+ \"name\": \"@riotprompt/agentic\",\n+ \"version\": \"0.0.1\",\n+ \"description\": \"Shared agentic framework for multi-turn AI conversations and tool orchestration\",\n+ \"type\": \"module\",\n+ \"main\": \"./dist/index.cjs\",\n+ \"module\": \"./dist/index.js\",\n+ \"types\": \"./dist/index.d.ts\",\n+ \"exports\": {\n+ \".\": {\n+ \"types\": \"./dist/index.d.ts\",\n+ \"import\": \"./dist/index.js\",\n+ \"require\": \"./dist/index.cjs\"\n+ }\n+ },\n+ \"scripts\": {\n+ \"build\": \"vite build\",\n+ \"test\": \"vitest run\",\n+ \"lint\": \"eslint src\"\n+ },\n+ \"keywords\": [\n+ \"agentic\",\n+ \"ai\",\n+ \"llm\",\n+ \"conversation\",\n+ \"tools\",\n+ \"multi-turn\",\n+ \"orchestration\"\n+ ],\n+ \"author\": \"Tim O'Brien <tobrien@discursive.com>\",\n+ \"license\": \"Apache-2.0\",\n+ \"dependencies\": {\n+ \"zod\": \"^3.25.51\"\n+ },\n+ \"devDependencies\": {\n+ \"@types/node\": \"^25.0.6\",\n+ \"@typescript-eslint/eslint-plugin\": \"^8.33.1\",\n+ \"@typescript-eslint/parser\": \"^8.33.1\",\n+ \"eslint\": \"^9.28.0\",\n+ \"globals\": \"^16.2.0\",\n+ \"typescript\": \"^5.8.3\",\n+ \"vite\": \"^7.0.4\",\n+ \"vite-plugin-dts\": \"^4.5.4\",\n+ \"vitest\": \"^3.2.4\"\n+ }\n+}\ndiff --git a/src/context-manager.test.ts b/src/context-manager.test.ts\nnew file mode 100644\nindex 0000000..e7e247d\n--- /dev/null\n+++ b/src/context-manager.test.ts\n@@ -0,0 +1,300 @@\n+import { describe, it, expect, beforeEach } from 'vitest';\n+import { ContextManager, type ContextStats } from './context-manager';\n+import type { DynamicContentItem } from './types';\n+\n+describe('ContextManager', () => {\n+ let manager: ContextManager;\n+\n+ beforeEach(() => {\n+ manager = new ContextManager();\n+ });\n+\n+ describe('track', () => {\n+ it('should track a context item', () => {\n+ const item: DynamicContentItem = {\n+ content: 'Test content',\n+ title: 'Test Title',\n+ };\n+\n+ manager.track(item, 5);\n+\n+ expect(manager.getAll()).toHaveLength(1);\n+ });\n+\n+ it('should auto-generate ID if not provided', () => {\n+ manager.track({ content: 'Content' }, 0);\n+\n+ const items = manager.getAll();\n+ expect(items[0].id).toMatch(/^ctx-\\d+-[a-z0-9]+$/);\n+ });\n+\n+ it('should use provided ID', () => {\n+ manager.track({ id: 'custom-id', content: 'Content' }, 0);\n+\n+ expect(manager.hasContext('custom-id')).toBe(true);\n+ });\n+\n+ it('should skip duplicate content by hash for items without ID', () => {\n+ manager.track({ content: 'Same content' }, 0);\n+ manager.track({ content: 'Same content' }, 1);\n+\n+ expect(manager.getAll()).toHaveLength(1);\n+ });\n+\n+ it('should not skip duplicate content if item has ID', () => {\n+ manager.track({ id: 'item1', content: 'Same content' }, 0);\n+ manager.track({ id: 'item2', content: 'Same content' }, 1);\n+\n+ expect(manager.getAll()).toHaveLength(2);\n+ });\n+\n+ it('should set default priority to medium', () => {\n+ manager.track({ content: 'Content' }, 0);\n+\n+ const items = manager.getAll();\n+ expect(items[0].priority).toBe('medium');\n+ });\n+\n+ it('should preserve provided priority', () => {\n+ manager.track({ content: 'Content', priority: 'high' }, 0);\n+\n+ const items = manager.getAll();\n+ expect(items[0].priority).toBe('high');\n+ });\n+\n+ it('should set injectedAt timestamp', () => {\n+ const beforeTime = new Date();\n+ manager.track({ content: 'Content' }, 0);\n+ const afterTime = new Date();\n+\n+ const items = manager.getAll();\n+ expect(items[0].injectedAt.getTime()).toBeGreaterThanOrEqual(beforeTime.getTime());\n+ expect(items[0].injectedAt.getTime()).toBeLessThanOrEqual(afterTime.getTime());\n+ });\n+\n+ it('should record position', () => {\n+ manager.track({ content: 'Content' }, 42);\n+\n+ const items = manager.getAll();\n+ expect(items[0].position).toBe(42);\n+ });\n+ });\n+\n+ describe('hasContext', () => {\n+ it('should return true for existing ID', () => {\n+ manager.track({ id: 'test-id', content: 'Content' }, 0);\n+\n+ expect(manager.hasContext('test-id')).toBe(true);\n+ });\n+\n+ it('should return false for non-existing ID', () => {\n+ expect(manager.hasContext('nonexistent')).toBe(false);\n+ });\n+ });\n+\n+ describe('hasContentHash', () => {\n+ it('should return true for existing content', () => {\n+ manager.track({ content: 'Unique content' }, 0);\n+\n+ expect(manager.hasContentHash('Unique content')).toBe(true);\n+ });\n+\n+ it('should return false for new content', () => {\n+ manager.track({ content: 'Content A' }, 0);\n+\n+ expect(manager.hasContentHash('Content B')).toBe(false);\n+ });\n+ });\n+\n+ describe('hasSimilarContent', () => {\n+ it('should find exact match', () => {\n+ manager.track({ content: 'Exact content' }, 0);\n+\n+ expect(manager.hasSimilarContent('Exact content')).toBe(true);\n+ });\n+\n+ it('should find normalized match (whitespace)', () => {\n+ manager.track({ content: 'Content with spaces' }, 0);\n+\n+ expect(manager.hasSimilarContent('Content with spaces')).toBe(true);\n+ });\n+\n+ it('should find substring match above threshold', () => {\n+ manager.track({ content: 'This is the full content' }, 0);\n+\n+ // 'This is the full conten' is 95% of 'This is the full content'\n+ expect(manager.hasSimilarContent('This is the full conten')).toBe(true);\n+ });\n+\n+ it('should not match dissimilar content', () => {\n+ manager.track({ content: 'Original content here' }, 0);\n+\n+ expect(manager.hasSimilarContent('Completely different')).toBe(false);\n+ });\n+ });\n+\n+ describe('get', () => {\n+ it('should return tracked item by ID', () => {\n+ manager.track({ id: 'item-1', content: 'Content 1' }, 0);\n+\n+ const item = manager.get('item-1');\n+\n+ expect(item?.content).toBe('Content 1');\n+ });\n+\n+ it('should return undefined for non-existing ID', () => {\n+ expect(manager.get('nonexistent')).toBeUndefined();\n+ });\n+ });\n+\n+ describe('getAll', () => {\n+ it('should return all tracked items', () => {\n+ manager.track({ id: 'a', content: 'A' }, 0);\n+ manager.track({ id: 'b', content: 'B' }, 1);\n+ manager.track({ id: 'c', content: 'C' }, 2);\n+\n+ expect(manager.getAll()).toHaveLength(3);\n+ });\n+\n+ it('should return empty array when nothing tracked', () => {\n+ expect(manager.getAll()).toEqual([]);\n+ });\n+ });\n+\n+ describe('getByCategory', () => {\n+ it('should filter by category', () => {\n+ manager.track({ id: 'a', content: 'A', category: 'code' }, 0);\n+ manager.track({ id: 'b', content: 'B', category: 'docs' }, 1);\n+ manager.track({ id: 'c', content: 'C', category: 'code' }, 2);\n+\n+ const codeItems = manager.getByCategory('code');\n+\n+ expect(codeItems).toHaveLength(2);\n+ expect(codeItems.every(i => i.category === 'code')).toBe(true);\n+ });\n+ });\n+\n+ describe('getByPriority', () => {\n+ it('should filter by priority', () => {\n+ manager.track({ id: 'a', content: 'A', priority: 'high' }, 0);\n+ manager.track({ id: 'b', content: 'B', priority: 'low' }, 1);\n+ manager.track({ id: 'c', content: 'C', priority: 'high' }, 2);\n+\n+ const highPriority = manager.getByPriority('high');\n+\n+ expect(highPriority).toHaveLength(2);\n+ });\n+ });\n+\n+ describe('getBySource', () => {\n+ it('should filter by source', () => {\n+ manager.track({ id: 'a', content: 'A', source: 'file' }, 0);\n+ manager.track({ id: 'b', content: 'B', source: 'api' }, 1);\n+ manager.track({ id: 'c', content: 'C', source: 'file' }, 2);\n+\n+ const fileItems = manager.getBySource('file');\n+\n+ expect(fileItems).toHaveLength(2);\n+ });\n+ });\n+\n+ describe('getCategories', () => {\n+ it('should return unique categories sorted', () => {\n+ manager.track({ id: 'a', content: 'A', category: 'zebra' }, 0);\n+ manager.track({ id: 'b', content: 'B', category: 'alpha' }, 1);\n+ manager.track({ id: 'c', content: 'C', category: 'zebra' }, 2);\n+ manager.track({ id: 'd', content: 'D', category: 'beta' }, 3);\n+\n+ const categories = manager.getCategories();\n+\n+ expect(categories).toEqual(['alpha', 'beta', 'zebra']);\n+ });\n+\n+ it('should return empty array if no categories', () => {\n+ manager.track({ id: 'a', content: 'A' }, 0);\n+\n+ expect(manager.getCategories()).toEqual([]);\n+ });\n+ });\n+\n+ describe('getStats', () => {\n+ it('should return comprehensive statistics', () => {\n+ manager.track({ id: 'a', content: 'A', category: 'code', priority: 'high', source: 'file' }, 0);\n+ manager.track({ id: 'b', content: 'B', category: 'code', priority: 'low', source: 'api' }, 1);\n+ manager.track({ id: 'c', content: 'C', category: 'docs', priority: 'high', source: 'file' }, 2);\n+\n+ const stats = manager.getStats();\n+\n+ expect(stats.totalItems).toBe(3);\n+ expect(stats.byCategory.get('code')).toBe(2);\n+ expect(stats.byCategory.get('docs')).toBe(1);\n+ expect(stats.byPriority.get('high')).toBe(2);\n+ expect(stats.byPriority.get('low')).toBe(1);\n+ expect(stats.bySource.get('file')).toBe(2);\n+ expect(stats.bySource.get('api')).toBe(1);\n+ });\n+\n+ it('should track timestamp range', () => {\n+ const timestamp1 = new Date('2024-01-01');\n+ const timestamp2 = new Date('2024-06-01');\n+ const timestamp3 = new Date('2024-03-01');\n+\n+ manager.track({ id: 'a', content: 'A', timestamp: timestamp1 }, 0);\n+ manager.track({ id: 'b', content: 'B', timestamp: timestamp2 }, 1);\n+ manager.track({ id: 'c', content: 'C', timestamp: timestamp3 }, 2);\n+\n+ const stats = manager.getStats();\n+\n+ expect(stats.oldestTimestamp).toEqual(timestamp1);\n+ expect(stats.newestTimestamp).toEqual(timestamp2);\n+ });\n+ });\n+\n+ describe('remove', () => {\n+ it('should remove item by ID', () => {\n+ manager.track({ id: 'to-remove', content: 'Content' }, 0);\n+\n+ const result = manager.remove('to-remove');\n+\n+ expect(result).toBe(true);\n+ expect(manager.hasContext('to-remove')).toBe(false);\n+ });\n+\n+ it('should return false for non-existing ID', () => {\n+ const result = manager.remove('nonexistent');\n+\n+ expect(result).toBe(false);\n+ });\n+\n+ it('should also remove from hash set', () => {\n+ manager.track({ id: 'item', content: 'Unique content' }, 0);\n+ manager.remove('item');\n+\n+ // Now the same content can be added again\n+ manager.track({ content: 'Unique content' }, 1);\n+ expect(manager.getAll()).toHaveLength(1);\n+ });\n+ });\n+\n+ describe('clear', () => {\n+ it('should remove all items', () => {\n+ manager.track({ id: 'a', content: 'A' }, 0);\n+ manager.track({ id: 'b', content: 'B' }, 1);\n+ manager.track({ id: 'c', content: 'C' }, 2);\n+\n+ manager.clear();\n+\n+ expect(manager.getAll()).toEqual([]);\n+ });\n+\n+ it('should clear hash set too', () => {\n+ manager.track({ content: 'Content' }, 0);\n+ manager.clear();\n+\n+ // Same content should be trackable again\n+ manager.track({ content: 'Content' }, 1);\n+ expect(manager.getAll()).toHaveLength(1);\n+ });\n+ });\n+});\n+\ndiff --git a/src/context-manager.ts b/src/context-manager.ts\nnew file mode 100644\nindex 0000000..4f487cb\n--- /dev/null\n+++ b/src/context-manager.ts\n@@ -0,0 +1,267 @@\n+/**\n+ * Agentic Package - Context Manager\n+ *\n+ * Tracks and manages dynamically injected context in conversations.\n+ */\n+\n+import crypto from 'crypto';\n+import { DEFAULT_LOGGER, wrapLogger, type Logger } from './logger';\n+import type { DynamicContentItem, TrackedContextItem } from './types';\n+\n+/**\n+ * Context statistics\n+ */\n+export interface ContextStats {\n+ totalItems: number;\n+ byCategory: Map<string, number>;\n+ byPriority: Map<string, number>;\n+ bySource: Map<string, number>;\n+ oldestTimestamp?: Date;\n+ newestTimestamp?: Date;\n+}\n+\n+/**\n+ * ContextManager tracks and manages dynamically injected context.\n+ */\n+export class ContextManager {\n+ private items: Map<string, TrackedContextItem>;\n+ private hashes: Set<string>;\n+ private logger: Logger;\n+\n+ constructor(logger?: Logger) {\n+ this.items = new Map();\n+ this.hashes = new Set();\n+ this.logger = wrapLogger(logger || DEFAULT_LOGGER, 'ContextManager');\n+ }\n+\n+ /**\n+ * Track a context item\n+ */\n+ track(item: DynamicContentItem, position: number): void {\n+ const hash = this.hashContent(item.content);\n+\n+ if (!item.id && this.hashes.has(hash)) {\n+ this.logger.debug('Skipping duplicate context item by hash', { hash });\n+ return;\n+ }\n+\n+ const id = item.id || this.generateId();\n+\n+ const trackedItem: TrackedContextItem = {\n+ ...item,\n+ id,\n+ hash,\n+ position,\n+ injectedAt: new Date(),\n+ timestamp: item.timestamp || new Date(),\n+ priority: item.priority || 'medium',\n+ };\n+\n+ this.items.set(id, trackedItem);\n+ this.hashes.add(hash);\n+\n+ this.logger.debug('Tracked context item', {\n+ id,\n+ category: item.category,\n+ position,\n+ });\n+ }\n+\n+ /**\n+ * Check if context with given ID exists\n+ */\n+ hasContext(id: string): boolean {\n+ return this.items.has(id);\n+ }\n+\n+ /**\n+ * Check if content with given hash exists\n+ */\n+ hasContentHash(content: string): boolean {\n+ const hash = this.hashContent(content);\n+ return this.hashes.has(hash);\n+ }\n+\n+ /**\n+ * Check if similar content exists (fuzzy match)\n+ */\n+ hasSimilarContent(\n+ content: string,\n+ similarityThreshold: number = 0.9\n+ ): boolean {\n+ const MAX_ITEMS_WARNING = 1000;\n+ if (this.items.size > MAX_ITEMS_WARNING) {\n+ this.logger.warn(\n+ 'Large number of context items, similarity check may be slow',\n+ {\n+ count: this.items.size,\n+ threshold: MAX_ITEMS_WARNING,\n+ }\n+ );\n+ }\n+\n+ const normalized = this.normalizeContent(content);\n+\n+ for (const item of this.items.values()) {\n+ const itemNormalized = this.normalizeContent(item.content || '');\n+\n+ if (normalized === itemNormalized) {\n+ return true;\n+ }\n+\n+ const longer =\n+ normalized.length > itemNormalized.length\n+ ? normalized\n+ : itemNormalized;\n+ const shorter =\n+ normalized.length <= itemNormalized.length\n+ ? normalized\n+ : itemNormalized;\n+\n+ const lengthRatio = shorter.length / longer.length;\n+\n+ if (lengthRatio >= similarityThreshold) {\n+ if (longer.includes(shorter)) {\n+ return true;\n+ }\n+ }\n+ }\n+\n+ return false;\n+ }\n+\n+ /**\n+ * Get context item by ID\n+ */\n+ get(id: string): TrackedContextItem | undefined {\n+ return this.items.get(id);\n+ }\n+\n+ /**\n+ * Get all tracked context items\n+ */\n+ getAll(): TrackedContextItem[] {\n+ return Array.from(this.items.values());\n+ }\n+\n+ /**\n+ * Get context items by category\n+ */\n+ getByCategory(category: string): TrackedContextItem[] {\n+ return this.getAll().filter((item) => item.category === category);\n+ }\n+\n+ /**\n+ * Get context items by priority\n+ */\n+ getByPriority(priority: 'high' | 'medium' | 'low'): TrackedContextItem[] {\n+ return this.getAll().filter((item) => item.priority === priority);\n+ }\n+\n+ /**\n+ * Get context items by source\n+ */\n+ getBySource(source: string): TrackedContextItem[] {\n+ return this.getAll().filter((item) => item.source === source);\n+ }\n+\n+ /**\n+ * Get all categories\n+ */\n+ getCategories(): string[] {\n+ const categories = new Set<string>();\n+ this.items.forEach((item) => {\n+ if (item.category) {\n+ categories.add(item.category);\n+ }\n+ });\n+ return Array.from(categories).sort();\n+ }\n+\n+ /**\n+ * Get context statistics\n+ */\n+ getStats(): ContextStats {\n+ const byCategory = new Map<string, number>();\n+ const byPriority = new Map<string, number>();\n+ const bySource = new Map<string, number>();\n+ let oldestTimestamp: Date | undefined;\n+ let newestTimestamp: Date | undefined;\n+\n+ this.items.forEach((item) => {\n+ if (item.category) {\n+ byCategory.set(\n+ item.category,\n+ (byCategory.get(item.category) || 0) + 1\n+ );\n+ }\n+\n+ const priority = item.priority || 'medium';\n+ byPriority.set(priority, (byPriority.get(priority) || 0) + 1);\n+\n+ if (item.source) {\n+ bySource.set(item.source, (bySource.get(item.source) || 0) + 1);\n+ }\n+\n+ if (item.timestamp) {\n+ if (!oldestTimestamp || item.timestamp < oldestTimestamp) {\n+ oldestTimestamp = item.timestamp;\n+ }\n+ if (!newestTimestamp || item.timestamp > newestTimestamp) {\n+ newestTimestamp = item.timestamp;\n+ }\n+ }\n+ });\n+\n+ return {\n+ totalItems: this.items.size,\n+ byCategory,\n+ byPriority,\n+ bySource,\n+ oldestTimestamp,\n+ newestTimestamp,\n+ };\n+ }\n+\n+ /**\n+ * Remove context item by ID\n+ */\n+ remove(id: string): boolean {\n+ const item = this.items.get(id);\n+ if (item) {\n+ this.items.delete(id);\n+ this.hashes.delete(item.hash);\n+ this.logger.debug('Removed context item', { id });\n+ return true;\n+ }\n+ return false;\n+ }\n+\n+ /**\n+ * Clear all tracked context\n+ */\n+ clear(): void {\n+ this.items.clear();\n+ this.hashes.clear();\n+ this.logger.debug('Cleared all context');\n+ }\n+\n+ private generateId(): string {\n+ return `ctx-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;\n+ }\n+\n+ private hashContent(content: string): string {\n+ return crypto\n+ .createHash('sha256')\n+ .update(content)\n+ .digest('hex')\n+ .substring(0, 16);\n+ }\n+\n+ private normalizeContent(content: string): string {\n+ return content.replace(/\\s+/g, ' ').trim().toLowerCase();\n+ }\n+}\n+\n+export default ContextManager;\n+\ndiff --git a/src/index.ts b/src/index.ts\nnew file mode 100644\nindex 0000000..a7ac357\n--- /dev/null\n+++ b/src/index.ts\n@@ -0,0 +1,62 @@\n+/**\n+ * Agentic Package\n+ *\n+ * Components for building agentic AI workflows:\n+ * - Tool registry and execution\n+ * - Context management\n+ * - Conversation state tracking\n+ *\n+ * @packageDocumentation\n+ */\n+\n+// Types\n+export type {\n+ ConversationMessage,\n+ ToolCall,\n+ Model,\n+ ToolUsagePolicy,\n+ IterationAction,\n+ ToolCallAction,\n+ InjectOptions,\n+ DynamicContentItem,\n+ TrackedContextItem,\n+ LLMClient,\n+ StrategyState,\n+ Insight,\n+ ToolResult,\n+ PhaseResult,\n+ TokenUsage,\n+ CompressionStats,\n+ CompressionStrategy,\n+ TokenBudgetConfig,\n+ LogFormat,\n+ LogConfig,\n+} from './types';\n+\n+// Logger\n+export type { Logger } from './logger';\n+export { DEFAULT_LOGGER, wrapLogger, LIBRARY_NAME } from './logger';\n+\n+// Tool Registry\n+export type {\n+ ToolParameter,\n+ ToolContext,\n+ ToolExample,\n+ ToolCost,\n+ Tool,\n+ OpenAITool,\n+ AnthropicTool,\n+ ToolExecutionResult,\n+ ToolUsageStats,\n+ ToolDefinition,\n+} from './tools';\n+export { ToolRegistry } from './tools';\n+\n+// Context Manager\n+export type { ContextStats } from './context-manager';\n+export { ContextManager } from './context-manager';\n+\n+/**\n+ * Package version\n+ */\n+export const VERSION = '0.0.1';\ndiff --git a/src/logger.ts b/src/logger.ts\nnew file mode 100644\nindex 0000000..4c823b5\n--- /dev/null\n+++ b/src/logger.ts\n@@ -0,0 +1,81 @@\n+/**\n+ * Agentic Package - Logger\n+ *\n+ * Shared logging infrastructure for agentic components.\n+ */\n+\n+/* eslint-disable no-var */\n+declare var console: {\n+ debug: (message: string, ...args: any[]) => void;\n+ info: (message: string, ...args: any[]) => void;\n+ warn: (message: string, ...args: any[]) => void;\n+ error: (message: string, ...args: any[]) => void;\n+};\n+/* eslint-enable no-var */\n+\n+export const LIBRARY_NAME = 'agentic';\n+\n+export interface Logger {\n+ name: string;\n+ debug: (message: string, ...args: any[]) => void;\n+ info: (message: string, ...args: any[]) => void;\n+ warn: (message: string, ...args: any[]) => void;\n+ error: (message: string, ...args: any[]) => void;\n+ verbose: (message: string, ...args: any[]) => void;\n+ silly: (message: string, ...args: any[]) => void;\n+}\n+\n+ \n+export const DEFAULT_LOGGER: Logger = {\n+ name: 'default',\n+ debug: (message: string, ...args: any[]) => console.debug(message, ...args),\n+ info: (message: string, ...args: any[]) => console.info(message, ...args),\n+ warn: (message: string, ...args: any[]) => console.warn(message, ...args),\n+ error: (message: string, ...args: any[]) => console.error(message, ...args),\n+ verbose: () => {},\n+ silly: () => {},\n+};\n+ \n+\n+export function wrapLogger(toWrap: Logger, name?: string): Logger {\n+ const requiredMethods: (keyof Logger)[] = [\n+ 'debug',\n+ 'info',\n+ 'warn',\n+ 'error',\n+ 'verbose',\n+ 'silly',\n+ ];\n+ const missingMethods = requiredMethods.filter(\n+ (method) => typeof toWrap[method] !== 'function'\n+ );\n+\n+ if (missingMethods.length > 0) {\n+ throw new Error(\n+ `Logger is missing required methods: ${missingMethods.join(', ')}`\n+ );\n+ }\n+\n+ const log = (level: keyof Logger, message: string, ...args: any[]) => {\n+ message = `[${LIBRARY_NAME}] ${name ? `[${name}]` : ''}: ${message}`;\n+\n+ if (level === 'debug') toWrap.debug(message, ...args);\n+ else if (level === 'info') toWrap.info(message, ...args);\n+ else if (level === 'warn') toWrap.warn(message, ...args);\n+ else if (level === 'error') toWrap.error(message, ...args);\n+ else if (level === 'verbose') toWrap.verbose(message, ...args);\n+ else if (level === 'silly') toWrap.silly(message, ...args);\n+ };\n+\n+ return {\n+ name: 'wrapped',\n+ debug: (message: string, ...args: any[]) => log('debug', message, ...args),\n+ info: (message: string, ...args: any[]) => log('info', message, ...args),\n+ warn: (message: string, ...args: any[]) => log('warn', message, ...args),\n+ error: (message: string, ...args: any[]) => log('error', message, ...args),\n+ verbose: (message: string, ...args: any[]) =>\n+ log('verbose', message, ...args),\n+ silly: (message: string, ...args: any[]) => log('silly', message, ...args),\n+ };\n+}\n+\ndiff --git a/src/tools.test.ts b/src/tools.test.ts\nnew file mode 100644\nindex 0000000..3bd2717\n--- /dev/null\n+++ b/src/tools.test.ts\n@@ -0,0 +1,515 @@\n+import { describe, it, expect, vi, beforeEach } from 'vitest';\n+import { ToolRegistry, type Tool, type ToolContext } from './tools';\n+\n+describe('ToolRegistry', () => {\n+ let registry: ToolRegistry;\n+\n+ beforeEach(() => {\n+ registry = ToolRegistry.create();\n+ });\n+\n+ describe('create', () => {\n+ it('should create an empty registry', () => {\n+ expect(registry.count()).toBe(0);\n+ });\n+\n+ it('should accept initial context', () => {\n+ const context: ToolContext = { workingDirectory: '/tmp' };\n+ const registryWithContext = ToolRegistry.create(context);\n+ \n+ expect(registryWithContext.getContext().workingDirectory).toBe('/tmp');\n+ });\n+ });\n+\n+ describe('register', () => {\n+ it('should register a valid tool', () => {\n+ const tool: Tool = {\n+ name: 'test_tool',\n+ description: 'A test tool',\n+ parameters: {\n+ type: 'object',\n+ properties: {\n+ input: { type: 'string', description: 'Input value' },\n+ },\n+ required: ['input'],\n+ },\n+ execute: async ({ input }) => `Received: ${input}`,\n+ };\n+\n+ registry.register(tool);\n+\n+ expect(registry.has('test_tool')).toBe(true);\n+ expect(registry.count()).toBe(1);\n+ });\n+\n+ it('should throw for invalid tool definition', () => {\n+ const invalidTool = {\n+ name: '', // Empty name should fail\n+ description: 'Invalid',\n+ parameters: { type: 'object', properties: {} },\n+ execute: async () => {},\n+ };\n+\n+ expect(() => registry.register(invalidTool as Tool)).toThrow();\n+ });\n+\n+ it('should allow overwriting existing tool with warning', () => {\n+ const tool1: Tool = {\n+ name: 'tool',\n+ description: 'Version 1',\n+ parameters: { type: 'object', properties: {} },\n+ execute: async () => 'v1',\n+ };\n+\n+ const tool2: Tool = {\n+ name: 'tool',\n+ description: 'Version 2',\n+ parameters: { type: 'object', properties: {} },\n+ execute: async () => 'v2',\n+ };\n+\n+ registry.register(tool1);\n+ registry.register(tool2);\n+\n+ expect(registry.count()).toBe(1);\n+ expect(registry.get('tool')?.description).toBe('Version 2');\n+ });\n+ });\n+\n+ describe('registerAll', () => {\n+ it('should register multiple tools', () => {\n+ const tools: Tool[] = [\n+ {\n+ name: 'tool1',\n+ description: 'Tool 1',\n+ parameters: { type: 'object', properties: {} },\n+ execute: async () => 'result1',\n+ },\n+ {\n+ name: 'tool2',\n+ description: 'Tool 2',\n+ parameters: { type: 'object', properties: {} },\n+ execute: async () => 'result2',\n+ },\n+ ];\n+\n+ registry.registerAll(tools);\n+\n+ expect(registry.count()).toBe(2);\n+ });\n+ });\n+\n+ describe('get', () => {\n+ it('should return tool by name', () => {\n+ const tool: Tool = {\n+ name: 'my_tool',\n+ description: 'My tool',\n+ parameters: { type: 'object', properties: {} },\n+ execute: async () => 'result',\n+ };\n+\n+ registry.register(tool);\n+\n+ expect(registry.get('my_tool')).toBe(tool);\n+ });\n+\n+ it('should return undefined for non-existent tool', () => {\n+ expect(registry.get('nonexistent')).toBeUndefined();\n+ });\n+ });\n+\n+ describe('getAll', () => {\n+ it('should return all registered tools', () => {\n+ registry.register({\n+ name: 'tool1',\n+ description: 'Tool 1',\n+ parameters: { type: 'object', properties: {} },\n+ execute: async () => {},\n+ });\n+ registry.register({\n+ name: 'tool2',\n+ description: 'Tool 2',\n+ parameters: { type: 'object', properties: {} },\n+ execute: async () => {},\n+ });\n+\n+ const tools = registry.getAll();\n+\n+ expect(tools).toHaveLength(2);\n+ });\n+ });\n+\n+ describe('getByCategory', () => {\n+ it('should filter tools by category', () => {\n+ registry.register({\n+ name: 'file_read',\n+ description: 'Read file',\n+ parameters: { type: 'object', properties: {} },\n+ execute: async () => {},\n+ category: 'filesystem',\n+ });\n+ registry.register({\n+ name: 'file_write',\n+ description: 'Write file',\n+ parameters: { type: 'object', properties: {} },\n+ execute: async () => {},\n+ category: 'filesystem',\n+ });\n+ registry.register({\n+ name: 'http_get',\n+ description: 'HTTP GET',\n+ parameters: { type: 'object', properties: {} },\n+ execute: async () => {},\n+ category: 'network',\n+ });\n+\n+ const fsTools = registry.getByCategory('filesystem');\n+\n+ expect(fsTools).toHaveLength(2);\n+ expect(fsTools.every(t => t.category === 'filesystem')).toBe(true);\n+ });\n+ });\n+\n+ describe('execute', () => {\n+ it('should execute tool and return result', async () => {\n+ registry.register({\n+ name: 'add',\n+ description: 'Add numbers',\n+ parameters: {\n+ type: 'object',\n+ properties: {\n+ a: { type: 'number', description: 'First number' },\n+ b: { type: 'number', description: 'Second number' },\n+ },\n+ required: ['a', 'b'],\n+ },\n+ execute: async ({ a, b }) => a + b,\n+ });\n+\n+ const result = await registry.execute('add', { a: 2, b: 3 });\n+\n+ expect(result).toBe(5);\n+ });\n+\n+ it('should throw for non-existent tool', async () => {\n+ await expect(registry.execute('nonexistent', {})).rejects.toThrow(\n+ 'Tool \"nonexistent\" not found'\n+ );\n+ });\n+\n+ it('should pass context to tool', async () => {\n+ const context: ToolContext = { \n+ workingDirectory: '/test',\n+ customData: 'test123',\n+ };\n+ const registryWithContext = ToolRegistry.create(context);\n+\n+ let receivedContext: ToolContext | undefined;\n+ registryWithContext.register({\n+ name: 'check_context',\n+ description: 'Check context',\n+ parameters: { type: 'object', properties: {} },\n+ execute: async (_params, ctx) => {\n+ receivedContext = ctx;\n+ return 'done';\n+ },\n+ });\n+\n+ await registryWithContext.execute('check_context', {});\n+\n+ expect(receivedContext?.workingDirectory).toBe('/test');\n+ expect(receivedContext?.customData).toBe('test123');\n+ });\n+\n+ it('should track usage statistics', async () => {\n+ registry.register({\n+ name: 'tracked_tool',\n+ description: 'Tracked tool',\n+ parameters: { type: 'object', properties: {} },\n+ execute: async () => 'result',\n+ });\n+\n+ await registry.execute('tracked_tool', {});\n+ await registry.execute('tracked_tool', {});\n+ await registry.execute('tracked_tool', {});\n+\n+ const stats = registry.getUsageStats();\n+ const toolStats = stats.get('tracked_tool');\n+\n+ expect(toolStats?.calls).toBe(3);\n+ expect(toolStats?.failures).toBe(0);\n+ expect(toolStats?.successRate).toBe(1);\n+ });\n+\n+ it('should track failures', async () => {\n+ registry.register({\n+ name: 'failing_tool',\n+ description: 'Failing tool',\n+ parameters: { type: 'object', properties: {} },\n+ execute: async () => {\n+ throw new Error('Tool failed');\n+ },\n+ });\n+\n+ await expect(registry.execute('failing_tool', {})).rejects.toThrow();\n+\n+ const stats = registry.getUsageStats();\n+ const toolStats = stats.get('failing_tool');\n+\n+ expect(toolStats?.calls).toBe(1);\n+ expect(toolStats?.failures).toBe(1);\n+ expect(toolStats?.successRate).toBe(0);\n+ });\n+ });\n+\n+ describe('executeBatch', () => {\n+ it('should execute multiple tools in sequence', async () => {\n+ registry.register({\n+ name: 'double',\n+ description: 'Double number',\n+ parameters: { type: 'object', properties: {} },\n+ execute: async ({ n }) => n * 2,\n+ });\n+\n+ const results = await registry.executeBatch([\n+ { name: 'double', params: { n: 1 } },\n+ { name: 'double', params: { n: 2 } },\n+ { name: 'double', params: { n: 3 } },\n+ ]);\n+\n+ expect(results).toEqual([2, 4, 6]);\n+ });\n+\n+ it('should handle errors in batch without stopping', async () => {\n+ registry.register({\n+ name: 'ok',\n+ description: 'OK',\n+ parameters: { type: 'object', properties: {} },\n+ execute: async () => 'ok',\n+ });\n+\n+ const results = await registry.executeBatch([\n+ { name: 'ok', params: {} },\n+ { name: 'nonexistent', params: {} },\n+ { name: 'ok', params: {} },\n+ ]);\n+\n+ expect(results[0]).toBe('ok');\n+ expect(results[1]).toHaveProperty('error');\n+ expect(results[2]).toBe('ok');\n+ });\n+ });\n+\n+ describe('toOpenAIFormat', () => {\n+ it('should convert tools to OpenAI format', () => {\n+ registry.register({\n+ name: 'search',\n+ description: 'Search for something',\n+ parameters: {\n+ type: 'object',\n+ properties: {\n+ query: { type: 'string', description: 'Search query' },\n+ },\n+ required: ['query'],\n+ },\n+ execute: async () => {},\n+ });\n+\n+ const openAITools = registry.toOpenAIFormat();\n+\n+ expect(openAITools).toHaveLength(1);\n+ expect(openAITools[0].type).toBe('function');\n+ expect(openAITools[0].function.name).toBe('search');\n+ expect(openAITools[0].function.description).toBe('Search for something');\n+ expect(openAITools[0].function.parameters.properties.query).toBeDefined();\n+ });\n+ });\n+\n+ describe('toAnthropicFormat', () => {\n+ it('should convert tools to Anthropic format', () => {\n+ registry.register({\n+ name: 'read_file',\n+ description: 'Read a file',\n+ parameters: {\n+ type: 'object',\n+ properties: {\n+ path: { type: 'string', description: 'File path' },\n+ },\n+ required: ['path'],\n+ },\n+ execute: async () => {},\n+ });\n+\n+ const anthropicTools = registry.toAnthropicFormat();\n+\n+ expect(anthropicTools).toHaveLength(1);\n+ expect(anthropicTools[0].name).toBe('read_file');\n+ expect(anthropicTools[0].description).toBe('Read a file');\n+ expect(anthropicTools[0].input_schema.type).toBe('object');\n+ expect(anthropicTools[0].input_schema.properties.path).toBeDefined();\n+ });\n+ });\n+\n+ describe('getDefinitions', () => {\n+ it('should return tool definitions without execute function', () => {\n+ registry.register({\n+ name: 'tool',\n+ description: 'Tool',\n+ parameters: { type: 'object', properties: {} },\n+ execute: async () => {},\n+ category: 'test',\n+ cost: 'cheap',\n+ });\n+\n+ const definitions = registry.getDefinitions();\n+\n+ expect(definitions).toHaveLength(1);\n+ expect(definitions[0].name).toBe('tool');\n+ expect(definitions[0].category).toBe('test');\n+ expect(definitions[0].cost).toBe('cheap');\n+ expect((definitions[0] as any).execute).toBeUndefined();\n+ });\n+ });\n+\n+ describe('updateContext', () => {\n+ it('should update context', () => {\n+ registry.updateContext({ workingDirectory: '/new/path' });\n+ \n+ expect(registry.getContext().workingDirectory).toBe('/new/path');\n+ });\n+\n+ it('should merge with existing context', () => {\n+ const registryWithContext = ToolRegistry.create({ \n+ workingDirectory: '/old',\n+ storage: 'memory',\n+ });\n+ \n+ registryWithContext.updateContext({ workingDirectory: '/new' });\n+ \n+ const ctx = registryWithContext.getContext();\n+ expect(ctx.workingDirectory).toBe('/new');\n+ expect(ctx.storage).toBe('memory');\n+ });\n+ });\n+\n+ describe('clear', () => {\n+ it('should remove all tools', () => {\n+ registry.register({\n+ name: 'tool',\n+ description: 'Tool',\n+ parameters: { type: 'object', properties: {} },\n+ execute: async () => {},\n+ });\n+\n+ registry.clear();\n+\n+ expect(registry.count()).toBe(0);\n+ });\n+ });\n+\n+ describe('unregister', () => {\n+ it('should remove specific tool', () => {\n+ registry.register({\n+ name: 'tool1',\n+ description: 'Tool 1',\n+ parameters: { type: 'object', properties: {} },\n+ execute: async () => {},\n+ });\n+ registry.register({\n+ name: 'tool2',\n+ description: 'Tool 2',\n+ parameters: { type: 'object', properties: {} },\n+ execute: async () => {},\n+ });\n+\n+ const result = registry.unregister('tool1');\n+\n+ expect(result).toBe(true);\n+ expect(registry.has('tool1')).toBe(false);\n+ expect(registry.has('tool2')).toBe(true);\n+ });\n+\n+ it('should return false for non-existent tool', () => {\n+ const result = registry.unregister('nonexistent');\n+ \n+ expect(result).toBe(false);\n+ });\n+ });\n+\n+ describe('getCategories', () => {\n+ it('should return unique categories', () => {\n+ registry.register({\n+ name: 't1',\n+ description: 'T1',\n+ parameters: { type: 'object', properties: {} },\n+ execute: async () => {},\n+ category: 'a',\n+ });\n+ registry.register({\n+ name: 't2',\n+ description: 'T2',\n+ parameters: { type: 'object', properties: {} },\n+ execute: async () => {},\n+ category: 'b',\n+ });\n+ registry.register({\n+ name: 't3',\n+ description: 'T3',\n+ parameters: { type: 'object', properties: {} },\n+ execute: async () => {},\n+ category: 'a',\n+ });\n+\n+ const categories = registry.getCategories();\n+\n+ expect(categories).toEqual(['a', 'b']);\n+ });\n+ });\n+\n+ describe('getMostUsed', () => {\n+ it('should return most frequently used tools', async () => {\n+ registry.register({\n+ name: 'popular',\n+ description: 'Popular',\n+ parameters: { type: 'object', properties: {} },\n+ execute: async () => {},\n+ });\n+ registry.register({\n+ name: 'unpopular',\n+ description: 'Unpopular',\n+ parameters: { type: 'object', properties: {} },\n+ execute: async () => {},\n+ });\n+\n+ await registry.execute('popular', {});\n+ await registry.execute('popular', {});\n+ await registry.execute('popular', {});\n+ await registry.execute('unpopular', {});\n+\n+ const mostUsed = registry.getMostUsed(1);\n+\n+ expect(mostUsed).toHaveLength(1);\n+ expect(mostUsed[0].name).toBe('popular');\n+ });\n+ });\n+\n+ describe('resetStats', () => {\n+ it('should reset all usage statistics', async () => {\n+ registry.register({\n+ name: 'tool',\n+ description: 'Tool',\n+ parameters: { type: 'object', properties: {} },\n+ execute: async () => {},\n+ });\n+\n+ await registry.execute('tool', {});\n+ await registry.execute('tool', {});\n+\n+ registry.resetStats();\n+\n+ const stats = registry.getUsageStats().get('tool');\n+ expect(stats?.calls).toBe(0);\n+ });\n+ });\n+});\n+\ndiff --git a/src/tools.ts b/src/tools.ts\nnew file mode 100644\nindex 0000000..7bf98bc\n--- /dev/null\n+++ b/src/tools.ts\n@@ -0,0 +1,463 @@\n+/**\n+ * Agentic Package - Tool Registry\n+ *\n+ * Manages tool definitions and execution for agentic workflows.\n+ */\n+\n+import { z } from 'zod';\n+import { DEFAULT_LOGGER, wrapLogger, type Logger } from './logger';\n+\n+// ===== TYPE DEFINITIONS =====\n+\n+/**\n+ * Parameter definition for a tool\n+ */\n+export interface ToolParameter {\n+ type: 'string' | 'number' | 'boolean' | 'array' | 'object';\n+ description: string;\n+ items?: ToolParameter;\n+ properties?: Record<string, ToolParameter>;\n+ required?: string[];\n+ enum?: string[];\n+ default?: any;\n+}\n+\n+/**\n+ * Context provided to tool execution\n+ */\n+export interface ToolContext {\n+ workingDirectory?: string;\n+ storage?: any;\n+ logger?: Logger;\n+ conversationState?: any;\n+ [key: string]: any;\n+}\n+\n+/**\n+ * Example usage of a tool\n+ */\n+export interface ToolExample {\n+ scenario: string;\n+ params: any;\n+ expectedResult: string;\n+}\n+\n+/**\n+ * Cost hint for tool execution\n+ */\n+export type ToolCost = 'cheap' | 'moderate' | 'expensive';\n+\n+/**\n+ * Tool definition\n+ */\n+export interface Tool {\n+ name: string;\n+ description: string;\n+ parameters: {\n+ type: 'object';\n+ properties: Record<string, ToolParameter>;\n+ required?: string[];\n+ };\n+ execute: (params: any, context?: ToolContext) => Promise<any>;\n+ category?: string;\n+ cost?: ToolCost;\n+ examples?: ToolExample[];\n+}\n+\n+/**\n+ * OpenAI-compatible tool format\n+ */\n+export interface OpenAITool {\n+ type: 'function';\n+ function: {\n+ name: string;\n+ description: string;\n+ parameters: {\n+ type: 'object';\n+ properties: Record<string, any>;\n+ required?: string[];\n+ };\n+ };\n+}\n+\n+/**\n+ * Anthropic-compatible tool format\n+ */\n+export interface AnthropicTool {\n+ name: string;\n+ description: string;\n+ input_schema: {\n+ type: 'object';\n+ properties: Record<string, any>;\n+ required?: string[];\n+ };\n+}\n+\n+/**\n+ * Tool execution result\n+ */\n+export interface ToolExecutionResult {\n+ success: boolean;\n+ result?: any;\n+ error?: string;\n+ duration?: number;\n+}\n+\n+/**\n+ * Usage statistics for a tool\n+ */\n+export interface ToolUsageStats {\n+ calls: number;\n+ failures: number;\n+ successRate: number;\n+ averageDuration?: number;\n+}\n+\n+/**\n+ * Tool definition for export\n+ */\n+export interface ToolDefinition {\n+ name: string;\n+ description: string;\n+ parameters: Tool['parameters'];\n+ category?: string;\n+ cost?: ToolCost;\n+ examples?: ToolExample[];\n+}\n+\n+// ===== VALIDATION SCHEMAS =====\n+\n+const ToolSchema = z\n+ .object({\n+ name: z.string().min(1),\n+ description: z.string().min(1),\n+ parameters: z\n+ .object({\n+ type: z.literal('object'),\n+ properties: z.record(z.string(), z.any()).default({}),\n+ required: z.array(z.string()).optional(),\n+ })\n+ .passthrough(),\n+ execute: z.custom<(params: any, context?: any) => Promise<any>>(\n+ (val) => typeof val === 'function',\n+ { message: 'execute must be a function' }\n+ ),\n+ category: z.string().optional(),\n+ cost: z.enum(['cheap', 'moderate', 'expensive']).optional(),\n+ examples: z\n+ .array(\n+ z.object({\n+ scenario: z.string(),\n+ params: z.any(),\n+ expectedResult: z.string(),\n+ })\n+ )\n+ .optional(),\n+ })\n+ .passthrough();\n+\n+// ===== TOOL REGISTRY =====\n+\n+/**\n+ * ToolRegistry manages tool definitions and execution.\n+ */\n+export class ToolRegistry {\n+ private tools: Map<string, Tool>;\n+ private context: ToolContext;\n+ private logger: Logger;\n+ private usageStats: Map<\n+ string,\n+ { calls: number; failures: number; totalDuration: number }\n+ >;\n+\n+ private constructor(context: ToolContext = {}, logger?: Logger) {\n+ this.tools = new Map();\n+ this.context = context;\n+ this.logger = wrapLogger(logger || DEFAULT_LOGGER, 'ToolRegistry');\n+ this.usageStats = new Map();\n+\n+ this.logger.debug('Created ToolRegistry');\n+ }\n+\n+ /**\n+ * Create a new ToolRegistry instance\n+ */\n+ static create(context?: ToolContext, logger?: Logger): ToolRegistry {\n+ return new ToolRegistry(context, logger);\n+ }\n+\n+ /**\n+ * Register a single tool\n+ */\n+ register(tool: Tool): void {\n+ try {\n+ ToolSchema.parse(tool);\n+ } catch (error) {\n+ throw new Error(`Invalid tool definition for \"${tool.name}\": ${error}`);\n+ }\n+\n+ if (this.tools.has(tool.name)) {\n+ this.logger.warn(`Tool \"${tool.name}\" already registered, overwriting`);\n+ }\n+\n+ this.tools.set(tool.name, tool);\n+ this.usageStats.set(tool.name, { calls: 0, failures: 0, totalDuration: 0 });\n+\n+ this.logger.debug('Registered tool', {\n+ name: tool.name,\n+ category: tool.category,\n+ });\n+ }\n+\n+ /**\n+ * Register multiple tools at once\n+ */\n+ registerAll(tools: Tool[]): void {\n+ this.logger.debug('Registering multiple tools', { count: tools.length });\n+ tools.forEach((tool) => this.register(tool));\n+ }\n+\n+ /**\n+ * Get a tool by name\n+ */\n+ get(name: string): Tool | undefined {\n+ return this.tools.get(name);\n+ }\n+\n+ /**\n+ * Get all registered tools\n+ */\n+ getAll(): Tool[] {\n+ return Array.from(this.tools.values());\n+ }\n+\n+ /**\n+ * Get tools by category\n+ */\n+ getByCategory(category: string): Tool[] {\n+ return this.getAll().filter((tool) => tool.category === category);\n+ }\n+\n+ /**\n+ * Check if a tool is registered\n+ */\n+ has(name: string): boolean {\n+ return this.tools.has(name);\n+ }\n+\n+ /**\n+ * Get number of registered tools\n+ */\n+ count(): number {\n+ return this.tools.size;\n+ }\n+\n+ /**\n+ * Execute a tool by name\n+ */\n+ async execute(name: string, params: any): Promise<any> {\n+ const tool = this.tools.get(name);\n+\n+ if (!tool) {\n+ throw new Error(`Tool \"${name}\" not found`);\n+ }\n+\n+ this.logger.debug('Executing tool', { name, params });\n+\n+ const startTime = Date.now();\n+ const stats = this.usageStats.get(name)!;\n+ stats.calls++;\n+\n+ try {\n+ const result = await tool.execute(params, this.context);\n+\n+ const duration = Date.now() - startTime;\n+ stats.totalDuration += duration;\n+\n+ this.logger.debug('Tool execution succeeded', { name, duration });\n+\n+ return result;\n+ } catch (error) {\n+ stats.failures++;\n+\n+ this.logger.error('Tool execution failed', { name, error });\n+\n+ throw error;\n+ }\n+ }\n+\n+ /**\n+ * Execute multiple tools in sequence\n+ */\n+ async executeBatch(\n+ calls: Array<{ name: string; params: any }>\n+ ): Promise<any[]> {\n+ this.logger.debug('Executing batch', { count: calls.length });\n+\n+ const results: any[] = [];\n+\n+ for (const call of calls) {\n+ try {\n+ const result = await this.execute(call.name, call.params);\n+ results.push(result);\n+ } catch (error) {\n+ results.push({ error: String(error) });\n+ }\n+ }\n+\n+ return results;\n+ }\n+\n+ /**\n+ * Export tools in OpenAI format\n+ */\n+ toOpenAIFormat(): OpenAITool[] {\n+ return this.getAll().map((tool) => ({\n+ type: 'function' as const,\n+ function: {\n+ name: tool.name,\n+ description: tool.description,\n+ parameters: {\n+ type: 'object' as const,\n+ properties: tool.parameters.properties,\n+ required: tool.parameters.required,\n+ },\n+ },\n+ }));\n+ }\n+\n+ /**\n+ * Export tools in Anthropic format\n+ */\n+ toAnthropicFormat(): AnthropicTool[] {\n+ return this.getAll().map((tool) => ({\n+ name: tool.name,\n+ description: tool.description,\n+ input_schema: {\n+ type: 'object' as const,\n+ properties: tool.parameters.properties,\n+ required: tool.parameters.required,\n+ },\n+ }));\n+ }\n+\n+ /**\n+ * Get tool definitions (without execute function)\n+ */\n+ getDefinitions(): ToolDefinition[] {\n+ return this.getAll().map((tool) => ({\n+ name: tool.name,\n+ description: tool.description,\n+ parameters: tool.parameters,\n+ category: tool.category,\n+ cost: tool.cost,\n+ examples: tool.examples,\n+ }));\n+ }\n+\n+ /**\n+ * Get usage statistics for all tools\n+ */\n+ getUsageStats(): Map<string, ToolUsageStats> {\n+ const stats = new Map<string, ToolUsageStats>();\n+\n+ this.usageStats.forEach((rawStats, name) => {\n+ stats.set(name, {\n+ calls: rawStats.calls,\n+ failures: rawStats.failures,\n+ successRate:\n+ rawStats.calls > 0\n+ ? (rawStats.calls - rawStats.failures) / rawStats.calls\n+ : 0,\n+ averageDuration:\n+ rawStats.calls > 0\n+ ? rawStats.totalDuration / rawStats.calls\n+ : undefined,\n+ });\n+ });\n+\n+ return stats;\n+ }\n+\n+ /**\n+ * Get most frequently used tools\n+ */\n+ getMostUsed(limit: number = 5): Tool[] {\n+ const sorted = Array.from(this.usageStats.entries())\n+ .sort((a, b) => b[1].calls - a[1].calls)\n+ .slice(0, limit)\n+ .map(([name]) => this.tools.get(name)!)\n+ .filter((tool) => tool !== undefined);\n+\n+ return sorted;\n+ }\n+\n+ /**\n+ * Get list of all categories\n+ */\n+ getCategories(): string[] {\n+ const categories = new Set<string>();\n+\n+ this.getAll().forEach((tool) => {\n+ if (tool.category) {\n+ categories.add(tool.category);\n+ }\n+ });\n+\n+ return Array.from(categories).sort();\n+ }\n+\n+ /**\n+ * Update execution context\n+ */\n+ updateContext(context: Partial<ToolContext>): void {\n+ this.context = { ...this.context, ...context };\n+ this.logger.debug('Updated context', { keys: Object.keys(context) });\n+ }\n+\n+ /**\n+ * Get current context\n+ */\n+ getContext(): ToolContext {\n+ return { ...this.context };\n+ }\n+\n+ /**\n+ * Clear all tools\n+ */\n+ clear(): void {\n+ this.logger.debug('Clearing all tools');\n+ this.tools.clear();\n+ this.usageStats.clear();\n+ }\n+\n+ /**\n+ * Unregister a specific tool\n+ */\n+ unregister(name: string): boolean {\n+ if (this.tools.has(name)) {\n+ this.tools.delete(name);\n+ this.usageStats.delete(name);\n+ this.logger.debug('Unregistered tool', { name });\n+ return true;\n+ }\n+\n+ return false;\n+ }\n+\n+ /**\n+ * Reset usage statistics\n+ */\n+ resetStats(): void {\n+ this.logger.debug('Resetting usage statistics');\n+\n+ this.usageStats.forEach((stats) => {\n+ stats.calls = 0;\n+ stats.failures = 0;\n+ stats.totalDuration = 0;\n+ });\n+ }\n+}\n+\n+export default ToolRegistry;\n+\ndiff --git a/src/types.ts b/src/types.ts\nnew file mode 100644\nindex 0000000..092dc6e\n--- /dev/null\n+++ b/src/types.ts\n@@ -0,0 +1,218 @@\n+/**\n+ * Agentic Package - Types\n+ *\n+ * Core type definitions for agentic workflows.\n+ */\n+\n+// ===== INLINE TYPES (from 'execution' package) =====\n+\n+export type Model = string;\n+\n+export interface ToolCall {\n+ id: string;\n+ type: 'function';\n+ function: {\n+ name: string;\n+ arguments: string;\n+ };\n+}\n+\n+export interface ConversationMessage {\n+ role: 'system' | 'user' | 'assistant' | 'tool' | 'developer';\n+ content: string | null;\n+ name?: string;\n+ tool_calls?: ToolCall[];\n+ tool_call_id?: string;\n+}\n+\n+// ===== TOOL USAGE AND STRATEGY TYPES =====\n+\n+/**\n+ * Tool usage policy for a phase\n+ */\n+export type ToolUsagePolicy = 'required' | 'encouraged' | 'optional' | 'forbidden';\n+\n+/**\n+ * Action to take after iteration\n+ */\n+export type IterationAction = 'continue' | 'stop' | 'next-phase';\n+\n+/**\n+ * Action to take for tool call\n+ */\n+export type ToolCallAction = 'execute' | 'skip' | 'defer';\n+\n+// ===== CONTEXT TYPES =====\n+\n+/**\n+ * Options for injecting context\n+ */\n+export interface InjectOptions {\n+ position?: 'end' | 'before-last' | 'after-system' | number;\n+ format?: 'structured' | 'inline' | 'reference';\n+ deduplicate?: boolean;\n+ deduplicateBy?: 'id' | 'content' | 'hash';\n+ priority?: 'high' | 'medium' | 'low';\n+ weight?: number;\n+ category?: string;\n+ source?: string;\n+}\n+\n+/**\n+ * Dynamic content item with enhanced tracking and lifecycle\n+ */\n+export interface DynamicContentItem {\n+ content: string;\n+ title?: string;\n+ weight?: number;\n+ id?: string;\n+ category?: string;\n+ source?: string;\n+ priority?: 'high' | 'medium' | 'low';\n+ timestamp?: Date;\n+}\n+\n+/**\n+ * Tracked context item with metadata\n+ */\n+export interface TrackedContextItem extends DynamicContentItem {\n+ id: string;\n+ hash: string;\n+ position: number;\n+ injectedAt: Date;\n+}\n+\n+// ===== STRATEGY TYPES =====\n+\n+/**\n+ * LLM client interface (generic, provider-agnostic)\n+ */\n+export interface LLMClient {\n+ complete(\n+ messages: ConversationMessage[],\n+ tools?: any[]\n+ ): Promise<{\n+ content: string | null;\n+ tool_calls?: ToolCall[];\n+ }>;\n+}\n+\n+/**\n+ * Current state of strategy execution\n+ */\n+export interface StrategyState {\n+ phase: string | number;\n+ iteration: number;\n+ toolCallsExecuted: number;\n+ startTime: number;\n+ insights: Insight[];\n+ findings: any[];\n+ errors: Error[];\n+ toolFailures: Map<string, number>;\n+ [key: string]: any;\n+}\n+\n+/**\n+ * Insight discovered during execution\n+ */\n+export interface Insight {\n+ source: string;\n+ content: string;\n+ confidence: number;\n+ relatedTo?: string[];\n+}\n+\n+/**\n+ * Result of tool execution\n+ */\n+export interface ToolResult {\n+ callId: string;\n+ toolName: string;\n+ result: any;\n+ error?: Error;\n+ duration: number;\n+}\n+\n+/**\n+ * Result of a phase\n+ */\n+export interface PhaseResult {\n+ name: string;\n+ iterations: number;\n+ toolCalls: number;\n+ success: boolean;\n+ insights?: Insight[];\n+}\n+\n+// ===== TOKEN BUDGET TYPES =====\n+\n+/**\n+ * Token usage information\n+ */\n+export interface TokenUsage {\n+ used: number;\n+ max: number;\n+ remaining: number;\n+ percentage: number;\n+}\n+\n+/**\n+ * Compression statistics\n+ */\n+export interface CompressionStats {\n+ messagesBefore: number;\n+ messagesAfter: number;\n+ tokensBefore: number;\n+ tokensAfter: number;\n+ tokensSaved: number;\n+ strategy: CompressionStrategy;\n+}\n+\n+/**\n+ * Compression strategy\n+ */\n+export type CompressionStrategy =\n+ | 'priority-based'\n+ | 'fifo'\n+ | 'summarize'\n+ | 'adaptive';\n+\n+/**\n+ * Token budget configuration\n+ */\n+export interface TokenBudgetConfig {\n+ max: number;\n+ reserveForResponse: number;\n+ warningThreshold?: number;\n+ strategy: CompressionStrategy;\n+ onBudgetExceeded: 'compress' | 'error' | 'warn' | 'truncate';\n+ preserveRecent?: number;\n+ preserveSystem?: boolean;\n+ preserveHighPriority?: boolean;\n+ onWarning?: (usage: TokenUsage) => void;\n+ onCompression?: (stats: CompressionStats) => void;\n+}\n+\n+// ===== LOGGING TYPES =====\n+\n+/**\n+ * Log format\n+ */\n+export type LogFormat = 'json' | 'markdown' | 'jsonl';\n+\n+/**\n+ * Log configuration\n+ */\n+export interface LogConfig {\n+ enabled: boolean;\n+ outputPath?: string;\n+ format?: LogFormat;\n+ filenameTemplate?: string;\n+ includeMetadata?: boolean;\n+ includePrompt?: boolean;\n+ redactSensitive?: boolean;\n+ redactPatterns?: RegExp[];\n+ onSaved?: (path: string) => void;\n+ onError?: (error: Error) => void;\n+}\n+\ndiff --git a/tsconfig.json b/tsconfig.json\nnew file mode 100644\nindex 0000000..50dd1ef\n--- /dev/null\n+++ b/tsconfig.json\n@@ -0,0 +1,21 @@\n+{\n+ \"compilerOptions\": {\n+ \"target\": \"ES2022\",\n+ \"lib\": [\"ES2022\"],\n+ \"module\": \"ESNext\",\n+ \"moduleResolution\": \"bundler\",\n+ \"strict\": true,\n+ \"declaration\": true,\n+ \"declarationMap\": true,\n+ \"sourceMap\": true,\n+ \"esModuleInterop\": true,\n+ \"skipLibCheck\": true,\n+ \"forceConsistentCasingInFileNames\": true,\n+ \"outDir\": \"./dist\",\n+ \"rootDir\": \"./src\",\n+ \"types\": [\"node\"]\n+ },\n+ \"include\": [\"src/**/*\"],\n+ \"exclude\": [\"node_modules\", \"dist\", \"tests\"]\n+}\n+\ndiff --git a/vite.config.ts b/vite.config.ts\nnew file mode 100644\nindex 0000000..98476af\n--- /dev/null\n+++ b/vite.config.ts\n@@ -0,0 +1,21 @@\n+import { defineConfig } from \"vite\";\n+import dts from \"vite-plugin-dts\";\n+import { resolve } from \"path\";\n+\n+export default defineConfig({\n+ build: {\n+ lib: {\n+ entry: resolve(__dirname, \"src/index.ts\"),\n+ name: \"agentic\",\n+ fileName: (format) => (format === \"es\" ? \"index.js\" : \"index.cjs\"),\n+ formats: [\"es\", \"cjs\"],\n+ },\n+ rollupOptions: {\n+ external: [\"zod\", \"execution\"],\n+ },\n+ sourcemap: true,\n+ minify: false,\n+ },\n+ plugins: [dts({ rollupTypes: true })],\n+});\n+\ndiff --git a/vitest.config.ts b/vitest.config.ts\nnew file mode 100644\nindex 0000000..9d8325f\n--- /dev/null\n+++ b/vitest.config.ts\n@@ -0,0 +1,15 @@\n+import { defineConfig } from \"vitest/config\";\n+\n+export default defineConfig({\n+ test: {\n+ include: [\"src/**/*.test.ts\", \"tests/**/*.test.ts\"],\n+ globals: true,\n+ coverage: {\n+ provider: \"v8\",\n+ reporter: [\"text\", \"html\"],\n+ include: [\"src/**/*.ts\"],\n+ exclude: [\"src/**/*.test.ts\"],\n+ },\n+ },\n+});\n+\n\n\n## Your Analysis Task\n\nWith 24 files changed, consider whether these represent multiple distinct changes that should be split into separate commits.\n\nGather signals to understand the changes:\n1. Use `get_file_modification_times` to see when files were modified relative to each other\n2. Use `group_files_by_concern` to understand how files relate by type/purpose\n3. Cross-reference both signals - temporal proximity and logical relatedness - to determine the best commit structure\n\nConsider:\n- What distinct features, fixes, or improvements are included?\n- Are files that were modified together also logically related?\n- Would separate commits make the history more understandable?\n\nWith many files, consider whether multiple focused commits would be clearer than one large commit.\n\nIf context information is provided, use it only if relevant to these specific changes.\nDon't force connections that don't exist - focus on what actually changed."
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"role": "assistant",
|
|
60
|
+
"content": null,
|
|
61
|
+
"tool_calls": [
|
|
62
|
+
{
|
|
63
|
+
"id": "call_WuqWa8rgMVtEbMFkbEOH16Ll",
|
|
64
|
+
"type": "function",
|
|
65
|
+
"function": {
|
|
66
|
+
"name": "get_file_modification_times",
|
|
67
|
+
"arguments": "{\"filePaths\": [\".github/dependabot.yml\", \".github/workflows/npm-publish.yml\", \".github/workflows/test.yml\", \".gitignore\", \".kodrdriv-test-cache.json\", \".npmignore\", \".npmrc\", \".nvmrc\", \"LICENSE\", \"README.md\", \"eslint.config.mjs\", \"guide/index.md\", \"package-lock.json\", \"package.json\", \"src/context-manager.test.ts\", \"src/context-manager.ts\", \"src/index.ts\", \"src/logger.ts\", \"src/tools.test.ts\", \"src/tools.ts\", \"src/types.ts\", \"tsconfig.json\", \"vite.config.ts\", \"vitest.config.ts\"]}"
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"id": "call_XWpNHVd3MezvZuGkvMaRvlGb",
|
|
72
|
+
"type": "function",
|
|
73
|
+
"function": {
|
|
74
|
+
"name": "group_files_by_concern",
|
|
75
|
+
"arguments": "{\"filePaths\": [\".github/dependabot.yml\", \".github/workflows/npm-publish.yml\", \".github/workflows/test.yml\", \".gitignore\", \".kodrdriv-test-cache.json\", \".npmignore\", \".npmrc\", \".nvmrc\", \"LICENSE\", \"README.md\", \"eslint.config.mjs\", \"guide/index.md\", \"package-lock.json\", \"package.json\", \"src/context-manager.test.ts\", \"src/context-manager.ts\", \"src/index.ts\", \"src/logger.ts\", \"src/tools.test.ts\", \"src/tools.ts\", \"src/types.ts\", \"tsconfig.json\", \"vite.config.ts\", \"vitest.config.ts\"]}"
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"id": "call_ME9YDGms0T5nK8MN8Q6Ozbj5",
|
|
80
|
+
"type": "function",
|
|
81
|
+
"function": {
|
|
82
|
+
"name": "get_recent_commits",
|
|
83
|
+
"arguments": "{\"filePaths\": [\"package.json\", \"src/index.ts\", \"src/tools.ts\", \"src/context-manager.ts\"], \"since\": \"1 week ago\", \"limit\": 5}"
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"role": "tool",
|
|
90
|
+
"tool_call_id": "call_WuqWa8rgMVtEbMFkbEOH16Ll",
|
|
91
|
+
"content": "## File Modification Times (sorted oldest to newest)\n\n2026-01-10 17:50:58 .github/workflows/npm-publish.yml\n2026-01-10 17:50:58 .github/dependabot.yml\n2026-01-10 17:50:58 .github/workflows/test.yml\n2026-01-10 17:50:58 .npmignore\n2026-01-10 17:50:58 .nvmrc\n2026-01-10 17:50:58 .gitignore\n2026-01-10 17:50:58 .npmrc\n2026-01-10 17:50:58 eslint.config.mjs\n2026-01-10 17:50:58 LICENSE\n2026-01-10 17:50:58 vite.config.ts\n2026-01-10 17:50:58 guide/index.md\n2026-01-10 17:55:51 src/context-manager.ts\n2026-01-10 17:55:51 src/index.ts\n2026-01-10 18:13:07 package-lock.json\n2026-01-10 18:14:59 tsconfig.json\n2026-01-10 18:14:59 src/types.ts\n2026-01-10 18:26:42 src/tools.test.ts\n2026-01-10 18:26:42 src/context-manager.test.ts\n2026-01-10 18:26:42 vitest.config.ts\n2026-01-10 18:26:42 README.md\n2026-01-10 18:43:01 src/tools.ts\n2026-01-10 18:43:01 src/logger.ts\n2026-01-10 19:12:54 package.json\n2026-01-10 19:15:14 .kodrdriv-test-cache.json\n\n\n## Temporal Clusters (files modified within 10 minutes of each other)\n\nFound 5 distinct work sessions:\n\n### Session 1 (13 files, 5 min span)\nTime: 2026-01-10 17:50:58 to 2026-01-10 17:55:51\nFiles:\n - .github/workflows/npm-publish.yml\n - .github/dependabot.yml\n - .github/workflows/test.yml\n - .npmignore\n - .nvmrc\n - .gitignore\n - .npmrc\n - eslint.config.mjs\n - LICENSE\n - vite.config.ts\n - guide/index.md\n - src/context-manager.ts\n - src/index.ts\n\n### Session 2 (3 files, 2 min span)\nTime: 2026-01-10 18:13:07 to 2026-01-10 18:14:59\nFiles:\n - package-lock.json\n - tsconfig.json\n - src/types.ts\n\n### Session 3 (4 files, 0 min span)\nTime: 2026-01-10 18:26:42 to 2026-01-10 18:26:42\nFiles:\n - src/tools.test.ts\n - src/context-manager.test.ts\n - vitest.config.ts\n - README.md\n\n### Session 4 (2 files, 0 min span)\nTime: 2026-01-10 18:43:01 to 2026-01-10 18:43:01\nFiles:\n - src/tools.ts\n - src/logger.ts\n\n### Session 5 (2 files, 2 min span)\nTime: 2026-01-10 19:12:54 to 2026-01-10 19:15:14\nFiles:\n - package.json\n - .kodrdriv-test-cache.json\n\n\n**Note**: These 5 temporal clusters may indicate separate work sessions. Cross-reference with logical groupings to determine if they represent distinct changes worth splitting.",
|
|
92
|
+
"name": "get_file_modification_times"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"role": "tool",
|
|
96
|
+
"tool_call_id": "call_XWpNHVd3MezvZuGkvMaRvlGb",
|
|
97
|
+
"content": "other (13 files):\n - .github/dependabot.yml\n - .github/workflows/npm-publish.yml\n - .github/workflows/test.yml\n - .gitignore\n - .kodrdriv-test-cache.json\n - .npmignore\n - .npmrc\n - .nvmrc\n - LICENSE\n - eslint.config.mjs\n - tsconfig.json\n - vite.config.ts\n - vitest.config.ts\n\ndocumentation (2 files):\n - README.md\n - guide/index.md\n\ndependencies (2 files):\n - package-lock.json\n - package.json\n\ntests (2 files):\n - src/context-manager.test.ts\n - src/tools.test.ts\n\nsource:src (5 files):\n - src/context-manager.ts\n - src/index.ts\n - src/logger.ts\n - src/tools.ts\n - src/types.ts\n\nSuggestion: These 5 groups might be better as separate commits if they represent different concerns.",
|
|
98
|
+
"name": "group_files_by_concern"
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"role": "tool",
|
|
102
|
+
"tool_call_id": "call_ME9YDGms0T5nK8MN8Q6Ozbj5",
|
|
103
|
+
"content": "Tool execution failed: Tool \"get_recent_commits\" execution failed: Failed to get recent commits: Command failed: git log --format=\"%h - %s (%an, %ar)\" --since=\"1 week ago\" -n 5 -- package.json src/index.ts src/tools.ts src/context-manager.ts\nfatal: your current branch 'main' does not have any commits yet\n",
|
|
104
|
+
"name": "get_recent_commits"
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"role": "assistant",
|
|
108
|
+
"content": "COMMIT_MESSAGE:\nfeat: add ToolRegistry and ContextManager core APIs\n\nSUGGESTED_SPLITS:\nSplit 1:\nFiles: [src/tools.ts, src/context-manager.ts, src/logger.ts, src/types.ts, src/index.ts]\nRationale: Core library implementation and public exports; logically cohesive feature work.\nMessage: feat: add ToolRegistry and ContextManager core APIs\n\nSplit 2:\nFiles: [src/tools.test.ts, src/context-manager.test.ts, vitest.config.ts]\nRationale: Test coverage and vitest configuration directly validating the new core APIs; should be reviewable separately from implementation.\nMessage: test: add vitest coverage for tool registry and context manager\n\nSplit 3:\nFiles: [package.json, package-lock.json, tsconfig.json, vite.config.ts, eslint.config.mjs, .npmignore, .npmrc, .nvmrc]\nRationale: Build/publish/lint toolchain and package metadata needed to compile and ship the library; mostly config/deps.\nMessage: chore: set up package build, lint, and publish configuration\n\nSplit 4:\nFiles: [README.md, guide/index.md, LICENSE]\nRationale: Documentation and licensing; independent of implementation details.\nMessage: docs: add README, agent guide, and Apache-2.0 license\n\nSplit 5:\nFiles: [.github/workflows/test.yml, .github/workflows/npm-publish.yml, .github/dependabot.yml]\nRationale: CI/CD and dependency automation; separate operational concern from library code.\nMessage: chore(ci): add test workflow, npm publish workflow, and dependabot\n\nSplit 6:\nFiles: [.gitignore, .kodrdriv-test-cache.json]\nRationale: Local tool cache shouldn’t be versioned; keep repo clean by ignoring it (and drop the tracked cache file).\nMessage: chore: ignore local kodrdriv test cache output"
|
|
109
|
+
}
|
|
110
|
+
]
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
</details>
|
|
114
|
+
|
|
115
|
+
## Generated Commit Message
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
feat: add ToolRegistry and ContextManager core APIs
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Suggested Commit Splits
|
|
122
|
+
|
|
123
|
+
### Split 1
|
|
124
|
+
|
|
125
|
+
**Files**: [src/tools.ts, src/context-manager.ts, src/logger.ts, src/types.ts, src/index.ts]
|
|
126
|
+
|
|
127
|
+
**Rationale**: Core library implementation and public exports; logically cohesive feature work.
|
|
128
|
+
|
|
129
|
+
**Message**:
|
|
130
|
+
```
|
|
131
|
+
feat: add ToolRegistry and ContextManager core APIs
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Split 2
|
|
135
|
+
|
|
136
|
+
**Files**: [src/tools.test.ts, src/context-manager.test.ts, vitest.config.ts]
|
|
137
|
+
|
|
138
|
+
**Rationale**: Test coverage and vitest configuration directly validating the new core APIs; should be reviewable separately from implementation.
|
|
139
|
+
|
|
140
|
+
**Message**:
|
|
141
|
+
```
|
|
142
|
+
test: add vitest coverage for tool registry and context manager
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Split 3
|
|
146
|
+
|
|
147
|
+
**Files**: [package.json, package-lock.json, tsconfig.json, vite.config.ts, eslint.config.mjs, .npmignore, .npmrc, .nvmrc]
|
|
148
|
+
|
|
149
|
+
**Rationale**: Build/publish/lint toolchain and package metadata needed to compile and ship the library; mostly config/deps.
|
|
150
|
+
|
|
151
|
+
**Message**:
|
|
152
|
+
```
|
|
153
|
+
chore: set up package build, lint, and publish configuration
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Split 4
|
|
157
|
+
|
|
158
|
+
**Files**: [README.md, guide/index.md, LICENSE]
|
|
159
|
+
|
|
160
|
+
**Rationale**: Documentation and licensing; independent of implementation details.
|
|
161
|
+
|
|
162
|
+
**Message**:
|
|
163
|
+
```
|
|
164
|
+
docs: add README, agent guide, and Apache-2.0 license
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Split 5
|
|
168
|
+
|
|
169
|
+
**Files**: [.github/workflows/test.yml, .github/workflows/npm-publish.yml, .github/dependabot.yml]
|
|
170
|
+
|
|
171
|
+
**Rationale**: CI/CD and dependency automation; separate operational concern from library code.
|
|
172
|
+
|
|
173
|
+
**Message**:
|
|
174
|
+
```
|
|
175
|
+
chore(ci): add test workflow, npm publish workflow, and dependabot
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Split 6
|
|
179
|
+
|
|
180
|
+
**Files**: [.gitignore, .kodrdriv-test-cache.json]
|
|
181
|
+
|
|
182
|
+
**Rationale**: Local tool cache shouldn’t be versioned; keep repo clean by ignoring it (and drop the tracked cache file).
|
|
183
|
+
|
|
184
|
+
**Message**:
|
|
185
|
+
```
|
|
186
|
+
chore: ignore local kodrdriv test cache output
|
|
187
|
+
```
|