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
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
import pc from 'picocolors';
|
|
3
|
+
import { isFlightRulesInstalled, fetchPayloadFromGitHub, copyPayloadFrom, getFlightRulesDir, ensureDir, writeManifest, getCliVersion } from '../utils/files.js';
|
|
4
|
+
import { isInteractive } from '../utils/interactive.js';
|
|
5
|
+
import { cpSync, existsSync } from 'fs';
|
|
6
|
+
import { join } from 'path';
|
|
7
|
+
const DOC_FILES = [
|
|
8
|
+
{ src: 'prd.md', dest: 'prd.md' },
|
|
9
|
+
{ src: 'progress.md', dest: 'progress.md' },
|
|
10
|
+
{ src: 'critical-learnings.md', dest: 'critical-learnings.md' },
|
|
11
|
+
{ src: 'implementation/overview.md', dest: 'implementation/overview.md' },
|
|
12
|
+
{ src: 'tech-stack.md', dest: 'tech-stack.md' },
|
|
13
|
+
];
|
|
14
|
+
async function copyDocsFromTemplates(templatesDir, docsDir, skipExisting) {
|
|
15
|
+
ensureDir(docsDir);
|
|
16
|
+
ensureDir(join(docsDir, 'implementation'));
|
|
17
|
+
ensureDir(join(docsDir, 'session_logs'));
|
|
18
|
+
for (const file of DOC_FILES) {
|
|
19
|
+
const srcPath = join(templatesDir, file.src);
|
|
20
|
+
const destPath = join(docsDir, file.dest);
|
|
21
|
+
if (!existsSync(srcPath))
|
|
22
|
+
continue;
|
|
23
|
+
if (skipExisting && existsSync(destPath)) {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
cpSync(srcPath, destPath);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export async function init() {
|
|
30
|
+
const cwd = process.cwd();
|
|
31
|
+
const interactive = isInteractive();
|
|
32
|
+
// Check if already installed
|
|
33
|
+
if (isFlightRulesInstalled(cwd)) {
|
|
34
|
+
if (interactive) {
|
|
35
|
+
const shouldContinue = await p.confirm({
|
|
36
|
+
message: 'Flight Rules is already installed. Do you want to reinstall? (This will overwrite existing files)',
|
|
37
|
+
initialValue: false,
|
|
38
|
+
});
|
|
39
|
+
if (p.isCancel(shouldContinue) || !shouldContinue) {
|
|
40
|
+
p.log.info('Installation cancelled.');
|
|
41
|
+
p.outro('Run `flight-rules upgrade` to update framework files while preserving your docs.');
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
// Non-interactive: skip reinstall (safe default)
|
|
47
|
+
p.log.info('Flight Rules is already installed. Skipping reinstall.');
|
|
48
|
+
p.outro('Run `flight-rules upgrade` to update framework files while preserving your docs.');
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const spinner = p.spinner();
|
|
53
|
+
// Fetch from GitHub
|
|
54
|
+
spinner.start('Fetching latest Flight Rules from GitHub...');
|
|
55
|
+
let fetched;
|
|
56
|
+
try {
|
|
57
|
+
fetched = await fetchPayloadFromGitHub();
|
|
58
|
+
spinner.stop(`Found Flight Rules version ${pc.cyan(fetched.version)}`);
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
spinner.stop('Failed to fetch from GitHub');
|
|
62
|
+
if (error instanceof Error) {
|
|
63
|
+
p.log.error(error.message);
|
|
64
|
+
}
|
|
65
|
+
p.outro('Check your network connection and try again.');
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
// Copy payload
|
|
69
|
+
spinner.start('Installing Flight Rules...');
|
|
70
|
+
try {
|
|
71
|
+
copyPayloadFrom(fetched.payloadPath, cwd);
|
|
72
|
+
// Write manifest to track deployed version
|
|
73
|
+
writeManifest(cwd, {
|
|
74
|
+
version: fetched.version,
|
|
75
|
+
deployedAt: new Date().toISOString(),
|
|
76
|
+
deployedBy: {
|
|
77
|
+
cli: getCliVersion(),
|
|
78
|
+
command: 'init',
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
spinner.stop('Flight Rules installed!');
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
spinner.stop('Failed to install Flight Rules');
|
|
85
|
+
fetched.cleanup();
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
fetched.cleanup();
|
|
89
|
+
// Ask about initializing docs
|
|
90
|
+
let initDocs;
|
|
91
|
+
if (interactive) {
|
|
92
|
+
const initDocsResult = await p.confirm({
|
|
93
|
+
message: 'Would you like to initialize project docs from templates?',
|
|
94
|
+
initialValue: true,
|
|
95
|
+
});
|
|
96
|
+
if (p.isCancel(initDocsResult)) {
|
|
97
|
+
p.outro('Flight Rules installed. Run `flight-rules adapter` to generate agent adapters.');
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
initDocs = initDocsResult;
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
// Non-interactive: default to yes (create docs)
|
|
104
|
+
initDocs = true;
|
|
105
|
+
}
|
|
106
|
+
if (initDocs) {
|
|
107
|
+
const flightRulesDir = getFlightRulesDir(cwd);
|
|
108
|
+
const templatesDir = join(flightRulesDir, 'doc-templates');
|
|
109
|
+
const docsDir = join(cwd, 'docs');
|
|
110
|
+
// Check if docs directory already exists with content
|
|
111
|
+
const docsExist = existsSync(docsDir);
|
|
112
|
+
let skipExisting = false;
|
|
113
|
+
if (docsExist) {
|
|
114
|
+
if (interactive) {
|
|
115
|
+
const handleExisting = await p.select({
|
|
116
|
+
message: 'A docs/ directory already exists. How would you like to proceed?',
|
|
117
|
+
options: [
|
|
118
|
+
{ value: 'skip', label: 'Skip existing files', hint: 'only create missing files' },
|
|
119
|
+
{ value: 'overwrite', label: 'Overwrite existing files' },
|
|
120
|
+
{ value: 'cancel', label: 'Cancel docs initialization' },
|
|
121
|
+
],
|
|
122
|
+
});
|
|
123
|
+
if (p.isCancel(handleExisting) || handleExisting === 'cancel') {
|
|
124
|
+
p.log.info('Skipped docs initialization.');
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
skipExisting = handleExisting === 'skip';
|
|
128
|
+
await copyDocsFromTemplates(templatesDir, docsDir, skipExisting);
|
|
129
|
+
p.log.success('Project docs initialized from templates.');
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
// Non-interactive: skip existing files (safe default)
|
|
134
|
+
skipExisting = true;
|
|
135
|
+
await copyDocsFromTemplates(templatesDir, docsDir, skipExisting);
|
|
136
|
+
p.log.success('Project docs initialized from templates (skipped existing files).');
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
await copyDocsFromTemplates(templatesDir, docsDir, false);
|
|
141
|
+
p.log.success('Project docs initialized from templates.');
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Ask about generating adapters
|
|
145
|
+
if (interactive) {
|
|
146
|
+
const generateAdaptersResult = await p.confirm({
|
|
147
|
+
message: 'Would you like to generate agent adapter files?',
|
|
148
|
+
initialValue: true,
|
|
149
|
+
});
|
|
150
|
+
if (p.isCancel(generateAdaptersResult)) {
|
|
151
|
+
p.outro('Done! Your project now has Flight Rules.');
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if (generateAdaptersResult) {
|
|
155
|
+
const adapters = await p.multiselect({
|
|
156
|
+
message: 'Which adapters would you like to generate?',
|
|
157
|
+
options: [
|
|
158
|
+
{ value: 'cursor', label: 'Cursor (AGENTS.md + .cursor/commands/)', hint: 'recommended' },
|
|
159
|
+
{ value: 'claude', label: 'Claude Code (CLAUDE.md + .claude/commands/)' },
|
|
160
|
+
],
|
|
161
|
+
initialValues: ['cursor'],
|
|
162
|
+
});
|
|
163
|
+
if (p.isCancel(adapters)) {
|
|
164
|
+
p.outro('Done! Your project now has Flight Rules.');
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
// Import and run adapter generation
|
|
168
|
+
const { generateAdapters: genAdapters } = await import('./adapter.js');
|
|
169
|
+
await genAdapters(adapters);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
// Non-interactive: skip adapter generation (user can run `flight-rules adapter` separately)
|
|
174
|
+
p.log.info('Skipping adapter generation. Run `flight-rules adapter --cursor` or `--claude` to generate adapters.');
|
|
175
|
+
}
|
|
176
|
+
// Ask about installing .editorconfig
|
|
177
|
+
const editorConfigPath = join(cwd, '.editorconfig');
|
|
178
|
+
const editorConfigExists = existsSync(editorConfigPath);
|
|
179
|
+
if (!editorConfigExists) {
|
|
180
|
+
if (interactive) {
|
|
181
|
+
const installEditorConfig = await p.confirm({
|
|
182
|
+
message: 'Would you like to add a standard .editorconfig? (helps prevent formatting diffs)',
|
|
183
|
+
initialValue: true,
|
|
184
|
+
});
|
|
185
|
+
if (!p.isCancel(installEditorConfig) && installEditorConfig) {
|
|
186
|
+
const flightRulesDir = getFlightRulesDir(cwd);
|
|
187
|
+
const srcEditorConfig = join(flightRulesDir, '.editorconfig');
|
|
188
|
+
if (existsSync(srcEditorConfig)) {
|
|
189
|
+
cpSync(srcEditorConfig, editorConfigPath);
|
|
190
|
+
p.log.success('Added .editorconfig to project root.');
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
// Non-interactive: skip .editorconfig installation (safe default)
|
|
195
|
+
}
|
|
196
|
+
p.outro(pc.green('Flight Rules is ready! Start with: "start coding session"'));
|
|
197
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function upgrade(version?: string): Promise<void>;
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
import pc from 'picocolors';
|
|
3
|
+
import { existsSync, cpSync } from 'fs';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { isFlightRulesInstalled, fetchPayloadFromGitHub, copyFrameworkFilesFrom, ensureDir, getInstalledVersion, writeManifest, getCliVersion } from '../utils/files.js';
|
|
6
|
+
import { isInteractive } from '../utils/interactive.js';
|
|
7
|
+
import { isCursorAdapterInstalled, isClaudeAdapterInstalled, setupCursorCommands, setupClaudeCommands, } from './adapter.js';
|
|
8
|
+
const DOC_FILES = [
|
|
9
|
+
{ src: 'prd.md', dest: 'prd.md' },
|
|
10
|
+
{ src: 'progress.md', dest: 'progress.md' },
|
|
11
|
+
{ src: 'critical-learnings.md', dest: 'critical-learnings.md' },
|
|
12
|
+
{ src: 'implementation/overview.md', dest: 'implementation/overview.md' },
|
|
13
|
+
{ src: 'tech-stack.md', dest: 'tech-stack.md' },
|
|
14
|
+
];
|
|
15
|
+
/**
|
|
16
|
+
* Copy new doc templates to docs/ without overwriting existing files.
|
|
17
|
+
* Returns the list of files that were copied.
|
|
18
|
+
*/
|
|
19
|
+
function copyNewDocsFromTemplates(templatesDir, docsDir) {
|
|
20
|
+
ensureDir(docsDir);
|
|
21
|
+
ensureDir(join(docsDir, 'implementation'));
|
|
22
|
+
ensureDir(join(docsDir, 'session_logs'));
|
|
23
|
+
const copied = [];
|
|
24
|
+
for (const file of DOC_FILES) {
|
|
25
|
+
const srcPath = join(templatesDir, file.src);
|
|
26
|
+
const destPath = join(docsDir, file.dest);
|
|
27
|
+
if (!existsSync(srcPath))
|
|
28
|
+
continue;
|
|
29
|
+
// Only copy if destination doesn't exist
|
|
30
|
+
if (!existsSync(destPath)) {
|
|
31
|
+
cpSync(srcPath, destPath);
|
|
32
|
+
copied.push(file.dest);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return copied;
|
|
36
|
+
}
|
|
37
|
+
export async function upgrade(version) {
|
|
38
|
+
const cwd = process.cwd();
|
|
39
|
+
// Check if Flight Rules is installed
|
|
40
|
+
if (!isFlightRulesInstalled(cwd)) {
|
|
41
|
+
p.log.error('Flight Rules is not installed in this directory.');
|
|
42
|
+
p.outro('Run `flight-rules init` to install Flight Rules first.');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
// Get current installed version
|
|
46
|
+
const currentVersion = getInstalledVersion(cwd);
|
|
47
|
+
// Detect installed adapters before upgrade
|
|
48
|
+
const cursorAdapterInstalled = isCursorAdapterInstalled(cwd);
|
|
49
|
+
const claudeAdapterInstalled = isClaudeAdapterInstalled(cwd);
|
|
50
|
+
const agentsMdExists = existsSync(join(cwd, 'AGENTS.md'));
|
|
51
|
+
const claudeMdExists = existsSync(join(cwd, 'CLAUDE.md'));
|
|
52
|
+
// Show what will be upgraded
|
|
53
|
+
p.log.info(`${pc.bold('The .flight-rules/ directory will be upgraded.')}`);
|
|
54
|
+
p.log.message(' Framework files (AGENTS.md, doc-templates/, commands/, prompts/) will be replaced.');
|
|
55
|
+
console.log();
|
|
56
|
+
// Show adapter upgrade info
|
|
57
|
+
const adaptersToUpgrade = [];
|
|
58
|
+
if (cursorAdapterInstalled) {
|
|
59
|
+
adaptersToUpgrade.push('Cursor (.cursor/commands/)');
|
|
60
|
+
}
|
|
61
|
+
if (claudeAdapterInstalled) {
|
|
62
|
+
adaptersToUpgrade.push('Claude Code (.claude/commands/)');
|
|
63
|
+
}
|
|
64
|
+
else if (claudeMdExists) {
|
|
65
|
+
// CLAUDE.md exists but .claude/commands/ doesn't - will be created on upgrade
|
|
66
|
+
adaptersToUpgrade.push('Claude Code (.claude/commands/ will be created)');
|
|
67
|
+
}
|
|
68
|
+
if (agentsMdExists) {
|
|
69
|
+
adaptersToUpgrade.push('AGENTS.md');
|
|
70
|
+
}
|
|
71
|
+
if (claudeMdExists && !adaptersToUpgrade.some(a => a.includes('Claude Code'))) {
|
|
72
|
+
adaptersToUpgrade.push('CLAUDE.md');
|
|
73
|
+
}
|
|
74
|
+
if (adaptersToUpgrade.length > 0) {
|
|
75
|
+
p.log.info(`${pc.bold('Installed adapters will also be upgraded:')}`);
|
|
76
|
+
p.log.message(` ${adaptersToUpgrade.join(', ')}`);
|
|
77
|
+
console.log();
|
|
78
|
+
}
|
|
79
|
+
p.log.info(`${pc.bold('Your project content will be preserved.')}`);
|
|
80
|
+
p.log.message(' New templates may be added to docs/, but existing files are never modified.');
|
|
81
|
+
console.log();
|
|
82
|
+
const spinner = p.spinner();
|
|
83
|
+
// Fetch from GitHub
|
|
84
|
+
spinner.start(version ? `Fetching Flight Rules ${version} from GitHub...` : 'Fetching latest Flight Rules from GitHub...');
|
|
85
|
+
let fetched;
|
|
86
|
+
try {
|
|
87
|
+
fetched = await fetchPayloadFromGitHub(version);
|
|
88
|
+
spinner.stop(`Found Flight Rules version ${pc.cyan(fetched.version)}`);
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
spinner.stop('Failed to fetch from GitHub');
|
|
92
|
+
if (error instanceof Error) {
|
|
93
|
+
p.log.error(error.message);
|
|
94
|
+
}
|
|
95
|
+
p.outro('Check your network connection and try again.');
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
// Show version comparison
|
|
99
|
+
if (currentVersion) {
|
|
100
|
+
p.log.info(`Current version: ${pc.yellow(currentVersion)} → ${pc.cyan(fetched.version)}`);
|
|
101
|
+
}
|
|
102
|
+
if (isInteractive()) {
|
|
103
|
+
const shouldContinue = await p.confirm({
|
|
104
|
+
message: `Upgrade to version ${fetched.version}?`,
|
|
105
|
+
initialValue: true,
|
|
106
|
+
});
|
|
107
|
+
if (p.isCancel(shouldContinue) || !shouldContinue) {
|
|
108
|
+
fetched.cleanup();
|
|
109
|
+
p.log.info('Upgrade cancelled.');
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
// Non-interactive: proceed with upgrade
|
|
115
|
+
p.log.info(`Upgrading to version ${fetched.version}...`);
|
|
116
|
+
}
|
|
117
|
+
// Upgrade .flight-rules/
|
|
118
|
+
spinner.start('Upgrading Flight Rules...');
|
|
119
|
+
try {
|
|
120
|
+
copyFrameworkFilesFrom(fetched.payloadPath, cwd);
|
|
121
|
+
// Write manifest to track deployed version
|
|
122
|
+
writeManifest(cwd, {
|
|
123
|
+
version: fetched.version,
|
|
124
|
+
deployedAt: new Date().toISOString(),
|
|
125
|
+
deployedBy: {
|
|
126
|
+
cli: getCliVersion(),
|
|
127
|
+
command: 'upgrade',
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
spinner.stop('Flight Rules framework upgraded!');
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
spinner.stop('Failed to upgrade Flight Rules');
|
|
134
|
+
fetched.cleanup();
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
p.log.success('Framework files have been updated.');
|
|
138
|
+
// Add new doc templates to docs/ (without overwriting existing files)
|
|
139
|
+
const templatesDir = join(fetched.payloadPath, 'doc-templates');
|
|
140
|
+
const docsDir = join(cwd, 'docs');
|
|
141
|
+
const newDocs = copyNewDocsFromTemplates(templatesDir, docsDir);
|
|
142
|
+
if (newDocs.length > 0) {
|
|
143
|
+
p.log.success(`Added ${newDocs.length} new template(s) to docs/: ${newDocs.join(', ')}`);
|
|
144
|
+
}
|
|
145
|
+
// Upgrade installed adapters
|
|
146
|
+
if (adaptersToUpgrade.length > 0) {
|
|
147
|
+
spinner.start('Upgrading adapters...');
|
|
148
|
+
try {
|
|
149
|
+
const sourceCommandsDir = join(fetched.payloadPath, 'commands');
|
|
150
|
+
// Upgrade Cursor commands if installed
|
|
151
|
+
if (cursorAdapterInstalled) {
|
|
152
|
+
const result = await setupCursorCommands(cwd, sourceCommandsDir, true); // skipPrompts = true for upgrade
|
|
153
|
+
if (result.copied.length > 0) {
|
|
154
|
+
p.log.success(`Updated ${result.copied.length} command(s) in .cursor/commands/`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// Upgrade Claude commands if installed, or create them if CLAUDE.md exists but .claude/commands/ doesn't
|
|
158
|
+
// (handles upgrade from pre-0.5.4 where Claude didn't have native commands)
|
|
159
|
+
if (claudeAdapterInstalled || claudeMdExists) {
|
|
160
|
+
const result = await setupClaudeCommands(cwd, sourceCommandsDir, true); // skipPrompts = true for upgrade
|
|
161
|
+
if (result.copied.length > 0) {
|
|
162
|
+
const action = claudeAdapterInstalled ? 'Updated' : 'Created';
|
|
163
|
+
p.log.success(`${action} ${result.copied.length} command(s) in .claude/commands/`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// Regenerate adapter files
|
|
167
|
+
const adaptersToRegenerate = [];
|
|
168
|
+
if (agentsMdExists) {
|
|
169
|
+
adaptersToRegenerate.push('cursor');
|
|
170
|
+
}
|
|
171
|
+
if (claudeMdExists) {
|
|
172
|
+
adaptersToRegenerate.push('claude');
|
|
173
|
+
}
|
|
174
|
+
if (adaptersToRegenerate.length > 0) {
|
|
175
|
+
// For upgrade, we regenerate silently (the files already exist, so we just overwrite)
|
|
176
|
+
for (const adapterName of adaptersToRegenerate) {
|
|
177
|
+
await regenerateAdapterFile(cwd, adapterName, sourceCommandsDir);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
spinner.stop('Adapters upgraded!');
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
spinner.stop('Failed to upgrade adapters');
|
|
184
|
+
fetched.cleanup();
|
|
185
|
+
throw error;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
fetched.cleanup();
|
|
189
|
+
p.outro(pc.green('Upgrade complete!'));
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Regenerate a single adapter file during upgrade (no prompts, just overwrite)
|
|
193
|
+
*/
|
|
194
|
+
async function regenerateAdapterFile(cwd, adapterName, _sourceCommandsDir) {
|
|
195
|
+
// Import the adapter config and content generator
|
|
196
|
+
const { writeFileSync } = await import('fs');
|
|
197
|
+
const { join } = await import('path');
|
|
198
|
+
const ADAPTERS = {
|
|
199
|
+
cursor: {
|
|
200
|
+
filename: 'AGENTS.md',
|
|
201
|
+
title: 'Flight Rules – Cursor Adapter',
|
|
202
|
+
description: 'This file is placed at the project root as `AGENTS.md` for Cursor compatibility.',
|
|
203
|
+
hasNativeCommands: true,
|
|
204
|
+
commandsDirectory: '.cursor/commands',
|
|
205
|
+
},
|
|
206
|
+
claude: {
|
|
207
|
+
filename: 'CLAUDE.md',
|
|
208
|
+
title: 'Flight Rules – Claude Code Adapter',
|
|
209
|
+
description: 'This file is placed at the project root as `CLAUDE.md` for Claude Code compatibility.',
|
|
210
|
+
hasNativeCommands: true,
|
|
211
|
+
commandsDirectory: '.claude/commands',
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
const config = ADAPTERS[adapterName];
|
|
215
|
+
if (!config)
|
|
216
|
+
return;
|
|
217
|
+
const commandLocation = config.hasNativeCommands && config.commandsDirectory
|
|
218
|
+
? `\`${config.commandsDirectory}/\` (as slash commands)`
|
|
219
|
+
: `\`.flight-rules/commands/\``;
|
|
220
|
+
const commandInstructions = config.hasNativeCommands
|
|
221
|
+
? `Use the \`/dev-session.start\` and \`/dev-session.end\` slash commands.`
|
|
222
|
+
: `When the user says "start coding session" or "end coding session", follow the instructions in \`.flight-rules/commands/\`.`;
|
|
223
|
+
const content = `# ${config.title}
|
|
224
|
+
|
|
225
|
+
${config.description}
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
**This project uses Flight Rules.**
|
|
230
|
+
|
|
231
|
+
Agent guidelines and workflows live in \`.flight-rules/\`. Project documentation lives in \`docs/\`.
|
|
232
|
+
|
|
233
|
+
## Quick Reference
|
|
234
|
+
|
|
235
|
+
| What | Where |
|
|
236
|
+
|------|-------|
|
|
237
|
+
| Agent Guidelines | \`.flight-rules/AGENTS.md\` |
|
|
238
|
+
| PRD | \`docs/prd.md\` |
|
|
239
|
+
| Implementation Specs | \`docs/implementation/\` |
|
|
240
|
+
| Progress Log | \`docs/progress.md\` |
|
|
241
|
+
| Session Commands | ${commandLocation} |
|
|
242
|
+
|
|
243
|
+
## For Agents
|
|
244
|
+
|
|
245
|
+
Please read \`.flight-rules/AGENTS.md\` for complete guidelines on:
|
|
246
|
+
- Project structure
|
|
247
|
+
- Implementation specs
|
|
248
|
+
- Coding sessions
|
|
249
|
+
- How to work with this system
|
|
250
|
+
|
|
251
|
+
${commandInstructions}
|
|
252
|
+
`;
|
|
253
|
+
const filePath = join(cwd, config.filename);
|
|
254
|
+
writeFileSync(filePath, content, 'utf-8');
|
|
255
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import * as p from '@clack/prompts';
|
|
3
|
+
import pc from 'picocolors';
|
|
4
|
+
import { init } from './commands/init.js';
|
|
5
|
+
import { upgrade } from './commands/upgrade.js';
|
|
6
|
+
import { adapter } from './commands/adapter.js';
|
|
7
|
+
import { getCliVersion } from './utils/files.js';
|
|
8
|
+
const command = process.argv[2];
|
|
9
|
+
const args = process.argv.slice(3);
|
|
10
|
+
/**
|
|
11
|
+
* Parse --version flag from args, returning the version value if present
|
|
12
|
+
*/
|
|
13
|
+
function parseVersionArg(args) {
|
|
14
|
+
const versionIndex = args.findIndex(arg => arg === '--version' || arg === '-V');
|
|
15
|
+
if (versionIndex !== -1 && args[versionIndex + 1]) {
|
|
16
|
+
return args[versionIndex + 1];
|
|
17
|
+
}
|
|
18
|
+
// Also support --version=0.1.4 format
|
|
19
|
+
const versionArg = args.find(arg => arg.startsWith('--version='));
|
|
20
|
+
if (versionArg) {
|
|
21
|
+
return versionArg.split('=')[1];
|
|
22
|
+
}
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
async function main() {
|
|
26
|
+
// Handle --version early, without intro banner
|
|
27
|
+
if (command === '--version' || command === '-v') {
|
|
28
|
+
console.log(getCliVersion());
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
console.log();
|
|
32
|
+
p.intro(pc.bgCyan(pc.black(' flight-rules ')));
|
|
33
|
+
switch (command) {
|
|
34
|
+
case 'init':
|
|
35
|
+
await init();
|
|
36
|
+
break;
|
|
37
|
+
case 'upgrade':
|
|
38
|
+
const version = parseVersionArg(args);
|
|
39
|
+
await upgrade(version);
|
|
40
|
+
break;
|
|
41
|
+
case 'adapter':
|
|
42
|
+
await adapter(args);
|
|
43
|
+
break;
|
|
44
|
+
case undefined:
|
|
45
|
+
case '--help':
|
|
46
|
+
case '-h':
|
|
47
|
+
showHelp();
|
|
48
|
+
break;
|
|
49
|
+
default:
|
|
50
|
+
p.log.error(`Unknown command: ${command}`);
|
|
51
|
+
showHelp();
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function showHelp() {
|
|
56
|
+
console.log(`
|
|
57
|
+
${pc.bold('Usage:')} flight-rules <command> [options]
|
|
58
|
+
|
|
59
|
+
${pc.bold('Commands:')}
|
|
60
|
+
init Install Flight Rules into the current project
|
|
61
|
+
upgrade Upgrade Flight Rules (preserves your docs)
|
|
62
|
+
adapter Generate agent-specific adapter files
|
|
63
|
+
|
|
64
|
+
${pc.bold('Upgrade Options:')}
|
|
65
|
+
--version <version> Upgrade to a specific version (e.g., 0.1.4)
|
|
66
|
+
Defaults to latest from main branch
|
|
67
|
+
|
|
68
|
+
${pc.bold('Adapter Options:')}
|
|
69
|
+
--cursor Generate AGENTS.md for Cursor
|
|
70
|
+
--claude Generate CLAUDE.md for Claude Code
|
|
71
|
+
--all Generate all adapters
|
|
72
|
+
|
|
73
|
+
${pc.bold('Examples:')}
|
|
74
|
+
flight-rules init
|
|
75
|
+
flight-rules upgrade
|
|
76
|
+
flight-rules upgrade --version 0.1.4
|
|
77
|
+
flight-rules adapter --cursor --claude
|
|
78
|
+
`);
|
|
79
|
+
}
|
|
80
|
+
main().catch((err) => {
|
|
81
|
+
p.log.error(err.message);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get the CLI version from package.json
|
|
3
|
+
*/
|
|
4
|
+
export declare function getCliVersion(): string;
|
|
5
|
+
/**
|
|
6
|
+
* Get the path to the payload directory (the Flight Rules content to install)
|
|
7
|
+
*/
|
|
8
|
+
export declare function getPayloadPath(): string;
|
|
9
|
+
/**
|
|
10
|
+
* Check if Flight Rules is already installed in the target directory
|
|
11
|
+
*/
|
|
12
|
+
export declare function isFlightRulesInstalled(targetDir: string): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Get the .flight-rules directory path in the target
|
|
15
|
+
*/
|
|
16
|
+
export declare function getFlightRulesDir(targetDir: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* Copy the payload to the target directory as .flight-rules
|
|
19
|
+
*/
|
|
20
|
+
export declare function copyPayload(targetDir: string): void;
|
|
21
|
+
/**
|
|
22
|
+
* Copy only framework files (not docs/) during upgrade
|
|
23
|
+
*/
|
|
24
|
+
export declare function copyFrameworkFiles(targetDir: string): void;
|
|
25
|
+
/**
|
|
26
|
+
* Ensure a directory exists
|
|
27
|
+
*/
|
|
28
|
+
export declare function ensureDir(dir: string): void;
|
|
29
|
+
/**
|
|
30
|
+
* Result of fetching payload from GitHub
|
|
31
|
+
*/
|
|
32
|
+
export interface FetchedPayload {
|
|
33
|
+
payloadPath: string;
|
|
34
|
+
version: string;
|
|
35
|
+
cleanup: () => void;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Fetch the Flight Rules payload from GitHub
|
|
39
|
+
* @param version - Git ref to fetch (tag like 'v0.1.4', branch like 'main', or 'latest' for main)
|
|
40
|
+
* @returns Object with payloadPath, version string, and cleanup function
|
|
41
|
+
*/
|
|
42
|
+
export declare function fetchPayloadFromGitHub(version?: string): Promise<FetchedPayload>;
|
|
43
|
+
/**
|
|
44
|
+
* Copy framework files from a source payload directory (used by both local and remote)
|
|
45
|
+
*/
|
|
46
|
+
export declare function copyFrameworkFilesFrom(sourcePayloadPath: string, targetDir: string): void;
|
|
47
|
+
/**
|
|
48
|
+
* Copy entire payload from a source directory (used by both local and remote)
|
|
49
|
+
*/
|
|
50
|
+
export declare function copyPayloadFrom(sourcePayloadPath: string, targetDir: string): void;
|
|
51
|
+
/**
|
|
52
|
+
* Manifest file structure for tracking deployed Flight Rules version
|
|
53
|
+
*/
|
|
54
|
+
export interface Manifest {
|
|
55
|
+
version: string;
|
|
56
|
+
deployedAt: string;
|
|
57
|
+
deployedBy: {
|
|
58
|
+
cli: string;
|
|
59
|
+
command: 'init' | 'upgrade';
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Read the manifest.json from a Flight Rules installation
|
|
64
|
+
* @returns The manifest data, or null if not found
|
|
65
|
+
*/
|
|
66
|
+
export declare function readManifest(targetDir: string): Manifest | null;
|
|
67
|
+
/**
|
|
68
|
+
* Write the manifest.json to a Flight Rules installation
|
|
69
|
+
*/
|
|
70
|
+
export declare function writeManifest(targetDir: string, data: Manifest): void;
|
|
71
|
+
/**
|
|
72
|
+
* Get the current version from manifest, falling back to AGENTS.md
|
|
73
|
+
* @returns The version string, or null if not found
|
|
74
|
+
*/
|
|
75
|
+
export declare function getInstalledVersion(targetDir: string): string | null;
|