@shawaze/agentspace 0.3.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 +106 -0
- package/assets/memory-bank-stop.cjs +136 -0
- package/dist/cli.js +1083 -0
- package/package.json +51 -0
- package/stack-agents/_generic.md +31 -0
- package/stack-agents/django.md +34 -0
- package/stack-agents/expo.md +34 -0
- package/stack-agents/go.md +33 -0
- package/stack-agents/nextjs.md +35 -0
- package/stack-agents/rails.md +36 -0
- package/stack-agents/spring-boot.md +34 -0
- package/stack-agents/stacks.yaml +22 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Shawaze Ahmer
|
|
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,106 @@
|
|
|
1
|
+
# agentspace
|
|
2
|
+
|
|
3
|
+
[](https://github.com/irucsS-9/agentspace/actions/workflows/ci.yml)
|
|
4
|
+
[](./LICENSE)
|
|
5
|
+
[](https://www.npmjs.com/package/@shawaze/agentspace)
|
|
6
|
+
|
|
7
|
+
**Scaffold an agent-native multi-repo workspace** — a coordination layer that sits
|
|
8
|
+
above your sibling repositories and keeps them coherent for AI coding agents.
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npx @shawaze/agentspace init # interactive wizard → scaffold the workspace
|
|
12
|
+
npx @shawaze/agentspace doctor # mechanical health checks on a workspace
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
> Status: **v0.3.** Workspace reconstruction, the memory-bank wiki, the
|
|
16
|
+
> enforcement pack, and the cross-repo contract layer all work today.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Why this exists
|
|
21
|
+
|
|
22
|
+
If you run a product as **several separate repositories** (a backend, a web app, a
|
|
23
|
+
mobile client, shared libraries) — a *polyrepo*, not a monorepo — AI coding agents
|
|
24
|
+
have a blind spot: an agent that changes an API in one repo can't see the consumers
|
|
25
|
+
it just broke in another, and every session re-derives the same cross-repo context
|
|
26
|
+
from scratch.
|
|
27
|
+
|
|
28
|
+
`agentspace` is **not** a monorepo tool (Nx, Turborepo, pnpm workspaces). Those
|
|
29
|
+
unify repos under one build. agentspace does the opposite: it leaves your repos
|
|
30
|
+
independent and adds a thin **coordination layer above them** — a declarative
|
|
31
|
+
manifest, an LLM-curated knowledge wiki, cross-repo contracts, and
|
|
32
|
+
boundary-enforced agents — so the *set* of repos stays coherent for an agent
|
|
33
|
+
without a human babysitting drift.
|
|
34
|
+
|
|
35
|
+
It is the generalization of a hand-built workspace methodology into a reusable tool.
|
|
36
|
+
|
|
37
|
+
## The four pillars
|
|
38
|
+
|
|
39
|
+
| Pillar | What it gives you | Status |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| **Workspace reconstruction** | A declarative `manifest.yaml` + an idempotent `clone-repos.sh` that rebuilds the whole workspace on any machine. | ✅ v0.1 |
|
|
42
|
+
| **LLM Wiki** (`memory-bank/`) | A [Karpathy-pattern](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f) knowledge base the agent curates as it works — citation discipline, staleness/size budgets, ingest/query/lint operations. | ✅ v0.1 (structure) |
|
|
43
|
+
| **Cross-app contracts** (`openspec/`) | A prescriptive contract layer + propose/apply/archive lifecycle that fights contract drift across repos. | ✅ v0.3 |
|
|
44
|
+
| **Agents + enforcement** | Boundary-enforced per-repo agents, a warm-until-warm Stop hook, a read-only cross-app reviewer. | ✅ v0.2 |
|
|
45
|
+
|
|
46
|
+
The point isn't any single pillar — it's the **integrated discipline** where they
|
|
47
|
+
reinforce each other.
|
|
48
|
+
|
|
49
|
+
## Topology-aware by design
|
|
50
|
+
|
|
51
|
+
`agentspace init` asks your **workspace shape** and only emits artifacts that shape
|
|
52
|
+
warrants. A single repo, four peer microservices, a library + consumers, and a
|
|
53
|
+
one-product/backend+clients workspace all get *different* output:
|
|
54
|
+
|
|
55
|
+
- A **one-product** workspace gets cross-app contract scaffolding and a
|
|
56
|
+
product-scoped wiki.
|
|
57
|
+
- A **single repo** or set of **unrelated repos** does **not** get a cross-app
|
|
58
|
+
contract layer, a cross-app reviewer, or a blocking hook — because none of that
|
|
59
|
+
applies.
|
|
60
|
+
|
|
61
|
+
You never get a pile of cork-shaped scaffolding that doesn't fit your project.
|
|
62
|
+
|
|
63
|
+
## What `init` generates today (v0.3)
|
|
64
|
+
|
|
65
|
+
- `manifest.yaml` + a resilient `clone-repos.sh`
|
|
66
|
+
- a `.gitignore` (sub-repos are independent git repos, ignored by the workspace)
|
|
67
|
+
- a root `CLAUDE.md` router + `README.md`
|
|
68
|
+
- a shape-aware `memory-bank/` wiki: numbered priority folders, a conventions
|
|
69
|
+
README, and seeded overview/contract stubs appropriate to your shape
|
|
70
|
+
- (enforcement pillar, opt-in) a `.claude/` pack: per-repo boundary-enforced
|
|
71
|
+
agents, `/ingest` `/query` `/lint` commands, a warm-until-warm Stop hook, and
|
|
72
|
+
a cross-app reviewer (contract-linked shapes).
|
|
73
|
+
- (contracts pillar, opt-in) an `openspec/` cross-repo contract layer — a
|
|
74
|
+
shape-aware `project.md` + `specs/`/`changes/`; the `/opsx:*` commands come
|
|
75
|
+
from the external `openspec` CLI (`openspec update`).
|
|
76
|
+
|
|
77
|
+
## Quick start
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
npx @shawaze/agentspace init
|
|
81
|
+
# answer: workspace name → shape → repos (name, remote, stack, role) → pillars
|
|
82
|
+
./clone-repos.sh # pull any sub-repos that aren't on disk yet
|
|
83
|
+
npx @shawaze/agentspace doctor # check workspace health (size budgets, staleness, manifest)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Roadmap
|
|
87
|
+
|
|
88
|
+
- More tool adapters (Cursor, Windsurf, …) via the same intent seam.
|
|
89
|
+
|
|
90
|
+
## Contributing
|
|
91
|
+
|
|
92
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md). The per-stack agent library is designed
|
|
93
|
+
so adding support for a new stack is a single markdown file — issues and PRs welcome.
|
|
94
|
+
|
|
95
|
+
## Development
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
npm install
|
|
99
|
+
npm test # vitest
|
|
100
|
+
npm run typecheck
|
|
101
|
+
npm run build # tsup → dist/cli.js
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## License
|
|
105
|
+
|
|
106
|
+
[MIT](./LICENSE) © Shawaze Ahmer
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* agentspace Stop hook — keeps the memory bank current on cross-repo work.
|
|
4
|
+
* Dep-free (runs with bare node). Reads .claude/agentspace-hook.json for config.
|
|
5
|
+
* Pure helpers are exported when required (for tests); the file runs when executed.
|
|
6
|
+
*/
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
const MUTATING_TOOLS = new Set(['Edit', 'Write', 'MultiEdit', 'NotebookEdit']);
|
|
13
|
+
const SEED_PAGES = new Set(['README.md', 'index.md', 'log.md', 'projectOverview.md', 'crossAppContracts.md']);
|
|
14
|
+
|
|
15
|
+
/** Pure decision: allow | warn | block. */
|
|
16
|
+
function decideStop({ mode, warm, crossAppMutation, memoryBankUpdated }) {
|
|
17
|
+
if (!crossAppMutation || memoryBankUpdated) return 'allow';
|
|
18
|
+
if (mode === 'warn') return 'warn';
|
|
19
|
+
if (mode === 'block') return 'block';
|
|
20
|
+
return warm ? 'block' : 'warn'; // auto
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Pure: warm when pages OR sessions cross their thresholds. */
|
|
24
|
+
function isWarm({ pages, sessions, warmPages, warmSessions }) {
|
|
25
|
+
return pages > warmPages || sessions >= warmSessions;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Count real (non-seed) memory-bank .md pages, recursively. */
|
|
29
|
+
function countRealPages(mbDir) {
|
|
30
|
+
let count = 0;
|
|
31
|
+
function walk(dir) {
|
|
32
|
+
let entries = [];
|
|
33
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
|
34
|
+
for (const e of entries) {
|
|
35
|
+
const full = path.join(dir, e.name);
|
|
36
|
+
if (e.isDirectory()) walk(full);
|
|
37
|
+
else if (e.name.endsWith('.md') && !SEED_PAGES.has(e.name)) count++;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
walk(mbDir);
|
|
41
|
+
return count;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function readState(stateFile) {
|
|
45
|
+
try { return JSON.parse(fs.readFileSync(stateFile, 'utf8')); } catch { return { sessions: 0 }; }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function writeState(stateFile, state) {
|
|
49
|
+
try {
|
|
50
|
+
fs.mkdirSync(path.dirname(stateFile), { recursive: true });
|
|
51
|
+
fs.writeFileSync(stateFile, JSON.stringify(state));
|
|
52
|
+
} catch { /* best effort */ }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function main() {
|
|
56
|
+
let input = {};
|
|
57
|
+
try { input = JSON.parse(fs.readFileSync(0, 'utf8') || '{}'); } catch { process.exit(0); }
|
|
58
|
+
if (input.stop_hook_active) process.exit(0);
|
|
59
|
+
|
|
60
|
+
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
61
|
+
const mbDir = path.join(projectDir, 'memory-bank');
|
|
62
|
+
if (!fs.existsSync(mbDir)) process.exit(0);
|
|
63
|
+
|
|
64
|
+
const configFile = path.join(projectDir, '.claude', 'agentspace-hook.json');
|
|
65
|
+
let cfg;
|
|
66
|
+
try { cfg = JSON.parse(fs.readFileSync(configFile, 'utf8')); } catch { process.exit(0); }
|
|
67
|
+
const subRepos = Array.isArray(cfg.subRepos) ? cfg.subRepos : [];
|
|
68
|
+
const mode = (cfg.mode === 'warn' || cfg.mode === 'block') ? cfg.mode : 'auto';
|
|
69
|
+
const warmPages = typeof cfg.warmPages === 'number' ? cfg.warmPages : 5;
|
|
70
|
+
const warmSessions = typeof cfg.warmSessions === 'number' ? cfg.warmSessions : 10;
|
|
71
|
+
|
|
72
|
+
// Count this session (every Stop on a configured workspace).
|
|
73
|
+
const stateFile = path.join(projectDir, '.agentspace', 'state.json');
|
|
74
|
+
const state = readState(stateFile);
|
|
75
|
+
state.sessions = (state.sessions || 0) + 1;
|
|
76
|
+
writeState(stateFile, state);
|
|
77
|
+
|
|
78
|
+
// Inspect the transcript for mutating tool uses.
|
|
79
|
+
let mutationCount = 0;
|
|
80
|
+
const touched = new Set();
|
|
81
|
+
let memoryBankUpdated = false;
|
|
82
|
+
const tp = input.transcript_path;
|
|
83
|
+
let transcriptLines = [];
|
|
84
|
+
try {
|
|
85
|
+
if (tp && fs.existsSync(tp)) transcriptLines = fs.readFileSync(tp, 'utf8').split('\n');
|
|
86
|
+
} catch { /* best effort — treat as no transcript, fail open */ }
|
|
87
|
+
if (transcriptLines.length) {
|
|
88
|
+
for (const line of transcriptLines) {
|
|
89
|
+
if (!line.trim()) continue;
|
|
90
|
+
let evt; try { evt = JSON.parse(line); } catch { continue; }
|
|
91
|
+
const content = evt && evt.message && evt.message.content;
|
|
92
|
+
if (!Array.isArray(content)) continue;
|
|
93
|
+
for (const block of content) {
|
|
94
|
+
if (!block || block.type !== 'tool_use' || !MUTATING_TOOLS.has(block.name)) continue;
|
|
95
|
+
mutationCount++;
|
|
96
|
+
const raw = (block.input && (block.input.file_path || block.input.notebook_path)) || '';
|
|
97
|
+
const rel = raw.startsWith(projectDir) ? raw.slice(projectDir.length + 1) : raw;
|
|
98
|
+
if (rel.startsWith('memory-bank/')) memoryBankUpdated = true;
|
|
99
|
+
for (const sub of subRepos) { if (rel.startsWith(sub + '/')) { touched.add(sub); break; } }
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const crossAppMutation = mutationCount > 0 && touched.size >= 2;
|
|
105
|
+
const warm = isWarm({
|
|
106
|
+
pages: countRealPages(mbDir),
|
|
107
|
+
sessions: state.sessions,
|
|
108
|
+
warmPages,
|
|
109
|
+
warmSessions,
|
|
110
|
+
});
|
|
111
|
+
const decision = decideStop({ mode, warm, crossAppMutation, memoryBankUpdated });
|
|
112
|
+
|
|
113
|
+
if (decision === 'allow') process.exit(0);
|
|
114
|
+
|
|
115
|
+
const list = [...touched].sort().join(', ');
|
|
116
|
+
const reason = [
|
|
117
|
+
`Cross-repo activity detected (${list}) — update the memory bank before ending.`,
|
|
118
|
+
'1. Refresh memory-bank/01-active/currentWork.md (date + status).',
|
|
119
|
+
'2. Append one line to memory-bank/log.md: `## [YYYY-MM-DD] <action> | <slug>`.',
|
|
120
|
+
'3. If a cross-repo contract was touched, record it in memory-bank/00-core/crossAppContracts.md (cite `file:line`).',
|
|
121
|
+
].join('\n');
|
|
122
|
+
|
|
123
|
+
if (decision === 'block') {
|
|
124
|
+
process.stdout.write(JSON.stringify({ decision: 'block', reason }));
|
|
125
|
+
} else {
|
|
126
|
+
// warn: surface a note but allow the stop.
|
|
127
|
+
process.stdout.write(JSON.stringify({ systemMessage: reason }));
|
|
128
|
+
}
|
|
129
|
+
process.exit(0);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (require.main === module) {
|
|
133
|
+
try { main(); } catch { process.exit(0); }
|
|
134
|
+
} else {
|
|
135
|
+
module.exports = { decideStop, isWarm, countRealPages, readState, writeState };
|
|
136
|
+
}
|