@produck/agent-toolkit 0.1.4 → 0.2.0
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 +62 -57
- package/bin/agent-toolkit.mjs +213 -18
- package/bin/build-publish-assets.mjs +105 -0
- package/package.json +5 -3
- package/publish-assets/instructions/produck/00-produck-base.instructions.md +331 -0
- package/publish-assets/instructions/produck/10-produck-node.instructions.md +152 -0
- package/publish-assets/instructions/produck/15-produck-workspace.instructions.md +293 -0
- package/publish-assets/instructions/produck/20-produck-commit.instructions.md +175 -0
- package/templates/help/sync-instructions.txt +7 -4
- package/templates/user-space-bootstrap.md +14 -0
- package/templates/default.instructions.md +0 -21
package/README.md
CHANGED
|
@@ -14,91 +14,95 @@ Central CLI toolkit for organization-level AI execution workflows.
|
|
|
14
14
|
|
|
15
15
|
Run preflight checks:
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
agent-toolkit preflight --cwd . --require package.json --ensure-dir logs
|
|
17
|
+
```
|
|
18
|
+
npm exec --package=@produck/agent-toolkit@latest -- agent-toolkit preflight --cwd . --require package.json --ensure-dir logs
|
|
19
|
+
```
|
|
19
20
|
|
|
20
21
|
Capture long output safely:
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
agent-toolkit run-capture --cwd . --cmd "npm run test" --out logs/test.log
|
|
23
|
+
```
|
|
24
|
+
npm exec --package=@produck/agent-toolkit@latest -- agent-toolkit run-capture --cwd . --cmd "npm run test" --out logs/test.log
|
|
25
|
+
```
|
|
24
26
|
|
|
25
27
|
Summarize captured output:
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
agent-toolkit summarize-log --file logs/test.log --match "FAIL|ERROR"
|
|
29
|
+
```
|
|
30
|
+
npm exec --package=@produck/agent-toolkit@latest -- agent-toolkit summarize-log --file logs/test.log --match "FAIL|ERROR"
|
|
31
|
+
```
|
|
29
32
|
|
|
30
33
|
Validate commit message format:
|
|
31
34
|
|
|
32
|
-
|
|
33
|
-
agent-toolkit validate-commit-msg --file .git/COMMIT_EDITMSG
|
|
35
|
+
```
|
|
36
|
+
npm exec --package=@produck/agent-toolkit@latest -- agent-toolkit validate-commit-msg --file .git/COMMIT_EDITMSG
|
|
37
|
+
```
|
|
34
38
|
|
|
35
|
-
Manual per-repository instruction distribution (write .instructions.md):
|
|
39
|
+
Manual per-repository instruction distribution (write .github/instructions/produck/\*.instructions.md):
|
|
36
40
|
|
|
37
|
-
|
|
38
|
-
agent-toolkit sync-instructions --cwd .
|
|
41
|
+
```
|
|
42
|
+
npm exec --package=@produck/agent-toolkit@latest -- agent-toolkit sync-instructions --cwd .
|
|
43
|
+
```
|
|
39
44
|
|
|
40
|
-
|
|
45
|
+
Legacy repository bootstrap behavior:
|
|
41
46
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
47
|
+
- If `.github/copilot-instructions.md` is missing, sync-instructions initializes it.
|
|
48
|
+
- The initialized file guides repository owners to keep organization baseline in
|
|
49
|
+
`.github/instructions/produck/*.instructions.md` and put local-only rules in
|
|
50
|
+
`.github/copilot-instructions.md`.
|
|
45
51
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
- `templates/default.instructions.md`
|
|
49
|
-
- `templates/help/*.txt`
|
|
52
|
+
Use organization source directory instead of built-in assets:
|
|
50
53
|
|
|
51
|
-
|
|
54
|
+
```
|
|
55
|
+
npm exec --package=@produck/agent-toolkit@latest -- agent-toolkit sync-instructions --cwd . --source path/to/org/.github/distribution/produck --force --prune
|
|
56
|
+
```
|
|
52
57
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
npm --workspace @produck/agent-toolkit run verify
|
|
56
|
-
npm --workspace @produck/agent-toolkit run pack:check
|
|
58
|
+
Built-in template location (for review and updates):
|
|
57
59
|
|
|
58
|
-
|
|
60
|
+
- `templates/user-space-bootstrap.md`
|
|
61
|
+
- `templates/help/*.txt`
|
|
59
62
|
|
|
60
|
-
|
|
63
|
+
Publish-time generated instruction assets:
|
|
61
64
|
|
|
62
|
-
|
|
65
|
+
- `publish-assets/instructions/produck/*.instructions.md`
|
|
66
|
+
- Generated from `.github/distribution/produck/*.instructions.md` via
|
|
67
|
+
`prepack`
|
|
68
|
+
- Included in npm package, ignored in git working tree
|
|
63
69
|
|
|
64
|
-
|
|
70
|
+
Downstream source maintenance in policy repository:
|
|
65
71
|
|
|
66
|
-
|
|
72
|
+
- Maintain source files directly under `.github/distribution/produck/*.instructions.md`
|
|
67
73
|
|
|
68
|
-
-
|
|
69
|
-
- action: dry-run or publish
|
|
70
|
-
- vcs mode: commit+tag / commit only / no commit+no tag
|
|
74
|
+
Organization-only instruction source (not published):
|
|
71
75
|
|
|
72
|
-
|
|
76
|
+
- `.github/instructions/produck/*.instructions.md`
|
|
73
77
|
|
|
74
|
-
|
|
75
|
-
- dry-run
|
|
76
|
-
- commit + tag
|
|
78
|
+
## Local verification
|
|
77
79
|
|
|
78
|
-
|
|
80
|
+
From repository root:
|
|
79
81
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
82
|
+
```bash
|
|
83
|
+
npm --workspace @produck/agent-toolkit run verify
|
|
84
|
+
npm --workspace @produck/agent-toolkit run pack:check
|
|
85
|
+
```
|
|
83
86
|
|
|
84
|
-
|
|
87
|
+
## Publishing
|
|
85
88
|
|
|
86
|
-
|
|
87
|
-
-
|
|
88
|
-
- `npm --workspace @produck/agent-toolkit run release -- patch --no-commit --no-tag`
|
|
89
|
+
Publishing is centralized at workspace root via lerna, not via
|
|
90
|
+
package-level release scripts.
|
|
89
91
|
|
|
90
|
-
|
|
92
|
+
From monorepo root (`produck/.github`):
|
|
91
93
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
```bash
|
|
95
|
+
npm run format:check
|
|
96
|
+
npm run test
|
|
97
|
+
npm run publish:dry-run
|
|
98
|
+
npm run publish
|
|
99
|
+
```
|
|
97
100
|
|
|
98
|
-
|
|
101
|
+
Notes:
|
|
99
102
|
|
|
100
|
-
|
|
101
|
-
|
|
103
|
+
- `publish:dry-run` validates package contents by running `npm pack --dry-run`
|
|
104
|
+
across non-private workspace packages.
|
|
105
|
+
- `publish` runs lerna publish flow from workspace root.
|
|
102
106
|
|
|
103
107
|
## GitHub workflow
|
|
104
108
|
|
|
@@ -108,19 +112,20 @@ Repository includes manual workflow:
|
|
|
108
112
|
|
|
109
113
|
Workflow behavior:
|
|
110
114
|
|
|
111
|
-
- Always runs verify
|
|
115
|
+
- Always runs verify and pack:check for `@produck/agent-toolkit`.
|
|
112
116
|
- Does not publish to npm.
|
|
113
|
-
- Used as release gate before
|
|
117
|
+
- Used as release gate before workspace-level publish.
|
|
114
118
|
|
|
115
119
|
Release policy:
|
|
116
120
|
|
|
117
121
|
- Default organization usage is @latest.
|
|
118
|
-
- Run
|
|
122
|
+
- Run format:check and test first, then workspace `publish:dry-run` before
|
|
123
|
+
`publish`.
|
|
119
124
|
- Keep rollback option by republishing previous stable version if needed.
|
|
120
125
|
|
|
121
126
|
Rollback quick steps:
|
|
122
127
|
|
|
123
128
|
1. Check latest published version:
|
|
124
129
|
`npm view @produck/agent-toolkit version`
|
|
125
|
-
2. Fix source and
|
|
130
|
+
2. Fix source and rerun workspace publish flow with a new version.
|
|
126
131
|
3. Push commit and tags.
|
package/bin/agent-toolkit.mjs
CHANGED
|
@@ -8,7 +8,12 @@ const ALLOWED_TAGS = ['INIT', 'ADD', 'REMOVE', 'FIX', 'REFACTOR', 'UPGRADE'];
|
|
|
8
8
|
const ALLOWED_TARGETS = ['docs', 'test', 'ci', 'deps', 'api', 'schema', 'infra'];
|
|
9
9
|
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
10
10
|
const TEMPLATE_ROOT = path.resolve(SCRIPT_DIR, '../templates');
|
|
11
|
-
const
|
|
11
|
+
const PUBLISH_ASSETS_ROOT = path.resolve(SCRIPT_DIR, '../publish-assets');
|
|
12
|
+
const PUBLISH_INSTRUCTIONS_ROOT = path.resolve(PUBLISH_ASSETS_ROOT, 'instructions');
|
|
13
|
+
const PUBLISH_NAMESPACE_ROOT = path.resolve(PUBLISH_INSTRUCTIONS_ROOT, 'produck');
|
|
14
|
+
const MANAGED_MARKER = '<!-- managed-by: @produck/agent-toolkit -->';
|
|
15
|
+
const DEFAULT_NAMESPACE_OUT_DIR = '.github/instructions/produck';
|
|
16
|
+
const USER_SPACE_ENTRYPOINT = '.github/copilot-instructions.md';
|
|
12
17
|
|
|
13
18
|
function loadTemplateFile(relativePath) {
|
|
14
19
|
const templatePath = path.resolve(TEMPLATE_ROOT, relativePath);
|
|
@@ -99,7 +104,63 @@ function printSyncInstructionsHelp() {
|
|
|
99
104
|
}
|
|
100
105
|
|
|
101
106
|
function loadDefaultInstructionsTemplate() {
|
|
102
|
-
|
|
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`);
|
|
103
164
|
if (!content.endsWith('\n')) {
|
|
104
165
|
content = `${content}\n`;
|
|
105
166
|
}
|
|
@@ -364,45 +425,166 @@ function runValidateCommitMsg(options) {
|
|
|
364
425
|
|
|
365
426
|
function runSyncInstructions(options) {
|
|
366
427
|
const cwd = path.resolve(getSingle(options, '--cwd', process.cwd()));
|
|
367
|
-
const outArg = getSingle(options, '--out',
|
|
428
|
+
const outArg = getSingle(options, '--out', DEFAULT_NAMESPACE_OUT_DIR);
|
|
368
429
|
const sourceArg = getSingle(options, '--source', '');
|
|
369
430
|
const force = hasFlag(options, '--force');
|
|
370
431
|
const dryRun = hasFlag(options, '--dry-run');
|
|
432
|
+
const prune = hasFlag(options, '--prune');
|
|
371
433
|
|
|
372
434
|
if (!fs.existsSync(cwd)) {
|
|
373
435
|
console.error(`CWD does not exist: ${cwd}`);
|
|
374
436
|
process.exit(2);
|
|
375
437
|
}
|
|
376
438
|
|
|
377
|
-
const
|
|
378
|
-
let
|
|
439
|
+
const defaults = loadDefaultInstructionsTemplate();
|
|
440
|
+
let sourceType = defaults.type;
|
|
441
|
+
let sourceResolved = defaults.sourcePath;
|
|
442
|
+
let entries = defaults.entries;
|
|
379
443
|
|
|
380
444
|
if (sourceArg) {
|
|
381
445
|
const sourcePath = path.resolve(cwd, sourceArg);
|
|
382
446
|
if (!fs.existsSync(sourcePath)) {
|
|
383
|
-
console.error(`Source
|
|
447
|
+
console.error(`Source path does not exist: ${sourcePath}`);
|
|
384
448
|
process.exit(2);
|
|
385
449
|
}
|
|
386
|
-
|
|
387
|
-
if (
|
|
388
|
-
|
|
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`);
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
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
|
+
}
|
|
389
540
|
}
|
|
390
541
|
}
|
|
391
542
|
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
|
|
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
|
+
}
|
|
395
550
|
console.error('Use --force to overwrite.');
|
|
396
551
|
process.exit(2);
|
|
397
552
|
}
|
|
398
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
|
+
|
|
399
570
|
const report = {
|
|
571
|
+
mode: 'directory',
|
|
400
572
|
cwd,
|
|
401
|
-
|
|
402
|
-
source:
|
|
403
|
-
|
|
404
|
-
overwritten: exists && force,
|
|
573
|
+
sourceType,
|
|
574
|
+
source: sourceResolved,
|
|
575
|
+
outDir,
|
|
405
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,
|
|
406
588
|
};
|
|
407
589
|
|
|
408
590
|
if (dryRun) {
|
|
@@ -410,8 +592,21 @@ function runSyncInstructions(options) {
|
|
|
410
592
|
process.exit(0);
|
|
411
593
|
}
|
|
412
594
|
|
|
413
|
-
fs.mkdirSync(
|
|
414
|
-
|
|
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
|
+
|
|
415
610
|
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
416
611
|
}
|
|
417
612
|
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const PACKAGE_ROOT = path.resolve(SCRIPT_DIR, '..');
|
|
8
|
+
const REPO_ROOT = path.resolve(PACKAGE_ROOT, '../..');
|
|
9
|
+
const SOURCE_DIR = path.resolve(REPO_ROOT, '.github/distribution/produck');
|
|
10
|
+
const OUTPUT_DIR = path.resolve(PACKAGE_ROOT, 'publish-assets/instructions/produck');
|
|
11
|
+
const LEGACY_OUTPUT_PATH = path.resolve(
|
|
12
|
+
PACKAGE_ROOT,
|
|
13
|
+
'publish-assets/instructions/org.instructions.md',
|
|
14
|
+
);
|
|
15
|
+
const MANAGED_MARKER = '<!-- managed-by: @produck/agent-toolkit -->';
|
|
16
|
+
|
|
17
|
+
function normalize(text) {
|
|
18
|
+
return text.replace(/\r\n/g, '\n').trimEnd() + '\n';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function readFrontmatter(text) {
|
|
22
|
+
const match = text.match(/^---\n([\s\S]*?)\n---\n/);
|
|
23
|
+
if (!match) {
|
|
24
|
+
return '';
|
|
25
|
+
}
|
|
26
|
+
return match[1];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function validateSourceFile(fileName, text) {
|
|
30
|
+
const frontmatter = readFrontmatter(text);
|
|
31
|
+
if (!frontmatter) {
|
|
32
|
+
throw new Error(`Missing frontmatter in source file: ${fileName}`);
|
|
33
|
+
}
|
|
34
|
+
if (!/^applyTo:\s*["'][^"']+["']\s*$/m.test(frontmatter)) {
|
|
35
|
+
throw new Error(`Missing applyTo in source file: ${fileName}`);
|
|
36
|
+
}
|
|
37
|
+
if (!text.includes(MANAGED_MARKER)) {
|
|
38
|
+
throw new Error(`Missing managed marker in source file: ${fileName}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function readSourceEntries() {
|
|
43
|
+
if (!fs.existsSync(SOURCE_DIR)) {
|
|
44
|
+
throw new Error(`Missing source directory: ${SOURCE_DIR}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const fileNames = fs
|
|
48
|
+
.readdirSync(SOURCE_DIR)
|
|
49
|
+
.filter((name) => name.endsWith('.instructions.md'))
|
|
50
|
+
.sort((a, b) => a.localeCompare(b));
|
|
51
|
+
|
|
52
|
+
if (fileNames.length === 0) {
|
|
53
|
+
throw new Error(`No source instruction files in: ${SOURCE_DIR}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return fileNames.map((fileName) => {
|
|
57
|
+
const sourcePath = path.resolve(SOURCE_DIR, fileName);
|
|
58
|
+
const text = normalize(fs.readFileSync(sourcePath, 'utf8'));
|
|
59
|
+
validateSourceFile(fileName, text);
|
|
60
|
+
return {
|
|
61
|
+
fileName,
|
|
62
|
+
sourcePath,
|
|
63
|
+
text,
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function cleanStaleManagedFiles(expectedNames) {
|
|
69
|
+
if (!fs.existsSync(OUTPUT_DIR)) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const existing = fs.readdirSync(OUTPUT_DIR).filter((name) => name.endsWith('.instructions.md'));
|
|
73
|
+
for (const name of existing) {
|
|
74
|
+
if (expectedNames.has(name)) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const filePath = path.resolve(OUTPUT_DIR, name);
|
|
78
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
79
|
+
if (content.includes(MANAGED_MARKER)) {
|
|
80
|
+
fs.unlinkSync(filePath);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function run() {
|
|
86
|
+
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
|
|
87
|
+
|
|
88
|
+
const sourceEntries = readSourceEntries();
|
|
89
|
+
const expectedNames = new Set(sourceEntries.map((entry) => entry.fileName));
|
|
90
|
+
|
|
91
|
+
for (const entry of sourceEntries) {
|
|
92
|
+
const outPath = path.resolve(OUTPUT_DIR, entry.fileName);
|
|
93
|
+
fs.writeFileSync(outPath, entry.text, 'utf8');
|
|
94
|
+
process.stdout.write(`Generated ${outPath} from ${entry.sourcePath}\n`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
cleanStaleManagedFiles(expectedNames);
|
|
98
|
+
|
|
99
|
+
if (fs.existsSync(LEGACY_OUTPUT_PATH)) {
|
|
100
|
+
fs.unlinkSync(LEGACY_OUTPUT_PATH);
|
|
101
|
+
process.stdout.write(`Removed legacy ${LEGACY_OUTPUT_PATH}\n`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
run();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@produck/agent-toolkit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Central CLI toolkit for organization AI execution workflows",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -12,12 +12,14 @@
|
|
|
12
12
|
"agent-toolkit": "bin/agent-toolkit.mjs"
|
|
13
13
|
},
|
|
14
14
|
"scripts": {
|
|
15
|
+
"prepack": "node ./bin/build-publish-assets.mjs",
|
|
15
16
|
"verify": "node ./bin/agent-toolkit.mjs --help && node ./bin/agent-toolkit.mjs preflight --cwd . --require package.json",
|
|
16
17
|
"pack:check": "npm pack --dry-run"
|
|
17
18
|
},
|
|
18
19
|
"files": [
|
|
19
20
|
"bin",
|
|
20
|
-
"templates"
|
|
21
|
+
"templates",
|
|
22
|
+
"publish-assets"
|
|
21
23
|
],
|
|
22
24
|
"publishConfig": {
|
|
23
25
|
"access": "public"
|
|
@@ -26,5 +28,5 @@
|
|
|
26
28
|
"node": ">=18.0.0"
|
|
27
29
|
},
|
|
28
30
|
"license": "MIT",
|
|
29
|
-
"gitHead": "
|
|
31
|
+
"gitHead": "8fbe9d26ada86a17e4f0444c2342e3904ba0f7f2"
|
|
30
32
|
}
|