ts-procedures 5.2.0 → 5.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/README.md +60 -0
- package/agent_config/bin/postinstall.mjs +105 -0
- package/agent_config/bin/setup.mjs +286 -0
- package/agent_config/claude-code/.claude-plugin/plugin.json +5 -0
- package/agent_config/claude-code/agents/ts-procedures-architect.md +173 -0
- package/agent_config/claude-code/skills/guide/SKILL.md +142 -0
- package/agent_config/claude-code/skills/guide/anti-patterns.md +502 -0
- package/agent_config/claude-code/skills/guide/api-reference.md +550 -0
- package/agent_config/claude-code/skills/guide/patterns.md +572 -0
- package/agent_config/claude-code/skills/review/SKILL.md +53 -0
- package/agent_config/claude-code/skills/review/checklist.md +141 -0
- package/agent_config/claude-code/skills/scaffold/SKILL.md +54 -0
- package/agent_config/claude-code/skills/scaffold/templates/express-rpc.md +134 -0
- package/agent_config/claude-code/skills/scaffold/templates/hono-rpc.md +139 -0
- package/agent_config/claude-code/skills/scaffold/templates/hono-stream.md +134 -0
- package/agent_config/claude-code/skills/scaffold/templates/procedure.md +77 -0
- package/agent_config/claude-code/skills/scaffold/templates/stream-procedure.md +113 -0
- package/agent_config/copilot/copilot-instructions.md +255 -0
- package/agent_config/cursor/cursorrules +255 -0
- package/agent_config/lib/install-claude.mjs +109 -0
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -771,6 +771,66 @@ import {
|
|
|
771
771
|
} from 'ts-procedures'
|
|
772
772
|
```
|
|
773
773
|
|
|
774
|
+
## AI Agent Setup
|
|
775
|
+
|
|
776
|
+
ts-procedures ships with built-in AI assistant configuration for **Claude Code**, **Cursor**, and **GitHub Copilot**. This gives AI tools framework-aware context when writing ts-procedures code in your project.
|
|
777
|
+
|
|
778
|
+
### Quick Setup
|
|
779
|
+
|
|
780
|
+
```bash
|
|
781
|
+
npx ts-procedures-setup
|
|
782
|
+
```
|
|
783
|
+
|
|
784
|
+
This installs rules for all supported AI tools. You can also target specific tools:
|
|
785
|
+
|
|
786
|
+
```bash
|
|
787
|
+
npx ts-procedures-setup claude # Claude Code only
|
|
788
|
+
npx ts-procedures-setup cursor # Cursor only
|
|
789
|
+
npx ts-procedures-setup copilot # GitHub Copilot only
|
|
790
|
+
```
|
|
791
|
+
|
|
792
|
+
### What Gets Installed
|
|
793
|
+
|
|
794
|
+
| Tool | Files | Auto-updates? |
|
|
795
|
+
|------|-------|---------------|
|
|
796
|
+
| **Claude Code** | `.claude/rules/ts-procedures.md`, `.claude/commands/ts-procedures-scaffold.md`, `.claude/commands/ts-procedures-review.md`, `.claude/agents/ts-procedures-architect.md` | Yes |
|
|
797
|
+
| **Cursor** | `.cursorrules` (marker-based section) | Yes |
|
|
798
|
+
| **GitHub Copilot** | `.github/copilot-instructions.md` (marker-based section) | Yes |
|
|
799
|
+
|
|
800
|
+
### Auto-Updates
|
|
801
|
+
|
|
802
|
+
After initial setup, rules are automatically refreshed on every `npm install` or `npm update`. When ts-procedures publishes a new version, your AI tools get the latest framework guidance without any manual steps.
|
|
803
|
+
|
|
804
|
+
### Claude Code Features
|
|
805
|
+
|
|
806
|
+
Once installed, Claude Code gets:
|
|
807
|
+
|
|
808
|
+
- **Framework reference** — auto-loaded rules with core API, schema system, error handling, and decision framework
|
|
809
|
+
- **Scaffold command** — `/project:ts-procedures-scaffold <type> <Name>` generates procedures, streams, and HTTP setups with correct patterns
|
|
810
|
+
- **Review command** — `/project:ts-procedures-review <path>` checks code against a 60+ item checklist
|
|
811
|
+
- **Architecture agent** — `ts-procedures-architect` helps plan procedure structure, schema design, and HTTP implementation choices
|
|
812
|
+
|
|
813
|
+
### CLI Options
|
|
814
|
+
|
|
815
|
+
```bash
|
|
816
|
+
npx ts-procedures-setup --force # Overwrite without prompting
|
|
817
|
+
npx ts-procedures-setup --dry-run # Preview what would be created/updated
|
|
818
|
+
npx ts-procedures-setup --check # Exit with code 1 if files are outdated (for CI)
|
|
819
|
+
```
|
|
820
|
+
|
|
821
|
+
### Gitignore
|
|
822
|
+
|
|
823
|
+
The `.claude/` files are auto-generated and regenerated on `npm install`. You can add them to `.gitignore`:
|
|
824
|
+
|
|
825
|
+
```gitignore
|
|
826
|
+
# Auto-generated AI agent rules (regenerated on npm install)
|
|
827
|
+
.claude/rules/ts-procedures.md
|
|
828
|
+
.claude/commands/ts-procedures-*.md
|
|
829
|
+
.claude/agents/ts-procedures-*.md
|
|
830
|
+
```
|
|
831
|
+
|
|
832
|
+
Cursor and Copilot files use marker-based sections that coexist with your own rules, so they should typically be committed.
|
|
833
|
+
|
|
774
834
|
## License
|
|
775
835
|
|
|
776
836
|
MIT
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
4
|
+
import { resolve, join, dirname } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = dirname(__filename);
|
|
9
|
+
const AGENT_CONFIG_DIR = resolve(__dirname, '..');
|
|
10
|
+
|
|
11
|
+
const MARKER_BEGIN = '<!-- BEGIN ts-procedures -->';
|
|
12
|
+
const MARKER_END = '<!-- END ts-procedures -->';
|
|
13
|
+
|
|
14
|
+
// INIT_CWD is set by npm/yarn/pnpm during lifecycle scripts — points to the project root
|
|
15
|
+
const projectRoot = process.env.INIT_CWD;
|
|
16
|
+
|
|
17
|
+
if (!projectRoot) {
|
|
18
|
+
process.exit(0);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Detect self-install (developing ts-procedures itself) — skip
|
|
22
|
+
const ownPackageRoot = resolve(__dirname, '../..');
|
|
23
|
+
if (resolve(projectRoot) === ownPackageRoot) {
|
|
24
|
+
process.exit(0);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ─── Helpers ──────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
function readSourceFile(relativePath) {
|
|
30
|
+
return readFileSync(join(AGENT_CONFIG_DIR, relativePath), 'utf-8');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function wrapWithMarkers(content) {
|
|
34
|
+
return `${MARKER_BEGIN}\n${content.trim()}\n${MARKER_END}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function replaceMarkerSection(existingContent, newContent) {
|
|
38
|
+
const beginIdx = existingContent.indexOf(MARKER_BEGIN);
|
|
39
|
+
const endIdx = existingContent.indexOf(MARKER_END);
|
|
40
|
+
|
|
41
|
+
if (beginIdx !== -1 && endIdx !== -1) {
|
|
42
|
+
const before = existingContent.slice(0, beginIdx);
|
|
43
|
+
const after = existingContent.slice(endIdx + MARKER_END.length);
|
|
44
|
+
return before + wrapWithMarkers(newContent) + after;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return null; // No existing markers — don't modify
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function updateMarkedFile(targetPath, sourceRelativePath) {
|
|
51
|
+
if (!existsSync(targetPath)) return false;
|
|
52
|
+
|
|
53
|
+
const existing = readFileSync(targetPath, 'utf-8');
|
|
54
|
+
if (!existing.includes(MARKER_BEGIN)) return false;
|
|
55
|
+
|
|
56
|
+
const sourceContent = readSourceFile(sourceRelativePath);
|
|
57
|
+
const updated = replaceMarkerSection(existing, sourceContent);
|
|
58
|
+
if (updated) {
|
|
59
|
+
writeFileSync(targetPath, updated, 'utf-8');
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ─── Auto-update ──────────────────────────────────────────
|
|
66
|
+
|
|
67
|
+
const rulesFile = join(projectRoot, '.claude', 'rules', 'ts-procedures.md');
|
|
68
|
+
const cursorFile = join(projectRoot, '.cursorrules');
|
|
69
|
+
const copilotFile = join(projectRoot, '.github', 'copilot-instructions.md');
|
|
70
|
+
|
|
71
|
+
// Check if any target was previously set up
|
|
72
|
+
const hasAnySetup = existsSync(rulesFile) || existsSync(cursorFile) || existsSync(copilotFile);
|
|
73
|
+
|
|
74
|
+
if (hasAnySetup) {
|
|
75
|
+
try {
|
|
76
|
+
const updated = [];
|
|
77
|
+
|
|
78
|
+
// Auto-update Claude Code files
|
|
79
|
+
if (existsSync(rulesFile)) {
|
|
80
|
+
const { installClaude } = await import('../lib/install-claude.mjs');
|
|
81
|
+
installClaude(projectRoot);
|
|
82
|
+
updated.push('Claude Code');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Auto-update Cursor rules (only if markers present)
|
|
86
|
+
if (updateMarkedFile(cursorFile, 'cursor/cursorrules')) {
|
|
87
|
+
updated.push('Cursor');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Auto-update Copilot instructions (only if markers present)
|
|
91
|
+
if (updateMarkedFile(copilotFile, 'copilot/copilot-instructions.md')) {
|
|
92
|
+
updated.push('Copilot');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (updated.length > 0) {
|
|
96
|
+
console.log('');
|
|
97
|
+
console.log(` ts-procedures — AI agent rules updated (${updated.join(', ')})`);
|
|
98
|
+
console.log('');
|
|
99
|
+
}
|
|
100
|
+
} catch {
|
|
101
|
+
// Never break npm install
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// No invitation message if not opted in — rely on README / npx ts-procedures-setup --help
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
4
|
+
import { join, dirname, resolve } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { createInterface } from 'node:readline';
|
|
7
|
+
import { installClaude } from '../lib/install-claude.mjs';
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = dirname(__filename);
|
|
11
|
+
const AGENT_CONFIG_DIR = resolve(__dirname, '..');
|
|
12
|
+
const PROJECT_DIR = process.cwd();
|
|
13
|
+
|
|
14
|
+
const MARKER_BEGIN = '<!-- BEGIN ts-procedures -->';
|
|
15
|
+
const MARKER_END = '<!-- END ts-procedures -->';
|
|
16
|
+
|
|
17
|
+
const VALID_TARGETS = ['claude', 'cursor', 'copilot', 'all'];
|
|
18
|
+
|
|
19
|
+
// ─── Helpers ──────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
function printUsage() {
|
|
22
|
+
console.log(`
|
|
23
|
+
Usage: npx ts-procedures-setup [targets...] [options]
|
|
24
|
+
|
|
25
|
+
Targets:
|
|
26
|
+
claude Install Claude Code rules and commands into .claude/
|
|
27
|
+
cursor Copy ts-procedures rules to .cursorrules
|
|
28
|
+
copilot Copy ts-procedures instructions to .github/copilot-instructions.md
|
|
29
|
+
all Set up all targets (default)
|
|
30
|
+
|
|
31
|
+
Options:
|
|
32
|
+
--help Show this help message
|
|
33
|
+
--force Overwrite existing files without prompting
|
|
34
|
+
--dry-run Show what would be created/updated without writing files
|
|
35
|
+
--check Exit with code 1 if any files are outdated (useful in CI)
|
|
36
|
+
|
|
37
|
+
Examples:
|
|
38
|
+
npx ts-procedures-setup # Set up all targets
|
|
39
|
+
npx ts-procedures-setup cursor # Set up Cursor only
|
|
40
|
+
npx ts-procedures-setup cursor copilot # Set up Cursor and Copilot
|
|
41
|
+
npx ts-procedures-setup --force # Overwrite without prompting
|
|
42
|
+
npx ts-procedures-setup --dry-run # Preview changes
|
|
43
|
+
npx ts-procedures-setup --check # Verify files are up to date
|
|
44
|
+
`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function readSourceFile(relativePath) {
|
|
48
|
+
const fullPath = join(AGENT_CONFIG_DIR, relativePath);
|
|
49
|
+
return readFileSync(fullPath, 'utf-8');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function wrapWithMarkers(content) {
|
|
53
|
+
return `${MARKER_BEGIN}\n${content.trim()}\n${MARKER_END}`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function replaceMarkerSection(existingContent, newContent) {
|
|
57
|
+
const beginIdx = existingContent.indexOf(MARKER_BEGIN);
|
|
58
|
+
const endIdx = existingContent.indexOf(MARKER_END);
|
|
59
|
+
|
|
60
|
+
if (beginIdx !== -1 && endIdx !== -1) {
|
|
61
|
+
const before = existingContent.slice(0, beginIdx);
|
|
62
|
+
const after = existingContent.slice(endIdx + MARKER_END.length);
|
|
63
|
+
return before + wrapWithMarkers(newContent) + after;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const separator = existingContent.trim() ? '\n\n' : '';
|
|
67
|
+
return existingContent.trim() + separator + wrapWithMarkers(newContent) + '\n';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function confirm(message) {
|
|
71
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
72
|
+
return new Promise((resolve) => {
|
|
73
|
+
rl.question(`${message} (y/N) `, (answer) => {
|
|
74
|
+
rl.close();
|
|
75
|
+
resolve(answer.toLowerCase() === 'y');
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ─── Check Mode ───────────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
function isFileOutdated(targetPath, expectedContent) {
|
|
83
|
+
if (!existsSync(targetPath)) return true;
|
|
84
|
+
const current = readFileSync(targetPath, 'utf-8');
|
|
85
|
+
return current !== expectedContent;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ─── Target Handlers ──────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
function setupClaude({ dryRun, check } = {}) {
|
|
91
|
+
if (dryRun) {
|
|
92
|
+
const claudeFiles = [
|
|
93
|
+
'.claude/rules/ts-procedures.md',
|
|
94
|
+
'.claude/commands/ts-procedures-scaffold.md',
|
|
95
|
+
'.claude/commands/ts-procedures-review.md',
|
|
96
|
+
'.claude/agents/ts-procedures-architect.md',
|
|
97
|
+
];
|
|
98
|
+
console.log('\n Claude Code (dry run)');
|
|
99
|
+
console.log(' ─────────────────────');
|
|
100
|
+
for (const f of claudeFiles) {
|
|
101
|
+
const status = existsSync(join(PROJECT_DIR, f)) ? 'update' : 'create';
|
|
102
|
+
console.log(` [${status}] ${f}`);
|
|
103
|
+
}
|
|
104
|
+
console.log('');
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const { files } = installClaude(PROJECT_DIR);
|
|
109
|
+
|
|
110
|
+
if (check) return false; // installClaude always writes — check mode handled at main level
|
|
111
|
+
|
|
112
|
+
console.log('\n Claude Code');
|
|
113
|
+
console.log(' ───────────');
|
|
114
|
+
console.log(' Installed (updates automatically on npm install/update):\n');
|
|
115
|
+
for (const f of files) {
|
|
116
|
+
console.log(` ${f}`);
|
|
117
|
+
}
|
|
118
|
+
console.log('');
|
|
119
|
+
console.log(' Commands: /project:ts-procedures-scaffold, /project:ts-procedures-review');
|
|
120
|
+
console.log(' Agent: ts-procedures-architect (architecture planning)');
|
|
121
|
+
console.log(' Reference: Auto-loaded via .claude/rules/ts-procedures.md\n');
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function setupCursor(force, { dryRun, check } = {}) {
|
|
126
|
+
const targetPath = join(PROJECT_DIR, '.cursorrules');
|
|
127
|
+
const sourceContent = readSourceFile('cursor/cursorrules');
|
|
128
|
+
|
|
129
|
+
if (dryRun || check) {
|
|
130
|
+
const exists = existsSync(targetPath);
|
|
131
|
+
const hasMarkers = exists && readFileSync(targetPath, 'utf-8').includes(MARKER_BEGIN);
|
|
132
|
+
const label = dryRun ? 'dry run' : 'check';
|
|
133
|
+
const status = !exists ? 'create' : hasMarkers ? 'update' : 'append';
|
|
134
|
+
console.log(`\n Cursor (${label})`);
|
|
135
|
+
console.log(` ──────`);
|
|
136
|
+
console.log(` [${status}] .cursorrules\n`);
|
|
137
|
+
return check && (status === 'create' || status === 'update');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
console.log('\n Cursor');
|
|
141
|
+
console.log(' ──────');
|
|
142
|
+
|
|
143
|
+
if (existsSync(targetPath)) {
|
|
144
|
+
const existing = readFileSync(targetPath, 'utf-8');
|
|
145
|
+
|
|
146
|
+
if (existing.includes(MARKER_BEGIN)) {
|
|
147
|
+
const updated = replaceMarkerSection(existing, sourceContent);
|
|
148
|
+
writeFileSync(targetPath, updated, 'utf-8');
|
|
149
|
+
console.log(' Updated ts-procedures section in .cursorrules\n');
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (!force) {
|
|
154
|
+
const ok = await confirm(' .cursorrules already exists. Append ts-procedures rules?');
|
|
155
|
+
if (!ok) {
|
|
156
|
+
console.log(' Skipped.\n');
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const updated = replaceMarkerSection(existing, sourceContent);
|
|
162
|
+
writeFileSync(targetPath, updated, 'utf-8');
|
|
163
|
+
console.log(' Appended ts-procedures rules to .cursorrules\n');
|
|
164
|
+
} else {
|
|
165
|
+
writeFileSync(targetPath, wrapWithMarkers(sourceContent) + '\n', 'utf-8');
|
|
166
|
+
console.log(' Created .cursorrules\n');
|
|
167
|
+
}
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function setupCopilot(force, { dryRun, check } = {}) {
|
|
172
|
+
const githubDir = join(PROJECT_DIR, '.github');
|
|
173
|
+
const targetPath = join(githubDir, 'copilot-instructions.md');
|
|
174
|
+
const sourceContent = readSourceFile('copilot/copilot-instructions.md');
|
|
175
|
+
|
|
176
|
+
if (dryRun || check) {
|
|
177
|
+
const exists = existsSync(targetPath);
|
|
178
|
+
const hasMarkers = exists && readFileSync(targetPath, 'utf-8').includes(MARKER_BEGIN);
|
|
179
|
+
const label = dryRun ? 'dry run' : 'check';
|
|
180
|
+
const status = !exists ? 'create' : hasMarkers ? 'update' : 'append';
|
|
181
|
+
console.log(`\n GitHub Copilot (${label})`);
|
|
182
|
+
console.log(` ──────────────`);
|
|
183
|
+
console.log(` [${status}] .github/copilot-instructions.md\n`);
|
|
184
|
+
return check && (status === 'create' || status === 'update');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
console.log('\n GitHub Copilot');
|
|
188
|
+
console.log(' ──────────────');
|
|
189
|
+
|
|
190
|
+
if (!existsSync(githubDir)) {
|
|
191
|
+
mkdirSync(githubDir, { recursive: true });
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (existsSync(targetPath)) {
|
|
195
|
+
const existing = readFileSync(targetPath, 'utf-8');
|
|
196
|
+
|
|
197
|
+
if (existing.includes(MARKER_BEGIN)) {
|
|
198
|
+
const updated = replaceMarkerSection(existing, sourceContent);
|
|
199
|
+
writeFileSync(targetPath, updated, 'utf-8');
|
|
200
|
+
console.log(' Updated ts-procedures section in .github/copilot-instructions.md\n');
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (!force) {
|
|
205
|
+
const ok = await confirm(' .github/copilot-instructions.md already exists. Append ts-procedures instructions?');
|
|
206
|
+
if (!ok) {
|
|
207
|
+
console.log(' Skipped.\n');
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const updated = replaceMarkerSection(existing, sourceContent);
|
|
213
|
+
writeFileSync(targetPath, updated, 'utf-8');
|
|
214
|
+
console.log(' Appended ts-procedures instructions to .github/copilot-instructions.md\n');
|
|
215
|
+
} else {
|
|
216
|
+
writeFileSync(targetPath, wrapWithMarkers(sourceContent) + '\n', 'utf-8');
|
|
217
|
+
console.log(' Created .github/copilot-instructions.md\n');
|
|
218
|
+
}
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ─── Main ─────────────────────────────────────────────────
|
|
223
|
+
|
|
224
|
+
async function main() {
|
|
225
|
+
const args = process.argv.slice(2);
|
|
226
|
+
const force = args.includes('--force');
|
|
227
|
+
const dryRun = args.includes('--dry-run');
|
|
228
|
+
const check = args.includes('--check');
|
|
229
|
+
const help = args.includes('--help') || args.includes('-h');
|
|
230
|
+
const targets = args.filter(a => !a.startsWith('--') && !a.startsWith('-'));
|
|
231
|
+
|
|
232
|
+
if (help) {
|
|
233
|
+
printUsage();
|
|
234
|
+
process.exit(0);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
for (const t of targets) {
|
|
238
|
+
if (!VALID_TARGETS.includes(t)) {
|
|
239
|
+
console.error(`Unknown target: "${t}". Valid targets: ${VALID_TARGETS.join(', ')}`);
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const selectedTargets = targets.length === 0 || targets.includes('all')
|
|
245
|
+
? ['claude', 'cursor', 'copilot']
|
|
246
|
+
: targets;
|
|
247
|
+
|
|
248
|
+
const mode = dryRun ? ' (dry run)' : check ? ' (check)' : '';
|
|
249
|
+
console.log(`\nts-procedures AI Agent Setup${mode}`);
|
|
250
|
+
console.log('════════════════════════════');
|
|
251
|
+
|
|
252
|
+
const opts = { dryRun, check };
|
|
253
|
+
let outdated = false;
|
|
254
|
+
|
|
255
|
+
if (selectedTargets.includes('claude')) {
|
|
256
|
+
outdated = setupClaude(opts) || outdated;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (selectedTargets.includes('cursor')) {
|
|
260
|
+
outdated = (await setupCursor(force, opts)) || outdated;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (selectedTargets.includes('copilot')) {
|
|
264
|
+
outdated = (await setupCopilot(force, opts)) || outdated;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (check) {
|
|
268
|
+
if (outdated) {
|
|
269
|
+
console.log(' Files are outdated. Run: npx ts-procedures-setup\n');
|
|
270
|
+
process.exit(1);
|
|
271
|
+
} else {
|
|
272
|
+
console.log(' All files are up to date.\n');
|
|
273
|
+
}
|
|
274
|
+
} else if (dryRun) {
|
|
275
|
+
console.log(' No files were modified (dry run).\n');
|
|
276
|
+
} else {
|
|
277
|
+
console.log(' Tip: Auto-generated .claude/ files can be added to .gitignore');
|
|
278
|
+
console.log(' (they are regenerated on npm install).\n');
|
|
279
|
+
console.log('Done!\n');
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
main().catch((err) => {
|
|
284
|
+
console.error('Error:', err.message);
|
|
285
|
+
process.exit(1);
|
|
286
|
+
});
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ts-procedures",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "AI coding assistant plugin for ts-procedures — a TypeScript RPC framework for type-safe, schema-validated procedure calls with automatic validation, streaming support, and HTTP framework integrations."
|
|
5
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ts-procedures-architect
|
|
3
|
+
description: "Architecture planning agent for ts-procedures RPC applications. Decides procedure structure, schema design, context shape, HTTP implementation choice, error handling strategy, and streaming architecture. Use when planning APIs, designing procedure sets, or choosing between Express/Hono implementations."
|
|
4
|
+
model: sonnet
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You are an architecture planning agent for applications built with **ts-procedures**, a TypeScript RPC framework that creates type-safe, schema-validated procedure calls. You help developers plan APIs by deciding procedure structure, schema design, context shape, and HTTP integration strategy.
|
|
8
|
+
|
|
9
|
+
## Core Concepts
|
|
10
|
+
|
|
11
|
+
| Concept | Role |
|
|
12
|
+
|---------|------|
|
|
13
|
+
| `Procedures<TContext, TExtendedConfig>(builder?)` | Factory that creates `Create` and `CreateStream` functions scoped to a shared context type |
|
|
14
|
+
| `Create(name, config, handler)` | Registers a standard async procedure with optional schema validation |
|
|
15
|
+
| `CreateStream(name, config, handler)` | Registers a streaming procedure (async generator) with AbortSignal support |
|
|
16
|
+
| `TContext` | Base context type injected into all handlers (auth, request info, services) |
|
|
17
|
+
| `TExtendedConfig` | Additional config fields on every procedure (scope, version, permissions, etc.) |
|
|
18
|
+
| `schema.params` | TypeBox schema — validated at runtime via AJV |
|
|
19
|
+
| `schema.returnType` | Documentation only — NOT validated at runtime |
|
|
20
|
+
| `schema.yieldType` | Schema for each streamed value — validated only if `validateYields: true` |
|
|
21
|
+
|
|
22
|
+
## Decision Framework
|
|
23
|
+
|
|
24
|
+
### Which procedure type?
|
|
25
|
+
|
|
26
|
+
1. Request → single response? → **Create** (standard async procedure)
|
|
27
|
+
2. Request → multiple values over time? → **CreateStream** (async generator)
|
|
28
|
+
3. Long-running task with progress? → **CreateStream** with progress yields
|
|
29
|
+
4. Real-time data feed? → **CreateStream** with SSE mode
|
|
30
|
+
|
|
31
|
+
### Which schema library?
|
|
32
|
+
|
|
33
|
+
- Use **TypeBox** (`import { Type } from 'typebox'`)
|
|
34
|
+
- TypeBox schemas are valid JSON Schema — they work directly with AJV
|
|
35
|
+
- Define schemas with `Type.Object({ ... })`, `Type.String()`, `Type.Number()`, etc.
|
|
36
|
+
- Use `Type.Optional(...)` for optional fields
|
|
37
|
+
|
|
38
|
+
### Which HTTP implementation?
|
|
39
|
+
|
|
40
|
+
| Implementation | Use When |
|
|
41
|
+
|----------------|----------|
|
|
42
|
+
| `ExpressRPCAppBuilder` | Existing Express app, need RPC endpoints alongside REST |
|
|
43
|
+
| `HonoRPCAppBuilder` | Existing Hono app, edge/serverless, need RPC endpoints |
|
|
44
|
+
| `HonoStreamAppBuilder` | Need streaming (SSE or text), Hono-based |
|
|
45
|
+
| Multiple builders | Combine RPC + streaming on the same Hono app |
|
|
46
|
+
|
|
47
|
+
### Stream mode?
|
|
48
|
+
|
|
49
|
+
- **SSE** (`'sse'`) — Browser EventSource API, automatic reconnection, event types. Default.
|
|
50
|
+
- **Text** (`'text'`) — Newline-delimited JSON. Simpler, works with any HTTP client.
|
|
51
|
+
|
|
52
|
+
### How to structure context?
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
// Minimal — just auth
|
|
56
|
+
type AppContext = { userId: string }
|
|
57
|
+
|
|
58
|
+
// With services — inject dependencies
|
|
59
|
+
type AppContext = { userId: string; db: Database; logger: Logger }
|
|
60
|
+
|
|
61
|
+
// With request metadata
|
|
62
|
+
type AppContext = { userId: string; requestId: string; signal?: AbortSignal }
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Context is resolved per-request by the HTTP builder's `factoryContext` function.
|
|
66
|
+
|
|
67
|
+
### How to structure extended config?
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
// API versioning + scoping (required for HTTP builders)
|
|
71
|
+
interface AppConfig extends RPCConfig {
|
|
72
|
+
scope: string | string[] // URL path segments
|
|
73
|
+
version: number // API version
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// With authorization
|
|
77
|
+
interface AppConfig extends RPCConfig {
|
|
78
|
+
permissions?: string[] // Required permissions
|
|
79
|
+
rateLimit?: number // Requests per minute
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### How to group procedures?
|
|
84
|
+
|
|
85
|
+
- **By domain**: `UserProcedures`, `OrderProcedures`, `PaymentProcedures`
|
|
86
|
+
- **By access level**: `PublicRPC`, `AuthenticatedRPC`, `AdminRPC`
|
|
87
|
+
- **By transport**: `StandardRPC` (Create), `StreamRPC` (CreateStream)
|
|
88
|
+
- Each group gets its own `Procedures<Context, Config>()` factory
|
|
89
|
+
|
|
90
|
+
### Error handling strategy?
|
|
91
|
+
|
|
92
|
+
| Layer | Mechanism |
|
|
93
|
+
|-------|-----------|
|
|
94
|
+
| Input validation | Automatic via `schema.params` — throws `ProcedureValidationError` |
|
|
95
|
+
| Business logic errors | `ctx.error(message, meta?)` — throws `ProcedureError` |
|
|
96
|
+
| Unexpected errors | Automatically wrapped in `ProcedureError` with stack enhancement |
|
|
97
|
+
| HTTP error responses | `onError` callback in HTTP builder config |
|
|
98
|
+
| Mid-stream errors | `onMidStreamError` callback in `HonoStreamAppBuilder` |
|
|
99
|
+
|
|
100
|
+
## Your Process
|
|
101
|
+
|
|
102
|
+
When asked to plan an API or procedure set:
|
|
103
|
+
|
|
104
|
+
1. **Understand the requirement** — ask clarifying questions if ambiguous
|
|
105
|
+
2. **Identify procedure groups** — which factories, what context/config types
|
|
106
|
+
3. **List procedures** — name, type (Create vs CreateStream), description
|
|
107
|
+
4. **Design schemas** — params (validated), returnType/yieldType (documented)
|
|
108
|
+
5. **Design context** — what each handler needs from the request
|
|
109
|
+
6. **Choose HTTP implementation** — Express, Hono, or both
|
|
110
|
+
7. **Plan error handling** — which layer for each error type
|
|
111
|
+
8. **Map route structure** — scope + version → URL paths
|
|
112
|
+
|
|
113
|
+
## Architecture Rules
|
|
114
|
+
|
|
115
|
+
- `schema.params` is validated at runtime; `schema.returnType` is documentation only.
|
|
116
|
+
- Handlers receive `(ctx, params)` where ctx includes base context + `error()` function + optional `signal`.
|
|
117
|
+
- Stream handlers always get `ctx.signal` (guaranteed `AbortSignal`). Standard handlers get it when provided by the HTTP implementation.
|
|
118
|
+
- Pass `signal` to all downstream async calls (fetch, database queries) for cancellation support.
|
|
119
|
+
- `onCreate` callback on the factory enables framework integration — use it for route registration, middleware setup, or documentation generation.
|
|
120
|
+
- `getProcedures()` returns all registered procedures for introspection (OpenAPI generation, testing, etc.).
|
|
121
|
+
- AJV is configured with `allErrors: true`, `coerceTypes: true`, `removeAdditional: true`.
|
|
122
|
+
|
|
123
|
+
## Output Format
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
## API: [name]
|
|
127
|
+
|
|
128
|
+
### Procedure Groups
|
|
129
|
+
- `PublicRPC` — context: { requestId }, config: RPCConfig
|
|
130
|
+
- `AuthRPC` — context: { userId, requestId }, config: RPCConfig
|
|
131
|
+
|
|
132
|
+
### Procedures
|
|
133
|
+
|
|
134
|
+
#### PublicRPC
|
|
135
|
+
- `HealthCheck` (Create) — Returns service health status
|
|
136
|
+
- `GetPublicConfig` (Create) — Returns public configuration
|
|
137
|
+
|
|
138
|
+
#### AuthRPC
|
|
139
|
+
- `GetUser` (Create) — Fetch user by ID
|
|
140
|
+
- `UpdateUser` (Create) — Update user fields
|
|
141
|
+
- `StreamActivity` (CreateStream, SSE) — Real-time activity feed
|
|
142
|
+
|
|
143
|
+
### Schema Design
|
|
144
|
+
```typescript
|
|
145
|
+
// GetUser params
|
|
146
|
+
Type.Object({ userId: Type.String() })
|
|
147
|
+
|
|
148
|
+
// GetUser returnType
|
|
149
|
+
Type.Object({ id: Type.String(), name: Type.String(), email: Type.String() })
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Context Design
|
|
153
|
+
```typescript
|
|
154
|
+
type PublicContext = { requestId: string }
|
|
155
|
+
type AuthContext = { userId: string; requestId: string; db: Database }
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### HTTP Setup
|
|
159
|
+
- ExpressRPCAppBuilder for standard RPC
|
|
160
|
+
- HonoStreamAppBuilder for streaming (SSE mode)
|
|
161
|
+
- Path prefix: /api
|
|
162
|
+
|
|
163
|
+
### Route Map
|
|
164
|
+
- POST /api/health/health-check/1
|
|
165
|
+
- POST /api/users/get-user/1
|
|
166
|
+
- GET|POST /api/activity/stream-activity/1
|
|
167
|
+
|
|
168
|
+
### Error Handling
|
|
169
|
+
- Input validation: automatic (schema.params)
|
|
170
|
+
- Auth failures: ctx.error('Unauthorized', { code: 401 })
|
|
171
|
+
- Not found: ctx.error('User not found', { code: 404 })
|
|
172
|
+
- Stream errors: onMidStreamError → yield error event, close stream
|
|
173
|
+
```
|