context-stash 1.0.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/LICENSE +21 -0
- package/README.md +442 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +67 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +10 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +268 -0
- package/dist/server.js.map +1 -0
- package/dist/types.d.ts +34 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +157 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +34 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +128 -0
- package/dist/utils.js.map +1 -0
- package/package.json +55 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mark Taylor
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
# context-stash
|
|
2
|
+
|
|
3
|
+
A workspace-scoped MCP server that provides a durable, Git-native memory layer for AI coding agents.
|
|
4
|
+
|
|
5
|
+
> **The missing piece in AI-assisted software development**: Give AI agents persistent memory of *why* changes were made.
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
**context-stash** stores atomic context entries as Markdown files inside a `.context/` directory at the root of your project. AI agents reference these entries in code comments (e.g., `// refer to context 00012`) to preserve reasoning and avoid reintroducing historical bugs.
|
|
10
|
+
|
|
11
|
+
## Why context-stash?
|
|
12
|
+
|
|
13
|
+
### The Problem: AI Forgets
|
|
14
|
+
|
|
15
|
+
Every AI coding assistant today suffers from the same flaw: **no persistent memory of past decisions**. This leads to:
|
|
16
|
+
|
|
17
|
+
- š Reintroduced bugs the AI fixed yesterday
|
|
18
|
+
- š¤ Repeated questions about architectural decisions
|
|
19
|
+
- š„ Breaking changes that ignore historical constraints
|
|
20
|
+
- š Lost context between sessions, models, and tools
|
|
21
|
+
|
|
22
|
+
### The Solution: A Reasoning Ledger
|
|
23
|
+
|
|
24
|
+
context-stash creates a **distributed, code-embedded reference system** that works like:
|
|
25
|
+
|
|
26
|
+
- Git commit messages (explains *what* changed)
|
|
27
|
+
- Architectural Decision Records (explains *why* it changed)
|
|
28
|
+
- Inline documentation (stays with the code)
|
|
29
|
+
- AI reasoning logs (durable across sessions)
|
|
30
|
+
|
|
31
|
+
**All unified into a single, simple mechanism.**
|
|
32
|
+
|
|
33
|
+
### Why It Works
|
|
34
|
+
|
|
35
|
+
š§ **Persistent Memory** - Context survives sessions, models, and tools
|
|
36
|
+
š **Cross-File References** - One decision can explain changes in multiple files
|
|
37
|
+
š **Human-Readable** - Markdown files anyone can read and audit
|
|
38
|
+
š **Immutable by Design** - Historical reasoning stays accurate
|
|
39
|
+
š **Universal** - Works with any MCP-compatible AI agent
|
|
40
|
+
š **Git-Native** - Version control for both code *and* reasoning
|
|
41
|
+
|
|
42
|
+
## What Makes It Different
|
|
43
|
+
|
|
44
|
+
### A Shared Language Between Humans and AI
|
|
45
|
+
|
|
46
|
+
Most AI tooling is either:
|
|
47
|
+
- **AI-only** (humans can't read it), or
|
|
48
|
+
- **Human-only** (AI ignores it)
|
|
49
|
+
|
|
50
|
+
context-stash is both:
|
|
51
|
+
- āļø **AI-generated** - Agents create context automatically
|
|
52
|
+
- š¤ **AI-consumable** - Agents retrieve it on demand
|
|
53
|
+
- šļø **Human-readable** - Plain Markdown anyone can understand
|
|
54
|
+
- āļø **Human-editable** - Edit manually when needed
|
|
55
|
+
- š **Git-tracked** - Full history of reasoning over time
|
|
56
|
+
|
|
57
|
+
### Simple, Standard-Ready Design
|
|
58
|
+
|
|
59
|
+
Like `.gitignore`, `.editorconfig`, and `README.md`, context-stash is:
|
|
60
|
+
- Small, obvious, and useful
|
|
61
|
+
- Editor-agnostic and model-agnostic
|
|
62
|
+
- Easy to adopt, easy to ignore
|
|
63
|
+
- Non-intrusive to existing workflows
|
|
64
|
+
|
|
65
|
+
**This is how standards emerge.**
|
|
66
|
+
|
|
67
|
+
## Links
|
|
68
|
+
|
|
69
|
+
- š **Website**: [https://nzmarktaylor.github.io/context-stash/](https://nzmarktaylor.github.io/context-stash/)
|
|
70
|
+
- š¦ **npm**: [https://www.npmjs.com/package/context-stash](https://www.npmjs.com/package/context-stash)
|
|
71
|
+
- š» **GitHub**: [https://github.com/nzmarktaylor/context-stash](https://github.com/nzmarktaylor/context-stash)
|
|
72
|
+
- š **Examples**: [EXAMPLES.md](EXAMPLES.md)
|
|
73
|
+
- š§ **Development**: [DEVELOPMENT.md](DEVELOPMENT.md)
|
|
74
|
+
|
|
75
|
+
## Features
|
|
76
|
+
|
|
77
|
+
- š§ **Persistent Memory** - Store reasoning, decisions, and context across AI coding sessions
|
|
78
|
+
- š **Immutable Entries** - Context entries are write-once, preventing accidental modifications
|
|
79
|
+
- š¦ **Git-Native** - Plain Markdown files that work seamlessly with version control
|
|
80
|
+
- š **Search & Discovery** - Find relevant context through keyword search
|
|
81
|
+
- š ļø **MCP Compatible** - Works with any MCP-compatible AI coding agent
|
|
82
|
+
- ā” **Zero Config** - Sensible defaults with optional customization
|
|
83
|
+
|
|
84
|
+
## Installation
|
|
85
|
+
|
|
86
|
+
### Global Installation (Recommended)
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
npm install -g context-stash
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Project-Specific Installation
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
npm install --save-dev context-stash
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Quick Start
|
|
99
|
+
|
|
100
|
+
### 1. Initialize Your Workspace
|
|
101
|
+
|
|
102
|
+
Navigate to your project root and run:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
context-stash --init
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
This creates:
|
|
109
|
+
- `.context/` directory for storing context entries
|
|
110
|
+
- `.context/config.json` with default configuration
|
|
111
|
+
- `agents.md` with usage instructions for AI agents
|
|
112
|
+
|
|
113
|
+
### 2. Configure Your AI Agent
|
|
114
|
+
|
|
115
|
+
Add context-stash to your AI agent's MCP server configuration. For example, in Claude Desktop's config:
|
|
116
|
+
|
|
117
|
+
```json
|
|
118
|
+
{
|
|
119
|
+
"mcpServers": {
|
|
120
|
+
"context-stash": {
|
|
121
|
+
"command": "context-stash",
|
|
122
|
+
"args": ["--serve"],
|
|
123
|
+
"cwd": "/path/to/your/project"
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### 3. Start Using Context
|
|
130
|
+
|
|
131
|
+
Your AI agent can now:
|
|
132
|
+
- **Create context entries**: `context.create` with Markdown content
|
|
133
|
+
- **Retrieve entries**: `context.get` with a reference number
|
|
134
|
+
- **List all entries**: `context.list`
|
|
135
|
+
- **Search entries**: `context.search` with a keyword
|
|
136
|
+
|
|
137
|
+
## Usage Pattern
|
|
138
|
+
|
|
139
|
+
### Creating Context Entries
|
|
140
|
+
|
|
141
|
+
When making a meaningful code change, create a context entry:
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
Agent calls: context.create
|
|
145
|
+
Input: { "markdown": "# Fix: Null Pointer in User Service\n\nAdded null check before accessing user.profile..." }
|
|
146
|
+
Output: { "file": "00012.md" }
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Referencing Context in Code
|
|
150
|
+
|
|
151
|
+
Add comments to reference context:
|
|
152
|
+
|
|
153
|
+
```javascript
|
|
154
|
+
// refer to context 00012
|
|
155
|
+
function getUserProfile(user) {
|
|
156
|
+
if (!user || !user.profile) return null;
|
|
157
|
+
return user.profile;
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Multiple References
|
|
162
|
+
|
|
163
|
+
A single context entry can be referenced in multiple files, and code can reference multiple entries:
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
# refer to context 00012, 00019, 00101
|
|
167
|
+
def process_user_data(user):
|
|
168
|
+
# ...
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## MCP Tools
|
|
172
|
+
|
|
173
|
+
### `context.create`
|
|
174
|
+
|
|
175
|
+
Create a new immutable context entry.
|
|
176
|
+
|
|
177
|
+
**Input:**
|
|
178
|
+
```json
|
|
179
|
+
{
|
|
180
|
+
"markdown": "string (max 50 lines by default)"
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**Output:**
|
|
185
|
+
```json
|
|
186
|
+
{
|
|
187
|
+
"file": "00012.md"
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### `context.get`
|
|
192
|
+
|
|
193
|
+
Retrieve a context entry by reference.
|
|
194
|
+
|
|
195
|
+
**Input:**
|
|
196
|
+
```json
|
|
197
|
+
{
|
|
198
|
+
"ref": "00012"
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
**Output:**
|
|
203
|
+
```json
|
|
204
|
+
{
|
|
205
|
+
"markdown": "# Fix: Null Pointer in User Service\n..."
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### `context.list`
|
|
210
|
+
|
|
211
|
+
List all context entries.
|
|
212
|
+
|
|
213
|
+
**Output:**
|
|
214
|
+
```json
|
|
215
|
+
{
|
|
216
|
+
"entries": [
|
|
217
|
+
{ "ref": "00001", "file": "00001.md" },
|
|
218
|
+
{ "ref": "00002", "file": "00002.md" }
|
|
219
|
+
]
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### `context.search`
|
|
224
|
+
|
|
225
|
+
Search context entries by keyword.
|
|
226
|
+
|
|
227
|
+
**Input:**
|
|
228
|
+
```json
|
|
229
|
+
{
|
|
230
|
+
"query": "null pointer"
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
**Output:**
|
|
235
|
+
```json
|
|
236
|
+
{
|
|
237
|
+
"results": [
|
|
238
|
+
{
|
|
239
|
+
"ref": "00012",
|
|
240
|
+
"file": "00012.md",
|
|
241
|
+
"snippet": "Added null check before accessing user.profile..."
|
|
242
|
+
}
|
|
243
|
+
]
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## Configuration
|
|
248
|
+
|
|
249
|
+
The `.context/config.json` file controls behavior:
|
|
250
|
+
|
|
251
|
+
```json
|
|
252
|
+
{
|
|
253
|
+
"maxLines": 50,
|
|
254
|
+
"startIndex": 1,
|
|
255
|
+
"leadingZeros": 5,
|
|
256
|
+
"filePrefix": "",
|
|
257
|
+
"fileSuffix": ".md"
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Configuration Options
|
|
262
|
+
|
|
263
|
+
| Option | Description | Default |
|
|
264
|
+
|--------|-------------|---------|
|
|
265
|
+
| `maxLines` | Maximum lines allowed per context entry | `50` |
|
|
266
|
+
| `startIndex` | First index number to use | `1` |
|
|
267
|
+
| `leadingZeros` | Number of digits in filenames | `5` |
|
|
268
|
+
| `filePrefix` | Optional filename prefix | `""` |
|
|
269
|
+
| `fileSuffix` | File extension | `".md"` |
|
|
270
|
+
|
|
271
|
+
### Custom Filename Format
|
|
272
|
+
|
|
273
|
+
Adjust the config to use custom naming:
|
|
274
|
+
|
|
275
|
+
```json
|
|
276
|
+
{
|
|
277
|
+
"filePrefix": "ctx-",
|
|
278
|
+
"fileSuffix": ".markdown",
|
|
279
|
+
"leadingZeros": 4
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
This produces filenames like: `ctx-0123.markdown`
|
|
284
|
+
|
|
285
|
+
## CLI Commands
|
|
286
|
+
|
|
287
|
+
### `--init`
|
|
288
|
+
|
|
289
|
+
Initialize a workspace with context-stash:
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
context-stash --init
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### `--serve`
|
|
296
|
+
|
|
297
|
+
Start the MCP server (typically called by your AI agent):
|
|
298
|
+
|
|
299
|
+
```bash
|
|
300
|
+
context-stash --serve
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### `--help`
|
|
304
|
+
|
|
305
|
+
Show help information:
|
|
306
|
+
|
|
307
|
+
```bash
|
|
308
|
+
context-stash --help
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
## Best Practices
|
|
312
|
+
|
|
313
|
+
### Keep Entries Atomic
|
|
314
|
+
|
|
315
|
+
Each context entry should explain one conceptual change or decision:
|
|
316
|
+
|
|
317
|
+
ā
**Good:**
|
|
318
|
+
```markdown
|
|
319
|
+
# Fix: Race Condition in Payment Processing
|
|
320
|
+
|
|
321
|
+
Added mutex lock around balance update to prevent concurrent modifications
|
|
322
|
+
that could result in incorrect balance calculations.
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
ā **Bad:**
|
|
326
|
+
```markdown
|
|
327
|
+
# Various Changes
|
|
328
|
+
|
|
329
|
+
Fixed payment bug, updated user service, refactored database layer...
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### Entries Are Immutable
|
|
333
|
+
|
|
334
|
+
Never modify existing context entries. If you need to add more information, create a new entry:
|
|
335
|
+
|
|
336
|
+
```javascript
|
|
337
|
+
// refer to context 00012, 00045
|
|
338
|
+
// Context 00012: Original fix
|
|
339
|
+
// Context 00045: Additional edge case handling
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### Use Descriptive Titles
|
|
343
|
+
|
|
344
|
+
Start each entry with a clear title:
|
|
345
|
+
|
|
346
|
+
```markdown
|
|
347
|
+
# Decision: Use PostgreSQL Instead of MongoDB
|
|
348
|
+
|
|
349
|
+
After evaluating both databases, chose PostgreSQL because...
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
### When to Create Context
|
|
353
|
+
|
|
354
|
+
Create context entries to capture:
|
|
355
|
+
|
|
356
|
+
- š **Bug Fixes**: What broke, why it broke, how you fixed it
|
|
357
|
+
- šÆ **Design Decisions**: Why you chose approach A over B
|
|
358
|
+
- ā ļø **Workarounds**: Why the "proper" solution wasn't feasible
|
|
359
|
+
- š **Refactoring**: What assumptions changed
|
|
360
|
+
- š« **Deprecated Code**: What future AI should avoid
|
|
361
|
+
- š§± **Constraints**: What limitations influenced the design
|
|
362
|
+
- š” **Intent**: What the code is *trying* to accomplish
|
|
363
|
+
|
|
364
|
+
**Think of it as a reasoning ledger, not just a change log.**
|
|
365
|
+
|
|
366
|
+
## Version Control
|
|
367
|
+
|
|
368
|
+
The `.context/` directory is designed to work with Git:
|
|
369
|
+
|
|
370
|
+
### Recommended `.gitignore`
|
|
371
|
+
|
|
372
|
+
Most teams should commit context entries:
|
|
373
|
+
|
|
374
|
+
```gitignore
|
|
375
|
+
# Don't ignore .context/ - it's valuable project documentation!
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
### When to Ignore
|
|
379
|
+
|
|
380
|
+
Only ignore if your context contains sensitive information:
|
|
381
|
+
|
|
382
|
+
```gitignore
|
|
383
|
+
.context/
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
## Requirements
|
|
387
|
+
|
|
388
|
+
- **Node.js**: >= 18.0.0
|
|
389
|
+
- **Operating System**: Windows, macOS, Linux
|
|
390
|
+
|
|
391
|
+
## Development
|
|
392
|
+
|
|
393
|
+
### Building from Source
|
|
394
|
+
|
|
395
|
+
```bash
|
|
396
|
+
git clone https://github.com/nzmarktaylor/context-stash.git
|
|
397
|
+
cd context-stash
|
|
398
|
+
npm install
|
|
399
|
+
npm run build
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
### Testing Locally
|
|
403
|
+
|
|
404
|
+
```bash
|
|
405
|
+
npm link
|
|
406
|
+
cd /path/to/test/project
|
|
407
|
+
context-stash --init
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
## Troubleshooting
|
|
411
|
+
|
|
412
|
+
### "Failed to load config" Error
|
|
413
|
+
|
|
414
|
+
Make sure you've initialized the workspace:
|
|
415
|
+
|
|
416
|
+
```bash
|
|
417
|
+
context-stash --init
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
### MCP Server Not Responding
|
|
421
|
+
|
|
422
|
+
Ensure the server is launched with the correct working directory (your project root).
|
|
423
|
+
|
|
424
|
+
### Context Entry Too Long
|
|
425
|
+
|
|
426
|
+
Reduce the markdown content or increase `maxLines` in `.context/config.json`.
|
|
427
|
+
|
|
428
|
+
## License
|
|
429
|
+
|
|
430
|
+
MIT
|
|
431
|
+
|
|
432
|
+
## Contributing
|
|
433
|
+
|
|
434
|
+
Contributions are welcome! Please open an issue or submit a pull request.
|
|
435
|
+
|
|
436
|
+
## Credits
|
|
437
|
+
|
|
438
|
+
Built with the [Model Context Protocol SDK](https://github.com/modelcontextprotocol/sdk).
|
|
439
|
+
|
|
440
|
+
---
|
|
441
|
+
|
|
442
|
+
**Note**: This is a workspace-scoped MCP server. It must be launched per workspace, not globally, and is sandboxed to the workspace root for security.
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { initializeWorkspace } from './utils.js';
|
|
3
|
+
import { startServer } from './server.js';
|
|
4
|
+
const args = process.argv.slice(2);
|
|
5
|
+
// refer to context 00001
|
|
6
|
+
async function main() {
|
|
7
|
+
if (args.includes('--init')) {
|
|
8
|
+
// Initialize workspace
|
|
9
|
+
const workspaceRoot = process.cwd();
|
|
10
|
+
await initializeWorkspace(workspaceRoot);
|
|
11
|
+
}
|
|
12
|
+
else if (args.includes('--serve')) {
|
|
13
|
+
// Start MCP server
|
|
14
|
+
await startServer();
|
|
15
|
+
}
|
|
16
|
+
else if (args.includes('--help') || args.includes('-h')) {
|
|
17
|
+
// Show help
|
|
18
|
+
console.log(`
|
|
19
|
+
context-stash - A workspace-scoped MCP server for AI coding agents
|
|
20
|
+
|
|
21
|
+
USAGE:
|
|
22
|
+
context-stash --init Initialize workspace with .context/ directory
|
|
23
|
+
context-stash --serve Start the MCP server (stdio transport)
|
|
24
|
+
context-stash --help Show this help message
|
|
25
|
+
|
|
26
|
+
DESCRIPTION:
|
|
27
|
+
context-stash provides a durable, Git-native memory layer for AI coding
|
|
28
|
+
agents. It stores atomic context entries as Markdown files inside a
|
|
29
|
+
.context/ directory at the root of your project.
|
|
30
|
+
|
|
31
|
+
INITIALIZATION:
|
|
32
|
+
Run 'context-stash --init' in your project root to create:
|
|
33
|
+
- .context/ Directory for context entries
|
|
34
|
+
- .context/config.json Configuration file
|
|
35
|
+
- agents.md Usage instructions for AI agents
|
|
36
|
+
|
|
37
|
+
MCP SERVER:
|
|
38
|
+
The MCP server provides four tools:
|
|
39
|
+
- context.create Create a new context entry
|
|
40
|
+
- context.get Retrieve a context entry by reference
|
|
41
|
+
- context.list List all context entries
|
|
42
|
+
- context.search Search context entries by keyword
|
|
43
|
+
|
|
44
|
+
The server must be launched with the workspace root as the working directory.
|
|
45
|
+
|
|
46
|
+
EXAMPLES:
|
|
47
|
+
# Initialize a new workspace
|
|
48
|
+
cd /path/to/your/project
|
|
49
|
+
context-stash --init
|
|
50
|
+
|
|
51
|
+
# Start the MCP server (typically configured in your AI agent)
|
|
52
|
+
context-stash --serve
|
|
53
|
+
|
|
54
|
+
MORE INFO:
|
|
55
|
+
https://github.com/yourusername/context-stash
|
|
56
|
+
`);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
console.error('Unknown command. Use --help for usage information.');
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
main().catch((error) => {
|
|
64
|
+
console.error('Error:', error.message);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
});
|
|
67
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAEnC,yBAAyB;AACzB,KAAK,UAAU,IAAI;IACjB,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC5B,uBAAuB;QACvB,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;QACpC,MAAM,mBAAmB,CAAC,aAAa,CAAC,CAAC;IAC3C,CAAC;SAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QACpC,mBAAmB;QACnB,MAAM,WAAW,EAAE,CAAC;IACtB,CAAC;SAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAC1D,YAAY;QACZ,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsCf,CAAC,CAAC;IACD,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,KAAK,CAAC,oDAAoD,CAAC,CAAC;QACpE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IACvC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACzE,cAAc,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACzE,cAAc,YAAY,CAAC"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Create and configure the MCP server
|
|
4
|
+
*/
|
|
5
|
+
export declare function createServer(): Server;
|
|
6
|
+
/**
|
|
7
|
+
* Start the MCP server
|
|
8
|
+
*/
|
|
9
|
+
export declare function startServer(): Promise<void>;
|
|
10
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAqBnE;;GAEG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAyGrC;AAoLD;;GAEG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAOjD"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
4
|
+
import { promises as fs } from 'fs';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
import { loadConfig, formatFilename, extractRef, getNextIndex, countLines, validatePath, } from './utils.js';
|
|
7
|
+
const CONTEXT_DIR = '.context';
|
|
8
|
+
const workspaceRoot = process.cwd();
|
|
9
|
+
/**
|
|
10
|
+
* Create and configure the MCP server
|
|
11
|
+
*/
|
|
12
|
+
export function createServer() {
|
|
13
|
+
const server = new Server({
|
|
14
|
+
name: 'context-stash',
|
|
15
|
+
version: '1.0.0',
|
|
16
|
+
}, {
|
|
17
|
+
capabilities: {
|
|
18
|
+
tools: {},
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
// List available tools
|
|
22
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
23
|
+
return {
|
|
24
|
+
tools: [
|
|
25
|
+
{
|
|
26
|
+
name: 'context.create',
|
|
27
|
+
description: 'Create a new immutable context entry',
|
|
28
|
+
inputSchema: {
|
|
29
|
+
type: 'object',
|
|
30
|
+
properties: {
|
|
31
|
+
markdown: {
|
|
32
|
+
type: 'string',
|
|
33
|
+
description: 'Markdown content for the context entry',
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
required: ['markdown'],
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: 'context.get',
|
|
41
|
+
description: 'Retrieve the contents of a context entry',
|
|
42
|
+
inputSchema: {
|
|
43
|
+
type: 'object',
|
|
44
|
+
properties: {
|
|
45
|
+
ref: {
|
|
46
|
+
type: 'string',
|
|
47
|
+
description: 'Reference number (e.g., "00012")',
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
required: ['ref'],
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'context.list',
|
|
55
|
+
description: 'List all context entries',
|
|
56
|
+
inputSchema: {
|
|
57
|
+
type: 'object',
|
|
58
|
+
properties: {},
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: 'context.search',
|
|
63
|
+
description: 'Search context entries by keyword',
|
|
64
|
+
inputSchema: {
|
|
65
|
+
type: 'object',
|
|
66
|
+
properties: {
|
|
67
|
+
query: {
|
|
68
|
+
type: 'string',
|
|
69
|
+
description: 'Search query string',
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
required: ['query'],
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
// Handle tool calls
|
|
79
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
80
|
+
try {
|
|
81
|
+
switch (request.params.name) {
|
|
82
|
+
case 'context.create':
|
|
83
|
+
return await handleCreate(request.params.arguments);
|
|
84
|
+
case 'context.get':
|
|
85
|
+
return await handleGet(request.params.arguments);
|
|
86
|
+
case 'context.list':
|
|
87
|
+
return await handleList();
|
|
88
|
+
case 'context.search':
|
|
89
|
+
return await handleSearch(request.params.arguments);
|
|
90
|
+
default:
|
|
91
|
+
throw new Error(`Unknown tool: ${request.params.name}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
96
|
+
return {
|
|
97
|
+
content: [
|
|
98
|
+
{
|
|
99
|
+
type: 'text',
|
|
100
|
+
text: `Error: ${message}`,
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
isError: true,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
return server;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Handle context.create tool
|
|
111
|
+
*/
|
|
112
|
+
async function handleCreate(args) {
|
|
113
|
+
const { markdown } = args;
|
|
114
|
+
if (typeof markdown !== 'string') {
|
|
115
|
+
throw new Error('markdown must be a string');
|
|
116
|
+
}
|
|
117
|
+
// Load config
|
|
118
|
+
const config = await loadConfig(workspaceRoot);
|
|
119
|
+
// Validate line count
|
|
120
|
+
const lineCount = countLines(markdown);
|
|
121
|
+
if (lineCount > config.maxLines) {
|
|
122
|
+
throw new Error(`Markdown exceeds maximum of ${config.maxLines} lines (got ${lineCount} lines)`);
|
|
123
|
+
}
|
|
124
|
+
// Get next index and format filename
|
|
125
|
+
const index = await getNextIndex(workspaceRoot, config);
|
|
126
|
+
const filename = formatFilename(index, config);
|
|
127
|
+
const filePath = path.join(workspaceRoot, CONTEXT_DIR, filename);
|
|
128
|
+
// Validate path
|
|
129
|
+
validatePath(filePath, workspaceRoot);
|
|
130
|
+
// Write file
|
|
131
|
+
await fs.writeFile(filePath, markdown, 'utf-8');
|
|
132
|
+
return {
|
|
133
|
+
content: [
|
|
134
|
+
{
|
|
135
|
+
type: 'text',
|
|
136
|
+
text: JSON.stringify({ file: filename }, null, 2),
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Handle context.get tool
|
|
143
|
+
*/
|
|
144
|
+
async function handleGet(args) {
|
|
145
|
+
const { ref } = args;
|
|
146
|
+
if (typeof ref !== 'string') {
|
|
147
|
+
throw new Error('ref must be a string');
|
|
148
|
+
}
|
|
149
|
+
// Load config
|
|
150
|
+
const config = await loadConfig(workspaceRoot);
|
|
151
|
+
// Determine filename
|
|
152
|
+
const filename = `${config.filePrefix}${ref}${config.fileSuffix}`;
|
|
153
|
+
const filePath = path.join(workspaceRoot, CONTEXT_DIR, filename);
|
|
154
|
+
// Validate path
|
|
155
|
+
validatePath(filePath, workspaceRoot);
|
|
156
|
+
// Read file
|
|
157
|
+
try {
|
|
158
|
+
const markdown = await fs.readFile(filePath, 'utf-8');
|
|
159
|
+
return {
|
|
160
|
+
content: [
|
|
161
|
+
{
|
|
162
|
+
type: 'text',
|
|
163
|
+
text: JSON.stringify({ markdown }, null, 2),
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
throw new Error(`Context entry '${ref}' not found`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Handle context.list tool
|
|
174
|
+
*/
|
|
175
|
+
async function handleList() {
|
|
176
|
+
// Load config
|
|
177
|
+
const config = await loadConfig(workspaceRoot);
|
|
178
|
+
const contextDir = path.join(workspaceRoot, CONTEXT_DIR);
|
|
179
|
+
try {
|
|
180
|
+
const files = await fs.readdir(contextDir);
|
|
181
|
+
const entries = [];
|
|
182
|
+
for (const file of files) {
|
|
183
|
+
if (file === 'config.json')
|
|
184
|
+
continue;
|
|
185
|
+
const ref = extractRef(file, config);
|
|
186
|
+
if (ref) {
|
|
187
|
+
entries.push({ ref, file });
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
// Sort by index
|
|
191
|
+
entries.sort((a, b) => {
|
|
192
|
+
const indexA = parseInt(a.ref, 10);
|
|
193
|
+
const indexB = parseInt(b.ref, 10);
|
|
194
|
+
return indexA - indexB;
|
|
195
|
+
});
|
|
196
|
+
return {
|
|
197
|
+
content: [
|
|
198
|
+
{
|
|
199
|
+
type: 'text',
|
|
200
|
+
text: JSON.stringify({ entries }, null, 2),
|
|
201
|
+
},
|
|
202
|
+
],
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
throw new Error('Failed to list context entries');
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Handle context.search tool
|
|
211
|
+
*/
|
|
212
|
+
async function handleSearch(args) {
|
|
213
|
+
const { query } = args;
|
|
214
|
+
if (typeof query !== 'string') {
|
|
215
|
+
throw new Error('query must be a string');
|
|
216
|
+
}
|
|
217
|
+
// Load config
|
|
218
|
+
const config = await loadConfig(workspaceRoot);
|
|
219
|
+
const contextDir = path.join(workspaceRoot, CONTEXT_DIR);
|
|
220
|
+
const results = [];
|
|
221
|
+
try {
|
|
222
|
+
const files = await fs.readdir(contextDir);
|
|
223
|
+
for (const file of files) {
|
|
224
|
+
if (file === 'config.json')
|
|
225
|
+
continue;
|
|
226
|
+
const ref = extractRef(file, config);
|
|
227
|
+
if (!ref)
|
|
228
|
+
continue;
|
|
229
|
+
const filePath = path.join(contextDir, file);
|
|
230
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
231
|
+
// Simple case-insensitive search
|
|
232
|
+
const lines = content.split('\n');
|
|
233
|
+
const lowerQuery = query.toLowerCase();
|
|
234
|
+
for (const line of lines) {
|
|
235
|
+
if (line.toLowerCase().includes(lowerQuery)) {
|
|
236
|
+
results.push({
|
|
237
|
+
ref,
|
|
238
|
+
file,
|
|
239
|
+
snippet: line.trim(),
|
|
240
|
+
});
|
|
241
|
+
break; // Only include first match per file
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
content: [
|
|
247
|
+
{
|
|
248
|
+
type: 'text',
|
|
249
|
+
text: JSON.stringify({ results }, null, 2),
|
|
250
|
+
},
|
|
251
|
+
],
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
throw new Error('Failed to search context entries');
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Start the MCP server
|
|
260
|
+
*/
|
|
261
|
+
export async function startServer() {
|
|
262
|
+
const server = createServer();
|
|
263
|
+
const transport = new StdioServerTransport();
|
|
264
|
+
await server.connect(transport);
|
|
265
|
+
// Keep process running
|
|
266
|
+
process.stdin.resume();
|
|
267
|
+
}
|
|
268
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration for context-stash
|
|
3
|
+
*/
|
|
4
|
+
export interface Config {
|
|
5
|
+
maxLines: number;
|
|
6
|
+
startIndex: number;
|
|
7
|
+
leadingZeros: number;
|
|
8
|
+
filePrefix: string;
|
|
9
|
+
fileSuffix: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Represents a context entry reference
|
|
13
|
+
*/
|
|
14
|
+
export interface ContextEntry {
|
|
15
|
+
ref: string;
|
|
16
|
+
file: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Result of a context search
|
|
20
|
+
*/
|
|
21
|
+
export interface SearchResult {
|
|
22
|
+
ref: string;
|
|
23
|
+
file: string;
|
|
24
|
+
snippet: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Default configuration values
|
|
28
|
+
*/
|
|
29
|
+
export declare const DEFAULT_CONFIG: Config;
|
|
30
|
+
/**
|
|
31
|
+
* Default agents.md content
|
|
32
|
+
*/
|
|
33
|
+
export declare const AGENTS_MD_CONTENT = "# Context Stash (MCP Tool)\n\nThis project uses **context\u2011stash**, a workspace\u2011local MCP server that stores durable reasoning context for AI coding agents.\n\n## Purpose\n\nContext entries explain *why* code changes were made. They help prevent reintroducing historical bugs and allow AI agents to understand the intent behind modifications.\n\n## Usage Pattern\n\n- When making a meaningful code change, call the MCP tool `context.create` with a short Markdown explanation.\n- The tool returns a reference number (e.g., `00012`).\n- Insert comments in relevant files such as:\n\n```\n// refer to context 00012\n```\n\n- Context entries are **immutable**. If new reasoning is needed, create a new entry rather than modifying an old one.\n- A single context entry may be referenced in many files.\n- Code may reference multiple entries:\n\n```\n// refer to context 00012, 00019, 00101\n```\n\n## When to Create Context\n\nCreate context entries for:\n\n- **Bug Fixes**: What broke, why it broke, and how you fixed it\n- **Design Decisions**: Why you chose approach A over B\n- **Workarounds**: Why the \"proper\" solution wasn't feasible\n- **Refactoring**: What assumptions changed and why\n- **Constraints**: What limitations influenced the design\n- **Deprecated Code**: What future AI should avoid and why\n- **Intent Clarification**: What the code is trying to accomplish beyond what's obvious\n\n**Do NOT create context for:**\n- Trivial changes (formatting, typos)\n- Self-explanatory code changes\n- Temporary experiments\n- Changes that are fully explained by existing documentation\n\n## Writing Good Context\n\n### Use Clear Structure\n\n```markdown\n# Brief Title (Bug Fix / Decision / Workaround)\n\n## Problem / Context\nWhat situation led to this change?\n\n## Solution / Decision\nWhat did you do and why?\n\n## Alternatives Considered (optional)\nWhat other approaches were rejected and why?\n\n## Impact (optional)\nWhat files/systems does this affect?\n```\n\n### Be Concise But Complete\n\n- Max 50 lines by default (configurable)\n- Focus on **WHY**, not just WHAT\n- Include enough detail that future AI can understand the reasoning\n- Avoid unnecessary verbosity\n\n### Think Cross-Session\n\nRemember: You might not be the AI agent that reads this later. Write for:\n- Different AI models\n- Future versions of yourself\n- Human developers reviewing code\n- Team members unfamiliar with the change\n\n## Best Practices for AI Agents\n\n### Before Making Changes\n\n1. **Search existing context**: Use `context.search` to check if similar issues were addressed\n2. **List recent context**: Use `context.list` to see what decisions were made recently\n3. **Read referenced context**: If you see `// refer to context XXXXX`, call `context.get` to understand WHY the code exists\n\n### When Making Changes\n\n1. **Create context FIRST** if the change needs explanation\n2. **Add references IMMEDIATELY** after creating context\n3. **Be consistent** with comment format: `// refer to context 00012`\n\n### Important Reminders\n\n- **NEVER edit existing context files** in `.context/` - they are immutable\n- **ALWAYS reference context** when the reasoning isn't obvious from the code alone\n- **CHECK for existing context** before assuming you know the full story\n- **CREATE new context** if you discover additional information about an old decision\n\n## Project-Specific Guidelines\n\n### TypeScript Project\n\nThis is a TypeScript project targeting Node.js 18+. When working with this codebase:\n\n- Use explicit types for function parameters and return values\n- Prefer `async/await` over raw promises\n- Use ES modules (`import/export`) not CommonJS\n- Follow existing code style and conventions\n- Add JSDoc comments for public APIs\n\n### MCP Server Implementation\n\nKey architectural decisions for this project:\n\n- **Stdio transport**: The server uses stdio for communication (not TCP/HTTP)\n- **Workspace-scoped**: All operations must be sandboxed to `process.cwd()`\n- **Path validation**: Always validate paths to prevent directory traversal\n- **Immutability**: Context entries are write-once by design\n- **Simplicity**: The server should stay \"dumb\" - no AI interpretation, just storage\n\n### Security Considerations\n\n- Never allow path traversal outside workspace root\n- No network operations (stdio only)\n- Validate all user inputs\n- Keep dependencies minimal\n\n## MCP Server\n\nTo enable this tool, configure your agent to launch:\n\n```\ncontext-stash --serve\n```\n\nThe server must be launched with the workspace root as the working directory.\n\n## Questions?\n\nIf you're unsure whether to create context, err on the side of **creating it**. It's better to have too much reasoning documented than too little.\n";
|
|
34
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,MAAM;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,MAM5B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,iBAAiB,0qJA8I7B,CAAC"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default configuration values
|
|
3
|
+
*/
|
|
4
|
+
export const DEFAULT_CONFIG = {
|
|
5
|
+
maxLines: 50,
|
|
6
|
+
startIndex: 1,
|
|
7
|
+
leadingZeros: 5,
|
|
8
|
+
filePrefix: "",
|
|
9
|
+
fileSuffix: ".md"
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Default agents.md content
|
|
13
|
+
*/
|
|
14
|
+
export const AGENTS_MD_CONTENT = `# Context Stash (MCP Tool)
|
|
15
|
+
|
|
16
|
+
This project uses **contextāstash**, a workspaceālocal MCP server that stores durable reasoning context for AI coding agents.
|
|
17
|
+
|
|
18
|
+
## Purpose
|
|
19
|
+
|
|
20
|
+
Context entries explain *why* code changes were made. They help prevent reintroducing historical bugs and allow AI agents to understand the intent behind modifications.
|
|
21
|
+
|
|
22
|
+
## Usage Pattern
|
|
23
|
+
|
|
24
|
+
- When making a meaningful code change, call the MCP tool \`context.create\` with a short Markdown explanation.
|
|
25
|
+
- The tool returns a reference number (e.g., \`00012\`).
|
|
26
|
+
- Insert comments in relevant files such as:
|
|
27
|
+
|
|
28
|
+
\`\`\`
|
|
29
|
+
// refer to context 00012
|
|
30
|
+
\`\`\`
|
|
31
|
+
|
|
32
|
+
- Context entries are **immutable**. If new reasoning is needed, create a new entry rather than modifying an old one.
|
|
33
|
+
- A single context entry may be referenced in many files.
|
|
34
|
+
- Code may reference multiple entries:
|
|
35
|
+
|
|
36
|
+
\`\`\`
|
|
37
|
+
// refer to context 00012, 00019, 00101
|
|
38
|
+
\`\`\`
|
|
39
|
+
|
|
40
|
+
## When to Create Context
|
|
41
|
+
|
|
42
|
+
Create context entries for:
|
|
43
|
+
|
|
44
|
+
- **Bug Fixes**: What broke, why it broke, and how you fixed it
|
|
45
|
+
- **Design Decisions**: Why you chose approach A over B
|
|
46
|
+
- **Workarounds**: Why the "proper" solution wasn't feasible
|
|
47
|
+
- **Refactoring**: What assumptions changed and why
|
|
48
|
+
- **Constraints**: What limitations influenced the design
|
|
49
|
+
- **Deprecated Code**: What future AI should avoid and why
|
|
50
|
+
- **Intent Clarification**: What the code is trying to accomplish beyond what's obvious
|
|
51
|
+
|
|
52
|
+
**Do NOT create context for:**
|
|
53
|
+
- Trivial changes (formatting, typos)
|
|
54
|
+
- Self-explanatory code changes
|
|
55
|
+
- Temporary experiments
|
|
56
|
+
- Changes that are fully explained by existing documentation
|
|
57
|
+
|
|
58
|
+
## Writing Good Context
|
|
59
|
+
|
|
60
|
+
### Use Clear Structure
|
|
61
|
+
|
|
62
|
+
\`\`\`markdown
|
|
63
|
+
# Brief Title (Bug Fix / Decision / Workaround)
|
|
64
|
+
|
|
65
|
+
## Problem / Context
|
|
66
|
+
What situation led to this change?
|
|
67
|
+
|
|
68
|
+
## Solution / Decision
|
|
69
|
+
What did you do and why?
|
|
70
|
+
|
|
71
|
+
## Alternatives Considered (optional)
|
|
72
|
+
What other approaches were rejected and why?
|
|
73
|
+
|
|
74
|
+
## Impact (optional)
|
|
75
|
+
What files/systems does this affect?
|
|
76
|
+
\`\`\`
|
|
77
|
+
|
|
78
|
+
### Be Concise But Complete
|
|
79
|
+
|
|
80
|
+
- Max 50 lines by default (configurable)
|
|
81
|
+
- Focus on **WHY**, not just WHAT
|
|
82
|
+
- Include enough detail that future AI can understand the reasoning
|
|
83
|
+
- Avoid unnecessary verbosity
|
|
84
|
+
|
|
85
|
+
### Think Cross-Session
|
|
86
|
+
|
|
87
|
+
Remember: You might not be the AI agent that reads this later. Write for:
|
|
88
|
+
- Different AI models
|
|
89
|
+
- Future versions of yourself
|
|
90
|
+
- Human developers reviewing code
|
|
91
|
+
- Team members unfamiliar with the change
|
|
92
|
+
|
|
93
|
+
## Best Practices for AI Agents
|
|
94
|
+
|
|
95
|
+
### Before Making Changes
|
|
96
|
+
|
|
97
|
+
1. **Search existing context**: Use \`context.search\` to check if similar issues were addressed
|
|
98
|
+
2. **List recent context**: Use \`context.list\` to see what decisions were made recently
|
|
99
|
+
3. **Read referenced context**: If you see \`// refer to context XXXXX\`, call \`context.get\` to understand WHY the code exists
|
|
100
|
+
|
|
101
|
+
### When Making Changes
|
|
102
|
+
|
|
103
|
+
1. **Create context FIRST** if the change needs explanation
|
|
104
|
+
2. **Add references IMMEDIATELY** after creating context
|
|
105
|
+
3. **Be consistent** with comment format: \`// refer to context 00012\`
|
|
106
|
+
|
|
107
|
+
### Important Reminders
|
|
108
|
+
|
|
109
|
+
- **NEVER edit existing context files** in \`.context/\` - they are immutable
|
|
110
|
+
- **ALWAYS reference context** when the reasoning isn't obvious from the code alone
|
|
111
|
+
- **CHECK for existing context** before assuming you know the full story
|
|
112
|
+
- **CREATE new context** if you discover additional information about an old decision
|
|
113
|
+
|
|
114
|
+
## Project-Specific Guidelines
|
|
115
|
+
|
|
116
|
+
### TypeScript Project
|
|
117
|
+
|
|
118
|
+
This is a TypeScript project targeting Node.js 18+. When working with this codebase:
|
|
119
|
+
|
|
120
|
+
- Use explicit types for function parameters and return values
|
|
121
|
+
- Prefer \`async/await\` over raw promises
|
|
122
|
+
- Use ES modules (\`import/export\`) not CommonJS
|
|
123
|
+
- Follow existing code style and conventions
|
|
124
|
+
- Add JSDoc comments for public APIs
|
|
125
|
+
|
|
126
|
+
### MCP Server Implementation
|
|
127
|
+
|
|
128
|
+
Key architectural decisions for this project:
|
|
129
|
+
|
|
130
|
+
- **Stdio transport**: The server uses stdio for communication (not TCP/HTTP)
|
|
131
|
+
- **Workspace-scoped**: All operations must be sandboxed to \`process.cwd()\`
|
|
132
|
+
- **Path validation**: Always validate paths to prevent directory traversal
|
|
133
|
+
- **Immutability**: Context entries are write-once by design
|
|
134
|
+
- **Simplicity**: The server should stay "dumb" - no AI interpretation, just storage
|
|
135
|
+
|
|
136
|
+
### Security Considerations
|
|
137
|
+
|
|
138
|
+
- Never allow path traversal outside workspace root
|
|
139
|
+
- No network operations (stdio only)
|
|
140
|
+
- Validate all user inputs
|
|
141
|
+
- Keep dependencies minimal
|
|
142
|
+
|
|
143
|
+
## MCP Server
|
|
144
|
+
|
|
145
|
+
To enable this tool, configure your agent to launch:
|
|
146
|
+
|
|
147
|
+
\`\`\`
|
|
148
|
+
context-stash --serve
|
|
149
|
+
\`\`\`
|
|
150
|
+
|
|
151
|
+
The server must be launched with the workspace root as the working directory.
|
|
152
|
+
|
|
153
|
+
## Questions?
|
|
154
|
+
|
|
155
|
+
If you're unsure whether to create context, err on the side of **creating it**. It's better to have too much reasoning documented than too little.
|
|
156
|
+
`;
|
|
157
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AA4BA;;GAEG;AACH,MAAM,CAAC,MAAM,cAAc,GAAW;IACpC,QAAQ,EAAE,EAAE;IACZ,UAAU,EAAE,CAAC;IACb,YAAY,EAAE,CAAC;IACf,UAAU,EAAE,EAAE;IACd,UAAU,EAAE,KAAK;CAClB,CAAC;AAEF;;GAEG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8IhC,CAAC"}
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Config } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Load configuration from .context/config.json
|
|
4
|
+
*/
|
|
5
|
+
export declare function loadConfig(workspaceRoot: string): Promise<Config>;
|
|
6
|
+
/**
|
|
7
|
+
* Save configuration to .context/config.json
|
|
8
|
+
*/
|
|
9
|
+
export declare function saveConfig(workspaceRoot: string, config: Config): Promise<void>;
|
|
10
|
+
/**
|
|
11
|
+
* Initialize the context-stash workspace
|
|
12
|
+
*/
|
|
13
|
+
export declare function initializeWorkspace(workspaceRoot: string): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Format index as filename according to config
|
|
16
|
+
*/
|
|
17
|
+
export declare function formatFilename(index: number, config: Config): string;
|
|
18
|
+
/**
|
|
19
|
+
* Extract reference number from filename
|
|
20
|
+
*/
|
|
21
|
+
export declare function extractRef(filename: string, config: Config): string | null;
|
|
22
|
+
/**
|
|
23
|
+
* Get the next available index
|
|
24
|
+
*/
|
|
25
|
+
export declare function getNextIndex(workspaceRoot: string, config: Config): Promise<number>;
|
|
26
|
+
/**
|
|
27
|
+
* Count lines in markdown content
|
|
28
|
+
*/
|
|
29
|
+
export declare function countLines(markdown: string): number;
|
|
30
|
+
/**
|
|
31
|
+
* Validate path to prevent directory traversal
|
|
32
|
+
*/
|
|
33
|
+
export declare function validatePath(requestedPath: string, workspaceRoot: string): void;
|
|
34
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAqC,MAAM,YAAY,CAAC;AAMvE;;GAEG;AACH,wBAAsB,UAAU,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAUvE;AAED;;GAEG;AACH,wBAAsB,UAAU,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGrF;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAkC9E;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAGpE;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAU1E;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA2BzF;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,aAAa,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,IAAI,CAO/E"}
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { DEFAULT_CONFIG, AGENTS_MD_CONTENT } from './types.js';
|
|
4
|
+
const CONTEXT_DIR = '.context';
|
|
5
|
+
const CONFIG_FILE = 'config.json';
|
|
6
|
+
const AGENTS_FILE = 'agents.md';
|
|
7
|
+
/**
|
|
8
|
+
* Load configuration from .context/config.json
|
|
9
|
+
*/
|
|
10
|
+
export async function loadConfig(workspaceRoot) {
|
|
11
|
+
const configPath = path.join(workspaceRoot, CONTEXT_DIR, CONFIG_FILE);
|
|
12
|
+
try {
|
|
13
|
+
const content = await fs.readFile(configPath, 'utf-8');
|
|
14
|
+
const config = JSON.parse(content);
|
|
15
|
+
return { ...DEFAULT_CONFIG, ...config };
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
throw new Error(`Failed to load config. Run 'context-stash --init' first.`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Save configuration to .context/config.json
|
|
23
|
+
*/
|
|
24
|
+
export async function saveConfig(workspaceRoot, config) {
|
|
25
|
+
const configPath = path.join(workspaceRoot, CONTEXT_DIR, CONFIG_FILE);
|
|
26
|
+
await fs.writeFile(configPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Initialize the context-stash workspace
|
|
30
|
+
*/
|
|
31
|
+
export async function initializeWorkspace(workspaceRoot) {
|
|
32
|
+
const contextDir = path.join(workspaceRoot, CONTEXT_DIR);
|
|
33
|
+
// Create .context directory
|
|
34
|
+
try {
|
|
35
|
+
await fs.mkdir(contextDir, { recursive: true });
|
|
36
|
+
console.log(`ā Created ${CONTEXT_DIR}/ directory`);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
console.log(`ā ${CONTEXT_DIR}/ directory already exists`);
|
|
40
|
+
}
|
|
41
|
+
// Create or update config.json
|
|
42
|
+
const configPath = path.join(contextDir, CONFIG_FILE);
|
|
43
|
+
try {
|
|
44
|
+
await fs.access(configPath);
|
|
45
|
+
console.log(`ā ${CONTEXT_DIR}/${CONFIG_FILE} already exists`);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
await saveConfig(workspaceRoot, DEFAULT_CONFIG);
|
|
49
|
+
console.log(`ā Created ${CONTEXT_DIR}/${CONFIG_FILE}`);
|
|
50
|
+
}
|
|
51
|
+
// Create or update agents.md
|
|
52
|
+
const agentsPath = path.join(workspaceRoot, AGENTS_FILE);
|
|
53
|
+
try {
|
|
54
|
+
await fs.access(agentsPath);
|
|
55
|
+
console.log(`ā ${AGENTS_FILE} already exists`);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
await fs.writeFile(agentsPath, AGENTS_MD_CONTENT, 'utf-8');
|
|
59
|
+
console.log(`ā Created ${AGENTS_FILE}`);
|
|
60
|
+
}
|
|
61
|
+
console.log('\nā Initialization complete!');
|
|
62
|
+
console.log(`\nTo use the MCP server, run:`);
|
|
63
|
+
console.log(` context-stash --serve`);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Format index as filename according to config
|
|
67
|
+
*/
|
|
68
|
+
export function formatFilename(index, config) {
|
|
69
|
+
const paddedIndex = String(index).padStart(config.leadingZeros, '0');
|
|
70
|
+
return `${config.filePrefix}${paddedIndex}${config.fileSuffix}`;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Extract reference number from filename
|
|
74
|
+
*/
|
|
75
|
+
export function extractRef(filename, config) {
|
|
76
|
+
const { filePrefix, fileSuffix } = config;
|
|
77
|
+
if (!filename.startsWith(filePrefix) || !filename.endsWith(fileSuffix)) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
const start = filePrefix.length;
|
|
81
|
+
const end = filename.length - fileSuffix.length;
|
|
82
|
+
return filename.substring(start, end);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Get the next available index
|
|
86
|
+
*/
|
|
87
|
+
export async function getNextIndex(workspaceRoot, config) {
|
|
88
|
+
const contextDir = path.join(workspaceRoot, CONTEXT_DIR);
|
|
89
|
+
try {
|
|
90
|
+
const files = await fs.readdir(contextDir);
|
|
91
|
+
const indices = [];
|
|
92
|
+
for (const file of files) {
|
|
93
|
+
if (file === CONFIG_FILE)
|
|
94
|
+
continue;
|
|
95
|
+
const ref = extractRef(file, config);
|
|
96
|
+
if (ref) {
|
|
97
|
+
const index = parseInt(ref, 10);
|
|
98
|
+
if (!isNaN(index)) {
|
|
99
|
+
indices.push(index);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (indices.length === 0) {
|
|
104
|
+
return config.startIndex;
|
|
105
|
+
}
|
|
106
|
+
return Math.max(...indices) + 1;
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
return config.startIndex;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Count lines in markdown content
|
|
114
|
+
*/
|
|
115
|
+
export function countLines(markdown) {
|
|
116
|
+
return markdown.split('\n').length;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Validate path to prevent directory traversal
|
|
120
|
+
*/
|
|
121
|
+
export function validatePath(requestedPath, workspaceRoot) {
|
|
122
|
+
const normalized = path.normalize(requestedPath);
|
|
123
|
+
const resolved = path.resolve(workspaceRoot, normalized);
|
|
124
|
+
if (!resolved.startsWith(workspaceRoot)) {
|
|
125
|
+
throw new Error('Path traversal detected');
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAU,cAAc,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEvE,MAAM,WAAW,GAAG,UAAU,CAAC;AAC/B,MAAM,WAAW,GAAG,aAAa,CAAC;AAClC,MAAM,WAAW,GAAG,WAAW,CAAC;AAEhC;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,aAAqB;IACpD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,WAAW,EAAE,WAAW,CAAC,CAAC;IAEtE,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QACvD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACnC,OAAO,EAAE,GAAG,cAAc,EAAE,GAAG,MAAM,EAAE,CAAC;IAC1C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,0DAA0D,CAAC,CAAC;IAC9E,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,aAAqB,EAAE,MAAc;IACpE,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,WAAW,EAAE,WAAW,CAAC,CAAC;IACtE,MAAM,EAAE,CAAC,SAAS,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;AAC3E,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,aAAqB;IAC7D,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC;IAEzD,4BAA4B;IAC5B,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,KAAK,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAChD,OAAO,CAAC,GAAG,CAAC,aAAa,WAAW,aAAa,CAAC,CAAC;IACrD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,GAAG,CAAC,KAAK,WAAW,4BAA4B,CAAC,CAAC;IAC5D,CAAC;IAED,+BAA+B;IAC/B,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;IACtD,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAC5B,OAAO,CAAC,GAAG,CAAC,KAAK,WAAW,IAAI,WAAW,iBAAiB,CAAC,CAAC;IAChE,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,UAAU,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC;QAChD,OAAO,CAAC,GAAG,CAAC,aAAa,WAAW,IAAI,WAAW,EAAE,CAAC,CAAC;IACzD,CAAC;IAED,6BAA6B;IAC7B,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC;IACzD,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAC5B,OAAO,CAAC,GAAG,CAAC,KAAK,WAAW,iBAAiB,CAAC,CAAC;IACjD,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,EAAE,CAAC,SAAS,CAAC,UAAU,EAAE,iBAAiB,EAAE,OAAO,CAAC,CAAC;QAC3D,OAAO,CAAC,GAAG,CAAC,aAAa,WAAW,EAAE,CAAC,CAAC;IAC1C,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAC;IAC5C,OAAO,CAAC,GAAG,CAAC,+BAA+B,CAAC,CAAC;IAC7C,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;AACzC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,KAAa,EAAE,MAAc;IAC1D,MAAM,WAAW,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,YAAY,EAAE,GAAG,CAAC,CAAC;IACrE,OAAO,GAAG,MAAM,CAAC,UAAU,GAAG,WAAW,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;AAClE,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,QAAgB,EAAE,MAAc;IACzD,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,GAAG,MAAM,CAAC;IAE1C,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;QACvE,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,KAAK,GAAG,UAAU,CAAC,MAAM,CAAC;IAChC,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC;IAChD,OAAO,QAAQ,CAAC,SAAS,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;AACxC,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,aAAqB,EAAE,MAAc;IACtE,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC;IAEzD,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QAC3C,MAAM,OAAO,GAAa,EAAE,CAAC;QAE7B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,IAAI,KAAK,WAAW;gBAAE,SAAS;YAEnC,MAAM,GAAG,GAAG,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;YACrC,IAAI,GAAG,EAAE,CAAC;gBACR,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;gBAChC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;oBAClB,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBACtB,CAAC;YACH,CAAC;QACH,CAAC;QAED,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzB,OAAO,MAAM,CAAC,UAAU,CAAC;QAC3B,CAAC;QAED,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAClC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,MAAM,CAAC,UAAU,CAAC;IAC3B,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,QAAgB;IACzC,OAAO,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC;AACrC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,aAAqB,EAAE,aAAqB;IACvE,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC;IACjD,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,UAAU,CAAC,CAAC;IAEzD,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;IAC7C,CAAC;AACH,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "context-stash",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A workspace-scoped MCP server that provides a durable, Git-native memory layer for AI coding agents",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"context-stash": "dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"watch": "tsc --watch",
|
|
13
|
+
"prepublishOnly": "npm run build",
|
|
14
|
+
"dev": "tsc && node dist/cli.js",
|
|
15
|
+
"preview:site": "echo Open docs/index.html in your browser to preview the website"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"mcp",
|
|
19
|
+
"model-context-protocol",
|
|
20
|
+
"ai",
|
|
21
|
+
"coding-agent",
|
|
22
|
+
"context",
|
|
23
|
+
"memory",
|
|
24
|
+
"documentation",
|
|
25
|
+
"git",
|
|
26
|
+
"claude",
|
|
27
|
+
"copilot",
|
|
28
|
+
"agent"
|
|
29
|
+
],
|
|
30
|
+
"author": "Mark Taylor",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "https://github.com/nzmarktaylor/context-stash.git"
|
|
35
|
+
},
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/nzmarktaylor/context-stash/issues"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://github.com/nzmarktaylor/context-stash#readme",
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=18.0.0"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@modelcontextprotocol/sdk": "^1.0.4"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/node": "^20.11.0",
|
|
48
|
+
"typescript": "^5.3.3"
|
|
49
|
+
},
|
|
50
|
+
"files": [
|
|
51
|
+
"dist",
|
|
52
|
+
"README.md",
|
|
53
|
+
"LICENSE"
|
|
54
|
+
]
|
|
55
|
+
}
|