@fitlab-ai/agent-infra 0.6.0 → 0.6.2-alpha.1

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 (93) hide show
  1. package/README.md +12 -12
  2. package/README.zh-CN.md +12 -12
  3. package/bin/cli.ts +5 -1
  4. package/dist/bin/cli.js +6 -1
  5. package/dist/lib/defaults.json +5 -4
  6. package/dist/lib/sandbox/config.js +25 -7
  7. package/dist/lib/sandbox/runtime-engines.js +27 -0
  8. package/dist/package.json +1 -1
  9. package/lib/defaults.json +5 -4
  10. package/lib/sandbox/config.ts +35 -7
  11. package/lib/sandbox/runtime-engines.ts +39 -0
  12. package/package.json +5 -3
  13. package/templates/.agents/README.en.md +8 -8
  14. package/templates/.agents/README.zh-CN.md +8 -8
  15. package/templates/{.claude → .agents}/hooks/check-version-format.sh +3 -3
  16. package/templates/.agents/rules/create-issue.github.en.md +6 -0
  17. package/templates/.agents/rules/create-issue.github.zh-CN.md +6 -0
  18. package/templates/.agents/rules/issue-fields.github.en.md +155 -0
  19. package/templates/.agents/rules/issue-fields.github.zh-CN.md +155 -0
  20. package/templates/.agents/rules/issue-pr-commands.github.en.md +1 -0
  21. package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +1 -0
  22. package/templates/.agents/rules/issue-sync.github.en.md +2 -1
  23. package/templates/.agents/rules/issue-sync.github.zh-CN.md +2 -1
  24. package/templates/.agents/rules/release-commands.github.en.md +9 -3
  25. package/templates/.agents/rules/release-commands.github.zh-CN.md +9 -3
  26. package/templates/.agents/rules/task-management.en.md +17 -9
  27. package/templates/.agents/rules/task-management.zh-CN.md +17 -9
  28. package/templates/.agents/rules/testing-discipline.en.md +40 -0
  29. package/templates/.agents/rules/testing-discipline.zh-CN.md +40 -0
  30. package/templates/.agents/rules/version-stamp.en.md +29 -0
  31. package/templates/.agents/rules/version-stamp.zh-CN.md +29 -0
  32. package/templates/.agents/scripts/platform-adapters/platform-sync.github.js +143 -6
  33. package/templates/.agents/scripts/validate-artifact.js +32 -5
  34. package/templates/.agents/skills/analyze-task/SKILL.en.md +3 -0
  35. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +3 -0
  36. package/templates/.agents/skills/analyze-task/config/verify.json +2 -0
  37. package/templates/.agents/skills/block-task/SKILL.en.md +3 -0
  38. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +3 -0
  39. package/templates/.agents/skills/block-task/config/verify.json +1 -0
  40. package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -0
  41. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -0
  42. package/templates/.agents/skills/cancel-task/config/verify.json +1 -0
  43. package/templates/.agents/skills/commit/SKILL.en.md +10 -0
  44. package/templates/.agents/skills/commit/SKILL.zh-CN.md +10 -0
  45. package/templates/.agents/skills/commit/config/verify.json +1 -0
  46. package/templates/.agents/skills/commit/reference/task-status-update.en.md +5 -0
  47. package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +5 -0
  48. package/templates/.agents/skills/complete-task/SKILL.en.md +4 -0
  49. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +4 -0
  50. package/templates/.agents/skills/complete-task/config/verify.json +2 -0
  51. package/templates/.agents/skills/create-pr/SKILL.en.md +5 -1
  52. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +5 -1
  53. package/templates/.agents/skills/create-pr/config/verify.json +1 -0
  54. package/templates/.agents/skills/create-release-note/SKILL.en.md +8 -11
  55. package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +8 -11
  56. package/templates/.agents/skills/create-task/SKILL.en.md +9 -0
  57. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +9 -0
  58. package/templates/.agents/skills/create-task/config/verify.json +1 -0
  59. package/templates/.agents/skills/implement-task/SKILL.en.md +16 -1
  60. package/templates/.agents/skills/implement-task/SKILL.zh-CN.md +16 -1
  61. package/templates/.agents/skills/implement-task/config/verify.json +2 -0
  62. package/templates/.agents/skills/import-codescan/config/verify.json +1 -0
  63. package/templates/.agents/skills/import-dependabot/config/verify.json +1 -0
  64. package/templates/.agents/skills/import-issue/SKILL.en.md +10 -0
  65. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +10 -0
  66. package/templates/.agents/skills/import-issue/config/verify.json +1 -0
  67. package/templates/.agents/skills/plan-task/SKILL.en.md +3 -0
  68. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +3 -0
  69. package/templates/.agents/skills/plan-task/config/verify.json +2 -0
  70. package/templates/.agents/skills/refine-task/SKILL.en.md +15 -1
  71. package/templates/.agents/skills/refine-task/SKILL.zh-CN.md +15 -1
  72. package/templates/.agents/skills/refine-task/config/verify.json +2 -0
  73. package/templates/.agents/skills/refine-task/reference/fix-workflow.en.md +9 -0
  74. package/templates/.agents/skills/refine-task/reference/fix-workflow.zh-CN.md +9 -0
  75. package/templates/.agents/skills/refine-task/reference/report-template.en.md +11 -0
  76. package/templates/.agents/skills/refine-task/reference/report-template.zh-CN.md +11 -0
  77. package/templates/.agents/skills/restore-task/SKILL.en.md +3 -0
  78. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +3 -0
  79. package/templates/.agents/skills/restore-task/config/verify.json +1 -0
  80. package/templates/.agents/skills/review-task/SKILL.en.md +16 -1
  81. package/templates/.agents/skills/review-task/SKILL.zh-CN.md +16 -1
  82. package/templates/.agents/skills/review-task/config/verify.json +3 -0
  83. package/templates/.agents/skills/review-task/reference/output-templates.en.md +20 -5
  84. package/templates/.agents/skills/review-task/reference/output-templates.zh-CN.md +20 -5
  85. package/templates/.agents/skills/review-task/reference/report-template.en.md +13 -0
  86. package/templates/.agents/skills/review-task/reference/report-template.zh-CN.md +13 -0
  87. package/templates/.agents/skills/review-task/reference/review-criteria.en.md +18 -0
  88. package/templates/.agents/skills/review-task/reference/review-criteria.zh-CN.md +18 -0
  89. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +5 -4
  90. package/templates/.agents/templates/task.en.md +5 -0
  91. package/templates/.agents/templates/task.zh-CN.md +5 -0
  92. package/templates/.claude/settings.json +1 -1
  93. package/templates/.codex/hooks.json +17 -0
package/README.md CHANGED
@@ -666,15 +666,15 @@ Use the top-level `.agents/.airc.json` `customTUIs` array when your team uses an
666
666
 
667
667
  | Field | Required | Meaning |
668
668
  |-------|----------|---------|
669
- | `name` | Yes | Display name shown in reports and next-step guidance, for example `Acme TUI`. |
670
- | `dir` | Yes | Command directory relative to the project root, for example `.acme/commands`. The path must stay inside the project root. |
669
+ | `name` | Yes | Display name shown in reports and next-step guidance, for example `<your-tui-name>`. |
670
+ | `dir` | Yes | Command directory relative to the project root, for example `.<your-tui>/commands`. The path must stay inside the project root. |
671
671
  | `invoke` | Yes | User-facing command template used in next-step guidance. |
672
672
 
673
673
  Supported `invoke` placeholders:
674
674
 
675
675
  | Placeholder | Replaced with | Example |
676
676
  |-------------|---------------|---------|
677
- | `${skillName}` | The skill command name, such as `review-task` or `commit`. | `acme ${skillName}` -> `acme review-task` |
677
+ | `${skillName}` | The skill command name, such as `review-task` or `commit`. | `<your-cli> ${skillName}` -> `<your-cli> review-task` |
678
678
  | `${projectName}` | The `.airc.json` `project` value. Use this for namespaced commands. | `/${projectName}:${skillName}` -> `/agent-infra:review-task` |
679
679
 
680
680
  Non-namespaced custom TUI:
@@ -683,9 +683,9 @@ Non-namespaced custom TUI:
683
683
  {
684
684
  "customTUIs": [
685
685
  {
686
- "name": "Acme TUI",
687
- "dir": ".acme/commands",
688
- "invoke": "acme ${skillName}"
686
+ "name": "<your-tui-name>",
687
+ "dir": ".<your-tui>/commands",
688
+ "invoke": "<your-cli> ${skillName}"
689
689
  }
690
690
  ]
691
691
  }
@@ -698,8 +698,8 @@ Namespaced custom TUI:
698
698
  "project": "agent-infra",
699
699
  "customTUIs": [
700
700
  {
701
- "name": "Internal Gemini",
702
- "dir": ".internal-gemini/commands",
701
+ "name": "<your-tui-name>",
702
+ "dir": ".<your-tui>/commands",
703
703
  "invoke": "/${projectName}:${skillName}"
704
704
  }
705
705
  ]
@@ -777,7 +777,7 @@ The generated `.agents/.airc.json` file is the central contract between the boot
777
777
  "project": "my-project",
778
778
  "org": "my-org",
779
779
  "language": "en",
780
- "templateVersion": "v0.6.0",
780
+ "templateVersion": "v0.6.1",
781
781
  "templates": {
782
782
  "sources": [
783
783
  { "type": "local", "path": "~/private-templates" }
@@ -790,9 +790,9 @@ The generated `.agents/.airc.json` file is the central contract between the boot
790
790
  },
791
791
  "customTUIs": [
792
792
  {
793
- "name": "Acme TUI",
794
- "dir": ".acme/commands",
795
- "invoke": "acme ${skillName}"
793
+ "name": "<your-tui-name>",
794
+ "dir": ".<your-tui>/commands",
795
+ "invoke": "<your-cli> ${skillName}"
796
796
  }
797
797
  ],
798
798
  "files": {
package/README.zh-CN.md CHANGED
@@ -642,15 +642,15 @@ args: "<task-id>" # 可选
642
642
 
643
643
  | 字段 | 必填 | 含义 |
644
644
  |------|------|------|
645
- | `name` | 是 | 报告和下一步提示中展示的工具名称,例如 `Acme TUI`。 |
646
- | `dir` | 是 | 相对项目根目录的命令目录,例如 `.acme/commands`。路径必须位于项目根目录内。 |
645
+ | `name` | 是 | 报告和下一步提示中展示的工具名称,例如 `<your-tui-name>`。 |
646
+ | `dir` | 是 | 相对项目根目录的命令目录,例如 `.<your-tui>/commands`。路径必须位于项目根目录内。 |
647
647
  | `invoke` | 是 | 面向用户展示的命令模板,用于生成下一步提示。 |
648
648
 
649
649
  `invoke` 支持的占位符:
650
650
 
651
651
  | 占位符 | 替换为 | 示例 |
652
652
  |--------|--------|------|
653
- | `${skillName}` | skill 命令名,例如 `review-task` 或 `commit`。 | `acme ${skillName}` -> `acme review-task` |
653
+ | `${skillName}` | skill 命令名,例如 `review-task` 或 `commit`。 | `<your-cli> ${skillName}` -> `<your-cli> review-task` |
654
654
  | `${projectName}` | `.airc.json` 中的 `project` 值,适用于带命名空间的命令。 | `/${projectName}:${skillName}` -> `/agent-infra:review-task` |
655
655
 
656
656
  不带命名空间的自定义 TUI:
@@ -659,9 +659,9 @@ args: "<task-id>" # 可选
659
659
  {
660
660
  "customTUIs": [
661
661
  {
662
- "name": "Acme TUI",
663
- "dir": ".acme/commands",
664
- "invoke": "acme ${skillName}"
662
+ "name": "<your-tui-name>",
663
+ "dir": ".<your-tui>/commands",
664
+ "invoke": "<your-cli> ${skillName}"
665
665
  }
666
666
  ]
667
667
  }
@@ -674,8 +674,8 @@ args: "<task-id>" # 可选
674
674
  "project": "agent-infra",
675
675
  "customTUIs": [
676
676
  {
677
- "name": "Internal Gemini",
678
- "dir": ".internal-gemini/commands",
677
+ "name": "<your-tui-name>",
678
+ "dir": ".<your-tui>/commands",
679
679
  "invoke": "/${projectName}:${skillName}"
680
680
  }
681
681
  ]
@@ -753,7 +753,7 @@ import-issue #42 从 GitHub Issue 导入任务
753
753
  "project": "my-project",
754
754
  "org": "my-org",
755
755
  "language": "en",
756
- "templateVersion": "v0.6.0",
756
+ "templateVersion": "v0.6.1",
757
757
  "templates": {
758
758
  "sources": [
759
759
  { "type": "local", "path": "~/private-templates" }
@@ -766,9 +766,9 @@ import-issue #42 从 GitHub Issue 导入任务
766
766
  },
767
767
  "customTUIs": [
768
768
  {
769
- "name": "Acme TUI",
770
- "dir": ".acme/commands",
771
- "invoke": "acme ${skillName}"
769
+ "name": "<your-tui-name>",
770
+ "dir": ".<your-tui>/commands",
771
+ "invoke": "<your-cli> ${skillName}"
772
772
  }
773
773
  ],
774
774
  "files": {
package/bin/cli.ts CHANGED
@@ -97,7 +97,11 @@ switch (command) {
97
97
  break;
98
98
  }
99
99
  case 'version': {
100
- console.log(`agent-infra ${VERSION}`);
100
+ if (process.argv[3] === '--raw') {
101
+ console.log(VERSION);
102
+ } else {
103
+ console.log(`agent-infra ${VERSION}`);
104
+ }
101
105
  break;
102
106
  }
103
107
  case 'help':
package/dist/bin/cli.js CHANGED
@@ -100,7 +100,12 @@ switch (command) {
100
100
  break;
101
101
  }
102
102
  case 'version': {
103
- console.log(`agent-infra ${VERSION}`);
103
+ if (process.argv[3] === '--raw') {
104
+ console.log(VERSION);
105
+ }
106
+ else {
107
+ console.log(`agent-infra ${VERSION}`);
108
+ }
104
109
  break;
105
110
  }
106
111
  case 'help':
@@ -5,13 +5,13 @@
5
5
  "sandbox": {
6
6
  "engine": null,
7
7
  "runtimes": [
8
- "node20"
8
+ "node22"
9
9
  ],
10
10
  "tools": [
11
11
  "claude-code",
12
12
  "codex",
13
- "opencode",
14
- "gemini-cli"
13
+ "gemini-cli",
14
+ "opencode"
15
15
  ],
16
16
  "dockerfile": null,
17
17
  "vm": {
@@ -27,6 +27,7 @@
27
27
  "managed": [
28
28
  ".agents/QUICKSTART.md",
29
29
  ".agents/README.md",
30
+ ".agents/hooks/",
30
31
  ".agents/rules/",
31
32
  ".agents/scripts/",
32
33
  ".agents/skills/",
@@ -34,7 +35,7 @@
34
35
  ".agents/workflows/",
35
36
  ".agents/workspace/README.md",
36
37
  ".claude/commands/",
37
- ".claude/hooks/",
38
+ ".codex/hooks.json",
38
39
  ".gemini/commands/",
39
40
  ".git-hooks/check-version-format.sh",
40
41
  ".github/scripts/",
@@ -2,12 +2,14 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { homedir, platform } from 'node:os';
4
4
  import { execFileSync } from 'node:child_process';
5
+ import pc from 'picocolors';
5
6
  import { validateSandboxEngine } from "./engine.js";
6
7
  import { hostJoin } from "./engines/wsl2-paths.js";
8
+ import { findRuntimeEngineMismatches } from "./runtime-engines.js";
7
9
  const DEFAULTS = Object.freeze({
8
10
  engine: null,
9
- runtimes: ['node20'],
10
- tools: ['claude-code', 'codex', 'opencode', 'gemini-cli'],
11
+ runtimes: ['node22'],
12
+ tools: ['claude-code', 'codex', 'gemini-cli', 'opencode'],
11
13
  dockerfile: null,
12
14
  vm: {
13
15
  cpu: null,
@@ -38,7 +40,7 @@ function cloneDefaults() {
38
40
  vm: { ...DEFAULTS.vm }
39
41
  };
40
42
  }
41
- export function loadConfig({ platformFn = platform } = {}) {
43
+ export function loadConfig({ platformFn = platform, writeStderr = (chunk) => process.stderr.write(chunk) } = {}) {
42
44
  const repoRoot = detectRepoRoot();
43
45
  const home = homedir();
44
46
  if (!home) {
@@ -56,6 +58,24 @@ export function loadConfig({ platformFn = platform } = {}) {
56
58
  if (!project || typeof project !== 'string') {
57
59
  throw new Error('sandbox: .agents/.airc.json is missing a valid "project" field');
58
60
  }
61
+ const runtimes = Array.isArray(sandbox.runtimes) && sandbox.runtimes.length > 0
62
+ ? [...sandbox.runtimes]
63
+ : defaults.runtimes;
64
+ const dockerfile = typeof sandbox.dockerfile === 'string' ? sandbox.dockerfile : defaults.dockerfile ?? null;
65
+ if (!dockerfile) {
66
+ let enginesNode;
67
+ try {
68
+ const pkg = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8'));
69
+ enginesNode = typeof pkg.engines?.node === 'string' ? pkg.engines.node : undefined;
70
+ }
71
+ catch {
72
+ enginesNode = undefined;
73
+ }
74
+ for (const { runtimes: invalidRuntimes, enginesNode: range } of findRuntimeEngineMismatches(runtimes, enginesNode)) {
75
+ writeStderr(pc.yellow(`Warning: sandbox runtimes ${invalidRuntimes.map((runtime) => `"${runtime}"`).join(', ')} do not satisfy this project's package.json "engines.node" ("${range}").\n` +
76
+ ' Update "sandbox.runtimes" in .agents/.airc.json (e.g. "node22"), or relax "engines.node".\n'));
77
+ }
78
+ }
59
79
  return {
60
80
  repoRoot,
61
81
  configPath,
@@ -68,13 +88,11 @@ export function loadConfig({ platformFn = platform } = {}) {
68
88
  shareBase: hostJoin(home, '.agent-infra', 'share', project),
69
89
  dotfilesDir: hostJoin(home, '.agent-infra', 'dotfiles'),
70
90
  engine,
71
- runtimes: Array.isArray(sandbox.runtimes) && sandbox.runtimes.length > 0
72
- ? [...sandbox.runtimes]
73
- : defaults.runtimes,
91
+ runtimes,
74
92
  tools: Array.isArray(sandbox.tools) && sandbox.tools.length > 0
75
93
  ? [...sandbox.tools]
76
94
  : defaults.tools,
77
- dockerfile: typeof sandbox.dockerfile === 'string' ? sandbox.dockerfile : defaults.dockerfile ?? null,
95
+ dockerfile,
78
96
  vm: {
79
97
  cpu: asPositiveNumberOrNull(sandbox.vm?.cpu) ?? defaults.vm.cpu,
80
98
  memory: asPositiveNumberOrNull(sandbox.vm?.memory) ?? defaults.vm.memory,
@@ -0,0 +1,27 @@
1
+ import semver from 'semver';
2
+ function nodeMajor(runtime) {
3
+ const match = /^node(\d+)$/.exec(runtime);
4
+ return match ? Number(match[1]) : null;
5
+ }
6
+ export function findRuntimeEngineMismatches(runtimes, enginesNode) {
7
+ if (!enginesNode) {
8
+ return [];
9
+ }
10
+ const range = semver.validRange(enginesNode);
11
+ if (!range) {
12
+ return [];
13
+ }
14
+ const nodeRuntimes = [];
15
+ for (const runtime of runtimes) {
16
+ const major = nodeMajor(runtime);
17
+ if (major === null) {
18
+ continue;
19
+ }
20
+ nodeRuntimes.push(runtime);
21
+ if (semver.intersects(`${major}.x`, range)) {
22
+ return [];
23
+ }
24
+ }
25
+ return nodeRuntimes.length > 0 ? [{ runtimes: nodeRuntimes, enginesNode }] : [];
26
+ }
27
+ //# sourceMappingURL=runtime-engines.js.map
package/dist/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@fitlab-ai/agent-infra",
3
- "version": "0.6.0",
3
+ "version": "0.6.2-alpha.1",
4
4
  "type": "module"
5
5
  }
package/lib/defaults.json CHANGED
@@ -5,13 +5,13 @@
5
5
  "sandbox": {
6
6
  "engine": null,
7
7
  "runtimes": [
8
- "node20"
8
+ "node22"
9
9
  ],
10
10
  "tools": [
11
11
  "claude-code",
12
12
  "codex",
13
- "opencode",
14
- "gemini-cli"
13
+ "gemini-cli",
14
+ "opencode"
15
15
  ],
16
16
  "dockerfile": null,
17
17
  "vm": {
@@ -27,6 +27,7 @@
27
27
  "managed": [
28
28
  ".agents/QUICKSTART.md",
29
29
  ".agents/README.md",
30
+ ".agents/hooks/",
30
31
  ".agents/rules/",
31
32
  ".agents/scripts/",
32
33
  ".agents/skills/",
@@ -34,7 +35,7 @@
34
35
  ".agents/workflows/",
35
36
  ".agents/workspace/README.md",
36
37
  ".claude/commands/",
37
- ".claude/hooks/",
38
+ ".codex/hooks.json",
38
39
  ".gemini/commands/",
39
40
  ".git-hooks/check-version-format.sh",
40
41
  ".github/scripts/",
@@ -2,13 +2,15 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { homedir, platform } from 'node:os';
4
4
  import { execFileSync } from 'node:child_process';
5
+ import pc from 'picocolors';
5
6
  import { validateSandboxEngine } from './engine.ts';
6
7
  import { hostJoin } from './engines/wsl2-paths.ts';
8
+ import { findRuntimeEngineMismatches } from './runtime-engines.ts';
7
9
 
8
10
  const DEFAULTS = Object.freeze({
9
11
  engine: null,
10
- runtimes: ['node20'],
11
- tools: ['claude-code', 'codex', 'opencode', 'gemini-cli'],
12
+ runtimes: ['node22'],
13
+ tools: ['claude-code', 'codex', 'gemini-cli', 'opencode'],
12
14
  dockerfile: null,
13
15
  vm: {
14
16
  cpu: null,
@@ -18,6 +20,7 @@ const DEFAULTS = Object.freeze({
18
20
  });
19
21
 
20
22
  type PlatformFn = typeof platform;
23
+ type WriteStderr = (chunk: string) => unknown;
21
24
 
22
25
  type SandboxConfigInput = {
23
26
  engine?: string | null;
@@ -82,7 +85,10 @@ function cloneDefaults(): SandboxConfigInput & { vm: SandboxVmConfig; runtimes:
82
85
  };
83
86
  }
84
87
 
85
- export function loadConfig({ platformFn = platform }: { platformFn?: PlatformFn } = {}): SandboxConfig {
88
+ export function loadConfig({
89
+ platformFn = platform,
90
+ writeStderr = (chunk) => process.stderr.write(chunk)
91
+ }: { platformFn?: PlatformFn; writeStderr?: WriteStderr } = {}): SandboxConfig {
86
92
  const repoRoot = detectRepoRoot();
87
93
  const home = homedir();
88
94
 
@@ -105,6 +111,30 @@ export function loadConfig({ platformFn = platform }: { platformFn?: PlatformFn
105
111
  throw new Error('sandbox: .agents/.airc.json is missing a valid "project" field');
106
112
  }
107
113
 
114
+ const runtimes = Array.isArray(sandbox.runtimes) && sandbox.runtimes.length > 0
115
+ ? [...sandbox.runtimes]
116
+ : defaults.runtimes;
117
+ const dockerfile = typeof sandbox.dockerfile === 'string' ? sandbox.dockerfile : defaults.dockerfile ?? null;
118
+
119
+ if (!dockerfile) {
120
+ let enginesNode: string | undefined;
121
+ try {
122
+ const pkg = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8')) as {
123
+ engines?: { node?: unknown };
124
+ };
125
+ enginesNode = typeof pkg.engines?.node === 'string' ? pkg.engines.node : undefined;
126
+ } catch {
127
+ enginesNode = undefined;
128
+ }
129
+
130
+ for (const { runtimes: invalidRuntimes, enginesNode: range } of findRuntimeEngineMismatches(runtimes, enginesNode)) {
131
+ writeStderr(pc.yellow(
132
+ `Warning: sandbox runtimes ${invalidRuntimes.map((runtime) => `"${runtime}"`).join(', ')} do not satisfy this project's package.json "engines.node" ("${range}").\n` +
133
+ ' Update "sandbox.runtimes" in .agents/.airc.json (e.g. "node22"), or relax "engines.node".\n'
134
+ ));
135
+ }
136
+ }
137
+
108
138
  return {
109
139
  repoRoot,
110
140
  configPath,
@@ -117,13 +147,11 @@ export function loadConfig({ platformFn = platform }: { platformFn?: PlatformFn
117
147
  shareBase: hostJoin(home, '.agent-infra', 'share', project),
118
148
  dotfilesDir: hostJoin(home, '.agent-infra', 'dotfiles'),
119
149
  engine,
120
- runtimes: Array.isArray(sandbox.runtimes) && sandbox.runtimes.length > 0
121
- ? [...sandbox.runtimes]
122
- : defaults.runtimes,
150
+ runtimes,
123
151
  tools: Array.isArray(sandbox.tools) && sandbox.tools.length > 0
124
152
  ? [...sandbox.tools]
125
153
  : defaults.tools,
126
- dockerfile: typeof sandbox.dockerfile === 'string' ? sandbox.dockerfile : defaults.dockerfile ?? null,
154
+ dockerfile,
127
155
  vm: {
128
156
  cpu: asPositiveNumberOrNull(sandbox.vm?.cpu) ?? defaults.vm.cpu,
129
157
  memory: asPositiveNumberOrNull(sandbox.vm?.memory) ?? defaults.vm.memory,
@@ -0,0 +1,39 @@
1
+ import semver from 'semver';
2
+
3
+ export type RuntimeEngineMismatch = {
4
+ runtimes: string[];
5
+ enginesNode: string;
6
+ };
7
+
8
+ function nodeMajor(runtime: string): number | null {
9
+ const match = /^node(\d+)$/.exec(runtime);
10
+ return match ? Number(match[1]) : null;
11
+ }
12
+
13
+ export function findRuntimeEngineMismatches(
14
+ runtimes: string[],
15
+ enginesNode: string | undefined
16
+ ): RuntimeEngineMismatch[] {
17
+ if (!enginesNode) {
18
+ return [];
19
+ }
20
+
21
+ const range = semver.validRange(enginesNode);
22
+ if (!range) {
23
+ return [];
24
+ }
25
+
26
+ const nodeRuntimes: string[] = [];
27
+ for (const runtime of runtimes) {
28
+ const major = nodeMajor(runtime);
29
+ if (major === null) {
30
+ continue;
31
+ }
32
+ nodeRuntimes.push(runtime);
33
+ if (semver.intersects(`${major}.x`, range)) {
34
+ return [];
35
+ }
36
+ }
37
+
38
+ return nodeRuntimes.length > 0 ? [{ runtimes: nodeRuntimes, enginesNode }] : [];
39
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fitlab-ai/agent-infra",
3
- "version": "0.6.0",
3
+ "version": "0.6.2-alpha.1",
4
4
  "description": "Bootstrap tool for AI multi-tool collaboration infrastructure — works with Claude Code, Codex, Gemini CLI, and OpenCode",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -46,6 +46,7 @@
46
46
  "@clack/prompts": "1.4.0",
47
47
  "cross-spawn": "^7.0.6",
48
48
  "picocolors": "1.1.1",
49
+ "semver": "^7.8.1",
49
50
  "smol-toml": "^1.6.1"
50
51
  },
51
52
  "scripts": {
@@ -60,7 +61,8 @@
60
61
  },
61
62
  "devDependencies": {
62
63
  "@types/cross-spawn": "^6.0.6",
63
- "@types/node": "^22.19.19",
64
- "typescript": "~5.9"
64
+ "@types/node": "^25.9.1",
65
+ "@types/semver": "^7.7.1",
66
+ "typescript": "~6.0"
65
67
  }
66
68
  }
@@ -199,15 +199,15 @@ Use the top-level `.agents/.airc.json` `customTUIs` array when your team uses an
199
199
 
200
200
  | Field | Required | Meaning |
201
201
  |-------|----------|---------|
202
- | `name` | Yes | Display name shown in reports and next-step guidance, for example `Acme TUI`. |
203
- | `dir` | Yes | Command directory relative to the project root, for example `.acme/commands`. The path must stay inside the project root. |
202
+ | `name` | Yes | Display name shown in reports and next-step guidance, for example `<your-tui-name>`. |
203
+ | `dir` | Yes | Command directory relative to the project root, for example `.<your-tui>/commands`. The path must stay inside the project root. |
204
204
  | `invoke` | Yes | User-facing command template used in next-step guidance. |
205
205
 
206
206
  Supported `invoke` placeholders:
207
207
 
208
208
  | Placeholder | Replaced with | Example |
209
209
  |-------------|---------------|---------|
210
- | `${skillName}` | The skill command name, such as `review-task` or `commit`. | `acme ${skillName}` -> `acme review-task` |
210
+ | `${skillName}` | The skill command name, such as `review-task` or `commit`. | `<your-cli> ${skillName}` -> `<your-cli> review-task` |
211
211
  | `${projectName}` | The `.airc.json` `project` value. Use this for namespaced commands. | `/${projectName}:${skillName}` -> `/agent-infra:review-task` |
212
212
 
213
213
  Non-namespaced custom TUI:
@@ -216,9 +216,9 @@ Non-namespaced custom TUI:
216
216
  {
217
217
  "customTUIs": [
218
218
  {
219
- "name": "Acme TUI",
220
- "dir": ".acme/commands",
221
- "invoke": "acme ${skillName}"
219
+ "name": "<your-tui-name>",
220
+ "dir": ".<your-tui>/commands",
221
+ "invoke": "<your-cli> ${skillName}"
222
222
  }
223
223
  ]
224
224
  }
@@ -231,8 +231,8 @@ Namespaced custom TUI:
231
231
  "project": "agent-infra",
232
232
  "customTUIs": [
233
233
  {
234
- "name": "Internal Gemini",
235
- "dir": ".internal-gemini/commands",
234
+ "name": "<your-tui-name>",
235
+ "dir": ".<your-tui>/commands",
236
236
  "invoke": "/${projectName}:${skillName}"
237
237
  }
238
238
  ]
@@ -199,15 +199,15 @@ args: "<task-id>" # 可选
199
199
 
200
200
  | 字段 | 必填 | 含义 |
201
201
  |------|------|------|
202
- | `name` | 是 | 报告和下一步提示中展示的工具名称,例如 `Acme TUI`。 |
203
- | `dir` | 是 | 相对项目根目录的命令目录,例如 `.acme/commands`。路径必须位于项目根目录内。 |
202
+ | `name` | 是 | 报告和下一步提示中展示的工具名称,例如 `<your-tui-name>`。 |
203
+ | `dir` | 是 | 相对项目根目录的命令目录,例如 `.<your-tui>/commands`。路径必须位于项目根目录内。 |
204
204
  | `invoke` | 是 | 面向用户展示的命令模板,用于生成下一步提示。 |
205
205
 
206
206
  `invoke` 支持的占位符:
207
207
 
208
208
  | 占位符 | 替换为 | 示例 |
209
209
  |--------|--------|------|
210
- | `${skillName}` | skill 命令名,例如 `review-task` 或 `commit`。 | `acme ${skillName}` -> `acme review-task` |
210
+ | `${skillName}` | skill 命令名,例如 `review-task` 或 `commit`。 | `<your-cli> ${skillName}` -> `<your-cli> review-task` |
211
211
  | `${projectName}` | `.airc.json` 中的 `project` 值,适用于带命名空间的命令。 | `/${projectName}:${skillName}` -> `/agent-infra:review-task` |
212
212
 
213
213
  不带命名空间的自定义 TUI:
@@ -216,9 +216,9 @@ args: "<task-id>" # 可选
216
216
  {
217
217
  "customTUIs": [
218
218
  {
219
- "name": "Acme TUI",
220
- "dir": ".acme/commands",
221
- "invoke": "acme ${skillName}"
219
+ "name": "<your-tui-name>",
220
+ "dir": ".<your-tui>/commands",
221
+ "invoke": "<your-cli> ${skillName}"
222
222
  }
223
223
  ]
224
224
  }
@@ -231,8 +231,8 @@ args: "<task-id>" # 可选
231
231
  "project": "agent-infra",
232
232
  "customTUIs": [
233
233
  {
234
- "name": "Internal Gemini",
235
- "dir": ".internal-gemini/commands",
234
+ "name": "<your-tui-name>",
235
+ "dir": ".<your-tui>/commands",
236
236
  "invoke": "/${projectName}:${skillName}"
237
237
  }
238
238
  ]
@@ -10,7 +10,7 @@ hook_command=$(
10
10
  try {
11
11
  const payload = JSON.parse(Buffer.concat(chunks).toString());
12
12
  process.stdout.write(payload.tool_input && payload.tool_input.command || "");
13
- } catch (error) {
13
+ } catch {
14
14
  process.stdout.write("");
15
15
  }
16
16
  });
@@ -30,14 +30,14 @@ repo_root=$(
30
30
  )
31
31
 
32
32
  if sh "$repo_root/.git-hooks/check-version-format.sh"; then
33
- echo "Claude hook: version check passed."
33
+ echo "AI hook: version check passed."
34
34
  exit 0
35
35
  else
36
36
  status=$?
37
37
  fi
38
38
 
39
39
  if [ "$status" -eq 1 ]; then
40
- echo "Claude hook: blocking git commit (version format error)."
40
+ echo "AI hook: blocking git commit (version format error)." >&2
41
41
  exit 2
42
42
  fi
43
43
 
@@ -149,6 +149,12 @@ gh api "repos/$upstream_repo/issues/{issue-number}" -X PATCH \
149
149
 
150
150
  Failure is non-blocking.
151
151
 
152
+ ### 6.5 Set Issue Fields (Optional)
153
+
154
+ If `has_push=true`, read `.agents/rules/issue-fields.md` and follow Flow A to write any applicable non-empty `priority`, `effort`, `start_date`, and `target_date` values from `task.md`.
155
+
156
+ Field write failures are non-blocking.
157
+
152
158
  ### 7. Write Back task.md
153
159
 
154
160
  Update task.md:
@@ -149,6 +149,12 @@ gh api "repos/$upstream_repo/issues/{issue-number}" -X PATCH \
149
149
 
150
150
  设置失败不阻断流程。
151
151
 
152
+ ### 6.5 设置 Issue 字段(可选)
153
+
154
+ 如果 `has_push=true`,读取 `.agents/rules/issue-fields.md`,按流程 A 写入 `task.md` 中适用且非空的 `priority`、`effort`、`start_date` 和 `target_date`。
155
+
156
+ 字段写入失败不阻断流程。
157
+
152
158
  ### 7. 回写 task.md
153
159
 
154
160
  更新 task.md: