@tyyyho/treg 0.1.15 → 0.1.17

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 CHANGED
@@ -1,122 +1,69 @@
1
1
  # @tyyyho/treg
2
2
 
3
- [繁體中文 README](./README.zh-hant.md)
3
+ `treg` applies tooling standards to existing repositories.
4
4
 
5
- `treg` is a CLI for quickly setting up project tooling conventions in an existing repository.
6
- It applies infra setup such as lint, format, TypeScript, test, husky, and AI skill guidance.
5
+ Scope:
7
6
 
8
- ## Quick Start
7
+ - lint
8
+ - format
9
+ - TypeScript
10
+ - test
11
+ - husky
12
+ - AI skill guidance
9
13
 
10
- ```bash
11
- npx @tyyyho/treg init
12
- ```
13
-
14
- ```bash
15
- pnpm dlx @tyyyho/treg init
16
- ```
17
-
18
- `init` auto-detects framework from dependencies.
19
-
20
- ## Commands
21
-
22
- ```bash
23
- npx @tyyyho/treg <command> [options]
24
- ```
25
-
26
- - `init`: Initialize infra rules (framework auto-detected from dependencies)
27
- - `add`: Add selected infra features to an existing project
28
- - `list`: List supported frameworks, features, formatters, and test runners
29
-
30
- ## Options
31
-
32
- - `--framework <node|react|next|vue|svelte|nuxt>`: Optional framework override
33
- - `--features <lint,format,typescript,test,husky>`: Features to install (defaults to all)
34
- - `--no-format`: Skip format feature setup and avoid changing format configs/scripts
35
- - `--no-test-runner`: Skip test feature setup and avoid changing test runner/config
36
- - `--dir <path>`: Target directory (defaults to current directory)
37
- - `--formatter <prettier|oxfmt>`: Formatter for format feature (default: `prettier`)
38
- - `--test-runner <jest|vitest>`: Optional test runner override when test feature is enabled
39
- - `--pm <pnpm|npm|yarn|auto>`: Package manager (auto-detected by default)
40
- - `--force`: Overwrite existing config files
41
- - `--dry-run`: Print full plan without writing files
42
- - `--skip-husky-install`: Skip husky install command
43
- - `--skills`: Update existing `CLAUDE.md`/`AGENTS.md`/`GEMINI.md` with skill guidance (enabled by default)
44
- - `--no-skills`: Disable skill guidance updates
45
- - `--help`: Show help
46
-
47
- ## Features
48
-
49
- Default feature set:
50
-
51
- - `husky`
52
- - `typescript`
53
- - `lint`
54
- - `format`
55
- - `test`
56
-
57
- ## Examples
58
-
59
- Initialize with auto-detected framework:
14
+ Quick start:
60
15
 
61
16
  ```bash
62
17
  npx @tyyyho/treg init
63
18
  ```
64
19
 
65
- Initialize with explicit framework override:
20
+ Commands:
66
21
 
67
- ```bash
68
- npx @tyyyho/treg init --framework react
69
- ```
22
+ - `init`
23
+ - `add`
24
+ - `list`
70
25
 
71
- Add only lint + format:
26
+ `init` interactive questions:
72
27
 
73
- ```bash
74
- npx @tyyyho/treg add --features lint,format
75
- ```
28
+ 1. package manager (`pnpm|npm|yarn|bun`)
29
+ 2. features (default: `all`)
30
+ 3. test runner (if `test` is selected, supports `skip`)
31
+ 4. formatter (if `format` is selected)
32
+ 5. ai tools (`Claude|Codex|Gemini`, multi-select, if AI skill guidance is selected)
76
33
 
77
- Use oxfmt instead of prettier:
34
+ `add` examples:
78
35
 
79
36
  ```bash
37
+ npx @tyyyho/treg add --features lint,format
80
38
  npx @tyyyho/treg add --features format --formatter oxfmt
39
+ npx @tyyyho/treg add --features test --test-runner vitest
81
40
  ```
82
41
 
83
- Skip format/test setup to keep existing project rules untouched:
42
+ Options:
84
43
 
85
- ```bash
86
- npx @tyyyho/treg add --no-format --no-test-runner
87
- ```
88
-
89
- Use Vitest for test feature:
44
+ `init`:
90
45
 
91
- ```bash
92
- npx @tyyyho/treg init --framework node --features test --test-runner vitest
46
+ ```text
47
+ --dry-run
48
+ --help
93
49
  ```
94
50
 
95
- Preview changes only:
96
-
97
- ```bash
98
- npx @tyyyho/treg init --framework react --dry-run
99
- ```
100
-
101
- Update AI skill guidance:
102
-
103
- ```bash
104
- npx @tyyyho/treg add --features lint,format,husky
105
- ```
106
-
107
- Target a different directory explicitly:
108
-
109
- ```bash
110
- npx @tyyyho/treg init --framework react --dir ./packages/web
51
+ `add`:
52
+
53
+ ```text
54
+ --framework <node|react|next|vue|svelte|nuxt>
55
+ --features <lint,format,typescript,test,husky>
56
+ --dir <path>
57
+ --formatter <prettier|oxfmt>
58
+ --test-runner <jest|vitest>
59
+ --force
60
+ --dry-run
61
+ --skip-husky-install
62
+ --help
111
63
  ```
112
64
 
113
- ## Notes
65
+ Defaults:
114
66
 
115
- - `init` auto-detects framework from repo dependencies.
116
- - Detection order is `nuxt -> next -> react -> vue -> svelte -> node`.
117
- - Default test runner is `vitest` for `vue`/`nuxt`, and `jest` for other frameworks.
118
- - Default formatter is `prettier` (`--formatter oxfmt` to override).
119
- - `--no-format` and `--no-test-runner` let you skip format/test setup to avoid overriding existing project config.
120
- - `add` lets you install only the features you specify.
121
- - Framework setup uses one stable config per framework (no `--framework-version` variants).
122
- - `--dry-run` prints the full plan and does not write files.
67
+ - framework detect order: `nuxt -> next -> react -> vue -> svelte -> node`
68
+ - test runner: `vue/nuxt = vitest`, others = `jest`
69
+ - formatter: `prettier`
package/README.npm.md ADDED
@@ -0,0 +1,69 @@
1
+ # @tyyyho/treg
2
+
3
+ `treg` applies tooling standards to existing repositories.
4
+
5
+ Scope:
6
+
7
+ - lint
8
+ - format
9
+ - TypeScript
10
+ - test
11
+ - husky
12
+ - AI skill guidance
13
+
14
+ Quick start:
15
+
16
+ ```bash
17
+ npx @tyyyho/treg init
18
+ ```
19
+
20
+ Commands:
21
+
22
+ - `init`
23
+ - `add`
24
+ - `list`
25
+
26
+ `init` interactive questions:
27
+
28
+ 1. package manager (`pnpm|npm|yarn|bun`)
29
+ 2. features (default: `all`)
30
+ 3. test runner (if `test` is selected, supports `skip`)
31
+ 4. formatter (if `format` is selected)
32
+ 5. ai tools (`Claude|Codex|Gemini`, multi-select, if AI skill guidance is selected)
33
+
34
+ `add` examples:
35
+
36
+ ```bash
37
+ npx @tyyyho/treg add --features lint,format
38
+ npx @tyyyho/treg add --features format --formatter oxfmt
39
+ npx @tyyyho/treg add --features test --test-runner vitest
40
+ ```
41
+
42
+ Options:
43
+
44
+ `init`:
45
+
46
+ ```text
47
+ --dry-run
48
+ --help
49
+ ```
50
+
51
+ `add`:
52
+
53
+ ```text
54
+ --framework <node|react|next|vue|svelte|nuxt>
55
+ --features <lint,format,typescript,test,husky>
56
+ --dir <path>
57
+ --formatter <prettier|oxfmt>
58
+ --test-runner <jest|vitest>
59
+ --force
60
+ --dry-run
61
+ --skip-husky-install
62
+ --help
63
+ ```
64
+
65
+ Defaults:
66
+
67
+ - framework detect order: `nuxt -> next -> react -> vue -> svelte -> node`
68
+ - test runner: `vue/nuxt = vitest`, others = `jest`
69
+ - formatter: `prettier`
package/README.zh-hant.md CHANGED
@@ -2,8 +2,15 @@
2
2
 
3
3
  [English README](./README.md)
4
4
 
5
- `treg` 是一個用於既有專案的 CLI 工具,可快速建立與套用工具鏈規範。
6
- 預設搭建基礎設定,包含 lint、format、TypeScript、test、husky、skills。
5
+ `treg` 是一個用於既有專案的 CLI,可快速套用一致的工具鏈規範。
6
+ 它只處理基礎設施設定:
7
+
8
+ - lint
9
+ - format
10
+ - TypeScript
11
+ - test
12
+ - husky
13
+ - AI skill 指引
7
14
 
8
15
  ## 快速開始
9
16
 
@@ -11,112 +18,124 @@
11
18
  npx @tyyyho/treg init
12
19
  ```
13
20
 
14
- ```bash
15
- pnpm dlx @tyyyho/treg init
16
- ```
17
-
18
- `init` 會依照依賴自動偵測 framework。
21
+ ## 指令總覽
19
22
 
20
- ## 指令
23
+ - `init`:初始化功能。
24
+ - `add`:只為既有專案加入指定功能。
25
+ - `list`:列出支援的 framework/feature/formatter/test runner。
21
26
 
22
- ```bash
23
- npx @tyyyho/treg <command> [options]
24
- ```
27
+ ## Init 互動流程
25
28
 
26
- - `init`:初始化基礎規範(依賴自動偵測 framework)
27
- - `add`:在既有專案中新增指定 feature
28
- - `list`:列出支援的 framework、feature、formatter 與 test runner
29
+ 執行 `init` 後,`treg` 會依序詢問:
29
30
 
30
- ## 參數
31
+ 1. 套件管理器(`pnpm|npm|yarn|bun`)
32
+ 2. 要加入的功能(預設勾選 `all`)
33
+ 3. 測試工具(僅在選到 `test` 時詢問,支援 `skip`)
34
+ 4. Formatter(僅在選到 `format` 時詢問)
35
+ 5. AI 工具(`Claude|Codex|Gemini` 可複選,僅在選到 AI skill guidance 時詢問)
31
36
 
32
- - `--framework <node|react|next|vue|svelte|nuxt>`:可選,手動覆寫 framework
33
- - `--features <lint,format,typescript,test,husky>`:指定要安裝的 feature(預設全部)
34
- - `--no-format`:略過 format feature,避免覆寫既有格式化設定與 scripts
35
- - `--no-test-runner`:略過 test feature,避免覆寫既有測試 runner 與設定
36
- - `--dir <path>`:指定目標目錄(預設為目前目錄)
37
- - `--formatter <prettier|oxfmt>`:format feature 使用的 formatter(預設為 `prettier`)
38
- - `--test-runner <jest|vitest>`:可選,啟用 test feature 時覆寫測試框架
39
- - `--pm <pnpm|npm|yarn|auto>`:套件管理器(預設自動偵測)
40
- - `--force`:覆寫既有設定檔
41
- - `--dry-run`:輸出完整執行計畫,但不寫入檔案
42
- - `--skip-husky-install`:略過 `husky install`
43
- - `--skills`:更新既有 `CLAUDE.md`/`AGENTS.md`/`GEMINI.md` 的 skill 指引(預設啟用)
44
- - `--no-skills`:停用 skill 指引更新
45
- - `--help`:顯示說明
37
+ 預設 `all` 內容:
46
38
 
47
- ## Features
39
+ - lint
40
+ - format
41
+ - TypeScript
42
+ - test
43
+ - husky
44
+ - AI skill guidance
48
45
 
49
- 預設 feature 組合:
46
+ ## 常見用法
50
47
 
51
- - `husky`
52
- - `typescript`
53
- - `lint`
54
- - `format`
55
- - `test`
56
-
57
- ## 使用範例
58
-
59
- 依賴自動偵測 framework 初始化:
48
+ 初始化:
60
49
 
61
50
  ```bash
62
51
  npx @tyyyho/treg init
63
52
  ```
64
53
 
65
- 手動指定 framework 初始化:
54
+ 只預覽 init 計畫:
66
55
 
67
56
  ```bash
68
- npx @tyyyho/treg init --framework react
57
+ npx @tyyyho/treg init --dry-run
69
58
  ```
70
59
 
71
- 只安裝 lint + format:
60
+ 只加入 lint + format:
72
61
 
73
62
  ```bash
74
63
  npx @tyyyho/treg add --features lint,format
75
64
  ```
76
65
 
77
- 改用 oxfmt(不使用 prettier):
66
+ format 使用 `oxfmt`:
78
67
 
79
68
  ```bash
80
69
  npx @tyyyho/treg add --features format --formatter oxfmt
81
70
  ```
82
71
 
83
- 若要保留既有設定,可跳過 format/test 安裝:
72
+ test 使用 `vitest`:
84
73
 
85
74
  ```bash
86
- npx @tyyyho/treg add --no-format --no-test-runner
75
+ npx @tyyyho/treg add --features test --test-runner vitest
87
76
  ```
88
77
 
89
- test feature 使用 Vitest:
78
+ ## CLI 參數
90
79
 
91
- ```bash
92
- npx @tyyyho/treg init --framework node --features test --test-runner vitest
93
- ```
80
+ `init` 可用參數:
94
81
 
95
- 僅預覽變更(不寫檔):
82
+ ```text
83
+ --dry-run
84
+ --help
85
+ ```
96
86
 
97
- ```bash
98
- npx @tyyyho/treg init --framework react --dry-run
87
+ `add` 可用參數:
88
+
89
+ ```text
90
+ --framework <node|react|next|vue|svelte|nuxt>
91
+ --features <lint,format,typescript,test,husky>
92
+ --dir <path>
93
+ --formatter <prettier|oxfmt>
94
+ --test-runner <jest|vitest>
95
+ --force
96
+ --dry-run
97
+ --skip-husky-install
98
+ --help
99
99
  ```
100
100
 
101
- 更新 AI skills 指引:
101
+ ## 預設行為
102
102
 
103
- ```bash
104
- npx @tyyyho/treg add --features lint,format,husky
105
- ```
103
+ framework 偵測順序:
104
+
105
+ `nuxt -> next -> react -> vue -> svelte -> node`
106
+
107
+ 測試工具預設:
108
+
109
+ - `vue` / `nuxt`:`vitest`
110
+ - 其他:`jest`
111
+
112
+ formatter 預設:
113
+
114
+ - `prettier`
115
+
116
+ ## AI Skills 行為
117
+
118
+ - 只會更新選擇的 AI 工具對應檔案:
119
+ - `Claude -> CLAUDE.md`
120
+ - `Codex -> AGENTS.md`
121
+ - `Gemini -> GEMINI.md`
122
+ - 僅更新 repo root 已存在的檔案。
123
+ - 不存在的檔案會跳過,不會自動建立。
124
+ - 每個啟用功能的 skill 檔只會建立一次。
106
125
 
107
- 明確指定其他目錄:
126
+ ## 發布
108
127
 
109
128
  ```bash
110
- npx @tyyyho/treg init --framework react --dir ./packages/web
129
+ npm run release -- patch
111
130
  ```
112
131
 
113
- ## 注意事項
132
+ 支援目標:
114
133
 
115
- - `init` 會依 repo 依賴自動偵測 framework。
116
- - 偵測順序:`nuxt -> next -> react -> vue -> svelte -> node`。
117
- - 預設測試工具為:`vue`/`nuxt` 使用 `vitest`,其他 framework 使用 `jest`。
118
- - 預設 formatter 為 `prettier`(可用 `--formatter oxfmt` 覆寫)。
119
- - 可透過 `--no-format` 與 `--no-test-runner` 跳過對 format/test 的設定,避免覆寫既有專案規則。
120
- - `add` 可只安裝你指定的 features。
121
- - 每個 framework 僅提供單一穩定設定,不支援 `--framework-version` 版本變體。
122
- - `--dry-run` 會輸出完整計畫且不寫入任何檔案。
134
+ - `patch`
135
+ - `minor`
136
+ - `major`
137
+ - `prepatch`
138
+ - `preminor`
139
+ - `premajor`
140
+ - `prerelease`
141
+ - 指定版本(`x.y.z`)
@@ -1,10 +1,4 @@
1
1
  const ALLOWED_COMMANDS = ["init", "add", "list"];
2
- const ALLOWED_PACKAGE_MANAGERS = [
3
- "pnpm",
4
- "npm",
5
- "yarn",
6
- "auto",
7
- ];
8
2
  const ALLOWED_FRAMEWORKS = [
9
3
  "node",
10
4
  "react",
@@ -22,30 +16,28 @@ const ALLOWED_FEATURES = [
22
16
  ];
23
17
  const ALLOWED_TEST_RUNNERS = ["jest", "vitest"];
24
18
  const ALLOWED_FORMATTERS = ["prettier", "oxfmt"];
19
+ const DEFAULT_AI_TOOLS = ["claude", "codex", "gemini"];
25
20
  export const USAGE = `Usage: treg <command> [options]
26
21
 
27
22
  Commands:
28
- init Initialize infra rules in a project (framework auto-detected from dependencies)
23
+ init Initialize infra rules in a project (interactive setup)
29
24
  add Add selected infra features to an existing project
30
25
  list List supported frameworks, features, formatters, and test runners
31
26
 
32
27
  Options:
28
+ --dry-run Print planned changes without writing files
29
+ -h, --help Show help
30
+
31
+ Add command options:
33
32
  --framework <node|react|next|vue|svelte|nuxt>
34
33
  Optional framework override (default: auto-detected)
35
34
  --features <lint,format,typescript,test,husky>
36
35
  Features to install (all selected by default)
37
- --no-format Skip format feature setup and avoid changing format config/scripts
38
- --no-test-runner Skip test feature setup and avoid changing test runner/config
39
36
  --dir <path> Target directory (defaults to current directory)
40
37
  --formatter <prettier|oxfmt> Formatter for format feature (default: prettier)
41
38
  --test-runner <jest|vitest> Optional test runner override (default: vue/nuxt=vitest, others=jest)
42
- --pm <pnpm|npm|yarn|auto> Package manager (auto-detected if omitted)
43
39
  --force Overwrite existing config files
44
- --dry-run Print planned changes without writing files
45
40
  --skip-husky-install Do not run husky install
46
- --skills Update CLAUDE.md/AGENTS.md/GEMINI.md with feature skill guidance (default: enabled)
47
- --no-skills Disable CLAUDE.md/AGENTS.md/GEMINI.md skill guidance updates
48
- -h, --help Show help
49
41
  `;
50
42
  function includes(allowed, value) {
51
43
  return allowed.includes(value);
@@ -53,9 +45,6 @@ function includes(allowed, value) {
53
45
  function isCommandName(value) {
54
46
  return includes(ALLOWED_COMMANDS, value);
55
47
  }
56
- function isPackageManagerOption(value) {
57
- return includes(ALLOWED_PACKAGE_MANAGERS, value);
58
- }
59
48
  function isFrameworkId(value) {
60
49
  return includes(ALLOWED_FRAMEWORKS, value);
61
50
  }
@@ -76,13 +65,11 @@ export function parseArgs(argv) {
76
65
  formatter: "prettier",
77
66
  features: [],
78
67
  testRunner: null,
79
- pm: null,
80
68
  force: false,
81
69
  dryRun: false,
82
- noFormat: false,
83
- noTestRunner: false,
84
70
  skipHuskyInstall: false,
85
71
  skills: true,
72
+ aiTools: [...DEFAULT_AI_TOOLS],
86
73
  help: false,
87
74
  };
88
75
  let cursor = 0;
@@ -98,8 +85,16 @@ export function parseArgs(argv) {
98
85
  }
99
86
  if (arg === "-h" || arg === "--help") {
100
87
  options.help = true;
88
+ continue;
89
+ }
90
+ if (arg === "--dry-run") {
91
+ options.dryRun = true;
92
+ continue;
93
+ }
94
+ if (options.command !== "add") {
95
+ throw new Error(`Unsupported option for ${options.command}: ${arg}. Only --dry-run and --help are allowed.`);
101
96
  }
102
- else if (arg === "--framework") {
97
+ if (arg === "--framework") {
103
98
  options.framework = readFlagValue(argv, i, "--framework");
104
99
  i += 1;
105
100
  }
@@ -134,34 +129,12 @@ export function parseArgs(argv) {
134
129
  else if (arg.startsWith("--test-runner=")) {
135
130
  options.testRunner = readInlineFlagValue(arg, "--test-runner");
136
131
  }
137
- else if (arg === "--pm") {
138
- options.pm = readFlagValue(argv, i, "--pm");
139
- i += 1;
140
- }
141
- else if (arg.startsWith("--pm=")) {
142
- options.pm = readInlineFlagValue(arg, "--pm");
143
- }
144
132
  else if (arg === "--force") {
145
133
  options.force = true;
146
134
  }
147
- else if (arg === "--dry-run") {
148
- options.dryRun = true;
149
- }
150
- else if (arg === "--no-format") {
151
- options.noFormat = true;
152
- }
153
- else if (arg === "--no-test-runner") {
154
- options.noTestRunner = true;
155
- }
156
135
  else if (arg === "--skip-husky-install") {
157
136
  options.skipHuskyInstall = true;
158
137
  }
159
- else if (arg === "--skills") {
160
- options.skills = true;
161
- }
162
- else if (arg === "--no-skills") {
163
- options.skills = false;
164
- }
165
138
  else {
166
139
  throw new Error(`Unknown argument: ${arg}`);
167
140
  }
@@ -199,9 +172,6 @@ function validateParsedOptions(options) {
199
172
  if (options.projectDir === "") {
200
173
  throw new Error("Missing value for --dir");
201
174
  }
202
- if (options.pm && !isPackageManagerOption(options.pm)) {
203
- throw new Error(`Unsupported package manager: ${options.pm}`);
204
- }
205
175
  if (options.framework && !isFrameworkId(options.framework)) {
206
176
  throw new Error(`Unsupported framework: ${options.framework}`);
207
177
  }
@@ -219,12 +189,6 @@ function validateParsedOptions(options) {
219
189
  }
220
190
  export function resolveFeatures(options) {
221
191
  const selected = new Set(options.features.length > 0 ? options.features : ALLOWED_FEATURES);
222
- if (options.noFormat) {
223
- selected.delete("format");
224
- }
225
- if (options.noTestRunner) {
226
- selected.delete("test");
227
- }
228
192
  return {
229
193
  lint: selected.has("lint"),
230
194
  format: selected.has("format"),
@@ -238,6 +202,8 @@ export function printSupportedTargets() {
238
202
  console.log("Features: lint, format, typescript, test, husky");
239
203
  console.log("Formatters: prettier, oxfmt");
240
204
  console.log("Test runners: jest, vitest");
205
+ console.log("Package managers: pnpm, npm, yarn, bun");
206
+ console.log("AI tools: Claude, Codex, Gemini");
241
207
  }
242
208
  export function resolveTestRunner(frameworkId, testRunner) {
243
209
  if (testRunner) {
@@ -3,6 +3,7 @@ import { promises as fs } from "node:fs";
3
3
  import path from "node:path";
4
4
  import { parseArgs, printSupportedTargets, resolveFeatures, resolveTestRunner, USAGE, } from "./cli.js";
5
5
  import { resolveFramework } from "./frameworks/index.js";
6
+ import { collectInitPrompts } from "./init-prompts.js";
6
7
  import { runFeatureRules } from "./mrm-rules/index.js";
7
8
  import { detectPackageManager, runScript } from "./package-manager.js";
8
9
  import { formatStep } from "./utils.js";
@@ -34,25 +35,42 @@ export async function main(argv = process.argv.slice(2)) {
34
35
  return;
35
36
  }
36
37
  const packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf8"));
37
- const pm = !options.pm || options.pm === "auto"
38
- ? detectPackageManager(projectDir)
39
- : options.pm;
40
38
  const framework = resolveFramework(options.framework, packageJson);
41
- const testRunner = resolveTestRunner(framework.id, options.testRunner);
42
- const enabledFeatures = resolveFeatures(options);
39
+ let pm = detectPackageManager(projectDir);
40
+ let formatter = options.formatter;
41
+ let testRunner = resolveTestRunner(framework.id, options.testRunner);
42
+ let enabledFeatures = resolveFeatures(options);
43
+ let skills = options.skills;
44
+ let aiTools = [...options.aiTools];
45
+ if (options.command === "init") {
46
+ const prompted = await collectInitPrompts({
47
+ pm,
48
+ formatter,
49
+ testRunner: resolveTestRunner(framework.id, null),
50
+ });
51
+ pm = prompted.pm;
52
+ formatter = prompted.formatter;
53
+ testRunner = prompted.testRunner;
54
+ enabledFeatures = prompted.enabledFeatures;
55
+ skills = prompted.skills;
56
+ aiTools = prompted.aiTools;
57
+ }
43
58
  const context = {
44
59
  ...options,
60
+ formatter,
45
61
  testRunner,
46
62
  projectDir,
47
63
  pm,
48
64
  framework,
49
65
  enabledFeatures,
66
+ skills,
67
+ aiTools,
50
68
  };
51
69
  console.log(formatStep(1, TOTAL_STEPS, "Resolve plan", options.dryRun));
52
- console.log(`${options.dryRun ? "[dry-run] " : ""}Framework=${framework.id}, features=${Object.entries(enabledFeatures)
70
+ console.log(`${options.dryRun ? "[dry-run] " : ""}Framework=${framework.id}, pm=${pm}, features=${Object.entries(enabledFeatures)
53
71
  .filter(([, enabled]) => enabled)
54
72
  .map(([name]) => name)
55
- .join(", ")}, formatter=${options.formatter}, testRunner=${testRunner}`);
73
+ .join(", ")}, formatter=${formatter}, testRunner=${testRunner}, aiTools=${skills ? aiTools.join(", ") : "disabled"}`);
56
74
  console.log(formatStep(2, TOTAL_STEPS, "Run mrm rules", options.dryRun));
57
75
  await runFeatureRules(context);
58
76
  console.log(formatStep(3, TOTAL_STEPS, "Finalize", options.dryRun));
@@ -0,0 +1,221 @@
1
+ import { stdin as input, stdout as output } from "node:process";
2
+ import { createInterface } from "node:readline/promises";
3
+ const DEFAULT_AI_TOOLS = ["claude", "codex", "gemini"];
4
+ const PACKAGE_MANAGER_CHOICES = [
5
+ { value: "pnpm", label: "pnpm" },
6
+ { value: "npm", label: "npm" },
7
+ { value: "yarn", label: "yarn" },
8
+ { value: "bun", label: "bun" },
9
+ ];
10
+ const TEST_RUNNER_CHOICES = [
11
+ { value: "jest", label: "jest" },
12
+ { value: "vitest", label: "vitest" },
13
+ { value: "skip", label: "skip (disable test feature)" },
14
+ ];
15
+ const FORMATTER_CHOICES = [
16
+ { value: "prettier", label: "prettier" },
17
+ { value: "oxfmt", label: "oxfmt" },
18
+ ];
19
+ const AI_TOOL_CHOICES = [
20
+ { value: "claude", label: "Claude" },
21
+ { value: "codex", label: "Codex" },
22
+ { value: "gemini", label: "Gemini" },
23
+ ];
24
+ const FEATURE_CHOICES = [
25
+ {
26
+ value: "all",
27
+ label: "all (lint, format, TypeScript, test, husky, AI skill guidance)",
28
+ },
29
+ { value: "lint", label: "lint" },
30
+ { value: "format", label: "format" },
31
+ { value: "typescript", label: "TypeScript" },
32
+ { value: "test", label: "test" },
33
+ { value: "husky", label: "husky" },
34
+ { value: "skills", label: "AI skill guidance" },
35
+ ];
36
+ function formatChoices(choices) {
37
+ return choices
38
+ .map((choice, index) => ` ${index + 1}. ${choice.label}`)
39
+ .join("\n");
40
+ }
41
+ function parseSingleChoice(rawInput, choices, defaultValue) {
42
+ const normalized = rawInput.trim().toLowerCase();
43
+ if (!normalized) {
44
+ return { ok: true, value: defaultValue };
45
+ }
46
+ const byIndex = Number.parseInt(normalized, 10);
47
+ if (!Number.isNaN(byIndex) && String(byIndex) === normalized) {
48
+ const selected = choices[byIndex - 1];
49
+ if (selected) {
50
+ return { ok: true, value: selected.value };
51
+ }
52
+ }
53
+ const byValue = choices.find(choice => choice.value === normalized);
54
+ if (byValue) {
55
+ return { ok: true, value: byValue.value };
56
+ }
57
+ return {
58
+ ok: false,
59
+ error: `Invalid input: ${rawInput}. Please enter a listed number or option name.`,
60
+ };
61
+ }
62
+ function parseMultiChoice(rawInput, choices, defaultValues) {
63
+ const normalized = rawInput.trim().toLowerCase();
64
+ if (!normalized) {
65
+ return { ok: true, value: [...defaultValues] };
66
+ }
67
+ if (normalized === "skip" || normalized === "none") {
68
+ return { ok: true, value: [] };
69
+ }
70
+ const tokens = normalized
71
+ .split(",")
72
+ .map(item => item.trim())
73
+ .filter(Boolean);
74
+ if (tokens.length === 0) {
75
+ return { ok: false, error: "Please enter at least one option." };
76
+ }
77
+ const selected = new Set();
78
+ for (const token of tokens) {
79
+ const byIndex = Number.parseInt(token, 10);
80
+ if (!Number.isNaN(byIndex) && String(byIndex) === token) {
81
+ const item = choices[byIndex - 1];
82
+ if (!item) {
83
+ return {
84
+ ok: false,
85
+ error: `Invalid index: ${token}. Please choose from listed options.`,
86
+ };
87
+ }
88
+ selected.add(item.value);
89
+ continue;
90
+ }
91
+ const byValue = choices.find(choice => choice.value === token);
92
+ if (!byValue) {
93
+ return {
94
+ ok: false,
95
+ error: `Invalid option: ${token}. Please choose from listed options.`,
96
+ };
97
+ }
98
+ selected.add(byValue.value);
99
+ }
100
+ return { ok: true, value: [...selected] };
101
+ }
102
+ function toFeatureSelection(selected) {
103
+ if (selected.includes("all")) {
104
+ return {
105
+ enabledFeatures: {
106
+ lint: true,
107
+ format: true,
108
+ typescript: true,
109
+ test: true,
110
+ husky: true,
111
+ },
112
+ skills: true,
113
+ };
114
+ }
115
+ return {
116
+ enabledFeatures: {
117
+ lint: selected.includes("lint"),
118
+ format: selected.includes("format"),
119
+ typescript: selected.includes("typescript"),
120
+ test: selected.includes("test"),
121
+ husky: selected.includes("husky"),
122
+ },
123
+ skills: selected.includes("skills"),
124
+ };
125
+ }
126
+ async function askUntilValid(ask, prompt, parser) {
127
+ while (true) {
128
+ const raw = await ask(prompt);
129
+ const parsed = parser(raw);
130
+ if (parsed.ok && parsed.value !== undefined) {
131
+ return parsed.value;
132
+ }
133
+ console.log(parsed.error ?? "Invalid input");
134
+ }
135
+ }
136
+ export async function collectInitPrompts(defaults) {
137
+ if (!input.isTTY || !output.isTTY) {
138
+ console.log("Non-interactive shell detected. Use init defaults.");
139
+ return {
140
+ pm: defaults.pm,
141
+ formatter: defaults.formatter,
142
+ testRunner: defaults.testRunner,
143
+ enabledFeatures: {
144
+ lint: true,
145
+ format: true,
146
+ typescript: true,
147
+ test: true,
148
+ husky: true,
149
+ },
150
+ skills: true,
151
+ aiTools: [...DEFAULT_AI_TOOLS],
152
+ };
153
+ }
154
+ const rl = createInterface({ input, output });
155
+ try {
156
+ console.log("\nInit setup");
157
+ console.log("\n1) Package manager");
158
+ console.log(formatChoices(PACKAGE_MANAGER_CHOICES));
159
+ const pm = await askUntilValid(rl.question.bind(rl), `Select package manager [default: ${defaults.pm}]: `, answer => parseSingleChoice(answer, PACKAGE_MANAGER_CHOICES, defaults.pm));
160
+ console.log("\n2) Features");
161
+ console.log(formatChoices(FEATURE_CHOICES));
162
+ const featureAnswers = await askUntilValid(rl.question.bind(rl), "Select features (comma-separated, default: all): ", answer => parseMultiChoice(answer, FEATURE_CHOICES, ["all"]));
163
+ const featureSelection = toFeatureSelection(featureAnswers);
164
+ let testRunner = defaults.testRunner;
165
+ const enabledFeatures = { ...featureSelection.enabledFeatures };
166
+ if (featureSelection.enabledFeatures.test) {
167
+ console.log("\n3) Test runner");
168
+ console.log(formatChoices(TEST_RUNNER_CHOICES));
169
+ const selectedTestRunner = await askUntilValid(rl.question.bind(rl), `Select test runner [default: ${defaults.testRunner}, or skip]: `, answer => parseSingleChoice(answer, TEST_RUNNER_CHOICES, defaults.testRunner));
170
+ if (selectedTestRunner === "skip") {
171
+ enabledFeatures.test = false;
172
+ console.log("Test feature disabled by selection: skip");
173
+ }
174
+ else {
175
+ testRunner = selectedTestRunner;
176
+ }
177
+ }
178
+ else {
179
+ console.log("\n3) Test runner skipped (test feature not selected)");
180
+ }
181
+ let formatter = defaults.formatter;
182
+ if (featureSelection.enabledFeatures.format) {
183
+ console.log("\n4) Formatter");
184
+ console.log(formatChoices(FORMATTER_CHOICES));
185
+ formatter = await askUntilValid(rl.question.bind(rl), `Select formatter [default: ${defaults.formatter}]: `, answer => parseSingleChoice(answer, FORMATTER_CHOICES, defaults.formatter));
186
+ }
187
+ else {
188
+ console.log("\n4) Formatter skipped (format feature not selected)");
189
+ }
190
+ let aiTools = [];
191
+ let skills = featureSelection.skills;
192
+ if (skills) {
193
+ console.log("\n5) AI tools");
194
+ console.log(formatChoices(AI_TOOL_CHOICES));
195
+ console.log("Type 'skip' to disable AI skill guidance.");
196
+ aiTools = await askUntilValid(rl.question.bind(rl), "Select AI tools (comma-separated, default: all): ", answer => parseMultiChoice(answer, AI_TOOL_CHOICES, DEFAULT_AI_TOOLS));
197
+ if (aiTools.length === 0) {
198
+ skills = false;
199
+ }
200
+ }
201
+ else {
202
+ console.log("\n5) AI tools skipped (AI skill guidance not selected)");
203
+ }
204
+ return {
205
+ pm,
206
+ formatter,
207
+ testRunner,
208
+ enabledFeatures,
209
+ skills,
210
+ aiTools,
211
+ };
212
+ }
213
+ finally {
214
+ rl.close();
215
+ }
216
+ }
217
+ export const __testables__ = {
218
+ parseSingleChoice,
219
+ parseMultiChoice,
220
+ toFeatureSelection,
221
+ };
@@ -5,7 +5,11 @@ const START_MARKER = "<!-- treg:skills:start -->";
5
5
  const END_MARKER = "<!-- treg:skills:end -->";
6
6
  const SKILL_SECTION_HEADING = "## treg AI Skills";
7
7
  const SKILLS_BASE_DIR = "skills";
8
- const SKILLS_DOC_CANDIDATES = ["CLAUDE.md", "AGENTS.md", "GEMINI.md"];
8
+ const AI_TOOL_DOCS = {
9
+ claude: "CLAUDE.md",
10
+ codex: "AGENTS.md",
11
+ gemini: "GEMINI.md",
12
+ };
9
13
  const FEATURE_SKILLS = {
10
14
  format: {
11
15
  name: "treg/format",
@@ -65,8 +69,11 @@ const FEATURE_STEP_LABELS = {
65
69
  test: "Test Configuration",
66
70
  typescript: "TypeScript Settings",
67
71
  };
68
- function resolveSkillsDocs(projectDir) {
69
- return SKILLS_DOC_CANDIDATES.map(fileName => path.join(projectDir, fileName)).filter(existsSync);
72
+ function resolveSkillsDocs(projectDir, aiTools) {
73
+ const docFiles = [...new Set(aiTools.map(tool => AI_TOOL_DOCS[tool]))];
74
+ return docFiles
75
+ .map(fileName => path.join(projectDir, fileName))
76
+ .filter(existsSync);
70
77
  }
71
78
  function getEnabledFeatures(enabledFeatures) {
72
79
  return Object.entries(enabledFeatures)
@@ -168,10 +175,10 @@ function upsertSkillSection(content, nextSection) {
168
175
  return `${content.trimEnd()}\n\n${nextSection.trim()}\n`;
169
176
  }
170
177
  export async function runAiSkillsRule(context) {
171
- const { projectDir, dryRun } = context;
172
- const targetFiles = resolveSkillsDocs(projectDir);
178
+ const { projectDir, dryRun, aiTools } = context;
179
+ const targetFiles = resolveSkillsDocs(projectDir, aiTools);
173
180
  if (targetFiles.length === 0) {
174
- console.log("Skip ai-skills (CLAUDE.md/AGENTS.md/GEMINI.md not found)");
181
+ console.log("Skip ai-skills (selected AI docs not found)");
175
182
  return;
176
183
  }
177
184
  const enabled = getEnabledFeatures(context.enabledFeatures);
@@ -53,6 +53,10 @@ function runHuskyInstallCommand(pm, projectDir) {
53
53
  runCommand("pnpm exec husky", projectDir, false);
54
54
  return;
55
55
  }
56
+ if (pm === "bun") {
57
+ runCommand("bunx husky", projectDir, false);
58
+ return;
59
+ }
56
60
  if (pm === "yarn") {
57
61
  runCommand("yarn husky", projectDir, false);
58
62
  return;
@@ -5,6 +5,10 @@ export function detectPackageManager(projectDir) {
5
5
  if (existsSync(path.join(projectDir, "pnpm-lock.yaml"))) {
6
6
  return "pnpm";
7
7
  }
8
+ if (existsSync(path.join(projectDir, "bun.lockb")) ||
9
+ existsSync(path.join(projectDir, "bun.lock"))) {
10
+ return "bun";
11
+ }
8
12
  if (existsSync(path.join(projectDir, "yarn.lock"))) {
9
13
  return "yarn";
10
14
  }
@@ -16,6 +20,8 @@ export function detectPackageManager(projectDir) {
16
20
  export function getRunCommand(pm) {
17
21
  if (pm === "pnpm")
18
22
  return "pnpm";
23
+ if (pm === "bun")
24
+ return "bun run";
19
25
  if (pm === "yarn")
20
26
  return "yarn";
21
27
  return "npm run";
@@ -32,6 +38,10 @@ export function runScript(pm, scriptName, cwd, dryRun = false) {
32
38
  runCommand(`pnpm ${scriptName}`, cwd, dryRun);
33
39
  return;
34
40
  }
41
+ if (pm === "bun") {
42
+ runCommand(`bun run ${scriptName}`, cwd, dryRun);
43
+ return;
44
+ }
35
45
  if (pm === "yarn") {
36
46
  runCommand(`yarn ${scriptName}`, cwd, dryRun);
37
47
  return;
@@ -46,6 +56,9 @@ export function installPackages(pm, projectDir, packages, isDev, dryRun = false)
46
56
  if (pm === "pnpm") {
47
57
  command = `pnpm add ${isDev ? "-D " : ""}${list}`;
48
58
  }
59
+ else if (pm === "bun") {
60
+ command = `bun add ${isDev ? "-d " : ""}${list}`;
61
+ }
49
62
  else if (pm === "yarn") {
50
63
  command = `yarn add ${isDev ? "-D " : ""}${list}`;
51
64
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tyyyho/treg",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "description": "CLI tool for initializing development conventions in existing projects.",
5
5
  "license": "MIT",
6
6
  "repository": {