@produck/agent-toolkit 0.2.0 → 0.3.3
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 +53 -9
- package/bin/agent-toolkit.mjs +76 -654
- package/bin/build-publish-assets.mjs +49 -0
- package/bin/command/enforce-node-baseline/help.txt +19 -0
- package/bin/command/enforce-node-baseline/index.mjs +155 -0
- package/{templates/help/main.txt → bin/command/main/help.txt} +7 -0
- package/bin/command/main/index.mjs +11 -0
- package/bin/command/preflight/help.txt +9 -0
- package/bin/command/preflight/index.mjs +147 -0
- package/bin/command/run-capture/index.mjs +100 -0
- package/bin/command/shared/args.mjs +45 -0
- package/bin/command/shared/text-resource.mjs +19 -0
- package/bin/command/summarize-log/index.mjs +64 -0
- package/bin/command/sync-coverage-script/help.txt +22 -0
- package/bin/command/sync-coverage-script/index.mjs +275 -0
- package/bin/command/sync-husky-hooks/help.txt +22 -0
- package/bin/command/sync-husky-hooks/index.mjs +267 -0
- package/bin/command/sync-instructions/index.mjs +272 -0
- package/bin/command/validate-commit-msg/help.txt +9 -0
- package/bin/command/validate-commit-msg/index.mjs +183 -0
- package/package.json +5 -3
- package/publish-assets/instructions/produck/00-produck-base.instructions.md +37 -39
- package/publish-assets/instructions/produck/10-produck-node.instructions.md +130 -26
- package/publish-assets/instructions/produck/15-produck-workspace.instructions.md +59 -27
- package/publish-assets/instructions/produck/20-produck-commit.instructions.md +24 -8
- package/publish-assets/instructions/produck/tooling-version-baseline.json +32 -0
- package/templates/help/preflight.txt +0 -3
- package/templates/help/validate-commit-msg.txt +0 -7
- /package/{templates/help/run-capture.txt → bin/command/run-capture/help.txt} +0 -0
- /package/{templates/help/summarize-log.txt → bin/command/summarize-log/help.txt} +0 -0
- /package/{templates/help/sync-instructions.txt → bin/command/sync-instructions/help.txt} +0 -0
- /package/{templates → bin/command/sync-instructions}/user-space-bootstrap.md +0 -0
package/bin/agent-toolkit.mjs
CHANGED
|
@@ -1,613 +1,71 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
return fallback;
|
|
67
|
-
}
|
|
68
|
-
return String(options[key][options[key].length - 1]);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function getMulti(options, key) {
|
|
72
|
-
if (!options[key]) {
|
|
73
|
-
return [];
|
|
74
|
-
}
|
|
75
|
-
return options[key].map((v) => String(v));
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function hasFlag(options, key) {
|
|
79
|
-
return Boolean(options[key]);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function printMainHelp() {
|
|
83
|
-
printTemplate('help/main.txt');
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function printPreflightHelp() {
|
|
87
|
-
printTemplate('help/preflight.txt');
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function printRunCaptureHelp() {
|
|
91
|
-
printTemplate('help/run-capture.txt');
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function printSummarizeHelp() {
|
|
95
|
-
printTemplate('help/summarize-log.txt');
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function printValidateHelp() {
|
|
99
|
-
printTemplate('help/validate-commit-msg.txt');
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function printSyncInstructionsHelp() {
|
|
103
|
-
printTemplate('help/sync-instructions.txt');
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function loadDefaultInstructionsTemplate() {
|
|
107
|
-
if (fs.existsSync(PUBLISH_NAMESPACE_ROOT)) {
|
|
108
|
-
const names = fs
|
|
109
|
-
.readdirSync(PUBLISH_NAMESPACE_ROOT)
|
|
110
|
-
.filter((name) => name.endsWith('.instructions.md'))
|
|
111
|
-
.sort((a, b) => a.localeCompare(b));
|
|
112
|
-
const entries = names.map((name) => {
|
|
113
|
-
const abs = path.resolve(PUBLISH_NAMESPACE_ROOT, name);
|
|
114
|
-
let text = fs.readFileSync(abs, 'utf8');
|
|
115
|
-
if (!text.endsWith('\n')) {
|
|
116
|
-
text = `${text}\n`;
|
|
117
|
-
}
|
|
118
|
-
return {
|
|
119
|
-
fileName: name,
|
|
120
|
-
content: text,
|
|
121
|
-
sourcePath: abs,
|
|
122
|
-
};
|
|
123
|
-
});
|
|
124
|
-
return {
|
|
125
|
-
type: 'dir',
|
|
126
|
-
sourcePath: PUBLISH_NAMESPACE_ROOT,
|
|
127
|
-
entries,
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
console.error('No built-in instruction assets found.');
|
|
132
|
-
console.error('Run prepack/publish to generate publish-assets, or pass --source explicitly.');
|
|
133
|
-
process.exit(2);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function readInstructionEntriesFromDirectory(sourceDir) {
|
|
137
|
-
const names = fs
|
|
138
|
-
.readdirSync(sourceDir)
|
|
139
|
-
.filter((name) => name.endsWith('.instructions.md'))
|
|
140
|
-
.sort((a, b) => a.localeCompare(b));
|
|
141
|
-
return names.map((name) => {
|
|
142
|
-
const sourcePath = path.resolve(sourceDir, name);
|
|
143
|
-
let content = fs.readFileSync(sourcePath, 'utf8');
|
|
144
|
-
if (!content.endsWith('\n')) {
|
|
145
|
-
content = `${content}\n`;
|
|
146
|
-
}
|
|
147
|
-
return {
|
|
148
|
-
fileName: name,
|
|
149
|
-
content,
|
|
150
|
-
sourcePath,
|
|
151
|
-
};
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function isManagedFile(filePath) {
|
|
156
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
157
|
-
return content.includes(MANAGED_MARKER);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function buildUserSpaceBootstrapContent(namespaceDirPath, cwd) {
|
|
161
|
-
const namespaceDisplayPath = path.relative(cwd, namespaceDirPath).replace(/\\/g, '/');
|
|
162
|
-
let content = loadTemplateFile('user-space-bootstrap.md');
|
|
163
|
-
content = content.replace(/\{\{NAMESPACE_GLOB\}\}/g, `${namespaceDisplayPath}/*.instructions.md`);
|
|
164
|
-
if (!content.endsWith('\n')) {
|
|
165
|
-
content = `${content}\n`;
|
|
166
|
-
}
|
|
167
|
-
return content;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function runPreflight(options) {
|
|
171
|
-
const cwd = path.resolve(getSingle(options, '--cwd', process.cwd()));
|
|
172
|
-
const required = getMulti(options, '--require');
|
|
173
|
-
const ensureDir = getMulti(options, '--ensure-dir');
|
|
174
|
-
const jsonFile = getSingle(options, '--json', '');
|
|
175
|
-
|
|
176
|
-
if (!fs.existsSync(cwd)) {
|
|
177
|
-
console.error(`CWD does not exist: ${cwd}`);
|
|
178
|
-
process.exit(2);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const report = {
|
|
182
|
-
cwd,
|
|
183
|
-
required: [],
|
|
184
|
-
ensuredDirectories: [],
|
|
185
|
-
ok: true,
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
for (const rel of required) {
|
|
189
|
-
const resolved = path.resolve(cwd, rel);
|
|
190
|
-
const exists = fs.existsSync(resolved);
|
|
191
|
-
report.required.push({ path: rel, resolved, exists });
|
|
192
|
-
if (!exists) {
|
|
193
|
-
report.ok = false;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
for (const rel of ensureDir) {
|
|
198
|
-
const resolved = path.resolve(cwd, rel);
|
|
199
|
-
fs.mkdirSync(resolved, { recursive: true });
|
|
200
|
-
report.ensuredDirectories.push({ path: rel, resolved, created: true });
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
if (jsonFile) {
|
|
204
|
-
const out = path.resolve(cwd, jsonFile);
|
|
205
|
-
fs.mkdirSync(path.dirname(out), { recursive: true });
|
|
206
|
-
fs.writeFileSync(out, `${JSON.stringify(report, null, 2)}\n`, 'utf8');
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
210
|
-
if (!report.ok) {
|
|
211
|
-
process.exit(2);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
function runCapture(options) {
|
|
216
|
-
const out = getSingle(options, '--out', '');
|
|
217
|
-
const cmd = getSingle(options, '--cmd', '');
|
|
218
|
-
const cwd = path.resolve(getSingle(options, '--cwd', process.cwd()));
|
|
219
|
-
const meta = getSingle(options, '--meta', '');
|
|
220
|
-
const allowPipe = hasFlag(options, '--allow-pipe');
|
|
221
|
-
|
|
222
|
-
if (!out || !cmd) {
|
|
223
|
-
printRunCaptureHelp();
|
|
224
|
-
process.exit(2);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
if (!allowPipe && cmd.includes('|')) {
|
|
228
|
-
console.error('Blocked command containing pipe. Use --allow-pipe if needed.');
|
|
229
|
-
process.exit(2);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
const outPath = path.resolve(out);
|
|
233
|
-
const metaPath = meta ? path.resolve(meta) : `${outPath}.meta.json`;
|
|
234
|
-
|
|
235
|
-
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
236
|
-
fs.mkdirSync(path.dirname(metaPath), { recursive: true });
|
|
237
|
-
|
|
238
|
-
const startAt = Date.now();
|
|
239
|
-
const outStream = fs.createWriteStream(outPath, { encoding: 'utf8' });
|
|
240
|
-
|
|
241
|
-
outStream.write(`# command: ${cmd}\n`);
|
|
242
|
-
outStream.write(`# cwd: ${cwd}\n`);
|
|
243
|
-
outStream.write(`# startedAt: ${new Date(startAt).toISOString()}\n\n`);
|
|
244
|
-
|
|
245
|
-
const child = spawn(cmd, {
|
|
246
|
-
cwd,
|
|
247
|
-
shell: true,
|
|
248
|
-
env: process.env,
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
let stdoutBytes = 0;
|
|
252
|
-
let stderrBytes = 0;
|
|
253
|
-
|
|
254
|
-
child.stdout.on('data', (chunk) => {
|
|
255
|
-
stdoutBytes += chunk.length;
|
|
256
|
-
outStream.write(chunk);
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
child.stderr.on('data', (chunk) => {
|
|
260
|
-
stderrBytes += chunk.length;
|
|
261
|
-
outStream.write(chunk);
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
child.on('error', (error) => {
|
|
265
|
-
outStream.write(`\n[agent-toolkit] spawn error: ${error.message}\n`);
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
child.on('close', (code, signal) => {
|
|
269
|
-
const endAt = Date.now();
|
|
270
|
-
const durationMs = endAt - startAt;
|
|
271
|
-
|
|
272
|
-
outStream.write('\n');
|
|
273
|
-
outStream.write(`# finishedAt: ${new Date(endAt).toISOString()}\n`);
|
|
274
|
-
outStream.write(`# exitCode: ${String(code)}\n`);
|
|
275
|
-
outStream.write(`# signal: ${signal ? String(signal) : 'none'}\n`);
|
|
276
|
-
outStream.end();
|
|
277
|
-
|
|
278
|
-
const report = {
|
|
279
|
-
command: cmd,
|
|
280
|
-
cwd,
|
|
281
|
-
startedAt: new Date(startAt).toISOString(),
|
|
282
|
-
finishedAt: new Date(endAt).toISOString(),
|
|
283
|
-
durationMs,
|
|
284
|
-
exitCode: code,
|
|
285
|
-
signal,
|
|
286
|
-
stdoutBytes,
|
|
287
|
-
stderrBytes,
|
|
288
|
-
outputFile: outPath,
|
|
289
|
-
};
|
|
290
|
-
|
|
291
|
-
fs.writeFileSync(metaPath, `${JSON.stringify(report, null, 2)}\n`, 'utf8');
|
|
292
|
-
|
|
293
|
-
if (typeof code === 'number') {
|
|
294
|
-
process.exit(code);
|
|
295
|
-
}
|
|
296
|
-
process.exit(1);
|
|
297
|
-
});
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
function runSummarize(options) {
|
|
301
|
-
const file = getSingle(options, '--file', '');
|
|
302
|
-
const last = Number(getSingle(options, '--last', '0')) || 0;
|
|
303
|
-
const match = getSingle(options, '--match', '');
|
|
304
|
-
const max = Number(getSingle(options, '--max', '200')) || 200;
|
|
305
|
-
|
|
306
|
-
if (!file) {
|
|
307
|
-
printSummarizeHelp();
|
|
308
|
-
process.exit(2);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
const filePath = path.resolve(file);
|
|
312
|
-
if (!fs.existsSync(filePath)) {
|
|
313
|
-
console.error(`Log file does not exist: ${filePath}`);
|
|
314
|
-
process.exit(2);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
const raw = fs.readFileSync(filePath, 'utf8');
|
|
318
|
-
const allLines = raw.split(/\r?\n/);
|
|
319
|
-
|
|
320
|
-
let lines = allLines;
|
|
321
|
-
let mode = 'all';
|
|
322
|
-
|
|
323
|
-
if (match) {
|
|
324
|
-
const pattern = new RegExp(match, 'i');
|
|
325
|
-
lines = allLines.filter((line) => pattern.test(line));
|
|
326
|
-
mode = 'match';
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
if (last > 0) {
|
|
330
|
-
lines = lines.slice(-last);
|
|
331
|
-
mode = mode === 'match' ? 'match+last' : 'last';
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
if (max > 0 && lines.length > max) {
|
|
335
|
-
lines = lines.slice(0, max);
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
const header = [
|
|
339
|
-
`# file: ${filePath}`,
|
|
340
|
-
`# totalLines: ${allLines.length}`,
|
|
341
|
-
`# selectedLines: ${lines.length}`,
|
|
342
|
-
`# mode: ${mode}`,
|
|
343
|
-
'',
|
|
344
|
-
].join('\n');
|
|
345
|
-
|
|
346
|
-
process.stdout.write(header);
|
|
347
|
-
process.stdout.write(lines.join('\n'));
|
|
348
|
-
process.stdout.write('\n');
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
function validateCommitLine(line, lineNo) {
|
|
352
|
-
if (line.trim() === '') {
|
|
353
|
-
return `Line ${lineNo}: empty line is not allowed`;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
const head = line.match(/^\[([A-Z]+)\]\s+/);
|
|
357
|
-
if (!head) {
|
|
358
|
-
return `Line ${lineNo}: must start with [TAG] followed by a space`;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
const tag = head[1];
|
|
362
|
-
if (!ALLOWED_TAGS.includes(tag)) {
|
|
363
|
-
return `Line ${lineNo}: tag [${tag}] is not allowed`;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
const rest = line.slice(head[0].length);
|
|
367
|
-
if (rest.trim() === '') {
|
|
368
|
-
return `Line ${lineNo}: summary is required after tag`;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
const targetMatch = rest.match(/^<([^>]+)>:\s+(.+)$/);
|
|
372
|
-
if (targetMatch) {
|
|
373
|
-
const target = targetMatch[1];
|
|
374
|
-
const summary = targetMatch[2];
|
|
375
|
-
if (!ALLOWED_TARGETS.includes(target)) {
|
|
376
|
-
return `Line ${lineNo}: target <${target}> is not allowed`;
|
|
377
|
-
}
|
|
378
|
-
if (summary.trim() === '') {
|
|
379
|
-
return `Line ${lineNo}: summary is required after target`;
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
return null;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
function runValidateCommitMsg(options) {
|
|
387
|
-
const file = getSingle(options, '--file', '');
|
|
388
|
-
if (!file) {
|
|
389
|
-
printValidateHelp();
|
|
390
|
-
process.exit(2);
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
const filePath = path.resolve(file);
|
|
394
|
-
if (!fs.existsSync(filePath)) {
|
|
395
|
-
console.error(`Message file not found: ${filePath}`);
|
|
396
|
-
process.exit(2);
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
const raw = fs.readFileSync(filePath, 'utf8').replace(/\r\n/g, '\n');
|
|
400
|
-
const lines = raw.endsWith('\n') ? raw.slice(0, -1).split('\n') : raw.split('\n');
|
|
401
|
-
|
|
402
|
-
if (lines.length === 0 || (lines.length === 1 && lines[0].trim() === '')) {
|
|
403
|
-
console.error('Commit message is empty');
|
|
404
|
-
process.exit(2);
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
const errors = [];
|
|
408
|
-
for (let i = 0; i < lines.length; i += 1) {
|
|
409
|
-
const err = validateCommitLine(lines[i], i + 1);
|
|
410
|
-
if (err) {
|
|
411
|
-
errors.push(err);
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
if (errors.length > 0) {
|
|
416
|
-
console.error('Commit message validation failed:');
|
|
417
|
-
for (const err of errors) {
|
|
418
|
-
console.error(`- ${err}`);
|
|
419
|
-
}
|
|
420
|
-
process.exit(1);
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
console.log('Commit message validation passed');
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
function runSyncInstructions(options) {
|
|
427
|
-
const cwd = path.resolve(getSingle(options, '--cwd', process.cwd()));
|
|
428
|
-
const outArg = getSingle(options, '--out', DEFAULT_NAMESPACE_OUT_DIR);
|
|
429
|
-
const sourceArg = getSingle(options, '--source', '');
|
|
430
|
-
const force = hasFlag(options, '--force');
|
|
431
|
-
const dryRun = hasFlag(options, '--dry-run');
|
|
432
|
-
const prune = hasFlag(options, '--prune');
|
|
433
|
-
|
|
434
|
-
if (!fs.existsSync(cwd)) {
|
|
435
|
-
console.error(`CWD does not exist: ${cwd}`);
|
|
436
|
-
process.exit(2);
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
const defaults = loadDefaultInstructionsTemplate();
|
|
440
|
-
let sourceType = defaults.type;
|
|
441
|
-
let sourceResolved = defaults.sourcePath;
|
|
442
|
-
let entries = defaults.entries;
|
|
443
|
-
|
|
444
|
-
if (sourceArg) {
|
|
445
|
-
const sourcePath = path.resolve(cwd, sourceArg);
|
|
446
|
-
if (!fs.existsSync(sourcePath)) {
|
|
447
|
-
console.error(`Source path does not exist: ${sourcePath}`);
|
|
448
|
-
process.exit(2);
|
|
449
|
-
}
|
|
450
|
-
const stat = fs.statSync(sourcePath);
|
|
451
|
-
if (stat.isDirectory()) {
|
|
452
|
-
sourceType = 'dir';
|
|
453
|
-
sourceResolved = sourcePath;
|
|
454
|
-
entries = readInstructionEntriesFromDirectory(sourcePath);
|
|
455
|
-
if (entries.length === 0) {
|
|
456
|
-
console.error(`No .instructions.md files in source directory: ${sourcePath}`);
|
|
457
|
-
process.exit(2);
|
|
458
|
-
}
|
|
459
|
-
} else {
|
|
460
|
-
sourceType = 'file';
|
|
461
|
-
sourceResolved = sourcePath;
|
|
462
|
-
let content = fs.readFileSync(sourcePath, 'utf8');
|
|
463
|
-
if (!content.endsWith('\n')) {
|
|
464
|
-
content = `${content}\n`;
|
|
465
|
-
}
|
|
466
|
-
entries = [
|
|
467
|
-
{
|
|
468
|
-
fileName: path.basename(sourcePath),
|
|
469
|
-
content,
|
|
470
|
-
sourcePath,
|
|
471
|
-
},
|
|
472
|
-
];
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
const outPath = path.resolve(cwd, outArg);
|
|
477
|
-
const outLooksLikeFile = outArg.endsWith('.md');
|
|
478
|
-
|
|
479
|
-
if (outLooksLikeFile && entries.length > 1) {
|
|
480
|
-
console.error('Target --out is a file path but source has multiple instruction files.');
|
|
481
|
-
console.error('Use an output directory for multi-file sync.');
|
|
482
|
-
process.exit(2);
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
if (outLooksLikeFile) {
|
|
486
|
-
const entry = entries[0];
|
|
487
|
-
const exists = fs.existsSync(outPath);
|
|
488
|
-
if (exists && !force) {
|
|
489
|
-
const current = fs.readFileSync(outPath, 'utf8');
|
|
490
|
-
if (current !== entry.content) {
|
|
491
|
-
console.error(`Target already exists: ${outPath}`);
|
|
492
|
-
console.error('Use --force to overwrite.');
|
|
493
|
-
process.exit(2);
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
const report = {
|
|
498
|
-
mode: 'single-file',
|
|
499
|
-
cwd,
|
|
500
|
-
sourceType,
|
|
501
|
-
source: sourceResolved,
|
|
502
|
-
outPath,
|
|
503
|
-
exists,
|
|
504
|
-
overwritten: exists && force,
|
|
505
|
-
dryRun,
|
|
506
|
-
prune: false,
|
|
507
|
-
initializedUserSpaceEntry: false,
|
|
508
|
-
userSpaceEntryPath: null,
|
|
509
|
-
};
|
|
510
|
-
|
|
511
|
-
if (dryRun) {
|
|
512
|
-
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
513
|
-
process.exit(0);
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
517
|
-
fs.writeFileSync(outPath, entry.content, 'utf8');
|
|
518
|
-
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
2
|
+
import { printMainHelp } from './command/main/index.mjs';
|
|
3
|
+
import {
|
|
4
|
+
printEnforceNodeBaselineHelp,
|
|
5
|
+
runEnforceNodeBaseline,
|
|
6
|
+
} from './command/enforce-node-baseline/index.mjs';
|
|
7
|
+
import { printPreflightHelp, runPreflight } from './command/preflight/index.mjs';
|
|
8
|
+
import { printRunCaptureHelp, runCapture } from './command/run-capture/index.mjs';
|
|
9
|
+
import { printSummarizeHelp, runSummarize } from './command/summarize-log/index.mjs';
|
|
10
|
+
import {
|
|
11
|
+
printSyncCoverageScriptHelp,
|
|
12
|
+
runSyncCoverageScript,
|
|
13
|
+
} from './command/sync-coverage-script/index.mjs';
|
|
14
|
+
import { printSyncHuskyHooksHelp, runSyncHuskyHooks } from './command/sync-husky-hooks/index.mjs';
|
|
15
|
+
import {
|
|
16
|
+
printSyncInstructionsHelp,
|
|
17
|
+
runSyncInstructions,
|
|
18
|
+
} from './command/sync-instructions/index.mjs';
|
|
19
|
+
import { hasFlag, parseCommonArgs } from './command/shared/args.mjs';
|
|
20
|
+
import {
|
|
21
|
+
printValidateCommitMsgHelp,
|
|
22
|
+
runValidateCommitMsg,
|
|
23
|
+
} from './command/validate-commit-msg/index.mjs';
|
|
24
|
+
|
|
25
|
+
const COMMANDS = {
|
|
26
|
+
'enforce-node-baseline': {
|
|
27
|
+
printHelp: printEnforceNodeBaselineHelp,
|
|
28
|
+
run: runEnforceNodeBaseline,
|
|
29
|
+
},
|
|
30
|
+
preflight: {
|
|
31
|
+
printHelp: printPreflightHelp,
|
|
32
|
+
run: runPreflight,
|
|
33
|
+
},
|
|
34
|
+
'run-capture': {
|
|
35
|
+
printHelp: printRunCaptureHelp,
|
|
36
|
+
run: runCapture,
|
|
37
|
+
},
|
|
38
|
+
'summarize-log': {
|
|
39
|
+
printHelp: printSummarizeHelp,
|
|
40
|
+
run: runSummarize,
|
|
41
|
+
},
|
|
42
|
+
'sync-coverage-script': {
|
|
43
|
+
printHelp: printSyncCoverageScriptHelp,
|
|
44
|
+
run: runSyncCoverageScript,
|
|
45
|
+
},
|
|
46
|
+
'sync-husky-hooks': {
|
|
47
|
+
printHelp: printSyncHuskyHooksHelp,
|
|
48
|
+
run: runSyncHuskyHooks,
|
|
49
|
+
},
|
|
50
|
+
'validate-commit-msg': {
|
|
51
|
+
printHelp: printValidateCommitMsgHelp,
|
|
52
|
+
run: runValidateCommitMsg,
|
|
53
|
+
},
|
|
54
|
+
'sync-instructions': {
|
|
55
|
+
printHelp: printSyncInstructionsHelp,
|
|
56
|
+
run: runSyncInstructions,
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const DEFAULT_COMMAND = 'enforce-node-baseline';
|
|
61
|
+
|
|
62
|
+
function printCommandHelp(command) {
|
|
63
|
+
const entry = COMMANDS[command];
|
|
64
|
+
if (!entry) {
|
|
65
|
+
printMainHelp();
|
|
519
66
|
return;
|
|
520
67
|
}
|
|
521
|
-
|
|
522
|
-
const outDir = outPath;
|
|
523
|
-
const planned = entries.map((entry) => {
|
|
524
|
-
return {
|
|
525
|
-
fileName: entry.fileName,
|
|
526
|
-
sourcePath: entry.sourcePath,
|
|
527
|
-
targetPath: path.resolve(outDir, entry.fileName),
|
|
528
|
-
content: entry.content,
|
|
529
|
-
};
|
|
530
|
-
});
|
|
531
|
-
|
|
532
|
-
const targetSet = new Set(planned.map((item) => item.targetPath));
|
|
533
|
-
const unchanged = [];
|
|
534
|
-
for (const item of planned) {
|
|
535
|
-
if (fs.existsSync(item.targetPath)) {
|
|
536
|
-
const current = fs.readFileSync(item.targetPath, 'utf8');
|
|
537
|
-
if (current === item.content) {
|
|
538
|
-
unchanged.push(item.targetPath);
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
const toWrite = planned.filter((item) => !unchanged.includes(item.targetPath));
|
|
544
|
-
const conflicts = toWrite.filter((item) => fs.existsSync(item.targetPath));
|
|
545
|
-
if (conflicts.length > 0 && !force) {
|
|
546
|
-
console.error('Some target files already exist and would change:');
|
|
547
|
-
for (const item of conflicts) {
|
|
548
|
-
console.error(`- ${item.targetPath}`);
|
|
549
|
-
}
|
|
550
|
-
console.error('Use --force to overwrite.');
|
|
551
|
-
process.exit(2);
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
const pruneDeletes = [];
|
|
555
|
-
if (prune && fs.existsSync(outDir)) {
|
|
556
|
-
const existing = fs
|
|
557
|
-
.readdirSync(outDir)
|
|
558
|
-
.filter((name) => name.endsWith('.instructions.md'))
|
|
559
|
-
.map((name) => path.resolve(outDir, name));
|
|
560
|
-
for (const existingPath of existing) {
|
|
561
|
-
if (!targetSet.has(existingPath) && isManagedFile(existingPath)) {
|
|
562
|
-
pruneDeletes.push(existingPath);
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
const userSpaceEntryPath = path.resolve(cwd, USER_SPACE_ENTRYPOINT);
|
|
568
|
-
const shouldInitUserSpaceEntry = !fs.existsSync(userSpaceEntryPath);
|
|
569
|
-
|
|
570
|
-
const report = {
|
|
571
|
-
mode: 'directory',
|
|
572
|
-
cwd,
|
|
573
|
-
sourceType,
|
|
574
|
-
source: sourceResolved,
|
|
575
|
-
outDir,
|
|
576
|
-
dryRun,
|
|
577
|
-
force,
|
|
578
|
-
prune,
|
|
579
|
-
initializedUserSpaceEntry: shouldInitUserSpaceEntry,
|
|
580
|
-
userSpaceEntryPath,
|
|
581
|
-
files: planned.map((item) => ({
|
|
582
|
-
fileName: item.fileName,
|
|
583
|
-
sourcePath: item.sourcePath,
|
|
584
|
-
targetPath: item.targetPath,
|
|
585
|
-
unchanged: unchanged.includes(item.targetPath),
|
|
586
|
-
})),
|
|
587
|
-
deleteFiles: pruneDeletes,
|
|
588
|
-
};
|
|
589
|
-
|
|
590
|
-
if (dryRun) {
|
|
591
|
-
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
592
|
-
process.exit(0);
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
fs.mkdirSync(outDir, { recursive: true });
|
|
596
|
-
for (const item of toWrite) {
|
|
597
|
-
fs.writeFileSync(item.targetPath, item.content, 'utf8');
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
if (shouldInitUserSpaceEntry) {
|
|
601
|
-
fs.mkdirSync(path.dirname(userSpaceEntryPath), { recursive: true });
|
|
602
|
-
const userSpaceBootstrap = buildUserSpaceBootstrapContent(outDir, cwd);
|
|
603
|
-
fs.writeFileSync(userSpaceEntryPath, userSpaceBootstrap, 'utf8');
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
for (const filePath of pruneDeletes) {
|
|
607
|
-
fs.unlinkSync(filePath);
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
68
|
+
entry.printHelp();
|
|
611
69
|
}
|
|
612
70
|
|
|
613
71
|
function main() {
|
|
@@ -615,62 +73,26 @@ function main() {
|
|
|
615
73
|
const command = parsed.positional[0] || '';
|
|
616
74
|
const options = parsed.options;
|
|
617
75
|
|
|
618
|
-
if (
|
|
76
|
+
if (command === '--help' || command === '-h' || (!command && hasFlag(options, '--help'))) {
|
|
619
77
|
printMainHelp();
|
|
620
78
|
process.exit(0);
|
|
621
79
|
}
|
|
622
80
|
|
|
623
|
-
|
|
624
|
-
if (command === 'preflight') {
|
|
625
|
-
printPreflightHelp();
|
|
626
|
-
process.exit(0);
|
|
627
|
-
}
|
|
628
|
-
if (command === 'run-capture') {
|
|
629
|
-
printRunCaptureHelp();
|
|
630
|
-
process.exit(0);
|
|
631
|
-
}
|
|
632
|
-
if (command === 'summarize-log') {
|
|
633
|
-
printSummarizeHelp();
|
|
634
|
-
process.exit(0);
|
|
635
|
-
}
|
|
636
|
-
if (command === 'validate-commit-msg') {
|
|
637
|
-
printValidateHelp();
|
|
638
|
-
process.exit(0);
|
|
639
|
-
}
|
|
640
|
-
if (command === 'sync-instructions') {
|
|
641
|
-
printSyncInstructionsHelp();
|
|
642
|
-
process.exit(0);
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
if (command === 'preflight') {
|
|
647
|
-
runPreflight(options);
|
|
648
|
-
return;
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
if (command === 'run-capture') {
|
|
652
|
-
runCapture(options);
|
|
653
|
-
return;
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
if (command === 'summarize-log') {
|
|
657
|
-
runSummarize(options);
|
|
658
|
-
return;
|
|
659
|
-
}
|
|
81
|
+
const effectiveCommand = command || DEFAULT_COMMAND;
|
|
660
82
|
|
|
661
|
-
if (
|
|
662
|
-
|
|
663
|
-
|
|
83
|
+
if (hasFlag(options, '--help') || hasFlag(options, '-h')) {
|
|
84
|
+
printCommandHelp(effectiveCommand);
|
|
85
|
+
process.exit(0);
|
|
664
86
|
}
|
|
665
87
|
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
88
|
+
const entry = COMMANDS[effectiveCommand];
|
|
89
|
+
if (!entry) {
|
|
90
|
+
console.error(`Unknown command: ${command}`);
|
|
91
|
+
printMainHelp();
|
|
92
|
+
process.exit(2);
|
|
669
93
|
}
|
|
670
94
|
|
|
671
|
-
|
|
672
|
-
printMainHelp();
|
|
673
|
-
process.exit(2);
|
|
95
|
+
entry.run(options);
|
|
674
96
|
}
|
|
675
97
|
|
|
676
98
|
main();
|