@fitlab-ai/agent-infra 0.5.6 → 0.5.7

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.
Files changed (100) hide show
  1. package/README.md +92 -4
  2. package/README.zh-CN.md +92 -4
  3. package/bin/cli.js +28 -4
  4. package/lib/defaults.json +1 -0
  5. package/lib/init.js +68 -4
  6. package/lib/prompt.js +28 -1
  7. package/lib/render.js +1 -1
  8. package/lib/sandbox/commands/rm.js +6 -4
  9. package/lib/sandbox/commands/vm.js +43 -16
  10. package/lib/sandbox/config.js +5 -0
  11. package/lib/sandbox/engine.js +125 -16
  12. package/lib/sandbox/task-resolver.js +13 -6
  13. package/package.json +2 -2
  14. package/templates/.agents/QUICKSTART.en.md +17 -0
  15. package/templates/.agents/QUICKSTART.zh-CN.md +17 -0
  16. package/templates/.agents/README.en.md +70 -1
  17. package/templates/.agents/README.zh-CN.md +70 -1
  18. package/templates/.agents/rules/issue-pr-commands.en.md +5 -0
  19. package/templates/.agents/rules/issue-pr-commands.zh-CN.md +5 -0
  20. package/templates/.agents/rules/issue-sync.en.md +5 -0
  21. package/templates/.agents/rules/issue-sync.zh-CN.md +5 -0
  22. package/templates/.agents/rules/label-milestone-setup.en.md +5 -0
  23. package/templates/.agents/rules/label-milestone-setup.zh-CN.md +5 -0
  24. package/templates/.agents/rules/milestone-inference.en.md +5 -0
  25. package/templates/.agents/rules/milestone-inference.zh-CN.md +5 -0
  26. package/templates/.agents/rules/pr-sync.en.md +5 -0
  27. package/templates/.agents/rules/pr-sync.zh-CN.md +5 -0
  28. package/templates/.agents/rules/release-commands.en.md +5 -0
  29. package/templates/.agents/rules/release-commands.zh-CN.md +5 -0
  30. package/templates/.agents/rules/security-alerts.en.md +5 -0
  31. package/templates/.agents/rules/security-alerts.zh-CN.md +5 -0
  32. package/templates/.agents/scripts/platform-adapters/platform-sync.js +6 -0
  33. package/templates/.agents/skills/analyze-task/SKILL.en.md +2 -2
  34. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +2 -2
  35. package/templates/.agents/skills/block-task/SKILL.en.md +1 -1
  36. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +1 -1
  37. package/templates/.agents/skills/cancel-task/SKILL.en.md +1 -1
  38. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +2 -2
  39. package/templates/.agents/skills/check-task/SKILL.en.md +1 -1
  40. package/templates/.agents/skills/check-task/SKILL.zh-CN.md +1 -1
  41. package/templates/.agents/skills/close-codescan/SKILL.en.md +1 -1
  42. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +1 -1
  43. package/templates/.agents/skills/close-dependabot/SKILL.en.md +1 -1
  44. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +1 -1
  45. package/templates/.agents/skills/commit/SKILL.en.md +1 -1
  46. package/templates/.agents/skills/commit/SKILL.zh-CN.md +1 -1
  47. package/templates/.agents/skills/create-issue/SKILL.en.md +2 -2
  48. package/templates/.agents/skills/create-issue/SKILL.zh-CN.md +2 -2
  49. package/templates/.agents/skills/create-pr/SKILL.en.md +1 -1
  50. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +1 -1
  51. package/templates/.agents/skills/create-release-note/SKILL.en.md +8 -1
  52. package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +8 -1
  53. package/templates/.agents/skills/create-task/SKILL.en.md +2 -2
  54. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +2 -2
  55. package/templates/.agents/skills/implement-task/SKILL.en.md +2 -2
  56. package/templates/.agents/skills/implement-task/SKILL.zh-CN.md +2 -2
  57. package/templates/.agents/skills/import-codescan/SKILL.en.md +2 -2
  58. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +2 -2
  59. package/templates/.agents/skills/import-dependabot/SKILL.en.md +2 -2
  60. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +2 -2
  61. package/templates/.agents/skills/import-issue/SKILL.en.md +2 -2
  62. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +2 -2
  63. package/templates/.agents/skills/init-labels/SKILL.en.md +1 -1
  64. package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +1 -1
  65. package/templates/.agents/skills/init-labels/scripts/init-labels.sh +6 -0
  66. package/templates/.agents/skills/init-milestones/SKILL.en.md +1 -1
  67. package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +1 -1
  68. package/templates/.agents/skills/init-milestones/scripts/init-milestones.sh +6 -0
  69. package/templates/.agents/skills/plan-task/SKILL.en.md +2 -2
  70. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +2 -2
  71. package/templates/.agents/skills/post-release/SKILL.en.md +95 -0
  72. package/templates/.agents/skills/post-release/SKILL.zh-CN.md +95 -0
  73. package/templates/.agents/skills/refine-task/SKILL.en.md +1 -1
  74. package/templates/.agents/skills/refine-task/SKILL.zh-CN.md +1 -1
  75. package/templates/.agents/skills/refine-title/SKILL.en.md +1 -1
  76. package/templates/.agents/skills/refine-title/SKILL.zh-CN.md +1 -1
  77. package/templates/.agents/skills/release/SKILL.en.md +6 -1
  78. package/templates/.agents/skills/release/SKILL.zh-CN.md +6 -1
  79. package/templates/.agents/skills/release/scripts/manage-milestones.sh +6 -0
  80. package/templates/.agents/skills/restore-task/SKILL.en.md +2 -2
  81. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +2 -2
  82. package/templates/.agents/skills/review-task/SKILL.en.md +2 -2
  83. package/templates/.agents/skills/review-task/SKILL.zh-CN.md +2 -2
  84. package/templates/.agents/skills/test/SKILL.en.md +1 -1
  85. package/templates/.agents/skills/test/SKILL.zh-CN.md +1 -1
  86. package/templates/.agents/skills/test-integration/SKILL.en.md +1 -1
  87. package/templates/.agents/skills/test-integration/SKILL.zh-CN.md +1 -1
  88. package/templates/.agents/skills/update-agent-infra/SKILL.en.md +10 -2
  89. package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +4 -2
  90. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +289 -12
  91. package/templates/.agents/skills/upgrade-dependency/SKILL.en.md +1 -1
  92. package/templates/.agents/skills/upgrade-dependency/SKILL.zh-CN.md +1 -1
  93. package/templates/.agents/templates/task.en.md +2 -2
  94. package/templates/.agents/templates/task.zh-CN.md +2 -2
  95. package/templates/.claude/commands/post-release.en.md +8 -0
  96. package/templates/.claude/commands/post-release.zh-CN.md +8 -0
  97. package/templates/.gemini/commands/_project_/post-release.en.toml +6 -0
  98. package/templates/.gemini/commands/_project_/post-release.zh-CN.toml +6 -0
  99. package/templates/.opencode/commands/post-release.en.md +9 -0
  100. package/templates/.opencode/commands/post-release.zh-CN.md +9 -0
@@ -24,6 +24,7 @@ const DEFAULTS = {
24
24
  "type": "github"
25
25
  },
26
26
  "sandbox": {
27
+ "engine": null,
27
28
  "runtimes": [
28
29
  "node20"
29
30
  ],
@@ -78,7 +79,7 @@ const DEFAULTS = {
78
79
  }
79
80
  };
80
81
 
81
- const INSTALLER_VERSION = "v0.5.6";
82
+ const INSTALLER_VERSION = "v0.5.7";
82
83
  const PACKAGE_NAME = '@fitlab-ai/agent-infra';
83
84
  // Add a new identifier here only after shipping matching .{platform}. template variants.
84
85
  const KNOWN_PLATFORMS = new Set(['github']);
@@ -86,6 +87,21 @@ const KNOWN_LANGUAGES = new Set(['en', 'zh-CN']);
86
87
 
87
88
  function norm(p) { return p.replace(/\\/g, '/'); }
88
89
 
90
+ function normDir(p) {
91
+ return norm(p).replace(/^\.\//, '').replace(/\/+$/, '');
92
+ }
93
+
94
+ function isInsideProject(projectRoot, relativePath) {
95
+ if (typeof relativePath !== 'string' || relativePath.trim() === '' || path.isAbsolute(relativePath)) {
96
+ return false;
97
+ }
98
+
99
+ const root = path.resolve(projectRoot);
100
+ const resolved = path.resolve(projectRoot, relativePath);
101
+ const rel = path.relative(root, resolved);
102
+ return rel !== '' && !rel.startsWith('..') && !path.isAbsolute(rel);
103
+ }
104
+
89
105
  function globMatch(pattern, filePath) {
90
106
  const p = norm(pattern), f = norm(filePath);
91
107
  const globstarDir = '__GLOBSTAR_DIR__';
@@ -192,17 +208,26 @@ function detectCustomSkills(projectRoot, templateSkillNames) {
192
208
  .sort((left, right) => left.dirName.localeCompare(right.dirName));
193
209
  }
194
210
 
195
- function isCustomProtected(targetPath, customSkills, project) {
211
+ function isCustomProtected(targetPath, customSkills, project, customTUICommandTargets) {
196
212
  const normalized = norm(targetPath);
197
213
 
198
214
  return customSkills.some(({ dirName }) => (
199
215
  normalized.startsWith(`.agents/skills/${dirName}/`) ||
200
216
  normalized === `.claude/commands/${dirName}.md` ||
201
217
  normalized === `.opencode/commands/${dirName}.md` ||
202
- normalized === '.gemini/commands/' + project + '/' + dirName + '.toml'
218
+ normalized === '.gemini/commands/' + project + '/' + dirName + '.toml' ||
219
+ customTUICommandTargets.has(normalized)
203
220
  ));
204
221
  }
205
222
 
223
+ function recordCustomTUISkipped(report, entry) {
224
+ report?.custom?.customTUIs?.skipped?.push(entry);
225
+ }
226
+
227
+ function recordCustomTUISkippedRef(report, entry) {
228
+ report?.custom?.customTUIs?.skippedRefs?.push(entry);
229
+ }
230
+
206
231
  function expandHome(inputPath) {
207
232
  if (inputPath === '~') return os.homedir();
208
233
  if (inputPath.startsWith('~/')) {
@@ -212,6 +237,81 @@ function expandHome(inputPath) {
212
237
  return path.resolve(inputPath);
213
238
  }
214
239
 
240
+ function mergeTemplateSources(baseRoot, sources, report) {
241
+ const sourceMap = new Map();
242
+ const sourceMeta = new Map();
243
+ const conflictsByRel = new Map();
244
+ const baseRels = walkDir(baseRoot).map((filePath) => norm(path.relative(baseRoot, filePath)));
245
+
246
+ for (const rel of baseRels) {
247
+ sourceMap.set(rel, baseRoot);
248
+ sourceMeta.set(rel, { type: 'builtin' });
249
+ }
250
+
251
+ const recordConflict = (rel, winner, ignored) => {
252
+ const existing = conflictsByRel.get(rel);
253
+ if (existing) {
254
+ existing.winner = winner;
255
+ existing.ignored.push(...ignored);
256
+ return;
257
+ }
258
+
259
+ const conflict = { rel, winner, ignored: [...ignored] };
260
+ conflictsByRel.set(rel, conflict);
261
+ report.templateSources.conflicts.push(conflict);
262
+ };
263
+
264
+ const templateSources = Array.isArray(sources) ? sources : [];
265
+ for (const [index, source] of templateSources.entries()) {
266
+ if (source?.type !== 'local') continue;
267
+ if (typeof source.path !== 'string' || source.path.trim() === '') {
268
+ report.templateSources.errors.push({
269
+ index,
270
+ type: String(source?.type || ''),
271
+ path: String(source?.path || ''),
272
+ reason: 'invalid path'
273
+ });
274
+ continue;
275
+ }
276
+
277
+ const srcDir = expandHome(source.path);
278
+ if (!fs.existsSync(srcDir) || !fs.statSync(srcDir).isDirectory()) {
279
+ report.templateSources.errors.push({
280
+ index,
281
+ type: source.type,
282
+ path: source.path,
283
+ reason: 'directory not found'
284
+ });
285
+ continue;
286
+ }
287
+
288
+ const extRels = walkDir(srcDir).map((filePath) => norm(path.relative(srcDir, filePath)));
289
+ const sourceInfo = { type: source.type, path: source.path };
290
+ for (const rel of extRels) {
291
+ const existing = sourceMeta.get(rel);
292
+ if (existing?.type === 'builtin') {
293
+ recordConflict(rel, existing, [sourceInfo]);
294
+ continue;
295
+ }
296
+
297
+ if (existing) {
298
+ recordConflict(rel, sourceInfo, [existing]);
299
+ }
300
+
301
+ sourceMap.set(rel, srcDir);
302
+ sourceMeta.set(rel, sourceInfo);
303
+ }
304
+
305
+ report.templateSources.loaded += 1;
306
+ report.templateSources.files += extRels.length;
307
+ }
308
+
309
+ return {
310
+ mergedRels: [...sourceMap.keys()],
311
+ sourceMap
312
+ };
313
+ }
314
+
215
315
  function writeIfChanged(projectRoot, targetPath, content, reportBucket) {
216
316
  const fullPath = path.join(projectRoot, targetPath);
217
317
  const exists = fs.existsSync(fullPath);
@@ -388,7 +488,154 @@ function generateOpenCodeCommand(skill, lang) {
388
488
  return `${lines.join('\n')}\n`;
389
489
  }
390
490
 
391
- function generateCustomCommands(projectRoot, customSkills, project, lang, report) {
491
+ function validateCustomTUIs(projectRoot, customTUIs, report) {
492
+ const tools = Array.isArray(customTUIs) ? customTUIs : [];
493
+ return tools
494
+ .map((tool, index) => {
495
+ if (typeof tool?.dir !== 'string' || tool.dir.trim() === '') {
496
+ recordCustomTUISkipped(report, {
497
+ index,
498
+ name: String(tool?.name || ''),
499
+ dir: String(tool?.dir || ''),
500
+ reason: 'invalid dir'
501
+ });
502
+ return null;
503
+ }
504
+
505
+ if (!isInsideProject(projectRoot, tool.dir)) {
506
+ recordCustomTUISkipped(report, {
507
+ index,
508
+ name: String(tool?.name || ''),
509
+ dir: tool.dir,
510
+ reason: 'dir must be a relative path inside the project root'
511
+ });
512
+ return null;
513
+ }
514
+
515
+ return { ...tool, index, dir: normDir(tool.dir) };
516
+ })
517
+ .filter(Boolean);
518
+ }
519
+
520
+ function customTUITargetPath(tool, refFile, refSkillName, skillName) {
521
+ const targetFile = refFile.includes(refSkillName)
522
+ ? refFile.replaceAll(refSkillName, skillName)
523
+ : `${skillName}${path.extname(refFile)}`;
524
+ return norm(path.join(tool.dir, targetFile));
525
+ }
526
+
527
+ function findCustomTUIReference(projectRoot, tool, templateSkillNames, report, logSkipped = false) {
528
+ const cmdDir = path.join(projectRoot, tool.dir);
529
+ if (!fs.existsSync(cmdDir) || !fs.statSync(cmdDir).isDirectory()) {
530
+ if (logSkipped) {
531
+ recordCustomTUISkipped(report, {
532
+ index: tool.index,
533
+ name: String(tool.name || ''),
534
+ dir: tool.dir,
535
+ reason: 'directory not found'
536
+ });
537
+ }
538
+ return null;
539
+ }
540
+
541
+ const cmdFiles = fs.readdirSync(cmdDir)
542
+ .filter((file) => fs.statSync(path.join(cmdDir, file)).isFile())
543
+ .sort((left, right) => left.localeCompare(right));
544
+ if (cmdFiles.length === 0) {
545
+ if (logSkipped) {
546
+ recordCustomTUISkipped(report, {
547
+ index: tool.index,
548
+ name: String(tool.name || ''),
549
+ dir: tool.dir,
550
+ reason: 'no command files'
551
+ });
552
+ }
553
+ return null;
554
+ }
555
+
556
+ let sawKnownSkillReference = false;
557
+
558
+ for (const file of cmdFiles) {
559
+ const content = fs.readFileSync(path.join(cmdDir, file), 'utf8');
560
+ const match = content.match(/\.agents\/skills\/([^/]+)\/SKILL\.md/);
561
+ if (!match) continue;
562
+
563
+ const skillName = match[1];
564
+ if (!templateSkillNames.has(skillName)) continue;
565
+
566
+ const skillMd = path.join(projectRoot, '.agents/skills', skillName, 'SKILL.md');
567
+ if (!fs.existsSync(skillMd)) continue;
568
+
569
+ const meta = parseSkillFrontmatter(skillMd);
570
+ if (!meta.description) continue;
571
+
572
+ sawKnownSkillReference = true;
573
+ if (!content.includes(meta.description)) {
574
+ if (logSkipped) {
575
+ recordCustomTUISkippedRef(report, {
576
+ index: tool.index,
577
+ name: String(tool.name || ''),
578
+ dir: tool.dir,
579
+ file,
580
+ skill: skillName,
581
+ reason: 'description not found in reference command file'
582
+ });
583
+ }
584
+ continue;
585
+ }
586
+
587
+ return { content, file, skillName, skillDesc: meta.description };
588
+ }
589
+
590
+ if (logSkipped) {
591
+ recordCustomTUISkipped(report, {
592
+ index: tool.index,
593
+ name: String(tool.name || ''),
594
+ dir: tool.dir,
595
+ reason: sawKnownSkillReference
596
+ ? 'no reference command file with matching description'
597
+ : 'no usable reference command file'
598
+ });
599
+ }
600
+
601
+ return null;
602
+ }
603
+
604
+ function buildCustomTUICommandTargets(projectRoot, customSkills, customTUIs, templateSkillNames) {
605
+ const targets = new Set();
606
+ for (const tool of customTUIs) {
607
+ const ref = findCustomTUIReference(projectRoot, tool, templateSkillNames, null, false);
608
+ if (!ref) continue;
609
+
610
+ for (const skill of customSkills) {
611
+ targets.add(customTUITargetPath(tool, ref.file, ref.skillName, skill.dirName));
612
+ }
613
+ }
614
+
615
+ return targets;
616
+ }
617
+
618
+ function learnAndGenerateCommands(projectRoot, customSkills, tool, templateSkillNames, report) {
619
+ const ref = findCustomTUIReference(projectRoot, tool, templateSkillNames, report, true);
620
+ if (!ref) return;
621
+
622
+ for (const skill of customSkills) {
623
+ const descToken = '__AGENT_INFRA_CUSTOM_SKILL_DESCRIPTION__';
624
+ const generated = ref.content
625
+ .replaceAll(ref.skillDesc, descToken)
626
+ .replaceAll(ref.skillName, skill.dirName)
627
+ .replaceAll(descToken, skill.description);
628
+
629
+ writeIfChanged(
630
+ projectRoot,
631
+ customTUITargetPath(tool, ref.file, ref.skillName, skill.dirName),
632
+ generated,
633
+ report.custom.commands
634
+ );
635
+ }
636
+ }
637
+
638
+ function generateCustomCommands(projectRoot, customSkills, project, lang, report, customTUIs, templateSkillNames) {
392
639
  for (const skill of customSkills) {
393
640
  writeIfChanged(
394
641
  projectRoot,
@@ -409,6 +656,11 @@ function generateCustomCommands(projectRoot, customSkills, project, lang, report
409
656
  report.custom.commands
410
657
  );
411
658
  }
659
+
660
+ const tools = Array.isArray(customTUIs) ? customTUIs : [];
661
+ for (const tool of tools) {
662
+ learnAndGenerateCommands(projectRoot, customSkills, tool, templateSkillNames, report);
663
+ }
412
664
  }
413
665
 
414
666
  function matchesAny(rel, patterns) {
@@ -698,6 +950,7 @@ function syncTemplates(projectRoot, templateRootOverride) {
698
950
 
699
951
  const { project, org, language: lang = 'en' } = cfg;
700
952
  const platformType = cfg.platform?.type || DEFAULTS.platform.type;
953
+ const customTUIsConfig = Array.isArray(cfg.customTUIs) ? cfg.customTUIs : [];
701
954
  const vars = { project, org };
702
955
  const templateSkillNames = listTemplateSkillNames(templateRoot);
703
956
  const protectedCustomSkills = detectCustomSkills(projectRoot, templateSkillNames);
@@ -710,6 +963,13 @@ function syncTemplates(projectRoot, templateRootOverride) {
710
963
  templateVersion: version,
711
964
  templateRoot: norm(templateRoot),
712
965
  registryAdded: [],
966
+ templateSources: {
967
+ configured: 0,
968
+ loaded: 0,
969
+ files: 0,
970
+ errors: [],
971
+ conflicts: []
972
+ },
713
973
  managed: { written: [], created: [], unchanged: [], skippedMerged: [], removed: [] },
714
974
  custom: {
715
975
  detected: [],
@@ -718,6 +978,7 @@ function syncTemplates(projectRoot, templateRootOverride) {
718
978
  unchanged: [],
719
979
  removed: [],
720
980
  sourceErrors: [],
981
+ customTUIs: { skipped: [], skippedRefs: [] },
721
982
  commands: { generated: [], updated: [], unchanged: [] }
722
983
  },
723
984
  ejected: { created: [], skipped: [] },
@@ -725,6 +986,13 @@ function syncTemplates(projectRoot, templateRootOverride) {
725
986
  configUpdated: false,
726
987
  selfUpdate: false
727
988
  };
989
+ const customTUIs = validateCustomTUIs(projectRoot, customTUIsConfig, report);
990
+ const customTUICommandTargets = buildCustomTUICommandTargets(
991
+ projectRoot,
992
+ protectedCustomSkills,
993
+ customTUIs,
994
+ templateSkillNames
995
+ );
728
996
 
729
997
  const known = new Set([...managed, ...merged, ...ejected]);
730
998
  for (const e of (DEFAULTS.files.managed || [])) {
@@ -734,7 +1002,10 @@ function syncTemplates(projectRoot, templateRootOverride) {
734
1002
  if (!known.has(e)) { merged.push(e); known.add(e); report.registryAdded.push({ entry: e, list: 'merged' }); }
735
1003
  }
736
1004
 
737
- const allRels = walkDir(templateRoot).map(f => norm(path.relative(templateRoot, f)));
1005
+ const templateSources = Array.isArray(cfg.templates?.sources) ? cfg.templates.sources : [];
1006
+ report.templateSources.configured = templateSources.length;
1007
+ const { mergedRels, sourceMap } = mergeTemplateSources(templateRoot, templateSources, report);
1008
+ const allRels = mergedRels;
738
1009
  const allSet = new Set(allRels);
739
1010
  for (const entry of managed) {
740
1011
  const isDir = entry.endsWith('/');
@@ -743,10 +1014,14 @@ function syncTemplates(projectRoot, templateRootOverride) {
743
1014
 
744
1015
  if (isDir) {
745
1016
  const dir = path.join(templateRoot, entry);
746
- if (!fs.existsSync(dir)) continue;
747
- entryRels = walkDir(dir).map(f => norm(path.relative(templateRoot, f)));
1017
+ const builtinRels = fs.existsSync(dir)
1018
+ ? walkDir(dir).map((filePath) => norm(path.relative(templateRoot, filePath)))
1019
+ : [];
1020
+ const prefix = norm(entry);
1021
+ const externalRels = allRels.filter((rel) => rel.startsWith(prefix) && !builtinRels.includes(rel));
1022
+ entryRels = [...builtinRels, ...externalRels];
1023
+ if (!entryRels.length) continue;
748
1024
  } else {
749
- entryRels = [];
750
1025
  entryRels = entryVariantRels(entry, allSet, platformType);
751
1026
  if (!entryRels.length) continue;
752
1027
  }
@@ -761,7 +1036,8 @@ function syncTemplates(projectRoot, templateRootOverride) {
761
1036
  continue;
762
1037
  }
763
1038
 
764
- const srcFull = path.join(templateRoot, src);
1039
+ const srcRoot = sourceMap.get(src) || templateRoot;
1040
+ const srcFull = path.join(srcRoot, src);
765
1041
  const dstFull = path.join(projectRoot, tgt);
766
1042
  const bin = isBinary(srcFull);
767
1043
  const content = bin
@@ -795,7 +1071,7 @@ function syncTemplates(projectRoot, templateRootOverride) {
795
1071
  for (const projFile of projFiles) {
796
1072
  if (expectedTargets.has(projFile)) continue;
797
1073
  if (projFile === configPathRel) continue;
798
- if (isCustomProtected(projFile, protectedCustomSkills, project)) continue;
1074
+ if (isCustomProtected(projFile, protectedCustomSkills, project, customTUICommandTargets)) continue;
799
1075
  if (matchesAny(projFile, merged) || matchesAny(projFile, ejected)) continue;
800
1076
 
801
1077
  fs.unlinkSync(path.join(projectRoot, projFile));
@@ -816,7 +1092,7 @@ function syncTemplates(projectRoot, templateRootOverride) {
816
1092
 
817
1093
  const customSkills = detectCustomSkills(projectRoot, templateSkillNames);
818
1094
  report.custom.detected = customSkills.map((skill) => skill.dirName);
819
- generateCustomCommands(projectRoot, customSkills, project, lang, report);
1095
+ generateCustomCommands(projectRoot, customSkills, project, lang, report, customTUIs, templateSkillNames);
820
1096
 
821
1097
  for (const entry of ejected) {
822
1098
  const dstFull = path.join(projectRoot, entry);
@@ -830,7 +1106,8 @@ function syncTemplates(projectRoot, templateRootOverride) {
830
1106
  const src = selected.get(target);
831
1107
  if (!src) continue;
832
1108
 
833
- const content = renderContent(fs.readFileSync(path.join(templateRoot, src), 'utf8'), vars);
1109
+ const srcRoot = sourceMap.get(src) || templateRoot;
1110
+ const content = renderContent(fs.readFileSync(path.join(srcRoot, src), 'utf8'), vars);
834
1111
  const dir = path.dirname(dstFull);
835
1112
  if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
836
1113
  fs.writeFileSync(dstFull, content);
@@ -61,7 +61,7 @@ Report:
61
61
 
62
62
  Suggest next step:
63
63
 
64
- > **IMPORTANT**: All TUI command formats listed below must be output in full. Do not show only the format for the current AI agent.
64
+ > **IMPORTANT**: All TUI command formats listed below must be output in full. Do not show only the format for the current AI agent. If `.agents/.airc.json` configures custom TUIs (via `customTUIs`), read each tool's `name` and `invoke`, then add the matching command line in the same format (`${skillName}` becomes the skill name and `${projectName}` becomes the project name).
65
65
 
66
66
  ```
67
67
  Next step - commit changes:
@@ -61,7 +61,7 @@ description: "升级项目依赖到新版本并验证"
61
61
 
62
62
  建议下一步:
63
63
 
64
- > **重要**:以下「下一步」中列出的所有 TUI 命令格式必须完整输出,不要只展示当前 AI 代理对应的格式。
64
+ > **重要**:以下「下一步」中列出的所有 TUI 命令格式必须完整输出,不要只展示当前 AI 代理对应的格式。如果 `.agents/.airc.json` 中配置了自定义 TUI(`customTUIs`),读取每个工具的 `name` 和 `invoke`,按同样格式补充对应命令行(`${skillName}` 替换为技能名,`${projectName}` 替换为项目名)。
65
65
 
66
66
  ```
67
67
  下一步 - 提交代码:
@@ -1,13 +1,13 @@
1
1
  ---
2
2
  id: task-XXX
3
3
  type: feature # feature | bugfix | refactor | docs | review
4
- branch: "" # <project>-<type>-<slug>
4
+ branch: # <project>-<type>-<slug>
5
5
  workflow: feature-development # feature-development | bug-fix | code-review | refactoring
6
6
  status: open # open | in-progress | review | blocked | completed
7
7
  created_at: YYYY-MM-DDTHH:mm:ss±HH:MM
8
8
  updated_at: YYYY-MM-DDTHH:mm:ss±HH:MM
9
9
  current_step: analysis # analysis | design | implementation | review | fix | commit
10
- assigned_to: "" # claude | codex | gemini | opencode | human
10
+ assigned_to: # claude | codex | gemini | opencode | human
11
11
  ---
12
12
 
13
13
  # Task: [Title]
@@ -1,13 +1,13 @@
1
1
  ---
2
2
  id: task-XXX
3
3
  type: feature # feature | bugfix | refactor | docs | review
4
- branch: "" # <project>-<type>-<slug>
4
+ branch: # <project>-<type>-<slug>
5
5
  workflow: feature-development # feature-development | bug-fix | code-review | refactoring
6
6
  status: open # open | in-progress | review | blocked | completed
7
7
  created_at: YYYY-MM-DDTHH:mm:ss±HH:MM
8
8
  updated_at: YYYY-MM-DDTHH:mm:ss±HH:MM
9
9
  current_step: analysis # analysis | design | implementation | review | fix | commit
10
- assigned_to: "" # claude | codex | gemini | opencode | human
10
+ assigned_to: # claude | codex | gemini | opencode | human
11
11
  ---
12
12
 
13
13
  # 任务:[标题]
@@ -0,0 +1,8 @@
1
+ ---
2
+ description: "Run post-release tasks"
3
+ disable-model-invocation: true
4
+ ---
5
+
6
+ Read and execute the post-release skill from `.agents/skills/post-release/SKILL.md`.
7
+
8
+ Follow all steps defined in the skill exactly.
@@ -0,0 +1,8 @@
1
+ ---
2
+ description: "执行版本发布后处理"
3
+ disable-model-invocation: true
4
+ ---
5
+
6
+ 读取并执行 `.agents/skills/post-release/SKILL.md` 中的 post-release 技能。
7
+
8
+ 严格按照技能中定义的所有步骤执行。
@@ -0,0 +1,6 @@
1
+ description = "Run post-release tasks"
2
+ prompt = """
3
+ Read and execute the post-release skill from `.agents/skills/post-release/SKILL.md`.
4
+
5
+ Follow all steps defined in the skill exactly.
6
+ """
@@ -0,0 +1,6 @@
1
+ description = "执行版本发布后处理"
2
+ prompt = """
3
+ 读取并执行 `.agents/skills/post-release/SKILL.md` 中的 post-release 技能。
4
+
5
+ 严格按照技能中定义的所有步骤执行。
6
+ """
@@ -0,0 +1,9 @@
1
+ ---
2
+ description: "Run post-release tasks"
3
+ agent: general
4
+ subtask: false
5
+ ---
6
+
7
+ Read and execute the post-release skill from `.agents/skills/post-release/SKILL.md`.
8
+
9
+ Follow all steps defined in the skill exactly.
@@ -0,0 +1,9 @@
1
+ ---
2
+ description: "执行版本发布后处理"
3
+ agent: general
4
+ subtask: false
5
+ ---
6
+
7
+ 读取并执行 `.agents/skills/post-release/SKILL.md` 中的 post-release 技能。
8
+
9
+ 严格按照技能中定义的所有步骤执行。