@tyyyho/treg 0.1.19 → 0.1.20
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.zh-hant.md
CHANGED
|
@@ -29,12 +29,12 @@ npx @tyyyho/treg init
|
|
|
29
29
|
執行 `init` 後,`treg` 會依序詢問:
|
|
30
30
|
|
|
31
31
|
1. 套件管理器(`pnpm|npm|yarn|bun`)
|
|
32
|
-
2.
|
|
32
|
+
2. 要加入的功能(可複選,預設全勾)
|
|
33
33
|
3. 測試工具(僅在選到 `test` 時詢問,支援 `skip`)
|
|
34
34
|
4. Formatter(僅在選到 `format` 時詢問)
|
|
35
35
|
5. AI 工具(`Claude|Codex|Gemini` 可複選,僅在選到 AI skill guidance 時詢問)
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
預設勾選功能:
|
|
38
38
|
|
|
39
39
|
- lint
|
|
40
40
|
- format
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { stdin as input, stdout as output } from "node:process";
|
|
2
|
-
import {
|
|
2
|
+
import { clearScreenDown, cursorTo, emitKeypressEvents, moveCursor as moveTerminalCursor, } from "node:readline";
|
|
3
3
|
const DEFAULT_AI_TOOLS = ["claude", "codex", "gemini"];
|
|
4
4
|
const PACKAGE_MANAGER_CHOICES = [
|
|
5
5
|
{ value: "pnpm", label: "pnpm" },
|
|
@@ -22,10 +22,6 @@ const AI_TOOL_CHOICES = [
|
|
|
22
22
|
{ value: "gemini", label: "Gemini" },
|
|
23
23
|
];
|
|
24
24
|
const FEATURE_CHOICES = [
|
|
25
|
-
{
|
|
26
|
-
value: "all",
|
|
27
|
-
label: "all (lint, format, TypeScript, test, husky, AI skill guidance)",
|
|
28
|
-
},
|
|
29
25
|
{ value: "lint", label: "lint" },
|
|
30
26
|
{ value: "format", label: "format" },
|
|
31
27
|
{ value: "typescript", label: "TypeScript" },
|
|
@@ -33,85 +29,30 @@ const FEATURE_CHOICES = [
|
|
|
33
29
|
{ value: "husky", label: "husky" },
|
|
34
30
|
{ value: "skills", label: "AI skill guidance" },
|
|
35
31
|
];
|
|
36
|
-
function
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
}
|
|
32
|
+
function moveCursorIndex(currentIndex, delta, size) {
|
|
33
|
+
if (size <= 0) {
|
|
34
|
+
return 0;
|
|
52
35
|
}
|
|
53
|
-
|
|
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
|
-
};
|
|
36
|
+
return (currentIndex + delta + size) % size;
|
|
61
37
|
}
|
|
62
|
-
function
|
|
63
|
-
const
|
|
64
|
-
if (
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (normalized === "skip" || normalized === "none") {
|
|
68
|
-
return { ok: true, value: [] };
|
|
38
|
+
function toggleSelectedValue(selectedValues, value) {
|
|
39
|
+
const next = new Set(selectedValues);
|
|
40
|
+
if (next.has(value)) {
|
|
41
|
+
next.delete(value);
|
|
42
|
+
return next;
|
|
69
43
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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] };
|
|
44
|
+
next.add(value);
|
|
45
|
+
return next;
|
|
46
|
+
}
|
|
47
|
+
function selectAllValues(choices) {
|
|
48
|
+
return new Set(choices.map(choice => choice.value));
|
|
49
|
+
}
|
|
50
|
+
function getSelectedValuesInOrder(choices, selectedValues) {
|
|
51
|
+
return choices
|
|
52
|
+
.filter(choice => selectedValues.has(choice.value))
|
|
53
|
+
.map(choice => choice.value);
|
|
101
54
|
}
|
|
102
55
|
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
56
|
return {
|
|
116
57
|
enabledFeatures: {
|
|
117
58
|
lint: selected.includes("lint"),
|
|
@@ -123,14 +64,170 @@ function toFeatureSelection(selected) {
|
|
|
123
64
|
skills: selected.includes("skills"),
|
|
124
65
|
};
|
|
125
66
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
67
|
+
function renderFrame(lines, previousLineCount) {
|
|
68
|
+
if (previousLineCount > 0) {
|
|
69
|
+
moveTerminalCursor(output, 0, -previousLineCount);
|
|
70
|
+
cursorTo(output, 0);
|
|
71
|
+
clearScreenDown(output);
|
|
72
|
+
}
|
|
73
|
+
output.write(lines.join("\n"));
|
|
74
|
+
output.write("\n");
|
|
75
|
+
return lines.length;
|
|
76
|
+
}
|
|
77
|
+
function isConfirmKey(key) {
|
|
78
|
+
return key.name === "return" || key.name === "enter";
|
|
79
|
+
}
|
|
80
|
+
function isSelectAllKey(key) {
|
|
81
|
+
return key.name === "a" && !key.ctrl && !key.meta;
|
|
82
|
+
}
|
|
83
|
+
function ensureRawMode() {
|
|
84
|
+
emitKeypressEvents(input);
|
|
85
|
+
input.setRawMode(true);
|
|
86
|
+
input.resume();
|
|
87
|
+
output.write("\x1b[?25l");
|
|
88
|
+
return () => {
|
|
89
|
+
input.setRawMode(false);
|
|
90
|
+
output.write("\x1b[?25h");
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function buildSingleSelectLines(title, choices, highlightedIndex) {
|
|
94
|
+
return [
|
|
95
|
+
title,
|
|
96
|
+
...choices.map((choice, index) => {
|
|
97
|
+
const cursor = index === highlightedIndex ? ">" : " ";
|
|
98
|
+
return `${cursor} ${choice.label}`;
|
|
99
|
+
}),
|
|
100
|
+
"Use up/down arrows to move, Enter to confirm.",
|
|
101
|
+
];
|
|
102
|
+
}
|
|
103
|
+
function buildMultiSelectLines(title, choices, selectedValues, highlightedIndex, allowSelectAll) {
|
|
104
|
+
const controls = allowSelectAll
|
|
105
|
+
? "Use up/down arrows to move, Space to toggle, A to select all, Enter to confirm."
|
|
106
|
+
: "Use up/down arrows to move, Space to toggle, Enter to confirm.";
|
|
107
|
+
return [
|
|
108
|
+
title,
|
|
109
|
+
...choices.map((choice, index) => {
|
|
110
|
+
const cursor = index === highlightedIndex ? ">" : " ";
|
|
111
|
+
const mark = selectedValues.has(choice.value) ? "x" : " ";
|
|
112
|
+
return `${cursor} [${mark}] ${choice.label}`;
|
|
113
|
+
}),
|
|
114
|
+
controls,
|
|
115
|
+
];
|
|
116
|
+
}
|
|
117
|
+
async function promptSingleChoice(title, choices, defaultValue) {
|
|
118
|
+
const defaultIndex = choices.findIndex(choice => choice.value === defaultValue);
|
|
119
|
+
let highlightedIndex = defaultIndex >= 0 ? defaultIndex : 0;
|
|
120
|
+
let renderedLineCount = 0;
|
|
121
|
+
const restore = ensureRawMode();
|
|
122
|
+
try {
|
|
123
|
+
renderedLineCount = renderFrame(buildSingleSelectLines(title, choices, highlightedIndex), renderedLineCount);
|
|
124
|
+
return await new Promise((resolve, reject) => {
|
|
125
|
+
let settled = false;
|
|
126
|
+
const onKeypress = (_, key) => {
|
|
127
|
+
if (key.ctrl && key.name === "c") {
|
|
128
|
+
if (settled) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
settled = true;
|
|
132
|
+
cleanup();
|
|
133
|
+
reject(new Error("Prompt cancelled by user"));
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (key.name === "up") {
|
|
137
|
+
highlightedIndex = moveCursorIndex(highlightedIndex, -1, choices.length);
|
|
138
|
+
renderedLineCount = renderFrame(buildSingleSelectLines(title, choices, highlightedIndex), renderedLineCount);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (key.name === "down") {
|
|
142
|
+
highlightedIndex = moveCursorIndex(highlightedIndex, 1, choices.length);
|
|
143
|
+
renderedLineCount = renderFrame(buildSingleSelectLines(title, choices, highlightedIndex), renderedLineCount);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (isConfirmKey(key)) {
|
|
147
|
+
const selected = choices[highlightedIndex];
|
|
148
|
+
if (selected) {
|
|
149
|
+
if (settled) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
settled = true;
|
|
153
|
+
cleanup();
|
|
154
|
+
resolve(selected.value);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
const cleanup = () => {
|
|
159
|
+
input.off("keypress", onKeypress);
|
|
160
|
+
};
|
|
161
|
+
input.on("keypress", onKeypress);
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
finally {
|
|
165
|
+
restore();
|
|
166
|
+
output.write("\n");
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
async function promptMultiChoice(title, choices, options) {
|
|
170
|
+
let highlightedIndex = 0;
|
|
171
|
+
let selectedValues = new Set(options.defaultValues);
|
|
172
|
+
let renderedLineCount = 0;
|
|
173
|
+
const allowSelectAll = options.allowSelectAll ?? false;
|
|
174
|
+
const restore = ensureRawMode();
|
|
175
|
+
try {
|
|
176
|
+
renderedLineCount = renderFrame(buildMultiSelectLines(title, choices, selectedValues, highlightedIndex, allowSelectAll), renderedLineCount);
|
|
177
|
+
return await new Promise((resolve, reject) => {
|
|
178
|
+
let settled = false;
|
|
179
|
+
const onKeypress = (_, key) => {
|
|
180
|
+
if (key.ctrl && key.name === "c") {
|
|
181
|
+
if (settled) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
settled = true;
|
|
185
|
+
cleanup();
|
|
186
|
+
reject(new Error("Prompt cancelled by user"));
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (key.name === "up") {
|
|
190
|
+
highlightedIndex = moveCursorIndex(highlightedIndex, -1, choices.length);
|
|
191
|
+
renderedLineCount = renderFrame(buildMultiSelectLines(title, choices, selectedValues, highlightedIndex, allowSelectAll), renderedLineCount);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (key.name === "down") {
|
|
195
|
+
highlightedIndex = moveCursorIndex(highlightedIndex, 1, choices.length);
|
|
196
|
+
renderedLineCount = renderFrame(buildMultiSelectLines(title, choices, selectedValues, highlightedIndex, allowSelectAll), renderedLineCount);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
if (key.name === "space") {
|
|
200
|
+
const highlighted = choices[highlightedIndex];
|
|
201
|
+
if (!highlighted) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
selectedValues = toggleSelectedValue(selectedValues, highlighted.value);
|
|
205
|
+
renderedLineCount = renderFrame(buildMultiSelectLines(title, choices, selectedValues, highlightedIndex, allowSelectAll), renderedLineCount);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
if (allowSelectAll && isSelectAllKey(key)) {
|
|
209
|
+
selectedValues = selectAllValues(choices);
|
|
210
|
+
renderedLineCount = renderFrame(buildMultiSelectLines(title, choices, selectedValues, highlightedIndex, allowSelectAll), renderedLineCount);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
if (isConfirmKey(key)) {
|
|
214
|
+
if (settled) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
settled = true;
|
|
218
|
+
cleanup();
|
|
219
|
+
resolve(getSelectedValuesInOrder(choices, selectedValues));
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
const cleanup = () => {
|
|
223
|
+
input.off("keypress", onKeypress);
|
|
224
|
+
};
|
|
225
|
+
input.on("keypress", onKeypress);
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
finally {
|
|
229
|
+
restore();
|
|
230
|
+
output.write("\n");
|
|
134
231
|
}
|
|
135
232
|
}
|
|
136
233
|
export async function collectInitPrompts(defaults) {
|
|
@@ -151,71 +248,67 @@ export async function collectInitPrompts(defaults) {
|
|
|
151
248
|
aiTools: [...DEFAULT_AI_TOOLS],
|
|
152
249
|
};
|
|
153
250
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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));
|
|
251
|
+
console.log("\nInit setup");
|
|
252
|
+
console.log("\n1) Package manager");
|
|
253
|
+
const pm = await promptSingleChoice("Select package manager:", PACKAGE_MANAGER_CHOICES, defaults.pm);
|
|
254
|
+
console.log("\n2) Features");
|
|
255
|
+
const featureAnswers = await promptMultiChoice("Select features:", FEATURE_CHOICES, {
|
|
256
|
+
defaultValues: FEATURE_CHOICES.map(choice => choice.value),
|
|
257
|
+
allowSelectAll: true,
|
|
258
|
+
});
|
|
259
|
+
const featureSelection = toFeatureSelection(featureAnswers);
|
|
260
|
+
let testRunner = defaults.testRunner;
|
|
261
|
+
const enabledFeatures = { ...featureSelection.enabledFeatures };
|
|
262
|
+
if (featureSelection.enabledFeatures.test) {
|
|
263
|
+
console.log("\n3) Test runner");
|
|
264
|
+
const selectedTestRunner = await promptSingleChoice("Select test runner:", TEST_RUNNER_CHOICES, defaults.testRunner);
|
|
265
|
+
if (selectedTestRunner === "skip") {
|
|
266
|
+
enabledFeatures.test = false;
|
|
267
|
+
console.log("Test feature disabled by selection: skip");
|
|
186
268
|
}
|
|
187
269
|
else {
|
|
188
|
-
|
|
270
|
+
testRunner = selectedTestRunner;
|
|
189
271
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
console.log("\n3) Test runner skipped (test feature not selected)");
|
|
275
|
+
}
|
|
276
|
+
let formatter = defaults.formatter;
|
|
277
|
+
if (featureSelection.enabledFeatures.format) {
|
|
278
|
+
console.log("\n4) Formatter");
|
|
279
|
+
formatter = await promptSingleChoice("Select formatter:", FORMATTER_CHOICES, defaults.formatter);
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
console.log("\n4) Formatter skipped (format feature not selected)");
|
|
283
|
+
}
|
|
284
|
+
let aiTools = [];
|
|
285
|
+
let skills = featureSelection.skills;
|
|
286
|
+
if (skills) {
|
|
287
|
+
console.log("\n5) AI tools");
|
|
288
|
+
aiTools = await promptMultiChoice("Select AI tools:", AI_TOOL_CHOICES, {
|
|
289
|
+
defaultValues: DEFAULT_AI_TOOLS,
|
|
290
|
+
allowSelectAll: true,
|
|
291
|
+
});
|
|
292
|
+
if (aiTools.length === 0) {
|
|
293
|
+
skills = false;
|
|
203
294
|
}
|
|
204
|
-
return {
|
|
205
|
-
pm,
|
|
206
|
-
formatter,
|
|
207
|
-
testRunner,
|
|
208
|
-
enabledFeatures,
|
|
209
|
-
skills,
|
|
210
|
-
aiTools,
|
|
211
|
-
};
|
|
212
295
|
}
|
|
213
|
-
|
|
214
|
-
|
|
296
|
+
else {
|
|
297
|
+
console.log("\n5) AI tools skipped (AI skill guidance not selected)");
|
|
215
298
|
}
|
|
299
|
+
return {
|
|
300
|
+
pm,
|
|
301
|
+
formatter,
|
|
302
|
+
testRunner,
|
|
303
|
+
enabledFeatures,
|
|
304
|
+
skills,
|
|
305
|
+
aiTools,
|
|
306
|
+
};
|
|
216
307
|
}
|
|
217
308
|
export const __testables__ = {
|
|
218
|
-
|
|
219
|
-
|
|
309
|
+
moveCursorIndex,
|
|
310
|
+
toggleSelectedValue,
|
|
311
|
+
selectAllValues,
|
|
312
|
+
getSelectedValuesInOrder,
|
|
220
313
|
toFeatureSelection,
|
|
221
314
|
};
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { promises as fs } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
const START_MARKER = "<!-- treg:skills:start -->";
|
|
5
|
-
const END_MARKER = "<!-- treg:skills:end -->";
|
|
6
4
|
const SKILL_SECTION_HEADING = "## treg AI Skills";
|
|
7
5
|
const SKILLS_BASE_DIR = "skills";
|
|
8
6
|
const AI_TOOL_DOCS = {
|
|
@@ -155,12 +153,6 @@ function upsertSkillSection(content, nextSection) {
|
|
|
155
153
|
const rebuilt = `${before}\n\n${nextSection.trim()}\n`;
|
|
156
154
|
return after ? `${rebuilt}\n${after}\n` : `${rebuilt}`;
|
|
157
155
|
};
|
|
158
|
-
const start = content.indexOf(START_MARKER);
|
|
159
|
-
const end = content.indexOf(END_MARKER);
|
|
160
|
-
if (start !== -1 && end !== -1 && end > start) {
|
|
161
|
-
const suffixStart = end + END_MARKER.length;
|
|
162
|
-
return replaceSection(start, suffixStart);
|
|
163
|
-
}
|
|
164
156
|
const headingStart = content.indexOf(SKILL_SECTION_HEADING);
|
|
165
157
|
if (headingStart !== -1) {
|
|
166
158
|
const nextHeading = content.indexOf("\n## ", headingStart + 1);
|
|
@@ -172,10 +164,6 @@ function upsertSkillSection(content, nextSection) {
|
|
|
172
164
|
}
|
|
173
165
|
return `${content.trimEnd()}\n\n${nextSection.trim()}\n`;
|
|
174
166
|
}
|
|
175
|
-
function buildInitialAiDocContent(fileName) {
|
|
176
|
-
const baseName = path.basename(fileName, path.extname(fileName));
|
|
177
|
-
return `# ${baseName}\n`;
|
|
178
|
-
}
|
|
179
167
|
export async function runAiSkillsRule(context) {
|
|
180
168
|
const { projectDir, dryRun, aiTools } = context;
|
|
181
169
|
const targetFiles = resolveSkillsDocs(projectDir, aiTools);
|
|
@@ -189,9 +177,7 @@ export async function runAiSkillsRule(context) {
|
|
|
189
177
|
continue;
|
|
190
178
|
}
|
|
191
179
|
const exists = existsSync(targetFile);
|
|
192
|
-
const current = exists
|
|
193
|
-
? await fs.readFile(targetFile, "utf8")
|
|
194
|
-
: buildInitialAiDocContent(targetFile);
|
|
180
|
+
const current = exists ? await fs.readFile(targetFile, "utf8") : "";
|
|
195
181
|
const updated = upsertSkillSection(current, section);
|
|
196
182
|
if (updated !== current) {
|
|
197
183
|
await fs.mkdir(path.dirname(targetFile), { recursive: true });
|