flight-rules 0.5.9
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 +270 -0
- package/dist/commands/adapter.d.ts +35 -0
- package/dist/commands/adapter.js +308 -0
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +197 -0
- package/dist/commands/upgrade.d.ts +1 -0
- package/dist/commands/upgrade.js +255 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +83 -0
- package/dist/utils/files.d.ts +75 -0
- package/dist/utils/files.js +245 -0
- package/dist/utils/interactive.d.ts +5 -0
- package/dist/utils/interactive.js +7 -0
- package/package.json +52 -0
- package/payload/.editorconfig +15 -0
- package/payload/AGENTS.md +247 -0
- package/payload/commands/dev-session.end.md +79 -0
- package/payload/commands/dev-session.start.md +54 -0
- package/payload/commands/impl.create.md +178 -0
- package/payload/commands/impl.outline.md +120 -0
- package/payload/commands/prd.clarify.md +139 -0
- package/payload/commands/prd.create.md +154 -0
- package/payload/commands/test.add.md +73 -0
- package/payload/commands/test.assess-current.md +75 -0
- package/payload/doc-templates/critical-learnings.md +21 -0
- package/payload/doc-templates/implementation/overview.md +27 -0
- package/payload/doc-templates/prd.md +49 -0
- package/payload/doc-templates/progress.md +19 -0
- package/payload/doc-templates/session-log.md +62 -0
- package/payload/doc-templates/tech-stack.md +101 -0
- package/payload/prompts/.gitkeep +0 -0
- package/payload/prompts/implementation/README.md +26 -0
- package/payload/prompts/implementation/plan-review.md +80 -0
- package/payload/prompts/prd/README.md +46 -0
- package/payload/prompts/prd/creation-conversational.md +46 -0
package/README.md
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# Flight Rules
|
|
2
|
+
|
|
3
|
+
> **Early Development**: This project is in active development (v0.x). APIs and conventions may change between versions. Install with the `dev` tag.
|
|
4
|
+
|
|
5
|
+
An opinionated framework for AI-assisted software development. Provides conventions for docs, implementation specs, and coding session workflows that both humans and agents can navigate.
|
|
6
|
+
|
|
7
|
+
The goal:
|
|
8
|
+
Any agent (or person) should be able to open a project that uses Flight Rules and understand:
|
|
9
|
+
|
|
10
|
+
- What the project is trying to do
|
|
11
|
+
- How the implementation is structured
|
|
12
|
+
- What has already been done
|
|
13
|
+
- What should happen next
|
|
14
|
+
- What we've learned along the way
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
Install the CLI globally:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install -g flight-rules@dev
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Then initialize Flight Rules in any project:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
cd your-project
|
|
30
|
+
flight-rules init
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Or run directly without installing:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npx flight-rules@dev init
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The `init` command will:
|
|
40
|
+
- Create a `.flight-rules/` directory with framework files
|
|
41
|
+
- Optionally create a `docs/` directory with project documentation from templates
|
|
42
|
+
- Optionally generate agent adapters (AGENTS.md for Cursor, CLAUDE.md for Claude Code, etc.)
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## How It Works
|
|
47
|
+
|
|
48
|
+
Flight Rules gives your project a structured documentation system that AI agents (and humans) can navigate consistently.
|
|
49
|
+
|
|
50
|
+
### Project Structure
|
|
51
|
+
|
|
52
|
+
| Location | Purpose |
|
|
53
|
+
|----------|---------|
|
|
54
|
+
| `.flight-rules/` | Framework files (replaced on upgrade) |
|
|
55
|
+
| `docs/` | Your project documentation (new templates added on upgrade, existing files preserved) |
|
|
56
|
+
|
|
57
|
+
**Inside `.flight-rules/`:**
|
|
58
|
+
|
|
59
|
+
| Directory | Purpose |
|
|
60
|
+
|-----------|---------|
|
|
61
|
+
| `AGENTS.md` | Guidelines for AI agents working on your project |
|
|
62
|
+
| `doc-templates/` | Templates for creating project docs |
|
|
63
|
+
| `commands/` | Workflow commands (start/end coding session) |
|
|
64
|
+
| `prompts/` | Reusable prompt templates |
|
|
65
|
+
|
|
66
|
+
### Implementation Specs (Single Source of Truth)
|
|
67
|
+
|
|
68
|
+
Implementation specs live in `docs/implementation/` and follow a three-level hierarchy:
|
|
69
|
+
|
|
70
|
+
| Level | Name | Example | Description |
|
|
71
|
+
|-------|------|---------|-------------|
|
|
72
|
+
| 1 | **Area** | `1-foundation-shell/` | A major implementation area |
|
|
73
|
+
| 2 | **Task Group** | `1.4-application-shell.md` | A file containing related tasks |
|
|
74
|
+
| 3 | **Task** | `1.4.1. Routing Structure` | A specific unit of work with status |
|
|
75
|
+
|
|
76
|
+
**Key principle:** The spec is the single source of truth. If code diverges from spec, update the spec.
|
|
77
|
+
|
|
78
|
+
### Coding Sessions
|
|
79
|
+
|
|
80
|
+
Flight Rules distinguishes between:
|
|
81
|
+
|
|
82
|
+
- **Ad-hoc requests** — "Change function X in file Y"
|
|
83
|
+
- **Structured sessions** — Follow a start/end ritual with documentation
|
|
84
|
+
|
|
85
|
+
Two core flows:
|
|
86
|
+
|
|
87
|
+
- **`dev-session.start`** — Review context, set goals, create a plan
|
|
88
|
+
- **`dev-session.end`** — Summarize work, update progress, capture learnings
|
|
89
|
+
|
|
90
|
+
Agents don't start these flows on their own; you explicitly invoke them.
|
|
91
|
+
|
|
92
|
+
### Versioning
|
|
93
|
+
|
|
94
|
+
Each project tracks which Flight Rules version it uses:
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
flight_rules_version: 0.1.2
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
This appears in `.flight-rules/AGENTS.md` and helps coordinate upgrades.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## What Gets Installed
|
|
105
|
+
|
|
106
|
+
When you run `flight-rules init`, you get:
|
|
107
|
+
|
|
108
|
+
```text
|
|
109
|
+
your-project/
|
|
110
|
+
├── AGENTS.md # (Optional) Adapter for Cursor - points to .flight-rules/
|
|
111
|
+
├── docs/ # YOUR content (new templates added on upgrade)
|
|
112
|
+
│ ├── prd.md
|
|
113
|
+
│ ├── progress.md
|
|
114
|
+
│ ├── critical-learnings.md
|
|
115
|
+
│ ├── tech-stack.md
|
|
116
|
+
│ ├── implementation/
|
|
117
|
+
│ │ └── overview.md
|
|
118
|
+
│ └── session_logs/
|
|
119
|
+
└── .flight-rules/ # Framework files (can be replaced on upgrade)
|
|
120
|
+
├── AGENTS.md # Agent guidelines
|
|
121
|
+
├── doc-templates/ # Templates for creating your docs
|
|
122
|
+
│ ├── prd.md
|
|
123
|
+
│ ├── progress.md
|
|
124
|
+
│ ├── critical-learnings.md
|
|
125
|
+
│ ├── tech-stack.md
|
|
126
|
+
│ ├── session-log.md
|
|
127
|
+
│ └── implementation/
|
|
128
|
+
│ └── overview.md
|
|
129
|
+
├── commands/
|
|
130
|
+
│ ├── dev-session.start.md
|
|
131
|
+
│ ├── dev-session.end.md
|
|
132
|
+
│ ├── test.add.md
|
|
133
|
+
│ └── test.assess-current.md
|
|
134
|
+
└── prompts/ # Reusable prompt templates
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Day-to-Day Usage
|
|
140
|
+
|
|
141
|
+
### When you open a project
|
|
142
|
+
|
|
143
|
+
1. Agents should read `.flight-rules/AGENTS.md`
|
|
144
|
+
2. Skim `docs/prd.md` and `docs/implementation/overview.md`
|
|
145
|
+
3. Check `docs/progress.md` for recent work
|
|
146
|
+
|
|
147
|
+
### For structured work
|
|
148
|
+
|
|
149
|
+
- **"start coding session"** — Sets goals, creates a plan
|
|
150
|
+
- **"end coding session"** — Summarizes work, updates docs
|
|
151
|
+
|
|
152
|
+
### When making changes
|
|
153
|
+
|
|
154
|
+
- Identify relevant Area, Task Group, and Task
|
|
155
|
+
- Update Tasks when reality diverges from plan
|
|
156
|
+
- Keep `Status:` fields current
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## CLI Commands
|
|
161
|
+
|
|
162
|
+
| Command | Description |
|
|
163
|
+
|---------|-------------|
|
|
164
|
+
| `flight-rules init` | Install Flight Rules into a project (interactive setup wizard) |
|
|
165
|
+
| `flight-rules upgrade` | Upgrade Flight Rules in an existing project (preserves your docs) |
|
|
166
|
+
| `flight-rules adapter` | Generate agent-specific adapters (`--cursor`, `--claude`) |
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Upgrading
|
|
171
|
+
|
|
172
|
+
Upgrade Flight Rules while preserving your content:
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
flight-rules upgrade
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
**What gets replaced:**
|
|
179
|
+
- The `.flight-rules/` directory (framework files)
|
|
180
|
+
|
|
181
|
+
**What gets added (without overwriting):**
|
|
182
|
+
- New templates are added to `docs/` if they don't already exist
|
|
183
|
+
- Your existing doc files are never modified
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Manual Installation
|
|
188
|
+
|
|
189
|
+
If you prefer not to use the CLI:
|
|
190
|
+
|
|
191
|
+
1. Clone this repo:
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
git clone https://github.com/ryanpacker/flight-rules.git ~/flight-rules
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
2. Copy the payload into your project:
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
cp -R ~/flight-rules/payload /path/to/your/project/.flight-rules
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
3. Create a `docs/` directory and copy templates from `.flight-rules/doc-templates/` to customize them.
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Future Directions
|
|
208
|
+
|
|
209
|
+
- **Skills/MCP integration** — Conventions for documenting tools and workflows
|
|
210
|
+
- **Hosted planning systems** — Potential integration with Linear, etc.
|
|
211
|
+
- **v1.0 stability** — Stabilizing APIs and conventions for a production-ready release
|
|
212
|
+
|
|
213
|
+
Current focus:
|
|
214
|
+
|
|
215
|
+
- A solid, Markdown-based structure
|
|
216
|
+
- Clear expectations for agents
|
|
217
|
+
- CLI tooling for easy installation and upgrades
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## Contributing
|
|
222
|
+
|
|
223
|
+
This repo is an npm package with the following structure:
|
|
224
|
+
|
|
225
|
+
| Path | Purpose |
|
|
226
|
+
|------|---------|
|
|
227
|
+
| `src/` | CLI source code (TypeScript) |
|
|
228
|
+
| `dist/` | Compiled CLI (committed for GitHub installs) |
|
|
229
|
+
| `payload/` | The content delivered to projects as `.flight-rules/` |
|
|
230
|
+
| `scripts/` | Build and release utilities |
|
|
231
|
+
|
|
232
|
+
### Development
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
git clone https://github.com/ryanpacker/flight-rules.git
|
|
236
|
+
cd flight-rules
|
|
237
|
+
npm install
|
|
238
|
+
npm run build
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Testing
|
|
242
|
+
|
|
243
|
+
The project uses [Vitest](https://vitest.dev/) for testing with ~93% code coverage.
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
npm test # Run tests once
|
|
247
|
+
npm run test:watch # Watch mode for development
|
|
248
|
+
npm run test:coverage # Generate coverage report
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Tests are located in `tests/` and mirror the `src/` structure:
|
|
252
|
+
|
|
253
|
+
| Test File | Coverage |
|
|
254
|
+
|-----------|----------|
|
|
255
|
+
| `tests/utils/files.test.ts` | 100% |
|
|
256
|
+
| `tests/commands/init.test.ts` | 98.5% |
|
|
257
|
+
| `tests/commands/upgrade.test.ts` | 100% |
|
|
258
|
+
| `tests/commands/adapter.test.ts` | 79% |
|
|
259
|
+
|
|
260
|
+
### Releasing
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
npm version patch # or minor/major
|
|
264
|
+
git push && git push --tags
|
|
265
|
+
npm publish --tag dev
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
This automatically builds, syncs the version to `payload/AGENTS.md`, creates a tagged commit, and publishes to npm.
|
|
269
|
+
|
|
270
|
+
> **Note:** Until v1.0, all releases use the `dev` tag. Users install with `npm install -g flight-rules@dev`.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copy command files to a destination directory with conflict handling
|
|
3
|
+
*/
|
|
4
|
+
export declare function copyCommandsWithConflictHandling(sourceDir: string, destDir: string, skipPrompts?: boolean): Promise<{
|
|
5
|
+
copied: string[];
|
|
6
|
+
skipped: string[];
|
|
7
|
+
}>;
|
|
8
|
+
/**
|
|
9
|
+
* Setup Cursor-specific directories and commands
|
|
10
|
+
*/
|
|
11
|
+
export declare function setupCursorCommands(cwd: string, sourceCommandsDir: string, skipPrompts?: boolean): Promise<{
|
|
12
|
+
copied: string[];
|
|
13
|
+
skipped: string[];
|
|
14
|
+
}>;
|
|
15
|
+
/**
|
|
16
|
+
* Check if Cursor adapter is installed (has .cursor/commands/)
|
|
17
|
+
*/
|
|
18
|
+
export declare function isCursorAdapterInstalled(cwd: string): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Setup Claude Code-specific directories and commands
|
|
21
|
+
*/
|
|
22
|
+
export declare function setupClaudeCommands(cwd: string, sourceCommandsDir: string, skipPrompts?: boolean): Promise<{
|
|
23
|
+
copied: string[];
|
|
24
|
+
skipped: string[];
|
|
25
|
+
}>;
|
|
26
|
+
/**
|
|
27
|
+
* Check if Claude Code adapter is installed (has .claude/commands/)
|
|
28
|
+
*/
|
|
29
|
+
export declare function isClaudeAdapterInstalled(cwd: string): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Check if a specific adapter file exists
|
|
32
|
+
*/
|
|
33
|
+
export declare function isAdapterInstalled(cwd: string, adapterKey: string): boolean;
|
|
34
|
+
export declare function adapter(args: string[]): Promise<void>;
|
|
35
|
+
export declare function generateAdapters(adapterNames: string[], sourceCommandsDir?: string, interactive?: boolean): Promise<void>;
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
import pc from 'picocolors';
|
|
3
|
+
import { writeFileSync, existsSync, readdirSync, cpSync } from 'fs';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { ensureDir, getFlightRulesDir } from '../utils/files.js';
|
|
6
|
+
import { isInteractive } from '../utils/interactive.js';
|
|
7
|
+
const ADAPTERS = {
|
|
8
|
+
cursor: {
|
|
9
|
+
name: 'Cursor',
|
|
10
|
+
filename: 'AGENTS.md',
|
|
11
|
+
title: 'Flight Rules – Cursor Adapter',
|
|
12
|
+
description: 'This file is placed at the project root as `AGENTS.md` for Cursor compatibility.',
|
|
13
|
+
hasNativeCommands: true,
|
|
14
|
+
commandsDirectory: '.cursor/commands',
|
|
15
|
+
},
|
|
16
|
+
claude: {
|
|
17
|
+
name: 'Claude Code',
|
|
18
|
+
filename: 'CLAUDE.md',
|
|
19
|
+
title: 'Flight Rules – Claude Code Adapter',
|
|
20
|
+
description: 'This file is placed at the project root as `CLAUDE.md` for Claude Code compatibility.',
|
|
21
|
+
hasNativeCommands: true,
|
|
22
|
+
commandsDirectory: '.claude/commands',
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
function generateAdapterContent(config) {
|
|
26
|
+
// Adapter-specific command instructions
|
|
27
|
+
const commandLocation = config.hasNativeCommands && config.commandsDirectory
|
|
28
|
+
? `\`${config.commandsDirectory}/\` (as slash commands)`
|
|
29
|
+
: `\`.flight-rules/commands/\``;
|
|
30
|
+
const commandInstructions = config.hasNativeCommands
|
|
31
|
+
? `Use the \`/dev-session.start\` and \`/dev-session.end\` slash commands.`
|
|
32
|
+
: `When the user says "start coding session" or "end coding session", follow the instructions in \`.flight-rules/commands/\`.`;
|
|
33
|
+
return `# ${config.title}
|
|
34
|
+
|
|
35
|
+
${config.description}
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
**This project uses Flight Rules.**
|
|
40
|
+
|
|
41
|
+
Agent guidelines and workflows live in \`.flight-rules/\`. Project documentation lives in \`docs/\`.
|
|
42
|
+
|
|
43
|
+
## Quick Reference
|
|
44
|
+
|
|
45
|
+
| What | Where |
|
|
46
|
+
|------|-------|
|
|
47
|
+
| Agent Guidelines | \`.flight-rules/AGENTS.md\` |
|
|
48
|
+
| PRD | \`docs/prd.md\` |
|
|
49
|
+
| Implementation Specs | \`docs/implementation/\` |
|
|
50
|
+
| Progress Log | \`docs/progress.md\` |
|
|
51
|
+
| Tech Stack | \`docs/tech-stack.md\` |
|
|
52
|
+
| Session Commands | ${commandLocation} |
|
|
53
|
+
|
|
54
|
+
## For Agents
|
|
55
|
+
|
|
56
|
+
Please read \`.flight-rules/AGENTS.md\` for complete guidelines on:
|
|
57
|
+
- Project structure
|
|
58
|
+
- Implementation specs
|
|
59
|
+
- Coding sessions
|
|
60
|
+
- How to work with this system
|
|
61
|
+
|
|
62
|
+
${commandInstructions}
|
|
63
|
+
`;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Copy command files to a destination directory with conflict handling
|
|
67
|
+
*/
|
|
68
|
+
export async function copyCommandsWithConflictHandling(sourceDir, destDir, skipPrompts = false) {
|
|
69
|
+
const copied = [];
|
|
70
|
+
const skipped = [];
|
|
71
|
+
if (!existsSync(sourceDir)) {
|
|
72
|
+
return { copied, skipped };
|
|
73
|
+
}
|
|
74
|
+
const files = readdirSync(sourceDir).filter(f => f.endsWith('.md'));
|
|
75
|
+
let batchAction = null;
|
|
76
|
+
for (const file of files) {
|
|
77
|
+
const srcPath = join(sourceDir, file);
|
|
78
|
+
const destPath = join(destDir, file);
|
|
79
|
+
if (existsSync(destPath)) {
|
|
80
|
+
// File already exists - need to handle conflict
|
|
81
|
+
if (skipPrompts) {
|
|
82
|
+
// During upgrade with no prompts, replace by default
|
|
83
|
+
cpSync(srcPath, destPath);
|
|
84
|
+
copied.push(file);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (batchAction === 'replace_all') {
|
|
88
|
+
cpSync(srcPath, destPath);
|
|
89
|
+
copied.push(file);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
else if (batchAction === 'skip_all') {
|
|
93
|
+
skipped.push(file);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
// Prompt for this specific file
|
|
97
|
+
const action = await promptForConflict(file, files.length > 1);
|
|
98
|
+
if (action === 'replace_all') {
|
|
99
|
+
batchAction = 'replace_all';
|
|
100
|
+
cpSync(srcPath, destPath);
|
|
101
|
+
copied.push(file);
|
|
102
|
+
}
|
|
103
|
+
else if (action === 'skip_all') {
|
|
104
|
+
batchAction = 'skip_all';
|
|
105
|
+
skipped.push(file);
|
|
106
|
+
}
|
|
107
|
+
else if (action === 'replace') {
|
|
108
|
+
cpSync(srcPath, destPath);
|
|
109
|
+
copied.push(file);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
skipped.push(file);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
// No conflict - just copy
|
|
117
|
+
cpSync(srcPath, destPath);
|
|
118
|
+
copied.push(file);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return { copied, skipped };
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Prompt user for how to handle a file conflict
|
|
125
|
+
*/
|
|
126
|
+
async function promptForConflict(filename, showBatchOptions) {
|
|
127
|
+
const options = [
|
|
128
|
+
{ value: 'replace', label: 'Replace', hint: 'overwrite with Flight Rules version' },
|
|
129
|
+
{ value: 'skip', label: 'Skip', hint: 'keep existing file' },
|
|
130
|
+
];
|
|
131
|
+
if (showBatchOptions) {
|
|
132
|
+
options.push({ value: 'replace_all', label: 'Replace all', hint: 'replace this and all remaining conflicts' }, { value: 'skip_all', label: 'Skip all', hint: 'skip this and all remaining conflicts' });
|
|
133
|
+
}
|
|
134
|
+
const action = await p.select({
|
|
135
|
+
message: `${pc.cyan(filename)} already exists. What would you like to do?`,
|
|
136
|
+
options,
|
|
137
|
+
});
|
|
138
|
+
if (p.isCancel(action)) {
|
|
139
|
+
return 'skip';
|
|
140
|
+
}
|
|
141
|
+
return action;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Setup Cursor-specific directories and commands
|
|
145
|
+
*/
|
|
146
|
+
export async function setupCursorCommands(cwd, sourceCommandsDir, skipPrompts = false) {
|
|
147
|
+
const cursorDir = join(cwd, '.cursor');
|
|
148
|
+
const cursorCommandsDir = join(cursorDir, 'commands');
|
|
149
|
+
// Create directories
|
|
150
|
+
ensureDir(cursorDir);
|
|
151
|
+
ensureDir(cursorCommandsDir);
|
|
152
|
+
// Copy commands with conflict handling
|
|
153
|
+
return copyCommandsWithConflictHandling(sourceCommandsDir, cursorCommandsDir, skipPrompts);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Check if Cursor adapter is installed (has .cursor/commands/)
|
|
157
|
+
*/
|
|
158
|
+
export function isCursorAdapterInstalled(cwd) {
|
|
159
|
+
return existsSync(join(cwd, '.cursor', 'commands'));
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Setup Claude Code-specific directories and commands
|
|
163
|
+
*/
|
|
164
|
+
export async function setupClaudeCommands(cwd, sourceCommandsDir, skipPrompts = false) {
|
|
165
|
+
const claudeDir = join(cwd, '.claude');
|
|
166
|
+
const claudeCommandsDir = join(claudeDir, 'commands');
|
|
167
|
+
// Create directories
|
|
168
|
+
ensureDir(claudeDir);
|
|
169
|
+
ensureDir(claudeCommandsDir);
|
|
170
|
+
// Copy commands with conflict handling
|
|
171
|
+
return copyCommandsWithConflictHandling(sourceCommandsDir, claudeCommandsDir, skipPrompts);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Check if Claude Code adapter is installed (has .claude/commands/)
|
|
175
|
+
*/
|
|
176
|
+
export function isClaudeAdapterInstalled(cwd) {
|
|
177
|
+
return existsSync(join(cwd, '.claude', 'commands'));
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Check if a specific adapter file exists
|
|
181
|
+
*/
|
|
182
|
+
export function isAdapterInstalled(cwd, adapterKey) {
|
|
183
|
+
const config = ADAPTERS[adapterKey];
|
|
184
|
+
if (!config)
|
|
185
|
+
return false;
|
|
186
|
+
if (adapterKey === 'cursor') {
|
|
187
|
+
// For Cursor, check both AGENTS.md and .cursor/commands/
|
|
188
|
+
return existsSync(join(cwd, config.filename)) || isCursorAdapterInstalled(cwd);
|
|
189
|
+
}
|
|
190
|
+
if (adapterKey === 'claude') {
|
|
191
|
+
// For Claude, check both CLAUDE.md and .claude/commands/
|
|
192
|
+
return existsSync(join(cwd, config.filename)) || isClaudeAdapterInstalled(cwd);
|
|
193
|
+
}
|
|
194
|
+
return existsSync(join(cwd, config.filename));
|
|
195
|
+
}
|
|
196
|
+
export async function adapter(args) {
|
|
197
|
+
const cwd = process.cwd();
|
|
198
|
+
const interactive = isInteractive();
|
|
199
|
+
// Parse arguments
|
|
200
|
+
let selectedAdapters = [];
|
|
201
|
+
if (args.includes('--all')) {
|
|
202
|
+
selectedAdapters = Object.keys(ADAPTERS);
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
for (const arg of args) {
|
|
206
|
+
const adapterName = arg.replace('--', '');
|
|
207
|
+
if (ADAPTERS[adapterName]) {
|
|
208
|
+
selectedAdapters.push(adapterName);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// If no adapters specified via args, prompt for selection (or error in non-interactive)
|
|
213
|
+
if (selectedAdapters.length === 0) {
|
|
214
|
+
if (!interactive) {
|
|
215
|
+
// Non-interactive: require explicit adapter flags
|
|
216
|
+
p.log.error('No adapters specified. In non-interactive mode, use:');
|
|
217
|
+
console.log(' flight-rules adapter --cursor');
|
|
218
|
+
console.log(' flight-rules adapter --claude');
|
|
219
|
+
console.log(' flight-rules adapter --all');
|
|
220
|
+
process.exit(1);
|
|
221
|
+
}
|
|
222
|
+
const selection = await p.multiselect({
|
|
223
|
+
message: 'Which adapters would you like to generate?',
|
|
224
|
+
options: Object.entries(ADAPTERS).map(([key, config]) => ({
|
|
225
|
+
value: key,
|
|
226
|
+
label: config.commandsDirectory
|
|
227
|
+
? `${config.name} (${config.filename} + ${config.commandsDirectory}/)`
|
|
228
|
+
: `${config.name} (${config.filename})`,
|
|
229
|
+
hint: key === 'cursor' ? 'recommended' : undefined,
|
|
230
|
+
})),
|
|
231
|
+
initialValues: ['cursor'],
|
|
232
|
+
});
|
|
233
|
+
if (p.isCancel(selection)) {
|
|
234
|
+
p.log.info('Cancelled.');
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
selectedAdapters = selection;
|
|
238
|
+
}
|
|
239
|
+
await generateAdapters(selectedAdapters, undefined, interactive);
|
|
240
|
+
}
|
|
241
|
+
export async function generateAdapters(adapterNames, sourceCommandsDir, interactive = true) {
|
|
242
|
+
const cwd = process.cwd();
|
|
243
|
+
// Default to .flight-rules/commands if no source specified
|
|
244
|
+
const commandsDir = sourceCommandsDir ?? join(getFlightRulesDir(cwd), 'commands');
|
|
245
|
+
for (const name of adapterNames) {
|
|
246
|
+
const config = ADAPTERS[name];
|
|
247
|
+
if (!config)
|
|
248
|
+
continue;
|
|
249
|
+
const filePath = join(cwd, config.filename);
|
|
250
|
+
let adapterFileWritten = false;
|
|
251
|
+
// Check if file already exists
|
|
252
|
+
if (existsSync(filePath)) {
|
|
253
|
+
if (interactive) {
|
|
254
|
+
const overwrite = await p.confirm({
|
|
255
|
+
message: `${config.filename} already exists. Overwrite?`,
|
|
256
|
+
initialValue: false,
|
|
257
|
+
});
|
|
258
|
+
if (p.isCancel(overwrite) || !overwrite) {
|
|
259
|
+
p.log.info(`Skipped ${config.filename}`);
|
|
260
|
+
// Don't continue - still need to set up commands directory
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
const content = generateAdapterContent(config);
|
|
264
|
+
writeFileSync(filePath, content, 'utf-8');
|
|
265
|
+
p.log.success(`Updated ${pc.cyan(config.filename)} for ${config.name}`);
|
|
266
|
+
adapterFileWritten = true;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
// Non-interactive: skip overwrite (safe default)
|
|
271
|
+
p.log.info(`Skipped ${config.filename} (already exists)`);
|
|
272
|
+
// Don't continue - still need to set up commands directory
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
const content = generateAdapterContent(config);
|
|
277
|
+
writeFileSync(filePath, content, 'utf-8');
|
|
278
|
+
p.log.success(`Created ${pc.cyan(config.filename)} for ${config.name}`);
|
|
279
|
+
adapterFileWritten = true;
|
|
280
|
+
}
|
|
281
|
+
// For Cursor, also set up .cursor/commands/
|
|
282
|
+
if (name === 'cursor') {
|
|
283
|
+
const skipPrompts = !interactive;
|
|
284
|
+
const commandsDirExists = isCursorAdapterInstalled(cwd);
|
|
285
|
+
const result = await setupCursorCommands(cwd, commandsDir, skipPrompts);
|
|
286
|
+
if (result.copied.length > 0) {
|
|
287
|
+
const action = commandsDirExists ? 'Updated' : 'Created';
|
|
288
|
+
p.log.success(`${action} ${pc.cyan('.cursor/commands/')} with ${result.copied.length} command(s)`);
|
|
289
|
+
}
|
|
290
|
+
if (result.skipped.length > 0) {
|
|
291
|
+
p.log.info(`Skipped ${result.skipped.length} existing command(s) in .cursor/commands/`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
// For Claude, also set up .claude/commands/
|
|
295
|
+
if (name === 'claude') {
|
|
296
|
+
const skipPrompts = !interactive;
|
|
297
|
+
const commandsDirExists = isClaudeAdapterInstalled(cwd);
|
|
298
|
+
const result = await setupClaudeCommands(cwd, commandsDir, skipPrompts);
|
|
299
|
+
if (result.copied.length > 0) {
|
|
300
|
+
const action = commandsDirExists ? 'Updated' : 'Created';
|
|
301
|
+
p.log.success(`${action} ${pc.cyan('.claude/commands/')} with ${result.copied.length} command(s)`);
|
|
302
|
+
}
|
|
303
|
+
if (result.skipped.length > 0) {
|
|
304
|
+
p.log.info(`Skipped ${result.skipped.length} existing command(s) in .claude/commands/`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function init(): Promise<void>;
|