cursor-sdd 1.0.3 → 1.0.5
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 +18 -1
- package/assign/README.md +9 -0
- package/assign/commands/README.md +4 -0
- package/assign/rules/README.md +3 -0
- package/assign/templates/README.md +3 -0
- package/bin/setup.js +106 -21
- package/package.json +3 -2
- package/templates/specs/init.json +5 -0
package/README.md
CHANGED
|
@@ -8,7 +8,13 @@ Cursor IDE 向けの Spec-Driven Development (SDD) テンプレート、ルー
|
|
|
8
8
|
npm install cursor-sdd
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
インストール時に自動的にプロジェクトの `.cursor/`
|
|
11
|
+
インストール時に自動的にプロジェクトの `.cursor/` フォルダにファイルがコピーされます。対話可能な環境では「新規のPJを立ち上げる / 既存PJにアサインする」を選択できます。
|
|
12
|
+
|
|
13
|
+
### モード指定
|
|
14
|
+
|
|
15
|
+
- 対話プロンプト: `npm install cursor-sdd` 実行時に `new` / `assign` を選択
|
|
16
|
+
- 非対話や CI: `npm install cursor-sdd --mode assign` または環境変数 `CURSOR_SDD_MODE=assign`
|
|
17
|
+
- 省略時デフォルト: `new`
|
|
12
18
|
|
|
13
19
|
### 手動セットアップ
|
|
14
20
|
|
|
@@ -33,10 +39,18 @@ Cursor IDE で以下のコマンドが使えるようになります:
|
|
|
33
39
|
| `/check-design` | 設計のレビュー |
|
|
34
40
|
| `/difference-check` | 差分チェック |
|
|
35
41
|
|
|
42
|
+
### `/init` の使い分け
|
|
43
|
+
|
|
44
|
+
- **PJ全体を初期化**: `/init <プロジェクト説明>`
|
|
45
|
+
- **個別画面/機能を初期化**: `/init --feature billing-history <画面の説明>`
|
|
46
|
+
- `--feature` / `-f` で指定したキーが `.cursor/<PJ名>/<feature>` ディレクトリとして作成されます
|
|
47
|
+
- 以降の `/requirements` などは `<PJ名>/<feature>` を引数に渡してください(例: `/requirements my-project/billing-history`)
|
|
48
|
+
|
|
36
49
|
## 含まれるファイル
|
|
37
50
|
|
|
38
51
|
```
|
|
39
52
|
.cursor/
|
|
53
|
+
├── (assign 用の内容をコピーする場合は assign/ 配下がコピーされます)
|
|
40
54
|
├── commands/ # Cursor コマンド定義
|
|
41
55
|
│ ├── init.md
|
|
42
56
|
│ ├── requirements.md
|
|
@@ -63,6 +77,8 @@ Cursor IDE で以下のコマンドが使えるようになります:
|
|
|
63
77
|
├── design.md
|
|
64
78
|
├── tasks.md
|
|
65
79
|
└── research.md
|
|
80
|
+
|
|
81
|
+
assign モード時に配布したいファイルはリポジトリ直下の `assign/` に配置してください(例: `assign/commands`, `assign/rules`, `assign/templates`)。
|
|
66
82
|
```
|
|
67
83
|
|
|
68
84
|
## ワークフロー
|
|
@@ -85,3 +101,4 @@ Cursor IDE で以下のコマンドが使えるようになります:
|
|
|
85
101
|
MIT
|
|
86
102
|
|
|
87
103
|
# cursor-sdd-package
|
|
104
|
+
|
package/assign/README.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# assign プロファイル
|
|
2
|
+
|
|
3
|
+
既存プロジェクトにアサインする際のカスタム `.cursor` 一式をここに配置します。
|
|
4
|
+
|
|
5
|
+
- `assign/commands` … アサイン時専用のコマンド定義
|
|
6
|
+
- `assign/rules` … アサイン時専用のルール
|
|
7
|
+
- `assign/templates` … アサイン時専用のテンプレート
|
|
8
|
+
|
|
9
|
+
`npm install cursor-sdd --mode assign` あるいは対話選択で `assign` を選ぶと、このフォルダ配下が `.cursor/` にコピーされます。
|
package/bin/setup.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
|
+
const readline = require('readline');
|
|
5
6
|
|
|
6
7
|
const isAuto = process.argv.includes('--auto');
|
|
7
8
|
const isForce = process.argv.includes('--force');
|
|
@@ -12,36 +13,34 @@ const packageRoot = path.resolve(__dirname, '..');
|
|
|
12
13
|
// プロジェクトのルートを取得(node_modules の2つ上)
|
|
13
14
|
function getProjectRoot() {
|
|
14
15
|
let current = process.cwd();
|
|
15
|
-
|
|
16
|
+
|
|
16
17
|
// npm install 経由の場合は INIT_CWD を使用
|
|
17
18
|
if (process.env.INIT_CWD) {
|
|
18
19
|
return process.env.INIT_CWD;
|
|
19
20
|
}
|
|
20
|
-
|
|
21
|
+
|
|
21
22
|
// node_modules から呼ばれた場合
|
|
22
23
|
const nodeModulesIndex = __dirname.indexOf('node_modules');
|
|
23
24
|
if (nodeModulesIndex !== -1) {
|
|
24
25
|
return __dirname.substring(0, nodeModulesIndex);
|
|
25
26
|
}
|
|
26
|
-
|
|
27
|
+
|
|
27
28
|
return current;
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
const projectRoot = getProjectRoot();
|
|
31
32
|
const targetDir = path.join(projectRoot, '.cursor');
|
|
32
33
|
|
|
33
|
-
const folders = ['templates', 'rules', 'commands'];
|
|
34
|
-
|
|
35
34
|
function copyRecursive(src, dest) {
|
|
36
35
|
if (!fs.existsSync(src)) return;
|
|
37
|
-
|
|
36
|
+
|
|
38
37
|
const stat = fs.statSync(src);
|
|
39
|
-
|
|
38
|
+
|
|
40
39
|
if (stat.isDirectory()) {
|
|
41
40
|
if (!fs.existsSync(dest)) {
|
|
42
41
|
fs.mkdirSync(dest, { recursive: true });
|
|
43
42
|
}
|
|
44
|
-
|
|
43
|
+
|
|
45
44
|
for (const item of fs.readdirSync(src)) {
|
|
46
45
|
copyRecursive(path.join(src, item), path.join(dest, item));
|
|
47
46
|
}
|
|
@@ -56,24 +55,107 @@ function copyRecursive(src, dest) {
|
|
|
56
55
|
}
|
|
57
56
|
}
|
|
58
57
|
|
|
59
|
-
function
|
|
58
|
+
function getArgValue(flag) {
|
|
59
|
+
const idx = process.argv.indexOf(flag);
|
|
60
|
+
if (idx === -1) return null;
|
|
61
|
+
const next = process.argv[idx + 1];
|
|
62
|
+
if (!next || next.startsWith('-')) return null;
|
|
63
|
+
return next;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function normalizeMode(value) {
|
|
67
|
+
if (!value) return null;
|
|
68
|
+
const lower = value.toLowerCase();
|
|
69
|
+
if (lower === 'assign' || lower === 'a') return 'assign';
|
|
70
|
+
if (lower === 'new' || lower === 'n') return 'new';
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function hasTTY() {
|
|
75
|
+
if (process.stdout.isTTY && process.stdin.isTTY) return true;
|
|
76
|
+
// npm install 時に stdin がパイプ扱いになる場合のため /dev/tty を確認
|
|
77
|
+
try {
|
|
78
|
+
fs.accessSync('/dev/tty', fs.constants.R_OK | fs.constants.W_OK);
|
|
79
|
+
return true;
|
|
80
|
+
} catch {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function createTTYInterface() {
|
|
86
|
+
// stdin が非TTYでも /dev/tty を使って対話できるようにする
|
|
87
|
+
const input = process.stdin.isTTY
|
|
88
|
+
? process.stdin
|
|
89
|
+
: (() => {
|
|
90
|
+
try {
|
|
91
|
+
return fs.createReadStream('/dev/tty');
|
|
92
|
+
} catch {
|
|
93
|
+
return process.stdin;
|
|
94
|
+
}
|
|
95
|
+
})();
|
|
96
|
+
const output = process.stdout; // 出力は常に標準出力に寄せる
|
|
97
|
+
return readline.createInterface({ input, output });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function shouldPromptForMode(explicitMode) {
|
|
101
|
+
return !explicitMode && hasTTY() && !process.env.CI;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function askMode() {
|
|
105
|
+
const rl = createTTYInterface();
|
|
106
|
+
const answer = await new Promise((resolve) => {
|
|
107
|
+
rl.question('新規PJを立ち上げますか?既存PJにアサインしますか? [n]ew/[a]ssign (default: new): ', resolve);
|
|
108
|
+
});
|
|
109
|
+
rl.close();
|
|
110
|
+
return normalizeMode(answer) || 'new';
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function resolveMode() {
|
|
114
|
+
const explicitMode = normalizeMode(getArgValue('--mode') || process.env.CURSOR_SDD_MODE);
|
|
115
|
+
if (explicitMode) return Promise.resolve(explicitMode);
|
|
116
|
+
if (shouldPromptForMode(explicitMode)) {
|
|
117
|
+
return askMode();
|
|
118
|
+
}
|
|
119
|
+
return Promise.resolve('new');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function getFolders(sourceRoot) {
|
|
123
|
+
if (!fs.existsSync(sourceRoot)) return [];
|
|
124
|
+
return fs
|
|
125
|
+
.readdirSync(sourceRoot)
|
|
126
|
+
.filter((item) => fs.statSync(path.join(sourceRoot, item)).isDirectory());
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function setup({ mode, sourceRoot, folders }) {
|
|
60
130
|
console.log('\n🚀 Setting up Cursor SDD...\n');
|
|
61
|
-
console.log(`📁 Target: ${targetDir}
|
|
62
|
-
|
|
131
|
+
console.log(`📁 Target: ${targetDir}`);
|
|
132
|
+
console.log(`🎚️ Mode: ${mode}\n`);
|
|
133
|
+
|
|
134
|
+
// 自動実行時は既存の .cursor がある場合スキップ
|
|
135
|
+
if (isAuto && fs.existsSync(targetDir) && !isForce) {
|
|
136
|
+
console.log('ℹ️ .cursor already exists. Run `npx cursor-sdd --force` to overwrite.');
|
|
137
|
+
process.exit(0);
|
|
138
|
+
}
|
|
139
|
+
|
|
63
140
|
// .cursor ディレクトリを作成
|
|
64
141
|
if (!fs.existsSync(targetDir)) {
|
|
65
142
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
66
143
|
}
|
|
67
|
-
|
|
144
|
+
|
|
145
|
+
if (!folders.length) {
|
|
146
|
+
console.log(`ℹ️ No folders to copy for mode: ${mode}.`);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
68
150
|
// 各フォルダをコピー
|
|
69
151
|
for (const folder of folders) {
|
|
70
|
-
const src = path.join(
|
|
152
|
+
const src = path.join(sourceRoot, folder);
|
|
71
153
|
const dest = path.join(targetDir, folder);
|
|
72
|
-
|
|
154
|
+
|
|
73
155
|
console.log(`📂 ${folder}/`);
|
|
74
156
|
copyRecursive(src, dest);
|
|
75
157
|
}
|
|
76
|
-
|
|
158
|
+
|
|
77
159
|
console.log('\n✨ Cursor SDD setup complete!\n');
|
|
78
160
|
console.log('Available commands:');
|
|
79
161
|
console.log(' /init - Initialize project specs');
|
|
@@ -84,11 +166,14 @@ function setup() {
|
|
|
84
166
|
console.log(' /status - Check status\n');
|
|
85
167
|
}
|
|
86
168
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
169
|
+
(async () => {
|
|
170
|
+
const mode = await resolveMode();
|
|
171
|
+
const sourceRoot = mode === 'assign' ? path.join(packageRoot, 'assign') : packageRoot;
|
|
172
|
+
const folders = getFolders(sourceRoot);
|
|
92
173
|
|
|
93
|
-
setup();
|
|
174
|
+
setup({ mode, sourceRoot, folders });
|
|
175
|
+
})().catch((err) => {
|
|
176
|
+
console.error(err);
|
|
177
|
+
process.exit(1);
|
|
178
|
+
});
|
|
94
179
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cursor-sdd",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "Cursor SDD (Spec-Driven Development) - AI-powered spec templates, rules and commands for Cursor IDE",
|
|
5
5
|
"bin": {
|
|
6
6
|
"cursor-sdd": "./bin/setup.js"
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
"bin",
|
|
10
10
|
"templates",
|
|
11
11
|
"rules",
|
|
12
|
-
"commands"
|
|
12
|
+
"commands",
|
|
13
|
+
"assign"
|
|
13
14
|
],
|
|
14
15
|
"scripts": {
|
|
15
16
|
"postinstall": "node bin/setup.js --auto"
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
{
|
|
2
|
+
"project_name": "{{PROJECT_NAME}}",
|
|
2
3
|
"feature_name": "{{FEATURE_NAME}}",
|
|
4
|
+
"feature_key": "{{FEATURE_KEY}}",
|
|
5
|
+
"feature_path": "{{FEATURE_PATH}}",
|
|
6
|
+
"mode": "{{INIT_MODE}}",
|
|
7
|
+
"project_summary": "{{PROJECT_DESCRIPTION}}",
|
|
3
8
|
"created_at": "{{TIMESTAMP}}",
|
|
4
9
|
"updated_at": "{{TIMESTAMP}}",
|
|
5
10
|
"language": "ja",
|