@tyyyho/treg 0.1.16 → 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,8 +1,8 @@
1
1
  # @tyyyho/treg
2
2
 
3
- `treg` is a CLI for applying tooling standards in existing repositories.
3
+ `treg` applies tooling standards to existing repositories.
4
4
 
5
- It sets up infrastructure only:
5
+ Scope:
6
6
 
7
7
  - lint
8
8
  - format
@@ -11,71 +11,59 @@ It sets up infrastructure only:
11
11
  - husky
12
12
  - AI skill guidance
13
13
 
14
- ## Quick Start
14
+ Quick start:
15
15
 
16
16
  ```bash
17
17
  npx @tyyyho/treg init
18
18
  ```
19
19
 
20
- ## Commands
20
+ Commands:
21
21
 
22
22
  - `init`
23
23
  - `add`
24
24
  - `list`
25
25
 
26
- ## Common Examples
26
+ `init` interactive questions:
27
27
 
28
- Initialize with auto-detection:
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)
29
33
 
30
- ```bash
31
- npx @tyyyho/treg init
32
- ```
33
-
34
- Initialize with explicit framework:
35
-
36
- ```bash
37
- npx @tyyyho/treg init --framework react
38
- ```
39
-
40
- Apply selected features:
34
+ `add` examples:
41
35
 
42
36
  ```bash
43
37
  npx @tyyyho/treg add --features lint,format
44
- ```
45
-
46
- Use `oxfmt`:
47
-
48
- ```bash
49
38
  npx @tyyyho/treg add --features format --formatter oxfmt
39
+ npx @tyyyho/treg add --features test --test-runner vitest
50
40
  ```
51
41
 
52
- Skip format and test setup:
42
+ Options:
53
43
 
54
- ```bash
55
- npx @tyyyho/treg add --no-format --no-test-runner
56
- ```
57
-
58
- Preview only:
44
+ `init`:
59
45
 
60
- ```bash
61
- npx @tyyyho/treg init --dry-run
46
+ ```text
47
+ --dry-run
48
+ --help
62
49
  ```
63
50
 
64
- ## Key Options
51
+ `add`:
65
52
 
66
53
  ```text
67
54
  --framework <node|react|next|vue|svelte|nuxt>
68
55
  --features <lint,format,typescript,test,husky>
69
- --no-format
70
- --no-test-runner
56
+ --dir <path>
71
57
  --formatter <prettier|oxfmt>
72
58
  --test-runner <jest|vitest>
73
- --pm <pnpm|npm|yarn|auto>
74
- --dir <path>
75
59
  --force
76
60
  --dry-run
77
61
  --skip-husky-install
78
- --skills
79
- --no-skills
80
62
  --help
81
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.npm.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # @tyyyho/treg
2
2
 
3
- `treg` is a CLI for applying tooling standards in existing repositories.
3
+ `treg` applies tooling standards to existing repositories.
4
4
 
5
- It sets up infrastructure only:
5
+ Scope:
6
6
 
7
7
  - lint
8
8
  - format
@@ -11,71 +11,59 @@ It sets up infrastructure only:
11
11
  - husky
12
12
  - AI skill guidance
13
13
 
14
- ## Quick Start
14
+ Quick start:
15
15
 
16
16
  ```bash
17
17
  npx @tyyyho/treg init
18
18
  ```
19
19
 
20
- ## Commands
20
+ Commands:
21
21
 
22
22
  - `init`
23
23
  - `add`
24
24
  - `list`
25
25
 
26
- ## Common Examples
26
+ `init` interactive questions:
27
27
 
28
- Initialize with auto-detection:
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)
29
33
 
30
- ```bash
31
- npx @tyyyho/treg init
32
- ```
33
-
34
- Initialize with explicit framework:
35
-
36
- ```bash
37
- npx @tyyyho/treg init --framework react
38
- ```
39
-
40
- Apply selected features:
34
+ `add` examples:
41
35
 
42
36
  ```bash
43
37
  npx @tyyyho/treg add --features lint,format
44
- ```
45
-
46
- Use `oxfmt`:
47
-
48
- ```bash
49
38
  npx @tyyyho/treg add --features format --formatter oxfmt
39
+ npx @tyyyho/treg add --features test --test-runner vitest
50
40
  ```
51
41
 
52
- Skip format and test setup:
42
+ Options:
53
43
 
54
- ```bash
55
- npx @tyyyho/treg add --no-format --no-test-runner
56
- ```
57
-
58
- Preview only:
44
+ `init`:
59
45
 
60
- ```bash
61
- npx @tyyyho/treg init --dry-run
46
+ ```text
47
+ --dry-run
48
+ --help
62
49
  ```
63
50
 
64
- ## Key Options
51
+ `add`:
65
52
 
66
53
  ```text
67
54
  --framework <node|react|next|vue|svelte|nuxt>
68
55
  --features <lint,format,typescript,test,husky>
69
- --no-format
70
- --no-test-runner
56
+ --dir <path>
71
57
  --formatter <prettier|oxfmt>
72
58
  --test-runner <jest|vitest>
73
- --pm <pnpm|npm|yarn|auto>
74
- --dir <path>
75
59
  --force
76
60
  --dry-run
77
61
  --skip-husky-install
78
- --skills
79
- --no-skills
80
62
  --help
81
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
@@ -3,7 +3,6 @@
3
3
  [English README](./README.md)
4
4
 
5
5
  `treg` 是一個用於既有專案的 CLI,可快速套用一致的工具鏈規範。
6
-
7
6
  它只處理基礎設施設定:
8
7
 
9
8
  - lint
@@ -13,78 +12,93 @@
13
12
  - husky
14
13
  - AI skill 指引
15
14
 
16
- ## 為什麼用 treg
17
-
18
- `treg` 可以在既有 repo 中快速建立一致的開發基線,避免每次手動重接工具。
19
-
20
- 適合用在:
21
-
22
- - 快速補齊專案工具鏈
23
- - 只套用部分 feature
24
- - 需要可重跑且不破壞設定(`idempotent`)
25
- - 先看完整計畫再寫檔(`--dry-run`)
26
-
27
15
  ## 快速開始
28
16
 
29
17
  ```bash
30
18
  npx @tyyyho/treg init
31
19
  ```
32
20
 
33
- ```bash
34
- pnpm dlx @tyyyho/treg init
35
- ```
36
-
37
21
  ## 指令總覽
38
22
 
39
- - `init`:依賴自動偵測 framework 並套用基礎規範。
40
- - `add`:只套用指定 features。
41
- - `list`:列出支援的 frameworkfeatureformattertest runner。
23
+ - `init`:初始化功能。
24
+ - `add`:只為既有專案加入指定功能。
25
+ - `list`:列出支援的 framework/feature/formatter/test runner。
26
+
27
+ ## Init 互動流程
28
+
29
+ 執行 `init` 後,`treg` 會依序詢問:
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 時詢問)
36
+
37
+ 預設 `all` 內容:
38
+
39
+ - lint
40
+ - format
41
+ - TypeScript
42
+ - test
43
+ - husky
44
+ - AI skill guidance
42
45
 
43
46
  ## 常見用法
44
47
 
45
- 自動偵測 framework 初始化:
48
+ 初始化:
46
49
 
47
50
  ```bash
48
51
  npx @tyyyho/treg init
49
52
  ```
50
53
 
51
- 手動指定 framework:
54
+ 只預覽 init 計畫:
52
55
 
53
56
  ```bash
54
- npx @tyyyho/treg init --framework react
57
+ npx @tyyyho/treg init --dry-run
55
58
  ```
56
59
 
57
- 只套用 lint + format:
60
+ 只加入 lint + format:
58
61
 
59
62
  ```bash
60
63
  npx @tyyyho/treg add --features lint,format
61
64
  ```
62
65
 
63
- format 改用 `oxfmt`:
66
+ format 使用 `oxfmt`:
64
67
 
65
68
  ```bash
66
69
  npx @tyyyho/treg add --features format --formatter oxfmt
67
70
  ```
68
71
 
69
- 保留既有設定,跳過 format/test:
72
+ test 使用 `vitest`:
70
73
 
71
74
  ```bash
72
- npx @tyyyho/treg add --no-format --no-test-runner
75
+ npx @tyyyho/treg add --features test --test-runner vitest
73
76
  ```
74
77
 
75
- 只預覽計畫不寫檔:
78
+ ## CLI 參數
79
+
80
+ `init` 可用參數:
76
81
 
77
- ```bash
78
- npx @tyyyho/treg init --framework react --dry-run
82
+ ```text
83
+ --dry-run
84
+ --help
79
85
  ```
80
86
 
81
- 指定目標目錄:
87
+ `add` 可用參數:
82
88
 
83
- ```bash
84
- npx @tyyyho/treg init --framework react --dir ./packages/web
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
85
99
  ```
86
100
 
87
- ## 重要預設
101
+ ## 預設行為
88
102
 
89
103
  framework 偵測順序:
90
104
 
@@ -97,38 +111,22 @@ framework 偵測順序:
97
111
 
98
112
  formatter 預設:
99
113
 
100
- - `prettier`(可用 `--formatter oxfmt` 覆寫)
101
-
102
- ## CLI 參數
103
-
104
- ```text
105
- --framework <node|react|next|vue|svelte|nuxt>
106
- --features <lint,format,typescript,test,husky>
107
- --no-format
108
- --no-test-runner
109
- --dir <path>
110
- --formatter <prettier|oxfmt>
111
- --test-runner <jest|vitest>
112
- --pm <pnpm|npm|yarn|auto>
113
- --force
114
- --dry-run
115
- --skip-husky-install
116
- --skills
117
- --no-skills
118
- --help
119
- ```
114
+ - `prettier`
120
115
 
121
116
  ## AI Skills 行為
122
117
 
123
- 啟用 skills 時:
124
-
125
- - repo root 已存在 `CLAUDE.md`、`AGENTS.md`、`GEMINI.md`,會更新對應內容。
126
- - 若檔案不存在,不會自動建立。
118
+ - 只會更新選擇的 AI 工具對應檔案:
119
+ - `Claude -> CLAUDE.md`
120
+ - `Codex -> AGENTS.md`
121
+ - `Gemini -> GEMINI.md`
122
+ - 僅更新 repo root 已存在的檔案。
123
+ - 不存在的檔案會跳過,不會自動建立。
124
+ - 每個啟用功能的 skill 檔只會建立一次。
127
125
 
128
126
  ## 發布
129
127
 
130
128
  ```bash
131
- pnpm release patch
129
+ npm run release -- patch
132
130
  ```
133
131
 
134
132
  支援目標:
@@ -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.16",
3
+ "version": "0.1.17",
4
4
  "description": "CLI tool for initializing development conventions in existing projects.",
5
5
  "license": "MIT",
6
6
  "repository": {