@p_tipso/agentive 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 +127 -0
- package/bin/index.js +23 -0
- package/package.json +49 -0
- package/src/commands/init.js +148 -0
- package/src/templates/context/project_overview.md +62 -0
- package/src/templates/rules/architecture.md +42 -0
- package/src/templates/skills/data-extractor.md +56 -0
- package/src/templates/skills/data-transformer.md +62 -0
- package/src/templates/skills/output-dispatcher.md +79 -0
- package/src/templates/skills/web-browser.md +54 -0
- package/src/utils/compilers.js +130 -0
- package/src/utils/fileSystem.js +167 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Pakawat Tipso
|
|
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,127 @@
|
|
|
1
|
+
<](https://www.npmjs.com/package/@p_tipso/agentive)
|
|
8
|
+
[](https://opensource.org/licenses/MIT)
|
|
9
|
+
[](https://nodejs.org)
|
|
10
|
+
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
Stop maintaining separate rule files for every AI tool. **agentive** scaffolds a single `.agent/` directory in your project — your source of truth for agent rules and skills — and compiles it into the specific formats each AI tool understands.
|
|
16
|
+
|
|
17
|
+
## ✨ What it does
|
|
18
|
+
|
|
19
|
+
1. **Scaffolds** a universal `.agent/` workspace in your project
|
|
20
|
+
2. **Syncs** your `.agent/` files into the native formats of your chosen AI tools:
|
|
21
|
+
- 🖱 **Cursor** → `.cursor/rules/`
|
|
22
|
+
- 🤖 **Claude** → `CLAUDE.md`
|
|
23
|
+
- 🌊 **Windsurf** → `.windsurfrules`
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 🚀 Quick Start
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx @p_tipso/agentive
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The interactive wizard will ask you:
|
|
34
|
+
1. **Project name** (pre-filled from your directory name)
|
|
35
|
+
2. **Your primary tech stack** (e.g. "Next.js, TypeScript, Postgres")
|
|
36
|
+
3. **Which AI tools** you use (multi-select: Cursor, Claude, Windsurf)
|
|
37
|
+
|
|
38
|
+
Then it scaffolds everything automatically.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 📁 Output Structure
|
|
43
|
+
|
|
44
|
+
After running `npx @p_tipso/agentive`, your project will have:
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
your-project/
|
|
48
|
+
├── .agent/
|
|
49
|
+
│ ├── settings.json ← project config (commit this)
|
|
50
|
+
│ ├── settings.local.json ← local overrides (gitignored automatically)
|
|
51
|
+
│ ├── rules/
|
|
52
|
+
│ │ └── architecture.md ← project-wide rules for all agents
|
|
53
|
+
│ └── skills/
|
|
54
|
+
│ ├── web-browser.md ← how agents should browse the web
|
|
55
|
+
│ ├── data-extractor.md ← how agents should extract data
|
|
56
|
+
│ ├── data-transformer.md ← how agents should transform data
|
|
57
|
+
│ └── output-dispatcher.md ← how agents should deliver output
|
|
58
|
+
│
|
|
59
|
+
│ ── (auto-synced based on your selection) ──
|
|
60
|
+
│
|
|
61
|
+
├── .cursor/rules/ ← if you selected Cursor
|
|
62
|
+
├── CLAUDE.md ← if you selected Claude
|
|
63
|
+
└── .windsurfrules ← if you selected Windsurf
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### `settings.json` example
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"projectName": "my-app",
|
|
71
|
+
"techStack": "Next.js, TypeScript, PostgreSQL",
|
|
72
|
+
"tools": ["cursor", "claude"],
|
|
73
|
+
"agentiveVersion": "1.0.0",
|
|
74
|
+
"createdAt": "2026-07-05T12:00:00.000Z"
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## 📦 Commands
|
|
81
|
+
|
|
82
|
+
| Command | Description |
|
|
83
|
+
|---|---|
|
|
84
|
+
| `npx @p_tipso/agentive` | Run the setup wizard and scaffold your `.agent/` workspace |
|
|
85
|
+
| `npx @p_tipso/agentive init` | Same as above (explicit subcommand) |
|
|
86
|
+
| `npx @p_tipso/agentive --version` | Print the current version |
|
|
87
|
+
| `npx @p_tipso/agentive --help` | Show available commands |
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## 🛠 Install Globally (optional)
|
|
92
|
+
|
|
93
|
+
If you use this frequently, install it globally:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
npm install -g @p_tipso/agentive
|
|
97
|
+
agentive
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## 🔄 Re-syncing
|
|
103
|
+
|
|
104
|
+
Edited your `.agent/` files and want to push changes to Cursor, Claude, etc.? Just re-run:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
npx @p_tipso/agentive
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
It will detect the existing `.agent/` directory and ask before overwriting.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## 🤝 Contributing
|
|
115
|
+
|
|
116
|
+
1. Fork the repo: [github.com/TiPS0/agentive](https://github.com/TiPS0/agentive)
|
|
117
|
+
2. Clone your fork
|
|
118
|
+
3. `npm install`
|
|
119
|
+
4. `npm link` (to use `agentive` locally while developing)
|
|
120
|
+
5. Make your changes and open a pull request!
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## 📄 License
|
|
125
|
+
|
|
126
|
+
MIT © [Pakawat Tipso](https://github.com/TiPS0)
|
|
127
|
+
]]>
|
package/bin/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
const { Command } = require('commander');
|
|
6
|
+
const { version } = require('../package.json');
|
|
7
|
+
|
|
8
|
+
const program = new Command();
|
|
9
|
+
|
|
10
|
+
program
|
|
11
|
+
.name('agentive')
|
|
12
|
+
.description('Universal AI agent workspace setup CLI')
|
|
13
|
+
.version(version, '-v, --version', 'Print the current version');
|
|
14
|
+
|
|
15
|
+
program
|
|
16
|
+
.command('init', { isDefault: true })
|
|
17
|
+
.description('Initialize a universal .agent/ workspace in your project')
|
|
18
|
+
.action(async () => {
|
|
19
|
+
const { runInit } = require('../src/commands/init');
|
|
20
|
+
await runInit();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
program.parse(process.argv);
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@p_tipso/agentive",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A universal, framework-agnostic AI agent repository setup CLI. Scaffold a .agent/ workspace and sync to Cursor, Claude, and Windsurf.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Pakawat Tipso",
|
|
7
|
+
"type": "commonjs",
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=18"
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"agentive": "bin/index.js"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"bin/",
|
|
16
|
+
"src/",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"keywords": [
|
|
21
|
+
"ai",
|
|
22
|
+
"agent",
|
|
23
|
+
"cli",
|
|
24
|
+
"cursor",
|
|
25
|
+
"claude",
|
|
26
|
+
"windsurf",
|
|
27
|
+
"scaffold",
|
|
28
|
+
"init",
|
|
29
|
+
"rules",
|
|
30
|
+
"context"
|
|
31
|
+
],
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "git+https://github.com/TiPS0/agentive.git"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://github.com/TiPS0/agentive#readme",
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/TiPS0/agentive/issues"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"prepare": "node -e \"console.log('agentive ready.')\"",
|
|
42
|
+
"test": "node bin/index.js --version"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"chalk": "^4.1.2",
|
|
46
|
+
"commander": "^12.1.0",
|
|
47
|
+
"prompts": "^2.4.2"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const prompts = require('prompts');
|
|
5
|
+
const chalk = require('chalk');
|
|
6
|
+
const { version } = require('../../package.json');
|
|
7
|
+
const {
|
|
8
|
+
createAgentDirectory,
|
|
9
|
+
copyTemplates,
|
|
10
|
+
agentDirectoryExists,
|
|
11
|
+
writeSettings,
|
|
12
|
+
} = require('../utils/fileSystem');
|
|
13
|
+
const { syncToCursor, syncToClaude, syncToWindsurf } = require('../utils/compilers');
|
|
14
|
+
|
|
15
|
+
const AI_TOOLS = [
|
|
16
|
+
{ title: '🖱 Cursor', value: 'cursor', description: 'Generates .cursor/rules/' },
|
|
17
|
+
{ title: '🤖 Claude', value: 'claude', description: 'Generates CLAUDE.md' },
|
|
18
|
+
{ title: '🌊 Windsurf', value: 'windsurf', description: 'Generates .windsurfrules' },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
async function runInit() {
|
|
22
|
+
const cwd = process.cwd();
|
|
23
|
+
|
|
24
|
+
console.log('');
|
|
25
|
+
console.log(chalk.bold.cyan(' 🤖 agentive') + chalk.gray(' — Universal AI Agent Workspace Setup'));
|
|
26
|
+
console.log(chalk.gray(' ─────────────────────────────────────────────'));
|
|
27
|
+
console.log('');
|
|
28
|
+
|
|
29
|
+
// --- Guard: check if .agent/ already exists ---
|
|
30
|
+
const alreadyExists = await agentDirectoryExists(cwd);
|
|
31
|
+
if (alreadyExists) {
|
|
32
|
+
const { overwrite } = await prompts({
|
|
33
|
+
type: 'confirm',
|
|
34
|
+
name: 'overwrite',
|
|
35
|
+
message: chalk.yellow('A .agent/ directory already exists. Overwrite it?'),
|
|
36
|
+
initial: false,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
if (!overwrite) {
|
|
40
|
+
console.log('');
|
|
41
|
+
console.log(chalk.gray(' Aborted. Your existing .agent/ directory was not modified.'));
|
|
42
|
+
console.log('');
|
|
43
|
+
process.exit(0);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// --- Wizard ---
|
|
48
|
+
const projectName = path.basename(cwd);
|
|
49
|
+
|
|
50
|
+
const answers = await prompts(
|
|
51
|
+
[
|
|
52
|
+
{
|
|
53
|
+
type: 'text',
|
|
54
|
+
name: 'projectName',
|
|
55
|
+
message: 'Project name?',
|
|
56
|
+
initial: projectName,
|
|
57
|
+
validate: (v) => v.trim().length > 0 || 'Required.',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
type: 'text',
|
|
61
|
+
name: 'techStack',
|
|
62
|
+
message: 'Primary tech stack?',
|
|
63
|
+
placeholder: 'e.g. Next.js, TypeScript, PostgreSQL',
|
|
64
|
+
validate: (v) => v.trim().length > 0 || 'Required.',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
type: 'multiselect',
|
|
68
|
+
name: 'tools',
|
|
69
|
+
message: 'Which AI tools do you want to target?',
|
|
70
|
+
choices: AI_TOOLS,
|
|
71
|
+
min: 1,
|
|
72
|
+
hint: '- Space to select. Return to submit',
|
|
73
|
+
instructions: false,
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
{
|
|
77
|
+
onCancel: () => {
|
|
78
|
+
console.log('');
|
|
79
|
+
console.log(chalk.gray(' Setup cancelled.'));
|
|
80
|
+
process.exit(0);
|
|
81
|
+
},
|
|
82
|
+
}
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const { tools, techStack } = answers;
|
|
86
|
+
const finalProjectName = answers.projectName;
|
|
87
|
+
|
|
88
|
+
console.log('');
|
|
89
|
+
console.log(chalk.gray(' Setting up your workspace...'));
|
|
90
|
+
console.log('');
|
|
91
|
+
|
|
92
|
+
// --- Scaffold .agent/ directory ---
|
|
93
|
+
const templatesDir = path.join(__dirname, '..', 'templates');
|
|
94
|
+
const agentDir = await createAgentDirectory(cwd, alreadyExists);
|
|
95
|
+
await copyTemplates(templatesDir, agentDir, techStack);
|
|
96
|
+
|
|
97
|
+
// --- Write settings.json and settings.local.json ---
|
|
98
|
+
await writeSettings(agentDir, {
|
|
99
|
+
projectName: finalProjectName,
|
|
100
|
+
techStack,
|
|
101
|
+
tools,
|
|
102
|
+
agentiveVersion: version,
|
|
103
|
+
createdAt: new Date().toISOString(),
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
console.log(chalk.green(' ✔ ') + chalk.white('Scaffolded ') + chalk.cyan('.agent/') + chalk.white(' workspace'));
|
|
107
|
+
|
|
108
|
+
// --- Sync to selected tools ---
|
|
109
|
+
const compilers = {
|
|
110
|
+
cursor: syncToCursor,
|
|
111
|
+
claude: syncToClaude,
|
|
112
|
+
windsurf: syncToWindsurf,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const toolLabels = {
|
|
116
|
+
cursor: '.cursor/rules/',
|
|
117
|
+
claude: 'CLAUDE.md',
|
|
118
|
+
windsurf: '.windsurfrules',
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
for (const tool of tools) {
|
|
122
|
+
try {
|
|
123
|
+
await compilers[tool](agentDir, cwd, techStack);
|
|
124
|
+
console.log(chalk.green(' ✔ ') + chalk.white('Synced → ') + chalk.cyan(toolLabels[tool]));
|
|
125
|
+
} catch (err) {
|
|
126
|
+
console.log(chalk.red(' ✘ ') + chalk.white(`Failed to sync ${tool}: `) + chalk.gray(err.message));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// --- Done ---
|
|
131
|
+
console.log('');
|
|
132
|
+
console.log(chalk.bold.green(' ✅ All done!'));
|
|
133
|
+
console.log('');
|
|
134
|
+
console.log(chalk.white(' Your AI workspace is ready at ') + chalk.cyan('.agent/'));
|
|
135
|
+
console.log('');
|
|
136
|
+
console.log(chalk.gray(' Structure:'));
|
|
137
|
+
console.log(chalk.gray(' .agent/'));
|
|
138
|
+
console.log(chalk.gray(' ├── settings.json ← project config (commit this)'));
|
|
139
|
+
console.log(chalk.gray(' ├── settings.local.json ← local overrides (gitignored)'));
|
|
140
|
+
console.log(chalk.gray(' ├── rules/ ← agent rules'));
|
|
141
|
+
console.log(chalk.gray(' └── skills/ ← agent skills'));
|
|
142
|
+
console.log('');
|
|
143
|
+
console.log(chalk.gray(' Edit the markdown files in .agent/ to customise agent behaviour.'));
|
|
144
|
+
console.log(chalk.gray(' Re-run ') + chalk.cyan('npx @p_tipso/agentive') + chalk.gray(' any time to re-sync to your AI tools.'));
|
|
145
|
+
console.log('');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
module.exports = { runInit };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Project Overview
|
|
2
|
+
|
|
3
|
+
> This file provides essential context for AI agents working on this project.
|
|
4
|
+
> Keep it updated as the project evolves.
|
|
5
|
+
|
|
6
|
+
## About This Project
|
|
7
|
+
|
|
8
|
+
<!-- Describe what this project does and who it is for -->
|
|
9
|
+
|
|
10
|
+
**Tech Stack:** {{TECH_STACK}}
|
|
11
|
+
|
|
12
|
+
## Project Goals
|
|
13
|
+
|
|
14
|
+
<!-- List the primary goals and success criteria for this project -->
|
|
15
|
+
|
|
16
|
+
1.
|
|
17
|
+
2.
|
|
18
|
+
3.
|
|
19
|
+
|
|
20
|
+
## Architecture Overview
|
|
21
|
+
|
|
22
|
+
<!-- Briefly describe the high-level architecture and major components -->
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
your-project/
|
|
26
|
+
├── src/ # Application source code
|
|
27
|
+
├── tests/ # Test files
|
|
28
|
+
└── docs/ # Documentation
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Key Conventions
|
|
32
|
+
|
|
33
|
+
<!-- Document conventions that are unique to this project -->
|
|
34
|
+
|
|
35
|
+
- **Branch naming**: `feat/`, `fix/`, `chore/` prefixes
|
|
36
|
+
- **Commit messages**: Follow [Conventional Commits](https://www.conventionalcommits.org/)
|
|
37
|
+
- **PR size**: Keep pull requests focused and under 400 lines changed
|
|
38
|
+
|
|
39
|
+
## Important Files & Directories
|
|
40
|
+
|
|
41
|
+
| Path | Purpose |
|
|
42
|
+
|------|---------|
|
|
43
|
+
| `src/` | Application source |
|
|
44
|
+
| `.ai/` | Agent rules, skills, and context (source of truth) |
|
|
45
|
+
|
|
46
|
+
## External Services & APIs
|
|
47
|
+
|
|
48
|
+
<!-- List any external services, APIs, or integrations this project depends on -->
|
|
49
|
+
|
|
50
|
+
| Service | Purpose | Docs |
|
|
51
|
+
|---------|---------|------|
|
|
52
|
+
| | | |
|
|
53
|
+
|
|
54
|
+
## Known Gotchas
|
|
55
|
+
|
|
56
|
+
<!-- Document non-obvious things that have caused bugs or confusion in the past -->
|
|
57
|
+
|
|
58
|
+
-
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
*Generated by [agentive](https://github.com/TiPS0/agentive) on {{YEAR}}.*
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Architecture Rules
|
|
2
|
+
|
|
3
|
+
> These rules govern how AI agents should reason about, write, and modify code
|
|
4
|
+
> in this project. All agents operating in this repository must follow them.
|
|
5
|
+
|
|
6
|
+
## Tech Stack
|
|
7
|
+
|
|
8
|
+
This project uses: **{{TECH_STACK}}**
|
|
9
|
+
|
|
10
|
+
All generated code must be idiomatic for this stack and follow its established patterns.
|
|
11
|
+
|
|
12
|
+
## Structural Principles
|
|
13
|
+
|
|
14
|
+
- **Single Responsibility**: Every function, module, or class should do one thing well.
|
|
15
|
+
- **Separation of Concerns**: Keep data fetching, business logic, and presentation in separate layers.
|
|
16
|
+
- **DRY (Don't Repeat Yourself)**: Before adding new code, search for existing utilities or helpers.
|
|
17
|
+
- **YAGNI (You Aren't Gonna Need It)**: Do not add features or abstractions that aren't required right now.
|
|
18
|
+
|
|
19
|
+
## Code Style
|
|
20
|
+
|
|
21
|
+
- Prefer **async/await** over raw Promises or callbacks.
|
|
22
|
+
- Always handle errors explicitly — never swallow exceptions silently.
|
|
23
|
+
- Use **descriptive, intent-revealing names** for variables, functions, and files.
|
|
24
|
+
- Write **small, focused functions** (prefer < 30 lines per function).
|
|
25
|
+
- Add **JSDoc or TSDoc comments** to all exported functions.
|
|
26
|
+
|
|
27
|
+
## File Organisation
|
|
28
|
+
|
|
29
|
+
- Group related code by **feature**, not by type (avoid giant `utils/` or `helpers/` dumping grounds).
|
|
30
|
+
- Keep files focused — if a file grows past ~200 lines, consider splitting it.
|
|
31
|
+
- Barrel exports (`index.js`) should only re-export, never contain logic.
|
|
32
|
+
|
|
33
|
+
## Testing
|
|
34
|
+
|
|
35
|
+
- Every new feature should have a corresponding test.
|
|
36
|
+
- Test **behaviour**, not implementation details.
|
|
37
|
+
- Prefer unit tests for pure functions; integration tests for side-effecting code.
|
|
38
|
+
|
|
39
|
+
## Dependencies
|
|
40
|
+
|
|
41
|
+
- Before adding a new dependency, consider if the task can be accomplished with built-ins.
|
|
42
|
+
- Avoid dependencies with no active maintenance or that have known security advisories.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Skill: Data Extractor Agent
|
|
2
|
+
|
|
3
|
+
> This skill defines how an AI agent should behave when its role is to
|
|
4
|
+
> extract structured data from raw, unstructured, or semi-structured sources.
|
|
5
|
+
|
|
6
|
+
## Role Description
|
|
7
|
+
|
|
8
|
+
You are a **Data Extractor Agent**. Your job is to parse raw content (HTML, text,
|
|
9
|
+
JSON, PDF, CSV, etc.) and extract specific, structured data points as defined by
|
|
10
|
+
the schema provided by the orchestrator. You surface data — you do not transform
|
|
11
|
+
or enrich it.
|
|
12
|
+
|
|
13
|
+
## Responsibilities
|
|
14
|
+
|
|
15
|
+
- Parse the provided raw content using the most appropriate method for its format.
|
|
16
|
+
- Extract only the fields specified in the schema — do not add extra fields.
|
|
17
|
+
- Return `null` for any field that cannot be found; do not guess or hallucinate values.
|
|
18
|
+
- Normalise whitespace and remove HTML tags from text values.
|
|
19
|
+
- Flag ambiguous or low-confidence extractions to the orchestrator.
|
|
20
|
+
|
|
21
|
+
## Inputs
|
|
22
|
+
|
|
23
|
+
| Input | Type | Description |
|
|
24
|
+
|-------|------|-------------|
|
|
25
|
+
| `content` | `string` | The raw content to parse |
|
|
26
|
+
| `format` | `string` | One of: `html`, `text`, `json`, `csv`, `markdown` |
|
|
27
|
+
| `schema` | `object` | JSON Schema defining the fields to extract |
|
|
28
|
+
|
|
29
|
+
## Outputs
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"data": {
|
|
34
|
+
"field_name": "extracted value",
|
|
35
|
+
"another_field": null
|
|
36
|
+
},
|
|
37
|
+
"confidence": 0.95,
|
|
38
|
+
"warnings": ["Could not find 'phone_number' in source"]
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Constraints
|
|
43
|
+
|
|
44
|
+
- **Never infer** or fabricate a value that isn't explicitly present in the source.
|
|
45
|
+
- **Always** include a `confidence` score between `0.0` and `1.0`.
|
|
46
|
+
- If `confidence < 0.7`, always include a `warnings` entry explaining the uncertainty.
|
|
47
|
+
- **Do not** make external network requests — work only with the content provided.
|
|
48
|
+
|
|
49
|
+
## Error Handling
|
|
50
|
+
|
|
51
|
+
| Error | Response |
|
|
52
|
+
|-------|----------|
|
|
53
|
+
| `Unsupported format` | Return an error; list supported formats |
|
|
54
|
+
| `Schema mismatch` | Return partial data with warnings for missing fields |
|
|
55
|
+
| `Empty content` | Return all fields as `null` with a warning |
|
|
56
|
+
| `Parse error` | Report the specific parse error and return an empty `data` object |
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Skill: Data Transformer Agent
|
|
2
|
+
|
|
3
|
+
> This skill defines how an AI agent should behave when its role is to
|
|
4
|
+
> clean, enrich, reformat, or restructure data from one schema to another.
|
|
5
|
+
|
|
6
|
+
## Role Description
|
|
7
|
+
|
|
8
|
+
You are a **Data Transformer Agent**. Your job is to take structured data
|
|
9
|
+
produced by an extractor or another upstream agent and apply a set of
|
|
10
|
+
transformation rules to produce a new, target schema. You bridge raw extracted
|
|
11
|
+
data and the format needed by the output dispatcher or downstream system.
|
|
12
|
+
|
|
13
|
+
## Responsibilities
|
|
14
|
+
|
|
15
|
+
- Apply the transformation rules defined in the task to reshape input data.
|
|
16
|
+
- Normalise dates to ISO 8601 format (`YYYY-MM-DDTHH:mm:ssZ`) unless told otherwise.
|
|
17
|
+
- Normalise currency values to a base unit (e.g. cents) when dealing with financial data.
|
|
18
|
+
- Deduplicate records when a `dedup_key` is specified.
|
|
19
|
+
- Apply computed fields (e.g. `full_name = first_name + " " + last_name`).
|
|
20
|
+
- Report rows/records that could not be transformed and explain why.
|
|
21
|
+
|
|
22
|
+
## Inputs
|
|
23
|
+
|
|
24
|
+
| Input | Type | Description |
|
|
25
|
+
|-------|------|-------------|
|
|
26
|
+
| `data` | `object \| array` | The raw structured data to transform |
|
|
27
|
+
| `rules` | `object` | Transformation rules (field mappings, computed fields, filters) |
|
|
28
|
+
| `target_schema` | `object` | The JSON Schema of the expected output |
|
|
29
|
+
|
|
30
|
+
## Outputs
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"transformed": [
|
|
35
|
+
{ "id": "abc-123", "full_name": "Ada Lovelace", "created_at": "1815-12-10T00:00:00Z" }
|
|
36
|
+
],
|
|
37
|
+
"failed": [
|
|
38
|
+
{ "record": { "raw": "..." }, "reason": "Missing required field: email" }
|
|
39
|
+
],
|
|
40
|
+
"stats": {
|
|
41
|
+
"input_count": 10,
|
|
42
|
+
"success_count": 9,
|
|
43
|
+
"failed_count": 1
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Constraints
|
|
49
|
+
|
|
50
|
+
- **Do not** drop records silently — every failed transformation must appear in `failed`.
|
|
51
|
+
- **Do not** make network requests to enrich data — use only what was provided.
|
|
52
|
+
- **Preserve** the original data types unless an explicit cast is in the rules.
|
|
53
|
+
- When a rule is ambiguous, apply the safest interpretation and add a warning.
|
|
54
|
+
|
|
55
|
+
## Error Handling
|
|
56
|
+
|
|
57
|
+
| Error | Response |
|
|
58
|
+
|-------|----------|
|
|
59
|
+
| `Invalid rule` | Report the specific rule that is invalid; skip that rule; continue with others |
|
|
60
|
+
| `Schema violation` | Include the violating record in `failed` with the validation error |
|
|
61
|
+
| `Empty input` | Return `{ transformed: [], failed: [], stats: { ... } }` immediately |
|
|
62
|
+
| `Type mismatch` | Attempt a safe cast; if not possible, add the record to `failed` |
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Skill: Output Dispatcher Agent
|
|
2
|
+
|
|
3
|
+
> This skill defines how an AI agent should behave when its role is to
|
|
4
|
+
> deliver final, processed data to one or more target destinations.
|
|
5
|
+
|
|
6
|
+
## Role Description
|
|
7
|
+
|
|
8
|
+
You are an **Output Dispatcher Agent**. Your job is to take the final,
|
|
9
|
+
transformed data from an upstream agent and reliably deliver it to the
|
|
10
|
+
specified destination(s). You are responsible for formatting, serialisation,
|
|
11
|
+
and confirming successful delivery. You are the last step in the pipeline.
|
|
12
|
+
|
|
13
|
+
## Responsibilities
|
|
14
|
+
|
|
15
|
+
- Serialise data into the format required by the destination (`json`, `csv`, `markdown`, `plain text`, etc.).
|
|
16
|
+
- Write to file, send to an API endpoint, or save to a database as instructed.
|
|
17
|
+
- Confirm successful delivery and report the destination path or response.
|
|
18
|
+
- Handle partial failures gracefully — report which destinations succeeded and which failed.
|
|
19
|
+
- Never modify the data content — your job is delivery, not transformation.
|
|
20
|
+
|
|
21
|
+
## Inputs
|
|
22
|
+
|
|
23
|
+
| Input | Type | Description |
|
|
24
|
+
|-------|------|-------------|
|
|
25
|
+
| `data` | `object \| array` | The final data to deliver |
|
|
26
|
+
| `destinations` | `array` | List of destination configs (see below) |
|
|
27
|
+
| `format` | `string` | Output format: `json`, `csv`, `markdown`, `text` |
|
|
28
|
+
|
|
29
|
+
### Destination Config Shape
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"type": "file",
|
|
34
|
+
"path": "./output/results.json"
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"type": "api",
|
|
41
|
+
"url": "https://api.example.com/results",
|
|
42
|
+
"method": "POST",
|
|
43
|
+
"headers": { "Authorization": "Bearer <token>" }
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Outputs
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"results": [
|
|
52
|
+
{ "destination": "./output/results.json", "status": "success", "bytes_written": 4096 },
|
|
53
|
+
{ "destination": "https://api.example.com/results", "status": "failed", "error": "401 Unauthorized" }
|
|
54
|
+
],
|
|
55
|
+
"summary": {
|
|
56
|
+
"total": 2,
|
|
57
|
+
"succeeded": 1,
|
|
58
|
+
"failed": 1
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Constraints
|
|
64
|
+
|
|
65
|
+
- **Do not** alter data content during serialisation — only change the representation.
|
|
66
|
+
- **Always** report `status` for every destination, even on failure.
|
|
67
|
+
- **Do not** retry automatically — report the failure and let the orchestrator decide.
|
|
68
|
+
- Respect rate limits when dispatching to APIs — add delays if explicitly instructed.
|
|
69
|
+
- **Never** log or store sensitive values (tokens, passwords) in the output.
|
|
70
|
+
|
|
71
|
+
## Error Handling
|
|
72
|
+
|
|
73
|
+
| Error | Response |
|
|
74
|
+
|-------|----------|
|
|
75
|
+
| `File write error` | Report the OS error; mark destination as `failed` |
|
|
76
|
+
| `API 4xx` | Report status code and body; mark as `failed`; do not retry |
|
|
77
|
+
| `API 5xx` | Report status code; mark as `failed`; note retry is possible |
|
|
78
|
+
| `Invalid format` | Report the unsupported format; list supported formats |
|
|
79
|
+
| `Network timeout` | Report timeout after 30 seconds; mark as `failed` |
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Skill: Web Browser Agent
|
|
2
|
+
|
|
3
|
+
> This skill defines how an AI agent should behave when its role is to
|
|
4
|
+
> browse the web, retrieve information, and interact with web pages.
|
|
5
|
+
|
|
6
|
+
## Role Description
|
|
7
|
+
|
|
8
|
+
You are a **Web Browser Agent**. Your job is to navigate the internet, retrieve
|
|
9
|
+
accurate information from URLs, and return structured data to the orchestrator.
|
|
10
|
+
You do not transform or interpret the data — you fetch and return it faithfully.
|
|
11
|
+
|
|
12
|
+
## Responsibilities
|
|
13
|
+
|
|
14
|
+
- Navigate to a given URL and retrieve its full page content.
|
|
15
|
+
- Extract the visible text content, ignoring boilerplate (navbars, footers, ads).
|
|
16
|
+
- Follow links within a domain when explicitly instructed to crawl.
|
|
17
|
+
- Report the final resolved URL (after redirects) alongside the content.
|
|
18
|
+
- Respect `robots.txt` and avoid rate-limiting target servers.
|
|
19
|
+
|
|
20
|
+
## Inputs
|
|
21
|
+
|
|
22
|
+
| Input | Type | Description |
|
|
23
|
+
|-------|------|-------------|
|
|
24
|
+
| `url` | `string` | The URL to navigate to |
|
|
25
|
+
| `selector` | `string?` | Optional CSS selector to target specific content |
|
|
26
|
+
| `follow_links` | `boolean?` | Whether to follow internal links (default: `false`) |
|
|
27
|
+
|
|
28
|
+
## Outputs
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"url": "https://example.com/page",
|
|
33
|
+
"title": "Page Title",
|
|
34
|
+
"content": "Extracted text content...",
|
|
35
|
+
"links": ["https://example.com/related"],
|
|
36
|
+
"status": 200
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Constraints
|
|
41
|
+
|
|
42
|
+
- **Do not execute** scripts or interact with dynamic JavaScript unless using a headless browser tool.
|
|
43
|
+
- **Do not** store credentials or session cookies beyond the current task.
|
|
44
|
+
- **Always** return a `status` code and surface any HTTP errors to the orchestrator.
|
|
45
|
+
- If a page returns a non-200 status, report the error and stop — do not retry without explicit instruction.
|
|
46
|
+
|
|
47
|
+
## Error Handling
|
|
48
|
+
|
|
49
|
+
| Error | Response |
|
|
50
|
+
|-------|----------|
|
|
51
|
+
| `404 Not Found` | Report to orchestrator; do not guess alternative URLs |
|
|
52
|
+
| `403 Forbidden` | Report to orchestrator; do not attempt bypass |
|
|
53
|
+
| `Timeout` | Retry once after 5 seconds; report failure if still unresponsive |
|
|
54
|
+
| `Invalid URL` | Return an error immediately without making a network request |
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs/promises');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Read all .md files from a directory and return their concatenated content.
|
|
10
|
+
* @param {string} dir
|
|
11
|
+
* @returns {Promise<string>}
|
|
12
|
+
*/
|
|
13
|
+
async function readMarkdownDir(dir) {
|
|
14
|
+
let content = '';
|
|
15
|
+
try {
|
|
16
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
17
|
+
for (const entry of entries) {
|
|
18
|
+
if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
19
|
+
const filePath = path.join(dir, entry.name);
|
|
20
|
+
const fileContent = await fs.readFile(filePath, 'utf-8');
|
|
21
|
+
const section = entry.name.replace('.md', '').replace(/-/g, ' ');
|
|
22
|
+
content += `\n## ${section}\n\n${fileContent}\n\n---\n`;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
} catch {
|
|
26
|
+
// Directory may not exist; silently skip
|
|
27
|
+
}
|
|
28
|
+
return content;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Ensure a directory exists, creating it if necessary.
|
|
33
|
+
* @param {string} dirPath
|
|
34
|
+
*/
|
|
35
|
+
async function ensureDir(dirPath) {
|
|
36
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Copy all files from a source directory to a destination directory.
|
|
41
|
+
* @param {string} src
|
|
42
|
+
* @param {string} dest
|
|
43
|
+
*/
|
|
44
|
+
async function copyDir(src, dest) {
|
|
45
|
+
await ensureDir(dest);
|
|
46
|
+
const entries = await fs.readdir(src, { withFileTypes: true });
|
|
47
|
+
for (const entry of entries) {
|
|
48
|
+
if (entry.isFile()) {
|
|
49
|
+
await fs.copyFile(
|
|
50
|
+
path.join(src, entry.name),
|
|
51
|
+
path.join(dest, entry.name)
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ─── Cursor ──────────────────────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Sync .agent/rules/ → .cursor/rules/
|
|
61
|
+
* @param {string} agentDir
|
|
62
|
+
* @param {string} cwd
|
|
63
|
+
*/
|
|
64
|
+
async function syncToCursor(agentDir, cwd) {
|
|
65
|
+
const src = path.join(agentDir, 'rules');
|
|
66
|
+
const dest = path.join(cwd, '.cursor', 'rules');
|
|
67
|
+
await copyDir(src, dest);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ─── Claude ──────────────────────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Compile .agent/ into a single CLAUDE.md file.
|
|
74
|
+
* @param {string} agentDir
|
|
75
|
+
* @param {string} cwd
|
|
76
|
+
* @param {string} techStack
|
|
77
|
+
*/
|
|
78
|
+
async function syncToClaude(agentDir, cwd, techStack) {
|
|
79
|
+
const rulesContent = await readMarkdownDir(path.join(agentDir, 'rules'));
|
|
80
|
+
const skillsContent = await readMarkdownDir(path.join(agentDir, 'skills'));
|
|
81
|
+
|
|
82
|
+
const claudeMd = `# CLAUDE.md — AI Agent Instructions
|
|
83
|
+
|
|
84
|
+
> This file was auto-generated by [agentive](https://github.com/TiPS0/agentive).
|
|
85
|
+
> Edit the source files in \`.agent/\` and re-run \`npx @p_tipso/agentive\` to regenerate.
|
|
86
|
+
|
|
87
|
+
## Project Context
|
|
88
|
+
|
|
89
|
+
**Tech Stack:** ${techStack}
|
|
90
|
+
|
|
91
|
+
# Rules
|
|
92
|
+
${rulesContent}
|
|
93
|
+
|
|
94
|
+
# Skills
|
|
95
|
+
${skillsContent}
|
|
96
|
+
`.trim();
|
|
97
|
+
|
|
98
|
+
await fs.writeFile(path.join(cwd, 'CLAUDE.md'), claudeMd, 'utf-8');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ─── Windsurf ────────────────────────────────────────────────────────────────
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Compile .agent/rules/ into a single .windsurfrules file.
|
|
105
|
+
* @param {string} agentDir
|
|
106
|
+
* @param {string} cwd
|
|
107
|
+
* @param {string} techStack
|
|
108
|
+
*/
|
|
109
|
+
async function syncToWindsurf(agentDir, cwd, techStack) {
|
|
110
|
+
const rulesContent = await readMarkdownDir(path.join(agentDir, 'rules'));
|
|
111
|
+
const skillsContent = await readMarkdownDir(path.join(agentDir, 'skills'));
|
|
112
|
+
|
|
113
|
+
const windsurfRules = `# .windsurfrules — AI Agent Instructions
|
|
114
|
+
|
|
115
|
+
> Auto-generated by [agentive](https://github.com/TiPS0/agentive).
|
|
116
|
+
> Edit \`.agent/\` and re-run \`npx @p_tipso/agentive\` to regenerate.
|
|
117
|
+
|
|
118
|
+
**Tech Stack:** ${techStack}
|
|
119
|
+
|
|
120
|
+
# Rules
|
|
121
|
+
${rulesContent}
|
|
122
|
+
|
|
123
|
+
# Skills
|
|
124
|
+
${skillsContent}
|
|
125
|
+
`.trim();
|
|
126
|
+
|
|
127
|
+
await fs.writeFile(path.join(cwd, '.windsurfrules'), windsurfRules, 'utf-8');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
module.exports = { syncToCursor, syncToClaude, syncToWindsurf };
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs/promises');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
// ─── Guards ──────────────────────────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Check if a .agent/ directory already exists in the given working directory.
|
|
10
|
+
* @param {string} cwd
|
|
11
|
+
* @returns {Promise<boolean>}
|
|
12
|
+
*/
|
|
13
|
+
async function agentDirectoryExists(cwd) {
|
|
14
|
+
try {
|
|
15
|
+
await fs.access(path.join(cwd, '.agent'));
|
|
16
|
+
return true;
|
|
17
|
+
} catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ─── Directory Creation ───────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Create (or recreate) the .agent/ directory structure inside the user's project.
|
|
26
|
+
*
|
|
27
|
+
* Structure:
|
|
28
|
+
* .agent/
|
|
29
|
+
* ├── settings.json
|
|
30
|
+
* ├── settings.local.json
|
|
31
|
+
* ├── rules/
|
|
32
|
+
* └── skills/
|
|
33
|
+
*
|
|
34
|
+
* @param {string} cwd - The user's current working directory
|
|
35
|
+
* @param {boolean} overwrite - Whether to remove an existing .agent/ directory first
|
|
36
|
+
* @returns {Promise<string>} The path to the created .agent/ directory
|
|
37
|
+
*/
|
|
38
|
+
async function createAgentDirectory(cwd, overwrite = false) {
|
|
39
|
+
const agentDir = path.join(cwd, '.agent');
|
|
40
|
+
|
|
41
|
+
if (overwrite) {
|
|
42
|
+
await fs.rm(agentDir, { recursive: true, force: true });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
await fs.mkdir(agentDir, { recursive: true });
|
|
46
|
+
await fs.mkdir(path.join(agentDir, 'rules'), { recursive: true });
|
|
47
|
+
await fs.mkdir(path.join(agentDir, 'skills'), { recursive: true });
|
|
48
|
+
|
|
49
|
+
return agentDir;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ─── Settings Files ───────────────────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Write settings.json and settings.local.json inside .agent/.
|
|
56
|
+
*
|
|
57
|
+
* - settings.json → tracked by git (project-wide config)
|
|
58
|
+
* - settings.local.json → gitignored (machine-specific overrides)
|
|
59
|
+
*
|
|
60
|
+
* Also appends `.agent/settings.local.json` to the project's .gitignore if one exists.
|
|
61
|
+
*
|
|
62
|
+
* @param {string} agentDir - Absolute path to .agent/
|
|
63
|
+
* @param {object} settings - The wizard answers to persist
|
|
64
|
+
*/
|
|
65
|
+
async function writeSettings(agentDir, settings) {
|
|
66
|
+
// settings.json — committed to git
|
|
67
|
+
const settingsJson = {
|
|
68
|
+
projectName: settings.projectName,
|
|
69
|
+
techStack: settings.techStack,
|
|
70
|
+
tools: settings.tools,
|
|
71
|
+
agentiveVersion: settings.agentiveVersion,
|
|
72
|
+
createdAt: settings.createdAt,
|
|
73
|
+
};
|
|
74
|
+
await fs.writeFile(
|
|
75
|
+
path.join(agentDir, 'settings.json'),
|
|
76
|
+
JSON.stringify(settingsJson, null, 2) + '\n',
|
|
77
|
+
'utf-8'
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
// settings.local.json — NOT committed (machine-specific overrides)
|
|
81
|
+
const localSettingsJson = {
|
|
82
|
+
_note: 'This file is gitignored. Add local machine-specific overrides here.',
|
|
83
|
+
overrides: {},
|
|
84
|
+
};
|
|
85
|
+
await fs.writeFile(
|
|
86
|
+
path.join(agentDir, 'settings.local.json'),
|
|
87
|
+
JSON.stringify(localSettingsJson, null, 2) + '\n',
|
|
88
|
+
'utf-8'
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
// Auto-append to .gitignore if it exists in cwd
|
|
92
|
+
const cwd = path.dirname(agentDir);
|
|
93
|
+
const gitignorePath = path.join(cwd, '.gitignore');
|
|
94
|
+
try {
|
|
95
|
+
let gitignore = await fs.readFile(gitignorePath, 'utf-8');
|
|
96
|
+
const entry = '.agent/settings.local.json';
|
|
97
|
+
if (!gitignore.includes(entry)) {
|
|
98
|
+
gitignore += `\n# Agentive local settings (machine-specific)\n${entry}\n`;
|
|
99
|
+
await fs.writeFile(gitignorePath, gitignore, 'utf-8');
|
|
100
|
+
}
|
|
101
|
+
} catch {
|
|
102
|
+
// No .gitignore present — silently skip
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ─── Template Copying ─────────────────────────────────────────────────────────
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Copy all template files from src/templates/ into the .agent/ directory.
|
|
110
|
+
* Replaces {{TECH_STACK}} and {{YEAR}} placeholders in template files.
|
|
111
|
+
*
|
|
112
|
+
* Note: The context/ folder from templates is not copied (not part of .agent/ structure).
|
|
113
|
+
*
|
|
114
|
+
* @param {string} templatesDir - Absolute path to src/templates/
|
|
115
|
+
* @param {string} destDir - Absolute path to .agent/
|
|
116
|
+
* @param {string} techStack - The user's tech stack string
|
|
117
|
+
*/
|
|
118
|
+
async function copyTemplates(templatesDir, destDir, techStack) {
|
|
119
|
+
// Only copy rules/ and skills/ — context/ is dropped from .agent/ structure
|
|
120
|
+
const foldersToInclude = ['rules', 'skills'];
|
|
121
|
+
|
|
122
|
+
for (const folder of foldersToInclude) {
|
|
123
|
+
const srcFolder = path.join(templatesDir, folder);
|
|
124
|
+
const destFolder = path.join(destDir, folder);
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
await fs.access(srcFolder); // skip if template folder doesn't exist
|
|
128
|
+
} catch {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
await copyDirectoryRecursive(srcFolder, destFolder, techStack);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Internal: recursively copies files from src to dest, replacing placeholders.
|
|
138
|
+
* @param {string} src
|
|
139
|
+
* @param {string} dest
|
|
140
|
+
* @param {string} techStack
|
|
141
|
+
*/
|
|
142
|
+
async function copyDirectoryRecursive(src, dest, techStack) {
|
|
143
|
+
const entries = await fs.readdir(src, { withFileTypes: true });
|
|
144
|
+
|
|
145
|
+
for (const entry of entries) {
|
|
146
|
+
const srcPath = path.join(src, entry.name);
|
|
147
|
+
const destPath = path.join(dest, entry.name);
|
|
148
|
+
|
|
149
|
+
if (entry.isDirectory()) {
|
|
150
|
+
await fs.mkdir(destPath, { recursive: true });
|
|
151
|
+
await copyDirectoryRecursive(srcPath, destPath, techStack);
|
|
152
|
+
} else {
|
|
153
|
+
let content = await fs.readFile(srcPath, 'utf-8');
|
|
154
|
+
content = content
|
|
155
|
+
.replace(/\{\{TECH_STACK\}\}/g, techStack)
|
|
156
|
+
.replace(/\{\{YEAR\}\}/g, new Date().getFullYear().toString());
|
|
157
|
+
await fs.writeFile(destPath, content, 'utf-8');
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
module.exports = {
|
|
163
|
+
agentDirectoryExists,
|
|
164
|
+
createAgentDirectory,
|
|
165
|
+
writeSettings,
|
|
166
|
+
copyTemplates,
|
|
167
|
+
};
|